Files
oh-my-pi/packages/coding-agent/DEVELOPMENT.md
T
can1357 a5d26bb5f5 feat(react-edit-benchmark): migrated benchmark to directory-based fixtures with RPC resource management
- Added `--no-rules` CLI flag to coding-agent to disable rules discovery and loading.
- Added `rules` option to CreateAgentSessionOptions to allow custom rules configuration.
- Added `sessionDir` option to RpcClientOptions and implemented Symbol.dispose() for resource cleanup.
- Removed tarball-based task loading; migrated to directory-based fixtures with required inputDir and expectedDir properties.
- Refactored runner to use RpcClient resource management with `using` statement and simplified fixture handling.
- Consolidated type definitions and removed tarball.ts module in favor of streamlined task interface.
2026-02-18 05:14:19 +01:00

32 KiB

coding-agent Development Guide

This document describes the architecture and development workflow for the coding-agent package.

Architecture Overview

The coding-agent is structured into distinct layers:

┌─────────────────────────────────────────────────────────────┐
│                         CLI Layer                           │
│  cli.ts → main.ts → cli/args.ts, cli/file-processor.ts     │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                       Mode Layer                            │
│  modes/interactive-mode.ts   modes/print-mode.ts   modes/rpc/│
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                       Core Layer                            │
│  session/agent-session.ts, sdk.ts (SDK wrapper)            │
│  session/session-manager.ts, config/model-resolver.ts, etc.│
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                   External Dependencies                     │
│  @oh-my-pi/pi-agent-core (Agent core)                      │
│  @oh-my-pi/pi-ai (models, providers)                   │
│  @oh-my-pi/pi-tui (TUI components)                         │
└─────────────────────────────────────────────────────────────┘

Directory Structure

src/
├── cli.ts                    # CLI entry point (shebang, calls main)
├── main.ts                   # Main orchestration, argument handling, mode routing
├── index.ts                  # Public API exports (SDK)
├── config.ts                 # APP_NAME, VERSION, paths (getAgentDir, etc.)
├── migrations.ts             # Session/config migration logic
├── sdk.ts                    # SDK wrapper for programmatic usage
├── system-prompt.ts          # buildSystemPrompt(), loadProjectContextFiles()
├── cursor.ts                 # Cursor exec bridge (flattened from execution/cursor/)

├── cli/                      # CLI-specific utilities
│   ├── args.ts               # parseArgs(), printHelp(), Args interface
│   ├── file-processor.ts     # processFileArguments() for @file args
│   ├── list-models.ts        # --list-models implementation
│   ├── plugin-cli.ts         # Plugin management CLI
│   ├── session-picker.ts     # selectSession() TUI for --resume
│   └── update-cli.ts         # Self-update CLI

├── capability/               # Capability system (extension types)
│   ├── index.ts              # Main capability registry and discovery
│   ├── context-file.ts       # Context file capability
│   ├── extension.ts          # Extension capability
│   ├── hook.ts               # Hook capability
│   ├── instruction.ts        # Instruction capability
│   ├── mcp.ts                # MCP capability
│   ├── prompt.ts             # Prompt capability
│   ├── rule.ts               # Rulebook rule capability
│   ├── skill.ts              # Skill capability
│   ├── slash-command.ts      # Slash command capability
│   ├── system-prompt.ts      # System prompt capability
│   └── tool.ts               # Tool capability

├── config/                   # Configuration management
│   ├── keybindings.ts        # Keybinding configuration
│   ├── model-registry.ts     # Model registry and configuration
│   ├── model-resolver.ts     # resolveModelScope(), restoreModelFromSession()
│   ├── prompt-templates.ts   # Prompt template loading and rendering
│   └── settings-manager.ts   # SettingsManager class - user preferences

├── discovery/                # Extension discovery from multiple sources
│   ├── index.ts              # Main discovery orchestration
│   ├── builtin.ts            # Built-in extensions
│   ├── claude.ts             # Claude.md discovery
│   ├── cline.ts              # Cline .mcp.json discovery
│   ├── codex.ts              # .codex discovery
│   ├── cursor.ts             # Cursor .cursorrules discovery
│   ├── gemini.ts             # Gemini .config discovery
│   ├── github.ts             # GitHub extension discovery
│   ├── mcp-json.ts           # MCP JSON discovery
│   ├── vscode.ts             # VSCode extension discovery
│   ├── windsurf.ts           # Windsurf discovery
│   └── helpers.ts            # Discovery helper functions

├── exa/                      # Exa MCP tools (22 tools)
│   ├── index.ts              # Exa tools exports
│   ├── company.ts            # Company search
│   ├── linkedin.ts           # LinkedIn search
│   ├── mcp-client.ts         # MCP client for Exa
│   ├── render.ts             # Exa result rendering
│   ├── researcher.ts         # Research tools
│   ├── search.ts             # Search tools
│   ├── types.ts              # Exa types
│   └── websets.ts            # Websets tools

├── exec/                     # Bash/shell execution
│   ├── bash-executor.ts      # executeBash() with streaming, abort
│   └── exec.ts               # Process execution utilities

├── export/                   # Export functionality
│   ├── custom-share.ts       # Custom sharing
│   ├── ttsr.ts               # Text-to-speech/speech-to-text utilities
│   └── html/                 # Session export to HTML
│       └── index.ts          # HTML export logic

├── extensibility/            # Extension and customization systems
│   ├── skills.ts             # loadSkills(), skill discovery
│   ├── slash-commands.ts     # loadSlashCommands()
│   │
│   ├── custom-commands/      # Custom command loading system
│   │   └── types.ts          # CustomCommand types
│   │
│   ├── custom-tools/         # Custom tool loading system
│   │   ├── index.ts          # Custom tool exports
│   │   ├── types.ts          # CustomToolFactory, CustomToolDefinition
│   │   ├── loader.ts         # loadCustomTools()
│   │   └── wrapper.ts        # Tool wrapper utilities
│   │
│   ├── extensions/           # Extension system
│   │   ├── index.ts          # Extension exports
│   │   ├── types.ts          # Extension types
│   │   ├── loader.ts         # Extension loading
│   │   ├── runner.ts         # Extension event dispatch
│   │   └── wrapper.ts        # Extension wrappers
│   │
│   ├── hooks/                # Hook system
│   │   ├── index.ts          # Hook exports
│   │   ├── types.ts          # HookAPI, HookContext, event types
│   │   ├── loader.ts         # loadHooks()
│   │   └── runner.ts         # runHook() event dispatch
│   │
│   └── plugins/              # Plugin system
│       └── (plugin loading and management)

├── internal-urls/            # Internal URL protocol handlers
│   ├── index.ts              # Router and exports
│   ├── rule-protocol.ts      # Rule protocol handler
│   └── skill-protocol.ts     # Skill protocol handler

├── ipy/                      # Python/Jupyter execution
│   ├── executor.ts           # executePython(), kernel execution
│   ├── gateway-coordinator.ts # Gateway coordination
│   ├── kernel.ts             # PythonKernel class
│   ├── modules.ts            # Python module discovery
│   ├── prelude.ts            # Python prelude (TS bindings)
│   └── prelude.py            # Python prelude script

├── lsp/                      # LSP integration
│   ├── index.ts              # LSP exports
│   ├── client.ts             # LSP client
│   ├── config.ts             # LSP configuration
│   ├── render.ts             # LSP result rendering
│   ├── utils.ts              # LSP utilities
│   └── clients/              # Language-specific clients
│       ├── index.ts          # Client exports
│       ├── biome-client.ts   # Biome LSP
│       └── lsp-linter-client.ts # Generic linter

├── mcp/                      # MCP (Model Context Protocol) integration
│   ├── index.ts              # MCP exports
│   ├── config.ts             # MCP configuration
│   ├── loader.ts             # MCP server loading
│   ├── manager.ts            # MCPManager class
│   ├── tool-bridge.ts        # Tool bridging
│   ├── tool-cache.ts         # Tool caching
│   ├── types.ts              # MCP types
│   └── transports/           # MCP transports
│       ├── http.ts           # HTTP transport
│       └── stdio.ts          # Stdio transport

