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

@chrodriguez-rts/claudetop

v0.3.4

Published

Real-time terminal dashboard for Claude workflows, agents, and sessions

Readme

claudetop

Real-time terminal dashboard for Claude workflows, agents, and sessions.

claudetop is an interactive terminal UI (TUI) — think htop/btop, but for your Claude API activity. A local daemon aggregates events from every process that calls Claude, and a keyboard-driven Ink dashboard renders live sessions, multi-step workflows, autonomous agent tool-call chains, and background jobs from a single terminal pane.

╔═══════════════════════════════════════════════════════════════════╗
║  claudetop  v0.1.0                       [?] help  [q] quit       ║
╠══════════════════╦══════════════════════════╦═════════════════════╣
║  SESSIONS  (3)   ║  DETAIL                  ║  BACKGROUND JOBS    ║
║ ● research-ag..  ║  Agent: research-agent    ║  batch-embed-001    ║
║ ● chat-session   ║  Status:  ⟳ RUNNING      ║  [████████░░]  80%  ║
║ ○ idle-worker    ║  Tokens: ↑ 2,341  ↓ 891  ║  custom-scrape-002  ║
║                  ║  ✓ web_search    (241ms)  ║  [████░░░░░░]  41%  ║
╠══════════════════╩══════════════════════════╩═════════════════════╣
║  ● 3 active  │  Tokens: 124,302 in / 89,441 out  │  Cost: $0.43  ║
╚═══════════════════════════════════════════════════════════════════╝

Features

  • Zero-friction observability — swap one import for the drop-in SDK wrapper and your Claude activity shows up automatically, no instrumentation code required.
  • Cross-process visibility — a local IPC daemon aggregates activity from many processes at once, so you can watch claudetop in one terminal while several services call Claude in others.
  • Live sessions — token counts (in/out), cost estimates, latency, turn count, and streaming state, updated in real time.
  • Workflows — multi-step pipelines rendered as a step ladder with per-step status.
  • Agents — autonomous agent iterations with a full tool-call chain (input, status, timing).
  • Background jobs — batch / embedding / fine-tune / custom jobs with animated progress bars.
  • Real-time log stream — scroll-lockable event feed across every source.
  • Beautiful, keyboard-driven UX — built with Ink v4 (React for the terminal) and Zustand, inspired by btop and lazygit.

Install

npm install -g @chrodriguez-rts/claudetop

This installs the claudetop (and ctop) commands globally. Or run it without installing:

npx @chrodriguez-rts/claudetop

Requires Node.js 22.13 or newer (history persistence uses the built-in node:sqlite module).

The CLI installs two equivalent binaries: claudetop and the shorthand alias ctop.

Quickstart

  1. Start the daemon (the central state aggregator). It runs in the foreground and keeps the process alive:

    claudetop daemon start
  2. Instrument your app so its Claude activity is reported to the daemon. The fastest path is the drop-in SDK wrapper — change one import:

    import Anthropic from "@chrodriguez-rts/claudetop/sdk";
    
    const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });
    
    const message = await client.messages.create({
      model: "claude-opus-4-5",
      max_tokens: 1024,
      messages: [{ role: "user", content: "Hello, Claude" }],
    });
  3. Open the dashboard in another terminal:

    claudetop

    Your app's session appears in the SESSIONS pane with live token counts and cost.

Just want to see what it looks like first? Run CLAUDETOP_DEMO=1 claudetop to launch the TUI pre-seeded with mock data — no daemon or API key needed. See Demo mode.

Integration paths

There are two ways to feed activity into claudetop. Use the SDK wrapper for automatic session tracking, or the manual client when you need explicit control over workflows, agents, and jobs.

(a) Drop-in SDK wrapper — @chrodriguez-rts/claudetop/sdk

@chrodriguez-rts/claudetop/sdk is a transparent proxy over @anthropic-ai/sdk. Its default export is a drop-in replacement for the real Anthropic client — the API surface and call signatures are identical, so the only change to your code is the import line.

// Before
import Anthropic from "@anthropic-ai/sdk";

