copilot-statusline 0.1.13 → 0.1.15

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
@@ -265,6 +265,7 @@ Copilot CLI spawns your status line command on every state change, passing sessi
265
265
  | Tokens Input | `tokens-input` | Total input tokens |
266
266
  | Tokens Output | `tokens-output` | Total output tokens |
267
267
  | Tokens Cached | `tokens-cached` | Total cached tokens (read + write) |
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 |
279
- | Context % (Usable) | `context-percentage-usable` | Percentage of usable context (80% of max) |
280
- | Context Bar | `context-bar` | Visual progress bar for context usage |
281
- | Remaining Tokens | `remaining-tokens` | Absolute remaining context tokens |
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`) |
280
+ | Context % | `context-percentage` | Live context % from Copilot (`current_context_used_percentage`) |
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
 
@@ -368,7 +376,8 @@ Widget-specific shortcuts:
368
376
  | Git widgets | `h` | Toggle hide `no git` output |
369
377
  | Git Branch | `l` | Toggle GitHub link |
370
378
  | Context % widgets | `u` | Toggle used/remaining display |
371
- | Context Bar | `p` | Cycle bar style (short/full) |
379
+ | Context % widgets | `p` | Cycle numeric/short bar display |
380
+ | Context Bar | `p` | Cycle bar style (medium/full/short/short-only) |
372
381
  | Current Working Dir | `h` | Toggle `~` home abbreviation |
373
382
  | Current Working Dir | `s` | Edit segment limit |
374
383
  | Current Working Dir | `f` | Toggle fish-style path |
@@ -620,4 +629,4 @@ This project includes substantial portions of code from [ccstatusline](https://g
620
629
  [![npm downloads](https://img.shields.io/npm/dm/copilot-statusline.svg)](https://www.npmjs.com/package/copilot-statusline)
621
630
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
622
631
 
623
- </div>
632
+ </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.13";
52916
+ var PACKAGE_VERSION = "0.1.15";
52917
52917
  function getPackageVersion() {
52918
52918
  if (/^\d+\.\d+\.\d+/.test(PACKAGE_VERSION)) {
52919
52919
  return PACKAGE_VERSION;
@@ -54075,18 +54075,13 @@ function getContextWindowMetrics(data) {
54075
54075
  const empty = {
54076
54076
  windowSize: null,
54077
54077
  displayedContextLimit: null,
54078
- usedTokens: null,
54079
- contextLengthTokens: null,
54080
54078
  currentContextTokens: null,
54081
54079
  currentContextUsedPercentage: null,
54082
- usedPercentage: null,
54083
- remainingPercentage: null,
54084
54080
  totalInputTokens: null,
54085
54081
  totalOutputTokens: null,
54086
54082
  cachedTokens: null,
54087
54083
  totalTokens: null,
54088
54084
  reasoningTokens: null,
54089
- remainingTokens: null,
54090
54085
  lastCallInputTokens: null,
54091
54086
  lastCallOutputTokens: null,
54092
54087
  cacheReadTokens: null,
@@ -54107,45 +54102,20 @@ function getContextWindowMetrics(data) {
54107
54102
  const totalOutputTokens = toFiniteNonNegativeNumber(contextWindow.total_output_tokens);
54108
54103
  const cacheReadTokens = toFiniteNonNegativeNumber(contextWindow.total_cache_read_tokens);
54109
54104
  const cacheWriteTokens = toFiniteNonNegativeNumber(contextWindow.total_cache_write_tokens);
54110
- const remainingTokens = toFiniteNonNegativeNumber(contextWindow.remaining_tokens);
54111
54105
  const lastCallInputTokens = toFiniteNonNegativeNumber(contextWindow.last_call_input_tokens);
54112
54106
  const lastCallOutputTokens = toFiniteNonNegativeNumber(contextWindow.last_call_output_tokens);
54113
54107
  const cachedTokens = cacheReadTokens !== null || cacheWriteTokens !== null ? (cacheReadTokens ?? 0) + (cacheWriteTokens ?? 0) : null;
54114
- let currentUsageTotalTokens = null;
54115
- let contextLengthTokens = null;
54116
- if (contextWindow.current_usage && typeof contextWindow.current_usage === "object") {
54117
- const usage = contextWindow.current_usage;
54118
- const inputTokens = toFiniteNonNegativeNumber(usage.input_tokens) ?? 0;
54119
- const outputTokens = toFiniteNonNegativeNumber(usage.output_tokens) ?? 0;
54120
- const cacheCreationTokens = toFiniteNonNegativeNumber(usage.cache_creation_input_tokens) ?? 0;
54121
- const cacheReadInputTokens = toFiniteNonNegativeNumber(usage.cache_read_input_tokens) ?? 0;
54122
- currentUsageTotalTokens = inputTokens + outputTokens + cacheCreationTokens + cacheReadInputTokens;
54123
- contextLengthTokens = inputTokens + cacheCreationTokens + cacheReadInputTokens;
54124
- }
54125
- const rawUsedPercentage = toFiniteNonNegativeNumber(contextWindow.used_percentage);
54126
- const rawRemainingPercentage = toFiniteNonNegativeNumber(contextWindow.remaining_percentage);
54127
- const usedTokensFromRemaining = windowSize !== null && remainingTokens !== null ? windowSize - remainingTokens : null;
54128
- const usedTokensFromPercentage = rawUsedPercentage !== null && windowSize !== null ? rawUsedPercentage / 100 * windowSize : null;
54129
- const usedTokens = usedTokensFromRemaining ?? usedTokensFromPercentage ?? currentUsageTotalTokens;
54130
- const usedPercentage = rawUsedPercentage !== null ? clampPercentage(rawUsedPercentage) : usedTokens !== null && windowSize !== null && windowSize > 0 ? clampPercentage(usedTokens / windowSize * 100) : null;
54131
- const remainingPercentage = rawRemainingPercentage !== null ? clampPercentage(rawRemainingPercentage) : usedPercentage !== null ? 100 - usedPercentage : null;
54132
- const totalTokens = currentUsageTotalTokens ?? toFiniteNonNegativeNumber(contextWindow.total_tokens) ?? (totalInputTokens !== null && totalOutputTokens !== null ? totalInputTokens + totalOutputTokens : null);
54133
- const contextLengthFromAuthoritative = usedTokensFromRemaining ?? usedTokensFromPercentage;
54108
+ const totalTokens = toFiniteNonNegativeNumber(contextWindow.total_tokens) ?? (totalInputTokens !== null && totalOutputTokens !== null ? totalInputTokens + totalOutputTokens : null);
54134
54109
  return {
54135
54110
  windowSize,
54136
54111
  displayedContextLimit,
54137
- usedTokens,
54138
- contextLengthTokens: contextLengthFromAuthoritative ?? contextLengthTokens ?? usedTokens,
54139
54112
  currentContextTokens,
54140
54113
  currentContextUsedPercentage,
54141
- usedPercentage,
54142
- remainingPercentage,
54143
54114
  totalInputTokens,
54144
54115
  totalOutputTokens,
54145
54116
  cachedTokens,
54146
54117
  totalTokens,
54147
54118
  reasoningTokens,
54148
- remainingTokens,
54149
54119
  lastCallInputTokens,
54150
54120
  lastCallOutputTokens,
54151
54121
  cacheReadTokens,
@@ -54441,95 +54411,117 @@ function handleContextInverseAction(action, item) {
54441
54411
  return toggleMetadataFlag(item, INVERSE_KEY);
54442
54412
  }
54443
54413
 
54444
- // src/widgets/ContextPercentage.ts
54445
- class ContextPercentageWidget {
54446
- getDefaultColor() {
54447
- return "blue";
54448
- }
54449
- getDescription() {
54450
- return "Shows percentage of context window used or remaining";
54451
- }
54452
- getDisplayName() {
54453
- return "Context %";
54454
- }
54455
- getCategory() {
54456
- return "Context";
54414
+ // src/widgets/shared/context-slider.ts
54415
+ var SLIDER_WIDTH = 10;
54416
+ var SLIDER_TOGGLE_KEYBIND = { key: "p", label: "(p)rogress toggle", action: "toggle-slider" };
54417
+ function makeSliderBar(percent, width = SLIDER_WIDTH) {
54418
+ const clamped = Math.max(0, Math.min(100, percent));
54419
+ const filled = Math.round(clamped / 100 * width);
54420
+ return "▓".repeat(filled) + "░".repeat(width - filled);
54421
+ }
54422
+ function getContextSliderMode(item) {
54423
+ const mode = item.metadata?.display;
54424
+ if (mode === "slider" || mode === "slider-only") {
54425
+ return mode;
54457
54426
  }
54458
- getEditorDisplay(item) {
54427
+ return "none";
54428
+ }
54429
+ function cycleContextSliderMode(item) {
54430
+ const currentMode = getContextSliderMode(item);
54431
+ const nextMode = currentMode === "none" ? "slider" : currentMode === "slider" ? "slider-only" : "none";
54432
+ if (nextMode === "none") {
54433
+ const nextMetadata = { ...item.metadata ?? {} };
54434
+ delete nextMetadata.display;
54459
54435
  return {
54460
- displayText: this.getDisplayName(),
54461
- modifierText: getContextInverseModifierText(item)
54436
+ ...item,
54437
+ metadata: Object.keys(nextMetadata).length > 0 ? nextMetadata : undefined
54462
54438
  };
54463
54439
  }
54464
- handleEditorAction(action, item) {
54465
- return handleContextInverseAction(action, item);
54466
- }
54467
- render(item, context, settings) {
54468
- const isInverse = isContextInverse(item);
54469
- if (context.isPreview) {
54470
- const previewValue = isInverse ? "90.7%" : "9.3%";
54471
- return formatRawOrLabeledValue(item, "Ctx: ", previewValue);
54472
- }
54473
- const metrics = getContextWindowMetrics(context.data);
54474
- if (metrics.currentContextUsedPercentage !== null) {
54475
- const pct = metrics.currentContextUsedPercentage;
54476
- const displayPercentage = isInverse ? 100 - pct : pct;
54477
- return formatRawOrLabeledValue(item, "Ctx: ", `${displayPercentage.toFixed(1)}%`);
54440
+ return {
54441
+ ...item,
54442
+ metadata: {
54443
+ ...item.metadata ?? {},
54444
+ display: nextMode
54478
54445
  }
54446
+ };
54447
+ }
54448
+ function renderContextSlider(mode, percent) {
54449
+ if (mode === "none") {
54479
54450
  return null;
54480
54451
  }
54481
- getCustomKeybinds() {
54482
- return [
54483
- { key: "u", label: "(u)sed/remaining", action: "toggle-inverse" }
54484
- ];
54452
+ const slider = makeSliderBar(percent);
54453
+ if (mode === "slider") {
54454
+ return `${slider} ${percent.toFixed(1)}%`;
54485
54455
  }
54486
- supportsRawValue() {
54487
- return true;
54456
+ return slider;
54457
+ }
54458
+ function getContextSliderModifierText(item) {
54459
+ const mode = getContextSliderMode(item);
54460
+ if (mode === "slider") {
54461
+ return "(short bar)";
54488
54462
  }
54489
- supportsColors(item) {
54490
- return true;
54463
+ if (mode === "slider-only") {
54464
+ return "(short bar only)";
54491
54465
  }
54466
+ return;
54492
54467
  }
54493
- // src/widgets/ContextPercentageUsable.ts
54494
- class ContextPercentageUsableWidget {
54468
+ function getContextSliderKeybinds() {
54469
+ return [SLIDER_TOGGLE_KEYBIND];
54470
+ }
54471
+
54472
+ // src/widgets/ContextPercentage.ts
54473
+ class ContextPercentageWidget {
54495
54474
  getDefaultColor() {
54496
54475
  return "blue";
54497
54476
  }
54498
54477
  getDescription() {
54499
- return "Shows percentage of usable context (current_context_tokens / displayed_context_limit)";
54478
+ return "Shows percentage of context window used or remaining";
54500
54479
  }
54501
54480
  getDisplayName() {
54502
- return "Context % (Usable)";
54481
+ return "Context %";
54503
54482
  }
54504
54483
  getCategory() {
54505
54484
  return "Context";
54506
54485
  }
54507
54486
  getEditorDisplay(item) {
54487
+ const modifiers = [
54488
+ getContextInverseModifierText(item),
54489
+ getContextSliderModifierText(item)
54490
+ ].filter((modifier) => modifier !== undefined);
54508
54491
  return {
54509
54492
  displayText: this.getDisplayName(),
54510
- modifierText: getContextInverseModifierText(item)
54493
+ modifierText: modifiers.length > 0 ? `(${modifiers.map((modifier) => modifier.replace(/^\(|\)$/g, "")).join(", ")})` : undefined
54511
54494
  };
54512
54495
  }
54513
54496
  handleEditorAction(action, item) {
54497
+ if (action === "toggle-slider") {
54498
+ return cycleContextSliderMode(item);
54499
+ }
54514
54500
  return handleContextInverseAction(action, item);
54515
54501
  }
54516
54502
  render(item, context, settings) {
54517
54503
  const isInverse = isContextInverse(item);
54504
+ const label = isInverse ? "Ctx Left: " : "Ctx Used: ";
54505
+ const sliderMode = getContextSliderMode(item);
54506
+ const formatPercentage = (displayPercentage) => {
54507
+ const sliderDisplay = renderContextSlider(sliderMode, displayPercentage);
54508
+ return formatRawOrLabeledValue(item, label, sliderDisplay ?? `${displayPercentage.toFixed(1)}%`);
54509
+ };
54518
54510
  if (context.isPreview) {
54519
- const previewValue = isInverse ? "88.4%" : "11.6%";
54520
- return formatRawOrLabeledValue(item, "Usable: ", previewValue);
54511
+ return formatPercentage(isInverse ? 90.7 : 9.3);
54521
54512
  }
54522
54513
  const metrics = getContextWindowMetrics(context.data);
54523
- if (metrics.currentContextTokens !== null && metrics.displayedContextLimit !== null && metrics.displayedContextLimit > 0) {
54524
- const usablePercent = Math.min(100, metrics.currentContextTokens / metrics.displayedContextLimit * 100);
54525
- const displayPercentage = isInverse ? 100 - usablePercent : usablePercent;
54526
- return formatRawOrLabeledValue(item, "Usable: ", `${displayPercentage.toFixed(1)}%`);
54514
+ if (metrics.currentContextUsedPercentage !== null) {
54515
+ const pct = metrics.currentContextUsedPercentage;
54516
+ const displayPercentage = isInverse ? 100 - pct : pct;
54517
+ return formatPercentage(displayPercentage);
54527
54518
  }
54528
54519
  return null;
54529
54520
  }
54530
54521
  getCustomKeybinds() {
54531
54522
  return [
54532
- { key: "u", label: "(u)sed/remaining", action: "toggle-inverse" }
54523
+ { key: "u", label: "(u)sed/remaining", action: "toggle-inverse" },
54524
+ ...getContextSliderKeybinds()
54533
54525
  ];
54534
54526
  }
54535
54527
  supportsRawValue() {
@@ -54554,7 +54546,14 @@ function makeUsageProgressBar(percent, width = 15, powerlineMode = false) {
54554
54546
 
54555
54547
  // src/widgets/ContextBar.ts
54556
54548
  function getDisplayMode(item) {
54557
- return item.metadata?.display === "progress" ? "progress" : "progress-short";
54549
+ const mode = item.metadata?.display;
54550
+ if (mode === "progress" || mode === "slider" || mode === "slider-only") {
54551
+ return mode;
54552
+ }
54553
+ return "progress-short";
54554
+ }
54555
+ function isSliderMode(mode) {
54556
+ return mode === "slider" || mode === "slider-only";
54558
54557
  }
54559
54558
 
54560
54559
  class ContextBarWidget {
@@ -54574,7 +54573,11 @@ class ContextBarWidget {
54574
54573
  const mode = getDisplayMode(item);
54575
54574
  const modifiers = [];
54576
54575
  if (mode === "progress-short") {
54576
+ modifiers.push("medium bar");
54577
+ } else if (mode === "slider") {
54577
54578
  modifiers.push("short bar");
54579
+ } else if (mode === "slider-only") {
54580
+ modifiers.push("short bar only");
54578
54581
  }
54579
54582
  return {
54580
54583
  displayText: this.getDisplayName(),
@@ -54586,7 +54589,7 @@ class ContextBarWidget {
54586
54589
  return null;
54587
54590
  }
54588
54591
  const currentMode = getDisplayMode(item);
54589
- const nextMode = currentMode === "progress-short" ? "progress" : "progress-short";
54592
+ const nextMode = currentMode === "progress-short" ? "progress" : currentMode === "progress" ? "slider" : currentMode === "slider" ? "slider-only" : "progress-short";
54590
54593
  return {
54591
54594
  ...item,
54592
54595
  metadata: {
@@ -54600,19 +54603,29 @@ class ContextBarWidget {
54600
54603
  const barWidth = displayMode === "progress" ? 32 : 16;
54601
54604
  const powerlineMode = settings.powerline.enabled;
54602
54605
  if (context.isPreview) {
54606
+ if (isSliderMode(displayMode)) {
54607
+ const slider = renderContextSlider("slider-only", 25);
54608
+ const sliderDisplay = displayMode === "slider" ? `${slider} 50k/200k (25%)` : slider;
54609
+ return item.rawValue ? sliderDisplay : `Context: ${sliderDisplay}`;
54610
+ }
54603
54611
  const previewDisplay = `${makeUsageProgressBar(25, barWidth, powerlineMode)} 50k/200k (25%)`;
54604
54612
  return item.rawValue ? previewDisplay : `Context: ${previewDisplay}`;
54605
54613
  }
54606
54614
  const contextWindowMetrics = getContextWindowMetrics(context.data);
54607
54615
  const total = contextWindowMetrics.displayedContextLimit ?? contextWindowMetrics.windowSize;
54608
54616
  const used = contextWindowMetrics.currentContextTokens;
54609
- if (used === null || total === null || total <= 0) {
54617
+ const upstreamPct = contextWindowMetrics.currentContextUsedPercentage;
54618
+ if (used === null || total === null || total <= 0 || upstreamPct === null) {
54610
54619
  return null;
54611
54620
  }
54612
- const percent = used / total * 100;
54613
- const clampedPercent = Math.max(0, Math.min(100, percent));
54621
+ const clampedPercent = Math.max(0, Math.min(100, upstreamPct));
54614
54622
  const usedK = Math.round(used / 1000);
54615
54623
  const totalK = Math.round(total / 1000);
54624
+ if (isSliderMode(displayMode)) {
54625
+ const slider = renderContextSlider("slider-only", clampedPercent);
54626
+ const sliderDisplay = displayMode === "slider" ? `${slider} ${usedK}k/${totalK}k (${Math.round(clampedPercent)}%)` : slider;
54627
+ return item.rawValue ? sliderDisplay : `Context: ${sliderDisplay}`;
54628
+ }
54616
54629
  const display = `${makeUsageProgressBar(clampedPercent, barWidth, powerlineMode)} ${usedK}k/${totalK}k (${Math.round(clampedPercent)}%)`;
54617
54630
  return item.rawValue ? display : `Context: ${display}`;
54618
54631
  }
@@ -55024,6 +55037,9 @@ function getGitChangeCounts(context) {
55024
55037
  deletions: unstagedCounts.deletions + stagedCounts.deletions
55025
55038
  };
55026
55039
  }
55040
+ function hasRenameOrCopyStatus(line) {
55041
+ return line.startsWith("R") || line.startsWith("C") || line[1] === "R" || line[1] === "C";
55042
+ }
55027
55043
  function getGitStatus(context) {
55028
55044
  const output = runGit("--no-optional-locks status --porcelain -z", context);
55029
55045
  if (!output) {
@@ -55048,13 +55064,39 @@ function getGitStatus(context) {
55048
55064
  untracked = true;
55049
55065
  if (staged && unstaged && untracked && conflicts)
55050
55066
  break;
55051
- const indexStatus = line[0];
55052
- if (indexStatus === "R" || indexStatus === "C") {
55067
+ if (hasRenameOrCopyStatus(line)) {
55053
55068
  index += 1;
55054
55069
  }
55055
55070
  }
55056
55071
  return { staged, unstaged, untracked, conflicts };
55057
55072
  }
55073
+ function getGitFileStatusCounts(context) {
55074
+ const output = runGit("--no-optional-locks status --porcelain -z", context);
55075
+ if (!output) {
55076
+ return { staged: 0, unstaged: 0, untracked: 0 };
55077
+ }
55078
+ let staged = 0;
55079
+ let unstaged = 0;
55080
+ let untracked = 0;
55081
+ const entries = output.split("\x00");
55082
+ for (let index = 0;index < entries.length; index += 1) {
55083
+ const line = entries[index];
55084
+ if (typeof line !== "string" || line.length < 2)
55085
+ continue;
55086
+ if (line.startsWith("??")) {
55087
+ untracked += 1;
55088
+ } else {
55089
+ if (/^[MADRCTU]/.test(line))
55090
+ staged += 1;
55091
+ if (/^.[MADRCTU]/.test(line))
55092
+ unstaged += 1;
55093
+ }
55094
+ if (hasRenameOrCopyStatus(line)) {
55095
+ index += 1;
55096
+ }
55097
+ }
55098
+ return { staged, unstaged, untracked };
55099
+ }
55058
55100
  function getGitAheadBehind(context) {
55059
55101
  const output = runGit("rev-list --left-right --count HEAD...@{upstream}", context);
55060
55102
  if (!output)
@@ -55973,6 +56015,55 @@ class GitStagedWidget {
55973
56015
  return true;
55974
56016
  }
55975
56017
  }
56018
+ // src/widgets/GitStagedFiles.ts
56019
+ class GitStagedFilesWidget {
56020
+ getDefaultColor() {
56021
+ return "green";
56022
+ }
56023
+ getDescription() {
56024
+ return "Shows count of staged files";
56025
+ }
56026
+ getDisplayName() {
56027
+ return "Git Staged Files";
56028
+ }
56029
+ getCategory() {
56030
+ return "Git";
56031
+ }
56032
+ getEditorDisplay(item) {
56033
+ return {
56034
+ displayText: this.getDisplayName(),
56035
+ modifierText: getHideNoGitModifierText(item)
56036
+ };
56037
+ }
56038
+ handleEditorAction(action, item) {
56039
+ return handleToggleNoGitAction(action, item);
56040
+ }
56041
+ render(item, context, _settings) {
56042
+ const hideNoGit = isHideNoGitEnabled(item);
56043
+ if (context.isPreview) {
56044
+ return item.rawValue ? "3" : "S:3";
56045
+ }
56046
+ if (!isInsideGitWorkTree(context)) {
56047
+ return hideNoGit ? null : "(no git)";
56048
+ }
56049
+ const count = getGitFileStatusCounts(context).staged;
56050
+ return item.rawValue ? count.toString() : `S:${count}`;
56051
+ }
56052
+ getCustomKeybinds() {
56053
+ return getHideNoGitKeybinds();
56054
+ }
56055
+ getNumericValue(context, _item) {
56056
+ if (!isInsideGitWorkTree(context))
56057
+ return null;
56058
+ return getGitFileStatusCounts(context).staged;
56059
+ }
56060
+ supportsRawValue() {
56061
+ return true;
56062
+ }
56063
+ supportsColors(_item) {
56064
+ return true;
56065
+ }
56066
+ }
55976
56067
  // src/widgets/GitUnstaged.ts
55977
56068
  var DEFAULT_SYMBOL2 = "*";
55978
56069
 
@@ -56032,6 +56123,55 @@ class GitUnstagedWidget {
56032
56123
  return true;
56033
56124
  }
56034
56125
  }
56126
+ // src/widgets/GitUnstagedFiles.ts
56127
+ class GitUnstagedFilesWidget {
56128
+ getDefaultColor() {
56129
+ return "yellow";
56130
+ }
56131
+ getDescription() {
56132
+ return "Shows count of unstaged tracked files";
56133
+ }
56134
+ getDisplayName() {
56135
+ return "Git Unstaged Files";
56136
+ }
56137
+ getCategory() {
56138
+ return "Git";
56139
+ }
56140
+ getEditorDisplay(item) {
56141
+ return {
56142
+ displayText: this.getDisplayName(),
56143
+ modifierText: getHideNoGitModifierText(item)
56144
+ };
56145
+ }
56146
+ handleEditorAction(action, item) {
56147
+ return handleToggleNoGitAction(action, item);
56148
+ }
56149
+ render(item, context, _settings) {
56150
+ const hideNoGit = isHideNoGitEnabled(item);
56151
+ if (context.isPreview) {
56152
+ return item.rawValue ? "2" : "M:2";
56153
+ }
56154
+ if (!isInsideGitWorkTree(context)) {
56155
+ return hideNoGit ? null : "(no git)";
56156
+ }
56157
+ const count = getGitFileStatusCounts(context).unstaged;
56158
+ return item.rawValue ? count.toString() : `M:${count}`;
56159
+ }
56160
+ getCustomKeybinds() {
56161
+ return getHideNoGitKeybinds();
56162
+ }
56163
+ getNumericValue(context, _item) {
56164
+ if (!isInsideGitWorkTree(context))
56165
+ return null;
56166
+ return getGitFileStatusCounts(context).unstaged;
56167
+ }
56168
+ supportsRawValue() {
56169
+ return true;
56170
+ }
56171
+ supportsColors(_item) {
56172
+ return true;
56173
+ }
56174
+ }
56035
56175
  // src/widgets/GitUntracked.ts
56036
56176
  var DEFAULT_SYMBOL3 = "?";
56037
56177
 
@@ -56091,6 +56231,103 @@ class GitUntrackedWidget {
56091
56231
  return true;
56092
56232
  }
56093
56233
  }
56234
+ // src/widgets/GitUntrackedFiles.ts
56235
+ class GitUntrackedFilesWidget {
56236
+ getDefaultColor() {
56237
+ return "red";
56238
+ }
56239
+ getDescription() {
56240
+ return "Shows count of untracked files";
56241
+ }
56242
+ getDisplayName() {
56243
+ return "Git Untracked Files";
56244
+ }
56245
+ getCategory() {
56246
+ return "Git";
56247
+ }
56248
+ getEditorDisplay(item) {
56249
+ return {
56250
+ displayText: this.getDisplayName(),
56251
+ modifierText: getHideNoGitModifierText(item)
56252
+ };
56253
+ }
56254
+ handleEditorAction(action, item) {
56255
+ return handleToggleNoGitAction(action, item);
56256
+ }
56257
+ render(item, context, _settings) {
56258
+ const hideNoGit = isHideNoGitEnabled(item);
56259
+ if (context.isPreview) {
56260
+ return item.rawValue ? "1" : "?:1";
56261
+ }
56262
+ if (!isInsideGitWorkTree(context)) {
56263
+ return hideNoGit ? null : "(no git)";
56264
+ }
56265
+ const count = getGitFileStatusCounts(context).untracked;
56266
+ return item.rawValue ? count.toString() : `?:${count}`;
56267
+ }
56268
+ getCustomKeybinds() {
56269
+ return getHideNoGitKeybinds();
56270
+ }
56271
+ getNumericValue(context, _item) {
56272
+ if (!isInsideGitWorkTree(context))
56273
+ return null;
56274
+ return getGitFileStatusCounts(context).untracked;
56275
+ }
56276
+ supportsRawValue() {
56277
+ return true;
56278
+ }
56279
+ supportsColors(_item) {
56280
+ return true;
56281
+ }
56282
+ }
56283
+ // src/widgets/GitCleanStatus.ts
56284
+ class GitCleanStatusWidget {
56285
+ getDefaultColor() {
56286
+ return "green";
56287
+ }
56288
+ getDescription() {
56289
+ return "Shows ✓ when the working tree is clean and ✗ when it is dirty";
56290
+ }
56291
+ getDisplayName() {
56292
+ return "Git Clean Status";
56293
+ }
56294
+ getCategory() {
56295
+ return "Git";
56296
+ }
56297
+ getEditorDisplay(item) {
56298
+ return {
56299
+ displayText: this.getDisplayName(),
56300
+ modifierText: getHideNoGitModifierText(item)
56301
+ };
56302
+ }
56303
+ handleEditorAction(action, item) {
56304
+ return handleToggleNoGitAction(action, item);
56305
+ }
56306
+ render(item, context, _settings) {
56307
+ const hideNoGit = isHideNoGitEnabled(item);
56308
+ if (context.isPreview) {
56309
+ return item.rawValue ? "clean" : "✓";
56310
+ }
56311
+ if (!isInsideGitWorkTree(context)) {
56312
+ return hideNoGit ? null : "(no git)";
56313
+ }
56314
+ const status = getGitStatus(context);
56315
+ const clean = !status.staged && !status.unstaged && !status.untracked && !status.conflicts;
56316
+ if (item.rawValue) {
56317
+ return clean ? "clean" : "dirty";
56318
+ }
56319
+ return clean ? "✓" : "✗";
56320
+ }
56321
+ getCustomKeybinds() {
56322
+ return getHideNoGitKeybinds();
56323
+ }
56324
+ supportsRawValue() {
56325
+ return true;
56326
+ }
56327
+ supportsColors(_item) {
56328
+ return true;
56329
+ }
56330
+ }
56094
56331
  // src/widgets/GitAheadBehind.ts
56095
56332
  class GitAheadBehindWidget {
56096
56333
  getDefaultColor() {
@@ -58218,7 +58455,6 @@ var WIDGET_MANIFEST = [
58218
58455
  { type: "context-length", create: () => new ContextLengthWidget },
58219
58456
  { type: "context-window", create: () => new ContextWindowWidget },
58220
58457
  { type: "context-percentage", create: () => new ContextPercentageWidget },
58221
- { type: "context-percentage-usable", create: () => new ContextPercentageUsableWidget },
58222
58458
  { type: "context-bar", create: () => new ContextBarWidget },
58223
58459
  { type: "session-clock", create: () => new SessionClockWidget },
58224
58460
  { type: "premium-requests", create: () => new PremiumRequestsWidget },
@@ -58237,8 +58473,12 @@ var WIDGET_MANIFEST = [
58237
58473
  { type: "git-pr", create: () => new GitPrWidget },
58238
58474
  { type: "git-status", create: () => new GitStatusWidget },
58239
58475
  { type: "git-staged", create: () => new GitStagedWidget },
58476
+ { type: "git-staged-files", create: () => new GitStagedFilesWidget },
58240
58477
  { type: "git-unstaged", create: () => new GitUnstagedWidget },
58478
+ { type: "git-unstaged-files", create: () => new GitUnstagedFilesWidget },
58241
58479
  { type: "git-untracked", create: () => new GitUntrackedWidget },
58480
+ { type: "git-untracked-files", create: () => new GitUntrackedFilesWidget },
58481
+ { type: "git-clean-status", create: () => new GitCleanStatusWidget },
58242
58482
  { type: "git-ahead-behind", create: () => new GitAheadBehindWidget },
58243
58483
  { type: "git-conflicts", create: () => new GitConflictsWidget },
58244
58484
  { type: "git-sha", create: () => new GitShaWidget },
@@ -62107,8 +62347,8 @@ function advanceGlobalPowerlineThemeIndex(currentIndex, entries) {
62107
62347
  // src/utils/context-percentage.ts
62108
62348
  function calculateContextPercentage(context) {
62109
62349
  const contextWindowMetrics = getContextWindowMetrics(context.data);
62110
- if (contextWindowMetrics.usedPercentage !== null) {
62111
- return contextWindowMetrics.usedPercentage;
62350
+ if (contextWindowMetrics.currentContextUsedPercentage !== null) {
62351
+ return contextWindowMetrics.currentContextUsedPercentage;
62112
62352
  }
62113
62353
  return 0;
62114
62354
  }
@@ -62509,17 +62749,18 @@ function renderStatusLine(widgets, settings, context, preRenderedWidgets, preCal
62509
62749
  if (!widget)
62510
62750
  continue;
62511
62751
  if (widget.type === "separator") {
62512
- let hasContentBefore = false;
62752
+ let previousWidgetRenderedContent = false;
62513
62753
  for (let j = i - 1;j >= 0; j--) {
62514
62754
  const prevWidget = widgets[j];
62515
- if (prevWidget && prevWidget.type !== "separator" && prevWidget.type !== "flex-separator") {
62516
- if (preRenderedWidgets[j]?.content) {
62517
- hasContentBefore = true;
62518
- break;
62519
- }
62755
+ if (prevWidget?.type === "separator" || prevWidget?.type === "flex-separator") {
62756
+ continue;
62757
+ }
62758
+ if (prevWidget) {
62759
+ previousWidgetRenderedContent = Boolean(preRenderedWidgets[j]?.content);
62760
+ break;
62520
62761
  }
62521
62762
  }
62522
- if (!hasContentBefore)
62763
+ if (!previousWidgetRenderedContent)
62523
62764
  continue;
62524
62765
  const sepChar = widget.character ?? (settings.defaultSeparator ?? "|");
62525
62766
  const formattedSep = formatSeparator(sepChar);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "copilot-statusline",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
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",