A tiny, transport-agnostic kernel for driving the local Claude CLI on your subscription through the Claude Agent SDK's query() — no API key. You bring the transport (an HTTP server, a WebSocket, a CLI) and the turn-control (session pooling, single-shot); this owns the two pieces every integration ends up rewriting:
-
buildOptions(fields)→ assemble thequery()Options: the Claude Code preset prompt with yoursystemas an append (or a fully customsystemPrompt),mcpServers,allowedTools/disallowedTools,permissionMode,includePartialMessages. -
mapMessage(msg, state)→ normalize the SDK's message stream into a flat event stream:event from fields sessionsystem/initsessionIdtextstreamed text_delta(or non-streaming fallback)texttool_useassistantid, name, inputtool_resultuserid, name, result, isErrorresultresulttext, subtype, isError, sessionId, costUsd, numTurns, usage, durationMs, errors
That's it — pooling, resume, streaming-input queues, and abort/interrupt stay in your app, where they differ per transport.
npm i @nevescloud/claude-agent-driverRequires the claude CLI installed and logged in (claude runs on your subscription; the SDK reads the same credentials).
Single-turn (string prompt, no pooling) — streamTurn:
import { streamTurn } from '@nevescloud/claude-agent-driver';
let answer = '';
for await (const ev of streamTurn({
prompt: 'Summarize the repo README.',
system: 'Be concise.', // appended to the Claude Code preset
permissionMode: 'dontAsk', // run pre-approved tools, deny the rest, never prompt
mcpServers: { mytools: { type: 'http', url: 'http://127.0.0.1:8771/mcp' } },
allowedTools: ['mcp__mytools'],
})) {
if (ev.type === 'text') process.stdout.write(ev.text); // live typing
else if (ev.type === 'tool_result') console.log('\n[tool]', ev.name, ev.result);
else if (ev.type === 'result') answer = ev.text; // durable final text
}Pooled / streaming-input — assemble options and map messages around your own long-lived query() loop:
import { query, buildOptions, mapMessage, newState, userMessage } from '@nevescloud/claude-agent-driver';
const q = query({ prompt: myStreamingInput, options: buildOptions({ system, permissionMode: 'bypassPermissions' }) });
const state = newState();
for await (const msg of q) {
for (const ev of mapMessage(msg, state)) { /* ... */ }
}resolveClaudeBin() resolves the operator's own claude for version parity (pass to pathToClaudeCodeExecutable).
| export | what |
|---|---|
streamTurn({ prompt, ...optionFields, queryFn? }) |
single-turn async generator of normalized events |
buildOptions(fields) |
build the query() Options object |
mapMessage(msg, state) |
generator: one SDK message → zero or more flat events |
newState() |
per-turn state for mapMessage |
userMessage(text) |
the streaming-input user message shape |
resolveClaudeBin(envOverride?) |
resolve the claude binary path |
query |
re-export of the Agent SDK's query |
MIT