cli

package
v0.1.6 Latest Latest
Warning

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

Go to latest
Published: May 15, 2026 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package cli provides adapters for AI-powered CLI tools.

This package implements a unified interface for interacting with various AI CLI tools such as Claude Code, Gemini CLI, and others. Each adapter handles:

  • Session management via tmux
  • Input delivery to the CLI
  • Response extraction from history files or hooks
  • Interactive state detection

Supported CLIs

  • Claude Code (claude): Anthropic's AI programming assistant
  • Gemini (gemini): Google's AI assistant
  • OpenCode (opencode): AI programming assistant

Architecture

The CLI adapter pattern separates transport layer (HTTP, file I/O, tmux) from protocol logic. Each adapter:

  1. Creates/manages tmux sessions for the CLI
  2. Sends user input via tmux send-keys
  3. Receives responses via two mechanisms: - Hook mode: Real-time notifications when CLI completes a task (use_hook: true) - Polling mode: Periodic tmux capture when output becomes stable (use_hook: false)
  4. Detects interactive states (prompts, confirmations)

Thread Safety

CLI adapters are not thread-safe and should not be accessed concurrently. The engine ensures serialized access to each adapter.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func ExtractLastAssistantResponse

func ExtractLastAssistantResponse(transcriptPath string) (string, error)

ExtractLastAssistantResponse extracts all assistant messages after the last user message

func ExtractLatestInteraction

func ExtractLatestInteraction(transcriptPath string) (string, string, error)

ExtractLatestInteraction exports the latest user prompt and assistant response extraction logic

Types

type ACPAdapter

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

ACPAdapter implements CLIAdapter using Agent Client Protocol

func NewACPAdapter

func NewACPAdapter(config ACPAdapterConfig) (*ACPAdapter, error)

NewACPAdapter creates a new ACP adapter

func (*ACPAdapter) Close

func (a *ACPAdapter) Close() error

Close cleans up ACP adapter resources

func (*ACPAdapter) CreateSession

func (a *ACPAdapter) CreateSession(sessionName string, opts ...SessionOption) error

CreateSession creates a new ACP session and starts connection

func (*ACPAdapter) DeleteSession

func (a *ACPAdapter) DeleteSession(sessionName string) error

DeleteSession terminates an ACP session

func (*ACPAdapter) GetPollInterval

func (a *ACPAdapter) GetPollInterval() time.Duration

GetPollInterval returns polling interval (ACP uses request/response)

func (*ACPAdapter) GetPollTimeout

func (a *ACPAdapter) GetPollTimeout() time.Duration

GetPollTimeout returns request timeout (for compatibility with polling mode)

func (*ACPAdapter) GetStableCount

func (a *ACPAdapter) GetStableCount() int

GetStableCount returns stable count (not used in ACP mode)

func (*ACPAdapter) HandleHookData

func (a *ACPAdapter) HandleHookData(data []byte) (string, string, string, error)

HandleHookData - not used in ACP mode

func (*ACPAdapter) IsSessionAlive

func (a *ACPAdapter) IsSessionAlive(sessionName string) bool

IsSessionAlive checks if session is active

func (*ACPAdapter) SendInput

func (a *ACPAdapter) SendInput(sessionName, input string) error

SendInput sends input to the ACP server

func (*ACPAdapter) SetEngine

func (a *ACPAdapter) SetEngine(engine Engine)

SetEngine sets the engine reference for sending responses

func (*ACPAdapter) StopSession added in v0.1.5

func (a *ACPAdapter) StopSession(sessionName string) error

StopSession delegates to DeleteSession for ACP sessions.

func (*ACPAdapter) UseHook

func (a *ACPAdapter) UseHook() bool

UseHook returns false - ACP doesn't use hook mode

type ACPAdapterConfig

type ACPAdapterConfig struct {
	// Idle timeout - max time without any activity before cancelling request
	// Default: 5 minutes. If the agent is actively working (sending updates),
	// the request will continue. Only cancelled if there's no activity for this duration.
	IdleTimeout time.Duration `yaml:"idle_timeout"`

	// Max total timeout - absolute maximum time for a request regardless of activity
	// Default: 1 hour. This is a hard limit to prevent truly hung requests.
	MaxTotalTimeout time.Duration `yaml:"max_total_timeout"`

	// Environment variables for ACP server process
	Env map[string]string `yaml:"env"`

	// Deprecated: Use IdleTimeout instead
	// Kept for backward compatibility
	RequestTimeout time.Duration `yaml:"request_timeout"`
}

ACPAdapterConfig configuration for ACP adapter

type ACPTransportType

type ACPTransportType string

ACPTransportType represents the ACP transport type

const (
	ACPTransportStdio ACPTransportType = "stdio"
	ACPTransportTCP   ACPTransportType = "tcp"
	ACPTransportUnix  ACPTransportType = "unix"
)

type BaseAdapter

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

BaseAdapter provides common fields and methods for CLI adapters

func NewBaseAdapter

func NewBaseAdapter(name, startCmd string, delay int) BaseAdapter

