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.
51 lines
1.7 KiB
TypeScript
51 lines
1.7 KiB
TypeScript
const send = (o: unknown) => process.stdout.write(`${JSON.stringify(o)}\n`);
|
|
|
|
const TOOLS = [
|
|
{
|
|
name: 'ping',
|
|
description: 'Returns pong plus whatever you send',
|
|
inputSchema: { type: 'object', properties: { note: { type: 'string' } }, required: ['note'] },
|
|
},
|
|
{
|
|
name: 'search',
|
|
description: 'Pretends to search',
|
|
inputSchema: { type: 'object', properties: { q: { type: 'string' } }, required: ['q'] },
|
|
},
|
|
];
|
|
|
|
const dec = new TextDecoder();
|
|
let buf = '';
|
|
|
|
for await (const chunk of Bun.stdin.stream()) {
|
|
buf += dec.decode(chunk);
|
|
let nl: number;
|
|
while ((nl = buf.indexOf('\n')) !== -1) {
|
|
const line = buf.slice(0, nl).trim();
|
|
buf = buf.slice(nl + 1);
|
|
if (!line) continue;
|
|
|
|
const msg = JSON.parse(line) as { id?: number; method: string; params?: Record<string, any> };
|
|
|
|
if (msg.method === 'initialize') {
|
|
send({
|
|
jsonrpc: '2.0',
|
|
id: msg.id,
|
|
result: {
|
|
protocolVersion: '2024-11-05',
|
|
capabilities: { tools: {} },
|
|
serverInfo: { name: 'stub-mcp', version: '0.0.1' },
|
|
},
|
|
});
|
|
} else if (msg.method === 'tools/list') {
|
|
send({ jsonrpc: '2.0', id: msg.id, result: { tools: TOOLS } });
|
|
} else if (msg.method === 'tools/call') {
|
|
const name = msg.params?.['name'];
|
|
const args = msg.params?.['arguments'] ?? {};
|
|
const text = name === 'ping' ? `pong: ${args['note']}` : `searched: ${args['q']}`;
|
|
send({ jsonrpc: '2.0', id: msg.id, result: { content: [{ type: 'text', text }] } });
|
|
} else if (msg.id !== undefined) {
|
|
send({ jsonrpc: '2.0', id: msg.id, error: { code: -32601, message: `no method ${msg.method}` } });
|
|
}
|
|
}
|
|
}
|