@robinpath/cli 1.81.0 → 1.82.0

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