@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.
Files changed (2) hide show
  1. package/dist/cli.mjs +344 -301
  2. 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.73.0" : "1.73.0";
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 as Box3, Text as Text3, useInput } from "ink";
24180
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
24181
- function InputBox({ placeholder = "Ask anything...", onSubmit, isActive = true }) {
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((prev) => prev.slice(0, -1) + "\n");
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((prev) => prev + "\n");
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 === "") process.exit(0);
24207
- setValue("");
24167
+ if (!value) {
24168
+ exit();
24169
+ onExit();
24170
+ } else setValue("");
24208
24171
  return;
24209
24172
  }
24210
24173
  if (key.backspace || key.delete) {
24211
- setValue((prev) => prev.slice(0, -1));
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((prev) => prev.replace(/\S+\s*$/, ""));
24183
+ setValue((p) => p.replace(/\S+\s*$/, ""));
24221
24184
  return;
24222
24185
  }
24223
- if (input && !key.ctrl && !key.meta) {
24224
- setValue((prev) => prev + input);
24225
- }
24226
- }, { isActive });
24186
+ if (input && !key.ctrl && !key.meta) setValue((p) => p + input);
24187
+ });
24227
24188
  const lines = value.split("\n");
24228
- const isEmpty = value === "";
24229
- const cols = process.stdout.columns || 80;
24230
- const innerWidth = Math.min(cols - 8, 74);
24231
- return /* @__PURE__ */ jsxs3(Box3, { flexDirection: "column", marginTop: 1, children: [
24232
- /* @__PURE__ */ jsx3(
24233
- Box3,
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
- // src/ui/ChatMessage.tsx
24263
- import { Box as Box4, Text as Text4 } from "ink";
24264
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
24265
- function ChatMessage({ role, content, isStreaming = false }) {
24266
- if (role === "system") return null;
24267
- if (role === "user") {
24268
- return /* @__PURE__ */ jsx4(Box4, { paddingX: 2, marginBottom: 0, children: /* @__PURE__ */ jsxs4(Text4, { children: [
24269
- /* @__PURE__ */ jsx4(Text4, { color: "cyan", bold: true, children: "\u276F " }),
24270
- /* @__PURE__ */ jsx4(Text4, { bold: true, children: content })
24271
- ] }) });
24272
- }
24273
- return /* @__PURE__ */ jsx4(Box4, { flexDirection: "column", paddingX: 2, paddingLeft: 4, marginBottom: 1, children: /* @__PURE__ */ jsxs4(Text4, { wrap: "wrap", children: [
24274
- content,
24275
- isStreaming ? /* @__PURE__ */ jsx4(Text4, { color: "cyan", children: "\u258D" }) : null
24276
- ] }) });
24277
- }
24278
-
24279
- // src/ui/Spinner.tsx
24280
- import { Box as Box5, Text as Text5 } from "ink";
24281
- import InkSpinner from "ink-spinner";
24282
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
24283
- function Spinner({ label = "Thinking" }) {
24284
- return /* @__PURE__ */ jsx5(Box5, { paddingX: 2, paddingLeft: 4, marginBottom: 1, children: /* @__PURE__ */ jsxs5(Text5, { dimColor: true, children: [
24285
- /* @__PURE__ */ jsx5(InkSpinner, { type: "dots" }),
24286
- " ",
24287
- label
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
- let projectInfo = "";
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 cwd = process.cwd();
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 entry of entries.slice(0, 100)) {
24292
+ for (const e of entries.slice(0, 100)) {
24411
24293
  try {
24412
- const s = statSync6(join12(cwd, entry));
24413
- if (s.isDirectory() && !["node_modules", "__pycache__", "dist", "build"].includes(entry)) dirCount++;
24414
- else if (entry.endsWith(".rp") || entry.endsWith(".robin")) rpCount++;
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
- const modeStr = devMode ? "dev (auto+verbose)" : autoAccept ? "auto" : "confirm";
24424
- const cwdDisplay = process.cwd().replace(homedir8(), "~");
24425
- const cwdShort = cwdDisplay.length > 40 ? "..." + cwdDisplay.slice(-37) : cwdDisplay;
24426
- async function handleMessage(text) {
24427
- const ui = global.__rpUI;
24428
- if (text === "/help") {
24429
- return "Commands: /model, /shell, /auto, /clear, /save, /sessions, /resume, /memory, /usage, /scan, /help, exit";
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 (text === "exit" || text === "quit") {
24432
- if (conversationMessages.length > 1) {
24433
- saveSession(sessionId, sessionName, conversationMessages, usage);
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
- process.exit(0);
24340
+ log("");
24341
+ continue;
24436
24342
  }
24437
- if (text === "/model") {
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
- return models.map((m, i) => `${i + 1}. ${m.name} \u2014 ${m.desc}`).join("\n");
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 (text === "/usage") {
24443
- const cost = usage.cost > 0 ? `$${usage.cost.toFixed(4)}` : "$0.00 (free)";
24444
- return `Tokens: ${usage.totalTokens.toLocaleString()} | Requests: ${usage.requests} | Cost: ${cost}`;
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 (text === "/clear") {
24447
- conversationMessages.length = 0;
24448
- return "Conversation cleared.";
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 (text.startsWith("/")) {
24451
- return `Unknown command: ${text}. Type /help for commands.`;
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(text);
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 fullResponse = "";
24460
- for (let loopCount = 0; loopCount < 15; loopCount++) {
24461
- if (ui?.setSpinnerLabel) ui.setSpinnerLabel(loopCount === 0 ? "Thinking..." : "Processing...");
24462
- const brainResult = await fetchBrainStream(
24463
- loopCount === 0 ? expanded : conversationMessages[conversationMessages.length - 1].content,
24464
- {
24465
- onToken: (delta) => {
24466
- if (delta === "\x1B[RETRY]") {
24467
- fullResponse = "";
24468
- if (ui?.setStreamText) ui.setStreamText("");
24469
- return;
24470
- }
24471
- fullResponse += delta;
24472
- const clean = fullResponse.replace(/<memory>[\s\S]*?<\/memory>/g, "").replace(/<cmd>[\s\S]*?<\/cmd>/g, "").replace(/\n{3,}/g, "\n\n");
24473
- if (ui?.setStreamText) ui.setStreamText(clean);
24474
- },
24475
- conversationHistory: conversationMessages.slice(0, -1),
24476
- provider: activeProvider,
24477
- model: activeModel,
24478
- apiKey: activeKey,
24479
- cliContext
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
- if (!brainResult || !brainResult.code) {
24483
- return fullResponse || "Brain returned no response. Check your connection or API key.";
24484
- }
24485
- if (brainResult.usage) {
24486
- const pt2 = brainResult.usage.prompt_tokens || 0;
24487
- const ct2 = brainResult.usage.completion_tokens || 0;
24488
- usage.promptTokens += pt2;
24489
- usage.completionTokens += ct2;
24490
- usage.totalTokens += pt2 + ct2;
24491
- usage.requests++;
24492
- usage.cost += estimateCost(activeModel, pt2, ct2);
24493
- if (ui?.setTokens) ui.setTokens(usage.totalTokens);
24494
- if (ui?.setCost) ui.setCost(usage.cost);
24495
- }
24496
- const commands = extractCommands(brainResult.code);
24497
- const { cleaned } = extractMemoryTags(stripCommandTags(brainResult.code));
24498
- if (cleaned) {
24499
- conversationMessages.push({ role: "assistant", content: cleaned });
24500
- fullResponse = cleaned;
24501
- }
24502
- if (commands.length === 0) break;
24503
- const cmdResults = [];
24504
- for (const cmd of commands) {
24505
- const result = await executeShellCommand(cmd);
24506
- cmdResults.push({
24507
- command: cmd,
24508
- stdout: result.stdout || "",
24509
- stderr: result.stderr || "",
24510
- exitCode: result.exitCode
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
- if (r.exitCode === 0) out += r.stdout || "(no output)";
24517
- else {
24518
- out += `Exit code: ${r.exitCode}
24547
+ if (r.exitCode === 0) o += r.stdout || "(no output)";
24548
+ else {
24549
+ o += `Exit: ${r.exitCode}
24519
24550
  `;
24520
- if (r.stderr) out += `stderr: ${r.stderr}`;
24521
- }
24522
- return out;
24523
- }).join("\n\n");
24524
- conversationMessages.push({ role: "user", content: `[Command results]
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
- fullResponse = "";
24527
- if (ui?.setStreamText) ui.setStreamText("");
24557
+ spinner = createSpinner("Processing");
24558
+ }
24559
+ } catch (err) {
24560
+ spinner.stop();
24561
+ log(color.red(` Error: ${err.message}`));
24528
24562
  }
24529
- saveSession(sessionId, sessionName, conversationMessages, usage);
24530
- return null;
24563
+ log("");
24564
+ if (usage.cost > 0) log(color.dim(` $${usage.cost.toFixed(4)} \xB7 ${usage.totalTokens.toLocaleString()} tokens`));
24531
24565
  }
24532
- const { waitUntilExit } = render(
24533
- /* @__PURE__ */ jsx7(
24534
- App,
24535
- {
24536
- version: CLI_VERSION,
24537
- model: modelShort,
24538
- mode: modeStr,
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";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robinpath/cli",
3
- "version": "1.73.0",
3
+ "version": "1.74.0",
4
4
  "description": "AI-powered scripting CLI — automate anything from your terminal",
5
5
  "type": "module",
6
6
  "license": "MIT",