@oxecli/oxe 1.0.50 → 1.0.52
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/dist/cli.js +67 -63
- package/dist/config.js +35 -35
- package/dist/engine.js +42 -42
- package/dist/skills.js +10 -4
- package/dist/system.js +2 -2
- package/dist/tools.js +103 -65
- package/dist/ui.js +242 -282
- package/package.json +1 -1
package/dist/tools.js
CHANGED
|
@@ -11,9 +11,6 @@ export { toolLoadSkill };
|
|
|
11
11
|
export function naturalSortKey(s) {
|
|
12
12
|
return s.replace(/\d+/g, (m) => m.padStart(12, "0")).toLowerCase();
|
|
13
13
|
}
|
|
14
|
-
function isGlob(s) {
|
|
15
|
-
return /[*?[]/.test(s);
|
|
16
|
-
}
|
|
17
14
|
function truncateDiffLine(line) {
|
|
18
15
|
const ending = line.endsWith("\n") ? "\n" : "";
|
|
19
16
|
const body = ending ? line.slice(0, -1) : line;
|
|
@@ -24,8 +21,8 @@ function truncateDiffLine(line) {
|
|
|
24
21
|
}
|
|
25
22
|
function displayDiff(pathName, oldContent, newContent) {
|
|
26
23
|
if (oldContent.length + newContent.length > max_diff_source_chars) {
|
|
27
|
-
|
|
28
|
-
`(diff hidden: exceeds ${max_diff_source_chars.toLocaleString()} char limit)\x1b[0m`);
|
|
24
|
+
process.stdout.write(`\x1b[2m${pathName}: ${oldContent.length.toLocaleString()} chars -> ${newContent.length.toLocaleString()} chars ` +
|
|
25
|
+
`(diff hidden: exceeds ${max_diff_source_chars.toLocaleString()} char limit)\x1b[0m\n`);
|
|
29
26
|
return;
|
|
30
27
|
}
|
|
31
28
|
const patch = structuredPatch(pathName, pathName, oldContent, newContent, "", "", { context: max_diff_context_lines });
|
|
@@ -44,8 +41,7 @@ function displayDiff(pathName, oldContent, newContent) {
|
|
|
44
41
|
const removed = contentLines.filter((c) => c.kind === "-").length;
|
|
45
42
|
if (!added && !removed)
|
|
46
43
|
return;
|
|
47
|
-
|
|
48
|
-
console.log(`\x1b[31m-${removed}\x1b[0m \x1b[32m+${added}\x1b[0m`);
|
|
44
|
+
process.stdout.write(`\x1b[90m┌─\x1b[0m \x1b[1m${pathName}\x1b[0m \x1b[32m+${added}\x1b[0m \x1b[31m-${removed}\x1b[0m\n`);
|
|
49
45
|
const shown = [];
|
|
50
46
|
let run = [];
|
|
51
47
|
let lastKind = null;
|
|
@@ -70,17 +66,23 @@ function displayDiff(pathName, oldContent, newContent) {
|
|
|
70
66
|
run.push(c);
|
|
71
67
|
}
|
|
72
68
|
flush();
|
|
73
|
-
const
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
.
|
|
78
|
-
|
|
69
|
+
for (const l of shown) {
|
|
70
|
+
if (l.startsWith("+")) {
|
|
71
|
+
process.stdout.write(`\x1b[90m│\x1b[0m \x1b[32m${l}\x1b[0m\n`);
|
|
72
|
+
}
|
|
73
|
+
else if (l.startsWith("-")) {
|
|
74
|
+
process.stdout.write(`\x1b[90m│\x1b[0m \x1b[31m${l}\x1b[0m\n`);
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
process.stdout.write(`\x1b[90m│\x1b[0m \x1b[2m${l}\x1b[0m\n`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
process.stdout.write(`\x1b[90m└────────────────────────────────────────\x1b[0m\n`);
|
|
79
81
|
}
|
|
80
82
|
export function truncateToolOutput(output, maxChars = max_output_chars) {
|
|
81
83
|
if (output.length > maxChars) {
|
|
82
84
|
return (output.slice(0, maxChars) +
|
|
83
|
-
`\n\n[... Output truncated! Total characters: ${output.length} ...]`);
|
|
85
|
+
`\n\n[... Output truncated! Total characters: ${output.length.toLocaleString()} ...]`);
|
|
84
86
|
}
|
|
85
87
|
return output;
|
|
86
88
|
}
|
|
@@ -129,11 +131,14 @@ function buildLineIndex(p) {
|
|
|
129
131
|
return null;
|
|
130
132
|
}
|
|
131
133
|
finally {
|
|
132
|
-
if (fd !== null)
|
|
134
|
+
if (fd !== null) {
|
|
133
135
|
try {
|
|
134
136
|
fs.closeSync(fd);
|
|
135
137
|
}
|
|
136
|
-
catch {
|
|
138
|
+
catch {
|
|
139
|
+
/* ignore */
|
|
140
|
+
}
|
|
141
|
+
}
|
|
137
142
|
}
|
|
138
143
|
if (base === 0)
|
|
139
144
|
return [[0], 0];
|
|
@@ -224,13 +229,30 @@ export function toolReadFile(pathName, startLine = 1, endLine) {
|
|
|
224
229
|
}
|
|
225
230
|
if (size > max_read_file_bytes) {
|
|
226
231
|
return (`Error: file too large (${size.toLocaleString()} bytes > ${max_read_file_bytes.toLocaleString()} cap). ` +
|
|
227
|
-
|
|
232
|
+
"Use grep or bash to inspect it instead of reading it whole.");
|
|
228
233
|
}
|
|
229
234
|
if (size === 0)
|
|
230
235
|
return "(empty file)";
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
236
|
+
try {
|
|
237
|
+
const head = fs.readFileSync(p, { flag: "r" }).subarray(0, 8192);
|
|
238
|
+
if (head.includes(0))
|
|
239
|
+
return `Error: refusing to read binary file: ${pathName}`;
|
|
240
|
+
}
|
|
241
|
+
catch {
|
|
242
|
+
/* ignore */
|
|
243
|
+
}
|
|
244
|
+
const sVal = typeof startLine === "number"
|
|
245
|
+
? startLine
|
|
246
|
+
: parseInt(String(startLine || 1), 10) || 1;
|
|
247
|
+
const start = Math.max(1, sVal);
|
|
248
|
+
let eVal = null;
|
|
249
|
+
if (endLine != null) {
|
|
250
|
+
const parsed = typeof endLine === "number" ? endLine : parseInt(String(endLine), 10);
|
|
251
|
+
if (!Number.isNaN(parsed))
|
|
252
|
+
eVal = parsed;
|
|
253
|
+
}
|
|
254
|
+
if (eVal != null && eVal < start) {
|
|
255
|
+
return `Error: end_line ${eVal} is before start_line ${start}`;
|
|
234
256
|
}
|
|
235
257
|
let end = null;
|
|
236
258
|
let total = 0;
|
|
@@ -240,9 +262,10 @@ export function toolReadFile(pathName, startLine = 1, endLine) {
|
|
|
240
262
|
if (idx) {
|
|
241
263
|
const [offsets, t] = idx;
|
|
242
264
|
total = t;
|
|
243
|
-
if (start > total)
|
|
265
|
+
if (start > total) {
|
|
244
266
|
return `Error: start_line ${start} exceeds file length (${total} lines)`;
|
|
245
|
-
|
|
267
|
+
}
|
|
268
|
+
end = eVal == null ? total : Math.min(total, eVal);
|
|
246
269
|
capped = end - start + 1 > max_read_lines;
|
|
247
270
|
if (capped)
|
|
248
271
|
end = start + max_read_lines - 1;
|
|
@@ -253,11 +276,12 @@ export function toolReadFile(pathName, startLine = 1, endLine) {
|
|
|
253
276
|
}
|
|
254
277
|
}
|
|
255
278
|
else {
|
|
256
|
-
const collectEnd = Math.min(Math.max(1,
|
|
279
|
+
const collectEnd = Math.min(Math.max(1, eVal != null ? eVal : start + max_read_lines - 1), start + max_read_lines - 1);
|
|
257
280
|
[total, numbered] = streamReadRange(p, start, collectEnd);
|
|
258
|
-
if (start > total)
|
|
281
|
+
if (start > total) {
|
|
259
282
|
return `Error: start_line ${start} exceeds file length (${total} lines)`;
|
|
260
|
-
|
|
283
|
+
}
|
|
284
|
+
end = eVal == null ? total : Math.min(total, eVal);
|
|
261
285
|
capped = end - start + 1 > max_read_lines;
|
|
262
286
|
if (capped) {
|
|
263
287
|
end = start + max_read_lines - 1;
|
|
@@ -286,8 +310,9 @@ export function toolWriteFile(pathName, content) {
|
|
|
286
310
|
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
287
311
|
if (fs.existsSync(p)) {
|
|
288
312
|
const head = fs.readFileSync(p).subarray(0, 8192);
|
|
289
|
-
if (head.includes(0))
|
|
313
|
+
if (head.includes(0)) {
|
|
290
314
|
return `Error: refusing to overwrite binary file: ${pathName}`;
|
|
315
|
+
}
|
|
291
316
|
const oldContent = fs.readFileSync(p, "utf-8");
|
|
292
317
|
displayDiff(pathName, oldContent, content);
|
|
293
318
|
}
|
|
@@ -299,7 +324,7 @@ export function toolWriteFile(pathName, content) {
|
|
|
299
324
|
catch (err) {
|
|
300
325
|
return `Error: ${err}`;
|
|
301
326
|
}
|
|
302
|
-
return `Wrote ${content.length} characters to ${pathName}`;
|
|
327
|
+
return `Wrote ${content.length.toLocaleString()} characters to ${pathName}`;
|
|
303
328
|
}
|
|
304
329
|
// ---------------------------------------------------------------------------
|
|
305
330
|
// edit_file
|
|
@@ -322,9 +347,12 @@ function withNewlines(text, nl) {
|
|
|
322
347
|
return text.replace(/\r\n/g, "\n").replace(/\r/g, "\n").replace(/\n/g, "\r\n");
|
|
323
348
|
}
|
|
324
349
|
function findWsBlocks(text, needle) {
|
|
325
|
-
const needleLines = withNewlines(needle, "\n")
|
|
326
|
-
|
|
350
|
+
const needleLines = withNewlines(needle, "\n")
|
|
351
|
+
.split("\n")
|
|
352
|
+
.map((l) => l.trim());
|
|
353
|
+
while (needleLines.length && needleLines[needleLines.length - 1] === "") {
|
|
327
354
|
needleLines.pop();
|
|
355
|
+
}
|
|
328
356
|
if (!needleLines.length)
|
|
329
357
|
return [];
|
|
330
358
|
const textLines = text.split("\n");
|
|
@@ -391,7 +419,7 @@ export function toolEditFile(pathName, oldString, newString, replaceAll = false)
|
|
|
391
419
|
}
|
|
392
420
|
const count = text.split(match).length - 1;
|
|
393
421
|
if (count > 1 && !replaceAll) {
|
|
394
|
-
return
|
|
422
|
+
return `Error: old_string matches ${count} locations; pass replace_all=true or make old_string more specific`;
|
|
395
423
|
}
|
|
396
424
|
const insert = withNewlines(newString, newline);
|
|
397
425
|
const newContent = replaceAll
|
|
@@ -412,7 +440,7 @@ export function toolEditFile(pathName, oldString, newString, replaceAll = false)
|
|
|
412
440
|
// ---------------------------------------------------------------------------
|
|
413
441
|
function terminateProcessTree(proc) {
|
|
414
442
|
try {
|
|
415
|
-
if (
|
|
443
|
+
if (process.platform === "win32") {
|
|
416
444
|
execFile("taskkill", ["/F", "/T", "/PID", String(proc.pid)], { timeout: 10000 });
|
|
417
445
|
}
|
|
418
446
|
else {
|
|
@@ -420,22 +448,19 @@ function terminateProcessTree(proc) {
|
|
|
420
448
|
process.kill(-proc.pid, "SIGKILL");
|
|
421
449
|
}
|
|
422
450
|
catch {
|
|
423
|
-
proc.kill();
|
|
451
|
+
proc.kill("SIGKILL");
|
|
424
452
|
}
|
|
425
453
|
}
|
|
426
454
|
}
|
|
427
455
|
catch {
|
|
428
456
|
try {
|
|
429
|
-
proc.kill();
|
|
457
|
+
proc.kill("SIGKILL");
|
|
430
458
|
}
|
|
431
459
|
catch {
|
|
432
460
|
/* ignore */
|
|
433
461
|
}
|
|
434
462
|
}
|
|
435
463
|
}
|
|
436
|
-
function isWindowsTool() {
|
|
437
|
-
return process.platform === "win32";
|
|
438
|
-
}
|
|
439
464
|
export function toolBash(command, timeout = 60, cwd) {
|
|
440
465
|
try {
|
|
441
466
|
let t = typeof timeout === "number" ? timeout : parseInt(String(timeout), 10);
|
|
@@ -445,7 +470,7 @@ export function toolBash(command, timeout = 60, cwd) {
|
|
|
445
470
|
return new Promise((resolve) => {
|
|
446
471
|
let child;
|
|
447
472
|
try {
|
|
448
|
-
if (
|
|
473
|
+
if (process.platform === "win32") {
|
|
449
474
|
child = spawn(command, {
|
|
450
475
|
shell: true,
|
|
451
476
|
cwd: cwd || undefined,
|
|
@@ -470,7 +495,10 @@ export function toolBash(command, timeout = 60, cwd) {
|
|
|
470
495
|
const timer = setTimeout(() => {
|
|
471
496
|
if (finished)
|
|
472
497
|
return;
|
|
473
|
-
terminateProcessTree({
|
|
498
|
+
terminateProcessTree({
|
|
499
|
+
pid: child.pid,
|
|
500
|
+
kill: (sig) => child.kill(sig),
|
|
501
|
+
});
|
|
474
502
|
const output = (stdout + stderr).trim();
|
|
475
503
|
let msg = `Error: command timed out after ${t}s`;
|
|
476
504
|
if (output)
|
|
@@ -504,17 +532,6 @@ export function toolBash(command, timeout = 60, cwd) {
|
|
|
504
532
|
// ---------------------------------------------------------------------------
|
|
505
533
|
// glob / grep
|
|
506
534
|
// ---------------------------------------------------------------------------
|
|
507
|
-
function pathUnderIgnoredDir(base, f) {
|
|
508
|
-
const rel = path.relative(base, f);
|
|
509
|
-
if (rel.startsWith("..") || path.isAbsolute(rel))
|
|
510
|
-
return false;
|
|
511
|
-
const parts = rel.split(/[\\/]/);
|
|
512
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
513
|
-
if (ignoredDirs.has(parts[i]))
|
|
514
|
-
return true;
|
|
515
|
-
}
|
|
516
|
-
return false;
|
|
517
|
-
}
|
|
518
535
|
function matchParts(parts, relParts) {
|
|
519
536
|
if (!parts.length)
|
|
520
537
|
return relParts.length === 0;
|
|
@@ -529,13 +546,12 @@ function matchParts(parts, relParts) {
|
|
|
529
546
|
globMatch(relParts[0], parts[0]) &&
|
|
530
547
|
matchParts(parts.slice(1), relParts.slice(1)));
|
|
531
548
|
}
|
|
532
|
-
// minimal glob matcher (supports * ? [..])
|
|
533
549
|
function globMatch(name, pattern) {
|
|
534
550
|
const regex = pattern
|
|
535
551
|
.replace(/[.+^${}()|[\]\\]/g, "\\$&")
|
|
536
552
|
.replace(/\*/g, ".*")
|
|
537
553
|
.replace(/\?/g, ".");
|
|
538
|
-
return new RegExp(`^${regex}
|
|
554
|
+
return new RegExp(`^${regex}$`, "i").test(name);
|
|
539
555
|
}
|
|
540
556
|
function walkFiles(root, current, rel, parts, out) {
|
|
541
557
|
let entries;
|
|
@@ -557,7 +573,10 @@ function walkFiles(root, current, rel, parts, out) {
|
|
|
557
573
|
}
|
|
558
574
|
else if (entry.isFile()) {
|
|
559
575
|
if (matchParts(parts, rel.concat(name))) {
|
|
560
|
-
out.push([
|
|
576
|
+
out.push([
|
|
577
|
+
path.join(root, ...rel, name),
|
|
578
|
+
fs.statSync(path.join(root, ...rel, name)).size,
|
|
579
|
+
]);
|
|
561
580
|
}
|
|
562
581
|
}
|
|
563
582
|
}
|
|
@@ -614,7 +633,7 @@ export function toolGlob(pattern, pathName = ".", limit = 200) {
|
|
|
614
633
|
const base = pathName;
|
|
615
634
|
if (!fs.existsSync(base))
|
|
616
635
|
return `Error: path not found: ${pathName}`;
|
|
617
|
-
|
|
636
|
+
const lim = typeof limit === "number" ? limit : parseInt(String(limit), 10) || 200;
|
|
618
637
|
let matches;
|
|
619
638
|
try {
|
|
620
639
|
matches = rglobPruned(base, pattern);
|
|
@@ -626,9 +645,9 @@ export function toolGlob(pattern, pathName = ".", limit = 200) {
|
|
|
626
645
|
if (!matches.length)
|
|
627
646
|
return "No files matched";
|
|
628
647
|
const total = matches.length;
|
|
629
|
-
let result = matches.slice(0,
|
|
630
|
-
if (total >
|
|
631
|
-
result += `\n\n[... ${total -
|
|
648
|
+
let result = matches.slice(0, lim).map(([p]) => p).join("\n");
|
|
649
|
+
if (total > lim) {
|
|
650
|
+
result += `\n\n[... ${total - lim} more matches not shown ...]`;
|
|
632
651
|
}
|
|
633
652
|
return result;
|
|
634
653
|
}
|
|
@@ -638,12 +657,13 @@ export function toolGrep(pattern, pathName = ".", glob = "*", limit = 200) {
|
|
|
638
657
|
return `Error: path not found: ${pathName}`;
|
|
639
658
|
let regex;
|
|
640
659
|
try {
|
|
641
|
-
regex = new RegExp(pattern);
|
|
660
|
+
regex = new RegExp(pattern, "i");
|
|
642
661
|
}
|
|
643
662
|
catch (err) {
|
|
644
663
|
return `Error: invalid regex pattern: ${err}`;
|
|
645
664
|
}
|
|
646
665
|
glob = glob || "*";
|
|
666
|
+
const lim = typeof limit === "number" ? limit : parseInt(String(limit), 10) || 200;
|
|
647
667
|
let targets;
|
|
648
668
|
if (fs.statSync(base).isFile()) {
|
|
649
669
|
targets =
|
|
@@ -670,7 +690,7 @@ export function toolGrep(pattern, pathName = ".", glob = "*", limit = 200) {
|
|
|
670
690
|
const line = capLine(lines[i]);
|
|
671
691
|
if (regex.test(line)) {
|
|
672
692
|
hits.push(`${fpath}:${i + 1}:${line}`);
|
|
673
|
-
if (hits.length >=
|
|
693
|
+
if (hits.length >= lim)
|
|
674
694
|
break;
|
|
675
695
|
}
|
|
676
696
|
}
|
|
@@ -678,7 +698,7 @@ export function toolGrep(pattern, pathName = ".", glob = "*", limit = 200) {
|
|
|
678
698
|
catch {
|
|
679
699
|
continue;
|
|
680
700
|
}
|
|
681
|
-
if (hits.length >=
|
|
701
|
+
if (hits.length >= lim)
|
|
682
702
|
break;
|
|
683
703
|
}
|
|
684
704
|
let result = hits.length ? hits.join("\n") : "No matches found";
|
|
@@ -710,7 +730,10 @@ export const RESPONSES_TOOLS = [
|
|
|
710
730
|
type: "object",
|
|
711
731
|
properties: {
|
|
712
732
|
path: { type: "string", description: "Path to the file" },
|
|
713
|
-
start_line: {
|
|
733
|
+
start_line: {
|
|
734
|
+
type: "integer",
|
|
735
|
+
description: "First line to read, 1-indexed (default 1)",
|
|
736
|
+
},
|
|
714
737
|
end_line: {
|
|
715
738
|
type: "integer",
|
|
716
739
|
description: `Last line to read, inclusive (default: end of file, capped at ${max_read_lines} lines from start_line)`,
|
|
@@ -784,8 +807,14 @@ export const RESPONSES_TOOLS = [
|
|
|
784
807
|
type: "object",
|
|
785
808
|
properties: {
|
|
786
809
|
pattern: { type: "string", description: "Glob pattern, e.g. '*.py'" },
|
|
787
|
-
path: {
|
|
788
|
-
|
|
810
|
+
path: {
|
|
811
|
+
type: "string",
|
|
812
|
+
description: "Directory to search from (default '.')",
|
|
813
|
+
},
|
|
814
|
+
limit: {
|
|
815
|
+
type: "integer",
|
|
816
|
+
description: "Max number of matches to return (default 200)",
|
|
817
|
+
},
|
|
789
818
|
},
|
|
790
819
|
required: ["pattern"],
|
|
791
820
|
},
|
|
@@ -798,13 +827,22 @@ export const RESPONSES_TOOLS = [
|
|
|
798
827
|
parameters: {
|
|
799
828
|
type: "object",
|
|
800
829
|
properties: {
|
|
801
|
-
pattern: {
|
|
802
|
-
|
|
830
|
+
pattern: {
|
|
831
|
+
type: "string",
|
|
832
|
+
description: "Regular expression to search for",
|
|
833
|
+
},
|
|
834
|
+
path: {
|
|
835
|
+
type: "string",
|
|
836
|
+
description: "Directory to search from (default '.')",
|
|
837
|
+
},
|
|
803
838
|
glob: {
|
|
804
839
|
type: "string",
|
|
805
840
|
description: "Only search files matching this glob (default '*')",
|
|
806
841
|
},
|
|
807
|
-
limit: {
|
|
842
|
+
limit: {
|
|
843
|
+
type: "integer",
|
|
844
|
+
description: "Max number of matches to return (default 200)",
|
|
845
|
+
},
|
|
808
846
|
},
|
|
809
847
|
required: ["pattern"],
|
|
810
848
|
},
|