rlm-cli 0.2.16 → 0.2.18
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 +2 -0
- package/dist/cli.js +13 -1
- package/dist/interactive.js +25 -12
- package/dist/repl.js +11 -2
- package/dist/rlm.js +1 -1
- package/dist/viewer.js +10 -2
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/cli.js
CHANGED
|
@@ -91,9 +91,15 @@ function parseArgs() {
|
|
|
91
91
|
return { modelId, file, url, useStdin, verbose, query };
|
|
92
92
|
}
|
|
93
93
|
// ── Helpers ─────────────────────────────────────────────────────────────────
|
|
94
|
+
const MAX_STDIN_BYTES = 50 * 1024 * 1024; // 50MB
|
|
94
95
|
async function readStdin() {
|
|
95
96
|
const chunks = [];
|
|
97
|
+
let total = 0;
|
|
96
98
|
for await (const chunk of process.stdin) {
|
|
99
|
+
total += chunk.length;
|
|
100
|
+
if (total > MAX_STDIN_BYTES) {
|
|
101
|
+
throw new Error(`stdin exceeds ${MAX_STDIN_BYTES / 1024 / 1024}MB limit`);
|
|
102
|
+
}
|
|
97
103
|
chunks.push(chunk);
|
|
98
104
|
}
|
|
99
105
|
return Buffer.concat(chunks).toString("utf-8");
|
|
@@ -129,7 +135,13 @@ async function main() {
|
|
|
129
135
|
let context;
|
|
130
136
|
if (args.file) {
|
|
131
137
|
console.error(`Reading context from file: ${args.file}`);
|
|
132
|
-
|
|
138
|
+
try {
|
|
139
|
+
context = fs.readFileSync(args.file, "utf-8");
|
|
140
|
+
}
|
|
141
|
+
catch (err) {
|
|
142
|
+
console.error(`Error: could not read file "${args.file}": ${err.message}`);
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
133
145
|
}
|
|
134
146
|
else if (args.url) {
|
|
135
147
|
console.error(`Fetching context from URL: ${args.url}`);
|
package/dist/interactive.js
CHANGED
|
@@ -652,11 +652,11 @@ function isBinaryFile(filePath) {
|
|
|
652
652
|
if (BINARY_EXTENSIONS.has(ext))
|
|
653
653
|
return true;
|
|
654
654
|
// Quick null-byte check on first 512 bytes
|
|
655
|
+
let fd;
|
|
655
656
|
try {
|
|
656
|
-
|
|
657
|
+
fd = fs.openSync(filePath, "r");
|
|
657
658
|
const buf = Buffer.alloc(512);
|
|
658
659
|
const bytesRead = fs.readSync(fd, buf, 0, 512, 0);
|
|
659
|
-
fs.closeSync(fd);
|
|
660
660
|
for (let i = 0; i < bytesRead; i++) {
|
|
661
661
|
if (buf[i] === 0)
|
|
662
662
|
return true;
|
|
@@ -665,9 +665,19 @@ function isBinaryFile(filePath) {
|
|
|
665
665
|
catch { /* unreadable → skip */
|
|
666
666
|
return true;
|
|
667
667
|
}
|
|
668
|
+
finally {
|
|
669
|
+
if (fd !== undefined)
|
|
670
|
+
try {
|
|
671
|
+
fs.closeSync(fd);
|
|
672
|
+
}
|
|
673
|
+
catch { }
|
|
674
|
+
}
|
|
668
675
|
return false;
|
|
669
676
|
}
|
|
670
|
-
|
|
677
|
+
const MAX_DIR_DEPTH = 30;
|
|
678
|
+
function walkDir(dir, depth = 0) {
|
|
679
|
+
if (depth > MAX_DIR_DEPTH)
|
|
680
|
+
return [];
|
|
671
681
|
const results = [];
|
|
672
682
|
let entries;
|
|
673
683
|
try {
|
|
@@ -679,11 +689,13 @@ function walkDir(dir) {
|
|
|
679
689
|
for (const entry of entries) {
|
|
680
690
|
if (entry.name.startsWith(".") && entry.name !== ".env")
|
|
681
691
|
continue;
|
|
692
|
+
if (entry.isSymbolicLink())
|
|
693
|
+
continue;
|
|
682
694
|
const full = path.join(dir, entry.name);
|
|
683
695
|
if (entry.isDirectory()) {
|
|
684
696
|
if (SKIP_DIRS.has(entry.name))
|
|
685
697
|
continue;
|
|
686
|
-
results.push(...walkDir(full));
|
|
698
|
+
results.push(...walkDir(full, depth + 1));
|
|
687
699
|
}
|
|
688
700
|
else if (entry.isFile()) {
|
|
689
701
|
if (!isBinaryFile(full))
|
|
@@ -694,12 +706,12 @@ function walkDir(dir) {
|
|
|
694
706
|
}
|
|
695
707
|
return results;
|
|
696
708
|
}
|
|
697
|
-
function simpleGlobMatch(pattern, filePath) {
|
|
698
|
-
// Expand {a,b,c} braces into alternatives
|
|
709
|
+
function simpleGlobMatch(pattern, filePath, _braceDepth = 0) {
|
|
710
|
+
// Expand {a,b,c} braces into alternatives (with depth limit)
|
|
699
711
|
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));
|
|
712
|
+
if (braceMatch && _braceDepth < 5) {
|
|
713
|
+
const alternatives = braceMatch[1].split(",").slice(0, 50);
|
|
714
|
+
return alternatives.some((alt) => simpleGlobMatch(pattern.replace(braceMatch[0], alt.trim()), filePath, _braceDepth + 1));
|
|
703
715
|
}
|
|
704
716
|
// Convert glob to regex
|
|
705
717
|
let regex = "^";
|
|
@@ -1092,6 +1104,7 @@ async function interactive() {
|
|
|
1092
1104
|
process.exit(0);
|
|
1093
1105
|
}
|
|
1094
1106
|
// Auto-select default model for chosen provider
|
|
1107
|
+
currentProviderName = provider.piProvider;
|
|
1095
1108
|
const defaultModel = getDefaultModelForProvider(provider.piProvider);
|
|
1096
1109
|
if (defaultModel) {
|
|
1097
1110
|
currentModelId = defaultModel;
|
|
@@ -1200,7 +1213,7 @@ async function interactive() {
|
|
|
1200
1213
|
break;
|
|
1201
1214
|
case "model":
|
|
1202
1215
|
case "m": {
|
|
1203
|
-
const curProvider = detectProvider();
|
|
1216
|
+
const curProvider = currentProviderName || detectProvider();
|
|
1204
1217
|
if (arg) {
|
|
1205
1218
|
// Accept a number (from current provider list) or a model ID
|
|
1206
1219
|
const curModels = getModelsForProvider(curProvider);
|
|
@@ -1265,7 +1278,7 @@ async function interactive() {
|
|
|
1265
1278
|
}
|
|
1266
1279
|
case "provider":
|
|
1267
1280
|
case "prov": {
|
|
1268
|
-
const curProvider = detectProvider();
|
|
1281
|
+
const curProvider = currentProviderName || detectProvider();
|
|
1269
1282
|
const curLabel = findSetupProvider(curProvider)?.name || curProvider;
|
|
1270
1283
|
console.log(`\n ${c.bold}Current provider:${c.reset} ${c.cyan}${curLabel}${c.reset}\n`);
|
|
1271
1284
|
for (let i = 0; i < SETUP_PROVIDERS.length; i++) {
|
|
@@ -1300,7 +1313,7 @@ async function interactive() {
|
|
|
1300
1313
|
currentModelId = defaultModel;
|
|
1301
1314
|
const provResolved = resolveModelWithProvider(currentModelId);
|
|
1302
1315
|
currentModel = provResolved?.model;
|
|
1303
|
-
currentProviderName = chosen.piProvider;
|
|
1316
|
+
currentProviderName = provResolved?.provider || chosen.piProvider;
|
|
1304
1317
|
console.log(` ${c.green}✓${c.reset} Switched to ${c.bold}${chosen.name}${c.reset}`);
|
|
1305
1318
|
console.log(` ${c.green}✓${c.reset} Default model: ${c.bold}${currentModelId}${c.reset}`);
|
|
1306
1319
|
console.log();
|
package/dist/repl.js
CHANGED
|
@@ -32,13 +32,18 @@ export class PythonRepl {
|
|
|
32
32
|
return;
|
|
33
33
|
const runtimePath = path.join(path.dirname(fileURLToPath(import.meta.url)), "runtime.py");
|
|
34
34
|
const pythonCmd = process.platform === "win32" ? "python" : "python3";
|
|
35
|
+
const homeDir = os.homedir();
|
|
35
36
|
this.proc = spawn(pythonCmd, [runtimePath], {
|
|
36
37
|
stdio: ["pipe", "pipe", "pipe"],
|
|
37
38
|
env: {
|
|
38
39
|
// Only pass what Python actually needs — not API keys or secrets
|
|
39
40
|
PATH: process.env.PATH,
|
|
40
|
-
HOME:
|
|
41
|
+
HOME: homeDir,
|
|
42
|
+
USERPROFILE: homeDir, // Windows uses USERPROFILE
|
|
41
43
|
PYTHONUNBUFFERED: "1",
|
|
44
|
+
// Windows needs SystemRoot/SYSTEMROOT for Python to find DLLs
|
|
45
|
+
...(process.env.SystemRoot ? { SystemRoot: process.env.SystemRoot } : {}),
|
|
46
|
+
...(process.env.SYSTEMROOT ? { SYSTEMROOT: process.env.SYSTEMROOT } : {}),
|
|
42
47
|
},
|
|
43
48
|
});
|
|
44
49
|
this.rl = readline.createInterface({ input: this.proc.stdout });
|
|
@@ -93,7 +98,11 @@ export class PythonRepl {
|
|
|
93
98
|
catch {
|
|
94
99
|
// stdin may already be closed
|
|
95
100
|
}
|
|
96
|
-
|
|
101
|
+
// SIGTERM is ignored on Windows; use SIGKILL as fallback
|
|
102
|
+
try {
|
|
103
|
+
this.proc.kill(process.platform === "win32" ? "SIGKILL" : "SIGTERM");
|
|
104
|
+
}
|
|
105
|
+
catch { /* already dead */ }
|
|
97
106
|
}
|
|
98
107
|
this.cleanup();
|
|
99
108
|
}
|
package/dist/rlm.js
CHANGED
|
@@ -247,7 +247,7 @@ export async function runRlmLoop(options) {
|
|
|
247
247
|
const errMsg = response.errorMessage;
|
|
248
248
|
if (errMsg.includes("authentication") || errMsg.includes("401")) {
|
|
249
249
|
return {
|
|
250
|
-
answer: `[API Authentication Error] ${errMsg}\n\nCheck your
|
|
250
|
+
answer: `[API Authentication Error] ${errMsg}\n\nCheck your API key in .env or run /provider to reconfigure.`,
|
|
251
251
|
iterations: iteration,
|
|
252
252
|
totalSubQueries,
|
|
253
253
|
completed: false,
|
package/dist/viewer.js
CHANGED
|
@@ -614,9 +614,17 @@ function syntaxHighlight(code) {
|
|
|
614
614
|
async function main() {
|
|
615
615
|
// Enter alternate screen buffer so output never scrolls the main terminal
|
|
616
616
|
W(c.altScreenOn);
|
|
617
|
-
// Ensure we always
|
|
618
|
-
const cleanup = () =>
|
|
617
|
+
// Ensure we always restore terminal on exit (alt screen, cursor, raw mode)
|
|
618
|
+
const cleanup = () => {
|
|
619
|
+
try {
|
|
620
|
+
process.stdin.setRawMode(false);
|
|
621
|
+
}
|
|
622
|
+
catch { }
|
|
623
|
+
W(c.showCursor, c.altScreenOff);
|
|
624
|
+
};
|
|
619
625
|
process.on("exit", cleanup);
|
|
626
|
+
process.on("SIGINT", () => { cleanup(); process.exit(0); });
|
|
627
|
+
process.on("SIGTERM", () => { cleanup(); process.exit(0); });
|
|
620
628
|
let filePath = process.argv[2];
|
|
621
629
|
if (!filePath) {
|
|
622
630
|
const files = listTrajectories();
|