- Watches
~/.codexfor changes tosessions/*.jsonl. - Optionally watches
~/.claude/projectsfor Claude Code conversation history (per-project JSONL files). - Parses JSONL chat events and exposes a local HTTP API + a minimal UI.
- Default port:
localhost:7077(configurable viaPORT). - Default Codex dir:
$HOME/.codex(override withCODEX_DIR). - Default Claude dir:
$HOME/.claude/projects(override withCLAUDE_DIR).
- This version uses polling to keep the session catalog current.
/api/searchuses a persistent Meilisearch index and does not fall back to the old in-memory search.- Unknown/extra JSON fields are preserved in a
rawblob for later analysis.
Prerequisites
- Go 1.21+
- Meilisearch for search:
curl -L https://install.meilisearch.com | sh
From source (current repo)
# build to ./bin/
make build
# or
go build -o bin/codex-watcher ./cmd/codex-watcher
# cross-compile examples
GOOS=darwin GOARCH=arm64 go build -o bin/codex-watcher-darwin-arm64 ./cmd/codex-watcher
GOOS=linux GOARCH=amd64 go build -o bin/codex-watcher-linux-amd64 ./cmd/codex-watcherOption A — script (recommended)
bash scripts/install-macos.sh
# Installs to /opt/codex-watcher and places a wrapper in /opt/homebrew/bin (or /usr/local/bin)Option B — manual
sudo mkdir -p /opt/codex-watcher
go build -o bin/codex-watcher ./cmd/codex-watcher
sudo cp bin/codex-watcher /opt/codex-watcher/
sudo rsync -a static/ /opt/codex-watcher/static/
# Add a small wrapper so WorkingDirectory is correct (static/ must be found)
sudo tee /opt/homebrew/bin/codex-watcher >/dev/null <<'SH'
#!/usr/bin/env sh
set -e
cd /opt/codex-watcher && exec ./codex-watcher "$@"
SH
sudo chmod +x /opt/homebrew/bin/codex-watcherbash scripts/uninstall-macos.shRun
go run ./cmd/codex-watcher
# or
PORT=8081 CODEX_DIR=/path/to/.codex go run ./cmd/codex-watcher
# Bind host (default 127.0.0.1)
HOST=0.0.0.0 go run ./cmd/codex-watcher --host 0.0.0.0
# Search backend
go run ./cmd/codex-watcher --meili_url http://127.0.0.1:7700 --meili_auto_start=true codex-watcher [flags]
codex-watcher serve [flags] # same as default
codex-watcher browse [flags] # ensure running, then open browser
codex-watcher start|stop|restart [flags]
Flags (with env var equivalents)
--host <host> Bind address (default 0.0.0.0)
env: HOST
--port <port> HTTP port (default 7077)
env: PORT
--codex <dir> Path to ~/.codex (default $HOME/.codex)
env: CODEX_DIR
--claude <dir> Path to ~/.claude/projects (default $HOME/.claude/projects)
env: CLAUDE_DIR
--meili_url <url> Meilisearch URL (default http://127.0.0.1:7700)
env: MEILI_URL
--meili_key <key> Meilisearch key if configured
env: MEILI_MASTER_KEY or MEILI_KEY
--meili_bin <path> Meilisearch binary for auto-start (default meilisearch)
env: MEILI_BIN
--meili_data_dir <dir> Meilisearch data directory (default $HOME/.codex/codex-watcher/meili_data)
env: MEILI_DATA_DIR
--meili_auto_start=<bool> Auto-start local Meilisearch when unavailable (default true)
env: MEILI_AUTO_START
--search_dictionary <csv> Extra Meilisearch dictionary entries
env: SEARCH_DICTIONARY
--search_synonyms <spec> Synonyms like py=python,python3;rs=rust
env: SEARCH_SYNONYMS
Examples
# foreground
codex-watcher --host 0.0.0.0 --port 7077 --codex "$HOME/.codex"
# with Claude support
codex-watcher --host 0.0.0.0 --port 7077 --codex "$HOME/.codex" --claude "$HOME/.claude/projects"
# background service (simple)
codex-watcher start --host 0.0.0.0 --port 7077 --codex "$HOME/.codex"
codex-watcher browse # open UI
codex-watcher stop
codex-watcher status
Notes
- The binary serves static files from a local `static/` folder; run from the repo root or keep `static/` adjacent to the binary when deploying (e.g., `/opt/codex-watcher/{codex-watcher,static/}`).
- The default host is `0.0.0.0` (listens on all interfaces). If you prefer local-only, run with `--host 127.0.0.1`.
- On startup, codex-watcher starts or connects to Meilisearch, applies search settings, imports changed JSONL records, and skips files already recorded in the Meilisearch import-state index.
- If Meilisearch cannot start or be queried, the web UI remains available but `/api/search` returns `503 Service Unavailable`.
GET /api/sessions— list discovered sessions with basic stats.- Supports
?source=codex|claudeand?project=<name>filters.
- Supports
GET /api/messages?session_id=...— messages for a session (latest 200 by default).GET /api/stats— aggregate counters (messages, sessions, roles, models if present).GET /api/search?q=...— search via Meilisearch only. Regex syntax is rejected with400 Bad Request.POST /api/reindex— trigger a lightweight catalog rescan for sessions/messages.
Export parameters (selected)
GET /api/export/session?session_id=...&format=jsonl|json|md|txt&exclude_shell=0|1&exclude_tool_outputs=0|1GET /api/export/by_dir?cwd=...&after=RFC3339&before=RFC3339&exclude_shell=0|1&exclude_tool_outputs=0|1(markdown)- Defaults: exclude_shell=1, exclude_tool_outputs=1
GET /— Minimal HTMX-based view listing sessions and messages.- Designed to work without Node tooling or bundlers.
Data Model (flexible)
- Session (id, title, first_at, last_at, message_count, models, tags)
- Message (id, session_id, ts, role, content, model, type, tool_name, raw)
- The parser attempts to map common fields; anything else is kept in
raw.
Assumptions About ~/.codex Structure
~/.codex/sessions/*.jsonl— per-session logs. Each line is a JSON object.- If the real schema differs, the parser is resilient and stores the full
rawJSON.
Next Steps
- If you can share 10–20 sample lines from both
history.jsonland asessions/*.jsonl, I can refine field mapping (e.g., tokens, cost, tools, errors, attachments, model, etc.).
