@raylin01/claude-client
v0.3.3
Published
Node.js client for controlling Claude Code CLI via stream-json protocol.
Downloads
286
Maintainers
Readme
@raylin01/claude-client
Node.js client for controlling the Claude Code CLI with stream-json I/O.
The package now supports two layers:
ClaudeClient.init(...)for the structured, handle-based APInew ClaudeClient(...)for the lower-level event-driven transport API
Choosing An API
Use the structured API for most applications.
- Choose
ClaudeClient.init(...)when you want a higher-level SDK with per-turn handles, pushed updates, open request tracking, and turn history. - Choose
new ClaudeClient(...)withclient.on(...)when you already have an event-driven integration layer, need raw Claude protocol events, or want to stay close to the stream-json transport.
Both APIs remain supported. The structured API is built on top of the raw client rather than replacing it.
Install
npm install @raylin01/claude-clientRequirements
- Node.js 18+
- Claude CLI installed and authenticated (
claude login)
Quickstart
Structured Mode (Recommended)
Structured mode gives you a TurnHandle for each send call. You get pushed updates, a current snapshot, open request tracking, and final completion without manually rebuilding state from raw events.
import { ClaudeClient } from '@raylin01/claude-client';
const client = await ClaudeClient.init({
cwd: process.cwd(),
includePartialMessages: true,
permissionPromptTool: true
});
const turn = client.send('Summarize this project in one paragraph.');
for await (const update of turn.updates()) {
if (update.kind === 'output' && update.snapshot.currentOutputKind === 'text') {
process.stdout.write(`\r${update.snapshot.text}`);
}
for (const request of update.snapshot.openRequests) {
if (request.status !== 'open') continue;
if (request.kind === 'question') {
await client.answerQuestion(request.id, ['beta']);
} else if (request.kind === 'tool_approval') {
await client.approveRequest(request.id, {
message: 'Approved by README example.'
});
}
}
}
const finalSnapshot = await turn.done;
console.log('\nDone:', finalSnapshot.result?.subtype);
client.close();Structured mode exports these main capabilities:
client.send(input)returns a liveTurnHandleturn.updates()streams pushed updates for that turnturn.current()returns the latest snapshotclient.getOpenRequests()returns unresolved tool or question requestsclient.approveRequest(...),client.denyRequest(...), andclient.answerQuestion(...)respond at the structured levelclient.createQuestionSession(requestId)creates an incremental helper for multi-question promptsclient.getHistory()returns completed turn snapshots
Stream Mode (Default)
Persistent process with bidirectional JSON stream:
import { ClaudeClient } from '@raylin01/claude-client';
const client = new ClaudeClient({
cwd: process.cwd(),
debug: false
});
client.on('ready', () => {
client.sendMessage('Summarize this project.');
});
client.on('text_delta', (text) => {
process.stdout.write(text);
});
client.on('result', (result) => {
console.log('\nDone:', result.subtype);
});
await client.start();This raw event-driven pattern is still fully available and remains a good fit for repos that already normalize streaming and permissions in their own adapter layer.
Print Mode (One-shot)
Spawns a new process per message with session persistence via --session-id/--resume:
import { ClaudeClient } from '@raylin01/claude-client';
const client = new ClaudeClient({
cwd: process.cwd(),
printMode: true, // Enable print mode
model: 'claude-sonnet'
});
// No process spawned yet - ready fires immediately
await client.start();
client.on('text_delta', (text) => {
process.stdout.write(text);
});
client.on('result', (result) => {
console.log('\nSession ID:', client.sessionId);
});
// First message: uses --session-id <uuid>
await client.sendMessage('What is 2+2?');
// Subsequent messages: uses --resume <session-id>
await client.sendMessage('What was my previous question?');Print Mode with Custom Session ID
const client = new ClaudeClient({
cwd: process.cwd(),
printMode: true,
sessionId: 'my-custom-session-id', // Use your own session ID
printModeAutoSession: false // Disable auto-generation
});
await client.start();
await client.sendMessage('Hello!');Event Model
The event model below applies to the lower-level raw client created with new ClaudeClient(...).
ready: CLI process is readytext_delta: incremental assistant text outputthinking_delta: incremental thinking outputtext_accumulated: running total of text outputthinking_accumulated: running total of thinking outputmessage: full assistant message objecttool_use_start: tool execution started with parsed inputtool_result: tool execution resultcontrol_request: permission/question callback from Clauderesult: turn completion eventstatus_change: session status changed (running/idle/input_needed/error)error: transport/process error
API
Structured API
await ClaudeClient.init(config)
Creates a StructuredClaudeClient backed by the existing Claude transport.
Main methods:
send(input, options?): returns a liveTurnHandlegetCurrentTurn(): latest active turn snapshot ornullgetHistory(): completed turn snapshots for the sessiongetOpenRequests(): unresolved question, tool approval, hook, or MCP requestsgetOpenRequest(id): fetch one open request by idapproveRequest(id, decision?): allow a tool or hook requestdenyRequest(id, reason?): deny a tool or hook requestanswerQuestion(id, answers): answer anAskUserQuestionrequestcreateQuestionSession(id): incrementally collect answers for a question request, thensubmit()theminterruptTurn(turnId?): interrupt the active turnsetPermissionMode(mode),setModel(model),setMaxThinkingTokens(tokens)listSupportedModels(timeoutMs?)close()
Question sessions let you step through multi-question prompts without assembling the final answer object up front:
const [request] = client.getOpenRequests();
if (request?.kind === 'question') {
const session = client.createQuestionSession(request.id);
session.setCurrentAnswer('Blue');
session.next();
session.setCurrentAnswer(['Cat', 'Dog']);
await session.submit();
}Session Browser Examples
The package also exposes read-only helpers for browsing Claude Code session history from disk.
import {
listClaudeSessionSummaries,
readClaudeSessionRecord
} from '@raylin01/claude-client/sessions';
const summaries = await listClaudeSessionSummaries(process.cwd());
const latest = summaries[0];
if (latest) {
console.log('Latest session:', latest.id, latest.summary);
const record = await readClaudeSessionRecord(latest.id, process.cwd());
for (const message of record.transcript) {
console.log(message.role, message.content.map((block) => block.type).join(','));
}
}record.rawSession and record.rawMessages preserve the provider-native data, while record.transcript gives you normalized cross-provider messages.
TurnHandle
Main methods and properties:
updates(): async iterator of pushed turn updatesonUpdate(listener): subscribe to updates with an event listenercurrent(): latest turn snapshothistory(): semantic per-turn event historygetOpenRequests(): unresolved requests for that turndone: promise resolving to the final turn snapshot
new ClaudeClient(config)
Key config fields:
cwd(required): working directoryclaudePath: custom CLI pathargs: extra CLI argumentsmodel,fallbackModel,maxTurns,maxBudgetUsdpermissionMode,allowedTools,disallowedToolsmcpServers: MCP server configurationworktree:truefor--worktree, or string name for--worktree <name>tmux:truefor--tmux, or string mode likeclassicsystemPrompt,appendSystemPrompt,effortdangerouslySkipPermissions,allowDangerouslySkipPermissionsdebugMode,debugFile,verbosefromPr,chrome,ide,disableSlashCommandssettings,extraArgs,sandboxthinking:{ maxTokens?, level? }for extended thinkingdebug: enable debug logsdebugLogger: optional custom logger callback
Print Mode Options
printMode: Enable print mode (-pflag) - spawns process per messageprintModeAutoSession: Auto-generate session ID (default:truewhen printMode enabled)sessionId: Custom session ID to use
Core methods
start(): Start the CLI process (or just emit ready in print mode)sendMessage(text): Send a text messagesendMessageWithContent(content): Send message with multiple content blocksqueueMessage(text): Queue message to send when Claude is readysendControlResponse(requestId, response): Respond to permission/questionsendControlRequest(request, timeoutMs?): Send control requestsetModel(model): Change model mid-sessionsetPermissionMode(mode): Set permission modesetMaxThinkingTokens(maxTokens): Configure thinking tokenslistSupportedModels(): Get available modelsinterrupt(): Interrupt current operationkill(): Terminate the session
Getters
sessionId: Current session IDgetStatus(): Current status (running|idle|input_needed|error)getPendingAction(): Pending permission/question requiring inputisProcessing(): Whether currently processing a message
Utility exports
@raylin01/claude-client/sessions@raylin01/claude-client/mcp@raylin01/claude-client/task-store@raylin01/claude-client/task-queue
Examples
See /examples:
basic.ts- Structured mode basicsstructured-requests.ts- Structured request handling for AskUserQuestion and tool approvalsevents.ts- Lower-level raw event handlingerror-handling.ts- Error handling patternsprint-mode.ts- Print mode with auto session IDprint-mode-session.ts- Custom session ID and resumption across client instances
Integration Scripts
Manual end-to-end scripts that run the real Claude CLI:
node scripts/integration-worktree-smoke.mjsnode scripts/integration-tmux-smoke.mjsnode scripts/integration-structured-smoke.mjsnode scripts/integration-structured-multipass.mjs
The structured integration scripts are intentionally pragmatic rather than perfectly deterministic. They are meant to validate that the JavaScript SDK works end to end with real Claude behavior, including streaming, questions, tool calls, and multi-turn memory, while persisting results to test-output/ for inspection.
The current live validation shows that multi-turn memory, AskUserQuestion, streaming updates, and tool-use capture work through the structured API. Actual permission-prompt behavior can still vary by environment and Claude runtime configuration.
Mode Comparison
| Feature | Stream Mode | Print Mode |
|---------|-------------|------------|
| Process lifecycle | Persistent | Spawn per message |
| Session persistence | In-memory | Disk-based via --resume |
| Memory usage | Higher | Lower (process exits) |
| Latency | Lower | Higher (spawn overhead) |
| Best for | Long-running sessions | Short queries, serverless |
Projects Using This Client
Troubleshooting
- If
readynever fires, verify yourclaudebinary path and authentication. - Enable
debug: trueand providedebugLoggerto inspect protocol events. - If you are using the structured API, inspect
turn.current()andclient.getOpenRequests()before assuming the model stalled. - If permission requests stall, ensure you handle
control_request. - In print mode, session ID is required for multi-turn conversations.
Versioning
This package uses independent semver releases.
License
ISC
