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.
- 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 +3 -3
- 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 +14 -14
- package/app/.next/server/app/_not-found.segments/_full.segment.rsc +14 -14
- package/app/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +13 -13
- package/app/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- 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 +16 -16
- package/app/.next/server/app/index.segments/__PAGE__.segment.rsc +15 -15
- package/app/.next/server/app/index.segments/_full.segment.rsc +16 -16
- package/app/.next/server/app/index.segments/_tree.segment.rsc +2 -2
- 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/_0136j91._.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/10x4j1wts3xx2.js +1 -0
- package/app/.next/static/chunks/1dz8a9ds45tdb.js +6 -0
- package/app/.next/static/chunks/1g3ep2o7fhgpe.css +1 -0
- package/app/.next/static/chunks/2iedud09ou282.js +1 -0
- package/app/.next/static/chunks/2k65elb1vo-2z.js +1 -0
- package/app/.next/static/chunks/2t205sbe-_sk4.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 +71 -0
- package/app/Dockerfile +34 -0
- package/app/LICENSE +21 -0
- package/app/README.md +277 -0
- package/app/bin/agent-session-inspector.mjs +159 -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 +19 -0
- package/app/install.sh +85 -0
- package/app/next.config.ts +17 -0
- package/app/package.json +1 -1
- package/app/pnpm-lock.yaml +6760 -0
- package/app/pnpm-workspace.yaml +2 -0
- package/app/postcss.config.mjs +7 -0
- package/app/scripts/demo-data.mjs +288 -0
- package/app/scripts/deploy-docker.sh +67 -0
- package/app/scripts/package-release.sh +84 -0
- package/app/server.js +1 -1
- package/app/tsconfig.json +34 -0
- package/package.json +1 -1
- 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 → KFq9wlUt8U-Sma3McE5Jw}/_buildManifest.js +0 -0
- /package/app/.next/static/{NMXKYaysRrCyMjSEbGwmR → KFq9wlUt8U-Sma3McE5Jw}/_clientMiddlewareManifest.js +0 -0
- /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
|
+
[](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
|
+
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
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
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;
|