@zzusp/ccsm 1.0.1 → 1.0.3

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 (70) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +236 -236
  3. package/bin/cli.mjs +52 -52
  4. package/dist/assets/{DiskUsage-CKhggLs5.js → DiskUsage-BY6XwffG.js} +2 -2
  5. package/dist/assets/DiskUsage-BY6XwffG.js.map +1 -0
  6. package/dist/assets/{ImportPage-wge4VhZ-.js → ImportPage-Cwq5bx7G.js} +2 -2
  7. package/dist/assets/ImportPage-Cwq5bx7G.js.map +1 -0
  8. package/dist/assets/MarkdownContent-BFu7Nkk_.js +2 -0
  9. package/dist/assets/MarkdownContent-BFu7Nkk_.js.map +1 -0
  10. package/dist/assets/{ProjectMemory-Q4XX40j_.js → ProjectMemory-CcE3KbUK.js} +2 -2
  11. package/dist/assets/ProjectMemory-CcE3KbUK.js.map +1 -0
  12. package/dist/assets/index-CrWxV6sb.css +1 -0
  13. package/dist/assets/index-DTbWl1jb.js +11 -0
  14. package/dist/assets/index-DTbWl1jb.js.map +1 -0
  15. package/dist/assets/markdown-Bag5rX3T.js +30 -0
  16. package/dist/assets/markdown-Bag5rX3T.js.map +1 -0
  17. package/dist/index.html +26 -26
  18. package/package.json +81 -83
  19. package/server/index.ts +130 -130
  20. package/server/lib/active-sessions.test.ts +119 -119
  21. package/server/lib/active-sessions.ts +95 -95
  22. package/server/lib/bundle.test.ts +182 -182
  23. package/server/lib/bundle.ts +86 -86
  24. package/server/lib/claude-paths.test.ts +126 -126
  25. package/server/lib/claude-paths.ts +43 -43
  26. package/server/lib/cleanup-suggestions.ts +131 -131
  27. package/server/lib/constants.ts +8 -8
  28. package/server/lib/delete-project.ts +100 -100
  29. package/server/lib/delete.test.ts +244 -244
  30. package/server/lib/delete.ts +192 -192
  31. package/server/lib/disk-usage.ts +81 -81
  32. package/server/lib/encode-cwd.ts +24 -24
  33. package/server/lib/export-bundle.ts +236 -236
  34. package/server/lib/export-import-bundle.test.ts +337 -337
  35. package/server/lib/fs-size.ts +38 -38
  36. package/server/lib/import-bundle.ts +488 -488
  37. package/server/lib/load-memory.ts +120 -120
  38. package/server/lib/load-session.ts +209 -209
  39. package/server/lib/modified-files.test.ts +280 -280
  40. package/server/lib/modified-files.ts +228 -228
  41. package/server/lib/open-folder.ts +47 -47
  42. package/server/lib/parse-jsonl.ts +160 -139
  43. package/server/lib/port.ts +23 -23
  44. package/server/lib/safe-id.test.ts +41 -41
  45. package/server/lib/safe-id.ts +6 -6
  46. package/server/lib/safe-remove.test.ts +73 -73
  47. package/server/lib/safe-remove.ts +25 -25
  48. package/server/lib/scan.ts +289 -286
  49. package/server/lib/search-all.ts +130 -130
  50. package/server/lib/search-session.ts +203 -203
  51. package/server/lib/system-tags.ts +20 -20
  52. package/server/lib/update.ts +67 -67
  53. package/server/lib/version.test.ts +39 -39
  54. package/server/lib/version.ts +117 -117
  55. package/server/routes/disk-cleanup.ts +54 -54
  56. package/server/routes/disk.ts +9 -9
  57. package/server/routes/import.ts +87 -87
  58. package/server/routes/projects.ts +104 -104
  59. package/server/routes/search.ts +79 -79
  60. package/server/routes/sessions.ts +130 -130
  61. package/server/routes/version.ts +34 -34
  62. package/server/types.ts +1 -1
  63. package/shared/constants.ts +7 -7
  64. package/shared/types.ts +513 -511
  65. package/dist/assets/DiskUsage-CKhggLs5.js.map +0 -1
  66. package/dist/assets/ImportPage-wge4VhZ-.js.map +0 -1
  67. package/dist/assets/ProjectMemory-Q4XX40j_.js.map +0 -1
  68. package/dist/assets/index-7aMrnHJG.js +0 -7
  69. package/dist/assets/index-7aMrnHJG.js.map +0 -1
  70. package/dist/assets/index-BOeI_J4B.css +0 -1
