Initial commit: shiro-neko 0.1.0-beta.1

Agentic coding CLI on Bun, Ink, and the AI SDK.

Core: streamText loop with SDK-level tool approval so a denied call provably never executes; endpoint fallback for OpenAI reasoning models; retry with backoff.

Tools: read/write/edit/glob/grep/bash, path-jailed, gitignore-aware, ripgrep with a JS fallback, binary rejection, live bash streaming.

Agents: five variants crossing thinking level with tool restriction; plan and review withhold mutating tools from the model.

Extensibility: frontmatter skills with on-demand bodies, plugin host with blocking hooks, MCP stdio and HTTP, read-only subagents.

State: durable per-project memory, session task lists, session persistence, compaction that repairs provider-item dependencies.

Distribution: five-platform cross-compiled binaries with checksums, install scripts, CI on three operating systems.

404 tests, typecheck clean.
This commit is contained in:
Muhammad Zakir Ramadhan
2026-09-02 17:30:18 +07:00
commit 5b8503fcd9
93 changed files with 12775 additions and 0 deletions
+63
View File
@@ -0,0 +1,63 @@
# Installs shiro-neko from a GitHub release.
#
# irm https://raw.githubusercontent.com/zakirkun/shiro-neko/main/scripts/install.ps1 | iex
#
# Set $env:SHIRO_VERSION to pin a version, $env:SHIRO_INSTALL_DIR to change the target.
$ErrorActionPreference = 'Stop'
$repo = if ($env:SHIRO_REPO) { $env:SHIRO_REPO } else { 'zakirkun/shiro-neko' }
$installDir = if ($env:SHIRO_INSTALL_DIR) { $env:SHIRO_INSTALL_DIR } else { Join-Path $HOME '.bun\bin' }
if ([Environment]::Is64BitOperatingSystem -ne $true) {
Write-Error 'shiro: only 64-bit Windows is supported'
}
$asset = 'shiro-windows-x64.exe'
$base = if ($env:SHIRO_VERSION) {
"https://github.com/$repo/releases/download/v$($env:SHIRO_VERSION -replace '^v','')"
} else {
"https://github.com/$repo/releases/latest/download"
}
$tmp = Join-Path $env:TEMP "shiro-install-$(Get-Random)"
New-Item -ItemType Directory -Force -Path $tmp | Out-Null
try {
Write-Host "downloading $asset from $base"
$downloaded = Join-Path $tmp $asset
Invoke-WebRequest -Uri "$base/$asset" -OutFile $downloaded -UseBasicParsing
# Verify against the published checksums when they are available; a corrupted
# 90 MB download otherwise fails later as an unexplained crash.
try {
$sums = (Invoke-WebRequest -Uri "$base/SHA256SUMS" -UseBasicParsing).Content
$line = ($sums -split "`n" | Where-Object { $_ -match "\s$([regex]::Escape($asset))$" } | Select-Object -First 1)
if ($line) {
$expected = ($line -split '\s+')[0]
$actual = (Get-FileHash -Path $downloaded -Algorithm SHA256).Hash.ToLower()
if ($actual -ne $expected.ToLower()) {
Write-Error 'shiro: checksum mismatch, refusing to install'
}
}
} catch {
Write-Host 'no checksums published for this release, skipping verification'
}
New-Item -ItemType Directory -Force -Path $installDir | Out-Null
$target = Join-Path $installDir 'shiro.exe'
Move-Item -Force -Path $downloaded -Destination $target
Write-Host "installed $target"
& $target --version
$onPath = ($env:PATH -split ';' | Where-Object { $_ -and (Join-Path $_ '') -eq (Join-Path $installDir '') })
if ($onPath) {
Write-Host 'run: shiro'
} else {
Write-Host ''
Write-Host "$installDir is not on PATH. Add it:"
Write-Host " [Environment]::SetEnvironmentVariable('PATH', `"`$env:PATH;$installDir`", 'User')"
}
} finally {
Remove-Item -Recurse -Force $tmp -ErrorAction SilentlyContinue
}
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env sh
# Installs shiro-neko from a GitHub release.
#
# curl -fsSL https://raw.githubusercontent.com/zakirkun/shiro-neko/main/scripts/install.sh | sh
#
# Set SHIRO_VERSION to pin a version, SHIRO_INSTALL_DIR to change the target.
set -eu
REPO="${SHIRO_REPO:-zakirkun/shiro-neko}"
INSTALL_DIR="${SHIRO_INSTALL_DIR:-$HOME/.local/bin}"
case "$(uname -s)" in
Linux) os=linux ;;
Darwin) os=darwin ;;
*) echo "shiro: unsupported OS $(uname -s). Windows users: use scripts/install.ps1" >&2; exit 1 ;;
esac
case "$(uname -m)" in
x86_64 | amd64) arch=x64 ;;
arm64 | aarch64) arch=arm64 ;;
*) echo "shiro: unsupported architecture $(uname -m)" >&2; exit 1 ;;
esac
asset="shiro-${os}-${arch}"
if [ -n "${SHIRO_VERSION:-}" ]; then
tag="v${SHIRO_VERSION#v}"
base="https://github.com/${REPO}/releases/download/${tag}"
else
base="https://github.com/${REPO}/releases/latest/download"
fi
tmp="$(mktemp -d)"
trap 'rm -rf "$tmp"' EXIT
echo "downloading ${asset} from ${base}"
if ! curl -fSL --progress-bar "${base}/${asset}" -o "${tmp}/shiro"; then
echo "shiro: download failed. No build for ${os}-${arch} at that version?" >&2
exit 1
fi
# Verify against the published checksums when they are available; a corrupted
# 90 MB download otherwise fails later as an unexplained crash.
if curl -fsSL "${base}/SHA256SUMS" -o "${tmp}/SHA256SUMS" 2>/dev/null; then
expected="$(grep " ${asset}\$" "${tmp}/SHA256SUMS" | cut -d' ' -f1)"
if [ -n "$expected" ]; then
if command -v sha256sum >/dev/null 2>&1; then
actual="$(sha256sum "${tmp}/shiro" | cut -d' ' -f1)"
elif command -v shasum >/dev/null 2>&1; then
actual="$(shasum -a 256 "${tmp}/shiro" | cut -d' ' -f1)"
else
actual=""
fi
if [ -n "$actual" ] && [ "$actual" != "$expected" ]; then
echo "shiro: checksum mismatch, refusing to install" >&2
exit 1
fi
fi
fi
mkdir -p "$INSTALL_DIR"
chmod +x "${tmp}/shiro"
mv "${tmp}/shiro" "${INSTALL_DIR}/shiro"
echo "installed ${INSTALL_DIR}/shiro"
"${INSTALL_DIR}/shiro" --version || true
case ":${PATH}:" in
*":${INSTALL_DIR}:"*) echo "run: shiro" ;;
*) echo ""; echo "${INSTALL_DIR} is not on PATH. Add it:"; echo " export PATH=\"${INSTALL_DIR}:\$PATH\"" ;;
esac
+58
View File
@@ -0,0 +1,58 @@
import { chmodSync, mkdirSync } from 'node:fs';
import { homedir, platform } from 'node:os';
import { join } from 'node:path';
import { VERSION } from '../src/version';
/**
* Installs the compiled binary onto PATH.
*
* `bun link` is not usable here: it writes a shim that re-execs `bun`, so it fails
* on any machine where bun is installed without `bun.exe` on PATH (an npm install,
* for one). The compiled binary embeds its own runtime and has no such dependency.
*/
const isWindows = platform() === 'win32';
const exe = isWindows ? 'shiro.exe' : 'shiro';
const source = join(import.meta.dir, '..', 'dist', exe);
const target = (() => {
const explicit = process.env['SHIRO_INSTALL_DIR'];
if (explicit) return explicit;
const bunBin = join(homedir(), '.bun', 'bin');
return isWindows ? bunBin : join(homedir(), '.local', 'bin');
})();
const built = Bun.file(source);
if (!(await built.exists())) {
console.error(`shiro: ${source} not found. Run "bun run build" first.`);
process.exit(1);
}
mkdirSync(target, { recursive: true });
const dest = join(target, exe);
try {
await Bun.write(dest, built);
} catch (e) {
const message = e instanceof Error ? e.message : String(e);
console.error(`shiro: could not write ${dest}: ${message}`);
if (message.includes('EBUSY') || message.includes('EACCES') || message.includes('EPERM')) {
console.error('shiro: a running shiro may be holding the file. Close it and try again.');
}
process.exit(1);
}
if (!isWindows) chmodSync(dest, 0o755);
const onPath = (process.env['PATH'] ?? '').split(isWindows ? ';' : ':').some((p) => p && join(p) === join(target));
console.log(`installed shiro-neko ${VERSION} to ${dest} (${(built.size / 1024 / 1024) | 0} MB)`);
if (onPath) {
console.log('run: shiro');
} else {
console.log(`\n${target} is not on PATH. Add it:`);
console.log(
isWindows
? ` [Environment]::SetEnvironmentVariable('PATH', "$env:PATH;${target}", 'User')`
: ` export PATH="${target}:$PATH"`,
);
}
+100
View File
@@ -0,0 +1,100 @@
import { mkdirSync, rmSync } from 'node:fs';
import { join } from 'node:path';
import { VERSION } from '../src/version';
export type Target = {
/** Bun cross-compilation target. */
target: string;
/** Suffix in the artifact name, matching what the installers look for. */
name: string;
windows?: boolean;
};
export const TARGETS: Target[] = [
{ target: 'bun-linux-x64', name: 'linux-x64' },
{ target: 'bun-linux-arm64', name: 'linux-arm64' },
{ target: 'bun-darwin-x64', name: 'darwin-x64' },
{ target: 'bun-darwin-arm64', name: 'darwin-arm64' },
{ target: 'bun-windows-x64', name: 'windows-x64', windows: true },
];
const OUT = 'dist/release';
/**
* Builds one executable per platform.
*
* Bun cross-compiles from any host, so a single runner produces every artifact and
* no build matrix is needed. The version is compiled into the binary from
* src/version.ts; a release tag must agree with it or the build stops, because a
* binary reporting the wrong version is worse than a failed release.
*/
async function main(): Promise<void> {
const args = process.argv.slice(2);
const only = args.filter((a) => !a.startsWith('-'));
const wanted = only.length > 0 ? TARGETS.filter((t) => only.includes(t.name)) : TARGETS;
if (wanted.length === 0) {
console.error(`no matching target. Available: ${TARGETS.map((t) => t.name).join(', ')}`);
process.exit(1);
}
const pkg = (await Bun.file('package.json').json()) as { version?: string };
if (pkg.version !== VERSION) {
console.error(`version mismatch: package.json is ${pkg.version}, src/version.ts is ${VERSION}`);
process.exit(1);
}
const tag = (process.env['GITHUB_REF_NAME'] ?? '').replace(/^v/, '');
if (tag && tag !== VERSION) {
console.error(`tag v${tag} does not match src/version.ts (${VERSION}). Bump the version or retag.`);
process.exit(1);
}
rmSync(OUT, { recursive: true, force: true });
mkdirSync(OUT, { recursive: true });
const built: { file: string; bytes: number }[] = [];
for (const t of wanted) {
const base = `shiro-${t.name}`;
const outfile = join(OUT, base);
const buildArgs = ['build', '--compile', '--minify', `--target=${t.target}`, 'src/cli.tsx', '--outfile', outfile];
if (t.windows) {
buildArgs.push(
'--windows-title=shiro-neko',
'--windows-description=Agentic coding CLI',
`--windows-version=${VERSION.split('-')[0]}.0`,
);
}
const proc = Bun.spawn(['bun', ...buildArgs], { stdout: 'inherit', stderr: 'inherit' });
const code = await proc.exited;
if (code !== 0) {
console.error(`\nbuild failed for ${t.name} (exit ${code})`);
process.exit(code);
}
const file = t.windows ? `${base}.exe` : base;
const artifact = Bun.file(join(OUT, file));
if (!(await artifact.exists())) {
console.error(`\n${file} was not produced`);
process.exit(1);
}
built.push({ file, bytes: artifact.size });
}
// Checksums let an installer verify a download without a second request.
const sums: string[] = [];
for (const { file } of built) {
const bytes = new Uint8Array(await Bun.file(join(OUT, file)).arrayBuffer());
sums.push(`${new Bun.CryptoHasher('sha256').update(bytes).digest('hex')} ${file}`);
}
await Bun.write(join(OUT, 'SHA256SUMS'), `${sums.join('\n')}\n`);
console.log(`\nshiro-neko ${VERSION}`);
for (const { file, bytes } of built) console.log(` ${file.padEnd(26)} ${Math.round(bytes / 1024 / 1024)} MB`);
console.log(` ${'SHA256SUMS'.padEnd(26)} ${built.length} entries`);
}
// Guarded so a test can import TARGETS without triggering a five-platform build.
if (import.meta.main) await main();