// After — identical API, automatic session tracking in claudetop
import Anthropic from "@chrodriguez-rts/claudetop/sdk";

const client = new Anthropic({ apiKey: process.env.ANTHROPIC_API_KEY });

Under the hood it registers a session with the daemon on the first call, counts tokens in real time, estimates cost, and updates session status to error if a call throws. Emits are fire-and-forget, so the happy path adds no latency.

You can name the session with the extra claudetopName option (everything else is forwarded to the real Anthropic constructor untouched):

const client = new Anthropic({
  apiKey: process.env.ANTHROPIC_API_KEY,
  claudetopName: "research-agent",
});

@anthropic-ai/sdk is an optional peer dependency

The real SDK is not bundled with @chrodriguez-rts/claudetop — it is an optional peer dependency, loaded lazily so that installing the package for the TUI alone never breaks. If you use @chrodriguez-rts/claudetop/sdk, install it yourself:

npm install @anthropic-ai/sdk

If it's missing, constructing the wrapped client throws:

@chrodriguez-rts/claudetop/sdk requires @anthropic-ai/sdk to be installed. Run `npm install @anthropic-ai/sdk`.

Phase-2.x streaming caveat

Both non-streaming messages.create(...) and async-iteration streaming are fully instrumented:

// ✅ Instrumented — async iteration over the stream
const stream = client.messages.stream({
  model: "claude-opus-4-5",
  max_tokens: 1024,
  messages: [{ role: "user", content: "Write a haiku." }],
});

for await (const event of stream) {
  // token counts update live in claudetop as events arrive
}
// ✅ Instrumented — create() with stream: true (async iterable)
const stream = await client.messages.create({
  model: "claude-opus-4-5",
  max_tokens: 1024,
  stream: true,
  messages: [{ role: "user", content: "Write a haiku." }],
});

for await (const event of stream) {
  // ...
}

The interceptor taps the stream's async iterator. Callback-style streaming via stream.on("text", ...) and other event-emitter handlers is NOT yet instrumented in Phase 2.x — those calls work exactly as the real SDK does, but their token deltas will not be reflected in claudetop. Use async iteration if you want live streaming metrics in the dashboard.

(b) Manual client — @chrodriguez-rts/claudetop/client

For apps that keep their own Anthropic SDK instance, or that need to model workflows, agents, and background jobs explicitly, use the manual registration API. Each helper returns the object it created or updated; you pass that object back in to advance it, so there is no hidden client state to manage.

import { claudetop } from "@chrodriguez-rts/claudetop/client";

Sessions

// register(input: { name: string; model: string; tags?: string[] }): Promise<ClaudeSession>
const session = await claudetop.session.register({
  name: "research-agent",
  model: "claude-opus-4-5",
  tags: ["batch", "internal"],
});

// update(session: ClaudeSession): Promise<void>
await claudetop.session.update({ ...session, tokensIn: 2341, tokensOut: 891 });

// complete(session: ClaudeSession, status?: Status): Promise<void>  — status defaults to "completed"
await claudetop.session.complete(session);

Workflows

// create(input: { sessionId: string; name: string; steps: string[] }): Promise<ClaudeWorkflow>
const workflow = await claudetop.workflow.create({
  sessionId: session.id,
  name: "Research Pipeline",
  steps: ["fetch", "summarize", "synthesize", "write"],
});

// step.advance(workflow: ClaudeWorkflow): Promise<ClaudeWorkflow>
// advance() returns the updated workflow — feed it back in to advance again.
let wf = await claudetop.workflow.step.advance(workflow); // fetch -> summarize
wf = await claudetop.workflow.step.advance(wf);           // summarize -> synthesize
// once the final step is passed, the workflow is marked "completed" automatically.

Agents

// register(input: {
//   sessionId: string; name: string; model: string;
//   availableTools?: string[]; maxIterations?: number;
// }): Promise<ClaudeAgent>
const agent = await claudetop.agent.register({
  sessionId: session.id,
  name: "researcher",
  model: "claude-opus-4-5",
  availableTools: ["web_search", "read_file", "write_file"],
  maxIterations: 10,
});

