Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5c30f95
refactor(ui): replace per-agent class bindings with agentColor() utility
sinnet3000 Feb 28, 2026
273cb99
test(ui): cover agent color rendering in breadcrumb and palette
sinnet3000 Feb 28, 2026
6934abe
feat(parser): add Amp session parser
sinnet3000 Feb 28, 2026
15cd2ba
feat(amp): wire Amp into sync engine, discovery, config, and frontend
sinnet3000 Feb 28, 2026
d90f733
fix(parser): handle Amp tool names and input field schemas
sinnet3000 Feb 28, 2026
5c7391f
fix(amp): classify thread paths and harden skill extraction
sinnet3000 Feb 28, 2026
e57e57b
test(parser): add focused coverage for amp skill name extraction
sinnet3000 Feb 28, 2026
6541898
Fix Amp incremental SyncPaths classification and coverage
sinnet3000 Feb 28, 2026
379126d
Tighten amp path validation and expand regression tests
sinnet3000 Feb 28, 2026
28f545a
test(frontend): align setProjectFilter expectations with preserved ag…
sinnet3000 Feb 28, 2026
bafd66d
fix(amp): scan traces backward for most recent valid endTime
sinnet3000 Feb 28, 2026
d70f836
docs: add Amp to README and CLAUDE.md
sinnet3000 Feb 28, 2026
29659a2
fix(amp): align edit_file display with Gemini convention
sinnet3000 Feb 28, 2026
7fc2064
refactor: centralize agent definitions in parser.Registry
wesm Feb 28, 2026
061d7b0
fix: address review findings for registry refactor
wesm Feb 28, 2026
d77f890
fix: defensive copy of AgentDirs, log malformed config values
wesm Feb 28, 2026
e23450f
test: cover defensive AgentDirs copy and malformed config warning
wesm Feb 28, 2026
f0b0a28
test: improve logger restore and add slice-aliasing coverage
wesm Feb 28, 2026
5e209d0
fix: use continue instead of early return in path classification
wesm Feb 28, 2026
569811f
test: add Claude-subagent fall-through overlap test
wesm Feb 28, 2026
4737a42
fix(amp): validate thread ID at parse time, fall back to filename
wesm Feb 28, 2026
ac4b881
fix(amp): prefer filename-derived ID over JSON id
wesm Feb 28, 2026
e7f9d9c
test(amp): cover JSON id fallback when filename is invalid
wesm Feb 28, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,14 @@

## Project Overview

agentsview is a local web viewer for AI agent sessions (Claude Code, Codex, Copilot CLI, Gemini CLI, OpenCode). It syncs session data from disk into SQLite (with FTS5 full-text search), serves a Svelte 5 SPA via an embedded Go HTTP server, and provides real-time updates via SSE.
agentsview is a local web viewer for AI agent sessions (Claude Code, Codex, Copilot CLI, Gemini CLI, OpenCode, Amp). It syncs session data from disk into SQLite (with FTS5 full-text search), serves a Svelte 5 SPA via an embedded Go HTTP server, and provides real-time updates via SSE.

## Architecture

```
CLI (agentsview) → Config → DB (SQLite/FTS5)
File Watcher → Sync Engine → Parser (Claude, Codex, Copilot, Gemini, OpenCode)
File Watcher → Sync Engine → Parser (Claude, Codex, Copilot, Gemini, OpenCode, Amp)
HTTP Server → REST API + SSE + Embedded SPA
```
Expand All @@ -18,15 +18,15 @@ CLI (agentsview) → Config → DB (SQLite/FTS5)
- **Storage**: SQLite with WAL mode, FTS5 for full-text search
- **Sync**: File watcher + periodic sync (15min) for session directories
- **Frontend**: Svelte 5 SPA embedded in the Go binary at build time
- **Config**: Env vars (`AGENT_VIEWER_DATA_DIR`, `CLAUDE_PROJECTS_DIR`, `CODEX_SESSIONS_DIR`, `COPILOT_DIR`, `GEMINI_DIR`, `OPENCODE_DIR`) and CLI flags
- **Config**: Env vars (`AGENT_VIEWER_DATA_DIR`, `CLAUDE_PROJECTS_DIR`, `CODEX_SESSIONS_DIR`, `COPILOT_DIR`, `GEMINI_DIR`, `OPENCODE_DIR`, `AMP_DIR`) and CLI flags