├── modes/                    # Run mode implementations
│   ├── index.ts              # Re-exports InteractiveMode, runPrintMode, runRpcMode
│   ├── interactive-mode.ts   # InteractiveMode class (main TUI)
│   ├── print-mode.ts         # Non-interactive mode
│   ├── types.ts              # Mode types
│   │
│   ├── components/           # TUI components
│   │   ├── index.ts          # Component exports
│   │   ├── assistant-message.ts    # Agent response rendering
│   │   ├── bash-execution.ts       # Bash output display
│   │   ├── custom-editor.ts        # Multi-line input editor
│   │   ├── dynamic-border.ts       # Adaptive border rendering
│   │   ├── footer.ts               # Status bar / footer
│   │   ├── hook-input.ts           # Hook input dialog
│   │   ├── hook-selector.ts        # Hook selection UI
│   │   ├── login-dialog.ts         # OAuth login dialog
│   │   ├── model-selector.ts       # Model picker
│   │   ├── session-selector.ts     # Session browser for --resume
│   │   ├── theme-selector.ts       # Theme picker
│   │   ├── thinking-selector.ts    # Thinking level picker
│   │   ├── tool-execution.ts       # Tool call/result rendering
│   │   ├── user-message.ts         # User message rendering
│   │   ├── status-line/            # Status line components
│   │   └── extensions/             # Extension UI components
│   │
│   ├── controllers/          # TUI controllers
│   │   ├── command-controller.ts   # Command handling
│   │   ├── event-controller.ts     # Event handling
│   │   ├── input-controller.ts     # Input handling
│   │   └── selector-controller.ts  # Selector handling
│   │
│   ├── theme/                # Theme system
│   │   ├── theme.ts          # Theme loading, getEditorTheme()
│   │   └── defaults/         # Default themes
│   │
│   ├── utils/                # Mode utilities
│   │   └── ui-helpers.ts     # UI helper functions
│   │
│   └── rpc/                  # RPC mode for programmatic control
│       ├── rpc-mode.ts       # runRpcMode() - JSON stdin/stdout protocol
│       ├── rpc-types.ts      # RpcCommand, RpcResponse types
│       └── rpc-client.ts     # RpcClient class

├── patch/                    # File patching/editing
│   ├── index.ts              # EditTool and exports
│   ├── applicator.ts         # Patch application
│   ├── diff.ts               # Diff utilities
│   ├── shared.ts             # Shared utilities
│   └── render.ts             # Patch rendering

├── prompts/                  # Prompt templates
│   ├── system/               # System prompts
│   ├── agents/               # Agent-specific prompts
│   ├── compaction/           # Compaction prompts
│   └── tools/                # Tool-specific prompts

├── session/                  # Session management
│   ├── agent-session.ts      # AgentSession class - THE central abstraction
│   ├── agent-storage.ts      # Agent storage utilities
│   ├── auth-storage.ts       # AuthStorage class - API keys and OAuth
│   ├── artifacts.ts          # Artifact management
│   ├── history-storage.ts    # History storage
│   ├── messages.ts           # Message types and transformers
│   ├── session-manager.ts    # SessionManager class - JSONL persistence
│   ├── session-storage.ts    # Session storage utilities
│   ├── storage-migration.ts  # Storage migration
│   ├── streaming-output.ts   # OutputSink with spill-to-disk for large outputs
│   └── compaction/           # Context compaction system
│       └── index.ts          # Compaction logic, summary generation

├── ssh/                      # SSH execution
│   ├── connection-manager.ts # SSH connection management
│   ├── ssh-executor.ts       # executeSSH() with streaming
│   └── sshfs-mount.ts        # SSHFS mounting

