@robota-sdk/agent-cli 3.0.0-beta.54 → 3.0.0-beta.55

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