ivorn 0.1.7

A web-based chat interface for ACP-compatible AI coding agents
Documentation
# Ivorn

A web-based chat interface for ACP-compatible AI coding agents (like Kiro CLI, Goose, etc.) that enables browser-based interaction with multiple parallel project sessions.

Named after Ivor the Engine, the beloved Welsh stop-motion steam train.

## Tech Stack

| Component | Technology | Purpose |
|-----------|------------|---------|
| Backend | [Rust]https://www.rust-lang.org/ + [Axum]https://github.com/tokio-rs/axum | HTTP server, process management, async runtime |
| Frontend | [Alpine.js]https://alpinejs.dev/ | Lightweight reactive UI (~15KB, no build step) |
| Communication | [SSE]https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events + POST | Server-to-browser streaming, client requests |
| Protocol | [ACP]https://agentclientprotocol.com/ | JSON-RPC 2.0 over stdio for agent communication |
| Build | cargo only | Single binary, no separate frontend build |

## Architecture

```mermaid
flowchart LR
    Browser["Browser<br/>(Alpine.js)"]
    Axum["Axum Server"]
    ACP["ACP Module"]
    Agent["ACP Agent<br/>(e.g., Kiro CLI)"]
    
    Browser -->|"POST /api/..."| Axum
    Axum -->|"SSE stream"| Browser
    Axum --- ACP
    ACP -->|"stdin (JSON-RPC)"| Agent
    Agent -->|"stdout (JSON-RPC)"| ACP
```

The browser connects to the Axum server via HTTP. User messages are sent as POST requests. Agent responses stream back via Server-Sent Events (SSE). The ACP module (`src/acp/`) handles JSON-RPC 2.0 communication with agents over stdin/stdout.

## Quick Start

### Prerequisites

- [Rust]https://rustup.rs/ (stable toolchain)

### Build and Run

```bash
# Build
cargo build --release

# Run (provide path to projects directory)
./target/release/ivorn /path/to/projects

# Or use environment variable
IVORN_PROJECTS_DIR=/path/to/projects ./target/release/ivorn
```

Open http://localhost:8181 in your browser.

## Configuration

<details>
<summary><strong>User Config (~/.config/ivorn/config.toml)</strong></summary>

Configuration is loaded from `~/.config/ivorn/config.toml` (XDG spec). See [config.example.toml](config.example.toml) for all available settings.

```toml
[server]
port = 8181
bind_address = "0.0.0.0"

[paths]
projects_dir = "/path/to/projects"

[logging]
conversation_log = true  # Enable conversation logging to markdown files

[upload]
max_size_mb = 25    # Maximum upload file size in MB
```

**Environment Variables**

All settings support environment variable overrides with `IVORN_` prefix:

| Variable | Description | Default |
|----------|-------------|---------|
| `IVORN_SERVER_PORT` | Server port | `8181` |
| `IVORN_SERVER_BIND_ADDRESS` | Bind address | `0.0.0.0` |
| `IVORN_PROJECTS_DIR` | Projects directory | Current directory |
| `IVORN_LOGGING_CONVERSATION_LOG` | Enable conversation logging | `true` |
| `IVORN_UPLOAD_MAX_SIZE_MB` | Maximum upload file size in MB | `25` |

**Precedence** (highest to lowest): CLI arguments > Environment variables > Config file > Defaults

</details>

