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

@cuongtran001/kanna

v0.74.0

Published

A beautiful web UI for Claude Code

Downloads

6,472

Readme

About this fork

Kanna started life as jakemor/kanna — a clean web UI for the Claude Code CLI. This fork (@cuongtran001/kanna) tracks upstream and layers on features needed for heavier day-to-day use, multi-account billing, and self-hosting.

Headline additions vs. upstream:

  • Subscription-billing PTY driver (KANNA_CLAUDE_DRIVER=pty) — runs the claude CLI under a pseudo-terminal so Pro/Max plans are charged instead of API rates. Includes JSONL event parity with the SDK driver, macOS sandbox-exec / Linux bwrap sandboxing, allowlist preflight probes, and failure-mode parity.
  • OAuth token pool — register multiple Claude OAuth tokens; Kanna rotates across them per chat with automatic fallover on rate-limit and an explicit disabled state.
  • Multi-provider chat — switch between Claude and Codex (OpenAI) from the composer with per-provider model + reasoning-effort controls and Codex fast mode.
  • Subagent orchestration — first-class subagent CRUD, @agent/ mentions, parallel runs, live activity labels, MCP progress notifications, and mcp__kanna__delegate_subagent so the main agent itself can delegate.
  • Durable tool-approval protocol (KANNA_MCP_TOOL_CALLBACKS=1) — pending AskUserQuestion / ExitPlanMode / built-in shims survive server restart and replay to the client on reconnect.
  • Cloudflare expose_port MCP tool — agent-callable port exposure with always-ask or auto-expose modes, replacing bash-output sniffing.
  • In-app self-update — one-click pull/rebuild/reload with a host-agnostic supervisor (works under pm2, systemd, docker, plain shell) or direct pm2 reload; install any prior release straight from the changelog UI.
  • Git worktree isolation per chat, bulk import of existing ~/.claude/projects/ sessions, proactive context compaction, password gate for HTTP/WS/API, PWA / mobile layout, mermaid rendering in transcripts, standalone HTML transcript export, and customizable keybindings.

See the full inventory in Features below.

Quickstart

bun install -g @cuongtran001/kanna

If Bun isn't installed, install it first:

curl -fsSL https://bun.sh/install | bash

Then run from any project directory:

kanna

That's it. Kanna opens in your browser at localhost:3210.

Features

Providers & models

  • Multi-provider support — switch between Claude and Codex (OpenAI) from the chat input, with per-provider model selection, reasoning-effort controls, and Codex fast mode
  • OAuth token pool — register multiple Claude OAuth tokens; Kanna rotates across them per chat
  • Subscription-billing PTY driver — optional KANNA_CLAUDE_DRIVER=pty runs the claude CLI under a pseudo-terminal so Pro/Max subscription billing is preserved instead of API rates

Chat & transcript

  • Rich transcript rendering — hydrated tool calls, collapsible tool groups, plan-mode dialogs, and interactive prompts with full result display
  • Inline diff viewer — file and commit diffs rendered directly in the transcript
  • Embedded terminal — per-project xterm terminal in a resizable side panel (macOS/Linux)
  • File & image uploads — drag-and-drop attachments into the composer
  • Slash commands & @-mentions — in-composer pickers for slash commands, file mentions, and subagents
  • Plan mode — review and approve agent plans before execution
  • Subagent orchestration — run and track parallel subagents within a turn
  • Background tasks — long-running tasks tracked out-of-band with a status indicator
  • Auto-continue — optionally continue a turn automatically when the agent stops short
  • Proactive compaction — context-window meter with automatic transcript compaction before limits are hit

Projects & sessions

  • Project-first sidebar — chats grouped under projects, with live status indicators (idle, running, waiting, failed)
  • Drag-and-drop project ordering — reorder project groups in the sidebar with persistent ordering
  • Local project discovery — auto-discovers projects from both Claude and Codex local history
  • Bulk import Claude Code sessions — one-click import of existing ~/.claude/projects/ sessions with full transcript and seamless resume via the Claude Agent SDK
  • Git worktree isolation — run a chat in an isolated worktree without disturbing your working tree
  • Session resumption — resume agent sessions with full context preservation
  • Auto-generated titles — chat titles generated in the background via Claude Haiku
  • Quick responses — lightweight structured queries (e.g. title generation) via Haiku with automatic Codex fallback

