@robota-sdk/agent-cli 3.0.0-beta.4 → 3.0.0-beta.41

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.
@@ -30,192 +30,680 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
30
30
  // src/index.ts
31
31
  var index_exports = {};
32
32
  __export(index_exports, {
33
- Session: () => import_agent_sdk3.Session,
34
- SessionStore: () => import_agent_sdk3.SessionStore,
35
- TRUST_TO_MODE: () => import_agent_sdk3.TRUST_TO_MODE,
36
- query: () => import_agent_sdk3.query,
33
+ Session: () => import_agent_sdk5.Session,
34
+ SessionStore: () => import_agent_sdk5.SessionStore,
35
+ TRUST_TO_MODE: () => import_agent_sdk5.TRUST_TO_MODE,
36
+ query: () => import_agent_sdk5.query,
37
37
  startCli: () => startCli
38
38
  });
39
39
  module.exports = __toCommonJS(index_exports);
40
- var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
40
+ var import_agent_sdk5 = require("@robota-sdk/agent-sdk");
41
41
 
42
42
  // src/cli.ts
43
- var import_node_util = require("util");
44
43
  var import_node_fs2 = require("fs");
45
- var import_node_path2 = require("path");
44
+ var import_node_path5 = require("path");
46
45
  var import_node_url = require("url");
47
- var readline = __toESM(require("readline"), 1);
48
- var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
46
+ var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
47
+ var import_agent_sdk4 = require("@robota-sdk/agent-sdk");
49
48
 
