toolcraft 0.0.18 → 0.0.20

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 (36) hide show
  1. package/dist/cli.js +32 -34
  2. package/node_modules/@poe-code/config-mutations/dist/execution/apply-mutation.js +3 -3
  3. package/node_modules/@poe-code/config-mutations/dist/mutations/template-mutation.d.ts +3 -3
  4. package/node_modules/@poe-code/config-mutations/dist/template/render.d.ts +0 -1
  5. package/node_modules/@poe-code/config-mutations/dist/template/render.js +2 -22
  6. package/node_modules/@poe-code/config-mutations/package.json +1 -4
  7. package/node_modules/@poe-code/design-system/dist/acp/components.js +15 -13
  8. package/node_modules/@poe-code/design-system/dist/components/color.d.ts +31 -0
  9. package/node_modules/@poe-code/design-system/dist/components/color.js +101 -0
  10. package/node_modules/@poe-code/design-system/dist/components/index.d.ts +4 -0
  11. package/node_modules/@poe-code/design-system/dist/components/index.js +2 -0
  12. package/node_modules/@poe-code/design-system/dist/components/logger.js +2 -2
  13. package/node_modules/@poe-code/design-system/dist/components/symbols.js +3 -3
  14. package/node_modules/@poe-code/design-system/dist/components/table.js +191 -40
  15. package/node_modules/@poe-code/design-system/dist/components/template.d.ts +6 -0
  16. package/node_modules/@poe-code/design-system/dist/components/template.js +271 -0
  17. package/node_modules/@poe-code/design-system/dist/components/text.js +3 -3
  18. package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +12 -12
  19. package/node_modules/@poe-code/design-system/dist/index.d.ts +4 -0
  20. package/node_modules/@poe-code/design-system/dist/index.js +2 -0
  21. package/node_modules/@poe-code/design-system/dist/internal/color-support.d.ts +9 -0
  22. package/node_modules/@poe-code/design-system/dist/internal/color-support.js +12 -0
  23. package/node_modules/@poe-code/design-system/dist/prompts/index.d.ts +1 -1
  24. package/node_modules/@poe-code/design-system/dist/prompts/index.js +5 -4
  25. package/node_modules/@poe-code/design-system/dist/prompts/primitives/cancel.js +2 -2
  26. package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +2 -2
  27. package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +4 -4
  28. package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +5 -5
  29. package/node_modules/@poe-code/design-system/dist/prompts/primitives/outro.js +2 -2
  30. package/node_modules/@poe-code/design-system/dist/prompts/primitives/spinner.js +3 -3
  31. package/node_modules/@poe-code/design-system/dist/static/menu.js +5 -5
  32. package/node_modules/@poe-code/design-system/dist/static/spinner.js +8 -8
  33. package/node_modules/@poe-code/design-system/dist/tokens/colors.js +29 -29
  34. package/node_modules/@poe-code/design-system/dist/tokens/typography.js +6 -6
  35. package/node_modules/@poe-code/design-system/package.json +5 -3
  36. package/package.json +2 -5
