@kolisachint/hoocode-agent 0.5.12 → 0.5.13
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 +2 -0
- package/dist/core/extensions/loader.d.ts +18 -6
- package/dist/core/extensions/loader.d.ts.map +1 -1
- package/dist/core/extensions/loader.js +35 -19
- package/dist/core/extensions/loader.js.map +1 -1
- package/dist/core/extensions/plugins/install.d.ts +51 -10
- package/dist/core/extensions/plugins/install.d.ts.map +1 -1
- package/dist/core/extensions/plugins/install.js +178 -31
- package/dist/core/extensions/plugins/install.js.map +1 -1
- package/dist/core/extensions/plugins/listing.d.ts +8 -2
- package/dist/core/extensions/plugins/listing.d.ts.map +1 -1
- package/dist/core/extensions/plugins/listing.js +29 -5
- package/dist/core/extensions/plugins/listing.js.map +1 -1
- package/dist/core/extensions/plugins/locations.d.ts +62 -12
- package/dist/core/extensions/plugins/locations.d.ts.map +1 -1
- package/dist/core/extensions/plugins/locations.js +74 -18
- package/dist/core/extensions/plugins/locations.js.map +1 -1
- package/dist/core/extensions/plugins/marketplace.d.ts +29 -3
- package/dist/core/extensions/plugins/marketplace.d.ts.map +1 -1
- package/dist/core/extensions/plugins/marketplace.js +40 -7
- package/dist/core/extensions/plugins/marketplace.js.map +1 -1
- package/dist/core/extensions/plugins/trust.d.ts +56 -0
- package/dist/core/extensions/plugins/trust.d.ts.map +1 -0
- package/dist/core/extensions/plugins/trust.js +88 -0
- package/dist/core/extensions/plugins/trust.js.map +1 -0
- package/dist/core/settings-defaults.d.ts +1 -0
- package/dist/core/settings-defaults.d.ts.map +1 -1
- package/dist/core/settings-defaults.js +1 -0
- package/dist/core/settings-defaults.js.map +1 -1
- package/dist/core/settings-manager.d.ts +7 -0
- package/dist/core/settings-manager.d.ts.map +1 -1
- package/dist/core/settings-manager.js +14 -0
- package/dist/core/settings-manager.js.map +1 -1
- package/dist/core/settings-types.d.ts +1 -0
- package/dist/core/settings-types.d.ts.map +1 -1
- package/dist/core/settings-types.js.map +1 -1
- package/dist/core/tools/plugins.d.ts.map +1 -1
- package/dist/core/tools/plugins.js +18 -3
- package/dist/core/tools/plugins.js.map +1 -1
- package/dist/extensions/core/marketplace.d.ts +7 -3
- package/dist/extensions/core/marketplace.d.ts.map +1 -1
- package/dist/extensions/core/marketplace.js +103 -11
- package/dist/extensions/core/marketplace.js.map +1 -1
- package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
- package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
- package/dist/modes/interactive/components/settings-selector.js +15 -2
- package/dist/modes/interactive/components/settings-selector.js.map +1 -1
- package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
- package/dist/modes/interactive/interactive-mode.js +6 -0
- package/dist/modes/interactive/interactive-mode.js.map +1 -1
- package/examples/extensions/custom-provider-anthropic/package.json +1 -1
- package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
- package/examples/extensions/sandbox/package.json +1 -1
- package/examples/extensions/with-deps/package.json +1 -1
- package/package.json +4 -4
|
@@ -4,8 +4,10 @@
|
|
|
4
4
|
* `/plugin` installs plugins from marketplaces (a git repo or local dir with a
|
|
5
5
|
* native `.agents-plugin/marketplace.json`, Claude `.claude-plugin/marketplace.json`,
|
|
6
6
|
* or Copilot-style `.github/marketplace.json` index). Installed plugins are placed
|
|
7
|
-
*
|
|
8
|
-
*
|
|
7
|
+
* at the chosen scope — `~/.agents/plugins/<name>` for `user`, or
|
|
8
|
+
* `<cwd>/.agents/plugins/<name>` for `project` — and loaded by the plugin loader
|
|
9
|
+
* after a reload. `install` asks which, since a human is right here to answer;
|
|
10
|
+
* the autonomous tool reads the `pluginInstallScope` setting instead.
|
|
9
11
|
*
|
|
10
12
|
* Adding a marketplace is the human trust boundary and stays here; the shared
|
|
11
13
|
* mechanics (discovery, install, remove, the bundled default marketplace) live in
|
|
@@ -16,8 +18,10 @@
|
|
|
16
18
|
* /plugin marketplace list
|
|
17
19
|
* /plugin marketplace refresh re-fetch every cached index now
|
|
18
20
|
* /plugin list list available plugins across marketplaces
|
|
19
|
-
* /plugin install <name>
|
|
21
|
+
* /plugin install <name> [--scope user|project]
|
|
20
22
|
* /plugin remove <name>
|
|
23
|
+
* /plugin trust [list] allow repo-committed plugins to run code here
|
|
24
|
+
* /plugin untrust
|
|
21
25
|
* /plugin publish <name> [--to <marketplace-dir>]
|
|
22
26
|
*
|
|
23
27
|
* `publish` is the human end of the publish lane (§3.2). The model can package a
|
|
@@ -31,6 +35,7 @@
|
|
|
31
35
|
*/
|
|
32
36
|
import { existsSync, mkdirSync, rmSync } from "node:fs";
|
|
33
37
|
import { join } from "node:path";
|
|
38
|
+
import { getAgentDir } from "../../config.js";
|
|
34
39
|
import { CATEGORY_GLYPH, SEGMENT_SEP } from "../../core/brand.js";
|
|
35
40
|
import { getPlugin } from "../../core/extensions/plugins/authoring.js";
|
|
36
41
|
import { findAvailablePlugin, installAvailablePlugin, listAvailablePlugins, listInstalledPlugins, readMarketplaceRecords, refreshMarketplaces, uninstallPlugin, } from "../../core/extensions/plugins/install.js";
|
|
@@ -38,10 +43,36 @@ import { availablePluginGroups } from "../../core/extensions/plugins/listing.js"
|
|
|
38
43
|
import { marketplaceCacheDir, marketplaceCacheRoot, marketplaceStorePath, } from "../../core/extensions/plugins/locations.js";
|
|
39
44
|
import { parseMarketplaceDir, readMarketplaceStore, resolvePluginSource, writeMarketplaceStore, } from "../../core/extensions/plugins/marketplace.js";
|
|
40
45
|
import { entryNeedsSource, packagePlugin, stageIntoMarketplace } from "../../core/extensions/plugins/packaging.js";
|
|
46
|
+
import { isWorkspaceTrusted, listTrustedWorkspaces, trustWorkspace, untrustWorkspace, } from "../../core/extensions/plugins/trust.js";
|
|
41
47
|
import { plural, renderList } from "../../core/format-list.js";
|
|
48
|
+
import { SettingsManager } from "../../core/settings-manager.js";
|
|
42
49
|
function isGitSource(loc) {
|
|
43
50
|
return /^https?:\/\//.test(loc) || loc.startsWith("git@") || loc.endsWith(".git");
|
|
44
51
|
}
|
|
52
|
+
function isInstallScope(value) {
|
|
53
|
+
return value === "user" || value === "project";
|
|
54
|
+
}
|
|
55
|
+
/** Labels carry the consequence, since that is the whole content of the choice. */
|
|
56
|
+
const SCOPE_CHOICES = [
|
|
57
|
+
{ scope: "user", label: "User — ~/.agents/plugins, available in every project (recommended)" },
|
|
58
|
+
{ scope: "project", label: "Project — <repo>/.agents/plugins, committed and shared with collaborators" },
|
|
59
|
+
];
|
|
60
|
+
/**
|
|
61
|
+
* Ask where the plugin should land. Returns undefined when the user dismisses
|
|
62
|
+
* the selector, which cancels the install rather than guessing.
|
|
63
|
+
*
|
|
64
|
+
* Headless (no UI to ask through) resolves to `fallback` — the standing setting
|
|
65
|
+
* — so a scripted or `--print` run still installs somewhere predictable.
|
|
66
|
+
*/
|
|
67
|
+
async function promptForScope(ctx, fallback) {
|
|
68
|
+
if (!ctx.hasUI)
|
|
69
|
+
return fallback;
|
|
70
|
+
const labels = SCOPE_CHOICES.map((c) => c.label);
|
|
71
|
+
const picked = await ctx.ui.select("Install scope", labels);
|
|
72
|
+
if (picked === undefined)
|
|
73
|
+
return undefined;
|
|
74
|
+
return SCOPE_CHOICES.find((c) => c.label === picked)?.scope ?? fallback;
|
|
75
|
+
}
|
|
45
76
|
/**
|
|
46
77
|
* Chat styling for a listing.
|
|
47
78
|
*
|
|
@@ -66,8 +97,8 @@ function listHeader(theme, glyph, summary, hint) {
|
|
|
66
97
|
}
|
|
67
98
|
export function setupMarketplace(pi) {
|
|
68
99
|
pi.registerCommand("plugin", {
|
|
69
|
-
description: "Manage plugin marketplaces. /plugin marketplace add <git-url|path> | /plugin marketplace list | /plugin marketplace refresh | /plugin list | /plugin install <name> | /plugin remove <name> | /plugin publish <name> [--to <dir>]",
|
|
70
|
-
getArgumentCompletions: (prefix) => ["marketplace", "list", "install", "remove", "refresh", "publish"]
|
|
100
|
+
description: "Manage plugin marketplaces. /plugin marketplace add <git-url|path> | /plugin marketplace list | /plugin marketplace refresh | /plugin list | /plugin install <name> [--scope user|project] | /plugin remove <name> | /plugin trust [list] | /plugin untrust | /plugin publish <name> [--to <dir>]",
|
|
101
|
+
getArgumentCompletions: (prefix) => ["marketplace", "list", "install", "remove", "refresh", "publish", "trust", "untrust"]
|
|
71
102
|
.filter((s) => s.startsWith(prefix))
|
|
72
103
|
.map((s) => ({ value: s, label: s })),
|
|
73
104
|
handler: async (args, ctx) => {
|
|
@@ -176,23 +207,83 @@ export function setupMarketplace(pi) {
|
|
|
176
207
|
ctx.ui.notify(`${header}\n${body}\n\n${theme.fg("dim", " /plugin install <name> to add one")}`, "info");
|
|
177
208
|
return;
|
|
178
209
|
}
|
|
179
|
-
// ── install <name>
|
|
210
|
+
// ── install <name> [--scope user|project] ───────────────────────────
|
|
180
211
|
if (trimmed.startsWith("install")) {
|
|
181
|
-
const
|
|
212
|
+
const rest = trimmed.slice("install".length).trim();
|
|
213
|
+
const scopeMatch = /(?:^|\s)--scope[= ]\s*(\S+)/.exec(rest);
|
|
214
|
+
const name = rest.replace(/(?:^|\s)--scope[= ]\s*\S+/, "").trim();
|
|
182
215
|
if (!name) {
|
|
183
|
-
ctx.ui.notify("Usage: /plugin install <name>", "warning");
|
|
216
|
+
ctx.ui.notify("Usage: /plugin install <name> [--scope user|project]", "warning");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (scopeMatch && !isInstallScope(scopeMatch[1])) {
|
|
220
|
+
ctx.ui.notify(`Unknown scope "${scopeMatch[1]}". Use --scope user or --scope project.`, "warning");
|
|
184
221
|
return;
|
|
185
222
|
}
|
|
186
223
|
if (!findAvailablePlugin(cwd, name)) {
|
|
187
224
|
ctx.ui.notify(`Plugin "${name}" not found in any marketplace.`, "error");
|
|
188
225
|
return;
|
|
189
226
|
}
|
|
190
|
-
|
|
227
|
+
// An explicit --scope wins; otherwise ask, because this is the human
|
|
228
|
+
// path and the destination is a real choice (portable vs. committed to
|
|
229
|
+
// the repo). With no UI to ask through there is nobody to ask, so it
|
|
230
|
+
// falls back to the same setting the autonomous path uses.
|
|
231
|
+
const scope = scopeMatch
|
|
232
|
+
? scopeMatch[1]
|
|
233
|
+
: await promptForScope(ctx, SettingsManager.create(cwd, getAgentDir()).getPluginInstallScope());
|
|
234
|
+
if (!scope) {
|
|
235
|
+
ctx.ui.notify("Install cancelled.", "info");
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
const outcome = await installAvailablePlugin(cwd, name, getAgentDir(), { scope });
|
|
191
239
|
if (!outcome.installed) {
|
|
192
240
|
ctx.ui.notify(outcome.message, "error");
|
|
193
241
|
return;
|
|
194
242
|
}
|
|
195
|
-
|
|
243
|
+
// A person typing this command in this directory is the human act
|
|
244
|
+
// workspace trust asks for, so an explicit project-scope install grants
|
|
245
|
+
// it — otherwise the plugin you just chose to install here would load
|
|
246
|
+
// with its hooks withheld, which is nobody's idea of what happened.
|
|
247
|
+
// The autonomous path deliberately does not do this.
|
|
248
|
+
let trustNote = "";
|
|
249
|
+
if (scope === "project" && !isWorkspaceTrusted(cwd, getAgentDir())) {
|
|
250
|
+
trustWorkspace(cwd, getAgentDir());
|
|
251
|
+
trustNote = ` Trusted this workspace, so its plugins may run hooks and MCP servers here (\`/plugin untrust\` reverses it).`;
|
|
252
|
+
}
|
|
253
|
+
ctx.ui.notify(`${outcome.message}${trustNote} Reloading…`, "info");
|
|
254
|
+
await ctx.reload();
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
// ── trust / untrust ─────────────────────────────────────────────────
|
|
258
|
+
if (trimmed === "trust" || trimmed.startsWith("trust ")) {
|
|
259
|
+
const sub = trimmed.slice("trust".length).trim();
|
|
260
|
+
if (sub === "list") {
|
|
261
|
+
const workspaces = listTrustedWorkspaces(getAgentDir());
|
|
262
|
+
ctx.ui.notify(workspaces.length
|
|
263
|
+
? `Trusted workspaces:\n${workspaces.map((w) => `${w.path} (since ${w.at.slice(0, 10)})`).join("\n")}`
|
|
264
|
+
: "No trusted workspaces. Plugins in a working tree load skills and commands, but not hooks or MCP servers.", "info");
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
if (sub) {
|
|
268
|
+
ctx.ui.notify("Usage: /plugin trust | /plugin trust list | /plugin untrust", "warning");
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (isWorkspaceTrusted(cwd, getAgentDir())) {
|
|
272
|
+
ctx.ui.notify(`Already trusted: ${cwd}`, "info");
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
trustWorkspace(cwd, getAgentDir());
|
|
276
|
+
ctx.ui.notify(`Trusted ${cwd}. Plugins committed to this repository may now run hooks and MCP servers here, ` +
|
|
277
|
+
"including any added by a later pull. Reverse it with /plugin untrust. Reloading…", "info");
|
|
278
|
+
await ctx.reload();
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
if (trimmed === "untrust") {
|
|
282
|
+
if (!untrustWorkspace(cwd, getAgentDir())) {
|
|
283
|
+
ctx.ui.notify(`Not trusted: ${cwd}`, "info");
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
ctx.ui.notify(`Revoked trust for ${cwd}. Reloading…`, "info");
|
|
196
287
|
await ctx.reload();
|
|
197
288
|
return;
|
|
198
289
|
}
|
|
@@ -251,7 +342,8 @@ export function setupMarketplace(pi) {
|
|
|
251
342
|
await ctx.reload();
|
|
252
343
|
return;
|
|
253
344
|
}
|
|
254
|
-
ctx.ui.notify("Usage: /plugin marketplace add|list|refresh | /plugin list | /plugin install <name> | " +
|
|
345
|
+
ctx.ui.notify("Usage: /plugin marketplace add|list|refresh | /plugin list | /plugin install <name> [--scope user|project] | " +
|
|
346
|
+
"/plugin trust [list] | /plugin untrust | " +
|
|
255
347
|
"/plugin remove <name> | /plugin publish <name> [--to <marketplace-dir>]", "warning");
|
|
256
348
|
},
|
|
257
349
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"marketplace.js","sourceRoot":"","sources":["../../../src/extensions/core/marketplace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,4CAA4C,CAAC;AACvE,OAAO,EACN,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,eAAe,GACf,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,EACN,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,GACpB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EACN,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,qBAAqB,GACrB,MAAM,8CAA8C,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AAEnH,OAAO,EAAkB,MAAM,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAE/E,SAAS,WAAW,CAAC,GAAW,EAAW;IAC1C,OAAO,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAAA,CAClF;AAED;;;;;;;GAOG;AACH,SAAS,SAAS,CAAC,KAA6C,EAAa;IAC5E,OAAO;QACN,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC;QAC3C,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC;QACxC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC;QACvC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC;QACxC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC;KAC9C,CAAC;AAAA,CACF;AAED,2EAAqE;AACrE,SAAS,UAAU,CAClB,KAA6C,EAC7C,KAAa,EACb,OAAe,EACf,IAAa,EACJ;IACT,MAAM,IAAI,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;IAC1E,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAAA,CAChE;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAgB,EAAQ;IACxD,EAAE,CAAC,eAAe,CAAC,QAAQ,EAAE;QAC5B,WAAW,EACV,mOAAmO;QACpO,sBAAsB,EAAE,CAAC,MAAc,EAAE,EAAE,CAC1C,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,CAAC;aAChE,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;aACnC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QACvC,OAAO,EAAE,KAAK,EAAE,IAAY,EAAE,GAA4B,EAAiB,EAAE,CAAC;YAC7E,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;YAEpB,+JAAuE;YACvE,IAAI,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;gBACvC,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;gBAEvD,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;oBAClC,MAAM,OAAO,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC;oBAC5C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;wBAC1B,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,uEAAuE,EAAE,MAAM,CAAC,CAAC;wBAC/F,OAAO;oBACR,CAAC;oBACD,MAAM,KAAK,GAAG,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC;oBAC3B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;wBAC/B,MAAM,MAAM,GAAG,mBAAmB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;wBAC1C,OAAO;4BACN,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,CAAC,CAAC,QAAQ;4BAChC,KAAK,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;4BAClG,kEAAkE;4BAClE,6CAA6C;4BAC7C,OAAO,EAAE,CAAC,CAAC,QAAQ;yBACnB,CAAC;oBAAA,CACF,CAAC,CAAC;oBACH,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE;wBACnC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,OAAO;wBACvB,MAAM,EAAE,CAAC;wBACT,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC;qBACvB,CAAC,CAAC;oBACH,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,EAAE,cAAc,CAAC,YAAY,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;oBACrG,GAAG,CAAC,EAAE,CAAC,MAAM,CACZ,GAAG,MAAM,KAAK,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,uCAAuC,CAAC,EAAE,EACnF,MAAM,CACN,CAAC;oBACF,OAAO;gBACR,CAAC;gBAED,IAAI,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC3B,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;oBAC3C,IAAI,CAAC,GAAG,EAAE,CAAC;wBACV,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,+CAA+C,EAAE,SAAS,CAAC,CAAC;wBAC1E,OAAO;oBACR,CAAC;oBAED,IAAI,GAAW,CAAC;oBAChB,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;wBACtB,GAAG,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;wBAC/B,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;wBAC9C,SAAS,CAAC,oBAAoB,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;wBACvD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;wBACtE,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;4BACpB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,iBAAiB,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;4BACpE,OAAO;wBACR,CAAC;oBACF,CAAC;yBAAM,CAAC;wBACP,GAAG,GAAG,mBAAmB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;wBAC5E,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;4BACtB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,mBAAmB,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC;4BACjD,OAAO;wBACR,CAAC;oBACF,CAAC;oBAED,MAAM,MAAM,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;oBACxC,IAAI,CAAC,MAAM,EAAE,CAAC;wBACb,GAAG,CAAC,EAAE,CAAC,MAAM,CACZ,gGAAgG,EAChG,OAAO,CACP,CAAC;wBACF,OAAO;oBACR,CAAC;oBAED,MAAM,OAAO,GAAG,oBAAoB,CAAC,oBAAoB,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,GAAG,CAAC,CAAC;oBAC/F,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;oBACrC,qBAAqB,CAAC,oBAAoB,EAAE,EAAE,OAAO,CAAC,CAAC;oBACvD,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,sBAAsB,MAAM,CAAC,IAAI,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,cAAc,EAAE,MAAM,CAAC,CAAC;oBAClG,OAAO;gBACR,CAAC;gBAED,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;oBACvB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,mCAAiC,EAAE,MAAM,CAAC,CAAC;oBACzD,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,mBAAmB,CAAC,GAAG,CAAC,CAAC;oBAC7D,MAAM,KAAK,GAAa,EAAE,CAAC;oBAC3B,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;wBAAE,KAAK,CAAC,IAAI,CAAC,cAAc,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;oBAC3E,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;wBAAE,KAAK,CAAC,IAAI,CAAC,sBAAsB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;oBAC7E,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,qBAAqB,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;oBACjG,OAAO;gBACR,CAAC;gBAED,GAAG,CAAC,EAAE,CAAC,MAAM,CACZ,wGAAwG,EACxG,SAAS,CACT,CAAC;gBACF,OAAO;YACR,CAAC;YAED,+JAAuE;YACvE,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;gBAC1C,MAAM,SAAS,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;gBAC5C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC5B,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,gDAAgD,EAAE,MAAM,CAAC,CAAC;oBACxE,OAAO;gBACR,CAAC;gBACD,MAAM,KAAK,GAAG,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC;gBAC3B,wEAAwE;gBACxE,sEAAsE;gBACtE,+DAA+D;gBAC/D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBACtE,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;gBAC/D,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,EAAE;oBAC/B,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,OAAO;oBACvB,MAAM,EAAE,CAAC;oBACT,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC;iBACvB,CAAC,CAAC;gBACH,MAAM,cAAc,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;gBAC7E,MAAM,OAAO,GACZ,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,WAAW,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE;oBACtF,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,WAAW,IAAI,cAAc,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBAC3E,MAAM,MAAM,GAAG,UAAU,CACxB,KAAK,EACL,cAAc,CAAC,OAAO,EACtB,OAAO,EACP,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,uBAAqB,CAAC,CAAC,CAAC,SAAS,CACtD,CAAC;gBACF,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,MAAM,KAAK,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,qCAAqC,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;gBACzG,OAAO;YACR,CAAC;YAED,+KAAuE;YACvE,IAAI,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBACnC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;gBACpD,IAAI,CAAC,IAAI,EAAE,CAAC;oBACX,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,+BAA+B,EAAE,SAAS,CAAC,CAAC;oBAC1D,OAAO;gBACR,CAAC;gBACD,IAAI,CAAC,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC;oBACrC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,WAAW,IAAI,iCAAiC,EAAE,OAAO,CAAC,CAAC;oBACzE,OAAO;gBACR,CAAC;gBACD,MAAM,OAAO,GAAG,MAAM,sBAAsB,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBACxD,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;oBACxB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;oBACxC,OAAO;gBACR,CAAC;gBACD,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,eAAa,EAAE,MAAM,CAAC,CAAC;gBACvD,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;gBACnB,OAAO;YACR,CAAC;YAED,6HAAuE;YACvE,IAAI,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBACnC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;gBACpD,MAAM,OAAO,GAAG,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAClD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;gBAC3D,IAAI,CAAC,IAAI,EAAE,CAAC;oBACX,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,wDAAwD,EAAE,SAAS,CAAC,CAAC;oBACnF,OAAO;gBACR,CAAC;gBACD,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBACpC,IAAI,CAAC,MAAM,EAAE,CAAC;oBACb,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,oBAAoB,IAAI,kCAAkC,EAAE,OAAO,CAAC,CAAC;oBACnF,OAAO;gBACR,CAAC;gBAED,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,cAAc,IAAI,iCAA6B,EAAE,MAAM,CAAC,CAAC;gBACvE,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC;gBAC3C,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;oBAChB,uEAAuE;oBACvE,qEAAqE;oBACrE,oCAAoC;oBACpC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,yBAAyB,MAAM,CAAC,YAAY,EAAE,EAAE,OAAO,CAAC,CAAC;oBACvE,OAAO;gBACR,CAAC;gBAED,IAAI,CAAC,OAAO,EAAE,CAAC;oBACd,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC;wBAC1C,CAAC,CAAC,gHAAgH;wBAClH,CAAC,CAAC,EAAE,CAAC;oBACN,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,YAAY,GAAG,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;oBACvD,OAAO;gBACR,CAAC;gBAED,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;gBACzE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;oBAChB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;oBACvC,OAAO;gBACR,CAAC;gBACD,GAAG,CAAC,EAAE,CAAC,MAAM,CACZ,GAAG,MAAM,CAAC,OAAO,0EAAwE;oBACxF,0EAA0E,EAC3E,MAAM,CACN,CAAC;gBACF,OAAO;YACR,CAAC;YAED,iLAAuE;YACvE,IAAI,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAClC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;gBACnD,IAAI,CAAC,IAAI,EAAE,CAAC;oBACX,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,8BAA8B,EAAE,SAAS,CAAC,CAAC;oBACzD,OAAO;gBACR,CAAC;gBACD,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBAC3C,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;oBACtB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;oBACvC,OAAO;gBACR,CAAC;gBACD,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,eAAa,EAAE,MAAM,CAAC,CAAC;gBACvD,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;gBACnB,OAAO;YACR,CAAC;YAED,GAAG,CAAC,EAAE,CAAC,MAAM,CACZ,wFAAwF;gBACvF,yEAAyE,EAC1E,SAAS,CACT,CAAC;QAAA,CACF;KACD,CAAC,CAAC;AAAA,CACH","sourcesContent":["/**\n * /plugin — marketplace add/list + plugin install/list/remove (the human path).\n *\n * `/plugin` installs plugins from marketplaces (a git repo or local dir with a\n * native `.agents-plugin/marketplace.json`, Claude `.claude-plugin/marketplace.json`,\n * or Copilot-style `.github/marketplace.json` index). Installed plugins are placed\n * in the global consumption home (`~/.agents/plugins/<name>`) and loaded by the\n * plugin loader after a reload.\n *\n * Adding a marketplace is the human trust boundary and stays here; the shared\n * mechanics (discovery, install, remove, the bundled default marketplace) live in\n * `core/extensions/plugins/install.ts` so this command and the model-facing\n * lifecycle tools never drift.\n *\n * /plugin marketplace add <git-url|path>\n * /plugin marketplace list\n * /plugin marketplace refresh re-fetch every cached index now\n * /plugin list list available plugins across marketplaces\n * /plugin install <name>\n * /plugin remove <name>\n * /plugin publish <name> [--to <marketplace-dir>]\n *\n * `publish` is the human end of the publish lane (§3.2). The model can package a\n * plugin — gates, README, index entry — but never publish it, because an agent\n * that can push executable code into a marketplace other agents install from\n * unattended is a supply-chain compromise primitive. So the last step is a\n * command a person types, and even then it stops at the machine boundary: with\n * `--to` it copies the plugin into a local marketplace checkout and updates that\n * index, leaving an uncommitted diff to review. Committing and opening the PR\n * stay manual.\n */\n\nimport { existsSync, mkdirSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { CATEGORY_GLYPH, SEGMENT_SEP } from \"../../core/brand.js\";\nimport { getPlugin } from \"../../core/extensions/plugins/authoring.js\";\nimport {\n\tfindAvailablePlugin,\n\tinstallAvailablePlugin,\n\tlistAvailablePlugins,\n\tlistInstalledPlugins,\n\treadMarketplaceRecords,\n\trefreshMarketplaces,\n\tuninstallPlugin,\n} from \"../../core/extensions/plugins/install.js\";\nimport { availablePluginGroups } from \"../../core/extensions/plugins/listing.js\";\nimport {\n\tmarketplaceCacheDir,\n\tmarketplaceCacheRoot,\n\tmarketplaceStorePath,\n} from \"../../core/extensions/plugins/locations.js\";\nimport {\n\tparseMarketplaceDir,\n\treadMarketplaceStore,\n\tresolvePluginSource,\n\twriteMarketplaceStore,\n} from \"../../core/extensions/plugins/marketplace.js\";\nimport { entryNeedsSource, packagePlugin, stageIntoMarketplace } from \"../../core/extensions/plugins/packaging.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\nimport { type ListStyle, plural, renderList } from \"../../core/format-list.js\";\n\nfunction isGitSource(loc: string): boolean {\n\treturn /^https?:\\/\\//.test(loc) || loc.startsWith(\"git@\") || loc.endsWith(\".git\");\n}\n\n/**\n * Chat styling for a listing.\n *\n * The name stays in the terminal's default foreground and everything else steps\n * down from it, so the column you scan is the brightest thing on the row. Note\n * this only reaches the screen because `showStatus` passes pre-styled messages\n * through instead of wrapping them in a blanket dim.\n */\nfunction listStyle(theme: ExtensionCommandContext[\"ui\"][\"theme\"]): ListStyle {\n\treturn {\n\t\tmarker: (text) => theme.fg(\"success\", text),\n\t\tfacts: (text) => theme.fg(\"muted\", text),\n\t\tdetail: (text) => theme.fg(\"dim\", text),\n\t\ttrailer: (text) => theme.fg(\"dim\", text),\n\t\tgroupTitle: (text) => theme.fg(\"mdLink\", text),\n\t};\n}\n\n/** `⬡ 4 plugins …` — the counted header every listing opens with. */\nfunction listHeader(\n\ttheme: ExtensionCommandContext[\"ui\"][\"theme\"],\n\tglyph: string,\n\tsummary: string,\n\thint?: string,\n): string {\n\tconst head = `${theme.fg(\"accent\", glyph)} ${theme.fg(\"muted\", summary)}`;\n\treturn hint ? `${head}\\n${theme.fg(\"dim\", ` ${hint}`)}` : head;\n}\n\nexport function setupMarketplace(pi: ExtensionAPI): void {\n\tpi.registerCommand(\"plugin\", {\n\t\tdescription:\n\t\t\t\"Manage plugin marketplaces. /plugin marketplace add <git-url|path> | /plugin marketplace list | /plugin marketplace refresh | /plugin list | /plugin install <name> | /plugin remove <name> | /plugin publish <name> [--to <dir>]\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\t[\"marketplace\", \"list\", \"install\", \"remove\", \"refresh\", \"publish\"]\n\t\t\t\t.filter((s) => s.startsWith(prefix))\n\t\t\t\t.map((s) => ({ value: s, label: s })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst trimmed = args.trim();\n\t\t\tconst cwd = ctx.cwd;\n\n\t\t\t// ── marketplace add / list ──────────────────────────────────────────\n\t\t\tif (trimmed.startsWith(\"marketplace\")) {\n\t\t\t\tconst sub = trimmed.slice(\"marketplace\".length).trim();\n\n\t\t\t\tif (sub === \"list\" || sub === \"\") {\n\t\t\t\t\tconst records = readMarketplaceRecords(cwd);\n\t\t\t\t\tif (records.length === 0) {\n\t\t\t\t\t\tctx.ui.notify(\"No marketplaces. Add one with /plugin marketplace add <git-url|path>.\", \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst theme = ctx.ui.theme;\n\t\t\t\t\tconst rows = records.map((r) => {\n\t\t\t\t\t\tconst market = parseMarketplaceDir(r.dir);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tname: market?.name ?? r.location,\n\t\t\t\t\t\t\tfacts: [plural(market?.plugins.length ?? 0, \"plugin\"), (market?.supportPlatform ?? []).join(\", \")],\n\t\t\t\t\t\t\t// The location is the longest and least-scanned token on the row;\n\t\t\t\t\t\t\t// on its own line it stops forcing the wrap.\n\t\t\t\t\t\t\ttrailer: r.location,\n\t\t\t\t\t\t};\n\t\t\t\t\t});\n\t\t\t\t\tconst body = renderList([{ rows }], {\n\t\t\t\t\t\tcolumns: ctx.ui.columns,\n\t\t\t\t\t\tindent: 2,\n\t\t\t\t\t\tstyle: listStyle(theme),\n\t\t\t\t\t});\n\t\t\t\t\tconst header = listHeader(theme, CATEGORY_GLYPH.marketplaces, plural(records.length, \"marketplace\"));\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`${header}\\n${body}\\n\\n${theme.fg(\"dim\", \" /plugin list to see what they offer\")}`,\n\t\t\t\t\t\t\"info\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (sub.startsWith(\"add\")) {\n\t\t\t\t\tconst loc = sub.slice(\"add\".length).trim();\n\t\t\t\t\tif (!loc) {\n\t\t\t\t\t\tctx.ui.notify(\"Usage: /plugin marketplace add <git-url|path>\", \"warning\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet dir: string;\n\t\t\t\t\tif (isGitSource(loc)) {\n\t\t\t\t\t\tdir = marketplaceCacheDir(loc);\n\t\t\t\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t\t\t\t\tmkdirSync(marketplaceCacheRoot(), { recursive: true });\n\t\t\t\t\t\tconst res = await pi.exec(\"git\", [\"clone\", \"--depth\", \"1\", loc, dir]);\n\t\t\t\t\t\tif (res.code !== 0) {\n\t\t\t\t\t\t\tctx.ui.notify(`Clone failed: ${res.stderr || res.stdout}`, \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdir = resolvePluginSource(loc, cwd).kind === \"local\" ? join(cwd, loc) : loc;\n\t\t\t\t\t\tif (!existsSync(dir)) {\n\t\t\t\t\t\t\tctx.ui.notify(`Path not found: ${dir}`, \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst market = parseMarketplaceDir(dir);\n\t\t\t\t\tif (!market) {\n\t\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t\t\"No marketplace manifest found (.agents-plugin/, .claude-plugin/, or .github/marketplace.json).\",\n\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst records = readMarketplaceStore(marketplaceStorePath()).filter((r) => r.location !== loc);\n\t\t\t\t\trecords.push({ location: loc, dir });\n\t\t\t\t\twriteMarketplaceStore(marketplaceStorePath(), records);\n\t\t\t\t\tctx.ui.notify(`Added marketplace \"${market.name}\" (${market.plugins.length} plugin(s)).`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (sub === \"refresh\") {\n\t\t\t\t\tctx.ui.notify(\"Refreshing marketplace indices…\", \"info\");\n\t\t\t\t\tconst { refreshed, errors } = await refreshMarketplaces(cwd);\n\t\t\t\t\tconst lines: string[] = [];\n\t\t\t\t\tif (refreshed.length > 0) lines.push(`Refreshed: ${refreshed.join(\", \")}`);\n\t\t\t\t\tif (errors.length > 0) lines.push(`Could not refresh: ${errors.join(\"; \")}`);\n\t\t\t\t\tctx.ui.notify(lines.join(\"\\n\") || \"Nothing to refresh.\", errors.length > 0 ? \"warning\" : \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\"Usage: /plugin marketplace add <git-url|path> | /plugin marketplace list | /plugin marketplace refresh\",\n\t\t\t\t\t\"warning\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── list available plugins ──────────────────────────────────────────\n\t\t\tif (trimmed === \"list\" || trimmed === \"\") {\n\t\t\t\tconst available = listAvailablePlugins(cwd);\n\t\t\t\tif (available.length === 0) {\n\t\t\t\t\tctx.ui.notify(\"No plugins available. Add a marketplace first.\", \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst theme = ctx.ui.theme;\n\t\t\t\t// Installed state is the fact the old listing could not answer: without\n\t\t\t\t// it, /plugin install on something already present silently re-clones\n\t\t\t\t// over it, discarding any local edits to the plugin directory.\n\t\t\t\tconst installed = new Set(listInstalledPlugins(cwd).map((p) => p.id));\n\t\t\t\tconst groups = availablePluginGroups(available, { installed });\n\t\t\t\tconst body = renderList(groups, {\n\t\t\t\t\tcolumns: ctx.ui.columns,\n\t\t\t\t\tindent: 2,\n\t\t\t\t\tstyle: listStyle(theme),\n\t\t\t\t});\n\t\t\t\tconst installedCount = available.filter((p) => installed.has(p.name)).length;\n\t\t\t\tconst summary =\n\t\t\t\t\t`${plural(available.length, \"plugin\")} across ${plural(groups.length, \"marketplace\")}` +\n\t\t\t\t\t(installedCount > 0 ? ` ${SEGMENT_SEP} ${installedCount} installed` : \"\");\n\t\t\t\tconst header = listHeader(\n\t\t\t\t\ttheme,\n\t\t\t\t\tCATEGORY_GLYPH.plugins,\n\t\t\t\t\tsummary,\n\t\t\t\t\tinstalledCount > 0 ? \"✓ already installed\" : undefined,\n\t\t\t\t);\n\t\t\t\tctx.ui.notify(`${header}\\n${body}\\n\\n${theme.fg(\"dim\", \" /plugin install <name> to add one\")}`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── install <name> ──────────────────────────────────────────────────\n\t\t\tif (trimmed.startsWith(\"install\")) {\n\t\t\t\tconst name = trimmed.slice(\"install\".length).trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin install <name>\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!findAvailablePlugin(cwd, name)) {\n\t\t\t\t\tctx.ui.notify(`Plugin \"${name}\" not found in any marketplace.`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst outcome = await installAvailablePlugin(cwd, name);\n\t\t\t\tif (!outcome.installed) {\n\t\t\t\t\tctx.ui.notify(outcome.message, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(`${outcome.message} Reloading…`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── publish <name> [--to <marketplace-dir>] ─────────────────────────\n\t\t\tif (trimmed.startsWith(\"publish\")) {\n\t\t\t\tconst rest = trimmed.slice(\"publish\".length).trim();\n\t\t\t\tconst toMatch = /(?:^|\\s)--to\\s+(\\S+)/.exec(rest);\n\t\t\t\tconst name = rest.replace(/(?:^|\\s)--to\\s+\\S+/, \"\").trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin publish <name> [--to <marketplace-dir>]\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst plugin = getPlugin(cwd, name);\n\t\t\t\tif (!plugin) {\n\t\t\t\t\tctx.ui.notify(`No plugin named \"${name}\" is installed or authored here.`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tctx.ui.notify(`Packaging \"${name}\" — running publish checks…`, \"info\");\n\t\t\t\tconst result = await packagePlugin(plugin);\n\t\t\t\tif (!result.ok) {\n\t\t\t\t\t// A red gate blocks staging too: the point of the strict run is that a\n\t\t\t\t\t// plugin other people install has passed it, and staging is the step\n\t\t\t\t\t// that puts it on the path to them.\n\t\t\t\t\tctx.ui.notify(`Not publishable yet.\\n${result.instructions}`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (!toMatch) {\n\t\t\t\t\tconst hint = entryNeedsSource(result.entry)\n\t\t\t\t\t\t? \"\\nRe-run with --to <marketplace-dir> to vendor it into a local marketplace checkout (which fills `source` in).\"\n\t\t\t\t\t\t: \"\";\n\t\t\t\t\tctx.ui.notify(`${result.instructions}${hint}`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst staged = stageIntoMarketplace(plugin, result.platform, toMatch[1]);\n\t\t\t\tif (!staged.ok) {\n\t\t\t\t\tctx.ui.notify(staged.message, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`${staged.message}\\n\\nReview the diff, then commit and open the pull request yourself — ` +\n\t\t\t\t\t\t\"hoocode deliberately stops short of pushing a plugin into a marketplace.\",\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── remove <name> ───────────────────────────────────────────────────\n\t\t\tif (trimmed.startsWith(\"remove\")) {\n\t\t\t\tconst name = trimmed.slice(\"remove\".length).trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin remove <name>\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst outcome = uninstallPlugin(cwd, name);\n\t\t\t\tif (!outcome.removed) {\n\t\t\t\t\tctx.ui.notify(outcome.message, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(`${outcome.message} Reloading…`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tctx.ui.notify(\n\t\t\t\t\"Usage: /plugin marketplace add|list|refresh | /plugin list | /plugin install <name> | \" +\n\t\t\t\t\t\"/plugin remove <name> | /plugin publish <name> [--to <marketplace-dir>]\",\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
|
|
1
|
+
{"version":3,"file":"marketplace.js","sourceRoot":"","sources":["../../../src/extensions/core/marketplace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,MAAM,EAAE,MAAM,SAAS,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAC9C,OAAO,EAAE,cAAc,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,4CAA4C,CAAC;AACvE,OAAO,EACN,mBAAmB,EACnB,sBAAsB,EACtB,oBAAoB,EACpB,oBAAoB,EACpB,sBAAsB,EACtB,mBAAmB,EACnB,eAAe,GACf,MAAM,0CAA0C,CAAC;AAClD,OAAO,EAAE,qBAAqB,EAAE,MAAM,0CAA0C,CAAC;AACjF,OAAO,EACN,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,GAEpB,MAAM,4CAA4C,CAAC;AACpD,OAAO,EACN,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,qBAAqB,GACrB,MAAM,8CAA8C,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,4CAA4C,CAAC;AACnH,OAAO,EACN,kBAAkB,EAClB,qBAAqB,EACrB,cAAc,EACd,gBAAgB,GAChB,MAAM,wCAAwC,CAAC;AAEhD,OAAO,EAAkB,MAAM,EAAE,UAAU,EAAE,MAAM,2BAA2B,CAAC;AAC/E,OAAO,EAAE,eAAe,EAAE,MAAM,gCAAgC,CAAC;AAEjE,SAAS,WAAW,CAAC,GAAW,EAAW;IAC1C,OAAO,cAAc,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;AAAA,CAClF;AAED,SAAS,cAAc,CAAC,KAAa,EAA+B;IACnE,OAAO,KAAK,KAAK,MAAM,IAAI,KAAK,KAAK,SAAS,CAAC;AAAA,CAC/C;AAED,mFAAmF;AACnF,MAAM,aAAa,GAAgE;IAClF,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,sEAAoE,EAAE;IAC9F,EAAE,KAAK,EAAE,SAAS,EAAE,KAAK,EAAE,6EAA2E,EAAE;CACxG,CAAC;AAEF;;;;;;GAMG;AACH,KAAK,UAAU,cAAc,CAC5B,GAA4B,EAC5B,QAA4B,EACc;IAC1C,IAAI,CAAC,GAAG,CAAC,KAAK;QAAE,OAAO,QAAQ,CAAC;IAChC,MAAM,MAAM,GAAG,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;IACjD,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,eAAe,EAAE,MAAM,CAAC,CAAC;IAC5D,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,SAAS,CAAC;IAC3C,OAAO,aAAa,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,MAAM,CAAC,EAAE,KAAK,IAAI,QAAQ,CAAC;AAAA,CACxE;AAED;;;;;;;GAOG;AACH,SAAS,SAAS,CAAC,KAA6C,EAAa;IAC5E,OAAO;QACN,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,SAAS,EAAE,IAAI,CAAC;QAC3C,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,IAAI,CAAC;QACxC,MAAM,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC;QACvC,OAAO,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,IAAI,CAAC;QACxC,UAAU,EAAE,CAAC,IAAI,EAAE,EAAE,CAAC,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,IAAI,CAAC;KAC9C,CAAC;AAAA,CACF;AAED,2EAAqE;AACrE,SAAS,UAAU,CAClB,KAA6C,EAC7C,KAAa,EACb,OAAe,EACf,IAAa,EACJ;IACT,MAAM,IAAI,GAAG,GAAG,KAAK,CAAC,EAAE,CAAC,QAAQ,EAAE,KAAK,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,CAAC;IAC1E,OAAO,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,KAAK,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AAAA,CAChE;AAED,MAAM,UAAU,gBAAgB,CAAC,EAAgB,EAAQ;IACxD,EAAE,CAAC,eAAe,CAAC,QAAQ,EAAE;QAC5B,WAAW,EACV,mSAAmS;QACpS,sBAAsB,EAAE,CAAC,MAAc,EAAE,EAAE,CAC1C,CAAC,aAAa,EAAE,MAAM,EAAE,SAAS,EAAE,QAAQ,EAAE,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,SAAS,CAAC;aACpF,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC;aACnC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QACvC,OAAO,EAAE,KAAK,EAAE,IAAY,EAAE,GAA4B,EAAiB,EAAE,CAAC;YAC7E,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;YAC5B,MAAM,GAAG,GAAG,GAAG,CAAC,GAAG,CAAC;YAEpB,+JAAuE;YACvE,IAAI,OAAO,CAAC,UAAU,CAAC,aAAa,CAAC,EAAE,CAAC;gBACvC,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;gBAEvD,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,EAAE,EAAE,CAAC;oBAClC,MAAM,OAAO,GAAG,sBAAsB,CAAC,GAAG,CAAC,CAAC;oBAC5C,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;wBAC1B,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,uEAAuE,EAAE,MAAM,CAAC,CAAC;wBAC/F,OAAO;oBACR,CAAC;oBACD,MAAM,KAAK,GAAG,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC;oBAC3B,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC;wBAC/B,MAAM,MAAM,GAAG,mBAAmB,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;wBAC1C,OAAO;4BACN,IAAI,EAAE,MAAM,EAAE,IAAI,IAAI,CAAC,CAAC,QAAQ;4BAChC,KAAK,EAAE,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,MAAM,IAAI,CAAC,EAAE,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,eAAe,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;4BAClG,kEAAkE;4BAClE,6CAA6C;4BAC7C,OAAO,EAAE,CAAC,CAAC,QAAQ;yBACnB,CAAC;oBAAA,CACF,CAAC,CAAC;oBACH,MAAM,IAAI,GAAG,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE;wBACnC,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,OAAO;wBACvB,MAAM,EAAE,CAAC;wBACT,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC;qBACvB,CAAC,CAAC;oBACH,MAAM,MAAM,GAAG,UAAU,CAAC,KAAK,EAAE,cAAc,CAAC,YAAY,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,aAAa,CAAC,CAAC,CAAC;oBACrG,GAAG,CAAC,EAAE,CAAC,MAAM,CACZ,GAAG,MAAM,KAAK,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,uCAAuC,CAAC,EAAE,EACnF,MAAM,CACN,CAAC;oBACF,OAAO;gBACR,CAAC;gBAED,IAAI,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC;oBAC3B,MAAM,GAAG,GAAG,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;oBAC3C,IAAI,CAAC,GAAG,EAAE,CAAC;wBACV,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,+CAA+C,EAAE,SAAS,CAAC,CAAC;wBAC1E,OAAO;oBACR,CAAC;oBAED,IAAI,GAAW,CAAC;oBAChB,IAAI,WAAW,CAAC,GAAG,CAAC,EAAE,CAAC;wBACtB,GAAG,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;wBAC/B,MAAM,CAAC,GAAG,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;wBAC9C,SAAS,CAAC,oBAAoB,EAAE,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;wBACvD,MAAM,GAAG,GAAG,MAAM,EAAE,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC;wBACtE,IAAI,GAAG,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;4BACpB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,iBAAiB,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,EAAE,OAAO,CAAC,CAAC;4BACpE,OAAO;wBACR,CAAC;oBACF,CAAC;yBAAM,CAAC;wBACP,GAAG,GAAG,mBAAmB,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,IAAI,KAAK,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;wBAC5E,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;4BACtB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,mBAAmB,GAAG,EAAE,EAAE,OAAO,CAAC,CAAC;4BACjD,OAAO;wBACR,CAAC;oBACF,CAAC;oBAED,MAAM,MAAM,GAAG,mBAAmB,CAAC,GAAG,CAAC,CAAC;oBACxC,IAAI,CAAC,MAAM,EAAE,CAAC;wBACb,GAAG,CAAC,EAAE,CAAC,MAAM,CACZ,gGAAgG,EAChG,OAAO,CACP,CAAC;wBACF,OAAO;oBACR,CAAC;oBAED,MAAM,OAAO,GAAG,oBAAoB,CAAC,oBAAoB,EAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,GAAG,CAAC,CAAC;oBAC/F,OAAO,CAAC,IAAI,CAAC,EAAE,QAAQ,EAAE,GAAG,EAAE,GAAG,EAAE,CAAC,CAAC;oBACrC,qBAAqB,CAAC,oBAAoB,EAAE,EAAE,OAAO,CAAC,CAAC;oBACvD,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,sBAAsB,MAAM,CAAC,IAAI,MAAM,MAAM,CAAC,OAAO,CAAC,MAAM,cAAc,EAAE,MAAM,CAAC,CAAC;oBAClG,OAAO;gBACR,CAAC;gBAED,IAAI,GAAG,KAAK,SAAS,EAAE,CAAC;oBACvB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,mCAAiC,EAAE,MAAM,CAAC,CAAC;oBACzD,MAAM,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,MAAM,mBAAmB,CAAC,GAAG,CAAC,CAAC;oBAC7D,MAAM,KAAK,GAAa,EAAE,CAAC;oBAC3B,IAAI,SAAS,CAAC,MAAM,GAAG,CAAC;wBAAE,KAAK,CAAC,IAAI,CAAC,cAAc,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;oBAC3E,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC;wBAAE,KAAK,CAAC,IAAI,CAAC,sBAAsB,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;oBAC7E,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,qBAAqB,EAAE,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;oBACjG,OAAO;gBACR,CAAC;gBAED,GAAG,CAAC,EAAE,CAAC,MAAM,CACZ,wGAAwG,EACxG,SAAS,CACT,CAAC;gBACF,OAAO;YACR,CAAC;YAED,+JAAuE;YACvE,IAAI,OAAO,KAAK,MAAM,IAAI,OAAO,KAAK,EAAE,EAAE,CAAC;gBAC1C,MAAM,SAAS,GAAG,oBAAoB,CAAC,GAAG,CAAC,CAAC;gBAC5C,IAAI,SAAS,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;oBAC5B,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,gDAAgD,EAAE,MAAM,CAAC,CAAC;oBACxE,OAAO;gBACR,CAAC;gBACD,MAAM,KAAK,GAAG,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC;gBAC3B,wEAAwE;gBACxE,sEAAsE;gBACtE,+DAA+D;gBAC/D,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC,oBAAoB,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;gBACtE,MAAM,MAAM,GAAG,qBAAqB,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,CAAC,CAAC;gBAC/D,MAAM,IAAI,GAAG,UAAU,CAAC,MAAM,EAAE;oBAC/B,OAAO,EAAE,GAAG,CAAC,EAAE,CAAC,OAAO;oBACvB,MAAM,EAAE,CAAC;oBACT,KAAK,EAAE,SAAS,CAAC,KAAK,CAAC;iBACvB,CAAC,CAAC;gBACH,MAAM,cAAc,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC;gBAC7E,MAAM,OAAO,GACZ,GAAG,MAAM,CAAC,SAAS,CAAC,MAAM,EAAE,QAAQ,CAAC,WAAW,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,aAAa,CAAC,EAAE;oBACtF,CAAC,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,WAAW,IAAI,cAAc,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;gBAC3E,MAAM,MAAM,GAAG,UAAU,CACxB,KAAK,EACL,cAAc,CAAC,OAAO,EACtB,OAAO,EACP,cAAc,GAAG,CAAC,CAAC,CAAC,CAAC,uBAAqB,CAAC,CAAC,CAAC,SAAS,CACtD,CAAC;gBACF,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,MAAM,KAAK,IAAI,OAAO,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,qCAAqC,CAAC,EAAE,EAAE,MAAM,CAAC,CAAC;gBACzG,OAAO;YACR,CAAC;YAED,iIAAuE;YACvE,IAAI,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBACnC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;gBACpD,MAAM,UAAU,GAAG,6BAA6B,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAC5D,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,2BAA2B,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;gBAClE,IAAI,CAAC,IAAI,EAAE,CAAC;oBACX,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,sDAAsD,EAAE,SAAS,CAAC,CAAC;oBACjF,OAAO;gBACR,CAAC;gBACD,IAAI,UAAU,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;oBAClD,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,kBAAkB,UAAU,CAAC,CAAC,CAAC,yCAAyC,EAAE,SAAS,CAAC,CAAC;oBACnG,OAAO;gBACR,CAAC;gBACD,IAAI,CAAC,mBAAmB,CAAC,GAAG,EAAE,IAAI,CAAC,EAAE,CAAC;oBACrC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,WAAW,IAAI,iCAAiC,EAAE,OAAO,CAAC,CAAC;oBACzE,OAAO;gBACR,CAAC;gBAED,qEAAqE;gBACrE,uEAAuE;gBACvE,qEAAqE;gBACrE,2DAA2D;gBAC3D,MAAM,KAAK,GAAG,UAAU;oBACvB,CAAC,CAAE,UAAU,CAAC,CAAC,CAAwB;oBACvC,CAAC,CAAC,MAAM,cAAc,CAAC,GAAG,EAAE,eAAe,CAAC,MAAM,CAAC,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC,qBAAqB,EAAE,CAAC,CAAC;gBACjG,IAAI,CAAC,KAAK,EAAE,CAAC;oBACZ,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC;oBAC5C,OAAO;gBACR,CAAC;gBAED,MAAM,OAAO,GAAG,MAAM,sBAAsB,CAAC,GAAG,EAAE,IAAI,EAAE,WAAW,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC;gBAClF,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,CAAC;oBACxB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;oBACxC,OAAO;gBACR,CAAC;gBAED,kEAAkE;gBAClE,wEAAwE;gBACxE,wEAAsE;gBACtE,oEAAoE;gBACpE,qDAAqD;gBACrD,IAAI,SAAS,GAAG,EAAE,CAAC;gBACnB,IAAI,KAAK,KAAK,SAAS,IAAI,CAAC,kBAAkB,CAAC,GAAG,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;oBACpE,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC;oBACnC,SAAS,GAAG,+GAA+G,CAAC;gBAC7H,CAAC;gBACD,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,GAAG,SAAS,eAAa,EAAE,MAAM,CAAC,CAAC;gBACnE,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;gBACnB,OAAO;YACR,CAAC;YAED,6KAAuE;YACvE,IAAI,OAAO,KAAK,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBACzD,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;gBACjD,IAAI,GAAG,KAAK,MAAM,EAAE,CAAC;oBACpB,MAAM,UAAU,GAAG,qBAAqB,CAAC,WAAW,EAAE,CAAC,CAAC;oBACxD,GAAG,CAAC,EAAE,CAAC,MAAM,CACZ,UAAU,CAAC,MAAM;wBAChB,CAAC,CAAC,wBAAwB,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,WAAW,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE;wBACtG,CAAC,CAAC,0GAA0G,EAC7G,MAAM,CACN,CAAC;oBACF,OAAO;gBACR,CAAC;gBACD,IAAI,GAAG,EAAE,CAAC;oBACT,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,6DAA6D,EAAE,SAAS,CAAC,CAAC;oBACxF,OAAO;gBACR,CAAC;gBACD,IAAI,kBAAkB,CAAC,GAAG,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;oBAC5C,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,oBAAoB,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;oBACjD,OAAO;gBACR,CAAC;gBACD,cAAc,CAAC,GAAG,EAAE,WAAW,EAAE,CAAC,CAAC;gBACnC,GAAG,CAAC,EAAE,CAAC,MAAM,CACZ,WAAW,GAAG,iFAAiF;oBAC9F,oFAAkF,EACnF,MAAM,CACN,CAAC;gBACF,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;gBACnB,OAAO;YACR,CAAC;YAED,IAAI,OAAO,KAAK,SAAS,EAAE,CAAC;gBAC3B,IAAI,CAAC,gBAAgB,CAAC,GAAG,EAAE,WAAW,EAAE,CAAC,EAAE,CAAC;oBAC3C,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,gBAAgB,GAAG,EAAE,EAAE,MAAM,CAAC,CAAC;oBAC7C,OAAO;gBACR,CAAC;gBACD,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,qBAAqB,GAAG,gBAAc,EAAE,MAAM,CAAC,CAAC;gBAC9D,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;gBACnB,OAAO;YACR,CAAC;YAED,6HAAuE;YACvE,IAAI,OAAO,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;gBACnC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,SAAS,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;gBACpD,MAAM,OAAO,GAAG,sBAAsB,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;gBAClD,MAAM,IAAI,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC;gBAC3D,IAAI,CAAC,IAAI,EAAE,CAAC;oBACX,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,wDAAwD,EAAE,SAAS,CAAC,CAAC;oBACnF,OAAO;gBACR,CAAC;gBACD,MAAM,MAAM,GAAG,SAAS,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBACpC,IAAI,CAAC,MAAM,EAAE,CAAC;oBACb,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,oBAAoB,IAAI,kCAAkC,EAAE,OAAO,CAAC,CAAC;oBACnF,OAAO;gBACR,CAAC;gBAED,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,cAAc,IAAI,iCAA6B,EAAE,MAAM,CAAC,CAAC;gBACvE,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,MAAM,CAAC,CAAC;gBAC3C,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;oBAChB,uEAAuE;oBACvE,qEAAqE;oBACrE,oCAAoC;oBACpC,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,yBAAyB,MAAM,CAAC,YAAY,EAAE,EAAE,OAAO,CAAC,CAAC;oBACvE,OAAO;gBACR,CAAC;gBAED,IAAI,CAAC,OAAO,EAAE,CAAC;oBACd,MAAM,IAAI,GAAG,gBAAgB,CAAC,MAAM,CAAC,KAAK,CAAC;wBAC1C,CAAC,CAAC,gHAAgH;wBAClH,CAAC,CAAC,EAAE,CAAC;oBACN,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,YAAY,GAAG,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;oBACvD,OAAO;gBACR,CAAC;gBAED,MAAM,MAAM,GAAG,oBAAoB,CAAC,MAAM,EAAE,MAAM,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC;gBACzE,IAAI,CAAC,MAAM,CAAC,EAAE,EAAE,CAAC;oBAChB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;oBACvC,OAAO;gBACR,CAAC;gBACD,GAAG,CAAC,EAAE,CAAC,MAAM,CACZ,GAAG,MAAM,CAAC,OAAO,0EAAwE;oBACxF,0EAA0E,EAC3E,MAAM,CACN,CAAC;gBACF,OAAO;YACR,CAAC;YAED,iLAAuE;YACvE,IAAI,OAAO,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAClC,MAAM,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAC;gBACnD,IAAI,CAAC,IAAI,EAAE,CAAC;oBACX,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,8BAA8B,EAAE,SAAS,CAAC,CAAC;oBACzD,OAAO;gBACR,CAAC;gBACD,MAAM,OAAO,GAAG,eAAe,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;gBAC3C,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,CAAC;oBACtB,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,OAAO,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;oBACvC,OAAO;gBACR,CAAC;gBACD,GAAG,CAAC,EAAE,CAAC,MAAM,CAAC,GAAG,OAAO,CAAC,OAAO,eAAa,EAAE,MAAM,CAAC,CAAC;gBACvD,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC;gBACnB,OAAO;YACR,CAAC;YAED,GAAG,CAAC,EAAE,CAAC,MAAM,CACZ,+GAA+G;gBAC9G,2CAA2C;gBAC3C,yEAAyE,EAC1E,SAAS,CACT,CAAC;QAAA,CACF;KACD,CAAC,CAAC;AAAA,CACH","sourcesContent":["/**\n * /plugin — marketplace add/list + plugin install/list/remove (the human path).\n *\n * `/plugin` installs plugins from marketplaces (a git repo or local dir with a\n * native `.agents-plugin/marketplace.json`, Claude `.claude-plugin/marketplace.json`,\n * or Copilot-style `.github/marketplace.json` index). Installed plugins are placed\n * at the chosen scope — `~/.agents/plugins/<name>` for `user`, or\n * `<cwd>/.agents/plugins/<name>` for `project` — and loaded by the plugin loader\n * after a reload. `install` asks which, since a human is right here to answer;\n * the autonomous tool reads the `pluginInstallScope` setting instead.\n *\n * Adding a marketplace is the human trust boundary and stays here; the shared\n * mechanics (discovery, install, remove, the bundled default marketplace) live in\n * `core/extensions/plugins/install.ts` so this command and the model-facing\n * lifecycle tools never drift.\n *\n * /plugin marketplace add <git-url|path>\n * /plugin marketplace list\n * /plugin marketplace refresh re-fetch every cached index now\n * /plugin list list available plugins across marketplaces\n * /plugin install <name> [--scope user|project]\n * /plugin remove <name>\n * /plugin trust [list] allow repo-committed plugins to run code here\n * /plugin untrust\n * /plugin publish <name> [--to <marketplace-dir>]\n *\n * `publish` is the human end of the publish lane (§3.2). The model can package a\n * plugin — gates, README, index entry — but never publish it, because an agent\n * that can push executable code into a marketplace other agents install from\n * unattended is a supply-chain compromise primitive. So the last step is a\n * command a person types, and even then it stops at the machine boundary: with\n * `--to` it copies the plugin into a local marketplace checkout and updates that\n * index, leaving an uncommitted diff to review. Committing and opening the PR\n * stay manual.\n */\n\nimport { existsSync, mkdirSync, rmSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { getAgentDir } from \"../../config.js\";\nimport { CATEGORY_GLYPH, SEGMENT_SEP } from \"../../core/brand.js\";\nimport { getPlugin } from \"../../core/extensions/plugins/authoring.js\";\nimport {\n\tfindAvailablePlugin,\n\tinstallAvailablePlugin,\n\tlistAvailablePlugins,\n\tlistInstalledPlugins,\n\treadMarketplaceRecords,\n\trefreshMarketplaces,\n\tuninstallPlugin,\n} from \"../../core/extensions/plugins/install.js\";\nimport { availablePluginGroups } from \"../../core/extensions/plugins/listing.js\";\nimport {\n\tmarketplaceCacheDir,\n\tmarketplaceCacheRoot,\n\tmarketplaceStorePath,\n\ttype PluginInstallScope,\n} from \"../../core/extensions/plugins/locations.js\";\nimport {\n\tparseMarketplaceDir,\n\treadMarketplaceStore,\n\tresolvePluginSource,\n\twriteMarketplaceStore,\n} from \"../../core/extensions/plugins/marketplace.js\";\nimport { entryNeedsSource, packagePlugin, stageIntoMarketplace } from \"../../core/extensions/plugins/packaging.js\";\nimport {\n\tisWorkspaceTrusted,\n\tlistTrustedWorkspaces,\n\ttrustWorkspace,\n\tuntrustWorkspace,\n} from \"../../core/extensions/plugins/trust.js\";\nimport type { ExtensionAPI, ExtensionCommandContext } from \"../../core/extensions/types.js\";\nimport { type ListStyle, plural, renderList } from \"../../core/format-list.js\";\nimport { SettingsManager } from \"../../core/settings-manager.js\";\n\nfunction isGitSource(loc: string): boolean {\n\treturn /^https?:\\/\\//.test(loc) || loc.startsWith(\"git@\") || loc.endsWith(\".git\");\n}\n\nfunction isInstallScope(value: string): value is PluginInstallScope {\n\treturn value === \"user\" || value === \"project\";\n}\n\n/** Labels carry the consequence, since that is the whole content of the choice. */\nconst SCOPE_CHOICES: ReadonlyArray<{ scope: PluginInstallScope; label: string }> = [\n\t{ scope: \"user\", label: \"User — ~/.agents/plugins, available in every project (recommended)\" },\n\t{ scope: \"project\", label: \"Project — <repo>/.agents/plugins, committed and shared with collaborators\" },\n];\n\n/**\n * Ask where the plugin should land. Returns undefined when the user dismisses\n * the selector, which cancels the install rather than guessing.\n *\n * Headless (no UI to ask through) resolves to `fallback` — the standing setting\n * — so a scripted or `--print` run still installs somewhere predictable.\n */\nasync function promptForScope(\n\tctx: ExtensionCommandContext,\n\tfallback: PluginInstallScope,\n): Promise<PluginInstallScope | undefined> {\n\tif (!ctx.hasUI) return fallback;\n\tconst labels = SCOPE_CHOICES.map((c) => c.label);\n\tconst picked = await ctx.ui.select(\"Install scope\", labels);\n\tif (picked === undefined) return undefined;\n\treturn SCOPE_CHOICES.find((c) => c.label === picked)?.scope ?? fallback;\n}\n\n/**\n * Chat styling for a listing.\n *\n * The name stays in the terminal's default foreground and everything else steps\n * down from it, so the column you scan is the brightest thing on the row. Note\n * this only reaches the screen because `showStatus` passes pre-styled messages\n * through instead of wrapping them in a blanket dim.\n */\nfunction listStyle(theme: ExtensionCommandContext[\"ui\"][\"theme\"]): ListStyle {\n\treturn {\n\t\tmarker: (text) => theme.fg(\"success\", text),\n\t\tfacts: (text) => theme.fg(\"muted\", text),\n\t\tdetail: (text) => theme.fg(\"dim\", text),\n\t\ttrailer: (text) => theme.fg(\"dim\", text),\n\t\tgroupTitle: (text) => theme.fg(\"mdLink\", text),\n\t};\n}\n\n/** `⬡ 4 plugins …` — the counted header every listing opens with. */\nfunction listHeader(\n\ttheme: ExtensionCommandContext[\"ui\"][\"theme\"],\n\tglyph: string,\n\tsummary: string,\n\thint?: string,\n): string {\n\tconst head = `${theme.fg(\"accent\", glyph)} ${theme.fg(\"muted\", summary)}`;\n\treturn hint ? `${head}\\n${theme.fg(\"dim\", ` ${hint}`)}` : head;\n}\n\nexport function setupMarketplace(pi: ExtensionAPI): void {\n\tpi.registerCommand(\"plugin\", {\n\t\tdescription:\n\t\t\t\"Manage plugin marketplaces. /plugin marketplace add <git-url|path> | /plugin marketplace list | /plugin marketplace refresh | /plugin list | /plugin install <name> [--scope user|project] | /plugin remove <name> | /plugin trust [list] | /plugin untrust | /plugin publish <name> [--to <dir>]\",\n\t\tgetArgumentCompletions: (prefix: string) =>\n\t\t\t[\"marketplace\", \"list\", \"install\", \"remove\", \"refresh\", \"publish\", \"trust\", \"untrust\"]\n\t\t\t\t.filter((s) => s.startsWith(prefix))\n\t\t\t\t.map((s) => ({ value: s, label: s })),\n\t\thandler: async (args: string, ctx: ExtensionCommandContext): Promise<void> => {\n\t\t\tconst trimmed = args.trim();\n\t\t\tconst cwd = ctx.cwd;\n\n\t\t\t// ── marketplace add / list ──────────────────────────────────────────\n\t\t\tif (trimmed.startsWith(\"marketplace\")) {\n\t\t\t\tconst sub = trimmed.slice(\"marketplace\".length).trim();\n\n\t\t\t\tif (sub === \"list\" || sub === \"\") {\n\t\t\t\t\tconst records = readMarketplaceRecords(cwd);\n\t\t\t\t\tif (records.length === 0) {\n\t\t\t\t\t\tctx.ui.notify(\"No marketplaces. Add one with /plugin marketplace add <git-url|path>.\", \"info\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t\tconst theme = ctx.ui.theme;\n\t\t\t\t\tconst rows = records.map((r) => {\n\t\t\t\t\t\tconst market = parseMarketplaceDir(r.dir);\n\t\t\t\t\t\treturn {\n\t\t\t\t\t\t\tname: market?.name ?? r.location,\n\t\t\t\t\t\t\tfacts: [plural(market?.plugins.length ?? 0, \"plugin\"), (market?.supportPlatform ?? []).join(\", \")],\n\t\t\t\t\t\t\t// The location is the longest and least-scanned token on the row;\n\t\t\t\t\t\t\t// on its own line it stops forcing the wrap.\n\t\t\t\t\t\t\ttrailer: r.location,\n\t\t\t\t\t\t};\n\t\t\t\t\t});\n\t\t\t\t\tconst body = renderList([{ rows }], {\n\t\t\t\t\t\tcolumns: ctx.ui.columns,\n\t\t\t\t\t\tindent: 2,\n\t\t\t\t\t\tstyle: listStyle(theme),\n\t\t\t\t\t});\n\t\t\t\t\tconst header = listHeader(theme, CATEGORY_GLYPH.marketplaces, plural(records.length, \"marketplace\"));\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t`${header}\\n${body}\\n\\n${theme.fg(\"dim\", \" /plugin list to see what they offer\")}`,\n\t\t\t\t\t\t\"info\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (sub.startsWith(\"add\")) {\n\t\t\t\t\tconst loc = sub.slice(\"add\".length).trim();\n\t\t\t\t\tif (!loc) {\n\t\t\t\t\t\tctx.ui.notify(\"Usage: /plugin marketplace add <git-url|path>\", \"warning\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet dir: string;\n\t\t\t\t\tif (isGitSource(loc)) {\n\t\t\t\t\t\tdir = marketplaceCacheDir(loc);\n\t\t\t\t\t\trmSync(dir, { recursive: true, force: true });\n\t\t\t\t\t\tmkdirSync(marketplaceCacheRoot(), { recursive: true });\n\t\t\t\t\t\tconst res = await pi.exec(\"git\", [\"clone\", \"--depth\", \"1\", loc, dir]);\n\t\t\t\t\t\tif (res.code !== 0) {\n\t\t\t\t\t\t\tctx.ui.notify(`Clone failed: ${res.stderr || res.stdout}`, \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdir = resolvePluginSource(loc, cwd).kind === \"local\" ? join(cwd, loc) : loc;\n\t\t\t\t\t\tif (!existsSync(dir)) {\n\t\t\t\t\t\t\tctx.ui.notify(`Path not found: ${dir}`, \"error\");\n\t\t\t\t\t\t\treturn;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t\tconst market = parseMarketplaceDir(dir);\n\t\t\t\t\tif (!market) {\n\t\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\t\t\"No marketplace manifest found (.agents-plugin/, .claude-plugin/, or .github/marketplace.json).\",\n\t\t\t\t\t\t\t\"error\",\n\t\t\t\t\t\t);\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\n\t\t\t\t\tconst records = readMarketplaceStore(marketplaceStorePath()).filter((r) => r.location !== loc);\n\t\t\t\t\trecords.push({ location: loc, dir });\n\t\t\t\t\twriteMarketplaceStore(marketplaceStorePath(), records);\n\t\t\t\t\tctx.ui.notify(`Added marketplace \"${market.name}\" (${market.plugins.length} plugin(s)).`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (sub === \"refresh\") {\n\t\t\t\t\tctx.ui.notify(\"Refreshing marketplace indices…\", \"info\");\n\t\t\t\t\tconst { refreshed, errors } = await refreshMarketplaces(cwd);\n\t\t\t\t\tconst lines: string[] = [];\n\t\t\t\t\tif (refreshed.length > 0) lines.push(`Refreshed: ${refreshed.join(\", \")}`);\n\t\t\t\t\tif (errors.length > 0) lines.push(`Could not refresh: ${errors.join(\"; \")}`);\n\t\t\t\t\tctx.ui.notify(lines.join(\"\\n\") || \"Nothing to refresh.\", errors.length > 0 ? \"warning\" : \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\"Usage: /plugin marketplace add <git-url|path> | /plugin marketplace list | /plugin marketplace refresh\",\n\t\t\t\t\t\"warning\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── list available plugins ──────────────────────────────────────────\n\t\t\tif (trimmed === \"list\" || trimmed === \"\") {\n\t\t\t\tconst available = listAvailablePlugins(cwd);\n\t\t\t\tif (available.length === 0) {\n\t\t\t\t\tctx.ui.notify(\"No plugins available. Add a marketplace first.\", \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst theme = ctx.ui.theme;\n\t\t\t\t// Installed state is the fact the old listing could not answer: without\n\t\t\t\t// it, /plugin install on something already present silently re-clones\n\t\t\t\t// over it, discarding any local edits to the plugin directory.\n\t\t\t\tconst installed = new Set(listInstalledPlugins(cwd).map((p) => p.id));\n\t\t\t\tconst groups = availablePluginGroups(available, { installed });\n\t\t\t\tconst body = renderList(groups, {\n\t\t\t\t\tcolumns: ctx.ui.columns,\n\t\t\t\t\tindent: 2,\n\t\t\t\t\tstyle: listStyle(theme),\n\t\t\t\t});\n\t\t\t\tconst installedCount = available.filter((p) => installed.has(p.name)).length;\n\t\t\t\tconst summary =\n\t\t\t\t\t`${plural(available.length, \"plugin\")} across ${plural(groups.length, \"marketplace\")}` +\n\t\t\t\t\t(installedCount > 0 ? ` ${SEGMENT_SEP} ${installedCount} installed` : \"\");\n\t\t\t\tconst header = listHeader(\n\t\t\t\t\ttheme,\n\t\t\t\t\tCATEGORY_GLYPH.plugins,\n\t\t\t\t\tsummary,\n\t\t\t\t\tinstalledCount > 0 ? \"✓ already installed\" : undefined,\n\t\t\t\t);\n\t\t\t\tctx.ui.notify(`${header}\\n${body}\\n\\n${theme.fg(\"dim\", \" /plugin install <name> to add one\")}`, \"info\");\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── install <name> [--scope user|project] ───────────────────────────\n\t\t\tif (trimmed.startsWith(\"install\")) {\n\t\t\t\tconst rest = trimmed.slice(\"install\".length).trim();\n\t\t\t\tconst scopeMatch = /(?:^|\\s)--scope[= ]\\s*(\\S+)/.exec(rest);\n\t\t\t\tconst name = rest.replace(/(?:^|\\s)--scope[= ]\\s*\\S+/, \"\").trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin install <name> [--scope user|project]\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (scopeMatch && !isInstallScope(scopeMatch[1])) {\n\t\t\t\t\tctx.ui.notify(`Unknown scope \"${scopeMatch[1]}\". Use --scope user or --scope project.`, \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (!findAvailablePlugin(cwd, name)) {\n\t\t\t\t\tctx.ui.notify(`Plugin \"${name}\" not found in any marketplace.`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// An explicit --scope wins; otherwise ask, because this is the human\n\t\t\t\t// path and the destination is a real choice (portable vs. committed to\n\t\t\t\t// the repo). With no UI to ask through there is nobody to ask, so it\n\t\t\t\t// falls back to the same setting the autonomous path uses.\n\t\t\t\tconst scope = scopeMatch\n\t\t\t\t\t? (scopeMatch[1] as PluginInstallScope)\n\t\t\t\t\t: await promptForScope(ctx, SettingsManager.create(cwd, getAgentDir()).getPluginInstallScope());\n\t\t\t\tif (!scope) {\n\t\t\t\t\tctx.ui.notify(\"Install cancelled.\", \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst outcome = await installAvailablePlugin(cwd, name, getAgentDir(), { scope });\n\t\t\t\tif (!outcome.installed) {\n\t\t\t\t\tctx.ui.notify(outcome.message, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\t// A person typing this command in this directory is the human act\n\t\t\t\t// workspace trust asks for, so an explicit project-scope install grants\n\t\t\t\t// it — otherwise the plugin you just chose to install here would load\n\t\t\t\t// with its hooks withheld, which is nobody's idea of what happened.\n\t\t\t\t// The autonomous path deliberately does not do this.\n\t\t\t\tlet trustNote = \"\";\n\t\t\t\tif (scope === \"project\" && !isWorkspaceTrusted(cwd, getAgentDir())) {\n\t\t\t\t\ttrustWorkspace(cwd, getAgentDir());\n\t\t\t\t\ttrustNote = ` Trusted this workspace, so its plugins may run hooks and MCP servers here (\\`/plugin untrust\\` reverses it).`;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(`${outcome.message}${trustNote} Reloading…`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── trust / untrust ─────────────────────────────────────────────────\n\t\t\tif (trimmed === \"trust\" || trimmed.startsWith(\"trust \")) {\n\t\t\t\tconst sub = trimmed.slice(\"trust\".length).trim();\n\t\t\t\tif (sub === \"list\") {\n\t\t\t\t\tconst workspaces = listTrustedWorkspaces(getAgentDir());\n\t\t\t\t\tctx.ui.notify(\n\t\t\t\t\t\tworkspaces.length\n\t\t\t\t\t\t\t? `Trusted workspaces:\\n${workspaces.map((w) => `${w.path} (since ${w.at.slice(0, 10)})`).join(\"\\n\")}`\n\t\t\t\t\t\t\t: \"No trusted workspaces. Plugins in a working tree load skills and commands, but not hooks or MCP servers.\",\n\t\t\t\t\t\t\"info\",\n\t\t\t\t\t);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (sub) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin trust | /plugin trust list | /plugin untrust\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tif (isWorkspaceTrusted(cwd, getAgentDir())) {\n\t\t\t\t\tctx.ui.notify(`Already trusted: ${cwd}`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\ttrustWorkspace(cwd, getAgentDir());\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`Trusted ${cwd}. Plugins committed to this repository may now run hooks and MCP servers here, ` +\n\t\t\t\t\t\t\"including any added by a later pull. Reverse it with /plugin untrust. Reloading…\",\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tif (trimmed === \"untrust\") {\n\t\t\t\tif (!untrustWorkspace(cwd, getAgentDir())) {\n\t\t\t\t\tctx.ui.notify(`Not trusted: ${cwd}`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(`Revoked trust for ${cwd}. Reloading…`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── publish <name> [--to <marketplace-dir>] ─────────────────────────\n\t\t\tif (trimmed.startsWith(\"publish\")) {\n\t\t\t\tconst rest = trimmed.slice(\"publish\".length).trim();\n\t\t\t\tconst toMatch = /(?:^|\\s)--to\\s+(\\S+)/.exec(rest);\n\t\t\t\tconst name = rest.replace(/(?:^|\\s)--to\\s+\\S+/, \"\").trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin publish <name> [--to <marketplace-dir>]\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst plugin = getPlugin(cwd, name);\n\t\t\t\tif (!plugin) {\n\t\t\t\t\tctx.ui.notify(`No plugin named \"${name}\" is installed or authored here.`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tctx.ui.notify(`Packaging \"${name}\" — running publish checks…`, \"info\");\n\t\t\t\tconst result = await packagePlugin(plugin);\n\t\t\t\tif (!result.ok) {\n\t\t\t\t\t// A red gate blocks staging too: the point of the strict run is that a\n\t\t\t\t\t// plugin other people install has passed it, and staging is the step\n\t\t\t\t\t// that puts it on the path to them.\n\t\t\t\t\tctx.ui.notify(`Not publishable yet.\\n${result.instructions}`, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tif (!toMatch) {\n\t\t\t\t\tconst hint = entryNeedsSource(result.entry)\n\t\t\t\t\t\t? \"\\nRe-run with --to <marketplace-dir> to vendor it into a local marketplace checkout (which fills `source` in).\"\n\t\t\t\t\t\t: \"\";\n\t\t\t\t\tctx.ui.notify(`${result.instructions}${hint}`, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tconst staged = stageIntoMarketplace(plugin, result.platform, toMatch[1]);\n\t\t\t\tif (!staged.ok) {\n\t\t\t\t\tctx.ui.notify(staged.message, \"error\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(\n\t\t\t\t\t`${staged.message}\\n\\nReview the diff, then commit and open the pull request yourself — ` +\n\t\t\t\t\t\t\"hoocode deliberately stops short of pushing a plugin into a marketplace.\",\n\t\t\t\t\t\"info\",\n\t\t\t\t);\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\t// ── remove <name> ───────────────────────────────────────────────────\n\t\t\tif (trimmed.startsWith(\"remove\")) {\n\t\t\t\tconst name = trimmed.slice(\"remove\".length).trim();\n\t\t\t\tif (!name) {\n\t\t\t\t\tctx.ui.notify(\"Usage: /plugin remove <name>\", \"warning\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst outcome = uninstallPlugin(cwd, name);\n\t\t\t\tif (!outcome.removed) {\n\t\t\t\t\tctx.ui.notify(outcome.message, \"info\");\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tctx.ui.notify(`${outcome.message} Reloading…`, \"info\");\n\t\t\t\tawait ctx.reload();\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tctx.ui.notify(\n\t\t\t\t\"Usage: /plugin marketplace add|list|refresh | /plugin list | /plugin install <name> [--scope user|project] | \" +\n\t\t\t\t\t\"/plugin trust [list] | /plugin untrust | \" +\n\t\t\t\t\t\"/plugin remove <name> | /plugin publish <name> [--to <marketplace-dir>]\",\n\t\t\t\t\"warning\",\n\t\t\t);\n\t\t},\n\t});\n}\n"]}
|
|
@@ -38,6 +38,7 @@ export interface SettingsConfig {
|
|
|
38
38
|
autoResizeImages: boolean;
|
|
39
39
|
blockImages: boolean;
|
|
40
40
|
enableSkillCommands: boolean;
|
|
41
|
+
pluginInstallScope: "user" | "project";
|
|
41
42
|
steeringMode: "all" | "one-at-a-time";
|
|
42
43
|
followUpMode: "all" | "one-at-a-time";
|
|
43
44
|
transport: Transport;
|
|
@@ -75,6 +76,7 @@ export interface SettingsCallbacks {
|
|
|
75
76
|
onAutoResizeImagesChange: (enabled: boolean) => void;
|
|
76
77
|
onBlockImagesChange: (blocked: boolean) => void;
|
|
77
78
|
onEnableSkillCommandsChange: (enabled: boolean) => void;
|
|
79
|
+
onPluginInstallScopeChange: (scope: "user" | "project") => void;
|
|
78
80
|
onSteeringModeChange: (mode: "all" | "one-at-a-time") => void;
|
|
79
81
|
onFollowUpModeChange: (mode: "all" | "one-at-a-time") => void;
|
|
80
82
|
onTransportChange: (transport: Transport) => void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"settings-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/settings-selector.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ThinkingLevel } from \"@kolisachint/hoocode-agent-core\";\nimport type { Transport } from \"@kolisachint/hoocode-ai\";\nimport {\n\tContainer,\n\tgetCapabilities,\n\tInput,\n\ttype SelectItem,\n\tSelectList,\n\ttype SelectListLayoutOptions,\n\ttype SettingItem,\n\tSettingsList,\n\tSpacer,\n\tText,\n} from \"@kolisachint/hoocode-tui\";\nimport type { WarningSettings } from \"../../../core/settings-manager.js\";\nimport { getSelectListTheme, getSettingsListTheme, getThemeDescription, theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { keyDisplayText } from \"./keybinding-hints.js\";\n\nconst SETTINGS_SUBMENU_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {\n\tminPrimaryColumnWidth: 12,\n\tmaxPrimaryColumnWidth: 32,\n};\n\nconst THINKING_DESCRIPTIONS: Record<ThinkingLevel, string> = {\n\toff: \"No reasoning\",\n\tminimal: \"Very brief reasoning (~1k tokens)\",\n\tlow: \"Light reasoning (~2k tokens)\",\n\tmedium: \"Moderate reasoning (~8k tokens)\",\n\thigh: \"Deep reasoning (~16k tokens)\",\n\txhigh: \"Maximum reasoning (~32k tokens)\",\n};\n\ninterface ToolToggleInfo {\n\t/** Tool name (e.g. \"read\", \"bash\"). */\n\tname: string;\n\t/** Whether the tool is currently enabled (not in the persisted disabled set). */\n\tenabled: boolean;\n}\n\ninterface FlagInfo {\n\t/** Flag name (without the leading --). */\n\tname: string;\n\tdescription?: string;\n\ttype: \"boolean\" | \"string\";\n\t/** Current effective value. */\n\tvalue: boolean | string;\n}\n\ninterface ToolGroupInfo {\n\t/** Group identifier (e.g. \"web\", \"embsearch\"). */\n\tid: string;\n\tlabel: string;\n\tdescription: string;\n\t/** Whether the group is currently enabled (its tools are available). */\n\tenabled: boolean;\n}\n\nexport interface SettingsConfig {\n\tautoCompact: boolean;\n\ttools: ToolToggleInfo[];\n\ttoolGroups: ToolGroupInfo[];\n\tflags: FlagInfo[];\n\ttoolOutputDisplay: \"collapsed\" | \"peek\" | \"standard\";\n\ttoolOutputMaxBytes: number;\n\ttoolOutputMaxLines: number;\n\tcontextGc: boolean;\n\tshowImages: boolean;\n\timageWidthCells: number;\n\tautoResizeImages: boolean;\n\tblockImages: boolean;\n\tenableSkillCommands: boolean;\n\tsteeringMode: \"all\" | \"one-at-a-time\";\n\tfollowUpMode: \"all\" | \"one-at-a-time\";\n\ttransport: Transport;\n\tthinkingLevel: ThinkingLevel;\n\tavailableThinkingLevels: ThinkingLevel[];\n\tcurrentTheme: string;\n\tavailableThemes: string[];\n\thideThinkingBlock: boolean;\n\tcollapseChangelog: boolean;\n\tenableInstallTelemetry: boolean;\n\tdoubleEscapeAction: \"fork\" | \"tree\" | \"none\";\n\ttreeFilterMode: \"default\" | \"no-tools\" | \"user-only\" | \"labeled-only\" | \"all\";\n\tshowHardwareCursor: boolean;\n\teditorBorder: \"rule\" | \"box\";\n\teditorPaddingX: number;\n\tautocompleteMaxVisible: number;\n\tquietStartup: boolean;\n\tclearOnShrink: boolean;\n\tshowTerminalProgress: boolean;\n\twarnings: WarningSettings;\n\tvoiceSilenceMs: number;\n\twebtoolsTimeoutSecs: number;\n}\n\nexport interface SettingsCallbacks {\n\tonAutoCompactChange: (enabled: boolean) => void;\n\tonToolEnabledChange: (name: string, enabled: boolean) => void;\n\tonToolGroupChange: (id: string, enabled: boolean) => void;\n\tonToolOutputDisplayChange: (level: \"collapsed\" | \"peek\" | \"standard\") => void;\n\tonToolOutputMaxBytesChange: (bytes: number) => void;\n\tonToolOutputMaxLinesChange: (lines: number) => void;\n\tonContextGcChange: (enabled: boolean) => void;\n\tonFlagChange: (name: string, value: boolean | string) => void;\n\tonShowImagesChange: (enabled: boolean) => void;\n\tonImageWidthCellsChange: (width: number) => void;\n\tonAutoResizeImagesChange: (enabled: boolean) => void;\n\tonBlockImagesChange: (blocked: boolean) => void;\n\tonEnableSkillCommandsChange: (enabled: boolean) => void;\n\tonSteeringModeChange: (mode: \"all\" | \"one-at-a-time\") => void;\n\tonFollowUpModeChange: (mode: \"all\" | \"one-at-a-time\") => void;\n\tonTransportChange: (transport: Transport) => void;\n\tonThinkingLevelChange: (level: ThinkingLevel) => void;\n\tonThemeChange: (theme: string) => void;\n\tonThemePreview?: (theme: string) => void;\n\tonHideThinkingBlockChange: (hidden: boolean) => void;\n\tonCollapseChangelogChange: (collapsed: boolean) => void;\n\tonEnableInstallTelemetryChange: (enabled: boolean) => void;\n\tonDoubleEscapeActionChange: (action: \"fork\" | \"tree\" | \"none\") => void;\n\tonTreeFilterModeChange: (mode: \"default\" | \"no-tools\" | \"user-only\" | \"labeled-only\" | \"all\") => void;\n\tonShowHardwareCursorChange: (enabled: boolean) => void;\n\tonEditorBorderChange: (border: \"rule\" | \"box\") => void;\n\tonEditorPaddingXChange: (padding: number) => void;\n\tonAutocompleteMaxVisibleChange: (maxVisible: number) => void;\n\tonQuietStartupChange: (enabled: boolean) => void;\n\tonClearOnShrinkChange: (enabled: boolean) => void;\n\tonShowTerminalProgressChange: (enabled: boolean) => void;\n\tonWarningsChange: (warnings: WarningSettings) => void;\n\tonVoiceSilenceMsChange: (ms: number) => void;\n\tonWebtoolsTimeoutSecsChange: (secs: number) => void;\n\tonCancel: () => void;\n}\n\n/**\n * A submenu component for selecting from a list of options.\n */\nclass WarningSettingsSubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\tprivate state: WarningSettings;\n\n\tconstructor(warnings: WarningSettings, onChange: (warnings: WarningSettings) => void, onCancel: () => void) {\n\t\tsuper();\n\n\t\tthis.state = { ...warnings };\n\n\t\tconst items: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"anthropic-extra-usage\",\n\t\t\t\tlabel: \"Anthropic extra usage\",\n\t\t\t\tdescription: \"Warn when Anthropic subscription auth may use paid extra usage\",\n\t\t\t\tcurrentValue: (this.state.anthropicExtraUsage ?? true) ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t];\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 10),\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tswitch (id) {\n\t\t\t\t\tcase \"anthropic-extra-usage\":\n\t\t\t\t\t\tthis.state = { ...this.state, anthropicExtraUsage: newValue === \"true\" };\n\t\t\t\t\t\tonChange({ ...this.state });\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t},\n\t\t\tonCancel,\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\n/**\n * Submenu for tool availability. The first rows are group switches (web,\n * semantic search) that decide whether a group's tools\n * exist at all — this is the same master switch that governs, e.g., the\n * webfetch/websearch tools. Below them are per-tool on/off toggles for the\n * tools that are currently available.\n *\n * Group switches change tool availability and apply on the next session;\n * per-tool toggles apply live and persist. A minimum core (read/bash/edit/\n * write) is guarded so the agent can never be left with no way to act.\n */\nclass ToolsSubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\tprivate enabled: Map<string, boolean>;\n\tprivate static readonly CORE = new Set([\"read\", \"bash\", \"edit\", \"write\"]);\n\tprivate static readonly GROUP_PREFIX = \"group:\";\n\n\tconstructor(\n\t\ttools: ToolToggleInfo[],\n\t\tgroups: ToolGroupInfo[],\n\t\tonChange: (name: string, enabled: boolean) => void,\n\t\tonGroupChange: (id: string, enabled: boolean) => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tthis.enabled = new Map(tools.map((t) => [t.name, t.enabled]));\n\n\t\tconst groupItems: SettingItem[] = groups.map((group) => ({\n\t\t\tid: `${ToolsSubmenu.GROUP_PREFIX}${group.id}`,\n\t\t\tlabel: `[group] ${group.label}`,\n\t\t\tdescription: `${group.description} Governs whether these tools exist; applies on the next session.`,\n\t\t\tcurrentValue: group.enabled ? \"on\" : \"off\",\n\t\t\tvalues: [\"on\", \"off\"],\n\t\t}));\n\n\t\tconst toolItems: SettingItem[] = tools.map((tool) => ({\n\t\t\tid: tool.name,\n\t\t\tlabel: tool.name,\n\t\t\tdescription: ToolsSubmenu.CORE.has(tool.name)\n\t\t\t\t? \"Core tool. Disabling leaves the agent unable to perform this action in every session.\"\n\t\t\t\t: \"Disable to remove this tool from the agent this session and every future session.\",\n\t\t\tcurrentValue: tool.enabled ? \"on\" : \"off\",\n\t\t\tvalues: [\"on\", \"off\"],\n\t\t}));\n\n\t\tconst items = [...groupItems, ...toolItems];\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 12),\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tconst wantEnabled = newValue === \"on\";\n\t\t\t\tif (id.startsWith(ToolsSubmenu.GROUP_PREFIX)) {\n\t\t\t\t\tonGroupChange(id.slice(ToolsSubmenu.GROUP_PREFIX.length), wantEnabled);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Guard: never let the last core tool be turned off.\n\t\t\t\tif (!wantEnabled && ToolsSubmenu.CORE.has(id)) {\n\t\t\t\t\tconst remainingCore = [...ToolsSubmenu.CORE].filter((n) => n !== id && this.enabled.get(n));\n\t\t\t\t\tif (remainingCore.length === 0) {\n\t\t\t\t\t\tthis.settingsList.updateValue(id, \"on\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.enabled.set(id, wantEnabled);\n\t\t\t\tonChange(id, wantEnabled);\n\t\t\t},\n\t\t\tonCancel,\n\t\t\t{ enableSearch: true },\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\n/** Byte-cap presets shown as human labels; mapped back to raw byte counts. */\nconst TOOL_OUTPUT_BYTE_PRESETS: ReadonlyArray<[label: string, bytes: number]> = [\n\t[\"8 KB\", 8 * 1024],\n\t[\"16 KB\", 16 * 1024],\n\t[\"32 KB\", 32 * 1024],\n\t[\"64 KB\", 64 * 1024],\n\t[\"128 KB\", 128 * 1024],\n];\n\nfunction bytesToLabel(bytes: number): string {\n\tconst match = TOOL_OUTPUT_BYTE_PRESETS.find(([, b]) => b === bytes);\n\treturn match ? match[0] : `${Math.round(bytes / 1024)} KB`;\n}\n\ninterface ToolSettingsConfig {\n\ttoolOutputMaxBytes: number;\n\ttoolOutputMaxLines: number;\n\tcontextGc: boolean;\n}\n\ninterface ToolSettingsCallbacks {\n\tonToolOutputMaxBytesChange: (bytes: number) => void;\n\tonToolOutputMaxLinesChange: (lines: number) => void;\n\tonContextGcChange: (enabled: boolean) => void;\n}\n\n/**\n * Submenu for per-tool runtime settings. These feed the tool runtime the next\n * time it is built (next session / rebuild), so changes apply to future tool\n * calls rather than retroactively.\n */\nclass ToolSettingsSubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(config: ToolSettingsConfig, callbacks: ToolSettingsCallbacks, onCancel: () => void) {\n\t\tsuper();\n\n\t\tconst items: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"output-max-bytes\",\n\t\t\t\tlabel: \"Output max bytes\",\n\t\t\t\tdescription: \"Byte cap on a single read/bash result before truncation. Applies to future tool calls.\",\n\t\t\t\tcurrentValue: bytesToLabel(config.toolOutputMaxBytes),\n\t\t\t\tvalues: TOOL_OUTPUT_BYTE_PRESETS.map(([label]) => label),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"output-max-lines\",\n\t\t\t\tlabel: \"Output max lines\",\n\t\t\t\tdescription: \"Line cap on a single read/bash result before truncation. Applies to future tool calls.\",\n\t\t\t\tcurrentValue: String(config.toolOutputMaxLines),\n\t\t\t\tvalues: [\"200\", \"400\", \"800\", \"1600\", \"3200\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"context-gc\",\n\t\t\t\tlabel: \"Context GC\",\n\t\t\t\tdescription: \"Stub superseded read results (files later edited/re-read) out of the outgoing context.\",\n\t\t\t\tcurrentValue: config.contextGc ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t];\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 10),\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tswitch (id) {\n\t\t\t\t\tcase \"output-max-bytes\": {\n\t\t\t\t\t\tconst preset = TOOL_OUTPUT_BYTE_PRESETS.find(([label]) => label === newValue);\n\t\t\t\t\t\tif (preset) callbacks.onToolOutputMaxBytesChange(preset[1]);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"output-max-lines\":\n\t\t\t\t\t\tcallbacks.onToolOutputMaxLinesChange(parseInt(newValue, 10));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"context-gc\":\n\t\t\t\t\t\tcallbacks.onContextGcChange(newValue === \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t},\n\t\t\tonCancel,\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\n/** Single-line text editor for a string flag value. */\nclass FlagStringEditSubmenu extends Container {\n\tprivate input: Input;\n\n\tconstructor(flagName: string, currentValue: string, done: (value?: string) => void) {\n\t\tsuper();\n\n\t\tthis.addChild(new Text(theme.bold(theme.fg(\"accent\", `Flag: --${flagName}`)), 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.fg(\"muted\", \"Enter a value · Enter to save · Esc to cancel\"), 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tthis.input = new Input();\n\t\tthis.input.setValue(currentValue);\n\t\tthis.input.onSubmit = (value: string) => done(value);\n\t\tthis.input.onEscape = () => done();\n\t\tthis.addChild(this.input);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.input.handleInput(data);\n\t}\n}\n\n/**\n * Submenu listing extension-registered flags. Boolean flags toggle on/off;\n * string flags open a text editor. Changes persist to settings.json and are\n * applied live best-effort — extensions that read a flag only at load time\n * pick up the new value on the next launch.\n */\nclass FlagsSubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(flags: FlagInfo[], onChange: (name: string, value: boolean | string) => void, onCancel: () => void) {\n\t\tsuper();\n\n\t\tconst items: SettingItem[] = flags.map((flag) => {\n\t\t\tconst baseDescription = flag.description ?? \"Extension-registered flag.\";\n\t\t\tconst description = `${baseDescription} Persists across sessions; some flags need a restart to fully apply.`;\n\t\t\tif (flag.type === \"boolean\") {\n\t\t\t\treturn {\n\t\t\t\t\tid: flag.name,\n\t\t\t\t\tlabel: flag.name,\n\t\t\t\t\tdescription,\n\t\t\t\t\tcurrentValue: flag.value ? \"on\" : \"off\",\n\t\t\t\t\tvalues: [\"on\", \"off\"],\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tid: flag.name,\n\t\t\t\tlabel: flag.name,\n\t\t\t\tdescription,\n\t\t\t\tcurrentValue: String(flag.value ?? \"\"),\n\t\t\t\tsubmenu: (currentValue, done) => new FlagStringEditSubmenu(flag.name, currentValue, done),\n\t\t\t};\n\t\t});\n\n\t\tconst typeByName = new Map(flags.map((f) => [f.name, f.type]));\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 12),\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tif (typeByName.get(id) === \"boolean\") {\n\t\t\t\t\tonChange(id, newValue === \"on\");\n\t\t\t\t} else {\n\t\t\t\t\tonChange(id, newValue);\n\t\t\t\t}\n\t\t\t},\n\t\t\tonCancel,\n\t\t\t{ enableSearch: true },\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\n/**\n * Generic submenu holding a subset of leaf settings under a category label.\n * Shares the parent's change handler so cycle rows behave exactly as they did\n * when flat; nested submenu rows (theme, thinking, warnings) keep their own\n * factories.\n */\nclass CategorySubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(items: SettingItem[], onChange: (id: string, newValue: string) => void, onCancel: () => void) {\n\t\tsuper();\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 10),\n\t\t\tgetSettingsListTheme(),\n\t\t\tonChange,\n\t\t\tonCancel,\n\t\t\t{\n\t\t\t\tenableSearch: true,\n\t\t\t},\n\t\t);\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\nclass SelectSubmenu extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(\n\t\ttitle: string,\n\t\tdescription: string,\n\t\toptions: SelectItem[],\n\t\tcurrentValue: string,\n\t\tonSelect: (value: string) => void,\n\t\tonCancel: () => void,\n\t\tonSelectionChange?: (value: string) => void,\n\t) {\n\t\tsuper();\n\n\t\t// Title\n\t\tthis.addChild(new Text(theme.bold(theme.fg(\"accent\", title)), 0, 0));\n\n\t\t// Description\n\t\tif (description) {\n\t\t\tthis.addChild(new Spacer(1));\n\t\t\tthis.addChild(new Text(theme.fg(\"muted\", description), 0, 0));\n\t\t}\n\n\t\t// Spacer\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Select list\n\t\tthis.selectList = new SelectList(\n\t\t\toptions,\n\t\t\tMath.min(options.length, 10),\n\t\t\tgetSelectListTheme(),\n\t\t\tSETTINGS_SUBMENU_SELECT_LIST_LAYOUT,\n\t\t);\n\n\t\t// Pre-select current value\n\t\tconst currentIndex = options.findIndex((o) => o.value === currentValue);\n\t\tif (currentIndex !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(currentIndex);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value);\n\t\t};\n\n\t\tthis.selectList.onCancel = onCancel;\n\n\t\tif (onSelectionChange) {\n\t\t\tthis.selectList.onSelectionChange = (item) => {\n\t\t\t\tonSelectionChange(item.value);\n\t\t\t};\n\t\t}\n\n\t\tthis.addChild(this.selectList);\n\n\t\t// Hint\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.fg(\"dim\", \" Enter to select · Esc to go back\"), 0, 0));\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.selectList.handleInput(data);\n\t}\n}\n\n/**\n * Main settings selector component.\n */\nexport class SettingsSelectorComponent extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(config: SettingsConfig, callbacks: SettingsCallbacks) {\n\t\tsuper();\n\n\t\tconst supportsImages = getCapabilities().images;\n\t\tconst followUpKey = keyDisplayText(\"app.message.followUp\");\n\t\tlet currentWarnings = { ...config.warnings };\n\n\t\tconst toolsOn = config.tools.filter((t) => t.enabled).length;\n\t\tconst toolsOff = config.tools.length - toolsOn;\n\n\t\tconst items: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"autocompact\",\n\t\t\t\tlabel: \"Auto-compact\",\n\t\t\t\tdescription: \"Automatically compact context when it gets too large\",\n\t\t\t\tcurrentValue: config.autoCompact ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"steering-mode\",\n\t\t\t\tlabel: \"Steering mode\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Enter while streaming queues steering messages. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.\",\n\t\t\t\tcurrentValue: config.steeringMode,\n\t\t\t\tvalues: [\"one-at-a-time\", \"all\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"follow-up-mode\",\n\t\t\t\tlabel: \"Follow-up mode\",\n\t\t\t\tdescription: `${followUpKey} queues follow-up messages until agent stops. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.`,\n\t\t\t\tcurrentValue: config.followUpMode,\n\t\t\t\tvalues: [\"one-at-a-time\", \"all\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"transport\",\n\t\t\t\tlabel: \"Transport\",\n\t\t\t\tdescription: \"Preferred transport for providers that support multiple transports\",\n\t\t\t\tcurrentValue: config.transport,\n\t\t\t\tvalues: [\"sse\", \"websocket\", \"websocket-cached\", \"auto\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"hide-thinking\",\n\t\t\t\tlabel: \"Hide thinking\",\n\t\t\t\tdescription: \"Hide thinking blocks in assistant responses\",\n\t\t\t\tcurrentValue: config.hideThinkingBlock ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"collapse-changelog\",\n\t\t\t\tlabel: \"Collapse changelog\",\n\t\t\t\tdescription: \"Show condensed changelog after updates\",\n\t\t\t\tcurrentValue: config.collapseChangelog ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"quiet-startup\",\n\t\t\t\tlabel: \"Quiet startup\",\n\t\t\t\tdescription: \"Disable verbose printing at startup\",\n\t\t\t\tcurrentValue: config.quietStartup ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"install-telemetry\",\n\t\t\t\tlabel: \"Install telemetry\",\n\t\t\t\tdescription: \"Send an anonymous version/update ping after changelog-detected updates\",\n\t\t\t\tcurrentValue: config.enableInstallTelemetry ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"double-escape-action\",\n\t\t\t\tlabel: \"Double-escape action\",\n\t\t\t\tdescription: \"Action when pressing Escape twice with empty editor\",\n\t\t\t\tcurrentValue: config.doubleEscapeAction,\n\t\t\t\tvalues: [\"tree\", \"fork\", \"none\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"tree-filter-mode\",\n\t\t\t\tlabel: \"Tree filter mode\",\n\t\t\t\tdescription: \"Default filter when opening /tree\",\n\t\t\t\tcurrentValue: config.treeFilterMode,\n\t\t\t\tvalues: [\"default\", \"no-tools\", \"user-only\", \"labeled-only\", \"all\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"warnings\",\n\t\t\t\tlabel: \"Warnings\",\n\t\t\t\tdescription: \"Enable or disable individual warnings\",\n\t\t\t\tcurrentValue: \"configure\",\n\t\t\t\tsubmenu: (_currentValue, done) =>\n\t\t\t\t\tnew WarningSettingsSubmenu(\n\t\t\t\t\t\tcurrentWarnings,\n\t\t\t\t\t\t(warnings) => {\n\t\t\t\t\t\t\tcurrentWarnings = warnings;\n\t\t\t\t\t\t\tcallbacks.onWarningsChange(warnings);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"thinking\",\n\t\t\t\tlabel: \"Thinking level\",\n\t\t\t\tdescription: \"Reasoning depth for thinking-capable models\",\n\t\t\t\tcurrentValue: config.thinkingLevel,\n\t\t\t\tsubmenu: (currentValue, done) =>\n\t\t\t\t\tnew SelectSubmenu(\n\t\t\t\t\t\t\"Thinking Level\",\n\t\t\t\t\t\t\"Select reasoning depth for thinking-capable models\",\n\t\t\t\t\t\tconfig.availableThinkingLevels.map((level) => ({\n\t\t\t\t\t\t\tvalue: level,\n\t\t\t\t\t\t\tlabel: level,\n\t\t\t\t\t\t\tdescription: THINKING_DESCRIPTIONS[level],\n\t\t\t\t\t\t})),\n\t\t\t\t\t\tcurrentValue,\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\tcallbacks.onThinkingLevelChange(value as ThinkingLevel);\n\t\t\t\t\t\t\tdone(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"theme\",\n\t\t\t\tlabel: \"Theme\",\n\t\t\t\tdescription: \"Color theme for the interface\",\n\t\t\t\tcurrentValue: config.currentTheme,\n\t\t\t\tsubmenu: (currentValue, done) =>\n\t\t\t\t\tnew SelectSubmenu(\n\t\t\t\t\t\t\"Theme\",\n\t\t\t\t\t\t\"Select color theme\",\n\t\t\t\t\t\tconfig.availableThemes.map((t) => ({\n\t\t\t\t\t\t\tvalue: t,\n\t\t\t\t\t\t\tlabel: t,\n\t\t\t\t\t\t\tdescription: getThemeDescription(t),\n\t\t\t\t\t\t})),\n\t\t\t\t\t\tcurrentValue,\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\tcallbacks.onThemeChange(value);\n\t\t\t\t\t\t\tdone(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t// Restore original theme on cancel\n\t\t\t\t\t\t\tcallbacks.onThemePreview?.(currentValue);\n\t\t\t\t\t\t\tdone();\n\t\t\t\t\t\t},\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\t// Preview theme on selection change\n\t\t\t\t\t\t\tcallbacks.onThemePreview?.(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t},\n\t\t];\n\n\t\t// Only show image toggle if terminal supports it\n\t\tif (supportsImages) {\n\t\t\t// Insert after autocompact\n\t\t\titems.splice(1, 0, {\n\t\t\t\tid: \"show-images\",\n\t\t\t\tlabel: \"Show images\",\n\t\t\t\tdescription: \"Render images inline in terminal\",\n\t\t\t\tcurrentValue: config.showImages ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t});\n\t\t\titems.splice(2, 0, {\n\t\t\t\tid: \"image-width-cells\",\n\t\t\t\tlabel: \"Image width\",\n\t\t\t\tdescription: \"Preferred inline image width in terminal cells\",\n\t\t\t\tcurrentValue: String(config.imageWidthCells),\n\t\t\t\tvalues: [\"60\", \"80\", \"120\"],\n\t\t\t});\n\t\t}\n\n\t\t// Image auto-resize toggle (always available, affects both attached and read images)\n\t\titems.splice(supportsImages ? 3 : 1, 0, {\n\t\t\tid: \"auto-resize-images\",\n\t\t\tlabel: \"Auto-resize images\",\n\t\t\tdescription: \"Resize large images to 2000x2000 max for better model compatibility\",\n\t\t\tcurrentValue: config.autoResizeImages ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Block images toggle (always available, insert after auto-resize-images)\n\t\tconst autoResizeIndex = items.findIndex((item) => item.id === \"auto-resize-images\");\n\t\titems.splice(autoResizeIndex + 1, 0, {\n\t\t\tid: \"block-images\",\n\t\t\tlabel: \"Block images\",\n\t\t\tdescription: \"Prevent images from being sent to LLM providers\",\n\t\t\tcurrentValue: config.blockImages ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Skill commands toggle (insert after block-images)\n\t\tconst blockImagesIndex = items.findIndex((item) => item.id === \"block-images\");\n\t\titems.splice(blockImagesIndex + 1, 0, {\n\t\t\tid: \"skill-commands\",\n\t\t\tlabel: \"Skill commands\",\n\t\t\tdescription: \"Register skills as /skill:name commands\",\n\t\t\tcurrentValue: config.enableSkillCommands ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Hardware cursor toggle (insert after skill-commands)\n\t\tconst skillCommandsIndex = items.findIndex((item) => item.id === \"skill-commands\");\n\t\titems.splice(skillCommandsIndex + 1, 0, {\n\t\t\tid: \"show-hardware-cursor\",\n\t\t\tlabel: \"Show hardware cursor\",\n\t\t\tdescription: \"Show the terminal cursor while still positioning it for IME support\",\n\t\t\tcurrentValue: config.showHardwareCursor ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Editor border toggle (insert after show-hardware-cursor)\n\t\tconst hardwareCursorIndex = items.findIndex((item) => item.id === \"show-hardware-cursor\");\n\t\titems.splice(hardwareCursorIndex + 1, 0, {\n\t\t\tid: \"editor-border\",\n\t\t\tlabel: \"Editor border\",\n\t\t\tdescription: \"Box draws side borders, rule draws horizontal lines only\",\n\t\t\tcurrentValue: config.editorBorder,\n\t\t\tvalues: [\"box\", \"rule\"],\n\t\t});\n\n\t\t// Editor padding toggle (insert after editor-border)\n\t\tconst editorBorderIndex = items.findIndex((item) => item.id === \"editor-border\");\n\t\titems.splice(editorBorderIndex + 1, 0, {\n\t\t\tid: \"editor-padding\",\n\t\t\tlabel: \"Editor padding\",\n\t\t\tdescription: \"Horizontal padding for input editor (0-3)\",\n\t\t\tcurrentValue: String(config.editorPaddingX),\n\t\t\tvalues: [\"0\", \"1\", \"2\", \"3\"],\n\t\t});\n\n\t\t// Autocomplete max visible toggle (insert after editor-padding)\n\t\tconst editorPaddingIndex = items.findIndex((item) => item.id === \"editor-padding\");\n\t\titems.splice(editorPaddingIndex + 1, 0, {\n\t\t\tid: \"autocomplete-max-visible\",\n\t\t\tlabel: \"Autocomplete max items\",\n\t\t\tdescription: \"Max visible items in autocomplete dropdown (3-20)\",\n\t\t\tcurrentValue: String(config.autocompleteMaxVisible),\n\t\t\tvalues: [\"3\", \"5\", \"7\", \"10\", \"15\", \"20\"],\n\t\t});\n\n\t\t// Clear on shrink toggle (insert after autocomplete-max-visible)\n\t\tconst autocompleteIndex = items.findIndex((item) => item.id === \"autocomplete-max-visible\");\n\t\titems.splice(autocompleteIndex + 1, 0, {\n\t\t\tid: \"clear-on-shrink\",\n\t\t\tlabel: \"Clear on shrink\",\n\t\t\tdescription: \"Clear empty rows when content shrinks (may cause flicker)\",\n\t\t\tcurrentValue: config.clearOnShrink ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Terminal progress toggle (insert after clear-on-shrink)\n\t\tconst clearOnShrinkIndex = items.findIndex((item) => item.id === \"clear-on-shrink\");\n\t\titems.splice(clearOnShrinkIndex + 1, 0, {\n\t\t\tid: \"terminal-progress\",\n\t\t\tlabel: \"Terminal progress\",\n\t\t\tdescription: \"Show OSC 9;4 progress indicators in the terminal tab bar\",\n\t\t\tcurrentValue: config.showTerminalProgress ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Voice silence window (insert after terminal-progress)\n\t\tconst terminalProgressIndex = items.findIndex((item) => item.id === \"terminal-progress\");\n\t\titems.splice(terminalProgressIndex + 1, 0, {\n\t\t\tid: \"voice-silence-ms\",\n\t\t\tlabel: \"Voice silence window\",\n\t\t\tdescription: \"Trailing-silence (ms) before voice capture auto-stops (300-10000). Env: VOICETOOLS_SILENCE_MS.\",\n\t\t\tcurrentValue: String(config.voiceSilenceMs),\n\t\t\tvalues: [\"300\", \"500\", \"800\", \"1200\", \"2000\", \"3000\", \"5000\", \"8000\", \"10000\"],\n\t\t});\n\n\t\t// Webtools request timeout (insert after voice-silence-ms)\n\t\tconst voiceSilenceIndex = items.findIndex((item) => item.id === \"voice-silence-ms\");\n\t\titems.splice(voiceSilenceIndex + 1, 0, {\n\t\t\tid: \"webtools-timeout-secs\",\n\t\t\tlabel: \"Web tools timeout\",\n\t\t\tdescription: \"Per-request timeout (secs) for webfetch/websearch (1-120). Env: HOOCODE_WEBTOOLS_TIMEOUT.\",\n\t\t\tcurrentValue: String(config.webtoolsTimeoutSecs),\n\t\t\tvalues: [\"5\", \"10\", \"15\", \"30\", \"60\", \"120\"],\n\t\t});\n\n\t\t// Keep the tool/flag controls together as one block near the top, inserted\n\t\t// after the image/terminal splices above so they aren't leapfrogged.\n\t\tconst toolFlagGroup: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"tools\",\n\t\t\t\tlabel: \"Tools\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Enable/disable tools and tool groups (web, semantic search). Changes persist across sessions.\",\n\t\t\t\tcurrentValue: toolsOff > 0 ? `${toolsOn} on · ${toolsOff} off` : `${toolsOn} on`,\n\t\t\t\tsubmenu: (_currentValue, done) =>\n\t\t\t\t\tnew ToolsSubmenu(\n\t\t\t\t\t\tconfig.tools,\n\t\t\t\t\t\tconfig.toolGroups,\n\t\t\t\t\t\t(name, enabled) => callbacks.onToolEnabledChange(name, enabled),\n\t\t\t\t\t\t(id, enabled) => callbacks.onToolGroupChange(id, enabled),\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"tool-output-display\",\n\t\t\t\tlabel: \"Tool output display\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"How tool results render. 'standard': shown (expandable). 'collapsed': hidden. 'peek': hidden with a ▸ reveal caret (press the expand key to reveal).\",\n\t\t\t\tcurrentValue: config.toolOutputDisplay,\n\t\t\t\tvalues: [\"standard\", \"collapsed\", \"peek\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"tool-settings\",\n\t\t\t\tlabel: \"Tool settings\",\n\t\t\t\tdescription: \"Per-tool runtime settings: output truncation caps and context garbage collection.\",\n\t\t\t\tcurrentValue: \"configure\",\n\t\t\t\tsubmenu: (_currentValue, done) =>\n\t\t\t\t\tnew ToolSettingsSubmenu(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttoolOutputMaxBytes: config.toolOutputMaxBytes,\n\t\t\t\t\t\t\ttoolOutputMaxLines: config.toolOutputMaxLines,\n\t\t\t\t\t\t\tcontextGc: config.contextGc,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tonToolOutputMaxBytesChange: callbacks.onToolOutputMaxBytesChange,\n\t\t\t\t\t\t\tonToolOutputMaxLinesChange: callbacks.onToolOutputMaxLinesChange,\n\t\t\t\t\t\t\tonContextGcChange: callbacks.onContextGcChange,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t];\n\t\tif (config.flags.length > 0) {\n\t\t\ttoolFlagGroup.push({\n\t\t\t\tid: \"flags\",\n\t\t\t\tlabel: \"Flags\",\n\t\t\t\tdescription: \"Set flags registered by extensions. Changes persist across sessions.\",\n\t\t\t\tcurrentValue: `${config.flags.length} flag${config.flags.length === 1 ? \"\" : \"s\"}`,\n\t\t\t\tsubmenu: (_currentValue, done) =>\n\t\t\t\t\tnew FlagsSubmenu(\n\t\t\t\t\t\tconfig.flags,\n\t\t\t\t\t\t(name, value) => callbacks.onFlagChange(name, value),\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t});\n\t\t}\n\t\t// Add borders\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Shared change handler for every leaf (cycle) setting; used by the\n\t\t// top-level list and each category submenu.\n\t\tconst applyChange = (id: string, newValue: string): void => {\n\t\t\tswitch (id) {\n\t\t\t\tcase \"autocompact\":\n\t\t\t\t\tcallbacks.onAutoCompactChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"tool-output-display\":\n\t\t\t\t\tcallbacks.onToolOutputDisplayChange(newValue as \"collapsed\" | \"peek\" | \"standard\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"show-images\":\n\t\t\t\t\tcallbacks.onShowImagesChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"image-width-cells\":\n\t\t\t\t\tcallbacks.onImageWidthCellsChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"auto-resize-images\":\n\t\t\t\t\tcallbacks.onAutoResizeImagesChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"block-images\":\n\t\t\t\t\tcallbacks.onBlockImagesChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"skill-commands\":\n\t\t\t\t\tcallbacks.onEnableSkillCommandsChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"steering-mode\":\n\t\t\t\t\tcallbacks.onSteeringModeChange(newValue as \"all\" | \"one-at-a-time\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"follow-up-mode\":\n\t\t\t\t\tcallbacks.onFollowUpModeChange(newValue as \"all\" | \"one-at-a-time\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"transport\":\n\t\t\t\t\tcallbacks.onTransportChange(newValue as Transport);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"hide-thinking\":\n\t\t\t\t\tcallbacks.onHideThinkingBlockChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"collapse-changelog\":\n\t\t\t\t\tcallbacks.onCollapseChangelogChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"quiet-startup\":\n\t\t\t\t\tcallbacks.onQuietStartupChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"install-telemetry\":\n\t\t\t\t\tcallbacks.onEnableInstallTelemetryChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"double-escape-action\":\n\t\t\t\t\tcallbacks.onDoubleEscapeActionChange(newValue as \"fork\" | \"tree\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"tree-filter-mode\":\n\t\t\t\t\tcallbacks.onTreeFilterModeChange(\n\t\t\t\t\t\tnewValue as \"default\" | \"no-tools\" | \"user-only\" | \"labeled-only\" | \"all\",\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"show-hardware-cursor\":\n\t\t\t\t\tcallbacks.onShowHardwareCursorChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"editor-border\":\n\t\t\t\t\tcallbacks.onEditorBorderChange(newValue as \"rule\" | \"box\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"editor-padding\":\n\t\t\t\t\tcallbacks.onEditorPaddingXChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"autocomplete-max-visible\":\n\t\t\t\t\tcallbacks.onAutocompleteMaxVisibleChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"clear-on-shrink\":\n\t\t\t\t\tcallbacks.onClearOnShrinkChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"terminal-progress\":\n\t\t\t\t\tcallbacks.onShowTerminalProgressChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"voice-silence-ms\":\n\t\t\t\t\tcallbacks.onVoiceSilenceMsChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"webtools-timeout-secs\":\n\t\t\t\t\tcallbacks.onWebtoolsTimeoutSecsChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t};\n\n\t\t// Partition the flat leaf settings into named category submenus so the\n\t\t// top level stays short. `items` holds autocompact + every leaf setting.\n\t\tconst byId = new Map(items.map((item) => [item.id, item] as const));\n\t\tconst pick = (ids: string[]): SettingItem[] =>\n\t\t\tids.map((id) => byId.get(id)).filter((item): item is SettingItem => item !== undefined);\n\t\tconst categoryRow = (id: string, label: string, description: string, ids: string[]): SettingItem => {\n\t\t\tconst members = pick(ids);\n\t\t\treturn {\n\t\t\t\tid,\n\t\t\t\tlabel,\n\t\t\t\tdescription,\n\t\t\t\tcurrentValue: `${members.length} setting${members.length === 1 ? \"\" : \"s\"}`,\n\t\t\t\tsubmenu: (_currentValue, done) => new CategorySubmenu(members, applyChange, () => done()),\n\t\t\t};\n\t\t};\n\n\t\tconst topItems: SettingItem[] = [\n\t\t\t...(byId.has(\"autocompact\") ? [byId.get(\"autocompact\")!] : []),\n\t\t\t...toolFlagGroup,\n\t\t\tcategoryRow(\n\t\t\t\t\"cat-behavior\",\n\t\t\t\t\"Behavior\",\n\t\t\t\t\"Agent and session behavior: steering, follow-up, thinking, escape, tree filter, transport.\",\n\t\t\t\t[\"steering-mode\", \"follow-up-mode\", \"thinking\", \"double-escape-action\", \"tree-filter-mode\", \"transport\"],\n\t\t\t),\n\t\t\tcategoryRow(\n\t\t\t\t\"cat-interface\",\n\t\t\t\t\"Interface\",\n\t\t\t\t\"Appearance and editor: theme, thinking visibility, cursor, border, padding, autocomplete, terminal.\",\n\t\t\t\t[\n\t\t\t\t\t\"theme\",\n\t\t\t\t\t\"hide-thinking\",\n\t\t\t\t\t\"show-hardware-cursor\",\n\t\t\t\t\t\"editor-border\",\n\t\t\t\t\t\"editor-padding\",\n\t\t\t\t\t\"autocomplete-max-visible\",\n\t\t\t\t\t\"clear-on-shrink\",\n\t\t\t\t\t\"terminal-progress\",\n\t\t\t\t],\n\t\t\t),\n\t\t\tcategoryRow(\"cat-images\", \"Images\", \"Inline image rendering and resizing.\", [\n\t\t\t\t\"show-images\",\n\t\t\t\t\"image-width-cells\",\n\t\t\t\t\"auto-resize-images\",\n\t\t\t\t\"block-images\",\n\t\t\t]),\n\t\t\tcategoryRow(\"cat-advanced\", \"Advanced\", \"Startup, telemetry, skills, warnings, voice, and web tools.\", [\n\t\t\t\t\"quiet-startup\",\n\t\t\t\t\"collapse-changelog\",\n\t\t\t\t\"install-telemetry\",\n\t\t\t\t\"skill-commands\",\n\t\t\t\t\"warnings\",\n\t\t\t\t\"voice-silence-ms\",\n\t\t\t\t\"webtools-timeout-secs\",\n\t\t\t]),\n\t\t];\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\ttopItems,\n\t\t\tMath.min(topItems.length, 10),\n\t\t\tgetSettingsListTheme(),\n\t\t\tapplyChange,\n\t\t\tcallbacks.onCancel,\n\t\t\t{\n\t\t\t\tenableSearch: true,\n\t\t\t},\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetSettingsList(): SettingsList {\n\t\treturn this.settingsList;\n\t}\n}\n"]}
|
|
1
|
+
{"version":3,"file":"settings-selector.d.ts","sourceRoot":"","sources":["../../../../src/modes/interactive/components/settings-selector.ts"],"names":[],"mappings":"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","sourcesContent":["import type { ThinkingLevel } from \"@kolisachint/hoocode-agent-core\";\nimport type { Transport } from \"@kolisachint/hoocode-ai\";\nimport {\n\tContainer,\n\tgetCapabilities,\n\tInput,\n\ttype SelectItem,\n\tSelectList,\n\ttype SelectListLayoutOptions,\n\ttype SettingItem,\n\tSettingsList,\n\tSpacer,\n\tText,\n} from \"@kolisachint/hoocode-tui\";\nimport type { WarningSettings } from \"../../../core/settings-manager.js\";\nimport { getSelectListTheme, getSettingsListTheme, getThemeDescription, theme } from \"../theme/theme.js\";\nimport { DynamicBorder } from \"./dynamic-border.js\";\nimport { keyDisplayText } from \"./keybinding-hints.js\";\n\nconst SETTINGS_SUBMENU_SELECT_LIST_LAYOUT: SelectListLayoutOptions = {\n\tminPrimaryColumnWidth: 12,\n\tmaxPrimaryColumnWidth: 32,\n};\n\nconst THINKING_DESCRIPTIONS: Record<ThinkingLevel, string> = {\n\toff: \"No reasoning\",\n\tminimal: \"Very brief reasoning (~1k tokens)\",\n\tlow: \"Light reasoning (~2k tokens)\",\n\tmedium: \"Moderate reasoning (~8k tokens)\",\n\thigh: \"Deep reasoning (~16k tokens)\",\n\txhigh: \"Maximum reasoning (~32k tokens)\",\n};\n\ninterface ToolToggleInfo {\n\t/** Tool name (e.g. \"read\", \"bash\"). */\n\tname: string;\n\t/** Whether the tool is currently enabled (not in the persisted disabled set). */\n\tenabled: boolean;\n}\n\ninterface FlagInfo {\n\t/** Flag name (without the leading --). */\n\tname: string;\n\tdescription?: string;\n\ttype: \"boolean\" | \"string\";\n\t/** Current effective value. */\n\tvalue: boolean | string;\n}\n\ninterface ToolGroupInfo {\n\t/** Group identifier (e.g. \"web\", \"embsearch\"). */\n\tid: string;\n\tlabel: string;\n\tdescription: string;\n\t/** Whether the group is currently enabled (its tools are available). */\n\tenabled: boolean;\n}\n\nexport interface SettingsConfig {\n\tautoCompact: boolean;\n\ttools: ToolToggleInfo[];\n\ttoolGroups: ToolGroupInfo[];\n\tflags: FlagInfo[];\n\ttoolOutputDisplay: \"collapsed\" | \"peek\" | \"standard\";\n\ttoolOutputMaxBytes: number;\n\ttoolOutputMaxLines: number;\n\tcontextGc: boolean;\n\tshowImages: boolean;\n\timageWidthCells: number;\n\tautoResizeImages: boolean;\n\tblockImages: boolean;\n\tenableSkillCommands: boolean;\n\tpluginInstallScope: \"user\" | \"project\";\n\tsteeringMode: \"all\" | \"one-at-a-time\";\n\tfollowUpMode: \"all\" | \"one-at-a-time\";\n\ttransport: Transport;\n\tthinkingLevel: ThinkingLevel;\n\tavailableThinkingLevels: ThinkingLevel[];\n\tcurrentTheme: string;\n\tavailableThemes: string[];\n\thideThinkingBlock: boolean;\n\tcollapseChangelog: boolean;\n\tenableInstallTelemetry: boolean;\n\tdoubleEscapeAction: \"fork\" | \"tree\" | \"none\";\n\ttreeFilterMode: \"default\" | \"no-tools\" | \"user-only\" | \"labeled-only\" | \"all\";\n\tshowHardwareCursor: boolean;\n\teditorBorder: \"rule\" | \"box\";\n\teditorPaddingX: number;\n\tautocompleteMaxVisible: number;\n\tquietStartup: boolean;\n\tclearOnShrink: boolean;\n\tshowTerminalProgress: boolean;\n\twarnings: WarningSettings;\n\tvoiceSilenceMs: number;\n\twebtoolsTimeoutSecs: number;\n}\n\nexport interface SettingsCallbacks {\n\tonAutoCompactChange: (enabled: boolean) => void;\n\tonToolEnabledChange: (name: string, enabled: boolean) => void;\n\tonToolGroupChange: (id: string, enabled: boolean) => void;\n\tonToolOutputDisplayChange: (level: \"collapsed\" | \"peek\" | \"standard\") => void;\n\tonToolOutputMaxBytesChange: (bytes: number) => void;\n\tonToolOutputMaxLinesChange: (lines: number) => void;\n\tonContextGcChange: (enabled: boolean) => void;\n\tonFlagChange: (name: string, value: boolean | string) => void;\n\tonShowImagesChange: (enabled: boolean) => void;\n\tonImageWidthCellsChange: (width: number) => void;\n\tonAutoResizeImagesChange: (enabled: boolean) => void;\n\tonBlockImagesChange: (blocked: boolean) => void;\n\tonEnableSkillCommandsChange: (enabled: boolean) => void;\n\tonPluginInstallScopeChange: (scope: \"user\" | \"project\") => void;\n\tonSteeringModeChange: (mode: \"all\" | \"one-at-a-time\") => void;\n\tonFollowUpModeChange: (mode: \"all\" | \"one-at-a-time\") => void;\n\tonTransportChange: (transport: Transport) => void;\n\tonThinkingLevelChange: (level: ThinkingLevel) => void;\n\tonThemeChange: (theme: string) => void;\n\tonThemePreview?: (theme: string) => void;\n\tonHideThinkingBlockChange: (hidden: boolean) => void;\n\tonCollapseChangelogChange: (collapsed: boolean) => void;\n\tonEnableInstallTelemetryChange: (enabled: boolean) => void;\n\tonDoubleEscapeActionChange: (action: \"fork\" | \"tree\" | \"none\") => void;\n\tonTreeFilterModeChange: (mode: \"default\" | \"no-tools\" | \"user-only\" | \"labeled-only\" | \"all\") => void;\n\tonShowHardwareCursorChange: (enabled: boolean) => void;\n\tonEditorBorderChange: (border: \"rule\" | \"box\") => void;\n\tonEditorPaddingXChange: (padding: number) => void;\n\tonAutocompleteMaxVisibleChange: (maxVisible: number) => void;\n\tonQuietStartupChange: (enabled: boolean) => void;\n\tonClearOnShrinkChange: (enabled: boolean) => void;\n\tonShowTerminalProgressChange: (enabled: boolean) => void;\n\tonWarningsChange: (warnings: WarningSettings) => void;\n\tonVoiceSilenceMsChange: (ms: number) => void;\n\tonWebtoolsTimeoutSecsChange: (secs: number) => void;\n\tonCancel: () => void;\n}\n\n/**\n * A submenu component for selecting from a list of options.\n */\nclass WarningSettingsSubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\tprivate state: WarningSettings;\n\n\tconstructor(warnings: WarningSettings, onChange: (warnings: WarningSettings) => void, onCancel: () => void) {\n\t\tsuper();\n\n\t\tthis.state = { ...warnings };\n\n\t\tconst items: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"anthropic-extra-usage\",\n\t\t\t\tlabel: \"Anthropic extra usage\",\n\t\t\t\tdescription: \"Warn when Anthropic subscription auth may use paid extra usage\",\n\t\t\t\tcurrentValue: (this.state.anthropicExtraUsage ?? true) ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t];\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 10),\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tswitch (id) {\n\t\t\t\t\tcase \"anthropic-extra-usage\":\n\t\t\t\t\t\tthis.state = { ...this.state, anthropicExtraUsage: newValue === \"true\" };\n\t\t\t\t\t\tonChange({ ...this.state });\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t},\n\t\t\tonCancel,\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\n/**\n * Submenu for tool availability. The first rows are group switches (web,\n * semantic search) that decide whether a group's tools\n * exist at all — this is the same master switch that governs, e.g., the\n * webfetch/websearch tools. Below them are per-tool on/off toggles for the\n * tools that are currently available.\n *\n * Group switches change tool availability and apply on the next session;\n * per-tool toggles apply live and persist. A minimum core (read/bash/edit/\n * write) is guarded so the agent can never be left with no way to act.\n */\nclass ToolsSubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\tprivate enabled: Map<string, boolean>;\n\tprivate static readonly CORE = new Set([\"read\", \"bash\", \"edit\", \"write\"]);\n\tprivate static readonly GROUP_PREFIX = \"group:\";\n\n\tconstructor(\n\t\ttools: ToolToggleInfo[],\n\t\tgroups: ToolGroupInfo[],\n\t\tonChange: (name: string, enabled: boolean) => void,\n\t\tonGroupChange: (id: string, enabled: boolean) => void,\n\t\tonCancel: () => void,\n\t) {\n\t\tsuper();\n\n\t\tthis.enabled = new Map(tools.map((t) => [t.name, t.enabled]));\n\n\t\tconst groupItems: SettingItem[] = groups.map((group) => ({\n\t\t\tid: `${ToolsSubmenu.GROUP_PREFIX}${group.id}`,\n\t\t\tlabel: `[group] ${group.label}`,\n\t\t\tdescription: `${group.description} Governs whether these tools exist; applies on the next session.`,\n\t\t\tcurrentValue: group.enabled ? \"on\" : \"off\",\n\t\t\tvalues: [\"on\", \"off\"],\n\t\t}));\n\n\t\tconst toolItems: SettingItem[] = tools.map((tool) => ({\n\t\t\tid: tool.name,\n\t\t\tlabel: tool.name,\n\t\t\tdescription: ToolsSubmenu.CORE.has(tool.name)\n\t\t\t\t? \"Core tool. Disabling leaves the agent unable to perform this action in every session.\"\n\t\t\t\t: \"Disable to remove this tool from the agent this session and every future session.\",\n\t\t\tcurrentValue: tool.enabled ? \"on\" : \"off\",\n\t\t\tvalues: [\"on\", \"off\"],\n\t\t}));\n\n\t\tconst items = [...groupItems, ...toolItems];\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 12),\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tconst wantEnabled = newValue === \"on\";\n\t\t\t\tif (id.startsWith(ToolsSubmenu.GROUP_PREFIX)) {\n\t\t\t\t\tonGroupChange(id.slice(ToolsSubmenu.GROUP_PREFIX.length), wantEnabled);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\t// Guard: never let the last core tool be turned off.\n\t\t\t\tif (!wantEnabled && ToolsSubmenu.CORE.has(id)) {\n\t\t\t\t\tconst remainingCore = [...ToolsSubmenu.CORE].filter((n) => n !== id && this.enabled.get(n));\n\t\t\t\t\tif (remainingCore.length === 0) {\n\t\t\t\t\t\tthis.settingsList.updateValue(id, \"on\");\n\t\t\t\t\t\treturn;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tthis.enabled.set(id, wantEnabled);\n\t\t\t\tonChange(id, wantEnabled);\n\t\t\t},\n\t\t\tonCancel,\n\t\t\t{ enableSearch: true },\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\n/** Byte-cap presets shown as human labels; mapped back to raw byte counts. */\nconst TOOL_OUTPUT_BYTE_PRESETS: ReadonlyArray<[label: string, bytes: number]> = [\n\t[\"8 KB\", 8 * 1024],\n\t[\"16 KB\", 16 * 1024],\n\t[\"32 KB\", 32 * 1024],\n\t[\"64 KB\", 64 * 1024],\n\t[\"128 KB\", 128 * 1024],\n];\n\nfunction bytesToLabel(bytes: number): string {\n\tconst match = TOOL_OUTPUT_BYTE_PRESETS.find(([, b]) => b === bytes);\n\treturn match ? match[0] : `${Math.round(bytes / 1024)} KB`;\n}\n\ninterface ToolSettingsConfig {\n\ttoolOutputMaxBytes: number;\n\ttoolOutputMaxLines: number;\n\tcontextGc: boolean;\n}\n\ninterface ToolSettingsCallbacks {\n\tonToolOutputMaxBytesChange: (bytes: number) => void;\n\tonToolOutputMaxLinesChange: (lines: number) => void;\n\tonContextGcChange: (enabled: boolean) => void;\n}\n\n/**\n * Submenu for per-tool runtime settings. These feed the tool runtime the next\n * time it is built (next session / rebuild), so changes apply to future tool\n * calls rather than retroactively.\n */\nclass ToolSettingsSubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(config: ToolSettingsConfig, callbacks: ToolSettingsCallbacks, onCancel: () => void) {\n\t\tsuper();\n\n\t\tconst items: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"output-max-bytes\",\n\t\t\t\tlabel: \"Output max bytes\",\n\t\t\t\tdescription: \"Byte cap on a single read/bash result before truncation. Applies to future tool calls.\",\n\t\t\t\tcurrentValue: bytesToLabel(config.toolOutputMaxBytes),\n\t\t\t\tvalues: TOOL_OUTPUT_BYTE_PRESETS.map(([label]) => label),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"output-max-lines\",\n\t\t\t\tlabel: \"Output max lines\",\n\t\t\t\tdescription: \"Line cap on a single read/bash result before truncation. Applies to future tool calls.\",\n\t\t\t\tcurrentValue: String(config.toolOutputMaxLines),\n\t\t\t\tvalues: [\"200\", \"400\", \"800\", \"1600\", \"3200\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"context-gc\",\n\t\t\t\tlabel: \"Context GC\",\n\t\t\t\tdescription: \"Stub superseded read results (files later edited/re-read) out of the outgoing context.\",\n\t\t\t\tcurrentValue: config.contextGc ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t];\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 10),\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tswitch (id) {\n\t\t\t\t\tcase \"output-max-bytes\": {\n\t\t\t\t\t\tconst preset = TOOL_OUTPUT_BYTE_PRESETS.find(([label]) => label === newValue);\n\t\t\t\t\t\tif (preset) callbacks.onToolOutputMaxBytesChange(preset[1]);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase \"output-max-lines\":\n\t\t\t\t\t\tcallbacks.onToolOutputMaxLinesChange(parseInt(newValue, 10));\n\t\t\t\t\t\tbreak;\n\t\t\t\t\tcase \"context-gc\":\n\t\t\t\t\t\tcallbacks.onContextGcChange(newValue === \"true\");\n\t\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t},\n\t\t\tonCancel,\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\n/** Single-line text editor for a string flag value. */\nclass FlagStringEditSubmenu extends Container {\n\tprivate input: Input;\n\n\tconstructor(flagName: string, currentValue: string, done: (value?: string) => void) {\n\t\tsuper();\n\n\t\tthis.addChild(new Text(theme.bold(theme.fg(\"accent\", `Flag: --${flagName}`)), 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.fg(\"muted\", \"Enter a value · Enter to save · Esc to cancel\"), 0, 0));\n\t\tthis.addChild(new Spacer(1));\n\n\t\tthis.input = new Input();\n\t\tthis.input.setValue(currentValue);\n\t\tthis.input.onSubmit = (value: string) => done(value);\n\t\tthis.input.onEscape = () => done();\n\t\tthis.addChild(this.input);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.input.handleInput(data);\n\t}\n}\n\n/**\n * Submenu listing extension-registered flags. Boolean flags toggle on/off;\n * string flags open a text editor. Changes persist to settings.json and are\n * applied live best-effort — extensions that read a flag only at load time\n * pick up the new value on the next launch.\n */\nclass FlagsSubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(flags: FlagInfo[], onChange: (name: string, value: boolean | string) => void, onCancel: () => void) {\n\t\tsuper();\n\n\t\tconst items: SettingItem[] = flags.map((flag) => {\n\t\t\tconst baseDescription = flag.description ?? \"Extension-registered flag.\";\n\t\t\tconst description = `${baseDescription} Persists across sessions; some flags need a restart to fully apply.`;\n\t\t\tif (flag.type === \"boolean\") {\n\t\t\t\treturn {\n\t\t\t\t\tid: flag.name,\n\t\t\t\t\tlabel: flag.name,\n\t\t\t\t\tdescription,\n\t\t\t\t\tcurrentValue: flag.value ? \"on\" : \"off\",\n\t\t\t\t\tvalues: [\"on\", \"off\"],\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tid: flag.name,\n\t\t\t\tlabel: flag.name,\n\t\t\t\tdescription,\n\t\t\t\tcurrentValue: String(flag.value ?? \"\"),\n\t\t\t\tsubmenu: (currentValue, done) => new FlagStringEditSubmenu(flag.name, currentValue, done),\n\t\t\t};\n\t\t});\n\n\t\tconst typeByName = new Map(flags.map((f) => [f.name, f.type]));\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 12),\n\t\t\tgetSettingsListTheme(),\n\t\t\t(id, newValue) => {\n\t\t\t\tif (typeByName.get(id) === \"boolean\") {\n\t\t\t\t\tonChange(id, newValue === \"on\");\n\t\t\t\t} else {\n\t\t\t\t\tonChange(id, newValue);\n\t\t\t\t}\n\t\t\t},\n\t\t\tonCancel,\n\t\t\t{ enableSearch: true },\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\n/**\n * Generic submenu holding a subset of leaf settings under a category label.\n * Shares the parent's change handler so cycle rows behave exactly as they did\n * when flat; nested submenu rows (theme, thinking, warnings) keep their own\n * factories.\n */\nclass CategorySubmenu extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(items: SettingItem[], onChange: (id: string, newValue: string) => void, onCancel: () => void) {\n\t\tsuper();\n\t\tthis.settingsList = new SettingsList(\n\t\t\titems,\n\t\t\tMath.min(items.length, 10),\n\t\t\tgetSettingsListTheme(),\n\t\t\tonChange,\n\t\t\tonCancel,\n\t\t\t{\n\t\t\t\tenableSearch: true,\n\t\t\t},\n\t\t);\n\t\tthis.addChild(this.settingsList);\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.settingsList.handleInput(data);\n\t}\n}\n\nclass SelectSubmenu extends Container {\n\tprivate selectList: SelectList;\n\n\tconstructor(\n\t\ttitle: string,\n\t\tdescription: string,\n\t\toptions: SelectItem[],\n\t\tcurrentValue: string,\n\t\tonSelect: (value: string) => void,\n\t\tonCancel: () => void,\n\t\tonSelectionChange?: (value: string) => void,\n\t) {\n\t\tsuper();\n\n\t\t// Title\n\t\tthis.addChild(new Text(theme.bold(theme.fg(\"accent\", title)), 0, 0));\n\n\t\t// Description\n\t\tif (description) {\n\t\t\tthis.addChild(new Spacer(1));\n\t\t\tthis.addChild(new Text(theme.fg(\"muted\", description), 0, 0));\n\t\t}\n\n\t\t// Spacer\n\t\tthis.addChild(new Spacer(1));\n\n\t\t// Select list\n\t\tthis.selectList = new SelectList(\n\t\t\toptions,\n\t\t\tMath.min(options.length, 10),\n\t\t\tgetSelectListTheme(),\n\t\t\tSETTINGS_SUBMENU_SELECT_LIST_LAYOUT,\n\t\t);\n\n\t\t// Pre-select current value\n\t\tconst currentIndex = options.findIndex((o) => o.value === currentValue);\n\t\tif (currentIndex !== -1) {\n\t\t\tthis.selectList.setSelectedIndex(currentIndex);\n\t\t}\n\n\t\tthis.selectList.onSelect = (item) => {\n\t\t\tonSelect(item.value);\n\t\t};\n\n\t\tthis.selectList.onCancel = onCancel;\n\n\t\tif (onSelectionChange) {\n\t\t\tthis.selectList.onSelectionChange = (item) => {\n\t\t\t\tonSelectionChange(item.value);\n\t\t\t};\n\t\t}\n\n\t\tthis.addChild(this.selectList);\n\n\t\t// Hint\n\t\tthis.addChild(new Spacer(1));\n\t\tthis.addChild(new Text(theme.fg(\"dim\", \" Enter to select · Esc to go back\"), 0, 0));\n\t}\n\n\thandleInput(data: string): void {\n\t\tthis.selectList.handleInput(data);\n\t}\n}\n\n/**\n * Main settings selector component.\n */\nexport class SettingsSelectorComponent extends Container {\n\tprivate settingsList: SettingsList;\n\n\tconstructor(config: SettingsConfig, callbacks: SettingsCallbacks) {\n\t\tsuper();\n\n\t\tconst supportsImages = getCapabilities().images;\n\t\tconst followUpKey = keyDisplayText(\"app.message.followUp\");\n\t\tlet currentWarnings = { ...config.warnings };\n\n\t\tconst toolsOn = config.tools.filter((t) => t.enabled).length;\n\t\tconst toolsOff = config.tools.length - toolsOn;\n\n\t\tconst items: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"autocompact\",\n\t\t\t\tlabel: \"Auto-compact\",\n\t\t\t\tdescription: \"Automatically compact context when it gets too large\",\n\t\t\t\tcurrentValue: config.autoCompact ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"steering-mode\",\n\t\t\t\tlabel: \"Steering mode\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Enter while streaming queues steering messages. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.\",\n\t\t\t\tcurrentValue: config.steeringMode,\n\t\t\t\tvalues: [\"one-at-a-time\", \"all\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"follow-up-mode\",\n\t\t\t\tlabel: \"Follow-up mode\",\n\t\t\t\tdescription: `${followUpKey} queues follow-up messages until agent stops. 'one-at-a-time': deliver one, wait for response. 'all': deliver all at once.`,\n\t\t\t\tcurrentValue: config.followUpMode,\n\t\t\t\tvalues: [\"one-at-a-time\", \"all\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"transport\",\n\t\t\t\tlabel: \"Transport\",\n\t\t\t\tdescription: \"Preferred transport for providers that support multiple transports\",\n\t\t\t\tcurrentValue: config.transport,\n\t\t\t\tvalues: [\"sse\", \"websocket\", \"websocket-cached\", \"auto\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"hide-thinking\",\n\t\t\t\tlabel: \"Hide thinking\",\n\t\t\t\tdescription: \"Hide thinking blocks in assistant responses\",\n\t\t\t\tcurrentValue: config.hideThinkingBlock ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"collapse-changelog\",\n\t\t\t\tlabel: \"Collapse changelog\",\n\t\t\t\tdescription: \"Show condensed changelog after updates\",\n\t\t\t\tcurrentValue: config.collapseChangelog ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"quiet-startup\",\n\t\t\t\tlabel: \"Quiet startup\",\n\t\t\t\tdescription: \"Disable verbose printing at startup\",\n\t\t\t\tcurrentValue: config.quietStartup ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"install-telemetry\",\n\t\t\t\tlabel: \"Install telemetry\",\n\t\t\t\tdescription: \"Send an anonymous version/update ping after changelog-detected updates\",\n\t\t\t\tcurrentValue: config.enableInstallTelemetry ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"double-escape-action\",\n\t\t\t\tlabel: \"Double-escape action\",\n\t\t\t\tdescription: \"Action when pressing Escape twice with empty editor\",\n\t\t\t\tcurrentValue: config.doubleEscapeAction,\n\t\t\t\tvalues: [\"tree\", \"fork\", \"none\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"tree-filter-mode\",\n\t\t\t\tlabel: \"Tree filter mode\",\n\t\t\t\tdescription: \"Default filter when opening /tree\",\n\t\t\t\tcurrentValue: config.treeFilterMode,\n\t\t\t\tvalues: [\"default\", \"no-tools\", \"user-only\", \"labeled-only\", \"all\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"warnings\",\n\t\t\t\tlabel: \"Warnings\",\n\t\t\t\tdescription: \"Enable or disable individual warnings\",\n\t\t\t\tcurrentValue: \"configure\",\n\t\t\t\tsubmenu: (_currentValue, done) =>\n\t\t\t\t\tnew WarningSettingsSubmenu(\n\t\t\t\t\t\tcurrentWarnings,\n\t\t\t\t\t\t(warnings) => {\n\t\t\t\t\t\t\tcurrentWarnings = warnings;\n\t\t\t\t\t\t\tcallbacks.onWarningsChange(warnings);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"thinking\",\n\t\t\t\tlabel: \"Thinking level\",\n\t\t\t\tdescription: \"Reasoning depth for thinking-capable models\",\n\t\t\t\tcurrentValue: config.thinkingLevel,\n\t\t\t\tsubmenu: (currentValue, done) =>\n\t\t\t\t\tnew SelectSubmenu(\n\t\t\t\t\t\t\"Thinking Level\",\n\t\t\t\t\t\t\"Select reasoning depth for thinking-capable models\",\n\t\t\t\t\t\tconfig.availableThinkingLevels.map((level) => ({\n\t\t\t\t\t\t\tvalue: level,\n\t\t\t\t\t\t\tlabel: level,\n\t\t\t\t\t\t\tdescription: THINKING_DESCRIPTIONS[level],\n\t\t\t\t\t\t})),\n\t\t\t\t\t\tcurrentValue,\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\tcallbacks.onThinkingLevelChange(value as ThinkingLevel);\n\t\t\t\t\t\t\tdone(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"theme\",\n\t\t\t\tlabel: \"Theme\",\n\t\t\t\tdescription: \"Color theme for the interface\",\n\t\t\t\tcurrentValue: config.currentTheme,\n\t\t\t\tsubmenu: (currentValue, done) =>\n\t\t\t\t\tnew SelectSubmenu(\n\t\t\t\t\t\t\"Theme\",\n\t\t\t\t\t\t\"Select color theme\",\n\t\t\t\t\t\tconfig.availableThemes.map((t) => ({\n\t\t\t\t\t\t\tvalue: t,\n\t\t\t\t\t\t\tlabel: t,\n\t\t\t\t\t\t\tdescription: getThemeDescription(t),\n\t\t\t\t\t\t})),\n\t\t\t\t\t\tcurrentValue,\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\tcallbacks.onThemeChange(value);\n\t\t\t\t\t\t\tdone(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => {\n\t\t\t\t\t\t\t// Restore original theme on cancel\n\t\t\t\t\t\t\tcallbacks.onThemePreview?.(currentValue);\n\t\t\t\t\t\t\tdone();\n\t\t\t\t\t\t},\n\t\t\t\t\t\t(value) => {\n\t\t\t\t\t\t\t// Preview theme on selection change\n\t\t\t\t\t\t\tcallbacks.onThemePreview?.(value);\n\t\t\t\t\t\t},\n\t\t\t\t\t),\n\t\t\t},\n\t\t];\n\n\t\t// Only show image toggle if terminal supports it\n\t\tif (supportsImages) {\n\t\t\t// Insert after autocompact\n\t\t\titems.splice(1, 0, {\n\t\t\t\tid: \"show-images\",\n\t\t\t\tlabel: \"Show images\",\n\t\t\t\tdescription: \"Render images inline in terminal\",\n\t\t\t\tcurrentValue: config.showImages ? \"true\" : \"false\",\n\t\t\t\tvalues: [\"true\", \"false\"],\n\t\t\t});\n\t\t\titems.splice(2, 0, {\n\t\t\t\tid: \"image-width-cells\",\n\t\t\t\tlabel: \"Image width\",\n\t\t\t\tdescription: \"Preferred inline image width in terminal cells\",\n\t\t\t\tcurrentValue: String(config.imageWidthCells),\n\t\t\t\tvalues: [\"60\", \"80\", \"120\"],\n\t\t\t});\n\t\t}\n\n\t\t// Image auto-resize toggle (always available, affects both attached and read images)\n\t\titems.splice(supportsImages ? 3 : 1, 0, {\n\t\t\tid: \"auto-resize-images\",\n\t\t\tlabel: \"Auto-resize images\",\n\t\t\tdescription: \"Resize large images to 2000x2000 max for better model compatibility\",\n\t\t\tcurrentValue: config.autoResizeImages ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Block images toggle (always available, insert after auto-resize-images)\n\t\tconst autoResizeIndex = items.findIndex((item) => item.id === \"auto-resize-images\");\n\t\titems.splice(autoResizeIndex + 1, 0, {\n\t\t\tid: \"block-images\",\n\t\t\tlabel: \"Block images\",\n\t\t\tdescription: \"Prevent images from being sent to LLM providers\",\n\t\t\tcurrentValue: config.blockImages ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Skill commands toggle (insert after block-images)\n\t\tconst blockImagesIndex = items.findIndex((item) => item.id === \"block-images\");\n\t\titems.splice(blockImagesIndex + 1, 0, {\n\t\t\tid: \"skill-commands\",\n\t\t\tlabel: \"Skill commands\",\n\t\t\tdescription: \"Register skills as /skill:name commands\",\n\t\t\tcurrentValue: config.enableSkillCommands ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Plugin install scope (insert after skill-commands). Governs the\n\t\t// autonomous InstallPlugin only — /plugin install asks per install.\n\t\tconst skillCommandsIdx = items.findIndex((item) => item.id === \"skill-commands\");\n\t\titems.splice(skillCommandsIdx + 1, 0, {\n\t\t\tid: \"plugin-install-scope\",\n\t\t\tlabel: \"Plugin install scope\",\n\t\t\tdescription: \"Where autonomous plugin installs go: user (~/.agents) or project (this repo, shared)\",\n\t\t\tcurrentValue: config.pluginInstallScope,\n\t\t\tvalues: [\"user\", \"project\"],\n\t\t});\n\n\t\t// Hardware cursor toggle (insert after plugin-install-scope)\n\t\tconst skillCommandsIndex = items.findIndex((item) => item.id === \"plugin-install-scope\");\n\t\titems.splice(skillCommandsIndex + 1, 0, {\n\t\t\tid: \"show-hardware-cursor\",\n\t\t\tlabel: \"Show hardware cursor\",\n\t\t\tdescription: \"Show the terminal cursor while still positioning it for IME support\",\n\t\t\tcurrentValue: config.showHardwareCursor ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Editor border toggle (insert after show-hardware-cursor)\n\t\tconst hardwareCursorIndex = items.findIndex((item) => item.id === \"show-hardware-cursor\");\n\t\titems.splice(hardwareCursorIndex + 1, 0, {\n\t\t\tid: \"editor-border\",\n\t\t\tlabel: \"Editor border\",\n\t\t\tdescription: \"Box draws side borders, rule draws horizontal lines only\",\n\t\t\tcurrentValue: config.editorBorder,\n\t\t\tvalues: [\"box\", \"rule\"],\n\t\t});\n\n\t\t// Editor padding toggle (insert after editor-border)\n\t\tconst editorBorderIndex = items.findIndex((item) => item.id === \"editor-border\");\n\t\titems.splice(editorBorderIndex + 1, 0, {\n\t\t\tid: \"editor-padding\",\n\t\t\tlabel: \"Editor padding\",\n\t\t\tdescription: \"Horizontal padding for input editor (0-3)\",\n\t\t\tcurrentValue: String(config.editorPaddingX),\n\t\t\tvalues: [\"0\", \"1\", \"2\", \"3\"],\n\t\t});\n\n\t\t// Autocomplete max visible toggle (insert after editor-padding)\n\t\tconst editorPaddingIndex = items.findIndex((item) => item.id === \"editor-padding\");\n\t\titems.splice(editorPaddingIndex + 1, 0, {\n\t\t\tid: \"autocomplete-max-visible\",\n\t\t\tlabel: \"Autocomplete max items\",\n\t\t\tdescription: \"Max visible items in autocomplete dropdown (3-20)\",\n\t\t\tcurrentValue: String(config.autocompleteMaxVisible),\n\t\t\tvalues: [\"3\", \"5\", \"7\", \"10\", \"15\", \"20\"],\n\t\t});\n\n\t\t// Clear on shrink toggle (insert after autocomplete-max-visible)\n\t\tconst autocompleteIndex = items.findIndex((item) => item.id === \"autocomplete-max-visible\");\n\t\titems.splice(autocompleteIndex + 1, 0, {\n\t\t\tid: \"clear-on-shrink\",\n\t\t\tlabel: \"Clear on shrink\",\n\t\t\tdescription: \"Clear empty rows when content shrinks (may cause flicker)\",\n\t\t\tcurrentValue: config.clearOnShrink ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Terminal progress toggle (insert after clear-on-shrink)\n\t\tconst clearOnShrinkIndex = items.findIndex((item) => item.id === \"clear-on-shrink\");\n\t\titems.splice(clearOnShrinkIndex + 1, 0, {\n\t\t\tid: \"terminal-progress\",\n\t\t\tlabel: \"Terminal progress\",\n\t\t\tdescription: \"Show OSC 9;4 progress indicators in the terminal tab bar\",\n\t\t\tcurrentValue: config.showTerminalProgress ? \"true\" : \"false\",\n\t\t\tvalues: [\"true\", \"false\"],\n\t\t});\n\n\t\t// Voice silence window (insert after terminal-progress)\n\t\tconst terminalProgressIndex = items.findIndex((item) => item.id === \"terminal-progress\");\n\t\titems.splice(terminalProgressIndex + 1, 0, {\n\t\t\tid: \"voice-silence-ms\",\n\t\t\tlabel: \"Voice silence window\",\n\t\t\tdescription: \"Trailing-silence (ms) before voice capture auto-stops (300-10000). Env: VOICETOOLS_SILENCE_MS.\",\n\t\t\tcurrentValue: String(config.voiceSilenceMs),\n\t\t\tvalues: [\"300\", \"500\", \"800\", \"1200\", \"2000\", \"3000\", \"5000\", \"8000\", \"10000\"],\n\t\t});\n\n\t\t// Webtools request timeout (insert after voice-silence-ms)\n\t\tconst voiceSilenceIndex = items.findIndex((item) => item.id === \"voice-silence-ms\");\n\t\titems.splice(voiceSilenceIndex + 1, 0, {\n\t\t\tid: \"webtools-timeout-secs\",\n\t\t\tlabel: \"Web tools timeout\",\n\t\t\tdescription: \"Per-request timeout (secs) for webfetch/websearch (1-120). Env: HOOCODE_WEBTOOLS_TIMEOUT.\",\n\t\t\tcurrentValue: String(config.webtoolsTimeoutSecs),\n\t\t\tvalues: [\"5\", \"10\", \"15\", \"30\", \"60\", \"120\"],\n\t\t});\n\n\t\t// Keep the tool/flag controls together as one block near the top, inserted\n\t\t// after the image/terminal splices above so they aren't leapfrogged.\n\t\tconst toolFlagGroup: SettingItem[] = [\n\t\t\t{\n\t\t\t\tid: \"tools\",\n\t\t\t\tlabel: \"Tools\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"Enable/disable tools and tool groups (web, semantic search). Changes persist across sessions.\",\n\t\t\t\tcurrentValue: toolsOff > 0 ? `${toolsOn} on · ${toolsOff} off` : `${toolsOn} on`,\n\t\t\t\tsubmenu: (_currentValue, done) =>\n\t\t\t\t\tnew ToolsSubmenu(\n\t\t\t\t\t\tconfig.tools,\n\t\t\t\t\t\tconfig.toolGroups,\n\t\t\t\t\t\t(name, enabled) => callbacks.onToolEnabledChange(name, enabled),\n\t\t\t\t\t\t(id, enabled) => callbacks.onToolGroupChange(id, enabled),\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"tool-output-display\",\n\t\t\t\tlabel: \"Tool output display\",\n\t\t\t\tdescription:\n\t\t\t\t\t\"How tool results render. 'standard': shown (expandable). 'collapsed': hidden. 'peek': hidden with a ▸ reveal caret (press the expand key to reveal).\",\n\t\t\t\tcurrentValue: config.toolOutputDisplay,\n\t\t\t\tvalues: [\"standard\", \"collapsed\", \"peek\"],\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: \"tool-settings\",\n\t\t\t\tlabel: \"Tool settings\",\n\t\t\t\tdescription: \"Per-tool runtime settings: output truncation caps and context garbage collection.\",\n\t\t\t\tcurrentValue: \"configure\",\n\t\t\t\tsubmenu: (_currentValue, done) =>\n\t\t\t\t\tnew ToolSettingsSubmenu(\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\ttoolOutputMaxBytes: config.toolOutputMaxBytes,\n\t\t\t\t\t\t\ttoolOutputMaxLines: config.toolOutputMaxLines,\n\t\t\t\t\t\t\tcontextGc: config.contextGc,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tonToolOutputMaxBytesChange: callbacks.onToolOutputMaxBytesChange,\n\t\t\t\t\t\t\tonToolOutputMaxLinesChange: callbacks.onToolOutputMaxLinesChange,\n\t\t\t\t\t\t\tonContextGcChange: callbacks.onContextGcChange,\n\t\t\t\t\t\t},\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t},\n\t\t];\n\t\tif (config.flags.length > 0) {\n\t\t\ttoolFlagGroup.push({\n\t\t\t\tid: \"flags\",\n\t\t\t\tlabel: \"Flags\",\n\t\t\t\tdescription: \"Set flags registered by extensions. Changes persist across sessions.\",\n\t\t\t\tcurrentValue: `${config.flags.length} flag${config.flags.length === 1 ? \"\" : \"s\"}`,\n\t\t\t\tsubmenu: (_currentValue, done) =>\n\t\t\t\t\tnew FlagsSubmenu(\n\t\t\t\t\t\tconfig.flags,\n\t\t\t\t\t\t(name, value) => callbacks.onFlagChange(name, value),\n\t\t\t\t\t\t() => done(),\n\t\t\t\t\t),\n\t\t\t});\n\t\t}\n\t\t// Add borders\n\t\tthis.addChild(new DynamicBorder());\n\n\t\t// Shared change handler for every leaf (cycle) setting; used by the\n\t\t// top-level list and each category submenu.\n\t\tconst applyChange = (id: string, newValue: string): void => {\n\t\t\tswitch (id) {\n\t\t\t\tcase \"autocompact\":\n\t\t\t\t\tcallbacks.onAutoCompactChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"tool-output-display\":\n\t\t\t\t\tcallbacks.onToolOutputDisplayChange(newValue as \"collapsed\" | \"peek\" | \"standard\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"show-images\":\n\t\t\t\t\tcallbacks.onShowImagesChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"image-width-cells\":\n\t\t\t\t\tcallbacks.onImageWidthCellsChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"auto-resize-images\":\n\t\t\t\t\tcallbacks.onAutoResizeImagesChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"block-images\":\n\t\t\t\t\tcallbacks.onBlockImagesChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"skill-commands\":\n\t\t\t\t\tcallbacks.onEnableSkillCommandsChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"plugin-install-scope\":\n\t\t\t\t\tcallbacks.onPluginInstallScopeChange(newValue as \"user\" | \"project\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"steering-mode\":\n\t\t\t\t\tcallbacks.onSteeringModeChange(newValue as \"all\" | \"one-at-a-time\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"follow-up-mode\":\n\t\t\t\t\tcallbacks.onFollowUpModeChange(newValue as \"all\" | \"one-at-a-time\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"transport\":\n\t\t\t\t\tcallbacks.onTransportChange(newValue as Transport);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"hide-thinking\":\n\t\t\t\t\tcallbacks.onHideThinkingBlockChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"collapse-changelog\":\n\t\t\t\t\tcallbacks.onCollapseChangelogChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"quiet-startup\":\n\t\t\t\t\tcallbacks.onQuietStartupChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"install-telemetry\":\n\t\t\t\t\tcallbacks.onEnableInstallTelemetryChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"double-escape-action\":\n\t\t\t\t\tcallbacks.onDoubleEscapeActionChange(newValue as \"fork\" | \"tree\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"tree-filter-mode\":\n\t\t\t\t\tcallbacks.onTreeFilterModeChange(\n\t\t\t\t\t\tnewValue as \"default\" | \"no-tools\" | \"user-only\" | \"labeled-only\" | \"all\",\n\t\t\t\t\t);\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"show-hardware-cursor\":\n\t\t\t\t\tcallbacks.onShowHardwareCursorChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"editor-border\":\n\t\t\t\t\tcallbacks.onEditorBorderChange(newValue as \"rule\" | \"box\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"editor-padding\":\n\t\t\t\t\tcallbacks.onEditorPaddingXChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"autocomplete-max-visible\":\n\t\t\t\t\tcallbacks.onAutocompleteMaxVisibleChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"clear-on-shrink\":\n\t\t\t\t\tcallbacks.onClearOnShrinkChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"terminal-progress\":\n\t\t\t\t\tcallbacks.onShowTerminalProgressChange(newValue === \"true\");\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"voice-silence-ms\":\n\t\t\t\t\tcallbacks.onVoiceSilenceMsChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t\tcase \"webtools-timeout-secs\":\n\t\t\t\t\tcallbacks.onWebtoolsTimeoutSecsChange(parseInt(newValue, 10));\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t};\n\n\t\t// Partition the flat leaf settings into named category submenus so the\n\t\t// top level stays short. `items` holds autocompact + every leaf setting.\n\t\tconst byId = new Map(items.map((item) => [item.id, item] as const));\n\t\tconst pick = (ids: string[]): SettingItem[] =>\n\t\t\tids.map((id) => byId.get(id)).filter((item): item is SettingItem => item !== undefined);\n\t\tconst categoryRow = (id: string, label: string, description: string, ids: string[]): SettingItem => {\n\t\t\tconst members = pick(ids);\n\t\t\treturn {\n\t\t\t\tid,\n\t\t\t\tlabel,\n\t\t\t\tdescription,\n\t\t\t\tcurrentValue: `${members.length} setting${members.length === 1 ? \"\" : \"s\"}`,\n\t\t\t\tsubmenu: (_currentValue, done) => new CategorySubmenu(members, applyChange, () => done()),\n\t\t\t};\n\t\t};\n\n\t\tconst topItems: SettingItem[] = [\n\t\t\t...(byId.has(\"autocompact\") ? [byId.get(\"autocompact\")!] : []),\n\t\t\t...toolFlagGroup,\n\t\t\tcategoryRow(\n\t\t\t\t\"cat-behavior\",\n\t\t\t\t\"Behavior\",\n\t\t\t\t\"Agent and session behavior: steering, follow-up, thinking, escape, tree filter, transport.\",\n\t\t\t\t[\"steering-mode\", \"follow-up-mode\", \"thinking\", \"double-escape-action\", \"tree-filter-mode\", \"transport\"],\n\t\t\t),\n\t\t\tcategoryRow(\n\t\t\t\t\"cat-interface\",\n\t\t\t\t\"Interface\",\n\t\t\t\t\"Appearance and editor: theme, thinking visibility, cursor, border, padding, autocomplete, terminal.\",\n\t\t\t\t[\n\t\t\t\t\t\"theme\",\n\t\t\t\t\t\"hide-thinking\",\n\t\t\t\t\t\"show-hardware-cursor\",\n\t\t\t\t\t\"editor-border\",\n\t\t\t\t\t\"editor-padding\",\n\t\t\t\t\t\"autocomplete-max-visible\",\n\t\t\t\t\t\"clear-on-shrink\",\n\t\t\t\t\t\"terminal-progress\",\n\t\t\t\t],\n\t\t\t),\n\t\t\tcategoryRow(\"cat-images\", \"Images\", \"Inline image rendering and resizing.\", [\n\t\t\t\t\"show-images\",\n\t\t\t\t\"image-width-cells\",\n\t\t\t\t\"auto-resize-images\",\n\t\t\t\t\"block-images\",\n\t\t\t]),\n\t\t\tcategoryRow(\"cat-advanced\", \"Advanced\", \"Startup, telemetry, skills, warnings, voice, and web tools.\", [\n\t\t\t\t\"quiet-startup\",\n\t\t\t\t\"collapse-changelog\",\n\t\t\t\t\"install-telemetry\",\n\t\t\t\t\"skill-commands\",\n\t\t\t\t\"warnings\",\n\t\t\t\t\"voice-silence-ms\",\n\t\t\t\t\"webtools-timeout-secs\",\n\t\t\t]),\n\t\t];\n\n\t\tthis.settingsList = new SettingsList(\n\t\t\ttopItems,\n\t\t\tMath.min(topItems.length, 10),\n\t\t\tgetSettingsListTheme(),\n\t\t\tapplyChange,\n\t\t\tcallbacks.onCancel,\n\t\t\t{\n\t\t\t\tenableSearch: true,\n\t\t\t},\n\t\t);\n\n\t\tthis.addChild(this.settingsList);\n\t\tthis.addChild(new DynamicBorder());\n\t}\n\n\tgetSettingsList(): SettingsList {\n\t\treturn this.settingsList;\n\t}\n}\n"]}
|
|
@@ -465,8 +465,18 @@ export class SettingsSelectorComponent extends Container {
|
|
|
465
465
|
currentValue: config.enableSkillCommands ? "true" : "false",
|
|
466
466
|
values: ["true", "false"],
|
|
467
467
|
});
|
|
468
|
-
//
|
|
469
|
-
|
|
468
|
+
// Plugin install scope (insert after skill-commands). Governs the
|
|
469
|
+
// autonomous InstallPlugin only — /plugin install asks per install.
|
|
470
|
+
const skillCommandsIdx = items.findIndex((item) => item.id === "skill-commands");
|
|
471
|
+
items.splice(skillCommandsIdx + 1, 0, {
|
|
472
|
+
id: "plugin-install-scope",
|
|
473
|
+
label: "Plugin install scope",
|
|
474
|
+
description: "Where autonomous plugin installs go: user (~/.agents) or project (this repo, shared)",
|
|
475
|
+
currentValue: config.pluginInstallScope,
|
|
476
|
+
values: ["user", "project"],
|
|
477
|
+
});
|
|
478
|
+
// Hardware cursor toggle (insert after plugin-install-scope)
|
|
479
|
+
const skillCommandsIndex = items.findIndex((item) => item.id === "plugin-install-scope");
|
|
470
480
|
items.splice(skillCommandsIndex + 1, 0, {
|
|
471
481
|
id: "show-hardware-cursor",
|
|
472
482
|
label: "Show hardware cursor",
|
|
@@ -606,6 +616,9 @@ export class SettingsSelectorComponent extends Container {
|
|
|
606
616
|
case "skill-commands":
|
|
607
617
|
callbacks.onEnableSkillCommandsChange(newValue === "true");
|
|
608
618
|
break;
|
|
619
|
+
case "plugin-install-scope":
|
|
620
|
+
callbacks.onPluginInstallScopeChange(newValue);
|
|
621
|
+
break;
|
|
609
622
|
case "steering-mode":
|
|
610
623
|
callbacks.onSteeringModeChange(newValue);
|
|
611
624
|
break;
|