@robinpath/cli 1.81.0 → 1.82.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 +189 -138
- 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.82.0" : "1.82.0";
|
|
18602
18602
|
var FLAG_QUIET = false;
|
|
18603
18603
|
var FLAG_VERBOSE = false;
|
|
18604
18604
|
var FLAG_AUTO_ACCEPT = false;
|
|
@@ -24164,25 +24164,29 @@ import { jsx, jsxs } from "react/jsx-runtime";
|
|
|
24164
24164
|
var nextId = 0;
|
|
24165
24165
|
var COMMANDS = {
|
|
24166
24166
|
"/model": "Switch AI model",
|
|
24167
|
-
"/auto": "Toggle auto-accept",
|
|
24167
|
+
"/auto": "Toggle auto-accept (commands)",
|
|
24168
24168
|
"/clear": "Clear conversation",
|
|
24169
|
+
"/compact": "Trim to last 10 messages",
|
|
24169
24170
|
"/save": "Save session",
|
|
24170
|
-
"/sessions": "List sessions",
|
|
24171
|
-
"/
|
|
24171
|
+
"/sessions": "List saved sessions",
|
|
24172
|
+
"/resume": "Resume a session",
|
|
24173
|
+
"/delete": "Delete a session",
|
|
24174
|
+
"/memory": "Show persistent memory",
|
|
24172
24175
|
"/remember": "Save a fact",
|
|
24176
|
+
"/forget": "Remove a memory",
|
|
24173
24177
|
"/usage": "Token usage & cost",
|
|
24174
24178
|
"/shell": "Switch shell",
|
|
24175
|
-
"/help": "
|
|
24179
|
+
"/help": "All commands"
|
|
24176
24180
|
};
|
|
24177
24181
|
function InputArea({ onSubmit, placeholder }) {
|
|
24178
24182
|
const [value, setValue] = useState("");
|
|
24179
|
-
const [showHints, setShowHints] = useState(false);
|
|
24180
24183
|
const { exit } = useApp();
|
|
24181
24184
|
const matchingCommands = useMemo(() => {
|
|
24182
24185
|
if (!value.startsWith("/")) return [];
|
|
24183
24186
|
if (value === "/") return Object.entries(COMMANDS);
|
|
24184
24187
|
return Object.entries(COMMANDS).filter(([cmd]) => cmd.startsWith(value));
|
|
24185
24188
|
}, [value]);
|
|
24189
|
+
const showHints = value.startsWith("/") && matchingCommands.length > 0;
|
|
24186
24190
|
useInput((ch, key) => {
|
|
24187
24191
|
if (key.return) {
|
|
24188
24192
|
if (value.endsWith("\\")) {
|
|
@@ -24193,7 +24197,6 @@ function InputArea({ onSubmit, placeholder }) {
|
|
|
24193
24197
|
if (text) {
|
|
24194
24198
|
onSubmit(text);
|
|
24195
24199
|
setValue("");
|
|
24196
|
-
setShowHints(false);
|
|
24197
24200
|
}
|
|
24198
24201
|
return;
|
|
24199
24202
|
}
|
|
@@ -24203,15 +24206,11 @@ function InputArea({ onSubmit, placeholder }) {
|
|
|
24203
24206
|
}
|
|
24204
24207
|
if (key.escape) {
|
|
24205
24208
|
setValue("");
|
|
24206
|
-
setShowHints(false);
|
|
24207
24209
|
return;
|
|
24208
24210
|
}
|
|
24209
24211
|
if (ch === "") {
|
|
24210
24212
|
if (!value) exit();
|
|
24211
|
-
else
|
|
24212
|
-
setValue("");
|
|
24213
|
-
setShowHints(false);
|
|
24214
|
-
}
|
|
24213
|
+
else setValue("");
|
|
24215
24214
|
return;
|
|
24216
24215
|
}
|
|
24217
24216
|
if (key.backspace || key.delete) {
|
|
@@ -24219,10 +24218,7 @@ function InputArea({ onSubmit, placeholder }) {
|
|
|
24219
24218
|
return;
|
|
24220
24219
|
}
|
|
24221
24220
|
if (key.tab) {
|
|
24222
|
-
if (matchingCommands.length === 1)
|
|
24223
|
-
setValue(matchingCommands[0][0] + " ");
|
|
24224
|
-
setShowHints(false);
|
|
24225
|
-
}
|
|
24221
|
+
if (matchingCommands.length === 1) setValue(matchingCommands[0][0]);
|
|
24226
24222
|
return;
|
|
24227
24223
|
}
|
|
24228
24224
|
if (ch === "") {
|
|
@@ -24235,73 +24231,78 @@ function InputArea({ onSubmit, placeholder }) {
|
|
|
24235
24231
|
}
|
|
24236
24232
|
if (ch && !key.ctrl && !key.meta) setValue((p) => p + ch);
|
|
24237
24233
|
});
|
|
24238
|
-
useEffect(() => {
|
|
24239
|
-
setShowHints(value.startsWith("/") && matchingCommands.length > 0);
|
|
24240
|
-
}, [value, matchingCommands.length]);
|
|
24241
24234
|
const lines = value.split("\n");
|
|
24242
24235
|
const empty = value === "";
|
|
24236
|
+
const w = Math.min(process.stdout.columns - 4 || 76, 76);
|
|
24243
24237
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginTop: 1, children: [
|
|
24244
|
-
showHints && /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginX: 2, marginBottom: 1, children: matchingCommands.slice(0,
|
|
24245
|
-
/* @__PURE__ */ jsx(Text, { color: "cyan", children: cmd.padEnd(
|
|
24238
|
+
showHints && /* @__PURE__ */ jsx(Box, { flexDirection: "column", marginX: 2, marginBottom: 1, children: matchingCommands.slice(0, 8).map(([cmd, desc]) => /* @__PURE__ */ jsxs(Text, { children: [
|
|
24239
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", children: cmd.padEnd(14) }),
|
|
24246
24240
|
/* @__PURE__ */ jsx(Text, { dimColor: true, children: desc })
|
|
24247
24241
|
] }, cmd)) }),
|
|
24248
24242
|
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginX: 1, children: [
|
|
24249
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(
|
|
24250
|
-
/* @__PURE__ */ jsx(Box, { paddingX: 1, children: empty ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
24243
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(w) }),
|
|
24244
|
+
/* @__PURE__ */ jsx(Box, { paddingX: 1, flexDirection: "column", children: empty ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
24251
24245
|
"> ",
|
|
24252
24246
|
placeholder
|
|
24253
|
-
] }) : /* @__PURE__ */ jsxs(Text, { children: [
|
|
24254
|
-
/* @__PURE__ */ jsx(Text, { color: "cyan", children: "> " }),
|
|
24255
|
-
|
|
24256
|
-
|
|
24257
|
-
|
|
24258
|
-
|
|
24259
|
-
] }, i))
|
|
24260
|
-
] }) }),
|
|
24261
|
-
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(Math.min(process.stdout.columns - 4, 76)) })
|
|
24247
|
+
] }) : lines.map((line, i) => /* @__PURE__ */ jsxs(Text, { children: [
|
|
24248
|
+
i === 0 ? /* @__PURE__ */ jsx(Text, { color: "cyan", children: "> " }) : /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
|
|
24249
|
+
line,
|
|
24250
|
+
i === lines.length - 1 ? /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u258E" }) : null
|
|
24251
|
+
] }, i)) }),
|
|
24252
|
+
/* @__PURE__ */ jsx(Text, { dimColor: true, children: "\u2500".repeat(w) })
|
|
24262
24253
|
] }),
|
|
24263
24254
|
/* @__PURE__ */ jsx(Box, { marginX: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
24264
24255
|
/* @__PURE__ */ jsx(Text, { color: "gray", children: "enter" }),
|
|
24265
24256
|
" send ",
|
|
24266
24257
|
/* @__PURE__ */ jsx(Text, { color: "gray", children: "\\" }),
|
|
24267
24258
|
" newline ",
|
|
24268
|
-
/* @__PURE__ */ jsx(Text, { color: "gray", children: "esc" }),
|
|
24269
|
-
" clear ",
|
|
24270
24259
|
/* @__PURE__ */ jsx(Text, { color: "gray", children: "/" }),
|
|
24271
24260
|
" commands ",
|
|
24272
24261
|
/* @__PURE__ */ jsx(Text, { color: "gray", children: "tab" }),
|
|
24273
|
-
" complete"
|
|
24262
|
+
" complete ",
|
|
24263
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "@/" }),
|
|
24264
|
+
" files"
|
|
24274
24265
|
] }) })
|
|
24275
24266
|
] });
|
|
24276
24267
|
}
|
|
24277
|
-
function ChatApp({
|
|
24268
|
+
function ChatApp({ engine }) {
|
|
24278
24269
|
const [messages, setMessages] = useState([]);
|
|
24279
24270
|
const [streaming, setStreaming] = useState("");
|
|
24280
24271
|
const [loading, setLoading] = useState(false);
|
|
24272
|
+
const [status, setStatus] = useState("");
|
|
24281
24273
|
useEffect(() => {
|
|
24282
|
-
|
|
24274
|
+
engine.ui = {
|
|
24283
24275
|
setStreaming,
|
|
24284
24276
|
setLoading,
|
|
24277
|
+
setStatus,
|
|
24285
24278
|
addMessage: (text, dim) => setMessages((p) => [...p, { id: ++nextId, text, dim }])
|
|
24286
24279
|
};
|
|
24280
|
+
engine.updateStatus();
|
|
24287
24281
|
}, []);
|
|
24288
24282
|
const handleSubmit = useCallback(async (text) => {
|
|
24289
24283
|
if (text === "exit" || text === "quit") {
|
|
24290
|
-
|
|
24284
|
+
engine.exit();
|
|
24285
|
+
return;
|
|
24286
|
+
}
|
|
24287
|
+
if (text.startsWith("/")) {
|
|
24288
|
+
const result = await engine.handleSlashCommand(text);
|
|
24289
|
+
if (result) setMessages((p) => [...p, { id: ++nextId, text: result, dim: true }]);
|
|
24290
|
+
engine.updateStatus();
|
|
24291
24291
|
return;
|
|
24292
24292
|
}
|
|
24293
24293
|
setMessages((p) => [...p, { id: ++nextId, text: `\u276F ${text}` }]);
|
|
24294
24294
|
setLoading(true);
|
|
24295
24295
|
setStreaming("");
|
|
24296
24296
|
try {
|
|
24297
|
-
const response = await
|
|
24297
|
+
const response = await engine.handleAIMessage(text);
|
|
24298
24298
|
if (response) setMessages((p) => [...p, { id: ++nextId, text: response }]);
|
|
24299
24299
|
} catch (err) {
|
|
24300
24300
|
setMessages((p) => [...p, { id: ++nextId, text: `Error: ${err.message}`, dim: true }]);
|
|
24301
24301
|
}
|
|
24302
24302
|
setLoading(false);
|
|
24303
24303
|
setStreaming("");
|
|
24304
|
-
|
|
24304
|
+
engine.updateStatus();
|
|
24305
|
+
}, [engine]);
|
|
24305
24306
|
const isFirst = messages.length === 0;
|
|
24306
24307
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
|
|
24307
24308
|
/* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
@@ -24314,7 +24315,10 @@ function ChatApp({ onMessage, statusText }) {
|
|
|
24314
24315
|
CLI_VERSION
|
|
24315
24316
|
] })
|
|
24316
24317
|
] }) }),
|
|
24317
|
-
/* @__PURE__ */ jsx(Static, { items: messages, children: (msg) => /* @__PURE__ */ jsx(Box, { paddingX: 1, marginBottom: msg.text.startsWith("\u276F") ? 0 : 1, children: msg.
|
|
24318
|
+
/* @__PURE__ */ jsx(Static, { items: messages, children: (msg) => /* @__PURE__ */ jsx(Box, { paddingX: 1, marginBottom: msg.text.startsWith("\u276F") ? 0 : 1, children: msg.text.startsWith("\u276F") ? /* @__PURE__ */ jsxs(Text, { children: [
|
|
24319
|
+
/* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u276F" }),
|
|
24320
|
+
/* @__PURE__ */ jsx(Text, { bold: true, children: msg.text.slice(1) })
|
|
24321
|
+
] }) : msg.dim ? /* @__PURE__ */ jsx(Text, { dimColor: true, wrap: "wrap", children: msg.text }) : /* @__PURE__ */ jsx(Text, { wrap: "wrap", children: msg.text }) }, msg.id) }),
|
|
24318
24322
|
loading ? /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, children: streaming ? /* @__PURE__ */ jsxs(Text, { wrap: "wrap", children: [
|
|
24319
24323
|
streaming,
|
|
24320
24324
|
/* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u258D" })
|
|
@@ -24328,76 +24332,92 @@ function ChatApp({ onMessage, statusText }) {
|
|
|
24328
24332
|
placeholder: isFirst ? "Anything to automate with RobinPath?" : "Ask anything..."
|
|
24329
24333
|
}
|
|
24330
24334
|
),
|
|
24331
|
-
/* @__PURE__ */ jsx(Box, { marginTop: 1, paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children:
|
|
24335
|
+
status ? /* @__PURE__ */ jsx(Box, { marginTop: 1, paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: status }) }) : null
|
|
24332
24336
|
] });
|
|
24333
24337
|
}
|
|
24334
|
-
|
|
24335
|
-
|
|
24336
|
-
|
|
24337
|
-
|
|
24338
|
-
|
|
24339
|
-
|
|
24338
|
+
var ReplEngine = class {
|
|
24339
|
+
config;
|
|
24340
|
+
autoAccept;
|
|
24341
|
+
apiKey;
|
|
24342
|
+
model;
|
|
24343
|
+
sessionId;
|
|
24344
|
+
sessionName;
|
|
24345
|
+
usage;
|
|
24346
|
+
conversationMessages;
|
|
24347
|
+
cliContext;
|
|
24348
|
+
ui = null;
|
|
24349
|
+
constructor(resumeSessionId, opts) {
|
|
24350
|
+
this.config = readAiConfig();
|
|
24351
|
+
this.autoAccept = opts.autoAccept || false;
|
|
24352
|
+
if (opts.devMode) setFlags({ verbose: true });
|
|
24353
|
+
this.apiKey = this.config.apiKey || null;
|
|
24354
|
+
this.model = this.apiKey ? this.config.model || "anthropic/claude-sonnet-4.6" : "robinpath-default";
|
|
24355
|
+
this.sessionId = resumeSessionId || randomUUID4().slice(0, 8);
|
|
24356
|
+
this.sessionName = `session-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
24357
|
+
this.usage = createUsageTracker();
|
|
24358
|
+
this.conversationMessages = [];
|
|
24359
|
+
this.cliContext = {
|
|
24360
|
+
platform: platform7(),
|
|
24361
|
+
shell: getShellConfig().name,
|
|
24362
|
+
cwd: process.cwd(),
|
|
24363
|
+
cliVersion: CLI_VERSION,
|
|
24364
|
+
nativeModules: getNativeModules().map((m) => m.name),
|
|
24365
|
+
installedModules: Object.keys(readModulesManifest())
|
|
24366
|
+
};
|
|
24367
|
+
const mem = buildMemoryContext();
|
|
24368
|
+
if (mem.trim()) {
|
|
24369
|
+
this.conversationMessages.push({ role: "user", content: `[Context] ${mem.trim()}` });
|
|
24370
|
+
this.conversationMessages.push({ role: "assistant", content: "Preferences loaded." });
|
|
24371
|
+
}
|
|
24372
|
+
if (resumeSessionId) {
|
|
24373
|
+
const session = loadSession(resumeSessionId);
|
|
24374
|
+
if (session) {
|
|
24375
|
+
this.sessionName = session.name;
|
|
24376
|
+
for (const msg of session.messages) this.conversationMessages.push(msg);
|
|
24377
|
+
if (session.usage) Object.assign(this.usage, session.usage);
|
|
24378
|
+
}
|
|
24379
|
+
}
|
|
24380
|
+
}
|
|
24381
|
+
resolveProvider(key) {
|
|
24340
24382
|
if (!key) return "gemini";
|
|
24341
24383
|
if (key.startsWith("sk-or-")) return "openrouter";
|
|
24342
24384
|
if (key.startsWith("sk-ant-")) return "anthropic";
|
|
24343
24385
|
if (key.startsWith("sk-")) return "openai";
|
|
24344
|
-
return config.provider || "gemini";
|
|
24345
|
-
};
|
|
24346
|
-
const apiKey = config.apiKey || null;
|
|
24347
|
-
const model = apiKey ? config.model || "anthropic/claude-sonnet-4.6" : "robinpath-default";
|
|
24348
|
-
const modelShort = (m) => m === "robinpath-default" ? "gemini-free" : m.includes("/") ? m.split("/").pop() : m;
|
|
24349
|
-
const cliContext = {
|
|
24350
|
-
platform: platform7(),
|
|
24351
|
-
shell: getShellConfig().name,
|
|
24352
|
-
cwd: process.cwd(),
|
|
24353
|
-
cliVersion: CLI_VERSION,
|
|
24354
|
-
nativeModules: getNativeModules().map((m) => m.name),
|
|
24355
|
-
installedModules: Object.keys(readModulesManifest())
|
|
24356
|
-
};
|
|
24357
|
-
let sessionId = resumeSessionId || randomUUID4().slice(0, 8);
|
|
24358
|
-
let sessionName = `session-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
24359
|
-
const usage = createUsageTracker();
|
|
24360
|
-
const conversationMessages = [];
|
|
24361
|
-
const mem = buildMemoryContext();
|
|
24362
|
-
if (mem.trim()) {
|
|
24363
|
-
conversationMessages.push({ role: "user", content: `[Context] ${mem.trim()}` });
|
|
24364
|
-
conversationMessages.push({ role: "assistant", content: "Preferences loaded." });
|
|
24386
|
+
return this.config.provider || "gemini";
|
|
24365
24387
|
}
|
|
24366
|
-
|
|
24367
|
-
const
|
|
24368
|
-
|
|
24369
|
-
|
|
24370
|
-
|
|
24371
|
-
if (session.usage) Object.assign(usage, session.usage);
|
|
24372
|
-
}
|
|
24388
|
+
updateStatus() {
|
|
24389
|
+
const m = this.model.includes("/") ? this.model.split("/").pop() : this.model;
|
|
24390
|
+
const cost = this.usage.cost > 0 ? ` \xB7 $${this.usage.cost.toFixed(4)}` : "";
|
|
24391
|
+
const tokens = this.usage.totalTokens > 0 ? ` \xB7 ${this.usage.totalTokens.toLocaleString()} tok` : "";
|
|
24392
|
+
this.ui?.setStatus(`${m} \xB7 ${getShellConfig().name} \xB7 ${this.autoAccept ? "auto" : "confirm"}${tokens}${cost}`);
|
|
24373
24393
|
}
|
|
24374
|
-
|
|
24375
|
-
|
|
24376
|
-
|
|
24377
|
-
const tokens = usage.totalTokens > 0 ? ` \xB7 ${usage.totalTokens.toLocaleString()} tok` : "";
|
|
24378
|
-
return `${m} \xB7 ${getShellConfig().name} \xB7 ${autoAccept ? "auto" : "confirm"}${tokens}${cost}`;
|
|
24394
|
+
exit() {
|
|
24395
|
+
if (this.conversationMessages.length > 1) saveSession(this.sessionId, this.sessionName, this.conversationMessages, this.usage);
|
|
24396
|
+
process.exit(0);
|
|
24379
24397
|
}
|
|
24380
|
-
|
|
24381
|
-
|
|
24398
|
+
// ── Slash commands — return display text, not a chat message ──
|
|
24399
|
+
async handleSlashCommand(text) {
|
|
24382
24400
|
if (text === "/" || text === "/help") {
|
|
24383
|
-
return Object.entries(COMMANDS).map(([cmd, desc]) => `${cmd.padEnd(
|
|
24401
|
+
return Object.entries(COMMANDS).map(([cmd, desc]) => `${cmd.padEnd(14)} ${desc}`).join("\n");
|
|
24384
24402
|
}
|
|
24385
24403
|
if (text === "/clear") {
|
|
24386
|
-
conversationMessages.length = 0;
|
|
24387
|
-
return "Conversation cleared.";
|
|
24404
|
+
this.conversationMessages.length = 0;
|
|
24405
|
+
return "\u2713 Conversation cleared.";
|
|
24388
24406
|
}
|
|
24389
|
-
if (text === "/
|
|
24390
|
-
|
|
24391
|
-
|
|
24407
|
+
if (text === "/compact") {
|
|
24408
|
+
if (this.conversationMessages.length > 12) {
|
|
24409
|
+
this.conversationMessages.splice(1, this.conversationMessages.length - 11);
|
|
24410
|
+
}
|
|
24411
|
+
return `\u2713 Trimmed to ${this.conversationMessages.length} messages.`;
|
|
24392
24412
|
}
|
|
24393
24413
|
if (text === "/auto") {
|
|
24394
|
-
autoAccept = !autoAccept;
|
|
24395
|
-
return `Auto-accept: ${autoAccept ? "ON" : "OFF"}`;
|
|
24414
|
+
this.autoAccept = !this.autoAccept;
|
|
24415
|
+
return `Auto-accept: ${this.autoAccept ? "ON \u2014 commands run without asking" : "OFF \u2014 confirm each command"}`;
|
|
24396
24416
|
}
|
|
24397
24417
|
if (text === "/model") {
|
|
24398
24418
|
const hasKey = !!readAiConfig().apiKey;
|
|
24399
24419
|
const models = hasKey ? AI_MODELS : AI_MODELS.filter((m) => !m.requiresKey);
|
|
24400
|
-
const cur = readAiConfig().model || model;
|
|
24420
|
+
const cur = readAiConfig().model || this.model;
|
|
24401
24421
|
return models.map((m, i) => {
|
|
24402
24422
|
const mark = m.id === cur ? " \u2713" : "";
|
|
24403
24423
|
return `${String(i + 1).padStart(2)}. ${m.name.padEnd(22)} ${m.desc}${mark}`;
|
|
@@ -24408,33 +24428,61 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
|
|
|
24408
24428
|
const models = hasKey ? AI_MODELS : AI_MODELS.filter((m) => !m.requiresKey);
|
|
24409
24429
|
const idx = parseInt(text.split(" ")[1], 10) - 1;
|
|
24410
24430
|
if (idx >= 0 && idx < models.length) {
|
|
24411
|
-
config.model = models[idx].id;
|
|
24412
|
-
|
|
24413
|
-
|
|
24431
|
+
this.config.model = models[idx].id;
|
|
24432
|
+
this.model = models[idx].id;
|
|
24433
|
+
writeAiConfig(this.config);
|
|
24434
|
+
return `\u2713 Model: ${models[idx].name}`;
|
|
24414
24435
|
}
|
|
24415
|
-
return "Invalid number.";
|
|
24436
|
+
return "Invalid number. Type /model to see the list.";
|
|
24437
|
+
}
|
|
24438
|
+
if (text === "/usage") {
|
|
24439
|
+
const c = this.usage.cost > 0 ? `$${this.usage.cost.toFixed(4)}` : "$0.00 (free)";
|
|
24440
|
+
return `${this.usage.totalTokens.toLocaleString()} tokens \xB7 ${this.usage.requests} requests \xB7 ${c}`;
|
|
24416
24441
|
}
|
|
24417
24442
|
if (text === "/memory") {
|
|
24418
24443
|
const m = loadMemory();
|
|
24419
|
-
return m.facts.length ? m.facts.map((f, i) => `${i + 1}. ${f}`).join("\n") : "No memories saved.";
|
|
24444
|
+
return m.facts.length ? m.facts.map((f, i) => `${i + 1}. ${f}`).join("\n") : "No memories saved yet.\nUse /remember <fact> to save something.";
|
|
24420
24445
|
}
|
|
24421
24446
|
if (text.startsWith("/remember ")) {
|
|
24422
|
-
|
|
24423
|
-
return "
|
|
24447
|
+
const fact = text.slice(10).trim();
|
|
24448
|
+
if (!fact) return "Usage: /remember <fact>";
|
|
24449
|
+
addMemoryFact(fact);
|
|
24450
|
+
return `\u2713 Remembered: "${fact}"`;
|
|
24424
24451
|
}
|
|
24425
24452
|
if (text.startsWith("/forget ")) {
|
|
24426
|
-
|
|
24427
|
-
|
|
24453
|
+
const idx = parseInt(text.slice(8).trim(), 10) - 1;
|
|
24454
|
+
const removed = removeMemoryFact(idx);
|
|
24455
|
+
return removed ? `\u2713 Forgot: "${removed}"` : "Invalid number. Type /memory to see the list.";
|
|
24428
24456
|
}
|
|
24429
24457
|
if (text === "/save" || text.startsWith("/save ")) {
|
|
24430
|
-
if (text.length > 5) sessionName = text.slice(5).trim();
|
|
24431
|
-
saveSession(sessionId, sessionName, conversationMessages, usage);
|
|
24432
|
-
return
|
|
24458
|
+
if (text.length > 5) this.sessionName = text.slice(5).trim();
|
|
24459
|
+
saveSession(this.sessionId, this.sessionName, this.conversationMessages, this.usage);
|
|
24460
|
+
return `\u2713 Saved: ${this.sessionName} (${this.sessionId})`;
|
|
24433
24461
|
}
|
|
24434
24462
|
if (text === "/sessions") {
|
|
24435
24463
|
const sessions = listSessions();
|
|
24436
24464
|
if (sessions.length === 0) return "No saved sessions.";
|
|
24437
|
-
return sessions.map((s) =>
|
|
24465
|
+
return sessions.map((s) => {
|
|
24466
|
+
const age = Math.round((Date.now() - new Date(s.updated || s.created).getTime()) / 6e4);
|
|
24467
|
+
const ago = age < 60 ? `${age}m` : age < 1440 ? `${Math.floor(age / 60)}h` : `${Math.floor(age / 1440)}d`;
|
|
24468
|
+
return `${s.id} ${s.name.padEnd(20)} ${s.messages} msgs \xB7 ${ago} ago`;
|
|
24469
|
+
}).join("\n") + "\n\nType /resume <id> to resume.";
|
|
24470
|
+
}
|
|
24471
|
+
if (text.startsWith("/resume ")) {
|
|
24472
|
+
const targetId = text.slice(8).trim();
|
|
24473
|
+
const session = loadSession(targetId);
|
|
24474
|
+
if (!session) return `Session '${targetId}' not found.`;
|
|
24475
|
+
this.sessionId = session.id;
|
|
24476
|
+
this.sessionName = session.name;
|
|
24477
|
+
this.conversationMessages.length = 0;
|
|
24478
|
+
for (const msg of session.messages) this.conversationMessages.push(msg);
|
|
24479
|
+
if (session.usage) Object.assign(this.usage, session.usage);
|
|
24480
|
+
return `\u2713 Resumed: ${session.name} (${session.messages.length} msgs)`;
|
|
24481
|
+
}
|
|
24482
|
+
if (text.startsWith("/delete ")) {
|
|
24483
|
+
const targetId = text.slice(8).trim();
|
|
24484
|
+
const deleted = deleteSession(targetId);
|
|
24485
|
+
return deleted ? `\u2713 Deleted session ${targetId}` : `Session '${targetId}' not found.`;
|
|
24438
24486
|
}
|
|
24439
24487
|
if (text === "/shell") {
|
|
24440
24488
|
return getAvailableShells().map((s) => {
|
|
@@ -24444,21 +24492,26 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
|
|
|
24444
24492
|
}
|
|
24445
24493
|
if (text.startsWith("/shell ")) {
|
|
24446
24494
|
setShellOverride(text.slice(7).trim());
|
|
24447
|
-
cliContext.shell = getShellConfig().name;
|
|
24448
|
-
return
|
|
24495
|
+
this.cliContext.shell = getShellConfig().name;
|
|
24496
|
+
return `\u2713 Shell: ${getShellConfig().name}`;
|
|
24449
24497
|
}
|
|
24450
|
-
|
|
24498
|
+
return `Unknown command: ${text}
|
|
24499
|
+
Type / to see available commands.`;
|
|
24500
|
+
}
|
|
24501
|
+
// ── AI message ──
|
|
24502
|
+
async handleAIMessage(text) {
|
|
24503
|
+
const ui = this.ui;
|
|
24451
24504
|
const { expanded } = expandFileRefs(text);
|
|
24452
|
-
conversationMessages.push({ role: "user", content: expanded });
|
|
24453
|
-
await autoCompact(conversationMessages);
|
|
24454
|
-
const activeModel = readAiConfig().model || model;
|
|
24455
|
-
const activeKey = readAiConfig().apiKey || apiKey;
|
|
24456
|
-
const activeProvider = resolveProvider(activeKey);
|
|
24505
|
+
this.conversationMessages.push({ role: "user", content: expanded });
|
|
24506
|
+
await autoCompact(this.conversationMessages);
|
|
24507
|
+
const activeModel = readAiConfig().model || this.model;
|
|
24508
|
+
const activeKey = readAiConfig().apiKey || this.apiKey;
|
|
24509
|
+
const activeProvider = this.resolveProvider(activeKey);
|
|
24457
24510
|
let finalResponse = "";
|
|
24458
24511
|
for (let loop = 0; loop < 15; loop++) {
|
|
24459
24512
|
let fullText = "";
|
|
24460
24513
|
const result = await fetchBrainStream(
|
|
24461
|
-
loop === 0 ? expanded : conversationMessages[conversationMessages.length - 1].content,
|
|
24514
|
+
loop === 0 ? expanded : this.conversationMessages[this.conversationMessages.length - 1].content,
|
|
24462
24515
|
{
|
|
24463
24516
|
onToken: (delta) => {
|
|
24464
24517
|
if (delta === "\x1B[RETRY]") {
|
|
@@ -24470,11 +24523,11 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
|
|
|
24470
24523
|
const clean = fullText.replace(/<memory>[\s\S]*?<\/memory>/g, "").replace(/<cmd>[\s\S]*?<\/cmd>/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
24471
24524
|
ui?.setStreaming(clean);
|
|
24472
24525
|
},
|
|
24473
|
-
conversationHistory: conversationMessages.slice(0, -1),
|
|
24526
|
+
conversationHistory: this.conversationMessages.slice(0, -1),
|
|
24474
24527
|
provider: activeProvider,
|
|
24475
24528
|
model: activeModel,
|
|
24476
24529
|
apiKey: activeKey,
|
|
24477
|
-
cliContext
|
|
24530
|
+
cliContext: this.cliContext
|
|
24478
24531
|
}
|
|
24479
24532
|
);
|
|
24480
24533
|
if (!result) {
|
|
@@ -24492,15 +24545,15 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
|
|
|
24492
24545
|
if (result.usage) {
|
|
24493
24546
|
const pt2 = result.usage.prompt_tokens || 0;
|
|
24494
24547
|
const ct2 = result.usage.completion_tokens || 0;
|
|
24495
|
-
usage.promptTokens += pt2;
|
|
24496
|
-
usage.completionTokens += ct2;
|
|
24497
|
-
usage.totalTokens += pt2 + ct2;
|
|
24498
|
-
usage.requests++;
|
|
24499
|
-
usage.cost += estimateCost(activeModel, pt2, ct2);
|
|
24548
|
+
this.usage.promptTokens += pt2;
|
|
24549
|
+
this.usage.completionTokens += ct2;
|
|
24550
|
+
this.usage.totalTokens += pt2 + ct2;
|
|
24551
|
+
this.usage.requests++;
|
|
24552
|
+
this.usage.cost += estimateCost(activeModel, pt2, ct2);
|
|
24500
24553
|
}
|
|
24501
24554
|
const { cleaned } = extractMemoryTags(stripCommandTags(result.code));
|
|
24502
24555
|
const commands = extractCommands(result.code);
|
|
24503
|
-
if (cleaned) conversationMessages.push({ role: "assistant", content: cleaned });
|
|
24556
|
+
if (cleaned) this.conversationMessages.push({ role: "assistant", content: cleaned });
|
|
24504
24557
|
if (commands.length === 0) {
|
|
24505
24558
|
finalResponse = cleaned || fullText;
|
|
24506
24559
|
break;
|
|
@@ -24518,35 +24571,33 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
|
|
|
24518
24571
|
}
|
|
24519
24572
|
const summary = commands.map((cmd) => `$ ${cmd}
|
|
24520
24573
|
(executed)`).join("\n");
|
|
24521
|
-
conversationMessages.push({ role: "user", content: `[Results]
|
|
24574
|
+
this.conversationMessages.push({ role: "user", content: `[Results]
|
|
24522
24575
|
${summary}` });
|
|
24523
24576
|
ui?.setStreaming("");
|
|
24524
24577
|
finalResponse = "";
|
|
24525
24578
|
}
|
|
24526
|
-
saveSession(sessionId, sessionName, conversationMessages, usage);
|
|
24579
|
+
saveSession(this.sessionId, this.sessionName, this.conversationMessages, this.usage);
|
|
24527
24580
|
return finalResponse;
|
|
24528
24581
|
}
|
|
24529
|
-
|
|
24530
|
-
|
|
24531
|
-
);
|
|
24532
|
-
|
|
24533
|
-
|
|
24534
|
-
process.exit(0);
|
|
24535
|
-
};
|
|
24582
|
+
};
|
|
24583
|
+
async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
|
|
24584
|
+
const engine = new ReplEngine(resumeSessionId, opts);
|
|
24585
|
+
const { waitUntilExit } = render(/* @__PURE__ */ jsx(ChatApp, { engine }));
|
|
24586
|
+
global.__rpExit = () => engine.exit();
|
|
24536
24587
|
if (initialPrompt) {
|
|
24537
24588
|
await new Promise((r) => setTimeout(r, 200));
|
|
24538
|
-
|
|
24539
|
-
ui?.
|
|
24540
|
-
ui?.setLoading(true);
|
|
24589
|
+
engine.ui?.addMessage(`\u276F ${initialPrompt}`);
|
|
24590
|
+
engine.ui?.setLoading(true);
|
|
24541
24591
|
try {
|
|
24542
|
-
const response = await
|
|
24543
|
-
if (response) ui?.addMessage(response);
|
|
24592
|
+
const response = await engine.handleAIMessage(initialPrompt);
|
|
24593
|
+
if (response) engine.ui?.addMessage(response);
|
|
24544
24594
|
} finally {
|
|
24545
|
-
ui?.setLoading(false);
|
|
24595
|
+
engine.ui?.setLoading(false);
|
|
24596
|
+
engine.updateStatus();
|
|
24546
24597
|
}
|
|
24547
24598
|
}
|
|
24548
24599
|
await waitUntilExit();
|
|
24549
|
-
|
|
24600
|
+
engine.exit();
|
|
24550
24601
|
}
|
|
24551
24602
|
|
|
24552
24603
|
// src/commands-modules.ts
|