├── task/                     # Task/subagent spawning
│   ├── index.ts              # TaskTool exports
│   ├── agents.ts             # Agent definitions
│   ├── commands.ts           # Task commands
│   ├── discovery.ts          # Task discovery
│   ├── executor.ts           # In-process task execution
│   ├── render.ts             # Task rendering
│   ├── subprocess-tool-registry.ts # Subprocess tool registry
│   ├── parallel.ts           # Task concurrency helpers
│   └── template.ts           # Task templating

├── tools/                    # Built-in tool implementations
│   ├── index.ts              # Tool exports, BUILTIN_TOOLS, createTools
│   ├── ask.ts                # User input tool
│   ├── bash.ts               # Bash command execution
│   ├── bash-interceptor.ts   # Bash command interception
│   ├── calculator.ts         # Calculator tool
│   ├── submit-result.ts      # Submit result tool
│   ├── context.ts            # Tool context utilities
│   ├── fetch.ts              # URL content fetching
│   ├── find.ts               # File search by glob
│   ├── gemini-image.ts       # Gemini image generation
│   ├── grep.ts               # Content search (regex/literal)
│   ├── ls.ts                 # Directory listing
│   ├── notebook.ts           # Jupyter notebook editing
│   ├── output-meta.ts        # OutputMetaBuilder, wrapToolWithMetaNotice
│   ├── output-utils.ts       # TailBuffer, allocateOutputArtifact helpers
│   ├── path-utils.ts         # Path resolution utilities
│   ├── python.ts             # Python tool (delegates to ipy/)
│   ├── read.ts               # File reading (text and images)
│   ├── renderers.ts          # Tool output renderers
│   ├── render-utils.ts       # Rendering utilities
│   ├── review.ts             # Code review tools
│   ├── ssh.ts                # SSH tool (delegates to ssh/)
│   ├── todo-write.ts         # Todo management
│   ├── tool-errors.ts        # Tool error types
│   ├── tool-result.ts        # ToolResultBuilder fluent API
│   ├── truncate.ts           # Output truncation utilities
│   └── write.ts              # File writing

├── utils/                    # Generic utilities
│   ├── changelog.ts          # parseChangelog(), getNewEntries()
│   ├── event-bus.ts          # Event bus for tool communication
│   ├── file-mentions.ts      # File mention detection
│   ├── frontmatter.ts        # Frontmatter parsing
│   ├── image-convert.ts      # Image format conversion
│   ├── image-resize.ts       # Image resizing utilities
│   ├── mime.ts               # MIME type detection
│   ├── shell.ts              # getShellConfig()
│   ├── terminal-notify.ts    # Terminal notification utilities
│   ├── timings.ts            # Performance timing utilities
│   ├── title-generator.ts    # Session title generation
│   ├── tools-manager.ts      # ensureTool() - download fd, etc.
│   └── utils.ts              # Generic utilities

├── vendor/                   # Vendored dependencies
│   └── photon/               # Photon image processing

└── web/                      # Web tools (merged web-scrapers + web-search)
    ├── scrapers/             # Web scraping
    │   ├── index.ts          # Scraper exports
    │   ├── types.ts          # Scraper types
    │   ├── utils.ts          # Scraper utilities
    │   └── (domain-specific scrapers)
    └── search/               # Web search
        ├── index.ts          # SearchTool exports
        ├── auth.ts           # Search auth
        ├── render.ts         # Search result rendering
        └── providers/        # Search providers
            ├── anthropic.ts  # Anthropic search
            ├── exa.ts        # Exa search
            └── perplexity.ts # Perplexity search

Key Abstractions

AgentSession (session/agent-session.ts)

The central abstraction that wraps the SDK Agent with:

  • Session persistence (via SessionManager)
  • Settings persistence (via SettingsManager)
  • Model cycling with scoped models
  • Context compaction
  • Bash command execution
  • Message queuing
  • Hook integration
  • Custom tool loading
  • Extension/capability system integration

All three modes (interactive, print, rpc) use AgentSession.

SDK (sdk.ts)

Wrapper around @oh-my-pi/pi-agent-core that provides a simplified interface for creating and managing agents programmatically. Used by AgentSession and available as a public API through index.ts exports.

InteractiveMode (modes/interactive-mode.ts)

