claude-doom-statusbar 0.1.1 → 0.3.0

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.
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-doom-statusbar",
3
- "version": "0.1.1",
3
+ "version": "0.3.0",
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,13 +33,14 @@ 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
- { mark:"", markRgb: OK, text:"scaffold project" },
40
- { mark:"", markRgb: OK, text:"render engine" },
41
- { mark:"", markRgb: CRIT, text:"port PIL alpha" },
42
- { mark:"", markRgb: null, text:"statusline values" },
40
+ { mark:"", markRgb: OK, text:"scaffold project" },
41
+ { mark:"", markRgb: OK, text:"render engine" },
42
+ { mark:"", markRgb: CRIT, text:"port PIL alpha" },
43
+ { mark:"", markRgb: null, text:"statusline values" },
43
44
  { mark:"🎯", markRgb: null, text:"hook bus" },
44
45
  { mark:"🎯", markRgb: null, text:"installer" },
45
46
  ],
@@ -58,7 +59,8 @@ export function vlen(s) {
58
59
  let n = 0;
59
60
  for (const ch of String(s).replace(ANSI_RE, "")) {
60
61
  const cp = ch.codePointAt(0);
61
- n += (cp >= 0x1f300 && cp <= 0x1faff) || (cp >= 0x23e9 && cp <= 0x23ec) ? 2 : 1;
62
+ n += (cp >= 0x1f300 && cp <= 0x1faff) || (cp >= 0x23e9 && cp <= 0x23ec)
63
+ || cp === 0x2705 || cp === 0x274c ? 2 : 1; // ✅ ❌ are emoji-presentation (2 cols)
62
64
  }
63
65
  return n;
64
66
  }
@@ -368,30 +370,35 @@ export function buildBar(cfg, target, spriteFor) {
368
370
  const items = VALUES[m.id] || [];
369
371
  const H = totalRows - (headers ? 1 : 0);
370
372
  const boundary = items.filter((it) => !Array.isArray(it) &&
371
- (it.mark === "" || it.mark === "")).length; // settled count (ignored for top anchor)
373
+ (it.mark === "" || it.mark === "")).length; // settled count (ignored for top anchor)
372
374
  const win = scrollWindow(items.length, H, m.anchor || "top", boundary);
373
375
  const shown = items.slice(win.start, win.start + H);
374
376
  shown.forEach((item, k) => {
375
377
  const first = k === 0, last = k === shown.length - 1;
376
- const over = first && win.up > 0 ? `↑${win.up} ` : last && win.down > 0 ? `↓${win.down} ` : "";
378
+ const marker = first && win.up > 0 ? `↑${win.up}` : last && win.down > 0 ? `↓${win.down}` : "";
379
+ const tail = marker ? " " + marker : ""; // right-aligned scroll marker (gap + ↑k/↓k)
380
+ const tailW = vlen(tail);
377
381
  let body;
378
382
  if (Array.isArray(item)) { // [left, right] (agents)
379
- const right = f(TEXT) + String(item[1]);
380
- const prefix = over + lbl; // ↑k/↓k marker + icon
381
- const labelMax = Math.max(0, w - vlen(prefix) - vlen(String(item[1])) - 1); // 1 = min gap
383
+ const right = f(TEXT) + String(item[1]) + (marker ? f(TEXT) + tail : "");
384
+ const rightW = vlen(String(item[1])) + tailW;
385
+ const labelMax = Math.max(0, w - vlen(lbl) - rightW - 1); // 1 = min gap
382
386
  let label = String(item[0]);
383
387
  if (vlen(label) > labelMax) label = [...label].slice(0, Math.max(0, labelMax - 1)).join("") + "…";
384
- const left = prefix + f(TEXT) + label;
385
- const room = Math.max(0, w - vlen(left) - vlen(right));
388
+ const left = lbl + f(TEXT) + label;
389
+ const room = Math.max(0, w - vlen(left) - rightW);
386
390
  body = left + " ".repeat(room) + right;
387
391
  } else { // {mark, markRgb, text} (tasks)
388
392
  const markCol = item.markRgb ? f(item.markRgb) : f(TEXT);
393
+ const m = String(item.mark);
394
+ const mPad = m + (vlen(m) < 2 ? " " : ""); // normalize mark to 2 cols so text aligns
389
395
  let text = String(item.text);
390
- const head = over + markCol + String(item.mark) + " " + f(TEXT);
391
- const max = w - vlen(over) - vlen(String(item.mark)) - 1;
396
+ const head = markCol + mPad + " " + f(TEXT);
397
+ const max = w - vlen(mPad) - 1 - tailW; // reserve gap + marker on the right
392
398
  if (vlen(text) > max) text = [...text].slice(0, Math.max(0, max - 1)).join("") + "…";
393
399
  body = head + text;
394
- body += " ".repeat(Math.max(0, w - vlen(body)));
400
+ body += " ".repeat(Math.max(0, w - tailW - vlen(body)));
401
+ if (tail) body += f(TEXT) + tail;
395
402
  }
396
403
  col.push(bgsgrBox(boxRgb) + " " + body + " " + RESET);
397
404
  });
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 || {};
@@ -218,8 +231,8 @@ function sysValues(cwd) {
218
231
  const PERM = { plan: "📋 plan", auto: "⏩ auto", acceptEdits: "⏩ auto", bypassPermissions: "⏩ bypass" };
219
232
  const OK_RGB = [96, 200, 104]; // matches render.js OK (done, green)
220
233
  const CRIT_RGB = [224, 84, 64]; // matches render.js CRIT (deleted, red)
221
- const TASK_MARK = { completed: ["", OK_RGB], deleted: ["", CRIT_RGB], in_progress: ["", null], pending: ["🎯", null] };
222
- const TASK_ORDER = { completed: 0, deleted: 1, in_progress: 2, pending: 3 }; // settled first, then open
234
+ const TASK_MARK = { completed: ["", OK_RGB], deleted: ["", CRIT_RGB], in_progress: ["", null], pending: ["🎯", null] };
235
+ const TASK_ORDER = { completed: 0, deleted: 0, in_progress: 1, pending: 2 }; // settled (done+deleted, by time) first, then open
223
236
 
224
237
  export function activityValues(st, now) {
225
238
  const v = {};