Observes your Claude Code sessions through supported hooks (Windows and macOS) and turns them into structured professional-work capability classifications, grouped into real-world projects.
Claude Code session → hooks → spool → watcher → session reconstruction
→ semantic classification (LLM) → project grouping → local web UI
By default everything stays on this machine, and the only network egress is
the classification call, which goes through your own local claude CLI (your
existing Claude Code login) and is recorded in a visible egress log — one entry
per call, with the exact payload hash.
An opt-in team mode adds a second egress channel: when upload.enabled is
set, the watcher pushes each session to a shared cloud endpoint so an internal
team can see all its machines' work in one place (see
Team view). It is off unless you configure it, and every
upload is written to the same egress log.
statusline watches every Claude Code session on your machine, in every directory, including personal and side projects. For each finished session it sends a text digest — prompts, file paths, commands run, short assistant excerpts — to an LLM through your own Claude Code login.
- Tool outputs and file contents are never captured. Paths, tool names and (secret-masked) commands are.
- Every LLM call is recorded in the Egress tab with the exact payload hash.
- Label any session Ignore to exclude it, or run
uninstallto stop everything. All data is local, in~/.statusline; delete that directory to erase it. - If you join a team deployment, your sessions' classification results and metadata — categories, technologies with evidence, counts, file paths, git identity — are uploaded to that team's endpoint. Prompt text, assistant excerpts, digest text and command text never upload; that is a hard rule enforced on both ends, not a setting. Who can see your session records is the org's policy: by default a member sees only their own and admins see everyone's. Nothing uploads until you enroll.
Read that paragraph before running the installer on a machine you use for personal work.
- Claude Code installed and logged in
- Node.js 22+ with
nodeon PATH — the installed hooks invoke it by bare name - Windows or macOS
No runtime dependencies at all: there is nothing to npm install. Everything
here runs on built-in Node modules. The devDependencies in package.json are
the formatter and the linter, used only when working on statusline itself.
Two ways in. Both end at the same place: hooks registered, watcher running at login, UI on http://127.0.0.1:45817.
In any Claude Code session:
/plugin marketplace add Instafill/statusline
/plugin install statusline@statusline
The repo is public, so this needs no GitHub account, no gh CLI and no
credentials — Claude Code clones it over plain HTTPS. Claude Code registers the
hooks itself, so ~/.claude/settings.json is never modified by us.
Updates are not automatic: Claude Code ships auto-update ON for its own
marketplaces and OFF for third-party ones like this repo, so a new release
reaches nobody until they ask for it (silently — there is no "a newer version
exists" notice). Either turn it on once, in /plugin → Marketplaces →
statusline → enable auto-update, or update by hand when a release lands —
see Updating.
One step remains, because a plugin cannot start a background service: from any
session, ask Claude to start the statusline watcher — the bundled
/statusline:statusline skill runs the setup for you (registers autostart,
starts the watcher, runs the preflight check). By hand it is:
node "$env:USERPROFILE\.claude\plugins\cache\statusline\statusline\<version>\src\cli.js" autostartWorks with no plugin system involved, and is the path to take if you intend to change the code.
Windows
git clone https://github.com/Instafill/statusline.git
cd statusline
.\install.ps1macOS
git clone https://github.com/Instafill/statusline.git
cd statusline
./install.shThe script verifies Node, installs the hooks, registers the watcher to start at
login, runs the preflight checks, and opens the UI at
http://127.0.0.1:45817. It is safe to re-run at any time — including after
moving the checkout, which repoints the hooks at the new path. Useful flags:
-NoAutostart / --no-autostart, -NoStart / --no-start,
-NoOpen / --no-open.
Prefer to do it by hand? The script does no more than this:
node src/cli.js install # add hook entries to ~/.claude/settings.json
node src/cli.js autostart # start the watcher at login
node src/cli.js doctor # verify the whole chain
Then use Claude Code normally, anywhere on the machine. Sessions appear in the UI live; each one is classified automatically when it ends or has been idle ~10 minutes (configurable) — never per-message.
node src/cli.js doctor
It checks the Node runtime, that claude resolves and is actually logged
in (one real round-trip), that the hook entries are present and current, that
autostart is registered, and that the watcher is up — printing the exact fix for
anything that fails. Use --no-auth to skip the LLM call.
The most common failure by far is the watcher isn't running: hooks keep
spooling events safely, but nothing is ingested or classified, so the UI looks
frozen. node src/cli.js autostart is the durable fix.
Plugin install — note this is claude plugin update, a terminal command:
claude plugin update statusline@statusline
Then restart Claude Code (or /reload-plugins) to apply it, and restart the
watcher so it runs the new code: ask Claude to restart the statusline
watcher, or kill the pid in ~/.statusline/watcher.lock and start it again.
claude plugin install does not upgrade an existing install — it reports
"already installed" and leaves the old version in place. To stop doing this by
hand, enable auto-update once in /plugin → Marketplaces → statusline.
Clone install:
git pull
node src/cli.js install # only needed if the hook set changed
You don't have to remember when that second line is needed — doctor and the
watcher's own startup output both flag an out-of-date install and tell you.
Re-running install is also how a moved or re-cloned checkout takes over: it
repoints the hook entries at the checkout you ran it from.
Removes only statusline's own hook entries and leaves every other setting byte-identical:
node src/cli.js autostart --off
node src/cli.js uninstall
| Command | What it does |
|---|---|
node src\cli.js install |
Add hooks to ~\.claude\settings.json (backup written first; idempotent) |
node src\cli.js uninstall |
Remove only statusline's hook entries |
node src\cli.js status |
Hook/watcher/spool status |
node src\cli.js start |
Run the watcher + local web UI |
node src\cli.js digest <session-id> |
Print the digest that WOULD be sent — nothing is sent |
node src\cli.js classify <session-id> |
Classify one session now (one LLM call) |
node src\cli.js recompute |
Recompute project grouping |
node src\cli.js refold [session-id] |
Re-derive session state from the event logs (after a capture change) |
node src\cli.js join <url> <code> |
Enroll this machine with a team deployment (one time) |
node src\cli.js doctor [--no-auth] |
Check the whole chain and name the fix for anything broken |
npm test |
Run the test suite (no LLM calls, no network) |
- Hooks (
hooks\hook-forward.js) — installed at the user level forUserPromptSubmit,Stop,PostToolUse(file/shell/web/MCP tools),SessionStart,SessionEnd. The hook script writes each event payload to~\.statusline\spool\new\and exits — always silent, always exit 0,asyncso it can never slow a session down. Events are never lost when the watcher isn't running; they sit in the spool until it starts. - Attention beep (optional, off by default) — a separate Claude Code
plugin,
statusline-beep— its own repo, so nothing about it runs unless you install it and nothing in this repo depends on it. Clone or link it into~\.claude\skills\statusline-beep, setbeep.enabledtotruein~\.statusline\config.json, and it plays a short tone whenever a session finishes a response or asks for a permission — so a session that needs you is audible across a wall of tabs. Pitch tells you which project (each repo hashes onto its own note) and the pattern tells you what it wants: one note for "ready to read", two lower notes for "blocked on you". Beeps from all tabs are debounced (beep.min_interval_ms),beep.quiet_hours(e.g.["22:00", "08:00"]) mutes it overnight,beep.commandswaps in your own player, andbeep.notification_filter: "permission"drops the idle nudge. /statuslineskill (optional) — linkskills\statuslineto~\.claude\skills\statuslineand any Claude Code session can answer "what did I work on this week", "what can I actually claim to know", and "why isn't this session classified" from your own data, with the verification caveats attached. It reads through a bundled read-only helper and never writes anything.- Watcher (
node src\cli.js start) — single instance; ingests the spool, appends each session's events to an append-only log, and re-derives session state from that log (restart-safe, duplicate-safe). Large values (file contents, oversized prompts) are pruned to short excerpts at ingest and credential-looking strings are masked. - Classification — a digest (metadata, prompts, best-effort assistant
excerpts from the local transcript, tool-activity summary) is built, capped
(~10k chars), stored with its sha256, then sent through
claude -p --model opus --effort low --safe-mode --tools "" --no-session-persistence.--safe-mode+ aSTATUSLINE_SELFguard prevent the watcher from observing its own classifier calls. Output is validated against a strict schema with one corrective retry. Each call's cost and token usage are recorded inegress.jsonland on the classification itself (_meta.cost_usd). - Evidence merge — LLM technology claims are combined with deterministic
evidence (file extensions edited, commands run, MCP servers used,
dependencies installed): tool evidence forces
hands_on; a session with zero tool calls is capped atdiscussedno matter what the model claims. Every technology carries averifiedflag —truemeans corroborated by tool evidence,falsemeans the classifier's judgment alone (shown dashed with a?in the UI). - Inheritance — a continuation session (resume / compact / fork) in a project with a recent LLM classification inherits it instead of spending a classifier call, unless its tool evidence diverges from the donor's technologies. Inherited results are marked and re-derive evidence levels from the new session's own activity; accumulating ~5 new turns makes the session stale and routes it to the real classifier.
- Heuristic fallback — when the classifier is unreachable (logged out,
offline, timing out), the session still gets a deterministic low-confidence
classification: category from the project's classification history,
technologies from tool evidence. Marked
heuristicin the UI and upgraded automatically once the classifier works again (attempts spaced ≥30 min). - Grouping — sessions in the same git repo (or directory) group
deterministically; similar projects elsewhere get merge suggestions
scored on project-hint/tech/industry overlap, temporal proximity, and
continuation signals. Your manual corrections (labels, field overrides,
moves, renames, dismissals) live in
corrections.jsonand always win.
The UI (http://127.0.0.1:45817)
- Sessions — every observed session, its state, and its classification. Open one to see prompts, tool activity, the classification with per-technology evidence ("why"), the exact digest that was sent, and raw events. Label it (Client work / Internal / Learning / Personal / Ignore), correct fields, reassign its project, or re-classify.
- Projects — grouped sessions with aggregated detected capabilities and merge suggestions (Accept / Dismiss).
- Egress log — every LLM call that left the machine.
- Settings — hook status, config editing, privacy summary.
Team mode collects sessions from every machine in an org into one shared web app, so the team sees all its Claude Code work in one place. The local UI keeps working exactly as before — it stays the machine-local diagnostic surface (digest previews, egress audit, corrections, classify-now). The team UI is read-only.
each machine's watcher → POST /v1/ingest (per-machine credential) → team UI
Accounts are invite-only — there is no sign-up page and no shared token to pass around.
- Open the invite link an admin sends you and sign in with Google. That
creates your account in their org and lands you on the Install page
(
/install), which already shows your single-use enroll command — and, from then on, the live status of every machine connected to you. - Install statusline if you have not already (above), then enroll this machine:
From a plugin install, ask Claude to
node src/cli.js join <url> <code>join <url> <code>and the skill runs it against the right path. A fresh clone can do both at once:Enrolling an install whose watcher is already running needs that watcher restarted afterwards (stop the pid ingit clone https://github.com/Instafill/statusline.git cd statusline .\install.ps1 -JoinUrl <url> -JoinCode <code> # Windows ./install.sh --join <url> <code> # macOS~/.statusline/watcher.lockand runnode src/cli.js start, or log out and back in if autostart is on).
Enrollment writes a credential belonging to that machine alone into
~/.statusline/config.json and binds the machine to you, so your sessions are
attributed to you from the first heartbeat. An admin can revoke a single
machine without touching anyone else. node src/cli.js doctor verifies the
whole chain, including that the endpoint accepts your credential.
Uploads are always content-stripped — classifications, counts and tool evidence flow; prompt text, assistant excerpts, digest text and shell commands never do. This is not configurable, and the server strips again at ingest so no client version can leak content. The credential is never displayed in the UI.
Uploads are incremental: a session is re-sent only when its content actually changes, a 60-second reconcile sweep catches anything missed (including whatever accumulated while you were offline), and a heartbeat every 5 minutes keeps the machine's status current. Every attempt — success or failure — is recorded in the local Egress tab alongside the classifier calls.
The server side — ingest, accounts, the org-wide rollups — is a separate application and is not part of this repo. This repo is the client: it works entirely on its own, and team mode is an opt-in second egress channel it can be pointed at.
Read CLAUDE.md first — it carries the hard rules, and breaking one is
the only way to do real damage here. The short version:
- No npm dependencies. Everything is built-in Node (
http,fs,node:test).package.jsonhas nodependenciesand is meant to stay that way. - Privacy is a contract, not a preference. Tool outputs and file contents
are never captured, and only two channels may touch the network (the
classifier through your own
claudeCLI, and opt-in team uploads/enrollment) — every attempt on either lands inegress.jsonl. ~/.claude/settings.jsonis shared and live. Change it only throughsrc/installer.js, never by hand.- Session state is event-sourced:
<sid>.events.jsonlis the truth and derived state is always re-folded from it.
npm test runs everything (no LLM calls, no network). Tests isolate by
setting STATUSLINE_HOME to a fresh temp dir before requiring anything from
src/; follow that or you will read and write your real data.
ARCHITECTURE.md explains why the aggregation model is shaped the way it is
(one LLM call per session, everything above it free and re-derivable) and where
it stops scaling — read it before changing what gets captured or how
experience is counted. FOLLOWUPS.md holds open work and deliberately
deferred decisions.
Two gotchas that bite everyone once: the hooks on your machine are live,
so editing hooks/hook-forward.js takes effect in your very next Claude Code
session; and changes under src/ need a watcher restart (stop the pid in
~/.statusline/watcher.lock, then node src/cli.js start).
All state lives in %USERPROFILE%\.statusline\ (spool, per-session event logs
and state, projects, corrections, egress log, settings backups). Tool
outputs and file contents are never captured — only tool names, commands
(secret-masked), and file paths. Delete the directory to erase everything.
Exactly two things can leave the machine, and both are logged in
egress.jsonl: the classifier call, and — only with upload.enabled — team
uploads. Raw event logs (<session>.events.jsonl) are never uploaded.
The classifier interface (src\classify\classifier.js) is deliberately
separated from the watcher so the claude -p adapter can be replaced by a
local model later without touching the rest of the app.
~\.statusline\config.json (editable in the Settings tab): port,
idle_minutes, reclassify_min_new_turns, digest caps, classifier
model/timeout_ms, classifier.inherit
(enabled/max_age_hours/min_token_overlap),
classifier.heuristic_fallback, spool rescan interval, retention windows, and
upload (enabled/endpoint/token/debounce_ms — see
Team view; disabled by default).
MIT — see LICENSE.