@oxecli/oxe 1.0.59 → 1.0.61

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.
Files changed (3) hide show
  1. package/dist/tools.js +37 -9
  2. package/dist/ui.js +31 -14
  3. package/package.json +1 -1
package/dist/tools.js CHANGED
@@ -4,6 +4,7 @@ import { execFile, spawn } from "node:child_process";
4
4
  import { structuredPatch } from "diff";
5
5
  import { max_diff_source_chars, max_diff_lines, max_diff_context_lines, max_diff_line_chars, max_read_line_chars, max_read_file_bytes, max_read_lines, max_output_chars, max_bash_timeout_seconds, max_grep_file_bytes, strict_max_properties, ignoredDirs, } from "./config.js";
6
6
  import { toolLoadSkill } from "./skills.js";
7
+ import { truncateStyled, plainLen, terminalWidth } from "./ui.js";
7
8
  export { toolLoadSkill };
8
9
  // ---------------------------------------------------------------------------
9
10
  // Path sanitization helper
@@ -52,7 +53,6 @@ function displayDiff(pathName, oldContent, newContent) {
52
53
  const removed = contentLines.filter((c) => c.kind === "-").length;
53
54
  if (!added && !removed)
54
55
  return;
55
- process.stdout.write(`\x1b[90m┌─\x1b[0m \x1b[1m${pathName}\x1b[0m \x1b[32m+${added}\x1b[0m \x1b[31m-${removed}\x1b[0m\n`);
56
56
  const shown = [];
57
57
  let run = [];
58
58
  let lastKind = null;
@@ -77,18 +77,46 @@ function displayDiff(pathName, oldContent, newContent) {
77
77
  run.push(c);
78
78
  }
79
79
  flush();
80
- for (const l of shown) {
81
- if (l.startsWith("+")) {
82
- process.stdout.write(`\x1b[90m│\x1b[0m \x1b[32m${l}\x1b[0m\n`);
83
- }
84
- else if (l.startsWith("-")) {
85
- process.stdout.write(`\x1b[90m│\x1b[0m \x1b[31m${l}\x1b[0m\n`);
80
+ // Build the lines, stripping the leading + / - / space marker for display.
81
+ const visible = shown.map((l) => {
82
+ const kind = l[0] === "+" ? "+" : l[0] === "-" ? "-" : " ";
83
+ return { kind, body: l.slice(1) };
84
+ });
85
+ // A "… (N lines hidden) …" summary line is neutral context.
86
+ const termW = terminalWidth();
87
+ let contentW = 0;
88
+ for (const v of visible) {
89
+ contentW = Math.max(contentW, plainLen(v.body));
90
+ }
91
+ const headerPlain = `${pathName} +${added} -${removed}`;
92
+ const boxW = Math.min(Math.max(contentW, plainLen(headerPlain)) + 6, Math.max(termW - 2, 20));
93
+ const innerW = Math.max(boxW - 4, 1);
94
+ const maxPath = Math.max(10, boxW - 2 - plainLen(` +${added} -${removed}`) - 3);
95
+ let pathDisp = pathName;
96
+ if (plainLen(pathDisp) > maxPath)
97
+ pathDisp = truncateStyled(pathDisp, Math.max(maxPath - 1, 1)) + "…";
98
+ const header = `\x1b[1m${pathDisp}\x1b[0m \x1b[32m+${added}\x1b[0m \x1b[31m-${removed}\x1b[0m`;
99
+ const headerStr = `─ ${header} `;
100
+ const top = `╭${headerStr}${"─".repeat(Math.max(0, boxW - 2 - plainLen(headerStr)))}╮`;
101
+ process.stdout.write(`\x1b[90m${top}\x1b[0m\n`);
102
+ for (const v of visible) {
103
+ let line;
104
+ if (v.kind === "+") {
105
+ const body = truncateStyled(v.body, innerW - 2);
106
+ line = `\x1b[32m+ ${body}\x1b[0m`;
107
+ }
108
+ else if (v.kind === "-") {
109
+ const body = truncateStyled(v.body, innerW - 2);
110
+ line = `\x1b[31m- ${body}\x1b[0m`;
86
111
  }
87
112
  else {
88
- process.stdout.write(`\x1b[90m│\x1b[0m \x1b[2m${l}\x1b[0m\n`);
113
+ const body = truncateStyled(v.body, innerW - 2);
114
+ line = `\x1b[2m ${body}\x1b[0m`;
89
115
  }
116
+ const fill = Math.max(0, innerW - plainLen(line));
117
+ process.stdout.write(`\x1b[90m│\x1b[0m ${line}${" ".repeat(fill)} \x1b[90m│\x1b[0m\n`);
90
118
  }
91
- process.stdout.write(`\x1b[90m└────────────────────────────────────────\x1b[0m\n`);
119
+ process.stdout.write(`\x1b[90m╰${"─".repeat(boxW - 2)}╯\x1b[0m\n`);
92
120
  }
93
121
  export function truncateToolOutput(output, maxChars = max_output_chars) {
94
122
  if (output.length > maxChars) {
package/dist/ui.js CHANGED
@@ -183,11 +183,14 @@ function highlightCodeLine(line, _lang = "") {
183
183
  }
184
184
  function formatInlineMarkdown(text) {
185
185
  let line = text;
186
+ // Links MUST be converted first: the other rules inject ANSI escape codes,
187
+ // and the link pattern `\[[^\]]+\]\([^)]+\)` would otherwise match the `[`
188
+ // inside an already-inserted escape (e.g. `\x1b[1m`) and corrupt the output.
189
+ line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "\x1b[4;36m$1\x1b[0m \x1b[2m($2)\x1b[0m");
186
190
  line = line.replace(/\*\*\*([^*]+)\*\*\*/g, "\x1b[1;3m$1\x1b[0m");
187
191
  line = line.replace(/\*\*([^*]+)\*\*/g, "\x1b[1m$1\x1b[0m");
188
192
  line = line.replace(/(^|[^\w*])\*([^*\n]+)\*(?=$|[^\w*])/g, "$1\x1b[3m$2\x1b[0m");
189
193
  line = line.replace(/`([^`]+)`/g, "\x1b[1;36m`$1`\x1b[0m");
190
- line = line.replace(/\[([^\]]+)\]\(([^)]+)\)/g, "\x1b[4;36m$1\x1b[0m \x1b[2m($2)\x1b[0m");
191
194
  return line;
192
195
  }
193
196
  // ---------------------------------------------------------------------------
@@ -213,9 +216,9 @@ function formatMarkdownTable(tableLines) {
213
216
  colWidths.push(Math.max(mw, 3));
214
217
  }
215
218
  const out = [];
216
- const topBorder = `\x1b[90m┌${colWidths.map((w) => "─".repeat(w + 2)).join("┬")}┐\x1b[0m`;
219
+ const topBorder = `\x1b[90m╭${colWidths.map((w) => "─".repeat(w + 2)).join("┬")}╮\x1b[0m`;
217
220
  const midBorder = `\x1b[90m├${colWidths.map((w) => "─".repeat(w + 2)).join("┼")}┤\x1b[0m`;
218
- const botBorder = `\x1b[90m└${colWidths.map((w) => "─".repeat(w + 2)).join("┴")}┘\x1b[0m`;
221
+ const botBorder = `\x1b[90m╰${colWidths.map((w) => "─".repeat(w + 2)).join("┴")}╯\x1b[0m`;
219
222
  out.push(topBorder);
220
223
  const headCells = header.map((h, i) => {
221
224
  const formatted = `\x1b[1;37m${formatInlineMarkdown(h)}\x1b[0m`;
@@ -251,12 +254,12 @@ export function markdownToAnsi(text) {
251
254
  return;
252
255
  const badge = codeLang ? ` \x1b[1;36m${codeLang}\x1b[0m ` : " ";
253
256
  const barLen = Math.max(10, Math.min(width - plainLen(badge) - 4, 60));
254
- out.push(`\x1b[90m┌─${badge}${"─".repeat(barLen)}┐\x1b[0m`);
257
+ out.push(`\x1b[90m╭─${badge}${"─".repeat(barLen)}╮\x1b[0m`);
255
258
  for (const cline of codeBuffer) {
256
259
  const highlighted = highlightCodeLine(cline, codeLang);
257
260
  out.push(`\x1b[90m│\x1b[0m ${highlighted}`);
258
261
  }
259
- out.push(`\x1b[90m└${"─".repeat(barLen + plainLen(badge) + 2)}┘\x1b[0m`);
262
+ out.push(`\x1b[90m╰${"─".repeat(barLen + plainLen(badge) + 2)}╯\x1b[0m`);
260
263
  codeBuffer = [];
261
264
  codeLang = "";
262
265
  };
@@ -302,20 +305,20 @@ export function markdownToAnsi(text) {
302
305
  continue;
303
306
  }
304
307
  let line = raw;
305
- // Headers
308
+ // Headers (render as styled headings with the '#' markers stripped)
306
309
  const h1 = line.match(/^#\s+(.+)$/);
307
310
  if (h1) {
308
- out.push(`\x1b[1;36m# ${formatInlineMarkdown(h1[1])}\x1b[0m`);
311
+ out.push(`\x1b[1;36m${formatInlineMarkdown(h1[1])}\x1b[0m`);
309
312
  continue;
310
313
  }
311
314
  const h2 = line.match(/^##\s+(.+)$/);
312
315
  if (h2) {
313
- out.push(`\x1b[1;34m## ${formatInlineMarkdown(h2[1])}\x1b[0m`);
316
+ out.push(`\x1b[1;34m${formatInlineMarkdown(h2[1])}\x1b[0m`);
314
317
  continue;
315
318
  }
316
319
  const h3 = line.match(/^###\s+(.+)$/);
317
320
  if (h3) {
318
- out.push(`\x1b[1;37m### ${formatInlineMarkdown(h3[1])}\x1b[0m`);
321
+ out.push(`\x1b[1;37m${formatInlineMarkdown(h3[1])}\x1b[0m`);
319
322
  continue;
320
323
  }
321
324
  // Blockquote
@@ -728,11 +731,25 @@ function wrapRuns(runs, width) {
728
731
  while (rest.length > 0) {
729
732
  if (curLen >= width)
730
733
  flush();
731
- let take = Math.min(rest.length, width - curLen);
732
- if (take < rest.length && curLen > 0) {
733
- const lastSpace = rest.slice(0, take).lastIndexOf(" ");
734
- if (lastSpace > 0)
735
- take = lastSpace + 1;
734
+ let take;
735
+ if (curLen === 0) {
736
+ // Start of a line: take a whole word (or one char for a lone space,
737
+ // or a full-width chunk for an overlong word).
738
+ const sp = rest.indexOf(" ");
739
+ take = sp === -1 ? rest.length : sp;
740
+ if (take === 0)
741
+ take = 1;
742
+ take = Math.min(take, width);
743
+ }
744
+ else {
745
+ // Mid-line: append the next whole word (plus its trailing space) only
746
+ // if it fits; otherwise move the entire word to the next line.
747
+ const sp = rest.indexOf(" ");
748
+ take = sp === -1 ? rest.length : sp + 1;
749
+ if (take > width - curLen) {
750
+ flush();
751
+ continue;
752
+ }
736
753
  }
737
754
  const chunk = rest.slice(0, take);
738
755
  cur += run.style ? run.style + chunk + "\x1b[0m" : chunk;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.59",
3
+ "version": "1.0.61",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },