@robinpath/cli 1.73.0 → 1.74.0
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/cli.mjs +344 -301
- package/package.json +1 -1
package/dist/cli.mjs
CHANGED
|
@@ -18598,7 +18598,7 @@ function getNativeModules() {
|
|
|
18598
18598
|
import { join as join3, basename as basename2 } from "node:path";
|
|
18599
18599
|
import { homedir as homedir2, platform as platform2 } from "node:os";
|
|
18600
18600
|
import { existsSync as existsSync2 } from "node:fs";
|
|
18601
|
-
var CLI_VERSION = true ? "1.
|
|
18601
|
+
var CLI_VERSION = true ? "1.74.0" : "1.74.0";
|
|
18602
18602
|
var FLAG_QUIET = false;
|
|
18603
18603
|
var FLAG_VERBOSE = false;
|
|
18604
18604
|
var FLAG_AUTO_ACCEPT = false;
|
|
@@ -24132,70 +24132,31 @@ ${resultSummary}`
|
|
|
24132
24132
|
}
|
|
24133
24133
|
|
|
24134
24134
|
// src/ink-repl.tsx
|
|
24135
|
-
import { render } from "ink";
|
|
24136
|
-
|
|
24137
|
-
// src/ui/App.tsx
|
|
24138
|
-
import { useState as useState2, useCallback } from "react";
|
|
24139
|
-
import { Box as Box6, Static } from "ink";
|
|
24140
|
-
|
|
24141
|
-
// src/ui/Banner.tsx
|
|
24142
|
-
import { Box, Text } from "ink";
|
|
24143
|
-
import { jsx, jsxs } from "react/jsx-runtime";
|
|
24144
|
-
function Banner({ version }) {
|
|
24145
|
-
return /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginBottom: 1, marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
24146
|
-
/* @__PURE__ */ jsx(Text, { bold: true, color: "cyan", children: " \u25C6 " }),
|
|
24147
|
-
/* @__PURE__ */ jsx(Text, { bold: true, children: "RobinPath" }),
|
|
24148
|
-
/* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
24149
|
-
" v",
|
|
24150
|
-
version
|
|
24151
|
-
] })
|
|
24152
|
-
] }) });
|
|
24153
|
-
}
|
|
24154
|
-
|
|
24155
|
-
// src/ui/StatusBar.tsx
|
|
24156
|
-
import { Box as Box2, Text as Text2 } from "ink";
|
|
24157
|
-
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
24158
|
-
function StatusBar({ model, shell, mode, cost = 0, tokens = 0 }) {
|
|
24159
|
-
return /* @__PURE__ */ jsx2(Box2, { paddingX: 2, marginTop: 0, children: /* @__PURE__ */ jsxs2(Text2, { dimColor: true, children: [
|
|
24160
|
-
model,
|
|
24161
|
-
/* @__PURE__ */ jsx2(Text2, { children: " \xB7 " }),
|
|
24162
|
-
shell,
|
|
24163
|
-
/* @__PURE__ */ jsx2(Text2, { children: " \xB7 " }),
|
|
24164
|
-
mode,
|
|
24165
|
-
tokens > 0 && /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
24166
|
-
" \xB7 ",
|
|
24167
|
-
tokens.toLocaleString(),
|
|
24168
|
-
" tokens"
|
|
24169
|
-
] }),
|
|
24170
|
-
cost > 0 && /* @__PURE__ */ jsxs2(Text2, { children: [
|
|
24171
|
-
" \xB7 $",
|
|
24172
|
-
cost.toFixed(4)
|
|
24173
|
-
] })
|
|
24174
|
-
] }) });
|
|
24175
|
-
}
|
|
24176
|
-
|
|
24177
|
-
// src/ui/InputBox.tsx
|
|
24178
24135
|
import { useState } from "react";
|
|
24179
|
-
import { Box
|
|
24180
|
-
import {
|
|
24181
|
-
|
|
24136
|
+
import { render, Box, Text, useInput, useApp } from "ink";
|
|
24137
|
+
import { homedir as homedir8, platform as platform7 } from "node:os";
|
|
24138
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
24139
|
+
import { readdirSync as readdirSync6, statSync as statSync6 } from "node:fs";
|
|
24140
|
+
import { join as join12 } from "node:path";
|
|
24141
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
24142
|
+
function InputPrompt({ placeholder, onSubmit, onExit }) {
|
|
24182
24143
|
const [value, setValue] = useState("");
|
|
24144
|
+
const { exit } = useApp();
|
|
24183
24145
|
useInput((input, key) => {
|
|
24184
|
-
if (!isActive) return;
|
|
24185
24146
|
if (key.return) {
|
|
24186
24147
|
if (value.endsWith("\\")) {
|
|
24187
|
-
setValue((
|
|
24148
|
+
setValue((p) => p.slice(0, -1) + "\n");
|
|
24188
24149
|
return;
|
|
24189
24150
|
}
|
|
24190
24151
|
const text = value.trim();
|
|
24191
24152
|
if (text) {
|
|
24153
|
+
exit();
|
|
24192
24154
|
onSubmit(text);
|
|
24193
|
-
setValue("");
|
|
24194
24155
|
}
|
|
24195
24156
|
return;
|
|
24196
24157
|
}
|
|
24197
24158
|
if (input === "\n") {
|
|
24198
|
-
setValue((
|
|
24159
|
+
setValue((p) => p + "\n");
|
|
24199
24160
|
return;
|
|
24200
24161
|
}
|
|
24201
24162
|
if (key.escape) {
|
|
@@ -24203,12 +24164,14 @@ function InputBox({ placeholder = "Ask anything...", onSubmit, isActive = true }
|
|
|
24203
24164
|
return;
|
|
24204
24165
|
}
|
|
24205
24166
|
if (input === "") {
|
|
24206
|
-
if (value
|
|
24207
|
-
|
|
24167
|
+
if (!value) {
|
|
24168
|
+
exit();
|
|
24169
|
+
onExit();
|
|
24170
|
+
} else setValue("");
|
|
24208
24171
|
return;
|
|
24209
24172
|
}
|
|
24210
24173
|
if (key.backspace || key.delete) {
|
|
24211
|
-
setValue((
|
|
24174
|
+
setValue((p) => p.slice(0, -1));
|
|
24212
24175
|
return;
|
|
24213
24176
|
}
|
|
24214
24177
|
if (key.tab) return;
|
|
@@ -24217,145 +24180,61 @@ function InputBox({ placeholder = "Ask anything...", onSubmit, isActive = true }
|
|
|
24217
24180
|
return;
|
|
24218
24181
|
}
|
|
24219
24182
|
if (input === "") {
|
|
24220
|
-
setValue((
|
|
24183
|
+
setValue((p) => p.replace(/\S+\s*$/, ""));
|
|
24221
24184
|
return;
|
|
24222
24185
|
}
|
|
24223
|
-
if (input && !key.ctrl && !key.meta)
|
|
24224
|
-
|
|
24225
|
-
}
|
|
24226
|
-
}, { isActive });
|
|
24186
|
+
if (input && !key.ctrl && !key.meta) setValue((p) => p + input);
|
|
24187
|
+
});
|
|
24227
24188
|
const lines = value.split("\n");
|
|
24228
|
-
const
|
|
24229
|
-
|
|
24230
|
-
|
|
24231
|
-
|
|
24232
|
-
|
|
24233
|
-
|
|
24234
|
-
|
|
24235
|
-
borderStyle: "round",
|
|
24236
|
-
borderColor: isActive ? "cyan" : "gray",
|
|
24237
|
-
flexDirection: "column",
|
|
24238
|
-
paddingX: 1,
|
|
24239
|
-
marginX: 1,
|
|
24240
|
-
children: isEmpty ? /* @__PURE__ */ jsx3(Text3, { dimColor: true, children: placeholder }) : lines.map((line, i) => /* @__PURE__ */ jsxs3(Text3, { children: [
|
|
24241
|
-
line,
|
|
24242
|
-
i === lines.length - 1 && isActive ? /* @__PURE__ */ jsx3(Text3, { color: "cyan", children: "\u2588" }) : null
|
|
24243
|
-
] }, i))
|
|
24244
|
-
}
|
|
24245
|
-
),
|
|
24246
|
-
/* @__PURE__ */ jsx3(Box3, { marginX: 2, marginTop: 0, children: /* @__PURE__ */ jsxs3(Text3, { dimColor: true, children: [
|
|
24247
|
-
/* @__PURE__ */ jsx3(Text3, { color: "gray", children: "enter" }),
|
|
24248
|
-
" send",
|
|
24249
|
-
/* @__PURE__ */ jsx3(Text3, { children: " \xB7 " }),
|
|
24250
|
-
/* @__PURE__ */ jsx3(Text3, { color: "gray", children: "\\" }),
|
|
24251
|
-
" newline",
|
|
24252
|
-
/* @__PURE__ */ jsx3(Text3, { children: " \xB7 " }),
|
|
24253
|
-
/* @__PURE__ */ jsx3(Text3, { color: "gray", children: "esc" }),
|
|
24254
|
-
" clear",
|
|
24255
|
-
/* @__PURE__ */ jsx3(Text3, { children: " \xB7 " }),
|
|
24256
|
-
/* @__PURE__ */ jsx3(Text3, { color: "gray", children: "/" }),
|
|
24257
|
-
" commands"
|
|
24258
|
-
] }) })
|
|
24189
|
+
const empty = value === "";
|
|
24190
|
+
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", children: [
|
|
24191
|
+
/* @__PURE__ */ jsx(Box, { borderStyle: "round", borderColor: "cyan", flexDirection: "column", paddingX: 1, marginX: 1, children: empty ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: placeholder }) : lines.map((line, i) => /* @__PURE__ */ jsxs(Text, { children: [
|
|
24192
|
+
line,
|
|
24193
|
+
i === lines.length - 1 ? /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u2588" }) : null
|
|
24194
|
+
] }, i)) }),
|
|
24195
|
+
/* @__PURE__ */ jsx(Box, { marginX: 2, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: "enter send \xB7 \\ newline \xB7 esc clear \xB7 / commands" }) })
|
|
24259
24196
|
] });
|
|
24260
24197
|
}
|
|
24261
|
-
|
|
24262
|
-
|
|
24263
|
-
|
|
24264
|
-
|
|
24265
|
-
|
|
24266
|
-
|
|
24267
|
-
|
|
24268
|
-
|
|
24269
|
-
|
|
24270
|
-
|
|
24271
|
-
|
|
24272
|
-
|
|
24273
|
-
|
|
24274
|
-
|
|
24275
|
-
|
|
24276
|
-
|
|
24277
|
-
}
|
|
24278
|
-
|
|
24279
|
-
|
|
24280
|
-
|
|
24281
|
-
|
|
24282
|
-
|
|
24283
|
-
|
|
24284
|
-
|
|
24285
|
-
|
|
24286
|
-
|
|
24287
|
-
|
|
24288
|
-
|
|
24289
|
-
|
|
24290
|
-
|
|
24291
|
-
// src/ui/App.tsx
|
|
24292
|
-
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
24293
|
-
var msgId = 0;
|
|
24294
|
-
function App({ version, model, mode, dir, shell, onSubmit }) {
|
|
24295
|
-
const [messages, setMessages] = useState2([]);
|
|
24296
|
-
const [isLoading, setIsLoading] = useState2(false);
|
|
24297
|
-
const [streamText, setStreamText] = useState2("");
|
|
24298
|
-
const [spinnerLabel, setSpinnerLabel] = useState2("Thinking");
|
|
24299
|
-
const [totalTokens, setTotalTokens] = useState2(0);
|
|
24300
|
-
const [totalCost, setTotalCost] = useState2(0);
|
|
24301
|
-
const ui = global.__rpUI = global.__rpUI || {};
|
|
24302
|
-
ui.setStreamText = setStreamText;
|
|
24303
|
-
ui.setSpinnerLabel = setSpinnerLabel;
|
|
24304
|
-
ui.setLoading = setIsLoading;
|
|
24305
|
-
ui.setTokens = setTotalTokens;
|
|
24306
|
-
ui.setCost = setTotalCost;
|
|
24307
|
-
ui.addMessage = (role, content) => {
|
|
24308
|
-
setMessages((prev) => [...prev, { id: ++msgId, role, content }]);
|
|
24309
|
-
};
|
|
24310
|
-
const handleSubmit = useCallback(async (text) => {
|
|
24311
|
-
setMessages((prev) => [...prev, { id: ++msgId, role: "user", content: text }]);
|
|
24312
|
-
setIsLoading(true);
|
|
24313
|
-
setStreamText("");
|
|
24314
|
-
setSpinnerLabel("Thinking");
|
|
24315
|
-
try {
|
|
24316
|
-
const response = await onSubmit(text);
|
|
24317
|
-
if (response) {
|
|
24318
|
-
setMessages((prev) => [...prev, { id: ++msgId, role: "assistant", content: response }]);
|
|
24319
|
-
}
|
|
24320
|
-
} catch (err) {
|
|
24321
|
-
setMessages((prev) => [...prev, { id: ++msgId, role: "assistant", content: `Error: ${err.message}` }]);
|
|
24322
|
-
} finally {
|
|
24323
|
-
setIsLoading(false);
|
|
24324
|
-
setStreamText("");
|
|
24325
|
-
}
|
|
24326
|
-
}, [onSubmit]);
|
|
24327
|
-
return /* @__PURE__ */ jsxs6(Box6, { flexDirection: "column", children: [
|
|
24328
|
-
/* @__PURE__ */ jsx6(Banner, { version }),
|
|
24329
|
-
/* @__PURE__ */ jsx6(Static, { items: messages, children: (msg) => /* @__PURE__ */ jsx6(ChatMessage, { role: msg.role, content: msg.content }, msg.id) }),
|
|
24330
|
-
isLoading && streamText ? /* @__PURE__ */ jsx6(ChatMessage, { role: "assistant", content: streamText, isStreaming: true }) : null,
|
|
24331
|
-
isLoading && !streamText ? /* @__PURE__ */ jsx6(Spinner, { label: spinnerLabel }) : null,
|
|
24332
|
-
/* @__PURE__ */ jsx6(
|
|
24333
|
-
InputBox,
|
|
24334
|
-
{
|
|
24335
|
-
onSubmit: handleSubmit,
|
|
24336
|
-
isActive: !isLoading,
|
|
24337
|
-
placeholder: messages.length === 0 ? "What do you want to automate?" : "Ask anything..."
|
|
24338
|
-
}
|
|
24339
|
-
),
|
|
24340
|
-
/* @__PURE__ */ jsx6(
|
|
24341
|
-
StatusBar,
|
|
24342
|
-
{
|
|
24343
|
-
model,
|
|
24344
|
-
shell,
|
|
24345
|
-
mode,
|
|
24346
|
-
tokens: totalTokens,
|
|
24347
|
-
cost: totalCost
|
|
24198
|
+
function collectInkInput(placeholder) {
|
|
24199
|
+
if (!process.stdin.isTTY) {
|
|
24200
|
+
return Promise.resolve(null);
|
|
24201
|
+
}
|
|
24202
|
+
return new Promise((resolve13) => {
|
|
24203
|
+
let resolved = false;
|
|
24204
|
+
const { waitUntilExit } = render(
|
|
24205
|
+
/* @__PURE__ */ jsx(
|
|
24206
|
+
InputPrompt,
|
|
24207
|
+
{
|
|
24208
|
+
placeholder,
|
|
24209
|
+
onSubmit: (v) => {
|
|
24210
|
+
if (!resolved) {
|
|
24211
|
+
resolved = true;
|
|
24212
|
+
resolve13(v);
|
|
24213
|
+
}
|
|
24214
|
+
},
|
|
24215
|
+
onExit: () => {
|
|
24216
|
+
if (!resolved) {
|
|
24217
|
+
resolved = true;
|
|
24218
|
+
resolve13(null);
|
|
24219
|
+
}
|
|
24220
|
+
}
|
|
24221
|
+
}
|
|
24222
|
+
)
|
|
24223
|
+
);
|
|
24224
|
+
waitUntilExit().then(() => {
|
|
24225
|
+
if (!resolved) {
|
|
24226
|
+
resolved = true;
|
|
24227
|
+
resolve13(null);
|
|
24348
24228
|
}
|
|
24349
|
-
)
|
|
24350
|
-
|
|
24229
|
+
});
|
|
24230
|
+
});
|
|
24231
|
+
}
|
|
24232
|
+
function printBanner(modelShort, modeStr, cwdShort, shellName) {
|
|
24233
|
+
log("");
|
|
24234
|
+
log(` ${color.cyan("\u25C6")} ${color.bold("RobinPath")} ${color.dim("v" + CLI_VERSION)}`);
|
|
24235
|
+
log(color.dim(` ${modelShort} \xB7 ${shellName} \xB7 ${modeStr}`));
|
|
24236
|
+
log("");
|
|
24351
24237
|
}
|
|
24352
|
-
|
|
24353
|
-
// src/ink-repl.tsx
|
|
24354
|
-
import { homedir as homedir8, platform as platform7 } from "node:os";
|
|
24355
|
-
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
24356
|
-
import { readdirSync as readdirSync6, statSync as statSync6 } from "node:fs";
|
|
24357
|
-
import { join as join12 } from "node:path";
|
|
24358
|
-
import { jsx as jsx7 } from "react/jsx-runtime";
|
|
24359
24238
|
async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
|
|
24360
24239
|
const config = readAiConfig();
|
|
24361
24240
|
let autoAccept = opts.autoAccept || false;
|
|
@@ -24384,6 +24263,7 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
|
|
|
24384
24263
|
let sessionName = `session-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
24385
24264
|
const usage = createUsageTracker();
|
|
24386
24265
|
const conversationMessages = [];
|
|
24266
|
+
const history = [];
|
|
24387
24267
|
const memContext = buildMemoryContext();
|
|
24388
24268
|
if (memContext.trim()) {
|
|
24389
24269
|
conversationMessages.push({ role: "user", content: `[Context] ${memContext.trim()}` });
|
|
@@ -24400,153 +24280,316 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
|
|
|
24400
24280
|
usage.totalTokens = session.usage.totalTokens || 0;
|
|
24401
24281
|
usage.requests = session.usage.requests || 0;
|
|
24402
24282
|
}
|
|
24283
|
+
log(color.green(` Resumed: ${sessionName} (${session.messages.length} msgs)`));
|
|
24403
24284
|
}
|
|
24404
24285
|
}
|
|
24405
|
-
|
|
24286
|
+
const modeStr = devMode ? "dev" : autoAccept ? "auto" : "confirm";
|
|
24287
|
+
const cwdShort = process.cwd().replace(homedir8(), "~");
|
|
24288
|
+
printBanner(modelShort, modeStr, cwdShort, getShellConfig().name);
|
|
24406
24289
|
try {
|
|
24407
|
-
const
|
|
24408
|
-
const entries = readdirSync6(cwd).filter((e) => !e.startsWith("."));
|
|
24290
|
+
const entries = readdirSync6(process.cwd()).filter((e) => !e.startsWith("."));
|
|
24409
24291
|
let rpCount = 0, dirCount = 0;
|
|
24410
|
-
for (const
|
|
24292
|
+
for (const e of entries.slice(0, 100)) {
|
|
24411
24293
|
try {
|
|
24412
|
-
const s = statSync6(join12(cwd,
|
|
24413
|
-
if (s.isDirectory() && !["node_modules", "__pycache__", "dist", "build"].includes(
|
|
24414
|
-
else if (
|
|
24294
|
+
const s = statSync6(join12(process.cwd(), e));
|
|
24295
|
+
if (s.isDirectory() && !["node_modules", "__pycache__", "dist", "build"].includes(e)) dirCount++;
|
|
24296
|
+
else if (e.endsWith(".rp") || e.endsWith(".robin")) rpCount++;
|
|
24415
24297
|
} catch {
|
|
24416
24298
|
}
|
|
24417
24299
|
}
|
|
24418
|
-
if (rpCount > 0 || dirCount > 0) {
|
|
24419
|
-
projectInfo = `${rpCount} .rp file(s), ${dirCount} dir(s)`;
|
|
24420
|
-
}
|
|
24300
|
+
if (rpCount > 0 || dirCount > 0) log(color.dim(` ${rpCount} .rp file(s), ${dirCount} dir(s)`));
|
|
24421
24301
|
} catch {
|
|
24422
24302
|
}
|
|
24423
|
-
|
|
24424
|
-
|
|
24425
|
-
|
|
24426
|
-
|
|
24427
|
-
|
|
24428
|
-
|
|
24429
|
-
|
|
24303
|
+
let isFirst = !resumeSessionId && !initialPrompt;
|
|
24304
|
+
while (true) {
|
|
24305
|
+
let trimmed;
|
|
24306
|
+
if (initialPrompt) {
|
|
24307
|
+
trimmed = initialPrompt.trim();
|
|
24308
|
+
initialPrompt = null;
|
|
24309
|
+
log(color.cyan(" \u276F ") + trimmed);
|
|
24310
|
+
} else {
|
|
24311
|
+
const input = await collectInkInput(
|
|
24312
|
+
isFirst ? "What do you want to automate?" : "Ask anything..."
|
|
24313
|
+
);
|
|
24314
|
+
isFirst = false;
|
|
24315
|
+
if (input === null) {
|
|
24316
|
+
exitWithSave();
|
|
24317
|
+
break;
|
|
24318
|
+
}
|
|
24319
|
+
trimmed = input.trim();
|
|
24320
|
+
if (!trimmed) continue;
|
|
24321
|
+
log(color.cyan(" \u276F ") + color.bold(trimmed));
|
|
24322
|
+
}
|
|
24323
|
+
if (trimmed === "/") {
|
|
24324
|
+
log("");
|
|
24325
|
+
for (const [cmd, desc] of Object.entries(SLASH_CMDS)) {
|
|
24326
|
+
log(` ${color.cyan(cmd.padEnd(14))} ${color.dim(desc)}`);
|
|
24327
|
+
}
|
|
24328
|
+
log("");
|
|
24329
|
+
continue;
|
|
24330
|
+
}
|
|
24331
|
+
if (trimmed === "exit" || trimmed === "quit") {
|
|
24332
|
+
exitWithSave();
|
|
24333
|
+
break;
|
|
24430
24334
|
}
|
|
24431
|
-
if (
|
|
24432
|
-
|
|
24433
|
-
|
|
24335
|
+
if (trimmed === "/help") {
|
|
24336
|
+
log("");
|
|
24337
|
+
for (const [cmd, desc] of Object.entries(SLASH_CMDS)) {
|
|
24338
|
+
log(` ${color.cyan(cmd.padEnd(14))} ${color.dim(desc)}`);
|
|
24434
24339
|
}
|
|
24435
|
-
|
|
24340
|
+
log("");
|
|
24341
|
+
continue;
|
|
24436
24342
|
}
|
|
24437
|
-
if (
|
|
24343
|
+
if (trimmed === "/clear") {
|
|
24344
|
+
conversationMessages.length = 0;
|
|
24345
|
+
log(color.green(" Cleared."));
|
|
24346
|
+
continue;
|
|
24347
|
+
}
|
|
24348
|
+
if (trimmed === "/usage") {
|
|
24349
|
+
const cost = usage.cost > 0 ? `$${usage.cost.toFixed(4)}` : "$0.00 (free)";
|
|
24350
|
+
log(` ${usage.totalTokens.toLocaleString()} tokens \xB7 ${usage.requests} requests \xB7 ${cost}`);
|
|
24351
|
+
continue;
|
|
24352
|
+
}
|
|
24353
|
+
if (trimmed === "/model") {
|
|
24438
24354
|
const hasKey = !!readAiConfig().apiKey;
|
|
24439
24355
|
const models = hasKey ? AI_MODELS : AI_MODELS.filter((m) => !m.requiresKey);
|
|
24440
|
-
|
|
24356
|
+
const cur = readAiConfig().model || model;
|
|
24357
|
+
log("");
|
|
24358
|
+
let lastGroup = "";
|
|
24359
|
+
for (let i = 0; i < models.length; i++) {
|
|
24360
|
+
const m = models[i];
|
|
24361
|
+
if (m.group !== lastGroup) {
|
|
24362
|
+
log(color.dim(` \u2500\u2500 ${m.group} \u2500\u2500`));
|
|
24363
|
+
lastGroup = m.group;
|
|
24364
|
+
}
|
|
24365
|
+
const mark = m.id === cur ? color.green(" \u2713") : "";
|
|
24366
|
+
log(` ${color.cyan(String(i + 1))}. ${m.name} ${color.dim("\u2014 " + m.desc)}${mark}`);
|
|
24367
|
+
}
|
|
24368
|
+
log("");
|
|
24369
|
+
const answer = await collectInkInput("Enter number...");
|
|
24370
|
+
if (answer) {
|
|
24371
|
+
const idx = parseInt(answer, 10) - 1;
|
|
24372
|
+
if (idx >= 0 && idx < models.length) {
|
|
24373
|
+
config.model = models[idx].id;
|
|
24374
|
+
writeAiConfig(config);
|
|
24375
|
+
log(color.green(` Model: ${models[idx].id}`));
|
|
24376
|
+
}
|
|
24377
|
+
}
|
|
24378
|
+
continue;
|
|
24441
24379
|
}
|
|
24442
|
-
if (
|
|
24443
|
-
const
|
|
24444
|
-
|
|
24380
|
+
if (trimmed === "/auto" || trimmed.startsWith("/auto ")) {
|
|
24381
|
+
const arg = trimmed.slice(5).trim().toLowerCase();
|
|
24382
|
+
if (arg === "on") autoAccept = true;
|
|
24383
|
+
else if (arg === "off") autoAccept = false;
|
|
24384
|
+
else autoAccept = !autoAccept;
|
|
24385
|
+
log(` Auto-accept: ${autoAccept ? color.green("ON") : color.yellow("OFF")}`);
|
|
24386
|
+
continue;
|
|
24445
24387
|
}
|
|
24446
|
-
if (
|
|
24447
|
-
|
|
24448
|
-
|
|
24388
|
+
if (trimmed === "/memory") {
|
|
24389
|
+
const mem = loadMemory();
|
|
24390
|
+
if (mem.facts.length === 0) log(color.dim(" No memories."));
|
|
24391
|
+
else mem.facts.forEach((f, i) => log(` ${i + 1}. ${f}`));
|
|
24392
|
+
continue;
|
|
24393
|
+
}
|
|
24394
|
+
if (trimmed.startsWith("/remember ")) {
|
|
24395
|
+
addMemoryFact(trimmed.slice(10).trim());
|
|
24396
|
+
log(color.green(" Remembered."));
|
|
24397
|
+
continue;
|
|
24398
|
+
}
|
|
24399
|
+
if (trimmed.startsWith("/forget ")) {
|
|
24400
|
+
removeMemoryFact(parseInt(trimmed.slice(8).trim(), 10) - 1);
|
|
24401
|
+
log(color.green(" Forgot."));
|
|
24402
|
+
continue;
|
|
24403
|
+
}
|
|
24404
|
+
if (trimmed === "/save" || trimmed.startsWith("/save ")) {
|
|
24405
|
+
if (trimmed.length > 5) sessionName = trimmed.slice(5).trim();
|
|
24406
|
+
saveSession(sessionId, sessionName, conversationMessages, usage);
|
|
24407
|
+
log(color.green(` Saved: ${sessionName}`));
|
|
24408
|
+
continue;
|
|
24409
|
+
}
|
|
24410
|
+
if (trimmed === "/sessions") {
|
|
24411
|
+
const sessions = listSessions();
|
|
24412
|
+
if (sessions.length === 0) log(color.dim(" No sessions."));
|
|
24413
|
+
else sessions.forEach((s) => log(` ${color.cyan(s.id)} ${s.name} ${color.dim(`${s.messages} msgs`)}`));
|
|
24414
|
+
continue;
|
|
24415
|
+
}
|
|
24416
|
+
if (trimmed === "/shell") {
|
|
24417
|
+
const shells = getAvailableShells();
|
|
24418
|
+
shells.forEach((s) => {
|
|
24419
|
+
const mark = s.current ? color.green(" \u2713") : s.available ? "" : color.dim(" (not found)");
|
|
24420
|
+
log(` ${s.available ? color.cyan(s.name) : color.dim(s.name)}${mark}`);
|
|
24421
|
+
});
|
|
24422
|
+
continue;
|
|
24449
24423
|
}
|
|
24450
|
-
if (
|
|
24451
|
-
|
|
24424
|
+
if (trimmed.startsWith("/shell ")) {
|
|
24425
|
+
setShellOverride(trimmed.slice(7).trim());
|
|
24426
|
+
cliContext.shell = getShellConfig().name;
|
|
24427
|
+
log(` Shell: ${color.cyan(getShellConfig().name)}`);
|
|
24428
|
+
continue;
|
|
24429
|
+
}
|
|
24430
|
+
if (trimmed.startsWith("/")) {
|
|
24431
|
+
log(color.dim(` Unknown: ${trimmed}. Type / for commands.`));
|
|
24432
|
+
continue;
|
|
24452
24433
|
}
|
|
24453
|
-
const { expanded } = expandFileRefs(
|
|
24434
|
+
const { expanded } = expandFileRefs(trimmed);
|
|
24454
24435
|
conversationMessages.push({ role: "user", content: expanded });
|
|
24455
24436
|
await autoCompact(conversationMessages);
|
|
24456
24437
|
const activeModel = readAiConfig().model || model;
|
|
24457
24438
|
const activeKey = readAiConfig().apiKey || apiKey;
|
|
24458
24439
|
const activeProvider = resolveProvider(activeKey);
|
|
24459
|
-
let
|
|
24460
|
-
|
|
24461
|
-
|
|
24462
|
-
|
|
24463
|
-
|
|
24464
|
-
|
|
24465
|
-
|
|
24466
|
-
|
|
24467
|
-
|
|
24468
|
-
|
|
24469
|
-
|
|
24470
|
-
|
|
24471
|
-
|
|
24472
|
-
|
|
24473
|
-
|
|
24474
|
-
|
|
24475
|
-
|
|
24476
|
-
|
|
24477
|
-
|
|
24478
|
-
|
|
24479
|
-
|
|
24440
|
+
let spinner = createSpinner("Thinking");
|
|
24441
|
+
try {
|
|
24442
|
+
for (let loop = 0; loop < 15; loop++) {
|
|
24443
|
+
let pending = "";
|
|
24444
|
+
let insideMemory = false;
|
|
24445
|
+
let insideCmd = false;
|
|
24446
|
+
const result = await fetchBrainStream(
|
|
24447
|
+
loop === 0 ? expanded : conversationMessages[conversationMessages.length - 1].content,
|
|
24448
|
+
{
|
|
24449
|
+
onToken: (delta) => {
|
|
24450
|
+
spinner.stop();
|
|
24451
|
+
if (delta === "\x1B[RETRY]") {
|
|
24452
|
+
pending = "";
|
|
24453
|
+
insideMemory = false;
|
|
24454
|
+
insideCmd = false;
|
|
24455
|
+
spinner = createSpinner("Retrying");
|
|
24456
|
+
return;
|
|
24457
|
+
}
|
|
24458
|
+
pending += delta;
|
|
24459
|
+
while (true) {
|
|
24460
|
+
if (insideMemory) {
|
|
24461
|
+
const ci2 = pending.indexOf("</memory>");
|
|
24462
|
+
if (ci2 === -1) break;
|
|
24463
|
+
const fact = pending.slice(0, ci2).trim();
|
|
24464
|
+
if (fact.length > 3 && fact.length < 300) addMemoryFact(fact);
|
|
24465
|
+
pending = pending.slice(ci2 + 9);
|
|
24466
|
+
insideMemory = false;
|
|
24467
|
+
continue;
|
|
24468
|
+
}
|
|
24469
|
+
if (insideCmd) {
|
|
24470
|
+
const ci2 = pending.indexOf("</cmd>");
|
|
24471
|
+
if (ci2 === -1) break;
|
|
24472
|
+
pending = pending.slice(ci2 + 6);
|
|
24473
|
+
insideCmd = false;
|
|
24474
|
+
continue;
|
|
24475
|
+
}
|
|
24476
|
+
const mi = pending.indexOf("<memory>");
|
|
24477
|
+
const ci = pending.indexOf("<cmd>");
|
|
24478
|
+
if (mi === -1 && ci === -1) {
|
|
24479
|
+
const lt2 = pending.lastIndexOf("<");
|
|
24480
|
+
if (lt2 !== -1 && lt2 > pending.length - 9) {
|
|
24481
|
+
if (lt2 > 0) {
|
|
24482
|
+
process.stdout.write(pending.slice(0, lt2).replace(/\n{3,}/g, "\n\n"));
|
|
24483
|
+
pending = pending.slice(lt2);
|
|
24484
|
+
}
|
|
24485
|
+
} else {
|
|
24486
|
+
process.stdout.write(pending.replace(/\n{3,}/g, "\n\n"));
|
|
24487
|
+
pending = "";
|
|
24488
|
+
}
|
|
24489
|
+
break;
|
|
24490
|
+
}
|
|
24491
|
+
const first = mi === -1 ? ci : ci === -1 ? mi : Math.min(mi, ci);
|
|
24492
|
+
if (first > 0) process.stdout.write(pending.slice(0, first).replace(/\n{3,}/g, "\n\n"));
|
|
24493
|
+
if (first === mi) {
|
|
24494
|
+
pending = pending.slice(first + 8);
|
|
24495
|
+
insideMemory = true;
|
|
24496
|
+
} else {
|
|
24497
|
+
pending = pending.slice(first + 5);
|
|
24498
|
+
insideCmd = true;
|
|
24499
|
+
}
|
|
24500
|
+
}
|
|
24501
|
+
},
|
|
24502
|
+
conversationHistory: conversationMessages.slice(0, -1),
|
|
24503
|
+
provider: activeProvider,
|
|
24504
|
+
model: activeModel,
|
|
24505
|
+
apiKey: activeKey,
|
|
24506
|
+
cliContext
|
|
24507
|
+
}
|
|
24508
|
+
);
|
|
24509
|
+
if (pending && !insideMemory && !insideCmd) process.stdout.write(pending);
|
|
24510
|
+
if (!result || !result.code) {
|
|
24511
|
+
spinner.stop();
|
|
24512
|
+
log(color.red("\n No response."));
|
|
24513
|
+
break;
|
|
24480
24514
|
}
|
|
24481
|
-
|
|
24482
|
-
|
|
24483
|
-
|
|
24484
|
-
|
|
24485
|
-
|
|
24486
|
-
|
|
24487
|
-
|
|
24488
|
-
|
|
24489
|
-
|
|
24490
|
-
|
|
24491
|
-
|
|
24492
|
-
|
|
24493
|
-
if (
|
|
24494
|
-
if (
|
|
24495
|
-
|
|
24496
|
-
|
|
24497
|
-
|
|
24498
|
-
|
|
24499
|
-
|
|
24500
|
-
|
|
24501
|
-
|
|
24502
|
-
|
|
24503
|
-
|
|
24504
|
-
|
|
24505
|
-
|
|
24506
|
-
|
|
24507
|
-
|
|
24508
|
-
stdout:
|
|
24509
|
-
|
|
24510
|
-
|
|
24511
|
-
|
|
24512
|
-
}
|
|
24513
|
-
const summary = cmdResults.map((r) => {
|
|
24514
|
-
let out = `$ ${r.command}
|
|
24515
|
+
if (result.usage) {
|
|
24516
|
+
const pt2 = result.usage.prompt_tokens || 0;
|
|
24517
|
+
const ct2 = result.usage.completion_tokens || 0;
|
|
24518
|
+
usage.promptTokens += pt2;
|
|
24519
|
+
usage.completionTokens += ct2;
|
|
24520
|
+
usage.totalTokens += pt2 + ct2;
|
|
24521
|
+
usage.requests++;
|
|
24522
|
+
usage.cost += estimateCost(activeModel, pt2, ct2);
|
|
24523
|
+
}
|
|
24524
|
+
const commands = extractCommands(result.code);
|
|
24525
|
+
const { cleaned } = extractMemoryTags(stripCommandTags(result.code));
|
|
24526
|
+
process.stdout.write("\n");
|
|
24527
|
+
if (cleaned) conversationMessages.push({ role: "assistant", content: cleaned });
|
|
24528
|
+
if (commands.length === 0) break;
|
|
24529
|
+
const cmdResults = [];
|
|
24530
|
+
for (const cmd of commands) {
|
|
24531
|
+
const decision = await confirmCommand(cmd, autoAccept);
|
|
24532
|
+
if (decision === "no") {
|
|
24533
|
+
cmdResults.push({ command: cmd, stdout: "", stderr: "(skipped)", exitCode: -1 });
|
|
24534
|
+
continue;
|
|
24535
|
+
}
|
|
24536
|
+
if (decision === "auto") {
|
|
24537
|
+
autoAccept = true;
|
|
24538
|
+
log(color.green(" Auto-accept ON"));
|
|
24539
|
+
}
|
|
24540
|
+
const r = await executeShellCommand(cmd);
|
|
24541
|
+
if (r.exitCode !== 0) log(color.red(` exit ${r.exitCode}: ${(r.stderr || "").slice(0, 80)}`));
|
|
24542
|
+
cmdResults.push({ command: cmd, stdout: r.stdout || "", stderr: r.stderr || "", exitCode: r.exitCode });
|
|
24543
|
+
}
|
|
24544
|
+
const summary = cmdResults.map((r) => {
|
|
24545
|
+
let o = `$ ${r.command}
|
|
24515
24546
|
`;
|
|
24516
|
-
|
|
24517
|
-
|
|
24518
|
-
|
|
24547
|
+
if (r.exitCode === 0) o += r.stdout || "(no output)";
|
|
24548
|
+
else {
|
|
24549
|
+
o += `Exit: ${r.exitCode}
|
|
24519
24550
|
`;
|
|
24520
|
-
|
|
24521
|
-
|
|
24522
|
-
|
|
24523
|
-
|
|
24524
|
-
|
|
24551
|
+
if (r.stderr) o += r.stderr;
|
|
24552
|
+
}
|
|
24553
|
+
return o;
|
|
24554
|
+
}).join("\n\n");
|
|
24555
|
+
conversationMessages.push({ role: "user", content: `[Command results]
|
|
24525
24556
|
${summary}` });
|
|
24526
|
-
|
|
24527
|
-
|
|
24557
|
+
spinner = createSpinner("Processing");
|
|
24558
|
+
}
|
|
24559
|
+
} catch (err) {
|
|
24560
|
+
spinner.stop();
|
|
24561
|
+
log(color.red(` Error: ${err.message}`));
|
|
24528
24562
|
}
|
|
24529
|
-
|
|
24530
|
-
|
|
24563
|
+
log("");
|
|
24564
|
+
if (usage.cost > 0) log(color.dim(` $${usage.cost.toFixed(4)} \xB7 ${usage.totalTokens.toLocaleString()} tokens`));
|
|
24531
24565
|
}
|
|
24532
|
-
|
|
24533
|
-
|
|
24534
|
-
|
|
24535
|
-
{
|
|
24536
|
-
|
|
24537
|
-
|
|
24538
|
-
|
|
24539
|
-
dir: cwdShort,
|
|
24540
|
-
shell: getShellConfig().name,
|
|
24541
|
-
onSubmit: handleMessage
|
|
24542
|
-
}
|
|
24543
|
-
)
|
|
24544
|
-
);
|
|
24545
|
-
await waitUntilExit();
|
|
24546
|
-
if (conversationMessages.length > 1) {
|
|
24547
|
-
saveSession(sessionId, sessionName, conversationMessages, usage);
|
|
24566
|
+
function exitWithSave() {
|
|
24567
|
+
if (conversationMessages.length > 1) {
|
|
24568
|
+
saveSession(sessionId, sessionName, conversationMessages, usage);
|
|
24569
|
+
log(color.dim(` Session saved: ${sessionId}`));
|
|
24570
|
+
}
|
|
24571
|
+
log(color.dim(" Goodbye!"));
|
|
24572
|
+
process.exit(0);
|
|
24548
24573
|
}
|
|
24574
|
+
process.on("SIGINT", () => {
|
|
24575
|
+
log("");
|
|
24576
|
+
exitWithSave();
|
|
24577
|
+
});
|
|
24549
24578
|
}
|
|
24579
|
+
var SLASH_CMDS = {
|
|
24580
|
+
"/help": "Show commands",
|
|
24581
|
+
"/model": "Switch AI model",
|
|
24582
|
+
"/shell": "Switch shell",
|
|
24583
|
+
"/auto": "Toggle auto-accept",
|
|
24584
|
+
"/clear": "Clear conversation",
|
|
24585
|
+
"/save": "Save session",
|
|
24586
|
+
"/sessions": "List sessions",
|
|
24587
|
+
"/memory": "Show memory",
|
|
24588
|
+
"/remember": "Save a fact",
|
|
24589
|
+
"/forget": "Remove a memory",
|
|
24590
|
+
"/usage": "Token usage & cost",
|
|
24591
|
+
"exit": "Quit"
|
|
24592
|
+
};
|
|
24550
24593
|
|
|
24551
24594
|
// src/commands-modules.ts
|
|
24552
24595
|
import { createInterface as createInterface3 } from "node:readline";
|