pi-ui-extend 0.1.5 → 0.1.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/app/app.d.ts +2 -0
- package/dist/app/app.js +18 -0
- package/dist/app/extension-ui-controller.js +1 -0
- package/dist/app/icons.d.ts +2 -0
- package/dist/app/icons.js +4 -0
- package/dist/app/mouse-controller.d.ts +4 -1
- package/dist/app/mouse-controller.js +12 -0
- package/dist/app/render-controller.js +1 -0
- package/dist/app/session-lifecycle-controller.js +3 -3
- package/dist/app/status-line-renderer.d.ts +5 -1
- package/dist/app/status-line-renderer.js +23 -2
- package/dist/app/terminal-bell-sound-controller.d.ts +11 -0
- package/dist/app/terminal-bell-sound-controller.js +58 -0
- package/dist/app/types.d.ts +10 -0
- package/extensions/terminal-bell/index.ts +43 -13
- package/external/pi-tools-suite/README.md +4 -50
- package/external/pi-tools-suite/src/async-subagents/core/spawn.ts +0 -1
- package/external/pi-tools-suite/src/default-pi-tools-suite-config.ts +8 -1
- package/external/pi-tools-suite/src/index.ts +0 -1
- package/external/pi-tools-suite/src/lsp/_shared/config.ts +38 -13
- package/external/pi-tools-suite/src/lsp/_shared/paths.ts +11 -1
- package/external/pi-tools-suite/src/lsp/async.ts +6 -1
- package/external/pi-tools-suite/src/lsp/child-process.ts +16 -2
- package/external/pi-tools-suite/src/lsp/client.ts +183 -4
- package/external/pi-tools-suite/src/lsp/manager.ts +44 -5
- package/external/pi-tools-suite/src/lsp/markdown-diagnostics.ts +157 -0
- package/package.json +1 -1
- package/external/pi-tools-suite/src/terminal-bell/index.ts +0 -309
package/dist/app/app.d.ts
CHANGED
|
@@ -26,6 +26,7 @@ export declare class PiUiExtendApp {
|
|
|
26
26
|
private readonly subagentsWidgetController;
|
|
27
27
|
private readonly todoWidgetController;
|
|
28
28
|
private readonly terminalController;
|
|
29
|
+
private readonly terminalBellSoundController;
|
|
29
30
|
private readonly toastController;
|
|
30
31
|
private readonly nerdFontController;
|
|
31
32
|
private readonly popupActions;
|
|
@@ -86,6 +87,7 @@ export declare class PiUiExtendApp {
|
|
|
86
87
|
private toolDefaultExpanded;
|
|
87
88
|
private stopBlinking;
|
|
88
89
|
private stop;
|
|
90
|
+
private toggleTerminalBellSound;
|
|
89
91
|
private refreshModelUsageStatusFromClick;
|
|
90
92
|
private showToast;
|
|
91
93
|
private clearToastTimers;
|
package/dist/app/app.js
CHANGED
|
@@ -37,6 +37,7 @@ import { AppTodoWidgetController } from "./todo-widget-controller.js";
|
|
|
37
37
|
import { AppTabsController } from "./tabs-controller.js";
|
|
38
38
|
import { TabLineRenderer } from "./tab-line-renderer.js";
|
|
39
39
|
import { AppTerminalController } from "./terminal-controller.js";
|
|
40
|
+
import { TerminalBellSoundController } from "./terminal-bell-sound-controller.js";
|
|
40
41
|
import { AppToastController } from "./toast-controller.js";
|
|
41
42
|
import { checkPixUpdate, formatPixStartupUpdateDialog } from "./update.js";
|
|
42
43
|
import { AppVoiceController } from "./voice-controller.js";
|
|
@@ -72,6 +73,7 @@ export class PiUiExtendApp {
|
|
|
72
73
|
subagentsWidgetController;
|
|
73
74
|
todoWidgetController;
|
|
74
75
|
terminalController;
|
|
76
|
+
terminalBellSoundController;
|
|
75
77
|
toastController;
|
|
76
78
|
nerdFontController;
|
|
77
79
|
popupActions;
|
|
@@ -174,6 +176,7 @@ export class PiUiExtendApp {
|
|
|
174
176
|
});
|
|
175
177
|
this.pixConfig = loadPixConfig();
|
|
176
178
|
setAppIconTheme(this.pixConfig.iconTheme.name);
|
|
179
|
+
this.terminalBellSoundController = new TerminalBellSoundController();
|
|
177
180
|
this.promptEnhancer = new AppPromptEnhancerController({
|
|
178
181
|
runtime: () => this.runtime,
|
|
179
182
|
inputEditor: () => this.inputEditor,
|
|
@@ -244,6 +247,8 @@ export class PiUiExtendApp {
|
|
|
244
247
|
promptEnhancerStatusWidgetText: () => this.promptEnhancer.statusWidgetText(),
|
|
245
248
|
promptEnhancerStatusWidgetActive: () => this.promptEnhancer.statusWidgetActive(),
|
|
246
249
|
promptEnhancerStatusWidgetEnabled: () => this.promptEnhancer.statusWidgetEnabled(),
|
|
250
|
+
terminalBellSoundStatusWidgetText: () => this.terminalBellSoundController.statusWidgetText(),
|
|
251
|
+
terminalBellSoundStatusWidgetEnabled: () => this.terminalBellSoundController.isEnabled(),
|
|
247
252
|
voiceStatusWidgetText: () => this.voiceController.statusWidgetText(),
|
|
248
253
|
voiceStatusWidgetActive: () => this.voiceController.statusWidgetActive(),
|
|
249
254
|
userMessageJumpMenuActive: () => this.popupMenus.directMenu === "user-message-jump",
|
|
@@ -502,6 +507,7 @@ export class PiUiExtendApp {
|
|
|
502
507
|
this.superCompactTools = !this.superCompactTools;
|
|
503
508
|
this.render();
|
|
504
509
|
},
|
|
510
|
+
toggleTerminalBellSound: () => this.toggleTerminalBellSound(),
|
|
505
511
|
handleExtensionInputMouse: (event) => this.extensionUiController.handleCustomUiMouse(event),
|
|
506
512
|
render: () => this.render(),
|
|
507
513
|
}, this.popupMenus, this.popupActions, this.scrollController, this.commandController);
|
|
@@ -657,6 +663,7 @@ export class PiUiExtendApp {
|
|
|
657
663
|
this.mouseController.statusUserJumpTarget = undefined;
|
|
658
664
|
this.mouseController.statusThinkingExpandTarget = undefined;
|
|
659
665
|
this.mouseController.statusCompactToolsTarget = undefined;
|
|
666
|
+
this.mouseController.statusTerminalBellSoundTarget = undefined;
|
|
660
667
|
this.mouseController.statusSessionTarget = undefined;
|
|
661
668
|
this.mouseController.statusPromptEnhancerTarget = undefined;
|
|
662
669
|
this.mouseController.statusVoiceMicTarget = undefined;
|
|
@@ -829,6 +836,17 @@ export class PiUiExtendApp {
|
|
|
829
836
|
async stop() {
|
|
830
837
|
await this.terminalController.stop();
|
|
831
838
|
}
|
|
839
|
+
toggleTerminalBellSound() {
|
|
840
|
+
try {
|
|
841
|
+
const enabled = this.terminalBellSoundController.toggle();
|
|
842
|
+
this.showToast(enabled ? "Terminal bell notifications enabled" : "Terminal bell notifications muted", "info");
|
|
843
|
+
this.render();
|
|
844
|
+
}
|
|
845
|
+
catch (error) {
|
|
846
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
847
|
+
this.showToast(`Could not update terminal bell notifications: ${message}`, "error");
|
|
848
|
+
}
|
|
849
|
+
}
|
|
832
850
|
refreshModelUsageStatusFromClick() {
|
|
833
851
|
const refresh = this.modelUsageController.refreshNow();
|
|
834
852
|
if (refresh.kind === "unsupported") {
|
package/dist/app/icons.d.ts
CHANGED
package/dist/app/icons.js
CHANGED
|
@@ -22,6 +22,8 @@ const NERD_FONT_ICONS = {
|
|
|
22
22
|
plus: "\u{f0415}",
|
|
23
23
|
record: "\u{f044a}",
|
|
24
24
|
refresh: "\u{f0450}",
|
|
25
|
+
volumeHigh: "\u{f057e}",
|
|
26
|
+
volumeOff: "\u{f0581}",
|
|
25
27
|
user: "\u{f0004}",
|
|
26
28
|
compactTools: "\u{f035c}",
|
|
27
29
|
thinkingExpanded: "\u{f0335}",
|
|
@@ -45,6 +47,8 @@ const FALLBACK_ICONS = {
|
|
|
45
47
|
plus: "+",
|
|
46
48
|
record: "●",
|
|
47
49
|
refresh: "↻",
|
|
50
|
+
volumeHigh: "♪",
|
|
51
|
+
volumeOff: "ø",
|
|
48
52
|
user: "@",
|
|
49
53
|
compactTools: "≡",
|
|
50
54
|
thinkingExpanded: ">",
|
|
@@ -6,7 +6,7 @@ import type { ToastEntry, ToastVariant } from "../ui.js";
|
|
|
6
6
|
import type { AppPopupActionController } from "./popup-action-controller.js";
|
|
7
7
|
import type { AppPopupMenuController } from "./popup-menu-controller.js";
|
|
8
8
|
import type { AppScrollController } from "./scroll-controller.js";
|
|
9
|
-
import type { Entry, ImageClickTarget, MouseEvent, MouseSelection, StatusContextTarget, StatusCompactToolsTarget, StatusModelTarget, StatusModelUsageTarget, StatusPromptEnhancerTarget, StatusSessionTarget, TabLineMouseTarget, StatusThinkingExpandTarget, StatusThinkingTarget, StatusUserJumpTarget, StatusVoiceLanguageTarget, StatusVoiceMicTarget } from "./types.js";
|
|
9
|
+
import type { Entry, ImageClickTarget, MouseEvent, MouseSelection, StatusContextTarget, StatusCompactToolsTarget, StatusModelTarget, StatusModelUsageTarget, StatusPromptEnhancerTarget, StatusSessionTarget, StatusTerminalBellSoundTarget, TabLineMouseTarget, StatusThinkingExpandTarget, StatusThinkingTarget, StatusUserJumpTarget, StatusVoiceLanguageTarget, StatusVoiceMicTarget } from "./types.js";
|
|
10
10
|
import { type RenderedLink } from "./file-links.js";
|
|
11
11
|
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
12
12
|
type ClickFlash = {
|
|
@@ -58,6 +58,7 @@ export type AppMouseControllerHost = {
|
|
|
58
58
|
refreshModelUsageStatus(): void | Promise<void>;
|
|
59
59
|
toggleAllThinkingExpanded?(): void;
|
|
60
60
|
toggleSuperCompactTools?(): void;
|
|
61
|
+
toggleTerminalBellSound?(): void;
|
|
61
62
|
copyTextToClipboard?(text: string): void;
|
|
62
63
|
handleExtensionInputMouse(event: MouseEvent & {
|
|
63
64
|
localRow: number;
|
|
@@ -101,6 +102,7 @@ export declare class AppMouseController {
|
|
|
101
102
|
statusUserJumpTarget: StatusUserJumpTarget | undefined;
|
|
102
103
|
statusThinkingExpandTarget: StatusThinkingExpandTarget | undefined;
|
|
103
104
|
statusCompactToolsTarget: StatusCompactToolsTarget | undefined;
|
|
105
|
+
statusTerminalBellSoundTarget: StatusTerminalBellSoundTarget | undefined;
|
|
104
106
|
statusSessionTarget: StatusSessionTarget | undefined;
|
|
105
107
|
statusPromptEnhancerTarget: StatusPromptEnhancerTarget | undefined;
|
|
106
108
|
statusVoiceMicTarget: StatusVoiceMicTarget | undefined;
|
|
@@ -152,6 +154,7 @@ export declare class AppMouseController {
|
|
|
152
154
|
private handleStatusUserJumpClick;
|
|
153
155
|
private handleStatusThinkingExpandClick;
|
|
154
156
|
private handleStatusCompactToolsClick;
|
|
157
|
+
private handleStatusTerminalBellSoundClick;
|
|
155
158
|
private handleStatusSessionClick;
|
|
156
159
|
private handleStatusPromptEnhancerClick;
|
|
157
160
|
private handleStatusVoiceMicClick;
|
|
@@ -25,6 +25,7 @@ export class AppMouseController {
|
|
|
25
25
|
statusUserJumpTarget;
|
|
26
26
|
statusThinkingExpandTarget;
|
|
27
27
|
statusCompactToolsTarget;
|
|
28
|
+
statusTerminalBellSoundTarget;
|
|
28
29
|
statusSessionTarget;
|
|
29
30
|
statusPromptEnhancerTarget;
|
|
30
31
|
statusVoiceMicTarget;
|
|
@@ -78,6 +79,8 @@ export class AppMouseController {
|
|
|
78
79
|
return;
|
|
79
80
|
if (event.button === 0 && this.withClickFlash(event, () => this.handleStatusCompactToolsClick(event)))
|
|
80
81
|
return;
|
|
82
|
+
if (event.button === 0 && this.withClickFlash(event, () => this.handleStatusTerminalBellSoundClick(event)))
|
|
83
|
+
return;
|
|
81
84
|
if (event.button === 0 && this.withClickFlash(event, () => this.handleStatusSessionClick(event)))
|
|
82
85
|
return;
|
|
83
86
|
if (event.button === 0 && this.withClickFlash(event, () => this.handleStatusPromptEnhancerClick(event)))
|
|
@@ -522,6 +525,15 @@ export class AppMouseController {
|
|
|
522
525
|
this.host.toggleSuperCompactTools?.();
|
|
523
526
|
return true;
|
|
524
527
|
}
|
|
528
|
+
handleStatusTerminalBellSoundClick(event) {
|
|
529
|
+
const target = this.statusTerminalBellSoundTarget;
|
|
530
|
+
if (!target)
|
|
531
|
+
return false;
|
|
532
|
+
if (event.y !== target.row || event.x < target.startColumn || event.x >= target.endColumn)
|
|
533
|
+
return false;
|
|
534
|
+
this.host.toggleTerminalBellSound?.();
|
|
535
|
+
return true;
|
|
536
|
+
}
|
|
525
537
|
handleStatusSessionClick(event) {
|
|
526
538
|
const target = this.statusSessionTarget;
|
|
527
539
|
if (!target)
|
|
@@ -294,6 +294,7 @@ export class AppRenderController {
|
|
|
294
294
|
this.deps.mouseController.statusUserJumpTarget = this.deps.statusLineRenderer.userJumpTarget?.(statusLayout, statusRow);
|
|
295
295
|
this.deps.mouseController.statusThinkingExpandTarget = this.deps.statusLineRenderer.thinkingExpandTarget?.(statusLayout, statusRow);
|
|
296
296
|
this.deps.mouseController.statusCompactToolsTarget = this.deps.statusLineRenderer.compactToolsTarget?.(statusLayout, statusRow);
|
|
297
|
+
this.deps.mouseController.statusTerminalBellSoundTarget = this.deps.statusLineRenderer.terminalBellSoundTarget?.(statusLayout, statusRow);
|
|
297
298
|
this.deps.mouseController.statusSessionTarget = this.deps.statusLineRenderer.sessionTarget(statusLayout.text, statusRow, statusLayout.sessionLabel, statusLayout.workspaceLabel);
|
|
298
299
|
this.deps.mouseController.statusPromptEnhancerTarget = this.deps.statusLineRenderer.promptEnhancerTarget(statusLayout, statusRow);
|
|
299
300
|
this.deps.mouseController.statusVoiceMicTarget = this.deps.statusLineRenderer.voiceMicTarget(statusLayout, statusRow);
|
|
@@ -67,6 +67,9 @@ export class AppSessionLifecycleController {
|
|
|
67
67
|
async bindCurrentSession() {
|
|
68
68
|
const runtime = this.requireRuntime();
|
|
69
69
|
this.unsubscribe?.();
|
|
70
|
+
this.unsubscribe = runtime.session.subscribe((event) => {
|
|
71
|
+
this.host.handleSessionEvent(event);
|
|
72
|
+
});
|
|
70
73
|
this.host.closeSdkMenuForBind();
|
|
71
74
|
this.host.clearExtensionWidgets();
|
|
72
75
|
await runtime.session.bindExtensions({
|
|
@@ -75,9 +78,6 @@ export class AppSessionLifecycleController {
|
|
|
75
78
|
shutdownHandler: this.host.extensionShutdownHandler(),
|
|
76
79
|
onError: (error) => this.host.handleExtensionError(error),
|
|
77
80
|
});
|
|
78
|
-
this.unsubscribe = runtime.session.subscribe((event) => {
|
|
79
|
-
this.host.handleSessionEvent(event);
|
|
80
|
-
});
|
|
81
81
|
}
|
|
82
82
|
unsubscribeSession() {
|
|
83
83
|
this.unsubscribe?.();
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentSession } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import type { Theme } from "../theme.js";
|
|
3
|
-
import type { SessionActivity, StatusCompactToolsTarget, StatusContextTarget, StatusLineLayout, StatusModelTarget, StatusModelUsageTarget, StatusPromptEnhancerTarget, StatusSessionTarget, StatusThinkingExpandTarget, StatusThinkingTarget, StatusUserJumpTarget, StatusVoiceLanguageTarget, StatusVoiceMicTarget } from "./types.js";
|
|
3
|
+
import type { SessionActivity, StatusCompactToolsTarget, StatusContextTarget, StatusLineLayout, StatusModelTarget, StatusModelUsageTarget, StatusPromptEnhancerTarget, StatusSessionTarget, StatusTerminalBellSoundTarget, StatusThinkingExpandTarget, StatusThinkingTarget, StatusUserJumpTarget, StatusVoiceLanguageTarget, StatusVoiceMicTarget } from "./types.js";
|
|
4
4
|
import type { ScreenStyler } from "./screen-styler.js";
|
|
5
5
|
import { type ModelColorsConfig } from "../config.js";
|
|
6
6
|
export type StatusLineRendererHost = {
|
|
@@ -22,6 +22,8 @@ export type StatusLineRendererHost = {
|
|
|
22
22
|
promptEnhancerStatusWidgetText(): string;
|
|
23
23
|
promptEnhancerStatusWidgetActive(): boolean;
|
|
24
24
|
promptEnhancerStatusWidgetEnabled(): boolean;
|
|
25
|
+
terminalBellSoundStatusWidgetText(): string;
|
|
26
|
+
terminalBellSoundStatusWidgetEnabled(): boolean;
|
|
25
27
|
voiceStatusWidgetText(): string;
|
|
26
28
|
voiceStatusWidgetActive(): boolean;
|
|
27
29
|
userMessageJumpMenuActive?(): boolean;
|
|
@@ -43,12 +45,14 @@ export declare class StatusLineRenderer {
|
|
|
43
45
|
userJumpTarget(layout: StatusLineLayout, row: number): StatusUserJumpTarget | undefined;
|
|
44
46
|
thinkingExpandTarget(layout: StatusLineLayout, row: number): StatusThinkingExpandTarget | undefined;
|
|
45
47
|
compactToolsTarget(layout: StatusLineLayout, row: number): StatusCompactToolsTarget | undefined;
|
|
48
|
+
terminalBellSoundTarget(layout: StatusLineLayout, row: number): StatusTerminalBellSoundTarget | undefined;
|
|
46
49
|
sessionTarget(statusText: string, row: number, label: string, workspaceLabel: string): StatusSessionTarget | undefined;
|
|
47
50
|
private segments;
|
|
48
51
|
private pushPromptEnhancerWidgetSegment;
|
|
49
52
|
private pushUserJumpWidgetSegment;
|
|
50
53
|
private pushThinkingExpandWidgetSegment;
|
|
51
54
|
private pushCompactToolsWidgetSegment;
|
|
55
|
+
private pushTerminalBellSoundWidgetSegment;
|
|
52
56
|
private pushVoiceWidgetSegment;
|
|
53
57
|
private pushWorkspaceSegments;
|
|
54
58
|
private pushModelUsageSegments;
|
|
@@ -16,9 +16,10 @@ export class StatusLineRenderer {
|
|
|
16
16
|
const userJumpButton = APP_ICONS.user;
|
|
17
17
|
const thinkingExpandButton = APP_ICONS.thinkingExpanded;
|
|
18
18
|
const compactToolsButton = APP_ICONS.compactTools;
|
|
19
|
+
const terminalBellSoundWidgetText = this.host.terminalBellSoundStatusWidgetText();
|
|
19
20
|
const promptEnhancerWidgetText = this.host.promptEnhancerStatusWidgetText();
|
|
20
21
|
const voiceWidgetText = this.host.voiceStatusWidgetText();
|
|
21
|
-
const rightWidgetText = [userJumpButton, thinkingExpandButton, compactToolsButton, promptEnhancerWidgetText, voiceWidgetText].filter((text) => text.length > 0).join(" ");
|
|
22
|
+
const rightWidgetText = [userJumpButton, terminalBellSoundWidgetText, thinkingExpandButton, compactToolsButton, promptEnhancerWidgetText, voiceWidgetText].filter((text) => text.length > 0).join(" ");
|
|
22
23
|
const rightWidgetWidth = stringDisplayWidth(rightWidgetText);
|
|
23
24
|
const leftWidth = rightWidgetWidth > 0 && contentWidth > rightWidgetWidth + 1 ? contentWidth - rightWidgetWidth - 1 : contentWidth;
|
|
24
25
|
const baseStatus = this.host.currentStatus();
|
|
@@ -38,6 +39,11 @@ export class StatusLineRenderer {
|
|
|
38
39
|
: undefined;
|
|
39
40
|
if (userJumpWidget)
|
|
40
41
|
nextWidgetStartColumn = userJumpWidget.endColumn + 1;
|
|
42
|
+
const terminalBellSoundWidget = leftWidth < contentWidth && terminalBellSoundWidgetText.length > 0
|
|
43
|
+
? this.widgetLayout(nextWidgetStartColumn, terminalBellSoundWidgetText)
|
|
44
|
+
: undefined;
|
|
45
|
+
if (terminalBellSoundWidget)
|
|
46
|
+
nextWidgetStartColumn = terminalBellSoundWidget.endColumn + 1;
|
|
41
47
|
const thinkingExpandWidget = leftWidth < contentWidth
|
|
42
48
|
? this.widgetLayout(nextWidgetStartColumn, thinkingExpandButton)
|
|
43
49
|
: undefined;
|
|
@@ -62,6 +68,7 @@ export class StatusLineRenderer {
|
|
|
62
68
|
...(userJumpWidget ? { userJumpWidget } : {}),
|
|
63
69
|
...(thinkingExpandWidget ? { thinkingExpandWidget } : {}),
|
|
64
70
|
...(compactToolsWidget ? { compactToolsWidget } : {}),
|
|
71
|
+
...(terminalBellSoundWidget ? { terminalBellSoundWidget } : {}),
|
|
65
72
|
...(modelUsageLabel ? { modelUsageLabel } : {}),
|
|
66
73
|
...(contextBarLabel ? { contextBarLabel } : {}),
|
|
67
74
|
...(promptEnhancerWidget ? { promptEnhancerWidget } : {}),
|
|
@@ -162,6 +169,12 @@ export class StatusLineRenderer {
|
|
|
162
169
|
return undefined;
|
|
163
170
|
return { row, startColumn: widget.startColumn, endColumn: widget.endColumn };
|
|
164
171
|
}
|
|
172
|
+
terminalBellSoundTarget(layout, row) {
|
|
173
|
+
const widget = layout.terminalBellSoundWidget;
|
|
174
|
+
if (!widget)
|
|
175
|
+
return undefined;
|
|
176
|
+
return { row, startColumn: widget.startColumn, endColumn: widget.endColumn };
|
|
177
|
+
}
|
|
165
178
|
sessionTarget(statusText, row, label, workspaceLabel) {
|
|
166
179
|
if (!this.host.session || !label)
|
|
167
180
|
return undefined;
|
|
@@ -185,6 +198,7 @@ export class StatusLineRenderer {
|
|
|
185
198
|
this.pushUserJumpWidgetSegment(segments, statusText);
|
|
186
199
|
this.pushThinkingExpandWidgetSegment(segments, statusText);
|
|
187
200
|
this.pushCompactToolsWidgetSegment(segments, statusText);
|
|
201
|
+
this.pushTerminalBellSoundWidgetSegment(segments, statusText);
|
|
188
202
|
this.pushWorkspaceSegments(segments, statusText, layout.workspaceLabel);
|
|
189
203
|
if (layout.modelUsageLabel)
|
|
190
204
|
this.pushModelUsageSegments(segments, statusText, layout.modelUsageLabel);
|
|
@@ -248,6 +262,13 @@ export class StatusLineRenderer {
|
|
|
248
262
|
return;
|
|
249
263
|
this.pushSegment(segments, start, buttonText.length, this.host.superCompactToolsActive?.() ? this.host.theme.colors.info : this.host.theme.colors.muted);
|
|
250
264
|
}
|
|
265
|
+
pushTerminalBellSoundWidgetSegment(segments, statusText) {
|
|
266
|
+
const widgetText = this.host.terminalBellSoundStatusWidgetText();
|
|
267
|
+
const start = statusText.lastIndexOf(widgetText);
|
|
268
|
+
if (start < 0 || widgetText.length <= 0)
|
|
269
|
+
return;
|
|
270
|
+
this.pushSegment(segments, start, widgetText.length, this.host.terminalBellSoundStatusWidgetEnabled() ? this.host.theme.colors.info : this.host.theme.colors.muted);
|
|
271
|
+
}
|
|
251
272
|
pushVoiceWidgetSegment(segments, statusText) {
|
|
252
273
|
const widgetText = this.host.voiceStatusWidgetText();
|
|
253
274
|
const start = statusText.lastIndexOf(widgetText);
|
|
@@ -261,7 +282,7 @@ export class StatusLineRenderer {
|
|
|
261
282
|
}
|
|
262
283
|
const separatorIndex = widgetText.indexOf(" ");
|
|
263
284
|
const micLength = separatorIndex >= 0 ? separatorIndex : widgetText.length;
|
|
264
|
-
this.pushSegment(segments, start, micLength, this.host.theme.colors.
|
|
285
|
+
this.pushSegment(segments, start, micLength, this.host.theme.colors.muted);
|
|
265
286
|
}
|
|
266
287
|
pushWorkspaceSegments(segments, statusText, workspaceLabel) {
|
|
267
288
|
const start = statusText.lastIndexOf(workspaceLabel);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare function getPiToolsSuiteUserConfigPath(homeDir?: string): string;
|
|
2
|
+
export declare function readTerminalBellSoundEnabled(configPath?: string): boolean;
|
|
3
|
+
export declare function writeTerminalBellSoundEnabled(enabled: boolean, configPath?: string): void;
|
|
4
|
+
export declare class TerminalBellSoundController {
|
|
5
|
+
private readonly configPath;
|
|
6
|
+
private enabled;
|
|
7
|
+
constructor(configPath?: string);
|
|
8
|
+
statusWidgetText(): string;
|
|
9
|
+
isEnabled(): boolean;
|
|
10
|
+
toggle(): boolean;
|
|
11
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
import { applyEdits, modify, parse as parseJsonc } from "jsonc-parser";
|
|
5
|
+
import { APP_ICONS } from "./icons.js";
|
|
6
|
+
const TERMINAL_BELL_CONFIG_KEY = "terminalBell";
|
|
7
|
+
const SOUND_CONFIG_KEY = "sound";
|
|
8
|
+
export function getPiToolsSuiteUserConfigPath(homeDir = homedir()) {
|
|
9
|
+
return join(homeDir, ".config", "pi", "pi-tools-suite.jsonc");
|
|
10
|
+
}
|
|
11
|
+
function isRecord(value) {
|
|
12
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
export function readTerminalBellSoundEnabled(configPath = getPiToolsSuiteUserConfigPath()) {
|
|
15
|
+
if (!existsSync(configPath))
|
|
16
|
+
return true;
|
|
17
|
+
try {
|
|
18
|
+
const parsed = parseJsonc(readFileSync(configPath, "utf-8"));
|
|
19
|
+
if (!isRecord(parsed))
|
|
20
|
+
return true;
|
|
21
|
+
const terminalBell = parsed[TERMINAL_BELL_CONFIG_KEY];
|
|
22
|
+
if (!isRecord(terminalBell))
|
|
23
|
+
return true;
|
|
24
|
+
return typeof terminalBell[SOUND_CONFIG_KEY] === "boolean" ? terminalBell[SOUND_CONFIG_KEY] : true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function writeTerminalBellSoundEnabled(enabled, configPath = getPiToolsSuiteUserConfigPath()) {
|
|
31
|
+
const original = existsSync(configPath) ? readFileSync(configPath, "utf-8") : "{}\n";
|
|
32
|
+
const edits = modify(original, [TERMINAL_BELL_CONFIG_KEY, SOUND_CONFIG_KEY], enabled, {
|
|
33
|
+
formattingOptions: { insertSpaces: true, tabSize: 2, eol: "\n" },
|
|
34
|
+
});
|
|
35
|
+
const updated = applyEdits(original, edits);
|
|
36
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
37
|
+
writeFileSync(configPath, updated.endsWith("\n") ? updated : `${updated}\n`, "utf-8");
|
|
38
|
+
}
|
|
39
|
+
export class TerminalBellSoundController {
|
|
40
|
+
configPath;
|
|
41
|
+
enabled;
|
|
42
|
+
constructor(configPath = getPiToolsSuiteUserConfigPath()) {
|
|
43
|
+
this.configPath = configPath;
|
|
44
|
+
this.enabled = readTerminalBellSoundEnabled(this.configPath);
|
|
45
|
+
}
|
|
46
|
+
statusWidgetText() {
|
|
47
|
+
return this.enabled ? APP_ICONS.volumeHigh : APP_ICONS.volumeOff;
|
|
48
|
+
}
|
|
49
|
+
isEnabled() {
|
|
50
|
+
return this.enabled;
|
|
51
|
+
}
|
|
52
|
+
toggle() {
|
|
53
|
+
const nextEnabled = !this.enabled;
|
|
54
|
+
writeTerminalBellSoundEnabled(nextEnabled, this.configPath);
|
|
55
|
+
this.enabled = nextEnabled;
|
|
56
|
+
return this.enabled;
|
|
57
|
+
}
|
|
58
|
+
}
|
package/dist/app/types.d.ts
CHANGED
|
@@ -266,6 +266,7 @@ export type StatusLineLayout = {
|
|
|
266
266
|
userJumpWidget?: StatusUserJumpWidgetLayout;
|
|
267
267
|
thinkingExpandWidget?: StatusThinkingExpandWidgetLayout;
|
|
268
268
|
compactToolsWidget?: StatusCompactToolsWidgetLayout;
|
|
269
|
+
terminalBellSoundWidget?: StatusTerminalBellSoundWidgetLayout;
|
|
269
270
|
promptEnhancerWidget?: StatusPromptEnhancerWidgetLayout;
|
|
270
271
|
voiceWidget?: StatusVoiceWidgetLayout;
|
|
271
272
|
};
|
|
@@ -281,10 +282,19 @@ export type StatusCompactToolsWidgetLayout = {
|
|
|
281
282
|
startColumn: number;
|
|
282
283
|
endColumn: number;
|
|
283
284
|
};
|
|
285
|
+
export type StatusTerminalBellSoundWidgetLayout = {
|
|
286
|
+
startColumn: number;
|
|
287
|
+
endColumn: number;
|
|
288
|
+
};
|
|
284
289
|
export type StatusPromptEnhancerWidgetLayout = {
|
|
285
290
|
startColumn: number;
|
|
286
291
|
endColumn: number;
|
|
287
292
|
};
|
|
293
|
+
export type StatusTerminalBellSoundTarget = {
|
|
294
|
+
row: number;
|
|
295
|
+
startColumn: number;
|
|
296
|
+
endColumn: number;
|
|
297
|
+
};
|
|
288
298
|
export type StatusVoiceWidgetLayout = {
|
|
289
299
|
startColumn: number;
|
|
290
300
|
micEndColumn: number;
|
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
2
2
|
import { spawn } from "node:child_process";
|
|
3
|
-
import { existsSync } from "node:fs";
|
|
3
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
4
|
+
import { homedir } from "node:os";
|
|
4
5
|
import { delimiter, isAbsolute, join } from "node:path";
|
|
6
|
+
import { parse as parseJsonc } from "jsonc-parser";
|
|
5
7
|
|
|
6
8
|
const BELL = "\x07";
|
|
7
9
|
const DEFAULT_IDLE_DELAY_MS = 250;
|
|
@@ -13,6 +15,8 @@ const DEFAULT_NOTIFICATION_TITLE = "Pi";
|
|
|
13
15
|
const DEFAULT_NOTIFICATION_MESSAGE = "Session stopped";
|
|
14
16
|
const DEFAULT_ASK_USER_NOTIFICATION_MESSAGE = "Waiting for your answer";
|
|
15
17
|
const DEFAULT_MAC_SOUND = "Glass";
|
|
18
|
+
const TERMINAL_BELL_CONFIG_KEY = "terminalBell";
|
|
19
|
+
const SOUND_CONFIG_KEY = "sound";
|
|
16
20
|
|
|
17
21
|
const TERM_PROGRAM_BUNDLE_IDS: Record<string, string> = {
|
|
18
22
|
Apple_Terminal: "com.apple.Terminal",
|
|
@@ -50,29 +54,55 @@ function extensionDisabled(): boolean {
|
|
|
50
54
|
return isTruthyEnv(process.env.HEADLESS) || isTruthyEnv(process.env.PI_TERMINAL_BELL_DISABLED);
|
|
51
55
|
}
|
|
52
56
|
|
|
53
|
-
function
|
|
54
|
-
|
|
55
|
-
if (process.env.PI_TERMINAL_BELL_FORCE === "1") return true;
|
|
56
|
-
return Boolean(process.stdout.isTTY || process.stderr.isTTY);
|
|
57
|
+
function getPiToolsSuiteUserConfigPath(homeDir = homedir()): string {
|
|
58
|
+
return join(homeDir, ".config", "pi", "pi-tools-suite.jsonc");
|
|
57
59
|
}
|
|
58
60
|
|
|
59
|
-
function
|
|
60
|
-
|
|
61
|
-
|
|
61
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
62
|
+
return value !== null && typeof value === "object" && !Array.isArray(value);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export function readTerminalBellSoundConfig(configPath = getPiToolsSuiteUserConfigPath()): boolean | undefined {
|
|
66
|
+
if (!existsSync(configPath)) return undefined;
|
|
67
|
+
try {
|
|
68
|
+
const parsed = parseJsonc(readFileSync(configPath, "utf-8")) as unknown;
|
|
69
|
+
if (!isRecord(parsed)) return undefined;
|
|
70
|
+
const terminalBell = parsed[TERMINAL_BELL_CONFIG_KEY];
|
|
71
|
+
if (!isRecord(terminalBell)) return undefined;
|
|
72
|
+
const sound = terminalBell[SOUND_CONFIG_KEY];
|
|
73
|
+
return typeof sound === "boolean" ? sound : undefined;
|
|
74
|
+
} catch {
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
62
77
|
}
|
|
63
78
|
|
|
64
|
-
function
|
|
79
|
+
export function terminalBellSoundEnabled(ctx: Pick<ExtensionContext, "hasUI">, configPath = getPiToolsSuiteUserConfigPath()): boolean {
|
|
65
80
|
if (process.env.PI_TERMINAL_BELL_SOUND === "0") return false;
|
|
66
81
|
if (process.env.PI_TERMINAL_BELL_SOUND === "1") return true;
|
|
82
|
+
const configured = readTerminalBellSoundConfig(configPath);
|
|
83
|
+
if (configured !== undefined) return configured;
|
|
67
84
|
return ctx.hasUI === true;
|
|
68
85
|
}
|
|
69
86
|
|
|
70
|
-
function
|
|
87
|
+
export function terminalBellNotificationsEnabled(ctx: Pick<ExtensionContext, "hasUI">, configPath = getPiToolsSuiteUserConfigPath()): boolean {
|
|
88
|
+
if (!terminalBellSoundEnabled(ctx, configPath)) return false;
|
|
71
89
|
if (process.env.PI_TERMINAL_BELL_NOTIFY === "0") return false;
|
|
72
90
|
if (process.env.PI_TERMINAL_BELL_NOTIFY === "1") return true;
|
|
73
91
|
return ctx.hasUI === true;
|
|
74
92
|
}
|
|
75
93
|
|
|
94
|
+
export function canRingTerminal(ctx: Pick<ExtensionContext, "hasUI">, configPath = getPiToolsSuiteUserConfigPath()): boolean {
|
|
95
|
+
if (!terminalBellSoundEnabled(ctx, configPath)) return false;
|
|
96
|
+
if (process.env.PI_TERMINAL_BELL === "0") return false;
|
|
97
|
+
if (process.env.PI_TERMINAL_BELL_FORCE === "1") return true;
|
|
98
|
+
return Boolean(process.stdout.isTTY || process.stderr.isTTY);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function writeBell(): void {
|
|
102
|
+
const stream = process.stdout.isTTY || !process.stderr.isTTY ? process.stdout : process.stderr;
|
|
103
|
+
stream.write(BELL);
|
|
104
|
+
}
|
|
105
|
+
|
|
76
106
|
function appleScriptString(value: string): string {
|
|
77
107
|
return `"${value.replace(/\\/g, "\\\\").replace(/"/g, '\\"')}"`;
|
|
78
108
|
}
|
|
@@ -124,7 +154,7 @@ function detectMacActivationBundleId(): string | undefined {
|
|
|
124
154
|
}
|
|
125
155
|
|
|
126
156
|
function playAttentionSound(ctx: ExtensionContext): void {
|
|
127
|
-
if (!
|
|
157
|
+
if (!terminalBellSoundEnabled(ctx)) return;
|
|
128
158
|
if (process.platform !== "darwin") return;
|
|
129
159
|
const sound = process.env.PI_TERMINAL_BELL_SOUND && process.env.PI_TERMINAL_BELL_SOUND !== "1"
|
|
130
160
|
? process.env.PI_TERMINAL_BELL_SOUND
|
|
@@ -139,7 +169,7 @@ function notifySessionStopped(
|
|
|
139
169
|
macActivationBundleId: string | undefined,
|
|
140
170
|
message = process.env.PI_TERMINAL_BELL_NOTIFY_MESSAGE ?? DEFAULT_NOTIFICATION_MESSAGE,
|
|
141
171
|
): void {
|
|
142
|
-
if (!
|
|
172
|
+
if (!terminalBellNotificationsEnabled(ctx)) return;
|
|
143
173
|
const title = process.env.PI_TERMINAL_BELL_NOTIFY_TITLE ?? DEFAULT_NOTIFICATION_TITLE;
|
|
144
174
|
|
|
145
175
|
if (process.platform === "darwin") {
|
|
@@ -221,7 +251,7 @@ export default function terminalBell(pi: ExtensionAPI) {
|
|
|
221
251
|
}
|
|
222
252
|
|
|
223
253
|
function notifyAttention(ctx: ExtensionContext, message?: string): void {
|
|
224
|
-
if (canRingTerminal()) writeBell();
|
|
254
|
+
if (canRingTerminal(ctx)) writeBell();
|
|
225
255
|
playAttentionSound(ctx);
|
|
226
256
|
notifySessionStopped(ctx, macActivationBundleId, message);
|
|
227
257
|
pi.events.emit(TERMINAL_BELL_ATTENTION_EVENT, {
|
|
@@ -2,11 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
Local all-in-one Pi extension package.
|
|
4
4
|
|
|
5
|
-
This package keeps
|
|
5
|
+
This package keeps shared Pi tools as ordinary source folders under `src/` and registers them through one entrypoint.
|
|
6
6
|
|
|
7
7
|
- `src/ast-grep` — `ast_grep` / `ast_apply`
|
|
8
8
|
- `src/async-subagents` — `subagents` tool and sub-agent slash commands, including oh-my-openagent-style `/ultrawork` (`/ulw`) and `/hyperplan` orchestration prompts, plus config-defined sub-agent model/thinking/args presets selected via `/subagent-preset` from `asyncSubagents` in `~/.config/pi/pi-tools-suite.jsonc`; includes the `frontend` profile for Gemini-friendly UI/UX and visual frontend work plus the `vision` profile for screenshot/image description via `openai-codex/gpt-5.4-mini`; enforces a 30-minute per-agent execution timeout, project-wide `maxConcurrent` queueing, optional retry/backoff, and `result.json` structured metadata/chaining fields next to raw `result.md`; stores project-local run files and a registry under `.pi/subagents/` so result/status collection can recover after compaction or reload while the main session remains alive
|
|
9
|
-
- `src/terminal-bell` — terminal bell, macOS attention sound, and best-effort OS notification when the main agent session returns to idle; defers the alert while sub-agents are still running or the main agent is waiting for sub-agent results
|
|
10
9
|
- `src/lsp` — shared LSP diagnostics hook/library that enriches mutating tool results with diagnostics and shuts down language servers on session shutdown
|
|
11
10
|
- `src/repo-discovery` — `/idx-init`, `/idx-update`, and indexed-only `repo_architecture` / `repo_structure` / `repo_ast` / `repo_search` / `repo_explain` / `repo_deps`; tools register only when the launch project has `.indexer-cli`
|
|
12
11
|
- `src/antigravity-auth` — `antigravity` custom provider with Google Antigravity OAuth login, startup account list, `/antigravity-import` credential migration from opencode, `/antigravity-add-account` OAuth append into rotation, `/antigravity-account` status display, account rotation/failover, Antigravity plus Gemini CLI model registration, and streaming through the Cloud Code Assist unified gateway
|
|
@@ -19,7 +18,7 @@ This package keeps the former standalone extensions as ordinary source folders u
|
|
|
19
18
|
|
|
20
19
|
`index.ts` is intentionally only a thin auto-discovery shim that re-exports `src/index.ts`. There is no `pi.extensions` manifest here, so local Pi auto-discovery loads the suite once via `~/.pi/agent/extensions/pi-tools-suite/index.ts` and does not double-register tools.
|
|
21
20
|
|
|
22
|
-
Registration order is preserved in `src/index.ts`: ast-grep, async-subagents,
|
|
21
|
+
Registration order is preserved in `src/index.ts`: ast-grep, async-subagents, lsp, repo-discovery command/tool gate, antigravity-auth provider, todo, model-tools, usage, web-search, dcp, then prompt-commands. Tool metadata and active model-specific tool sets have two modes: standard and repo-aware. When `.indexer-cli` enables `repo_*`, those tools stay active ahead of overlapping lower-level aliases so the indexed discovery surface has priority.
|
|
23
22
|
|
|
24
23
|
## Disabling modules
|
|
25
24
|
|
|
@@ -27,14 +26,14 @@ Disable suite modules without editing `src/index.ts` via config or environment v
|
|
|
27
26
|
|
|
28
27
|
```jsonc
|
|
29
28
|
{
|
|
30
|
-
"disabledModules": ["
|
|
29
|
+
"disabledModules": ["web-search"]
|
|
31
30
|
}
|
|
32
31
|
```
|
|
33
32
|
|
|
34
33
|
Environment overrides are applied last:
|
|
35
34
|
|
|
36
35
|
```bash
|
|
37
|
-
PI_TOOLS_SUITE_DISABLED_MODULES=
|
|
36
|
+
PI_TOOLS_SUITE_DISABLED_MODULES=web-search pi ...
|
|
38
37
|
PI_TOOLS_SUITE_DISABLED=1 pi ... # disables all pi-tools-suite modules
|
|
39
38
|
```
|
|
40
39
|
|
|
@@ -152,50 +151,6 @@ Troubleshooting:
|
|
|
152
151
|
|
|
153
152
|
Do not send secrets, tokens, private repository text, or credential-bearing URLs through these tools; Ollama may query external web services to satisfy the request.
|
|
154
153
|
|
|
155
|
-
## Terminal bell / idle alert
|
|
156
|
-
|
|
157
|
-
`src/terminal-bell` alerts the user when the main Pi agent returns to idle. It does not alert while sub-agents are still running or while the main agent is waiting for sub-agent results.
|
|
158
|
-
|
|
159
|
-
Disable it entirely for headless runs:
|
|
160
|
-
|
|
161
|
-
```bash
|
|
162
|
-
HEADLESS=1 pi ...
|
|
163
|
-
# or
|
|
164
|
-
PI_TERMINAL_BELL_DISABLED=1 pi ...
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
Common environment options:
|
|
168
|
-
|
|
169
|
-
| Variable | Effect |
|
|
170
|
-
| --- | --- |
|
|
171
|
-
| `PI_TERMINAL_BELL=0` | Disable terminal `\x07` bell only |
|
|
172
|
-
| `PI_TERMINAL_BELL_FORCE=1` | Emit terminal bell even without TTY |
|
|
173
|
-
| `PI_TERMINAL_BELL_DELAY_MS=250` | Delay before alerting after idle |
|
|
174
|
-
| `PI_TERMINAL_BELL_SOUND=0` | Disable macOS `afplay` attention sound |
|
|
175
|
-
| `PI_TERMINAL_BELL_SOUND=Glass` | macOS sound name or absolute `.aiff` path |
|
|
176
|
-
| `PI_TERMINAL_BELL_NOTIFY=0` | Disable OS notification only |
|
|
177
|
-
| `PI_TERMINAL_BELL_NOTIFY=1` | Force OS notification even outside UI mode |
|
|
178
|
-
| `PI_TERMINAL_BELL_NOTIFY_TITLE=Pi` | Notification title |
|
|
179
|
-
| `PI_TERMINAL_BELL_NOTIFY_MESSAGE="Session stopped"` | Notification body |
|
|
180
|
-
|
|
181
|
-
macOS clickable notifications require `terminal-notifier`:
|
|
182
|
-
|
|
183
|
-
```bash
|
|
184
|
-
brew install terminal-notifier
|
|
185
|
-
```
|
|
186
|
-
|
|
187
|
-
At extension startup, the module resolves the app to activate on click from `PI_TERMINAL_BELL_NOTIFY_ACTIVATE`, `__CFBundleIdentifier`, or `TERM_PROGRAM` (Zed, iTerm2, Terminal, WezTerm, Warp, Ghostty, Kitty, Alacritty, VS Code). The resolved bundle id is passed to `terminal-notifier -activate` and to an explicit `open -b <bundleId>` click action.
|
|
188
|
-
|
|
189
|
-
macOS-specific notification options:
|
|
190
|
-
|
|
191
|
-
| Variable | Effect |
|
|
192
|
-
| --- | --- |
|
|
193
|
-
| `PI_TERMINAL_BELL_NOTIFY_ACTIVATE=dev.zed.Zed` | Override click activation bundle id |
|
|
194
|
-
| `PI_TERMINAL_BELL_NOTIFY_ACTIVATE=0` | Disable click activation |
|
|
195
|
-
| `PI_TERMINAL_BELL_NOTIFIER=/path/to/terminal-notifier` | Use a custom notifier binary |
|
|
196
|
-
| `PI_TERMINAL_BELL_NOTIFY_SENDER=1` | Also pass `-sender <bundleId>` (can break click handling on some macOS versions) |
|
|
197
|
-
| `PI_TERMINAL_BELL_NOTIFY_OSASCRIPT=1` | Use the `osascript` fallback when `terminal-notifier` is missing; clicking these notifications can open Script Editor |
|
|
198
|
-
|
|
199
154
|
## Layout
|
|
200
155
|
|
|
201
156
|
```text
|
|
@@ -206,7 +161,6 @@ pi-tools-suite/
|
|
|
206
161
|
index.ts
|
|
207
162
|
ast-grep/
|
|
208
163
|
async-subagents/
|
|
209
|
-
terminal-bell/
|
|
210
164
|
lsp/
|
|
211
165
|
repo-discovery/
|
|
212
166
|
antigravity-auth/
|