agentx

package module
v0.2.0 Latest Latest
Warning

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

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

README

AgentX

Part of the Agent Experience (AX) toolchain by SageOx — a new category of developer tooling that optimizes CLIs for AI coding agents, not just humans.

Build agent-aware CLI tools that know which AI coding assistant is calling them.

AgentX detects whether your tool is running inside Claude Code, Cursor, Aider, or 14 other coding agents - and gives you access to their configuration, context files, capabilities, and extension management.

Why AX?

AI coding agents are the new users of your CLI. Agent Experience (AX) is the practice of designing tools that work well for both humans and agents — not just tolerating agent usage, but actively optimizing for it.

AgentX gives your CLI the primitives it needs:

  • Which agent is calling: Tailor output format, verbosity, or behavior per agent
  • Where agent config lives: Read/write to ~/.claude, ~/.cursor, etc.
  • What context files exist: Find CLAUDE.md, .cursorrules, and similar files
  • How to propagate context: Set AGENT_ENV so downstream tools know too
package main

import (
    "fmt"

    "github.com/sageox/agentx/pkg"
)

func main() {
    // Detect agent and set AGENT_ENV for child processes
    agent := agentx.Init()

    if agent != nil {
        fmt.Printf("Running in %s\n", agent.Name())
        fmt.Printf("Config: %s\n", must(agent.UserConfigPath(agentx.NewSystemEnvironment())))
        fmt.Printf("Context files: %v\n", agent.ContextFiles())
    }
}

Key Features

1. Automatic Agent Detection

Detects 14 coding agents via environment variables and heuristics:

agent := agentx.Detect()
if agent != nil {
    switch agent.Type() {
    case agentx.AgentTypeClaudeCode:
        // Claude Code specific behavior
    case agentx.AgentTypeCursor:
        // Cursor specific behavior
    }
}
2. AGENT_ENV Propagation

Init() detects the agent and sets AGENT_ENV in the process environment. This lets downstream code and child processes simply check AGENT_ENV instead of implementing their own detection:

func main() {
    agentx.Init() // Detects "cursor" from heuristics, sets AGENT_ENV=cursor

    // Now any library or subprocess can do:
    // os.Getenv("AGENT_ENV") → "cursor"
}
3. Agent Configuration Paths

Access each agent's user and project configuration directories:

agent, _ := agentx.DefaultRegistry.Get(agentx.AgentTypeClaudeCode)
env := agentx.NewSystemEnvironment()

userConfig, _ := agent.UserConfigPath(env)   // ~/.claude
projectConfig := agent.ProjectConfigPath()   // .claude
Agent User Config Project Config
Claude Code ~/.claude .claude
Cursor ~/.cursor .cursor
Aider ~/.aider .aider
Windsurf ~/.codeium .windsurf
Cody ~/.config/cody .cody
4. Context Files

Know which instruction files each agent uses:

agent, _ := agentx.DefaultRegistry.Get(agentx.AgentTypeClaudeCode)
files := agent.ContextFiles() // ["CLAUDE.md", "AGENTS.md"]
Agent Context Files
Claude Code CLAUDE.md, AGENTS.md
Cursor .cursorrules
Windsurf .windsurfrules
GitHub Copilot .github/copilot-instructions.md
5. Installation Detection

Check if an agent is installed on the system:

ctx := context.Background()
env := agentx.NewSystemEnvironment()

agent, _ := agentx.DefaultRegistry.Get(agentx.AgentTypeCursor)
installed, _ := agent.IsInstalled(ctx, env)
6. Agent Capabilities

Query what features each agent supports:

agent, _ := agentx.DefaultRegistry.Get(agentx.AgentTypeClaudeCode)
caps := agent.Capabilities()

if caps.Hooks {
    // Agent supports lifecycle hooks
}
if caps.CustomCommands {
    // Agent supports slash commands
}
if caps.MCPServers {
    // Agent supports MCP server configuration
}
7. Hook and Command Management

For agents that support extensions, manage hooks and custom commands:

agent, _ := agentx.DefaultRegistry.Get(agentx.AgentTypeClaudeCode)

// Install custom slash commands
if cmdMgr := agent.CommandManager(); cmdMgr != nil {
    commands := []agentx.CommandFile{
        {Name: "my-tool.md", Content: []byte("# My Tool\n..."), Version: "1.0.0"},
    }
    written, _ := cmdMgr.Install(ctx, projectRoot, commands, false)
}

// Install hooks and MCP servers
if hookMgr := agent.HookManager(); hookMgr != nil {
    config := agentx.HookConfig{
        MCPServers: map[string]agentx.MCPServerConfig{
            "my-server": {Command: "my-mcp-server", Args: []string{"--port", "8080"}},
        },
        Merge: true,
    }
    hookMgr.Install(ctx, config)
}

Installation

go get github.com/sageox/agentx/pkg

Quick Start

package main

import (
    "fmt"
    "os"

    "github.com/sageox/agentx/pkg"
)

func main() {
    // Initialize - detects agent and sets AGENT_ENV
    agent := agentx.Init()

    if agent == nil {
        // Not running in a coding agent
        fmt.Println("Run this from within a coding agent")
        os.Exit(1)
    }

    fmt.Printf("Agent: %s (%s)\n", agent.Name(), agent.Type())
    fmt.Printf("URL: %s\n", agent.URL())
    fmt.Printf("Context files: %v\n", agent.ContextFiles())
}

Supported Agents

Agent Type AGENT_ENV Native Detection
Claude Code claude claude CLAUDECODE=1, CLAUDE_CODE_ENTRYPOINT, CLAUDE_CODE_SESSION_ID
Cursor cursor cursor CURSOR_AGENT=1, $_ path
Windsurf windsurf windsurf WINDSURF_AGENT=1, CODEIUM_AGENT=1
GitHub Copilot copilot copilot COPILOT_AGENT=1
Aider aider aider AIDER=1
Cody cody cody CODY_AGENT=1
Continue continue continue CONTINUE_AGENT=1
Code Puppy code-puppy code-puppy CODE_PUPPY=1
Kiro kiro kiro KIRO=1
OpenCode opencode opencode OPENCODE=1
Goose goose goose GOOSE=1
Amp amp amp AMP=1
Cline cline cline CLINE=1, CLINE_AGENT=1
Droid droid droid DROID=1, FACTORY_DROID=1

Detection Priority

  1. AGENT_ENV - If set, this is the definitive answer (no fallback)
  2. Native env vars - Agent-specific variables like CLAUDECODE=1
  3. Binary heuristics - Checking $_ for agent name in the executable path

API Reference

Initialization
// Init detects the agent and sets AGENT_ENV for child processes.
// Call early in main() to propagate agent context.
func Init() Agent

// InitWithEnv is like Init but uses a custom environment (for testing).
func InitWithEnv(env Environment) Agent
Detection
// Detect returns the active agent without setting AGENT_ENV.
func Detect() Agent

// DetectWithEnv detects using a custom environment.
func DetectWithEnv(env Environment) Agent

// IsAgentContext returns true if running in any coding agent.
func IsAgentContext() bool

// RequireAgent returns an error message if not in agent context.
func RequireAgent(commandName string) string
Agent Interface

The Agent interface is composed of focused sub-interfaces:

// AgentIdentity - basic identification
type AgentIdentity interface {
    Type() AgentType  // "claude", "cursor", etc.
    Name() string     // "Claude Code", "Cursor", etc.
    URL() string      // Official project URL
}

// AgentDetector - detection capabilities
type AgentDetector interface {
    Detect(ctx, env) (bool, error)      // Check if this agent is active
    IsInstalled(ctx, env) (bool, error) // Check if installed on system
}

// AgentConfig - configuration paths
type AgentConfig interface {
    UserConfigPath(env) (string, error)  // ~/.claude, ~/.cursor, etc.
    ProjectConfigPath() string           // .claude, .cursor, etc.
    ContextFiles() []string              // ["CLAUDE.md"], [".cursorrules"], etc.
    SupportsXDGConfig() bool             // true if uses ~/.config/app/
}

// AgentExtensions - hooks and commands
type AgentExtensions interface {
    Capabilities() Capabilities    // What features this agent supports
    HookManager() HookManager      // Hook management (or nil)
    CommandManager() CommandManager // Command management (or nil)
}

// Agent - full interface (embeds all above)
type Agent interface {
    AgentIdentity
    AgentDetector
    AgentConfig
    AgentExtensions
}
Registry
// Get agent by type
agent, ok := agentx.DefaultRegistry.Get(agentx.AgentTypeClaudeCode)

// List all registered agents
agents := agentx.DefaultRegistry.List()

Testing

Use MockEnvironment for deterministic testing:

func TestMyTool(t *testing.T) {
    env := agentx.NewMockEnvironment(map[string]string{
        "AGENT_ENV": "claude",
    })

    agent := agentx.DetectWithEnv(env)
    if agent.Type() != agentx.AgentTypeClaudeCode {
        t.Error("expected Claude Code")
    }
}

Configure mock paths and binaries:

env := agentx.NewMockEnvironment(nil)
env.Home = "/home/testuser"
env.ExistingDirs = map[string]bool{"/home/testuser/.claude": true}
env.PathBinaries = map[string]string{"claude": "/usr/bin/claude"}

Package Structure

Single package - just import and use:

import "github.com/sageox/agentx/pkg"

All 14 agents are automatically registered.

AX Patterns

Agent-aware output
agent := agentx.Detect()
if agent != nil && agent.Type() == agentx.AgentTypeClaudeCode {
    // Claude Code prefers markdown
    fmt.Println("## Results\n")
} else {
    // Default terminal output
    fmt.Println("Results:")
}
Find agent context files
agent := agentx.Detect()
if agent != nil {
    for _, file := range agent.ContextFiles() {
        if content, err := os.ReadFile(file); err == nil {
            fmt.Printf("Found %s:\n%s\n", file, content)
        }
    }
}
Require agent context
func main() {
    if msg := agentx.RequireAgent("my-tool"); msg != "" {
        fmt.Fprintln(os.Stderr, msg)
        os.Exit(1)
    }
    // Tool requires agent context to function
}

AX Toolchain

AgentX is part of the Agent Experience (AX) toolchain — open-source Go libraries for building CLIs that work well for AI coding agents, not just humans:

  • AgentX (this repo) — Detect which coding agent is calling and adapt behavior accordingly
  • FrictionAX — CLI error detection, smart correction, and telemetry for surfacing agent desire paths
  • ox — The CLI where the AX toolchain was born

Read more: The Hive is Buzzing — how we use the AX toolchain to fine-tune CLIs for coding agents.

License

MIT License - see LICENSE for details.

Documentation

Overview

Package agentx provides coding agent detection, hook management, and configuration.

agentx is a helper library for understanding how to extend the feature set of coding agents. It provides a unified interface for:

  • Detecting which coding agent is currently running
  • Understanding each agent's capabilities and configuration options
  • Installing hooks and integrations for supported agents
  • Managing agent-specific configuration paths (user and project level)
  • Identifying context files each agent supports (CLAUDE.md, .cursorrules, etc.)

Supported agents: Claude Code, Cursor, Windsurf, GitHub Copilot, Aider, Cody, Continue, Code Puppy, Kiro, OpenCode, Codex, Goose, Amp, Cline, and Droid.

This package is designed to be standalone with no external dependencies, making it suitable for use by any tool that needs coding agent integration.

Usage:

import (
    "github.com/sageox/agentx"
    _ "github.com/sageox/agentx/setup" // registers default agents
)

// Detect the current agent
if agentx.IsAgentContext() {
    agent := agentx.CurrentAgent()
    fmt.Printf("Running in %s\n", agent.Name())
    fmt.Printf("Context files: %v\n", agent.ContextFiles())
}

Attribution

