@robota-sdk/agent-cli 3.0.0-beta.5 → 3.0.0-beta.50

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