@@ -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;
@@ -0,0 +1,271 @@
1
+ const HTML_ESCAPE = {
2
+ "&": "&amp;",
3
+ "<": "&lt;",
4
+ ">": "&gt;",
5
+ '"': "&quot;",
6
+ "'": "&#39;",
7
+ "/": "&#x2F;",
8
+ "`": "&#x60;",
9
+ "=": "&#x3D;"
10
+ };
11
+ export function renderTemplate(template, view, options = {}) {
12
+ const prepared = options.yield === undefined
13
+ ? template
14
+ : template.split("{{yield}}").join(options.yield);
15
+ const tokens = parseTemplate(prepared);
16
+ const escape = options.escape === "none" ? String : escapeHtml;
17
+ const preserveMissing = options.yield !== undefined && options.escape === "none";
18
+ return renderTokens(tokens, { view }, prepared, escape, preserveMissing);
19
+ }
20
+ function renderTemplateInContext(template, context, escape, preserveMissing) {
21
+ return renderTokens(parseTemplate(template), context, template, escape, preserveMissing);
22
+ }
23
+ function parseTemplate(template) {
24
+ const root = [];
25
+ const stack = [];
26
+ let tokens = root;
27
+ let index = 0;
28
+ while (index < template.length) {
29
+ const open = template.indexOf("{{", index);
30
+ if (open === -1) {
31
+ appendText(tokens, template.slice(index), index);
32
+ break;
33
+ }
34
+ appendText(tokens, template.slice(index, open), index);
35
+ const parsed = parseTag(template, open);
36
+ const standalone = getStandalone(template, open, parsed.end, parsed.kind);
37
+ if (standalone !== undefined) {
38
+ trimTextAfter(tokens, standalone.lineStart);
39
+ }
40
+ if (parsed.kind === "comment") {
41
+ index = standalone?.nextIndex ?? parsed.end;
42
+ continue;
43
+ }
44
+ if (parsed.kind === "partial") {
45
+ throw new Error(`Partials are not supported: "${parsed.name}"`);
46
+ }
47
+ if (parsed.kind === "delimiter") {
48
+ throw new Error("Custom delimiters are not supported");
49
+ }
50
+ if (parsed.kind === "section" || parsed.kind === "inverted") {
51
+ const token = {
52
+ type: parsed.kind,
53
+ name: parsed.name,
54
+ children: [],
55
+ rawStart: parsed.end,
56
+ rawEnd: standalone?.lineStart ?? open
57
+ };
58
+ tokens.push(token);
59
+ stack.push({ token, parent: tokens });
60
+ tokens = token.children;
61
+ index = standalone?.nextIndex ?? parsed.end;
62
+ continue;
63
+ }
64
+ if (parsed.kind === "close") {
65
+ const frame = stack.pop();
66
+ if (frame === undefined) {
67
+ throw new Error(`Closing unopened section "${parsed.name}"`);
68
+ }
69
+ if (frame.token.name !== parsed.name) {
70
+ throw new Error(`Unclosed section "${frame.token.name}" before closing "${parsed.name}"`);
71
+ }
72
+ frame.token.rawEnd = open;
73
+ tokens = frame.parent;
74
+ index = standalone?.nextIndex ?? parsed.end;
75
+ continue;
76
+ }
77
+ tokens.push({ type: parsed.kind, name: parsed.name, raw: template.slice(open, parsed.end) });
78
+ index = parsed.end;
79
+ }
80
+ const frame = stack.pop();
81
+ if (frame !== undefined) {
82
+ throw new Error(`Unclosed section "${frame.token.name}"`);
83
+ }
84
+ return root;
85
+ }
86
+ function parseTag(template, open) {
87
+ if (template.startsWith("{{{", open)) {
88
+ const close = template.indexOf("}}}", open + 3);
89
+ if (close === -1) {
90
+ throw new Error("Unclosed unescaped tag");
91
+ }
92
+ return { kind: "unescaped", name: template.slice(open + 3, close).trim(), end: close + 3 };
93
+ }
94
+ const close = template.indexOf("}}", open + 2);
95
+ if (close === -1) {
96
+ throw new Error("Unclosed tag");
97
+ }
98
+ const raw = template.slice(open + 2, close).trim();
99
+ const sigil = raw[0];
100
+ const name = sigil === undefined ? "" : raw.slice(1).trim();
101
+ const end = close + 2;
102
+ if (sigil === "#")
103
+ return { kind: "section", name, end };
104
+ if (sigil === "^")
105
+ return { kind: "inverted", name, end };
106
+ if (sigil === "/")
107
+ return { kind: "close", name, end };
108
+ if (sigil === "!")
109
+ return { kind: "comment", name, end };
110
+ if (sigil === "&")
111
+ return { kind: "unescaped", name, end };
112
+ if (sigil === ">")
113
+ return { kind: "partial", name, end };
114
+ if (sigil === "=" && raw.endsWith("="))
115
+ return { kind: "delimiter", name, end };
116
+ return { kind: "name", name: raw, end };
117
+ }
118
+ function getStandalone(template, tagStart, tagEnd, kind) {
119
+ if (!["section", "inverted", "close", "comment", "partial", "delimiter"].includes(kind)) {
120
+ return undefined;
121
+ }
122
+ const lineStart = template.lastIndexOf("\n", tagStart - 1) + 1;
123
+ if (!isWhitespace(template.slice(lineStart, tagStart))) {
124
+ return undefined;
125
+ }
126
+ let cursor = tagEnd;
127
+ while (cursor < template.length && (template[cursor] === " " || template[cursor] === "\t")) {
128
+ cursor += 1;
129
+ }
130
+ if (template.startsWith("\r\n", cursor)) {
131
+ return { lineStart, nextIndex: cursor + 2 };
132
+ }
133
+ if (template[cursor] === "\n") {
134
+ return { lineStart, nextIndex: cursor + 1 };
135
+ }
136
+ if (cursor === template.length) {
137
+ return { lineStart, nextIndex: cursor };
138
+ }
139
+ return undefined;
140
+ }
141
+ function renderTokens(tokens, context, template, escape, preserveMissing) {
142
+ let output = "";
143
+ for (const token of tokens) {
144
+ switch (token.type) {
145
+ case "text":
146
+ output += token.value;
147
+ continue;
148
+ case "name":
149
+ case "unescaped": {
150
+ const value = lookup(context, token.name);
151
+ if (value == null) {
152
+ if (preserveMissing) {
153
+ output += token.raw;
154
+ }
155
+ continue;
156
+ }
157
+ const rendered = String(value);
158
+ output += token.type === "name" ? escape(rendered) : rendered;
159
+ continue;
160
+ }
161
+ case "inverted": {
162
+ const value = lookup(context, token.name);
163
+ if (!value || (Array.isArray(value) && value.length === 0)) {
164
+ output += renderTokens(token.children, context, template, escape, preserveMissing);
165
+ }
166
+ continue;
167
+ }
168
+ case "section": {
169
+ const value = lookup(context, token.name);
170
+ if (!value) {
171
+ continue;
172
+ }
173
+ if (Array.isArray(value)) {
174
+ for (const item of value) {
175
+ output += renderTokens(token.children, pushContext(context, item), template, escape, preserveMissing);
176
+ }
177
+ continue;
178
+ }
179
+ if (typeof value === "function") {
180
+ const raw = template.slice(token.rawStart, token.rawEnd);
181
+ const rendered = value.call(context.view, raw, (nextTemplate) => renderTemplateInContext(nextTemplate, context, escape, preserveMissing));
182
+ if (rendered != null) {
183
+ output += String(rendered);
184
+ }
185
+ continue;
186
+ }
187
+ if (typeof value === "object" || typeof value === "string" || typeof value === "number") {
188
+ output += renderTokens(token.children, pushContext(context, value), template, escape, preserveMissing);
189
+ continue;
190
+ }
191
+ output += renderTokens(token.children, context, template, escape, preserveMissing);
192
+ }
193
+ }
194
+ }
195
+ return output;
196
+ }
197
+ function lookup(context, name) {
198
+ if (name === ".") {
199
+ return callLambda(context.view, context.view);
200
+ }
201
+ let cursor = context;
202
+ while (cursor !== undefined) {
203
+ const result = name.includes(".")
204
+ ? lookupDotted(cursor.view, name)
205
+ : lookupName(cursor.view, name);
206
+ if (result.hit) {
207
+ return callLambda(result.value, cursor.view);
208
+ }
209
+ cursor = cursor.parent;
210
+ }
211
+ return undefined;
212
+ }
213
+ function lookupName(view, name) {
214
+ if (!isPropertyContainer(view) || !hasProperty(view, name)) {
215
+ return { hit: false, value: undefined };
216
+ }
217
+ return { hit: true, value: view[name] };
218
+ }
219
+ function lookupDotted(view, name) {
220
+ const parts = name.split(".");
221
+ let value = view;
222
+ let hit = false;
223
+ for (let index = 0; value != null && index < parts.length; index += 1) {
224
+ const part = parts[index] ?? "";
225
+ if (index === parts.length - 1) {
226
+ hit = hasProperty(Object(value), part);
227
+ }
228
+ value = Object(value)[part];
229
+ }
230
+ return { hit, value };
231
+ }
232
+ function callLambda(value, view) {
233
+ return typeof value === "function" ? value.call(view) : value;
234
+ }
235
+ function pushContext(parent, view) {
236
+ return { view, parent };
237
+ }
238
+ function appendText(tokens, value, start) {
239
+ if (value === "") {
240
+ return;
241
+ }
242
+ const previous = tokens[tokens.length - 1];
243
+ if (previous?.type === "text") {
244
+ previous.value += value;
245
+ return;
246
+ }
247
+ tokens.push({ type: "text", value, start });
248
+ }
249
+ function trimTextAfter(tokens, lineStart) {
250
+ const previous = tokens[tokens.length - 1];
251
+ if (previous?.type !== "text") {
252
+ return;
253
+ }
254
+ const keep = Math.max(0, lineStart - previous.start);
255
+ previous.value = previous.value.slice(0, keep);
256
+ if (previous.value === "") {
257
+ tokens.pop();
258
+ }
259
+ }
260
+ function escapeHtml(value) {
261
+ return value.replace(/[&<>"'`=/]/g, (char) => HTML_ESCAPE[char] ?? char);
262
+ }
263
+ function isWhitespace(value) {
264
+ return value.trim() === "";
265
+ }
266
+ function isPropertyContainer(value) {
267
+ return (typeof value === "object" && value !== null) || typeof value === "function";
268
+ }
269
+ function hasProperty(value, key) {
270
+ return key in value;
271
+ }
@@ -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
  import { typography } from "../tokens/typography.js";
@@ -57,7 +57,7 @@ export const text = {
57
57
  return content;
58
58
  if (format === "markdown")
59
59
  return `\`${content}\``;
60
- return chalk.yellow(content);
60
+ return color.yellow(content);
61
61
  },
62
62
  example(content) {
63
63
  const format = resolveOutputFormat();
@@ -73,7 +73,7 @@ export const text = {
73
73
  return content;
74
74
  if (format === "markdown")
75
75
  return `\`${content}\``;
76
- return chalk.green(content);
76
+ return color.green(content);
77
77
  },
78
78
  link(content) {
79
79
  const format = resolveOutputFormat();
@@ -1,4 +1,4 @@
1
- import chalk from "chalk";
1
+ import { color } from "../components/color.js";
2
2
  const EMPTY_CELL = { ch: " ", style: {} };
3
3
  export class ScreenBuffer {
4
4
  _width;
@@ -119,7 +119,7 @@ export function diff(prev, next) {
119
119
  }
120
120
  export function cellToAnsi(cell) {
121
121
  const style = cell.style ?? {};
122
- let painter = chalk;
122
+ let painter = color;
123
123
  if (style.bold) {
124
124
  painter = painter.bold;
125
125
  }
@@ -177,20 +177,20 @@ function cellsEqual(left, right) {
177
177
  && left.style.dim === right.style.dim
178
178
  && left.style.underline === right.style.underline;
179
179
  }
180
- function applyForegroundColor(instance, color) {
181
- if (color.startsWith("#")) {
182
- return instance.hex(color);
180
+ function applyForegroundColor(instance, ansiColor) {
181
+ if (ansiColor.startsWith("#")) {
182
+ return instance.hex(ansiColor);
183
183
  }
184
- const painter = instance[color];
184
+ const painter = instance[ansiColor];
185
185
  return typeof painter === "function" ? painter : instance;
186
186
  }
187
- function applyBackgroundColor(instance, color) {
188
- if (color.startsWith("#")) {
189
- return instance.bgHex(color);
187
+ function applyBackgroundColor(instance, ansiColor) {
188
+ if (ansiColor.startsWith("#")) {
189
+ return instance.bgHex(ansiColor);
190
190
  }
191
- const methodName = color.startsWith("bg")
192
- ? color
193
- : `bg${color.charAt(0).toUpperCase()}${color.slice(1)}`;
191
+ const methodName = ansiColor.startsWith("bg")
192
+ ? ansiColor
193
+ : `bg${ansiColor.charAt(0).toUpperCase()}${ansiColor.slice(1)}`;
194
194
  const painter = instance[methodName];
195
195
  return typeof painter === "function" ? painter : instance;
196
196
  }
@@ -5,6 +5,8 @@ export { spacing } from "./tokens/spacing.js";
5
5
  export { typography } from "./tokens/typography.js";
6
6
  export { widths } from "./tokens/widths.js";
7
7
  export { text } from "./components/text.js";
8
+ export { color } from "./components/color.js";
9
+ export type { Color } from "./components/color.js";
8
10
  export { symbols } from "./components/symbols.js";
9
11
  export { createLogger, logger } from "./components/logger.js";
10
12
  export type { LoggerOutput } from "./components/logger.js";
@@ -15,6 +17,8 @@ export { formatCommandNotFound } from "./components/command-errors.js";
15
17
  export { formatCommandNotFoundPanel } from "./components/command-errors.js";
16
18
  export { renderTable } from "./components/table.js";
17
19
  export type { TableColumn, RenderTableOptions } from "./components/table.js";
20
+ export { renderTemplate } from "./components/template.js";
21
+ export type { RenderTemplateOptions, TemplateEscape } from "./components/template.js";
18
22
  export * as acp from "./acp/index.js";
19
23
  export * as dashboard from "./dashboard/index.js";
20
24
  export { createDashboard, shouldUseInteractiveDashboard } from "./dashboard/index.js";
@@ -6,6 +6,7 @@ export { typography } from "./tokens/typography.js";
6
6
  export { widths } from "./tokens/widths.js";
7
7
  // Components
8
8
  export { text } from "./components/text.js";
9
+ export { color } from "./components/color.js";
9
10
  export { symbols } from "./components/symbols.js";
10
11
  export { createLogger, logger } from "./components/logger.js";
11
12
  export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./components/help-formatter.js";
@@ -13,6 +14,7 @@ export * as helpFormatterPlain from "./components/help-formatter-plain.js";
13
14
  export { formatCommandNotFound } from "./components/command-errors.js";
14
15
  export { formatCommandNotFoundPanel } from "./components/command-errors.js";
15
16
  export { renderTable } from "./components/table.js";
17
+ export { renderTemplate } from "./components/template.js";
16
18
  // ACP rendering
17
19
  export * as acp from "./acp/index.js";
18
20
  // Dashboard
@@ -0,0 +1,9 @@
1
+ export interface ColorSupportEnv {
2
+ NO_COLOR?: string;
3
+ FORCE_COLOR?: string;
4
+ TERM?: string;
5
+ }
6
+ export interface ColorSupportStream {
7
+ isTTY?: boolean;
8
+ }
9
+ export declare function supportsColor(env?: ColorSupportEnv, stream?: ColorSupportStream): boolean;
@@ -0,0 +1,12 @@
1
+ export function supportsColor(env = process.env, stream = process.stdout) {
2
+ if (env.FORCE_COLOR !== undefined && env.FORCE_COLOR !== "0") {
3
+ return true;
4
+ }
5
+ if (env.NO_COLOR !== undefined) {
6
+ return false;
7
+ }
8
+ if (stream.isTTY !== true) {
9
+ return false;
10
+ }
11
+ return typeof env.TERM === "string" && env.TERM.length > 0 && env.TERM !== "dumb";
12
+ }
@@ -58,7 +58,7 @@ export type SpinnerOptions = {
58
58
  message: (message?: string) => void;
59
59
  };
60
60
  export interface WithSpinnerOptions<T> {
61
- message: string;
61
+ message: string | (() => string);
62
62
  fn: () => Promise<T>;
63
63
  stopMessage?: (result: T) => string;
64
64
  subtext?: (result: T) => string | undefined;