<details>
<summary><strong>ACP Agent Config (~/.config/ivorn/acp/*.toml or *.json)</strong></summary>

ACP agents are external executables that implement the [Agent Client Protocol](https://agentclientprotocol.com/) over stdio. Ivorn discovers agents from configuration files.

**Configuration Locations**

- **Global**: `~/.config/ivorn/acp/*.json` or `*.toml`
- **Project-local**: `{project}/.ivorn/acp/*.json` or `*.toml` (takes precedence)

When both JSON and TOML files define the same agent name, TOML takes precedence.

**TOML format** (`kiro-cli.toml`):

```toml
name = "kiro-cli"
title = "Kiro CLI"
command = "/usr/local/bin/kiro-cli"
args = ["acp"]
description = "Default Kiro CLI agent"

[[env]]
name = "KEY"
value = "val"
```

**JSON format** (`kiro-cli.json`):

```json
{
  "name": "kiro-cli",
  "title": "Kiro CLI",
  "command": "/usr/local/bin/kiro-cli",
  "args": ["acp"],
  "description": "Default Kiro CLI agent",
  "env": [{"name": "KEY", "value": "val"}]
}
```

| Field | Required | Description |
|-------|----------|-------------|
| `name` | Yes | Unique identifier for the agent |
| `title` | No | Display name (falls back to `name`) |
| `command` | Yes | Path to the executable |
| `args` | No | Command-line arguments |
| `description` | No | Description shown in UI |
| `env` | No | Environment variables to set |

</details>

<details>
<summary><strong>Project Context (.ivorn/context.toml or context-hook)</strong></summary>

Projects can provide custom context injected into every message sent to the ACP agent.

Context is loaded from three sources, merged in order (later overrides earlier):
1. **Global static**`~/.config/ivorn/context.toml` or `context.json`
2. **Project static**`.ivorn/context.toml` or `.ivorn/context.json`
3. **Dynamic hook**`.ivorn/context-hook` script (highest priority)

**Static Context** (`.ivorn/context.toml`):

```toml
team = "platform"
environment = "development"
```

**Dynamic Hook** (`.ivorn/context-hook`):

```bash
#!/bin/sh
echo "git_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo unknown)"
```

All values are prefixed with `project_hook_` in the ivorn-context block.

</details>

<details>
<summary><strong>Project Commands (.ivorn/commands/*.toml)</strong></summary>

Projects can define custom slash commands that execute shell commands or send prompts to the LLM.

**Configuration Locations**

- **Global**: `~/.config/ivorn/commands/*.json` or `*.toml`
- **Project-local**: `{project}/.ivorn/commands/*.json` or `*.toml` (takes precedence)

**Example** (`.ivorn/commands/dev.toml`):

```toml
[[commands]]
name = "test"
description = "Run tests"
command = "cargo test {args}"
output = "display"

[[commands]]
name = "clippy"
description = "Run clippy and fix issues"
command = "cargo clippy {args} 2>&1"
output = "prompt"
prompt = "Fix these clippy warnings:\n\n{}"
input = { hint = "clippy args (e.g., --fix)" }
```

| Field | Required | Description |
|-------|----------|-------------|
| `name` | Yes | Command name (without leading `/`) |
| `description` | Yes | Description shown in command palette |
| `command` | No | Shell command to execute (`{args}` for user arguments) |
| `output` | No | `display` (default), `file`, or `prompt` |
| `prompt` | No | Prompt template (`{}` for output content) |
| `input` | No | Input hint object with `hint` field |

</details>

## Project Structure

```
ivorn/
├── src/
│   ├── main.rs              # Entry point, CLI parsing
│   ├── config.rs            # User configuration loading
│   ├── state.rs             # Application state management
│   ├── acp_agents.rs        # ACP agent discovery
│   ├── acp/                 # ACP protocol implementation
│   │   ├── jsonrpc.rs       # JSON-RPC types and builders
│   │   └── subprocess/      # Agent subprocess management
│   │       ├── lifecycle.rs # Spawn, kill, stdin writer
│   │       ├── protocol.rs  # Initialize, session methods
│   │       ├── reader.rs    # Background stdout reader
│   │       └── ...          # Notifications, permissions, etc.
│   ├── api/                 # HTTP API endpoints
│   │   ├── session/         # Session management
│   │   ├── projects.rs      # Project listing
│   │   ├── upload.rs        # File upload handling
│   │   └── ...
│   ├── history.rs           # Chat history (fjall DB)
│   ├── events.rs            # SSE event types
│   └── ...
├── static/                  # HTML pages (session.html, projects.html)
├── tests/                   # JavaScript tests (Deno)
└── Cargo.toml
```

## Features

- Session naming with click-to-edit in session header
- Light/dark theme toggle with system theme follow
- Running sessions visible on project list with status indicators
- Session termination control (explicit terminate vs. leave running)
- File upload (drag-and-drop, clipboard paste, or button)
- Tool call cards with purpose, parameters, output, and diff display
- Tool call status-based card tinting (blue=in_progress, green=completed, red=failed)
- Tool call selection for follow-up messages (pin button)
- Interactive permission request UI
- Config options UI (model/agent dropdowns with search, keyboard shortcuts)
- Unified prompt menu (type `@` or click button)
- Command palette (type `/` to discover slash commands)
- Shell command execution (`!`-prefixed messages)
- Real-time streaming via Server-Sent Events
- Mobile-friendly responsive design
- Markdown rendering with syntax highlighting
- Clickable items (list items and headings submit as prompts)
- Server-side chat history storage for multi-device sync
- Version mismatch detection with reload banner
- Server-side conversation logging to markdown files
- Ivorn context injection (project metadata in every message)
- Project context hooks (static files and dynamic scripts)

## Data Storage

All application data is stored in the OS data directory:

| Platform | Location |
|----------|----------|
| Linux | `~/.local/share/ivorn/` |
| macOS | `~/Library/Application Support/ivorn/` |
| Windows | `C:\Users\<user>\AppData\Roaming\ivorn\` |

```
~/.local/share/ivorn/
├── state/                              # Global state files
│   ├── sessions.json                   # Active session metadata
│   ├── terminated-sessions.json        # Terminated session records
│   └── project-usage.json              # Project usage timestamps
└── projects/{project}/                 # Per-project data
    ├── conversations/yyyy/mm/dd/       # Conversation logs
    └── uploads/                        # Uploaded files
```

## Development

```bash
# Format code
just fmt

# Run CI checks
just ci

# Run locally
just run /path/to/projects

# Run JavaScript tests (requires Deno)
just test-js
```

## License

[AGPL-3.0-or-later](LICENSE)

Copyright (C) 2026 Paul Campbell