@robinpath/cli 1.79.0 → 1.81.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 +202 -74
  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.79.0" : "1.79.0";
18601
+ var CLI_VERSION = true ? "1.81.0" : "1.81.0";
18602
18602
  var FLAG_QUIET = false;
18603
18603
  var FLAG_VERBOSE = false;
18604
18604
  var FLAG_AUTO_ACCEPT = false;
@@ -24155,72 +24155,156 @@ ${resultSummary}`
24155
24155
  }
24156
24156
 
24157
24157
  // src/ink-repl.tsx
24158
- import { useState, useEffect } from "react";
24158
+ import { useState, useCallback, useEffect, useMemo } from "react";
24159
24159
  import { render, Box, Text, Static, useInput, useApp } from "ink";
24160
24160
  import InkSpinner from "ink-spinner";
24161
24161
  import { platform as platform7 } from "node:os";
24162
24162
  import { randomUUID as randomUUID4 } from "node:crypto";
24163
24163
  import { jsx, jsxs } from "react/jsx-runtime";
24164
24164
  var nextId = 0;
24165
- function ChatApp({ onMessage }) {
24166
- const [input, setInput] = useState("");
24167
- const [messages, setMessages] = useState([]);
24168
- const [streaming, setStreaming] = useState("");
24169
- const [loading, setLoading] = useState(false);
24165
+ var COMMANDS = {
24166
+ "/model": "Switch AI model",
24167
+ "/auto": "Toggle auto-accept",
24168
+ "/clear": "Clear conversation",
24169
+ "/save": "Save session",
24170
+ "/sessions": "List sessions",
24171
+ "/memory": "Show memory",
24172
+ "/remember": "Save a fact",
24173
+ "/usage": "Token usage & cost",
24174
+ "/shell": "Switch shell",
24175
+ "/help": "Show commands"
24176
+ };
24177
+ function InputArea({ onSubmit, placeholder }) {
24178
+ const [value, setValue] = useState("");
24179
+ const [showHints, setShowHints] = useState(false);
24170
24180
  const { exit } = useApp();
24171
- useEffect(() => {
24172
- global.__rpUI = {
24173
- setStreaming,
24174
- setLoading,
24175
- addMessage: (text) => setMessages((prev) => [...prev, { id: ++nextId, text }])
24176
- };
24177
- }, []);
24178
- useInput((character, key) => {
24179
- if (loading) return;
24181
+ const matchingCommands = useMemo(() => {
24182
+ if (!value.startsWith("/")) return [];
24183
+ if (value === "/") return Object.entries(COMMANDS);
24184
+ return Object.entries(COMMANDS).filter(([cmd]) => cmd.startsWith(value));
24185
+ }, [value]);
24186
+ useInput((ch, key) => {
24180
24187
  if (key.return) {
24181
- if (!input.trim()) return;
24182
- if (input.endsWith("\\")) {
24183
- setInput((prev) => prev.slice(0, -1) + "\n");
24188
+ if (value.endsWith("\\")) {
24189
+ setValue((p) => p.slice(0, -1) + "\n");
24184
24190
  return;
24185
24191
  }
24186
- const text = input.trim();
24187
- setInput("");
24188
- if (text === "exit" || text === "quit") {
24189
- exit();
24190
- return;
24192
+ const text = value.trim();
24193
+ if (text) {
24194
+ onSubmit(text);
24195
+ setValue("");
24196
+ setShowHints(false);
24191
24197
  }
24192
- setMessages((prev) => [...prev, { id: ++nextId, text: `\u276F ${text}` }]);
24193
- setLoading(true);
24194
- setStreaming("");
24195
- onMessage(text).then((response) => {
24196
- if (response) {
24197
- setMessages((prev) => [...prev, { id: ++nextId, text: response }]);
24198
- }
24199
- setLoading(false);
24200
- setStreaming("");
24201
- }).catch((err) => {
24202
- setMessages((prev) => [...prev, { id: ++nextId, text: `Error: ${err.message}` }]);
24203
- setLoading(false);
24204
- setStreaming("");
24205
- });
24206
24198
  return;
24207
24199
  }
24208
- if (input.length > 0 && (key.backspace || key.delete)) {
24209
- setInput((prev) => prev.slice(0, -1));
24200
+ if (ch === "\n") {
24201
+ setValue((p) => p + "\n");
24210
24202
  return;
24211
24203
  }
24212
24204
  if (key.escape) {
24213
- setInput("");
24205
+ setValue("");
24206
+ setShowHints(false);
24214
24207
  return;
24215
24208
  }
24216
- if (key.tab) return;
24217
- if (character && !key.ctrl && !key.meta) {
24218
- setInput((prev) => prev + character);
24209
+ if (ch === "") {
24210
+ if (!value) exit();
24211
+ else {
24212
+ setValue("");
24213
+ setShowHints(false);
24214
+ }
24215
+ return;
24219
24216
  }
24217
+ if (key.backspace || key.delete) {
24218
+ setValue((p) => p.slice(0, -1));
24219
+ return;
24220
+ }
24221
+ if (key.tab) {
24222
+ if (matchingCommands.length === 1) {
24223
+ setValue(matchingCommands[0][0] + " ");
24224
+ setShowHints(false);
24225
+ }
24226
+ return;
24227
+ }
24228
+ if (ch === "") {
24229
+ setValue("");
24230
+ return;
24231
+ }
24232
+ if (ch === "") {
24233
+ setValue((p) => p.replace(/\S+\s*$/, ""));
24234
+ return;
24235
+ }
24236
+ if (ch && !key.ctrl && !key.meta) setValue((p) => p + ch);
24220
24237
  });
24221
- const lines = input.split("\n");
24238
+ useEffect(() => {
24239
+ setShowHints(value.startsWith("/") && matchingCommands.length > 0);
24240
+ }, [value, matchingCommands.length]);
24241
+ const lines = value.split("\n");
24242
+ const empty = value === "";
24243
+ 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) }),
24246
+ /* @__PURE__ */ jsx(Text, { dimColor: true, children: desc })
24247
+ ] }, cmd)) }),
24248
+ /* @__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: [
24251
+ "> ",
24252
+ 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)) })
24262
+ ] }),
24263
+ /* @__PURE__ */ jsx(Box, { marginX: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
24264
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "enter" }),
24265
+ " send ",
24266
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "\\" }),
24267
+ " newline ",
24268
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "esc" }),
24269
+ " clear ",
24270
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "/" }),
24271
+ " commands ",
24272
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "tab" }),
24273
+ " complete"
24274
+ ] }) })
24275
+ ] });
24276
+ }
24277
+ function ChatApp({ onMessage, statusText }) {
24278
+ const [messages, setMessages] = useState([]);
24279
+ const [streaming, setStreaming] = useState("");
24280
+ const [loading, setLoading] = useState(false);
24281
+ useEffect(() => {
24282
+ global.__rpUI = {
24283
+ setStreaming,
24284
+ setLoading,
24285
+ addMessage: (text, dim) => setMessages((p) => [...p, { id: ++nextId, text, dim }])
24286
+ };
24287
+ }, []);
24288
+ const handleSubmit = useCallback(async (text) => {
24289
+ if (text === "exit" || text === "quit") {
24290
+ global.__rpExit?.();
24291
+ return;
24292
+ }
24293
+ setMessages((p) => [...p, { id: ++nextId, text: `\u276F ${text}` }]);
24294
+ setLoading(true);
24295
+ setStreaming("");
24296
+ try {
24297
+ const response = await onMessage(text);
24298
+ if (response) setMessages((p) => [...p, { id: ++nextId, text: response }]);
24299
+ } catch (err) {
24300
+ setMessages((p) => [...p, { id: ++nextId, text: `Error: ${err.message}`, dim: true }]);
24301
+ }
24302
+ setLoading(false);
24303
+ setStreaming("");
24304
+ }, [onMessage]);
24305
+ const isFirst = messages.length === 0;
24222
24306
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
24223
- /* @__PURE__ */ jsxs(Text, { children: [
24307
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsxs(Text, { children: [
24224
24308
  /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u25C6" }),
24225
24309
  " ",
24226
24310
  /* @__PURE__ */ jsx(Text, { bold: true, children: "RobinPath" }),
@@ -24229,21 +24313,22 @@ function ChatApp({ onMessage }) {
24229
24313
  "v",
24230
24314
  CLI_VERSION
24231
24315
  ] })
24232
- ] }),
24233
- /* @__PURE__ */ jsx(Text, { dimColor: true, children: " " }),
24234
- /* @__PURE__ */ jsx(Static, { items: messages, children: (msg) => /* @__PURE__ */ jsx(Text, { wrap: "wrap", children: msg.text }, msg.id) }),
24235
- loading && streaming ? /* @__PURE__ */ jsx(Text, { wrap: "wrap", children: streaming }) : null,
24236
- loading && !streaming ? /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
24316
+ ] }) }),
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
+ loading ? /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, children: streaming ? /* @__PURE__ */ jsxs(Text, { wrap: "wrap", children: [
24319
+ streaming,
24320
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u258D" })
24321
+ ] }) : /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
24237
24322
  /* @__PURE__ */ jsx(InkSpinner, { type: "dots" }),
24238
24323
  " Thinking"
24239
- ] }) : null,
24240
- !loading ? /* @__PURE__ */ jsx(Box, { marginTop: 1, children: /* @__PURE__ */ jsxs(Text, { children: [
24241
- /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u276F " }),
24242
- input === "" ? /* @__PURE__ */ jsx(Text, { dimColor: true, children: messages.length === 0 ? "What do you want to automate?" : "Ask anything..." }) : /* @__PURE__ */ jsxs(Text, { children: [
24243
- input,
24244
- /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u258E" })
24245
- ] })
24246
- ] }) }) : null
24324
+ ] }) }) : /* @__PURE__ */ jsx(
24325
+ InputArea,
24326
+ {
24327
+ onSubmit: handleSubmit,
24328
+ placeholder: isFirst ? "Anything to automate with RobinPath?" : "Ask anything..."
24329
+ }
24330
+ ),
24331
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: statusText }) })
24247
24332
  ] });
24248
24333
  }
24249
24334
  async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
@@ -24260,6 +24345,7 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
24260
24345
  };
24261
24346
  const apiKey = config.apiKey || null;
24262
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;
24263
24349
  const cliContext = {
24264
24350
  platform: platform7(),
24265
24351
  shell: getShellConfig().name,
@@ -24285,15 +24371,23 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
24285
24371
  if (session.usage) Object.assign(usage, session.usage);
24286
24372
  }
24287
24373
  }
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}`;
24379
+ }
24288
24380
  async function handleMessage(text) {
24289
24381
  const ui = global.__rpUI;
24290
- if (text === "/" || text === "/help") return "/model /auto /clear /save /usage /memory exit";
24382
+ if (text === "/" || text === "/help") {
24383
+ return Object.entries(COMMANDS).map(([cmd, desc]) => `${cmd.padEnd(12)} ${desc}`).join("\n");
24384
+ }
24291
24385
  if (text === "/clear") {
24292
24386
  conversationMessages.length = 0;
24293
- return "Cleared.";
24387
+ return "Conversation cleared.";
24294
24388
  }
24295
24389
  if (text === "/usage") {
24296
- const c = usage.cost > 0 ? `$${usage.cost.toFixed(4)}` : "$0 (free)";
24390
+ const c = usage.cost > 0 ? `$${usage.cost.toFixed(4)}` : "$0.00 (free)";
24297
24391
  return `${usage.totalTokens.toLocaleString()} tokens \xB7 ${usage.requests} requests \xB7 ${c}`;
24298
24392
  }
24299
24393
  if (text === "/auto") {
@@ -24303,7 +24397,11 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
24303
24397
  if (text === "/model") {
24304
24398
  const hasKey = !!readAiConfig().apiKey;
24305
24399
  const models = hasKey ? AI_MODELS : AI_MODELS.filter((m) => !m.requiresKey);
24306
- return models.map((m, i) => `${i + 1}. ${m.name} \u2014 ${m.desc}`).join("\n");
24400
+ const cur = readAiConfig().model || model;
24401
+ return models.map((m, i) => {
24402
+ const mark = m.id === cur ? " \u2713" : "";
24403
+ return `${String(i + 1).padStart(2)}. ${m.name.padEnd(22)} ${m.desc}${mark}`;
24404
+ }).join("\n") + "\n\nType /model <number> to switch.";
24307
24405
  }
24308
24406
  if (text.match(/^\/model \d+$/)) {
24309
24407
  const hasKey = !!readAiConfig().apiKey;
@@ -24312,20 +24410,44 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
24312
24410
  if (idx >= 0 && idx < models.length) {
24313
24411
  config.model = models[idx].id;
24314
24412
  writeAiConfig(config);
24315
- return `Model: ${models[idx].id}`;
24413
+ return `Model changed to ${models[idx].name}`;
24316
24414
  }
24317
24415
  return "Invalid number.";
24318
24416
  }
24319
24417
  if (text === "/memory") {
24320
24418
  const m = loadMemory();
24321
- return m.facts.length ? m.facts.map((f, i) => `${i + 1}. ${f}`).join("\n") : "No memories.";
24419
+ return m.facts.length ? m.facts.map((f, i) => `${i + 1}. ${f}`).join("\n") : "No memories saved.";
24420
+ }
24421
+ if (text.startsWith("/remember ")) {
24422
+ addMemoryFact(text.slice(10).trim());
24423
+ return "Remembered.";
24424
+ }
24425
+ if (text.startsWith("/forget ")) {
24426
+ removeMemoryFact(parseInt(text.slice(8).trim(), 10) - 1);
24427
+ return "Forgotten.";
24322
24428
  }
24323
- if (text.startsWith("/save")) {
24429
+ if (text === "/save" || text.startsWith("/save ")) {
24324
24430
  if (text.length > 5) sessionName = text.slice(5).trim();
24325
24431
  saveSession(sessionId, sessionName, conversationMessages, usage);
24326
- return `Saved: ${sessionName}`;
24432
+ return `Session saved: ${sessionName}`;
24327
24433
  }
24328
- if (text.startsWith("/")) return `Unknown: ${text}. Type / for help.`;
24434
+ if (text === "/sessions") {
24435
+ const sessions = listSessions();
24436
+ if (sessions.length === 0) return "No saved sessions.";
24437
+ return sessions.map((s) => `${s.id} ${s.name} (${s.messages} msgs)`).join("\n");
24438
+ }
24439
+ if (text === "/shell") {
24440
+ return getAvailableShells().map((s) => {
24441
+ const mark = s.current ? " \u2713" : s.available ? "" : " (not found)";
24442
+ return `${s.name}${mark}`;
24443
+ }).join("\n") + "\n\nType /shell <name> to switch.";
24444
+ }
24445
+ if (text.startsWith("/shell ")) {
24446
+ setShellOverride(text.slice(7).trim());
24447
+ cliContext.shell = getShellConfig().name;
24448
+ return `Shell: ${getShellConfig().name}`;
24449
+ }
24450
+ if (text.startsWith("/")) return `Unknown command: ${text}. Type / for help.`;
24329
24451
  const { expanded } = expandFileRefs(text);
24330
24452
  conversationMessages.push({ role: "user", content: expanded });
24331
24453
  await autoCompact(conversationMessages);
@@ -24364,7 +24486,7 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
24364
24486
  break;
24365
24487
  }
24366
24488
  if (!result.code) {
24367
- finalResponse = fullText || "No response from AI. Try again.";
24489
+ finalResponse = fullText || "No response. Try again.";
24368
24490
  break;
24369
24491
  }
24370
24492
  if (result.usage) {
@@ -24386,15 +24508,15 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
24386
24508
  if (cleaned) ui?.addMessage(cleaned);
24387
24509
  for (const cmd of commands) {
24388
24510
  const preview = cmd.split("\n")[0].slice(0, 80);
24389
- ui?.addMessage(`$ ${preview}${cmd.includes("\n") ? " ..." : ""}`);
24511
+ ui?.addMessage(`$ ${preview}${cmd.includes("\n") ? " ..." : ""}`, true);
24390
24512
  const r = await executeShellCommand(cmd);
24391
24513
  if (r.exitCode === 0 && r.stdout?.trim()) {
24392
- ui?.addMessage(r.stdout.trim().split("\n").slice(0, 5).join("\n"));
24514
+ ui?.addMessage(r.stdout.trim().split("\n").slice(0, 5).join("\n"), true);
24393
24515
  } else if (r.exitCode !== 0) {
24394
- ui?.addMessage(`exit ${r.exitCode}: ${(r.stderr || "").slice(0, 100)}`);
24516
+ ui?.addMessage(`exit ${r.exitCode}: ${(r.stderr || "").slice(0, 100)}`, true);
24395
24517
  }
24396
24518
  }
24397
- const summary = commands.map((cmd, i) => `$ ${cmd}
24519
+ const summary = commands.map((cmd) => `$ ${cmd}
24398
24520
  (executed)`).join("\n");
24399
24521
  conversationMessages.push({ role: "user", content: `[Results]
24400
24522
  ${summary}` });
@@ -24404,7 +24526,13 @@ ${summary}` });
24404
24526
  saveSession(sessionId, sessionName, conversationMessages, usage);
24405
24527
  return finalResponse;
24406
24528
  }
24407
- const { waitUntilExit } = render(/* @__PURE__ */ jsx(ChatApp, { onMessage: handleMessage }));
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
+ };
24408
24536
  if (initialPrompt) {
24409
24537
  await new Promise((r) => setTimeout(r, 200));
24410
24538
  const ui = global.__rpUI;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@robinpath/cli",
3
- "version": "1.79.0",
3
+ "version": "1.81.0",
4
4
  "description": "AI-powered scripting CLI — automate anything from your terminal",
5
5
  "type": "module",
6
6
  "license": "MIT",