@robota-sdk/agent-cli 3.0.0-beta.16 → 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/dist/node/bin.cjs +310 -252
- package/dist/node/bin.js +1 -1
- package/dist/node/{chunk-OZHWJAII.js → chunk-4WB3L3RU.js} +313 -255
- package/dist/node/index.cjs +310 -252
- 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
151
|
import { Box as Box7, Text as Text9, useApp, useInput as useInput5 } from "ink";
|
|
24
|
-
|
|
25
|
-
|
|
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,6 +958,33 @@ function PermissionPrompt({ request }) {
|
|
|
707
958
|
] });
|
|
708
959
|
}
|
|
709
960
|
|
|
961
|
+
// src/utils/tool-call-extractor.ts
|
|
962
|
+
var TOOL_ARG_MAX_LENGTH = 80;
|
|
963
|
+
var TOOL_ARG_TRUNCATE_LENGTH = 77;
|
|
964
|
+
function extractToolCalls(history, startIndex) {
|
|
965
|
+
const lines = [];
|
|
966
|
+
for (let i = startIndex; i < history.length; i++) {
|
|
967
|
+
const msg = history[i];
|
|
968
|
+
if (msg.role === "assistant" && msg.toolCalls) {
|
|
969
|
+
for (const tc of msg.toolCalls) {
|
|
970
|
+
const value = parseFirstArgValue(tc.function.arguments);
|
|
971
|
+
const truncated = value.length > TOOL_ARG_MAX_LENGTH ? value.slice(0, TOOL_ARG_TRUNCATE_LENGTH) + "..." : value;
|
|
972
|
+
lines.push(`${tc.function.name}(${truncated})`);
|
|
973
|
+
}
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
return lines;
|
|
977
|
+
}
|
|
978
|
+
function parseFirstArgValue(argsJson) {
|
|
979
|
+
try {
|
|
980
|
+
const parsed = JSON.parse(argsJson);
|
|
981
|
+
const firstVal = Object.values(parsed)[0];
|
|
982
|
+
return typeof firstVal === "string" ? firstVal : JSON.stringify(firstVal);
|
|
983
|
+
} catch {
|
|
984
|
+
return argsJson;
|
|
985
|
+
}
|
|
986
|
+
}
|
|
987
|
+
|
|
710
988
|
// src/ui/App.tsx
|
|
711
989
|
import { jsx as jsx9, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
712
990
|
var msgIdCounter = 0;
|
|
@@ -789,121 +1067,23 @@ function useMessages() {
|
|
|
789
1067
|
}, []);
|
|
790
1068
|
return { messages, setMessages, addMessage };
|
|
791
1069
|
}
|
|
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
|
-
}
|
|
1070
|
+
var EXIT_DELAY_MS = 500;
|
|
901
1071
|
function useSlashCommands(session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId) {
|
|
902
1072
|
return useCallback3(
|
|
903
1073
|
async (input) => {
|
|
904
1074
|
const parts = input.slice(1).split(/\s+/);
|
|
905
1075
|
const cmd = parts[0]?.toLowerCase() ?? "";
|
|
906
|
-
|
|
1076
|
+
const args = parts.slice(1).join(" ");
|
|
1077
|
+
const clearMessages = () => setMessages([]);
|
|
1078
|
+
const result = await executeSlashCommand(cmd, args, session, addMessage, clearMessages, registry);
|
|
1079
|
+
if (result.pendingModelId) {
|
|
1080
|
+
pendingModelChangeRef.current = result.pendingModelId;
|
|
1081
|
+
setPendingModelId(result.pendingModelId);
|
|
1082
|
+
}
|
|
1083
|
+
if (result.exitRequested) {
|
|
1084
|
+
setTimeout(() => exit(), EXIT_DELAY_MS);
|
|
1085
|
+
}
|
|
1086
|
+
return result.handled;
|
|
907
1087
|
},
|
|
908
1088
|
[session, addMessage, setMessages, exit, registry, pendingModelChangeRef, setPendingModelId]
|
|
909
1089
|
);
|
|
@@ -929,24 +1109,10 @@ async function runSessionPrompt(prompt, session, addMessage, clearStreamingText,
|
|
|
929
1109
|
const response = await session.run(prompt);
|
|
930
1110
|
clearStreamingText();
|
|
931
1111
|
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
|
-
}
|
|
1112
|
+
const toolLines = extractToolCalls(
|
|
1113
|
+
history,
|
|
1114
|
+
historyBefore
|
|
1115
|
+
);
|
|
950
1116
|
if (toolLines.length > 0) {
|
|
951
1117
|
addMessage({ role: "tool", content: toolLines.join("\n"), toolName: `${toolLines.length} tools` });
|
|
952
1118
|
}
|
|
@@ -1085,17 +1251,8 @@ function App(props) {
|
|
|
1085
1251
|
pendingModelChangeRef.current = null;
|
|
1086
1252
|
if (index === 0) {
|
|
1087
1253
|
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");
|
|
1254
|
+
const settingsPath = getUserSettingsPath();
|
|
1255
|
+
updateModelInSettings(settingsPath, pendingModelId);
|
|
1099
1256
|
addMessage({ role: "system", content: `Model changed to ${getModelName(pendingModelId)}. Restarting...` });
|
|
1100
1257
|
setTimeout(() => exit(), 500);
|
|
1101
1258
|
} catch (err) {
|
|
@@ -1157,11 +1314,10 @@ function renderApp(options) {
|
|
|
1157
1314
|
}
|
|
1158
1315
|
|
|
1159
1316
|
// src/cli.ts
|
|
1160
|
-
var VALID_MODES = ["plan", "default", "acceptEdits", "bypassPermissions"];
|
|
1161
1317
|
function readVersion() {
|
|
1162
1318
|
try {
|
|
1163
1319
|
const thisFile = fileURLToPath(import.meta.url);
|
|
1164
|
-
const dir =
|
|
1320
|
+
const dir = dirname2(thisFile);
|
|
1165
1321
|
const candidates = [join3(dir, "..", "..", "package.json"), join3(dir, "..", "package.json")];
|
|
1166
1322
|
for (const pkgPath of candidates) {
|
|
1167
1323
|
try {
|
|
@@ -1178,103 +1334,6 @@ function readVersion() {
|
|
|
1178
1334
|
return "0.0.0";
|
|
1179
1335
|
}
|
|
1180
1336
|
}
|
|
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
1337
|
async function ensureConfig(cwd) {
|
|
1279
1338
|
const userPath = getUserSettingsPath();
|
|
1280
1339
|
const projectPath = join3(cwd, ".robota", "settings.json");
|
|
@@ -1323,7 +1382,7 @@ async function ensureConfig(cwd) {
|
|
|
1323
1382
|
process.stderr.write("\n No API key provided. Exiting.\n");
|
|
1324
1383
|
process.exit(1);
|
|
1325
1384
|
}
|
|
1326
|
-
const settingsDir =
|
|
1385
|
+
const settingsDir = dirname2(userPath);
|
|
1327
1386
|
mkdirSync2(settingsDir, { recursive: true });
|
|
1328
1387
|
const settings = {
|
|
1329
1388
|
provider: {
|
|
@@ -1340,8 +1399,7 @@ async function ensureConfig(cwd) {
|
|
|
1340
1399
|
}
|
|
1341
1400
|
function resetConfig() {
|
|
1342
1401
|
const userPath = getUserSettingsPath();
|
|
1343
|
-
if (
|
|
1344
|
-
unlinkSync2(userPath);
|
|
1402
|
+
if (deleteSettings(userPath)) {
|
|
1345
1403
|
process.stdout.write(`Deleted ${userPath}
|
|
1346
1404
|
`);
|
|
1347
1405
|
} else {
|