@robinpath/cli 1.79.0 → 1.80.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 +191 -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.80.0" : "1.80.0";
18602
18602
  var FLAG_QUIET = false;
18603
18603
  var FLAG_VERBOSE = false;
18604
18604
  var FLAG_AUTO_ACCEPT = false;
@@ -24155,72 +24155,145 @@ ${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);
24207
+ return;
24208
+ }
24209
+ if (ch === "") {
24210
+ if (!value) exit();
24211
+ else {
24212
+ setValue("");
24213
+ setShowHints(false);
24214
+ }
24215
+ return;
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
+ }
24214
24226
  return;
24215
24227
  }
24216
- if (key.tab) return;
24217
- if (character && !key.ctrl && !key.meta) {
24218
- setInput((prev) => prev + character);
24228
+ if (ch === "") {
24229
+ setValue("");
24230
+ return;
24231
+ }
24232
+ if (ch === "") {
24233
+ setValue((p) => p.replace(/\S+\s*$/, ""));
24234
+ return;
24219
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__ */ 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: [
24249
+ line,
24250
+ i === lines.length - 1 ? /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u2588" }) : null
24251
+ ] }, i)) }),
24252
+ /* @__PURE__ */ jsx(Box, { marginX: 2, children: /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
24253
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "enter" }),
24254
+ " send ",
24255
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "\\" }),
24256
+ " newline ",
24257
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "esc" }),
24258
+ " clear ",
24259
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "/" }),
24260
+ " commands ",
24261
+ /* @__PURE__ */ jsx(Text, { color: "gray", children: "tab" }),
24262
+ " complete"
24263
+ ] }) })
24264
+ ] });
24265
+ }
24266
+ function ChatApp({ onMessage, statusText }) {
24267
+ const [messages, setMessages] = useState([]);
24268
+ const [streaming, setStreaming] = useState("");
24269
+ const [loading, setLoading] = useState(false);
24270
+ useEffect(() => {
24271
+ global.__rpUI = {
24272
+ setStreaming,
24273
+ setLoading,
24274
+ addMessage: (text, dim) => setMessages((p) => [...p, { id: ++nextId, text, dim }])
24275
+ };
24276
+ }, []);
24277
+ const handleSubmit = useCallback(async (text) => {
24278
+ if (text === "exit" || text === "quit") {
24279
+ global.__rpExit?.();
24280
+ return;
24281
+ }
24282
+ setMessages((p) => [...p, { id: ++nextId, text: `\u276F ${text}` }]);
24283
+ setLoading(true);
24284
+ setStreaming("");
24285
+ try {
24286
+ const response = await onMessage(text);
24287
+ if (response) setMessages((p) => [...p, { id: ++nextId, text: response }]);
24288
+ } catch (err) {
24289
+ setMessages((p) => [...p, { id: ++nextId, text: `Error: ${err.message}`, dim: true }]);
24290
+ }
24291
+ setLoading(false);
24292
+ setStreaming("");
24293
+ }, [onMessage]);
24294
+ const isFirst = messages.length === 0;
24222
24295
  return /* @__PURE__ */ jsxs(Box, { flexDirection: "column", padding: 1, children: [
24223
- /* @__PURE__ */ jsxs(Text, { children: [
24296
+ /* @__PURE__ */ jsx(Box, { marginBottom: 1, children: /* @__PURE__ */ jsxs(Text, { children: [
24224
24297
  /* @__PURE__ */ jsx(Text, { color: "cyan", bold: true, children: "\u25C6" }),
24225
24298
  " ",
24226
24299
  /* @__PURE__ */ jsx(Text, { bold: true, children: "RobinPath" }),
@@ -24229,21 +24302,22 @@ function ChatApp({ onMessage }) {
24229
24302
  "v",
24230
24303
  CLI_VERSION
24231
24304
  ] })
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: [
24305
+ ] }) }),
24306
+ /* @__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) }),
24307
+ loading ? /* @__PURE__ */ jsx(Box, { flexDirection: "column", paddingX: 1, children: streaming ? /* @__PURE__ */ jsxs(Text, { wrap: "wrap", children: [
24308
+ streaming,
24309
+ /* @__PURE__ */ jsx(Text, { color: "cyan", children: "\u258D" })
24310
+ ] }) : /* @__PURE__ */ jsxs(Text, { dimColor: true, children: [
24237
24311
  /* @__PURE__ */ jsx(InkSpinner, { type: "dots" }),
24238
24312
  " 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
24313
+ ] }) }) : /* @__PURE__ */ jsx(
24314
+ InputArea,
24315
+ {
24316
+ onSubmit: handleSubmit,
24317
+ placeholder: isFirst ? "Anything to automate with RobinPath?" : "Ask anything..."
24318
+ }
24319
+ ),
24320
+ /* @__PURE__ */ jsx(Box, { marginTop: 1, paddingX: 1, children: /* @__PURE__ */ jsx(Text, { dimColor: true, children: statusText }) })
24247
24321
  ] });
24248
24322
  }
24249
24323
  async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
@@ -24260,6 +24334,7 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
24260
24334
  };
24261
24335
  const apiKey = config.apiKey || null;
24262
24336
  const model = apiKey ? config.model || "anthropic/claude-sonnet-4.6" : "robinpath-default";
24337
+ const modelShort = (m) => m === "robinpath-default" ? "gemini-free" : m.includes("/") ? m.split("/").pop() : m;
24263
24338
  const cliContext = {
24264
24339
  platform: platform7(),
24265
24340
  shell: getShellConfig().name,
@@ -24285,15 +24360,23 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
24285
24360
  if (session.usage) Object.assign(usage, session.usage);
24286
24361
  }
24287
24362
  }