50
- // src/permissions/permission-prompt.ts
51
- var import_chalk = __toESM(require("chalk"), 1);
52
- var PERMISSION_OPTIONS = ["Allow", "Deny"];
53
- var ALLOW_INDEX = 0;
54
- function formatArgs(toolArgs) {
55
- const entries = Object.entries(toolArgs);
56
- if (entries.length === 0) {
57
- return "(no arguments)";
49
+ // src/utils/cli-args.ts
50
+ var import_node_util = require("util");
51
+ var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
52
+ function parsePermissionMode(raw) {
53
+ if (raw === void 0) return void 0;
54
+ if (!VALID_MODES.includes(raw)) {
55
+ process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
56
+ `);
57
+ process.exit(1);
58
58
  }
59
- return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
59
+ return raw;
60
60
  }
61
- async function promptForApproval(terminal, toolName, toolArgs) {
62
- terminal.writeLine("");
63
- terminal.writeLine(import_chalk.default.yellow(`[Permission Required] Tool: ${toolName}`));
64
- terminal.writeLine(import_chalk.default.dim(` ${formatArgs(toolArgs)}`));
65
- terminal.writeLine("");
66
- const selected = await terminal.select(PERMISSION_OPTIONS, ALLOW_INDEX);
67
- return selected === ALLOW_INDEX;
61
+ function parseMaxTurns(raw) {
62
+ if (raw === void 0) return void 0;
63
+ const n = parseInt(raw, 10);
64
+ if (isNaN(n) || n <= 0) {
65
+ process.stderr.write(`Invalid --max-turns "${raw}". Must be a positive integer.
66
+ `);
67
+ process.exit(1);
68
+ }
69
+ return n;
70
+ }
71
+ function parseCliArgs() {
72
+ const { values, positionals } = (0, import_node_util.parseArgs)({
73
+ allowPositionals: true,
74
+ options: {
75
+ p: { type: "boolean", short: "p", default: false },
76
+ c: { type: "boolean", short: "c", default: false },
77
+ r: { type: "string", short: "r" },
78
+ model: { type: "string" },
79
+ language: { type: "string" },
80
+ "permission-mode": { type: "string" },
81
+ "max-turns": { type: "string" },
82
+ version: { type: "boolean", default: false },
83
+ reset: { type: "boolean", default: false }
84
+ }
85
+ });
86
+ return {
87
+ positional: positionals,
88
+ printMode: values["p"] ?? false,
89
+ continueMode: values["c"] ?? false,
90
+ resumeId: values["r"],
91
+ model: values["model"],
92
+ language: values["language"],
93
+ permissionMode: parsePermissionMode(values["permission-mode"]),
94
+ maxTurns: parseMaxTurns(values["max-turns"]),
95
+ version: values["version"] ?? false,
96
+ reset: values["reset"] ?? false
97
+ };
68
98
  }
69
99
 
100
+ // src/utils/settings-io.ts
101
+ var import_node_fs = require("fs");
102
+ var import_node_path = require("path");
103
+ function getUserSettingsPath() {
104
+ const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
105
+ return (0, import_node_path.join)(home, ".robota", "settings.json");
106
+ }
107
+ function readSettings(path) {
108
+ if (!(0, import_node_fs.existsSync)(path)) return {};
109
+ return JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
110
+ }
111
+ function writeSettings(path, settings) {
112
+ (0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
113
+ (0, import_node_fs.writeFileSync)(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
114
+ }
115
+ function updateModelInSettings(settingsPath, modelId) {
116
+ const settings = readSettings(settingsPath);
117
+ const provider = settings.provider ?? {};
118
+ provider.model = modelId;
119
+ settings.provider = provider;
120
+ writeSettings(settingsPath, settings);
121
+ }
122
+ function deleteSettings(path) {
123
+ if ((0, import_node_fs.existsSync)(path)) {
124
+ (0, import_node_fs.unlinkSync)(path);
125
+ return true;
126
+ }
127
+ return false;
128
+ }
129
+
130
+ // src/print-terminal.ts
131
+ var readline = __toESM(require("readline"), 1);
132
+ var PrintTerminal = class {
133
+ write(text) {
134
+ process.stdout.write(text);
135
+ }
136
+ writeLine(text) {
137
+ process.stdout.write(text + "\n");
138
+ }
139
+ writeMarkdown(md) {
140
+ process.stdout.write(md);
141
+ }
142
+ writeError(text) {
143
+ process.stderr.write(text + "\n");
144
+ }
145
+ prompt(question) {
146
+ return new Promise((resolve) => {
147
+ const rl = readline.createInterface({
148
+ input: process.stdin,
149
+ output: process.stdout,
150
+ terminal: false,
151
+ historySize: 0
152
+ });
153
+ rl.question(question, (answer) => {
154
+ rl.close();
155
+ resolve(answer);
156
+ });
157
+ });
158
+ }
159
+ async select(options, initialIndex = 0) {
160
+ for (let i = 0; i < options.length; i++) {
161
+ const marker = i === initialIndex ? ">" : " ";
162
+ process.stdout.write(` ${marker} ${i + 1}) ${options[i]}
163
+ `);
164
+ }
165
+ const answer = await this.prompt(
166
+ ` Choose [1-${options.length}] (default: ${options[initialIndex]}): `
167
+ );
168
+ const trimmed = answer.trim().toLowerCase();
169
+ if (trimmed === "") return initialIndex;
170
+ const num = parseInt(trimmed, 10);
171
+ if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
172
+ return initialIndex;
173
+ }
174
+ spinner(_message) {
175
+ return { stop() {
176
+ }, update() {
177
+ } };
178
+ }
179
+ };
180
+
70
181
  // src/ui/render.tsx
71
- var import_ink9 = require("ink");
182
+ var import_ink14 = require("ink");
72
183
 
73
184
  // src/ui/App.tsx
74
- var import_react5 = require("react");
75
- var import_ink8 = require("ink");
185
+ var import_react12 = require("react");
186
+ var import_ink13 = require("ink");
187
+ var import_agent_core4 = require("@robota-sdk/agent-core");
188
+
189
+ // src/ui/hooks/useInteractiveSession.ts
190
+ var import_react = require("react");
191
+ var import_node_os = require("os");
192
+ var import_node_path3 = require("path");
76
193
  var import_agent_sdk = require("@robota-sdk/agent-sdk");
194
+ var import_agent_core = require("@robota-sdk/agent-core");
77
195
 
78
- // src/commands/command-registry.ts
79
- var CommandRegistry = class {
80
- sources = [];
81
- addSource(source) {
82
- this.sources.push(source);
83
- }
84
- /** Get all commands, optionally filtered by prefix */
85
- getCommands(filter) {
86
- const all = [];
87
- for (const source of this.sources) {
88
- all.push(...source.getCommands());
89
- }
90
- if (!filter) return all;
91
- const lower = filter.toLowerCase();
92
- return all.filter((cmd) => cmd.name.toLowerCase().startsWith(lower));
93
- }
94
- /** Get subcommands for a specific command */
95
- getSubcommands(commandName) {
96
- const lower = commandName.toLowerCase();
97
- for (const source of this.sources) {
98
- for (const cmd of source.getCommands()) {
99
- if (cmd.name.toLowerCase() === lower && cmd.subcommands) {
100
- return cmd.subcommands;
101
- }
196
+ // src/commands/plugin-source.ts
197
+ var PluginCommandSource = class {
198
+ name = "plugin";
199
+ plugins;
200
+ constructor(plugins) {
201
+ this.plugins = plugins;
202
+ }
203
+ getCommands() {
204
+ const commands = [];
205
+ for (const plugin of this.plugins) {
206
+ for (const skill of plugin.skills) {
207
+ const baseName = skill.name.includes("@") ? skill.name.split("@")[0] : skill.name;
208
+ commands.push({
209
+ name: baseName,
210
+ description: `(${plugin.manifest.name}) ${skill.description}`,
211
+ source: "plugin",
212
+ skillContent: skill.skillContent,
213
+ pluginDir: plugin.pluginDir
214
+ });
215
+ }
216
+ for (const cmd of plugin.commands) {
217
+ commands.push({
218
+ name: cmd.name,
219
+ description: cmd.description,
220
+ source: "plugin",
221
+ skillContent: cmd.skillContent,
222
+ pluginDir: plugin.pluginDir
223
+ });
102
224
  }
103
225
  }
104
- return [];
226
+ return commands;
105
227
  }
106
228
  };
107
229
 
108
- // src/commands/builtin-source.ts
109
- function createBuiltinCommands() {
110
- return [
111
- { name: "help", description: "Show available commands", source: "builtin" },
112
- { name: "clear", description: "Clear conversation history", source: "builtin" },
113
- {
114
- name: "mode",
115
- description: "Permission mode",
116
- source: "builtin",
117
- subcommands: [
118
- { name: "plan", description: "Plan only, no execution", source: "builtin" },
119
- { name: "default", description: "Ask before risky actions", source: "builtin" },
120
- { name: "acceptEdits", description: "Auto-approve file edits", source: "builtin" },
121
- { name: "bypassPermissions", description: "Skip all permission checks", source: "builtin" }
122
- ]
123
- },
124
- {
125
- name: "model",
126
- description: "Select AI model",
127
- source: "builtin",
128
- subcommands: [
129
- { name: "claude-opus-4-6", description: "Opus 4.6 (highest quality)", source: "builtin" },
130
- { name: "claude-sonnet-4-6", description: "Sonnet 4.6 (balanced)", source: "builtin" },
131
- { name: "claude-haiku-4-5", description: "Haiku 4.5 (fastest)", source: "builtin" }
132
- ]
133
- },
134
- { name: "compact", description: "Compress context window", source: "builtin" },
135
- { name: "cost", description: "Show session info", source: "builtin" },
136
- { name: "context", description: "Context window info", source: "builtin" },
137
- { name: "permissions", description: "Permission rules", source: "builtin" },
138
- { name: "exit", description: "Exit CLI", source: "builtin" }
139
- ];
230
+ // src/utils/skill-prompt.ts
231
+ var import_node_child_process = require("child_process");
232
+ function substituteVariables(content, args, context) {
233
+ const argParts = args ? args.split(/\s+/) : [];
234
+ let result = content;
235
+ result = result.replace(/\$ARGUMENTS\[(\d+)]/g, (_match, index) => {
236
+ return argParts[Number(index)] ?? "";
237
+ });
238
+ result = result.replace(/\$ARGUMENTS/g, args);
239
+ result = result.replace(/\$(\d)(?!\d|\w|\[)/g, (_match, digit) => {
240
+ return argParts[Number(digit)] ?? "";
241
+ });
242
+ result = result.replace(/\$\{CLAUDE_SESSION_ID}/g, context?.sessionId ?? "");
243
+ result = result.replace(/\$\{CLAUDE_SKILL_DIR}/g, context?.skillDir ?? "");
244
+ return result;
140
245
  }
141
- var BuiltinCommandSource = class {
142
- name = "builtin";
143
- commands;
144
- constructor() {
145
- this.commands = createBuiltinCommands();
246
+ async function preprocessShellCommands(content) {
247
+ const shellPattern = /!`([^`]+)`/g;
248
+ if (!shellPattern.test(content)) {
249
+ return content;
146
250
  }
147
- getCommands() {
148
- return this.commands;
251
+ shellPattern.lastIndex = 0;
252
+ let result = content;
253
+ let match;
254
+ const matches = [];
255
+ while ((match = shellPattern.exec(content)) !== null) {
256
+ matches.push({ full: match[0], command: match[1] });
149
257
  }
150
- };
258
+ for (const { full, command } of matches) {
259
+ let output = "";
260
+ try {
261
+ output = (0, import_node_child_process.execSync)(command, {
262
+ timeout: 5e3,
263
+ encoding: "utf-8",
264
+ stdio: ["pipe", "pipe", "pipe"]
265
+ }).trimEnd();
266
+ } catch {
267
+ output = "";
268
+ }
269
+ result = result.replace(full, output);
270
+ }
271
+ return result;
272
+ }
273
+ async function buildSkillPrompt(input, registry, context) {
274
+ const parts = input.slice(1).split(/\s+/);
275
+ const cmd = parts[0]?.toLowerCase() ?? "";
276
+ const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
277
+ if (!skillCmd) return null;
278
+ const args = parts.slice(1).join(" ").trim();
279
+ const userInstruction = args || skillCmd.description;
280
+ if (skillCmd.skillContent) {
281
+ let processed = await preprocessShellCommands(skillCmd.skillContent);
282
+ processed = substituteVariables(processed, args, context);
283
+ return `<skill name="${cmd}">
284
+ ${processed}
285
+ </skill>
151
286
 
152
- // src/commands/skill-source.ts
153
- var import_node_fs = require("fs");
154
- var import_node_path = require("path");
155
- var import_node_os = require("os");
156
- function parseFrontmatter(content) {
157
- const lines = content.split("\n");
158
- if (lines[0]?.trim() !== "---") return null;
159
- let name = "";
160
- let description = "";
161
- for (let i = 1; i < lines.length; i++) {
162
- const line = lines[i];
163
- if (line.trim() === "---") break;
164
- const nameMatch = line.match(/^name:\s*(.+)/);
165
- if (nameMatch) {
166
- name = nameMatch[1].trim();
167
- continue;
287
+ Execute the "${cmd}" skill: ${userInstruction}`;
288
+ }
289
+ return `Use the "${cmd}" skill: ${userInstruction}`;
290
+ }
291
+
292
+ // src/ui/hooks/plugin-hooks-merger.ts
293
+ var import_node_path2 = require("path");
294
+ function buildPluginEnv(plugin) {
295
+ const dataDir = (0, import_node_path2.join)((0, import_node_path2.dirname)((0, import_node_path2.dirname)(plugin.pluginDir)), "data", plugin.manifest.name);
296
+ return {
297
+ CLAUDE_PLUGIN_ROOT: plugin.pluginDir,
298
+ CLAUDE_PLUGIN_PATH: plugin.pluginDir,
299
+ CLAUDE_PLUGIN_DATA: dataDir
300
+ };
301
+ }
302
+ function resolvePluginRoot(group, pluginDir) {
303
+ if (Array.isArray(group.hooks)) {
304
+ return {
305
+ ...group,
306
+ hooks: group.hooks.map((h) => {
307
+ if (typeof h.command === "string") {
308
+ return {
309
+ ...h,
310
+ command: h.command.replace(/\$\{CLAUDE_PLUGIN_ROOT\}/g, pluginDir)
311
+ };
312
+ }
313
+ return h;
314
+ })
315
+ };
316
+ }
317
+ return group;
318
+ }
319
+ function mergePluginHooks(plugins) {
320
+ const merged = {};
321
+ for (const plugin of plugins) {
322
+ const hooksObj = plugin.hooks;
323
+ if (!hooksObj) continue;
324
+ const pluginEnv = buildPluginEnv(plugin);
325
+ const innerHooks = hooksObj.hooks ?? hooksObj;
326
+ for (const [event, groups] of Object.entries(innerHooks)) {
327
+ if (!Array.isArray(groups)) continue;
328
+ if (!merged[event]) merged[event] = [];
329
+ const resolved = groups.map((group) => {
330
+ const r = resolvePluginRoot(group, plugin.pluginDir);
331
+ r.env = pluginEnv;
332
+ return r;
333
+ });
334
+ merged[event].push(...resolved);
168
335
  }
169
- const descMatch = line.match(/^description:\s*(.+)/);
170
- if (descMatch) {
171
- description = descMatch[1].trim();
336
+ }
337
+ return merged;
338
+ }
339
+ function mergeHooksIntoConfig(configHooks, pluginHooks) {
340
+ const pluginKeys = Object.keys(pluginHooks);
341
+ if (pluginKeys.length === 0) return configHooks;
342
+ const merged = {};
343
+ for (const [event, groups] of Object.entries(pluginHooks)) {
344
+ merged[event] = [...groups];
345
+ }
346
+ if (configHooks) {
347
+ for (const [event, groups] of Object.entries(configHooks)) {
348
+ if (!Array.isArray(groups)) continue;
349
+ if (!merged[event]) merged[event] = [];
350
+ merged[event].push(...groups);
172
351
  }
173
352
  }
174
- return name ? { name, description } : null;
353
+ return merged;
175
354
  }
176
- function scanSkillsDir(skillsDir) {
177
- if (!(0, import_node_fs.existsSync)(skillsDir)) return [];
178
- const commands = [];
179
- const entries = (0, import_node_fs.readdirSync)(skillsDir, { withFileTypes: true });
180
- for (const entry of entries) {
181
- if (!entry.isDirectory()) continue;
182
- const skillFile = (0, import_node_path.join)(skillsDir, entry.name, "SKILL.md");
183
- if (!(0, import_node_fs.existsSync)(skillFile)) continue;
184
- const content = (0, import_node_fs.readFileSync)(skillFile, "utf-8");
185
- const frontmatter = parseFrontmatter(content);
186
- commands.push({
187
- name: frontmatter?.name ?? entry.name,
188
- description: frontmatter?.description ?? `Skill: ${entry.name}`,
189
- source: "skill"
190
- });
355
+
356
+ // src/ui/hooks/useInteractiveSession.ts
357
+ var MAX_RENDERED_MESSAGES = 100;
358
+ function initializeSession(props, permissionHandler) {
359
+ const cwd = props.cwd ?? process.cwd();
360
+ const registry = new import_agent_sdk.CommandRegistry();
361
+ registry.addSource(new import_agent_sdk.BuiltinCommandSource());
362
+ registry.addSource(new import_agent_sdk.SkillCommandSource(cwd));
363
+ let pluginHooks = {};
364
+ const pluginsDir = (0, import_node_path3.join)((0, import_node_os.homedir)(), ".robota", "plugins");
365
+ const loader = new import_agent_sdk.BundlePluginLoader(pluginsDir);
366
+ try {
367
+ const plugins = loader.loadPluginsSync();
368
+ if (plugins.length > 0) {
369
+ registry.addSource(new PluginCommandSource(plugins));
370
+ pluginHooks = mergePluginHooks(plugins);
371
+ }
372
+ } catch {
191
373
  }
192
- return commands;
374
+ const mergedConfig = {
375
+ ...props.config,
376
+ hooks: mergeHooksIntoConfig(
377
+ props.config.hooks,
378
+ pluginHooks
379
+ )
380
+ };
381
+ const interactiveSession = new import_agent_sdk.InteractiveSession({
382
+ config: mergedConfig,
383
+ context: props.context,
384
+ projectInfo: props.projectInfo,
385
+ sessionStore: props.sessionStore,
386
+ permissionMode: props.permissionMode,
387
+ maxTurns: props.maxTurns,
388
+ cwd,
389
+ permissionHandler
390
+ });
391
+ return {
392
+ interactiveSession,
393
+ registry,
394
+ commandExecutor: new import_agent_sdk.SystemCommandExecutor(),
395
+ pluginHooks
396
+ };
193
397
  }
194
- var SkillCommandSource = class {
195
- name = "skill";
196
- cwd;
197
- cachedCommands = null;
198
- constructor(cwd) {
199
- this.cwd = cwd;
398
+ function useInteractiveSession(props) {
399
+ const [messages, setMessages] = (0, import_react.useState)([]);
400
+ const addMessage = (0, import_react.useCallback)((msg) => {
401
+ setMessages((prev) => {
402
+ const updated = [...prev, msg];
403
+ return updated.length > MAX_RENDERED_MESSAGES ? updated.slice(-MAX_RENDERED_MESSAGES) : updated;
404
+ });
405
+ }, []);
406
+ const [streamingText, setStreamingText] = (0, import_react.useState)("");
407
+ const [activeTools, setActiveTools] = (0, import_react.useState)([]);
408
+ const [isThinking, setIsThinking] = (0, import_react.useState)(false);
409
+ const [isAborting, setIsAborting] = (0, import_react.useState)(false);
410
+ const [pendingPrompt, setPendingPrompt] = (0, import_react.useState)(null);
411
+ const [contextState, setContextState] = (0, import_react.useState)({ percentage: 0, usedTokens: 0, maxTokens: 0 });
412
+ const [permissionRequest, setPermissionRequest] = (0, import_react.useState)(null);
413
+ const permissionQueueRef = (0, import_react.useRef)([]);
414
+ const processingRef = (0, import_react.useRef)(false);
415
+ const processNextPermission = (0, import_react.useCallback)(() => {
416
+ if (processingRef.current) return;
417
+ const next = permissionQueueRef.current[0];
418
+ if (!next) {
419
+ setPermissionRequest(null);
420
+ return;
421
+ }
422
+ processingRef.current = true;
423
+ setPermissionRequest({
424
+ toolName: next.toolName,
425
+ toolArgs: next.toolArgs,
426
+ resolve: (result) => {
427
+ permissionQueueRef.current.shift();
428
+ processingRef.current = false;
429
+ setPermissionRequest(null);
430
+ next.resolve(result);
431
+ setTimeout(() => processNextPermission(), 0);
432
+ }
433
+ });
434
+ }, []);
435
+ const permissionHandler = (0, import_react.useCallback)(
436
+ (toolName, toolArgs) => new Promise((resolve) => {
437
+ permissionQueueRef.current.push({ toolName, toolArgs, resolve });
438
+ processNextPermission();
439
+ }),
440
+ [processNextPermission]
441
+ );
442
+ const stateRef = (0, import_react.useRef)(null);
443
+ if (stateRef.current === null) {
444
+ stateRef.current = initializeSession(props, permissionHandler);
200
445
  }
201
- getCommands() {
202
- if (this.cachedCommands) return this.cachedCommands;
203
- const projectSkills = scanSkillsDir((0, import_node_path.join)(this.cwd, ".agents", "skills"));
204
- const userSkills = scanSkillsDir((0, import_node_path.join)((0, import_node_os.homedir)(), ".claude", "skills"));
205
- const seen = new Set(projectSkills.map((cmd) => cmd.name));
206
- const merged = [...projectSkills];
207
- for (const cmd of userSkills) {
208
- if (!seen.has(cmd.name)) {
209
- merged.push(cmd);
446
+ const { interactiveSession, registry, commandExecutor } = stateRef.current;
447
+ (0, import_react.useEffect)(() => {
448
+ let streamBuf = "";
449
+ const onTextDelta = (delta) => {
450
+ streamBuf += delta;
451
+ setStreamingText(streamBuf);
452
+ };
453
+ const onToolStart = (state) => {
454
+ setActiveTools((prev) => [...prev, state]);
455
+ };
456
+ const onToolEnd = (state) => {
457
+ setActiveTools(
458
+ (prev) => prev.map((t) => t.toolName === state.toolName && t.isRunning ? state : t)
459
+ );
460
+ };
461
+ const onThinking = (thinking) => {
462
+ setIsThinking(thinking);
463
+ if (!thinking) {
464
+ setIsAborting(false);
465
+ streamBuf = "";
466
+ setStreamingText("");
467
+ setActiveTools([]);
468
+ }
469
+ };
470
+ const onComplete = (result) => {
471
+ setContextState({
472
+ percentage: result.contextState.usedPercentage,
473
+ usedTokens: result.contextState.usedTokens,
474
+ maxTokens: result.contextState.maxTokens
475
+ });
476
+ };
477
+ const onInterrupted = () => {
478
+ };
479
+ const onError = () => {
480
+ };
481
+ interactiveSession.on("text_delta", onTextDelta);
482
+ interactiveSession.on("tool_start", onToolStart);
483
+ interactiveSession.on("tool_end", onToolEnd);
484
+ interactiveSession.on("thinking", onThinking);
485
+ interactiveSession.on("complete", onComplete);
486
+ interactiveSession.on("interrupted", onInterrupted);
487
+ interactiveSession.on("error", onError);
488
+ return () => {
489
+ interactiveSession.off("text_delta", onTextDelta);
490
+ interactiveSession.off("tool_start", onToolStart);
491
+ interactiveSession.off("tool_end", onToolEnd);
492
+ interactiveSession.off("thinking", onThinking);
493
+ interactiveSession.off("complete", onComplete);
494
+ interactiveSession.off("interrupted", onInterrupted);
495
+ interactiveSession.off("error", onError);
496
+ };
497
+ }, [interactiveSession]);
498
+ (0, import_react.useEffect)(() => {
499
+ if (!isThinking) {
500
+ const sessionMessages = interactiveSession.getMessages();
501
+ if (sessionMessages.length > 0) {
502
+ setMessages(
503
+ sessionMessages.length > MAX_RENDERED_MESSAGES ? sessionMessages.slice(-MAX_RENDERED_MESSAGES) : [...sessionMessages]
504
+ );
210
505
  }
506
+ setPendingPrompt(interactiveSession.getPendingPrompt());
211
507
  }
212
- this.cachedCommands = merged;
213
- return this.cachedCommands;
508
+ }, [isThinking, interactiveSession]);
509
+ const handleSubmit = (0, import_react.useCallback)(
510
+ async (input) => {
511
+ if (input.startsWith("/")) {
512
+ const parts = input.slice(1).split(/\s+/);
513
+ const cmd = parts[0]?.toLowerCase() ?? "";
514
+ const args = parts.slice(1).join(" ");
515
+ const result = await commandExecutor.execute(cmd, interactiveSession, args);
516
+ if (result) {
517
+ addMessage((0, import_agent_core.createSystemMessage)(result.message));
518
+ const effects = interactiveSession;
519
+ if (result.data?.modelId) {
520
+ effects._pendingModelId = result.data.modelId;
521
+ return;
522
+ }
523
+ if (result.data?.language) {
524
+ effects._pendingLanguage = result.data.language;
525
+ return;
526
+ }
527
+ if (result.data?.resetRequested) {
528
+ effects._resetRequested = true;
529
+ return;
530
+ }
531
+ const ctx = interactiveSession.getContextState();
532
+ setContextState({
533
+ percentage: ctx.usedPercentage,
534
+ usedTokens: ctx.usedTokens,
535
+ maxTokens: ctx.maxTokens
536
+ });
537
+ return;
538
+ }
539
+ const skillCmd = registry.getCommands().find((c) => c.name === cmd && (c.source === "skill" || c.source === "plugin"));
540
+ if (skillCmd) {
541
+ const prompt = await buildSkillPrompt(input, registry);
542
+ if (prompt) {
543
+ await interactiveSession.submit(prompt);
544
+ return;
545
+ }
546
+ }
547
+ if (cmd === "exit") {
548
+ interactiveSession._exitRequested = true;
549
+ return;
550
+ }
551
+ if (cmd === "plugin") {
552
+ interactiveSession._triggerPluginTUI = true;
553
+ return;
554
+ }
555
+ addMessage((0, import_agent_core.createSystemMessage)(`Unknown command "/${cmd}". Type /help for help.`));
556
+ return;
557
+ }
558
+ await interactiveSession.submit(input);
559
+ },
560
+ [interactiveSession, commandExecutor, registry, addMessage]
561
+ );
562
+ const handleAbort = (0, import_react.useCallback)(() => {
563
+ setIsAborting(true);
564
+ interactiveSession.abort();
565
+ }, [interactiveSession]);
566
+ const handleCancelQueue = (0, import_react.useCallback)(() => {
567
+ interactiveSession.cancelQueue();
568
+ setPendingPrompt(null);
569
+ }, [interactiveSession]);
570
+ if (contextState.maxTokens === 0) {
571
+ const ctx = interactiveSession.getContextState();
572
+ setContextState({
573
+ percentage: ctx.usedPercentage,
574
+ usedTokens: ctx.usedTokens,
575
+ maxTokens: ctx.maxTokens
576
+ });
214
577
  }
215
- };
578
+ return {
579
+ interactiveSession,
580
+ registry,
581
+ commandExecutor,
582
+ pluginHooks: stateRef.current.pluginHooks,
583
+ messages,
584
+ addMessage,
585
+ setMessages,
586
+ streamingText,
587
+ activeTools,
588
+ isThinking,
589
+ isAborting,
590
+ pendingPrompt,
591
+ permissionRequest,
592
+ contextState,
593
+ handleSubmit,
594
+ handleAbort,
595
+ handleCancelQueue
596
+ };
597
+ }
598
+
599
+ // src/ui/hooks/usePluginCallbacks.ts
600
+ var import_react2 = require("react");
601
+ var import_node_os2 = require("os");
602
+ var import_node_path4 = require("path");
603
+ var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
604
+ function usePluginCallbacks(cwd) {
605
+ return (0, import_react2.useMemo)(() => {
606
+ const home = (0, import_node_os2.homedir)();
607
+ const pluginsDir = (0, import_node_path4.join)(home, ".robota", "plugins");
608
+ const userSettingsPath = (0, import_node_path4.join)(home, ".robota", "settings.json");
609
+ const settingsStore = new import_agent_sdk2.PluginSettingsStore(userSettingsPath);
610
+ const marketplace = new import_agent_sdk2.MarketplaceClient({ pluginsDir });
611
+ const installer = new import_agent_sdk2.BundlePluginInstaller({
612
+ pluginsDir,
613
+ settingsStore,
614
+ marketplaceClient: marketplace
615
+ });
616
+ const loader = new import_agent_sdk2.BundlePluginLoader(pluginsDir);
617
+ return {
618
+ listInstalled: async () => {
619
+ const plugins = await loader.loadAll();
620
+ const enabledMap = settingsStore.getEnabledPlugins();
621
+ return plugins.map((p) => {
622
+ const parts = p.pluginDir.split("/");
623
+ const cacheIdx = parts.indexOf("cache");
624
+ const marketplaceName = cacheIdx >= 0 ? parts[cacheIdx + 1] : "";
625
+ const fullId = marketplaceName ? `${p.manifest.name}@${marketplaceName}` : p.manifest.name;
626
+ return {
627
+ name: fullId,
628
+ description: p.manifest.description,
629
+ enabled: enabledMap[fullId] !== false && enabledMap[p.manifest.name] !== false
630
+ };
631
+ });
632
+ },
633
+ listAvailablePlugins: async (marketplaceName) => {
634
+ let manifest;
635
+ try {
636
+ manifest = marketplace.fetchManifest(marketplaceName);
637
+ } catch {
638
+ return [];
639
+ }
640
+ const installed = installer.getInstalledPlugins();
641
+ const installedNames = new Set(Object.values(installed).map((r) => r.pluginName));
642
+ return manifest.plugins.map((p) => ({
643
+ name: p.name,
644
+ description: p.description,
645
+ installed: installedNames.has(p.name)
646
+ }));
647
+ },
648
+ install: async (pluginId, scope) => {
649
+ const [name, marketplaceName] = pluginId.split("@");
650
+ if (!name || !marketplaceName) {
651
+ throw new Error("Plugin ID must be in format: name@marketplace");
652
+ }
653
+ if (scope === "project") {
654
+ const projectPluginsDir = (0, import_node_path4.join)(cwd, ".robota", "plugins");
655
+ const projectInstaller = new import_agent_sdk2.BundlePluginInstaller({
656
+ pluginsDir: projectPluginsDir,
657
+ settingsStore,
658
+ marketplaceClient: marketplace
659
+ });
660
+ await projectInstaller.install(name, marketplaceName);
661
+ } else {
662
+ await installer.install(name, marketplaceName);
663
+ }
664
+ },
665
+ uninstall: async (pluginId) => {
666
+ await installer.uninstall(pluginId);
667
+ },
668
+ enable: async (pluginId) => {
669
+ await installer.enable(pluginId);
670
+ },
671
+ disable: async (pluginId) => {
672
+ await installer.disable(pluginId);
673
+ },
674
+ marketplaceAdd: async (source) => {
675
+ if (source.includes("/") && !source.includes(":")) {
676
+ return marketplace.addMarketplace({ type: "github", repo: source });
677
+ } else {
678
+ return marketplace.addMarketplace({ type: "git", url: source });
679
+ }
680
+ },
681
+ marketplaceRemove: async (name) => {
682
+ const installedFromMarketplace = installer.getPluginsByMarketplace(name);
683
+ for (const record of installedFromMarketplace) {
684
+ await installer.uninstall(`${record.pluginName}@${record.marketplace}`);
685
+ }
686
+ marketplace.removeMarketplace(name);
687
+ },
688
+ marketplaceUpdate: async (name) => {
689
+ marketplace.updateMarketplace(name);
690
+ },
691
+ marketplaceList: async () => {
692
+ return marketplace.listMarketplaces().map((m) => ({
693
+ name: m.name,
694
+ type: m.source.type
695
+ }));
696
+ },
697
+ reloadPlugins: async () => {
698
+ }
699
+ };
700
+ }, [cwd]);
701
+ }
216
702
 
217
703
  // src/ui/MessageList.tsx
218
- var import_ink = require("ink");
704
+ var import_react3 = __toESM(require("react"), 1);
705
+ var import_ink2 = require("ink");
706
+ var import_agent_core2 = require("@robota-sdk/agent-core");
219
707
 
220
708
  // src/ui/render-markdown.ts
221
709
  var import_marked = require("marked");
@@ -228,54 +716,161 @@ function renderMarkdown(md) {
228
716
  return typeof result === "string" ? result.trimEnd() : md;
229
717
  }
230
718
 
231
- // src/ui/MessageList.tsx
719
+ // src/ui/DiffBlock.tsx
720
+ var import_ink = require("ink");
232
721
  var import_jsx_runtime = require("react/jsx-runtime");
722
+ var MAX_DIFF_LINES = 12;
723
+ var TRUNCATED_SHOW = 10;
724
+ function DiffBlock({ file, lines }) {
725
+ const truncated = lines.length > MAX_DIFF_LINES;
726
+ const visible = truncated ? lines.slice(0, TRUNCATED_SHOW) : lines;
727
+ const remaining = lines.length - TRUNCATED_SHOW;
728
+ const maxLineNum = Math.max(...visible.map((l) => l.lineNumber), 0);
729
+ const numWidth = String(maxLineNum).length;
730
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", marginLeft: 4, children: [
731
+ file && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
732
+ "\u2502 ",
733
+ file
734
+ ] }),
735
+ visible.map((line, i) => {
736
+ const lineNum = String(line.lineNumber).padStart(numWidth, " ");
737
+ if (line.type === "context") {
738
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
739
+ "\u2502 ",
740
+ lineNum,
741
+ " ",
742
+ line.text
743
+ ] }, i);
744
+ }
745
+ const prefix = line.type === "remove" ? "-" : "+";
746
+ const bgColor = line.type === "remove" ? "#5c1a1a" : "#1a3d1a";
747
+ const fgColor = line.type === "remove" ? "#ff9999" : "#99ff99";
748
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: fgColor, backgroundColor: bgColor, children: [
749
+ "\u2502 ",
750
+ lineNum,
751
+ " ",
752
+ prefix,
753
+ " ",
754
+ line.text
755
+ ] }, i);
756
+ }),
757
+ truncated && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "white", dimColor: true, children: [
758
+ "\u2502 ... and ",
759
+ remaining,
760
+ " more lines"
761
+ ] })
762
+ ] });
763
+ }
764
+
765
+ // src/ui/MessageList.tsx
766
+ var import_jsx_runtime2 = require("react/jsx-runtime");
233
767
  function RoleLabel({ role }) {
234
768
  switch (role) {
235
769
  case "user":
236
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "green", bold: true, children: [
770
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", bold: true, children: [
237
771
  "You:",
238
772
  " "
239
773
  ] });
240
774
  case "assistant":
241
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "cyan", bold: true, children: [
775
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "cyan", bold: true, children: [
242
776
  "Robota:",
243
777
  " "
244
778
  ] });
245
779
  case "system":
246
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "yellow", bold: true, children: [
780
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "yellow", bold: true, children: [
247
781
  "System:",
248
782
  " "
249
783
  ] });
250
784
  case "tool":
251
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "magenta", bold: true, children: [
785
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
252
786
  "Tool:",
253
787
  " "
254
788
  ] });
255
789
  }
256
790
  }
257
- function MessageItem({ message }) {
258
- return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { flexDirection: "column", marginBottom: 1, children: [
259
- /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Box, { children: [
260
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RoleLabel, { role: message.role }),
261
- message.toolName && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_ink.Text, { color: "magenta", dimColor: true, children: [
262
- "[",
263
- message.toolName,
264
- "]",
791
+ function ToolMessage({ message }) {
792
+ if (!(0, import_agent_core2.isToolMessage)(message)) {
793
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, {});
794
+ }
795
+ const toolName = message.name;
796
+ const content = message.content;
797
+ let summaries = null;
798
+ try {
799
+ const parsed = JSON.parse(content);
800
+ if (Array.isArray(parsed) && parsed.length > 0 && typeof parsed[0].line === "string") {
801
+ summaries = parsed;
802
+ }
803
+ } catch {
804
+ }
805
+ if (summaries) {
806
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
807
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { children: [
808
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
809
+ "Tool:",
810
+ " "
811
+ ] }),
812
+ toolName && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", dimColor: true, children: [
813
+ "[",
814
+ toolName,
815
+ "]"
816
+ ] })
817
+ ] }),
818
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
819
+ summaries.map((s, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", children: [
820
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", children: [
821
+ " ",
822
+ "\u2713",
823
+ " ",
824
+ s.line
825
+ ] }),
826
+ s.diffLines && s.diffLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(DiffBlock, { file: s.diffFile, lines: s.diffLines })
827
+ ] }, i))
828
+ ] });
829
+ }
830
+ const lines = content.split("\n").filter((l) => l.trim());
831
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
832
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { children: [
833
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", bold: true, children: [
834
+ "Tool:",
265
835
  " "
836
+ ] }),
837
+ toolName && /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "white", dimColor: true, children: [
838
+ "[",
839
+ toolName,
840
+ "]"
266
841
  ] })
267
842
  ] }),
268
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { children: " " }),
269
- /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Text, { wrap: "wrap", children: message.role === "assistant" ? renderMarkdown(message.content) : message.content }) })
843
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
844
+ lines.map((line, i) => /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: "green", children: [
845
+ " ",
846
+ "\u2713",
847
+ " ",
848
+ line
849
+ ] }, i))
270
850
  ] });
271
851
  }
852
+ var MessageItem = import_react3.default.memo(function MessageItem2({
853
+ message
854
+ }) {
855
+ if ((0, import_agent_core2.isToolMessage)(message)) {
856
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ToolMessage, { message });
857
+ }
858
+ const content = message.content ?? "";
859
+ const isInterrupted = message.state === "interrupted";
860
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Box, { flexDirection: "column", marginBottom: 1, children: [
861
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(RoleLabel, { role: message.role }) }),
862
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: " " }),
863
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { wrap: "wrap", children: (0, import_agent_core2.isAssistantMessage)(message) ? renderMarkdown(content + (isInterrupted ? "\n\n_(interrupted)_" : "")) : content }) })
864
+ ] });
865
+ });
272
866
  function MessageList({ messages }) {
273
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_ink.Box, { flexDirection: "column", children: messages.map((msg) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(MessageItem, { message: msg }, msg.id)) });
867
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Box, { flexDirection: "column", children: messages.map((msg) => /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(MessageItem, { message: msg }, msg.id)) });
274
868
  }
275
869
 
276
870
  // src/ui/StatusBar.tsx
277
- var import_ink2 = require("ink");
278
- var import_jsx_runtime2 = require("react/jsx-runtime");
871
+ var import_ink3 = require("ink");
872
+ var import_agent_core3 = require("@robota-sdk/agent-core");
873
+ var import_jsx_runtime3 = require("react/jsx-runtime");
279
874
  var CONTEXT_YELLOW_THRESHOLD = 70;
280
875
  var CONTEXT_RED_THRESHOLD = 90;
