toolcraft 0.0.11 → 0.0.13

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 (56) hide show
  1. package/dist/cli.js +274 -160
  2. package/dist/renderer.d.ts +8 -2
  3. package/dist/renderer.js +71 -12
  4. package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.d.ts +4 -0
  5. package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.js +132 -0
  6. package/node_modules/@poe-code/design-system/dist/components/help-formatter.d.ts +13 -0
  7. package/node_modules/@poe-code/design-system/dist/components/help-formatter.js +116 -7
  8. package/node_modules/@poe-code/design-system/dist/components/index.d.ts +2 -2
  9. package/node_modules/@poe-code/design-system/dist/components/index.js +1 -1
  10. package/node_modules/@poe-code/design-system/dist/components/text.d.ts +1 -0
  11. package/node_modules/@poe-code/design-system/dist/components/text.js +8 -0
  12. package/node_modules/@poe-code/design-system/dist/index.d.ts +3 -2
  13. package/node_modules/@poe-code/design-system/dist/index.js +2 -1
  14. package/node_modules/@poe-code/process-runner/README.md +41 -0
  15. package/node_modules/@poe-code/process-runner/dist/docker/args.d.ts +2 -0
  16. package/node_modules/@poe-code/process-runner/dist/docker/args.js +40 -0
  17. package/node_modules/@poe-code/process-runner/dist/docker/context.d.ts +3 -0
  18. package/node_modules/@poe-code/process-runner/dist/docker/context.js +30 -0
  19. package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.d.ts +28 -0
  20. package/node_modules/@poe-code/process-runner/dist/docker/docker-execution-env.js +428 -0
  21. package/node_modules/@poe-code/process-runner/dist/docker/docker-runner.d.ts +2 -0
  22. package/node_modules/@poe-code/process-runner/dist/docker/docker-runner.js +131 -0
  23. package/node_modules/@poe-code/process-runner/dist/docker/engine.d.ts +3 -0
  24. package/node_modules/@poe-code/process-runner/dist/docker/engine.js +24 -0
  25. package/node_modules/@poe-code/process-runner/dist/host/host-execution-env.d.ts +2 -0
  26. package/node_modules/@poe-code/process-runner/dist/host/host-execution-env.js +48 -0
  27. package/node_modules/@poe-code/process-runner/dist/host/host-runner.d.ts +3 -0
  28. package/node_modules/@poe-code/process-runner/dist/host/host-runner.js +74 -0
  29. package/node_modules/@poe-code/process-runner/dist/index.d.ts +8 -0
  30. package/node_modules/@poe-code/process-runner/dist/index.js +7 -0
  31. package/node_modules/@poe-code/process-runner/dist/testing/index.d.ts +2 -0
  32. package/node_modules/@poe-code/process-runner/dist/testing/index.js +1 -0
  33. package/node_modules/@poe-code/process-runner/dist/testing/mock-runner.d.ts +3 -0
  34. package/node_modules/@poe-code/process-runner/dist/testing/mock-runner.js +115 -0
  35. package/node_modules/@poe-code/process-runner/dist/testing/verify.d.ts +1 -0
  36. package/node_modules/@poe-code/process-runner/dist/testing/verify.js +359 -0
  37. package/node_modules/@poe-code/process-runner/dist/types.d.ts +180 -0
  38. package/node_modules/@poe-code/process-runner/dist/types.js +1 -0
  39. package/node_modules/@poe-code/process-runner/package.json +27 -0
  40. package/node_modules/@poe-code/task-list/README.md +49 -5
  41. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.d.ts +19 -0
  42. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.js +62 -0
  43. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.d.ts +13 -0
  44. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +627 -0
  45. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +253 -41
  46. package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +7 -1
  47. package/node_modules/@poe-code/task-list/dist/backends/utils.js +21 -0
  48. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +171 -16
  49. package/node_modules/@poe-code/task-list/dist/index.d.ts +3 -1
  50. package/node_modules/@poe-code/task-list/dist/index.js +1 -1
  51. package/node_modules/@poe-code/task-list/dist/open.d.ts +4 -2
  52. package/node_modules/@poe-code/task-list/dist/open.js +27 -3
  53. package/node_modules/@poe-code/task-list/dist/types.d.ts +51 -3
  54. package/node_modules/@poe-code/task-list/dist/types.js +25 -0
  55. package/node_modules/@poe-code/task-list/package.json +1 -0
  56. package/package.json +11 -4
