@nevescloud/claude-agent-driver
v0.1.1
Published
Transport-agnostic kernel for driving local Claude on your subscription via the Claude Agent SDK query(): option-builder + message→event normalizer.
Downloads
26
Maintainers
Readme
claude-agent-driver
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 | |---|---|---| |
session|system/init|sessionId| |text| streamedtext_delta(or non-streaming fallback) |text| |tool_use|assistant|id, name, input| |tool_result|user|id, name, result, isError| |result|result|text, 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.
Install
npm i @nevescloud/claude-agent-driverRequires the claude CLI installed and logged in (claude runs on your subscription; the SDK reads the same credentials).
Use
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).
API
| 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 |
License
MIT
