npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@zzusp/ccsm

v1.0.4

Published

Local web UI to view and clean up Claude Code session history (~/.claude/)

Readme

Claude Code Session Manager

A local web UI to view and clean up Claude Code session history stored under ~/.claude/.

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.

Screenshots

See docs/screenshots/README.md for the capture recipe (Windows / macOS / Linux) if you want to refresh these.

Features

| Page | What you can do | |---|---| | Projects (/) | See every Claude Code project (one per cwd) with session count, total bytes on disk, last-activity time. | | 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. | | 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. | | Session detail (/projects/:id/sessions/:sid) | Chat-style message timeline (text, collapsible tool calls, tool results, thinking blocks) centered in a reading column. A slim sticky header carries the live/working status, the inline-editable title, an Open modified files link, and Delete (which returns to the project list). Beneath it a sticky search bar with client-side highlight plus toggles to show/hide system messages (<command-name> etc.) or filter to your messages / errors. While the session is live the timeline auto-tails newly written messages; while Claude is actively processing, the header shows a working indicator and a trailing “Claude is working…” row marks the open turn. | | 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. | | Disk usage (/disk) | Pie chart by project, monthly bar chart, top-20 largest sessions with deep links. | | 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. |

The persistent sidebar carries the search trigger, a Recents list of the most recently active sessions across every project (each with a live / working / idle activity dot, the currently-open one highlighted), a Close sidebar toggle that collapses the rail on desktop (with a floating handle to re-open it; the choice is remembered), 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.

Quick start

Requires Node 22+ (Node 24 recommended).

git clone <this-repo>
cd claude-code-session
npm install
npm run dev

Open http://localhost:5173.

