@gajae-code/coding-agent 0.7.7 → 0.7.9
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/CHANGELOG.md +38 -1
- package/README.md +14 -0
- package/dist/types/cli/local-provider-smoke.d.ts +59 -0
- package/dist/types/cli/notify-cli.d.ts +1 -1
- package/dist/types/cli/update-cli.d.ts +24 -1
- package/dist/types/commands/local-provider.d.ts +34 -0
- package/dist/types/config/keybindings.d.ts +1 -1
- package/dist/types/config/model-profile-activation.d.ts +19 -2
- package/dist/types/config/model-profiles.d.ts +1 -0
- package/dist/types/config/model-registry.d.ts +9 -0
- package/dist/types/config/models-config-schema.d.ts +5 -0
- package/dist/types/config/settings-schema.d.ts +45 -0
- package/dist/types/config/settings.d.ts +7 -0
- package/dist/types/coordinator-mcp/server.d.ts +8 -0
- package/dist/types/gjc-runtime/launch-tmux.d.ts +14 -5
- package/dist/types/gjc-runtime/team-runtime.d.ts +1 -1
- package/dist/types/gjc-runtime/tmux-common.d.ts +12 -0
- package/dist/types/gjc-runtime/ultragoal-guard.d.ts +3 -1
- package/dist/types/modes/components/custom-model-preset-wizard.d.ts +1 -1
- package/dist/types/modes/components/hook-input.d.ts +1 -0
- package/dist/types/modes/components/model-selector.d.ts +11 -0
- package/dist/types/modes/components/settings-selector.d.ts +4 -1
- package/dist/types/modes/components/status-line/types.d.ts +5 -7
- package/dist/types/modes/components/status-line.d.ts +7 -3
- package/dist/types/modes/controllers/extension-ui-controller.d.ts +3 -1
- package/dist/types/modes/controllers/input-controller.d.ts +1 -0
- package/dist/types/modes/controllers/selector-controller.d.ts +2 -1
- package/dist/types/modes/interactive-mode.d.ts +3 -1
- package/dist/types/modes/prompt-action-autocomplete.d.ts +1 -0
- package/dist/types/modes/shared/agent-wire/deep-interview-gate.d.ts +21 -1
- package/dist/types/modes/tmux-scroll.d.ts +18 -0
- package/dist/types/modes/types.d.ts +3 -1
- package/dist/types/modes/utils/terminal-bell.d.ts +3 -0
- package/dist/types/notifications/daemon-paths.d.ts +9 -0
- package/dist/types/notifications/telegram-daemon-cli.d.ts +21 -4
- package/dist/types/notifications/telegram-daemon.d.ts +1 -10
- package/dist/types/tools/ask.d.ts +24 -0
- package/dist/types/tools/ultragoal-ask-guard.d.ts +9 -2
- package/package.json +7 -7
- package/src/cli/local-provider-smoke.ts +632 -0
- package/src/cli/notify-cli.ts +5 -3
- package/src/cli/update-cli.ts +155 -13
- package/src/cli.ts +19 -0
- package/src/commands/launch.ts +46 -1
- package/src/commands/local-provider.ts +62 -0
- package/src/commands/notify.ts +1 -1
- package/src/config/keybindings.ts +1 -1
- package/src/config/model-profile-activation.ts +92 -11
- package/src/config/model-profiles.ts +15 -2
- package/src/config/model-registry.ts +136 -6
- package/src/config/models-config-schema.ts +9 -0
- package/src/config/settings-schema.ts +48 -0
- package/src/config/settings.ts +11 -0
- package/src/coordinator-mcp/server.ts +280 -36
- package/src/deep-interview/render-middleware.ts +1 -0
- package/src/defaults/gjc/skills/deep-interview/SKILL.md +14 -6
- package/src/defaults/gjc/skills/ralplan/SKILL.md +14 -7
- package/src/gjc-runtime/deep-interview-runtime.ts +1 -1
- package/src/gjc-runtime/launch-tmux.ts +321 -38
- package/src/gjc-runtime/launch-worktree.ts +16 -2
- package/src/gjc-runtime/team-runtime.ts +87 -7
- package/src/gjc-runtime/tmux-common.ts +16 -0
- package/src/gjc-runtime/tmux-sessions.ts +13 -9
- package/src/gjc-runtime/ultragoal-guard.ts +11 -5
- package/src/internal-urls/docs-index.generated.ts +10 -9
- package/src/modes/components/agent-dashboard.ts +2 -1
- package/src/modes/components/custom-model-preset-wizard.ts +31 -192
- package/src/modes/components/hook-editor.ts +7 -4
- package/src/modes/components/hook-input.ts +4 -0
- package/src/modes/components/model-selector.ts +260 -12
- package/src/modes/components/session-observer-overlay.ts +3 -3
- package/src/modes/components/settings-selector.ts +506 -5
- package/src/modes/components/status-line/segments.ts +16 -19
- package/src/modes/components/status-line/types.ts +6 -2
- package/src/modes/components/status-line.ts +70 -35
- package/src/modes/controllers/event-controller.ts +98 -4
- package/src/modes/controllers/extension-ui-controller.ts +5 -0
- package/src/modes/controllers/input-controller.ts +15 -0
- package/src/modes/controllers/selector-controller.ts +142 -11
- package/src/modes/interactive-mode.ts +7 -2
- package/src/modes/prompt-action-autocomplete.ts +8 -0
- package/src/modes/shared/agent-wire/deep-interview-gate.ts +11 -4
- package/src/modes/tmux-scroll.ts +92 -0
- package/src/modes/types.ts +6 -1
- package/src/modes/utils/terminal-bell.ts +48 -0
- package/src/notifications/daemon-paths.ts +22 -0
- package/src/notifications/telegram-daemon-cli.ts +125 -13
- package/src/notifications/telegram-daemon.ts +6 -21
- package/src/prompts/system/system-prompt.md +5 -0
- package/src/sdk.ts +10 -1
- package/src/session/agent-session.ts +84 -6
- package/src/slash-commands/builtin-registry.ts +106 -13
- package/src/tools/ask.ts +11 -1
- package/src/tools/eval.ts +2 -2
- package/src/tools/render-utils.ts +2 -0
- package/src/tools/ultragoal-ask-guard.ts +39 -5
|
@@ -27,7 +27,9 @@ import { getTabBarTheme } from "../shared";
|
|
|
27
27
|
import { DynamicBorder } from "./dynamic-border";
|
|
28
28
|
import { handleInputOrEscape, PluginSettingsComponent } from "./plugin-settings";
|
|
29
29
|
import { getSettingsForTab, type SettingDef } from "./settings-defs";
|
|
30
|
+
import type { StatusLineSegmentOptions } from "./status-line";
|
|
30
31
|
import { getPreset } from "./status-line/presets";
|
|
32
|
+
import { ALL_SEGMENT_IDS } from "./status-line/segments";
|
|
31
33
|
|
|
32
34
|
/**
|
|
33
35
|
* A submenu component for selecting from a list of options.
|
|
@@ -161,6 +163,457 @@ class SelectSubmenu extends Container {
|
|
|
161
163
|
this.#selectList.handleInput(data);
|
|
162
164
|
}
|
|
163
165
|
}
|
|
166
|
+
const STATUS_LINE_CUSTOM_EDITOR_ID = "statusLine.customEditor";
|
|
167
|
+
const PUBLIC_STATUS_SEGMENTS = ALL_SEGMENT_IDS.filter(id => id !== "pi");
|
|
168
|
+
|
|
169
|
+
type StatusLineDraft = Required<
|
|
170
|
+
Pick<StatusLinePreviewSettings, "preset" | "leftSegments" | "rightSegments" | "separator" | "segmentOptions">
|
|
171
|
+
>;
|
|
172
|
+
|
|
173
|
+
const BOOL_VALUES = ["true", "false"];
|
|
174
|
+
const PATH_LENGTH_OPTIONS: SelectItem[] = [16, 24, 32, 40, 50, 60, 80].map(value => ({
|
|
175
|
+
value: String(value),
|
|
176
|
+
label: String(value),
|
|
177
|
+
}));
|
|
178
|
+
const TIME_FORMAT_OPTIONS: SelectItem[] = [
|
|
179
|
+
{ value: "24h", label: "24h" },
|
|
180
|
+
{ value: "12h", label: "12h" },
|
|
181
|
+
];
|
|
182
|
+
const USAGE_MODE_OPTIONS: SelectItem[] = [
|
|
183
|
+
{ value: "used", label: "Used" },
|
|
184
|
+
{ value: "remaining", label: "Remaining" },
|
|
185
|
+
];
|
|
186
|
+
|
|
187
|
+
function cloneSegmentOptions(options: StatusLineSegmentOptions | undefined): StatusLineSegmentOptions {
|
|
188
|
+
return mergeSegmentOptions(undefined, options);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function mergeSegmentOptions(
|
|
192
|
+
base: StatusLineSegmentOptions | undefined,
|
|
193
|
+
overrides: StatusLineSegmentOptions | undefined,
|
|
194
|
+
): StatusLineSegmentOptions {
|
|
195
|
+
return {
|
|
196
|
+
model: base?.model || overrides?.model ? { ...(base?.model ?? {}), ...(overrides?.model ?? {}) } : undefined,
|
|
197
|
+
path: base?.path || overrides?.path ? { ...(base?.path ?? {}), ...(overrides?.path ?? {}) } : undefined,
|
|
198
|
+
git: base?.git || overrides?.git ? { ...(base?.git ?? {}), ...(overrides?.git ?? {}) } : undefined,
|
|
199
|
+
time: base?.time || overrides?.time ? { ...(base?.time ?? {}), ...(overrides?.time ?? {}) } : undefined,
|
|
200
|
+
usage: base?.usage || overrides?.usage ? { ...(base?.usage ?? {}), ...(overrides?.usage ?? {}) } : undefined,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function effectiveSegmentOptions(
|
|
205
|
+
preset: StatusLinePreset,
|
|
206
|
+
options: StatusLineSegmentOptions | undefined,
|
|
207
|
+
): StatusLineSegmentOptions {
|
|
208
|
+
return mergeSegmentOptions(getPreset(preset).segmentOptions, options);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function effectiveCustomSegments(
|
|
212
|
+
preset: StatusLinePreset,
|
|
213
|
+
leftSegments: StatusLineSegmentId[],
|
|
214
|
+
rightSegments: StatusLineSegmentId[],
|
|
215
|
+
): { leftSegments: StatusLineSegmentId[]; rightSegments: StatusLineSegmentId[] } {
|
|
216
|
+
if (preset === "custom") {
|
|
217
|
+
return { leftSegments: [...leftSegments], rightSegments: [...rightSegments] };
|
|
218
|
+
}
|
|
219
|
+
const presetDef = getPreset(preset);
|
|
220
|
+
return {
|
|
221
|
+
leftSegments: [...presetDef.leftSegments],
|
|
222
|
+
rightSegments: [...presetDef.rightSegments],
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
function statusSegmentLabel(id: StatusLineSegmentId): string {
|
|
227
|
+
return id.replace(/_/g, " ");
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function segmentPlacement(
|
|
231
|
+
id: StatusLineSegmentId,
|
|
232
|
+
leftSegments: StatusLineSegmentId[],
|
|
233
|
+
rightSegments: StatusLineSegmentId[],
|
|
234
|
+
): "hidden" | "left" | "right" {
|
|
235
|
+
if (leftSegments.includes(id)) return "left";
|
|
236
|
+
if (rightSegments.includes(id)) return "right";
|
|
237
|
+
return "hidden";
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
class StatusLineCustomEditor extends Container {
|
|
241
|
+
#list!: SettingsList;
|
|
242
|
+
#draft: StatusLineDraft;
|
|
243
|
+
#previewHighlightSegment: StatusLineSegmentId | undefined;
|
|
244
|
+
|
|
245
|
+
constructor(
|
|
246
|
+
private readonly callbacks: SettingsCallbacks,
|
|
247
|
+
private readonly done: (value?: string) => void,
|
|
248
|
+
) {
|
|
249
|
+
super();
|
|
250
|
+
const preset = settings.get("statusLine.preset");
|
|
251
|
+
const seeded = effectiveCustomSegments(
|
|
252
|
+
preset,
|
|
253
|
+
settings.get("statusLine.leftSegments"),
|
|
254
|
+
settings.get("statusLine.rightSegments"),
|
|
255
|
+
);
|
|
256
|
+
this.#draft = {
|
|
257
|
+
preset: "custom",
|
|
258
|
+
leftSegments: seeded.leftSegments,
|
|
259
|
+
rightSegments: seeded.rightSegments,
|
|
260
|
+
separator: settings.get("statusLine.separator"),
|
|
261
|
+
segmentOptions: effectiveSegmentOptions(
|
|
262
|
+
preset,
|
|
263
|
+
settings.get("statusLine.segmentOptions") as StatusLineSegmentOptions,
|
|
264
|
+
),
|
|
265
|
+
};
|
|
266
|
+
this.#preview();
|
|
267
|
+
this.#build();
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
#build(): void {
|
|
271
|
+
this.clear();
|
|
272
|
+
this.addChild(new Text(theme.bold(theme.fg("accent", "Status Line Custom Editor")), 0, 0));
|
|
273
|
+
this.addChild(new Spacer(1));
|
|
274
|
+
this.#list = new SettingsList(
|
|
275
|
+
this.#items(),
|
|
276
|
+
14,
|
|
277
|
+
getSettingsListTheme(),
|
|
278
|
+
(id, value) => this.#handleChange(id, value),
|
|
279
|
+
() => this.#cancel(),
|
|
280
|
+
item => this.#setSelectedItem(item),
|
|
281
|
+
2,
|
|
282
|
+
);
|
|
283
|
+
this.addChild(this.#list);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
#refresh(): void {
|
|
287
|
+
this.#list.setItems(this.#items());
|
|
288
|
+
}
|
|
289
|
+
#setSelectedItem(item: SettingItem | undefined): void {
|
|
290
|
+
this.#previewHighlightSegment = this.#highlightSegmentForItem(item);
|
|
291
|
+
this.#preview();
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
#highlightSegmentForItem(item: SettingItem | undefined): StatusLineSegmentId | undefined {
|
|
295
|
+
if (!item) return undefined;
|
|
296
|
+
if (item.id.startsWith("segment.")) {
|
|
297
|
+
return item.id.slice("segment.".length) as StatusLineSegmentId;
|
|
298
|
+
}
|
|
299
|
+
if (item.id.startsWith("moveup.")) {
|
|
300
|
+
return item.id.slice("moveup.".length) as StatusLineSegmentId;
|
|
301
|
+
}
|
|
302
|
+
if (item.id.startsWith("movedown.")) {
|
|
303
|
+
return item.id.slice("movedown.".length) as StatusLineSegmentId;
|
|
304
|
+
}
|
|
305
|
+
if (item.id.startsWith("option.")) {
|
|
306
|
+
const [segment] = item.id.slice("option.".length).split(".");
|
|
307
|
+
return segment as StatusLineSegmentId;
|
|
308
|
+
}
|
|
309
|
+
return undefined;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
#items(): SettingItem[] {
|
|
313
|
+
const items: SettingItem[] = [
|
|
314
|
+
{
|
|
315
|
+
id: "action.save",
|
|
316
|
+
label: "Save custom status line",
|
|
317
|
+
description: "Persist the previewed custom status line to user config.",
|
|
318
|
+
currentValue: "approve",
|
|
319
|
+
values: ["approve"],
|
|
320
|
+
},
|
|
321
|
+
{
|
|
322
|
+
id: "action.cancel",
|
|
323
|
+
label: "Cancel and restore",
|
|
324
|
+
description: "Close without persisting draft settings and restore the previous preview.",
|
|
325
|
+
currentValue: "restore",
|
|
326
|
+
values: ["restore"],
|
|
327
|
+
},
|
|
328
|
+
{
|
|
329
|
+
id: "separator",
|
|
330
|
+
label: "Separator",
|
|
331
|
+
description: "Separator style between status line segments.",
|
|
332
|
+
currentValue: this.#draft.separator,
|
|
333
|
+
submenu: (currentValue, done) =>
|
|
334
|
+
new SelectSubmenu(
|
|
335
|
+
"Status Line Separator",
|
|
336
|
+
"Style of separators between segments.",
|
|
337
|
+
[
|
|
338
|
+
{ value: "powerline", label: "Powerline" },
|
|
339
|
+
{ value: "powerline-thin", label: "Thin chevron" },
|
|
340
|
+
{ value: "slash", label: "Slash" },
|
|
341
|
+
{ value: "pipe", label: "Pipe" },
|
|
342
|
+
{ value: "block", label: "Block" },
|
|
343
|
+
{ value: "none", label: "None" },
|
|
344
|
+
{ value: "ascii", label: "ASCII" },
|
|
345
|
+
],
|
|
346
|
+
currentValue,
|
|
347
|
+
done,
|
|
348
|
+
() => done(),
|
|
349
|
+
),
|
|
350
|
+
},
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
for (const id of PUBLIC_STATUS_SEGMENTS) {
|
|
354
|
+
items.push({
|
|
355
|
+
id: `segment.${id}`,
|
|
356
|
+
label: `Segment: ${statusSegmentLabel(id)}`,
|
|
357
|
+
description: "Cycle placement: hidden → left → right. Use move actions below to reorder visible segments.",
|
|
358
|
+
currentValue: segmentPlacement(id, this.#draft.leftSegments, this.#draft.rightSegments),
|
|
359
|
+
values: ["hidden", "left", "right"],
|
|
360
|
+
});
|
|
361
|
+
if (segmentPlacement(id, this.#draft.leftSegments, this.#draft.rightSegments) !== "hidden") {
|
|
362
|
+
items.push(
|
|
363
|
+
{
|
|
364
|
+
id: `moveup.${id}`,
|
|
365
|
+
label: `Move left: ${statusSegmentLabel(id)}`,
|
|
366
|
+
currentValue: "←",
|
|
367
|
+
values: ["←"],
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
id: `movedown.${id}`,
|
|
371
|
+
label: `Move right: ${statusSegmentLabel(id)}`,
|
|
372
|
+
currentValue: "→",
|
|
373
|
+
values: ["→"],
|
|
374
|
+
},
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
if (id === "usage") {
|
|
378
|
+
items.push({
|
|
379
|
+
id: "option.usage.mode",
|
|
380
|
+
label: "Usage: mode",
|
|
381
|
+
currentValue: this.#draft.segmentOptions.usage?.mode ?? "used",
|
|
382
|
+
submenu: (currentValue, done) =>
|
|
383
|
+
new SelectSubmenu(
|
|
384
|
+
"Usage mode",
|
|
385
|
+
"Show used quota or remaining quota in the usage segment.",
|
|
386
|
+
USAGE_MODE_OPTIONS,
|
|
387
|
+
currentValue,
|
|
388
|
+
done,
|
|
389
|
+
() => done(),
|
|
390
|
+
),
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
items.push(
|
|
396
|
+
{
|
|
397
|
+
id: "option.model.showThinkingLevel",
|
|
398
|
+
label: "Model: show thinking level",
|
|
399
|
+
currentValue: String(this.#draft.segmentOptions.model?.showThinkingLevel !== false),
|
|
400
|
+
values: BOOL_VALUES,
|
|
401
|
+
},
|
|
402
|
+
{
|
|
403
|
+
id: "option.path.abbreviate",
|
|
404
|
+
label: "Path: abbreviate",
|
|
405
|
+
currentValue: String(this.#draft.segmentOptions.path?.abbreviate !== false),
|
|
406
|
+
values: BOOL_VALUES,
|
|
407
|
+
},
|
|
408
|
+
{
|
|
409
|
+
id: "option.path.maxLength",
|
|
410
|
+
label: "Path: max length",
|
|
411
|
+
currentValue: String(this.#draft.segmentOptions.path?.maxLength ?? 32),
|
|
412
|
+
submenu: (currentValue, done) =>
|
|
413
|
+
new SelectSubmenu(
|
|
414
|
+
"Path max length",
|
|
415
|
+
"Maximum displayed path length.",
|
|
416
|
+
PATH_LENGTH_OPTIONS,
|
|
417
|
+
currentValue,
|
|
418
|
+
done,
|
|
419
|
+
() => done(),
|
|
420
|
+
),
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
id: "option.path.stripWorkPrefix",
|
|
424
|
+
label: "Path: strip work prefix",
|
|
425
|
+
currentValue: String(this.#draft.segmentOptions.path?.stripWorkPrefix === true),
|
|
426
|
+
values: BOOL_VALUES,
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
id: "option.git.showBranch",
|
|
430
|
+
label: "Git: show branch",
|
|
431
|
+
currentValue: String(this.#draft.segmentOptions.git?.showBranch !== false),
|
|
432
|
+
values: BOOL_VALUES,
|
|
433
|
+
},
|
|
434
|
+
{
|
|
435
|
+
id: "option.git.showStaged",
|
|
436
|
+
label: "Git: show staged",
|
|
437
|
+
currentValue: String(this.#draft.segmentOptions.git?.showStaged !== false),
|
|
438
|
+
values: BOOL_VALUES,
|
|
439
|
+
},
|
|
440
|
+
{
|
|
441
|
+
id: "option.git.showUnstaged",
|
|
442
|
+
label: "Git: show unstaged",
|
|
443
|
+
currentValue: String(this.#draft.segmentOptions.git?.showUnstaged !== false),
|
|
444
|
+
values: BOOL_VALUES,
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
id: "option.git.showUntracked",
|
|
448
|
+
label: "Git: show untracked",
|
|
449
|
+
currentValue: String(this.#draft.segmentOptions.git?.showUntracked !== false),
|
|
450
|
+
values: BOOL_VALUES,
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
id: "option.time.format",
|
|
454
|
+
label: "Time: format",
|
|
455
|
+
currentValue: this.#draft.segmentOptions.time?.format ?? "24h",
|
|
456
|
+
submenu: (currentValue, done) =>
|
|
457
|
+
new SelectSubmenu(
|
|
458
|
+
"Time format",
|
|
459
|
+
"Clock format for the time segment.",
|
|
460
|
+
TIME_FORMAT_OPTIONS,
|
|
461
|
+
currentValue,
|
|
462
|
+
done,
|
|
463
|
+
() => done(),
|
|
464
|
+
),
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
id: "option.time.showSeconds",
|
|
468
|
+
label: "Time: show seconds",
|
|
469
|
+
currentValue: String(this.#draft.segmentOptions.time?.showSeconds === true),
|
|
470
|
+
values: BOOL_VALUES,
|
|
471
|
+
},
|
|
472
|
+
);
|
|
473
|
+
|
|
474
|
+
return items;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
#handleChange(id: string, value: string): void {
|
|
478
|
+
if (id === "action.save") {
|
|
479
|
+
this.#save();
|
|
480
|
+
return;
|
|
481
|
+
}
|
|
482
|
+
if (id === "action.cancel") {
|
|
483
|
+
this.#cancel();
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
if (id === "separator") {
|
|
487
|
+
this.#draft.separator = value as StatusLineSeparatorStyle;
|
|
488
|
+
} else if (id.startsWith("segment.")) {
|
|
489
|
+
this.#setSegmentPlacement(
|
|
490
|
+
id.slice("segment.".length) as StatusLineSegmentId,
|
|
491
|
+
value as "hidden" | "left" | "right",
|
|
492
|
+
);
|
|
493
|
+
} else if (id.startsWith("moveup.")) {
|
|
494
|
+
this.#moveSegment(id.slice("moveup.".length) as StatusLineSegmentId, -1);
|
|
495
|
+
} else if (id.startsWith("movedown.")) {
|
|
496
|
+
this.#moveSegment(id.slice("movedown.".length) as StatusLineSegmentId, 1);
|
|
497
|
+
} else if (id.startsWith("option.")) {
|
|
498
|
+
this.#setOption(id.slice("option.".length), value);
|
|
499
|
+
}
|
|
500
|
+
this.#preview();
|
|
501
|
+
this.#refresh();
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
#setSegmentPlacement(id: StatusLineSegmentId, placement: "hidden" | "left" | "right"): void {
|
|
505
|
+
this.#draft.leftSegments = this.#draft.leftSegments.filter(segment => segment !== id);
|
|
506
|
+
this.#draft.rightSegments = this.#draft.rightSegments.filter(segment => segment !== id);
|
|
507
|
+
if (placement === "left") this.#draft.leftSegments.push(id);
|
|
508
|
+
if (placement === "right") this.#draft.rightSegments.push(id);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
#moveSegment(id: StatusLineSegmentId, delta: -1 | 1): void {
|
|
512
|
+
const group = this.#draft.leftSegments.includes(id) ? this.#draft.leftSegments : this.#draft.rightSegments;
|
|
513
|
+
const index = group.indexOf(id);
|
|
514
|
+
if (index < 0) return;
|
|
515
|
+
const nextIndex = Math.max(0, Math.min(group.length - 1, index + delta));
|
|
516
|
+
if (nextIndex === index) return;
|
|
517
|
+
const [segment] = group.splice(index, 1);
|
|
518
|
+
if (segment) group.splice(nextIndex, 0, segment);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
#setOption(path: string, value: string): void {
|
|
522
|
+
const bool = value === "true";
|
|
523
|
+
switch (path) {
|
|
524
|
+
case "model.showThinkingLevel":
|
|
525
|
+
this.#draft.segmentOptions.model = { ...(this.#draft.segmentOptions.model ?? {}), showThinkingLevel: bool };
|
|
526
|
+
break;
|
|
527
|
+
case "path.abbreviate":
|
|
528
|
+
this.#draft.segmentOptions.path = { ...(this.#draft.segmentOptions.path ?? {}), abbreviate: bool };
|
|
529
|
+
break;
|
|
530
|
+
case "path.maxLength":
|
|
531
|
+
this.#draft.segmentOptions.path = { ...(this.#draft.segmentOptions.path ?? {}), maxLength: Number(value) };
|
|
532
|
+
break;
|
|
533
|
+
case "path.stripWorkPrefix":
|
|
534
|
+
this.#draft.segmentOptions.path = { ...(this.#draft.segmentOptions.path ?? {}), stripWorkPrefix: bool };
|
|
535
|
+
break;
|
|
536
|
+
case "git.showBranch":
|
|
537
|
+
this.#draft.segmentOptions.git = { ...(this.#draft.segmentOptions.git ?? {}), showBranch: bool };
|
|
538
|
+
break;
|
|
539
|
+
case "git.showStaged":
|
|
540
|
+
this.#draft.segmentOptions.git = { ...(this.#draft.segmentOptions.git ?? {}), showStaged: bool };
|
|
541
|
+
break;
|
|
542
|
+
case "git.showUnstaged":
|
|
543
|
+
this.#draft.segmentOptions.git = { ...(this.#draft.segmentOptions.git ?? {}), showUnstaged: bool };
|
|
544
|
+
break;
|
|
545
|
+
case "git.showUntracked":
|
|
546
|
+
this.#draft.segmentOptions.git = { ...(this.#draft.segmentOptions.git ?? {}), showUntracked: bool };
|
|
547
|
+
break;
|
|
548
|
+
case "time.format":
|
|
549
|
+
this.#draft.segmentOptions.time = {
|
|
550
|
+
...(this.#draft.segmentOptions.time ?? {}),
|
|
551
|
+
format: value as "12h" | "24h",
|
|
552
|
+
};
|
|
553
|
+
break;
|
|
554
|
+
case "time.showSeconds":
|
|
555
|
+
this.#draft.segmentOptions.time = { ...(this.#draft.segmentOptions.time ?? {}), showSeconds: bool };
|
|
556
|
+
break;
|
|
557
|
+
case "usage.mode":
|
|
558
|
+
this.#draft.segmentOptions.usage = {
|
|
559
|
+
...(this.#draft.segmentOptions.usage ?? {}),
|
|
560
|
+
mode: value === "remaining" ? "remaining" : "used",
|
|
561
|
+
};
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
#preview(): void {
|
|
567
|
+
this.callbacks.onStatusLinePreview?.({
|
|
568
|
+
preset: "custom",
|
|
569
|
+
leftSegments: [...this.#draft.leftSegments],
|
|
570
|
+
rightSegments: [...this.#draft.rightSegments],
|
|
571
|
+
separator: this.#draft.separator,
|
|
572
|
+
segmentOptions: cloneSegmentOptions(this.#draft.segmentOptions),
|
|
573
|
+
previewHighlightSegment: this.#previewHighlightSegment,
|
|
574
|
+
});
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
#restorePreview(): void {
|
|
578
|
+
this.callbacks.onStatusLinePreview?.({
|
|
579
|
+
preset: settings.get("statusLine.preset"),
|
|
580
|
+
leftSegments: settings.get("statusLine.leftSegments"),
|
|
581
|
+
rightSegments: settings.get("statusLine.rightSegments"),
|
|
582
|
+
separator: settings.get("statusLine.separator"),
|
|
583
|
+
segmentOptions: cloneSegmentOptions(settings.get("statusLine.segmentOptions") as StatusLineSegmentOptions),
|
|
584
|
+
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
585
|
+
previewHighlightSegment: undefined,
|
|
586
|
+
});
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
#save(): void {
|
|
590
|
+
settings.set("statusLine.preset", "custom");
|
|
591
|
+
settings.set("statusLine.leftSegments", [...this.#draft.leftSegments]);
|
|
592
|
+
settings.set("statusLine.rightSegments", [...this.#draft.rightSegments]);
|
|
593
|
+
settings.set("statusLine.separator", this.#draft.separator);
|
|
594
|
+
settings.set(
|
|
595
|
+
"statusLine.segmentOptions",
|
|
596
|
+
cloneSegmentOptions(this.#draft.segmentOptions) as Record<string, unknown>,
|
|
597
|
+
);
|
|
598
|
+
this.callbacks.onChange("statusLine.preset", "custom");
|
|
599
|
+
this.callbacks.onChange("statusLine.leftSegments", [...this.#draft.leftSegments]);
|
|
600
|
+
this.callbacks.onChange("statusLine.rightSegments", [...this.#draft.rightSegments]);
|
|
601
|
+
this.callbacks.onChange("statusLine.separator", this.#draft.separator);
|
|
602
|
+
this.callbacks.onChange("statusLine.segmentOptions", cloneSegmentOptions(this.#draft.segmentOptions));
|
|
603
|
+
this.#previewHighlightSegment = undefined;
|
|
604
|
+
this.#preview();
|
|
605
|
+
this.done("saved");
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
#cancel(): void {
|
|
609
|
+
this.#restorePreview();
|
|
610
|
+
this.done();
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
handleInput(data: string): void {
|
|
614
|
+
this.#list.handleInput(data);
|
|
615
|
+
}
|
|
616
|
+
}
|
|
164
617
|
|
|
165
618
|
function getSettingsTabs(): Tab[] {
|
|
166
619
|
return [
|
|
@@ -194,6 +647,8 @@ export interface StatusLinePreviewSettings {
|
|
|
194
647
|
leftSegments?: StatusLineSegmentId[];
|
|
195
648
|
rightSegments?: StatusLineSegmentId[];
|
|
196
649
|
separator?: StatusLineSeparatorStyle;
|
|
650
|
+
segmentOptions?: StatusLineSegmentOptions;
|
|
651
|
+
previewHighlightSegment?: StatusLineSegmentId;
|
|
197
652
|
sessionAccent?: boolean;
|
|
198
653
|
}
|
|
199
654
|
|
|
@@ -207,7 +662,7 @@ export interface SettingsCallbacks {
|
|
|
207
662
|
/** Called for status line preview while configuring */
|
|
208
663
|
onStatusLinePreview?: (settings: StatusLinePreviewSettings) => void;
|
|
209
664
|
/** Get current rendered status line for inline preview */
|
|
210
|
-
getStatusLinePreview?: () => string;
|
|
665
|
+
getStatusLinePreview?: (width?: number) => string;
|
|
211
666
|
/** Called when plugins change */
|
|
212
667
|
onPluginsChanged?: () => void;
|
|
213
668
|
/** Called when settings panel is closed */
|
|
@@ -372,7 +827,9 @@ export class SettingsSelectorComponent extends Container {
|
|
|
372
827
|
} else if (def.path === "theme.dark" || def.path === "theme.light") {
|
|
373
828
|
options = this.context.availableThemes.map(t => ({ value: t, label: t }));
|
|
374
829
|
}
|
|
375
|
-
|
|
830
|
+
if (def.path === "statusLine.preset") {
|
|
831
|
+
options = options.filter(option => option.value !== "custom");
|
|
832
|
+
}
|
|
376
833
|
// Preview handlers
|
|
377
834
|
let onPreview: ((value: string) => void | Promise<void>) | undefined;
|
|
378
835
|
let onPreviewCancel: (() => void) | undefined;
|
|
@@ -395,17 +852,31 @@ export class SettingsSelectorComponent extends Container {
|
|
|
395
852
|
leftSegments: presetDef.leftSegments,
|
|
396
853
|
rightSegments: presetDef.rightSegments,
|
|
397
854
|
separator: presetDef.separator,
|
|
855
|
+
previewHighlightSegment: undefined,
|
|
398
856
|
});
|
|
399
857
|
this.#updateStatusPreview();
|
|
400
858
|
};
|
|
401
859
|
onPreviewCancel = () => {
|
|
402
860
|
const currentPreset = settings.get("statusLine.preset");
|
|
403
861
|
const presetDef = getPreset(currentPreset);
|
|
862
|
+
const savedCustomSettings =
|
|
863
|
+
currentPreset === "custom"
|
|
864
|
+
? {
|
|
865
|
+
leftSegments: settings.get("statusLine.leftSegments"),
|
|
866
|
+
rightSegments: settings.get("statusLine.rightSegments"),
|
|
867
|
+
separator: settings.get("statusLine.separator"),
|
|
868
|
+
segmentOptions: cloneSegmentOptions(
|
|
869
|
+
settings.get("statusLine.segmentOptions") as StatusLineSegmentOptions,
|
|
870
|
+
),
|
|
871
|
+
}
|
|
872
|
+
: {};
|
|
404
873
|
this.callbacks.onStatusLinePreview?.({
|
|
405
874
|
preset: currentPreset,
|
|
406
875
|
leftSegments: presetDef.leftSegments,
|
|
407
876
|
rightSegments: presetDef.rightSegments,
|
|
408
877
|
separator: presetDef.separator,
|
|
878
|
+
...savedCustomSettings,
|
|
879
|
+
previewHighlightSegment: undefined,
|
|
409
880
|
});
|
|
410
881
|
this.#updateStatusPreview();
|
|
411
882
|
};
|
|
@@ -416,7 +887,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
416
887
|
};
|
|
417
888
|
onPreviewCancel = () => {
|
|
418
889
|
const separator = settings.get("statusLine.separator");
|
|
419
|
-
this.callbacks.onStatusLinePreview?.({ separator });
|
|
890
|
+
this.callbacks.onStatusLinePreview?.({ separator, previewHighlightSegment: undefined });
|
|
420
891
|
this.#updateStatusPreview();
|
|
421
892
|
};
|
|
422
893
|
}
|
|
@@ -509,7 +980,7 @@ export class SettingsSelectorComponent extends Container {
|
|
|
509
980
|
}
|
|
510
981
|
|
|
511
982
|
this.#currentList = new SettingsList(
|
|
512
|
-
this.#
|
|
983
|
+
this.#buildItemsForTab(defs, tabId),
|
|
513
984
|
10,
|
|
514
985
|
getSettingsListTheme(),
|
|
515
986
|
(id, newValue) => {
|
|
@@ -553,10 +1024,38 @@ export class SettingsSelectorComponent extends Container {
|
|
|
553
1024
|
return items;
|
|
554
1025
|
}
|
|
555
1026
|
|
|
1027
|
+
#buildItemsForTab(defs: SettingDef[], tabId: SettingTab): SettingItem[] {
|
|
1028
|
+
const items = this.#buildItemsForDefs(defs);
|
|
1029
|
+
if (tabId === "appearance") {
|
|
1030
|
+
const customEditorCallbacks: SettingsCallbacks = {
|
|
1031
|
+
...this.callbacks,
|
|
1032
|
+
onStatusLinePreview: previewSettings => {
|
|
1033
|
+
this.callbacks.onStatusLinePreview?.(previewSettings);
|
|
1034
|
+
this.#updateStatusPreview();
|
|
1035
|
+
},
|
|
1036
|
+
};
|
|
1037
|
+
const customEditorItem: SettingItem = {
|
|
1038
|
+
id: STATUS_LINE_CUSTOM_EDITOR_ID,
|
|
1039
|
+
label: "Status Line Custom Editor",
|
|
1040
|
+
description:
|
|
1041
|
+
"Edit custom status line segments, placement, separator, and typed segment options with live previews.",
|
|
1042
|
+
currentValue: "open",
|
|
1043
|
+
submenu: (_currentValue, done) => new StatusLineCustomEditor(customEditorCallbacks, done),
|
|
1044
|
+
};
|
|
1045
|
+
const presetIndex = items.findIndex(item => item.id === "statusLine.preset");
|
|
1046
|
+
if (presetIndex >= 0) {
|
|
1047
|
+
items.splice(presetIndex + 1, 0, customEditorItem);
|
|
1048
|
+
} else {
|
|
1049
|
+
items.push(customEditorItem);
|
|
1050
|
+
}
|
|
1051
|
+
}
|
|
1052
|
+
return items;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
556
1055
|
/** Re-evaluate condition gates against the current settings and refresh the active list. */
|
|
557
1056
|
#refreshCurrentTabItems(defs: SettingDef[]): void {
|
|
558
1057
|
if (this.#currentTabId === "plugins" || !this.#currentList) return;
|
|
559
|
-
this.#currentList.setItems(this.#
|
|
1058
|
+
this.#currentList.setItems(this.#buildItemsForTab(defs, this.#currentTabId));
|
|
560
1059
|
}
|
|
561
1060
|
|
|
562
1061
|
/**
|
|
@@ -578,7 +1077,9 @@ export class SettingsSelectorComponent extends Container {
|
|
|
578
1077
|
leftSegments: settings.get("statusLine.leftSegments"),
|
|
579
1078
|
rightSegments: settings.get("statusLine.rightSegments"),
|
|
580
1079
|
separator: settings.get("statusLine.separator"),
|
|
1080
|
+
segmentOptions: cloneSegmentOptions(settings.get("statusLine.segmentOptions") as StatusLineSegmentOptions),
|
|
581
1081
|
sessionAccent: settings.get("statusLine.sessionAccent"),
|
|
1082
|
+
previewHighlightSegment: undefined,
|
|
582
1083
|
};
|
|
583
1084
|
this.callbacks.onStatusLinePreview?.(statusLineSettings);
|
|
584
1085
|
this.#updateStatusPreview();
|
|
@@ -480,7 +480,12 @@ const sessionNameSegment: StatusLineSegment = {
|
|
|
480
480
|
},
|
|
481
481
|
};
|
|
482
482
|
|
|
483
|
-
function pickUsageColor(percent: number): "muted" | "warning" | "error" {
|
|
483
|
+
function pickUsageColor(percent: number, mode: "used" | "remaining"): "muted" | "warning" | "error" {
|
|
484
|
+
if (mode === "remaining") {
|
|
485
|
+
if (percent <= 20) return "error";
|
|
486
|
+
if (percent <= 50) return "warning";
|
|
487
|
+
return "muted";
|
|
488
|
+
}
|
|
484
489
|
if (percent >= 80) return "error";
|
|
485
490
|
if (percent >= 50) return "warning";
|
|
486
491
|
return "muted";
|
|
@@ -505,28 +510,20 @@ const usageSegment: StatusLineSegment = {
|
|
|
505
510
|
id: "usage",
|
|
506
511
|
render(ctx) {
|
|
507
512
|
const u = ctx.usage;
|
|
508
|
-
if (!u ||
|
|
513
|
+
if (!u || u.windows.length === 0) {
|
|
509
514
|
return { content: "", visible: false };
|
|
510
515
|
}
|
|
511
|
-
const
|
|
512
|
-
|
|
513
|
-
const
|
|
514
|
-
|
|
515
|
-
const
|
|
516
|
-
u.fiveHour.resetMinutes !== undefined
|
|
517
|
-
? theme.fg("muted", ` (${formatUsageReset(u.fiveHour.resetMinutes, "m")})`)
|
|
518
|
-
: "";
|
|
519
|
-
parts.push(`5h ${pctText}${reset}`);
|
|
520
|
-
}
|
|
521
|
-
if (u.sevenDay) {
|
|
522
|
-
const pct = u.sevenDay.percent;
|
|
523
|
-
const pctText = theme.fg(pickUsageColor(pct), `${Math.round(pct)}%`);
|
|
516
|
+
const mode = ctx.options.usage?.mode === "remaining" ? "remaining" : "used";
|
|
517
|
+
const parts = u.windows.map(window => {
|
|
518
|
+
const displayPercent =
|
|
519
|
+
mode === "remaining" ? Math.max(0, Math.min(100, 100 - window.percent)) : window.percent;
|
|
520
|
+
const pctText = theme.fg(pickUsageColor(displayPercent, mode), `${Math.round(displayPercent)}%`);
|
|
524
521
|
const reset =
|
|
525
|
-
|
|
526
|
-
? theme.fg("muted", ` (${formatUsageReset(
|
|
522
|
+
window.resetValue !== undefined && window.resetUnit !== undefined
|
|
523
|
+
? theme.fg("muted", ` (${formatUsageReset(window.resetValue, window.resetUnit)})`)
|
|
527
524
|
: "";
|
|
528
|
-
|
|
529
|
-
}
|
|
525
|
+
return `${window.label} ${pctText}${reset}`;
|
|
526
|
+
});
|
|
530
527
|
const content = withIcon(theme.icon.time, parts.join(theme.sep.dot));
|
|
531
528
|
return { content, visible: true };
|
|
532
529
|
},
|
|
@@ -51,8 +51,12 @@ export interface SegmentContext {
|
|
|
51
51
|
pr: { number: number; url: string } | null;
|
|
52
52
|
};
|
|
53
53
|
usage: {
|
|
54
|
-
|
|
55
|
-
|
|
54
|
+
windows: Array<{
|
|
55
|
+
label: string;
|
|
56
|
+
percent: number;
|
|
57
|
+
resetValue?: number;
|
|
58
|
+
resetUnit?: "m" | "h";
|
|
59
|
+
}>;
|
|
56
60
|
} | null;
|
|
57
61
|
}
|
|
58
62
|
|