rlm-cli 0.2.14 → 0.2.16

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
@@ -83,6 +83,41 @@ Or set context first, then ask multiple questions:
83
83
 
84
84
  Type `/help` inside the terminal for all commands.
85
85
 
86
+ ### Loading Context
87
+
88
+ You can load single files, multiple files, entire directories, or glob patterns as context.
89
+
90
+ **Single file:**
91
+
92
+ ```bash
93
+ > @src/main.ts what does this do?
94
+ > /file src/main.ts
95
+ ```
96
+
97
+ **Multiple files:**
98
+
99
+ ```bash
100
+ > @src/main.ts @src/config.ts how do these interact?
101
+ > /file src/main.ts src/config.ts
102
+ ```
103
+
104
+ **Directory** (recursive — skips `node_modules`, `.git`, `dist`, binaries, etc.):
105
+
106
+ ```bash
107
+ > @src/ summarize this codebase
108
+ > /file src/
109
+ ```
110
+
111
+ **Glob patterns:**
112
+
113
+ ```bash
114
+ > @src/**/*.ts list all exports
115
+ > /file src/**/*.ts
116
+ > /file lib/*.{js,ts}
117
+ ```
118
+
119
+ Safety limits: max 100 files, max 10MB total. Use `/context` to see what's loaded.
120
+
86
121
  ### Single-Shot Mode
87
122
 
88
123
  For scripting or one-off queries:
@@ -84,6 +84,7 @@ const W = Math.min(process.stdout.columns || 80, 100);
84
84
  // ── Session state ───────────────────────────────────────────────────────────
85
85
  let currentModelId = DEFAULT_MODEL;
86
86
  let currentModel;
87
+ let currentProviderName = "";
87
88
  let contextText = "";
88
89
  let contextSource = "";
89
90
  let queryCount = 0;
