# 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/   modes/print-mode.ts   modes/rpc/     │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                       Core Layer                            │
│  core/agent-session.ts (central abstraction)               │
│  core/session-manager.ts, core/model-config.ts, etc.       │
└─────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────┐
│                   External Dependencies                     │
│  @mariozechner/pi-agent (Agent, tools)                     │
│  @mariozechner/pi-ai (models, providers)                   │
│  @mariozechner/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
├── config.ts                 # APP_NAME, VERSION, paths (getAgentDir, etc.)

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

├── core/                     # Core business logic (mode-agnostic)
│   ├── index.ts              # Core exports
│   ├── agent-session.ts      # AgentSession class - THE central abstraction
│   ├── bash-executor.ts      # executeBash() with streaming, abort
│   ├── compaction.ts         # Context compaction logic
│   ├── export-html.ts        # exportSession(), exportFromFile()
│   ├── messages.ts           # BashExecutionMessage, messageTransformer
│   ├── model-config.ts       # findModel(), getAvailableModels(), getApiKeyForModel()
│   ├── model-resolver.ts     # resolveModelScope(), restoreModelFromSession()
│   ├── session-manager.ts    # SessionManager class - JSONL persistence
│   ├── settings-manager.ts   # SettingsManager class - user preferences
│   ├── skills.ts             # loadSkills(), skill discovery from multiple locations
│   ├── slash-commands.ts     # loadSlashCommands() from ~/.pi/agent/commands/
│   ├── system-prompt.ts      # buildSystemPrompt(), loadProjectContextFiles()
│   │
│   ├── oauth/                # OAuth authentication
│   │   ├── index.ts          # OAuth exports
│   │   ├── anthropic.ts      # Anthropic OAuth (Claude Pro/Max)
│   │   ├── github-copilot.ts # GitHub Copilot OAuth
│   │   └── storage.ts        # Token persistence
│   │
│   ├── hooks/                # Hook system for extending behavior
│   │   ├── index.ts          # Hook exports
│   │   ├── types.ts          # HookAPI, HookContext, event types
│   │   ├── loader.ts         # loadHooks() from multiple locations
│   │   ├── runner.ts         # runHook() event dispatch
│   │   └── tool-wrapper.ts   # wrapToolsWithHooks() for tool_call events
│   │
│   ├── custom-tools/         # Custom tool loading system
│   │   ├── index.ts          # Custom tool exports
│   │   ├── types.ts          # CustomToolFactory, CustomToolDefinition
│   │   └── loader.ts         # loadCustomTools() from multiple locations
│   │
│   └── tools/                # Built-in tool implementations
│       ├── index.ts          # Tool exports, allTools, codingTools
│       ├── bash.ts           # Bash command execution
│       ├── edit.ts           # Surgical file editing
│       ├── find.ts           # File search by glob
│       ├── grep.ts           # Content search (regex/literal)
│       ├── ls.ts             # Directory listing
│       ├── read.ts           # File reading (text and images)
│       ├── write.ts          # File writing
│       ├── path-utils.ts     # Path resolution utilities
│       └── truncate.ts       # Output truncation utilities

├── modes/                    # Run mode implementations
│   ├── index.ts              # Re-exports InteractiveMode, runPrintMode, runRpcMode, RpcClient
│   ├── print-mode.ts         # Non-interactive: process messages, print output, exit
│   │
│   ├── rpc/                  # RPC mode for programmatic control
│   │   ├── rpc-mode.ts       # runRpcMode() - JSON stdin/stdout protocol
│   │   ├── rpc-types.ts      # RpcCommand, RpcResponse, RpcSessionState types
│   │   └── rpc-client.ts     # RpcClient class for spawning/controlling agent
│   │
│   └── interactive/          # Interactive TUI mode
│       ├── interactive-mode.ts   # InteractiveMode class
│       │
│       ├── components/           # TUI components
│       │   ├── assistant-message.ts    # Agent response rendering
│       │   ├── bash-execution.ts       # Bash output display
│       │   ├── compaction.ts           # Compaction status 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
│       │   ├── model-selector.ts       # Model picker
│       │   ├── oauth-selector.ts       # OAuth provider picker
│       │   ├── queue-mode-selector.ts  # Message queue mode picker
│       │   ├── session-selector.ts     # Session browser for --resume
│       │   ├── show-images-selector.ts # Image display toggle
│       │   ├── theme-selector.ts       # Theme picker
│       │   ├── thinking-selector.ts    # Thinking level picker
│       │   ├── tool-execution.ts       # Tool call/result rendering
│       │   ├── user-message-selector.ts # Message selector for /branch
│       │   └── user-message.ts         # User message rendering
│       │
│       └── theme/
│           ├── theme.ts      # Theme loading, getEditorTheme(), etc.
│           ├── dark.json
│           ├── light.json
│           └── theme-schema.json

