terminal-pilot 0.0.48 → 0.0.49

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 (80) hide show
  1. package/dist/cli.js +584 -163
  2. package/dist/cli.js.map +4 -4
  3. package/dist/commands/close-session.js.map +1 -1
  4. package/dist/commands/create-session.js.map +1 -1
  5. package/dist/commands/daemon-runtime.js.map +1 -1
  6. package/dist/commands/fill.js.map +1 -1
  7. package/dist/commands/get-session.js.map +1 -1
  8. package/dist/commands/index.js +61 -11
  9. package/dist/commands/index.js.map +4 -4
  10. package/dist/commands/install.js +65 -15
  11. package/dist/commands/install.js.map +4 -4
  12. package/dist/commands/installer.js +21 -7
  13. package/dist/commands/installer.js.map +2 -2
  14. package/dist/commands/list-sessions.js.map +1 -1
  15. package/dist/commands/press-key.js.map +1 -1
  16. package/dist/commands/read-history.js.map +1 -1
  17. package/dist/commands/read-screen.js.map +1 -1
  18. package/dist/commands/resize.js.map +1 -1
  19. package/dist/commands/runtime.js.map +1 -1
  20. package/dist/commands/screenshot.js.map +1 -1
  21. package/dist/commands/send-signal.js.map +1 -1
  22. package/dist/commands/type.js.map +1 -1
  23. package/dist/commands/uninstall.js +21 -7
  24. package/dist/commands/uninstall.js.map +2 -2
  25. package/dist/commands/wait-for-exit.js.map +1 -1
  26. package/dist/commands/wait-for.js.map +1 -1
  27. package/dist/composition.json +6 -1
  28. package/dist/testing/cli-repl.js +584 -163
  29. package/dist/testing/cli-repl.js.map +4 -4
  30. package/dist/testing/qa-cli.js +584 -163
  31. package/dist/testing/qa-cli.js.map +4 -4
  32. package/node_modules/@poe-code/agent-defs/README.md +14 -1
  33. package/node_modules/@poe-code/agent-defs/dist/agents/claude-code.js +2 -1
  34. package/node_modules/@poe-code/agent-defs/dist/agents/claude-desktop.js +1 -0
  35. package/node_modules/@poe-code/agent-defs/dist/agents/codex.js +2 -1
  36. package/node_modules/@poe-code/agent-defs/dist/agents/cursor.js +1 -0
  37. package/node_modules/@poe-code/agent-defs/dist/agents/gemini-cli.js +2 -1
  38. package/node_modules/@poe-code/agent-defs/dist/agents/goose.js +1 -0
  39. package/node_modules/@poe-code/agent-defs/dist/agents/kimi.js +2 -1
  40. package/node_modules/@poe-code/agent-defs/dist/agents/opencode.js +2 -1
  41. package/node_modules/@poe-code/agent-defs/dist/agents/pi.js +2 -1
  42. package/node_modules/@poe-code/agent-defs/dist/agents/poe-agent.js +1 -0
  43. package/node_modules/@poe-code/agent-defs/dist/capabilities.d.ts +14 -0
  44. package/node_modules/@poe-code/agent-defs/dist/capabilities.js +63 -0
  45. package/node_modules/@poe-code/agent-defs/dist/index.d.ts +2 -1
  46. package/node_modules/@poe-code/agent-defs/dist/index.js +1 -0
  47. package/node_modules/@poe-code/agent-defs/dist/registry.js +3 -0
  48. package/node_modules/@poe-code/agent-defs/dist/types.d.ts +7 -0
  49. package/node_modules/@poe-code/agent-skill-config/dist/apply.d.ts +2 -0
  50. package/node_modules/@poe-code/agent-skill-config/dist/apply.js +18 -14
  51. package/node_modules/@poe-code/agent-skill-config/dist/bridge-active-skills.js +3 -1
  52. package/node_modules/@poe-code/agent-skill-config/dist/configs.d.ts +2 -0
  53. package/node_modules/@poe-code/agent-skill-config/dist/configs.js +2 -1
  54. package/node_modules/@poe-code/frontmatter/README.md +3 -0
  55. package/node_modules/@poe-code/frontmatter/dist/index.d.ts +1 -1
  56. package/node_modules/@poe-code/frontmatter/dist/index.js +1 -1
  57. package/node_modules/@poe-code/frontmatter/dist/parse.d.ts +9 -0
  58. package/node_modules/@poe-code/frontmatter/dist/parse.js +16 -0
  59. package/node_modules/toolcraft-design/dist/acp/components.d.ts +8 -1
  60. package/node_modules/toolcraft-design/dist/acp/components.js +11 -6
  61. package/node_modules/toolcraft-design/dist/acp/index.d.ts +1 -0
  62. package/node_modules/toolcraft-design/dist/components/command-errors.d.ts +2 -0
  63. package/node_modules/toolcraft-design/dist/components/command-errors.js +8 -2
  64. package/node_modules/toolcraft-design/dist/components/help-formatter-plain.js +61 -25
  65. package/node_modules/toolcraft-design/dist/components/help-formatter.d.ts +15 -0
  66. package/node_modules/toolcraft-design/dist/components/help-formatter.js +128 -29
  67. package/node_modules/toolcraft-design/dist/components/index.d.ts +3 -3
  68. package/node_modules/toolcraft-design/dist/components/index.js +2 -2
  69. package/node_modules/toolcraft-design/dist/components/table.d.ts +1 -0
  70. package/node_modules/toolcraft-design/dist/components/table.js +52 -5
  71. package/node_modules/toolcraft-design/dist/components/template.d.ts +9 -0
  72. package/node_modules/toolcraft-design/dist/components/template.js +27 -2
  73. package/node_modules/toolcraft-design/dist/help-formatter.d.ts +2 -2
  74. package/node_modules/toolcraft-design/dist/help-formatter.js +1 -1
  75. package/node_modules/toolcraft-design/dist/index.d.ts +4 -4
  76. package/node_modules/toolcraft-design/dist/index.js +3 -3
  77. package/node_modules/toolcraft-design/dist/prompts/interactive/core.d.ts +5 -0
  78. package/node_modules/toolcraft-design/dist/prompts/interactive/core.js +15 -1
  79. package/node_modules/toolcraft-design/dist/prompts/primitives/spinner.js +1 -1
  80. package/package.json +4 -3
