@oxecli/oxe 1.0.58 → 1.0.60

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 +32 -15
  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
@@ -213,9 +213,9 @@ function formatMarkdownTable(tableLines) {
213
213
  colWidths.push(Math.max(mw, 3));
214
214
  }
215
215
  const out = [];
216
- const topBorder = `\x1b[90m┌${colWidths.map((w) => "─".repeat(w + 2)).join("┬")}┐\x1b[0m`;
216
+ const topBorder = `\x1b[90m╭${colWidths.map((w) => "─".repeat(w + 2)).join("┬")}╮\x1b[0m`;
217
217
  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`;
218
+ const botBorder = `\x1b[90m╰${colWidths.map((w) => "─".repeat(w + 2)).join("┴")}╯\x1b[0m`;
219
219
  out.push(topBorder);
220
220
  const headCells = header.map((h, i) => {
221
221
  const formatted = `\x1b[1;37m${formatInlineMarkdown(h)}\x1b[0m`;
@@ -302,20 +302,20 @@ export function markdownToAnsi(text) {
302
302
  continue;
303
303
  }
304
304
  let line = raw;
305
- // Headers
305
+ // Headers (render as styled headings with the '#' markers stripped)
306
306
  const h1 = line.match(/^#\s+(.+)$/);
307
307
  if (h1) {
308
- out.push(`\x1b[1;36m# ${formatInlineMarkdown(h1[1])}\x1b[0m`);
308
+ out.push(`\x1b[1;36m${formatInlineMarkdown(h1[1])}\x1b[0m`);
309
309
  continue;
310
310
  }
311
311
  const h2 = line.match(/^##\s+(.+)$/);
312
312
  if (h2) {
313
- out.push(`\x1b[1;34m## ${formatInlineMarkdown(h2[1])}\x1b[0m`);
313
+ out.push(`\x1b[1;34m${formatInlineMarkdown(h2[1])}\x1b[0m`);
314
314
  continue;
315
315
  }
316
316
  const h3 = line.match(/^###\s+(.+)$/);
317
317
  if (h3) {
318
- out.push(`\x1b[1;37m### ${formatInlineMarkdown(h3[1])}\x1b[0m`);
318
+ out.push(`\x1b[1;37m${formatInlineMarkdown(h3[1])}\x1b[0m`);
319
319
  continue;
320
320
  }
321
321
  // Blockquote
@@ -728,11 +728,25 @@ function wrapRuns(runs, width) {
728
728
  while (rest.length > 0) {
729
729
  if (curLen >= width)
730
730
  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;
731
+ let take;
732
+ if (curLen === 0) {
733
+ // Start of a line: take a whole word (or one char for a lone space,
734
+ // or a full-width chunk for an overlong word).
735
+ const sp = rest.indexOf(" ");
736
+ take = sp === -1 ? rest.length : sp;
737
+ if (take === 0)
738
+ take = 1;
739
+ take = Math.min(take, width);
740
+ }
741
+ else {
742
+ // Mid-line: append the next whole word (plus its trailing space) only
743
+ // if it fits; otherwise move the entire word to the next line.
744
+ const sp = rest.indexOf(" ");
745
+ take = sp === -1 ? rest.length : sp + 1;
746
+ if (take > width - curLen) {
747
+ flush();
748
+ continue;
749
+ }
736
750
  }
737
751
  const chunk = rest.slice(0, take);
738
752
  cur += run.style ? run.style + chunk + "\x1b[0m" : chunk;
@@ -874,9 +888,6 @@ export function isShiftEnterKey(str, key) {
874
888
  seq === "\x1bOM") {
875
889
  return true;
876
890
  }
877
- if (!key.ctrl && (str === "\n" || seq === "\n" || key.name === "linefeed")) {
878
- return true;
879
- }
880
891
  return false;
881
892
  }
882
893
  export const isNewlineKey = isShiftEnterKey;
@@ -887,7 +898,6 @@ export function isSubmitEnterKey(str, key) {
887
898
  return false;
888
899
  const seq = key.sequence || str || "";
889
900
  if ((key.name === "return" ||
890
- key.name === "enter" ||
891
901
  seq === "\r" ||
892
902
  seq === "\x1b[13u" ||
893
903
  seq === "\x1b[13;1u") &&
@@ -1374,6 +1384,13 @@ export function askBottomPrompt(label = "You", prefix = "❯", history = []) {
1374
1384
  repaint();
1375
1385
  return;
1376
1386
  }
1387
+ // Ctrl+J sends a bare LF (0x0a). Node reports it as name "enter" or
1388
+ // "linefeed" with no shift modifier. Only Shift+Enter should create a
1389
+ // new row, so ignore a lone LF (it is not a real Enter — a real Enter
1390
+ // arrives as CR / name "return").
1391
+ if (key && !key.shift && (key.name === "enter" || key.name === "linefeed")) {
1392
+ return;
1393
+ }
1377
1394
  if (str) {
1378
1395
  const isPaste = str.length > 1;
1379
1396
  insert(str.replace(/\r\n/g, "\n").replace(/\r/g, "\n"), isPaste);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxecli/oxe",
3
- "version": "1.0.58",
3
+ "version": "1.0.60",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },