claudecli

package module
v0.1.2 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 2, 2026 License: MIT Imports: 26 Imported by: 0

README

claudecli-go

Go package for invoking the Claude Code CLI as a subprocess with typed streaming events, functional options, and pluggable execution.

Requires: claude CLI installed and on PATH.

Install

go get github.com/allbin/claudecli-go

See CHANGELOG.md for what changed between versions and upgrade notes.

Quick start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/allbin/claudecli-go"
)

func main() {
    // One-off blocking call
    text, result, err := claudecli.RunText(context.Background(), "Say hello in 5 words")
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(text)
    fmt.Printf("Cost: $%.4f, Tokens: %d (%d in / %d out)\n",
        result.CostUSD, result.Usage.TotalTokens(),
        result.Usage.InputTokens, result.Usage.OutputTokens)
}

Streaming

stream := claudecli.Run(ctx, "Explain quicksort",
    claudecli.WithModel(claudecli.ModelSonnet),
)

for event := range stream.Events() {
    switch e := event.(type) {
    case *claudecli.TextEvent:
        fmt.Print(e.Content)
    case *claudecli.ThinkingEvent:
        // model thinking output
    case *claudecli.ToolUseEvent:
        fmt.Printf("[tool: %s]\n", e.Name)
    case *claudecli.StderrEvent:
        log.Println("stderr:", e.Content)
    case *claudecli.ErrorEvent:
        log.Println("error:", e.Err)
    case *claudecli.ResultEvent:
        fmt.Printf("\n--- Done: $%.4f ---\n", e.CostUSD)
    }
}

Typed JSON responses

type Analysis struct {
    Summary string   `json:"summary"`
    Tags    []string `json:"tags"`
}

analysis, result, err := claudecli.RunJSON[Analysis](ctx, client, prompt,
    claudecli.WithModel(claudecli.ModelHaiku),
)

RunJSON automatically strips markdown code fences (```json ... ```) before unmarshaling.

Blocking mode

When you don't need streaming events, use RunBlocking for a simpler, more reliable path. Uses --output-format json internally.

result, err := client.RunBlocking(ctx, "Summarize this file")
fmt.Println(result.Text)
fmt.Printf("Cost: $%.4f, Turns: %d\n", result.CostUSD, result.NumTurns)

For typed JSON with schema validation:

type Analysis struct {
    Summary string   `json:"summary"`
    Tags    []string `json:"tags"`
}

// When WithJSONSchema is set, parses the schema-validated structured_output field.
// Otherwise, parses the text result with code fence stripping.
analysis, result, err := claudecli.RunBlockingJSON[Analysis](ctx, client, prompt,
    claudecli.WithJSONSchema(`{"type":"object","properties":{"summary":{"type":"string"},"tags":{"type":"array","items":{"type":"string"}}},"required":["summary","tags"]}`),
)

Cost & token usage

Every run reports its cost and token usage. CostUSD is the dollar cost; Usage holds the token breakdown. Use Usage.TotalTokens() for the headline "tokens used" figure (input + output + cache read + cache create) instead of summing the fields by hand.

text, result, err := claudecli.RunText(ctx, "Say hello")
// ...
fmt.Printf("cost $%.4f for %d tokens\n", result.CostUSD, result.Usage.TotalTokens())
fmt.Println(result.Usage) // Usage{in: 12, out: 5, cacheRead: 0, cacheCreate: 0, total: 17}

The same fields are on BlockingResult (from RunBlocking) and on each *ResultEvent in streaming mode. The SDK reports cost and usage per call and holds no running total — accumulating across runs is the caller's concern.

For a per-model breakdown (including context window and web search/fetch counts), read ResultEvent.ModelUsage, keyed by model ID; each entry has its own CostUSD and TotalTokens().

Client with defaults

client := claudecli.New(
    claudecli.WithModel(claudecli.ModelSonnet),
    claudecli.WithPermissionMode(claudecli.PermissionPlan),
    claudecli.WithMaxBudget(0.50),
)

// All calls inherit these defaults
stream := client.Run(ctx, "Review this code")

// Per-call overrides replace defaults
stream := client.Run(ctx, "Quick check",
    claudecli.WithModel(claudecli.ModelHaiku),
)

Authentication

Check, login, and logout via the CLI's auth subcommands.

// Check current auth state (three-state: authenticated/unauthenticated/unknown)
status, err := client.AuthStatus(ctx)
fmt.Println(status.Status, status.Email, status.OrgName)

// Start OAuth login — returns URL for user to visit
proc, err := client.AuthLogin(ctx,
    claudecli.WithNoBrowser(), // required for SubmitCode
)
// Show AutoOpenURL to the user (localhost redirect).
// After authorizing, the browser redirects to localhost which fails on remote
// machines. User copies the failed redirect URL and passes it to SubmitCode.
fmt.Println("Visit:", proc.AutoOpenURL)

// User pastes the redirect URL (http://localhost:PORT/callback?code=X&state=Y):
err = proc.SubmitCode(redirectURL)

err = proc.Wait() // blocks until login completes

// Logout
err = client.AuthLogout(ctx)

Package-level shortcuts (AuthStatus, AuthLogin, AuthLogout) use the default client. Use NewClient([]ClientOption{WithLogger(logger)}) for debug logging.

