agent-session-inspector 0.1.3 → 0.1.5
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 +154 -64
- package/VERSION +1 -1
- package/app/.next/BUILD_ID +1 -1
- package/app/.next/app-path-routes-manifest.json +2 -0
- package/app/.next/build-manifest.json +3 -3
- package/app/.next/prerender-manifest.json +5 -5
- package/app/.next/required-server-files.json +1 -1
- package/app/.next/routes-manifest.json +15 -0
- package/app/.next/server/app/_global-error.html +1 -1
- package/app/.next/server/app/_global-error.rsc +1 -1
- package/app/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/app/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/app/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/app/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/app/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/_not-found.html +1 -1
- package/app/.next/server/app/_not-found.rsc +16 -16
- package/app/.next/server/app/_not-found.segments/_full.segment.rsc +16 -16
- package/app/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +19 -18
- package/app/.next/server/app/_not-found.segments/_tree.segment.rsc +3 -3
- package/app/.next/server/app/api/analytics/route.js +2 -2
- package/app/.next/server/app/api/analytics/route.js.nft.json +1 -1
- package/app/.next/server/app/api/logs/route.js +2 -2
- package/app/.next/server/app/api/logs/route.js.nft.json +1 -1
- package/app/.next/server/app/api/pricing/route/app-paths-manifest.json +3 -0
- package/app/.next/server/app/api/pricing/route/build-manifest.json +12 -0
- package/app/.next/server/app/api/pricing/route/server-reference-manifest.json +4 -0
- package/app/.next/server/app/api/pricing/route.js +6 -0
- package/app/.next/server/app/api/pricing/route.js.map +5 -0
- package/app/.next/server/app/api/pricing/route.js.nft.json +1 -0
- package/app/.next/server/app/api/pricing/route_client-reference-manifest.js +3 -0
- package/app/.next/server/app/api/providers/route.js +2 -2
- package/app/.next/server/app/api/providers/route.js.nft.json +1 -1
- package/app/.next/server/app/api/search/route.js +2 -2
- package/app/.next/server/app/api/search/route.js.nft.json +1 -1
- package/app/.next/server/app/api/sessions/[provider]/[id]/reveal/route/app-paths-manifest.json +3 -0
- package/app/.next/server/app/api/sessions/[provider]/[id]/reveal/route/build-manifest.json +12 -0
- package/app/.next/server/app/api/sessions/[provider]/[id]/reveal/route/server-reference-manifest.json +4 -0
- package/app/.next/server/app/api/sessions/[provider]/[id]/reveal/route.js +7 -0
- package/app/.next/server/app/api/sessions/[provider]/[id]/reveal/route.js.map +5 -0
- package/app/.next/server/app/api/sessions/[provider]/[id]/reveal/route.js.nft.json +1 -0
- package/app/.next/server/app/api/sessions/[provider]/[id]/reveal/route_client-reference-manifest.js +3 -0
- package/app/.next/server/app/api/sessions/[provider]/[id]/route.js +2 -2
- package/app/.next/server/app/api/sessions/[provider]/[id]/route.js.nft.json +1 -1
- package/app/.next/server/app/api/sessions/route.js +2 -2
- package/app/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/app/.next/server/app/index.html +1 -1
- package/app/.next/server/app/index.rsc +18 -18
- package/app/.next/server/app/index.segments/__PAGE__.segment.rsc +16 -16
- package/app/.next/server/app/index.segments/_full.segment.rsc +18 -18
- package/app/.next/server/app/index.segments/_tree.segment.rsc +3 -3
- package/app/.next/server/app/page.js.nft.json +1 -1
- package/app/.next/server/app/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app/sessions/[provider]/[id]/page.js.nft.json +1 -1
- package/app/.next/server/app/sessions/[provider]/[id]/page_client-reference-manifest.js +1 -1
- package/app/.next/server/app-paths-manifest.json +2 -0
- package/app/.next/server/chunks/1oeh_server_app_api_sessions_[provider]_[id]_reveal_route_actions_1mqlatn.js +3 -0
- package/app/.next/server/chunks/[root-of-the-server]__0dyq0rc._.js +3 -0
- package/app/.next/server/chunks/[root-of-the-server]__0kgkqhn._.js +3 -0
- package/app/.next/server/chunks/[root-of-the-server]__0yxic1z._.js +6 -0
- package/app/.next/server/chunks/[root-of-the-server]__10p8y9z._.js +3 -0
- package/app/.next/server/chunks/[root-of-the-server]__1cceere._.js +3 -0
- package/app/.next/server/chunks/[root-of-the-server]__1eclfq8._.js +3 -0
- package/app/.next/server/chunks/[root-of-the-server]__1lz20wm._.js +3 -0
- package/app/.next/server/chunks/[root-of-the-server]__1zdh1mi._.js +3 -0
- package/app/.next/server/chunks/[root-of-the-server]__20epvou._.js +3 -0
- package/app/.next/server/chunks/_next-internal_server_app_api_pricing_route_actions_06i5bdm.js +3 -0
- package/app/.next/server/chunks/ssr/[root-of-the-server]__1ei65vj._.js +1 -1
- package/app/.next/server/chunks/ssr/_0136j91._.js +1 -1
- package/app/.next/server/chunks/ssr/_0f8nmc3._.js +1 -1
- package/app/.next/server/chunks/ssr/_0o294sf._.js +3 -0
- package/app/.next/server/chunks/ssr/_1-5v1mi._.js +3 -0
- package/app/.next/server/chunks/ssr/_12__495._.js +1 -1
- package/app/.next/server/chunks/ssr/_1ck8cbf._.js +1 -1
- package/app/.next/server/chunks/ssr/src_app_page_tsx_1chiuah._.js +1 -1
- package/app/.next/server/chunks/ssr/src_app_sessions_[provider]_[id]_page_tsx_0uf635h._.js +3 -3
- package/app/.next/server/middleware-build-manifest.js +3 -3
- package/app/.next/server/pages/404.html +1 -1
- package/app/.next/server/pages/500.html +1 -1
- package/app/.next/server/server-reference-manifest.js +1 -1
- package/app/.next/server/server-reference-manifest.json +1 -1
- package/app/.next/static/chunks/043k5ranw7p1-.js +1 -0
- package/app/.next/static/chunks/0mihgzifdany7.js +1 -0
- package/app/.next/static/chunks/{367imffzz5g8x.css → 0ua57-s0meis5.css} +2 -2
- package/app/.next/static/chunks/{31hdfb4m8lwzz.js → 1fe2mxakoweia.js} +1 -1
- package/app/.next/static/chunks/1syzk-359a-tl.js +6 -0
- package/app/.next/static/chunks/23pvfhng1yidh.css +1 -0
- package/app/.next/static/chunks/2t205sbe-_sk4.js +1 -0
- package/app/.next/static/chunks/3-g9jvt_m-6_4.js +1 -0
- package/app/.next/static/chunks/{0u22wlboqnsjn.js → 3_e2d472hbms0.js} +1 -1
- package/app/AGENTS.md +9 -0
- package/app/CLAUDE.md +1 -0
- package/app/CONTRIBUTING.md +90 -0
- package/app/Dockerfile +34 -0
- package/app/LICENSE +21 -0
- package/app/README.md +367 -0
- package/app/bin/agent-session-inspector.mjs +185 -0
- package/app/components.json +25 -0
- package/app/docs/screenshots/analytics-dark.png +0 -0
- package/app/docs/screenshots/analytics.png +0 -0
- package/app/docs/screenshots/session-overview-dark.png +0 -0
- package/app/docs/screenshots/session-overview.png +0 -0
- package/app/docs/screenshots/sessions-dark.png +0 -0
- package/app/docs/screenshots/sessions.png +0 -0
- package/app/docs/screenshots/timeline-dark.png +0 -0
- package/app/docs/screenshots/timeline.png +0 -0
- package/app/docs/screenshots/token-optimizer-dark.png +0 -0
- package/app/docs/screenshots/token-optimizer.png +0 -0
- package/app/docs/social-preview.png +0 -0
- package/app/eslint.config.mjs +20 -0
- package/app/install.sh +85 -0
- package/app/next.config.ts +17 -0
- package/app/package.json +6 -2
- package/app/pnpm-lock.yaml +7031 -0
- package/app/pnpm-workspace.yaml +3 -0
- package/app/postcss.config.mjs +7 -0
- package/app/scripts/build-cli.mjs +26 -0
- package/app/scripts/demo-data.mjs +335 -0
- package/app/scripts/deploy-docker.sh +71 -0
- package/app/scripts/package-release.sh +86 -0
- package/app/server.js +1 -1
- package/app/tsconfig.json +34 -0
- package/bin/agent-session-inspector.mjs +37 -11
- package/cli/cli.mjs +7610 -0
- package/package.json +5 -2
- package/app/.next/server/chunks/[externals]__0xll52b._.js +0 -3
- package/app/.next/server/chunks/[root-of-the-server]__093lyxv._.js +0 -3
- package/app/.next/server/chunks/[root-of-the-server]__0ne74op._.js +0 -3
- package/app/.next/server/chunks/[root-of-the-server]__0uqb915._.js +0 -6
- package/app/.next/server/chunks/[root-of-the-server]__0uv5tnc._.js +0 -3
- package/app/.next/server/chunks/[root-of-the-server]__1lfc-ga._.js +0 -3
- package/app/.next/server/chunks/[root-of-the-server]__1n_sepq._.js +0 -3
- package/app/.next/server/chunks/ssr/_0w9kj20._.js +0 -3
- package/app/.next/server/chunks/ssr/_1te8t5n._.js +0 -3
- package/app/.next/static/chunks/105fms2oszpby.js +0 -1
- package/app/.next/static/chunks/13ads2-fzhdry.css +0 -1
- package/app/.next/static/chunks/1n1htnt-u9ghv.js +0 -6
- package/app/.next/static/chunks/2bron83qy3bb-.js +0 -1
- package/app/.next/static/chunks/2q8c3-tw6cvrv.js +0 -1
- package/app/.next/static/chunks/3rd1dhhkaulu1.js +0 -1
- /package/app/.next/static/{NMXKYaysRrCyMjSEbGwmR → -wq1RTFV0ts5s-g4PiOpq}/_buildManifest.js +0 -0
- /package/app/.next/static/{NMXKYaysRrCyMjSEbGwmR → -wq1RTFV0ts5s-g4PiOpq}/_clientMiddlewareManifest.js +0 -0
- /package/app/.next/static/{NMXKYaysRrCyMjSEbGwmR → -wq1RTFV0ts5s-g4PiOpq}/_ssgManifest.js +0 -0
package/app/README.md
ADDED
|
@@ -0,0 +1,367 @@
|
|
|
1
|
+
# Agent Session Inspector
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/agent-session-inspector)
|
|
4
|
+
[](https://github.com/kishanmundha/agent-session-inspector/actions/workflows/ci.yml)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
[](LICENSE)
|
|
7
|
+
|
|
8
|
+
See exactly where a coding-agent session's tokens and dollars went. A local
|
|
9
|
+
inspector for Claude Code, Codex, GitHub Copilot, Cursor, Gemini CLI, OpenCode
|
|
10
|
+
and Hermes transcripts: every prompt, thought, tool call and injected piece of
|
|
11
|
+
context in order, each with its own token count and cost.
|
|
12
|
+
|
|
13
|
+
It answers the questions a usage total cannot: why one session cost $12, which
|
|
14
|
+
turn blew up the context, how much of it was system prompt and tool results
|
|
15
|
+
rather than conversation, and which tool kept failing.
|
|
16
|
+
|
|
17
|
+
> **Your sessions never leave your machine.** The app collects nothing: no
|
|
18
|
+
> telemetry, no analytics, no account, no cloud. It reads the transcript files
|
|
19
|
+
> already on your disk, read-only, and serves them to your own browser on
|
|
20
|
+
> `127.0.0.1`. See [Privacy](#privacy) for the details and how to check.
|
|
21
|
+
|
|
22
|
+
<picture>
|
|
23
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/kishanmundha/agent-session-inspector/main/docs/screenshots/session-overview-dark.png">
|
|
24
|
+
<img alt="Session overview: token, cost, health and timing stats above the event timeline" src="https://raw.githubusercontent.com/kishanmundha/agent-session-inspector/main/docs/screenshots/session-overview.png">
|
|
25
|
+
</picture>
|
|
26
|
+
|
|
27
|
+
## Quick start
|
|
28
|
+
|
|
29
|
+
Run it with [Node.js](https://nodejs.org) 22.13 or newer, nothing to install:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
npx agent-session-inspector
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
It serves on [http://localhost:3000](http://localhost:3000) (or the next free
|
|
36
|
+
port), bound to `127.0.0.1` only, and opens your browser. `--port`, `--host` and
|
|
37
|
+
`--no-open` change that; `--help` lists everything.
|
|
38
|
+
|
|
39
|
+
`npx agent-session-inspector@latest` picks up a new release. To keep the command
|
|
40
|
+
around instead, install it globally:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
npm install -g agent-session-inspector
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Install script
|
|
47
|
+
|
|
48
|
+
The same build is on GitHub Releases, with an installer that needs only Node,
|
|
49
|
+
`curl` and `tar`:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
curl -fsSL https://github.com/kishanmundha/agent-session-inspector/releases/latest/download/install.sh | bash
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
It puts the app in `~/.agent-session-inspector` and links an
|
|
56
|
+
`agent-session-inspector` command into `~/.local/bin`. Re-run the install
|
|
57
|
+
command to update. To uninstall:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
rm -rf ~/.agent-session-inspector ~/.local/bin/agent-session-inspector
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The install script needs bash, so it covers macOS, Linux and WSL. On Windows
|
|
64
|
+
use `npx`.
|
|
65
|
+
|
|
66
|
+
## Features
|
|
67
|
+
|
|
68
|
+
### Inspect one session
|
|
69
|
+
|
|
70
|
+
**Step-by-step timeline.** Prompts, reasoning, tool calls and their results in
|
|
71
|
+
order, with per-step token counts and cost. What the agent was given shows up
|
|
72
|
+
too: system prompt, tool catalog, MCP instructions and skill listings appear as
|
|
73
|
+
events with their size. Expand any event to see its content, or open the raw
|
|
74
|
+
record.
|
|
75
|
+
|
|
76
|
+
<picture>
|
|
77
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/kishanmundha/agent-session-inspector/main/docs/screenshots/timeline-dark.png">
|
|
78
|
+
<img alt="Event timeline showing user prompt, thinking, and Grep/Read tool calls" src="https://raw.githubusercontent.com/kishanmundha/agent-session-inspector/main/docs/screenshots/timeline.png">
|
|
79
|
+
</picture>
|
|
80
|
+
|
|
81
|
+
**Token Optimizer.** What the session cost by token class, model and turn, where
|
|
82
|
+
the context went (system prompt vs. tool results vs. replies), which tools ran
|
|
83
|
+
most, and hints for trimming expensive sessions.
|
|
84
|
+
|
|
85
|
+
<picture>
|
|
86
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/kishanmundha/agent-session-inspector/main/docs/screenshots/token-optimizer-dark.png">
|
|
87
|
+
<img alt="Token Optimizer: estimated cost by token class and model, and a cost-by-turn chart" src="https://raw.githubusercontent.com/kishanmundha/agent-session-inspector/main/docs/screenshots/token-optimizer.png">
|
|
88
|
+
</picture>
|
|
89
|
+
|
|
90
|
+
**Session health.** A score out of 100 and a grade for how smoothly the session
|
|
91
|
+
ran, with links to the failed tools, retry loops and interrupted turns that cost
|
|
92
|
+
it points. See [Session health](#session-health).
|
|
93
|
+
|
|
94
|
+
**Edits per turn.** A session's Edits tab lists each prompt with the files the
|
|
95
|
+
agent wrote, edited or patched in response, linked to the event in the timeline.
|
|
96
|
+
|
|
97
|
+
### Find the session worth inspecting
|
|
98
|
+
|
|
99
|
+
**Full-text search.** `⌘K` / `Ctrl+K` searches every transcript of every agent:
|
|
100
|
+
prompts, replies, reasoning, tool arguments and tool results, as well as titles,
|
|
101
|
+
repos and branches. Each hit shows the matching line and what kind of event it
|
|
102
|
+
came from.
|
|
103
|
+
|
|
104
|
+
**All sessions in one place.** Every session of every supported agent on the
|
|
105
|
+
machine, filterable by agent. Each card shows turns, tool calls, health grade,
|
|
106
|
+
cost and tokens in/out at a glance.
|
|
107
|
+
|
|
108
|
+
<picture>
|
|
109
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/kishanmundha/agent-session-inspector/main/docs/screenshots/sessions-dark.png">
|
|
110
|
+
<img alt="Session list with agent filters, search and per-session health, cost and token counts" src="https://raw.githubusercontent.com/kishanmundha/agent-session-inspector/main/docs/screenshots/sessions.png">
|
|
111
|
+
</picture>
|
|
112
|
+
|
|
113
|
+
**Analytics across sessions.** Cost, messages, tool calls and active time for
|
|
114
|
+
the selected range, with an activity calendar and filters for project, agent
|
|
115
|
+
and model.
|
|
116
|
+
|
|
117
|
+
<picture>
|
|
118
|
+
<source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/kishanmundha/agent-session-inspector/main/docs/screenshots/analytics-dark.png">
|
|
119
|
+
<img alt="Analytics tab: cost, session and token totals above an activity calendar" src="https://raw.githubusercontent.com/kishanmundha/agent-session-inspector/main/docs/screenshots/analytics.png">
|
|
120
|
+
</picture>
|
|
121
|
+
|
|
122
|
+
**Projects.** Sessions are grouped by the repository they ran in, so work
|
|
123
|
+
started from a subfolder or a git worktree lands in the same project. The
|
|
124
|
+
Projects tab lists each one with its sessions, cost and tokens, and picking a
|
|
125
|
+
project filters Analytics, Usage and Sessions together. Outside a repository,
|
|
126
|
+
the working directory is the project; the dated scratch folders the Codex app
|
|
127
|
+
creates for project-less chats count as one project.
|
|
128
|
+
|
|
129
|
+
**Activity.** One day at a time: how many sessions were working at once in each
|
|
130
|
+
15-minute slot, the agent time and cost of each session, and the day's split by
|
|
131
|
+
project, model and agent.
|
|
132
|
+
|
|
133
|
+
**Skills and MCP tools.** The Analytics tab counts which skills were loaded and
|
|
134
|
+
which MCP servers and tools were called, for the selected range.
|
|
135
|
+
|
|
136
|
+
> Screenshots use synthetic demo data. To reproduce them locally, see
|
|
137
|
+
> [CONTRIBUTING.md](CONTRIBUTING.md#demo-data).
|
|
138
|
+
|
|
139
|
+
## Terminal reports
|
|
140
|
+
|
|
141
|
+
The same numbers are available without the web UI. Each command reads the
|
|
142
|
+
transcripts, prints a report and exits:
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
npx agent-session-inspector usage # tokens and estimated cost per day
|
|
146
|
+
npx agent-session-inspector stats # sessions, messages, tools and time, by agent
|
|
147
|
+
npx agent-session-inspector sessions # recent sessions with cost and health
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
$ npx agent-session-inspector usage --by model --all
|
|
152
|
+
Token usage · all time · by model
|
|
153
|
+
|
|
154
|
+
Model Sessions Input Output Cache read Cache write Total Est. cost
|
|
155
|
+
───────────────────────── ──────── ───── ────── ────────── ─────────── ────── ─────────
|
|
156
|
+
claude-opus-5-5 1 2.5K 3.4K 318.0K 23.1K 347.0K $0.26 ████████████████
|
|
157
|
+
claude-sonnet-5 1 2.0K 2.5K 235.1K 19.1K 258.8K $0.12 ███████▊
|
|
158
|
+
gpt-5.1 1 11.6K 1.6K 27.3K 0 40.5K $0.03 ██▏
|
|
159
|
+
───────────────────────── ──────── ───── ────── ────────── ─────────── ────── ─────────
|
|
160
|
+
Total 3 16.1K 7.5K 580.4K 42.2K 646.3K $0.41
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
Reports cover the last 30 days unless `--days <n>` or `--all` says otherwise.
|
|
164
|
+
`--agent`, `--project` and `--model` narrow them, and `--json` prints the data
|
|
165
|
+
for scripts. `usage` groups by day by default; `--by` takes `week`, `month`,
|
|
166
|
+
`project`, `model` or `agent`. `sessions --sort cost` lists the most expensive
|
|
167
|
+
sessions first. `<command> --help` lists every option.
|
|
168
|
+
|
|
169
|
+
#### Scripting
|
|
170
|
+
|
|
171
|
+
The tables are for reading: values are rounded, and piping one drops the rules
|
|
172
|
+
and bars but keeps the rounding. For automation use `--json`, which prints exact
|
|
173
|
+
numbers under stable names:
|
|
174
|
+
|
|
175
|
+
```bash
|
|
176
|
+
npx agent-session-inspector usage --days 1 --json | jq '.total.costUSD'
|
|
177
|
+
npx agent-session-inspector sessions --sort cost --json | jq -r '.sessions[].id'
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Every report carries `schemaVersion` (currently `1`) and `days` (`null` for all
|
|
181
|
+
time). New fields may appear within a version; a field is renamed or removed
|
|
182
|
+
only with a new one. Costs are US dollars, or `null` where no price is known.
|
|
183
|
+
|
|
184
|
+
| Command | Fields |
|
|
185
|
+
| ---------- | -------------------------------------------------------------------------------------------------------- |
|
|
186
|
+
| `usage` | `by`, `rows[]`, `total`, `unpricedModels`. A row has `key`, `sessions`, `inputTokens`, `outputTokens`, `cacheReadTokens`, `cacheWriteTokens`, `totalTokens`, `costUSD`; `total` has the same without `key`. |
|
|
187
|
+
| `stats` | `totals`, `messagesPerSession`, `agents[]`, `projects[]`, `tools[]`, `skills[]`, `mcpServers[]`, `unpricedModels`. |
|
|
188
|
+
| `sessions` | `sort`, `total` (matches before `--limit`), `sessions[]`. A session has `agent`, `id`, `title`, `project`, `model`, `createdAt`, `updatedAt`, `userMessages`, `toolCalls`, `inputTokens`, `outputTokens`, `estimatedCostUSD`, `health`. |
|
|
189
|
+
|
|
190
|
+
## Supported agents
|
|
191
|
+
|
|
192
|
+
| Agent | Reads from | Session titles | Logs |
|
|
193
|
+
| ----------------------------- | ---------------------------------------------- | --------------------- | ---- |
|
|
194
|
+
| GitHub Copilot CLI | `~/.copilot/session-state`, `~/.copilot/logs` | checkpoint DB | yes |
|
|
195
|
+
| GitHub Copilot Chat (VS Code) | `<Code>/User/workspaceStorage/*/chatSessions` | chat title | no |
|
|
196
|
+
| Claude Code | `~/.claude/projects/<project>/<session>.jsonl` | `custom`/`ai` titles | no |
|
|
197
|
+
| Claude Cowork | `<Claude>/local-agent-mode-sessions` | session descriptor | no |
|
|
198
|
+
| OpenAI Codex CLI | `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl` | `session_index.jsonl` | no |
|
|
199
|
+
| OpenCode | `~/.local/share/opencode/opencode.db` | `session` table | no |
|
|
200
|
+
| Hermes Agent | `~/.hermes/state.db` | `sessions` table | no |
|
|
201
|
+
| Gemini CLI | `~/.gemini/tmp/<project>/chats/session-*` | session summary | no |
|
|
202
|
+
| Cursor | `<Cursor>/User/globalStorage/state.vscdb` | chat name | no |
|
|
203
|
+
|
|
204
|
+
`<Code>`, `<Claude>` and `<Cursor>` are the desktop apps' data folders:
|
|
205
|
+
`~/Library/Application Support/…` on macOS, `%APPDATA%\…` on Windows,
|
|
206
|
+
`~/.config/…` on Linux. VS Code Insiders is read too. The SQLite stores are
|
|
207
|
+
opened read-only.
|
|
208
|
+
|
|
209
|
+
Three things differ from the CLIs that log every request:
|
|
210
|
+
|
|
211
|
+
- **Copilot Chat** meters a whole request, and its prompt count is that of the
|
|
212
|
+
last model round. For a request that called tools, input tokens and cost are a
|
|
213
|
+
floor.
|
|
214
|
+
- **Hermes** counts tokens per session, so its usage appears as one checkpoint at
|
|
215
|
+
the end of the timeline rather than per message.
|
|
216
|
+
- **Cursor** rarely writes token counts to disk, so most of its sessions show the
|
|
217
|
+
timeline, tools and timing but no tokens or cost. Only the chat table of
|
|
218
|
+
`state.vscdb` is read. A session that is not in it, such as one from the
|
|
219
|
+
Cursor CLI, is read from the plainer transcript under `~/.cursor/projects`,
|
|
220
|
+
which has no tool results and is timed to the minute.
|
|
221
|
+
|
|
222
|
+
Whichever directories exist on the machine show up; the rest are hidden.
|
|
223
|
+
|
|
224
|
+
## Privacy
|
|
225
|
+
|
|
226
|
+
Transcripts hold your prompts, code and file contents, so the app is built to
|
|
227
|
+
keep them on the machine. Nothing about you or your sessions is collected, by
|
|
228
|
+
this project or anyone else:
|
|
229
|
+
|
|
230
|
+
- **No data collection.** There is no telemetry, analytics, crash reporting,
|
|
231
|
+
account or sync, and Next.js's own telemetry is switched off.
|
|
232
|
+
- **No outbound requests.** The app reads the session files in place and talks
|
|
233
|
+
only to your browser. Fonts and the price table used for cost estimates are
|
|
234
|
+
bundled, not fetched. The one download is the app itself, when `npx` or the
|
|
235
|
+
install script fetches it.
|
|
236
|
+
- **Local only.** The server binds to `127.0.0.1`, so other machines on your
|
|
237
|
+
network cannot reach it unless you pass `--host`.
|
|
238
|
+
- **Read-only.** Session files are only read, and the SQLite stores are opened
|
|
239
|
+
read-only. The app never edits or deletes a transcript.
|
|
240
|
+
|
|
241
|
+
You don't have to take this on trust. The code is open, and the browser's
|
|
242
|
+
Network tab shows every request going to `localhost` and nowhere else; the app
|
|
243
|
+
works the same with the network disconnected.
|
|
244
|
+
|
|
245
|
+
## How it compares
|
|
246
|
+
|
|
247
|
+
[AgentsView](https://github.com/kenn-io/agentsview) and
|
|
248
|
+
[ccusage](https://github.com/ryoppippi/ccusage) read the same transcript files.
|
|
249
|
+
AgentsView is an archive and search tool across all your sessions; ccusage
|
|
250
|
+
prints usage and cost reports in the terminal. This project is for the step
|
|
251
|
+
after that: opening one session to see what the agent was given, what it did
|
|
252
|
+
with it, and what each step cost. It keeps no database and makes no outbound
|
|
253
|
+
requests.
|
|
254
|
+
|
|
255
|
+
## How it works
|
|
256
|
+
|
|
257
|
+
Every agent writes a different transcript format, so the app normalizes them into
|
|
258
|
+
one canonical event vocabulary and the UI only ever sees that.
|
|
259
|
+
|
|
260
|
+
```
|
|
261
|
+
src/lib/providers/
|
|
262
|
+
types.ts canonical model: SessionMeta, AgentEvent, SessionDetail, SessionProvider
|
|
263
|
+
analysis.ts provider-agnostic stats + token-cost hints
|
|
264
|
+
pricing.ts dated price table lookup, per-event and per-session cost
|
|
265
|
+
prices.json bundled list prices, USD per million tokens
|
|
266
|
+
fs-utils.ts jsonl reading, directory walking, mtime-keyed caching
|
|
267
|
+
sqlite-utils.ts read-only access to agents that keep sessions in SQLite
|
|
268
|
+
copilot.ts ~/.copilot adapter
|
|
269
|
+
vscode.ts Copilot Chat in VS Code: replays the editor's chat change logs
|
|
270
|
+
claude.ts ~/.claude adapter
|
|
271
|
+
cowork.ts Claude desktop agent mode; reuses the Claude transcript parser
|
|
272
|
+
codex.ts ~/.codex adapter
|
|
273
|
+
opencode.ts OpenCode's opencode.db
|
|
274
|
+
hermes.ts Hermes Agent's state.db
|
|
275
|
+
gemini.ts Gemini CLI: replays the session logs under ~/.gemini/tmp
|
|
276
|
+
cursor.ts Cursor's state.vscdb, else its ~/.cursor/projects transcripts
|
|
277
|
+
index.ts registry: listSessions / getSession / listLogs across providers
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
Events are typed `category.subCategory` — `user.message`, `assistant.message`,
|
|
281
|
+
`assistant.thinking`, `tool.execution_start`, `tool.execution_complete`,
|
|
282
|
+
`external_tool.requested`, `context.attachment`, `file.patch_applied`,
|
|
283
|
+
`web.search`, `session.*`. Anything an adapter emits outside that list still
|
|
284
|
+
renders through a generic card, so a new record type is never a crash.
|
|
285
|
+
|
|
286
|
+
Token accounting is centralized: adapters put per-message usage on the event
|
|
287
|
+
(`inputTokens` / `outputTokens` / `cacheReadTokens`) or, when a provider reports
|
|
288
|
+
running totals, attach `data.sessionTotals`. The latest `sessionTotals` wins over
|
|
289
|
+
summation.
|
|
290
|
+
|
|
291
|
+
### Cost estimates
|
|
292
|
+
|
|
293
|
+
Each session, and each request in the timeline, shows an estimated cost in
|
|
294
|
+
dollars. Adapters attach `data.billedUsage` to the events that carry usage, with
|
|
295
|
+
uncached input, cache reads, cache writes and output kept as separate,
|
|
296
|
+
non-overlapping counts, and `pricing.ts` multiplies them by the model's list
|
|
297
|
+
price. The Token Optimizer tab splits the total by token class and by model.
|
|
298
|
+
|
|
299
|
+
The figure is an API-equivalent estimate, not a bill: subscription and
|
|
300
|
+
request-based plans charge differently, and requests an agent makes outside the
|
|
301
|
+
transcript (title generation, for example) are not counted.
|
|
302
|
+
|
|
303
|
+
Prices are never stored with a session; cost is computed on every load. To
|
|
304
|
+
correct a price, add a model the bundled table does not know, or record a price
|
|
305
|
+
change, create `~/.agent-session-inspector/pricing.json`. It is merged over
|
|
306
|
+
[`prices.json`](src/lib/providers/prices.json) per model and picked up without a
|
|
307
|
+
restart:
|
|
308
|
+
|
|
309
|
+
```json
|
|
310
|
+
{
|
|
311
|
+
"my-local-model": { "input": 0, "output": 0 },
|
|
312
|
+
"claude-opus-5-5": [
|
|
313
|
+
{
|
|
314
|
+
"input": 4,
|
|
315
|
+
"output": 20,
|
|
316
|
+
"cacheRead": 0.2,
|
|
317
|
+
"cacheWrite": 5,
|
|
318
|
+
"cacheWrite1h": 8
|
|
319
|
+
},
|
|
320
|
+
{
|
|
321
|
+
"from": "2027-01-01",
|
|
322
|
+
"input": 3,
|
|
323
|
+
"output": 15,
|
|
324
|
+
"cacheRead": 0.15,
|
|
325
|
+
"cacheWrite": 3.75,
|
|
326
|
+
"cacheWrite1h": 6
|
|
327
|
+
}
|
|
328
|
+
]
|
|
329
|
+
}
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
A model with several entries is priced by date: each request uses the entry
|
|
333
|
+
whose `from` day is the latest one on or before the request, so a price change
|
|
334
|
+
does not rewrite the cost of older sessions. Models with no price show as
|
|
335
|
+
unpriced and are left out of the total rather than counted as free.
|
|
336
|
+
|
|
337
|
+
### Session health
|
|
338
|
+
|
|
339
|
+
Every session gets a score out of 100 and a grade (A to F), shown on its card
|
|
340
|
+
and in its header. The score is rule-based and read straight off the transcript
|
|
341
|
+
by `health.ts`: it measures how smoothly the session ran, not whether the result
|
|
342
|
+
was any good. A session starts at 100 and loses points for:
|
|
343
|
+
|
|
344
|
+
| Signal | What it means | Points |
|
|
345
|
+
| ----------------- | ---------------------------------------------------- | -------------- |
|
|
346
|
+
| Unfinished | Ends on an API error, or stops without a final reply | 25 / 15 |
|
|
347
|
+
| Tool failures | Share of tool results that failed | up to 25 |
|
|
348
|
+
| Retry loops | The same tool failing three times in a row | 6 each, max 18 |
|
|
349
|
+
| API errors | Errors and retried requests | 2 each, max 10 |
|
|
350
|
+
| Interrupted turns | Turns stopped before the agent finished | 4 each, max 12 |
|
|
351
|
+
| Compactions | Context summarized mid-session | 3 each, max 9 |
|
|
352
|
+
|
|
353
|
+
90 and up is an A, 80 a B, 70 a C, 60 a D. A session that is still running is not
|
|
354
|
+
marked down for being unfinished. Click the Health card on a session to see what
|
|
355
|
+
it lost points for and jump to those events; the Quality tab on the home page
|
|
356
|
+
shows grades, outcomes and the most common problems across sessions.
|
|
357
|
+
|
|
358
|
+
## Contributing
|
|
359
|
+
|
|
360
|
+
Bug reports and pull requests are welcome; open an
|
|
361
|
+
[issue](https://github.com/kishanmundha/agent-session-inspector/issues) for anything that looks wrong, ideally with the
|
|
362
|
+
agent, OS and Node version. [CONTRIBUTING.md](CONTRIBUTING.md) covers running
|
|
363
|
+
from source, demo data, adding a provider and releasing.
|
|
364
|
+
|
|
365
|
+
## License
|
|
366
|
+
|
|
367
|
+
[MIT](LICENSE) © Kishan Mundha
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Launcher for the packaged (standalone) build, run by npx or the installed
|
|
3
|
+
// command. Lives at <package>/bin/ beside app/, which holds the server.js that
|
|
4
|
+
// `next build` emits; see scripts/package-release.sh.
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
7
|
+
import { createRequire } from "node:module";
|
|
8
|
+
import net from "node:net";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
11
|
+
|
|
12
|
+
const NAME = "agent-session-inspector";
|
|
13
|
+
const MIN_NODE = [22, 13];
|
|
14
|
+
const PKG_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
|
|
15
|
+
const SERVER = path.join(PKG_DIR, "app", "server.js");
|
|
16
|
+
|
|
17
|
+
// Report commands print to the terminal instead of starting the server; they
|
|
18
|
+
// live in the bundle scripts/build-cli.mjs writes to <package>/cli/.
|
|
19
|
+
const CLI = path.join(PKG_DIR, "cli", "cli.mjs");
|
|
20
|
+
const COMMANDS = ["usage", "stats", "sessions"];
|
|
21
|
+
|
|
22
|
+
const HELP = `Usage: ${NAME} [options]
|
|
23
|
+
${NAME} <command> [options]
|
|
24
|
+
|
|
25
|
+
Browse Claude Code, Codex, Copilot, Cursor, Gemini CLI, OpenCode and Hermes
|
|
26
|
+
session transcripts in a local web UI, or print a report in the terminal.
|
|
27
|
+
|
|
28
|
+
Commands:
|
|
29
|
+
usage Tokens and estimated cost by day, model, project or agent
|
|
30
|
+
stats Sessions, messages, tool calls and time, by agent
|
|
31
|
+
sessions Recent sessions with their cost and health
|
|
32
|
+
|
|
33
|
+
Run a command with --help for its options.
|
|
34
|
+
|
|
35
|
+
Options:
|
|
36
|
+
-p, --port <port> Port to listen on (default: 3000, or the next free one)
|
|
37
|
+
-H, --host <host> Interface to bind (default: 127.0.0.1)
|
|
38
|
+
--no-open Don't open the browser
|
|
39
|
+
-v, --version Print the version
|
|
40
|
+
-h, --help Show this help
|
|
41
|
+
`;
|
|
42
|
+
|
|
43
|
+
function fail(message) {
|
|
44
|
+
console.error(`${NAME}: ${message}`);
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function parseArgs(argv) {
|
|
49
|
+
const opts = { port: null, host: "127.0.0.1", open: true };
|
|
50
|
+
for (let i = 0; i < argv.length; i++) {
|
|
51
|
+
const arg = argv[i];
|
|
52
|
+
const [flag, inline] = arg.startsWith("--") ? arg.split("=", 2) : [arg];
|
|
53
|
+
const value = () => inline ?? argv[++i] ?? fail(`${flag} needs a value`);
|
|
54
|
+
switch (flag) {
|
|
55
|
+
case "-p":
|
|
56
|
+
case "--port": {
|
|
57
|
+
const port = Number(value());
|
|
58
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) fail("invalid port");
|
|
59
|
+
opts.port = port;
|
|
60
|
+
break;
|
|
61
|
+
}
|
|
62
|
+
case "-H":
|
|
63
|
+
case "--host":
|
|
64
|
+
opts.host = value();
|
|
65
|
+
break;
|
|
66
|
+
case "--no-open":
|
|
67
|
+
opts.open = false;
|
|
68
|
+
break;
|
|
69
|
+
case "-v":
|
|
70
|
+
case "--version":
|
|
71
|
+
console.log(readVersion());
|
|
72
|
+
process.exit(0);
|
|
73
|
+
break;
|
|
74
|
+
case "-h":
|
|
75
|
+
case "--help":
|
|
76
|
+
console.log(HELP);
|
|
77
|
+
process.exit(0);
|
|
78
|
+
break;
|
|
79
|
+
default:
|
|
80
|
+
fail(`unknown option ${arg}\n\n${HELP}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return opts;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function readVersion() {
|
|
87
|
+
try {
|
|
88
|
+
return readFileSync(path.join(PKG_DIR, "VERSION"), "utf8").trim();
|
|
89
|
+
} catch {
|
|
90
|
+
return "dev";
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function isFree(port, host) {
|
|
95
|
+
return new Promise((resolve) => {
|
|
96
|
+
const probe = net.createServer();
|
|
97
|
+
probe.once("error", () => resolve(false));
|
|
98
|
+
probe.listen(port, host, () => probe.close(() => resolve(true)));
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function pickPort(preferred, host) {
|
|
103
|
+
for (let port = preferred; port < preferred + 20; port++) {
|
|
104
|
+
if (await isFree(port, host)) return port;
|
|
105
|
+
}
|
|
106
|
+
fail(`no free port between ${preferred} and ${preferred + 19}; pass --port`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function waitUntilUp(url) {
|
|
110
|
+
for (let i = 0; i < 100; i++) {
|
|
111
|
+
try {
|
|
112
|
+
await fetch(url, { method: "HEAD" });
|
|
113
|
+
return true;
|
|
114
|
+
} catch {
|
|
115
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return false;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function openBrowser(url) {
|
|
122
|
+
const [cmd, args] =
|
|
123
|
+
process.platform === "darwin"
|
|
124
|
+
? ["open", [url]]
|
|
125
|
+
: process.platform === "win32"
|
|
126
|
+
? ["cmd", ["/c", "start", "", url]]
|
|
127
|
+
: ["xdg-open", [url]];
|
|
128
|
+
// No browser (ssh, container) is fine; the URL is already printed.
|
|
129
|
+
spawn(cmd, args, { stdio: "ignore", detached: true })
|
|
130
|
+
.on("error", () => {})
|
|
131
|
+
.unref();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const argv = process.argv.slice(2);
|
|
135
|
+
const command = COMMANDS.includes(argv[0]) ? argv[0] : null;
|
|
136
|
+
if (!command && argv[0] && !argv[0].startsWith("-")) {
|
|
137
|
+
fail(`unknown command ${argv[0]}\n\n${HELP}`);
|
|
138
|
+
}
|
|
139
|
+
const opts = command ? null : parseArgs(argv);
|
|
140
|
+
|
|
141
|
+
// npx only warns about package.json "engines", and the app needs node:sqlite.
|
|
142
|
+
const [major, minor] = process.versions.node.split(".").map(Number);
|
|
143
|
+
if (major < MIN_NODE[0] || (major === MIN_NODE[0] && minor < MIN_NODE[1])) {
|
|
144
|
+
fail(`Node.js ${MIN_NODE.join(".")} or newer is required (found ${process.version}).`);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// node:sqlite still announces itself as experimental on Node 22; not useful here.
|
|
148
|
+
const emitWarning = process.emitWarning;
|
|
149
|
+
process.emitWarning = (warning, ...rest) => {
|
|
150
|
+
if (String(warning).includes("SQLite is an experimental feature")) return;
|
|
151
|
+
emitWarning.call(process, warning, ...rest);
|
|
152
|
+
};
|
|
153
|
+
|
|
154
|
+
if (command) {
|
|
155
|
+
if (!existsSync(CLI)) {
|
|
156
|
+
fail(`no command bundle found at ${CLI}\nRun \`pnpm cli ${command}\` from a checkout.`);
|
|
157
|
+
}
|
|
158
|
+
const { run } = await import(pathToFileURL(CLI).href);
|
|
159
|
+
process.exit(run(command, argv.slice(1)));
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!existsSync(SERVER)) {
|
|
163
|
+
fail(`no build found at ${SERVER}\nRun scripts/package-release.sh and use dist/${NAME}/bin/${NAME}.mjs.`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
let port = opts.port;
|
|
167
|
+
if (port === null) {
|
|
168
|
+
port = await pickPort(Number(process.env.PORT) || 3000, opts.host);
|
|
169
|
+
} else if (!(await isFree(port, opts.host))) {
|
|
170
|
+
fail(`port ${port} is already in use`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
process.env.NODE_ENV = "production";
|
|
174
|
+
process.env.NEXT_TELEMETRY_DISABLED = "1";
|
|
175
|
+
process.env.PORT = String(port);
|
|
176
|
+
process.env.HOSTNAME = opts.host;
|
|
177
|
+
|
|
178
|
+
// server.js is CommonJS and starts listening on load.
|
|
179
|
+
createRequire(import.meta.url)(SERVER);
|
|
180
|
+
|
|
181
|
+
if (opts.open) {
|
|
182
|
+
const browseHost = opts.host === "0.0.0.0" || opts.host === "::" ? "localhost" : opts.host;
|
|
183
|
+
const url = `http://${browseHost.includes(":") ? `[${browseHost}]` : browseHost}:${port}`;
|
|
184
|
+
if (await waitUntilUp(url)) openBrowser(url);
|
|
185
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://ui.shadcn.com/schema.json",
|
|
3
|
+
"style": "base-nova",
|
|
4
|
+
"rsc": true,
|
|
5
|
+
"tsx": true,
|
|
6
|
+
"tailwind": {
|
|
7
|
+
"config": "",
|
|
8
|
+
"css": "src/app/globals.css",
|
|
9
|
+
"baseColor": "neutral",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"iconLibrary": "lucide",
|
|
14
|
+
"rtl": false,
|
|
15
|
+
"aliases": {
|
|
16
|
+
"components": "@/components",
|
|
17
|
+
"utils": "@/lib/utils",
|
|
18
|
+
"ui": "@/components/ui",
|
|
19
|
+
"lib": "@/lib",
|
|
20
|
+
"hooks": "@/hooks"
|
|
21
|
+
},
|
|
22
|
+
"menuColor": "default",
|
|
23
|
+
"menuAccent": "subtle",
|
|
24
|
+
"registries": {}
|
|
25
|
+
}
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { defineConfig, globalIgnores } from "eslint/config";
|
|
2
|
+
import nextVitals from "eslint-config-next/core-web-vitals";
|
|
3
|
+
import nextTs from "eslint-config-next/typescript";
|
|
4
|
+
|
|
5
|
+
const eslintConfig = defineConfig([
|
|
6
|
+
...nextVitals,
|
|
7
|
+
...nextTs,
|
|
8
|
+
// Override default ignores of eslint-config-next.
|
|
9
|
+
globalIgnores([
|
|
10
|
+
// Default ignores of eslint-config-next:
|
|
11
|
+
".next/**",
|
|
12
|
+
"out/**",
|
|
13
|
+
"build/**",
|
|
14
|
+
"dist/**",
|
|
15
|
+
"cli/**",
|
|
16
|
+
"next-env.d.ts",
|
|
17
|
+
]),
|
|
18
|
+
]);
|
|
19
|
+
|
|
20
|
+
export default eslintConfig;
|