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