copilot-statusline 0.1.14 → 0.1.16

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
@@ -264,7 +264,8 @@ Copilot CLI spawns your status line command on every state change, passing sessi
264
264
  |--------|------|-------------|
265
265
  | Tokens Input | `tokens-input` | Total input tokens |
266
266
  | Tokens Output | `tokens-output` | Total output tokens |
267
- | Tokens Cached | `tokens-cached` | Total cached tokens (read + write) |
267
+ | Tokens Cached | `tokens-cached` | Cached tokens from the most recent API call (`current_usage.cache_creation_input_tokens + cache_read_input_tokens`; rendered as `Last Cache:`) |
268
+ | Tokens Reasoning | `tokens-reasoning` | Total reasoning (thinking) tokens consumed |
268
269
  | Tokens Total | `tokens-total` | Total tokens |
269
270
  | Last Call Input | `last-call-input` | Input tokens from most recent API call |
270
271
  | Last Call Output | `last-call-output` | Output tokens from most recent API call |
@@ -274,11 +275,11 @@ Copilot CLI spawns your status line command on every state change, passing sessi
274
275
  ### Context
275
276
  | Widget | Type | Description |
276
277
  |--------|------|-------------|
277
- | Context Length | `context-length` | Context window size |
278
- | Context % | `context-percentage` | Percentage of context window used or remaining |
278
+ | Context Length | `context-length` | Current context length in tokens (live, from `current_context_tokens`) |
279
+ | Context Window | `context-window` | Model's context window size (max tokens, from `displayed_context_limit ?? context_window_size`) |
279
280
  | Context % | `context-percentage` | Live context % from Copilot (`current_context_used_percentage`) |
280
- | Context Bar | `context-bar` | Visual progress bar for context usage |
281
- | Remaining Tokens | `remaining-tokens` | Absolute remaining context tokens |
281
+ | Context Bar | `context-bar` | Visual progress bar for live context usage |
282
+ | Remaining Tokens | `remaining-tokens` | Live remaining tokens (`displayed_context_limit − current_context_tokens`) |
282
283
 
283
284
  ### Git
284
285
  | Widget | Type | Description |
@@ -289,8 +290,12 @@ Copilot CLI spawns your status line command on every state change, passing sessi
289
290
  | Git Deletions | `git-deletions` | Uncommitted deletions only |
290
291
  | Git Status | `git-status` | Staged/unstaged/untracked/conflicts indicators |
291
292
  | Git Staged | `git-staged` | Staged changes indicator |
293
+ | Git Staged Files | `git-staged-files` | Count of staged files (`S:3`, raw: `3`) |
292
294
  | Git Unstaged | `git-unstaged` | Unstaged changes indicator |
295
+ | Git Unstaged Files | `git-unstaged-files` | Count of unstaged tracked files (`M:2`, raw: `2`) |
293
296
  | Git Untracked | `git-untracked` | Untracked files indicator |
297
+ | Git Untracked Files | `git-untracked-files` | Count of untracked files (`?:1`, raw: `1`) |
298
+ | Git Clean Status | `git-clean-status` | Working tree clean/dirty status (`✓`/`✗`, raw: `clean`/`dirty`) |
294
299
  | Git Conflicts | `git-conflicts` | Merge conflict count |
295
300
  | Git Ahead/Behind | `git-ahead-behind` | Commits ahead/behind upstream |
296
301
  | Git SHA | `git-sha` | Short commit hash |
@@ -303,6 +308,8 @@ Copilot CLI spawns your status line command on every state change, passing sessi
303
308
  | Git Upstream Repo | `git-upstream-repo` | Upstream remote repo name |
304
309
  | Git Upstream Owner/Repo | `git-upstream-owner-repo` | Upstream `owner/repo` |
305
310
  | Git Is Fork | `git-is-fork` | Fork detection indicator |
311
+ | Git Worktree | `git-worktree` | Current git worktree name (probes `git rev-parse --git-dir`) |
312
+ | Git Worktree Mode | `git-worktree-mode` | `⎇` indicator when current dir is a linked worktree |
306
313
 
307
314
  ### System
308
315
  | Widget | Type | Description |
@@ -339,7 +346,8 @@ These widgets are exclusive to copilot-statusline and not available in ccstatusl
339
346
  | Premium Rate | `premium-rate` | Burn rate in requests/minute |
340
347
  | Last Call Input | `last-call-input` | Input tokens from the most recent API call |
341
348
  | Last Call Output | `last-call-output` | Output tokens from the most recent API call |
342
- | Remaining Tokens | `remaining-tokens` | Absolute remaining context window tokens |
349
+ | Remaining Tokens | `remaining-tokens` | Live remaining tokens (`displayed_context_limit current_context_tokens`) |
350
+ | Tokens Reasoning | `tokens-reasoning` | Total reasoning (thinking) tokens consumed |
343
351
  | Cache Read Tokens | `cache-read-tokens` | Total cache read tokens |
344
352
  | Cache Write Tokens | `cache-write-tokens` | Total cache write tokens |
345
353
 
@@ -366,9 +374,11 @@ Widget-specific shortcuts:
366
374
  | Widget | Key | Action |
367
375
  |--------|-----|--------|
368
376
  | Git widgets | `h` | Toggle hide `no git` output |
377
+ | Count widgets | `z` | Toggle hide when rendered value is zero |
369
378
  | Git Branch | `l` | Toggle GitHub link |
370
379
  | Context % widgets | `u` | Toggle used/remaining display |
371
- | Context Bar | `p` | Cycle bar style (short/full) |
380
+ | Context % widgets | `p` | Cycle numeric/short bar display |
381
+ | Context Bar | `p` | Cycle bar style (medium/full/short/short-only) |
372
382
  | Current Working Dir | `h` | Toggle `~` home abbreviation |
373
383
  | Current Working Dir | `s` | Edit segment limit |
374
384
  | Current Working Dir | `f` | Toggle fish-style path |
@@ -620,4 +630,4 @@ This project includes substantial portions of code from [ccstatusline](https://g
620
630
  [![npm downloads](https://img.shields.io/npm/dm/copilot-statusline.svg)](https://www.npmjs.com/package/copilot-statusline)
621
631
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
622
632
 
623
- </div>
633
+ </div>
@@ -52913,7 +52913,7 @@ import { execSync as execSync3 } from "child_process";
52913
52913
  import * as fs5 from "fs";
52914
52914
  import * as path4 from "path";
52915
52915
  var __dirname = "/Users/ts/workspace/active/statusline/copilot_statusline/src/utils";
52916
- var PACKAGE_VERSION = "0.1.14";
52916
+ var PACKAGE_VERSION = "0.1.16";
52917
52917
  function getPackageVersion() {
52918
52918
  if (/^\d+\.\d+\.\d+/.test(PACKAGE_VERSION)) {
52919
52919
  return PACKAGE_VERSION;
@@ -54104,7 +54104,8 @@ function getContextWindowMetrics(data) {
54104
54104
  const cacheWriteTokens = toFiniteNonNegativeNumber(contextWindow.total_cache_write_tokens);
54105
54105
  const lastCallInputTokens = toFiniteNonNegativeNumber(contextWindow.last_call_input_tokens);
54106
54106
  const lastCallOutputTokens = toFiniteNonNegativeNumber(contextWindow.last_call_output_tokens);
54107
- const cachedTokens = cacheReadTokens !== null || cacheWriteTokens !== null ? (cacheReadTokens ?? 0) + (cacheWriteTokens ?? 0) : null;
54107
+ const currentUsage = contextWindow.current_usage;
54108
+ const cachedTokens = currentUsage ? (toFiniteNonNegativeNumber(currentUsage.cache_creation_input_tokens) ?? 0) + (toFiniteNonNegativeNumber(currentUsage.cache_read_input_tokens) ?? 0) : null;
54108
54109
  const totalTokens = toFiniteNonNegativeNumber(contextWindow.total_tokens) ?? (totalInputTokens !== null && totalOutputTokens !== null ? totalInputTokens + totalOutputTokens : null);
54109
54110
  return {
54110
54111
  windowSize,
@@ -54211,7 +54212,7 @@ class TokensCachedWidget {
54211
54212
  return "brightBlack";
54212
54213
  }
54213
54214
  getDescription() {
54214
- return "Shows total cached tokens (read + write) for the current session";
54215
+ return "Shows cached tokens for the latest API call (cache read + cache creation)";
54215
54216
  }
54216
54217
  getDisplayName() {
54217
54218
  return "Tokens Cached";
@@ -54224,11 +54225,11 @@ class TokensCachedWidget {
54224
54225
  }
54225
54226
  render(item, context, settings) {
54226
54227
  if (context.isPreview) {
54227
- return formatRawOrLabeledValue(item, "Cached: ", "8.5k");
54228
+ return formatRawOrLabeledValue(item, "Last Cache: ", "8.5k");
54228
54229
  }
54229
54230
  const metrics = getContextWindowMetrics(context.data);
54230
54231
  if (metrics.cachedTokens !== null) {
54231
- return formatRawOrLabeledValue(item, "Cached: ", formatTokens(metrics.cachedTokens));
54232
+ return formatRawOrLabeledValue(item, "Last Cache: ", formatTokens(metrics.cachedTokens));
54232
54233
  }
54233
54234
  return null;
54234
54235
  }
@@ -54239,6 +54240,49 @@ class TokensCachedWidget {
54239
54240
  return true;
54240
54241
  }
54241
54242
  }
54243
+ // src/widgets/shared/editor-display.ts
54244
+ function makeModifierText(modifiers) {
54245
+ return modifiers.length > 0 ? `(${modifiers.join(", ")})` : undefined;
54246
+ }
54247
+
54248
+ // src/widgets/shared/metadata.ts
54249
+ function isMetadataFlagEnabled(item, key) {
54250
+ return item.metadata?.[key] === "true";
54251
+ }
54252
+ function toggleMetadataFlag(item, key) {
54253
+ return {
54254
+ ...item,
54255
+ metadata: {
54256
+ ...item.metadata,
54257
+ [key]: (!isMetadataFlagEnabled(item, key)).toString()
54258
+ }
54259
+ };
54260
+ }
54261
+
54262
+ // src/widgets/shared/hide-when-zero.ts
54263
+ var HIDE_WHEN_ZERO_KEY = "hideWhenZero";
54264
+ var TOGGLE_HIDE_WHEN_ZERO_ACTION = "toggle-hide-when-zero";
54265
+ var HIDE_WHEN_ZERO_KEYBIND = {
54266
+ key: "z",
54267
+ label: "(z) hide when zero",
54268
+ action: TOGGLE_HIDE_WHEN_ZERO_ACTION
54269
+ };
54270
+ function isHideWhenZeroEnabled(item) {
54271
+ return isMetadataFlagEnabled(item, HIDE_WHEN_ZERO_KEY);
54272
+ }
54273
+ function getHideWhenZeroLabel(item) {
54274
+ return isHideWhenZeroEnabled(item) ? "hide when zero" : undefined;
54275
+ }
54276
+ function handleToggleHideWhenZeroAction(action, item) {
54277
+ if (action !== TOGGLE_HIDE_WHEN_ZERO_ACTION) {
54278
+ return null;
54279
+ }
54280
+ return toggleMetadataFlag(item, HIDE_WHEN_ZERO_KEY);
54281
+ }
54282
+ function getHideWhenZeroKeybinds() {
54283
+ return [HIDE_WHEN_ZERO_KEYBIND];
54284
+ }
54285
+
54242
54286
  // src/widgets/TokensReasoning.ts
54243
54287
  class TokensReasoningWidget {
54244
54288
  getDefaultColor() {
@@ -54253,8 +54297,14 @@ class TokensReasoningWidget {
54253
54297
  getCategory() {
54254
54298
  return "Tokens";
54255
54299
  }
54256
- getEditorDisplay(_item) {
54257
- return { displayText: this.getDisplayName() };
54300
+ getEditorDisplay(item) {
54301
+ return {
54302
+ displayText: this.getDisplayName(),
54303
+ modifierText: makeModifierText([getHideWhenZeroLabel(item)].filter((label) => label !== undefined))
54304
+ };
54305
+ }
54306
+ handleEditorAction(action, item) {
54307
+ return handleToggleHideWhenZeroAction(action, item);
54258
54308
  }
54259
54309
  render(item, context, _settings) {
54260
54310
  if (context.isPreview) {
@@ -54262,10 +54312,16 @@ class TokensReasoningWidget {
54262
54312
  }
54263
54313
  const metrics = getContextWindowMetrics(context.data);
54264
54314
  if (metrics.reasoningTokens !== null) {
54315
+ if (metrics.reasoningTokens === 0 && isHideWhenZeroEnabled(item)) {
54316
+ return null;
54317
+ }
54265
54318
  return formatRawOrLabeledValue(item, "Reasoning: ", formatTokens(metrics.reasoningTokens));
54266
54319
  }
54267
54320
  return null;
54268
54321
  }
54322
+ getCustomKeybinds() {
54323
+ return getHideWhenZeroKeybinds();
54324
+ }
54269
54325
  supportsRawValue() {
54270
54326
  return true;
54271
54327
  }
@@ -54376,33 +54432,14 @@ class ContextWindowWidget {
54376
54432
  return true;
54377
54433
  }
54378
54434
  }
54379
- // src/widgets/shared/editor-display.ts
54380
- function makeModifierText(modifiers) {
54381
- return modifiers.length > 0 ? `(${modifiers.join(", ")})` : undefined;
54382
- }
54383
-
54384
- // src/widgets/shared/metadata.ts
54385
- function isMetadataFlagEnabled(item, key) {
54386
- return item.metadata?.[key] === "true";
54387
- }
54388
- function toggleMetadataFlag(item, key) {
54389
- return {
54390
- ...item,
54391
- metadata: {
54392
- ...item.metadata,
54393
- [key]: (!isMetadataFlagEnabled(item, key)).toString()
54394
- }
54395
- };
54396
- }
54397
-
54398
54435
  // src/widgets/shared/context-inverse.ts
54399
54436
  var INVERSE_KEY = "inverse";
54400
54437
  var TOGGLE_INVERSE_ACTION = "toggle-inverse";
54401
54438
  function isContextInverse(item) {
54402
54439
  return isMetadataFlagEnabled(item, INVERSE_KEY);
54403
54440
  }
54404
- function getContextInverseModifierText(item) {
54405
- return makeModifierText(isContextInverse(item) ? ["remaining"] : []);
54441
+ function getContextInverseLabel(item) {
54442
+ return isContextInverse(item) ? "remaining" : undefined;
54406
54443
  }
54407
54444
  function handleContextInverseAction(action, item) {
54408
54445
  if (action !== TOGGLE_INVERSE_ACTION) {
@@ -54411,6 +54448,64 @@ function handleContextInverseAction(action, item) {
54411
54448
  return toggleMetadataFlag(item, INVERSE_KEY);
54412
54449
  }
54413
54450
 
54451
+ // src/widgets/shared/context-slider.ts
54452
+ var SLIDER_WIDTH = 10;
54453
+ var SLIDER_TOGGLE_KEYBIND = { key: "p", label: "(p)rogress toggle", action: "toggle-slider" };
54454
+ function makeSliderBar(percent, width = SLIDER_WIDTH) {
54455
+ const clamped = Math.max(0, Math.min(100, percent));
54456
+ const filled = Math.round(clamped / 100 * width);
54457
+ return "▓".repeat(filled) + "░".repeat(width - filled);
54458
+ }
54459
+ function getContextSliderMode(item) {
54460
+ const mode = item.metadata?.display;
54461
+ if (mode === "slider" || mode === "slider-only") {
54462
+ return mode;
54463
+ }
54464
+ return "none";
54465
+ }
54466
+ function cycleContextSliderMode(item) {
54467
+ const currentMode = getContextSliderMode(item);
54468
+ const nextMode = currentMode === "none" ? "slider" : currentMode === "slider" ? "slider-only" : "none";
54469
+ if (nextMode === "none") {
54470
+ const nextMetadata = { ...item.metadata ?? {} };
54471
+ delete nextMetadata.display;
54472
+ return {
54473
+ ...item,
54474
+ metadata: Object.keys(nextMetadata).length > 0 ? nextMetadata : undefined
54475
+ };
54476
+ }
54477
+ return {
54478
+ ...item,
54479
+ metadata: {
54480
+ ...item.metadata ?? {},
54481
+ display: nextMode
54482
+ }
54483
+ };
54484
+ }
54485
+ function renderContextSlider(mode, percent) {
54486
+ if (mode === "none") {
54487
+ return null;
54488
+ }
54489
+ const slider = makeSliderBar(percent);
54490
+ if (mode === "slider") {
54491
+ return `${slider} ${percent.toFixed(1)}%`;
54492
+ }
54493
+ return slider;
54494
+ }
54495
+ function getContextSliderLabel(item) {
54496
+ const mode = getContextSliderMode(item);
54497
+ if (mode === "slider") {
54498
+ return "short bar";
54499
+ }
54500
+ if (mode === "slider-only") {
54501
+ return "short bar only";
54502
+ }
54503
+ return;
54504
+ }
54505
+ function getContextSliderKeybinds() {
54506
+ return [SLIDER_TOGGLE_KEYBIND];
54507
+ }
54508
+
54414
54509
  // src/widgets/ContextPercentage.ts
54415
54510
  class ContextPercentageWidget {
54416
54511
  getDefaultColor() {
@@ -54426,31 +54521,44 @@ class ContextPercentageWidget {
54426
54521
  return "Context";
54427
54522
  }
54428
54523
  getEditorDisplay(item) {
54524
+ const labels = [
54525
+ getContextInverseLabel(item),
54526
+ getContextSliderLabel(item)
54527
+ ].filter((label) => label !== undefined);
54429
54528
  return {
54430
54529
  displayText: this.getDisplayName(),
54431
- modifierText: getContextInverseModifierText(item)
54530
+ modifierText: makeModifierText(labels)
54432
54531
  };
54433
54532
  }
54434
54533
  handleEditorAction(action, item) {
54534
+ if (action === "toggle-slider") {
54535
+ return cycleContextSliderMode(item);
54536
+ }
54435
54537
  return handleContextInverseAction(action, item);
54436
54538
  }
54437
54539
  render(item, context, settings) {
54438
54540
  const isInverse = isContextInverse(item);
54541
+ const label = isInverse ? "Ctx Left: " : "Ctx Used: ";
54542
+ const sliderMode = getContextSliderMode(item);
54543
+ const formatPercentage = (displayPercentage) => {
54544
+ const sliderDisplay = renderContextSlider(sliderMode, displayPercentage);
54545
+ return formatRawOrLabeledValue(item, label, sliderDisplay ?? `${displayPercentage.toFixed(1)}%`);
54546
+ };
54439
54547
  if (context.isPreview) {
54440
- const previewValue = isInverse ? "90.7%" : "9.3%";
54441
- return formatRawOrLabeledValue(item, "Ctx: ", previewValue);
54548
+ return formatPercentage(isInverse ? 90.7 : 9.3);
54442
54549
  }
54443
54550
  const metrics = getContextWindowMetrics(context.data);
54444
54551
  if (metrics.currentContextUsedPercentage !== null) {
54445
54552
  const pct = metrics.currentContextUsedPercentage;
54446
54553
  const displayPercentage = isInverse ? 100 - pct : pct;
54447
- return formatRawOrLabeledValue(item, "Ctx: ", `${displayPercentage.toFixed(1)}%`);
54554
+ return formatPercentage(displayPercentage);
54448
54555
  }
54449
54556
  return null;
54450
54557
  }
54451
54558
  getCustomKeybinds() {
54452
54559
  return [
54453
- { key: "u", label: "(u)sed/remaining", action: "toggle-inverse" }
54560
+ { key: "u", label: "(u)sed/remaining", action: "toggle-inverse" },
54561
+ ...getContextSliderKeybinds()
54454
54562
  ];
54455
54563
  }
54456
54564
  supportsRawValue() {
@@ -54474,8 +54582,27 @@ function makeUsageProgressBar(percent, width = 15, powerlineMode = false) {
54474
54582
  }
54475
54583
 
54476
54584
  // src/widgets/ContextBar.ts
54585
+ var DISPLAY_MODE_CYCLE = {
54586
+ "progress-short": "progress",
54587
+ progress: "slider",
54588
+ slider: "slider-only",
54589
+ "slider-only": "progress-short"
54590
+ };
54591
+ var DISPLAY_MODE_LABELS = {
54592
+ "progress-short": "medium bar",
54593
+ progress: "",
54594
+ slider: "short bar",
54595
+ "slider-only": "short bar only"
54596
+ };
54477
54597
  function getDisplayMode(item) {
54478
- return item.metadata?.display === "progress" ? "progress" : "progress-short";
54598
+ const mode = item.metadata?.display;
54599
+ if (mode === "progress" || mode === "slider" || mode === "slider-only") {
54600
+ return mode;
54601
+ }
54602
+ return "progress-short";
54603
+ }
54604
+ function isSliderMode(mode) {
54605
+ return mode === "slider" || mode === "slider-only";
54479
54606
  }
54480
54607
 
54481
54608
  class ContextBarWidget {
@@ -54492,22 +54619,17 @@ class ContextBarWidget {
54492
54619
  return "Context";
54493
54620
  }
54494
54621
  getEditorDisplay(item) {
54495
- const mode = getDisplayMode(item);
54496
- const modifiers = [];
54497
- if (mode === "progress-short") {
54498
- modifiers.push("short bar");
54499
- }
54622
+ const label = DISPLAY_MODE_LABELS[getDisplayMode(item)];
54500
54623
  return {
54501
54624
  displayText: this.getDisplayName(),
54502
- modifierText: modifiers.length > 0 ? `(${modifiers.join(", ")})` : undefined
54625
+ modifierText: label ? `(${label})` : undefined
54503
54626
  };
54504
54627
  }
54505
54628
  handleEditorAction(action, item) {
54506
54629
  if (action !== "toggle-progress") {
54507
54630
  return null;
54508
54631
  }
54509
- const currentMode = getDisplayMode(item);
54510
- const nextMode = currentMode === "progress-short" ? "progress" : "progress-short";
54632
+ const nextMode = DISPLAY_MODE_CYCLE[getDisplayMode(item)];
54511
54633
  return {
54512
54634
  ...item,
54513
54635
  metadata: {
@@ -54521,6 +54643,11 @@ class ContextBarWidget {
54521
54643
  const barWidth = displayMode === "progress" ? 32 : 16;
54522
54644
  const powerlineMode = settings.powerline.enabled;
54523
54645
  if (context.isPreview) {
54646
+ if (isSliderMode(displayMode)) {
54647
+ const slider = renderContextSlider("slider-only", 25);
54648
+ const sliderDisplay = displayMode === "slider" ? `${slider} 50k/200k (25%)` : slider;
54649
+ return item.rawValue ? sliderDisplay : `Context: ${sliderDisplay}`;
54650
+ }
54524
54651
  const previewDisplay = `${makeUsageProgressBar(25, barWidth, powerlineMode)} 50k/200k (25%)`;
54525
54652
  return item.rawValue ? previewDisplay : `Context: ${previewDisplay}`;
54526
54653
  }
@@ -54534,6 +54661,11 @@ class ContextBarWidget {
54534
54661
  const clampedPercent = Math.max(0, Math.min(100, upstreamPct));
54535
54662
  const usedK = Math.round(used / 1000);
54536
54663
  const totalK = Math.round(total / 1000);
54664
+ if (isSliderMode(displayMode)) {
54665
+ const slider = renderContextSlider("slider-only", clampedPercent);
54666
+ const sliderDisplay = displayMode === "slider" ? `${slider} ${usedK}k/${totalK}k (${Math.round(clampedPercent)}%)` : slider;
54667
+ return item.rawValue ? sliderDisplay : `Context: ${sliderDisplay}`;
54668
+ }
54537
54669
  const display = `${makeUsageProgressBar(clampedPercent, barWidth, powerlineMode)} ${usedK}k/${totalK}k (${Math.round(clampedPercent)}%)`;
54538
54670
  return item.rawValue ? display : `Context: ${display}`;
54539
54671
  }
@@ -54615,7 +54747,13 @@ class PremiumRequestsWidget {
54615
54747
  return "Session";
54616
54748
  }
54617
54749
  getEditorDisplay(item) {
54618
- return { displayText: this.getDisplayName() };
54750
+ return {
54751
+ displayText: this.getDisplayName(),
54752
+ modifierText: makeModifierText([getHideWhenZeroLabel(item)].filter((label) => label !== undefined))
54753
+ };
54754
+ }
54755
+ handleEditorAction(action, item) {
54756
+ return handleToggleHideWhenZeroAction(action, item);
54619
54757
  }
54620
54758
  render(item, context, settings) {
54621
54759
  if (context.isPreview) {
@@ -54623,10 +54761,16 @@ class PremiumRequestsWidget {
54623
54761
  }
54624
54762
  const requests = context.data?.cost?.total_premium_requests;
54625
54763
  if (typeof requests === "number") {
54764
+ if (requests === 0 && isHideWhenZeroEnabled(item)) {
54765
+ return null;
54766
+ }
54626
54767
  return formatRawOrLabeledValue(item, "Reqs: ", String(requests));
54627
54768
  }
54628
54769
  return null;
54629
54770
  }
54771
+ getCustomKeybinds() {
54772
+ return getHideWhenZeroKeybinds();
54773
+ }
54630
54774
  supportsRawValue() {
54631
54775
  return true;
54632
54776
  }
@@ -54652,7 +54796,13 @@ class ApiCallsWidget {
54652
54796
  return "Session";
54653
54797
  }
54654
54798
  getEditorDisplay(item) {
54655
- return { displayText: this.getDisplayName() };
54799
+ return {
54800
+ displayText: this.getDisplayName(),
54801
+ modifierText: makeModifierText([getHideWhenZeroLabel(item)].filter((label) => label !== undefined))
54802
+ };
54803
+ }
54804
+ handleEditorAction(action, item) {
54805
+ return handleToggleHideWhenZeroAction(action, item);
54656
54806
  }
54657
54807
  render(item, context, settings) {
54658
54808
  if (context.isPreview) {
@@ -54665,10 +54815,16 @@ class ApiCallsWidget {
54665
54815
  const parsed = parseDisplayName(context.data?.model?.display_name);
54666
54816
  if (parsed.multiplierValue && parsed.multiplierValue > 0) {
54667
54817
  const apiCalls = Math.round(requests / parsed.multiplierValue);
54818
+ if (apiCalls === 0 && isHideWhenZeroEnabled(item)) {
54819
+ return null;
54820
+ }
54668
54821
  return formatRawOrLabeledValue(item, "Calls: ", String(apiCalls));
54669
54822
  }
54670
54823
  return null;
54671
54824
  }
54825
+ getCustomKeybinds() {
54826
+ return getHideWhenZeroKeybinds();
54827
+ }
54672
54828
  supportsRawValue() {
54673
54829
  return true;
54674
54830
  }
@@ -54890,7 +55046,7 @@ class CacheWriteTokensWidget {
54890
55046
  }
54891
55047
  }
54892
55048
  // src/utils/git.ts
54893
- import { execSync as execSync4 } from "child_process";
55049
+ import { execFileSync } from "child_process";
54894
55050
  var gitCommandCache = new Map;
54895
55051
  function resolveGitCwd(context) {
54896
55052
  const candidates = [
@@ -54905,13 +55061,14 @@ function resolveGitCwd(context) {
54905
55061
  return;
54906
55062
  }
54907
55063
  function runGit(command, context) {
55064
+ const args = command.trim().split(/\s+/).filter(Boolean);
54908
55065
  const cwd2 = resolveGitCwd(context);
54909
55066
  const cacheKey = `${command}|${cwd2 ?? ""}`;
54910
55067
  if (gitCommandCache.has(cacheKey)) {
54911
55068
  return gitCommandCache.get(cacheKey) ?? null;
54912
55069
  }
54913
55070
  try {
54914
- const output = execSync4(`git ${command}`, {
55071
+ const output = execFileSync("git", args, {
54915
55072
  encoding: "utf8",
54916
55073
  stdio: ["pipe", "pipe", "ignore"],
54917
55074
  ...cwd2 ? { cwd: cwd2 } : {}
@@ -54945,6 +55102,23 @@ function getGitChangeCounts(context) {
54945
55102
  deletions: unstagedCounts.deletions + stagedCounts.deletions
54946
55103
  };
54947
55104
  }
55105
+ function hasRenameOrCopyStatus(line) {
55106
+ return line.startsWith("R") || line.startsWith("C") || line[1] === "R" || line[1] === "C";
55107
+ }
55108
+ function forEachPorcelainEntry(output, visit) {
55109
+ const entries = output.split("\x00");
55110
+ for (let index = 0;index < entries.length; index += 1) {
55111
+ const line = entries[index];
55112
+ if (typeof line !== "string" || line.length < 2)
55113
+ continue;
55114
+ const shouldContinue = visit(line);
55115
+ if (hasRenameOrCopyStatus(line)) {
55116
+ index += 1;
55117
+ }
55118
+ if (shouldContinue === false)
55119
+ return;
55120
+ }
55121
+ }
54948
55122
  function getGitStatus(context) {
54949
55123
  const output = runGit("--no-optional-locks status --porcelain -z", context);
54950
55124
  if (!output) {
@@ -54954,11 +55128,7 @@ function getGitStatus(context) {
54954
55128
  let unstaged = false;
54955
55129
  let untracked = false;
54956
55130
  let conflicts = false;
54957
- const entries = output.split("\x00");
54958
- for (let index = 0;index < entries.length; index += 1) {
54959
- const line = entries[index];
54960
- if (typeof line !== "string" || line.length < 2)
54961
- continue;
55131
+ forEachPorcelainEntry(output, (line) => {
54962
55132
  if (!conflicts && /^(DD|AU|UD|UA|DU|AA|UU)/.test(line))
54963
55133
  conflicts = true;
54964
55134
  if (!staged && /^[MADRCTU]/.test(line))
@@ -54968,14 +55138,32 @@ function getGitStatus(context) {
54968
55138
  if (!untracked && line.startsWith("??"))
54969
55139
  untracked = true;
54970
55140
  if (staged && unstaged && untracked && conflicts)
54971
- break;
54972
- const indexStatus = line[0];
54973
- if (indexStatus === "R" || indexStatus === "C") {
54974
- index += 1;
54975
- }
54976
- }
55141
+ return false;
55142
+ return;
55143
+ });
54977
55144
  return { staged, unstaged, untracked, conflicts };
54978
55145
  }
55146
+ function getGitFileStatusCounts(context) {
55147
+ const output = runGit("--no-optional-locks status --porcelain -z", context);
55148
+ if (!output) {
55149
+ return { staged: 0, unstaged: 0, untracked: 0 };
55150
+ }
55151
+ let staged = 0;
55152
+ let unstaged = 0;
55153
+ let untracked = 0;
55154
+ forEachPorcelainEntry(output, (line) => {
55155
+ if (line.startsWith("??")) {
55156
+ untracked += 1;
55157
+ return;
55158
+ }
55159
+ if (/^[MADRCTU]/.test(line))
55160
+ staged += 1;
55161
+ if (/^.[MADRCTU]/.test(line))
55162
+ unstaged += 1;
55163
+ return;
55164
+ });
55165
+ return { staged, unstaged, untracked };
55166
+ }
54979
55167
  function getGitAheadBehind(context) {
54980
55168
  const output = runGit("rev-list --left-right --count HEAD...@{upstream}", context);
54981
55169
  if (!output)
@@ -55161,6 +55349,9 @@ var HIDE_NO_GIT_KEYBIND = {
55161
55349
  function isHideNoGitEnabled(item) {
55162
55350
  return isMetadataFlagEnabled(item, HIDE_NO_GIT_KEY);
55163
55351
  }
55352
+ function getHideNoGitLabel(item) {
55353
+ return isHideNoGitEnabled(item) ? "hide 'no git'" : undefined;
55354
+ }
55164
55355
  function getHideNoGitModifierText(item) {
55165
55356
  return makeModifierText(isHideNoGitEnabled(item) ? ["hide 'no git'"] : []);
55166
55357
  }
@@ -55515,7 +55706,7 @@ class GitRootDirWidget {
55515
55706
  }
55516
55707
  }
55517
55708
  // src/utils/gh-pr-cache.ts
55518
- import { execFileSync } from "child_process";
55709
+ import { execFileSync as execFileSync2 } from "child_process";
55519
55710
  import {
55520
55711
  existsSync as existsSync6,
55521
55712
  mkdirSync as mkdirSync3,
@@ -55530,7 +55721,7 @@ var PR_CACHE_TTL = 30000;
55530
55721
  var GH_TIMEOUT = 5000;
55531
55722
  var DEFAULT_TITLE_MAX_WIDTH = 30;
55532
55723
  var DEFAULT_PR_CACHE_DEPS = {
55533
- execFileSync,
55724
+ execFileSync: execFileSync2,
55534
55725
  existsSync: existsSync6,
55535
55726
  mkdirSync: mkdirSync3,
55536
55727
  readFileSync: readFileSync4,
@@ -55894,6 +56085,59 @@ class GitStagedWidget {
55894
56085
  return true;
55895
56086
  }
55896
56087
  }
56088
+ // src/widgets/GitStagedFiles.ts
56089
+ class GitStagedFilesWidget {
56090
+ getDefaultColor() {
56091
+ return "green";
56092
+ }
56093
+ getDescription() {
56094
+ return "Shows count of staged files";
56095
+ }
56096
+ getDisplayName() {
56097
+ return "Git Staged Files";
56098
+ }
56099
+ getCategory() {
56100
+ return "Git";
56101
+ }
56102
+ getEditorDisplay(item) {
56103
+ const labels = [getHideNoGitLabel(item), getHideWhenZeroLabel(item)].filter((label) => label !== undefined);
56104
+ return {
56105
+ displayText: this.getDisplayName(),
56106
+ modifierText: makeModifierText(labels)
56107
+ };
56108
+ }
56109
+ handleEditorAction(action, item) {
56110
+ return handleToggleNoGitAction(action, item) ?? handleToggleHideWhenZeroAction(action, item);
56111
+ }
56112
+ render(item, context, _settings) {
56113
+ const hideNoGit = isHideNoGitEnabled(item);
56114
+ if (context.isPreview) {
56115
+ return item.rawValue ? "3" : "S:3";
56116
+ }
56117
+ if (!isInsideGitWorkTree(context)) {
56118
+ return hideNoGit ? null : "(no git)";
56119
+ }
56120
+ const count = getGitFileStatusCounts(context).staged;
56121
+ if (count === 0 && isHideWhenZeroEnabled(item)) {
56122
+ return null;
56123
+ }
56124
+ return item.rawValue ? count.toString() : `S:${count}`;
56125
+ }
56126
+ getCustomKeybinds() {
56127
+ return [...getHideNoGitKeybinds(), ...getHideWhenZeroKeybinds()];
56128
+ }
56129
+ getNumericValue(context, _item) {
56130
+ if (!isInsideGitWorkTree(context))
56131
+ return null;
56132
+ return getGitFileStatusCounts(context).staged;
56133
+ }
56134
+ supportsRawValue() {
56135
+ return true;
56136
+ }
56137
+ supportsColors(_item) {
56138
+ return true;
56139
+ }
56140
+ }
55897
56141
  // src/widgets/GitUnstaged.ts
55898
56142
  var DEFAULT_SYMBOL2 = "*";
55899
56143
 
@@ -55953,6 +56197,59 @@ class GitUnstagedWidget {
55953
56197
  return true;
55954
56198
  }
55955
56199
  }
56200
+ // src/widgets/GitUnstagedFiles.ts
56201
+ class GitUnstagedFilesWidget {
56202
+ getDefaultColor() {
56203
+ return "yellow";
56204
+ }
56205
+ getDescription() {
56206
+ return "Shows count of unstaged tracked files";
56207
+ }
56208
+ getDisplayName() {
56209
+ return "Git Unstaged Files";
56210
+ }
56211
+ getCategory() {
56212
+ return "Git";
56213
+ }
56214
+ getEditorDisplay(item) {
56215
+ const labels = [getHideNoGitLabel(item), getHideWhenZeroLabel(item)].filter((label) => label !== undefined);
56216
+ return {
56217
+ displayText: this.getDisplayName(),
56218
+ modifierText: makeModifierText(labels)
56219
+ };
56220
+ }
56221
+ handleEditorAction(action, item) {
56222
+ return handleToggleNoGitAction(action, item) ?? handleToggleHideWhenZeroAction(action, item);
56223
+ }
56224
+ render(item, context, _settings) {
56225
+ const hideNoGit = isHideNoGitEnabled(item);
56226
+ if (context.isPreview) {
56227
+ return item.rawValue ? "2" : "M:2";
56228
+ }
56229
+ if (!isInsideGitWorkTree(context)) {
56230
+ return hideNoGit ? null : "(no git)";
56231
+ }
56232
+ const count = getGitFileStatusCounts(context).unstaged;
56233
+ if (count === 0 && isHideWhenZeroEnabled(item)) {
56234
+ return null;
56235
+ }
56236
+ return item.rawValue ? count.toString() : `M:${count}`;
56237
+ }
56238
+ getCustomKeybinds() {
56239
+ return [...getHideNoGitKeybinds(), ...getHideWhenZeroKeybinds()];
56240
+ }
56241
+ getNumericValue(context, _item) {
56242
+ if (!isInsideGitWorkTree(context))
56243
+ return null;
56244
+ return getGitFileStatusCounts(context).unstaged;
56245
+ }
56246
+ supportsRawValue() {
56247
+ return true;
56248
+ }
56249
+ supportsColors(_item) {
56250
+ return true;
56251
+ }
56252
+ }
55956
56253
  // src/widgets/GitUntracked.ts
55957
56254
  var DEFAULT_SYMBOL3 = "?";
55958
56255
 
@@ -56012,6 +56309,107 @@ class GitUntrackedWidget {
56012
56309
  return true;
56013
56310
  }
56014
56311
  }
56312
+ // src/widgets/GitUntrackedFiles.ts
56313
+ class GitUntrackedFilesWidget {
56314
+ getDefaultColor() {
56315
+ return "red";
56316
+ }
56317
+ getDescription() {
56318
+ return "Shows count of untracked files";
56319
+ }
56320
+ getDisplayName() {
56321
+ return "Git Untracked Files";
56322
+ }
56323
+ getCategory() {
56324
+ return "Git";
56325
+ }
56326
+ getEditorDisplay(item) {
56327
+ const labels = [getHideNoGitLabel(item), getHideWhenZeroLabel(item)].filter((label) => label !== undefined);
56328
+ return {
56329
+ displayText: this.getDisplayName(),
56330
+ modifierText: makeModifierText(labels)
56331
+ };
56332
+ }
56333
+ handleEditorAction(action, item) {
56334
+ return handleToggleNoGitAction(action, item) ?? handleToggleHideWhenZeroAction(action, item);
56335
+ }
56336
+ render(item, context, _settings) {
56337
+ const hideNoGit = isHideNoGitEnabled(item);
56338
+ if (context.isPreview) {
56339
+ return item.rawValue ? "1" : "?:1";
56340
+ }
56341
+ if (!isInsideGitWorkTree(context)) {
56342
+ return hideNoGit ? null : "(no git)";
56343
+ }
56344
+ const count = getGitFileStatusCounts(context).untracked;
56345
+ if (count === 0 && isHideWhenZeroEnabled(item)) {
56346
+ return null;
56347
+ }
56348
+ return item.rawValue ? count.toString() : `?:${count}`;
56349
+ }
56350
+ getCustomKeybinds() {
56351
+ return [...getHideNoGitKeybinds(), ...getHideWhenZeroKeybinds()];
56352
+ }
56353
+ getNumericValue(context, _item) {
56354
+ if (!isInsideGitWorkTree(context))
56355
+ return null;
56356
+ return getGitFileStatusCounts(context).untracked;
56357
+ }
56358
+ supportsRawValue() {
56359
+ return true;
56360
+ }
56361
+ supportsColors(_item) {
56362
+ return true;
56363
+ }
56364
+ }
56365
+ // src/widgets/GitCleanStatus.ts
56366
+ class GitCleanStatusWidget {
56367
+ getDefaultColor() {
56368
+ return "green";
56369
+ }
56370
+ getDescription() {
56371
+ return "Shows ✓ when the working tree is clean and ✗ when it is dirty";
56372
+ }
56373
+ getDisplayName() {
56374
+ return "Git Clean Status";
56375
+ }
56376
+ getCategory() {
56377
+ return "Git";
56378
+ }
56379
+ getEditorDisplay(item) {
56380
+ return {
56381
+ displayText: this.getDisplayName(),
56382
+ modifierText: getHideNoGitModifierText(item)
56383
+ };
56384
+ }
56385
+ handleEditorAction(action, item) {
56386
+ return handleToggleNoGitAction(action, item);
56387
+ }
56388
+ render(item, context, _settings) {
56389
+ const hideNoGit = isHideNoGitEnabled(item);
56390
+ if (context.isPreview) {
56391
+ return item.rawValue ? "clean" : "✓";
56392
+ }
56393
+ if (!isInsideGitWorkTree(context)) {
56394
+ return hideNoGit ? null : "(no git)";
56395
+ }
56396
+ const status = getGitStatus(context);
56397
+ const clean = !status.staged && !status.unstaged && !status.untracked && !status.conflicts;
56398
+ if (item.rawValue) {
56399
+ return clean ? "clean" : "dirty";
56400
+ }
56401
+ return clean ? "✓" : "✗";
56402
+ }
56403
+ getCustomKeybinds() {
56404
+ return getHideNoGitKeybinds();
56405
+ }
56406
+ supportsRawValue() {
56407
+ return true;
56408
+ }
56409
+ supportsColors(_item) {
56410
+ return true;
56411
+ }
56412
+ }
56015
56413
  // src/widgets/GitAheadBehind.ts
56016
56414
  class GitAheadBehindWidget {
56017
56415
  getDefaultColor() {
@@ -56099,17 +56497,14 @@ class GitConflictsWidget {
56099
56497
  return "Git";
56100
56498
  }
56101
56499
  getEditorDisplay(item) {
56102
- const modifiers = [];
56103
- const noGitText = getHideNoGitModifierText(item);
56104
- if (noGitText)
56105
- modifiers.push("hide 'no git'");
56500
+ const labels = [getHideNoGitLabel(item), getHideWhenZeroLabel(item)].filter((label) => label !== undefined);
56106
56501
  return {
56107
56502
  displayText: this.getDisplayName(),
56108
- modifierText: makeModifierText(modifiers)
56503
+ modifierText: makeModifierText(labels)
56109
56504
  };
56110
56505
  }
56111
56506
  handleEditorAction(action, item) {
56112
- return handleToggleNoGitAction(action, item);
56507
+ return handleToggleNoGitAction(action, item) ?? handleToggleHideWhenZeroAction(action, item);
56113
56508
  }
56114
56509
  render(item, context, _settings) {
56115
56510
  const hideNoGit = isHideNoGitEnabled(item);
@@ -56122,13 +56517,16 @@ class GitConflictsWidget {
56122
56517
  return hideNoGit ? null : "(no git)";
56123
56518
  }
56124
56519
  const count = getGitConflictCount(context);
56520
+ if (count === 0 && isHideWhenZeroEnabled(item)) {
56521
+ return null;
56522
+ }
56125
56523
  if (item.rawValue) {
56126
56524
  return count.toString();
56127
56525
  }
56128
56526
  return `⚠ ${count}`;
56129
56527
  }
56130
56528
  getCustomKeybinds() {
56131
- return getHideNoGitKeybinds();
56529
+ return [...getHideNoGitKeybinds(), ...getHideWhenZeroKeybinds()];
56132
56530
  }
56133
56531
  getNumericValue(context, _item) {
56134
56532
  if (!isInsideGitWorkTree(context))
@@ -57008,7 +57406,7 @@ class TerminalWidthWidget {
57008
57406
  }
57009
57407
  }
57010
57408
  // src/widgets/FreeMemory.ts
57011
- import { execSync as execSync5 } from "child_process";
57409
+ import { execSync as execSync4 } from "child_process";
57012
57410
  import os8 from "os";
57013
57411
  function formatBytes(bytes) {
57014
57412
  const GB = 1024 ** 3;
@@ -57024,7 +57422,7 @@ function formatBytes(bytes) {
57024
57422
  }
57025
57423
  function getUsedMemoryMacOS() {
57026
57424
  try {
57027
- const output = execSync5("vm_stat", { encoding: "utf8" });
57425
+ const output = execSync4("vm_stat", { encoding: "utf8" });
57028
57426
  const lines = output.split(`
57029
57427
  `);
57030
57428
  const firstLine = lines[0];
@@ -57337,7 +57735,7 @@ var CustomSymbolEditor = ({ widget, onComplete, onCancel }) => {
57337
57735
  }, undefined, true, undefined, this);
57338
57736
  };
57339
57737
  // src/widgets/CustomCommand.tsx
57340
- import { execSync as execSync6 } from "child_process";
57738
+ import { execSync as execSync5 } from "child_process";
57341
57739
  var import_react32 = __toESM(require_react(), 1);
57342
57740
 
57343
57741
  // src/utils/ansi.ts
@@ -57725,7 +58123,7 @@ class CustomCommandWidget {
57725
58123
  try {
57726
58124
  const timeout = item.timeout ?? 1000;
57727
58125
  const jsonInput = JSON.stringify(context.data);
57728
- let output = execSync6(item.commandPath, {
58126
+ let output = execSync5(item.commandPath, {
57729
58127
  encoding: "utf8",
57730
58128
  input: jsonInput,
57731
58129
  timeout,
@@ -58157,8 +58555,12 @@ var WIDGET_MANIFEST = [
58157
58555
  { type: "git-pr", create: () => new GitPrWidget },
58158
58556
  { type: "git-status", create: () => new GitStatusWidget },
58159
58557
  { type: "git-staged", create: () => new GitStagedWidget },
58558
+ { type: "git-staged-files", create: () => new GitStagedFilesWidget },
58160
58559
  { type: "git-unstaged", create: () => new GitUnstagedWidget },
58560
+ { type: "git-unstaged-files", create: () => new GitUnstagedFilesWidget },
58161
58561
  { type: "git-untracked", create: () => new GitUntrackedWidget },
58562
+ { type: "git-untracked-files", create: () => new GitUntrackedFilesWidget },
58563
+ { type: "git-clean-status", create: () => new GitCleanStatusWidget },
58162
58564
  { type: "git-ahead-behind", create: () => new GitAheadBehindWidget },
58163
58565
  { type: "git-conflicts", create: () => new GitConflictsWidget },
58164
58566
  { type: "git-sha", create: () => new GitShaWidget },
@@ -62429,17 +62831,18 @@ function renderStatusLine(widgets, settings, context, preRenderedWidgets, preCal
62429
62831
  if (!widget)
62430
62832
  continue;
62431
62833
  if (widget.type === "separator") {
62432
- let hasContentBefore = false;
62834
+ let previousWidgetRenderedContent = false;
62433
62835
  for (let j = i - 1;j >= 0; j--) {
62434
62836
  const prevWidget = widgets[j];
62435
- if (prevWidget && prevWidget.type !== "separator" && prevWidget.type !== "flex-separator") {
62436
- if (preRenderedWidgets[j]?.content) {
62437
- hasContentBefore = true;
62438
- break;
62439
- }
62837
+ if (prevWidget?.type === "separator" || prevWidget?.type === "flex-separator") {
62838
+ continue;
62839
+ }
62840
+ if (prevWidget) {
62841
+ previousWidgetRenderedContent = Boolean(preRenderedWidgets[j]?.content);
62842
+ break;
62440
62843
  }
62441
62844
  }
62442
- if (!hasContentBefore)
62845
+ if (!previousWidgetRenderedContent)
62443
62846
  continue;
62444
62847
  const sepChar = widget.character ?? (settings.defaultSeparator ?? "|");
62445
62848
  const formattedSep = formatSeparator(sepChar);
@@ -63474,8 +63877,8 @@ async function ensureWindowsUtf8CodePage() {
63474
63877
  return;
63475
63878
  }
63476
63879
  try {
63477
- const { execFileSync: execFileSync2 } = await import("child_process");
63478
- execFileSync2("chcp.com", ["65001"], { stdio: "ignore" });
63880
+ const { execFileSync: execFileSync3 } = await import("child_process");
63881
+ execFileSync3("chcp.com", ["65001"], { stdio: "ignore" });
63479
63882
  } catch {}
63480
63883
  }
63481
63884
  async function renderMultipleLines(data) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "copilot-statusline",
3
- "version": "0.1.14",
3
+ "version": "0.1.16",
4
4
  "description": "A customizable status line formatter for GitHub Copilot CLI — based on ccstatusline",
5
5
  "module": "src/copilot-statusline.ts",
6
6
  "type": "module",