toolcraft 0.0.44 → 0.0.45

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.
@@ -1,5 +1,6 @@
1
1
  import { resolveOutputFormat } from "../internal/output-format.js";
2
2
  import { stripAnsi } from "../internal/strip-ansi.js";
3
+ import { text } from "./text.js";
3
4
  function applyTone(theme, value, tone) {
4
5
  return tone === undefined ? value : theme[tone](value);
5
6
  }
@@ -38,7 +39,7 @@ function renderTerminal(options) {
38
39
  return blocks.join("\n\n");
39
40
  }
40
41
  function escapeMarkdown(value) {
41
- return stripAnsi(value).replaceAll("`", "\\`");
42
+ return stripAnsi(value);
42
43
  }
43
44
  function renderMarkdown(options) {
44
45
  const blocks = [`# ${escapeMarkdown(options.title)}`];
@@ -55,7 +56,7 @@ function renderMarkdown(options) {
55
56
  }
56
57
  const items = group.items.map((item) => {
57
58
  const detail = item.detail === undefined ? "" : ` — ${escapeMarkdown(item.detail)}`;
58
- return `- \`${escapeMarkdown(item.label)}\` \`${escapeMarkdown(item.value)}\`${detail}`;
59
+ return `- ${text.command(escapeMarkdown(item.label))} ${text.command(escapeMarkdown(item.value))}${detail}`;
59
60
  });
60
61
  blocks.push(`${header.join("\n\n")}\n\n${items.join("\n")}`);
61
62
  }
@@ -1,44 +1,20 @@
1
+ import stringWidth from "fast-string-width";
2
+ import { wrapAnsi } from "fast-wrap-ansi";
1
3
  import { widths } from "../tokens/widths.js";
2
4
  function wrap(value, width) {
3
- const lines = [];
4
- for (const paragraph of value.split("\n")) {
5
- let line = "";
6
- for (const rawWord of paragraph.split(" ")) {
7
- const words = [];
8
- let word = rawWord;
9
- while (word.length > width) {
10
- words.push(word.slice(0, width));
11
- word = word.slice(width);
12
- }
13
- words.push(word);
14
- for (const word of words) {
15
- if (line.length === 0) {
16
- line = word;
17
- continue;
18
- }
19
- if (`${line} ${word}`.length <= width) {
20
- line = `${line} ${word}`;
21
- continue;
22
- }
23
- lines.push(line);
24
- line = word;
25
- }
26
- }
27
- lines.push(line);
28
- }
29
- return lines;
5
+ return wrapAnsi(value, Math.max(1, width), { hard: true, trim: true }).split("\n");
30
6
  }
31
7
  function renderRows(rows, theme, width) {
32
8
  if (rows.length === 0)
33
9
  return [];
34
- const labelWidth = Math.max(...rows.map((row) => row.label.length));
10
+ const labelWidth = Math.max(...rows.map((row) => stringWidth(row.label)));
35
11
  const valueWidth = Math.max(20, width - labelWidth - 2);
36
12
  const continuation = " ".repeat(labelWidth + 2);
37
13
  return rows.flatMap((row) => {
38
14
  const values = wrap(row.value, valueWidth);
39
15
  return [
40
16
  `${theme.muted(row.label.padEnd(labelWidth))} ${values[0] ?? ""}`,
41
- ...values.slice(1).map((value) => `${continuation}${value}`),
17
+ ...values.slice(1).map((value) => `${continuation}${value}`)
42
18
  ];
43
19
  });
44
20
  }
@@ -46,8 +22,10 @@ export function renderDetailCard(options) {
46
22
  const width = options.width ?? widths.maxLine;
47
23
  const identity = [
48
24
  options.theme.header(options.title),
49
- options.subtitle ? options.theme.muted(options.subtitle) : undefined,
50
- ].filter((value) => value !== undefined).join(" ");
25
+ options.subtitle ? options.theme.muted(options.subtitle) : undefined
26
+ ]
27
+ .filter((value) => value !== undefined)
28
+ .join(" ");
51
29
  const hero = options.badges?.length
52
30
  ? `${identity}\n${options.theme.muted(options.badges.map((badge) => badge[0] + badge.slice(1).toLowerCase()).join(" · "))}`