281
876
  function getContextColor(percentage) {
@@ -294,8 +889,8 @@ function StatusBar({
294
889
  contextMaxTokens
295
890
  }) {
296
891
  const contextColor = getContextColor(contextPercentage);
297
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
298
- import_ink2.Box,
892
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
893
+ import_ink3.Box,
299
894
  {
300
895
  borderStyle: "single",
301
896
  borderColor: "gray",
@@ -303,26 +898,26 @@ function StatusBar({
303
898
  paddingRight: 1,
304
899
  justifyContent: "space-between",
305
900
  children: [
306
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { children: [
307
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { color: "cyan", bold: true, children: "Mode:" }),
901
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { children: [
902
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: "cyan", bold: true, children: "Mode:" }),
308
903
  " ",
309
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { children: permissionMode }),
904
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { children: permissionMode }),
310
905
  " | ",
311
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { dimColor: true, children: modelName }),
906
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { dimColor: true, children: modelName }),
312
907
  " | ",
313
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { color: contextColor, children: [
908
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { color: contextColor, children: [
314
909
  "Context: ",
315
910
  Math.round(contextPercentage),
316
911
  "% (",
317
- (contextUsedTokens / 1e3).toFixed(1),
318
- "k/",
319
- (contextMaxTokens / 1e3).toFixed(0),
320
- "k)"
912
+ (0, import_agent_core3.formatTokenCount)(contextUsedTokens),
913
+ "/",
914
+ (0, import_agent_core3.formatTokenCount)(contextMaxTokens),
915
+ ")"
321
916
  ] })
322
917
  ] }),
323
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { children: [
324
- isThinking && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_ink2.Text, { color: "yellow", children: "Thinking... " }),
325
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { dimColor: true, children: [
918
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { children: [
919
+ isThinking && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { color: "yellow", children: "Thinking... " }),
920
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_ink3.Text, { dimColor: true, children: [
326
921
  "msgs: ",
327
922
  messageCount
328
923
  ] })
@@ -333,146 +928,224 @@ function StatusBar({
333
928
  }
334
929
 
335
930
  // src/ui/InputArea.tsx
336
- var import_react3 = __toESM(require("react"), 1);
337
- var import_ink6 = require("ink");
931
+ var import_react6 = __toESM(require("react"), 1);
932
+ var import_ink7 = require("ink");
338
933
 
339
934
  // src/ui/CjkTextInput.tsx
340
- var import_react = require("react");
341
- var import_ink3 = require("ink");
935
+ var import_react4 = require("react");
936
+ var import_ink4 = require("ink");
937
+ var import_chalk = __toESM(require("chalk"), 1);
342
938
  var import_string_width = __toESM(require("string-width"), 1);
343
- var import_chalk2 = __toESM(require("chalk"), 1);
344
- var import_jsx_runtime3 = require("react/jsx-runtime");
939
+ var import_jsx_runtime4 = require("react/jsx-runtime");
940
+ var PASTE_START = "[200~";
941
+ var PASTE_END = "[201~";
942
+ function filterPrintable(input) {
943
+ if (!input || input.length === 0) return "";
944
+ return input.replace(/[\x00-\x1f\x7f]/g, "");
945
+ }
946
+ function insertAtCursor(value, cursor, input) {
947
+ const next = value.slice(0, cursor) + input + value.slice(cursor);
948
+ return { value: next, cursor: cursor + input.length };
949
+ }
950
+ function displayOffset(chars, charIndex, width) {
951
+ let offset = 0;
952
+ for (let i = 0; i < charIndex && i < chars.length; i++) {
953
+ const w = (0, import_string_width.default)(chars[i]);
954
+ const col = offset % width;
955
+ if (col + w > width) offset += width - col;
956
+ offset += w;
957
+ }
958
+ return offset;
959
+ }
960
+ function charIndexAtDisplayOffset(chars, target, width) {
961
+ let offset = 0;
962
+ for (let i = 0; i < chars.length; i++) {
963
+ if (offset >= target) return i;
964
+ const w = (0, import_string_width.default)(chars[i]);
965
+ const col = offset % width;
966
+ if (col + w > width) offset += width - col;
967
+ offset += w;
968
+ }
969
+ return chars.length;
970
+ }
345
971
  function CjkTextInput({
346
972
  value,
347
973
  onChange,
348
974
  onSubmit,
975
+ onPaste,
349
976
  placeholder = "",
350
977
  focus = true,
351
- showCursor = true
978
+ showCursor = true,
979
+ availableWidth
352
980
  }) {
353
- const valueRef = (0, import_react.useRef)(value);
354
- const cursorRef = (0, import_react.useRef)(value.length);
355
- const [, forceRender] = (0, import_react.useState)(0);
356
- const { setCursorPosition } = (0, import_ink3.useCursor)();
981
+ const valueRef = (0, import_react4.useRef)(value);
982
+ const cursorRef = (0, import_react4.useRef)(value.length);
983
+ const [, forceRender] = (0, import_react4.useState)(0);
984
+ const isPastingRef = (0, import_react4.useRef)(false);
985
+ const pasteBufferRef = (0, import_react4.useRef)("");
357
986
  if (value !== valueRef.current) {
358
987
  valueRef.current = value;
359
988
  if (cursorRef.current > value.length) {
360
989
  cursorRef.current = value.length;
361
990
  }
362
991
  }
363
- (0, import_ink3.useInput)(
992
+ (0, import_ink4.useInput)(
364
993
  (input, key) => {
365
- if (key.upArrow || key.downArrow || key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
366
- return;
367
- }
368
- if (key.return) {
369
- onSubmit?.(valueRef.current);
370
- return;
371
- }
372
- if (key.leftArrow) {
373
- if (cursorRef.current > 0) {
374
- cursorRef.current -= 1;
375
- forceRender((n) => n + 1);
994
+ try {
995
+ if (input === PASTE_START || input.startsWith(PASTE_START)) {
996
+ isPastingRef.current = true;
997
+ const afterMarker = input.slice(PASTE_START.length);
998
+ if (afterMarker.length > 0) {
999
+ pasteBufferRef.current += afterMarker;
1000
+ }
1001
+ return;
376
1002
  }
377
- return;
378
- }
379
- if (key.rightArrow) {
380
- if (cursorRef.current < valueRef.current.length) {
381
- cursorRef.current += 1;
382
- forceRender((n) => n + 1);
1003
+ if (isPastingRef.current) {
1004
+ if (input === PASTE_END || input.includes(PASTE_END)) {
1005
+ const beforeMarker = input.split(PASTE_END)[0] ?? "";
1006
+ pasteBufferRef.current += beforeMarker;
1007
+ const text = pasteBufferRef.current.replace(/\r\n?/g, "\n");
1008
+ pasteBufferRef.current = "";
1009
+ isPastingRef.current = false;
1010
+ if (text.length > 0) {
1011
+ if (text.includes("\n") && onPaste) {
1012
+ onPaste(text);
1013
+ } else {
1014
+ const printable2 = filterPrintable(text);
1015
+ if (printable2.length > 0) {
1016
+ const result2 = insertAtCursor(valueRef.current, cursorRef.current, printable2);
1017
+ cursorRef.current = result2.cursor;
1018
+ valueRef.current = result2.value;
1019
+ onChange(result2.value);
1020
+ }
1021
+ }
1022
+ }
1023
+ } else {
1024
+ pasteBufferRef.current += input;
1025
+ }
1026
+ return;
383
1027
  }
384
- return;
385
- }
386
- if (key.backspace || key.delete) {
387
- if (cursorRef.current > 0) {
388
- const v2 = valueRef.current;
389
- const next2 = v2.slice(0, cursorRef.current - 1) + v2.slice(cursorRef.current);
390
- cursorRef.current -= 1;
391
- valueRef.current = next2;
392
- onChange(next2);
1028
+ if (key.ctrl && input === "c" || key.tab || key.shift && key.tab) {
1029
+ return;
393
1030
  }
394
- return;
1031
+ if (key.upArrow || key.downArrow) {
1032
+ if (availableWidth && availableWidth > 0) {
1033
+ const chars = [...valueRef.current];
1034
+ const offset = displayOffset(chars, cursorRef.current, availableWidth);
1035
+ const target = key.upArrow ? offset - availableWidth : offset + availableWidth;
1036
+ if (target >= 0) {
1037
+ const newCursor = charIndexAtDisplayOffset(chars, target, availableWidth);
1038
+ if (newCursor !== cursorRef.current) {
1039
+ cursorRef.current = newCursor;
1040
+ forceRender((n) => n + 1);
1041
+ }
1042
+ }
1043
+ }
1044
+ return;
1045
+ }
1046
+ if (key.return) {
1047
+ onSubmit?.(valueRef.current);
1048
+ return;
1049
+ }
1050
+ if (input.length > 1 && (input.includes("\n") || input.includes("\r")) && onPaste) {
1051
+ onPaste(input.replace(/\r\n?/g, "\n"));
1052
+ return;
1053
+ }
1054
+ if (key.leftArrow) {
1055
+ if (cursorRef.current > 0) {
1056
+ cursorRef.current -= 1;
1057
+ forceRender((n) => n + 1);
1058
+ }
1059
+ return;
1060
+ }
1061
+ if (key.rightArrow) {
1062
+ if (cursorRef.current < valueRef.current.length) {
1063
+ cursorRef.current += 1;
1064
+ forceRender((n) => n + 1);
1065
+ }
1066
+ return;
1067
+ }
1068
+ if (key.backspace || key.delete) {
1069
+ if (cursorRef.current > 0) {
1070
+ const v = valueRef.current;
1071
+ const next = v.slice(0, cursorRef.current - 1) + v.slice(cursorRef.current);
1072
+ cursorRef.current -= 1;
1073
+ valueRef.current = next;
1074
+ onChange(next);
1075
+ }
1076
+ return;
1077
+ }
1078
+ const printable = filterPrintable(input);
1079
+ if (printable.length === 0) return;
1080
+ const result = insertAtCursor(valueRef.current, cursorRef.current, printable);
1081
+ cursorRef.current = result.cursor;
1082
+ valueRef.current = result.value;
1083
+ onChange(result.value);
1084
+ } catch {
395
1085
  }
396
- const v = valueRef.current;
397
- const c = cursorRef.current;
398
- const next = v.slice(0, c) + input + v.slice(c);
399
- cursorRef.current = c + input.length;
400
- valueRef.current = next;
401
- onChange(next);
402
1086
  },
403
1087
  { isActive: focus }
404
1088
  );
405
- if (showCursor && focus) {
406
- const textBeforeCursor = [...valueRef.current].slice(0, cursorRef.current).join("");
407
- const cursorX = 4 + (0, import_string_width.default)(textBeforeCursor);
408
- setCursorPosition({ x: cursorX, y: 0 });
409
- }
410
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_ink3.Text, { children: renderWithCursor(valueRef.current, cursorRef.current, placeholder, showCursor && focus) });
1089
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink4.Text, { children: renderWithCursor(valueRef.current, cursorRef.current, placeholder, showCursor && focus) });
411
1090
  }
412
1091
  function renderWithCursor(value, cursorOffset, placeholder, showCursor) {
413
1092
  if (!showCursor) {
414
- return value.length > 0 ? value : placeholder ? import_chalk2.default.gray(placeholder) : "";
1093
+ return value.length > 0 ? value : placeholder ? import_chalk.default.gray(placeholder) : "";
415
1094
  }
416
1095
  if (value.length === 0) {
417
1096
  if (placeholder.length > 0) {
418
- return import_chalk2.default.inverse(placeholder[0]) + import_chalk2.default.gray(placeholder.slice(1));
1097
+ return import_chalk.default.inverse(placeholder[0]) + import_chalk.default.gray(placeholder.slice(1));
419
1098
  }
420
- return import_chalk2.default.inverse(" ");
1099
+ return import_chalk.default.inverse(" ");
421
1100
  }
422
1101
  const chars = [...value];
423
1102
  let rendered = "";
424
1103
  for (let i = 0; i < chars.length; i++) {
425
1104
  const char = chars[i] ?? "";
426
- rendered += i === cursorOffset ? import_chalk2.default.inverse(char) : char;
1105
+ rendered += i === cursorOffset ? import_chalk.default.inverse(char) : char;
427
1106
  }
428
1107
  if (cursorOffset >= chars.length) {
429
- rendered += import_chalk2.default.inverse(" ");
1108
+ rendered += import_chalk.default.inverse(" ");
430
1109
  }
431
1110
  return rendered;
432
1111
  }
433
1112
 
434
1113
  // src/ui/WaveText.tsx
435
- var import_react2 = require("react");
436
- var import_ink4 = require("ink");
437
- var import_jsx_runtime4 = require("react/jsx-runtime");
1114
+ var import_react5 = require("react");
1115
+ var import_ink5 = require("ink");
1116
+ var import_jsx_runtime5 = require("react/jsx-runtime");
438
1117
  var WAVE_COLORS = ["#666666", "#888888", "#aaaaaa", "#888888"];
439
1118
  var INTERVAL_MS = 400;
440
1119
  var CHARS_PER_GROUP = 4;
441
1120
  function WaveText({ text }) {
442
- const [tick, setTick] = (0, import_react2.useState)(0);
443
- (0, import_react2.useEffect)(() => {
1121
+ const [tick, setTick] = (0, import_react5.useState)(0);
1122
+ (0, import_react5.useEffect)(() => {
444
1123
  const timer = setInterval(() => {
445
1124
  setTick((prev) => prev + 1);
446
1125
  }, INTERVAL_MS);
447
1126
  return () => clearInterval(timer);
448
1127
  }, []);
449
1128
  const chars = [...text];
450
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink4.Text, { children: chars.map((char, i) => {
1129
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { children: chars.map((char, i) => {
451
1130
  const group = Math.floor(i / CHARS_PER_GROUP);
452
1131
  const colorIndex = (tick + group) % WAVE_COLORS.length;
453
- return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_ink4.Text, { color: WAVE_COLORS[colorIndex], children: char }, i);
1132
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: WAVE_COLORS[colorIndex], children: char }, i);
454
1133
  }) });
455
1134
  }
456
1135
 
457
1136
  // src/ui/SlashAutocomplete.tsx
458
- var import_ink5 = require("ink");
459
- var import_jsx_runtime5 = require("react/jsx-runtime");
1137
+ var import_ink6 = require("ink");
1138
+ var import_jsx_runtime6 = require("react/jsx-runtime");
460
1139
  var MAX_VISIBLE = 8;
461
1140
  function CommandRow(props) {
462
1141
  const { cmd, isSelected, showSlash } = props;
463
- const prefix = showSlash ? "/" : "";
464
1142
  const indicator = isSelected ? "\u25B8 " : " ";
465
1143
  const nameColor = isSelected ? "cyan" : void 0;
466
1144
  const dimmed = !isSelected;
467
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink5.Box, { children: [
468
- /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink5.Text, { color: nameColor, dimColor: dimmed, children: [
469
- indicator,
470
- prefix,
471
- cmd.name
472
- ] }),
473
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { dimColor: dimmed, children: " " }),
474
- /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: nameColor, dimColor: dimmed, children: cmd.description })
475
- ] });
1145
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Text, { color: nameColor, dimColor: dimmed, children: [
1146
+ indicator,
1147
+ showSlash ? `/${cmd.name} ${cmd.description}` : cmd.description
1148
+ ] }) });
476
1149
  }
477
1150
  function SlashAutocomplete({
478
1151
  commands,
@@ -483,7 +1156,7 @@ function SlashAutocomplete({
483
1156
  if (!visible || commands.length === 0) return null;
484
1157
  const scrollOffset = computeScrollOffset(selectedIndex, commands.length);
485
1158
  const visibleCommands = commands.slice(scrollOffset, scrollOffset + MAX_VISIBLE);
486
- return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: visibleCommands.map((cmd, i) => /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
1159
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: visibleCommands.map((cmd, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
487
1160
  CommandRow,
488
1161
  {
489
1162
  cmd,
@@ -500,8 +1173,14 @@ function computeScrollOffset(selectedIndex, total) {
500
1173
  return Math.min(selectedIndex - MAX_VISIBLE + 1, maxOffset);
501
1174
  }
502
1175
 
1176
+ // src/utils/paste-labels.ts
1177
+ var PASTE_LABEL_RE = /\[Pasted text #(\d+)(?: \+\d+ lines)?\]/g;
1178
+ function expandPasteLabels(text, store) {
1179
+ return text.replace(PASTE_LABEL_RE, (_, id) => store.get(Number(id)) ?? "");
1180
+ }
1181
+
503
1182
  // src/ui/InputArea.tsx
504
- var import_jsx_runtime6 = require("react/jsx-runtime");
1183
+ var import_jsx_runtime7 = require("react/jsx-runtime");
505
1184
  function parseSlashInput(value) {
506
1185
  if (!value.startsWith("/")) return { isSlash: false, parentCommand: "", filter: "" };
507
1186
  const afterSlash = value.slice(1);
@@ -512,16 +1191,16 @@ function parseSlashInput(value) {
512
1191
  return { isSlash: true, parentCommand: parent, filter: rest };
513
1192
  }
514
1193
  function useAutocomplete(value, registry) {
515
- const [selectedIndex, setSelectedIndex] = (0, import_react3.useState)(0);
516
- const [dismissed, setDismissed] = (0, import_react3.useState)(false);
517
- const prevValueRef = import_react3.default.useRef(value);
1194
+ const [selectedIndex, setSelectedIndex] = (0, import_react6.useState)(0);
1195
+ const [dismissed, setDismissed] = (0, import_react6.useState)(false);
1196
+ const prevValueRef = import_react6.default.useRef(value);
518
1197
  if (prevValueRef.current !== value) {
519
1198
  prevValueRef.current = value;
520
1199
  if (dismissed) setDismissed(false);
521
1200
  }
522
1201
  const parsed = parseSlashInput(value);
523
1202
  const isSubcommandMode = parsed.isSlash && parsed.parentCommand.length > 0;
524
- const filteredCommands = (0, import_react3.useMemo)(() => {
1203
+ const filteredCommands = (0, import_react6.useMemo)(() => {
525
1204
  if (!registry || !parsed.isSlash || dismissed) return [];
526
1205
  if (isSubcommandMode) {
527
1206
  const subs = registry.getSubcommands(parsed.parentCommand);
@@ -554,8 +1233,24 @@ function useAutocomplete(value, registry) {
554
1233
  }
555
1234
  };
556
1235
  }
557
- function InputArea({ onSubmit, isDisabled, registry }) {
558
- const [value, setValue] = (0, import_react3.useState)("");
1236
+ var BORDER_HORIZONTAL = 2;
1237
+ var PADDING_LEFT = 1;
1238
+ var PROMPT_WIDTH = 2;
1239
+ var INPUT_AREA_OVERHEAD = BORDER_HORIZONTAL + PADDING_LEFT + PROMPT_WIDTH;
1240
+ function InputArea({
1241
+ onSubmit,
1242
+ onCancelQueue,
1243
+ isDisabled,
1244
+ isAborting,
1245
+ pendingPrompt,
1246
+ registry
1247
+ }) {
1248
+ const [value, setValue] = (0, import_react6.useState)("");
1249
+ const pasteStore = (0, import_react6.useRef)(/* @__PURE__ */ new Map());
1250
+ const { stdout } = (0, import_ink7.useStdout)();
1251
+ const terminalColumns = stdout?.columns ?? 80;
1252
+ const availableWidth = Math.max(1, terminalColumns - INPUT_AREA_OVERHEAD);
1253
+ const pasteIdRef = (0, import_react6.useRef)(0);
559
1254
  const {
560
1255
  showPopup,
561
1256
  filteredCommands,
@@ -564,7 +1259,15 @@ function InputArea({ onSubmit, isDisabled, registry }) {
564
1259
  isSubcommandMode,
565
1260
  setShowPopup
566
1261
  } = useAutocomplete(value, registry);
567
- const handleSubmit = (0, import_react3.useCallback)(
1262
+ const handlePaste = (0, import_react6.useCallback)((text) => {
1263
+ pasteIdRef.current += 1;
1264
+ const id = pasteIdRef.current;
1265
+ pasteStore.current.set(id, text);
1266
+ const lineCount = text.split("\n").length;
1267
+ const label = `[Pasted text #${id} +${lineCount} lines]`;
1268
+ setValue((prev) => prev ? `${prev} ${label}` : label);
1269
+ }, []);
1270
+ const handleSubmit = (0, import_react6.useCallback)(
568
1271
  (text) => {
569
1272
  const trimmed = text.trim();
570
1273
  if (trimmed.length === 0) return;
@@ -572,12 +1275,15 @@ function InputArea({ onSubmit, isDisabled, registry }) {
572
1275
  selectCommand(filteredCommands[selectedIndex]);
573
1276
  return;
574
1277
  }
1278
+ const expanded = expandPasteLabels(trimmed, pasteStore.current);
575
1279
  setValue("");
576
- onSubmit(trimmed);
1280
+ pasteStore.current.clear();
1281
+ pasteIdRef.current = 0;
1282
+ onSubmit(expanded);
577
1283
  },
578
1284
  [showPopup, filteredCommands, selectedIndex, onSubmit]
579
1285
  );
580
- const selectCommand = (0, import_react3.useCallback)(
1286
+ const selectCommand = (0, import_react6.useCallback)(
581
1287
  (cmd) => {
582
1288
  const parsed = parseSlashInput(value);
583
1289
  if (parsed.parentCommand) {
@@ -596,7 +1302,7 @@ function InputArea({ onSubmit, isDisabled, registry }) {
596
1302
  },
597
1303
  [value, onSubmit, setSelectedIndex]
598
1304
  );
599
- (0, import_ink6.useInput)(
1305
+ (0, import_ink7.useInput)(
600
1306
  (_input, key) => {
601
1307
  if (!showPopup) return;
602
1308
  if (key.upArrow) {
@@ -612,8 +1318,16 @@ function InputArea({ onSubmit, isDisabled, registry }) {
612
1318
  },
613
1319
  { isActive: showPopup && !isDisabled }
614
1320
  );
615
- return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Box, { flexDirection: "column", children: [
616
- showPopup && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
1321
+ (0, import_ink7.useInput)(
1322
+ (_input, key) => {
1323
+ if ((key.backspace || key.delete) && pendingPrompt) {
1324
+ onCancelQueue?.();
1325
+ }
1326
+ },
1327
+ { isActive: !!pendingPrompt }
1328
+ );
1329
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { flexDirection: "column", children: [
1330
+ showPopup && /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
617
1331
  SlashAutocomplete,
618
1332
  {
619
1333
  commands: filteredCommands,
@@ -622,41 +1336,100 @@ function InputArea({ onSubmit, isDisabled, registry }) {
622
1336
  isSubcommandMode
623
1337
  }
624
1338
  ),
625
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Box, { borderStyle: "single", borderColor: isDisabled ? "gray" : "green", paddingLeft: 1, children: isDisabled ? /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(WaveText, { text: " Waiting for response..." }) : /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(import_ink6.Box, { children: [
626
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(import_ink6.Text, { color: "green", bold: true, children: "> " }),
627
- /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
628
- CjkTextInput,
629
- {
630
- value,
631
- onChange: setValue,
632
- onSubmit: handleSubmit,
633
- placeholder: "Type a message or /help"
634
- }
635
- )
636
- ] }) })
1339
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1340
+ import_ink7.Box,
1341
+ {
1342
+ borderStyle: "single",
1343
+ borderColor: isAborting ? "yellow" : pendingPrompt ? "cyan" : isDisabled ? "gray" : "green",
1344
+ paddingLeft: 1,
1345
+ children: isAborting ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "yellow", children: " Interrupting..." }) : pendingPrompt ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { color: "cyan", children: [
1346
+ " ",
1347
+ "Queued: ",
1348
+ pendingPrompt.length > 50 ? pendingPrompt.slice(0, 47) + "..." : pendingPrompt,
1349
+ " ",
1350
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { dimColor: true, children: "(Backspace to cancel)" })
1351
+ ] }) : isDisabled ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(WaveText, { text: " Waiting for response... (ESC to interrupt)" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { children: [
1352
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "green", bold: true, children: "> " }),
1353
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
1354
+ CjkTextInput,
1355
+ {
1356
+ value,
1357
+ onChange: setValue,
1358
+ onSubmit: handleSubmit,
1359
+ onPaste: handlePaste,
1360
+ placeholder: "Type a message or /help",
1361
+ availableWidth
1362
+ }
1363
+ )
1364
+ ] })
1365
+ }
1366
+ )
1367
+ ] });
1368
+ }
1369
+
1370
+ // src/ui/ConfirmPrompt.tsx
1371
+ var import_react7 = require("react");
1372
+ var import_ink8 = require("ink");
1373
+ var import_jsx_runtime8 = require("react/jsx-runtime");
1374
+ function ConfirmPrompt({
1375
+ message,
1376
+ options = ["Yes", "No"],
1377
+ onSelect
1378
+ }) {
1379
+ const [selected, setSelected] = (0, import_react7.useState)(0);
1380
+ const resolvedRef = (0, import_react7.useRef)(false);
1381
+ const doSelect = (0, import_react7.useCallback)(
1382
+ (index) => {
1383
+ if (resolvedRef.current) return;
1384
+ resolvedRef.current = true;
1385
+ onSelect(index);
1386
+ },
1387
+ [onSelect]
1388
+ );
1389
+ (0, import_ink8.useInput)((input, key) => {
1390
+ if (resolvedRef.current) return;
1391
+ if (key.leftArrow || key.upArrow) {
1392
+ setSelected((prev) => prev > 0 ? prev - 1 : prev);
1393
+ } else if (key.rightArrow || key.downArrow) {
1394
+ setSelected((prev) => prev < options.length - 1 ? prev + 1 : prev);
1395
+ } else if (key.return) {
1396
+ doSelect(selected);
1397
+ } else if (input === "y" && options.length === 2) {
1398
+ doSelect(0);
1399
+ } else if (input === "n" && options.length === 2) {
1400
+ doSelect(1);
1401
+ }
1402
+ });
1403
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
1404
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "yellow", children: message }),
1405
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Box, { marginTop: 1, children: options.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
1406
+ i === selected ? "> " : " ",
1407
+ opt
1408
+ ] }) }, opt)) }),
1409
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { dimColor: true, children: " arrow keys to select, Enter to confirm" })
637
1410
  ] });
638
1411
  }
639
1412
 
640
1413
  // src/ui/PermissionPrompt.tsx
641
- var import_react4 = __toESM(require("react"), 1);
642
- var import_ink7 = require("ink");
643
- var import_jsx_runtime7 = require("react/jsx-runtime");
1414
+ var import_react8 = __toESM(require("react"), 1);
1415
+ var import_ink9 = require("ink");
1416
+ var import_jsx_runtime9 = require("react/jsx-runtime");
644
1417
  var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
645
- function formatArgs2(args) {
1418
+ function formatArgs(args) {
646
1419
  const entries = Object.entries(args);
647
1420
  if (entries.length === 0) return "(no arguments)";
648
1421
  return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
649
1422
  }
650
1423
  function PermissionPrompt({ request }) {
651
- const [selected, setSelected] = import_react4.default.useState(0);
652
- const resolvedRef = import_react4.default.useRef(false);
653
- const prevRequestRef = import_react4.default.useRef(request);
1424
+ const [selected, setSelected] = import_react8.default.useState(0);
1425
+ const resolvedRef = import_react8.default.useRef(false);
1426
+ const prevRequestRef = import_react8.default.useRef(request);
654
1427
  if (prevRequestRef.current !== request) {
655
1428
  prevRequestRef.current = request;
656
1429
  resolvedRef.current = false;
657
1430
  setSelected(0);
658
1431
  }
659
- const doResolve = import_react4.default.useCallback(
1432
+ const doResolve = import_react8.default.useCallback(
660
1433
  (index) => {
661
1434
  if (resolvedRef.current) return;
662
1435
  resolvedRef.current = true;
@@ -666,7 +1439,7 @@ function PermissionPrompt({ request }) {
666
1439
  },
667
1440
  [request]
668
1441
  );
669
- (0, import_ink7.useInput)((input, key) => {
1442
+ (0, import_ink9.useInput)((input, key) => {
670
1443
  if (resolvedRef.current) return;
671
1444
  if (key.upArrow || key.leftArrow) {
672
1445
  setSelected((prev) => prev > 0 ? prev - 1 : prev);
@@ -682,364 +1455,692 @@ function PermissionPrompt({ request }) {
682
1455
  doResolve(2);
683
1456
  }
684
1457
  });
685
- return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
686
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "yellow", bold: true, children: "[Permission Required]" }),
687
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { children: [
688
- "Tool:",
689
- " ",
690
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "cyan", bold: true, children: request.toolName })
1458
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
1459
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "yellow", bold: true, children: "[Permission Required]" }),
1460
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { children: [
1461
+ "Tool:",
1462
+ " ",
1463
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "cyan", bold: true, children: request.toolName })
1464
+ ] }),
1465
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { dimColor: true, children: [
1466
+ " ",
1467
+ formatArgs(request.toolArgs)
1468
+ ] }),
1469
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { marginTop: 1, children: OPTIONS.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
1470
+ i === selected ? "> " : " ",
1471
+ opt
1472
+ ] }) }, opt)) }),
1473
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
1474
+ ] });
1475
+ }
1476
+
1477
+ // src/ui/StreamingIndicator.tsx
1478
+ var import_ink10 = require("ink");
1479
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1480
+ function getToolStyle(t) {
1481
+ if (t.isRunning) return { color: "yellow", icon: "\u27F3", strikethrough: false };
1482
+ if (t.result === "error") return { color: "red", icon: "\u2717", strikethrough: true };
1483
+ if (t.result === "denied") return { color: "yellowBright", icon: "\u2298", strikethrough: true };
1484
+ return { color: "green", icon: "\u2713", strikethrough: false };
1485
+ }
1486
+ function StreamingIndicator({ text, activeTools }) {
1487
+ const hasTools = activeTools.length > 0;
1488
+ const hasText = text.length > 0;
1489
+ if (!hasTools && !hasText) {
1490
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_jsx_runtime10.Fragment, {});
1491
+ }
1492
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
1493
+ hasTools && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", marginBottom: 1, children: [
1494
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "white", bold: true, children: "Tools:" }),
1495
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { children: " " }),
1496
+ activeTools.map((t, i) => {
1497
+ const { color, icon, strikethrough } = getToolStyle(t);
1498
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", children: [
1499
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Text, { color, strikethrough, children: [
1500
+ " ",
1501
+ icon,
1502
+ " ",
1503
+ t.toolName,
1504
+ "(",
1505
+ t.firstArg,
1506
+ ")"
1507
+ ] }),
1508
+ t.diffLines && t.diffLines.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(DiffBlock, { file: t.diffFile, lines: t.diffLines })
1509
+ ] }, `${t.toolName}-${i}`);
1510
+ })
1511
+ ] }),
1512
+ hasText && /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_ink10.Box, { flexDirection: "column", marginBottom: 1, children: [
1513
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { color: "cyan", bold: true, children: "Robota:" }),
1514
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { children: " " }),
1515
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(import_ink10.Text, { wrap: "wrap", children: renderMarkdown(text) }) })
1516
+ ] })
1517
+ ] });
1518
+ }
1519
+
1520
+ // src/ui/PluginTUI.tsx
1521
+ var import_react11 = require("react");
1522
+
1523
+ // src/ui/MenuSelect.tsx
1524
+ var import_react9 = require("react");
1525
+ var import_ink11 = require("ink");
1526
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1527
+ function MenuSelect({
1528
+ title,
1529
+ items,
1530
+ onSelect,
1531
+ onBack,
1532
+ loading,
1533
+ error
1534
+ }) {
1535
+ const [selected, setSelected] = (0, import_react9.useState)(0);
1536
+ const selectedRef = (0, import_react9.useRef)(0);
1537
+ const resolvedRef = (0, import_react9.useRef)(false);
1538
+ const doSelect = (0, import_react9.useCallback)(
1539
+ (index) => {
1540
+ if (resolvedRef.current || items.length === 0) return;
1541
+ resolvedRef.current = true;
1542
+ onSelect(items[index].value);
1543
+ },
1544
+ [items, onSelect]
1545
+ );
1546
+ (0, import_ink11.useInput)((input, key) => {
1547
+ if (resolvedRef.current) return;
1548
+ if (key.escape) {
1549
+ resolvedRef.current = true;
1550
+ onBack();
1551
+ return;
1552
+ }
1553
+ if (loading || error || items.length === 0) return;
1554
+ if (key.upArrow) {
1555
+ const next = selectedRef.current > 0 ? selectedRef.current - 1 : selectedRef.current;
1556
+ selectedRef.current = next;
1557
+ setSelected(next);
1558
+ } else if (key.downArrow) {
1559
+ const next = selectedRef.current < items.length - 1 ? selectedRef.current + 1 : selectedRef.current;
1560
+ selectedRef.current = next;
1561
+ setSelected(next);
1562
+ } else if (key.return) {
1563
+ doSelect(selectedRef.current);
1564
+ }
1565
+ });
1566
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
1567
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { color: "yellow", bold: true, children: title }),
1568
+ loading && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Box, { marginTop: 1, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { dimColor: true, children: "Loading..." }) }),
1569
+ error && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { marginTop: 1, flexDirection: "column", children: [
1570
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { color: "red", children: error }),
1571
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { dimColor: true, children: "Press Esc to go back" })
1572
+ ] }),
1573
+ !loading && !error && /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Box, { flexDirection: "column", marginTop: 1, children: items.map((item, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Box, { children: [
1574
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
1575
+ i === selected ? "> " : " ",
1576
+ item.label
1577
+ ] }),
1578
+ item.hint && /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_ink11.Text, { dimColor: true, children: [
1579
+ " ",
1580
+ item.hint
1581
+ ] })
1582
+ ] }, item.value)) }),
1583
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(import_ink11.Text, { dimColor: true, children: loading || error ? "" : " \u2191\u2193 Navigate Enter Select Esc Back" })
1584
+ ] });
1585
+ }
1586
+
1587
+ // src/ui/TextPrompt.tsx
1588
+ var import_react10 = require("react");
1589
+ var import_ink12 = require("ink");
1590
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1591
+ function TextPrompt({
1592
+ title,
1593
+ placeholder,
1594
+ onSubmit,
1595
+ onCancel,
1596
+ validate
1597
+ }) {
1598
+ const [value, setValue] = (0, import_react10.useState)("");
1599
+ const [error, setError] = (0, import_react10.useState)();
1600
+ const resolvedRef = (0, import_react10.useRef)(false);
1601
+ const valueRef = (0, import_react10.useRef)("");
1602
+ const handleSubmit = (0, import_react10.useCallback)(() => {
1603
+ if (resolvedRef.current) return;
1604
+ const trimmed = valueRef.current.trim();
1605
+ if (!trimmed) return;
1606
+ if (validate) {
1607
+ const err = validate(trimmed);
1608
+ if (err) {
1609
+ setError(err);
1610
+ return;
1611
+ }
1612
+ }
1613
+ resolvedRef.current = true;
1614
+ onSubmit(trimmed);
1615
+ }, [validate, onSubmit]);
1616
+ (0, import_ink12.useInput)((input, key) => {
1617
+ if (resolvedRef.current) return;
1618
+ if (key.escape) {
1619
+ resolvedRef.current = true;
1620
+ onCancel();
1621
+ return;
1622
+ }
1623
+ if (key.return) {
1624
+ handleSubmit();
1625
+ return;
1626
+ }
1627
+ if (key.backspace || key.delete) {
1628
+ valueRef.current = valueRef.current.slice(0, -1);
1629
+ setValue(valueRef.current);
1630
+ setError(void 0);
1631
+ return;
1632
+ }
1633
+ if (input && !key.ctrl && !key.meta) {
1634
+ valueRef.current = valueRef.current + input;
1635
+ setValue(valueRef.current);
1636
+ setError(void 0);
1637
+ }
1638
+ });
1639
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
1640
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "yellow", bold: true, children: title }),
1641
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_ink12.Box, { marginTop: 1, children: [
1642
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "cyan", children: "> " }),
1643
+ value ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { children: value }) : placeholder ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { dimColor: true, children: placeholder }) : null,
1644
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "cyan", children: "\u2588" })
691
1645
  ] }),
