agent-session-inspector 0.1.3 → 0.1.4

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.
Files changed (135) hide show
  1. package/VERSION +1 -1
  2. package/app/.next/BUILD_ID +1 -1
  3. package/app/.next/app-path-routes-manifest.json +2 -0
  4. package/app/.next/build-manifest.json +3 -3
  5. package/app/.next/prerender-manifest.json +3 -3
  6. package/app/.next/required-server-files.json +1 -1
  7. package/app/.next/routes-manifest.json +15 -0
  8. package/app/.next/server/app/_global-error.html +1 -1
  9. package/app/.next/server/app/_global-error.rsc +1 -1
  10. package/app/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  11. package/app/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  12. package/app/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  13. package/app/.next/server/app/_not-found/page.js.nft.json +1 -1
  14. package/app/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  15. package/app/.next/server/app/_not-found.html +1 -1
  16. package/app/.next/server/app/_not-found.rsc +14 -14
  17. package/app/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
  18. package/app/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
  19. package/app/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  20. package/app/.next/server/app/api/analytics/route.js +2 -2
  21. package/app/.next/server/app/api/analytics/route.js.nft.json +1 -1
  22. package/app/.next/server/app/api/logs/route.js +2 -2
  23. package/app/.next/server/app/api/logs/route.js.nft.json +1 -1
  24. package/app/.next/server/app/api/pricing/route/app-paths-manifest.json +3 -0
  25. package/app/.next/server/app/api/pricing/route/build-manifest.json +12 -0
  26. package/app/.next/server/app/api/pricing/route/server-reference-manifest.json +4 -0
  27. package/app/.next/server/app/api/pricing/route.js +6 -0
  28. package/app/.next/server/app/api/pricing/route.js.map +5 -0
  29. package/app/.next/server/app/api/pricing/route.js.nft.json +1 -0
  30. package/app/.next/server/app/api/pricing/route_client-reference-manifest.js +3 -0
  31. package/app/.next/server/app/api/providers/route.js +2 -2
  32. package/app/.next/server/app/api/providers/route.js.nft.json +1 -1
  33. package/app/.next/server/app/api/search/route.js +2 -2
  34. package/app/.next/server/app/api/search/route.js.nft.json +1 -1
  35. package/app/.next/server/app/api/sessions/[provider]/[id]/reveal/route/app-paths-manifest.json +3 -0
  36. package/app/.next/server/app/api/sessions/[provider]/[id]/reveal/route/build-manifest.json +12 -0
  37. package/app/.next/server/app/api/sessions/[provider]/[id]/reveal/route/server-reference-manifest.json +4 -0
  38. package/app/.next/server/app/api/sessions/[provider]/[id]/reveal/route.js +7 -0
  39. package/app/.next/server/app/api/sessions/[provider]/[id]/reveal/route.js.map +5 -0
  40. package/app/.next/server/app/api/sessions/[provider]/[id]/reveal/route.js.nft.json +1 -0
  41. package/app/.next/server/app/api/sessions/[provider]/[id]/reveal/route_client-reference-manifest.js +3 -0
  42. package/app/.next/server/app/api/sessions/[provider]/[id]/route.js +2 -2
  43. package/app/.next/server/app/api/sessions/[provider]/[id]/route.js.nft.json +1 -1
  44. package/app/.next/server/app/api/sessions/route.js +2 -2
  45. package/app/.next/server/app/api/sessions/route.js.nft.json +1 -1
  46. package/app/.next/server/app/index.html +1 -1
  47. package/app/.next/server/app/index.rsc +16 -16
  48. package/app/.next/server/app/index.segments/__PAGE__.segment.rsc +15 -15
  49. package/app/.next/server/app/index.segments/_full.segment.rsc +16 -16
  50. package/app/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  51. package/app/.next/server/app/page.js.nft.json +1 -1
  52. package/app/.next/server/app/page_client-reference-manifest.js +1 -1
  53. package/app/.next/server/app/sessions/[provider]/[id]/page.js.nft.json +1 -1
  54. package/app/.next/server/app/sessions/[provider]/[id]/page_client-reference-manifest.js +1 -1
  55. package/app/.next/server/app-paths-manifest.json +2 -0
  56. package/app/.next/server/chunks/1oeh_server_app_api_sessions_[provider]_[id]_reveal_route_actions_1mqlatn.js +3 -0
  57. package/app/.next/server/chunks/[root-of-the-server]__0dyq0rc._.js +3 -0
  58. package/app/.next/server/chunks/[root-of-the-server]__0kgkqhn._.js +3 -0
  59. package/app/.next/server/chunks/[root-of-the-server]__0yxic1z._.js +6 -0
  60. package/app/.next/server/chunks/[root-of-the-server]__10p8y9z._.js +3 -0
  61. package/app/.next/server/chunks/[root-of-the-server]__1cceere._.js +3 -0
  62. package/app/.next/server/chunks/[root-of-the-server]__1eclfq8._.js +3 -0
  63. package/app/.next/server/chunks/[root-of-the-server]__1lz20wm._.js +3 -0
  64. package/app/.next/server/chunks/[root-of-the-server]__1zdh1mi._.js +3 -0
  65. package/app/.next/server/chunks/[root-of-the-server]__20epvou._.js +3 -0
  66. package/app/.next/server/chunks/_next-internal_server_app_api_pricing_route_actions_06i5bdm.js +3 -0
  67. package/app/.next/server/chunks/ssr/_0136j91._.js +1 -1
  68. package/app/.next/server/chunks/ssr/_0o294sf._.js +3 -0
  69. package/app/.next/server/chunks/ssr/_1-5v1mi._.js +3 -0
  70. package/app/.next/server/chunks/ssr/_12__495._.js +1 -1
  71. package/app/.next/server/chunks/ssr/_1ck8cbf._.js +1 -1
  72. package/app/.next/server/chunks/ssr/src_app_page_tsx_1chiuah._.js +1 -1
  73. package/app/.next/server/chunks/ssr/src_app_sessions_[provider]_[id]_page_tsx_0uf635h._.js +3 -3
  74. package/app/.next/server/middleware-build-manifest.js +3 -3
  75. package/app/.next/server/pages/404.html +1 -1
  76. package/app/.next/server/pages/500.html +1 -1
  77. package/app/.next/server/server-reference-manifest.js +1 -1
  78. package/app/.next/server/server-reference-manifest.json +1 -1
  79. package/app/.next/static/chunks/10x4j1wts3xx2.js +1 -0
  80. package/app/.next/static/chunks/1dz8a9ds45tdb.js +6 -0
  81. package/app/.next/static/chunks/1g3ep2o7fhgpe.css +1 -0
  82. package/app/.next/static/chunks/2iedud09ou282.js +1 -0
  83. package/app/.next/static/chunks/2k65elb1vo-2z.js +1 -0
  84. package/app/.next/static/chunks/2t205sbe-_sk4.js +1 -0
  85. package/app/.next/static/chunks/{0u22wlboqnsjn.js → 3_e2d472hbms0.js} +1 -1
  86. package/app/AGENTS.md +9 -0
  87. package/app/CLAUDE.md +1 -0
  88. package/app/CONTRIBUTING.md +71 -0
  89. package/app/Dockerfile +34 -0
  90. package/app/LICENSE +21 -0
  91. package/app/README.md +277 -0
  92. package/app/bin/agent-session-inspector.mjs +159 -0
  93. package/app/components.json +25 -0
  94. package/app/docs/screenshots/analytics-dark.png +0 -0
  95. package/app/docs/screenshots/analytics.png +0 -0
  96. package/app/docs/screenshots/session-overview-dark.png +0 -0
  97. package/app/docs/screenshots/session-overview.png +0 -0
  98. package/app/docs/screenshots/sessions-dark.png +0 -0
  99. package/app/docs/screenshots/sessions.png +0 -0
  100. package/app/docs/screenshots/timeline-dark.png +0 -0
  101. package/app/docs/screenshots/timeline.png +0 -0
  102. package/app/docs/screenshots/token-optimizer-dark.png +0 -0
  103. package/app/docs/screenshots/token-optimizer.png +0 -0
  104. package/app/docs/social-preview.png +0 -0
  105. package/app/eslint.config.mjs +19 -0
  106. package/app/install.sh +85 -0
  107. package/app/next.config.ts +17 -0
  108. package/app/package.json +1 -1
  109. package/app/pnpm-lock.yaml +6760 -0
  110. package/app/pnpm-workspace.yaml +2 -0
  111. package/app/postcss.config.mjs +7 -0
  112. package/app/scripts/demo-data.mjs +288 -0
  113. package/app/scripts/deploy-docker.sh +67 -0
  114. package/app/scripts/package-release.sh +84 -0
  115. package/app/server.js +1 -1
  116. package/app/tsconfig.json +34 -0
  117. package/package.json +1 -1
  118. package/app/.next/server/chunks/[externals]__0xll52b._.js +0 -3
  119. package/app/.next/server/chunks/[root-of-the-server]__093lyxv._.js +0 -3
  120. package/app/.next/server/chunks/[root-of-the-server]__0ne74op._.js +0 -3
  121. package/app/.next/server/chunks/[root-of-the-server]__0uqb915._.js +0 -6
  122. package/app/.next/server/chunks/[root-of-the-server]__0uv5tnc._.js +0 -3
  123. package/app/.next/server/chunks/[root-of-the-server]__1lfc-ga._.js +0 -3
  124. package/app/.next/server/chunks/[root-of-the-server]__1n_sepq._.js +0 -3
  125. package/app/.next/server/chunks/ssr/_0w9kj20._.js +0 -3
  126. package/app/.next/server/chunks/ssr/_1te8t5n._.js +0 -3
  127. package/app/.next/static/chunks/105fms2oszpby.js +0 -1
  128. package/app/.next/static/chunks/13ads2-fzhdry.css +0 -1
  129. package/app/.next/static/chunks/1n1htnt-u9ghv.js +0 -6
  130. package/app/.next/static/chunks/2bron83qy3bb-.js +0 -1
  131. package/app/.next/static/chunks/2q8c3-tw6cvrv.js +0 -1
  132. package/app/.next/static/chunks/3rd1dhhkaulu1.js +0 -1
  133. /package/app/.next/static/{NMXKYaysRrCyMjSEbGwmR → KFq9wlUt8U-Sma3McE5Jw}/_buildManifest.js +0 -0
  134. /package/app/.next/static/{NMXKYaysRrCyMjSEbGwmR → KFq9wlUt8U-Sma3McE5Jw}/_clientMiddlewareManifest.js +0 -0
  135. /package/app/.next/static/{NMXKYaysRrCyMjSEbGwmR → KFq9wlUt8U-Sma3McE5Jw}/_ssgManifest.js +0 -0
