codebyplan 1.13.33 → 1.13.35

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/dist/cli.js CHANGED
@@ -14,7 +14,7 @@ var VERSION, PACKAGE_NAME;
14
14
  var init_version = __esm({
15
15
  "src/lib/version.ts"() {
16
16
  "use strict";
17
- VERSION = "1.13.33";
17
+ VERSION = "1.13.35";
18
18
  PACKAGE_NAME = "codebyplan";
19
19
  }
20
20
  });
@@ -394,7 +394,7 @@ var init_statusline_config = __esm({
394
394
  lines: {
395
395
  identity: true,
396
396
  context: true,
397
- cost: true,
397
+ cost: false,
398
398
  rate_limits: true,
399
399
  repo_pr: true,
400
400
  worktree: true,
@@ -1606,22 +1606,16 @@ function mergeEnabledPluginsIntoSettings(settings, plugins) {
1606
1606
  }
1607
1607
  return settings;
1608
1608
  }
1609
- function stripOwnedHooksFromSettings(settings) {
1609
+ function collapseEmptyHookBlocks(settings) {
1610
1610
  if (!settings.hooks) {
1611
- return settings;
1611
+ return;
1612
1612
  }
1613
1613
  for (const event of Object.keys(settings.hooks)) {
1614
1614
  const eventBlocks = settings.hooks[event];
1615
1615
  if (!eventBlocks) {
1616
1616
  continue;
1617
1617
  }
1618
- const survivingBlocks = [];
1619
- for (const block of eventBlocks) {
1620
- block.hooks = block.hooks.filter((e) => e._owner !== OWNER);
1621
- if (block.hooks.length > 0) {
1622
- survivingBlocks.push(block);
1623
- }
1624
- }
1618
+ const survivingBlocks = eventBlocks.filter((b) => b.hooks.length > 0);
1625
1619
  if (survivingBlocks.length > 0) {
1626
1620
  settings.hooks[event] = survivingBlocks;
1627
1621
  } else {
@@ -1631,6 +1625,54 @@ function stripOwnedHooksFromSettings(settings) {
1631
1625
  if (Object.keys(settings.hooks).length === 0) {
1632
1626
  delete settings.hooks;
1633
1627
  }
1628
+ }
1629
+ function stripOwnedHooksFromSettings(settings) {
1630
+ if (!settings.hooks) {
1631
+ return settings;
1632
+ }
1633
+ for (const event of Object.keys(settings.hooks)) {
1634
+ const eventBlocks = settings.hooks[event];
1635
+ if (!eventBlocks) {
1636
+ continue;
1637
+ }
1638
+ for (const block of eventBlocks) {
1639
+ block.hooks = block.hooks.filter((e) => e._owner !== OWNER);
1640
+ }
1641
+ }
1642
+ collapseEmptyHookBlocks(settings);
1643
+ return settings;
1644
+ }
1645
+ function pruneMissingManagedHooks(settings, hooksJson, onPruned) {
1646
+ if (!settings.hooks) {
1647
+ return settings;
1648
+ }
1649
+ const liveIdsByEvent = /* @__PURE__ */ new Map();
1650
+ for (const [event, matchers] of Object.entries(hooksJson.hooks)) {
1651
+ const ids = /* @__PURE__ */ new Set();
1652
+ for (const block of matchers) {
1653
+ for (const cmd of block.hooks) {
1654
+ ids.add(extractHookId(cmd.command));
1655
+ }
1656
+ }
1657
+ liveIdsByEvent.set(event, ids);
1658
+ }
1659
+ for (const event of Object.keys(settings.hooks)) {
1660
+ const eventBlocks = settings.hooks[event];
1661
+ if (!eventBlocks) {
1662
+ continue;
1663
+ }
1664
+ const liveIds = liveIdsByEvent.get(event) ?? /* @__PURE__ */ new Set();
1665
+ for (const block of eventBlocks) {
1666
+ block.hooks = block.hooks.filter((e) => {
1667
+ const shouldPrune = e._owner === OWNER && e._hook_id !== void 0 && !liveIds.has(e._hook_id);
1668
+ if (shouldPrune && onPruned) {
1669
+ onPruned(e._hook_id);
1670
+ }
1671
+ return !shouldPrune;
1672
+ });
1673
+ }
1674
+ }
1675
+ collapseEmptyHookBlocks(settings);
1634
1676
  return settings;
1635
1677
  }
1636
1678
  var OWNER, PLACEHOLDER_RE, REPLACEMENT, SCALAR_BASE_KEYS, DEPRECATED_BASE_KEYS;
@@ -2558,9 +2600,9 @@ async function runLspFull(projectPath, opts = {}) {
2558
2600
  ` Detected LSP servers: ${servers.map((s) => s.plugin).join(", ")}
2559
2601
  `
2560
2602
  );
2561
- const settingsPath = join10(projectPath, ".claude", "settings.json");
2603
+ const settingsLocalPath = join10(projectPath, ".claude", "settings.local.json");
2562
2604
  let settings = {};
2563
- const existingSettingsRaw = await readJsonFile(settingsPath);
2605
+ const existingSettingsRaw = await readJsonFile(settingsLocalPath);
2564
2606
  if (existingSettingsRaw) {
2565
2607
  settings = existingSettingsRaw;
2566
2608
  }
@@ -2569,15 +2611,15 @@ async function runLspFull(projectPath, opts = {}) {
2569
2611
  servers.map((s) => s.plugin)
2570
2612
  );
2571
2613
  if (dryRun) {
2572
- console.log(` [dry-run] would update ${settingsPath}`);
2614
+ console.log(` [dry-run] would update ${settingsLocalPath}`);
2573
2615
  } else {
2574
2616
  await mkdir4(join10(projectPath, ".claude"), { recursive: true });
2575
2617
  await writeFile6(
2576
- settingsPath,
2618
+ settingsLocalPath,
2577
2619
  JSON.stringify(settings, null, 2) + "\n",
2578
2620
  "utf-8"
2579
2621
  );
2580
- console.log(` Updated ${settingsPath}`);
2622
+ console.log(` Updated ${settingsLocalPath}`);
2581
2623
  }
2582
2624
  const lspJsonPath = join10(projectPath, ".codebyplan", "lsp.json");
2583
2625
  const lspJsonContent = {
@@ -2648,7 +2690,7 @@ async function runLspFull(projectPath, opts = {}) {
2648
2690
  ` Enabled plugins: ${pluginNames.length > 0 ? pluginNames.join(", ") : "none"}`
2649
2691
  );
2650
2692
  console.log(
2651
- ` (settings.json key format: <plugin>@claude-plugins-official)`
2693
+ ` (settings.local.json key format: <plugin>@claude-plugins-official)`
2652
2694
  );
2653
2695
  if (autoInstalled.length > 0) {
2654
2696
  console.log(
@@ -2811,6 +2853,13 @@ async function runInstall(opts, deps = {}) {
2811
2853
  fs3.readFileSync(hooksJsonPath, "utf8")
2812
2854
  );
2813
2855
  mergeHooksIntoSettings(existingSettings, hooksJson);
2856
+ pruneMissingManagedHooks(
2857
+ existingSettings,
2858
+ hooksJson,
2859
+ opts.verbose ? (id) => console.log(
2860
+ `${opts.dryRun ? "[dry-run] would prune" : "pruned"} removed managed hook: ${id}`
2861
+ ) : void 0
2862
+ );
2814
2863
  }
2815
2864
  if (!opts.dryRun) {
2816
2865
  fs3.mkdirSync(path4.dirname(settingsPath), { recursive: true });
@@ -8806,6 +8855,13 @@ async function runUpdate(opts, deps = {}) {
8806
8855
  fs7.readFileSync(hooksJsonPath, "utf8")
8807
8856
  );
8808
8857
  mergeHooksIntoSettings(existingSettings, hooksJson);
8858
+ pruneMissingManagedHooks(
8859
+ existingSettings,
8860
+ hooksJson,
8861
+ opts.verbose ? (id) => console.log(
8862
+ `${opts.dryRun ? "[dry-run] would prune" : "pruned"} removed managed hook: ${id}`
8863
+ ) : void 0
8864
+ );
8809
8865
  }
8810
8866
  if (!opts.dryRun) {
8811
8867
  fs7.mkdirSync(path8.dirname(settingsPath), { recursive: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codebyplan",
3
- "version": "1.13.33",
3
+ "version": "1.13.35",
4
4
  "description": "CLI for CodeByPlan — AI-powered development planning and tracking",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ // @scope: org-shared
2
3
  // @hook: NOT-A-HOOK (statusLine renderer, invoked via the cbp-statusline.sh dispatcher)
3
4
  // Claude Code Status Line — node renderer (ESM; .mjs forces ESM regardless of the
4
5
  // host repo's package.json "type", so the script is portable into any consumer).
@@ -42,7 +43,10 @@ function main() {
42
43
  };
43
44
 
44
45
  const MODEL_ID = get(data, ["model", "id"], "");
45
- const MODEL_NAME = get(data, ["model", "display_name"], "");
46
+ const MODEL_NAME = get(data, ["model", "display_name"], "").replace(
47
+ /\s*\(1M context\)\s*$/,
48
+ ""
49
+ );
46
50
  const CWD = get(data, ["cwd"], "");
47
51
  const WS_CURRENT_DIR = get(data, ["workspace", "current_dir"], "");
48
52
  const WS_REPO_HOST = get(data, ["workspace", "repo", "host"], "");
@@ -55,26 +59,6 @@ function main() {
55
59
  const LINES_DEL = get(data, ["cost", "total_lines_removed"], 0);
56
60
  const CTX_SIZE = get(data, ["context_window", "context_window_size"], 200000);
57
61
  const CTX_PCT = get(data, ["context_window", "used_percentage"], 0);
58
- const CUR_IN = get(
59
- data,
60
- ["context_window", "current_usage", "input_tokens"],
61
- 0
62
- );
63
- const CUR_OUT = get(
64
- data,
65
- ["context_window", "current_usage", "output_tokens"],
66
- 0
67
- );
68
- const CACHE_CREATE = get(
69
- data,
70
- ["context_window", "current_usage", "cache_creation_input_tokens"],
71
- 0
72
- );
73
- const CACHE_READ = get(
74
- data,
75
- ["context_window", "current_usage", "cache_read_input_tokens"],
76
- 0
77
- );
78
62
  const EXCEEDS_200K = get(data, ["exceeds_200k_tokens"], false);
79
63
  const EFFORT = get(data, ["effort", "level"], "");
80
64
  const RATE_5H_PCT = get(
@@ -113,7 +97,7 @@ function main() {
113
97
  const cfg = {
114
98
  identity: true,
115
99
  context: true,
116
- cost: true,
100
+ cost: false,
117
101
  rate_limits: true,
118
102
  repo_pr: true,
119
103
  worktree: true,
@@ -234,7 +218,8 @@ function main() {
234
218
  const delta = Math.trunc(Number(epoch)) - cbpNow();
235
219
  if (delta <= 0) return "now";
236
220
  if (delta >= 86400) return `${Math.floor(delta / 86400)}d`;
237
- if (delta >= 3600) return `${Math.floor(delta / 3600)}h`;
221
+ if (delta >= 3600)
222
+ return `${Math.floor(delta / 3600)}h${Math.floor((delta % 3600) / 60)}m`;
238
223
  return `${Math.floor(delta / 60)}m`;
239
224
  };
240
225
 
@@ -266,10 +251,11 @@ function main() {
266
251
  // LINE 1 — Identity
267
252
  // ============================================================
268
253
  if (shouldShow("IDENTITY", cfg.identity)) {
254
+ // Line A — location: folder + branch + wt/session/agent prefix
269
255
  let L1 = "";
270
256
  if (FOLDER) {
271
- L1 = `${C.BOLD}${C.BLUE}${FOLDER}${C.RST}`;
272
- if (BRANCH) L1 += ` ${C.DIM}⎇${C.RST}${C.CYAN}${BRANCH}${C.RST}`;
257
+ L1 = `📂 ${C.BOLD}${C.BLUE}${FOLDER}${C.RST}`;
258
+ if (BRANCH) L1 += ` ${C.DIM}🔀${C.RST} ${C.CYAN}${BRANCH}${C.RST}`;
273
259
  L1 += " ";
274
260
  }
275
261
  if (WT_NAME) {
@@ -279,16 +265,19 @@ function main() {
279
265
  } else if (AGENT_NAME) {
280
266
  L1 += `${C.DIM}agent:${C.RST}${C.MAGENTA}${AGENT_NAME}${C.RST} `;
281
267
  }
268
+ // Line B — model: display name + effort + style + vim
269
+ let L1B = "";
282
270
  if (MODEL_NAME) {
283
- L1 += `${C.BOLD}${C.CYAN}${MODEL_NAME}${C.RST}`;
271
+ L1B += `${C.BOLD}${C.CYAN}${MODEL_NAME}${C.RST}`;
284
272
  } else if (MODEL_ID) {
285
- L1 += `${C.BOLD}${C.CYAN}${MODEL_ID}${C.RST}`;
273
+ L1B += `${C.BOLD}${C.CYAN}${MODEL_ID}${C.RST}`;
286
274
  }
287
- if (EFFORT) L1 += ` ${C.DIM}effort:${C.RST}${EFFORT}`;
275
+ if (EFFORT) L1B += ` ${C.DIM}effort:${C.RST}${EFFORT}`;
288
276
  if (OUTPUT_STYLE && OUTPUT_STYLE !== "default")
289
- L1 += ` ${C.DIM}style:${C.RST}${OUTPUT_STYLE}`;
290
- if (VIM_MODE) L1 += ` ${C.DIM}[${VIM_MODE}]${C.RST}`;
277
+ L1B += ` ${C.DIM}style:${C.RST}${OUTPUT_STYLE}`;
278
+ if (VIM_MODE) L1B += ` ${C.DIM}[${VIM_MODE}]${C.RST}`;
291
279
  if (L1) out.push(L1);
280
+ if (L1B) out.push(L1B);
292
281
  }
293
282
 
294
283
  // ============================================================
@@ -306,7 +295,6 @@ function main() {
306
295
  const bar = "▓".repeat(filled) + "░".repeat(empty);
307
296
 
308
297
  let L2 = `${barColor}${bar}${C.RST} ${barColor}${numStr(CTX_PCT)}%${C.RST}${C.DIM}/${fmtK(CTX_SIZE)}${C.RST}`;
309
- L2 += ` ${C.DIM}in:${C.RST}${C.BLUE}${fmtK(CUR_IN)}${C.RST} ${C.DIM}out:${C.RST}${C.MAGENTA}${fmtK(CUR_OUT)}${C.RST} ${C.DIM}cache_cr:${C.RST}${fmtK(CACHE_CREATE)} ${C.DIM}cache_rd:${C.RST}${fmtK(CACHE_READ)}`;
310
298
  if (EXCEEDS_200K === true) L2 += ` ${C.YELLOW}⚠ 200k+${C.RST}`;
311
299
  out.push(L2);
312
300
  }
@@ -333,7 +321,7 @@ function main() {
333
321
  if (gte(r5, 80)) c5 = C.RED;
334
322
  else if (gte(r5, 60)) c5 = C.YELLOW;
335
323
  else c5 = C.GREEN;
336
- L4 = `${C.DIM}5h:${C.RST}${c5}${r5}%${C.RST} ${C.DIM}(resets in ${fmtRelTime(RATE_5H_RESETS)})${C.RST}`;
324
+ L4 = `${C.DIM}5h:${C.RST}${c5}${r5}%${C.RST} ${C.DIM}⏱${C.RST} ${fmtRelTime(RATE_5H_RESETS)}`;
337
325
  }
338
326
  if (has7d) {
339
327
  const r7 = fmtPct(RATE_7D_PCT);
@@ -341,7 +329,7 @@ function main() {
341
329
  if (gte(r7, 80)) c7 = C.RED;
342
330
  else if (gte(r7, 60)) c7 = C.YELLOW;
343
331
  else c7 = C.GREEN;
344
- const seg7 = `${C.DIM}7d:${C.RST}${c7}${r7}%${C.RST} ${C.DIM}(resets in ${fmtRelTime(RATE_7D_RESETS)})${C.RST}`;
332
+ const seg7 = `${C.DIM}7d:${C.RST}${c7}${r7}%${C.RST} ${C.DIM}⏱${C.RST} ${fmtRelTime(RATE_7D_RESETS)}`;
345
333
  L4 = L4 ? `${L4} ${C.DIM}|${C.RST} ${seg7}` : seg7;
346
334
  }
347
335
  out.push(L4);
@@ -489,9 +477,9 @@ function main() {
489
477
  typeof pParsed?.version === "string" ? pParsed.version : "";
490
478
  installed = iVer;
491
479
  if (mVer && iVer && mVer !== iVer) {
492
- // manifest ≠ installed → .claude is out of sync run claude update
493
- // (mirrors the doctor's version_skip → in_sync:false). No npm info in
494
- // the offline fallback, so never the newer-available marker.
480
+ // manifest ≠ installed → .claude is out of sync. Thenag was removed
481
+ // (CHK-195); only the bare version renders. inSync is retained for the
482
+ // guard shape but no longer drives any output.
495
483
  inSync = false;
496
484
  }
497
485
  } catch {
@@ -502,13 +490,7 @@ function main() {
502
490
  }
503
491
 
504
492
  if (!guarded && installed) {
505
- let L8 = `${C.DIM}cbp${C.RST} ${installed}`;
506
- if (newer && latest) {
507
- L8 += ` ${C.YELLOW}↑${latest}${C.RST}`;
508
- }
509
- if (!inSync) {
510
- L8 += ` ${C.YELLOW}⟳ run claude update${C.RST}`;
511
- }
493
+ const L8 = `${C.DIM}cbp${C.RST} ${installed}`;
512
494
  out.push(L8);
513
495
  }
514
496
  }
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python3
2
+ # @scope: org-shared
2
3
  # @hook: NOT-A-HOOK (statusLine renderer, invoked via the cbp-statusline.sh dispatcher)
3
4
  # Claude Code Status Line — python renderer.
4
5
  # Byte-identical output to the bash renderer in cbp-statusline.sh and the node
@@ -11,6 +12,7 @@
11
12
  import json
12
13
  import math
13
14
  import os
15
+ import re
14
16
  import subprocess
15
17
  import sys
16
18
  import time
@@ -41,7 +43,7 @@ def main():
41
43
  root = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", ".."))
42
44
 
43
45
  MODEL_ID = _get(data, ["model", "id"], "")
44
- MODEL_NAME = _get(data, ["model", "display_name"], "")
46
+ MODEL_NAME = re.sub(r"\s*\(1M context\)\s*$", "", _get(data, ["model", "display_name"], ""))
45
47
  CWD = _get(data, ["cwd"], "")
46
48
  WS_CURRENT_DIR = _get(data, ["workspace", "current_dir"], "")
47
49
  WS_REPO_HOST = _get(data, ["workspace", "repo", "host"], "")
@@ -54,10 +56,6 @@ def main():
54
56
  LINES_DEL = _get(data, ["cost", "total_lines_removed"], 0)
55
57
  CTX_SIZE = _get(data, ["context_window", "context_window_size"], 200000)
56
58
  CTX_PCT = _get(data, ["context_window", "used_percentage"], 0)
57
- CUR_IN = _get(data, ["context_window", "current_usage", "input_tokens"], 0)
58
- CUR_OUT = _get(data, ["context_window", "current_usage", "output_tokens"], 0)
59
- CACHE_CREATE = _get(data, ["context_window", "current_usage", "cache_creation_input_tokens"], 0)
60
- CACHE_READ = _get(data, ["context_window", "current_usage", "cache_read_input_tokens"], 0)
61
59
  EXCEEDS_200K = _get(data, ["exceeds_200k_tokens"], False)
62
60
  EFFORT = _get(data, ["effort", "level"], "")
63
61
  RATE_5H_PCT = _get(data, ["rate_limits", "five_hour", "used_percentage"], "")
@@ -78,7 +76,7 @@ def main():
78
76
 
79
77
  # ---- Config: line toggles + no_color -------------------------------------
80
78
  cfg = {
81
- "identity": True, "context": True, "cost": True,
79
+ "identity": True, "context": True, "cost": False,
82
80
  "rate_limits": True, "repo_pr": True, "worktree": True,
83
81
  "infra_drift": True, "package_freshness": True, "no_color": False,
84
82
  }
@@ -172,7 +170,7 @@ def main():
172
170
  if delta >= 86400:
173
171
  return "%dd" % (delta // 86400)
174
172
  if delta >= 3600:
175
- return "%dh" % (delta // 3600)
173
+ return "%dh%dm" % (delta // 3600, (delta % 3600) // 60)
176
174
  return "%dm" % (delta // 60)
177
175
 
178
176
  def gte(v, t):
@@ -202,11 +200,12 @@ def main():
202
200
 
203
201
  # ===== LINE 1 — Identity =====
204
202
  if should_show("IDENTITY", cfg["identity"]):
203
+ # Line A — location: folder + branch + wt/session/agent prefix
205
204
  l1 = ""
206
205
  if folder:
207
- l1 = "%s%s%s%s" % (BOLD, BLUE, folder, RST)
206
+ l1 = "📂 %s%s%s%s" % (BOLD, BLUE, folder, RST)
208
207
  if branch:
209
- l1 += " %s⎇%s%s%s%s" % (DIM, RST, CYAN, branch, RST)
208
+ l1 += " %s🔀%s %s%s%s" % (DIM, RST, CYAN, branch, RST)
210
209
  l1 += " "
211
210
  if WT_NAME:
212
211
  l1 += "%swt:%s%s%s%s " % (DIM, RST, MAGENTA, WT_NAME, RST)
@@ -214,18 +213,22 @@ def main():
214
213
  l1 += "%ssession:%s%s%s%s " % (DIM, RST, MAGENTA, SESSION_NAME, RST)
215
214
  elif AGENT_NAME:
216
215
  l1 += "%sagent:%s%s%s%s " % (DIM, RST, MAGENTA, AGENT_NAME, RST)
216
+ # Line B — model: display name + effort + style + vim
217
+ l1b = ""
217
218
  if MODEL_NAME:
218
- l1 += "%s%s%s%s" % (BOLD, CYAN, MODEL_NAME, RST)
219
+ l1b += "%s%s%s%s" % (BOLD, CYAN, MODEL_NAME, RST)
219
220
  elif MODEL_ID:
220
- l1 += "%s%s%s%s" % (BOLD, CYAN, MODEL_ID, RST)
221
+ l1b += "%s%s%s%s" % (BOLD, CYAN, MODEL_ID, RST)
221
222
  if EFFORT:
222
- l1 += " %seffort:%s%s" % (DIM, RST, EFFORT)
223
+ l1b += " %seffort:%s%s" % (DIM, RST, EFFORT)
223
224
  if OUTPUT_STYLE and OUTPUT_STYLE != "default":
224
- l1 += " %sstyle:%s%s" % (DIM, RST, OUTPUT_STYLE)
225
+ l1b += " %sstyle:%s%s" % (DIM, RST, OUTPUT_STYLE)
225
226
  if VIM_MODE:
226
- l1 += " %s[%s]%s" % (DIM, VIM_MODE, RST)
227
+ l1b += " %s[%s]%s" % (DIM, VIM_MODE, RST)
227
228
  if l1:
228
229
  out.append(l1)
230
+ if l1b:
231
+ out.append(l1b)
229
232
 
230
233
  # ===== LINE 2 — Context window =====
231
234
  if should_show("CONTEXT", cfg["context"]):
@@ -245,12 +248,6 @@ def main():
245
248
  l2 = "%s%s%s %s%s%%%s%s/%s%s" % (
246
249
  bar_color, bar, RST, bar_color, num_str(CTX_PCT), RST, DIM, fmt_k(CTX_SIZE), RST,
247
250
  )
248
- l2 += " %sin:%s%s%s%s %sout:%s%s%s%s %scache_cr:%s%s %scache_rd:%s%s" % (
249
- DIM, RST, BLUE, fmt_k(CUR_IN), RST,
250
- DIM, RST, MAGENTA, fmt_k(CUR_OUT), RST,
251
- DIM, RST, fmt_k(CACHE_CREATE),
252
- DIM, RST, fmt_k(CACHE_READ),
253
- )
254
251
  if EXCEEDS_200K is True:
255
252
  l2 += " %s⚠ 200k+%s" % (YELLOW, RST)
256
253
  out.append(l2)
@@ -281,8 +278,8 @@ def main():
281
278
  c5 = YELLOW
282
279
  else:
283
280
  c5 = GREEN
284
- l4 = "%s5h:%s%s%s%%%s %s(resets in %s)%s" % (
285
- DIM, RST, c5, r5, RST, DIM, fmt_rel_time(RATE_5H_RESETS), RST,
281
+ l4 = "%s5h:%s%s%s%%%s %s⏱%s %s" % (
282
+ DIM, RST, c5, r5, RST, DIM, RST, fmt_rel_time(RATE_5H_RESETS),
286
283
  )
287
284
  if has_7d:
288
285
  r7 = fmt_pct(RATE_7D_PCT)
@@ -292,8 +289,8 @@ def main():
292
289
  c7 = YELLOW
293
290
  else:
294
291
  c7 = GREEN
295
- seg7 = "%s7d:%s%s%s%%%s %s(resets in %s)%s" % (
296
- DIM, RST, c7, r7, RST, DIM, fmt_rel_time(RATE_7D_RESETS), RST,
292
+ seg7 = "%s7d:%s%s%s%%%s %s⏱%s %s" % (
293
+ DIM, RST, c7, r7, RST, DIM, RST, fmt_rel_time(RATE_7D_RESETS),
297
294
  )
298
295
  l4 = ("%s %s|%s %s" % (l4, DIM, RST, seg7)) if l4 else seg7
299
296
  out.append(l4)
@@ -393,9 +390,9 @@ def main():
393
390
  i_ver = i_ver if isinstance(i_ver, str) else ""
394
391
  _installed = i_ver
395
392
  if m_ver and i_ver and m_ver != i_ver:
396
- # manifest != installed -> .claude is out of sync -> run claude update
397
- # (mirrors the doctor's version_skip -> in_sync:false). No npm info
398
- # in the offline fallback, so never the up-arrow newer marker.
393
+ # manifest != installed -> .claude is out of sync. The nag was
394
+ # removed (CHK-195); only the bare version renders. _in_sync is
395
+ # retained for the guard shape but no longer drives any output.
399
396
  _in_sync = False
400
397
  except Exception:
401
398
  # Can't read files → hide segment.
@@ -403,10 +400,6 @@ def main():
403
400
 
404
401
  if not _guarded and _installed:
405
402
  l8 = "%scbp%s %s" % (DIM, RST, _installed)
406
- if _newer and _latest:
407
- l8 += " %s↑%s%s" % (YELLOW, _latest, RST)
408
- if not _in_sync:
409
- l8 += " %s⟳ run claude update%s" % (YELLOW, RST)
410
403
  out.append(l8)
411
404
 
412
405
  sys.stdout.write(("\n".join(out) + "\n") if out else "")
@@ -1,4 +1,5 @@
1
1
  #!/bin/bash
2
+ # @scope: org-shared
2
3
  # @hook: NOT-A-HOOK (statusLine renderer, invoked by settings.json statusLine.command)
3
4
  # Claude Code Status Line — multi-runtime dispatcher + bash renderer
4
5
  # Purpose: Renders up to 6 structured lines of Claude Code status from stdin JSON.
@@ -89,10 +90,6 @@ eval "$(echo "$INPUT" | jq -r '
89
90
  @sh "LINES_DEL=\(.cost.total_lines_removed // 0)",
90
91
  @sh "CTX_SIZE=\(.context_window.context_window_size // 200000)",
91
92
  @sh "CTX_PCT=\(.context_window.used_percentage // 0)",
92
- @sh "CUR_IN=\(.context_window.current_usage.input_tokens // 0)",
93
- @sh "CUR_OUT=\(.context_window.current_usage.output_tokens // 0)",
94
- @sh "CACHE_CREATE=\(.context_window.current_usage.cache_creation_input_tokens // 0)",
95
- @sh "CACHE_READ=\(.context_window.current_usage.cache_read_input_tokens // 0)",
96
93
  @sh "EXCEEDS_200K=\(.exceeds_200k_tokens // false)",
97
94
  @sh "EFFORT=\(.effort.level // "")",
98
95
  @sh "RATE_5H_PCT=\(.rate_limits.five_hour.used_percentage // "")",
@@ -114,18 +111,23 @@ eval "$(echo "$INPUT" | jq -r '
114
111
  # Note: RATE_*_PCT use `// ""` (not `// 0`) so the absence gate below distinguishes
115
112
  # "API said 0% used" from "rate_limits object absent". Do not change to `// 0`.
116
113
 
114
+ # Strip trailing ' (1M context)' suffix from model display name (whitespace-tolerant).
115
+ MODEL_NAME="$(printf '%s' "$MODEL_NAME" | sed -E 's/[[:space:]]*\(1M context\)[[:space:]]*$//')"
116
+
117
117
  # ---- Config: line toggles + no_color from .codebyplan/statusline.json --------
118
- CFG_IDENTITY=true; CFG_CONTEXT=true; CFG_COST=true
118
+ CFG_IDENTITY=true; CFG_CONTEXT=true; CFG_COST=false
119
119
  CFG_RATE_LIMITS=true; CFG_REPO_PR=true; CFG_WORKTREE=true; CFG_INFRA_DRIFT=true; CFG_PACKAGE_FRESHNESS=true; CFG_NO_COLOR=false
120
120
  CBP_CFG="$CBP_ROOT/.codebyplan/statusline.json"
121
121
  if [ -f "$CBP_CFG" ] && command -v jq >/dev/null 2>&1; then
122
122
  # Use `!= false` / `== true` (NOT jq `//`): the `//` operator treats an explicit
123
123
  # `false` as absent and would coerce a hidden line back to visible. `!= false`
124
- # yields true for missing/true and false only for an explicit false.
124
+ # yields true for missing/true and false only for an explicit false — correct for
125
+ # default-on lines. `cost` defaults OFF (CHK-195), so it uses `== true`: a missing
126
+ # key falls back to hidden, mirroring the node/python hardcoded `cost: false`.
125
127
  eval "$(jq -r '
126
128
  "CFG_IDENTITY=\(.lines.identity != false)",
127
129
  "CFG_CONTEXT=\(.lines.context != false)",
128
- "CFG_COST=\(.lines.cost != false)",
130
+ "CFG_COST=\(.lines.cost == true)",
129
131
  "CFG_RATE_LIMITS=\(.lines.rate_limits != false)",
130
132
  "CFG_REPO_PR=\(.lines.repo_pr != false)",
131
133
  "CFG_WORKTREE=\(.lines.worktree != false)",
@@ -209,7 +211,7 @@ fmt_rel_time() {
209
211
  local delta=$(( epoch - now ))
210
212
  if [ "$delta" -le 0 ]; then printf "now"
211
213
  elif [ "$delta" -ge 86400 ]; then printf "%dd" $(( delta / 86400 ))
212
- elif [ "$delta" -ge 3600 ]; then printf "%dh" $(( delta / 3600 ))
214
+ elif [ "$delta" -ge 3600 ]; then printf "%dh%dm" $(( delta / 3600 )) $(( (delta % 3600) / 60 ))
213
215
  else printf "%dm" $(( delta / 60 )); fi
214
216
  }
215
217
 
@@ -229,12 +231,13 @@ fi
229
231
  # ============================================================
230
232
  if should_show IDENTITY "$CFG_IDENTITY"; then
231
233
  L1=""
234
+ L1B=""
232
235
 
233
- # Folder + branch git-derived, visible even without a registered worktree
236
+ # Line Alocation: folder + branch + wt/session/agent prefix
234
237
  if [ -n "$FOLDER" ]; then
235
- L1="${BOLD}${BLUE}${FOLDER}${RST}"
238
+ L1="📂 ${BOLD}${BLUE}${FOLDER}${RST}"
236
239
  if [ -n "$BRANCH" ]; then
237
- L1="${L1} ${DIM}⎇${RST}${CYAN}${BRANCH}${RST}"
240
+ L1="${L1} ${DIM}🔀${RST} ${CYAN}${BRANCH}${RST}"
238
241
  fi
239
242
  L1="${L1} "
240
243
  fi
@@ -248,31 +251,34 @@ if should_show IDENTITY "$CFG_IDENTITY"; then
248
251
  L1="${L1}${DIM}agent:${RST}${MAGENTA}${AGENT_NAME}${RST} "
249
252
  fi
250
253
 
251
- # Model — display name only (no redundant id-in-parens)
254
+ # Line B model: display name + effort + style + vim
252
255
  if [ -n "$MODEL_NAME" ]; then
253
- L1="${L1}${BOLD}${CYAN}${MODEL_NAME}${RST}"
256
+ L1B="${L1B}${BOLD}${CYAN}${MODEL_NAME}${RST}"
254
257
  elif [ -n "$MODEL_ID" ]; then
255
- L1="${L1}${BOLD}${CYAN}${MODEL_ID}${RST}"
258
+ L1B="${L1B}${BOLD}${CYAN}${MODEL_ID}${RST}"
256
259
  fi
257
260
 
258
261
  # Effort level (when present)
259
262
  if [ -n "$EFFORT" ]; then
260
- L1="${L1} ${DIM}effort:${RST}${EFFORT}"
263
+ L1B="${L1B} ${DIM}effort:${RST}${EFFORT}"
261
264
  fi
262
265
 
263
266
  # Output style (when present and not "default")
264
267
  if [ -n "$OUTPUT_STYLE" ] && [ "$OUTPUT_STYLE" != "default" ]; then
265
- L1="${L1} ${DIM}style:${RST}${OUTPUT_STYLE}"
268
+ L1B="${L1B} ${DIM}style:${RST}${OUTPUT_STYLE}"
266
269
  fi
267
270
 
268
271
  # Vim mode
269
272
  if [ -n "$VIM_MODE" ]; then
270
- L1="${L1} ${DIM}[${VIM_MODE}]${RST}"
273
+ L1B="${L1B} ${DIM}[${VIM_MODE}]${RST}"
271
274
  fi
272
275
 
273
276
  if [ -n "$L1" ]; then
274
277
  printf "%b\n" "$L1"
275
278
  fi
279
+ if [ -n "$L1B" ]; then
280
+ printf "%b\n" "$L1B"
281
+ fi
276
282
  fi
277
283
 
278
284
  # ============================================================
@@ -295,13 +301,8 @@ if should_show CONTEXT "$CFG_CONTEXT"; then
295
301
  for ((i=0; i<EMPTY; i++)); do BAR+="░"; done
296
302
 
297
303
  CTX_SIZE_F=$(fmt_k "$CTX_SIZE")
298
- CUR_IN_F=$(fmt_k "$CUR_IN")
299
- CUR_OUT_F=$(fmt_k "$CUR_OUT")
300
- CACHE_CR_F=$(fmt_k "$CACHE_CREATE")
301
- CACHE_RD_F=$(fmt_k "$CACHE_READ")
302
304
 
303
305
  L2="${BAR_COLOR}${BAR}${RST} ${BAR_COLOR}${CTX_PCT}%${RST}${DIM}/${CTX_SIZE_F}${RST}"
304
- L2="${L2} ${DIM}in:${RST}${BLUE}${CUR_IN_F}${RST} ${DIM}out:${RST}${MAGENTA}${CUR_OUT_F}${RST} ${DIM}cache_cr:${RST}${CACHE_CR_F} ${DIM}cache_rd:${RST}${CACHE_RD_F}"
305
306
 
306
307
  if [ "$EXCEEDS_200K" = "true" ]; then
307
308
  L2="${L2} ${YELLOW}⚠ 200k+${RST}"
@@ -343,7 +344,7 @@ if should_show RATE_LIMITS "$CFG_RATE_LIMITS"; then
343
344
  C5="$GREEN"
344
345
  fi
345
346
  REL5=$(fmt_rel_time "$RATE_5H_RESETS")
346
- L4="${DIM}5h:${RST}${C5}${R5}%${RST} ${DIM}(resets in ${REL5})${RST}"
347
+ L4="${DIM}5h:${RST}${C5}${R5}%${RST} ${DIM}⏱${RST} ${REL5}"
347
348
  fi
348
349
 
349
350
  if [ -n "$RATE_7D_PCT" ] && [ "$RATE_7D_RESETS" != "0" ]; then
@@ -356,7 +357,7 @@ if should_show RATE_LIMITS "$CFG_RATE_LIMITS"; then
356
357
  C7="$GREEN"
357
358
  fi
358
359
  REL7=$(fmt_rel_time "$RATE_7D_RESETS")
359
- SEG7="${DIM}7d:${RST}${C7}${R7}%${RST} ${DIM}(resets in ${REL7})${RST}"
360
+ SEG7="${DIM}7d:${RST}${C7}${R7}%${RST} ${DIM}⏱${RST} ${REL7}"
360
361
  if [ -n "$L4" ]; then
361
362
  L4="${L4} ${DIM}|${RST} ${SEG7}"
362
363
  else
@@ -479,9 +480,9 @@ if should_show PACKAGE_FRESHNESS "$CFG_PACKAGE_FRESHNESS"; then
479
480
  _cbp_iver="$(jq -r '.version // ""' "$_cbp_pkg" 2>/dev/null)"
480
481
  _CBP_INSTALLED="$_cbp_iver"
481
482
  if [ -n "$_cbp_mver" ] && [ -n "$_cbp_iver" ] && [ "$_cbp_mver" != "$_cbp_iver" ]; then
482
- # manifest ≠ installed → .claude is out of sync run claude update
483
- # (mirrors the doctor's version_skip → in_sync:false). No npm info in the
484
- # offline fallback, so never the newer-available marker.
483
+ # manifest ≠ installed → .claude is out of sync. Thenag was removed
484
+ # (CHK-195); only the bare version renders. _CBP_IN_SYNC is retained for
485
+ # the guard shape but no longer drives any output.
485
486
  _CBP_IN_SYNC=false
486
487
  fi
487
488
  else
@@ -492,12 +493,6 @@ if should_show PACKAGE_FRESHNESS "$CFG_PACKAGE_FRESHNESS"; then
492
493
 
493
494
  if [ "$_CBP_GUARDED" = "false" ] && [ -n "$_CBP_INSTALLED" ]; then
494
495
  L8="${DIM}cbp${RST} ${_CBP_INSTALLED}"
495
- if [ "$_CBP_NEWER" = "true" ] && [ -n "$_CBP_LATEST" ]; then
496
- L8="${L8} ${YELLOW}↑${_CBP_LATEST}${RST}"
497
- fi
498
- if [ "$_CBP_IN_SYNC" = "false" ]; then
499
- L8="${L8} ${YELLOW}⟳ run claude update${RST}"
500
- fi
501
496
  printf "%b\n" "$L8"
502
497
  fi
503
498
  fi
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env node
2
+ // @scope: org-shared
2
3
  // @hook: NOT-A-HOOK (subagentStatusLine renderer, invoked via the cbp-subagent-statusline.sh dispatcher)
3
4
  // Claude Code Subagent Status Line — node renderer (ESM; .mjs forces ESM regardless
4
5
  // of the host repo's package.json "type"). Byte-identical output to the bash renderer
@@ -1,4 +1,5 @@
1
1
  #!/usr/bin/env python3
2
+ # @scope: org-shared
2
3
  # @hook: NOT-A-HOOK (subagentStatusLine renderer, invoked via the cbp-subagent-statusline.sh dispatcher)
3
4
  # Claude Code Subagent Status Line — python renderer. Byte-identical output to the
4
5
  # bash renderer in cbp-subagent-statusline.sh and the node renderer in
@@ -1,4 +1,5 @@
1
1
  #!/bin/bash
2
+ # @scope: org-shared
2
3
  # Portability: bash 3.2+ (macOS default /bin/bash). UTF-8 multibyte chars in
3
4
  # user-controlled fields are byte-iterated under 3.2 (over-counts visible width
4
5
  # for non-ASCII content); acceptable safe-direction approximation — and the node