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 committed 2026-09-02 17:30:18 +07:00
commit 5b8503fcd9
93 files changed
+12775

No files matched your search

+145
View File
@@ -0,0 +1,145 @@
export type CommandAction =
| { type: 'none' }
| { type: 'prompt'; text: string }
| { type: 'exit' }
| { type: 'clear' }
| { type: 'compact' }
| { type: 'tools' }
| { type: 'cost' }
| { type: 'sessions' }
| { type: 'save' }
| { type: 'provider' }
| { type: 'models' }
| { type: 'init' }
| { type: 'context' }
| { type: 'todos' }
| { type: 'notes' }
| { type: 'skills' }
| { type: 'plugins' }
| { type: 'memory' }
| { type: 'agent'; agent?: string }
| { type: 'think'; level?: string }
| { type: 'info'; text: string }
| { type: 'model'; model: string }
| { type: 'resume'; id: string }
| { type: 'unknown'; name: string };
export type CommandSpec = {
name: string;
/** Extra names that resolve to the same command, hidden from the menu. */
aliases?: string[];
arg?: string;
summary: string;
};
/** Single source of truth for the menu, `/help`, and the parser. */
export const COMMANDS: CommandSpec[] = [
{ name: 'help', aliases: ['?'], summary: 'list these commands' },
{ name: 'agent', arg: '[name]', summary: 'switch agent: default, quick, deep, plan, review' },
{ name: 'think', arg: '[level]', summary: 'thinking level: off, low, medium, high, max' },
{ name: 'provider', aliases: ['login'], summary: 'set up a provider: pick, paste API key, choose model' },
{ name: 'models', summary: 'pick a model from the current provider' },
{ name: 'model', arg: '<id>', summary: 'switch model by name' },
{ name: 'skills', summary: 'list loaded skills' },
{ name: 'plugins', summary: 'list active plugins' },
{ name: 'init', summary: 'have the agent write AGENTS.md for this project' },
{ name: 'context', summary: 'show which instruction files are loaded' },
{ name: 'todos', summary: "show the agent's task list" },
{ name: 'notes', summary: 'show what the agent remembers about this project' },
{ name: 'memory', summary: 'compact the project memory with the model' },
{ name: 'tools', summary: 'list available tools' },
{ name: 'compact', summary: 'replace history with a model-written summary' },
{ name: 'cost', summary: 'tokens and estimated spend this session' },
{ name: 'sessions', summary: 'list saved sessions' },
{ name: 'resume', arg: '<id>', summary: 'load a saved session' },
{ name: 'save', summary: 'write the session to disk now' },
{ name: 'clear', summary: 'clear the transcript and history' },
{ name: 'exit', aliases: ['quit'], summary: 'quit' },
];
const usage = (c: CommandSpec) => `/${c.name}${c.arg ? ` ${c.arg}` : ''}`;
export const HELP = [
...COMMANDS.map((c) => `${usage(c).padEnd(18)} ${c.summary}`),
'',
'esc interrupt the running turn',
'tab complete the highlighted command',
'up / down recall earlier prompts',
].join('\n');
/**
* Commands whose name starts with the typed prefix, for the `/` menu.
* An exact name sorts first so pressing enter on `/model` cannot run `/models`.
* Aliases stay hidden to keep the list short.
*/
export function matchCommands(input: string): CommandSpec[] {
if (!input.startsWith('/')) return [];
const typed = input.slice(1).toLowerCase();
if (typed.includes(' ')) return [];
const hits = COMMANDS.filter((c) => c.name.startsWith(typed));
const exact = hits.findIndex((c) => c.name === typed);
return exact > 0 ? [hits[exact]!, ...hits.filter((_, i) => i !== exact)] : hits;
}
/** True while the input is a bare command name being typed, so the menu should show. */
export const isMenuOpen = (input: string) => input.startsWith('/') && !input.includes(' ');
/** Pure parser: no IO, so the TUI and headless mode share one definition. */
export function parseCommand(raw: string): CommandAction {
const input = raw.trim();
if (!input) return { type: 'none' };
if (!input.startsWith('/')) return { type: 'prompt', text: input };
const [name = '', ...rest] = input.slice(1).split(/\s+/);
const arg = rest.join(' ').trim();
switch (name) {
case 'help':
case '?':
return { type: 'info', text: HELP };
case 'exit':
case 'quit':
return { type: 'exit' };
case 'clear':
return { type: 'clear' };
case 'compact':
return { type: 'compact' };
case 'tools':
return { type: 'tools' };
case 'cost':
return { type: 'cost' };
case 'sessions':
return { type: 'sessions' };
case 'save':
return { type: 'save' };
case 'provider':
case 'login':
return { type: 'provider' };
case 'models':
return { type: 'models' };
case 'init':
return { type: 'init' };
case 'context':
return { type: 'context' };
case 'todos':
return { type: 'todos' };
case 'notes':
return { type: 'notes' };
case 'skills':
return { type: 'skills' };
case 'plugins':
return { type: 'plugins' };
case 'memory':
return { type: 'memory' };
case 'agent':
return arg ? { type: 'agent', agent: arg } : { type: 'agent' };
case 'think':
return arg ? { type: 'think', level: arg } : { type: 'think' };
case 'model':
return arg ? { type: 'model', model: arg } : { type: 'models' };
case 'resume':
return arg ? { type: 'resume', id: arg } : { type: 'info', text: 'usage: /resume <session-id>' };
default:
return { type: 'unknown', name };
}
}