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.
- package/dist/cli.js +274 -160
- package/dist/renderer.d.ts +8 -2
- package/dist/renderer.js +71 -12
- package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.d.ts +4 -0
- package/node_modules/@poe-code/design-system/dist/components/help-formatter-plain.js +132 -0
- package/node_modules/@poe-code/design-system/dist/components/help-formatter.d.ts +13 -0
- package/node_modules/@poe-code/design-system/dist/components/help-formatter.js +116 -7
- package/node_modules/@poe-code/design-system/dist/components/index.d.ts +2 -2
- package/node_modules/@poe-code/design-system/dist/components/index.js +1 -1
- package/node_modules/@poe-code/design-system/dist/components/text.d.ts +1 -0
- package/node_modules/@poe-code/design-system/dist/components/text.js +8 -0
- package/node_modules/@poe-code/design-system/dist/index.d.ts +3 -2
- package/node_modules/@poe-code/design-system/dist/index.js +2 -1
- package/node_modules/@poe-code/task-list/README.md +49 -5
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.d.ts +19 -0
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues-client.js +62 -0
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues.d.ts +13 -0
- package/node_modules/@poe-code/task-list/dist/backends/gh-issues.js +627 -0
- package/node_modules/@poe-code/task-list/dist/backends/markdown-dir.js +253 -41
- package/node_modules/@poe-code/task-list/dist/backends/utils.d.ts +7 -1
- package/node_modules/@poe-code/task-list/dist/backends/utils.js +21 -0
- package/node_modules/@poe-code/task-list/dist/backends/yaml-file.js +171 -16
- package/node_modules/@poe-code/task-list/dist/index.d.ts +3 -1
- package/node_modules/@poe-code/task-list/dist/index.js +1 -1
- package/node_modules/@poe-code/task-list/dist/open.d.ts +4 -2
- package/node_modules/@poe-code/task-list/dist/open.js +27 -3
- package/node_modules/@poe-code/task-list/dist/types.d.ts +51 -3
- package/node_modules/@poe-code/task-list/dist/types.js +25 -0
- package/node_modules/@poe-code/task-list/package.json +1 -0
- package/package.json +3 -2
package/dist/renderer.d.ts
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import type { Command, RenderPrimitives } from "./index.js";
|
|
2
2
|
export type OutputMode = "rich" | "md" | "json";
|
|
3
|
-
type
|
|
4
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5
|
-
|
|
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
|
-
|
|
13
|
-
|
|
109
|
+
return formatColumns({
|
|
110
|
+
rows: [{ left: text.option(flags), right: description }]
|
|
111
|
+
});
|
|
14
112
|
}
|
|
15
113
|
export function formatCommandList(commands) {
|
|
16
|
-
return
|
|
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
|
|
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
|
|
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
|
|
7
|
+
`@poe-code/task-list` exposes one API over these backends:
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
|
|
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
|
-
|
|
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;
|