@@ -106,8 +107,8 @@ function resolveModel(modelId) {
106
107
  const PROVIDER_KEYS = {
107
108
  anthropic: "ANTHROPIC_API_KEY",
108
109
  openai: "OPENAI_API_KEY",
109
- google: "GOOGLE_API_KEY",
110
- "google-gemini-cli": "GOOGLE_API_KEY",
110
+ google: "GEMINI_API_KEY",
111
+ "google-gemini-cli": "GEMINI_API_KEY",
111
112
  "google-vertex": "GOOGLE_VERTEX_API_KEY",
112
113
  groq: "GROQ_API_KEY",
113
114
  xai: "XAI_API_KEY",
@@ -120,7 +121,7 @@ const PROVIDER_KEYS = {
120
121
  const SETUP_PROVIDERS = [
121
122
  { name: "Anthropic", label: "Claude", env: "ANTHROPIC_API_KEY", piProvider: "anthropic" },
122
123
  { name: "OpenAI", label: "GPT", env: "OPENAI_API_KEY", piProvider: "openai" },
123
- { name: "Google", label: "Gemini", env: "GOOGLE_API_KEY", piProvider: "google" },
124
+ { name: "Google", label: "Gemini", env: "GEMINI_API_KEY", piProvider: "google" },
124
125
  { name: "Groq", label: "Groq", env: "GROQ_API_KEY", piProvider: "groq" },
125
126
  { name: "xAI", label: "Grok", env: "XAI_API_KEY", piProvider: "xai" },
126
127
  { name: "Mistral", label: "Mistral", env: "MISTRAL_API_KEY", piProvider: "mistral" },
@@ -273,7 +274,7 @@ ${c.dim} Recursive Language Models — arXiv:2512.24601${c.reset}
273
274
  }
274
275
  // ── Status line ─────────────────────────────────────────────────────────────
275
276
  function printStatusLine() {
276
- const provider = detectProvider();
277
+ const provider = currentProviderName || detectProvider();
277
278
  const modelShort = currentModelId.length > 35
278
279
  ? currentModelId.slice(0, 32) + "..."
279
280
  : currentModelId;
@@ -295,48 +296,80 @@ function printWelcome() {
295
296
  // ── Help ────────────────────────────────────────────────────────────────────
296
297
  function printCommandHelp() {
297
298
  console.log(`
298
- ${c.bold}Context${c.reset}
299
- ${c.cyan}/file${c.reset} <path> Load file as context
300
- ${c.cyan}/url${c.reset} <url> Fetch URL as context
301
- ${c.cyan}/paste${c.reset} Multi-line paste mode (EOF to finish)
302
- ${c.cyan}/context${c.reset} Show loaded context info
303
- ${c.cyan}/clear-context${c.reset} Unload context
299
+ ${c.bold}Loading Context${c.reset}
300
+ ${c.cyan}/file${c.reset} <path> Load a single file
301
+ ${c.cyan}/file${c.reset} <p1> <p2> ... Load multiple files
302
+ ${c.cyan}/file${c.reset} <dir>/ Load all files in a directory (recursive)
303
+ ${c.cyan}/file${c.reset} src/**/*.ts Load files matching a glob pattern
304
+ ${c.cyan}/url${c.reset} <url> Fetch URL as context
305
+ ${c.cyan}/paste${c.reset} Multi-line paste mode (type EOF to finish)
306
+ ${c.cyan}/context${c.reset} Show loaded context info + file list
307
+ ${c.cyan}/clear-context${c.reset} Unload context
304
308
 
305
- ${c.bold}Model${c.reset}
306
- ${c.cyan}/model${c.reset} List models for current provider
307
- ${c.cyan}/model${c.reset} <#|id> Switch model by number or ID
308
- ${c.cyan}/provider${c.reset} Switch provider
309
+ ${c.bold}@ Shorthand${c.reset} ${c.dim}(inline file loading)${c.reset}
310
+ ${c.cyan}@file.ts${c.reset} <query> Load file and ask in one shot
311
+ ${c.cyan}@a.ts @b.ts${c.reset} <query> Load multiple files + query
312
+ ${c.cyan}@src/${c.reset} <query> Load directory + query
313
+ ${c.cyan}@src/**/*.ts${c.reset} <query> Load glob + query
314
+
315
+ ${c.bold}Model & Provider${c.reset}
316
+ ${c.cyan}/model${c.reset} List models for current provider
317
+ ${c.cyan}/model${c.reset} <#|id> Switch model by number or ID
318
+ ${c.cyan}/provider${c.reset} Switch provider (Anthropic, OpenAI, Google, ...)
309
319
 
310
320
  ${c.bold}Tools${c.reset}
311
- ${c.cyan}/trajectories${c.reset} List saved runs
321
+ ${c.cyan}/trajectories${c.reset} List saved runs
312
322
 
313
323
  ${c.bold}General${c.reset}
314
- ${c.cyan}/clear${c.reset} Clear screen
315
- ${c.cyan}/help${c.reset} Show this help
316
- ${c.cyan}/quit${c.reset} Exit
324
+ ${c.cyan}/clear${c.reset} Clear screen
325
+ ${c.cyan}/help${c.reset} Show this help
326
+ ${c.cyan}/quit${c.reset} Exit
317
327
 
318
- ${c.dim}Or just paste a URL or 4+ lines of code, then type your query.${c.reset}
328
+ ${c.bold}Tips${c.reset}
329
+ ${c.dim}•${c.reset} Just type a question — no context needed for general queries
330
+ ${c.dim}•${c.reset} Paste a URL directly to fetch it as context
331
+ ${c.dim}•${c.reset} Paste 4+ lines of text to set it as context
332
+ ${c.dim}•${c.reset} ${c.bold}Ctrl+C${c.reset} stops a running query, ${c.bold}Ctrl+C twice${c.reset} exits
333
+ ${c.dim}•${c.reset} Directories skip node_modules, .git, dist, binaries, etc.
334
+ ${c.dim}•${c.reset} Limits: ${MAX_FILES} files max, ${MAX_TOTAL_BYTES / 1024 / 1024}MB total
319
335
  `);
320
336
  }
321
337
  // ── Slash command handlers ──────────────────────────────────────────────────
322
338
  async function handleFile(arg) {
323
339
  if (!arg) {
324
- console.log(` ${c.red}Usage: /file <path>${c.reset}`);
340
+ console.log(` ${c.red}Usage: /file <path|dir|glob> [...]${c.reset}`);
341
+ console.log(` ${c.dim}Examples: /file src/main.ts | /file src/ | /file src/**/*.ts${c.reset}`);
325
342
  return;
326
343
  }
327
- const filePath = path.resolve(arg);
328
- if (!fs.existsSync(filePath)) {
329
- console.log(` ${c.red}File not found: ${filePath}${c.reset}`);
344
+ const args = arg.split(/\s+/).filter(Boolean);
345
+ const filePaths = resolveFileArgs(args);
346
+ if (filePaths.length === 0) {
347
+ console.log(` ${c.red}No files found.${c.reset}`);
330
348
  return;
331
349
  }
332
- try {
333
- contextText = fs.readFileSync(filePath, "utf-8");
334
- contextSource = arg;
335
- const lines = contextText.split("\n").length;
336
- console.log(` ${c.green}✓${c.reset} Loaded ${c.bold}${contextText.length.toLocaleString()}${c.reset} chars (${lines.toLocaleString()} lines) from ${c.underline}${arg}${c.reset}`);
350
+ if (filePaths.length === 1) {
351
+ try {
352
+ contextText = fs.readFileSync(filePaths[0], "utf-8");
353
+ contextSource = path.relative(process.cwd(), filePaths[0]) || filePaths[0];
354
+ const lines = contextText.split("\n").length;
355
+ console.log(` ${c.green}✓${c.reset} Loaded ${c.bold}${contextText.length.toLocaleString()}${c.reset} chars (${lines.toLocaleString()} lines) from ${c.underline}${contextSource}${c.reset}`);
356
+ }
357
+ catch (err) {
358
+ console.log(` ${c.red}Could not read file: ${err.message}${c.reset}`);
359
+ }
337
360
  }
338
- catch (err) {
339
- console.log(` ${c.red}Could not read file: ${err.message}${c.reset}`);
361
+ else {
362
+ const { text, count, totalBytes } = loadMultipleFiles(filePaths);
363
+ contextText = text;
364
+ contextSource = `${count} files`;
365
+ console.log(` ${c.green}✓${c.reset} Loaded ${c.bold}${count}${c.reset} files (${(totalBytes / 1024).toFixed(1)}KB total)`);
366
+ // Show file list
367
+ for (const fp of filePaths.slice(0, 20)) {
368
+ console.log(` ${c.dim}•${c.reset} ${path.relative(process.cwd(), fp)}`);
369
+ }
370
+ if (filePaths.length > 20) {
371
+ console.log(` ${c.dim}... and ${filePaths.length - 20} more${c.reset}`);
372
+ }
340
373
  }
341
374
  }
342
375
  async function handleUrl(arg) {
@@ -378,20 +411,35 @@ function handlePaste(rl) {
378
411
  }
379
412
  function handleContext() {
380
413
  if (!contextText) {
381
- console.log(` ${c.dim}No context loaded. Use /file, /url, or /paste.${c.reset}`);
414
+ console.log(` ${c.dim}No context loaded. Use /file, /url, @file, or /paste.${c.reset}`);
382
415
  return;
383
416
  }
384
417
  const lines = contextText.split("\n").length;
385
- console.log(` ${c.bold}Context:${c.reset} ${contextText.length.toLocaleString()} chars, ${lines.toLocaleString()} lines`);
418
+ const sizeKB = (contextText.length / 1024).toFixed(1);
419
+ console.log(` ${c.bold}Context:${c.reset} ${contextText.length.toLocaleString()} chars (${sizeKB}KB), ${lines.toLocaleString()} lines`);
386
420
  console.log(` ${c.bold}Source:${c.reset} ${contextSource}`);
387
- console.log();
388
- const preview = contextText.slice(0, 500);
389
- const previewLines = preview.split("\n").slice(0, 8);
390
- for (const l of previewLines) {
391
- console.log(` ${c.dim}│${c.reset} ${l}`);
421
+ // For multi-file context, extract and display individual file paths
422
+ const fileSeparators = contextText.match(/^=== .+ ===$/gm);
423
+ if (fileSeparators && fileSeparators.length > 1) {
424
+ console.log(` ${c.bold}Files:${c.reset} ${fileSeparators.length}`);
425
+ for (const sep of fileSeparators.slice(0, 20)) {
426
+ const name = sep.replace(/^=== /, "").replace(/ ===$/, "");
427
+ console.log(` ${c.dim}•${c.reset} ${name}`);
428
+ }
429
+ if (fileSeparators.length > 20) {
430
+ console.log(` ${c.dim}... and ${fileSeparators.length - 20} more${c.reset}`);
431
+ }
392
432
  }
393
- if (contextText.length > 500) {
394
- console.log(` ${c.dim}│ ...${c.reset}`);
433
+ else {
434
+ console.log();
435
+ const preview = contextText.slice(0, 500);
436
+ const previewLines = preview.split("\n").slice(0, 8);
437
+ for (const l of previewLines) {
438
+ console.log(` ${c.dim}│${c.reset} ${l}`);
439
+ }
440
+ if (contextText.length > 500) {
441
+ console.log(` ${c.dim}│ ...${c.reset}`);
442
+ }
395
443
  }
396
444
  }
397
445
  function handleTrajectories() {
@@ -580,12 +628,176 @@ function getModelsForProvider(providerName) {
580
628
  function truncateStr(text, max) {
581
629
  return text.length <= max ? text : text.slice(0, max - 3) + "...";
582
630
  }
631
+ // ── Multi-file context loading ──────────────────────────────────────────────
632
+ const MAX_FILES = 100;
633
+ const MAX_TOTAL_BYTES = 10 * 1024 * 1024; // 10MB
634
+ const BINARY_EXTENSIONS = new Set([
635
+ ".png", ".jpg", ".jpeg", ".gif", ".bmp", ".ico", ".webp", ".svg",
636
+ ".mp3", ".mp4", ".wav", ".ogg", ".flac", ".avi", ".mov", ".mkv",
637
+ ".zip", ".gz", ".tar", ".bz2", ".7z", ".rar", ".xz",
638
+ ".exe", ".dll", ".so", ".dylib", ".bin", ".o", ".a",
639
+ ".woff", ".woff2", ".ttf", ".otf", ".eot",
640
+ ".pdf", ".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx",
641
+ ".pyc", ".pyo", ".class", ".jar",
642
+ ".db", ".sqlite", ".sqlite3",
643
+ ".DS_Store",
644
+ ]);
645
+ const SKIP_DIRS = new Set([
646
+ "node_modules", ".git", "dist", "build", "__pycache__", ".venv",
647
+ "venv", ".next", ".nuxt", "coverage", ".cache", ".tsc-output",
648
+ ".svelte-kit", "target", "out",
649
+ ]);
650
+ function isBinaryFile(filePath) {
651
+ const ext = path.extname(filePath).toLowerCase();
652
+ if (BINARY_EXTENSIONS.has(ext))
653
+ return true;
654
+ // Quick null-byte check on first 512 bytes
655
+ try {
656
+ const fd = fs.openSync(filePath, "r");
657
+ const buf = Buffer.alloc(512);
658
+ const bytesRead = fs.readSync(fd, buf, 0, 512, 0);
659
+ fs.closeSync(fd);
660
+ for (let i = 0; i < bytesRead; i++) {
661
+ if (buf[i] === 0)
662
+ return true;
663
+ }
664
+ }
665
+ catch { /* unreadable → skip */
666
+ return true;
667
+ }
668
+ return false;
669
+ }
670
+ function walkDir(dir) {
671
+ const results = [];
672
+ let entries;
673
+ try {
674
+ entries = fs.readdirSync(dir, { withFileTypes: true });
675
+ }
676
+ catch {
677
+ return results;
678
+ }
679
+ for (const entry of entries) {
680
+ if (entry.name.startsWith(".") && entry.name !== ".env")
681
+ continue;
682
+ const full = path.join(dir, entry.name);
683
+ if (entry.isDirectory()) {
684
+ if (SKIP_DIRS.has(entry.name))
685
+ continue;
686
+ results.push(...walkDir(full));
687
+ }
688
+ else if (entry.isFile()) {
689
+ if (!isBinaryFile(full))
690
+ results.push(full);
691
+ }
692
+ if (results.length > MAX_FILES)
693
+ break;
694
+ }
695
+ return results;
696
+ }
697
+ function simpleGlobMatch(pattern, filePath) {
698
+ // Expand {a,b,c} braces into alternatives
699
+ const braceMatch = pattern.match(/\{([^}]+)\}/);
700
+ if (braceMatch) {
701
+ const alternatives = braceMatch[1].split(",");
702
+ return alternatives.some((alt) => simpleGlobMatch(pattern.replace(braceMatch[0], alt.trim()), filePath));
703
+ }
704
+ // Convert glob to regex
705
+ let regex = "^";
706
+ let i = 0;
707
+ while (i < pattern.length) {
708
+ const ch = pattern[i];
709
+ if (ch === "*" && pattern[i + 1] === "*") {
710
+ // ** matches any path segment(s)
711
+ regex += ".*";
712
+ i += 2;
713
+ if (pattern[i] === "/")
714
+ i++; // skip trailing slash after **
715
+ }
716
+ else if (ch === "*") {
717
+ regex += "[^/]*";
718
+ i++;
719
+ }
720
+ else if (ch === "?") {
721
+ regex += "[^/]";
722
+ i++;
723
+ }
724
+ else if (".+^$|()[]\\".includes(ch)) {
725
+ regex += "\\" + ch;
726
+ i++;
727
+ }
728
+ else {
729
+ regex += ch;
730
+ i++;
731
+ }
732
+ }
733
+ regex += "$";
734
+ return new RegExp(regex).test(filePath);
735
+ }
736
+ function resolveFileArgs(args) {
737
+ const files = [];
738
+ for (const arg of args) {
739
+ const resolved = path.resolve(arg);
740
+ // Glob pattern (contains * or ?)
741
+ if (arg.includes("*") || arg.includes("?")) {
742
+ // Find the base directory (portion before the first glob char)
743
+ const firstGlob = arg.search(/[*?{]/);
744
+ const baseDir = firstGlob > 0 ? path.resolve(arg.slice(0, arg.lastIndexOf("/", firstGlob) + 1) || ".") : process.cwd();
745
+ const allFiles = walkDir(baseDir);
746
+ for (const f of allFiles) {
747
+ const rel = path.relative(process.cwd(), f);
748
+ if (simpleGlobMatch(arg, rel) || simpleGlobMatch(arg, f)) {
749
+ files.push(f);
750
+ }
751
+ }
752
+ continue;
753
+ }
754
+ // Directory
755
+ if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
756
+ files.push(...walkDir(resolved));
757
+ continue;
758
+ }
759
+ // Regular file
760
+ if (fs.existsSync(resolved)) {
761
+ if (!isBinaryFile(resolved))
762
+ files.push(resolved);
763
+ continue;
764
+ }
765
+ console.log(` ${c.yellow}⚠${c.reset} Not found: ${arg}`);
766
+ }
767
+ return [...new Set(files)]; // deduplicate
768
+ }
769
+ function loadMultipleFiles(filePaths) {
770
+ if (filePaths.length > MAX_FILES) {
771
+ console.log(` ${c.yellow}⚠${c.reset} Too many files (${filePaths.length}). Limit is ${MAX_FILES}.`);
772
+ filePaths = filePaths.slice(0, MAX_FILES);
773
+ }
774
+ const parts = [];
775
+ let totalBytes = 0;
776
+ for (const fp of filePaths) {
777
+ try {
778
+ const content = fs.readFileSync(fp, "utf-8");
779
+ if (totalBytes + content.length > MAX_TOTAL_BYTES) {
780
+ console.log(` ${c.yellow}⚠${c.reset} Size limit reached (${(MAX_TOTAL_BYTES / 1024 / 1024).toFixed(0)}MB). Loaded ${parts.length} of ${filePaths.length} files.`);
781
+ break;
782
+ }
783
+ const rel = path.relative(process.cwd(), fp);
784
+ parts.push(`=== ${rel} ===\n${content}`);
785
+ totalBytes += content.length;
786
+ }
787
+ catch { /* skip unreadable */ }
788
+ }
789
+ return { text: parts.join("\n\n"), count: parts.length, totalBytes };
790
+ }
583
791
  // ── Run RLM query ───────────────────────────────────────────────────────────
584
792
  async function runQuery(query) {
585
793
  const effectiveContext = contextText || query;
586
794
  const isDirectMode = !contextText;
587
795
  if (!currentModel) {
588
- currentModel = resolveModel(currentModelId);
796
+ const resolved = resolveModelWithProvider(currentModelId);
797
+ if (resolved) {
798
+ currentModel = resolved.model;
799
+ currentProviderName = resolved.provider;
800
+ }
589
801
  }
590
802
  if (!currentModel) {
591
803
  console.log(`\n ${c.red}Model "${currentModelId}" not found.${c.reset}`);
@@ -778,28 +990,45 @@ function extractFilePath(input) {
778
990
  return { filePath: null, query: input };
779
991
  }
780
992
  function expandAtFiles(input) {
781
- const atMatch = input.match(/^@(\S+)\s*(.*)/);
782
- if (atMatch) {
783
- const filePath = path.resolve(atMatch[1]);
784
- if (fs.existsSync(filePath)) {
785
- try {
786
- contextText = fs.readFileSync(filePath, "utf-8");
787
- contextSource = atMatch[1];
788
- const lines = contextText.split("\n").length;
789
- console.log(` ${c.green}✓${c.reset} Loaded ${c.bold}${contextText.length.toLocaleString()}${c.reset} chars (${lines} lines) from ${c.underline}${atMatch[1]}${c.reset}`);
790
- return atMatch[2] || "";
791
- }
792
- catch (err) {
793
- console.log(` ${c.red}Could not read file: ${err.message}${c.reset}`);
794
- return "";
795
- }
993
+ // Extract all @tokens from input
994
+ const tokens = [];
995
+ const remaining = [];
996
+ for (const part of input.split(/\s+/)) {
997
+ if (part.startsWith("@") && part.length > 1) {
998
+ tokens.push(part.slice(1));
796
999
  }
797
1000
  else {
798
- console.log(` ${c.red}File not found: ${atMatch[1]}${c.reset}`);
1001
+ remaining.push(part);
1002
+ }
1003
+ }
1004
+ if (tokens.length === 0)
1005
+ return input;
1006
+ const filePaths = resolveFileArgs(tokens);
1007
+ if (filePaths.length === 0) {
1008
+ console.log(` ${c.red}No files found for: ${tokens.join(", ")}${c.reset}`);
1009
+ return "";
1010
+ }
1011
+ if (filePaths.length === 1) {
1012
+ // Single file — simple load
1013
+ try {
1014
+ contextText = fs.readFileSync(filePaths[0], "utf-8");
1015
+ contextSource = path.relative(process.cwd(), filePaths[0]) || filePaths[0];
1016
+ const lines = contextText.split("\n").length;
1017
+ console.log(` ${c.green}✓${c.reset} Loaded ${c.bold}${contextText.length.toLocaleString()}${c.reset} chars (${lines} lines) from ${c.underline}${contextSource}${c.reset}`);
1018
+ }
1019
+ catch (err) {
1020
+ console.log(` ${c.red}Could not read file: ${err.message}${c.reset}`);
799
1021
  return "";
800
1022
  }
801
1023
  }
802
- return input;
1024
+ else {
1025
+ // Multiple files — concatenate with separators
1026
+ const { text, count, totalBytes } = loadMultipleFiles(filePaths);
1027
+ contextText = text;
1028
+ contextSource = `${count} files`;
1029
+ console.log(` ${c.green}✓${c.reset} Loaded ${c.bold}${count}${c.reset} files (${(totalBytes / 1024).toFixed(1)}KB total)`);
1030
+ }
1031
+ return remaining.join(" ");
803
1032
  }
804
1033
  // ── Auto-detect URLs ────────────────────────────────────────────────────────
805
1034
  async function detectAndLoadUrl(input) {
@@ -874,7 +1103,11 @@ async function interactive() {
874
1103
  setupRl.close();
875
1104
  }
876
1105
  // Resolve model
877
- currentModel = resolveModel(currentModelId);
1106
+ const initialResolved = resolveModelWithProvider(currentModelId);
1107
+ if (initialResolved) {
1108
+ currentModel = initialResolved.model;
1109
+ currentProviderName = initialResolved.provider;
1110
+ }
878
1111
  if (!currentModel) {
879
1112
  console.log(`\n ${c.red}Model "${currentModelId}" not found.${c.reset}`);
880
1113
  console.log(` Check ${c.bold}RLM_MODEL${c.reset} in your .env file.\n`);
@@ -1004,6 +1237,7 @@ async function interactive() {
1004
1237
  }
1005
1238
  currentModelId = pick;
1006
1239
  currentModel = resolved.model;
1240
+ currentProviderName = resolved.provider;
1007
1241
  console.log(` ${c.green}✓${c.reset} Switched to ${c.bold}${currentModelId}${c.reset}`);
1008
1242
  console.log();
1009
1243
  printStatusLine();
@@ -1064,7 +1298,9 @@ async function interactive() {
1064
1298
  const defaultModel = getDefaultModelForProvider(chosen.piProvider);
1065
1299
  if (defaultModel) {
1066
1300
  currentModelId = defaultModel;
1067
- currentModel = resolveModel(currentModelId);
1301
+ const provResolved = resolveModelWithProvider(currentModelId);
1302
+ currentModel = provResolved?.model;
1303
+ currentProviderName = chosen.piProvider;
1068
1304
  console.log(` ${c.green}✓${c.reset} Switched to ${c.bold}${chosen.name}${c.reset}`);
1069
1305
  console.log(` ${c.green}✓${c.reset} Default model: ${c.bold}${currentModelId}${c.reset}`);
1070
1306
  console.log();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "rlm-cli",
3
- "version": "0.2.14",
3
+ "version": "0.2.16",
4
4
  "description": "Standalone CLI for Recursive Language Models (RLMs) — implements Algorithm 1 from arXiv:2512.24601",
5
5
  "type": "module",
6
6
  "bin": {