@easytocloud/sessionscope
v0.3.11
Published
Tail Claude Code JSONL sessions and stream SessionScope records over SSE.
Readme
@easytocloud/sessionscope
Tails Claude Code's JSONL session logs and streams SessionScope records
over SSE using @easytocloud/sessionscope-core to parse each
session — so the three views (Dialog / Graph / Timeline) in
@easytocloud/sessionscope-web light up against a real
~/.claude session instead of mock data.
src/server.js startServer({dir, port, capture, serveDir}) — chokidar tail +
incremental read + SSE broadcast + capture matching
src/tee.js startTee({port, capture, upstream}) — optional
ANTHROPIC_BASE_URL tee that writes the capture log
bin/sessionscope.js CLI: parses flags, calls startServer()
bin/tee.js CLI: parses flags, calls startTee()Both startServer and startTee are exported so a future
@easytocloud/sessionscope-vscode extension can start them in-process inside
a webview host, instead of shelling out to the CLI.
Run
npx @easytocloud/sessionscope # watches ~/.claude/projects, SSE on :4317
sessionscope --dir ~/.claude/projects --port 4317
sessionscope --serve ../web/dist # override with a custom-built UIThe published package already bundles a built @easytocloud/sessionscope-web
UI, served at / by default — no --serve flag needed. Pass --serve <dir>
to override it with a UI built from source instead. Open
http://localhost:4317/raw for the built-in raw feed (no build step, always
available). No real data yet? npm run fixture at the repo root, then
sessionscope --dir ./fixtures.
Captured payloads (the drawer's Reconstructed ↔ Captured diff)
The JSONL faithfully reconstructs the message body but not the request
envelope (tools schemas, cache_control placement, max_tokens,
anthropic-beta). Feed those from a proxy capture:
# terminal 1 — tee sits in front of the real API, logs request bodies by request-id
sessionscope-tee --capture ./capture.ndjson
export ANTHROPIC_BASE_URL=http://localhost:4318
# terminal 2 — server reads the same capture log
sessionscope --capture ./capture.ndjsonThe tee keys each body by the response request-id header, which is exactly
the requestId Claude Code records in the JSONL — so the matcher lines them
up per node. Already have your own BASE_URL interceptor? Just emit NDJSON
of { "requestId": "<response request-id>", "body": { ...request... } } to
the capture file and skip the tee.
Transparent capture (zero manual steps)
Skip the manual export/restore dance entirely. Run once per project:
sessionscope-tee-installThis installs SessionStart/SessionEnd hooks into .claude/settings.local.json.
From then on, just run claude normally in any terminal — the first session in
the project transparently starts a shared sessionscope-tee daemon and points
ANTHROPIC_BASE_URL at it; opening more terminals joins the same daemon
instead of starting another one; closing the last session stops the daemon and
restores ANTHROPIC_BASE_URL to whatever it was before (including removing it
entirely if it wasn't set). Subagents are captured automatically too — they run
in-process in the same claude invocation and inherit the same env, so there's
nothing extra to wire up.
Capture lands at .claude/sessionscope-tee/capture.ndjson (git-ignored
automatically); point the viewer at it:
sessionscope --capture .claude/sessionscope-tee/capture.ndjsonIf a session crashes without a clean exit, the next SessionStart detects the
dead daemon and recovers automatically. For a manual reset (force-stop the
daemon, restore settings) run sessionscope-tee-install --teardown.
Known limitation: background/remote agent dispatch does not reliably
inherit ANTHROPIC_BASE_URL from the gateway environment, so traffic from
those won't be captured by this mechanism.
Endpoints
| route | returns |
|---|---|
| GET /events[?session=<id>] | SSE: reset, then a record per node, plus meta |
| GET /snapshot[?session=<id>] | full current record set as JSON |
| GET /sessions | known session files, newest first, + the active one, each with a title |
| GET /record?session=<id>&uuid=<u>&blockIndex=<n> | full (untruncated) text for one content block |
| GET / | bundled web UI, or --serve <dir> override if set, else the raw feed |
| GET /raw | status page + live raw feed (always available) |
Default active session = most-recently-modified *.jsonl under --dir. Pin
one with ?session=<uuid>.
Every SessionScope node ships firstLine()-truncated previews only — the
full text of a prompt, tool input, or tool result is fetched on demand via
/record, keyed by the {uuid, blockIndex} ref the parser attaches to each
message/tool node. /sessions titles prefer Claude Code's own ai-title
record, falling back to the first real user prompt.
