claude-doom-statusbar 0.2.0 → 0.3.1

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.
package/README.md CHANGED
@@ -1,9 +1,9 @@
1
1
  # claude-doom-statusbar
2
2
 
3
- A DOOM-inspired status bar for the [Claude Code](https://docs.claude.com/en/docs/claude-code) CLI. Your session, read off the Doomguy HUD: a mugshot whose face tracks your health, boxes for usage, model, project and system, and a live list of running subagents.
3
+ A DOOM-inspired status bar for the [Claude Code](https://docs.claude.com/en/docs/claude-code) CLI. Your session, read off the Doomguy HUD: a mugshot whose face tracks your health, boxes for usage, model, project and system, and live lists of running agents and tasks.
4
4
 
5
5
  <p align="center">
6
- <img src="assets/images/hud.png" alt="claude-doom-statusbar HUD: MODEL, USAGE, PROJECT, the DOOM mugshot, ACTIVITY, SUBAGENTS and SYS boxes">
6
+ <img src="assets/images/hud.png" alt="claude-doom-statusbar HUD: MODEL, USAGE, PROJECT, the DOOM mugshot, ACTIVITY, AGENTS, TASKS and SYS boxes">
7
7
  </p>
8
8
 
9
9
  The mugshot is the real DOOM (1993) status-face sprite, rasterised into the terminal at runtime — not ASCII art of it.
@@ -15,9 +15,10 @@ The HUD is a row of boxes centred on the mugshot. Each box is configurable; the
15
15
  - **mugshot** — the Doomguy face. Its HP (how bloodied it looks) follows your *usage headroom* — `min(5h, 7d) rate-limit room`, context as a fallback. It glances around when idle, winces on errors, snarls on writes, grins on a clean finish, dies when you're tapped out, and flashes invulnerable just after an advisor consult.
16
16
  - **MODEL** — model name + reasoning effort (a waxing-moon→sun icon), thinking/fast toggles, output style, and the configured `/advisor` model.
17
17
  - **USAGE** — context window (HP bar), the 5h / 7d rate-limit bars (with reset countdowns), RAM, session cost.
18
- - **PROJECT** — cwd, git branch, ahead/behind, dirty count, lines added/removed, PR state. The cwd, branch and PR are **clickable** (OSC 8 hyperlinks): Ctrl/Cmd-click to open the folder, the branch on the host, or the pull request.
19
- - **ACTIVITY** — a tool-activity "geiger" sparkline (duty-cycle over the last 30 s), running-subagent count, task progress, error count.
20
- - **SUBAGENTS** — a live list of running subagents (type/description + ticking runtime), always visible, widening to fit.
18
+ - **PROJECT** — session name, cwd, git branch, a merged work line (changed files + ahead/behind), lines added/removed, PR state. The cwd, branch and PR are **clickable** (OSC 8 hyperlinks): Ctrl/Cmd-click to open the folder, the branch on the host, or the pull request. Long names are clipped to 24 chars so the box can't blow up.
19
+ - **ACTIVITY** — a tool-activity "geiger" sparkline (duty-cycle over the last 30 s), running-agent count, task progress, error count.
20
+ - **AGENTS** — a live list of running subagents (type/description + ticking runtime), always visible. Long lists scroll within the box height, with ↑/↓ markers counting the rows hidden off-screen.
21
+ - **TASKS** — the session's todo list: settled items (✅ done, ❌ removed) on top, open items (⏩ in-progress, 🎯 pending) below. Scrolls like AGENTS, anchored on the open/settled boundary.
21
22
  - **SYS** — CPU, disk, session length, wall clock.
22
23
 
23
24
  Anything the session can't supply is hidden automatically, so the same config degrades cleanly.
@@ -54,32 +55,6 @@ npm i -g claude-doom-statusbar@latest # global install
54
55
  npx claude-doom-statusbar@latest install
55
56
  ```
56
57
 
57
- ### Manual wiring
58
-
59
- If you'd rather edit `~/.claude/settings.json` by hand, point it at the package's `src/` (use the absolute install path; on Windows use forward slashes):
60
-
61
- ```json
62
- {
63
- "env": { "DOOMBAR_PRESET": "/abs/path/claude-doom-statusbar/presets/full.toml", "FORCE_HYPERLINK": "1" },
64
- "statusLine": {
65
- "type": "command",
66
- "command": "node \"/abs/path/claude-doom-statusbar/src/statusline.js\"",
67
- "refreshInterval": 1
68
- },
69
- "hooks": {
70
- "PreToolUse": [{ "hooks": [{ "type": "command", "command": "node \"/abs/path/claude-doom-statusbar/src/hook.js\"" }] }],
71
- "PostToolUse": [{ "hooks": [{ "type": "command", "command": "node \"/abs/path/claude-doom-statusbar/src/hook.js\"" }] }],
72
- "PostToolUseFailure": [{ "hooks": [{ "type": "command", "command": "node \"/abs/path/claude-doom-statusbar/src/hook.js\"" }] }],
73
- "Stop": [{ "hooks": [{ "type": "command", "command": "node \"/abs/path/claude-doom-statusbar/src/hook.js\"" }] }],
74
- "PermissionDenied": [{ "hooks": [{ "type": "command", "command": "node \"/abs/path/claude-doom-statusbar/src/hook.js\"" }] }],
75
- "SubagentStart": [{ "hooks": [{ "type": "command", "command": "node \"/abs/path/claude-doom-statusbar/src/hook.js\"" }] }],
76
- "SubagentStop": [{ "hooks": [{ "type": "command", "command": "node \"/abs/path/claude-doom-statusbar/src/hook.js\"" }] }],
77
- "TaskCreated": [{ "hooks": [{ "type": "command", "command": "node \"/abs/path/claude-doom-statusbar/src/hook.js\"" }] }],
78
- "TaskCompleted": [{ "hooks": [{ "type": "command", "command": "node \"/abs/path/claude-doom-statusbar/src/hook.js\"" }] }]
79
- }
80
- }
81
- ```
82
-
83
58
  ### Clickable links
84
59
 
85
60
  The cwd / branch / PR are emitted as OSC 8 hyperlinks. They render in any terminal but only click in ones Claude Code detects as hyperlink-capable (iTerm2, kitty, WezTerm, …). **Windows Terminal isn't auto-detected** — launch with `FORCE_HYPERLINK=1` to enable them:
@@ -96,7 +71,7 @@ $env:FORCE_HYPERLINK = "1"; claude
96
71
  - **`default`** — balanced HUD.
97
72
  - **`full`** — every box, the look in the screenshot above.
98
73
 
99
- A preset is TOML: a `[bar]` style block, a `[mugshot]` block, and a list of `[[segment]]` boxes. Each box lists metrics with a render type — `bar`, `number`, `text`, `spark`, `ammo`, `list`, or a `group`. Copy one and rearrange the boxes, swap icons, or change which metrics show.
74
+ A preset is TOML: a `[bar]` style block, a `[mugshot]` block, and a list of `[[segment]]` boxes. Each box lists metrics with a render type — `bar`, `number`, `text`, `spark`, `ammo`, `list`, `scroll`, or a `group`. Copy one and rearrange the boxes, swap icons, or change which metrics show.
100
75
 
101
76
  ## How it works
102
77
 
@@ -110,6 +85,7 @@ See [`docs/ideation/`](docs/ideation/) for the full design write-up.
110
85
 
111
86
  - The status-face sprites are from **DOOM** (1993), id Software.
112
87
  - Mugshot rasterisation by **[chafa](https://hpjansson.org/chafa/)** (Hans Petter Jansson).
88
+ - Prior art that shaped what this HUD shows: **[claude-hud](https://github.com/jarrodwatts/claude-hud)** and **[ccstatusline](https://github.com/sirmalloc/ccstatusline)**.
113
89
 
114
90
  ## License
115
91
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-doom-statusbar",
3
- "version": "0.2.0",
3
+ "version": "0.3.1",
4
4
  "description": "DOOM-inspired status bar for the Claude Code CLI — a mugshot that tracks session health, plus usage, model, project, system, and a live subagent list.",
5
5
  "type": "module",
6
6
  "bin": {
package/presets/full.toml CHANGED
@@ -34,12 +34,12 @@ metric = [
34
34
  type = "box"
35
35
  title = "PROJECT"
36
36
  metric = [
37
- { id = "loc.cwd", render = "text", icon = "📁" },
38
- { id = "git.branch", render = "text", icon = "🌿" },
39
- { group = ["git.behind", "git.ahead"], render = "number", sep = " ", icon = "" },
40
- { id = "git.status", render = "number", icon = "" },
41
- { id = "loc.churn", render = "text", icon = "📝" },
42
- { id = "pr.state", render = "text", icon = "⇧ " },
37
+ { id = "session.name", render = "text", icon = "🎮" },
38
+ { id = "loc.cwd", render = "text", icon = "📁" },
39
+ { id = "git.branch", render = "text", icon = "🌿" },
40
+ { id = "git.work", render = "text" }, #files ⇅ pull/push
41
+ { id = "loc.churn", render = "text", icon = "📝" },
42
+ { id = "pr.state", render = "text", icon = "⇧ " },
43
43
  ]
44
44
 
45
45
  [[segment]]
package/src/render.js CHANGED
@@ -33,7 +33,8 @@ export const SAMPLE = {
33
33
  "usage.reset5h": "2h13m", "usage.reset7d": "3d4h", "sys.session": "5h55m", "loc.churn": "+185 / -62",
34
34
  "context.hp": 78, "ratelimit.5h": 64, "ratelimit.7d": 31, "cost.total": "$1.83",
35
35
  "git.branch": "main", "git.behind": "↓2", "git.ahead": "↑3", "git.status": "3",
36
- "pr.state": "#1234",
36
+ "git.work": "✎ 3 ⇅ ↓2 ↑3", "session.name": "doom-hud-demo",
37
+ "pr.state": "#1234", "loc.cwd": "claude-doom-statusbar",
37
38
  "act.subagents": [["hook events", "2m13s"], ["find configs", "12s"]], "act.agents": "2",
38
39
  "act.tasklist": [
39
40
  { mark:"✅", markRgb: OK, text:"scaffold project" },
package/src/statusline.js CHANGED
@@ -41,6 +41,10 @@ function git(cwd, ...args) {
41
41
  }
42
42
  }
43
43
 
44
+ // Clip a display label to at most `n` code points, ending with … when truncated,
45
+ // so an oversized repo or branch name can't blow up the PROJECT box width.
46
+ const clip = (s, n) => ([...String(s)].length > n ? [...String(s)].slice(0, n - 1).join("") + "…" : String(s));
47
+
44
48
  function _dur(secsF) {
45
49
  let secs = Math.max(0, Math.trunc(secsF));
46
50
  const d = Math.floor(secs / 86400); secs %= 86400;
@@ -141,12 +145,15 @@ export function buildValues(data) {
141
145
  let repoUrl = "";
142
146
  if (repo.host && repo.owner && repo.name) repoUrl = `https://${repo.host}/${repo.owner}/${repo.name}`;
143
147
 
148
+ const sname = data.session_name || data.session_id; // session_name only set via /rename or --name
149
+ if (sname) v["session.name"] = clip(sname, 24);
150
+
144
151
  const cwd = data.cwd || (data.workspace || {}).current_dir;
145
152
  if (cwd) {
146
- const name = path.basename(cwd.replace(/[/\\]+$/, "")) || cwd;
153
+ const name = clip(path.basename(cwd.replace(/[/\\]+$/, "")) || cwd, 24);
147
154
  try { v["loc.cwd"] = _link(name, pathToFileURL(cwd).href); } catch { v["loc.cwd"] = name; }
148
155
  const br = git(cwd, "branch", "--show-current");
149
- if (br) v["git.branch"] = repoUrl ? _link(br, `${repoUrl}/tree/${br}`) : br;
156
+ if (br) { const brLbl = clip(br, 24); v["git.branch"] = repoUrl ? _link(brLbl, `${repoUrl}/tree/${br}`) : brLbl; }
150
157
  const lr = git(cwd, "rev-list", "--count", "--left-right", "@{u}...HEAD");
151
158
  if (lr && lr.includes("\t")) {
152
159
  const [behind, ahead] = lr.split("\t");
@@ -154,6 +161,12 @@ export function buildValues(data) {
154
161
  }
155
162
  const st = git(cwd, "status", "--porcelain");
156
163
  if (st !== null) v["git.status"] = String(st.split("\n").filter((l) => l.trim()).length);
164
+ // Merge changed-file count + pull/push onto one line (icons baked in, like model.mode):
165
+ // "✎ <files> ⇅ ↓<behind> ↑<ahead>" — files first, then pull/push.
166
+ const work = [];
167
+ if (v["git.status"] !== undefined) work.push(`✎ ${v["git.status"]}`);
168
+ if (v["git.behind"] !== undefined) work.push(`⇅ ${v["git.behind"]} ${v["git.ahead"]}`);
169
+ if (work.length) v["git.work"] = work.join(" ");
157
170
  }
158
171
 
159
172
  const pr = data.pr || {};
@@ -246,11 +259,7 @@ export function activityValues(st, now) {
246
259
  const squad = st.squad || {};
247
260
  v["act.agents"] = String(Object.keys(squad).length);
248
261
  const agents = Object.values(squad).sort((a, b) => a.start - b.start);
249
- v["act.subagents"] = agents.map((a) => {
250
- let label = a.desc || a.type || "agent";
251
- if ([...label].length > 20) label = [...label].slice(0, 19).join("") + "…";
252
- return [label, _dur(now - a.start)];
253
- });
262
+ v["act.subagents"] = agents.map((a) => [clip(a.desc || a.type || "agent", 24), _dur(now - a.start)]);
254
263
 
255
264
  const tasks = st.tasks && typeof st.tasks === "object" ? Object.values(st.tasks) : [];
256
265
  const live = tasks.filter((t) => t.status !== "deleted");
@@ -261,7 +270,7 @@ export function activityValues(st, now) {
261
270
  .sort((a, b) => (TASK_ORDER[a.status] - TASK_ORDER[b.status]) || (a.ts - b.ts));
262
271
  v["act.tasklist"] = ordered.map((t) => {
263
272
  const [mark, markRgb] = TASK_MARK[t.status] || ["🎯", null];
264
- return { mark, markRgb, text: t.title };
273
+ return { mark, markRgb, text: clip(t.title, 24) };
265
274
  });
266
275
 
267
276
  if ("errors" in st) v["act.errors"] = String(st.errors);