toolcraft-openapi 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.
- package/node_modules/@poe-code/design-system/dist/acp/components.js +15 -13
- package/node_modules/@poe-code/design-system/dist/components/color.d.ts +31 -0
- package/node_modules/@poe-code/design-system/dist/components/color.js +101 -0
- package/node_modules/@poe-code/design-system/dist/components/index.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/components/index.js +2 -0
- package/node_modules/@poe-code/design-system/dist/components/logger.js +2 -2
- package/node_modules/@poe-code/design-system/dist/components/symbols.js +3 -3
- package/node_modules/@poe-code/design-system/dist/components/table.js +191 -40
- package/node_modules/@poe-code/design-system/dist/components/template.d.ts +6 -0
- package/node_modules/@poe-code/design-system/dist/components/template.js +271 -0
- package/node_modules/@poe-code/design-system/dist/components/text.js +3 -3
- package/node_modules/@poe-code/design-system/dist/dashboard/buffer.js +12 -12
- package/node_modules/@poe-code/design-system/dist/index.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/index.js +2 -0
- package/node_modules/@poe-code/design-system/dist/internal/color-support.d.ts +9 -0
- package/node_modules/@poe-code/design-system/dist/internal/color-support.js +12 -0
- package/node_modules/@poe-code/design-system/dist/prompts/index.d.ts +1 -1
- package/node_modules/@poe-code/design-system/dist/prompts/index.js +5 -4
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/cancel.js +2 -2
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/intro.js +2 -2
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/log.js +4 -4
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/note.js +5 -5
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/outro.js +2 -2
- package/node_modules/@poe-code/design-system/dist/prompts/primitives/spinner.js +3 -3
- package/node_modules/@poe-code/design-system/dist/static/menu.js +5 -5
- package/node_modules/@poe-code/design-system/dist/static/spinner.js +8 -8
- package/node_modules/@poe-code/design-system/dist/tokens/colors.js +29 -29
- package/node_modules/@poe-code/design-system/dist/tokens/typography.js +6 -6
- package/node_modules/@poe-code/design-system/package.json +5 -3
- package/package.json +2 -4
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
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) =>
|
|
14
|
-
edit: (text) =>
|
|
15
|
-
read: (text) =>
|
|
16
|
-
search: (text) =>
|
|
17
|
-
think: (text) =>
|
|
18
|
-
other: (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) =>
|
|
21
|
+
return KIND_COLORS[kind] ?? ((text) => color.dim(text));
|
|
22
22
|
}
|
|
23
23
|
function writeLine(line) {
|
|
24
24
|
getAcpWriter()(line);
|
|
25
25
|
}
|
|
26
|
-
|
|
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(`${
|
|
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(
|
|
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(
|
|
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(
|
|
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
|
|
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 ??
|
|
56
|
+
log.message(message, { symbol: symbol ?? color.gray("│") });
|
|
57
57
|
}
|
|
58
58
|
};
|
|
59
59
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
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
|
|
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
|
|
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
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
|
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
|
|
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,271 @@
|
|
|
1
|
+
const HTML_ESCAPE = {
|
|
2
|
+
"&": "&",
|
|
3
|
+
"<": "<",
|
|
4
|
+
">": ">",
|
|
5
|
+
'"': """,
|
|
6
|
+
"'": "'",
|
|
7
|
+
"/": "/",
|
|
8
|
+
"`": "`",
|
|
9
|
+
"=": "="
|
|
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
|
|
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
|
|
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
|
|
76
|
+
return color.green(content);
|
|
77
77
|
},
|
|
78
78
|
link(content) {
|
|
79
79
|
const format = resolveOutputFormat();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
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 =
|
|
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,
|
|
181
|
-
if (
|
|
182
|
-
return instance.hex(
|
|
180
|
+
function applyForegroundColor(instance, ansiColor) {
|
|
181
|
+
if (ansiColor.startsWith("#")) {
|
|
182
|
+
return instance.hex(ansiColor);
|
|
183
183
|
}
|
|
184
|
-
const painter = instance[
|
|
184
|
+
const painter = instance[ansiColor];
|
|
185
185
|
return typeof painter === "function" ? painter : instance;
|
|
186
186
|
}
|
|
187
|
-
function applyBackgroundColor(instance,
|
|
188
|
-
if (
|
|
189
|
-
return instance.bgHex(
|
|
187
|
+
function applyBackgroundColor(instance, ansiColor) {
|
|
188
|
+
if (ansiColor.startsWith("#")) {
|
|
189
|
+
return instance.bgHex(ansiColor);
|
|
190
190
|
}
|
|
191
|
-
const methodName =
|
|
192
|
-
?
|
|
193
|
-
: `bg${
|
|
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;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { color } from "../components/color.js";
|
|
2
2
|
import * as clack from "@clack/prompts";
|
|
3
3
|
import { resolveOutputFormat } from "../internal/output-format.js";
|
|
4
4
|
import { stripAnsi } from "../internal/strip-ansi.js";
|
|
@@ -19,7 +19,7 @@ export function introPlain(title) {
|
|
|
19
19
|
if (format === "json") {
|
|
20
20
|
return;
|
|
21
21
|
}
|
|
22
|
-
process.stdout.write(`${
|
|
22
|
+
process.stdout.write(`${color.gray("┌")} ${title}\n`);
|
|
23
23
|
}
|
|
24
24
|
export async function select(opts) {
|
|
25
25
|
return clack.select(opts);
|
|
@@ -80,6 +80,7 @@ function formatElapsed(ms) {
|
|
|
80
80
|
}
|
|
81
81
|
export async function withSpinner(options) {
|
|
82
82
|
const { message, fn, stopMessage, subtext } = options;
|
|
83
|
+
const readMessage = () => (typeof message === "function" ? message() : message);
|
|
83
84
|
if (resolveOutputFormat() === "json") {
|
|
84
85
|
const result = await fn();
|
|
85
86
|
const sub = subtext ? subtext(result) : undefined;
|
|
@@ -106,9 +107,9 @@ export async function withSpinner(options) {
|
|
|
106
107
|
}
|
|
107
108
|
const s = spinner();
|
|
108
109
|
const start = Date.now();
|
|
109
|
-
s.start(
|
|
110
|
+
s.start(readMessage());
|
|
110
111
|
const timer = setInterval(() => {
|
|
111
|
-
s.message(`${
|
|
112
|
+
s.message(`${readMessage()} [${formatElapsed(Date.now() - start)}]`);
|
|
112
113
|
}, 1000);
|
|
113
114
|
try {
|
|
114
115
|
const result = await fn();
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { color } from "../../components/color.js";
|
|
2
2
|
export { isCancel } from "@clack/prompts";
|
|
3
3
|
import { resolveOutputFormat } from "../../internal/output-format.js";
|
|
4
4
|
export function cancel(msg = "") {
|
|
5
5
|
if (resolveOutputFormat() !== "terminal") {
|
|
6
6
|
return;
|
|
7
7
|
}
|
|
8
|
-
process.stdout.write(`${
|
|
8
|
+
process.stdout.write(`${color.gray("└")} ${color.red(msg)}\n\n`);
|
|
9
9
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { color } from "../../components/color.js";
|
|
2
2
|
import { text } from "../../components/text.js";
|
|
3
3
|
import { resolveOutputFormat } from "../../internal/output-format.js";
|
|
4
4
|
import { stripAnsi } from "../../internal/strip-ansi.js";
|
|
@@ -11,5 +11,5 @@ export function intro(title) {
|
|
|
11
11
|
process.stdout.write(`# ${stripAnsi(title)}\n\n`);
|
|
12
12
|
return;
|
|
13
13
|
}
|
|
14
|
-
process.stdout.write(`${
|
|
14
|
+
process.stdout.write(`${color.gray("┌")} ${text.intro(title)}\n`);
|
|
15
15
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { color } from "../../components/color.js";
|
|
2
2
|
import { symbols } from "../../components/symbols.js";
|
|
3
3
|
import { resolveOutputFormat } from "../../internal/output-format.js";
|
|
4
4
|
import { stripAnsi } from "../../internal/strip-ansi.js";
|
|
5
|
-
function writeTerminalMessage(msg, { symbol =
|
|
5
|
+
function writeTerminalMessage(msg, { symbol = color.gray("│"), secondarySymbol = color.gray("│"), spacing = 1, withGuide = true } = {}) {
|
|
6
6
|
const lines = [];
|
|
7
7
|
const showGuide = withGuide !== false;
|
|
8
8
|
const contentLines = msg.split("\n");
|
|
@@ -78,7 +78,7 @@ export function warn(msg) {
|
|
|
78
78
|
process.stdout.write(`${JSON.stringify({ level: "warn", message: stripAnsi(msg) })}\n`);
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
|
-
message(msg, { symbol:
|
|
81
|
+
message(msg, { symbol: color.yellow("▲") });
|
|
82
82
|
}
|
|
83
83
|
export function error(msg) {
|
|
84
84
|
const format = resolveOutputFormat();
|
|
@@ -90,7 +90,7 @@ export function error(msg) {
|
|
|
90
90
|
process.stdout.write(`${JSON.stringify({ level: "error", message: stripAnsi(msg) })}\n`);
|
|
91
91
|
return;
|
|
92
92
|
}
|
|
93
|
-
message(msg, { symbol:
|
|
93
|
+
message(msg, { symbol: color.red("■") });
|
|
94
94
|
}
|
|
95
95
|
export const log = {
|
|
96
96
|
info,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { color } from "../../components/color.js";
|
|
2
2
|
import { resolveOutputFormat } from "../../internal/output-format.js";
|
|
3
3
|
import { stripAnsi } from "../../internal/strip-ansi.js";
|
|
4
4
|
function getVisibleWidth(value) {
|
|
@@ -8,13 +8,13 @@ function renderTerminalNote(message, title) {
|
|
|
8
8
|
const contentLines = ["", ...message.split("\n"), ""];
|
|
9
9
|
const visibleTitle = stripAnsi(title ?? "");
|
|
10
10
|
const contentWidth = Math.max(visibleTitle.length, ...contentLines.map((line) => getVisibleWidth(line))) + 2;
|
|
11
|
-
const titleLine = `${
|
|
11
|
+
const titleLine = `${color.green("◇")} ${color.reset(title ?? "")} ${color.gray(`${"─".repeat(Math.max(contentWidth - visibleTitle.length - 1, 1))}╮`)}`;
|
|
12
12
|
const content = contentLines.map((line) => {
|
|
13
13
|
const padding = " ".repeat(contentWidth - getVisibleWidth(line));
|
|
14
|
-
return `${
|
|
14
|
+
return `${color.gray("│")} ${line}${padding}${color.gray("│")}`;
|
|
15
15
|
});
|
|
16
|
-
const bottom =
|
|
17
|
-
return [
|
|
16
|
+
const bottom = color.gray(`├${"─".repeat(contentWidth + 2)}╯`);
|
|
17
|
+
return [color.gray("│"), titleLine, ...content, bottom].join("\n");
|
|
18
18
|
}
|
|
19
19
|
export function note(message, title) {
|
|
20
20
|
const format = resolveOutputFormat();
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { color } from "../../components/color.js";
|
|
2
2
|
import { resolveOutputFormat } from "../../internal/output-format.js";
|
|
3
3
|
import { stripAnsi } from "../../internal/strip-ansi.js";
|
|
4
4
|
export function outro(message) {
|
|
@@ -12,5 +12,5 @@ export function outro(message) {
|
|
|
12
12
|
process.stdout.write(`${JSON.stringify({ type: "outro", message: stripped })}\n`);
|
|
13
13
|
return;
|
|
14
14
|
}
|
|
15
|
-
process.stdout.write(`${
|
|
15
|
+
process.stdout.write(`${color.gray("│")}\n${color.gray("└")} ${message}\n\n`);
|
|
16
16
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { color } from "../../components/color.js";
|
|
2
2
|
import { resolveOutputFormat } from "../../internal/output-format.js";
|
|
3
3
|
import { stripAnsi } from "../../internal/strip-ansi.js";
|
|
4
4
|
import { SPINNER_FRAMES } from "../../static/spinner.js";
|
|
@@ -62,8 +62,8 @@ export function spinner() {
|
|
|
62
62
|
}
|
|
63
63
|
clearTimer();
|
|
64
64
|
const symbol = code === undefined || code === 0
|
|
65
|
-
?
|
|
66
|
-
:
|
|
65
|
+
? color.green("◆")
|
|
66
|
+
: color.red("■");
|
|
67
67
|
if (fallback) {
|
|
68
68
|
process.stdout.write(`${symbol} ${currentMessage}\n`);
|
|
69
69
|
return;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { color } from "../components/color.js";
|
|
2
2
|
import { symbols } from "../components/symbols.js";
|
|
3
3
|
import { resolveOutputFormat } from "../internal/output-format.js";
|
|
4
4
|
import { getTheme } from "../internal/theme-detect.js";
|
|
@@ -20,15 +20,15 @@ export function renderMenu(opts) {
|
|
|
20
20
|
});
|
|
21
21
|
}
|
|
22
22
|
const theme = getTheme();
|
|
23
|
-
const bar =
|
|
23
|
+
const bar = color.gray(symbols.bar);
|
|
24
24
|
const lines = [];
|
|
25
|
-
lines.push(`${
|
|
25
|
+
lines.push(`${color.cyan(symbols.active)} ${opts.message}`);
|
|
26
26
|
lines.push(bar);
|
|
27
27
|
opts.options.forEach((option, index) => {
|
|
28
28
|
const isSelected = index === selectedIndex;
|
|
29
|
-
const prefix = isSelected ?
|
|
29
|
+
const prefix = isSelected ? color.cyan(symbols.active) : color.gray(symbols.inactive);
|
|
30
30
|
const label = isSelected ? theme.accent(option.label) : option.label;
|
|
31
|
-
const hint = option.hint ?
|
|
31
|
+
const hint = option.hint ? color.dim(` (${option.hint})`) : "";
|
|
32
32
|
lines.push(`${bar} ${prefix} ${label}${hint}`);
|
|
33
33
|
});
|
|
34
34
|
lines.push(`${bar}`);
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { color } from "../components/color.js";
|
|
2
2
|
import { symbols } from "../components/symbols.js";
|
|
3
3
|
import { resolveOutputFormat } from "../internal/output-format.js";
|
|
4
4
|
export const SPINNER_FRAMES = ["◒", "◐", "◓", "◑"];
|
|
@@ -16,9 +16,9 @@ export function renderSpinnerFrame(options) {
|
|
|
16
16
|
})}\n`;
|
|
17
17
|
}
|
|
18
18
|
const frame = options.frame ?? 0;
|
|
19
|
-
const spinnerChar =
|
|
20
|
-
const timerSuffix = options.timer ?
|
|
21
|
-
const bar =
|
|
19
|
+
const spinnerChar = color.magenta(SPINNER_FRAMES[frame % SPINNER_FRAMES.length]);
|
|
20
|
+
const timerSuffix = options.timer ? color.dim(` [${options.timer}]`) : "";
|
|
21
|
+
const bar = color.gray(symbols.bar);
|
|
22
22
|
return `${spinnerChar} ${options.message}${timerSuffix}\n${bar}`;
|
|
23
23
|
}
|
|
24
24
|
export function renderSpinnerStopped(options) {
|
|
@@ -35,12 +35,12 @@ export function renderSpinnerStopped(options) {
|
|
|
35
35
|
})}\n`;
|
|
36
36
|
}
|
|
37
37
|
const code = options.code ?? 0;
|
|
38
|
-
const symbol = code === 0 ?
|
|
39
|
-
const timerSuffix = options.timer ?
|
|
40
|
-
const bar =
|
|
38
|
+
const symbol = code === 0 ? color.green("◆") : color.red("■");
|
|
39
|
+
const timerSuffix = options.timer ? color.dim(` [${options.timer}]`) : "";
|
|
40
|
+
const bar = color.gray(symbols.bar);
|
|
41
41
|
let output = `${symbol} ${options.message}${timerSuffix}`;
|
|
42
42
|
if (options.subtext) {
|
|
43
|
-
output += `\n${bar} ${
|
|
43
|
+
output += `\n${bar} ${color.dim(options.subtext)}`;
|
|
44
44
|
}
|
|
45
45
|
return output;
|
|
46
46
|
}
|
|
@@ -1,34 +1,34 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { color } from "../components/color.js";
|
|
2
2
|
export const brand = "#a200ff";
|
|
3
3
|
export const dark = {
|
|
4
|
-
header: (text) =>
|
|
5
|
-
divider: (text) =>
|
|
6
|
-
prompt: (text) =>
|
|
7
|
-
number: (text) =>
|
|
8
|
-
intro: (text) =>
|
|
9
|
-
resolvedSymbol:
|
|
10
|
-
errorSymbol:
|
|
11
|
-
accent: (text) =>
|
|
12
|
-
muted: (text) =>
|
|
13
|
-
success: (text) =>
|
|
14
|
-
warning: (text) =>
|
|
15
|
-
error: (text) =>
|
|
16
|
-
info: (text) =>
|
|
17
|
-
badge: (text) =>
|
|
4
|
+
header: (text) => color.magentaBright.bold(text),
|
|
5
|
+
divider: (text) => color.dim(text),
|
|
6
|
+
prompt: (text) => color.cyan(text),
|
|
7
|
+
number: (text) => color.cyanBright(text),
|
|
8
|
+
intro: (text) => color.bgMagenta.white(` Poe - ${text} `),
|
|
9
|
+
resolvedSymbol: color.magenta("◇"),
|
|
10
|
+
errorSymbol: color.red("■"),
|
|
11
|
+
accent: (text) => color.cyan(text),
|
|
12
|
+
muted: (text) => color.dim(text),
|
|
13
|
+
success: (text) => color.green(text),
|
|
14
|
+
warning: (text) => color.yellow(text),
|
|
15
|
+
error: (text) => color.red(text),
|
|
16
|
+
info: (text) => color.magenta(text),
|
|
17
|
+
badge: (text) => color.bgYellow.black(` ${text} `)
|
|
18
18
|
};
|
|
19
19
|
export const light = {
|
|
20
|
-
header: (text) =>
|
|
21
|
-
divider: (text) =>
|
|
22
|
-
prompt: (text) =>
|
|
23
|
-
number: (text) =>
|
|
24
|
-
intro: (text) =>
|
|
25
|
-
resolvedSymbol:
|
|
26
|
-
errorSymbol:
|
|
27
|
-
accent: (text) =>
|
|
28
|
-
muted: (text) =>
|
|
29
|
-
success: (text) =>
|
|
30
|
-
warning: (text) =>
|
|
31
|
-
error: (text) =>
|
|
32
|
-
info: (text) =>
|
|
33
|
-
badge: (text) =>
|
|
20
|
+
header: (text) => color.hex("#a200ff").bold(text),
|
|
21
|
+
divider: (text) => color.hex("#666666")(text),
|
|
22
|
+
prompt: (text) => color.hex("#006699").bold(text),
|
|
23
|
+
number: (text) => color.hex("#0077cc").bold(text),
|
|
24
|
+
intro: (text) => color.bgHex("#a200ff").white(` Poe - ${text} `),
|
|
25
|
+
resolvedSymbol: color.hex("#a200ff")("◇"),
|
|
26
|
+
errorSymbol: color.hex("#cc0000")("■"),
|
|
27
|
+
accent: (text) => color.hex("#006699").bold(text),
|
|
28
|
+
muted: (text) => color.hex("#666666")(text),
|
|
29
|
+
success: (text) => color.hex("#008800")(text),
|
|
30
|
+
warning: (text) => color.hex("#cc6600")(text),
|
|
31
|
+
error: (text) => color.hex("#cc0000")(text),
|
|
32
|
+
info: (text) => color.hex("#a200ff")(text),
|
|
33
|
+
badge: (text) => color.bgHex("#cc6600").white(` ${text} `)
|
|
34
34
|
};
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { color } from "../components/color.js";
|
|
2
2
|
export const typography = {
|
|
3
|
-
bold: (text) =>
|
|
4
|
-
dim: (text) =>
|
|
5
|
-
italic: (text) =>
|
|
6
|
-
underline: (text) =>
|
|
7
|
-
strikethrough: (text) =>
|
|
3
|
+
bold: (text) => color.bold(text),
|
|
4
|
+
dim: (text) => color.dim(text),
|
|
5
|
+
italic: (text) => color.italic(text),
|
|
6
|
+
underline: (text) => color.underline(text),
|
|
7
|
+
strikethrough: (text) => color.strikethrough(text)
|
|
8
8
|
};
|
|
@@ -19,8 +19,10 @@
|
|
|
19
19
|
"dist"
|
|
20
20
|
],
|
|
21
21
|
"peerDependencies": {
|
|
22
|
-
"@clack/prompts": "^1.0.0"
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
"@clack/prompts": "^1.0.0"
|
|
23
|
+
},
|
|
24
|
+
"devDependencies": {
|
|
25
|
+
"@types/mustache": "^4.2.6",
|
|
26
|
+
"mustache": "^4.2.0"
|
|
25
27
|
}
|
|
26
28
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "toolcraft-openapi",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.20",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -32,9 +32,7 @@
|
|
|
32
32
|
"@clack/prompts": "^1.0.0",
|
|
33
33
|
"@poe-code/design-system": "^0.0.2",
|
|
34
34
|
"auth-store": "^0.0.1",
|
|
35
|
-
"
|
|
36
|
-
"console-table-printer": "^2.15.0",
|
|
37
|
-
"toolcraft": "^0.0.18",
|
|
35
|
+
"toolcraft": "^0.0.20",
|
|
38
36
|
"yaml": "^2.8.2"
|
|
39
37
|
},
|
|
40
38
|
"engines": {
|