@@ -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 wrapWords(value, width) {
58
- const words = splitWords(value);
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
- if (!line) {
66
- line = word;
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
- lines.push(line);
74
- line = word;
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(20, totalWidth - leftWidth - indent);
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 [`${firstIndent}${row.left}`];
132
+ return leftLines.map((line, index) => index === 0 ? `${firstIndent}${line}` : `${leftHangIndent}${line}`);
101
133
  }
102
134
  const rightLines = wrapWords(row.right, rightWidth);
103
- if (row.left.length >= leftWidth) {
104
- return [
105
- `${firstIndent}${row.left}`,
106
- ...rightLines.map((line) => `${continuationIndent}${line}`)
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 firstLine = `${firstIndent}${padEndVisible(row.left, leftWidth)}${rightLines[0]}`;
110
- const continuationLines = rightLines
111
- .slice(1)
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.name}`,
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.flags,
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 wrapWords(value, width) {
107
- const words = splitWords(value);
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
- if (!line) {
115
- line = word;
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
- lines.push(line);
123
- line = word;
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(20, totalWidth - leftWidth - indent);
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 [`${firstIndent}${row.left}`];
207
+ return leftLines.map((line, index) => index === 0 ? `${firstIndent}${line}` : `${leftHangIndent}${line}`);
160
208
  }
161
209
  const rightLines = wrapWords(row.right, rightWidth);
162
- if (visibleWidth(row.left) >= leftWidth) {
163
- return [
164
- `${firstIndent}${row.left}`,
165
- ...rightLines.map((line) => `${continuationIndent}${line}`)
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 firstLine = `${firstIndent}${padEndVisible(row.left, leftWidth)}${rightLines[0]}`;
169
- const continuationLines = rightLines
170
- .slice(1)
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
- left: `${" ".repeat((cmd.depth ?? 0) * 2)}${text.command(cmd.name)}`,
194
- right: cmd.description
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: text.option(opt.flags),
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";
@@ -12,4 +12,5 @@ export interface RenderTableOptions {
12
12
  variant?: "table" | "detail";
13
13
  maxWidth?: number;
14
14
  }
15
+ export declare function loggerTableWidth(): number | undefined;
15
16
  export declare function renderTable(options: RenderTableOptions): string;
@@ -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 top = renderBorder(computedColumns, theme, { left: "┌", mid: "┬", right: "┐" });
251
- const header = renderTerminalRow(computedColumns.map((column) => theme.header(column.title)), computedColumns, theme);
252
- const headerBottom = renderBorder(computedColumns, theme, { left: "├", mid: "┼", right: "┤" });
253
- const bottom = renderBorder(computedColumns, theme, { left: "", mid: "", right: "" });
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(computedColumns.map((column) => getCell(row, column.name)), computedColumns, theme));
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;
@@ -1,4 +1,17 @@
1
1
  const MAX_PARTIAL_DEPTH = 100;
2
+ const MAX_TAG_EXCERPT_LENGTH = 40;
3
+ export class TemplateParseError extends Error {
4
+ description;
5
+ line;
6
+ column;
7
+ constructor(description, position) {
8
+ super(`${description} at line ${position.line}, column ${position.column}`);
9
+ this.name = "TemplateParseError";
10
+ this.description = description;
11
+ this.line = position.line;
12
+ this.column = position.column;
13
+ }
14
+ }
2
15
  const HTML_ESCAPE = {
3
16
  "&": "&amp;",
4
17
  "<": "&lt;",
@@ -114,13 +127,13 @@ function parseTag(template, open) {
114
127
  if (template.startsWith("{{{", open)) {
115
128
  const close = template.indexOf("}}}", open + 3);
116
129
  if (close === -1) {
117
- throw new Error("Unclosed unescaped tag");
130
+ throw unclosedTagError(template, open, "}}}");
118
131
  }
119
132
  return { kind: "unescaped", name: template.slice(open + 3, close).trim(), end: close + 3 };
120
133
  }
121
134
  const close = template.indexOf("}}", open + 2);
122
135
  if (close === -1) {
123
- throw new Error("Unclosed tag");
136
+ throw unclosedTagError(template, open, "}}");
124
137
  }
125
138
  const raw = template.slice(open + 2, close).trim();
126
139
  const sigil = raw[0];
@@ -142,6 +155,18 @@ function parseTag(template, open) {
142
155
  return { kind: "delimiter", name, end };
143
156
  return { kind: "name", name: raw, end };
144
157
  }
158
+ function unclosedTagError(template, open, expected) {
159
+ const before = template.slice(0, open);
160
+ const lineEnd = template.indexOf("\n", open);
161
+ const opened = template.slice(open, lineEnd === -1 ? template.length : lineEnd).trimEnd();
162
+ const tag = opened.length > MAX_TAG_EXCERPT_LENGTH
163
+ ? `${opened.slice(0, MAX_TAG_EXCERPT_LENGTH)}...`
164
+ : opened;
165
+ return new TemplateParseError(`Unclosed tag "${tag}": expected "${expected}"`, {
166
+ line: before.split("\n").length,
167
+ column: open - (before.lastIndexOf("\n") + 1) + 1
168
+ });
169
+ }
145
170
  function getStandalone(template, tagStart, tagEnd, kind) {
146
171
  if (!["section", "inverted", "close", "comment", "partial", "delimiter"].includes(kind)) {
147
172
  return undefined;
@@ -1,2 +1,2 @@
1
- export { helpFormatter } from "./components/help-formatter.js";
2
- export type { CommandInfo, FormatColumnsOptions, OptionInfo } from "./components/help-formatter.js";
1
+ export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList, styleHelpToken, joinHelpTokens, renderHelpTokens } from "./components/help-formatter.js";
2
+ export type { CommandInfo, FormatColumnsOptions, OptionInfo, HelpToken, HelpTokenRole } from "./components/help-formatter.js";
@@ -1 +1 @@
1
- export { helpFormatter } from "./components/help-formatter.js";
1
+ export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList, styleHelpToken, joinHelpTokens, renderHelpTokens } from "./components/help-formatter.js";
@@ -12,12 +12,12 @@ export type { Color } from "./components/color.js";
12
12
  export { symbols } from "./components/symbols.js";
13
13
  export { createLogger, logger } from "./components/logger.js";
14
14
  export type { LoggerOutput } from "./components/logger.js";
15
- export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./components/help-formatter.js";
15
+ export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList, styleHelpToken, joinHelpTokens, renderHelpTokens } from "./components/help-formatter.js";
16
16
  export * as helpFormatterPlain from "./components/help-formatter-plain.js";
17
- export type { CommandInfo, OptionInfo, FormatColumnsOptions } from "./components/help-formatter.js";
17
+ export type { CommandInfo, OptionInfo, FormatColumnsOptions, HelpToken, HelpTokenRole } from "./components/help-formatter.js";
18
18
  export { formatCommandNotFound } from "./components/command-errors.js";
19
19
  export { formatCommandNotFoundPanel } from "./components/command-errors.js";
20
- export { renderTable } from "./components/table.js";
20
+ export { loggerTableWidth, renderTable } from "./components/table.js";
21
21
  export type { TableColumn, RenderTableOptions } from "./components/table.js";
22
22
  export { renderFileChanges } from "./components/file-changes.js";
23
23
  export type { FileChange, FileChangeDisplayMode, FileChangeKind, FileChangeOutputFormat, RenderFileChangesOptions } from "./components/file-changes.js";
@@ -29,7 +29,7 @@ export { renderInspectorCard } from "./components/inspector-card.js";
29
29
  export type { InspectorField, InspectorSection, RenderInspectorCardOptions } from "./components/inspector-card.js";
30
30
  export { renderResourceBrowser } from "./components/resource-browser.js";
31
31
  export type { RenderResourceBrowserOptions, ResourceBrowserGroup, ResourceBrowserItem } from "./components/resource-browser.js";
32
- export { getTemplatePartialNames, renderTemplate, resolveTemplatePartials } from "./components/template.js";
32
+ export { TemplateParseError, getTemplatePartialNames, renderTemplate, resolveTemplatePartials } from "./components/template.js";
33
33
  export type { RenderTemplateOptions, TemplateEscape } from "./components/template.js";
34
34
  export { openExternal } from "./components/browser.js";
35
35
  export * as acp from "./acp/index.js";
@@ -10,17 +10,17 @@ export { text } from "./components/text.js";
10
10
  export { color } from "./components/color.js";
11
11
  export { symbols } from "./components/symbols.js";
12
12
  export { createLogger, logger } from "./components/logger.js";
13
- export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./components/help-formatter.js";
13
+ export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList, styleHelpToken, joinHelpTokens, renderHelpTokens } from "./components/help-formatter.js";
14
14
  export * as helpFormatterPlain from "./components/help-formatter-plain.js";
15
15
  export { formatCommandNotFound } from "./components/command-errors.js";
16
16
  export { formatCommandNotFoundPanel } from "./components/command-errors.js";
17
- export { renderTable } from "./components/table.js";
17
+ export { loggerTableWidth, renderTable } from "./components/table.js";
18
18
  export { renderFileChanges } from "./components/file-changes.js";
19
19
  export { renderCatalog } from "./components/catalog.js";
20
20
  export { renderDetailCard } from "./components/detail-card.js";
21
21
  export { renderInspectorCard } from "./components/inspector-card.js";
22
22
  export { renderResourceBrowser } from "./components/resource-browser.js";
23
- export { getTemplatePartialNames, renderTemplate, resolveTemplatePartials } from "./components/template.js";
23
+ export { TemplateParseError, getTemplatePartialNames, renderTemplate, resolveTemplatePartials } from "./components/template.js";
24
24
  export { openExternal } from "./components/browser.js";
25
25
  // ACP rendering
26
26
  export * as acp from "./acp/index.js";
@@ -1,6 +1,11 @@
1
1
  import { EventEmitter } from "node:events";
2
2
  import { CANCEL } from "./cancel-symbol.js";
3
3
  export type PromptStateName = "initial" | "active" | "submit" | "cancel" | "error";
4
+ /**
5
+ * Builds the non-TTY rejection message, naming the documented `--yes` flag for the
6
+ * command being run and keeping `POE_NO_PROMPT=1` as the secondary CI alternative.
7
+ */
8
+ export declare function nonTtyPromptMessage(argv?: string[]): string;
4
9
  export interface PromptState<Value> {
5
10
  state: PromptStateName;
6
11
  value: Value | undefined;