cursor-usage-report
Produces an Excel report of the user's Cursor activity, grouped by day, with one row per session: Date | 星期 | Project | Model | Duration | Msgs | Tokens | Cost | cursor-agent-generated summary.
When to invoke
User asks to summarize their Cursor usage over a period — typically a month, a date range, or "last N days". Sample triggers:
- "帮我总结 4 月份我的 cursor 报告"
- "出一下我 3 月的 Cursor 使用报表"
- "我这个月在 Cursor 里都做了什么"
- "summarize my Cursor usage for April"
Do NOT invoke for:
- Per-token / per-dollar cost analysis → Cursor is subscription-based; transcripts have no usage field. Tell the user to check cursor.com Usage page instead.
- Single-session questions without a date scope → ask user for a date range first
Workflow
Step 1 — Determine the date range
Parse from the user request. Resolve relative dates against the current date.
- "4 月份" / "April" → first-of-month to last-of-month
- "上个月" / "last month" → previous calendar month
- "这周" / "this week" → Monday to today
- Custom range → use as-is
Always express as ISO dates: --since YYYY-MM-DD --until YYYY-MM-DD (since inclusive, until exclusive).
Step 2 — Run the pipeline
Run the orchestrator from the skill root directory (the directory containing this SKILL.md) with a sensible output path:
node ./scripts/run.js \
--since 2026-04-01 --until 2026-05-01 \
--out ~/Desktop/cursor-usage-2026-04.xlsx
The script does four things internally and prints progress:
- extract — scan
~/.cursor/projects/*/agent-transcripts/*/*.jsonl, filter by file ctime (session creation time), parse user messages
- fetch usage events — read the IDE access token from
~/Library/Application Support/Cursor/User/globalStorage/state.vscdb (key cursorAuth/accessToken), then call POST https://api2.cursor.sh/aiserver.v1.DashboardService/GetFilteredUsageEvents (Bearer JWT) for every event in the date range. Each event has model + tokenUsage (input/output/cacheRead/cacheWrite) + chargedCents. Events are attributed to sessions by timestamp falling inside [ctime - 2min, mtime + 10min].
- summarize — for each session not already in cache, send its user messages to
cursor-agent for a one-line abstract summary (no verbatim copy). If cursor-agent is unavailable, print warning and continue with cache + heuristic summary.
- build-xlsx — invoke
scripts/build-xlsx.py to write the styled Excel
The script prints, in order:
- Progress for each step
- A final "统计 / Stats" block (session count, active days, project count, user message total)
- A final highlighted block with the report file path — this is the most important thing to relay to the user
Concurrency for summarization is 4 by default. ~100–200 sessions per month finishes in ~3–10 minutes depending on cursor-agent latency.
Step 3 — Report back
When the script finishes, tell the user (in this order, terse):
- 报表位置 — the Desktop xlsx path (paste verbatim from the final highlighted block; this is what the user wants most)
- 总账 — total sessions, total tokens (M), total cost ($), active days
- 归属率 —
X/Y events attributed, Z unattributed ($N). If unattributed > 30%, note it: most likely IDE-side requests (Tab autocomplete, Cmd+K inline edits, background composer) that don't write transcript files.
- Top 3 高消费日 with the heaviest session summary each — optional, only if it fits
Keep under 10 lines unless the user asks for more detail.
Prerequisites (check before running)
node >= 18
python3 with openpyxl (pip3 install --user openpyxl if missing)
cursor-agent CLI on PATH and authenticated. Verify with which cursor-agent; it lives at ~/.local/bin/cursor-agent on most installs.
If openpyxl is missing, run pip3 install --user openpyxl and continue — do not ask the user unless install fails.
Notes / gotchas
- Token / cost data source. Cursor transcripts themselves don't carry usage. We pull them from
https://api2.cursor.sh/aiserver.v1.DashboardService/GetFilteredUsageEvents using the IDE's JWT access token (read from the local vscdb). This is a private/undocumented API that powers the Cursor Settings → Usage page. Always-true caveat: Cursor can change or break this endpoint at any time without warning.
- Token = sum of input + output + cacheRead + cacheWrite as returned per-event. Cost is
chargedCents (the value Cursor actually billed you for, after included quota and bonus credits).
- Attribution rule: an event is attributed to a session if its timestamp falls inside
[session.ctime - 2min, session.mtime + 10min]. Multiple matches → pick the session whose mtime is closest to the event. No matches → goes into a red (未归属) footer row.
- Unattributed events are normal. Cursor's Tab autocomplete, Cmd+K inline edits, Composer in background mode, and various IDE-internal model calls do NOT create transcript jsonl files but still consume tokens. Expect 20–40% of events / spend to land in the
(未归属) row, especially for users who lean on Tab heavily.
- Most transcripts have zero attributed tokens. Many
agent-transcripts/<uuid>.jsonl files are short scratch sessions where the user opened Composer but didn't actually send a paid request. ~60–80% of session rows showing tokens=- is expected.
- Timestamps come from file ctime / mtime, not from inline event timestamps (which Cursor doesn't write).
firstTs = file ctime (creation), lastTs = file mtime (last write). Duration = mtime - ctime; capped at 12h+ for sanity.
- Session is included if ctime falls in
[since, until). A session that started 2026-04-30 and was resumed on 2026-05-02 is still attributed to 2026-04-30; we do NOT split per-day for Cursor (unlike the Claude Code version).
- Project label is derived from the directory slug (e.g.
Users-yinminqian-Code-iHealth-needleApp → iHealth/needleApp). The slug→path reverse is lossy (dots and spaces are eaten), so weird-looking paths happen — that's expected.
- System / tmp slugs (
var-folders-...) and bare numeric workspaceIds are filtered out or labeled (workspace#N) since they aren't real projects.
- Sessions with zero real user messages (
<system_*> only, automation, etc.) are dropped from the report.
- The cache at
<skillRoot>/cache/ is keyed by md5(sessionFile + first 3 user messages). Safe to delete to force re-summarization.
- Model: default is
haiku-4.5 (cheap, fast). Override with --model sonnet-4 if you want higher-quality summaries.
- Access token comes from vscdb. If
sqlite3 isn't on PATH or the user isn't signed into Cursor IDE, the script falls back to producing a report without Token/Cost columns and prints an explicit warning.
1---2name: cursor-usage-report3description: Generate a styled Excel report of Cursor usage by session for a given month or date range. Reads JSONL session logs from ~/.cursor/projects/**/agent-transcripts/, pulls token-level usage events from api2.cursor.sh (using the local IDE access token in vscdb), attributes events to sessions by timestamp, summarizes each session via cursor-agent (Haiku), and outputs an XLSX to the Desktop. Use this when the user asks for a Cursor usage report — phrases like "帮我总结X月份我的cursor报告", "总结我X月份的Cursor使用", "出X月Cursor报表", "我这个月在Cursor里做了什么", "summarize my Cursor usage for [period]".4---56# cursor-usage-report78Produces an Excel report of the user's Cursor activity, grouped by day, with one row per session: Date | 星期 | Project | Model | Duration | Msgs | Tokens | Cost | cursor-agent-generated summary.910## When to invoke1112User asks to **summarize their Cursor usage** over a period — typically a month, a date range, or "last N days". Sample triggers:13- "帮我总结 4 月份我的 cursor 报告"14- "出一下我 3 月的 Cursor 使用报表"15- "我这个月在 Cursor 里都做了什么"16- "summarize my Cursor usage for April"1718Do NOT invoke for:19- Per-token / per-dollar cost analysis → Cursor is subscription-based; transcripts have no usage field. Tell the user to check cursor.com Usage page instead.20- Single-session questions without a date scope → ask user for a date range first2122## Workflow2324### Step 1 — Determine the date range2526Parse from the user request. Resolve relative dates against the current date.2728- "4 月份" / "April" → first-of-month to last-of-month29- "上个月" / "last month" → previous calendar month30- "这周" / "this week" → Monday to today31- Custom range → use as-is3233Always express as ISO dates: `--since YYYY-MM-DD --until YYYY-MM-DD` (since inclusive, until exclusive).3435### Step 2 — Run the pipeline3637Run the orchestrator from the **skill root directory** (the directory containing this `SKILL.md`) with a sensible output path:3839```bash40node ./scripts/run.js \41 --since 2026-04-01 --until 2026-05-01 \42 --out ~/Desktop/cursor-usage-2026-04.xlsx43```4445The script does four things internally and prints progress:46471. **extract** — scan `~/.cursor/projects/*/agent-transcripts/*/*.jsonl`, filter by file ctime (session creation time), parse user messages482. **fetch usage events** — read the IDE access token from `~/Library/Application Support/Cursor/User/globalStorage/state.vscdb` (key `cursorAuth/accessToken`), then call `POST https://api2.cursor.sh/aiserver.v1.DashboardService/GetFilteredUsageEvents` (Bearer JWT) for every event in the date range. Each event has model + `tokenUsage` (input/output/cacheRead/cacheWrite) + `chargedCents`. Events are attributed to sessions by timestamp falling inside `[ctime - 2min, mtime + 10min]`.493. **summarize** — for each session not already in cache, send its user messages to `cursor-agent` for a one-line abstract summary (no verbatim copy). If `cursor-agent` is unavailable, print warning and continue with cache + heuristic summary.504. **build-xlsx** — invoke `scripts/build-xlsx.py` to write the styled Excel5152The script prints, in order:53- Progress for each step54- A final "统计 / Stats" block (session count, active days, project count, user message total)55- A final highlighted block with the **report file path** — this is the most important thing to relay to the user5657Concurrency for summarization is 4 by default. ~100–200 sessions per month finishes in ~3–10 minutes depending on cursor-agent latency.5859### Step 3 — Report back6061When the script finishes, tell the user (in this order, terse):62631. **报表位置** — the Desktop xlsx path (paste verbatim from the final highlighted block; this is what the user wants most)642. **总账** — total sessions, total tokens (M), total cost ($), active days653. **归属率** — `X/Y events attributed, Z unattributed ($N)`. If unattributed > 30%, note it: most likely IDE-side requests (Tab autocomplete, Cmd+K inline edits, background composer) that don't write transcript files.664. **Top 3 高消费日** with the heaviest session summary each — optional, only if it fits6768Keep under 10 lines unless the user asks for more detail.6970## Prerequisites (check before running)7172- `node` >= 1873- `python3` with `openpyxl` (`pip3 install --user openpyxl` if missing)74- `cursor-agent` CLI on PATH and authenticated. Verify with `which cursor-agent`; it lives at `~/.local/bin/cursor-agent` on most installs.7576If `openpyxl` is missing, run `pip3 install --user openpyxl` and continue — do not ask the user unless install fails.7778## Notes / gotchas7980- **Token / cost data source.** Cursor transcripts themselves don't carry usage. We pull them from `https://api2.cursor.sh/aiserver.v1.DashboardService/GetFilteredUsageEvents` using the IDE's JWT access token (read from the local vscdb). This is a private/undocumented API that powers the Cursor Settings → Usage page. **Always-true caveat:** Cursor can change or break this endpoint at any time without warning.81- **Token = sum of input + output + cacheRead + cacheWrite** as returned per-event. Cost is `chargedCents` (the value Cursor actually billed you for, after included quota and bonus credits).82- **Attribution rule**: an event is attributed to a session if its timestamp falls inside `[session.ctime - 2min, session.mtime + 10min]`. Multiple matches → pick the session whose mtime is closest to the event. No matches → goes into a red `(未归属)` footer row.83- **Unattributed events are normal.** Cursor's Tab autocomplete, Cmd+K inline edits, Composer in background mode, and various IDE-internal model calls do NOT create transcript jsonl files but still consume tokens. Expect 20–40% of events / spend to land in the `(未归属)` row, especially for users who lean on Tab heavily.84- **Most transcripts have zero attributed tokens.** Many `agent-transcripts/<uuid>.jsonl` files are short scratch sessions where the user opened Composer but didn't actually send a paid request. ~60–80% of session rows showing `tokens=-` is expected.85- **Timestamps come from file ctime / mtime**, not from inline event timestamps (which Cursor doesn't write). `firstTs = file ctime` (creation), `lastTs = file mtime` (last write). Duration = mtime - ctime; capped at `12h+` for sanity.86- **Session is included if ctime falls in `[since, until)`.** A session that started 2026-04-30 and was resumed on 2026-05-02 is still attributed to 2026-04-30; we do NOT split per-day for Cursor (unlike the Claude Code version).87- **Project label** is derived from the directory slug (e.g. `Users-yinminqian-Code-iHealth-needleApp` → `iHealth/needleApp`). The slug→path reverse is lossy (dots and spaces are eaten), so weird-looking paths happen — that's expected.88- **System / tmp slugs** (`var-folders-...`) and **bare numeric workspaceIds** are filtered out or labeled `(workspace#N)` since they aren't real projects.89- **Sessions with zero real user messages** (`<system_*>` only, automation, etc.) are dropped from the report.90- **The cache at `<skillRoot>/cache/`** is keyed by `md5(sessionFile + first 3 user messages)`. Safe to delete to force re-summarization.91- **Model**: default is `haiku-4.5` (cheap, fast). Override with `--model sonnet-4` if you want higher-quality summaries.92- **Access token comes from vscdb.** If `sqlite3` isn't on PATH or the user isn't signed into Cursor IDE, the script falls back to producing a report without Token/Cost columns and prints an explicit warning.