@synap-core/cli 1.12.2 → 1.13.1-next.20261102.ec3d166
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/LICENSE +21 -0
- package/README.md +38 -11
- package/dist/build-info.json +2 -3
- package/dist/commands/agents.d.ts +0 -1
- package/dist/commands/agents.js +13 -3
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/app.js +2 -1
- package/dist/commands/app.js.map +1 -1
- package/dist/commands/automation.js +46 -41
- package/dist/commands/automation.js.map +1 -1
- package/dist/commands/box.d.ts +42 -0
- package/dist/commands/box.js +89 -0
- package/dist/commands/box.js.map +1 -0
- package/dist/commands/bridge-setup.js +3 -1
- package/dist/commands/bridge-setup.js.map +1 -1
- package/dist/commands/capability.d.ts +87 -1
- package/dist/commands/capability.js +174 -64
- package/dist/commands/capability.js.map +1 -1
- package/dist/commands/close-questions.d.ts +27 -0
- package/dist/commands/close-questions.js +49 -0
- package/dist/commands/close-questions.js.map +1 -0
- package/dist/commands/close-questions.test.d.ts +1 -0
- package/dist/commands/close-questions.test.js +52 -0
- package/dist/commands/close-questions.test.js.map +1 -0
- package/dist/commands/connect.d.ts +1 -3
- package/dist/commands/connect.js +2 -2
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/context.js +72 -43
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/data-extra.d.ts +13 -0
- package/dist/commands/data-extra.js +35 -6
- package/dist/commands/data-extra.js.map +1 -1
- package/dist/commands/data.js +2 -1
- package/dist/commands/data.js.map +1 -1
- package/dist/commands/doctor.d.ts +39 -0
- package/dist/commands/doctor.js +136 -14
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/hooks.d.ts +19 -0
- package/dist/commands/hooks.js +70 -0
- package/dist/commands/hooks.js.map +1 -0
- package/dist/commands/import.d.ts +25 -0
- package/dist/commands/import.js +98 -12
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/init.d.ts +14 -0
- package/dist/commands/init.js +63 -21
- 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 +13 -3
- package/dist/commands/knowledge.js.map +1 -1
- package/dist/commands/lens.js +2 -1
- package/dist/commands/lens.js.map +1 -1
- package/dist/commands/market-authoring.js +1 -1
- package/dist/commands/market-authoring.js.map +1 -1
- package/dist/commands/market.d.ts +61 -4
- package/dist/commands/market.js +106 -48
- 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/observe.category.test.d.ts +1 -0
- package/dist/commands/observe.category.test.js +59 -0
- package/dist/commands/observe.category.test.js.map +1 -0
- package/dist/commands/observe.d.ts +3 -0
- package/dist/commands/observe.js +7 -5
- package/dist/commands/observe.js.map +1 -1
- package/dist/commands/observe.title.test.d.ts +1 -0
- package/dist/commands/observe.title.test.js +37 -0
- package/dist/commands/observe.title.test.js.map +1 -0
- package/dist/commands/playbook.js +9 -0
- package/dist/commands/playbook.js.map +1 -1
- package/dist/commands/pods.d.ts +6 -1
- package/dist/commands/pods.js +38 -17
- 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/proton-setup.js +3 -1
- package/dist/commands/proton-setup.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/session-feed-wait.d.ts +48 -0
- package/dist/commands/session-feed-wait.js +261 -0
- package/dist/commands/session-feed-wait.js.map +1 -0
- package/dist/commands/session-post.d.ts +40 -0
- package/dist/commands/session-post.js +96 -0
- package/dist/commands/session-post.js.map +1 -0
- package/dist/commands/session-wait.d.ts +9 -4
- package/dist/commands/session-wait.js +20 -5
- package/dist/commands/session-wait.js.map +1 -1
- package/dist/commands/sessions.d.ts +2 -0
- package/dist/commands/sessions.js +23 -0
- package/dist/commands/sessions.js.map +1 -1
- package/dist/commands/skills-commands.d.ts +2 -0
- package/dist/commands/skills-commands.js +9 -1
- package/dist/commands/skills-commands.js.map +1 -1
- package/dist/commands/skills-commands.test.d.ts +1 -0
- package/dist/commands/skills-commands.test.js +57 -0
- package/dist/commands/skills-commands.test.js.map +1 -0
- package/dist/commands/status.js +13 -7
- 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 +68 -6
- package/dist/commands/tracks.js +272 -16
- 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/whoami.d.ts +7 -0
- package/dist/commands/whoami.js +17 -0
- package/dist/commands/whoami.js.map +1 -1
- package/dist/commands/worker-accounts.d.ts +149 -0
- package/dist/commands/worker-accounts.js +339 -0
- package/dist/commands/worker-accounts.js.map +1 -0
- package/dist/commands/worker-accounts.test.d.ts +1 -0
- package/dist/commands/worker-accounts.test.js +224 -0
- package/dist/commands/worker-accounts.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 +406 -10
- package/dist/commands/worker.js +1180 -126
- package/dist/commands/worker.js.map +1 -1
- package/dist/commands/worker.test.js +588 -11
- package/dist/commands/worker.test.js.map +1 -1
- package/dist/index.js +220 -28
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-door.d.ts +1 -3
- package/dist/lib/agent-door.js +10 -7
- package/dist/lib/agent-door.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 +28 -2
- package/dist/lib/auth.js +52 -12
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/box/egress.d.ts +82 -0
- package/dist/lib/box/egress.js +264 -0
- package/dist/lib/box/egress.js.map +1 -0
- package/dist/lib/box/evidence.d.ts +86 -0
- package/dist/lib/box/evidence.js +121 -0
- package/dist/lib/box/evidence.js.map +1 -0
- package/dist/lib/box/job-server.d.ts +72 -0
- package/dist/lib/box/job-server.js +250 -0
- package/dist/lib/box/job-server.js.map +1 -0
- package/dist/lib/box/provisioner.d.ts +83 -0
- package/dist/lib/box/provisioner.js +312 -0
- package/dist/lib/box/provisioner.js.map +1 -0
- package/dist/lib/box/proxmox.d.ts +112 -0
- package/dist/lib/box/proxmox.js +211 -0
- package/dist/lib/box/proxmox.js.map +1 -0
- package/dist/lib/box/wire.d.ts +98 -0
- package/dist/lib/box/wire.js +70 -0
- package/dist/lib/box/wire.js.map +1 -0
- 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.d.ts +1 -0
- package/dist/lib/client-header.test.js +103 -0
- package/dist/lib/client-header.test.js.map +1 -0
- package/dist/lib/conversation-headers.d.ts +89 -9
- package/dist/lib/conversation-headers.js +156 -3
- 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 +40 -2
- package/dist/lib/credential-class.js +48 -6
- 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/exporter-coverage.d.ts +1 -1
- package/dist/lib/exporter-coverage.js +1 -1
- package/dist/lib/goal-words.d.ts +38 -0
- package/dist/lib/goal-words.js +115 -0
- package/dist/lib/goal-words.js.map +1 -0
- package/dist/lib/hub-client.d.ts +87 -8
- package/dist/lib/hub-client.js +205 -70
- package/dist/lib/hub-client.js.map +1 -1
- package/dist/lib/init-handoff.d.ts +8 -0
- package/dist/lib/init-handoff.js +9 -2
- package/dist/lib/init-handoff.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/key-source.d.ts +3 -1
- package/dist/lib/key-source.js +7 -4
- package/dist/lib/key-source.js.map +1 -1
- package/dist/lib/knowledge-title.d.ts +3 -2
- package/dist/lib/knowledge-title.js +3 -2
- package/dist/lib/knowledge-title.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 +50 -0
- package/dist/lib/mcp-bridge.js +232 -11
- package/dist/lib/mcp-bridge.js.map +1 -1
- package/dist/lib/mcp-bridge.test.js +224 -4
- 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-moved.d.ts +70 -0
- package/dist/lib/pod-moved.js +146 -0
- package/dist/lib/pod-moved.js.map +1 -0
- package/dist/lib/pod-reach.d.ts +39 -0
- package/dist/lib/pod-reach.js +117 -0
- package/dist/lib/pod-reach.js.map +1 -0
- package/dist/lib/pod-reach.test.d.ts +1 -0
- package/dist/lib/pod-reach.test.js +67 -0
- package/dist/lib/pod-reach.test.js.map +1 -0
- package/dist/lib/pod.d.ts +8 -4
- package/dist/lib/pod.js +12 -10
- package/dist/lib/pod.js.map +1 -1
- package/dist/lib/register-claude-code-mcp.test.d.ts +1 -0
- package/dist/lib/register-claude-code-mcp.test.js +97 -0
- package/dist/lib/register-claude-code-mcp.test.js.map +1 -0
- package/dist/lib/review-credential.js +3 -1
- package/dist/lib/review-credential.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/session-feed.d.ts +185 -0
- package/dist/lib/session-feed.js +257 -0
- package/dist/lib/session-feed.js.map +1 -0
- package/dist/lib/targets.d.ts +52 -3
- package/dist/lib/targets.js +150 -209
- 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/dist/lib/wake-hook-settings.d.ts +57 -0
- package/dist/lib/wake-hook-settings.js +153 -0
- package/dist/lib/wake-hook-settings.js.map +1 -0
- package/dist/lib/wake-state.d.ts +65 -0
- package/dist/lib/wake-state.js +122 -0
- package/dist/lib/wake-state.js.map +1 -0
- package/package.json +5 -2
- package/skills/README.md +1 -1
- package/skills/agent-os/SKILL.md +8 -8
- package/skills/synap/SKILL.md +178 -88
- package/skills/synap/code-skills.md +84 -0
- package/skills/synap/concepts.md +38 -20
- package/skills/synap/creative-loop.md +1 -1
- package/skills/synap/document-embeds.md +17 -1
- package/skills/synap/focus-sessions.md +17 -8
- package/skills/synap/from-intent.md +33 -17
- package/skills/synap/governance-rules.md +3 -1
- package/skills/synap/lenses.md +4 -4
- package/skills/synap/linking.md +2 -2
- package/skills/synap/mental-model.md +1 -1
- package/skills/synap/operating-mode-rules.md +1 -1
- package/skills/synap/quick-reference.md +2 -1
- package/skills/synap/reflexes.md +8 -8
- package/skills/synap/viewframe-cells.md +1 -1
- package/skills/synap/work-model.md +24 -10
- package/skills/synap/workspace-design.md +5 -5
- package/skills/synap/writes.md +16 -9
- package/skills/synap/writing-titles.md +7 -0
- package/skills/synap-app/SKILL.md +148 -66
- package/skills/synap-app/creating-the-base-app.md +12 -7
- package/skills/synap-app/governance.md +27 -16
- package/skills/synap-app/install-commands.md +18 -9
- package/skills/synap-app/overview.md +16 -9
- package/skills/synap-app/using-the-sdk.md +75 -25
- package/skills/synap-market/SKILL.md +1 -1
- package/skills/synap-market/governance-and-catalog.md +1 -1
- package/skills/synap-schema/SKILL.md +24 -21
- package/skills/synap-schema/intent-vocabulary-ssot.md +24 -21
- package/templates/box/runner.py +206 -0
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";
|
|
@@ -144,38 +145,6 @@ function claudeDesktopBaseDir() {
|
|
|
144
145
|
return CLAUDE_DESKTOP_LINUX;
|
|
145
146
|
}
|
|
146
147
|
}
|
|
147
|
-
/** Raycast app-support root (macOS only — Raycast is macOS-only). */
|
|
148
|
-
function raycastSupportDir() {
|
|
149
|
-
return path.join(os.homedir(), "Library", "Application Support", "com.raycast.macos");
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* Locate Raycast's EXISTING `mcp-config.json`. Raycast stores all non-dev MCP
|
|
153
|
-
* servers in ONE file inside the "Manage MCP Servers" extension's support
|
|
154
|
-
* directory — an opaque UUID folder under `…/com.raycast.macos/extensions/`.
|
|
155
|
-
* The folder name is only known to Raycast and the file is created lazily the
|
|
156
|
-
* first time the user opens "Manage MCP Servers".
|
|
157
|
-
*
|
|
158
|
-
* We therefore scan the support dirs for an EXISTING `mcp-config.json` and
|
|
159
|
-
* return its path (so we can merge into it). Returns `null` when none exists —
|
|
160
|
-
* the caller then prints the block for the user to paste via Raycast's
|
|
161
|
-
* "Show Config File in Finder" action (the only reliable first-time path).
|
|
162
|
-
*/
|
|
163
|
-
function raycastMcpConfigPath() {
|
|
164
|
-
const extensionsDir = path.join(raycastSupportDir(), "extensions");
|
|
165
|
-
try {
|
|
166
|
-
for (const entry of fs.readdirSync(extensionsDir, { withFileTypes: true })) {
|
|
167
|
-
if (!entry.isDirectory())
|
|
168
|
-
continue;
|
|
169
|
-
const candidate = path.join(extensionsDir, entry.name, "mcp-config.json");
|
|
170
|
-
if (fs.existsSync(candidate))
|
|
171
|
-
return candidate;
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
catch {
|
|
175
|
-
/* extensions dir missing — Raycast not installed or never opened */
|
|
176
|
-
}
|
|
177
|
-
return null;
|
|
178
|
-
}
|
|
179
148
|
export const TARGETS = {
|
|
180
149
|
"claude-code": {
|
|
181
150
|
name: "claude-code",
|
|
@@ -207,9 +176,11 @@ export const TARGETS = {
|
|
|
207
176
|
},
|
|
208
177
|
raycast: {
|
|
209
178
|
name: "raycast",
|
|
210
|
-
keyStore: {
|
|
179
|
+
keyStore: {
|
|
180
|
+
none: "Raycast keeps its MCP sign-in in its own store; the commands' key is in ~/.synap/config.json",
|
|
181
|
+
},
|
|
211
182
|
label: "Raycast",
|
|
212
|
-
description: "
|
|
183
|
+
description: "Synap extension commands + the pod's MCP server in Raycast AI (HTTP, OAuth)",
|
|
213
184
|
supports: { skills: false, mcp: true },
|
|
214
185
|
},
|
|
215
186
|
openwebui: {
|
|
@@ -347,7 +318,7 @@ async function prepareMcpSurface(cfg, agentType, opts) {
|
|
|
347
318
|
// site keeps showing "validate this proposal" on every reconnect. The pod
|
|
348
319
|
// returns `alreadyValid` for an existing key; we recover the plaintext from
|
|
349
320
|
// local storage and reuse it, rotating only on positive invalidation.
|
|
350
|
-
const { hubApiKey: effectiveApiKey, agentUserId } = await provisionAgentKey(cfg.podUrl, cfg.apiKey, agentType, { idempotent: opts?.idempotent ?? true });
|
|
321
|
+
const { hubApiKey: effectiveApiKey, agentUserId } = await provisionAgentKey(cfg.podUrl, cfg.apiKey, agentType, { idempotent: opts?.idempotent ?? true, toolProfile: opts?.toolProfile });
|
|
351
322
|
await enrollAgentIfNeeded(cfg.podUrl, cfg.apiKey, agentUserId, cfg.workspaceId);
|
|
352
323
|
return { effectiveApiKey, agentUserId, mcpUrl: buildMcpUrl(cfg.podUrl, cfg.workspaceId, cfg.projectId) };
|
|
353
324
|
}
|
|
@@ -495,8 +466,8 @@ export async function installForTarget(target, cfg, opts) {
|
|
|
495
466
|
result = await installGeneric(cfg);
|
|
496
467
|
break;
|
|
497
468
|
}
|
|
498
|
-
// Post-install: agent context wizard for MCP targets. Skip Raycast —
|
|
499
|
-
//
|
|
469
|
+
// Post-install: agent context wizard for MCP targets. Skip Raycast — its AI
|
|
470
|
+
// reads the pod's MCP instructions; CONTEXT.md has no Raycast consumer.
|
|
500
471
|
if (result && info.supports.mcp && target !== "generic" && target !== "raycast") {
|
|
501
472
|
const { getSurfaceAgentKey } = await import("./pod.js");
|
|
502
473
|
const saved = getSurfaceAgentKey(target);
|
|
@@ -666,13 +637,10 @@ export async function resolveWorkspaceId(cfg) {
|
|
|
666
637
|
export async function writeClaudeCodeEnv(cfg, { writeMcp = true } = {}) {
|
|
667
638
|
const settingsPath = path.join(os.homedir(), ".claude", "settings.json");
|
|
668
639
|
const settingsDir = path.dirname(settingsPath);
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
}
|
|
674
|
-
catch { /* start fresh */ }
|
|
675
|
-
}
|
|
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);
|
|
676
644
|
// ── Resolve human user ID and provision an agent-owned API key ──────────
|
|
677
645
|
// SYNAP_USER_ID = human user (for entity attribution)
|
|
678
646
|
// SYNAP_HUB_API_KEY = agent-owned key (identity encoded in the key itself)
|
|
@@ -756,7 +724,7 @@ export async function writeClaudeCodeEnv(cfg, { writeMcp = true } = {}) {
|
|
|
756
724
|
if (!fs.existsSync(settingsDir)) {
|
|
757
725
|
fs.mkdirSync(settingsDir, { recursive: true });
|
|
758
726
|
}
|
|
759
|
-
|
|
727
|
+
writeClientConfig(settingsPath, settings);
|
|
760
728
|
// ── Register the MCP server where Claude Code actually reads it ───────────
|
|
761
729
|
// The official `claude mcp add` writes to the user-scope registry that
|
|
762
730
|
// `claude mcp list` + the runtime consult — unlike settings.json mcpServers,
|
|
@@ -778,9 +746,11 @@ export async function writeClaudeCodeEnv(cfg, { writeMcp = true } = {}) {
|
|
|
778
746
|
* Server name is pod-derived (`mcpServerNameFromPodUrl`) so multi-pod setups
|
|
779
747
|
* (perso + team) do not overwrite each other — same pattern as Grok/Cursor.
|
|
780
748
|
*/
|
|
781
|
-
function registerClaudeCodeMcp(mcpUrl, apiKey, serverName) {
|
|
749
|
+
export function registerClaudeCodeMcp(mcpUrl, apiKey, serverName) {
|
|
782
750
|
const header = `Authorization: Bearer ${apiKey}`;
|
|
783
751
|
const bridge = claudeCodeMcpTransport() === "bridge";
|
|
752
|
+
const invocation = bridge ? resolveBridgeInvocation() : null;
|
|
753
|
+
const bridgeCommand = invocation ? synapBridgeCommand(mcpUrl, invocation) : [];
|
|
784
754
|
try {
|
|
785
755
|
// Remove legacy single-name entry and any prior pod-derived name.
|
|
786
756
|
for (const name of new Set(["synap", serverName])) {
|
|
@@ -795,9 +765,14 @@ function registerClaudeCodeMcp(mcpUrl, apiKey, serverName) {
|
|
|
795
765
|
? // Stdio: Claude Code hands a stdio server its own session id, which
|
|
796
766
|
// the bridge forwards — see lib/mcp-bridge.ts. The key rides in the
|
|
797
767
|
// server's env, as the header does for http.
|
|
798
|
-
|
|
768
|
+
// `-e` is variadic in Claude Code's CLI: the server name must come
|
|
769
|
+
// BEFORE it, or `-e` swallows it as an env var.
|
|
770
|
+
["mcp", "add", "--scope", "user", serverName, "-e", `SYNAP_MCP_API_KEY=${apiKey}`, "--", ...bridgeCommand]
|
|
799
771
|
: ["mcp", "add", "--transport", "http", serverName, mcpUrl, "--header", header, "--scope", "user"], { stdio: "ignore" });
|
|
800
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);
|
|
801
776
|
if (serverName !== "synap") {
|
|
802
777
|
log.dim("Multi-pod: connect each profile separately — server names are derived from the pod host so they don't overwrite:");
|
|
803
778
|
log.dim(" synap connect --target=claude-code --name=perso");
|
|
@@ -812,15 +787,20 @@ function registerClaudeCodeMcp(mcpUrl, apiKey, serverName) {
|
|
|
812
787
|
try {
|
|
813
788
|
ensureSecretDir(path.dirname(keyFile));
|
|
814
789
|
writeSecretFile(keyFile, `export SYNAP_MCP_KEY='${apiKey}'\n`);
|
|
815
|
-
log.dim(
|
|
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):`);
|
|
816
791
|
log.dim(` source ${keyFile}`);
|
|
817
|
-
log.dim(
|
|
792
|
+
log.dim(bridge
|
|
793
|
+
? ` claude mcp add --scope user ${serverName} -e SYNAP_MCP_API_KEY="$SYNAP_MCP_KEY" -- ${bridgeCommand.map(shellQuote).join(" ")}`
|
|
794
|
+
: ` claude mcp add --transport http ${serverName} ${mcpUrl} --header 'Authorization: Bearer \${SYNAP_MCP_KEY}' --scope user`);
|
|
818
795
|
}
|
|
819
796
|
catch {
|
|
820
797
|
log.dim("Could not run `claude mcp add` (is the claude CLI on PATH?) and could not save the key file — run `synap mcp url` instead.");
|
|
821
798
|
}
|
|
822
799
|
}
|
|
823
800
|
}
|
|
801
|
+
function shellQuote(arg) {
|
|
802
|
+
return /^[A-Za-z0-9_\-./:=@]+$/.test(arg) ? arg : `'${arg.replace(/'/g, `'\\''`)}'`;
|
|
803
|
+
}
|
|
824
804
|
/**
|
|
825
805
|
* How Claude Code reaches the pod's MCP: `"http"` (direct) or `"bridge"` (the
|
|
826
806
|
* synap stdio bridge, which tells the pod which Claude Code conversation each
|
|
@@ -844,13 +824,8 @@ export function claudeCodeMcpTransport() {
|
|
|
844
824
|
*/
|
|
845
825
|
export function setClaudeCodeMcpTransport(transport) {
|
|
846
826
|
const settingsPath = path.join(os.homedir(), ".claude", "settings.json");
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
settings = JSON.parse(fs.readFileSync(settingsPath, "utf-8"));
|
|
850
|
-
}
|
|
851
|
-
catch {
|
|
852
|
-
return null;
|
|
853
|
-
}
|
|
827
|
+
// Missing → `{}` → no stored pod → null. Unreadable → throws (never rewritten).
|
|
828
|
+
const settings = readClientConfig(settingsPath);
|
|
854
829
|
const env = (settings.env ?? {});
|
|
855
830
|
const podUrl = env["SYNAP_POD_URL"];
|
|
856
831
|
const apiKey = env["SYNAP_HUB_API_KEY"];
|
|
@@ -861,24 +836,59 @@ export function setClaudeCodeMcpTransport(transport) {
|
|
|
861
836
|
else
|
|
862
837
|
delete env["SYNAP_MCP_TRANSPORT"];
|
|
863
838
|
settings.env = env;
|
|
864
|
-
|
|
839
|
+
writeClientConfig(settingsPath, settings);
|
|
865
840
|
const serverName = mcpServerNameFromPodUrl(podUrl);
|
|
866
841
|
registerClaudeCodeMcp(buildMcpUrl(podUrl, env["SYNAP_WORKSPACE_ID"], env["SYNAP_PROJECT_ID"]), apiKey, serverName);
|
|
867
842
|
return serverName;
|
|
868
843
|
}
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
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
|
+
}
|
|
880
875
|
}
|
|
881
|
-
return
|
|
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}`;
|
|
890
|
+
}
|
|
891
|
+
return null;
|
|
882
892
|
}
|
|
883
893
|
// ─── Agent key provisioning helper ──────────────────────────────────────────
|
|
884
894
|
/**
|
|
@@ -923,7 +933,7 @@ export class AgentKeyMintError extends Error {
|
|
|
923
933
|
* differs from the CLI surface (e.g. ChatGPT provisions under
|
|
924
934
|
* "generic" but the CLI surface is "chatgpt").
|
|
925
935
|
*/
|
|
926
|
-
export async function provisionAgentKey(podUrl, humanApiKey, agentType, { requireApproval = true, idempotent = false, surfaceName, deferApproval = false, instanceId, fallbackCandidate, } = {}) {
|
|
936
|
+
export async function provisionAgentKey(podUrl, humanApiKey, agentType, { requireApproval = true, idempotent = false, surfaceName, deferApproval = false, instanceId, fallbackCandidate, toolProfile, } = {}) {
|
|
927
937
|
// `synap init` already minted — and the person approved — this key in one
|
|
928
938
|
// step with the others (`approveAgentKeysAtOnce`): hand it over, mint nothing.
|
|
929
939
|
if (!deferApproval) {
|
|
@@ -988,6 +998,7 @@ export async function provisionAgentKey(podUrl, humanApiKey, agentType, { requir
|
|
|
988
998
|
requireApproval,
|
|
989
999
|
instanceId: instance,
|
|
990
1000
|
instanceLabel: machineInstanceLabel(),
|
|
1001
|
+
...(toolProfile ? { toolProfile } : {}),
|
|
991
1002
|
}),
|
|
992
1003
|
signal: AbortSignal.timeout(30000),
|
|
993
1004
|
});
|
|
@@ -1059,6 +1070,7 @@ export async function provisionAgentKey(podUrl, humanApiKey, agentType, { requir
|
|
|
1059
1070
|
surfaceName,
|
|
1060
1071
|
deferApproval,
|
|
1061
1072
|
instanceId: instance,
|
|
1073
|
+
toolProfile,
|
|
1062
1074
|
});
|
|
1063
1075
|
}
|
|
1064
1076
|
if (!body.hubApiKey) {
|
|
@@ -1204,7 +1216,7 @@ async function installClaudeDesktop(info, cfg) {
|
|
|
1204
1216
|
command: "npx",
|
|
1205
1217
|
// The header value rides in env (this file is 0600), never in the
|
|
1206
1218
|
// mcp-remote process's argv where any local `ps` can read it.
|
|
1207
|
-
args:
|
|
1219
|
+
args: mcpRemoteArgs(desktopMcpUrl),
|
|
1208
1220
|
env: { SYNAP_AUTH: `Bearer ${effectiveApiKey}` },
|
|
1209
1221
|
});
|
|
1210
1222
|
if (desktopServerName !== "synap")
|
|
@@ -1276,10 +1288,20 @@ async function installCursor(info, cfg) {
|
|
|
1276
1288
|
return true;
|
|
1277
1289
|
}
|
|
1278
1290
|
// ─── Raycast ─────────────────────────────────────────────────────────────────
|
|
1291
|
+
/**
|
|
1292
|
+
* Raycast has two halves (decision B1, 2026-10-21):
|
|
1293
|
+
* - the Synap extension's native COMMANDS (search, capture, review…) call Hub
|
|
1294
|
+
* REST with a dedicated `raycast` agent key, read from ~/.synap/config.json;
|
|
1295
|
+
* - Raycast AI talks to the pod's own MCP server over HTTP (Streamable HTTP),
|
|
1296
|
+
* signed in with OAuth, or with that same agent key in a header as the
|
|
1297
|
+
* fallback. The extension ships no AI tools of its own any more.
|
|
1298
|
+
*
|
|
1299
|
+
* Raycast keeps its MCP servers in its own store, filled through its "Install
|
|
1300
|
+
* MCP Server" form; its file format for an HTTP server is not documented, so we
|
|
1301
|
+
* print the form's fields rather than write a guessed shape into Raycast's
|
|
1302
|
+
* config. The key is never printed: the fallback header lives in a 0600 file.
|
|
1303
|
+
*/
|
|
1279
1304
|
async function installRaycast(cfg) {
|
|
1280
|
-
// Default path: native Synap extension (@synap) is the catalog. Persist
|
|
1281
|
-
// workspace + provision a dedicated raycast agent key for extension writes.
|
|
1282
|
-
// Full MCP (mcp-remote → mcp-config.json) is opt-in via cfg.withMcp.
|
|
1283
1305
|
const { setActiveWorkspaceId, clearActiveWorkspaceId } = await import("./pod.js");
|
|
1284
1306
|
if (cfg.workspaceId) {
|
|
1285
1307
|
setActiveWorkspaceId(cfg.workspaceId);
|
|
@@ -1287,73 +1309,29 @@ async function installRaycast(cfg) {
|
|
|
1287
1309
|
else {
|
|
1288
1310
|
clearActiveWorkspaceId();
|
|
1289
1311
|
}
|
|
1290
|
-
|
|
1312
|
+
// A key minted here lists the 9-tool starter set on /mcp and loads more on
|
|
1313
|
+
// demand (`entry`); an existing live key is reused as it is.
|
|
1314
|
+
const { effectiveApiKey, agentUserId, mcpUrl } = await prepareMcpSurface(cfg, "raycast", {
|
|
1315
|
+
toolProfile: "entry",
|
|
1316
|
+
});
|
|
1291
1317
|
const { setSurfaceAgentKey } = await import("./pod.js");
|
|
1292
1318
|
setSurfaceAgentKey("raycast", { hubApiKey: effectiveApiKey, agentUserId, podUrl: cfg.podUrl });
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
log.
|
|
1319
|
+
const { writeMcpSnippetFiles } = await import("./mcp-snippets.js");
|
|
1320
|
+
const files = writeMcpSnippetFiles(mcpUrl, effectiveApiKey, agentUserId);
|
|
1321
|
+
log.success("Raycast agent key stored for the Synap extension's commands.");
|
|
1296
1322
|
log.dim(` Pod: ${cfg.podUrl}`);
|
|
1297
1323
|
log.dim(` Scope: ${cfg.workspaceId ? `workspace ${cfg.workspaceId}` : "all workspaces (pod-wide)"}`);
|
|
1298
|
-
if (!cfg.withMcp) {
|
|
1299
|
-
log.blank();
|
|
1300
|
-
log.dim("Full MCP (mcp-remote) is opt-in: synap connect --target=raycast --with-mcp");
|
|
1301
|
-
return true;
|
|
1302
|
-
}
|
|
1303
|
-
// ── Opt-in power door: write Raycast MCP server config (merge, never clobber) ─
|
|
1304
|
-
// Raycast's native MCP is stdio-only — bridge the HTTP /mcp endpoint with the
|
|
1305
|
-
// `mcp-remote` npm package (npx spawns it). Same bridge as Claude Desktop/Zed.
|
|
1306
|
-
// Tool names will overlap the @synap extension catalog.
|
|
1307
1324
|
log.blank();
|
|
1308
|
-
const
|
|
1309
|
-
log.info(
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
// otherwise we can't know the folder, so we print the block to paste.
|
|
1319
|
-
const existingPath = raycastMcpConfigPath();
|
|
1320
|
-
let wrote = false;
|
|
1321
|
-
if (existingPath) {
|
|
1322
|
-
try {
|
|
1323
|
-
let mcpConfig = {};
|
|
1324
|
-
try {
|
|
1325
|
-
mcpConfig = JSON.parse(fs.readFileSync(existingPath, "utf-8"));
|
|
1326
|
-
}
|
|
1327
|
-
catch {
|
|
1328
|
-
try {
|
|
1329
|
-
copySecretBackup(existingPath, `${existingPath}.bak`);
|
|
1330
|
-
}
|
|
1331
|
-
catch { /* best-effort */ }
|
|
1332
|
-
}
|
|
1333
|
-
mcpConfig.mcpServers = mcpConfig.mcpServers ?? {};
|
|
1334
|
-
mcpConfig.mcpServers[raycastServerName] = synapServerEntry;
|
|
1335
|
-
if (raycastServerName !== "synap")
|
|
1336
|
-
delete mcpConfig.mcpServers["synap"];
|
|
1337
|
-
writeSecretFile(existingPath, JSON.stringify(mcpConfig, null, 2) + "\n", { mode: 0o600 });
|
|
1338
|
-
wrote = true;
|
|
1339
|
-
log.success(`Synap MCP server added to Raycast: ${existingPath}`);
|
|
1340
|
-
log.dim(`Reopen Raycast → 'Manage MCP Servers' → '${raycastServerName}' is connected with all tools.`);
|
|
1341
|
-
log.dim("First call spawns 'npx mcp-remote' (needs Node on PATH).");
|
|
1342
|
-
}
|
|
1343
|
-
catch (err) {
|
|
1344
|
-
log.warn(`Could not write Raycast MCP config: ${err.message}`);
|
|
1345
|
-
}
|
|
1346
|
-
}
|
|
1347
|
-
if (!wrote) {
|
|
1348
|
-
log.info("To add Synap to Raycast as an MCP server:");
|
|
1349
|
-
log.dim(" 1. Open Raycast → run 'Manage MCP Servers'");
|
|
1350
|
-
log.dim(" 2. Action 'Show Config File in Finder' → open mcp-config.json");
|
|
1351
|
-
log.dim(` 3. Paste the '${raycastServerName}' entry below into "mcpServers", then save:`);
|
|
1352
|
-
log.blank();
|
|
1353
|
-
log.dim(JSON.stringify({ mcpServers: { [raycastServerName]: synapServerEntry } }, null, 2));
|
|
1354
|
-
log.blank();
|
|
1355
|
-
log.dim(" (Re-running `synap connect --target=raycast --with-mcp` after step 2 writes it for you.)");
|
|
1356
|
-
}
|
|
1325
|
+
const serverName = mcpServerNameFromPodUrl(cfg.podUrl);
|
|
1326
|
+
log.info("Add Synap to Raycast AI (one time, Raycast Pro):");
|
|
1327
|
+
log.dim(" 1. In Raycast, run “Install MCP Server”.");
|
|
1328
|
+
log.dim(` 2. Name: ${serverName} Transport: HTTP URL: ${mcpUrl}`);
|
|
1329
|
+
log.dim(" 3. Authentication: OAuth → Dynamic → Sign In, then approve on your pod's consent page.");
|
|
1330
|
+
log.dim(" Fallback, if Sign In fails: no OAuth; add the HTTP header");
|
|
1331
|
+
log.dim(` Authorization: Bearer ${redactKey(effectiveApiKey)} (full value: ${files.jsonFile}, 0600)`);
|
|
1332
|
+
log.dim(" 4. Press “Install MCP Server” (⌘↵). In Raycast AI, mention the server and ask anything.");
|
|
1333
|
+
log.blank();
|
|
1334
|
+
log.dim("Raycast AI starts with the 9 essential Synap tools; it loads more when a task needs them.");
|
|
1357
1335
|
return true;
|
|
1358
1336
|
}
|
|
1359
1337
|
// ─── Open WebUI ──────────────────────────────────────────────────────────────
|
|
@@ -1396,7 +1374,7 @@ async function installOpenWebUI(cfg) {
|
|
|
1396
1374
|
*
|
|
1397
1375
|
* OAuth: ChatGPT also supports OAuth (no header field). The pod's /mcp is
|
|
1398
1376
|
* Bearer-only, so OAuth for a SINGLE pod isn't available — it lives on the
|
|
1399
|
-
* Control Plane (`https://
|
|
1377
|
+
* Control Plane (`https://mcp.synap.live`), which is an OAuth MCP server
|
|
1400
1378
|
* exposing `list_pods` / `connect_pod` / 14 proxied `pod__*` tools. That path
|
|
1401
1379
|
* is multi-pod and managed; `synap mcp connect-claude` prints it for
|
|
1402
1380
|
* claude.ai, and the same URL works for ChatGPT's OAuth connector.
|
|
@@ -1425,7 +1403,7 @@ async function installChatGpt(cfg) {
|
|
|
1425
1403
|
headers: { Authorization: `Bearer ${effectiveApiKey}` },
|
|
1426
1404
|
},
|
|
1427
1405
|
oauthAlternative: {
|
|
1428
|
-
url:
|
|
1406
|
+
url: MANAGED_MCP_URL,
|
|
1429
1407
|
note: "ChatGPT OAuth connector (no header field). Multi-pod: list_pods, " +
|
|
1430
1408
|
"connect_pod, then proxied pod__* tools. Same URL as claude.ai.",
|
|
1431
1409
|
},
|
|
@@ -1447,7 +1425,7 @@ async function installChatGpt(cfg) {
|
|
|
1447
1425
|
log.info("Path 2 — OAuth (multi-pod, no key to paste):");
|
|
1448
1426
|
log.blank();
|
|
1449
1427
|
log.info(" ChatGPT → Settings → Connectors → Add custom connector");
|
|
1450
|
-
log.info(
|
|
1428
|
+
log.info(` URL: ${MANAGED_MCP_URL}`);
|
|
1451
1429
|
log.info(" Auth: OAuth (leave API key blank)");
|
|
1452
1430
|
log.blank();
|
|
1453
1431
|
log.dim(" After OAuth, ask ChatGPT to 'list my Synap pods' (list_pods) and connect " +
|
|
@@ -1575,19 +1553,10 @@ async function installZed(cfg) {
|
|
|
1575
1553
|
if (!fs.existsSync(settingsDir))
|
|
1576
1554
|
fs.mkdirSync(settingsDir, { recursive: true });
|
|
1577
1555
|
const { effectiveApiKey, agentUserId, mcpUrl } = await prepareMcpSurface(cfg, "zed");
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
// Zed settings.json is JSONC — strip // line comments and /* block comments */
|
|
1583
|
-
// before parsing. Comments will not be preserved in the rewritten file.
|
|
1584
|
-
const stripped = raw
|
|
1585
|
-
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
1586
|
-
.replace(/\/\/[^\n]*/g, "");
|
|
1587
|
-
settings = JSON.parse(stripped);
|
|
1588
|
-
}
|
|
1589
|
-
catch { /* malformed — start from scratch */ }
|
|
1590
|
-
}
|
|
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);
|
|
1591
1560
|
const contextServers = (settings.context_servers ?? {});
|
|
1592
1561
|
// Pod-derived name — a second pod must not overwrite the first.
|
|
1593
1562
|
const zedServerName = mcpServerNameFromPodUrl(cfg.podUrl);
|
|
@@ -1597,17 +1566,17 @@ async function installZed(cfg) {
|
|
|
1597
1566
|
source: "custom",
|
|
1598
1567
|
command: {
|
|
1599
1568
|
path: "npx",
|
|
1600
|
-
args:
|
|
1569
|
+
args: mcpRemoteArgs(mcpUrl),
|
|
1601
1570
|
env: { SYNAP_AUTH: `Bearer ${effectiveApiKey}` },
|
|
1602
1571
|
},
|
|
1603
1572
|
settings: {},
|
|
1604
1573
|
};
|
|
1605
1574
|
settings.context_servers = contextServers;
|
|
1606
|
-
|
|
1575
|
+
writeClientConfig(settingsPath, settings);
|
|
1607
1576
|
const { setSurfaceAgentKey: save } = await import("./pod.js");
|
|
1608
1577
|
save("zed", { hubApiKey: effectiveApiKey, agentUserId, podUrl: cfg.podUrl });
|
|
1609
1578
|
log.success(`Zed settings updated: ${settingsPath}`);
|
|
1610
|
-
log.dim(
|
|
1579
|
+
log.dim(`Note: Zed settings are JSONC — comments are not kept in the rewrite. The previous file is at ${clientConfigBackupPath(settingsPath)}.`);
|
|
1611
1580
|
log.dim("Reload Zed to pick up the new context server (mcp-remote bridge).");
|
|
1612
1581
|
return true;
|
|
1613
1582
|
}
|
|
@@ -1626,13 +1595,8 @@ async function installVSCode(cfg) {
|
|
|
1626
1595
|
fs.mkdirSync(mcpDir, { recursive: true });
|
|
1627
1596
|
const { effectiveApiKey, agentUserId, mcpUrl } = await prepareMcpSurface(cfg, "vscode");
|
|
1628
1597
|
// VS Code 1.99+ global MCP format uses "servers" (not "mcpServers")
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
try {
|
|
1632
|
-
existing = JSON.parse(fs.readFileSync(mcpJsonPath, "utf-8"));
|
|
1633
|
-
}
|
|
1634
|
-
catch { /* ignore */ }
|
|
1635
|
-
}
|
|
1598
|
+
// VS Code's mcp.json is JSONC (comments, trailing commas). Unreadable → refused.
|
|
1599
|
+
const existing = readClientConfig(mcpJsonPath);
|
|
1636
1600
|
existing.servers = existing.servers ?? {};
|
|
1637
1601
|
// Pod-derived name — a second pod must not overwrite the first.
|
|
1638
1602
|
const vscodeServerName = mcpServerNameFromPodUrl(cfg.podUrl);
|
|
@@ -1643,7 +1607,7 @@ async function installVSCode(cfg) {
|
|
|
1643
1607
|
url: mcpUrl,
|
|
1644
1608
|
headers: { Authorization: `Bearer ${effectiveApiKey}` },
|
|
1645
1609
|
};
|
|
1646
|
-
|
|
1610
|
+
writeClientConfig(mcpJsonPath, existing);
|
|
1647
1611
|
const { setSurfaceAgentKey: save } = await import("./pod.js");
|
|
1648
1612
|
save("vscode", { hubApiKey: effectiveApiKey, agentUserId, podUrl: cfg.podUrl });
|
|
1649
1613
|
log.success(`VS Code MCP config updated: ${mcpJsonPath}`);
|
|
@@ -1758,7 +1722,7 @@ async function installGeneric(cfg) {
|
|
|
1758
1722
|
log.dim(`headers = { Authorization = "Bearer ${shown}" }`);
|
|
1759
1723
|
log.blank();
|
|
1760
1724
|
log.info("stdio bridge (mcp-remote) — key via env, not argv:");
|
|
1761
|
-
log.dim(` command: npx -y
|
|
1725
|
+
log.dim(` command: npx -y ${MCP_REMOTE_PACKAGE} ${mcpUrl} --header "Authorization:\${SYNAP_AUTH}"`);
|
|
1762
1726
|
log.dim(` env: SYNAP_AUTH="Bearer ${shown}"`);
|
|
1763
1727
|
log.dim(" Real key: ~/.synap/mcp-config.json (0600).");
|
|
1764
1728
|
log.blank();
|
|
@@ -1766,19 +1730,10 @@ async function installGeneric(cfg) {
|
|
|
1766
1730
|
ensureSecretDir(synapDir);
|
|
1767
1731
|
const configFile = path.join(synapDir, "mcp-config.json");
|
|
1768
1732
|
// Merge with existing so dual-pod generic installs don't clobber each other
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
try {
|
|
1772
|
-
existing = JSON.parse(fs.readFileSync(configFile, "utf-8"));
|
|
1773
|
-
}
|
|
1774
|
-
catch {
|
|
1775
|
-
/* replace */
|
|
1776
|
-
}
|
|
1777
|
-
}
|
|
1733
|
+
// Unreadable → refused: it may hold another pod's entry.
|
|
1734
|
+
const existing = readClientConfig(configFile);
|
|
1778
1735
|
const merged = { ...existing, ...httpConfig };
|
|
1779
|
-
|
|
1780
|
-
mode: 0o600,
|
|
1781
|
-
});
|
|
1736
|
+
writeClientConfig(configFile, merged);
|
|
1782
1737
|
const { setSurfaceAgentKey: save } = await import("./pod.js");
|
|
1783
1738
|
save("generic", {
|
|
1784
1739
|
hubApiKey: effectiveApiKey,
|
|
@@ -1881,8 +1836,15 @@ async function pickProvider(podUrl, apiKey) {
|
|
|
1881
1836
|
const spinner = ora("Fetching providers from pod vault...").start();
|
|
1882
1837
|
let providers;
|
|
1883
1838
|
try {
|
|
1884
|
-
|
|
1839
|
+
const creds = await fetchCredentials(podUrl, apiKey);
|
|
1885
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;
|
|
1886
1848
|
}
|
|
1887
1849
|
catch (err) {
|
|
1888
1850
|
spinner.fail(`Could not fetch providers: ${err.message}`);
|
|
@@ -1932,16 +1894,8 @@ export async function installOpencode(cfg) {
|
|
|
1932
1894
|
if (!fs.existsSync(configDir)) {
|
|
1933
1895
|
fs.mkdirSync(configDir, { recursive: true });
|
|
1934
1896
|
}
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
try {
|
|
1938
|
-
existing = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
1939
|
-
}
|
|
1940
|
-
catch {
|
|
1941
|
-
copySecretBackup(configPath, `${configPath}.bak`);
|
|
1942
|
-
log.warn("Existing opencode.json unreadable — backed up to opencode.json.bak");
|
|
1943
|
-
}
|
|
1944
|
-
}
|
|
1897
|
+
// opencode.json is JSONC. Unreadable → refused, never replaced.
|
|
1898
|
+
const existing = readClientConfig(configPath);
|
|
1945
1899
|
const providerBlock = (existing.provider ?? {});
|
|
1946
1900
|
providerBlock[provider.providerId] = {
|
|
1947
1901
|
npm: "@ai-sdk/openai-compatible",
|
|
@@ -1967,7 +1921,7 @@ export async function installOpencode(cfg) {
|
|
|
1967
1921
|
mcp: mcpBlock,
|
|
1968
1922
|
...(existing.model ? {} : { model: defaultModel }),
|
|
1969
1923
|
};
|
|
1970
|
-
|
|
1924
|
+
writeClientConfig(configPath, updated);
|
|
1971
1925
|
const { setSurfaceAgentKey: save } = await import("./pod.js");
|
|
1972
1926
|
save("opencode", { hubApiKey: agentKey, agentUserId, podUrl: cfg.podUrl });
|
|
1973
1927
|
log.success(`opencode configured: ~/.config/opencode/opencode.json`);
|
|
@@ -2016,38 +1970,25 @@ export function writeMcpServerEntry(configPath, serverName, server) {
|
|
|
2016
1970
|
if (!fs.existsSync(dir)) {
|
|
2017
1971
|
fs.mkdirSync(dir, { recursive: true });
|
|
2018
1972
|
}
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
2023
|
-
}
|
|
2024
|
-
catch {
|
|
2025
|
-
// corrupt or unreadable — back up and start fresh
|
|
2026
|
-
const backup = `${configPath}.bak-${Date.now()}`;
|
|
2027
|
-
copySecretBackup(configPath, backup);
|
|
2028
|
-
log.warn(`Existing config unreadable; backed up to ${path.basename(backup)}`);
|
|
2029
|
-
}
|
|
2030
|
-
}
|
|
1973
|
+
// Unreadable → refused (it holds the person's other MCP servers), never
|
|
1974
|
+
// "backed up and started fresh".
|
|
1975
|
+
const config = readClientConfig(configPath);
|
|
2031
1976
|
config.mcpServers = config.mcpServers ?? {};
|
|
2032
1977
|
config.mcpServers[serverName] = server;
|
|
2033
|
-
|
|
2034
|
-
mode: 0o600,
|
|
2035
|
-
});
|
|
1978
|
+
writeClientConfig(configPath, config);
|
|
2036
1979
|
}
|
|
2037
1980
|
/** Best-effort remove of a named MCP server entry (e.g. legacy `"synap"`). */
|
|
2038
1981
|
function removeMcpServerEntry(configPath, serverName) {
|
|
2039
1982
|
if (!fs.existsSync(configPath))
|
|
2040
1983
|
return;
|
|
2041
1984
|
try {
|
|
2042
|
-
const config =
|
|
1985
|
+
const config = readClientConfig(configPath);
|
|
2043
1986
|
if (!config.mcpServers?.[serverName])
|
|
2044
1987
|
return;
|
|
2045
1988
|
delete config.mcpServers[serverName];
|
|
2046
1989
|
if (Object.keys(config.mcpServers).length === 0)
|
|
2047
1990
|
delete config.mcpServers;
|
|
2048
|
-
|
|
2049
|
-
mode: 0o600,
|
|
2050
|
-
});
|
|
1991
|
+
writeClientConfig(configPath, config);
|
|
2051
1992
|
}
|
|
2052
1993
|
catch {
|
|
2053
1994
|
/* leave config alone if unreadable */
|