@oh-my-pi/pi-coding-agent 3.13.1337 → 3.15.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/CHANGELOG.md +88 -0
- package/docs/theme.md +38 -5
- package/examples/sdk/11-sessions.ts +2 -2
- package/package.json +7 -4
- package/src/cli/file-processor.ts +51 -2
- package/src/cli/plugin-cli.ts +25 -19
- package/src/cli/update-cli.ts +4 -3
- package/src/core/agent-session.ts +31 -4
- package/src/core/compaction/branch-summarization.ts +4 -32
- package/src/core/compaction/compaction.ts +6 -84
- package/src/core/compaction/utils.ts +2 -3
- package/src/core/custom-tools/types.ts +2 -0
- package/src/core/export-html/index.ts +1 -1
- package/src/core/hooks/index.ts +1 -1
- package/src/core/hooks/tool-wrapper.ts +0 -1
- package/src/core/hooks/types.ts +2 -2
- package/src/core/plugins/doctor.ts +9 -1
- package/src/core/sdk.ts +2 -1
- package/src/core/session-manager.ts +552 -41
- package/src/core/settings-manager.ts +174 -0
- package/src/core/system-prompt.ts +9 -14
- package/src/core/title-generator.ts +2 -8
- package/src/core/tools/ask.ts +19 -37
- package/src/core/tools/bash.ts +2 -37
- package/src/core/tools/edit.ts +2 -9
- package/src/core/tools/exa/render.ts +52 -48
- package/src/core/tools/find.ts +10 -8
- package/src/core/tools/grep.ts +45 -17
- package/src/core/tools/ls.ts +22 -2
- package/src/core/tools/lsp/clients/biome-client.ts +207 -0
- package/src/core/tools/lsp/clients/index.ts +49 -0
- package/src/core/tools/lsp/clients/lsp-linter-client.ts +98 -0
- package/src/core/tools/lsp/config.ts +3 -0
- package/src/core/tools/lsp/index.ts +107 -55
- package/src/core/tools/lsp/render.ts +192 -79
- package/src/core/tools/lsp/types.ts +27 -0
- package/src/core/tools/lsp/utils.ts +62 -22
- package/src/core/tools/notebook.ts +9 -1
- package/src/core/tools/output.ts +37 -14
- package/src/core/tools/read.ts +349 -34
- package/src/core/tools/renderers.ts +290 -89
- package/src/core/tools/review.ts +12 -5
- package/src/core/tools/task/agents.ts +5 -5
- package/src/core/tools/task/commands.ts +3 -3
- package/src/core/tools/task/executor.ts +33 -1
- package/src/core/tools/task/index.ts +93 -6
- package/src/core/tools/task/render.ts +147 -66
- package/src/core/tools/task/types.ts +14 -9
- package/src/core/tools/web-fetch.ts +242 -103
- package/src/core/tools/web-search/index.ts +64 -20
- package/src/core/tools/web-search/providers/exa.ts +68 -172
- package/src/core/tools/web-search/render.ts +264 -74
- package/src/core/tools/write.ts +2 -8
- package/src/main.ts +10 -6
- package/src/modes/cleanup.ts +23 -0
- package/src/modes/index.ts +9 -4
- package/src/modes/interactive/components/bash-execution.ts +6 -3
- package/src/modes/interactive/components/branch-summary-message.ts +1 -1
- package/src/modes/interactive/components/compaction-summary-message.ts +1 -1
- package/src/modes/interactive/components/dynamic-border.ts +1 -1
- package/src/modes/interactive/components/extensions/extension-dashboard.ts +4 -5
- package/src/modes/interactive/components/extensions/extension-list.ts +18 -16
- package/src/modes/interactive/components/extensions/inspector-panel.ts +8 -8
- package/src/modes/interactive/components/hook-message.ts +2 -2
- package/src/modes/interactive/components/hook-selector.ts +1 -1
- package/src/modes/interactive/components/model-selector.ts +22 -9
- package/src/modes/interactive/components/oauth-selector.ts +20 -4
- package/src/modes/interactive/components/plugin-settings.ts +4 -2
- package/src/modes/interactive/components/session-selector.ts +9 -6
- package/src/modes/interactive/components/settings-defs.ts +285 -1
- package/src/modes/interactive/components/settings-selector.ts +176 -3
- package/src/modes/interactive/components/status-line/index.ts +4 -0
- package/src/modes/interactive/components/status-line/presets.ts +94 -0
- package/src/modes/interactive/components/status-line/segments.ts +350 -0
- package/src/modes/interactive/components/status-line/separators.ts +55 -0
- package/src/modes/interactive/components/status-line/types.ts +81 -0
- package/src/modes/interactive/components/status-line-segment-editor.ts +357 -0
- package/src/modes/interactive/components/status-line.ts +169 -233
- package/src/modes/interactive/components/tool-execution.ts +446 -211
- package/src/modes/interactive/components/tree-selector.ts +17 -6
- package/src/modes/interactive/components/ttsr-notification.ts +4 -4
- package/src/modes/interactive/components/welcome.ts +27 -19
- package/src/modes/interactive/interactive-mode.ts +98 -13
- package/src/modes/interactive/theme/dark.json +3 -2
- package/src/modes/interactive/theme/defaults/dark-arctic.json +111 -0
- package/src/modes/interactive/theme/defaults/dark-catppuccin.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-cyberpunk.json +109 -0
- package/src/modes/interactive/theme/defaults/dark-dracula.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-forest.json +103 -0
- package/src/modes/interactive/theme/defaults/dark-github.json +112 -0
- package/src/modes/interactive/theme/defaults/dark-gruvbox.json +119 -0
- package/src/modes/interactive/theme/defaults/dark-monochrome.json +101 -0
- package/src/modes/interactive/theme/defaults/dark-monokai.json +105 -0
- package/src/modes/interactive/theme/defaults/dark-nord.json +104 -0
- package/src/modes/interactive/theme/defaults/dark-ocean.json +108 -0
- package/src/modes/interactive/theme/defaults/dark-one.json +107 -0
- package/src/modes/interactive/theme/defaults/dark-retro.json +99 -0
- package/src/modes/interactive/theme/defaults/dark-rose-pine.json +95 -0
- package/src/modes/interactive/theme/defaults/dark-solarized.json +96 -0
- package/src/modes/interactive/theme/defaults/dark-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/dark-synthwave.json +102 -0
- package/src/modes/interactive/theme/defaults/dark-tokyo-night.json +108 -0
- package/src/modes/interactive/theme/defaults/index.ts +67 -0
- package/src/modes/interactive/theme/defaults/light-arctic.json +106 -0
- package/src/modes/interactive/theme/defaults/light-catppuccin.json +105 -0
- package/src/modes/interactive/theme/defaults/light-cyberpunk.json +103 -0
- package/src/modes/interactive/theme/defaults/light-forest.json +107 -0
- package/src/modes/interactive/theme/defaults/light-github.json +114 -0
- package/src/modes/interactive/theme/defaults/light-gruvbox.json +115 -0
- package/src/modes/interactive/theme/defaults/light-monochrome.json +100 -0
- package/src/modes/interactive/theme/defaults/light-ocean.json +106 -0
- package/src/modes/interactive/theme/defaults/light-one.json +105 -0
- package/src/modes/interactive/theme/defaults/light-retro.json +105 -0
- package/src/modes/interactive/theme/defaults/light-solarized.json +101 -0
- package/src/modes/interactive/theme/defaults/light-sunset.json +106 -0
- package/src/modes/interactive/theme/defaults/light-synthwave.json +105 -0
- package/src/modes/interactive/theme/defaults/light-tokyo-night.json +118 -0
- package/src/modes/interactive/theme/light.json +3 -2
- package/src/modes/interactive/theme/theme-schema.json +120 -4
- package/src/modes/interactive/theme/theme.ts +1228 -14
- package/src/prompts/branch-summary-preamble.md +3 -0
- package/src/prompts/branch-summary.md +28 -0
- package/src/prompts/compaction-summary.md +34 -0
- package/src/prompts/compaction-turn-prefix.md +16 -0
- package/src/prompts/compaction-update-summary.md +41 -0
- package/src/prompts/init.md +30 -0
- package/src/{core/tools/task/bundled-agents → prompts}/reviewer.md +6 -0
- package/src/prompts/summarization-system.md +3 -0
- package/src/prompts/system-prompt.md +27 -0
- package/src/{core/tools/task/bundled-agents → prompts}/task.md +2 -0
- package/src/prompts/title-system.md +8 -0
- package/src/prompts/tools/ask.md +24 -0
- package/src/prompts/tools/bash.md +23 -0
- package/src/prompts/tools/edit.md +9 -0
- package/src/prompts/tools/find.md +6 -0
- package/src/prompts/tools/grep.md +12 -0
- package/src/prompts/tools/lsp.md +14 -0
- package/src/prompts/tools/output.md +23 -0
- package/src/prompts/tools/read.md +25 -0
- package/src/prompts/tools/web-fetch.md +8 -0
- package/src/prompts/tools/web-search.md +10 -0
- package/src/prompts/tools/write.md +10 -0
- package/src/commands/init.md +0 -20
- /package/src/{core/tools/task/bundled-commands → prompts}/architect-plan.md +0 -0
- /package/src/{core/tools/task/bundled-agents → prompts}/browser.md +0 -0
- /package/src/{core/tools/task/bundled-agents → prompts}/explore.md +0 -0
- /package/src/{core/tools/task/bundled-commands → prompts}/implement-with-critic.md +0 -0
- /package/src/{core/tools/task/bundled-commands → prompts}/implement.md +0 -0
- /package/src/{core/tools/task/bundled-agents → prompts}/plan.md +0 -0
|
@@ -152,8 +152,10 @@ export class ExtensionList implements Component {
|
|
|
152
152
|
}
|
|
153
153
|
|
|
154
154
|
private renderMasterSwitch(item: ListItem & { type: "master" }, isSelected: boolean, width: number): string {
|
|
155
|
-
const checkbox = item.enabled
|
|
156
|
-
|
|
155
|
+
const checkbox = item.enabled
|
|
156
|
+
? theme.fg("success", theme.checkbox.checked)
|
|
157
|
+
: theme.fg("dim", theme.checkbox.unchecked);
|
|
158
|
+
const icon = theme.icon.package;
|
|
157
159
|
const label = `Enable ${item.providerName}`;
|
|
158
160
|
const badge = theme.fg("warning", "(Master Switch)");
|
|
159
161
|
|
|
@@ -229,39 +231,39 @@ export class ExtensionList implements Component {
|
|
|
229
231
|
private getKindIcon(kind: ExtensionKind): string {
|
|
230
232
|
switch (kind) {
|
|
231
233
|
case "skill":
|
|
232
|
-
return
|
|
234
|
+
return theme.icon.extensionSkill;
|
|
233
235
|
case "tool":
|
|
234
|
-
return
|
|
236
|
+
return theme.icon.extensionTool;
|
|
235
237
|
case "slash-command":
|
|
236
|
-
return
|
|
238
|
+
return theme.icon.extensionSlashCommand;
|
|
237
239
|
case "mcp":
|
|
238
|
-
return
|
|
240
|
+
return theme.icon.extensionMcp;
|
|
239
241
|
case "rule":
|
|
240
|
-
return
|
|
242
|
+
return theme.icon.extensionRule;
|
|
241
243
|
case "hook":
|
|
242
|
-
return
|
|
244
|
+
return theme.icon.extensionHook;
|
|
243
245
|
case "prompt":
|
|
244
|
-
return
|
|
246
|
+
return theme.icon.extensionPrompt;
|
|
245
247
|
case "context-file":
|
|
246
|
-
return
|
|
248
|
+
return theme.icon.extensionContextFile;
|
|
247
249
|
case "instruction":
|
|
248
|
-
return
|
|
250
|
+
return theme.icon.extensionInstruction;
|
|
249
251
|
default:
|
|
250
|
-
return
|
|
252
|
+
return theme.format.bullet;
|
|
251
253
|
}
|
|
252
254
|
}
|
|
253
255
|
|
|
254
256
|
private getStateIcon(state: ExtensionState, masterDisabled: boolean): string {
|
|
255
257
|
if (masterDisabled) {
|
|
256
|
-
return theme.fg("dim",
|
|
258
|
+
return theme.fg("dim", theme.status.disabled);
|
|
257
259
|
}
|
|
258
260
|
switch (state) {
|
|
259
261
|
case "active":
|
|
260
|
-
return theme.fg("success",
|
|
262
|
+
return theme.fg("success", theme.status.enabled);
|
|
261
263
|
case "disabled":
|
|
262
|
-
return theme.fg("dim",
|
|
264
|
+
return theme.fg("dim", theme.status.disabled);
|
|
263
265
|
case "shadowed":
|
|
264
|
-
return theme.fg("warning",
|
|
266
|
+
return theme.fg("warning", theme.status.shadowed);
|
|
265
267
|
}
|
|
266
268
|
}
|
|
267
269
|
|
|
@@ -95,7 +95,7 @@ export class InspectorPanel implements Component {
|
|
|
95
95
|
private renderFilePreview(path: string, width: number): string[] {
|
|
96
96
|
const lines: string[] = [];
|
|
97
97
|
lines.push(theme.fg("muted", "Preview:"));
|
|
98
|
-
lines.push(theme.fg("dim",
|
|
98
|
+
lines.push(theme.fg("dim", theme.boxSharp.horizontal.repeat(Math.min(width - 2, 40))));
|
|
99
99
|
|
|
100
100
|
try {
|
|
101
101
|
const content = readFileSync(path, "utf-8");
|
|
@@ -144,7 +144,7 @@ export class InspectorPanel implements Component {
|
|
|
144
144
|
private renderToolArgs(raw: unknown, width: number): string[] {
|
|
145
145
|
const lines: string[] = [];
|
|
146
146
|
lines.push(theme.fg("muted", "Arguments:"));
|
|
147
|
-
lines.push(theme.fg("dim",
|
|
147
|
+
lines.push(theme.fg("dim", theme.boxSharp.horizontal.repeat(Math.min(width - 2, 40))));
|
|
148
148
|
|
|
149
149
|
try {
|
|
150
150
|
const tool = raw as any;
|
|
@@ -183,7 +183,7 @@ export class InspectorPanel implements Component {
|
|
|
183
183
|
private renderSkillContent(raw: unknown, width: number): string[] {
|
|
184
184
|
const lines: string[] = [];
|
|
185
185
|
lines.push(theme.fg("muted", "Instruction:"));
|
|
186
|
-
lines.push(theme.fg("dim",
|
|
186
|
+
lines.push(theme.fg("dim", theme.boxSharp.horizontal.repeat(Math.min(width - 2, 40))));
|
|
187
187
|
|
|
188
188
|
try {
|
|
189
189
|
const skill = raw as any;
|
|
@@ -212,7 +212,7 @@ export class InspectorPanel implements Component {
|
|
|
212
212
|
private renderMcpDetails(raw: unknown, width: number): string[] {
|
|
213
213
|
const lines: string[] = [];
|
|
214
214
|
lines.push(theme.fg("muted", "Connection:"));
|
|
215
|
-
lines.push(theme.fg("dim",
|
|
215
|
+
lines.push(theme.fg("dim", theme.boxSharp.horizontal.repeat(Math.min(width - 2, 40))));
|
|
216
216
|
|
|
217
217
|
try {
|
|
218
218
|
const mcp = raw as any;
|
|
@@ -251,7 +251,7 @@ export class InspectorPanel implements Component {
|
|
|
251
251
|
// Show trigger pattern if present
|
|
252
252
|
if (ext.trigger) {
|
|
253
253
|
lines.push(theme.fg("muted", "Trigger:"));
|
|
254
|
-
lines.push(theme.fg("dim",
|
|
254
|
+
lines.push(theme.fg("dim", theme.boxSharp.horizontal.repeat(Math.min(width - 2, 40))));
|
|
255
255
|
lines.push(` ${theme.fg("accent", ext.trigger)}`);
|
|
256
256
|
lines.push("");
|
|
257
257
|
}
|
|
@@ -279,7 +279,7 @@ export class InspectorPanel implements Component {
|
|
|
279
279
|
private getStatusBadge(state: ExtensionState, reason?: string, shadowedBy?: string): string {
|
|
280
280
|
switch (state) {
|
|
281
281
|
case "active":
|
|
282
|
-
return theme.fg("success",
|
|
282
|
+
return theme.fg("success", `${theme.status.enabled} Active`);
|
|
283
283
|
case "disabled": {
|
|
284
284
|
const reasonText =
|
|
285
285
|
reason === "provider-disabled"
|
|
@@ -287,10 +287,10 @@ export class InspectorPanel implements Component {
|
|
|
287
287
|
: reason === "item-disabled"
|
|
288
288
|
? "manually disabled"
|
|
289
289
|
: "unknown";
|
|
290
|
-
return theme.fg("dim",
|
|
290
|
+
return theme.fg("dim", `${theme.status.disabled} Disabled (${reasonText})`);
|
|
291
291
|
}
|
|
292
292
|
case "shadowed":
|
|
293
|
-
return theme.fg("warning",
|
|
293
|
+
return theme.fg("warning", `${theme.status.shadowed} Shadowed${shadowedBy ? ` by ${shadowedBy}` : ""}`);
|
|
294
294
|
}
|
|
295
295
|
}
|
|
296
296
|
|
|
@@ -64,7 +64,7 @@ export class HookMessageComponent extends Container {
|
|
|
64
64
|
this.box.clear();
|
|
65
65
|
|
|
66
66
|
// Default rendering: label + content
|
|
67
|
-
const label = theme.fg("customMessageLabel",
|
|
67
|
+
const label = theme.fg("customMessageLabel", theme.bold(`[${this.message.customType}]`));
|
|
68
68
|
this.box.addChild(new Text(label, 0, 0));
|
|
69
69
|
this.box.addChild(new Spacer(1));
|
|
70
70
|
|
|
@@ -83,7 +83,7 @@ export class HookMessageComponent extends Container {
|
|
|
83
83
|
if (!this._expanded) {
|
|
84
84
|
const lines = text.split("\n");
|
|
85
85
|
if (lines.length > 5) {
|
|
86
|
-
text = `${lines.slice(0, 5).join("\n")}\n
|
|
86
|
+
text = `${lines.slice(0, 5).join("\n")}\n${theme.format.ellipsis}`;
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
@@ -56,7 +56,7 @@ export class HookSelectorComponent extends Container {
|
|
|
56
56
|
|
|
57
57
|
let text = "";
|
|
58
58
|
if (isSelected) {
|
|
59
|
-
text = theme.fg("accent",
|
|
59
|
+
text = theme.fg("accent", `${theme.nav.cursor} `) + theme.fg("accent", option);
|
|
60
60
|
} else {
|
|
61
61
|
text = ` ${theme.fg("text", option)}`;
|
|
62
62
|
}
|
|
@@ -13,6 +13,7 @@ import {
|
|
|
13
13
|
Spacer,
|
|
14
14
|
Text,
|
|
15
15
|
type TUI,
|
|
16
|
+
visibleWidth,
|
|
16
17
|
} from "@oh-my-pi/pi-tui";
|
|
17
18
|
import type { ModelRegistry } from "../../../core/model-registry";
|
|
18
19
|
import { parseModelString } from "../../../core/model-resolver";
|
|
@@ -276,7 +277,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
276
277
|
}
|
|
277
278
|
|
|
278
279
|
parts.push(" ");
|
|
279
|
-
parts.push(theme.fg("dim",
|
|
280
|
+
parts.push(theme.fg("dim", `(${theme.nav.back}/${theme.nav.cursor} or Tab to switch)`));
|
|
280
281
|
|
|
281
282
|
this.headerContainer.addChild(new Text(parts.join(""), 0, 0));
|
|
282
283
|
}
|
|
@@ -341,14 +342,14 @@ export class ModelSelectorComponent extends Container {
|
|
|
341
342
|
|
|
342
343
|
// Build role badges (right-aligned style)
|
|
343
344
|
const badges: string[] = [];
|
|
344
|
-
if (isDefault) badges.push(theme.fg("success",
|
|
345
|
-
if (isSmol) badges.push(theme.fg("warning",
|
|
346
|
-
if (isSlow) badges.push(theme.fg("accent",
|
|
345
|
+
if (isDefault) badges.push(theme.fg("success", `${theme.sep.pipe}DEFAULT${theme.sep.pipe}`));
|
|
346
|
+
if (isSmol) badges.push(theme.fg("warning", `${theme.sep.pipe}SMOL${theme.sep.pipe}`));
|
|
347
|
+
if (isSlow) badges.push(theme.fg("accent", `${theme.sep.pipe}SLOW${theme.sep.pipe}`));
|
|
347
348
|
const badgeText = badges.length > 0 ? ` ${badges.join(" ")}` : "";
|
|
348
349
|
|
|
349
350
|
let line = "";
|
|
350
351
|
if (isSelected) {
|
|
351
|
-
const prefix = theme.fg("accent",
|
|
352
|
+
const prefix = theme.fg("accent", `${theme.nav.cursor} `);
|
|
352
353
|
const modelText = item.id;
|
|
353
354
|
if (showProvider) {
|
|
354
355
|
const providerBadge = theme.fg("muted", `[${item.provider}]`);
|
|
@@ -405,9 +406,21 @@ export class ModelSelectorComponent extends Container {
|
|
|
405
406
|
const selectedModel = this.filteredModels[this.selectedIndex];
|
|
406
407
|
if (!selectedModel) return;
|
|
407
408
|
|
|
409
|
+
const headerText = ` Action for: ${selectedModel.id}`;
|
|
410
|
+
const hintText = " Enter: confirm Esc: cancel";
|
|
411
|
+
const actionLines = MENU_ACTIONS.map((action, index) => {
|
|
412
|
+
const prefix = index === this.menuSelectedIndex ? ` ${theme.nav.cursor} ` : " ";
|
|
413
|
+
return `${prefix}${action.label}`;
|
|
414
|
+
});
|
|
415
|
+
const menuWidth = Math.max(
|
|
416
|
+
visibleWidth(headerText),
|
|
417
|
+
visibleWidth(hintText),
|
|
418
|
+
...actionLines.map((line) => visibleWidth(line)),
|
|
419
|
+
);
|
|
420
|
+
|
|
408
421
|
// Menu header
|
|
409
422
|
this.menuContainer.addChild(new Spacer(1));
|
|
410
|
-
this.menuContainer.addChild(new Text(theme.fg("border",
|
|
423
|
+
this.menuContainer.addChild(new Text(theme.fg("border", theme.boxSharp.horizontal.repeat(menuWidth)), 0, 0));
|
|
411
424
|
this.menuContainer.addChild(new Text(theme.fg("text", ` Action for: ${theme.bold(selectedModel.id)}`), 0, 0));
|
|
412
425
|
this.menuContainer.addChild(new Spacer(1));
|
|
413
426
|
|
|
@@ -418,7 +431,7 @@ export class ModelSelectorComponent extends Container {
|
|
|
418
431
|
|
|
419
432
|
let line: string;
|
|
420
433
|
if (isSelected) {
|
|
421
|
-
line = theme.fg("accent", `
|
|
434
|
+
line = theme.fg("accent", ` ${theme.nav.cursor} ${action.label}`);
|
|
422
435
|
} else {
|
|
423
436
|
line = theme.fg("muted", ` ${action.label}`);
|
|
424
437
|
}
|
|
@@ -426,8 +439,8 @@ export class ModelSelectorComponent extends Container {
|
|
|
426
439
|
}
|
|
427
440
|
|
|
428
441
|
this.menuContainer.addChild(new Spacer(1));
|
|
429
|
-
this.menuContainer.addChild(new Text(theme.fg("dim",
|
|
430
|
-
this.menuContainer.addChild(new Text(theme.fg("border",
|
|
442
|
+
this.menuContainer.addChild(new Text(theme.fg("dim", hintText), 0, 0));
|
|
443
|
+
this.menuContainer.addChild(new Text(theme.fg("border", theme.boxSharp.horizontal.repeat(menuWidth)), 0, 0));
|
|
431
444
|
}
|
|
432
445
|
|
|
433
446
|
handleInput(keyData: string): void {
|
|
@@ -15,6 +15,7 @@ export class OAuthSelectorComponent extends Container {
|
|
|
15
15
|
private authStorage: AuthStorage;
|
|
16
16
|
private onSelectCallback: (providerId: string) => void;
|
|
17
17
|
private onCancelCallback: () => void;
|
|
18
|
+
private statusMessage: string | undefined;
|
|
18
19
|
|
|
19
20
|
constructor(
|
|
20
21
|
mode: "login" | "logout",
|
|
@@ -71,11 +72,11 @@ export class OAuthSelectorComponent extends Container {
|
|
|
71
72
|
// Check if user is logged in for this provider
|
|
72
73
|
const credentials = this.authStorage.get(provider.id);
|
|
73
74
|
const isLoggedIn = credentials?.type === "oauth";
|
|
74
|
-
const statusIndicator = isLoggedIn ? theme.fg("success",
|
|
75
|
+
const statusIndicator = isLoggedIn ? theme.fg("success", ` ${theme.status.success} logged in`) : "";
|
|
75
76
|
|
|
76
77
|
let line = "";
|
|
77
78
|
if (isSelected) {
|
|
78
|
-
const prefix = theme.fg("accent",
|
|
79
|
+
const prefix = theme.fg("accent", `${theme.nav.cursor} `);
|
|
79
80
|
const text = isAvailable ? theme.fg("accent", provider.name) : theme.fg("dim", provider.name);
|
|
80
81
|
line = prefix + text + statusIndicator;
|
|
81
82
|
} else {
|
|
@@ -92,24 +93,39 @@ export class OAuthSelectorComponent extends Container {
|
|
|
92
93
|
this.mode === "login" ? "No OAuth providers available" : "No OAuth providers logged in. Use /login first.";
|
|
93
94
|
this.listContainer.addChild(new TruncatedText(theme.fg("muted", ` ${message}`), 0, 0));
|
|
94
95
|
}
|
|
96
|
+
|
|
97
|
+
if (this.statusMessage) {
|
|
98
|
+
this.listContainer.addChild(new Spacer(1));
|
|
99
|
+
this.listContainer.addChild(new TruncatedText(theme.fg("warning", ` ${this.statusMessage}`), 0, 0));
|
|
100
|
+
}
|
|
95
101
|
}
|
|
96
102
|
|
|
97
103
|
handleInput(keyData: string): void {
|
|
98
104
|
// Up arrow
|
|
99
105
|
if (isArrowUp(keyData)) {
|
|
100
|
-
|
|
106
|
+
if (this.allProviders.length > 0) {
|
|
107
|
+
this.selectedIndex = this.selectedIndex === 0 ? this.allProviders.length - 1 : this.selectedIndex - 1;
|
|
108
|
+
}
|
|
109
|
+
this.statusMessage = undefined;
|
|
101
110
|
this.updateList();
|
|
102
111
|
}
|
|
103
112
|
// Down arrow
|
|
104
113
|
else if (isArrowDown(keyData)) {
|
|
105
|
-
|
|
114
|
+
if (this.allProviders.length > 0) {
|
|
115
|
+
this.selectedIndex = this.selectedIndex === this.allProviders.length - 1 ? 0 : this.selectedIndex + 1;
|
|
116
|
+
}
|
|
117
|
+
this.statusMessage = undefined;
|
|
106
118
|
this.updateList();
|
|
107
119
|
}
|
|
108
120
|
// Enter
|
|
109
121
|
else if (isEnter(keyData)) {
|
|
110
122
|
const selectedProvider = this.allProviders[this.selectedIndex];
|
|
111
123
|
if (selectedProvider?.available) {
|
|
124
|
+
this.statusMessage = undefined;
|
|
112
125
|
this.onSelectCallback(selectedProvider.id);
|
|
126
|
+
} else if (selectedProvider) {
|
|
127
|
+
this.statusMessage = "Provider unavailable in this environment.";
|
|
128
|
+
this.updateList();
|
|
113
129
|
}
|
|
114
130
|
}
|
|
115
131
|
// Escape
|
|
@@ -63,13 +63,15 @@ export class PluginListComponent extends Container {
|
|
|
63
63
|
}
|
|
64
64
|
|
|
65
65
|
const items: SelectItem[] = plugins.map((p) => {
|
|
66
|
-
const status = p.enabled
|
|
66
|
+
const status = p.enabled
|
|
67
|
+
? theme.fg("success", theme.status.enabled)
|
|
68
|
+
: theme.fg("muted", theme.status.disabled);
|
|
67
69
|
const featureCount = p.manifest.features ? Object.keys(p.manifest.features).length : 0;
|
|
68
70
|
const enabledCount = p.enabledFeatures?.length ?? featureCount;
|
|
69
71
|
|
|
70
72
|
let details = `v${p.version}`;
|
|
71
73
|
if (featureCount > 0) {
|
|
72
|
-
details += `
|
|
74
|
+
details += ` ${theme.sep.dot} ${enabledCount}/${featureCount} features`;
|
|
73
75
|
}
|
|
74
76
|
|
|
75
77
|
return {
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
Spacer,
|
|
11
11
|
Text,
|
|
12
12
|
truncateToWidth,
|
|
13
|
+
visibleWidth,
|
|
13
14
|
} from "@oh-my-pi/pi-tui";
|
|
14
15
|
import type { SessionInfo } from "../../../core/session-manager";
|
|
15
16
|
import { fuzzyFilter } from "../../../utils/fuzzy";
|
|
@@ -99,21 +100,23 @@ class SessionList implements Component {
|
|
|
99
100
|
const normalizedMessage = session.firstMessage.replace(/\n/g, " ").trim();
|
|
100
101
|
|
|
101
102
|
// First line: cursor + title (or first message if no title)
|
|
102
|
-
const
|
|
103
|
-
const
|
|
103
|
+
const cursorSymbol = `${theme.nav.cursor} `;
|
|
104
|
+
const cursorWidth = visibleWidth(cursorSymbol);
|
|
105
|
+
const cursor = isSelected ? theme.fg("accent", cursorSymbol) : " ".repeat(cursorWidth);
|
|
106
|
+
const maxWidth = width - cursorWidth; // Account for cursor width
|
|
104
107
|
|
|
105
108
|
if (session.title) {
|
|
106
109
|
// Has title: show title on first line, dimmed first message on second line
|
|
107
|
-
const truncatedTitle = truncateToWidth(session.title, maxWidth,
|
|
110
|
+
const truncatedTitle = truncateToWidth(session.title, maxWidth, theme.format.ellipsis);
|
|
108
111
|
const titleLine = cursor + (isSelected ? theme.bold(truncatedTitle) : truncatedTitle);
|
|
109
112
|
lines.push(titleLine);
|
|
110
113
|
|
|
111
114
|
// Second line: dimmed first message preview
|
|
112
|
-
const truncatedPreview = truncateToWidth(normalizedMessage, maxWidth,
|
|
115
|
+
const truncatedPreview = truncateToWidth(normalizedMessage, maxWidth, theme.format.ellipsis);
|
|
113
116
|
lines.push(` ${theme.fg("dim", truncatedPreview)}`);
|
|
114
117
|
} else {
|
|
115
118
|
// No title: show first message as main line
|
|
116
|
-
const truncatedMsg = truncateToWidth(normalizedMessage, maxWidth,
|
|
119
|
+
const truncatedMsg = truncateToWidth(normalizedMessage, maxWidth, theme.format.ellipsis);
|
|
117
120
|
const messageLine = cursor + (isSelected ? theme.bold(truncatedMsg) : truncatedMsg);
|
|
118
121
|
lines.push(messageLine);
|
|
119
122
|
}
|
|
@@ -121,7 +124,7 @@ class SessionList implements Component {
|
|
|
121
124
|
// Metadata line: date + message count
|
|
122
125
|
const modified = formatDate(session.modified);
|
|
123
126
|
const msgCount = `${session.messageCount} message${session.messageCount !== 1 ? "s" : ""}`;
|
|
124
|
-
const metadata = ` ${modified}
|
|
127
|
+
const metadata = ` ${modified} ${theme.sep.dot} ${msgCount}`;
|
|
125
128
|
const metadataLine = theme.fg("dim", truncateToWidth(metadata, width, ""));
|
|
126
129
|
|
|
127
130
|
lines.push(metadataLine);
|
|
@@ -10,7 +10,13 @@
|
|
|
10
10
|
|
|
11
11
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
12
12
|
import { getCapabilities } from "@oh-my-pi/pi-tui";
|
|
13
|
-
import type {
|
|
13
|
+
import type {
|
|
14
|
+
SettingsManager,
|
|
15
|
+
StatusLinePreset,
|
|
16
|
+
StatusLineSeparatorStyle,
|
|
17
|
+
SymbolPreset,
|
|
18
|
+
} from "../../../core/settings-manager";
|
|
19
|
+
import { getPreset } from "./status-line/presets";
|
|
14
20
|
|
|
15
21
|
// Setting value types
|
|
16
22
|
export type SettingValue = boolean | string;
|
|
@@ -208,6 +214,20 @@ export const SETTINGS_DEFS: SettingDef[] = [
|
|
|
208
214
|
set: (sm, v) => sm.setTheme(v),
|
|
209
215
|
getOptions: () => [], // Filled dynamically from context
|
|
210
216
|
},
|
|
217
|
+
{
|
|
218
|
+
id: "symbolPreset",
|
|
219
|
+
tab: "config",
|
|
220
|
+
type: "submenu",
|
|
221
|
+
label: "Symbol preset",
|
|
222
|
+
description: "Icon/symbol style (overrides theme default)",
|
|
223
|
+
get: (sm) => sm.getSymbolPreset() ?? "unicode",
|
|
224
|
+
set: (sm, v) => sm.setSymbolPreset(v as SymbolPreset),
|
|
225
|
+
getOptions: () => [
|
|
226
|
+
{ value: "unicode", label: "Unicode", description: "Standard Unicode symbols (default)" },
|
|
227
|
+
{ value: "nerd", label: "Nerd Font", description: "Nerd Font icons (requires Nerd Font)" },
|
|
228
|
+
{ value: "ascii", label: "ASCII", description: "ASCII-only characters (maximum compatibility)" },
|
|
229
|
+
],
|
|
230
|
+
},
|
|
211
231
|
|
|
212
232
|
// LSP tab
|
|
213
233
|
{
|
|
@@ -293,6 +313,270 @@ export const SETTINGS_DEFS: SettingDef[] = [
|
|
|
293
313
|
get: (sm) => sm.getExaSettings().enableWebsets,
|
|
294
314
|
set: (sm, v) => sm.setExaWebsetsEnabled(v),
|
|
295
315
|
},
|
|
316
|
+
|
|
317
|
+
// Status Line tab
|
|
318
|
+
{
|
|
319
|
+
id: "statusLinePreset",
|
|
320
|
+
tab: "status",
|
|
321
|
+
type: "submenu",
|
|
322
|
+
label: "Preset",
|
|
323
|
+
description: "Pre-built status line configurations",
|
|
324
|
+
get: (sm) => sm.getStatusLinePreset(),
|
|
325
|
+
set: (sm, v) => sm.setStatusLinePreset(v as StatusLinePreset),
|
|
326
|
+
getOptions: () => [
|
|
327
|
+
{ value: "default", label: "Default", description: "Model, path, git, context, tokens, cost" },
|
|
328
|
+
{ value: "minimal", label: "Minimal", description: "Path and git only" },
|
|
329
|
+
{ value: "compact", label: "Compact", description: "Model, git, cost, context" },
|
|
330
|
+
{ value: "full", label: "Full", description: "All segments including time" },
|
|
331
|
+
{ value: "nerd", label: "Nerd", description: "Maximum info with Nerd Font icons" },
|
|
332
|
+
{ value: "ascii", label: "ASCII", description: "No special characters" },
|
|
333
|
+
{ value: "custom", label: "Custom", description: "User-defined segments" },
|
|
334
|
+
],
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
id: "statusLineSeparator",
|
|
338
|
+
tab: "status",
|
|
339
|
+
type: "submenu",
|
|
340
|
+
label: "Separator style",
|
|
341
|
+
description: "Style of separators between segments",
|
|
342
|
+
get: (sm) => {
|
|
343
|
+
const settings = sm.getStatusLineSettings();
|
|
344
|
+
if (settings.separator) return settings.separator;
|
|
345
|
+
return getPreset(sm.getStatusLinePreset()).separator;
|
|
346
|
+
},
|
|
347
|
+
set: (sm, v) => sm.setStatusLineSeparator(v as StatusLineSeparatorStyle),
|
|
348
|
+
getOptions: () => [
|
|
349
|
+
{ value: "powerline", label: "Powerline", description: "Solid arrows (requires Nerd Font)" },
|
|
350
|
+
{ value: "powerline-thin", label: "Thin chevron", description: "Thin arrows (requires Nerd Font)" },
|
|
351
|
+
{ value: "slash", label: "Slash", description: "Forward slashes" },
|
|
352
|
+
{ value: "pipe", label: "Pipe", description: "Vertical pipes" },
|
|
353
|
+
{ value: "block", label: "Block", description: "Solid blocks" },
|
|
354
|
+
{ value: "none", label: "None", description: "Space only" },
|
|
355
|
+
{ value: "ascii", label: "ASCII", description: "Greater-than signs" },
|
|
356
|
+
],
|
|
357
|
+
},
|
|
358
|
+
{
|
|
359
|
+
id: "statusLineShowHooks",
|
|
360
|
+
tab: "status",
|
|
361
|
+
type: "boolean",
|
|
362
|
+
label: "Show hook status",
|
|
363
|
+
description: "Display hook status messages below status line",
|
|
364
|
+
get: (sm) => sm.getStatusLineShowHookStatus(),
|
|
365
|
+
set: (sm, v) => sm.setStatusLineShowHookStatus(v),
|
|
366
|
+
},
|
|
367
|
+
{
|
|
368
|
+
id: "statusLineSegments",
|
|
369
|
+
tab: "status",
|
|
370
|
+
type: "submenu",
|
|
371
|
+
label: "Configure segments",
|
|
372
|
+
description: "Choose and arrange status line segments",
|
|
373
|
+
get: () => "configure...",
|
|
374
|
+
set: () => {}, // Handled specially
|
|
375
|
+
getOptions: () => [{ value: "open", label: "Open segment editor..." }],
|
|
376
|
+
},
|
|
377
|
+
{
|
|
378
|
+
id: "statusLineModelThinking",
|
|
379
|
+
tab: "status",
|
|
380
|
+
type: "enum",
|
|
381
|
+
label: "Model thinking level",
|
|
382
|
+
description: "Show thinking level in the model segment",
|
|
383
|
+
values: ["default", "on", "off"],
|
|
384
|
+
get: (sm) => {
|
|
385
|
+
const value = sm.getStatusLineSegmentOptions().model?.showThinkingLevel;
|
|
386
|
+
if (value === undefined) return "default";
|
|
387
|
+
return value ? "on" : "off";
|
|
388
|
+
},
|
|
389
|
+
set: (sm, v) => {
|
|
390
|
+
if (v === "default") {
|
|
391
|
+
sm.clearStatusLineSegmentOption("model", "showThinkingLevel");
|
|
392
|
+
} else {
|
|
393
|
+
sm.setStatusLineSegmentOption("model", "showThinkingLevel", v === "on");
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
{
|
|
398
|
+
id: "statusLinePathAbbreviate",
|
|
399
|
+
tab: "status",
|
|
400
|
+
type: "enum",
|
|
401
|
+
label: "Path abbreviate",
|
|
402
|
+
description: "Use ~ and strip home prefix in path segment",
|
|
403
|
+
values: ["default", "on", "off"],
|
|
404
|
+
get: (sm) => {
|
|
405
|
+
const value = sm.getStatusLineSegmentOptions().path?.abbreviate;
|
|
406
|
+
if (value === undefined) return "default";
|
|
407
|
+
return value ? "on" : "off";
|
|
408
|
+
},
|
|
409
|
+
set: (sm, v) => {
|
|
410
|
+
if (v === "default") {
|
|
411
|
+
sm.clearStatusLineSegmentOption("path", "abbreviate");
|
|
412
|
+
} else {
|
|
413
|
+
sm.setStatusLineSegmentOption("path", "abbreviate", v === "on");
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
},
|
|
417
|
+
{
|
|
418
|
+
id: "statusLinePathMaxLength",
|
|
419
|
+
tab: "status",
|
|
420
|
+
type: "submenu",
|
|
421
|
+
label: "Path max length",
|
|
422
|
+
description: "Maximum length for displayed path",
|
|
423
|
+
get: (sm) => {
|
|
424
|
+
const value = sm.getStatusLineSegmentOptions().path?.maxLength;
|
|
425
|
+
return typeof value === "number" ? String(value) : "default";
|
|
426
|
+
},
|
|
427
|
+
set: (sm, v) => {
|
|
428
|
+
if (v === "default") {
|
|
429
|
+
sm.clearStatusLineSegmentOption("path", "maxLength");
|
|
430
|
+
} else {
|
|
431
|
+
sm.setStatusLineSegmentOption("path", "maxLength", Number.parseInt(v, 10));
|
|
432
|
+
}
|
|
433
|
+
},
|
|
434
|
+
getOptions: () => [
|
|
435
|
+
{ value: "default", label: "Preset default" },
|
|
436
|
+
{ value: "20", label: "20" },
|
|
437
|
+
{ value: "30", label: "30" },
|
|
438
|
+
{ value: "40", label: "40" },
|
|
439
|
+
{ value: "50", label: "50" },
|
|
440
|
+
{ value: "60", label: "60" },
|
|
441
|
+
{ value: "80", label: "80" },
|
|
442
|
+
],
|
|
443
|
+
},
|
|
444
|
+
{
|
|
445
|
+
id: "statusLinePathStripWorkPrefix",
|
|
446
|
+
tab: "status",
|
|
447
|
+
type: "enum",
|
|
448
|
+
label: "Path strip /work",
|
|
449
|
+
description: "Strip /work prefix in path segment",
|
|
450
|
+
values: ["default", "on", "off"],
|
|
451
|
+
get: (sm) => {
|
|
452
|
+
const value = sm.getStatusLineSegmentOptions().path?.stripWorkPrefix;
|
|
453
|
+
if (value === undefined) return "default";
|
|
454
|
+
return value ? "on" : "off";
|
|
455
|
+
},
|
|
456
|
+
set: (sm, v) => {
|
|
457
|
+
if (v === "default") {
|
|
458
|
+
sm.clearStatusLineSegmentOption("path", "stripWorkPrefix");
|
|
459
|
+
} else {
|
|
460
|
+
sm.setStatusLineSegmentOption("path", "stripWorkPrefix", v === "on");
|
|
461
|
+
}
|
|
462
|
+
},
|
|
463
|
+
},
|
|
464
|
+
{
|
|
465
|
+
id: "statusLineGitShowBranch",
|
|
466
|
+
tab: "status",
|
|
467
|
+
type: "enum",
|
|
468
|
+
label: "Git show branch",
|
|
469
|
+
description: "Show branch name in git segment",
|
|
470
|
+
values: ["default", "on", "off"],
|
|
471
|
+
get: (sm) => {
|
|
472
|
+
const value = sm.getStatusLineSegmentOptions().git?.showBranch;
|
|
473
|
+
if (value === undefined) return "default";
|
|
474
|
+
return value ? "on" : "off";
|
|
475
|
+
},
|
|
476
|
+
set: (sm, v) => {
|
|
477
|
+
if (v === "default") {
|
|
478
|
+
sm.clearStatusLineSegmentOption("git", "showBranch");
|
|
479
|
+
} else {
|
|
480
|
+
sm.setStatusLineSegmentOption("git", "showBranch", v === "on");
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
},
|
|
484
|
+
{
|
|
485
|
+
id: "statusLineGitShowStaged",
|
|
486
|
+
tab: "status",
|
|
487
|
+
type: "enum",
|
|
488
|
+
label: "Git show staged",
|
|
489
|
+
description: "Show staged file count in git segment",
|
|
490
|
+
values: ["default", "on", "off"],
|
|
491
|
+
get: (sm) => {
|
|
492
|
+
const value = sm.getStatusLineSegmentOptions().git?.showStaged;
|
|
493
|
+
if (value === undefined) return "default";
|
|
494
|
+
return value ? "on" : "off";
|
|
495
|
+
},
|
|
496
|
+
set: (sm, v) => {
|
|
497
|
+
if (v === "default") {
|
|
498
|
+
sm.clearStatusLineSegmentOption("git", "showStaged");
|
|
499
|
+
} else {
|
|
500
|
+
sm.setStatusLineSegmentOption("git", "showStaged", v === "on");
|
|
501
|
+
}
|
|
502
|
+
},
|
|
503
|
+
},
|
|
504
|
+
{
|
|
505
|
+
id: "statusLineGitShowUnstaged",
|
|
506
|
+
tab: "status",
|
|
507
|
+
type: "enum",
|
|
508
|
+
label: "Git show unstaged",
|
|
509
|
+
description: "Show unstaged file count in git segment",
|
|
510
|
+
values: ["default", "on", "off"],
|
|
511
|
+
get: (sm) => {
|
|
512
|
+
const value = sm.getStatusLineSegmentOptions().git?.showUnstaged;
|
|
513
|
+
if (value === undefined) return "default";
|
|
514
|
+
return value ? "on" : "off";
|
|
515
|
+
},
|
|
516
|
+
set: (sm, v) => {
|
|
517
|
+
if (v === "default") {
|
|
518
|
+
sm.clearStatusLineSegmentOption("git", "showUnstaged");
|
|
519
|
+
} else {
|
|
520
|
+
sm.setStatusLineSegmentOption("git", "showUnstaged", v === "on");
|
|
521
|
+
}
|
|
522
|
+
},
|
|
523
|
+
},
|
|
524
|
+
{
|
|
525
|
+
id: "statusLineGitShowUntracked",
|
|
526
|
+
tab: "status",
|
|
527
|
+
type: "enum",
|
|
528
|
+
label: "Git show untracked",
|
|
529
|
+
description: "Show untracked file count in git segment",
|
|
530
|
+
values: ["default", "on", "off"],
|
|
531
|
+
get: (sm) => {
|
|
532
|
+
const value = sm.getStatusLineSegmentOptions().git?.showUntracked;
|
|
533
|
+
if (value === undefined) return "default";
|
|
534
|
+
return value ? "on" : "off";
|
|
535
|
+
},
|
|
536
|
+
set: (sm, v) => {
|
|
537
|
+
if (v === "default") {
|
|
538
|
+
sm.clearStatusLineSegmentOption("git", "showUntracked");
|
|
539
|
+
} else {
|
|
540
|
+
sm.setStatusLineSegmentOption("git", "showUntracked", v === "on");
|
|
541
|
+
}
|
|
542
|
+
},
|
|
543
|
+
},
|
|
544
|
+
{
|
|
545
|
+
id: "statusLineTimeFormat",
|
|
546
|
+
tab: "status",
|
|
547
|
+
type: "enum",
|
|
548
|
+
label: "Time format",
|
|
549
|
+
description: "Clock segment time format",
|
|
550
|
+
values: ["default", "12h", "24h"],
|
|
551
|
+
get: (sm) => sm.getStatusLineSegmentOptions().time?.format ?? "default",
|
|
552
|
+
set: (sm, v) => {
|
|
553
|
+
if (v === "default") {
|
|
554
|
+
sm.clearStatusLineSegmentOption("time", "format");
|
|
555
|
+
} else {
|
|
556
|
+
sm.setStatusLineSegmentOption("time", "format", v);
|
|
557
|
+
}
|
|
558
|
+
},
|
|
559
|
+
},
|
|
560
|
+
{
|
|
561
|
+
id: "statusLineTimeShowSeconds",
|
|
562
|
+
tab: "status",
|
|
563
|
+
type: "enum",
|
|
564
|
+
label: "Time show seconds",
|
|
565
|
+
description: "Include seconds in clock segment",
|
|
566
|
+
values: ["default", "on", "off"],
|
|
567
|
+
get: (sm) => {
|
|
568
|
+
const value = sm.getStatusLineSegmentOptions().time?.showSeconds;
|
|
569
|
+
if (value === undefined) return "default";
|
|
570
|
+
return value ? "on" : "off";
|
|
571
|
+
},
|
|
572
|
+
set: (sm, v) => {
|
|
573
|
+
if (v === "default") {
|
|
574
|
+
sm.clearStatusLineSegmentOption("time", "showSeconds");
|
|
575
|
+
} else {
|
|
576
|
+
sm.setStatusLineSegmentOption("time", "showSeconds", v === "on");
|
|
577
|
+
}
|
|
578
|
+
},
|
|
579
|
+
},
|
|
296
580
|
];
|
|
297
581
|
|
|
298
582
|
/**
|