@synap-core/cli 1.13.0 → 1.13.1-next.20261102.4b45534
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 +13 -3
- package/dist/build-info.json +2 -3
- package/dist/commands/agents.js +12 -2
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/capability.d.ts +86 -0
- package/dist/commands/capability.js +117 -30
- package/dist/commands/capability.js.map +1 -1
- package/dist/commands/data-extra.d.ts +13 -0
- package/dist/commands/data-extra.js +23 -2
- package/dist/commands/data-extra.js.map +1 -1
- package/dist/commands/doctor.d.ts +18 -0
- package/dist/commands/doctor.js +28 -1
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.js +4 -2
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/keys-request.d.ts +48 -0
- package/dist/commands/keys-request.js +236 -0
- package/dist/commands/keys-request.js.map +1 -0
- package/dist/commands/keys-request.test.d.ts +1 -0
- package/dist/commands/keys-request.test.js +176 -0
- package/dist/commands/keys-request.test.js.map +1 -0
- package/dist/commands/knowledge.d.ts +6 -0
- package/dist/commands/knowledge.js +9 -1
- package/dist/commands/knowledge.js.map +1 -1
- package/dist/commands/market.d.ts +74 -6
- package/dist/commands/market.js +162 -53
- package/dist/commands/market.js.map +1 -1
- package/dist/commands/mcp.d.ts +1 -1
- package/dist/commands/mcp.js +14 -9
- package/dist/commands/mcp.js.map +1 -1
- package/dist/commands/pods.js +6 -2
- package/dist/commands/pods.js.map +1 -1
- package/dist/commands/proposals-actions.js +7 -1
- package/dist/commands/proposals-actions.js.map +1 -1
- package/dist/commands/providers.d.ts +22 -6
- package/dist/commands/providers.js +39 -32
- package/dist/commands/providers.js.map +1 -1
- package/dist/commands/raycast.d.ts +15 -0
- package/dist/commands/raycast.js +36 -10
- package/dist/commands/raycast.js.map +1 -1
- package/dist/commands/status.js +12 -6
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/statusline.backoff.test.js +13 -3
- package/dist/commands/statusline.backoff.test.js.map +1 -1
- package/dist/commands/statusline.d.ts +104 -9
- package/dist/commands/statusline.js +392 -148
- package/dist/commands/statusline.js.map +1 -1
- package/dist/commands/statusline.line2.test.d.ts +15 -0
- package/dist/commands/statusline.line2.test.js +226 -0
- package/dist/commands/statusline.line2.test.js.map +1 -0
- package/dist/commands/statusline.needs-you.test.d.ts +1 -0
- package/dist/commands/statusline.needs-you.test.js +17 -0
- package/dist/commands/statusline.needs-you.test.js.map +1 -0
- package/dist/commands/statusline.now.test.d.ts +10 -0
- package/dist/commands/statusline.now.test.js +250 -0
- package/dist/commands/statusline.now.test.js.map +1 -0
- package/dist/commands/tracks.d.ts +1 -0
- package/dist/commands/tracks.js +4 -0
- package/dist/commands/tracks.js.map +1 -1
- package/dist/commands/upload.js +12 -0
- package/dist/commands/upload.js.map +1 -1
- package/dist/commands/wait.d.ts +124 -0
- package/dist/commands/wait.js +384 -0
- package/dist/commands/wait.js.map +1 -0
- package/dist/commands/wait.test.d.ts +8 -0
- package/dist/commands/wait.test.js +308 -0
- package/dist/commands/wait.test.js.map +1 -0
- package/dist/commands/worker-github-app.d.ts +63 -0
- package/dist/commands/worker-github-app.js +161 -0
- package/dist/commands/worker-github-app.js.map +1 -0
- package/dist/commands/worker-github-app.test.d.ts +1 -0
- package/dist/commands/worker-github-app.test.js +102 -0
- package/dist/commands/worker-github-app.test.js.map +1 -0
- package/dist/commands/worker-github-broker.d.ts +39 -0
- package/dist/commands/worker-github-broker.js +106 -0
- package/dist/commands/worker-github-broker.js.map +1 -0
- package/dist/commands/worker-github-broker.test.d.ts +1 -0
- package/dist/commands/worker-github-broker.test.js +90 -0
- package/dist/commands/worker-github-broker.test.js.map +1 -0
- package/dist/commands/worker-runs.test.d.ts +1 -0
- package/dist/commands/worker-runs.test.js +150 -0
- package/dist/commands/worker-runs.test.js.map +1 -0
- package/dist/commands/worker-worktrees.d.ts +34 -0
- package/dist/commands/worker-worktrees.js +87 -0
- package/dist/commands/worker-worktrees.js.map +1 -0
- package/dist/commands/worker.d.ts +192 -5
- package/dist/commands/worker.js +467 -45
- package/dist/commands/worker.js.map +1 -1
- package/dist/commands/worker.test.js +269 -3
- package/dist/commands/worker.test.js.map +1 -1
- package/dist/index.js +59 -4
- package/dist/index.js.map +1 -1
- package/dist/lib/agents-config.d.ts +9 -0
- package/dist/lib/agents-config.js +20 -0
- package/dist/lib/agents-config.js.map +1 -1
- package/dist/lib/atomic-file.d.ts +20 -1
- package/dist/lib/atomic-file.js +100 -2
- package/dist/lib/atomic-file.js.map +1 -1
- package/dist/lib/auth.d.ts +25 -0
- package/dist/lib/auth.js +49 -9
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/box/job-server.js +4 -0
- package/dist/lib/box/job-server.js.map +1 -1
- package/dist/lib/bridge-invocation.test.d.ts +1 -0
- package/dist/lib/bridge-invocation.test.js +75 -0
- package/dist/lib/bridge-invocation.test.js.map +1 -0
- package/dist/lib/bridge-status.d.ts +144 -0
- package/dist/lib/bridge-status.js +227 -0
- package/dist/lib/bridge-status.js.map +1 -0
- package/dist/lib/bridge-status.test.d.ts +6 -0
- package/dist/lib/bridge-status.test.js +197 -0
- package/dist/lib/bridge-status.test.js.map +1 -0
- package/dist/lib/client-header.test.js +19 -1
- package/dist/lib/client-header.test.js.map +1 -1
- package/dist/lib/conversation-headers.d.ts +67 -2
- package/dist/lib/conversation-headers.js +122 -6
- package/dist/lib/conversation-headers.js.map +1 -1
- package/dist/lib/conversation-headers.test.d.ts +1 -0
- package/dist/lib/conversation-headers.test.js +78 -0
- package/dist/lib/conversation-headers.test.js.map +1 -0
- package/dist/lib/credential-class.d.ts +12 -1
- package/dist/lib/credential-class.js +20 -0
- package/dist/lib/credential-class.js.map +1 -1
- package/dist/lib/device-connect.d.ts +45 -0
- package/dist/lib/device-connect.js +60 -0
- package/dist/lib/device-connect.js.map +1 -1
- package/dist/lib/goal-words.js +6 -3
- package/dist/lib/goal-words.js.map +1 -1
- package/dist/lib/hub-client.d.ts +59 -1
- package/dist/lib/hub-client.js +130 -36
- package/dist/lib/hub-client.js.map +1 -1
- package/dist/lib/key-locations.d.ts +3 -3
- package/dist/lib/key-locations.js +3 -39
- package/dist/lib/key-locations.js.map +1 -1
- package/dist/lib/machine-instance.d.ts +7 -5
- package/dist/lib/machine-instance.js +7 -5
- package/dist/lib/machine-instance.js.map +1 -1
- package/dist/lib/mcp-bridge.d.ts +43 -0
- package/dist/lib/mcp-bridge.js +199 -11
- package/dist/lib/mcp-bridge.js.map +1 -1
- package/dist/lib/mcp-bridge.test.js +194 -2
- package/dist/lib/mcp-bridge.test.js.map +1 -1
- package/dist/lib/mcp-call.d.ts +5 -1
- package/dist/lib/mcp-call.js +7 -6
- package/dist/lib/mcp-call.js.map +1 -1
- package/dist/lib/mcp-connector-url.test.d.ts +1 -0
- package/dist/lib/mcp-connector-url.test.js +13 -0
- package/dist/lib/mcp-connector-url.test.js.map +1 -0
- package/dist/lib/mcp-snippets.d.ts +16 -0
- package/dist/lib/mcp-snippets.js +20 -2
- package/dist/lib/mcp-snippets.js.map +1 -1
- package/dist/lib/now-line.d.ts +66 -0
- package/dist/lib/now-line.js +193 -0
- package/dist/lib/now-line.js.map +1 -0
- package/dist/lib/now-line.test.d.ts +5 -0
- package/dist/lib/now-line.test.js +177 -0
- package/dist/lib/now-line.test.js.map +1 -0
- package/dist/lib/package-updates.d.ts +66 -0
- package/dist/lib/package-updates.js +76 -0
- package/dist/lib/package-updates.js.map +1 -0
- package/dist/lib/pod.d.ts +0 -4
- package/dist/lib/pod.js +0 -7
- package/dist/lib/pod.js.map +1 -1
- package/dist/lib/register-claude-code-mcp.test.js +19 -0
- package/dist/lib/register-claude-code-mcp.test.js.map +1 -1
- package/dist/lib/safe-open.d.ts +5 -3
- package/dist/lib/safe-open.js +9 -8
- package/dist/lib/safe-open.js.map +1 -1
- package/dist/lib/self-host.d.ts +17 -0
- package/dist/lib/self-host.js +24 -0
- package/dist/lib/self-host.js.map +1 -0
- package/dist/lib/targets.d.ts +31 -0
- package/dist/lib/targets.js +100 -104
- package/dist/lib/targets.js.map +1 -1
- package/dist/lib/term.d.ts +24 -0
- package/dist/lib/term.js +52 -0
- package/dist/lib/term.js.map +1 -0
- package/dist/lib/term.test.d.ts +1 -0
- package/dist/lib/term.test.js +29 -0
- package/dist/lib/term.test.js.map +1 -0
- package/dist/lib/transcript-index.d.ts +94 -0
- package/dist/lib/transcript-index.js +321 -0
- package/dist/lib/transcript-index.js.map +1 -0
- package/dist/lib/transcript-index.test.d.ts +10 -0
- package/dist/lib/transcript-index.test.js +200 -0
- package/dist/lib/transcript-index.test.js.map +1 -0
- package/dist/lib/wait-help.d.ts +11 -0
- package/dist/lib/wait-help.js +25 -0
- package/dist/lib/wait-help.js.map +1 -0
- package/dist/lib/wait-state.d.ts +57 -0
- package/dist/lib/wait-state.js +114 -0
- package/dist/lib/wait-state.js.map +1 -0
- package/package.json +2 -2
- package/skills/synap/SKILL.md +71 -38
- package/skills/synap/code-skills.md +15 -1
- package/skills/synap/concepts.md +36 -26
- package/skills/synap/document-embeds.md +15 -1
- package/skills/synap/focus-sessions.md +4 -4
- package/skills/synap/from-intent.md +2 -2
- package/skills/synap/governance-rules.md +2 -0
- package/skills/synap/reflexes.md +4 -4
- package/skills/synap/work-model.md +1 -1
- package/skills/synap/writing-titles.md +7 -0
- package/skills/synap-app/SKILL.md +2 -2
- package/skills/synap-app/overview.md +2 -2
- package/skills/synap-market/SKILL.md +1 -1
- package/skills/synap-market/governance-and-catalog.md +1 -1
- package/skills/synap-schema/SKILL.md +4 -1
- package/skills/synap-schema/intent-vocabulary-ssot.md +4 -1
- package/dist/commands/template.d.ts +0 -42
- package/dist/commands/template.js +0 -191
- package/dist/commands/template.js.map +0 -1
- package/dist/lib/browser-auth.d.ts +0 -35
- package/dist/lib/browser-auth.js +0 -170
- package/dist/lib/browser-auth.js.map +0 -1
- package/dist/lib/openclaw.d.ts +0 -32
- package/dist/lib/openclaw.js +0 -165
- package/dist/lib/openclaw.js.map +0 -1
- package/dist/lib/seed.d.ts +0 -13
- package/dist/lib/seed.js +0 -135
- package/dist/lib/seed.js.map +0 -1
package/dist/lib/targets.js
CHANGED
|
@@ -17,8 +17,9 @@ import chalk from "chalk";
|
|
|
17
17
|
import ora from "ora";
|
|
18
18
|
import prompts from "prompts";
|
|
19
19
|
import { log } from "../utils/logger.js";
|
|
20
|
-
import { writeSecretFile,
|
|
21
|
-
import { redactKey } from "./mcp-snippets.js";
|
|
20
|
+
import { writeSecretFile, ensureSecretDir, readClientConfig, writeClientConfig, clientConfigBackupPath, } from "./atomic-file.js";
|
|
21
|
+
import { redactKey, mcpRemoteArgs, MCP_REMOTE_PACKAGE } from "./mcp-snippets.js";
|
|
22
|
+
import { MANAGED_MCP_URL } from "./auth.js";
|
|
22
23
|
import { installSkills, getDeliverableSkills } from "./skills-installer.js";
|
|
23
24
|
import { TARGET_KEY_MINT, takePreApprovedKey } from "./batch-approval.js";
|
|
24
25
|
import { agentTypeOfSurface, machineInstanceId, machineInstanceLabel } from "./machine-instance.js";
|
|
@@ -636,13 +637,10 @@ export async function resolveWorkspaceId(cfg) {
|
|
|
636
637
|
export async function writeClaudeCodeEnv(cfg, { writeMcp = true } = {}) {
|
|
637
638
|
const settingsPath = path.join(os.homedir(), ".claude", "settings.json");
|
|
638
639
|
const settingsDir = path.dirname(settingsPath);
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
}
|
|
644
|
-
catch { /* start fresh */ }
|
|
645
|
-
}
|
|
640
|
+
// Throws when the file exists but cannot be parsed: an unreadable
|
|
641
|
+
// settings.json is never rewritten as `{}` (it holds every hook, permission
|
|
642
|
+
// and env the person set).
|
|
643
|
+
const settings = readClientConfig(settingsPath);
|
|
646
644
|
// ── Resolve human user ID and provision an agent-owned API key ──────────
|
|
647
645
|
// SYNAP_USER_ID = human user (for entity attribution)
|
|
648
646
|
// SYNAP_HUB_API_KEY = agent-owned key (identity encoded in the key itself)
|
|
@@ -726,7 +724,7 @@ export async function writeClaudeCodeEnv(cfg, { writeMcp = true } = {}) {
|
|
|
726
724
|
if (!fs.existsSync(settingsDir)) {
|
|
727
725
|
fs.mkdirSync(settingsDir, { recursive: true });
|
|
728
726
|
}
|
|
729
|
-
|
|
727
|
+
writeClientConfig(settingsPath, settings);
|
|
730
728
|
// ── Register the MCP server where Claude Code actually reads it ───────────
|
|
731
729
|
// The official `claude mcp add` writes to the user-scope registry that
|
|
732
730
|
// `claude mcp list` + the runtime consult — unlike settings.json mcpServers,
|
|
@@ -751,6 +749,8 @@ export async function writeClaudeCodeEnv(cfg, { writeMcp = true } = {}) {
|
|
|
751
749
|
export function registerClaudeCodeMcp(mcpUrl, apiKey, serverName) {
|
|
752
750
|
const header = `Authorization: Bearer ${apiKey}`;
|
|
753
751
|
const bridge = claudeCodeMcpTransport() === "bridge";
|
|
752
|
+
const invocation = bridge ? resolveBridgeInvocation() : null;
|
|
753
|
+
const bridgeCommand = invocation ? synapBridgeCommand(mcpUrl, invocation) : [];
|
|
754
754
|
try {
|
|
755
755
|
// Remove legacy single-name entry and any prior pod-derived name.
|
|
756
756
|
for (const name of new Set(["synap", serverName])) {
|
|
@@ -767,9 +767,12 @@ export function registerClaudeCodeMcp(mcpUrl, apiKey, serverName) {
|
|
|
767
767
|
// server's env, as the header does for http.
|
|
768
768
|
// `-e` is variadic in Claude Code's CLI: the server name must come
|
|
769
769
|
// BEFORE it, or `-e` swallows it as an env var.
|
|
770
|
-
["mcp", "add", "--scope", "user", serverName, "-e", `SYNAP_MCP_API_KEY=${apiKey}`, "--", ...
|
|
770
|
+
["mcp", "add", "--scope", "user", serverName, "-e", `SYNAP_MCP_API_KEY=${apiKey}`, "--", ...bridgeCommand]
|
|
771
771
|
: ["mcp", "add", "--transport", "http", serverName, mcpUrl, "--header", header, "--scope", "user"], { stdio: "ignore" });
|
|
772
772
|
log.success(`MCP server '${serverName}' registered with Claude Code (\`claude mcp add\`, user scope${bridge ? ", via the synap stdio bridge" : ""}). Relaunch Claude Code to load the tools.`);
|
|
773
|
+
const note = invocation && bridgeInvocationNote(invocation);
|
|
774
|
+
if (note)
|
|
775
|
+
log.dim(note);
|
|
773
776
|
if (serverName !== "synap") {
|
|
774
777
|
log.dim("Multi-pod: connect each profile separately — server names are derived from the pod host so they don't overwrite:");
|
|
775
778
|
log.dim(" synap connect --target=claude-code --name=perso");
|
|
@@ -787,7 +790,7 @@ export function registerClaudeCodeMcp(mcpUrl, apiKey, serverName) {
|
|
|
787
790
|
log.dim(`Could not run \`claude mcp add\` (is the claude CLI on PATH?). The '${serverName}' MCP server may now be UNREGISTERED in Claude Code. Register it by hand (the key is in a 0600 file, not on this screen):`);
|
|
788
791
|
log.dim(` source ${keyFile}`);
|
|
789
792
|
log.dim(bridge
|
|
790
|
-
? ` claude mcp add --scope user ${serverName} -e SYNAP_MCP_API_KEY="$SYNAP_MCP_KEY" -- ${
|
|
793
|
+
? ` claude mcp add --scope user ${serverName} -e SYNAP_MCP_API_KEY="$SYNAP_MCP_KEY" -- ${bridgeCommand.map(shellQuote).join(" ")}`
|
|
791
794
|
: ` claude mcp add --transport http ${serverName} ${mcpUrl} --header 'Authorization: Bearer \${SYNAP_MCP_KEY}' --scope user`);
|
|
792
795
|
}
|
|
793
796
|
catch {
|
|
@@ -821,13 +824,8 @@ export function claudeCodeMcpTransport() {
|
|
|
821
824
|
*/
|
|
822
825
|
export function setClaudeCodeMcpTransport(transport) {
|
|
823
826
|
const settingsPath = path.join(os.homedir(), ".claude", "settings.json");
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
827
|
-
}
|
|
828
|
-
catch {
|
|
829
|
-
return null;
|
|
830
|
-
}
|
|
827
|
+
// Missing → `{}` → no stored pod → null. Unreadable → throws (never rewritten).
|
|
828
|
+
const settings = readClientConfig(settingsPath);
|
|
831
829
|
const env = (settings.env ?? {});
|
|
832
830
|
const podUrl = env["SYNAP_POD_URL"];
|
|
833
831
|
const apiKey = env["SYNAP_HUB_API_KEY"];
|
|
@@ -838,24 +836,59 @@ export function setClaudeCodeMcpTransport(transport) {
|
|
|
838
836
|
else
|
|
839
837
|
delete env["SYNAP_MCP_TRANSPORT"];
|
|
840
838
|
settings.env = env;
|
|
841
|
-
|
|
839
|
+
writeClientConfig(settingsPath, settings);
|
|
842
840
|
const serverName = mcpServerNameFromPodUrl(podUrl);
|
|
843
841
|
registerClaudeCodeMcp(buildMcpUrl(podUrl, env["SYNAP_WORKSPACE_ID"], env["SYNAP_PROJECT_ID"]), apiKey, serverName);
|
|
844
842
|
return serverName;
|
|
845
843
|
}
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
844
|
+
export function resolveBridgeInvocation(self = process.argv[1], execPath = process.execPath) {
|
|
845
|
+
const nm = `${path.sep}node_modules${path.sep}`;
|
|
846
|
+
if (!self || self.includes(`${path.sep}_npx${path.sep}`))
|
|
847
|
+
return { kind: "npx", command: ["synap"] };
|
|
848
|
+
const entry = path.resolve(self);
|
|
849
|
+
const realEntry = fs.realpathSync(entry);
|
|
850
|
+
const isLink = fs.lstatSync(entry).isSymbolicLink();
|
|
851
|
+
if (!isLink && !entry.includes(nm))
|
|
852
|
+
return { kind: "checkout", command: [execPath, realEntry], realEntry };
|
|
853
|
+
// Run through the package path (`<prefix>/lib/node_modules/@synap-core/cli/dist/index.js`):
|
|
854
|
+
// the bin npm installed next to it, when it leads to this same entry.
|
|
855
|
+
let installed = entry;
|
|
856
|
+
const pkgDir = `${nm}@synap-core${path.sep}cli${path.sep}`;
|
|
857
|
+
const at = entry.lastIndexOf(pkgDir);
|
|
858
|
+
if (!isLink && at >= 0) {
|
|
859
|
+
const prefix = entry.slice(0, at);
|
|
860
|
+
const bins = [
|
|
861
|
+
path.join(path.dirname(prefix), "bin", "synap"), // global: <prefix>/lib/node_modules → <prefix>/bin
|
|
862
|
+
path.join(prefix, "node_modules", ".bin", "synap"), // local: <dir>/node_modules → <dir>/node_modules/.bin
|
|
863
|
+
];
|
|
864
|
+
for (const bin of bins) {
|
|
865
|
+
try {
|
|
866
|
+
if (fs.realpathSync(bin) === realEntry) {
|
|
867
|
+
installed = bin;
|
|
868
|
+
break;
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
catch {
|
|
872
|
+
/* no such bin — try the next */
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
return { kind: realEntry.includes(nm) ? "installed" : "linked", command: [execPath, installed], realEntry };
|
|
877
|
+
}
|
|
878
|
+
/** The command Claude Code spawns for the bridge (see `resolveBridgeInvocation`). */
|
|
879
|
+
function synapBridgeCommand(mcpUrl, invocation) {
|
|
880
|
+
return [...invocation.command, "mcp", "bridge", "--url", mcpUrl];
|
|
881
|
+
}
|
|
882
|
+
/** One line on what the bridge runs, when it is not an installed release. */
|
|
883
|
+
export function bridgeInvocationNote(invocation) {
|
|
884
|
+
const install = "To run a release instead: `npm install -g @synap-core/cli@next`, then `synap mcp use-bridge`.";
|
|
885
|
+
if (invocation.kind === "checkout") {
|
|
886
|
+
return `The MCP bridge runs this checkout build (${invocation.realEntry}), not an installed CLI. ${install}`;
|
|
887
|
+
}
|
|
888
|
+
if (invocation.kind === "linked") {
|
|
889
|
+
return `The MCP bridge runs the installed \`synap\` (${invocation.command[1]}), which is a link to a checkout (${invocation.realEntry}). ${install}`;
|
|
857
890
|
}
|
|
858
|
-
return
|
|
891
|
+
return null;
|
|
859
892
|
}
|
|
860
893
|
// ─── Agent key provisioning helper ──────────────────────────────────────────
|
|
861
894
|
/**
|
|
@@ -1183,7 +1216,7 @@ async function installClaudeDesktop(info, cfg) {
|
|
|
1183
1216
|
command: "npx",
|
|
1184
1217
|
// The header value rides in env (this file is 0600), never in the
|
|
1185
1218
|
// mcp-remote process's argv where any local `ps` can read it.
|
|
1186
|
-
args:
|
|
1219
|
+
args: mcpRemoteArgs(desktopMcpUrl),
|
|
1187
1220
|
env: { SYNAP_AUTH: `Bearer ${effectiveApiKey}` },
|
|
1188
1221
|
});
|
|
1189
1222
|
if (desktopServerName !== "synap")
|
|
@@ -1341,7 +1374,7 @@ async function installOpenWebUI(cfg) {
|
|
|
1341
1374
|
*
|
|
1342
1375
|
* OAuth: ChatGPT also supports OAuth (no header field). The pod's /mcp is
|
|
1343
1376
|
* Bearer-only, so OAuth for a SINGLE pod isn't available — it lives on the
|
|
1344
|
-
* Control Plane (`https://
|
|
1377
|
+
* Control Plane (`https://mcp.synap.live`), which is an OAuth MCP server
|
|
1345
1378
|
* exposing `list_pods` / `connect_pod` / 14 proxied `pod__*` tools. That path
|
|
1346
1379
|
* is multi-pod and managed; `synap mcp connect-claude` prints it for
|
|
1347
1380
|
* claude.ai, and the same URL works for ChatGPT's OAuth connector.
|
|
@@ -1370,7 +1403,7 @@ async function installChatGpt(cfg) {
|
|
|
1370
1403
|
headers: { Authorization: `Bearer ${effectiveApiKey}` },
|
|
1371
1404
|
},
|
|
1372
1405
|
oauthAlternative: {
|
|
1373
|
-
url:
|
|
1406
|
+
url: MANAGED_MCP_URL,
|
|
1374
1407
|
note: "ChatGPT OAuth connector (no header field). Multi-pod: list_pods, " +
|
|
1375
1408
|
"connect_pod, then proxied pod__* tools. Same URL as claude.ai.",
|
|
1376
1409
|
},
|
|
@@ -1392,7 +1425,7 @@ async function installChatGpt(cfg) {
|
|
|
1392
1425
|
log.info("Path 2 — OAuth (multi-pod, no key to paste):");
|
|
1393
1426
|
log.blank();
|
|
1394
1427
|
log.info(" ChatGPT → Settings → Connectors → Add custom connector");
|
|
1395
|
-
log.info(
|
|
1428
|
+
log.info(` URL: ${MANAGED_MCP_URL}`);
|
|
1396
1429
|
log.info(" Auth: OAuth (leave API key blank)");
|
|
1397
1430
|
log.blank();
|
|
1398
1431
|
log.dim(" After OAuth, ask ChatGPT to 'list my Synap pods' (list_pods) and connect " +
|
|
@@ -1520,19 +1553,10 @@ async function installZed(cfg) {
|
|
|
1520
1553
|
if (!fs.existsSync(settingsDir))
|
|
1521
1554
|
fs.mkdirSync(settingsDir, { recursive: true });
|
|
1522
1555
|
const { effectiveApiKey, agentUserId, mcpUrl } = await prepareMcpSurface(cfg, "zed");
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
// Zed settings.json is JSONC — strip // line comments and /* block comments */
|
|
1528
|
-
// before parsing. Comments will not be preserved in the rewritten file.
|
|
1529
|
-
const stripped = raw
|
|
1530
|
-
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
1531
|
-
.replace(/\/\/[^\n]*/g, "");
|
|
1532
|
-
settings = JSON.parse(stripped);
|
|
1533
|
-
}
|
|
1534
|
-
catch { /* malformed — start from scratch */ }
|
|
1535
|
-
}
|
|
1556
|
+
// Zed settings.json is JSONC. The strings-aware reader keeps the `//` of the
|
|
1557
|
+
// URLs inside it (a regex strip ate them and the parse failure wiped the
|
|
1558
|
+
// file); a file that still does not parse is refused, never replaced.
|
|
1559
|
+
const settings = readClientConfig(settingsPath);
|
|
1536
1560
|
const contextServers = (settings.context_servers ?? {});
|
|
1537
1561
|
// Pod-derived name — a second pod must not overwrite the first.
|
|
1538
1562
|
const zedServerName = mcpServerNameFromPodUrl(cfg.podUrl);
|
|
@@ -1542,17 +1566,17 @@ async function installZed(cfg) {
|
|
|
1542
1566
|
source: "custom",
|
|
1543
1567
|
command: {
|
|
1544
1568
|
path: "npx",
|
|
1545
|
-
args:
|
|
1569
|
+
args: mcpRemoteArgs(mcpUrl),
|
|
1546
1570
|
env: { SYNAP_AUTH: `Bearer ${effectiveApiKey}` },
|
|
1547
1571
|
},
|
|
1548
1572
|
settings: {},
|
|
1549
1573
|
};
|
|
1550
1574
|
settings.context_servers = contextServers;
|
|
1551
|
-
|
|
1575
|
+
writeClientConfig(settingsPath, settings);
|
|
1552
1576
|
const { setSurfaceAgentKey: save } = await import("./pod.js");
|
|
1553
1577
|
save("zed", { hubApiKey: effectiveApiKey, agentUserId, podUrl: cfg.podUrl });
|
|
1554
1578
|
log.success(`Zed settings updated: ${settingsPath}`);
|
|
1555
|
-
log.dim(
|
|
1579
|
+
log.dim(`Note: Zed settings are JSONC — comments are not kept in the rewrite. The previous file is at ${clientConfigBackupPath(settingsPath)}.`);
|
|
1556
1580
|
log.dim("Reload Zed to pick up the new context server (mcp-remote bridge).");
|
|
1557
1581
|
return true;
|
|
1558
1582
|
}
|
|
@@ -1571,13 +1595,8 @@ async function installVSCode(cfg) {
|
|
|
1571
1595
|
fs.mkdirSync(mcpDir, { recursive: true });
|
|
1572
1596
|
const { effectiveApiKey, agentUserId, mcpUrl } = await prepareMcpSurface(cfg, "vscode");
|
|
1573
1597
|
// VS Code 1.99+ global MCP format uses "servers" (not "mcpServers")
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
try {
|
|
1577
|
-
existing = JSON.parse(fs.readFileSync(mcpJsonPath, "utf-8"));
|
|
1578
|
-
}
|
|
1579
|
-
catch { /* ignore */ }
|
|
1580
|
-
}
|
|
1598
|
+
// VS Code's mcp.json is JSONC (comments, trailing commas). Unreadable → refused.
|
|
1599
|
+
const existing = readClientConfig(mcpJsonPath);
|
|
1581
1600
|
existing.servers = existing.servers ?? {};
|
|
1582
1601
|
// Pod-derived name — a second pod must not overwrite the first.
|
|
1583
1602
|
const vscodeServerName = mcpServerNameFromPodUrl(cfg.podUrl);
|
|
@@ -1588,7 +1607,7 @@ async function installVSCode(cfg) {
|
|
|
1588
1607
|
url: mcpUrl,
|
|
1589
1608
|
headers: { Authorization: `Bearer ${effectiveApiKey}` },
|
|
1590
1609
|
};
|
|
1591
|
-
|
|
1610
|
+
writeClientConfig(mcpJsonPath, existing);
|
|
1592
1611
|
const { setSurfaceAgentKey: save } = await import("./pod.js");
|
|
1593
1612
|
save("vscode", { hubApiKey: effectiveApiKey, agentUserId, podUrl: cfg.podUrl });
|
|
1594
1613
|
log.success(`VS Code MCP config updated: ${mcpJsonPath}`);
|
|
@@ -1703,7 +1722,7 @@ async function installGeneric(cfg) {
|
|
|
1703
1722
|
log.dim(`headers = { Authorization = "Bearer ${shown}" }`);
|
|
1704
1723
|
log.blank();
|
|
1705
1724
|
log.info("stdio bridge (mcp-remote) — key via env, not argv:");
|
|
1706
|
-
log.dim(` command: npx -y
|
|
1725
|
+
log.dim(` command: npx -y ${MCP_REMOTE_PACKAGE} ${mcpUrl} --header "Authorization:\${SYNAP_AUTH}"`);
|
|
1707
1726
|
log.dim(` env: SYNAP_AUTH="Bearer ${shown}"`);
|
|
1708
1727
|
log.dim(" Real key: ~/.synap/mcp-config.json (0600).");
|
|
1709
1728
|
log.blank();
|
|
@@ -1711,19 +1730,10 @@ async function installGeneric(cfg) {
|
|
|
1711
1730
|
ensureSecretDir(synapDir);
|
|
1712
1731
|
const configFile = path.join(synapDir, "mcp-config.json");
|
|
1713
1732
|
// Merge with existing so dual-pod generic installs don't clobber each other
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
try {
|
|
1717
|
-
existing = JSON.parse(fs.readFileSync(configFile, "utf-8"));
|
|
1718
|
-
}
|
|
1719
|
-
catch {
|
|
1720
|
-
/* replace */
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1733
|
+
// Unreadable → refused: it may hold another pod's entry.
|
|
1734
|
+
const existing = readClientConfig(configFile);
|
|
1723
1735
|
const merged = { ...existing, ...httpConfig };
|
|
1724
|
-
|
|
1725
|
-
mode: 0o600,
|
|
1726
|
-
});
|
|
1736
|
+
writeClientConfig(configFile, merged);
|
|
1727
1737
|
const { setSurfaceAgentKey: save } = await import("./pod.js");
|
|
1728
1738
|
save("generic", {
|
|
1729
1739
|
hubApiKey: effectiveApiKey,
|
|
@@ -1826,8 +1836,15 @@ async function pickProvider(podUrl, apiKey) {
|
|
|
1826
1836
|
const spinner = ora("Fetching providers from pod vault...").start();
|
|
1827
1837
|
let providers;
|
|
1828
1838
|
try {
|
|
1829
|
-
|
|
1839
|
+
const creds = await fetchCredentials(podUrl, apiKey);
|
|
1830
1840
|
spinner.stop();
|
|
1841
|
+
if (creds.masked) {
|
|
1842
|
+
// A masked key written into a tool's config would fail on its first call.
|
|
1843
|
+
log.warn("This pod gives provider keys only to its own services, not to an agent key.");
|
|
1844
|
+
log.dim("Point the tool at the pod's OpenAI-compatible endpoint instead: synap providers pull --target show");
|
|
1845
|
+
return null;
|
|
1846
|
+
}
|
|
1847
|
+
providers = creds.providers;
|
|
1831
1848
|
}
|
|
1832
1849
|
catch (err) {
|
|
1833
1850
|
spinner.fail(`Could not fetch providers: ${err.message}`);
|
|
@@ -1877,16 +1894,8 @@ export async function installOpencode(cfg) {
|
|
|
1877
1894
|
if (!fs.existsSync(configDir)) {
|
|
1878
1895
|
fs.mkdirSync(configDir, { recursive: true });
|
|
1879
1896
|
}
|
|
1880
|
-
|
|
1881
|
-
|
|
1882
|
-
try {
|
|
1883
|
-
existing = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
1884
|
-
}
|
|
1885
|
-
catch {
|
|
1886
|
-
copySecretBackup(configPath, `${configPath}.bak`);
|
|
1887
|
-
log.warn("Existing opencode.json unreadable — backed up to opencode.json.bak");
|
|
1888
|
-
}
|
|
1889
|
-
}
|
|
1897
|
+
// opencode.json is JSONC. Unreadable → refused, never replaced.
|
|
1898
|
+
const existing = readClientConfig(configPath);
|
|
1890
1899
|
const providerBlock = (existing.provider ?? {});
|
|
1891
1900
|
providerBlock[provider.providerId] = {
|
|
1892
1901
|
npm: "@ai-sdk/openai-compatible",
|
|
@@ -1912,7 +1921,7 @@ export async function installOpencode(cfg) {
|
|
|
1912
1921
|
mcp: mcpBlock,
|
|
1913
1922
|
...(existing.model ? {} : { model: defaultModel }),
|
|
1914
1923
|
};
|
|
1915
|
-
|
|
1924
|
+
writeClientConfig(configPath, updated);
|
|
1916
1925
|
const { setSurfaceAgentKey: save } = await import("./pod.js");
|
|
1917
1926
|
save("opencode", { hubApiKey: agentKey, agentUserId, podUrl: cfg.podUrl });
|
|
1918
1927
|
log.success(`opencode configured: ~/.config/opencode/opencode.json`);
|
|
@@ -1961,38 +1970,25 @@ export function writeMcpServerEntry(configPath, serverName, server) {
|
|
|
1961
1970
|
if (!fs.existsSync(dir)) {
|
|
1962
1971
|
fs.mkdirSync(dir, { recursive: true });
|
|
1963
1972
|
}
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
1968
|
-
}
|
|
1969
|
-
catch {
|
|
1970
|
-
// corrupt or unreadable — back up and start fresh
|
|
1971
|
-
const backup = `${configPath}.bak-${Date.now()}`;
|
|
1972
|
-
copySecretBackup(configPath, backup);
|
|
1973
|
-
log.warn(`Existing config unreadable; backed up to ${path.basename(backup)}`);
|
|
1974
|
-
}
|
|
1975
|
-
}
|
|
1973
|
+
// Unreadable → refused (it holds the person's other MCP servers), never
|
|
1974
|
+
// "backed up and started fresh".
|
|
1975
|
+
const config = readClientConfig(configPath);
|
|
1976
1976
|
config.mcpServers = config.mcpServers ?? {};
|
|
1977
1977
|
config.mcpServers[serverName] = server;
|
|
1978
|
-
|
|
1979
|
-
mode: 0o600,
|
|
1980
|
-
});
|
|
1978
|
+
writeClientConfig(configPath, config);
|
|
1981
1979
|
}
|
|
1982
1980
|
/** Best-effort remove of a named MCP server entry (e.g. legacy `"synap"`). */
|
|
1983
1981
|
function removeMcpServerEntry(configPath, serverName) {
|
|
1984
1982
|
if (!fs.existsSync(configPath))
|
|
1985
1983
|
return;
|
|
1986
1984
|
try {
|
|
1987
|
-
const config =
|
|
1985
|
+
const config = readClientConfig(configPath);
|
|
1988
1986
|
if (!config.mcpServers?.[serverName])
|
|
1989
1987
|
return;
|
|
1990
1988
|
delete config.mcpServers[serverName];
|
|
1991
1989
|
if (Object.keys(config.mcpServers).length === 0)
|
|
1992
1990
|
delete config.mcpServers;
|
|
1993
|
-
|
|
1994
|
-
mode: 0o600,
|
|
1995
|
-
});
|
|
1991
|
+
writeClientConfig(configPath, config);
|
|
1996
1992
|
}
|
|
1997
1993
|
catch {
|
|
1998
1994
|
/* leave config alone if unreadable */
|