Handles TUI rendering and user interaction:

  • Subscribes to AgentSession events
  • Renders messages, tool executions, streaming
  • Manages editor, selectors, key handlers
  • Delegates all business logic to AgentSession

RPC Mode (modes/rpc/)

Headless operation via JSON protocol over stdin/stdout:

  • rpc-mode.ts: runRpcMode() function that listens for JSON commands on stdin and emits responses/events on stdout
  • rpc-types.ts: Typed protocol definitions (RpcCommand, RpcResponse, RpcSessionState)
  • rpc-client.ts: RpcClient class for spawning the agent as a subprocess and controlling it programmatically

The RPC mode exposes the full AgentSession API via JSON commands. See docs/rpc.md for protocol documentation.

SessionManager (session/session-manager.ts)

Handles session persistence:

  • JSONL format for append-only writes
  • Session file location management
  • Message loading/saving
  • Model/thinking level persistence

SettingsManager (config/settings-manager.ts)

Handles user preferences:

  • Default model/provider
  • Theme selection
  • Queue mode
  • Thinking block visibility
  • Compaction settings
  • Hook/custom tool paths
  • Thinking budgets (thinkingBudgets setting for custom token budgets per level)
  • Image blocking (blockImages setting to prevent images from being sent to LLM)

Hook System (extensibility/hooks/)

Extensibility layer for intercepting agent behavior:

  • loader.ts: Discovers and loads hooks from ~/.omp/agent/hooks/, .omp/hooks/, and CLI
  • runner.ts: Dispatches events to registered hooks
  • types.ts: Event types (session, tool_call, tool_result, message, error, user_bash)

See docs/hooks.md for full documentation.

Extension System Architecture

The extension system uses a shared runtime pattern:

  1. ExtensionRuntime (extensibility/extensions/types.ts): Shared state and action handlers for all extensions
  2. Extension: Per-extension registration data (handlers, tools, commands, shortcuts)
  3. ExtensionAPI: Per-extension API that writes registrations to Extension and delegates actions to runtime
  4. ExtensionRunner: Orchestrates event dispatch and provides context to handlers

Key extension events:

  • before_agent_start: Receives systemPrompt and can return full replacement (not just append)
  • user_bash: Intercept !/!! commands for custom execution (e.g., remote SSH)
  • session_shutdown: Cleanup notification before exit

Custom Tools (extensibility/custom-tools/)

System for adding LLM-callable tools:

  • loader.ts: Discovers and loads tools from ~/.omp/agent/tools/, .omp/tools/, and CLI
  • types.ts: CustomToolFactory, CustomToolDefinition, CustomToolResult

See docs/custom-tools.md for full documentation.

Skills (extensibility/skills.ts)

On-demand capability packages:

  • Discovers SKILL.md files from multiple locations
  • Provides specialized workflows and instructions
  • Loaded when task matches description

See docs/skills.md for full documentation.

Capability System (capability/)

Unified extension system that discovers and loads capabilities from multiple sources:

  • Extension Discovery (discovery/): Discovers extensions from Claude.md, .cursorrules, .codex, MCP servers, etc.
  • Capability Types: Hooks, tools, context files, rules, skills, slash commands, system prompts, etc.
  • Multi-source: Global (~/.omp/), project (.omp/), and built-in capabilities

See docs/extensions.md for full documentation.

Tool Output Infrastructure (tools/)

Standardized system for handling tool outputs with truncation, streaming, and artifact storage.

Core Components

OutputSink (session/streaming-output.ts): Line-buffered output collector with automatic spill-to-disk.

  • Tracks total lines/bytes as data flows through
  • Spills to artifact file when memory threshold exceeded
  • Produces OutputSummary with truncation metadata
  • Used by executors (bash, python, ssh) for streaming command output

TailBuffer (tools/output-utils.ts): Simple rolling buffer keeping the last N bytes.

  • Used for UI preview during streaming
  • Handles UTF-8 boundaries correctly
  • Lightweight alternative to OutputSink for non-spilling tools

