rlm-cli 0.2.0 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,34 +13,38 @@ CLI for **Recursive Language Models** — based on the [RLM paper](https://arxiv
13
13
 
14
14
  Instead of dumping a huge context into a single LLM call, RLM lets the model write Python code to process it — slicing, chunking, running sub-queries on pieces, and building up an answer across multiple iterations.
15
15
 
16
- ## Quick Start
16
+ ## Install
17
+
18
+ ```bash
19
+ npm install -g rlm-cli
20
+ ```
21
+
22
+ Set your API key:
23
+
24
+ ```bash
25
+ export ANTHROPIC_API_KEY=sk-ant-...
26
+ # or
27
+ export OPENAI_API_KEY=sk-...
28
+ ```
29
+
30
+ That's it. Run `rlm` and you're in.
31
+
32
+ ### From Source
17
33
 
18
34
  ```bash
19
35
  git clone https://github.com/viplismism/rlm-cli.git
20
36
  cd rlm-cli
21
37
  npm install
22
38
  npm run build
23
- npm link # makes `rlm` available globally
39
+ npm link
24
40
  ```
25
41
 
26
- Then create a `.env` file with your API key:
42
+ Create a `.env` file in the project root with your API key:
27
43
 
28
44
  ```bash
29
45
  cp .env.example .env
30
46
  ```
31
47
 
32
- ```bash
33
- # .env
34
- ANTHROPIC_API_KEY=sk-ant-...
35
- # or
36
- OPENAI_API_KEY=sk-...
37
-
38
- # Optional: override default model
39
- # RLM_MODEL=claude-sonnet-4-5-20250929
40
- ```
41
-
42
- That's it. Run `rlm` and you're in.
43
-
44
48
  ## Usage
45
49
 
46
50
  ### Interactive Terminal
@@ -153,19 +153,23 @@ function printWelcome() {
153
153
  function printCommandHelp() {
154
154
  console.log(`
155
155
  ${c.bold}Context${c.reset}
156
- ${c.yellow}/file${c.reset} <path> Load file as context
157
- ${c.yellow}/url${c.reset} <url> Fetch URL as context
158
- ${c.yellow}/paste${c.reset} Multi-line paste mode (EOF to finish)
159
- ${c.yellow}/context${c.reset} Show loaded context info
160
- ${c.yellow}/clear-context${c.reset} Unload context
156
+ ${c.cyan}/file${c.reset} <path> Load file as context
157
+ ${c.cyan}/url${c.reset} <url> Fetch URL as context
158
+ ${c.cyan}/paste${c.reset} Multi-line paste mode (EOF to finish)
159
+ ${c.cyan}/context${c.reset} Show loaded context info
160
+ ${c.cyan}/clear-context${c.reset} Unload context
161
+
162
+ ${c.bold}Model${c.reset}
163
+ ${c.cyan}/model${c.reset} List available models
164
+ ${c.cyan}/model${c.reset} <#|id> Switch model by number or ID
161
165
 
162
166
  ${c.bold}Tools${c.reset}
163
- ${c.yellow}/trajectories${c.reset} List saved runs
167
+ ${c.cyan}/trajectories${c.reset} List saved runs
164
168
 
165
169
  ${c.bold}General${c.reset}
166
- ${c.yellow}/clear${c.reset} Clear screen
167
- ${c.yellow}/help${c.reset} Show this help
168
- ${c.yellow}/quit${c.reset} Exit
170
+ ${c.cyan}/clear${c.reset} Clear screen
171
+ ${c.cyan}/help${c.reset} Show this help
172
+ ${c.cyan}/quit${c.reset} Exit
169
173
 
170
174
  ${c.dim}Or just paste a URL or 4+ lines of code, then type your query.${c.reset}
171
175
  `);
@@ -353,6 +357,20 @@ function displaySubQueryResult(info) {
353
357
  }
354
358
  console.log(` ${c.magenta}└─${c.reset} ${c.dim}${elapsed}s · ${formatSize(info.resultLength)} received${c.reset}`);
355
359
  }
360
+ // ── Available models list ────────────────────────────────────────────────────
361
+ /** Collect models from providers that have an API key set. */
362
+ function getAvailableModels() {
363
+ const items = [];
364
+ for (const provider of getProviders()) {
365
+ const providerKey = `${provider.toUpperCase().replace(/-/g, "_")}_API_KEY`;
366
+ if (!process.env[providerKey] && provider !== detectProvider())
367
+ continue;
368
+ for (const m of getModels(provider)) {
369
+ items.push({ id: m.id, provider });
370
+ }
371
+ }
372
+ return items;
373
+ }
356
374
  // ── Truncate helper ─────────────────────────────────────────────────────────
357
375
  function truncateStr(text, max) {
358
376
  return text.length <= max ? text : text.slice(0, max - 3) + "...";
@@ -595,9 +613,13 @@ async function interactive() {
595
613
  // Validate env
596
614
  const hasApiKey = process.env.ANTHROPIC_API_KEY || process.env.OPENAI_API_KEY;
597
615
  if (!hasApiKey) {
598
- console.log(`\n ${c.red}No API key found.${c.reset}`);
599
- console.log(` Set ${c.bold}ANTHROPIC_API_KEY${c.reset} or ${c.bold}OPENAI_API_KEY${c.reset} in your .env file.\n`);
600
- process.exit(1);
616
+ printBanner();
617
+ console.log(` ${c.red}No API key found.${c.reset}\n`);
618
+ console.log(` Set one of these environment variables:\n`);
619
+ console.log(` ${c.yellow}export ANTHROPIC_API_KEY=sk-ant-...${c.reset} ${c.dim}# Anthropic (Claude)${c.reset}`);
620
+ console.log(` ${c.yellow}export OPENAI_API_KEY=sk-...${c.reset} ${c.dim}# OpenAI (GPT)${c.reset}\n`);
621
+ console.log(` ${c.dim}Add to your shell profile (~/.zshrc or ~/.bashrc) to persist across sessions.${c.reset}\n`);
622
+ process.exit(0);
601
623
  }
602
624
  // Resolve model
603
625
  currentModel = resolveModel(currentModelId);
@@ -613,6 +635,21 @@ async function interactive() {
613
635
  prompt: `${c.cyan}>${c.reset} `,
614
636
  terminal: true,
615
637
  });
638
+ // Color slash commands cyan as the user types
639
+ const rlAny = rl;
640
+ const promptStr = rl.getPrompt();
641
+ rlAny._writeToOutput = function (str) {
642
+ if (!rlAny.line?.startsWith("/")) {
643
+ rlAny.output.write(str);
644
+ return;
645
+ }
646
+ if (str.startsWith(promptStr)) {
647
+ rlAny.output.write(promptStr + c.cyan + str.slice(promptStr.length) + c.reset);
648
+ }
649
+ else {
650
+ rlAny.output.write(c.cyan + str + c.reset);
651
+ }
652
+ };
616
653
  rl.prompt();
617
654
  rl.on("line", async (rawLine) => {
618
655
  if (isRunning)
@@ -675,6 +712,40 @@ async function interactive() {
675
712
  contextSource = "";
676
713
  console.log(` ${c.green}✓${c.reset} Context cleared.`);
677
714
  break;
715
+ case "model":
716
+ case "m": {
717
+ const available = getAvailableModels();
718
+ if (arg) {
719
+ // Accept a number or a model ID
720
+ const pick = /^\d+$/.test(arg) ? available[parseInt(arg, 10) - 1]?.id : arg;
721
+ const newModel = pick ? resolveModel(pick) : undefined;
722
+ if (newModel && pick) {
723
+ currentModelId = pick;
724
+ currentModel = newModel;
725
+ console.log(` ${c.green}✓${c.reset} Switched to ${c.bold}${currentModelId}${c.reset}`);
726
+ console.log();
727
+ printStatusLine();
728
+ }
729
+ else {
730
+ console.log(` ${c.red}Model "${arg}" not found.${c.reset} Use ${c.cyan}/model${c.reset} to list available models.`);
731
+ }
732
+ }
733
+ else {
734
+ console.log(`\n ${c.bold}Current model:${c.reset} ${c.cyan}${currentModelId}${c.reset}\n`);
735
+ const pad = String(available.length).length;
736
+ for (let i = 0; i < available.length; i++) {
737
+ const m = available[i];
738
+ const num = String(i + 1).padStart(pad);
739
+ const dot = m.id === currentModelId ? `${c.green}●${c.reset}` : ` `;
740
+ const label = m.id === currentModelId
741
+ ? `${c.cyan}${m.id}${c.reset}`
742
+ : `${c.dim}${m.id}${c.reset}`;
743
+ console.log(` ${c.dim}${num}${c.reset} ${dot} ${label}`);
744
+ }
745
+ console.log(`\n ${c.dim}Type${c.reset} ${c.cyan}/model <number>${c.reset} ${c.dim}or${c.reset} ${c.cyan}/model <id>${c.reset} ${c.dim}to switch.${c.reset}`);
746
+ }
747
+ break;
748
+ }
678
749
  case "trajectories":
679
750
  case "traj":
680
751
  handleTrajectories();
@@ -689,7 +760,7 @@ async function interactive() {
689
760
  process.exit(0);
690
761
  break;
691
762
  default:
692
- console.log(` ${c.red}Unknown command: /${cmd}${c.reset}. Type ${c.yellow}/help${c.reset} for commands.`);
763
+ console.log(` ${c.red}Unknown command: /${cmd}${c.reset}. Type ${c.cyan}/help${c.reset} for commands.`);
693
764
  }
694
765
  rl.prompt();
695
766
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rlm-cli",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "Standalone CLI for Recursive Language Models (RLMs) — implements Algorithm 1 from arXiv:2512.24601",
5
5
  "type": "module",
6
6
  "bin": {
@@ -32,7 +32,7 @@
32
32
  ],
33
33
  "license": "MIT",
34
34
  "dependencies": {
35
- "@mariozechner/pi-ai": "^0.53.0"
35
+ "@mariozechner/pi-ai": "^0.55.1"
36
36
  },
37
37
  "devDependencies": {
38
38
  "esbuild": "^0.27.3",