| Script | Effect | |---|---| | npm run dev | Concurrent backend (:3131) + Vite dev server (:5173) with HMR. Vite proxies /api/* to the backend. | | npm run build | Builds the SPA to dist/. | | npm run start | Single-process production mode: backend on :3131 serves dist/ + the API. | | npm run typecheck | tsc -b over both server and web projects. |

The HTTP server binds to 127.0.0.1 only — never reachable from the LAN. Default port is 3131. If it's busy, the server tries 3132 … 3140 and prints the actual port to stdout.

Install as a CLI

Published to npm, the manager installs as a single command — ccsm — that starts the production server (the built SPA + the API in one process, no separate Vite dev server).

# Run once, without installing
npx @zzusp/ccsm

# …or install globally and run `ccsm` from anywhere
npm install -g @zzusp/ccsm
ccsm

It prints the URL it picked (e.g. http://127.0.0.1:3131); open that. Requires Node 22+.

| Flag | Effect | |---|---| | -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. | | --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. | | -o, --open | Open the UI in your default browser once the server is listening. | | -h, --help | Show usage and exit. | | -v, --version | Print the version and exit. |

ccsm --port 4000 --open       # custom port, then pop open the browser
ccsm --host 0.0.0.0           # expose on the LAN (trusted networks only)

Install from a local checkout (without publishing to npm):

git clone <this-repo> && cd claude-code-session
npm install
npm run build         # produce dist/ — the CLI serves it
npm install -g .      # or: npm link — registers the `ccsm` command
ccsm

What gets deleted

Claude Code stores session data across five locations under ~/.claude/. Deleting a session here cleans up all of them in one shot:

| Path | Contents | |---|---| | projects/<encoded-cwd>/<sessionId>.jsonl | Main message stream | | projects/<encoded-cwd>/<sessionId>/ | Sub-agent threads + per-session memory | | file-history/<sessionId>/ | Snapshots of every file Claude edited in this session (often the largest contributor) | | session-env/<sessionId>/ | Environment snapshots | | history.jsonl | Lines whose sessionId matches are stripped via atomic rewrite (backup → tmp → rename) | | sessions/<pid>.json | Removed only when the PID has actually exited |

Safety rails (a session is skipped, not deleted, when):

  • Its sessionId appears in a sessions/<pid>.json file whose PID is alive (process.kill(pid, 0) on Unix, tasklist on Windows).
  • Its .jsonl was modified within the last 5 minutes (could still be in use after a /clear even if the PID file points elsewhere).
  • The decoded path escapes ~/.claude/ (path-traversal guard).
  • The session id contains /, \, .., or starts with ..

The UI's confirmation dialog shows the exact files and bytes that will be removed and lists which selections will be skipped and why.

Cross-device sharing

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.

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.

Bundle (a plain folder you can copy, or keep in a git repo / cloud drive):

<bundle>/
  manifest.json            # schema, source platform + cwd, per-file sha256
  memory/                  # MEMORY.md + *.md, copied verbatim
  sessions/<sid>/
    conversation.jsonl     # cwd     -> ${CLAUDE_PROJECT_ROOT}
    history.ndjson         # project -> ${CLAUDE_PROJECT_ROOT}

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.

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).

Architecture

shared/         Wire types + constants imported by BOTH server and web.
server/         Hono + @hono/node-server backend, all filesystem operations.
  lib/          claude-paths, encode-cwd, scan, parse-jsonl, load-session,
                load-memory, rename-session, search-all, search-session,
                active-sessions, delete, bundle, export-bundle, import-bundle,
                disk-usage, fs-size, safe-id, system-tags, port, …
  routes/       projects, sessions, disk, search, import
web/            React 19 + Vite + Tailwind v4 SPA.
  src/routes/   ProjectsList, ProjectDetail, SessionDetail,
                ProjectMemory, DiskUsage (lazy), ImportPage (lazy)
  src/components  Sidebar, SearchModal, PageHeader, MessageBubble,
                  ToolBlock, DeleteDialog, ExportDialog, HighlightedText, …
  src/lib       api, query-keys, i18n, theme, hotkeys, format
docs/spec/      Design notes (start here before refactoring).
docs/acceptance/  Per-feature e2e plans, round evidence, retrospectives.

HTTP API

| Method | Path | Purpose | |---|---|---| | GET | /api/health | claudeRoot, platform, pid — used by the UI's empty-state banner. | | GET | /api/projects | Project summaries (one per cwd). | | GET | /api/projects/:id/sessions | Session list for a project. | | GET | /api/projects/:id/memory | Memory file index + content for the two-pane reader. | | GET | /api/sessions/recent | Most recently active sessions across every project (sidebar Recents); ?limit= 1–50, default 12. | | GET | /api/sessions/:projectId/:sessionId | Full parsed message timeline. | | PATCH | /api/sessions/:projectId/:sessionId | Rename a session by appending a custom-title record to the .jsonl. | | DELETE | /api/sessions | Cascade-delete one or more sessions; CSRF-checked via Origin. | | POST | /api/projects/:id/export | Write a path-independent bundle (memory + conversations) to a folder outside ~/.claude/; CSRF-checked. | | POST | /api/import/preview | Dry run: remap plan, per-session/memory actions, and history-line count — writes nothing; CSRF-checked. | | POST | /api/import | Commit an import into ~/.claude/ (atomic, never overwrites a live/recent session); CSRF-checked. | | GET | /api/disk-usage | Per-project totals + monthly buckets + top-N sessions. | | GET | /api/search?q=... | NDJSON stream of matches across every project. |

| Layer | Tech | |---|---| | Backend runtime | Node 22+ + tsx (TS direct execution) | | HTTP | Hono + @hono/node-server | | Frontend | React 19 + Vite 6 + Tailwind v4 + TanStack Query + React Router 7 | | Charts | Recharts (lazy-loaded only on /disk) |

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.

Cross-platform

| OS | ~/.claude/ resolves to | projects/ subdir naming | |---|---|---| | macOS / Linux | $HOME/.claude/ | /foo/bar-foo-bar | | Windows | C:\Users\<you>\.claude\ | C:\foo\barC--foo-bar |

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.

Troubleshooting

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.

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())'.

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.

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.

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.

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.

Security model

This tool is intended for a single user on their own machine. It is not hardened for multi-user / shared environments.

  • 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.
  • 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.
  • 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.
  • All filesystem paths are validated with path.resolve(...).startsWith(claudeRoot) (Windows-aware case-folded) before any read or write.
  • IDs from URL params are rejected if they contain /, \, .., or start with ..
  • The cross-session search endpoint streams NDJSON and aborts when the client disconnects, so a closed browser tab stops the scan immediately.

There is no authentication. If you're paranoid, run behind a firewall rule or only invoke the tool when needed.

Project layout

See docs/spec/session-manager-design.md for the full design rationale (data model, routing decisions, cross-platform strategy, future work).

Contributing & releases

Commits follow Conventional Commits (enforced by a commit-msg hook), versions follow SemVer, 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 for the workflow and docs/spec/release-process.md for the full policy.

License

MIT — see LICENSE.