└── utils/                    # Generic utilities
    ├── changelog.ts          # parseChangelog(), getNewEntries()
    ├── clipboard.ts          # copyToClipboard()
    ├── fuzzy.ts              # Fuzzy string matching
    ├── mime.ts               # MIME type detection
    ├── shell.ts              # getShellConfig()
    └── tools-manager.ts      # ensureTool() - download fd, etc.
```

## Key Abstractions

### AgentSession (core/agent-session.ts)

The central abstraction that wraps the low-level `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

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

### InteractiveMode (modes/interactive/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](docs/rpc.md) for protocol documentation.

### SessionManager (core/session-manager.ts)

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

### SettingsManager (core/settings-manager.ts)

Handles user preferences:
- Default model/provider
- Theme selection
- Queue mode
- Thinking block visibility
- Compaction settings
- Hook/custom tool paths

### Hook System (core/hooks/)

Extensibility layer for intercepting agent behavior:
- **loader.ts**: Discovers and loads hooks from `~/.pi/agent/hooks/`, `.pi/hooks/`, and CLI
- **runner.ts**: Dispatches events to registered hooks
- **tool-wrapper.ts**: Wraps tools to emit `tool_call` and `tool_result` events
- **types.ts**: Event types (`session`, `tool_call`, `tool_result`, `message`, `error`)

See [docs/hooks.md](docs/hooks.md) for full documentation.

### Custom Tools (core/custom-tools/)

System for adding LLM-callable tools:
- **loader.ts**: Discovers and loads tools from `~/.pi/agent/tools/`, `.pi/tools/`, and CLI
- **types.ts**: `CustomToolFactory`, `CustomToolDefinition`, `CustomToolResult`

See [docs/custom-tools.md](docs/custom-tools.md) for full documentation.

### Skills (core/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](docs/skills.md) for full documentation.

## Development Workflow

### Running in Development

Start the watch build in the monorepo root to continuously rebuild all packages:

```bash
# Terminal 1: Watch build (from monorepo root)
npm run dev
```

Then run the CLI with tsx in a separate terminal:

```bash
# Terminal 2: Run CLI (from monorepo root)
npx tsx packages/coding-agent/src/cli.ts

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

# RPC mode
npx tsx packages/coding-agent/src/cli.ts --mode rpc --no-session
```

The watch build ensures changes to dependent packages (`pi-agent`, `pi-ai`, `pi-tui`) are automatically rebuilt.

### Type Checking

```bash
# From monorepo root
npm run check
```

### Building

```bash
# Build all packages
npm run build

# Build standalone binary
cd packages/coding-agent
npm run build:binary
```

## 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 in `core/tools/` following existing patterns
2. Export from `core/tools/index.ts`
3. Add to `allTools` and optionally `codingTools`
4. Add description to `toolDescriptions` in `core/system-prompt.ts`

### Adding a New Hook Event

1. Add event type to `HookEvent` union in `core/hooks/types.ts`
2. Add emission point in relevant code (AgentSession, tool wrapper, etc.)
3. Document in `docs/hooks.md`

### Adding a New RPC Command

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

### Adding a New Selector

1. Create component in `modes/interactive/components/`
2. Use `showSelector()` helper in `interactive-mode.ts`:

```typescript
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() };
    });
}
```

## Testing

The package uses E2E tests only (no unit tests by design). Tests are in `test/`:

```bash
# Run all tests
npm test

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

# Run RPC example interactively
npx tsx test/rpc-example.ts
```

## Code Style

- No `any` types unless absolutely necessary
- No inline dynamic imports
- Use `showStatus()` for dim status messages
- Use `showError()` / `showWarning()` for errors/warnings
- Keep InteractiveMode focused on UI, delegate logic to AgentSession
