paseo-room 0.1.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,1114 @@
1
+ #!/usr/bin/env node
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __esm = (fn, res) => function __init() {
5
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
6
+ };
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+
12
+ // package.json
13
+ var package_default;
14
+ var init_package = __esm({
15
+ "package.json"() {
16
+ package_default = {
17
+ name: "paseo-room",
18
+ version: "0.1.0-alpha.0",
19
+ description: "One CLI that configures Codex/Claude role homes in $HOME and registers them with your local Paseo daemon.",
20
+ keywords: [
21
+ "paseo",
22
+ "codex",
23
+ "claude-code",
24
+ "agent",
25
+ "orchestration",
26
+ "cli"
27
+ ],
28
+ homepage: "https://github.com/cuongntr/paseo-room#readme",
29
+ bugs: "https://github.com/cuongntr/paseo-room/issues",
30
+ repository: {
31
+ type: "git",
32
+ url: "git+https://github.com/cuongntr/paseo-room.git"
33
+ },
34
+ license: "MIT",
35
+ author: "Invoker <cuongnt1987@gmail.com>",
36
+ type: "module",
37
+ engines: {
38
+ node: ">=22"
39
+ },
40
+ bin: {
41
+ "paseo-room": "dist/index.js"
42
+ },
43
+ files: [
44
+ "dist/"
45
+ ],
46
+ scripts: {
47
+ typecheck: "tsc --noEmit",
48
+ lint: "eslint . --max-warnings 0",
49
+ test: "vitest run",
50
+ build: "tsup",
51
+ prepack: "npm run build",
52
+ verify: "npm run typecheck && npm run lint && npm test && npm run build"
53
+ },
54
+ dependencies: {
55
+ "@clack/prompts": "^1.8.0",
56
+ "@getpaseo/client": "0.8.0-beta.1",
57
+ commander: "^14.0.3",
58
+ semver: "^7.8.5",
59
+ "smol-toml": "^1.8.0",
60
+ zod: "^4.5.4"
61
+ },
62
+ devDependencies: {
63
+ "@eslint/js": "^10.0.1",
64
+ "@types/node": "^22.0.0",
65
+ "@types/semver": "^7.7.0",
66
+ eslint: "^10.10.0",
67
+ tsup: "^8.5.1",
68
+ typescript: "~5.9.3",
69
+ "typescript-eslint": "^8.70.0",
70
+ vitest: "^4.1.11"
71
+ }
72
+ };
73
+ }
74
+ });
75
+
76
+ // src/result.ts
77
+ function pass(id, message) {
78
+ return { id, status: "pass", message };
79
+ }
80
+ function fail(id, message, fix) {
81
+ return { id, status: "fail", message, fix };
82
+ }
83
+ function failed(command, checks) {
84
+ return { command, outcome: "failed", changed: false, checks, operations: [] };
85
+ }
86
+ function hasFailure(checks) {
87
+ return checks.some((check) => check.status === "fail");
88
+ }
89
+ function exitCode(result) {
90
+ return result.outcome === "failed" || hasFailure(result.checks) ? 1 : 0;
91
+ }
92
+ var init_result = __esm({
93
+ "src/result.ts"() {
94
+ "use strict";
95
+ }
96
+ });
97
+
98
+ // src/layout.ts
99
+ import { homedir } from "os";
100
+ import { join, resolve, sep } from "path";
101
+ function pick(...candidates) {
102
+ for (const candidate of candidates) if (candidate?.trim()) return candidate.trim();
103
+ throw new Error("Could not determine HOME; pass explicit --room-home and --codex-home/--claude-home paths.");
104
+ }
105
+ function resolveLayout(options = {}, env = process.env) {
106
+ const home = pick(env.HOME, homedir());
107
+ return {
108
+ home,
109
+ roomHome: resolve(pick(options.roomHome, env.PASEO_ROOM_HOME, join(home, ".paseo-room"))),
110
+ paseoHome: resolve(pick(env.PASEO_HOME, join(home, ".paseo"))),
111
+ agentHome: {
112
+ codex: resolve(pick(options.codexHome, env.CODEX_HOME, join(home, ".codex"))),
113
+ claude: resolve(pick(options.claudeHome, env.CLAUDE_CONFIG_DIR, join(home, ".claude")))
114
+ },
115
+ bin: {
116
+ codex: pick(options.codexBin, env.CODEX_BIN, "codex"),
117
+ claude: pick(options.claudeBin, env.CLAUDE_BIN, "claude"),
118
+ paseo: pick(options.paseoBin, env.PASEO_BIN, "paseo")
119
+ }
120
+ };
121
+ }
122
+ function contains(root, path) {
123
+ return path === root || path.startsWith(root.endsWith(sep) ? root : root + sep);
124
+ }
125
+ function layoutChecks(layout, agents) {
126
+ const checks = [];
127
+ if (contains(layout.roomHome, layout.home)) {
128
+ checks.push(fail(
129
+ "room.home",
130
+ `Refusing to use ${layout.roomHome} as the room home: it contains your home directory.`,
131
+ "Point --room-home at a dedicated directory, by default ~/.paseo-room."
132
+ ));
133
+ }
134
+ for (const id of agents) {
135
+ const home = layout.agentHome[id];
136
+ if (contains(layout.roomHome, home) || contains(home, layout.roomHome)) {
137
+ checks.push(fail(
138
+ `${id}.home`,
139
+ `The ${id} home ${home} overlaps the room home ${layout.roomHome}.`,
140
+ `Pass --${id}-home pointing at your own ${id} configuration (this happens when setup runs inside a room seat).`
141
+ ));
142
+ }
143
+ }
144
+ return checks;
145
+ }
146
+ function roleHome(layout, agent, role) {
147
+ return join(layout.roomHome, "roles", agent, role);
148
+ }
149
+ function sharedRoom(layout) {
150
+ return join(layout.roomHome, "room");
151
+ }
152
+ var init_layout = __esm({
153
+ "src/layout.ts"() {
154
+ "use strict";
155
+ init_result();
156
+ }
157
+ });
158
+
159
+ // src/fsops.ts
160
+ import { chmod, lstat, mkdir, readFile, readlink, rm, symlink, writeFile } from "fs/promises";
161
+ import { dirname, join as join2 } from "path";
162
+ async function current(entry) {
163
+ let stat;
164
+ try {
165
+ stat = await lstat(entry.path);
166
+ } catch {
167
+ return "absent";
168
+ }
169
+ if (entry.kind === "dir") return stat.isDirectory() ? "same" : "different";
170
+ if (entry.kind === "link") {
171
+ if (!stat.isSymbolicLink()) return "different";
172
+ return await readlink(entry.path) === entry.target ? "same" : "different";
173
+ }
174
+ if (!stat.isFile()) return "different";
175
+ if (entry.once) return "same";
176
+ return await readFile(entry.path, "utf8") === entry.content ? "same" : "different";
177
+ }
178
+ async function planEntries(entries) {
179
+ const operations = [];
180
+ for (const entry of entries) {
181
+ const state = await current(entry);
182
+ operations.push({
183
+ action: state === "same" ? "noop" : state === "absent" ? "create" : "update",
184
+ kind: entry.kind,
185
+ target: entry.path
186
+ });
187
+ }
188
+ return operations;
189
+ }
190
+ async function applyEntries(entries) {
191
+ for (const entry of entries) {
192
+ if (await current(entry) === "same") continue;
193
+ if (entry.kind === "dir") {
194
+ await mkdir(entry.path, { recursive: true, mode: DIR_MODE });
195
+ continue;
196
+ }
197
+ await mkdir(dirname(entry.path), { recursive: true, mode: DIR_MODE });
198
+ await rm(entry.path, { force: true, recursive: true });
199
+ if (entry.kind === "link") await symlink(entry.target, entry.path);
200
+ else {
201
+ await writeFile(entry.path, entry.content, { mode: FILE_MODE });
202
+ await chmod(entry.path, FILE_MODE);
203
+ }
204
+ }
205
+ }
206
+ async function exists(path) {
207
+ try {
208
+ await lstat(path);
209
+ return true;
210
+ } catch {
211
+ return false;
212
+ }
213
+ }
214
+ async function existingPaths(home, names) {
215
+ const paths = names.map((name) => join2(home, name));
216
+ const present = await Promise.all(paths.map(exists));
217
+ return paths.filter((_, index) => present[index] === true);
218
+ }
219
+ async function readIfPresent(path) {
220
+ try {
221
+ return await readFile(path, "utf8");
222
+ } catch {
223
+ return void 0;
224
+ }
225
+ }
226
+ var DIR_MODE, FILE_MODE;
227
+ var init_fsops = __esm({
228
+ "src/fsops.ts"() {
229
+ "use strict";
230
+ DIR_MODE = 448;
231
+ FILE_MODE = 384;
232
+ }
233
+ });
234
+
235
+ // src/room/clauses.ts
236
+ function clause(source) {
237
+ return source.trim().split(/\n\s*\n/).map((statement) => statement.split("\n").map((line) => line.trim()).join(" "));
238
+ }
239
+ var CLAUSES, SHARED_IDS;
240
+ var init_clauses = __esm({
241
+ "src/room/clauses.ts"() {
242
+ "use strict";
243
+ CLAUSES = {
244
+ "RC-001": clause(`
245
+ Human owns product goals, priority, material-cost choices, external effects, and
246
+ irreversible-risk decisions. No agent may take ownership of these decisions; obtain
247
+ Human approval before crossing those boundaries.
248
+
249
+ Human may direct the technical route, decomposition, and lifecycle, and may override
250
+ an acceptance decision. Human does not normally operate the agent protocol.
251
+ `),
252
+ "RC-002": clause(`
253
+ Each role must read workspace-local docs/WORKSPACE_PROTOCOL.md when it exists.
254
+ Repository conventions, narrower scopes, validation commands, and escalation details
255
+ may refine workflow, but cannot weaken this authority contract.
256
+
257
+ A local protocol cannot give Peer orchestration, give Supervisor or Peer technical
258
+ acceptance, permit multiple writable Peers, or transfer Human decisions to an agent.
259
+ `),
260
+ "RC-003": clause(`
261
+ Use tests, artifacts, lifecycle states, and completion/error/attention events as
262
+ evidence, not as automatic authorization or acceptance.
263
+
264
+ Wait for state-changing events when progress depends on another actor. Do not
265
+ repeatedly poll unchanged state; resume when new evidence or a relevant event arrives.
266
+ `),
267
+ "RC-004": clause(`
268
+ Preserve unrelated work. Stay within the granted repository scope and external-action
269
+ authority; do not treat access to a tool as permission to expand either boundary.
270
+ `),
271
+ "RC-101": clause(`
272
+ Supervisor routes the Human directive to Lead without changing its outcome, requested
273
+ output, constraints, or approval gates. Label added context separately; it must not
274
+ rewrite the directive.
275
+ `),
276
+ "RC-102": clause(`
277
+ Supervisor observes technical work rather than choosing architecture, decomposing
278
+ work, or moving write ownership. Prefer routing work through Lead.
279
+
280
+ Supervisor must not edit project work, run project validation, or decide technical
281
+ acceptance. Supervisor must not direct Peer while Lead is healthy; an unhealthy Lead
282
+ calls for bounded recovery or escalation, not taking over Peer work.
283
+ `),
284
+ "RC-103": clause(`
285
+ Supervisor has Paseo tools enabled solely within its authority. Use the smallest Paseo
286
+ room/session lifecycle action needed for an explicit Human request or bounded room
287
+ recovery; preserve current ownership and inform Lead of every change.
288
+ `),
289
+ "RC-104": clause(`
290
+ Supervisor sends technical questions and evidence to Lead. Escalate product, priority,
291
+ material-cost, external-effect, and irreversible-risk choices to Human rather than
292
+ deciding them.
293
+ `),
294
+ "RC-201": clause(`
295
+ Lead owns project framing, architecture, dependencies, integration, verification, and
296
+ technical acceptance within Human boundaries. Lead executes the Human outcome and
297
+ constraints, escalating Human-owned choices to Human.
298
+
299
+ Lead has Paseo tools enabled to manage project agents and direct Peer; this capability
300
+ does not expand project or external-action authority.
301
+ `),
302
+ "RC-202": clause(`
303
+ Lead owns decomposition and moving write-scope assignment: give each moving scope
304
+ exactly one owner, with at most one active writable Peer across the project at a time.
305
+ Lead must not edit a scope concurrently with its writing Peer.
306
+
307
+ Before transferring write ownership, stop the prior writer and establish a stable
308
+ handoff. Read-only review does not create another writer.
309
+ `),
310
+ "RC-203": clause(`
311
+ Before delegation, Lead supplies a complete Peer brief: one bounded outcome,
312
+ prerequisites, explicit write scope or read-only mode, stable contract and invariants,
313
+ required acceptance evidence, and conditions that reopen the decision.
314
+ `),
315
+ "RC-204": clause(`
316
+ Lead permits independent Peer judgment: REOPEN_REQUEST challenges a premise;
317
+ DEPENDENCY_REQUEST asks for an unowned prerequisite; BLOCKED reports that no safe
318
+ progress is possible.
319
+
320
+ For each signal, Peer provides evidence, consequence, and the needed decision or
321
+ dependency. Lead resolves technical signals or escalates Human-owned choices; Peer
322
+ must not seize unowned work while waiting.
323
+ `),
324
+ "RC-205": clause(`
325
+ Lead inspects the exact candidate or a deterministic snapshot and explicitly accepts
326
+ or rejects it with a technical reason. Passing tests and completion reports are
327
+ evidence, not acceptance. Among agents, Lead alone accepts; Human retains override
328
+ authority.
329
+ `),
330
+ "RC-206": clause(`
331
+ When material uncertainty warrants independent review, Lead may dispatch a fresh
332
+ read-only Peer with an exact stable candidate and a bounded question. Review is
333
+ optional: do not introduce a dedicated reviewer role or a fixed reviewer count.
334
+ `),
335
+ "RC-301": clause(`
336
+ Peer owns exactly one Lead-delegated bounded outcome and proportionate evidence. Do
337
+ not add adjacent tasks; send technical questions to Lead and escalate Human-owned
338
+ choices through Lead.
339
+ `),
340
+ "RC-302": clause(`
341
+ A writing Peer owns only its assigned moving write scope. A review Peer stays
342
+ read-only and inspects only the named candidate or snapshot, without changing project
343
+ files or the candidate.
344
+ `),
345
+ "RC-303": clause(`
346
+ Peer must not spawn, manage, coordinate, or infer room topology, direct another Peer,
347
+ or perform Paseo room/session lifecycle operations. Peer receives no Paseo tools;
348
+ enabled is false. This capability boundary is not an operating-system sandbox.
349
+ `),
350
+ "RC-304": clause(`
351
+ Peer hands off a candidate by naming an immutable commit or deterministic snapshot,
352
+ the original base, all changed paths, verification performed and its results, and
353
+ residual risk. Make the candidate reproducible for Lead inspection.
354
+ `),
355
+ "RC-305": clause(`
356
+ Peer must not self-accept any work, including difficult work. Peer tests and
357
+ completion are evidence only; Lead alone performs technical acceptance among agents,
358
+ subject to Human override.
359
+ `)
360
+ };
361
+ SHARED_IDS = ["RC-001", "RC-002", "RC-003", "RC-004"];
362
+ }
363
+ });
364
+
365
+ // src/room/instructions.ts
366
+ function instructionIds(kind) {
367
+ if (kind === "workspace") return Object.keys(CLAUSES);
368
+ return [...SHARED_IDS, ...ROLE_IDS[kind]];
369
+ }
370
+ function renderInstructions(kind) {
371
+ const sections = instructionIds(kind).map(
372
+ (id) => `## ${id}
373
+ ${CLAUSES[id].map((statement) => `- ${statement}`).join("\n")}`
374
+ );
375
+ return [`# ${TITLES[kind]}`, PREFACES[kind], ...sections].join("\n\n") + "\n";
376
+ }
377
+ var ROLE_IDS, TITLES, PREFACES;
378
+ var init_instructions = __esm({
379
+ "src/room/instructions.ts"() {
380
+ "use strict";
381
+ init_clauses();
382
+ ROLE_IDS = {
383
+ supervisor: ["RC-101", "RC-102", "RC-103", "RC-104"],
384
+ lead: ["RC-201", "RC-202", "RC-203", "RC-204", "RC-205", "RC-206"],
385
+ // Peer needs the challenge protocol as well as its own numbered obligations.
386
+ peer: ["RC-204", "RC-301", "RC-302", "RC-303", "RC-304", "RC-305"]
387
+ };
388
+ TITLES = {
389
+ workspace: "Paseo Room workspace protocol \u2014 operator reference/template",
390
+ supervisor: "Supervisor role instructions",
391
+ lead: "Lead role instructions",
392
+ peer: "Peer role instructions"
393
+ };
394
+ PREFACES = {
395
+ workspace: "Operator reference: copy what you need into a workspace-local docs/WORKSPACE_PROTOCOL.md. Installation never creates or replaces that workspace file.",
396
+ supervisor: "You are Supervisor, the Human-facing routing seat, not the project Lead.",
397
+ lead: "You are Lead, the project technical owner under Human authority.",
398
+ peer: "You are Peer, executing one brief from Lead in writing or read-only review mode."
399
+ };
400
+ }
401
+ });
402
+
403
+ // src/which.ts
404
+ import { execFile } from "child_process";
405
+ import { access, constants, realpath } from "fs/promises";
406
+ import { delimiter, isAbsolute, join as join3, resolve as resolve2 } from "path";
407
+ import { promisify } from "util";
408
+ async function which(command, env = process.env) {
409
+ const candidates = isAbsolute(command) || command.includes("/") ? [resolve2(command)] : (env.PATH ?? "").split(delimiter).filter(Boolean).map((entry) => join3(entry, command));
410
+ for (const candidate of candidates) {
411
+ try {
412
+ await access(candidate, constants.X_OK);
413
+ return await realpath(candidate);
414
+ } catch {
415
+ }
416
+ }
417
+ return void 0;
418
+ }
419
+ async function probe(executable, args, env) {
420
+ try {
421
+ const { stdout } = await run(executable, [...args], { env, timeout: 2e4, maxBuffer: 16 * 1024 * 1024, encoding: "utf8" });
422
+ return { ok: true, stdout };
423
+ } catch {
424
+ return { ok: false, stdout: "" };
425
+ }
426
+ }
427
+ var run;
428
+ var init_which = __esm({
429
+ "src/which.ts"() {
430
+ "use strict";
431
+ run = promisify(execFile);
432
+ }
433
+ });
434
+
435
+ // src/agents/claude.ts
436
+ import { basename, join as join4 } from "path";
437
+ function asObject(value) {
438
+ return value !== null && typeof value === "object" && !Array.isArray(value) ? { ...value } : {};
439
+ }
440
+ function readObject(source) {
441
+ try {
442
+ return asObject(source === void 0 ? {} : JSON.parse(source));
443
+ } catch {
444
+ return {};
445
+ }
446
+ }
447
+ function renderRoleSettings(source, role) {
448
+ const settings = readObject(source);
449
+ settings.env = { ...asObject(settings.env), PASEO_ROOM_ROLE: role };
450
+ return JSON.stringify(settings, null, 2) + "\n";
451
+ }
452
+ function renderRoleState(source) {
453
+ const root = readObject(source);
454
+ const state = { hasCompletedOnboarding: true };
455
+ for (const key of SEEDED_KEYS) if (key in root) state[key] = root[key];
456
+ return JSON.stringify(state, null, 2) + "\n";
457
+ }
458
+ var SHARED, SEEDED_KEYS, claudeAgent;
459
+ var init_claude = __esm({
460
+ "src/agents/claude.ts"() {
461
+ "use strict";
462
+ init_layout();
463
+ init_fsops();
464
+ init_result();
465
+ init_instructions();
466
+ init_which();
467
+ SHARED = [".credentials.json", "skills", "plugins", "commands", "hooks"];
468
+ SEEDED_KEYS = ["hasCompletedOnboarding", "theme", "installMethod", "userID", "oauthAccount"];
469
+ claudeAgent = {
470
+ id: "claude",
471
+ label: "Claude Code",
472
+ homeEnv: "CLAUDE_CONFIG_DIR",
473
+ // Claude's own subagents would be a second control plane; Paseo owns agent
474
+ // lifecycle. This is the Claude counterpart of Codex's [agents] enabled = false.
475
+ pins: { disallowedTools: ["Task"] },
476
+ async build(layout, roles) {
477
+ const home = layout.agentHome.claude;
478
+ const binary = await which(layout.bin.claude);
479
+ if (!binary) {
480
+ return { entries: [], checks: [fail("claude.bin", "Claude Code executable not found.", "Install Claude Code, or pass --claude-bin /path/to/claude.")] };
481
+ }
482
+ if (!await exists(home)) {
483
+ return { entries: [], checks: [fail("claude.home", `No Claude config directory at ${home}.`, "Run claude once to initialise it, or pass --claude-home.")] };
484
+ }
485
+ const checks = [pass("claude.home", `Claude Code found at ${binary} using ${home}.`)];
486
+ const settingsSource = await readIfPresent(join4(home, "settings.json"));
487
+ const stateSource = await readIfPresent(join4(layout.home, ".claude.json"));
488
+ const shared = await existingPaths(home, SHARED);
489
+ const entries = [];
490
+ for (const role of roles) {
491
+ const target = roleHome(layout, "claude", role);
492
+ entries.push({ kind: "dir", path: target });
493
+ entries.push({ kind: "file", path: join4(target, "CLAUDE.md"), content: renderInstructions(role) });
494
+ entries.push({ kind: "file", path: join4(target, "settings.json"), content: renderRoleSettings(settingsSource, role) });
495
+ entries.push({ kind: "file", path: join4(target, ".claude.json"), content: renderRoleState(stateSource), once: true });
496
+ for (const path of shared) entries.push({ kind: "link", path: join4(target, basename(path)), target: path });
497
+ }
498
+ return { entries, checks, binary };
499
+ }
500
+ };
501
+ }
502
+ });
503
+
504
+ // src/agents/codex.ts
505
+ import { basename as basename2, join as join5 } from "path";
506
+ import { parse, stringify } from "smol-toml";
507
+ function table(parent, key) {
508
+ const value = parent[key];
509
+ if (value === void 0 || typeof value !== "object" || Array.isArray(value) || value instanceof Date) {
510
+ return parent[key] = {};
511
+ }
512
+ return value;
513
+ }
514
+ function renderRoleConfig(source, input) {
515
+ const config = structuredClone(source);
516
+ config.sandbox_mode = "danger-full-access";
517
+ config.approval_policy = "never";
518
+ if (typeof config.profile === "string") {
519
+ const profile = table(table(config, "profiles"), config.profile);
520
+ profile.sandbox_mode = "danger-full-access";
521
+ profile.approval_policy = "never";
522
+ }
523
+ config.developer_instructions = input.roleDocument;
524
+ if (input.catalogPath) config.model_catalog_json = input.catalogPath;
525
+ table(config, "agents").enabled = false;
526
+ const features = table(config, "features");
527
+ features.multi_agent = false;
528
+ if (typeof features.multi_agent_v2 === "boolean" || features.multi_agent_v2 === void 0) features.multi_agent_v2 = false;
529
+ else table(features, "multi_agent_v2").enabled = false;
530
+ return stringify(config) + "\n";
531
+ }
532
+ function renderCatalog(catalog) {
533
+ return JSON.stringify(catalog, (key, value) => key === "multi_agent_version" ? null : value, 2) + "\n";
534
+ }
535
+ var SHARED2, CATALOG, codexAgent;
536
+ var init_codex = __esm({
537
+ "src/agents/codex.ts"() {
538
+ "use strict";
539
+ init_layout();
540
+ init_fsops();
541
+ init_result();
542
+ init_instructions();
543
+ init_which();
544
+ SHARED2 = ["auth.json", "AGENTS.md", "skills", "plugins", "hooks.json"];
545
+ CATALOG = "model-catalog.json";
546
+ codexAgent = {
547
+ id: "codex",
548
+ label: "Codex",
549
+ homeEnv: "CODEX_HOME",
550
+ // Without these, Paseo's own mode preset (default auto-review) is sent to the
551
+ // app-server and outranks the sandbox/approval keys in the generated config.
552
+ pins: { params: { sandbox_mode: "danger-full-access", approval_policy: "never" } },
553
+ async build(layout, roles) {
554
+ const checks = [];
555
+ const home = layout.agentHome.codex;
556
+ const binary = await which(layout.bin.codex);
557
+ if (!binary) {
558
+ return { entries: [], checks: [fail("codex.bin", "Codex executable not found.", "Install Codex, or pass --codex-bin /path/to/codex.")] };
559
+ }
560
+ const configPath = join5(home, "config.toml");
561
+ const raw = await readIfPresent(configPath);
562
+ if (raw === void 0) {
563
+ return { entries: [], checks: [fail("codex.home", `No config.toml in ${home}.`, "Run codex once to initialise it, or pass --codex-home.")] };
564
+ }
565
+ let source;
566
+ try {
567
+ source = parse(raw);
568
+ } catch {
569
+ return { entries: [], checks: [fail("codex.config", `Could not read ${configPath} as TOML.`, "Fix the syntax in your Codex config, then run setup again.")] };
570
+ }
571
+ checks.push(pass("codex.home", `Codex found at ${binary} using ${home}.`));
572
+ const entries = [];
573
+ let catalogSource;
574
+ const models = await probe(binary, ["debug", "models"], { HOME: layout.home, CODEX_HOME: home, PATH: process.env.PATH ?? "" });
575
+ if (models.ok) {
576
+ try {
577
+ catalogSource = renderCatalog(JSON.parse(models.stdout));
578
+ } catch {
579
+ catalogSource = void 0;
580
+ }
581
+ }
582
+ if (!catalogSource) checks.push({ id: "codex.catalog", status: "warn", message: "Codex model catalog unavailable; roles keep the default catalog." });
583
+ const shared = await existingPaths(home, SHARED2);
584
+ for (const role of roles) {
585
+ const target = roleHome(layout, "codex", role);
586
+ const catalogPath = catalogSource ? join5(target, CATALOG) : void 0;
587
+ const roleDocument = renderInstructions(role);
588
+ entries.push({ kind: "dir", path: target });
589
+ entries.push({ kind: "file", path: join5(target, "role-instructions.md"), content: roleDocument });
590
+ entries.push({
591
+ kind: "file",
592
+ path: join5(target, "config.toml"),
593
+ content: renderRoleConfig(source, { roleDocument, ...catalogPath ? { catalogPath } : {} })
594
+ });
595
+ if (catalogSource && catalogPath) entries.push({ kind: "file", path: catalogPath, content: catalogSource });
596
+ for (const path of shared) entries.push({ kind: "link", path: join5(target, basename2(path)), target: path });
597
+ }
598
+ return { entries, checks, binary };
599
+ }
600
+ };
601
+ }
602
+ });
603
+
604
+ // src/paseo.ts
605
+ import { gte, valid } from "semver";
606
+ import { z } from "zod";
607
+ function normalizeUrl(listen) {
608
+ const match = /^(?:ws:\/\/)?([^:/]+|\[[^\]]+\]):(\d+)$/.exec(listen.trim());
609
+ if (!match) return void 0;
610
+ const [, host = "", port = ""] = match;
611
+ const local = ["localhost", "127.0.0.1", "0.0.0.0", "[::1]", "[::]"].includes(host) ? "127.0.0.1" : host;
612
+ return `ws://${local}:${port}`;
613
+ }
614
+ function assessStatus(raw) {
615
+ const parsed = statusSchema.safeParse(raw);
616
+ if (!parsed.success) {
617
+ return { checks: [fail("paseo.status", "Paseo status output was not understood.", "Upgrade Paseo, then run: paseo daemon status --json")] };
618
+ }
619
+ const status = parsed.data;
620
+ if (status.localDaemon !== "running") {
621
+ return { checks: [fail("paseo.daemon", `Paseo daemon is ${status.localDaemon}.`, "Start it with: paseo daemon start")] };
622
+ }
623
+ const cli = valid(status.cliVersion);
624
+ const daemon = status.daemonVersion === null ? null : valid(status.daemonVersion);
625
+ if (!cli || !daemon) {
626
+ return { checks: [fail("paseo.version", "Paseo did not report a usable version.", "Upgrade Paseo to a release that reports semver versions.")] };
627
+ }
628
+ if (cli !== daemon) {
629
+ return { checks: [fail("paseo.version", `Paseo CLI is ${cli} but the running daemon is ${daemon}.`, "Restart the daemon with: paseo daemon restart")] };
630
+ }
631
+ if (!gte(daemon, MINIMUM_VERSION)) {
632
+ return { checks: [fail("paseo.version", `Paseo ${daemon} is older than the required ${MINIMUM_VERSION}.`, "Upgrade Paseo, then run setup again.")] };
633
+ }
634
+ const url = normalizeUrl(status.listen);
635
+ if (!url) {
636
+ return { checks: [fail("paseo.listen", `Paseo listen address ${status.listen} was not understood.`, "Use a host:port listen address in the Paseo config.")] };
637
+ }
638
+ return { checks: [pass("paseo.version", `Paseo ${daemon} is running on ${url} (compatible, needs >= ${MINIMUM_VERSION}).`)], daemon: { url, version: daemon } };
639
+ }
640
+ async function checkDaemon(layout, env = process.env) {
641
+ const binary = await which(layout.bin.paseo, env);
642
+ if (!binary) {
643
+ return { checks: [fail("paseo.bin", "Paseo executable not found.", "Install Paseo, or pass --paseo-bin /path/to/paseo.")] };
644
+ }
645
+ const output = await probe(binary, ["daemon", "status", "--json"], {
646
+ HOME: layout.home,
647
+ PASEO_HOME: layout.paseoHome,
648
+ PATH: env.PATH ?? "",
649
+ ...env.PASEO_PASSWORD === void 0 ? {} : { PASEO_PASSWORD: env.PASEO_PASSWORD }
650
+ });
651
+ if (!output.ok) {
652
+ return { checks: [fail("paseo.status", "Could not read Paseo daemon status.", "Run: paseo daemon status --json")] };
653
+ }
654
+ try {
655
+ return assessStatus(JSON.parse(output.stdout));
656
+ } catch {
657
+ return assessStatus(void 0);
658
+ }
659
+ }
660
+ async function withSession(daemon, run2, options = {}) {
661
+ const quiet = { debug() {
662
+ }, info() {
663
+ }, warn() {
664
+ }, error() {
665
+ } };
666
+ const factory = options.factory ?? (await import("@getpaseo/client")).createPaseoClient;
667
+ const client = factory({
668
+ url: `${daemon.url}/ws`,
669
+ ...options.password === void 0 ? {} : { password: options.password },
670
+ appVersion: MINIMUM_VERSION,
671
+ connectTimeoutMs: 1e4,
672
+ reconnect: { enabled: false },
673
+ logger: quiet
674
+ });
675
+ try {
676
+ await client.connect();
677
+ return await run2({
678
+ async readProviders() {
679
+ const response = configSchema.parse(await client.config.get());
680
+ return response.config.providers ?? {};
681
+ },
682
+ async writeProviders(providers) {
683
+ await client.config.patch({ providers });
684
+ },
685
+ async removeProviders(ids) {
686
+ await client.config.patch({ removeProviders: [...ids] });
687
+ },
688
+ async refresh(ids) {
689
+ await client.providers.refresh({ providers: [...ids] });
690
+ }
691
+ });
692
+ } finally {
693
+ await client.close();
694
+ }
695
+ }
696
+ function providerMatches(desired, live) {
697
+ if (live === null || typeof live !== "object") return false;
698
+ const entry = live;
699
+ const env = entry.env;
700
+ const tools = entry.paseoTools;
701
+ const same = (left, right) => JSON.stringify(left ?? null) === JSON.stringify(right ?? null);
702
+ return entry.extends === desired.extends && same(entry.command, desired.command) && env !== null && typeof env === "object" && Object.entries(desired.env).every(([key, value]) => env[key] === value) && tools !== null && typeof tools === "object" && tools.enabled === desired.paseoTools.enabled && // Pins are the room's guarantee: drifting them silently re-enables what they block.
703
+ same(entry.params, desired.params) && same(entry.disallowedTools, desired.disallowedTools);
704
+ }
705
+ var MINIMUM_VERSION, statusSchema, configSchema;
706
+ var init_paseo = __esm({
707
+ "src/paseo.ts"() {
708
+ "use strict";
709
+ init_result();
710
+ init_which();
711
+ MINIMUM_VERSION = "0.8.0-beta.1";
712
+ statusSchema = z.object({
713
+ listen: z.string().min(1),
714
+ localDaemon: z.enum(["running", "stopped", "stale_pid", "unresponsive"]),
715
+ cliVersion: z.string(),
716
+ daemonVersion: z.string().nullable()
717
+ });
718
+ configSchema = z.object({ config: z.object({ providers: z.record(z.string(), z.unknown()).optional() }) });
719
+ }
720
+ });
721
+
722
+ // src/roles.ts
723
+ function providerId(agent, role) {
724
+ return `${agent}-${role}`;
725
+ }
726
+ function providerLabel(agent, role) {
727
+ const title = (value) => value.charAt(0).toUpperCase() + value.slice(1);
728
+ return `${title(agent)} ${title(role)}`;
729
+ }
730
+ var ROLES, AGENT_IDS, ROLE_PASEO_TOOLS;
731
+ var init_roles = __esm({
732
+ "src/roles.ts"() {
733
+ "use strict";
734
+ ROLES = ["supervisor", "lead", "peer"];
735
+ AGENT_IDS = ["codex", "claude"];
736
+ ROLE_PASEO_TOOLS = { supervisor: true, lead: true, peer: false };
737
+ }
738
+ });
739
+
740
+ // src/room.ts
741
+ import { join as join6 } from "path";
742
+ import { z as z2 } from "zod";
743
+ function renderMarker(version, agents, roles) {
744
+ return JSON.stringify({ version, agents: [...agents], roles: [...roles] }, null, 2) + "\n";
745
+ }
746
+ async function readMarker(layout) {
747
+ const source = await readIfPresent(join6(layout.roomHome, MARKER));
748
+ if (source === void 0) return void 0;
749
+ try {
750
+ return markerSchema.parse(JSON.parse(source));
751
+ } catch {
752
+ return void 0;
753
+ }
754
+ }
755
+ var MARKER, markerSchema;
756
+ var init_room = __esm({
757
+ "src/room.ts"() {
758
+ "use strict";
759
+ init_fsops();
760
+ init_roles();
761
+ MARKER = "room.json";
762
+ markerSchema = z2.object({
763
+ version: z2.string(),
764
+ agents: z2.array(z2.enum(AGENT_IDS)).min(1),
765
+ roles: z2.array(z2.enum(ROLES)).min(1)
766
+ });
767
+ }
768
+ });
769
+
770
+ // src/commands.ts
771
+ import { join as join7 } from "path";
772
+ import { rm as rm2 } from "fs/promises";
773
+ function sessionOptions(options) {
774
+ const password = (options.env ?? process.env).PASEO_PASSWORD;
775
+ return { ...password === void 0 ? {} : { password }, ...options.factory ? { factory: options.factory } : {} };
776
+ }
777
+ function roleProviders(layout, agent, binary, roles) {
778
+ return Object.fromEntries(roles.map((role) => [providerId(agent.id, role), {
779
+ extends: agent.id,
780
+ label: providerLabel(agent.id, role),
781
+ command: [binary],
782
+ env: { [agent.homeEnv]: roleHome(layout, agent.id, role) },
783
+ paseoTools: { enabled: ROLE_PASEO_TOOLS[role] },
784
+ ...agent.pins
785
+ }]));
786
+ }
787
+ async function buildDesired(layout, agents, roles) {
788
+ const entries = [
789
+ { kind: "dir", path: layout.roomHome },
790
+ { kind: "dir", path: sharedRoom(layout) },
791
+ { kind: "file", path: join7(sharedRoom(layout), "workspace-protocol.md"), content: renderInstructions("workspace") }
792
+ ];
793
+ const providers = {};
794
+ const checks = [];
795
+ for (const id of agents) {
796
+ const agent = AGENTS[id];
797
+ const plan = await agent.build(layout, roles);
798
+ entries.push(...plan.entries);
799
+ checks.push(...plan.checks);
800
+ if (plan.binary !== void 0) Object.assign(providers, roleProviders(layout, agent, plan.binary, roles));
801
+ }
802
+ entries.push({ kind: "file", path: join7(layout.roomHome, MARKER), content: renderMarker(package_default.version, agents, roles) });
803
+ return { entries, providers, checks };
804
+ }
805
+ async function staleFrom(layout, previous, agents, roles) {
806
+ if (!previous) return NOTHING_STALE;
807
+ const kept = new Set(agents.flatMap((agent) => roles.map((role) => providerId(agent, role))));
808
+ const providerIds = [];
809
+ const directories = [];
810
+ for (const agent of previous.agents) {
811
+ for (const role of previous.roles) {
812
+ if (kept.has(providerId(agent, role))) continue;
813
+ providerIds.push(providerId(agent, role));
814
+ const path = roleHome(layout, agent, role);
815
+ if (await exists(path)) directories.push(path);
816
+ }
817
+ }
818
+ return { providerIds, directories };
819
+ }
820
+ async function planRoom(session, desired, stale) {
821
+ const live = await session.readProviders();
822
+ return [
823
+ ...await planEntries(desired.entries),
824
+ ...Object.entries(desired.providers).map(([id, provider]) => ({
825
+ action: !(id in live) ? "create" : providerMatches(provider, live[id]) ? "noop" : "update",
826
+ kind: "provider",
827
+ target: id
828
+ })),
829
+ ...stale.providerIds.filter((id) => id in live).map((id) => ({ action: "remove", kind: "provider", target: id })),
830
+ ...stale.directories.map((path) => ({ action: "remove", kind: "dir", target: path }))
831
+ ];
832
+ }
833
+ async function setup(options = {}) {
834
+ const layout = resolveLayout(options, options.env);
835
+ const agents = options.agents ?? ["codex"];
836
+ const invalid = layoutChecks(layout, agents);
837
+ if (invalid.length > 0) return failed("setup", invalid);
838
+ const daemon = await checkDaemon(layout, options.env);
839
+ if (!daemon.daemon) return failed("setup", daemon.checks);
840
+ const desired = await buildDesired(layout, agents, ROLES);
841
+ const checks = [...daemon.checks, ...desired.checks];
842
+ if (hasFailure(checks)) return failed("setup", checks);
843
+ const stale = await staleFrom(layout, await readMarker(layout), agents, ROLES);
844
+ return withSession(daemon.daemon, async (session) => {
845
+ const operations = await planRoom(session, desired, stale);
846
+ const pending = operations.filter((operation) => operation.action !== "noop");
847
+ if (!options.apply) {
848
+ return {
849
+ command: "setup",
850
+ outcome: pending.length > 0 ? "changes-planned" : "ok",
851
+ changed: false,
852
+ checks,
853
+ operations
854
+ };
855
+ }
856
+ await applyEntries(desired.entries);
857
+ for (const path of stale.directories) await rm2(path, { recursive: true, force: true });
858
+ const ids = Object.keys(desired.providers);
859
+ await session.writeProviders(desired.providers);
860
+ if (stale.providerIds.length > 0) await session.removeProviders(stale.providerIds);
861
+ await session.refresh(ids);
862
+ return {
863
+ command: "setup",
864
+ outcome: "ok",
865
+ changed: pending.length > 0,
866
+ checks: [...checks, pass("room.applied", `Room ready at ${layout.roomHome} with ${String(ids.length)} Paseo providers.`)],
867
+ operations
868
+ };
869
+ }, sessionOptions(options));
870
+ }
871
+ async function verify(options = {}) {
872
+ const layout = resolveLayout(options, options.env);
873
+ const marker = await readMarker(layout);
874
+ if (!marker) {
875
+ return failed("verify", [fail("room.marker", `No room found at ${layout.roomHome}.`, "Run: paseo-room setup --apply")]);
876
+ }
877
+ const invalid = layoutChecks(layout, marker.agents);
878
+ if (invalid.length > 0) return failed("verify", invalid);
879
+ const daemon = await checkDaemon(layout, options.env);
880
+ if (!daemon.daemon) return failed("verify", daemon.checks);
881
+ const desired = await buildDesired(layout, marker.agents, marker.roles);
882
+ const checks = [...daemon.checks, ...desired.checks];
883
+ if (hasFailure(checks)) return failed("verify", checks);
884
+ return withSession(daemon.daemon, async (session) => {
885
+ const operations = await planRoom(session, desired, NOTHING_STALE);
886
+ const drifted = operations.filter((operation) => operation.action !== "noop");
887
+ const providers = drifted.filter((operation) => operation.kind === "provider").length;
888
+ const files = drifted.length - providers;
889
+ const all = [
890
+ ...checks,
891
+ files === 0 ? pass("room.files", "Every managed role file matches the current definition.") : fail("room.files", `${String(files)} managed role files are missing or outdated.`, "Run: paseo-room setup --apply"),
892
+ providers === 0 ? pass("room.providers", `All ${String(Object.keys(desired.providers).length)} Paseo providers are registered as expected.`) : fail("room.providers", `${String(providers)} Paseo providers are missing or differ.`, "Run: paseo-room setup --apply")
893
+ ];
894
+ return {
895
+ command: "verify",
896
+ outcome: hasFailure(all) ? "failed" : "ok",
897
+ changed: false,
898
+ checks: all,
899
+ operations
900
+ };
901
+ }, sessionOptions(options));
902
+ }
903
+ async function remove(options = {}) {
904
+ const layout = resolveLayout(options, options.env);
905
+ const marker = await readMarker(layout);
906
+ if (!marker) {
907
+ return failed("remove", [fail("room.marker", `No room found at ${layout.roomHome}.`, "Nothing to remove; the room home was never created here.")]);
908
+ }
909
+ const invalid = layoutChecks(layout, marker.agents);
910
+ if (invalid.length > 0) return failed("remove", invalid);
911
+ const ids = marker.agents.flatMap((agent) => marker.roles.map((role) => providerId(agent, role)));
912
+ const operations = [
913
+ ...ids.map((id) => ({ action: "remove", kind: "provider", target: id })),
914
+ { action: "remove", kind: "dir", target: layout.roomHome }
915
+ ];
916
+ if (!options.apply) {
917
+ return { command: "remove", outcome: "changes-planned", changed: false, checks: [], operations };
918
+ }
919
+ const daemon = await checkDaemon(layout, options.env);
920
+ const checks = [];
921
+ if (daemon.daemon) {
922
+ await withSession(daemon.daemon, (session) => session.removeProviders(ids), sessionOptions(options));
923
+ checks.push(pass("room.providers", `Removed ${String(ids.length)} Paseo providers.`));
924
+ } else {
925
+ checks.push({ id: "room.providers", status: "warn", message: "Paseo is unreachable; provider entries were left in place.", fix: "Start Paseo and run remove again to clear them." });
926
+ }
927
+ await rm2(layout.roomHome, { recursive: true, force: true });
928
+ checks.push(pass("room.files", `Deleted ${layout.roomHome}.`));
929
+ return { command: "remove", outcome: "ok", changed: true, checks, operations };
930
+ }
931
+ var AGENTS, NOTHING_STALE;
932
+ var init_commands = __esm({
933
+ "src/commands.ts"() {
934
+ "use strict";
935
+ init_package();
936
+ init_claude();
937
+ init_codex();
938
+ init_fsops();
939
+ init_layout();
940
+ init_paseo();
941
+ init_result();
942
+ init_instructions();
943
+ init_room();
944
+ init_roles();
945
+ AGENTS = { codex: codexAgent, claude: claudeAgent };
946
+ NOTHING_STALE = { providerIds: [], directories: [] };
947
+ }
948
+ });
949
+
950
+ // src/wizard.ts
951
+ var wizard_exports = {};
952
+ __export(wizard_exports, {
953
+ runWizard: () => runWizard,
954
+ terminalPrompts: () => terminalPrompts
955
+ });
956
+ import * as clack from "@clack/prompts";
957
+ async function runWizard(prompts, emit, write, options = {}) {
958
+ const cancelled = () => {
959
+ write("Cancelled; nothing was changed.\n");
960
+ return 0;
961
+ };
962
+ const action = await prompts.select({
963
+ message: "What do you want to do?",
964
+ options: [
965
+ { value: "setup", label: "Set up / update the room" },
966
+ { value: "verify", label: "Verify the current room" },
967
+ { value: "remove", label: "Remove the room" }
968
+ ]
969
+ });
970
+ if (typeof action === "symbol") return cancelled();
971
+ if (action === "verify") return emit(await verify(options));
972
+ if (action === "remove") {
973
+ const preview2 = await remove(options);
974
+ const status2 = emit(preview2);
975
+ if (preview2.outcome !== "changes-planned") return status2;
976
+ const approved2 = await prompts.confirm({ message: "Delete the room home and its Paseo providers?" });
977
+ return approved2 === true ? emit(await remove({ ...options, apply: true })) : cancelled();
978
+ }
979
+ const agents = await prompts.multiselect({
980
+ message: "Which coding agents should the room use?",
981
+ options: AGENT_IDS.map((value) => ({ value, label: AGENTS[value].label }))
982
+ });
983
+ if (typeof agents === "symbol" || agents.length === 0) return cancelled();
984
+ const preview = await setup({ ...options, agents });
985
+ const status = emit(preview);
986
+ if (preview.outcome !== "changes-planned") return status;
987
+ const approved = await prompts.confirm({ message: "Apply these changes?" });
988
+ if (approved !== true) return cancelled();
989
+ return emit(await setup({ ...options, agents, apply: true }));
990
+ }
991
+ var terminalPrompts;
992
+ var init_wizard = __esm({
993
+ "src/wizard.ts"() {
994
+ "use strict";
995
+ init_commands();
996
+ init_roles();
997
+ terminalPrompts = {
998
+ select: (options) => clack.select({ message: options.message, options: [...options.options] }),
999
+ multiselect: (options) => clack.multiselect({ message: options.message, options: [...options.options], required: true }),
1000
+ confirm: (options) => clack.confirm({ message: options.message, initialValue: false })
1001
+ };
1002
+ }
1003
+ });
1004
+
1005
+ // src/cli.ts
1006
+ init_package();
1007
+ init_commands();
1008
+ import { Command, CommanderError } from "commander";
1009
+
1010
+ // src/render.ts
1011
+ function renderJson(result) {
1012
+ return `${JSON.stringify({ schemaVersion: 2, ...result }, null, 2)}
1013
+ `;
1014
+ }
1015
+ var ICONS = { pass: "\u2713", warn: "!", fail: "\u2717" };
1016
+ function renderHuman(result) {
1017
+ const lines = [];
1018
+ for (const check of result.checks) {
1019
+ lines.push(`${ICONS[check.status]} ${check.message}`);
1020
+ if (check.fix) lines.push(` \u2192 ${check.fix}`);
1021
+ }
1022
+ const changes = result.operations.filter((operation) => operation.action !== "noop");
1023
+ if (changes.length > 0) {
1024
+ lines.push("", result.changed ? "Applied:" : "Planned changes:");
1025
+ for (const operation of changes) lines.push(` ${operation.action} ${operation.kind} ${operation.target}`);
1026
+ const unchanged = result.operations.length - changes.length;
1027
+ if (unchanged > 0) lines.push(` (${String(unchanged)} already up to date)`);
1028
+ } else if (result.operations.length > 0) {
1029
+ lines.push("", "Everything is already up to date.");
1030
+ }
1031
+ lines.push("", `${result.command}: ${result.outcome}`);
1032
+ return `${lines.join("\n")}
1033
+ `;
1034
+ }
1035
+
1036
+ // src/cli.ts
1037
+ init_result();
1038
+ init_roles();
1039
+ function collectAgent(value, previous) {
1040
+ const agent = AGENT_IDS.find((id) => id === value);
1041
+ if (!agent) throw new CommanderError(2, "agent", `Unknown agent: ${value}`);
1042
+ const seated = previous ?? [];
1043
+ return seated.includes(agent) ? seated : [...seated, agent];
1044
+ }
1045
+ var PATH_FLAGS = ["roomHome", "codexHome", "claudeHome", "codexBin", "claudeBin", "paseoBin"];
1046
+ function optionsFrom(raw, base) {
1047
+ const paths = {};
1048
+ for (const flag of PATH_FLAGS) {
1049
+ const value = raw[flag];
1050
+ if (typeof value === "string" && value.trim()) paths[flag] = value.trim();
1051
+ }
1052
+ const agents = Array.isArray(raw.agent) && raw.agent.length > 0 ? raw.agent : void 0;
1053
+ return { ...base, ...paths, ...agents ? { agents } : {}, ...raw.apply === true ? { apply: true } : {} };
1054
+ }
1055
+ async function runCli(argv, output, context = {}) {
1056
+ const password = (context.options?.env ?? process.env).PASEO_PASSWORD;
1057
+ const redact = (text) => password ? text.split(password).join("[redacted]") : text;
1058
+ let json = false;
1059
+ const emit = (result) => {
1060
+ output.stdout(redact(json ? renderJson(result) : renderHuman(result)));
1061
+ return exitCode(result);
1062
+ };
1063
+ const isTTY = context.isTTY ?? (process.stdin.isTTY && process.stdout.isTTY);
1064
+ const base = context.options ?? {};
1065
+ if (argv.length === 0) {
1066
+ if (!isTTY) {
1067
+ output.stderr("paseo-room: no command given. Try: paseo-room setup, verify, remove, or --help.\n");
1068
+ return 2;
1069
+ }
1070
+ const { runWizard: runWizard2, terminalPrompts: terminalPrompts2 } = await Promise.resolve().then(() => (init_wizard(), wizard_exports));
1071
+ return runWizard2(context.prompts ?? terminalPrompts2, emit, (text) => {
1072
+ output.stdout(text);
1073
+ }, base);
1074
+ }
1075
+ const program = new Command().name("paseo-room").description("Configure Codex/Claude role homes in $HOME and register them with your local Paseo daemon.").version(package_default.version).argument("<command>", "setup | verify | remove").option("--agent <agent>", "codex or claude; repeat for both (default: codex)", collectAgent).option("--apply", "actually make the changes (default: dry run)").option("--json", "machine-readable output").option("--room-home <path>", "where role homes are written (default: ~/.paseo-room)").option("--codex-home <path>", "source Codex config (default: ~/.codex)").option("--claude-home <path>", "source Claude Code config (default: ~/.claude)").option("--codex-bin <path>", "Codex executable (default: found on PATH)").option("--claude-bin <path>", "Claude Code executable (default: found on PATH)").option("--paseo-bin <path>", "Paseo executable (default: found on PATH)").configureOutput({ writeOut: (text) => {
1076
+ output.stdout(text);
1077
+ }, writeErr: (text) => {
1078
+ output.stderr(text);
1079
+ } }).exitOverride();
1080
+ let command;
1081
+ let options;
1082
+ try {
1083
+ program.parse([...argv], { from: "user" });
1084
+ const raw = program.opts();
1085
+ json = raw.json === true;
1086
+ command = program.args[0] ?? "";
1087
+ options = optionsFrom(raw, base);
1088
+ } catch (error) {
1089
+ if (error instanceof CommanderError && error.exitCode === 0) return 0;
1090
+ return 2;
1091
+ }
1092
+ try {
1093
+ if (command === "setup") return emit(await setup(options));
1094
+ if (command === "verify") return emit(await verify(options));
1095
+ if (command === "remove") return emit(await remove(options));
1096
+ } catch (error) {
1097
+ const detail = error instanceof Error ? error.message : "unknown error";
1098
+ return emit(failed(command, [fail(`${command}.error`, redact(detail), "Check that Paseo is running and reachable, then try again.")]));
1099
+ }
1100
+ output.stderr(`paseo-room: unknown command "${command}". Try: setup, verify, remove.
1101
+ `);
1102
+ return 2;
1103
+ }
1104
+
1105
+ // src/index.ts
1106
+ process.exitCode = await runCli(process.argv.slice(2), {
1107
+ stdout: (text) => {
1108
+ process.stdout.write(text);
1109
+ },
1110
+ stderr: (text) => {
1111
+ process.stderr.write(text);
1112
+ }
1113
+ });
1114
+ //# sourceMappingURL=index.js.map