@oh-my-pi/pi-coding-agent 15.8.3 → 15.9.1
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 +113 -31
- package/dist/types/capability/skill.d.ts +7 -0
- package/dist/types/cli/update-cli.d.ts +15 -1
- package/dist/types/config/append-only-context-mode.d.ts +8 -0
- package/dist/types/config/model-registry.d.ts +3 -0
- package/dist/types/config/models-config-schema.d.ts +15 -0
- package/dist/types/config/settings-schema.d.ts +38 -24
- package/dist/types/debug/protocol-probe.d.ts +38 -0
- package/dist/types/debug/terminal-info.d.ts +34 -0
- package/dist/types/exa/mcp-client.d.ts +2 -1
- package/dist/types/export/html/template.generated.d.ts +1 -1
- package/dist/types/extensibility/custom-tools/types.d.ts +1 -1
- package/dist/types/extensibility/extensions/types.d.ts +11 -0
- package/dist/types/extensibility/shared-events.d.ts +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/mcp/json-rpc.d.ts +6 -1
- package/dist/types/mcp/tool-bridge.d.ts +4 -0
- package/dist/types/mcp/transports/stdio.d.ts +16 -7
- package/dist/types/mnemopi/state.d.ts +2 -2
- package/dist/types/modes/components/agent-dashboard.d.ts +1 -0
- package/dist/types/modes/components/assistant-message.d.ts +3 -2
- package/dist/types/modes/components/extensions/extension-dashboard.d.ts +1 -0
- package/dist/types/modes/components/hook-selector.d.ts +11 -0
- package/dist/types/modes/components/plugin-settings.d.ts +40 -8
- package/dist/types/modes/components/session-selector.d.ts +8 -3
- package/dist/types/modes/components/settings-selector.d.ts +1 -1
- package/dist/types/modes/components/todo-reminder.d.ts +1 -1
- package/dist/types/modes/components/transcript-container.d.ts +36 -0
- package/dist/types/modes/interactive-mode.d.ts +2 -1
- package/dist/types/modes/rpc/rpc-types.d.ts +1 -1
- package/dist/types/modes/theme/theme.d.ts +20 -1
- package/dist/types/modes/utils/keybinding-matchers.d.ts +4 -0
- package/dist/types/plan-mode/plan-handoff.d.ts +20 -0
- package/dist/types/session/agent-session.d.ts +5 -2
- package/dist/types/session/history-storage.d.ts +3 -4
- package/dist/types/session/indexed-session-storage.d.ts +59 -0
- package/dist/types/session/messages.d.ts +1 -0
- package/dist/types/session/redis-session-storage.d.ts +12 -85
- package/dist/types/session/session-manager.d.ts +21 -0
- package/dist/types/session/session-storage.d.ts +5 -7
- package/dist/types/session/sql-session-storage.d.ts +16 -85
- package/dist/types/slash-commands/types.d.ts +17 -4
- package/dist/types/task/executor.d.ts +9 -0
- package/dist/types/task/index.d.ts +3 -1
- package/dist/types/telemetry-export.d.ts +19 -0
- package/dist/types/tiny/compiled-runtime.d.ts +35 -0
- package/dist/types/tiny/text.d.ts +17 -0
- package/dist/types/tools/ask.d.ts +1 -0
- package/dist/types/tools/index.d.ts +4 -2
- package/dist/types/tools/path-utils.d.ts +1 -1
- package/dist/types/tools/search.d.ts +2 -2
- package/dist/types/tools/{todo-write.d.ts → todo.d.ts} +18 -18
- package/dist/types/utils/session-color.d.ts +7 -2
- package/dist/types/web/search/index.d.ts +1 -1
- package/dist/types/web/search/provider.d.ts +2 -2
- package/dist/types/web/search/providers/base.d.ts +14 -0
- package/dist/types/web/search/providers/exa.d.ts +9 -0
- package/dist/types/web/search/providers/perplexity.d.ts +2 -2
- package/dist/types/web/search/types.d.ts +66 -1
- package/package.json +15 -9
- package/scripts/build-binary.ts +12 -0
- package/src/capability/skill.ts +7 -0
- package/src/cli/args.ts +1 -1
- package/src/cli/session-picker.ts +1 -0
- package/src/cli/update-cli.ts +54 -2
- package/src/commands/completions.ts +1 -1
- package/src/config/append-only-context-mode.ts +37 -0
- package/src/config/models-config-schema.ts +1 -0
- package/src/config/settings-schema.ts +24 -57
- package/src/debug/index.ts +67 -1
- package/src/debug/protocol-probe.ts +267 -0
- package/src/debug/terminal-info.ts +127 -0
- package/src/exa/mcp-client.ts +11 -5
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +3 -3
- package/src/extensibility/custom-tools/types.ts +1 -1
- package/src/extensibility/extensions/types.ts +11 -0
- package/src/extensibility/shared-events.ts +1 -1
- package/src/extensibility/skills.ts +3 -3
- package/src/index.ts +1 -0
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/main.ts +16 -2
- package/src/mcp/json-rpc.ts +8 -0
- package/src/mcp/render.ts +3 -0
- package/src/mcp/tool-bridge.ts +10 -2
- package/src/mcp/transports/http.ts +33 -16
- package/src/mcp/transports/stdio.ts +37 -12
- package/src/mnemopi/state.ts +4 -4
- package/src/modes/acp/acp-agent.ts +168 -3
- package/src/modes/acp/acp-event-mapper.ts +7 -7
- package/src/modes/components/agent-dashboard.ts +103 -31
- package/src/modes/components/assistant-message.ts +3 -2
- package/src/modes/components/extensions/extension-dashboard.ts +56 -10
- package/src/modes/components/history-search.ts +128 -14
- package/src/modes/components/hook-selector.ts +149 -14
- package/src/modes/components/plugin-settings.ts +270 -36
- package/src/modes/components/session-selector.ts +81 -19
- package/src/modes/components/settings-selector.ts +1 -1
- package/src/modes/components/status-line/segments.ts +2 -1
- package/src/modes/components/status-line.ts +1 -1
- package/src/modes/components/tips.txt +6 -2
- package/src/modes/components/todo-reminder.ts +1 -1
- package/src/modes/components/tool-execution.ts +9 -4
- package/src/modes/components/transcript-container.ts +109 -0
- package/src/modes/controllers/command-controller.ts +4 -3
- package/src/modes/controllers/event-controller.ts +12 -7
- package/src/modes/controllers/extension-ui-controller.ts +3 -0
- package/src/modes/controllers/input-controller.ts +10 -5
- package/src/modes/controllers/selector-controller.ts +30 -19
- package/src/modes/controllers/todo-command-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +56 -9
- package/src/modes/print-mode.ts +5 -0
- package/src/modes/rpc/rpc-types.ts +1 -1
- package/src/modes/theme/theme.ts +48 -8
- package/src/modes/utils/keybinding-matchers.ts +10 -0
- package/src/modes/utils/ui-helpers.ts +1 -0
- package/src/plan-mode/plan-handoff.ts +37 -0
- package/src/priority.json +4 -0
- package/src/prompts/goals/goal-continuation.md +1 -1
- package/src/prompts/steering/user-interjection.md +10 -0
- package/src/prompts/system/agent-creation-architect.md +1 -26
- package/src/prompts/system/eager-todo.md +3 -3
- package/src/prompts/system/orchestrate-notice.md +5 -5
- package/src/prompts/system/plan-mode-approved.md +14 -17
- package/src/prompts/system/plan-mode-reference.md +3 -6
- package/src/prompts/system/subagent-system-prompt.md +11 -0
- package/src/prompts/system/system-prompt.md +143 -145
- package/src/prompts/system/title-system.md +3 -2
- package/src/prompts/system/workflow-notice.md +1 -1
- package/src/prompts/tools/browser.md +33 -30
- package/src/prompts/tools/render-mermaid.md +2 -2
- package/src/prompts/tools/search.md +1 -1
- package/src/prompts/tools/task.md +3 -1
- package/src/prompts/tools/{todo-write.md → todo.md} +5 -5
- package/src/sdk.ts +6 -21
- package/src/session/agent-session.ts +44 -21
- package/src/session/history-storage.ts +11 -18
- package/src/session/indexed-session-storage.ts +430 -0
- package/src/session/messages.ts +80 -0
- package/src/session/redis-session-storage.ts +66 -377
- package/src/session/session-manager.ts +109 -23
- package/src/session/session-storage.ts +148 -68
- package/src/session/sql-session-storage.ts +131 -382
- package/src/slash-commands/helpers/todo.ts +2 -2
- package/src/slash-commands/types.ts +27 -10
- package/src/task/executor.ts +9 -1
- package/src/task/index.ts +51 -1
- package/src/telemetry-export.ts +126 -0
- package/src/tiny/compiled-runtime.ts +179 -0
- package/src/tiny/text.ts +112 -1
- package/src/tiny/worker.ts +24 -2
- package/src/tools/ask.ts +133 -87
- package/src/tools/fetch.ts +17 -4
- package/src/tools/find.ts +2 -2
- package/src/tools/index.ts +6 -4
- package/src/tools/memory-recall.ts +1 -1
- package/src/tools/memory-reflect.ts +1 -1
- package/src/tools/path-utils.ts +16 -7
- package/src/tools/renderers.ts +2 -2
- package/src/tools/search.ts +6 -3
- package/src/tools/ssh.ts +26 -10
- package/src/tools/{todo-write.ts → todo.ts} +32 -35
- package/src/tools/write.ts +14 -2
- package/src/tui/status-line.ts +15 -4
- package/src/utils/session-color.ts +39 -14
- package/src/utils/title-generator.ts +9 -2
- package/src/web/search/index.ts +4 -2
- package/src/web/search/provider.ts +19 -35
- package/src/web/search/providers/base.ts +17 -0
- package/src/web/search/providers/exa.ts +111 -7
- package/src/web/search/providers/perplexity.ts +8 -4
- package/src/web/search/types.ts +74 -32
|
@@ -2,8 +2,9 @@
|
|
|
2
2
|
* Plugin settings UI components.
|
|
3
3
|
*
|
|
4
4
|
* Provides a hierarchical settings interface:
|
|
5
|
-
* - Plugin list (
|
|
6
|
-
* -
|
|
5
|
+
* - Plugin list (npm plugins + marketplace plugins)
|
|
6
|
+
* - npm plugin detail (enable/disable, features, config)
|
|
7
|
+
* - Marketplace plugin detail (enable/disable + read-only metadata)
|
|
7
8
|
* - Feature toggles
|
|
8
9
|
* - Config value editor
|
|
9
10
|
*/
|
|
@@ -17,9 +18,20 @@ import {
|
|
|
17
18
|
Spacer,
|
|
18
19
|
Text,
|
|
19
20
|
} from "@oh-my-pi/pi-tui";
|
|
21
|
+
import { logger } from "@oh-my-pi/pi-utils";
|
|
22
|
+
import { clearPluginRootsAndCaches, resolveOrDefaultProjectRegistryPath } from "../../discovery/helpers";
|
|
20
23
|
import { PluginManager } from "../../extensibility/plugins/manager";
|
|
24
|
+
import type { InstalledPluginSummary } from "../../extensibility/plugins/marketplace";
|
|
25
|
+
import {
|
|
26
|
+
getInstalledPluginsRegistryPath,
|
|
27
|
+
getMarketplacesCacheDir,
|
|
28
|
+
getMarketplacesRegistryPath,
|
|
29
|
+
getPluginsCacheDir,
|
|
30
|
+
MarketplaceManager,
|
|
31
|
+
} from "../../extensibility/plugins/marketplace";
|
|
21
32
|
import type { InstalledPlugin, PluginSettingSchema } from "../../extensibility/plugins/types";
|
|
22
33
|
import { getSelectListTheme, getSettingsListTheme, theme } from "../../modes/theme/theme";
|
|
34
|
+
import { shortenPath } from "../../tools/render-utils";
|
|
23
35
|
import { DynamicBorder } from "./dynamic-border";
|
|
24
36
|
|
|
25
37
|
/**
|
|
@@ -41,20 +53,53 @@ export function handleInputOrEscape(
|
|
|
41
53
|
// Plugin List Component
|
|
42
54
|
// =============================================================================
|
|
43
55
|
|
|
56
|
+
/**
|
|
57
|
+
* One row in the unified plugin list. npm and marketplace plugins live in
|
|
58
|
+
* separate registries with different shapes, so a tagged union keeps both
|
|
59
|
+
* paths type-safe end-to-end (list rendering, value lookup, detail callback).
|
|
60
|
+
*/
|
|
61
|
+
export type PluginListEntry =
|
|
62
|
+
| { kind: "npm"; plugin: InstalledPlugin }
|
|
63
|
+
| { kind: "marketplace"; plugin: InstalledPluginSummary };
|
|
64
|
+
|
|
44
65
|
export interface PluginListCallbacks {
|
|
45
|
-
|
|
66
|
+
onNpmSelect: (plugin: InstalledPlugin) => void;
|
|
67
|
+
onMarketplaceSelect: (plugin: InstalledPluginSummary) => void;
|
|
46
68
|
onCancel: () => void;
|
|
47
69
|
}
|
|
48
70
|
|
|
49
71
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
72
|
+
* True when the marketplace summary's first entry is not explicitly disabled.
|
|
73
|
+
* Mirrors the `/plugins list` convention: a missing `enabled` flag means enabled.
|
|
74
|
+
*/
|
|
75
|
+
function marketplaceEnabled(summary: InstalledPluginSummary): boolean {
|
|
76
|
+
return summary.entries[0]?.enabled !== false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Stable SelectList value for a list entry. Combined with `findEntryByValue`
|
|
81
|
+
* this keeps lookup correct even when the same plugin id exists in both user
|
|
82
|
+
* and project scope (one of which is `shadowedBy: "project"`).
|
|
83
|
+
*/
|
|
84
|
+
function entryValue(entry: PluginListEntry): string {
|
|
85
|
+
if (entry.kind === "npm") return `npm:${entry.plugin.name}`;
|
|
86
|
+
return `mkt:${entry.plugin.scope}:${entry.plugin.id}`;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function findEntryByValue(entries: ReadonlyArray<PluginListEntry>, value: string): PluginListEntry | undefined {
|
|
90
|
+
return entries.find(e => entryValue(e) === value);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Shows installed plugins from both registries (npm + marketplace) with
|
|
95
|
+
* enable/disable status, scope tag, and shadow indicator. Selecting an entry
|
|
96
|
+
* fans out to the kind-specific detail callback.
|
|
52
97
|
*/
|
|
53
98
|
export class PluginListComponent extends Container {
|
|
54
99
|
readonly #selectList: SelectList;
|
|
55
100
|
|
|
56
101
|
constructor(
|
|
57
|
-
private readonly
|
|
102
|
+
private readonly entries: ReadonlyArray<PluginListEntry>,
|
|
58
103
|
callbacks: PluginListCallbacks,
|
|
59
104
|
) {
|
|
60
105
|
super();
|
|
@@ -64,53 +109,87 @@ export class PluginListComponent extends Container {
|
|
|
64
109
|
this.addChild(new Text(theme.bold(theme.fg("accent", " Plugins")), 0, 0));
|
|
65
110
|
this.addChild(new Spacer(1));
|
|
66
111
|
|
|
67
|
-
if (
|
|
112
|
+
if (entries.length === 0) {
|
|
68
113
|
this.addChild(new Text(theme.fg("muted", " No plugins installed"), 0, 0));
|
|
69
114
|
this.addChild(new Spacer(1));
|
|
70
|
-
this.addChild(new Text(theme.fg("dim", " Install
|
|
115
|
+
this.addChild(new Text(theme.fg("dim", " Install npm plugins: omp plugin install <package>"), 0, 0));
|
|
116
|
+
this.addChild(
|
|
117
|
+
new Text(theme.fg("dim", " Install marketplace plugins: omp plugin install <name>@<marketplace>"), 0, 0),
|
|
118
|
+
);
|
|
71
119
|
this.addChild(new Spacer(1));
|
|
72
120
|
this.addChild(new DynamicBorder());
|
|
73
121
|
|
|
74
|
-
//
|
|
122
|
+
// Empty list still handles Escape so the user can leave the panel.
|
|
75
123
|
this.#selectList = new SelectList([], 1, getSelectListTheme());
|
|
76
124
|
this.#selectList.onCancel = callbacks.onCancel;
|
|
77
125
|
return;
|
|
78
126
|
}
|
|
79
127
|
|
|
80
|
-
const items: SelectItem[] =
|
|
128
|
+
const items: SelectItem[] = entries.map(entry => this.#renderItem(entry));
|
|
129
|
+
|
|
130
|
+
// Marketplace plugin ids (`name@marketplace`) routinely run past the
|
|
131
|
+
// SelectList default primary column (32 chars). Widen the bound so the
|
|
132
|
+
// id remains readable; the description gets whatever width is left.
|
|
133
|
+
this.#selectList = new SelectList(items, Math.min(items.length, 8), getSelectListTheme(), {
|
|
134
|
+
minPrimaryColumnWidth: 24,
|
|
135
|
+
maxPrimaryColumnWidth: 64,
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
this.#selectList.onSelect = item => {
|
|
139
|
+
const found = findEntryByValue(this.entries, item.value);
|
|
140
|
+
if (!found) return;
|
|
141
|
+
if (found.kind === "npm") callbacks.onNpmSelect(found.plugin);
|
|
142
|
+
else callbacks.onMarketplaceSelect(found.plugin);
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
this.#selectList.onCancel = callbacks.onCancel;
|
|
146
|
+
|
|
147
|
+
this.addChild(this.#selectList);
|
|
148
|
+
this.addChild(new Spacer(1));
|
|
149
|
+
this.addChild(new Text(theme.fg("dim", " Enter to configure · Esc to go back"), 0, 0));
|
|
150
|
+
this.addChild(new DynamicBorder());
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
#renderItem(entry: PluginListEntry): SelectItem {
|
|
154
|
+
const kindBadge = theme.fg("dim", entry.kind === "npm" ? "[npm]" : "[marketplace]");
|
|
155
|
+
|
|
156
|
+
if (entry.kind === "npm") {
|
|
157
|
+
const p = entry.plugin;
|
|
81
158
|
const status = p.enabled
|
|
82
159
|
? theme.fg("success", theme.status.enabled)
|
|
83
160
|
: theme.fg("muted", theme.status.disabled);
|
|
84
161
|
const featureCount = p.manifest.features ? Object.keys(p.manifest.features).length : 0;
|
|
85
162
|
const enabledCount = p.enabledFeatures?.length ?? featureCount;
|
|
86
163
|
|
|
87
|
-
let details =
|
|
164
|
+
let details = `${kindBadge} ${theme.sep.dot} v${p.version}`;
|
|
88
165
|
if (featureCount > 0) {
|
|
89
166
|
details += ` ${theme.sep.dot} ${enabledCount}/${featureCount} features`;
|
|
90
167
|
}
|
|
91
168
|
|
|
92
169
|
return {
|
|
93
|
-
value:
|
|
170
|
+
value: entryValue(entry),
|
|
94
171
|
label: `${status} ${p.name}`,
|
|
95
172
|
description: details,
|
|
96
173
|
};
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
this.#selectList = new SelectList(items, Math.min(items.length, 8), getSelectListTheme());
|
|
174
|
+
}
|
|
100
175
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
176
|
+
const summary = entry.plugin;
|
|
177
|
+
const enabled = marketplaceEnabled(summary);
|
|
178
|
+
const status = enabled ? theme.fg("success", theme.status.enabled) : theme.fg("muted", theme.status.disabled);
|
|
179
|
+
const scopeTag = theme.fg("dim", `[${summary.scope}]`);
|
|
180
|
+
const shadowMarker = summary.shadowedBy ? ` ${theme.fg("warning", theme.status.shadowed)}` : "";
|
|
181
|
+
const version = summary.entries[0]?.version ?? "?";
|
|
107
182
|
|
|
108
|
-
|
|
183
|
+
let details = `${kindBadge} ${scopeTag} ${theme.sep.dot} v${version}`;
|
|
184
|
+
if (summary.shadowedBy) {
|
|
185
|
+
details += ` ${theme.sep.dot} shadowed by ${summary.shadowedBy}`;
|
|
186
|
+
}
|
|
109
187
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
188
|
+
return {
|
|
189
|
+
value: entryValue(entry),
|
|
190
|
+
label: `${status} ${summary.id}${shadowMarker}`,
|
|
191
|
+
description: details,
|
|
192
|
+
};
|
|
114
193
|
}
|
|
115
194
|
|
|
116
195
|
handleInput(data: string): void {
|
|
@@ -296,6 +375,98 @@ export class PluginDetailComponent extends Container {
|
|
|
296
375
|
}
|
|
297
376
|
}
|
|
298
377
|
|
|
378
|
+
// =============================================================================
|
|
379
|
+
// Marketplace Plugin Detail Component
|
|
380
|
+
// =============================================================================
|
|
381
|
+
|
|
382
|
+
export interface MarketplacePluginDetailCallbacks {
|
|
383
|
+
onEnabledChange: (enabled: boolean) => void;
|
|
384
|
+
onBack: () => void;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Detail view for a marketplace plugin. Marketplace plugins do not declare
|
|
389
|
+
* features or settings, so the panel exposes a single enable/disable toggle
|
|
390
|
+
* plus the read-only metadata from the installed-plugins registry.
|
|
391
|
+
*/
|
|
392
|
+
export class MarketplacePluginDetailComponent extends Container {
|
|
393
|
+
#settingsList: SettingsList;
|
|
394
|
+
|
|
395
|
+
constructor(
|
|
396
|
+
private plugin: InstalledPluginSummary,
|
|
397
|
+
private readonly callbacks: MarketplacePluginDetailCallbacks,
|
|
398
|
+
) {
|
|
399
|
+
super();
|
|
400
|
+
|
|
401
|
+
const entry = plugin.entries[0];
|
|
402
|
+
const enabled = marketplaceEnabled(plugin);
|
|
403
|
+
|
|
404
|
+
// Header
|
|
405
|
+
this.addChild(new DynamicBorder());
|
|
406
|
+
this.addChild(new Text(theme.bold(theme.fg("accent", ` ${plugin.id}`)), 0, 0));
|
|
407
|
+
|
|
408
|
+
const subtitleParts = [`[${plugin.scope}]`];
|
|
409
|
+
if (plugin.shadowedBy) subtitleParts.push(`${theme.status.shadowed} shadowed by ${plugin.shadowedBy}`);
|
|
410
|
+
this.addChild(new Text(theme.fg("muted", ` ${subtitleParts.join(" ")}`), 0, 0));
|
|
411
|
+
this.addChild(new Spacer(1));
|
|
412
|
+
|
|
413
|
+
const items: SettingItem[] = [
|
|
414
|
+
{
|
|
415
|
+
id: "__enabled__",
|
|
416
|
+
label: "Enabled",
|
|
417
|
+
description: "Enable or disable this marketplace plugin",
|
|
418
|
+
currentValue: enabled ? "true" : "false",
|
|
419
|
+
values: ["true", "false"],
|
|
420
|
+
},
|
|
421
|
+
];
|
|
422
|
+
|
|
423
|
+
this.#settingsList = new SettingsList(
|
|
424
|
+
items,
|
|
425
|
+
items.length,
|
|
426
|
+
getSettingsListTheme(),
|
|
427
|
+
(id, newValue) => {
|
|
428
|
+
if (id === "__enabled__") {
|
|
429
|
+
const next = newValue === "true";
|
|
430
|
+
this.callbacks.onEnabledChange(next);
|
|
431
|
+
this.plugin = {
|
|
432
|
+
...this.plugin,
|
|
433
|
+
entries: this.plugin.entries.map(e => ({ ...e, enabled: next })),
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
},
|
|
437
|
+
this.callbacks.onBack,
|
|
438
|
+
);
|
|
439
|
+
|
|
440
|
+
this.addChild(this.#settingsList);
|
|
441
|
+
this.addChild(new Spacer(1));
|
|
442
|
+
|
|
443
|
+
// Read-only metadata. SettingsList rejects items without `values`/`submenu`,
|
|
444
|
+
// so we render the metadata as plain text rows beneath the toggle.
|
|
445
|
+
this.addChild(new Text(theme.fg("dim", ` version ${entry?.version ?? "(unknown)"}`), 0, 0));
|
|
446
|
+
this.addChild(new Text(theme.fg("dim", ` scope ${plugin.scope}`), 0, 0));
|
|
447
|
+
this.addChild(
|
|
448
|
+
new Text(
|
|
449
|
+
theme.fg("dim", ` install path ${entry?.installPath ? shortenPath(entry.installPath) : "(unknown)"}`),
|
|
450
|
+
0,
|
|
451
|
+
0,
|
|
452
|
+
),
|
|
453
|
+
);
|
|
454
|
+
this.addChild(new Text(theme.fg("dim", ` installed at ${entry?.installedAt ?? "(unknown)"}`), 0, 0));
|
|
455
|
+
this.addChild(new Text(theme.fg("dim", ` last updated ${entry?.lastUpdated ?? "(unknown)"}`), 0, 0));
|
|
456
|
+
if (entry?.gitCommitSha) {
|
|
457
|
+
this.addChild(new Text(theme.fg("dim", ` git sha ${entry.gitCommitSha}`), 0, 0));
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
this.addChild(new Spacer(1));
|
|
461
|
+
this.addChild(new Text(theme.fg("dim", " Enter to toggle · Esc to go back"), 0, 0));
|
|
462
|
+
this.addChild(new DynamicBorder());
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
handleInput(data: string): void {
|
|
466
|
+
this.#settingsList.handleInput(data);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
299
470
|
// =============================================================================
|
|
300
471
|
// Config Submenus
|
|
301
472
|
// =============================================================================
|
|
@@ -408,7 +579,7 @@ class ConfigInputSubmenu extends Container {
|
|
|
408
579
|
|
|
409
580
|
export interface PluginSettingsCallbacks {
|
|
410
581
|
onClose: () => void;
|
|
411
|
-
onPluginChanged: () => void
|
|
582
|
+
onPluginChanged: () => void | Promise<void>;
|
|
412
583
|
}
|
|
413
584
|
|
|
414
585
|
/** Component with handleInput method */
|
|
@@ -421,31 +592,66 @@ interface InputHandler {
|
|
|
421
592
|
* Manages navigation between plugin list and plugin detail views.
|
|
422
593
|
*/
|
|
423
594
|
export class PluginSettingsComponent extends Container {
|
|
595
|
+
#cwd: string;
|
|
424
596
|
#manager: PluginManager;
|
|
425
597
|
#viewComponent: (Container & InputHandler) | null = null;
|
|
426
598
|
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: state tracking for view management
|
|
427
|
-
#currentView: "list" | "detail" = "list";
|
|
599
|
+
#currentView: "list" | "npm-detail" | "marketplace-detail" = "list";
|
|
428
600
|
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: state tracking for view management
|
|
429
601
|
#currentPlugin: InstalledPlugin | null = null;
|
|
602
|
+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: state tracking for view management
|
|
603
|
+
#currentMarketplacePlugin: InstalledPluginSummary | null = null;
|
|
430
604
|
|
|
431
605
|
constructor(
|
|
432
606
|
cwd: string,
|
|
433
607
|
private readonly callbacks: PluginSettingsCallbacks,
|
|
434
608
|
) {
|
|
435
609
|
super();
|
|
610
|
+
this.#cwd = cwd;
|
|
436
611
|
this.#manager = new PluginManager(cwd);
|
|
437
612
|
this.#showPluginList();
|
|
438
613
|
}
|
|
439
614
|
|
|
615
|
+
async #buildMarketplaceManager(): Promise<MarketplaceManager> {
|
|
616
|
+
return new MarketplaceManager({
|
|
617
|
+
marketplacesRegistryPath: getMarketplacesRegistryPath(),
|
|
618
|
+
installedRegistryPath: getInstalledPluginsRegistryPath(),
|
|
619
|
+
projectInstalledRegistryPath: await resolveOrDefaultProjectRegistryPath(this.#cwd),
|
|
620
|
+
marketplacesCacheDir: getMarketplacesCacheDir(),
|
|
621
|
+
pluginsCacheDir: getPluginsCacheDir(),
|
|
622
|
+
clearPluginRootsCache: clearPluginRootsAndCaches,
|
|
623
|
+
});
|
|
624
|
+
}
|
|
625
|
+
|
|
440
626
|
async #showPluginList(): Promise<void> {
|
|
441
627
|
this.#currentView = "list";
|
|
442
628
|
this.#currentPlugin = null;
|
|
629
|
+
this.#currentMarketplacePlugin = null;
|
|
443
630
|
this.clear();
|
|
444
631
|
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
632
|
+
// Surface marketplace failures without taking the npm path down with it —
|
|
633
|
+
// the registry can fail to load (corrupt JSON, missing project root) and
|
|
634
|
+
// the user still benefits from seeing their npm plugins.
|
|
635
|
+
const [npmPlugins, marketplacePlugins] = await Promise.all([
|
|
636
|
+
this.#manager.list(),
|
|
637
|
+
this.#buildMarketplaceManager()
|
|
638
|
+
.then(mgr => mgr.listInstalledPlugins())
|
|
639
|
+
.catch(err => {
|
|
640
|
+
logger.error("Settings → Plugins: failed to list marketplace plugins", {
|
|
641
|
+
error: err instanceof Error ? err.message : String(err),
|
|
642
|
+
});
|
|
643
|
+
return [] as InstalledPluginSummary[];
|
|
644
|
+
}),
|
|
645
|
+
]);
|
|
646
|
+
|
|
647
|
+
const entries: PluginListEntry[] = [
|
|
648
|
+
...npmPlugins.map(plugin => ({ kind: "npm" as const, plugin })),
|
|
649
|
+
...marketplacePlugins.map(plugin => ({ kind: "marketplace" as const, plugin })),
|
|
650
|
+
];
|
|
651
|
+
|
|
652
|
+
this.#viewComponent = new PluginListComponent(entries, {
|
|
653
|
+
onNpmSelect: plugin => this.#showPluginDetail(plugin),
|
|
654
|
+
onMarketplaceSelect: plugin => this.#showMarketplaceDetail(plugin),
|
|
449
655
|
onCancel: () => this.callbacks.onClose(),
|
|
450
656
|
});
|
|
451
657
|
|
|
@@ -453,14 +659,15 @@ export class PluginSettingsComponent extends Container {
|
|
|
453
659
|
}
|
|
454
660
|
|
|
455
661
|
#showPluginDetail(plugin: InstalledPlugin): void {
|
|
456
|
-
this.#currentView = "detail";
|
|
662
|
+
this.#currentView = "npm-detail";
|
|
457
663
|
this.#currentPlugin = plugin;
|
|
664
|
+
this.#currentMarketplacePlugin = null;
|
|
458
665
|
this.clear();
|
|
459
666
|
|
|
460
667
|
this.#viewComponent = new PluginDetailComponent(plugin, this.#manager, {
|
|
461
668
|
onEnabledChange: async enabled => {
|
|
462
669
|
await this.#manager.setEnabled(plugin.name, enabled);
|
|
463
|
-
this.callbacks.onPluginChanged();
|
|
670
|
+
await this.callbacks.onPluginChanged();
|
|
464
671
|
},
|
|
465
672
|
onFeatureChange: async (feature, enabled) => {
|
|
466
673
|
const current = new Set((await this.#manager.getEnabledFeatures(plugin.name)) ?? []);
|
|
@@ -470,11 +677,38 @@ export class PluginSettingsComponent extends Container {
|
|
|
470
677
|
current.delete(feature);
|
|
471
678
|
}
|
|
472
679
|
await this.#manager.setEnabledFeatures(plugin.name, [...current]);
|
|
473
|
-
this.callbacks.onPluginChanged();
|
|
680
|
+
await this.callbacks.onPluginChanged();
|
|
474
681
|
},
|
|
475
682
|
onConfigChange: async (key, value) => {
|
|
476
683
|
await this.#manager.setPluginSetting(plugin.name, key, value);
|
|
477
|
-
this.callbacks.onPluginChanged();
|
|
684
|
+
await this.callbacks.onPluginChanged();
|
|
685
|
+
},
|
|
686
|
+
onBack: () => this.#showPluginList(),
|
|
687
|
+
});
|
|
688
|
+
|
|
689
|
+
this.addChild(this.#viewComponent);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
#showMarketplaceDetail(plugin: InstalledPluginSummary): void {
|
|
693
|
+
this.#currentView = "marketplace-detail";
|
|
694
|
+
this.#currentPlugin = null;
|
|
695
|
+
this.#currentMarketplacePlugin = plugin;
|
|
696
|
+
this.clear();
|
|
697
|
+
|
|
698
|
+
this.#viewComponent = new MarketplacePluginDetailComponent(plugin, {
|
|
699
|
+
onEnabledChange: async enabled => {
|
|
700
|
+
try {
|
|
701
|
+
const mgr = await this.#buildMarketplaceManager();
|
|
702
|
+
await mgr.setPluginEnabled(plugin.id, enabled, plugin.scope);
|
|
703
|
+
await this.callbacks.onPluginChanged();
|
|
704
|
+
} catch (err) {
|
|
705
|
+
logger.error("Settings → Plugins: failed to toggle marketplace plugin", {
|
|
706
|
+
pluginId: plugin.id,
|
|
707
|
+
scope: plugin.scope,
|
|
708
|
+
enabled,
|
|
709
|
+
error: err instanceof Error ? err.message : String(err),
|
|
710
|
+
});
|
|
711
|
+
}
|
|
478
712
|
},
|
|
479
713
|
onBack: () => this.#showPluginList(),
|
|
480
714
|
});
|
|
@@ -14,11 +14,34 @@ import {
|
|
|
14
14
|
import { formatBytes } from "@oh-my-pi/pi-utils";
|
|
15
15
|
import { theme } from "../../modes/theme/theme";
|
|
16
16
|
import { matchesAppInterrupt, matchesSelectDown, matchesSelectUp } from "../../modes/utils/keybinding-matchers";
|
|
17
|
-
import type { SessionInfo } from "../../session/session-manager";
|
|
17
|
+
import type { SessionInfo, SessionStatus } from "../../session/session-manager";
|
|
18
18
|
import { shortenPath } from "../../tools/render-utils";
|
|
19
19
|
import { DynamicBorder } from "./dynamic-border";
|
|
20
20
|
import { HookSelectorComponent } from "./hook-selector";
|
|
21
21
|
|
|
22
|
+
/**
|
|
23
|
+
* Themed glyph + colored label for a session's lifecycle status, or `undefined`
|
|
24
|
+
* when there is nothing useful to show (`unknown`/unset) so the metadata line
|
|
25
|
+
* stays uncluttered. The glyph resolves through the active symbol preset
|
|
26
|
+
* (nerdfont / unicode / ascii) via `theme.status.*`.
|
|
27
|
+
*/
|
|
28
|
+
function formatSessionStatus(status: SessionStatus | undefined): string | undefined {
|
|
29
|
+
switch (status) {
|
|
30
|
+
case "complete":
|
|
31
|
+
return theme.fg("success", `${theme.status.success} done`);
|
|
32
|
+
case "interrupted":
|
|
33
|
+
return theme.fg("warning", `${theme.status.warning} interrupted`);
|
|
34
|
+
case "aborted":
|
|
35
|
+
return theme.fg("muted", `${theme.status.aborted} aborted`);
|
|
36
|
+
case "error":
|
|
37
|
+
return theme.fg("error", `${theme.status.error} error`);
|
|
38
|
+
case "pending":
|
|
39
|
+
return theme.fg("accent", `${theme.status.pending} pending`);
|
|
40
|
+
default:
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
22
45
|
/** Returns the IDs of sessions whose recorded prompts match a query, best first. */
|
|
23
46
|
export type SessionHistoryMatcher = (query: string) => string[];
|
|
24
47
|
|
|
@@ -28,10 +51,10 @@ export type SessionHistoryMatcher = (query: string) => string[];
|
|
|
28
51
|
*
|
|
29
52
|
* - `fuzzy` is the ordered fuzzy-filter result over session metadata (best first).
|
|
30
53
|
* - `historyIds` are session IDs whose recorded prompts matched the query,
|
|
31
|
-
* ordered by history
|
|
54
|
+
* ordered by prompt-history rank (typically newest matching prompt first); duplicates are tolerated.
|
|
32
55
|
*
|
|
33
56
|
* Ranking: sessions matched by **both** signals lead (keeping fuzzy order), then
|
|
34
|
-
* fuzzy-only matches, then history-only matches (by history order). A fuzzy match
|
|
57
|
+
* fuzzy-only matches, then history-only matches (by prompt-history order). A fuzzy match
|
|
35
58
|
* is never dropped, and history matches not present in `allSessions` (e.g. deleted
|
|
36
59
|
* or out-of-scope sessions) are ignored since they cannot be resumed from here.
|
|
37
60
|
*/
|
|
@@ -72,7 +95,9 @@ class SessionList implements Component {
|
|
|
72
95
|
onCancel?: () => void;
|
|
73
96
|
onExit: () => void = () => {};
|
|
74
97
|
onToggleScope?: () => void;
|
|
75
|
-
|
|
98
|
+
// Snapshot of the live terminal-row getter; the visible window is derived
|
|
99
|
+
// from it per render so the picker fits the viewport (and adapts to resize).
|
|
100
|
+
readonly #getTerminalRows: () => number;
|
|
76
101
|
|
|
77
102
|
onDeleteRequest?: (session: SessionInfo) => void;
|
|
78
103
|
|
|
@@ -80,7 +105,13 @@ class SessionList implements Component {
|
|
|
80
105
|
#showCwd: boolean;
|
|
81
106
|
readonly #historyMatcher?: SessionHistoryMatcher;
|
|
82
107
|
|
|
83
|
-
constructor(
|
|
108
|
+
constructor(
|
|
109
|
+
sessions: SessionInfo[],
|
|
110
|
+
showCwd = false,
|
|
111
|
+
historyMatcher?: SessionHistoryMatcher,
|
|
112
|
+
getTerminalRows: () => number = () => 24,
|
|
113
|
+
) {
|
|
114
|
+
this.#getTerminalRows = getTerminalRows;
|
|
84
115
|
this.#allSessions = sessions;
|
|
85
116
|
this.#showCwd = showCwd;
|
|
86
117
|
this.#historyMatcher = historyMatcher;
|
|
@@ -96,6 +127,25 @@ class SessionList implements Component {
|
|
|
96
127
|
};
|
|
97
128
|
}
|
|
98
129
|
|
|
130
|
+
/**
|
|
131
|
+
* Number of sessions to show at once, sized so the whole picker fits the
|
|
132
|
+
* current viewport instead of pushing its header/search off the top.
|
|
133
|
+
*
|
|
134
|
+
* Budget = rows − chrome − reserve, divided by the worst-case per-session
|
|
135
|
+
* height. Chrome (12) is the surrounding spacers/borders/header (7) plus the
|
|
136
|
+
* list's search line, blank, scroll indicator, blank, and hint (5). A titled
|
|
137
|
+
* session is the tallest item at 4 lines (title + preview + metadata +
|
|
138
|
+
* blank); budgeting for that guarantees no overflow even when every visible
|
|
139
|
+
* entry has a title. The reserve covers below-editor hook widgets / cursor.
|
|
140
|
+
*/
|
|
141
|
+
#visibleCount(): number {
|
|
142
|
+
const CHROME = 12;
|
|
143
|
+
const PER_SESSION = 4;
|
|
144
|
+
const RESERVE = 1;
|
|
145
|
+
const budget = this.#getTerminalRows() - CHROME - RESERVE;
|
|
146
|
+
return Math.max(2, Math.floor(budget / PER_SESSION));
|
|
147
|
+
}
|
|
148
|
+
|
|
99
149
|
/** Replace the visible dataset, e.g. when toggling folder/all-projects scope. */
|
|
100
150
|
setSessions(sessions: SessionInfo[], showCwd: boolean): void {
|
|
101
151
|
this.#allSessions = sessions;
|
|
@@ -187,17 +237,16 @@ class SessionList implements Component {
|
|
|
187
237
|
return date.toLocaleDateString();
|
|
188
238
|
};
|
|
189
239
|
|
|
190
|
-
// Calculate visible range with scrolling
|
|
240
|
+
// Calculate visible range with scrolling. The window is sized to the
|
|
241
|
+
// current viewport so the picker never overflows past the top.
|
|
242
|
+
const maxVisible = this.#visibleCount();
|
|
191
243
|
const startIndex = Math.max(
|
|
192
244
|
0,
|
|
193
|
-
Math.min(
|
|
194
|
-
this.#selectedIndex - Math.floor(this.#maxVisible / 2),
|
|
195
|
-
this.#filteredSessions.length - this.#maxVisible,
|
|
196
|
-
),
|
|
245
|
+
Math.min(this.#selectedIndex - Math.floor(maxVisible / 2), this.#filteredSessions.length - maxVisible),
|
|
197
246
|
);
|
|
198
|
-
const endIndex = Math.min(startIndex +
|
|
247
|
+
const endIndex = Math.min(startIndex + maxVisible, this.#filteredSessions.length);
|
|
199
248
|
|
|
200
|
-
// Render visible sessions (
|
|
249
|
+
// Render visible sessions (3 lines, or 4 when a title adds a preview line).
|
|
201
250
|
for (let i = startIndex; i < endIndex; i++) {
|
|
202
251
|
const session = this.#filteredSessions[i];
|
|
203
252
|
const isSelected = i === this.#selectedIndex;
|
|
@@ -227,13 +276,21 @@ class SessionList implements Component {
|
|
|
227
276
|
lines.push(messageLine);
|
|
228
277
|
}
|
|
229
278
|
|
|
230
|
-
// Metadata line: date + file size (+ project dir in
|
|
279
|
+
// Metadata line: date + file size + lifecycle status (+ project dir in
|
|
280
|
+
// all-projects scope). The status segment carries its own color, so each
|
|
281
|
+
// segment is dimmed individually rather than wrapping the whole line.
|
|
282
|
+
const dim = (s: string) => theme.fg("dim", s);
|
|
283
|
+
const dot = dim(theme.sep.dot);
|
|
231
284
|
const modified = formatDate(session.modified);
|
|
232
|
-
let metadata = ` ${modified} ${
|
|
285
|
+
let metadata = ` ${dim(modified)} ${dot} ${dim(formatBytes(session.size))}`;
|
|
286
|
+
const status = formatSessionStatus(session.status);
|
|
287
|
+
if (status) {
|
|
288
|
+
metadata += ` ${dot} ${status}`;
|
|
289
|
+
}
|
|
233
290
|
if (this.#showCwd && session.cwd) {
|
|
234
|
-
metadata += ` ${
|
|
291
|
+
metadata += ` ${dot} ${dim(shortenPath(session.cwd))}`;
|
|
235
292
|
}
|
|
236
|
-
const metadataLine =
|
|
293
|
+
const metadataLine = truncateToWidth(metadata, width);
|
|
237
294
|
|
|
238
295
|
lines.push(metadataLine);
|
|
239
296
|
lines.push(""); // Blank line between sessions
|
|
@@ -280,12 +337,12 @@ class SessionList implements Component {
|
|
|
280
337
|
}
|
|
281
338
|
// Page up - jump up by maxVisible items
|
|
282
339
|
if (matchesKey(keyData, "pageUp")) {
|
|
283
|
-
this.#selectedIndex = Math.max(0, this.#selectedIndex - this.#
|
|
340
|
+
this.#selectedIndex = Math.max(0, this.#selectedIndex - this.#visibleCount());
|
|
284
341
|
return;
|
|
285
342
|
}
|
|
286
343
|
// Page down - jump down by maxVisible items
|
|
287
344
|
if (matchesKey(keyData, "pageDown")) {
|
|
288
|
-
this.#selectedIndex = Math.min(this.#filteredSessions.length - 1, this.#selectedIndex + this.#
|
|
345
|
+
this.#selectedIndex = Math.min(this.#filteredSessions.length - 1, this.#selectedIndex + this.#visibleCount());
|
|
289
346
|
return;
|
|
290
347
|
}
|
|
291
348
|
// Enter
|
|
@@ -328,6 +385,11 @@ export interface SessionSelectorOptions {
|
|
|
328
385
|
allSessions?: SessionInfo[];
|
|
329
386
|
/** Open directly in all-projects scope (e.g. the current folder has no sessions). */
|
|
330
387
|
startInAllScope?: boolean;
|
|
388
|
+
/**
|
|
389
|
+
* Reads the live terminal height so the visible window fits the viewport.
|
|
390
|
+
* Omitted only in tests; defaults to a conservative 24 rows.
|
|
391
|
+
*/
|
|
392
|
+
getTerminalRows?: () => number;
|
|
331
393
|
}
|
|
332
394
|
|
|
333
395
|
/**
|
|
@@ -374,7 +436,7 @@ export class SessionSelectorComponent extends Container {
|
|
|
374
436
|
this.addChild(new Spacer(1));
|
|
375
437
|
this.addChild(this.#messageContainer);
|
|
376
438
|
// Create session list
|
|
377
|
-
this.#sessionList = new SessionList(initialSessions, startAll, options.historyMatcher);
|
|
439
|
+
this.#sessionList = new SessionList(initialSessions, startAll, options.historyMatcher, options.getTerminalRows);
|
|
378
440
|
this.#sessionList.onSelect = onSelect;
|
|
379
441
|
this.#sessionList.onCancel = onCancel;
|
|
380
442
|
this.#sessionList.onExit = onExit;
|
|
@@ -208,7 +208,7 @@ export interface SettingsCallbacks {
|
|
|
208
208
|
/** Get current rendered status line for inline preview */
|
|
209
209
|
getStatusLinePreview?: () => string;
|
|
210
210
|
/** Called when plugins change */
|
|
211
|
-
onPluginsChanged?: () => void
|
|
211
|
+
onPluginsChanged?: () => void | Promise<void>;
|
|
212
212
|
/** Called when settings panel is closed */
|
|
213
213
|
onCancel: () => void;
|
|
214
214
|
}
|
|
@@ -485,7 +485,8 @@ const sessionNameSegment: StatusLineSegment = {
|
|
|
485
485
|
const name = sessionManager?.getSessionName();
|
|
486
486
|
if (!name) return { content: "", visible: false };
|
|
487
487
|
|
|
488
|
-
const ansi =
|
|
488
|
+
const ansi =
|
|
489
|
+
getSessionAccentAnsi(getSessionAccentHex(name, theme.accentSurfaceLuminance)) ?? theme.getFgAnsi("accent");
|
|
489
490
|
return { content: `${ansi}${sanitizeStatusText(name)}\x1b[39m`, visible: true };
|
|
490
491
|
},
|
|
491
492
|
};
|
|
@@ -764,7 +764,7 @@ export class StatusLineComponent implements Component {
|
|
|
764
764
|
const gapWidth = Math.max(1, topFillWidth - leftWidth - rightWidth);
|
|
765
765
|
const sessionName =
|
|
766
766
|
effectiveSettings.sessionAccent !== false ? this.session.sessionManager?.getSessionName() : undefined;
|
|
767
|
-
const accentHex = sessionName ? getSessionAccentHex(sessionName) : undefined;
|
|
767
|
+
const accentHex = sessionName ? getSessionAccentHex(sessionName, theme.accentSurfaceLuminance) : undefined;
|
|
768
768
|
const gapColor = getSessionAccentAnsi(accentHex) ?? theme.getFgAnsi("border");
|
|
769
769
|
const gapFill = `${gapColor}${theme.boxRound.horizontal.repeat(gapWidth)}\x1b[39m`;
|
|
770
770
|
return leftGroup + gapFill + rightGroup;
|
|
@@ -5,10 +5,14 @@ Find out which model you emotionally abuse the most with `omp stats`
|
|
|
5
5
|
Try task isolation to create CoW worktrees
|
|
6
6
|
Your LLM can call an LLM using `llm(x...)`. Have a big batch of tasks? Ask clanker to use it!
|
|
7
7
|
Spaghetti code? Try complaining with /omfg
|
|
8
|
-
Did you know? Each kitty/tmux split keeps its own session — `omp -c` resumes the right one
|
|
8
|
+
Did you know? Each kitty/tmux/cmux split keeps its own session — `omp -c` resumes the right one
|
|
9
9
|
Drop the word `ultrathink` in your message for harder multi-step reasoning — watch it glow rainbow as you type
|
|
10
10
|
Say `orchestrate` in your message to drive a multi-phase task with parallel subagents — watch it glow as you type
|
|
11
11
|
Say `workflow` in your message to drive the task with parallel subagents in eval — watch it glow as you type
|
|
12
12
|
Log in to several accounts of the same provider — `/login` again — and omp load-balances across them automatically
|
|
13
13
|
Run `omp auth-broker serve` once and every machine pulls live tokens over the wire — refresh keys never leave the host; `omp auth-gateway` fronts it as a drop-in proxy any OpenAI-compatible client can hit
|
|
14
|
-
Press alt+p (or /switch) to switch provider, and ctrl+p to cycle role models smol -> slow -> etc
|
|
14
|
+
Press alt+p (or /switch) to switch provider, and ctrl+p to cycle role models smol -> slow -> etc
|
|
15
|
+
Press ctrl+r to search your prompt history and reuse a past message
|
|
16
|
+
`/force read` pins the next turn to one specific tool when the model keeps reaching for the wrong one
|
|
17
|
+
`/copy code` grabs the last code block to your clipboard — `/copy cmd` grabs the last shell/python command
|
|
18
|
+
`/shake` rips heavy tool results out of context to reclaim tokens without a full /compact — `/shake images` drops just images
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Box, Container, Spacer, Text } from "@oh-my-pi/pi-tui";
|
|
2
2
|
import { theme } from "../../modes/theme/theme";
|
|
3
|
-
import type { TodoItem } from "../../tools/todo
|
|
3
|
+
import type { TodoItem } from "../../tools/todo";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Component that renders a todo completion reminder notification.
|