taskchef 0.0.1 → 1.0.1
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/BACKLOG.md +67 -0
- package/README.md +156 -3
- package/SPEC.md +310 -0
- package/assets/AGENTS.md.template +18 -0
- package/bin/taskchef.js +7 -2
- package/index.js +23 -3
- package/package.json +20 -5
- package/skills/taskchef-bootstrap/SKILL.md +50 -0
- package/skills/taskchef-bootstrap/agents/openai.yaml +4 -0
- package/skills/taskchef-delegate/SKILL.md +43 -0
- package/skills/taskchef-delegate/agents/openai.yaml +4 -0
- package/skills/taskchef-reconcile/SKILL.md +35 -0
- package/skills/taskchef-reconcile/agents/openai.yaml +4 -0
- package/src/cli.js +303 -0
- package/src/workspace.js +882 -0
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: taskchef-bootstrap
|
|
3
|
+
description: "Initialize, diagnose, or refresh TaskChef dispatcher workspaces, project configuration, managed AGENTS.md instructions, and TaskChef skill links. Use when creating a TaskChef workspace, adding, importing, listing, or removing configured projects, running TaskChef doctor, repairing dispatcher setup, or upgrading installed TaskChef skills. Do not dispatch user work or reconcile executor threads."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TaskChef Bootstrap
|
|
7
|
+
|
|
8
|
+
Initialize or refresh a data-only TaskChef dispatcher workspace.
|
|
9
|
+
|
|
10
|
+
Resolve this linked skill with `realpath`. The TaskChef source root is two
|
|
11
|
+
parents above the skill directory. Invoke `<source-root>/bin/taskchef.js` for
|
|
12
|
+
all deterministic workspace operations.
|
|
13
|
+
|
|
14
|
+
## Boundaries
|
|
15
|
+
|
|
16
|
+
- Keep implementation, tests, and reports in the TaskChef source repository.
|
|
17
|
+
- Keep only `AGENTS.md`, `taskchef.json`, `tasks/*/task.json`, and the three
|
|
18
|
+
TaskChef skill links in a dispatcher workspace.
|
|
19
|
+
- Do not dispatch tasks or reconcile executor threads during bootstrap unless
|
|
20
|
+
the user separately requests those actions.
|
|
21
|
+
- Never use collaboration agents, hooks, schedules, polling, or daemons.
|
|
22
|
+
|
|
23
|
+
## Initialize and repair
|
|
24
|
+
|
|
25
|
+
1. Run `workspace init --json`. It takes no stdin, creates an empty
|
|
26
|
+
configuration when missing, and idempotently creates or refreshes the task
|
|
27
|
+
directory, managed instructions, and all three skill links.
|
|
28
|
+
2. Run `doctor --json` after setup or when the user asks to diagnose the
|
|
29
|
+
workspace. Doctor is read-only; rerun `workspace init --json` to repair the
|
|
30
|
+
managed scaffold.
|
|
31
|
+
3. Report the actions or failed checks. End without dispatching unless the user
|
|
32
|
+
separately requested work.
|
|
33
|
+
|
|
34
|
+
## Configure projects
|
|
35
|
+
|
|
36
|
+
1. List native Codex projects once when discovery is necessary. Configure only
|
|
37
|
+
projects local to the TaskChef workspace's execution host. Remote connection
|
|
38
|
+
projects are outside the v1 contract.
|
|
39
|
+
2. Add one project with `project add <path>`, normally supplying `--name` and a
|
|
40
|
+
curated `--description`. The CLI detects Git status, exact Git root, and a
|
|
41
|
+
canonical GitHub `origin`; use `--no-github` or `--github-repo` only to
|
|
42
|
+
override detection.
|
|
43
|
+
3. Bulk import with `project import <file|-> --json`. Input is a JSON array of
|
|
44
|
+
objects containing `path` plus optional `name`, `description`, and
|
|
45
|
+
`githubRepo`. Import merges by canonical path and preserves an existing name
|
|
46
|
+
or description when omitted. Use `--replace` only when the user explicitly
|
|
47
|
+
requests replacement.
|
|
48
|
+
4. Inspect configured projects with `project list --json`. Remove by name with
|
|
49
|
+
`project remove`; require explicit user intent before `--force` when task
|
|
50
|
+
records reference the project.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: taskchef-delegate
|
|
3
|
+
description: "Dispatch actionable requests from an initialized TaskChef workspace into independently openable Codex project tasks. Use for ordinary work requests in a TaskChef workspace, explicit delegation, splitting work across projects, or retrying pending executor creation. Dispatch must return immediately and must never use subagents, hooks, schedules, or foreground waiting. Use taskchef-bootstrap instead for workspace setup or configuration changes."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TaskChef Delegate
|
|
7
|
+
|
|
8
|
+
Create real Codex tasks from a TaskChef data workspace and return immediately.
|
|
9
|
+
Use `taskchef-reconcile` first when the current prompt has active recorded tasks.
|
|
10
|
+
|
|
11
|
+
Resolve this linked skill with `realpath`. The TaskChef source root is two
|
|
12
|
+
parents above the skill directory. Invoke `<source-root>/bin/taskchef.js` for
|
|
13
|
+
all deterministic workspace and task-record operations.
|
|
14
|
+
|
|
15
|
+
## Boundaries
|
|
16
|
+
|
|
17
|
+
- Keep implementation, tests, and reports in the TaskChef source repository.
|
|
18
|
+
- Keep only `AGENTS.md`, `taskchef.json`, `tasks/*/task.json`, and the three
|
|
19
|
+
TaskChef skill links in a dispatcher workspace.
|
|
20
|
+
- Use real Codex tasks, never collaboration or subagent tools.
|
|
21
|
+
- Never use hooks, callbacks, schedules, polling, daemons, or event logs.
|
|
22
|
+
- Never wait for delegated work after executor creation.
|
|
23
|
+
- Never collect transcripts or hidden reasoning.
|
|
24
|
+
|
|
25
|
+
## Dispatch
|
|
26
|
+
|
|
27
|
+
1. Run `project list --json` to load and validate the configured routing
|
|
28
|
+
targets. Use `taskchef-bootstrap` if the workspace is missing or unhealthy.
|
|
29
|
+
2. Split the request into the smallest independently useful outcomes. Include
|
|
30
|
+
constraints, expected testing, and reporting in every instruction.
|
|
31
|
+
3. Classify against configured `name`, `githubRepo`, and `description`. Use
|
|
32
|
+
`path` only as checkout identity. Ask when metadata does not produce one
|
|
33
|
+
clear project match.
|
|
34
|
+
4. Resolve native projects once and require the exact configured path.
|
|
35
|
+
5. Reuse a matching pending record for an explicit retry; otherwise create one
|
|
36
|
+
pending record before executor creation.
|
|
37
|
+
6. Create one real Codex task per record using the exact saved project and a
|
|
38
|
+
local environment on its executor host.
|
|
39
|
+
7. Immediately record every returned `threadId` with status `running`. Never
|
|
40
|
+
persist `hostId`.
|
|
41
|
+
8. Leave a failed creation pending. Do not invent an ID or delete the record.
|
|
42
|
+
9. Return immediately with a created-thread directive for every success. Do
|
|
43
|
+
not read or wait for a newly created executor.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: taskchef-reconcile
|
|
3
|
+
description: "Reconcile active TaskChef task records with their visible Codex executor threads. Use at the start of ordinary prompts in a TaskChef workspace, for TaskChef status or progress requests, after delegated work may have completed or blocked, or for an explicitly requested full refresh. Performs one bounded snapshot pass and never polls or waits for future activity."
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# TaskChef Reconcile
|
|
7
|
+
|
|
8
|
+
Refresh TaskChef's recorded task state from native Codex threads exactly once.
|
|
9
|
+
|
|
10
|
+
Resolve this linked skill with `realpath`. The TaskChef source root is two
|
|
11
|
+
parents above the skill directory. Invoke `<source-root>/bin/taskchef.js` for
|
|
12
|
+
all deterministic record operations.
|
|
13
|
+
|
|
14
|
+
## Reconcile
|
|
15
|
+
|
|
16
|
+
1. Run `task reconcile-candidates --json` once. It returns only `running` and
|
|
17
|
+
`blocked` tasks with thread IDs. Do not scan pending or finished records.
|
|
18
|
+
2. Query every returned thread exactly once using immediate native snapshots,
|
|
19
|
+
with no more than eight targets per call.
|
|
20
|
+
3. Do not wait for future activity and do not poll.
|
|
21
|
+
4. Map an active attempt to `running`, a user-input or external dependency to
|
|
22
|
+
`blocked`, and any concluded attempt to `finished`, including failed or
|
|
23
|
+
partial attempts.
|
|
24
|
+
5. Preserve an existing result unless the thread provides a meaningful newer
|
|
25
|
+
report. Results contain exactly `message`, `githubPRs`, and `githubIssues`.
|
|
26
|
+
6. Update each changed record once with `task update`.
|
|
27
|
+
7. Report a concise snapshot and continue with the user's request. If there
|
|
28
|
+
were no candidates or changes, say so briefly.
|
|
29
|
+
|
|
30
|
+
Use `task reconcile-candidates --include-finished --json` only when the user
|
|
31
|
+
explicitly requests a full refresh or says a finished executor received new
|
|
32
|
+
work. A finished task with a new active attempt becomes `running` again.
|
|
33
|
+
|
|
34
|
+
The native thread is authoritative between reconciliations. Executor reports
|
|
35
|
+
are evidence, not independently verified completion guarantees.
|
package/src/cli.js
ADDED
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
import { readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
addProject,
|
|
6
|
+
buildReconciliationCandidates,
|
|
7
|
+
buildTaskSummary,
|
|
8
|
+
createTask,
|
|
9
|
+
doctorWorkspace,
|
|
10
|
+
filterTasks,
|
|
11
|
+
importProjects,
|
|
12
|
+
initializeWorkspace,
|
|
13
|
+
listProjects,
|
|
14
|
+
readTask,
|
|
15
|
+
removeProject,
|
|
16
|
+
updateTask,
|
|
17
|
+
} from "./workspace.js";
|
|
18
|
+
|
|
19
|
+
async function readStdin() {
|
|
20
|
+
let input = "";
|
|
21
|
+
process.stdin.setEncoding("utf8");
|
|
22
|
+
for await (const chunk of process.stdin) input += chunk;
|
|
23
|
+
return input;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function readJsonStdin() {
|
|
27
|
+
const input = await readStdin();
|
|
28
|
+
if (input.trim().length === 0) throw new Error("expected JSON on standard input");
|
|
29
|
+
return JSON.parse(input);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function option(args, name, fallback) {
|
|
33
|
+
const index = args.indexOf(name);
|
|
34
|
+
if (index === -1) return fallback;
|
|
35
|
+
if (!args[index + 1] || args[index + 1].startsWith("--")) {
|
|
36
|
+
throw new Error(`${name} requires a value`);
|
|
37
|
+
}
|
|
38
|
+
return args[index + 1];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function options(args, name) {
|
|
42
|
+
const values = [];
|
|
43
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
44
|
+
if (args[index] !== name) continue;
|
|
45
|
+
if (!args[index + 1] || args[index + 1].startsWith("--")) {
|
|
46
|
+
throw new Error(`${name} requires a value`);
|
|
47
|
+
}
|
|
48
|
+
values.push(args[index + 1]);
|
|
49
|
+
index += 1;
|
|
50
|
+
}
|
|
51
|
+
return values;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function validateCommandArgs(
|
|
55
|
+
args,
|
|
56
|
+
startIndex,
|
|
57
|
+
{ values = [], switches = [], repeatable = [] } = {},
|
|
58
|
+
) {
|
|
59
|
+
const valueOptions = new Set(values);
|
|
60
|
+
const booleanOptions = new Set(switches);
|
|
61
|
+
const repeatableOptions = new Set(repeatable);
|
|
62
|
+
const seen = new Set();
|
|
63
|
+
for (let index = startIndex; index < args.length; index += 1) {
|
|
64
|
+
const token = args[index];
|
|
65
|
+
if (!token.startsWith("--") || token.includes("=")) {
|
|
66
|
+
throw new Error(`unexpected argument: ${token}`);
|
|
67
|
+
}
|
|
68
|
+
if (!valueOptions.has(token) && !booleanOptions.has(token)) {
|
|
69
|
+
throw new Error(`unsupported option: ${token}`);
|
|
70
|
+
}
|
|
71
|
+
if (seen.has(token) && !repeatableOptions.has(token)) {
|
|
72
|
+
throw new Error(`duplicate option: ${token}`);
|
|
73
|
+
}
|
|
74
|
+
seen.add(token);
|
|
75
|
+
if (valueOptions.has(token)) {
|
|
76
|
+
if (!args[index + 1] || args[index + 1].startsWith("--")) {
|
|
77
|
+
throw new Error(`${token} requires a value`);
|
|
78
|
+
}
|
|
79
|
+
index += 1;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function workspaceRoot(args) {
|
|
85
|
+
return path.resolve(option(args, "--workspace", process.cwd()));
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function print(value, args, human) {
|
|
89
|
+
if (args.includes("--json")) {
|
|
90
|
+
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
process.stdout.write(`${human ? human(value) : JSON.stringify(value, null, 2)}\n`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function table(headers, rows) {
|
|
97
|
+
const widths = headers.map((header, index) =>
|
|
98
|
+
Math.max(header.length, ...rows.map((row) => String(row[index]).length)));
|
|
99
|
+
const format = (row) => row.map((value, index) => String(value).padEnd(widths[index])).join(" ");
|
|
100
|
+
return [format(headers), ...rows.map(format)].join("\n");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function initialize(args) {
|
|
104
|
+
validateCommandArgs(args, 2, { values: ["--workspace"], switches: ["--json"] });
|
|
105
|
+
const result = await initializeWorkspace(workspaceRoot(args));
|
|
106
|
+
print(result, args, (value) => [
|
|
107
|
+
`Workspace: ${value.workspace}`,
|
|
108
|
+
`Configuration: ${value.config.action}`,
|
|
109
|
+
`Instructions: ${value.instructions.action}`,
|
|
110
|
+
`Skills: ${value.skills.skills.map((skill) => `${skill.name}=${skill.action}`).join(", ")}`,
|
|
111
|
+
].join("\n"));
|
|
112
|
+
return 0;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
async function doctor(args) {
|
|
116
|
+
validateCommandArgs(args, 1, { values: ["--workspace"], switches: ["--json"] });
|
|
117
|
+
const result = await doctorWorkspace(workspaceRoot(args));
|
|
118
|
+
print(result, args, (value) => value.checks
|
|
119
|
+
.map((check) => `${check.status === "pass" ? "✓" : "✗"} ${check.name}: ${check.message}`)
|
|
120
|
+
.join("\n"));
|
|
121
|
+
return result.ok ? 0 : 1;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
async function projectAdd(args) {
|
|
125
|
+
if (!args[2] || args[2].startsWith("--")) throw new Error("project add requires a path");
|
|
126
|
+
validateCommandArgs(args, 3, {
|
|
127
|
+
values: ["--workspace", "--name", "--description", "--github-repo"],
|
|
128
|
+
switches: ["--json", "--no-github"],
|
|
129
|
+
});
|
|
130
|
+
if (args.includes("--no-github") && args.includes("--github-repo")) {
|
|
131
|
+
throw new Error("--no-github and --github-repo cannot be used together");
|
|
132
|
+
}
|
|
133
|
+
const input = { path: args[2] };
|
|
134
|
+
const name = option(args, "--name", null);
|
|
135
|
+
const description = option(args, "--description", null);
|
|
136
|
+
if (name !== null) input.name = name;
|
|
137
|
+
if (description !== null) input.description = description;
|
|
138
|
+
if (args.includes("--no-github")) input.githubRepo = null;
|
|
139
|
+
else if (args.includes("--github-repo")) input.githubRepo = option(args, "--github-repo");
|
|
140
|
+
const project = await addProject(workspaceRoot(args), input);
|
|
141
|
+
print(project, args, (value) => `Added ${value.name}: ${value.path}`);
|
|
142
|
+
return 0;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
async function projectImport(args) {
|
|
146
|
+
const hasSource = Boolean(args[2] && !args[2].startsWith("--"));
|
|
147
|
+
const source = hasSource ? args[2] : "-";
|
|
148
|
+
validateCommandArgs(args, hasSource ? 3 : 2, {
|
|
149
|
+
values: ["--workspace"],
|
|
150
|
+
switches: ["--json", "--replace"],
|
|
151
|
+
});
|
|
152
|
+
const content = source === "-"
|
|
153
|
+
? await readStdin()
|
|
154
|
+
: await readFile(path.resolve(source), "utf8");
|
|
155
|
+
if (content.trim().length === 0) throw new Error("project import input is empty");
|
|
156
|
+
const result = await importProjects(workspaceRoot(args), JSON.parse(content), {
|
|
157
|
+
replace: args.includes("--replace"),
|
|
158
|
+
});
|
|
159
|
+
print(result, args, (value) =>
|
|
160
|
+
`Imported ${value.importedCount} project(s); ${value.projectCount} configured (${value.mode}).`);
|
|
161
|
+
return 0;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function projectList(args) {
|
|
165
|
+
validateCommandArgs(args, 2, { values: ["--workspace"], switches: ["--json"] });
|
|
166
|
+
const projects = await listProjects(workspaceRoot(args));
|
|
167
|
+
print({ projectCount: projects.length, projects }, args, (value) => table(
|
|
168
|
+
["NAME", "KIND", "PATH"],
|
|
169
|
+
value.projects.map((project) => [
|
|
170
|
+
project.name,
|
|
171
|
+
project.isGitRepository ? "git" : "folder",
|
|
172
|
+
project.path,
|
|
173
|
+
]),
|
|
174
|
+
));
|
|
175
|
+
return 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function projectRemove(args) {
|
|
179
|
+
if (!args[2] || args[2].startsWith("--")) throw new Error("project remove requires a name");
|
|
180
|
+
validateCommandArgs(args, 3, {
|
|
181
|
+
values: ["--workspace"],
|
|
182
|
+
switches: ["--json", "--force"],
|
|
183
|
+
});
|
|
184
|
+
const result = await removeProject(workspaceRoot(args), args[2], {
|
|
185
|
+
force: args.includes("--force"),
|
|
186
|
+
});
|
|
187
|
+
print(result, args, (value) => `Removed ${value.project.name}: ${value.project.path}`);
|
|
188
|
+
return 0;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async function create(args) {
|
|
192
|
+
validateCommandArgs(args, 2, { values: ["--workspace"], switches: ["--json"] });
|
|
193
|
+
const task = await createTask(workspaceRoot(args), await readJsonStdin());
|
|
194
|
+
print(task, args, (value) => `Created ${value.id}: ${value.title} (${value.status})`);
|
|
195
|
+
return 0;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async function update(args) {
|
|
199
|
+
validateCommandArgs(args, 3, { values: ["--workspace"], switches: ["--json"] });
|
|
200
|
+
const task = await updateTask(workspaceRoot(args), args[2], await readJsonStdin());
|
|
201
|
+
print(task, args, (value) => `Updated ${value.id}: ${value.status}`);
|
|
202
|
+
return 0;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function show(args) {
|
|
206
|
+
validateCommandArgs(args, 3, { values: ["--workspace"], switches: ["--json"] });
|
|
207
|
+
print(await readTask(workspaceRoot(args), args[2]), args);
|
|
208
|
+
return 0;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
async function taskList(args) {
|
|
212
|
+
validateCommandArgs(args, 2, {
|
|
213
|
+
values: ["--workspace", "--status", "--project"],
|
|
214
|
+
switches: ["--json"],
|
|
215
|
+
repeatable: ["--status"],
|
|
216
|
+
});
|
|
217
|
+
const tasks = await filterTasks(workspaceRoot(args), {
|
|
218
|
+
statuses: options(args, "--status"),
|
|
219
|
+
project: option(args, "--project", null),
|
|
220
|
+
});
|
|
221
|
+
const projects = await listProjects(workspaceRoot(args));
|
|
222
|
+
const projectNames = new Map(projects.map((project) => [project.path, project.name]));
|
|
223
|
+
const result = { taskCount: tasks.length, tasks };
|
|
224
|
+
print(result, args, (value) => table(
|
|
225
|
+
["ID", "STATUS", "PROJECT", "TITLE"],
|
|
226
|
+
value.tasks.map((task) => [
|
|
227
|
+
task.id,
|
|
228
|
+
task.status,
|
|
229
|
+
projectNames.get(task.project) ?? path.basename(task.project),
|
|
230
|
+
task.title,
|
|
231
|
+
]),
|
|
232
|
+
));
|
|
233
|
+
return 0;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
async function taskSummary(args) {
|
|
237
|
+
validateCommandArgs(args, 2, { values: ["--workspace"], switches: ["--json"] });
|
|
238
|
+
const summary = await buildTaskSummary(workspaceRoot(args));
|
|
239
|
+
print(summary, args, (value) => [
|
|
240
|
+
`Tasks: ${value.taskCount}`,
|
|
241
|
+
...Object.entries(value.statusCounts).map(([status, count]) => `${status}: ${count}`),
|
|
242
|
+
].join("\n"));
|
|
243
|
+
return 0;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function reconciliationCandidates(args) {
|
|
247
|
+
validateCommandArgs(args, 2, {
|
|
248
|
+
values: ["--workspace"],
|
|
249
|
+
switches: ["--json", "--include-finished"],
|
|
250
|
+
});
|
|
251
|
+
const result = await buildReconciliationCandidates(workspaceRoot(args), {
|
|
252
|
+
includeFinished: args.includes("--include-finished"),
|
|
253
|
+
});
|
|
254
|
+
print(result, args);
|
|
255
|
+
return 0;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
function usage() {
|
|
259
|
+
process.stdout.write(`TaskChef workspace utility
|
|
260
|
+
|
|
261
|
+
Usage:
|
|
262
|
+
taskchef help
|
|
263
|
+
taskchef doctor [--json] [--workspace <path>]
|
|
264
|
+
taskchef workspace init [--json] [--workspace <path>]
|
|
265
|
+
taskchef project add <path> [--name <name>] [--description <text>] [--github-repo <url> | --no-github] [--json] [--workspace <path>]
|
|
266
|
+
taskchef project import [<file> | -] [--replace] [--json] [--workspace <path>]
|
|
267
|
+
taskchef project list [--json] [--workspace <path>]
|
|
268
|
+
taskchef project remove <name> [--force] [--json] [--workspace <path>]
|
|
269
|
+
taskchef task create [--json] [--workspace <path>]
|
|
270
|
+
taskchef task update <task-id> [--json] [--workspace <path>]
|
|
271
|
+
taskchef task show <task-id> [--json] [--workspace <path>]
|
|
272
|
+
taskchef task list [--status <status>]... [--project <name-or-path>] [--json] [--workspace <path>]
|
|
273
|
+
taskchef task summary [--json] [--workspace <path>]
|
|
274
|
+
taskchef task reconcile-candidates [--include-finished] [--json] [--workspace <path>]
|
|
275
|
+
|
|
276
|
+
Task create and update read JSON from standard input. Project import reads a
|
|
277
|
+
JSON array from a file, or from standard input when the source is '-' or omitted.
|
|
278
|
+
`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export async function runCli(args) {
|
|
282
|
+
if (args.length === 0 || args[0] === "help" || args[0] === "--help") {
|
|
283
|
+
usage();
|
|
284
|
+
return 0;
|
|
285
|
+
}
|
|
286
|
+
if (args[0] === "doctor") return doctor(args);
|
|
287
|
+
if (args[0] === "workspace" && args[1] === "init") return initialize(args);
|
|
288
|
+
if (args[0] === "project" && args[1] === "add") return projectAdd(args);
|
|
289
|
+
if (args[0] === "project" && args[1] === "import") return projectImport(args);
|
|
290
|
+
if (args[0] === "project" && args[1] === "list") return projectList(args);
|
|
291
|
+
if (args[0] === "project" && args[1] === "remove") return projectRemove(args);
|
|
292
|
+
if (args[0] === "task" && args[1] === "create") return create(args);
|
|
293
|
+
if (args[0] === "task" && args[1] === "update" && args[2]) return update(args);
|
|
294
|
+
if (args[0] === "task" && args[1] === "show" && args[2]) return show(args);
|
|
295
|
+
if (args[0] === "task" && args[1] === "list") return taskList(args);
|
|
296
|
+
if (args[0] === "task" && args[1] === "summary") return taskSummary(args);
|
|
297
|
+
if (args[0] === "task" && args[1] === "reconcile-candidates") {
|
|
298
|
+
return reconciliationCandidates(args);
|
|
299
|
+
}
|
|
300
|
+
process.stderr.write(`Unknown command: ${args.join(" ")}\n`);
|
|
301
|
+
usage();
|
|
302
|
+
return 2;
|
|
303
|
+
}
|