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 ¶
- func New(opts ...Option) platform.Platform
- type Emulator
- func (e *Emulator) BotAPIURL() string
- func (e *Emulator) Close()
- func (e *Emulator) Journal(chatID int64) ([]platform.JournalEntry, error)
- func (e *Emulator) Metrics(chatID int64) []TurnMetrics
- func (e *Emulator) SetWebhook(string, *http.Client)
- func (e *Emulator) SubmitClick(int64, platform.User, string, int) error
- func (e *Emulator) SubmitText(chatID int64, user platform.User, text string) error
- func (e *Emulator) ToolCalls(chatID int64) []ToolCall
- func (e *Emulator) Transcript(chatID int64) string
- func (e *Emulator) WaitForEdit(int64, int, int, time.Duration) (*platform.Message, bool)
- func (e *Emulator) WaitForMessage(chatID int64, consumed int, timeout time.Duration) (*platform.Message, bool)
- type Option
- func WithAllowedTools(tools string) Option
- func WithClaudeBinary(path string) Option
- func WithEnv(env map[string]string) Option
- func WithExtraArgs(args ...string) Option
- func WithMaxTurns(n int) Option
- func WithModel(model string) Option
- func WithPluginDirs(dirs ...string) Option
- func WithTurnTimeout(d time.Duration) Option
- func WithWorkDir(dir string) Option
- type Options
- type ToolCall
- type TurnMetrics
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
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 ¶
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 ¶
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 ¶
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 ¶
SubmitClick always fails: print mode has no interactive-action concept — a `claude -p` turn only ever produces text.
func (*Emulator) SubmitText ¶
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 ¶
ToolCalls returns every tool_use block recorded for chatID so far, across every turn, in the order claude emitted them.
func (*Emulator) Transcript ¶
Transcript renders a chronological, human-readable dump of everything recorded for chatID.
func (*Emulator) WaitForEdit ¶
WaitForEdit always returns false immediately: print-mode turns never produce an in-place edit, so there is nothing to wait for.
type Option ¶
type Option func(*Options)
Option configures Options at claudeprint.New construction time.
func WithAllowedTools ¶
WithAllowedTools overrides the --allowedTools flag (default "Bash,Skill,Read").
func WithClaudeBinary ¶
WithClaudeBinary overrides the executable run in place of "claude" — the seam offline tests use to point at a fake stand-in script.
func WithEnv ¶
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 ¶
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 ¶
WithMaxTurns overrides the --max-turns flag (default 12).
func WithPluginDirs ¶
WithPluginDirs appends one --plugin-dir flag per directory given. Calling it more than once accumulates rather than replacing.
func WithTurnTimeout ¶
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 ¶
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 ¶
TurnMetrics is the accounting stream-json's terminal "result" event reports for one turn.