pi-bar 0.3.29 → 0.3.30

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
@@ -3,25 +3,25 @@
3
3
  [![npm version](https://img.shields.io/npm/v/pi-bar.svg)](https://www.npmjs.com/package/pi-bar)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/pi-bar.svg)](https://www.npmjs.com/package/pi-bar)
5
5
 
6
- **Never accidentally run Opus on a typo again.** pi-bar keeps your model, thinking level, context pressure, extension status, and a live one-line TLDR visible in pi's footer.
6
+ **Never accidentally run Opus on a typo again.** pi-bar keeps your model, thinking level, context pressure, extension status, and a live one-line progress update visible in pi's footer.
7
7
 
8
8
  ```text
9
9
  claude-opus-4.7 ❯ think:med ❯ 2.6% / 1.0M ❯ Inspecting package structure ❯ plan:active
10
10
  ```
11
11
 
12
- ![pi-bar with low context usage](https://cdn.jsdelivr.net/npm/pi-bar@0.3.29/assets/screenshot-green.png)
13
- ![pi-bar with medium context usage](https://cdn.jsdelivr.net/npm/pi-bar@0.3.29/assets/screenshot-yellow.png)
14
- ![pi-bar with high context usage](https://cdn.jsdelivr.net/npm/pi-bar@0.3.29/assets/screenshot-red.png)
12
+ ![pi-bar with low context usage](https://cdn.jsdelivr.net/npm/pi-bar@0.3.30/assets/screenshot-green.png)
13
+ ![pi-bar with medium context usage](https://cdn.jsdelivr.net/npm/pi-bar@0.3.30/assets/screenshot-yellow.png)
14
+ ![pi-bar with high context usage](https://cdn.jsdelivr.net/npm/pi-bar@0.3.30/assets/screenshot-red.png)
15
15
 
16
16
  ## Why use it?
17
17
 
18
18
  - **See the active model at a glance** — catch accidental model switches before an expensive or sensitive task starts.
19
19
  - **Track thinking level in place** — immediately notice when you are using the wrong reasoning setting.
20
20
  - **Watch context pressure early** — context usage turns green, yellow, then red as you approach the limit.
21
- - **Follow what pi is doing** — a live TLDR keeps the current task visible without scrolling.
21
+ - **Follow what pi is doing** — a live progress update keeps the current task visible without scrolling.
22
22
  - **Keep extension statuses visible** — statuses set with `ctx.ui.setStatus()` still appear in the custom footer.
23
23
 
24
- pi-bar is intentionally tiny: one small extension, no broad behavior changes, and no commands to learn. It replaces pi's built-in footer with a compact model / thinking / context / TLDR / extension-status line.
24
+ pi-bar is intentionally tiny: one small extension, no broad behavior changes, and no commands to learn. It replaces pi's built-in footer with a compact model / thinking / context / progress / extension-status line.
25
25
 
26
26
  ## Install
27
27
 
@@ -42,23 +42,25 @@ pi-bar works out of the box, but you can tune it with environment variables befo
42
42
  ### Show or hide segments
43
43
 
44
44
  ```bash
45
- PI_BAR_SHOW=model,thinking,context,tldr,extensions pi
45
+ PI_BAR_SHOW=model,thinking,context,progress,extensions pi
46
46
  PI_BAR_SHOW=model,context pi
47
47
  ```
48
48
 
49
- Allowed segments are `model`, `thinking`, `context`, `tldr`, and `extensions`. The `tldr` segment is hidden until pi-bar generates a current TLDR. The `extensions` segment is hidden when no extension has set a status.
49
+ Allowed segments are `model`, `thinking`, `context`, `progress`, and `extensions`. The `progress` segment is hidden until pi-bar generates a current update. The `extensions` segment is hidden when no extension has set a status.
50
50
 
51
- ### Configure live TLDR
51
+ > The legacy `tldr` segment name and `PI_BAR_TLDR_MODEL` / `bar.tldrModel` settings still work and are kept as aliases for existing configs.
52
52
 
53
- pi-bar shows a concise live TLDR of the current task in the footer. It generates TLDRs from recent prompt, assistant, and deterministic code-compressed tool facts (for example `bash ... ok`, `grep ... 4 matches`, `read file`, or `custom_tool key=value ok`). It resets when you navigate the session tree so stale summaries do not follow you across branches. Set `PI_BAR_SHOW` without `tldr` to hide it.
53
+ ### Configure live progress updates
54
54
 
55
- Override the TLDR model with:
55
+ pi-bar shows a concise live progress update of the current task in the footer. It generates updates from recent prompt, assistant, and deterministic code-compressed tool facts (for example `bash ... ok`, `grep ... 4 matches`, `read file`, or `custom_tool key=value ok`). It resets when you navigate the session tree so stale summaries do not follow you across branches. Set `PI_BAR_SHOW` without `progress` to hide it.
56
+
57
+ Override the progress update model with:
56
58
 
57
59
  ```bash
58
- PI_BAR_TLDR_MODEL=anthropic/claude-haiku-4-5 pi
60
+ PI_BAR_PROGRESS_MODEL=anthropic/claude-haiku-4-5 pi
59
61
  ```
60
62
 
61
- You can also set `bar.tldrModel` in pi settings.
63
+ You can also set `bar.progressModel` in pi settings.
62
64
 
63
65
  ### Configure extension statuses
64
66
 
@@ -90,4 +92,4 @@ The first number is the warning/yellow threshold. The second number is the dange
90
92
 
91
93
  Pi extensions run with your local user permissions. Review any pi package source before installing it.
92
94
 
93
- Live TLDR generation sends short activity snippets to the selected TLDR model provider. Disable the `tldr` segment with `PI_BAR_SHOW` if that is unacceptable.
95
+ Live progress update generation sends short activity snippets to the selected provider. Disable the `progress` segment with `PI_BAR_SHOW` if that is unacceptable.
@@ -2,7 +2,7 @@
2
2
  * pi-bar — footer/statusline extension.
3
3
  *
4
4
  * Replaces the built-in footer with left-aligned segments:
5
- * <model name> ❯ think:<level> ❯ <context% / window> ❯ <tldr> ❯ <extension statuses>
5
+ * <model name> ❯ think:<level> ❯ <context% / window> ❯ <progress> ❯ <extension statuses>
6
6
  *
7
7
  * Examples:
8
8
  * claude-opus-4.7 ❯ think:med ❯ 2.6% / 1.0M
@@ -11,7 +11,7 @@
11
11
  * and after each assistant turn so context usage stays current.
12
12
  *
13
13
  * Configuration env vars:
14
- * PI_BAR_SHOW=model,thinking,context,tldr,extensions
14
+ * PI_BAR_SHOW=model,thinking,context,progress,extensions
15
15
  * PI_BAR_THRESHOLDS=70,90
16
16
  */
17
17
 
@@ -33,39 +33,39 @@ import {
33
33
  truncateToWidth,
34
34
  } from "@earendil-works/pi-tui";
35
35
 
36
- type SegmentName = "model" | "thinking" | "context" | "tldr" | "extensions";
36
+ type SegmentName = "model" | "thinking" | "context" | "progress" | "extensions";
37
37
  type StatusFilter =
38
38
  | { mode: "all"; hidden: Set<string> }
39
39
  | { mode: "only"; shown: Set<string> };
40
40
  type SerializedStatusFilter =
41
41
  | { mode: "all"; hidden: string[] }
42
42
  | { mode: "only"; shown: string[] };
43
- type TldrActivityType =
43
+ type ProgressActivityType =
44
44
  | "user_message"
45
45
  | "assistant_update"
46
46
  | "tool_call"
47
47
  | "tool_result"
48
48
  | "assistant_final"
49
49
  | "assistant_failure";
50
- type TldrDisplayPriority = "immediate" | "normal" | "final";
51
- type TldrActivity = {
50
+ type ProgressDisplayPriority = "immediate" | "normal" | "final";
51
+ type ProgressActivity = {
52
52
  index: number;
53
- activityType: TldrActivityType;
54
- displayPriority: TldrDisplayPriority;
53
+ activityType: ProgressActivityType;
54
+ displayPriority: ProgressDisplayPriority;
55
55
  text: string;
56
56
  toolCallId?: string;
57
57
  };
58
- type TldrCheckpoint = {
58
+ type ProgressCheckpoint = {
59
59
  activityIndex: number;
60
- displayPriority: TldrDisplayPriority;
60
+ displayPriority: ProgressDisplayPriority;
61
61
  text: string;
62
62
  };
63
- type TldrCheckpointJob = {
63
+ type ProgressCheckpointJob = {
64
64
  activityIndex: number;
65
- displayPriority: TldrDisplayPriority;
65
+ displayPriority: ProgressDisplayPriority;
66
66
  runId: number;
67
67
  };
68
- type TldrModelPreference = { provider: string; id: string };
68
+ type ProgressModelPreference = { provider: string; id: string };
69
69
  type FastModelAuth = {
70
70
  model: Parameters<typeof complete>[0];
71
71
  apiKey: string;
@@ -73,7 +73,10 @@ type FastModelAuth = {
73
73
  };
74
74
 
75
75
  const STATUS_FILTER_ENTRY_TYPE = "pi-bar-status-filter";
76
- const SETTINGS_TLDR_KEY = "tldr";
76
+ const SETTINGS_PROGRESS_KEY = "progress";
77
+ // Legacy settings/env names. The `tldr` keys remain accepted so existing user
78
+ // configs continue to work after the rename to "progress updates".
79
+ const LEGACY_SETTINGS_TLDR_KEY = "tldr";
77
80
  const SETTINGS_BAR_KEY = "bar";
78
81
  const MAX_ACTIVITY_TEXT_CHARS = 800;
79
82
  const MAX_USER_TEXT_CHARS = 700;
@@ -84,10 +87,10 @@ const MAX_FINAL_TEXT_CHARS = 700;
84
87
  const MAX_RETAINED_RAW_ACTIVITIES = 128;
85
88
  const ANSI_PATTERN = /\x1B\[[0-?]*[ -/]*[@-~]/g;
86
89
  const MAX_CONTEXT_CHECKPOINTS = 8;
87
- const TLDR_MAX_TOKENS = 120;
88
- const TLDR_REQUEST_TIMEOUT_MS = 2_000;
89
- const TLDR_DISPLAY_UPDATE_INTERVAL_MS = 1_200;
90
- const TLDR_TARGET_SUMMARY_CHARS = 60;
90
+ const PROGRESS_MAX_TOKENS = 120;
91
+ const PROGRESS_REQUEST_TIMEOUT_MS = 2_000;
92
+ const PROGRESS_DISPLAY_UPDATE_INTERVAL_MS = 1_200;
93
+ const PROGRESS_TARGET_SUMMARY_CHARS = 60;
91
94
  // Burst debouncing on the generation side: coalesce rapid-fire activities into
92
95
  // one model call. Quiet window catches short bursts; max wait surfaces progress
93
96
  // during continuous activity.
@@ -97,7 +100,7 @@ const NORMAL_CHECKPOINT_QUIET_MS = 1_500;
97
100
  const NORMAL_CHECKPOINT_MAX_WAIT_MS = 2_500;
98
101
  // Defensive ceiling: even if the model ignores the length instruction, never
99
102
  // blast a large payload into the footer.
100
- const MAX_SAFE_TLDR_CHARS = 240;
103
+ const MAX_SAFE_PROGRESS_CHARS = 240;
101
104
  const CONFIG_PATH =
102
105
  process.env.PI_BAR_CONFIG ?? join(homedir(), ".pi", "agent", "pi-bar.json");
103
106
 
@@ -105,7 +108,7 @@ const DEFAULT_SEGMENTS: SegmentName[] = [
105
108
  "model",
106
109
  "thinking",
107
110
  "context",
108
- "tldr",
111
+ "progress",
109
112
  "extensions",
110
113
  ];
111
114
  const DEFAULT_WARNING_THRESHOLD = 70;
@@ -168,11 +171,14 @@ function parseSegments(): SegmentName[] {
168
171
  const raw = process.env.PI_BAR_SHOW;
169
172
  if (!raw) return DEFAULT_SEGMENTS;
170
173
 
174
+ // `tldr` is the legacy segment name, kept working as an alias so existing
175
+ // PI_BAR_SHOW configs do not break after the rename to "progress".
171
176
  const requested = raw
172
177
  .split(",")
173
178
  .map((segment) => segment.trim().toLowerCase())
179
+ .map((segment) => (segment === "tldr" ? "progress" : segment))
174
180
  .filter((segment): segment is SegmentName =>
175
- ["model", "thinking", "context", "tldr", "extensions"].includes(segment),
181
+ ["model", "thinking", "context", "progress", "extensions"].includes(segment),
176
182
  );
177
183
 
178
184
  return requested.length > 0 ? requested : DEFAULT_SEGMENTS;
@@ -206,7 +212,7 @@ function parseThresholds(): { warningThreshold: number; errorThreshold: number }
206
212
  };
207
213
  }
208
214
 
209
- function parseTldrModelSpec(value: string): TldrModelPreference | undefined {
215
+ function parseProgressModelSpec(value: string): ProgressModelPreference | undefined {
210
216
  const trimmed = value.trim();
211
217
  if (!trimmed || trimmed === "auto") return undefined;
212
218
  const separator = trimmed.indexOf("/");
@@ -217,51 +223,64 @@ function parseTldrModelSpec(value: string): TldrModelPreference | undefined {
217
223
  function settingsModelValue(settings: Record<string, unknown>): string | undefined {
218
224
  const bar = settings[SETTINGS_BAR_KEY];
219
225
  if (bar && typeof bar === "object" && !Array.isArray(bar)) {
220
- const value = (bar as Record<string, unknown>).tldrModel;
226
+ const record = bar as Record<string, unknown>;
227
+ // Canonical key after rename. Falls through to legacy `tldrModel` so old
228
+ // settings still work without manual migration.
229
+ if (typeof record.progressModel === "string") return record.progressModel;
230
+ if (typeof record.tldrModel === "string") return record.tldrModel;
231
+ }
232
+
233
+ const progressSection = settings[SETTINGS_PROGRESS_KEY];
234
+ if (progressSection && typeof progressSection === "object" && !Array.isArray(progressSection)) {
235
+ const value = (progressSection as Record<string, unknown>).model;
221
236
  if (typeof value === "string") return value;
222
237
  }
223
238
 
224
- const tldr = settings[SETTINGS_TLDR_KEY];
225
- if (tldr && typeof tldr === "object" && !Array.isArray(tldr)) {
226
- const value = (tldr as Record<string, unknown>).model;
239
+ const legacy = settings[LEGACY_SETTINGS_TLDR_KEY];
240
+ if (legacy && typeof legacy === "object" && !Array.isArray(legacy)) {
241
+ const value = (legacy as Record<string, unknown>).model;
227
242
  if (typeof value === "string") return value;
228
243
  }
229
244
 
230
245
  return undefined;
231
246
  }
232
247
 
233
- function resolveTldrModelPreference(cwd: string): TldrModelPreference | undefined {
234
- const envModel = process.env.PI_BAR_TLDR_MODEL;
235
- if (envModel) return parseTldrModelSpec(envModel);
248
+ function resolveProgressModelPreference(cwd: string): ProgressModelPreference | undefined {
249
+ // PI_BAR_PROGRESS_MODEL is the canonical env var. PI_BAR_TLDR_MODEL is
250
+ // honored as a backwards-compat alias for users who set it before the
251
+ // rename.
252
+ const envModel = process.env.PI_BAR_PROGRESS_MODEL ?? process.env.PI_BAR_TLDR_MODEL;
253
+ if (envModel) return parseProgressModelSpec(envModel);
236
254
 
237
255
  const settings = SettingsManager.create(cwd);
238
256
  const projectModel = settingsModelValue(
239
257
  settings.getProjectSettings() as Record<string, unknown>,
240
258
  );
241
- if (projectModel !== undefined) return parseTldrModelSpec(projectModel);
259
+ if (projectModel !== undefined) return parseProgressModelSpec(projectModel);
242
260
 
243
261
  const globalModel = settingsModelValue(
244
262
  settings.getGlobalSettings() as Record<string, unknown>,
245
263
  );
246
- return globalModel ? parseTldrModelSpec(globalModel) : undefined;
264
+ return globalModel ? parseProgressModelSpec(globalModel) : undefined;
247
265
  }
248
266
 
249
- // Fallback order optimizes for low latency + low cost first. The TLDR path does
250
- // not need reasoning quality; codex/gpt-5.4-mini is fastest in practice.
251
- const FAST_TLDR_MODELS: readonly TldrModelPreference[] = [
267
+ // Fallback order optimizes for low latency + low cost first. The progress
268
+ // update path does not need reasoning quality; codex/gpt-5.4-mini is fastest
269
+ // in practice.
270
+ const FAST_PROGRESS_MODELS: readonly ProgressModelPreference[] = [
252
271
  { provider: "openai-codex", id: "gpt-5.4-mini" },
253
272
  { provider: "openai-codex", id: "gpt-5.3-codex-spark" },
254
273
  { provider: "anthropic", id: "claude-haiku-4-5" },
255
274
  { provider: "anthropic", id: "claude-haiku-4-5-20251001" },
256
275
  ];
257
276
 
258
- function formatTldrModelKey(model: TldrModelPreference): string {
277
+ function formatProgressModelKey(model: ProgressModelPreference): string {
259
278
  return `${model.provider}/${model.id}`;
260
279
  }
261
280
 
262
281
  async function getModelAuth(
263
282
  ctx: ExtensionContext,
264
- preference: TldrModelPreference,
283
+ preference: ProgressModelPreference,
265
284
  ): Promise<FastModelAuth | undefined> {
266
285
  const model = ctx.modelRegistry.find(preference.provider, preference.id);
267
286
  if (!model) return undefined;
@@ -272,17 +291,17 @@ async function getModelAuth(
272
291
  : undefined;
273
292
  }
274
293
 
275
- async function getFastTldrModelAuth(
294
+ async function getFastProgressModelAuth(
276
295
  ctx: ExtensionContext,
277
- configuredModel?: TldrModelPreference,
296
+ configuredModel?: ProgressModelPreference,
278
297
  ): Promise<FastModelAuth | undefined> {
279
298
  if (configuredModel) {
280
299
  const configuredAuth = await getModelAuth(ctx, configuredModel);
281
300
  if (configuredAuth) return configuredAuth;
282
301
  }
283
302
 
284
- for (const candidate of FAST_TLDR_MODELS) {
285
- if (configuredModel && formatTldrModelKey(candidate) === formatTldrModelKey(configuredModel)) {
303
+ for (const candidate of FAST_PROGRESS_MODELS) {
304
+ if (configuredModel && formatProgressModelKey(candidate) === formatProgressModelKey(configuredModel)) {
286
305
  continue;
287
306
  }
288
307
  const auth = await getModelAuth(ctx, candidate);
@@ -450,16 +469,16 @@ function extractTextContent(content: readonly unknown[] | undefined): string | u
450
469
  return compacted.length > 0 ? compacted : undefined;
451
470
  }
452
471
 
453
- class TldrFactCollector {
472
+ class ProgressFactCollector {
454
473
  private nextIndex = 1;
455
- private readonly activities: TldrActivity[] = [];
474
+ private readonly activities: ProgressActivity[] = [];
456
475
 
457
476
  resetConversation(): void {
458
477
  this.nextIndex = 1;
459
478
  this.activities.splice(0);
460
479
  }
461
480
 
462
- recordUserMessage(prompt: string): TldrActivity {
481
+ recordUserMessage(prompt: string): ProgressActivity {
463
482
  return this.addActivity(
464
483
  "user_message",
465
484
  "immediate",
@@ -467,7 +486,7 @@ class TldrFactCollector {
467
486
  );
468
487
  }
469
488
 
470
- recordAssistantUpdate(message: unknown): TldrActivity | undefined {
489
+ recordAssistantUpdate(message: unknown): ProgressActivity | undefined {
471
490
  if (!message || typeof message !== "object") return undefined;
472
491
  const record = message as Record<string, unknown>;
473
492
  if (record.role !== "assistant") return undefined;
@@ -492,7 +511,7 @@ class TldrFactCollector {
492
511
  toolName: string;
493
512
  input?: Record<string, unknown>;
494
513
  toolCallId?: string;
495
- }): TldrActivity {
514
+ }): ProgressActivity {
496
515
  return this.addActivity(
497
516
  "tool_call",
498
517
  "normal",
@@ -508,7 +527,7 @@ class TldrFactCollector {
508
527
  content?: readonly unknown[];
509
528
  details?: unknown;
510
529
  toolCallId?: string;
511
- }): TldrActivity {
530
+ }): ProgressActivity {
512
531
  const isError = Boolean(event.isError);
513
532
  const resultText = summarizeToolResult(
514
533
  event.toolName,
@@ -540,7 +559,7 @@ class TldrFactCollector {
540
559
  );
541
560
  }
542
561
 
543
- recordMessageEnd(message: unknown): TldrActivity | "emptyFinalStop" | "ignored" {
562
+ recordMessageEnd(message: unknown): ProgressActivity | "emptyFinalStop" | "ignored" {
544
563
  if (!message || typeof message !== "object") return "ignored";
545
564
  const record = message as Record<string, unknown>;
546
565
  if (record.role !== "assistant") return "ignored";
@@ -569,7 +588,7 @@ class TldrFactCollector {
569
588
  : "ignored";
570
589
  }
571
590
 
572
- activitiesAfter(previousIndex: number, throughIndex: number): readonly TldrActivity[] {
591
+ activitiesAfter(previousIndex: number, throughIndex: number): readonly ProgressActivity[] {
573
592
  return this.activities.filter(
574
593
  (activity) => activity.index > previousIndex && activity.index <= throughIndex,
575
594
  );
@@ -591,11 +610,11 @@ class TldrFactCollector {
591
610
  }
592
611
 
593
612
  private addActivity(
594
- activityType: TldrActivityType,
595
- displayPriority: TldrDisplayPriority,
613
+ activityType: ProgressActivityType,
614
+ displayPriority: ProgressDisplayPriority,
596
615
  text: string,
597
616
  toolCallId?: string,
598
- ): TldrActivity {
617
+ ): ProgressActivity {
599
618
  const activity = {
600
619
  index: this.nextIndex,
601
620
  activityType,
@@ -612,25 +631,25 @@ class TldrFactCollector {
612
631
  }
613
632
  }
614
633
 
615
- class FooterTldrEngine {
616
- private readonly facts = new TldrFactCollector();
617
- private configuredModel?: TldrModelPreference;
634
+ class FooterProgressEngine {
635
+ private readonly facts = new ProgressFactCollector();
636
+ private configuredModel?: ProgressModelPreference;
618
637
  private runId = 0;
619
638
  private latestAcceptedActivityIndex = 0;
620
639
  private lastRenderedActivityIndex = 0;
621
640
  private lastRenderedText = "";
622
641
  private lastDisplayAt = Number.NEGATIVE_INFINITY;
623
- private readonly acceptedCheckpoints: TldrCheckpoint[] = [];
624
- private checkpointQueue: TldrCheckpointJob[] = [];
625
- private inFlightCheckpoint?: TldrCheckpointJob;
626
- private pendingDisplayCheckpoint?: TldrCheckpoint;
642
+ private readonly acceptedCheckpoints: ProgressCheckpoint[] = [];
643
+ private checkpointQueue: ProgressCheckpointJob[] = [];
644
+ private inFlightCheckpoint?: ProgressCheckpointJob;
645
+ private pendingDisplayCheckpoint?: ProgressCheckpoint;
627
646
  private displayTimer?: ReturnType<typeof setTimeout>;
628
647
  private abortController?: AbortController;
629
648
  private currentText: string | null = null;
630
649
  // Normal-priority bursts wait for a quiet window before triggering a model
631
650
  // call. Pi often emits many rapid activities (e.g. streaming reads); without
632
- // debouncing we would hammer the TLDR model for intermediate states.
633
- private pendingNormalCheckpoint?: TldrCheckpointJob;
651
+ // debouncing we would hammer the progress update model for intermediate states.
652
+ private pendingNormalCheckpoint?: ProgressCheckpointJob;
634
653
  private normalCheckpointTimer?: ReturnType<typeof setTimeout>;
635
654
  private normalCheckpointBurstStartedAt?: number;
636
655
 
@@ -641,7 +660,7 @@ class FooterTldrEngine {
641
660
  }
642
661
 
643
662
  startSession(cwd: string): void {
644
- this.configuredModel = resolveTldrModelPreference(cwd);
663
+ this.configuredModel = resolveProgressModelPreference(cwd);
645
664
  this.facts.resetConversation();
646
665
  this.startFreshRun();
647
666
  }
@@ -652,7 +671,7 @@ class FooterTldrEngine {
652
671
  }
653
672
 
654
673
  recordUserMessage(ctx: ExtensionContext, prompt: string): void {
655
- // A new user turn replaces the entire visible TLDR context, so wipe both
674
+ // A new user turn replaces the entire visible progress update context, so wipe both
656
675
  // the rendered text and the prior-turn checkpoint context that feeds the
657
676
  // model. Carrying old checkpoints biased the next turn's phrasing toward
658
677
  // the previous task.
@@ -766,7 +785,7 @@ class FooterTldrEngine {
766
785
 
767
786
  private scheduleNormalCheckpoint(
768
787
  ctx: ExtensionContext,
769
- job: TldrCheckpointJob,
788
+ job: ProgressCheckpointJob,
770
789
  ): void {
771
790
  this.pendingNormalCheckpoint = job;
772
791
  this.normalCheckpointBurstStartedAt ??= performance.now();
@@ -804,7 +823,7 @@ class FooterTldrEngine {
804
823
  this.pump(ctx);
805
824
  }
806
825
 
807
- private enqueue(ctx: ExtensionContext, activity: TldrActivity): void {
826
+ private enqueue(ctx: ExtensionContext, activity: ProgressActivity): void {
808
827
  if (!ctx.hasUI) return;
809
828
  const job = {
810
829
  activityIndex: activity.index,
@@ -868,19 +887,19 @@ class FooterTldrEngine {
868
887
  void this.runCheckpointRequest(ctx, job);
869
888
  }
870
889
 
871
- private isCurrentJob(job: TldrCheckpointJob): boolean {
890
+ private isCurrentJob(job: ProgressCheckpointJob): boolean {
872
891
  return job.runId === this.runId && this.inFlightCheckpoint === job;
873
892
  }
874
893
 
875
894
  private async runCheckpointRequest(
876
895
  ctx: ExtensionContext,
877
- job: TldrCheckpointJob,
896
+ job: ProgressCheckpointJob,
878
897
  ): Promise<void> {
879
898
  let abortController: AbortController | undefined;
880
899
  try {
881
900
  const prompt = this.checkpointPrompt(job);
882
901
  if (!prompt) return;
883
- const auth = await getFastTldrModelAuth(ctx, this.configuredModel);
902
+ const auth = await getFastProgressModelAuth(ctx, this.configuredModel);
884
903
  if (!this.isCurrentJob(job) || !auth) return;
885
904
 
886
905
  abortController = new AbortController();
@@ -891,17 +910,17 @@ class FooterTldrEngine {
891
910
  {
892
911
  apiKey: auth.apiKey,
893
912
  headers: auth.headers,
894
- maxTokens: TLDR_MAX_TOKENS,
913
+ maxTokens: PROGRESS_MAX_TOKENS,
895
914
  maxRetries: 0,
896
915
  cacheRetention: "none",
897
- timeoutMs: TLDR_REQUEST_TIMEOUT_MS,
916
+ timeoutMs: PROGRESS_REQUEST_TIMEOUT_MS,
898
917
  signal: abortController.signal,
899
918
  },
900
919
  );
901
920
  if (!this.isCurrentJob(job) || response.stopReason !== "stop") return;
902
921
 
903
922
  const rawText = extractTextContent(response.content) ?? "";
904
- const text = sanitizeTldrText(rawText);
923
+ const text = sanitizeProgressText(rawText);
905
924
  if (!text) return;
906
925
  const checkpoint = {
907
926
  activityIndex: job.activityIndex,
@@ -921,7 +940,7 @@ class FooterTldrEngine {
921
940
  }
922
941
  }
923
942
 
924
- private acceptCheckpoint(checkpoint: TldrCheckpoint): void {
943
+ private acceptCheckpoint(checkpoint: ProgressCheckpoint): void {
925
944
  this.acceptedCheckpoints.push(checkpoint);
926
945
  if (this.acceptedCheckpoints.length > MAX_CONTEXT_CHECKPOINTS) {
927
946
  this.acceptedCheckpoints.splice(
@@ -935,7 +954,7 @@ class FooterTldrEngine {
935
954
 
936
955
  private considerDisplayingCheckpoint(
937
956
  ctx: ExtensionContext,
938
- checkpoint: TldrCheckpoint,
957
+ checkpoint: ProgressCheckpoint,
939
958
  ): void {
940
959
  if (checkpoint.activityIndex <= this.lastRenderedActivityIndex) return;
941
960
  if (checkpoint.activityIndex !== this.facts.latestActivityIndex()) return;
@@ -947,7 +966,7 @@ class FooterTldrEngine {
947
966
  }
948
967
 
949
968
  const elapsedMs = performance.now() - this.lastDisplayAt;
950
- if (elapsedMs >= TLDR_DISPLAY_UPDATE_INTERVAL_MS) {
969
+ if (elapsedMs >= PROGRESS_DISPLAY_UPDATE_INTERVAL_MS) {
951
970
  this.clearPendingDisplay();
952
971
  this.renderCheckpoint(checkpoint);
953
972
  return;
@@ -961,16 +980,16 @@ class FooterTldrEngine {
961
980
  this.pendingDisplayCheckpoint = undefined;
962
981
  if (!pending || pending.activityIndex !== this.facts.latestActivityIndex()) return;
963
982
  this.renderCheckpoint(pending);
964
- }, TLDR_DISPLAY_UPDATE_INTERVAL_MS - elapsedMs);
983
+ }, PROGRESS_DISPLAY_UPDATE_INTERVAL_MS - elapsedMs);
965
984
  }
966
985
 
967
- private renderCheckpoint(checkpoint: TldrCheckpoint): void {
986
+ private renderCheckpoint(checkpoint: ProgressCheckpoint): void {
968
987
  if (checkpoint.activityIndex <= this.lastRenderedActivityIndex) return;
969
988
  if (checkpoint.text === this.lastRenderedText) return;
970
989
  // Skip near-duplicates: a burst of consecutive checkpoints often produces
971
990
  // the same action fragment with only punctuation/casing differences. They
972
991
  // flash visibly in the footer without adding information.
973
- if (isNearDuplicateTldr(checkpoint.text, this.lastRenderedText)) {
992
+ if (isNearDuplicateProgress(checkpoint.text, this.lastRenderedText)) {
974
993
  this.lastRenderedActivityIndex = checkpoint.activityIndex;
975
994
  return;
976
995
  }
@@ -981,7 +1000,7 @@ class FooterTldrEngine {
981
1000
  this.requestRender();
982
1001
  }
983
1002
 
984
- private checkpointPrompt(job: TldrCheckpointJob): UserMessage | undefined {
1003
+ private checkpointPrompt(job: ProgressCheckpointJob): UserMessage | undefined {
985
1004
  const rawActivities = this.facts.activitiesAfter(
986
1005
  this.latestAcceptedActivityIndex,
987
1006
  job.activityIndex,
@@ -993,13 +1012,13 @@ class FooterTldrEngine {
993
1012
  {
994
1013
  type: "text",
995
1014
  text: [
996
- "Prior TLDRs (context only, do not copy phrasing):",
1015
+ "Prior progress updates (context only, do not copy phrasing):",
997
1016
  previousCheckpointLines(this.acceptedCheckpoints),
998
1017
  "",
999
1018
  "New activity to summarize:",
1000
1019
  ...rawActivities.map(formatRawActivity),
1001
1020
  "",
1002
- "Write the next TLDR.",
1021
+ "Write the next progress update.",
1003
1022
  ].join("\n"),
1004
1023
  },
1005
1024
  ],
@@ -1090,11 +1109,12 @@ const BANNED_FIRST_WORDS = [
1090
1109
  "Find",
1091
1110
  ] as const;
1092
1111
 
1093
- function checkpointSystemPrompt(job: TldrCheckpointJob): string {
1094
- // Tense + exemplars vary by priority. Final TLDRs describe completed turns;
1095
- // using -ing examples for final caused all final TLDRs to slip back into
1096
- // present-progressive even though the literal instruction said past tense.
1097
- // Immediate TLDRs cover a brand-new user request; they need a rephrase
1112
+ function checkpointSystemPrompt(job: ProgressCheckpointJob): string {
1113
+ // Tense + exemplars vary by priority. Final progress updates describe
1114
+ // completed turns; using -ing examples for final caused all of them to slip
1115
+ // back into present-progressive even though the literal instruction said
1116
+ // past tense.
1117
+ // Immediate progress updates cover a brand-new user request; they need a rephrase
1098
1118
  // directive instead of generic filler like "Continuing with task".
1099
1119
  let tenseInstruction: string;
1100
1120
  let goodExamples: string;
@@ -1103,7 +1123,7 @@ function checkpointSystemPrompt(job: TldrCheckpointJob): string {
1103
1123
  tenseInstruction = "Start with a past-tense verb describing what was completed.";
1104
1124
  goodExamples = [
1105
1125
  "- Updated footer summary behavior",
1106
- "- Investigated live TLDR regressions",
1126
+ "- Investigated live progress regressions",
1107
1127
  "- Refined sanitizer for stray prefixes",
1108
1128
  "- Wrapped up extension release",
1109
1129
  ].join("\n");
@@ -1113,7 +1133,7 @@ function checkpointSystemPrompt(job: TldrCheckpointJob): string {
1113
1133
  "Rephrase the user's new request as a concise present-progressive task clause. If the request is opaque (e.g. \"continue\", \"go\", \"ok\"), name the carry-over task with a noun (e.g. \"Resuming refactor work\"), not generic filler.";
1114
1134
  goodExamples = [
1115
1135
  "- Reviewing footer summary behavior",
1116
- "- Investigating live TLDR regressions",
1136
+ "- Investigating live progress regressions",
1117
1137
  "- Refining sanitizer for stray prefixes",
1118
1138
  "- Preparing extension release",
1119
1139
  "- Resuming refactor work",
@@ -1123,7 +1143,7 @@ function checkpointSystemPrompt(job: TldrCheckpointJob): string {
1123
1143
  tenseInstruction = "Start with a present-tense -ing verb describing current work.";
1124
1144
  goodExamples = [
1125
1145
  "- Reviewing footer summary behavior",
1126
- "- Investigating live TLDR regressions",
1146
+ "- Investigating live progress regressions",
1127
1147
  "- Refining sanitizer for stray prefixes",
1128
1148
  "- Wrapping up extension release",
1129
1149
  ].join("\n");
@@ -1136,7 +1156,7 @@ function checkpointSystemPrompt(job: TldrCheckpointJob): string {
1136
1156
  // suffixes, file path leaks, and markdown formatting. The HARD CONSTRAINTS
1137
1157
  // block sits at the tail of the prompt because instruction-following models
1138
1158
  // give the most weight to the most-recent directives (recency bias).
1139
- return `Write one plain-English TLDR for a Pi coding agent.
1159
+ return `Write one plain-English progress update for a Pi coding agent.
1140
1160
  Describe the work progress as if a human developer were doing it.
1141
1161
  Focus on the task activity and current outcome, not agent mechanics.
1142
1162
  Do not mention tools, tool calls, prompts, messages, model output, or implementation details.
@@ -1145,11 +1165,11 @@ Do not use file paths, file extensions, code identifiers, package names, or vers
1145
1165
  Do not use backticks, asterisks, underscores, quotes, or any markdown formatting.
1146
1166
  Do not append filler suffixes such as "with success", "successfully", or "completed successfully".
1147
1167
  Do not claim progress or completion that is not present in the activity.
1148
- Use the prior TLDRs for context and the new activity for the update.
1168
+ Use the prior progress updates for context and the new activity for the update.
1149
1169
  Summarize the current state of work; do not narrate the history.
1150
1170
  If context is sparse, still summarize the available activity.
1151
1171
  Never ask for more information or say there is not enough context.
1152
- Return one concise status fragment under ${TLDR_TARGET_SUMMARY_CHARS} characters.
1172
+ Return one concise status fragment under ${PROGRESS_TARGET_SUMMARY_CHARS} characters.
1153
1173
  Omit subjects like "the agent" or "it".
1154
1174
  Prefer verb + direct object. Include outcome only if important.
1155
1175
  Do not address the user.
@@ -1163,7 +1183,7 @@ Bad examples:
1163
1183
  - Editing extensions/status-footer.ts with success.
1164
1184
  - Reading status-footer file completed successfully.
1165
1185
  - Publishing \`pi-bar@0.3.3\` to npm.
1166
- - Grepping for sanitizeTldrText callers.
1186
+ - Grepping for sanitizeProgressText callers.
1167
1187
  - Verifying repository status after commit.
1168
1188
  - Investigating user input responses.
1169
1189
 
@@ -1180,7 +1200,7 @@ function extractStopReason(message: unknown): string | undefined {
1180
1200
  return typeof stopReason === "string" ? stopReason : undefined;
1181
1201
  }
1182
1202
 
1183
- // Two TLDRs are near-duplicates if their normalized action fragments (lowercase,
1203
+ // Two progress updates are near-duplicates if their normalized action fragments (lowercase,
1184
1204
  // punctuation/whitespace collapsed) match. Catches bursts of cosmetic variants
1185
1205
  // like "Editing X." vs "Editing X" vs "editing x.".
1186
1206
  function normalizedActionFragment(text: string): string {
@@ -1191,25 +1211,25 @@ function normalizedActionFragment(text: string): string {
1191
1211
  .trim();
1192
1212
  }
1193
1213
 
1194
- function isNearDuplicateTldr(current: string, previous: string): boolean {
1214
+ function isNearDuplicateProgress(current: string, previous: string): boolean {
1195
1215
  if (!previous) return false;
1196
1216
  return normalizedActionFragment(current) === normalizedActionFragment(previous);
1197
1217
  }
1198
1218
 
1199
- function previousCheckpointLines(checkpoints: readonly TldrCheckpoint[]): string {
1219
+ function previousCheckpointLines(checkpoints: readonly ProgressCheckpoint[]): string {
1200
1220
  if (checkpoints.length === 0) return "none";
1201
1221
  return checkpoints
1202
1222
  .slice(-MAX_CONTEXT_CHECKPOINTS)
1203
- .map((checkpoint) => `- ${sanitizeTldrText(checkpoint.text)}`)
1223
+ .map((checkpoint) => `- ${sanitizeProgressText(checkpoint.text)}`)
1204
1224
  .join("\n");
1205
1225
  }
1206
1226
 
1207
- function formatRawActivity(activity: TldrActivity): string {
1227
+ function formatRawActivity(activity: ProgressActivity): string {
1208
1228
  return `- ${activity.text}`;
1209
1229
  }
1210
1230
 
1211
1231
  // Strips ANSI/CSI/OSC/DCS/SOS/PM/APC escape sequences from model output before
1212
- // it lands in the terminal. The TLDR text is model-controlled and renders into
1232
+ // it lands in the terminal. The progress update text is model-controlled and renders into
1213
1233
  // the footer; the system prompt asking for plain text is not a security
1214
1234
  // boundary, so we defensively remove terminal controls here.
1215
1235
  const ESC_BYTE = 0x1b;
@@ -1315,7 +1335,7 @@ function stripTerminalControls(text: string): string {
1315
1335
  // parrot them; the regex is narrow enough not to clip natural starts like
1316
1336
  // "Activity slowed after retry" or "Checkpointing release state".
1317
1337
  const LEAKED_PREFIX_PATTERN =
1318
- /^\s*(?:[-*•]\s*)?(?:(?:through\s+activity|activity|checkpoint)\s+\d+\s*[:.\-—–]\s*|(?:tldr|summary)\s*[:.\-—–]\s*)+/i;
1338
+ /^\s*(?:[-*•]\s*)?(?:(?:through\s+activity|activity|checkpoint)\s+\d+\s*[:.\-—–]\s*|(?:tldr|summary|progress\s+update|progress)\s*[:.\-—–]\s*)+/i;
1319
1339
  const LEADING_PUNCT_PATTERN = /^[\s\-—–•*:#.,;]+/;
1320
1340
  const TRAILING_PUNCT_PATTERN = /[\s\-—–•*:#.,;]+$/;
1321
1341
 
@@ -1369,6 +1389,15 @@ const DANGLING_PREP_CHAIN_PATTERN =
1369
1389
  // "Reviewing for image update". Collapse the bare verb+prep into just the verb.
1370
1390
  const VERB_BARE_PREP_PATTERN =
1371
1391
  /\b(Reviewing|Investigating|Updating|Refining|Exploring|Fixing|Implementing|Bumping|Releasing|Preparing|Drafting|Resuming|Pulling|Surveying|Recording)\s+(?:for|in|on|with|after|before|to|at|as|of|by|from)\s+/gi;
1392
+ // Identifier strip removes version numbers like "0.3.4" but can leave stranded
1393
+ // release adjectives behind: "Released latest of package" was "Released latest
1394
+ // 0.3.4 of package"; "Released new with X" was "Released new 0.3.4 with X".
1395
+ // The chain pattern keeps the noun phrase that followed the version. The
1396
+ // trailing pattern handles bare "... new" or "... latest" tails.
1397
+ const RELEASE_FRAGMENT_CHAIN_PATTERN =
1398
+ /\b(Released|Releasing|Bumped|Bumping|Published|Publishing|Updated|Updating|Shipped|Shipping)\s+(?:new|latest|version|update)\s+(?:of|with|to|and|in|on|for)\s+/gi;
1399
+ const RELEASE_FRAGMENT_TRAILING_PATTERN =
1400
+ /\s+(?:new|latest|version|update)\s*(?:of|with|to|and|in|on)?\s*[.!?,;:]?\s*$/i;
1372
1401
 
1373
1402
  function stripDanglingPrepositions(text: string): string {
1374
1403
  let cleaned = text;
@@ -1376,14 +1405,16 @@ function stripDanglingPrepositions(text: string): string {
1376
1405
  do {
1377
1406
  previous = cleaned;
1378
1407
  cleaned = cleaned.replace(VERB_BARE_PREP_PATTERN, "$1 ");
1408
+ cleaned = cleaned.replace(RELEASE_FRAGMENT_CHAIN_PATTERN, "$1 ");
1379
1409
  cleaned = cleaned.replace(DANGLING_PREP_CHAIN_PATTERN, "$2");
1410
+ cleaned = cleaned.replace(RELEASE_FRAGMENT_TRAILING_PATTERN, "").trim();
1380
1411
  cleaned = cleaned.replace(DANGLING_TRAILING_PREP_PATTERN, "").trim();
1381
1412
  } while (cleaned !== previous && cleaned.length > 0);
1382
1413
  return cleaned;
1383
1414
  }
1384
1415
 
1385
1416
  // Last-resort fallback when the model ignored the allow-list and started the
1386
- // TLDR with a banned tool-narration verb. The map preserves tense (present
1417
+ // progress update with a banned tool-narration verb. The map preserves tense (present
1387
1418
  // vs past). Case of the first letter follows the original word.
1388
1419
  const BANNED_FIRST_WORD_REWRITES: Record<string, string> = {
1389
1420
  Reading: "Reviewing",
@@ -1437,7 +1468,7 @@ function rewriteBannedFirstWord(text: string): string {
1437
1468
  const original = match[1];
1438
1469
  const replacement = BANNED_FIRST_WORD_REWRITES[original];
1439
1470
  if (!replacement) return text;
1440
- // Preserve lowercase-start TLDRs (rare but possible) by matching original case.
1471
+ // Preserve lowercase-start outputs (rare but possible) by matching original case.
1441
1472
  const cased =
1442
1473
  original[0] === original[0].toLowerCase()
1443
1474
  ? replacement[0].toLowerCase() + replacement.slice(1)
@@ -1466,9 +1497,9 @@ function stripSuccessSuffix(text: string): string {
1466
1497
  return cleaned;
1467
1498
  }
1468
1499
 
1469
- function sanitizeTldrText(
1500
+ function sanitizeProgressText(
1470
1501
  text: string,
1471
- maxChars = MAX_SAFE_TLDR_CHARS,
1502
+ maxChars = MAX_SAFE_PROGRESS_CHARS,
1472
1503
  ): string {
1473
1504
  const stripped = stripTerminalControls(text);
1474
1505
  const withoutMarkdown = stripMarkdownFormatting(stripped);
@@ -1576,7 +1607,7 @@ export default function (pi: ExtensionAPI) {
1576
1607
  let statusFilter: StatusFilter = { mode: "all", hidden: new Set() };
1577
1608
  const seenStatusKeys = new Set<string>();
1578
1609
  const refresh = () => requestRender?.();
1579
- const tldr = new FooterTldrEngine(refresh);
1610
+ const progress = new FooterProgressEngine(refresh);
1580
1611
  const restoreStatusFilter = (ctx: ExtensionContext) => {
1581
1612
  let restoredFilter = readGlobalStatusFilter();
1582
1613
  if (!restoredFilter) {
@@ -1764,32 +1795,32 @@ export default function (pi: ExtensionAPI) {
1764
1795
  pi.on("thinking_level_select", async () => refresh());
1765
1796
  pi.on("turn_end", async () => refresh());
1766
1797
  pi.on("before_agent_start", async (event, ctx) => {
1767
- tldr.recordUserMessage(ctx, event.prompt);
1798
+ progress.recordUserMessage(ctx, event.prompt);
1768
1799
  });
1769
1800
  pi.on("message_update", async (event, ctx) => {
1770
- tldr.recordAssistantUpdate(ctx, event.message);
1801
+ progress.recordAssistantUpdate(ctx, event.message);
1771
1802
  });
1772
1803
  pi.on("tool_call", async (event, ctx) => {
1773
- tldr.recordToolCall(ctx, event);
1804
+ progress.recordToolCall(ctx, event);
1774
1805
  });
1775
1806
  pi.on("tool_result", async (event, ctx) => {
1776
- tldr.recordToolResult(ctx, event);
1807
+ progress.recordToolResult(ctx, event);
1777
1808
  });
1778
1809
  pi.on("message_end", async (event, ctx) => {
1779
- tldr.recordMessageEnd(ctx, event.message);
1810
+ progress.recordMessageEnd(ctx, event.message);
1780
1811
  refresh();
1781
1812
  });
1782
1813
  pi.on("session_before_tree", async () => {
1783
- tldr.shutdown();
1814
+ progress.shutdown();
1784
1815
  });
1785
1816
  pi.on("session_tree", async (_event, ctx) => {
1786
- tldr.startSession(ctx.cwd);
1817
+ progress.startSession(ctx.cwd);
1787
1818
  restoreStatusFilter(ctx);
1788
1819
  refresh();
1789
1820
  });
1790
1821
 
1791
1822
  pi.on("session_start", async (_event, ctx) => {
1792
- tldr.startSession(ctx.cwd);
1823
+ progress.startSession(ctx.cwd);
1793
1824
  restoreStatusFilter(ctx);
1794
1825
 
1795
1826
  if (!ctx.hasUI) return;
@@ -1823,13 +1854,13 @@ export default function (pi: ExtensionAPI) {
1823
1854
  const contextText = usage
1824
1855
  ? `${usage.percent !== null ? `${usage.percent.toFixed(1)}%` : "—%"} / ${formatTokens(usage.contextWindow)}`
1825
1856
  : "—";
1826
- const tldrText = tldr.text();
1857
+ const progressText = progress.text();
1827
1858
 
1828
1859
  const segmentRenderers: Record<SegmentName, string | null> = {
1829
1860
  model: theme.fg("accent", modelName),
1830
1861
  thinking: theme.fg(thinkingColor(thinkingLevel), `think:${thinkingLevel}`),
1831
1862
  context: theme.fg(contextSegmentColor, contextText),
1832
- tldr: tldrText ? theme.fg("muted", tldrText) : null,
1863
+ progress: progressText ? theme.fg("muted", progressText) : null,
1833
1864
  extensions: extensionStatuses ? theme.fg("muted", extensionStatuses) : null,
1834
1865
  };
1835
1866
 
@@ -1846,7 +1877,7 @@ export default function (pi: ExtensionAPI) {
1846
1877
  });
1847
1878
 
1848
1879
  pi.on("session_shutdown", async (_event, ctx) => {
1849
- tldr.shutdown();
1880
+ progress.shutdown();
1850
1881
  if (ctx.hasUI) ctx.ui.setFooter(undefined);
1851
1882
  });
1852
1883
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "pi-bar",
3
- "version": "0.3.29",
4
- "description": "Pi footer/statusline showing active model, thinking level, context pressure, live TLDR, and extension statuses.",
3
+ "version": "0.3.30",
4
+ "description": "Pi footer/statusline showing active model, thinking level, context pressure, live progress updates, and extension statuses.",
5
5
  "type": "module",
6
6
  "keywords": [
7
7
  "pi-package",
@@ -14,6 +14,7 @@
14
14
  "thinking",
15
15
  "context",
16
16
  "context-window",
17
+ "progress",
17
18
  "tldr"
18
19
  ],
19
20
  "license": "MIT",
@@ -51,6 +52,6 @@
51
52
  "extensions": [
52
53
  "./extensions/status-footer.ts"
53
54
  ],
54
- "image": "https://cdn.jsdelivr.net/npm/pi-bar@0.3.29/assets/screenshot-yellow.png"
55
+ "image": "https://cdn.jsdelivr.net/npm/pi-bar@0.3.30/assets/screenshot-yellow.png"
55
56
  }
56
57
  }