cofounder-crew 0.1.11 → 0.1.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +198 -263
- package/dist/src/cli.js +949 -175
- package/dist/src/cli.js.map +1 -1
- package/dist/src/codex.d.ts +3 -1
- package/dist/src/codex.js +9 -6
- package/dist/src/codex.js.map +1 -1
- package/dist/src/codexConfig.d.ts +6 -2
- package/dist/src/codexConfig.js +98 -19
- package/dist/src/codexConfig.js.map +1 -1
- package/dist/src/init.js +5 -0
- package/dist/src/init.js.map +1 -1
- package/dist/src/manage.d.ts +64 -0
- package/dist/src/manage.js +355 -0
- package/dist/src/manage.js.map +1 -0
- package/dist/src/memberRuntime.d.ts +2 -0
- package/dist/src/memberRuntime.js +48 -6
- package/dist/src/memberRuntime.js.map +1 -1
- package/dist/src/runtime.js +9 -2
- package/dist/src/runtime.js.map +1 -1
- package/dist/src/setup.js +6 -3
- package/dist/src/setup.js.map +1 -1
- package/dist/src/skills.d.ts +22 -0
- package/dist/src/skills.js +283 -0
- package/dist/src/skills.js.map +1 -0
- package/dist/src/templates.d.ts +1 -0
- package/dist/src/templates.js +27 -4
- package/dist/src/templates.js.map +1 -1
- package/dist/src/types.d.ts +11 -1
- package/docs/README.md +15 -0
- package/docs/cli.md +137 -0
- package/docs/configuration.md +200 -0
- package/docs/development.md +19 -0
- package/docs/examples.md +79 -0
- package/docs/faq.md +89 -0
- package/docs/prompts/bootstrap-codex.md +78 -0
- package/docs/releasing.md +23 -0
- package/docs/runtime.md +96 -0
- package/docs/updating.md +93 -0
- package/package.json +3 -2
package/dist/src/cli.js
CHANGED
|
@@ -1,160 +1,651 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { execFile } from "node:child_process";
|
|
3
|
+
import { createInterface } from "node:readline/promises";
|
|
4
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
5
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { promisify } from "node:util";
|
|
8
|
+
import { getMember, loadMemberSettings, loadProject } from "./config.js";
|
|
2
9
|
import { CofounderError } from "./errors.js";
|
|
3
10
|
import { initProject, syncProjectInstructions } from "./init.js";
|
|
11
|
+
import { addMcpServer, addMember, addSkill, assignMcpServer, assignSkill, listProjectMcpServers, listProjectSkills, removeMcpServer, removeMember, removeSkill, setContextMode, setMember } from "./manage.js";
|
|
4
12
|
import { startMcpServer } from "./mcp.js";
|
|
13
|
+
import { CONFIG_DIR, findProjectRoot, pathExists } from "./paths.js";
|
|
5
14
|
import { formatCodexSetup, installCodexMcp } from "./setup.js";
|
|
6
|
-
import { listProjectTemplates } from "./templates.js";
|
|
7
15
|
import { applyTaskPatch, cancelTask, delegateMember, formatLogEntry, formatTaskStatus, formatTeam, getCapabilities, getTask, interruptTask, listTeam, readTaskEventContent, readTaskLogs, readTaskPatch, readTaskResult, runMember, runWorkerTask } from "./runtime.js";
|
|
16
|
+
const execFileAsync = promisify(execFile);
|
|
17
|
+
const commands = [
|
|
18
|
+
{
|
|
19
|
+
path: ["help"],
|
|
20
|
+
summary: "Show help for Cofounder or a command.",
|
|
21
|
+
usage: "cofounder help [command]",
|
|
22
|
+
run: (args) => printHelp(args)
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
path: ["start"],
|
|
26
|
+
summary: "Run the project onboarding and health flow.",
|
|
27
|
+
usage: "cofounder start [--template <default|worktree>] [--setup-codex] [--yes]",
|
|
28
|
+
details: "Initializes missing project files, checks Codex/MCP/project state, and prints next steps.",
|
|
29
|
+
run: commandStart
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
path: ["doctor"],
|
|
33
|
+
summary: "Check local project setup.",
|
|
34
|
+
usage: "cofounder doctor [--json]",
|
|
35
|
+
run: commandDoctor
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
path: ["add"],
|
|
39
|
+
summary: "Interactive shortcut for adding teammates, MCP servers, or skills.",
|
|
40
|
+
usage: "cofounder add [member|mcp|skill]",
|
|
41
|
+
run: commandAdd
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
path: ["init"],
|
|
45
|
+
summary: "Create the .cofounder project skeleton.",
|
|
46
|
+
usage: "cofounder init [--template <default|worktree>] [--setup-codex] [--yes]",
|
|
47
|
+
run: commandInit
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
path: ["update"],
|
|
51
|
+
summary: "Update this project and repair Codex MCP.",
|
|
52
|
+
usage: "cofounder update [--yes] [--no-setup-codex]",
|
|
53
|
+
run: commandUpdate
|
|
54
|
+
},
|
|
55
|
+
{
|
|
56
|
+
path: ["pin"],
|
|
57
|
+
summary: "Pin Cofounder as a project-local dev dependency.",
|
|
58
|
+
usage: "cofounder pin [--yes]",
|
|
59
|
+
details: "Optional advanced mode for teams that want cofounder-crew recorded in this project's package.json.",
|
|
60
|
+
run: commandPin
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
path: ["self"],
|
|
64
|
+
summary: "Manage the user-level Cofounder installation.",
|
|
65
|
+
usage: "cofounder self <update>",
|
|
66
|
+
run: () => printNamespaceHelp(["self"])
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
path: ["self", "update"],
|
|
70
|
+
summary: "Update the globally installed cofounder command.",
|
|
71
|
+
usage: "cofounder self update",
|
|
72
|
+
run: commandSelfUpdate
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
path: ["setup", "codex"],
|
|
76
|
+
summary: "Print or install the Codex MCP entry.",
|
|
77
|
+
usage: "cofounder setup codex [--install]",
|
|
78
|
+
run: commandSetupCodex
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
path: ["team"],
|
|
82
|
+
summary: "Show the current team roster.",
|
|
83
|
+
usage: "cofounder team",
|
|
84
|
+
run: async () => console.log(formatTeam(await listTeam()))
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
path: ["member"],
|
|
88
|
+
summary: "Manage teammates.",
|
|
89
|
+
usage: "cofounder member <list|show|add|set|remove>",
|
|
90
|
+
run: () => printNamespaceHelp(["member"])
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
path: ["member", "list"],
|
|
94
|
+
summary: "List team members.",
|
|
95
|
+
usage: "cofounder member list",
|
|
96
|
+
run: commandMemberList
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
path: ["member", "show"],
|
|
100
|
+
summary: "Show one member.",
|
|
101
|
+
usage: "cofounder member show <member>",
|
|
102
|
+
run: commandMemberShow
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
path: ["member", "add"],
|
|
106
|
+
summary: "Add a teammate.",
|
|
107
|
+
usage: "cofounder member add [id] [--title <title>] [--model <model>] [--write-mode <direct|worktree>] [--responsibility <text>] [--can-call <ids>] [--yes]",
|
|
108
|
+
run: commandMemberAdd
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
path: ["member", "set"],
|
|
112
|
+
summary: "Edit model, sandbox, approval, write mode, MCP mode, or skill mode.",
|
|
113
|
+
usage: "cofounder member set <member> [--model <model>] [--reasoning <level>] [--sandbox <mode>] [--approval <policy>] [--write-mode <direct|worktree>] [--mcp-mode <mode>] [--skills-mode <mode>]",
|
|
114
|
+
run: commandMemberSet
|
|
115
|
+
},
|
|
116
|
+
{
|
|
117
|
+
path: ["member", "remove"],
|
|
118
|
+
summary: "Remove a teammate from the roster.",
|
|
119
|
+
usage: "cofounder member remove <member> [--delete-files] [--yes]",
|
|
120
|
+
run: commandMemberRemove
|
|
121
|
+
},
|
|
122
|
+
{
|
|
123
|
+
path: ["mcp", "list"],
|
|
124
|
+
summary: "List project-owned MCP servers and assignments.",
|
|
125
|
+
usage: "cofounder mcp list",
|
|
126
|
+
run: commandMcpList
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
path: ["mcp"],
|
|
130
|
+
summary: "Manage MCP servers and assignments.",
|
|
131
|
+
usage: "cofounder mcp <list|add|assign|remove>",
|
|
132
|
+
run: () => printNamespaceHelp(["mcp"])
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
path: ["mcp", "add"],
|
|
136
|
+
summary: "Add a project-owned MCP server.",
|
|
137
|
+
usage: "cofounder mcp add [id] [--url <url> | --command <cmd>] [--arg <arg>] [--cwd <cwd>] [--env KEY=VALUE] [--assign <members>] [--yes]",
|
|
138
|
+
run: commandMcpAdd
|
|
139
|
+
},
|
|
140
|
+
{
|
|
141
|
+
path: ["mcp", "assign"],
|
|
142
|
+
summary: "Assign an MCP server to teammates.",
|
|
143
|
+
usage: "cofounder mcp assign <server> <member[,member]> [--source <team|main>]",
|
|
144
|
+
run: commandMcpAssign
|
|
145
|
+
},
|
|
146
|
+
{
|
|
147
|
+
path: ["mcp", "remove"],
|
|
148
|
+
summary: "Remove a project-owned MCP server and all assignments.",
|
|
149
|
+
usage: "cofounder mcp remove <server> [--yes]",
|
|
150
|
+
run: commandMcpRemove
|
|
151
|
+
},
|
|
152
|
+
{
|
|
153
|
+
path: ["skill", "list"],
|
|
154
|
+
summary: "List project and team-only skills.",
|
|
155
|
+
usage: "cofounder skill list",
|
|
156
|
+
run: commandSkillList
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
path: ["skill"],
|
|
160
|
+
summary: "Manage skills and assignments.",
|
|
161
|
+
usage: "cofounder skill <list|add|assign|remove>",
|
|
162
|
+
run: () => printNamespaceHelp(["skill"])
|
|
163
|
+
},
|
|
164
|
+
{
|
|
165
|
+
path: ["skill", "add"],
|
|
166
|
+
summary: "Add or register a skill.",
|
|
167
|
+
usage: "cofounder skill add [id] [--scope <project|team|main>] [--description <text>] [--assign <members>] [--yes]",
|
|
168
|
+
run: commandSkillAdd
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
path: ["skill", "assign"],
|
|
172
|
+
summary: "Assign a skill to teammates.",
|
|
173
|
+
usage: "cofounder skill assign <skill> <member[,member]> [--scope <project|team|main>]",
|
|
174
|
+
run: commandSkillAssign
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
path: ["skill", "remove"],
|
|
178
|
+
summary: "Remove skill assignments, optionally deleting project/team skill files.",
|
|
179
|
+
usage: "cofounder skill remove <skill> [--scope <project|team|main>] [--delete-files] [--yes]",
|
|
180
|
+
run: commandSkillRemove
|
|
181
|
+
},
|
|
182
|
+
{
|
|
183
|
+
path: ["context", "show"],
|
|
184
|
+
summary: "Show worker project context mode and file.",
|
|
185
|
+
usage: "cofounder context show",
|
|
186
|
+
run: commandContextShow
|
|
187
|
+
},
|
|
188
|
+
{
|
|
189
|
+
path: ["context"],
|
|
190
|
+
summary: "Manage worker project context.",
|
|
191
|
+
usage: "cofounder context <show|sync|mode>",
|
|
192
|
+
run: () => printNamespaceHelp(["context"])
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
path: ["context", "sync"],
|
|
196
|
+
summary: "Refresh .cofounder/project.md from AGENTS.md.",
|
|
197
|
+
usage: "cofounder context sync",
|
|
198
|
+
run: commandContextSync
|
|
199
|
+
},
|
|
200
|
+
{
|
|
201
|
+
path: ["context", "mode"],
|
|
202
|
+
summary: "Switch worker project context mode.",
|
|
203
|
+
usage: "cofounder context mode <auto|manual>",
|
|
204
|
+
run: commandContextMode
|
|
205
|
+
},
|
|
206
|
+
{
|
|
207
|
+
path: ["task", "run"],
|
|
208
|
+
summary: "Run a member synchronously and stream output.",
|
|
209
|
+
usage: "cofounder task run <member> <task> [--caller <member>]",
|
|
210
|
+
run: commandTaskRun
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
path: ["task"],
|
|
214
|
+
summary: "Run and inspect Cofounder tasks.",
|
|
215
|
+
usage: "cofounder task <run|delegate|list|status|logs|watch|result|diff|apply|cancel|interrupt>",
|
|
216
|
+
run: () => printNamespaceHelp(["task"])
|
|
217
|
+
},
|
|
218
|
+
{
|
|
219
|
+
path: ["task", "delegate"],
|
|
220
|
+
summary: "Start an async member task.",
|
|
221
|
+
usage: "cofounder task delegate <member> <task> [--caller <member>]",
|
|
222
|
+
run: commandTaskDelegate
|
|
223
|
+
},
|
|
224
|
+
{
|
|
225
|
+
path: ["task", "list"],
|
|
226
|
+
summary: "List recent Cofounder tasks.",
|
|
227
|
+
usage: "cofounder task list [--limit <n>]",
|
|
228
|
+
run: commandTaskList
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
path: ["task", "status"],
|
|
232
|
+
summary: "Show task status.",
|
|
233
|
+
usage: "cofounder task status <task_id>",
|
|
234
|
+
run: async (args) => console.log(formatTaskStatus(await getTask(requiredArg(args[0], "task_id"))))
|
|
235
|
+
},
|
|
236
|
+
{
|
|
237
|
+
path: ["task", "logs"],
|
|
238
|
+
summary: "Show task logs.",
|
|
239
|
+
usage: "cofounder task logs <task_id> [--tail <n>]",
|
|
240
|
+
run: commandTaskLogs
|
|
241
|
+
},
|
|
242
|
+
{
|
|
243
|
+
path: ["task", "watch"],
|
|
244
|
+
summary: "Follow task events until it finishes.",
|
|
245
|
+
usage: "cofounder task watch <task_id>",
|
|
246
|
+
run: commandTaskWatch
|
|
247
|
+
},
|
|
248
|
+
{
|
|
249
|
+
path: ["task", "result"],
|
|
250
|
+
summary: "Print task result.",
|
|
251
|
+
usage: "cofounder task result <task_id>",
|
|
252
|
+
run: commandTaskResult
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
path: ["task", "diff"],
|
|
256
|
+
summary: "Print a worktree task patch.",
|
|
257
|
+
usage: "cofounder task diff <task_id>",
|
|
258
|
+
run: async (args) => process.stdout.write(await readTaskPatch(requiredArg(args[0], "task_id")))
|
|
259
|
+
},
|
|
260
|
+
{
|
|
261
|
+
path: ["task", "apply"],
|
|
262
|
+
summary: "Apply a worktree task patch to the main tree.",
|
|
263
|
+
usage: "cofounder task apply <task_id>",
|
|
264
|
+
run: commandTaskApply
|
|
265
|
+
},
|
|
266
|
+
{
|
|
267
|
+
path: ["task", "cancel"],
|
|
268
|
+
summary: "Cancel a running task.",
|
|
269
|
+
usage: "cofounder task cancel <task_id>",
|
|
270
|
+
run: async (args) => console.log(formatTaskStatus(await cancelTask(requiredArg(args[0], "task_id"))))
|
|
271
|
+
},
|
|
272
|
+
{
|
|
273
|
+
path: ["task", "interrupt"],
|
|
274
|
+
summary: "Cancel and resume a running task with steering instructions.",
|
|
275
|
+
usage: "cofounder task interrupt <task_id> <message>",
|
|
276
|
+
run: commandTaskInterrupt
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
path: ["capabilities"],
|
|
280
|
+
summary: "Print runner capabilities as JSON.",
|
|
281
|
+
usage: "cofounder capabilities",
|
|
282
|
+
run: () => console.log(JSON.stringify(getCapabilities(), null, 2))
|
|
283
|
+
},
|
|
284
|
+
{
|
|
285
|
+
path: ["serve", "mcp"],
|
|
286
|
+
summary: "Run the Cofounder MCP server over stdio.",
|
|
287
|
+
usage: "cofounder serve mcp",
|
|
288
|
+
hidden: true,
|
|
289
|
+
run: () => startMcpServer()
|
|
290
|
+
},
|
|
291
|
+
{
|
|
292
|
+
path: ["__worker"],
|
|
293
|
+
summary: "Run an internal task worker.",
|
|
294
|
+
usage: "cofounder __worker <task_id>",
|
|
295
|
+
hidden: true,
|
|
296
|
+
run: async (args) => { await runWorkerTask(requiredArg(args[0], "task_id")); }
|
|
297
|
+
}
|
|
298
|
+
];
|
|
8
299
|
async function main(argv = process.argv.slice(2)) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
return;
|
|
17
|
-
case "setup":
|
|
18
|
-
await commandSetup(args);
|
|
19
|
-
return;
|
|
20
|
-
case "sync":
|
|
21
|
-
await commandSync(args);
|
|
22
|
-
return;
|
|
23
|
-
case "team":
|
|
24
|
-
await commandTeam();
|
|
25
|
-
return;
|
|
26
|
-
case "run":
|
|
27
|
-
await commandRun(args);
|
|
28
|
-
return;
|
|
29
|
-
case "delegate":
|
|
30
|
-
await commandDelegate(args);
|
|
31
|
-
return;
|
|
32
|
-
case "status":
|
|
33
|
-
await commandStatus(args);
|
|
34
|
-
return;
|
|
35
|
-
case "logs":
|
|
36
|
-
await commandLogs(args);
|
|
37
|
-
return;
|
|
38
|
-
case "diff":
|
|
39
|
-
await commandDiff(args);
|
|
40
|
-
return;
|
|
41
|
-
case "apply":
|
|
42
|
-
await commandApply(args);
|
|
43
|
-
return;
|
|
44
|
-
case "watch":
|
|
45
|
-
await commandWatch(args);
|
|
46
|
-
return;
|
|
47
|
-
case "cancel":
|
|
48
|
-
await commandCancel(args);
|
|
49
|
-
return;
|
|
50
|
-
case "interrupt":
|
|
51
|
-
await commandInterrupt(args);
|
|
52
|
-
return;
|
|
53
|
-
case "capabilities":
|
|
54
|
-
commandCapabilities();
|
|
55
|
-
return;
|
|
56
|
-
case "mcp":
|
|
57
|
-
await startMcpServer();
|
|
58
|
-
return;
|
|
59
|
-
case "__worker":
|
|
60
|
-
await commandWorker(args);
|
|
61
|
-
return;
|
|
62
|
-
case "-h":
|
|
63
|
-
case "--help":
|
|
64
|
-
case undefined:
|
|
65
|
-
printHelp();
|
|
66
|
-
return;
|
|
67
|
-
default:
|
|
68
|
-
throw new CofounderError(`Unknown command: ${command}`);
|
|
300
|
+
if (argv.length === 0) {
|
|
301
|
+
await commandStart([]);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
const match = matchCommand(argv);
|
|
305
|
+
if (!match) {
|
|
306
|
+
throw new CofounderError(`Unknown command: ${argv.join(" ")}\n\nRun cofounder help to see available commands.`);
|
|
69
307
|
}
|
|
308
|
+
await match.command.run(match.args, { argv, commandPath: match.command.path });
|
|
70
309
|
}
|
|
71
|
-
async function
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
310
|
+
async function commandStart(args) {
|
|
311
|
+
const options = parseOptions(args);
|
|
312
|
+
const yes = options.flags.has("yes") || options.flags.has("y");
|
|
313
|
+
const interactive = process.stdin.isTTY && process.stdout.isTTY && !yes;
|
|
314
|
+
const root = process.cwd();
|
|
315
|
+
let template = options.values.template ?? "default";
|
|
316
|
+
let setupCodex = options.flags.has("setup-codex");
|
|
317
|
+
printHeader("Cofounder Start");
|
|
318
|
+
if (interactive) {
|
|
319
|
+
template = await choose("Template", ["default", "worktree"], template);
|
|
320
|
+
setupCodex = await confirm("Install or repair the Codex MCP entry now?", setupCodex);
|
|
75
321
|
}
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
console.log(`created ${item}`);
|
|
322
|
+
const hasProject = Boolean(await findProjectRoot(root));
|
|
323
|
+
if (!hasProject) {
|
|
324
|
+
const result = await initProject(root, { template });
|
|
325
|
+
printResult("Initialized project", result.created, result.skipped, result.notices);
|
|
81
326
|
}
|
|
82
|
-
|
|
83
|
-
|
|
327
|
+
else {
|
|
328
|
+
printInfo("Project already has .cofounder/team.yaml. Skipping init.");
|
|
84
329
|
}
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
console.log(notice);
|
|
330
|
+
if (setupCodex) {
|
|
331
|
+
const command = await installCodexMcp();
|
|
332
|
+
printInfo(`Installed Codex MCP: ${command}`);
|
|
89
333
|
}
|
|
334
|
+
await printDoctor({ json: false, compact: true });
|
|
335
|
+
printNextSteps(setupCodex);
|
|
336
|
+
}
|
|
337
|
+
async function commandDoctor(args) {
|
|
338
|
+
const options = parseOptions(args);
|
|
339
|
+
await printDoctor({ json: options.flags.has("json"), compact: false });
|
|
90
340
|
}
|
|
91
|
-
function
|
|
92
|
-
|
|
93
|
-
|
|
341
|
+
async function commandAdd(args) {
|
|
342
|
+
const kind = args[0] ?? await choose("Add", ["member", "mcp", "skill"], "member");
|
|
343
|
+
if (kind === "member") {
|
|
344
|
+
await commandMemberAdd(args.slice(1));
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
if (kind === "mcp") {
|
|
348
|
+
await commandMcpAdd(args.slice(1));
|
|
349
|
+
return;
|
|
94
350
|
}
|
|
351
|
+
if (kind === "skill") {
|
|
352
|
+
await commandSkillAdd(args.slice(1));
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
throw new CofounderError("add target must be member, mcp, or skill");
|
|
95
356
|
}
|
|
96
|
-
async function
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
357
|
+
async function commandInit(args) {
|
|
358
|
+
const options = parseOptions(args);
|
|
359
|
+
const template = options.values.template ?? "default";
|
|
360
|
+
const result = await initProject(process.cwd(), { template });
|
|
361
|
+
printResult(`Initialized ${result.template} template`, result.created, result.skipped, result.notices);
|
|
362
|
+
if (options.flags.has("setup-codex")) {
|
|
363
|
+
const command = await installCodexMcp();
|
|
364
|
+
printInfo(`Installed Codex MCP: ${command}`);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
async function commandUpdate(args) {
|
|
368
|
+
const options = parseOptions(args);
|
|
369
|
+
const yes = options.flags.has("yes") || options.flags.has("y");
|
|
370
|
+
let setupCodex = !options.flags.has("no-setup-codex");
|
|
371
|
+
if (options.flags.has("setup-codex")) {
|
|
372
|
+
setupCodex = true;
|
|
373
|
+
}
|
|
374
|
+
const projectRoot = await findProjectRoot(process.cwd()) ?? process.cwd();
|
|
375
|
+
printHeader("Update Cofounder");
|
|
376
|
+
const packageInfo = await readPackageInfo(projectRoot);
|
|
377
|
+
if (!packageInfo) {
|
|
378
|
+
printInfo("No package.json found. Leaving project dependencies unchanged.");
|
|
100
379
|
}
|
|
101
|
-
|
|
380
|
+
else {
|
|
381
|
+
const dependencyType = getCofounderDependencyType(packageInfo.data);
|
|
382
|
+
if (dependencyType) {
|
|
383
|
+
printInfo(`Project has a local cofounder-crew pin in ${dependencyType}. Run cofounder pin --yes to update it.`);
|
|
384
|
+
}
|
|
385
|
+
else {
|
|
386
|
+
printInfo("No project-local cofounder-crew pin. Global Cofounder is the default runtime.");
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
if (setupCodex) {
|
|
390
|
+
if (!yes && process.stdin.isTTY && process.stdout.isTTY) {
|
|
391
|
+
setupCodex = await confirm("Install or repair the Codex MCP entry?", true);
|
|
392
|
+
}
|
|
393
|
+
if (setupCodex) {
|
|
394
|
+
const command = await installCodexMcp();
|
|
395
|
+
printInfo(`Installed Codex MCP: ${command}`);
|
|
396
|
+
}
|
|
397
|
+
else {
|
|
398
|
+
printInfo("Codex MCP unchanged.");
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
else {
|
|
402
|
+
printInfo("Codex MCP unchanged because --no-setup-codex was passed.");
|
|
403
|
+
}
|
|
404
|
+
console.log("");
|
|
405
|
+
printInfo("Cofounder update does not overwrite .cofounder/, member settings, memory, or AGENTS.md.");
|
|
406
|
+
await printDoctor({ json: false, compact: true });
|
|
407
|
+
}
|
|
408
|
+
async function commandPin(args) {
|
|
409
|
+
const options = parseOptions(args);
|
|
410
|
+
const yes = options.flags.has("yes") || options.flags.has("y");
|
|
411
|
+
const projectRoot = await findProjectRoot(process.cwd()) ?? process.cwd();
|
|
412
|
+
const packageInfo = await readPackageInfo(projectRoot);
|
|
413
|
+
if (!packageInfo) {
|
|
414
|
+
throw new CofounderError("cofounder pin requires package.json. Run it from a Node project or use global Cofounder without pinning.");
|
|
415
|
+
}
|
|
416
|
+
const dependencyType = getCofounderDependencyType(packageInfo.data);
|
|
417
|
+
const npmArgs = dependencyType === "dependencies"
|
|
418
|
+
? ["install", "cofounder-crew@latest"]
|
|
419
|
+
: ["install", "--save-dev", "cofounder-crew@latest"];
|
|
420
|
+
if (!yes) {
|
|
421
|
+
await confirmUnlessYes(options, dependencyType
|
|
422
|
+
? `Update project-local cofounder-crew pin in ${dependencyType}?`
|
|
423
|
+
: "Add cofounder-crew@latest as a project-local dev dependency?");
|
|
424
|
+
}
|
|
425
|
+
printHeader("Pin Cofounder");
|
|
426
|
+
printInfo(`Running npm ${npmArgs.join(" ")}`);
|
|
427
|
+
await runLoggedCommand("npm", npmArgs, projectRoot);
|
|
428
|
+
printInfo("Pinned project-local cofounder-crew. Daily use can still go through the global cofounder command.");
|
|
429
|
+
}
|
|
430
|
+
async function commandSelfUpdate(_args) {
|
|
431
|
+
printHeader("Update Cofounder CLI");
|
|
432
|
+
printInfo("Running npm install -g cofounder-crew@latest");
|
|
433
|
+
await runLoggedCommand("npm", ["install", "-g", "cofounder-crew@latest"], process.cwd());
|
|
434
|
+
printInfo("Updated global cofounder command.");
|
|
435
|
+
}
|
|
436
|
+
async function commandSetupCodex(args) {
|
|
437
|
+
const options = parseOptions(args);
|
|
438
|
+
if (options.flags.has("install")) {
|
|
102
439
|
const command = await installCodexMcp();
|
|
103
440
|
console.log(`installed ${command}`);
|
|
104
441
|
return;
|
|
105
442
|
}
|
|
106
443
|
console.log(formatCodexSetup());
|
|
107
444
|
}
|
|
108
|
-
async function
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
|
|
445
|
+
async function commandMemberList() {
|
|
446
|
+
const team = await listTeam();
|
|
447
|
+
for (const member of team.members) {
|
|
448
|
+
console.log(`${member.id.padEnd(12)} ${member.title}`);
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
async function commandMemberShow(args) {
|
|
452
|
+
const memberId = requiredArg(args[0], "member");
|
|
453
|
+
const project = await loadProject();
|
|
454
|
+
const member = getMember(project, memberId);
|
|
455
|
+
const settingsPath = path.join(project.configRoot, member.settings);
|
|
456
|
+
const promptPath = path.join(project.configRoot, member.prompt);
|
|
457
|
+
printHeader(`${member.id}: ${member.title}`);
|
|
458
|
+
console.log(`runner: ${member.runner}`);
|
|
459
|
+
console.log(`prompt: ${path.relative(project.projectRoot, promptPath)}`);
|
|
460
|
+
console.log(`settings: ${path.relative(project.projectRoot, settingsPath)}`);
|
|
461
|
+
console.log(`home: ${member.home ? path.join(CONFIG_DIR, member.home) : "none"}`);
|
|
462
|
+
console.log(`can_call: ${member.can_call.length ? member.can_call.join(", ") : "none"}`);
|
|
463
|
+
console.log("responsibilities:");
|
|
464
|
+
for (const item of member.responsibilities)
|
|
465
|
+
console.log(` - ${item}`);
|
|
466
|
+
console.log("");
|
|
467
|
+
console.log((await readFile(settingsPath, "utf8")).trim());
|
|
468
|
+
}
|
|
469
|
+
async function commandMemberAdd(args) {
|
|
470
|
+
const options = parseOptions(args);
|
|
471
|
+
const id = options.positionals[0] ?? await askRequired("Member id");
|
|
472
|
+
const result = await addMember(process.cwd(), {
|
|
473
|
+
id,
|
|
474
|
+
title: options.values.title,
|
|
475
|
+
model: options.values.model,
|
|
476
|
+
reasoning_effort: options.values.reasoning,
|
|
477
|
+
sandbox: options.values.sandbox,
|
|
478
|
+
approval: options.values.approval,
|
|
479
|
+
write_mode: options.values["write-mode"],
|
|
480
|
+
responsibilities: valuesOrList(options.repeated.responsibility),
|
|
481
|
+
can_call: csv(options.values["can-call"])
|
|
482
|
+
});
|
|
483
|
+
printChangeResult(result);
|
|
484
|
+
}
|
|
485
|
+
async function commandMemberSet(args) {
|
|
486
|
+
const options = parseOptions(args);
|
|
487
|
+
const memberId = requiredArg(options.positionals[0], "member");
|
|
488
|
+
const result = await setMember(process.cwd(), memberId, {
|
|
489
|
+
model: options.values.model,
|
|
490
|
+
reasoning_effort: options.values.reasoning,
|
|
491
|
+
sandbox: options.values.sandbox,
|
|
492
|
+
approval: options.values.approval,
|
|
493
|
+
write_mode: options.values["write-mode"],
|
|
494
|
+
mcp_mode: options.values["mcp-mode"],
|
|
495
|
+
skills_mode: options.values["skills-mode"]
|
|
496
|
+
});
|
|
497
|
+
printChangeResult(result);
|
|
498
|
+
}
|
|
499
|
+
async function commandMemberRemove(args) {
|
|
500
|
+
const options = parseOptions(args);
|
|
501
|
+
const memberId = requiredArg(options.positionals[0], "member");
|
|
502
|
+
await confirmUnlessYes(options, `Remove member ${memberId}?`);
|
|
503
|
+
const result = await removeMember(process.cwd(), memberId, { deleteFiles: options.flags.has("delete-files") });
|
|
504
|
+
printChangeResult(result);
|
|
505
|
+
}
|
|
506
|
+
async function commandMcpList() {
|
|
507
|
+
const project = await loadProject();
|
|
508
|
+
const servers = await listProjectMcpServers(process.cwd());
|
|
509
|
+
printHeader("Project MCP Servers");
|
|
510
|
+
console.log(servers.length ? servers.map((server) => `- ${server}`).join("\n") : "none");
|
|
511
|
+
console.log("");
|
|
512
|
+
printHeader("Assignments");
|
|
513
|
+
for (const member of Object.values(project.team.members)) {
|
|
514
|
+
const settings = await loadMemberSettings(project, member);
|
|
515
|
+
const team = settings.mcp?.team ?? [];
|
|
516
|
+
const main = settings.mcp?.from_main ?? [];
|
|
517
|
+
console.log(`${member.id}: team=[${team.join(", ")}] main=[${main.join(", ")}] mode=${settings.mcp?.mode ?? "inherit"}`);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
async function commandMcpAdd(args) {
|
|
521
|
+
const options = parseOptions(args);
|
|
522
|
+
const id = options.positionals[0] ?? await askRequired("MCP server id");
|
|
523
|
+
const url = options.values.url ?? (!options.values.command ? await askOptional("MCP URL (leave empty for command transport)") : undefined);
|
|
524
|
+
const command = options.values.command ?? (!url ? await askRequired("Command") : undefined);
|
|
525
|
+
const result = await addMcpServer(process.cwd(), {
|
|
526
|
+
id,
|
|
527
|
+
url: url || undefined,
|
|
528
|
+
command,
|
|
529
|
+
args: options.repeated.arg ?? [],
|
|
530
|
+
cwd: options.values.cwd,
|
|
531
|
+
env: parseEnv(options.repeated.env ?? []),
|
|
532
|
+
assign: csv(options.values.assign)
|
|
533
|
+
});
|
|
534
|
+
printChangeResult(result);
|
|
535
|
+
}
|
|
536
|
+
async function commandMcpAssign(args) {
|
|
537
|
+
const options = parseOptions(args);
|
|
538
|
+
const server = requiredArg(options.positionals[0], "server");
|
|
539
|
+
const members = csv(requiredArg(options.positionals[1], "member[,member]"));
|
|
540
|
+
const source = (options.values.source ?? "team");
|
|
541
|
+
const result = await assignMcpServer(process.cwd(), server, source, members);
|
|
542
|
+
printChangeResult(result);
|
|
543
|
+
}
|
|
544
|
+
async function commandMcpRemove(args) {
|
|
545
|
+
const options = parseOptions(args);
|
|
546
|
+
const server = requiredArg(options.positionals[0], "server");
|
|
547
|
+
await confirmUnlessYes(options, `Remove MCP ${server}?`);
|
|
548
|
+
printChangeResult(await removeMcpServer(process.cwd(), server));
|
|
549
|
+
}
|
|
550
|
+
async function commandSkillList() {
|
|
551
|
+
printHeader("Project Skills (.agents/skills)");
|
|
552
|
+
const projectSkills = await listProjectSkills(process.cwd(), "project");
|
|
553
|
+
console.log(projectSkills.length ? projectSkills.map((skill) => `- ${skill}`).join("\n") : "none");
|
|
554
|
+
console.log("");
|
|
555
|
+
printHeader("Team-Only Skills (.cofounder/skills)");
|
|
556
|
+
const teamSkills = await listProjectSkills(process.cwd(), "team");
|
|
557
|
+
console.log(teamSkills.length ? teamSkills.map((skill) => `- ${skill}`).join("\n") : "none");
|
|
558
|
+
}
|
|
559
|
+
async function commandSkillAdd(args) {
|
|
560
|
+
const options = parseOptions(args);
|
|
561
|
+
const id = options.positionals[0] ?? await askRequired("Skill id");
|
|
562
|
+
const source = normalizeSkillSource(options.values.scope ?? await choose("Skill scope", ["project", "team", "main"], "project"));
|
|
563
|
+
const result = await addSkill(process.cwd(), {
|
|
564
|
+
id,
|
|
565
|
+
source,
|
|
566
|
+
description: options.values.description,
|
|
567
|
+
assign: csv(options.values.assign)
|
|
568
|
+
});
|
|
569
|
+
printChangeResult(result);
|
|
570
|
+
}
|
|
571
|
+
async function commandSkillAssign(args) {
|
|
572
|
+
const options = parseOptions(args);
|
|
573
|
+
const skill = requiredArg(options.positionals[0], "skill");
|
|
574
|
+
const members = csv(requiredArg(options.positionals[1], "member[,member]"));
|
|
575
|
+
const source = normalizeSkillSource(options.values.scope ?? "project");
|
|
576
|
+
printChangeResult(await assignSkill(process.cwd(), skill, source, members));
|
|
577
|
+
}
|
|
578
|
+
async function commandSkillRemove(args) {
|
|
579
|
+
const options = parseOptions(args);
|
|
580
|
+
const skill = requiredArg(options.positionals[0], "skill");
|
|
581
|
+
const source = normalizeSkillSource(options.values.scope ?? "project");
|
|
582
|
+
await confirmUnlessYes(options, `Remove skill ${skill} assignments?`);
|
|
583
|
+
printChangeResult(await removeSkill(process.cwd(), skill, source, { deleteFiles: options.flags.has("delete-files") }));
|
|
584
|
+
}
|
|
585
|
+
async function commandContextShow() {
|
|
586
|
+
const project = await loadProject();
|
|
587
|
+
console.log(`mode: ${project.team.project_context.mode}`);
|
|
588
|
+
console.log(`file: .cofounder/${project.team.project_context.file}`);
|
|
589
|
+
const contextPath = path.join(project.configRoot, project.team.project_context.file);
|
|
590
|
+
if (await pathExists(contextPath)) {
|
|
591
|
+
console.log("");
|
|
592
|
+
console.log((await readFile(contextPath, "utf8")).trim());
|
|
112
593
|
}
|
|
594
|
+
}
|
|
595
|
+
async function commandContextSync() {
|
|
113
596
|
const result = await syncProjectInstructions();
|
|
114
597
|
console.log(`updated ${result.path}`);
|
|
115
598
|
console.log(`source ${result.source}`);
|
|
116
599
|
console.log(`derived ${result.derived ? "yes" : "no"}`);
|
|
117
600
|
}
|
|
118
|
-
async function
|
|
119
|
-
|
|
601
|
+
async function commandContextMode(args) {
|
|
602
|
+
const mode = requiredArg(args[0], "auto|manual");
|
|
603
|
+
if (mode !== "auto" && mode !== "manual") {
|
|
604
|
+
throw new CofounderError("context mode must be auto or manual");
|
|
605
|
+
}
|
|
606
|
+
printChangeResult(await setContextMode(process.cwd(), mode));
|
|
120
607
|
}
|
|
121
|
-
async function
|
|
122
|
-
const { memberId, task, caller } = parseMemberTask(args
|
|
608
|
+
async function commandTaskRun(args) {
|
|
609
|
+
const { memberId, task, caller } = parseMemberTask(args);
|
|
123
610
|
const finalRecord = await runMember(memberId, task, { caller, streamToConsole: true });
|
|
124
611
|
await printTaskResult(finalRecord.id);
|
|
125
612
|
}
|
|
126
|
-
async function
|
|
127
|
-
const { memberId, task, caller } = parseMemberTask(args
|
|
613
|
+
async function commandTaskDelegate(args) {
|
|
614
|
+
const { memberId, task, caller } = parseMemberTask(args);
|
|
128
615
|
const record = await delegateMember(memberId, task, { caller });
|
|
129
616
|
console.log(record.id);
|
|
130
617
|
}
|
|
131
|
-
async function
|
|
132
|
-
const
|
|
133
|
-
|
|
618
|
+
async function commandTaskList(args) {
|
|
619
|
+
const options = parseOptions(args);
|
|
620
|
+
const projectRoot = await findProjectRoot(process.cwd());
|
|
621
|
+
if (!projectRoot)
|
|
622
|
+
throw new CofounderError("Missing .cofounder/team.yaml");
|
|
623
|
+
const runsDir = path.join(projectRoot, ".cofounder", "runs");
|
|
624
|
+
if (!(await pathExists(runsDir))) {
|
|
625
|
+
console.log("No tasks yet.");
|
|
626
|
+
return;
|
|
627
|
+
}
|
|
628
|
+
const limit = Number(options.values.limit ?? "20");
|
|
629
|
+
const tasks = (await readdir(runsDir, { withFileTypes: true }))
|
|
630
|
+
.filter((entry) => entry.isDirectory())
|
|
631
|
+
.map((entry) => entry.name)
|
|
632
|
+
.sort()
|
|
633
|
+
.reverse()
|
|
634
|
+
.slice(0, limit);
|
|
635
|
+
for (const taskId of tasks) {
|
|
636
|
+
const task = await getTask(taskId, projectRoot);
|
|
637
|
+
console.log(`${task.id} ${task.status} ${task.assignee} ${task.created_at}`);
|
|
638
|
+
}
|
|
134
639
|
}
|
|
135
|
-
async function
|
|
136
|
-
const
|
|
137
|
-
const
|
|
640
|
+
async function commandTaskLogs(args) {
|
|
641
|
+
const options = parseOptions(args);
|
|
642
|
+
const taskId = requiredArg(options.positionals[0], "task_id");
|
|
643
|
+
const tail = Number(options.values.tail ?? "50");
|
|
138
644
|
const entries = await readTaskLogs(taskId, { tail });
|
|
139
|
-
for (const entry of entries)
|
|
645
|
+
for (const entry of entries)
|
|
140
646
|
console.log(formatLogEntry(entry));
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
async function commandDiff(args) {
|
|
144
|
-
const taskId = requiredArg(args[0], "task_id");
|
|
145
|
-
const patch = await readTaskPatch(taskId);
|
|
146
|
-
process.stdout.write(patch);
|
|
147
647
|
}
|
|
148
|
-
async function
|
|
149
|
-
const taskId = requiredArg(args[0], "task_id");
|
|
150
|
-
const result = await applyTaskPatch(taskId);
|
|
151
|
-
console.log(`applied ${result.files.length} file(s) from ${taskId}`);
|
|
152
|
-
console.log(`patch: ${result.patch_path}`);
|
|
153
|
-
if (result.files.length > 0) {
|
|
154
|
-
console.log(`files: ${result.files.join(", ")}`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
async function commandWatch(args) {
|
|
648
|
+
async function commandTaskWatch(args) {
|
|
158
649
|
const taskId = requiredArg(args[0], "task_id");
|
|
159
650
|
let offset = 0;
|
|
160
651
|
while (true) {
|
|
@@ -162,101 +653,384 @@ async function commandWatch(args) {
|
|
|
162
653
|
const content = await readTaskEventContent(taskId);
|
|
163
654
|
const next = content.slice(offset);
|
|
164
655
|
offset = content.length;
|
|
165
|
-
for (const line of next.split("\n").filter(Boolean))
|
|
656
|
+
for (const line of next.split("\n").filter(Boolean))
|
|
166
657
|
console.log(formatLogEntry(JSON.parse(line)));
|
|
167
|
-
|
|
168
|
-
if (["succeeded", "failed", "cancelled"].includes(task.status)) {
|
|
658
|
+
if (["succeeded", "failed", "cancelled"].includes(task.status))
|
|
169
659
|
break;
|
|
170
|
-
}
|
|
171
660
|
await sleep(750);
|
|
172
661
|
}
|
|
173
662
|
}
|
|
174
|
-
async function
|
|
663
|
+
async function commandTaskResult(args) {
|
|
664
|
+
const result = await readTaskResult(requiredArg(args[0], "task_id"));
|
|
665
|
+
console.log(result.trim());
|
|
666
|
+
}
|
|
667
|
+
async function commandTaskApply(args) {
|
|
175
668
|
const taskId = requiredArg(args[0], "task_id");
|
|
176
|
-
|
|
669
|
+
const result = await applyTaskPatch(taskId);
|
|
670
|
+
console.log(`applied ${result.files.length} file(s) from ${taskId}`);
|
|
671
|
+
console.log(`patch: ${result.patch_path}`);
|
|
672
|
+
if (result.files.length > 0)
|
|
673
|
+
console.log(`files: ${result.files.join(", ")}`);
|
|
177
674
|
}
|
|
178
|
-
async function
|
|
675
|
+
async function commandTaskInterrupt(args) {
|
|
179
676
|
const taskId = requiredArg(args[0], "task_id");
|
|
180
677
|
const message = args.slice(1).join(" ").trim();
|
|
181
|
-
if (!message)
|
|
678
|
+
if (!message)
|
|
182
679
|
throw new CofounderError("Missing interrupt message");
|
|
183
|
-
}
|
|
184
680
|
const record = await interruptTask(taskId, message);
|
|
185
681
|
console.log(record.id);
|
|
186
682
|
}
|
|
187
|
-
function commandCapabilities() {
|
|
188
|
-
console.log(JSON.stringify(getCapabilities(), null, 2));
|
|
189
|
-
}
|
|
190
|
-
async function commandWorker(args) {
|
|
191
|
-
const taskId = requiredArg(args[0], "task_id");
|
|
192
|
-
await runWorkerTask(taskId);
|
|
193
|
-
}
|
|
194
683
|
async function printTaskResult(taskId) {
|
|
195
684
|
const result = await readTaskResult(taskId);
|
|
196
685
|
if (result.trim().length > 0) {
|
|
197
|
-
console.log("
|
|
686
|
+
console.log("");
|
|
687
|
+
printHeader("Result");
|
|
198
688
|
console.log(result.trim());
|
|
199
689
|
}
|
|
200
690
|
}
|
|
201
|
-
function
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
691
|
+
async function printDoctor(options) {
|
|
692
|
+
const checks = await collectDoctorChecks();
|
|
693
|
+
if (options.json) {
|
|
694
|
+
console.log(JSON.stringify(checks, null, 2));
|
|
695
|
+
return;
|
|
696
|
+
}
|
|
697
|
+
printHeader("Doctor");
|
|
698
|
+
for (const check of checks) {
|
|
699
|
+
console.log(`${check.ok ? "OK " : "ERR"} ${check.name}${check.detail ? ` - ${check.detail}` : ""}`);
|
|
700
|
+
}
|
|
701
|
+
if (!options.compact) {
|
|
702
|
+
const failing = checks.filter((check) => !check.ok);
|
|
703
|
+
if (failing.length > 0) {
|
|
704
|
+
console.log("");
|
|
705
|
+
console.log("Fix failing checks, then run cofounder doctor again.");
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
async function collectDoctorChecks() {
|
|
710
|
+
const checks = [];
|
|
711
|
+
const nodeMajor = Number(process.versions.node.split(".")[0]);
|
|
712
|
+
checks.push({ name: "Node.js >=22", ok: nodeMajor >= 22, detail: process.version });
|
|
713
|
+
checks.push(await commandCheck("npm", ["--version"], "npm available"));
|
|
714
|
+
checks.push(await commandCheck("codex", ["--version"], "Codex CLI available"));
|
|
715
|
+
const root = await findProjectRoot(process.cwd());
|
|
716
|
+
checks.push({ name: ".cofounder/team.yaml", ok: Boolean(root), detail: root ?? "not found" });
|
|
717
|
+
const projectRoot = root ?? process.cwd();
|
|
718
|
+
checks.push({ name: "AGENTS.md", ok: await pathExists(path.join(projectRoot, "AGENTS.md")) });
|
|
719
|
+
if (await pathExists(path.join(projectRoot, "AGENTS.md"))) {
|
|
720
|
+
const agents = await readFile(path.join(projectRoot, "AGENTS.md"), "utf8");
|
|
721
|
+
checks.push({ name: "Cofounder AGENTS bridge", ok: agents.includes("Cofounder Crew") && agents.includes("Cofounder/orchestrator") });
|
|
722
|
+
}
|
|
723
|
+
checks.push(await commandCheck("git", ["rev-parse", "--is-inside-work-tree"], "Git repository"));
|
|
724
|
+
checks.push(await commandCheck("git", ["rev-parse", "--verify", "HEAD"], "Git HEAD exists"));
|
|
725
|
+
checks.push(await codexMcpCheck());
|
|
726
|
+
if (root) {
|
|
727
|
+
try {
|
|
728
|
+
const project = await loadProject(root);
|
|
729
|
+
checks.push({ name: "Team config loads", ok: true, detail: `${Object.keys(project.team.members).length} members` });
|
|
730
|
+
const ignorePath = path.join(project.configRoot, ".gitignore");
|
|
731
|
+
const ignore = await pathExists(ignorePath) ? await readFile(ignorePath, "utf8") : "";
|
|
732
|
+
for (const entry of ["runs/", "worktrees/", "members/*/home/"]) {
|
|
733
|
+
checks.push({ name: `.cofounder/.gitignore ${entry}`, ok: ignore.includes(entry) });
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
catch (error) {
|
|
737
|
+
checks.push({ name: "Team config loads", ok: false, detail: error instanceof Error ? error.message : String(error) });
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
return checks;
|
|
741
|
+
}
|
|
742
|
+
async function readPackageInfo(projectRoot) {
|
|
743
|
+
const packagePath = path.join(projectRoot, "package.json");
|
|
744
|
+
if (!(await pathExists(packagePath))) {
|
|
745
|
+
return null;
|
|
746
|
+
}
|
|
747
|
+
try {
|
|
748
|
+
const data = JSON.parse(await readFile(packagePath, "utf8"));
|
|
749
|
+
return { path: packagePath, data };
|
|
750
|
+
}
|
|
751
|
+
catch (error) {
|
|
752
|
+
throw new CofounderError(`Could not read package.json: ${error instanceof Error ? error.message : String(error)}`);
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
function getCofounderDependencyType(packageJson) {
|
|
756
|
+
if (hasPackageDependency(packageJson.devDependencies)) {
|
|
757
|
+
return "devDependencies";
|
|
758
|
+
}
|
|
759
|
+
if (hasPackageDependency(packageJson.dependencies)) {
|
|
760
|
+
return "dependencies";
|
|
761
|
+
}
|
|
762
|
+
return null;
|
|
763
|
+
}
|
|
764
|
+
function hasPackageDependency(value) {
|
|
765
|
+
return typeof value === "object" && value !== null && Object.prototype.hasOwnProperty.call(value, "cofounder-crew");
|
|
766
|
+
}
|
|
767
|
+
async function runLoggedCommand(command, args, cwd) {
|
|
768
|
+
try {
|
|
769
|
+
const { stdout, stderr } = await execFileAsync(command, args, {
|
|
770
|
+
cwd,
|
|
771
|
+
env: process.env,
|
|
772
|
+
maxBuffer: 20 * 1024 * 1024
|
|
773
|
+
});
|
|
774
|
+
if (stdout.trim()) {
|
|
775
|
+
console.log(stdout.trim());
|
|
776
|
+
}
|
|
777
|
+
if (stderr.trim()) {
|
|
778
|
+
console.error(stderr.trim());
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
catch (error) {
|
|
782
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
783
|
+
throw new CofounderError(`${command} ${args.join(" ")} failed: ${message}`);
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
async function commandCheck(command, args, name) {
|
|
787
|
+
try {
|
|
788
|
+
const { stdout } = await execFileAsync(command, args, { timeout: 5_000 });
|
|
789
|
+
return { name, ok: true, detail: stdout.trim().split("\n")[0] };
|
|
790
|
+
}
|
|
791
|
+
catch (error) {
|
|
792
|
+
return { name, ok: false, detail: error instanceof Error ? error.message : String(error) };
|
|
793
|
+
}
|
|
794
|
+
}
|
|
795
|
+
async function codexMcpCheck() {
|
|
796
|
+
try {
|
|
797
|
+
const { stdout } = await execFileAsync("codex", ["mcp", "get", "cofounder"], { timeout: 5_000 });
|
|
798
|
+
const normalized = stdout.replace(/\s+/g, " ");
|
|
799
|
+
const ok = normalized.includes("cofounder serve mcp");
|
|
800
|
+
return {
|
|
801
|
+
name: "Codex MCP cofounder",
|
|
802
|
+
ok,
|
|
803
|
+
detail: ok ? "cofounder serve mcp" : "run cofounder setup codex --install"
|
|
804
|
+
};
|
|
805
|
+
}
|
|
806
|
+
catch (error) {
|
|
807
|
+
return { name: "Codex MCP cofounder", ok: false, detail: error instanceof Error ? error.message : String(error) };
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
function parseMemberTask(args) {
|
|
811
|
+
const options = parseOptions(args);
|
|
812
|
+
const memberId = requiredArg(options.positionals[0], "member");
|
|
813
|
+
const task = options.positionals.slice(1).join(" ").trim();
|
|
814
|
+
if (!task)
|
|
207
815
|
throw new CofounderError("Missing task text");
|
|
816
|
+
return { memberId, task, caller: options.values.caller ?? "lead" };
|
|
817
|
+
}
|
|
818
|
+
function matchCommand(argv) {
|
|
819
|
+
const matches = commands
|
|
820
|
+
.filter((command) => command.path.every((part, index) => argv[index] === part))
|
|
821
|
+
.sort((a, b) => b.path.length - a.path.length);
|
|
822
|
+
const match = matches[0];
|
|
823
|
+
return match ? { command: match, args: argv.slice(match.path.length) } : null;
|
|
824
|
+
}
|
|
825
|
+
function printHelp(args = []) {
|
|
826
|
+
if (args.length > 0) {
|
|
827
|
+
const match = matchCommand(args);
|
|
828
|
+
if (!match)
|
|
829
|
+
throw new CofounderError(`Unknown help topic: ${args.join(" ")}`);
|
|
830
|
+
const hasChildren = commands.some((command) => isChildCommand(command.path, match.command.path));
|
|
831
|
+
if (hasChildren && args.length === match.command.path.length) {
|
|
832
|
+
printNamespaceHelp(match.command.path);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
printCommandHelp(match.command);
|
|
836
|
+
return;
|
|
837
|
+
}
|
|
838
|
+
printHeader("Cofounder Crew");
|
|
839
|
+
console.log("Conversation-first local AI teams for Codex.");
|
|
840
|
+
console.log("");
|
|
841
|
+
console.log("Usage:");
|
|
842
|
+
console.log(" cofounder start");
|
|
843
|
+
console.log(" cofounder help <command>");
|
|
844
|
+
console.log("");
|
|
845
|
+
printCommandGroup("Setup", ["start", "doctor", "init", "update", "pin", "self update", "setup codex"]);
|
|
846
|
+
printCommandGroup("Team", ["team", "add", "member list", "member add", "member set", "mcp add", "skill add", "context show"]);
|
|
847
|
+
printCommandGroup("Tasks", ["task delegate", "task run", "task list", "task status", "task logs", "task diff", "task apply"]);
|
|
848
|
+
}
|
|
849
|
+
function printNamespaceHelp(prefix) {
|
|
850
|
+
const namespace = commands.find((command) => samePath(command.path, prefix));
|
|
851
|
+
if (namespace) {
|
|
852
|
+
printCommandHelp(namespace);
|
|
853
|
+
console.log("");
|
|
854
|
+
}
|
|
855
|
+
else {
|
|
856
|
+
printHeader(prefix.join(" "));
|
|
857
|
+
}
|
|
858
|
+
const children = commands
|
|
859
|
+
.filter((command) => isChildCommand(command.path, prefix) && !command.hidden)
|
|
860
|
+
.sort((a, b) => a.path.join(" ").localeCompare(b.path.join(" ")));
|
|
861
|
+
if (children.length > 0) {
|
|
862
|
+
console.log("Commands:");
|
|
863
|
+
for (const command of children) {
|
|
864
|
+
console.log(` ${`cofounder ${command.path.join(" ")}`.padEnd(30)} ${command.summary}`);
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
function printCommandHelp(command) {
|
|
869
|
+
printHeader(command.path.join(" "));
|
|
870
|
+
console.log(command.summary);
|
|
871
|
+
console.log("");
|
|
872
|
+
console.log("Usage:");
|
|
873
|
+
console.log(` ${command.usage}`);
|
|
874
|
+
if (command.details) {
|
|
875
|
+
console.log("");
|
|
876
|
+
console.log(command.details);
|
|
208
877
|
}
|
|
209
|
-
return { memberId, task, caller };
|
|
210
878
|
}
|
|
211
|
-
function
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
879
|
+
function printCommandGroup(title, names) {
|
|
880
|
+
console.log(`${title}:`);
|
|
881
|
+
for (const name of names) {
|
|
882
|
+
const command = commands.find((item) => item.path.join(" ") === name);
|
|
883
|
+
if (command && !command.hidden) {
|
|
884
|
+
console.log(` ${`cofounder ${command.path.join(" ")}`.padEnd(30)} ${command.summary}`);
|
|
885
|
+
}
|
|
215
886
|
}
|
|
216
|
-
|
|
887
|
+
console.log("");
|
|
888
|
+
}
|
|
889
|
+
function samePath(left, right) {
|
|
890
|
+
return left.length === right.length && left.every((part, index) => part === right[index]);
|
|
217
891
|
}
|
|
218
|
-
function
|
|
219
|
-
|
|
892
|
+
function isChildCommand(pathParts, prefix) {
|
|
893
|
+
return pathParts.length > prefix.length && prefix.every((part, index) => pathParts[index] === part);
|
|
894
|
+
}
|
|
895
|
+
function parseOptions(args) {
|
|
896
|
+
const positionals = [];
|
|
897
|
+
const values = {};
|
|
898
|
+
const repeated = {};
|
|
899
|
+
const flags = new Set();
|
|
220
900
|
for (let index = 0; index < args.length; index += 1) {
|
|
221
|
-
|
|
222
|
-
|
|
901
|
+
const arg = args[index];
|
|
902
|
+
if (arg === "-y") {
|
|
903
|
+
flags.add("y");
|
|
904
|
+
continue;
|
|
905
|
+
}
|
|
906
|
+
if (!arg.startsWith("--")) {
|
|
907
|
+
positionals.push(arg);
|
|
223
908
|
continue;
|
|
224
909
|
}
|
|
225
|
-
|
|
910
|
+
const optionText = arg.slice(2);
|
|
911
|
+
const equalsIndex = optionText.indexOf("=");
|
|
912
|
+
const rawName = equalsIndex === -1 ? optionText : optionText.slice(0, equalsIndex);
|
|
913
|
+
const inlineValue = equalsIndex === -1 ? undefined : optionText.slice(equalsIndex + 1);
|
|
914
|
+
if (isBooleanFlag(rawName)) {
|
|
915
|
+
flags.add(rawName);
|
|
916
|
+
continue;
|
|
917
|
+
}
|
|
918
|
+
const value = inlineValue ?? args[index + 1];
|
|
919
|
+
if (!value || value.startsWith("--")) {
|
|
920
|
+
throw new CofounderError(`Missing --${rawName} value`);
|
|
921
|
+
}
|
|
922
|
+
index += inlineValue === undefined ? 1 : 0;
|
|
923
|
+
if (["arg", "env", "responsibility"].includes(rawName)) {
|
|
924
|
+
repeated[rawName] = [...(repeated[rawName] ?? []), value];
|
|
925
|
+
}
|
|
926
|
+
else {
|
|
927
|
+
values[rawName] = value;
|
|
928
|
+
}
|
|
226
929
|
}
|
|
227
|
-
return
|
|
930
|
+
return { positionals, values, repeated, flags };
|
|
931
|
+
}
|
|
932
|
+
function isBooleanFlag(name) {
|
|
933
|
+
return ["yes", "y", "install", "setup-codex", "no-setup-codex", "delete-files", "json"].includes(name);
|
|
228
934
|
}
|
|
229
935
|
function requiredArg(value, name) {
|
|
230
|
-
if (!value)
|
|
936
|
+
if (!value)
|
|
231
937
|
throw new CofounderError(`Missing ${name}`);
|
|
938
|
+
return value;
|
|
939
|
+
}
|
|
940
|
+
function normalizeSkillSource(value) {
|
|
941
|
+
if (value !== "project" && value !== "main" && value !== "team") {
|
|
942
|
+
throw new CofounderError("skill scope must be project, team, or main");
|
|
943
|
+
}
|
|
944
|
+
return value;
|
|
945
|
+
}
|
|
946
|
+
function csv(value) {
|
|
947
|
+
return value ? value.split(",").map((item) => item.trim()).filter(Boolean) : [];
|
|
948
|
+
}
|
|
949
|
+
function valuesOrList(values) {
|
|
950
|
+
return values?.flatMap((item) => csv(item).length ? csv(item) : [item]);
|
|
951
|
+
}
|
|
952
|
+
function parseEnv(values) {
|
|
953
|
+
const env = {};
|
|
954
|
+
for (const value of values) {
|
|
955
|
+
const index = value.indexOf("=");
|
|
956
|
+
if (index === -1)
|
|
957
|
+
throw new CofounderError(`Invalid --env ${value}; expected KEY=VALUE`);
|
|
958
|
+
env[value.slice(0, index)] = value.slice(index + 1);
|
|
959
|
+
}
|
|
960
|
+
return env;
|
|
961
|
+
}
|
|
962
|
+
async function askRequired(label) {
|
|
963
|
+
const value = await askOptional(label);
|
|
964
|
+
if (!value)
|
|
965
|
+
throw new CofounderError(`Missing ${label}`);
|
|
966
|
+
return value;
|
|
967
|
+
}
|
|
968
|
+
async function askOptional(label) {
|
|
969
|
+
const reader = createInterface({ input, output });
|
|
970
|
+
try {
|
|
971
|
+
return (await reader.question(`${label}: `)).trim();
|
|
232
972
|
}
|
|
973
|
+
finally {
|
|
974
|
+
reader.close();
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
async function choose(label, choices, fallback) {
|
|
978
|
+
const answer = await askOptional(`${label} (${choices.join("/")}) [${fallback}]`);
|
|
979
|
+
const value = answer || fallback;
|
|
980
|
+
if (!choices.includes(value))
|
|
981
|
+
throw new CofounderError(`${label} must be one of: ${choices.join(", ")}`);
|
|
233
982
|
return value;
|
|
234
983
|
}
|
|
984
|
+
async function confirm(message, fallback) {
|
|
985
|
+
const answer = (await askOptional(`${message} (${fallback ? "Y/n" : "y/N"})`)).toLowerCase();
|
|
986
|
+
return answer ? ["y", "yes"].includes(answer) : fallback;
|
|
987
|
+
}
|
|
988
|
+
async function confirmUnlessYes(options, message) {
|
|
989
|
+
if (options.flags.has("yes") || options.flags.has("y"))
|
|
990
|
+
return;
|
|
991
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
992
|
+
if (await confirm(message, false))
|
|
993
|
+
return;
|
|
994
|
+
}
|
|
995
|
+
throw new CofounderError(`${message} Re-run with --yes to confirm.`);
|
|
996
|
+
}
|
|
997
|
+
function printChangeResult(result) {
|
|
998
|
+
for (const note of result.notes)
|
|
999
|
+
console.log(note);
|
|
1000
|
+
for (const item of result.changed)
|
|
1001
|
+
console.log(`changed ${item}`);
|
|
1002
|
+
for (const item of result.skipped)
|
|
1003
|
+
console.log(`skipped ${item}`);
|
|
1004
|
+
}
|
|
1005
|
+
function printResult(title, created, skipped, notices) {
|
|
1006
|
+
printHeader(title);
|
|
1007
|
+
for (const item of created)
|
|
1008
|
+
console.log(`created ${item}`);
|
|
1009
|
+
for (const item of skipped)
|
|
1010
|
+
console.log(`skipped ${item}`);
|
|
1011
|
+
for (const notice of notices) {
|
|
1012
|
+
console.log("");
|
|
1013
|
+
console.log("Notice:");
|
|
1014
|
+
console.log(notice);
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
function printNextSteps(setupCodex) {
|
|
1018
|
+
printHeader("Next");
|
|
1019
|
+
if (!setupCodex)
|
|
1020
|
+
console.log("1. Run cofounder setup codex --install");
|
|
1021
|
+
console.log(`${setupCodex ? "1" : "2"}. Open Codex from this project directory.`);
|
|
1022
|
+
console.log(`${setupCodex ? "2" : "3"}. Ask: Use the Cofounder team. Show me who is available.`);
|
|
1023
|
+
}
|
|
1024
|
+
function printHeader(title) {
|
|
1025
|
+
console.log(title);
|
|
1026
|
+
console.log("-".repeat(title.length));
|
|
1027
|
+
}
|
|
1028
|
+
function printInfo(message) {
|
|
1029
|
+
console.log(message);
|
|
1030
|
+
}
|
|
235
1031
|
function sleep(ms) {
|
|
236
1032
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
237
1033
|
}
|
|
238
|
-
function printHelp() {
|
|
239
|
-
console.log(`Usage:
|
|
240
|
-
cofounder init
|
|
241
|
-
cofounder init --template <default|worktree>
|
|
242
|
-
cofounder templates
|
|
243
|
-
cofounder setup codex
|
|
244
|
-
cofounder setup codex --install
|
|
245
|
-
cofounder sync project
|
|
246
|
-
cofounder team
|
|
247
|
-
cofounder run <member> <task>
|
|
248
|
-
cofounder delegate <member> <task> [--caller <member>]
|
|
249
|
-
cofounder status <task_id>
|
|
250
|
-
cofounder logs <task_id> [--tail <n>]
|
|
251
|
-
cofounder diff <task_id>
|
|
252
|
-
cofounder apply <task_id>
|
|
253
|
-
cofounder watch <task_id>
|
|
254
|
-
cofounder cancel <task_id>
|
|
255
|
-
cofounder interrupt <task_id> <message>
|
|
256
|
-
cofounder capabilities
|
|
257
|
-
cofounder mcp
|
|
258
|
-
`);
|
|
259
|
-
}
|
|
260
1034
|
main().catch((error) => {
|
|
261
1035
|
if (error instanceof CofounderError) {
|
|
262
1036
|
console.error(`error: ${error.message}`);
|