@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
package/dist/node/index.cjs
CHANGED
|
@@ -40,21 +40,275 @@ module.exports = __toCommonJS(index_exports);
|
|
|
40
40
|
var import_agent_sdk4 = require("@robota-sdk/agent-sdk");
|
|
41
41
|
|
|
42
42
|
// src/cli.ts
|
|
43
|
-
var
|
|
44
|
-
var
|
|
45
|
-
var import_node_path2 = require("path");
|
|
43
|
+
var import_node_fs3 = require("fs");
|
|
44
|
+
var import_node_path3 = require("path");
|
|
46
45
|
var import_node_url = require("url");
|
|
47
|
-
var readline = __toESM(require("readline"), 1);
|
|
48
46
|
var import_agent_sdk2 = require("@robota-sdk/agent-sdk");
|
|
49
47
|
var import_agent_sdk3 = require("@robota-sdk/agent-sdk");
|
|
50
48
|
|
|
49
|
+
// src/utils/cli-args.ts
|
|
50
|
+
var import_node_util = require("util");
|
|
51
|
+
var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
52
|
+
function parsePermissionMode(raw) {
|
|
53
|
+
if (raw === void 0) return void 0;
|
|
54
|
+
if (!VALID_MODES.includes(raw)) {
|
|
55
|
+
process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
|
|
56
|
+
`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
}
|
|
59
|
+
return raw;
|
|
60
|
+
}
|
|
61
|
+
function parseMaxTurns(raw) {
|
|
62
|
+
if (raw === void 0) return void 0;
|
|
63
|
+
const n = parseInt(raw, 10);
|
|
64
|
+
if (isNaN(n) || n <= 0) {
|
|
65
|
+
process.stderr.write(`Invalid --max-turns "${raw}". Must be a positive integer.
|
|
66
|
+
`);
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
return n;
|
|
70
|
+
}
|
|
71
|
+
function parseCliArgs() {
|
|
72
|
+
const { values, positionals } = (0, import_node_util.parseArgs)({
|
|
73
|
+
allowPositionals: true,
|
|
74
|
+
options: {
|
|
75
|
+
p: { type: "boolean", short: "p", default: false },
|
|
76
|
+
c: { type: "boolean", short: "c", default: false },
|
|
77
|
+
r: { type: "string", short: "r" },
|
|
78
|
+
model: { type: "string" },
|
|
79
|
+
"permission-mode": { type: "string" },
|
|
80
|
+
"max-turns": { type: "string" },
|
|
81
|
+
version: { type: "boolean", default: false },
|
|
82
|
+
reset: { type: "boolean", default: false }
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
return {
|
|
86
|
+
positional: positionals,
|
|
87
|
+
printMode: values["p"] ?? false,
|
|
88
|
+
continueMode: values["c"] ?? false,
|
|
89
|
+
resumeId: values["r"],
|
|
90
|
+
model: values["model"],
|
|
91
|
+
permissionMode: parsePermissionMode(values["permission-mode"]),
|
|
92
|
+
maxTurns: parseMaxTurns(values["max-turns"]),
|
|
93
|
+
version: values["version"] ?? false,
|
|
94
|
+
reset: values["reset"] ?? false
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/utils/settings-io.ts
|
|
99
|
+
var import_node_fs = require("fs");
|
|
100
|
+
var import_node_path = require("path");
|
|
101
|
+
function getUserSettingsPath() {
|
|
102
|
+
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
|
|
103
|
+
return (0, import_node_path.join)(home, ".robota", "settings.json");
|
|
104
|
+
}
|
|
105
|
+
function readSettings(path) {
|
|
106
|
+
if (!(0, import_node_fs.existsSync)(path)) return {};
|
|
107
|
+
return JSON.parse((0, import_node_fs.readFileSync)(path, "utf8"));
|
|
108
|
+
}
|
|
109
|
+
function writeSettings(path, settings) {
|
|
110
|
+
(0, import_node_fs.mkdirSync)((0, import_node_path.dirname)(path), { recursive: true });
|
|
111
|
+
(0, import_node_fs.writeFileSync)(path, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
112
|
+
}
|
|
113
|
+
function updateModelInSettings(settingsPath, modelId) {
|
|
114
|
+
const settings = readSettings(settingsPath);
|
|
115
|
+
const provider = settings.provider ?? {};
|
|
116
|
+
provider.model = modelId;
|
|
117
|
+
settings.provider = provider;
|
|
118
|
+
writeSettings(settingsPath, settings);
|
|
119
|
+
}
|
|
120
|
+
function deleteSettings(path) {
|
|
121
|
+
if ((0, import_node_fs.existsSync)(path)) {
|
|
122
|
+
(0, import_node_fs.unlinkSync)(path);
|
|
123
|
+
return true;
|
|
124
|
+
}
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// src/print-terminal.ts
|
|
129
|
+
var readline = __toESM(require("readline"), 1);
|
|
130
|
+
var PrintTerminal = class {
|
|
131
|
+
write(text) {
|
|
132
|
+
process.stdout.write(text);
|
|
133
|
+
}
|
|
134
|
+
writeLine(text) {
|
|
135
|
+
process.stdout.write(text + "\n");
|
|
136
|
+
}
|
|
137
|
+
writeMarkdown(md) {
|
|
138
|
+
process.stdout.write(md);
|
|
139
|
+
}
|
|
140
|
+
writeError(text) {
|
|
141
|
+
process.stderr.write(text + "\n");
|
|
142
|
+
}
|
|
143
|
+
prompt(question) {
|
|
144
|
+
return new Promise((resolve) => {
|
|
145
|
+
const rl = readline.createInterface({
|
|
146
|
+
input: process.stdin,
|
|
147
|
+
output: process.stdout,
|
|
148
|
+
terminal: false,
|
|
149
|
+
historySize: 0
|
|
150
|
+
});
|
|
151
|
+
rl.question(question, (answer) => {
|
|
152
|
+
rl.close();
|
|
153
|
+
resolve(answer);
|
|
154
|
+
});
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
async select(options, initialIndex = 0) {
|
|
158
|
+
for (let i = 0; i < options.length; i++) {
|
|
159
|
+
const marker = i === initialIndex ? ">" : " ";
|
|
160
|
+
process.stdout.write(` ${marker} ${i + 1}) ${options[i]}
|
|
161
|
+
`);
|
|
162
|
+
}
|
|
163
|
+
const answer = await this.prompt(
|
|
164
|
+
` Choose [1-${options.length}] (default: ${options[initialIndex]}): `
|
|
165
|
+
);
|
|
166
|
+
const trimmed = answer.trim().toLowerCase();
|
|
167
|
+
if (trimmed === "") return initialIndex;
|
|
168
|
+
const num = parseInt(trimmed, 10);
|
|
169
|
+
if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
|
|
170
|
+
return initialIndex;
|
|
171
|
+
}
|
|
172
|
+
spinner(_message) {
|
|
173
|
+
return { stop() {
|
|
174
|
+
}, update() {
|
|
175
|
+
} };
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
51
179
|
// src/ui/render.tsx
|
|
180
|
+
var import_ink10 = require("ink");
|
|
181
|
+
|
|
182
|
+
// src/ui/App.tsx
|
|
183
|
+
var import_react6 = require("react");
|
|
52
184
|
var import_ink9 = require("ink");
|
|
53
185
|
|
|
186
|
+
// src/commands/slash-executor.ts
|
|
187
|
+
var VALID_MODES2 = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
188
|
+
var HELP_TEXT = [
|
|
189
|
+
"Available commands:",
|
|
190
|
+
" /help \u2014 Show this help",
|
|
191
|
+
" /clear \u2014 Clear conversation",
|
|
192
|
+
" /compact [instr] \u2014 Compact context (optional focus instructions)",
|
|
193
|
+
" /mode [m] \u2014 Show/change permission mode",
|
|
194
|
+
" /cost \u2014 Show session info",
|
|
195
|
+
" /reset \u2014 Delete settings and exit",
|
|
196
|
+
" /exit \u2014 Exit CLI"
|
|
197
|
+
].join("\n");
|
|
198
|
+
function handleHelp(addMessage) {
|
|
199
|
+
addMessage({ role: "system", content: HELP_TEXT });
|
|
200
|
+
return { handled: true };
|
|
201
|
+
}
|
|
202
|
+
function handleClear(addMessage, clearMessages, session) {
|
|
203
|
+
clearMessages();
|
|
204
|
+
session.clearHistory();
|
|
205
|
+
addMessage({ role: "system", content: "Conversation cleared." });
|
|
206
|
+
return { handled: true };
|
|
207
|
+
}
|
|
208
|
+
async function handleCompact(args, session, addMessage) {
|
|
209
|
+
const instructions = args.trim() || void 0;
|
|
210
|
+
const before = session.getContextState().usedPercentage;
|
|
211
|
+
addMessage({ role: "system", content: "Compacting context..." });
|
|
212
|
+
await session.compact(instructions);
|
|
213
|
+
const after = session.getContextState().usedPercentage;
|
|
214
|
+
addMessage({
|
|
215
|
+
role: "system",
|
|
216
|
+
content: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`
|
|
217
|
+
});
|
|
218
|
+
return { handled: true };
|
|
219
|
+
}
|
|
220
|
+
function handleMode(arg, session, addMessage) {
|
|
221
|
+
if (!arg) {
|
|
222
|
+
addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
|
|
223
|
+
} else if (VALID_MODES2.includes(arg)) {
|
|
224
|
+
session.setPermissionMode(arg);
|
|
225
|
+
addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
|
|
226
|
+
} else {
|
|
227
|
+
addMessage({ role: "system", content: `Invalid mode. Valid: ${VALID_MODES2.join(" | ")}` });
|
|
228
|
+
}
|
|
229
|
+
return { handled: true };
|
|
230
|
+
}
|
|
231
|
+
function handleModel(modelId, addMessage) {
|
|
232
|
+
if (!modelId) {
|
|
233
|
+
addMessage({ role: "system", content: "Select a model from the /model submenu." });
|
|
234
|
+
return { handled: true };
|
|
235
|
+
}
|
|
236
|
+
return { handled: true, pendingModelId: modelId };
|
|
237
|
+
}
|
|
238
|
+
function handleCost(session, addMessage) {
|
|
239
|
+
addMessage({
|
|
240
|
+
role: "system",
|
|
241
|
+
content: `Session: ${session.getSessionId()}
|
|
242
|
+
Messages: ${session.getMessageCount()}`
|
|
243
|
+
});
|
|
244
|
+
return { handled: true };
|
|
245
|
+
}
|
|
246
|
+
function handlePermissions(session, addMessage) {
|
|
247
|
+
const mode = session.getPermissionMode();
|
|
248
|
+
const sessionAllowed = session.getSessionAllowedTools();
|
|
249
|
+
const lines = [`Permission mode: ${mode}`];
|
|
250
|
+
if (sessionAllowed.length > 0) {
|
|
251
|
+
lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
|
|
252
|
+
} else {
|
|
253
|
+
lines.push("No session-approved tools.");
|
|
254
|
+
}
|
|
255
|
+
addMessage({ role: "system", content: lines.join("\n") });
|
|
256
|
+
return { handled: true };
|
|
257
|
+
}
|
|
258
|
+
function handleContext(session, addMessage) {
|
|
259
|
+
const ctx = session.getContextState();
|
|
260
|
+
addMessage({
|
|
261
|
+
role: "system",
|
|
262
|
+
content: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`
|
|
263
|
+
});
|
|
264
|
+
return { handled: true };
|
|
265
|
+
}
|
|
266
|
+
function handleReset(addMessage) {
|
|
267
|
+
const settingsPath = getUserSettingsPath();
|
|
268
|
+
if (deleteSettings(settingsPath)) {
|
|
269
|
+
addMessage({ role: "system", content: `Deleted ${settingsPath}. Exiting...` });
|
|
270
|
+
} else {
|
|
271
|
+
addMessage({ role: "system", content: "No user settings found." });
|
|
272
|
+
}
|
|
273
|
+
return { handled: true, exitRequested: true };
|
|
274
|
+
}
|
|
275
|
+
async function executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry) {
|
|
276
|
+
switch (cmd) {
|
|
277
|
+
case "help":
|
|
278
|
+
return handleHelp(addMessage);
|
|
279
|
+
case "clear":
|
|
280
|
+
return handleClear(addMessage, clearMessages, session);
|
|
281
|
+
case "compact":
|
|
282
|
+
return handleCompact(args, session, addMessage);
|
|
283
|
+
case "mode":
|
|
284
|
+
return handleMode(args.split(/\s+/)[0] || void 0, session, addMessage);
|
|
285
|
+
case "model":
|
|
286
|
+
return handleModel(args.split(/\s+/)[0] || void 0, addMessage);
|
|
287
|
+
case "cost":
|
|
288
|
+
return handleCost(session, addMessage);
|
|
289
|
+
case "permissions":
|
|
290
|
+
return handlePermissions(session, addMessage);
|
|
291
|
+
case "context":
|
|
292
|
+
return handleContext(session, addMessage);
|
|
293
|
+
case "reset":
|
|
294
|
+
return handleReset(addMessage);
|
|
295
|
+
case "exit":
|
|
296
|
+
return { handled: true, exitRequested: true };
|
|
297
|
+
default: {
|
|
298
|
+
const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
|
|
299
|
+
if (skillCmd) {
|
|
300
|
+
addMessage({ role: "system", content: `Invoking skill: ${cmd}` });
|
|
301
|
+
return { handled: false };
|
|
302
|
+
}
|
|
303
|
+
addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
|
|
304
|
+
return { handled: true };
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
54
309
|
// src/ui/App.tsx
|
|
55
|
-
var import_react5 = require("react");
|
|
56
|
-
var import_ink8 = require("ink");
|
|
57
310
|
var import_agent_sdk = require("@robota-sdk/agent-sdk");
|
|
311
|
+
var import_agent_core3 = require("@robota-sdk/agent-core");
|
|
58
312
|
|
|
59
313
|
// src/commands/command-registry.ts
|
|
60
314
|
var CommandRegistry = class {
|
|
@@ -87,6 +341,21 @@ var CommandRegistry = class {
|
|
|
87
341
|
};
|
|
88
342
|
|
|
89
343
|
// src/commands/builtin-source.ts
|
|
344
|
+
var import_agent_core = require("@robota-sdk/agent-core");
|
|
345
|
+
function buildModelSubcommands() {
|
|
346
|
+
const seen = /* @__PURE__ */ new Set();
|
|
347
|
+
const commands = [];
|
|
348
|
+
for (const model of Object.values(import_agent_core.CLAUDE_MODELS)) {
|
|
349
|
+
if (seen.has(model.name)) continue;
|
|
350
|
+
seen.add(model.name);
|
|
351
|
+
commands.push({
|
|
352
|
+
name: model.id,
|
|
353
|
+
description: `${model.name} (${(0, import_agent_core.formatTokenCount)(model.contextWindow).toUpperCase()})`,
|
|
354
|
+
source: "builtin"
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
return commands;
|
|
358
|
+
}
|
|
90
359
|
function createBuiltinCommands() {
|
|
91
360
|
return [
|
|
92
361
|
{ name: "help", description: "Show available commands", source: "builtin" },
|
|
@@ -106,11 +375,7 @@ function createBuiltinCommands() {
|
|
|
106
375
|
name: "model",
|
|
107
376
|
description: "Select AI model",
|
|
108
377
|
source: "builtin",
|
|
109
|
-
subcommands:
|
|
110
|
-
{ name: "claude-opus-4-6", description: "Opus 4.6 (highest quality)", source: "builtin" },
|
|
111
|
-
{ name: "claude-sonnet-4-6", description: "Sonnet 4.6 (balanced)", source: "builtin" },
|
|
112
|
-
{ name: "claude-haiku-4-5", description: "Haiku 4.5 (fastest)", source: "builtin" }
|
|
113
|
-
]
|
|
378
|
+
subcommands: buildModelSubcommands()
|
|
114
379
|
},
|
|
115
380
|
{ name: "compact", description: "Compress context window", source: "builtin" },
|
|
116
381
|
{ name: "cost", description: "Show session info", source: "builtin" },
|
|
@@ -132,8 +397,8 @@ var BuiltinCommandSource = class {
|
|
|
132
397
|
};
|
|
133
398
|
|
|
134
399
|
// src/commands/skill-source.ts
|
|
135
|
-
var
|
|
136
|
-
var
|
|
400
|
+
var import_node_fs2 = require("fs");
|
|
401
|
+
var import_node_path2 = require("path");
|
|
137
402
|
var import_node_os = require("os");
|
|
138
403
|
function parseFrontmatter(content) {
|
|
139
404
|
const lines = content.split("\n");
|
|
@@ -156,14 +421,14 @@ function parseFrontmatter(content) {
|
|
|
156
421
|
return name ? { name, description } : null;
|
|
157
422
|
}
|
|
158
423
|
function scanSkillsDir(skillsDir) {
|
|
159
|
-
if (!(0,
|
|
424
|
+
if (!(0, import_node_fs2.existsSync)(skillsDir)) return [];
|
|
160
425
|
const commands = [];
|
|
161
|
-
const entries = (0,
|
|
426
|
+
const entries = (0, import_node_fs2.readdirSync)(skillsDir, { withFileTypes: true });
|
|
162
427
|
for (const entry of entries) {
|
|
163
428
|
if (!entry.isDirectory()) continue;
|
|
164
|
-
const skillFile = (0,
|
|
165
|
-
if (!(0,
|
|
166
|
-
const content = (0,
|
|
429
|
+
const skillFile = (0, import_node_path2.join)(skillsDir, entry.name, "SKILL.md");
|
|
430
|
+
if (!(0, import_node_fs2.existsSync)(skillFile)) continue;
|
|
431
|
+
const content = (0, import_node_fs2.readFileSync)(skillFile, "utf-8");
|
|
167
432
|
const frontmatter = parseFrontmatter(content);
|
|
168
433
|
commands.push({
|
|
169
434
|
name: frontmatter?.name ?? entry.name,
|
|
@@ -183,8 +448,8 @@ var SkillCommandSource = class {
|
|
|
183
448
|
}
|
|
184
449
|
getCommands() {
|
|
185
450
|
if (this.cachedCommands) return this.cachedCommands;
|
|
186
|
-
const projectSkills = scanSkillsDir((0,
|
|
187
|
-
const userSkills = scanSkillsDir((0,
|
|
451
|
+
const projectSkills = scanSkillsDir((0, import_node_path2.join)(this.cwd, ".agents", "skills"));
|
|
452
|
+
const userSkills = scanSkillsDir((0, import_node_path2.join)((0, import_node_os.homedir)(), ".claude", "skills"));
|
|
188
453
|
const seen = new Set(projectSkills.map((cmd) => cmd.name));
|
|
189
454
|
const merged = [...projectSkills];
|
|
190
455
|
for (const cmd of userSkills) {
|
|
@@ -258,6 +523,7 @@ function MessageList({ messages }) {
|
|
|
258
523
|
|
|
259
524
|
// src/ui/StatusBar.tsx
|
|
260
525
|
var import_ink2 = require("ink");
|
|
526
|
+
var import_agent_core2 = require("@robota-sdk/agent-core");
|
|
261
527
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
262
528
|
var CONTEXT_YELLOW_THRESHOLD = 70;
|
|
263
529
|
var CONTEXT_RED_THRESHOLD = 90;
|
|
@@ -297,10 +563,10 @@ function StatusBar({
|
|
|
297
563
|
"Context: ",
|
|
298
564
|
Math.round(contextPercentage),
|
|
299
565
|
"% (",
|
|
300
|
-
(
|
|
301
|
-
"
|
|
302
|
-
(
|
|
303
|
-
"
|
|
566
|
+
(0, import_agent_core2.formatTokenCount)(contextUsedTokens),
|
|
567
|
+
"/",
|
|
568
|
+
(0, import_agent_core2.formatTokenCount)(contextMaxTokens),
|
|
569
|
+
")"
|
|
304
570
|
] })
|
|
305
571
|
] }),
|
|
306
572
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_ink2.Text, { children: [
|
|
@@ -447,19 +713,13 @@ var import_jsx_runtime5 = require("react/jsx-runtime");
|
|
|
447
713
|
var MAX_VISIBLE = 8;
|
|
448
714
|
function CommandRow(props) {
|
|
449
715
|
const { cmd, isSelected, showSlash } = props;
|
|
450
|
-
const prefix = showSlash ? "/" : "";
|
|
451
716
|
const indicator = isSelected ? "\u25B8 " : " ";
|
|
452
717
|
const nameColor = isSelected ? "cyan" : void 0;
|
|
453
718
|
const dimmed = !isSelected;
|
|
454
|
-
return /* @__PURE__ */ (0, import_jsx_runtime5.
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
cmd.name
|
|
459
|
-
] }),
|
|
460
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { dimColor: dimmed, children: " " }),
|
|
461
|
-
/* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Text, { color: nameColor, dimColor: dimmed, children: cmd.description })
|
|
462
|
-
] });
|
|
719
|
+
return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(import_ink5.Box, { children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)(import_ink5.Text, { color: nameColor, dimColor: dimmed, children: [
|
|
720
|
+
indicator,
|
|
721
|
+
showSlash ? `/${cmd.name} ${cmd.description}` : cmd.description
|
|
722
|
+
] }) });
|
|
463
723
|
}
|
|
464
724
|
function SlashAutocomplete({
|
|
465
725
|
commands,
|
|
@@ -624,10 +884,53 @@ function InputArea({ onSubmit, isDisabled, registry }) {
|
|
|
624
884
|
] });
|
|
625
885
|
}
|
|
626
886
|
|
|
627
|
-
// src/ui/
|
|
628
|
-
var import_react4 =
|
|
887
|
+
// src/ui/ConfirmPrompt.tsx
|
|
888
|
+
var import_react4 = require("react");
|
|
629
889
|
var import_ink7 = require("ink");
|
|
630
890
|
var import_jsx_runtime7 = require("react/jsx-runtime");
|
|
891
|
+
function ConfirmPrompt({
|
|
892
|
+
message,
|
|
893
|
+
options = ["Yes", "No"],
|
|
894
|
+
onSelect
|
|
895
|
+
}) {
|
|
896
|
+
const [selected, setSelected] = (0, import_react4.useState)(0);
|
|
897
|
+
const resolvedRef = (0, import_react4.useRef)(false);
|
|
898
|
+
const doSelect = (0, import_react4.useCallback)(
|
|
899
|
+
(index) => {
|
|
900
|
+
if (resolvedRef.current) return;
|
|
901
|
+
resolvedRef.current = true;
|
|
902
|
+
onSelect(index);
|
|
903
|
+
},
|
|
904
|
+
[onSelect]
|
|
905
|
+
);
|
|
906
|
+
(0, import_ink7.useInput)((input, key) => {
|
|
907
|
+
if (resolvedRef.current) return;
|
|
908
|
+
if (key.leftArrow || key.upArrow) {
|
|
909
|
+
setSelected((prev) => prev > 0 ? prev - 1 : prev);
|
|
910
|
+
} else if (key.rightArrow || key.downArrow) {
|
|
911
|
+
setSelected((prev) => prev < options.length - 1 ? prev + 1 : prev);
|
|
912
|
+
} else if (key.return) {
|
|
913
|
+
doSelect(selected);
|
|
914
|
+
} else if (input === "y" && options.length === 2) {
|
|
915
|
+
doSelect(0);
|
|
916
|
+
} else if (input === "n" && options.length === 2) {
|
|
917
|
+
doSelect(1);
|
|
918
|
+
}
|
|
919
|
+
});
|
|
920
|
+
return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
921
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { color: "yellow", children: message }),
|
|
922
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Box, { marginTop: 1, children: options.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_ink7.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
|
|
923
|
+
i === selected ? "> " : " ",
|
|
924
|
+
opt
|
|
925
|
+
] }) }, opt)) }),
|
|
926
|
+
/* @__PURE__ */ (0, import_jsx_runtime7.jsx)(import_ink7.Text, { dimColor: true, children: " arrow keys to select, Enter to confirm" })
|
|
927
|
+
] });
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// src/ui/PermissionPrompt.tsx
|
|
931
|
+
var import_react5 = __toESM(require("react"), 1);
|
|
932
|
+
var import_ink8 = require("ink");
|
|
933
|
+
var import_jsx_runtime8 = require("react/jsx-runtime");
|
|
631
934
|
var OPTIONS = ["Allow", "Allow always (this session)", "Deny"];
|
|
632
935
|
function formatArgs(args) {
|
|
633
936
|
const entries = Object.entries(args);
|
|
@@ -635,15 +938,15 @@ function formatArgs(args) {
|
|
|
635
938
|
return entries.map(([k, v]) => `${k}: ${typeof v === "string" ? v : JSON.stringify(v)}`).join(", ");
|
|
636
939
|
}
|
|
637
940
|
function PermissionPrompt({ request }) {
|
|
638
|
-
const [selected, setSelected] =
|
|
639
|
-
const resolvedRef =
|
|
640
|
-
const prevRequestRef =
|
|
941
|
+
const [selected, setSelected] = import_react5.default.useState(0);
|
|
942
|
+
const resolvedRef = import_react5.default.useRef(false);
|
|
943
|
+
const prevRequestRef = import_react5.default.useRef(request);
|
|
641
944
|
if (prevRequestRef.current !== request) {
|
|
642
945
|
prevRequestRef.current = request;
|
|
643
946
|
resolvedRef.current = false;
|
|
644
947
|
setSelected(0);
|
|
645
948
|
}
|
|
646
|
-
const doResolve =
|
|
949
|
+
const doResolve = import_react5.default.useCallback(
|
|
647
950
|
(index) => {
|
|
648
951
|
if (resolvedRef.current) return;
|
|
649
952
|
resolvedRef.current = true;
|
|
@@ -653,7 +956,7 @@ function PermissionPrompt({ request }) {
|
|
|
653
956
|
},
|
|
654
957
|
[request]
|
|
655
958
|
);
|
|
656
|
-
(0,
|
|
959
|
+
(0, import_ink8.useInput)((input, key) => {
|
|
657
960
|
if (resolvedRef.current) return;
|
|
658
961
|
if (key.upArrow || key.leftArrow) {
|
|
659
962
|
setSelected((prev) => prev > 0 ? prev - 1 : prev);
|
|
@@ -669,27 +972,54 @@ function PermissionPrompt({ request }) {
|
|
|
669
972
|
doResolve(2);
|
|
670
973
|
}
|
|
671
974
|
});
|
|
672
|
-
return /* @__PURE__ */ (0,
|
|
673
|
-
/* @__PURE__ */ (0,
|
|
674
|
-
/* @__PURE__ */ (0,
|
|
975
|
+
return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Box, { flexDirection: "column", borderStyle: "round", borderColor: "yellow", paddingX: 1, children: [
|
|
976
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "yellow", bold: true, children: "[Permission Required]" }),
|
|
977
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Text, { children: [
|
|
675
978
|
"Tool:",
|
|
676
979
|
" ",
|
|
677
|
-
/* @__PURE__ */ (0,
|
|
980
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { color: "cyan", bold: true, children: request.toolName })
|
|
678
981
|
] }),
|
|
679
|
-
/* @__PURE__ */ (0,
|
|
982
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Text, { dimColor: true, children: [
|
|
680
983
|
" ",
|
|
681
984
|
formatArgs(request.toolArgs)
|
|
682
985
|
] }),
|
|
683
|
-
/* @__PURE__ */ (0,
|
|
986
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Box, { marginTop: 1, children: OPTIONS.map((opt, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Box, { marginRight: 2, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_ink8.Text, { color: i === selected ? "cyan" : void 0, bold: i === selected, children: [
|
|
684
987
|
i === selected ? "> " : " ",
|
|
685
988
|
opt
|
|
686
989
|
] }) }, opt)) }),
|
|
687
|
-
/* @__PURE__ */ (0,
|
|
990
|
+
/* @__PURE__ */ (0, import_jsx_runtime8.jsx)(import_ink8.Text, { dimColor: true, children: " left/right to select, Enter to confirm" })
|
|
688
991
|
] });
|
|
689
992
|
}
|
|
690
993
|
|
|
994
|
+
// src/utils/tool-call-extractor.ts
|
|
995
|
+
var TOOL_ARG_MAX_LENGTH = 80;
|
|
996
|
+
var TOOL_ARG_TRUNCATE_LENGTH = 77;
|
|
997
|
+
function extractToolCalls(history, startIndex) {
|
|
998
|
+
const lines = [];
|
|
999
|
+
for (let i = startIndex; i < history.length; i++) {
|
|
1000
|
+
const msg = history[i];
|
|
1001
|
+
if (msg.role === "assistant" && msg.toolCalls) {
|
|
1002
|
+
for (const tc of msg.toolCalls) {
|
|
1003
|
+
const value = parseFirstArgValue(tc.function.arguments);
|
|
1004
|
+
const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_TRUNCATE_LENGTH) + "..." : value;
|
|
1005
|
+
lines.push(`${tc.function.name}(${truncated})`);
|
|
1006
|
+
}
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
return lines;
|
|
1010
|
+
}
|
|
1011
|
+
function parseFirstArgValue(argsJson) {
|
|
1012
|
+
try {
|
|
1013
|
+
const parsed = JSON.parse(argsJson);
|
|
1014
|
+
const firstVal = Object.values(parsed)[0];
|
|
1015
|
+
return typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
|
|
1016
|
+
} catch {
|
|
1017
|
+
return argsJson;
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
|
|
691
1021
|
// src/ui/App.tsx
|
|
692
|
-
var
|
|
1022
|
+
var import_jsx_runtime9 = require("react/jsx-runtime");
|
|
693
1023
|
var msgIdCounter = 0;
|
|
694
1024
|
function nextId() {
|
|
695
1025
|
msgIdCounter += 1;
|
|
@@ -711,11 +1041,11 @@ var NOOP_TERMINAL = {
|
|
|
711
1041
|
} })
|
|
712
1042
|
};
|
|
713
1043
|
function useSession(props) {
|
|
714
|
-
const [permissionRequest, setPermissionRequest] = (0,
|
|
715
|
-
const [streamingText, setStreamingText] = (0,
|
|
716
|
-
const permissionQueueRef = (0,
|
|
717
|
-
const processingRef = (0,
|
|
718
|
-
const processNextPermission = (0,
|
|
1044
|
+
const [permissionRequest, setPermissionRequest] = (0, import_react6.useState)(null);
|
|
1045
|
+
const [streamingText, setStreamingText] = (0, import_react6.useState)("");
|
|
1046
|
+
const permissionQueueRef = (0, import_react6.useRef)([]);
|
|
1047
|
+
const processingRef = (0, import_react6.useRef)(false);
|
|
1048
|
+
const processNextPermission = (0, import_react6.useCallback)(() => {
|
|
719
1049
|
if (processingRef.current) return;
|
|
720
1050
|
const next = permissionQueueRef.current[0];
|
|
721
1051
|
if (!next) {
|
|
@@ -735,7 +1065,7 @@ function useSession(props) {
|
|
|
735
1065
|
}
|
|
736
1066
|
});
|
|
737
1067
|
}, []);
|
|
738
|
-
const sessionRef = (0,
|
|
1068
|
+
const sessionRef = (0, import_react6.useRef)(null);
|
|
739
1069
|
if (sessionRef.current === null) {
|
|
740
1070
|
const permissionHandler = (toolName, toolArgs) => {
|
|
741
1071
|
return new Promise((resolve) => {
|
|
@@ -760,138 +1090,49 @@ function useSession(props) {
|
|
|
760
1090
|
onTextDelta
|
|
761
1091
|
});
|
|
762
1092
|
}
|
|
763
|
-
const clearStreamingText = (0,
|
|
1093
|
+
const clearStreamingText = (0, import_react6.useCallback)(() => setStreamingText(""), []);
|
|
764
1094
|
return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText };
|
|
765
1095
|
}
|
|
766
1096
|
function useMessages() {
|
|
767
|
-
const [messages, setMessages] = (0,
|
|
768
|
-
const addMessage = (0,
|
|
1097
|
+
const [messages, setMessages] = (0, import_react6.useState)([]);
|
|
1098
|
+
const addMessage = (0, import_react6.useCallback)((msg) => {
|
|
769
1099
|
setMessages((prev) => [...prev, { ...msg, id: nextId(), timestamp: /* @__PURE__ */ new Date() }]);
|
|
770
1100
|
}, []);
|
|
771
1101
|
return { messages, setMessages, addMessage };
|
|
772
1102
|
}
|
|
773
|
-
var
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
" /clear \u2014 Clear conversation",
|
|
777
|
-
" /compact [instr] \u2014 Compact context (optional focus instructions)",
|
|
778
|
-
" /mode [m] \u2014 Show/change permission mode",
|
|
779
|
-
" /cost \u2014 Show session info",
|
|
780
|
-
" /reset \u2014 Delete settings and exit",
|
|
781
|
-
" /exit \u2014 Exit CLI"
|
|
782
|
-
].join("\n");
|
|
783
|
-
function handleModeCommand(arg, session, addMessage) {
|
|
784
|
-
const validModes = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
785
|
-
if (!arg) {
|
|
786
|
-
addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
|
|
787
|
-
} else if (validModes.includes(arg)) {
|
|
788
|
-
session.setPermissionMode(arg);
|
|
789
|
-
addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
|
|
790
|
-
} else {
|
|
791
|
-
addMessage({ role: "system", content: `Invalid mode. Valid: ${validModes.join(" | ")}` });
|
|
792
|
-
}
|
|
793
|
-
return true;
|
|
794
|
-
}
|
|
795
|
-
async function executeSlashCommand(cmd, parts, session, addMessage, setMessages, exit, registry) {
|
|
796
|
-
switch (cmd) {
|
|
797
|
-
case "help":
|
|
798
|
-
addMessage({ role: "system", content: HELP_TEXT });
|
|
799
|
-
return true;
|
|
800
|
-
case "clear":
|
|
801
|
-
setMessages([]);
|
|
802
|
-
session.clearHistory();
|
|
803
|
-
addMessage({ role: "system", content: "Conversation cleared." });
|
|
804
|
-
return true;
|
|
805
|
-
case "compact": {
|
|
806
|
-
const instructions = parts.slice(1).join(" ").trim() || void 0;
|
|
807
|
-
const before = session.getContextState().usedPercentage;
|
|
808
|
-
addMessage({ role: "system", content: "Compacting context..." });
|
|
809
|
-
await session.compact(instructions);
|
|
810
|
-
const after = session.getContextState().usedPercentage;
|
|
811
|
-
addMessage({
|
|
812
|
-
role: "system",
|
|
813
|
-
content: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`
|
|
814
|
-
});
|
|
815
|
-
return true;
|
|
816
|
-
}
|
|
817
|
-
case "mode":
|
|
818
|
-
return handleModeCommand(parts[1], session, addMessage);
|
|
819
|
-
case "cost":
|
|
820
|
-
addMessage({
|
|
821
|
-
role: "system",
|
|
822
|
-
content: `Session: ${session.getSessionId()}
|
|
823
|
-
Messages: ${session.getMessageCount()}`
|
|
824
|
-
});
|
|
825
|
-
return true;
|
|
826
|
-
case "permissions": {
|
|
827
|
-
const mode = session.getPermissionMode();
|
|
828
|
-
const sessionAllowed = session.getSessionAllowedTools();
|
|
829
|
-
const lines = [`Permission mode: ${mode}`];
|
|
830
|
-
if (sessionAllowed.length > 0) {
|
|
831
|
-
lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
|
|
832
|
-
} else {
|
|
833
|
-
lines.push("No session-approved tools.");
|
|
834
|
-
}
|
|
835
|
-
addMessage({ role: "system", content: lines.join("\n") });
|
|
836
|
-
return true;
|
|
837
|
-
}
|
|
838
|
-
case "context": {
|
|
839
|
-
const ctx = session.getContextState();
|
|
840
|
-
addMessage({
|
|
841
|
-
role: "system",
|
|
842
|
-
content: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`
|
|
843
|
-
});
|
|
844
|
-
return true;
|
|
845
|
-
}
|
|
846
|
-
case "reset": {
|
|
847
|
-
const { existsSync: exists, unlinkSync: unlink } = await import("fs");
|
|
848
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
|
|
849
|
-
const settingsPath = `${home}/.robota/settings.json`;
|
|
850
|
-
if (exists(settingsPath)) {
|
|
851
|
-
unlink(settingsPath);
|
|
852
|
-
addMessage({ role: "system", content: `Deleted ${settingsPath}. Exiting...` });
|
|
853
|
-
} else {
|
|
854
|
-
addMessage({ role: "system", content: "No user settings found." });
|
|
855
|
-
}
|
|
856
|
-
setTimeout(() => exit(), 500);
|
|
857
|
-
return true;
|
|
858
|
-
}
|
|
859
|
-
case "exit":
|
|
860
|
-
exit();
|
|
861
|
-
return true;
|
|
862
|
-
default: {
|
|
863
|
-
const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
|
|
864
|
-
if (skillCmd) {
|
|
865
|
-
addMessage({ role: "system", content: `Invoking skill: ${cmd}` });
|
|
866
|
-
return false;
|
|
867
|
-
}
|
|
868
|
-
addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
|
|
869
|
-
return true;
|
|
870
|
-
}
|
|
871
|
-
}
|
|
872
|
-
}
|
|
873
|
-
function useSlashCommands(session, addMessage, setMessages, exit, registry) {
|
|
874
|
-
return (0, import_react5.useCallback)(
|
|
1103
|
+
var EXIT_DELAY_MS = 500;
|
|
1104
|
+
function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
|
|
1105
|
+
return (0, import_react6.useCallback)(
|
|
875
1106
|
async (input) => {
|
|
876
1107
|
const parts = input.slice(1).split(/\s+/);
|
|
877
1108
|
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
878
|
-
|
|
1109
|
+
const args = parts.slice(1).join(" ");
|
|
1110
|
+
const clearMessages = () => setMessages([]);
|
|
1111
|
+
const result = await executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry);
|
|
1112
|
+
if (result.pendingModelId) {
|
|
1113
|
+
pendingModelChangeRef.current = result.pendingModelId;
|
|
1114
|
+
setPendingModelId(result.pendingModelId);
|
|
1115
|
+
}
|
|
1116
|
+
if (result.exitRequested) {
|
|
1117
|
+
setTimeout(() => exit(), EXIT_DELAY_MS);
|
|
1118
|
+
}
|
|
1119
|
+
return result.handled;
|
|
879
1120
|
},
|
|
880
|
-
[session, addMessage, setMessages, exit, registry]
|
|
1121
|
+
[session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId]
|
|
881
1122
|
);
|
|
882
1123
|
}
|
|
883
1124
|
function StreamingIndicator({ text }) {
|
|
884
1125
|
if (text) {
|
|
885
|
-
return /* @__PURE__ */ (0,
|
|
886
|
-
/* @__PURE__ */ (0,
|
|
1126
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", children: [
|
|
1127
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { color: "cyan", bold: true, children: [
|
|
887
1128
|
"Robota:",
|
|
888
1129
|
" "
|
|
889
1130
|
] }),
|
|
890
|
-
/* @__PURE__ */ (0,
|
|
891
|
-
/* @__PURE__ */ (0,
|
|
1131
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { children: " " }),
|
|
1132
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { marginLeft: 2, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { wrap: "wrap", children: renderMarkdown(text) }) })
|
|
892
1133
|
] });
|
|
893
1134
|
}
|
|
894
|
-
return /* @__PURE__ */ (0,
|
|
1135
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "yellow", children: "Thinking..." });
|
|
895
1136
|
}
|
|
896
1137
|
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextPercentage) {
|
|
897
1138
|
setIsThinking(true);
|
|
@@ -901,24 +1142,10 @@ async function runSessionPrompt(prompt, session, addMessage, clearStreamingText,
|
|
|
901
1142
|
const response = await session.run(prompt);
|
|
902
1143
|
clearStreamingText();
|
|
903
1144
|
const history = session.getHistory();
|
|
904
|
-
const toolLines =
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
for (const tc of msg.toolCalls) {
|
|
909
|
-
let value = "";
|
|
910
|
-
try {
|
|
911
|
-
const parsed = JSON.parse(tc.function.arguments);
|
|
912
|
-
const firstVal = Object.values(parsed)[0];
|
|
913
|
-
value = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
|
|
914
|
-
} catch {
|
|
915
|
-
value = tc.function.arguments;
|
|
916
|
-
}
|
|
917
|
-
const truncated = value.length > 80 ? value.slice(0, 77) + "..." : value;
|
|
918
|
-
toolLines.push(`${tc.function.name}(${truncated})`);
|
|
919
|
-
}
|
|
920
|
-
}
|
|
921
|
-
}
|
|
1145
|
+
const toolLines = extractToolCalls(
|
|
1146
|
+
history,
|
|
1147
|
+
historyBefore
|
|
1148
|
+
);
|
|
922
1149
|
if (toolLines.length > 0) {
|
|
923
1150
|
addMessage({ role: "tool", content: toolLines.join("\n"), toolName: `${toolLines.length} tools` });
|
|
924
1151
|
}
|
|
@@ -953,7 +1180,7 @@ Execute the "${cmd}" skill: ${userInstruction}`;
|
|
|
953
1180
|
return `Use the "${cmd}" skill: ${userInstruction}`;
|
|
954
1181
|
}
|
|
955
1182
|
function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextPercentage, registry) {
|
|
956
|
-
return (0,
|
|
1183
|
+
return (0, import_react6.useCallback)(
|
|
957
1184
|
async (input) => {
|
|
958
1185
|
if (input.startsWith("/")) {
|
|
959
1186
|
const handled = await handleSlashCommand(input);
|
|
@@ -994,7 +1221,7 @@ function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamin
|
|
|
994
1221
|
);
|
|
995
1222
|
}
|
|
996
1223
|
function useCommandRegistry(cwd) {
|
|
997
|
-
const registryRef = (0,
|
|
1224
|
+
const registryRef = (0, import_react6.useRef)(null);
|
|
998
1225
|
if (registryRef.current === null) {
|
|
999
1226
|
const registry = new CommandRegistry();
|
|
1000
1227
|
registry.addSource(new BuiltinCommandSource());
|
|
@@ -1004,13 +1231,15 @@ function useCommandRegistry(cwd) {
|
|
|
1004
1231
|
return registryRef.current;
|
|
1005
1232
|
}
|
|
1006
1233
|
function App(props) {
|
|
1007
|
-
const { exit } = (0,
|
|
1234
|
+
const { exit } = (0, import_ink9.useApp)();
|
|
1008
1235
|
const { session, permissionRequest, streamingText, clearStreamingText } = useSession(props);
|
|
1009
1236
|
const { messages, setMessages, addMessage } = useMessages();
|
|
1010
|
-
const [isThinking, setIsThinking] = (0,
|
|
1011
|
-
const [contextPercentage, setContextPercentage] = (0,
|
|
1237
|
+
const [isThinking, setIsThinking] = (0, import_react6.useState)(false);
|
|
1238
|
+
const [contextPercentage, setContextPercentage] = (0, import_react6.useState)(0);
|
|
1012
1239
|
const registry = useCommandRegistry(props.cwd ?? process.cwd());
|
|
1013
|
-
const
|
|
1240
|
+
const pendingModelChangeRef = (0, import_react6.useRef)(null);
|
|
1241
|
+
const [pendingModelId, setPendingModelId] = (0, import_react6.useState)(null);
|
|
1242
|
+
const handleSlashCommand = useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId);
|
|
1014
1243
|
const handleSubmit = useSubmitHandler(
|
|
1015
1244
|
session,
|
|
1016
1245
|
addMessage,
|
|
@@ -1020,37 +1249,59 @@ function App(props) {
|
|
|
1020
1249
|
setContextPercentage,
|
|
1021
1250
|
registry
|
|
1022
1251
|
);
|
|
1023
|
-
(0,
|
|
1252
|
+
(0, import_ink9.useInput)(
|
|
1024
1253
|
(_input, key) => {
|
|
1025
1254
|
if (key.ctrl && _input === "c") exit();
|
|
1026
1255
|
if (key.escape && isThinking) session.abort();
|
|
1027
1256
|
},
|
|
1028
1257
|
{ isActive: !permissionRequest }
|
|
1029
1258
|
);
|
|
1030
|
-
return /* @__PURE__ */ (0,
|
|
1031
|
-
/* @__PURE__ */ (0,
|
|
1032
|
-
/* @__PURE__ */ (0,
|
|
1259
|
+
return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", children: [
|
|
1260
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
1261
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { color: "cyan", bold: true, children: `
|
|
1033
1262
|
____ ___ ____ ___ _____ _
|
|
1034
1263
|
| _ \\ / _ \\| __ ) / _ \\_ _|/ \\
|
|
1035
1264
|
| |_) | | | | _ \\| | | || | / _ \\
|
|
1036
1265
|
| _ <| |_| | |_) | |_| || |/ ___ \\
|
|
1037
1266
|
|_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
|
|
1038
1267
|
` }),
|
|
1039
|
-
/* @__PURE__ */ (0,
|
|
1268
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Text, { dimColor: true, children: [
|
|
1040
1269
|
" v",
|
|
1041
1270
|
props.version ?? "0.0.0"
|
|
1042
1271
|
] })
|
|
1043
1272
|
] }),
|
|
1044
|
-
/* @__PURE__ */ (0,
|
|
1045
|
-
/* @__PURE__ */ (0,
|
|
1046
|
-
isThinking && /* @__PURE__ */ (0,
|
|
1273
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_ink9.Box, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
|
|
1274
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(MessageList, { messages }),
|
|
1275
|
+
isThinking && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Box, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(StreamingIndicator, { text: streamingText }) })
|
|
1047
1276
|
] }),
|
|
1048
|
-
permissionRequest && /* @__PURE__ */ (0,
|
|
1049
|
-
/* @__PURE__ */ (0,
|
|
1277
|
+
permissionRequest && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(PermissionPrompt, { request: permissionRequest }),
|
|
1278
|
+
pendingModelId && /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1279
|
+
ConfirmPrompt,
|
|
1280
|
+
{
|
|
1281
|
+
message: `Change model to ${(0, import_agent_core3.getModelName)(pendingModelId)}? This will restart the session.`,
|
|
1282
|
+
onSelect: (index) => {
|
|
1283
|
+
setPendingModelId(null);
|
|
1284
|
+
pendingModelChangeRef.current = null;
|
|
1285
|
+
if (index === 0) {
|
|
1286
|
+
try {
|
|
1287
|
+
const settingsPath = getUserSettingsPath();
|
|
1288
|
+
updateModelInSettings(settingsPath, pendingModelId);
|
|
1289
|
+
addMessage({ role: "system", content: `Model changed to ${(0, import_agent_core3.getModelName)(pendingModelId)}. Restarting...` });
|
|
1290
|
+
setTimeout(() => exit(), 500);
|
|
1291
|
+
} catch (err) {
|
|
1292
|
+
addMessage({ role: "system", content: `Failed: ${err instanceof Error ? err.message : String(err)}` });
|
|
1293
|
+
}
|
|
1294
|
+
} else {
|
|
1295
|
+
addMessage({ role: "system", content: "Model change cancelled." });
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
),
|
|
1300
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1050
1301
|
StatusBar,
|
|
1051
1302
|
{
|
|
1052
1303
|
permissionMode: session.getPermissionMode(),
|
|
1053
|
-
modelName: props.config.provider.model,
|
|
1304
|
+
modelName: (0, import_agent_core3.getModelName)(props.config.provider.model),
|
|
1054
1305
|
sessionId: session.getSessionId(),
|
|
1055
1306
|
messageCount: messages.length,
|
|
1056
1307
|
isThinking,
|
|
@@ -1059,7 +1310,7 @@ function App(props) {
|
|
|
1059
1310
|
contextMaxTokens: session.getContextState().maxTokens
|
|
1060
1311
|
}
|
|
1061
1312
|
),
|
|
1062
|
-
/* @__PURE__ */ (0,
|
|
1313
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
|
|
1063
1314
|
InputArea,
|
|
1064
1315
|
{
|
|
1065
1316
|
onSubmit: handleSubmit,
|
|
@@ -1067,12 +1318,12 @@ function App(props) {
|
|
|
1067
1318
|
registry
|
|
1068
1319
|
}
|
|
1069
1320
|
),
|
|
1070
|
-
/* @__PURE__ */ (0,
|
|
1321
|
+
/* @__PURE__ */ (0, import_jsx_runtime9.jsx)(import_ink9.Text, { children: " " })
|
|
1071
1322
|
] });
|
|
1072
1323
|
}
|
|
1073
1324
|
|
|
1074
1325
|
// src/ui/render.tsx
|
|
1075
|
-
var
|
|
1326
|
+
var import_jsx_runtime10 = require("react/jsx-runtime");
|
|
1076
1327
|
function renderApp(options) {
|
|
1077
1328
|
process.on("unhandledRejection", (reason) => {
|
|
1078
1329
|
process.stderr.write(`
|
|
@@ -1083,7 +1334,7 @@ function renderApp(options) {
|
|
|
1083
1334
|
`);
|
|
1084
1335
|
}
|
|
1085
1336
|
});
|
|
1086
|
-
const instance = (0,
|
|
1337
|
+
const instance = (0, import_ink10.render)(/* @__PURE__ */ (0, import_jsx_runtime10.jsx)(App, { ...options }), {
|
|
1087
1338
|
exitOnCtrlC: true
|
|
1088
1339
|
});
|
|
1089
1340
|
instance.waitUntilExit().catch((err) => {
|
|
@@ -1097,15 +1348,14 @@ function renderApp(options) {
|
|
|
1097
1348
|
|
|
1098
1349
|
// src/cli.ts
|
|
1099
1350
|
var import_meta = {};
|
|
1100
|
-
var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
1101
1351
|
function readVersion() {
|
|
1102
1352
|
try {
|
|
1103
1353
|
const thisFile = (0, import_node_url.fileURLToPath)(import_meta.url);
|
|
1104
|
-
const dir = (0,
|
|
1105
|
-
const candidates = [(0,
|
|
1354
|
+
const dir = (0, import_node_path3.dirname)(thisFile);
|
|
1355
|
+
const candidates = [(0, import_node_path3.join)(dir, "..", "..", "package.json"), (0, import_node_path3.join)(dir, "..", "package.json")];
|
|
1106
1356
|
for (const pkgPath of candidates) {
|
|
1107
1357
|
try {
|
|
1108
|
-
const raw = (0,
|
|
1358
|
+
const raw = (0, import_node_fs3.readFileSync)(pkgPath, "utf-8");
|
|
1109
1359
|
const pkg = JSON.parse(raw);
|
|
1110
1360
|
if (pkg.version !== void 0 && pkg.name !== void 0) {
|
|
1111
1361
|
return pkg.version;
|
|
@@ -1118,108 +1368,11 @@ function readVersion() {
|
|
|
1118
1368
|
return "0.0.0";
|
|
1119
1369
|
}
|
|
1120
1370
|
}
|
|
1121
|
-
function parsePermissionMode(raw) {
|
|
1122
|
-
if (raw === void 0) return void 0;
|
|
1123
|
-
if (!VALID_MODES.includes(raw)) {
|
|
1124
|
-
process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
|
|
1125
|
-
`);
|
|
1126
|
-
process.exit(1);
|
|
1127
|
-
}
|
|
1128
|
-
return raw;
|
|
1129
|
-
}
|
|
1130
|
-
function parseMaxTurns(raw) {
|
|
1131
|
-
if (raw === void 0) return void 0;
|
|
1132
|
-
const n = parseInt(raw, 10);
|
|
1133
|
-
if (isNaN(n) || n <= 0) {
|
|
1134
|
-
process.stderr.write(`Invalid --max-turns "${raw}". Must be a positive integer.
|
|
1135
|
-
`);
|
|
1136
|
-
process.exit(1);
|
|
1137
|
-
}
|
|
1138
|
-
return n;
|
|
1139
|
-
}
|
|
1140
|
-
function parseCliArgs() {
|
|
1141
|
-
const { values, positionals } = (0, import_node_util.parseArgs)({
|
|
1142
|
-
allowPositionals: true,
|
|
1143
|
-
options: {
|
|
1144
|
-
p: { type: "boolean", short: "p", default: false },
|
|
1145
|
-
c: { type: "boolean", short: "c", default: false },
|
|
1146
|
-
r: { type: "string", short: "r" },
|
|
1147
|
-
model: { type: "string" },
|
|
1148
|
-
"permission-mode": { type: "string" },
|
|
1149
|
-
"max-turns": { type: "string" },
|
|
1150
|
-
version: { type: "boolean", default: false },
|
|
1151
|
-
reset: { type: "boolean", default: false }
|
|
1152
|
-
}
|
|
1153
|
-
});
|
|
1154
|
-
return {
|
|
1155
|
-
positional: positionals,
|
|
1156
|
-
printMode: values["p"] ?? false,
|
|
1157
|
-
continueMode: values["c"] ?? false,
|
|
1158
|
-
resumeId: values["r"],
|
|
1159
|
-
model: values["model"],
|
|
1160
|
-
permissionMode: parsePermissionMode(values["permission-mode"]),
|
|
1161
|
-
maxTurns: parseMaxTurns(values["max-turns"]),
|
|
1162
|
-
version: values["version"] ?? false,
|
|
1163
|
-
reset: values["reset"] ?? false
|
|
1164
|
-
};
|
|
1165
|
-
}
|
|
1166
|
-
var PrintTerminal = class {
|
|
1167
|
-
write(text) {
|
|
1168
|
-
process.stdout.write(text);
|
|
1169
|
-
}
|
|
1170
|
-
writeLine(text) {
|
|
1171
|
-
process.stdout.write(text + "\n");
|
|
1172
|
-
}
|
|
1173
|
-
writeMarkdown(md) {
|
|
1174
|
-
process.stdout.write(md);
|
|
1175
|
-
}
|
|
1176
|
-
writeError(text) {
|
|
1177
|
-
process.stderr.write(text + "\n");
|
|
1178
|
-
}
|
|
1179
|
-
prompt(question) {
|
|
1180
|
-
return new Promise((resolve) => {
|
|
1181
|
-
const rl = readline.createInterface({
|
|
1182
|
-
input: process.stdin,
|
|
1183
|
-
output: process.stdout,
|
|
1184
|
-
terminal: false,
|
|
1185
|
-
historySize: 0
|
|
1186
|
-
});
|
|
1187
|
-
rl.question(question, (answer) => {
|
|
1188
|
-
rl.close();
|
|
1189
|
-
resolve(answer);
|
|
1190
|
-
});
|
|
1191
|
-
});
|
|
1192
|
-
}
|
|
1193
|
-
async select(options, initialIndex = 0) {
|
|
1194
|
-
for (let i = 0; i < options.length; i++) {
|
|
1195
|
-
const marker = i === initialIndex ? ">" : " ";
|
|
1196
|
-
process.stdout.write(` ${marker} ${i + 1}) ${options[i]}
|
|
1197
|
-
`);
|
|
1198
|
-
}
|
|
1199
|
-
const answer = await this.prompt(
|
|
1200
|
-
` Choose [1-${options.length}] (default: ${options[initialIndex]}): `
|
|
1201
|
-
);
|
|
1202
|
-
const trimmed = answer.trim().toLowerCase();
|
|
1203
|
-
if (trimmed === "") return initialIndex;
|
|
1204
|
-
const num = parseInt(trimmed, 10);
|
|
1205
|
-
if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
|
|
1206
|
-
return initialIndex;
|
|
1207
|
-
}
|
|
1208
|
-
spinner(_message) {
|
|
1209
|
-
return { stop() {
|
|
1210
|
-
}, update() {
|
|
1211
|
-
} };
|
|
1212
|
-
}
|
|
1213
|
-
};
|
|
1214
|
-
function getUserSettingsPath() {
|
|
1215
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
|
|
1216
|
-
return (0, import_node_path2.join)(home, ".robota", "settings.json");
|
|
1217
|
-
}
|
|
1218
1371
|
async function ensureConfig(cwd) {
|
|
1219
1372
|
const userPath = getUserSettingsPath();
|
|
1220
|
-
const projectPath = (0,
|
|
1221
|
-
const localPath = (0,
|
|
1222
|
-
if ((0,
|
|
1373
|
+
const projectPath = (0, import_node_path3.join)(cwd, ".robota", "settings.json");
|
|
1374
|
+
const localPath = (0, import_node_path3.join)(cwd, ".robota", "settings.local.json");
|
|
1375
|
+
if ((0, import_node_fs3.existsSync)(userPath) || (0, import_node_fs3.existsSync)(projectPath) || (0, import_node_fs3.existsSync)(localPath)) {
|
|
1223
1376
|
return;
|
|
1224
1377
|
}
|
|
1225
1378
|
process.stdout.write("\n");
|
|
@@ -1263,8 +1416,8 @@ async function ensureConfig(cwd) {
|
|
|
1263
1416
|
process.stderr.write("\n No API key provided. Exiting.\n");
|
|
1264
1417
|
process.exit(1);
|
|
1265
1418
|
}
|
|
1266
|
-
const settingsDir = (0,
|
|
1267
|
-
(0,
|
|
1419
|
+
const settingsDir = (0, import_node_path3.dirname)(userPath);
|
|
1420
|
+
(0, import_node_fs3.mkdirSync)(settingsDir, { recursive: true });
|
|
1268
1421
|
const settings = {
|
|
1269
1422
|
provider: {
|
|
1270
1423
|
name: "anthropic",
|
|
@@ -1272,7 +1425,7 @@ async function ensureConfig(cwd) {
|
|
|
1272
1425
|
apiKey
|
|
1273
1426
|
}
|
|
1274
1427
|
};
|
|
1275
|
-
(0,
|
|
1428
|
+
(0, import_node_fs3.writeFileSync)(userPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
1276
1429
|
process.stdout.write(`
|
|
1277
1430
|
Config saved to ${userPath}
|
|
1278
1431
|
|
|
@@ -1280,8 +1433,7 @@ async function ensureConfig(cwd) {
|
|
|
1280
1433
|
}
|
|
1281
1434
|
function resetConfig() {
|
|
1282
1435
|
const userPath = getUserSettingsPath();
|
|
1283
|
-
if ((
|
|
1284
|
-
(0, import_node_fs2.unlinkSync)(userPath);
|
|
1436
|
+
if (deleteSettings(userPath)) {
|
|
1285
1437
|
process.stdout.write(`Deleted ${userPath}
|
|
1286
1438
|
`);
|
|
1287
1439
|
} else {
|