claudeprint

package
v0.9.0 Latest Latest
Warning

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

Go to latest
Published: Sep 7, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package claudeprint implements a Claude Code Platform for Chatwright that drives `claude` in print mode (`claude -p ... --output-format stream-json`) instead of the interactive channel contract claudechannel uses.

claudechannel exists first and is the more capable design on paper — one long-running claude session, driven over the claude/channel MCP contract, so a scenario can send several turns into the same conversation the way a human would type into a chat. In practice that design is blocked: as of claude 2.1.263, a manually configured ("server:<name>") channel is rejected by a platform-side approved-channels allowlist even with --dangerously-load-development-channels, so no message ever reaches the model (see claudechannel's package doc, "Known limitation").

claudeprint sidesteps the blocker by never opening a channel at all: each user turn spawns its own `claude -p <text> --output-format stream-json --verbose ...` process, non-interactive from the start, so there is no allowlist gate to trip. The trade-off is real — one OS process per turn instead of one long-running session, no interactive-action (button) support, and multi-turn continuity depends on `--resume <session-id>` re-attaching to the same server-side conversation rather than a live process holding it in memory — but it works today, against the current claude binary, with no known gate blocking it.

Session continuity

Emulator generates one UUID per chat the first time SubmitText is called for it, passes it as `--session-id <uuid>` on that chat's first turn, and as `--resume <uuid>` on every later turn — the same session ID, so claude resumes the prior turn's server-side conversation state instead of starting fresh.

Environment

Every claude process claudeprint spawns runs with a copy of the current process environment from which every CLAUDE*-prefixed variable has been removed before Options.Env is applied on top — the same nested-session-detection leak documented in claudechannel's package doc (sanitizedEnviron): when claudeprint's own test suite runs inside a claude session, CLAUDECODE and friends would otherwise leak into the child by plain os.Environ() inheritance. ANTHROPIC_*-prefixed variables (API auth) are left untouched.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func New

func New(opts ...Option) platform.Platform

New returns the claudeprint Platform for use with cw.OnPlatform. The bot-under-test is a real `claude` process (or, in tests, a stand-in binary set with WithClaudeBinary) spawned fresh for every user turn.

Types

type Emulator

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

Emulator runs one `claude` process per user turn and normalizes its stream-json output into Chatwright's neutral Message/Journal model. It implements platform.Emulator. Unlike Telegram, WhatsApp or claudechannel, it needs no HTTP server of its own: SubmitText spawns the claude process directly rather than queuing an update for something else to deliver.

func NewEmulator

func NewEmulator(opts Options) *Emulator

NewEmulator constructs an Emulator that spawns claude processes per opts. Most callers should use claudeprint.New instead; NewEmulator is exported for tests and callers that build a platform.Emulator directly.

func (*Emulator) BotAPIURL

func (e *Emulator) BotAPIURL() string

BotAPIURL returns "": claudeprint drives claude as a direct subprocess, so there is no bot API server for anything to be configured against.

func (*Emulator) Close

func (e *Emulator) Close()

Close marks the emulator closed (further SubmitText calls fail) and waits for any in-flight turn to finish so a test's t.Cleanup does not race a still-running claude process past the end of the test.

func (*Emulator) Journal

func (e *Emulator) Journal(chatID int64) ([]platform.JournalEntry, error)

Journal returns chatID's chronological, structured journal entries.

func (*Emulator) Metrics

func (e *Emulator) Metrics(chatID int64) []TurnMetrics

Metrics returns the per-turn accounting recorded for chatID so far, one entry per completed successful turn, in turn order.

func (*Emulator) SetWebhook

func (e *Emulator) SetWebhook(string, *http.Client)

SetWebhook is a no-op: claudeprint has no update-delivery transport for a webhook to receive. Accepted only to satisfy platform.Emulator.

func (*Emulator) SubmitClick

func (e *Emulator) SubmitClick(int64, platform.User, string, int) error

SubmitClick always fails: print mode has no interactive-action concept — a `claude -p` turn only ever produces text.

func (*Emulator) SubmitText

func (e *Emulator) SubmitText(chatID int64, user platform.User, text string) error

SubmitText journals the inbound user message, then spawns `claude -p <text> ...` in the background for this chat's turn: the first turn of a chat passes --session-id <uuid> (freshly generated per chat); every later turn passes --resume <uuid> so the conversation continues server-side. The returned error reports only that the emulator is closed — normal per-turn failures (a non-zero claude exit, a timeout, a missing result event) are delivered as an "error: ..." bot message instead, exactly like any other bot reply, so WaitForMessage observes them uniformly.

func (*Emulator) ToolCalls

func (e *Emulator) ToolCalls(chatID int64) []ToolCall

ToolCalls returns every tool_use block recorded for chatID so far, across every turn, in the order claude emitted them.

func (*Emulator) Transcript

func (e *Emulator) Transcript(chatID int64) string

Transcript renders a chronological, human-readable dump of everything recorded for chatID.

func (*Emulator) WaitForEdit

func (e *Emulator) WaitForEdit(int64, int, int, time.Duration) (*platform.Message, bool)

WaitForEdit always returns false immediately: print-mode turns never produce an in-place edit, so there is nothing to wait for.

func (*Emulator) WaitForMessage

func (e *Emulator) WaitForMessage(chatID int64, consumed int, timeout time.Duration) (*platform.Message, bool)

WaitForMessage waits for the (consumed+1)-th outbound bot message to chatID.

type Option

type Option func(*Options)

Option configures Options at claudeprint.New construction time.

func WithAllowedTools

func WithAllowedTools(tools string) Option

WithAllowedTools overrides the --allowedTools flag (default "Bash,Skill,Read").

func WithClaudeBinary

func WithClaudeBinary(path string) Option

WithClaudeBinary overrides the executable run in place of "claude" — the seam offline tests use to point at a fake stand-in script.

func WithEnv

func WithEnv(env map[string]string) Option

WithEnv merges the given key/value pairs into the environment applied on top of the sanitized process environment. Calling it more than once merges rather than replacing.

func WithExtraArgs

func WithExtraArgs(args ...string) Option

WithExtraArgs appends arguments verbatim to the claude command line, after every flag claudeprint itself sets. Calling it more than once accumulates rather than replacing.

func WithMaxTurns

func WithMaxTurns(n int) Option

WithMaxTurns overrides the --max-turns flag (default 12).

func WithModel

func WithModel(model string) Option

WithModel sets the --model flag passed to every turn.

func WithPluginDirs

func WithPluginDirs(dirs ...string) Option

WithPluginDirs appends one --plugin-dir flag per directory given. Calling it more than once accumulates rather than replacing.

func WithTurnTimeout

func WithTurnTimeout(d time.Duration) Option

WithTurnTimeout overrides the wall-clock ceiling a single turn (one `claude -p ...` invocation) is allowed to run before it is killed and reported as an error message (default 5 minutes).

func WithWorkDir

func WithWorkDir(dir string) Option

WithWorkDir sets the directory the claude process runs in.

type Options

type Options struct {
	// Model is passed as --model. Left empty, the flag is omitted and claude
	// falls back to its own default model.
	Model string

	// WorkDir is the working directory the claude process runs in — a
	// scratch directory outside any repository, so no ambient CLAUDE.md,
	// hooks or project settings interfere with the session under test.
	WorkDir string

	// PluginDirs is passed as one --plugin-dir flag per entry.
	PluginDirs []string

	// Env is applied on top of a copy of the current process environment
	// from which every CLAUDE*-prefixed variable has first been removed
	// (see sanitizedEnviron) — the same leak claudechannel guards against:
	// running inside a claude session ourselves must not make the child
	// look like a nested session.
	Env map[string]string

	// AllowedTools is passed as --allowedTools. Defaults to "Bash,Skill,Read".
	AllowedTools string

	// MaxTurns is passed as --max-turns. Defaults to 12.
	MaxTurns int

	// ExtraArgs are appended verbatim to the claude command line, after
	// every flag claudeprint itself sets.
	ExtraArgs []string

	// ClaudeBinary is the executable claudeprint runs. Defaults to "claude";
	// override with a fake binary in tests.
	ClaudeBinary string

	// TurnTimeout bounds a single `claude -p ...` turn. Defaults to 5
	// minutes.
	TurnTimeout time.Duration
}

Options configures the claude process claudeprint.New launches for every turn. Build it with the With* functions below rather than constructing it directly — New applies the defaults documented on each option.

type ToolCall

type ToolCall struct {
	Name    string         // e.g. "Bash", "Read", "Skill"
	Input   map[string]any // the tool_use block's raw "input" object
	Command string         // convenience: Input["command"] when Name == "Bash" and it is a string
}

ToolCall is one assistant tool_use content block captured from a turn's stream-json output.

type TurnMetrics

type TurnMetrics struct {
	NumTurns     int
	TotalCostUSD float64
	DurationMS   int64
}

TurnMetrics is the accounting stream-json's terminal "result" event reports for one turn.

Jump to

Keyboard shortcuts

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