@topolo/cli 0.13.8 → 0.13.10

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 CHANGED
@@ -14,7 +14,7 @@ function registerAgentPacksCommand(program2) {
14
14
  throw new Error("Exact closed smoke operation, role/case and UUID required.");
15
15
  const store = packs.opts().store ?? resolve(homedir(), "AgentPacks");
16
16
  if (options.harness === "claude") {
17
- const { operateClaudeSmoke } = await import("./claude-pack-backend-3YJNKMYD.js");
17
+ const { operateClaudeSmoke } = await import("./claude-pack-backend-RMSOPHVQ.js");
18
18
  console.log(JSON.stringify(await operateClaudeSmoke({
19
19
  store,
20
20
  kind: "smoke-" + operation,
@@ -44,7 +44,7 @@ function registerAgentPacksCommand(program2) {
44
44
  });
45
45
  packs.command("configure").description("Qualify and configure an adopted Claude roster through its live native backend").argument("<pack>").option("--harness <name>", "Host adapter", "claude").requiredOption("--policy <path>", "Reviewed execution policy JSON").requiredOption("--endpoint <url>", "Explicit loopback main-process inspector endpoint, enabled separately with authorization").requiredOption("--pid <pid>", "Expected Claude main-process PID").option("--apply", "Apply native groups, pins, models, effort and permission modes; otherwise preflight only").action(async (pack, options) => {
46
46
  if (options.harness !== "claude") throw new Error("Native configure is currently qualified only for Claude Desktop.");
47
- const { configureClaudePack } = await import("./claude-pack-backend-3YJNKMYD.js");
47
+ const { configureClaudePack } = await import("./claude-pack-backend-RMSOPHVQ.js");
48
48
  const result = await configureClaudePack({
49
49
  store: packs.opts().store ?? resolve(homedir(), "AgentPacks"),
50
50
  pack,
@@ -67,8 +67,10 @@ function registerAgentPacksCommand(program2) {
67
67
  "list",
68
68
  "recover",
69
69
  "doctor",
70
+ "open",
70
71
  "policy",
71
72
  "permissions",
73
+ "lane",
72
74
  "approvals",
73
75
  "remote-recover",
74
76
  "connect",
@@ -80,9 +82,40 @@ function registerAgentPacksCommand(program2) {
80
82
  for (const name of commands) {
81
83
  const command = packs.command(name).description(name === "add" ? "Add an installed or bundled pack, group, or agent" : `Run the pack ${name} operation`);
82
84
  if (name === "switch" || name === "recover") command.option("--policy <path>", "Reviewed policy for an adopted Claude roster").option("--endpoint <url>", "Explicit authorized Claude main-process inspector endpoint").option("--pid <pid>", "Expected Claude main-process PID").option("--apply", "Apply a native switch; recovery is always read-only");
83
- if (name === "approvals") command.option("--intake <id>", "Exact owning native intake identity").option("--fingerprint <hash>", "Exact pending native request fingerprint").option("--response <path>", "Recorded one-request response JSON").option("--human-decision-recorded", "Assert that the exact human decision is recorded in the intake");
85
+ if (name === "lane") command.option("--input <path>", "Closed native lifecycle input JSON").option("--pack <name>", "Exact installed pack for Claude native lifecycle").option("--policy <path>", "Current reviewed source execution policy").option("--endpoint <url>", "Explicit authorized current Claude main-process inspector endpoint").option("--pid <pid>", "Expected current Claude native main PID").option("--connection <id>", "Exact explicit owned lane connection");
86
+ if (name === "approvals") command.option("--connection <id>", "Exact private dynamic lane control connection").option("--intake <id>", "Exact owning native intake identity").option("--fingerprint <hash>", "Exact pending native request fingerprint").option("--response <path>", "Recorded one-request response JSON").option("--human-decision-recorded", "Assert that the exact human decision is recorded in the intake");
84
87
  command.option("--harness <name>", "Local host adapter (codex, claude Desktop Code sessions)", "codex").option("--adapter-help", "Show every option supported by the selected adapter").argument("[arguments...]").allowUnknownOption().allowExcessArguments().addHelpText("after", name === "add" ? "\nExamples:\n topolo agent-packs add founder\n topolo agent-packs add founder --group specialists\n topolo agent-packs add founder --agent strategy\n" : "\nRun topolo agent-packs <command> --adapter-help for all adapter options. Cloud commands require explicit --environment and --context-key.\n").action(async (args, options) => {
85
88
  if (!["codex", "claude"].includes(options.harness)) throw new Error(`The ${options.harness} pack adapter is not available. Supported adapters: codex, claude.`);
89
+ if (name === "lane" && options.harness === "claude" && !options.adapterHelp && !["status", "models-template", "models-install"].includes(args[0] ?? "")) {
90
+ if (options.connection) {
91
+ if (args.length !== 1 || !["read", "context-start", "context-read", "fixture-start", "fixture-read", "retire", "close"].includes(args[0]))
92
+ throw new Error("Closed existing Claude lane control operation required.");
93
+ const input3 = options.input ? JSON.parse(readFileSync(options.input, "utf8")) : {};
94
+ if (!input3 || typeof input3 !== "object" || Array.isArray(input3) || Object.keys(input3).some((k) => k !== "operationId")) throw new Error("Only an exact bootstrap/fixture operation UUID may be supplied.");
95
+ const { operateClaudeLaneControl } = await import("./claude-pack-backend-RMSOPHVQ.js");
96
+ console.log(JSON.stringify(await operateClaudeLaneControl({
97
+ store: packs.opts().store ?? resolve(homedir(), "AgentPacks"),
98
+ connection: options.connection,
99
+ operation: { kind: args[0], ...input3 }
100
+ }), null, 2));
101
+ return;
102
+ }
103
+ if (args.length !== 1 || !options.input || !options.pack || !options.policy || !options.endpoint || !options.pid)
104
+ throw new Error("Closed Claude lane operation requires --input, --pack, --policy, --endpoint and --pid.");
105
+ const input2 = JSON.parse(readFileSync(options.input, "utf8"));
106
+ if (!input2 || typeof input2 !== "object" || Array.isArray(input2)) throw new Error("Closed native lane input object required.");
107
+ const { operateClaudeLane } = await import("./claude-pack-backend-RMSOPHVQ.js");
108
+ console.log(JSON.stringify(await operateClaudeLane({
109
+ store: packs.opts().store ?? resolve(homedir(), "AgentPacks"),
110
+ pack: options.pack,
111
+ policy: options.policy,
112
+ endpoint: options.endpoint,
113
+ pid: Number(options.pid),
114
+ operation: args[0],
115
+ input: input2
116
+ }), null, 2));
117
+ return;
118
+ }
86
119
  if (name === "approvals" && options.harness === "claude" && !options.adapterHelp) {
87
120
  if (!["list", "details", "respond"].includes(args[0] ?? "") || args.length > 2)
88
121
  throw new Error("Approvals requires list, details KEY, or respond KEY.");
@@ -96,7 +129,20 @@ function registerAgentPacksCommand(program2) {
96
129
  throw new Error("Only a one-request once/deny response is supported.");
97
130
  decision = response.decision;
98
131
  }
99
- const { operateClaudeApprovals } = await import("./claude-pack-backend-3YJNKMYD.js");
132
+ if (options.connection) {
133
+ const { operateClaudeLaneControl } = await import("./claude-pack-backend-RMSOPHVQ.js");
134
+ console.log(JSON.stringify(await operateClaudeLaneControl({
135
+ store: packs.opts().store ?? resolve(homedir(), "AgentPacks"),
136
+ connection: options.connection,
137
+ operation: {
138
+ kind: "approval-" + args[0],
139
+ ...args[0] !== "list" ? { key: args[1], intake: options.intake, fingerprint: options.fingerprint } : {},
140
+ ...args[0] === "respond" ? { decision, humanDecisionRecorded: options.humanDecisionRecorded } : {}
141
+ }
142
+ }), null, 2));
143
+ return;
144
+ }
145
+ const { operateClaudeApprovals } = await import("./claude-pack-backend-RMSOPHVQ.js");
100
146
  console.log(JSON.stringify(await operateClaudeApprovals({
101
147
  store: packs.opts().store ?? resolve(homedir(), "AgentPacks"),
102
148
  kind: args[0],
@@ -108,10 +154,10 @@ function registerAgentPacksCommand(program2) {
108
154
  }), null, 2));
109
155
  return;
110
156
  }
111
- if (options.policy || options.endpoint || options.pid || options.apply) {
157
+ if (name !== "lane" && (options.policy || options.endpoint || options.pid || options.apply)) {
112
158
  if (options.harness !== "claude" || !options.policy || !options.endpoint || !options.pid || args.length !== 1)
113
159
  throw new Error("Native switch/recovery requires one Claude pack, --policy, --endpoint and --pid.");
114
- const { configureClaudePack } = await import("./claude-pack-backend-3YJNKMYD.js");
160
+ const { configureClaudePack } = await import("./claude-pack-backend-RMSOPHVQ.js");
115
161
  const result2 = await configureClaudePack({
116
162
  store: packs.opts().store ?? resolve(homedir(), "AgentPacks"),
117
163
  pack: args[0],
@@ -129,7 +175,12 @@ function registerAgentPacksCommand(program2) {
129
175
  if (!adapter) throw new Error("This CLI installation is missing its bundled Topolo Agent Pack adapter. Reinstall @topolo/cli.");
130
176
  const store = packs.opts().store;
131
177
  const forwarded = (options.adapterHelp ? ["--help"] : args).map((value) => value === "--group" ? "--set" : value);
178
+ if (name === "lane" && !options.adapterHelp) {
179
+ if (options.input) forwarded.push("--input", options.input);
180
+ if (options.connection) forwarded.push("--connection", options.connection);
181
+ }
132
182
  if (name === "approvals" && !options.adapterHelp) {
183
+ if (options.connection) forwarded.push("--connection", options.connection);
133
184
  if (options.intake) forwarded.push("--intake", options.intake);
134
185
  if (options.fingerprint) forwarded.push("--fingerprint", options.fingerprint);
135
186
  if (options.response) forwarded.push("--response", options.response);
@@ -158,7 +209,7 @@ import { platform as platform2 } from "os";
158
209
  import { createInterface } from "readline/promises";
159
210
  import { stdin as input } from "process";
160
211
 
161
- // node_modules/.pnpm/@topolo+sdk@0.23.20/node_modules/@topolo/sdk/dist/index.js
212
+ // node_modules/.pnpm/@topolo+sdk@0.23.21/node_modules/@topolo/sdk/dist/index.js
162
213
  function record(value) {
163
214
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
164
215
  }
@@ -523,6 +574,9 @@ function publicTopoloErrorMessage(error) {
523
574
  const code = canonicalTopoloServiceErrorCode(
524
575
  typeof error === "string" ? error : error.code ?? extractTopoloServiceErrorCode(error.body)
525
576
  ) ?? "request_failed";
577
+ if (code === "organization_policy_admin_permission_required") {
578
+ return "Organization policy administration permission is required.";
579
+ }
526
580
  if (/(?:organization|org).*(?:enroll|membership|member|admission)/.test(code)) {
527
581
  return "Organization or application enrollment is required.";
528
582
  }
@@ -550,6 +604,9 @@ function topoloErrorHint(error) {
550
604
  if (code === "enrollment_required" || code === "membership_required" || /(?:organization|org).*(?:enroll|membership|member|admission)|(?:enroll|membership).*(?:organization|org)/.test(code)) {
551
605
  return "The current identity is not enrolled for this organization or application. Use an enrolled identity or ask an organization administrator to complete enrollment.";
552
606
  }
607
+ if (code === "organization_policy_admin_permission_required") {
608
+ return "This action requires explicit organization policy administration permission. Ask an authorized organization administrator to review the exact grant and the published principal and workspace authorization contract.";
609
+ }
553
610
  if (/(?:organization|org).*(?:context|required|missing|invalid|unknown|not_found)/.test(code)) {
554
611
  return "Select a signed-in organization context, then verify the effective identity and organization before retrying.";
555
612
  }
@@ -1741,7 +1798,7 @@ var TopoloClient = class _TopoloClient {
1741
1798
  } catch (error) {
1742
1799
  const transportFailure = !(error instanceof TopoloSdkError) && !opts.signal?.aborted;
1743
1800
  if (!transportFailure || attempt === maxAttempts) throw error;
1744
- await new Promise((resolve4) => setTimeout(resolve4, 250));
1801
+ await new Promise((resolve5) => setTimeout(resolve5, 250));
1745
1802
  }
1746
1803
  }
1747
1804
  throw new TopoloSdkError("network_error", "Topolo read retry loop exhausted.");
@@ -3740,7 +3797,7 @@ function createTopolo(options) {
3740
3797
  async function pollUntilApproved(params) {
3741
3798
  let interval = Math.max(params.intervalSeconds ?? 5, 1);
3742
3799
  while (Date.now() < params.expiresAtMs) {
3743
- await new Promise((resolve4) => setTimeout(resolve4, Math.min(interval * 1e3, params.expiresAtMs - Date.now())));
3800
+ await new Promise((resolve5) => setTimeout(resolve5, Math.min(interval * 1e3, params.expiresAtMs - Date.now())));
3744
3801
  if (Date.now() >= params.expiresAtMs) break;
3745
3802
  try {
3746
3803
  return await params.oauth.pollDeviceToken({
@@ -3768,7 +3825,7 @@ async function pollUntilApproved(params) {
3768
3825
  throw new Error("Device code expired. Run `topolo auth login` again.");
3769
3826
  }
3770
3827
 
3771
- // node_modules/.pnpm/@topolo+sdk@0.23.20/node_modules/@topolo/sdk/dist/node/credential-store.js
3828
+ // node_modules/.pnpm/@topolo+sdk@0.23.21/node_modules/@topolo/sdk/dist/node/credential-store.js
3772
3829
  import { mkdir as mkdir2, readFile as readFile2, writeFile as writeFile2, chmod as chmod2, lstat, readdir as readdir2 } from "fs/promises";
3773
3830
  import { existsSync as existsSync2, mkdirSync, lstatSync, readdirSync, readFileSync as readFileSync2, openSync, writeFileSync, fsyncSync, closeSync, renameSync, unlinkSync, rmdirSync, linkSync } from "fs";
3774
3831
  import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
@@ -6521,7 +6578,7 @@ import { spawnSync as spawnSync4 } from "child_process";
6521
6578
  import { existsSync as existsSync6 } from "fs";
6522
6579
  import { readFile as readFile6, rm as rm2 } from "fs/promises";
6523
6580
  import { homedir as homedir5 } from "os";
6524
- import { dirname as dirname6, join as join7, resolve as resolve2 } from "path";
6581
+ import { dirname as dirname6, join as join7, resolve as resolve3 } from "path";
6525
6582
  import { fileURLToPath as fileURLToPath3 } from "url";
6526
6583
  import { createRequire } from "module";
6527
6584
 
@@ -6530,7 +6587,7 @@ import { spawnSync as spawnSync3 } from "child_process";
6530
6587
  import { existsSync as existsSync4 } from "fs";
6531
6588
  import { readFile as readFile4 } from "fs/promises";
6532
6589
  import { homedir as homedir4 } from "os";
6533
- import { isAbsolute, join as join6 } from "path";
6590
+ import { isAbsolute, join as join6, resolve as resolve2 } from "path";
6534
6591
  var SUPPORTED_AGENT_IDS = [
6535
6592
  "claude",
6536
6593
  "claude-desktop",
@@ -6570,6 +6627,18 @@ var AGENT_NAME_FOR = {
6570
6627
  goose: "goose",
6571
6628
  "amazon-q": "amazon-q"
6572
6629
  };
6630
+ function resolveAgentHostTarget(options) {
6631
+ const homeDir = resolve2(options.homeDir ?? homedir4());
6632
+ const codexHome = options.codexHome ?? (options.homeDir === void 0 ? process.env.CODEX_HOME || void 0 : void 0);
6633
+ if (codexHome !== void 0 && (!codexHome.trim() || !isAbsolute(codexHome))) {
6634
+ throw new Error("Codex home must be an absolute nonempty directory.");
6635
+ }
6636
+ return Object.freeze({
6637
+ homeDir,
6638
+ ...options.projectDir ? { projectDir: resolve2(options.projectDir) } : {},
6639
+ codexHome: codexHome ?? join6(homeDir, ".codex")
6640
+ });
6641
+ }
6573
6642
  var RESERVED_OPERATOR_REGISTRATION = ["topolo", "platform"].join("-");
6574
6643
  function isPublicRegistrationName(name) {
6575
6644
  return name === "topolo" || name.startsWith("topolo-") && name !== RESERVED_OPERATOR_REGISTRATION;
@@ -6585,14 +6654,14 @@ function stableNodeCommand() {
6585
6654
  });
6586
6655
  return selectStableNodeCommand(probe.status === 0 ? probe.stdout : void 0);
6587
6656
  }
6588
- function agentSkillPath(agent, homeDir = homedir4(), projectDir) {
6657
+ function agentSkillPath(agent, homeDir = homedir4(), projectDir, codexHome) {
6589
6658
  switch (agent) {
6590
6659
  case "claude":
6591
6660
  return projectDir ? join6(projectDir, ".claude", "skills", "topolo", "SKILL.md") : join6(homeDir, ".claude", "skills", "topolo", "SKILL.md");
6592
6661
  case "claude-desktop":
6593
6662
  return join6(homeDir, ".claude", "skills", "topolo", "SKILL.md");
6594
6663
  case "codex":
6595
- return join6(homeDir, ".codex", "skills", "topolo", "SKILL.md");
6664
+ return join6(codexHome ?? join6(homeDir, ".codex"), "skills", "topolo", "SKILL.md");
6596
6665
  case "cursor":
6597
6666
  return join6(projectDir ?? homeDir, ".cursor", "rules", "topolo.mdc");
6598
6667
  case "grok":
@@ -6610,10 +6679,10 @@ function agentSkillPath(agent, homeDir = homedir4(), projectDir) {
6610
6679
  return void 0;
6611
6680
  }
6612
6681
  }
6613
- function agentConfigPaths(agent, homeDir = homedir4(), projectDir) {
6682
+ function agentConfigPaths(agent, homeDir = homedir4(), projectDir, codexHome) {
6614
6683
  switch (agent) {
6615
6684
  case "claude":
6616
- return [{ path: join6(homeDir, ".claude.json"), format: "json", rootKey: "mcpServers" }];
6685
+ return [{ path: projectDir ? join6(projectDir, ".mcp.json") : join6(homeDir, ".claude.json"), format: "json", rootKey: "mcpServers" }];
6617
6686
  case "claude-desktop":
6618
6687
  return [{
6619
6688
  path: claudeDesktopConfigPath(homeDir),
@@ -6621,7 +6690,7 @@ function agentConfigPaths(agent, homeDir = homedir4(), projectDir) {
6621
6690
  rootKey: "mcpServers"
6622
6691
  }];
6623
6692
  case "codex":
6624
- return [{ path: join6(homeDir, ".codex", "config.toml"), format: "toml" }];
6693
+ return [{ path: join6(codexHome ?? join6(homeDir, ".codex"), "config.toml"), format: "toml" }];
6625
6694
  case "cursor":
6626
6695
  return [{
6627
6696
  path: projectDir ? join6(projectDir, ".cursor", "mcp.json") : join6(homeDir, ".cursor", "mcp.json"),
@@ -6702,8 +6771,36 @@ function agentCliNames(agent) {
6702
6771
  return ["qchat", "q"];
6703
6772
  }
6704
6773
  }
6705
- function agentConfigExists(agent, homeDir = homedir4(), projectDir) {
6706
- return agentConfigPaths(agent, homeDir, projectDir).some((config) => existsSync4(config.path));
6774
+ function agentConfigExists(agent, homeDir = homedir4(), projectDir, codexHome) {
6775
+ return agentConfigPaths(agent, homeDir, projectDir, codexHome).some((config) => existsSync4(config.path));
6776
+ }
6777
+ async function assertClaudeProjectScope(homeDir, projectDir, name) {
6778
+ const path = join6(homeDir, ".claude.json");
6779
+ let contents;
6780
+ try {
6781
+ contents = await readFile4(path, "utf8");
6782
+ } catch (error) {
6783
+ if (error.code === "ENOENT") return;
6784
+ throw error;
6785
+ }
6786
+ let config;
6787
+ try {
6788
+ config = JSON.parse(contents);
6789
+ if (!config || typeof config !== "object" || Array.isArray(config)) throw new Error();
6790
+ } catch {
6791
+ throw new Error("Cannot verify malformed Claude scope configuration at " + path + ".");
6792
+ }
6793
+ const object = (value) => {
6794
+ if (value === void 0) return void 0;
6795
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
6796
+ throw new Error("Cannot verify malformed Claude scope configuration at " + path + ".");
6797
+ }
6798
+ return value;
6799
+ };
6800
+ const local = object(object(config.projects)?.[resolve2(projectDir)]);
6801
+ if (Object.hasOwn(object(config.mcpServers) ?? {}, name) || Object.hasOwn(object(local?.mcpServers) ?? {}, name)) {
6802
+ throw new Error("Claude MCP scope conflict for " + name + ": a user or local definition can override the project registration. Resolve that scope before setup.");
6803
+ }
6707
6804
  }
6708
6805
  function claudeDesktopConfigPath(homeDir) {
6709
6806
  if (process.platform === "darwin") return join6(homeDir, "Library", "Application Support", "Claude", "claude_desktop_config.json");
@@ -6715,9 +6812,9 @@ function vscodeUserConfigPath(homeDir) {
6715
6812
  if (process.platform === "win32") return join6(process.env.APPDATA ?? join6(homeDir, "AppData", "Roaming"), "Code", "User", "mcp.json");
6716
6813
  return join6(process.env.XDG_CONFIG_HOME ?? join6(homeDir, ".config"), "Code", "User", "mcp.json");
6717
6814
  }
6718
- async function readAgentRegistrations(agent, homeDir = homedir4(), projectDir) {
6815
+ async function readAgentRegistrations(agent, homeDir = homedir4(), projectDir, codexHome) {
6719
6816
  const registrations = [];
6720
- for (const config of agentConfigPaths(agent, homeDir, projectDir)) {
6817
+ for (const config of agentConfigPaths(agent, homeDir, projectDir, codexHome)) {
6721
6818
  if (!existsSync4(config.path)) continue;
6722
6819
  if (config.format === "json") {
6723
6820
  registrations.push(...await readJsonRegistrations(config.path, config.rootKey ?? "mcpServers", agent));
@@ -6982,17 +7079,19 @@ function message(error) {
6982
7079
 
6983
7080
  // src/commands/agent-config.ts
6984
7081
  import { existsSync as existsSync5 } from "fs";
6985
- import { mkdir as mkdir3, readFile as readFile5, rename as rename2, writeFile as writeFile3 } from "fs/promises";
7082
+ import { lstat as lstat2, mkdir as mkdir3, open, readFile as readFile5, rename as rename2, unlink, writeFile as writeFile3 } from "fs/promises";
7083
+ import { randomUUID as randomUUID3 } from "crypto";
6986
7084
  import { dirname as dirname5 } from "path";
6987
- async function installAgentSkill(agent, source, homeDir, projectDir) {
6988
- const path = agentSkillPath(agent, homeDir, projectDir);
7085
+ async function installAgentSkill(agent, source, homeDir, projectDir, codexHome) {
7086
+ const path = agentSkillPath(agent, homeDir, projectDir, codexHome);
6989
7087
  if (!path) return { installed: false };
6990
7088
  const contents = formatSkillForAgent(agent, source);
6991
7089
  await atomicWrite(path, contents);
6992
7090
  return { path, installed: true };
6993
7091
  }
6994
- async function registerAgentConfig(agent, mcp, homeDir, registration, projectDir) {
6995
- for (const config of agentConfigPaths(agent, homeDir, projectDir)) {
7092
+ async function registerAgentConfig(agent, mcp, homeDir, registration, projectDir, codexHome) {
7093
+ if (agent === "claude" && projectDir) await assertClaudeProjectScope(homeDir, projectDir, registration.name);
7094
+ for (const config of agentConfigPaths(agent, homeDir, projectDir, codexHome)) {
6996
7095
  if (config.format === "json") {
6997
7096
  await registerJson(config, agent, mcp, registration);
6998
7097
  } else if (config.format === "toml") {
@@ -7002,8 +7101,8 @@ async function registerAgentConfig(agent, mcp, homeDir, registration, projectDir
7002
7101
  }
7003
7102
  }
7004
7103
  }
7005
- async function unregisterAgentConfig(agent, homeDir, name, projectDir) {
7006
- for (const config of agentConfigPaths(agent, homeDir, projectDir)) {
7104
+ async function unregisterAgentConfig(agent, homeDir, name, projectDir, codexHome) {
7105
+ for (const config of agentConfigPaths(agent, homeDir, projectDir, codexHome)) {
7007
7106
  if (!existsSync5(config.path)) continue;
7008
7107
  if (config.format === "json") {
7009
7108
  await unregisterJson(config, agent, name);
@@ -7016,18 +7115,39 @@ async function unregisterAgentConfig(agent, homeDir, name, projectDir) {
7016
7115
  }
7017
7116
  }
7018
7117
  function registrationEnvironment(agent, registration) {
7118
+ let privateAliases = {};
7119
+ if (registration.configPath) {
7120
+ const prefixes = {
7121
+ production: "TOPOLO_PRODUCTION",
7122
+ staging: "TOPOLO_STAGING",
7123
+ development: "TOPOLO_DEVELOPMENT",
7124
+ local: "TOPOLO_LOCAL"
7125
+ };
7126
+ const prefix = prefixes[registration.environment];
7127
+ if (!prefix) throw new Error("Cannot isolate a private profile for an unknown environment.");
7128
+ privateAliases = {
7129
+ TOPOLO_API_KEY: "",
7130
+ TOPOLO_ACCESS_TOKEN: "",
7131
+ [prefix + "_API_KEY"]: "",
7132
+ [prefix + "_ACCESS_TOKEN"]: "",
7133
+ TOPOLO_ENV: registration.environment
7134
+ };
7135
+ }
7019
7136
  return {
7020
7137
  TOPOLO_AGENT_NAME: AGENT_NAME_FOR[agent],
7021
7138
  ...registration.configPath ? { TOPOLO_CONFIG_PATH: registration.configPath } : {},
7022
7139
  ...registration.installationId ? { TOPOLO_AGENT_INSTALLATION_ID: registration.installationId } : {},
7023
- ...registration.context ? { TOPOLO_CONTEXT: registration.context, TOPOLO_ENV: registration.environment } : {}
7140
+ ...registration.context ? { TOPOLO_CONTEXT: registration.context, TOPOLO_ENV: registration.environment } : {},
7141
+ ...privateAliases
7024
7142
  };
7025
7143
  }
7026
7144
  async function registerJson(config, agent, mcp, registration) {
7145
+ const before = await fileSnapshot(config.path);
7027
7146
  let parsed = {};
7028
- if (existsSync5(config.path)) {
7147
+ if (before.contents !== void 0) {
7029
7148
  try {
7030
- parsed = JSON.parse(await readFile5(config.path, "utf8"));
7149
+ parsed = JSON.parse(before.contents);
7150
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Expected an object.");
7031
7151
  } catch (error) {
7032
7152
  throw new Error(
7033
7153
  "Cannot update malformed " + (agent === "claude" ? "Claude" : AGENT_LABELS[agent]) + " configuration at " + config.path + ": " + (error instanceof Error ? error.message : String(error))
@@ -7048,7 +7168,7 @@ async function registerJson(config, agent, mcp, registration) {
7048
7168
  if (agent === "claude" || agent === "claude-desktop" || rootKey === "servers") entry.type = "stdio";
7049
7169
  servers[registration.name] = entry;
7050
7170
  parsed[rootKey] = servers;
7051
- await atomicWrite(config.path, JSON.stringify(parsed, null, 2) + "\n");
7171
+ await atomicWrite(config.path, JSON.stringify(parsed, null, 2) + "\n", before);
7052
7172
  }
7053
7173
  async function unregisterJson(config, agent, name) {
7054
7174
  let parsed;
@@ -7070,13 +7190,14 @@ async function unregisterJson(config, agent, name) {
7070
7190
  await atomicWrite(config.path, JSON.stringify(parsed, null, 2) + "\n");
7071
7191
  }
7072
7192
  async function registerToml(path, agent, mcp, registration) {
7073
- const existing = existsSync5(path) ? await readFile5(path, "utf8") : "";
7193
+ const before = await fileSnapshot(path);
7194
+ const existing = before.contents ?? "";
7074
7195
  const text2 = withoutMcpServerSections(existing, registration.name);
7075
7196
  const args = mcp.args.length ? "\nargs = [" + mcp.args.map((item) => JSON.stringify(item)).join(", ") + "]" : "";
7076
7197
  const environment = registrationEnvironment(agent, registration);
7077
7198
  const environmentText = Object.entries(environment).map(([key, value]) => JSON.stringify(key) + " = " + JSON.stringify(value)).join(", ");
7078
7199
  const block = agent === "grok" ? "\n\n[mcp_servers." + registration.name + "]\ncommand = " + JSON.stringify(mcp.command) + args + "\nenv = { " + environmentText + " }\n" : "\n\n[mcp_servers." + registration.name + "]\ncommand = " + JSON.stringify(mcp.command) + args + "\n\n[mcp_servers." + registration.name + ".env]\n" + Object.entries(environment).map(([key, value]) => key + " = " + JSON.stringify(value)).join("\n") + "\n";
7079
- await atomicWrite(path, text2 + block);
7200
+ await atomicWrite(path, text2 + block, before);
7080
7201
  }
7081
7202
  async function registerGoose(path, mcp, registration) {
7082
7203
  const existing = existsSync5(path) ? await readFile5(path, "utf8") : "";
@@ -7146,11 +7267,82 @@ function formatSkillForAgent(agent, source) {
7146
7267
  }
7147
7268
  return "---\ntrigger: model_decision\ndescription: Use Topolo MCP for Topolo platform operations.\n---\n\n" + body;
7148
7269
  }
7149
- async function atomicWrite(path, contents) {
7270
+ function fileIdentity(stat2) {
7271
+ return [stat2.dev, stat2.ino, stat2.size, stat2.mtimeMs, stat2.ctimeMs, stat2.mode, stat2.nlink].join(":");
7272
+ }
7273
+ async function fileSnapshot(path) {
7274
+ const parents = [];
7275
+ for (let parent = dirname5(path); ; parent = dirname5(parent)) {
7276
+ try {
7277
+ const stat2 = await lstat2(parent);
7278
+ if (!stat2.isDirectory() || stat2.isSymbolicLink()) throw new Error("Host destination contains a symbolic link or non-directory at " + parent + ".");
7279
+ parents.push({ path: parent, identity: [stat2.dev, stat2.ino].join(":") });
7280
+ } catch (error) {
7281
+ if (error.code !== "ENOENT") throw error;
7282
+ }
7283
+ if (dirname5(parent) === parent) break;
7284
+ }
7285
+ try {
7286
+ const stat2 = await lstat2(path);
7287
+ if (!stat2.isFile() || stat2.isSymbolicLink() || stat2.nlink !== 1) throw new Error("Host configuration must be a single regular file at " + path + ".");
7288
+ const contents = await readFile5(path, "utf8");
7289
+ if (fileIdentity(await lstat2(path)) !== fileIdentity(stat2)) throw new Error("Host configuration changed while reading at " + path + ".");
7290
+ return { contents, identity: fileIdentity(stat2), parents };
7291
+ } catch (error) {
7292
+ if (error.code === "ENOENT") return { parents };
7293
+ throw error;
7294
+ }
7295
+ }
7296
+ async function assertSnapshot(path, before) {
7297
+ const current = await fileSnapshot(path);
7298
+ if (current.contents !== before.contents || current.identity !== before.identity || before.parents.some((parent) => !current.parents.some((candidate) => candidate.path === parent.path && candidate.identity === parent.identity))) {
7299
+ throw new Error("Host destination changed before write at " + path + ".");
7300
+ }
7301
+ }
7302
+ async function atomicWrite(path, contents, before) {
7303
+ before ??= await fileSnapshot(path);
7304
+ await assertSnapshot(path, before);
7305
+ if (before.contents === contents) return;
7150
7306
  await mkdir3(dirname5(path), { recursive: true });
7151
- const temporaryPath = path + ".topolo-" + process.pid + ".tmp";
7152
- await writeFile3(temporaryPath, contents, { encoding: "utf8", mode: 384 });
7153
- await rename2(temporaryPath, path);
7307
+ const destination = await fileSnapshot(path);
7308
+ await assertSnapshot(path, before);
7309
+ const temporaryPath = path + ".topolo-" + randomUUID3() + ".tmp";
7310
+ const handle = await open(temporaryPath, "wx", 384);
7311
+ let temporaryIdentity;
7312
+ let handleClosed = false;
7313
+ let temporaryPending = true;
7314
+ try {
7315
+ const created = await handle.stat();
7316
+ temporaryIdentity = [created.dev, created.ino].join(":");
7317
+ await assertSnapshot(path, destination);
7318
+ await writeFile3(handle, contents, { encoding: "utf8" });
7319
+ await handle.close();
7320
+ handleClosed = true;
7321
+ await assertSnapshot(path, destination);
7322
+ const temporary = await lstat2(temporaryPath);
7323
+ if (!temporary.isFile() || temporary.isSymbolicLink() || temporary.nlink !== 1 || [temporary.dev, temporary.ino].join(":") !== temporaryIdentity) {
7324
+ throw new Error("Host temporary file custody changed before rename at " + path + ".");
7325
+ }
7326
+ await rename2(temporaryPath, path);
7327
+ temporaryPending = false;
7328
+ const installed = await fileSnapshot(path);
7329
+ if (installed.contents !== contents) throw new Error("Host configuration read-back failed at " + path + ".");
7330
+ } finally {
7331
+ if (!handleClosed) await handle.close();
7332
+ if (temporaryPending && temporaryIdentity) {
7333
+ let cleanupOwned = false;
7334
+ try {
7335
+ const parentsOwned = await Promise.all(destination.parents.map(async (parent) => {
7336
+ const current2 = await lstat2(parent.path);
7337
+ return current2.isDirectory() && !current2.isSymbolicLink() && [current2.dev, current2.ino].join(":") === parent.identity;
7338
+ }));
7339
+ const current = await lstat2(temporaryPath);
7340
+ cleanupOwned = parentsOwned.every(Boolean) && current.isFile() && !current.isSymbolicLink() && current.nlink === 1 && [current.dev, current.ino].join(":") === temporaryIdentity;
7341
+ } catch {
7342
+ }
7343
+ if (cleanupOwned) await unlink(temporaryPath);
7344
+ }
7345
+ }
7154
7346
  }
7155
7347
  function withoutMcpServerSections(text2, name) {
7156
7348
  const section = "[mcp_servers." + name + "]";
@@ -7171,8 +7363,8 @@ function withoutMcpServerSections(text2, name) {
7171
7363
  var AGENT_LABELS2 = AGENT_LABELS;
7172
7364
  function skillsSourceDir() {
7173
7365
  const here = dirname6(fileURLToPath3(import.meta.url));
7174
- const distCandidate = resolve2(here, "../skills");
7175
- const srcCandidate = resolve2(here, "../../skills");
7366
+ const distCandidate = resolve3(here, "../skills");
7367
+ const srcCandidate = resolve3(here, "../../skills");
7176
7368
  for (const candidate of [distCandidate, srcCandidate]) {
7177
7369
  if (existsSync6(join7(candidate, "topolo", "SKILL.md"))) return candidate;
7178
7370
  }
@@ -7224,14 +7416,22 @@ function resolveMcpRegistration(options) {
7224
7416
  };
7225
7417
  }
7226
7418
  async function installForAgent(agent, sourceDir, mcp, opts) {
7419
+ const { homeDir, projectDir, codexHome } = opts.target;
7420
+ if (opts.withMcp && agent === "claude" && projectDir) {
7421
+ try {
7422
+ await assertClaudeProjectScope(homeDir, projectDir, opts.registration.name);
7423
+ } catch (err) {
7424
+ return { agent, skillInstalled: false, mcp: "failed", detail: err instanceof Error ? err.message : String(err) };
7425
+ }
7426
+ }
7227
7427
  const source = await readFile6(join7(sourceDir, "topolo", "SKILL.md"), "utf8");
7228
- const skill = await installAgentSkill(agent, source, opts.homeDir, opts.projectDir);
7428
+ const skill = await installAgentSkill(agent, source, homeDir, projectDir, codexHome);
7229
7429
  if (!opts.withMcp) {
7230
7430
  return { agent, skillPath: skill.path, skillInstalled: skill.installed, mcp: "skipped" };
7231
7431
  }
7232
7432
  try {
7233
- await registerAgentConfig(agent, mcp, opts.homeDir, opts.registration, opts.projectDir);
7234
- const installed = (await readAgentRegistrations(agent, opts.homeDir, opts.projectDir)).find((registration) => registration.name === opts.registration.name);
7433
+ await registerAgentConfig(agent, mcp, homeDir, opts.registration, projectDir, codexHome);
7434
+ const installed = (await readAgentRegistrations(agent, homeDir, projectDir, codexHome)).find((registration) => registration.name === opts.registration.name);
7235
7435
  const expectedEnvironment = registrationEnvironment(agent, opts.registration);
7236
7436
  if (!installed || installed.command !== mcp.command || JSON.stringify(installed.args) !== JSON.stringify(mcp.args) || Object.entries(expectedEnvironment).some(([key, value]) => installed.env[key] !== value)) {
7237
7437
  throw new Error(`Host configuration did not retain the ${opts.registration.name} MCP registration.`);
@@ -7265,12 +7465,12 @@ function resolveTargets(opts) {
7265
7465
  if (opts.all) return [...SUPPORTED_AGENT_IDS];
7266
7466
  const homeDir = opts.homeDir ?? homedir5();
7267
7467
  const detectBinaries = homeDir === homedir5();
7268
- const detected = SUPPORTED_AGENT_IDS.filter((agent) => agentConfigExists(agent, homeDir, opts.project) || detectBinaries && agentBinaryExists(agent));
7468
+ const detected = SUPPORTED_AGENT_IDS.filter((agent) => agentConfigExists(agent, homeDir, opts.project, opts.codexHome) || detectBinaries && agentBinaryExists(agent));
7269
7469
  return [.../* @__PURE__ */ new Set(["claude", "codex", ...detected])];
7270
7470
  }
7271
7471
  function registerSkillsCommand(program2) {
7272
7472
  const skills = program2.command("skills").description("Install the Topolo agent skill + MCP server into supported agent hosts");
7273
- skills.command("install").description("Copy the skill file and register the topolo MCP server (credential-free)").option("--claude", "Target Claude Code only").option("--codex", "Target Codex only").option("--agent <host...>", "Target one or more hosts by id (for example cursor gemini)").option("--all", "Configure every supported local host").option("--project <dir>", "Write project-scoped host files under <dir> where supported").option("--agent-home <dir>", "Target an alternate agent home (managed installs and clean-room verification)").option("--name <name>", "Register a named pinned MCP, such as topolo-acme").option("--context <context>", "Pin the named MCP to an exact context returned by topolo context ls").option("--installation <id>", "Bind the MCP runtime to a registered external-agent installation").option("--no-mcp", "Copy the skill file but do not register the MCP server").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(
7473
+ skills.command("install").description("Copy the skill file and register the topolo MCP server (credential-free)").option("--claude", "Target Claude Code only").option("--codex", "Target Codex only").option("--agent <host...>", "Target one or more hosts by id (for example cursor gemini)").option("--all", "Configure every supported local host").option("--project <dir>", "Write project-scoped host files under <dir> where supported").option("--codex-home <dir>", "Use an explicit native Codex home instead of <agent-home>/.codex").option("--agent-home <dir>", "Target an alternate agent home (managed installs and clean-room verification)").option("--name <name>", "Register a named pinned MCP, such as topolo-acme").option("--context <context>", "Pin the named MCP to an exact context returned by topolo context ls").option("--installation <id>", "Bind the MCP runtime to a registered external-agent installation").option("--no-mcp", "Copy the skill file but do not register the MCP server").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(
7274
7474
  async (opts) => {
7275
7475
  const mode = resolveOutputMode(opts.json);
7276
7476
  try {
@@ -7286,11 +7486,12 @@ function registerSkillsCommand(program2) {
7286
7486
  }
7287
7487
  }
7288
7488
  );
7289
- skills.command("status").description("List public Topolo skill and MCP registrations for each agent").option("--agent <host...>", "Inspect one or more hosts by id").option("--all", "Inspect every supported host").option("--project <dir>", "Inspect project-scoped host files under <dir>").option("--agent-home <dir>", "Inspect an alternate agent home").option("--name <name>", "Inspect one registration name").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(async (opts) => {
7489
+ skills.command("status").description("List public Topolo skill and MCP registrations for each agent").option("--agent <host...>", "Inspect one or more hosts by id").option("--all", "Inspect every supported host").option("--project <dir>", "Inspect project-scoped host files under <dir>").option("--codex-home <dir>", "Use an explicit native Codex home instead of <agent-home>/.codex").option("--agent-home <dir>", "Inspect an alternate agent home").option("--name <name>", "Inspect one registration name").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(async (opts) => {
7290
7490
  const mode = resolveOutputMode(opts.json);
7291
7491
  try {
7292
7492
  const rows = await agentSurfaceStatus({
7293
- homeDir: opts.agentHome ? resolve2(opts.agentHome) : void 0,
7493
+ homeDir: opts.agentHome ? resolve3(opts.agentHome) : void 0,
7494
+ codexHome: opts.codexHome,
7294
7495
  name: opts.name,
7295
7496
  agent: opts.agent,
7296
7497
  all: opts.all,
@@ -7301,12 +7502,12 @@ function registerSkillsCommand(program2) {
7301
7502
  fail2(err, mode);
7302
7503
  }
7303
7504
  });
7304
- skills.command("uninstall").description("Remove the skill file and MCP registration for each agent").option("--claude", "Target Claude Code only").option("--codex", "Target Codex only").option("--agent <host...>", "Target one or more hosts by id").option("--all", "Remove from every supported host").option("--project <dir>", "Remove project-scoped host files under <dir>").option("--agent-home <dir>", "Target an alternate agent home (managed installs and clean-room verification)").option("--name <name>", "Remove one registration (defaults to topolo)").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(async (opts) => {
7505
+ skills.command("uninstall").description("Remove the skill file and MCP registration for each agent").option("--claude", "Target Claude Code only").option("--codex", "Target Codex only").option("--agent <host...>", "Target one or more hosts by id").option("--all", "Remove from every supported host").option("--project <dir>", "Remove project-scoped host files under <dir>").option("--codex-home <dir>", "Use an explicit native Codex home instead of <agent-home>/.codex").option("--agent-home <dir>", "Target an alternate agent home (managed installs and clean-room verification)").option("--name <name>", "Remove one registration (defaults to topolo)").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(async (opts) => {
7305
7506
  const mode = resolveOutputMode(opts.json);
7306
7507
  try {
7307
7508
  const removed = await uninstallAgentSurfaces({
7308
7509
  ...opts,
7309
- homeDir: opts.agentHome ? resolve2(opts.agentHome) : void 0
7510
+ homeDir: opts.agentHome ? resolve3(opts.agentHome) : void 0
7310
7511
  });
7311
7512
  emit(
7312
7513
  { removed },
@@ -7325,20 +7526,20 @@ async function installAgentSurfaces(opts) {
7325
7526
  }
7326
7527
  const mcp = resolveMcpCommand();
7327
7528
  const registration = resolveMcpRegistration(opts);
7328
- const homeDir = opts.homeDir ?? homedir5();
7529
+ const target = resolveAgentHostTarget({ homeDir: opts.homeDir, projectDir: opts.project, codexHome: opts.codexHome });
7530
+ const { homeDir, projectDir, codexHome } = target;
7329
7531
  const results = [];
7330
- for (const agent of resolveTargets({ ...opts, homeDir, project: opts.project })) {
7532
+ for (const agent of resolveTargets({ ...opts, homeDir, project: projectDir, codexHome })) {
7331
7533
  results.push(await installForAgent(agent, sourceDir, mcp, {
7332
7534
  withMcp: opts.mcp !== false,
7333
- projectDir: opts.project,
7334
- homeDir,
7535
+ target,
7335
7536
  registration
7336
7537
  }));
7337
7538
  }
7338
7539
  return { results, mcp };
7339
7540
  }
7340
7541
  function registerSetupCommand(program2) {
7341
- program2.command("setup").description("Install the Topolo skill and deterministic local MCP server for supported agent hosts").option("--claude", "Target Claude Code only").option("--codex", "Target Codex only").option("--agent <host...>", "Target one or more hosts by id").option("--all", "Configure every supported local host").option("--project <dir>", "Write project-scoped host files under <dir> where supported").option("--agent-home <dir>", "Target an alternate agent home (managed installs and clean-room verification)").option("--name <name>", "Register a named pinned MCP, such as topolo-acme").option("--context <context>", "Pin the named MCP to an exact context returned by topolo context ls").option("--installation <id>", "Bind the MCP runtime to a registered external-agent installation").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(async (opts) => {
7542
+ program2.command("setup").description("Install the Topolo skill and deterministic local MCP server for supported agent hosts").option("--claude", "Target Claude Code only").option("--codex", "Target Codex only").option("--agent <host...>", "Target one or more hosts by id").option("--all", "Configure every supported local host").option("--project <dir>", "Write project-scoped host files under <dir> where supported").option("--codex-home <dir>", "Use an explicit native Codex home instead of <agent-home>/.codex").option("--agent-home <dir>", "Target an alternate agent home (managed installs and clean-room verification)").option("--name <name>", "Register a named pinned MCP, such as topolo-acme").option("--context <context>", "Pin the named MCP to an exact context returned by topolo context ls").option("--installation <id>", "Bind the MCP runtime to a registered external-agent installation").option("--json", "Output JSON", true).option("--no-json", "Output human-readable text").action(async (opts) => {
7342
7543
  const mode = resolveOutputMode(opts.json);
7343
7544
  try {
7344
7545
  const { results, mcp } = await installAgentSurfaces({
@@ -7360,18 +7561,20 @@ function assertAgentSetupSucceeded(results) {
7360
7561
  }
7361
7562
  }
7362
7563
  async function agentSurfaceStatus(options = {}) {
7363
- const homeDir = options.homeDir ?? homedir5();
7564
+ const target = resolveAgentHostTarget({ homeDir: options.homeDir, projectDir: options.project, codexHome: options.codexHome });
7565
+ const { homeDir, projectDir, codexHome } = target;
7364
7566
  const sourceSkill = await readFile6(join7(skillsSourceDir(), "topolo", "SKILL.md"), "utf8");
7365
7567
  const agents = resolveTargets({
7366
7568
  agent: options.agent,
7367
7569
  all: options.all,
7368
7570
  homeDir,
7369
- project: options.project
7571
+ project: projectDir,
7572
+ codexHome
7370
7573
  });
7371
7574
  return Promise.all(agents.map(async (agent) => {
7372
- const skillPath = agentSkillPath(agent, homeDir, options.project);
7575
+ const skillPath = agentSkillPath(agent, homeDir, projectDir, codexHome);
7373
7576
  const installedSkill = skillPath && existsSync6(skillPath) ? await readFile6(skillPath, "utf8") : void 0;
7374
- const registrations = (await readAgentRegistrations(agent, homeDir, options.project)).filter((registration) => !options.name || registration.name === options.name);
7577
+ const registrations = (await readAgentRegistrations(agent, homeDir, projectDir, codexHome)).filter((registration) => !options.name || registration.name === options.name);
7375
7578
  return {
7376
7579
  agent,
7377
7580
  skillInstalled: installedSkill !== void 0,
@@ -7384,16 +7587,17 @@ async function agentSurfaceStatus(options = {}) {
7384
7587
  }));
7385
7588
  }
7386
7589
  async function uninstallAgentSurfaces(options) {
7387
- const homeDir = options.homeDir ?? homedir5();
7590
+ const target = resolveAgentHostTarget({ homeDir: options.homeDir, projectDir: options.project, codexHome: options.codexHome });
7591
+ const { homeDir, projectDir, codexHome } = target;
7388
7592
  const name = options.name?.trim() || "topolo";
7389
7593
  resolveMcpRegistration({ name, ...name === "topolo" ? {} : { context: "uninstall-validation" } });
7390
7594
  const removed = [];
7391
- for (const agent of resolveTargets({ ...options, homeDir, project: options.project })) {
7392
- const before = await readAgentRegistrations(agent, homeDir, options.project);
7595
+ for (const agent of resolveTargets({ ...options, homeDir, project: projectDir, codexHome })) {
7596
+ const before = await readAgentRegistrations(agent, homeDir, projectDir, codexHome);
7393
7597
  const registrationRemoved = before.some((entry) => entry.name === name);
7394
- await unregisterAgentConfig(agent, homeDir, name, options.project);
7395
- const remaining = await readAgentRegistrations(agent, homeDir, options.project);
7396
- const skillPath = agentSkillPath(agent, homeDir, options.project);
7598
+ await unregisterAgentConfig(agent, homeDir, name, projectDir, codexHome);
7599
+ const remaining = await readAgentRegistrations(agent, homeDir, projectDir, codexHome);
7600
+ const skillPath = agentSkillPath(agent, homeDir, projectDir, codexHome);
7397
7601
  const skillRemoved = remaining.length === 0 && Boolean(skillPath && existsSync6(skillPath));
7398
7602
  if (skillRemoved && skillPath) await rm2(dirname6(skillPath), { recursive: true, force: true });
7399
7603
  removed.push({ agent, name, registrationRemoved, skillRemoved });
@@ -7426,7 +7630,7 @@ function formatStatus(rows) {
7426
7630
  // src/commands/doctor.ts
7427
7631
  import { access } from "fs/promises";
7428
7632
  import { homedir as homedir6 } from "os";
7429
- import { resolve as resolve3 } from "path";
7633
+ import { resolve as resolve4 } from "path";
7430
7634
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
7431
7635
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
7432
7636
  var defaultDependencies = {
@@ -7488,7 +7692,7 @@ async function verifyMcpProtocol(command, args, env = definedEnvironment(process
7488
7692
  }
7489
7693
  async function resolveInstalledDoctorTarget(options) {
7490
7694
  const name = options.name?.trim() || "topolo";
7491
- const homeDir = options.agentHome ? resolve3(options.agentHome) : homedir6();
7695
+ const homeDir = options.agentHome ? resolve4(options.agentHome) : homedir6();
7492
7696
  const expected = resolveMcpCommand();
7493
7697
  const requestedAgents = resolveTargets({
7494
7698
  ...options,