NewBaseAdapter creates a new BaseAdapter

func (*BaseAdapter) CreateSession

func (b *BaseAdapter) CreateSession(sessionName string, opts ...SessionOption) error

CreateSession creates a new tmux session and starts the CLI This method is idempotent: if the session already exists, it returns successfully The transportURL parameter is ignored by base adapters (only used by ACP)

func (*BaseAdapter) IsSessionAlive

func (b *BaseAdapter) IsSessionAlive(sessionName string) bool

func (*BaseAdapter) SendInput

func (b *BaseAdapter) SendInput(sessionName, input string) error

SendInput sends input to the CLI via tmux

func (*BaseAdapter) Start

func (b *BaseAdapter) Start(sessionName, startCmd string) error

Start starts the CLI in the specified tmux session

func (*BaseAdapter) StopSession added in v0.1.5

func (b *BaseAdapter) StopSession(sessionName string) error

StopSession kills the tmux session.

type CLIAdapter

type CLIAdapter interface {
	// SendInput sends input to the CLI (via tmux send-keys)
	SendInput(sessionName, input string) error

	// HandleHookData handles raw hook data from the CLI
	// The adapter is responsible for:
	//   - Parsing the data (in any format: JSON, text, etc.)
	//   - Extracting the last user prompt for tmux filtering
	//   - Extracting the session name from the data
	//   - Processing the hook data and generating the response
	//
	// This interface is protocol-agnostic - it works with HTTP, gRPC, message queues, etc.
	// The engine is responsible for extracting the raw data from the transport layer.
	//
	// Parameter data: raw hook data (bytes)
	// Returns: (sessionName, lastUserPrompt, responseText, error)
	//   - sessionName: which session this hook is for (cwd)
	//   - lastUserPrompt: the last user's input (for filtering tmux output)
	//   - responseText: the CLI's response to send back to the user
	//   - error: any error that occurred
	HandleHookData(data []byte) (sessionName string, lastUserPrompt string, response string, err error)

	// IsSessionAlive checks if the session is still alive
	IsSessionAlive(sessionName string) bool

	// CreateSession creates a new session and starts the CLI.
	// Use WithWorkDir, WithStartCmd, WithTransportURL, WithEnv, WithYolo, WithResume options.
	CreateSession(sessionName string, opts ...SessionOption) error

	// StopSession stops and cleans up a running session.
	StopSession(sessionName string) error
}

CLIAdapter defines the interface for CLI adapters

type ClaudeAdapter

type ClaudeAdapter struct {
	BaseAdapter
}

ClaudeAdapter implements CLIAdapter for Claude Code

func NewClaudeAdapter

func NewClaudeAdapter(config ClaudeAdapterConfig) (*ClaudeAdapter, error)

NewClaudeAdapter creates a new Claude Code adapter

func (*ClaudeAdapter) HandleHookData

func (c *ClaudeAdapter) HandleHookData(data []byte) (string, string, string, error)

HandleHookData handles raw hook data from Claude Code Expected data format (JSON):

{"cwd": "/path/to/workdir", "session_id": "...", "transcript_path": "...", ...}

This returns the cwd as the session identifier, which will be matched against the configured session's work_dir in the engine.

Parameter data: raw hook data (JSON bytes) Returns: (cwd, lastUserPrompt, response, error)

type ClaudeAdapterConfig

type ClaudeAdapterConfig struct {
	Env map[string]string // Environment variables to set for the CLI process
}

ClaudeAdapterConfig configuration for Claude Code adapter

type ContentBlock

type ContentBlock struct {
	Type     string `json:"type"` // "text", "thinking", "image", etc.
	Text     string `json:"text,omitempty"`
	Thinking string `json:"thinking,omitempty"`
}

ContentBlock represents a block of content (text, thinking, image, etc.)

type Engine

type Engine interface {
	SendToBot(platform, channel, message string)
	SendResponseToSession(sessionName, message string)
}

Engine defines the interface for sending responses to users. It's implemented by the core Engine and passed to adapters.

type GeminiAdapter

type GeminiAdapter struct {
	BaseAdapter
}

GeminiAdapter implements CLIAdapter for Gemini CLI

func NewGeminiAdapter

func NewGeminiAdapter(config GeminiAdapterConfig) (*GeminiAdapter, error)

NewGeminiAdapter creates a new Gemini CLI adapter

func (*GeminiAdapter) ExtractLatestInteraction

func (g *GeminiAdapter) ExtractLatestInteraction(transcriptPath string, cwd string) (string, string, error)

ExtractLatestInteraction exports the latest Gemini response extraction logic

func (*GeminiAdapter) HandleHookData

func (g *GeminiAdapter) HandleHookData(data []byte) (string, string, string, error)

HandleHookData handles raw hook data from Gemini CLI Expected data format (JSON):

{"session_id": "...", "cwd": "...", ...}

Gemini stores history in: ~/.gemini/tmp/{project_hash}/chats/session-*.json where project_hash = SHA256(project_path)

This returns the cwd as the session identifier, which will be matched against the configured session's work_dir in the engine.