// toolCall(agent: ClaudeAgent, input: { tool: string; input?: Record<string, unknown> }):
//   Promise<{ agent: ClaudeAgent; callId: string }>
const { agent: a1, callId } = await claudetop.agent.toolCall(agent, {
  tool: "web_search",
  input: { query: "claudetop release notes" },
});

// toolResult(agent: ClaudeAgent, callId: string,
//   result: { status: "success" | "error"; output?: Record<string, unknown>; durationMs?: number }):
//   Promise<ClaudeAgent>
const a2 = await claudetop.agent.toolResult(a1, callId, {
  status: "success",
  output: { hits: 5 },
  durationMs: 241,
});

Note: there is no dedicated agent-registered event. agent.register and agent.toolCall both emit on the same channel, and the daemon upserts the full agent payload either way.

Background jobs

// register(input: {
//   type: "batch" | "embedding" | "fine-tune" | "custom";
//   label: string; metadata?: Record<string, unknown>;
// }): Promise<BackgroundJob>
const job = await claudetop.job.register({
  type: "batch",
  label: "embed-corpus-001",
  metadata: { source: "docs/" },
});

// progress(job: BackgroundJob, progressPct: number): Promise<BackgroundJob>
// progressPct is a fraction in the range 0.0 – 1.0. Returns the updated job.
let j = await claudetop.job.progress(job, 0.45);
j = await claudetop.job.progress(j, 0.9);

// complete(job: BackgroundJob): Promise<void>  — marks status "completed", progress 1.0
await claudetop.job.complete(j);

The client connects to the daemon lazily on the first call and shares a single socket connection across every helper.

Daemon CLI

| Command | Description | |---|---| | claudetop | Launch the TUI dashboard. | | claudetop daemon start | Start the daemon as a detached background process that outlives the launching terminal, then return. Its stdout/stderr are redirected to ~/.claudetop/daemon.log. Add -f / --foreground to run it in the foreground (blocks, logs to the terminal; for use under a process supervisor). No-op if one is already running. | | claudetop daemon status | Report whether a daemon is reachable. Prints daemon: running / daemon: not running and exits 0 / 1. | | claudetop daemon stop | Stop the running daemon by signalling the pid recorded in ~/.claudetop/daemon.pid (SIGTERM). Prints daemon: stopped (pid N) / daemon: not running; a stale pidfile is cleaned up. | | claudetop capture enable / disable / status | Toggle/report auto-capture of Claude Code CLI sessions (transcript tailer). | | claudetop hooks install / uninstall / status | Add/remove user-scope hooks in ~/.claude/settings.json for low-latency capture. | | claudetop --version / claudetop -v | Print the version. |

The ctop alias works identically (e.g. ctop daemon start).

Capture Claude Code CLI activity

By default claudetop only sees apps you instrument with the /sdk wrapper or the /client API. It can also auto-capture every Claude Code CLI session user-wide — no per-app changes and no Anthropic API key — by reading the transcripts and hook events Claude Code already produces locally. Nothing is sent to Anthropic; token counts come straight from the local transcripts and cost is derived with the same pricing.ts table.

claudetop capture enable      # turn on the transcript tailer
claudetop hooks install       # add low-latency lifecycle hooks (merges into settings.json)
claudetop daemon start        # (re)start the daemon to apply
claudetop                     # open the TUI — your Claude Code sessions appear automatically

Two cooperating sources feed the daemon:

  • Transcript tailer (authoritative) — backfills recent sessions (last capture.backfillDays days) and tails ~/.claude/projects/**/*.jsonl live for model, token/cache usage, derived cost, cwd, and git branch. Subagent transcripts roll into their parent session (tagged subagents).
  • User-scope hooks (low-latency)claudetop hooks install merges command hooks (SessionStart/PreToolUse/PostToolUse/Stop/…) into ~/.claude/settings.json for instant lifecycle/status updates. It appends to your existing hooks (backing the file up first) and hooks uninstall removes only claudetop's entries.

