Effect-TS wrapper for Claude Code CLI with @effect/ai integration
npm install @knpkv/effect-ai-claude-code-cli> Warning
> This package is experimental and in early development. Code is primarily AI-generated and not yet publicly published. For preview, use snapshot releases.
Effect-TS wrapper for Claude Code CLI with @effect/ai integration.
Provides a type-safe, functional interface to programmatically interact with Claude Code CLI, including:
- Non-blocking query execution
- Streaming responses with full event support
- Tool call visibility (Read, Write, Bash, etc.)
- Comprehensive error handling
- @effect/ai LanguageModel integration
- Type-safe tool names with IDE autocomplete
``bash`
npm install @knpkv/effect-ai-claude-code-cli effect
Prerequisites:
- Claude Code CLI installed globally
- Node.js 18+
- Effect-TS 3.x
`bash`
npm install -g @anthropics/claude-code
`typescript
import { ClaudeCodeCliClient } from "@knpkv/effect-ai-claude-code-cli"
import { Effect } from "effect"
const program = Effect.gen(function* () {
const client = yield* ClaudeCodeCliClient.ClaudeCodeCliClient
const response = yield* client.query("What is Effect-TS?")
console.log(response)
})
Effect.runPromise(program.pipe(Effect.provide(ClaudeCodeCliClient.layer())))
`
`typescript
import { ClaudeCodeCliClient } from "@knpkv/effect-ai-claude-code-cli"
import { Effect, Stream } from "effect"
const program = Effect.gen(function* () {
const client = yield* ClaudeCodeCliClient.ClaudeCodeCliClient
const stream = client.queryStream("Write a haiku about TypeScript")
yield* stream.pipe(
Stream.runForEach((chunk) =>
Effect.sync(() => {
if (chunk.type === "text") {
process.stdout.write(chunk.text)
}
})
)
)
})
Effect.runPromise(program.pipe(Effect.provide(ClaudeCodeCliClient.layer())))
`
`typescript
import { LanguageModel } from "@effect/ai"
import { ClaudeCodeCliClient, ClaudeCodeCliLanguageModel } from "@knpkv/effect-ai-claude-code-cli"
import { Effect } from "effect"
const program = Effect.gen(function* () {
const model = yield* LanguageModel.LanguageModel
const response = yield* model.generateText({
prompt: [{ role: "user", content: [{ type: "text", text: "Explain monads" }] }]
})
console.log(response.text)
console.log("Usage:", response.usage)
})
Effect.runPromise(
program.pipe(Effect.provide(ClaudeCodeCliLanguageModel.layer()), Effect.provide(ClaudeCodeCliClient.layer()))
)
`
Control which tools Claude can use:
`typescript
import { ClaudeCodeCliConfig } from "@knpkv/effect-ai-claude-code-cli"
import { Layer } from "effect"
const config = Layer.succeed(
ClaudeCodeCliConfig.ClaudeCodeCliConfig,
ClaudeCodeCliConfig.ClaudeCodeCliConfig.of({
allowedTools: ["Read", "Glob", "Bash"], // IDE autocomplete for known tools
disallowedTools: ["Write", "Edit"]
})
)
Effect.runPromise(program.pipe(Effect.provide(ClaudeCodeCliClient.layer()), Effect.provide(config)))
`
`typescript`
const config = ClaudeCodeCliConfig.ClaudeCodeCliConfig.of({
model: "claude-sonnet-4-5"
})
The client emits detailed chunk types for comprehensive event handling:
- TextChunk - Text content deltas
- ToolUseStartChunk - Tool invocation begins (includes tool name and ID)
- ToolInputChunk - Tool input JSON streaming
- ContentBlockStartChunk - Content block boundaries
- ContentBlockStopChunk - Content block completion
- MessageStartChunk - Message metadata (model, usage)
- MessageDeltaChunk - Updates (stop_reason, usage)
- MessageStopChunk - Stream completion
`typescript
const stream = client.queryStream("Read package.json and summarize it")
yield *
stream.pipe(
Stream.runForEach((chunk) =>
Effect.gen(function* () {
switch (chunk.type) {
case "text":
yield* Console.log("Text:", chunk.text)
break
case "tool_use_start":
yield* Console.log(Tool: ${chunk.name} (${chunk.id}))`
break
case "tool_input":
yield* Console.log("Input:", chunk.partialJson)
break
case "message_delta":
yield* Console.log("Usage:", chunk.usage)
break
}
})
)
)
The package provides branded types for critical identifiers to prevent string confusion:
`typescript
import { Brand, Validation } from "@knpkv/effect-ai-claude-code-cli"
// Validate and construct branded types
const model = yield * Validation.validateModel("claude-4-sonnet-20250514") // ModelId
const prompt = yield * Validation.validatePrompt("Your prompt here") // PromptText
const tool = yield * Validation.validateToolName("Read") // ToolName
`
Comprehensive validation functions ensure inputs meet requirements:
`typescript
import { Validation } from "@knpkv/effect-ai-claude-code-cli"
// Validate prompt (non-empty, length limits)
const prompt = yield * Validation.validatePrompt("Explain TypeScript")
// Validate model ID (starts with "claude-")
const model = yield * Validation.validateModel("claude-4-sonnet")
// Validate tool name (PascalCase format)
const tool = yield * Validation.validateToolName("Read", false) // strict mode optional
// Validate file path (no null bytes, no path traversal)
const path = yield * Validation.validateFilePath("/home/user/file.txt")
// Validate timeout (1s to 10min)
const timeout = yield * Validation.validateTimeout(30000)
// Validate multiple tools
const tools = yield * Validation.validateTools(["Read", "Write", "Bash"])
`
Helper functions for working with stream chunks:
`typescript
import { TypeGuards } from "@knpkv/effect-ai-claude-code-cli"
import { Stream } from "effect"
// Filter to only text chunks
const textStream = stream.pipe(Stream.filter(TypeGuards.isTextChunk))
// Extract usage information
const usage = stream.pipe(
Stream.filterMap((chunk) => TypeGuards.extractUsage(chunk)),
Stream.runLast
)
`
Ensure CLI compatibility:
`typescript
import { CliVersion } from "@knpkv/effect-ai-claude-code-cli"
const program = Effect.gen(function* () {
// Check CLI version on startup
yield* CliVersion.checkCliVersion() // Validates minimum version
const client = yield* ClaudeCodeCliClient.ClaudeCodeCliClient
// ... use client
})
`
The package provides typed error handling with specific error types:
`typescript
import { ClaudeCodeCliError } from "@knpkv/effect-ai-claude-code-cli"
import { Match } from "effect"
const handleError = Match.type
Match.tag("CliNotFoundError", () => Console.error("Claude CLI not found. Install: npm i -g @anthropics/claude-code")),
Match.tag("RateLimitError", (error) => Console.error(Rate limited. Retry after ${error.retryAfter}s)),Version ${error.installed} < required ${error.required}
Match.tag("InvalidApiKeyError", (error) => Console.error("Invalid API key:", error.stderr)),
Match.tag("ValidationError", (error) => Console.error("Validation failed:", error.message)),
Match.tag("CliVersionMismatchError", (error) =>
Console.error()Parse error at line: ${error.line}
),
Match.tag("StreamParsingError", (error) => Console.error()),
Match.orElse((error) => Console.error("Error:", error))
)
Effect.runPromise(program.pipe(Effect.catchAll(handleError), Effect.provide(ClaudeCodeCliClient.layer())))
`
Service Tag: ClaudeCodeCliClient.ClaudeCodeCliClient
#### Methods
- query(prompt: string): Effect - Execute non-streaming queryqueryStream(prompt: string): Stream
- - Stream response with full event visibility
#### Layers
- layer(): Layer - Default layerlayerConfig(config): Layer
- - Layer with inline config
Service Tag: ClaudeCodeCliConfig.ClaudeCodeCliConfig
#### Configuration
`typescript`
interface ClaudeCodeCliConfig {
model?: string // Model name (default: from CLI config)
allowedTools?: ReadonlyArray
disallowedTools?: ReadonlyArray
}
#### Layers
- ClaudeCodeCliConfig.default - Empty configuration (uses CLI defaults)
Service Tag: LanguageModel.LanguageModel (from @effect/ai)
#### Layers
- layer(config?): Layer - @effect/ai integration
#### Model Constructor
- model(config?): AiModel<"claude-code-cli", LanguageModel, ClaudeCodeCliClient>
See the examples directory for complete working examples:
- basic.ts - Simple query
- streaming.ts - Streaming responses
- tools.ts - Tool permissions
- error-handling.ts - Error handling patterns
- language-model.ts - @effect/ai integration
- chunk-logging.ts - Complete event visibility
`bashInstall dependencies
pnpm install
This package follows Effect-TS patterns:
- Services - Context.Tag-based dependency injection
- Layers - Composable service providers
- Effects - Typed, referentially transparent computations
- Streams - Incremental, backpressure-aware processing
- Errors - Typed error channel with discriminated unions
MIT
- Effect Documentation
- @effect/ai Documentation
- Claude Code CLI
- GitHub Repository