53
31
  : identity;
@@ -61,9 +39,7 @@ export function renderDetailCard(options) {
61
39
  if (section.rows.length === 0)
62
40
  continue;
63
41
  const rows = renderRows(section.rows, options.theme, width);
64
- blocks.push(section.title
65
- ? [options.theme.header(section.title), ...rows].join("\n")
66
- : rows.join("\n"));
42
+ blocks.push(section.title ? [options.theme.header(section.title), ...rows].join("\n") : rows.join("\n"));
67
43
  }
68
44
  return blocks.join("\n\n");
69
45
  }
@@ -18,7 +18,8 @@ function renderMarkdownCode(content) {
18
18
  currentRun = 0;
19
19
  }
20
20
  const delimiter = "`".repeat(longestRun + 1);
21
- return `${delimiter}${value}${delimiter}`;
21
+ const paddedValue = value.startsWith("`") || value.endsWith("`") ? ` ${value} ` : value;
22
+ return `${delimiter}${paddedValue}${delimiter}`;
22
23
  }
23
24
  function renderMarkdownLink(content) {
24
25
  const value = renderMarkdownInline(content);
@@ -1,3 +1,50 @@
1
1
  export function stripAnsi(value) {
2
- return value.replace(/\u001b\[[0-9;]*m/g, "");
2
+ let output = "";
3
+ let index = 0;
4
+ while (index < value.length) {
5
+ const char = value[index];
6
+ if (char === "\u001b") {
7
+ index = skipEscapeSequence(value, index);
8
+ continue;
9
+ }
10
+ if (char === "\u009b") {
11
+ index = skipCsiSequence(value, index + 1);
12
+ continue;
13
+ }
14
+ output += char;
15
+ index += char.length;
16
+ }
17
+ return output;
18
+ }
19
+ function skipEscapeSequence(value, index) {
20
+ const next = value[index + 1];
21
+ if (next === "[") {
22
+ return skipCsiSequence(value, index + 2);
23
+ }
24
+ if (next === "]") {
25
+ return skipOscSequence(value, index + 2);
26
+ }
27
+ return Math.min(value.length, index + 2);
28
+ }
29
+ function skipCsiSequence(value, index) {
30
+ while (index < value.length) {
31
+ const codePoint = value.charCodeAt(index);
32
+ index += 1;
33
+ if (codePoint >= 0x40 && codePoint <= 0x7e) {
34
+ break;
35
+ }
36
+ }
37
+ return index;
38
+ }
39
+ function skipOscSequence(value, index) {
40
+ while (index < value.length) {
41
+ if (value[index] === "\u0007") {
42
+ return index + 1;
43
+ }
44
+ if (value[index] === "\u001b" && value[index + 1] === "\\") {
45
+ return index + 2;
46
+ }
47
+ index += 1;
48
+ }
49
+ return index;
3
50
  }
@@ -5,7 +5,7 @@ export const SPINNER_FRAMES = Object.freeze(["◒", "◐", "◓", "◑"]);
5
5
  export function renderSpinnerFrame(options) {
6
6
  const format = resolveOutputFormat();
7
7
  if (format === "markdown") {
8
- return `- ${options.message}${options.timer ? ` [${options.timer}]` : ""}...\n`;
8
+ return `- ${renderMarkdownInline(options.message)}${options.timer ? ` [${renderMarkdownInline(options.timer)}]` : ""}...\n`;
9
9
  }
10
10
  if (format === "json") {
11
11
  return `${JSON.stringify({
@@ -500,15 +500,16 @@ function parseInlineCode(input, start, offsets) {
500
500
  }
501
501
  const closingFenceLength = readRunLength(input, index, "`");
502
502
  if (closingFenceLength === fenceLength) {
503
+ const value = normalizeInlineCodeValue(input.slice(start + fenceLength, index));
503
504
  return {
504
505
  node: offsets === undefined
505
506
  ? {
506
507
  type: "inlineCode",
507
- value: input.slice(start + fenceLength, index)
508
+ value
508
509
  }
509
510
  : withRange({
510
511
  type: "inlineCode",
511
- value: input.slice(start + fenceLength, index)
512
+ value
512
513
  }, createRange(offsets, start, index + fenceLength)),
513
514
  end: index + fenceLength
514
515
  };
@@ -517,6 +518,16 @@ function parseInlineCode(input, start, offsets) {
517
518
  }
518
519
  return null;
519
520
  }
521
+ function normalizeInlineCodeValue(value) {
522
+ if (value.length >= 2 &&
523
+ value.startsWith(" ") &&
524
+ value.endsWith(" ") &&
525
+ (value[1] === "`" || value[value.length - 2] === "`") &&
526
+ value.trim().length > 0) {
527
+ return value.slice(1, -1);
528
+ }
529
+ return value;
530
+ }
520
531
  function parseLink(input, start, footnoteLabels, offsets) {
521
532
  const label = parseBracketedLabel(input, start);
522
533
  if (label === null || label.end >= input.length || input[label.end] !== "(") {
@@ -538,7 +549,9 @@ function parseLink(input, start, footnoteLabels, offsets) {
538
549
  ? {}
539
550
  : { offsets: sliceOffsetMap(offsets, label.contentStart, label.end - 1) })
540
551
  })
541
- }, offsets === undefined ? { start, end: destination.end } : createRange(offsets, start, destination.end)),
552
+ }, offsets === undefined
553
+ ? { start, end: destination.end }
554
+ : createRange(offsets, start, destination.end)),
542
555
  end: destination.end
543
556
  };
544
557
  }
@@ -557,7 +570,9 @@ function parseImage(input, start, offsets) {
557
570
  url: destination.url,
558
571
  alt: decodeEscapes(label.value),
559
572
  ...(destination.title === undefined ? {} : { title: destination.title })
560
- }, offsets === undefined ? { start, end: destination.end } : createRange(offsets, start, destination.end)),
573
+ }, offsets === undefined
574
+ ? { start, end: destination.end }
575
+ : createRange(offsets, start, destination.end)),
561
576
  end: destination.end
562
577
  };
563
578
  }
@@ -620,7 +635,7 @@ function parseLinkDestination(input, openParenIndex) {
620
635
  index += 2;
621
636
  continue;
622
637
  }
623
- if (char === "\"" || char === "'") {
638
+ if (char === '"' || char === "'") {
624
639
  quote = char;
625
640
  index += 1;
626
641
  continue;
@@ -651,7 +666,7 @@ function parseLinkDestinationContent(content) {
651
666
  return { url: "" };
652
667
  }
653
668
  const quote = content[trimmedEnd - 1];
654
- if (quote === "\"" || quote === "'") {
669
+ if (quote === '"' || quote === "'") {
655
670
  const titleStart = findTrailingQuotedSegmentStart(content, trimmedEnd, quote);
656
671
  if (titleStart !== -1) {
657
672
  let separatorStart = titleStart;
@@ -699,7 +714,9 @@ function parseAutolink(input, start, offsets) {
699
714
  type: "link",
700
715
  url,
701
716
  children: [
702
- createTextNode(url, offsets === undefined ? { start: start + 1, end: index } : createRange(offsets, start + 1, index))
717
+ createTextNode(url, offsets === undefined
718
+ ? { start: start + 1, end: index }
719
+ : createRange(offsets, start + 1, index))
703
720
  ]
704
721
  }, offsets === undefined ? { start, end: index + 1 } : createRange(offsets, start, index + 1)),
705
722
  end: index + 1
@@ -727,7 +744,9 @@ function parseFootnoteReference(input, start, footnoteLabels, offsets) {
727
744
  return null;
728
745
  }
729
746
  return {
730
- node: withRange({ type: "footnoteReference", label }, offsets === undefined ? { start, end: labelEnd + 1 } : createRange(offsets, start, labelEnd + 1)),
747
+ node: withRange({ type: "footnoteReference", label }, offsets === undefined
748
+ ? { start, end: labelEnd + 1 }
749
+ : createRange(offsets, start, labelEnd + 1)),
731
750
  end: labelEnd + 1
732
751
  };
733
752
  }
@@ -754,7 +773,9 @@ function createLiteralAutolinkNode(text, url, start, end, offsets) {
754
773
  node: withRange({
755
774
  type: "link",
756
775
  url,
757
- children: [createTextNode(text, offsets === undefined ? { start, end } : createRange(offsets, start, end))]
776
+ children: [
777
+ createTextNode(text, offsets === undefined ? { start, end } : createRange(offsets, start, end))
778
+ ]
758
779
  }, offsets === undefined ? { start, end } : createRange(offsets, start, end)),
759
780
  end
760
781
  };
@@ -865,7 +886,7 @@ function parseInlineHtmlTag(input, start, offsets) {
865
886
  return null;
866
887
  }
867
888
  const quote = input[index];
868
- if (quote === "\"" || quote === "'") {
889
+ if (quote === '"' || quote === "'") {
869
890
  index += 1;
870
891
  while (index < input.length && input[index] !== quote) {
871
892
  index += 1;
@@ -878,7 +899,7 @@ function parseInlineHtmlTag(input, start, offsets) {
878
899
  }
879
900
  while (index < input.length && !isHtmlWhitespace(input[index]) && input[index] !== ">") {
880
901
  const char = input[index];
881
- if (char === "\"" || char === "'" || char === "<" || char === "=" || char === "`") {
902
+ if (char === '"' || char === "'" || char === "<" || char === "=" || char === "`") {
882
903
  return null;
883
904
  }
884
905
  index += 1;
@@ -1065,7 +1086,7 @@ function skipHtmlWhitespace(value, start) {
1065
1086
  }
1066
1087
  function isEscapable(value) {
1067
1088
  return (value === "!" ||
1068
- value === "\"" ||
1089
+ value === '"' ||
1069
1090
  value === "#" ||
1070
1091
  value === "$" ||
1071
1092
  value === "%" ||
@@ -1116,7 +1137,13 @@ function isDigit(value) {
1116
1137
  return value >= "0" && value <= "9";
1117
1138
  }
1118
1139
  function isEmailLocalPartChar(value) {
1119
- return isAsciiLetter(value) || isDigit(value) || value === "." || value === "_" || value === "%" || value === "+" || value === "-";
1140
+ return (isAsciiLetter(value) ||
1141
+ isDigit(value) ||
1142
+ value === "." ||
1143
+ value === "_" ||
1144
+ value === "%" ||
1145
+ value === "+" ||
1146
+ value === "-");
1120
1147
  }
1121
1148
  function isEmailDomainChar(value) {
1122
1149
  return isAsciiLetter(value) || isDigit(value) || value === "." || value === "-";
@@ -1131,10 +1158,7 @@ function isHtmlAttributeNameStartChar(value) {
1131
1158
  return isAsciiLetter(value) || value === ":" || value === "_";
1132
1159
  }
1133
1160
  function isHtmlAttributeNameChar(value) {
1134
- return (isHtmlAttributeNameStartChar(value) ||
1135
- isDigit(value) ||
1136
- value === "-" ||
1137
- value === ".");
1161
+ return isHtmlAttributeNameStartChar(value) || isDigit(value) || value === "-" || value === ".";
1138
1162
  }
1139
1163
  function createRange(offsets, start, end) {
1140
1164
  return {
@@ -1,4 +1,5 @@
1
1
  import { symbols } from "../components/symbols.js";
2
+ import { displayWidth, graphemes } from "../dashboard/terminal-width.js";
2
3
  import { getTheme } from "../internal/theme-detect.js";
3
4
  import { stripAnsi } from "../internal/strip-ansi.js";
4
5
  import { spacing } from "../tokens/spacing.js";
@@ -81,7 +82,7 @@ function renderHeading(node, context) {
81
82
  }
82
83
  const styledLines = lines.map((line) => styleHeading(line, node.depth, context));
83
84
  if (node.depth === 1) {
84
- const underlineWidth = Math.max(...lines.map((line) => stripAnsi(line).length));
85
+ const underlineWidth = Math.max(...lines.map((line) => visibleWidth(line)));
85
86
  return `${styledLines.join("\n")}\n${context.theme.header(lineChar.repeat(underlineWidth))}\n\n`;
86
87
  }
87
88
  return `${styledLines.join("\n")}\n\n`;
@@ -111,8 +112,8 @@ function renderAlert(node, context) {
111
112
  }
112
113
  function renderCodeBlock(node, context) {
113
114
  const indent = " ".repeat(spacing.sm);
114
- const lines = node.value.split("\n");
115
- const longestLine = lines.reduce((max, line) => Math.max(max, line.length), 0);
115
+ const lines = node.value.split("\n").map((line) => stripAnsi(line));
116
+ const longestLine = lines.reduce((max, line) => Math.max(max, visibleWidth(line)), 0);
116
117
  const borderWidth = Math.max(3, Math.min(context.width - indent.length, longestLine));
117
118
  const border = context.theme.muted(`${indent}${lineChar.repeat(borderWidth)}`);
118
119
  const content = lines.map((line) => `${indent}${line}`).join("\n");
@@ -145,7 +146,7 @@ function renderTable(node, context) {
145
146
  const cell = row.children[columnIndex];
146
147
  return cell?.type === "tableCell" ? renderTableCell(cell, context) : "";
147
148
  }));
148
- const columnWidths = Array.from({ length: columnCount }, (_, columnIndex) => Math.max(...renderedRows.map((row) => stripAnsi(row[columnIndex] ?? "").length), 0));
149
+ const columnWidths = Array.from({ length: columnCount }, (_, columnIndex) => Math.max(...renderedRows.map((row) => visibleWidth(row[columnIndex] ?? "")), 0));
149
150
  if (getRenderedTableWidth(columnWidths) > context.width) {
150
151
  return renderStackedTable(rows, columnCount, context);
151
152
  }
@@ -157,9 +158,7 @@ function renderTable(node, context) {
157
158
  if (rowIndex !== 0 || lines.length === 1) {
158
159
  return line;
159
160
  }
160
- const divider = context.theme.muted(`├${columnWidths
161
- .map((width) => lineChar.repeat(width + spacing.sm * 2))
162
- .join("┼")}┤`);
161
+ const divider = context.theme.muted(`├${columnWidths.map((width) => lineChar.repeat(width + spacing.sm * 2)).join("┼")}┤`);
163
162
  return `${line}\n${divider}`;
164
163
  });
165
164
  return `${outputLines.join("\n")}\n\n`;
@@ -171,7 +170,9 @@ function renderStackedTable(rows, columnCount, context) {
171
170
  }
172
171
  const dataRows = rows.slice(1);
173
172
  if (dataRows.length === 0) {
174
- const headerLines = Array.from({ length: columnCount }, (_, columnIndex) => tokenizeText(getStackedTableLabel(headerRow.children[columnIndex], columnIndex, context), [typography.bold])).flatMap((tokens) => wrapTokens(tokens, context.width));
173
+ const headerLines = Array.from({ length: columnCount }, (_, columnIndex) => tokenizeText(getStackedTableLabel(headerRow.children[columnIndex], columnIndex, context), [
174
+ typography.bold
175
+ ])).flatMap((tokens) => wrapTokens(tokens, context.width));
175
176
  return headerLines.length === 0 ? "" : `${headerLines.join("\n")}\n\n`;
176
177
  }
177
178
  const blocks = dataRows
@@ -235,7 +236,7 @@ function formatFrontmatterValue(value) {
235
236
  });
236
237
  }
237
238
  function renderHtml(node, context) {
238
- const value = stripHtmlTags(node.value).trim();
239
+ const value = stripAnsi(stripHtmlTags(node.value)).trim();
239
240
  if (value.length === 0) {
240
241
  return "";
241
242
  }
@@ -286,7 +287,7 @@ function renderReferencedFootnotes(context) {
286
287
  function renderFootnoteDefinition(node, number, context) {
287
288
  const marker = typography.dim(`[${number}]`);
288
289
  const firstPrefix = `${" ".repeat(spacing.sm)}${marker} `;
289
- const continuationPrefix = `${" ".repeat(spacing.sm + stripAnsi(`[${number}] `).length)}`;
290
+ const continuationPrefix = `${" ".repeat(spacing.sm + visibleWidth(`[${number}] `))}`;
290
291
  const body = renderBlockChildren(node.children, reduceWidth(context, firstPrefix));
291
292
  if (body.length === 0) {
292
293
  return firstPrefix.trimEnd();
@@ -317,7 +318,7 @@ function prefixBlock(rendered, firstPrefix, restPrefix) {
317
318
  function reduceWidth(context, prefix) {
318
319
  return {
319
320
  ...context,
320
- width: Math.max(1, context.width - stripAnsi(prefix).length)
321
+ width: Math.max(1, context.width - visibleWidth(prefix))
321
322
  };
322
323
  }
323
324
  function renderInline(nodes, context) {
@@ -334,7 +335,7 @@ function tokenizeInline(nodes, context) {
334
335
  function collectInlineTokens(node, formatters, context, tokens) {
335
336
  switch (node.type) {
336
337
  case "text":
337
- tokens.push(...tokenizeText(node.value, formatters));
338
+ tokens.push(...tokenizeText(stripAnsi(node.value), formatters));
338
339
  return;
339
340
  case "strong":
340
341
  collectChildren(node.children, [...formatters, typography.bold], context, tokens);
@@ -346,13 +347,16 @@ function collectInlineTokens(node, formatters, context, tokens) {
346
347
  collectChildren(node.children, [...formatters, typography.strikethrough], context, tokens);
347
348
  return;
348
349
  case "inlineCode":
349
- tokens.push(...tokenizeText(node.value, [...formatters, context.theme.accent]));
350
+ tokens.push(...tokenizeText(stripAnsi(node.value), [...formatters, context.theme.accent]));
350
351
  return;
351
352
  case "link":
352
353
  collectLinkTokens(node, formatters, context, tokens);
353
354
  return;
354
355
  case "image":
355
- tokens.push(createWordToken(formatImagePlaceholder(node.alt), [...formatters, context.theme.muted]));
356
+ tokens.push(createWordToken(formatImagePlaceholder(stripAnsi(node.alt)), [
357
+ ...formatters,
358
+ context.theme.muted
359
+ ]));
356
360
  return;
357
361
  case "footnoteReference": {
358
362
  const footnoteNumber = resolveFootnoteNumber(node.label, context);
@@ -362,7 +366,7 @@ function collectInlineTokens(node, formatters, context, tokens) {
362
366
  return;
363
367
  }
364
368
  case "html": {
365
- const value = stripHtmlTags(node.value);
369
+ const value = stripAnsi(stripHtmlTags(node.value));
366
370
  if (value.length > 0) {
367
371
  tokens.push(...tokenizeText(value, formatters));
368
372
  }
@@ -384,7 +388,7 @@ function collectChildren(children, formatters, context, tokens) {
384
388
  }
385
389
  function collectLinkTokens(node, formatters, context, tokens) {
386
390
  if (isAutolink(node)) {
387
- tokens.push(createWordToken(node.url, [...formatters, context.theme.accent]));
391
+ tokens.push(createWordToken(stripAnsi(node.url), [...formatters, context.theme.accent]));
388
392
  return;
389
393
  }
390
394
  const childTokens = [];
@@ -394,7 +398,7 @@ function collectLinkTokens(node, formatters, context, tokens) {
394
398
  if (trimmedChildTokens.some((token) => token.type === "word")) {
395
399
  tokens.push({ type: "space", value: " " });
396
400
  }
397
- tokens.push(createWordToken(`(${node.url})`, [...formatters, context.theme.accent]));
401
+ tokens.push(createWordToken(`(${stripAnsi(node.url)})`, [...formatters, context.theme.accent]));
398
402
  }
399
403
  function isAutolink(node) {
400
404
  if (node.children.length !== 1 || node.children[0]?.type !== "text") {
@@ -424,7 +428,9 @@ function tokenizeText(value, formatters) {
424
428
  const pieces = line
425
429
  .split(/([ \t]+)/)
426
430
  .filter((piece) => piece.length > 0)
427
- .map((piece) => /^[ \t]+$/.test(piece) ? { type: "space", value: piece } : { type: "word", value: piece, formatters });
431
+ .map((piece) => /^[ \t]+$/.test(piece)
432
+ ? { type: "space", value: piece }
433
+ : { type: "word", value: piece, formatters });
428
434
  return lineIndex < lines.length - 1 ? [...pieces, { type: "break" }] : pieces;
429
435
  });
430
436
  }
@@ -454,8 +460,9 @@ function wrapTokens(tokens, width) {
454
460
  for (let index = 0; index < chunks.length; index++) {
455
461
  const chunk = chunks[index];
456
462
  const gap = index === 0 ? pendingSpace : "";
457
- const gapWidth = gap.length;
458
- if (currentWidth > 0 && currentWidth + chunk.length + gapWidth > width) {
463
+ const chunkWidth = visibleWidth(chunk);
464
+ const gapWidth = visibleWidth(gap);
465
+ if (currentWidth > 0 && currentWidth + chunkWidth + gapWidth > width) {
459
466
  flushLine();
460
467
  }
461
468
  if (currentWidth > 0 && gapWidth > 0) {
@@ -463,7 +470,7 @@ function wrapTokens(tokens, width) {
463
470
  currentWidth += gapWidth;
464
471
  }
465
472
  currentLine += applyFormatters(chunk, token.formatters);
466
- currentWidth += chunk.length;
473
+ currentWidth += chunkWidth;
467
474
  pendingSpace = "";
468
475
  if (index < chunks.length - 1) {
469
476
  flushLine();
@@ -476,12 +483,29 @@ function wrapTokens(tokens, width) {
476
483
  return lines;
477
484
  }
478
485
  function splitWord(value, width) {
479
- if (value.length <= width) {
486
+ if (visibleWidth(value) <= width) {
480
487
  return [value];
481
488
  }
482
489
  const chunks = [];
483
- for (let index = 0; index < value.length; index += width) {
484
- chunks.push(value.slice(index, index + width));
490
+ let chunk = "";
491
+ let chunkWidth = 0;
492
+ for (const grapheme of graphemes(value)) {
493
+ const graphemeWidth = visibleWidth(grapheme);
494
+ if (chunk.length > 0 && chunkWidth + graphemeWidth > width) {
495
+ chunks.push(chunk);
496
+ chunk = "";
497
+ chunkWidth = 0;
498
+ }
499
+ chunk += grapheme;
500
+ chunkWidth += graphemeWidth;
501
+ if (chunkWidth >= width) {
502
+ chunks.push(chunk);
503
+ chunk = "";
504
+ chunkWidth = 0;
505
+ }
506
+ }
507
+ if (chunk.length > 0) {
508
+ chunks.push(chunk);
485
509
  }
486
510
  return chunks;
487
511
  }
@@ -557,8 +581,7 @@ function getRenderedTableWidth(columnWidths) {
557
581
  return columnWidths.reduce((totalWidth, width) => totalWidth + width + spacing.sm * 2, columnWidths.length + 1);
558
582
  }
559
583
  function alignTableCell(value, width, align) {
560
- const visibleWidth = stripAnsi(value).length;
561
- const extraSpace = Math.max(0, width - visibleWidth);
584
+ const extraSpace = Math.max(0, width - visibleWidth(value));
562
585
  if (align === "right") {
563
586
  return `${" ".repeat(extraSpace)}${value}`;
564
587
  }
@@ -587,3 +610,6 @@ function stripHtmlTags(value) {
587
610
  }
588
611
  return output;
589
612
  }
613
+ function visibleWidth(value) {
614
+ return displayWidth(stripAnsi(value));
615
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft",
3
- "version": "0.0.44",
3
+ "version": "0.0.45",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -47,7 +47,7 @@
47
47
  "postpack": "node ../../scripts/manage-bundled-workspace-deps.mjs cleanup . toolcraft-design @poe-code/frontmatter @poe-code/agent-mcp-config @poe-code/agent-human-in-loop @poe-code/task-list @poe-code/agent-defs @poe-code/config-mutations @poe-code/process-runner tiny-mcp-client mcp-oauth auth-store"
48
48
  },
49
49
  "dependencies": {
50
- "toolcraft-schema": "0.0.44",
50
+ "toolcraft-schema": "0.0.45",
51
51
  "commander": "^14.0.3",
52
52
  "fast-string-width": "^3.0.2",
53
53
  "fast-wrap-ansi": "^0.2.0",