rlm-cli 0.2.17 → 0.2.19
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 +39 -15
- package/dist/repl.js +11 -2
- 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 {
|
|
@@ -677,29 +687,40 @@ function walkDir(dir) {
|
|
|
677
687
|
return results;
|
|
678
688
|
}
|
|
679
689
|
for (const entry of entries) {
|
|
690
|
+
if (results.length >= MAX_FILES)
|
|
691
|
+
break;
|
|
680
692
|
if (entry.name.startsWith(".") && entry.name !== ".env")
|
|
681
693
|
continue;
|
|
694
|
+
if (entry.isSymbolicLink())
|
|
695
|
+
continue;
|
|
682
696
|
const full = path.join(dir, entry.name);
|
|
683
697
|
if (entry.isDirectory()) {
|
|
684
698
|
if (SKIP_DIRS.has(entry.name))
|
|
685
699
|
continue;
|
|
686
|
-
|
|
700
|
+
const sub = walkDir(full, depth + 1);
|
|
701
|
+
const remaining = MAX_FILES - results.length;
|
|
702
|
+
results.push(...sub.slice(0, remaining));
|
|
687
703
|
}
|
|
688
704
|
else if (entry.isFile()) {
|
|
689
705
|
if (!isBinaryFile(full))
|
|
690
706
|
results.push(full);
|
|
691
707
|
}
|
|
692
|
-
if (results.length > MAX_FILES)
|
|
693
|
-
break;
|
|
694
708
|
}
|
|
695
709
|
return results;
|
|
696
710
|
}
|
|
697
|
-
|
|
698
|
-
|
|
711
|
+
/** Normalize path separators to forward slash for consistent matching. */
|
|
712
|
+
function toForwardSlash(p) {
|
|
713
|
+
return p.replace(/\\/g, "/");
|
|
714
|
+
}
|
|
715
|
+
function simpleGlobMatch(pattern, filePath, _braceDepth = 0) {
|
|
716
|
+
// Normalize both to forward slashes for cross-platform matching
|
|
717
|
+
pattern = toForwardSlash(pattern);
|
|
718
|
+
filePath = toForwardSlash(filePath);
|
|
719
|
+
// Expand {a,b,c} braces into alternatives (with depth limit)
|
|
699
720
|
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));
|
|
721
|
+
if (braceMatch && _braceDepth < 5) {
|
|
722
|
+
const alternatives = braceMatch[1].split(",").slice(0, 50);
|
|
723
|
+
return alternatives.some((alt) => simpleGlobMatch(pattern.replace(braceMatch[0], alt.trim()), filePath, _braceDepth + 1));
|
|
703
724
|
}
|
|
704
725
|
// Convert glob to regex
|
|
705
726
|
let regex = "^";
|
|
@@ -740,8 +761,9 @@ function resolveFileArgs(args) {
|
|
|
740
761
|
// Glob pattern (contains * or ?)
|
|
741
762
|
if (arg.includes("*") || arg.includes("?")) {
|
|
742
763
|
// Find the base directory (portion before the first glob char)
|
|
743
|
-
const
|
|
744
|
-
const
|
|
764
|
+
const normalized = toForwardSlash(arg);
|
|
765
|
+
const firstGlob = normalized.search(/[*?{]/);
|
|
766
|
+
const baseDir = firstGlob > 0 ? path.resolve(normalized.slice(0, normalized.lastIndexOf("/", firstGlob) + 1) || ".") : process.cwd();
|
|
745
767
|
const allFiles = walkDir(baseDir);
|
|
746
768
|
for (const f of allFiles) {
|
|
747
769
|
const rel = path.relative(process.cwd(), f);
|
|
@@ -971,7 +993,8 @@ function extractFilePath(input) {
|
|
|
971
993
|
return { filePath, query };
|
|
972
994
|
}
|
|
973
995
|
}
|
|
974
|
-
|
|
996
|
+
// Unix absolute path (/...) or Windows absolute path (C:\...)
|
|
997
|
+
const absPathMatch = input.match(/((?:\/|[A-Za-z]:[\\\/])[^\s]+)/);
|
|
975
998
|
if (absPathMatch) {
|
|
976
999
|
const filePath = absPathMatch[1];
|
|
977
1000
|
if (fs.existsSync(filePath)) {
|
|
@@ -979,7 +1002,8 @@ function extractFilePath(input) {
|
|
|
979
1002
|
return { filePath, query };
|
|
980
1003
|
}
|
|
981
1004
|
}
|
|
982
|
-
|
|
1005
|
+
// Relative path with forward or back slashes
|
|
1006
|
+
const relPathMatch = input.match(/([\w\-\.]+[\/\\][\w\-\.\/\\]+\.\w{2,6})/);
|
|
983
1007
|
if (relPathMatch) {
|
|
984
1008
|
const filePath = path.resolve(relPathMatch[1]);
|
|
985
1009
|
if (fs.existsSync(filePath)) {
|
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/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();
|