ToolResultBuilder (tools/tool-result.ts): Fluent builder for constructing AgentToolResult.

  • Chains .text(), .truncation*(), .limits(), .sourcePath() methods
  • Automatically populates details.meta with structured metadata
  • Produces well-formed result via .done()

OutputMetaBuilder (tools/output-meta.ts): Fluent builder for OutputMeta structure.

  • Methods for truncation info, limits, source paths, diagnostics
  • Accepts TruncationResult (from truncate.ts) or OutputSummary (from OutputSink)
  • Returns undefined if no metadata to report (empty case)

wrapToolWithMetaNotice (tools/output-meta.ts): Tool wrapper applied to all built-in tools.

  • Automatically appends truncation/limit notices from details.meta
  • Catches exceptions and renders them via ToolError.render()

Standard Pattern for Streaming Tools

Tools that produce potentially large streaming output (bash, python, ssh):

async execute(...): Promise<AgentToolResult<MyDetails>> {
  // 1. Allocate artifact path for full output storage
  const { artifactPath, artifactId } = await allocateOutputArtifact(this.session, "toolname");

  // 2. Create tail buffer for UI preview
  const tailBuffer = createTailBuffer(DEFAULT_MAX_BYTES);

  // 3. Execute with streaming callback
  const result = await executeCommand({
    artifactPath,
    artifactId,
    onChunk: (chunk) => {
      tailBuffer.append(chunk);
      onUpdate?.({
        content: [{ type: "text", text: tailBuffer.text() }],
        details: {},
      });
    },
  });

  // 4. Build result with truncation metadata
  return toolResult<MyDetails>({})
    .text(result.output)
    .truncationFromSummary(result, { direction: "tail" })
    .done();
}

Standard Pattern for Non-Streaming Tools

Tools that produce output in one shot (grep, find, ls):

async execute(...): Promise<AgentToolResult<MyDetails>> {
  const { items, limitReached } = await doWork();
  const output = formatItems(items);

  return toolResult<MyDetails>({})
    .text(output)
    .limits({ resultLimit: limitReached ? effectiveLimit : undefined })
    .done();
}

Key Principles

  1. Always allocate artifact before execution — ensures path exists for spill
  2. Pass onChunk to OutputSink/executor — required for live UI updates during streaming
  3. Use ToolResultBuilder for all results — ensures consistent metadata structure
  4. Close file sinks on error — prevent descriptor leaks in failure paths
  5. Truncation direction matters — "tail" for command output (show recent), "head" for file reads (show beginning)

Development Workflow

Running in Development

Run the CLI directly with bun (this is a bun-based project):

# From monorepo root
bun run dev

# Or run directly
bun packages/coding-agent/src/cli.ts

# With arguments
bun packages/coding-agent/src/cli.ts --help
bun packages/coding-agent/src/cli.ts -p "Hello"

# RPC mode
bun packages/coding-agent/src/cli.ts --mode rpc --no-session

Type Checking

# From monorepo root (runs biome + tsgo type check)
bun run check

# From packages/coding-agent
bun run check

Building

# Type check and build (from packages/coding-agent)
bun run build

# Build standalone binary
bun run build:binary

Testing

# Run tests (from packages/coding-agent)
bun test

# Run specific test pattern
bun test --testNamePattern="RPC"

# Run RPC example interactively
bun test/rpc-example.ts

Managed Binaries

Tools like fd and rg are auto-downloaded to ~/.omp/bin/ (migrated from ~/.omp/agent/tools/).

Adding New Features

Adding a New Slash Command

  1. If it's a UI-only command (e.g., /theme), add handler in interactive-mode.ts setupEditorSubmitHandler()
  2. If it needs session logic, add method to AgentSession and call from mode

Adding a New Tool

  1. Create tool factory in tools/ following existing patterns (e.g., createMyTool(session: ToolSession))
  2. Export factory and types from tools/index.ts
  3. Add to BUILTIN_TOOLS map in tools/index.ts
  4. Add tool prompt template to prompts/tools/ if needed
  5. Tool will automatically be included in system prompt
  6. Use ToolResultBuilder for results and OutputMeta for truncation/limit metadata (see "Tool Output Infrastructure" section)
  7. For streaming tools: use allocateOutputArtifact() + createTailBuffer() pattern with onChunk callback

Adding a New Hook Event

  1. Add event type to hook event types in extensibility/hooks/types.ts
  2. Add emission point in relevant code (AgentSession, tool wrapper, etc.)
  3. Update docs/hooks.md with the new event type

Adding a New RPC Command

  1. Add command type to RpcCommand union in modes/rpc/rpc-types.ts
  2. Add response type to RpcResponse union in modes/rpc/rpc-types.ts
  3. Add handler case in handleCommand() switch in modes/rpc/rpc-mode.ts
  4. Add client method in RpcClient class in modes/rpc/rpc-client.ts
  5. Update docs/rpc.md with the new command

Adding a New Selector

  1. Create component in modes/components/
  2. Use showSelector() helper in interactive-mode.ts:
private showMySelector(): void {
    this.showSelector((done) => {
        const selector = new MySelectorComponent(
            // ... params
            (result) => {
                // Handle selection
                done();
                this.showStatus(`Selected: ${result}`);
            },
            () => {
                done();
                this.ui.requestRender();
            },
        );
        return { component: selector, focus: selector.getSelectList() };
    });
}

Adding a New Extension Source

  1. Create discovery module in discovery/ (e.g., my-source.ts)
  2. Implement discovery functions that return capability objects
  3. Add to discovery chain in discovery/index.ts
  4. Update docs/extension-loading.md with the new source

Adding a New Capability Type

  1. Create capability module in capability/ (e.g., my-capability.ts)
  2. Define capability type and schema
  3. Add to capability registry in capability/index.ts
  4. Add loader/handler in relevant core module
  5. Update docs/extensions.md with the new capability type

Adding a New Keybinding

  1. Add the action name to AppAction type in config/keybindings.ts
  2. Add default binding to DEFAULT_APP_KEYBINDINGS
  3. Add to APP_ACTIONS array
  4. Handle the action in CustomEditor or InteractiveMode

Example: The dequeue action (Alt+Up) restores queued messages to the editor.

Code Style

  • TypeScript with strict type checking (tsgo)
  • No any types unless absolutely necessary
  • No inline dynamic imports
  • Formatting via Biome (bun run check or bun run fix)
  • Keep InteractiveMode focused on UI, delegate logic to AgentSession
  • Use event bus for tool/extension communication
  • Components should override invalidate() to rebuild on theme changes

Package Structure

This is part of a monorepo with the following packages:

  • @oh-my-pi/pi-coding-agent (this package) - Main CLI and TUI
  • @oh-my-pi/pi-agent-core - Core agent implementation
  • @oh-my-pi/pi-tui - TUI components
  • @oh-my-pi/pi-ai - External AI provider library

CLI Flags

Key CLI flags for development:

  • --no-tools: Disable all built-in tools (extension-only setups)
  • --no-extensions: Disable extension discovery (explicit -e paths still work)
  • --no-skills: Disable skill discovery
  • --no-rules: Disable rules discovery
  • --session <id>: Resume by session ID prefix (UUID match) or path

SDK Exports

The SDK (src/index.ts) exports run modes for programmatic usage:

  • InteractiveMode: Full TUI mode
  • runPrintMode(): Non-interactive, process messages and exit
  • runRpcMode(): JSON stdin/stdout protocol

Extension types and utilities are also exported for building custom extensions.

Documentation

See the docs/ directory for detailed documentation:

  • docs/sdk.md - SDK usage and examples
  • docs/rpc.md - RPC protocol documentation
  • docs/hooks.md - Hook system documentation
  • docs/extensions.md - Extension system documentation
  • docs/custom-tools.md - Custom tool development
  • docs/skills.md - Skill system documentation
  • docs/compaction.md - Context compaction system
  • docs/session.md - Session management
  • docs/theme.md - Theme customization
  • docs/tui.md - TUI architecture