Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
62 changes: 60 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,11 @@ Browse and render Codex session `.jsonl` files with a web UI, and optionally sha

## Features
- Indexes `~/.codex/sessions/{year}/{month}/{day}` and lists sessions by date.
- Adds `/active` for day-by-day active threads across all directories, with manual end/reopen controls.
- Accepts Codex CLI notification webhooks on `/hook` and lets you inspect them on `/notifications`.
- Renders conversations as HTML with markdown support and dark theme.
- Shows only user/agent messages and reasoning; tool calls and other events are omitted.
- Consecutive messages are merged; for user groups, only the last message is kept.
- Shows user/agent messages, non-empty reasoning summaries, and response items such as tool calls, tool outputs, web searches, custom tool activity, and ghost snapshots.
- Consecutive messages and reasoning items are merged; visible tool/system items stay separate. For user groups, only the last message is kept.
- User messages can be trimmed to content after `## My request for Codex:` (default on).
- Share button saves a hard‑to‑guess HTML file to `~/.codex/shares` and copies its URL.
- Separate share server serves only exact filenames (no directory listing).
Expand Down Expand Up @@ -57,6 +59,11 @@ Visit:
- UI: http://localhost:8080/
- Share server: http://localhost:8081/

Key pages:
- `/` date/directory browser
- `/active` active thread list (default: today in your browser time zone)
- `/notifications` received webhook notifications

## Autostart (systemd --user)
If your environment supports user services (WSL with systemd, Linux desktops), you can keep it running.

Expand Down Expand Up @@ -107,6 +114,27 @@ systemctl --user restart codex-manager
- `-full` disable trimming to `## My request for Codex:`
- `-h` / `--help`

## Repository overrides
- Default path: `~/.codex/session_repository_overrides.json`
- Purpose: override the GitHub repository used for branch links for specific `cwd` prefixes
- Create this file only when you need overrides; if the file is missing, Codex Manager falls back to `session_meta.git.repository_url`
- Matching: longest `cwd_prefix` match wins
- Changes are loaded at startup, so restart Codex Manager after editing the file
- Format:
```json
{
"version": 1,
"rules": [
{
"cwd_prefix": "/home/makoto/codex-manager",
"repository_url": "https://github.com/makoto-soracom/codex-manager.git"
}
]
}
```
- Resolution order: repository override, then `session_meta.git.repository_url`
- Scope: this only changes the repository used for branch links; branch names and resume commands still come from session metadata

## HTMLBucket notes
- Auth file path: `~/.hb/auth.json`
- Auth file format:
Expand Down Expand Up @@ -140,6 +168,36 @@ Clicking “Share”:
- Copies the share URL to your clipboard
- Displays a banner showing the copied URL

## Active thread state
- New top-level threads are treated as active by default.
- `/active` uses your browser time zone (stored in a cookie) to decide which day a thread belongs to.
- A thread is shown as:
- `Waiting for user`
- `Waiting for agent`
- `Ended`
- `Ended` is a manual flag stored in `~/.codex/session_state.json`.
- If a thread receives new JSONL activity after being marked ended, it automatically returns to active.
- `/active` refreshes while the page is open without changing the global `--rescan-interval`.

## Codex CLI notifications
You can point Codex CLI `notify` to the local server:

```toml
notify = [
"curl",
"-sS",
"-X", "POST",
"http://localhost:8080/hook",
"-H", "Content-Type: application/json",
"--data-binary"
]
```

- Incoming requests are accepted on `/hook`.
- Received notifications are appended to `~/.codex/notifications.jsonl`.
- `/notifications` shows the newest notifications first, with headers and body.
- The page auto-refreshes while open so you can inspect payloads as they arrive.

## Development
```bash
go test ./...
Expand Down
38 changes: 38 additions & 0 deletions cmd/codex-manager/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,12 @@ import (
"strings"
"time"

"codex-manager/internal/active"
"codex-manager/internal/config"
"codex-manager/internal/htmlbucket"
"codex-manager/internal/notifications"
"codex-manager/internal/render"
"codex-manager/internal/repooverride"
"codex-manager/internal/search"
"codex-manager/internal/sessions"
"codex-manager/internal/web"
Expand All @@ -42,6 +45,32 @@ func main() {
log.Printf("initial scan failed: %v", err)
}

activeIdx := active.NewIndex()
if err := activeIdx.RefreshFrom(idx); err != nil {
log.Printf("initial active index build failed: %v", err)
}

activeState, err := active.LoadStateStore(active.DefaultStatePath(cfg.SessionsDir))
if err != nil {
log.Printf("active state load failed: %v", err)
activeState, _ = active.LoadStateStore("")
}
if err := activeState.Reconcile(activeIdx.Summaries()); err != nil {
log.Printf("initial active state reconcile failed: %v", err)
}

notificationStore, err := notifications.LoadStore(notifications.DefaultPath(cfg.SessionsDir))
if err != nil {
log.Printf("notification store load failed: %v", err)
notificationStore, _ = notifications.LoadStore("")
}

repositoryOverrideStore, err := repooverride.LoadStore(repooverride.DefaultPath(cfg.SessionsDir))
if err != nil {
log.Printf("repository override load failed: %v", err)
repositoryOverrideStore, _ = repooverride.LoadStore("")
}

searchIdx := search.NewIndex()
if err := searchIdx.RefreshFrom(idx); err != nil {
log.Printf("initial search index build failed: %v", err)
Expand All @@ -55,6 +84,12 @@ func main() {
log.Printf("rescan failed: %v", err)
continue
}
if err := activeIdx.RefreshFrom(idx); err != nil {
log.Printf("active reindex failed: %v", err)
}
if err := activeState.Reconcile(activeIdx.Summaries()); err != nil {
log.Printf("active state reconcile failed: %v", err)
}
if err := searchIdx.RefreshFrom(idx); err != nil {
log.Printf("search reindex failed: %v", err)
}
Expand All @@ -67,6 +102,9 @@ func main() {
}

server := web.NewServer(idx, searchIdx, renderer, cfg.SessionsDir, cfg.ShareDir, cfg.ShareAddr, cfg.Theme)
server.EnableActive(activeIdx, activeState, 15*time.Second)
server.EnableNotifications(notificationStore)
server.EnableRepoOverrides(repositoryOverrideStore)
if htmlBucketClient != nil {
server.EnableHTMLBucket(htmlBucketClient)
log.Printf("Using htmlbucket share backend (%s)", htmlBucketAuthPath)
Expand Down
Loading