@robota-sdk/agent-cli 3.0.0-beta.33 → 3.0.0-beta.35

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.
@@ -1,5 +1,5 @@
1
1
  // src/cli.ts
2
- import { readFileSync as readFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
2
+ import { readFileSync as readFileSync4, existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
3
3
  import { join as join5, dirname as dirname3 } from "path";
4
4
  import { fileURLToPath } from "url";
5
5
  import {
@@ -149,8 +149,8 @@ var PrintTerminal = class {
149
149
  import { render } from "ink";
150
150
 
151
151
  // src/ui/App.tsx
152
- import { useState as useState7, useRef as useRef6 } from "react";
153
- import { Box as Box9, Text as Text11, useApp, useInput as useInput5 } from "ink";
152
+ import { useState as useState10, useRef as useRef8 } from "react";
153
+ import { Box as Box11, Text as Text13, useApp, useInput as useInput7 } from "ink";
154
154
  import { getModelName } from "@robota-sdk/agent-core";
155
155
 
156
156
  // src/ui/hooks/useSession.ts
@@ -158,25 +158,69 @@ import { useState, useCallback, useRef } from "react";
158
158
  import { createSession, FileSessionLogger, projectPaths } from "@robota-sdk/agent-sdk";
159
159
 
160
160
  // src/utils/edit-diff.ts
161
- function generateDiffLines(oldStr, newStr) {
161
+ import { readFileSync as readFileSync2 } from "fs";
162
+ var CONTEXT_LINES = 2;
163
+ function generateDiffLines(oldStr, newStr, startLine = 1) {
162
164
  if (oldStr === newStr) return [];
163
165
  const lines = [];
164
- for (const line of oldStr.split("\n")) {
165
- lines.push({ type: "remove", text: line });
166
+ const oldLines = oldStr.split("\n");
167
+ const newLines = newStr.split("\n");
168
+ for (let i = 0; i < oldLines.length; i++) {
169
+ lines.push({ type: "remove", text: oldLines[i], lineNumber: startLine + i });
166
170
  }
167
- for (const line of newStr.split("\n")) {
168
- lines.push({ type: "add", text: line });
171
+ for (let i = 0; i < newLines.length; i++) {
172
+ lines.push({ type: "add", text: newLines[i], lineNumber: startLine + i });
169
173
  }
170
174
  return lines;
171
175
  }
172
- function extractEditDiff(toolName, toolArgs) {
176
+ function generateDiffLinesWithContext(oldStr, newStr, startLine, filePath) {
177
+ if (oldStr === newStr) return [];
178
+ const diffLines = generateDiffLines(oldStr, newStr, startLine);
179
+ let fileLines;
180
+ try {
181
+ fileLines = readFileSync2(filePath, "utf-8").split("\n");
182
+ } catch {
183
+ return diffLines;
184
+ }
185
+ const result = [];
186
+ const contextStart = Math.max(0, startLine - 1 - CONTEXT_LINES);
187
+ for (let i = contextStart; i < startLine - 1; i++) {
188
+ if (i < fileLines.length) {
189
+ result.push({ type: "context", text: fileLines[i], lineNumber: i + 1 });
190
+ }
191
+ }
192
+ result.push(...diffLines);
193
+ const newLineCount = newStr.split("\n").length;
194
+ const afterStart = startLine - 1 + newLineCount;
195
+ for (let i = afterStart; i < afterStart + CONTEXT_LINES; i++) {
196
+ if (i < fileLines.length) {
197
+ result.push({ type: "context", text: fileLines[i], lineNumber: i + 1 });
198
+ }
199
+ }
200
+ return result;
201
+ }
202
+ function extractEditDiff(toolName, toolArgs, startLine) {
173
203
  if (toolName !== "Edit" || !toolArgs) return null;
174
204
  const filePath = toolArgs.file_path ?? toolArgs.filePath;
175
205
  const oldStr = toolArgs.old_string ?? toolArgs.oldString;
176
206
  const newStr = toolArgs.new_string ?? toolArgs.newString;
177
207
  if (typeof filePath !== "string") return null;
178
208
  if (typeof oldStr !== "string" || typeof newStr !== "string") return null;
179
- const lines = generateDiffLines(oldStr, newStr);
209
+ let sl = startLine ?? 0;
210
+ if (!sl) {
211
+ try {
212
+ const fileContent = readFileSync2(filePath, "utf-8");
213
+ const idx = fileContent.indexOf(newStr);
214
+ if (idx >= 0) {
215
+ sl = fileContent.substring(0, idx).split("\n").length;
216
+ } else {
217
+ sl = 1;
218
+ }
219
+ } catch {
220
+ sl = 1;
221
+ }
222
+ }
223
+ const lines = generateDiffLinesWithContext(oldStr, newStr, sl, filePath);
180
224
  if (lines.length === 0) return null;
181
225
  return { file: filePath, lines };
182
226
  }
@@ -254,9 +298,20 @@ function useSession(props) {
254
298
  setActiveTools((prev) => {
255
299
  const updated = prev.map((t) => {
256
300
  if (!(t.toolName === event.toolName && t.isRunning)) return t;
301
+ let startLine;
302
+ if (event.toolResultData && event.toolName === "Edit") {
303
+ try {
304
+ const parsed = JSON.parse(event.toolResultData);
305
+ if (typeof parsed.startLine === "number") {
306
+ startLine = parsed.startLine;
307
+ }
308
+ } catch {
309
+ }
310
+ }
257
311
  const editDiff = extractEditDiff(
258
312
  event.toolName,
259
- t._toolArgs
313
+ t._toolArgs,
314
+ startLine
260
315
  );
261
316
  const finished = {
262
317
  ...t,
@@ -448,18 +503,9 @@ async function handlePluginCommand(args, addMessage, callbacks) {
448
503
  try {
449
504
  switch (subcommand) {
450
505
  case "":
451
- case void 0: {
452
- const plugins = await callbacks.listInstalled();
453
- if (plugins.length === 0) {
454
- addMessage({ role: "system", content: "No plugins installed." });
455
- } else {
456
- const lines = plugins.map(
457
- (p) => ` ${p.name} \u2014 ${p.description} [${p.enabled ? "enabled" : "disabled"}]`
458
- );
459
- addMessage({ role: "system", content: `Installed plugins:
460
- ${lines.join("\n")}` });
461
- }
462
- return { handled: true };
506
+ case void 0:
507
+ case "manage": {
508
+ return { handled: true, triggerPluginTUI: true };
463
509
  }
464
510
  case "install": {
465
511
  if (!subArgs) {
@@ -606,7 +652,7 @@ async function executeSlashCommand(cmd, args, session, addMessage, clearMessages
606
652
 
607
653
  // src/ui/hooks/useSlashCommands.ts
608
654
  var EXIT_DELAY_MS = 500;
609
- function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId, pluginCallbacks) {
655
+ function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId, pluginCallbacks, setShowPluginTUI) {
610
656
  return useCallback3(
611
657
  async (input) => {
612
658
  const parts = input.slice(1).split(/\s+/);
@@ -626,6 +672,9 @@ function useSlashCommands(session, addMessage, setMessages, exit, registry, pend
626
672
  pendingModelChangeRef.current = result.pendingModelId;
627
673
  setPendingModelId(result.pendingModelId);
628
674
  }
675
+ if (result.triggerPluginTUI) {
676
+ setShowPluginTUI?.(true);
677
+ }
629
678
  if (result.exitRequested) {
630
679
  setTimeout(() => exit(), EXIT_DELAY_MS);
631
680
  }
@@ -639,7 +688,8 @@ function useSlashCommands(session, addMessage, setMessages, exit, registry, pend
639
688
  registry,
640
689
  pendingModelChangeRef,
641
690
  setPendingModelId,
642
- pluginCallbacks
691
+ pluginCallbacks,
692
+ setShowPluginTUI
643
693
  ]
644
694
  );
645
695
  }
@@ -1027,22 +1077,7 @@ function createBuiltinCommands() {
1027
1077
  { name: "cost", description: "Show session info", source: "builtin" },
1028
1078
  { name: "context", description: "Context window info", source: "builtin" },
1029
1079
  { name: "permissions", description: "Permission rules", source: "builtin" },
1030
- {
1031
- name: "plugin",
1032
- description: "Manage plugins",
1033
- source: "builtin",
1034
- subcommands: [
1035
- { name: "install", description: "Install a plugin (name@marketplace)", source: "builtin" },
1036
- {
1037
- name: "uninstall",
1038
- description: "Uninstall a plugin (name@marketplace)",
1039
- source: "builtin"
1040
- },
1041
- { name: "enable", description: "Enable a plugin (name@marketplace)", source: "builtin" },
1042
- { name: "disable", description: "Disable a plugin (name@marketplace)", source: "builtin" },
1043
- { name: "marketplace", description: "Manage marketplace sources", source: "builtin" }
1044
- ]
1045
- },
1080
+ { name: "plugin", description: "Manage plugins", source: "builtin" },
1046
1081
  { name: "reload-plugins", description: "Reload all plugin resources", source: "builtin" },
1047
1082
  { name: "reset", description: "Delete settings and exit", source: "builtin" },
1048
1083
  { name: "exit", description: "Exit CLI", source: "builtin" }
@@ -1060,7 +1095,7 @@ var BuiltinCommandSource = class {
1060
1095
  };
1061
1096
 
1062
1097
  // src/commands/skill-source.ts
1063
- import { readdirSync, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
1098
+ import { readdirSync, readFileSync as readFileSync3, existsSync as existsSync2 } from "fs";
1064
1099
  import { join as join2, basename } from "path";
1065
1100
  import { homedir } from "os";
1066
1101
  var BOOLEAN_KEYS = /* @__PURE__ */ new Set(["disable-model-invocation", "user-invocable"]);
@@ -1116,7 +1151,7 @@ function scanSkillsDir(skillsDir) {
1116
1151
  if (!entry.isDirectory()) continue;
1117
1152
  const skillFile = join2(skillsDir, entry.name, "SKILL.md");
1118
1153
  if (!existsSync2(skillFile)) continue;
1119
- const content = readFileSync2(skillFile, "utf-8");
1154
+ const content = readFileSync3(skillFile, "utf-8");
1120
1155
  const frontmatter = parseFrontmatter(content);
1121
1156
  commands.push(buildCommand(frontmatter, content, entry.name));
1122
1157
  }
@@ -1129,7 +1164,7 @@ function scanCommandsDir(commandsDir) {
1129
1164
  for (const entry of entries) {
1130
1165
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
1131
1166
  const filePath = join2(commandsDir, entry.name);
1132
- const content = readFileSync2(filePath, "utf-8");
1167
+ const content = readFileSync3(filePath, "utf-8");
1133
1168
  const frontmatter = parseFrontmatter(content);
1134
1169
  const fallbackName = basename(entry.name, ".md");
1135
1170
  commands.push(buildCommand(frontmatter, content, fallbackName));
@@ -1314,18 +1349,50 @@ function usePluginCallbacks(cwd) {
1314
1349
  return {
1315
1350
  listInstalled: async () => {
1316
1351
  const plugins = await loader.loadAll();
1317
- return plugins.map((p) => ({
1318
- name: p.manifest.name,
1319
- description: p.manifest.description,
1320
- enabled: true
1352
+ const enabledMap = settingsStore.getEnabledPlugins();
1353
+ return plugins.map((p) => {
1354
+ const parts = p.pluginDir.split("/");
1355
+ const cacheIdx = parts.indexOf("cache");
1356
+ const marketplaceName = cacheIdx >= 0 ? parts[cacheIdx + 1] : "";
1357
+ const fullId = marketplaceName ? `${p.manifest.name}@${marketplaceName}` : p.manifest.name;
1358
+ return {
1359
+ name: fullId,
1360
+ description: p.manifest.description,
1361
+ enabled: enabledMap[fullId] !== false && enabledMap[p.manifest.name] !== false
1362
+ };
1363
+ });
1364
+ },
1365
+ listAvailablePlugins: async (marketplaceName) => {
1366
+ let manifest;
1367
+ try {
1368
+ manifest = marketplace.fetchManifest(marketplaceName);
1369
+ } catch {
1370
+ return [];
1371
+ }
1372
+ const installed = installer.getInstalledPlugins();
1373
+ const installedNames = new Set(Object.values(installed).map((r) => r.pluginName));
1374
+ return manifest.plugins.map((p) => ({
1375
+ name: p.name,
1376
+ description: p.description,
1377
+ installed: installedNames.has(p.name)
1321
1378
  }));
1322
1379
  },
1323
- install: async (pluginId) => {
1380
+ install: async (pluginId, scope) => {
1324
1381
  const [name, marketplaceName] = pluginId.split("@");
1325
1382
  if (!name || !marketplaceName) {
1326
1383
  throw new Error("Plugin ID must be in format: name@marketplace");
1327
1384
  }
1328
- await installer.install(name, marketplaceName);
1385
+ if (scope === "project") {
1386
+ const projectPluginsDir = join4(cwd, ".robota", "plugins");
1387
+ const projectInstaller = new BundlePluginInstaller({
1388
+ pluginsDir: projectPluginsDir,
1389
+ settingsStore,
1390
+ marketplaceClient: marketplace
1391
+ });
1392
+ await projectInstaller.install(name, marketplaceName);
1393
+ } else {
1394
+ await installer.install(name, marketplaceName);
1395
+ }
1329
1396
  },
1330
1397
  uninstall: async (pluginId) => {
1331
1398
  await installer.uninstall(pluginId);
@@ -1383,23 +1450,41 @@ function renderMarkdown(md) {
1383
1450
  // src/ui/DiffBlock.tsx
1384
1451
  import { Box, Text } from "ink";
1385
1452
  import { jsxs } from "react/jsx-runtime";
1386
- var MAX_DIFF_LINES = 10;
1387
- var TRUNCATED_SHOW = 8;
1453
+ var MAX_DIFF_LINES = 12;
1454
+ var TRUNCATED_SHOW = 10;
1388
1455
  function DiffBlock({ file, lines }) {
1389
1456
  const truncated = lines.length > MAX_DIFF_LINES;
1390
1457
  const visible = truncated ? lines.slice(0, TRUNCATED_SHOW) : lines;
1391
1458
  const remaining = lines.length - TRUNCATED_SHOW;
1459
+ const maxLineNum = Math.max(...visible.map((l) => l.lineNumber), 0);
1460
+ const numWidth = String(maxLineNum).length;
1392
1461
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginLeft: 4, children: [
1393
1462
  file && /* @__PURE__ */ jsxs(Text, { color: "white", dimColor: true, children: [
1394
1463
  "\u2502 ",
1395
1464
  file
1396
1465
  ] }),
1397
- visible.map((line, i) => /* @__PURE__ */ jsxs(Text, { color: line.type === "remove" ? "red" : "greenBright", children: [
1398
- "\u2502 ",
1399
- line.type === "remove" ? "-" : "+",
1400
- " ",
1401
- line.text
1402
- ] }, i)),
1466
+ visible.map((line, i) => {
1467
+ const lineNum = String(line.lineNumber).padStart(numWidth, " ");
1468
+ if (line.type === "context") {
1469
+ return /* @__PURE__ */ jsxs(Text, { color: "white", dimColor: true, children: [
1470
+ "\u2502 ",
1471
+ lineNum,
1472
+ " ",
1473
+ line.text
1474
+ ] }, i);
1475
+ }
1476
+ const prefix = line.type === "remove" ? "-" : "+";
1477
+ const bgColor = line.type === "remove" ? "#5c1a1a" : "#1a3d1a";
1478
+ const fgColor = line.type === "remove" ? "#ff9999" : "#99ff99";
1479
+ return /* @__PURE__ */ jsxs(Text, { color: fgColor, backgroundColor: bgColor, children: [
1480
+ "\u2502 ",
1481
+ lineNum,
1482
+ " ",
1483
+ prefix,
1484
+ " ",
1485
+ line.text
1486
+ ] }, i);
1487
+ }),
1403
1488
  truncated && /* @__PURE__ */ jsxs(Text, { color: "white", dimColor: true, children: [
1404
1489
  "\u2502 ... and ",
1405
1490
  remaining,
@@ -2052,8 +2137,459 @@ function StreamingIndicator({ text, activeTools }) {
2052
2137
  ] });
2053
2138
  }
2054
2139
 
2055
- // src/ui/App.tsx
2140
+ // src/ui/PluginTUI.tsx
2141
+ import { useState as useState9, useEffect as useEffect2, useCallback as useCallback9 } from "react";
2142
+
2143
+ // src/ui/MenuSelect.tsx
2144
+ import { useState as useState7, useCallback as useCallback7, useRef as useRef6 } from "react";
2145
+ import { Box as Box9, Text as Text11, useInput as useInput5 } from "ink";
2056
2146
  import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
2147
+ function MenuSelect({
2148
+ title,
2149
+ items,
2150
+ onSelect,
2151
+ onBack,
2152
+ loading,
2153
+ error
2154
+ }) {
2155
+ const [selected, setSelected] = useState7(0);
2156
+ const selectedRef = useRef6(0);
2157
+ const resolvedRef = useRef6(false);
2158
+ const doSelect = useCallback7(
2159
+ (index) => {
2160
+ if (resolvedRef.current || items.length === 0) return;
2161
+ resolvedRef.current = true;
2162
+ onSelect(items[index].value);
2163
+ },
2164
+ [items, onSelect]
2165
+ );
2166
+ useInput5((input, key) => {
2167
+ if (resolvedRef.current) return;
2168
+ if (key.escape) {
2169
+ resolvedRef.current = true;
2170
+ onBack();
2171
+ return;
2172
+ }
2173
+ if (loading || error || items.length === 0) return;
2174
+ if (key.upArrow) {
2175
+ const next = selectedRef.current > 0 ? selectedRef.current - 1 : selectedRef.current;
2176
+ selectedRef.current = next;
2177
+ setSelected(next);
2178
+ } else if (key.downArrow) {
2179
+ const next = selectedRef.current < items.length - 1 ? selectedRef.current + 1 : selectedRef.current;
2180
+ selectedRef.current = next;
2181
+ setSelected(next);
2182
+ } else if (key.return) {
2183
+ doSelect(selectedRef.current);
2184
+ }
2185
+ });
2186
+ return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
2187
+ /* @__PURE__ */ jsx10(Text11, { color: "yellow", bold: true, children: title }),
2188
+ loading && /* @__PURE__ */ jsx10(Box9, { marginTop: 1, children: /* @__PURE__ */ jsx10(Text11, { dimColor: true, children: "Loading..." }) }),
2189
+ error && /* @__PURE__ */ jsxs9(Box9, { marginTop: 1, flexDirection: "column", children: [
2190
+ /* @__PURE__ */ jsx10(Text11, { color: "red", children: error }),
2191
+ /* @__PURE__ */ jsx10(Text11, { dimColor: true, children: "Press Esc to go back" })
2192
+ ] }),
2193
+ !loading && !error && /* @__PURE__ */ jsx10(Box9, { flexDirection: "column", marginTop: 1, children: items.map((item, i) => /* @__PURE__ */ jsxs9(Box9, { children: [
2194
+ /* @__PURE__ */ jsxs9(Text11, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
2195
+ i === selected ? "> " : " ",
2196
+ item.label
2197
+ ] }),
2198
+ item.hint && /* @__PURE__ */ jsxs9(Text11, { dimColor: true, children: [
2199
+ " ",
2200
+ item.hint
2201
+ ] })
2202
+ ] }, item.value)) }),
2203
+ /* @__PURE__ */ jsx10(Text11, { dimColor: true, children: loading || error ? "" : " \u2191\u2193 Navigate Enter Select Esc Back" })
2204
+ ] });
2205
+ }
2206
+
2207
+ // src/ui/TextPrompt.tsx
2208
+ import { useState as useState8, useRef as useRef7, useCallback as useCallback8 } from "react";
2209
+ import { Box as Box10, Text as Text12, useInput as useInput6 } from "ink";
2210
+ import { jsx as jsx11, jsxs as jsxs10 } from "react/jsx-runtime";
2211
+ function TextPrompt({
2212
+ title,
2213
+ placeholder,
2214
+ onSubmit,
2215
+ onCancel,
2216
+ validate
2217
+ }) {
2218
+ const [value, setValue] = useState8("");
2219
+ const [error, setError] = useState8();
2220
+ const resolvedRef = useRef7(false);
2221
+ const valueRef = useRef7("");
2222
+ const handleSubmit = useCallback8(() => {
2223
+ if (resolvedRef.current) return;
2224
+ const trimmed = valueRef.current.trim();
2225
+ if (!trimmed) return;
2226
+ if (validate) {
2227
+ const err = validate(trimmed);
2228
+ if (err) {
2229
+ setError(err);
2230
+ return;
2231
+ }
2232
+ }
2233
+ resolvedRef.current = true;
2234
+ onSubmit(trimmed);
2235
+ }, [validate, onSubmit]);
2236
+ useInput6((input, key) => {
2237
+ if (resolvedRef.current) return;
2238
+ if (key.escape) {
2239
+ resolvedRef.current = true;
2240
+ onCancel();
2241
+ return;
2242
+ }
2243
+ if (key.return) {
2244
+ handleSubmit();
2245
+ return;
2246
+ }
2247
+ if (key.backspace || key.delete) {
2248
+ valueRef.current = valueRef.current.slice(0, -1);
2249
+ setValue(valueRef.current);
2250
+ setError(void 0);
2251
+ return;
2252
+ }
2253
+ if (input && !key.ctrl && !key.meta) {
2254
+ valueRef.current = valueRef.current + input;
2255
+ setValue(valueRef.current);
2256
+ setError(void 0);
2257
+ }
2258
+ });
2259
+ return /* @__PURE__ */ jsxs10(Box10, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
2260
+ /* @__PURE__ */ jsx11(Text12, { color: "yellow", bold: true, children: title }),
2261
+ /* @__PURE__ */ jsxs10(Box10, { marginTop: 1, children: [
2262
+ /* @__PURE__ */ jsx11(Text12, { color: "cyan", children: "> " }),
2263
+ value ? /* @__PURE__ */ jsx11(Text12, { children: value }) : placeholder ? /* @__PURE__ */ jsx11(Text12, { dimColor: true, children: placeholder }) : null,
2264
+ /* @__PURE__ */ jsx11(Text12, { color: "cyan", children: "\u2588" })
2265
+ ] }),
2266
+ error && /* @__PURE__ */ jsx11(Text12, { color: "red", children: error }),
2267
+ /* @__PURE__ */ jsx11(Text12, { dimColor: true, children: " Enter Submit Esc Cancel" })
2268
+ ] });
2269
+ }
2270
+
2271
+ // src/ui/plugin-tui-handlers.ts
2272
+ function handleMainSelect(value, nav) {
2273
+ if (value === "marketplace") {
2274
+ nav.push({ screen: "marketplace-list" });
2275
+ } else if (value === "installed") {
2276
+ nav.push({ screen: "installed-list" });
2277
+ }
2278
+ }
2279
+ function handleMarketplaceListSelect(value, nav) {
2280
+ if (value === "__add__") {
2281
+ nav.push({ screen: "marketplace-add" });
2282
+ } else {
2283
+ nav.push({ screen: "marketplace-action", context: { marketplace: value } });
2284
+ }
2285
+ }
2286
+ function handleMarketplaceActionSelect(value, marketplace, callbacks, nav) {
2287
+ if (value === "browse") {
2288
+ nav.push({ screen: "marketplace-browse", context: { marketplace } });
2289
+ } else if (value === "update") {
2290
+ callbacks.marketplaceUpdate(marketplace).then(() => {
2291
+ nav.notify(`Updated marketplace "${marketplace}".`);
2292
+ nav.pop();
2293
+ }).catch((err) => {
2294
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
2295
+ });
2296
+ } else if (value === "remove") {
2297
+ nav.setConfirm({
2298
+ message: `Remove marketplace "${marketplace}" and all its plugins?`,
2299
+ onConfirm: () => {
2300
+ nav.setConfirm(void 0);
2301
+ callbacks.marketplaceRemove(marketplace).then(() => {
2302
+ nav.notify(`Removed marketplace "${marketplace}".`);
2303
+ nav.popN(2);
2304
+ }).catch((err) => {
2305
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
2306
+ });
2307
+ },
2308
+ onCancel: () => nav.setConfirm(void 0)
2309
+ });
2310
+ }
2311
+ }
2312
+ function handleMarketplaceBrowseSelect(value, marketplace, items, nav) {
2313
+ const fullId = `${value}@${marketplace}`;
2314
+ const item = items.find((i) => i.value === value);
2315
+ if (item?.hint === "installed") {
2316
+ nav.push({ screen: "installed-action", context: { pluginId: fullId } });
2317
+ } else {
2318
+ nav.push({ screen: "marketplace-install-scope", context: { marketplace, pluginId: fullId } });
2319
+ }
2320
+ }
2321
+ function handleInstallScopeSelect(value, pluginId, callbacks, nav) {
2322
+ const scope = value;
2323
+ callbacks.install(pluginId, scope).then(() => {
2324
+ nav.notify(`Installed plugin "${pluginId}" (${scope} scope).`);
2325
+ nav.popN(2);
2326
+ }).catch((err) => {
2327
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
2328
+ });
2329
+ }
2330
+ function handleInstalledListSelect(value, callbacks, nav) {
2331
+ nav.setConfirm({
2332
+ message: `Uninstall plugin "${value}"?`,
2333
+ onConfirm: () => {
2334
+ nav.setConfirm(void 0);
2335
+ callbacks.uninstall(value).then(() => {
2336
+ nav.notify(`Uninstalled plugin "${value}".`);
2337
+ nav.refresh();
2338
+ }).catch((err) => {
2339
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
2340
+ });
2341
+ },
2342
+ onCancel: () => nav.setConfirm(void 0)
2343
+ });
2344
+ }
2345
+ function handleInstalledActionSelect(value, pluginId, callbacks, nav) {
2346
+ if (value === "uninstall") {
2347
+ nav.setConfirm({
2348
+ message: `Uninstall plugin "${pluginId}"?`,
2349
+ onConfirm: () => {
2350
+ nav.setConfirm(void 0);
2351
+ callbacks.uninstall(pluginId).then(() => {
2352
+ nav.notify(`Uninstalled plugin "${pluginId}".`);
2353
+ nav.popN(2);
2354
+ }).catch((err) => {
2355
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
2356
+ });
2357
+ },
2358
+ onCancel: () => nav.setConfirm(void 0)
2359
+ });
2360
+ }
2361
+ }
2362
+
2363
+ // src/ui/PluginTUI.tsx
2364
+ import { jsx as jsx12 } from "react/jsx-runtime";
2365
+ function PluginTUI({ callbacks, onClose, addMessage }) {
2366
+ const [stack, setStack] = useState9([{ screen: "main" }]);
2367
+ const [items, setItems] = useState9([]);
2368
+ const [loading, setLoading] = useState9(false);
2369
+ const [error, setError] = useState9();
2370
+ const [confirm, setConfirm] = useState9();
2371
+ const [refreshCounter, setRefreshCounter] = useState9(0);
2372
+ const current = stack[stack.length - 1] ?? { screen: "main" };
2373
+ const push = useCallback9((state) => {
2374
+ setStack((prev) => [...prev, state]);
2375
+ setItems([]);
2376
+ setError(void 0);
2377
+ }, []);
2378
+ const pop = useCallback9(() => {
2379
+ setStack((prev) => {
2380
+ if (prev.length <= 1) {
2381
+ onClose();
2382
+ return prev;
2383
+ }
2384
+ return prev.slice(0, -1);
2385
+ });
2386
+ setItems([]);
2387
+ setError(void 0);
2388
+ }, [onClose]);
2389
+ const popN = useCallback9(
2390
+ (n) => {
2391
+ setStack((prev) => {
2392
+ const next = prev.slice(0, Math.max(1, prev.length - n));
2393
+ if (next.length === 0) {
2394
+ onClose();
2395
+ return prev;
2396
+ }
2397
+ return next;
2398
+ });
2399
+ setItems([]);
2400
+ setError(void 0);
2401
+ },
2402
+ [onClose]
2403
+ );
2404
+ const notify = useCallback9(
2405
+ (content) => {
2406
+ addMessage?.({ role: "system", content });
2407
+ },
2408
+ [addMessage]
2409
+ );
2410
+ const refresh = useCallback9(() => {
2411
+ setItems([]);
2412
+ setRefreshCounter((c) => c + 1);
2413
+ }, []);
2414
+ const nav = { push, pop, popN, notify, setConfirm, refresh };
2415
+ useEffect2(() => {
2416
+ const screen2 = current.screen;
2417
+ if (screen2 === "marketplace-list") {
2418
+ setLoading(true);
2419
+ callbacks.marketplaceList().then((sources) => {
2420
+ const baseItems = [{ label: "Add Marketplace", value: "__add__" }];
2421
+ const sourceItems = sources.map((s) => ({
2422
+ label: s.name,
2423
+ value: s.name,
2424
+ hint: s.type
2425
+ }));
2426
+ setItems([...baseItems, ...sourceItems]);
2427
+ setLoading(false);
2428
+ }).catch((err) => {
2429
+ setError(err instanceof Error ? err.message : String(err));
2430
+ setLoading(false);
2431
+ });
2432
+ } else if (screen2 === "marketplace-browse") {
2433
+ const marketplace = current.context?.marketplace ?? "";
2434
+ setLoading(true);
2435
+ callbacks.listAvailablePlugins(marketplace).then((plugins) => {
2436
+ setItems(
2437
+ plugins.map((p) => ({
2438
+ label: p.name,
2439
+ value: p.name,
2440
+ hint: p.installed ? "installed" : p.description
2441
+ }))
2442
+ );
2443
+ setLoading(false);
2444
+ }).catch((err) => {
2445
+ setError(err instanceof Error ? err.message : String(err));
2446
+ setLoading(false);
2447
+ });
2448
+ } else if (screen2 === "installed-list") {
2449
+ setLoading(true);
2450
+ callbacks.listInstalled().then((plugins) => {
2451
+ setItems(
2452
+ plugins.map((p) => ({
2453
+ label: p.name,
2454
+ value: p.name,
2455
+ hint: p.description
2456
+ }))
2457
+ );
2458
+ setLoading(false);
2459
+ }).catch((err) => {
2460
+ setError(err instanceof Error ? err.message : String(err));
2461
+ setLoading(false);
2462
+ });
2463
+ }
2464
+ }, [stack.length, current.screen, current.context?.marketplace, callbacks, refreshCounter]);
2465
+ const handleSelect = useCallback9(
2466
+ (value) => {
2467
+ const screen2 = current.screen;
2468
+ const ctx = current.context;
2469
+ if (screen2 === "main") handleMainSelect(value, nav);
2470
+ else if (screen2 === "marketplace-list") handleMarketplaceListSelect(value, nav);
2471
+ else if (screen2 === "marketplace-action")
2472
+ handleMarketplaceActionSelect(value, ctx?.marketplace ?? "", callbacks, nav);
2473
+ else if (screen2 === "marketplace-browse")
2474
+ handleMarketplaceBrowseSelect(value, ctx?.marketplace ?? "", items, nav);
2475
+ else if (screen2 === "marketplace-install-scope")
2476
+ handleInstallScopeSelect(value, ctx?.pluginId ?? "", callbacks, nav);
2477
+ else if (screen2 === "installed-list") handleInstalledListSelect(value, callbacks, nav);
2478
+ else if (screen2 === "installed-action")
2479
+ handleInstalledActionSelect(value, ctx?.pluginId ?? "", callbacks, nav);
2480
+ },
2481
+ [current, items, callbacks, push, pop, popN, notify, setConfirm, refresh]
2482
+ );
2483
+ const handleTextSubmit = useCallback9(
2484
+ (value) => {
2485
+ if (current.screen === "marketplace-add") {
2486
+ callbacks.marketplaceAdd(value).then((name) => {
2487
+ notify(`Added marketplace "${name}" from ${value}.`);
2488
+ pop();
2489
+ }).catch((err) => {
2490
+ notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
2491
+ pop();
2492
+ });
2493
+ }
2494
+ },
2495
+ [current.screen, callbacks, notify, pop]
2496
+ );
2497
+ if (confirm) {
2498
+ return /* @__PURE__ */ jsx12(
2499
+ ConfirmPrompt,
2500
+ {
2501
+ message: confirm.message,
2502
+ onSelect: (index) => {
2503
+ if (index === 0) confirm.onConfirm();
2504
+ else confirm.onCancel();
2505
+ }
2506
+ }
2507
+ );
2508
+ }
2509
+ const screen = current.screen;
2510
+ if (screen === "marketplace-add") {
2511
+ return /* @__PURE__ */ jsx12(
2512
+ TextPrompt,
2513
+ {
2514
+ title: "Add Marketplace Source",
2515
+ placeholder: "owner/repo or git URL",
2516
+ onSubmit: handleTextSubmit,
2517
+ onCancel: pop,
2518
+ validate: (v) => !v.includes("/") ? "Must be owner/repo or a git URL" : void 0
2519
+ }
2520
+ );
2521
+ }
2522
+ if (screen === "marketplace-action") {
2523
+ return /* @__PURE__ */ jsx12(
2524
+ MenuSelect,
2525
+ {
2526
+ title: `Marketplace: ${current.context?.marketplace ?? ""}`,
2527
+ items: [
2528
+ { label: "Browse plugins", value: "browse" },
2529
+ { label: "Update", value: "update" },
2530
+ { label: "Remove", value: "remove" }
2531
+ ],
2532
+ onSelect: handleSelect,
2533
+ onBack: pop
2534
+ },
2535
+ stack.length
2536
+ );
2537
+ }
2538
+ if (screen === "marketplace-install-scope") {
2539
+ return /* @__PURE__ */ jsx12(
2540
+ MenuSelect,
2541
+ {
2542
+ title: `Install scope for "${current.context?.pluginId ?? ""}"`,
2543
+ items: [
2544
+ { label: "User scope", value: "user" },
2545
+ { label: "Project scope", value: "project" }
2546
+ ],
2547
+ onSelect: handleSelect,
2548
+ onBack: pop
2549
+ },
2550
+ stack.length
2551
+ );
2552
+ }
2553
+ if (screen === "installed-action") {
2554
+ return /* @__PURE__ */ jsx12(
2555
+ MenuSelect,
2556
+ {
2557
+ title: `Plugin: ${current.context?.pluginId ?? ""}`,
2558
+ items: [{ label: "Uninstall", value: "uninstall" }],
2559
+ onSelect: handleSelect,
2560
+ onBack: pop
2561
+ },
2562
+ stack.length
2563
+ );
2564
+ }
2565
+ const titleMap = {
2566
+ main: "Plugin Management",
2567
+ "marketplace-list": "Marketplace",
2568
+ "marketplace-browse": `Browse: ${current.context?.marketplace ?? ""}`,
2569
+ "installed-list": "Installed Plugins"
2570
+ };
2571
+ const staticItemsMap = {
2572
+ main: [
2573
+ { label: "Marketplace", value: "marketplace" },
2574
+ { label: "Installed Plugins", value: "installed" }
2575
+ ]
2576
+ };
2577
+ return /* @__PURE__ */ jsx12(
2578
+ MenuSelect,
2579
+ {
2580
+ title: titleMap[screen] ?? "Plugin Management",
2581
+ items: staticItemsMap[screen] ?? items,
2582
+ onSelect: handleSelect,
2583
+ onBack: pop,
2584
+ loading,
2585
+ error
2586
+ },
2587
+ `${screen}-${stack.length}-${refreshCounter}`
2588
+ );
2589
+ }
2590
+
2591
+ // src/ui/App.tsx
2592
+ import { jsx as jsx13, jsxs as jsxs11 } from "react/jsx-runtime";
2057
2593
  var EXIT_DELAY_MS2 = 500;
2058
2594
  function mergeHooksIntoConfig(configHooks, pluginHooks) {
2059
2595
  const pluginKeys = Object.keys(pluginHooks);
@@ -2085,15 +2621,16 @@ function App(props) {
2085
2621
  { ...props, config: configWithPluginHooks }
2086
2622
  );
2087
2623
  const { messages, setMessages, addMessage } = useMessages();
2088
- const [isThinking, setIsThinking] = useState7(false);
2624
+ const [isThinking, setIsThinking] = useState10(false);
2089
2625
  const initialCtx = session.getContextState();
2090
- const [contextState, setContextState] = useState7({
2626
+ const [contextState, setContextState] = useState10({
2091
2627
  percentage: initialCtx.usedPercentage,
2092
2628
  usedTokens: initialCtx.usedTokens,
2093
2629
  maxTokens: initialCtx.maxTokens
2094
2630
  });
2095
- const pendingModelChangeRef = useRef6(null);
2096
- const [pendingModelId, setPendingModelId] = useState7(null);
2631
+ const pendingModelChangeRef = useRef8(null);
2632
+ const [pendingModelId, setPendingModelId] = useState10(null);
2633
+ const [showPluginTUI, setShowPluginTUI] = useState10(false);
2097
2634
  const pluginCallbacks = usePluginCallbacks(props.cwd ?? process.cwd());
2098
2635
  const handleSlashCommand = useSlashCommands(
2099
2636
  session,
@@ -2103,7 +2640,8 @@ function App(props) {
2103
2640
  registry,
2104
2641
  pendingModelChangeRef,
2105
2642
  setPendingModelId,
2106
- pluginCallbacks
2643
+ pluginCallbacks,
2644
+ setShowPluginTUI
2107
2645
  );
2108
2646
  const handleSubmit = useSubmitHandler(
2109
2647
  session,
@@ -2114,33 +2652,33 @@ function App(props) {
2114
2652
  setContextState,
2115
2653
  registry
2116
2654
  );
2117
- useInput5(
2655
+ useInput7(
2118
2656
  (_input, key) => {
2119
2657
  if (key.ctrl && _input === "c") exit();
2120
2658
  if (key.escape && isThinking) session.abort();
2121
2659
  },
2122
- { isActive: !permissionRequest }
2660
+ { isActive: !permissionRequest && !showPluginTUI }
2123
2661
  );
2124
- return /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", children: [
2125
- /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
2126
- /* @__PURE__ */ jsx10(Text11, { color: "cyan", bold: true, children: `
2662
+ return /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", children: [
2663
+ /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
2664
+ /* @__PURE__ */ jsx13(Text13, { color: "cyan", bold: true, children: `
2127
2665
  ____ ___ ____ ___ _____ _
2128
2666
  | _ \\ / _ \\| __ ) / _ \\_ _|/ \\
2129
2667
  | |_) | | | | _ \\| | | || | / _ \\
2130
2668
  | _ <| |_| | |_) | |_| || |/ ___ \\
2131
2669
  |_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
2132
2670
  ` }),
2133
- /* @__PURE__ */ jsxs9(Text11, { dimColor: true, children: [
2671
+ /* @__PURE__ */ jsxs11(Text13, { dimColor: true, children: [
2134
2672
  " v",
2135
2673
  props.version ?? "0.0.0"
2136
2674
  ] })
2137
2675
  ] }),
2138
- /* @__PURE__ */ jsxs9(Box9, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
2139
- /* @__PURE__ */ jsx10(MessageList, { messages }),
2140
- isThinking && /* @__PURE__ */ jsx10(Box9, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx10(StreamingIndicator, { text: streamingText, activeTools }) })
2676
+ /* @__PURE__ */ jsxs11(Box11, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
2677
+ /* @__PURE__ */ jsx13(MessageList, { messages }),
2678
+ isThinking && /* @__PURE__ */ jsx13(Box11, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx13(StreamingIndicator, { text: streamingText, activeTools }) })
2141
2679
  ] }),
2142
- permissionRequest && /* @__PURE__ */ jsx10(PermissionPrompt, { request: permissionRequest }),
2143
- pendingModelId && /* @__PURE__ */ jsx10(
2680
+ permissionRequest && /* @__PURE__ */ jsx13(PermissionPrompt, { request: permissionRequest }),
2681
+ pendingModelId && /* @__PURE__ */ jsx13(
2144
2682
  ConfirmPrompt,
2145
2683
  {
2146
2684
  message: `Change model to ${getModelName(pendingModelId)}? This will restart the session.`,
@@ -2168,7 +2706,15 @@ function App(props) {
2168
2706
  }
2169
2707
  }
2170
2708
  ),
2171
- /* @__PURE__ */ jsx10(
2709
+ showPluginTUI && /* @__PURE__ */ jsx13(
2710
+ PluginTUI,
2711
+ {
2712
+ callbacks: pluginCallbacks,
2713
+ onClose: () => setShowPluginTUI(false),
2714
+ addMessage: (msg) => addMessage(msg)
2715
+ }
2716
+ ),
2717
+ /* @__PURE__ */ jsx13(
2172
2718
  StatusBar,
2173
2719
  {
2174
2720
  permissionMode: session.getPermissionMode(),
@@ -2181,20 +2727,20 @@ function App(props) {
2181
2727
  contextMaxTokens: contextState.maxTokens
2182
2728
  }
2183
2729
  ),
2184
- /* @__PURE__ */ jsx10(
2730
+ /* @__PURE__ */ jsx13(
2185
2731
  InputArea,
2186
2732
  {
2187
2733
  onSubmit: handleSubmit,
2188
- isDisabled: isThinking || !!permissionRequest,
2734
+ isDisabled: isThinking || !!permissionRequest || showPluginTUI,
2189
2735
  registry
2190
2736
  }
2191
2737
  ),
2192
- /* @__PURE__ */ jsx10(Text11, { children: " " })
2738
+ /* @__PURE__ */ jsx13(Text13, { children: " " })
2193
2739
  ] });
2194
2740
  }
2195
2741
 
2196
2742
  // src/ui/render.tsx
2197
- import { jsx as jsx11 } from "react/jsx-runtime";
2743
+ import { jsx as jsx14 } from "react/jsx-runtime";
2198
2744
  function renderApp(options) {
2199
2745
  process.on("unhandledRejection", (reason) => {
2200
2746
  process.stderr.write(`
@@ -2205,7 +2751,7 @@ function renderApp(options) {
2205
2751
  `);
2206
2752
  }
2207
2753
  });
2208
- const instance = render(/* @__PURE__ */ jsx11(App, { ...options }), {
2754
+ const instance = render(/* @__PURE__ */ jsx14(App, { ...options }), {
2209
2755
  exitOnCtrlC: true
2210
2756
  });
2211
2757
  instance.waitUntilExit().catch((err) => {
@@ -2221,7 +2767,7 @@ function renderApp(options) {
2221
2767
  function checkSettingsFile(filePath) {
2222
2768
  if (!existsSync3(filePath)) return "missing";
2223
2769
  try {
2224
- const raw = readFileSync3(filePath, "utf8").trim();
2770
+ const raw = readFileSync4(filePath, "utf8").trim();
2225
2771
  if (raw.length === 0) return "incomplete";
2226
2772
  const parsed = JSON.parse(raw);
2227
2773
  const provider = parsed.provider;
@@ -2238,7 +2784,7 @@ function readVersion() {
2238
2784
  const candidates = [join5(dir, "..", "..", "package.json"), join5(dir, "..", "package.json")];
2239
2785
  for (const pkgPath of candidates) {
2240
2786
  try {
2241
- const raw = readFileSync3(pkgPath, "utf-8");
2787
+ const raw = readFileSync4(pkgPath, "utf-8");
2242
2788
  const pkg = JSON.parse(raw);
2243
2789
  if (pkg.version !== void 0 && pkg.name !== void 0) {
2244
2790
  return pkg.version;