## Project Structure

- `cmd/agentsview/` - Go server entrypoint
- `cmd/testfixture/` - Test data generator for E2E tests
- `internal/config/` - Config loading, flag registration, legacy migration
- `internal/db/` - SQLite operations (sessions, messages, search, analytics)
- `internal/parser/` - Session file parsers (Claude, Codex, content extraction)
- `internal/parser/` - Session file parsers (Claude, Codex, Copilot, Gemini, OpenCode, Amp, content extraction)
- `internal/server/` - HTTP handlers, SSE, middleware, search, export
- `internal/sync/` - Sync engine, file watcher, discovery, hashing
- `internal/timeutil/` - Time parsing utilities
Expand All @@ -50,6 +50,7 @@ CLI (agentsview) → Config → DB (SQLite/FTS5)
| `internal/parser/claude.go` | Claude Code session parser |
| `internal/parser/codex.go` | Codex session parser |
| `internal/parser/copilot.go` | Copilot CLI session parser |
| `internal/parser/amp.go` | Amp session parser |
| `internal/config/config.go` | Config loading, flag registration |

## Development
Expand Down
11 changes: 6 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A local web application for browsing, searching, and analyzing
AI agent coding sessions. Supports Claude Code, Codex,
Copilot CLI, Gemini CLI, and OpenCode. A next-generation rewrite of
Copilot CLI, Gemini CLI, OpenCode, and Amp. A next-generation rewrite of
[agent-session-viewer](https://github.com/wesm/agent-session-viewer)
in Go.

Expand Down Expand Up @@ -47,7 +47,7 @@ patterns over time.
- **Full-text search** across all message content, instantly
- **Analytics dashboard** with activity heatmaps, tool usage,
velocity metrics, and project breakdowns
- **Multi-agent support** for Claude Code, Codex, Copilot CLI, Gemini CLI, and OpenCode
- **Multi-agent support** for Claude Code, Codex, Copilot CLI, Gemini CLI, OpenCode, and Amp
- **Live updates** via SSE as active sessions receive new messages
- **Keyboard-first** navigation (vim-style `j`/`k`/`[`/`]`)
- **Export and publish** sessions as HTML or to GitHub Gist
Expand All @@ -63,7 +63,7 @@ agentsview -no-browser # headless mode
```

On startup, agentsview discovers sessions from Claude Code, Codex,
Copilot CLI, Gemini CLI, and OpenCode, syncs them into a local SQLite database
Copilot CLI, Gemini CLI, OpenCode, and Amp, syncs them into a local SQLite database
with FTS5 full-text search, and opens a web UI at
`http://127.0.0.1:8080`.

Expand Down Expand Up @@ -123,7 +123,7 @@ make e2e # Playwright E2E tests
cmd/agentsview/ CLI entrypoint
internal/config/ Configuration loading
internal/db/ SQLite operations (sessions, search, analytics)
internal/parser/ Session parsers (Claude, Codex, Copilot, Gemini, OpenCode)
internal/parser/ Session parsers (Claude, Codex, Copilot, Gemini, OpenCode, Amp)
internal/server/ HTTP handlers, SSE, middleware
internal/sync/ Sync engine, file watcher, discovery
frontend/ Svelte 5 SPA (Vite, TypeScript)
Expand All @@ -138,9 +138,10 @@ frontend/ Svelte 5 SPA (Vite, TypeScript)
| Copilot CLI | `~/.copilot/session-state/` |
| Gemini CLI | `~/.gemini/` |
| OpenCode | `~/.local/share/opencode/` |
| Amp | `~/.local/share/amp/threads/` |

Override with `CLAUDE_PROJECTS_DIR`, `CODEX_SESSIONS_DIR`,
`COPILOT_DIR`, `GEMINI_DIR`, or `OPENCODE_DIR` environment variables.
`COPILOT_DIR`, `GEMINI_DIR`, `OPENCODE_DIR`, or `AMP_DIR` environment variables.

## Acknowledgements

Expand Down
75 changes: 30 additions & 45 deletions cmd/agentsview/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import (

"github.com/wesm/agentsview/internal/config"
"github.com/wesm/agentsview/internal/db"
"github.com/wesm/agentsview/internal/parser"
"github.com/wesm/agentsview/internal/server"
"github.com/wesm/agentsview/internal/sync"
)
Expand Down Expand Up @@ -62,9 +63,9 @@ func main() {
func printUsage() {
fmt.Printf(`agentsview %s - local web viewer for AI agent sessions

Syncs Claude Code, Codex, Copilot CLI, Gemini CLI, OpenCode, and Cursor
session data into SQLite, serves an analytics dashboard and session
browser via a local web UI.
Syncs Claude Code, Codex, Copilot CLI, Gemini CLI, OpenCode, Cursor,
and Amp session data into SQLite, serves an analytics dashboard and
session browser via a local web UI.

Usage:
agentsview [flags] Start the server (default command)
Expand Down Expand Up @@ -99,6 +100,7 @@ Environment variables:
GEMINI_DIR Gemini CLI directory
OPENCODE_DIR OpenCode data directory
CURSOR_PROJECTS_DIR Cursor projects directory
AMP_DIR Amp threads directory
AGENT_VIEWER_DATA_DIR Data directory (database, config)

Multiple directories:
Expand Down Expand Up @@ -144,25 +146,20 @@ func runServe(args []string) {
database := mustOpenDB(cfg)
defer database.Close()

warnMissingDirs(cfg.ResolveClaudeDirs(), "claude")
warnMissingDirs(cfg.ResolveCodexDirs(), "codex")
warnMissingDirs(cfg.ResolveCopilotDirs(), "copilot")
warnMissingDirs(cfg.ResolveGeminiDirs(), "gemini")
warnMissingDirs(cfg.ResolveOpenCodeDirs(), "opencode")
for _, def := range parser.Registry {
warnMissingDirs(
cfg.ResolveDirs(def.Type),
string(def.Type),
)
}

// Remove stale temp DB from a prior crashed resync.
cleanResyncTemp(cfg.DBPath)

engine := sync.NewEngine(
database,
cfg.ResolveClaudeDirs(),
cfg.ResolveCodexDirs(),
cfg.ResolveCopilotDirs(),
cfg.ResolveGeminiDirs(),
cfg.ResolveOpenCodeDirs(),
cfg.CursorProjectsDir,
"local",
)
engine := sync.NewEngine(database, sync.EngineConfig{
AgentDirs: cfg.AgentDirs,
Machine: "local",
})

runInitialSync(engine)

Expand Down Expand Up @@ -344,34 +341,22 @@ func startFileWatcher(
}

var roots []watchRoot
for _, d := range cfg.ResolveClaudeDirs() {
if _, err := os.Stat(d); err == nil {
roots = append(roots, watchRoot{d, d})
}
}
for _, d := range cfg.ResolveCodexDirs() {
if _, err := os.Stat(d); err == nil {
roots = append(roots, watchRoot{d, d})
}
}
for _, d := range cfg.ResolveCopilotDirs() {
copilotState := filepath.Join(d, "session-state")
if _, err := os.Stat(copilotState); err == nil {
roots = append(roots, watchRoot{d, copilotState})
}
}
for _, d := range cfg.ResolveGeminiDirs() {
geminiTmp := filepath.Join(d, "tmp")
if _, err := os.Stat(geminiTmp); err == nil {
roots = append(roots, watchRoot{d, geminiTmp})
for _, def := range parser.Registry {
if !def.FileBased {
continue
}
}
if cfg.CursorProjectsDir != "" {
if _, err := os.Stat(cfg.CursorProjectsDir); err == nil {
roots = append(roots, watchRoot{
cfg.CursorProjectsDir,
cfg.CursorProjectsDir,
})
for _, d := range cfg.ResolveDirs(def.Type) {
watchDir := d
if def.WatchSubdir != "" {
watchDir = filepath.Join(
d, def.WatchSubdir,
)
}
if _, err := os.Stat(watchDir); err == nil {
roots = append(
roots, watchRoot{d, watchDir},
)
}
}
}

Expand Down
Loading