Login option Description
WithAuthMethod(method) AuthMethodClaudeAI (default) or AuthMethodConsole (API billing).
WithSSO() Force SSO login flow.
WithLoginEmail(string) Pre-populate email on login page.
WithNoBrowser() Suppress browser; required for SubmitCode.
LoginProcess field/method Description
URL Manual-visit URL (platform.claude.com redirect, shows CODE#STATE).
AutoOpenURL Browser URL (localhost redirect). Use this for remote/headless setups.
CallbackPort() int Port of the CLI's local callback server (0 if unavailable).
SubmitCode(string) error Submit auth via localhost callback. Accepts a full redirect URL (http://localhost:PORT/callback?code=X&state=Y) or CODE#STATE.
Wait() error Block until login completes.
Cancel() error Terminate the login process.

Stream state

Poll the stream's lifecycle state at any time:

stream := client.Run(ctx, prompt)

// State is tracked automatically as events flow
stream.State() // StateStarting -> StateRunning -> StateDone/StateFailed

// Block until completion
result, err := stream.Wait() // idempotent, safe to call multiple times

States: StateStarting, StateRunning, StateDone, StateFailed.

Session lifecycle differs: StateStartingStateIdle (after Connect) → StateRunning (during Query) → StateIdle (after result) → StateDone (after Close).

Activity state (watchdogs)

Lifecycle state tells you what phase the session is in; activity state tells you what the CLI is doing right now. For watchdogs that time out on event silence, use activity state to distinguish "model is generating" from "CLI is executing a tool" from "between turns":

for ev := range session.Events() {
    if cs, ok := ev.(*CLIStateChangeEvent); ok {
        // cs.State is one of:
        //   ActivityIdle              — between turns
        //   ActivityThinking          — model is generating
        //   ActivityAwaitingToolResult — a top-level tool_use is outstanding
        resetTimer(cs.State)
    }
}

info := session.ProcessInfo() // {LastStdoutAt, ActivityState, Lifecycle, SessionID}

CLIStateChangeEvent is emitted immediately BEFORE the event that triggered the transition (e.g. before the first ToolUseEvent of a turn), so consumers can update their state before processing the triggering event. ProcessInfo().LastStdoutAt is stamped from the stdout scanner independent of parsed events, so a stall can be distinguished from a quiet turn without inferring it from ToolUseEvent/ToolResultEvent pairing.

While the session sits in ActivityAwaitingToolResult, a ToolProgressEvent is emitted every 30 s carrying the first pending top-level tool_use's ToolUseID, ToolName, and Elapsed since it started. This is a pushed liveness signal for long tool runs — consumers can render "Bash running for 4m 12s" directly without polling ProcessInfo() or computing elapsed time themselves. Parallel tool_use calls do not change the reported tool; the first one is stable until the awaiting state ends.

Sessions

// Resume a previous session
stream := client.Run(ctx, "Continue where we left off",
    claudecli.WithSessionID("sess-abc123"),
)

// Fork from an existing session
stream := client.Run(ctx, "Try a different approach",
    claudecli.WithSessionID("sess-abc123"),
    claudecli.WithForkSession(),
)

// Continue the most recent session
stream := client.Run(ctx, "What were we doing?",
    claudecli.WithContinue(),
)

Agents

// Use a named agent
stream := client.Run(ctx, "Review this PR",
    claudecli.WithAgent("reviewer"),
)

// Define custom agents inline
stream := client.Run(ctx, "Check the code",
    claudecli.WithAgentDef(`{"reviewer": {"description": "Reviews code", "prompt": "You are a code reviewer"}}`),
    claudecli.WithAgent("reviewer"),
)

Interactive sessions

Connect() starts a bidirectional session with the CLI's control protocol. Supports multi-turn conversations, programmatic tool permission callbacks, and mid-session model/permission changes.

session, err := client.Connect(ctx,
    claudecli.WithModel(claudecli.ModelSonnet),
    claudecli.WithCanUseTool(func(name string, input json.RawMessage) (*claudecli.PermissionResponse, error) {
        if name == "Bash" {
            return &claudecli.PermissionResponse{Allow: false, DenyMessage: "no shell"}, nil
        }
        return &claudecli.PermissionResponse{Allow: true}, nil
    }),
)
if err != nil {
    log.Fatal(err)
}
defer session.Close()

// Send queries
session.Query("What files are in this directory?")

// Read events
for event := range session.Events() {
    switch e := event.(type) {
    case *claudecli.TextEvent:
        fmt.Print(e.Content)
    case *claudecli.ResultEvent:
        fmt.Printf("\nDone: $%.4f\n", e.CostUSD)
    }
}

// Or block until completion
result, err := session.Wait()

Session methods:

  • Query(prompt) — send a text-only user message (sets up result tracking for Wait())
  • QueryWithContent(prompt, blocks...) — send a message with text and multimodal content blocks
  • SendMessage(prompt) — send a message without result tracking (can be called mid-turn)
  • SendMessageWithContent(prompt, blocks...) — multimodal variant of SendMessage
  • Events() — event channel
  • Wait() — block until result (idempotent)
  • Interrupt() — send interrupt signal
  • Ping(timeout) — round-trip a no-op control request to prove the CLI's read loop is alive (not just that the process is running). Watchdog-friendly: any CLI response, including "unknown subtype", counts as success
  • SetPermissionMode(mode) — change permissions mid-session
  • SetModel(model) — change model mid-session
  • GetServerInfo() — raw JSON from the initialize handshake
  • RewindFiles(userMessageID) — rewind files to a checkpoint
  • ReconnectMCPServer(name) — reconnect a named MCP server (non-blocking)
  • ReconnectMCPServerWait(name, timeout) — reconnect and block until connected (polls mcp_status; 0 timeout = 10s default)
  • ToggleMCPServer(name, enabled) — enable/disable an MCP server
  • StopTask(taskID) — stop a running task
  • GetMCPStatus() — query MCP server status (fire-and-forget)
  • QueryMCPStatus() — query MCP server status, returns []MCPServerStatus
  • Close() — terminate session
Mid-turn message injection

Query rejects while a turn is running ("query already in progress") because it manages result tracking for Wait(). Use SendMessage to inject a message mid-turn — it writes directly to stdin without state gating:

session.Query("Refactor the auth module")

// Later, while the agent is still working:
session.SendMessage("Also update the tests")

The CLI receives the message immediately but processes it at a safe boundary (between tool calls, not mid-generation). The injected message is folded into the current turn — the next ResultEvent from Wait() covers both the original query and injected messages.

SendMessage does not set up result tracking. If called without a prior Query, Wait() will hang. Use Query to start a turn, SendMessage to inject into it.

Concurrency: writes to stdin are mutex-serialized, so concurrent SendMessage calls are safe. Under extreme write volume the OS pipe buffer (64KB on Linux) provides natural backpressure — SendMessage blocks until the CLI drains stdin. If the pipe fills while the CLI is waiting for a control response (permission prompt), this could theoretically deadlock. In practice this requires dozens of queued messages and is unlikely for normal usage patterns.

Delivery confirmation: by default, SendMessage is fire-and-forget — you write to stdin with no acknowledgment. Enable WithReplayUserMessages() to have the CLI echo each user message back on stdout as a UserEvent with IsReplay=true. This confirms the CLI has read and accepted the message:

session, err := client.Connect(ctx,
    claudecli.WithReplayUserMessages(),
)

session.Query("Start the refactor")
session.SendMessage("Also update the tests")

for event := range session.Events() {
    switch e := event.(type) {
    case *claudecli.UserEvent:
        if e.IsReplay {
            fmt.Printf("CLI confirmed: %s\n", e.Text())
        }
    }
}
Forking an existing session

WithForkSession paired with WithResume / WithContinue / WithSessionID runs the CLI against a new session ID seeded with the parent's full history. The parent's session file on disk is not modified. Useful for asking a one-off question against a live session's context without polluting its transcript:

result, err := client.RunBlocking(ctx, "Summarize this session in 2-4 words.",
    claudecli.WithResume(parentSessionID),
    claudecli.WithForkSession(),
    claudecli.WithModel(claudecli.ModelHaiku),
)
// result.SessionID is the fork's new ID (unique from parentSessionID).
// The parent session file on disk is byte-for-byte unchanged.

Prompt-cache hits on the shared prefix keep forks cheap. The fork writes its own session file to disk — callers that fork repeatedly should plan to clean these up. The parent must have been persisted (started via Connect, WithSessionID, WithResume, or WithContinue; RunBlocking without any of those defaults to --no-session-persistence).

User input (AskUserQuestion)

When Claude calls the AskUserQuestion tool, it arrives as a can_use_tool control request. Use WithUserInput to handle these with a dedicated callback instead of routing them through WithCanUseTool:

session, err := client.Connect(ctx,
    claudecli.WithUserInput(func(questions []claudecli.Question) (map[string]string, error) {
        answers := make(map[string]string)
        for _, q := range questions {
            // Present q.Header, q.Question, q.Options to your UI
            answers[q.Question] = getUserSelection(q)
        }
        return answers, nil
    }),
    claudecli.WithCanUseTool(func(name string, input json.RawMessage) (*claudecli.PermissionResponse, error) {
        return &claudecli.PermissionResponse{Allow: true}, nil
    }),
)

Routing rules:

  • Both registered: AskUserQuestionuserInput, other tools → canUseTool
  • Only WithCanUseTool: AskUserQuestion falls through to canUseTool (backward compatible)
  • Only WithUserInput: AskUserQuestionuserInput, other tools get error response

Multi-session pool

Pool is a registry that tracks multiple sessions and multiplexes their events into a single channel, tagged by session ID. The pool is purely additive — it doesn't modify Session or Client APIs.

pool := claudecli.NewPool()
defer pool.Close()

s1, _ := client.Connect(ctx)
s1.Query("start task A")
// Wait for InitEvent so SessionID is set...

s2, _ := client.Connect(ctx)
s2.Query("start task B")

pool.Add(s1, claudecli.SessionMeta{Name: "task-a", Labels: map[string]string{"role": "worker"}})
pool.Add(s2, claudecli.SessionMeta{Name: "task-b"})

// Single event loop for all sessions
for pe := range pool.Events() {
    fmt.Printf("[%s] %T\n", pe.SessionID, pe.Event)
}

Pool methods: Add, Remove, Get, List, Events, Close, CloseAll. All are thread-safe. CloseAll closes every registered session in parallel (respecting each session's grace period), then closes the pool — useful for clean application shutdown.

Inter-agent messaging

FormatAgentMessage wraps content in a structured format that Claude recognizes as peer communication. Pool.SendAgentMessage is a convenience that looks up sessions and calls SendMessage on the target.

// Direct formatting
msg := claudecli.FormatAgentMessage("task-a", "I finished the auth refactor")
session.SendMessage(msg)

// Via pool — uses sender's SessionMeta.Name automatically
pool.SendAgentMessage(s1.SessionID(), s2.SessionID(), "I finished the auth refactor")
Typed Agent tool input

When a session spawns a sub-agent, the ToolUseEvent has Name: "Agent". Use ParseAgentInput() to extract structured fields without manual JSON parsing:

case *claudecli.ToolUseEvent:
    if agent := e.ParseAgentInput(); agent != nil {
        fmt.Printf("Agent: %s (%s) — %s\n", agent.Name, agent.SubagentType, agent.Description)
        if agent.Isolation == "worktree" {
            fmt.Println("  running in isolated git worktree")
        }
    }

AgentInput fields: Description, Prompt, SubagentType, Name, RunInBackground, Model, Isolation ("worktree" for git worktree isolation), Mode (permission mode: "plan", "acceptEdits", etc.), TeamName (team name for spawning).

Subagent activity tracking

UserEvent makes subagent execution visible. Use ParentToolUseID to correlate events with their parent Agent tool call, and AgentResult to detect completion:

case *claudecli.UserEvent:
    if e.ParentToolUseID != "" {
        // This event belongs to the subagent spawned by that Agent tool call.
        fmt.Printf("  [subagent %s] tool result\n", e.ParentToolUseID)
    }
    if e.AgentResult != nil {
        fmt.Printf("  Agent %s (%s) completed: %d tokens, %dms, %d tool calls\n",
            e.AgentResult.AgentID, e.AgentResult.AgentType,
            e.AgentResult.TotalTokens, e.AgentResult.TotalDurationMs,
            e.AgentResult.TotalToolUseCount)
    }
case *claudecli.TaskEvent:
    // Real-time subagent lifecycle: task_started → task_progress → task_notification
    fmt.Printf("  [task %s] %s (tokens: %d, tools: %d, %dms)\n",
        e.Subtype, e.Description, e.TotalTokens, e.ToolUses, e.DurationMs)
case *claudecli.ToolUseEvent:
    if e.ParentToolUseID != "" {
        fmt.Printf("  [subagent] tool: %s\n", e.Name) // from a subagent
    } else {
        fmt.Printf("[tool: %s]\n", e.Name) // top-level
    }

Dynamic workflows

Dynamic workflows orchestrate many subagents from a script the CLI runtime runs in the background. There is no new flag or API to trigger one — it is prompt content: include ultracode, ask in your own words ("run a workflow…"), or invoke a saved/bundled command like /deep-research. In headless mode (-p) the run starts automatically.

A workflow surfaces through the ordinary event stream as a single synthetic task with TaskType == "local_workflow". The run is a two-turn lifecycle that emits two ResultEvents: the first says the workflow is running in the background, the second (after it completes) carries the real answer — so take the last ResultEvent, not the first.

for event := range stream.Events() {
    switch e := event.(type) {
    case *claudecli.UserEvent:
        if wl := e.WorkflowLaunch; wl != nil {
            fmt.Printf("workflow %q launched (run %s)\n", wl.WorkflowName, wl.RunID)
        }
    case *claudecli.TaskEvent:
        if e.IsWorkflow() {
            for _, p := range e.WorkflowProgress { // per-phase / per-agent state
                if p.IsAgent() {
                    fmt.Printf("  [%s] %s: %s (%d tok)\n", p.PhaseTitle, p.Label, p.State, p.Tokens)
                }
            }
        }
    case *claudecli.ResultEvent:
        fmt.Println("answer:", e.Text) // keep the LAST one
    }
}

The CLI stamps task_type only on the task_started event; the later task_progress, task_updated, and task_notification events for the same task omit it. The SDK backfills TaskType and WorkflowName from the matching task_started, so IsWorkflow() and WorkflowName stay correct across the whole lifecycle — you can gate on IsWorkflow() for progress and the terminal task_notification too, not just the launch.

Monitoring out-of-band

The runtime also persists live run state on disk keyed by RunID (it survives the SDK's --no-session-persistence). Given a WorkflowLaunch, you can monitor the run from a separate goroutine without consuming the event stream:

launch := userEvent.WorkflowLaunch
snaps, err := claudecli.WatchWorkflow(ctx, launch,
    claudecli.WithPollInterval(time.Second))
for snap := range snaps { // closes on terminal status (completed/stopped/killed)
    fmt.Printf("%s: %d/%d agents, %d tokens\n",
        snap.Status, len(snap.Agents()), snap.AgentCount, snap.TotalTokens)
}

// Or a one-shot read (e.g. to fetch the final Result after completion):
snap, err := claudecli.ReadWorkflowSnapshot(launch)

WorkflowLaunch.ManifestPath() / JournalPath() expose the underlying file paths. This reads an undocumented internal CLI layout that may change between versions, so it degrades gracefully (transient read/parse errors are retried; Raw preserves the full manifest). The in-stream WorkflowProgress carries the same live data, so the filesystem path is a complement for out-of-band or fire-and-forget monitoring, not a requirement.

Multimodal input

Send images and documents alongside text in interactive sessions:

imgData, _ := os.ReadFile("screenshot.png")
session.QueryWithContent("Describe this image",
    claudecli.ImageBlock("image/png", imgData),
)

pdfData, _ := os.ReadFile("report.pdf")
session.QueryWithContent("Summarize this document",
    claudecli.DocumentBlock("application/pdf", pdfData),
)

Content block constructors: TextBlock, ImageBlock, DocumentBlock. Base64 encoding is handled internally.

Custom executor

The Executor interface controls how the CLI process is spawned. Implement it to run Claude in Docker, over SSH, or any other environment.

type Executor interface {
    Start(ctx context.Context, cfg *StartConfig) (*Process, error)
}

type StartConfig struct {
    Args                    []string
    Stdin                   io.Reader
    Env                     map[string]string
    WorkDir                 string
    KeepStdinOpen           bool
    EnableFileCheckpointing bool
}
// Example: run Claude inside a Docker container
type DockerExecutor struct {
    Image  string
    Mounts []string
}

func (d *DockerExecutor) Start(ctx context.Context, cfg *claudecli.StartConfig) (*claudecli.Process, error) {
    dockerArgs := []string{"run", "--rm", "-i", d.Image}
    dockerArgs = append(dockerArgs, "claude")
    dockerArgs = append(dockerArgs, cfg.Args...)
    cmd := exec.CommandContext(ctx, "docker", dockerArgs...)
    cmd.Stdin = cfg.Stdin
    if cfg.WorkDir != "" {
        cmd.Dir = cfg.WorkDir
    }
    // ... set up stdout/stderr pipes ...
    cmd.Start()
    return &claudecli.Process{
        Stdout: stdout,
        Stderr: stderr,
        Wait:   cmd.Wait,
    }, nil
}

client := claudecli.NewWithExecutor(&DockerExecutor{Image: "my-claude:latest"},
    claudecli.WithModel(claudecli.ModelSonnet),
)

Testing

Use FixtureExecutor to replay recorded JSONL streams without invoking the real CLI:

func TestMyFeature(t *testing.T) {
    exec, err := claudecli.NewFixtureExecutorFromFile("testdata/session.jsonl")
    if err != nil {
        t.Fatal(err)
    }
    client := claudecli.NewWithExecutor(exec)

    text, _, err := client.RunText(context.Background(), "ignored prompt")
    if err != nil {
        t.Fatal(err)
    }
    if text != "expected output" {
        t.Errorf("got %q", text)
    }
}

For testing interactive sessions, use BidiFixtureExecutor:

bidi := claudecli.NewBidiFixtureExecutor()
client := claudecli.NewWithExecutor(bidi)

go func() {
    // Simulate CLI responses on bidi.StdoutWriter
    // Read SDK requests from bidi.StdinReader
    bidi.StdoutWriter.Write([]byte(`{"type":"system","session_id":"test","model":"sonnet"}` + "\n"))
    bidi.StdoutWriter.Close()
}()

session, _ := client.Connect(ctx)

You can also parse JSONL directly:

ch := make(chan claudecli.Event, 64)
go func() {
    defer close(ch)
    claudecli.ParseEvents(ctx, reader, ch)
}()
for event := range ch {
    // ...
}

Event types

All events implement the sealed Event interface. Use type switches or type assertions.

Type Description
*StartEvent Emitted before process launch. Contains resolved model, args, working dir.
*InitEvent CLI session started. Session ID, model, available tools, agents, skills, MCP servers. ModelDisplayName() renders the model ID as e.g. "Opus 4.8".
*CompactStatusEvent Compaction status change. Status is "compacting" or "" (cleared).
*CompactBoundaryEvent Compaction boundary marker. Trigger ("manual"/"auto"), PreTokens, Raw metadata.
*TaskEvent Subagent lifecycle update (system subtypes task_started, task_progress, task_updated, task_notification). ToolUseID links to the parent Agent call. Fields: TaskID, Description, TaskType, Prompt, LastToolName, Status, Summary, TotalTokens, ToolUses, DurationMs, EndTime. IsWorkflow() is true for dynamic-workflow runs (TaskType == "local_workflow"), where WorkflowName, WorkflowProgress (per-phase/per-agent []WorkflowProgressEntry), and OutputFile (on completion) are also set. See Dynamic workflows.
*HookEvent Hook lifecycle event (system subtypes hook_started, hook_progress, hook_response). Fields: HookID, HookName, HookEvent (e.g. "SessionStart"), and on hook_response: Output, Stdout, Stderr, ExitCode, Outcome.
*ThinkingEvent Model thinking output. Includes Signature for verification. Content may be empty while Signature is set — treat Content=="" && Signature!="" as "thinking hidden", not "no thinking". ParentToolUseID set when from a subagent.
*TextEvent Assistant text output. ParentToolUseID set when from a subagent.
*TurnEvent New assistant turn started. Turn is a 1-based counter, ToolName is the first tool in the turn (empty for text-only turns). Only emitted for top-level turns (subagent messages excluded).
*ToolUseEvent Tool invocation with name and input. ParseAgentInput() returns typed *AgentInput for Agent tool calls. ParentToolUseID set when from a subagent. ServerSide is true for server-side tools (web search, code execution). MCP is true for MCP tool calls.
*ToolResultEvent Result from a tool invocation. Content is []ToolContent supporting text and image blocks. Text() returns concatenated text. ParentToolUseID set when from a subagent.
*UserEvent Tool result or subagent message fed back to the model. Content is []UserContent (text or tool_result blocks). ParentToolUseID links subagent events to the parent Agent tool call (empty for top-level). AgentResult (non-nil on subagent completion) carries AgentID, AgentType, Prompt, TotalDurationMs, TotalTokens, TotalToolUseCount. WorkflowLaunch (non-nil when a dynamic workflow is launched in the background) carries RunID, WorkflowName, ScriptPath, TranscriptDir and helpers for out-of-band monitoring — see Dynamic workflows. IsReplay is true when echoed via --replay-user-messages. Text() returns concatenated text.
*UnknownEvent Unrecognized event type from CLI. Type is the raw type string (or "content/<type>" for unknown content blocks), Raw is the full JSON. Forward-compat catch-all — also used for error fallback diagnostics on non-zero exit.
*RateLimitEvent Rate limit status change. Fields: Status, Utilization, ResetsAt, RateLimitType, overage fields, UUID, SessionID, Raw.
*StderrEvent A line of stderr output from the CLI process.
*ResultEvent Session complete. Text, cost, duration, usage, NumTurns, StopReason, StructuredOutput, ModelUsage (per-model context window, token limits, web search/fetch counts), ContextSnapshot (per-API-call usage from last message_start/message_delta; requires WithIncludePartialMessages; nil otherwise). Synthesized if CLI exits cleanly without one.
*ContextManagementEvent Emitted when the CLI compresses or summarizes older turns to fit the context window. Raw contains the full JSON payload.
*ThinkingTokensEvent Running estimate of thinking-token usage during a turn (system subtype thinking_tokens). EstimatedTokens (cumulative) and EstimatedTokensDelta (increment). A progress signal, not authoritative accounting — use ResultEvent.Usage for final counts.
*CLIStateChangeEvent Activity-state transition (idle / thinking / awaiting_tool_result). Emitted immediately BEFORE the triggering event so consumers can flip their state before processing the event. Lets watchdogs distinguish "model generating" from "CLI running a tool" without inferring pairing from ToolUseEvent/ToolResultEvent. Backward-compatible: ignore in the type switch if unused.
*ToolProgressEvent Periodic heartbeat (every 30 s) while in awaiting_tool_result. Carries ToolUseID, ToolName, and Elapsed for the first pending top-level tool_use (stable across parallel tool_use calls). Pushed liveness signal so consumers don't poll ProcessInfo() to render "Bash running for 4m 12s". Backward-compatible: ignore in the type switch if unused.
*CLIToolProgressEvent Tool progress event from the CLI JSONL stream (top-level tool_progress type). Unlike the synthetic ToolProgressEvent, this comes directly from the CLI and carries ElapsedSeconds and optional TaskID.
*ToolUseSummaryEvent Emitted after tool execution with a human-readable summary. PrecedingToolUseIDs lists the tool_use IDs covered.
*AuthStatusEvent Authentication status change during a session (e.g. token refresh). IsAuthenticating, Output, Error.
*FilesPersistedEvent File persistence confirmation. Files lists successfully persisted files (Filename, FileID); Failed lists failures.
*ControlRequestEvent Control request from CLI (handled internally in sessions).
*StreamEvent Partial message update (when WithIncludePartialMessages is on).
*ErrorEvent Error during streaming. Fatal field distinguishes process failures (which set StateFailed) from non-fatal errors (parse errors, API errors). API errors are classified via errors.Is with sentinel errors (see error handling below).
*CLIExitEvent Final event before the events channel closes (Session mode). Reason ("normal" / "killed" / "crashed" / "context_canceled" / "unknown"), ExitCode (-1 if signaled or non-*exec.ExitError), Signal (e.g. "SIGKILL", empty if not signaled), Err (underlying *Error or context error), At. Lets consumers give actionable termination messages instead of inferring cause from a closed channel. Backward-compatible: ignore in the type switch if unused.

Options

Option Description
WithBinaryPath(string) Path to the claude binary. Only effective in New(). Default: "claude".
WithModel(Model) Model to use (ModelHaiku, ModelSonnet, ModelOpus, ModelFable). Default: ModelSonnet. Also accepts any CLI alias or full name as a string, e.g. Model("claude-fable-5").
WithFallbackModel(Model) Fallback model if primary is unavailable.
WithBetas(...string) Beta features to enable.
WithSystemPrompt(string) System prompt.
WithSystemPromptFile(string) Load system prompt from a file.
WithAppendSystemPrompt(string) Append to the default system prompt.
WithAppendSystemPromptFile(string) Append to the default system prompt from a file.
WithTools(...string) Allowed tools. Accepts individual names or comma-separated ("A,B" == "A", "B"). Deduplicates.
WithDisallowedTools(...string) Disallowed tools. Same comma/dedup behavior as WithTools.
WithBuiltinTools(...string) Restrict available built-in tools. "default" for all, "" for none, or names like "Bash", "Edit".
WithPermissionMode(PermissionMode) Permission mode (PermissionDefault, PermissionPlan, PermissionAcceptEdits, PermissionBypass, PermissionDontAsk, PermissionAuto).
WithDangerouslySkipPermissions() Bypass all permission checks. Emits both --allow-dangerously-skip-permissions and --dangerously-skip-permissions. Only for sandboxed environments.
WithBare() Minimal mode: skip hooks, LSP, plugin sync, attribution, auto-memory, background prefetches, keychain reads, CLAUDE.md auto-discovery.
WithJSONSchema(string) JSON schema for structured output validation.
WithMaxBudget(float64) Maximum cost budget in USD.
WithMaxTurns(int) Maximum agentic turns before stopping.
WithWorkDir(string) Working directory for the CLI process.
WithAddDirs(...string) Additional directories to allow tool access to.
WithSessionID(string) Resume a specific session.
WithSessionName(string) Display name for the session (shown in /resume and terminal title).
WithForkSession() Fork from the session (requires WithSessionID).
WithContinue() Continue the most recent session.
WithEffort(EffortLevel) Reasoning effort (EffortLow, EffortMedium, EffortHigh, EffortXHigh, EffortMax). DefaultEffort is EffortXHigh.
WithThinking(ThinkingConfig) Extended thinking mode. Use ThinkingAdaptive{} (emits --thinking adaptive), ThinkingEnabled{BudgetTokens: N} (emits --max-thinking-tokens N), or ThinkingDisabled{} (emits --thinking disabled). Overlaps with WithEffort; prefer WithEffort unless explicit control is needed.
WithTaskBudget(int) Cap total tokens per task. Emits --task-budget. Zero is ignored.
WithMCPConfig(...string) MCP server configs — file paths or inline JSON strings.
WithStrictMCPConfig() Only use MCP servers from WithMCPConfig, ignoring all other MCP configurations.
WithAgent(string) Named agent for the session.
WithAgentDef(string) Custom agent definitions as JSON.
WithIncludePartialMessages() Include partial message chunks (streaming only).
WithSettings(string) Path to settings file.
WithSettingSources(...string) Setting sources (comma-joined).
WithPluginDirs(...string) Plugin directories.
WithResume(string) Resume a session by ID (mutually exclusive with WithSessionID/WithContinue).
WithCanUseTool(ToolPermissionFunc) Tool permission callback (sessions only).
WithUserInput(UserInputFunc) Dedicated callback for AskUserQuestion tool requests (sessions only).
WithControlTimeout(time.Duration) Timeout for control protocol round-trips (default: 30s). Sessions only.
WithInitTimeout(time.Duration) Timeout for the initialize handshake (default: 60s). Increase if MCP servers are slow to connect. Sessions only.
WithPermissionPromptToolName(string) Custom permission prompt tool name (default: "stdio"). Sessions only.
WithEnv(map[string]string) Additional environment variables. Can override CLAUDE_CODE_ENTRYPOINT (default: "sdk-go").
WithExtraArgs(map[string]string) Arbitrary --key value flags for forward compatibility. Empty value emits flag only.
WithUser(string) User identifier passed to the CLI.
WithStderrCallback(func(string)) Called per stderr line in addition to StderrEvent emission.
WithDebugFile(string) Write CLI debug logs to a file path.
WithDisableSlashCommands() Disable all slash command / skill processing in prompts.
WithFileCheckpointing() Enable SDK file checkpointing via CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING env var.
WithReplayUserMessages() Echo user messages back on stdout as UserEvent with IsReplay=true, confirming message delivery. Useful for tracking SendMessage acknowledgment during active turns. Sessions only.

Options set at call time replace (not merge with) client-level defaults.

Error handling

import "errors"

text, _, err := client.RunText(ctx, prompt)

// Empty output (no text events received)
if errors.Is(err, claudecli.ErrEmptyOutput) { ... }

// Classify API errors with sentinel errors
if errors.Is(err, claudecli.ErrInvalidRequest) { ... }  // 400 bad request
if errors.Is(err, claudecli.ErrAuth) { ... }             // 401 authentication
if errors.Is(err, claudecli.ErrBilling) { ... }           // 402 billing/payment
if errors.Is(err, claudecli.ErrPermission) { ... }        // 403 permission denied
if errors.Is(err, claudecli.ErrNotFound) { ... }          // 404 not found
if errors.Is(err, claudecli.ErrRequestTooLarge) { ... }   // 413 request too large
if errors.Is(err, claudecli.ErrRateLimit) { ... }         // 429 rate limited
if errors.Is(err, claudecli.ErrAPI) { ... }               // 500 internal API error
if errors.Is(err, claudecli.ErrOverloaded) { ... }        // 529 API overloaded
if errors.Is(err, claudecli.ErrMaxTurns) { ... }          // max turns reached
if errors.Is(err, claudecli.ErrContextWindowExceeded) { ... } // context window exceeded

// Extract turn count from max turns errors
var mte *claudecli.MaxTurnsError
if errors.As(err, &mte) {
    fmt.Printf("hit max turns: %d\n", mte.Turns)
}

// Extract retry timing from rate limit errors
var rlErr *claudecli.RateLimitError
if errors.As(err, &rlErr) {
    time.Sleep(rlErr.RetryAfter)
}

// CLI process failure with exit code and stderr
// Error.Error() returns a concise message (auto-inferred from stderr patterns
// like "command not found", "permission denied", "no such file or directory").
// For full stderr, access the Stderr field directly.
// LastEvents contains the last 10 raw JSONL lines from stdout for post-mortem
// diagnostics when stderr and classified errors yield no information.
var cliErr *claudecli.Error
if errors.As(err, &cliErr) {
    fmt.Println(cliErr.ExitCode)
    fmt.Println(cliErr.Message)    // concise, auto-inferred from stderr
    fmt.Println(cliErr.Stderr)     // full stderr output
    fmt.Println(cliErr.LastEvents) // last 10 raw JSONL lines from stdout
}

// RunJSON/RunBlockingJSON failed to parse response as JSON
var ue *claudecli.UnmarshalError
if errors.As(err, &ue) {
    fmt.Println(ue.RawText) // original model output before fence stripping
}

Architecture

claudecli-go/
  doc.go         Package overview, thread safety, prerequisites
  event.go       Sealed Event interface, event types
  model.go       Model constants, EffortLevel constants (including DefaultEffort)
  permission.go  PermissionMode constants
  option.go      Functional options + CLI arg builder
  executor.go         Executor interface, LocalExecutor, FixtureExecutor, BidiFixtureExecutor
  executor_unix.go    Unix process group attrs (Setpgid, SIGTERM), stdbuf wrapping
  executor_windows.go Windows no-op platform attrs
  parse.go       JSONL stream parser (decoupled from process lifecycle)
  stream.go      Stream with State(), Events(), Next(), Wait(), Close()
  client.go      Client struct, Run/RunText/RunJSON/Connect, package-level shortcuts
  session.go     Interactive session with bidirectional control protocol
  control.go     Control message types, ContentBlock/ImageSource for multimodal input
  blocking.go    RunBlocking/RunBlockingJSON — non-streaming JSON output mode
  auth.go        AuthStatus (defensive three-state parsing), AuthLogin (BROWSER capture + localhost callback), AuthLogout, LoginProcess
  pool.go        Pool multi-session registry, FormatAgentMessage, SendAgentMessage
  version.go     sdkVersion (module version reported to CLI, build-info sourced), SDKVersion dev fallback, MinCLIVersion, CLI version checking with semver parsing
  internal.go    Stderr ring buffer, processExitError with heuristic inference, code fence stripping
  error.go       Sentinel errors (ErrInvalidRequest, ErrAuth, ErrBilling, ErrPermission, ErrNotFound, ErrRequestTooLarge, ErrRateLimit, ErrAPI, ErrOverloaded, ErrMaxTurns, ErrContextWindowExceeded), RateLimitError, MaxTurnsError, Error, UnmarshalError

Layers:

  1. Parse (parse.go) — JSONL deserialization into typed events. Zero coupling to process execution. Testable with fixtures. Returns immediately after the result event to avoid blocking on CLI hang bugs.
  2. Execute (executor.go, executor_{unix,windows}.go) — Executor interface abstracts process spawning. LocalExecutor handles the real CLI with platform-aware command construction: stdbuf -oL wrapping on Linux, npm .cmd shim bypass on Windows.
  3. Client (client.go) — Composes executor + options. Builds CLI args, starts process synchronously, reads events in goroutine. Synthesizes ResultEvent if CLI exits without one. Connect() creates interactive sessions.
  4. Session (session.go) — Bidirectional control protocol over stdin/stdout. Handles initialize handshake, control request routing (tool permissions), and multi-turn conversations. Connect() marks the session ready immediately after the initialize handshake (CLI 2.1.81+ defers the system init event until the first user message).
  5. Blocking (blocking.go) — Non-streaming path using --output-format json. Simpler execution model for RunBlocking/RunBlockingJSON.

Known limitations / TODO

  • JSONL format is unversioned — Claude CLI's stream-json output format is not formally versioned by Anthropic. Tested with Claude Code CLI 2.x. Breaking changes across CLI versions are possible.
  • No retry/backoffRateLimitEvent is emitted (with ResetsAt timestamp and RateLimitType) but the package does not automatically retry or backoff. Consumers must implement their own retry logic.
  • stdbuf recommended on LinuxLocalExecutor uses stdbuf -oL for line-buffered stdout on Linux when available, falling back to direct execution without it.
  • MCP server startup can be slow — The CLI waits for MCP server connections during the initialize handshake. With many MCP servers configured, this can take 30+ seconds. The WithInitTimeout option (default 60s) controls this; increase it if Connect() times out.
  • WithExtraArgs validates reserved flags — Passing print, output-format, input-format, or verbose via WithExtraArgs panics at construction time to prevent conflicting CLI arguments.
  • Blocking stderr capped at 10 MBRunBlocking caps stderr collection at 10 MB. The streaming path uses a 1000-line ring buffer.
  • Fork-session needs a persisted parentRunBlocking by default emits --no-session-persistence, so the parent must be started with WithSessionID, WithResume/WithContinue, or via Connect for WithForkSession to find the parent on disk.
  • AuthStatus fail-close — When the CLI exits 0 with non-JSON output, AuthStatus returns AuthStateUnknown (not AuthStateAuthenticated). Callers should handle this explicitly.
  • Thinking text may be hidden — The CLI can emit ThinkingEvents with empty Content but a set Signature (the model thought, but the text was withheld). No SDK option changes this. Distinguish "thinking hidden" from "no thinking" via Content == "" && Signature != "".
  • Workflows emit two ResultEvents — A dynamic workflow run produces two result events: the first reports it launched in the background, the second carries the real answer once it completes. Consume the last ResultEvent. A workflow also does not survive its parent CLI process (the run settles at status: "killed").
  • Workflow on-disk state is an internal layoutWatchWorkflow/ReadWorkflowSnapshot read CLI run-state files (~/.claude/projects/.../workflows/<runId>.json) whose paths and JSON shape are undocumented and may change across CLI versions. They parse defensively and preserve Raw, but treat this as best-effort. File GC/lifetime is unverified.

Documentation

Overview

Package claudecli invokes the Claude Code CLI as a subprocess with typed streaming events, functional options, and pluggable execution.

Prerequisites

The claude CLI binary must be installed and on PATH. See https://docs.anthropic.com/en/docs/claude-code for installation.

Thread Safety

Client is safe for concurrent use — each Run call is independent. Stream is single-consumer: only one goroutine should read events. Wait() is safe to call concurrently (idempotent, returns cached result).

The package-level Run and RunText functions use a shared default client and are safe for concurrent use.

RunJSON

RunJSON is a package-level generic function rather than a Client method because Go does not allow type parameters on methods. Use it as:

result, info, err := claudecli.RunJSON[MyType](ctx, client, prompt)

RunJSON automatically strips markdown code fences (```json ... ```) before unmarshaling. On parse failure it returns *UnmarshalError which contains the original model output in RawText for debugging.

Blocking Mode

RunBlocking and RunBlockingJSON use --output-format json instead of streaming. Simpler and avoids known CLI bugs with hanging stdout. RunBlockingJSON prefers the schema-validated structured_output field when WithJSONSchema is set.

result, err := client.RunBlocking(ctx, prompt)
val, result, err := claudecli.RunBlockingJSON[T](ctx, client, prompt)

Index

Examples

Constants

View Source
const MinCLIVersion = "2.0.0"

MinCLIVersion is the minimum Claude CLI version required by this SDK.

View Source
const SDKVersion = "0.0.0-dev"

SDKVersion is the fallback version reported to the CLI via CLAUDE_AGENT_SDK_VERSION when this module's version cannot be read from the enclosing binary's module info — e.g. local dev builds, or when this repo is itself the main module. Consumers that import a tagged release advertise their pinned version automatically (see sdkVersion), so this placeholder is only ever seen for unversioned builds.

Variables

View Source
var (
	ErrInvalidRequest        = errors.New("invalid request")
	ErrAuth                  = errors.New("authentication failed")
	ErrBilling               = errors.New("billing error")
	ErrPermission            = errors.New("permission denied")
	ErrNotFound              = errors.New("not found")
	ErrRequestTooLarge       = errors.New("request too large")
	ErrRateLimit             = errors.New("rate limit")
	ErrAPI                   = errors.New("API error")
	ErrOverloaded            = errors.New("API overloaded")
	ErrMaxTurns              = errors.New("max turns reached")
	ErrContextWindowExceeded = errors.New("context window exceeded") // detected from error message content, not error type
)

Sentinel errors for CLI error classification. Use errors.Is to check from any error in the chain (Error, ErrorEvent, etc).

View Source
var ErrEmptyOutput = errors.New("claudecli: empty output")

ErrEmptyOutput is returned when RunText/RunJSON receive no text output.

View Source
var ErrNoManifestPath = errors.New("claudecli: cannot derive workflow manifest path")

ErrNoManifestPath is returned when a workflow's manifest path cannot be derived from a WorkflowLaunch (missing runId / script path).

Functions

func AuthLogout

func AuthLogout(ctx context.Context) error

func CheckCLIVersion

func CheckCLIVersion(ctx context.Context, binaryPath string) error

CheckCLIVersion runs `claude -v` and returns an error if the version is below MinCLIVersion. Returns nil if the version is OK or cannot be determined (fail-open).

func FormatAgentMessage

func FormatAgentMessage(senderName, content string) string

FormatAgentMessage formats a message as coming from another agent session. The formatted string is suitable for injection via Session.SendMessage().

func ModelDisplayName

func ModelDisplayName(id string) string

ModelDisplayName converts a model identifier into a human-readable name, e.g. "claude-opus-4-8" -> "Opus 4.8", "claude-haiku-4-5-20251001" -> "Haiku 4.5".

The name is parsed from the ID's structure (tier + major.minor) rather than a lookup table, so new model releases render correctly without code changes. A trailing 8-digit date stamp and any bracketed suffix (e.g. "[1m]") are ignored. Bare aliases work too: "opus" -> "Opus". If no opus/sonnet/haiku tier is found, the input is returned unchanged (with any bracketed suffix stripped).

func ParseEvents

func ParseEvents(ctx context.Context, r io.Reader, ch chan<- Event)

ParseEvents reads JSONL from r and sends parsed events to ch. Does not close ch — the caller is responsible for closing it. Safe to call from a goroutine.

When ctx is cancelled, ParseEvents stops processing new lines and returns. Note: cancellation does not unblock a pending scanner.Scan() — the caller must close the reader (e.g. by killing the subprocess) to unblock reads.

func WatchWorkflow

func WatchWorkflow(ctx context.Context, launch *WorkflowLaunch, opts ...WatchOption) (<-chan WorkflowSnapshot, error)

WatchWorkflow polls a launched workflow's on-disk manifest and streams a WorkflowSnapshot whenever its contents change, until the run reaches a terminal status or ctx is cancelled. The returned channel is closed when watching ends; the final snapshot sent before closure is the terminal one (unless ctx was cancelled first).

This monitors the run out-of-band: it does not consume the event stream and works from any goroutine or process that can read ~/.claude/projects. A not-yet-written manifest is tolerated — WatchWorkflow keeps polling until it appears. Note the workflow does not survive its parent CLI process; if that process exits, the manifest settles at a terminal status ("killed").

It returns an error only when the manifest path cannot be derived; all runtime read/parse hiccups are treated as transient and retried.

Types

type ActivityState

type ActivityState string

ActivityState describes the high-level activity of a CLI session. Distinct from the lifecycle State (starting/idle/running/done/failed): it tells consumers whether silence in the event stream means the model is generating, a tool is executing, or the session is between turns.

const (
	// ActivityIdle means the session is between turns (no query in flight).
	ActivityIdle ActivityState = "idle"
	// ActivityThinking means the model is generating.
	ActivityThinking ActivityState = "thinking"
	// ActivityAwaitingToolResult means at least one top-level tool_use has
	// been emitted without its matching tool_result; the CLI is executing
	// the tool (or waiting for a permission callback).
	ActivityAwaitingToolResult ActivityState = "awaiting_tool_result"
)

type AgentInput

type AgentInput struct {
	Description     string `json:"description"`
	Prompt          string `json:"prompt"`
	SubagentType    string `json:"subagent_type"`
	Name            string `json:"name"`
	RunInBackground bool   `json:"run_in_background"`
	Model           string `json:"model"`
	Isolation       string `json:"isolation"`
	Mode            string `json:"mode"`
	TeamName        string `json:"team_name"`
}

AgentInput contains the parsed fields from an Agent tool invocation.

type AgentResult

type AgentResult struct {
	Status            string
	Prompt            string
	AgentID           string
	AgentType         string
	Content           []ToolContent
	TotalDurationMs   int
	TotalTokens       int
	TotalToolUseCount int
}

AgentResult contains metadata from a completed subagent execution. Present on UserEvent when the event carries the final output of an Agent tool call.

type AuthLoginOption

type AuthLoginOption func(*authLoginConfig)

AuthLoginOption configures AuthLogin behavior.

func WithAuthMethod

func WithAuthMethod(m AuthMethod) AuthLoginOption

WithAuthMethod sets the authentication provider.

func WithLoginEmail

func WithLoginEmail(email string) AuthLoginOption

WithLoginEmail pre-populates the email on the login page.

func WithNoBrowser

func WithNoBrowser() AuthLoginOption

WithNoBrowser suppresses the CLI's automatic browser opening by setting BROWSER=true in the subprocess environment. Callers that already have the login URL (e.g. from LoginProcess.URL) can use this to avoid duplicate tabs.

func WithSSO

func WithSSO() AuthLoginOption

WithSSO forces the SSO login flow.

type AuthMethod

type AuthMethod string

AuthMethod selects the authentication provider for login.

const (
	AuthMethodClaudeAI AuthMethod = "claudeai" // Claude subscription (default)
	AuthMethodConsole  AuthMethod = "console"  // Anthropic Console (API billing)
)

type AuthState

type AuthState string

AuthState represents a three-state authentication status.

const (
	AuthStateAuthenticated   AuthState = "authenticated"
	AuthStateUnauthenticated AuthState = "unauthenticated"
	AuthStateUnknown         AuthState = "unknown"
)

type AuthStatusEvent

type AuthStatusEvent struct {
	IsAuthenticating bool
	Output           string
	Error            string
}

AuthStatusEvent is emitted when the CLI reports authentication status changes during a session (e.g. token refresh).

func (*AuthStatusEvent) String

func (e *AuthStatusEvent) String() string

type AuthStatusResult

type AuthStatusResult struct {
	// Status is the three-state auth status derived from defensive parsing.
	// Use this instead of LoggedIn for robust auth checks.
	Status AuthState `json:"-"`

	// Message contains a human-readable explanation when Status is not
	// AuthStateAuthenticated (e.g. "not logged in", version mismatch).
	Message string `json:"-"`

	LoggedIn         bool   `json:"loggedIn"`
	AuthMethod       string `json:"authMethod,omitempty"`  // e.g. "claude.ai", "api-key"
	APIProvider      string `json:"apiProvider,omitempty"` // e.g. "firstParty", "bedrock", "vertex"
	Email            string `json:"email,omitempty"`
	OrgID            string `json:"orgId,omitempty"`
	OrgName          string `json:"orgName,omitempty"`
	SubscriptionType string `json:"subscriptionType,omitempty"` // e.g. "team", "pro"
}

AuthStatusResult represents the authentication state returned by the CLI.

func AuthStatus

func AuthStatus(ctx context.Context) (*AuthStatusResult, error)

type BidiFixtureExecutor

type BidiFixtureExecutor struct {
	StdoutWriter io.WriteCloser
	StdinReader  io.ReadCloser
	// contains filtered or unexported fields
}

BidiFixtureExecutor supports bidirectional I/O for testing sessions.

func NewBidiFixtureExecutor

func NewBidiFixtureExecutor() *BidiFixtureExecutor

NewBidiFixtureExecutor creates a bidirectional executor for session tests.

func (*BidiFixtureExecutor) Start

type BlockingResult

type BlockingResult struct {
	Text             string
	StructuredOutput json.RawMessage
	Subtype          string
	SessionID        string
	CostUSD          float64
	Duration         time.Duration
	NumTurns         int
	IsError          bool
	Usage            Usage
	Stderr           string
}

BlockingResult contains the output from a non-streaming CLI invocation using --output-format json. Unlike streaming, this returns only the final result with no intermediate events.

func RunBlockingJSON

func RunBlockingJSON[T any](ctx context.Context, c *Client, prompt string, opts ...Option) (T, *BlockingResult, error)

RunBlockingJSON runs a prompt with --output-format json and unmarshals the result into T. When WithJSONSchema is set, parses the schema-validated structured_output field. Otherwise, parses the text result (with code fence stripping).

type CLIExitEvent

type CLIExitEvent struct {
	Reason   ExitReason
	ExitCode int    // process exit code; -1 if not an *exec.ExitError
	Signal   string // signal name (e.g. "SIGKILL", "SIGTERM"); empty if not signaled
	Err      error  // underlying error (e.g. *Error from processExitError); nil on clean exit
	At       time.Time
}

CLIExitEvent is the last event emitted before the events channel closes. Describes the cause of the CLI process termination so consumers can distinguish a clean shutdown from a crash, signal kill, or context cancel.

Backward compatible: callers that don't type-switch for CLIExitEvent simply ignore it.

func (*CLIExitEvent) String

func (e *CLIExitEvent) String() string

type CLIStateChangeEvent

type CLIStateChangeEvent struct {
	State ActivityState
	At    time.Time
}

CLIStateChangeEvent signals a transition in activity state. Emitted immediately BEFORE the triggering event (e.g. the first top-level ToolUseEvent of a turn is preceded by a transition to ActivityAwaitingToolResult), so consumers can update their view of the session before processing the event itself.

Backward compatible: callers that don't care about activity state can ignore it in their type switch.

func (*CLIStateChangeEvent) String

func (e *CLIStateChangeEvent) String() string

type CLIToolProgressEvent

type CLIToolProgressEvent struct {
	ToolUseID      string
	ToolName       string
	ElapsedSeconds float64
	TaskID         string
}

CLIToolProgressEvent is emitted by the CLI (not the SDK) when a tool execution is in progress. Unlike the synthetic ToolProgressEvent (emitted by Session's ticker), this comes directly from the CLI's JSONL stream as a top-level "tool_progress" event.

func (*CLIToolProgressEvent) String

func (e *CLIToolProgressEvent) String() string

type Client

type Client struct {
	// contains filtered or unexported fields
}

Client wraps a Claude CLI executor with default options.

func New

func New(defaults ...Option) *Client

New creates a client. ClientOption values configure the client; Option values set defaults for all Run/Connect calls. Use WithBinaryPath to override the CLI binary location.

func NewClient

func NewClient(clientOpts []ClientOption, defaults ...Option) *Client

NewClient creates a client with explicit client options.

func NewWithExecutor

func NewWithExecutor(executor Executor, defaults ...Option) *Client

NewWithExecutor creates a client with a specific executor and default options.

Example (Fixture)
package main

import (
	"context"
	"fmt"

	"github.com/allbin/claudecli-go"
)

func main() {
	exec, err := claudecli.NewFixtureExecutorFromFile("testdata/basic.jsonl")
	if err != nil {
		panic(err)
	}
	client := claudecli.NewWithExecutor(exec)

	text, result, err := client.RunText(context.Background(), "ignored prompt")
	if err != nil {
		panic(err)
	}
	fmt.Printf("Got %d chars, cost $%.4f\n", len(text), result.CostUSD)
}
Output:
Got 28 chars, cost $0.0124

func (*Client) AuthLogin

func (c *Client) AuthLogin(ctx context.Context, opts ...AuthLoginOption) (*LoginProcess, error)

AuthLogin starts an OAuth login flow. Returns a LoginProcess with the authorization URL once it's available. Call Wait on the returned process to block until login completes.

func (*Client) AuthLogout

func (c *Client) AuthLogout(ctx context.Context) error

AuthLogout signs out of the current Anthropic account.

func (*Client) AuthStatus

func (c *Client) AuthStatus(ctx context.Context) (*AuthStatusResult, error)

AuthStatus returns the current authentication state. Errors are reserved for infrastructure failures (binary not found, timeout). "Not logged in" is returned as a result with Status == AuthStateUnauthenticated, not an error.

func (*Client) Connect

func (c *Client) Connect(ctx context.Context, opts ...Option) (*Session, error)

Connect starts an interactive session with bidirectional control protocol. Returns a Session for multi-turn conversations, permission callbacks, etc.

func (*Client) Run

func (c *Client) Run(ctx context.Context, prompt string, opts ...Option) *Stream

Run starts a streaming Claude session. Returns a Stream for event consumption. The executor is started synchronously so the process is running when Run returns. Callers can safely defer cleanup of resources the process depends on (temp files, mounts).

func (*Client) RunBlocking

func (c *Client) RunBlocking(ctx context.Context, prompt string, opts ...Option) (*BlockingResult, error)

RunBlocking runs a prompt with --output-format json (no streaming). Simpler and more reliable than streaming when intermediate events aren't needed. When WithJSONSchema is set, the validated output is available in StructuredOutput.

func (*Client) RunText

func (c *Client) RunText(ctx context.Context, prompt string, opts ...Option) (string, *ResultEvent, error)

RunText runs a prompt and returns the accumulated text output.

type ClientOption

type ClientOption func(*Client)

ClientOption configures the Client itself (not individual Run calls).

func WithLogger

func WithLogger(l *slog.Logger) ClientOption

WithLogger sets a structured logger for auth and diagnostic output. If nil or not set, a no-op logger is used.

type CompactBoundaryEvent

type CompactBoundaryEvent struct {
	SessionID string
	Trigger   string
	PreTokens int
	Raw       json.RawMessage
}

CompactBoundaryEvent marks the compaction boundary. Trigger is "manual" (user invoked /compact) or "auto" (context limit). PreTokens is the token count before compaction. Raw contains the full compact_metadata JSON for forward compatibility.

func (*CompactBoundaryEvent) String

func (e *CompactBoundaryEvent) String() string

type CompactStatusEvent

type CompactStatusEvent struct {
	SessionID string
	Status    string
}

CompactStatusEvent is emitted when the CLI's compaction status changes. Status is "compacting" when compaction starts, or "" when cleared.

func (*CompactStatusEvent) String

func (e *CompactStatusEvent) String() string

type ContentBlock

type ContentBlock struct {
	// contains filtered or unexported fields
}

ContentBlock is an opaque content block for multimodal messages. Create with TextBlock, ImageBlock, or DocumentBlock.

func DocumentBlock

func DocumentBlock(mediaType string, data []byte) ContentBlock

DocumentBlock creates a document content block (e.g. PDF).

func ImageBlock

func ImageBlock(mediaType string, data []byte) ContentBlock

ImageBlock creates an image content block. mediaType: "image/png", "image/jpeg", "image/gif", or "image/webp".

func TextBlock

func TextBlock(text string) ContentBlock

TextBlock creates a text content block.

func (ContentBlock) MarshalJSON

func (b ContentBlock) MarshalJSON() ([]byte, error)

type ContextManagementEvent

type ContextManagementEvent struct {
	Raw json.RawMessage
}

ContextManagementEvent is emitted when the CLI compresses or summarizes older conversation turns to stay within the context window. Raw contains the full JSON payload for forward compatibility.

func (*ContextManagementEvent) String

func (e *ContextManagementEvent) String() string

type ContextSnapshot

type ContextSnapshot struct {
	InputTokens              int
	CacheReadInputTokens     int
	CacheCreationInputTokens int
	OutputTokens             int
	ContextWindow            int
}

ContextSnapshot captures token usage from the last API call in a streaming session. Populated from the last message_start + message_delta pair observed in stream_event events. Nil on ResultEvent when WithIncludePartialMessages is not enabled.

type ControlRequestEvent

type ControlRequestEvent struct {
	RequestID string
	Subtype   string
	Body      json.RawMessage
}

ControlRequestEvent is emitted when the CLI sends a control request. In session mode, these are handled internally and not exposed.

func (*ControlRequestEvent) String

func (e *ControlRequestEvent) String() string

type EffortLevel

type EffortLevel string

EffortLevel controls reasoning intensity. WithEffort emits --effort <level>; how a given model uses it is the model's business.

const (
	EffortLow    EffortLevel = "low"
	EffortMedium EffortLevel = "medium"
	EffortHigh   EffortLevel = "high"
	EffortXHigh  EffortLevel = "xhigh"
	EffortMax    EffortLevel = "max"

	// DefaultEffort matches the Claude Code CLI default.
	DefaultEffort = EffortXHigh
)

type Error

type Error struct {
	ExitCode int
	Stderr   string
	Message  string
	// LastEvents contains the last few raw JSONL lines from stdout before
	// the process exited. Useful for diagnosing failures where stderr and
	// classified errors yield no information.
	LastEvents []string
	// contains filtered or unexported fields
}

Error represents a CLI process failure with context.

func (*Error) Error

func (e *Error) Error() string

func (*Error) Unwrap

func (e *Error) Unwrap() []error

type ErrorEvent

type ErrorEvent struct {
	Err   error
	Fatal bool
}

ErrorEvent is emitted when an error occurs during streaming. Fatal errors (process failures) transition the stream to StateFailed. Non-fatal errors (e.g. malformed JSONL) are emitted but don't affect state.

func (*ErrorEvent) Error

func (e *ErrorEvent) Error() string

func (*ErrorEvent) String

func (e *ErrorEvent) String() string

func (*ErrorEvent) Unwrap

func (e *ErrorEvent) Unwrap() error

type Event

type Event interface {
	// contains filtered or unexported methods
}

Event is a sealed interface representing a Claude CLI stream event. Consumers use type switches or type assertions to access event data.

type Executor

type Executor interface {
	Start(ctx context.Context, cfg *StartConfig) (*Process, error)
}

Executor controls how the Claude CLI process is spawned. Implement this interface to customize execution (e.g. Docker, SSH).

type ExitReason

type ExitReason string

ExitReason classifies why the CLI process terminated. Carried by CLIExitEvent so consumers can give users actionable messages and distinguish clean shutdowns from crashes.

const (
	// ExitReasonNormal indicates the process exited cleanly with code 0.
	ExitReasonNormal ExitReason = "normal"
	// ExitReasonKilled indicates the process was terminated by a signal
	// (SIGKILL, SIGTERM, OOM kill, etc.).
	ExitReasonKilled ExitReason = "killed"
	// ExitReasonCrashed indicates the process exited with a non-zero code
	// without being signaled (CLI bug, panic, fatal API error).
	ExitReasonCrashed ExitReason = "crashed"
	// ExitReasonContextCanceled indicates the session context was canceled
	// (Close timeout, parent ctx cancel) and the SDK terminated the process.
	ExitReasonContextCanceled ExitReason = "context_canceled"
	// ExitReasonUnknown is used when the cause cannot be classified.
	ExitReasonUnknown ExitReason = "unknown"
)

type FailedFile

type FailedFile struct {
	Filename string
	Error    string
}

FailedFile describes a file that failed to persist.

type FilesPersistedEvent

type FilesPersistedEvent struct {
	Files  []PersistedFile
	Failed []FailedFile
}

FilesPersistedEvent is emitted when the CLI confirms file persistence. Files lists successfully persisted files; Failed lists files that failed.

func (*FilesPersistedEvent) String

func (e *FilesPersistedEvent) String() string

type FixtureExecutor

type FixtureExecutor struct {
	// contains filtered or unexported fields
}

FixtureExecutor replays JSONL from an io.Reader for testing.

func NewFixtureExecutor

func NewFixtureExecutor(r io.Reader) *FixtureExecutor

NewFixtureExecutor creates an executor that replays JSONL fixtures.

func NewFixtureExecutorFromFile

func NewFixtureExecutorFromFile(path string) (*FixtureExecutor, error)

NewFixtureExecutorFromFile creates an executor that replays a JSONL file.

func (*FixtureExecutor) Start

type HookEvent

type HookEvent struct {
	Subtype   string // "hook_started" or "hook_response"
	HookID    string
	HookName  string
	HookEvent string // e.g. "SessionStart", "PreToolUse"
	UUID      string
	SessionID string

	// hook_response only
	Output   string
	Stdout   string
	Stderr   string
	ExitCode int
	Outcome  string

	// Raw contains the full JSON line for forward compatibility.
	Raw json.RawMessage
}

HookEvent is emitted when the CLI runs a configured hook (SessionStart, PreToolUse, PostToolUse, etc.). Subtype is "hook_started" when the hook begins and "hook_response" when it finishes.

On "hook_started" only HookID, HookName, HookEvent, UUID, and SessionID are populated.

On "hook_response" Output, Stdout, Stderr, ExitCode, and Outcome are also populated. Outcome is typically "success" or "failure".

func (*HookEvent) String

func (e *HookEvent) String() string

type InitEvent

type InitEvent struct {
	SessionID  string
	Model      string
	Tools      []string
	Agents     []string
	Skills     []string
	MCPServers []MCPServerStatus
}

InitEvent is emitted by the CLI at the start of a session.

func (*InitEvent) ModelDisplayName

func (e *InitEvent) ModelDisplayName() string

ModelDisplayName returns the human-readable name of the session model, e.g. "Opus 4.8". It is shorthand for ModelDisplayName(e.Model).

func (*InitEvent) String

func (e *InitEvent) String() string

type LocalExecutor

type LocalExecutor struct {
	// BinaryPath overrides the CLI binary. Defaults to "claude".
	BinaryPath string
	// contains filtered or unexported fields
}

LocalExecutor spawns the Claude CLI as a local subprocess.

func NewLocalExecutor

func NewLocalExecutor() *LocalExecutor

NewLocalExecutor returns an executor that runs Claude CLI locally.

func (*LocalExecutor) Start

func (e *LocalExecutor) Start(ctx context.Context, cfg *StartConfig) (*Process, error)

type LoginProcess

type LoginProcess struct {
	URL         string // manual-visit URL (platform.claude.com redirect)
	AutoOpenURL string // browser URL (localhost redirect, may be empty)
	// contains filtered or unexported fields
}

LoginProcess represents an in-progress OAuth login.

Two URLs are available:

  • URL: the manual-visit URL (redirect_uri=platform.claude.com). Shows CODE#STATE to the user, but the code cannot be submitted programmatically.
  • AutoOpenURL: the browser URL (redirect_uri=localhost:PORT). After authorizing, the browser redirects to localhost which may fail if the browser is on a different machine. Use SubmitCode with the failed redirect URL to complete.

For remote/headless setups, show AutoOpenURL to the user. After authorizing, they copy the localhost redirect URL (from the browser error page) and pass it to SubmitCode.

func AuthLogin

func AuthLogin(ctx context.Context, opts ...AuthLoginOption) (*LoginProcess, error)

func (*LoginProcess) CallbackPort

func (p *LoginProcess) CallbackPort() int

CallbackPort returns the port of the CLI's local OAuth callback server. Returns 0 if the port could not be determined (e.g. BROWSER capture failed).

func (*LoginProcess) Cancel

func (p *LoginProcess) Cancel() error

Cancel terminates the login process.

func (*LoginProcess) SubmitCode

func (p *LoginProcess) SubmitCode(code string) error

SubmitCode completes the OAuth flow by submitting an authorization code to the CLI's local callback server. Accepts either:

The code must have been issued for the AutoOpenURL flow (redirect_uri=localhost), not the manual URL flow (redirect_uri=platform.claude.com).

func (*LoginProcess) Wait

func (p *LoginProcess) Wait() error

Wait blocks until the login process completes. Returns nil on success.

type MCPServerStatus

type MCPServerStatus struct {
	Name   string `json:"name"`
	Status string `json:"status"`
}

MCPServerStatus describes a connected MCP server and its connection state.

type MaxTurnsError

type MaxTurnsError struct {
	Turns   int
	Message string
}

MaxTurnsError carries the turn limit that was reached. Use errors.As to extract Turns from any error in the chain.

func (*MaxTurnsError) Error

func (e *MaxTurnsError) Error() string

func (*MaxTurnsError) Is

func (e *MaxTurnsError) Is(target error) bool

type Model

type Model string

Model represents a Claude model identifier.

const (
	ModelHaiku  Model = "haiku"
	ModelSonnet Model = "sonnet"
	ModelOpus   Model = "opus"
	// ModelFable is Claude Fable 5, the most capable tier (above Opus).
	ModelFable Model = "fable"

	// DefaultModel is used when no model is specified.
	DefaultModel = ModelSonnet
)

type ModelUsage

type ModelUsage struct {
	InputTokens       int
	OutputTokens      int
	CacheReadTokens   int
	CacheCreateTokens int
	CostUSD           float64
	ContextWindow     int
	MaxOutputTokens   int
	WebSearchRequests int
	WebFetchRequests  int
}

ModelUsage contains per-model usage statistics including context window metadata. The result event reports one entry per model used during the session.

func (ModelUsage) TotalTokens

func (m ModelUsage) TotalTokens() int

TotalTokens returns the sum of every token field for this model.

type Option

type Option func(*options)

Option configures a Run call. Options set at call time replace (not merge with) client-level defaults.

func WithAddDirs

func WithAddDirs(dirs ...string) Option

WithAddDirs adds directories the CLI tools can access beyond the working directory.

func WithAgent

func WithAgent(name string) Option

WithAgent selects a named agent for the session.

func WithAgentDef

func WithAgentDef(jsonDef string) Option

WithAgentDef defines custom agents via a JSON string. Example: `{"reviewer": {"description": "Reviews code", "prompt": "You are a code reviewer"}}`.

func WithAppendSystemPrompt

func WithAppendSystemPrompt(p string) Option

func WithAppendSystemPromptFile

func WithAppendSystemPromptFile(p string) Option

func WithBare

func WithBare() Option

WithBare enables minimal mode: skip hooks, LSP, plugin sync, attribution, auto-memory, background prefetches, keychain reads, and CLAUDE.md auto-discovery.

func WithBetas

func WithBetas(betas ...string) Option

func WithBinaryPath

func WithBinaryPath(path string) Option

WithBinaryPath sets the Claude CLI binary path. Only effective when passed to New() (ignored at call time). Defaults to "claude".

func WithBuiltinTools

func WithBuiltinTools(tools ...string) Option

WithBuiltinTools restricts which built-in tools are available. Use "default" for all tools, "" to disable all, or specific names like "Bash", "Edit", "Read". Different from WithTools which controls permission prompts — this controls tool availability.

func WithCanUseTool

func WithCanUseTool(fn ToolPermissionFunc) Option

WithCanUseTool registers a callback for tool permission requests. Only effective with Connect() sessions.

The callback runs in a goroutine and must return promptly. If the session's context is cancelled (e.g. via Close), the SDK stops waiting for the callback but cannot forcibly terminate it. A callback that blocks indefinitely will leak its goroutine. Long-running callbacks should select on ctx.Done().

func WithContinue

func WithContinue() Option

func WithControlTimeout

func WithControlTimeout(d time.Duration) Option

WithControlTimeout sets the timeout for control protocol request/response round-trips (e.g. set_model, mcp operations). Defaults to 30s. Does not affect the initialize handshake — use WithInitTimeout for that. Only effective with Connect() sessions.

func WithDangerouslySkipPermissions

func WithDangerouslySkipPermissions() Option

WithDangerouslySkipPermissions bypasses all permission checks. Emits both --allow-dangerously-skip-permissions and --dangerously-skip-permissions. Only use in sandboxed environments with no internet access.

func WithDebugFile

func WithDebugFile(path string) Option

func WithDisableSlashCommands

func WithDisableSlashCommands() Option

func WithDisallowedTools

func WithDisallowedTools(tools ...string) Option

WithDisallowedTools sets disallowed tools. Accepts individual names or comma-separated lists.

func WithEffort

func WithEffort(level EffortLevel) Option

func WithEnv

func WithEnv(env map[string]string) Option

func WithExtraArgs

func WithExtraArgs(args map[string]string) Option

WithExtraArgs passes additional CLI flags. Keys are flag names without the leading "--". Flags managed by the SDK (print, output-format, input-format, verbose, model) are rejected with a panic to prevent conflicting arguments.

func WithFallbackModel

func WithFallbackModel(m Model) Option

func WithFileCheckpointing

func WithFileCheckpointing() Option

func WithForkSession

func WithForkSession() Option

func WithIncludePartialMessages

func WithIncludePartialMessages() Option

WithIncludePartialMessages enables partial message chunks as they arrive. Only works with streaming output format.

func WithInitTimeout

func WithInitTimeout(d time.Duration) Option

WithInitTimeout sets the timeout for the initialize handshake during Connect(). This is separate from WithControlTimeout because initialization can be slow when the CLI is connecting to MCP servers. Defaults to 60s. Only effective with Connect() sessions.

func WithJSONSchema

func WithJSONSchema(schema string) Option

func WithMCPConfig

func WithMCPConfig(configs ...string) Option

func WithMaxBudget

func WithMaxBudget(usd float64) Option

func WithMaxTurns

func WithMaxTurns(n int) Option

func WithModel

func WithModel(m Model) Option

func WithPermissionMode

func WithPermissionMode(m PermissionMode) Option

func WithPermissionPromptToolName

func WithPermissionPromptToolName(name string) Option

func WithPluginDirs

func WithPluginDirs(dirs ...string) Option

func WithReplayUserMessages

func WithReplayUserMessages() Option

WithReplayUserMessages causes the CLI to echo user messages back on stdout after reading them from stdin. The echoed messages appear as UserEvent with IsReplay=true, confirming message delivery. Only works with interactive sessions (Connect) which use stream-json I/O.

func WithResume

func WithResume(sessionID string) Option

func WithSessionID

func WithSessionID(id string) Option

func WithSessionName

func WithSessionName(name string) Option

func WithSettingSources

func WithSettingSources(sources ...string) Option

func WithSettings

func WithSettings(s string) Option

func WithSkipVersionCheck

func WithSkipVersionCheck() Option

func WithStderrCallback

func WithStderrCallback(fn func(string)) Option

func WithStrictMCPConfig

func WithStrictMCPConfig() Option

func WithSystemPrompt

func WithSystemPrompt(p string) Option

func WithSystemPromptFile

func WithSystemPromptFile(p string) Option

func WithTaskBudget

func WithTaskBudget(totalTokens int) Option

WithTaskBudget caps the total tokens a task may consume. Emits --task-budget to the CLI. Zero or negative values are ignored.

func WithThinking

func WithThinking(cfg ThinkingConfig) Option

WithThinking configures extended thinking mode directly, emitting the CLI's --thinking or --max-thinking-tokens flag. Use ThinkingAdaptive{}, ThinkingEnabled{BudgetTokens: N}, or ThinkingDisabled{}.

Mutually overlapping with WithEffort — if both are set the CLI receives both flags. Prefer WithEffort for normal use; WithThinking is for callers that need explicit budget control or to force thinking off.

func WithTimeout

func WithTimeout(d time.Duration) Option

func WithTools

func WithTools(tools ...string) Option

WithTools sets allowed tools. Accepts individual names or comma-separated lists. Both WithTools("A", "B") and WithTools("A,B") produce one --allowedTools per tool.

func WithUser

func WithUser(user string) Option

func WithUserInput

func WithUserInput(fn UserInputFunc) Option

WithUserInput registers a callback for AskUserQuestion tool requests. Only effective with Connect() sessions.

When registered, AskUserQuestion requests route here instead of the ToolPermissionFunc callback. Other tool permission requests are unaffected. Also adds --permission-prompt-tool (same as WithCanUseTool).

func WithWorkDir

func WithWorkDir(dir string) Option

type PermissionMode

type PermissionMode string

PermissionMode controls what the CLI is allowed to do.

const (
	PermissionDefault     PermissionMode = "default"
	PermissionPlan        PermissionMode = "plan"
	PermissionAcceptEdits PermissionMode = "acceptEdits"
	PermissionBypass      PermissionMode = "bypassPermissions"
	PermissionDontAsk     PermissionMode = "dontAsk"
	PermissionAuto        PermissionMode = "auto"
)

type PermissionResponse

type PermissionResponse struct {
	Allow        bool
	UpdatedInput json.RawMessage
	DenyMessage  string
}

PermissionResponse is returned by the ToolPermissionFunc callback.

type PersistedFile

type PersistedFile struct {
	Filename string
	FileID   string
}

PersistedFile describes a successfully persisted file.

type Pool

type Pool struct {
	// contains filtered or unexported fields
}

Pool is a multi-session registry that multiplexes events from registered Sessions into a single channel. Thread-safe for concurrent Add/Remove/Get/List.

func NewPool

func NewPool() *Pool

NewPool creates a Pool with a buffered event channel (capacity 256).

func (*Pool) Add

func (p *Pool) Add(session *Session, meta SessionMeta) error

Add registers a session with metadata. The pool immediately starts forwarding events from this session to the multiplexed channel. The session must have a SessionID (i.e., its InitEvent must have been received).

func (*Pool) Close

func (p *Pool) Close()

Close stops all forwarders and closes the events channel. Does not close individual sessions. Idempotent.

func (*Pool) CloseAll

func (p *Pool) CloseAll() error

CloseAll closes every registered session in parallel, then closes the pool. Each session gets up to its grace period (default 5s) for the CLI to flush. Returns the first error encountered, or nil if all sessions closed cleanly.

func (*Pool) Events

func (p *Pool) Events() <-chan PoolEvent

Events returns a channel of tagged events from all registered sessions.

func (*Pool) Get

func (p *Pool) Get(sessionID string) (*Session, SessionMeta, bool)

Get returns a session and its metadata by ID.

func (*Pool) List

func (p *Pool) List() []SessionEntry

List returns all registered sessions with metadata.

func (*Pool) Remove

func (p *Pool) Remove(sessionID string) error

Remove unregisters a session. Does not close the session itself.

func (*Pool) SendAgentMessage

func (p *Pool) SendAgentMessage(fromSessionID, toSessionID, content string) error

SendAgentMessage formats and sends an inter-agent message between two sessions registered in the pool. Uses the sender's SessionMeta.Name.

type PoolEvent

type PoolEvent struct {
	SessionID string
	Event     Event
}

PoolEvent wraps an Event with the ID of the session that produced it.

type Process

type Process struct {
	Stdout io.ReadCloser
	Stderr io.ReadCloser
	Stdin  io.WriteCloser // nil when stdin was closed after initial write
	Wait   func() error
}

Process represents a running CLI subprocess.

type ProcessInfo

type ProcessInfo struct {
	// LastStdoutAt is the time the CLI last wrote a line to stdout. Zero
	// until the first line is received.
	LastStdoutAt time.Time
	// ActivityState is the derived activity state (idle, thinking, awaiting_tool_result).
	ActivityState ActivityState
	// Lifecycle is the session lifecycle state.
	Lifecycle State
	// SessionID is the CLI-assigned session ID, or empty if not yet assigned.
	SessionID string
}

ProcessInfo reports process-level state for watchdogs and health monitoring. LastStdoutAt is updated from the stdout scanner loop and is independent of parsed events, so a stall can be distinguished from a quiet turn.

type Question

type Question struct {
	Question    string           `json:"question"`
	Header      string           `json:"header,omitempty"`
	Options     []QuestionOption `json:"options,omitempty"`
	MultiSelect bool             `json:"multiSelect,omitempty"`
}

Question represents a single question from an AskUserQuestion tool call.

type QuestionOption

type QuestionOption struct {
	Label       string `json:"label"`
	Description string `json:"description,omitempty"`
}

QuestionOption is a selectable option within a Question.

type RateLimitError

type RateLimitError struct {
	RetryAfter time.Duration
	Message    string
}

RateLimitError carries retry timing for rate limit errors. Use errors.As to extract RetryAfter from any error in the chain.

func (*RateLimitError) Error

func (e *RateLimitError) Error() string

func (*RateLimitError) Is

func (e *RateLimitError) Is(target error) bool

type RateLimitEvent

type RateLimitEvent struct {
	Status                string
	Utilization           float64
	ResetsAt              int64  // unix timestamp when rate limit window resets (0 if absent)
	RateLimitType         string // e.g. "five_hour", "seven_day", "seven_day_opus"
	OverageStatus         string // overage/pay-as-you-go status if applicable
	OverageResetsAt       int64
	OverageDisabledReason string
	UUID                  string
	SessionID             string
	Raw                   map[string]any // full raw dict for forward compat
}

RateLimitEvent is emitted when the CLI reports rate limit status changes. Status is "allowed", "allowed_warning" (approaching limit), or "rejected" (limit hit).

func (*RateLimitEvent) String

func (e *RateLimitEvent) String() string

type ResultEvent

type ResultEvent struct {
	Text             string
	Subtype          string
	StopReason       string
	StructuredOutput json.RawMessage
	Duration         time.Duration
	CostUSD          float64
	SessionID        string
	NumTurns         int
	Usage            Usage
	// ModelUsage contains per-model usage keyed by model ID.
	ModelUsage map[string]ModelUsage
	// ContextSnapshot captures usage from the last API call's stream events.
	// Nil if no stream_event events were observed.
	ContextSnapshot *ContextSnapshot
}

ResultEvent is emitted at the end of a session (successful or error).

func RunJSON

func RunJSON[T any](ctx context.Context, c *Client, prompt string, opts ...Option) (T, *ResultEvent, error)

RunJSON runs a prompt and unmarshals the text output into T. Markdown code fences (```json ... ``` or ``` ... ```) are stripped before unmarshaling so that model responses wrapped in fences parse correctly.

func RunText

func RunText(ctx context.Context, prompt string, opts ...Option) (string, *ResultEvent, error)

RunText runs a prompt and returns text using the default local executor.

func (*ResultEvent) String

func (e *ResultEvent) String() string

type Session

type Session struct {
	// contains filtered or unexported fields
}

Session represents a long-lived interactive Claude CLI session with bidirectional control protocol support.

Create via Client.Connect(). Send messages with Query(). Read events from Events(). Close when done.

func Connect

func Connect(ctx context.Context, opts ...Option) (*Session, error)

Connect starts an interactive session using the default local executor.

func (*Session) ActivityState

func (s *Session) ActivityState() ActivityState

ActivityState returns the current activity state.

func (*Session) Close

func (s *Session) Close() error

Close terminates the session. Closes stdin (EOF signal) and waits up to 5 seconds for the CLI to exit gracefully before canceling the context (SIGTERM). The grace period prevents interrupting session file writes which can lose the last assistant message.

func (*Session) Events

func (s *Session) Events() <-chan Event

Events returns the event channel. Closed when session ends. Control requests are handled internally and not exposed here.

func (*Session) GetMCPStatus

func (s *Session) GetMCPStatus() error

GetMCPStatus queries MCP server connection status.

func (*Session) GetServerInfo

func (s *Session) GetServerInfo() json.RawMessage

GetServerInfo returns the raw JSON from the initialize response.

func (*Session) Interrupt

func (s *Session) Interrupt() error

Interrupt sends an interrupt to the CLI.

func (*Session) Ping

func (s *Session) Ping(timeout time.Duration) error

Ping sends a no-op control request and returns when the CLI responds. Used by watchdogs to prove the CLI's read loop is alive during long tool executions, not just that the process hasn't exited. Returns error on timeout or transport failure.

Any response from the CLI — including an "unknown subtype" error — proves the read loop parsed stdin and wrote stdout, so such responses are treated as success. Failure modes:

  • timeout: no response within the supplied deadline
  • transport error: write to stdin failed, or the session ended (readLoop exited) before the CLI responded

A zero timeout uses the session's configured control timeout.

func (*Session) ProcessInfo

func (s *Session) ProcessInfo() ProcessInfo

ProcessInfo returns a snapshot of process-level state useful for watchdogs. Consumers can compare LastStdoutAt against the wall clock to detect stdout stalls without having to infer state from event pairings.

func (*Session) Query

func (s *Session) Query(prompt string) error

Query sends a user message to the CLI.

func (*Session) QueryMCPStatus

func (s *Session) QueryMCPStatus() ([]MCPServerStatus, error)

QueryMCPStatus queries MCP server connection status and returns the parsed result.

func (*Session) QueryWithContent

func (s *Session) QueryWithContent(prompt string, blocks ...ContentBlock) error

QueryWithContent sends a user message with multimodal content blocks. The prompt is prepended as a text block, followed by the provided blocks.

func (*Session) ReconnectMCPServer

func (s *Session) ReconnectMCPServer(serverName string) error

ReconnectMCPServer reconnects a named MCP server.

func (*Session) ReconnectMCPServerWait

func (s *Session) ReconnectMCPServerWait(serverName string, timeout time.Duration) error

ReconnectMCPServerWait reconnects a named MCP server and blocks until it reports connected status. A zero timeout uses the default (10s).

func (*Session) RewindFiles

func (s *Session) RewindFiles(userMessageID string) error

RewindFiles rewinds files to a previous checkpoint.

func (*Session) SendMessage

func (s *Session) SendMessage(prompt string) error

SendMessage sends a user message without result tracking. Unlike Query, it can be called while another query is in progress, allowing mid-turn message injection. The CLI folds injected messages into the current turn's result.

func (*Session) SendMessageWithContent

func (s *Session) SendMessageWithContent(prompt string, blocks ...ContentBlock) error

SendMessageWithContent sends a multimodal user message without result tracking. See SendMessage for usage details.

func (*Session) SessionID

func (s *Session) SessionID() string

SessionID returns the session ID assigned by the CLI.

func (*Session) SetModel

func (s *Session) SetModel(model Model) error

SetModel changes the model mid-session.

func (*Session) SetPermissionMode

func (s *Session) SetPermissionMode(mode PermissionMode) error

SetPermissionMode changes the permission mode mid-session.

func (*Session) State

func (s *Session) State() State

State returns the current lifecycle state.

func (*Session) StopTask

func (s *Session) StopTask(taskID string) error

StopTask stops a running task by ID.

func (*Session) ToggleMCPServer

func (s *Session) ToggleMCPServer(serverName string, enabled bool) error

ToggleMCPServer enables or disables a named MCP server.

func (*Session) Wait

func (s *Session) Wait() (*ResultEvent, error)

Wait blocks until a ResultEvent or error for the current query. In multi-turn sessions, returns after each result (not at process exit). Idempotent within a single query: multiple calls return the same result. Safe to call concurrently with Events() -- Wait does not consume events.

type SessionEntry

type SessionEntry struct {
	Session *Session
	Meta    SessionMeta
}

SessionEntry pairs a Session with its metadata.

type SessionMeta

type SessionMeta struct {
	Name   string
	Labels map[string]string
}

SessionMeta holds consumer-defined metadata about a session in a Pool.

type StartConfig

type StartConfig struct {
	Args                    []string
	Stdin                   io.Reader
	Env                     map[string]string
	WorkDir                 string
	KeepStdinOpen           bool // if true, don't close stdin after initial write
	EnableFileCheckpointing bool
	SkipVersionCheck        bool
}

StartConfig holds parameters for starting a CLI process.

type StartEvent

type StartEvent struct {
	Model   Model
	Args    []string
	WorkDir string
}

StartEvent is emitted by the client before the CLI process starts. Contains the resolved configuration for observability.

func (*StartEvent) String

func (e *StartEvent) String() string

type State

type State int

State represents the lifecycle state of a Stream.

const (
	StateStarting State = iota
	StateIdle
	StateRunning
	StateDone
	StateFailed
)

func (State) String

func (s State) String() string

type StderrEvent

type StderrEvent struct {
	Content string
}

StderrEvent contains a line of stderr output from the CLI process.

func (*StderrEvent) String

func (e *StderrEvent) String() string

type Stream

type Stream struct {
	// contains filtered or unexported fields
}

Stream provides access to events from a Claude CLI session. Use Events() for channel-based iteration, Next() for pull-based, or Wait() to block until completion.

Example (Events)
package main

import (
	"context"
	"fmt"

	"github.com/allbin/claudecli-go"
)

func main() {
	exec, err := claudecli.NewFixtureExecutorFromFile("testdata/basic.jsonl")
	if err != nil {
		panic(err)
	}
	client := claudecli.NewWithExecutor(exec)

	stream := client.Run(context.Background(), "ignored prompt")

	var eventCount int
	for event := range stream.Events() {
		_ = event
		eventCount++
	}
	fmt.Printf("Received %d events, state: %s\n", eventCount, stream.State())
}
Output:
Received 10 events, state: done

func Run

func Run(ctx context.Context, prompt string, opts ...Option) *Stream

Run starts a streaming session using the default local executor.

func (*Stream) Close

func (s *Stream) Close() error

Close cancels the underlying process and waits for cleanup.

func (*Stream) Events

func (s *Stream) Events() <-chan Event

Events returns a channel of events. The channel is closed when the stream ends. Safe for range iteration. State is tracked automatically.

func (*Stream) Next

func (s *Stream) Next() (Event, bool)

Next returns the next event and true, or zero value and false when done.

func (*Stream) State

func (s *Stream) State() State

State returns the current lifecycle state.

func (*Stream) Wait

func (s *Stream) Wait() (*ResultEvent, error)

Wait drains all remaining events and returns the final result. Idempotent: multiple calls return the same result.

type StreamEvent

type StreamEvent struct {
	UUID      string
	SessionID string
	Event     json.RawMessage
}

StreamEvent represents a partial message update (when include_partial_messages is on).

func (*StreamEvent) String

func (e *StreamEvent) String() string

type TaskEvent

type TaskEvent struct {
	Subtype   string // "task_started", "task_progress", "task_updated", "task_notification"
	TaskID    string
	ToolUseID string // parent Agent ToolUseEvent.ID
	SessionID string

	// task_started
	Description string
	// TaskType classifies the task, e.g. "local_agent" or "local_workflow".
	// The CLI sends it only on task_started; the SDK backfills it onto the
	// same task's later events (see IsWorkflow).
	TaskType string
	Prompt   string
	// WorkflowName is set when TaskType == "local_workflow". Like TaskType it
	// is backfilled onto the task's later events from its task_started.
	WorkflowName string

	// task_progress
	LastToolName string
	// WorkflowProgress carries per-phase and per-agent state for a workflow
	// task (TaskType == "local_workflow"). Empty for ordinary subagent tasks.
	WorkflowProgress []WorkflowProgressEntry

	// task_notification
	Status  string
	Summary string
	// OutputFile is the path to the workflow's result file, set on a
	// completed workflow task_notification. Empty otherwise.
	OutputFile string

	// task_updated
	EndTime int64 // patch.end_time, epoch milliseconds; 0 if absent

	// task_progress + task_notification
	TotalTokens int
	ToolUses    int
	DurationMs  int

	// Raw contains the full JSON line for forward compatibility.
	Raw json.RawMessage
}

TaskEvent is emitted for subagent lifecycle updates (system subtypes "task_started", "task_progress", "task_updated", "task_notification").

ToolUseID links to the parent Agent ToolUseEvent.ID that spawned this task. TaskID is a unique identifier for the subagent task instance.

Subtype meanings:

  • "task_started": subagent spawned. Description, TaskType, Prompt are set.
  • "task_progress": subagent working. Usage fields update, LastToolName shows current tool.
  • "task_updated": lightweight status patch (Status, EndTime).
  • "task_notification": subagent finished. Status ("completed"), Summary, final Usage.

Dynamic workflows (https://code.claude.com/docs/en/workflows) surface through this same machinery as a single synthetic task with TaskType == "local_workflow" (see IsWorkflow). For those, WorkflowName is set, Prompt carries the full workflow script (on task_started), WorkflowProgress carries per-phase / per-agent progress (on task_progress), and OutputFile points at the workflow's result file (on a completed task_notification). To monitor a workflow out-of-band, see UserEvent.WorkflowLaunch and WatchWorkflow.

func (*TaskEvent) IsWorkflow

func (e *TaskEvent) IsWorkflow() bool

IsWorkflow reports whether this task is a dynamic workflow run (TaskType == "local_workflow") rather than an ordinary subagent.

The CLI stamps task_type only on task_started; later task_progress, task_updated, and task_notification events for the same task_id omit it. The SDK backfills TaskType (and WorkflowName) onto those events from the task_started of the same task_id, so IsWorkflow stays correct across the whole lifecycle — including the terminal task_notification.

func (*TaskEvent) String

func (e *TaskEvent) String() string

type TextEvent

type TextEvent struct {
	Content         string
	ParentToolUseID string
}

TextEvent contains assistant text output. ParentToolUseID is set when this event comes from a subagent (links to the parent Agent ToolUseEvent.ID). Empty for top-level assistant turns.

func (*TextEvent) String

func (e *TextEvent) String() string

type ThinkingAdaptive

type ThinkingAdaptive struct{}

ThinkingAdaptive selects adaptive thinking. Emits --thinking adaptive.

type ThinkingConfig

type ThinkingConfig interface {
	// contains filtered or unexported methods
}

ThinkingConfig is a sealed interface for extended thinking configuration passed to WithThinking. Implementations: ThinkingAdaptive, ThinkingEnabled, ThinkingDisabled.

type ThinkingDisabled

type ThinkingDisabled struct{}

ThinkingDisabled turns extended thinking off. Emits --thinking disabled.

type ThinkingEnabled

type ThinkingEnabled struct {
	BudgetTokens int
}

ThinkingEnabled requests thinking with an explicit token budget. Emits --max-thinking-tokens <BudgetTokens> (the CLI infers enabled state from the flag). Whether a model honors the budget depends on the model.

type ThinkingEvent

type ThinkingEvent struct {
	Content         string
	Signature       string
	ParentToolUseID string
}

ThinkingEvent contains the model's thinking output.

Content may be empty while Signature is set: the CLI can emit a thinking block whose text is withheld but whose signature is present. Treat Content=="" with a non-empty Signature as "thinking hidden", not "no thinking occurred".

ParentToolUseID is set when this event comes from a subagent (links to the parent Agent ToolUseEvent.ID). Empty for top-level assistant turns.

func (*ThinkingEvent) String

func (e *ThinkingEvent) String() string

type ThinkingTokensEvent

type ThinkingTokensEvent struct {
	EstimatedTokens      int
	EstimatedTokensDelta int
	SessionID            string
	UUID                 string
}

ThinkingTokensEvent is emitted by the CLI as a running estimate of the model's thinking-token usage during a turn (system subtype "thinking_tokens"). EstimatedTokens is the cumulative estimate and EstimatedTokensDelta is the increment since the previous tick. It is a progress/telemetry signal, not authoritative accounting — use ResultEvent.Usage for final token counts. Appears in ordinary sessions, not only during workflows.

func (*ThinkingTokensEvent) String

func (e *ThinkingTokensEvent) String() string

type ToolContent

type ToolContent struct {
	Type string // "text" or "image"

	// Text block fields.
	Text string // populated when Type == "text"

	// Image block fields.
	MediaType string // e.g. "image/png"; populated when Type == "image"
	Data      string // base64-encoded image data; populated when Type == "image"
}

ToolContent represents a single content block inside a tool result. Use the Type field to distinguish between block kinds.

type ToolPermissionFunc

type ToolPermissionFunc func(toolName string, input json.RawMessage) (*PermissionResponse, error)

ToolPermissionFunc is called when the CLI requests permission to use a tool.

func ResolveCanUseTool

func ResolveCanUseTool(opts ...Option) ToolPermissionFunc

ResolveCanUseTool applies the given options and returns the ToolPermissionFunc callback, or nil if none was set. Used by test infrastructure to extract callbacks that would normally be consumed internally by Connect().

type ToolPermissionRequest

type ToolPermissionRequest struct {
	ToolName              string            `json:"tool_name"`
	Input                 json.RawMessage   `json:"input"`
	PermissionSuggestions []json.RawMessage `json:"permission_suggestions,omitempty"`
}

ToolPermissionRequest is the data inside a "can_use_tool" control request.

type ToolProgressEvent

type ToolProgressEvent struct {
	ToolUseID string
	ToolName  string
	Elapsed   time.Duration
	At        time.Time
}

ToolProgressEvent is emitted periodically while the session is in ActivityAwaitingToolResult. It proves liveness in the absence of parsed events and carries elapsed time since the tool_use was emitted.

ToolUseID / ToolName identify the first pending top-level tool_use and remain stable across ticks even if additional parallel tool_use calls are outstanding. Consumers can render "Bash running for 4m 12s" without computing elapsed time themselves.

func (*ToolProgressEvent) String

func (e *ToolProgressEvent) String() string

type ToolResultEvent

type ToolResultEvent struct {
	ToolUseID       string
	Content         []ToolContent
	ParentToolUseID string
}

ToolResultEvent contains the result of a tool invocation. ParentToolUseID is set when this event comes from a subagent (links to the parent Agent ToolUseEvent.ID). Empty for top-level assistant turns.

func (*ToolResultEvent) String

func (e *ToolResultEvent) String() string

func (*ToolResultEvent) Text

func (e *ToolResultEvent) Text() string

Text returns the concatenated text of all text content blocks.

type ToolUseEvent

type ToolUseEvent struct {
	ID              string
	Name            string
	Input           json.RawMessage
	ParentToolUseID string
	ServerSide      bool
	MCP             bool
}

ToolUseEvent is emitted when the assistant invokes a tool. ParentToolUseID is set when this event comes from a subagent (links to the parent Agent ToolUseEvent.ID). Empty for top-level assistant turns.

ServerSide is true for server_tool_use blocks (web search, code execution) and MCP is true for mcp_tool_use blocks. Both carry the same ID/Name/Input shape as regular tool_use.

func (*ToolUseEvent) ParseAgentInput

func (e *ToolUseEvent) ParseAgentInput() *AgentInput

ParseAgentInput extracts structured fields from an Agent tool_use event. Returns nil if the event is not an Agent tool call or input is malformed.

func (*ToolUseEvent) String

func (e *ToolUseEvent) String() string

type ToolUseSummaryEvent

type ToolUseSummaryEvent struct {
	Summary             string
	PrecedingToolUseIDs []string
}

ToolUseSummaryEvent is emitted after tool execution with a summary of what the tool did. PrecedingToolUseIDs lists the tool_use IDs that this summary covers.

func (*ToolUseSummaryEvent) String

func (e *ToolUseSummaryEvent) String() string

type TurnEvent

type TurnEvent struct {
	Turn     int
	ToolName string
}

TurnEvent is emitted when a new assistant turn starts. Turn is a 1-based counter incremented for each top-level assistant message. ToolName is the name of the first tool_use block in the turn, or empty if the turn contains only text/thinking.

func (*TurnEvent) String

func (e *TurnEvent) String() string

type UnknownEvent

type UnknownEvent struct {
	Type string
	Raw  json.RawMessage
}

UnknownEvent is emitted when the CLI sends an event type not recognized by this SDK version. Preserves the full raw JSON for inspection.

func (*UnknownEvent) String

func (e *UnknownEvent) String() string

type UnmarshalError

type UnmarshalError struct {
	Err     error
	RawText string
}

UnmarshalError is returned by RunJSON when the response text cannot be parsed as JSON. RawText contains the original model output for debugging.

func (*UnmarshalError) Error

func (e *UnmarshalError) Error() string

func (*UnmarshalError) Unwrap

func (e *UnmarshalError) Unwrap() error

type Usage

type Usage struct {
	InputTokens       int
	OutputTokens      int
	CacheReadTokens   int
	CacheCreateTokens int
}

Usage contains token usage statistics.

func (Usage) String

func (u Usage) String() string

func (Usage) TotalTokens

func (u Usage) TotalTokens() int

TotalTokens returns the sum of every token field — input, output, cache read and cache create. This is the headline "tokens used" figure for a run; pair it with ResultEvent.CostUSD (or ModelUsage.CostUSD) to report cost.

type UserContent

type UserContent struct {
	Type      string        // "text" or "tool_result"
	Text      string        // populated when Type == "text"
	ToolUseID string        // populated when Type == "tool_result"
	Content   []ToolContent // tool result content; populated when Type == "tool_result"
}

UserContent represents a content block in a user message. Type is "text" for prompt/text content, or "tool_result" for tool output.

type UserEvent

type UserEvent struct {
	Content         []UserContent
	ParentToolUseID string
	AgentResult     *AgentResult
	WorkflowLaunch  *WorkflowLaunch
	SessionID       string
	UUID            string
	Timestamp       string
	// IsReplay is true when this event is an echo of a user message sent via
	// stdin, produced by the CLI's --replay-user-messages flag. Replay events
	// confirm that the CLI has read and accepted the message.
	IsReplay bool
}

UserEvent is emitted when the CLI feeds a message back to the model.

The CLI emits these as "type":"user" JSONL events. They appear in two contexts:

  1. Tool results — after any tool executes, this carries the output back to the model for its next turn. Correlate with the preceding ToolUseEvent via Content[].ToolUseID.

  2. Subagent activity — when the Agent tool spawns a subagent, its prompt dispatch, internal tool results, and final completion all appear as UserEvents with ParentToolUseID set to the Agent ToolUseEvent.ID.

Use ParentToolUseID to distinguish subagent events from top-level tool results:

  • Empty: top-level tool result or user input
  • Non-empty: belongs to the subagent spawned by that Agent tool call

When AgentResult is non-nil, this event completes a subagent execution and contains its metadata (agent type, duration, token usage).

When WorkflowLaunch is non-nil, this event reports that a dynamic workflow was launched in the background (tool_use_result status "async_launched"). Use it to monitor the run out-of-band — see WatchWorkflow and ReadWorkflowSnapshot.

func (*UserEvent) String

func (e *UserEvent) String() string

func (*UserEvent) Text

func (e *UserEvent) Text() string

Text returns the concatenated text of all text content blocks.

type UserInputFunc

type UserInputFunc func(questions []Question) (answers map[string]string, err error)

UserInputFunc receives parsed questions and returns a map of question text -> selected answer(s). For multiSelect questions, multiple answers can be joined with newlines or returned as a JSON array.

type VersionError

type VersionError struct {
	Found   string
	Minimum string
}

VersionError indicates the CLI version is below minimum.

func (*VersionError) Error

func (e *VersionError) Error() string

type WatchOption

type WatchOption func(*watchConfig)

WatchOption configures WatchWorkflow.

func WithPollInterval

func WithPollInterval(d time.Duration) WatchOption

WithPollInterval sets how often WatchWorkflow re-reads the manifest. Values <= 0 are ignored. Defaults to 500ms.

type WorkflowLaunch

type WorkflowLaunch struct {
	Status        string `json:"status"` // "async_launched"
	TaskID        string `json:"taskId"`
	TaskType      string `json:"taskType"` // "local_workflow"
	WorkflowName  string `json:"workflowName"`
	RunID         string `json:"runId"`
	Summary       string `json:"summary"`
	TranscriptDir string `json:"transcriptDir"`
	ScriptPath    string `json:"scriptPath"`
}

WorkflowLaunch reports that a dynamic workflow (https://code.claude.com/docs/en/workflows) was launched in the background. It is parsed from the "async_launched" tool_use_result the CLI emits when a workflow starts, and is exposed on UserEvent.WorkflowLaunch.

The workflow itself streams progress through TaskEvent (TaskType == "local_workflow"); the final answer arrives as a later TextEvent / ResultEvent in the same session. A WorkflowLaunch is the handle for monitoring the run out-of-band via the on-disk run state — see ManifestPath, JournalPath, ReadWorkflowSnapshot, and WatchWorkflow.

func (*WorkflowLaunch) JournalPath

func (l *WorkflowLaunch) JournalPath() string

JournalPath returns the path to the workflow's append-only per-agent journal (<transcriptDir>/journal.jsonl). Returns "" if it cannot be derived. Each line is a {"type":"started"|"result", "agentId", ...} record appended as agents start and finish.

func (*WorkflowLaunch) ManifestPath

func (l *WorkflowLaunch) ManifestPath() string

ManifestPath returns the path to the workflow's run-state manifest (<session>/workflows/<runId>.json), derived from ScriptPath. Returns "" if the path cannot be derived.

The manifest is written and updated live by the CLI's workflow runtime and survives the --no-session-persistence flag. Its layout is an undocumented CLI implementation detail and may change between versions.

type WorkflowPhase

type WorkflowPhase struct {
	Title string `json:"title"`
}

WorkflowPhase names a phase declared by a workflow script.

type WorkflowProgressEntry

type WorkflowProgressEntry struct {
	Type  string `json:"type"` // "workflow_agent" or "workflow_phase"
	Index int    `json:"index"`

	// workflow_phase
	Title string `json:"title,omitempty"`

	// workflow_agent
	Label           string `json:"label,omitempty"`
	PhaseIndex      int    `json:"phaseIndex,omitempty"`
	PhaseTitle      string `json:"phaseTitle,omitempty"`
	AgentID         string `json:"agentId,omitempty"`
	Model           string `json:"model,omitempty"`
	State           string `json:"state,omitempty"` // "queued","start","progress","done","error"
	Attempt         int    `json:"attempt,omitempty"`
	LastToolName    string `json:"lastToolName,omitempty"`
	LastToolSummary string `json:"lastToolSummary,omitempty"`
	PromptPreview   string `json:"promptPreview,omitempty"`
	ResultPreview   string `json:"resultPreview,omitempty"`
	Tokens          int    `json:"tokens,omitempty"`
	ToolCalls       int    `json:"toolCalls,omitempty"`
	DurationMs      int    `json:"durationMs,omitempty"`
	QueuedAt        int64  `json:"queuedAt,omitempty"`
	StartedAt       int64  `json:"startedAt,omitempty"`
	LastProgressAt  int64  `json:"lastProgressAt,omitempty"`
}

WorkflowProgressEntry is one entry in a workflow's progress list. The same shape appears in the stream (TaskEvent.WorkflowProgress) and in the on-disk manifest (WorkflowSnapshot.Progress). Type distinguishes the two kinds: "workflow_phase" entries carry only Index and Title; "workflow_agent" entries describe a single subagent. Unknown fields are ignored; consult the owning event's or snapshot's raw JSON for forward compatibility.

func (WorkflowProgressEntry) IsAgent

func (e WorkflowProgressEntry) IsAgent() bool

IsAgent reports whether this entry describes a subagent.

func (WorkflowProgressEntry) IsPhase

func (e WorkflowProgressEntry) IsPhase() bool

IsPhase reports whether this entry marks a phase boundary.

type WorkflowSnapshot

type WorkflowSnapshot struct {
	RunID          string                  `json:"runId"`
	TaskID         string                  `json:"taskId"`
	WorkflowName   string                  `json:"workflowName"`
	Status         string                  `json:"status"` // "running","completed","stopped","killed","error"
	Result         json.RawMessage         `json:"result"`
	AgentCount     int                     `json:"agentCount"`
	DurationMs     int                     `json:"durationMs"`
	StartTime      int64                   `json:"startTime"`
	Timestamp      string                  `json:"timestamp"`
	TotalTokens    int                     `json:"totalTokens"`
	TotalToolCalls int                     `json:"totalToolCalls"`
	DefaultModel   string                  `json:"defaultModel"`
	ScriptPath     string                  `json:"scriptPath"`
	Phases         []WorkflowPhase         `json:"phases"`
	Progress       []WorkflowProgressEntry `json:"workflowProgress"`

	// Raw is the full manifest JSON, preserved for forward compatibility.
	Raw json.RawMessage `json:"-"`
}

WorkflowSnapshot is a point-in-time view of a workflow run, read from its on-disk manifest. The manifest is checkpointed live, so polling it (see WatchWorkflow) yields successively more complete snapshots until Status is terminal. Result is the workflow's return value (a JSON string or object); it is populated once the run completes.

func ReadWorkflowSnapshot

func ReadWorkflowSnapshot(launch *WorkflowLaunch) (*WorkflowSnapshot, error)

ReadWorkflowSnapshot reads and parses the workflow's manifest once. Use it to fetch the final Result after the run completes, or for a single point-in-time status check. It wraps os errors, so callers can test for a not-yet-written manifest with errors.Is(err, fs.ErrNotExist).

func (*WorkflowSnapshot) Agents

Agents returns only the subagent entries from Progress.

func (*WorkflowSnapshot) IsTerminal

func (s *WorkflowSnapshot) IsTerminal() bool

IsTerminal reports whether the run has reached a final status and will not progress further.

Directories

Path Synopsis
cmd
authtest command
Command authtest exercises the AuthLogin flow with full debug logging.
Command authtest exercises the AuthLogin flow with full debug logging.
capture command
Command capture runs the Claude CLI and saves raw stdout/stderr for analysis.
Command capture runs the Claude CLI and saves raw stdout/stderr for analysis.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL