osdy-pi 1.2.0 → 1.3.0
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 +160 -30
- package/bin/osdy-pi.mjs +29 -6
- package/bin/osdy.mjs +43 -0
- package/extensions/osdy-pi/account-profiles.test.ts +111 -97
- package/extensions/osdy-pi/account-profiles.ts +91 -162
- package/extensions/osdy-pi/animation.test.ts +84 -0
- package/extensions/osdy-pi/animation.ts +17 -8
- package/extensions/osdy-pi/codex-usage-ui.test.ts +1145 -0
- package/extensions/osdy-pi/codex-usage-ui.ts +728 -0
- package/extensions/osdy-pi/codex-usage.test.ts +276 -0
- package/extensions/osdy-pi/codex-usage.ts +276 -0
- package/extensions/osdy-pi/constants.ts +134 -34
- package/extensions/osdy-pi/context-usage-ui.test.ts +168 -0
- package/extensions/osdy-pi/context-usage-ui.ts +60 -0
- package/extensions/osdy-pi/editor-settings.test.ts +121 -23
- package/extensions/osdy-pi/editor-settings.ts +39 -3
- package/extensions/osdy-pi/metrics.test.ts +95 -0
- package/extensions/osdy-pi/metrics.ts +39 -1
- package/extensions/osdy-pi/profile-label.ts +2 -2
- package/extensions/osdy-pi/runtime-helpers.test.ts +220 -1
- package/extensions/osdy-pi/runtime-helpers.ts +42 -19
- package/extensions/osdy-pi/runtime.test.ts +355 -7
- package/extensions/osdy-pi/runtime.ts +344 -66
- package/extensions/osdy-pi/types.ts +26 -1
- package/extensions/osdy-pi/ui.test.ts +128 -0
- package/extensions/osdy-pi/ui.ts +57 -40
- package/package.json +5 -2
- package/scripts/osdy-pi-account-profiles.mjs +521 -76
- package/scripts/osdy-pi-gentle-coexistence.mjs +200 -0
- package/scripts/osdy-pi-profile-setup.mjs +175 -0
- package/themes/osdy-pi-catppuccin-frappe.json +6 -2
- package/themes/osdy-pi-catppuccin-latte.json +6 -2
- package/themes/osdy-pi-catppuccin-macchiato.json +6 -2
- package/themes/osdy-pi-catppuccin-mocha.json +6 -2
- package/themes/osdy-pi-dark.json +6 -2
- package/themes/osdy-pi-dracula.json +6 -2
- package/themes/osdy-pi-kanagawa-dragon.json +6 -2
- package/themes/osdy-pi-kanagawa-lotus.json +6 -2
- package/themes/osdy-pi-kanagawa-wave.json +6 -2
- package/themes/osdy-pi-lucent-orange.json +6 -2
- package/themes/osdy-pi-matrix.json +6 -2
- package/themes/osdy-pi-new.json +6 -2
- package/themes/osdy-pi-sexy.json +6 -2
- package/themes/osdy-pi-tokyo-night.json +6 -2
|
@@ -12,17 +12,22 @@ import { createEditorSettingsStore } from "./editor-settings.js";
|
|
|
12
12
|
import { registerAccountProfilesCommand } from "./account-profiles.js";
|
|
13
13
|
import {
|
|
14
14
|
applyOsdyPi,
|
|
15
|
+
clearGentleShellChangesWidget,
|
|
15
16
|
createResponsiveCoordinator,
|
|
16
17
|
disableOsdyPi,
|
|
17
18
|
notifyStatus,
|
|
18
19
|
reconcileResponsiveUi,
|
|
20
|
+
syncWorkingTreeWidget,
|
|
19
21
|
} from "./runtime-helpers.js";
|
|
20
22
|
import { runSoundSetupWizard } from "./sound-setup-wizard.js";
|
|
21
23
|
import {
|
|
22
24
|
DEFAULT_EDITOR_MODE,
|
|
23
25
|
EDITOR_MODES,
|
|
26
|
+
HEADER_VARIANT_CHOICES,
|
|
24
27
|
type EditorMode,
|
|
25
28
|
type HeaderVariant,
|
|
29
|
+
MASCOT_CHOICES,
|
|
30
|
+
type MascotChoice,
|
|
26
31
|
type OsdyState,
|
|
27
32
|
type WorkingTreeState,
|
|
28
33
|
type WorkingWidgetState,
|
|
@@ -34,6 +39,15 @@ import {
|
|
|
34
39
|
shouldRefreshWorkingTree,
|
|
35
40
|
} from "./working-tree.js";
|
|
36
41
|
import { showWorkingTreeDiffPanel } from "./diff-panel.js";
|
|
42
|
+
import {
|
|
43
|
+
extractCodexAccountId,
|
|
44
|
+
fetchCodexUsage,
|
|
45
|
+
type CodexUsageAuth,
|
|
46
|
+
type CodexUsageSnapshot,
|
|
47
|
+
} from "./codex-usage.js";
|
|
48
|
+
import { showCodexUsagePanel } from "./codex-usage-ui.js";
|
|
49
|
+
import { modelLabel } from "./metrics.js";
|
|
50
|
+
import { resolveActiveProfileLabel } from "./profile-label.js";
|
|
37
51
|
import {
|
|
38
52
|
createWorkingController,
|
|
39
53
|
type WorkingController,
|
|
@@ -86,10 +100,35 @@ function parseCommandArgs(args: string): string[] {
|
|
|
86
100
|
return args.trim().split(/\s+/).filter(Boolean);
|
|
87
101
|
}
|
|
88
102
|
|
|
89
|
-
|
|
103
|
+
function isMascotChoice(value: string): value is MascotChoice {
|
|
104
|
+
return MASCOT_CHOICES.includes(value as MascotChoice);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function mascotLabel(mascot: MascotChoice): string {
|
|
108
|
+
return mascot === "bts" ? "Bts" : mascot;
|
|
109
|
+
}
|
|
90
110
|
|
|
91
111
|
function isHeaderVariant(value: string): value is HeaderVariant {
|
|
92
|
-
return
|
|
112
|
+
return HEADER_VARIANT_CHOICES.includes(value as HeaderVariant);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function saveVisualSettings(
|
|
116
|
+
state: OsdyState,
|
|
117
|
+
settingsStore: ReturnType<typeof createEditorSettingsStore>,
|
|
118
|
+
): Promise<boolean> {
|
|
119
|
+
try {
|
|
120
|
+
await settingsStore.save({
|
|
121
|
+
version: 1,
|
|
122
|
+
enabled: state.enabled,
|
|
123
|
+
editorMode: state.editorMode,
|
|
124
|
+
workingTreeEnabled: state.workingTreeEnabled,
|
|
125
|
+
headerVariant: state.headerVariant,
|
|
126
|
+
mascot: state.mascot,
|
|
127
|
+
});
|
|
128
|
+
return true;
|
|
129
|
+
} catch {
|
|
130
|
+
return false;
|
|
131
|
+
}
|
|
93
132
|
}
|
|
94
133
|
|
|
95
134
|
async function enableOsdyPi(
|
|
@@ -99,55 +138,124 @@ async function enableOsdyPi(
|
|
|
99
138
|
workingState: WorkingWidgetState,
|
|
100
139
|
workingTreeState: WorkingTreeState,
|
|
101
140
|
controller: WorkingController,
|
|
141
|
+
settingsStore: ReturnType<typeof createEditorSettingsStore>,
|
|
102
142
|
): Promise<void> {
|
|
103
143
|
state.enabled = true;
|
|
104
|
-
applyOsdyPi(pi, ctx, state, workingState, workingTreeState
|
|
144
|
+
applyOsdyPi(pi, ctx, state, workingState, workingTreeState);
|
|
105
145
|
controller.refreshWorking();
|
|
146
|
+
const saved = await saveVisualSettings(state, settingsStore);
|
|
106
147
|
if (state.workingTreeEnabled)
|
|
107
148
|
await refreshWorkingTree(pi, ctx, workingTreeState);
|
|
149
|
+
ctx.ui.notify(
|
|
150
|
+
saved ? "osdy-pi enabled" : "osdy-pi enabled but could not be saved",
|
|
151
|
+
saved ? "info" : "warning",
|
|
152
|
+
);
|
|
108
153
|
}
|
|
109
154
|
|
|
110
|
-
function disableOsdyPiCommand(
|
|
155
|
+
async function disableOsdyPiCommand(
|
|
111
156
|
ctx: ExtensionContext,
|
|
112
157
|
state: OsdyState,
|
|
113
158
|
workingTreeState: WorkingTreeState,
|
|
114
159
|
controller: WorkingController,
|
|
115
160
|
stopResponsive: () => void,
|
|
116
|
-
|
|
161
|
+
settingsStore: ReturnType<typeof createEditorSettingsStore>,
|
|
162
|
+
): Promise<void> {
|
|
117
163
|
stopResponsive();
|
|
118
164
|
state.enabled = false;
|
|
119
165
|
workingTreeState.visible = false;
|
|
120
166
|
controller.stopWorking();
|
|
121
167
|
clearWorkingTree(workingTreeState);
|
|
122
168
|
disableOsdyPi(ctx, state);
|
|
169
|
+
const saved = await saveVisualSettings(state, settingsStore);
|
|
170
|
+
ctx.ui.notify(
|
|
171
|
+
saved ? "osdy-pi disabled" : "osdy-pi disabled but could not be saved",
|
|
172
|
+
saved ? "info" : "warning",
|
|
173
|
+
);
|
|
123
174
|
}
|
|
124
175
|
|
|
125
|
-
function
|
|
176
|
+
async function handleMascotCommand(
|
|
177
|
+
action: string | undefined,
|
|
178
|
+
rest: string[],
|
|
126
179
|
pi: ExtensionAPI,
|
|
127
180
|
ctx: ExtensionContext,
|
|
128
181
|
state: OsdyState,
|
|
129
182
|
workingState: WorkingWidgetState,
|
|
130
183
|
workingTreeState: WorkingTreeState,
|
|
131
|
-
|
|
132
|
-
): void {
|
|
133
|
-
|
|
184
|
+
settingsStore: ReturnType<typeof createEditorSettingsStore>,
|
|
185
|
+
): Promise<void> {
|
|
186
|
+
const mascotUsage = `Usage: /osdy-pi mascot ${MASCOT_CHOICES.join(" | ")} | status`;
|
|
187
|
+
if (rest.length > 0) {
|
|
188
|
+
ctx.ui.notify(mascotUsage, "warning");
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
if (action === "status" || action === undefined) {
|
|
192
|
+
ctx.ui.notify(`osdy-pi mascot: ${mascotLabel(state.mascot)}`, "info");
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
if (!isMascotChoice(action)) {
|
|
196
|
+
ctx.ui.notify(mascotUsage, "warning");
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
state.mascot = action;
|
|
200
|
+
if (state.enabled) applyOsdyPi(pi, ctx, state, workingState, workingTreeState);
|
|
201
|
+
const saved = await saveVisualSettings(state, settingsStore);
|
|
202
|
+
ctx.ui.notify(
|
|
203
|
+
saved
|
|
204
|
+
? `osdy-pi mascot: ${mascotLabel(action)}`
|
|
205
|
+
: "osdy-pi mascot changed but could not be saved",
|
|
206
|
+
saved ? "info" : "warning",
|
|
207
|
+
);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
async function handleHeaderCommand(
|
|
211
|
+
action: string | undefined,
|
|
212
|
+
rest: string[],
|
|
213
|
+
pi: ExtensionAPI,
|
|
214
|
+
ctx: ExtensionContext,
|
|
215
|
+
state: OsdyState,
|
|
216
|
+
workingState: WorkingWidgetState,
|
|
217
|
+
workingTreeState: WorkingTreeState,
|
|
218
|
+
settingsStore: ReturnType<typeof createEditorSettingsStore>,
|
|
219
|
+
): Promise<void> {
|
|
220
|
+
const headerUsage = `Usage: /osdy-pi header ${HEADER_VARIANT_CHOICES.join(" | ")} | status`;
|
|
221
|
+
if (
|
|
222
|
+
rest.length > 0 ||
|
|
223
|
+
(action !== undefined && action !== "status" && !isHeaderVariant(action))
|
|
224
|
+
) {
|
|
225
|
+
ctx.ui.notify(headerUsage, "warning");
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
if (action === "status" || action === undefined) {
|
|
229
|
+
ctx.ui.notify(`osdy-pi header: ${state.headerVariant}`, "info");
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
232
|
+
state.headerVariant = action;
|
|
134
233
|
if (state.enabled) applyOsdyPi(pi, ctx, state, workingState, workingTreeState);
|
|
135
|
-
|
|
234
|
+
const saved = await saveVisualSettings(state, settingsStore);
|
|
235
|
+
ctx.ui.notify(
|
|
236
|
+
saved
|
|
237
|
+
? `osdy-pi header: ${action}`
|
|
238
|
+
: "osdy-pi header changed but could not be saved",
|
|
239
|
+
saved ? "info" : "warning",
|
|
240
|
+
);
|
|
136
241
|
}
|
|
137
242
|
|
|
138
|
-
function getOsdyCommandCompletions(prefix: string) {
|
|
243
|
+
export function getOsdyCommandCompletions(prefix: string) {
|
|
139
244
|
const trimmed = prefix.trim();
|
|
140
245
|
const parts = parseCommandArgs(trimmed);
|
|
141
246
|
if (parts.length === 0) {
|
|
142
247
|
return [
|
|
143
248
|
"enable",
|
|
144
249
|
"disable",
|
|
250
|
+
"on",
|
|
251
|
+
"off",
|
|
145
252
|
"status",
|
|
146
253
|
"sound",
|
|
147
254
|
"working-tree",
|
|
148
255
|
"editor",
|
|
256
|
+
"mascot",
|
|
257
|
+
"header",
|
|
149
258
|
"diff",
|
|
150
|
-
...HEADER_VARIANTS,
|
|
151
259
|
].map((value) => ({ value, label: value }));
|
|
152
260
|
}
|
|
153
261
|
if (trimmed === "sound") {
|
|
@@ -163,6 +271,18 @@ function getOsdyCommandCompletions(prefix: string) {
|
|
|
163
271
|
"working-tree position bottom",
|
|
164
272
|
].map((value) => ({ value, label: value }));
|
|
165
273
|
}
|
|
274
|
+
if (trimmed === "mascot") {
|
|
275
|
+
return [
|
|
276
|
+
...MASCOT_CHOICES.map((choice) => `mascot ${choice}`),
|
|
277
|
+
"mascot status",
|
|
278
|
+
].map((value) => ({ value, label: value }));
|
|
279
|
+
}
|
|
280
|
+
if (trimmed === "header") {
|
|
281
|
+
return [
|
|
282
|
+
...HEADER_VARIANT_CHOICES.map((choice) => `header ${choice}`),
|
|
283
|
+
"header status",
|
|
284
|
+
].map((value) => ({ value, label: value }));
|
|
285
|
+
}
|
|
166
286
|
if (trimmed === "editor") {
|
|
167
287
|
return [
|
|
168
288
|
"editor auto",
|
|
@@ -179,12 +299,15 @@ function getOsdyCommandCompletions(prefix: string) {
|
|
|
179
299
|
return [
|
|
180
300
|
"enable",
|
|
181
301
|
"disable",
|
|
302
|
+
"on",
|
|
303
|
+
"off",
|
|
182
304
|
"status",
|
|
183
305
|
"sound",
|
|
184
306
|
"working-tree",
|
|
185
307
|
"editor",
|
|
308
|
+
"mascot",
|
|
309
|
+
"header",
|
|
186
310
|
"diff",
|
|
187
|
-
...HEADER_VARIANTS,
|
|
188
311
|
]
|
|
189
312
|
.filter((value) => value.startsWith(valuePrefix))
|
|
190
313
|
.map((value) => ({ value, label: value }));
|
|
@@ -195,6 +318,18 @@ function getOsdyCommandCompletions(prefix: string) {
|
|
|
195
318
|
.filter((value) => value.startsWith(valuePrefix))
|
|
196
319
|
.map((value) => ({ value: `sound ${value}`, label: `sound ${value}` }));
|
|
197
320
|
}
|
|
321
|
+
if (parts[0] === "header" && parts.length === 2) {
|
|
322
|
+
const valuePrefix = parts[1] ?? "";
|
|
323
|
+
return [...HEADER_VARIANT_CHOICES, "status"]
|
|
324
|
+
.filter((value) => value.startsWith(valuePrefix))
|
|
325
|
+
.map((value) => ({ value: `header ${value}`, label: `header ${value}` }));
|
|
326
|
+
}
|
|
327
|
+
if (parts[0] === "mascot" && parts.length === 2) {
|
|
328
|
+
const valuePrefix = parts[1] ?? "";
|
|
329
|
+
return [...MASCOT_CHOICES, "status"]
|
|
330
|
+
.filter((value) => value.startsWith(valuePrefix))
|
|
331
|
+
.map((value) => ({ value: `mascot ${value}`, label: `mascot ${value}` }));
|
|
332
|
+
}
|
|
198
333
|
if (parts[0] === "editor" && parts.length === 2) {
|
|
199
334
|
const valuePrefix = parts[1] ?? "";
|
|
200
335
|
return ["auto", "extended", "simple", "on", "off", "toggle", "status"]
|
|
@@ -287,19 +422,13 @@ async function setEditorMode(
|
|
|
287
422
|
): Promise<void> {
|
|
288
423
|
state.editorMode = mode;
|
|
289
424
|
if (state.enabled) reconcileResponsiveUi(pi, ctx, state, workingTreeState);
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
} catch {
|
|
298
|
-
ctx.ui.notify(
|
|
299
|
-
"osdy-pi editor mode changed but could not be saved",
|
|
300
|
-
"warning",
|
|
301
|
-
);
|
|
302
|
-
}
|
|
425
|
+
const saved = await saveVisualSettings(state, settingsStore);
|
|
426
|
+
ctx.ui.notify(
|
|
427
|
+
saved
|
|
428
|
+
? `osdy-pi editor mode: ${mode}`
|
|
429
|
+
: "osdy-pi editor mode changed but could not be saved",
|
|
430
|
+
saved ? "info" : "warning",
|
|
431
|
+
);
|
|
303
432
|
}
|
|
304
433
|
|
|
305
434
|
async function handleEditorCommand(
|
|
@@ -360,16 +489,7 @@ async function saveWorkingTreePreference(
|
|
|
360
489
|
state: OsdyState,
|
|
361
490
|
settingsStore: ReturnType<typeof createEditorSettingsStore>,
|
|
362
491
|
): Promise<boolean> {
|
|
363
|
-
|
|
364
|
-
await settingsStore.save({
|
|
365
|
-
version: 1,
|
|
366
|
-
editorMode: state.editorMode,
|
|
367
|
-
workingTreeEnabled: state.workingTreeEnabled,
|
|
368
|
-
});
|
|
369
|
-
return true;
|
|
370
|
-
} catch {
|
|
371
|
-
return false;
|
|
372
|
-
}
|
|
492
|
+
return saveVisualSettings(state, settingsStore);
|
|
373
493
|
}
|
|
374
494
|
|
|
375
495
|
async function handleWorkingTreeCommand(
|
|
@@ -396,6 +516,7 @@ async function handleWorkingTreeCommand(
|
|
|
396
516
|
if (action === "on") {
|
|
397
517
|
state.workingTreeEnabled = true;
|
|
398
518
|
workingTreeState.enabled = true;
|
|
519
|
+
syncWorkingTreeWidget(ctx, state, workingState, workingTreeState);
|
|
399
520
|
reconcileResponsiveUi(pi, ctx, state, workingTreeState);
|
|
400
521
|
const saved = await saveWorkingTreePreference(state, settingsStore);
|
|
401
522
|
await refreshWorkingTree(pi, ctx, workingTreeState);
|
|
@@ -410,6 +531,7 @@ async function handleWorkingTreeCommand(
|
|
|
410
531
|
if (action === "off") {
|
|
411
532
|
state.workingTreeEnabled = false;
|
|
412
533
|
workingTreeState.enabled = false;
|
|
534
|
+
syncWorkingTreeWidget(ctx, state, workingState, workingTreeState);
|
|
413
535
|
reconcileResponsiveUi(pi, ctx, state, workingTreeState);
|
|
414
536
|
clearWorkingTree(workingTreeState);
|
|
415
537
|
const saved = await saveWorkingTreePreference(state, settingsStore);
|
|
@@ -481,6 +603,95 @@ async function openDiffCommand(
|
|
|
481
603
|
}
|
|
482
604
|
}
|
|
483
605
|
|
|
606
|
+
type CodexUsageRefreshContext = {
|
|
607
|
+
modelRegistry: {
|
|
608
|
+
getProviderAuth(
|
|
609
|
+
provider: string,
|
|
610
|
+
): Promise<{ auth: { apiKey?: string } } | undefined>;
|
|
611
|
+
};
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
type CodexUsageRefreshState = {
|
|
615
|
+
codexUsage: OsdyState["codexUsage"];
|
|
616
|
+
tui?: { requestRender(): void } | undefined;
|
|
617
|
+
};
|
|
618
|
+
|
|
619
|
+
type CodexUsageFetcher = (
|
|
620
|
+
auth: CodexUsageAuth,
|
|
621
|
+
options: { signal: AbortSignal },
|
|
622
|
+
) => Promise<CodexUsageSnapshot>;
|
|
623
|
+
|
|
624
|
+
export async function refreshCodexUsage(
|
|
625
|
+
ctx: CodexUsageRefreshContext,
|
|
626
|
+
state: CodexUsageRefreshState,
|
|
627
|
+
abort: AbortController,
|
|
628
|
+
fetchUsage: CodexUsageFetcher = fetchCodexUsage,
|
|
629
|
+
): Promise<void> {
|
|
630
|
+
state.codexUsage = { kind: "loading", snapshot: undefined };
|
|
631
|
+
state.tui?.requestRender();
|
|
632
|
+
try {
|
|
633
|
+
const providerAuth = await ctx.modelRegistry.getProviderAuth("openai-codex");
|
|
634
|
+
const accessToken = providerAuth?.auth.apiKey;
|
|
635
|
+
if (!accessToken) throw new Error("Codex login is required");
|
|
636
|
+
const snapshot = await fetchUsage(
|
|
637
|
+
{ accessToken, accountId: extractCodexAccountId(accessToken) },
|
|
638
|
+
{ signal: abort.signal },
|
|
639
|
+
);
|
|
640
|
+
if (abort.signal.aborted) return;
|
|
641
|
+
state.codexUsage = { kind: "ready", snapshot };
|
|
642
|
+
} catch (error) {
|
|
643
|
+
if (abort.signal.aborted) return;
|
|
644
|
+
state.codexUsage = {
|
|
645
|
+
kind: "error",
|
|
646
|
+
message:
|
|
647
|
+
error instanceof Error ? error.message : "Codex usage is unavailable",
|
|
648
|
+
snapshot: undefined,
|
|
649
|
+
};
|
|
650
|
+
}
|
|
651
|
+
state.tui?.requestRender();
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
export function createActiveSessionRefresh<T>(
|
|
655
|
+
getSessionContext: () => T | undefined,
|
|
656
|
+
startRefresh: (context: T) => Promise<void>,
|
|
657
|
+
): () => Promise<void> {
|
|
658
|
+
return async () => {
|
|
659
|
+
const activeSessionContext = getSessionContext();
|
|
660
|
+
if (activeSessionContext) await startRefresh(activeSessionContext);
|
|
661
|
+
};
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function registerUsageCommand(
|
|
665
|
+
pi: ExtensionAPI,
|
|
666
|
+
state: OsdyState,
|
|
667
|
+
getSessionContext: () => ExtensionContext | undefined,
|
|
668
|
+
startRefresh: (ctx: ExtensionContext) => Promise<void>,
|
|
669
|
+
): void {
|
|
670
|
+
const refreshActiveSessionUsage = createActiveSessionRefresh(
|
|
671
|
+
getSessionContext,
|
|
672
|
+
startRefresh,
|
|
673
|
+
);
|
|
674
|
+
pi.registerCommand("usage", {
|
|
675
|
+
description: "Show current Codex subscription usage.",
|
|
676
|
+
handler: async (_args, ctx) => {
|
|
677
|
+
if (!ctx.hasUI) {
|
|
678
|
+
ctx.ui.notify("Codex usage requires the interactive UI", "warning");
|
|
679
|
+
return;
|
|
680
|
+
}
|
|
681
|
+
await showCodexUsagePanel(
|
|
682
|
+
ctx,
|
|
683
|
+
() => state.codexUsage,
|
|
684
|
+
refreshActiveSessionUsage,
|
|
685
|
+
{
|
|
686
|
+
profile: resolveActiveProfileLabel(),
|
|
687
|
+
provider: ctx.model?.provider ?? "unknown",
|
|
688
|
+
model: modelLabel(ctx),
|
|
689
|
+
},
|
|
690
|
+
);
|
|
691
|
+
},
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
|
|
484
695
|
function registerCommand(
|
|
485
696
|
pi: ExtensionAPI,
|
|
486
697
|
state: OsdyState,
|
|
@@ -494,11 +705,11 @@ function registerCommand(
|
|
|
494
705
|
): void {
|
|
495
706
|
pi.registerCommand("osdy-pi", {
|
|
496
707
|
description:
|
|
497
|
-
"Manage the Osdy Pi experience: enable
|
|
708
|
+
"Manage the Osdy Pi experience: enable/disable (on/off), status, mascot, editor, style, sound setup, working tree, or diff panel.",
|
|
498
709
|
getArgumentCompletions: getOsdyCommandCompletions,
|
|
499
710
|
handler: async (args, ctx) => {
|
|
500
711
|
const [action = "status", ...rest] = parseCommandArgs(args);
|
|
501
|
-
if (
|
|
712
|
+
if (["enable", "on"].includes(action)) {
|
|
502
713
|
await enableOsdyPi(
|
|
503
714
|
pi,
|
|
504
715
|
ctx,
|
|
@@ -506,17 +717,19 @@ function registerCommand(
|
|
|
506
717
|
workingState,
|
|
507
718
|
workingTreeState,
|
|
508
719
|
controller,
|
|
720
|
+
editorSettingsStore,
|
|
509
721
|
);
|
|
510
722
|
startResponsive();
|
|
511
723
|
return;
|
|
512
724
|
}
|
|
513
|
-
if (
|
|
514
|
-
disableOsdyPiCommand(
|
|
725
|
+
if (["disable", "off"].includes(action)) {
|
|
726
|
+
await disableOsdyPiCommand(
|
|
515
727
|
ctx,
|
|
516
728
|
state,
|
|
517
729
|
workingTreeState,
|
|
518
730
|
controller,
|
|
519
731
|
stopResponsive,
|
|
732
|
+
editorSettingsStore,
|
|
520
733
|
);
|
|
521
734
|
return;
|
|
522
735
|
}
|
|
@@ -528,6 +741,32 @@ function registerCommand(
|
|
|
528
741
|
await handleSoundCommand(rest, ctx, settingsStore);
|
|
529
742
|
return;
|
|
530
743
|
}
|
|
744
|
+
if (action === "mascot") {
|
|
745
|
+
await handleMascotCommand(
|
|
746
|
+
rest[0],
|
|
747
|
+
rest.slice(1),
|
|
748
|
+
pi,
|
|
749
|
+
ctx,
|
|
750
|
+
state,
|
|
751
|
+
workingState,
|
|
752
|
+
workingTreeState,
|
|
753
|
+
editorSettingsStore,
|
|
754
|
+
);
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (action === "header") {
|
|
758
|
+
await handleHeaderCommand(
|
|
759
|
+
rest[0],
|
|
760
|
+
rest.slice(1),
|
|
761
|
+
pi,
|
|
762
|
+
ctx,
|
|
763
|
+
state,
|
|
764
|
+
workingState,
|
|
765
|
+
workingTreeState,
|
|
766
|
+
editorSettingsStore,
|
|
767
|
+
);
|
|
768
|
+
return;
|
|
769
|
+
}
|
|
531
770
|
if (action === "editor") {
|
|
532
771
|
await handleEditorCommand(
|
|
533
772
|
rest[0],
|
|
@@ -557,34 +796,23 @@ function registerCommand(
|
|
|
557
796
|
await openDiffCommand(pi, ctx, state, workingState, workingTreeState);
|
|
558
797
|
return;
|
|
559
798
|
}
|
|
560
|
-
if (isHeaderVariant(action)) {
|
|
561
|
-
setHeaderVariant(pi, ctx, state, workingState, workingTreeState, action);
|
|
562
|
-
return;
|
|
563
|
-
}
|
|
564
799
|
ctx.ui.notify(
|
|
565
|
-
|
|
800
|
+
`Usage: /osdy-pi enable|disable|on|off|status | mascot ${MASCOT_CHOICES.join("|")}|status | header ${HEADER_VARIANT_CHOICES.join("|")}|status | editor auto|extended|simple|on|off|toggle|status | sound setup | working-tree ... | diff`,
|
|
566
801
|
"warning",
|
|
567
802
|
);
|
|
568
803
|
},
|
|
569
804
|
});
|
|
570
|
-
|
|
571
|
-
for (const variant of HEADER_VARIANTS) {
|
|
572
|
-
pi.registerCommand(`osdy-pi-${variant}`, {
|
|
573
|
-
description: `Switch Osdy Pi header to ${variant}.`,
|
|
574
|
-
handler: (_args, ctx) => {
|
|
575
|
-
setHeaderVariant(pi, ctx, state, workingState, workingTreeState, variant);
|
|
576
|
-
return Promise.resolve();
|
|
577
|
-
},
|
|
578
|
-
});
|
|
579
|
-
}
|
|
580
805
|
}
|
|
581
806
|
|
|
582
807
|
export function registerOsdyPi(pi: ExtensionAPI): void {
|
|
583
808
|
const state: OsdyState = {
|
|
809
|
+
codexUsage: { kind: "idle" },
|
|
584
810
|
enabled: true,
|
|
585
811
|
editorEffective: false,
|
|
586
812
|
editorMode: DEFAULT_EDITOR_MODE,
|
|
813
|
+
fallbackEditorFactory: undefined,
|
|
587
814
|
headerVariant: "osdy-theme",
|
|
815
|
+
mascot: "current",
|
|
588
816
|
smallMode: false,
|
|
589
817
|
tui: undefined,
|
|
590
818
|
workingTreeEnabled: true,
|
|
@@ -609,7 +837,6 @@ export function registerOsdyPi(pi: ExtensionAPI): void {
|
|
|
609
837
|
const settingsStore = createAudioSoundSettingsStore();
|
|
610
838
|
const editorSettingsStore = createEditorSettingsStore();
|
|
611
839
|
registerAudioNotificationFlags(pi);
|
|
612
|
-
registerAccountProfilesCommand(pi);
|
|
613
840
|
const audioRouter = createAudioEventRouter(
|
|
614
841
|
createAudioNotificationService(
|
|
615
842
|
pi,
|
|
@@ -621,12 +848,42 @@ export function registerOsdyPi(pi: ExtensionAPI): void {
|
|
|
621
848
|
| ReturnType<typeof createResponsiveCoordinator>
|
|
622
849
|
| undefined;
|
|
623
850
|
let sessionContext: ExtensionContext | undefined;
|
|
851
|
+
let codexUsageAbort: AbortController | undefined;
|
|
852
|
+
const refreshCurrentCodexUsage = async (
|
|
853
|
+
ctx: ExtensionContext,
|
|
854
|
+
): Promise<void> => {
|
|
855
|
+
codexUsageAbort?.abort();
|
|
856
|
+
const abort = new AbortController();
|
|
857
|
+
codexUsageAbort = abort;
|
|
858
|
+
await refreshCodexUsage(ctx, state, abort);
|
|
859
|
+
if (codexUsageAbort === abort) codexUsageAbort = undefined;
|
|
860
|
+
};
|
|
861
|
+
registerAccountProfilesCommand(pi, {
|
|
862
|
+
requestRender: () => {
|
|
863
|
+
state.tui?.requestRender();
|
|
864
|
+
},
|
|
865
|
+
refreshUsage: async () => {
|
|
866
|
+
const activeSessionContext = sessionContext;
|
|
867
|
+
if (activeSessionContext)
|
|
868
|
+
await refreshCurrentCodexUsage(activeSessionContext);
|
|
869
|
+
},
|
|
870
|
+
});
|
|
624
871
|
const pendingOsdyRefreshes = new Set<ReturnType<typeof setTimeout>>();
|
|
625
872
|
const cancelOsdyRefreshes = (): void => {
|
|
626
873
|
for (const timeout of pendingOsdyRefreshes) clearTimeout(timeout);
|
|
627
874
|
pendingOsdyRefreshes.clear();
|
|
628
875
|
};
|
|
629
|
-
const startResponsive = (): void =>
|
|
876
|
+
const startResponsive = (): void => {
|
|
877
|
+
if (!responsiveCoordinator && sessionContext) {
|
|
878
|
+
responsiveCoordinator = createResponsiveCoordinator(
|
|
879
|
+
pi,
|
|
880
|
+
sessionContext,
|
|
881
|
+
state,
|
|
882
|
+
workingTreeState,
|
|
883
|
+
);
|
|
884
|
+
}
|
|
885
|
+
responsiveCoordinator?.start();
|
|
886
|
+
};
|
|
630
887
|
const stopResponsive = (): void => responsiveCoordinator?.stop();
|
|
631
888
|
|
|
632
889
|
subscribeQuestionPromptAudioNotification(
|
|
@@ -642,6 +899,17 @@ export function registerOsdyPi(pi: ExtensionAPI): void {
|
|
|
642
899
|
pi.on("agent_end", (_event, ctx) => {
|
|
643
900
|
controller.onAgentEnd();
|
|
644
901
|
audioRouter.onAgentEnd(ctx);
|
|
902
|
+
if (state.enabled) clearGentleShellChangesWidget(ctx);
|
|
903
|
+
});
|
|
904
|
+
pi.on("agent_settled", () => {
|
|
905
|
+
const activeSessionContext = sessionContext;
|
|
906
|
+
if (
|
|
907
|
+
state.enabled &&
|
|
908
|
+
activeSessionContext &&
|
|
909
|
+
activeSessionContext.model?.provider === "openai-codex"
|
|
910
|
+
) {
|
|
911
|
+
void refreshCurrentCodexUsage(activeSessionContext);
|
|
912
|
+
}
|
|
645
913
|
});
|
|
646
914
|
pi.on("tool_execution_start", (event) =>
|
|
647
915
|
controller.onToolStart(event.toolName),
|
|
@@ -649,7 +917,9 @@ export function registerOsdyPi(pi: ExtensionAPI): void {
|
|
|
649
917
|
pi.on("tool_execution_end", (event, ctx) => {
|
|
650
918
|
controller.onToolEnd();
|
|
651
919
|
audioRouter.onToolExecutionEnd(event.isError === true, ctx);
|
|
920
|
+
if (state.enabled) clearGentleShellChangesWidget(ctx);
|
|
652
921
|
if (
|
|
922
|
+
state.enabled &&
|
|
653
923
|
state.workingTreeEnabled &&
|
|
654
924
|
event.isError !== true &&
|
|
655
925
|
shouldRefreshWorkingTree(event.toolName)
|
|
@@ -658,10 +928,12 @@ export function registerOsdyPi(pi: ExtensionAPI): void {
|
|
|
658
928
|
}
|
|
659
929
|
});
|
|
660
930
|
pi.on("session_shutdown", () => {
|
|
931
|
+
codexUsageAbort?.abort();
|
|
932
|
+
codexUsageAbort = undefined;
|
|
933
|
+
state.codexUsage = { kind: "idle" };
|
|
661
934
|
cancelOsdyRefreshes();
|
|
662
935
|
controller.onShutdown();
|
|
663
936
|
stopResponsive();
|
|
664
|
-
if (sessionContext) disableOsdyPi(sessionContext, state);
|
|
665
937
|
workingTreeState.tui = undefined;
|
|
666
938
|
sessionContext = undefined;
|
|
667
939
|
});
|
|
@@ -670,10 +942,15 @@ export function registerOsdyPi(pi: ExtensionAPI): void {
|
|
|
670
942
|
stopResponsive();
|
|
671
943
|
sessionContext = ctx;
|
|
672
944
|
const editorSettings = await editorSettingsStore.load();
|
|
945
|
+
if (sessionContext !== ctx) return;
|
|
946
|
+
state.fallbackEditorFactory = ctx.ui.getEditorComponent();
|
|
947
|
+
state.enabled = editorSettings.enabled;
|
|
673
948
|
state.editorMode = editorSettings.editorMode;
|
|
674
949
|
state.workingTreeEnabled = editorSettings.workingTreeEnabled;
|
|
950
|
+
state.headerVariant = editorSettings.headerVariant;
|
|
951
|
+
state.mascot = editorSettings.mascot;
|
|
675
952
|
workingTreeState.enabled = editorSettings.workingTreeEnabled;
|
|
676
|
-
if (
|
|
953
|
+
if (!state.enabled) return;
|
|
677
954
|
claimOsdyVisualLayer(
|
|
678
955
|
pi,
|
|
679
956
|
ctx,
|
|
@@ -683,18 +960,19 @@ export function registerOsdyPi(pi: ExtensionAPI): void {
|
|
|
683
960
|
pendingOsdyRefreshes,
|
|
684
961
|
() => sessionContext === ctx,
|
|
685
962
|
);
|
|
686
|
-
|
|
687
|
-
pi,
|
|
688
|
-
ctx,
|
|
689
|
-
state,
|
|
690
|
-
workingTreeState,
|
|
691
|
-
);
|
|
692
|
-
responsiveCoordinator.start();
|
|
963
|
+
startResponsive();
|
|
693
964
|
if (state.workingTreeEnabled) {
|
|
694
965
|
void refreshWorkingTree(pi, ctx, workingTreeState);
|
|
695
966
|
}
|
|
967
|
+
void refreshCurrentCodexUsage(ctx);
|
|
696
968
|
});
|
|
697
969
|
|
|
970
|
+
registerUsageCommand(
|
|
971
|
+
pi,
|
|
972
|
+
state,
|
|
973
|
+
() => sessionContext,
|
|
974
|
+
refreshCurrentCodexUsage,
|
|
975
|
+
);
|
|
698
976
|
registerCommand(
|
|
699
977
|
pi,
|
|
700
978
|
state,
|