24363
+ function getStatusText() {
24364
+ const m = modelShort(readAiConfig().model || model);
24365
+ const cost = usage.cost > 0 ? ` \xB7 $${usage.cost.toFixed(4)}` : "";
24366
+ const tokens = usage.totalTokens > 0 ? ` \xB7 ${usage.totalTokens.toLocaleString()} tok` : "";
24367
+ return `${m} \xB7 ${getShellConfig().name} \xB7 ${autoAccept ? "auto" : "confirm"}${tokens}${cost}`;
24368
+ }
24288
24369
  async function handleMessage(text) {
24289
24370
  const ui = global.__rpUI;
24290
- if (text === "/" || text === "/help") return "/model /auto /clear /save /usage /memory exit";
24371
+ if (text === "/" || text === "/help") {
24372
+ return Object.entries(COMMANDS).map(([cmd, desc]) => `${cmd.padEnd(12)} ${desc}`).join("\n");
24373
+ }
24291
24374
  if (text === "/clear") {
24292
24375
  conversationMessages.length = 0;
24293
- return "Cleared.";
24376
+ return "Conversation cleared.";
24294
24377
  }
24295
24378
  if (text === "/usage") {
24296
- const c = usage.cost > 0 ? `$${usage.cost.toFixed(4)}` : "$0 (free)";
24379
+ const c = usage.cost > 0 ? `$${usage.cost.toFixed(4)}` : "$0.00 (free)";
24297
24380
  return `${usage.totalTokens.toLocaleString()} tokens \xB7 ${usage.requests} requests \xB7 ${c}`;
24298
24381
  }
24299
24382
  if (text === "/auto") {
@@ -24303,7 +24386,11 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
24303
24386
  if (text === "/model") {
24304
24387
  const hasKey = !!readAiConfig().apiKey;
24305
24388
  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");
24389
+ const cur = readAiConfig().model || model;
24390
+ return models.map((m, i) => {
24391
+ const mark = m.id === cur ? " \u2713" : "";
24392
+ return `${String(i + 1).padStart(2)}. ${m.name.padEnd(22)} ${m.desc}${mark}`;
24393
+ }).join("\n") + "\n\nType /model <number> to switch.";
24307
24394
  }
24308
24395
  if (text.match(/^\/model \d+$/)) {
24309
24396
  const hasKey = !!readAiConfig().apiKey;
@@ -24312,20 +24399,44 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
24312
24399
  if (idx >= 0 && idx < models.length) {
24313
24400
  config.model = models[idx].id;
24314
24401
  writeAiConfig(config);
24315
- return `Model: ${models[idx].id}`;
24402
+ return `Model changed to ${models[idx].name}`;
24316
24403
  }
24317
24404
  return "Invalid number.";
24318
24405
  }
24319
24406
  if (text === "/memory") {
24320
24407
  const m = loadMemory();
24321
- return m.facts.length ? m.facts.map((f, i) => `${i + 1}. ${f}`).join("\n") : "No memories.";
24408
+ return m.facts.length ? m.facts.map((f, i) => `${i + 1}. ${f}`).join("\n") : "No memories saved.";
24322
24409
  }
24323
- if (text.startsWith("/save")) {
24410
+ if (text.startsWith("/remember ")) {
24411
+ addMemoryFact(text.slice(10).trim());
24412
+ return "Remembered.";
24413
+ }
24414
+ if (text.startsWith("/forget ")) {
24415
+ removeMemoryFact(parseInt(text.slice(8).trim(), 10) - 1);
24416
+ return "Forgotten.";
24417
+ }
24418
+ if (text === "/save" || text.startsWith("/save ")) {
24324
24419
  if (text.length > 5) sessionName = text.slice(5).trim();
24325
24420
  saveSession(sessionId, sessionName, conversationMessages, usage);
24326
- return `Saved: ${sessionName}`;
24421
+ return `Session saved: ${sessionName}`;
24327
24422
  }
24328
- if (text.startsWith("/")) return `Unknown: ${text}. Type / for help.`;
24423
+ if (text === "/sessions") {
24424
+ const sessions = listSessions();
24425
+ if (sessions.length === 0) return "No saved sessions.";
24426
+ return sessions.map((s) => `${s.id} ${s.name} (${s.messages} msgs)`).join("\n");
24427
+ }
24428
+ if (text === "/shell") {
24429
+ return getAvailableShells().map((s) => {
24430
+ const mark = s.current ? " \u2713" : s.available ? "" : " (not found)";
24431
+ return `${s.name}${mark}`;
24432
+ }).join("\n") + "\n\nType /shell <name> to switch.";
24433
+ }
24434
+ if (text.startsWith("/shell ")) {
24435
+ setShellOverride(text.slice(7).trim());
24436
+ cliContext.shell = getShellConfig().name;
24437
+ return `Shell: ${getShellConfig().name}`;
24438
+ }
24439
+ if (text.startsWith("/")) return `Unknown command: ${text}. Type / for help.`;
24329
24440
  const { expanded } = expandFileRefs(text);
24330
24441
  conversationMessages.push({ role: "user", content: expanded });
24331
24442
  await autoCompact(conversationMessages);
@@ -24364,7 +24475,7 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
24364
24475
  break;
24365
24476
  }
24366
24477
  if (!result.code) {
24367
- finalResponse = fullText || "No response from AI. Try again.";
24478
+ finalResponse = fullText || "No response. Try again.";
24368
24479
  break;
24369
24480
  }
24370
24481
  if (result.usage) {
@@ -24386,15 +24497,15 @@ async function startInkREPL(initialPrompt, resumeSessionId, opts = {}) {
24386
24497
  if (cleaned) ui?.addMessage(cleaned);
24387
24498
  for (const cmd of commands) {
24388
24499
  const preview = cmd.split("\n")[0].slice(0, 80);
24389
- ui?.addMessage(`$ ${preview}${cmd.includes("\n") ? " ..." : ""}`);
24500
+ ui?.addMessage(`$ ${preview}${cmd.includes("\n") ? " ..." : ""}`, true);
24390
24501
  const r = await executeShellCommand(cmd);
24391
24502
  if (r.exitCode === 0 && r.stdout?.trim()) {
24392
- ui?.addMessage(r.stdout.trim().split("\n").slice(0, 5).join("\n"));
24503
+ ui?.addMessage(r.stdout.trim().split("\n").slice(0, 5).join("\n"), true);
24393
24504
  } else if (r.exitCode !== 0) {
24394
- ui?.addMessage(`exit ${r.exitCode}: ${(r.stderr || "").slice(0, 100)}`);
24505
+ ui?.addMessage(`exit ${r.exitCode}: ${(r.stderr || "").slice(0, 100)}`, true);
24395
24506
  }
24396
24507
  }
24397
- const summary = commands.map((cmd, i) => `$ ${cmd}
24508
+ const summary = commands.map((cmd) => `$ ${cmd}
24398
24509
  (executed)`).join("\n");
24399
24510
  conversationMessages.push({ role: "user", content: `[Results]
24400
24511
  ${summary}` });
@@ -24404,7 +24515,13 @@ ${summary}` });
24404
24515
  saveSession(sessionId, sessionName, conversationMessages, usage);
24405
24516
  return finalResponse;
24406
24517
  }
24407
- const { waitUntilExit } = render(/* @__PURE__ */ jsx(ChatApp, { onMessage: handleMessage }));
24518
+ const { waitUntilExit } = render(
24519
+ /* @__PURE__ */ jsx(ChatApp, { onMessage: handleMessage, statusText: getStatusText() })
24520
+ );
24521
+ global.__rpExit = () => {
24522
+ if (conversationMessages.length > 1) saveSession(sessionId, sessionName, conversationMessages, usage);
24523
+ process.exit(0);
24524
+ };
24408
24525
  if (initialPrompt) {
24409
24526
  await new Promise((r) => setTimeout(r, 200));
24410
24527
  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.80.0",
4
4
  "description": "AI-powered scripting CLI — automate anything from your terminal",
5
5
  "type": "module",
6
6
  "license": "MIT",