@@ -1,5 +1,11 @@
1
1
  import type { Command, RenderPrimitives } from "./index.js";
2
2
  export type OutputMode = "rich" | "md" | "json";
3
- type WriteFn = (chunk: string) => void;
4
- export declare function renderResult(command: Command<any, any, any, any>, result: unknown, output: OutputMode, primitives: RenderPrimitives, write?: WriteFn): void;
3
+ type WriteStream = "stdout" | "stderr";
4
+ type WriteFn = (chunk: string, stream?: WriteStream) => void;
5
+ export interface RenderResultStatus {
6
+ mcpError: boolean;
7
+ }
8
+ export declare function renderObjectTable(result: Record<string, unknown>, primitives: RenderPrimitives): string;
9
+ export declare function renderArrayTable(result: Array<Record<string, unknown>>, primitives: RenderPrimitives): string;
10
+ export declare function renderResult(command: Command<any, any, any, any>, result: unknown, output: OutputMode, primitives: RenderPrimitives, write?: WriteFn): RenderResultStatus;
5
11
  export {};
package/dist/renderer.js CHANGED
@@ -1,6 +1,47 @@
1
+ import YAML from "yaml";
1
2
  function isObject(value) {
2
3
  return value !== null && typeof value === "object" && !Array.isArray(value);
3
4
  }
5
+ function isMcpCallToolResult(value) {
6
+ if (!isObject(value)) {
7
+ return false;
8
+ }
9
+ const hasContent = Array.isArray(value.content);
10
+ const hasStructured = value.structuredContent !== undefined;
11
+ if (!hasContent && !hasStructured) {
12
+ return false;
13
+ }
14
+ return Object.keys(value).every((key) => key === "content" || key === "structuredContent" || key === "isError" || key === "_meta");
15
+ }
16
+ function isMcpTextContent(value) {
17
+ return isObject(value) && value.type === "text" && typeof value.text === "string";
18
+ }
19
+ function extractMcpPayload(envelope) {
20
+ const structuredContent = envelope.structuredContent;
21
+ if (isObject(structuredContent) && "result" in structuredContent) {
22
+ return structuredContent.result;
23
+ }
24
+ if (structuredContent !== undefined) {
25
+ return structuredContent;
26
+ }
27
+ if (Array.isArray(envelope.content)) {
28
+ const text = envelope.content
29
+ .filter(isMcpTextContent)
30
+ .map((block) => block.text)
31
+ .join("\n");
32
+ return text.length > 0 ? text : undefined;
33
+ }
34
+ return undefined;
35
+ }
36
+ function unwrapMcpEnvelope(result) {
37
+ if (!isMcpCallToolResult(result)) {
38
+ return { result, mcpError: false };
39
+ }
40
+ return {
41
+ result: extractMcpPayload(result),
42
+ mcpError: result.isError === true,
43
+ };
44
+ }
4
45
  function isArrayOfObjects(value) {
5
46
  return Array.isArray(value) && value.every((entry) => isObject(entry));
6
47
  }
@@ -21,7 +62,7 @@ function stringifyJson(value, spaces) {
21
62
  return String(value);
22
63
  }
23
64
  }