692
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { dimColor: true, children: [
693
- " ",
694
- formatArgs2(request.toolArgs)
695
- ] }),
696
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Box, { marginTop: 1, children: OPTIONS.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
697
- i === selected ? "> " : " ",
698
- opt
699
- ] }) }, opt)) }),
700
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
1646
+ error && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { color: "red", children: error }),
1647
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(import_ink12.Text, { dimColor: true, children: " Enter Submit Esc Cancel" })
701
1648
  ] });
702
1649
  }
703
1650
 
704
- // src/ui/App.tsx
705
- var import_jsx_runtime8 = require("react/jsx-runtime");
706
- var msgIdCounter = 0;
707
- function nextId() {
708
- msgIdCounter += 1;
709
- return `msg_${msgIdCounter}`;
1651
+ // src/ui/plugin-tui-handlers.ts
1652
+ function handleMainSelect(value, nav) {
1653
+ if (value === "marketplace") {
1654
+ nav.push({ screen: "marketplace-list" });
1655
+ } else if (value === "installed") {
1656
+ nav.push({ screen: "installed-list" });
1657
+ }
710
1658
  }
711
- var NOOP_TERMINAL = {
712
- write: () => {
713
- },
714
- writeLine: () => {
715
- },
716
- writeMarkdown: () => {
717
- },
718
- writeError: () => {
719
- },
720
- prompt: () => Promise.resolve(""),
721
- select: () => Promise.resolve(0),
722
- spinner: () => ({ stop: () => {
723
- }, update: () => {
724
- } })
725
- };
726
- function useSession(props) {
727
- const [permissionRequest, setPermissionRequest] = (0, import_react5.useState)(null);
728
- const [streamingText, setStreamingText] = (0, import_react5.useState)("");
729
- const permissionQueueRef = (0, import_react5.useRef)([]);
730
- const processingRef = (0, import_react5.useRef)(false);
731
- const processNextPermission = (0, import_react5.useCallback)(() => {
732
- if (processingRef.current) return;
733
- const next = permissionQueueRef.current[0];
734
- if (!next) {
735
- setPermissionRequest(null);
736
- return;
737
- }
738
- processingRef.current = true;
739
- setPermissionRequest({
740
- toolName: next.toolName,
741
- toolArgs: next.toolArgs,
742
- resolve: (result) => {
743
- permissionQueueRef.current.shift();
744
- processingRef.current = false;
745
- setPermissionRequest(null);
746
- next.resolve(result);
747
- setTimeout(() => processNextPermission(), 0);
748
- }
1659
+ function handleMarketplaceListSelect(value, nav) {
1660
+ if (value === "__add__") {
1661
+ nav.push({ screen: "marketplace-add" });
1662
+ } else {
1663
+ nav.push({ screen: "marketplace-action", context: { marketplace: value } });
1664
+ }
1665
+ }
1666
+ function handleMarketplaceActionSelect(value, marketplace, callbacks, nav) {
1667
+ if (value === "browse") {
1668
+ nav.push({ screen: "marketplace-browse", context: { marketplace } });
1669
+ } else if (value === "update") {
1670
+ callbacks.marketplaceUpdate(marketplace).then(() => {
1671
+ nav.notify(`Updated marketplace "${marketplace}".`);
1672
+ nav.pop();
1673
+ }).catch((err) => {
1674
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
749
1675
  });
750
- }, []);
751
- const sessionRef = (0, import_react5.useRef)(null);
752
- if (sessionRef.current === null) {
753
- const permissionHandler = (toolName, toolArgs) => {
754
- return new Promise((resolve) => {
755
- permissionQueueRef.current.push({ toolName, toolArgs, resolve });
756
- processNextPermission();
757
- });
758
- };
759
- const onTextDelta = (delta) => {
760
- setStreamingText((prev) => prev + delta);
761
- };
762
- const paths = (0, import_agent_sdk.projectPaths)(props.cwd ?? process.cwd());
763
- sessionRef.current = (0, import_agent_sdk.createSession)({
764
- config: props.config,
765
- context: props.context,
766
- terminal: NOOP_TERMINAL,
767
- sessionLogger: new import_agent_sdk.FileSessionLogger(paths.logs),
768
- projectInfo: props.projectInfo,
769
- sessionStore: props.sessionStore,
770
- permissionMode: props.permissionMode,
771
- maxTurns: props.maxTurns,
772
- permissionHandler,
773
- onTextDelta
1676
+ } else if (value === "remove") {
1677
+ nav.setConfirm({
1678
+ message: `Remove marketplace "${marketplace}" and all its plugins?`,
1679
+ onConfirm: () => {
1680
+ nav.setConfirm(void 0);
1681
+ callbacks.marketplaceRemove(marketplace).then(() => {
1682
+ nav.notify(`Removed marketplace "${marketplace}".`);
1683
+ nav.popN(2);
1684
+ }).catch((err) => {
1685
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
1686
+ });
1687
+ },
1688
+ onCancel: () => nav.setConfirm(void 0)
774
1689
  });
775
1690
  }
776
- const clearStreamingText = (0, import_react5.useCallback)(() => setStreamingText(""), []);
777
- return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText };
778
- }
779
- function useMessages() {
780
- const [messages, setMessages] = (0, import_react5.useState)([]);
781
- const addMessage = (0, import_react5.useCallback)((msg) => {
782
- setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
783
- }, []);
784
- return { messages, setMessages, addMessage };
785
1691
  }
786
- var HELP_TEXT = [
787
- "Available commands:",
788
- " /help \u2014 Show this help",
789
- " /clear \u2014 Clear conversation",
790
- " /compact [instr] \u2014 Compact context (optional focus instructions)",
791
- " /mode [m] \u2014 Show/change permission mode",
792
- " /cost \u2014 Show session info",
793
- " /exit \u2014 Exit CLI"
794
- ].join("\n");
795
- function handleModeCommand(arg, session, addMessage) {
796
- const validModes = ["plan", "default", "acceptEdits", "bypassPermissions"];
797
- if (!arg) {
798
- addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
799
- } else if (validModes.includes(arg)) {
800
- session.setPermissionMode(arg);
801
- addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
1692
+ function handleMarketplaceBrowseSelect(value, marketplace, items, nav) {
1693
+ const fullId = `${value}@${marketplace}`;
1694
+ const item = items.find((i) => i.value === value);
1695
+ if (item?.hint === "installed") {
1696
+ nav.push({ screen: "installed-action", context: { pluginId: fullId } });
802
1697
  } else {
803
- addMessage({ role: "system", content: `Invalid mode. Valid: ${validModes.join(" | ")}` });
1698
+ nav.push({ screen: "marketplace-install-scope", context: { marketplace, pluginId: fullId } });
804
1699
  }
805
- return true;
806
1700
  }
807
- async function executeSlashCommand(cmd, parts, session, addMessage, setMessages, exit, registry) {
808
- switch (cmd) {
809
- case "help":
810
- addMessage({ role: "system", content: HELP_TEXT });
811
- return true;
812
- case "clear":
813
- setMessages([]);
814
- session.clearHistory();
815
- addMessage({ role: "system", content: "Conversation cleared." });
816
- return true;
817
- case "compact": {
818
- const instructions = parts.slice(1).join(" ").trim() || void 0;
819
- const before = session.getContextState().usedPercentage;
820
- addMessage({ role: "system", content: "Compacting context..." });
821
- await session.compact(instructions);
822
- const after = session.getContextState().usedPercentage;
823
- addMessage({
824
- role: "system",
825
- content: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`
826
- });
827
- return true;
828
- }
829
- case "mode":
830
- return handleModeCommand(parts[1], session, addMessage);
831
- case "cost":
832
- addMessage({
833
- role: "system",
834
- content: `Session: ${session.getSessionId()}
835
- Messages: ${session.getMessageCount()}`
836
- });
837
- return true;
838
- case "permissions": {
839
- const mode = session.getPermissionMode();
840
- const sessionAllowed = session.getSessionAllowedTools();
841
- const lines = [`Permission mode: ${mode}`];
842
- if (sessionAllowed.length > 0) {
843
- lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
844
- } else {
845
- lines.push("No session-approved tools.");
846
- }
847
- addMessage({ role: "system", content: lines.join("\n") });
848
- return true;
849
- }
850
- case "context": {
851
- const ctx = session.getContextState();
852
- addMessage({
853
- role: "system",
854
- content: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`
855
- });
856
- return true;
857
- }
858
- case "exit":
859
- exit();
860
- return true;
861
- default: {
862
- const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
863
- if (skillCmd) {
864
- addMessage({ role: "system", content: `Invoking skill: ${cmd}` });
865
- return false;
866
- }
867
- addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
868
- return true;
869
- }
870
- }
1701
+ function handleInstallScopeSelect(value, pluginId, callbacks, nav) {
1702
+ const scope = value;
1703
+ callbacks.install(pluginId, scope).then(() => {
1704
+ nav.notify(`Installed plugin "${pluginId}" (${scope} scope).`);
1705
+ nav.popN(2);
1706
+ }).catch((err) => {
1707
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
1708
+ });
871
1709
  }
872
- function useSlashCommands(session, addMessage, setMessages, exit, registry) {
873
- return (0, import_react5.useCallback)(
874
- async (input) => {
875
- const parts = input.slice(1).split(/\s+/);
876
- const cmd = parts[0]?.toLowerCase() ?? "";
877
- return executeSlashCommand(cmd, parts, session, addMessage, setMessages, exit, registry);
1710
+ function handleInstalledListSelect(value, callbacks, nav) {
1711
+ nav.setConfirm({
1712
+ message: `Uninstall plugin "${value}"?`,
1713
+ onConfirm: () => {
1714
+ nav.setConfirm(void 0);
1715
+ callbacks.uninstall(value).then(() => {
1716
+ nav.notify(`Uninstalled plugin "${value}".`);
1717
+ nav.refresh();
1718
+ }).catch((err) => {
1719
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
1720
+ });
878
1721
  },
879
- [session, addMessage, setMessages, exit, registry]
880
- );
881
- }
882
- function StreamingIndicator({ text }) {
883
- if (text) {
884
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", children: [
885
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Text, { color: "cyan", bold: true, children: [
886
- "Robota:",
887
- " "
888
- ] }),
889
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { children: " " }),
890
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { wrap: "wrap", children: renderMarkdown(text) }) })
891
- ] });
892
- }
893
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "yellow", children: "Thinking..." });
1722
+ onCancel: () => nav.setConfirm(void 0)
1723
+ });
894
1724
  }
895
- async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextPercentage) {
896
- setIsThinking(true);
897
- clearStreamingText();
898
- try {
899
- const response = await session.run(prompt);
900
- clearStreamingText();
901
- addMessage({ role: "assistant", content: response || "(empty response)" });
902
- setContextPercentage(session.getContextState().usedPercentage);
903
- } catch (err) {
904
- clearStreamingText();
905
- if (err instanceof DOMException && err.name === "AbortError") {
906
- addMessage({ role: "system", content: "Cancelled." });
907
- } else {
908
- const errMsg = err instanceof Error ? err.message : String(err);
909
- addMessage({ role: "system", content: `Error: ${errMsg}` });
910
- }
911
- } finally {
912
- setIsThinking(false);
1725
+ function handleInstalledActionSelect(value, pluginId, callbacks, nav) {
1726
+ if (value === "uninstall") {
1727
+ nav.setConfirm({
1728
+ message: `Uninstall plugin "${pluginId}"?`,
1729
+ onConfirm: () => {
1730
+ nav.setConfirm(void 0);
1731
+ callbacks.uninstall(pluginId).then(() => {
1732
+ nav.notify(`Uninstalled plugin "${pluginId}".`);
1733
+ nav.popN(2);
1734
+ }).catch((err) => {
1735
+ nav.notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
1736
+ });
1737
+ },
1738
+ onCancel: () => nav.setConfirm(void 0)
1739
+ });
913
1740
  }
914
1741
  }
915
- function buildSkillPrompt(input, registry) {
916
- const parts = input.slice(1).split(/\s+/);
917
- const cmd = parts[0]?.toLowerCase() ?? "";
918
- const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
919
- if (!skillCmd) return null;
920
- const args = parts.slice(1).join(" ").trim();
921
- return args ? `Use the "${cmd}" skill: ${args}` : `Use the "${cmd}" skill: ${skillCmd.description}`;
922
- }
923
- function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextPercentage, registry) {
924
- return (0, import_react5.useCallback)(
925
- async (input) => {
926
- if (input.startsWith("/")) {
927
- const handled = await handleSlashCommand(input);
928
- if (handled) {
929
- setContextPercentage(session.getContextState().usedPercentage);
930
- return;
1742
+
1743
+ // src/ui/PluginTUI.tsx
1744
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1745
+ function PluginTUI({ callbacks, onClose, addMessage }) {
1746
+ const [stack, setStack] = (0, import_react11.useState)([{ screen: "main" }]);
1747
+ const [items, setItems] = (0, import_react11.useState)([]);
1748
+ const [loading, setLoading] = (0, import_react11.useState)(false);
1749
+ const [error, setError] = (0, import_react11.useState)();
1750
+ const [confirm, setConfirm] = (0, import_react11.useState)();
1751
+ const [refreshCounter, setRefreshCounter] = (0, import_react11.useState)(0);
1752
+ const current = stack[stack.length - 1] ?? { screen: "main" };
1753
+ const push = (0, import_react11.useCallback)((state) => {
1754
+ setStack((prev) => [...prev, state]);
1755
+ setItems([]);
1756
+ setError(void 0);
1757
+ }, []);
1758
+ const pop = (0, import_react11.useCallback)(() => {
1759
+ setStack((prev) => {
1760
+ if (prev.length <= 1) {
1761
+ onClose();
1762
+ return prev;
1763
+ }
1764
+ return prev.slice(0, -1);
1765
+ });
1766
+ setItems([]);
1767
+ setError(void 0);
1768
+ }, [onClose]);
1769
+ const popN = (0, import_react11.useCallback)(
1770
+ (n) => {
1771
+ setStack((prev) => {
1772
+ const next = prev.slice(0, Math.max(1, prev.length - n));
1773
+ if (next.length === 0) {
1774
+ onClose();
1775
+ return prev;
931
1776
  }
932
- const prompt = buildSkillPrompt(input, registry);
933
- if (!prompt) return;
934
- return runSessionPrompt(
935
- prompt,
936
- session,
937
- addMessage,
938
- clearStreamingText,
939
- setIsThinking,
940
- setContextPercentage
1777
+ return next;
1778
+ });
1779
+ setItems([]);
1780
+ setError(void 0);
1781
+ },
1782
+ [onClose]
1783
+ );
1784
+ const notify = (0, import_react11.useCallback)(
1785
+ (content) => {
1786
+ addMessage?.({ role: "system", content });
1787
+ },
1788
+ [addMessage]
1789
+ );
1790
+ const refresh = (0, import_react11.useCallback)(() => {
1791
+ setItems([]);
1792
+ setRefreshCounter((c) => c + 1);
1793
+ }, []);
1794
+ const nav = { push, pop, popN, notify, setConfirm, refresh };
1795
+ (0, import_react11.useEffect)(() => {
1796
+ const screen2 = current.screen;
1797
+ if (screen2 === "marketplace-list") {
1798
+ setLoading(true);
1799
+ callbacks.marketplaceList().then((sources) => {
1800
+ const baseItems = [{ label: "Add Marketplace", value: "__add__" }];
1801
+ const sourceItems = sources.map((s) => ({
1802
+ label: s.name,
1803
+ value: s.name,
1804
+ hint: s.type
1805
+ }));
1806
+ setItems([...baseItems, ...sourceItems]);
1807
+ setLoading(false);
1808
+ }).catch((err) => {
1809
+ setError(err instanceof Error ? err.message : String(err));
1810
+ setLoading(false);
1811
+ });
1812
+ } else if (screen2 === "marketplace-browse") {
1813
+ const marketplace = current.context?.marketplace ?? "";
1814
+ setLoading(true);
1815
+ callbacks.listAvailablePlugins(marketplace).then((plugins) => {
1816
+ setItems(
1817
+ plugins.map((p) => ({
1818
+ label: p.name,
1819
+ value: p.name,
1820
+ hint: p.installed ? "installed" : p.description
1821
+ }))
1822
+ );
1823
+ setLoading(false);
1824
+ }).catch((err) => {
1825
+ setError(err instanceof Error ? err.message : String(err));
1826
+ setLoading(false);
1827
+ });
1828
+ } else if (screen2 === "installed-list") {
1829
+ setLoading(true);
1830
+ callbacks.listInstalled().then((plugins) => {
1831
+ setItems(
1832
+ plugins.map((p) => ({
1833
+ label: p.name,
1834
+ value: p.name,
1835
+ hint: p.description
1836
+ }))
941
1837
  );
1838
+ setLoading(false);
1839
+ }).catch((err) => {
1840
+ setError(err instanceof Error ? err.message : String(err));
1841
+ setLoading(false);
1842
+ });
1843
+ }
1844
+ }, [stack.length, current.screen, current.context?.marketplace, callbacks, refreshCounter]);
1845
+ const handleSelect = (0, import_react11.useCallback)(
1846
+ (value) => {
1847
+ const screen2 = current.screen;
1848
+ const ctx = current.context;
1849
+ if (screen2 === "main") handleMainSelect(value, nav);
1850
+ else if (screen2 === "marketplace-list") handleMarketplaceListSelect(value, nav);
1851
+ else if (screen2 === "marketplace-action")
1852
+ handleMarketplaceActionSelect(value, ctx?.marketplace ?? "", callbacks, nav);
1853
+ else if (screen2 === "marketplace-browse")
1854
+ handleMarketplaceBrowseSelect(value, ctx?.marketplace ?? "", items, nav);
1855
+ else if (screen2 === "marketplace-install-scope")
1856
+ handleInstallScopeSelect(value, ctx?.pluginId ?? "", callbacks, nav);
1857
+ else if (screen2 === "installed-list") handleInstalledListSelect(value, callbacks, nav);
1858
+ else if (screen2 === "installed-action")
1859
+ handleInstalledActionSelect(value, ctx?.pluginId ?? "", callbacks, nav);
1860
+ },
1861
+ [current, items, callbacks, push, pop, popN, notify, setConfirm, refresh]
1862
+ );
1863
+ const handleTextSubmit = (0, import_react11.useCallback)(
1864
+ (value) => {
1865
+ if (current.screen === "marketplace-add") {
1866
+ callbacks.marketplaceAdd(value).then((name) => {
1867
+ notify(`Added marketplace "${name}" from ${value}.`);
1868
+ pop();
1869
+ }).catch((err) => {
1870
+ notify(`Error: ${err instanceof Error ? err.message : String(err)}`);
1871
+ pop();
1872
+ });
942
1873
  }
943
- addMessage({ role: "user", content: input });
944
- return runSessionPrompt(
945
- input,
946
- session,
947
- addMessage,
948
- clearStreamingText,
949
- setIsThinking,
950
- setContextPercentage
951
- );
952
1874
  },
953
- [
954
- session,
955
- addMessage,
956
- handleSlashCommand,
957
- clearStreamingText,
958
- setIsThinking,
959
- setContextPercentage,
960
- registry
1875
+ [current.screen, callbacks, notify, pop]
1876
+ );
1877
+ if (confirm) {
1878
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1879
+ ConfirmPrompt,
1880
+ {
1881
+ message: confirm.message,
1882
+ onSelect: (index) => {
1883
+ if (index === 0) confirm.onConfirm();
1884
+ else confirm.onCancel();
1885
+ }
1886
+ }
1887
+ );
1888
+ }
1889
+ const screen = current.screen;
1890
+ if (screen === "marketplace-add") {
1891
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1892
+ TextPrompt,
1893
+ {
1894
+ title: "Add Marketplace Source",
1895
+ placeholder: "owner/repo or git URL",
1896
+ onSubmit: handleTextSubmit,
1897
+ onCancel: pop,
1898
+ validate: (v) => !v.includes("/") ? "Must be owner/repo or a git URL" : void 0
1899
+ }
1900
+ );
1901
+ }
1902
+ if (screen === "marketplace-action") {
1903
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1904
+ MenuSelect,
1905
+ {
1906
+ title: `Marketplace: ${current.context?.marketplace ?? ""}`,
1907
+ items: [
1908
+ { label: "Browse plugins", value: "browse" },
1909
+ { label: "Update", value: "update" },
1910
+ { label: "Remove", value: "remove" }
1911
+ ],
1912
+ onSelect: handleSelect,
1913
+ onBack: pop
1914
+ },
1915
+ stack.length
1916
+ );
1917
+ }
1918
+ if (screen === "marketplace-install-scope") {
1919
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1920
+ MenuSelect,
1921
+ {
1922
+ title: `Install scope for "${current.context?.pluginId ?? ""}"`,
1923
+ items: [
1924
+ { label: "User scope", value: "user" },
1925
+ { label: "Project scope", value: "project" }
1926
+ ],
1927
+ onSelect: handleSelect,
1928
+ onBack: pop
1929
+ },
1930
+ stack.length
1931
+ );
1932
+ }
1933
+ if (screen === "installed-action") {
1934
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1935
+ MenuSelect,
1936
+ {
1937
+ title: `Plugin: ${current.context?.pluginId ?? ""}`,
1938
+ items: [{ label: "Uninstall", value: "uninstall" }],
1939
+ onSelect: handleSelect,
1940
+ onBack: pop
1941
+ },
1942
+ stack.length
1943
+ );
1944
+ }
1945
+ const titleMap = {
1946
+ main: "Plugin Management",
1947
+ "marketplace-list": "Marketplace",
1948
+ "marketplace-browse": `Browse: ${current.context?.marketplace ?? ""}`,
1949
+ "installed-list": "Installed Plugins"
1950
+ };
1951
+ const staticItemsMap = {
1952
+ main: [
1953
+ { label: "Marketplace", value: "marketplace" },
1954
+ { label: "Installed Plugins", value: "installed" }
961
1955
  ]
1956
+ };
1957
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1958
+ MenuSelect,
1959
+ {
1960
+ title: titleMap[screen] ?? "Plugin Management",
1961
+ items: staticItemsMap[screen] ?? items,
1962
+ onSelect: handleSelect,
1963
+ onBack: pop,
1964
+ loading,
1965
+ error
1966
+ },
1967
+ `${screen}-${stack.length}-${refreshCounter}`
962
1968
  );
963
1969
  }
964
- function useCommandRegistry(cwd) {
965
- const registryRef = (0, import_react5.useRef)(null);
966
- if (registryRef.current === null) {
967
- const registry = new CommandRegistry();
968
- registry.addSource(new BuiltinCommandSource());
969
- registry.addSource(new SkillCommandSource(cwd));
970
- registryRef.current = registry;
971
- }
972
- return registryRef.current;
973
- }
1970
+
1971
+ // src/ui/App.tsx
1972
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1973
+ var EXIT_DELAY_MS = 500;
974
1974
  function App(props) {
975
- const { exit } = (0, import_ink8.useApp)();
976
- const { session, permissionRequest, streamingText, clearStreamingText } = useSession(props);
977
- const { messages, setMessages, addMessage } = useMessages();
978
- const [isThinking, setIsThinking] = (0, import_react5.useState)(false);
979
- const [contextPercentage, setContextPercentage] = (0, import_react5.useState)(0);
980
- const registry = useCommandRegistry(props.cwd ?? process.cwd());
981
- const handleSlashCommand = useSlashCommands(session, addMessage, setMessages, exit, registry);
982
- const handleSubmit = useSubmitHandler(
983
- session,
1975
+ const { exit } = (0, import_ink13.useApp)();
1976
+ const cwd = props.cwd ?? process.cwd();
1977
+ const {
1978
+ interactiveSession,
1979
+ registry,
1980
+ messages,
984
1981
  addMessage,
985
- handleSlashCommand,
986
- clearStreamingText,
987
- setIsThinking,
988
- setContextPercentage,
989
- registry
990
- );
991
- (0, import_ink8.useInput)(
1982
+ streamingText,
1983
+ activeTools,
1984
+ isThinking,
1985
+ isAborting,
1986
+ pendingPrompt,
1987
+ permissionRequest,
1988
+ contextState,
1989
+ handleSubmit: baseHandleSubmit,
1990
+ handleAbort,
1991
+ handleCancelQueue
1992
+ } = useInteractiveSession({
1993
+ config: props.config,
1994
+ context: props.context,
1995
+ projectInfo: props.projectInfo,
1996
+ sessionStore: props.sessionStore,
1997
+ permissionMode: props.permissionMode,
1998
+ maxTurns: props.maxTurns,
1999
+ cwd
2000
+ });
2001
+ const pluginCallbacks = usePluginCallbacks(cwd);
2002
+ const [pendingModelId, setPendingModelId] = (0, import_react12.useState)(null);
2003
+ const pendingModelChangeRef = (0, import_react12.useRef)(null);
2004
+ const [showPluginTUI, setShowPluginTUI] = (0, import_react12.useState)(false);
2005
+ const handleSubmit = async (input) => {
2006
+ await baseHandleSubmit(input);
2007
+ const sideEffects = interactiveSession;
2008
+ if (sideEffects._pendingModelId) {
2009
+ const modelId = sideEffects._pendingModelId;
2010
+ delete sideEffects._pendingModelId;
2011
+ pendingModelChangeRef.current = modelId;
2012
+ setPendingModelId(modelId);
2013
+ return;
2014
+ }
2015
+ if (sideEffects._pendingLanguage) {
2016
+ const lang = sideEffects._pendingLanguage;
2017
+ delete sideEffects._pendingLanguage;
2018
+ const settingsPath = getUserSettingsPath();
2019
+ const settings = readSettings(settingsPath);
2020
+ settings.language = lang;
2021
+ writeSettings(settingsPath, settings);
2022
+ addMessage((0, import_agent_core4.createSystemMessage)(`Language set to "${lang}". Restarting...`));
2023
+ setTimeout(() => exit(), EXIT_DELAY_MS);
2024
+ return;
2025
+ }
2026
+ if (sideEffects._resetRequested) {
2027
+ delete sideEffects._resetRequested;
2028
+ const settingsPath = getUserSettingsPath();
2029
+ if (deleteSettings(settingsPath)) {
2030
+ addMessage((0, import_agent_core4.createSystemMessage)(`Deleted ${settingsPath}. Exiting...`));
2031
+ } else {
2032
+ addMessage((0, import_agent_core4.createSystemMessage)("No user settings found."));
2033
+ }
2034
+ setTimeout(() => exit(), EXIT_DELAY_MS);
2035
+ return;
2036
+ }
2037
+ if (sideEffects._exitRequested) {
2038
+ delete sideEffects._exitRequested;
2039
+ setTimeout(() => exit(), EXIT_DELAY_MS);
2040
+ return;
2041
+ }
2042
+ if (sideEffects._triggerPluginTUI) {
2043
+ delete sideEffects._triggerPluginTUI;
2044
+ setShowPluginTUI(true);
2045
+ return;
2046
+ }
2047
+ };
2048
+ (0, import_ink13.useInput)(
992
2049
  (_input, key) => {
993
- if (key.ctrl && _input === "c") exit();
994
- if (key.escape && isThinking) session.abort();
2050
+ if (key.escape && isThinking) {
2051
+ handleAbort();
2052
+ }
995
2053
  },
996
- { isActive: !permissionRequest }
2054
+ { isActive: !permissionRequest && !showPluginTUI }
997
2055
  );
998
- return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", children: [
999
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
1000
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "cyan", bold: true, children: `
2056
+ const session = interactiveSession.getSession();
2057
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Box, { flexDirection: "column", children: [
2058
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
2059
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink13.Text, { color: "cyan", bold: true, children: `
1001
2060
  ____ ___ ____ ___ _____ _
1002
2061
  | _ \\ / _ \\| __ ) / _ \\_ _|/ \\
1003
2062
  | |_) | | | | _ \\| | | || | / _ \\
1004
2063
  | _ <| |_| | |_) | |_| || |/ ___ \\
1005
2064
  |_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
1006
2065
  ` }),
1007
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Text, { dimColor: true, children: [
2066
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Text, { dimColor: true, children: [
1008
2067
  " v",
1009
2068
  props.version ?? "0.0.0"
1010
2069
  ] })
1011
2070
  ] }),
1012
- /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
1013
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MessageList, { messages }),
1014
- isThinking && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(StreamingIndicator, { text: streamingText }) })
2071
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_ink13.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
2072
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(MessageList, { messages }),
2073
+ isThinking && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink13.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(StreamingIndicator, { text: streamingText, activeTools }) })
1015
2074
  ] }),
1016
- permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(PermissionPrompt, { request: permissionRequest }),
1017
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2075
+ permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(PermissionPrompt, { request: permissionRequest }),
2076
+ pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2077
+ ConfirmPrompt,
2078
+ {
2079
+ message: `Change model to ${(0, import_agent_core4.getModelName)(pendingModelId)}? This will restart the session.`,
2080
+ onSelect: (index) => {
2081
+ setPendingModelId(null);
2082
+ pendingModelChangeRef.current = null;
2083
+ if (index === 0) {
2084
+ try {
2085
+ const settingsPath = getUserSettingsPath();
2086
+ updateModelInSettings(settingsPath, pendingModelId);
2087
+ addMessage(
2088
+ (0, import_agent_core4.createSystemMessage)(
2089
+ `Model changed to ${(0, import_agent_core4.getModelName)(pendingModelId)}. Restarting...`
2090
+ )
2091
+ );
2092
+ setTimeout(() => exit(), EXIT_DELAY_MS);
2093
+ } catch (err) {
2094
+ addMessage(
2095
+ (0, import_agent_core4.createSystemMessage)(
2096
+ `Failed: ${err instanceof Error ? err.message : String(err)}`
2097
+ )
2098
+ );
2099
+ }
2100
+ } else {
2101
+ addMessage((0, import_agent_core4.createSystemMessage)("Model change cancelled."));
2102
+ }
2103
+ }
2104
+ }
2105
+ ),
2106
+ showPluginTUI && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2107
+ PluginTUI,
2108
+ {
2109
+ callbacks: pluginCallbacks,
2110
+ onClose: () => setShowPluginTUI(false),
2111
+ addMessage: (msg) => addMessage((0, import_agent_core4.createSystemMessage)(msg.content))
2112
+ }
2113
+ ),
2114
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1018
2115
  StatusBar,
1019
2116
  {
1020
2117
  permissionMode: session.getPermissionMode(),
1021
- modelName: props.config.provider.model,
2118
+ modelName: (0, import_agent_core4.getModelName)(props.config.provider.model),
1022
2119
  sessionId: session.getSessionId(),
1023
2120
  messageCount: messages.length,
1024
2121
  isThinking,
1025
- contextPercentage,
1026
- contextUsedTokens: session.getContextState().usedTokens,
1027
- contextMaxTokens: session.getContextState().maxTokens
2122
+ contextPercentage: contextState.percentage,
2123
+ contextUsedTokens: contextState.usedTokens,
2124
+ contextMaxTokens: contextState.maxTokens
1028
2125
  }
1029
2126
  ),
1030
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
2127
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1031
2128
  InputArea,
1032
2129
  {
1033
2130
  onSubmit: handleSubmit,
1034
- isDisabled: isThinking || !!permissionRequest,
2131
+ onCancelQueue: handleCancelQueue,
2132
+ isDisabled: !!permissionRequest || showPluginTUI || isThinking && !!pendingPrompt,
2133
+ isAborting,
2134
+ pendingPrompt,
1035
2135
  registry
1036
2136
  }
1037
- )
2137
+ ),
2138
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(import_ink13.Text, { children: " " })
1038
2139
  ] });
1039
2140
  }
1040
2141
 
1041
2142
  // src/ui/render.tsx
1042
- var import_jsx_runtime9 = require("react/jsx-runtime");
2143
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1043
2144
  function renderApp(options) {
1044
2145
  process.on("unhandledRejection", (reason) => {
1045
2146
  process.stderr.write(`
@@ -1050,26 +2151,47 @@ function renderApp(options) {
1050
2151
  `);
1051
2152
  }
1052
2153
  });
1053
- const instance = (0, import_ink9.render)(/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(App, { ...options }), {
2154
+ if (process.stdin.isTTY && process.stdout.isTTY) {
2155
+ process.stdout.write("\x1B[?2004h");
2156
+ }
2157
+ const instance = (0, import_ink14.render)(/* @__PURE__ */ (0, import_jsx_runtime15.jsx)(App, { ...options }), {
1054
2158
  exitOnCtrlC: true
1055
2159
  });
1056
- instance.waitUntilExit().catch((err) => {
2160
+ instance.waitUntilExit().then(() => {
2161
+ if (process.stdout.isTTY) {
2162
+ process.stdout.write("\x1B[?2004l");
2163
+ }
2164
+ process.exit(0);
2165
+ }).catch((err) => {
1057
2166
  if (err) {
1058
2167
  process.stderr.write(`
1059
2168
  [EXIT ERROR] ${err}
1060
2169
  `);
1061
2170
  }
2171
+ process.exit(1);
1062
2172
  });
1063
2173
  }
1064
2174
 
1065
2175
  // src/cli.ts
1066
2176
  var import_meta = {};
1067
- var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
2177
+ function checkSettingsFile(filePath) {
2178
+ if (!(0, import_node_fs2.existsSync)(filePath)) return "missing";
2179
+ try {
2180
+ const raw = (0, import_node_fs2.readFileSync)(filePath, "utf8").trim();
2181
+ if (raw.length === 0) return "incomplete";
2182
+ const parsed = JSON.parse(raw);
2183
+ const provider = parsed.provider;
2184
+ if (!provider?.apiKey) return "incomplete";
2185
+ return "valid";
2186
+ } catch {
2187
+ return "corrupt";
2188
+ }
2189
+ }
1068
2190
  function readVersion() {
1069
2191
  try {
1070
2192
  const thisFile = (0, import_node_url.fileURLToPath)(import_meta.url);
1071
- const dir = (0, import_node_path2.dirname)(thisFile);
1072
- const candidates = [(0, import_node_path2.join)(dir, "..", "..", "package.json"), (0, import_node_path2.join)(dir, "..", "package.json")];
2193
+ const dir = (0, import_node_path5.dirname)(thisFile);
2194
+ const candidates = [(0, import_node_path5.join)(dir, "..", "..", "package.json"), (0, import_node_path5.join)(dir, "..", "package.json")];
1073
2195
  for (const pkgPath of candidates) {
1074
2196
  try {
1075
2197
  const raw = (0, import_node_fs2.readFileSync)(pkgPath, "utf-8");
@@ -1085,97 +2207,107 @@ function readVersion() {
1085
2207
  return "0.0.0";
1086
2208
  }
1087
2209
  }
1088
- function parsePermissionMode(raw) {
1089
- if (raw === void 0) return void 0;
1090
- if (!VALID_MODES.includes(raw)) {
1091
- process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
1092
- `);
1093
- process.exit(1);
1094
- }
1095
- return raw;
1096
- }
1097
- function parseMaxTurns(raw) {
1098
- if (raw === void 0) return void 0;
1099
- const n = parseInt(raw, 10);
1100
- if (isNaN(n) || n <= 0) {
1101
- process.stderr.write(`Invalid --max-turns "${raw}". Must be a positive integer.
1102
- `);
1103
- process.exit(1);
1104
- }
1105
- return n;
1106
- }
1107
- function parseCliArgs() {
1108
- const { values, positionals } = (0, import_node_util.parseArgs)({
1109
- allowPositionals: true,
1110
- options: {
1111
- p: { type: "boolean", short: "p", default: false },
1112
- c: { type: "boolean", short: "c", default: false },
1113
- r: { type: "string", short: "r" },
1114
- model: { type: "string" },
1115
- "permission-mode": { type: "string" },
1116
- "max-turns": { type: "string" },
1117
- version: { type: "boolean", default: false }
1118
- }
2210
+ function promptInput(label, masked = false) {
2211
+ return new Promise((resolve) => {
2212
+ process.stdout.write(label);
2213
+ let input = "";
2214
+ const stdin = process.stdin;
2215
+ const wasRaw = stdin.isRaw;
2216
+ stdin.setRawMode(true);
2217
+ stdin.resume();
2218
+ stdin.setEncoding("utf8");
2219
+ const onData = (data) => {
2220
+ for (const ch of data) {
2221
+ if (ch === "\r" || ch === "\n") {
2222
+ stdin.removeListener("data", onData);
2223
+ stdin.setRawMode(wasRaw ?? false);
2224
+ stdin.pause();
2225
+ process.stdout.write("\n");
2226
+ resolve(input.trim());
2227
+ return;
2228
+ } else if (ch === "\x7F" || ch === "\b") {
2229
+ if (input.length > 0) {
2230
+ input = input.slice(0, -1);
2231
+ process.stdout.write("\b \b");
2232
+ }
2233
+ } else if (ch === "") {
2234
+ process.stdout.write("\n");
2235
+ process.exit(0);
2236
+ } else if (ch.charCodeAt(0) >= 32) {
2237
+ input += ch;
2238
+ process.stdout.write(masked ? "*" : ch);
2239
+ }
2240
+ }
2241
+ };
2242
+ stdin.on("data", onData);
1119
2243
  });
1120
- return {
1121
- positional: positionals,
1122
- printMode: values["p"] ?? false,
1123
- continueMode: values["c"] ?? false,
1124
- resumeId: values["r"],
1125
- model: values["model"],
1126
- permissionMode: parsePermissionMode(values["permission-mode"]),
1127
- maxTurns: parseMaxTurns(values["max-turns"]),
1128
- version: values["version"] ?? false
1129
- };
1130
2244
  }
1131
- var PrintTerminal = class {
1132
- write(text) {
1133
- process.stdout.write(text);
2245
+ async function ensureConfig(cwd) {
2246
+ const userPath = getUserSettingsPath();
2247
+ const projectPath = (0, import_node_path5.join)(cwd, ".robota", "settings.json");
2248
+ const localPath = (0, import_node_path5.join)(cwd, ".robota", "settings.local.json");
2249
+ const paths = [userPath, projectPath, localPath];
2250
+ const checks = paths.map((p) => ({ path: p, status: checkSettingsFile(p) }));
2251
+ if (checks.some((c) => c.status === "valid")) {
2252
+ return;
1134
2253
  }
1135
- writeLine(text) {
1136
- process.stdout.write(text + "\n");
2254
+ const corrupt = checks.filter((c) => c.status === "corrupt");
2255
+ const incomplete = checks.filter((c) => c.status === "incomplete");
2256
+ process.stdout.write("\n");
2257
+ if (corrupt.length > 0) {
2258
+ for (const c of corrupt) {
2259
+ process.stderr.write(` ERROR: Settings file is corrupt (invalid JSON): ${c.path}
2260
+ `);
2261
+ }
2262
+ process.stdout.write("\n");
1137
2263
  }
1138
- writeMarkdown(md) {
1139
- process.stdout.write(md);
2264
+ if (incomplete.length > 0) {
2265
+ for (const c of incomplete) {
2266
+ process.stderr.write(` WARNING: Settings file is missing provider.apiKey: ${c.path}
2267
+ `);
2268
+ }
2269
+ process.stdout.write("\n");
1140
2270
  }
1141
- writeError(text) {
1142
- process.stderr.write(text + "\n");
2271
+ if (corrupt.length === 0 && incomplete.length === 0) {
2272
+ process.stdout.write(" Welcome to Robota CLI!\n");
2273
+ process.stdout.write(" No configuration found. Let's set up.\n");
2274
+ } else {
2275
+ process.stdout.write(" Reconfiguring...\n");
1143
2276
  }
1144
- prompt(question) {
1145
- return new Promise((resolve) => {
1146
- const rl = readline.createInterface({
1147
- input: process.stdin,
1148
- output: process.stdout,
1149
- terminal: false,
1150
- historySize: 0
1151
- });
1152
- rl.question(question, (answer) => {
1153
- rl.close();
1154
- resolve(answer);
1155
- });
1156
- });
2277
+ process.stdout.write("\n");
2278
+ const apiKey = await promptInput(" Anthropic API key: ", true);
2279
+ if (!apiKey) {
2280
+ process.stderr.write("\n No API key provided. Exiting.\n");
2281
+ process.exit(1);
1157
2282
  }
1158
- async select(options, initialIndex = 0) {
1159
- for (let i = 0; i < options.length; i++) {
1160
- const marker = i === initialIndex ? ">" : " ";
1161
- process.stdout.write(` ${marker} ${i + 1}) ${options[i]}
1162
- `);
2283
+ const language = await promptInput(" Response language (ko/en/ja/zh, default: en): ");
2284
+ const settingsDir = (0, import_node_path5.dirname)(userPath);
2285
+ (0, import_node_fs2.mkdirSync)(settingsDir, { recursive: true });
2286
+ const settings = {
2287
+ provider: {
2288
+ name: "anthropic",
2289
+ model: "claude-sonnet-4-6",
2290
+ apiKey
1163
2291
  }
1164
- const answer = await this.prompt(
1165
- ` Choose [1-${options.length}] (default: ${options[initialIndex]}): `
1166
- );
1167
- const trimmed = answer.trim().toLowerCase();
1168
- if (trimmed === "") return initialIndex;
1169
- const num = parseInt(trimmed, 10);
1170
- if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
1171
- return initialIndex;
2292
+ };
2293
+ if (language) {
2294
+ settings.language = language;
1172
2295
  }
1173
- spinner(_message) {
1174
- return { stop() {
1175
- }, update() {
1176
- } };
2296
+ (0, import_node_fs2.writeFileSync)(userPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
2297
+ process.stdout.write(`
2298
+ Config saved to ${userPath}
2299
+
2300
+ `);
2301
+ }
2302
+ function resetConfig() {
2303
+ const userPath = getUserSettingsPath();
2304
+ if (deleteSettings(userPath)) {
2305
+ process.stdout.write(`Deleted ${userPath}
2306
+ `);
2307
+ } else {
2308
+ process.stdout.write("No user settings found.\n");
1177
2309
  }
1178
- };
2310
+ }
1179
2311
  async function startCli() {
1180
2312
  const args = parseCliArgs();
1181
2313
  if (args.version) {
@@ -1183,16 +2315,24 @@ async function startCli() {
1183
2315
  `);
1184
2316
  return;
1185
2317
  }
2318
+ if (args.reset) {
2319
+ resetConfig();
2320
+ return;
2321
+ }
1186
2322
  const cwd = process.cwd();
2323
+ await ensureConfig(cwd);
1187
2324
  const [config, context, projectInfo] = await Promise.all([
1188
- (0, import_agent_sdk2.loadConfig)(cwd),
1189
- (0, import_agent_sdk2.loadContext)(cwd),
1190
- (0, import_agent_sdk2.detectProject)(cwd)
2325
+ (0, import_agent_sdk3.loadConfig)(cwd),
2326
+ (0, import_agent_sdk3.loadContext)(cwd),
2327
+ (0, import_agent_sdk3.detectProject)(cwd)
1191
2328
  ]);
1192
2329
  if (args.model !== void 0) {
1193
2330
  config.provider.model = args.model;
1194
2331
  }
1195
- const sessionStore = new import_agent_sdk2.SessionStore();
2332
+ if (args.language !== void 0) {
2333
+ config.language = args.language;
2334
+ }
2335
+ const sessionStore = new import_agent_sdk3.SessionStore();
1196
2336
  if (args.printMode) {
1197
2337
  const prompt = args.positional.join(" ").trim();
1198
2338
  if (prompt.length === 0) {
@@ -1200,15 +2340,15 @@ async function startCli() {
1200
2340
  process.exit(1);
1201
2341
  }
1202
2342
  const terminal = new PrintTerminal();
1203
- const paths = (0, import_agent_sdk2.projectPaths)(cwd);
1204
- const session = (0, import_agent_sdk2.createSession)({
2343
+ const paths = (0, import_agent_sdk3.projectPaths)(cwd);
2344
+ const session = (0, import_agent_sdk3.createSession)({
1205
2345
  config,
1206
2346
  context,
1207
2347
  terminal,
1208
- sessionLogger: new import_agent_sdk2.FileSessionLogger(paths.logs),
2348
+ sessionLogger: new import_agent_sdk3.FileSessionLogger(paths.logs),
1209
2349
  projectInfo,
1210
2350
  permissionMode: args.permissionMode,
1211
- promptForApproval
2351
+ promptForApproval: import_agent_sdk4.promptForApproval
1212
2352
  });
1213
2353
  const response = await session.run(prompt);
1214
2354
  process.stdout.write(response + "\n");