@oxecli/oxe 1.0.75 → 1.0.77

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/engine.js CHANGED
@@ -103,7 +103,7 @@ export class InferenceEngine {
103
103
  result = await toolBash(args.command, args.timeout, args.cwd);
104
104
  break;
105
105
  case "glob":
106
- result = toolGlob(args.pattern, args.path, args.limit);
106
+ result = toolGlob(args.pattern, args.path, args.limit, args.depth);
107
107
  break;
108
108
  case "grep":
109
109
  result = toolGrep(args.pattern, args.path, args.glob, args.limit);
package/dist/tools.js CHANGED
@@ -44,7 +44,7 @@ function displayDiff(pathName, oldContent, newContent) {
44
44
  if (oldContent.length + newContent.length > max_diff_source_chars) {
45
45
  pendingDiffOutput.push(`\x1b[90m${pathName}: ${oldContent.length.toLocaleString()} chars -> ${newContent.length.toLocaleString()} chars ` +
46
46
  `(diff hidden: exceeds ${max_diff_source_chars.toLocaleString()} char limit)\x1b[0m`);
47
- return;
47
+ return null;
48
48
  }
49
49
  const patch = structuredPatch(pathName, pathName, oldContent, newContent, "", "", { context: max_diff_context_lines });
50
50
  const contentLines = [];
@@ -74,7 +74,7 @@ function displayDiff(pathName, oldContent, newContent) {
74
74
  const added = contentLines.filter((c) => c.kind === "+").length;
75
75
  const removed = contentLines.filter((c) => c.kind === "-").length;
76
76
  if (!added && !removed)
77
- return;
77
+ return null;
78
78
  const shown = [];
79
79
  let run = [];
80
80
  let lastKind = null;
@@ -121,51 +121,30 @@ function displayDiff(pathName, oldContent, newContent) {
121
121
  if (v.newNum)
122
122
  maxNum = Math.max(maxNum, v.newNum);
123
123
  }
124
- const numW = Math.max(String(maxNum).length, 2);
125
- let contentW = 0;
126
- for (const v of visible) {
127
- const len = v.kind === "~" ? plainLen(v.body) : numW + 1 + 2 + plainLen(v.body);
128
- contentW = Math.max(contentW, len);
129
- }
130
- const headerPlain = `${pathName} +${added} -${removed}`;
131
- const boxW = Math.min(Math.max(contentW + 6, plainLen(headerPlain) + 6), Math.max(termW - 2, 20));
132
- const innerW = Math.max(boxW - 4, 1);
133
- const maxPath = Math.max(10, boxW - 2 - plainLen(` +${added} -${removed}`) - 3);
134
- let pathDisp = pathName;
135
- if (plainLen(pathDisp) > maxPath)
136
- pathDisp = truncateStyled(pathDisp, Math.max(maxPath - 1, 1)) + "…";
137
- const header = `\x1b[1m${pathDisp}\x1b[0m \x1b[32m+${added}\x1b[0m \x1b[31m-${removed}\x1b[0m`;
138
- const headerStr = `─ ${header} `;
139
- const top = `╭${headerStr}${"─".repeat(Math.max(0, boxW - 2 - plainLen(headerStr)))}╮`;
140
- pendingDiffOutput.push(`\x1b[90m${top}\x1b[0m`);
141
- const bodyMax = Math.max(innerW - numW - 3, 1);
124
+ const numW = String(maxNum).length;
125
+ const bodyMax = Math.max(termW - numW - 4, 20);
142
126
  const gap = " ".repeat(numW);
143
127
  for (const v of visible) {
144
128
  if (v.kind === "~") {
145
- const body = truncateStyled(v.body, innerW - 2);
146
- const text = `\x1b[90m${gap} ${body}\x1b[0m`;
147
- const fill = Math.max(0, innerW - plainLen(text));
148
- pendingDiffOutput.push(`\x1b[90m│\x1b[0m ${text}${" ".repeat(fill)} \x1b[90m│\x1b[0m`);
129
+ const body = truncateStyled(v.body, bodyMax);
130
+ pendingDiffOutput.push(`\x1b[90m${gap} ${body}\x1b[0m`);
149
131
  continue;
150
132
  }
151
133
  const num = v.kind === "+" ? v.newNum : v.oldNum;
152
134
  const numStr = num ? String(num).padStart(numW, " ") : gap;
153
135
  if (v.kind === "+" || v.kind === "-") {
154
- const color = v.kind === "+" ? "\x1b[32m" : "\x1b[31m";
136
+ const bg = v.kind === "+" ? "\x1b[42m" : "\x1b[41m";
137
+ const sign = v.kind === "+" ? "+" : "-";
155
138
  const body = truncateStyled(v.body, bodyMax);
156
- const text = `${numStr} ${v.kind === "+" ? "+ " : "- "}${body}`;
157
- const fill = Math.max(0, innerW - plainLen(text));
158
- // Whole line is painted green/red, filling to the right edge.
159
- pendingDiffOutput.push(`\x1b[90m│\x1b[0m ${color}${text}${" ".repeat(fill)}\x1b[0m \x1b[90m│\x1b[0m`);
139
+ const fill = Math.max(0, termW - numW - 4 - plainLen(body));
140
+ pendingDiffOutput.push(`${bg}\x1b[97m${numStr} ${sign} ${body}${" ".repeat(fill)}\x1b[0m`);
160
141
  }
161
142
  else {
162
143
  const body = truncateStyled(v.kind === "\\" ? v.body : highlightCodeLine(v.body), bodyMax);
163
- const text = `\x1b[90m${numStr} ${body}\x1b[0m`;
164
- const fill = Math.max(0, innerW - plainLen(text));
165
- pendingDiffOutput.push(`\x1b[90m│\x1b[0m ${text}${" ".repeat(fill)} \x1b[90m│\x1b[0m`);
144
+ pendingDiffOutput.push(`\x1b[90m${numStr} ${body}\x1b[0m`);
166
145
  }
167
146
  }
168
- pendingDiffOutput.push(`\x1b[90m╰${"─".repeat(boxW - 2)}╯\x1b[0m`);
147
+ return { added, removed };
169
148
  }
170
149
  export function truncateToolOutput(output, maxChars = max_output_chars) {
171
150
  if (output.length > maxChars) {
@@ -491,7 +470,6 @@ export function toolEditFile(pathName, oldString, newString, replaceAll = false)
491
470
  const text = data.toString("utf-8");
492
471
  const newline = detectNewline(text);
493
472
  let match;
494
- let normalized = false;
495
473
  if (text.includes(oldString)) {
496
474
  match = oldString;
497
475
  }
@@ -499,7 +477,6 @@ export function toolEditFile(pathName, oldString, newString, replaceAll = false)
499
477
  const norm = withNewlines(oldString, newline);
500
478
  if (text.includes(norm)) {
501
479
  match = norm;
502
- normalized = true;
503
480
  }
504
481
  else {
505
482
  const blocks = findWsBlocks(text, oldString);
@@ -508,7 +485,6 @@ export function toolEditFile(pathName, oldString, newString, replaceAll = false)
508
485
  "(line endings and per-line leading/trailing whitespace are normalized for matching)");
509
486
  }
510
487
  match = blocks[0];
511
- normalized = true;
512
488
  }
513
489
  }
514
490
  const count = text.split(match).length - 1;
@@ -519,15 +495,17 @@ export function toolEditFile(pathName, oldString, newString, replaceAll = false)
519
495
  const newContent = replaceAll
520
496
  ? text.split(match).join(insert)
521
497
  : text.replace(match, insert);
522
- displayDiff(pathName, text, newContent);
498
+ const summary = displayDiff(pathName, text, newContent);
523
499
  try {
524
500
  fs.writeFileSync(p, newContent, "utf-8");
525
501
  }
526
502
  catch (err) {
527
503
  return `Error: ${err}`;
528
504
  }
529
- return (`Edited ${pathName} (${count} occurrence${count !== 1 ? "s" : ""} replaced` +
530
- `${normalized ? "; line endings and per-line whitespace normalized" : ""})`);
505
+ if (!summary)
506
+ return `Edited ${pathName}`;
507
+ return (`Added ${summary.added} line${summary.added !== 1 ? "s" : ""}, ` +
508
+ `removed ${summary.removed} line${summary.removed !== 1 ? "s" : ""}`);
531
509
  }
