Outside-in web observer for Claude Code sessions and callstack call trees
Project description
unwind
Local web observer for Claude Code sessions. Run unwind in a project folder; a browser tab opens showing every Claude Code session that has run there, the call tree between forked sessions, and a live-updating view of each session's conversation.
unwind is an outside-in observer. It never wraps or intercepts Claude Code. It reads:
~/.claude/projects/<slug>/*.jsonl— Claude Code's own session logs<project>/.claude/callstack/log/— callstack plugin invocation reports
…and renders them in a web UI. Claude Code itself keeps running in your terminal, untouched.
What you get
- Left pane — every session in the project, newest first. Status dot (green = live, amber = idle, grey = done), title (first user prompt), relative time, message count, git branch.
- Middle pane — the call tree rooted at the selected session. Nested rows for callstack forks. Chevrons collapse/expand; each row shows task label, status badge, duration, short session id.
- Right pane — the thread for the selected session (or selected tree node). Markdown rendered, tool calls in collapsible cards pairing
tool_usewith its matchingtool_result, meta events togglable. - Live updates — new sessions, new messages, and new callstack children appear without a refresh, driven by a WebSocket fed from a
watchdogfilesystem observer. - Keyboard —
j/kstep through sessions,/focuses search. --all— project picker across every~/.claude/projects/entry.
Install
pip install unwind
From source:
git clone https://github.com/amolk/agent-callstack
cd agent-callstack/unwind
poetry install
cd web && npm install && npm run build && cd ..
poetry run unwind
Usage
cd /any/claude-project
unwind
A browser tab opens on an ephemeral 127.0.0.1 port with that project's sessions.
Flags:
| Flag | Purpose |
|---|---|
unwind [path] |
Serve a folder other than CWD. |
unwind --port 8765 |
Fix the port. |
unwind --no-browser |
Print the URL, don't open. |
unwind --all |
Project picker over every ~/.claude/projects/ dir. |
unwind --host 0.0.0.0 |
Bind beyond loopback (use with care — no auth). |
Dev loop
Two terminals:
# terminal 1 — Python backend with autoreload
poetry shell
UNWIND_DEFAULT_PATH=/some/project \
UNWIND_DEFAULT_SLUG=-some-project \
uvicorn unwind.server:create_app --factory --reload --port 8765
# terminal 2 — Vite dev server (proxies /api + WebSocket to :8765)
cd web && npm run dev
# open http://localhost:5173
Smoke test:
./dev/smoke.sh
Architecture
See dev/PRD.md and dev/PLAN.md for the full design. Short version:
- Backend (
src/unwind/): FastAPI app factory, per-projectSessionIndexwith mtime/size caching,CallstackIndexreadingreport.yaml,ProjectWatcher(watchdog) publishing typed events to a per-slugEventBus, WebSocket endpoint that fans out events to subscribers. - Frontend (
web/): Vite + React 18 + TypeScript + Tailwind v3 + hand-written shadcn primitives. TanStack Query for REST; a reconnecting WebSocket client patches the query cache so panes update without polling. zustand for UI state. - Packaging:
npm run buildinweb/emits directly intosrc/unwind/static/, which is included in the wheel automatically as package data. Onepip install= full app.
Scope / non-goals
unwind does not:
- run or control Claude Code
- send input to any session
- modify or delete any JSONL or callstack data
- talk to any remote service (binds to
127.0.0.1by default)
If you want compaction assist, session control, or forking UX, that belongs in Claude Code itself or in a plugin — unwind stays a read-only view.
Status
Early alpha. Tested only on macOS / Python 3.12. Windows is out of scope for v1. Known rough edges:
claudeprocess detection can't map a PID to a specific session id (Claude doesn't expose that), so status is scoped to the project. A session counts as "live" if its JSONL was modified in the last ~45s and anyclaudeprocess is running in the project cwd.- Very large JSONLs (tens of MB) load the whole thread in one shot. Pagination is a Phase 7 item.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file unwind_labs-0.1.0.tar.gz.
File metadata
- Download URL: unwind_labs-0.1.0.tar.gz
- Upload date:
- Size: 232.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
52a4157ec6dd259604e2ef87386d62fd9894c579ef5a39a2a00c02122b09a3b4
|
|
| MD5 |
b0c20be2b36cfa4e7b2bc9d0c83c07a6
|
|
| BLAKE2b-256 |
429b288a53d55c674e04f83f54ac81c095f11cef64be86e2c26290acf665792c
|
Provenance
The following attestation bundles were made for unwind_labs-0.1.0.tar.gz:
Publisher:
publish.yaml on unwind-labs/unwind
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
unwind_labs-0.1.0.tar.gz -
Subject digest:
52a4157ec6dd259604e2ef87386d62fd9894c579ef5a39a2a00c02122b09a3b4 - Sigstore transparency entry: 1399343792
- Sigstore integration time:
-
Permalink:
unwind-labs/unwind@ce7a8b8f06d25151c1abe946a60fc5af08c2c1e8 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/unwind-labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@ce7a8b8f06d25151c1abe946a60fc5af08c2c1e8 -
Trigger Event:
push
-
Statement type:
File details
Details for the file unwind_labs-0.1.0-py3-none-any.whl.
File metadata
- Download URL: unwind_labs-0.1.0-py3-none-any.whl
- Upload date:
- Size: 240.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
d4fe3b940c5dc5ea72fe9bb5f38006a57642d92a56401952a3a95cdcbd040b59
|
|
| MD5 |
cb1968b460987b3af80a1ab27d8b1731
|
|
| BLAKE2b-256 |
1178fb3df6a558796dd490ddf011e8981359e4a5d88bcc88e7355d31535a01e7
|
Provenance
The following attestation bundles were made for unwind_labs-0.1.0-py3-none-any.whl:
Publisher:
publish.yaml on unwind-labs/unwind
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
unwind_labs-0.1.0-py3-none-any.whl -
Subject digest:
d4fe3b940c5dc5ea72fe9bb5f38006a57642d92a56401952a3a95cdcbd040b59 - Sigstore transparency entry: 1399343798
- Sigstore integration time:
-
Permalink:
unwind-labs/unwind@ce7a8b8f06d25151c1abe946a60fc5af08c2c1e8 -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/unwind-labs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yaml@ce7a8b8f06d25151c1abe946a60fc5af08c2c1e8 -
Trigger Event:
push
-
Statement type: