toolcraft 0.0.18 → 0.0.19

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 (34) hide show
  1. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +3 -3
  2. package/node_modules/@poe-code/config-mutations/dist/mutations/template-mutation.d.ts +3 -3
  3. package/node_modules/@poe-code/config-mutations/dist/template/render.d.ts +0 -1
  4. package/node_modules/@poe-code/config-mutations/dist/template/render.js +2 -22
  5. package/node_modules/@poe-code/config-mutations/package.json +1 -4
  6. package/node_modules/@poe-code/design-system/dist/acp/components.js +15 -13
  7. package/node_modules/@poe-code/design-system/dist/components/color.d.ts +31 -0
  8. package/node_modules/@poe-code/design-system/dist/components/color.js +101 -0
  9. package/node_modules/@poe-code/design-system/dist/components/index.d.ts +4 -0
  10. package/node_modules/@poe-code/design-system/dist/components/index.js +2 -0
  11. package/node_modules/@poe-code/design-system/dist/components/logger.js +2 -2
  12. package/node_modules/@poe-code/design-system/dist/components/symbols.js +3 -3
  13. package/node_modules/@poe-code/design-system/dist/components/table.js +191 -40
  14. package/node_modules/@poe-code/design-system/dist/components/template.d.ts +6 -0
  15. package/node_modules/@poe-code/design-system/dist/components/template.js +271 -0
  16. package/node_modules/@poe-code/design-system/dist/components/text.js +3 -3
  17. package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +12 -12
  18. package/node_modules/@poe-code/design-system/dist/index.d.ts +4 -0
  19. package/node_modules/@poe-code/design-system/dist/index.js +2 -0
  20. package/node_modules/@poe-code/design-system/dist/internal/color-support.d.ts +9 -0
  21. package/node_modules/@poe-code/design-system/dist/internal/color-support.js +12 -0
  22. package/node_modules/@poe-code/design-system/dist/prompts/index.js +2 -2
  23. package/node_modules/@poe-code/design-system/dist/prompts/primitives/cancel.js +2 -2
  24. package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +2 -2
  25. package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +4 -4
  26. package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +5 -5
  27. package/node_modules/@poe-code/design-system/dist/prompts/primitives/outro.js +2 -2
  28. package/node_modules/@poe-code/design-system/dist/prompts/primitives/spinner.js +3 -3
  29. package/node_modules/@poe-code/design-system/dist/static/menu.js +5 -5
  30. package/node_modules/@poe-code/design-system/dist/static/spinner.js +8 -8
  31. package/node_modules/@poe-code/design-system/dist/tokens/colors.js +29 -29
  32. package/node_modules/@poe-code/design-system/dist/tokens/typography.js +6 -6
  33. package/node_modules/@poe-code/design-system/package.json +5 -3
  34. package/package.json +2 -5
@@ -1,4 +1,4 @@
1
- import Mustache from "mustache";
1
+ import { renderTemplate } from "@poe-code/design-system";
2
2
  import { getConfigFormat, detectFormat } from "../formats/index.js";
3
3
  import { resolvePath } from "./path-utils.js";
4
4
  import { isNotFound, readFileIfExists, pathExists, createTimestamp } from "../fs-utils.js";
@@ -481,7 +481,7 @@ async function applyTemplateWrite(mutation, context, options) {
481
481
  const templateContext = mutation.context
482
482
  ? resolveValue(mutation.context, options)
483
483
  : {};
484
- const rendered = Mustache.render(template, templateContext);
484
+ const rendered = renderTemplate(template, templateContext);
485
485
  const existed = await pathExists(context.fs, targetPath);
486
486
  if (!context.dryRun) {
487
487
  await context.fs.writeFile(targetPath, rendered, { encoding: "utf8" });
@@ -513,7 +513,7 @@ async function applyTemplateMerge(mutation, context, options, formatName) {
513
513
  const templateContext = mutation.context
514
514
  ? resolveValue(mutation.context, options)
515
515
  : {};
516
- const rendered = Mustache.render(template, templateContext);
516
+ const rendered = renderTemplate(template, templateContext);
517
517
  // Parse rendered template
518
518
  let templateDoc;
519
519
  try {
@@ -4,7 +4,7 @@ export interface WriteOptions {
4
4
  target: ValueResolver<string>;
5
5
  /** Template ID to load via template loader */
6
6
  templateId: string;
7
- /** Context to pass to Mustache.render() */
7
+ /** Context to pass to renderTemplate() */
8
8
  context?: ValueResolver<ConfigObject>;
9
9
  /** Optional human-readable label for logging */
10
10
  label?: string;
@@ -14,7 +14,7 @@ export interface MergeTomlOptions {
14
14
  target: ValueResolver<string>;
15
15
  /** Template ID to load via template loader */
16
16
  templateId: string;
17
- /** Context to pass to Mustache.render() */
17
+ /** Context to pass to renderTemplate() */
18
18
  context?: ValueResolver<ConfigObject>;
19
19
  /** Optional human-readable label for logging */
20
20
  label?: string;
@@ -24,7 +24,7 @@ export interface MergeJsonOptions {
24
24
  target: ValueResolver<string>;
25
25
  /** Template ID to load via template loader */
26
26
  templateId: string;
27
- /** Context to pass to Mustache.render() */
27
+ /** Context to pass to renderTemplate() */
28
28
  context?: ValueResolver<ConfigObject>;
29
29
  /** Optional human-readable label for logging */
30
30
  label?: string;
@@ -1,7 +1,6 @@
1
1
  export type TemplateVariables = Record<string, string | number | boolean | string[]>;
2
2
  /**
3
3
  * Render a mustache template with the given variables.
4
- * Arrays are automatically joined with newlines.
5
4
  * HTML escaping is disabled.
6
5
  */
7
6
  export declare function renderTemplate(template: string, variables: TemplateVariables): string;
@@ -1,28 +1,8 @@
1
- import Mustache from "mustache";
2
- // Disable HTML escaping - we're rendering prompts, not HTML
3
- const originalEscape = Mustache.escape;
1
+ import { renderTemplate as renderDesignTemplate } from "@poe-code/design-system";
4
2
  /**
5
3
  * Render a mustache template with the given variables.
6
- * Arrays are automatically joined with newlines.
7
4
  * HTML escaping is disabled.
8
5
  */
9
6
  export function renderTemplate(template, variables) {
10
- // Pre-process variables to handle arrays
11
- const processed = {};
12
- for (const [key, value] of Object.entries(variables)) {
13
- if (Array.isArray(value)) {
14
- processed[key] = value.join("\n");
15
- }
16
- else {
17
- processed[key] = value;
18
- }
19
- }
20
- // Temporarily disable HTML escaping
21
- Mustache.escape = (text) => text;
22
- try {
23
- return Mustache.render(template, processed);
24
- }
25
- finally {
26
- Mustache.escape = originalEscape;
27
- }
7
+ return renderDesignTemplate(template, variables, { escape: "none" });
28
8
  }
@@ -22,12 +22,9 @@
22
22
  "dist"
23
23
  ],
24
24
  "dependencies": {
25
+ "@poe-code/design-system": "*",
25
26
  "jsonc-parser": "^3.3.1",
26
- "mustache": "^4.2.0",
27
27
  "smol-toml": "^1.3.0",
28
28
  "yaml": "^2.8.1"
29
- },
30
- "devDependencies": {
31
- "@types/mustache": "^4.2.6"
32
29
  }
33
30
  }
@@ -1,4 +1,4 @@
1
- import chalk from "chalk";
1
+ import { color } from "../components/color.js";
2
2
  import { resolveOutputFormat } from "../internal/output-format.js";
3
3
  import { renderMarkdown } from "../terminal-markdown/index.js";
4
4
  import { getAcpWriter } from "./writer.js";
@@ -10,20 +10,22 @@ function truncate(text, maxLength) {
10
10
  return `${text.slice(0, maxLength - 3)}...`;
11
11
  }
12
12
  const KIND_COLORS = {
13
- exec: (text) => chalk.yellow(text),
14
- edit: (text) => chalk.magenta(text),
15
- read: (text) => chalk.cyan(text),
16
- search: (text) => chalk.blue(text),
17
- think: (text) => chalk.dim(text),
18
- other: (text) => chalk.dim(text)
13
+ exec: (text) => color.yellow(text),
14
+ edit: (text) => color.magenta(text),
15
+ read: (text) => color.cyan(text),
16
+ search: (text) => color.blue(text),
17
+ think: (text) => color.dim(text),
18
+ other: (text) => color.dim(text)
19
19
  };
20
20
  function colorForKind(kind) {
21
- return KIND_COLORS[kind] ?? ((text) => chalk.dim(text));
21
+ return KIND_COLORS[kind] ?? ((text) => color.dim(text));
22
22
  }
23
23
  function writeLine(line) {
24
24
  getAcpWriter()(line);
25
25
  }
26
- const AGENT_PREFIX = `${chalk.green.bold("✓")} agent: `;
26
+ function agentPrefix() {
27
+ return `${color.green.bold("✓")} agent: `;
28
+ }
27
29
  function formatCost(costUsd) {
28
30
  return new Intl.NumberFormat("en-US", {
29
31
  style: "currency",
@@ -43,7 +45,7 @@ export function renderAgentMessage(text) {
43
45
  return;
44
46
  }
45
47
  const rendered = renderMarkdown(text).trimEnd();
46
- writeLine(`${AGENT_PREFIX}${rendered}`);
48
+ writeLine(`${agentPrefix()}${rendered}`);
47
49
  }
48
50
  export function renderToolStart(kind, title) {
49
51
  const format = resolveOutputFormat();
@@ -81,7 +83,7 @@ export function renderReasoning(text) {
81
83
  writeLine(JSON.stringify({ event: "reasoning", text }));
82
84
  return;
83
85
  }
84
- writeLine(chalk.dim(` ✓ ${truncate(text, 80)}`));
86
+ writeLine(color.dim(` ✓ ${truncate(text, 80)}`));
85
87
  }
86
88
  export function renderUsage(tokens) {
87
89
  const format = resolveOutputFormat();
@@ -105,7 +107,7 @@ export function renderUsage(tokens) {
105
107
  return;
106
108
  }
107
109
  writeLine("");
108
- writeLine(chalk.green(`✓ tokens: ${tokens.input} in${cached} → ${tokens.output} out${cost}`));
110
+ writeLine(color.green(`✓ tokens: ${tokens.input} in${cached} → ${tokens.output} out${cost}`));
109
111
  }
110
112
  export function renderError(message) {
111
113
  const format = resolveOutputFormat();
@@ -117,5 +119,5 @@ export function renderError(message) {
117
119
  writeLine(JSON.stringify({ event: "error", message }));
118
120
  return;
119
121
  }
120
- writeLine(chalk.red(`✗ ${message}`));
122
+ writeLine(color.red(`✗ ${message}`));
121
123
  }
@@ -0,0 +1,31 @@
1
+ export interface Color {
2
+ (text: string): string;
3
+ reset: Color;
4
+ bold: Color;
5
+ dim: Color;
6
+ italic: Color;
7
+ underline: Color;
8
+ inverse: Color;
9
+ strikethrough: Color;
10
+ black: Color;
11
+ red: Color;
12
+ green: Color;
13
+ yellow: Color;
14
+ blue: Color;
15
+ magenta: Color;
16
+ cyan: Color;
17
+ white: Color;
18
+ gray: Color;
19
+ magentaBright: Color;
20
+ cyanBright: Color;
21
+ bgRed: Color;
22
+ bgGreen: Color;
23
+ bgYellow: Color;
24
+ bgBlue: Color;
25
+ bgMagenta: Color;
26
+ hex: (value: string) => Color;
27
+ rgb: (red: number, green: number, blue: number) => Color;
28
+ bgHex: (value: string) => Color;
29
+ bgRgb: (red: number, green: number, blue: number) => Color;
30
+ }
31
+ export declare const color: Color;
@@ -0,0 +1,101 @@
1
+ import { supportsColor } from "../internal/color-support.js";
2
+ const reset = "\x1b[0m";
3
+ const ansiStyles = {
4
+ reset: { open: reset },
5
+ bold: { open: "\x1b[1m" },
6
+ dim: { open: "\x1b[2m" },
7
+ italic: { open: "\x1b[3m" },
8
+ underline: { open: "\x1b[4m" },
9
+ inverse: { open: "\x1b[7m" },
10
+ strikethrough: { open: "\x1b[9m" },
11
+ black: { open: "\x1b[30m" },
12
+ red: { open: "\x1b[31m" },
13
+ green: { open: "\x1b[32m" },
14
+ yellow: { open: "\x1b[33m" },
15
+ blue: { open: "\x1b[34m" },
16
+ magenta: { open: "\x1b[35m" },
17
+ cyan: { open: "\x1b[36m" },
18
+ white: { open: "\x1b[37m" },
19
+ gray: { open: "\x1b[90m" },
20
+ magentaBright: { open: "\x1b[95m" },
21
+ cyanBright: { open: "\x1b[96m" },
22
+ bgRed: { open: "\x1b[41m" },
23
+ bgGreen: { open: "\x1b[42m" },
24
+ bgYellow: { open: "\x1b[43m" },
25
+ bgBlue: { open: "\x1b[44m" },
26
+ bgMagenta: { open: "\x1b[45m" }
27
+ };
28
+ const styleNames = Object.keys(ansiStyles);
29
+ function replaceAll(value, search, replacement) {
30
+ return value.split(search).join(replacement);
31
+ }
32
+ function applyStyles(text, styles) {
33
+ if (!supportsColor() || styles.length === 0) {
34
+ return text;
35
+ }
36
+ const open = styles.map((style) => style.open).join("");
37
+ const output = text.includes(reset) ? replaceAll(text, reset, `${reset}${open}`) : text;
38
+ return `${open}${output}${reset}`;
39
+ }
40
+ function clampRgb(value) {
41
+ if (Number.isNaN(value)) {
42
+ return 0;
43
+ }
44
+ return Math.min(255, Math.max(0, Math.round(value)));
45
+ }
46
+ function hexChannel(value, offset) {
47
+ return Number.parseInt(value.slice(offset, offset + 2), 16);
48
+ }
49
+ function normalizeHex(value) {
50
+ const normalized = value.startsWith("#") ? value.slice(1) : value;
51
+ if (normalized.length === 3) {
52
+ const red = normalized[0];
53
+ const green = normalized[1];
54
+ const blue = normalized[2];
55
+ return [
56
+ Number.parseInt(`${red}${red}`, 16),
57
+ Number.parseInt(`${green}${green}`, 16),
58
+ Number.parseInt(`${blue}${blue}`, 16)
59
+ ];
60
+ }
61
+ if (normalized.length === 6) {
62
+ return [
63
+ hexChannel(normalized, 0),
64
+ hexChannel(normalized, 2),
65
+ hexChannel(normalized, 4)
66
+ ];
67
+ }
68
+ return [0, 0, 0];
69
+ }
70
+ function rgbStyle(red, green, blue) {
71
+ return {
72
+ open: `\x1b[38;2;${clampRgb(red)};${clampRgb(green)};${clampRgb(blue)}m`
73
+ };
74
+ }
75
+ function bgRgbStyle(red, green, blue) {
76
+ return {
77
+ open: `\x1b[48;2;${clampRgb(red)};${clampRgb(green)};${clampRgb(blue)}m`
78
+ };
79
+ }
80
+ function createColor(styles = []) {
81
+ const builder = ((text) => applyStyles(String(text), styles));
82
+ for (const name of styleNames) {
83
+ Object.defineProperty(builder, name, {
84
+ configurable: true,
85
+ enumerable: true,
86
+ get: () => createColor([...styles, ansiStyles[name]])
87
+ });
88
+ }
89
+ builder.hex = (value) => {
90
+ const [red, green, blue] = normalizeHex(value);
91
+ return createColor([...styles, rgbStyle(red, green, blue)]);
92
+ };
93
+ builder.rgb = (red, green, blue) => createColor([...styles, rgbStyle(red, green, blue)]);
94
+ builder.bgHex = (value) => {
95
+ const [red, green, blue] = normalizeHex(value);
96
+ return createColor([...styles, bgRgbStyle(red, green, blue)]);
97
+ };
98
+ builder.bgRgb = (red, green, blue) => createColor([...styles, bgRgbStyle(red, green, blue)]);
99
+ return builder;
100
+ }
101
+ export const color = createColor();
@@ -1,4 +1,6 @@
1
1
  export { text } from "./text.js";
2
+ export { color } from "./color.js";
3
+ export type { Color } from "./color.js";
2
4
  export { symbols } from "./symbols.js";
3
5
  export { createLogger, logger } from "./logger.js";
4
6
  export type { LoggerOutput } from "./logger.js";
@@ -8,3 +10,5 @@ export { formatCommandNotFound } from "./command-errors.js";
8
10
  export { formatCommandNotFoundPanel } from "./command-errors.js";
9
11
  export { renderTable } from "./table.js";
10
12
  export type { TableColumn, RenderTableOptions } from "./table.js";
13
+ export { renderTemplate } from "./template.js";
14
+ export type { RenderTemplateOptions, TemplateEscape } from "./template.js";
@@ -1,7 +1,9 @@
1
1
  export { text } from "./text.js";
2
+ export { color } from "./color.js";
2
3
  export { symbols } from "./symbols.js";
3
4
  export { createLogger, logger } from "./logger.js";
4
5
  export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./help-formatter.js";
5
6
  export { formatCommandNotFound } from "./command-errors.js";
6
7
  export { formatCommandNotFoundPanel } from "./command-errors.js";
7
8
  export { renderTable } from "./table.js";
9
+ export { renderTemplate } from "./template.js";
@@ -1,4 +1,4 @@
1
- import chalk from "chalk";
1
+ import { color } from "./color.js";
2
2
  import { log } from "../prompts/primitives/log.js";
3
3
  import { symbols } from "./symbols.js";
4
4
  export function createLogger(emitter) {
@@ -53,7 +53,7 @@ export function createLogger(emitter) {
53
53
  emitter(message);
54
54
  return;
55
55
  }
56
- log.message(message, { symbol: symbol ?? chalk.gray("│") });
56
+ log.message(message, { symbol: symbol ?? color.gray("│") });
57
57
  }
58
58
  };
59
59
  }
@@ -1,4 +1,4 @@
1
- import chalk from "chalk";
1
+ import { color } from "./color.js";
2
2
  import { resolveOutputFormat } from "../internal/output-format.js";
3
3
  import { getTheme } from "../internal/theme-detect.js";
4
4
  export const symbols = {
@@ -8,7 +8,7 @@ export const symbols = {
8
8
  return "info";
9
9
  if (format === "markdown")
10
10
  return "(i)";
11
- return chalk.magenta("●");
11
+ return color.magenta("●");
12
12
  },
13
13
  get success() {
14
14
  const format = resolveOutputFormat();
@@ -16,7 +16,7 @@ export const symbols = {
16
16
  return "success";
17
17
  if (format === "markdown")
18
18
  return "[ok]";
19
- return chalk.magenta("◆");
19
+ return color.magenta("◆");
20
20
  },
21
21
  get resolved() {
22
22
  const format = resolveOutputFormat();
@@ -1,52 +1,203 @@
1
- import { Table } from "console-table-printer";
2
1
  import { resolveOutputFormat } from "../internal/output-format.js";
3
2
  import { stripAnsi } from "../internal/strip-ansi.js";
3
+ const reset = "\x1b[0m";
4
+ const ellipsis = "…";
5
+ const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
6
+ function isAnsiSequence(value, index) {
7
+ return value[index] === "\u001b" && value[index + 1] === "[";
8
+ }
9
+ function readAnsiSequence(value, index) {
10
+ let nextIndex = index + 2;
11
+ while (nextIndex < value.length && value[nextIndex] !== "m") {
12
+ nextIndex += 1;
13
+ }
14
+ if (nextIndex < value.length) {
15
+ nextIndex += 1;
16
+ }
17
+ return { sequence: value.slice(index, nextIndex), nextIndex };
18
+ }
19
+ function isCombiningCodePoint(codePoint) {
20
+ return ((codePoint >= 0x0300 && codePoint <= 0x036f) ||
21
+ (codePoint >= 0x1ab0 && codePoint <= 0x1aff) ||
22
+ (codePoint >= 0x1dc0 && codePoint <= 0x1dff) ||
23
+ (codePoint >= 0x20d0 && codePoint <= 0x20ff) ||
24
+ (codePoint >= 0xfe20 && codePoint <= 0xfe2f));
25
+ }
26
+ function isWideCodePoint(codePoint) {
27
+ return ((codePoint >= 0x1100 && codePoint <= 0x115f) ||
28
+ codePoint === 0x2329 ||
29
+ codePoint === 0x232a ||
30
+ (codePoint >= 0x2e80 && codePoint <= 0xa4cf && codePoint !== 0x303f) ||
31
+ (codePoint >= 0xac00 && codePoint <= 0xd7a3) ||
32
+ (codePoint >= 0xf900 && codePoint <= 0xfaff) ||
33
+ (codePoint >= 0xfe10 && codePoint <= 0xfe19) ||
34
+ (codePoint >= 0xfe30 && codePoint <= 0xfe6f) ||
35
+ (codePoint >= 0xff00 && codePoint <= 0xff60) ||
36
+ (codePoint >= 0xffe0 && codePoint <= 0xffe6) ||
37
+ (codePoint >= 0x2600 && codePoint <= 0x27bf) ||
38
+ (codePoint >= 0x1f300 && codePoint <= 0x1faff) ||
39
+ (codePoint >= 0x20000 && codePoint <= 0x3fffd));
40
+ }
41
+ function isEmojiClusterCodePoint(codePoint) {
42
+ return ((codePoint >= 0x1f1e6 && codePoint <= 0x1f1ff) ||
43
+ (codePoint >= 0x1f300 && codePoint <= 0x1faff) ||
44
+ (codePoint >= 0x2600 && codePoint <= 0x27bf));
45
+ }
46
+ function codePointWidth(char) {
47
+ const codePoint = char.codePointAt(0) ?? 0;
48
+ if (codePoint === 0 || codePoint < 0x20 || (codePoint >= 0x7f && codePoint < 0xa0)) {
49
+ return 0;
50
+ }
51
+ if (codePoint === 0x200d ||
52
+ (codePoint >= 0xfe00 && codePoint <= 0xfe0f) ||
53
+ isCombiningCodePoint(codePoint)) {
54
+ return 0;
55
+ }
56
+ return isWideCodePoint(codePoint) ? 2 : 1;
57
+ }
58
+ function readPrintableCluster(value, index) {
59
+ const nextAnsiIndex = value.indexOf("\u001b[", index);
60
+ const plainText = value.slice(index, nextAnsiIndex === -1 ? undefined : nextAnsiIndex);
61
+ const firstSegment = graphemeSegmenter.segment(plainText)[Symbol.iterator]().next().value;
62
+ return firstSegment?.segment ?? Array.from(plainText)[0] ?? "";
63
+ }
64
+ function clusterWidth(cluster) {
65
+ const codePoints = Array.from(cluster).map((char) => char.codePointAt(0) ?? 0);
66
+ const isEmojiCluster = codePoints.length > 1 &&
67
+ codePoints.some((codePoint) => codePoint === 0x200d ||
68
+ (codePoint >= 0xfe00 && codePoint <= 0xfe0f) ||
69
+ isEmojiClusterCodePoint(codePoint));
70
+ if (isEmojiCluster) {
71
+ return 2;
72
+ }
73
+ return codePoints.reduce((width, codePoint) => width + codePointWidth(String.fromCodePoint(codePoint)), 0);
74
+ }
75
+ function displayWidth(value) {
76
+ let width = 0;
77
+ let index = 0;
78
+ while (index < value.length) {
79
+ if (isAnsiSequence(value, index)) {
80
+ index = readAnsiSequence(value, index).nextIndex;
81
+ continue;
82
+ }
83
+ const cluster = readPrintableCluster(value, index);
84
+ width += clusterWidth(cluster);
85
+ index += cluster.length;
86
+ }
87
+ return width;
88
+ }
89
+ function truncateToWidth(value, width) {
90
+ if (displayWidth(value) <= width) {
91
+ return value;
92
+ }
93
+ if (width <= 0) {
94
+ return "";
95
+ }
96
+ const targetWidth = width <= 1 ? 0 : width - displayWidth(ellipsis);
97
+ let output = "";
98
+ let currentWidth = 0;
99
+ let index = 0;
100
+ let sawAnsi = false;
101
+ while (index < value.length) {
102
+ if (isAnsiSequence(value, index)) {
103
+ const ansi = readAnsiSequence(value, index);
104
+ sawAnsi = true;
105
+ output += ansi.sequence;
106
+ index = ansi.nextIndex;
107
+ continue;
108
+ }
109
+ const cluster = readPrintableCluster(value, index);
110
+ const width = clusterWidth(cluster);
111
+ if (currentWidth + width > targetWidth) {
112
+ break;
113
+ }
114
+ output += cluster;
115
+ currentWidth += width;
116
+ index += cluster.length;
117
+ }
118
+ return `${output}${ellipsis}${sawAnsi ? reset : ""}`;
119
+ }
120
+ function padCell(value, width, alignment) {
121
+ const visibleWidth = displayWidth(value);
122
+ const padding = Math.max(0, width - visibleWidth);
123
+ if (alignment === "right") {
124
+ return `${" ".repeat(padding)}${value}`;
125
+ }
126
+ if (alignment === "center") {
127
+ const left = Math.floor(padding / 2);
128
+ const right = padding - left;
129
+ return `${" ".repeat(left)}${value}${" ".repeat(right)}`;
130
+ }
131
+ return `${value}${" ".repeat(padding)}`;
132
+ }
133
+ function getAlignment(column) {
134
+ const alignment = column.alignment;
135
+ return alignment === "right" || alignment === "center" ? alignment : "left";
136
+ }
137
+ function getColumnWidth(column) {
138
+ const configuredMin = column.minLen;
139
+ const minWidth = Math.max(1, configuredMin ?? 1);
140
+ return Math.max(minWidth, column.maxLen);
141
+ }
142
+ function computeColumns(columns) {
143
+ return columns.map((column) => ({
144
+ name: column.name,
145
+ title: column.title,
146
+ alignment: getAlignment(column),
147
+ width: getColumnWidth(column)
148
+ }));
149
+ }
150
+ function renderBorder(columns, theme, parts) {
151
+ const horizontal = theme.muted("─");
152
+ const segments = columns.map((column) => horizontal.repeat(column.width + 2));
153
+ return [
154
+ theme.muted(parts.left),
155
+ segments.join(theme.muted(parts.mid)),
156
+ theme.muted(parts.right)
157
+ ].join("");
158
+ }
159
+ function renderTerminalRow(values, columns, theme) {
160
+ const vertical = theme.muted("│");
161
+ const cells = values.map((value, index) => {
162
+ const column = columns[index];
163
+ const truncated = truncateToWidth(value, column.width);
164
+ return ` ${padCell(truncated, column.width, column.alignment)} `;
165
+ });
166
+ return `${vertical}${cells.join(vertical)}${vertical}`;
167
+ }
4
168
  function renderTableTerminal(options) {
5
169
  const { theme, columns, rows } = options;
6
- const table = new Table({
7
- style: {
8
- headerTop: {
9
- left: theme.muted("┌"),
10
- mid: theme.muted("┬"),
11
- right: theme.muted(""),
12
- other: theme.muted("")
13
- },
14
- headerBottom: {
15
- left: theme.muted("├"),
16
- mid: theme.muted("┼"),
17
- right: theme.muted("┤"),
18
- other: theme.muted("")
19
- },
20
- tableBottom: {
21
- left: theme.muted("└"),
22
- mid: theme.muted("┴"),
23
- right: theme.muted("┘"),
24
- other: theme.muted("─")
25
- },
26
- vertical: theme.muted("│"),
27
- rowSeparator: {
28
- left: theme.muted("├"),
29
- mid: theme.muted("┼"),
30
- right: theme.muted("┤"),
31
- other: theme.muted("─")
32
- }
33
- },
34
- columns: columns.map((col) => ({
35
- name: col.name,
36
- title: theme.header(col.title),
37
- alignment: col.alignment,
38
- maxLen: col.maxLen
39
- }))
40
- });
41
- for (const row of rows) {
42
- table.addRow(row);
170
+ const computedColumns = computeColumns(columns);
171
+ const separatorOptions = options;
172
+ const includeRowSeparators = separatorOptions.rowSeparator === true || separatorOptions.rowSeparators === true;
173
+ const top = renderBorder(computedColumns, theme, { left: "┌", mid: "┬", right: "┐" });
174
+ const header = renderTerminalRow(computedColumns.map((column) => theme.header(column.title)), computedColumns, theme);
175
+ const headerBottom = renderBorder(computedColumns, theme, { left: "├", mid: "┼", right: "" });
176
+ const bottom = renderBorder(computedColumns, theme, { left: "", mid: "┴", right: "┘" });
177
+ const renderedRows = [];
178
+ for (const [index, row] of rows.entries()) {
179
+ if (includeRowSeparators && index > 0) {
180
+ renderedRows.push(headerBottom);
181
+ }
182
+ renderedRows.push(renderTerminalRow(computedColumns.map((column) => row[column.name] ?? ""), computedColumns, theme));
43
183
  }
44
- return table.render();
184
+ return [top, header, headerBottom, ...renderedRows, bottom].join("\n");
45
185
  }
46
186
  function renderTableMarkdown(options) {
47
187
  const { columns, rows } = options;
48
188
  const header = `| ${columns.map((c) => c.title).join(" | ")} |`;
49
- const separator = `| ${columns.map((c) => (c.alignment === "right" ? "---:" : ":---")).join(" | ")} |`;
189
+ const separator = `| ${columns
190
+ .map((c) => {
191
+ const alignment = getAlignment(c);
192
+ if (alignment === "right") {
193
+ return "---:";
194
+ }
195
+ if (alignment === "center") {
196
+ return ":---:";
197
+ }
198
+ return ":---";
199
+ })
200
+ .join(" | ")} |`;
50
201
  const dataRows = rows.map((row) => `| ${columns.map((c) => stripAnsi(row[c.name] ?? "").replace(/\|/g, "\\|")).join(" | ")} |`);
51
202
  return [header, separator, ...dataRows].join("\n");
52
203
  }
@@ -0,0 +1,6 @@
1
+ export type TemplateEscape = "html" | "none";
2
+ export interface RenderTemplateOptions {
3
+ escape?: TemplateEscape;
4
+ yield?: string;
5
+ }
6
+ export declare function renderTemplate(template: string, view: Record<string, unknown>, options?: RenderTemplateOptions): string;