This package is developed and maintained by SageOx (https://sageox.ai). If you use this package, please give credit to SageOx.

Index

Constants

View Source
const DefaultStampPrefix = "agentx"

DefaultStampPrefix is the default prefix used for command file stamps.

Variables

AllPhases is the ordered list of canonical lifecycle phases for matrix display.

View Source
var DefaultRegistry = NewRegistry()

DefaultRegistry is the global registry with all supported agents.

SupportedAgents is the canonical list of coding agents and orchestrators that agentx supports.

Functions

func BuildEventPhaseMap added in v0.1.1

func BuildEventPhaseMap() map[string]EventPhaseMap

BuildEventPhaseMap builds a map from AGENT_ENV alias -> (native event -> phase) by iterating all registered agents that implement LifecycleEventMapper. The returned map uses AGENT_ENV aliases as keys (e.g., "claude-code"), not AgentType slugs (e.g., "claude").

func CompareVersions added in v0.1.1

func CompareVersions(a, b string) bool

CompareVersions returns true if version a is strictly older than version b. Uses simple semver comparison (major.minor.patch).

func ContentHash added in v0.1.1

func ContentHash(content []byte) string

ContentHash returns the first 12 characters of the SHA-256 hex digest of content.

func ExtractCommandHash added in v0.1.1

func ExtractCommandHash(content []byte, prefix string) string

ExtractCommandHash extracts the content hash from a stamped command file. Returns empty string if no hash stamp is found.

func ExtractStampVersion added in v0.1.1

func ExtractStampVersion(content []byte, prefix string) string

ExtractStampVersion extracts the CLI version from a stamped command file. Returns empty string if no version is found in the stamp.

func IsAgentContext

func IsAgentContext() bool

IsAgentContext returns true if running inside any coding agent. This is a convenience function using the default registry.

func IsCommandStale added in v0.1.1

func IsCommandStale(existing []byte, cmd CommandFile, prefix string) bool

IsCommandStale determines whether an installed command file is outdated compared to expected content. Returns false for user-managed files (no stamp) and files installed by a newer version (downgrade guard).

func OrchestratorType added in v0.1.1

func OrchestratorType() string

OrchestratorType returns the type string of the detected orchestrator, or empty string if none detected.

func RequireAgent

func RequireAgent(commandName string) string

RequireAgent returns an error message if not running in an agent context. Returns empty string if in agent context.

func ShouldWriteCommand added in v0.1.1

func ShouldWriteCommand(existing []byte, cmd CommandFile, overwrite bool, prefix string) bool

ShouldWriteCommand determines whether a command file should be written. existing is the current file content on disk (nil if the file doesn't exist). Returns true if the file should be written, false if it should be skipped.

Decision logic:

  • File doesn't exist -> write
  • File exists + overwrite=false -> skip
  • File exists + no hash stamp -> skip (user-managed)
  • Hash matches -> skip (content identical)
  • Hash differs + installed version is newer -> skip (downgrade guard)
  • Otherwise -> write

func StampComment added in v0.1.1

func StampComment(prefix string) string

StampComment returns the HTML comment prefix for a given stamp prefix. Format: "<!-- <prefix>-hash: "

func StampedContent added in v0.1.1

func StampedContent(content []byte, version, prefix string) []byte

StampedContent returns the command content with a hash+version stamp prepended. Format: <!-- <prefix>-hash: <12-char-hash> ver: <version> -->

Types

type Agent

Agent represents a coding agent with full detection and configuration capabilities. It embeds all focused interfaces for complete agent functionality.

When defining functions that work with agents, prefer using the narrowest interface that meets your needs:

  • AgentIdentity: when you only need type, name, or URL
  • AgentDetector: when you need to check if agent is running/installed
  • AgentConfig: when you need configuration paths
  • AgentExtensions: when you need hooks
  • AgentSession: when you need session identification
  • Agent: when you need full functionality or storage/registry

func CurrentAgent

func CurrentAgent() Agent

CurrentAgent returns the currently detected coding agent, or nil if none.

func CurrentOrchestrator added in v0.1.1

func CurrentOrchestrator() Agent

CurrentOrchestrator returns the currently detected orchestrator, or nil if none.

type AgentConfig added in v0.1.1

type AgentConfig interface {
	// UserConfigPath returns the user-level configuration directory (e.g., ~/.claude)
	UserConfigPath(env Environment) (string, error)

	// ProjectConfigPath returns the project-level configuration directory (e.g., .claude/)
	// Returns empty string if the agent doesn't support project-level config
	ProjectConfigPath() string

	// ContextFiles returns the list of context/instruction files this agent supports
	// Examples: CLAUDE.md, AGENTS.md, .cursorrules, .windsurfrules
	ContextFiles() []string

	// SupportsXDGConfig returns true if the agent stores user config in XDG-compliant
	// locations (~/.config/app/) rather than home directory dotfiles (~/.app/).
	// XDG Base Directory Specification is the preferred standard for config locations.
	// See: https://specifications.freedesktop.org/basedir-spec/latest/
	SupportsXDGConfig() bool
}

AgentConfig provides configuration path information. Use this interface when you need to locate agent configuration files.

type AgentDetector added in v0.1.1

type AgentDetector interface {
	// Detect checks if this agent is currently running
	Detect(ctx context.Context, env Environment) (bool, error)

	// IsInstalled checks if this agent is installed on the system.
	// Detection methods vary by agent: binary in PATH, config directory exists,
	// application bundle present (macOS), etc.
	IsInstalled(ctx context.Context, env Environment) (bool, error)

	// DetectVersion attempts to determine the installed version of this agent.
	// Returns empty string if version cannot be determined (best-effort).
	// Strategies vary by agent: CLI --version, reading package.json, etc.
	DetectVersion(ctx context.Context, env Environment) string
}

AgentDetector provides agent detection capabilities. Use this interface when you need to check if an agent is running or installed.

type AgentExtensions added in v0.1.1

type AgentExtensions interface {
	// Capabilities returns what features this agent supports
	Capabilities() Capabilities

	// HookManager returns the hook manager for this agent, or nil if hooks not supported
	HookManager() HookManager

	// CommandManager returns the command manager for this agent, or nil if custom commands not supported
	CommandManager() CommandManager
}

AgentExtensions provides hook and command management capabilities. Use this interface when you need to work with agent extensions.

type AgentIdentity added in v0.1.1

type AgentIdentity interface {
	// Type returns the agent type slug
	Type() AgentType

	// Name returns the human-readable agent name
	Name() string

	// URL returns the official project URL (typically GitHub repo)
	URL() string

	// Role returns whether this is a coding agent or an orchestrator
	Role() AgentRole
}

AgentIdentity provides basic agent identification. Use this interface when you only need to identify an agent without needing detection or configuration capabilities.

type AgentRole added in v0.1.1

type AgentRole string

AgentRole distinguishes agents from orchestrators. Agents are coding tools (Claude Code, Cursor); orchestrators launch and manage agents (OpenClaw, Conductor, Gas Town).

const (
	// RoleAgent is a coding agent that directly invokes commands.
	RoleAgent AgentRole = "agent"

	// RoleOrchestrator launches and manages coding agents.
	RoleOrchestrator AgentRole = "orchestrator"
)

type AgentSession added in v0.1.1

type AgentSession interface {
	// SupportsSession returns true if this agent has a native session/thread
	// concept. True means the agent CAN provide a session ID, even if one
	// isn't available right now (e.g., agent not running, env var not set).
	SupportsSession() bool

	// SessionID returns the agent's native session identifier from the
	// environment. Returns empty string if not currently available.
	// For hook-based agents without a dedicated env var, this returns ""
	// and the caller should use HookInput.SessionID instead.
	SessionID(env Environment) string
}

AgentSession provides access to the agent's native session identifier. Agents that support sessions (most hook-based agents) return true from SupportsSession. The actual session ID may come from an environment variable (e.g., CLAUDE_CODE_SESSION_ID, CODEX_THREAD_ID) or from hook stdin JSON (HookInput.SessionID, handled separately).

type AgentType

type AgentType string

AgentType identifies a coding agent or orchestrator.

const (
	AgentTypeUnknown    AgentType = ""
	AgentTypeClaudeCode AgentType = "claude"
	AgentTypeCursor     AgentType = "cursor"
	AgentTypeWindsurf   AgentType = "windsurf"
	AgentTypeCopilot    AgentType = "copilot"
	AgentTypeAider      AgentType = "aider"
	AgentTypeCody       AgentType = "cody"
	AgentTypeContinue   AgentType = "continue"
	AgentTypeCodePuppy  AgentType = "code-puppy"
	AgentTypeKiro       AgentType = "kiro"
	AgentTypeOpenCode   AgentType = "opencode"
	AgentTypeCodex      AgentType = "codex"
	AgentTypeGoose      AgentType = "goose"
	AgentTypeAmp        AgentType = "amp"
	AgentTypeCline      AgentType = "cline"
	AgentTypeDroid      AgentType = "droid"
	AgentTypePi         AgentType = "pi"
	AgentTypeCustom     AgentType = "custom"

	// Orchestrators
	AgentTypeOpenClaw  AgentType = "openclaw"
	AgentTypeConductor AgentType = "conductor"
)

func ResolveAgentENV added in v0.1.1

func ResolveAgentENV(agentEnv string) AgentType

ResolveAgentENV resolves an AGENT_ENV string to an AgentType. Returns AgentTypeUnknown if not found.

type Capabilities added in v0.1.1

type Capabilities struct {
	// Hooks indicates the agent supports hook installation
	Hooks bool

	// MCPServers indicates the agent supports MCP server configuration
	MCPServers bool

	// SystemPrompt indicates the agent supports custom system instructions
	SystemPrompt bool

	// ProjectContext indicates the agent reads project context files (CLAUDE.md, etc.)
	ProjectContext bool

	// CustomCommands indicates the agent supports custom slash commands
	CustomCommands bool

	// MinVersion is the minimum agent version required for full feature support (future)
	MinVersion string
}

Capabilities describes what features a coding agent supports. This allows tools to adapt their behavior based on agent capabilities.

type CommandFile added in v0.1.1

type CommandFile struct {
	// Name is the filename (e.g., "ox-status.md")
	Name string

	// Content is the file content (without stamp; stamp is added on write)
	Content []byte

	// Version is the version that ships this command.
	// Used as a downgrade guard: an older binary won't overwrite commands
	// installed by a newer binary.
	Version string
}

CommandFile represents a custom slash command to install.

func ReadCommandFiles added in v0.1.1

func ReadCommandFiles(fsys fs.FS, dir string) ([]CommandFile, error)

ReadCommandFiles reads .md files from an fs.FS directory into CommandFile slices. Useful for converting go:embed filesystems into CommandFile arrays for installation.

type CommandManager added in v0.1.1

type CommandManager interface {
	// Install writes command files to the agent's command directory.
	// When overwrite is false, existing files are skipped entirely (safe for init).
	// When overwrite is true, existing files are replaced only if content differs (safe for doctor).
	// Returns the list of filenames that were written.
	Install(ctx context.Context, projectRoot string, commands []CommandFile, overwrite bool) ([]string, error)

	// Uninstall removes command files matching the prefix from the command directory.
	// Returns the list of filenames that were removed.
	Uninstall(ctx context.Context, projectRoot string, prefix string) ([]string, error)

	// Validate checks which expected files are missing or stale in the command directory.
	// Returns missing filenames and stale filenames (content differs from expected).
	Validate(ctx context.Context, projectRoot string, commands []CommandFile) (missing []string, stale []string, err error)

	// CommandDir returns the path to the command directory for a project.
	CommandDir(projectRoot string) string
}

CommandManager handles custom slash command installation for agents.

type Detector added in v0.1.1

type Detector interface {
	// Detect identifies the active coding agent (RoleAgent), returning nil if none detected.
	// Orchestrators are excluded — use DetectOrchestrator for those.
	Detect(ctx context.Context) (Agent, error)

	// DetectOrchestrator returns the active orchestrator (RoleOrchestrator), or nil.
	// This is independent of Detect() which returns the coding agent.
	DetectOrchestrator(ctx context.Context) (Agent, error)

	// DetectAll returns all detected agents and orchestrators
	DetectAll(ctx context.Context) ([]Agent, error)

	// DetectByType checks if a specific agent type is active
	DetectByType(ctx context.Context, agentType AgentType) (bool, error)
}

Detector identifies which coding agent(s) and orchestrator(s) are currently active.

func NewDetector added in v0.1.1

func NewDetector() Detector

NewDetector creates a detector with the default registry.

func NewDetectorWithEnv added in v0.1.1

func NewDetectorWithEnv(env Environment) Detector

NewDetectorWithEnv creates a detector with a custom environment.

type Environment

type Environment interface {
	// GetEnv retrieves an environment variable value
	GetEnv(key string) string

	// LookupEnv retrieves an environment variable and reports if it exists
	LookupEnv(key string) (string, bool)

	// HomeDir returns the user's home directory
	HomeDir() (string, error)

	// ConfigDir returns the XDG config directory
	ConfigDir() (string, error)

	// DataDir returns the XDG data directory
	DataDir() (string, error)

	// CacheDir returns the XDG cache directory
	CacheDir() (string, error)

	// GOOS returns the operating system name
	GOOS() string

	// LookPath searches for an executable in PATH
	LookPath(name string) (string, error)

	// FileExists checks if a file or directory exists
	FileExists(path string) bool

	// IsDir checks if a path is a directory
	IsDir(path string) bool

	// Exec runs a command and returns its stdout output.
	// Returns error if the command fails or is not found.
	Exec(ctx context.Context, name string, args ...string) ([]byte, error)

	// ReadFile reads a file and returns its contents.
	ReadFile(path string) ([]byte, error)

	// WriteFile writes data to a file, creating it if necessary.
	WriteFile(path string, data []byte, perm os.FileMode) error

	// ReadDir reads a directory and returns its entries.
	ReadDir(path string) ([]os.DirEntry, error)

	// MkdirAll creates a directory and all parents.
	MkdirAll(path string, perm os.FileMode) error

	// Remove removes a file or empty directory.
	Remove(path string) error

	// Stat returns file info.
	Stat(path string) (os.FileInfo, error)
}

Environment provides access to system environment for agent detection. This abstraction enables testing without real file system access.

func NewSystemEnvironment

func NewSystemEnvironment() Environment

NewSystemEnvironment creates a new system environment.

type EventHooks added in v0.1.1

type EventHooks map[HookEvent][]HookRule

EventHooks maps lifecycle events to their hook rules. This is the structure used in settings.json under the "hooks" key.

type EventPhaseMap added in v0.1.1

type EventPhaseMap map[HookEvent]Phase

EventPhaseMap maps an agent's native hook events to canonical lifecycle phases.

type HookAction added in v0.1.1

type HookAction struct {
	// Type is the action type. Currently only "command" is supported.
	Type string `json:"type"`

	// Command is the shell command to execute. Receives tool context via stdin as JSON.
	// Exit code 2 from PreToolUse/PermissionRequest hooks denies the action.
	Command string `json:"command"`
}

HookAction defines what happens when a hook triggers.

type HookConfig added in v0.1.1

type HookConfig struct {
	// SourcePath is the path to hook source files (templates, commands)
	SourcePath string

	// MCPServers are MCP server configurations to add
	MCPServers map[string]MCPServerConfig

	// SystemInstructions are custom instructions for the agent
	SystemInstructions string

	// EventHooks are lifecycle event hooks to install (PreToolUse, PostToolUse, etc.)
	// See HookEvent constants for available events.
	EventHooks EventHooks

	// Merge indicates whether to merge with existing config (true) or replace (false)
	Merge bool
}

HookConfig represents hook configuration for installation.

type HookEvent added in v0.1.1

type HookEvent string

HookEvent represents a coding agent lifecycle event that can trigger hooks. Each agent has its own native event names (e.g., Claude's "SessionStart" vs Cursor's "sessionStart" vs Windsurf's "pre_read_code"). Use the agent-specific constants below, or the canonical constants for events shared across agents.

const (
	HookEventSessionStart      HookEvent = "SessionStart"
	HookEventSessionEnd        HookEvent = "SessionEnd"
	HookEventPreToolUse        HookEvent = "PreToolUse"
	HookEventPostToolUse       HookEvent = "PostToolUse"
	HookEventUserPromptSubmit  HookEvent = "UserPromptSubmit"
	HookEventPermissionRequest HookEvent = "PermissionRequest"
	HookEventStop              HookEvent = "Stop"
	HookEventSubagentStop      HookEvent = "SubagentStop"
	HookEventPreCompact        HookEvent = "PreCompact"
)

Canonical hook events shared across multiple agents. Agents may use these exact strings or their own native names.

const (
	CursorEventSessionStart         HookEvent = "sessionStart"
	CursorEventSessionEnd           HookEvent = "sessionEnd"
	CursorEventPreToolUse           HookEvent = "preToolUse"
	CursorEventPostToolUse          HookEvent = "postToolUse"
	CursorEventPostToolUseFailure   HookEvent = "postToolUseFailure"
	CursorEventSubagentStart        HookEvent = "subagentStart"
	CursorEventSubagentStop         HookEvent = "subagentStop"
	CursorEventBeforeShellExecution HookEvent = "beforeShellExecution"
	CursorEventAfterShellExecution  HookEvent = "afterShellExecution"
	CursorEventBeforeMCPExecution   HookEvent = "beforeMCPExecution"
	CursorEventAfterMCPExecution    HookEvent = "afterMCPExecution"
	CursorEventBeforeReadFile       HookEvent = "beforeReadFile"
	CursorEventAfterFileEdit        HookEvent = "afterFileEdit"
	CursorEventBeforeSubmitPrompt   HookEvent = "beforeSubmitPrompt"
	CursorEventPreCompact           HookEvent = "preCompact"
	CursorEventStop                 HookEvent = "stop"
	CursorEventAfterAgentResponse   HookEvent = "afterAgentResponse"
	CursorEventAfterAgentThought    HookEvent = "afterAgentThought"
)

Cursor-specific hook events. Reference: https://cursor.com/docs/agent/hooks

const (
	WindsurfEventPreReadCode                       HookEvent = "pre_read_code"
	WindsurfEventPostReadCode                      HookEvent = "post_read_code"
	WindsurfEventPreWriteCode                      HookEvent = "pre_write_code"
	WindsurfEventPostWriteCode                     HookEvent = "post_write_code"
	WindsurfEventPreRunCommand                     HookEvent = "pre_run_command"
	WindsurfEventPostRunCommand                    HookEvent = "post_run_command"
	WindsurfEventPreMCPToolUse                     HookEvent = "pre_mcp_tool_use"
	WindsurfEventPostMCPToolUse                    HookEvent = "post_mcp_tool_use"
	WindsurfEventPreUserPrompt                     HookEvent = "pre_user_prompt"
	WindsurfEventPostCascadeResponse               HookEvent = "post_cascade_response"
	WindsurfEventPostCascadeResponseWithTranscript HookEvent = "post_cascade_response_with_transcript"
	WindsurfEventPostSetupWorktree                 HookEvent = "post_setup_worktree"
)

Windsurf-specific hook events. Reference: https://docs.windsurf.com/windsurf/cascade/hooks

const (
	KiroEventPromptSubmit      HookEvent = "PromptSubmit"
	KiroEventAgentStop         HookEvent = "AgentStop"
	KiroEventPreToolUse        HookEvent = "PreToolUse"  // same as canonical
	KiroEventPostToolUse       HookEvent = "PostToolUse" // same as canonical
	KiroEventFileCreate        HookEvent = "FileCreate"
	KiroEventFileSave          HookEvent = "FileSave"
	KiroEventFileDelete        HookEvent = "FileDelete"
	KiroEventPreTaskExecution  HookEvent = "PreTaskExecution"
	KiroEventPostTaskExecution HookEvent = "PostTaskExecution"
	KiroEventManualTrigger     HookEvent = "ManualTrigger"
)

Kiro-specific hook events. Reference: https://kiro.dev/docs/hooks/types/

const (
	ClineEventTaskStart        HookEvent = "TaskStart"
	ClineEventTaskResume       HookEvent = "TaskResume"
	ClineEventTaskCancel       HookEvent = "TaskCancel"
	ClineEventTaskComplete     HookEvent = "TaskComplete"
	ClineEventPreToolUse       HookEvent = "PreToolUse"  // same as canonical
	ClineEventPostToolUse      HookEvent = "PostToolUse" // same as canonical
	ClineEventUserPromptSubmit HookEvent = "UserPromptSubmit"
	ClineEventPreCompact       HookEvent = "PreCompact"
)

Cline-specific hook events. Reference: https://docs.cline.bot/features/hooks

const (
	CopilotEventSessionStart        HookEvent = "sessionStart"
	CopilotEventSessionEnd          HookEvent = "sessionEnd"
	CopilotEventUserPromptSubmitted HookEvent = "userPromptSubmitted"
	CopilotEventPreToolUse          HookEvent = "preToolUse"
	CopilotEventPostToolUse         HookEvent = "postToolUse"
	CopilotEventErrorOccurred       HookEvent = "errorOccurred"
)

GitHub Copilot hook events. Reference: https://docs.github.com/en/copilot/reference/hooks-configuration

const (
	AmpEventToolPreExecute  HookEvent = "tool:pre-execute"
	AmpEventToolPostExecute HookEvent = "tool:post-execute"
)

Amp-specific hook events. Reference: https://ampcode.com/news/hooks

const (
	OpenCodeEventSessionCreated    HookEvent = "session.created"
	OpenCodeEventSessionCompacted  HookEvent = "session.compacted"
	OpenCodeEventSessionDeleted    HookEvent = "session.deleted"
	OpenCodeEventSessionError      HookEvent = "session.error"
	OpenCodeEventSessionIdle       HookEvent = "session.idle"
	OpenCodeEventSessionStatus     HookEvent = "session.status"
	OpenCodeEventSessionUpdated    HookEvent = "session.updated"
	OpenCodeEventToolExecuteBefore HookEvent = "tool.execute.before"
	OpenCodeEventToolExecuteAfter  HookEvent = "tool.execute.after"
	OpenCodeEventFileEdited        HookEvent = "file.edited"
	OpenCodeEventPermissionAsked   HookEvent = "permission.asked"
	OpenCodeEventPermissionReplied HookEvent = "permission.replied"
	OpenCodeEventMessageUpdated    HookEvent = "message.updated"
	OpenCodeEventCommandExecuted   HookEvent = "command.executed"
)

OpenCode-specific hook events (plugin system). Reference: https://opencode.ai/docs/plugins/

const (
	DroidEventNotification HookEvent = "Notification"
)

Droid-specific hook events (Factory). Reference: https://docs.factory.ai/reference/hooks-reference

type HookInput added in v0.1.1

type HookInput struct {
	SessionID     string          `json:"session_id"`
	HookEventName string          `json:"hook_event_name"`
	Source        string          `json:"source,omitempty"`  // session start/end source (startup, resume, clear, compact)
	Trigger       string          `json:"trigger,omitempty"` // compact trigger (manual, auto)
	ToolName      string          `json:"tool_name,omitempty"`
	ToolInput     json.RawMessage `json:"tool_input,omitempty"`
	ToolResponse  json.RawMessage `json:"tool_response,omitempty"`
	ToolError     string          `json:"error,omitempty"`

	// RawBytes is the original stdin payload, preserved for faithful passthrough
	// to subprocesses. Not serialized to JSON — used internally only.
	RawBytes []byte `json:"-"`
}

HookInput is the generalized stdin JSON payload from any coding agent's hook system. Agents pipe event context to hook commands via stdin as JSON. This struct captures the common fields across agents (Claude Code, Cursor, Windsurf, Cline, etc.).

Not all fields are present for every event — tool fields only appear for tool events, Source only for session start/end, etc.

RawBytes preserves the original stdin bytes for faithful passthrough to subprocesses. This avoids losing unknown/agent-specific fields during re-serialization.

func ReadHookInput added in v0.1.1

func ReadHookInput(r io.Reader) *HookInput

ReadHookInput reads and parses hook input JSON from the given reader. Returns nil if the reader is empty or doesn't contain valid JSON. Use with os.Stdin in production; pass any io.Reader for testing.

Reads up to maxHookInputSize (256KB) to accommodate large tool payloads. Uses io.ReadAll (with a limit) to handle fragmented pipe reads correctly.

func ReadHookInputFromStdin added in v0.1.1

func ReadHookInputFromStdin() *HookInput

ReadHookInputFromStdin reads hook input from os.Stdin, returning nil if stdin is a terminal (not a pipe) or doesn't contain valid JSON. This is the standard entry point for CLI hook commands.

Note: On some Windows terminal environments, the pipe detection via os.ModeCharDevice may not be reliable. In those cases, this returns nil and the hook proceeds without parsed input.

type HookManager added in v0.1.1

type HookManager interface {
	// Install installs hooks for the agent
	Install(ctx context.Context, config HookConfig) error

	// Uninstall removes installed hooks
	Uninstall(ctx context.Context) error

	// IsInstalled checks if hooks are installed
	IsInstalled(ctx context.Context) (bool, error)

	// Validate validates the current hook configuration
	Validate(ctx context.Context) error
}

HookManager handles hook installation and management for an agent.

type HookRule added in v0.1.1

type HookRule struct {
	// Matcher is a tool name pattern (e.g., "Bash", "Edit|Write", "*" for all).
	// Only applies to PreToolUse, PostToolUse, and PermissionRequest events.
	Matcher string `json:"matcher,omitempty"`

	// Hooks are the actions to execute when this rule matches.
	Hooks []HookAction `json:"hooks"`
}

HookRule defines when and how a hook triggers. Rules use matchers to filter which tools activate the hook.

type HookSupportEntry added in v0.1.1

type HookSupportEntry struct {
	AgentType AgentType
	AgentName string
	Phases    map[Phase][]HookEvent // phase -> native event names (may have multiple)
}

HookSupportEntry represents one agent's lifecycle hook support. Phases maps each canonical phase to the agent's native event name.

func HookSupportMatrix added in v0.1.1

func HookSupportMatrix() []HookSupportEntry

HookSupportMatrix returns the hook support matrix for all registered agents that implement LifecycleEventMapper. Each entry shows which phases the agent supports and the native event name for each phase.

type LifecycleEventMapper added in v0.1.1

type LifecycleEventMapper interface {
	// EventPhases returns the mapping from this agent's native events to canonical phases.
	EventPhases() EventPhaseMap

	// AgentENVAliases returns the AGENT_ENV values that identify this agent in hook commands.
	// The first value is the canonical/preferred alias used in hook installation.
	// Example: Claude Code returns ["claude-code", "claudecode", "claude"].
	AgentENVAliases() []string
}

LifecycleEventMapper is implemented by agents that support lifecycle hooks. It maps the agent's native hook events to canonical lifecycle phases, enabling generic event handling across different coding agents.

type MCPServerConfig added in v0.1.1

type MCPServerConfig struct {
	Command string            `json:"command"`
	Args    []string          `json:"args,omitempty"`
	Env     map[string]string `json:"env,omitempty"`
}

MCPServerConfig represents an MCP server configuration.

type MockEnvironment

type MockEnvironment struct {
	EnvVars       map[string]string
	Home          string
	Config        string
	Data          string
	Cache         string
	OS            string
	HomeError     error
	ExecOutputs   map[string][]byte        // keyed by command name
	ExecErrors    map[string]error         // keyed by command name
	Files         map[string][]byte        // keyed by file path
	PathBinaries  map[string]string        // keyed by binary name -> path
	ExistingFiles map[string]bool          // keyed by path
	ExistingDirs  map[string]bool          // keyed by path
	WrittenFiles  map[string][]byte        // tracks WriteFile calls
	CreatedDirs   map[string]bool          // tracks MkdirAll calls
	RemovedPaths  map[string]bool          // tracks Remove calls
	WriteErrors   map[string]error         // inject write errors by path
	RemoveErrors  map[string]error         // inject remove errors by path
	DirEntries    map[string][]os.DirEntry // mock ReadDir results
	StatErrors    map[string]error         // inject stat errors by path
}

MockEnvironment is a test implementation of Environment.

func NewMockEnvironment

func NewMockEnvironment(envVars map[string]string) *MockEnvironment

NewMockEnvironment creates a mock environment for testing.

func (*MockEnvironment) CacheDir added in v0.1.1

func (e *MockEnvironment) CacheDir() (string, error)

func (*MockEnvironment) ConfigDir

func (e *MockEnvironment) ConfigDir() (string, error)

func (*MockEnvironment) DataDir added in v0.1.1

func (e *MockEnvironment) DataDir() (string, error)

func (*MockEnvironment) Exec added in v0.1.1

func (e *MockEnvironment) Exec(ctx context.Context, name string, args ...string) ([]byte, error)

func (*MockEnvironment) FileExists

func (e *MockEnvironment) FileExists(path string) bool

func (*MockEnvironment) GOOS

func (e *MockEnvironment) GOOS() string

func (*MockEnvironment) GetEnv

func (e *MockEnvironment) GetEnv(key string) string

func (*MockEnvironment) HomeDir

func (e *MockEnvironment) HomeDir() (string, error)

func (*MockEnvironment) IsDir

func (e *MockEnvironment) IsDir(path string) bool

func (*MockEnvironment) LookPath

func (e *MockEnvironment) LookPath(name string) (string, error)

func (*MockEnvironment) LookupEnv

func (e *MockEnvironment) LookupEnv(key string) (string, bool)

func (*MockEnvironment) MkdirAll added in v0.1.3

func (e *MockEnvironment) MkdirAll(path string, perm os.FileMode) error

func (*MockEnvironment) ReadDir added in v0.1.3

func (e *MockEnvironment) ReadDir(path string) ([]os.DirEntry, error)

func (*MockEnvironment) ReadFile added in v0.1.1

func (e *MockEnvironment) ReadFile(path string) ([]byte, error)

func (*MockEnvironment) Remove added in v0.1.3

func (e *MockEnvironment) Remove(path string) error

func (*MockEnvironment) Stat added in v0.1.3

func (e *MockEnvironment) Stat(path string) (os.FileInfo, error)

func (*MockEnvironment) WriteFile added in v0.1.3

func (e *MockEnvironment) WriteFile(path string, data []byte, perm os.FileMode) error

type Phase added in v0.1.1

type Phase string

Phase represents a canonical agent lifecycle moment. Phases are agent-agnostic — each agent maps its native events to these phases.

const (
	PhaseStart      Phase = "start"       // session began
	PhaseEnd        Phase = "end"         // session ending
	PhaseBeforeTool Phase = "before_tool" // tool about to execute
	PhaseAfterTool  Phase = "after_tool"  // tool just completed
	PhasePrompt     Phase = "prompt"      // user submitted a prompt
	PhaseStop       Phase = "stop"        // agent finished responding
	PhaseCompact    Phase = "compact"     // context about to be compacted
)

type Registry

type Registry interface {
	// Register adds an agent to the registry
	Register(agent Agent) error

	// Get retrieves an agent by type
	Get(agentType AgentType) (Agent, bool)

	// List returns all registered agents
	List() []Agent

	// Detector returns a detector for registered agents
	Detector() Detector

	// BuildEventPhaseMap builds alias -> event phase map for all LifecycleEventMapper agents.
	BuildEventPhaseMap() map[string]EventPhaseMap

	// ResolveAgentENV resolves an AGENT_ENV alias to an AgentType.
	ResolveAgentENV(agentEnv string) AgentType

	// HookSupportMatrix returns hook support entries for all LifecycleEventMapper agents.
	HookSupportMatrix() []HookSupportEntry
}

Registry manages available agents and provides detection.

func NewRegistry

func NewRegistry() Registry

NewRegistry creates a new empty agent registry.

type SessionOrigin added in v0.1.2

type SessionOrigin string

SessionOrigin describes who initiated a coding session. Used by session recording systems to classify sessions in metadata.

const (
	// OriginHuman indicates a human is actively prompting the AI coworker.
	// This is the default — most sessions are human-driven.
	OriginHuman SessionOrigin = "human"

	// OriginSubagent indicates the session was spawned by a parent agent session.
	// Detected when known subagent environment signals are present.
	OriginSubagent SessionOrigin = "subagent"

	// OriginAgent indicates autonomous/background agent work with no human in the loop.
	// Detected via CI environment variables or explicitly declared by the caller.
	OriginAgent SessionOrigin = "agent"
)

func DetectOrigin added in v0.1.2

func DetectOrigin(env Environment, explicit SessionOrigin) SessionOrigin

DetectOrigin classifies session initiation from environment signals. Uses definitive signals only — no heuristics or PID sniffing.

Priority:

  1. Known subagent env vars (definitive parent signal) → OriginSubagent
  2. Explicit value from caller (e.g., --origin=agent flag) → returns as-is
  3. Known CI env vars (CI=true, GITHUB_ACTIONS, etc.) → OriginAgent
  4. Default → OriginHuman

func DetectOriginFromOS added in v0.1.2

func DetectOriginFromOS(explicit SessionOrigin) SessionOrigin

DetectOriginFromOS is a convenience wrapper that uses the real system environment.

func ValidOrigins added in v0.1.2

func ValidOrigins() []SessionOrigin

ValidOrigins returns all valid SessionOrigin values.

func (SessionOrigin) IsValid added in v0.1.2

func (o SessionOrigin) IsValid() bool

IsValid returns true if the origin is a recognized value.

type SystemEnvironment

type SystemEnvironment struct{}

SystemEnvironment implements Environment using the real system.

func (*SystemEnvironment) CacheDir added in v0.1.1

func (e *SystemEnvironment) CacheDir() (string, error)

func (*SystemEnvironment) ConfigDir

func (e *SystemEnvironment) ConfigDir() (string, error)

func (*SystemEnvironment) DataDir added in v0.1.1

func (e *SystemEnvironment) DataDir() (string, error)

func (*SystemEnvironment) Exec added in v0.1.1

func (e *SystemEnvironment) Exec(ctx context.Context, name string, args ...string) ([]byte, error)

func (*SystemEnvironment) FileExists

func (e *SystemEnvironment) FileExists(path string) bool

func (*SystemEnvironment) GOOS

func (e *SystemEnvironment) GOOS() string

func (*SystemEnvironment) GetEnv

func (e *SystemEnvironment) GetEnv(key string) string

func (*SystemEnvironment) HomeDir

func (e *SystemEnvironment) HomeDir() (string, error)

func (*SystemEnvironment) IsDir

func (e *SystemEnvironment) IsDir(path string) bool

func (*SystemEnvironment) LookPath

func (e *SystemEnvironment) LookPath(name string) (string, error)

func (*SystemEnvironment) LookupEnv

func (e *SystemEnvironment) LookupEnv(key string) (string, bool)

func (*SystemEnvironment) MkdirAll added in v0.1.3

func (e *SystemEnvironment) MkdirAll(path string, perm os.FileMode) error

func (*SystemEnvironment) ReadDir added in v0.1.3

func (e *SystemEnvironment) ReadDir(path string) ([]os.DirEntry, error)

func (*SystemEnvironment) ReadFile added in v0.1.1

func (e *SystemEnvironment) ReadFile(path string) ([]byte, error)

func (*SystemEnvironment) Remove added in v0.1.3

func (e *SystemEnvironment) Remove(path string) error

func (*SystemEnvironment) Stat added in v0.1.3

func (e *SystemEnvironment) Stat(path string) (os.FileInfo, error)

func (*SystemEnvironment) WriteFile added in v0.1.3

func (e *SystemEnvironment) WriteFile(path string, data []byte, perm os.FileMode) error

Directories

Path Synopsis
Package config provides configuration path resolution for agentx.
Package config provides configuration path resolution for agentx.
Package hooks provides hook management for coding agents.
Package hooks provides hook management for coding agents.
Package setup provides initialization for the agentx package.
Package setup provides initialization for the agentx package.

Jump to

Keyboard shortcuts

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