cli

package
v0.1.4 Latest Latest
Warning

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

Go to latest
Published: Mar 28, 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, workDir, startCmd, transportURL string, env map[string]string) 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) 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, workDir, startCmd, transportURL string, env map[string]string) 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

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 with the specified command
	// The startCmd parameter allows sessions to use different commands than the adapter default
	// The transportURL parameter is for ACP adapter (e.g., "stdio://", "tcp://host:port", "unix:///path")
	// Other adapters should ignore this parameter
	// The env parameter sets session-level environment variables (merged with adapter-level env)
	CreateSession(sessionName, workDir, startCmd, transportURL string, env map[string]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 TranscriptMessage

type TranscriptMessage struct {
	Type      string         `json:"type"` // "user", "assistant", "progress", etc.
	SessionID string         `json:"sessionId"`
	IsMeta    bool           `json:"isMeta"`
	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

Jump to

Keyboard shortcuts

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