ccgauge 0.3.1 → 1.0.0
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/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-build-manifest.json +41 -41
- package/.next/standalone/.next/app-path-routes-manifest.json +6 -6
- package/.next/standalone/.next/build-manifest.json +2 -2
- package/.next/standalone/.next/prerender-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/blocks/route.js +1 -1
- package/.next/standalone/.next/server/app/api/blocks/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/export/usage/route.js +1 -1
- package/.next/standalone/.next/server/app/api/export/usage/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/export/usage/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/pricing/route.js +1 -1
- package/.next/standalone/.next/server/app/api/pricing/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/projects/route.js +1 -1
- package/.next/standalone/.next/server/app/api/projects/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/scan/route.js +1 -1
- package/.next/standalone/.next/server/app/api/scan/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/sessions/route.js +1 -1
- package/.next/standalone/.next/server/app/api/sessions/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/api/usage/route.js +1 -1
- package/.next/standalone/.next/server/app/api/usage/route_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/models/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/page.js +2 -2
- package/.next/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/projects/page.js +1 -1
- package/.next/standalone/.next/server/app/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/sessions/page.js +1 -1
- package/.next/standalone/.next/server/app/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/settings/page.js +2 -2
- package/.next/standalone/.next/server/app/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/usage/page.js +2 -2
- package/.next/standalone/.next/server/app/usage/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app-paths-manifest.json +6 -6
- package/.next/standalone/.next/server/chunks/426.js +9 -4
- package/.next/standalone/.next/server/chunks/520.js +1 -1
- package/.next/standalone/.next/server/chunks/716.js +1 -1
- package/.next/standalone/.next/server/chunks/775.js +1 -1
- package/.next/standalone/.next/server/functions-config-manifest.json +4 -4
- package/.next/standalone/.next/server/pages/500.html +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/148-557ee562aff993b1.js +1 -0
- package/.next/standalone/.next/static/chunks/app/{error-89ee9e078058915d.js → error-3e48784f89c5ae8d.js} +1 -1
- package/.next/standalone/.next/static/chunks/app/layout-6c973d790f015707.js +1 -0
- package/.next/standalone/.next/static/chunks/app/models/page-dff43b9050382020.js +1 -0
- package/.next/standalone/.next/static/chunks/app/page-6d87d7a8aa752100.js +1 -0
- package/.next/standalone/.next/static/chunks/app/projects/[id]/page-3f812f0e20137f2b.js +1 -0
- package/.next/standalone/.next/static/chunks/app/sessions/[id]/page-3f812f0e20137f2b.js +1 -0
- package/.next/standalone/.next/static/chunks/app/settings/{page-334168b522eac1b1.js → page-d1af886a5c22af9b.js} +1 -1
- package/.next/standalone/.next/static/chunks/app/usage/page-26297e0641d51da8.js +1 -0
- package/.next/standalone/.next/static/css/b07523b7c353538d.css +3 -0
- package/.next/standalone/node_modules/next/node_modules/postcss/package.json +0 -0
- package/.next/standalone/package.json +20 -4
- package/CHANGELOG.md +208 -0
- package/README.md +235 -2
- package/README.zh-CN.md +229 -2
- package/bin/cli.mjs +123 -3
- package/dist/mcp/server.mjs +23622 -0
- package/dist/report/index.mjs +2098 -0
- package/package.json +29 -15
- package/.next/standalone/.next/static/chunks/454-d0e7d0fa6f643c41.js +0 -1
- package/.next/standalone/.next/static/chunks/app/layout-a6e30ba3a7f39737.js +0 -1
- package/.next/standalone/.next/static/chunks/app/models/page-e0e1b5979547421a.js +0 -1
- package/.next/standalone/.next/static/chunks/app/page-9347dfa20dabb24b.js +0 -1
- package/.next/standalone/.next/static/chunks/app/projects/[id]/page-5804875e3dc384df.js +0 -1
- package/.next/standalone/.next/static/chunks/app/sessions/[id]/page-5804875e3dc384df.js +0 -1
- package/.next/standalone/.next/static/chunks/app/usage/page-7789fec27778df9a.js +0 -1
- package/.next/standalone/.next/static/css/2e5f36bcdf442844.css +0 -3
- package/.next/standalone/node_modules/semver/classes/comparator.js +0 -143
- package/.next/standalone/node_modules/semver/classes/range.js +0 -557
- package/.next/standalone/node_modules/semver/classes/semver.js +0 -333
- package/.next/standalone/node_modules/semver/functions/cmp.js +0 -54
- package/.next/standalone/node_modules/semver/functions/coerce.js +0 -62
- package/.next/standalone/node_modules/semver/functions/compare.js +0 -7
- package/.next/standalone/node_modules/semver/functions/eq.js +0 -5
- package/.next/standalone/node_modules/semver/functions/gt.js +0 -5
- package/.next/standalone/node_modules/semver/functions/gte.js +0 -5
- package/.next/standalone/node_modules/semver/functions/lt.js +0 -5
- package/.next/standalone/node_modules/semver/functions/lte.js +0 -5
- package/.next/standalone/node_modules/semver/functions/neq.js +0 -5
- package/.next/standalone/node_modules/semver/functions/parse.js +0 -18
- package/.next/standalone/node_modules/semver/functions/satisfies.js +0 -12
- package/.next/standalone/node_modules/semver/internal/constants.js +0 -37
- package/.next/standalone/node_modules/semver/internal/debug.js +0 -11
- package/.next/standalone/node_modules/semver/internal/identifiers.js +0 -29
- package/.next/standalone/node_modules/semver/internal/lrucache.js +0 -42
- package/.next/standalone/node_modules/semver/internal/parse-options.js +0 -17
- package/.next/standalone/node_modules/semver/internal/re.js +0 -223
- package/.next/standalone/node_modules/semver/package.json +0 -78
- /package/.next/standalone/.next/static/{kmNFwlVOydWtqBX3zI8OH → ZPycmg0NLiIflO5NXMT75}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{kmNFwlVOydWtqBX3zI8OH → ZPycmg0NLiIflO5NXMT75}/_ssgManifest.js +0 -0
package/README.md
CHANGED
|
@@ -128,7 +128,46 @@ Background mode persists state under `~/.ccgauge/`:
|
|
|
128
128
|
| `ccgauge restart [options]` | Stop and re-start with new options. |
|
|
129
129
|
| `ccgauge status [--json]` | Inspect the background service. |
|
|
130
130
|
| `ccgauge open` | Open the running dashboard in your browser. |
|
|
131
|
-
| `ccgauge logs [-f] [-n <lines>]` | Print background
|
|
131
|
+
| `ccgauge logs [-f] [-n <lines>]` | Print background-service log file (the server's stdout). |
|
|
132
|
+
| `ccgauge report [options]` | Print a formatted **usage report** to stdout (one-shot, no server). |
|
|
133
|
+
| `ccgauge mcp` | Start the MCP server on stdio so LLMs can query usage. |
|
|
134
|
+
|
|
135
|
+
### Report
|
|
136
|
+
|
|
137
|
+
A no-server one-shot summary that reads the same JSONL files the dashboard does
|
|
138
|
+
and prints a colored, aligned report:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
ccgauge report # last 7d, all sources, top 10 models
|
|
142
|
+
ccgauge report -r 30d -b project # 30 days, broken down by project
|
|
143
|
+
ccgauge report -s codex -m gpt-5.5 # only codex, only gpt-5.5*
|
|
144
|
+
ccgauge report --json # JSON output for scripting
|
|
145
|
+
ccgauge report --since 2026-05-01 --until 2026-05-08
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Report options:
|
|
149
|
+
|
|
150
|
+
| Option | Default | Purpose |
|
|
151
|
+
| --- | --- | --- |
|
|
152
|
+
| `-r, --range <range>` | `7d` | `today` / `1d` / `7d` / `30d` / `90d` / `all` |
|
|
153
|
+
| `-s, --source <provider>` | `all` | `claude` / `codex` / `all` |
|
|
154
|
+
| `-b, --by <dim>` | `model` | Breakdown dimension: `model` / `project` / `session` |
|
|
155
|
+
| `-g, --gran <granularity>` | `day` | Trend bucket: `hour` / `day` / `week` / `month` |
|
|
156
|
+
| `-n, --limit <n>` | `10` | Rows in the breakdown table |
|
|
157
|
+
| `--since <date>` | — | Override range start (ISO date or `YYYY-MM-DD`) |
|
|
158
|
+
| `--until <date>` | — | Override range end |
|
|
159
|
+
| `-m, --model <pat>` | — | Filter records whose model contains `<pat>` |
|
|
160
|
+
| `--project <pat>` | — | Filter by project basename / cwd substring |
|
|
161
|
+
| `-j, --json` | off | Machine-readable JSON instead of formatted text |
|
|
162
|
+
| `--no-color` | — | Disable ANSI colors (auto-disabled when piped) |
|
|
163
|
+
| `--no-trend` | — | Skip the trend chart |
|
|
164
|
+
| `--no-breakdown` | — | Skip the breakdown table |
|
|
165
|
+
|
|
166
|
+
Date-only `--since/--until` values use local calendar-day boundaries, so
|
|
167
|
+
`--until 2026-05-08` includes all of May 8.
|
|
168
|
+
|
|
169
|
+
> The name `report` (not `logs`) avoids clashing with `ccgauge logs`, which tails
|
|
170
|
+
> the background server's stdout log file.
|
|
132
171
|
|
|
133
172
|
### Startup options
|
|
134
173
|
|
|
@@ -143,6 +182,200 @@ Background mode persists state under `~/.ccgauge/`:
|
|
|
143
182
|
| `--strict-port` | start, restart, root | Fail if the preferred port is busy. |
|
|
144
183
|
| `--log <path>` | start --background, restart | Background log file path. |
|
|
145
184
|
|
|
185
|
+
## MCP server (let an LLM query your usage)
|
|
186
|
+
|
|
187
|
+
ccgauge ships an [Model Context Protocol](https://modelcontextprotocol.io/)
|
|
188
|
+
server so any MCP-aware client (Claude Desktop, Cursor, Cline, Codex CLI,
|
|
189
|
+
your own agent…) can talk to your local Claude Code + Codex CLI history
|
|
190
|
+
through structured tools — no copy-paste, no screenshots of the dashboard.
|
|
191
|
+
|
|
192
|
+
### What you can ask
|
|
193
|
+
|
|
194
|
+
Once configured, you can ask things like:
|
|
195
|
+
|
|
196
|
+
- *"How much did I spend on AI coding this week? Break it down by Claude vs Codex."*
|
|
197
|
+
- *"What did I work on yesterday?"*
|
|
198
|
+
- *"Show me my 10 most expensive sessions this month."*
|
|
199
|
+
- *"Which project ate the most tokens in the last 30 days?"*
|
|
200
|
+
- *"Was prompt caching saving me money? How much?"*
|
|
201
|
+
- *"Estimate the cost of 100K input + 20K output on Opus 4.7."*
|
|
202
|
+
- *"How big was my Codex reasoning overhead last week?"*
|
|
203
|
+
- *"Give me a weekly stand-up bullet list of what I shipped."*
|
|
204
|
+
|
|
205
|
+
The LLM picks the right tool, calls it locally, and answers in plain
|
|
206
|
+
English with real numbers from your machine.
|
|
207
|
+
|
|
208
|
+
### Capabilities at a glance
|
|
209
|
+
|
|
210
|
+
| Tool | What it answers |
|
|
211
|
+
| --- | --- |
|
|
212
|
+
| `usage_summary` | Total tokens / cost / cache savings for a date range. Always returns combined totals + per-source breakdown. |
|
|
213
|
+
| `usage_by_time` | Time-series buckets (hour / day / week / month) for trend questions. |
|
|
214
|
+
| `usage_by_model` | Per-model cost share. Each entry tagged with its `source`. |
|
|
215
|
+
| `usage_by_project` | Per-project (cwd) cost share + session counts + last-activity. |
|
|
216
|
+
| `usage_by_session` | Session list with title (= first user message), models, duration, cost. Sort by recent / cost / tokens / duration. |
|
|
217
|
+
| `daily_summary` | "What did I do today / yesterday / Monday / on YYYY-MM-DD?" Sessions grouped by project + models + top tool calls. |
|
|
218
|
+
| `weekly_summary` | 7-day roll-up: per-day cost trend, top sessions, top projects, models. `week_offset=-1` for last week. |
|
|
219
|
+
| `recent_activity` | The N most recently active sessions across providers. |
|
|
220
|
+
|
|
221
|
+
| Resource URI | Content |
|
|
222
|
+
| --- | --- |
|
|
223
|
+
| `ccgauge://providers` | Detected providers, data dirs, file/record counts, indexer status. |
|
|
224
|
+
|
|
225
|
+
**Common arguments** (every analytical tool accepts these):
|
|
226
|
+
|
|
227
|
+
- `source`: `"claude"` | `"codex"` | `"all"` (default `"all"`). When `"all"`, the response carries combined totals **and** a `bySource: { claude, codex }` breakdown so the LLM can answer either combined or provider-specific questions in a single call.
|
|
228
|
+
- Date range: pass `range` (one of `today`, `yesterday`, `this_week`, `last_week`, `this_month`, `last_month`, `7d`, `30d`, `90d`, `all`) **or** explicit `from` / `to` (ISO date or full timestamp).
|
|
229
|
+
|
|
230
|
+
### Configure your MCP client
|
|
231
|
+
|
|
232
|
+
The exact config-file location depends on your client; the snippet shape
|
|
233
|
+
is the same.
|
|
234
|
+
|
|
235
|
+
#### Claude Desktop
|
|
236
|
+
|
|
237
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json` (macOS) /
|
|
238
|
+
`%APPDATA%\Claude\claude_desktop_config.json` (Windows):
|
|
239
|
+
|
|
240
|
+
```json
|
|
241
|
+
{
|
|
242
|
+
"mcpServers": {
|
|
243
|
+
"ccgauge": {
|
|
244
|
+
"command": "npx",
|
|
245
|
+
"args": ["-y", "ccgauge", "mcp"]
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
If you've installed ccgauge globally (`npm i -g ccgauge`), drop the `npx`:
|
|
252
|
+
|
|
253
|
+
```json
|
|
254
|
+
{
|
|
255
|
+
"mcpServers": {
|
|
256
|
+
"ccgauge": {
|
|
257
|
+
"command": "ccgauge",
|
|
258
|
+
"args": ["mcp"]
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Restart Claude Desktop. The 8 ccgauge tools appear in the tool picker.
|
|
265
|
+
|
|
266
|
+
#### Cursor
|
|
267
|
+
|
|
268
|
+
`~/.cursor/mcp.json` (project-level: `<project>/.cursor/mcp.json`):
|
|
269
|
+
|
|
270
|
+
```json
|
|
271
|
+
{
|
|
272
|
+
"mcpServers": {
|
|
273
|
+
"ccgauge": {
|
|
274
|
+
"command": "ccgauge",
|
|
275
|
+
"args": ["mcp"]
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
#### Cline / Continue / generic MCP clients
|
|
282
|
+
|
|
283
|
+
Anything that follows the standard `{ command, args, env? }` shape works.
|
|
284
|
+
Use either `npx -y ccgauge mcp` (no global install) or `ccgauge mcp`
|
|
285
|
+
(with global install). To override scan paths, pass them via `env`:
|
|
286
|
+
|
|
287
|
+
```json
|
|
288
|
+
{
|
|
289
|
+
"mcpServers": {
|
|
290
|
+
"ccgauge": {
|
|
291
|
+
"command": "ccgauge",
|
|
292
|
+
"args": ["mcp"],
|
|
293
|
+
"env": {
|
|
294
|
+
"CCGAUGE_CODEX_DIR": "/custom/codex/path",
|
|
295
|
+
"CLAUDE_CONFIG_DIR": "/custom/claude/path",
|
|
296
|
+
"CCGAUGE_STATE_DIR": "/custom/cache/path"
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
#### Verify it's working
|
|
304
|
+
|
|
305
|
+
In Claude Desktop, open a new chat and ask:
|
|
306
|
+
|
|
307
|
+
> *"What ccgauge tools do you have? Run usage_summary for the last 7 days."*
|
|
308
|
+
|
|
309
|
+
You should see Claude pick `usage_summary`, return a JSON payload with
|
|
310
|
+
`totals` + `bySource`, then summarise it in prose with real numbers.
|
|
311
|
+
|
|
312
|
+
### Prompt cookbook
|
|
313
|
+
|
|
314
|
+
Drop these into Claude Desktop / Cursor / Cline as-is. The italics next
|
|
315
|
+
to each one are the tool(s) the LLM will pick — useful if you want to
|
|
316
|
+
debug "why did it answer X".
|
|
317
|
+
|
|
318
|
+
#### Cost & usage
|
|
319
|
+
|
|
320
|
+
- *"How much did I spend on AI coding this week, broken down by Claude and Codex?"*
|
|
321
|
+
→ `usage_summary({ range: "7d" })`
|
|
322
|
+
- *"What's my AI coding cost this month? How does that compare to last month?"*
|
|
323
|
+
→ `usage_summary({ range: "this_month" })` + `usage_summary({ range: "last_month" })`
|
|
324
|
+
- *"Show me a daily cost trend for the last 30 days."*
|
|
325
|
+
→ `usage_by_time({ range: "30d", granularity: "day" })`
|
|
326
|
+
- *"Which Claude model did I use the most this month, and how much did it cost?"*
|
|
327
|
+
→ `usage_by_model({ range: "this_month", source: "claude" })`
|
|
328
|
+
- *"Top 5 most expensive sessions this month?"*
|
|
329
|
+
→ `usage_by_session({ range: "this_month", sort: "cost", limit: 5 })`
|
|
330
|
+
|
|
331
|
+
#### Work content / standup
|
|
332
|
+
|
|
333
|
+
- *"What did I work on yesterday? Group by project."*
|
|
334
|
+
→ `daily_summary({ date: "yesterday" })`
|
|
335
|
+
- *"Generate a Monday stand-up bullet list of what I shipped last week."*
|
|
336
|
+
→ `weekly_summary({ week_offset: -1 })`
|
|
337
|
+
- *"Which 3 projects have I touched most in the last 2 weeks?"*
|
|
338
|
+
→ `usage_by_project({ range: "14d", limit: 3 })` (LLM may also pull `weekly_summary`)
|
|
339
|
+
- *"What was my last coding session about?"*
|
|
340
|
+
→ `recent_activity({ limit: 1 })`
|
|
341
|
+
|
|
342
|
+
#### Caching / efficiency
|
|
343
|
+
|
|
344
|
+
- *"How many tokens did Anthropic prompt caching save me this month?"*
|
|
345
|
+
→ `usage_summary({ range: "this_month", source: "claude" })` — the response includes `saved_usd`.
|
|
346
|
+
- *"What percentage of my Codex output is reasoning tokens this week?"*
|
|
347
|
+
→ `usage_summary({ range: "7d", source: "codex" })` — response carries `reasoning_tokens` next to `output_tokens`.
|
|
348
|
+
|
|
349
|
+
#### Budget / planning
|
|
350
|
+
|
|
351
|
+
- *"At my current burn rate, how much will I spend this month?"*
|
|
352
|
+
→ `usage_summary({ range: "this_month" })` + `usage_by_time({ range: "this_month", granularity: "day" })` — LLM extrapolates.
|
|
353
|
+
- *"If I run another 200K input + 50K output on Opus 4.7 today, what does that add to my month-to-date cost?"*
|
|
354
|
+
→ `usage_summary({ range: "this_month" })` + LLM does the arithmetic from the published per-1M-token rates.
|
|
355
|
+
|
|
356
|
+
#### Cross-source comparisons
|
|
357
|
+
|
|
358
|
+
- *"Am I getting more value out of Claude or Codex this month, by tokens-per-dollar?"*
|
|
359
|
+
→ `usage_summary({ range: "this_month" })` — both totals are in `bySource`.
|
|
360
|
+
- *"For each provider, which project ate the most tokens last week?"*
|
|
361
|
+
→ `usage_by_project({ range: "last_week" })` (each entry already carries `source`).
|
|
362
|
+
|
|
363
|
+
### Privacy posture
|
|
364
|
+
|
|
365
|
+
- **stdio only** in v1 — no network ports, no remote access
|
|
366
|
+
- Reads only the JSONL files you already have on disk; no upstream API calls
|
|
367
|
+
- Absolute paths in error messages are scrubbed (`$HOME` → `~`)
|
|
368
|
+
- The MCP server uses a separate persisted cache (`~/.ccgauge/cache/index-mcp-v2.json`) so it never fights the dashboard for the same on-disk state file
|
|
369
|
+
|
|
370
|
+
### Troubleshooting
|
|
371
|
+
|
|
372
|
+
| Symptom | Try |
|
|
373
|
+
| --- | --- |
|
|
374
|
+
| Client doesn't see ccgauge tools | Restart the client after editing the config; check `npx -y ccgauge mcp` runs in your shell |
|
|
375
|
+
| First call is slow | First call after a cold start indexes all JSONL files (~1–3 s for 100 files); subsequent calls are O(1) |
|
|
376
|
+
| "no providers detected" in the resource | The MCP process can't see `~/.claude/projects` / `~/.codex/sessions`; pass `CLAUDE_CONFIG_DIR` / `CCGAUGE_CODEX_DIR` via `env` in the MCP config |
|
|
377
|
+
| Want to see what the server is logging | Watch the client's MCP log; ccgauge writes to **stderr** (stdout is reserved for JSON-RPC) |
|
|
378
|
+
|
|
146
379
|
## Configuration
|
|
147
380
|
|
|
148
381
|
ccgauge auto-detects the standard locations:
|
|
@@ -203,7 +436,7 @@ This repo is a working Next.js project — run the dashboard against your live d
|
|
|
203
436
|
git clone https://github.com/chengzuopeng/ccgauge.git
|
|
204
437
|
cd ccgauge
|
|
205
438
|
pnpm install
|
|
206
|
-
pnpm dev # http://localhost:
|
|
439
|
+
pnpm dev # http://localhost:3738
|
|
207
440
|
```
|
|
208
441
|
|
|
209
442
|
Scripts:
|
package/README.zh-CN.md
CHANGED
|
@@ -128,7 +128,44 @@ ccgauge stop
|
|
|
128
128
|
| `ccgauge restart [options]` | 停止再用新参数启动。 |
|
|
129
129
|
| `ccgauge status [--json]` | 查看后台状态。 |
|
|
130
130
|
| `ccgauge open` | 在浏览器打开正在运行的看板。 |
|
|
131
|
-
| `ccgauge logs [-f] [-n <lines>]` |
|
|
131
|
+
| `ccgauge logs [-f] [-n <lines>]` | 查看后台服务的日志(server stdout)。 |
|
|
132
|
+
| `ccgauge report [options]` | 命令行**用量报告**,直接打到终端(一次性,不起服务)。 |
|
|
133
|
+
| `ccgauge mcp` | 起 MCP 服务(stdio),让 LLM 查你的用量。 |
|
|
134
|
+
|
|
135
|
+
### 命令行报告(report)
|
|
136
|
+
|
|
137
|
+
不需要起 server,直接读 JSONL,在终端打印漂亮的彩色对齐报告:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
ccgauge report # 默认:近 7 天 / 所有数据源 / 前 10 个模型
|
|
141
|
+
ccgauge report -r 30d -b project # 30 天,按项目分组
|
|
142
|
+
ccgauge report -s codex -m gpt-5.5 # 只看 codex 的 gpt-5.5*
|
|
143
|
+
ccgauge report --json # 输出 JSON 给脚本用
|
|
144
|
+
ccgauge report --since 2026-05-01 --until 2026-05-08
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
report 参数:
|
|
148
|
+
|
|
149
|
+
| 参数 | 默认 | 作用 |
|
|
150
|
+
| --- | --- | --- |
|
|
151
|
+
| `-r, --range <range>` | `7d` | `today` / `1d` / `7d` / `30d` / `90d` / `all` |
|
|
152
|
+
| `-s, --source <provider>` | `all` | `claude` / `codex` / `all` |
|
|
153
|
+
| `-b, --by <dim>` | `model` | 分组维度:`model` / `project` / `session` |
|
|
154
|
+
| `-g, --gran <granularity>` | `day` | 趋势粒度:`hour` / `day` / `week` / `month` |
|
|
155
|
+
| `-n, --limit <n>` | `10` | 分组表显示行数 |
|
|
156
|
+
| `--since <date>` | — | 自定义起始日期(覆盖 `--range`,支持 `YYYY-MM-DD`) |
|
|
157
|
+
| `--until <date>` | — | 自定义截止日期 |
|
|
158
|
+
| `-m, --model <pat>` | — | 按模型名子串过滤 |
|
|
159
|
+
| `--project <pat>` | — | 按项目名 / cwd 子串过滤 |
|
|
160
|
+
| `-j, --json` | off | 输出 JSON 而不是格式化文本 |
|
|
161
|
+
| `--no-color` | — | 关掉 ANSI 颜色(管道里会自动关) |
|
|
162
|
+
| `--no-trend` | — | 不画趋势条 |
|
|
163
|
+
| `--no-breakdown` | — | 不打分组表 |
|
|
164
|
+
|
|
165
|
+
只写日期的 `--since/--until` 会按本地自然日边界处理,所以
|
|
166
|
+
`--until 2026-05-08` 会包含 5 月 8 日整天。
|
|
167
|
+
|
|
168
|
+
> 用 `report` 而不是 `logs` 是为了避免和 `ccgauge logs`(tail 后台 server 的 stdout)混淆。
|
|
132
169
|
|
|
133
170
|
### 启动参数
|
|
134
171
|
|
|
@@ -143,6 +180,196 @@ ccgauge stop
|
|
|
143
180
|
| `--strict-port` | start, restart, 根命令 | 端口不可用时直接失败。 |
|
|
144
181
|
| `--log <path>` | start --background, restart | 后台日志文件。 |
|
|
145
182
|
|
|
183
|
+
## MCP 服务(让大模型直接查你的用量)
|
|
184
|
+
|
|
185
|
+
ccgauge 内置了一个 [Model Context Protocol](https://modelcontextprotocol.io/) 服务,
|
|
186
|
+
任何 MCP 客户端(Claude Desktop / Cursor / Cline / Codex CLI / 自建 agent)都能
|
|
187
|
+
通过结构化 tool 调用,直接问大模型关于你本机 Claude Code + Codex CLI 历史的问题——
|
|
188
|
+
不用复制粘贴、不用截图看板。
|
|
189
|
+
|
|
190
|
+
### 你能问什么
|
|
191
|
+
|
|
192
|
+
配好之后,可以这样问:
|
|
193
|
+
|
|
194
|
+
- *"我这周在 AI 编程上花了多少?分别看下 Claude 和 Codex。"*
|
|
195
|
+
- *"我昨天都在做什么?"*
|
|
196
|
+
- *"列一下本月最贵的 10 个会话。"*
|
|
197
|
+
- *"过去 30 天哪个项目最吃 token?"*
|
|
198
|
+
- *"prompt caching 帮我省了多少钱?"*
|
|
199
|
+
- *"如果我在 Opus 4.7 上再跑 100K input + 20K output,要多少钱?"*
|
|
200
|
+
- *"上周 Codex 的 reasoning 开销有多大?"*
|
|
201
|
+
- *"给我一份本周完成事项的 standup bullet list。"*
|
|
202
|
+
|
|
203
|
+
LLM 会自动选合适的 tool、本地调用、用大白话给你带真实数字的答案。
|
|
204
|
+
|
|
205
|
+
### 工具一览
|
|
206
|
+
|
|
207
|
+
| Tool | 回答什么 |
|
|
208
|
+
| --- | --- |
|
|
209
|
+
| `usage_summary` | 一段时间内总 tokens / 花费 / 缓存节省。永远同时返回合并总数 + 按 source 拆分。 |
|
|
210
|
+
| `usage_by_time` | 时间序列(小时/天/周/月),用于趋势 / "什么时候开销爆了"。 |
|
|
211
|
+
| `usage_by_model` | 按模型的成本占比,每条带 source。 |
|
|
212
|
+
| `usage_by_project` | 按项目(cwd)的成本占比 + 会话数 + 最近活跃时间。 |
|
|
213
|
+
| `usage_by_session` | 会话列表,含标题(首条用户消息)/ 模型 / 时长 / 花费。可按 recent / cost / tokens / duration 排序。 |
|
|
214
|
+
| `daily_summary` | "今天 / 昨天 / 周一 / YYYY-MM-DD 我都干了啥?" 按项目分组的会话 + 模型 + top 工具调用。 |
|
|
215
|
+
| `weekly_summary` | 7 天 roll-up:每日花费趋势 + top 会话 + top 项目 + 模型分布。`week_offset=-1` 看上周。 |
|
|
216
|
+
| `recent_activity` | 最近 N 条活跃会话(不限日期)。 |
|
|
217
|
+
|
|
218
|
+
| Resource URI | 内容 |
|
|
219
|
+
| --- | --- |
|
|
220
|
+
| `ccgauge://providers` | 检测到的 provider、数据目录、文件 / 记录数、indexer 状态。 |
|
|
221
|
+
|
|
222
|
+
**公共参数**(每个分析类工具都接):
|
|
223
|
+
|
|
224
|
+
- `source`:`"claude"` | `"codex"` | `"all"`(默认 `"all"`)。当 `"all"` 时,响应同时带合并总数 **和** `bySource: { claude, codex }` 拆分,让 LLM 一次调用就能回答 "总共多少" 和 "分别多少" 两类问题。
|
|
225
|
+
- 时间范围:传 `range`(`today` / `yesterday` / `this_week` / `last_week` / `this_month` / `last_month` / `7d` / `30d` / `90d` / `all`),**或**显式 `from` / `to`(ISO 日期或完整时间戳)。
|
|
226
|
+
|
|
227
|
+
### 在 MCP 客户端里配置
|
|
228
|
+
|
|
229
|
+
不同客户端的配置文件位置不同,但 snippet 形状一样。
|
|
230
|
+
|
|
231
|
+
#### Claude Desktop
|
|
232
|
+
|
|
233
|
+
`~/Library/Application Support/Claude/claude_desktop_config.json`(macOS)/
|
|
234
|
+
`%APPDATA%\Claude\claude_desktop_config.json`(Windows):
|
|
235
|
+
|
|
236
|
+
```json
|
|
237
|
+
{
|
|
238
|
+
"mcpServers": {
|
|
239
|
+
"ccgauge": {
|
|
240
|
+
"command": "npx",
|
|
241
|
+
"args": ["-y", "ccgauge", "mcp"]
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
如果已经全局装了 ccgauge(`npm i -g ccgauge`),可以省掉 `npx`:
|
|
248
|
+
|
|
249
|
+
```json
|
|
250
|
+
{
|
|
251
|
+
"mcpServers": {
|
|
252
|
+
"ccgauge": {
|
|
253
|
+
"command": "ccgauge",
|
|
254
|
+
"args": ["mcp"]
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
重启 Claude Desktop,工具选择器里就能看到 ccgauge 的 8 个工具。
|
|
261
|
+
|
|
262
|
+
#### Cursor
|
|
263
|
+
|
|
264
|
+
`~/.cursor/mcp.json`(项目级:`<project>/.cursor/mcp.json`):
|
|
265
|
+
|
|
266
|
+
```json
|
|
267
|
+
{
|
|
268
|
+
"mcpServers": {
|
|
269
|
+
"ccgauge": {
|
|
270
|
+
"command": "ccgauge",
|
|
271
|
+
"args": ["mcp"]
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
#### Cline / Continue / 通用 MCP 客户端
|
|
278
|
+
|
|
279
|
+
任何遵循标准 `{ command, args, env? }` 格式的客户端都能用。`npx -y ccgauge mcp`
|
|
280
|
+
(无需全局装)或 `ccgauge mcp`(已全局装)任选其一。要覆盖扫描路径,通过 `env` 传:
|
|
281
|
+
|
|
282
|
+
```json
|
|
283
|
+
{
|
|
284
|
+
"mcpServers": {
|
|
285
|
+
"ccgauge": {
|
|
286
|
+
"command": "ccgauge",
|
|
287
|
+
"args": ["mcp"],
|
|
288
|
+
"env": {
|
|
289
|
+
"CCGAUGE_CODEX_DIR": "/custom/codex/path",
|
|
290
|
+
"CLAUDE_CONFIG_DIR": "/custom/claude/path",
|
|
291
|
+
"CCGAUGE_STATE_DIR": "/custom/cache/path"
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
#### 验证是否生效
|
|
299
|
+
|
|
300
|
+
在 Claude Desktop 新开一个对话,问:
|
|
301
|
+
|
|
302
|
+
> *"你有哪些 ccgauge 工具?跑一下 usage_summary 看最近 7 天数据。"*
|
|
303
|
+
|
|
304
|
+
如果配置成功,Claude 会调 `usage_summary`,返回带 `totals` + `bySource` 的 JSON,
|
|
305
|
+
然后用大白话总结成带真实数字的回答。
|
|
306
|
+
|
|
307
|
+
### Prompt 示例集
|
|
308
|
+
|
|
309
|
+
直接复制丢进 Claude Desktop / Cursor / Cline 即可。每个 prompt 后面斜体注的是
|
|
310
|
+
LLM 大概率会调的工具——方便你"为什么会这样回答"反查。
|
|
311
|
+
|
|
312
|
+
#### 用量与花费
|
|
313
|
+
|
|
314
|
+
- *"我这周用 AI 编程花了多少钱?分开看 Claude 和 Codex。"*
|
|
315
|
+
→ `usage_summary({ range: "7d" })`
|
|
316
|
+
- *"本月 AI 编程花了多少?跟上个月比怎么样?"*
|
|
317
|
+
→ `usage_summary({ range: "this_month" })` + `usage_summary({ range: "last_month" })`
|
|
318
|
+
- *"画一下最近 30 天的每日花费趋势。"*
|
|
319
|
+
→ `usage_by_time({ range: "30d", granularity: "day" })`
|
|
320
|
+
- *"本月用的最多的 Claude 模型是哪个?花了多少?"*
|
|
321
|
+
→ `usage_by_model({ range: "this_month", source: "claude" })`
|
|
322
|
+
- *"本月最贵的 5 个会话是哪些?"*
|
|
323
|
+
→ `usage_by_session({ range: "this_month", sort: "cost", limit: 5 })`
|
|
324
|
+
|
|
325
|
+
#### 工作内容回顾 / standup
|
|
326
|
+
|
|
327
|
+
- *"我昨天都做了什么?按项目分一下。"*
|
|
328
|
+
→ `daily_summary({ date: "yesterday" })`
|
|
329
|
+
- *"给我一份周一 standup 用的 bullet list,列我上周完成的事。"*
|
|
330
|
+
→ `weekly_summary({ week_offset: -1 })`
|
|
331
|
+
- *"过去两周我接触最多的 3 个项目是什么?"*
|
|
332
|
+
→ `usage_by_project({ range: "14d", limit: 3 })`(LLM 也可能补一次 `weekly_summary`)
|
|
333
|
+
- *"我最近一次的编码会话是关于什么的?"*
|
|
334
|
+
→ `recent_activity({ limit: 1 })`
|
|
335
|
+
|
|
336
|
+
#### 缓存 / 效率
|
|
337
|
+
|
|
338
|
+
- *"本月 Anthropic prompt caching 帮我省了多少 tokens?"*
|
|
339
|
+
→ `usage_summary({ range: "this_month", source: "claude" })`——返回里有 `saved_usd`。
|
|
340
|
+
- *"本周 Codex 的 output 里有多少比例是 reasoning tokens?"*
|
|
341
|
+
→ `usage_summary({ range: "7d", source: "codex" })`——返回里 `reasoning_tokens` 紧挨着 `output_tokens`。
|
|
342
|
+
|
|
343
|
+
#### 预算 / 规划
|
|
344
|
+
|
|
345
|
+
- *"按当前消耗速度,本月预计花多少?"*
|
|
346
|
+
→ `usage_summary({ range: "this_month" })` + `usage_by_time({ range: "this_month", granularity: "day" })`——LLM 自己外推。
|
|
347
|
+
- *"如果我今天再在 Opus 4.7 上跑 200K input + 50K output,本月累计要多少?"*
|
|
348
|
+
→ `usage_summary({ range: "this_month" })` + LLM 按公开单价做算术。
|
|
349
|
+
|
|
350
|
+
#### 跨数据源对比
|
|
351
|
+
|
|
352
|
+
- *"本月 Claude 和 Codex 哪个性价比更高(按每美元 tokens)?"*
|
|
353
|
+
→ `usage_summary({ range: "this_month" })`——两边数字都在 `bySource` 里。
|
|
354
|
+
- *"上周每个 provider 的最吃 token 项目分别是哪个?"*
|
|
355
|
+
→ `usage_by_project({ range: "last_week" })`(每条 entry 自带 `source`)。
|
|
356
|
+
|
|
357
|
+
### 隐私边界
|
|
358
|
+
|
|
359
|
+
- v1 **仅 stdio**——不开网络端口,不能远程访问
|
|
360
|
+
- 只读本机已有的 JSONL 文件,零上游 API 调用
|
|
361
|
+
- 错误信息里的绝对路径会脱敏(`$HOME` → `~`)
|
|
362
|
+
- MCP 用独立的持久化缓存文件(`~/.ccgauge/cache/index-mcp-v2.json`),永远不会和看板抢同一份磁盘状态
|
|
363
|
+
|
|
364
|
+
### 排障
|
|
365
|
+
|
|
366
|
+
| 现象 | 建议 |
|
|
367
|
+
| --- | --- |
|
|
368
|
+
| 客户端看不到 ccgauge 工具 | 改完配置重启客户端;终端里手动跑 `npx -y ccgauge mcp` 看是否能起 |
|
|
369
|
+
| 第一次调用比较慢 | 冷启动后第一次会全量索引(100 文件 ~1–3s);之后都是 O(1) |
|
|
370
|
+
| Resource 显示 "no providers detected" | MCP 进程看不到 `~/.claude/projects` / `~/.codex/sessions`;通过 MCP 配置的 `env` 传 `CLAUDE_CONFIG_DIR` / `CCGAUGE_CODEX_DIR` |
|
|
371
|
+
| 想看 server 在打什么日志 | 看客户端的 MCP 日志;ccgauge 把日志写到 **stderr**(stdout 被 JSON-RPC 占用)|
|
|
372
|
+
|
|
146
373
|
## 配置
|
|
147
374
|
|
|
148
375
|
ccgauge 会自动识别标准路径:
|
|
@@ -202,7 +429,7 @@ lib/providers/<name>/
|
|
|
202
429
|
git clone https://github.com/chengzuopeng/ccgauge.git
|
|
203
430
|
cd ccgauge
|
|
204
431
|
pnpm install
|
|
205
|
-
pnpm dev # http://localhost:
|
|
432
|
+
pnpm dev # http://localhost:3738
|
|
206
433
|
```
|
|
207
434
|
|
|
208
435
|
常用脚本:
|
package/bin/cli.mjs
CHANGED
|
@@ -4,7 +4,7 @@ import { closeSync, createReadStream, existsSync, openSync } from 'node:fs';
|
|
|
4
4
|
import { mkdir, readFile, rm, stat, writeFile } from 'node:fs/promises';
|
|
5
5
|
import os from 'node:os';
|
|
6
6
|
import { dirname, join, resolve } from 'node:path';
|
|
7
|
-
import { fileURLToPath } from 'node:url';
|
|
7
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
8
8
|
import { createRequire } from 'node:module';
|
|
9
9
|
|
|
10
10
|
const require = createRequire(import.meta.url);
|
|
@@ -25,8 +25,15 @@ const DEFAULT_LOG_FILE = join(STATE_DIR, 'ccgauge.log');
|
|
|
25
25
|
const STATE_VERSION = 1;
|
|
26
26
|
const DEFAULT_PORT = '3737';
|
|
27
27
|
const DEFAULT_HOST = '127.0.0.1';
|
|
28
|
-
const COMMAND_NAMES = new Set([
|
|
29
|
-
|
|
28
|
+
const COMMAND_NAMES = new Set([
|
|
29
|
+
'start', 'stop', 'restart', 'status', 'open', 'logs', 'mcp',
|
|
30
|
+
'report',
|
|
31
|
+
]);
|
|
32
|
+
const VALUE_OPTIONS = new Set([
|
|
33
|
+
'-p', '--port', '-H', '--host', '--dir', '--log', '-n', '--lines',
|
|
34
|
+
'-r', '--range', '-s', '--source', '-b', '--by', '-g', '--gran',
|
|
35
|
+
'-m', '--model', '--project', '--since', '--until',
|
|
36
|
+
]);
|
|
30
37
|
|
|
31
38
|
function browserHost(host) {
|
|
32
39
|
if (!host || host === '0.0.0.0' || host === '::' || host === '[::]') return '127.0.0.1';
|
|
@@ -132,6 +139,35 @@ program
|
|
|
132
139
|
await logs(opts);
|
|
133
140
|
});
|
|
134
141
|
|
|
142
|
+
program
|
|
143
|
+
.command('mcp')
|
|
144
|
+
.description('start the MCP server (stdio) so LLMs can query usage data')
|
|
145
|
+
.action(async () => {
|
|
146
|
+
await startMcp();
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
function addReportOptions(cmd) {
|
|
150
|
+
return cmd
|
|
151
|
+
.option('-r, --range <range>', 'today | 1d | 7d | 30d | 90d | all', '7d')
|
|
152
|
+
.option('-s, --source <provider>', 'claude | codex | all', 'all')
|
|
153
|
+
.option('-b, --by <dim>', 'breakdown dimension: model | project | session', 'model')
|
|
154
|
+
.option('-g, --gran <granularity>', 'trend granularity: hour | day | week | month', 'day')
|
|
155
|
+
.option('-n, --limit <n>', 'rows in breakdown table', '10')
|
|
156
|
+
.option('--since <date>', 'override range start (ISO date or YYYY-MM-DD)')
|
|
157
|
+
.option('--until <date>', 'override range end (ISO date or YYYY-MM-DD)')
|
|
158
|
+
.option('-m, --model <pat>', 'filter by model substring')
|
|
159
|
+
.option('--project <pat>', 'filter by project (cwd basename match)')
|
|
160
|
+
.option('-j, --json', 'output JSON instead of formatted text')
|
|
161
|
+
.option('--no-color', 'disable ANSI colors')
|
|
162
|
+
.option('--no-trend', 'skip the trend chart')
|
|
163
|
+
.option('--no-breakdown', 'skip the breakdown table');
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
addReportOptions(program.command('report').description('print a formatted usage report to stdout'))
|
|
167
|
+
.action(async (opts) => {
|
|
168
|
+
await report(opts);
|
|
169
|
+
});
|
|
170
|
+
|
|
135
171
|
await program.parseAsync(normalizeArgv(process.argv));
|
|
136
172
|
|
|
137
173
|
function normalizeArgv(argv) {
|
|
@@ -221,6 +257,9 @@ async function startBackground(standaloneEntry, opts) {
|
|
|
221
257
|
env,
|
|
222
258
|
detached: true,
|
|
223
259
|
stdio: ['ignore', out, err],
|
|
260
|
+
// Suppress the fleeting console window that Windows pops up for a
|
|
261
|
+
// detached background child. No-op on macOS/Linux.
|
|
262
|
+
windowsHide: true,
|
|
224
263
|
});
|
|
225
264
|
child.unref();
|
|
226
265
|
// Once spawn() has dup'd these fds into the child, the parent can release them.
|
|
@@ -344,6 +383,87 @@ or run the dev server with
|
|
|
344
383
|
process.exit(1);
|
|
345
384
|
}
|
|
346
385
|
|
|
386
|
+
async function report(opts) {
|
|
387
|
+
const bundle = join(packageRoot, 'dist', 'report', 'index.mjs');
|
|
388
|
+
if (!existsSync(bundle)) {
|
|
389
|
+
console.error(`
|
|
390
|
+
[ccgauge] Report bundle not found:
|
|
391
|
+
${bundle}
|
|
392
|
+
|
|
393
|
+
If you installed ccgauge from npm: please reinstall — the published package
|
|
394
|
+
should include the report bundle.
|
|
395
|
+
|
|
396
|
+
If you are running from source: build it first with
|
|
397
|
+
$ pnpm build:report
|
|
398
|
+
or run the full build with
|
|
399
|
+
$ pnpm build
|
|
400
|
+
`);
|
|
401
|
+
process.exit(1);
|
|
402
|
+
}
|
|
403
|
+
const limit = parseInt(String(opts.limit ?? '10'), 10);
|
|
404
|
+
const reportOpts = {
|
|
405
|
+
range: String(opts.range ?? '7d'),
|
|
406
|
+
source: String(opts.source ?? 'all'),
|
|
407
|
+
by: String(opts.by ?? 'model'),
|
|
408
|
+
gran: String(opts.gran ?? 'day'),
|
|
409
|
+
limit: Number.isFinite(limit) && limit > 0 ? limit : 10,
|
|
410
|
+
since: opts.since ? String(opts.since) : undefined,
|
|
411
|
+
until: opts.until ? String(opts.until) : undefined,
|
|
412
|
+
json: Boolean(opts.json),
|
|
413
|
+
color: opts.color !== false && process.stdout.isTTY,
|
|
414
|
+
showTrend: opts.trend !== false,
|
|
415
|
+
showBreakdown: opts.breakdown !== false,
|
|
416
|
+
model: opts.model ? String(opts.model) : undefined,
|
|
417
|
+
project: opts.project ? String(opts.project) : undefined,
|
|
418
|
+
};
|
|
419
|
+
try {
|
|
420
|
+
const mod = await import(pathToFileURL(bundle).href);
|
|
421
|
+
const out = await mod.runReport(reportOpts);
|
|
422
|
+
process.stdout.write(out);
|
|
423
|
+
if (!out.endsWith('\n')) process.stdout.write('\n');
|
|
424
|
+
} catch (err) {
|
|
425
|
+
console.error(`[ccgauge] report failed: ${(err && err.message) || err}`);
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
// The indexer keeps fs watchers alive, which would block process exit.
|
|
429
|
+
// For a one-shot report we explicitly exit once stdout is drained.
|
|
430
|
+
process.stdout.once?.('drain', () => process.exit(0));
|
|
431
|
+
if (process.stdout.writableLength === 0) process.exit(0);
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async function startMcp() {
|
|
435
|
+
const bundle = join(packageRoot, 'dist', 'mcp', 'server.mjs');
|
|
436
|
+
if (!existsSync(bundle)) {
|
|
437
|
+
console.error(`
|
|
438
|
+
[ccgauge-mcp] Build artifact not found:
|
|
439
|
+
${bundle}
|
|
440
|
+
|
|
441
|
+
If you installed ccgauge from npm: please reinstall — the published package should
|
|
442
|
+
include the MCP server bundle.
|
|
443
|
+
|
|
444
|
+
If you are running from source: build first with
|
|
445
|
+
$ pnpm build:mcp
|
|
446
|
+
or run the full build with
|
|
447
|
+
$ pnpm build
|
|
448
|
+
`);
|
|
449
|
+
process.exit(1);
|
|
450
|
+
}
|
|
451
|
+
// Hand control to the bundled MCP server. It owns the stdio JSON-RPC
|
|
452
|
+
// session for the lifetime of the parent (the LLM client) process.
|
|
453
|
+
const child = spawn(process.execPath, [bundle], {
|
|
454
|
+
stdio: 'inherit',
|
|
455
|
+
env: process.env,
|
|
456
|
+
});
|
|
457
|
+
const forward = (sig) => () => {
|
|
458
|
+
if (!child.killed) child.kill(sig);
|
|
459
|
+
};
|
|
460
|
+
process.on('SIGINT', forward('SIGINT'));
|
|
461
|
+
process.on('SIGTERM', forward('SIGTERM'));
|
|
462
|
+
child.on('exit', (code, sig) => {
|
|
463
|
+
process.exit(typeof code === 'number' ? code : sig ? 128 : 0);
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
347
467
|
async function resolvePort(opts) {
|
|
348
468
|
const preferred = parseInt(String(opts.port), 10);
|
|
349
469
|
if (!Number.isInteger(preferred) || preferred <= 0 || preferred > 65535) {
|