ccstatusline 2.0.27 → 2.0.29

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
@@ -46,18 +46,19 @@
46
46
 
47
47
  ## 🆕 Recent Updates
48
48
 
49
- ### v2.0.27 - Git Root Dir widget and raw-mode editor guardrails
50
-
51
- - **📁 Git Root Dir widget** - Added a new Git widget that shows the repository root directory name.
52
-
53
- ### v2.0.26 - Session naming, CWD options, and TUI workflow improvements
54
-
55
- - **🏷️ Session Name widget** - Added a new widget that shows the current Claude Code session name from `/rename`.
56
- - **🏠 Current Working Directory home abbreviation** - Added a `~` abbreviation option for CWD display in both preview and live rendering.
57
- - **🧠 Context model suffix fix** - Context widgets now recognize the `[1m]` suffix across models, not just a single model path.
58
- - **🧭 Widget picker UX updates** - Improved widget discovery/navigation and added clearer, safer clear-line behavior.
59
- - **⌨️ TUI editor input fix** - Prevented shortcut/input leakage into widget editor flows.
60
- - **📄 Repo docs update** - Migrated guidance from `CLAUDE.md` to `AGENTS.md` (with symlink compatibility).
49
+ ### v2.0.26 - v2.0.29 - Performance, git internals, and workflow improvements
50
+
51
+ - **🧠 Memory Usage widget (v2.0.29)** - Added a new widget that shows current system memory usage (`Mem: used/total`).
52
+ - **⚡ Block timer cache (v2.0.28)** - Cache block timer metrics to reduce JSONL parsing on every render, with per-config hashed cache files and automatic 5-hour block invalidation.
53
+ - **🧱 Git widget command refactor (v2.0.28)** - Refactored git widgets to use shared git command helpers and expanded coverage for failure and edge-case tests.
54
+ - **🪟 Windows UTF-8 piped output fix (v2.0.28)** - Sets the Windows UTF-8 code page for piped status line rendering.
55
+ - **📁 Git Root Dir widget (v2.0.27)** - Added a new Git widget that shows the repository root directory name.
56
+ - **🏷️ Session Name widget (v2.0.26)** - Added a new widget that shows the current Claude Code session name from `/rename`.
57
+ - **🏠 Current Working Directory home abbreviation (v2.0.26)** - Added a `~` abbreviation option for CWD display in both preview and live rendering.
58
+ - **🧠 Context model suffix fix (v2.0.26)** - Context widgets now recognize the `[1m]` suffix across models, not just a single model path.
59
+ - **🧭 Widget picker UX updates (v2.0.26)** - Improved widget discovery/navigation and added clearer, safer clear-line behavior.
60
+ - **⌨️ TUI editor input fix (v2.0.26)** - Prevented shortcut/input leakage into widget editor flows.
61
+ - **📄 Repo docs update (v2.0.26)** - Migrated guidance from `CLAUDE.md` to `AGENTS.md` (with symlink compatibility).
61
62
 
62
63
  ### v2.0.16 - Add fish style path abbreviation toggle to Current Working Directory widget
63
64
 
@@ -381,6 +382,7 @@ Once configured, ccstatusline automatically formats your Claude Code status line
381
382
  - **Context Percentage** - Shows percentage of context limit used (dynamic: 1M for Sonnet 4.5 with `[1m]` suffix, 200k otherwise)
382
383
  - **Context Percentage (usable)** - Shows percentage of usable context (dynamic: 800k for Sonnet 4.5 with `[1m]` suffix, 160k otherwise, accounting for auto-compact at 80%)
383
384
  - **Terminal Width** - Shows detected terminal width (for debugging)
385
+ - **Memory Usage** - Shows system memory usage (used/total, e.g., "Mem: 12.4G/16.0G")
384
386
  - **Custom Text** - Add your own custom text to the status line
385
387
  - **Custom Command** - Execute shell commands and display their output (refreshes whenever the statusline is updated by Claude Code)
386
388
  - **Separator** - Visual divider between widgets (customizable: |, -, comma, space)
@@ -51405,7 +51405,7 @@ import { execSync as execSync3 } from "child_process";
51405
51405
  import * as fs5 from "fs";
51406
51406
  import * as path4 from "path";
51407
51407
  var __dirname = "/Users/sirmalloc/Projects/Personal/ccstatusline/src/utils";
51408
- var PACKAGE_VERSION = "2.0.27";
51408
+ var PACKAGE_VERSION = "2.0.29";
51409
51409
  function getPackageVersion() {
51410
51410
  if (/^\d+\.\d+\.\d+/.test(PACKAGE_VERSION)) {
51411
51411
  return PACKAGE_VERSION;
@@ -52313,9 +52313,39 @@ class OutputStyleWidget {
52313
52313
  return true;
52314
52314
  }
52315
52315
  }
52316
- // src/widgets/GitBranch.ts
52316
+ // src/utils/git.ts
52317
52317
  import { execSync as execSync4 } from "child_process";
52318
+ function resolveGitCwd(context) {
52319
+ const candidates = [
52320
+ context.data?.cwd,
52321
+ context.data?.workspace?.current_dir,
52322
+ context.data?.workspace?.project_dir
52323
+ ];
52324
+ for (const candidate of candidates) {
52325
+ if (typeof candidate === "string" && candidate.trim().length > 0) {
52326
+ return candidate;
52327
+ }
52328
+ }
52329
+ return;
52330
+ }
52331
+ function runGit(command, context) {
52332
+ try {
52333
+ const cwd2 = resolveGitCwd(context);
52334
+ const output = execSync4(`git ${command}`, {
52335
+ encoding: "utf8",
52336
+ stdio: ["pipe", "pipe", "ignore"],
52337
+ ...cwd2 ? { cwd: cwd2 } : {}
52338
+ }).trim();
52339
+ return output.length > 0 ? output : null;
52340
+ } catch {
52341
+ return null;
52342
+ }
52343
+ }
52344
+ function isInsideGitWorkTree(context) {
52345
+ return runGit("rev-parse --is-inside-work-tree", context) === "true";
52346
+ }
52318
52347
 
52348
+ // src/widgets/GitBranch.ts
52319
52349
  class GitBranchWidget {
52320
52350
  getDefaultColor() {
52321
52351
  return "magenta";
@@ -52358,21 +52388,16 @@ class GitBranchWidget {
52358
52388
  if (context.isPreview) {
52359
52389
  return item.rawValue ? "main" : "⎇ main";
52360
52390
  }
52361
- const branch = this.getGitBranch();
52391
+ if (!isInsideGitWorkTree(context)) {
52392
+ return hideNoGit ? null : "⎇ no git";
52393
+ }
52394
+ const branch = this.getGitBranch(context);
52362
52395
  if (branch)
52363
52396
  return item.rawValue ? branch : `⎇ ${branch}`;
52364
52397
  return hideNoGit ? null : "⎇ no git";
52365
52398
  }
52366
- getGitBranch() {
52367
- try {
52368
- const branch = execSync4("git branch --show-current", {
52369
- encoding: "utf8",
52370
- stdio: ["pipe", "pipe", "ignore"]
52371
- }).trim();
52372
- return branch || null;
52373
- } catch {
52374
- return null;
52375
- }
52399
+ getGitBranch(context) {
52400
+ return runGit("branch --show-current", context);
52376
52401
  }
52377
52402
  getCustomKeybinds() {
52378
52403
  return [
@@ -52387,8 +52412,6 @@ class GitBranchWidget {
52387
52412
  }
52388
52413
  }
52389
52414
  // src/widgets/GitChanges.ts
52390
- import { execSync as execSync5 } from "child_process";
52391
-
52392
52415
  class GitChangesWidget {
52393
52416
  getDefaultColor() {
52394
52417
  return "yellow";
@@ -52431,40 +52454,33 @@ class GitChangesWidget {
52431
52454
  if (context.isPreview) {
52432
52455
  return "(+42,-10)";
52433
52456
  }
52434
- const changes = this.getGitChanges();
52457
+ if (!isInsideGitWorkTree(context)) {
52458
+ return hideNoGit ? null : "(no git)";
52459
+ }
52460
+ const changes = this.getGitChanges(context);
52435
52461
  if (changes)
52436
52462
  return `(+${changes.insertions},-${changes.deletions})`;
52437
52463
  else
52438
52464
  return hideNoGit ? null : "(no git)";
52439
52465
  }
52440
- getGitChanges() {
52441
- try {
52442
- let totalInsertions = 0;
52443
- let totalDeletions = 0;
52444
- const unstagedStat = execSync5("git diff --shortstat", {
52445
- encoding: "utf8",
52446
- stdio: ["pipe", "pipe", "ignore"]
52447
- }).trim();
52448
- const stagedStat = execSync5("git diff --cached --shortstat", {
52449
- encoding: "utf8",
52450
- stdio: ["pipe", "pipe", "ignore"]
52451
- }).trim();
52452
- if (unstagedStat) {
52453
- const insertMatch = /(\d+) insertion/.exec(unstagedStat);
52454
- const deleteMatch = /(\d+) deletion/.exec(unstagedStat);
52455
- totalInsertions += insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0;
52456
- totalDeletions += deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0;
52457
- }
52458
- if (stagedStat) {
52459
- const insertMatch = /(\d+) insertion/.exec(stagedStat);
52460
- const deleteMatch = /(\d+) deletion/.exec(stagedStat);
52461
- totalInsertions += insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0;
52462
- totalDeletions += deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0;
52463
- }
52464
- return { insertions: totalInsertions, deletions: totalDeletions };
52465
- } catch {
52466
- return null;
52467
- }
52466
+ getGitChanges(context) {
52467
+ let totalInsertions = 0;
52468
+ let totalDeletions = 0;
52469
+ const unstagedStat = runGit("diff --shortstat", context) ?? "";
52470
+ const stagedStat = runGit("diff --cached --shortstat", context) ?? "";
52471
+ if (unstagedStat) {
52472
+ const insertMatch = /(\d+) insertion/.exec(unstagedStat);
52473
+ const deleteMatch = /(\d+) deletion/.exec(unstagedStat);
52474
+ totalInsertions += insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0;
52475
+ totalDeletions += deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0;
52476
+ }
52477
+ if (stagedStat) {
52478
+ const insertMatch = /(\d+) insertion/.exec(stagedStat);
52479
+ const deleteMatch = /(\d+) deletion/.exec(stagedStat);
52480
+ totalInsertions += insertMatch?.[1] ? parseInt(insertMatch[1], 10) : 0;
52481
+ totalDeletions += deleteMatch?.[1] ? parseInt(deleteMatch[1], 10) : 0;
52482
+ }
52483
+ return { insertions: totalInsertions, deletions: totalDeletions };
52468
52484
  }
52469
52485
  getCustomKeybinds() {
52470
52486
  return [
@@ -52479,8 +52495,6 @@ class GitChangesWidget {
52479
52495
  }
52480
52496
  }
52481
52497
  // src/widgets/GitRootDir.ts
52482
- import { execSync as execSync6 } from "child_process";
52483
-
52484
52498
  class GitRootDirWidget {
52485
52499
  getDefaultColor() {
52486
52500
  return "cyan";
@@ -52523,22 +52537,17 @@ class GitRootDirWidget {
52523
52537
  if (context.isPreview) {
52524
52538
  return "my-repo";
52525
52539
  }
52526
- const rootDir = this.getGitRootDir();
52540
+ if (!isInsideGitWorkTree(context)) {
52541
+ return hideNoGit ? null : "no git";
52542
+ }
52543
+ const rootDir = this.getGitRootDir(context);
52527
52544
  if (rootDir) {
52528
52545
  return this.getRootDirName(rootDir);
52529
52546
  }
52530
52547
  return hideNoGit ? null : "no git";
52531
52548
  }
52532
- getGitRootDir() {
52533
- try {
52534
- const rootDir = execSync6("git rev-parse --show-toplevel", {
52535
- encoding: "utf8",
52536
- stdio: ["pipe", "pipe", "ignore"]
52537
- }).trim();
52538
- return rootDir || null;
52539
- } catch {
52540
- return null;
52541
- }
52549
+ getGitRootDir(context) {
52550
+ return runGit("rev-parse --show-toplevel", context);
52542
52551
  }
52543
52552
  getRootDirName(rootDir) {
52544
52553
  const trimmedRootDir = rootDir.replace(/[\\/]+$/, "");
@@ -52560,8 +52569,6 @@ class GitRootDirWidget {
52560
52569
  }
52561
52570
  }
52562
52571
  // src/widgets/GitWorktree.ts
52563
- import { execSync as execSync7 } from "child_process";
52564
-
52565
52572
  class GitWorktreeWidget {
52566
52573
  getDefaultColor() {
52567
52574
  return "blue";
@@ -52603,24 +52610,29 @@ class GitWorktreeWidget {
52603
52610
  const hideNoGit = item.metadata?.hideNoGit === "true";
52604
52611
  if (context.isPreview)
52605
52612
  return item.rawValue ? "main" : "\uD81A\uDC30 main";
52606
- const worktree = this.getGitWorktree();
52613
+ if (!isInsideGitWorkTree(context)) {
52614
+ return hideNoGit ? null : "\uD81A\uDC30 no git";
52615
+ }
52616
+ const worktree = this.getGitWorktree(context);
52607
52617
  if (worktree)
52608
52618
  return item.rawValue ? worktree : `\uD81A\uDC30 ${worktree}`;
52609
52619
  return hideNoGit ? null : "\uD81A\uDC30 no git";
52610
52620
  }
52611
- getGitWorktree() {
52612
- try {
52613
- const worktreeDir = execSync7("git rev-parse --git-dir", {
52614
- encoding: "utf8",
52615
- stdio: ["pipe", "pipe", "ignore"]
52616
- }).trim();
52617
- if (worktreeDir.endsWith("/.git") || worktreeDir === ".git")
52618
- return "main";
52619
- const [, worktree] = worktreeDir.split(".git/worktrees/");
52620
- return worktree ?? null;
52621
- } catch {
52621
+ getGitWorktree(context) {
52622
+ const worktreeDir = runGit("rev-parse --git-dir", context);
52623
+ if (!worktreeDir) {
52624
+ return null;
52625
+ }
52626
+ const normalizedGitDir = worktreeDir.replace(/\\/g, "/");
52627
+ if (normalizedGitDir.endsWith("/.git") || normalizedGitDir === ".git")
52628
+ return "main";
52629
+ const marker = ".git/worktrees/";
52630
+ const markerIndex = normalizedGitDir.lastIndexOf(marker);
52631
+ if (markerIndex === -1) {
52622
52632
  return null;
52623
52633
  }
52634
+ const worktree = normalizedGitDir.slice(markerIndex + marker.length);
52635
+ return worktree.length > 0 ? worktree : null;
52624
52636
  }
52625
52637
  getCustomKeybinds() {
52626
52638
  return [
@@ -53904,7 +53916,7 @@ var CustomTextEditor = ({ widget, onComplete, onCancel }) => {
53904
53916
  }, undefined, true, undefined, this);
53905
53917
  };
53906
53918
  // src/widgets/CustomCommand.tsx
53907
- import { execSync as execSync8 } from "child_process";
53919
+ import { execSync as execSync5 } from "child_process";
53908
53920
  var import_react30 = __toESM(require_react(), 1);
53909
53921
  var jsx_dev_runtime2 = __toESM(require_jsx_dev_runtime(), 1);
53910
53922
 
@@ -53953,7 +53965,7 @@ class CustomCommandWidget {
53953
53965
  try {
53954
53966
  const timeout = item.timeout ?? 1000;
53955
53967
  const jsonInput = JSON.stringify(context.data);
53956
- let output = execSync8(item.commandPath, {
53968
+ let output = execSync5(item.commandPath, {
53957
53969
  encoding: "utf8",
53958
53970
  input: jsonInput,
53959
53971
  timeout,
@@ -54556,6 +54568,89 @@ class ClaudeSessionIdWidget {
54556
54568
  return true;
54557
54569
  }
54558
54570
  }
54571
+ // src/widgets/FreeMemory.ts
54572
+ import { execSync as execSync6 } from "child_process";
54573
+ import os6 from "os";
54574
+ function formatBytes(bytes) {
54575
+ const GB = 1024 ** 3;
54576
+ const MB = 1024 ** 2;
54577
+ const KB = 1024;
54578
+ if (bytes >= GB)
54579
+ return `${(bytes / GB).toFixed(1)}G`;
54580
+ if (bytes >= MB)
54581
+ return `${(bytes / MB).toFixed(0)}M`;
54582
+ if (bytes >= KB)
54583
+ return `${(bytes / KB).toFixed(0)}K`;
54584
+ return `${bytes}B`;
54585
+ }
54586
+ function getUsedMemoryMacOS() {
54587
+ try {
54588
+ const output = execSync6("vm_stat", { encoding: "utf8" });
54589
+ const lines = output.split(`
54590
+ `);
54591
+ const firstLine = lines[0];
54592
+ if (!firstLine)
54593
+ return null;
54594
+ const pageSizeMatch = /page size of (\d+) bytes/.exec(firstLine);
54595
+ const pageSizeString = pageSizeMatch?.[1];
54596
+ if (!pageSizeString)
54597
+ return null;
54598
+ const pageSize = parseInt(pageSizeString, 10);
54599
+ let activePages = 0;
54600
+ let wiredPages = 0;
54601
+ for (const line of lines) {
54602
+ const activeMatch = /Pages active:\s+(\d+)/.exec(line);
54603
+ const activeValue = activeMatch?.[1];
54604
+ if (activeValue)
54605
+ activePages = parseInt(activeValue, 10);
54606
+ const wiredMatch = /Pages wired down:\s+(\d+)/.exec(line);
54607
+ const wiredValue = wiredMatch?.[1];
54608
+ if (wiredValue)
54609
+ wiredPages = parseInt(wiredValue, 10);
54610
+ }
54611
+ return (activePages + wiredPages) * pageSize;
54612
+ } catch {
54613
+ return null;
54614
+ }
54615
+ }
54616
+
54617
+ class FreeMemoryWidget {
54618
+ getDefaultColor() {
54619
+ return "cyan";
54620
+ }
54621
+ getDescription() {
54622
+ return "Shows system memory usage (used/total)";
54623
+ }
54624
+ getDisplayName() {
54625
+ return "Memory Usage";
54626
+ }
54627
+ getCategory() {
54628
+ return "Environment";
54629
+ }
54630
+ getEditorDisplay(item) {
54631
+ return { displayText: this.getDisplayName() };
54632
+ }
54633
+ render(item, context, settings) {
54634
+ if (context.isPreview) {
54635
+ return item.rawValue ? "12.4G/16.0G" : "Mem: 12.4G/16.0G";
54636
+ }
54637
+ const total = os6.totalmem();
54638
+ let used;
54639
+ if (os6.platform() === "darwin") {
54640
+ used = getUsedMemoryMacOS() ?? total - os6.freemem();
54641
+ } else {
54642
+ used = total - os6.freemem();
54643
+ }
54644
+ const value = `${formatBytes(used)}/${formatBytes(total)}`;
54645
+ return item.rawValue ? value : `Mem: ${value}`;
54646
+ }
54647
+ supportsRawValue() {
54648
+ return true;
54649
+ }
54650
+ supportsColors(item) {
54651
+ return true;
54652
+ }
54653
+ }
54559
54654
  // src/widgets/SessionName.ts
54560
54655
  import * as fs6 from "fs";
54561
54656
 
@@ -54632,7 +54727,8 @@ var widgetRegistry = new Map([
54632
54727
  ["custom-text", new CustomTextWidget],
54633
54728
  ["custom-command", new CustomCommandWidget],
54634
54729
  ["claude-session-id", new ClaudeSessionIdWidget],
54635
- ["session-name", new SessionNameWidget]
54730
+ ["session-name", new SessionNameWidget],
54731
+ ["free-memory", new FreeMemoryWidget]
54636
54732
  ]);
54637
54733
  function getWidget(type) {
54638
54734
  return widgetRegistry.get(type) ?? null;
@@ -56992,7 +57088,7 @@ var MainMenu = ({ onSelect, isClaudeInstalled, hasChanges, initialSelection = 0,
56992
57088
  };
56993
57089
  // src/tui/components/PowerlineSetup.tsx
56994
57090
  var import_react41 = __toESM(require_react(), 1);
56995
- import * as os6 from "os";
57091
+ import * as os7 from "os";
56996
57092
 
56997
57093
  // src/tui/components/PowerlineSeparatorEditor.tsx
56998
57094
  var import_react39 = __toESM(require_react(), 1);
@@ -57709,7 +57805,7 @@ var PowerlineSetup = ({
57709
57805
  }, undefined, false, undefined, this)
57710
57806
  ]
57711
57807
  }, undefined, true, undefined, this),
57712
- os6.platform() === "darwin" && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
57808
+ os7.platform() === "darwin" && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
57713
57809
  children: [
57714
57810
  /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
57715
57811
  dimColor: true,
@@ -57725,7 +57821,7 @@ var PowerlineSetup = ({
57725
57821
  }, undefined, false, undefined, this)
57726
57822
  ]
57727
57823
  }, undefined, true, undefined, this),
57728
- os6.platform() === "linux" && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
57824
+ os7.platform() === "linux" && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
57729
57825
  children: [
57730
57826
  /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
57731
57827
  dimColor: true,
@@ -57741,7 +57837,7 @@ var PowerlineSetup = ({
57741
57837
  }, undefined, false, undefined, this)
57742
57838
  ]
57743
57839
  }, undefined, true, undefined, this),
57744
- os6.platform() === "win32" && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
57840
+ os7.platform() === "win32" && /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(jsx_dev_runtime13.Fragment, {
57745
57841
  children: [
57746
57842
  /* @__PURE__ */ jsx_dev_runtime13.jsxDEV(Text, {
57747
57843
  dimColor: true,
@@ -58923,6 +59019,8 @@ var StatusJSONSchema = exports_external.looseObject({
58923
59019
 
58924
59020
  // src/utils/jsonl.ts
58925
59021
  import * as fs7 from "fs";
59022
+ import { createHash } from "node:crypto";
59023
+ import os8 from "node:os";
58926
59024
  import path6 from "node:path";
58927
59025
 
58928
59026
  // node_modules/tinyglobby/dist/index.mjs
@@ -59714,6 +59812,81 @@ import { promisify } from "util";
59714
59812
  var readFile4 = promisify(fs7.readFile);
59715
59813
  var readFileSync5 = fs7.readFileSync;
59716
59814
  var statSync4 = fs7.statSync;
59815
+ var writeFileSync2 = fs7.writeFileSync;
59816
+ var mkdirSync3 = fs7.mkdirSync;
59817
+ var existsSync7 = fs7.existsSync;
59818
+ function normalizeConfigDir(configDir) {
59819
+ return path6.resolve(configDir);
59820
+ }
59821
+ function getBlockCachePath(configDir = getClaudeConfigDir()) {
59822
+ const normalizedConfigDir = normalizeConfigDir(configDir);
59823
+ const configHash = createHash("sha256").update(normalizedConfigDir).digest("hex").slice(0, 16);
59824
+ return path6.join(os8.homedir(), ".cache", "ccstatusline", `block-cache-${configHash}.json`);
59825
+ }
59826
+ function readBlockCache(expectedConfigDir) {
59827
+ try {
59828
+ const normalizedExpectedConfigDir = expectedConfigDir !== undefined ? normalizeConfigDir(expectedConfigDir) : undefined;
59829
+ const cachePath = getBlockCachePath(normalizedExpectedConfigDir);
59830
+ if (!existsSync7(cachePath)) {
59831
+ return null;
59832
+ }
59833
+ const content = readFileSync5(cachePath, "utf-8");
59834
+ const cache3 = JSON.parse(content);
59835
+ if (typeof cache3.startTime !== "string") {
59836
+ return null;
59837
+ }
59838
+ if (normalizedExpectedConfigDir !== undefined) {
59839
+ if (typeof cache3.configDir !== "string") {
59840
+ return null;
59841
+ }
59842
+ if (cache3.configDir !== normalizedExpectedConfigDir) {
59843
+ return null;
59844
+ }
59845
+ }
59846
+ const date5 = new Date(cache3.startTime);
59847
+ if (Number.isNaN(date5.getTime())) {
59848
+ return null;
59849
+ }
59850
+ return date5;
59851
+ } catch {
59852
+ return null;
59853
+ }
59854
+ }
59855
+ function writeBlockCache(startTime, configDir = getClaudeConfigDir()) {
59856
+ try {
59857
+ const normalizedConfigDir = normalizeConfigDir(configDir);
59858
+ const cachePath = getBlockCachePath(normalizedConfigDir);
59859
+ const cacheDir = path6.dirname(cachePath);
59860
+ if (!existsSync7(cacheDir)) {
59861
+ mkdirSync3(cacheDir, { recursive: true });
59862
+ }
59863
+ const cache3 = {
59864
+ startTime: startTime.toISOString(),
59865
+ configDir: normalizedConfigDir
59866
+ };
59867
+ writeFileSync2(cachePath, JSON.stringify(cache3), "utf-8");
59868
+ } catch {}
59869
+ }
59870
+ function getCachedBlockMetrics(sessionDurationHours = 5) {
59871
+ const sessionDurationMs = sessionDurationHours * 60 * 60 * 1000;
59872
+ const now = new Date;
59873
+ const activeConfigDir = getClaudeConfigDir();
59874
+ const cachedStartTime = readBlockCache(activeConfigDir);
59875
+ if (cachedStartTime) {
59876
+ const blockEndTime = new Date(cachedStartTime.getTime() + sessionDurationMs);
59877
+ if (now.getTime() <= blockEndTime.getTime()) {
59878
+ return {
59879
+ startTime: cachedStartTime,
59880
+ lastActivity: now
59881
+ };
59882
+ }
59883
+ }
59884
+ const metrics = getBlockMetrics();
59885
+ if (metrics) {
59886
+ writeBlockCache(metrics.startTime, activeConfigDir);
59887
+ }
59888
+ return metrics;
59889
+ }
59717
59890
  async function getSessionDuration(transcriptPath) {
59718
59891
  try {
59719
59892
  if (!fs7.existsSync(transcriptPath)) {
@@ -59974,6 +60147,15 @@ async function readStdin() {
59974
60147
  return null;
59975
60148
  }
59976
60149
  }
60150
+ async function ensureWindowsUtf8CodePage() {
60151
+ if (process.platform !== "win32") {
60152
+ return;
60153
+ }
60154
+ try {
60155
+ const { execFileSync } = await import("child_process");
60156
+ execFileSync("chcp.com", ["65001"], { stdio: "ignore" });
60157
+ } catch {}
60158
+ }
59977
60159
  async function renderMultipleLines(data) {
59978
60160
  const settings = await loadSettings();
59979
60161
  source_default.level = settings.colorLevel;
@@ -59992,7 +60174,7 @@ async function renderMultipleLines(data) {
59992
60174
  }
59993
60175
  let blockMetrics = null;
59994
60176
  if (hasBlockTimer) {
59995
- blockMetrics = getBlockMetrics();
60177
+ blockMetrics = getCachedBlockMetrics();
59996
60178
  }
59997
60179
  const context = {
59998
60180
  data,
@@ -60040,6 +60222,7 @@ async function renderMultipleLines(data) {
60040
60222
  }
60041
60223
  async function main() {
60042
60224
  if (!process.stdin.isTTY) {
60225
+ await ensureWindowsUtf8CodePage();
60043
60226
  const input = await readStdin();
60044
60227
  if (input && input.trim() !== "") {
60045
60228
  try {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccstatusline",
3
- "version": "2.0.27",
3
+ "version": "2.0.29",
4
4
  "description": "A customizable status line formatter for Claude Code CLI",
5
5
  "module": "src/ccstatusline.ts",
6
6
  "type": "module",