Ivorn
A web-based chat interface for Kiro CLI that enables browser-based interaction with multiple parallel project sessions.
Quick Start
# Build
# Run (provide path to projects directory)
# Or use environment variable
IVORN_PROJECTS_DIR=/path/to/projects
Open http://localhost:8181 in your browser.
Configuration
Config File
Configuration is loaded from ~/.config/ivorn/config.toml (XDG spec).
See config.example.toml for all available settings.
[]
= 8181
= "0.0.0.0"
[]
= "/path/to/projects"
[]
= true # Enable conversation logging to markdown files
[]
= 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
Configuration values are resolved in this order (highest to lowest):
- CLI arguments
- Environment variables
- Config file
- Defaults
ACP Agents
ACP (Agent Client Protocol) agents are external executables that implement the ACP protocol over stdio. Ivorn discovers agents from JSON configuration files.
Configuration Locations
- Global:
~/.config/ivorn/acp/*.json - Project-local:
{project}/.ivorn/acp/*.json(takes precedence over global)
Config File Format
| 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 |
Example: Kiro CLI
Create ~/.config/ivorn/acp/kiro-cli.json:
API Endpoint
GET /api/acp-agents returns the list of discovered agents (global only, project-local requires session context).
Projects Directory Structure
The projects directory should contain project subdirectories directly:
/path/to/projects/
├── project-a/
├── project-b/
└── project-c/
Context Hooks
Projects can provide custom context that is injected into every message sent to the ACP agent. This enables project-specific metadata like CI status, deployment environment, or feature flags.
Setup
Create an executable script at one of these locations (first found wins):
.ivorn/context-hook(executable).ivorn/context-hook.sh
The script runs with the project directory as its working directory.
Output Format
The script should output key=value lines to stdout:
#!/bin/sh
Lines are parsed and prefixed with project_hook_ in the ivorn-context block:
project_hook_ci_status=passing
project_hook_environment=development
project_hook_feature_flags=dark_mode,beta_api
Parsing Rules
- Each line should be
key=valueformat - Lines starting with
#are ignored (comments) - Blank lines are ignored
- Lines without
=are ignored - Whitespace around keys and values is trimmed
Timeout and Errors
- Scripts have a 5-second timeout
- On timeout:
project_hook_error=timeoutis injected - On non-zero exit:
project_hook_error=exit(N)is injected (where N is the exit code) - On IO error:
project_hook_error=io_erroris injected
Example: Git Branch Context
#!/bin/sh
# .ivorn/context-hook
branch=
# Check if there are uncommitted changes
if ; then
else
fi
Features
- Multi-tab interface for parallel project sessions
- Standalone session pages:
/projectsfor ACP-only project list,/session/{id}for single-session view (opens in new browser tab) - Running sessions visible on project list: each project shows its active sessions with status indicators and clickable links to open in new tab
- Rich activity indicators on session tabs: 5 distinct states (idle, streaming, permission_pending, turn_complete, timeout) with visual icons and colors; timeout triggers after 30s of no SSE events (pauses during tool execution); standalone pages update browser title with state prefix (⟳, ⚠, ✓, ⏳)
- Terminated session auto-resume: "Show history" toggle on projects page reveals terminated sessions; clicking resurrects the session by spawning a new ACP subprocess and calling
session/loadto replay kiro-cli's history - Real-time project page updates: project list subscribes to global SSE stream (
/api/stream) for instant session lifecycle events (created, closed, status changes) without page refresh - Session termination control: ⏻ button explicitly terminates session (kills subprocess); closing browser tab leaves session alive for later reconnection;
beforeunloadprompt reminds user session is still running - Per-tab input state (draft messages preserved when switching tabs)
- Project list sorted by most recently used, then alphabetically
- ACP (Agent Client Protocol) sessions using JSON-RPC 2.0 for structured agent communication
- File upload for ACP sessions: drag-and-drop, clipboard paste, or button; stored in OS data directory with text reference
Attached file: ./uploads/{filename} (mime/type, size) - All file types supported; images (detected via magic bytes) sent as base64 Image content blocks + text reference; non-images sent as text reference only
- File attachment display: images render as clickable thumbnails with modal; non-images render as card-style badges with MIME-based icons (📄 PDF, 📦 archives, 📝 text, 📋 JSON, 📎 other)
- MIME type detection using
infercrate (magic bytes) with extension fallback for text files (txt, md, json, xml, csv, log) - Session-type-aware SSE handlers with shared event infrastructure
- ACP spec-aligned tool call visibility (kind, status, title, raw I/O)
- Tool call cards with purpose display, collapsible Parameters and Output sections, and status badges
- Tool call status-based card tinting: subtle background and border color changes for at-a-glance state visibility (blue=in_progress, green=completed, red=failed, amber=rejected)
- Tool call Parameters and Output sections: both shown when expanded, each independently collapsible with state persisted across page reloads
- Tool call output formatting: structured key-value tree rendering (matching Parameters style); MCP response wrappers (
items[*].Jsonanditems[*].Textvariants) automatically unwrapped; direct JSON objects rendered as tree without unwrapping - Tool call diff display: file changes shown in separate collapsible "Diff" section (outside Output section) with colored inline diff (red/green); edit tools with diff hide the Output section when rawOutput is empty
- Tool call kind-based default expansion: impactful tools (edit, delete, move, execute) expanded by default; read-only tools collapsed
- Tool call expansion state persisted across SSE updates and page reloads via toolCallsById
- Tool call content display: text (markdown), diff, terminal, and unknown content types rendered in expandable cards
- Tool call error display: failed tool calls show error details in collapsible section with red styling; auto-expands when tool fails
- Interspersed tool cards: tool calls appear inline at their first occurrence position, not grouped at bottom
- Turn completion dividers with elapsed time and timestamps
- Unified inline activity indicator: single indicator appears after the last user message; shows "Waiting for response..." with spinner + timer + cancel during waiting state, transitions to bouncing dots during typing state; timer and cancel button visible in both states
- Bidirectional agent communication (incoming requests, interactive permission UI)
- Interactive permission request UI: inline menu above input area, "View" link to highlight tool card, user approves/rejects tool calls
- Agent info and capability detection (name, version, image prompts, session restore, MCP transports)
- ACP authentication support (auth method parsing and automatic authenticate flow)
- ACP agent selection: projects page shows collapsible sections with agent cards; click agent to start session; auto-expand projects with active sessions; localStorage persistence for expand/collapse state
- Session mode and config option parsing from session/new response
- Session config option setting via ACP session/set_config_option (generic key-value)
- Config change system messages: full-width banner in chat history when config options change (e.g., agent, model), showing old→new values; distinguishes user-initiated vs agent-initiated changes
- Config snapshot in message footer: assistant messages display current config values (agent, model) left of timestamp for context
- Config options UI for ACP sessions (model/agent dropdowns with search, keyboard shortcuts: Ctrl+M model, Ctrl+A agent)
- Unified prompt menu: type
@at start of message OR click the@button to search and select prompts (arrow keys, Enter/Tab to select, Esc to dismiss), sorted alphabetically by name; ACP sessions receive prompts via SSE with full content for local/global prompts, auto-refreshed on agent change - Session load/resume via fjall history database (single source of truth for chat history)
- Session resume auto-message: on page reload, automatically sends a message to the agent with time since last activity, asking it to summarize where things left off and suggest next steps
- Config options restored on page reload via
/messages/latestAPI (ACP sessions only) - Real-time streaming via Server-Sent Events (SSE) including stop reason events
- Agent execution plan display (ACP plan events)
- Graceful handling of unknown/future ACP session update types
- Mobile-friendly responsive design with consolidated gear menu (⚙️) for config options, history, prompts, and file upload
- Text selection popup: desktop centers on mouse position, mobile centers on viewport with smart vertical placement
- Markdown rendering with syntax highlighting for both user and agent messages; diff code blocks display with red/green line coloring
- Clickable lines in ACP sessions: list items and headings submit as prompts on click (cursor pointer, green hover border)
- Session persistence across page reloads
- Session isolation: every message stamped with
_sid(session ID), validated on load/tab-switch/periodic, misplaced messages auto-corrected - Server-side chat history storage (fjall embedded DB) for multi-device sync and server restart resilience
- Build info system: compile-time capture of version, git hash, jj change id, build timestamp via
build.rs - About modal: hamburger menu → "About" displays version, git hash, jj change id, build time
- Version mismatch detection: polls
/api/healthevery 5 min + checks before each message send; non-blocking gold banner with reload button when server version changes - Graceful model unavailability handling (ACP): inline error banner, pulsing model selector highlight, automatic retry on model change
- Session error surfacing (ACP): fatal errors (subprocess death) show red banner with "New Session" button; recoverable errors (JSON-RPC failures) show yellow banner with "Retry" button
- Scroll-to-bottom button: circular floating button (bottom-right) appears when scrolled up, with unread message count badge
- Loading progress indicator: thin indeterminate progress bar at top of input area during history sync, terminated session load, and SSE connection setup
- Prompt navigation buttons: ↑/↓ buttons on user messages navigate between user messages; ↑/↓ buttons on write tool calls (edit/delete/move) navigate between write tools
- Keyboard shortcuts for navigation:
J(next user message) andK(previous user message),j(next write tool) andk(previous write tool) when input not focused - Frontend performance optimizations for long conversations: CSS
content-visibility: autofor virtual scrolling (95% DOM reduction at 1000 messages), rendered HTML caching on message objects, debounced scroll and copy button operations via requestAnimationFrame - Server-side conversation logging (ACP only): real-time markdown logs saved to OS data directory for conversation recovery after server issues
- Shell command execution (ACP only):
!-prefixed messages execute as shell commands in the project directory; output streamed with ANSI color rendering (standard colors, bright colors, bold, 256-color palette) in dark terminal-style container with copy button; stop button kills process group - Ivorn context injection (ACP only): every user message is prepended with
<ivorn-context>XML block containing project name, working directory, session ID, turn count, last activity timestamp, OS/hostname/timezone, VCS type/root/branch, project type, conversation log paths, uploads directory, recent uploads, project file count, previous sessions, and server version - Project context hooks (ACP only): projects can provide
.ivorn/context-hookor.ivorn/context-hook.shscripts that inject custom metadata into the ivorn-context block; script stdout is parsed askey=valuelines and prefixed withproject_hook_; 5-second timeout with error field injection on failure
Data Storage
All application data is stored in the OS data directory, keeping project directories clean:
| Platform | Location |
|---|---|
| Linux | ~/.local/share/ivorn/ |
| macOS | ~/Library/Application Support/ivorn/ |
| Windows | C:\Users\<user>\AppData\Roaming\ivorn\ |
Directory Layout
~/.local/share/ivorn/
├── state/ # Global state files
│ ├── sessions.json # Active session metadata
│ ├── terminated-sessions.json # Terminated session records
│ ├── project-usage.json # Project usage timestamps
│ └── .ivorn-sessions.lock # File lock for atomic updates
└── projects/{project}/ # Per-project data
├── conversations/yyyy/mm/dd/ # Conversation logs
│ ├── {session_id}.md
│ └── {session_id}/ # Tool output attachments
└── uploads/ # Uploaded files
Conversation Log Files
ACP sessions automatically log conversations to markdown files for recovery after server issues.
Logs include:
- User messages (as blockquotes)
- Assistant messages (plain text)
- Tool calls with purpose and parameters
- Tool status updates (✅ completed, ❌ failed, ⚠️ rejected)
- Tool results: short results (<21 lines) inlined, long results saved to
{session_id}/{tool_call_id}.txt - Turn separators
Logs are created lazily on first user message and can be used to resume conversations in new sessions by referencing the file.
Privacy Note: Conversation logs contain full message content. Set conversation_log = false in the [logging] config section to disable.
Session State Files
sessions.json— Active session IDs, projects, types, and clean termination statusterminated-sessions.json— Terminated session metadata for auto-resumeproject-usage.json— Last-used timestamps for project list sorting
Migration
On first run, files are automatically migrated from old locations:
| Old Location | New Location |
|---|---|
{projects_dir}/ivorn-sessions.json |
state/sessions.json |
{projects_dir}/ivorn-terminated-sessions.json |
state/terminated-sessions.json |
{projects_dir}/ivorn-project-usage.json |
state/project-usage.json |
{projects_dir}/{project}/uploads/ |
projects/{project}/uploads/ |
conversations/{project}/ |
projects/{project}/conversations/ |
Migration uses rename() for efficiency. If migration fails (e.g., cross-filesystem), a warning is logged with the manual mv command to run. The application continues working from the old location until manually migrated.
Development
# Format code
# Run CI checks
# Run locally
JavaScript Tests
Frontend unit tests use Deno (no Node.js required):
# Run JS tests locally
# Or directly with Deno
Requirements: Deno must be installed locally (mise install deno or see deno.land).
See design-docs/initial-design.md for architecture details.
License
Copyright (C) 2026 Paul Campbell
This program is free software: you can redistribute it and/or modify it under the terms of the GNU Affero General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License along with this program. If not, see https://www.gnu.org/licenses/.