@oh-my-pi/pi-coding-agent 1.337.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 +1228 -0
- package/README.md +1041 -0
- package/docs/compaction.md +403 -0
- package/docs/custom-tools.md +541 -0
- package/docs/extension-loading.md +1004 -0
- package/docs/hooks.md +867 -0
- package/docs/rpc.md +1040 -0
- package/docs/sdk.md +994 -0
- package/docs/session-tree-plan.md +441 -0
- package/docs/session.md +240 -0
- package/docs/skills.md +290 -0
- package/docs/theme.md +637 -0
- package/docs/tree.md +197 -0
- package/docs/tui.md +341 -0
- package/examples/README.md +21 -0
- package/examples/custom-tools/README.md +124 -0
- package/examples/custom-tools/hello/index.ts +20 -0
- package/examples/custom-tools/question/index.ts +84 -0
- package/examples/custom-tools/subagent/README.md +172 -0
- package/examples/custom-tools/subagent/agents/planner.md +37 -0
- package/examples/custom-tools/subagent/agents/reviewer.md +35 -0
- package/examples/custom-tools/subagent/agents/scout.md +50 -0
- package/examples/custom-tools/subagent/agents/worker.md +24 -0
- package/examples/custom-tools/subagent/agents.ts +156 -0
- package/examples/custom-tools/subagent/commands/implement-and-review.md +10 -0
- package/examples/custom-tools/subagent/commands/implement.md +10 -0
- package/examples/custom-tools/subagent/commands/scout-and-plan.md +9 -0
- package/examples/custom-tools/subagent/index.ts +1002 -0
- package/examples/custom-tools/todo/index.ts +212 -0
- package/examples/hooks/README.md +56 -0
- package/examples/hooks/auto-commit-on-exit.ts +49 -0
- package/examples/hooks/confirm-destructive.ts +59 -0
- package/examples/hooks/custom-compaction.ts +116 -0
- package/examples/hooks/dirty-repo-guard.ts +52 -0
- package/examples/hooks/file-trigger.ts +41 -0
- package/examples/hooks/git-checkpoint.ts +53 -0
- package/examples/hooks/handoff.ts +150 -0
- package/examples/hooks/permission-gate.ts +34 -0
- package/examples/hooks/protected-paths.ts +30 -0
- package/examples/hooks/qna.ts +119 -0
- package/examples/hooks/snake.ts +343 -0
- package/examples/hooks/status-line.ts +40 -0
- package/examples/sdk/01-minimal.ts +22 -0
- package/examples/sdk/02-custom-model.ts +49 -0
- package/examples/sdk/03-custom-prompt.ts +44 -0
- package/examples/sdk/04-skills.ts +44 -0
- package/examples/sdk/05-tools.ts +90 -0
- package/examples/sdk/06-hooks.ts +61 -0
- package/examples/sdk/07-context-files.ts +36 -0
- package/examples/sdk/08-slash-commands.ts +42 -0
- package/examples/sdk/09-api-keys-and-oauth.ts +55 -0
- package/examples/sdk/10-settings.ts +38 -0
- package/examples/sdk/11-sessions.ts +48 -0
- package/examples/sdk/12-full-control.ts +95 -0
- package/examples/sdk/README.md +154 -0
- package/package.json +81 -0
- package/src/cli/args.ts +246 -0
- package/src/cli/file-processor.ts +72 -0
- package/src/cli/list-models.ts +104 -0
- package/src/cli/plugin-cli.ts +650 -0
- package/src/cli/session-picker.ts +41 -0
- package/src/cli.ts +10 -0
- package/src/commands/init.md +20 -0
- package/src/config.ts +159 -0
- package/src/core/agent-session.ts +1900 -0
- package/src/core/auth-storage.ts +236 -0
- package/src/core/bash-executor.ts +196 -0
- package/src/core/compaction/branch-summarization.ts +343 -0
- package/src/core/compaction/compaction.ts +742 -0
- package/src/core/compaction/index.ts +7 -0
- package/src/core/compaction/utils.ts +154 -0
- package/src/core/custom-tools/index.ts +21 -0
- package/src/core/custom-tools/loader.ts +248 -0
- package/src/core/custom-tools/types.ts +169 -0
- package/src/core/custom-tools/wrapper.ts +28 -0
- package/src/core/exec.ts +129 -0
- package/src/core/export-html/index.ts +211 -0
- package/src/core/export-html/template.css +781 -0
- package/src/core/export-html/template.html +54 -0
- package/src/core/export-html/template.js +1185 -0
- package/src/core/export-html/vendor/highlight.min.js +1213 -0
- package/src/core/export-html/vendor/marked.min.js +6 -0
- package/src/core/hooks/index.ts +16 -0
- package/src/core/hooks/loader.ts +312 -0
- package/src/core/hooks/runner.ts +434 -0
- package/src/core/hooks/tool-wrapper.ts +99 -0
- package/src/core/hooks/types.ts +773 -0
- package/src/core/index.ts +52 -0
- package/src/core/mcp/client.ts +158 -0
- package/src/core/mcp/config.ts +154 -0
- package/src/core/mcp/index.ts +45 -0
- package/src/core/mcp/loader.ts +68 -0
- package/src/core/mcp/manager.ts +181 -0
- package/src/core/mcp/tool-bridge.ts +148 -0
- package/src/core/mcp/transports/http.ts +316 -0
- package/src/core/mcp/transports/index.ts +6 -0
- package/src/core/mcp/transports/stdio.ts +252 -0
- package/src/core/mcp/types.ts +220 -0
- package/src/core/messages.ts +189 -0
- package/src/core/model-registry.ts +317 -0
- package/src/core/model-resolver.ts +393 -0
- package/src/core/plugins/doctor.ts +59 -0
- package/src/core/plugins/index.ts +38 -0
- package/src/core/plugins/installer.ts +189 -0
- package/src/core/plugins/loader.ts +338 -0
- package/src/core/plugins/manager.ts +672 -0
- package/src/core/plugins/parser.ts +105 -0
- package/src/core/plugins/paths.ts +32 -0
- package/src/core/plugins/types.ts +190 -0
- package/src/core/sdk.ts +760 -0
- package/src/core/session-manager.ts +1128 -0
- package/src/core/settings-manager.ts +443 -0
- package/src/core/skills.ts +437 -0
- package/src/core/slash-commands.ts +248 -0
- package/src/core/system-prompt.ts +439 -0
- package/src/core/timings.ts +25 -0
- package/src/core/tools/ask.ts +211 -0
- package/src/core/tools/bash-interceptor.ts +120 -0
- package/src/core/tools/bash.ts +250 -0
- package/src/core/tools/context.ts +32 -0
- package/src/core/tools/edit-diff.ts +475 -0
- package/src/core/tools/edit.ts +208 -0
- package/src/core/tools/exa/company.ts +59 -0
- package/src/core/tools/exa/index.ts +64 -0
- package/src/core/tools/exa/linkedin.ts +59 -0
- package/src/core/tools/exa/logger.ts +56 -0
- package/src/core/tools/exa/mcp-client.ts +368 -0
- package/src/core/tools/exa/render.ts +196 -0
- package/src/core/tools/exa/researcher.ts +90 -0
- package/src/core/tools/exa/search.ts +337 -0
- package/src/core/tools/exa/types.ts +168 -0
- package/src/core/tools/exa/websets.ts +248 -0
- package/src/core/tools/find.ts +261 -0
- package/src/core/tools/grep.ts +555 -0
- package/src/core/tools/index.ts +202 -0
- package/src/core/tools/ls.ts +140 -0
- package/src/core/tools/lsp/client.ts +605 -0
- package/src/core/tools/lsp/config.ts +147 -0
- package/src/core/tools/lsp/edits.ts +101 -0
- package/src/core/tools/lsp/index.ts +804 -0
- package/src/core/tools/lsp/render.ts +447 -0
- package/src/core/tools/lsp/rust-analyzer.ts +145 -0
- package/src/core/tools/lsp/types.ts +463 -0
- package/src/core/tools/lsp/utils.ts +486 -0
- package/src/core/tools/notebook.ts +229 -0
- package/src/core/tools/path-utils.ts +61 -0
- package/src/core/tools/read.ts +240 -0
- package/src/core/tools/renderers.ts +540 -0
- package/src/core/tools/task/agents.ts +153 -0
- package/src/core/tools/task/artifacts.ts +114 -0
- package/src/core/tools/task/bundled-agents/browser.md +71 -0
- package/src/core/tools/task/bundled-agents/explore.md +82 -0
- package/src/core/tools/task/bundled-agents/plan.md +54 -0
- package/src/core/tools/task/bundled-agents/reviewer.md +59 -0
- package/src/core/tools/task/bundled-agents/task.md +53 -0
- package/src/core/tools/task/bundled-commands/architect-plan.md +10 -0
- package/src/core/tools/task/bundled-commands/implement-with-critic.md +11 -0
- package/src/core/tools/task/bundled-commands/implement.md +11 -0
- package/src/core/tools/task/commands.ts +213 -0
- package/src/core/tools/task/discovery.ts +208 -0
- package/src/core/tools/task/executor.ts +367 -0
- package/src/core/tools/task/index.ts +388 -0
- package/src/core/tools/task/model-resolver.ts +115 -0
- package/src/core/tools/task/parallel.ts +38 -0
- package/src/core/tools/task/render.ts +232 -0
- package/src/core/tools/task/types.ts +99 -0
- package/src/core/tools/truncate.ts +265 -0
- package/src/core/tools/web-fetch.ts +2370 -0
- package/src/core/tools/web-search/auth.ts +193 -0
- package/src/core/tools/web-search/index.ts +537 -0
- package/src/core/tools/web-search/providers/anthropic.ts +198 -0
- package/src/core/tools/web-search/providers/exa.ts +302 -0
- package/src/core/tools/web-search/providers/perplexity.ts +195 -0
- package/src/core/tools/web-search/render.ts +182 -0
- package/src/core/tools/web-search/types.ts +180 -0
- package/src/core/tools/write.ts +99 -0
- package/src/index.ts +176 -0
- package/src/main.ts +464 -0
- package/src/migrations.ts +135 -0
- package/src/modes/index.ts +43 -0
- package/src/modes/interactive/components/armin.ts +382 -0
- package/src/modes/interactive/components/assistant-message.ts +86 -0
- package/src/modes/interactive/components/bash-execution.ts +196 -0
- package/src/modes/interactive/components/bordered-loader.ts +41 -0
- package/src/modes/interactive/components/branch-summary-message.ts +42 -0
- package/src/modes/interactive/components/compaction-summary-message.ts +45 -0
- package/src/modes/interactive/components/custom-editor.ts +122 -0
- package/src/modes/interactive/components/diff.ts +147 -0
- package/src/modes/interactive/components/dynamic-border.ts +25 -0
- package/src/modes/interactive/components/footer.ts +381 -0
- package/src/modes/interactive/components/hook-editor.ts +117 -0
- package/src/modes/interactive/components/hook-input.ts +64 -0
- package/src/modes/interactive/components/hook-message.ts +96 -0
- package/src/modes/interactive/components/hook-selector.ts +91 -0
- package/src/modes/interactive/components/model-selector.ts +247 -0
- package/src/modes/interactive/components/oauth-selector.ts +120 -0
- package/src/modes/interactive/components/plugin-settings.ts +479 -0
- package/src/modes/interactive/components/queue-mode-selector.ts +56 -0
- package/src/modes/interactive/components/session-selector.ts +204 -0
- package/src/modes/interactive/components/settings-selector.ts +453 -0
- package/src/modes/interactive/components/show-images-selector.ts +45 -0
- package/src/modes/interactive/components/theme-selector.ts +62 -0
- package/src/modes/interactive/components/thinking-selector.ts +64 -0
- package/src/modes/interactive/components/tool-execution.ts +675 -0
- package/src/modes/interactive/components/tree-selector.ts +866 -0
- package/src/modes/interactive/components/user-message-selector.ts +159 -0
- package/src/modes/interactive/components/user-message.ts +18 -0
- package/src/modes/interactive/components/visual-truncate.ts +50 -0
- package/src/modes/interactive/components/welcome.ts +183 -0
- package/src/modes/interactive/interactive-mode.ts +2516 -0
- package/src/modes/interactive/theme/dark.json +101 -0
- package/src/modes/interactive/theme/light.json +98 -0
- package/src/modes/interactive/theme/theme-schema.json +308 -0
- package/src/modes/interactive/theme/theme.ts +998 -0
- package/src/modes/print-mode.ts +128 -0
- package/src/modes/rpc/rpc-client.ts +527 -0
- package/src/modes/rpc/rpc-mode.ts +483 -0
- package/src/modes/rpc/rpc-types.ts +203 -0
- package/src/utils/changelog.ts +99 -0
- package/src/utils/clipboard.ts +265 -0
- package/src/utils/fuzzy.ts +108 -0
- package/src/utils/mime.ts +30 -0
- package/src/utils/shell.ts +276 -0
- package/src/utils/tools-manager.ts +274 -0
|
@@ -0,0 +1,479 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin settings UI components.
|
|
3
|
+
*
|
|
4
|
+
* Provides a hierarchical settings interface:
|
|
5
|
+
* - Plugin list (shows all installed plugins)
|
|
6
|
+
* - Plugin detail (enable/disable, features, config)
|
|
7
|
+
* - Feature toggles
|
|
8
|
+
* - Config value editor
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
Container,
|
|
13
|
+
Input,
|
|
14
|
+
type SelectItem,
|
|
15
|
+
SelectList,
|
|
16
|
+
type SettingItem,
|
|
17
|
+
SettingsList,
|
|
18
|
+
Spacer,
|
|
19
|
+
Text,
|
|
20
|
+
} from "@oh-my-pi/pi-tui";
|
|
21
|
+
import { PluginManager } from "../../../core/plugins/manager.js";
|
|
22
|
+
import type { InstalledPlugin, PluginSettingSchema } from "../../../core/plugins/types.js";
|
|
23
|
+
import { getSelectListTheme, getSettingsListTheme, theme } from "../theme/theme.js";
|
|
24
|
+
import { DynamicBorder } from "./dynamic-border.js";
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Plugin List Component
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
export interface PluginListCallbacks {
|
|
31
|
+
onPluginSelect: (plugin: InstalledPlugin) => void;
|
|
32
|
+
onCancel: () => void;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Shows list of installed plugins with enable/disable status.
|
|
37
|
+
* Selecting a plugin opens its detail view.
|
|
38
|
+
*/
|
|
39
|
+
export class PluginListComponent extends Container {
|
|
40
|
+
private selectList: SelectList;
|
|
41
|
+
private plugins: InstalledPlugin[];
|
|
42
|
+
|
|
43
|
+
constructor(plugins: InstalledPlugin[], callbacks: PluginListCallbacks) {
|
|
44
|
+
super();
|
|
45
|
+
this.plugins = plugins;
|
|
46
|
+
|
|
47
|
+
// Title
|
|
48
|
+
this.addChild(new DynamicBorder());
|
|
49
|
+
this.addChild(new Text(theme.bold(theme.fg("accent", " Plugins")), 0, 0));
|
|
50
|
+
this.addChild(new Spacer(1));
|
|
51
|
+
|
|
52
|
+
if (plugins.length === 0) {
|
|
53
|
+
this.addChild(new Text(theme.fg("muted", " No plugins installed"), 0, 0));
|
|
54
|
+
this.addChild(new Spacer(1));
|
|
55
|
+
this.addChild(new Text(theme.fg("dim", " Install with: pi plugin install <package>"), 0, 0));
|
|
56
|
+
this.addChild(new Spacer(1));
|
|
57
|
+
this.addChild(new DynamicBorder());
|
|
58
|
+
|
|
59
|
+
// Create empty list that just handles escape
|
|
60
|
+
this.selectList = new SelectList([], 1, getSelectListTheme());
|
|
61
|
+
this.selectList.onCancel = callbacks.onCancel;
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const items: SelectItem[] = plugins.map((p) => {
|
|
66
|
+
const status = p.enabled ? theme.fg("success", "●") : theme.fg("muted", "○");
|
|
67
|
+
const featureCount = p.manifest.features ? Object.keys(p.manifest.features).length : 0;
|
|
68
|
+
const enabledCount = p.enabledFeatures?.length ?? featureCount;
|
|
69
|
+
|
|
70
|
+
let details = `v${p.version}`;
|
|
71
|
+
if (featureCount > 0) {
|
|
72
|
+
details += ` · ${enabledCount}/${featureCount} features`;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return {
|
|
76
|
+
value: p.name,
|
|
77
|
+
label: `${status} ${p.name}`,
|
|
78
|
+
description: details,
|
|
79
|
+
};
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
this.selectList = new SelectList(items, Math.min(items.length, 8), getSelectListTheme());
|
|
83
|
+
|
|
84
|
+
this.selectList.onSelect = (item) => {
|
|
85
|
+
const plugin = this.plugins.find((p) => p.name === item.value);
|
|
86
|
+
if (plugin) {
|
|
87
|
+
callbacks.onPluginSelect(plugin);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
this.selectList.onCancel = callbacks.onCancel;
|
|
92
|
+
|
|
93
|
+
this.addChild(this.selectList);
|
|
94
|
+
this.addChild(new Spacer(1));
|
|
95
|
+
this.addChild(new Text(theme.fg("dim", " Enter to configure · Esc to go back"), 0, 0));
|
|
96
|
+
this.addChild(new DynamicBorder());
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
handleInput(data: string): void {
|
|
100
|
+
this.selectList.handleInput(data);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// =============================================================================
|
|
105
|
+
// Plugin Detail Component
|
|
106
|
+
// =============================================================================
|
|
107
|
+
|
|
108
|
+
export interface PluginDetailCallbacks {
|
|
109
|
+
onEnabledChange: (enabled: boolean) => void;
|
|
110
|
+
onFeatureChange: (feature: string, enabled: boolean) => void;
|
|
111
|
+
onConfigChange: (key: string, value: unknown) => void;
|
|
112
|
+
onBack: () => void;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Shows detail settings for a single plugin:
|
|
117
|
+
* - Enable/disable toggle
|
|
118
|
+
* - Feature toggles
|
|
119
|
+
* - Config settings
|
|
120
|
+
*/
|
|
121
|
+
export class PluginDetailComponent extends Container {
|
|
122
|
+
private settingsList!: SettingsList;
|
|
123
|
+
private plugin: InstalledPlugin;
|
|
124
|
+
private manager: PluginManager;
|
|
125
|
+
private callbacks: PluginDetailCallbacks;
|
|
126
|
+
|
|
127
|
+
constructor(plugin: InstalledPlugin, manager: PluginManager, callbacks: PluginDetailCallbacks) {
|
|
128
|
+
super();
|
|
129
|
+
this.plugin = plugin;
|
|
130
|
+
this.manager = manager;
|
|
131
|
+
this.callbacks = callbacks;
|
|
132
|
+
|
|
133
|
+
this.rebuild();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
private rebuild(): void {
|
|
137
|
+
this.clear();
|
|
138
|
+
|
|
139
|
+
const plugin = this.plugin;
|
|
140
|
+
const manifest = plugin.manifest;
|
|
141
|
+
|
|
142
|
+
// Header
|
|
143
|
+
this.addChild(new DynamicBorder());
|
|
144
|
+
this.addChild(new Text(theme.bold(theme.fg("accent", ` ${plugin.name}`)), 0, 0));
|
|
145
|
+
if (manifest.description) {
|
|
146
|
+
this.addChild(new Text(theme.fg("muted", ` ${manifest.description}`), 0, 0));
|
|
147
|
+
}
|
|
148
|
+
this.addChild(new Spacer(1));
|
|
149
|
+
|
|
150
|
+
const items: SettingItem[] = [];
|
|
151
|
+
|
|
152
|
+
// Enable/disable toggle
|
|
153
|
+
items.push({
|
|
154
|
+
id: "__enabled__",
|
|
155
|
+
label: "Enabled",
|
|
156
|
+
description: "Enable or disable this plugin",
|
|
157
|
+
currentValue: plugin.enabled ? "true" : "false",
|
|
158
|
+
values: ["true", "false"],
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// Feature toggles
|
|
162
|
+
if (manifest.features && Object.keys(manifest.features).length > 0) {
|
|
163
|
+
const enabledSet = new Set(plugin.enabledFeatures ?? []);
|
|
164
|
+
const defaultFeatures = Object.entries(manifest.features)
|
|
165
|
+
.filter(([_, f]) => f.default)
|
|
166
|
+
.map(([name]) => name);
|
|
167
|
+
|
|
168
|
+
// If enabledFeatures is null, use defaults
|
|
169
|
+
const effectiveEnabled = plugin.enabledFeatures === null ? new Set(defaultFeatures) : enabledSet;
|
|
170
|
+
|
|
171
|
+
for (const [featName, feat] of Object.entries(manifest.features)) {
|
|
172
|
+
const isEnabled = effectiveEnabled.has(featName);
|
|
173
|
+
items.push({
|
|
174
|
+
id: `feature:${featName}`,
|
|
175
|
+
label: ` ${featName}`,
|
|
176
|
+
description: feat.description || `Enable ${featName} feature`,
|
|
177
|
+
currentValue: isEnabled ? "true" : "false",
|
|
178
|
+
values: ["true", "false"],
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Config settings
|
|
184
|
+
if (manifest.settings && Object.keys(manifest.settings).length > 0) {
|
|
185
|
+
const settings = this.manager.getPluginSettings(plugin.name);
|
|
186
|
+
|
|
187
|
+
for (const [key, schema] of Object.entries(manifest.settings)) {
|
|
188
|
+
const currentValue = settings[key] ?? schema.default;
|
|
189
|
+
const displayValue = schema.secret && currentValue ? "••••••••" : String(currentValue ?? "(not set)");
|
|
190
|
+
|
|
191
|
+
if (schema.type === "boolean") {
|
|
192
|
+
items.push({
|
|
193
|
+
id: `config:${key}`,
|
|
194
|
+
label: ` ${key}`,
|
|
195
|
+
description: schema.description || `Configure ${key}`,
|
|
196
|
+
currentValue: currentValue ? "true" : "false",
|
|
197
|
+
values: ["true", "false"],
|
|
198
|
+
});
|
|
199
|
+
} else if (schema.type === "enum") {
|
|
200
|
+
items.push({
|
|
201
|
+
id: `config:${key}`,
|
|
202
|
+
label: ` ${key}`,
|
|
203
|
+
description: schema.description || `Configure ${key}`,
|
|
204
|
+
currentValue: String(currentValue ?? schema.default ?? ""),
|
|
205
|
+
submenu: (cv, done) =>
|
|
206
|
+
new ConfigEnumSubmenu(
|
|
207
|
+
key,
|
|
208
|
+
schema.description || `Select value for ${key}`,
|
|
209
|
+
schema.values,
|
|
210
|
+
cv,
|
|
211
|
+
(value) => {
|
|
212
|
+
this.callbacks.onConfigChange(key, value);
|
|
213
|
+
done(value);
|
|
214
|
+
},
|
|
215
|
+
() => done(),
|
|
216
|
+
),
|
|
217
|
+
});
|
|
218
|
+
} else {
|
|
219
|
+
// string or number - show as submenu with input
|
|
220
|
+
items.push({
|
|
221
|
+
id: `config:${key}`,
|
|
222
|
+
label: ` ${key}`,
|
|
223
|
+
description: schema.description || `Configure ${key}`,
|
|
224
|
+
currentValue: displayValue,
|
|
225
|
+
submenu: (cv, done) =>
|
|
226
|
+
new ConfigInputSubmenu(
|
|
227
|
+
key,
|
|
228
|
+
schema,
|
|
229
|
+
cv === "(not set)" ? "" : cv,
|
|
230
|
+
(value) => {
|
|
231
|
+
const parsed = schema.type === "number" ? Number(value) : value;
|
|
232
|
+
this.callbacks.onConfigChange(key, parsed);
|
|
233
|
+
done(String(value));
|
|
234
|
+
},
|
|
235
|
+
() => done(),
|
|
236
|
+
),
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
this.settingsList = new SettingsList(
|
|
243
|
+
items,
|
|
244
|
+
Math.min(items.length, 10),
|
|
245
|
+
getSettingsListTheme(),
|
|
246
|
+
(id, newValue) => {
|
|
247
|
+
if (id === "__enabled__") {
|
|
248
|
+
this.callbacks.onEnabledChange(newValue === "true");
|
|
249
|
+
this.plugin = { ...this.plugin, enabled: newValue === "true" };
|
|
250
|
+
} else if (id.startsWith("feature:")) {
|
|
251
|
+
const featName = id.slice(8);
|
|
252
|
+
this.callbacks.onFeatureChange(featName, newValue === "true");
|
|
253
|
+
// Update local state
|
|
254
|
+
const current = new Set(this.plugin.enabledFeatures ?? []);
|
|
255
|
+
if (newValue === "true") {
|
|
256
|
+
current.add(featName);
|
|
257
|
+
} else {
|
|
258
|
+
current.delete(featName);
|
|
259
|
+
}
|
|
260
|
+
this.plugin = { ...this.plugin, enabledFeatures: [...current] };
|
|
261
|
+
} else if (id.startsWith("config:")) {
|
|
262
|
+
const key = id.slice(7);
|
|
263
|
+
const schema = this.plugin.manifest.settings?.[key];
|
|
264
|
+
if (schema?.type === "boolean") {
|
|
265
|
+
this.callbacks.onConfigChange(key, newValue === "true");
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
},
|
|
269
|
+
this.callbacks.onBack,
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
this.addChild(this.settingsList);
|
|
273
|
+
this.addChild(new Spacer(1));
|
|
274
|
+
this.addChild(new Text(theme.fg("dim", " Enter to edit · Esc to go back"), 0, 0));
|
|
275
|
+
this.addChild(new DynamicBorder());
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
handleInput(data: string): void {
|
|
279
|
+
this.settingsList.handleInput(data);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// =============================================================================
|
|
284
|
+
// Config Submenus
|
|
285
|
+
// =============================================================================
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Submenu for enum config values.
|
|
289
|
+
*/
|
|
290
|
+
class ConfigEnumSubmenu extends Container {
|
|
291
|
+
private selectList: SelectList;
|
|
292
|
+
|
|
293
|
+
constructor(
|
|
294
|
+
key: string,
|
|
295
|
+
description: string,
|
|
296
|
+
values: string[],
|
|
297
|
+
currentValue: string,
|
|
298
|
+
onSelect: (value: string) => void,
|
|
299
|
+
onCancel: () => void,
|
|
300
|
+
) {
|
|
301
|
+
super();
|
|
302
|
+
|
|
303
|
+
this.addChild(new Text(theme.bold(theme.fg("accent", key)), 0, 0));
|
|
304
|
+
if (description) {
|
|
305
|
+
this.addChild(new Spacer(1));
|
|
306
|
+
this.addChild(new Text(theme.fg("muted", description), 0, 0));
|
|
307
|
+
}
|
|
308
|
+
this.addChild(new Spacer(1));
|
|
309
|
+
|
|
310
|
+
const items: SelectItem[] = values.map((v) => ({ value: v, label: v }));
|
|
311
|
+
this.selectList = new SelectList(items, Math.min(items.length, 8), getSelectListTheme());
|
|
312
|
+
|
|
313
|
+
const currentIndex = values.indexOf(currentValue);
|
|
314
|
+
if (currentIndex !== -1) {
|
|
315
|
+
this.selectList.setSelectedIndex(currentIndex);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
this.selectList.onSelect = (item) => onSelect(item.value);
|
|
319
|
+
this.selectList.onCancel = onCancel;
|
|
320
|
+
|
|
321
|
+
this.addChild(this.selectList);
|
|
322
|
+
this.addChild(new Spacer(1));
|
|
323
|
+
this.addChild(new Text(theme.fg("dim", " Enter to select · Esc to cancel"), 0, 0));
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
handleInput(data: string): void {
|
|
327
|
+
this.selectList.handleInput(data);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
/**
|
|
332
|
+
* Submenu for string/number config values with text input.
|
|
333
|
+
*/
|
|
334
|
+
class ConfigInputSubmenu extends Container {
|
|
335
|
+
private input: Input;
|
|
336
|
+
private onSubmit: (value: string) => void;
|
|
337
|
+
private onCancel: () => void;
|
|
338
|
+
|
|
339
|
+
constructor(
|
|
340
|
+
key: string,
|
|
341
|
+
schema: PluginSettingSchema,
|
|
342
|
+
currentValue: string,
|
|
343
|
+
onSubmit: (value: string) => void,
|
|
344
|
+
onCancel: () => void,
|
|
345
|
+
) {
|
|
346
|
+
super();
|
|
347
|
+
this.onSubmit = onSubmit;
|
|
348
|
+
this.onCancel = onCancel;
|
|
349
|
+
|
|
350
|
+
this.addChild(new Text(theme.bold(theme.fg("accent", key)), 0, 0));
|
|
351
|
+
if (schema.description) {
|
|
352
|
+
this.addChild(new Spacer(1));
|
|
353
|
+
this.addChild(new Text(theme.fg("muted", schema.description), 0, 0));
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Type hint
|
|
357
|
+
let hint = `Type: ${schema.type}`;
|
|
358
|
+
if (schema.type === "number") {
|
|
359
|
+
const numSchema = schema as { min?: number; max?: number };
|
|
360
|
+
if (numSchema.min !== undefined || numSchema.max !== undefined) {
|
|
361
|
+
hint += ` (${numSchema.min ?? ""}..${numSchema.max ?? ""})`;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
this.addChild(new Spacer(1));
|
|
365
|
+
this.addChild(new Text(theme.fg("dim", hint), 0, 0));
|
|
366
|
+
|
|
367
|
+
this.addChild(new Spacer(1));
|
|
368
|
+
|
|
369
|
+
// Input field
|
|
370
|
+
this.input = new Input();
|
|
371
|
+
if (!schema.secret && currentValue) {
|
|
372
|
+
this.input.setValue(currentValue);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
this.input.onSubmit = (value) => {
|
|
376
|
+
if (value.trim()) {
|
|
377
|
+
this.onSubmit(value);
|
|
378
|
+
} else {
|
|
379
|
+
this.onCancel();
|
|
380
|
+
}
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
this.addChild(this.input);
|
|
384
|
+
this.addChild(new Spacer(1));
|
|
385
|
+
this.addChild(new Text(theme.fg("dim", " Enter to save · Esc to cancel"), 0, 0));
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
handleInput(data: string): void {
|
|
389
|
+
if (data === "\x1b" || data === "\x1b\x1b") {
|
|
390
|
+
this.onCancel();
|
|
391
|
+
return;
|
|
392
|
+
}
|
|
393
|
+
this.input.handleInput(data);
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// =============================================================================
|
|
398
|
+
// Main Plugin Settings Selector
|
|
399
|
+
// =============================================================================
|
|
400
|
+
|
|
401
|
+
export interface PluginSettingsCallbacks {
|
|
402
|
+
onClose: () => void;
|
|
403
|
+
onPluginChanged: () => void;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/** Component with handleInput method */
|
|
407
|
+
interface InputHandler {
|
|
408
|
+
handleInput(data: string): void;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Top-level plugin settings component.
|
|
413
|
+
* Manages navigation between plugin list and plugin detail views.
|
|
414
|
+
*/
|
|
415
|
+
export class PluginSettingsComponent extends Container {
|
|
416
|
+
private manager: PluginManager;
|
|
417
|
+
private callbacks: PluginSettingsCallbacks;
|
|
418
|
+
private viewComponent: (Container & InputHandler) | null = null;
|
|
419
|
+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: state tracking for view management
|
|
420
|
+
private currentView: "list" | "detail" = "list";
|
|
421
|
+
// biome-ignore lint/correctness/noUnusedPrivateClassMembers: state tracking for view management
|
|
422
|
+
private currentPlugin: InstalledPlugin | null = null;
|
|
423
|
+
|
|
424
|
+
constructor(cwd: string, callbacks: PluginSettingsCallbacks) {
|
|
425
|
+
super();
|
|
426
|
+
this.manager = new PluginManager(cwd);
|
|
427
|
+
this.callbacks = callbacks;
|
|
428
|
+
this.showPluginList();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
private async showPluginList(): Promise<void> {
|
|
432
|
+
this.currentView = "list";
|
|
433
|
+
this.currentPlugin = null;
|
|
434
|
+
this.clear();
|
|
435
|
+
|
|
436
|
+
const plugins = await this.manager.list();
|
|
437
|
+
|
|
438
|
+
this.viewComponent = new PluginListComponent(plugins, {
|
|
439
|
+
onPluginSelect: (plugin) => this.showPluginDetail(plugin),
|
|
440
|
+
onCancel: () => this.callbacks.onClose(),
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
this.addChild(this.viewComponent);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
private showPluginDetail(plugin: InstalledPlugin): void {
|
|
447
|
+
this.currentView = "detail";
|
|
448
|
+
this.currentPlugin = plugin;
|
|
449
|
+
this.clear();
|
|
450
|
+
|
|
451
|
+
this.viewComponent = new PluginDetailComponent(plugin, this.manager, {
|
|
452
|
+
onEnabledChange: async (enabled) => {
|
|
453
|
+
await this.manager.setEnabled(plugin.name, enabled);
|
|
454
|
+
this.callbacks.onPluginChanged();
|
|
455
|
+
},
|
|
456
|
+
onFeatureChange: async (feature, enabled) => {
|
|
457
|
+
const current = new Set(this.manager.getEnabledFeatures(plugin.name) ?? []);
|
|
458
|
+
if (enabled) {
|
|
459
|
+
current.add(feature);
|
|
460
|
+
} else {
|
|
461
|
+
current.delete(feature);
|
|
462
|
+
}
|
|
463
|
+
await this.manager.setEnabledFeatures(plugin.name, [...current]);
|
|
464
|
+
this.callbacks.onPluginChanged();
|
|
465
|
+
},
|
|
466
|
+
onConfigChange: (key, value) => {
|
|
467
|
+
this.manager.setPluginSetting(plugin.name, key, value);
|
|
468
|
+
this.callbacks.onPluginChanged();
|
|
469
|
+
},
|
|
470
|
+
onBack: () => this.showPluginList(),
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
this.addChild(this.viewComponent);
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
handleInput(data: string): void {
|
|
477
|
+
this.viewComponent?.handleInput(data);
|
|
478
|
+
}
|
|
479
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { Container, type SelectItem, SelectList } from "@oh-my-pi/pi-tui";
|
|
2
|
+
import { getSelectListTheme } from "../theme/theme.js";
|
|
3
|
+
import { DynamicBorder } from "./dynamic-border.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Component that renders a queue mode selector with borders
|
|
7
|
+
*/
|
|
8
|
+
export class QueueModeSelectorComponent extends Container {
|
|
9
|
+
private selectList: SelectList;
|
|
10
|
+
|
|
11
|
+
constructor(
|
|
12
|
+
currentMode: "all" | "one-at-a-time",
|
|
13
|
+
onSelect: (mode: "all" | "one-at-a-time") => void,
|
|
14
|
+
onCancel: () => void,
|
|
15
|
+
) {
|
|
16
|
+
super();
|
|
17
|
+
|
|
18
|
+
const queueModes: SelectItem[] = [
|
|
19
|
+
{
|
|
20
|
+
value: "one-at-a-time",
|
|
21
|
+
label: "one-at-a-time",
|
|
22
|
+
description: "Process queued messages one by one (recommended)",
|
|
23
|
+
},
|
|
24
|
+
{ value: "all", label: "all", description: "Process all queued messages at once" },
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
// Add top border
|
|
28
|
+
this.addChild(new DynamicBorder());
|
|
29
|
+
|
|
30
|
+
// Create selector
|
|
31
|
+
this.selectList = new SelectList(queueModes, 2, getSelectListTheme());
|
|
32
|
+
|
|
33
|
+
// Preselect current mode
|
|
34
|
+
const currentIndex = queueModes.findIndex((item) => item.value === currentMode);
|
|
35
|
+
if (currentIndex !== -1) {
|
|
36
|
+
this.selectList.setSelectedIndex(currentIndex);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.selectList.onSelect = (item) => {
|
|
40
|
+
onSelect(item.value as "all" | "one-at-a-time");
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
this.selectList.onCancel = () => {
|
|
44
|
+
onCancel();
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
this.addChild(this.selectList);
|
|
48
|
+
|
|
49
|
+
// Add bottom border
|
|
50
|
+
this.addChild(new DynamicBorder());
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
getSelectList(): SelectList {
|
|
54
|
+
return this.selectList;
|
|
55
|
+
}
|
|
56
|
+
}
|