24
- function renderObjectTable(result, primitives) {
65
+ export function renderObjectTable(result, primitives) {
25
66
  const rows = Object.entries(result).map(([key, value]) => ({
26
67
  key,
27
68
  value: stringifyValue(value),
@@ -59,7 +100,7 @@ function getColumnNames(rows) {
59
100
  }
60
101
  return [...names];
61
102
  }
62
- function renderArrayTable(result, primitives) {
103
+ export function renderArrayTable(result, primitives) {
63
104
  if (result.length === 0) {
64
105
  return "[]";
65
106
  }
@@ -87,7 +128,7 @@ function renderArrayMarkdown(result) {
87
128
  .join(" | ")} |`);
88
129
  return [header, separator, ...rows].join("\n");
89
130
  }
90
- function autoRender(result, output, primitives) {
131
+ function autoRender(result, output, _primitives) {
91
132
  if (result === null || result === undefined) {
92
133
  if (output === "json") {
93
134
  return stringifyJson({ ok: true }, 2);
@@ -100,14 +141,16 @@ function autoRender(result, output, primitives) {
100
141
  }
101
142
  return result;
102
143
  }
144
+ if (output === "rich" && Array.isArray(result) && result.every((value) => typeof value === "string")) {
145
+ return result.join("\n");
146
+ }
103
147
  if (isObject(result)) {
104
- if (output === "rich") {
105
- return renderObjectTable(result, primitives);
106
- }
107
148
  if (output === "md") {
108
149
  return renderObjectMarkdown(result);
109
150
  }
110
- return stringifyJson(result, 2);
151
+ if (output === "json") {
152
+ return stringifyJson(result, 2);
153
+ }
111
154
  }
112
155
  if (isArrayOfObjects(result)) {
113
156
  if (output === "md") {
@@ -116,33 +159,49 @@ function autoRender(result, output, primitives) {
116
159
  if (output === "json") {
117
160
  return stringifyJson(result, 2);
118
161
  }
119
- return renderArrayTable(result, primitives);
162
+ }
163
+ if (output === "rich") {
164
+ return YAML.stringify(result);
120
165
  }
121
166
  return stringifyJson(result, 2);
122
167
  }
123
- export function renderResult(command, result, output, primitives, write = (chunk) => {
168
+ export function renderResult(command, result, output, primitives, write = (chunk, stream = "stdout") => {
169
+ if (stream === "stderr") {
170
+ process.stderr.write(chunk);
171
+ return;
172
+ }
124
173
  process.stdout.write(chunk);
125
174
  }) {
175
+ const unwrapped = unwrapMcpEnvelope(result);
176
+ result = unwrapped.result;
177
+ if (unwrapped.mcpError) {
178
+ const payload = autoRender(result, output, primitives);
179
+ if (payload.length > 0) {
180
+ write(`${payload}\n`, "stderr");
181
+ }
182
+ return { mcpError: true };
183
+ }
126
184
  if (output === "json" && command.render?.json) {
127
185
  const payload = command.render.json(result, primitives);
128
186
  if (payload !== undefined) {
129
187
  write(`${stringifyJson(payload, 2)}\n`);
130
188
  }
131
- return;
189
+ return { mcpError: false };
132
190
  }
133
191
  if (output === "md" && command.render?.markdown) {
134
192
  const payload = command.render.markdown(result, primitives);
135
193
  if (typeof payload === "string" && payload.length > 0) {
136
194
  write(`${payload}\n`);
137
195
  }
138
- return;
196
+ return { mcpError: false };
139
197
  }
140
198
  if (output === "rich" && command.render?.rich) {
141
199
  command.render.rich(result, primitives);
142
- return;
200
+ return { mcpError: false };
143
201
  }
144
202
  const payload = autoRender(result, output, primitives);
145
203
  if (payload.length > 0) {
146
204
  write(`${payload}\n`);
147
205
  }
206
+ return { mcpError: false };
148
207
  }
@@ -0,0 +1,4 @@
1
+ import type { CommandInfo, FormatColumnsOptions, OptionInfo } from "./help-formatter.js";
2
+ export declare function formatColumns(opts: FormatColumnsOptions): string;
3
+ export declare function formatCommandList(commands: CommandInfo[]): string;
4
+ export declare function formatOptionList(options: OptionInfo[]): string;
@@ -0,0 +1,132 @@
1
+ function stripAnsi(value) {
2
+ let output = "";
3
+ for (let index = 0; index < value.length; index += 1) {
4
+ if (value[index] === "\u001b") {
5
+ if (value[index + 1] === "[") {
6
+ index += 2;
7
+ while (index < value.length) {
8
+ const code = value.charCodeAt(index);
9
+ if (code >= 0x40 && code <= 0x7e) {
10
+ break;
11
+ }
12
+ index += 1;
13
+ }
14
+ }
15
+ continue;
16
+ }
17
+ output += value[index];
18
+ }
19
+ return output;
20
+ }
21
+ function toAscii(value) {
22
+ let output = "";
23
+ const stripped = stripAnsi(value);
24
+ for (let index = 0; index < stripped.length; index += 1) {
25
+ const code = stripped.charCodeAt(index);
26
+ output += code <= 0x7f ? stripped[index] : "?";
27
+ }
28
+ return output;
29
+ }
30
+ function clamp(value, min, max) {
31
+ return Math.min(Math.max(value, min), max);
32
+ }
33
+ function padEndVisible(value, width) {
34
+ return value + " ".repeat(Math.max(0, width - value.length));
35
+ }
36
+ function isWhitespace(char) {
37
+ return char === " " || char === "\n" || char === "\t" || char === "\r";
38
+ }
39
+ function splitWords(value) {
40
+ const words = [];
41
+ let word = "";
42
+ for (const char of value) {
43
+ if (isWhitespace(char)) {
44
+ if (word) {
45
+ words.push(word);
46
+ word = "";
47
+ }
48
+ continue;
49
+ }
50
+ word += char;
51
+ }
52
+ if (word) {
53
+ words.push(word);
54
+ }
55
+ return words;
56
+ }
57
+ function wrapWords(value, width) {
58
+ const words = splitWords(value);
59
+ if (words.length === 0) {
60
+ return [""];
61
+ }
62
+ const lines = [];
63
+ let line = "";
64
+ for (const word of words) {
65
+ if (!line) {
66
+ line = word;
67
+ continue;
68
+ }
69
+ if (line.length + 1 + word.length <= width) {
70
+ line += ` ${word}`;
71
+ continue;
72
+ }
73
+ lines.push(line);
74
+ line = word;
75
+ }
76
+ lines.push(line);
77
+ return lines;
78
+ }
79
+ export function formatColumns(opts) {
80
+ const rows = opts.rows.map((row) => ({
81
+ left: toAscii(row.left),
82
+ right: toAscii(row.right)
83
+ }));
84
+ if (rows.length === 0) {
85
+ return "";
86
+ }
87
+ const totalWidth = opts.totalWidth ?? process.stdout.columns ?? 100;
88
+ const minLeftWidth = opts.minLeftWidth ?? 12;
89
+ const maxLeftWidth = opts.maxLeftWidth ?? 32;
90
+ const gap = opts.gap ?? 3;
91
+ const indent = opts.indent ?? 2;
92
+ const maxLeftContentWidth = Math.max(...rows.map((row) => row.left.length));
93
+ const leftWidth = clamp(maxLeftContentWidth + gap, minLeftWidth, maxLeftWidth);
94
+ const rightWidth = Math.max(20, totalWidth - leftWidth - indent);
95
+ const firstIndent = " ".repeat(indent);
96
+ const continuationIndent = " ".repeat(indent + leftWidth);
97
+ return rows
98
+ .flatMap((row) => {
99
+ if (row.right.length === 0) {
100
+ return [`${firstIndent}${row.left}`];
101
+ }
102
+ 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
+ ];
108
+ }
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];
114
+ })
115
+ .join("\n");
116
+ }
117
+ export function formatCommandList(commands) {
118
+ return formatColumns({
119
+ rows: commands.map((cmd) => ({
120
+ left: cmd.name,
121
+ right: cmd.description
122
+ }))
123
+ });
124
+ }
125
+ export function formatOptionList(options) {
126
+ return formatColumns({
127
+ rows: options.map((opt) => ({
128
+ left: opt.flags,
129
+ right: opt.description
130
+ }))
131
+ });
132
+ }
@@ -6,12 +6,25 @@ export interface OptionInfo {
6
6
  flags: string;
7
7
  description: string;
8
8
  }
9
+ export interface FormatColumnsOptions {
10
+ rows: Array<{
11
+ left: string;
12
+ right: string;
13
+ }>;
14
+ totalWidth?: number;
15
+ minLeftWidth?: number;
16
+ maxLeftWidth?: number;
17
+ gap?: number;
18
+ indent?: number;
19
+ }
20
+ export declare function formatColumns(opts: FormatColumnsOptions): string;
9
21
  export declare function formatCommand(name: string, description: string): string;
10
22
  export declare function formatUsage(command: string, args?: string): string;
11
23
  export declare function formatOption(flags: string, description: string): string;
12
24
  export declare function formatCommandList(commands: CommandInfo[]): string;
13
25
  export declare function formatOptionList(options: OptionInfo[]): string;
14
26
  export declare const helpFormatter: {
27
+ readonly formatColumns: typeof formatColumns;
15
28
  readonly formatCommand: typeof formatCommand;
16
29
  readonly formatUsage: typeof formatUsage;
17
30
  readonly formatOption: typeof formatOption;
@@ -1,24 +1,133 @@
1
1
  import { text } from "./text.js";
2
- import { widths } from "../tokens/widths.js";
2
+ function stripAnsi(value) {
3
+ let output = "";
4
+ for (let index = 0; index < value.length; index += 1)
5
+ if (value[index] === "\u001b" && value[index + 1] === "[")
6
+ while (index < value.length && value[index] !== "m")
7
+ index += 1;
8
+ else
9
+ output += value[index];
10
+ return output;
11
+ }
12
+ function visibleWidth(value) {
13
+ return stripAnsi(value).length;
14
+ }
15
+ function clamp(value, min, max) {
16
+ return Math.min(Math.max(value, min), max);
17
+ }
18
+ function padEndVisible(value, width) {
19
+ return value + " ".repeat(Math.max(0, width - visibleWidth(value)));
20
+ }
21
+ function isWhitespace(char) {
22
+ return char === " " || char === "\n" || char === "\t" || char === "\r";
23
+ }
24
+ function splitWords(value) {
25
+ const words = [];
26
+ let word = "";
27
+ for (const char of value) {
28
+ if (isWhitespace(char)) {
29
+ if (word) {
30
+ words.push(word);
31
+ word = "";
32
+ }
33
+ continue;
34
+ }
35
+ word += char;
36
+ }
37
+ if (word) {
38
+ words.push(word);
39
+ }
40
+ return words;
41
+ }
42
+ function wrapWords(value, width) {
43
+ const words = splitWords(value);
44
+ if (words.length === 0) {
45
+ return [""];
46
+ }
47
+ const lines = [];
48
+ let line = "";
49
+ for (const word of words) {
50
+ if (!line) {
51
+ line = word;
52
+ continue;
53
+ }
54
+ if (visibleWidth(line) + 1 + visibleWidth(word) <= width) {
55
+ line += ` ${word}`;
56
+ continue;
57
+ }
58
+ lines.push(line);
59
+ line = word;
60
+ }
61
+ lines.push(line);
62
+ return lines;
63
+ }
64
+ export function formatColumns(opts) {
65
+ const { rows } = opts;
66
+ if (rows.length === 0) {
67
+ return "";
68
+ }
69
+ const totalWidth = opts.totalWidth ?? process.stdout.columns ?? 100;
70
+ const minLeftWidth = opts.minLeftWidth ?? 12;
71
+ const maxLeftWidth = opts.maxLeftWidth ?? 32;
72
+ const gap = opts.gap ?? 3;
73
+ const indent = opts.indent ?? 2;
74
+ const maxLeftContentWidth = Math.max(...rows.map((row) => visibleWidth(row.left)));
75
+ const leftWidth = clamp(maxLeftContentWidth + gap, minLeftWidth, maxLeftWidth);
76
+ const rightWidth = Math.max(20, totalWidth - leftWidth - indent);
77
+ const firstIndent = " ".repeat(indent);
78
+ const continuationIndent = " ".repeat(indent + leftWidth);
79
+ return rows
80
+ .flatMap((row) => {
81
+ if (row.right.length === 0) {
82
+ return [`${firstIndent}${row.left}`];
83
+ }
84
+ const rightLines = wrapWords(row.right, rightWidth);
85
+ if (visibleWidth(row.left) > leftWidth) {
86
+ return [
87
+ `${firstIndent}${row.left}`,
88
+ ...rightLines.map((line) => `${continuationIndent}${line}`)
89
+ ];
90
+ }
91
+ const firstLine = `${firstIndent}${padEndVisible(row.left, leftWidth)}${rightLines[0]}`;
92
+ const continuationLines = rightLines
93
+ .slice(1)
94
+ .map((line) => `${continuationIndent}${line}`);
95
+ return [firstLine, ...continuationLines];
96
+ })
97
+ .join("\n");
98
+ }
3
99
  export function formatCommand(name, description) {
4
- const paddedName = name.padEnd(widths.helpColumn);
5
- return ` ${text.command(paddedName)} ${description}`;
100
+ return formatColumns({
101
+ rows: [{ left: text.command(name), right: description }]
102
+ });
6
103
  }
7
104
  export function formatUsage(command, args) {
8
105
  const argsStr = args ? ` ${text.argument(args)}` : "";
9
106
  return `${text.usageCommand(command)}${argsStr}`;
10
107
  }
11
108
  export function formatOption(flags, description) {
12
- const paddedFlags = flags.padEnd(widths.helpColumn);
13
- return ` ${text.option(paddedFlags)} ${description}`;
109
+ return formatColumns({
110
+ rows: [{ left: text.option(flags), right: description }]
111
+ });
14
112
  }
15
113
  export function formatCommandList(commands) {
16
- return commands.map((cmd) => formatCommand(cmd.name, cmd.description)).join("\n");
114
+ return formatColumns({
115
+ rows: commands.map((cmd) => ({
116
+ left: text.command(cmd.name),
117
+ right: cmd.description
118
+ }))
119
+ });
17
120
  }
18
121
  export function formatOptionList(options) {
19
- return options.map((opt) => formatOption(opt.flags, opt.description)).join("\n");
122
+ return formatColumns({
123
+ rows: options.map((opt) => ({
124
+ left: text.option(opt.flags),
125
+ right: opt.description
126
+ }))
127
+ });
20
128
  }
21
129
  export const helpFormatter = {
130
+ formatColumns,
22
131
  formatCommand,
23
132
  formatUsage,
24
133
  formatOption,
@@ -2,8 +2,8 @@ export { text } from "./text.js";
2
2
  export { symbols } from "./symbols.js";
3
3
  export { createLogger, logger } from "./logger.js";
4
4
  export type { LoggerOutput } from "./logger.js";
5
- export { helpFormatter, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./help-formatter.js";
6
- export type { CommandInfo, OptionInfo } from "./help-formatter.js";
5
+ export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./help-formatter.js";
6
+ export type { CommandInfo, OptionInfo, FormatColumnsOptions } from "./help-formatter.js";
7
7
  export { formatCommandNotFound } from "./command-errors.js";
8
8
  export { formatCommandNotFoundPanel } from "./command-errors.js";
9
9
  export { renderTable } from "./table.js";
@@ -1,7 +1,7 @@
1
1
  export { text } from "./text.js";
2
2
  export { symbols } from "./symbols.js";
3
3
  export { createLogger, logger } from "./logger.js";
4
- export { helpFormatter, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./help-formatter.js";
4
+ export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./help-formatter.js";
5
5
  export { formatCommandNotFound } from "./command-errors.js";
6
6
  export { formatCommandNotFoundPanel } from "./command-errors.js";
7
7
  export { renderTable } from "./table.js";
@@ -2,6 +2,7 @@ export declare const text: {
2
2
  readonly intro: (content: string) => string;
3
3
  readonly heading: (content: string) => string;
4
4
  readonly section: (content: string) => string;
5
+ readonly sectionHeader: (content: string) => string;
5
6
  readonly command: (content: string) => string;
6
7
  readonly argument: (content: string) => string;
7
8
  readonly option: (content: string) => string;
@@ -27,6 +27,14 @@ export const text = {
27
27
  return `**${content}**`;
28
28
  return typography.bold(content);
29
29
  },
30
+ sectionHeader(content) {
31
+ const format = resolveOutputFormat();
32
+ if (format === "json")
33
+ return content;
34
+ if (format === "markdown")
35
+ return `## ${content}`;
36
+ return typography.bold(content.toUpperCase());
37
+ },
30
38
  command(content) {
31
39
  const format = resolveOutputFormat();
32
40
  if (format === "json")
@@ -8,8 +8,9 @@ export { text } from "./components/text.js";
8
8
  export { symbols } from "./components/symbols.js";
9
9
  export { createLogger, logger } from "./components/logger.js";
10
10
  export type { LoggerOutput } from "./components/logger.js";
11
- export { helpFormatter, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./components/help-formatter.js";
12
- export type { CommandInfo, OptionInfo } from "./components/help-formatter.js";
11
+ export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./components/help-formatter.js";
12
+ export * as helpFormatterPlain from "./components/help-formatter-plain.js";
13
+ export type { CommandInfo, OptionInfo, FormatColumnsOptions } from "./components/help-formatter.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";
@@ -8,7 +8,8 @@ export { widths } from "./tokens/widths.js";
8
8
  export { text } from "./components/text.js";
9
9
  export { symbols } from "./components/symbols.js";
10
10
  export { createLogger, logger } from "./components/logger.js";
11
- export { helpFormatter, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./components/help-formatter.js";
11
+ export { helpFormatter, formatColumns, formatCommand, formatUsage, formatOption, formatCommandList, formatOptionList } from "./components/help-formatter.js";
12
+ export * as helpFormatterPlain from "./components/help-formatter-plain.js";
12
13
  export { formatCommandNotFound } from "./components/command-errors.js";
13
14
  export { formatCommandNotFoundPanel } from "./components/command-errors.js";
14
15
  export { renderTable } from "./components/table.js";
@@ -0,0 +1,41 @@
1
+ # @poe-code/process-runner
2
+
3
+ Low-level process execution abstraction. Single interface for launching processes on the host or inside Docker containers.
4
+
5
+ ## Overview
6
+
7
+ - No external dependencies
8
+ - Consumed by `@poe-code/agent-spawn`
9
+ - Consumed by `process-launcher`
10
+
11
+ ## Host Factory
12
+
13
+ - `createHostRunner(options)` returns a `Runner` that launches commands with `node:child_process.spawn`.
14
+ - `options.detached` starts the child as a detached process and uses process-group kill on Unix.
15
+ - `hostExecutionEnvFactory` implements the shared execution environment contract for local host execution.
16
+ - Host environments do not upload, download, detach, or reattach because the caller workspace is already local and there is no addressable remote environment.
17
+
18
+ ## Docker Factories
19
+
20
+ - `createDockerRunner(options)` returns a one-shot `Runner` that executes each command in a Docker or Podman container.
21
+ - `dockerExecutionEnvFactory` creates an addressable long-lived container environment for Poe Code runtime jobs.
22
+ - Docker environments support workspace upload/download, command execution, interactive shell, detach, attach, and cleanup.
23
+
24
+ `DockerRunnerOptions`:
25
+
26
+ - `image`: container image to run.
27
+ - `engine`: `docker` or `podman`; detected when omitted.
28
+ - `context`: Docker context name; detected when omitted.
29
+ - `mounts`: container mounts.
30
+ - `ports`: port mappings.
31
+ - `network`: Docker network.
32
+ - `extraArgs`: additional runtime arguments.
33
+ - `containerName`: optional container name prefix.
34
+
35
+ ## Environment variables
36
+
37
+ This package exposes no environment variables.
38
+
39
+ ## Configuration
40
+
41
+ This package currently exposes no package-level configuration options.
@@ -0,0 +1,2 @@
1
+ import type { DockerRunArgs } from "../types.js";
2
+ export declare function buildDockerRunArgs(input: DockerRunArgs): string[];
@@ -0,0 +1,40 @@
1
+ import path from "node:path";
2
+ export function buildDockerRunArgs(input) {
3
+ const args = [input.engine];
4
+ if (input.engine === "docker" && input.context) {
5
+ args.push("--context", input.context);
6
+ }
7
+ args.push("run");
8
+ if (input.rm) {
9
+ args.push("--rm");
10
+ }
11
+ if (input.detached) {
12
+ args.push("-d");
13
+ }
14
+ if (input.interactive) {
15
+ args.push("-i");
16
+ }
17
+ if (input.tty) {
18
+ args.push("-t");
19
+ }
20
+ args.push("--name", input.containerName);
21
+ if (input.cwd !== undefined) {
22
+ args.push("-w", input.cwd);
23
+ }
24
+ for (const [key, value] of Object.entries(input.env ?? {})) {
25
+ args.push("-e", `${key}=${value}`);
26
+ }
27
+ for (const mount of input.mounts) {
28
+ const volume = `${path.resolve(mount.source)}:${mount.target}${mount.readonly ? ":ro" : ""}`;
29
+ args.push("-v", volume);
30
+ }
31
+ for (const port of input.ports) {
32
+ const mapping = `${port.host}:${port.container}${port.protocol === undefined || port.protocol === "tcp" ? "" : `/${port.protocol}`}`;
33
+ args.push("-p", mapping);
34
+ }
35
+ if (input.network !== undefined) {
36
+ args.push("--network", input.network);
37
+ }
38
+ args.push(...input.extraArgs, input.image, input.command, ...input.args);
39
+ return args;
40
+ }
@@ -0,0 +1,3 @@
1
+ import type { Engine } from "../types.js";
2
+ export declare function detectContext(): string | null;
3
+ export declare function buildContextArgs(engine: Engine, context: string | null): string[];
@@ -0,0 +1,30 @@
1
+ import { execSync } from "node:child_process";
2
+ export function detectContext() {
3
+ try {
4
+ const output = execSync("colima list --json", {
5
+ encoding: "utf-8",
6
+ stdio: ["pipe", "pipe", "ignore"]
7
+ });
8
+ const lines = output.trim().split("\n").filter(Boolean);
9
+ for (const line of lines) {
10
+ const profile = JSON.parse(line);
11
+ if (profile.status === "Running" && profile.runtime === "docker") {
12
+ const name = profile.name ?? profile.profile;
13
+ if (!name) {
14
+ continue;
15
+ }
16
+ return name === "default" ? "colima" : `colima-${name}`;
17
+ }
18
+ }
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ return null;
24
+ }
25
+ export function buildContextArgs(engine, context) {
26
+ if (engine === "docker" && context) {
27
+ return ["--context", context];
28
+ }
29
+ return [];
30
+ }