@robinpath/cli 1.80.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 +193 -131
- 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,62 +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
|
-
/* @__PURE__ */
|
|
24249
|
-
|
|
24250
|
-
|
|
24251
|
-
|
|
24242
|
+
/* @__PURE__ */ jsxs(Box, { flexDirection: "column", marginX: 1, 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: [
|
|
24245
|
+
"> ",
|
|
24246
|
+
placeholder
|
|
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) })
|
|
24253
|
+
] }),
|
|
24252
24254
|
/* @__PURE__ */ jsx(Box, { marginX: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
|
|
24253
24255
|
/* @__PURE__ */ jsx(Text, { color: "gray", children: "enter" }),
|
|
24254
24256
|
" send ",
|
|
24255
24257
|
/* @__PURE__ */ jsx(Text, { color: "gray", children: "\\" }),
|
|
24256
24258
|
" newline ",
|
|
24257
|
-
/* @__PURE__ */ jsx(Text, { color: "gray", children: "esc" }),
|
|
24258
|
-
" clear ",
|
|
24259
24259
|
/* @__PURE__ */ jsx(Text, { color: "gray", children: "/" }),
|
|
24260
24260
|
" commands ",
|
|
24261
24261
|
/* @__PURE__ */ jsx(Text, { color: "gray", children: "tab" }),
|
|
24262
|
-
" complete"
|
|
24262
|
+
" complete ",
|
|
24263
|
+
/* @__PURE__ */ jsx(Text, { color: "gray", children: "@/" }),
|
|
24264
|
+
" files"
|
|
24263
24265
|
] }) })
|
|
24264
24266
|
] });
|
|
24265
24267
|
}
|
|
24266
|
-
function ChatApp({
|
|
24268
|
+
function ChatApp({ engine }) {
|
|
24267
24269
|
const [messages, setMessages] = useState([]);
|
|
24268
24270
|
const [streaming, setStreaming] = useState("");
|
|
24269
24271
|
const [loading, setLoading] = useState(false);
|
|
24272
|
+
const [status, setStatus] = useState("");
|
|
24270
24273
|
useEffect(() => {
|
|
24271
|
-
|
|
24274
|
+
engine.ui = {
|
|
24272
24275
|
setStreaming,
|
|
24273
24276
|
setLoading,
|
|
24277
|
+
setStatus,
|
|
24274
24278
|
addMessage: (text, dim) => setMessages((p) => [...p, { id: ++nextId, text, dim }])
|
|
24275
24279
|
};
|
|
24280
|
+
engine.updateStatus();
|
|
24276
24281
|
}, []);
|
|
24277
24282
|
const handleSubmit = useCallback(async (text) => {
|
|
24278
24283
|
if (text === "exit" || text === "quit") {
|
|
24279
|
-
|
|
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();
|
|
24280
24291
|
return;
|
|
24281
24292
|
}
|
|
24282
24293
|
setMessages((p) => [...p, { id: ++nextId, text: `\u276F ${text}` }]);
|
|
24283
24294
|
setLoading(true);
|
|
24284
24295
|
setStreaming("");
|
|
24285
24296
|
try {
|
|
24286
|
-
const response = await
|
|
24297
|
+
const response = await engine.handleAIMessage(text);
|
|
24287
24298
|
if (response) setMessages((p) => [...p, { id: ++nextId, text: response }]);
|
|
24288
24299
|
} catch (err) {
|
|
24289
24300
|
setMessages((p) => [...p, { id: ++nextId, text: `Error: ${err.message}`, dim: true }]);
|
|
24290
24301
|
}
|
|
24291
24302
|
setLoading(false);
|
|
24292
24303
|
setStreaming("");
|
|
24293
|
-
|
|
24304
|
+
engine.updateStatus();
|
|
24305
|
+
}, [engine]);
|
|
24294
24306
|
const isFirst = messages.length === 0;
|
|
24295
24307
|
return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
|
|
24296
24308
|
/* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsxs(Text, { children: [
|
|
@@ -24303,7 +24315,10 @@ function ChatApp({ onMessage, statusText }) {
|
|
|
24303
24315
|
CLI_VERSION
|
|
24304
24316
|
] })
|
|
24305
24317
|
] }) }),
|
|
24306
|
-
/* @__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) }),
|
|
24307
24322
|
loading ? /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, children: streaming ? /* @__PURE__ */ jsxs(Text, { wrap: "wrap", children: [
|
|
24308
24323
|
streaming,
|
|
24309
24324
|
/* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u258D" })
|
|
@@ -24317,76 +24332,92 @@ function ChatApp({ onMessage, statusText }) {
|
|
|
24317
24332
|
placeholder: isFirst ? "Anything to automate with RobinPath?" : "Ask anything..."
|
|
24318
24333
|
}
|
|
24319
24334
|
),
|
|
24320
|
-
/* @__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
|
|
24321
24336
|
] });
|
|
24322
24337
|
}
|
|
24323
|
-
|
|
24324
|
-
|
|
24325
|
-
|
|
24326
|
-
|
|
24327
|
-
|
|
24328
|
-
|
|
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) {
|
|
24329
24382
|
if (!key) return "gemini";
|
|
24330
24383
|
if (key.startsWith("sk-or-")) return "openrouter";
|
|
24331
24384
|
if (key.startsWith("sk-ant-")) return "anthropic";
|
|
24332
24385
|
if (key.startsWith("sk-")) return "openai";
|
|
24333
|
-
return config.provider || "gemini";
|
|
24334
|
-
};
|
|
24335
|
-
const apiKey = config.apiKey || null;
|
|
24336
|
-
const model = apiKey ? config.model || "anthropic/claude-sonnet-4.6" : "robinpath-default";
|
|
24337
|
-
const modelShort = (m) => m === "robinpath-default" ? "gemini-free" : m.includes("/") ? m.split("/").pop() : m;
|
|
24338
|
-
const cliContext = {
|
|
24339
|
-
platform: platform7(),
|
|
24340
|
-
shell: getShellConfig().name,
|
|
24341
|
-
cwd: process.cwd(),
|
|
24342
|
-
cliVersion: CLI_VERSION,
|
|
24343
|
-
nativeModules: getNativeModules().map((m) => m.name),
|
|
24344
|
-
installedModules: Object.keys(readModulesManifest())
|
|
24345
|
-
};
|
|
24346
|
-
let sessionId = resumeSessionId || randomUUID4().slice(0, 8);
|
|
24347
|
-
let sessionName = `session-${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}`;
|
|
24348
|
-
const usage = createUsageTracker();
|
|
24349
|
-
const conversationMessages = [];
|
|
24350
|
-
const mem = buildMemoryContext();
|
|
24351
|
-
if (mem.trim()) {
|
|
24352
|
-
conversationMessages.push({ role: "user", content: `[Context] ${mem.trim()}` });
|
|
24353
|
-
conversationMessages.push({ role: "assistant", content: "Preferences loaded." });
|
|
24386
|
+
return this.config.provider || "gemini";
|
|
24354
24387
|
}
|
|
24355
|
-
|
|
24356
|
-
const
|
|
24357
|
-
|
|
24358
|
-
|
|
24359
|
-
|
|
24360
|
-
if (session.usage) Object.assign(usage, session.usage);
|
|
24361
|
-
}
|
|
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}`);
|
|
24362
24393
|
}
|
|
24363
|
-
|
|
24364
|
-
|
|
24365
|
-
|
|
24366
|
-
const tokens = usage.totalTokens > 0 ? ` \xB7 ${usage.totalTokens.toLocaleString()} tok` : "";
|
|
24367
|
-
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);
|
|
24368
24397
|
}
|
|
24369
|
-
|
|
24370
|
-
|
|
24398
|
+
// ── Slash commands — return display text, not a chat message ──
|
|
24399
|
+
async handleSlashCommand(text) {
|
|
24371
24400
|
if (text === "/" || text === "/help") {
|
|
24372
|
-
return Object.entries(COMMANDS).map(([cmd, desc]) => `${cmd.padEnd(
|
|
24401
|
+
return Object.entries(COMMANDS).map(([cmd, desc]) => `${cmd.padEnd(14)} ${desc}`).join("\n");
|
|
24373
24402
|
}
|
|
24374
24403
|
if (text === "/clear") {
|
|
24375
|
-
conversationMessages.length = 0;
|
|
24376
|
-
return "Conversation cleared.";
|
|
24404
|
+
this.conversationMessages.length = 0;
|
|
24405
|
+
return "\u2713 Conversation cleared.";
|
|
24377
24406
|
}
|
|
24378
|
-
if (text === "/
|
|
24379
|
-
|
|
24380
|
-
|
|
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.`;
|
|
24381
24412
|
}
|
|
24382
24413
|
if (text === "/auto") {
|
|
24383
|
-
autoAccept = !autoAccept;
|
|
24384
|
-
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"}`;
|
|
24385
24416
|
}
|
|
24386
24417
|
if (text === "/model") {
|
|
24387
24418
|
const hasKey = !!readAiConfig().apiKey;
|
|
24388
24419
|
const models = hasKey ? AI_MODELS : AI_MODELS.filter((m) => !m.requiresKey);
|
|
24389
|
-
const cur = readAiConfig().model || model;
|
|
24420
|
+
const cur = readAiConfig().model || this.model;
|
|
24390
24421
|
return models.map((m, i) => {
|
|
24391
24422
|
const mark = m.id === cur ? " \u2713" : "";
|
|
24392
24423
|
return `${String(i + 1).padStart(2)}. ${m.name.padEnd(22)} ${m.desc}${mark}`;
|
|
@@ -24397,33 +24428,61 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
|
|
|
24397
24428
|
const models = hasKey ? AI_MODELS : AI_MODELS.filter((m) => !m.requiresKey);
|
|
24398
24429
|
const idx = parseInt(text.split(" ")[1], 10) - 1;
|
|
24399
24430
|
if (idx >= 0 && idx < models.length) {
|
|
24400
|
-
config.model = models[idx].id;
|
|
24401
|
-
|
|
24402
|
-
|
|
24431
|
+
this.config.model = models[idx].id;
|
|
24432
|
+
this.model = models[idx].id;
|
|
24433
|
+
writeAiConfig(this.config);
|
|
24434
|
+
return `\u2713 Model: ${models[idx].name}`;
|
|
24403
24435
|
}
|
|
24404
|
-
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}`;
|
|
24405
24441
|
}
|
|
24406
24442
|
if (text === "/memory") {
|
|
24407
24443
|
const m = loadMemory();
|
|
24408
|
-
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.";
|
|
24409
24445
|
}
|
|
24410
24446
|
if (text.startsWith("/remember ")) {
|
|
24411
|
-
|
|
24412
|
-
return "
|
|
24447
|
+
const fact = text.slice(10).trim();
|
|
24448
|
+
if (!fact) return "Usage: /remember <fact>";
|
|
24449
|
+
addMemoryFact(fact);
|
|
24450
|
+
return `\u2713 Remembered: "${fact}"`;
|
|
24413
24451
|
}
|
|
24414
24452
|
if (text.startsWith("/forget ")) {
|
|
24415
|
-
|
|
24416
|
-
|
|
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.";
|
|
24417
24456
|
}
|
|
24418
24457
|
if (text === "/save" || text.startsWith("/save ")) {
|
|
24419
|
-
if (text.length > 5) sessionName = text.slice(5).trim();
|
|
24420
|
-
saveSession(sessionId, sessionName, conversationMessages, usage);
|
|
24421
|
-
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})`;
|
|
24422
24461
|
}
|
|
24423
24462
|
if (text === "/sessions") {
|
|
24424
24463
|
const sessions = listSessions();
|
|
24425
24464
|
if (sessions.length === 0) return "No saved sessions.";
|
|
24426
|
-
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.`;
|
|
24427
24486
|
}
|
|
24428
24487
|
if (text === "/shell") {
|
|
24429
24488
|
return getAvailableShells().map((s) => {
|
|
@@ -24433,21 +24492,26 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
|
|
|
24433
24492
|
}
|
|
24434
24493
|
if (text.startsWith("/shell ")) {
|
|
24435
24494
|
setShellOverride(text.slice(7).trim());
|
|
24436
|
-
cliContext.shell = getShellConfig().name;
|
|
24437
|
-
return
|
|
24495
|
+
this.cliContext.shell = getShellConfig().name;
|
|
24496
|
+
return `\u2713 Shell: ${getShellConfig().name}`;
|
|
24438
24497
|
}
|
|
24439
|
-
|
|
24498
|
+
return `Unknown command: ${text}
|
|
24499
|
+
Type / to see available commands.`;
|
|
24500
|
+
}
|
|
24501
|
+
// ── AI message ──
|
|
24502
|
+
async handleAIMessage(text) {
|
|
24503
|
+
const ui = this.ui;
|
|
24440
24504
|
const { expanded } = expandFileRefs(text);
|
|
24441
|
-
conversationMessages.push({ role: "user", content: expanded });
|
|
24442
|
-
await autoCompact(conversationMessages);
|
|
24443
|
-
const activeModel = readAiConfig().model || model;
|
|
24444
|
-
const activeKey = readAiConfig().apiKey || apiKey;
|
|
24445
|
-
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);
|
|
24446
24510
|
let finalResponse = "";
|
|
24447
24511
|
for (let loop = 0; loop < 15; loop++) {
|
|
24448
24512
|
let fullText = "";
|
|
24449
24513
|
const result = await fetchBrainStream(
|
|
24450
|
-
loop === 0 ? expanded : conversationMessages[conversationMessages.length - 1].content,
|
|
24514
|
+
loop === 0 ? expanded : this.conversationMessages[this.conversationMessages.length - 1].content,
|
|
24451
24515
|
{
|
|
24452
24516
|
onToken: (delta) => {
|
|
24453
24517
|
if (delta === "\x1B[RETRY]") {
|
|
@@ -24459,11 +24523,11 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
|
|
|
24459
24523
|
const clean = fullText.replace(/<memory>[\s\S]*?<\/memory>/g, "").replace(/<cmd>[\s\S]*?<\/cmd>/g, "").replace(/\n{3,}/g, "\n\n").trim();
|
|
24460
24524
|
ui?.setStreaming(clean);
|
|
24461
24525
|
},
|
|
24462
|
-
conversationHistory: conversationMessages.slice(0, -1),
|
|
24526
|
+
conversationHistory: this.conversationMessages.slice(0, -1),
|
|
24463
24527
|
provider: activeProvider,
|
|
24464
24528
|
model: activeModel,
|
|
24465
24529
|
apiKey: activeKey,
|
|
24466
|
-
cliContext
|
|
24530
|
+
cliContext: this.cliContext
|
|
24467
24531
|
}
|
|
24468
24532
|
);
|
|
24469
24533
|
if (!result) {
|
|
@@ -24481,15 +24545,15 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
|
|
|
24481
24545
|
if (result.usage) {
|
|
24482
24546
|
const pt2 = result.usage.prompt_tokens || 0;
|
|
24483
24547
|
const ct2 = result.usage.completion_tokens || 0;
|
|
24484
|
-
usage.promptTokens += pt2;
|
|
24485
|
-
usage.completionTokens += ct2;
|
|
24486
|
-
usage.totalTokens += pt2 + ct2;
|
|
24487
|
-
usage.requests++;
|
|
24488
|
-
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);
|
|
24489
24553
|
}
|
|
24490
24554
|
const { cleaned } = extractMemoryTags(stripCommandTags(result.code));
|
|
24491
24555
|
const commands = extractCommands(result.code);
|
|
24492
|
-
if (cleaned) conversationMessages.push({ role: "assistant", content: cleaned });
|
|
24556
|
+
if (cleaned) this.conversationMessages.push({ role: "assistant", content: cleaned });
|
|
24493
24557
|
if (commands.length === 0) {
|
|
24494
24558
|
finalResponse = cleaned || fullText;
|
|
24495
24559
|
break;
|
|
@@ -24507,35 +24571,33 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
|
|
|
24507
24571
|
}
|
|
24508
24572
|
const summary = commands.map((cmd) => `$ ${cmd}
|
|
24509
24573
|
(executed)`).join("\n");
|
|
24510
|
-
conversationMessages.push({ role: "user", content: `[Results]
|
|
24574
|
+
this.conversationMessages.push({ role: "user", content: `[Results]
|
|
24511
24575
|
${summary}` });
|
|
24512
24576
|
ui?.setStreaming("");
|
|
24513
24577
|
finalResponse = "";
|
|
24514
24578
|
}
|
|
24515
|
-
saveSession(sessionId, sessionName, conversationMessages, usage);
|
|
24579
|
+
saveSession(this.sessionId, this.sessionName, this.conversationMessages, this.usage);
|
|
24516
24580
|
return finalResponse;
|
|
24517
24581
|
}
|
|
24518
|
-
|
|
24519
|
-
|
|
24520
|
-
);
|
|
24521
|
-
|
|
24522
|
-
|
|
24523
|
-
process.exit(0);
|
|
24524
|
-
};
|
|
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();
|
|
24525
24587
|
if (initialPrompt) {
|
|
24526
24588
|
await new Promise((r) => setTimeout(r, 200));
|
|
24527
|
-
|
|
24528
|
-
ui?.
|
|
24529
|
-
ui?.setLoading(true);
|
|
24589
|
+
engine.ui?.addMessage(`\u276F ${initialPrompt}`);
|
|
24590
|
+
engine.ui?.setLoading(true);
|
|
24530
24591
|
try {
|
|
24531
|
-
const response = await
|
|
24532
|
-
if (response) ui?.addMessage(response);
|
|
24592
|
+
const response = await engine.handleAIMessage(initialPrompt);
|
|
24593
|
+
if (response) engine.ui?.addMessage(response);
|
|
24533
24594
|
} finally {
|
|
24534
|
-
ui?.setLoading(false);
|
|
24595
|
+
engine.ui?.setLoading(false);
|
|
24596
|
+
engine.updateStatus();
|
|
24535
24597
|
}
|
|
24536
24598
|
}
|
|
24537
24599
|
await waitUntilExit();
|
|
24538
|
-
|
|
24600
|
+
engine.exit();
|
|
24539
24601
|
}
|
|
24540
24602
|
|
|
24541
24603
|
// src/commands-modules.ts
|