532
510
  // ---------------------------------------------------------------------------
533
511
  // bash
@@ -651,7 +629,7 @@ function globMatch(name, pattern) {
651
629
  .replace(/\?/g, ".");
652
630
  return new RegExp(`^${regex}$`, "i").test(name);
653
631
  }
654
- function walkFiles(root, current, rel, parts, out) {
632
+ function walkFiles(root, current, rel, parts, out, depthLeft) {
655
633
  let entries;
656
634
  try {
657
635
  entries = fs.readdirSync(current, { withFileTypes: true });
@@ -665,8 +643,10 @@ function walkFiles(root, current, rel, parts, out) {
665
643
  if (entry.isDirectory()) {
666
644
  if (ignoredDirs.has(name))
667
645
  continue;
646
+ if (depthLeft <= 0)
647
+ continue;
668
648
  rel.push(name);
669
- walkFiles(root, path.join(current, name), rel, parts, out);
649
+ walkFiles(root, path.join(current, name), rel, parts, out, depthLeft - 1);
670
650
  rel.pop();
671
651
  }
672
652
  else if (entry.isFile()) {
@@ -683,13 +663,13 @@ function walkFiles(root, current, rel, parts, out) {
683
663
  }
684
664
  }
685
665
  }
686
- function rglobPruned(base, pattern) {
666
+ function rglobPruned(base, pattern, depth = 12) {
687
667
  const norm = pattern.replace(/\\/g, "/");
688
668
  const parts = norm.split("/").filter((p) => p !== "" && p !== ".");
689
669
  const hasDotDot = parts.includes("..");
690
670
  if (hasDotDot) {
691
671
  const out = [];
692
- const walk = (dir) => {
672
+ const walk = (dir, depthLeft) => {
693
673
  let entries;
694
674
  try {
695
675
  entries = fs.readdirSync(dir, { withFileTypes: true });
@@ -700,8 +680,8 @@ function rglobPruned(base, pattern) {
700
680
  for (const e of entries) {
701
681
  const full = path.join(dir, e.name);
702
682
  if (e.isDirectory()) {
703
- if (!ignoredDirs.has(e.name))
704
- walk(full);
683
+ if (!ignoredDirs.has(e.name) && depthLeft > 0)
684
+ walk(full, depthLeft - 1);
705
685
  }
706
686
  else if (e.isFile()) {
707
687
  out.push([full, fs.statSync(full).size]);
@@ -712,7 +692,7 @@ function rglobPruned(base, pattern) {
712
692
  out.push([base, fs.statSync(base).size]);
713
693
  }
714
694
  else {
715
- walk(base);
695
+ walk(base, depth);
716
696
  }
717
697
  return out;
718
698
  }
@@ -724,17 +704,18 @@ function rglobPruned(base, pattern) {
724
704
  if (!parts.length)
725
705
  return [];
726
706
  const out = [];
727
- walkFiles(base, base, [], ["**", ...parts], out);
707
+ walkFiles(base, base, [], ["**", ...parts], out, depth);
728
708
  return out;
729
709
  }
730
- export function toolGlob(pattern, pathName = ".", limit = 200) {
710
+ export function toolGlob(pattern, pathName = ".", limit = 200, depth = 12) {
731
711
  const base = sanitizePath(pathName) || ".";
732
712
  if (!fs.existsSync(base))
733
713
  return `Error: path not found: ${pathName}`;
734
714
  const lim = typeof limit === "number" ? limit : parseInt(String(limit), 10) || 200;
715
+ const dep = Math.max(0, typeof depth === "number" ? depth : parseInt(String(depth), 10) || 12);
735
716
  let matches;
736
717
  try {
737
- matches = rglobPruned(base, pattern);
718
+ matches = rglobPruned(base, pattern, dep);
738
719
  }
739
720
  catch {
740
721
  return `Error: path not found: ${pathName}`;
@@ -913,6 +894,10 @@ export const RESPONSES_TOOLS = [
913
894
  type: "integer",
914
895
  description: "Max number of matches to return (default 200)",
915
896
  },
897
+ depth: {
898
+ type: "integer",
899
+ description: "Max directory depth to descend (default 12)",
900
+ },
916
901
  },
917
902
  required: ["pattern"],
918
903
  },
package/dist/ui.js CHANGED
@@ -556,7 +556,7 @@ export class Spinner {
556
556
  draw() {
557
557
  const text = this.render ? this.render() : "";
558
558
  const icon = SPINNER_FRAMES[this.frame % SPINNER_FRAMES.length];
559
- const rendered = `\x1b[36m${icon}\x1b[0m \x1b[90m${text}\x1b[0m`;
559
+ const rendered = `\x1b[1;36m${icon}\x1b[0m \x1b[90m${text}\x1b[0m`;
560
560
  // Nothing changed on this tick -> do not rewrite the line (avoids flicker).
561
561
  if (rendered === this.lastRendered)
562
562
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.75",
3
+ "version": "1.0.77",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },