@codex-modules/teams 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/doctor.js ADDED
@@ -0,0 +1,128 @@
1
+ import { spawnSync } from "node:child_process";
2
+ import { accessSync, constants as fsConstants, existsSync, statSync } from "node:fs";
3
+ import { dirname } from "node:path";
4
+ import { findCodexBinary, listInstalledTeams, parseModelCatalog, resolveAgentsRoot, resolveCodexHome } from "./agents.js";
5
+ import { resolveStateRoot } from "./state.js";
6
+ export function doctor(opts = {}) {
7
+ const env = opts.env ?? process.env;
8
+ const codexHome = resolveCodexHome(env, opts.codexHome);
9
+ const bin = findCodexBinary(env);
10
+ const version = bin ? getCodexVersion(bin) : null;
11
+ let features = [];
12
+ let featuresError;
13
+ if (bin) {
14
+ try {
15
+ features = listFeatures(bin, { ...process.env, ...env, CODEX_HOME: codexHome });
16
+ }
17
+ catch (error) {
18
+ featuresError = error instanceof Error ? error.message : String(error);
19
+ }
20
+ }
21
+ const multiAgent = featureState(features, "multi_agent");
22
+ const fanout = featureState(features, "enable_fanout");
23
+ const multiAgentV2 = featureState(features, "multi_agent_v2");
24
+ const agentsRoot = resolveAgentsRoot("user", { codexHome, env });
25
+ const projectAgentsRoot = resolveAgentsRoot("project", { cwd: opts.cwd, env });
26
+ const stateRoot = resolveStateRoot({ cwd: opts.cwd, stateDir: opts.stateDir, env });
27
+ return {
28
+ codexBinary: bin,
29
+ version,
30
+ features,
31
+ featuresError,
32
+ multiAgent,
33
+ fanout,
34
+ multiAgentV2,
35
+ models: bin ? debugModels(bin, { ...process.env, ...env, CODEX_HOME: codexHome }) : { ok: false, values: [], error: "codex binary not found" },
36
+ agentsDirWritable: canWriteDir(agentsRoot),
37
+ stateDirWritable: canWriteDir(stateRoot),
38
+ userInstalledTeams: existsSync(agentsRoot) ? listInstalledTeams(agentsRoot) : [],
39
+ projectInstalledTeams: existsSync(projectAgentsRoot) ? listInstalledTeams(projectAgentsRoot) : [],
40
+ };
41
+ }
42
+ export function doctorIsHealthy(report) {
43
+ return Boolean(report.codexBinary && report.multiAgent.present && report.multiAgent.stage === "stable" && report.multiAgent.enabled);
44
+ }
45
+ export function formatDoctor(report) {
46
+ const lines = [
47
+ `codex binary: ${report.codexBinary ?? "not found"}`,
48
+ `version: ${report.version ?? "unknown"}`,
49
+ `multi_agent: ${formatFeature(report.multiAgent)}`,
50
+ `enable_fanout: ${formatFeature(report.fanout)} (under development - report only)`,
51
+ `multi_agent_v2: ${formatFeature(report.multiAgentV2)} (under development - report only)`,
52
+ report.featuresError ? `features error: ${report.featuresError}` : null,
53
+ report.models.ok ? `models: ${report.models.values.length}` : `models: unavailable (${report.models.error ?? "unknown error"})`,
54
+ `agents dir writable: ${report.agentsDirWritable}`,
55
+ `state dir writable: ${report.stateDirWritable}`,
56
+ `user installed teams: ${report.userInstalledTeams.length ? report.userInstalledTeams.join(", ") : "none"}`,
57
+ `project installed teams: ${report.projectInstalledTeams.length ? report.projectInstalledTeams.join(", ") : "none"}`,
58
+ ].filter((line) => Boolean(line));
59
+ return `${lines.join("\n")}\n`;
60
+ }
61
+ function getCodexVersion(bin) {
62
+ const result = spawnSync(bin, ["--version"], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000 });
63
+ if (result.status !== 0 || result.error)
64
+ return null;
65
+ return `${result.stdout}${result.stderr}`.trim() || null;
66
+ }
67
+ function listFeatures(bin, env) {
68
+ const result = spawnSync(bin, ["features", "list"], { env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 5000 });
69
+ if (result.status !== 0 || result.error) {
70
+ const reason = result.error ? result.error.message : result.stderr.trim();
71
+ throw new Error(`codex features list failed${reason ? `: ${reason}` : ""}`);
72
+ }
73
+ return result.stdout
74
+ .split(/\r?\n/)
75
+ .map(line => line.trim())
76
+ .filter(Boolean)
77
+ .flatMap(line => {
78
+ const match = line.match(/^(\S+)\s+(.+)\s+(true|false)$/i);
79
+ if (!match)
80
+ return [];
81
+ return [{ name: match[1], stage: match[2].trim(), enabled: match[3].toLowerCase() === "true" }];
82
+ });
83
+ }
84
+ function debugModels(bin, env) {
85
+ const result = spawnSync(bin, ["debug", "models"], { env, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 10_000 });
86
+ if (result.status !== 0 || result.error) {
87
+ const reason = result.error ? result.error.message : result.stderr.trim();
88
+ return { ok: false, values: [], error: reason || "codex debug models failed" };
89
+ }
90
+ try {
91
+ return { ok: true, values: parseModelCatalog(result.stdout) };
92
+ }
93
+ catch (error) {
94
+ return { ok: false, values: [], error: error instanceof Error ? error.message : String(error) };
95
+ }
96
+ }
97
+ function featureState(features, name) {
98
+ const found = features.find(feature => feature.name === name);
99
+ return found ? { present: true, stage: found.stage, enabled: found.enabled } : { present: false };
100
+ }
101
+ function formatFeature(feature) {
102
+ if (!feature || !feature.present)
103
+ return "not reported";
104
+ return `${feature.stage ?? "unknown"} enabled=${feature.enabled ?? false}`;
105
+ }
106
+ function canWriteDir(dir) {
107
+ try {
108
+ const target = nearestExistingPath(dir);
109
+ if (!statSync(target).isDirectory())
110
+ return false;
111
+ accessSync(target, fsConstants.W_OK);
112
+ return true;
113
+ }
114
+ catch {
115
+ return false;
116
+ }
117
+ }
118
+ function nearestExistingPath(path) {
119
+ let current = path;
120
+ for (;;) {
121
+ if (existsSync(current))
122
+ return current;
123
+ const parent = dirname(current);
124
+ if (parent === current)
125
+ return current;
126
+ current = parent;
127
+ }
128
+ }
@@ -0,0 +1,11 @@
1
+ export type { BoundMember, DoctorReport, FinishStatus, InstallScope, JournalEntry, JournalKind, MemberDef, MemberLens, SandboxMode, StateFile, TaskRecord, TaskStatus, TasksFile, TeamDefaults, TeamDef, TeamStatus, } from "./types.js";
2
+ export type { InstallOptions, InstallResult, UninstallResult } from "./agents.js";
3
+ export type { DoctorOptions } from "./doctor.js";
4
+ export type { DryRunPlan, RunOptions, RunResult } from "./runner.js";
5
+ export type { StateOptions } from "./state.js";
6
+ export { installTeam, listInstalledTeams, renderAgentToml, resolveAgentsRoot, uninstallTeam } from "./agents.js";
7
+ export { doctor, doctorIsHealthy, formatDoctor } from "./doctor.js";
8
+ export { assembleLeaderPrompt } from "./prompt.js";
9
+ export { buildRunPlan, runTeam } from "./runner.js";
10
+ export { addNote, addTask, bindMember, claimTask, completeTask, finishState, initState, listNotes, listTasks, showState } from "./state.js";
11
+ export { parseTeamJson, scaffoldTeam, validateTeamDef, writePreset } from "./team.js";
package/dist/index.js ADDED
@@ -0,0 +1,6 @@
1
+ export { installTeam, listInstalledTeams, renderAgentToml, resolveAgentsRoot, uninstallTeam } from "./agents.js";
2
+ export { doctor, doctorIsHealthy, formatDoctor } from "./doctor.js";
3
+ export { assembleLeaderPrompt } from "./prompt.js";
4
+ export { buildRunPlan, runTeam } from "./runner.js";
5
+ export { addNote, addTask, bindMember, claimTask, completeTask, finishState, initState, listNotes, listTasks, showState } from "./state.js";
6
+ export { parseTeamJson, scaffoldTeam, validateTeamDef, writePreset } from "./team.js";
package/dist/lock.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ export type LockHandle = {
2
+ path: string;
3
+ release(): void;
4
+ };
5
+ export declare function acquireMkdirLock(path: string, opts?: {
6
+ ttlMs?: number;
7
+ waitMs?: number;
8
+ purpose?: string;
9
+ }): LockHandle;
10
+ export declare function withLock<T>(path: string, opts: {
11
+ ttlMs?: number;
12
+ waitMs?: number;
13
+ purpose?: string;
14
+ }, fn: () => T): T;
package/dist/lock.js ADDED
@@ -0,0 +1,58 @@
1
+ import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
2
+ import { hostname } from "node:os";
3
+ import { join } from "node:path";
4
+ export function acquireMkdirLock(path, opts = {}) {
5
+ const ttlMs = opts.ttlMs ?? 30_000;
6
+ const waitMs = opts.waitMs ?? 10_000;
7
+ const deadline = Date.now() + waitMs;
8
+ for (;;) {
9
+ try {
10
+ mkdirSync(path, { recursive: false });
11
+ writeFileSync(join(path, "owner.json"), `${JSON.stringify({
12
+ pid: process.pid,
13
+ host: hostname(),
14
+ acquiredAt: new Date().toISOString(),
15
+ expiresAt: new Date(Date.now() + ttlMs).toISOString(),
16
+ purpose: opts.purpose ?? "teams",
17
+ }, null, 2)}\n`);
18
+ return {
19
+ path,
20
+ release() {
21
+ rmSync(path, { recursive: true, force: true });
22
+ },
23
+ };
24
+ }
25
+ catch {
26
+ if (isStale(path)) {
27
+ rmSync(path, { recursive: true, force: true });
28
+ continue;
29
+ }
30
+ if (Date.now() > deadline)
31
+ throw new Error(`Timed out acquiring lock: ${path}`);
32
+ sleep(25);
33
+ }
34
+ }
35
+ }
36
+ export function withLock(path, opts, fn) {
37
+ const lock = acquireMkdirLock(path, opts);
38
+ try {
39
+ return fn();
40
+ }
41
+ finally {
42
+ lock.release();
43
+ }
44
+ }
45
+ function isStale(path) {
46
+ if (!existsSync(path))
47
+ return false;
48
+ try {
49
+ const owner = JSON.parse(readFileSync(join(path, "owner.json"), "utf8"));
50
+ return owner.expiresAt ? Date.parse(owner.expiresAt) <= Date.now() : false;
51
+ }
52
+ catch {
53
+ return false;
54
+ }
55
+ }
56
+ function sleep(ms) {
57
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
58
+ }
package/dist/main.d.ts ADDED
@@ -0,0 +1 @@
1
+ export declare function main(args: string[]): Promise<void>;
package/dist/main.js ADDED
@@ -0,0 +1,378 @@
1
+ import { installTeam, uninstallTeam } from "./agents.js";
2
+ import { doctor, doctorIsHealthy, formatDoctor } from "./doctor.js";
3
+ import { assembleLeaderPrompt } from "./prompt.js";
4
+ import { runTeam } from "./runner.js";
5
+ import { installSkill, uninstallSkill } from "./skill.js";
6
+ import { addNote, addTask, bindMember, claimTask, completeTask, finishState, initState, listNotes, listTasks, showState, } from "./state.js";
7
+ import { collectTeamErrors, parseTeamJson, writePreset } from "./team.js";
8
+ export async function main(args) {
9
+ try {
10
+ await run(args);
11
+ }
12
+ catch (error) {
13
+ console.error(error instanceof Error ? error.message : String(error));
14
+ process.exitCode = 1;
15
+ }
16
+ }
17
+ async function run(args) {
18
+ const [command, ...rest] = args;
19
+ if (!command || command === "--help" || command === "-h") {
20
+ printHelp();
21
+ return;
22
+ }
23
+ if (command === "init") {
24
+ const parsed = parseArgs(rest);
25
+ const preset = flag(parsed, "preset") ?? "review-panel";
26
+ if (preset !== "review-panel" && preset !== "swarm" && preset !== "pipeline")
27
+ throw new Error("--preset must be review-panel, swarm, or pipeline");
28
+ writePreset(flag(parsed, "out") ?? "team.json", preset);
29
+ console.log("OK init");
30
+ return;
31
+ }
32
+ if (command === "validate") {
33
+ const parsed = parseArgs(rest);
34
+ const file = parsed.positional[0];
35
+ if (!file)
36
+ throw new Error("usage: codex-teams validate <team.json>");
37
+ const raw = JSON.parse(await import("node:fs").then(fs => fs.readFileSync(file, "utf8")));
38
+ const errors = collectTeamErrors(raw);
39
+ if (errors.length > 0)
40
+ throw new Error(`${file} is invalid:\n- ${errors.join("\n- ")}`);
41
+ console.log("OK validate");
42
+ return;
43
+ }
44
+ if (command === "doctor") {
45
+ const parsed = parseArgs(rest);
46
+ const report = doctor({ codexHome: flag(parsed, "codex-home"), stateDir: flag(parsed, "state-dir") });
47
+ if (boolFlag(parsed, "json"))
48
+ console.log(JSON.stringify(report, null, 2));
49
+ else
50
+ process.stdout.write(formatDoctor(report));
51
+ if (!doctorIsHealthy(report))
52
+ process.exitCode = 1;
53
+ return;
54
+ }
55
+ if (command === "install") {
56
+ const parsed = parseArgs(rest);
57
+ const file = parsed.positional[0];
58
+ if (!file)
59
+ throw new Error("usage: codex-teams install <team.json>");
60
+ const result = installTeam(file, {
61
+ codexHome: flag(parsed, "codex-home"),
62
+ scope: scopeFlag(parsed),
63
+ force: boolFlag(parsed, "force"),
64
+ skipModelCheck: boolFlag(parsed, "skip-model-check"),
65
+ });
66
+ for (const warning of result.warnings)
67
+ console.error(`WARN ${warning}`);
68
+ console.log(`OK install ${result.team} ${result.files.length} file(s)`);
69
+ return;
70
+ }
71
+ if (command === "uninstall") {
72
+ const parsed = parseArgs(rest);
73
+ const team = parsed.positional[0];
74
+ if (!team)
75
+ throw new Error("usage: codex-teams uninstall <team-name>");
76
+ const result = uninstallTeam(team, { codexHome: flag(parsed, "codex-home"), scope: scopeFlag(parsed) });
77
+ for (const warning of result.warnings)
78
+ console.error(`WARN ${warning}`);
79
+ console.log(`OK uninstall ${result.team} removed=${result.removed.length} restored=${result.restored.length}`);
80
+ return;
81
+ }
82
+ if (command === "leader-prompt") {
83
+ const parsed = parseArgs(rest);
84
+ const file = parsed.positional[0];
85
+ const goal = requiredFlag(parsed, "goal");
86
+ if (!file)
87
+ throw new Error("usage: codex-teams leader-prompt <team.json> --goal <text>");
88
+ process.stdout.write(assembleLeaderPrompt(parseTeamJson(file), goal));
89
+ return;
90
+ }
91
+ if (command === "skill") {
92
+ const [subcommand, ...skillRest] = rest;
93
+ const parsed = parseArgs(skillRest);
94
+ if (subcommand === "install") {
95
+ const result = installSkill({ codexHome: flag(parsed, "codex-home"), force: boolFlag(parsed, "force") });
96
+ console.log(`OK skill install ${result.file}`);
97
+ return;
98
+ }
99
+ if (subcommand === "uninstall") {
100
+ const result = uninstallSkill({ codexHome: flag(parsed, "codex-home") });
101
+ console.log(`OK skill uninstall removed=${result.removed.length} restored=${result.restored.length}`);
102
+ return;
103
+ }
104
+ throw new Error("usage: codex-teams skill install|uninstall");
105
+ }
106
+ if (command === "run") {
107
+ const parsed = parseArgs(rest);
108
+ const file = parsed.positional[0];
109
+ if (!file)
110
+ throw new Error("usage: codex-teams run <team.json> --goal <text>");
111
+ const result = await runTeam(file, {
112
+ goal: requiredFlag(parsed, "goal"),
113
+ codexHome: flag(parsed, "codex-home"),
114
+ stateDir: flag(parsed, "state-dir"),
115
+ sandbox: sandboxFlag(parsed),
116
+ timeoutSec: numberFlag(parsed, "timeout-sec"),
117
+ stallSec: numberFlag(parsed, "stall-sec"),
118
+ execute: boolFlag(parsed, "execute"),
119
+ allowCodex: boolFlag(parsed, "allow-codex"),
120
+ });
121
+ if (result.mode === "dry-run") {
122
+ console.log("DRY-RUN codex argv:");
123
+ console.log(result.argv.map(shellQuote).join(" "));
124
+ console.log("DRY-RUN leader prompt:");
125
+ process.stdout.write(result.prompt);
126
+ }
127
+ else {
128
+ console.log(`OK run ${result.status} exit=${result.exitCode ?? "null"} runDir=${result.runDir}`);
129
+ }
130
+ return;
131
+ }
132
+ if (command === "state") {
133
+ handleState(rest);
134
+ return;
135
+ }
136
+ if (command === "member") {
137
+ handleMember(rest);
138
+ return;
139
+ }
140
+ if (command === "task") {
141
+ handleTask(rest);
142
+ return;
143
+ }
144
+ if (command === "note") {
145
+ handleNote(rest);
146
+ return;
147
+ }
148
+ throw new Error(`unknown command: ${command}`);
149
+ }
150
+ function handleState(args) {
151
+ const [subcommand, ...rest] = args;
152
+ const parsed = parseArgs(rest);
153
+ const team = parsed.positional[0];
154
+ if (!team)
155
+ throw new Error("usage: codex-teams state init|show|finish <team>");
156
+ const opts = { stateDir: flag(parsed, "state-dir") };
157
+ if (subcommand === "init") {
158
+ const result = initState(team, requiredFlag(parsed, "goal"), { ...opts, noGitignore: boolFlag(parsed, "no-gitignore") });
159
+ console.log(JSON.stringify(result, null, 2));
160
+ return;
161
+ }
162
+ if (subcommand === "show") {
163
+ const result = showState(team, opts);
164
+ if (boolFlag(parsed, "json"))
165
+ console.log(JSON.stringify(result, null, 2));
166
+ else
167
+ console.log(`team=${result.team} status=${result.status} goal=${result.goal}`);
168
+ return;
169
+ }
170
+ if (subcommand === "finish") {
171
+ const status = requiredFlag(parsed, "status");
172
+ if (status !== "ok" && status !== "partial")
173
+ throw new Error("--status must be ok or partial");
174
+ console.log(JSON.stringify(finishState(team, status, opts), null, 2));
175
+ return;
176
+ }
177
+ throw new Error("usage: codex-teams state init|show|finish <team>");
178
+ }
179
+ function handleMember(args) {
180
+ const [subcommand, ...rest] = args;
181
+ const parsed = parseArgs(rest);
182
+ if (subcommand !== "bind")
183
+ throw new Error("usage: codex-teams member bind <team> <member> --agent-id <id>");
184
+ const [team, member] = parsed.positional;
185
+ if (!team || !member)
186
+ throw new Error("usage: codex-teams member bind <team> <member> --agent-id <id>");
187
+ console.log(JSON.stringify(bindMember(team, member, requiredFlag(parsed, "agent-id"), { stateDir: flag(parsed, "state-dir"), nickname: flag(parsed, "nickname") }), null, 2));
188
+ }
189
+ function handleTask(args) {
190
+ const [subcommand, ...rest] = args;
191
+ const parsed = parseArgs(rest);
192
+ const [team, taskId] = parsed.positional;
193
+ if (!team)
194
+ throw new Error("usage: codex-teams task add|claim|complete|fail|list <team>");
195
+ const opts = { stateDir: flag(parsed, "state-dir") };
196
+ if (subcommand === "add") {
197
+ const task = addTask(team, { title: requiredFlag(parsed, "title"), detail: flag(parsed, "detail"), dependsOn: splitCsv(flag(parsed, "depends-on")) }, opts);
198
+ console.log(JSON.stringify(task, null, 2));
199
+ return;
200
+ }
201
+ if (subcommand === "claim") {
202
+ if (!taskId)
203
+ throw new Error("usage: codex-teams task claim <team> <task-id> --actor <name>");
204
+ console.log(JSON.stringify(claimTask(team, taskId, { actor: requiredFlag(parsed, "actor"), leaseSec: numberFlag(parsed, "lease-sec") }, opts), null, 2));
205
+ return;
206
+ }
207
+ if (subcommand === "complete" || subcommand === "fail") {
208
+ if (!taskId)
209
+ throw new Error(`usage: codex-teams task ${subcommand} <team> <task-id> --actor <name>`);
210
+ console.log(JSON.stringify(completeTask(team, taskId, { actor: requiredFlag(parsed, "actor"), result: flag(parsed, "result"), failed: subcommand === "fail" }, opts), null, 2));
211
+ return;
212
+ }
213
+ if (subcommand === "list") {
214
+ const tasks = listTasks(team, { ...opts, reclaim: boolFlag(parsed, "reclaim") });
215
+ if (boolFlag(parsed, "json"))
216
+ console.log(JSON.stringify(tasks, null, 2));
217
+ else
218
+ for (const task of tasks.tasks)
219
+ console.log(`${task.id}\t${task.status}\t${task.title}`);
220
+ return;
221
+ }
222
+ throw new Error("usage: codex-teams task add|claim|complete|fail|list <team>");
223
+ }
224
+ function handleNote(args) {
225
+ const [subcommand, ...rest] = args;
226
+ const parsed = parseArgs(rest);
227
+ const team = parsed.positional[0];
228
+ if (!team)
229
+ throw new Error("usage: codex-teams note add|list <team>");
230
+ const opts = { stateDir: flag(parsed, "state-dir") };
231
+ if (subcommand === "add") {
232
+ console.log(JSON.stringify(addNote(team, { actor: requiredFlag(parsed, "actor"), text: requiredFlag(parsed, "text"), kind: kindFlag(parsed) }, opts), null, 2));
233
+ return;
234
+ }
235
+ if (subcommand === "list") {
236
+ const notes = listNotes(team, opts);
237
+ if (boolFlag(parsed, "json"))
238
+ console.log(JSON.stringify(notes, null, 2));
239
+ else
240
+ for (const note of notes)
241
+ console.log(`${note.ts}\t${note.actor}\t${note.kind}\t${note.text}`);
242
+ return;
243
+ }
244
+ throw new Error("usage: codex-teams note add|list <team>");
245
+ }
246
+ function parseArgs(args) {
247
+ const positional = [];
248
+ const flags = new Map();
249
+ const valueFlags = new Set([
250
+ "actor",
251
+ "agent-id",
252
+ "codex-home",
253
+ "depends-on",
254
+ "detail",
255
+ "goal",
256
+ "kind",
257
+ "lease-sec",
258
+ "model",
259
+ "nickname",
260
+ "out",
261
+ "preset",
262
+ "result",
263
+ "sandbox",
264
+ "scope",
265
+ "stall-sec",
266
+ "state-dir",
267
+ "status",
268
+ "text",
269
+ "timeout-sec",
270
+ "title",
271
+ ]);
272
+ for (let i = 0; i < args.length; i++) {
273
+ const arg = args[i];
274
+ if (arg === "--") {
275
+ positional.push(...args.slice(i + 1));
276
+ break;
277
+ }
278
+ if (arg.startsWith("--")) {
279
+ const eq = arg.indexOf("=");
280
+ if (eq !== -1) {
281
+ flags.set(arg.slice(2, eq), arg.slice(eq + 1));
282
+ continue;
283
+ }
284
+ const name = arg.slice(2);
285
+ const next = args[i + 1];
286
+ if (valueFlags.has(name)) {
287
+ if (next === undefined)
288
+ throw new Error(`--${name} requires a value`);
289
+ flags.set(name, next);
290
+ i++;
291
+ }
292
+ else {
293
+ flags.set(name, true);
294
+ }
295
+ continue;
296
+ }
297
+ if (arg === "-s") {
298
+ const next = args[++i];
299
+ if (!next)
300
+ throw new Error("-s requires a value");
301
+ flags.set("sandbox", next);
302
+ continue;
303
+ }
304
+ positional.push(arg);
305
+ }
306
+ return { positional, flags };
307
+ }
308
+ function flag(parsed, name) {
309
+ const value = parsed.flags.get(name);
310
+ if (value === undefined || value === true)
311
+ return undefined;
312
+ return value;
313
+ }
314
+ function requiredFlag(parsed, name) {
315
+ const value = flag(parsed, name);
316
+ if (!value)
317
+ throw new Error(`--${name} is required`);
318
+ return value;
319
+ }
320
+ function boolFlag(parsed, name) {
321
+ return parsed.flags.get(name) === true || parsed.flags.get(name) === "true";
322
+ }
323
+ function numberFlag(parsed, name) {
324
+ const value = flag(parsed, name);
325
+ if (value === undefined)
326
+ return undefined;
327
+ const parsedValue = Number(value);
328
+ if (!Number.isFinite(parsedValue))
329
+ throw new Error(`--${name} must be a number`);
330
+ return parsedValue;
331
+ }
332
+ function scopeFlag(parsed) {
333
+ const value = flag(parsed, "scope") ?? "user";
334
+ if (value !== "user" && value !== "project")
335
+ throw new Error("--scope must be user or project");
336
+ return value;
337
+ }
338
+ function sandboxFlag(parsed) {
339
+ const value = flag(parsed, "sandbox");
340
+ if (value === undefined)
341
+ return undefined;
342
+ if (value !== "read-only" && value !== "workspace-write")
343
+ throw new Error("-s/--sandbox must be read-only or workspace-write");
344
+ return value;
345
+ }
346
+ function kindFlag(parsed) {
347
+ const value = flag(parsed, "kind");
348
+ if (value === undefined)
349
+ return undefined;
350
+ if (value !== "note" && value !== "handoff" && value !== "decision")
351
+ throw new Error("--kind must be note, handoff, or decision");
352
+ return value;
353
+ }
354
+ function splitCsv(value) {
355
+ return value ? value.split(",").map(item => item.trim()).filter(Boolean) : undefined;
356
+ }
357
+ function shellQuote(value) {
358
+ if (/^[A-Za-z0-9_./:=+-]+$/.test(value))
359
+ return value;
360
+ return `'${value.replace(/'/g, "'\\''")}'`;
361
+ }
362
+ function printHelp() {
363
+ console.log(`codex-teams
364
+
365
+ Usage:
366
+ codex-teams init [--preset review-panel|swarm|pipeline] [--out team.json]
367
+ codex-teams validate <team.json>
368
+ codex-teams doctor [--codex-home <dir>] [--json]
369
+ codex-teams install <team.json> [--codex-home <dir>] [--scope user|project] [--force] [--skip-model-check]
370
+ codex-teams uninstall <team-name> [--codex-home <dir>] [--scope user|project]
371
+ codex-teams leader-prompt <team.json> --goal <text>
372
+ codex-teams skill install|uninstall [--codex-home <dir>]
373
+ codex-teams run <team.json> --goal <text> [--execute --allow-codex] [-s workspace-write] [--timeout-sec N] [--stall-sec N]
374
+ codex-teams state init|show|finish <team>
375
+ codex-teams member bind <team> <member> --agent-id <id>
376
+ codex-teams task add|claim|complete|fail|list <team>
377
+ codex-teams note add|list <team>`);
378
+ }
@@ -0,0 +1,2 @@
1
+ export declare function assertConfinedRoot(root: string, base: string): string;
2
+ export declare function isConfinedPath(candidate: string, root: string): boolean;
package/dist/paths.js ADDED
@@ -0,0 +1,53 @@
1
+ import { existsSync, lstatSync, realpathSync } from "node:fs";
2
+ import { dirname, isAbsolute, relative, resolve, sep } from "node:path";
3
+ export function assertConfinedRoot(root, base) {
4
+ const basePath = resolve(base);
5
+ const rootPath = resolve(root);
6
+ if (!isPathInsideOrSame(rootPath, basePath)) {
7
+ throw new Error(`project-owned root escapes project: ${rootPath}`);
8
+ }
9
+ assertNoSymlinkComponents(rootPath, basePath);
10
+ const baseReal = realpathOrLogical(basePath);
11
+ const rootReal = existsSync(rootPath) ? realpathSync(rootPath) : rootPath;
12
+ if (existsSync(rootPath) && !isPathInsideOrSame(rootReal, baseReal)) {
13
+ throw new Error(`project-owned root resolves outside project: ${rootPath}`);
14
+ }
15
+ return rootPath;
16
+ }
17
+ export function isConfinedPath(candidate, root) {
18
+ const rootReal = realpathOrLogical(resolve(root));
19
+ const logicalCandidate = resolve(candidate);
20
+ const candidatePath = existsSync(logicalCandidate) ? realpathSync(logicalCandidate) : logicalCandidate;
21
+ return isPathInsideOrSame(candidatePath, rootReal) || isPathInsideOrSame(candidatePath, resolve(root));
22
+ }
23
+ function assertNoSymlinkComponents(rootPath, basePath) {
24
+ let current = basePath;
25
+ const rel = relative(basePath, rootPath);
26
+ if (!rel || rel === ".")
27
+ return;
28
+ if (rel.startsWith("..") || isAbsolute(rel))
29
+ throw new Error(`project-owned root escapes project: ${rootPath}`);
30
+ for (const part of rel.split(sep)) {
31
+ current = resolve(current, part);
32
+ if (!existsSync(current))
33
+ continue;
34
+ if (lstatSync(current).isSymbolicLink()) {
35
+ throw new Error(`project-owned root contains symlink component: ${current}`);
36
+ }
37
+ }
38
+ }
39
+ function realpathOrLogical(path) {
40
+ let current = path;
41
+ for (;;) {
42
+ if (existsSync(current))
43
+ return realpathSync(current);
44
+ const parent = dirname(current);
45
+ if (parent === current)
46
+ return path;
47
+ current = parent;
48
+ }
49
+ }
50
+ function isPathInsideOrSame(candidate, base) {
51
+ const rel = relative(base, candidate);
52
+ return rel === "" || (!rel.startsWith("..") && !isAbsolute(rel));
53
+ }
@@ -0,0 +1,3 @@
1
+ import type { MemberDef, TeamDef } from "./types.js";
2
+ export declare function assembleDeveloperInstructions(team: TeamDef, member: MemberDef): string;
3
+ export declare function assembleLeaderPrompt(team: TeamDef, goal: string): string;