@robota-sdk/agent-cli 3.0.0-beta.16 → 3.0.0-beta.18
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/dist/node/bin.cjs +416 -300
- package/dist/node/bin.js +1 -1
- package/dist/node/{chunk-OZHWJAII.js → chunk-M723M4M4.js} +416 -300
- package/dist/node/index.cjs +416 -300
- package/dist/node/index.js +1 -1
- package/package.json +3 -3
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
// src/cli.ts
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import { join as join3, 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,14 +13,267 @@ 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
150
|
import { useState as useState5, useCallback as useCallback3, useRef as useRef3 } from "react";
|
|
23
|
-
import { Box as
|
|
24
|
-
|
|
25
|
-
|
|
151
|
+
import { Box as Box8, Text as Text10, 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
|
|
26
277
|
import { createSession, FileSessionLogger, projectPaths } from "@robota-sdk/agent-sdk";
|
|
27
278
|
import { getModelName } from "@robota-sdk/agent-core";
|
|
28
279
|
|
|
@@ -113,8 +364,8 @@ var BuiltinCommandSource = class {
|
|
|
113
364
|
};
|
|
114
365
|
|
|
115
366
|
// src/commands/skill-source.ts
|
|
116
|
-
import { readdirSync, readFileSync, existsSync } from "fs";
|
|
117
|
-
import { join } from "path";
|
|
367
|
+
import { readdirSync, readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
|
|
368
|
+
import { join as join2 } from "path";
|
|
118
369
|
import { homedir } from "os";
|
|
119
370
|
function parseFrontmatter(content) {
|
|
120
371
|
const lines = content.split("\n");
|
|
@@ -137,14 +388,14 @@ function parseFrontmatter(content) {
|
|
|
137
388
|
return name ? { name, description } : null;
|
|
138
389
|
}
|
|
139
390
|
function scanSkillsDir(skillsDir) {
|
|
140
|
-
if (!
|
|
391
|
+
if (!existsSync2(skillsDir)) return [];
|
|
141
392
|
const commands = [];
|
|
142
393
|
const entries = readdirSync(skillsDir, { withFileTypes: true });
|
|
143
394
|
for (const entry of entries) {
|
|
144
395
|
if (!entry.isDirectory()) continue;
|
|
145
|
-
const skillFile =
|
|
146
|
-
if (!
|
|
147
|
-
const content =
|
|
396
|
+
const skillFile = join2(skillsDir, entry.name, "SKILL.md");
|
|
397
|
+
if (!existsSync2(skillFile)) continue;
|
|
398
|
+
const content = readFileSync2(skillFile, "utf-8");
|
|
148
399
|
const frontmatter = parseFrontmatter(content);
|
|
149
400
|
commands.push({
|
|
150
401
|
name: frontmatter?.name ?? entry.name,
|
|
@@ -164,8 +415,8 @@ var SkillCommandSource = class {
|
|
|
164
415
|
}
|
|
165
416
|
getCommands() {
|
|
166
417
|
if (this.cachedCommands) return this.cachedCommands;
|
|
167
|
-
const projectSkills = scanSkillsDir(
|
|
168
|
-
const userSkills = scanSkillsDir(
|
|
418
|
+
const projectSkills = scanSkillsDir(join2(this.cwd, ".agents", "skills"));
|
|
419
|
+
const userSkills = scanSkillsDir(join2(homedir(), ".claude", "skills"));
|
|
169
420
|
const seen = new Set(projectSkills.map((cmd) => cmd.name));
|
|
170
421
|
const merged = [...projectSkills];
|
|
171
422
|
for (const cmd of userSkills) {
|
|
@@ -707,8 +958,66 @@ function PermissionPrompt({ request }) {
|
|
|
707
958
|
] });
|
|
708
959
|
}
|
|
709
960
|
|
|
710
|
-
// src/
|
|
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
|
+
|
|
988
|
+
// src/ui/StreamingIndicator.tsx
|
|
989
|
+
import { Box as Box7, Text as Text9 } from "ink";
|
|
711
990
|
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
991
|
+
function StreamingIndicator({ text, activeTools }) {
|
|
992
|
+
const hasTools = activeTools.length > 0;
|
|
993
|
+
const hasText = text.length > 0;
|
|
994
|
+
if (!hasTools && !hasText) {
|
|
995
|
+
return /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "Thinking..." });
|
|
996
|
+
}
|
|
997
|
+
return /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", children: [
|
|
998
|
+
hasTools && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, children: [
|
|
999
|
+
/* @__PURE__ */ jsx9(Text9, { color: "gray", bold: true, children: "Tools:" }),
|
|
1000
|
+
/* @__PURE__ */ jsx9(Text9, { children: " " }),
|
|
1001
|
+
activeTools.map((t, i) => /* @__PURE__ */ jsxs7(Text9, { color: t.isRunning ? "yellow" : "green", children: [
|
|
1002
|
+
" ",
|
|
1003
|
+
t.isRunning ? "\u27F3" : "\u2713",
|
|
1004
|
+
" ",
|
|
1005
|
+
t.toolName,
|
|
1006
|
+
"(",
|
|
1007
|
+
t.firstArg,
|
|
1008
|
+
")"
|
|
1009
|
+
] }, `${t.toolName}-${i}`))
|
|
1010
|
+
] }),
|
|
1011
|
+
hasText && /* @__PURE__ */ jsxs7(Box7, { flexDirection: "column", marginBottom: 1, children: [
|
|
1012
|
+
/* @__PURE__ */ jsx9(Text9, { color: "cyan", bold: true, children: "Robota:" }),
|
|
1013
|
+
/* @__PURE__ */ jsx9(Text9, { children: " " }),
|
|
1014
|
+
/* @__PURE__ */ jsx9(Box7, { marginLeft: 2, children: /* @__PURE__ */ jsx9(Text9, { wrap: "wrap", children: renderMarkdown(text) }) })
|
|
1015
|
+
] })
|
|
1016
|
+
] });
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
// src/ui/App.tsx
|
|
1020
|
+
import { jsx as jsx10, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
712
1021
|
var msgIdCounter = 0;
|
|
713
1022
|
function nextId() {
|
|
714
1023
|
msgIdCounter += 1;
|
|
@@ -732,6 +1041,7 @@ var NOOP_TERMINAL = {
|
|
|
732
1041
|
function useSession(props) {
|
|
733
1042
|
const [permissionRequest, setPermissionRequest] = useState5(null);
|
|
734
1043
|
const [streamingText, setStreamingText] = useState5("");
|
|
1044
|
+
const [activeTools, setActiveTools] = useState5([]);
|
|
735
1045
|
const permissionQueueRef = useRef3([]);
|
|
736
1046
|
const processingRef = useRef3(false);
|
|
737
1047
|
const processNextPermission = useCallback3(() => {
|
|
@@ -765,6 +1075,25 @@ function useSession(props) {
|
|
|
765
1075
|
const onTextDelta = (delta) => {
|
|
766
1076
|
setStreamingText((prev) => prev + delta);
|
|
767
1077
|
};
|
|
1078
|
+
const TOOL_ARG_DISPLAY_MAX = 80;
|
|
1079
|
+
const TOOL_ARG_TRUNCATE_AT = 77;
|
|
1080
|
+
const onToolExecution = (event) => {
|
|
1081
|
+
if (event.type === "start") {
|
|
1082
|
+
let firstArg = "";
|
|
1083
|
+
if (event.toolArgs) {
|
|
1084
|
+
const firstVal = Object.values(event.toolArgs)[0];
|
|
1085
|
+
const raw = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal ?? "");
|
|
1086
|
+
firstArg = raw.length > TOOL_ARG_DISPLAY_MAX ? raw.slice(0, TOOL_ARG_TRUNCATE_AT) + "..." : raw;
|
|
1087
|
+
}
|
|
1088
|
+
setActiveTools((prev) => [...prev, { toolName: event.toolName, firstArg, isRunning: true }]);
|
|
1089
|
+
} else {
|
|
1090
|
+
setActiveTools(
|
|
1091
|
+
(prev) => prev.map(
|
|
1092
|
+
(t) => t.toolName === event.toolName && t.isRunning ? { ...t, isRunning: false } : t
|
|
1093
|
+
)
|
|
1094
|
+
);
|
|
1095
|
+
}
|
|
1096
|
+
};
|
|
768
1097
|
const paths = projectPaths(props.cwd ?? process.cwd());
|
|
769
1098
|
sessionRef.current = createSession({
|
|
770
1099
|
config: props.config,
|
|
@@ -776,11 +1105,15 @@ function useSession(props) {
|
|
|
776
1105
|
permissionMode: props.permissionMode,
|
|
777
1106
|
maxTurns: props.maxTurns,
|
|
778
1107
|
permissionHandler,
|
|
779
|
-
onTextDelta
|
|
1108
|
+
onTextDelta,
|
|
1109
|
+
onToolExecution
|
|
780
1110
|
});
|
|
781
1111
|
}
|
|
782
|
-
const clearStreamingText = useCallback3(() =>
|
|
783
|
-
|
|
1112
|
+
const clearStreamingText = useCallback3(() => {
|
|
1113
|
+
setStreamingText("");
|
|
1114
|
+
setActiveTools([]);
|
|
1115
|
+
}, []);
|
|
1116
|
+
return { session: sessionRef.current, permissionRequest, streamingText, clearStreamingText, activeTools };
|
|
784
1117
|
}
|
|
785
1118
|
function useMessages() {
|
|
786
1119
|
const [messages, setMessages] = useState5([]);
|
|
@@ -789,139 +1122,32 @@ function useMessages() {
|
|
|
789
1122
|
}, []);
|
|
790
1123
|
return { messages, setMessages, addMessage };
|
|
791
1124
|
}
|
|
792
|
-
var
|
|
793
|
-
"Available commands:",
|
|
794
|
-
" /help \u2014 Show this help",
|
|
795
|
-
" /clear \u2014 Clear conversation",
|
|
796
|
-
" /compact [instr] \u2014 Compact context (optional focus instructions)",
|
|
797
|
-
" /mode [m] \u2014 Show/change permission mode",
|
|
798
|
-
" /cost \u2014 Show session info",
|
|
799
|
-
" /reset \u2014 Delete settings and exit",
|
|
800
|
-
" /exit \u2014 Exit CLI"
|
|
801
|
-
].join("\n");
|
|
802
|
-
function handleModeCommand(arg, session, addMessage) {
|
|
803
|
-
const validModes = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
804
|
-
if (!arg) {
|
|
805
|
-
addMessage({ role: "system", content: `Current mode: ${session.getPermissionMode()}` });
|
|
806
|
-
} else if (validModes.includes(arg)) {
|
|
807
|
-
session.setPermissionMode(arg);
|
|
808
|
-
addMessage({ role: "system", content: `Permission mode set to: ${arg}` });
|
|
809
|
-
} else {
|
|
810
|
-
addMessage({ role: "system", content: `Invalid mode. Valid: ${validModes.join(" | ")}` });
|
|
811
|
-
}
|
|
812
|
-
return true;
|
|
813
|
-
}
|
|
814
|
-
async function executeSlashCommand(cmd, parts, session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
|
|
815
|
-
switch (cmd) {
|
|
816
|
-
case "help":
|
|
817
|
-
addMessage({ role: "system", content: HELP_TEXT });
|
|
818
|
-
return true;
|
|
819
|
-
case "clear":
|
|
820
|
-
setMessages([]);
|
|
821
|
-
session.clearHistory();
|
|
822
|
-
addMessage({ role: "system", content: "Conversation cleared." });
|
|
823
|
-
return true;
|
|
824
|
-
case "compact": {
|
|
825
|
-
const instructions = parts.slice(1).join(" ").trim() || void 0;
|
|
826
|
-
const before = session.getContextState().usedPercentage;
|
|
827
|
-
addMessage({ role: "system", content: "Compacting context..." });
|
|
828
|
-
await session.compact(instructions);
|
|
829
|
-
const after = session.getContextState().usedPercentage;
|
|
830
|
-
addMessage({
|
|
831
|
-
role: "system",
|
|
832
|
-
content: `Context compacted: ${Math.round(before)}% -> ${Math.round(after)}%`
|
|
833
|
-
});
|
|
834
|
-
return true;
|
|
835
|
-
}
|
|
836
|
-
case "mode":
|
|
837
|
-
return handleModeCommand(parts[1], session, addMessage);
|
|
838
|
-
case "model": {
|
|
839
|
-
const modelId = parts[1];
|
|
840
|
-
if (!modelId) {
|
|
841
|
-
addMessage({ role: "system", content: "Select a model from the /model submenu." });
|
|
842
|
-
return true;
|
|
843
|
-
}
|
|
844
|
-
pendingModelChangeRef.current = modelId;
|
|
845
|
-
setPendingModelId(modelId);
|
|
846
|
-
return true;
|
|
847
|
-
}
|
|
848
|
-
case "cost":
|
|
849
|
-
addMessage({
|
|
850
|
-
role: "system",
|
|
851
|
-
content: `Session: ${session.getSessionId()}
|
|
852
|
-
Messages: ${session.getMessageCount()}`
|
|
853
|
-
});
|
|
854
|
-
return true;
|
|
855
|
-
case "permissions": {
|
|
856
|
-
const mode = session.getPermissionMode();
|
|
857
|
-
const sessionAllowed = session.getSessionAllowedTools();
|
|
858
|
-
const lines = [`Permission mode: ${mode}`];
|
|
859
|
-
if (sessionAllowed.length > 0) {
|
|
860
|
-
lines.push(`Session-approved tools: ${sessionAllowed.join(", ")}`);
|
|
861
|
-
} else {
|
|
862
|
-
lines.push("No session-approved tools.");
|
|
863
|
-
}
|
|
864
|
-
addMessage({ role: "system", content: lines.join("\n") });
|
|
865
|
-
return true;
|
|
866
|
-
}
|
|
867
|
-
case "context": {
|
|
868
|
-
const ctx = session.getContextState();
|
|
869
|
-
addMessage({
|
|
870
|
-
role: "system",
|
|
871
|
-
content: `Context: ${ctx.usedTokens.toLocaleString()} / ${ctx.maxTokens.toLocaleString()} tokens (${Math.round(ctx.usedPercentage)}%)`
|
|
872
|
-
});
|
|
873
|
-
return true;
|
|
874
|
-
}
|
|
875
|
-
case "reset": {
|
|
876
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
|
|
877
|
-
const settingsPath = `${home}/.robota/settings.json`;
|
|
878
|
-
if (existsSync2(settingsPath)) {
|
|
879
|
-
unlinkSync(settingsPath);
|
|
880
|
-
addMessage({ role: "system", content: `Deleted ${settingsPath}. Exiting...` });
|
|
881
|
-
} else {
|
|
882
|
-
addMessage({ role: "system", content: "No user settings found." });
|
|
883
|
-
}
|
|
884
|
-
setTimeout(() => exit(), 500);
|
|
885
|
-
return true;
|
|
886
|
-
}
|
|
887
|
-
case "exit":
|
|
888
|
-
exit();
|
|
889
|
-
return true;
|
|
890
|
-
default: {
|
|
891
|
-
const skillCmd = registry.getCommands().find((c) => c.name === cmd && c.source === "skill");
|
|
892
|
-
if (skillCmd) {
|
|
893
|
-
addMessage({ role: "system", content: `Invoking skill: ${cmd}` });
|
|
894
|
-
return false;
|
|
895
|
-
}
|
|
896
|
-
addMessage({ role: "system", content: `Unknown command "/${cmd}". Type /help for help.` });
|
|
897
|
-
return true;
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
}
|
|
1125
|
+
var EXIT_DELAY_MS = 500;
|
|
901
1126
|
function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
|
|
902
1127
|
return useCallback3(
|
|
903
1128
|
async (input) => {
|
|
904
1129
|
const parts = input.slice(1).split(/\s+/);
|
|
905
1130
|
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
906
|
-
|
|
1131
|
+
const args = parts.slice(1).join(" ");
|
|
1132
|
+
const clearMessages = () => setMessages([]);
|
|
1133
|
+
const result = await executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry);
|
|
1134
|
+
if (result.pendingModelId) {
|
|
1135
|
+
pendingModelChangeRef.current = result.pendingModelId;
|
|
1136
|
+
setPendingModelId(result.pendingModelId);
|
|
1137
|
+
}
|
|
1138
|
+
if (result.exitRequested) {
|
|
1139
|
+
setTimeout(() => exit(), EXIT_DELAY_MS);
|
|
1140
|
+
}
|
|
1141
|
+
return result.handled;
|
|
907
1142
|
},
|
|
908
1143
|
[session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId]
|
|
909
1144
|
);
|
|
910
1145
|
}
|
|
911
|
-
function
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
/* @__PURE__ */ jsxs7(Text9, { color: "cyan", bold: true, children: [
|
|
915
|
-
"Robota:",
|
|
916
|
-
" "
|
|
917
|
-
] }),
|
|
918
|
-
/* @__PURE__ */ jsx9(Text9, { children: " " }),
|
|
919
|
-
/* @__PURE__ */ jsx9(Box7, { marginLeft: 2, children: /* @__PURE__ */ jsx9(Text9, { wrap: "wrap", children: renderMarkdown(text) }) })
|
|
920
|
-
] });
|
|
921
|
-
}
|
|
922
|
-
return /* @__PURE__ */ jsx9(Text9, { color: "yellow", children: "Thinking..." });
|
|
1146
|
+
function syncContextState(session, setter) {
|
|
1147
|
+
const ctx = session.getContextState();
|
|
1148
|
+
setter({ percentage: ctx.usedPercentage, usedTokens: ctx.usedTokens, maxTokens: ctx.maxTokens });
|
|
923
1149
|
}
|
|
924
|
-
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking,
|
|
1150
|
+
async function runSessionPrompt(prompt, session, addMessage, clearStreamingText, setIsThinking, setContextState) {
|
|
925
1151
|
setIsThinking(true);
|
|
926
1152
|
clearStreamingText();
|
|
927
1153
|
const historyBefore = session.getHistory().length;
|
|
@@ -929,29 +1155,15 @@ async function runSessionPrompt(prompt, session, addMessage, clearStreamingText,
|
|
|
929
1155
|
const response = await session.run(prompt);
|
|
930
1156
|
clearStreamingText();
|
|
931
1157
|
const history = session.getHistory();
|
|
932
|
-
const toolLines =
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
for (const tc of msg.toolCalls) {
|
|
937
|
-
let value = "";
|
|
938
|
-
try {
|
|
939
|
-
const parsed = JSON.parse(tc.function.arguments);
|
|
940
|
-
const firstVal = Object.values(parsed)[0];
|
|
941
|
-
value = typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
|
|
942
|
-
} catch {
|
|
943
|
-
value = tc.function.arguments;
|
|
944
|
-
}
|
|
945
|
-
const truncated = value.length > 80 ? value.slice(0, 77) + "..." : value;
|
|
946
|
-
toolLines.push(`${tc.function.name}(${truncated})`);
|
|
947
|
-
}
|
|
948
|
-
}
|
|
949
|
-
}
|
|
1158
|
+
const toolLines = extractToolCalls(
|
|
1159
|
+
history,
|
|
1160
|
+
historyBefore
|
|
1161
|
+
);
|
|
950
1162
|
if (toolLines.length > 0) {
|
|
951
1163
|
addMessage({ role: "tool", content: toolLines.join("\n"), toolName: `${toolLines.length} tools` });
|
|
952
1164
|
}
|
|
953
1165
|
addMessage({ role: "assistant", content: response || "(empty response)" });
|
|
954
|
-
|
|
1166
|
+
syncContextState(session, setContextState);
|
|
955
1167
|
} catch (err) {
|
|
956
1168
|
clearStreamingText();
|
|
957
1169
|
if (err instanceof DOMException && err.name === "AbortError") {
|
|
@@ -980,13 +1192,13 @@ Execute the "${cmd}" skill: ${userInstruction}`;
|
|
|
980
1192
|
}
|
|
981
1193
|
return `Use the "${cmd}" skill: ${userInstruction}`;
|
|
982
1194
|
}
|
|
983
|
-
function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking,
|
|
1195
|
+
function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamingText, setIsThinking, setContextState, registry) {
|
|
984
1196
|
return useCallback3(
|
|
985
1197
|
async (input) => {
|
|
986
1198
|
if (input.startsWith("/")) {
|
|
987
1199
|
const handled = await handleSlashCommand(input);
|
|
988
1200
|
if (handled) {
|
|
989
|
-
|
|
1201
|
+
syncContextState(session, setContextState);
|
|
990
1202
|
return;
|
|
991
1203
|
}
|
|
992
1204
|
const prompt = buildSkillPrompt(input, registry);
|
|
@@ -997,7 +1209,7 @@ function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamin
|
|
|
997
1209
|
addMessage,
|
|
998
1210
|
clearStreamingText,
|
|
999
1211
|
setIsThinking,
|
|
1000
|
-
|
|
1212
|
+
setContextState
|
|
1001
1213
|
);
|
|
1002
1214
|
}
|
|
1003
1215
|
addMessage({ role: "user", content: input });
|
|
@@ -1007,7 +1219,7 @@ function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamin
|
|
|
1007
1219
|
addMessage,
|
|
1008
1220
|
clearStreamingText,
|
|
1009
1221
|
setIsThinking,
|
|
1010
|
-
|
|
1222
|
+
setContextState
|
|
1011
1223
|
);
|
|
1012
1224
|
},
|
|
1013
1225
|
[
|
|
@@ -1016,7 +1228,7 @@ function useSubmitHandler(session, addMessage, handleSlashCommand, clearStreamin
|
|
|
1016
1228
|
handleSlashCommand,
|
|
1017
1229
|
clearStreamingText,
|
|
1018
1230
|
setIsThinking,
|
|
1019
|
-
|
|
1231
|
+
setContextState,
|
|
1020
1232
|
registry
|
|
1021
1233
|
]
|
|
1022
1234
|
);
|
|
@@ -1033,10 +1245,10 @@ function useCommandRegistry(cwd) {
|
|
|
1033
1245
|
}
|
|
1034
1246
|
function App(props) {
|
|
1035
1247
|
const { exit } = useApp();
|
|
1036
|
-
const { session, permissionRequest, streamingText, clearStreamingText } = useSession(props);
|
|
1248
|
+
const { session, permissionRequest, streamingText, clearStreamingText, activeTools } = useSession(props);
|
|
1037
1249
|
const { messages, setMessages, addMessage } = useMessages();
|
|
1038
1250
|
const [isThinking, setIsThinking] = useState5(false);
|
|
1039
|
-
const [
|
|
1251
|
+
const [contextState, setContextState] = useState5({ percentage: 0, usedTokens: 0, maxTokens: 0 });
|
|
1040
1252
|
const registry = useCommandRegistry(props.cwd ?? process.cwd());
|
|
1041
1253
|
const pendingModelChangeRef = useRef3(null);
|
|
1042
1254
|
const [pendingModelId, setPendingModelId] = useState5(null);
|
|
@@ -1047,7 +1259,7 @@ function App(props) {
|
|
|
1047
1259
|
handleSlashCommand,
|
|
1048
1260
|
clearStreamingText,
|
|
1049
1261
|
setIsThinking,
|
|
1050
|
-
|
|
1262
|
+
setContextState,
|
|
1051
1263
|
registry
|
|
1052
1264
|
);
|
|
1053
1265
|
useInput5(
|
|
@@ -1057,26 +1269,26 @@ function App(props) {
|
|
|
1057
1269
|
},
|
|
1058
1270
|
{ isActive: !permissionRequest }
|
|
1059
1271
|
);
|
|
1060
|
-
return /* @__PURE__ */
|
|
1061
|
-
/* @__PURE__ */
|
|
1062
|
-
/* @__PURE__ */
|
|
1272
|
+
return /* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", children: [
|
|
1273
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, marginBottom: 1, children: [
|
|
1274
|
+
/* @__PURE__ */ jsx10(Text10, { color: "cyan", bold: true, children: `
|
|
1063
1275
|
____ ___ ____ ___ _____ _
|
|
1064
1276
|
| _ \\ / _ \\| __ ) / _ \\_ _|/ \\
|
|
1065
1277
|
| |_) | | | | _ \\| | | || | / _ \\
|
|
1066
1278
|
| _ <| |_| | |_) | |_| || |/ ___ \\
|
|
1067
1279
|
|_| \\_\\\\___/|____/ \\___/ |_/_/ \\_\\
|
|
1068
1280
|
` }),
|
|
1069
|
-
/* @__PURE__ */
|
|
1281
|
+
/* @__PURE__ */ jsxs8(Text10, { dimColor: true, children: [
|
|
1070
1282
|
" v",
|
|
1071
1283
|
props.version ?? "0.0.0"
|
|
1072
1284
|
] })
|
|
1073
1285
|
] }),
|
|
1074
|
-
/* @__PURE__ */
|
|
1075
|
-
/* @__PURE__ */
|
|
1076
|
-
isThinking && /* @__PURE__ */
|
|
1286
|
+
/* @__PURE__ */ jsxs8(Box8, { flexDirection: "column", paddingX: 1, flexGrow: 1, children: [
|
|
1287
|
+
/* @__PURE__ */ jsx10(MessageList, { messages }),
|
|
1288
|
+
isThinking && /* @__PURE__ */ jsx10(Box8, { flexDirection: "column", marginBottom: 1, children: /* @__PURE__ */ jsx10(StreamingIndicator, { text: streamingText, activeTools }) })
|
|
1077
1289
|
] }),
|
|
1078
|
-
permissionRequest && /* @__PURE__ */
|
|
1079
|
-
pendingModelId && /* @__PURE__ */
|
|
1290
|
+
permissionRequest && /* @__PURE__ */ jsx10(PermissionPrompt, { request: permissionRequest }),
|
|
1291
|
+
pendingModelId && /* @__PURE__ */ jsx10(
|
|
1080
1292
|
ConfirmPrompt,
|
|
1081
1293
|
{
|
|
1082
1294
|
message: `Change model to ${getModelName(pendingModelId)}? This will restart the session.`,
|
|
@@ -1085,17 +1297,8 @@ function App(props) {
|
|
|
1085
1297
|
pendingModelChangeRef.current = null;
|
|
1086
1298
|
if (index === 0) {
|
|
1087
1299
|
try {
|
|
1088
|
-
const
|
|
1089
|
-
|
|
1090
|
-
let settings = {};
|
|
1091
|
-
if (existsSync2(settingsPath)) {
|
|
1092
|
-
settings = JSON.parse(readFileSync2(settingsPath, "utf8"));
|
|
1093
|
-
}
|
|
1094
|
-
const provider = settings.provider ?? {};
|
|
1095
|
-
provider.model = pendingModelId;
|
|
1096
|
-
settings.provider = provider;
|
|
1097
|
-
mkdirSync(join2(home, ".robota"), { recursive: true });
|
|
1098
|
-
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
1300
|
+
const settingsPath = getUserSettingsPath();
|
|
1301
|
+
updateModelInSettings(settingsPath, pendingModelId);
|
|
1099
1302
|
addMessage({ role: "system", content: `Model changed to ${getModelName(pendingModelId)}. Restarting...` });
|
|
1100
1303
|
setTimeout(() => exit(), 500);
|
|
1101
1304
|
} catch (err) {
|
|
@@ -1107,7 +1310,7 @@ function App(props) {
|
|
|
1107
1310
|
}
|
|
1108
1311
|
}
|
|
1109
1312
|
),
|
|
1110
|
-
/* @__PURE__ */
|
|
1313
|
+
/* @__PURE__ */ jsx10(
|
|
1111
1314
|
StatusBar,
|
|
1112
1315
|
{
|
|
1113
1316
|
permissionMode: session.getPermissionMode(),
|
|
@@ -1115,12 +1318,12 @@ function App(props) {
|
|
|
1115
1318
|
sessionId: session.getSessionId(),
|
|
1116
1319
|
messageCount: messages.length,
|
|
1117
1320
|
isThinking,
|
|
1118
|
-
contextPercentage,
|
|
1119
|
-
contextUsedTokens:
|
|
1120
|
-
contextMaxTokens:
|
|
1321
|
+
contextPercentage: contextState.percentage,
|
|
1322
|
+
contextUsedTokens: contextState.usedTokens,
|
|
1323
|
+
contextMaxTokens: contextState.maxTokens
|
|
1121
1324
|
}
|
|
1122
1325
|
),
|
|
1123
|
-
/* @__PURE__ */
|
|
1326
|
+
/* @__PURE__ */ jsx10(
|
|
1124
1327
|
InputArea,
|
|
1125
1328
|
{
|
|
1126
1329
|
onSubmit: handleSubmit,
|
|
@@ -1128,12 +1331,12 @@ function App(props) {
|
|
|
1128
1331
|
registry
|
|
1129
1332
|
}
|
|
1130
1333
|
),
|
|
1131
|
-
/* @__PURE__ */
|
|
1334
|
+
/* @__PURE__ */ jsx10(Text10, { children: " " })
|
|
1132
1335
|
] });
|
|
1133
1336
|
}
|
|
1134
1337
|
|
|
1135
1338
|
// src/ui/render.tsx
|
|
1136
|
-
import { jsx as
|
|
1339
|
+
import { jsx as jsx11 } from "react/jsx-runtime";
|
|
1137
1340
|
function renderApp(options) {
|
|
1138
1341
|
process.on("unhandledRejection", (reason) => {
|
|
1139
1342
|
process.stderr.write(`
|
|
@@ -1144,7 +1347,7 @@ function renderApp(options) {
|
|
|
1144
1347
|
`);
|
|
1145
1348
|
}
|
|
1146
1349
|
});
|
|
1147
|
-
const instance = render(/* @__PURE__ */
|
|
1350
|
+
const instance = render(/* @__PURE__ */ jsx11(App, { ...options }), {
|
|
1148
1351
|
exitOnCtrlC: true
|
|
1149
1352
|
});
|
|
1150
1353
|
instance.waitUntilExit().catch((err) => {
|
|
@@ -1157,11 +1360,22 @@ function renderApp(options) {
|
|
|
1157
1360
|
}
|
|
1158
1361
|
|
|
1159
1362
|
// src/cli.ts
|
|
1160
|
-
|
|
1363
|
+
function hasValidSettingsFile(filePath) {
|
|
1364
|
+
if (!existsSync3(filePath)) return false;
|
|
1365
|
+
try {
|
|
1366
|
+
const raw = readFileSync3(filePath, "utf8").trim();
|
|
1367
|
+
if (raw.length === 0) return false;
|
|
1368
|
+
const parsed = JSON.parse(raw);
|
|
1369
|
+
const provider = parsed.provider;
|
|
1370
|
+
return !!provider?.apiKey;
|
|
1371
|
+
} catch {
|
|
1372
|
+
return false;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1161
1375
|
function readVersion() {
|
|
1162
1376
|
try {
|
|
1163
1377
|
const thisFile = fileURLToPath(import.meta.url);
|
|
1164
|
-
const dir =
|
|
1378
|
+
const dir = dirname2(thisFile);
|
|
1165
1379
|
const candidates = [join3(dir, "..", "..", "package.json"), join3(dir, "..", "package.json")];
|
|
1166
1380
|
for (const pkgPath of candidates) {
|
|
1167
1381
|
try {
|
|
@@ -1178,108 +1392,11 @@ function readVersion() {
|
|
|
1178
1392
|
return "0.0.0";
|
|
1179
1393
|
}
|
|
1180
1394
|
}
|
|
1181
|
-
function parsePermissionMode(raw) {
|
|
1182
|
-
if (raw === void 0) return void 0;
|
|
1183
|
-
if (!VALID_MODES.includes(raw)) {
|
|
1184
|
-
process.stderr.write(`Invalid --permission-mode "${raw}". Valid: ${VALID_MODES.join(" | ")}
|
|
1185
|
-
`);
|
|
1186
|
-
process.exit(1);
|
|
1187
|
-
}
|
|
1188
|
-
return raw;
|
|
1189
|
-
}
|
|
1190
|
-
function parseMaxTurns(raw) {
|
|
1191
|
-
if (raw === void 0) return void 0;
|
|
1192
|
-
const n = parseInt(raw, 10);
|
|
1193
|
-
if (isNaN(n) || n <= 0) {
|
|
1194
|
-
process.stderr.write(`Invalid --max-turns "${raw}". Must be a positive integer.
|
|
1195
|
-
`);
|
|
1196
|
-
process.exit(1);
|
|
1197
|
-
}
|
|
1198
|
-
return n;
|
|
1199
|
-
}
|
|
1200
|
-
function parseCliArgs() {
|
|
1201
|
-
const { values, positionals } = parseArgs({
|
|
1202
|
-
allowPositionals: true,
|
|
1203
|
-
options: {
|
|
1204
|
-
p: { type: "boolean", short: "p", default: false },
|
|
1205
|
-
c: { type: "boolean", short: "c", default: false },
|
|
1206
|
-
r: { type: "string", short: "r" },
|
|
1207
|
-
model: { type: "string" },
|
|
1208
|
-
"permission-mode": { type: "string" },
|
|
1209
|
-
"max-turns": { type: "string" },
|
|
1210
|
-
version: { type: "boolean", default: false },
|
|
1211
|
-
reset: { type: "boolean", default: false }
|
|
1212
|
-
}
|
|
1213
|
-
});
|
|
1214
|
-
return {
|
|
1215
|
-
positional: positionals,
|
|
1216
|
-
printMode: values["p"] ?? false,
|
|
1217
|
-
continueMode: values["c"] ?? false,
|
|
1218
|
-
resumeId: values["r"],
|
|
1219
|
-
model: values["model"],
|
|
1220
|
-
permissionMode: parsePermissionMode(values["permission-mode"]),
|
|
1221
|
-
maxTurns: parseMaxTurns(values["max-turns"]),
|
|
1222
|
-
version: values["version"] ?? false,
|
|
1223
|
-
reset: values["reset"] ?? false
|
|
1224
|
-
};
|
|
1225
|
-
}
|
|
1226
|
-
var PrintTerminal = class {
|
|
1227
|
-
write(text) {
|
|
1228
|
-
process.stdout.write(text);
|
|
1229
|
-
}
|
|
1230
|
-
writeLine(text) {
|
|
1231
|
-
process.stdout.write(text + "\n");
|
|
1232
|
-
}
|
|
1233
|
-
writeMarkdown(md) {
|
|
1234
|
-
process.stdout.write(md);
|
|
1235
|
-
}
|
|
1236
|
-
writeError(text) {
|
|
1237
|
-
process.stderr.write(text + "\n");
|
|
1238
|
-
}
|
|
1239
|
-
prompt(question) {
|
|
1240
|
-
return new Promise((resolve) => {
|
|
1241
|
-
const rl = readline.createInterface({
|
|
1242
|
-
input: process.stdin,
|
|
1243
|
-
output: process.stdout,
|
|
1244
|
-
terminal: false,
|
|
1245
|
-
historySize: 0
|
|
1246
|
-
});
|
|
1247
|
-
rl.question(question, (answer) => {
|
|
1248
|
-
rl.close();
|
|
1249
|
-
resolve(answer);
|
|
1250
|
-
});
|
|
1251
|
-
});
|
|
1252
|
-
}
|
|
1253
|
-
async select(options, initialIndex = 0) {
|
|
1254
|
-
for (let i = 0; i < options.length; i++) {
|
|
1255
|
-
const marker = i === initialIndex ? ">" : " ";
|
|
1256
|
-
process.stdout.write(` ${marker} ${i + 1}) ${options[i]}
|
|
1257
|
-
`);
|
|
1258
|
-
}
|
|
1259
|
-
const answer = await this.prompt(
|
|
1260
|
-
` Choose [1-${options.length}] (default: ${options[initialIndex]}): `
|
|
1261
|
-
);
|
|
1262
|
-
const trimmed = answer.trim().toLowerCase();
|
|
1263
|
-
if (trimmed === "") return initialIndex;
|
|
1264
|
-
const num = parseInt(trimmed, 10);
|
|
1265
|
-
if (!isNaN(num) && num >= 1 && num <= options.length) return num - 1;
|
|
1266
|
-
return initialIndex;
|
|
1267
|
-
}
|
|
1268
|
-
spinner(_message) {
|
|
1269
|
-
return { stop() {
|
|
1270
|
-
}, update() {
|
|
1271
|
-
} };
|
|
1272
|
-
}
|
|
1273
|
-
};
|
|
1274
|
-
function getUserSettingsPath() {
|
|
1275
|
-
const home = process.env.HOME ?? process.env.USERPROFILE ?? "/";
|
|
1276
|
-
return join3(home, ".robota", "settings.json");
|
|
1277
|
-
}
|
|
1278
1395
|
async function ensureConfig(cwd) {
|
|
1279
1396
|
const userPath = getUserSettingsPath();
|
|
1280
1397
|
const projectPath = join3(cwd, ".robota", "settings.json");
|
|
1281
1398
|
const localPath = join3(cwd, ".robota", "settings.local.json");
|
|
1282
|
-
if (
|
|
1399
|
+
if (hasValidSettingsFile(userPath) || hasValidSettingsFile(projectPath) || hasValidSettingsFile(localPath)) {
|
|
1283
1400
|
return;
|
|
1284
1401
|
}
|
|
1285
1402
|
process.stdout.write("\n");
|
|
@@ -1323,7 +1440,7 @@ async function ensureConfig(cwd) {
|
|
|
1323
1440
|
process.stderr.write("\n No API key provided. Exiting.\n");
|
|
1324
1441
|
process.exit(1);
|
|
1325
1442
|
}
|
|
1326
|
-
const settingsDir =
|
|
1443
|
+
const settingsDir = dirname2(userPath);
|
|
1327
1444
|
mkdirSync2(settingsDir, { recursive: true });
|
|
1328
1445
|
const settings = {
|
|
1329
1446
|
provider: {
|
|
@@ -1340,8 +1457,7 @@ async function ensureConfig(cwd) {
|
|
|
1340
1457
|
}
|
|
1341
1458
|
function resetConfig() {
|
|
1342
1459
|
const userPath = getUserSettingsPath();
|
|
1343
|
-
if (
|
|
1344
|
-
unlinkSync2(userPath);
|
|
1460
|
+
if (deleteSettings(userPath)) {
|
|
1345
1461
|
process.stdout.write(`Deleted ${userPath}
|
|
1346
1462
|
`);
|
|
1347
1463
|
} else {
|