Parameter data: raw hook data (JSON bytes) Returns: (cwd, lastUserPrompt, response, error)

type GeminiAdapterConfig

type GeminiAdapterConfig struct {
	Env map[string]string // Environment variables to set for the CLI process
}

GeminiAdapterConfig configuration for Gemini CLI adapter

type MessageContent

type MessageContent struct {
	ID          string         `json:"id,omitempty"`
	Type        string         `json:"type,omitempty"`  // "message" for assistant
	Role        string         `json:"role,omitempty"`  // "user" or "assistant"
	Model       string         `json:"model,omitempty"` // Model name
	Content     []ContentBlock `json:"content,omitempty"`
	ContentText string         `json:"-"`                     // Extracted when content is a string
	StopReason  string         `json:"stop_reason,omitempty"` // null if incomplete, "end_turn"/"max_tokens" if complete
}

MessageContent represents the message content structure Note: content can be either a string (user messages) or an array (assistant messages)

func (*MessageContent) UnmarshalJSON

func (mc *MessageContent) UnmarshalJSON(data []byte) error

UnmarshalJSON implements custom JSON unmarshaling for MessageContent

type OpenCodeAdapter

type OpenCodeAdapter struct {
	BaseAdapter
}

OpenCodeAdapter implements CLIAdapter for OpenCode

func NewOpenCodeAdapter

func NewOpenCodeAdapter(config OpenCodeAdapterConfig) (*OpenCodeAdapter, error)

NewOpenCodeAdapter creates a new OpenCode adapter

func (*OpenCodeAdapter) ExtractLatestInteraction

func (o *OpenCodeAdapter) ExtractLatestInteraction(transcriptPath string) (string, string, error)

ExtractLatestInteraction legacy support

func (*OpenCodeAdapter) HandleHookData

func (o *OpenCodeAdapter) HandleHookData(data []byte) (string, string, string, error)

HandleHookData handles raw hook data from OpenCode Expected data format (JSON):

{"cwd": "/path/to/workdir", "session_id": "...", "hook_event_name": "..."}

Returns: (cwd, lastUserPrompt, response, error)

type OpenCodeAdapterConfig

type OpenCodeAdapterConfig struct {
	Env map[string]string // Environment variables to set for the CLI process
}

OpenCodeAdapterConfig configuration for OpenCode adapter

type OpenCodeSessionInfo

type OpenCodeSessionInfo struct {
	ID        string `json:"id"`
	ProjectID string `json:"projectID"`
	Time      struct {
		Updated int64 `json:"updated"`
	} `json:"time"`
}

OpenCodeSessionInfo represents the structure of an OpenCode session info file

type SessionOption added in v0.1.5

type SessionOption func(*SessionOptions)

SessionOption configures a SessionOptions value.

func WithEnv added in v0.1.5

func WithEnv(env map[string]string) SessionOption

WithEnv sets session-level environment variables.

func WithResume added in v0.1.5

func WithResume(resume bool) SessionOption

WithResume marks the session as having prior history so the first turn uses resume mode.

func WithStartCmd added in v0.1.5

func WithStartCmd(cmd string) SessionOption

WithStartCmd sets the command to start the CLI.

func WithTransportURL added in v0.1.5

func WithTransportURL(transportURL string) SessionOption

WithTransportURL sets the transport URL (for ACP adapter).

func WithWorkDir added in v0.1.5

func WithWorkDir(dir string) SessionOption

WithWorkDir sets the working directory for the session.

func WithYolo added in v0.1.5

func WithYolo(yolo bool) SessionOption

WithYolo enables auto-approve mode (each CLI appends its own flag).

type SessionOptions added in v0.1.5

type SessionOptions struct {
	WorkDir      string
	StartCmd     string
	TransportURL string
	Env          map[string]string
	Yolo         bool
	Resume       bool // Resume previous conversation on first turn
}

SessionOptions holds parameters for session creation.

func ApplySessionOptions added in v0.1.5

func ApplySessionOptions(opts []SessionOption) SessionOptions

ApplySessionOptions applies all options to a new SessionOptions value.

type TranscriptMessage

type TranscriptMessage struct {
	Type             string         `json:"type"` // "user", "assistant", "progress", etc.
	SessionID        string         `json:"sessionId"`
	IsMeta           bool           `json:"isMeta"`           // Legacy field, replaced by IsSidechain in newer versions
	IsSidechain      bool           `json:"isSidechain"`      // Replaces isMeta in Claude Code >= 2.1.x
	IsCompactSummary bool           `json:"isCompactSummary"` // Context compaction summary messages
	Message          MessageContent `json:"message"`
}

TranscriptMessage represents a single message in Claude Code's transcript.jsonl Each line in the file is a JSON object with this structure

Directories

Path Synopsis
Package stdio provides a unified adapter framework for CLI tools that communicate via stdin/stdout using structured JSON protocols.
Package stdio provides a unified adapter framework for CLI tools that communicate via stdin/stdout using structured JSON protocols.

Jump to

Keyboard shortcuts

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