To undo: claudetop hooks uninstall and claudetop capture disable. Honor a non-default Claude data dir via CLAUDE_CONFIG_DIR / CLAUDE_CONFIG_DIRS or capture.claudeConfigDir.

Troubleshooting

Nothing shows up / the dashboard is empty. Read the status bar first:

  • ● daemon disconnected — no daemon is running. Start one with claudetop daemon start, then press r in the TUI to reconnect.
  • ● daemon connected but no sessions — the daemon is up and idle. The detail pane shows first-run guidance: instrument an app with the /sdk wrapper or /client API, or turn on Claude Code capture.

A cancelled session reappears as active. Fixed — terminal states (cancelled / completed) are now sticky and survive the capture tailer's mtime sweep. If you still see it, restart the daemon to clear stale state.

EADDRINUSE when starting. A daemon already owns the socket. claudetop daemon start pre-checks and prints daemon: already running instead of a stack trace. If a crash left things wedged, claudetop daemon stop (SIGTERM via the pidfile, clears a stale pidfile) then start again.

The daemon dies when I close the terminal. It shouldn't — claudetop daemon start runs detached and outlives the launching terminal. Only -f / --foreground ties it to the terminal. Its output lands in ~/.claudetop/daemon.log; check there if it exits unexpectedly.

Capture isn't working. It needs all three: claudetop capture enable, claudetop hooks install, and a daemon restart to apply. Confirm with claudetop capture status (transcripts on/off + hooks installed count). For a non-default Claude data dir, set CLAUDE_CONFIG_DIR.

Raw mode is not supported / TTY error. The TUI needs a real interactive terminal; it can't run piped or under CI. The same applies to CLAUDETOP_DEMO=1 claudetop.

Where do things live? Config ~/.claudetop/config.json, history ~/.claudetop/history.db, daemon log ~/.claudetop/daemon.log, pidfile ~/.claudetop/daemon.pid, socket ~/.claudetop/daemon.sock (a named pipe on Windows).

Keyboard shortcuts

Press ? inside the TUI to toggle this overlay at any time.

| Key | Action | |---|---| | tab / shift+tab | Cycle panel focus | | / | Navigate list items | | k | Kill / cancel selected | | r | Reconnect to daemon | | t | Cycle color theme | | d | Toggle detail panel mode | | l | Toggle log scroll-lock | | ? | Toggle this help | | q | Quit claudetop |

Configuration

claudetop reads ~/.claudetop/config.json on startup. Every key is optional; the defaults are shown below.

{
  "theme": "nord",
  "daemon": {
    "autoStart": true,
    "socketPath": "~/.claudetop/daemon.sock",
    "historyRetentionHours": 24,
    "port": null
  },
  "display": {
    "showCost": true,
    "defaultPanel": "sessions",
    "logLevel": "info",
    "maxLogLines": 500,
    "scrollLock": false
  },
  "pricing": {
    "overrides": {}
  },
  "capture": {
    "transcripts": false,
    "hooks": false,
    "backfillDays": 7,
    "maxBackfillFiles": 200,
    "claudeConfigDir": null
  }
}

| Key | Default | Description | |---|---|---| | theme | "nord" | Color theme. One of nord, dracula, one-dark, tokyo-night. | | daemon.autoStart | true | Reserved — not yet wired; start the daemon manually with claudetop daemon start. | | daemon.socketPath | "~/.claudetop/daemon.sock" | Unix socket path for the daemon (see Platform support for Windows). Honored, after CLAUDETOP_SOCKET. | | daemon.historyRetentionHours | 24 | How many hours of session/metric history to persist via Node's built-in node:sqlite (needs Node ≥ 22.13; otherwise history is in-memory only). | | daemon.port | null | Reserved for future TCP transport; unused by the local Unix-socket daemon. | | display.showCost | true | Show the estimated cost segment in the status bar. | | display.defaultPanel | "sessions" | Panel focused on launch. | | display.logLevel | "info" | Reserved — not yet wired. Intended minimum log level for the stream. | | display.maxLogLines | 500 | Reserved — not yet wired; the buffer is currently fixed at 500 lines. | | display.scrollLock | false | Start with the log stream scroll-locked. | | pricing.overrides | {} | Per-model price overrides so teams on custom-contracted pricing get accurate cost estimates without editing source. | | capture.transcripts | false | Auto-capture Claude Code CLI sessions by tailing ~/.claude/projects. Toggle with claudetop capture enable/disable; restart the daemon to apply. | | capture.hooks | false | Reserved flag for the hooks source; hooks are enabled by claudetop hooks install (which edits ~/.claude/settings.json) and forward over the socket regardless. | | capture.backfillDays | 7 | On daemon start, backfill transcripts modified within this many days, then tail live. 0 = live only (clamped to 0–90). | | capture.maxBackfillFiles | 200 | Cap on transcripts scanned during backfill (most-recent first; clamped to 1–5000). | | capture.claudeConfigDir | null | Override the Claude data dir. Precedence: CLAUDE_CONFIG_DIR / first of CLAUDE_CONFIG_DIRS env > this > ~/.claude. |

The socket path can also be overridden at runtime with the CLAUDETOP_SOCKET environment variable (useful for tests or custom layouts).

Themes

claudetop ships four themes:

  • Nord (default)
  • Dracula
  • One Dark
  • Tokyo Night

Set the active theme via the theme key in ~/.claudetop/config.json.

Demo mode

To explore the dashboard without a daemon or an API key, set CLAUDETOP_DEMO to any non-empty value. The store is pre-seeded with mock sessions, workflows, agents, jobs, and log events so you can eyeball the full layout and navigation:

CLAUDETOP_DEMO=1 claudetop

Platform support

| Platform | Transport | Status | |---|---|---| | macOS | Unix domain socket (~/.claudetop/daemon.sock) | Fully supported | | Linux | Unix domain socket (~/.claudetop/daemon.sock) | Fully supported | | Windows | Named pipe (\\.\pipe\claudetop-daemon) | Supported via named-pipe fallback |

Node does not support Unix domain sockets on Windows, so claudetop transparently uses a named pipe there instead. The socket/pipe address is resolved automatically per platform and can be overridden with the CLAUDETOP_SOCKET environment variable.

Architecture

claudetop is two independent layers that communicate over a local socket using a line-delimited JSON protocol. Each message is a typed envelope: { type, payload, timestamp }.

  Your application(s)
    import Anthropic from "@chrodriguez-rts/claudetop/sdk"          (wrapper)
        ── or ──
    import { claudetop } from "@chrodriguez-rts/claudetop/client"   (manual)
            │
            │  IPC (Unix socket / named pipe)
            ▼
   ┌───────────────────────┐
   │   claudetop daemon    │   aggregates state (sessions / workflows /
   │                       │   agents / jobs + metrics), broadcasts deltas,
   │                       │   persists history (optional SQLite)
   └───────────┬───────────┘
               │  IPC (Unix socket / named pipe)
               ▼
   ┌───────────────────────┐
   │   claudetop TUI       │   your terminal dashboard (one or many)
   └───────────────────────┘
  • Daemon — a singleton background process and central state aggregator. It owns authoritative in-memory state for all sessions, workflows, agents, and jobs, broadcasts delta updates to every connected client, and persists a rolling history to SQLite.
  • TUI client — an Ink/React terminal application that connects to the daemon on startup, subscribes to state updates via Zustand store slices, and renders the live dashboard. Multiple TUI instances can connect to the same daemon at once.

Package entry points

| Import path | Use case | |---|---| | @chrodriguez-rts/claudetop | Reserved for the future programmatic API. | | @chrodriguez-rts/claudetop/sdk | Drop-in Anthropic SDK wrapper. | | @chrodriguez-rts/claudetop/client | Manual session / workflow / agent / job registration. | | @chrodriguez-rts/claudetop/daemon | Daemon lifecycle management (startDaemon, pingDaemon). |

License

MIT