@synap-core/cli 1.11.0 → 1.13.0
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 +40 -198
- package/dist/build-info.json +6 -0
- package/dist/commands/agent-run.js +3 -2
- package/dist/commands/agent-run.js.map +1 -1
- package/dist/commands/agents.d.ts +32 -17
- package/dist/commands/agents.js +203 -237
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/app.d.ts +143 -0
- package/dist/commands/app.js +524 -0
- package/dist/commands/app.js.map +1 -0
- package/dist/commands/automate.d.ts +21 -0
- package/dist/commands/automate.js +42 -0
- package/dist/commands/automate.js.map +1 -0
- package/dist/commands/automation-schema.test.d.ts +16 -0
- package/dist/commands/automation-schema.test.js +77 -0
- package/dist/commands/automation-schema.test.js.map +1 -0
- package/dist/commands/automation.d.ts +12 -0
- package/dist/commands/automation.js +116 -66
- 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.d.ts +14 -1
- package/dist/commands/bridge-setup.js +395 -129
- package/dist/commands/bridge-setup.js.map +1 -1
- package/dist/commands/cap-connections.d.ts +4 -4
- package/dist/commands/cap-connections.js +14 -14
- package/dist/commands/cap-connections.js.map +1 -1
- package/dist/commands/capability.d.ts +3 -1
- package/dist/commands/capability.js +249 -176
- package/dist/commands/capability.js.map +1 -1
- package/dist/commands/catalog.d.ts +275 -0
- package/dist/commands/catalog.js +481 -0
- package/dist/commands/catalog.js.map +1 -0
- package/dist/commands/cell.d.ts +155 -8
- package/dist/commands/cell.js +416 -40
- package/dist/commands/cell.js.map +1 -1
- package/dist/commands/centrality.js +1 -13
- package/dist/commands/centrality.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 +36 -13
- package/dist/commands/connect.js +68 -167
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/connections.d.ts +3 -2
- package/dist/commands/connections.js +95 -25
- package/dist/commands/connections.js.map +1 -1
- package/dist/commands/context.js +79 -44
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/data-extra.d.ts +34 -0
- package/dist/commands/data-extra.js +203 -25
- package/dist/commands/data-extra.js.map +1 -1
- package/dist/commands/data.d.ts +120 -2
- package/dist/commands/data.js +295 -21
- package/dist/commands/data.js.map +1 -1
- package/dist/commands/diagnose.d.ts +66 -4
- package/dist/commands/diagnose.js +285 -28
- package/dist/commands/diagnose.js.map +1 -1
- package/dist/commands/doc.d.ts +11 -4
- package/dist/commands/doc.js +39 -14
- package/dist/commands/doc.js.map +1 -1
- package/dist/commands/doctor.d.ts +133 -3
- package/dist/commands/doctor.js +761 -41
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/explain.js +5 -5
- package/dist/commands/explain.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 +393 -13
- package/dist/commands/import.js +1150 -54
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/init.d.ts +34 -9
- package/dist/commands/init.js +481 -981
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/keys.d.ts +1 -0
- package/dist/commands/keys.js +15 -2
- package/dist/commands/keys.js.map +1 -1
- package/dist/commands/knowledge.d.ts +22 -6
- package/dist/commands/knowledge.js +121 -57
- package/dist/commands/knowledge.js.map +1 -1
- package/dist/commands/lens.d.ts +37 -1
- package/dist/commands/lens.js +194 -72
- package/dist/commands/lens.js.map +1 -1
- package/dist/commands/market-authoring.d.ts +29 -2
- package/dist/commands/market-authoring.js +348 -7
- package/dist/commands/market-authoring.js.map +1 -1
- package/dist/commands/market-official.d.ts +58 -0
- package/dist/commands/market-official.js +84 -0
- package/dist/commands/market-official.js.map +1 -0
- package/dist/commands/market.d.ts +400 -16
- package/dist/commands/market.js +1802 -146
- package/dist/commands/market.js.map +1 -1
- package/dist/commands/mcp.d.ts +60 -25
- package/dist/commands/mcp.js +251 -64
- 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/open.d.ts +39 -9
- package/dist/commands/open.js +80 -25
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/playbook.d.ts +42 -0
- package/dist/commands/playbook.js +171 -0
- package/dist/commands/playbook.js.map +1 -0
- package/dist/commands/pod.js +1 -1
- package/dist/commands/pod.js.map +1 -1
- package/dist/commands/pods.d.ts +11 -1
- package/dist/commands/pods.js +129 -131
- package/dist/commands/pods.js.map +1 -1
- package/dist/commands/proposals-actions.d.ts +62 -0
- package/dist/commands/proposals-actions.js +143 -8
- package/dist/commands/proposals-actions.js.map +1 -1
- package/dist/commands/proposals-await.d.ts +68 -0
- package/dist/commands/proposals-await.js +248 -0
- package/dist/commands/proposals-await.js.map +1 -0
- package/dist/commands/proton-setup.d.ts +73 -0
- package/dist/commands/proton-setup.js +579 -0
- package/dist/commands/proton-setup.js.map +1 -0
- package/dist/commands/providers.d.ts +23 -0
- package/dist/commands/providers.js +126 -1
- package/dist/commands/providers.js.map +1 -1
- package/dist/commands/raycast.js +6 -2
- package/dist/commands/raycast.js.map +1 -1
- package/dist/commands/rule.d.ts +150 -0
- package/dist/commands/rule.js +447 -0
- package/dist/commands/rule.js.map +1 -0
- 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 +116 -0
- package/dist/commands/session-wait.js +224 -0
- package/dist/commands/session-wait.js.map +1 -0
- package/dist/commands/sessions.d.ts +54 -0
- package/dist/commands/sessions.js +501 -58
- package/dist/commands/sessions.js.map +1 -1
- package/dist/commands/skill-manage.d.ts +21 -0
- package/dist/commands/skill-manage.js +136 -19
- package/dist/commands/skill-manage.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/skills.js +16 -0
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/status.d.ts +1 -4
- package/dist/commands/status.js +6 -35
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/statusline.backoff.test.d.ts +13 -0
- package/dist/commands/statusline.backoff.test.js +100 -0
- package/dist/commands/statusline.backoff.test.js.map +1 -0
- package/dist/commands/statusline.js +16 -9
- package/dist/commands/statusline.js.map +1 -1
- package/dist/commands/sync-status.d.ts +12 -0
- package/dist/commands/sync-status.js +125 -0
- package/dist/commands/sync-status.js.map +1 -0
- package/dist/commands/templates-retire.d.ts +34 -0
- package/dist/commands/templates-retire.js +192 -0
- package/dist/commands/templates-retire.js.map +1 -0
- package/dist/commands/tools.js +6 -21
- package/dist/commands/tools.js.map +1 -1
- package/dist/commands/tracks.d.ts +112 -0
- package/dist/commands/tracks.js +543 -0
- package/dist/commands/tracks.js.map +1 -0
- package/dist/commands/update.d.ts +3 -11
- package/dist/commands/update.js +5 -134
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/upload.d.ts +41 -3
- package/dist/commands/upload.js +120 -18
- package/dist/commands/upload.js.map +1 -1
- package/dist/commands/vendor.d.ts +28 -0
- package/dist/commands/vendor.js +110 -0
- package/dist/commands/vendor.js.map +1 -0
- package/dist/commands/versions.js +6 -0
- package/dist/commands/versions.js.map +1 -1
- package/dist/commands/view.js +10 -5
- package/dist/commands/view.js.map +1 -1
- package/dist/commands/whoami.d.ts +30 -3
- package/dist/commands/whoami.js +186 -23
- 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.d.ts +331 -0
- package/dist/commands/worker.js +1067 -0
- package/dist/commands/worker.js.map +1 -0
- package/dist/commands/worker.test.d.ts +1 -0
- package/dist/commands/worker.test.js +427 -0
- package/dist/commands/worker.test.js.map +1 -0
- package/dist/commands/workspace-ops.d.ts +35 -0
- package/dist/commands/workspace-ops.js +153 -0
- package/dist/commands/workspace-ops.js.map +1 -0
- package/dist/index.d.ts +6 -6
- package/dist/index.js +1043 -186
- package/dist/index.js.map +1 -1
- package/dist/lib/agent-connections.d.ts +31 -0
- package/dist/lib/agent-connections.js +33 -0
- package/dist/lib/agent-connections.js.map +1 -0
- package/dist/lib/agent-detect.d.ts +27 -0
- package/dist/lib/agent-detect.js +71 -0
- package/dist/lib/agent-detect.js.map +1 -0
- package/dist/lib/agent-door.d.ts +136 -0
- package/dist/lib/agent-door.js +429 -0
- package/dist/lib/agent-door.js.map +1 -0
- package/dist/lib/agents-config.d.ts +2 -0
- package/dist/lib/agents-config.js +4 -9
- package/dist/lib/agents-config.js.map +1 -1
- package/dist/lib/approval-poll.d.ts +31 -2
- package/dist/lib/approval-poll.js +20 -7
- package/dist/lib/approval-poll.js.map +1 -1
- package/dist/lib/atomic-file.d.ts +48 -0
- package/dist/lib/atomic-file.js +129 -0
- package/dist/lib/atomic-file.js.map +1 -0
- package/dist/lib/auth.d.ts +17 -11
- package/dist/lib/auth.js +47 -64
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/batch-approval.d.ts +85 -0
- package/dist/lib/batch-approval.js +170 -0
- package/dist/lib/batch-approval.js.map +1 -0
- 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 +246 -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/capture-lane.d.ts +6 -5
- package/dist/lib/capture-lane.js +6 -5
- package/dist/lib/capture-lane.js.map +1 -1
- package/dist/lib/capture-structure.d.ts +230 -11
- package/dist/lib/capture-structure.js +251 -13
- package/dist/lib/capture-structure.js.map +1 -1
- package/dist/lib/catalog-templates.d.ts +59 -0
- package/dist/lib/catalog-templates.js +126 -0
- package/dist/lib/catalog-templates.js.map +1 -0
- package/dist/lib/cli-build.d.ts +19 -0
- package/dist/lib/cli-build.js +37 -0
- package/dist/lib/cli-build.js.map +1 -0
- package/dist/lib/client-header.test.d.ts +1 -0
- package/dist/lib/client-header.test.js +85 -0
- package/dist/lib/client-header.test.js.map +1 -0
- package/dist/lib/conversation-headers.d.ts +33 -0
- package/dist/lib/conversation-headers.js +69 -0
- package/dist/lib/conversation-headers.js.map +1 -0
- package/dist/lib/cp-packages.d.ts +167 -11
- package/dist/lib/cp-packages.js +253 -21
- package/dist/lib/cp-packages.js.map +1 -1
- package/dist/lib/credential-class.d.ts +144 -0
- package/dist/lib/credential-class.js +138 -0
- package/dist/lib/credential-class.js.map +1 -0
- package/dist/lib/describe-lens.d.ts +35 -0
- package/dist/lib/describe-lens.js +40 -0
- package/dist/lib/describe-lens.js.map +1 -1
- package/dist/lib/device-connect.d.ts +125 -0
- package/dist/lib/device-connect.js +256 -0
- package/dist/lib/device-connect.js.map +1 -0
- package/dist/lib/directory-lens.d.ts +99 -0
- package/dist/lib/directory-lens.js +187 -0
- package/dist/lib/directory-lens.js.map +1 -0
- package/dist/lib/exporter-coverage.d.ts +71 -0
- package/dist/lib/exporter-coverage.js +97 -0
- package/dist/lib/exporter-coverage.js.map +1 -0
- package/dist/lib/goal-words.d.ts +38 -0
- package/dist/lib/goal-words.js +112 -0
- package/dist/lib/goal-words.js.map +1 -0
- package/dist/lib/hub-client.d.ts +57 -15
- package/dist/lib/hub-client.js +396 -82
- package/dist/lib/hub-client.js.map +1 -1
- package/dist/lib/init-connect.d.ts +41 -0
- package/dist/lib/init-connect.js +75 -0
- package/dist/lib/init-connect.js.map +1 -0
- package/dist/lib/init-exit.d.ts +24 -0
- package/dist/lib/init-exit.js +41 -0
- package/dist/lib/init-exit.js.map +1 -0
- package/dist/lib/init-flow.d.ts +25 -0
- package/dist/lib/init-flow.js +40 -0
- package/dist/lib/init-flow.js.map +1 -0
- package/dist/lib/init-handoff.d.ts +24 -0
- package/dist/lib/init-handoff.js +26 -0
- package/dist/lib/init-handoff.js.map +1 -0
- package/dist/lib/init-output.d.ts +18 -0
- package/dist/lib/init-output.js +43 -0
- package/dist/lib/init-output.js.map +1 -0
- package/dist/lib/init-pod.d.ts +45 -0
- package/dist/lib/init-pod.js +36 -0
- package/dist/lib/init-pod.js.map +1 -0
- package/dist/lib/installed.d.ts +210 -11
- package/dist/lib/installed.js +234 -36
- package/dist/lib/installed.js.map +1 -1
- package/dist/lib/key-approval.d.ts +57 -0
- package/dist/lib/key-approval.js +117 -0
- package/dist/lib/key-approval.js.map +1 -0
- package/dist/lib/key-diagnosis.d.ts +56 -0
- package/dist/lib/key-diagnosis.js +227 -0
- package/dist/lib/key-diagnosis.js.map +1 -0
- package/dist/lib/key-doctor.d.ts +136 -0
- package/dist/lib/key-doctor.js +286 -0
- package/dist/lib/key-doctor.js.map +1 -0
- package/dist/lib/key-locations.d.ts +120 -0
- package/dist/lib/key-locations.js +326 -0
- package/dist/lib/key-locations.js.map +1 -0
- package/dist/lib/key-source.d.ts +68 -0
- package/dist/lib/key-source.js +109 -0
- package/dist/lib/key-source.js.map +1 -0
- package/dist/lib/key-state.d.ts +70 -0
- package/dist/lib/key-state.js +115 -0
- package/dist/lib/key-state.js.map +1 -0
- package/dist/lib/kind-package.d.ts +181 -0
- package/dist/lib/kind-package.js +679 -0
- package/dist/lib/kind-package.js.map +1 -0
- package/dist/lib/knowledge-title.d.ts +25 -0
- package/dist/lib/knowledge-title.js +47 -0
- package/dist/lib/knowledge-title.js.map +1 -0
- package/dist/lib/knowledge-title.test.d.ts +1 -0
- package/dist/lib/knowledge-title.test.js +34 -0
- package/dist/lib/knowledge-title.test.js.map +1 -0
- package/dist/lib/machine-instance.d.ts +64 -0
- package/dist/lib/machine-instance.js +142 -0
- package/dist/lib/machine-instance.js.map +1 -0
- package/dist/lib/mcp-bridge.d.ts +49 -0
- package/dist/lib/mcp-bridge.js +180 -0
- package/dist/lib/mcp-bridge.js.map +1 -0
- package/dist/lib/mcp-bridge.test.d.ts +1 -0
- package/dist/lib/mcp-bridge.test.js +118 -0
- package/dist/lib/mcp-bridge.test.js.map +1 -0
- package/dist/lib/mcp-call.d.ts +30 -0
- package/dist/lib/mcp-call.js +107 -0
- package/dist/lib/mcp-call.js.map +1 -0
- package/dist/lib/mcp-snippets.d.ts +53 -0
- package/dist/lib/mcp-snippets.js +82 -0
- package/dist/lib/mcp-snippets.js.map +1 -0
- package/dist/lib/next-steps.d.ts +8 -0
- package/dist/lib/next-steps.js +31 -1
- package/dist/lib/next-steps.js.map +1 -1
- package/dist/lib/openclaw.js +3 -2
- package/dist/lib/openclaw.js.map +1 -1
- package/dist/lib/package-wire.d.ts +179 -0
- package/dist/lib/package-wire.js +68 -0
- package/dist/lib/package-wire.js.map +1 -0
- package/dist/lib/pair-link.d.ts +38 -0
- package/dist/lib/pair-link.js +68 -0
- package/dist/lib/pair-link.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-wide.d.ts +40 -0
- package/dist/lib/pod-wide.js +46 -0
- package/dist/lib/pod-wide.js.map +1 -0
- package/dist/lib/pod.d.ts +74 -112
- package/dist/lib/pod.js +146 -415
- package/dist/lib/pod.js.map +1 -1
- package/dist/lib/project-ref.d.ts +9 -3
- package/dist/lib/project-ref.js +10 -4
- package/dist/lib/project-ref.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 +78 -0
- package/dist/lib/register-claude-code-mcp.test.js.map +1 -0
- package/dist/lib/review-credential.d.ts +119 -0
- package/dist/lib/review-credential.js +149 -0
- package/dist/lib/review-credential.js.map +1 -0
- package/dist/lib/safe-open.d.ts +49 -0
- package/dist/lib/safe-open.js +119 -0
- package/dist/lib/safe-open.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/session-lens.d.ts +40 -1
- package/dist/lib/session-lens.js +54 -4
- package/dist/lib/session-lens.js.map +1 -1
- package/dist/lib/superwhisper-import.d.ts +8 -0
- package/dist/lib/superwhisper-import.js +14 -7
- package/dist/lib/superwhisper-import.js.map +1 -1
- package/dist/lib/superwhisper-title.test.d.ts +1 -0
- package/dist/lib/superwhisper-title.test.js +18 -0
- package/dist/lib/superwhisper-title.test.js.map +1 -0
- package/dist/lib/targets.d.ts +152 -10
- package/dist/lib/targets.js +974 -325
- package/dist/lib/targets.js.map +1 -1
- package/dist/lib/template-file.d.ts +4 -0
- package/dist/lib/template-file.js.map +1 -1
- package/dist/lib/template-lifecycle.d.ts +217 -0
- package/dist/lib/template-lifecycle.js +433 -0
- package/dist/lib/template-lifecycle.js.map +1 -0
- package/dist/lib/template-steps.d.ts +88 -0
- package/dist/lib/template-steps.js +118 -0
- package/dist/lib/template-steps.js.map +1 -0
- package/dist/lib/time-ago.d.ts +2 -0
- package/dist/lib/time-ago.js +15 -0
- package/dist/lib/time-ago.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/dist/utils/logger.js +5 -2
- package/dist/utils/logger.js.map +1 -1
- package/package.json +26 -17
- package/skills/README.md +13 -11
- package/skills/agent-os/SKILL.md +214 -99
- package/skills/manifest.json +1 -1
- package/skills/onboard/SKILL.md +55 -26
- package/skills/synap/README.md +5 -8
- package/skills/synap/SKILL.md +1149 -289
- package/skills/synap/_order.txt +7 -0
- package/skills/synap/_skill.yaml +1 -1
- package/skills/synap/automations.md +15 -0
- package/skills/synap/capture.md +5 -0
- package/skills/synap/cli-operations.md +24 -18
- package/skills/synap/code-skills.md +70 -0
- package/skills/synap/concepts.md +36 -0
- package/skills/synap/content-os.md +10 -1
- package/skills/synap/creative-loop.md +15 -10
- package/skills/synap/crm.md +21 -52
- package/skills/synap/document-embeds.md +143 -47
- package/skills/synap/escalation-ladder.md +20 -10
- package/skills/synap/focus-sessions.md +56 -21
- package/skills/synap/from-intent.md +158 -0
- package/skills/synap/governance-rules.md +5 -5
- package/skills/synap/governance.md +10 -7
- package/skills/synap/hand-off.md +33 -0
- package/skills/synap/inline-patterns.md +15 -7
- package/skills/synap/lenses.md +34 -16
- package/skills/synap/linking.md +42 -41
- package/skills/synap/mcp-remote-servers.md +64 -0
- package/skills/synap/mental-model.md +17 -15
- package/skills/synap/operating-mode-rules.md +2 -2
- package/skills/synap/processes.md +118 -0
- package/skills/synap/quick-reference.md +7 -2
- package/skills/synap/reading.md +3 -1
- package/skills/synap/reflexes.md +12 -42
- package/skills/synap/viewframe-cells.md +38 -5
- package/skills/synap/work-flow.md +1 -1
- package/skills/synap/work-model.md +110 -0
- package/skills/synap/worked-examples.md +4 -5
- package/skills/synap/workspace-design.md +15 -11
- package/skills/synap/writes.md +105 -25
- package/skills/synap/writing-titles.md +63 -0
- package/skills/synap-app/SKILL.md +149 -67
- package/skills/synap-app/_skill.yaml +1 -1
- 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-design/SKILL.md +474 -0
- package/skills/synap-design/_order.txt +4 -0
- package/skills/synap-design/_skill.yaml +21 -0
- package/skills/synap-design/loop.md +134 -0
- package/skills/synap-design/overview.md +44 -0
- package/skills/synap-design/principles.md +130 -0
- package/skills/synap-design/system.md +133 -0
- package/skills/synap-market/SKILL.md +306 -16
- package/skills/synap-market/_skill.yaml +1 -1
- package/skills/synap-market/author-and-publish.md +145 -8
- package/skills/synap-market/governance-and-catalog.md +7 -0
- package/skills/synap-market/install.md +79 -1
- package/skills/synap-market/overview.md +74 -6
- package/skills/synap-schema/SKILL.md +172 -10
- package/skills/synap-schema/_order.txt +2 -0
- package/skills/synap-schema/_skill.yaml +4 -2
- package/skills/synap-schema/common-mistakes.md +4 -2
- package/skills/synap-schema/discover-profiles.md +1 -1
- package/skills/synap-schema/entity-scope.md +15 -3
- package/skills/synap-schema/extend-first.md +29 -0
- package/skills/synap-schema/extend-vs-create.md +1 -1
- package/skills/synap-schema/intent-vocabulary-ssot.md +111 -0
- package/skills/synap-schema/property-types.md +1 -1
- package/skills/synap-schema/relations.md +1 -1
- package/skills/synap-ui/SKILL.md +19 -12
- package/skills/synap-ui/_skill.yaml +1 -1
- package/skills/synap-ui/ai-companion.md +1 -1
- package/skills/synap-ui/arranging-bento.md +2 -0
- package/skills/synap-ui/bento-layouts.md +10 -10
- package/skills/synap-ui/bento-recipes.md +313 -543
- package/skills/synap-ui/four-builds.md +5 -0
- package/skills/synap-ui/view-types.md +2 -2
- package/skills/synap-ui/widget-catalog.md +22 -278
- package/templates/automations/bookmark-enrichment.automation.json +290 -13
- package/templates/automations/url-bookmark-capture.automation.json +73 -11
- package/templates/box/runner.py +206 -0
- package/templates/skills/authoring-playbooks/SKILL.md +6 -3
- package/templates/skills/authoring-playbooks/reference/stages-contract.md +15 -0
- package/templates/skills/authoring-playbooks/reference/worked-example.md +3 -0
- package/dist/commands/infra.d.ts +0 -21
- package/dist/commands/infra.js +0 -262
- package/dist/commands/infra.js.map +0 -1
- package/node_modules/@synap/hub-rest-client/dist/index.cjs +0 -1261
- package/node_modules/@synap/hub-rest-client/dist/index.d.cts +0 -1647
- package/node_modules/@synap/hub-rest-client/dist/index.d.ts +0 -1647
- package/node_modules/@synap/hub-rest-client/dist/index.js +0 -1231
- package/node_modules/@synap/hub-rest-client/package.json +0 -45
- package/node_modules/@synap/hub-rest-client/src/client.ts +0 -1631
- package/node_modules/@synap/hub-rest-client/src/errors.ts +0 -30
- package/node_modules/@synap/hub-rest-client/src/index.ts +0 -153
- package/node_modules/@synap/hub-rest-client/src/setup.ts +0 -133
- package/node_modules/@synap/hub-rest-client/src/types.ts +0 -1299
|
@@ -18,27 +18,133 @@
|
|
|
18
18
|
* Hidden on purpose: this is a dogfood/dev convenience, not a first-class
|
|
19
19
|
* surface. Not shown in `synap --help`.
|
|
20
20
|
*
|
|
21
|
+
* The bridge lands data POD-WIDE by default. An OPTIONAL project scope can be
|
|
22
|
+
* picked interactively (or via --project-id); --workspace-id remains an
|
|
23
|
+
* advanced/legacy escape hatch to pin a single workspace, never prompted.
|
|
24
|
+
*
|
|
21
25
|
* Usage:
|
|
22
26
|
* synap bridge-setup --client-id <discord-app-id> [--bridge-dir <path>]
|
|
23
|
-
* [--pod <profile-name>] [--workspace-id <uuid>]
|
|
27
|
+
* [--pod <profile-name>] [--workspace-id <uuid>] [--project-id <uuid>]
|
|
24
28
|
* [--proactive-channel <discord-channel-id>] [--enable-react]
|
|
25
29
|
* [--pod-url <url>] [--api-key <key>]
|
|
26
30
|
*/
|
|
27
31
|
import path from "node:path";
|
|
28
32
|
import os from "node:os";
|
|
29
33
|
import fs from "node:fs";
|
|
34
|
+
import { writeSecretFile } from "../lib/atomic-file.js";
|
|
30
35
|
import { createRequire } from "node:module";
|
|
31
36
|
import { fileURLToPath } from "node:url";
|
|
32
37
|
import chalk from "chalk";
|
|
33
38
|
import ora from "ora";
|
|
34
39
|
import prompts from "prompts";
|
|
35
|
-
import { hubGet, hubPost, hubPatch, resolveUserId } from "../lib/hub-client.js";
|
|
40
|
+
import { hubGet, hubPost, hubPatch, resolveUserId, renderHubError } from "../lib/hub-client.js";
|
|
36
41
|
import { unwrapList } from "../lib/unwrapList.js";
|
|
37
|
-
import {
|
|
42
|
+
import { fetchProjects } from "../lib/project.js";
|
|
43
|
+
import { parseSkillDir, importSkill } from "../lib/skill-import.js";
|
|
44
|
+
import { CLI_TEMPLATES_DIR, resolveAgencySkillDirs, resolveAutomationTemplatesDir, resolveInstalledCatalogRoot, } from "../lib/catalog-templates.js";
|
|
38
45
|
import { checkPodHealth, getSurfaceAgentKey, setSurfaceAgentKey, listPodProfiles, getSurfacePodName, setSurfacePod, podNotFoundError, } from "../lib/pod.js";
|
|
39
46
|
import { enrollAgentIfNeeded, ensureAgentGovernance, provisionAgentKey, configureAgentContext, } from "../lib/targets.js";
|
|
40
47
|
import { log, banner } from "../utils/logger.js";
|
|
41
48
|
const DEFAULT_BRIDGE_DIR = path.join(os.homedir(), "Documents", "Code", "telegram-discord-bridge");
|
|
49
|
+
/** Parse a dotenv file into KEY→value. Values are used, never printed. */
|
|
50
|
+
function readEnvFile(filePath) {
|
|
51
|
+
if (!fs.existsSync(filePath))
|
|
52
|
+
return {};
|
|
53
|
+
const out = {};
|
|
54
|
+
for (const line of fs.readFileSync(filePath, "utf-8").split("\n")) {
|
|
55
|
+
const m = line.match(/^([A-Z0-9_]+)=(.*)$/);
|
|
56
|
+
if (m)
|
|
57
|
+
out[m[1]] = m[2].trim();
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
/** Local-only detection — no network, so it runs before the first prompt. */
|
|
62
|
+
function detectPriorState(bridgeDir) {
|
|
63
|
+
const envPath = path.join(bridgeDir, ".env");
|
|
64
|
+
const env = readEnvFile(envPath);
|
|
65
|
+
const podName = getSurfacePodName("discord");
|
|
66
|
+
const hasAgentKey = Boolean(getSurfaceAgentKey("discord")?.hubApiKey);
|
|
67
|
+
const ctx = path.join(os.homedir(), ".synap", "contexts", "discord.md");
|
|
68
|
+
const vaultRef = env.SYNAP_DISCORD_TOKEN_REF || undefined;
|
|
69
|
+
return {
|
|
70
|
+
isReRun: Boolean(podName || hasAgentKey || env.SYNAP_POD_URL || vaultRef),
|
|
71
|
+
bridgeProvisioned: Boolean(hasAgentKey || env.SYNAP_POD_URL || vaultRef),
|
|
72
|
+
envPath,
|
|
73
|
+
podName,
|
|
74
|
+
hasAgentKey,
|
|
75
|
+
contextPath: fs.existsSync(ctx) ? ctx : null,
|
|
76
|
+
vaultRef,
|
|
77
|
+
secretId: vaultRef?.startsWith("vault://")
|
|
78
|
+
? vaultRef.slice("vault://".length)
|
|
79
|
+
: undefined,
|
|
80
|
+
workspaceId: env.SYNAP_WORKSPACE_ID || undefined,
|
|
81
|
+
projectId: env.SYNAP_PROJECT_ID || undefined,
|
|
82
|
+
guildId: env.DISCORD_GUILD_ID || undefined,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Report what is already configured BEFORE any prompt, so the operator can see
|
|
87
|
+
* this is a re-run rather than a first run — and can see exactly what will be
|
|
88
|
+
* reused vs. re-asked. Silent on a genuine first run (unchanged experience).
|
|
89
|
+
*/
|
|
90
|
+
function printPriorState(prior, reconfigure) {
|
|
91
|
+
if (!prior.isReRun)
|
|
92
|
+
return;
|
|
93
|
+
log.blank();
|
|
94
|
+
log.heading(reconfigure
|
|
95
|
+
? "Existing setup detected — --reconfigure: re-asking everything"
|
|
96
|
+
: "Existing setup detected — reusing what is already configured");
|
|
97
|
+
const found = [];
|
|
98
|
+
const ask = [];
|
|
99
|
+
const item = (label, detail, ifMissing) => {
|
|
100
|
+
if (detail)
|
|
101
|
+
found.push(` ${chalk.green("✓")} ${label.padEnd(20)}${detail}`);
|
|
102
|
+
else
|
|
103
|
+
ask.push(`${label.toLowerCase()} (${ifMissing})`);
|
|
104
|
+
};
|
|
105
|
+
item("Pod profile", prior.podName && `${prior.podName} ${chalk.dim("(discord surface)")}`, "will ask");
|
|
106
|
+
item("Discord agent key", prior.hasAgentKey ? "stored locally — reused, not rotated" : null, "will be minted");
|
|
107
|
+
item("Agent template", prior.contextPath, "will ask");
|
|
108
|
+
item("Bot token", prior.vaultRef ? `in the pod vault ${chalk.dim(prior.vaultRef)} — not re-asked` : null, "will ask");
|
|
109
|
+
item("Workspace pin", prior.workspaceId ?? null, "pod-wide");
|
|
110
|
+
item("Project pin", prior.projectId ?? null, "pod-wide");
|
|
111
|
+
item("Discord server id", prior.guildId ?? null, "pass --guild-id");
|
|
112
|
+
if (reconfigure) {
|
|
113
|
+
// Nothing below is reused — say so rather than listing it as "found".
|
|
114
|
+
log.dim(" Every question is asked again and every step re-provisioned.");
|
|
115
|
+
}
|
|
116
|
+
else {
|
|
117
|
+
for (const line of found)
|
|
118
|
+
console.log(line);
|
|
119
|
+
log.dim(ask.length
|
|
120
|
+
? ` Still to configure: ${ask.join(", ")}`
|
|
121
|
+
: " Nothing left to ask — everything above is reused.");
|
|
122
|
+
}
|
|
123
|
+
log.dim(" Capabilities, skills and automations re-apply idempotently — each reports its own line below.");
|
|
124
|
+
if (!reconfigure) {
|
|
125
|
+
log.dim(` To re-ask everything instead: ${chalk.cyan("synap bridge-setup --reconfigure")}`);
|
|
126
|
+
}
|
|
127
|
+
log.blank();
|
|
128
|
+
}
|
|
129
|
+
/**
|
|
130
|
+
* Confirm the bot token a previous run vaulted is STILL in the vault, before
|
|
131
|
+
* deciding not to ask for it. Never returns or logs the secret itself.
|
|
132
|
+
*
|
|
133
|
+
* - "present" → skip the prompt, reuse the ref
|
|
134
|
+
* - "missing" → the .env ref is stale; ask for the token again (and say why)
|
|
135
|
+
* - "unknown" → the read failed / the door isn't deployed; keep the ref
|
|
136
|
+
* WITHOUT re-asking, and say it could not be verified
|
|
137
|
+
*/
|
|
138
|
+
async function checkVaultSecret(cfg, workspaceId, secretId) {
|
|
139
|
+
try {
|
|
140
|
+
const res = await hubGet("/vault/secrets", { workspaceId }, cfg);
|
|
141
|
+
const rows = unwrapList(res, ["secrets"]);
|
|
142
|
+
return rows.some((r) => r.id === secretId) ? "present" : "missing";
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
return "unknown";
|
|
146
|
+
}
|
|
147
|
+
}
|
|
42
148
|
// Bot invite permissions — the full set the bridge's features actually use.
|
|
43
149
|
// Bits per Discord's permission reference (https://discord.com/developers/docs/topics/permissions).
|
|
44
150
|
// BigInt because thread permissions exceed 2^31. Least-privilege *within* what we
|
|
@@ -73,10 +179,11 @@ const BOT_PERMISSIONS = Object.values(BOT_PERMISSION_BITS)
|
|
|
73
179
|
* Discord bridge points at a different (e.g. team / client) pod.
|
|
74
180
|
*
|
|
75
181
|
* Order: explicit --pod-url/--api-key escape hatch → --pod <name> →
|
|
76
|
-
* single saved profile (auto) →
|
|
77
|
-
*
|
|
182
|
+
* single saved profile (auto) → the pod THIS BRIDGE is already connected to
|
|
183
|
+
* (re-run, no prompt) → interactive picker (default = the pod already assigned
|
|
184
|
+
* to the discord surface, else the CLI-active one).
|
|
78
185
|
*/
|
|
79
|
-
async function resolveBridgePod(opts) {
|
|
186
|
+
async function resolveBridgePod(opts, prior, reconfigure) {
|
|
80
187
|
// 1. Raw URL+key escape hatch — bypasses profiles entirely.
|
|
81
188
|
if (opts.podUrl && opts.apiKey) {
|
|
82
189
|
return {
|
|
@@ -99,6 +206,14 @@ async function resolveBridgePod(opts) {
|
|
|
99
206
|
chosen = profiles[0];
|
|
100
207
|
log.dim(`Using your only saved pod profile: ${chosen.name}`);
|
|
101
208
|
}
|
|
209
|
+
else if (!reconfigure &&
|
|
210
|
+
prior.podName &&
|
|
211
|
+
profiles.some((p) => p.name === prior.podName)) {
|
|
212
|
+
// Re-run: the bridge is already bound to a pod (discord surface override).
|
|
213
|
+
// Re-asking would only invite a mis-click that repoints a live bridge.
|
|
214
|
+
chosen = profiles.find((p) => p.name === prior.podName);
|
|
215
|
+
log.dim(` [skip] pod — already connected to "${chosen.name}" (--pod or --reconfigure to change)`);
|
|
216
|
+
}
|
|
102
217
|
else {
|
|
103
218
|
const defaultName = getSurfacePodName("discord") ?? profiles.find((p) => p.active)?.name ?? profiles[0].name;
|
|
104
219
|
const initial = Math.max(0, profiles.findIndex((p) => p.name === defaultName));
|
|
@@ -124,7 +239,9 @@ async function resolveBridgePod(opts) {
|
|
|
124
239
|
cfg: {
|
|
125
240
|
podUrl: c.podUrl,
|
|
126
241
|
apiKey: c.hubApiKey,
|
|
127
|
-
|
|
242
|
+
// Acting user asked of the pod; the key's agent id rides apart (HubConfig.userId).
|
|
243
|
+
userId: "cli",
|
|
244
|
+
agentUserId: c.agentUserId || undefined,
|
|
128
245
|
workspaceId: c.workspaceId || undefined,
|
|
129
246
|
},
|
|
130
247
|
podName: chosen.name,
|
|
@@ -133,11 +250,17 @@ async function resolveBridgePod(opts) {
|
|
|
133
250
|
export async function bridgeSetup(opts) {
|
|
134
251
|
banner();
|
|
135
252
|
log.heading("Discord bridge — one-shot setup");
|
|
253
|
+
// ── 0. Detect what a previous run already configured, and SAY SO before we
|
|
254
|
+
// ask anything. Purely local — no network, no prompt. ────────────────
|
|
255
|
+
const bridgeDir = opts.bridgeDir ?? DEFAULT_BRIDGE_DIR;
|
|
256
|
+
const reconfigure = Boolean(opts.reconfigure) || process.env.SYNAP_BRIDGE_SETUP_RECONFIGURE === "1";
|
|
257
|
+
const prior = detectPriorState(bridgeDir);
|
|
258
|
+
printPriorState(prior, reconfigure);
|
|
136
259
|
// ── 1. Choose the pod the BRIDGE connects to (independent of the CLI pod) ──
|
|
137
260
|
let cfg;
|
|
138
261
|
let bridgePodName = null;
|
|
139
262
|
try {
|
|
140
|
-
const resolved = await resolveBridgePod(opts);
|
|
263
|
+
const resolved = await resolveBridgePod(opts, prior, reconfigure);
|
|
141
264
|
cfg = resolved.cfg;
|
|
142
265
|
bridgePodName = resolved.podName;
|
|
143
266
|
}
|
|
@@ -157,13 +280,11 @@ export async function bridgeSetup(opts) {
|
|
|
157
280
|
process.exit(1);
|
|
158
281
|
}
|
|
159
282
|
log.success(`Pod healthy: ${cfg.podUrl}${bridgePodName ? ` (profile: ${bridgePodName})` : ""}`);
|
|
160
|
-
// ── 3. Resolve workspace
|
|
161
|
-
const workspaceId = await
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
log.dim(`Workspace: ${workspaceId}`);
|
|
283
|
+
// ── 3. Resolve scope — pod-wide by default, workspace/project optional ───
|
|
284
|
+
const { workspaceId, projectId } = await resolveScope(opts, cfg, prior, reconfigure);
|
|
285
|
+
log.dim(workspaceId ? `Workspace: ${workspaceId} (explicit pin)` : "Workspace: pod-wide (no pin)");
|
|
286
|
+
if (projectId)
|
|
287
|
+
log.dim(`Project: ${projectId}`);
|
|
167
288
|
// ── 3b. Key + scope sanity check (GET /capabilities needs hub-protocol.read) ─
|
|
168
289
|
const probe = ora("Checking key scope…").start();
|
|
169
290
|
try {
|
|
@@ -172,7 +293,7 @@ export async function bridgeSetup(opts) {
|
|
|
172
293
|
}
|
|
173
294
|
catch (err) {
|
|
174
295
|
probe.fail("Key/scope check failed");
|
|
175
|
-
|
|
296
|
+
renderHubError(err);
|
|
176
297
|
log.dim("The key may be expired or missing scope. Run `synap keys rotate` or `synap init`.");
|
|
177
298
|
process.exit(1);
|
|
178
299
|
}
|
|
@@ -182,33 +303,65 @@ export async function bridgeSetup(opts) {
|
|
|
182
303
|
// reads to the operator's data floor (hub-protocol-rest.ts auth middleware)
|
|
183
304
|
// while attributing writes to the agent for governed proposals. All the
|
|
184
305
|
// operator-key work below (capability apply, vault grant) keeps using `cfg`.
|
|
185
|
-
const agentKey = await provisionDiscordAgentKey(cfg.podUrl, cfg.apiKey, cfg.workspaceId);
|
|
306
|
+
const agentKey = await provisionDiscordAgentKey(cfg.podUrl, cfg.apiKey, cfg.workspaceId, !reconfigure ? prior.contextPath : null);
|
|
186
307
|
// ── 4. Provision the capability (vault credential + tool + skill) ─────────
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
308
|
+
// The bot token is the sharpest re-run case: it is ALREADY in the pod vault,
|
|
309
|
+
// so re-prompting for it asks the operator to paste a live secret the system
|
|
310
|
+
// holds. Verify the vaulted secret instead, and only ask when it is genuinely
|
|
311
|
+
// gone (or when --reconfigure / --bot-token says to rotate it).
|
|
312
|
+
let botToken;
|
|
313
|
+
let reuseVaultRef;
|
|
314
|
+
if (opts.botToken) {
|
|
315
|
+
botToken = opts.botToken;
|
|
316
|
+
}
|
|
317
|
+
else if (!reconfigure && prior.secretId) {
|
|
318
|
+
const state = await checkVaultSecret(cfg, workspaceId, prior.secretId);
|
|
319
|
+
if (state === "present") {
|
|
320
|
+
reuseVaultRef = prior.vaultRef;
|
|
321
|
+
log.dim(` [skip] bot token — already in the pod vault (${prior.vaultRef}); not re-asked`);
|
|
322
|
+
}
|
|
323
|
+
else if (state === "unknown") {
|
|
324
|
+
reuseVaultRef = prior.vaultRef;
|
|
325
|
+
log.warn(` [warn] bot token — could not read the vault to confirm ${prior.vaultRef}; ` +
|
|
326
|
+
"keeping it rather than asking you to paste the token again (--reconfigure to re-enter).");
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
log.warn(` [warn] bot token — the vault no longer holds ${prior.vaultRef} (deleted or on another pod); asking again.`);
|
|
330
|
+
botToken = await resolveBotToken(undefined);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
botToken = await resolveBotToken(undefined);
|
|
335
|
+
}
|
|
336
|
+
const { vaultRef, secretId, error: capabilityError } = await applyBridgeCapability(bridgeDir, workspaceId, cfg, botToken, reuseVaultRef);
|
|
190
337
|
// ── 4. Choose agent governance mode ──────────────────────────────────
|
|
191
338
|
const discordAgentUserId = getSurfaceAgentKey("discord")?.agentUserId;
|
|
192
339
|
if (discordAgentUserId) {
|
|
193
|
-
|
|
340
|
+
// A re-run leaves the agent's governance exactly as the operator set it.
|
|
341
|
+
// (The pod exposes no GET for it, so we say "unchanged" — never claim a
|
|
342
|
+
// specific mode we did not read.)
|
|
343
|
+
if (!opts.governance && !reconfigure && prior.bridgeProvisioned) {
|
|
344
|
+
log.dim(" [skip] governance — left unchanged from your previous run " +
|
|
345
|
+
"(--governance safe|normal|crazy, or --reconfigure, to change it)");
|
|
346
|
+
}
|
|
347
|
+
else {
|
|
348
|
+
await ensureAgentGovernance(cfg, discordAgentUserId, opts.governance);
|
|
349
|
+
}
|
|
194
350
|
}
|
|
195
351
|
// ── 4b. Seed the bundled instruction skills FIRST, then the capabilities ────
|
|
196
|
-
// Order matters: the
|
|
197
|
-
//
|
|
198
|
-
//
|
|
199
|
-
//
|
|
200
|
-
// REUSES that same pod-scoped skill row by name (create-from-definition is
|
|
201
|
-
// idempotent) instead of creating a docless instruction-only copy.
|
|
352
|
+
// Order matters: seeding the bundled SKILL.md dirs first attaches their full
|
|
353
|
+
// reference-doc corpus; a capability apply below that embeds a skill of the
|
|
354
|
+
// same name REUSES that pod-scoped row (create-from-definition is idempotent)
|
|
355
|
+
// instead of creating a docless instruction-only copy.
|
|
202
356
|
await applyAgencySkills(cfg);
|
|
203
357
|
// ── 4c. Apply the agency capability templates (idempotent, self-healing) ────
|
|
204
358
|
await applyAgencyCapabilities(cfg, workspaceId);
|
|
205
359
|
// ── 4e. Seed the GENERIC automation templates (idempotent, event-driven) ────
|
|
206
|
-
// Only the domain-agnostic automations
|
|
207
|
-
//
|
|
208
|
-
//
|
|
209
|
-
//
|
|
210
|
-
//
|
|
211
|
-
// packages/apply with no bridge-setup — stands them up. Thread the operator's
|
|
360
|
+
// Only the domain-agnostic automations are seeded here (mail-feed,
|
|
361
|
+
// url/bookmark capture), read from the installed catalog package when it
|
|
362
|
+
// ships them, else from templates/automations/. The grant operation itself
|
|
363
|
+
// ships with the `grants` workspace template (its "Grant Process" method +
|
|
364
|
+
// automations). Thread the operator's
|
|
212
365
|
// chosen proactive channel through — output nodes pin their post target with it
|
|
213
366
|
// when it's a Synap channels.id (see the note in applyAutomationTemplates).
|
|
214
367
|
await applyAutomationTemplates(cfg, workspaceId, opts.proactiveChannel);
|
|
@@ -219,16 +372,22 @@ export async function bridgeSetup(opts) {
|
|
|
219
372
|
await setDiscordToolConfig(cfg, workspaceId, { reactCapture: true });
|
|
220
373
|
}
|
|
221
374
|
// ── 4d. Grant the bridge redeem access — token lives ONLY in the vault ─────
|
|
375
|
+
// Re-run path: `secretId` is recovered from the .env vault ref, so the grant
|
|
376
|
+
// is re-verified (it answers "(existing)") WITHOUT the token being re-entered.
|
|
222
377
|
let granted = false;
|
|
223
|
-
if (secretId
|
|
378
|
+
if (secretId) {
|
|
224
379
|
// Grant to the AGENT (the bridge's redeem identity), not the operator.
|
|
225
380
|
const agentUserId = getSurfaceAgentKey("discord")?.agentUserId;
|
|
226
381
|
granted = await grantRedeemAccess(secretId, cfg, agentUserId);
|
|
227
382
|
}
|
|
228
383
|
// ── 5. Proactive delivery routing (optional) ─────────────────────────────
|
|
229
|
-
|
|
384
|
+
// Delivery preferences live ON a workspace — pod-wide has none to route to.
|
|
385
|
+
if (opts.proactiveChannel && workspaceId) {
|
|
230
386
|
await configureProactive(workspaceId, opts.proactiveChannel, cfg);
|
|
231
387
|
}
|
|
388
|
+
else if (opts.proactiveChannel) {
|
|
389
|
+
log.warn("Proactive routing needs a workspace — pass --workspace-id to enable it.");
|
|
390
|
+
}
|
|
232
391
|
// ── 6. Write the bridge .env ─────────────────────────────────────────────
|
|
233
392
|
const envPath = path.join(bridgeDir, ".env");
|
|
234
393
|
const managed = {
|
|
@@ -237,12 +396,14 @@ export async function bridgeSetup(opts) {
|
|
|
237
396
|
// the operator's raw key. Reads remap to the operator floor; writes are
|
|
238
397
|
// governed as the agent.
|
|
239
398
|
SYNAP_HUB_API_KEY: agentKey,
|
|
240
|
-
SYNAP_WORKSPACE_ID: workspaceId,
|
|
241
399
|
};
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
400
|
+
// Pod-wide by default: SYNAP_WORKSPACE_ID/SYNAP_PROJECT_ID are only written
|
|
401
|
+
// when the operator explicitly pinned a workspace (--workspace-id) or chose
|
|
402
|
+
// a project in the picker. Neither is required.
|
|
403
|
+
if (workspaceId)
|
|
404
|
+
managed.SYNAP_WORKSPACE_ID = workspaceId;
|
|
405
|
+
if (projectId)
|
|
406
|
+
managed.SYNAP_PROJECT_ID = projectId;
|
|
246
407
|
// The token is NEVER written to .env — it lives in the pod vault and the
|
|
247
408
|
// bridge redeems it on boot via this ref. (Requires the grant below to land;
|
|
248
409
|
// if it didn't, we warn loudly rather than leaking the token to env.)
|
|
@@ -253,8 +414,15 @@ export async function bridgeSetup(opts) {
|
|
|
253
414
|
if (opts.proactiveChannel)
|
|
254
415
|
managed.SYNAP_PROACTIVE_CHANNEL_ID = opts.proactiveChannel;
|
|
255
416
|
try {
|
|
256
|
-
// Once the token is in the vault, purge any stale env copy of it.
|
|
257
|
-
|
|
417
|
+
// Once the token is in the vault, purge any stale env copy of it. Also
|
|
418
|
+
// purge a stale scope pin from a prior run — re-running pod-wide (no
|
|
419
|
+
// --workspace-id / no project picked) must actually clear an old pin.
|
|
420
|
+
const removeKeys = [
|
|
421
|
+
...(vaultRef ? ["DISCORD_BOT_TOKEN"] : []),
|
|
422
|
+
...(workspaceId ? [] : ["SYNAP_WORKSPACE_ID"]),
|
|
423
|
+
...(projectId ? [] : ["SYNAP_PROJECT_ID"]),
|
|
424
|
+
];
|
|
425
|
+
upsertEnv(envPath, managed, removeKeys);
|
|
258
426
|
log.success(`Wrote pod credentials → ${envPath}`);
|
|
259
427
|
}
|
|
260
428
|
catch (err) {
|
|
@@ -270,10 +438,12 @@ export async function bridgeSetup(opts) {
|
|
|
270
438
|
const envHasGuild = fs.existsSync(envPath) &&
|
|
271
439
|
/^DISCORD_GUILD_ID=.+/m.test(fs.readFileSync(envPath, "utf-8"));
|
|
272
440
|
printNextSteps(opts.clientId, bridgeDir, {
|
|
273
|
-
hasToken: Boolean(botToken),
|
|
441
|
+
hasToken: Boolean(botToken) || Boolean(reuseVaultRef),
|
|
274
442
|
hasGuild: Boolean(opts.guildId) || envHasGuild,
|
|
275
443
|
vaultRef,
|
|
276
444
|
granted,
|
|
445
|
+
reusedToken: Boolean(reuseVaultRef),
|
|
446
|
+
capabilityError,
|
|
277
447
|
});
|
|
278
448
|
await printSynapNextSteps(cfg, workspaceId);
|
|
279
449
|
}
|
|
@@ -295,11 +465,6 @@ export async function bridgeSetup(opts) {
|
|
|
295
465
|
const AGENCY_CAPABILITY_PLAN = [
|
|
296
466
|
{ key: "nango-google", needs: null, params: {}, workspaceScoped: false },
|
|
297
467
|
{ key: "agency-skills", needs: null, params: {}, workspaceScoped: true },
|
|
298
|
-
// stellar-grant-client bundles the grant PLAYBOOK, all four grant AUTOMATIONS
|
|
299
|
-
// (including stellar-grant-provision + proactive-grant-review, moved here from
|
|
300
|
-
// templates/automations/), and the stellar-scf-grants-advisor SKILL — so one
|
|
301
|
-
// capability apply stands up the whole grant operation on ANY install door.
|
|
302
|
-
{ key: "stellar-grant-client", needs: null, params: {}, workspaceScoped: true },
|
|
303
468
|
// Cal.com scheduling connector — requires a `calApiKey` param (a `cal_live_...`
|
|
304
469
|
// token), so gate it like the other keyed connectors: skip gracefully unless
|
|
305
470
|
// CALCOM_API_KEY is set. Seeds the `cal_com` tool + booking skills + vault key,
|
|
@@ -326,25 +491,6 @@ const AGENCY_CAPABILITY_PLAN = [
|
|
|
326
491
|
workspaceScoped: false,
|
|
327
492
|
},
|
|
328
493
|
];
|
|
329
|
-
/**
|
|
330
|
-
* Resolve the automations templates directory — CLI-BUNDLED (like the skills at
|
|
331
|
-
* templates/skills). The backend stays agnostic: it ships no feature templates;
|
|
332
|
-
* automation config is bundled with the CLI that seeds it.
|
|
333
|
-
*
|
|
334
|
-
* Resolution order:
|
|
335
|
-
* 1. AUTOMATION_TEMPLATES_DIR env var (override for CI / custom installs)
|
|
336
|
-
* 2. CLI-bundled default: <cli-package-root>/templates/automations
|
|
337
|
-
*/
|
|
338
|
-
function resolveAutomationsTemplatesDir() {
|
|
339
|
-
if (process.env.AUTOMATION_TEMPLATES_DIR) {
|
|
340
|
-
const override = process.env.AUTOMATION_TEMPLATES_DIR;
|
|
341
|
-
return fs.existsSync(override) ? override : null;
|
|
342
|
-
}
|
|
343
|
-
const thisFile = fileURLToPath(import.meta.url);
|
|
344
|
-
const cliRoot = path.resolve(path.dirname(thisFile), "..", "..");
|
|
345
|
-
const candidate = path.resolve(cliRoot, "templates", "automations");
|
|
346
|
-
return fs.existsSync(candidate) ? candidate : null;
|
|
347
|
-
}
|
|
348
494
|
/**
|
|
349
495
|
* Apply the agency capability templates idempotently via the Control Plane catalog.
|
|
350
496
|
*
|
|
@@ -367,6 +513,12 @@ async function applyAgencyCapabilities(cfg, workspaceId) {
|
|
|
367
513
|
log.dim(` [skip] ${item.key} — set ${item.needs} to seed it`);
|
|
368
514
|
continue;
|
|
369
515
|
}
|
|
516
|
+
// Playbook-bundling capabilities require a workspace to land in — pod-wide
|
|
517
|
+
// (no --workspace-id) has none, so skip rather than round-trip a known 4xx.
|
|
518
|
+
if (item.workspaceScoped && !workspaceId) {
|
|
519
|
+
log.dim(` [skip] ${item.key} — bundles playbooks; pass --workspace-id to seed it`);
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
370
522
|
try {
|
|
371
523
|
const res = (await hubPost("/capabilities/apply", item.workspaceScoped
|
|
372
524
|
? { templateKey: item.key, params: item.params, workspaceId }
|
|
@@ -396,19 +548,22 @@ export function resolveBundledSkillsDir() {
|
|
|
396
548
|
return fs.existsSync(candidate) ? candidate : null;
|
|
397
549
|
}
|
|
398
550
|
/**
|
|
399
|
-
* Seed the
|
|
400
|
-
*
|
|
401
|
-
*
|
|
551
|
+
* Seed the Synap-native skills (standard SKILL.md directories): the agency
|
|
552
|
+
* skills from the installed catalog package when it ships them, else from
|
|
553
|
+
* templates/skills/; the CLI runtime skills from templates/skills/. Each becomes
|
|
554
|
+
* a pod-wide instruction skill via /agent-skills/import; references/ files
|
|
555
|
+
* become linked documents. Idempotent (slug-unique guard).
|
|
402
556
|
*/
|
|
403
557
|
async function applyAgencySkills(cfg) {
|
|
404
|
-
const
|
|
405
|
-
|
|
558
|
+
const catalogRoot = resolveInstalledCatalogRoot();
|
|
559
|
+
const { dirs, fromCatalog } = resolveAgencySkillDirs({ cliSkillsDir: resolveBundledSkillsDir(), catalogRoot });
|
|
560
|
+
if (dirs.length === 0) {
|
|
406
561
|
log.warn("Bundled skill templates not found — skipping.");
|
|
407
562
|
return;
|
|
408
563
|
}
|
|
409
564
|
const userId = await resolveUserId(cfg);
|
|
410
|
-
|
|
411
|
-
|
|
565
|
+
log.dim(`Seeding ${dirs.length} skill(s)` +
|
|
566
|
+
(fromCatalog.length ? ` (${fromCatalog.length} from ${catalogRoot})` : "") + "…");
|
|
412
567
|
for (const dir of dirs) {
|
|
413
568
|
const parsed = parseSkillDir(dir);
|
|
414
569
|
if (!parsed)
|
|
@@ -559,8 +714,8 @@ async function resolveGoogleConnectionId(cfg, workspaceId) {
|
|
|
559
714
|
const google = containers.find((c) => /google/i.test(c.name ?? ""));
|
|
560
715
|
if (!google?.id)
|
|
561
716
|
return null;
|
|
562
|
-
const connRes = await hubGet(`/capabilities/${google.id}/
|
|
563
|
-
const conns = unwrapList(connRes, ["
|
|
717
|
+
const connRes = await hubGet(`/capabilities/${google.id}/credentials`, {}, cfg);
|
|
718
|
+
const conns = unwrapList(connRes, ["credentials"]);
|
|
564
719
|
if (conns.length === 0)
|
|
565
720
|
return null;
|
|
566
721
|
return (conns.find((c) => c.isDefault) ?? conns[0]).id;
|
|
@@ -570,7 +725,9 @@ async function resolveGoogleConnectionId(cfg, workspaceId) {
|
|
|
570
725
|
}
|
|
571
726
|
}
|
|
572
727
|
/**
|
|
573
|
-
* Apply automation templates
|
|
728
|
+
* Apply the *.automation.json templates: AUTOMATION_TEMPLATES_DIR when set, else
|
|
729
|
+
* the installed catalog package's automations/ when it ships them, else the
|
|
730
|
+
* CLI's templates/automations/.
|
|
574
731
|
*
|
|
575
732
|
* For each file:
|
|
576
733
|
* - Fill placeholders + validate references in the flowDefinition:
|
|
@@ -588,7 +745,11 @@ async function resolveGoogleConnectionId(cfg, workspaceId) {
|
|
|
588
745
|
* - Aborts (never partial) if the prerequisite skills/automations fetch fails.
|
|
589
746
|
*/
|
|
590
747
|
async function applyAutomationTemplates(cfg, workspaceId, feedChannelId) {
|
|
591
|
-
const templatesDir =
|
|
748
|
+
const templatesDir = resolveAutomationTemplatesDir({
|
|
749
|
+
override: process.env.AUTOMATION_TEMPLATES_DIR,
|
|
750
|
+
catalogRoot: resolveInstalledCatalogRoot(),
|
|
751
|
+
cliDir: path.join(CLI_TEMPLATES_DIR, "automations"),
|
|
752
|
+
});
|
|
592
753
|
if (!templatesDir) {
|
|
593
754
|
log.warn("Automation templates dir not found — skipping. " +
|
|
594
755
|
"Set AUTOMATION_TEMPLATES_DIR=<path> to seed from a custom location.");
|
|
@@ -613,6 +774,32 @@ async function applyAutomationTemplates(cfg, workspaceId, feedChannelId) {
|
|
|
613
774
|
log.warn(`Could not fetch existing automations — aborting seeding: ${err.message}`);
|
|
614
775
|
return;
|
|
615
776
|
}
|
|
777
|
+
// Fetch the capability catalog's verb ids once — needed for capability-node
|
|
778
|
+
// validation. This is a DIFFERENT namespace from `/skills`: a catalog verb's
|
|
779
|
+
// backing skill need not appear in the `/skills` list at all. Dogfood: the
|
|
780
|
+
// `ai.generate` verb on "Synap Core" is `enabled: true, runnable: true` and is
|
|
781
|
+
// what every AI automation node uses, yet its backing skill
|
|
782
|
+
// (e441519b-…) is absent from all 50 rows `/skills` returns. Validating verbs
|
|
783
|
+
// against skill NAMES therefore marked every AI automation as missing and
|
|
784
|
+
// silently skipped it.
|
|
785
|
+
let catalogVerbIds = new Set();
|
|
786
|
+
try {
|
|
787
|
+
const res = await hubGet("/capabilities/catalog", { userId, workspaceId }, cfg);
|
|
788
|
+
for (const cap of unwrapList(res, [
|
|
789
|
+
"capabilities",
|
|
790
|
+
])) {
|
|
791
|
+
for (const v of cap.verbs ?? [])
|
|
792
|
+
if (v.verbId)
|
|
793
|
+
catalogVerbIds.add(v.verbId);
|
|
794
|
+
}
|
|
795
|
+
}
|
|
796
|
+
catch (err) {
|
|
797
|
+
// NON-fatal, unlike the skills fetch: an empty verb set must not skip every
|
|
798
|
+
// AI automation. Fall back to accepting any verbId and let the runtime be the
|
|
799
|
+
// judge — a seeded automation that fails loudly beats one silently not seeded.
|
|
800
|
+
log.warn(`Could not fetch the capability catalog — verb ids will not be pre-validated: ${err.message}`);
|
|
801
|
+
catalogVerbIds = new Set();
|
|
802
|
+
}
|
|
616
803
|
// Fetch existing skills once — needed for skill-node injection.
|
|
617
804
|
let existingSkills = [];
|
|
618
805
|
try {
|
|
@@ -660,11 +847,16 @@ async function applyAutomationTemplates(cfg, workspaceId, feedChannelId) {
|
|
|
660
847
|
missing ??= `skill "${skillName}"`;
|
|
661
848
|
}
|
|
662
849
|
}
|
|
663
|
-
// `capability` node → validate verbId
|
|
664
|
-
//
|
|
850
|
+
// `capability` node → validate verbId against the CAPABILITY CATALOG first
|
|
851
|
+
// (its real home), falling back to seeded skill names for verbs that are
|
|
852
|
+
// skill-backed by name. When the catalog could not be fetched the set is
|
|
853
|
+
// empty and we do not pre-validate at all — see the fetch above.
|
|
665
854
|
if (node.type === "capability") {
|
|
666
855
|
const data = node.data;
|
|
667
|
-
if (data.verbId &&
|
|
856
|
+
if (data.verbId &&
|
|
857
|
+
catalogVerbIds.size > 0 &&
|
|
858
|
+
!catalogVerbIds.has(data.verbId) &&
|
|
859
|
+
!existingSkills.some((s) => s.name === data.verbId)) {
|
|
668
860
|
missing ??= `verb "${data.verbId}"`;
|
|
669
861
|
}
|
|
670
862
|
const sel = data.connectionSelector;
|
|
@@ -800,9 +992,12 @@ async function applyAutomationTemplates(cfg, workspaceId, feedChannelId) {
|
|
|
800
992
|
* @param podUrl Pod base URL.
|
|
801
993
|
* @param operatorKey The operator's raw hub key (authorizes the provisioning).
|
|
802
994
|
* @param workspaceId Workspace to enroll the agent into (omit for all).
|
|
995
|
+
* @param existingContextPath Path to the CONTEXT.md a previous run already
|
|
996
|
+
* wrote — when set, the agent-template wizard is SKIPPED (same reuse-and-say-so
|
|
997
|
+
* contract as the agent key itself). Pass null to run it.
|
|
803
998
|
* @returns the Discord agent key to write as SYNAP_HUB_API_KEY.
|
|
804
999
|
*/
|
|
805
|
-
async function provisionDiscordAgentKey(podUrl, operatorKey, workspaceId) {
|
|
1000
|
+
async function provisionDiscordAgentKey(podUrl, operatorKey, workspaceId, existingContextPath) {
|
|
806
1001
|
const spinner = ora("Provisioning Discord agent key…").start();
|
|
807
1002
|
let hubApiKey;
|
|
808
1003
|
let agentUserId;
|
|
@@ -833,44 +1028,67 @@ async function provisionDiscordAgentKey(podUrl, operatorKey, workspaceId) {
|
|
|
833
1028
|
// Enroll the agent user into the operator's workspace(s) using the OPERATOR
|
|
834
1029
|
// key (before the bridge ever uses the agent key).
|
|
835
1030
|
await enrollAgentIfNeeded(podUrl, operatorKey, agentUserId, workspaceId);
|
|
836
|
-
// Same CONTEXT.md routing file every other provisioned agent gets
|
|
837
|
-
|
|
838
|
-
|
|
1031
|
+
// Same CONTEXT.md routing file every other provisioned agent gets — written
|
|
1032
|
+
// once. A re-run keeps the template the operator already picked.
|
|
1033
|
+
if (existingContextPath) {
|
|
1034
|
+
log.dim(` [skip] agent template — already configured (${existingContextPath}); --reconfigure to pick another`);
|
|
839
1035
|
}
|
|
840
|
-
|
|
841
|
-
|
|
1036
|
+
else {
|
|
1037
|
+
try {
|
|
1038
|
+
await configureAgentContext(podUrl, operatorKey, "discord", agentUserId);
|
|
1039
|
+
}
|
|
1040
|
+
catch (err) {
|
|
1041
|
+
log.warn(`Agent context wizard failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1042
|
+
}
|
|
842
1043
|
}
|
|
843
1044
|
return hubApiKey;
|
|
844
1045
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
1046
|
+
/**
|
|
1047
|
+
* Resolve the OPTIONAL scope the bridge writes into — pod-wide by default.
|
|
1048
|
+
*
|
|
1049
|
+
* Workspace: an explicit --workspace-id is the only way to pin one (advanced/
|
|
1050
|
+
* legacy escape hatch) — never prompted, never required.
|
|
1051
|
+
*
|
|
1052
|
+
* Project: a peer, independent lens. --project-id skips the prompt; otherwise
|
|
1053
|
+
* offer a picker with "Pod-wide (no project)" as the default alongside the
|
|
1054
|
+
* operator's existing projects. No projects on the pod → skip the prompt.
|
|
1055
|
+
*
|
|
1056
|
+
* Re-run: BOTH lenses are inherited from the bridge's own .env instead of being
|
|
1057
|
+
* re-asked. That also fixes a real re-run defect — re-running without
|
|
1058
|
+
* --workspace-id used to silently UNPIN a bridge that had been pinned (the .env
|
|
1059
|
+
* key is purged), which in turn silently skipped every playbook-bundling
|
|
1060
|
+
* capability. --reconfigure restores the ask-me-everything behaviour.
|
|
1061
|
+
*/
|
|
1062
|
+
async function resolveScope(opts, cfg, prior, reconfigure) {
|
|
1063
|
+
let workspaceId = opts.workspaceId;
|
|
1064
|
+
if (!workspaceId && !reconfigure && prior.workspaceId) {
|
|
1065
|
+
workspaceId = prior.workspaceId;
|
|
1066
|
+
log.dim(` [skip] workspace — keeping the pin from your previous run (${workspaceId}); --reconfigure to clear it`);
|
|
1067
|
+
}
|
|
1068
|
+
let projectId = opts.projectId;
|
|
1069
|
+
if (!projectId && !reconfigure && prior.bridgeProvisioned) {
|
|
1070
|
+
projectId = prior.projectId;
|
|
1071
|
+
log.dim(prior.projectId
|
|
1072
|
+
? ` [skip] project — keeping ${prior.projectId} from your previous run (--project-id or --reconfigure to change)`
|
|
1073
|
+
: " [skip] project — you chose pod-wide previously; keeping it (--project-id or --reconfigure to change)");
|
|
1074
|
+
}
|
|
1075
|
+
else if (!projectId) {
|
|
1076
|
+
const projects = await fetchProjects(cfg);
|
|
1077
|
+
if (projects.length > 0) {
|
|
1078
|
+
const { picked } = await prompts({
|
|
1079
|
+
type: "select",
|
|
1080
|
+
name: "picked",
|
|
1081
|
+
message: "Which project should the bridge write into? (optional — pod-wide by default)",
|
|
1082
|
+
initial: 0,
|
|
1083
|
+
choices: [
|
|
1084
|
+
{ title: "Pod-wide (no project)", value: "" },
|
|
1085
|
+
...projects.map((p) => ({ title: p.name, description: p.id, value: p.id })),
|
|
1086
|
+
],
|
|
1087
|
+
});
|
|
1088
|
+
projectId = picked || undefined;
|
|
1089
|
+
}
|
|
857
1090
|
}
|
|
858
|
-
|
|
859
|
-
return defaultWs;
|
|
860
|
-
}
|
|
861
|
-
if (list.length === 0)
|
|
862
|
-
return defaultWs;
|
|
863
|
-
if (list.length === 1)
|
|
864
|
-
return list[0].id;
|
|
865
|
-
const defaultIdx = defaultWs ? list.findIndex((w) => w.id === defaultWs) : -1;
|
|
866
|
-
const { ws } = await prompts({
|
|
867
|
-
type: "select",
|
|
868
|
-
name: "ws",
|
|
869
|
-
message: "Which workspace should the bridge write into? (channels + entities land here)",
|
|
870
|
-
choices: list.map((w) => ({ title: `${w.name ?? "(unnamed)"} ${chalk.dim(w.id)}`, value: w.id })),
|
|
871
|
-
initial: defaultIdx >= 0 ? defaultIdx : 0,
|
|
872
|
-
});
|
|
873
|
-
return ws || defaultWs;
|
|
1091
|
+
return { workspaceId, projectId };
|
|
874
1092
|
}
|
|
875
1093
|
async function resolveBotToken(provided) {
|
|
876
1094
|
if (provided)
|
|
@@ -882,8 +1100,27 @@ async function resolveBotToken(provided) {
|
|
|
882
1100
|
});
|
|
883
1101
|
return token || undefined;
|
|
884
1102
|
}
|
|
885
|
-
|
|
1103
|
+
/**
|
|
1104
|
+
* @param existingVaultRef `vault://<id>` from a previous run. When there is no
|
|
1105
|
+
* NEW token but the credential is already vaulted, the definition is applied
|
|
1106
|
+
* with the vault entry dropped (we must never store a `{{botToken}}`
|
|
1107
|
+
* placeholder) while the tools' credentialRef is REPOINTED at the existing
|
|
1108
|
+
* secret — which is byte-identical to what a token-carrying apply produces,
|
|
1109
|
+
* since createVaultSecret reuses the same secret id. Without this the re-run
|
|
1110
|
+
* would strip the tool's credentialRef and break a working bridge.
|
|
1111
|
+
*/
|
|
1112
|
+
async function applyBridgeCapability(bridgeDir, workspaceId, cfg, botToken, existingVaultRef) {
|
|
886
1113
|
const spinner = ora("Provisioning capability…").start();
|
|
1114
|
+
// What we still know about the vaulted credential if the apply can't run —
|
|
1115
|
+
// returning it keeps the .env + grant + summary truthful on a re-run.
|
|
1116
|
+
const reuse = existingVaultRef
|
|
1117
|
+
? {
|
|
1118
|
+
vaultRef: existingVaultRef,
|
|
1119
|
+
secretId: existingVaultRef.startsWith("vault://")
|
|
1120
|
+
? existingVaultRef.slice("vault://".length)
|
|
1121
|
+
: undefined,
|
|
1122
|
+
}
|
|
1123
|
+
: {};
|
|
887
1124
|
// Read the capability definition from the bridge repo — ONE source of truth.
|
|
888
1125
|
const capPath = path.join(bridgeDir, "src", "synapCapabilities.js");
|
|
889
1126
|
let rawDef;
|
|
@@ -899,11 +1136,11 @@ async function applyBridgeCapability(bridgeDir, workspaceId, cfg, botToken) {
|
|
|
899
1136
|
spinner.fail("Capability definition not found");
|
|
900
1137
|
log.dim(` Looked in: ${capPath}`);
|
|
901
1138
|
log.dim(` ${err.message}`);
|
|
902
|
-
return {};
|
|
1139
|
+
return { ...reuse, error: `capability definition not found at ${capPath}` };
|
|
903
1140
|
}
|
|
904
1141
|
if (!rawDef) {
|
|
905
1142
|
spinner.fail("No CAPABILITY_DEFINITION export in the bridge");
|
|
906
|
-
return {};
|
|
1143
|
+
return { ...reuse, error: "no CAPABILITY_DEFINITION export in the bridge" };
|
|
907
1144
|
}
|
|
908
1145
|
// Deep-clone + inject the bot token into the vault credential. With no token,
|
|
909
1146
|
// drop the vault + credentialRef so we never store an empty/placeholder secret.
|
|
@@ -916,25 +1153,32 @@ async function applyBridgeCapability(bridgeDir, workspaceId, cfg, botToken) {
|
|
|
916
1153
|
}
|
|
917
1154
|
}
|
|
918
1155
|
else {
|
|
919
|
-
// No token → drop the vault
|
|
920
|
-
//
|
|
921
|
-
//
|
|
1156
|
+
// No NEW token → drop the vault entry, so we never store a placeholder
|
|
1157
|
+
// secret. Two cases for the tools that referenced it:
|
|
1158
|
+
// · re-run (existingVaultRef) → REPOINT at the already-vaulted secret,
|
|
1159
|
+
// preserving the exact wiring a token-carrying apply would produce.
|
|
1160
|
+
// · first run with no token → delete the ref (no dangling reference).
|
|
922
1161
|
const vaultRefs = new Set((def.vault ?? []).map((v) => v.ref).filter((r) => Boolean(r)));
|
|
923
1162
|
delete def.vault;
|
|
924
1163
|
for (const t of def.tools ?? []) {
|
|
925
1164
|
if (t.credentialRef &&
|
|
926
1165
|
(vaultRefs.has(t.credentialRef) || t.credentialRef.startsWith("vault://"))) {
|
|
927
|
-
|
|
1166
|
+
if (existingVaultRef)
|
|
1167
|
+
t.credentialRef = existingVaultRef;
|
|
1168
|
+
else
|
|
1169
|
+
delete t.credentialRef;
|
|
928
1170
|
}
|
|
929
1171
|
}
|
|
930
|
-
spinner.text =
|
|
1172
|
+
spinner.text = existingVaultRef
|
|
1173
|
+
? "Provisioning capability (token already vaulted → reusing the credential)…"
|
|
1174
|
+
: "Provisioning capability (no token → structure only)…";
|
|
931
1175
|
}
|
|
932
1176
|
try {
|
|
933
1177
|
const res = (await hubPost("/capabilities/apply", { definition: def, workspaceId }, cfg));
|
|
934
1178
|
const tools = res.created?.tools?.length ?? 0;
|
|
935
1179
|
const skills = res.created?.skills?.length ?? 0;
|
|
936
|
-
const vaultRef = res.created?.vault?.[0]?.vaultRef;
|
|
937
|
-
const secretId = res.created?.vault?.[0]?.secretId;
|
|
1180
|
+
const vaultRef = res.created?.vault?.[0]?.vaultRef ?? reuse.vaultRef;
|
|
1181
|
+
const secretId = res.created?.vault?.[0]?.secretId ?? reuse.secretId;
|
|
938
1182
|
spinner.succeed(`Capability provisioned: ${res.capabilityKey ?? "?"} (${vaultRef ? "vault + " : ""}${tools} tool, ${skills} skill)`);
|
|
939
1183
|
return { vaultRef, secretId };
|
|
940
1184
|
}
|
|
@@ -942,6 +1186,14 @@ async function applyBridgeCapability(bridgeDir, workspaceId, cfg, botToken) {
|
|
|
942
1186
|
const msg = err.message;
|
|
943
1187
|
spinner.fail("Capability apply failed");
|
|
944
1188
|
log.error(` ${msg}`);
|
|
1189
|
+
if (existingVaultRef) {
|
|
1190
|
+
// Re-run: the pod already holds a working credential + tool wiring, so a
|
|
1191
|
+
// failed RE-apply is not fatal — but it is not a "skip" either. Say what
|
|
1192
|
+
// failed, say what was left alone, and carry it into the final summary.
|
|
1193
|
+
log.dim(" The capability was NOT re-applied — the pod's existing wiring is untouched.");
|
|
1194
|
+
log.dim(" Setup continues; nothing below depends on this apply succeeding.");
|
|
1195
|
+
return { ...reuse, error: msg };
|
|
1196
|
+
}
|
|
945
1197
|
if (botToken) {
|
|
946
1198
|
// The bot token MUST be vaulted for the bridge to run — continuing to write
|
|
947
1199
|
// a .env and printing "Done" would leave a crash-looping bridge (redeem
|
|
@@ -954,7 +1206,7 @@ async function applyBridgeCapability(bridgeDir, workspaceId, cfg, botToken) {
|
|
|
954
1206
|
}
|
|
955
1207
|
process.exit(1);
|
|
956
1208
|
}
|
|
957
|
-
return {};
|
|
1209
|
+
return { ...reuse, error: msg };
|
|
958
1210
|
}
|
|
959
1211
|
}
|
|
960
1212
|
/**
|
|
@@ -971,6 +1223,14 @@ async function grantRedeemAccess(secretId, cfg, agentUserId) {
|
|
|
971
1223
|
// with no_grant. Fall back to the operator only if the agent id is unknown.
|
|
972
1224
|
const principal = agentUserId || (await resolveUserId(cfg));
|
|
973
1225
|
const res = (await hubPost(`/vault/secrets/${secretId}/grant`, { grantedTo: principal, scope: "permanent" }, cfg));
|
|
1226
|
+
if (res.status === "proposed") {
|
|
1227
|
+
// An agent key cannot grant vault access by itself: the pod filed it for
|
|
1228
|
+
// the secret owner's approval. Nothing is granted until it is approved.
|
|
1229
|
+
spinner.warn("Redeem access is waiting for your approval");
|
|
1230
|
+
if (res.reviewUrl)
|
|
1231
|
+
log.dim(` Approve it: ${res.reviewUrl}`);
|
|
1232
|
+
return false;
|
|
1233
|
+
}
|
|
974
1234
|
spinner.succeed(`Redeem access granted${res.reused ? " (existing)" : ""} — token stays in the vault`);
|
|
975
1235
|
return Boolean(res.grantId);
|
|
976
1236
|
}
|
|
@@ -1041,7 +1301,7 @@ function upsertEnv(filePath, kv, remove = []) {
|
|
|
1041
1301
|
for (const [k, v] of remaining)
|
|
1042
1302
|
out.push(`${k}=${v}`);
|
|
1043
1303
|
}
|
|
1044
|
-
|
|
1304
|
+
writeSecretFile(filePath, out.join("\n"), { mode: 0o600 });
|
|
1045
1305
|
}
|
|
1046
1306
|
/**
|
|
1047
1307
|
* Data-aware "what you must do next INSIDE Synap" — the discoverability layer:
|
|
@@ -1079,8 +1339,14 @@ function printNextSteps(clientId, bridgeDir, state) {
|
|
|
1079
1339
|
log.blank();
|
|
1080
1340
|
log.heading("Done");
|
|
1081
1341
|
log.blank();
|
|
1082
|
-
if (state.
|
|
1083
|
-
console.log(` ${chalk.
|
|
1342
|
+
if (state.capabilityError) {
|
|
1343
|
+
console.log(` ${chalk.yellow("⚠")} Capability was not re-applied: ${state.capabilityError}`);
|
|
1344
|
+
console.log(` Everything else below still ran; the pod's existing capability wiring was left untouched.`);
|
|
1345
|
+
}
|
|
1346
|
+
if (state.vaultRef && state.granted && state.reusedToken) {
|
|
1347
|
+
console.log(` ${chalk.green("✓")} Bot token reused from the pod vault (never re-entered), redeem access confirmed ${chalk.dim(state.vaultRef)}`);
|
|
1348
|
+
}
|
|
1349
|
+
else if (state.vaultRef && state.granted) {
|
|
1084
1350
|
}
|
|
1085
1351
|
else if (state.vaultRef && state.hasToken) {
|
|
1086
1352
|
console.log(` ${chalk.yellow("⚠")} Token stored in the vault (${chalk.dim(state.vaultRef)}) but the redeem GRANT did not land.`);
|