worktrust 0.6.1 → 0.6.3
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/package.json +1 -1
- package/setup-mcp.mjs +32 -3
package/package.json
CHANGED
package/setup-mcp.mjs
CHANGED
|
@@ -43,6 +43,23 @@ function fromClaude() {
|
|
|
43
43
|
} catch { return null; }
|
|
44
44
|
}
|
|
45
45
|
const discovered = fromClaude();
|
|
46
|
+
/**
|
|
47
|
+
* EVERY DIRECT WORKTRUST ENTRY CLAUDE CODE HOLDS, BY NAME (2026-10-04). A coupling made by hand may
|
|
48
|
+
* carry any name ("worktrust-imac-home") and a key in its header; `npx worktrust` once read it as
|
|
49
|
+
* "already coupled" and left Claude Code on that unbound key. With the bridge, each such entry is
|
|
50
|
+
* taken out and the bridge put in; on removal, they all go. Only entries whose address is a
|
|
51
|
+
* WorkTrust door are touched, user scope here and local scope in the folder that holds it.
|
|
52
|
+
*/
|
|
53
|
+
function directClaudeEntries() {
|
|
54
|
+
try {
|
|
55
|
+
const config = JSON.parse(readFileSync(join(homedir(), ".claude.json"), "utf8"));
|
|
56
|
+
const door = (server) => typeof server?.url === "string" && /\/api\/mcp\b/.test(server.url) && /worktrust/i.test(new URL(server.url).host);
|
|
57
|
+
const entries = Object.entries(config.mcpServers ?? {}).filter(([, server]) => door(server)).map(([name]) => ({ name, scope: "user", dir: null }));
|
|
58
|
+
for (const [dir, project] of Object.entries(config.projects ?? {})) for (const [name, server] of Object.entries(project?.mcpServers ?? {})) if (door(server)) entries.push({ name, scope: "local", dir });
|
|
59
|
+
const bridged = Object.values(config.mcpServers ?? {}).some((server) => Array.isArray(server?.args) && server.args.includes(BRIDGE));
|
|
60
|
+
return { entries, bridged };
|
|
61
|
+
} catch { return { entries: [], bridged: false }; }
|
|
62
|
+
}
|
|
46
63
|
const URL_ = value("url") ?? process.env.WORKTRUST_MCP_URL ?? discovered?.url;
|
|
47
64
|
const TOKEN = value("token") ?? process.env.WORKTRUST_MCP_TOKEN ?? discovered?.token;
|
|
48
65
|
if (!URL_ || (!TOKEN && !BRIDGE)) { console.error("no coupling: pass --url and --token (from Sources → Claude (MCP) → Add coupling), or couple Claude Code first"); process.exit(1); }
|
|
@@ -75,7 +92,15 @@ for (const client of CLIENTS) {
|
|
|
75
92
|
// it when the command is not. User scope, so the coupling holds in every folder on this machine.
|
|
76
93
|
const claudeArgs = REMOVE ? ["mcp", "remove", "-s", "user", NAME] : BRIDGE ? ["mcp", "add", "-s", "user", NAME, "--", NODE, BRIDGE, "mcp"] : ["mcp", "add", "--transport", "http", "-s", "user", NAME, URL_, "-H", `Authorization: Bearer ${TOKEN}`];
|
|
77
94
|
const shown = REMOVE ? `claude mcp remove -s user ${NAME}` : BRIDGE ? `claude mcp add -s user ${NAME} -- "${NODE}" "${BRIDGE}" mcp` : `claude mcp add --transport http -s user ${NAME} "${URL_}" -H "Authorization: Bearer <token>"`;
|
|
78
|
-
|
|
95
|
+
const direct = directClaudeEntries();
|
|
96
|
+
if (!REMOVE && (BRIDGE ? direct.bridged && direct.entries.length === 0 : discovered)) { console.log(` ✓ ${client.label}: already coupled`); standing += 1; continue; }
|
|
97
|
+
// A direct entry under any name is replaced by the bridge, and every one goes on removal.
|
|
98
|
+
if (BRIDGE || REMOVE) for (const entry of direct.entries.filter((one) => !(REMOVE && one.name === NAME && one.scope === "user"))) {
|
|
99
|
+
const verb = `claude mcp remove -s ${entry.scope} ${entry.name}`;
|
|
100
|
+
if (!WRITE) { console.log(` ↻ ${client.label}: the direct entry "${entry.name}" (${entry.scope}) is replaced`); continue; }
|
|
101
|
+
const out = claudeOnPath() ? spawnSync("claude", ["mcp", "remove", "-s", entry.scope, entry.name], { encoding: "utf8", shell: platform() === "win32", ...(entry.dir ? { cwd: entry.dir } : {}) }) : null;
|
|
102
|
+
console.log(out?.status === 0 ? ` – ${client.label}: removed the direct entry "${entry.name}" (${entry.scope})` : ` ! ${client.label}: could not remove "${entry.name}"; run it yourself${entry.dir ? ` in ${entry.dir}` : ""}: ${verb}`);
|
|
103
|
+
}
|
|
79
104
|
if (WRITE && claudeOnPath()) {
|
|
80
105
|
const run = spawnSync("claude", claudeArgs, { encoding: "utf8", shell: platform() === "win32" });
|
|
81
106
|
if (run.status === 0) { console.log(` + ${client.label}: ${REMOVE ? "removed" : "coupled"} through \`claude mcp ${REMOVE ? "remove" : "add"}\` (user scope)`); touched += 1; continue; }
|
|
@@ -86,7 +111,8 @@ for (const client of CLIENTS) {
|
|
|
86
111
|
continue;
|
|
87
112
|
}
|
|
88
113
|
const text = existsSync(client.file) ? readFileSync(client.file, "utf8") : "";
|
|
89
|
-
|
|
114
|
+
// With the bridge, coupled means the bridge is in AND no entry with a key in it is left beside it.
|
|
115
|
+
const already = REMOVE ? text.includes(URL_) || /worktrust\.mjs/.test(text) : BRIDGE !== undefined ? text.includes(JSON.stringify(BRIDGE).slice(1, -1)) && !text.includes(URL_) : text.includes(URL_);
|
|
90
116
|
// THE WAY OUT, ON THE MACHINE. Uncoupling in the app ends the TOKEN; the config that names the
|
|
91
117
|
// door stays behind on every client here, so the next person to read this file believes the
|
|
92
118
|
// machine is coupled. `--remove` takes the entry out; the account side is a separate act and
|
|
@@ -107,7 +133,10 @@ for (const client of CLIENTS) {
|
|
|
107
133
|
} else if (client.shape === "toml") {
|
|
108
134
|
const text = readFileSync(client.file, "utf8");
|
|
109
135
|
// The block and nothing after it: from its own header to the next one, or the end.
|
|
110
|
-
|
|
136
|
+
// Every block that names the door, under any name (a hand-made one too, 2026-10-04), or ours.
|
|
137
|
+
// A block runs to the next table header at the START of a line, never to the next "[" — the
|
|
138
|
+
// bridge's own `args = ["…", "mcp"]` holds one, and cutting there left half a block behind.
|
|
139
|
+
const stripped = text.replace(/\n?\[mcp_servers\.("?)([A-Za-z0-9_.-]+)\1\][\s\S]*?(?=\n\[|$)/g, (block, _q, name) => (name === NAME || block.includes(URL_) || /worktrust\.mjs/.test(block) ? "" : block));
|
|
111
140
|
console.log(` − ${client.label}: ${WRITE ? "removed from" : "would remove from"} ${client.file}`);
|
|
112
141
|
if (WRITE) writeFileSync(client.file, stripped);
|
|
113
142
|
}
|