@robota-sdk/agent-cli 3.0.0-beta.15 → 3.0.0-beta.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +10 -3
- package/dist/node/bin.cjs +467 -315
- package/dist/node/bin.js +1 -1
- package/dist/node/{chunk-B423R5N3.js → chunk-4WB3L3RU.js} +465 -313
- package/dist/node/index.cjs +467 -315
- package/dist/node/index.js +1 -1
- package/package.json +4 -3
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
// src/cli.ts
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { join as join2, dirname } from "path";
|
|
2
|
+
import { readFileSync as readFileSync3, existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync as writeFileSync2 } from "fs";
|
|
3
|
+
import { join as join3, dirname as dirname2 } from "path";
|
|
5
4
|
import { fileURLToPath } from "url";
|
|
6
|
-
import * as readline from "readline";
|
|
7
5
|
import {
|
|
8
6
|
loadConfig,
|
|
9
7
|
loadContext,
|
|
@@ -15,13 +13,269 @@ import {
|
|
|
15
13
|
} from "@robota-sdk/agent-sdk";
|
|
16
14
|
import { promptForApproval } from "@robota-sdk/agent-sdk";
|
|
17
15
|
|
|
16
|
+
// src/utils/cli-args.ts
|
|
17
|
+
import { parseArgs } from "util";
|
|
18
|
+
var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
19
|
+
function parsePermissionMode(raw) {
|
|
20
|
+
if (raw === void 0) return void 0;
|
|
21
|
+
if (!VALID_MODES.includes(raw)) {
|
|
22
|
+
process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
|
|
23
|
+
`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
return raw;
|
|
27
|
+
}
|
|
28
|
+
function parseMaxTurns(raw) {
|
|
29
|
+
if (raw === void 0) return void 0;
|
|
30
|
+
const n = parseInt(raw, 10);
|
|
31
|
+
if (isNaN(n) || n <= 0) {
|
|
32
|
+
process.stderr.write(`Invalid --max-turns "${raw}". Must be a positive integer.
|
|
33
|
+
`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
return n;
|
|
37
|
+
}
|
|
38
|
+
function parseCliArgs() {
|
|
39
|
+
const { values, positionals } = parseArgs({
|
|
40
|
+
allowPositionals: true,
|
|
41
|
+
options: {
|
|
42
|
+
p: { type: "boolean", short: "p", default: false },
|
|
43
|
+
c: { type: "boolean", short: "c", default: false },
|
|
44
|
+
r: { type: "string", short: "r" },
|
|
45
|
+
model: { type: "string" },
|
|
46
|
+
"permission-mode": { type: "string" },
|
|
47
|
+
"max-turns": { type: "string" },
|
|
48
|
+
version: { type: "boolean", default: false },
|
|
49
|
+
reset: { type: "boolean", default: false }
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
return {
|
|
53
|
+
positional: positionals,
|
|
54
|
+
printMode: values["p"] ?? false,
|
|
55
|
+
continueMode: values["c"] ?? false,
|
|
56
|
+
resumeId: values["r"],
|
|
57
|
+
model: values["model"],
|
|
58
|
+
permissionMode: parsePermissionMode(values["permission-mode"]),
|
|
59
|
+
maxTurns: parseMaxTurns(values["max-turns"]),
|
|
60
|
+
version: values["version"] ?? false,
|
|
61
|
+
reset: values["reset"] ?? false
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// src/utils/settings-io.ts
|
|
66
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from "fs";
|
|
67
|
+
import { join, dirname } from "path";
|
|
68
|
+
function getUserSettingsPath() {
|
|
69
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
|
|
70
|
+
return join(home, ".robota", "settings.json");
|
|
71
|
+
}
|
|
72
|
+
function readSettings(path) {
|
|
73
|
+
if (!existsSync(path)) return {};
|
|
74
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
75
|
+
}
|
|
76
|
+
function writeSettings(path, settings) {
|
|
77
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
78
|
+
writeFileSync(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
79
|
+
}
|
|
80
|
+
function updateModelInSettings(settingsPath, modelId) {
|
|
81
|
+
const settings = readSettings(settingsPath);
|
|
82
|
+
const provider = settings.provider ?? {};
|
|
83
|
+
provider.model = modelId;
|
|
84
|
+
settings.provider = provider;
|
|
85
|
+
writeSettings(settingsPath, settings);
|
|
86
|
+
}
|
|
87
|
+
function deleteSettings(path) {
|
|
88
|
+
if (existsSync(path)) {
|
|
89
|
+
unlinkSync(path);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// src/print-terminal.ts
|
|
96
|
+
import * as readline from "readline";
|
|
97
|
+
var PrintTerminal = class {
|
|
98
|
+
write(text) {
|
|
99
|
+
process.stdout.write(text);
|
|
100
|
+
}
|
|
101
|
+
writeLine(text) {
|
|
102
|
+
process.stdout.write(text + "\n");
|
|
103
|
+
}
|
|
104
|
+
writeMarkdown(md) {
|
|
105
|
+
process.stdout.write(md);
|
|
106
|
+
}
|
|
107
|
+
writeError(text) {
|
|
108
|
+
process.stderr.write(text + "\n");
|
|
109
|
+
}
|
|
110
|
+
prompt(question) {
|
|
111
|
+
return new Promise((resolve) => {
|
|
112
|
+
const rl = readline.createInterface({
|
|
113
|
+
input: process.stdin,
|
|
114
|
+
output: process.stdout,
|
|
115
|
+
terminal: false,
|
|
116
|
+
historySize: 0
|
|
117
|
+
});
|
|
118
|
+
rl.question(question, (answer) => {
|
|
119
|
+
rl.close();
|
|
120
|
+
resolve(answer);
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
async select(options, initialIndex = 0) {
|
|
125
|
+
for (let i = 0; i < options.length; i++) {
|
|
126
|
+
const marker = i === initialIndex ? ">" : " ";
|
|
127
|
+
process.stdout.write(` ${marker} ${i + 1}) ${options[i]}
|
|
128
|
+
`);
|
|
129
|
+
}
|
|
130
|
+
const answer = await this.prompt(
|
|
131
|
+
` Choose [1-${options.length}] (default: ${options[initialIndex]}): `
|
|
132
|
+
);
|
|
133
|
+
const trimmed = answer.trim().toLowerCase();
|
|
134
|
+
if (trimmed === "") return initialIndex;
|
|
135
|
+
const num = parseInt(trimmed, 10);
|
|
136
|
+
if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
|
|
137
|
+
return initialIndex;
|
|
138
|
+
}
|
|
139
|
+
spinner(_message) {
|
|
140
|
+
return { stop() {
|
|
141
|
+
}, update() {
|
|
142
|
+
} };
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
|
|
18
146
|
// src/ui/render.tsx
|
|
19
147
|
import { render } from "ink";
|
|
20
148
|
|
|
21
149
|
// src/ui/App.tsx
|
|
22
|
-
import { useState as
|
|
23
|
-
import { Box as
|
|
150
|
+
import { useState as useState5, useCallback as useCallback3, useRef as useRef3 } from "react";
|
|
151
|
+
import { Box as Box7, Text as Text9, useApp, useInput as useInput5 } from "ink";
|
|
152
|
+
|
|
153
|
+
// src/commands/slash-executor.ts
|
|
154
|
+
var VALID_MODES2 = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
155
|
+
var HELP_TEXT = [
|
|
156
|
+
"Available commands:",
|
|
157
|
+
" /help \u2014 Show this help",
|
|
158
|
+
" /clear \u2014 Clear conversation",
|
|
159
|
+
" /compact [instr] \u2014 Compact context (optional focus instructions)",
|
|
160
|
+
" /mode [m] \u2014 Show/change permission mode",
|
|
161
|
+
" /cost \u2014 Show session info",
|
|
162
|
+
" /reset \u2014 Delete settings and exit",
|
|
163
|
+
" /exit \u2014 Exit CLI"
|
|
164
|
+
].join("\n");
|
|
165
|
+
function handleHelp(addMessage) {
|
|
166
|
+
addMessage({ role: "system", content: HELP_TEXT });
|
|
167
|
+
return { handled: true };
|
|
168
|
+
}
|
|
169
|
+
function handleClear(addMessage, clearMessages, session) {
|
|
170
|
+
clearMessages();
|
|
171
|
+
session.clearHistory();
|
|
172
|
+
addMessage({ role: "system", content: "Conversation cleared." });
|
|
173
|
+
return { handled: true };
|
|
174
|
+
}
|
|
175
|
+
async function handleCompact(args, session, addMessage) {
|
|
176
|
+
const instructions = args.trim() || void 0;
|
|
177
|
+
const before = session.getContextState().usedPercentage;
|
|
178
|
+
addMessage({ role: "system", content: "Compacting context..." });
|
|
179
|
+
await session.compact(instructions);
|
|
180
|
+
const after = session.getContextState().usedPercentage;
|
|
181
|
+
addMessage({
|
|
182
|
+
role: "system",
|
|
183
|
+
content: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`
|
|
184
|
+
});
|
|
185
|
+
return { handled: true };
|
|
186
|
+
}
|
|
187
|
+
function handleMode(arg, session, addMessage) {
|
|
188
|
+
if (!arg) {
|
|
189
|
+
addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
|
|
190
|
+
} else if (VALID_MODES2.includes(arg)) {
|
|
191
|
+
session.setPermissionMode(arg);
|
|
192
|
+
addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
|
|
193
|
+
} else {
|
|
194
|
+
addMessage({ role: "system", content: `Invalid mode. Valid: ${VALID_MODES2.join(" | ")}` });
|
|
195
|
+
}
|
|
196
|
+
return { handled: true };
|
|
197
|
+
}
|
|
198
|
+
function handleModel(modelId, addMessage) {
|
|
199
|
+
if (!modelId) {
|
|
200
|
+
addMessage({ role: "system", content: "Select a model from the /model submenu." });
|
|
201
|
+
return { handled: true };
|
|
202
|
+
}
|
|
203
|
+
return { handled: true, pendingModelId: modelId };
|
|
204
|
+
}
|
|
205
|
+
function handleCost(session, addMessage) {
|
|
206
|
+
addMessage({
|
|
207
|
+
role: "system",
|
|
208
|
+
content: `Session: ${session.getSessionId()}
|
|
209
|
+
Messages: ${session.getMessageCount()}`
|
|
210
|
+
});
|
|
211
|
+
return { handled: true };
|
|
212
|
+
}
|
|
213
|
+
function handlePermissions(session, addMessage) {
|
|
214
|
+
const mode = session.getPermissionMode();
|
|
215
|
+
const sessionAllowed = session.getSessionAllowedTools();
|
|
216
|
+
const lines = [`Permission mode: ${mode}`];
|
|
217
|
+
if (sessionAllowed.length > 0) {
|
|
218
|
+
lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
|
|
219
|
+
} else {
|
|
220
|
+
lines.push("No session-approved tools.");
|
|
221
|
+
}
|
|
222
|
+
addMessage({ role: "system", content: lines.join("\n") });
|
|
223
|
+
return { handled: true };
|
|
224
|
+
}
|
|
225
|
+
function handleContext(session, addMessage) {
|
|
226
|
+
const ctx = session.getContextState();
|
|
227
|
+
addMessage({
|
|
228
|
+
role: "system",
|
|
229
|
+
content: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`
|
|
230
|
+
});
|
|
231
|
+
return { handled: true };
|
|
232
|
+
}
|
|
233
|
+
function handleReset(addMessage) {
|
|
234
|
+
const settingsPath = getUserSettingsPath();
|
|
235
|
+
if (deleteSettings(settingsPath)) {
|
|
236
|
+
addMessage({ role: "system", content: `Deleted ${settingsPath}. Exiting...` });
|
|
237
|
+
} else {
|
|
238
|
+
addMessage({ role: "system", content: "No user settings found." });
|
|
239
|
+
}
|
|
240
|
+
return { handled: true, exitRequested: true };
|
|
241
|
+
}
|
|
242
|
+
async function executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry) {
|
|
243
|
+
switch (cmd) {
|
|
244
|
+
case "help":
|
|
245
|
+
return handleHelp(addMessage);
|
|
246
|
+
case "clear":
|
|
247
|
+
return handleClear(addMessage, clearMessages, session);
|
|
248
|
+
case "compact":
|
|
249
|
+
return handleCompact(args, session, addMessage);
|
|
250
|
+
case "mode":
|
|
251
|
+
return handleMode(args.split(/\s+/)[0] || void 0, session, addMessage);
|
|
252
|
+
case "model":
|
|
253
|
+
return handleModel(args.split(/\s+/)[0] || void 0, addMessage);
|
|
254
|
+
case "cost":
|
|
255
|
+
return handleCost(session, addMessage);
|
|
256
|
+
case "permissions":
|
|
257
|
+
return handlePermissions(session, addMessage);
|
|
258
|
+
case "context":
|
|
259
|
+
return handleContext(session, addMessage);
|
|
260
|
+
case "reset":
|
|
261
|
+
return handleReset(addMessage);
|
|
262
|
+
case "exit":
|
|
263
|
+
return { handled: true, exitRequested: true };
|
|
264
|
+
default: {
|
|
265
|
+
const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
|
|
266
|
+
if (skillCmd) {
|
|
267
|
+
addMessage({ role: "system", content: `Invoking skill: ${cmd}` });
|
|
268
|
+
return { handled: false };
|
|
269
|
+
}
|
|
270
|
+
addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
|
|
271
|
+
return { handled: true };
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// src/ui/App.tsx
|
|
24
277
|
import { createSession, FileSessionLogger, projectPaths } from "@robota-sdk/agent-sdk";
|
|
278
|
+
import { getModelName } from "@robota-sdk/agent-core";
|
|
25
279
|
|
|
26
280
|
// src/commands/command-registry.ts
|
|
27
281
|
var CommandRegistry = class {
|
|
@@ -54,6 +308,21 @@ var CommandRegistry = class {
|
|
|
54
308
|
};
|
|
55
309
|
|
|
56
310
|
// src/commands/builtin-source.ts
|
|
311
|
+
import { CLAUDE_MODELS, formatTokenCount } from "@robota-sdk/agent-core";
|
|
312
|
+
function buildModelSubcommands() {
|
|
313
|
+
const seen = /* @__PURE__ */ new Set();
|
|
314
|
+
const commands = [];
|
|
315
|
+
for (const model of Object.values(CLAUDE_MODELS)) {
|
|
316
|
+
if (seen.has(model.name)) continue;
|
|
317
|
+
seen.add(model.name);
|
|
318
|
+
commands.push({
|
|
319
|
+
name: model.id,
|
|
320
|
+
description: `${model.name} (${formatTokenCount(model.contextWindow).toUpperCase()})`,
|
|
321
|
+
source: "builtin"
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
return commands;
|
|
325
|
+
}
|
|
57
326
|
function createBuiltinCommands() {
|
|
58
327
|
return [
|
|
59
328
|
{ name: "help", description: "Show available commands", source: "builtin" },
|
|
@@ -73,11 +342,7 @@ function createBuiltinCommands() {
|
|
|
73
342
|
name: "model",
|
|
74
343
|
description: "Select AI model",
|
|
75
344
|
source: "builtin",
|
|
76
|
-
subcommands:
|
|
77
|
-
{ name: "claude-opus-4-6", description: "Opus 4.6 (highest quality)", source: "builtin" },
|
|
78
|
-
{ name: "claude-sonnet-4-6", description: "Sonnet 4.6 (balanced)", source: "builtin" },
|
|
79
|
-
{ name: "claude-haiku-4-5", description: "Haiku 4.5 (fastest)", source: "builtin" }
|
|
80
|
-
]
|
|
345
|
+
subcommands: buildModelSubcommands()
|
|
81
346
|
},
|
|
82
347
|
{ name: "compact", description: "Compress context window", source: "builtin" },
|
|
83
348
|
{ name: "cost", description: "Show session info", source: "builtin" },
|
|
@@ -99,8 +364,8 @@ var BuiltinCommandSource = class {
|
|
|
99
364
|
};
|
|
100
365
|
|
|
101
366
|
// src/commands/skill-source.ts
|
|
102
|
-
import { readdirSync, readFileSync, existsSync } from "fs";
|
|
103
|
-
import { join } from "path";
|
|
367
|
+
import { readdirSync, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
368
|
+
import { join as join2 } from "path";
|
|
104
369
|
import { homedir } from "os";
|
|
105
370
|
function parseFrontmatter(content) {
|
|
106
371
|
const lines = content.split("\n");
|
|
@@ -123,14 +388,14 @@ function parseFrontmatter(content) {
|
|
|
123
388
|
return name ? { name, description } : null;
|
|
124
389
|
}
|
|
125
390
|
function scanSkillsDir(skillsDir) {
|
|
126
|
-
if (!
|
|
391
|
+
if (!existsSync2(skillsDir)) return [];
|
|
127
392
|
const commands = [];
|
|
128
393
|
const entries = readdirSync(skillsDir, { withFileTypes: true });
|
|
129
394
|
for (const entry of entries) {
|
|
130
395
|
if (!entry.isDirectory()) continue;
|
|
131
|
-
const skillFile =
|
|
132
|
-
if (!
|
|
133
|
-
const content =
|
|
396
|
+
const skillFile = join2(skillsDir, entry.name, "SKILL.md");
|
|
397
|
+
if (!existsSync2(skillFile)) continue;
|
|
398
|
+
const content = readFileSync2(skillFile, "utf-8");
|
|
134
399
|
const frontmatter = parseFrontmatter(content);
|
|
135
400
|
commands.push({
|
|
136
401
|
name: frontmatter?.name ?? entry.name,
|
|
@@ -150,8 +415,8 @@ var SkillCommandSource = class {
|
|
|
150
415
|
}
|
|
151
416
|
getCommands() {
|
|
152
417
|
if (this.cachedCommands) return this.cachedCommands;
|
|
153
|
-
const projectSkills = scanSkillsDir(
|
|
154
|
-
const userSkills = scanSkillsDir(
|
|
418
|
+
const projectSkills = scanSkillsDir(join2(this.cwd, ".agents", "skills"));
|
|
419
|
+
const userSkills = scanSkillsDir(join2(homedir(), ".claude", "skills"));
|
|
155
420
|
const seen = new Set(projectSkills.map((cmd) => cmd.name));
|
|
156
421
|
const merged = [...projectSkills];
|
|
157
422
|
for (const cmd of userSkills) {
|
|
@@ -225,6 +490,7 @@ function MessageList({ messages }) {
|
|
|
225
490
|
|
|
226
491
|
// src/ui/StatusBar.tsx
|
|
227
492
|
import { Box as Box2, Text as Text2 } from "ink";
|
|
493
|
+
import { formatTokenCount as formatTokenCount2 } from "@robota-sdk/agent-core";
|
|
228
494
|
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
229
495
|
var CONTEXT_YELLOW_THRESHOLD = 70;
|
|
230
496
|
var CONTEXT_RED_THRESHOLD = 90;
|
|
@@ -264,10 +530,10 @@ function StatusBar({
|
|
|
264
530
|
"Context: ",
|
|
265
531
|
Math.round(contextPercentage),
|
|
266
532
|
"% (",
|
|
267
|
-
(contextUsedTokens
|
|
268
|
-
"
|
|
269
|
-
(contextMaxTokens
|
|
270
|
-
"
|
|
533
|
+
formatTokenCount2(contextUsedTokens),
|
|
534
|
+
"/",
|
|
535
|
+
formatTokenCount2(contextMaxTokens),
|
|
536
|
+
")"
|
|
271
537
|
] })
|
|
272
538
|
] }),
|
|
273
539
|
/* @__PURE__ */ jsxs2(Text2, { children: [
|
|
@@ -414,19 +680,13 @@ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
|
414
680
|
var MAX_VISIBLE = 8;
|
|
415
681
|
function CommandRow(props) {
|
|
416
682
|
const { cmd, isSelected, showSlash } = props;
|
|
417
|
-
const prefix = showSlash ? "/" : "";
|
|
418
683
|
const indicator = isSelected ? "\u25B8 " : " ";
|
|
419
684
|
const nameColor = isSelected ? "cyan" : void 0;
|
|
420
685
|
const dimmed = !isSelected;
|
|
421
|
-
return /* @__PURE__ */
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
cmd.name
|
|
426
|
-
] }),
|
|
427
|
-
/* @__PURE__ */ jsx5(Text5, { dimColor: dimmed, children: " " }),
|
|
428
|
-
/* @__PURE__ */ jsx5(Text5, { color: nameColor, dimColor: dimmed, children: cmd.description })
|
|
429
|
-
] });
|
|
686
|
+
return /* @__PURE__ */ jsx5(Box3, { children: /* @__PURE__ */ jsxs3(Text5, { color: nameColor, dimColor: dimmed, children: [
|
|
687
|
+
indicator,
|
|
688
|
+
showSlash ? `/${cmd.name} ${cmd.description}` : cmd.description
|
|
689
|
+
] }) });
|
|
430
690
|
}
|
|
431
691
|
function SlashAutocomplete({
|
|
432
692
|
commands,
|
|
@@ -591,10 +851,53 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
591
851
|
] });
|
|
592
852
|
}
|
|
593
853
|
|
|
594
|
-
// src/ui/
|
|
595
|
-
import
|
|
854
|
+
// src/ui/ConfirmPrompt.tsx
|
|
855
|
+
import { useState as useState4, useCallback as useCallback2, useRef as useRef2 } from "react";
|
|
596
856
|
import { Box as Box5, Text as Text7, useInput as useInput3 } from "ink";
|
|
597
857
|
import { jsx as jsx7, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
858
|
+
function ConfirmPrompt({
|
|
859
|
+
message,
|
|
860
|
+
options = ["Yes", "No"],
|
|
861
|
+
onSelect
|
|
862
|
+
}) {
|
|
863
|
+
const [selected, setSelected] = useState4(0);
|
|
864
|
+
const resolvedRef = useRef2(false);
|
|
865
|
+
const doSelect = useCallback2(
|
|
866
|
+
(index) => {
|
|
867
|
+
if (resolvedRef.current) return;
|
|
868
|
+
resolvedRef.current = true;
|
|
869
|
+
onSelect(index);
|
|
870
|
+
},
|
|
871
|
+
[onSelect]
|
|
872
|
+
);
|
|
873
|
+
useInput3((input, key) => {
|
|
874
|
+
if (resolvedRef.current) return;
|
|
875
|
+
if (key.leftArrow || key.upArrow) {
|
|
876
|
+
setSelected((prev) => prev > 0 ? prev - 1 : prev);
|
|
877
|
+
} else if (key.rightArrow || key.downArrow) {
|
|
878
|
+
setSelected((prev) => prev < options.length - 1 ? prev + 1 : prev);
|
|
879
|
+
} else if (key.return) {
|
|
880
|
+
doSelect(selected);
|
|
881
|
+
} else if (input === "y" && options.length === 2) {
|
|
882
|
+
doSelect(0);
|
|
883
|
+
} else if (input === "n" && options.length === 2) {
|
|
884
|
+
doSelect(1);
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
return /* @__PURE__ */ jsxs5(Box5, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
888
|
+
/* @__PURE__ */ jsx7(Text7, { color: "yellow", children: message }),
|
|
889
|
+
/* @__PURE__ */ jsx7(Box5, { marginTop: 1, children: options.map((opt, i) => /* @__PURE__ */ jsx7(Box5, { marginRight: 2, children: /* @__PURE__ */ jsxs5(Text7, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
|
|
890
|
+
i === selected ? "> " : " ",
|
|
891
|
+
opt
|
|
892
|
+
] }) }, opt)) }),
|
|
893
|
+
/* @__PURE__ */ jsx7(Text7, { dimColor: true, children: " arrow keys to select, Enter to confirm" })
|
|
894
|
+
] });
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// src/ui/PermissionPrompt.tsx
|
|
898
|
+
import React5 from "react";
|
|
899
|
+
import { Box as Box6, Text as Text8, useInput as useInput4 } from "ink";
|
|
900
|
+
import { jsx as jsx8, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
598
901
|
var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
|
|
599
902
|
function formatArgs(args) {
|
|
600
903
|
const entries = Object.entries(args);
|
|
@@ -602,15 +905,15 @@ function formatArgs(args) {
|
|
|
602
905
|
return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
603
906
|
}
|
|
604
907
|
function PermissionPrompt({ request }) {
|
|
605
|
-
const [selected, setSelected] =
|
|
606
|
-
const resolvedRef =
|
|
607
|
-
const prevRequestRef =
|
|
908
|
+
const [selected, setSelected] = React5.useState(0);
|
|
909
|
+
const resolvedRef = React5.useRef(false);
|
|
910
|
+
const prevRequestRef = React5.useRef(request);
|
|
608
911
|
if (prevRequestRef.current !== request) {
|
|
609
912
|
prevRequestRef.current = request;
|
|
610
913
|
resolvedRef.current = false;
|
|
611
914
|
setSelected(0);
|
|
612
915
|
}
|
|
613
|
-
const doResolve =
|
|
916
|
+
const doResolve = React5.useCallback(
|
|
614
917
|
(index) => {
|
|
615
918
|
if (resolvedRef.current) return;
|
|
616
919
|
resolvedRef.current = true;
|
|
@@ -620,7 +923,7 @@ function PermissionPrompt({ request }) {
|
|
|
620
923
|
},
|
|
621
924
|
[request]
|
|
622
925
|
);
|
|
623
|
-
|
|
926
|
+
useInput4((input, key) => {
|
|
624
927
|
if (resolvedRef.current) return;
|
|
625
928
|
if (key.upArrow || key.leftArrow) {
|
|
626
929
|
setSelected((prev) => prev > 0 ? prev - 1 : prev);
|
|
@@ -636,27 +939,54 @@ function PermissionPrompt({ request }) {
|
|
|
636
939
|
doResolve(2);
|
|
637
940
|
}
|
|
638
941
|
});
|
|
639
|
-
return /* @__PURE__ */
|
|
640
|
-
/* @__PURE__ */
|
|
641
|
-
/* @__PURE__ */
|
|
942
|
+
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
943
|
+
/* @__PURE__ */ jsx8(Text8, { color: "yellow", bold: true, children: "[Permission Required]" }),
|
|
944
|
+
/* @__PURE__ */ jsxs6(Text8, { children: [
|
|
642
945
|
"Tool:",
|
|
643
946
|
" ",
|
|
644
|
-
/* @__PURE__ */
|
|
947
|
+
/* @__PURE__ */ jsx8(Text8, { color: "cyan", bold: true, children: request.toolName })
|
|
645
948
|
] }),
|
|
646
|
-
/* @__PURE__ */
|
|
949
|
+
/* @__PURE__ */ jsxs6(Text8, { dimColor: true, children: [
|
|
647
950
|
" ",
|
|
648
951
|
formatArgs(request.toolArgs)
|
|
649
952
|
] }),
|
|
650
|
-
/* @__PURE__ */
|
|
953
|
+
/* @__PURE__ */ jsx8(Box6, { marginTop: 1, children: OPTIONS.map((opt, i) => /* @__PURE__ */ jsx8(Box6, { marginRight: 2, children: /* @__PURE__ */ jsxs6(Text8, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
|
|
651
954
|
i === selected ? "> " : " ",
|
|
652
955
|
opt
|
|
653
956
|
] }) }, opt)) }),
|
|
654
|
-
/* @__PURE__ */
|
|
957
|
+
/* @__PURE__ */ jsx8(Text8, { dimColor: true, children: " left/right to select, Enter to confirm" })
|
|
655
958
|
] });
|
|
656
959
|
}
|
|
657
960
|
|
|
961
|
+
// src/utils/tool-call-extractor.ts
|
|
962
|
+
var TOOL_ARG_MAX_LENGTH = 80;
|
|
963
|
+
var TOOL_ARG_TRUNCATE_LENGTH = 77;
|
|
964
|
+
function extractToolCalls(history, startIndex) {
|
|
965
|
+
const lines = [];
|
|
966
|
+
for (let i = startIndex; i < history.length; i++) {
|
|
967
|
+
const msg = history[i];
|
|
968
|
+
if (msg.role === "assistant" && msg.toolCalls) {
|
|
969
|
+
for (const tc of msg.toolCalls) {
|
|
970
|
+
const value = parseFirstArgValue(tc.function.arguments);
|
|
971
|
+
const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_TRUNCATE_LENGTH) + "..." : value;
|
|
972
|
+
lines.push(`${tc.function.name}(${truncated})`);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return lines;
|
|
977
|
+
}
|
|
978
|
+
function parseFirstArgValue(argsJson) {
|
|
979
|
+
try {
|
|
980
|
+
const parsed = JSON.parse(argsJson);
|
|
981
|
+
const firstVal = Object.values(parsed)[0];
|
|
982
|
+
return typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
|
|
983
|
+
} catch {
|
|
984
|
+
return argsJson;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
658
988
|
// src/ui/App.tsx
|
|
659
|
-
import { jsx as
|
|
989
|
+
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
660
990
|
var msgIdCounter = 0;
|
|
661
991
|
function nextId() {
|
|
662
992
|
msgIdCounter += 1;
|
|
@@ -678,11 +1008,11 @@ var NOOP_TERMINAL = {
|
|
|
678
1008
|
} })
|
|
679
1009
|
};
|
|
680
1010
|
function useSession(props) {
|
|
681
|
-
const [permissionRequest, setPermissionRequest] =
|
|
682
|
-
const [streamingText, setStreamingText] =
|
|
683
|
-
const permissionQueueRef =
|
|
684
|
-
const processingRef =
|
|
685
|
-
const processNextPermission =
|
|
1011
|
+
const [permissionRequest, setPermissionRequest] = useState5(null);
|
|
1012
|
+
const [streamingText, setStreamingText] = useState5("");
|
|
1013
|
+
const permissionQueueRef = useRef3([]);
|
|
1014
|
+
const processingRef = useRef3(false);
|
|
1015
|
+
const processNextPermission = useCallback3(() => {
|
|
686
1016
|
if (processingRef.current) return;
|
|
687
1017
|
const next = permissionQueueRef.current[0];
|
|
688
1018
|
if (!next) {
|
|
@@ -702,7 +1032,7 @@ function useSession(props) {
|
|
|
702
1032
|
}
|
|
703
1033
|
});
|
|
704
1034
|
}, []);
|
|
705
|
-
const sessionRef =
|
|
1035
|
+
const sessionRef = useRef3(null);
|
|
706
1036
|
if (sessionRef.current === null) {
|
|
707
1037
|
const permissionHandler = (toolName, toolArgs) => {
|
|
708
1038
|
return new Promise((resolve) => {
|
|
@@ -727,138 +1057,49 @@ function useSession(props) {
|
|
|
727
1057
|
onTextDelta
|
|
728
1058
|
});
|
|
729
1059
|
}
|
|
730
|
-
const clearStreamingText =
|
|
1060
|
+
const clearStreamingText = useCallback3(() => setStreamingText(""), []);
|
|
731
1061
|
return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText };
|
|
732
1062
|
}
|
|
733
1063
|
function useMessages() {
|
|
734
|
-
const [messages, setMessages] =
|
|
735
|
-
const addMessage =
|
|
1064
|
+
const [messages, setMessages] = useState5([]);
|
|
1065
|
+
const addMessage = useCallback3((msg) => {
|
|
736
1066
|
setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
|
|
737
1067
|
}, []);
|
|
738
1068
|
return { messages, setMessages, addMessage };
|
|
739
1069
|
}
|
|
740
|
-
var
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
" /clear \u2014 Clear conversation",
|
|
744
|
-
" /compact [instr] \u2014 Compact context (optional focus instructions)",
|
|
745
|
-
" /mode [m] \u2014 Show/change permission mode",
|
|
746
|
-
" /cost \u2014 Show session info",
|
|
747
|
-
" /reset \u2014 Delete settings and exit",
|
|
748
|
-
" /exit \u2014 Exit CLI"
|
|
749
|
-
].join("\n");
|
|
750
|
-
function handleModeCommand(arg, session, addMessage) {
|
|
751
|
-
const validModes = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
752
|
-
if (!arg) {
|
|
753
|
-
addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
|
|
754
|
-
} else if (validModes.includes(arg)) {
|
|
755
|
-
session.setPermissionMode(arg);
|
|
756
|
-
addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
|
|
757
|
-
} else {
|
|
758
|
-
addMessage({ role: "system", content: `Invalid mode. Valid: ${validModes.join(" | ")}` });
|
|
759
|
-
}
|
|
760
|
-
return true;
|
|
761
|
-
}
|
|
762
|
-
async function executeSlashCommand(cmd, parts, session, addMessage, setMessages, exit, registry) {
|
|
763
|
-
switch (cmd) {
|
|
764
|
-
case "help":
|
|
765
|
-
addMessage({ role: "system", content: HELP_TEXT });
|
|
766
|
-
return true;
|
|
767
|
-
case "clear":
|
|
768
|
-
setMessages([]);
|
|
769
|
-
session.clearHistory();
|
|
770
|
-
addMessage({ role: "system", content: "Conversation cleared." });
|
|
771
|
-
return true;
|
|
772
|
-
case "compact": {
|
|
773
|
-
const instructions = parts.slice(1).join(" ").trim() || void 0;
|
|
774
|
-
const before = session.getContextState().usedPercentage;
|
|
775
|
-
addMessage({ role: "system", content: "Compacting context..." });
|
|
776
|
-
await session.compact(instructions);
|
|
777
|
-
const after = session.getContextState().usedPercentage;
|
|
778
|
-
addMessage({
|
|
779
|
-
role: "system",
|
|
780
|
-
content: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`
|
|
781
|
-
});
|
|
782
|
-
return true;
|
|
783
|
-
}
|
|
784
|
-
case "mode":
|
|
785
|
-
return handleModeCommand(parts[1], session, addMessage);
|
|
786
|
-
case "cost":
|
|
787
|
-
addMessage({
|
|
788
|
-
role: "system",
|
|
789
|
-
content: `Session: ${session.getSessionId()}
|
|
790
|
-
Messages: ${session.getMessageCount()}`
|
|
791
|
-
});
|
|
792
|
-
return true;
|
|
793
|
-
case "permissions": {
|
|
794
|
-
const mode = session.getPermissionMode();
|
|
795
|
-
const sessionAllowed = session.getSessionAllowedTools();
|
|
796
|
-
const lines = [`Permission mode: ${mode}`];
|
|
797
|
-
if (sessionAllowed.length > 0) {
|
|
798
|
-
lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
|
|
799
|
-
} else {
|
|
800
|
-
lines.push("No session-approved tools.");
|
|
801
|
-
}
|
|
802
|
-
addMessage({ role: "system", content: lines.join("\n") });
|
|
803
|
-
return true;
|
|
804
|
-
}
|
|
805
|
-
case "context": {
|
|
806
|
-
const ctx = session.getContextState();
|
|
807
|
-
addMessage({
|
|
808
|
-
role: "system",
|
|
809
|
-
content: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`
|
|
810
|
-
});
|
|
811
|
-
return true;
|
|
812
|
-
}
|
|
813
|
-
case "reset": {
|
|
814
|
-
const { existsSync: exists, unlinkSync: unlink } = await import("fs");
|
|
815
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
|
|
816
|
-
const settingsPath = `${home}/.robota/settings.json`;
|
|
817
|
-
if (exists(settingsPath)) {
|
|
818
|
-
unlink(settingsPath);
|
|
819
|
-
addMessage({ role: "system", content: `Deleted ${settingsPath}. Exiting...` });
|
|
820
|
-
} else {
|
|
821
|
-
addMessage({ role: "system", content: "No user settings found." });
|
|
822
|
-
}
|
|
823
|
-
setTimeout(() => exit(), 500);
|
|
824
|
-
return true;
|
|
825
|
-
}
|
|
826
|
-
case "exit":
|
|
827
|
-
exit();
|
|
828
|
-
return true;
|
|
829
|
-
default: {
|
|
830
|
-
const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
|
|
831
|
-
if (skillCmd) {
|
|
832
|
-
addMessage({ role: "system", content: `Invoking skill: ${cmd}` });
|
|
833
|
-
return false;
|
|
834
|
-
}
|
|
835
|
-
addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
|
|
836
|
-
return true;
|
|
837
|
-
}
|
|
838
|
-
}
|
|
839
|
-
}
|
|
840
|
-
function useSlashCommands(session, addMessage, setMessages, exit, registry) {
|
|
841
|
-
return useCallback2(
|
|
1070
|
+
var EXIT_DELAY_MS = 500;
|
|
1071
|
+
function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
|
|
1072
|
+
return useCallback3(
|
|
842
1073
|
async (input) => {
|
|
843
1074
|
const parts = input.slice(1).split(/\s+/);
|
|
844
1075
|
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
845
|
-
|
|
1076
|
+
const args = parts.slice(1).join(" ");
|
|
1077
|
+
const clearMessages = () => setMessages([]);
|
|
1078
|
+
const result = await executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry);
|
|
1079
|
+
if (result.pendingModelId) {
|
|
1080
|
+
pendingModelChangeRef.current = result.pendingModelId;
|
|
1081
|
+
setPendingModelId(result.pendingModelId);
|
|
1082
|
+
}
|
|
1083
|
+
if (result.exitRequested) {
|
|
1084
|
+
setTimeout(() => exit(), EXIT_DELAY_MS);
|
|
1085
|
+
}
|
|
1086
|
+
return result.handled;
|
|
846
1087
|
},
|
|
847
|
-
[session, addMessage, setMessages, exit, registry]
|
|
1088
|
+
[session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId]
|
|
848
1089
|
);
|
|
849
1090
|
}
|
|
850
1091
|
function StreamingIndicator({ text }) {
|
|
851
1092
|
if (text) {
|
|
852
|
-
return /* @__PURE__ */
|
|
853
|
-
/* @__PURE__ */
|
|
1093
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
1094
|
+
/* @__PURE__ */ jsxs7(Text9, { color: "cyan", bold: true, children: [
|
|
854
1095
|
"Robota:",
|
|
855
1096
|
" "
|
|
856
1097
|
] }),
|
|
857
|
-
/* @__PURE__ */
|
|
858
|
-
/* @__PURE__ */
|
|
1098
|
+
/* @__PURE__ */ jsx9(Text9, { children: " " }),
|
|
1099
|
+
/* @__PURE__ */ jsx9(Box7, { marginLeft: 2, children: /* @__PURE__ */ jsx9(Text9, { wrap: "wrap", children: renderMarkdown(text) }) })
|
|
859
1100
|
] });
|
|
860
1101
|
}
|
|
861
|
-
return /* @__PURE__ */
|
|
1102
|
+
return /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "Thinking..." });
|
|
862
1103
|
}
|
|
863
1104
|
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextPercentage) {
|
|
864
1105
|
setIsThinking(true);
|
|
@@ -868,24 +1109,10 @@ async function runSessionPrompt(prompt, session, addMessage, clearStreamingText,
|
|
|
868
1109
|
const response = await session.run(prompt);
|
|
869
1110
|
clearStreamingText();
|
|
870
1111
|
const history = session.getHistory();
|
|
871
|
-
const toolLines =
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
for (const tc of msg.toolCalls) {
|
|
876
|
-
let value = "";
|
|
877
|
-
try {
|
|
878
|
-
const parsed = JSON.parse(tc.function.arguments);
|
|
879
|
-
const firstVal = Object.values(parsed)[0];
|
|
880
|
-
value = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
|
|
881
|
-
} catch {
|
|
882
|
-
value = tc.function.arguments;
|
|
883
|
-
}
|
|
884
|
-
const truncated = value.length > 80 ? value.slice(0, 77) + "..." : value;
|
|
885
|
-
toolLines.push(`${tc.function.name}(${truncated})`);
|
|
886
|
-
}
|
|
887
|
-
}
|
|
888
|
-
}
|
|
1112
|
+
const toolLines = extractToolCalls(
|
|
1113
|
+
history,
|
|
1114
|
+
historyBefore
|
|
1115
|
+
);
|
|
889
1116
|
if (toolLines.length > 0) {
|
|
890
1117
|
addMessage({ role: "tool", content: toolLines.join("\n"), toolName: `${toolLines.length} tools` });
|
|
891
1118
|
}
|
|
@@ -920,7 +1147,7 @@ Execute the "${cmd}" skill: ${userInstruction}`;
|
|
|
920
1147
|
return `Use the "${cmd}" skill: ${userInstruction}`;
|
|
921
1148
|
}
|
|
922
1149
|
function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextPercentage, registry) {
|
|
923
|
-
return
|
|
1150
|
+
return useCallback3(
|
|
924
1151
|
async (input) => {
|
|
925
1152
|
if (input.startsWith("/")) {
|
|
926
1153
|
const handled = await handleSlashCommand(input);
|
|
@@ -961,7 +1188,7 @@ function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamin
|
|
|
961
1188
|
);
|
|
962
1189
|
}
|
|
963
1190
|
function useCommandRegistry(cwd) {
|
|
964
|
-
const registryRef =
|
|
1191
|
+
const registryRef = useRef3(null);
|
|
965
1192
|
if (registryRef.current === null) {
|
|
966
1193
|
const registry = new CommandRegistry();
|
|
967
1194
|
registry.addSource(new BuiltinCommandSource());
|
|
@@ -974,10 +1201,12 @@ function App(props) {
|
|
|
974
1201
|
const { exit } = useApp();
|
|
975
1202
|
const { session, permissionRequest, streamingText, clearStreamingText } = useSession(props);
|
|
976
1203
|
const { messages, setMessages, addMessage } = useMessages();
|
|
977
|
-
const [isThinking, setIsThinking] =
|
|
978
|
-
const [contextPercentage, setContextPercentage] =
|
|
1204
|
+
const [isThinking, setIsThinking] = useState5(false);
|
|
1205
|
+
const [contextPercentage, setContextPercentage] = useState5(0);
|
|
979
1206
|
const registry = useCommandRegistry(props.cwd ?? process.cwd());
|
|
980
|
-
const
|
|
1207
|
+
const pendingModelChangeRef = useRef3(null);
|
|
1208
|
+
const [pendingModelId, setPendingModelId] = useState5(null);
|
|
1209
|
+
const handleSlashCommand = useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId);
|
|
981
1210
|
const handleSubmit = useSubmitHandler(
|
|
982
1211
|
session,
|
|
983
1212
|
addMessage,
|
|
@@ -987,37 +1216,59 @@ function App(props) {
|
|
|
987
1216
|
setContextPercentage,
|
|
988
1217
|
registry
|
|
989
1218
|
);
|
|
990
|
-
|
|
1219
|
+
useInput5(
|
|
991
1220
|
(_input, key) => {
|
|
992
1221
|
if (key.ctrl && _input === "c") exit();
|
|
993
1222
|
if (key.escape && isThinking) session.abort();
|
|
994
1223
|
},
|
|
995
1224
|
{ isActive: !permissionRequest }
|
|
996
1225
|
);
|
|
997
|
-
return /* @__PURE__ */
|
|
998
|
-
/* @__PURE__ */
|
|
999
|
-
/* @__PURE__ */
|
|
1226
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
1227
|
+
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
1228
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: `
|
|
1000
1229
|
____ ___ ____ ___ _____ _
|
|
1001
1230
|
| _ \\ / _ \\| __ ) / _ \\_ _|/ \\
|
|
1002
1231
|
| |_) | | | | _ \\| | | || | / _ \\
|
|
1003
1232
|
| _ <| |_| | |_) | |_| || |/ ___ \\
|
|
1004
1233
|
|_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
|
|
1005
1234
|
` }),
|
|
1006
|
-
/* @__PURE__ */
|
|
1235
|
+
/* @__PURE__ */ jsxs7(Text9, { dimColor: true, children: [
|
|
1007
1236
|
" v",
|
|
1008
1237
|
props.version ?? "0.0.0"
|
|
1009
1238
|
] })
|
|
1010
1239
|
] }),
|
|
1011
|
-
/* @__PURE__ */
|
|
1012
|
-
/* @__PURE__ */
|
|
1013
|
-
isThinking && /* @__PURE__ */
|
|
1240
|
+
/* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
|
|
1241
|
+
/* @__PURE__ */ jsx9(MessageList, { messages }),
|
|
1242
|
+
isThinking && /* @__PURE__ */ jsx9(Box7, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx9(StreamingIndicator, { text: streamingText }) })
|
|
1014
1243
|
] }),
|
|
1015
|
-
permissionRequest && /* @__PURE__ */
|
|
1016
|
-
/* @__PURE__ */
|
|
1244
|
+
permissionRequest && /* @__PURE__ */ jsx9(PermissionPrompt, { request: permissionRequest }),
|
|
1245
|
+
pendingModelId && /* @__PURE__ */ jsx9(
|
|
1246
|
+
ConfirmPrompt,
|
|
1247
|
+
{
|
|
1248
|
+
message: `Change model to ${getModelName(pendingModelId)}? This will restart the session.`,
|
|
1249
|
+
onSelect: (index) => {
|
|
1250
|
+
setPendingModelId(null);
|
|
1251
|
+
pendingModelChangeRef.current = null;
|
|
1252
|
+
if (index === 0) {
|
|
1253
|
+
try {
|
|
1254
|
+
const settingsPath = getUserSettingsPath();
|
|
1255
|
+
updateModelInSettings(settingsPath, pendingModelId);
|
|
1256
|
+
addMessage({ role: "system", content: `Model changed to ${getModelName(pendingModelId)}. Restarting...` });
|
|
1257
|
+
setTimeout(() => exit(), 500);
|
|
1258
|
+
} catch (err) {
|
|
1259
|
+
addMessage({ role: "system", content: `Failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
1260
|
+
}
|
|
1261
|
+
} else {
|
|
1262
|
+
addMessage({ role: "system", content: "Model change cancelled." });
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
),
|
|
1267
|
+
/* @__PURE__ */ jsx9(
|
|
1017
1268
|
StatusBar,
|
|
1018
1269
|
{
|
|
1019
1270
|
permissionMode: session.getPermissionMode(),
|
|
1020
|
-
modelName: props.config.provider.model,
|
|
1271
|
+
modelName: getModelName(props.config.provider.model),
|
|
1021
1272
|
sessionId: session.getSessionId(),
|
|
1022
1273
|
messageCount: messages.length,
|
|
1023
1274
|
isThinking,
|
|
@@ -1026,7 +1277,7 @@ function App(props) {
|
|
|
1026
1277
|
contextMaxTokens: session.getContextState().maxTokens
|
|
1027
1278
|
}
|
|
1028
1279
|
),
|
|
1029
|
-
/* @__PURE__ */
|
|
1280
|
+
/* @__PURE__ */ jsx9(
|
|
1030
1281
|
InputArea,
|
|
1031
1282
|
{
|
|
1032
1283
|
onSubmit: handleSubmit,
|
|
@@ -1034,12 +1285,12 @@ function App(props) {
|
|
|
1034
1285
|
registry
|
|
1035
1286
|
}
|
|
1036
1287
|
),
|
|
1037
|
-
/* @__PURE__ */
|
|
1288
|
+
/* @__PURE__ */ jsx9(Text9, { children: " " })
|
|
1038
1289
|
] });
|
|
1039
1290
|
}
|
|
1040
1291
|
|
|
1041
1292
|
// src/ui/render.tsx
|
|
1042
|
-
import { jsx as
|
|
1293
|
+
import { jsx as jsx10 } from "react/jsx-runtime";
|
|
1043
1294
|
function renderApp(options) {
|
|
1044
1295
|
process.on("unhandledRejection", (reason) => {
|
|
1045
1296
|
process.stderr.write(`
|
|
@@ -1050,7 +1301,7 @@ function renderApp(options) {
|
|
|
1050
1301
|
`);
|
|
1051
1302
|
}
|
|
1052
1303
|
});
|
|
1053
|
-
const instance = render(/* @__PURE__ */
|
|
1304
|
+
const instance = render(/* @__PURE__ */ jsx10(App, { ...options }), {
|
|
1054
1305
|
exitOnCtrlC: true
|
|
1055
1306
|
});
|
|
1056
1307
|
instance.waitUntilExit().catch((err) => {
|
|
@@ -1063,15 +1314,14 @@ function renderApp(options) {
|
|
|
1063
1314
|
}
|
|
1064
1315
|
|
|
1065
1316
|
// src/cli.ts
|
|
1066
|
-
var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
1067
1317
|
function readVersion() {
|
|
1068
1318
|
try {
|
|
1069
1319
|
const thisFile = fileURLToPath(import.meta.url);
|
|
1070
|
-
const dir =
|
|
1071
|
-
const candidates = [
|
|
1320
|
+
const dir = dirname2(thisFile);
|
|
1321
|
+
const candidates = [join3(dir, "..", "..", "package.json"), join3(dir, "..", "package.json")];
|
|
1072
1322
|
for (const pkgPath of candidates) {
|
|
1073
1323
|
try {
|
|
1074
|
-
const raw =
|
|
1324
|
+
const raw = readFileSync3(pkgPath, "utf-8");
|
|
1075
1325
|
const pkg = JSON.parse(raw);
|
|
1076
1326
|
if (pkg.version !== void 0 && pkg.name !== void 0) {
|
|
1077
1327
|
return pkg.version;
|
|
@@ -1084,108 +1334,11 @@ function readVersion() {
|
|
|
1084
1334
|
return "0.0.0";
|
|
1085
1335
|
}
|
|
1086
1336
|
}
|
|
1087
|
-
function parsePermissionMode(raw) {
|
|
1088
|
-
if (raw === void 0) return void 0;
|
|
1089
|
-
if (!VALID_MODES.includes(raw)) {
|
|
1090
|
-
process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
|
|
1091
|
-
`);
|
|
1092
|
-
process.exit(1);
|
|
1093
|
-
}
|
|
1094
|
-
return raw;
|
|
1095
|
-
}
|
|
1096
|
-
function parseMaxTurns(raw) {
|
|
1097
|
-
if (raw === void 0) return void 0;
|
|
1098
|
-
const n = parseInt(raw, 10);
|
|
1099
|
-
if (isNaN(n) || n <= 0) {
|
|
1100
|
-
process.stderr.write(`Invalid --max-turns "${raw}". Must be a positive integer.
|
|
1101
|
-
`);
|
|
1102
|
-
process.exit(1);
|
|
1103
|
-
}
|
|
1104
|
-
return n;
|
|
1105
|
-
}
|
|
1106
|
-
function parseCliArgs() {
|
|
1107
|
-
const { values, positionals } = parseArgs({
|
|
1108
|
-
allowPositionals: true,
|
|
1109
|
-
options: {
|
|
1110
|
-
p: { type: "boolean", short: "p", default: false },
|
|
1111
|
-
c: { type: "boolean", short: "c", default: false },
|
|
1112
|
-
r: { type: "string", short: "r" },
|
|
1113
|
-
model: { type: "string" },
|
|
1114
|
-
"permission-mode": { type: "string" },
|
|
1115
|
-
"max-turns": { type: "string" },
|
|
1116
|
-
version: { type: "boolean", default: false },
|
|
1117
|
-
reset: { type: "boolean", default: false }
|
|
1118
|
-
}
|
|
1119
|
-
});
|
|
1120
|
-
return {
|
|
1121
|
-
positional: positionals,
|
|
1122
|
-
printMode: values["p"] ?? false,
|
|
1123
|
-
continueMode: values["c"] ?? false,
|
|
1124
|
-
resumeId: values["r"],
|
|
1125
|
-
model: values["model"],
|
|
1126
|
-
permissionMode: parsePermissionMode(values["permission-mode"]),
|
|
1127
|
-
maxTurns: parseMaxTurns(values["max-turns"]),
|
|
1128
|
-
version: values["version"] ?? false,
|
|
1129
|
-
reset: values["reset"] ?? false
|
|
1130
|
-
};
|
|
1131
|
-
}
|
|
1132
|
-
var PrintTerminal = class {
|
|
1133
|
-
write(text) {
|
|
1134
|
-
process.stdout.write(text);
|
|
1135
|
-
}
|
|
1136
|
-
writeLine(text) {
|
|
1137
|
-
process.stdout.write(text + "\n");
|
|
1138
|
-
}
|
|
1139
|
-
writeMarkdown(md) {
|
|
1140
|
-
process.stdout.write(md);
|
|
1141
|
-
}
|
|
1142
|
-
writeError(text) {
|
|
1143
|
-
process.stderr.write(text + "\n");
|
|
1144
|
-
}
|
|
1145
|
-
prompt(question) {
|
|
1146
|
-
return new Promise((resolve) => {
|
|
1147
|
-
const rl = readline.createInterface({
|
|
1148
|
-
input: process.stdin,
|
|
1149
|
-
output: process.stdout,
|
|
1150
|
-
terminal: false,
|
|
1151
|
-
historySize: 0
|
|
1152
|
-
});
|
|
1153
|
-
rl.question(question, (answer) => {
|
|
1154
|
-
rl.close();
|
|
1155
|
-
resolve(answer);
|
|
1156
|
-
});
|
|
1157
|
-
});
|
|
1158
|
-
}
|
|
1159
|
-
async select(options, initialIndex = 0) {
|
|
1160
|
-
for (let i = 0; i < options.length; i++) {
|
|
1161
|
-
const marker = i === initialIndex ? ">" : " ";
|
|
1162
|
-
process.stdout.write(` ${marker} ${i + 1}) ${options[i]}
|
|
1163
|
-
`);
|
|
1164
|
-
}
|
|
1165
|
-
const answer = await this.prompt(
|
|
1166
|
-
` Choose [1-${options.length}] (default: ${options[initialIndex]}): `
|
|
1167
|
-
);
|
|
1168
|
-
const trimmed = answer.trim().toLowerCase();
|
|
1169
|
-
if (trimmed === "") return initialIndex;
|
|
1170
|
-
const num = parseInt(trimmed, 10);
|
|
1171
|
-
if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
|
|
1172
|
-
return initialIndex;
|
|
1173
|
-
}
|
|
1174
|
-
spinner(_message) {
|
|
1175
|
-
return { stop() {
|
|
1176
|
-
}, update() {
|
|
1177
|
-
} };
|
|
1178
|
-
}
|
|
1179
|
-
};
|
|
1180
|
-
function getUserSettingsPath() {
|
|
1181
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
|
|
1182
|
-
return join2(home, ".robota", "settings.json");
|
|
1183
|
-
}
|
|
1184
1337
|
async function ensureConfig(cwd) {
|
|
1185
1338
|
const userPath = getUserSettingsPath();
|
|
1186
|
-
const projectPath =
|
|
1187
|
-
const localPath =
|
|
1188
|
-
if (
|
|
1339
|
+
const projectPath = join3(cwd, ".robota", "settings.json");
|
|
1340
|
+
const localPath = join3(cwd, ".robota", "settings.local.json");
|
|
1341
|
+
if (existsSync3(userPath) || existsSync3(projectPath) || existsSync3(localPath)) {
|
|
1189
1342
|
return;
|
|
1190
1343
|
}
|
|
1191
1344
|
process.stdout.write("\n");
|
|
@@ -1229,8 +1382,8 @@ async function ensureConfig(cwd) {
|
|
|
1229
1382
|
process.stderr.write("\n No API key provided. Exiting.\n");
|
|
1230
1383
|
process.exit(1);
|
|
1231
1384
|
}
|
|
1232
|
-
const settingsDir =
|
|
1233
|
-
|
|
1385
|
+
const settingsDir = dirname2(userPath);
|
|
1386
|
+
mkdirSync2(settingsDir, { recursive: true });
|
|
1234
1387
|
const settings = {
|
|
1235
1388
|
provider: {
|
|
1236
1389
|
name: "anthropic",
|
|
@@ -1238,7 +1391,7 @@ async function ensureConfig(cwd) {
|
|
|
1238
1391
|
apiKey
|
|
1239
1392
|
}
|
|
1240
1393
|
};
|
|
1241
|
-
|
|
1394
|
+
writeFileSync2(userPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
1242
1395
|
process.stdout.write(`
|
|
1243
1396
|
Config saved to ${userPath}
|
|
1244
1397
|
|
|
@@ -1246,8 +1399,7 @@ async function ensureConfig(cwd) {
|
|
|
1246
1399
|
}
|
|
1247
1400
|
function resetConfig() {
|
|
1248
1401
|
const userPath = getUserSettingsPath();
|
|
1249
|
-
if (
|
|
1250
|
-
unlinkSync(userPath);
|
|
1402
|
+
if (deleteSettings(userPath)) {
|
|
1251
1403
|
process.stdout.write(`Deleted ${userPath}
|
|
1252
1404
|
`);
|
|
1253
1405
|
} else {
|