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:
commit
5b8503fcd9
93 files changed
+12775
No files matched your search
+145
@@ -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 };
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user