taskchef 8.7.1 → 9.0.0
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/.codex-plugin/plugin.json +7 -10
- package/.mcp.json +3 -2
- package/README.md +31 -158
- package/docs/mcp-app.md +3 -3
- package/docs/spec.md +38 -746
- package/docs/taskchef-next-storage.md +2 -2
- package/index.js +2 -99
- package/package.json +12 -49
- package/skills/delegate/SKILL.md +44 -0
- package/skills/delegate/agents/openai.yaml +4 -0
- package/src/codex-app.js +3 -152
- package/src/codex-session-scanner.js +2 -2
- package/src/file-lock.js +25 -0
- package/src/mcp-app/react/NextTaskDetail.test.tsx +1 -1
- package/src/mcp-app/react/NextTaskDetail.tsx +8 -8
- package/src/mcp-app/react/main.test.tsx +3 -3
- package/src/mcp-app/react/main.tsx +7 -7
- package/src/mcp-app.js +2 -2
- package/src/mcp.js +4 -542
- package/src/next-cache.js +2 -2
- package/src/next-github.js +3 -3
- package/src/next-notifications.js +2 -2
- package/src/{dashboard/react → ui}/components/CardStatusIcons.tsx +1 -1
- package/src/{dashboard/react → ui}/components/GitHubLinks.tsx +1 -1
- package/src/{dashboard/react → ui}/components/LinkedText.tsx +1 -1
- package/src/{dashboard/react → ui}/components/RelativeTime.test.tsx +1 -1
- package/src/{dashboard/react → ui}/components/RelativeTime.tsx +1 -1
- package/src/{dashboard/react → ui}/components/ReplyImagePreview.test.tsx +1 -1
- package/src/{dashboard/react → ui}/components/TaskBoard.tsx +1 -1
- package/src/{dashboard/react → ui}/components/TaskCard.tsx +1 -1
- package/src/{dashboard/react → ui}/components/TurnContent.tsx +1 -1
- package/src/{dashboard/react → ui}/presentation.ts +1 -1
- package/src/version.js +0 -1
- package/BACKLOG.md +0 -48
- package/assets/e2e-benchmark-example.json +0 -50
- package/assets/taskchef-dispatcher-instructions.md +0 -44
- package/bin/taskchef.js +0 -20
- package/docs/advanced-guide.md +0 -497
- package/docs/dashboard-lifecycle.md +0 -149
- package/docs/dashboard-ui.md +0 -40
- package/docs/firstmate-taskchef-comparison.md +0 -283
- package/docs/images/ccusage-token-consumption.png +0 -0
- package/docs/images/dashboard-identity.jpg +0 -0
- package/docs/images/dashboard-token-pending.jpg +0 -0
- package/docs/images/interrupted-turn-recovery.jpg +0 -0
- package/docs/images/notification-event-snapshots.jpg +0 -0
- package/docs/images/readme-board.png +0 -0
- package/docs/images/readme-completed.png +0 -0
- package/docs/images/readme-list.png +0 -0
- package/docs/images/readme-needs-input.png +0 -0
- package/docs/images/readme-roles.png +0 -0
- package/docs/images/reported-work-duration-desktop.jpg +0 -0
- package/docs/images/reported-work-duration-mobile.jpg +0 -0
- package/docs/images/result-history-dashboard.jpg +0 -0
- package/docs/images/taskchef-pr-icon-comparison.jpg +0 -0
- package/docs/images/taskchef-unified-status-popup.jpg +0 -0
- package/docs/workflows.md +0 -476
- package/mcp/dashboard-session.js +0 -51
- package/scripts/benchmark-dispatch-prepare.js +0 -90
- package/scripts/e2e-benchmark.js +0 -206
- package/scripts/roles/resolve_roles.py +0 -293
- package/skills/taskchef-bootstrap/SKILL.md +0 -96
- package/skills/taskchef-bootstrap/agents/openai.yaml +0 -4
- package/skills/taskchef-bootstrap/references/project-index.md +0 -154
- package/skills/taskchef-copilot/SKILL.md +0 -75
- package/skills/taskchef-copilot/agents/openai.yaml +0 -4
- package/skills/taskchef-dashboard/SKILL.md +0 -24
- package/skills/taskchef-dashboard/agents/openai.yaml +0 -4
- package/skills/taskchef-delegate/SKILL.md +0 -102
- package/skills/taskchef-delegate/agents/openai.yaml +0 -4
- package/skills/taskchef-delegate/references/model-roles.md +0 -29
- package/skills/taskchef-executor/SKILL.md +0 -154
- package/skills/taskchef-executor/agents/openai.yaml +0 -4
- package/skills/taskchef-executor/references/compatibility.md +0 -22
- package/skills/taskchef-executor/references/ending-actions.md +0 -26
- package/src/backups.js +0 -359
- package/src/cli.js +0 -733
- package/src/config-audit.js +0 -99
- package/src/config-mutations.js +0 -106
- package/src/dashboard/actions.js +0 -136
- package/src/dashboard/app.js +0 -1172
- package/src/dashboard/dist/app.js +0 -269
- package/src/dashboard/dist/app.js.map +0 -1
- package/src/dashboard/dist/index.html +0 -16
- package/src/dashboard/dist/styles.css +0 -1
- package/src/dashboard/index.html +0 -148
- package/src/dashboard/react/App.test.tsx +0 -346
- package/src/dashboard/react/App.tsx +0 -515
- package/src/dashboard/react/api.test.ts +0 -40
- package/src/dashboard/react/api.ts +0 -103
- package/src/dashboard/react/components/ActivityTimeline.tsx +0 -118
- package/src/dashboard/react/components/ModelSettings.test.tsx +0 -180
- package/src/dashboard/react/components/ModelSettings.tsx +0 -106
- package/src/dashboard/react/components/NotificationCenter.test.tsx +0 -76
- package/src/dashboard/react/components/NotificationCenter.tsx +0 -111
- package/src/dashboard/react/components/PullRequestBadges.tsx +0 -18
- package/src/dashboard/react/components/TaskDetail.test.tsx +0 -255
- package/src/dashboard/react/components/TaskDetail.tsx +0 -271
- package/src/dashboard/react/components/UsagePanel.tsx +0 -96
- package/src/dashboard/react/index.html +0 -15
- package/src/dashboard/react/main.tsx +0 -12
- package/src/dashboard/react/overlay-state.test.ts +0 -110
- package/src/dashboard/react/overlay-state.ts +0 -60
- package/src/dashboard/react/presentation.test.tsx +0 -571
- package/src/dashboard/styles.css +0 -261
- package/src/dashboard-control.js +0 -34
- package/src/dashboard-manager.js +0 -339
- package/src/dashboard-session-process.js +0 -88
- package/src/dashboard-session.js +0 -86
- package/src/dashboard.js +0 -1303
- package/src/delegation.js +0 -277
- package/src/github.js +0 -107
- package/src/model-roles.js +0 -70
- package/src/recorded-usage-tracker.js +0 -92
- package/src/usage-tracker.js +0 -650
- package/src/usage.js +0 -367
- package/src/workspace-path.js +0 -48
- package/src/workspace.js +0 -3629
- /package/src/{dashboard/react → ui}/components/AnimatedUsage.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/CardStatusIcons.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/ImageGallery.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/OpenChatButton.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/OpenChatButton.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/PrGlyph.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/QueuedPrompts.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/QueuedPrompts.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/ReplyCover.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/ReplyCover.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/ReplyImagePreview.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/ReplyMarkdown.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/ReplyMarkdown.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/SavedUsageFormat.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/SavedUsageFormat.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/ShimmerText.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/StatsGrid.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/StatusBadge.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/TaskBoard.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/TaskCardStats.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/TaskCardStats.tsx +0 -0
- /package/src/{dashboard/react → ui}/components/TurnContent.test.tsx +0 -0
- /package/src/{dashboard/react → ui}/fixtures.ts +0 -0
- /package/src/{dashboard → ui}/github-links.js +0 -0
- /package/src/{dashboard/react → ui}/image-cache.test.ts +0 -0
- /package/src/{dashboard/react → ui}/image-cache.ts +0 -0
- /package/src/{dashboard → ui}/state.js +0 -0
- /package/src/{dashboard/react → ui}/styles.css +0 -0
- /package/src/{dashboard/react → ui}/test-setup.ts +0 -0
- /package/src/{dashboard → ui}/time.js +0 -0
- /package/src/{dashboard/react → ui}/types.ts +0 -0
package/src/cli.js
DELETED
|
@@ -1,733 +0,0 @@
|
|
|
1
|
-
import { access, readFile, realpath } from "node:fs/promises";
|
|
2
|
-
import path from "node:path";
|
|
3
|
-
|
|
4
|
-
import { openWorkspaceInCodex } from "./codex-app.js";
|
|
5
|
-
import { createDashboardServer } from "./dashboard.js";
|
|
6
|
-
import { resolveWorkspacePath } from "./workspace-path.js";
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
addProject,
|
|
10
|
-
createWorkspaceBackup,
|
|
11
|
-
buildCopilotBrief,
|
|
12
|
-
buildTaskSummary,
|
|
13
|
-
doctorWorkspace,
|
|
14
|
-
filterTasks,
|
|
15
|
-
importProjects,
|
|
16
|
-
initializeWorkspace,
|
|
17
|
-
listProjects,
|
|
18
|
-
listWorkspaceBackups,
|
|
19
|
-
pruneWorkspaceBackups,
|
|
20
|
-
readProjectIndex,
|
|
21
|
-
readWorkspaceMaintenance,
|
|
22
|
-
listTasks,
|
|
23
|
-
migrateTaskLog,
|
|
24
|
-
prepareDispatch,
|
|
25
|
-
recordTask,
|
|
26
|
-
removeProject,
|
|
27
|
-
restoreWorkspaceBackup,
|
|
28
|
-
requireSafeId,
|
|
29
|
-
updateProject,
|
|
30
|
-
verifyWorkspaceBackup,
|
|
31
|
-
} from "./workspace.js";
|
|
32
|
-
|
|
33
|
-
const BLANK_TABLE_CELL = Symbol("blank table cell");
|
|
34
|
-
|
|
35
|
-
async function readStdin() {
|
|
36
|
-
let input = "";
|
|
37
|
-
process.stdin.setEncoding("utf8");
|
|
38
|
-
for await (const chunk of process.stdin) input += chunk;
|
|
39
|
-
return input;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
export function assertTaskRecordStdin(stdin = process.stdin) {
|
|
43
|
-
if (stdin.isTTY) {
|
|
44
|
-
throw new Error(
|
|
45
|
-
"task record requires non-interactive JSON on standard input; pipe one JSON value and close stdin",
|
|
46
|
-
);
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function readJsonStdin() {
|
|
51
|
-
assertTaskRecordStdin();
|
|
52
|
-
const input = await readStdin();
|
|
53
|
-
if (input.trim().length === 0) throw new Error("expected JSON on standard input");
|
|
54
|
-
return JSON.parse(input);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function option(args, name, fallback) {
|
|
58
|
-
const index = args.indexOf(name);
|
|
59
|
-
if (index === -1) return fallback;
|
|
60
|
-
if (!args[index + 1] || args[index + 1].startsWith("--")) {
|
|
61
|
-
throw new Error(`${name} requires a value`);
|
|
62
|
-
}
|
|
63
|
-
return args[index + 1];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function options(args, name) {
|
|
67
|
-
const values = [];
|
|
68
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
69
|
-
if (args[index] === name) values.push(args[index + 1]);
|
|
70
|
-
}
|
|
71
|
-
return values;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
function validateCommandArgs(
|
|
75
|
-
args,
|
|
76
|
-
startIndex,
|
|
77
|
-
{ values = [], switches = [], repeatable = [] } = {},
|
|
78
|
-
) {
|
|
79
|
-
const valueOptions = new Set(values);
|
|
80
|
-
const booleanOptions = new Set(switches);
|
|
81
|
-
const repeatableOptions = new Set(repeatable);
|
|
82
|
-
const seen = new Set();
|
|
83
|
-
for (let index = startIndex; index < args.length; index += 1) {
|
|
84
|
-
const token = args[index];
|
|
85
|
-
if (!token.startsWith("--") || token.includes("=")) {
|
|
86
|
-
throw new Error(`unexpected argument: ${token}`);
|
|
87
|
-
}
|
|
88
|
-
if (!valueOptions.has(token) && !booleanOptions.has(token)) {
|
|
89
|
-
throw new Error(`unsupported option: ${token}`);
|
|
90
|
-
}
|
|
91
|
-
if (seen.has(token) && !repeatableOptions.has(token)) {
|
|
92
|
-
throw new Error(`duplicate option: ${token}`);
|
|
93
|
-
}
|
|
94
|
-
seen.add(token);
|
|
95
|
-
if (valueOptions.has(token)) {
|
|
96
|
-
if (!args[index + 1] || args[index + 1].startsWith("--")) {
|
|
97
|
-
throw new Error(`${token} requires a value`);
|
|
98
|
-
}
|
|
99
|
-
index += 1;
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
function workspaceSelection(args) {
|
|
105
|
-
return resolveWorkspacePath({
|
|
106
|
-
explicit: args.includes("--workspace") ? option(args, "--workspace") : null,
|
|
107
|
-
});
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function workspaceRoot(args) {
|
|
111
|
-
return workspaceSelection(args).workspace;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function print(value, args, human) {
|
|
115
|
-
if (args.includes("--json")) {
|
|
116
|
-
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
117
|
-
return;
|
|
118
|
-
}
|
|
119
|
-
process.stdout.write(`${human ? human(value) : JSON.stringify(value, null, 2)}\n`);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
async function mutationOptions(args) {
|
|
123
|
-
const confirmedPath = option(args, "--confirm-removed", null);
|
|
124
|
-
let confirmRemoved;
|
|
125
|
-
if (confirmedPath !== null) {
|
|
126
|
-
confirmRemoved = JSON.parse(await readFile(path.resolve(confirmedPath), "utf8"));
|
|
127
|
-
}
|
|
128
|
-
const numberOption = (name) => {
|
|
129
|
-
const value = option(args, name, null);
|
|
130
|
-
if (value === null) return undefined;
|
|
131
|
-
const parsed = Number(value);
|
|
132
|
-
if (!Number.isSafeInteger(parsed) || parsed < 0) throw new Error(`${name} must be a non-negative integer`);
|
|
133
|
-
return parsed;
|
|
134
|
-
};
|
|
135
|
-
return {
|
|
136
|
-
dryRun: args.includes("--dry-run"),
|
|
137
|
-
expectedConfigHash: option(args, "--expect-config-hash", undefined),
|
|
138
|
-
expectedProjectCount: numberOption("--expect-project-count"),
|
|
139
|
-
expectedAfterCount: numberOption("--expect-after-count"),
|
|
140
|
-
confirmPlan: option(args, "--confirm-plan", undefined),
|
|
141
|
-
confirmRemoved,
|
|
142
|
-
confirmCountCollapse: option(args, "--confirm-count-collapse", undefined),
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
function table(headers, rows) {
|
|
147
|
-
const display = (value) => {
|
|
148
|
-
if (value === BLANK_TABLE_CELL) return "";
|
|
149
|
-
if (value === null || value === undefined || value === "") return "-";
|
|
150
|
-
return String(value);
|
|
151
|
-
};
|
|
152
|
-
const widths = headers.map((header, index) =>
|
|
153
|
-
Math.max(header.length, ...rows.map((row) => display(row[index]).length)));
|
|
154
|
-
const format = (row) => row.map((value, index) => index === row.length - 1
|
|
155
|
-
? display(value)
|
|
156
|
-
: display(value).padEnd(widths[index])).join(" ").trimEnd();
|
|
157
|
-
return [format(headers), ...rows.map(format)].join("\n");
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
function projectRows(projects) {
|
|
161
|
-
return projects.flatMap((project) => {
|
|
162
|
-
const [primaryRepository = null, ...additionalRepositories] = project.githubRepos;
|
|
163
|
-
return [[
|
|
164
|
-
project.name,
|
|
165
|
-
project.isGitRepository ? "git" : "folder",
|
|
166
|
-
primaryRepository,
|
|
167
|
-
project.path,
|
|
168
|
-
], ...additionalRepositories.map((repository) => [
|
|
169
|
-
BLANK_TABLE_CELL,
|
|
170
|
-
BLANK_TABLE_CELL,
|
|
171
|
-
repository,
|
|
172
|
-
BLANK_TABLE_CELL,
|
|
173
|
-
])];
|
|
174
|
-
});
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function sortTasksByCreatedAt(tasks, ascending) {
|
|
178
|
-
return tasks
|
|
179
|
-
.map((task, index) => ({ task, index }))
|
|
180
|
-
.sort((left, right) => {
|
|
181
|
-
const leftCreatedAt = left.task.createdAt;
|
|
182
|
-
const rightCreatedAt = right.task.createdAt;
|
|
183
|
-
if (!leftCreatedAt && !rightCreatedAt) return left.index - right.index;
|
|
184
|
-
if (!leftCreatedAt) return 1;
|
|
185
|
-
if (!rightCreatedAt) return -1;
|
|
186
|
-
const chronological = Date.parse(leftCreatedAt) - Date.parse(rightCreatedAt);
|
|
187
|
-
return (ascending ? chronological : -chronological) || left.index - right.index;
|
|
188
|
-
})
|
|
189
|
-
.map(({ task }) => task);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function displayId(value, fullId) {
|
|
193
|
-
if (fullId || value === null || value === undefined) return value;
|
|
194
|
-
const uuidSection = String(value).match(/^[0-9a-fA-F]{8}(?=-)/);
|
|
195
|
-
return uuidSection ? uuidSection[0] : value;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
function singleLineDetail(value) {
|
|
199
|
-
return String(value).replaceAll("\r", "\\r").replaceAll("\n", "\\n");
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
function taskDetails(task) {
|
|
203
|
-
const lastResult = task.lastResult;
|
|
204
|
-
return [
|
|
205
|
-
`Title: ${singleLineDetail(task.title)}`,
|
|
206
|
-
`Project: ${singleLineDetail(task.project.name)}`,
|
|
207
|
-
`Current status: ${singleLineDetail(task.status ?? "unknown")}`,
|
|
208
|
-
`Current turn ref: ${singleLineDetail(task.turnRef ?? "-")}`,
|
|
209
|
-
`Current Codex turn ID: ${singleLineDetail(task.turnId ?? "-")}`,
|
|
210
|
-
`Last result status: ${singleLineDetail(lastResult?.status ?? "-")}`,
|
|
211
|
-
`Last result summary: ${singleLineDetail(lastResult?.summary ?? "-")}`,
|
|
212
|
-
`Last result turn ref: ${singleLineDetail(lastResult?.turnRef ?? "-")}`,
|
|
213
|
-
`Last result Codex turn ID: ${singleLineDetail(lastResult?.turnId ?? "-")}`,
|
|
214
|
-
`Last result updated: ${singleLineDetail(lastResult?.updatedAt ?? "-")}`,
|
|
215
|
-
`Project path: ${singleLineDetail(task.project.path)}`,
|
|
216
|
-
`Created: ${singleLineDetail(task.createdAt)}`,
|
|
217
|
-
`Updated: ${singleLineDetail(task.updatedAt ?? "-")}`,
|
|
218
|
-
`Updated by: ${singleLineDetail(task.updatedBy ?? "-")}`,
|
|
219
|
-
`Task ID: ${singleLineDetail(task.id)}`,
|
|
220
|
-
`Thread ID: ${singleLineDetail(task.threadId ?? "-")}`,
|
|
221
|
-
`Turn count: ${task.turns.length}`,
|
|
222
|
-
"Activity timeline (newest first):",
|
|
223
|
-
...[...task.turns].reverse().map((turn) => (
|
|
224
|
-
`- ${singleLineDetail(turn.startedAt)} | ${singleLineDetail(turn.result?.status ?? "working")} | ref ${singleLineDetail(turn.turnRef ?? "-")} | Codex turn ${singleLineDetail(turn.turnId ?? "-")} | request: ${singleLineDetail(turn.requestSummary ?? "not recorded")} | result: ${singleLineDetail(turn.result?.summary ?? "in progress")}`
|
|
225
|
-
)),
|
|
226
|
-
"Instruction:",
|
|
227
|
-
task.instruction,
|
|
228
|
-
].join("\n");
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
async function migrate(args) {
|
|
232
|
-
validateCommandArgs(args, 2, { values: ["--workspace"], switches: ["--json"] });
|
|
233
|
-
const result = await migrateTaskLog(workspaceRoot(args));
|
|
234
|
-
print(result, args, (value) => [
|
|
235
|
-
`Task log: ${value.action}`,
|
|
236
|
-
`Tasks: ${value.taskCount}`,
|
|
237
|
-
`Turns: ${value.turnCount}`,
|
|
238
|
-
`Migrated: ${value.migratedCount}`,
|
|
239
|
-
`Native turn refs: ${value.nativeTurnRefCount}`,
|
|
240
|
-
`Fallback turn refs: ${value.fallbackTurnRefCount}`,
|
|
241
|
-
`Backup: ${value.backupPath ?? "not needed"}`,
|
|
242
|
-
].join("\n"));
|
|
243
|
-
return 0;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
async function readTaskForShow(workspace, taskId) {
|
|
247
|
-
const id = requireSafeId(taskId, "taskId");
|
|
248
|
-
const tasks = await listTasks(workspace);
|
|
249
|
-
if (!/^[0-9a-fA-F]{8}$/.test(id)) {
|
|
250
|
-
const exact = tasks.find((task) => task.id === id);
|
|
251
|
-
if (exact) return exact;
|
|
252
|
-
if (id.length < 8) {
|
|
253
|
-
throw new Error(
|
|
254
|
-
`task ID prefix is too short: ${id}; use all 8 characters shown by taskchef task list`,
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
if (id.length === 8) {
|
|
258
|
-
throw new Error(
|
|
259
|
-
`malformed task ID prefix: ${id}; use the 8 hexadecimal characters shown by taskchef task list`,
|
|
260
|
-
);
|
|
261
|
-
}
|
|
262
|
-
throw new Error(`task not found: ${id}`);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
const matches = tasks.filter((task) => displayId(task.id, false) === id);
|
|
266
|
-
if (matches.length === 0) {
|
|
267
|
-
throw new Error(
|
|
268
|
-
`task not found for ID prefix: ${id}; run taskchef task list --full-id to verify the task ID`,
|
|
269
|
-
);
|
|
270
|
-
}
|
|
271
|
-
if (matches.length > 1) {
|
|
272
|
-
throw new Error(
|
|
273
|
-
`task ID prefix is ambiguous: ${id}; run taskchef task list --full-id and pass the full task ID`,
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
return matches[0];
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
async function initialize(args) {
|
|
280
|
-
validateCommandArgs(args, 2, {
|
|
281
|
-
values: ["--workspace", "--codex-cli"],
|
|
282
|
-
switches: ["--json", "--register-codex"],
|
|
283
|
-
});
|
|
284
|
-
const resolution = workspaceSelection(args);
|
|
285
|
-
const result = await initializeWorkspace(resolution.workspace);
|
|
286
|
-
result.resolutionSource = resolution.source;
|
|
287
|
-
let registrationFailed = false;
|
|
288
|
-
if (args.includes("--register-codex")) {
|
|
289
|
-
try {
|
|
290
|
-
result.registration = await openWorkspaceInCodex(result.workspace, {
|
|
291
|
-
explicit: args.includes("--codex-cli") ? option(args, "--codex-cli") : null,
|
|
292
|
-
});
|
|
293
|
-
} catch (error) {
|
|
294
|
-
registrationFailed = true;
|
|
295
|
-
result.registration = { status: "failed", reason: error.message };
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
print(result, args, (value) => [
|
|
299
|
-
`Workspace: ${value.workspace}`,
|
|
300
|
-
`Configuration: ${value.config.action}`,
|
|
301
|
-
`Task log: ${value.tasks.action}`,
|
|
302
|
-
`Instructions: ${value.instructions.action}`,
|
|
303
|
-
...(value.registration ? [`Codex opening: ${value.registration.status}`] : []),
|
|
304
|
-
].join("\n"));
|
|
305
|
-
return registrationFailed ? 5 : 0;
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
async function workspacePath(args) {
|
|
309
|
-
validateCommandArgs(args, 2, { values: ["--workspace"], switches: ["--json"] });
|
|
310
|
-
const resolution = workspaceSelection(args);
|
|
311
|
-
const exists = await access(resolution.workspace).then(() => true).catch(() => false);
|
|
312
|
-
const workspace = exists ? await realpath(resolution.workspace) : resolution.workspace;
|
|
313
|
-
print({
|
|
314
|
-
schemaVersion: 1,
|
|
315
|
-
workspace,
|
|
316
|
-
source: resolution.source,
|
|
317
|
-
exists,
|
|
318
|
-
}, args, (value) => value.workspace);
|
|
319
|
-
return 0;
|
|
320
|
-
}
|
|
321
|
-
|
|
322
|
-
async function doctor(args) {
|
|
323
|
-
validateCommandArgs(args, 1, { values: ["--workspace"], switches: ["--json"] });
|
|
324
|
-
const result = await doctorWorkspace(workspaceRoot(args));
|
|
325
|
-
print(result, args, (value) => value.checks
|
|
326
|
-
.map((check) => `${check.status === "pass" ? "✓" : "✗"} ${check.name}: ${check.message}`)
|
|
327
|
-
.join("\n"));
|
|
328
|
-
return result.ok ? 0 : 1;
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
async function projectAdd(args) {
|
|
332
|
-
if (!args[2] || args[2].startsWith("--")) throw new Error("project add requires a path");
|
|
333
|
-
validateCommandArgs(args, 3, {
|
|
334
|
-
values: ["--workspace", "--name", "--description", "--github-repo"],
|
|
335
|
-
switches: ["--json", "--no-github", "--dry-run"],
|
|
336
|
-
repeatable: ["--github-repo"],
|
|
337
|
-
});
|
|
338
|
-
if (args.includes("--no-github") && args.includes("--github-repo")) {
|
|
339
|
-
throw new Error("--no-github and --github-repo cannot be used together");
|
|
340
|
-
}
|
|
341
|
-
const input = { path: args[2] };
|
|
342
|
-
const name = option(args, "--name", null);
|
|
343
|
-
const description = option(args, "--description", null);
|
|
344
|
-
if (name !== null) input.name = name;
|
|
345
|
-
if (description !== null) input.description = description;
|
|
346
|
-
if (args.includes("--no-github")) input.githubRepos = [];
|
|
347
|
-
else if (args.includes("--github-repo")) input.githubRepos = options(args, "--github-repo");
|
|
348
|
-
const result = await addProject(workspaceRoot(args), input, {
|
|
349
|
-
dryRun: args.includes("--dry-run"),
|
|
350
|
-
});
|
|
351
|
-
print(result, args, (value) => {
|
|
352
|
-
const project = value.project ?? value;
|
|
353
|
-
return `${args.includes("--dry-run") ? "Would add" : "Added"} ${project.name}: ${project.path}`;
|
|
354
|
-
});
|
|
355
|
-
return 0;
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
async function projectImport(args) {
|
|
359
|
-
const hasSource = Boolean(args[2] && !args[2].startsWith("--"));
|
|
360
|
-
const source = hasSource ? args[2] : "-";
|
|
361
|
-
validateCommandArgs(args, hasSource ? 3 : 2, {
|
|
362
|
-
values: [
|
|
363
|
-
"--workspace", "--expect-config-hash", "--expect-project-count", "--expect-after-count",
|
|
364
|
-
"--confirm-plan", "--confirm-removed", "--confirm-count-collapse",
|
|
365
|
-
],
|
|
366
|
-
switches: ["--json", "--replace", "--dry-run"],
|
|
367
|
-
});
|
|
368
|
-
const content = source === "-"
|
|
369
|
-
? await readStdin()
|
|
370
|
-
: await readFile(path.resolve(source), "utf8");
|
|
371
|
-
if (content.trim().length === 0) throw new Error("project import input is empty");
|
|
372
|
-
const result = await importProjects(workspaceRoot(args), JSON.parse(content), {
|
|
373
|
-
replace: args.includes("--replace"),
|
|
374
|
-
...(await mutationOptions(args)),
|
|
375
|
-
});
|
|
376
|
-
print(result, args, (value) =>
|
|
377
|
-
`Imported ${value.importedCount} project(s); ${value.projectCount} configured (${value.mode}).`);
|
|
378
|
-
return 0;
|
|
379
|
-
}
|
|
380
|
-
|
|
381
|
-
async function projectList(args) {
|
|
382
|
-
validateCommandArgs(args, 2, {
|
|
383
|
-
values: ["--workspace"],
|
|
384
|
-
switches: ["--json", "--allow-missing-paths"],
|
|
385
|
-
});
|
|
386
|
-
const result = await readProjectIndex(workspaceRoot(args), {
|
|
387
|
-
checkPaths: !args.includes("--allow-missing-paths"),
|
|
388
|
-
});
|
|
389
|
-
print(result, args, (value) => table(
|
|
390
|
-
["NAME", "KIND", "GITHUB REPOSITORY", "PATH"],
|
|
391
|
-
projectRows(value.projects),
|
|
392
|
-
));
|
|
393
|
-
return 0;
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
async function dispatchPrepare(args) {
|
|
397
|
-
validateCommandArgs(args, 2, { values: ["--workspace"], switches: ["--json"] });
|
|
398
|
-
const resolution = workspaceSelection(args);
|
|
399
|
-
const prepared = await prepareDispatch(resolution.workspace);
|
|
400
|
-
prepared.workspaceSource = resolution.source;
|
|
401
|
-
print(prepared, args, (value) => [
|
|
402
|
-
`Workspace: ${value.workspace}`,
|
|
403
|
-
`Task ID: ${value.taskId}`,
|
|
404
|
-
`Prepared: ${value.preparedAt}`,
|
|
405
|
-
`Projects: ${value.projectCount}`,
|
|
406
|
-
].join("\n"));
|
|
407
|
-
return 0;
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
async function projectRemove(args) {
|
|
411
|
-
if (!args[2] || args[2].startsWith("--")) throw new Error("project remove requires a name");
|
|
412
|
-
validateCommandArgs(args, 3, {
|
|
413
|
-
values: [
|
|
414
|
-
"--workspace", "--expect-config-hash", "--expect-project-count", "--expect-after-count",
|
|
415
|
-
"--confirm-plan", "--confirm-removed", "--confirm-count-collapse",
|
|
416
|
-
],
|
|
417
|
-
switches: ["--json", "--dry-run"],
|
|
418
|
-
});
|
|
419
|
-
const result = await removeProject(workspaceRoot(args), args[2], await mutationOptions(args));
|
|
420
|
-
print(result, args, (value) =>
|
|
421
|
-
`${args.includes("--dry-run") ? "Would remove" : "Removed"} ${value.project.name}: ${value.project.path}`);
|
|
422
|
-
return 0;
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
async function projectUpdate(args) {
|
|
426
|
-
const hasName = Boolean(args[2] && !args[2].startsWith("--"));
|
|
427
|
-
validateCommandArgs(args, hasName ? 3 : 2, {
|
|
428
|
-
values: [
|
|
429
|
-
"--workspace", "--path", "--name", "--description", "--github-repo",
|
|
430
|
-
"--expect-config-hash",
|
|
431
|
-
],
|
|
432
|
-
switches: ["--json", "--clear-description", "--no-github", "--refresh-git", "--dry-run"],
|
|
433
|
-
repeatable: ["--github-repo"],
|
|
434
|
-
});
|
|
435
|
-
const selectedPath = option(args, "--path", null);
|
|
436
|
-
if (hasName === Boolean(selectedPath)) throw new Error("project update requires exactly one name or --path selector");
|
|
437
|
-
if (args.includes("--description") && args.includes("--clear-description")) {
|
|
438
|
-
throw new Error("--description and --clear-description cannot be combined");
|
|
439
|
-
}
|
|
440
|
-
if (args.includes("--no-github") && args.includes("--github-repo")) {
|
|
441
|
-
throw new Error("--no-github and --github-repo cannot be combined");
|
|
442
|
-
}
|
|
443
|
-
const input = {};
|
|
444
|
-
if (args.includes("--name")) input.name = option(args, "--name", null);
|
|
445
|
-
if (args.includes("--description")) input.description = option(args, "--description", null);
|
|
446
|
-
if (args.includes("--clear-description")) input.clearDescription = true;
|
|
447
|
-
if (args.includes("--no-github")) input.githubRepos = [];
|
|
448
|
-
else if (args.includes("--github-repo")) input.githubRepos = options(args, "--github-repo");
|
|
449
|
-
if (args.includes("--refresh-git")) input.refreshGit = true;
|
|
450
|
-
const result = await updateProject(
|
|
451
|
-
workspaceRoot(args),
|
|
452
|
-
selectedPath ? { path: selectedPath } : { name: args[2] },
|
|
453
|
-
input,
|
|
454
|
-
await mutationOptions(args),
|
|
455
|
-
);
|
|
456
|
-
print(result, args, (value) => value.changed
|
|
457
|
-
? `Updated ${value.project.name}: ${value.project.path}`
|
|
458
|
-
: `Unchanged ${value.project.name}: ${value.project.path}`);
|
|
459
|
-
return 0;
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
async function backupCommand(args) {
|
|
463
|
-
const action = args[1];
|
|
464
|
-
if (action === "list") {
|
|
465
|
-
validateCommandArgs(args, 2, { values: ["--workspace"], switches: ["--json"] });
|
|
466
|
-
const result = await listWorkspaceBackups(workspaceRoot(args));
|
|
467
|
-
print(result, args, (value) => value.backups.map((item) =>
|
|
468
|
-
`${item.id} ${item.usable ? "usable" : item.integrityValid ? "forensic" : "corrupt"}`)
|
|
469
|
-
.join("\n") || "No backups.");
|
|
470
|
-
return 0;
|
|
471
|
-
}
|
|
472
|
-
if (action === "create") {
|
|
473
|
-
validateCommandArgs(args, 2, { values: ["--workspace"], switches: ["--json"] });
|
|
474
|
-
const result = await createWorkspaceBackup(workspaceRoot(args), { reason: "manual" });
|
|
475
|
-
print(result, args, (value) => `Created backup ${value.id}.`);
|
|
476
|
-
return 0;
|
|
477
|
-
}
|
|
478
|
-
if (action === "verify" && args[2]) {
|
|
479
|
-
validateCommandArgs(args, 3, { values: ["--workspace"], switches: ["--json"] });
|
|
480
|
-
const result = await verifyWorkspaceBackup(workspaceRoot(args), args[2]);
|
|
481
|
-
print(result, args, (value) => `${value.id}: ${value.usable ? "usable" : value.error}`);
|
|
482
|
-
return result.usable ? 0 : 1;
|
|
483
|
-
}
|
|
484
|
-
if (action === "prune") {
|
|
485
|
-
validateCommandArgs(args, 2, { values: ["--workspace"], switches: ["--json", "--apply", "--dry-run"] });
|
|
486
|
-
if (args.includes("--apply") && args.includes("--dry-run")) throw new Error("--apply and --dry-run conflict");
|
|
487
|
-
const result = await pruneWorkspaceBackups(workspaceRoot(args), { apply: args.includes("--apply") });
|
|
488
|
-
print(result, args, (value) => `${value.apply ? "Pruned" : "Would prune"} ${value.removed.length} backup(s).`);
|
|
489
|
-
return 0;
|
|
490
|
-
}
|
|
491
|
-
if (action === "restore") {
|
|
492
|
-
const hasBackupId = Boolean(args[2] && !args[2].startsWith("--"));
|
|
493
|
-
validateCommandArgs(args, hasBackupId ? 3 : 2, {
|
|
494
|
-
values: [
|
|
495
|
-
"--workspace", "--scope", "--expect-config-hash", "--expect-project-count",
|
|
496
|
-
"--expect-after-count", "--confirm-plan", "--confirm-removed", "--confirm-count-collapse",
|
|
497
|
-
"--resume", "--rollback",
|
|
498
|
-
],
|
|
499
|
-
switches: [
|
|
500
|
-
"--json", "--dry-run", "--approve-restore", "--confirm-offline",
|
|
501
|
-
"--confirm-unreadable-current",
|
|
502
|
-
],
|
|
503
|
-
});
|
|
504
|
-
const resume = option(args, "--resume", null);
|
|
505
|
-
const rollback = option(args, "--rollback", null);
|
|
506
|
-
if ([hasBackupId, resume !== null, rollback !== null].filter(Boolean).length !== 1) {
|
|
507
|
-
throw new Error("backup restore requires exactly one backup ID, --resume ID, or --rollback ID");
|
|
508
|
-
}
|
|
509
|
-
let backupId = hasBackupId ? args[2] : null;
|
|
510
|
-
let maintenanceTransaction = null;
|
|
511
|
-
if (resume !== null || rollback !== null) {
|
|
512
|
-
const maintenance = await readWorkspaceMaintenance(workspaceRoot(args));
|
|
513
|
-
maintenanceTransaction = resume ?? rollback;
|
|
514
|
-
if (!maintenance.active || maintenance.transactionId !== maintenanceTransaction) {
|
|
515
|
-
throw new Error("maintenance transaction not found");
|
|
516
|
-
}
|
|
517
|
-
backupId = resume !== null ? maintenance.backupId : maintenance.safetyBackupId;
|
|
518
|
-
}
|
|
519
|
-
const result = await restoreWorkspaceBackup(workspaceRoot(args), backupId, {
|
|
520
|
-
...(await mutationOptions(args)),
|
|
521
|
-
scope: resume !== null || rollback !== null ? "state" : option(args, "--scope", "projects"),
|
|
522
|
-
approveRestore: args.includes("--approve-restore"),
|
|
523
|
-
confirmOffline: args.includes("--confirm-offline"),
|
|
524
|
-
confirmUnreadableCurrent: args.includes("--confirm-unreadable-current"),
|
|
525
|
-
maintenanceTransaction,
|
|
526
|
-
});
|
|
527
|
-
print(result, args, (value) => value.dryRun
|
|
528
|
-
? `Restore preview ${value.beforeCount} -> ${value.afterCount}.`
|
|
529
|
-
: `Restored backup ${value.backupId} (${value.scope}).`);
|
|
530
|
-
return 0;
|
|
531
|
-
}
|
|
532
|
-
throw new Error("backup command requires list, create, verify, prune, or restore");
|
|
533
|
-
}
|
|
534
|
-
|
|
535
|
-
async function taskRecord(args) {
|
|
536
|
-
validateCommandArgs(args, 2, { values: ["--workspace"], switches: ["--json"] });
|
|
537
|
-
const dispatch = await recordTask(workspaceRoot(args), await readJsonStdin());
|
|
538
|
-
print(dispatch, args, (value) => `Recorded ${value.id}: ${value.title}`);
|
|
539
|
-
return 0;
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
async function taskShow(args) {
|
|
543
|
-
validateCommandArgs(args, 3, { values: ["--workspace"], switches: ["--json"] });
|
|
544
|
-
print(await readTaskForShow(workspaceRoot(args), args[2]), args, taskDetails);
|
|
545
|
-
return 0;
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
async function taskList(args) {
|
|
549
|
-
validateCommandArgs(args, 2, {
|
|
550
|
-
values: ["--workspace", "--project"],
|
|
551
|
-
switches: ["--ascending", "--full-id", "--json"],
|
|
552
|
-
});
|
|
553
|
-
const filtered = await filterTasks(workspaceRoot(args), {
|
|
554
|
-
project: option(args, "--project", null),
|
|
555
|
-
});
|
|
556
|
-
const dispatches = sortTasksByCreatedAt(filtered, args.includes("--ascending"));
|
|
557
|
-
const result = { taskCount: dispatches.length, tasks: dispatches };
|
|
558
|
-
const fullId = args.includes("--full-id");
|
|
559
|
-
print(result, args, (value) => table(
|
|
560
|
-
["TITLE", "PROJECT", "STATUS", "UPDATED", "ID", "THREAD ID"],
|
|
561
|
-
value.tasks.map((dispatch) => [
|
|
562
|
-
dispatch.title,
|
|
563
|
-
dispatch.project?.name,
|
|
564
|
-
dispatch.status ?? "unknown",
|
|
565
|
-
dispatch.updatedAt ?? dispatch.createdAt,
|
|
566
|
-
displayId(dispatch.id, fullId),
|
|
567
|
-
displayId(dispatch.threadId, fullId),
|
|
568
|
-
]),
|
|
569
|
-
));
|
|
570
|
-
return 0;
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
async function taskSummary(args) {
|
|
574
|
-
validateCommandArgs(args, 2, { values: ["--workspace"], switches: ["--json"] });
|
|
575
|
-
const summary = await buildTaskSummary(workspaceRoot(args));
|
|
576
|
-
print(summary, args, (value) => [
|
|
577
|
-
`Tasks: ${value.taskCount}`,
|
|
578
|
-
...Object.entries(value.projectCounts).map(([project, count]) => `${project}: ${count}`),
|
|
579
|
-
].join("\n"));
|
|
580
|
-
return 0;
|
|
581
|
-
}
|
|
582
|
-
|
|
583
|
-
async function taskBrief(args) {
|
|
584
|
-
const hasTaskId = Boolean(args[2] && !args[2].startsWith("--"));
|
|
585
|
-
validateCommandArgs(args, hasTaskId ? 3 : 2, {
|
|
586
|
-
values: ["--workspace", "--project"],
|
|
587
|
-
switches: ["--all", "--json"],
|
|
588
|
-
});
|
|
589
|
-
if (hasTaskId && args.includes("--project")) {
|
|
590
|
-
throw new Error("task brief accepts either a task ID or --project, not both");
|
|
591
|
-
}
|
|
592
|
-
const taskId = hasTaskId
|
|
593
|
-
? (await readTaskForShow(workspaceRoot(args), args[2])).id
|
|
594
|
-
: null;
|
|
595
|
-
const brief = await buildCopilotBrief(workspaceRoot(args), {
|
|
596
|
-
taskId,
|
|
597
|
-
project: option(args, "--project", null),
|
|
598
|
-
includeOldTerminal: args.includes("--all"),
|
|
599
|
-
});
|
|
600
|
-
print(brief, args, (value) => {
|
|
601
|
-
const rows = value.tasks.map((task) => [
|
|
602
|
-
task.title,
|
|
603
|
-
task.project.name,
|
|
604
|
-
task.state,
|
|
605
|
-
task.attention?.kind ?? "-",
|
|
606
|
-
task.nextAction.kind,
|
|
607
|
-
displayId(task.id, false),
|
|
608
|
-
]);
|
|
609
|
-
return [
|
|
610
|
-
`Cached TaskChef brief (${value.scope})`,
|
|
611
|
-
table(["TITLE", "PROJECT", "STATE", "ATTENTION", "NEXT ACTION", "ID"], rows),
|
|
612
|
-
value.omittedTerminalCount > 0
|
|
613
|
-
? `Omitted old terminal tasks: ${value.omittedTerminalCount}`
|
|
614
|
-
: null,
|
|
615
|
-
].filter(Boolean).join("\n");
|
|
616
|
-
});
|
|
617
|
-
return 0;
|
|
618
|
-
}
|
|
619
|
-
|
|
620
|
-
function dashboardPort(args) {
|
|
621
|
-
const value = option(args, "--port", "3210");
|
|
622
|
-
if (!/^\d+$/.test(value)) throw new Error("--port must be an integer from 0 to 65535");
|
|
623
|
-
const port = Number(value);
|
|
624
|
-
if (port > 65_535) throw new Error("--port must be an integer from 0 to 65535");
|
|
625
|
-
return port;
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
async function dashboard(args) {
|
|
629
|
-
validateCommandArgs(args, 1, {
|
|
630
|
-
values: ["--port", "--workspace"],
|
|
631
|
-
switches: ["--json"],
|
|
632
|
-
});
|
|
633
|
-
const port = dashboardPort(args);
|
|
634
|
-
let server;
|
|
635
|
-
try {
|
|
636
|
-
server = await createDashboardServer({
|
|
637
|
-
workspace: workspaceRoot(args),
|
|
638
|
-
port,
|
|
639
|
-
});
|
|
640
|
-
} catch (error) {
|
|
641
|
-
if (error?.code === "EADDRINUSE") {
|
|
642
|
-
throw new Error(
|
|
643
|
-
`dashboard port 127.0.0.1:${port} is already in use; `
|
|
644
|
-
+ "stop the existing listener or choose another --port (TaskChef will not terminate it)",
|
|
645
|
-
);
|
|
646
|
-
}
|
|
647
|
-
throw error;
|
|
648
|
-
}
|
|
649
|
-
print({
|
|
650
|
-
schemaVersion: 1,
|
|
651
|
-
url: server.url,
|
|
652
|
-
workspace: server.monitor.workspace,
|
|
653
|
-
}, args, (value) => [
|
|
654
|
-
`TaskChef dashboard: ${value.url}`,
|
|
655
|
-
`Workspace: ${value.workspace}`,
|
|
656
|
-
"Press Ctrl+C to stop.",
|
|
657
|
-
].join("\n"));
|
|
658
|
-
|
|
659
|
-
let stop;
|
|
660
|
-
await new Promise((resolve) => {
|
|
661
|
-
stop = resolve;
|
|
662
|
-
process.once("SIGINT", stop);
|
|
663
|
-
process.once("SIGTERM", stop);
|
|
664
|
-
});
|
|
665
|
-
process.off("SIGINT", stop);
|
|
666
|
-
process.off("SIGTERM", stop);
|
|
667
|
-
await server.close();
|
|
668
|
-
return 0;
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
function usage() {
|
|
672
|
-
process.stdout.write(`TaskChef workspace utility
|
|
673
|
-
|
|
674
|
-
Usage:
|
|
675
|
-
taskchef help
|
|
676
|
-
taskchef dashboard [--port <number>] [--json] [--workspace <path>]
|
|
677
|
-
taskchef doctor [--json] [--workspace <path>]
|
|
678
|
-
taskchef workspace path [--json] [--workspace <path>]
|
|
679
|
-
taskchef workspace init [--register-codex] [--codex-cli <path>] [--json] [--workspace <path>]
|
|
680
|
-
taskchef workspace migrate [--json] [--workspace <path>]
|
|
681
|
-
taskchef project add <path> [--name <name>] [--description <text>] [--github-repo <url> ... | --no-github] [--json] [--workspace <path>]
|
|
682
|
-
taskchef project import [<file> | -] [--replace] [--json] [--workspace <path>]
|
|
683
|
-
taskchef project list [--allow-missing-paths] [--json] [--workspace <path>]
|
|
684
|
-
taskchef project update <name> [fields] [--dry-run] [--expect-config-hash <sha256>] [--json]
|
|
685
|
-
taskchef project remove <name> --dry-run [--json] [--workspace <path>]
|
|
686
|
-
taskchef backup list|create|verify|restore|prune [options]
|
|
687
|
-
taskchef dispatch prepare [--json] [--workspace <path>]
|
|
688
|
-
taskchef task record [--json] [--workspace <path>]
|
|
689
|
-
taskchef task brief [<task-id-or-8-character-prefix> | --project <name-or-path>] [--all] [--json] [--workspace <path>]
|
|
690
|
-
taskchef task show <task-id-or-8-character-prefix> [--json] [--workspace <path>]
|
|
691
|
-
taskchef task list [--project <name-or-path>] [--ascending] [--full-id] [--json] [--workspace <path>]
|
|
692
|
-
taskchef task summary [--json] [--workspace <path>]
|
|
693
|
-
|
|
694
|
-
Task record reads one JSON value from closed, non-interactive standard input.
|
|
695
|
-
Task show accepts a full task ID or the exact 8-character ID printed by task list.
|
|
696
|
-
Task show prints human-readable details by default; --json prints the complete task object.
|
|
697
|
-
Project import reads a JSON
|
|
698
|
-
array from a file, or from standard input when the source is '-' or omitted.
|
|
699
|
-
Workspace resolution precedence is --workspace, TASKCHEF_WORKSPACE, then
|
|
700
|
-
~/.agents/taskchef.
|
|
701
|
-
The foreground dashboard binds to 127.0.0.1 and creates one verified startup
|
|
702
|
-
snapshot before reading the canonical task log. The dispatcher MCP may reuse a
|
|
703
|
-
compatible foreground server on port 3210; neither mode terminates a listener
|
|
704
|
-
that already occupies its requested port.
|
|
705
|
-
`);
|
|
706
|
-
}
|
|
707
|
-
|
|
708
|
-
export async function runCli(args) {
|
|
709
|
-
if (args.length === 0 || args[0] === "help" || args[0] === "--help") {
|
|
710
|
-
usage();
|
|
711
|
-
return 0;
|
|
712
|
-
}
|
|
713
|
-
if (args[0] === "dashboard") return dashboard(args);
|
|
714
|
-
if (args[0] === "doctor") return doctor(args);
|
|
715
|
-
if (args[0] === "workspace" && args[1] === "path") return workspacePath(args);
|
|
716
|
-
if (args[0] === "workspace" && args[1] === "init") return initialize(args);
|
|
717
|
-
if (args[0] === "workspace" && args[1] === "migrate") return migrate(args);
|
|
718
|
-
if (args[0] === "project" && args[1] === "add") return projectAdd(args);
|
|
719
|
-
if (args[0] === "project" && args[1] === "import") return projectImport(args);
|
|
720
|
-
if (args[0] === "project" && args[1] === "list") return projectList(args);
|
|
721
|
-
if (args[0] === "project" && args[1] === "update") return projectUpdate(args);
|
|
722
|
-
if (args[0] === "project" && args[1] === "remove") return projectRemove(args);
|
|
723
|
-
if (args[0] === "backup") return backupCommand(args);
|
|
724
|
-
if (args[0] === "dispatch" && args[1] === "prepare") return dispatchPrepare(args);
|
|
725
|
-
if (args[0] === "task" && args[1] === "record") return taskRecord(args);
|
|
726
|
-
if (args[0] === "task" && args[1] === "brief") return taskBrief(args);
|
|
727
|
-
if (args[0] === "task" && args[1] === "show" && args[2]) return taskShow(args);
|
|
728
|
-
if (args[0] === "task" && args[1] === "list") return taskList(args);
|
|
729
|
-
if (args[0] === "task" && args[1] === "summary") return taskSummary(args);
|
|
730
|
-
process.stderr.write(`Unknown command: ${args.join(" ")}\n`);
|
|
731
|
-
usage();
|
|
732
|
-
return 2;
|
|
733
|
-
}
|