zozul-cli 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,182 +1,131 @@
1
- # zozul-cli
1
+ # zozul
2
2
 
3
- Observability for [Claude Code](https://code.claude.com/) track token usage, costs, turns, and full conversation history. No external services, no Docker, no cloud.
3
+ Observability for [Claude Code](https://code.claude.com/). Track costs, sessions, and conversations across projects.
4
4
 
5
- ## What it does
6
-
7
- zozul is a single local process that captures everything Claude Code does. Data flows in from three complementary sources and lands in a SQLite database at `~/.zozul/zozul.db`. A built-in web dashboard and JSON API sit on top of that.
8
-
9
- | Source | What it provides |
10
- |---|---|
11
- | **OTEL receiver** | Token counts, cost (USD), active time, API events, user prompts — streamed from Claude Code every ~60s |
12
- | **Hooks** | Real-time session lifecycle, tool calls, user prompts — fired synchronously as events happen |
13
- | **JSONL watcher** | Full turn content, assistant responses, per-turn token detail — read directly from Claude Code's transcript files |
14
-
15
- Each source has different strengths. OTEL is the authoritative source for **cost and duration**. JSONL is the only source for **full conversation text**. Hooks provide **real-time signals** and trigger transcript ingestion on session end. The `sessions` table is kept in sync from all three.
5
+ Works locally out of the box. Optionally syncs to a remote backend for persistent storage and team visibility.
16
6
 
17
7
  ## Quick start
18
8
 
19
9
  ```bash
20
- # Install globally from npm
21
10
  npm install -g zozul-cli
22
11
 
23
- # Configure Claude Code and install as a background service (recommended)
12
+ # Configure Claude Code and run as a background service
24
13
  zozul install --service
25
14
 
26
15
  # Open the dashboard
27
16
  open http://localhost:7890/dashboard
28
-
29
- # Use Claude Code normally — data appears automatically
30
- claude
31
17
  ```
32
18
 
33
- Or if you'd rather manage the process yourself:
19
+ That's it. Use Claude Code normally data appears automatically.
34
20
 
35
- ```bash
36
- zozul install # Configure Claude Code hooks + OTEL
37
- zozul serve # Start the server
38
- open http://localhost:7890/dashboard
39
- ```
21
+ ## What you get
40
22
 
41
- ## Dashboard
23
+ A single local process that captures everything Claude Code does via three sources:
42
24
 
43
- `http://localhost:7890/dashboard`
25
+ - **OTEL** — cost, tokens, active time. Streamed every ~60s. The authoritative source for spend.
26
+ - **Hooks** — session lifecycle, tool calls, user prompts. Real-time.
27
+ - **JSONL** — full conversation text, assistant responses. Read from Claude Code's transcript files.
44
28
 
45
- - **Stats bar** sessions, user prompts, interruptions, tokens, and cost at a glance
46
- - **Token usage chart** — daily input/output/cache token trends with time range controls
47
- - **Cost chart** — daily spend
48
- - **Tool usage** — which tools Claude uses most
49
- - **Model breakdown** — cost and tokens per model
50
- - **Sessions table** — paginated (Load More), filterable; click any session for the full conversation with per-turn token counts, expandable tool call inputs/outputs
51
- - **Auto-refresh** — dashboard polls every 10s automatically; click the Auto button to refresh immediately
29
+ All data lands in SQLite at `~/.zozul/zozul.db`. A web dashboard and JSON API sit on top.
52
30
 
53
- ## Commands
31
+ ## Dashboard
54
32
 
55
- | Command | Description |
56
- |---|---|
57
- | `zozul serve` | Start the server (dashboard, hooks, OTEL receiver, API) on port 7890 |
58
- | `zozul install` | Configure Claude Code hooks and OTEL in `~/.claude/settings.json` |
59
- | `zozul install --service` | Configure Claude Code **and** install zozul as a login service (auto-starts) |
60
- | `zozul install --status` | Show whether the background service is installed and running |
61
- | `zozul install --restart` | Restart the background service (picks up new builds) |
62
- | `zozul install --dry-run` | Preview the config that would be installed |
63
- | `zozul uninstall` | Remove zozul hooks, OTEL config, git hook, and background service |
64
- | `zozul context <tags...>` | Set active task tags for tagging turns (e.g. `zozul context "UI" "Feature"`) |
65
- | `zozul context --list` | List all tasks that have been used |
66
- | `zozul context --clear` | Clear the active task context |
67
- | `zozul sync` | Sync local data to the remote zozul backend |
68
- | `zozul sync --dry-run` | Show what would be synced without sending data |
69
-
70
- ## Architecture
33
+ `http://localhost:7890/dashboard`
71
34
 
72
- ```
73
- Claude Code
74
- |
75
- +-------------+-------------+
76
- | | |
77
- OTEL export Hook POSTs ~/.claude/projects/
78
- (every ~60s) (real-time) <project>/<uuid>.jsonl
79
- | | |
80
- v v v
81
- /v1/metrics /hook/* fs.watch (live)
82
- /v1/logs | zozul ingest (manual)
83
- | | |
84
- | updateSessionFromOtel |
85
- | | persistSession
86
- +------+------+------+------+
87
- |
88
- SQLite (WAL)
89
- ~/.zozul/zozul.db
90
- |
91
- +------+------+
92
- | |
93
- /dashboard /api/*
94
- (browser) (JSON)
95
- ```
35
+ | View | What it answers |
36
+ |---|---|
37
+ | **Summary** | How much have I spent? Cost chart, project breakdown, totals. |
38
+ | **Tasks** | What did I work on? Groups turns by tag combination, shows cost and time per task. |
39
+ | **Tags** | How much per category? Per-tag stats with drill-down into individual prompts. |
40
+ | **Sessions** | Raw session list. Sortable, filterable, click to see full conversation. |
96
41
 
97
- Everything runs in a single process on port 7890.
42
+ All views support time filtering (7d / 30d / All). Auto-refreshes every 10s.
98
43
 
99
- ### Data sources and ownership
44
+ When a remote backend is configured, the dashboard auto-detects it via health check and uses it as the data source. Falls back to local if unavailable.
100
45
 
101
- Each field in the `sessions` table has a designated owner:
46
+ ## Task tagging
102
47
 
103
- | Field | Owner | Notes |
104
- |---|---|---|
105
- | `id`, `started_at`, `project_path`, `model` | JSONL | Set from transcript filename and content |
106
- | `total_turns` | JSONL | Count of turns parsed from transcript |
107
- | `total_cost_usd` | OTEL | JSONL transcripts do not include cost data |
108
- | `total_duration_ms` | OTEL | Accumulated from `claude_code.active_time.total` |
109
- | `total_*_tokens` (session level) | OTEL (preferred) | JSONL provides seeds; OTEL accumulates via `MAX()` |
110
- | `ended_at` | Both | OTEL keeps it current as batches arrive; JSONL sets it at ingest |
48
+ Tag your work so costs are attributed to what you're building:
111
49
 
112
- The `sessions` upsert uses `MAX()` for all metric fields so OTEL-accumulated values are never clobbered by a JSONL re-ingest that may have lower (or zero) values.
50
+ ```bash
51
+ zozul context "auth" "backend" # Set active tags
52
+ # ... use Claude Code ...
53
+ git commit # Tags auto-clear on commit
54
+ ```
113
55
 
114
- ### JSONL watcher
56
+ Tags appear in the Tasks and Tags views. Turns are grouped by their tag combination.
115
57
 
116
- When `zozul serve` starts it:
58
+ ## Remote sync
117
59
 
118
- 1. Performs a catch-up pass ingests all JSONL files found under `~/.claude/projects/`
119
- 2. Watches that directory for changes via `fs.watch` (recursive, FSEvents on macOS)
120
- 3. Debounces per-file at 500ms and calls `ingestSessionFile` on each change
60
+ Optionally push data to a remote backend:
121
61
 
122
- This means starting zozul after Claude Code is already running is fine — all existing turns are recovered immediately and new turns appear within ~500ms of being written.
62
+ ```bash
63
+ # Set in .env or environment
64
+ ZOZUL_API_URL=https://your-backend.example.com
65
+ ZOZUL_API_KEY=your-key
123
66
 
124
- ### OTEL metrics
67
+ zozul sync
68
+ ```
125
69
 
126
- Claude Code exports OTLP JSON to `http://localhost:7890` on a 60s interval (metrics) and 5s interval (logs). Each batch contains **delta values** for the export window — not cumulative totals. zozul accumulates these into the `sessions` table via `updateSessionFromOtel` on every batch received.
70
+ Sync is incremental (watermark-based) and also runs automatically on session end when the service is running. The dashboard switches to the remote API when available.
127
71
 
128
- Raw metric rows are also stored in `otel_metrics` and `otel_events` for dashboard charts and event replay.
72
+ ## Commands
129
73
 
130
- ## Background service
74
+ | Command | Description |
75
+ |---|---|
76
+ | `zozul serve` | Start the server on port 7890 |
77
+ | `zozul install` | Configure Claude Code hooks and OTEL |
78
+ | `zozul install --service` | Also install as a background service (auto-starts on login) |
79
+ | `zozul install --status` | Check if the service is running |
80
+ | `zozul install --restart` | Restart the service after code changes |
81
+ | `zozul uninstall` | Remove all hooks, config, and service |
82
+ | `zozul context <tags...>` | Set active task tags |
83
+ | `zozul context --clear` | Clear tags |
84
+ | `zozul sync` | Push local data to remote backend |
85
+
86
+ ## How it works
131
87
 
132
- `zozul install --service` installs zozul as a persistent background service:
88
+ ```
89
+ Claude Code
90
+ |
91
+ +--- OTEL export (every ~60s) ---> /v1/metrics, /v1/logs
92
+ +--- Hook POSTs (real-time) -----> /hook/*
93
+ +--- JSONL transcripts ----------> fs.watch
94
+ |
95
+ v
96
+ SQLite (~/.zozul/zozul.db)
97
+ |
98
+ +--- /dashboard (browser)
99
+ +--- /api/* (JSON)
100
+ +--- zozul sync --> remote backend (optional)
101
+ ```
133
102
 
134
- - **macOS**: writes `~/Library/LaunchAgents/com.zozul.serve.plist` and loads it via `launchctl`. Starts on login, restarts on crash.
135
- - **Linux**: writes `~/.config/systemd/user/zozul.service` and enables it with `systemctl --user`.
103
+ Single process on port 7890. macOS uses launchd, Linux uses systemd.
136
104
 
137
- The service bakes in the exact node binary path (nvm-safe) and the script path at install time, so it doesn't depend on shell PATH.
105
+ ### Data ownership
138
106
 
139
- Logs write to `~/.zozul/zozul.log`.
107
+ | What | Source | Notes |
108
+ |---|---|---|
109
+ | Cost | OTEL | JSONL doesn't include cost |
110
+ | Duration | OTEL | Accumulated from active time metrics |
111
+ | Tokens | OTEL (preferred) | JSONL provides initial values, OTEL accumulates |
112
+ | Conversation text | JSONL | Full turns, tool calls, assistant responses |
113
+ | Session events | Hooks | Start, end, stop, tool use |
140
114
 
141
115
  ## Configuration
142
116
 
143
- Settings via `.env` in the working directory (see `.env.example`) or environment variables:
117
+ Via `.env` or environment variables:
144
118
 
145
119
  ```bash
146
- ZOZUL_PORT=7890 # Server port (default: 7890)
147
- ZOZUL_DB_PATH=~/.zozul/zozul.db # Database path
148
- ZOZUL_VERBOSE=1 # Log every event to stderr
149
- OTEL_ENDPOINT=http://localhost:7890 # Where Claude Code sends OTEL
150
- OTEL_PROTOCOL=http/json # Must be http/json
151
- OTEL_LOG_USER_PROMPTS=1 # Include prompt text in OTEL events
152
- OTEL_LOG_TOOL_DETAILS=1 # Include tool names in OTEL events
120
+ ZOZUL_PORT=7890 # Default: 7890
121
+ ZOZUL_DB_PATH=~/.zozul/zozul.db # Default: ~/.zozul/zozul.db
122
+ ZOZUL_VERBOSE=1 # Log all events
123
+ ZOZUL_API_URL=https://... # Remote backend URL (optional)
124
+ ZOZUL_API_KEY=... # Remote backend API key (optional)
153
125
  ```
154
126
 
155
- CLI flags override `.env` values.
156
-
157
- ## Data captured
158
-
159
- | Data point | Source | Granularity |
160
- |---|---|---|
161
- | Token usage (input/output/cache/creation) | OTEL + JSONL | Per-session and per-turn |
162
- | Cost (USD) | OTEL | Per-session, per-model |
163
- | Active time | OTEL | Per-session |
164
- | Turns / API calls | JSONL | Full content and metadata |
165
- | User prompts | Hooks (`UserPromptSubmit`) + JSONL | Count (aggregate) + full text (per-turn) |
166
- | Interruptions | Hooks (`Stop`) | Count (aggregate) |
167
- | Model responses | JSONL only | Full text |
168
- | Tool calls and results | Hooks + JSONL | Name, input, output |
169
- | Session lifecycle | Hooks | Start, end, stop events |
170
-
171
127
  ## Requirements
172
128
 
173
129
  - Node.js 18+
174
- - npm
175
- - Claude Code installed (`claude --version`)
130
+ - Claude Code (`claude --version`)
176
131
  - Claude Pro, Max, Teams, Enterprise, or API key
177
-
178
- ## Updating
179
-
180
- ```bash
181
- npm install -g zozul-cli
182
- ```
@@ -590,6 +590,7 @@
590
590
  let dataSource = 'local';
591
591
  let autoRefreshTimer = null;
592
592
  let currentView = 'summary';
593
+ let currentTimeWindow = '7d';
593
594
  let previousView = 'tasks';
594
595
  let allTaskGroups = [];
595
596
  let allTagStats = [];
@@ -844,7 +845,7 @@ async function loadSummary() {
844
845
  const [stats, tasks, costSeries, sessionsResp] = await Promise.all([
845
846
  fetchJson('/api/stats'),
846
847
  fetchJson('/api/tasks'),
847
- fetchJson('/api/metrics/cost?range=30d'),
848
+ fetchJson('/api/metrics/cost?range=' + (currentTimeWindow === 'all' ? '90d' : currentTimeWindow) + '&step=1d'),
848
849
  fetchJson('/api/sessions?limit=500&offset=0'),
849
850
  ]);
850
851
  renderSummaryStats({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zozul-cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Observability for Claude Code — track token usage, costs, turns, and conversation history",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -590,6 +590,7 @@
590
590
  let dataSource = 'local';
591
591
  let autoRefreshTimer = null;
592
592
  let currentView = 'summary';
593
+ let currentTimeWindow = '7d';
593
594
  let previousView = 'tasks';
594
595
  let allTaskGroups = [];
595
596
  let allTagStats = [];
@@ -844,7 +845,7 @@ async function loadSummary() {
844
845
  const [stats, tasks, costSeries, sessionsResp] = await Promise.all([
845
846
  fetchJson('/api/stats'),
846
847
  fetchJson('/api/tasks'),
847
- fetchJson('/api/metrics/cost?range=30d'),
848
+ fetchJson('/api/metrics/cost?range=' + (currentTimeWindow === 'all' ? '90d' : currentTimeWindow) + '&step=1d'),
848
849
  fetchJson('/api/sessions?limit=500&offset=0'),
849
850
  ]);
850
851
  renderSummaryStats({