package/LICENSE CHANGED
@@ -1,21 +1,21 @@
1
- MIT License
2
-
3
- Copyright (c) 2026 Aaron Sun
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.
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aaron Sun
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/README.md CHANGED
@@ -1,236 +1,236 @@
1
- # Claude Code Session Manager
2
-
3
- A local web UI to view and clean up Claude Code session history stored under `~/.claude/`.
4
-
5
- > **Read-only on disk by default.** The server only writes when you explicitly act in the UI — *Delete* a session, *Export* a bundle (written to a folder **outside** `~/.claude/`), or *Import* one. Active sessions (with a live PID or recent activity) are never deleted or overwritten.
6
-
7
- ## Screenshots
8
-
9
- > See [`docs/screenshots/README.md`](docs/screenshots/README.md) for the capture recipe (Windows / macOS / Linux) if you want to refresh these.
10
-
11
- <p align="center">
12
- <a href="docs/screenshots/project-detail.png"><img src="docs/screenshots/project-detail.png" alt="Project detail — session table with multi-select and live/idle badges" width="720"></a>
13
- <br><sub><b>Project detail</b> — sessions in one project, multi-select, status badges</sub>
14
- </p>
15
-
16
- <p align="center">
17
- <a href="docs/screenshots/session-detail.png"><img src="docs/screenshots/session-detail.png" alt="Session detail — message timeline with a highlighted search hit" width="720"></a>
18
- <br><sub><b>Session detail</b> — full message timeline with search highlight</sub>
19
- </p>
20
-
21
- <p align="center">
22
- <a href="docs/screenshots/disk-usage.png"><img src="docs/screenshots/disk-usage.png" alt="Disk usage — pie chart, monthly bar chart, top-20 sessions table" width="720"></a>
23
- <br><sub><b>Disk usage</b> — pie + monthly bars + top-20 table</sub>
24
- </p>
25
-
26
- ## Features
27
-
28
- | Page | What you can do |
29
- |---|---|
30
- | **Projects** (`/`) | See every Claude Code project (one per `cwd`) with session count, total bytes on disk, last-activity time. |
31
- | **Project detail** (`/projects/:id`) | Browse all sessions in one project. Multi-select + cascade-delete. Each row shows title, message count, byte breakdown, and a status badge (`working` / `live · pid N` / `recently active` / `idle`) — *working* narrows *live* to a session Claude is actively processing right now (live PID + an unfinished last turn). Inline rename appends a `custom-title` record to the session's `.jsonl` (refused while a live PID owns the session). *Open folder* reveals the project's working directory in the OS file manager (Explorer / Finder / `xdg-open`). *Export* bundles the selected sessions (or all) plus the project's memory into a portable folder. |
32
- | **Import** (`/import`) | Bring a project's sessions + memory from another device. The bundle is path-independent; on import you pick the local target folder and paths are remapped to this machine. A dry-run preview shows exactly what will be created / skipped / overwritten before any write. See [Cross-device sharing](#cross-device-sharing). |
33
- | **Session detail** (`/projects/:id/sessions/:sid`) | Full message timeline: text, tool calls (collapsible), tool results, thinking blocks. Sticky search bar with client-side highlight. Toggle to show or hide system messages (`<command-name>` etc.). Inline *Delete* (top-right of the masthead) removes the current session and returns to the project list. While the session is live the timeline auto-tails newly written messages; while Claude is actively processing, a *working* masthead badge and a trailing “Claude is working…” row mark the open turn. |
34
- | **Project memory** (`/projects/:id/memory`) | Two-pane reader for `~/.claude/projects/<encoded-cwd>/memory/`: searchable file list (sort by index / recent / name / size) on the left, rendered Markdown on the right, with `MEMORY.md` pinned as the index. |
35
- | **Disk usage** (`/disk`) | Pie chart by project, monthly bar chart, top-20 largest sessions with deep links. |
36
- | **Cross-session search** (⌘K / Ctrl+K) | Global modal that streams matches from every project as you type. Searches text, tool calls, and thinking blocks; each result deep-links into the session at the matched message. |
37
-
38
- The persistent sidebar carries the search trigger plus locale (zh / en) and theme (light / dark) toggles, and a **version indicator** at the bottom. On open it checks GitHub for the latest release (cached for an hour); when a newer version exists it shows an amber "new version" pill. Clicking it opens a dialog with the release notes and links to the release page and repository, plus a one-click **Update now** that runs `npm install -g @zzusp/ccsm@latest` on the server and prompts you to restart `ccsm`. If the check fails (offline, or no GitHub release published yet) it silently falls back to showing just the current version. The HTTP listener exposes the same content as a single SPA, so deep links like `/projects/:id/sessions/:sid?q=foo` are sharable between browser tabs on the same machine.
39
-
40
- ## Quick start
41
-
42
- Requires **Node 22+** (Node 24 recommended).
43
-
44
- ```bash
45
- git clone <this-repo>
46
- cd claude-code-session
47
- npm install
48
- npm run dev
49
- ```
50
-
51
- Open <http://localhost:5173>.
52
-
53
- | Script | Effect |
54
- |---|---|
55
- | `npm run dev` | Concurrent backend (`:3131`) + Vite dev server (`:5173`) with HMR. Vite proxies `/api/*` to the backend. |
56
- | `npm run build` | Builds the SPA to `dist/`. |
57
- | `npm run start` | Single-process production mode: backend on `:3131` serves `dist/` + the API. |
58
- | `npm run typecheck` | `tsc -b` over both server and web projects. |
59
-
60
- The HTTP server binds to `127.0.0.1` only — never reachable from the LAN.
61
- Default port is `3131`. If it's busy, the server tries `3132 … 3140` and prints the actual port to stdout.
62
-
63
- ## Install as a CLI
64
-
65
- Published to npm, the manager installs as a single command — `ccsm` — that starts the
66
- production server (the built SPA + the API in one process, no separate Vite dev server).
67
-
68
- ```bash
69
- # Run once, without installing
70
- npx @zzusp/ccsm
71
-
72
- # …or install globally and run `ccsm` from anywhere
73
- npm install -g @zzusp/ccsm
74
- ccsm
75
- ```
76
-
77
- It prints the URL it picked (e.g. <http://127.0.0.1:3131>); open that. Requires **Node 22+**.
78
-
79
- | Flag | Effect |
80
- |---|---|
81
- | `-p, --port <number>` | Port to listen on. Default: first free port in `3131 … 3140`. If the given port is busy, `ccsm` exits — it won't silently pick another. |
82
- | `--host <host>` | Host to bind. Default `127.0.0.1` (loopback only). Pass `0.0.0.0` to expose the UI on your LAN — **no authentication**, see [Security model](#security-model). |
83
- | `-o, --open` | Open the UI in your default browser once the server is listening. |
84
- | `-h, --help` | Show usage and exit. |
85
- | `-v, --version` | Print the version and exit. |
86
-
87
- ```bash
88
- ccsm --port 4000 --open # custom port, then pop open the browser
89
- ccsm --host 0.0.0.0 # expose on the LAN (trusted networks only)
90
- ```
91
-
92
- **Install from a local checkout** (without publishing to npm):
93
-
94
- ```bash
95
- git clone <this-repo> && cd claude-code-session
96
- npm install
97
- npm run build # produce dist/ — the CLI serves it
98
- npm install -g . # or: npm link — registers the `ccsm` command
99
- ccsm
100
- ```
101
-
102
- ## What gets deleted
103
-
104
- Claude Code stores session data across **five** locations under `~/.claude/`. Deleting a session here cleans up all of them in one shot:
105
-
106
- | Path | Contents |
107
- |---|---|
108
- | `projects/<encoded-cwd>/<sessionId>.jsonl` | Main message stream |
109
- | `projects/<encoded-cwd>/<sessionId>/` | Sub-agent threads + per-session memory |
110
- | `file-history/<sessionId>/` | Snapshots of every file Claude edited in this session (often the largest contributor) |
111
- | `session-env/<sessionId>/` | Environment snapshots |
112
- | `history.jsonl` | Lines whose `sessionId` matches are stripped via atomic rewrite (backup → tmp → rename) |
113
- | `sessions/<pid>.json` | Removed only when the PID has actually exited |
114
-
115
- **Safety rails (a session is *skipped*, not deleted, when):**
116
- - Its `sessionId` appears in a `sessions/<pid>.json` file whose PID is alive (`process.kill(pid, 0)` on Unix, `tasklist` on Windows).
117
- - Its `.jsonl` was modified within the last 5 minutes (could still be in use after a `/clear` even if the PID file points elsewhere).
118
- - The decoded path escapes `~/.claude/` (path-traversal guard).
119
- - The session id contains `/`, `\`, `..`, or starts with `.`.
120
-
121
- The UI's confirmation dialog shows the exact files and bytes that will be removed *and* lists which selections will be skipped and why.
122
-
123
- ## Cross-device sharing
124
-
125
- Export/import move a project's **memory + conversation history** between machines, accounting for the fact that the same project lives at a different absolute path on each device.
126
-
127
- **The path problem.** A project id is just the encoded `cwd`, and the real path is recorded *inside* the data — as `cwd` on every session `.jsonl` line and as `project` on every matching `history.jsonl` line. A bundle is therefore made **path-independent** at export: those two fields are replaced with a `${CLAUDE_PROJECT_ROOT}` sentinel. On import you choose the local target folder and the sentinel is substituted with that path. Message bodies, `gitBranch`, and `version` are **never** rewritten — they're an archival record.
128
-
129
- **Bundle (a plain folder you can copy, or keep in a git repo / cloud drive):**
130
-
131
- ```
132
- <bundle>/
133
- manifest.json # schema, source platform + cwd, per-file sha256
134
- memory/ # MEMORY.md + *.md, copied verbatim
135
- sessions/<sid>/
136
- conversation.jsonl # cwd -> ${CLAUDE_PROJECT_ROOT}
137
- history.ndjson # project -> ${CLAUDE_PROJECT_ROOT}
138
- ```
139
-
140
- Scope is the **core tier**: memory, conversations, and the matching `history.jsonl` lines (all path-portable). `file-history/`, `session-env/`, and the session subdir are intentionally excluded — they embed source-device absolute paths and aren't portable.
141
-
142
- **Import is safe by construction.** It reuses the same guards as delete: paths are validated under `~/.claude/`, a session that is live or was modified in the last 5 minutes is never overwritten, files are written via tmp→rename, and the `history.jsonl` merge is an atomic backup→tmp→rename append with de-duplication (so re-importing the same bundle is a no-op). When a session id already exists you choose **skip** (default), **overwrite if newer**, or **keep both** (imports under a fresh id).
143
-
144
- ## Architecture
145
-
146
- ```
147
- shared/ Wire types + constants imported by BOTH server and web.
148
- server/ Hono + @hono/node-server backend, all filesystem operations.
149
- lib/ claude-paths, encode-cwd, scan, parse-jsonl, load-session,
150
- load-memory, rename-session, search-all, search-session,
151
- active-sessions, delete, bundle, export-bundle, import-bundle,
152
- disk-usage, fs-size, safe-id, system-tags, port, …
153
- routes/ projects, sessions, disk, search, import
154
- web/ React 19 + Vite + Tailwind v4 SPA.
155
- src/routes/ ProjectsList, ProjectDetail, SessionDetail,
156
- ProjectMemory, DiskUsage (lazy), ImportPage (lazy)
157
- src/components Sidebar, SearchModal, PageHeader, MessageBubble,
158
- ToolBlock, DeleteDialog, ExportDialog, HighlightedText, …
159
- src/lib api, query-keys, i18n, theme, hotkeys, format
160
- docs/spec/ Design notes (start here before refactoring).
161
- docs/acceptance/ Per-feature e2e plans, round evidence, retrospectives.
162
- ```
163
-
164
- ### HTTP API
165
-
166
- | Method | Path | Purpose |
167
- |---|---|---|
168
- | `GET` | `/api/health` | `claudeRoot`, platform, pid — used by the UI's empty-state banner. |
169
- | `GET` | `/api/projects` | Project summaries (one per `cwd`). |
170
- | `GET` | `/api/projects/:id/sessions` | Session list for a project. |
171
- | `GET` | `/api/projects/:id/memory` | Memory file index + content for the two-pane reader. |
172
- | `GET` | `/api/sessions/:projectId/:sessionId` | Full parsed message timeline. |
173
- | `PATCH` | `/api/sessions/:projectId/:sessionId` | Rename a session by appending a `custom-title` record to the `.jsonl`. |
174
- | `DELETE` | `/api/sessions` | Cascade-delete one or more sessions; CSRF-checked via `Origin`. |
175
- | `POST` | `/api/projects/:id/export` | Write a path-independent bundle (memory + conversations) to a folder outside `~/.claude/`; CSRF-checked. |
176
- | `POST` | `/api/import/preview` | Dry run: remap plan, per-session/memory actions, and history-line count — writes nothing; CSRF-checked. |
177
- | `POST` | `/api/import` | Commit an import into `~/.claude/` (atomic, never overwrites a live/recent session); CSRF-checked. |
178
- | `GET` | `/api/disk-usage` | Per-project totals + monthly buckets + top-N sessions. |
179
- | `GET` | `/api/search?q=...` | NDJSON stream of matches across every project. |
180
-
181
- | Layer | Tech |
182
- |---|---|
183
- | Backend runtime | Node 22+ + `tsx` (TS direct execution) |
184
- | HTTP | Hono + `@hono/node-server` |
185
- | Frontend | React 19 + Vite 6 + Tailwind v4 + TanStack Query + React Router 7 |
186
- | Charts | Recharts (lazy-loaded only on `/disk`) |
187
-
188
- The DiskUsage page is code-split, so the initial bundle is **~124 KB gzipped**. Recharts (~80 KB gzipped) loads on demand when you navigate to `/disk`.
189
-
190
- ## Cross-platform
191
-
192
- | OS | `~/.claude/` resolves to | `projects/` subdir naming |
193
- |---|---|---|
194
- | macOS / Linux | `$HOME/.claude/` | `/foo/bar` → `-foo-bar` |
195
- | Windows | `C:\Users\<you>\.claude\` | `C:\foo\bar` → `C--foo-bar` |
196
-
197
- Decoding a project id back to a real path uses each session's own `cwd` field (recorded inside the `.jsonl`) when available; otherwise it falls back to a heuristic decode and stat-checks the result. Projects whose decoded path no longer exists on disk are flagged in the UI.
198
-
199
- ## Troubleshooting
200
-
201
- **Port 3131 busy.** The server auto-picks the next free port up to 3140 and prints it on startup. Look at the `[server] listening on http://127.0.0.1:<port>` line.
202
-
203
- **`Claude root .../.claude doesn't exist`.** The Projects page shows an amber banner with the resolved path. Either Claude Code hasn't been run on this machine, or `$HOME` resolves somewhere unexpected. Check `node -e 'console.log(require("os").homedir())'`.
204
-
205
- **Delete dialog says my current session is skipped.** Expected — your live Claude Code process has its `sessionId` registered in `~/.claude/sessions/<pid>.json` *or* the `.jsonl` was just modified. Wait 5+ minutes after closing Claude, then try again.
206
-
207
- **`history.jsonl.bak-<timestamp>` left behind.** Means the swap rename failed mid-flight. The original is intact (the backup *is* the original). Compare both files and either `mv backup history.jsonl` to restore or `rm` the backup if the new one looks fine.
208
-
209
- **Windows: delete fails with `EBUSY`.** Some Windows AV / indexer can hold a lock on `.jsonl` files. Close any text editors that have the file open and retry.
210
-
211
- **Browser can't reach the server.** The bind is `127.0.0.1`, not `0.0.0.0`. Make sure you're using `http://localhost:3131` from the same machine — not a LAN IP.
212
-
213
- ## Security model
214
-
215
- This tool is intended for a single user on their own machine. It is *not* hardened for multi-user / shared environments.
216
-
217
- - The HTTP listener binds to `127.0.0.1` only by default — never reachable from the LAN. The CLI's `--host` flag can override this (e.g. `--host 0.0.0.0`); doing so exposes the UI on your network with **no authentication**, so anyone who can reach the host:port can read and delete your Claude Code history. The server prints a warning on startup whenever it binds to a non-loopback host. Only do this on a network you trust.
218
- - Mutating endpoints (`DELETE /api/sessions`, `PATCH /api/sessions/:projectId/:sessionId`, `POST /api/projects/:id/export`, `POST /api/import` and its `/preview`) require an `Origin` header matching `http(s)://(localhost|127.0.0.1):*`. This blocks other web pages your browser opens from triggering writes via CSRF, but cannot stop another local process running as the same user.
219
- - *Export* refuses to write inside `~/.claude/`; *import* validates every destination under `~/.claude/`, skips any live/recently-active session, and writes atomically — the same safety rails as delete.
220
- - All filesystem paths are validated with `path.resolve(...).startsWith(claudeRoot)` (Windows-aware case-folded) before any read or write.
221
- - IDs from URL params are rejected if they contain `/`, `\`, `..`, or start with `.`.
222
- - The cross-session search endpoint streams NDJSON and aborts when the client disconnects, so a closed browser tab stops the scan immediately.
223
-
224
- There is no authentication. If you're paranoid, run behind a firewall rule or only invoke the tool when needed.
225
-
226
- ## Project layout
227
-
228
- See [`docs/spec/session-manager-design.md`](docs/spec/session-manager-design.md) for the full design rationale (data model, routing decisions, cross-platform strategy, future work).
229
-
230
- ## Contributing & releases
231
-
232
- Commits follow [Conventional Commits](https://www.conventionalcommits.org/) (enforced by a `commit-msg` hook), versions follow [SemVer](https://semver.org/), and releases are cut locally with `release-it`. See [CONTRIBUTING.md](CONTRIBUTING.md) for the workflow and [docs/spec/release-process.md](docs/spec/release-process.md) for the full policy.
233
-
234
- ## License
235
-
236
- MIT — see [LICENSE](LICENSE).
1
+ # Claude Code Session Manager
2
+
3
+ A local web UI to view and clean up Claude Code session history stored under `~/.claude/`.
4
+
5
+ > **Read-only on disk by default.** The server only writes when you explicitly act in the UI — *Delete* a session, *Export* a bundle (written to a folder **outside** `~/.claude/`), or *Import* one. Active sessions (with a live PID or recent activity) are never deleted or overwritten.
6
+
7
+ ## Screenshots
8
+
9
+ > See [`docs/screenshots/README.md`](docs/screenshots/README.md) for the capture recipe (Windows / macOS / Linux) if you want to refresh these.
10
+
11
+ <p align="center">
12
+ <a href="docs/screenshots/project-detail.png"><img src="docs/screenshots/project-detail.png" alt="Project detail — session table with multi-select and live/idle badges" width="720"></a>
13
+ <br><sub><b>Project detail</b> — sessions in one project, multi-select, status badges</sub>
14
+ </p>
15
+
16
+ <p align="center">
17
+ <a href="docs/screenshots/session-detail.png"><img src="docs/screenshots/session-detail.png" alt="Session detail — message timeline with a highlighted search hit" width="720"></a>
18
+ <br><sub><b>Session detail</b> — full message timeline with search highlight</sub>
19
+ </p>
20
+
21
+ <p align="center">
22
+ <a href="docs/screenshots/disk-usage.png"><img src="docs/screenshots/disk-usage.png" alt="Disk usage — pie chart, monthly bar chart, top-20 sessions table" width="720"></a>
23
+ <br><sub><b>Disk usage</b> — pie + monthly bars + top-20 table</sub>
24
+ </p>
25
+
26
+ ## Features
27
+
28
+ | Page | What you can do |
29
+ |---|---|
30
+ | **Projects** (`/`) | See every Claude Code project (one per `cwd`) with session count, total bytes on disk, last-activity time. |
31
+ | **Project detail** (`/projects/:id`) | Browse all sessions in one project. Multi-select + cascade-delete. Each row shows title, message count, byte breakdown, and a status badge (`working` / `live · pid N` / `recently active` / `idle`) — *working* narrows *live* to a session Claude is actively processing right now (live PID + an unfinished last turn). Inline rename appends a `custom-title` record to the session's `.jsonl` (refused while a live PID owns the session). *Open folder* reveals the project's working directory in the OS file manager (Explorer / Finder / `xdg-open`). *Export* bundles the selected sessions (or all) plus the project's memory into a portable folder. |
32
+ | **Import** (`/import`) | Bring a project's sessions + memory from another device. The bundle is path-independent; on import you pick the local target folder and paths are remapped to this machine. A dry-run preview shows exactly what will be created / skipped / overwritten before any write. See [Cross-device sharing](#cross-device-sharing). |
33
+ | **Session detail** (`/projects/:id/sessions/:sid`) | Full message timeline: text, tool calls (collapsible), tool results, thinking blocks. Sticky search bar with client-side highlight. Toggle to show or hide system messages (`<command-name>` etc.). Inline *Delete* (top-right of the masthead) removes the current session and returns to the project list. While the session is live the timeline auto-tails newly written messages; while Claude is actively processing, a *working* masthead badge and a trailing “Claude is working…” row mark the open turn. |
34
+ | **Project memory** (`/projects/:id/memory`) | Two-pane reader for `~/.claude/projects/<encoded-cwd>/memory/`: searchable file list (sort by index / recent / name / size) on the left, rendered Markdown on the right, with `MEMORY.md` pinned as the index. |
35
+ | **Disk usage** (`/disk`) | Pie chart by project, monthly bar chart, top-20 largest sessions with deep links. |
36
+ | **Cross-session search** (⌘K / Ctrl+K) | Global modal that streams matches from every project as you type. Searches text, tool calls, and thinking blocks; each result deep-links into the session at the matched message. |
37
+
38
+ The persistent sidebar carries the search trigger plus locale (zh / en) and theme (light / dark) toggles, and a **version indicator** at the bottom. On open it checks GitHub for the latest release (cached for an hour); when a newer version exists it shows an amber "new version" pill. Clicking it opens a dialog with the release notes and links to the release page and repository, plus a one-click **Update now** that runs `npm install -g @zzusp/ccsm@latest` on the server and prompts you to restart `ccsm`. If the check fails (offline, or no GitHub release published yet) it silently falls back to showing just the current version. The HTTP listener exposes the same content as a single SPA, so deep links like `/projects/:id/sessions/:sid?q=foo` are sharable between browser tabs on the same machine.
39
+
40
+ ## Quick start
41
+
42
+ Requires **Node 22+** (Node 24 recommended).
43
+
44
+ ```bash
45
+ git clone <this-repo>
46
+ cd claude-code-session
47
+ npm install
48
+ npm run dev
49
+ ```
50
+
51
+ Open <http://localhost:5173>.
52
+
53
+ | Script | Effect |
54
+ |---|---|
55
+ | `npm run dev` | Concurrent backend (`:3131`) + Vite dev server (`:5173`) with HMR. Vite proxies `/api/*` to the backend. |
56
+ | `npm run build` | Builds the SPA to `dist/`. |
57
+ | `npm run start` | Single-process production mode: backend on `:3131` serves `dist/` + the API. |
58
+ | `npm run typecheck` | `tsc -b` over both server and web projects. |
59
+
60
+ The HTTP server binds to `127.0.0.1` only — never reachable from the LAN.
61
+ Default port is `3131`. If it's busy, the server tries `3132 … 3140` and prints the actual port to stdout.
62
+
63
+ ## Install as a CLI
64
+
65
+ Published to npm, the manager installs as a single command — `ccsm` — that starts the
66
+ production server (the built SPA + the API in one process, no separate Vite dev server).
67
+
68
+ ```bash
69
+ # Run once, without installing
70
+ npx @zzusp/ccsm
71
+
72
+ # …or install globally and run `ccsm` from anywhere
73
+ npm install -g @zzusp/ccsm
74
+ ccsm
75
+ ```
76
+
77
+ It prints the URL it picked (e.g. <http://127.0.0.1:3131>); open that. Requires **Node 22+**.
78
+
79
+ | Flag | Effect |
80
+ |---|---|
81
+ | `-p, --port <number>` | Port to listen on. Default: first free port in `3131 … 3140`. If the given port is busy, `ccsm` exits — it won't silently pick another. |
82
+ | `--host <host>` | Host to bind. Default `127.0.0.1` (loopback only). Pass `0.0.0.0` to expose the UI on your LAN — **no authentication**, see [Security model](#security-model). |
83
+ | `-o, --open` | Open the UI in your default browser once the server is listening. |
84
+ | `-h, --help` | Show usage and exit. |
85
+ | `-v, --version` | Print the version and exit. |
86
+
87
+ ```bash
88
+ ccsm --port 4000 --open # custom port, then pop open the browser
89
+ ccsm --host 0.0.0.0 # expose on the LAN (trusted networks only)
90
+ ```
91
+
92
+ **Install from a local checkout** (without publishing to npm):
93
+
94
+ ```bash
95
+ git clone <this-repo> && cd claude-code-session
96
+ npm install
97
+ npm run build # produce dist/ — the CLI serves it
98
+ npm install -g . # or: npm link — registers the `ccsm` command
99
+ ccsm
100
+ ```
101
+
102
+ ## What gets deleted
103
+
104
+ Claude Code stores session data across **five** locations under `~/.claude/`. Deleting a session here cleans up all of them in one shot:
105
+
106
+ | Path | Contents |
107
+ |---|---|
108
+ | `projects/<encoded-cwd>/<sessionId>.jsonl` | Main message stream |
109
+ | `projects/<encoded-cwd>/<sessionId>/` | Sub-agent threads + per-session memory |
110
+ | `file-history/<sessionId>/` | Snapshots of every file Claude edited in this session (often the largest contributor) |
111
+ | `session-env/<sessionId>/` | Environment snapshots |
112
+ | `history.jsonl` | Lines whose `sessionId` matches are stripped via atomic rewrite (backup → tmp → rename) |
113
+ | `sessions/<pid>.json` | Removed only when the PID has actually exited |
114
+
115
+ **Safety rails (a session is *skipped*, not deleted, when):**
116
+ - Its `sessionId` appears in a `sessions/<pid>.json` file whose PID is alive (`process.kill(pid, 0)` on Unix, `tasklist` on Windows).
117
+ - Its `.jsonl` was modified within the last 5 minutes (could still be in use after a `/clear` even if the PID file points elsewhere).
118
+ - The decoded path escapes `~/.claude/` (path-traversal guard).
119
+ - The session id contains `/`, `\`, `..`, or starts with `.`.
120
+
121
+ The UI's confirmation dialog shows the exact files and bytes that will be removed *and* lists which selections will be skipped and why.
122
+
123
+ ## Cross-device sharing
124
+
125
+ Export/import move a project's **memory + conversation history** between machines, accounting for the fact that the same project lives at a different absolute path on each device.
126
+
127
+ **The path problem.** A project id is just the encoded `cwd`, and the real path is recorded *inside* the data — as `cwd` on every session `.jsonl` line and as `project` on every matching `history.jsonl` line. A bundle is therefore made **path-independent** at export: those two fields are replaced with a `${CLAUDE_PROJECT_ROOT}` sentinel. On import you choose the local target folder and the sentinel is substituted with that path. Message bodies, `gitBranch`, and `version` are **never** rewritten — they're an archival record.
128
+
129
+ **Bundle (a plain folder you can copy, or keep in a git repo / cloud drive):**
130
+
131
+ ```
132
+ <bundle>/
133
+ manifest.json # schema, source platform + cwd, per-file sha256
134
+ memory/ # MEMORY.md + *.md, copied verbatim
135
+ sessions/<sid>/
136
+ conversation.jsonl # cwd -> ${CLAUDE_PROJECT_ROOT}
137
+ history.ndjson # project -> ${CLAUDE_PROJECT_ROOT}
138
+ ```
139
+
140
+ Scope is the **core tier**: memory, conversations, and the matching `history.jsonl` lines (all path-portable). `file-history/`, `session-env/`, and the session subdir are intentionally excluded — they embed source-device absolute paths and aren't portable.
141
+
142
+ **Import is safe by construction.** It reuses the same guards as delete: paths are validated under `~/.claude/`, a session that is live or was modified in the last 5 minutes is never overwritten, files are written via tmp→rename, and the `history.jsonl` merge is an atomic backup→tmp→rename append with de-duplication (so re-importing the same bundle is a no-op). When a session id already exists you choose **skip** (default), **overwrite if newer**, or **keep both** (imports under a fresh id).
143
+
144
+ ## Architecture
145
+
146
+ ```
147
+ shared/ Wire types + constants imported by BOTH server and web.
148
+ server/ Hono + @hono/node-server backend, all filesystem operations.
149
+ lib/ claude-paths, encode-cwd, scan, parse-jsonl, load-session,
150
+ load-memory, rename-session, search-all, search-session,
151
+ active-sessions, delete, bundle, export-bundle, import-bundle,
152
+ disk-usage, fs-size, safe-id, system-tags, port, …
153
+ routes/ projects, sessions, disk, search, import
154
+ web/ React 19 + Vite + Tailwind v4 SPA.
155
+ src/routes/ ProjectsList, ProjectDetail, SessionDetail,
156
+ ProjectMemory, DiskUsage (lazy), ImportPage (lazy)
157
+ src/components Sidebar, SearchModal, PageHeader, MessageBubble,
158
+ ToolBlock, DeleteDialog, ExportDialog, HighlightedText, …
159
+ src/lib api, query-keys, i18n, theme, hotkeys, format
160
+ docs/spec/ Design notes (start here before refactoring).
161
+ docs/acceptance/ Per-feature e2e plans, round evidence, retrospectives.
162
+ ```
163
+
164
+ ### HTTP API
165
+
166
+ | Method | Path | Purpose |
167
+ |---|---|---|
168
+ | `GET` | `/api/health` | `claudeRoot`, platform, pid — used by the UI's empty-state banner. |
169
+ | `GET` | `/api/projects` | Project summaries (one per `cwd`). |
170
+ | `GET` | `/api/projects/:id/sessions` | Session list for a project. |
171
+ | `GET` | `/api/projects/:id/memory` | Memory file index + content for the two-pane reader. |
172
+ | `GET` | `/api/sessions/:projectId/:sessionId` | Full parsed message timeline. |
173
+ | `PATCH` | `/api/sessions/:projectId/:sessionId` | Rename a session by appending a `custom-title` record to the `.jsonl`. |
174
+ | `DELETE` | `/api/sessions` | Cascade-delete one or more sessions; CSRF-checked via `Origin`. |
175
+ | `POST` | `/api/projects/:id/export` | Write a path-independent bundle (memory + conversations) to a folder outside `~/.claude/`; CSRF-checked. |
176
+ | `POST` | `/api/import/preview` | Dry run: remap plan, per-session/memory actions, and history-line count — writes nothing; CSRF-checked. |
177
+ | `POST` | `/api/import` | Commit an import into `~/.claude/` (atomic, never overwrites a live/recent session); CSRF-checked. |
178
+ | `GET` | `/api/disk-usage` | Per-project totals + monthly buckets + top-N sessions. |
179
+ | `GET` | `/api/search?q=...` | NDJSON stream of matches across every project. |
180
+
181
+ | Layer | Tech |
182
+ |---|---|
183
+ | Backend runtime | Node 22+ + `tsx` (TS direct execution) |
184
+ | HTTP | Hono + `@hono/node-server` |
185
+ | Frontend | React 19 + Vite 6 + Tailwind v4 + TanStack Query + React Router 7 |
186
+ | Charts | Recharts (lazy-loaded only on `/disk`) |
187
+
188
+ The DiskUsage page is code-split, so the initial bundle is **~124 KB gzipped**. Recharts (~80 KB gzipped) loads on demand when you navigate to `/disk`.
189
+
190
+ ## Cross-platform
191
+
192
+ | OS | `~/.claude/` resolves to | `projects/` subdir naming |
193
+ |---|---|---|
194
+ | macOS / Linux | `$HOME/.claude/` | `/foo/bar` → `-foo-bar` |
195
+ | Windows | `C:\Users\<you>\.claude\` | `C:\foo\bar` → `C--foo-bar` |
196
+
197
+ Decoding a project id back to a real path uses each session's own `cwd` field (recorded inside the `.jsonl`) when available; otherwise it falls back to a heuristic decode and stat-checks the result. Projects whose decoded path no longer exists on disk are flagged in the UI.
198
+
199
+ ## Troubleshooting
200
+
201
+ **Port 3131 busy.** The server auto-picks the next free port up to 3140 and prints it on startup. Look at the `[server] listening on http://127.0.0.1:<port>` line.
202
+
203
+ **`Claude root .../.claude doesn't exist`.** The Projects page shows an amber banner with the resolved path. Either Claude Code hasn't been run on this machine, or `$HOME` resolves somewhere unexpected. Check `node -e 'console.log(require("os").homedir())'`.
204
+
205
+ **Delete dialog says my current session is skipped.** Expected — your live Claude Code process has its `sessionId` registered in `~/.claude/sessions/<pid>.json` *or* the `.jsonl` was just modified. Wait 5+ minutes after closing Claude, then try again.
206
+
207
+ **`history.jsonl.bak-<timestamp>` left behind.** Means the swap rename failed mid-flight. The original is intact (the backup *is* the original). Compare both files and either `mv backup history.jsonl` to restore or `rm` the backup if the new one looks fine.
208
+
209
+ **Windows: delete fails with `EBUSY`.** Some Windows AV / indexer can hold a lock on `.jsonl` files. Close any text editors that have the file open and retry.
210
+
211
+ **Browser can't reach the server.** The bind is `127.0.0.1`, not `0.0.0.0`. Make sure you're using `http://localhost:3131` from the same machine — not a LAN IP.
212
+
213
+ ## Security model
214
+
215
+ This tool is intended for a single user on their own machine. It is *not* hardened for multi-user / shared environments.
216
+
217
+ - The HTTP listener binds to `127.0.0.1` only by default — never reachable from the LAN. The CLI's `--host` flag can override this (e.g. `--host 0.0.0.0`); doing so exposes the UI on your network with **no authentication**, so anyone who can reach the host:port can read and delete your Claude Code history. The server prints a warning on startup whenever it binds to a non-loopback host. Only do this on a network you trust.
218
+ - Mutating endpoints (`DELETE /api/sessions`, `PATCH /api/sessions/:projectId/:sessionId`, `POST /api/projects/:id/export`, `POST /api/import` and its `/preview`) require an `Origin` header matching `http(s)://(localhost|127.0.0.1):*`. This blocks other web pages your browser opens from triggering writes via CSRF, but cannot stop another local process running as the same user.
219
+ - *Export* refuses to write inside `~/.claude/`; *import* validates every destination under `~/.claude/`, skips any live/recently-active session, and writes atomically — the same safety rails as delete.
220
+ - All filesystem paths are validated with `path.resolve(...).startsWith(claudeRoot)` (Windows-aware case-folded) before any read or write.
221
+ - IDs from URL params are rejected if they contain `/`, `\`, `..`, or start with `.`.
222
+ - The cross-session search endpoint streams NDJSON and aborts when the client disconnects, so a closed browser tab stops the scan immediately.
223
+
224
+ There is no authentication. If you're paranoid, run behind a firewall rule or only invoke the tool when needed.
225
+
226
+ ## Project layout
227
+
228
+ See [`docs/spec/session-manager-design.md`](docs/spec/session-manager-design.md) for the full design rationale (data model, routing decisions, cross-platform strategy, future work).
229
+
230
+ ## Contributing & releases
231
+
232
+ Commits follow [Conventional Commits](https://www.conventionalcommits.org/) (enforced by a `commit-msg` hook), versions follow [SemVer](https://semver.org/), and releases are tag-triggered: run `npm version` locally, then `git push --follow-tags` fires a GitHub Actions workflow that publishes to npm and cuts a GitHub Release. See [CONTRIBUTING.md](CONTRIBUTING.md) for the workflow and [docs/spec/release-process.md](docs/spec/release-process.md) for the full policy.
233
+
234
+ ## License
235
+
236
+ MIT — see [LICENSE](LICENSE).