Persistence & realtime

  • Persistent local history — refresh-safe routes backed by append-only JSONL event logs and compacted snapshots
  • WebSocket-driven — real-time subscription model with reactive state broadcasting
  • Standalone transcript export — export a chat as a self-contained HTML viewer

Access & notifications

  • Password protection — optional launch password gating the app, WebSocket, and API routes
  • Public share link--share creates a temporary trycloudflare.com URL with a terminal QR code
  • Cloudflare tunnel via expose_port tool — opt-in; the agent proactively calls the Kanna expose_port MCP tool with a port. In always-ask mode Kanna shows an inline "expose via Cloudflare" card for you to accept; in auto-expose mode cloudflared tunnel --url spawns immediately. Both modes are gated by the Cloudflare Tunnel setting
  • Web push & sound notifications — browser push and sound alerts when a chat needs attention
  • Customizable keybindings — user-editable keyboard shortcuts
  • In-app self-update — one-click update that pulls, rebuilds, and hot-reloads (host-agnostic supervisor or pm2)
  • Mobile-friendly — responsive layout, installable as a standalone PWA

Architecture

flowchart LR
    Browser["Browser<br/>React + Zustand"]

    subgraph Server["Bun Server (src/server/**)"]
        direction TB
        WS["WSRouter<br/>subscriptions + commands"]
        Auth["Auth gate"]
        Agent["AgentCoordinator<br/>multi-provider turns"]
        ES["EventStore<br/>append-only JSONL + snapshots"]
        RM["ReadModels<br/>derived views"]
        Diff["DiffStore"]
        Term["TerminalManager"]
        Up["Uploads"]
        Disc["Discovery"]
        Push["Push"]
        Tun["Share / Tunnel"]
        Upd["UpdateManager"]

        subgraph Adapters["*.adapter.ts (IO seal exempt)"]
            direction LR
            FsA["fs / chokidar"]
            DbA["bun:sqlite / pg"]
            SpA["Bun.spawn / child_process"]
            HtA["node:http / fetch"]
            PtyA["Bun.Terminal (PTY)"]
        end

        WS --> Agent
        WS --> ES
        WS --> RM
        Agent --> ES
        Agent -.spawn.-> SpA
        Agent -.spawn.-> PtyA
        ES -.fs.-> FsA
        Diff -.fs+spawn.-> SpA
        Diff -.fs.-> FsA
        Term -.pty.-> PtyA
        Up -.fs.-> FsA
        Disc -.fs.-> FsA
        Tun -.spawn+http.-> SpA
        Tun -.http.-> HtA
        Upd -.spawn.-> SpA
    end

    subgraph Shared["src/shared/** (pure)"]
        Proto["protocol types"]
        Types["domain types"]
    end

    subgraph External["External processes"]
        CC["Claude Agent SDK / claude CLI (PTY)"]
        CX["Codex App Server"]
        FS["Local FS<br/>~/.kanna/data/, project dirs"]
    end

    Browser <-->|WebSocket| WS
    Browser -.types.-> Shared
    Server -.types.-> Shared

    SpA --> CC
    SpA --> CX
    PtyA --> CC
    FsA --> FS

