terminal-pilot 0.0.48 → 0.0.50
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/dist/cli.js +584 -163
- package/dist/cli.js.map +4 -4
- package/dist/commands/close-session.js.map +1 -1
- package/dist/commands/create-session.js.map +1 -1
- package/dist/commands/daemon-runtime.js.map +1 -1
- package/dist/commands/fill.js.map +1 -1
- package/dist/commands/get-session.js.map +1 -1
- package/dist/commands/index.js +61 -11
- package/dist/commands/index.js.map +4 -4
- package/dist/commands/install.js +65 -15
- package/dist/commands/install.js.map +4 -4
- package/dist/commands/installer.js +21 -7
- package/dist/commands/installer.js.map +2 -2
- package/dist/commands/list-sessions.js.map +1 -1
- package/dist/commands/press-key.js.map +1 -1
- package/dist/commands/read-history.js.map +1 -1
- package/dist/commands/read-screen.js.map +1 -1
- package/dist/commands/resize.js.map +1 -1
- package/dist/commands/runtime.js.map +1 -1
- package/dist/commands/screenshot.js.map +1 -1
- package/dist/commands/send-signal.js.map +1 -1
- package/dist/commands/type.js.map +1 -1
- package/dist/commands/uninstall.js +21 -7
- package/dist/commands/uninstall.js.map +2 -2
- package/dist/commands/wait-for-exit.js.map +1 -1
- package/dist/commands/wait-for.js.map +1 -1
- package/dist/composition.json +6 -1
- package/dist/testing/cli-repl.js +584 -163
- package/dist/testing/cli-repl.js.map +4 -4
- package/dist/testing/qa-cli.js +584 -163
- package/dist/testing/qa-cli.js.map +4 -4
- package/node_modules/@poe-code/agent-defs/README.md +14 -1
- package/node_modules/@poe-code/agent-defs/dist/agents/claude-code.js +2 -1
- package/node_modules/@poe-code/agent-defs/dist/agents/claude-desktop.js +1 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/codex.js +2 -1
- package/node_modules/@poe-code/agent-defs/dist/agents/cursor.js +1 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/gemini-cli.js +2 -1
- package/node_modules/@poe-code/agent-defs/dist/agents/goose.js +1 -0
- package/node_modules/@poe-code/agent-defs/dist/agents/kimi.js +2 -1
- package/node_modules/@poe-code/agent-defs/dist/agents/opencode.js +2 -1
- package/node_modules/@poe-code/agent-defs/dist/agents/pi.js +2 -1
- package/node_modules/@poe-code/agent-defs/dist/agents/poe-agent.js +1 -0
- package/node_modules/@poe-code/agent-defs/dist/capabilities.d.ts +14 -0
- package/node_modules/@poe-code/agent-defs/dist/capabilities.js +63 -0
- package/node_modules/@poe-code/agent-defs/dist/index.d.ts +2 -1
- package/node_modules/@poe-code/agent-defs/dist/index.js +1 -0
- package/node_modules/@poe-code/agent-defs/dist/registry.js +3 -0
- package/node_modules/@poe-code/agent-defs/dist/types.d.ts +7 -0
- package/node_modules/@poe-code/agent-skill-config/dist/apply.d.ts +2 -0
- package/node_modules/@poe-code/agent-skill-config/dist/apply.js +18 -14
- package/node_modules/@poe-code/agent-skill-config/dist/bridge-active-skills.js +3 -1
- package/node_modules/@poe-code/agent-skill-config/dist/configs.d.ts +2 -0
- package/node_modules/@poe-code/agent-skill-config/dist/configs.js +2 -1
- package/node_modules/@poe-code/frontmatter/README.md +3 -0
- package/node_modules/@poe-code/frontmatter/dist/index.d.ts +1 -1
- package/node_modules/@poe-code/frontmatter/dist/index.js +1 -1
- package/node_modules/@poe-code/frontmatter/dist/parse.d.ts +9 -0
- package/node_modules/@poe-code/frontmatter/dist/parse.js +16 -0
- package/node_modules/@poe-code/user-error/README.md +56 -0
- package/node_modules/@poe-code/user-error/dist/index.d.ts +17 -0
- package/node_modules/@poe-code/user-error/dist/index.js +21 -0
- package/node_modules/@poe-code/user-error/package.json +24 -0
- package/node_modules/toolcraft-design/dist/acp/components.d.ts +8 -1
- package/node_modules/toolcraft-design/dist/acp/components.js +11 -6
- package/node_modules/toolcraft-design/dist/acp/index.d.ts +1 -0
- package/node_modules/toolcraft-design/dist/components/command-errors.d.ts +2 -0
- package/node_modules/toolcraft-design/dist/components/command-errors.js +8 -2
- package/node_modules/toolcraft-design/dist/components/help-formatter-plain.js +61 -25
- package/node_modules/toolcraft-design/dist/components/help-formatter.d.ts +15 -0
- package/node_modules/toolcraft-design/dist/components/help-formatter.js +128 -29
- package/node_modules/toolcraft-design/dist/components/index.d.ts +3 -3
- package/node_modules/toolcraft-design/dist/components/index.js +2 -2
- package/node_modules/toolcraft-design/dist/components/table.d.ts +1 -0
- package/node_modules/toolcraft-design/dist/components/table.js +52 -5
- package/node_modules/toolcraft-design/dist/components/template.d.ts +9 -0
- package/node_modules/toolcraft-design/dist/components/template.js +27 -2
- package/node_modules/toolcraft-design/dist/help-formatter.d.ts +2 -2
- package/node_modules/toolcraft-design/dist/help-formatter.js +1 -1
- package/node_modules/toolcraft-design/dist/index.d.ts +4 -4
- package/node_modules/toolcraft-design/dist/index.js +3 -3
- package/node_modules/toolcraft-design/dist/prompts/interactive/core.d.ts +5 -0
- package/node_modules/toolcraft-design/dist/prompts/interactive/core.js +15 -1
- package/node_modules/toolcraft-design/dist/prompts/primitives/spinner.js +1 -1
- package/package.json +5 -3
|
@@ -25,8 +25,13 @@ function colorForKind(kind) {
|
|
|
25
25
|
function writeLine(line) {
|
|
26
26
|
getAcpWriter()(line);
|
|
27
27
|
}
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
const STATE_GLYPHS = {
|
|
29
|
+
streaming: () => color.dim("·"),
|
|
30
|
+
success: () => color.green.bold("✓"),
|
|
31
|
+
error: () => color.red.bold("✗")
|
|
32
|
+
};
|
|
33
|
+
function agentPrefix(state) {
|
|
34
|
+
return `${STATE_GLYPHS[state]()} agent: `;
|
|
30
35
|
}
|
|
31
36
|
function formatCost(costUsd) {
|
|
32
37
|
return new Intl.NumberFormat("en-US", {
|
|
@@ -36,7 +41,7 @@ function formatCost(costUsd) {
|
|
|
36
41
|
maximumFractionDigits: 6
|
|
37
42
|
}).format(costUsd);
|
|
38
43
|
}
|
|
39
|
-
export function renderAgentMessage(text) {
|
|
44
|
+
export function renderAgentMessage(text, state = "streaming") {
|
|
40
45
|
const format = resolveOutputFormat();
|
|
41
46
|
if (format === "markdown") {
|
|
42
47
|
writeLine(`- **agent:** ${text}`);
|
|
@@ -47,7 +52,7 @@ export function renderAgentMessage(text) {
|
|
|
47
52
|
return;
|
|
48
53
|
}
|
|
49
54
|
const rendered = renderMarkdown(text).trimEnd();
|
|
50
|
-
writeLine(`${agentPrefix()}${rendered}`);
|
|
55
|
+
writeLine(`${agentPrefix(state)}${rendered}`);
|
|
51
56
|
}
|
|
52
57
|
export function renderToolStart(kind, title) {
|
|
53
58
|
const format = resolveOutputFormat();
|
|
@@ -85,7 +90,7 @@ export function renderReasoning(text) {
|
|
|
85
90
|
writeLine(JSON.stringify({ event: "reasoning", text }));
|
|
86
91
|
return;
|
|
87
92
|
}
|
|
88
|
-
writeLine(color.dim(`
|
|
93
|
+
writeLine(color.dim(` · ${truncate(text, 80)}`));
|
|
89
94
|
}
|
|
90
95
|
export function renderUsage(tokens) {
|
|
91
96
|
const format = resolveOutputFormat();
|
|
@@ -109,7 +114,7 @@ export function renderUsage(tokens) {
|
|
|
109
114
|
return;
|
|
110
115
|
}
|
|
111
116
|
writeLine("");
|
|
112
|
-
writeLine(color.
|
|
117
|
+
writeLine(color.dim(`· tokens: ${tokens.input} in${cached} → ${tokens.output} out${cost}`));
|
|
113
118
|
}
|
|
114
119
|
export function renderPermissionRejected(title) {
|
|
115
120
|
const format = resolveOutputFormat();
|
|
@@ -1,3 +1,4 @@
|
|
|
1
1
|
export { renderAgentMessage, renderToolStart, renderToolComplete, renderReasoning, renderUsage, renderError, renderPermissionRejected } from "./components.js";
|
|
2
|
+
export type { AcpOutputState } from "./components.js";
|
|
2
3
|
export { getAcpWriter, withAcpWriter } from "./writer.js";
|
|
3
4
|
export type { AcpLineWriter } from "./writer.js";
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export declare function formatCommandNotFound(input: {
|
|
2
2
|
unknownCommand: string;
|
|
3
3
|
helpCommand: string;
|
|
4
|
+
suggestions?: readonly string[];
|
|
4
5
|
}): {
|
|
5
6
|
label: string;
|
|
6
7
|
hint: string;
|
|
@@ -8,6 +9,7 @@ export declare function formatCommandNotFound(input: {
|
|
|
8
9
|
export declare function formatCommandNotFoundPanel(input: {
|
|
9
10
|
unknownCommand: string;
|
|
10
11
|
helpCommand: string;
|
|
12
|
+
suggestions?: readonly string[];
|
|
11
13
|
title?: string;
|
|
12
14
|
}): {
|
|
13
15
|
title: string;
|
|
@@ -5,15 +5,21 @@ export function formatCommandNotFound(input) {
|
|
|
5
5
|
const unknown = unknownInput.length > 0
|
|
6
6
|
? unknownInput
|
|
7
7
|
: "<command>";
|
|
8
|
+
const suggestions = input.suggestions ?? [];
|
|
9
|
+
const didYouMean = suggestions.length > 0
|
|
10
|
+
? `
|
|
11
|
+
${text.muted("Did you mean:")} ${suggestions.map((suggestion) => text.command(suggestion)).join(text.muted(", "))}${text.muted("?")}`
|
|
12
|
+
: "";
|
|
8
13
|
return {
|
|
9
|
-
label: `${typography.bold("Unknown command:")} ${text.command(unknown)}`,
|
|
14
|
+
label: `${typography.bold("Unknown command:")} ${text.command(unknown)}${didYouMean}`,
|
|
10
15
|
hint: `${text.muted("Run")} ${text.usageCommand(input.helpCommand)} ${text.muted("for available commands.")}`
|
|
11
16
|
};
|
|
12
17
|
}
|
|
13
18
|
export function formatCommandNotFoundPanel(input) {
|
|
14
19
|
const message = formatCommandNotFound({
|
|
15
20
|
unknownCommand: input.unknownCommand,
|
|
16
|
-
helpCommand: input.helpCommand
|
|
21
|
+
helpCommand: input.helpCommand,
|
|
22
|
+
suggestions: input.suggestions
|
|
17
23
|
});
|
|
18
24
|
return {
|
|
19
25
|
title: input.title ?? "command not found",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { joinHelpTokens } from "./help-formatter.js";
|
|
1
2
|
export function stripAnsi(value) {
|
|
2
3
|
let output = "";
|
|
3
4
|
for (let index = 0; index < value.length; index += 1) {
|
|
@@ -54,26 +55,49 @@ function splitWords(value) {
|
|
|
54
55
|
}
|
|
55
56
|
return words;
|
|
56
57
|
}
|
|
57
|
-
function
|
|
58
|
-
|
|
58
|
+
function leadingWhitespace(value) {
|
|
59
|
+
let index = 0;
|
|
60
|
+
while (index < value.length && isWhitespace(value[index])) {
|
|
61
|
+
index += 1;
|
|
62
|
+
}
|
|
63
|
+
return { prefix: value.slice(0, index), rest: value.slice(index) };
|
|
64
|
+
}
|
|
65
|
+
function takePrefix(value, width) {
|
|
66
|
+
return { prefix: value.slice(0, width), rest: value.slice(width) };
|
|
67
|
+
}
|
|
68
|
+
function wrapWords(value, width, continuationWidth = width) {
|
|
69
|
+
// Preserve leading whitespace only on the first wrapped line so hang-indented
|
|
70
|
+
// left cells (command depth prefixes) do not re-indent every continuation.
|
|
71
|
+
const { prefix, rest } = leadingWhitespace(value);
|
|
72
|
+
const firstContentWidth = Math.max(1, width - prefix.length);
|
|
73
|
+
const words = splitWords(rest);
|
|
59
74
|
if (words.length === 0) {
|
|
60
|
-
return [
|
|
75
|
+
return [prefix];
|
|
61
76
|
}
|
|
62
77
|
const lines = [];
|
|
63
78
|
let line = "";
|
|
79
|
+
let isFirstLine = true;
|
|
64
80
|
for (const word of words) {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
continue;
|
|
68
|
-
}
|
|
69
|
-
if (line.length + 1 + word.length <= width) {
|
|
81
|
+
const limit = isFirstLine ? firstContentWidth : continuationWidth;
|
|
82
|
+
if (line && line.length + 1 + word.length <= limit) {
|
|
70
83
|
line += ` ${word}`;
|
|
71
84
|
continue;
|
|
72
85
|
}
|
|
73
|
-
|
|
74
|
-
|
|
86
|
+
if (line) {
|
|
87
|
+
lines.push(isFirstLine ? `${prefix}${line}` : line);
|
|
88
|
+
isFirstLine = false;
|
|
89
|
+
line = "";
|
|
90
|
+
}
|
|
91
|
+
let remaining = word;
|
|
92
|
+
while (remaining.length > (isFirstLine ? firstContentWidth : continuationWidth)) {
|
|
93
|
+
const chunk = takePrefix(remaining, isFirstLine ? firstContentWidth : continuationWidth);
|
|
94
|
+
lines.push(isFirstLine ? `${prefix}${chunk.prefix}` : chunk.prefix);
|
|
95
|
+
isFirstLine = false;
|
|
96
|
+
remaining = chunk.rest;
|
|
97
|
+
}
|
|
98
|
+
line = remaining;
|
|
75
99
|
}
|
|
76
|
-
lines.push(line);
|
|
100
|
+
lines.push(isFirstLine ? `${prefix}${line}` : line);
|
|
77
101
|
return lines;
|
|
78
102
|
}
|
|
79
103
|
export function formatColumns(opts) {
|
|
@@ -91,33 +115,43 @@ export function formatColumns(opts) {
|
|
|
91
115
|
const indent = opts.indent ?? 2;
|
|
92
116
|
const maxLeftContentWidth = Math.max(...rows.map((row) => row.left.length));
|
|
93
117
|
const leftWidth = clamp(maxLeftContentWidth + gap, minLeftWidth, maxLeftWidth);
|
|
94
|
-
const rightWidth = Math.max(
|
|
118
|
+
const rightWidth = Math.max(1, totalWidth - leftWidth - indent);
|
|
119
|
+
const leftWrapWidth = Math.max(1, totalWidth - indent);
|
|
95
120
|
const firstIndent = " ".repeat(indent);
|
|
96
121
|
const continuationIndent = " ".repeat(indent + leftWidth);
|
|
97
122
|
return rows
|
|
98
123
|
.flatMap((row) => {
|
|
124
|
+
let leftLeadingWidth = 0;
|
|
125
|
+
while (leftLeadingWidth < row.left.length && isWhitespace(row.left[leftLeadingWidth])) {
|
|
126
|
+
leftLeadingWidth += 1;
|
|
127
|
+
}
|
|
128
|
+
// Continuations hang under the left cell start (including depth prefix) by +2.
|
|
129
|
+
const leftHangIndent = " ".repeat(indent + leftLeadingWidth + 2);
|
|
130
|
+
const leftLines = wrapWords(row.left, leftWrapWidth, Math.max(1, totalWidth - leftHangIndent.length));
|
|
99
131
|
if (row.right.length === 0) {
|
|
100
|
-
return
|
|
132
|
+
return leftLines.map((line, index) => index === 0 ? `${firstIndent}${line}` : `${leftHangIndent}${line}`);
|
|
101
133
|
}
|
|
102
134
|
const rightLines = wrapWords(row.right, rightWidth);
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
135
|
+
const leftFitsInColumn = row.left.length < leftWidth;
|
|
136
|
+
if (leftFitsInColumn && leftLines.length === 1) {
|
|
137
|
+
const firstLine = `${firstIndent}${padEndVisible(leftLines[0] ?? "", leftWidth)}${rightLines[0]}`;
|
|
138
|
+
const continuationLines = rightLines
|
|
139
|
+
.slice(1)
|
|
140
|
+
.map((line) => `${continuationIndent}${line}`);
|
|
141
|
+
return [firstLine, ...continuationLines];
|
|
108
142
|
}
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
|
|
112
|
-
.map((line) => `${continuationIndent}${line}`);
|
|
113
|
-
return [firstLine, ...continuationLines];
|
|
143
|
+
const renderedLeft = leftLines.map((line, index) => index === 0 ? `${firstIndent}${line}` : `${leftHangIndent}${line}`);
|
|
144
|
+
const renderedRight = rightLines.map((line) => `${continuationIndent}${line}`);
|
|
145
|
+
return [...renderedLeft, ...renderedRight];
|
|
114
146
|
})
|
|
115
147
|
.join("\n");
|
|
116
148
|
}
|
|
117
149
|
export function formatCommandList(commands) {
|
|
118
150
|
return formatColumns({
|
|
119
151
|
rows: commands.map((cmd) => ({
|
|
120
|
-
left: `${" ".repeat((cmd.depth ?? 0) * 2)}${cmd.
|
|
152
|
+
left: `${" ".repeat((cmd.depth ?? 0) * 2)}${cmd.nameTokens !== undefined && cmd.nameTokens.length > 0
|
|
153
|
+
? joinHelpTokens(cmd.nameTokens)
|
|
154
|
+
: cmd.name}`,
|
|
121
155
|
right: cmd.description
|
|
122
156
|
}))
|
|
123
157
|
});
|
|
@@ -125,7 +159,9 @@ export function formatCommandList(commands) {
|
|
|
125
159
|
export function formatOptionList(options) {
|
|
126
160
|
return formatColumns({
|
|
127
161
|
rows: options.map((opt) => ({
|
|
128
|
-
left: opt.
|
|
162
|
+
left: opt.flagTokens !== undefined && opt.flagTokens.length > 0
|
|
163
|
+
? joinHelpTokens(opt.flagTokens)
|
|
164
|
+
: opt.flags,
|
|
129
165
|
right: opt.description
|
|
130
166
|
}))
|
|
131
167
|
});
|
|
@@ -1,11 +1,20 @@
|
|
|
1
|
+
export type HelpTokenRole = "command" | "argument" | "option" | "literal" | "dim";
|
|
2
|
+
export interface HelpToken {
|
|
3
|
+
text: string;
|
|
4
|
+
role: HelpTokenRole;
|
|
5
|
+
}
|
|
1
6
|
export interface CommandInfo {
|
|
2
7
|
name: string;
|
|
8
|
+
/** Structured tokens for TTY/markdown styling. Plain `name` is used when absent. */
|
|
9
|
+
nameTokens?: HelpToken[];
|
|
3
10
|
description: string;
|
|
4
11
|
/** Nesting depth relative to the help target. Depth 0 is a direct child. */
|
|
5
12
|
depth?: number;
|
|
6
13
|
}
|
|
7
14
|
export interface OptionInfo {
|
|
8
15
|
flags: string;
|
|
16
|
+
/** Structured tokens for TTY/markdown styling. Plain `flags` is used when absent. */
|
|
17
|
+
flagTokens?: HelpToken[];
|
|
9
18
|
description: string;
|
|
10
19
|
}
|
|
11
20
|
export interface FormatColumnsOptions {
|
|
@@ -20,6 +29,9 @@ export interface FormatColumnsOptions {
|
|
|
20
29
|
indent?: number;
|
|
21
30
|
}
|
|
22
31
|
export declare function formatColumns(opts: FormatColumnsOptions): string;
|
|
32
|
+
export declare function styleHelpToken(token: HelpToken): string;
|
|
33
|
+
export declare function joinHelpTokens(tokens: HelpToken[]): string;
|
|
34
|
+
export declare function renderHelpTokens(tokens: HelpToken[]): string;
|
|
23
35
|
export declare function formatCommand(name: string, description: string): string;
|
|
24
36
|
export declare function formatUsage(command: string, args?: string): string;
|
|
25
37
|
export declare function formatOption(flags: string, description: string): string;
|
|
@@ -32,4 +44,7 @@ export declare const helpFormatter: {
|
|
|
32
44
|
readonly formatOption: typeof formatOption;
|
|
33
45
|
readonly formatCommandList: typeof formatCommandList;
|
|
34
46
|
readonly formatOptionList: typeof formatOptionList;
|
|
47
|
+
readonly styleHelpToken: typeof styleHelpToken;
|
|
48
|
+
readonly joinHelpTokens: typeof joinHelpTokens;
|
|
49
|
+
readonly renderHelpTokens: typeof renderHelpTokens;
|
|
35
50
|
};
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { resolveOutputFormat } from "../internal/output-format.js";
|
|
2
|
+
import { typography } from "../tokens/typography.js";
|
|
1
3
|
import { text } from "./text.js";
|
|
2
4
|
const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
3
5
|
function normalizeInline(value) {
|
|
@@ -103,26 +105,67 @@ function splitWords(value) {
|
|
|
103
105
|
}
|
|
104
106
|
return words;
|
|
105
107
|
}
|
|
106
|
-
function
|
|
107
|
-
|
|
108
|
+
function leadingWhitespaceWidth(value) {
|
|
109
|
+
let index = 0;
|
|
110
|
+
while (index < value.length && isWhitespace(value[index])) {
|
|
111
|
+
index += 1;
|
|
112
|
+
}
|
|
113
|
+
return { prefix: value.slice(0, index), rest: value.slice(index) };
|
|
114
|
+
}
|
|
115
|
+
function takeVisiblePrefix(value, width) {
|
|
116
|
+
let visible = 0;
|
|
117
|
+
let index = 0;
|
|
118
|
+
while (index < value.length) {
|
|
119
|
+
const controlEnd = readControlSequence(value, index);
|
|
120
|
+
if (controlEnd !== undefined) {
|
|
121
|
+
index = controlEnd;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const segment = graphemeSegmenter.segment(value.slice(index))[Symbol.iterator]().next().value;
|
|
125
|
+
const cluster = segment?.segment ?? value[index] ?? "";
|
|
126
|
+
const nextWidth = clusterWidth(cluster);
|
|
127
|
+
if (visible > 0 && visible + nextWidth > width) {
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
visible += nextWidth;
|
|
131
|
+
index += cluster.length || 1;
|
|
132
|
+
}
|
|
133
|
+
return { prefix: value.slice(0, index), rest: value.slice(index) };
|
|
134
|
+
}
|
|
135
|
+
function wrapWords(value, width, continuationWidth = width) {
|
|
136
|
+
// Preserve leading whitespace only on the first wrapped line so hang-indented
|
|
137
|
+
// left cells (command depth prefixes) do not re-indent every continuation.
|
|
138
|
+
const { prefix, rest } = leadingWhitespaceWidth(value);
|
|
139
|
+
const prefixWidth = visibleWidth(prefix);
|
|
140
|
+
const firstContentWidth = Math.max(1, width - prefixWidth);
|
|
141
|
+
const words = splitWords(rest);
|
|
108
142
|
if (words.length === 0) {
|
|
109
|
-
return [
|
|
143
|
+
return [prefix];
|
|
110
144
|
}
|
|
111
145
|
const lines = [];
|
|
112
146
|
let line = "";
|
|
147
|
+
let isFirstLine = true;
|
|
113
148
|
for (const word of words) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
if (visibleWidth(line) + 1 + visibleWidth(word) <= width) {
|
|
149
|
+
const limit = isFirstLine ? firstContentWidth : continuationWidth;
|
|
150
|
+
if (line && visibleWidth(line) + 1 + visibleWidth(word) <= limit) {
|
|
119
151
|
line += ` ${word}`;
|
|
120
152
|
continue;
|
|
121
153
|
}
|
|
122
|
-
|
|
123
|
-
|
|
154
|
+
if (line) {
|
|
155
|
+
lines.push(isFirstLine ? `${prefix}${line}` : line);
|
|
156
|
+
isFirstLine = false;
|
|
157
|
+
line = "";
|
|
158
|
+
}
|
|
159
|
+
let remaining = word;
|
|
160
|
+
while (visibleWidth(remaining) > (isFirstLine ? firstContentWidth : continuationWidth)) {
|
|
161
|
+
const chunk = takeVisiblePrefix(remaining, isFirstLine ? firstContentWidth : continuationWidth);
|
|
162
|
+
lines.push(isFirstLine ? `${prefix}${chunk.prefix}` : chunk.prefix);
|
|
163
|
+
isFirstLine = false;
|
|
164
|
+
remaining = chunk.rest;
|
|
165
|
+
}
|
|
166
|
+
line = remaining;
|
|
124
167
|
}
|
|
125
|
-
lines.push(line);
|
|
168
|
+
lines.push(isFirstLine ? `${prefix}${line}` : line);
|
|
126
169
|
return lines;
|
|
127
170
|
}
|
|
128
171
|
function validateLayoutValue(value, name) {
|
|
@@ -150,29 +193,74 @@ export function formatColumns(opts) {
|
|
|
150
193
|
validateLayoutValue(indent, "indent");
|
|
151
194
|
const maxLeftContentWidth = Math.max(...rows.map((row) => visibleWidth(row.left)));
|
|
152
195
|
const leftWidth = clamp(maxLeftContentWidth + gap, minLeftWidth, maxLeftWidth);
|
|
153
|
-
const rightWidth = Math.max(
|
|
196
|
+
const rightWidth = Math.max(1, totalWidth - leftWidth - indent);
|
|
197
|
+
const leftWrapWidth = Math.max(1, totalWidth - indent);
|
|
154
198
|
const firstIndent = " ".repeat(indent);
|
|
155
199
|
const continuationIndent = " ".repeat(indent + leftWidth);
|
|
156
200
|
return rows
|
|
157
201
|
.flatMap((row) => {
|
|
202
|
+
const leftLeading = leadingWhitespaceWidth(row.left).prefix;
|
|
203
|
+
// Continuations hang under the left cell start (including depth prefix) by +2.
|
|
204
|
+
const leftHangIndent = " ".repeat(indent + visibleWidth(leftLeading) + 2);
|
|
205
|
+
const leftLines = wrapWords(row.left, leftWrapWidth, Math.max(1, totalWidth - visibleWidth(leftHangIndent)));
|
|
158
206
|
if (row.right.length === 0) {
|
|
159
|
-
return
|
|
207
|
+
return leftLines.map((line, index) => index === 0 ? `${firstIndent}${line}` : `${leftHangIndent}${line}`);
|
|
160
208
|
}
|
|
161
209
|
const rightLines = wrapWords(row.right, rightWidth);
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
210
|
+
const leftFitsInColumn = visibleWidth(row.left) < leftWidth;
|
|
211
|
+
if (leftFitsInColumn && leftLines.length === 1) {
|
|
212
|
+
const firstLine = `${firstIndent}${padEndVisible(leftLines[0] ?? "", leftWidth)}${rightLines[0]}`;
|
|
213
|
+
const continuationLines = rightLines
|
|
214
|
+
.slice(1)
|
|
215
|
+
.map((line) => `${continuationIndent}${line}`);
|
|
216
|
+
return [firstLine, ...continuationLines];
|
|
167
217
|
}
|
|
168
|
-
const
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
.map((line) => `${continuationIndent}${line}`);
|
|
172
|
-
return [firstLine, ...continuationLines];
|
|
218
|
+
const renderedLeft = leftLines.map((line, index) => index === 0 ? `${firstIndent}${line}` : `${leftHangIndent}${line}`);
|
|
219
|
+
const renderedRight = rightLines.map((line) => `${continuationIndent}${line}`);
|
|
220
|
+
return [...renderedLeft, ...renderedRight];
|
|
173
221
|
})
|
|
174
222
|
.join("\n");
|
|
175
223
|
}
|
|
224
|
+
export function styleHelpToken(token) {
|
|
225
|
+
switch (token.role) {
|
|
226
|
+
case "command":
|
|
227
|
+
return text.command(token.text);
|
|
228
|
+
case "argument":
|
|
229
|
+
return styleArgumentToken(token.text);
|
|
230
|
+
case "option":
|
|
231
|
+
return text.option(token.text);
|
|
232
|
+
case "dim":
|
|
233
|
+
return styleDim(token.text);
|
|
234
|
+
case "literal":
|
|
235
|
+
return token.text;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function styleArgumentToken(content) {
|
|
239
|
+
// Token text already includes angle brackets. text.argument re-wraps in markdown,
|
|
240
|
+
// so strip first there; terminal/json keep the full `<value>` form.
|
|
241
|
+
const format = resolveOutputFormat();
|
|
242
|
+
if (format === "markdown" && content.startsWith("<") && content.endsWith(">")) {
|
|
243
|
+
return text.argument(content.slice(1, -1));
|
|
244
|
+
}
|
|
245
|
+
if (format === "json") {
|
|
246
|
+
return content;
|
|
247
|
+
}
|
|
248
|
+
return text.argument(content);
|
|
249
|
+
}
|
|
250
|
+
function styleDim(content) {
|
|
251
|
+
// Structural brackets stay unstyled in markdown/json; italicizing them as muted is wrong.
|
|
252
|
+
const format = resolveOutputFormat();
|
|
253
|
+
if (format === "json" || format === "markdown") {
|
|
254
|
+
return content;
|
|
255
|
+
}
|
|
256
|
+
return typography.dim(content);
|
|
257
|
+
}
|
|
258
|
+
export function joinHelpTokens(tokens) {
|
|
259
|
+
return tokens.map((token) => token.text).join("");
|
|
260
|
+
}
|
|
261
|
+
export function renderHelpTokens(tokens) {
|
|
262
|
+
return tokens.map((token) => styleHelpToken(token)).join("");
|
|
263
|
+
}
|
|
176
264
|
export function formatCommand(name, description) {
|
|
177
265
|
return formatColumns({
|
|
178
266
|
rows: [{ left: text.command(name), right: description }]
|
|
@@ -189,16 +277,24 @@ export function formatOption(flags, description) {
|
|
|
189
277
|
}
|
|
190
278
|
export function formatCommandList(commands) {
|
|
191
279
|
return formatColumns({
|
|
192
|
-
rows: commands.map((cmd) =>
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
280
|
+
rows: commands.map((cmd) => {
|
|
281
|
+
const depthPrefix = " ".repeat((cmd.depth ?? 0) * 2);
|
|
282
|
+
const styledName = cmd.nameTokens !== undefined && cmd.nameTokens.length > 0
|
|
283
|
+
? renderHelpTokens(cmd.nameTokens)
|
|
284
|
+
: text.command(cmd.name);
|
|
285
|
+
return {
|
|
286
|
+
left: `${depthPrefix}${styledName}`,
|
|
287
|
+
right: cmd.description
|
|
288
|
+
};
|
|
289
|
+
})
|
|
196
290
|
});
|
|
197
291
|
}
|
|
198
292
|
export function formatOptionList(options) {
|
|
199
293
|
return formatColumns({
|
|
200
294
|
rows: options.map((opt) => ({
|
|
201
|
-
left:
|
|
295
|
+
left: opt.flagTokens !== undefined && opt.flagTokens.length > 0
|
|
296
|
+
? renderHelpTokens(opt.flagTokens)
|
|
297
|
+
: text.option(opt.flags),
|
|
202
298
|
right: opt.description
|
|
203
299
|
}))
|
|
204
300
|
});
|
|
@@ -209,5 +305,8 @@ export const helpFormatter = {
|
|
|
209
305
|
formatUsage,
|
|
210
306
|
formatOption,
|
|
211
307
|
formatCommandList,
|
|
212
|
-
formatOptionList
|
|
308
|
+
formatOptionList,
|
|
309
|
+
styleHelpToken,
|
|
310
|
+
joinHelpTokens,
|
|
311
|
+
renderHelpTokens
|
|
213
312
|
};
|
|
@@ -4,11 +4,11 @@ export type { Color } from "./color.js";
|
|
|
4
4
|
export { symbols } from "./symbols.js";
|
|
5
5
|
export { createLogger, logger } from "./logger.js";
|
|
6
6
|
export type { LoggerOutput } from "./logger.js";
|
|
7
|
-
export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./help-formatter.js";
|
|
8
|
-
export type { CommandInfo, OptionInfo, FormatColumnsOptions } from "./help-formatter.js";
|
|
7
|
+
export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList, styleHelpToken, joinHelpTokens, renderHelpTokens } from "./help-formatter.js";
|
|
8
|
+
export type { CommandInfo, OptionInfo, FormatColumnsOptions, HelpToken, HelpTokenRole } from "./help-formatter.js";
|
|
9
9
|
export { formatCommandNotFound } from "./command-errors.js";
|
|
10
10
|
export { formatCommandNotFoundPanel } from "./command-errors.js";
|
|
11
|
-
export { renderTable } from "./table.js";
|
|
11
|
+
export { loggerTableWidth, renderTable } from "./table.js";
|
|
12
12
|
export type { TableColumn, RenderTableOptions } from "./table.js";
|
|
13
13
|
export { renderFileChanges } from "./file-changes.js";
|
|
14
14
|
export type { FileChange, FileChangeDisplayMode, FileChangeKind, FileChangeOutputFormat, RenderFileChangesOptions } from "./file-changes.js";
|
|
@@ -2,10 +2,10 @@ export { text } from "./text.js";
|
|
|
2
2
|
export { color } from "./color.js";
|
|
3
3
|
export { symbols } from "./symbols.js";
|
|
4
4
|
export { createLogger, logger } from "./logger.js";
|
|
5
|
-
export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./help-formatter.js";
|
|
5
|
+
export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList, styleHelpToken, joinHelpTokens, renderHelpTokens } from "./help-formatter.js";
|
|
6
6
|
export { formatCommandNotFound } from "./command-errors.js";
|
|
7
7
|
export { formatCommandNotFoundPanel } from "./command-errors.js";
|
|
8
|
-
export { renderTable } from "./table.js";
|
|
8
|
+
export { loggerTableWidth, renderTable } from "./table.js";
|
|
9
9
|
export { renderFileChanges } from "./file-changes.js";
|
|
10
10
|
export { renderCatalog } from "./catalog.js";
|
|
11
11
|
export { getTemplatePartialNames, renderTemplate, resolveTemplatePartials } from "./template.js";
|
|
@@ -3,6 +3,7 @@ import { resolveOutputFormat } from "../internal/output-format.js";
|
|
|
3
3
|
import { stripAnsi } from "../internal/strip-ansi.js";
|
|
4
4
|
const reset = "\x1b[0m";
|
|
5
5
|
const ellipsis = "…";
|
|
6
|
+
const minCellWidth = 4;
|
|
6
7
|
const graphemeSegmenter = new Intl.Segmenter(undefined, { granularity: "grapheme" });
|
|
7
8
|
function getCell(row, name) {
|
|
8
9
|
return Object.prototype.hasOwnProperty.call(row, name) ? row[name] ?? "" : "";
|
|
@@ -161,6 +162,51 @@ function computeColumns(columns) {
|
|
|
161
162
|
width: getColumnWidth(column)
|
|
162
163
|
}));
|
|
163
164
|
}
|
|
165
|
+
// Each column is framed as "│ cell ", plus the closing "│".
|
|
166
|
+
function frameWidth(columnCount) {
|
|
167
|
+
return columnCount * 3 + 1;
|
|
168
|
+
}
|
|
169
|
+
// Log output indents every line with a "│ " guide, so a table emitted through the
|
|
170
|
+
// logger has that much less room than the terminal. Undefined without a terminal:
|
|
171
|
+
// piped output has no width to fit.
|
|
172
|
+
export function loggerTableWidth() {
|
|
173
|
+
const columns = process.stdout.columns;
|
|
174
|
+
return columns === undefined ? undefined : columns - 3;
|
|
175
|
+
}
|
|
176
|
+
// Without an explicit budget or a TTY there is no width to fit: the consumer of the
|
|
177
|
+
// piped output decides, so columns keep their declared widths.
|
|
178
|
+
function budgetColumns(columns, maxWidth) {
|
|
179
|
+
if (maxWidth === undefined) {
|
|
180
|
+
return columns;
|
|
181
|
+
}
|
|
182
|
+
const available = maxWidth - frameWidth(columns.length);
|
|
183
|
+
const contentWidth = (cap) => columns.reduce((total, column) => total + Math.min(column.width, cap), 0);
|
|
184
|
+
if (columns.reduce((total, column) => total + column.width, 0) <= available) {
|
|
185
|
+
return columns;
|
|
186
|
+
}
|
|
187
|
+
// Widest-first: raise a shared cap as far as the budget allows, so narrow columns
|
|
188
|
+
// keep their declared width and only the columns above the cap lose room.
|
|
189
|
+
let cap = minCellWidth;
|
|
190
|
+
while (contentWidth(cap + 1) <= available) {
|
|
191
|
+
cap += 1;
|
|
192
|
+
}
|
|
193
|
+
const budgeted = columns.map((column) => ({ ...column, width: Math.min(column.width, cap) }));
|
|
194
|
+
let slack = available - contentWidth(cap);
|
|
195
|
+
while (slack > 0) {
|
|
196
|
+
const growable = budgeted.filter((column, index) => column.width < columns[index].width);
|
|
197
|
+
if (growable.length === 0) {
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
for (const column of growable) {
|
|
201
|
+
if (slack === 0) {
|
|
202
|
+
break;
|
|
203
|
+
}
|
|
204
|
+
column.width += 1;
|
|
205
|
+
slack -= 1;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return budgeted;
|
|
209
|
+
}
|
|
164
210
|
function renderBorder(columns, theme, parts) {
|
|
165
211
|
const horizontal = theme.muted("─");
|
|
166
212
|
const segments = columns.map((column) => horizontal.repeat(column.width + 2));
|
|
@@ -247,16 +293,17 @@ function renderTableTerminal(options) {
|
|
|
247
293
|
}
|
|
248
294
|
const separatorOptions = options;
|
|
249
295
|
const includeRowSeparators = separatorOptions.rowSeparator === true || separatorOptions.rowSeparators === true;
|
|
250
|
-
const
|
|
251
|
-
const
|
|
252
|
-
const
|
|
253
|
-
const
|
|
296
|
+
const budgetedColumns = budgetColumns(computedColumns, options.maxWidth ?? process.stdout.columns);
|
|
297
|
+
const top = renderBorder(budgetedColumns, theme, { left: "┌", mid: "┬", right: "┐" });
|
|
298
|
+
const header = renderTerminalRow(budgetedColumns.map((column) => theme.header(column.title)), budgetedColumns, theme);
|
|
299
|
+
const headerBottom = renderBorder(budgetedColumns, theme, { left: "├", mid: "┼", right: "┤" });
|
|
300
|
+
const bottom = renderBorder(budgetedColumns, theme, { left: "└", mid: "┴", right: "┘" });
|
|
254
301
|
const renderedRows = [];
|
|
255
302
|
for (const [index, row] of rows.entries()) {
|
|
256
303
|
if (includeRowSeparators && index > 0) {
|
|
257
304
|
renderedRows.push(headerBottom);
|
|
258
305
|
}
|
|
259
|
-
renderedRows.push(renderTerminalRow(
|
|
306
|
+
renderedRows.push(renderTerminalRow(budgetedColumns.map((column) => getCell(row, column.name)), budgetedColumns, theme));
|
|
260
307
|
}
|
|
261
308
|
return [top, header, headerBottom, ...renderedRows, bottom].join("\n");
|
|
262
309
|
}
|
|
@@ -5,6 +5,15 @@ export interface RenderTemplateOptions {
|
|
|
5
5
|
validate?: boolean;
|
|
6
6
|
yield?: string;
|
|
7
7
|
}
|
|
8
|
+
export declare class TemplateParseError extends Error {
|
|
9
|
+
readonly description: string;
|
|
10
|
+
readonly line: number;
|
|
11
|
+
readonly column: number;
|
|
12
|
+
constructor(description: string, position: {
|
|
13
|
+
line: number;
|
|
14
|
+
column: number;
|
|
15
|
+
});
|
|
16
|
+
}
|
|
8
17
|
export declare function renderTemplate(template: string, view: Record<string, unknown>, options?: RenderTemplateOptions): string;
|
|
9
18
|
export declare function getTemplatePartialNames(template: string): string[];
|
|
10
19
|
export declare function resolveTemplatePartials(template: string, partials: Record<string, string>): string;
|