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/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
- const [command, ...args] = argv;
10
- switch (command) {
11
- case "init":
12
- await commandInit(args);
13
- return;
14
- case "templates":
15
- commandTemplates();
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 commandInit(args) {
72
- const templateOption = readOption(args, "--template");
73
- if (args.includes("--template") && !templateOption) {
74
- throw new CofounderError("Missing --template value");
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 template = templateOption ?? "default";
77
- const result = await initProject(process.cwd(), { template });
78
- console.log(`template ${result.template}`);
79
- for (const item of result.created) {
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
- for (const item of result.skipped) {
83
- console.log(`skipped ${item}`);
327
+ else {
328
+ printInfo("Project already has .cofounder/team.yaml. Skipping init.");
84
329
  }
85
- for (const notice of result.notices) {
86
- console.log("");
87
- console.log("Notice:");
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 commandTemplates() {
92
- for (const template of listProjectTemplates()) {
93
- console.log(`${template.name}: ${template.description}`);
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 commandSetup(args) {
97
- const target = requiredArg(args[0], "setup target");
98
- if (target !== "codex") {
99
- throw new CofounderError(`Unknown setup target: ${target}`);
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
- if (args.includes("--install")) {
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 commandSync(args) {
109
- const target = requiredArg(args[0], "sync target");
110
- if (target !== "project") {
111
- throw new CofounderError(`Unknown sync target: ${target}`);
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 commandTeam() {
119
- console.log(formatTeam(await listTeam()));
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 commandRun(args) {
122
- const { memberId, task, caller } = parseMemberTask(args, { defaultCaller: "lead" });
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 commandDelegate(args) {
127
- const { memberId, task, caller } = parseMemberTask(args, { defaultCaller: "lead" });
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 commandStatus(args) {
132
- const taskId = requiredArg(args[0], "task_id");
133
- console.log(formatTaskStatus(await getTask(taskId)));
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 commandLogs(args) {
136
- const taskId = requiredArg(args[0], "task_id");
137
- const tail = Number(readOption(args, "--tail") ?? "50");
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 commandApply(args) {
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 commandCancel(args) {
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
- console.log(formatTaskStatus(await cancelTask(taskId)));
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 commandInterrupt(args) {
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("\n--- result ---");
686
+ console.log("");
687
+ printHeader("Result");
198
688
  console.log(result.trim());
199
689
  }
200
690
  }
201
- function parseMemberTask(args, options) {
202
- const caller = readOption(args, "--caller") ?? options.defaultCaller;
203
- const positional = stripOptions(args, ["--caller"]);
204
- const memberId = requiredArg(positional[0], "member");
205
- const task = positional.slice(1).join(" ").trim();
206
- if (!task) {
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 readOption(args, name) {
212
- const index = args.indexOf(name);
213
- if (index === -1) {
214
- return undefined;
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
- return args[index + 1];
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 stripOptions(args, optionNames) {
219
- const result = [];
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
- if (optionNames.includes(args[index])) {
222
- index += 1;
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
- result.push(args[index]);
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 result;
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}`);