Layer rules (lint-enforced, see CLAUDE.md):

  • src/shared/** + src/client/** — pure. ESLint no-restricted-imports errors on node:fs, bun:sqlite, node:child_process, node:http, Bun.spawn, Bun.file, Bun.serve, …
  • src/server/** production — also sealed at error. Side-effect call sites only allowed inside files matching **/*.adapter.ts (or the legacy src/server/adapters/** dir).
  • Mixed-concern modules extract their IO into a sibling *-io.adapter.ts and import through it.

Key patterns: Event sourcing for all state mutations. CQRS with separate write (event log) and read (derived snapshots) paths. Reactive broadcasting — subscribers get pushed fresh snapshots on every state change. Multi-provider agent coordination with tool gating for user-approval flows. Provider-agnostic transcript hydration for unified rendering.

Workflow: adding code that touches IO

flowchart TD
    Start(["You need fs / spawn / http / DB / Bun globals"]) --> Layer{"Which layer?"}
    Layer -->|src/shared or src/client| Reject["ESLint errors at CI"]
    Reject --> Move["Move the module to src/server/**<br/>or inject through a typed parameter"]
    Move --> Server
    Layer -->|src/server| Server{"File responsibility?"}
    Server -->|leaf IO wrapper| RenameAdapter["Name it foo.adapter.ts<br/>(exempt from seal)"]
    Server -->|mixed domain + IO| SiblingAdapter["Extract calls into foo-io.adapter.ts<br/>keep domain logic in foo.ts<br/>import helpers from the adapter"]
    Server -->|domain only| Port["Take a typed port parameter<br/>provided by caller's adapter"]
    RenameAdapter --> Lint["bun run lint"]
    SiblingAdapter --> Lint
    Port --> Lint
    Lint --> CI(["CI: lint + tests + build"])

For the longer story (90 → 0 burndown, ratchet pipeline retired in PR #303) see the Side-Effect Lint section of CLAUDE.md.

Requirements

Embedded terminal support uses Bun's native PTY APIs and currently works on macOS/Linux.

Install

Install Kanna globally:

bun install -g @cuongtran001/kanna

If Bun isn't installed, install it first:

curl -fsSL https://bun.sh/install | bash

Or clone and build from source:

git clone https://github.com/cuongtranba/kanna.git
cd kanna
bun install
bun run build

Usage

kanna                  # start with defaults (localhost only)
kanna --port 4000      # custom port
kanna --strict-port    # fail instead of trying another port
kanna --no-open        # don't open browser
kanna --password <secret>      # require a password before loading the app
kanna --share                # create a public quick tunnel + terminal QR
kanna --cloudflared <token>  # run a named Cloudflare tunnel from a token

Default URL: http://localhost:3210

Network access (Tailscale / LAN)

By default Kanna binds to 127.0.0.1 (localhost only). Use --host to bind a specific interface, or --remote as a shorthand for 0.0.0.0:

kanna --remote                     # bind all interfaces — browser opens localhost:3210
kanna --host dev-box               # bind to a specific hostname — browser opens http://dev-box:3210
kanna --host 192.168.1.x           # bind to a specific LAN IP
kanna --host 100.64.x.x            # bind to a specific Tailscale IP

When --host <hostname> is given, the browser opens http://<hostname>:3210 automatically. Other machines on your network can connect to the same URL:

Password protection

Use --password to require a launch password before the app or websocket can connect:

kanna --password my-secret
bun run dev --password my-secret

Kanna verifies the password once, then sets a browser-session cookie. The password itself is not stored in the browser. When password protection is enabled, the backend requires authentication for API routes and /ws. The SPA shell still loads, /health remains public for restart detection, and the same in-app password screen is used in both dev and production.

Public share link

Use --share to create a temporary public trycloudflare.com URL and print a terminal QR code:

kanna --share
kanna --share --port 4000
kanna --cloudflared <token>

--share is incompatible with --host and --remote. It does not open a browser automatically.

Without a token, it prints:

QR Code:
...

Public URL:
https://<random>.trycloudflare.com

Local URL:
http://localhost:3210

With --cloudflared <token>, Kanna runs cloudflared tunnel run --token <token> --url <local-url>. If Kanna can detect the public hostname from cloudflared output, it prints the same QR/public/local block. If not, it keeps the tunnel running, warns that no public hostname was detected, and prints the local URL so you can use the hostname already configured for that tunnel in Cloudflare.

Auto-expose detected ports

When the agent runs a Bash command in a chat (bun run dev, go run, uvicorn, etc.), Kanna can detect any listening port from the command's stdout and offer to expose it through a Cloudflare quick tunnel without leaving the chat.

Enable from Settings → Cloudflare Tunnel:

  • Toggle — opt-in (off by default)
  • ModeAlways ask (one card per detected port; click Expose to spawn) or Auto-expose (spawn immediately on detection)
  • cloudflared path — defaults to cloudflared on $PATH

Each detected port shows up inline in the transcript. Click Expose, watch the spinner until cloudflared returns the *.trycloudflare.com URL, then click Stop when done. Tunnels are also stopped automatically when the chat closes or the server restarts.

Requires the cloudflared binary installed locally — brew install cloudflared on macOS, or see Cloudflare's downloads.

Development

bun run dev

The same --remote and --host flags can be used with bun run dev for remote development. --share is also supported in dev mode and exposes the Vite client URL publicly:

bun run dev --share
bun run dev --cloudflared <token>
bun run dev --port 3333 --share

In dev, --port sets the Vite client port and the backend runs on port + 1, so bun run dev --port 3333 --share publishes http://localhost:3333. --share remains incompatible with --host and --remote. Use bun run dev --port 4000 to run the Vite client on 4000 and the backend on 4001.

Or run client and server separately:

bun run dev:client   # http://localhost:5174
bun run dev:server   # http://localhost:5175

Scripts

| Command | Description | | -------------------- | ------------------------------------ | | bun run build | Build client + standalone export viewer | | bun run check | Typecheck, lint, and build | | bun run lint | ESLint over src/ (zero-warning gate) | | bun run dev | Run client + server together | | bun run dev:client | Vite dev server only (:5174) | | bun run dev:server | Bun backend only (:5175) | | bun run start | Start production server | | bun test | Run the test suite |

Project Structure

Abridged — the actual tree has more modules, each with co-located *.test.ts:

src/
├── client/          React UI layer
│   ├── app/         App router, pages, central state hook, socket client
│   ├── components/  chat-ui, messages, settings, ui primitives, modals
│   ├── hooks/       mobile/standalone detection, theme, mention/slash suggestions
│   ├── stores/      Zustand stores (chat input, preferences, terminal, tasks…)
│   └── lib/         formatters, path utils, transcript parsing, keybindings
├── server/          Bun backend
│   ├── cli.ts · cli-runtime.ts   CLI entry, flag parsing, supervisor
│   ├── server.ts                 HTTP/WS server + static serving
│   ├── auth.ts                   password gate for HTTP/WS/API
│   ├── ws-router.ts              WebSocket routing & subscriptions
│   ├── agent.ts                  AgentCoordinator (multi-provider turns)
│   ├── codex-app-server.ts       Codex App Server JSON-RPC client
│   ├── claude-pty/               PTY driver (subscription billing)
│   ├── oauth-pool/               Claude OAuth token rotation
│   ├── provider-catalog.ts       provider/model/effort normalization
│   ├── quick-response.ts         structured queries w/ provider fallback
│   ├── event-store.ts            JSONL persistence, replay & compaction
│   ├── read-models.ts            derived view models
│   ├── events.ts                 event type definitions
│   ├── discovery.ts              auto-discover Claude/Codex projects
│   ├── claude-session-importer.ts  bulk import existing sessions
│   ├── diff-store.ts             per-chat diff hydration
│   ├── terminal-manager.ts       embedded-terminal PTY sessions
│   ├── uploads.ts                attachment intake
│   ├── subagent-orchestrator.ts  parallel subagent runs
│   ├── background-tasks.ts       out-of-band task tracking
│   ├── worktree-store.ts         git worktree isolation
│   ├── push/                     web-push notifications
│   ├── share.ts · cloudflare-tunnel/  trycloudflare / expose_port tunnels
│   ├── update-manager.ts · update-strategy.ts  self-update
│   ├── kanna-mcp.ts              Kanna MCP tools (built-in shims)
│   └── keybindings.ts            persisted keybindings
└── shared/          Shared between client & server
    ├── types.ts     core domain types, provider catalog, transcript entries
    ├── tools.ts     tool-call normalization & hydration
    ├── protocol.ts  WebSocket wire envelopes
    ├── ports.ts     default ports & dev-mode offsets
    ├── share.ts     share/tunnel shared types
    └── branding.ts  app name & data-directory paths

Data Storage

All state is stored locally at ~/.kanna/data/:

| File | Purpose | | ---------------- | ----------------------------------------- | | projects.jsonl | Project open/remove events | | chats.jsonl | Chat create/rename/delete events | | messages.jsonl | Transcript message entries | | turns.jsonl | Agent turn start/finish/cancel events | | snapshot.json | Compacted state snapshot for fast startup |

Event logs are append-only JSONL. On startup, Kanna replays the log tail after the last snapshot, then compacts if the logs exceed 2 MB.

Self-hosting on macOS (pm2 + Cloudflare tunnel)

Run Kanna as a background service on macOS under pm2, exposed through a named Cloudflare tunnel. The in-app Update button then pulls the latest commit, rebuilds, and hot-reloads the pm2 process — no terminal round-trip needed.

1. Link the repo as the global install

bun link makes the global kanna binary resolve to your checkout:

cd ~/path/to/kanna
bun install
bun run build
bun link           # registers @cuongtran001/kanna → repo

After this, ~/.bun/install/global/node_modules/@cuongtran001/kanna is a symlink to your repo.

2. Create a named Cloudflare tunnel

In the Cloudflare Zero Trust dashboardNetworks → Tunnels → Create tunnel (type: Cloudflared):

  1. Name the tunnel (e.g. kanna) and copy the connector token Cloudflare shows you. You will paste it as KANNA_CLOUDFLARED_TOKEN in the next step.
  2. Add a public hostname route: pick your subdomain (e.g. kanna.example.com) and point service to HTTPlocalhost:5174 (or whatever --port you plan to run). Kanna binds 127.0.0.1 automatically when --cloudflared is set, so the tunnel is the only ingress.
  3. Save. The hostname's TLS is terminated at Cloudflare's edge.

3. Write scripts/pm2.env (untracked secrets)

scripts/deploy.sh reads this file and passes the values to kanna as --cloudflared <TOKEN> --password <PW>. Without it, deploy launches kanna with no token and no password — kanna will then run as plain HTTP on localhost, trustProxy will not auto-enable, and every /auth/login POST through the tunnel will return 403 because the CSRF origin check compares the browser's https:// Origin against the server's http:// req.url.

Create scripts/pm2.env (gitignored) with at least:

KANNA_CLOUDFLARED_TOKEN=<paste the connector token from step 2>
KANNA_PASSWORD=<a long random password>
# Optional: pass through to spawned Claude Code agents
# CLAUDE_CODE_OAUTH_TOKEN=sk-ant-oat01-...

Generate a strong password with openssl rand -base64 24.

4. (Migrating from launchd) Unload the old agent

If you previously ran Kanna under launchd, unload it once so pm2 can take over:

launchctl bootout gui/$(id -u)/io.silentium.kanna || true

5. First deploy

scripts/deploy.sh installs pm2 if missing, renders scripts/pm2.config.cjs from the template (via envsubst from brew install gettext), and starts the pm2 process:

./scripts/deploy.sh
pm2 list               # kanna should be "online"
pm2 logs kanna --lines 50

pm2 save persists the running process list. To resurrect after a reboot, run pm2 startup once (pm2 prints the exact command) and then pm2 save again.

The pm2 config sets KANNA_RELOADER=pm2 and KANNA_REPO_DIR=<repo> so the in-app Update button triggers the pm2 reload pipeline (see next section). Override the pm2 process name with KANNA_PM2_PROCESS_NAME before running ./scripts/deploy.sh if you need to run multiple instances.

6. Redeploy / update

Two ways to ship a new build:

a. From the UI (fastest). Click Update in the running app. The server runs git pull --ff-only → conditional bun installbun run buildpm2.reload internally, and the UI reconnects to the fresh build. If any step fails, the UI shows a red banner with the stderr tail and the old build keeps serving.

b. From the terminal. Useful for non-Kanna deploys (e.g., pm2 config edits) or when the UI is unreachable:

git pull
./scripts/deploy.sh

7. Troubleshooting: 403 on login

If the login screen rejects the correct password with 403 behind a Cloudflare (or any HTTPS-terminating) tunnel, the server is running without trustProxy enabled. The CSRF origin check then compares the browser's https://kanna.example.com Origin against the local http://127.0.0.1:<port> req.url and rejects them as mismatched. Two ways to enable it:

  • Recommended. Pass --cloudflared <TOKEN> (or --share) on the kanna command line. Both flags auto-enable trustProxy and bind to 127.0.0.1. With scripts/pm2.env populated, scripts/deploy.sh does this for you — verify with pm2 logs kanna --lines 20 that the startup line includes --cloudflared.
  • Running cloudflared separately? Use --cloudflared on kanna anyway and let kanna spawn the tunnel; the standalone cloudflared daemon does not set trustProxy for you. (There is no standalone --trust-proxy CLI flag today.)

Other things to check if the 403 persists:

  • Cloudflare tunnel public hostname points to http://localhost:<KANNA_PORT>, not https:// — kanna terminates plain HTTP locally.
  • The public hostname's TLS mode is Full or Flexible (Cloudflare → Origin is HTTP), not Full (strict) against a self-signed origin.
  • No Access policy in front of the hostname is stripping or rewriting the Origin header.

8. Update strategies

The update mechanism is abstracted behind UpdateChecker + UpdateReloader interfaces in src/server/update-strategy.ts, selected at startup by KANNA_RELOADER:

| KANNA_RELOADER | Check | Reload | Notes | |---|---|---|---| | unset / supervisor | npm registry for @cuongtran001/kanna | <pm> install -g @cuongtran001/kanna@latest, exit 76, supervisor respawns | Default. End-user path. <pm> auto-detected: bun/npm/pnpm/yarn. Override via KANNA_UPDATE_COMMAND. | | pm2 | git fetch + HEAD vs origin/main | git pull --ff-only → cond. bun installbun run buildpm2 reload | Dev/self-host path. Requires KANNA_REPO_DIR. |

Host-agnostic supervisor mode. When KANNA_RELOADER is unset (default), the in-app Update button works under any process host (pm2, systemd, docker, screen, plain shell) — the internal supervisor catches the child's exit-76 and respawns. The package manager used to install the new version is auto-detected from the running binary path:

  • ~/.bun/bin/kannabun install -g
  • ~/.local/share/pnpm/kanna (or any pnpm/ path) → pnpm add -g
  • ~/.yarn/bin/kanna (or any .yarn/ path) → yarn global add
  • anything else (e.g. /usr/local/bin/kanna, ~/.npm-global/bin/kanna) → npm install -g

If the detected manager is not on PATH, kanna falls back through bun → npm → pnpm → yarn. To override the install command entirely — useful for custom installers, monorepo wrappers, docker pulls, ansible, etc. — set KANNA_UPDATE_COMMAND. Placeholders {package} and {version} are substituted; the result is executed via sh -c.

# Force npm regardless of detection
KANNA_UPDATE_COMMAND="npm install -g {package}@{version}" pm2 start kanna
# Custom: chain pre-install hook
KANNA_UPDATE_COMMAND="my-deploy-hook && npm install -g {package}@{version}" kanna

To add another reload mechanism (e.g., docker, systemd) at the strategy layer, implement UpdateChecker + UpdateReloader and branch inside createUpdateStrategy; no changes to UpdateManager, server.ts, or any client code are needed.

Star History

Contributing

Contributions are welcome! Feel free to open PRs

License

MIT