claudebox

package module
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Apr 16, 2026 License: MIT Imports: 12 Imported by: 0

README

go-claudebox

Go client for the claudebox API. Lets you run Claude Code prompts, manage files, and check server status from Go.

Features

  • Run prompts (sync, async, fire-and-forget, resume sessions)
  • Async job management (start, poll, cancel by run ID)
  • Full verbose output with typed turns, tool calls, and tool results
  • Upload, download, list, and delete workspace files
  • Check health, status, and cancel running jobs
  • Bearer token auth
  • MockableClaudebox interface for easy testing
  • Minimal dependencies — ctxerrors, common-go, testify, godotenv
  • Integration tests against a live claudebox instance (go test -tags=real)
  • Strict linting

Install

go get github.com/psyb0t/go-claudebox@latest

Usage

package main

import (
    "context"
    "fmt"
    "io"
    "log"
    "os"
    "time"

    claudebox "github.com/psyb0t/go-claudebox"
)

func main() {
    c := claudebox.New("http://localhost:8080", claudebox.WithToken("my-secret"))

    // Check health
    h, err := c.Health(context.Background())
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(h.Status)

    // Run a prompt
    resp, err := c.Run(context.Background(), &claudebox.RunRequest{
        Prompt: "list all files in the project",
        Model:  "sonnet",
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(resp.Result)
    fmt.Printf("cost: $%.4f, turns: %d\n", resp.TotalCostUSD, resp.NumTurns)

    // Run with verbose output (includes tool call history)
    verbose, err := c.Run(context.Background(), &claudebox.RunRequest{
        Prompt:       "read main.go and explain it",
        Model:        "haiku",
        OutputFormat: "json-verbose",
    })
    if err != nil {
        log.Fatal(err)
    }
    for _, turn := range verbose.Turns {
        for _, block := range turn.Content {
            switch block.Type {
            case "tool_use":
                fmt.Printf("[%s] %s\n", block.Name, string(block.Input))
            case "tool_result":
                fmt.Printf("  → %s\n", block.Content)
            case "text":
                fmt.Println(block.Text)
            }
        }
    }

    // Run async — returns immediately
    async, err := c.RunAsync(context.Background(), &claudebox.RunRequest{
        Prompt:    "refactor the entire codebase",
        Workspace: "myproject",
    })
    if err != nil {
        log.Fatal(err)
    }
    fmt.Printf("started run %s\n", async.RunID)

    // Cancel by run ID (if needed):
    // _, _ = c.CancelRun(context.Background(), async.RunID)

    // Poll for result
    for {
        res, err := c.RunResult(context.Background(), async.RunID)
        if err != nil {
            log.Fatal(err)
        }
        if res.Status == "running" {
            time.Sleep(5 * time.Second)
            continue
        }
        if res.Status == "completed" {
            fmt.Println(res.Result.Result)
        }
        if res.Status == "failed" {
            fmt.Printf("failed: %s\n", res.Error)
        }
        break
    }

    // Upload a file
    _, err = c.WriteFile(context.Background(), "notes.txt", []byte("hello"))
    if err != nil {
        log.Fatal(err)
    }

    // Read it back (streaming — caller closes Body)
    file, err := c.ReadFile(context.Background(), "notes.txt")
    if err != nil {
        log.Fatal(err)
    }
    defer file.Body.Close()
    fmt.Printf("type: %s, size: %d\n", file.ContentType, file.ContentLength)
    io.Copy(os.Stdout, file.Body)
}

Mocking

The Claudebox interface makes testing straightforward:

type mockClient struct {
    runFunc func(ctx context.Context, req *claudebox.RunRequest) (*claudebox.RunResponse, error)
}

func (m *mockClient) Run(ctx context.Context, req *claudebox.RunRequest) (*claudebox.RunResponse, error) {
    return m.runFunc(ctx, req)
}

func (m *mockClient) Health(context.Context) (*claudebox.HealthResponse, error) {
    return &claudebox.HealthResponse{Status: "ok"}, nil
}

// ... implement other methods as needed

func TestMyService(t *testing.T) {
    mock := &mockClient{
        runFunc: func(_ context.Context, req *claudebox.RunRequest) (*claudebox.RunResponse, error) {
            return &claudebox.RunResponse{
                Result:  "mocked response",
                IsError: false,
            }, nil
        },
    }

    svc := NewMyService(mock) // your code accepts claudebox.Claudebox
    // test away
}

API

Method What
New(baseURL, ...Option) Create client
Health(ctx) GET /health
Status(ctx) GET /status — busy workspaces + async runs
Run(ctx, *RunRequest) POST /run — execute prompt (sync)
RunAsync(ctx, *RunRequest) POST /run — start async job
RunResult(ctx, runID) GET /run/result — poll async result
Cancel(ctx, workspace) POST /run/cancel — by workspace
CancelRun(ctx, runID) POST /run/cancel — by run ID
ListFiles(ctx, path) GET /files or GET /files/{path}
ReadFile(ctx, path) GET /files/{path} — streaming download
WriteFile(ctx, path, content) PUT /files/{path}
DeleteFile(ctx, path) DELETE /files/{path}
Options
Option What
WithToken(token) Set Bearer token for auth
WithHTTPClient(hc) Override default http.Client (default: 10min timeout)
RunRequest fields
Field JSON What
Prompt prompt The prompt to run
Workspace workspace Target workspace
Model model Model to use (sonnet, opus, haiku)
SystemPrompt systemPrompt Override system prompt
AppendSystemPrompt appendSystemPrompt Append to system prompt
JSONSchema jsonSchema Constrain output to schema
Effort effort low, medium, high, max
OutputFormat outputFormat json (default) or json-verbose
NoContinue noContinue Don't auto-continue
Resume resume Resume a previous session
FireAndForget fireAndForget Start and return immediately
AsyncRunResponse fields

Returned by RunAsync:

Field JSON What
RunID runId Unique run identifier for polling
Workspace workspace Resolved workspace path
Status status Always "running"
RunResultResponse fields

Returned by RunResult:

Field JSON What
RunID runId The run identifier
Workspace workspace Workspace path (non-completed)
Status status running, completed, cancelled, failed
Error error Error message (failed only)
Result Full *RunResponse (completed only)

Results are purged server-side after first read (except running). Unread results expire after 6 hours.

RunResponse fields
Field JSON What
RunID runId Run identifier (set for async results)
Type type Always "result"
Subtype subtype "success" or "error"
Result result The response text
IsError isError Whether the run errored
NumTurns numTurns Number of conversation turns
DurationMs durationMs Total duration in ms
DurationAPIMs durationApiMs API call duration in ms
StopReason stopReason Why the run stopped (e.g. "end_turn")
SessionID sessionId Session ID for resuming
TotalCostUSD totalCostUsd Total cost in USD
UUID uuid Unique run identifier
FastModeState fastModeState Fast mode state ("off", "on")
Usage usage Token usage (see Usage)
ModelUsage modelUsage Per-model stats map (see ModelStats)
Turns turns Conversation turns (json-verbose only)
System system Session metadata (json-verbose only)
PermissionDenials permissionDenials Any permission denials
Usage fields
Field What
InputTokens Input token count
OutputTokens Output token count
CacheCreationInputTokens Tokens used to create cache
CacheReadInputTokens Tokens read from cache
ServerToolUse Web search/fetch counters (WebSearchRequests, WebFetchRequests)
ServiceTier Service tier (e.g. "standard")
CacheCreation Cache creation breakdown (Ephemeral1hInputTokens, Ephemeral5mInputTokens)
InferenceGeo Inference region (e.g. "us-east-1")
Iterations Per-iteration breakdown (raw JSON)
Speed Speed tier (e.g. "standard")
ModelStats fields

Per-model usage in ModelUsage map (keyed by model ID like "claude-haiku-4-5-20251001"):

Field What
InputTokens Input tokens for this model
OutputTokens Output tokens for this model
CacheReadInputTokens Tokens read from cache
CacheCreationInputTokens Tokens used to create cache
WebSearchRequests Web search requests made
CostUSD Cost in USD for this model
ContextWindow Context window size
MaxOutputTokens Max output tokens
Turn and ContentBlock

Verbose output includes []Turn, each with Role ("assistant" or "tool_result") and []ContentBlock.

Content block types:

  • text: Text field set
  • tool_use: ID, Name, Input (json.RawMessage) set
  • tool_result: ToolUseID, IsError, Content set. Optionally Truncated, TotalLength, SHA256 for large results.
ReadFileResponse

ReadFile returns a streaming response instead of buffering the entire file in memory:

Field Type What
ContentType string MIME type (e.g. "text/plain", "application/octet-stream")
ContentLength int64 File size in bytes, or -1 if unknown
Body io.ReadCloser Streaming file data — caller must close
Error handling

Non-2xx responses return *claudebox.APIError:

var apiErr *claudebox.APIError
if errors.As(err, &apiErr) {
    fmt.Printf("HTTP %d: %s\n", apiErr.StatusCode, apiErr.Body)
}

Testing

Unit tests run without any external dependencies:

make test
# or: go test -race ./...

Integration tests run against a live claudebox instance. Create .env.test with your instance details:

CLAUDEBOX_URL=http://localhost:8080
CLAUDEBOX_TOKEN=your-api-token

Then run:

make test-with-real
# or: go test -race -tags=real -timeout=5m ./...

The integration tests verify every response field is properly deserialized — token counts, model usage, cost, turns with tool calls, system info, content types, the works.

License

MIT. Do whatever.

Documentation

Overview

Package claudebox provides a Go client for the claudebox API — a runtime harness for Claude Code running in Docker containers.

The Claudebox interface enables mocking for tests. Use New to create a concrete *Client.

Package claudebox provides a Go client for the claudebox direct API.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func FilePath

func FilePath(parts ...string) string

FilePath joins path segments for use with file operations.

Types

type APIError

type APIError struct {
	StatusCode int
	Body       string
}

APIError is returned when the server responds with a non-2xx status code.

func (*APIError) Error

func (e *APIError) Error() string

type AsyncRunResponse

type AsyncRunResponse struct {
	RunID     string `json:"runId"`
	Workspace string `json:"workspace"`
	Status    string `json:"status"`
}

AsyncRunResponse is returned by POST /run when async mode is enabled.

type CacheCreation

type CacheCreation struct {
	Ephemeral1hInputTokens int `json:"ephemeral1hInputTokens"`
	Ephemeral5mInputTokens int `json:"ephemeral5mInputTokens"`
}

CacheCreation holds cache creation token breakdown.

type CancelResponse

type CancelResponse struct {
	Status    string `json:"status"`
	RunID     string `json:"runId,omitempty"`
	Workspace string `json:"workspace"`
}

CancelResponse is the response from POST /run/cancel.

type Claudebox

type Claudebox interface {
	// Health checks if the server is up.
	Health(ctx context.Context) (*HealthResponse, error)

	// Status returns currently busy workspaces.
	Status(ctx context.Context) (*StatusResponse, error)

	// Run executes a prompt via POST /run.
	Run(
		ctx context.Context,
		req *RunRequest,
	) (*RunResponse, error)

	// RunAsync starts an async run and returns
	// immediately. Poll with RunResult.
	RunAsync(
		ctx context.Context,
		req *RunRequest,
	) (*AsyncRunResponse, error)

	// RunResult polls for the result of an async run.
	RunResult(
		ctx context.Context,
		runID string,
	) (*RunResultResponse, error)

	// Cancel kills a running process in the given
	// workspace (empty = default).
	Cancel(
		ctx context.Context,
		workspace string,
	) (*CancelResponse, error)

	// CancelRun cancels a running async job by run ID.
	CancelRun(
		ctx context.Context,
		runID string,
	) (*CancelResponse, error)

	// ListFiles lists files at the given path
	// (empty = workspace root).
	ListFiles(
		ctx context.Context,
		dirPath string,
	) (*ListFilesResponse, error)

	// ReadFile downloads a file. The caller must close
	// the returned ReadFileResponse.Body when done.
	ReadFile(
		ctx context.Context,
		filePath string,
	) (*ReadFileResponse, error)

	// WriteFile uploads content to the given path.
	WriteFile(
		ctx context.Context,
		filePath string,
		content []byte,
	) (*WriteFileResponse, error)

	// DeleteFile deletes a file at the given path.
	DeleteFile(
		ctx context.Context,
		filePath string,
	) (*DeleteFileResponse, error)
}

Claudebox is the interface for all claudebox API operations. Implement this for mocking in tests.

type Client

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

Client talks to a claudebox API server.

func New

func New(baseURL string, opts ...Option) *Client

New creates a claudebox client. baseURL is the server root, e.g. "http://localhost:8080".

func (*Client) Cancel

func (c *Client) Cancel(
	ctx context.Context,
	workspace string,
) (*CancelResponse, error)

Cancel kills a running process in the given workspace (empty = default).

func (*Client) CancelRun

func (c *Client) CancelRun(
	ctx context.Context,
	runID string,
) (*CancelResponse, error)

CancelRun cancels a running async job by run ID.

func (*Client) DeleteFile

func (c *Client) DeleteFile(
	ctx context.Context,
	filePath string,
) (*DeleteFileResponse, error)

DeleteFile deletes a file at the given path.

func (*Client) Health

func (c *Client) Health(
	ctx context.Context,
) (*HealthResponse, error)

Health checks if the server is up.

func (*Client) ListFiles

func (c *Client) ListFiles(
	ctx context.Context,
	dirPath string,
) (*ListFilesResponse, error)

ListFiles lists files at the given path (empty = workspace root).

func (*Client) ReadFile

func (c *Client) ReadFile(
	ctx context.Context,
	filePath string,
) (*ReadFileResponse, error)

ReadFile downloads a file and returns a streaming response. The caller must close Body when done.

func (*Client) Run

func (c *Client) Run(
	ctx context.Context,
	req *RunRequest,
) (*RunResponse, error)

Run executes a prompt via POST /run and returns the parsed response.

func (*Client) RunAsync

func (c *Client) RunAsync(
	ctx context.Context,
	req *RunRequest,
) (*AsyncRunResponse, error)

RunAsync starts an async run and returns immediately. Poll with RunResult to get the outcome.

func (*Client) RunResult

func (c *Client) RunResult(
	ctx context.Context,
	runID string,
) (*RunResultResponse, error)

RunResult polls for the result of an async run. Results are purged after first read (except running).

func (*Client) Status

func (c *Client) Status(
	ctx context.Context,
) (*StatusResponse, error)

Status returns currently busy workspaces.

func (*Client) WriteFile

func (c *Client) WriteFile(
	ctx context.Context,
	filePath string,
	content []byte,
) (*WriteFileResponse, error)

WriteFile uploads content to the given path.

type ContentBlock

type ContentBlock struct {
	Type string `json:"type"`

	// text block fields
	Text string `json:"text,omitempty"`

	// tool_use block fields
	ID    string          `json:"id,omitempty"`
	Name  string          `json:"name,omitempty"`
	Input json.RawMessage `json:"input,omitempty"`

	// tool_result block fields
	ToolUseID   string `json:"toolUseId,omitempty"`
	IsError     bool   `json:"isError,omitempty"`
	Content     string `json:"content,omitempty"`
	Truncated   bool   `json:"truncated,omitempty"`
	TotalLength int    `json:"totalLength,omitempty"`
	SHA256      string `json:"sha256,omitempty"`
}

ContentBlock is a single block within a turn. The Type field determines which other fields are set.

Type "text": Text is set. Type "tool_use": ID, Name, Input are set. Type "tool_result": ToolUseID, IsError, Content, and optionally Truncated/TotalLength/SHA256 are set.

type DeleteFileResponse

type DeleteFileResponse struct {
	Status string `json:"status"`
	Path   string `json:"path"`
}

DeleteFileResponse is the response from DELETE /files/{path}.

type FileEntry

type FileEntry struct {
	Name string `json:"name"`
	Type string `json:"type"`
	Size int64  `json:"size,omitempty"`
}

FileEntry is a single item in a directory listing.

type HealthResponse

type HealthResponse struct {
	Status string `json:"status"`
}

HealthResponse is the response from GET /health.

type ListFilesResponse

type ListFilesResponse struct {
	Path    string      `json:"path"`
	Entries []FileEntry `json:"entries"`
}

ListFilesResponse is the response from GET /files or GET /files/{path} on a directory.

type ModelStats

type ModelStats struct {
	InputTokens              int     `json:"inputTokens"`
	OutputTokens             int     `json:"outputTokens"`
	CacheReadInputTokens     int     `json:"cacheReadInputTokens"`
	CacheCreationInputTokens int     `json:"cacheCreationInputTokens"`
	WebSearchRequests        int     `json:"webSearchRequests"`
	CostUSD                  float64 `json:"costUSD"` //nolint:tagliatelle // server sends costUSD
	ContextWindow            int     `json:"contextWindow"`
	MaxOutputTokens          int     `json:"maxOutputTokens"`
}

ModelStats holds per-model usage and cost info.

type Option

type Option func(*Client)

Option configures a Client.

func WithHTTPClient

func WithHTTPClient(hc *http.Client) Option

WithHTTPClient overrides the default http.Client.

func WithToken

func WithToken(token string) Option

WithToken sets the Bearer token for authenticated requests.

type ReadFileResponse

type ReadFileResponse struct {
	// ContentType is the MIME type from the server
	// (e.g. "text/plain", "application/octet-stream").
	ContentType string

	// ContentLength is the file size in bytes, or -1
	// if the server did not send Content-Length.
	ContentLength int64

	// Body is the file data stream. The caller must
	// close it when done.
	Body io.ReadCloser
}

ReadFileResponse wraps a streamed file download. The caller must close Body when done reading.

type RunInfo

type RunInfo struct {
	RunID     string `json:"runId"`
	Workspace string `json:"workspace"`
	Status    string `json:"status"`
}

RunInfo is a summary of an async run, returned by GET /status.

type RunRequest

type RunRequest struct {
	Prompt             string `json:"prompt"`
	Workspace          string `json:"workspace,omitempty"`
	Model              string `json:"model,omitempty"`
	SystemPrompt       string `json:"systemPrompt,omitempty"`
	AppendSystemPrompt string `json:"appendSystemPrompt,omitempty"`
	JSONSchema         string `json:"jsonSchema,omitempty"`
	Effort             string `json:"effort,omitempty"`
	OutputFormat       string `json:"outputFormat,omitempty"`
	NoContinue         bool   `json:"noContinue,omitempty"`
	Resume             string `json:"resume,omitempty"`
	FireAndForget      bool   `json:"fireAndForget,omitempty"`
}

RunRequest is the body for POST /run.

type RunResponse

type RunResponse struct {
	RunID         string                `json:"runId,omitempty"`
	Type          string                `json:"type"`
	Subtype       string                `json:"subtype"`
	Result        string                `json:"result"`
	IsError       bool                  `json:"isError"`
	NumTurns      int                   `json:"numTurns"`
	DurationMs    int64                 `json:"durationMs"`
	DurationAPIMs int64                 `json:"durationApiMs"`
	StopReason    string                `json:"stopReason"`
	SessionID     string                `json:"sessionId"`
	TotalCostUSD  float64               `json:"totalCostUsd"`
	UUID          string                `json:"uuid"`
	FastModeState string                `json:"fastModeState"`
	Usage         Usage                 `json:"usage"`
	ModelUsage    map[string]ModelStats `json:"modelUsage,omitempty"`
	Turns         []Turn                `json:"turns,omitempty"`
	System        *SystemInfo           `json:"system,omitempty"`

	// PermissionDenials lists any permission denials
	// that occurred during the run.
	PermissionDenials []json.RawMessage `json:"permissionDenials,omitempty"`
	// contains filtered or unexported fields
}

RunResponse is the JSON response from POST /run.

func (*RunResponse) Raw

func (r *RunResponse) Raw() json.RawMessage

Raw returns the full unparsed JSON response body.

type RunResultResponse

type RunResultResponse struct {
	RunID     string `json:"runId"`
	Workspace string `json:"workspace,omitempty"`
	Status    string `json:"status"`
	Error     string `json:"error,omitempty"`

	// Result is set when Status is "completed".
	Result *RunResponse `json:"-"`
}

RunResultResponse is the response from GET /run/result. Check Status to determine the outcome:

  • "running": still in progress
  • "completed": Result is populated
  • "cancelled": run was cancelled
  • "failed": Error contains the failure message

type ServerToolUse

type ServerToolUse struct {
	WebSearchRequests int `json:"webSearchRequests"`
	WebFetchRequests  int `json:"webFetchRequests"`
}

ServerToolUse holds server-side tool usage counters.

type StatusResponse

type StatusResponse struct {
	BusyWorkspaces []string  `json:"busyWorkspaces"`
	Runs           []RunInfo `json:"runs,omitempty"`
}

StatusResponse is the response from GET /status.

type SystemInfo

type SystemInfo struct {
	SessionID string   `json:"sessionId"`
	Model     string   `json:"model"`
	Cwd       string   `json:"cwd"`
	Tools     []string `json:"tools"`
}

SystemInfo holds session metadata from verbose output.

type Turn

type Turn struct {
	Role    string         `json:"role"`
	Content []ContentBlock `json:"content"`
}

Turn represents a conversation turn in verbose output.

type Usage

type Usage struct {
	InputTokens              int               `json:"inputTokens"`
	OutputTokens             int               `json:"outputTokens"`
	CacheCreationInputTokens int               `json:"cacheCreationInputTokens"`
	CacheReadInputTokens     int               `json:"cacheReadInputTokens"`
	ServerToolUse            *ServerToolUse    `json:"serverToolUse,omitempty"`
	ServiceTier              string            `json:"serviceTier,omitempty"`
	CacheCreation            *CacheCreation    `json:"cacheCreation,omitempty"`
	InferenceGeo             string            `json:"inferenceGeo,omitempty"`
	Iterations               []json.RawMessage `json:"iterations,omitempty"`
	Speed                    string            `json:"speed,omitempty"`
}

Usage holds token usage stats.

type WriteFileResponse

type WriteFileResponse struct {
	Status string `json:"status"`
	Path   string `json:"path"`
	Size   int    `json:"size"`
}

WriteFileResponse is the response from PUT /files/{path}.

Jump to

Keyboard shortcuts

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