paseo-room 0.3.0 → 0.4.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/README.md +10 -1
- package/dist/index.js +118 -28
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -45,6 +45,7 @@ interactive stdin/stdout/stderr, and targets exactly one selected agent and role
|
|
|
45
45
|
| `--agent <codex\|claude\|pi>` | `codex` | Which coding agent to seat. Repeat the flag to combine agents. |
|
|
46
46
|
| `--apply` | off | Actually write setup/remove changes; `auth login` does not use it. |
|
|
47
47
|
| `--json` | off | One machine-readable document instead of text. |
|
|
48
|
+
| `--no-claude-memory-contract` | off | Omit the role contract from Claude role `CLAUDE.md` files, leaving the room plugin as the only Claude carrier. Your global memory is still carried. `setup` only. |
|
|
48
49
|
| `--room-home <path>` | `~/.paseo-room` | Where role homes are written. |
|
|
49
50
|
| `--codex-home <path>` | `~/.codex` | Source Codex configuration. |
|
|
50
51
|
| `--claude-home <path>` | `~/.claude` | Source Claude Code configuration. |
|
|
@@ -99,7 +100,7 @@ starts, its exit code is preserved; a signal is returned using the conventional
|
|
|
99
100
|
auth.json # created and owned by Codex after role login, if file-backed
|
|
100
101
|
AGENTS.md, skills, plugins, hooks.json → symlinks into ~/.codex when present (Peer: see below)
|
|
101
102
|
roles/claude/<role>/
|
|
102
|
-
CLAUDE.md # your global memory + role instructions
|
|
103
|
+
CLAUDE.md # your global memory + role instructions (contract omitted with --no-claude-memory-contract)
|
|
103
104
|
settings.json # your settings.json + PASEO_ROOM_ROLE
|
|
104
105
|
.claude.json # seeded once from yours, then owned by Claude
|
|
105
106
|
.credentials.json # created and owned by Claude after role login, if file-backed
|
|
@@ -258,6 +259,14 @@ the plugin is absent, disabled, failed, registered from another path, or drifted
|
|
|
258
259
|
silently missing carrier is not a guarantee. The hook runs for newly created sessions;
|
|
259
260
|
recreate an existing Claude session after setup or an update.
|
|
260
261
|
|
|
262
|
+
`setup --no-claude-memory-contract` leaves the plugin as the only Claude carrier: each role
|
|
263
|
+
`CLAUDE.md` then holds your global memory alone, and the file is not written at all when you
|
|
264
|
+
have none. Only the contract is dropped, never your own memory, and an earlier generation of it
|
|
265
|
+
is removed rather than left behind. The choice is recorded in `room.json`, so `verify` compares
|
|
266
|
+
against it and rejects the flag itself; `setup` without the flag restores the fallback. Keep the
|
|
267
|
+
fallback unless you have a reason not to: the plugin hook is verified, but whether a *resumed*
|
|
268
|
+
session re-enters it is unproven, and `CLAUDE.md` is what covers that case.
|
|
269
|
+
|
|
261
270
|
Pi providers use a strict command tail:
|
|
262
271
|
|
|
263
272
|
```text
|
package/dist/index.js
CHANGED
|
@@ -15,7 +15,7 @@ var init_package = __esm({
|
|
|
15
15
|
"package.json"() {
|
|
16
16
|
package_default = {
|
|
17
17
|
name: "paseo-room",
|
|
18
|
-
version: "0.
|
|
18
|
+
version: "0.4.1",
|
|
19
19
|
description: "One CLI that configures Codex, Claude Code, and Pi role homes in $HOME and registers them with your local Paseo daemon.",
|
|
20
20
|
keywords: [
|
|
21
21
|
"paseo",
|
|
@@ -270,7 +270,7 @@ async function lstatOrAbsent(path) {
|
|
|
270
270
|
}
|
|
271
271
|
}
|
|
272
272
|
async function staleChildren(entry) {
|
|
273
|
-
const declared = new Set(entry.children);
|
|
273
|
+
const declared = /* @__PURE__ */ new Set([...entry.children, ...entry.reserved ?? []]);
|
|
274
274
|
let names;
|
|
275
275
|
try {
|
|
276
276
|
names = await readdir(entry.path);
|
|
@@ -282,6 +282,7 @@ async function staleChildren(entry) {
|
|
|
282
282
|
}
|
|
283
283
|
async function current(entry) {
|
|
284
284
|
const stat2 = await lstatOrAbsent(entry.path);
|
|
285
|
+
if (entry.kind === "absent") return stat2 ? "different" : "same";
|
|
285
286
|
if (!stat2) return "absent";
|
|
286
287
|
if (entry.kind === "dir") return stat2.isDirectory() ? "same" : "different";
|
|
287
288
|
if (entry.kind === "managed-dir") {
|
|
@@ -301,9 +302,9 @@ async function planEntries(entries) {
|
|
|
301
302
|
for (const entry of entries) {
|
|
302
303
|
const state = await current(entry);
|
|
303
304
|
operations.push({
|
|
304
|
-
action: state === "same" ? "noop" : state === "absent" ? "create" : "update",
|
|
305
|
+
action: state === "same" ? "noop" : entry.kind === "absent" ? "remove" : state === "absent" ? "create" : "update",
|
|
305
306
|
// A managed directory is still a directory to everything that reports operations.
|
|
306
|
-
kind: entry.kind === "managed-dir" ? "dir" : entry.kind,
|
|
307
|
+
kind: entry.kind === "managed-dir" ? "dir" : entry.kind === "absent" ? "file" : entry.kind,
|
|
307
308
|
target: entry.path
|
|
308
309
|
});
|
|
309
310
|
}
|
|
@@ -372,6 +373,12 @@ async function applyManagedDirectory(entry) {
|
|
|
372
373
|
await rm(child, { force: true });
|
|
373
374
|
}
|
|
374
375
|
}
|
|
376
|
+
async function removeManagedFile(path) {
|
|
377
|
+
const stat2 = await lstatOrAbsent(path);
|
|
378
|
+
if (!stat2) return;
|
|
379
|
+
if (!stat2.isFile()) refuse(path, "a regular file this room generated", stat2);
|
|
380
|
+
await rm(path, { force: true });
|
|
381
|
+
}
|
|
375
382
|
async function applyEntries(entries) {
|
|
376
383
|
for (const entry of entries) {
|
|
377
384
|
if (await current(entry) === "same") continue;
|
|
@@ -383,6 +390,10 @@ async function applyEntries(entries) {
|
|
|
383
390
|
await applyManagedDirectory(entry);
|
|
384
391
|
continue;
|
|
385
392
|
}
|
|
393
|
+
if (entry.kind === "absent") {
|
|
394
|
+
await removeManagedFile(entry.path);
|
|
395
|
+
continue;
|
|
396
|
+
}
|
|
386
397
|
await mkdir(dirname(entry.path), { recursive: true, mode: DIR_MODE });
|
|
387
398
|
await assertReplaceable(entry);
|
|
388
399
|
await replaceAtomically(entry);
|
|
@@ -461,12 +472,14 @@ var init_roles = __esm({
|
|
|
461
472
|
// src/room.ts
|
|
462
473
|
import { join as join3 } from "path";
|
|
463
474
|
import { z } from "zod";
|
|
464
|
-
function renderMarker(version, agents, roles, contract) {
|
|
475
|
+
function renderMarker(version, agents, roles, contract, claudeMemoryContract) {
|
|
465
476
|
return JSON.stringify({
|
|
466
477
|
version,
|
|
467
478
|
agents: [...agents],
|
|
468
479
|
roles: [...roles],
|
|
469
|
-
...contract === void 0 ? {} : { contract }
|
|
480
|
+
...contract === void 0 ? {} : { contract },
|
|
481
|
+
// Written only when it differs from the default, so an unchanged room's marker is unchanged.
|
|
482
|
+
...claudeMemoryContract === false ? { claudeMemoryContract: false } : {}
|
|
470
483
|
}, null, 2) + "\n";
|
|
471
484
|
}
|
|
472
485
|
async function readMarker(layout) {
|
|
@@ -494,7 +507,13 @@ var init_room = __esm({
|
|
|
494
507
|
* written before contract provenance existed has no digest, and such a marker must still
|
|
495
508
|
* parse rather than look like a foreign file.
|
|
496
509
|
*/
|
|
497
|
-
contract: z.string().min(1).optional()
|
|
510
|
+
contract: z.string().min(1).optional(),
|
|
511
|
+
/**
|
|
512
|
+
* False when this room deliberately suppressed the role contract in Claude's `CLAUDE.md`,
|
|
513
|
+
* leaving the plugin as its only Claude carrier. Optional and defaulted to true so a room
|
|
514
|
+
* written before the option existed keeps parsing and keeps its current behaviour.
|
|
515
|
+
*/
|
|
516
|
+
claudeMemoryContract: z.boolean().optional()
|
|
498
517
|
});
|
|
499
518
|
}
|
|
500
519
|
});
|
|
@@ -1021,9 +1040,9 @@ function isPaseoSkill(name) {
|
|
|
1021
1040
|
function receivesExecutableResources(role) {
|
|
1022
1041
|
return role !== "peer";
|
|
1023
1042
|
}
|
|
1024
|
-
async function skillNames(path) {
|
|
1043
|
+
async function skillNames(path, reserved) {
|
|
1025
1044
|
try {
|
|
1026
|
-
return (await readdir2(path)).filter((name) => !isPaseoSkill(name)).sort();
|
|
1045
|
+
return (await readdir2(path)).filter((name) => !isPaseoSkill(name) && !reserved.has(name)).sort();
|
|
1027
1046
|
} catch (error) {
|
|
1028
1047
|
if (error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT") return [];
|
|
1029
1048
|
throw error;
|
|
@@ -1032,6 +1051,7 @@ async function skillNames(path) {
|
|
|
1032
1051
|
async function roleResourceEntries(input) {
|
|
1033
1052
|
const present = new Set(input.shared);
|
|
1034
1053
|
const executable = new Set(input.executable);
|
|
1054
|
+
const reserved = new Set(input.reservedSkills ?? []);
|
|
1035
1055
|
const entries = [];
|
|
1036
1056
|
for (const name of input.names) {
|
|
1037
1057
|
const path = join6(input.home, name);
|
|
@@ -1039,8 +1059,14 @@ async function roleResourceEntries(input) {
|
|
|
1039
1059
|
if (!receivesExecutableResources(input.role)) {
|
|
1040
1060
|
if (executable.has(name)) continue;
|
|
1041
1061
|
if (name === SKILLS) {
|
|
1042
|
-
const children = present.has(path) ? await skillNames(path) : [];
|
|
1043
|
-
entries.push({
|
|
1062
|
+
const children = present.has(path) ? await skillNames(path, reserved) : [];
|
|
1063
|
+
entries.push({
|
|
1064
|
+
kind: "managed-dir",
|
|
1065
|
+
path: alias,
|
|
1066
|
+
children,
|
|
1067
|
+
legacyLink: path,
|
|
1068
|
+
...reserved.size === 0 ? {} : { reserved: [...reserved] }
|
|
1069
|
+
});
|
|
1044
1070
|
for (const child of children) entries.push({ kind: "link", path: join6(alias, child), target: join6(path, child) });
|
|
1045
1071
|
continue;
|
|
1046
1072
|
}
|
|
@@ -1098,8 +1124,10 @@ function renderRoleState(source) {
|
|
|
1098
1124
|
for (const key of SEEDED_KEYS) if (key in root) state[key] = root[key];
|
|
1099
1125
|
return JSON.stringify(state, null, 2) + "\n";
|
|
1100
1126
|
}
|
|
1101
|
-
function renderRoleMemory(source, role) {
|
|
1127
|
+
function renderRoleMemory(source, role, contract = true) {
|
|
1102
1128
|
const operator = source?.trim();
|
|
1129
|
+
if (!contract) return operator ? `${operator}
|
|
1130
|
+
` : void 0;
|
|
1103
1131
|
return operator ? `${operator}
|
|
1104
1132
|
|
|
1105
1133
|
${renderInstructions(role)}` : renderInstructions(role);
|
|
@@ -1153,7 +1181,7 @@ function mcpDriftCheck(role, home, path, divergence) {
|
|
|
1153
1181
|
`If this role should have the same servers, add them with: CLAUDE_CONFIG_DIR=${home} claude mcp add ... \u2014 or delete ${path} and run setup --apply to reseed it from your current Claude state, which discards that role's other runtime state.`
|
|
1154
1182
|
);
|
|
1155
1183
|
}
|
|
1156
|
-
var SHARED, EXECUTABLE, MCP_KEY, AUTH_ENV, CONTROL_PLANE_ENV, SECURE_STORAGE_ENV, SEEDED_KEYS, claudeAgent;
|
|
1184
|
+
var SHARED, EXECUTABLE, RUNTIME_SKILLS, MCP_KEY, AUTH_ENV, CONTROL_PLANE_ENV, SECURE_STORAGE_ENV, SEEDED_KEYS, claudeAgent;
|
|
1157
1185
|
var init_claude = __esm({
|
|
1158
1186
|
"src/agents/claude.ts"() {
|
|
1159
1187
|
"use strict";
|
|
@@ -1176,6 +1204,7 @@ var init_claude = __esm({
|
|
|
1176
1204
|
"themes"
|
|
1177
1205
|
];
|
|
1178
1206
|
EXECUTABLE = ["plugins", "commands", "hooks"];
|
|
1207
|
+
RUNTIME_SKILLS = ["synced"];
|
|
1179
1208
|
MCP_KEY = "mcpServers";
|
|
1180
1209
|
AUTH_ENV = [
|
|
1181
1210
|
"ANTHROPIC_AUTH_TOKEN",
|
|
@@ -1219,7 +1248,7 @@ var init_claude = __esm({
|
|
|
1219
1248
|
"CronList"
|
|
1220
1249
|
]
|
|
1221
1250
|
},
|
|
1222
|
-
async build(layout, roles) {
|
|
1251
|
+
async build(layout, roles, options = {}) {
|
|
1223
1252
|
const home = layout.agentHome.claude;
|
|
1224
1253
|
const binary = await which(layout.bin.claude, layout.searchPath);
|
|
1225
1254
|
if (!binary) {
|
|
@@ -1272,10 +1301,12 @@ var init_claude = __esm({
|
|
|
1272
1301
|
if (divergence) drift.push(mcpDriftCheck(role, target, statePath, divergence));
|
|
1273
1302
|
}
|
|
1274
1303
|
entries.push({ kind: "dir", path: target });
|
|
1275
|
-
|
|
1304
|
+
const memoryPath = join7(target, "CLAUDE.md");
|
|
1305
|
+
const memory = renderRoleMemory(memorySource, role, options.memoryContract ?? true);
|
|
1306
|
+
entries.push(memory === void 0 ? { kind: "absent", path: memoryPath } : { kind: "file", path: memoryPath, content: memory });
|
|
1276
1307
|
entries.push({ kind: "file", path: join7(target, "settings.json"), content: renderRoleSettings(settingsSource, role, target) });
|
|
1277
1308
|
entries.push({ kind: "file", path: statePath, content: renderRoleState(stateSource), once: true });
|
|
1278
|
-
entries.push(...await roleResourceEntries({ role, target, home, names: SHARED, shared, executable: EXECUTABLE }));
|
|
1309
|
+
entries.push(...await roleResourceEntries({ role, target, home, names: SHARED, shared, executable: EXECUTABLE, reservedSkills: RUNTIME_SKILLS }));
|
|
1279
1310
|
credentials.push(await claudeCredentialDiagnostic(layout, role, settingsSource, process.platform, binary));
|
|
1280
1311
|
providerEnv[role] = { [SECURE_STORAGE_ENV]: target };
|
|
1281
1312
|
}
|
|
@@ -2157,7 +2188,7 @@ function roleProfiles(agent, roles) {
|
|
|
2157
2188
|
notes: ROLE_NOTES[role]
|
|
2158
2189
|
}));
|
|
2159
2190
|
}
|
|
2160
|
-
async function buildDesired(layout, agents, roles) {
|
|
2191
|
+
async function buildDesired(layout, agents, roles, build = {}) {
|
|
2161
2192
|
const entries = [
|
|
2162
2193
|
{ kind: "dir", path: layout.roomHome },
|
|
2163
2194
|
{ kind: "dir", path: sharedRoom(layout) },
|
|
@@ -2169,7 +2200,7 @@ async function buildDesired(layout, agents, roles) {
|
|
|
2169
2200
|
const binaries = {};
|
|
2170
2201
|
for (const id of agents) {
|
|
2171
2202
|
const agent = AGENTS[id];
|
|
2172
|
-
const plan = await agent.build(layout, roles);
|
|
2203
|
+
const plan = await agent.build(layout, roles, build);
|
|
2173
2204
|
entries.push(...plan.entries);
|
|
2174
2205
|
checks.push(...plan.checks);
|
|
2175
2206
|
const credentials = plan.credentials ?? [];
|
|
@@ -2206,7 +2237,7 @@ async function buildDesired(layout, agents, roles) {
|
|
|
2206
2237
|
}
|
|
2207
2238
|
const pluginPath = agents.includes("claude") ? claudeCarrierPluginDir(layout) : void 0;
|
|
2208
2239
|
if (pluginPath !== void 0) entries.push(...claudeCarrierEntries(layout, roles));
|
|
2209
|
-
entries.push({ kind: "file", path: join11(layout.roomHome, MARKER), content: renderMarker(package_default.version, agents, roles, contractDigest()) });
|
|
2240
|
+
entries.push({ kind: "file", path: join11(layout.roomHome, MARKER), content: renderMarker(package_default.version, agents, roles, contractDigest(), markerMemoryContract(agents, build.memoryContract ?? true)) });
|
|
2210
2241
|
return { entries, providers, profiles, checks, ...pluginPath === void 0 ? {} : { pluginPath } };
|
|
2211
2242
|
}
|
|
2212
2243
|
function contractProvenance(previous) {
|
|
@@ -2232,6 +2263,14 @@ function thinkingDiagnostics(live, desired) {
|
|
|
2232
2263
|
`Choose a lower reasoning option for those seats in Paseo if you want the room's own defaults (${ROLE_THINKING.supervisor} for Supervisor, ${ROLE_THINKING.lead} for Lead and Peer).`
|
|
2233
2264
|
)];
|
|
2234
2265
|
}
|
|
2266
|
+
function fileDriftSummary(drifted) {
|
|
2267
|
+
const removals = drifted.filter((operation) => operation.action === "remove").length;
|
|
2268
|
+
const count = String(drifted.length);
|
|
2269
|
+
const subject = drifted.length === 1 ? "managed role file is" : "managed role files are";
|
|
2270
|
+
if (removals === 0) return `${count} ${subject} missing or outdated.`;
|
|
2271
|
+
if (removals === drifted.length) return `${count} ${subject} present but suppressed by this room.`;
|
|
2272
|
+
return `${count} ${subject} missing or outdated, and ${String(removals)} of them are present but suppressed by this room.`;
|
|
2273
|
+
}
|
|
2235
2274
|
function fileDriftDetail(targets) {
|
|
2236
2275
|
const catalogs = targets.filter((target) => target.endsWith(`${sep2}model-catalog.json`));
|
|
2237
2276
|
if (catalogs.length === 0) return "";
|
|
@@ -2394,9 +2433,21 @@ async function reconcilePlugin(session, desired, plan, pending, expectedPath) {
|
|
|
2394
2433
|
}
|
|
2395
2434
|
return [pluginRuntimeCheck(plugin, expectedPath)];
|
|
2396
2435
|
}
|
|
2436
|
+
function markerMemoryContract(agents, memoryContract) {
|
|
2437
|
+
return agents.includes("claude") ? memoryContract : void 0;
|
|
2438
|
+
}
|
|
2439
|
+
function memoryContractDiagnostic(agents, memoryContract) {
|
|
2440
|
+
if (memoryContract || !agents.includes("claude")) return [];
|
|
2441
|
+
return [warn(
|
|
2442
|
+
"claude.memory-contract",
|
|
2443
|
+
"Claude role CLAUDE.md files carry your global memory only: the role contract was not written to them, so the trusted server plugin is this room's only Claude carrier. Only a newly created agent passes through its creation hook, and whether a resumed session re-enters that hook is unproven.",
|
|
2444
|
+
"Drop --no-claude-memory-contract and run setup --apply to restore the file fallback."
|
|
2445
|
+
)];
|
|
2446
|
+
}
|
|
2397
2447
|
async function setup(options = {}) {
|
|
2398
2448
|
const layout = resolveLayout(options, options.env);
|
|
2399
2449
|
const agents = options.agents ?? ["codex"];
|
|
2450
|
+
const memoryContract = options.claudeMemoryContract ?? true;
|
|
2400
2451
|
const invalid = layoutChecks(layout, agents);
|
|
2401
2452
|
if (invalid.length > 0) return failed("setup", invalid);
|
|
2402
2453
|
const rootSafety = await roomPathSafety(layout, [], []);
|
|
@@ -2408,7 +2459,7 @@ async function setup(options = {}) {
|
|
|
2408
2459
|
const compatibilityAgents = [.../* @__PURE__ */ new Set([...agents, ...previous?.agents ?? []])];
|
|
2409
2460
|
const daemon = await checkDaemon(layout, options.env, minimumPaseoVersion(compatibilityAgents));
|
|
2410
2461
|
if (!daemon.daemon) return failed("setup", daemon.checks);
|
|
2411
|
-
const desired = await buildDesired(layout, agents, ROLES);
|
|
2462
|
+
const desired = await buildDesired(layout, agents, ROLES, { memoryContract });
|
|
2412
2463
|
const stale = await staleFrom(layout, previous, agents, ROLES);
|
|
2413
2464
|
const retained = stale.retainedDirectories.length === 0 ? [] : [warn(
|
|
2414
2465
|
"room.stale-role-homes-preserved",
|
|
@@ -2423,7 +2474,7 @@ async function setup(options = {}) {
|
|
|
2423
2474
|
const { operations, liveProfiles } = plan;
|
|
2424
2475
|
const pending = operations.filter((operation) => operation.action !== "noop");
|
|
2425
2476
|
const pluginPreconditions = desired.pluginPath === void 0 ? plan.pluginChecks : [pluginsEnabledCheck(await session.pluginsEnabled()), ...plan.pluginChecks];
|
|
2426
|
-
const preApplyDiagnostics = [...checks, ...pluginPreconditions, ...contractProvenance(previous), ...thinkingDiagnostics(liveProfiles, desired.profiles)];
|
|
2477
|
+
const preApplyDiagnostics = [...checks, ...pluginPreconditions, ...memoryContractDiagnostic(agents, memoryContract), ...contractProvenance(previous), ...thinkingDiagnostics(liveProfiles, desired.profiles)];
|
|
2427
2478
|
if (hasFailure(preApplyDiagnostics) || !options.apply) {
|
|
2428
2479
|
return {
|
|
2429
2480
|
command: "setup",
|
|
@@ -2451,13 +2502,15 @@ async function setup(options = {}) {
|
|
|
2451
2502
|
version: package_default.version,
|
|
2452
2503
|
agents: [...agents],
|
|
2453
2504
|
roles: [...ROLES],
|
|
2454
|
-
contract: contractDigest()
|
|
2505
|
+
contract: contractDigest(),
|
|
2506
|
+
...markerMemoryContract(agents, memoryContract) === false ? { claudeMemoryContract: false } : {}
|
|
2455
2507
|
};
|
|
2456
2508
|
const appliedChecks = [
|
|
2457
2509
|
...checks,
|
|
2458
2510
|
...pluginPreconditions,
|
|
2459
2511
|
...pluginChecks,
|
|
2460
2512
|
pass("room.applied", `Room ready at ${layout.roomHome} with ${String(ids.length)} Paseo providers.`),
|
|
2513
|
+
...memoryContractDiagnostic(agents, memoryContract),
|
|
2461
2514
|
...contractProvenance(appliedMarker),
|
|
2462
2515
|
...thinkingDiagnostics(appliedProfiles, desired.profiles)
|
|
2463
2516
|
];
|
|
@@ -2484,7 +2537,10 @@ async function verify(options = {}) {
|
|
|
2484
2537
|
if (hasFailure(pathSafety)) return failed("verify", pathSafety);
|
|
2485
2538
|
const daemon = await checkDaemon(layout, options.env, minimumPaseoVersion(marker.agents));
|
|
2486
2539
|
if (!daemon.daemon) return failed("verify", daemon.checks);
|
|
2487
|
-
const desired = await buildDesired(layout, marker.agents, marker.roles
|
|
2540
|
+
const desired = await buildDesired(layout, marker.agents, marker.roles, {
|
|
2541
|
+
// The room's own recorded choice, so verify compares against what setup wrote.
|
|
2542
|
+
memoryContract: marker.claudeMemoryContract ?? true
|
|
2543
|
+
});
|
|
2488
2544
|
const checks = [...daemon.checks, ...desired.checks];
|
|
2489
2545
|
if (hasFailure(checks)) return failed("verify", checks);
|
|
2490
2546
|
return withSession(daemon.daemon, async (session) => {
|
|
@@ -2504,11 +2560,12 @@ async function verify(options = {}) {
|
|
|
2504
2560
|
];
|
|
2505
2561
|
const all = [
|
|
2506
2562
|
...checks,
|
|
2507
|
-
files === 0 ? pass("room.files", "Every managed role file matches the current definition.") : fail("room.files", `${
|
|
2563
|
+
files === 0 ? pass("room.files", "Every managed role file matches the current definition.") : fail("room.files", `${fileDriftSummary(driftedFiles)}${fileDriftDetail(driftedFiles.map((operation) => operation.target))}`, "Run: paseo-room setup --apply"),
|
|
2508
2564
|
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"),
|
|
2509
2565
|
profiles === 0 ? pass("room.profiles", `All ${String(desired.profiles.length)} Paseo agent profiles are registered as expected.`) : fail("room.profiles", `${String(profiles)} Paseo agent profiles are missing or differ.`, "Run: paseo-room setup --apply"),
|
|
2510
2566
|
...pluginChecks,
|
|
2511
2567
|
// Diagnostics last: a warning never precedes the failure it might be mistaken for.
|
|
2568
|
+
...memoryContractDiagnostic(marker.agents, marker.claudeMemoryContract ?? true),
|
|
2512
2569
|
...contractProvenance(marker),
|
|
2513
2570
|
...thinkingDiagnostics(liveProfiles, desired.profiles)
|
|
2514
2571
|
];
|
|
@@ -2658,12 +2715,25 @@ async function runWizard(prompts, emit, write, options = {}, onAction) {
|
|
|
2658
2715
|
options: AGENT_IDS.map((value) => ({ value, label: AGENTS[value].label }))
|
|
2659
2716
|
});
|
|
2660
2717
|
if (typeof agents === "symbol" || agents.length === 0) return cancelled();
|
|
2661
|
-
|
|
2718
|
+
let claudeMemoryContract = true;
|
|
2719
|
+
if (agents.includes("claude")) {
|
|
2720
|
+
const carrier = await prompts.select({
|
|
2721
|
+
message: "Claude role contract carrier: the room plugin is always used. Also write the contract into each role CLAUDE.md?",
|
|
2722
|
+
options: [
|
|
2723
|
+
{ value: "both", label: "Yes \u2014 keep it as a fallback (recommended; resume behaviour is unproven)" },
|
|
2724
|
+
{ value: "plugin", label: "No \u2014 plugin only; CLAUDE.md keeps your global memory alone" }
|
|
2725
|
+
]
|
|
2726
|
+
});
|
|
2727
|
+
if (typeof carrier === "symbol") return cancelled();
|
|
2728
|
+
claudeMemoryContract = carrier === "both";
|
|
2729
|
+
}
|
|
2730
|
+
const setupOptions = { ...options, agents, claudeMemoryContract };
|
|
2731
|
+
const preview = await setup(setupOptions);
|
|
2662
2732
|
const status = emit(preview);
|
|
2663
2733
|
if (preview.outcome !== "changes-planned") return status;
|
|
2664
2734
|
const approved = await prompts.confirm({ message: "Apply these changes? Authentication will not be validated or started." });
|
|
2665
2735
|
if (approved !== true) return cancelled();
|
|
2666
|
-
return emit(await setup({ ...
|
|
2736
|
+
return emit(await setup({ ...setupOptions, apply: true }));
|
|
2667
2737
|
}
|
|
2668
2738
|
var terminalPrompts;
|
|
2669
2739
|
var init_wizard = __esm({
|
|
@@ -2683,6 +2753,7 @@ var init_wizard = __esm({
|
|
|
2683
2753
|
init_package();
|
|
2684
2754
|
init_auth();
|
|
2685
2755
|
init_commands();
|
|
2756
|
+
init_fsops();
|
|
2686
2757
|
import { Command, CommanderError, InvalidArgumentError } from "commander";
|
|
2687
2758
|
|
|
2688
2759
|
// src/render.ts
|
|
@@ -2729,7 +2800,14 @@ function optionsFrom(raw, base) {
|
|
|
2729
2800
|
if (typeof value === "string" && value.trim()) paths[flag] = value.trim();
|
|
2730
2801
|
}
|
|
2731
2802
|
const agents = Array.isArray(raw.agent) && raw.agent.length > 0 ? raw.agent : void 0;
|
|
2732
|
-
return {
|
|
2803
|
+
return {
|
|
2804
|
+
...base,
|
|
2805
|
+
...paths,
|
|
2806
|
+
...agents ? { agents } : {},
|
|
2807
|
+
...raw.apply === true ? { apply: true } : {},
|
|
2808
|
+
// Commander defaults a --no- flag to true, so only an explicit opt-out is carried.
|
|
2809
|
+
...raw.claudeMemoryContract === false ? { claudeMemoryContract: false } : {}
|
|
2810
|
+
};
|
|
2733
2811
|
}
|
|
2734
2812
|
function failedFromThrown(command, error) {
|
|
2735
2813
|
if (error instanceof PromptAssetError) {
|
|
@@ -2739,6 +2817,9 @@ function failedFromThrown(command, error) {
|
|
|
2739
2817
|
"Reinstall paseo-room, then try again."
|
|
2740
2818
|
)]);
|
|
2741
2819
|
}
|
|
2820
|
+
if (error instanceof ManagedPathError) {
|
|
2821
|
+
return failed(command, [fail(`${command}.managed-path`, error.message, error.fix)]);
|
|
2822
|
+
}
|
|
2742
2823
|
const detail = error instanceof Error ? error.message : "unknown error";
|
|
2743
2824
|
return failed(command, [fail(
|
|
2744
2825
|
`${command}.error`,
|
|
@@ -2779,7 +2860,7 @@ async function runCli(argv, output, context = {}) {
|
|
|
2779
2860
|
return emit(failedFromThrown(wizardCommand, error));
|
|
2780
2861
|
}
|
|
2781
2862
|
}
|
|
2782
|
-
const program = new Command().name("paseo-room").description("Configure Codex/Claude/Pi role homes in $HOME and register them with your local Paseo daemon.").version(package_default.version).usage("<setup|verify|remove> [options]\n auth login <codex|claude|pi> <supervisor|lead|peer> [options]").argument("<command>", "setup | verify | remove | auth").argument("[command-arguments...]", "auth login <agent> <role>").option("--agent <agent>", "codex, claude, or pi; repeat to combine (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("--pi-home <path>", "source Pi config (default: ~/.pi/agent)").option("--codex-bin <path>", "Codex executable (default: found on PATH)").option("--claude-bin <path>", "Claude Code executable (default: found on PATH)").option("--pi-bin <path>", "Pi executable (default: found on PATH)").option("--paseo-bin <path>", "Paseo executable (default: found on PATH)").configureOutput({ writeOut: (text) => {
|
|
2863
|
+
const program = new Command().name("paseo-room").description("Configure Codex/Claude/Pi role homes in $HOME and register them with your local Paseo daemon.").version(package_default.version).usage("<setup|verify|remove> [options]\n auth login <codex|claude|pi> <supervisor|lead|peer> [options]").argument("<command>", "setup | verify | remove | auth").argument("[command-arguments...]", "auth login <agent> <role>").option("--agent <agent>", "codex, claude, or pi; repeat to combine (default: codex)", collectAgent).option("--apply", "actually make the changes (default: dry run)").option("--json", "machine-readable output").option("--no-claude-memory-contract", "omit the role contract from Claude role CLAUDE.md files, leaving the room plugin as the only Claude carrier").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("--pi-home <path>", "source Pi config (default: ~/.pi/agent)").option("--codex-bin <path>", "Codex executable (default: found on PATH)").option("--claude-bin <path>", "Claude Code executable (default: found on PATH)").option("--pi-bin <path>", "Pi executable (default: found on PATH)").option("--paseo-bin <path>", "Paseo executable (default: found on PATH)").configureOutput({ writeOut: (text) => {
|
|
2783
2864
|
output.stdout(text);
|
|
2784
2865
|
}, writeErr: (text) => {
|
|
2785
2866
|
output.stderr(text);
|
|
@@ -2800,7 +2881,16 @@ async function runCli(argv, output, context = {}) {
|
|
|
2800
2881
|
return 2;
|
|
2801
2882
|
}
|
|
2802
2883
|
try {
|
|
2884
|
+
if (raw.claudeMemoryContract === false && (command === "verify" || command === "remove")) {
|
|
2885
|
+
output.stderr(`paseo-room ${command}: --no-claude-memory-contract is a setup choice recorded in the room marker; ${command} reads it from there.
|
|
2886
|
+
`);
|
|
2887
|
+
return 2;
|
|
2888
|
+
}
|
|
2803
2889
|
if (command === "auth") {
|
|
2890
|
+
if (raw.claudeMemoryContract === false) {
|
|
2891
|
+
output.stderr("paseo-room auth login only authenticates a role and does not use --no-claude-memory-contract.\n");
|
|
2892
|
+
return 2;
|
|
2893
|
+
}
|
|
2804
2894
|
if (raw.apply === true) {
|
|
2805
2895
|
output.stderr("paseo-room auth login is already explicit and does not use --apply.\n");
|
|
2806
2896
|
return 2;
|