@scottwalker/kraube-konnektor
v0.6.6
Published
Programmatic Node.js interface for Claude Code CLI
Maintainers
Readme
Kraube Konnektor
Programmatic Node.js interface for Claude Code CLI.
Use Claude Code from your application code — no terminal required. Works with your existing Max/Team/Enterprise subscription.
Website | Examples | API Reference | Architecture
Why
Claude Code is a powerful AI coding agent, but it only runs in a terminal. kraube-konnektor turns it into a programmable API — so you can embed it into CI pipelines, build custom tools, orchestrate multi-agent workflows, or integrate it with any Node.js application.
Key design decisions:
- CLI wrapper, not API client — uses your local
claudebinary and subscription, not the Anthropic HTTP API - Two execution modes — persistent SDK session (fast, default) or CLI process spawning (simple)
- Executor abstraction — swap CLI for SDK or HTTP backend without changing your code
- Full CLI parity — exposes all 45+ Claude Code flags through typed options
- Typed handles —
StreamHandle(fluent.on().done()+for-await) andChatHandle(multi-turn conversations)
Requirements
- Node.js >= 18.0.0
- Claude Code CLI installed and authenticated
Install
npm install @scottwalker/kraube-konnektorCLI Setup
Bootstrap Claude Code on a fresh server with a single command:
npx @scottwalker/kraube-konnektor setupThe setup wizard will:
- Check Node.js version
- Install Claude Code globally (if not installed)
- Ask for a config directory (default:
~/.claude) — use different paths for isolated instances - Ask for an HTTP proxy (optional) — for servers behind a proxy
- Run
claude loginfor authentication - Print a ready-to-use code example with your settings
Use --proxy to skip the interactive proxy prompt:
npx @scottwalker/kraube-konnektor setup --proxy "http://user:pass@host:port"Each instance can have its own config directory and proxy — no global environment variables needed:
const claude = new Claude({
model: 'sonnet',
env: {
CLAUDE_CONFIG_DIR: '/opt/my-project/.claude',
HTTPS_PROXY: 'http://user:pass@host:port',
},
})Quick Start
import { Claude, PERMISSION_ACCEPT_EDITS } from '@scottwalker/kraube-konnektor'
const claude = new Claude({ permissionMode: PERMISSION_ACCEPT_EDITS })
// Simple query
const result = await claude.query('Find and fix bugs in auth.ts')
console.log(result.text)
console.log(result.sessionId) // resume later
console.log(result.usage) // { inputTokens, outputTokens }Features
Custom CLI Path
Point to a specific Claude Code installation when multiple versions coexist:
import { Claude } from '@scottwalker/kraube-konnektor'
const claude = new Claude({
executable: '/opt/claude-code/v2/bin/claude',
cwd: '/path/to/project',
})Streaming
Real-time output as Claude works. stream() returns a StreamHandle — use the fluent .on().done() API or classic for-await:
import {
Claude,
EVENT_TEXT, EVENT_TOOL_USE, EVENT_RESULT, EVENT_ERROR,
} from '@scottwalker/kraube-konnektor'
const claude = new Claude()
// Fluent API (.on / .done)
const result = await claude
.stream('Rewrite the auth module')
.on(EVENT_TEXT, (e) => process.stdout.write(e.text))
.on(EVENT_TOOL_USE, (e) => console.log(`[Tool] ${e.toolName}`))
.on(EVENT_ERROR, (e) => console.error(e.message))
.done()
console.log(`Done in ${result.durationMs}ms`)
// Classic for-await
const handle = claude.stream('Rewrite the auth module')
for await (const event of handle) {
switch (event.type) {
case EVENT_TEXT:
process.stdout.write(event.text)
break
case EVENT_TOOL_USE:
console.log(`[Tool] ${event.toolName}`)
break
case EVENT_RESULT:
console.log(`\nDone in ${event.durationMs}ms`)
break
case EVENT_ERROR:
console.error(event.message)
break
}
}Multi-turn Sessions
Maintain conversation context across queries:
import { Claude } from '@scottwalker/kraube-konnektor'
const claude = new Claude()
const session = claude.session()
await session.query('Analyze the architecture of this project')
await session.query('Now refactor the auth module based on your analysis')
// ^ Claude remembers the previous context
// Resume a session later (even across process restarts)
const s2 = claude.session({ resume: session.sessionId! })
await s2.query('Continue where we left off')
// Fork a session (branch without modifying the original)
const s3 = claude.session({ resume: session.sessionId!, fork: true })Structured Output
Get typed JSON responses via JSON Schema:
import { Claude } from '@scottwalker/kraube-konnektor'
const claude = new Claude()
const result = await claude.query('Extract all API endpoints from the codebase', {
schema: {
type: 'object',
properties: {
endpoints: {
type: 'array',
items: {
type: 'object',
properties: {
method: { type: 'string' },
path: { type: 'string' },
handler: { type: 'string' },
},
},
},
},
},
})
console.log(result.structured)
// { endpoints: [{ method: 'GET', path: '/api/users', handler: 'getUsers' }, ...] }Parallel Execution
Run independent queries concurrently (each spawns a separate CLI process):
import { Claude, PERMISSION_PLAN } from '@scottwalker/kraube-konnektor'
const claude = new Claude()
const [bugs, tests, docs] = await claude.parallel([
{ prompt: 'Find bugs in src/', options: { cwd: './src' } },
{ prompt: 'Run the test suite', options: { allowedTools: ['Bash'] } },
{ prompt: 'Review documentation', options: { permissionMode: PERMISSION_PLAN } },
])Recurring Tasks
Node.js-level equivalent of the /loop CLI command:
import { Claude, SCHED_RESULT, SCHED_ERROR } from '@scottwalker/kraube-konnektor'
const claude = new Claude()
const job = claude.loop('5m', 'Check CI pipeline status and report failures')
job.on(SCHED_RESULT, (result) => {
console.log(`[${new Date().toISOString()}] ${result.text}`)
})
job.on(SCHED_ERROR, (err) => {
console.error('Check failed:', err.message)
})
// Stop when no longer needed
job.stop()Supported intervals: '30s', '5m', '2h', '1d', or raw milliseconds.
MCP Servers
Connect Model Context Protocol servers:
import { Claude } from '@scottwalker/kraube-konnektor'
// SDK mode (default) — inline definitions
const claude = new Claude({
mcpServers: {
playwright: {
command: 'npx',
args: ['@playwright/mcp@latest'],
},
database: {
type: 'http',
url: 'http://localhost:3001/mcp',
},
},
})
// CLI mode — config file path
const cliClaude = new Claude({
useSdk: false,
mcpConfig: './mcp.json',
})Custom Subagents
Define specialized agents:
import { Claude, PERMISSION_ACCEPT_EDITS } from '@scottwalker/kraube-konnektor'
const claude = new Claude({
agents: {
reviewer: {
description: 'Code review expert',
model: 'haiku',
tools: ['Read', 'Glob', 'Grep'],
prompt: 'Review code for bugs, security issues, and style',
},
deployer: {
description: 'Deployment automation agent',
tools: ['Bash', 'Read'],
permissionMode: PERMISSION_ACCEPT_EDITS,
},
},
})Git Worktree Isolation
Run operations in an isolated copy of the repository:
import { Claude } from '@scottwalker/kraube-konnektor'
const claude = new Claude()
const result = await claude.query('Refactor the entire auth module', {
worktree: 'refactor-auth', // or `true` for auto-generated name
})Piped Input
Pass data alongside the prompt (like echo data | claude -p "prompt"):
import { readFileSync } from 'node:fs'
import { Claude } from '@scottwalker/kraube-konnektor'
const claude = new Claude()
const result = await claude.query('Analyze this error log and suggest fixes', {
input: readFileSync('./error.log', 'utf-8'),
})Lifecycle Hooks
Attach hooks to tool execution:
import { Claude } from '@scottwalker/kraube-konnektor'
const claude = new Claude({
hooks: {
PostToolUse: [
{
matcher: 'Edit|Write',
hooks: [{ command: 'prettier --write ${file_path}' }],
},
],
PreToolUse: [
{
matcher: 'Bash',
hooks: [{ command: './scripts/validate-command.sh', timeout: 5 }],
},
],
},
})Programmatic Permissions
Control tool approval with a callback instead of static permission modes:
import { Claude } from '@scottwalker/kraube-konnektor'
const claude = new Claude({
canUseTool: async (toolName, input, { signal }) => {
if (toolName === 'Bash' && String(input.command).includes('rm -rf'))
return { behavior: 'deny', message: 'Dangerous command blocked' }
return { behavior: 'allow' }
},
})In-Process MCP Tools
Define custom tools that run in-process — no external MCP server required:
import { Claude, createSdkMcpServer, sdkTool } from '@scottwalker/kraube-konnektor'
import { z } from 'zod/v4'
const server = await createSdkMcpServer({
name: 'my-tools',
tools: [
await sdkTool('getUser', 'Get user by ID', { id: z.string() },
async ({ id }) => ({
content: [{ type: 'text', text: JSON.stringify({ name: 'Alice', role: 'admin' }) }],
})
),
],
})
const claude = new Claude({ mcpServers: { myTools: server } })JS Hook Callbacks
Subscribe to all 21 hook events with native JS callbacks (no shell commands):
import { Claude } from '@scottwalker/kraube-konnektor'
const claude = new Claude({
hookCallbacks: {
PreToolUse: [{
matcher: 'Bash',
hooks: [async (input) => {
if (String(input.tool_input?.command).includes('sudo'))
return { decision: 'block', reason: 'sudo not allowed' }
return { continue: true }
}],
}],
Notification: [{
hooks: [async (input) => {
console.log('Notification:', input.message)
return {}
}],
}],
},
})Thinking Config
Control Claude's reasoning behavior:
import { Claude } from '@scottwalker/kraube-konnektor'
const claude = new Claude({ thinking: { type: 'enabled' } })
// 'adaptive' — Claude decides | 'enabled' — always think | 'disabled' — no thinkingRuntime Control
Change model or permission mode during a session:
import { Claude, PERMISSION_PLAN } from '@scottwalker/kraube-konnektor'
const claude = new Claude({ model: 'sonnet' })
claude.setModel('opus') // switch to a more capable model
claude.setPermissionMode(PERMISSION_PLAN) // tighten permissions mid-sessionDynamic MCP
Add, reconnect, or toggle MCP servers at runtime:
const claude = new Claude()
claude.setMcpServers({ db: { command: 'npx', args: ['@db/mcp'] } })
await claude.reconnectMcpServer('db') // restart after config change
claude.toggleMcpServer('db', false) // temporarily disableFile Checkpointing
Snapshot and restore files modified by Claude:
const claude = new Claude({ enableFileCheckpointing: true })
const result = await claude.query('Refactor the auth module')
// Something went wrong? Roll back all file changes:
await claude.rewindFiles(result.sessionId!)Account & Model Info
Query your subscription info and available models:
const claude = new Claude()
const account = await claude.accountInfo() // { plan, usage, limits }
const models = await claude.supportedModels() // ['opus', 'sonnet', ...]
const agents = await claude.supportedAgents() // available agent typesPer-Query Abort
Cancel individual queries with standard AbortSignal:
const controller = new AbortController()
setTimeout(() => controller.abort(), 30_000) // 30s timeout
const result = await claude.query('Long analysis task', {
signal: controller.signal,
})Subagent Control
Monitor and stop spawned subagent tasks:
const handle = claude.stream('Run a full analysis')
.on('task_started', (e) => console.log(`Subagent: ${e.taskId}`))
.on('task_progress', (e) => console.log(`Progress: ${e.status}`))
.on('task_notification', (e) => console.log(e.message))
// Stop a specific subagent
await claude.stopTask(taskId)Settings & Plugins
Provide CLAUDE.md instructions, settings overrides, and plugins programmatically:
const claude = new Claude({
settingSources: ['user', 'project'],
settings: {
permissions: { allow: ['Bash(npm test)', 'Read(*)'] },
},
plugins: [
{ type: 'local', path: './my-plugin' },
],
})Custom Process Spawn
Override how Claude Code processes are created — useful for VMs, containers, or remote execution:
import { Claude } from '@scottwalker/kraube-konnektor'
const claude = new Claude({
spawnClaudeCodeProcess: (options) => {
// options: { command, args, cwd, env, signal }
// Run inside a Docker container instead of locally
return spawn('docker', ['exec', 'my-sandbox', options.command, ...options.args], {
env: options.env,
cwd: options.cwd,
})
},
})Session Utilities
List and inspect past sessions:
import { listSessions, getSessionMessages } from '@scottwalker/kraube-konnektor'
const sessions = await listSessions({ limit: 10 }) // all session IDs + metadata
const messages = await getSessionMessages(sessions[0].sessionId) // full message historyFull Configuration
All Claude Code CLI capabilities in one place:
import {
Claude,
EFFORT_HIGH, PERMISSION_ACCEPT_EDITS, PERMISSION_PLAN,
} from '@scottwalker/kraube-konnektor'
const claude = new Claude({
// CLI binary
executable: '/usr/local/bin/claude',
cwd: '/path/to/project',
// Model
model: 'opus', // 'opus' | 'sonnet' | 'haiku' | full model ID
effortLevel: EFFORT_HIGH, // EFFORT_LOW | EFFORT_MEDIUM | EFFORT_HIGH | EFFORT_MAX
fallbackModel: 'sonnet', // auto-fallback on failure
// Permissions
permissionMode: PERMISSION_ACCEPT_EDITS, // PERMISSION_DEFAULT | PERMISSION_ACCEPT_EDITS | PERMISSION_PLAN | PERMISSION_AUTO | PERMISSION_DONT_ASK | PERMISSION_BYPASS
allowedTools: ['Read', 'Edit', 'Bash(npm run *)'],
disallowedTools: ['WebFetch'],
// Prompts
systemPrompt: 'You are a senior TypeScript developer',
appendSystemPrompt: 'Always write tests for new code',
// Limits
maxTurns: 10, // max agentic turns per query
maxBudget: 5.0, // max USD per query
// Directories
additionalDirs: ['../shared-lib', '../proto'],
// MCP (inline definitions for SDK mode)
mcpServers: { /* ... */ },
// Agents
agents: { /* ... */ },
// Hooks
hooks: { /* ... */ },
// Environment
env: {
MAX_THINKING_TOKENS: '50000',
CLAUDE_CODE_DISABLE_AUTO_MEMORY: '1',
},
// Session
noSessionPersistence: true, // for CI/automation
})
// Override any option per query
const result = await claude.query('Analyze this module', {
model: 'haiku', // cheaper model for this query
permissionMode: PERMISSION_PLAN, // read-only
maxTurns: 3,
})Error Handling
All errors extend KraubeKonnektorError for uniform catching:
import {
Claude,
KraubeKonnektorError,
CliNotFoundError,
CliExecutionError,
CliTimeoutError,
ParseError,
ValidationError,
} from '@scottwalker/kraube-konnektor'
const claude = new Claude()
try {
const result = await claude.query('Fix the bug')
} catch (err) {
if (err instanceof CliNotFoundError) {
// Claude Code CLI not installed or wrong path
console.error(`CLI not found: ${err.executable}`)
} else if (err instanceof CliExecutionError) {
// Non-zero exit code
console.error(`Exit ${err.exitCode}: ${err.stderr}`)
} else if (err instanceof CliTimeoutError) {
// Exceeded timeout
console.error(`Timed out after ${err.timeoutMs}ms`)
} else if (err instanceof ParseError) {
// Unexpected CLI output
console.error(`Parse failed: ${err.rawOutput.slice(0, 100)}`)
} else if (err instanceof KraubeKonnektorError) {
// Any other library error
console.error(err.message)
}
}Custom Executor
The IExecutor abstraction lets you swap the CLI backend for testing, mocking, or alternative transports:
import {
Claude, EVENT_TEXT, EVENT_RESULT,
type IExecutor, type ExecuteOptions, type QueryResult, type StreamEvent,
} from '@scottwalker/kraube-konnektor'
class MockExecutor implements IExecutor {
async execute(args: readonly string[], options: ExecuteOptions): Promise<QueryResult> {
return {
text: 'Mocked response',
sessionId: 'mock-session',
usage: { inputTokens: 0, outputTokens: 0 },
cost: null,
durationMs: 0,
messages: [],
structured: null,
raw: {},
}
}
async *stream(args: readonly string[], options: ExecuteOptions): AsyncIterable<StreamEvent> {
yield { type: EVENT_TEXT, text: 'Mocked stream' }
yield {
type: EVENT_RESULT,
text: 'Mocked stream',
sessionId: 'mock-session',
usage: { inputTokens: 0, outputTokens: 0 },
cost: null,
durationMs: 0,
}
}
}
// Use in tests or with future backends
const claude = new Claude({ model: 'opus' }, new MockExecutor())Architecture
┌──────────┐ ┌─────────────┐ ┌─────────────┐ ┌───────────────┐
│ Claude │────>│ ArgsBuilder │────>│ IExecutor │────>│ CLI Process │
│ (facade) │ │ │ │ (abstract) │ │ (claude -p) │
└──────────┘ └─────────────┘ └─────────────┘ └───────────────┘
│ ^
v |
Session SdkExecutor (default, persistent session)
Scheduler CliExecutor (useSdk: false, process-per-query)- Two modes — SDK (persistent session, fast) or CLI (process-per-query, simple)
- Executor pattern — swap CLI for SDK/HTTP without touching consumer code
- Immutable config — client options frozen at construction, per-query overrides are non-destructive
See docs/ARCHITECTURE.md for detailed design documentation.
Examples
| Example | Description |
|---------|-------------|
| examples/interactive-chat | Terminal chat — ask questions, get answers in real time |
| examples/integration-test | Package integration test (mock executor) |
# Try the interactive chat:
cd examples/interactive-chat
npm install
npm start # standard mode
npm run stream # streaming mode (word by word)Documentation
| Document | Description | |----------|-------------| | Architecture | Design principles, SOLID breakdown, data flow diagrams | | API Reference | Complete reference for all classes, methods, types, and options | | Examples | Comprehensive cookbook covering every feature with code snippets | | Changelog | Version history | | Contributing | Development setup and guidelines |
Development
git clone [email protected]:scott-walker/kraube-konnektor.git
cd kraube-konnektor
npm install
npm run build # compile TypeScript
npm test # run unit tests
npm run test:integration # build + run integration test
npm run typecheck # type-check without emitting