@@ -0,0 +1,71 @@
1
+ # Contributing
2
+
3
+ Issues and pull requests are welcome. For a bug, include the agent, your OS and
4
+ Node version, and what the session looked like (never the transcript itself if
5
+ it holds anything private).
6
+
7
+ ## Running from source
8
+
9
+ ```bash
10
+ pnpm install
11
+ pnpm dev
12
+ ```
13
+
14
+ Open [http://localhost:3000](http://localhost:3000).
15
+
16
+ To run it in Docker with the transcript directories mounted read-only:
17
+
18
+ ```bash
19
+ pnpm deploy:docker
20
+ ```
21
+
22
+ CI runs `pnpm lint` and `pnpm build` (which also type-checks) on every pull
23
+ request, so run both before opening one.
24
+
25
+ ## Demo data
26
+
27
+ To try the UI without your own transcripts (or to retake the screenshots),
28
+ generate a fake home directory with a few synthetic sessions and point the app
29
+ at it:
30
+
31
+ ```bash
32
+ node scripts/demo-data.mjs /tmp/asi-demo
33
+ HOME=/tmp/asi-demo pnpm dev
34
+ ```
35
+
36
+ ## Adding a provider
37
+
38
+ 1. Write `src/lib/providers/<name>.ts` exporting a `SessionProvider`: `info`,
39
+ `isAvailable`, `listSessions`, `getSession`, plus `listLogs`/`getLogContent`
40
+ (return empty when the agent has no log directory).
41
+ 2. Map its records onto the canonical event types; cap huge payloads before they
42
+ reach the client, and set `resultChars` on tool results so the token review
43
+ can size them. Attach `billedUsage` wherever the transcript reports usage so
44
+ the session can be priced.
45
+ 3. Register it in `src/lib/providers/index.ts` and add its id to
46
+ `ProviderId` in `types.ts`.
47
+ 4. Add its badge colours to `src/lib/provider-meta.ts`.
48
+
49
+ Routes are provider-scoped: `/sessions/<provider>/<id>` and
50
+ `/api/sessions/<provider>/<id>`.
51
+
52
+ ## Releasing
53
+
54
+ Change `version` in `package.json` and push to `main`. The
55
+ [release workflow](.github/workflows/release.yml) builds the app, publishes it
56
+ to npm, and creates the `vX.Y.Z` tag and GitHub release with
57
+ `agent-session-inspector.tar.gz` and `install.sh`, which is what the install
58
+ command downloads. A push that leaves the version alone releases nothing.
59
+
60
+ The workflow signs in to npm by
61
+ [trusted publishing](https://docs.npmjs.com/trusted-publishers), so the
62
+ repository holds no npm token. The package's settings on npmjs.com must list
63
+ this repository and `release.yml` as its trusted publisher.
64
+
65
+ `pnpm package` runs the same build locally into `dist/`. To try the result
66
+ without publishing:
67
+
68
+ ```bash
69
+ npx ./dist/agent-session-inspector
70
+ ASI_TARBALL_URL="file://$PWD/dist/agent-session-inspector.tar.gz" bash install.sh
71
+ ```
package/app/Dockerfile ADDED
@@ -0,0 +1,34 @@
1
+ # syntax=docker/dockerfile:1.7
2
+
3
+ FROM node:22-alpine AS base
4
+ ENV NEXT_TELEMETRY_DISABLED=1
5
+
6
+ FROM base AS deps
7
+ WORKDIR /app
8
+ RUN apk add --no-cache libc6-compat
9
+ COPY package.json pnpm-lock.yaml pnpm-workspace.yaml ./
10
+ RUN corepack enable && pnpm install --frozen-lockfile
11
+
12
+ FROM base AS builder
13
+ WORKDIR /app
14
+ RUN apk add --no-cache libc6-compat
15
+ COPY --from=deps /app/node_modules ./node_modules
16
+ COPY . .
17
+ RUN corepack enable && pnpm run build
18
+
19
+ FROM node:22-alpine AS runner
20
+ WORKDIR /app
21
+ ENV NODE_ENV=production
22
+ ENV NEXT_TELEMETRY_DISABLED=1
23
+
24
+ RUN addgroup -g 1001 -S nodejs && adduser -S nextjs -u 1001
25
+
26
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/standalone ./
27
+ COPY --from=builder --chown=nextjs:nodejs /app/.next/static ./.next/static
28
+
29
+ USER nextjs
30
+
31
+ EXPOSE 3000
32
+ ENV PORT=3000
33
+
34
+ CMD ["node", "server.js"]
package/app/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Kishan Mundha
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/app/README.md ADDED
@@ -0,0 +1,277 @@
1
+ # Agent Session Inspector
2
+
3
+ [![npm version](https://img.shields.io/npm/v/agent-session-inspector)](https://www.npmjs.com/package/agent-session-inspector)
4
+ [![CI](https://github.com/kishanmundha/agent-session-inspector/actions/workflows/ci.yml/badge.svg?branch=main)](https://github.com/kishanmundha/agent-session-inspector/actions/workflows/ci.yml)
5
+ [![node](https://img.shields.io/node/v/agent-session-inspector)](https://nodejs.org)
6
+ [![license](https://img.shields.io/npm/l/agent-session-inspector)](LICENSE)
7
+
8
+ A local web UI for reading coding-agent transcripts (Claude Code, Codex, GitHub
9
+ Copilot, OpenCode, Hermes): what the agent did, how long it took, and where the
10
+ tokens went.
11
+
12
+ > **Your sessions never leave your machine.** The app collects nothing: no
13
+ > telemetry, no analytics, no account, no cloud. It reads the transcript files
14
+ > already on your disk, read-only, and serves them to your own browser on
15
+ > `127.0.0.1`. See [Privacy](#privacy) for the details and how to check.
16
+
17
+ <picture>
18
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/kishanmundha/agent-session-inspector/main/docs/screenshots/session-overview-dark.png">
19
+ <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">
20
+ </picture>
21
+
22
+ ## Quick start
23
+
24
+ Run it with [Node.js](https://nodejs.org) 22.13 or newer, nothing to install:
25
+
26
+ ```bash
27
+ npx agent-session-inspector
28
+ ```
29
+
30
+ It serves on [http://localhost:3000](http://localhost:3000) (or the next free
31
+ port), bound to `127.0.0.1` only, and opens your browser. `--port`, `--host` and
32
+ `--no-open` change that; `--help` lists everything.
33
+
34
+ `npx agent-session-inspector@latest` picks up a new release. To keep the command
35
+ around instead, install it globally:
36
+
37
+ ```bash
38
+ npm install -g agent-session-inspector
39
+ ```
40
+
41
+ ### Install script
42
+
43
+ The same build is on GitHub Releases, with an installer that needs only Node,
44
+ `curl` and `tar`:
45
+
46
+ ```bash
47
+ curl -fsSL https://github.com/kishanmundha/agent-session-inspector/releases/latest/download/install.sh | bash
48
+ ```
49
+
50
+ It puts the app in `~/.agent-session-inspector` and links an
51
+ `agent-session-inspector` command into `~/.local/bin`. Re-run the install
52
+ command to update. To uninstall:
53
+
54
+ ```bash
55
+ rm -rf ~/.agent-session-inspector ~/.local/bin/agent-session-inspector
56
+ ```
57
+
58
+ The install script needs bash, so it covers macOS, Linux and WSL. On Windows
59
+ use `npx`.
60
+
61
+ ## Supported agents
62
+
63
+ | Agent | Reads from | Session titles | Logs |
64
+ | ----------------------------- | ---------------------------------------------- | --------------------- | ---- |
65
+ | GitHub Copilot CLI | `~/.copilot/session-state`, `~/.copilot/logs` | checkpoint DB | yes |
66
+ | GitHub Copilot Chat (VS Code) | `<Code>/User/workspaceStorage/*/chatSessions` | chat title | no |
67
+ | Claude Code | `~/.claude/projects/<project>/<session>.jsonl` | `custom`/`ai` titles | no |
68
+ | Claude Cowork | `<Claude>/local-agent-mode-sessions` | session descriptor | no |
69
+ | OpenAI Codex CLI | `~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl` | `session_index.jsonl` | no |
70
+ | OpenCode | `~/.local/share/opencode/opencode.db` | `session` table | no |
71
+ | Hermes Agent | `~/.hermes/state.db` | `sessions` table | no |
72
+
73
+ `<Code>` and `<Claude>` are the desktop apps' data folders: `~/Library/Application
74
+ Support/…` on macOS, `%APPDATA%\…` on Windows, `~/.config/…` on Linux. VS Code
75
+ Insiders is read too. The two SQLite stores are opened read-only.
76
+
77
+ Two things differ from the CLIs that log every request:
78
+
79
+ - **Copilot Chat** meters a whole request, and its prompt count is that of the
80
+ last model round. For a request that called tools, input tokens and cost are a
81
+ floor.
82
+ - **Hermes** counts tokens per session, so its usage appears as one checkpoint at
83
+ the end of the timeline rather than per message.
84
+
85
+ Whichever directories exist on the machine show up; the rest are hidden.
86
+
87
+ ## Privacy
88
+
89
+ Transcripts hold your prompts, code and file contents, so the app is built to
90
+ keep them on the machine. Nothing about you or your sessions is collected, by
91
+ this project or anyone else:
92
+
93
+ - **No data collection.** There is no telemetry, analytics, crash reporting,
94
+ account or sync, and Next.js's own telemetry is switched off.
95
+ - **No outbound requests.** The app reads the session files in place and talks
96
+ only to your browser. Fonts and the price table used for cost estimates are
97
+ bundled, not fetched. The one download is the app itself, when `npx` or the
98
+ install script fetches it.
99
+ - **Local only.** The server binds to `127.0.0.1`, so other machines on your
100
+ network cannot reach it unless you pass `--host`.
101
+ - **Read-only.** Session files are only read, and the SQLite stores are opened
102
+ read-only. The app never edits or deletes a transcript.
103
+
104
+ You don't have to take this on trust. The code is open, and the browser's
105
+ Network tab shows every request going to `localhost` and nowhere else; the app
106
+ works the same with the network disconnected.
107
+
108
+ ## Features
109
+
110
+ **Analytics across sessions.** Cost, messages, tool calls and active time for
111
+ the selected range, with an activity calendar and filters for project, agent
112
+ and model.
113
+
114
+ <picture>
115
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/kishanmundha/agent-session-inspector/main/docs/screenshots/analytics-dark.png">
116
+ <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">
117
+ </picture>
118
+
119
+ **All sessions in one place.** Every session of every supported agent on the
120
+ machine, filterable by agent and searchable by title, repo, branch or id.
121
+ Each card shows turns, tool calls, health grade, cost and tokens in/out at a
122
+ glance.
123
+
124
+ <picture>
125
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/kishanmundha/agent-session-inspector/main/docs/screenshots/sessions-dark.png">
126
+ <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">
127
+ </picture>
128
+
129
+ **Step-by-step timeline.** Prompts, reasoning, tool calls and their results in
130
+ order, with per-step token counts and cost. Expand any event to see its content, or open
131
+ the raw record.
132
+
133
+ <picture>
134
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/kishanmundha/agent-session-inspector/main/docs/screenshots/timeline-dark.png">
135
+ <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">
136
+ </picture>
137
+
138
+ **Token Optimizer.** What the session cost by token class, model and turn, where
139
+ the context went (system prompt vs. tool results vs. replies), which tools ran
140
+ most, and hints for trimming expensive sessions.
141
+
142
+ <picture>
143
+ <source media="(prefers-color-scheme: dark)" srcset="https://raw.githubusercontent.com/kishanmundha/agent-session-inspector/main/docs/screenshots/token-optimizer-dark.png">
144
+ <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">
145
+ </picture>
146
+
147
+ **Projects.** Sessions are grouped by the repository they ran in, so work
148
+ started from a subfolder or a git worktree lands in the same project. The
149
+ Projects tab lists each one with its sessions, cost and tokens, and picking a
150
+ project filters Analytics, Usage and Sessions together. Outside a repository,
151
+ the working directory is the project; the dated scratch folders the Codex app
152
+ creates for project-less chats count as one project.
153
+
154
+ **Activity.** One day at a time: how many sessions were working at once in each
155
+ 15-minute slot, the agent time and cost of each session, and the day's split by
156
+ project, model and agent.
157
+
158
+ **Edits per turn.** A session's Edits tab lists each prompt with the files the
159
+ agent wrote, edited or patched in response, linked to the event in the timeline.
160
+
161
+ **Skills and MCP tools.** The Analytics tab counts which skills were loaded and
162
+ which MCP servers and tools were called, for the selected range.
163
+
164
+ > Screenshots use synthetic demo data. To reproduce them locally, see
165
+ > [CONTRIBUTING.md](CONTRIBUTING.md#demo-data).
166
+
167
+ ## How it works
168
+
169
+ Every agent writes a different transcript format, so the app normalizes them into
170
+ one canonical event vocabulary and the UI only ever sees that.
171
+
172
+ ```
173
+ src/lib/providers/
174
+ types.ts canonical model: SessionMeta, AgentEvent, SessionDetail, SessionProvider
175
+ analysis.ts provider-agnostic stats + token-cost hints
176
+ pricing.ts dated price table lookup, per-event and per-session cost
177
+ prices.json bundled list prices, USD per million tokens
178
+ fs-utils.ts jsonl reading, directory walking, mtime-keyed caching
179
+ sqlite-utils.ts read-only access to agents that keep sessions in SQLite
180
+ copilot.ts ~/.copilot adapter
181
+ vscode.ts Copilot Chat in VS Code: replays the editor's chat change logs
182
+ claude.ts ~/.claude adapter
183
+ cowork.ts Claude desktop agent mode; reuses the Claude transcript parser
184
+ codex.ts ~/.codex adapter
185
+ opencode.ts OpenCode's opencode.db
186
+ hermes.ts Hermes Agent's state.db
187
+ index.ts registry: listSessions / getSession / listLogs across providers
188
+ ```
189
+
190
+ Events are typed `category.subCategory` — `user.message`, `assistant.message`,
191
+ `assistant.thinking`, `tool.execution_start`, `tool.execution_complete`,
192
+ `external_tool.requested`, `context.attachment`, `file.patch_applied`,
193
+ `web.search`, `session.*`. Anything an adapter emits outside that list still
194
+ renders through a generic card, so a new record type is never a crash.
195
+
196
+ Token accounting is centralized: adapters put per-message usage on the event
197
+ (`inputTokens` / `outputTokens` / `cacheReadTokens`) or, when a provider reports
198
+ running totals, attach `data.sessionTotals`. The latest `sessionTotals` wins over
199
+ summation.
200
+
201
+ ### Cost estimates
202
+
203
+ Each session, and each request in the timeline, shows an estimated cost in
204
+ dollars. Adapters attach `data.billedUsage` to the events that carry usage, with
205
+ uncached input, cache reads, cache writes and output kept as separate,
206
+ non-overlapping counts, and `pricing.ts` multiplies them by the model's list
207
+ price. The Token Optimizer tab splits the total by token class and by model.
208
+
209
+ The figure is an API-equivalent estimate, not a bill: subscription and
210
+ request-based plans charge differently, and requests an agent makes outside the
211
+ transcript (title generation, for example) are not counted.
212
+
213
+ Prices are never stored with a session; cost is computed on every load. To
214
+ correct a price, add a model the bundled table does not know, or record a price
215
+ change, create `~/.agent-session-inspector/pricing.json`. It is merged over
216
+ [`prices.json`](src/lib/providers/prices.json) per model and picked up without a
217
+ restart:
218
+
219
+ ```json
220
+ {
221
+ "my-local-model": { "input": 0, "output": 0 },
222
+ "claude-opus-5-5": [
223
+ {
224
+ "input": 4,
225
+ "output": 20,
226
+ "cacheRead": 0.2,
227
+ "cacheWrite": 5,
228
+ "cacheWrite1h": 8
229
+ },
230
+ {
231
+ "from": "2027-01-01",
232
+ "input": 3,
233
+ "output": 15,
234
+ "cacheRead": 0.15,
235
+ "cacheWrite": 3.75,
236
+ "cacheWrite1h": 6
237
+ }
238
+ ]
239
+ }
240
+ ```
241
+
242
+ A model with several entries is priced by date: each request uses the entry
243
+ whose `from` day is the latest one on or before the request, so a price change
244
+ does not rewrite the cost of older sessions. Models with no price show as
245
+ unpriced and are left out of the total rather than counted as free.
246
+
247
+ ### Session health
248
+
249
+ Every session gets a score out of 100 and a grade (A to F), shown on its card
250
+ and in its header. The score is rule-based and read straight off the transcript
251
+ by `health.ts`: it measures how smoothly the session ran, not whether the result
252
+ was any good. A session starts at 100 and loses points for:
253
+
254
+ | Signal | What it means | Points |
255
+ | ----------------- | ---------------------------------------------------- | -------------- |
256
+ | Unfinished | Ends on an API error, or stops without a final reply | 25 / 15 |
257
+ | Tool failures | Share of tool results that failed | up to 25 |
258
+ | Retry loops | The same tool failing three times in a row | 6 each, max 18 |
259
+ | API errors | Errors and retried requests | 2 each, max 10 |
260
+ | Interrupted turns | Turns stopped before the agent finished | 4 each, max 12 |
261
+ | Compactions | Context summarized mid-session | 3 each, max 9 |
262
+
263
+ 90 and up is an A, 80 a B, 70 a C, 60 a D. A session that is still running is not
264
+ marked down for being unfinished. Click the Health card on a session to see what
265
+ it lost points for and jump to those events; the Quality tab on the home page
266
+ shows grades, outcomes and the most common problems across sessions.
267
+
268
+ ## Contributing
269
+
270
+ Bug reports and pull requests are welcome; open an
271
+ [issue](https://github.com/kishanmundha/agent-session-inspector/issues) for anything that looks wrong, ideally with the
272
+ agent, OS and Node version. [CONTRIBUTING.md](CONTRIBUTING.md) covers running
273
+ from source, demo data, adding a provider and releasing.
274
+
275
+ ## License
276
+
277
+ [MIT](LICENSE) © Kishan Mundha
@@ -0,0 +1,159 @@
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 } 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
+ const HELP = `Usage: ${NAME} [options]
18
+
19
+ Browse Claude Code, Codex, Copilot, OpenCode and Hermes session transcripts in a
20
+ local web UI.
21
+
22
+ Options:
23
+ -p, --port <port> Port to listen on (default: 3000, or the next free one)
24
+ -H, --host <host> Interface to bind (default: 127.0.0.1)
25
+ --no-open Don't open the browser
26
+ -v, --version Print the version
27
+ -h, --help Show this help
28
+ `;
29
+
30
+ function fail(message) {
31
+ console.error(`${NAME}: ${message}`);
32
+ process.exit(1);
33
+ }
34
+
35
+ function parseArgs(argv) {
36
+ const opts = { port: null, host: "127.0.0.1", open: true };
37
+ for (let i = 0; i < argv.length; i++) {
38
+ const arg = argv[i];
39
+ const [flag, inline] = arg.startsWith("--") ? arg.split("=", 2) : [arg];
40
+ const value = () => inline ?? argv[++i] ?? fail(`${flag} needs a value`);
41
+ switch (flag) {
42
+ case "-p":
43
+ case "--port": {
44
+ const port = Number(value());
45
+ if (!Number.isInteger(port) || port < 1 || port > 65535) fail("invalid port");
46
+ opts.port = port;
47
+ break;
48
+ }
49
+ case "-H":
50
+ case "--host":
51
+ opts.host = value();
52
+ break;
53
+ case "--no-open":
54
+ opts.open = false;
55
+ break;
56
+ case "-v":
57
+ case "--version":
58
+ console.log(readVersion());
59
+ process.exit(0);
60
+ break;
61
+ case "-h":
62
+ case "--help":
63
+ console.log(HELP);
64
+ process.exit(0);
65
+ break;
66
+ default:
67
+ fail(`unknown option ${arg}\n\n${HELP}`);
68
+ }
69
+ }
70
+ return opts;
71
+ }
72
+
73
+ function readVersion() {
74
+ try {
75
+ return readFileSync(path.join(PKG_DIR, "VERSION"), "utf8").trim();
76
+ } catch {
77
+ return "dev";
78
+ }
79
+ }
80
+
81
+ function isFree(port, host) {
82
+ return new Promise((resolve) => {
83
+ const probe = net.createServer();
84
+ probe.once("error", () => resolve(false));
85
+ probe.listen(port, host, () => probe.close(() => resolve(true)));
86
+ });
87
+ }
88
+
89
+ async function pickPort(preferred, host) {
90
+ for (let port = preferred; port < preferred + 20; port++) {
91
+ if (await isFree(port, host)) return port;
92
+ }
93
+ fail(`no free port between ${preferred} and ${preferred + 19}; pass --port`);
94
+ }
95
+
96
+ async function waitUntilUp(url) {
97
+ for (let i = 0; i < 100; i++) {
98
+ try {
99
+ await fetch(url, { method: "HEAD" });
100
+ return true;
101
+ } catch {
102
+ await new Promise((r) => setTimeout(r, 100));
103
+ }
104
+ }
105
+ return false;
106
+ }
107
+
108
+ function openBrowser(url) {
109
+ const [cmd, args] =
110
+ process.platform === "darwin"
111
+ ? ["open", [url]]
112
+ : process.platform === "win32"
113
+ ? ["cmd", ["/c", "start", "", url]]
114
+ : ["xdg-open", [url]];
115
+ // No browser (ssh, container) is fine; the URL is already printed.
116
+ spawn(cmd, args, { stdio: "ignore", detached: true })
117
+ .on("error", () => {})
118
+ .unref();
119
+ }
120
+
121
+ const opts = parseArgs(process.argv.slice(2));
122
+
123
+ // npx only warns about package.json "engines", and the app needs node:sqlite.
124
+ const [major, minor] = process.versions.node.split(".").map(Number);
125
+ if (major < MIN_NODE[0] || (major === MIN_NODE[0] && minor < MIN_NODE[1])) {
126
+ fail(`Node.js ${MIN_NODE.join(".")} or newer is required (found ${process.version}).`);
127
+ }
128
+
129
+ if (!existsSync(SERVER)) {
130
+ fail(`no build found at ${SERVER}\nRun scripts/package-release.sh and use dist/${NAME}/bin/${NAME}.mjs.`);
131
+ }
132
+
133
+ let port = opts.port;
134
+ if (port === null) {
135
+ port = await pickPort(Number(process.env.PORT) || 3000, opts.host);
136
+ } else if (!(await isFree(port, opts.host))) {
137
+ fail(`port ${port} is already in use`);
138
+ }
139
+
140
+ process.env.NODE_ENV = "production";
141
+ process.env.NEXT_TELEMETRY_DISABLED = "1";
142
+ process.env.PORT = String(port);
143
+ process.env.HOSTNAME = opts.host;
144
+
145
+ // node:sqlite still announces itself as experimental on Node 22; not useful here.
146
+ const emitWarning = process.emitWarning;
147
+ process.emitWarning = (warning, ...rest) => {
148
+ if (String(warning).includes("SQLite is an experimental feature")) return;
149
+ emitWarning.call(process, warning, ...rest);
150
+ };
151
+
152
+ // server.js is CommonJS and starts listening on load.
153
+ createRequire(import.meta.url)(SERVER);
154
+
155
+ if (opts.open) {
156
+ const browseHost = opts.host === "0.0.0.0" || opts.host === "::" ? "localhost" : opts.host;
157
+ const url = `http://${browseHost.includes(":") ? `[${browseHost}]` : browseHost}:${port}`;
158
+ if (await waitUntilUp(url)) openBrowser(url);
159
+ }
@@ -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
@@ -0,0 +1,19 @@
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
+ "next-env.d.ts",
16
+ ]),
17
+ ]);
18
+
19
+ export default eslintConfig;