toolcraft 0.0.11 → 0.0.12

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 (30) 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/task-list/README.md +49 -5
  15. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.d.ts +19 -0
  16. package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.js +62 -0
  17. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.d.ts +13 -0
  18. package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +627 -0
  19. package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +253 -41
  20. package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +7 -1
  21. package/node_modules/@poe-code/task-list/dist/backends/utils.js +21 -0
  22. package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +171 -16
  23. package/node_modules/@poe-code/task-list/dist/index.d.ts +3 -1
  24. package/node_modules/@poe-code/task-list/dist/index.js +1 -1
  25. package/node_modules/@poe-code/task-list/dist/open.d.ts +4 -2
  26. package/node_modules/@poe-code/task-list/dist/open.js +27 -3
  27. package/node_modules/@poe-code/task-list/dist/types.d.ts +51 -3
  28. package/node_modules/@poe-code/task-list/dist/types.js +25 -0
  29. package/node_modules/@poe-code/task-list/package.json +1 -0
  30. package/package.json +3 -2
@@ -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";
@@ -4,10 +4,13 @@ Multi-list task manager with pluggable storage backends.
4
4
 
5
5
  ## Backends
6
6
 
7
- `@poe-code/task-list` exposes one API over two backends:
7
+ `@poe-code/task-list` exposes one API over these backends:
8
8
 
9
- - `markdown-dir`: one Markdown file per task, organized into subdirectories per list
10
- - `yaml-file`: one YAML document with a top-level `lists:` mapping
9
+ | Backend | Storage |
10
+ | --- | --- |
11
+ | `markdown-dir` | One Markdown file per task, organized into subdirectories per list. |
12
+ | `yaml-file` | One YAML document with a top-level `lists:` mapping. |
13
+ | `gh-issues` | GitHub Issues in one repository, ordered and state-tracked through a GitHub Project v2 Status field. |
11
14
 
12
15
  The task lifecycle is `draft -> planned -> in-progress -> done -> archived`. `archived` is terminal.
13
16
 
@@ -47,7 +50,7 @@ Pass a custom machine with `openTaskList({ stateMachine })`. If omitted, the pac
47
50
 
48
51
  | Option | Type | Default | Behavior |
49
52
  | --- | --- | --- | --- |
50
- | `type` | `"markdown-dir" \| "yaml-file"` | required | Selects the backend implementation. |
53
+ | `type` | `"markdown-dir" \| "yaml-file" \| "gh-issues"` | required | Selects the backend implementation. |
51
54
  | `path` | `string` | required | Root directory for `markdown-dir` or YAML file path for `yaml-file`. |
52
55
  | `defaults` | `TaskDefaults` | `{ metadata: {} }` | Seeds omitted metadata on new tasks only. New tasks always start at the configured state machine's initial state. |
53
56
  | `create` | `boolean` | `false` | Creates missing storage for the selected backend when enabled. |
@@ -58,7 +61,9 @@ Pass a custom machine with `openTaskList({ stateMachine })`. If omitted, the pac
58
61
 
59
62
  ## Env vars
60
63
 
61
- None.
64
+ | Env var | Behavior |
65
+ | --- | --- |
66
+ | `GH_HOST` | Defers to gh CLI's host configuration; set GH_HOST to override. |
62
67
 
63
68
  ## Usage
64
69
 
@@ -105,6 +110,36 @@ await planning.fire("review-release", "plan");
105
110
  await planning.fire("review-release", "start");
106
111
  ```
107
112
 
113
+ ### `gh-issues`
114
+
115
+ ```ts
116
+ import { openTaskList } from "@poe-code/task-list";
117
+
118
+ const taskList = await openTaskList({
119
+ type: "gh-issues",
120
+ repo: "octo-org/octo-repo",
121
+ project: {
122
+ owner: "octo-org",
123
+ number: 7
124
+ }
125
+ });
126
+
127
+ const project = taskList.list("octo-org/7");
128
+
129
+ await taskList.lists(); // ["octo-org/7"]
130
+
131
+ const created = await project.create({
132
+ id: "local-id-is-ignored",
133
+ name: "Review release checklist"
134
+ });
135
+
136
+ await project.fire(created.id, "In Progress");
137
+ await project.move(created.id, { position: "top" });
138
+ await project.move(created.id, { after: "42" });
139
+ ```
140
+
141
+ `gh-issues` exposes one list named `${project.owner}/${project.number}`. `create()` ignores `TaskCreate.id` because GitHub assigns issue numbers. `fire(state)` writes the Project v2 `Status` field to the matching single-select option. `move()` reorders project items.
142
+
108
143
  ## Notes
109
144
 
110
145
  The package never overwrites existing task files or store files. `defaults.metadata` is applied only when creating new tasks and does not retroactively update existing tasks.
@@ -112,3 +147,12 @@ The package never overwrites existing task files or store files. `defaults.metad
112
147
  Task state changes are event-driven: use `fire(id, event)` to move between states, `canFire(id, event)` to check whether an event is currently legal, and `events(id)` to list the currently legal event names. There is no `transition()` API.
113
148
 
114
149
  `create()` always starts new tasks at `stateMachine.initial`. `update()` cannot change `state`; use `fire()` instead.
150
+
151
+ ### `gh-issues` limitations
152
+
153
+ - The state machine is fetched at open and frozen for the session; re-open to refresh project Status options.
154
+ - `archived` is not supported and returns an empty result.
155
+ - `update()` does not write labels, assignees, or milestone in v1.
156
+ - `moveBetweenLists()` is unsupported on `gh-issues`.
157
+ - `id` on `TaskCreate` is ignored on this backend.
158
+ - `gh auth token` must be available, or pass `auth: { token }`.
@@ -0,0 +1,19 @@
1
+ import { type Runner } from "@poe-code/process-runner";
2
+ export interface GhClientOptions {
3
+ token: string;
4
+ endpoint?: string;
5
+ fetch?: typeof fetch;
6
+ }
7
+ export interface GhClient {
8
+ graphql<T>(query: string, variables: Record<string, unknown>): Promise<T>;
9
+ }
10
+ export declare function createGhClient(options: GhClientOptions): GhClient;
11
+ export interface ResolveAuthOptions {
12
+ explicitToken?: string;
13
+ runner?: Runner;
14
+ }
15
+ export declare function resolveAuth(options: ResolveAuthOptions): Promise<string>;
16
+ export interface ResolveEndpointOptions {
17
+ env?: Record<string, string | undefined>;
18
+ }
19
+ export declare function resolveEndpoint(options?: ResolveEndpointOptions): string;