@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/README.md +34 -5
- package/agent-packs/README.md +98 -0
- package/agent-packs/coverage_changed.py +1 -1
- package/agent-packs/src/topolo_agent_pack/approvals.py +22 -0
- package/agent-packs/src/topolo_agent_pack/cli.py +37 -4
- package/agent-packs/src/topolo_agent_pack/execution.py +70 -2
- package/agent-packs/src/topolo_agent_pack/lanes.py +749 -0
- package/agent-packs/src/topolo_agent_pack/live.py +914 -16
- package/agent-packs/src/topolo_agent_pack/policy.py +6 -0
- package/agent-packs/src/topolo_agent_pack/task_models.py +200 -0
- package/agent-packs/src/topolo_agent_pack/topolo.py +42 -3
- package/dist/claude-pack-backend-RMSOPHVQ.js +2380 -0
- package/dist/index.js +270 -66
- package/package.json +5 -5
- package/dist/claude-pack-backend-3YJNKMYD.js +0 -1207
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-
|
|
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-
|
|
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 === "
|
|
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
|
-
|
|
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-
|
|
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.
|
|
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((
|
|
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((
|
|
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.
|
|
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
|
|
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
|
-
|
|
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 (
|
|
7147
|
+
if (before.contents !== void 0) {
|
|
7029
7148
|
try {
|
|
7030
|
-
parsed = JSON.parse(
|
|
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
|
|
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
|
-
|
|
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
|
|
7152
|
-
await
|
|
7153
|
-
|
|
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 =
|
|
7175
|
-
const srcCandidate =
|
|
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,
|
|
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,
|
|
7234
|
-
const installed = (await readAgentRegistrations(agent,
|
|
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 ?
|
|
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 ?
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
|
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:
|
|
7571
|
+
project: projectDir,
|
|
7572
|
+
codexHome
|
|
7370
7573
|
});
|
|
7371
7574
|
return Promise.all(agents.map(async (agent) => {
|
|
7372
|
-
const skillPath = agentSkillPath(agent, homeDir,
|
|
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,
|
|
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
|
|
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:
|
|
7392
|
-
const before = await readAgentRegistrations(agent, homeDir,
|
|
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,
|
|
7395
|
-
const remaining = await readAgentRegistrations(agent, homeDir,
|
|
7396
|
-
const skillPath = agentSkillPath(agent, homeDir,
|
|
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
|
|
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 ?
|
|
7695
|
+
const homeDir = options.agentHome ? resolve4(options.agentHome) : homedir6();
|
|
7492
7696
|
const expected = resolveMcpCommand();
|
|
7493
7697
|
const requestedAgents = resolveTargets({
|
|
7494
7698
|
...options,
|