@synap-core/cli 1.10.0 → 1.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +23 -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 +33 -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 +523 -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 +80 -35
- package/dist/commands/automation.js.map +1 -1
- package/dist/commands/bridge-setup.d.ts +14 -1
- package/dist/commands/bridge-setup.js +392 -128
- 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 +2 -0
- package/dist/commands/capability.js +297 -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 +3 -14
- package/dist/commands/centrality.js.map +1 -1
- package/dist/commands/connect.d.ts +38 -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 +9 -2
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/data-extra.d.ts +34 -0
- package/dist/commands/data-extra.js +198 -25
- package/dist/commands/data-extra.js.map +1 -1
- package/dist/commands/data.d.ts +120 -2
- package/dist/commands/data.js +307 -24
- package/dist/commands/data.js.map +1 -1
- package/dist/commands/diagnose.d.ts +66 -4
- package/dist/commands/diagnose.js +329 -29
- package/dist/commands/diagnose.js.map +1 -1
- package/dist/commands/digest.js +2 -1
- package/dist/commands/digest.js.map +1 -1
- package/dist/commands/doc.d.ts +25 -4
- package/dist/commands/doc.js +100 -18
- package/dist/commands/doc.js.map +1 -1
- package/dist/commands/doctor.d.ts +130 -0
- package/dist/commands/doctor.js +814 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/events.js +3 -1
- package/dist/commands/events.js.map +1 -1
- package/dist/commands/explain.js +5 -5
- package/dist/commands/explain.js.map +1 -1
- package/dist/commands/graph.js +5 -3
- package/dist/commands/graph.js.map +1 -1
- package/dist/commands/import.d.ts +368 -13
- package/dist/commands/import.js +1096 -55
- package/dist/commands/import.js.map +1 -1
- package/dist/commands/init.d.ts +20 -9
- package/dist/commands/init.js +433 -973
- 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 +28 -6
- package/dist/commands/knowledge.js +358 -73
- package/dist/commands/knowledge.js.map +1 -1
- package/dist/commands/launch.js +2 -2
- package/dist/commands/launch.js.map +1 -1
- package/dist/commands/lens.d.ts +37 -1
- package/dist/commands/lens.js +193 -72
- package/dist/commands/lens.js.map +1 -1
- package/dist/commands/market-authoring.d.ts +72 -0
- package/dist/commands/market-authoring.js +566 -0
- package/dist/commands/market-authoring.js.map +1 -0
- 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 +419 -20
- package/dist/commands/market.js +2124 -193
- package/dist/commands/market.js.map +1 -1
- package/dist/commands/mcp.d.ts +70 -13
- package/dist/commands/mcp.js +308 -40
- package/dist/commands/mcp.js.map +1 -1
- 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 +162 -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 +19 -0
- package/dist/commands/pods.js +220 -85
- package/dist/commands/pods.js.map +1 -1
- package/dist/commands/proposals-actions.d.ts +62 -0
- package/dist/commands/proposals-actions.js +146 -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 +577 -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-wait.d.ts +111 -0
- package/dist/commands/session-wait.js +209 -0
- package/dist/commands/session-wait.js.map +1 -0
- package/dist/commands/sessions.d.ts +52 -0
- package/dist/commands/sessions.js +479 -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.js +19 -1
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/status.d.ts +1 -4
- package/dist/commands/status.js +5 -34
- 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 +51 -0
- package/dist/commands/tracks.js +291 -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 +68 -0
- package/dist/commands/upload.js +257 -0
- package/dist/commands/upload.js.map +1 -0
- 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 +23 -3
- package/dist/commands/whoami.js +169 -23
- package/dist/commands/whoami.js.map +1 -1
- package/dist/commands/worker.d.ts +122 -0
- package/dist/commands/worker.js +435 -0
- package/dist/commands/worker.js.map +1 -0
- package/dist/commands/worker.test.d.ts +1 -0
- package/dist/commands/worker.test.js +116 -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 +1009 -171
- 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 +138 -0
- package/dist/lib/agent-door.js +426 -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 +14 -9
- package/dist/lib/auth.js +44 -61
- 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/capture-lane.d.ts +6 -5
- package/dist/lib/capture-lane.js +11 -8
- package/dist/lib/capture-lane.js.map +1 -1
- package/dist/lib/capture-structure.d.ts +342 -0
- package/dist/lib/capture-structure.js +295 -0
- package/dist/lib/capture-structure.js.map +1 -0
- 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/conversation-headers.d.ts +18 -0
- package/dist/lib/conversation-headers.js +32 -0
- package/dist/lib/conversation-headers.js.map +1 -0
- package/dist/lib/cp-packages.d.ts +216 -4
- package/dist/lib/cp-packages.js +414 -11
- package/dist/lib/cp-packages.js.map +1 -1
- package/dist/lib/credential-class.d.ts +117 -0
- package/dist/lib/credential-class.js +116 -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/hub-client.d.ts +43 -9
- package/dist/lib/hub-client.js +429 -65
- package/dist/lib/hub-client.js.map +1 -1
- package/dist/lib/id.d.ts +23 -0
- package/dist/lib/id.js +35 -0
- package/dist/lib/id.js.map +1 -0
- 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 +16 -0
- package/dist/lib/init-handoff.js +19 -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 +66 -0
- package/dist/lib/key-source.js +106 -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 +24 -0
- package/dist/lib/knowledge-title.js +46 -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 +42 -0
- package/dist/lib/mcp-bridge.js +147 -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 +90 -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/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-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 +119 -115
- package/dist/lib/pod.js +206 -412
- 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/review-credential.d.ts +119 -0
- package/dist/lib/review-credential.js +147 -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-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 +134 -10
- package/dist/lib/targets.js +962 -258
- package/dist/lib/targets.js.map +1 -1
- package/dist/lib/template-file.d.ts +78 -0
- package/dist/lib/template-file.js +140 -0
- package/dist/lib/template-file.js.map +1 -0
- 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/utils/logger.js +5 -2
- package/dist/utils/logger.js.map +1 -1
- package/package.json +25 -18
- package/skills/README.md +12 -10
- package/skills/agent-os/SKILL.md +220 -99
- package/skills/manifest.json +2 -2
- package/skills/onboard/SKILL.md +61 -25
- package/skills/synap/README.md +5 -8
- package/skills/synap/SKILL.md +1185 -301
- 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 +54 -151
- package/skills/synap/cli-operations.md +24 -18
- package/skills/synap/common-mistakes.md +1 -0
- package/skills/synap/concepts.md +28 -0
- package/skills/synap/content-os.md +92 -0
- package/skills/synap/creative-loop.md +15 -10
- package/skills/synap/crm.md +21 -52
- package/skills/synap/digesting-a-channel.md +25 -0
- package/skills/synap/document-embeds.md +141 -47
- package/skills/synap/escalation-ladder.md +20 -10
- package/skills/synap/focus-sessions.md +47 -21
- package/skills/synap/from-intent.md +142 -0
- package/skills/synap/governance-rules.md +4 -4
- 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 +33 -15
- package/skills/synap/linking.md +41 -40
- package/skills/synap/mcp-remote-servers.md +64 -0
- package/skills/synap/mental-model.md +24 -22
- package/skills/synap/multi-entity-capture.md +6 -4
- package/skills/synap/operating-mode-rules.md +1 -1
- package/skills/synap/processes.md +118 -0
- package/skills/synap/quick-reference.md +5 -1
- 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 +96 -0
- package/skills/synap/worked-examples.md +21 -1
- package/skills/synap/workspace-design.md +14 -10
- package/skills/synap/writes.md +165 -35
- package/skills/synap/writing-titles.md +63 -0
- package/skills/synap-app/SKILL.md +319 -0
- package/skills/synap-app/_order.txt +5 -0
- package/skills/synap-app/_skill.yaml +20 -0
- package/skills/synap-app/creating-the-base-app.md +64 -0
- package/skills/synap-app/governance.md +42 -0
- package/skills/synap-app/install-commands.md +55 -0
- package/skills/synap-app/overview.md +50 -0
- package/skills/synap-app/using-the-sdk.md +73 -0
- 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 +625 -0
- package/skills/synap-market/_order.txt +5 -0
- package/skills/synap-market/_skill.yaml +24 -0
- package/skills/synap-market/author-and-publish.md +212 -0
- package/skills/synap-market/discover-and-fetch.md +59 -0
- package/skills/synap-market/governance-and-catalog.md +63 -0
- package/skills/synap-market/install.md +133 -0
- package/skills/synap-market/overview.md +119 -0
- 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/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/dist/lib/browser-auth.d.ts +0 -35
- package/dist/lib/browser-auth.js +0 -170
- package/dist/lib/browser-auth.js.map +0 -1
- package/dist/lib/openclaw.d.ts +0 -32
- package/dist/lib/openclaw.js +0 -164
- package/dist/lib/openclaw.js.map +0 -1
- package/dist/lib/seed.d.ts +0 -13
- package/dist/lib/seed.js +0 -135
- package/dist/lib/seed.js.map +0 -1
|
@@ -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));
|
|
@@ -133,11 +248,17 @@ async function resolveBridgePod(opts) {
|
|
|
133
248
|
export async function bridgeSetup(opts) {
|
|
134
249
|
banner();
|
|
135
250
|
log.heading("Discord bridge — one-shot setup");
|
|
251
|
+
// ── 0. Detect what a previous run already configured, and SAY SO before we
|
|
252
|
+
// ask anything. Purely local — no network, no prompt. ────────────────
|
|
253
|
+
const bridgeDir = opts.bridgeDir ?? DEFAULT_BRIDGE_DIR;
|
|
254
|
+
const reconfigure = Boolean(opts.reconfigure) || process.env.SYNAP_BRIDGE_SETUP_RECONFIGURE === "1";
|
|
255
|
+
const prior = detectPriorState(bridgeDir);
|
|
256
|
+
printPriorState(prior, reconfigure);
|
|
136
257
|
// ── 1. Choose the pod the BRIDGE connects to (independent of the CLI pod) ──
|
|
137
258
|
let cfg;
|
|
138
259
|
let bridgePodName = null;
|
|
139
260
|
try {
|
|
140
|
-
const resolved = await resolveBridgePod(opts);
|
|
261
|
+
const resolved = await resolveBridgePod(opts, prior, reconfigure);
|
|
141
262
|
cfg = resolved.cfg;
|
|
142
263
|
bridgePodName = resolved.podName;
|
|
143
264
|
}
|
|
@@ -157,13 +278,11 @@ export async function bridgeSetup(opts) {
|
|
|
157
278
|
process.exit(1);
|
|
158
279
|
}
|
|
159
280
|
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}`);
|
|
281
|
+
// ── 3. Resolve scope — pod-wide by default, workspace/project optional ───
|
|
282
|
+
const { workspaceId, projectId } = await resolveScope(opts, cfg, prior, reconfigure);
|
|
283
|
+
log.dim(workspaceId ? `Workspace: ${workspaceId} (explicit pin)` : "Workspace: pod-wide (no pin)");
|
|
284
|
+
if (projectId)
|
|
285
|
+
log.dim(`Project: ${projectId}`);
|
|
167
286
|
// ── 3b. Key + scope sanity check (GET /capabilities needs hub-protocol.read) ─
|
|
168
287
|
const probe = ora("Checking key scope…").start();
|
|
169
288
|
try {
|
|
@@ -172,7 +291,7 @@ export async function bridgeSetup(opts) {
|
|
|
172
291
|
}
|
|
173
292
|
catch (err) {
|
|
174
293
|
probe.fail("Key/scope check failed");
|
|
175
|
-
|
|
294
|
+
renderHubError(err);
|
|
176
295
|
log.dim("The key may be expired or missing scope. Run `synap keys rotate` or `synap init`.");
|
|
177
296
|
process.exit(1);
|
|
178
297
|
}
|
|
@@ -182,33 +301,65 @@ export async function bridgeSetup(opts) {
|
|
|
182
301
|
// reads to the operator's data floor (hub-protocol-rest.ts auth middleware)
|
|
183
302
|
// while attributing writes to the agent for governed proposals. All the
|
|
184
303
|
// operator-key work below (capability apply, vault grant) keeps using `cfg`.
|
|
185
|
-
const agentKey = await provisionDiscordAgentKey(cfg.podUrl, cfg.apiKey, cfg.workspaceId);
|
|
304
|
+
const agentKey = await provisionDiscordAgentKey(cfg.podUrl, cfg.apiKey, cfg.workspaceId, !reconfigure ? prior.contextPath : null);
|
|
186
305
|
// ── 4. Provision the capability (vault credential + tool + skill) ─────────
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
306
|
+
// The bot token is the sharpest re-run case: it is ALREADY in the pod vault,
|
|
307
|
+
// so re-prompting for it asks the operator to paste a live secret the system
|
|
308
|
+
// holds. Verify the vaulted secret instead, and only ask when it is genuinely
|
|
309
|
+
// gone (or when --reconfigure / --bot-token says to rotate it).
|
|
310
|
+
let botToken;
|
|
311
|
+
let reuseVaultRef;
|
|
312
|
+
if (opts.botToken) {
|
|
313
|
+
botToken = opts.botToken;
|
|
314
|
+
}
|
|
315
|
+
else if (!reconfigure && prior.secretId) {
|
|
316
|
+
const state = await checkVaultSecret(cfg, workspaceId, prior.secretId);
|
|
317
|
+
if (state === "present") {
|
|
318
|
+
reuseVaultRef = prior.vaultRef;
|
|
319
|
+
log.dim(` [skip] bot token — already in the pod vault (${prior.vaultRef}); not re-asked`);
|
|
320
|
+
}
|
|
321
|
+
else if (state === "unknown") {
|
|
322
|
+
reuseVaultRef = prior.vaultRef;
|
|
323
|
+
log.warn(` [warn] bot token — could not read the vault to confirm ${prior.vaultRef}; ` +
|
|
324
|
+
"keeping it rather than asking you to paste the token again (--reconfigure to re-enter).");
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
log.warn(` [warn] bot token — the vault no longer holds ${prior.vaultRef} (deleted or on another pod); asking again.`);
|
|
328
|
+
botToken = await resolveBotToken(undefined);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
botToken = await resolveBotToken(undefined);
|
|
333
|
+
}
|
|
334
|
+
const { vaultRef, secretId, error: capabilityError } = await applyBridgeCapability(bridgeDir, workspaceId, cfg, botToken, reuseVaultRef);
|
|
190
335
|
// ── 4. Choose agent governance mode ──────────────────────────────────
|
|
191
336
|
const discordAgentUserId = getSurfaceAgentKey("discord")?.agentUserId;
|
|
192
337
|
if (discordAgentUserId) {
|
|
193
|
-
|
|
338
|
+
// A re-run leaves the agent's governance exactly as the operator set it.
|
|
339
|
+
// (The pod exposes no GET for it, so we say "unchanged" — never claim a
|
|
340
|
+
// specific mode we did not read.)
|
|
341
|
+
if (!opts.governance && !reconfigure && prior.bridgeProvisioned) {
|
|
342
|
+
log.dim(" [skip] governance — left unchanged from your previous run " +
|
|
343
|
+
"(--governance safe|normal|crazy, or --reconfigure, to change it)");
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
await ensureAgentGovernance(cfg, discordAgentUserId, opts.governance);
|
|
347
|
+
}
|
|
194
348
|
}
|
|
195
349
|
// ── 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.
|
|
350
|
+
// Order matters: seeding the bundled SKILL.md dirs first attaches their full
|
|
351
|
+
// reference-doc corpus; a capability apply below that embeds a skill of the
|
|
352
|
+
// same name REUSES that pod-scoped row (create-from-definition is idempotent)
|
|
353
|
+
// instead of creating a docless instruction-only copy.
|
|
202
354
|
await applyAgencySkills(cfg);
|
|
203
355
|
// ── 4c. Apply the agency capability templates (idempotent, self-healing) ────
|
|
204
356
|
await applyAgencyCapabilities(cfg, workspaceId);
|
|
205
357
|
// ── 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
|
|
358
|
+
// Only the domain-agnostic automations are seeded here (mail-feed,
|
|
359
|
+
// url/bookmark capture), read from the installed catalog package when it
|
|
360
|
+
// ships them, else from templates/automations/. The grant operation itself
|
|
361
|
+
// ships with the `grants` workspace template (its "Grant Process" method +
|
|
362
|
+
// automations). Thread the operator's
|
|
212
363
|
// chosen proactive channel through — output nodes pin their post target with it
|
|
213
364
|
// when it's a Synap channels.id (see the note in applyAutomationTemplates).
|
|
214
365
|
await applyAutomationTemplates(cfg, workspaceId, opts.proactiveChannel);
|
|
@@ -219,16 +370,22 @@ export async function bridgeSetup(opts) {
|
|
|
219
370
|
await setDiscordToolConfig(cfg, workspaceId, { reactCapture: true });
|
|
220
371
|
}
|
|
221
372
|
// ── 4d. Grant the bridge redeem access — token lives ONLY in the vault ─────
|
|
373
|
+
// Re-run path: `secretId` is recovered from the .env vault ref, so the grant
|
|
374
|
+
// is re-verified (it answers "(existing)") WITHOUT the token being re-entered.
|
|
222
375
|
let granted = false;
|
|
223
|
-
if (secretId
|
|
376
|
+
if (secretId) {
|
|
224
377
|
// Grant to the AGENT (the bridge's redeem identity), not the operator.
|
|
225
378
|
const agentUserId = getSurfaceAgentKey("discord")?.agentUserId;
|
|
226
379
|
granted = await grantRedeemAccess(secretId, cfg, agentUserId);
|
|
227
380
|
}
|
|
228
381
|
// ── 5. Proactive delivery routing (optional) ─────────────────────────────
|
|
229
|
-
|
|
382
|
+
// Delivery preferences live ON a workspace — pod-wide has none to route to.
|
|
383
|
+
if (opts.proactiveChannel && workspaceId) {
|
|
230
384
|
await configureProactive(workspaceId, opts.proactiveChannel, cfg);
|
|
231
385
|
}
|
|
386
|
+
else if (opts.proactiveChannel) {
|
|
387
|
+
log.warn("Proactive routing needs a workspace — pass --workspace-id to enable it.");
|
|
388
|
+
}
|
|
232
389
|
// ── 6. Write the bridge .env ─────────────────────────────────────────────
|
|
233
390
|
const envPath = path.join(bridgeDir, ".env");
|
|
234
391
|
const managed = {
|
|
@@ -237,12 +394,14 @@ export async function bridgeSetup(opts) {
|
|
|
237
394
|
// the operator's raw key. Reads remap to the operator floor; writes are
|
|
238
395
|
// governed as the agent.
|
|
239
396
|
SYNAP_HUB_API_KEY: agentKey,
|
|
240
|
-
SYNAP_WORKSPACE_ID: workspaceId,
|
|
241
397
|
};
|
|
242
|
-
//
|
|
243
|
-
//
|
|
244
|
-
|
|
245
|
-
|
|
398
|
+
// Pod-wide by default: SYNAP_WORKSPACE_ID/SYNAP_PROJECT_ID are only written
|
|
399
|
+
// when the operator explicitly pinned a workspace (--workspace-id) or chose
|
|
400
|
+
// a project in the picker. Neither is required.
|
|
401
|
+
if (workspaceId)
|
|
402
|
+
managed.SYNAP_WORKSPACE_ID = workspaceId;
|
|
403
|
+
if (projectId)
|
|
404
|
+
managed.SYNAP_PROJECT_ID = projectId;
|
|
246
405
|
// The token is NEVER written to .env — it lives in the pod vault and the
|
|
247
406
|
// bridge redeems it on boot via this ref. (Requires the grant below to land;
|
|
248
407
|
// if it didn't, we warn loudly rather than leaking the token to env.)
|
|
@@ -253,8 +412,15 @@ export async function bridgeSetup(opts) {
|
|
|
253
412
|
if (opts.proactiveChannel)
|
|
254
413
|
managed.SYNAP_PROACTIVE_CHANNEL_ID = opts.proactiveChannel;
|
|
255
414
|
try {
|
|
256
|
-
// Once the token is in the vault, purge any stale env copy of it.
|
|
257
|
-
|
|
415
|
+
// Once the token is in the vault, purge any stale env copy of it. Also
|
|
416
|
+
// purge a stale scope pin from a prior run — re-running pod-wide (no
|
|
417
|
+
// --workspace-id / no project picked) must actually clear an old pin.
|
|
418
|
+
const removeKeys = [
|
|
419
|
+
...(vaultRef ? ["DISCORD_BOT_TOKEN"] : []),
|
|
420
|
+
...(workspaceId ? [] : ["SYNAP_WORKSPACE_ID"]),
|
|
421
|
+
...(projectId ? [] : ["SYNAP_PROJECT_ID"]),
|
|
422
|
+
];
|
|
423
|
+
upsertEnv(envPath, managed, removeKeys);
|
|
258
424
|
log.success(`Wrote pod credentials → ${envPath}`);
|
|
259
425
|
}
|
|
260
426
|
catch (err) {
|
|
@@ -270,10 +436,12 @@ export async function bridgeSetup(opts) {
|
|
|
270
436
|
const envHasGuild = fs.existsSync(envPath) &&
|
|
271
437
|
/^DISCORD_GUILD_ID=.+/m.test(fs.readFileSync(envPath, "utf-8"));
|
|
272
438
|
printNextSteps(opts.clientId, bridgeDir, {
|
|
273
|
-
hasToken: Boolean(botToken),
|
|
439
|
+
hasToken: Boolean(botToken) || Boolean(reuseVaultRef),
|
|
274
440
|
hasGuild: Boolean(opts.guildId) || envHasGuild,
|
|
275
441
|
vaultRef,
|
|
276
442
|
granted,
|
|
443
|
+
reusedToken: Boolean(reuseVaultRef),
|
|
444
|
+
capabilityError,
|
|
277
445
|
});
|
|
278
446
|
await printSynapNextSteps(cfg, workspaceId);
|
|
279
447
|
}
|
|
@@ -295,11 +463,6 @@ export async function bridgeSetup(opts) {
|
|
|
295
463
|
const AGENCY_CAPABILITY_PLAN = [
|
|
296
464
|
{ key: "nango-google", needs: null, params: {}, workspaceScoped: false },
|
|
297
465
|
{ 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
466
|
// Cal.com scheduling connector — requires a `calApiKey` param (a `cal_live_...`
|
|
304
467
|
// token), so gate it like the other keyed connectors: skip gracefully unless
|
|
305
468
|
// CALCOM_API_KEY is set. Seeds the `cal_com` tool + booking skills + vault key,
|
|
@@ -326,25 +489,6 @@ const AGENCY_CAPABILITY_PLAN = [
|
|
|
326
489
|
workspaceScoped: false,
|
|
327
490
|
},
|
|
328
491
|
];
|
|
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
492
|
/**
|
|
349
493
|
* Apply the agency capability templates idempotently via the Control Plane catalog.
|
|
350
494
|
*
|
|
@@ -367,6 +511,12 @@ async function applyAgencyCapabilities(cfg, workspaceId) {
|
|
|
367
511
|
log.dim(` [skip] ${item.key} — set ${item.needs} to seed it`);
|
|
368
512
|
continue;
|
|
369
513
|
}
|
|
514
|
+
// Playbook-bundling capabilities require a workspace to land in — pod-wide
|
|
515
|
+
// (no --workspace-id) has none, so skip rather than round-trip a known 4xx.
|
|
516
|
+
if (item.workspaceScoped && !workspaceId) {
|
|
517
|
+
log.dim(` [skip] ${item.key} — bundles playbooks; pass --workspace-id to seed it`);
|
|
518
|
+
continue;
|
|
519
|
+
}
|
|
370
520
|
try {
|
|
371
521
|
const res = (await hubPost("/capabilities/apply", item.workspaceScoped
|
|
372
522
|
? { templateKey: item.key, params: item.params, workspaceId }
|
|
@@ -396,19 +546,22 @@ export function resolveBundledSkillsDir() {
|
|
|
396
546
|
return fs.existsSync(candidate) ? candidate : null;
|
|
397
547
|
}
|
|
398
548
|
/**
|
|
399
|
-
* Seed the
|
|
400
|
-
*
|
|
401
|
-
*
|
|
549
|
+
* Seed the Synap-native skills (standard SKILL.md directories): the agency
|
|
550
|
+
* skills from the installed catalog package when it ships them, else from
|
|
551
|
+
* templates/skills/; the CLI runtime skills from templates/skills/. Each becomes
|
|
552
|
+
* a pod-wide instruction skill via /agent-skills/import; references/ files
|
|
553
|
+
* become linked documents. Idempotent (slug-unique guard).
|
|
402
554
|
*/
|
|
403
555
|
async function applyAgencySkills(cfg) {
|
|
404
|
-
const
|
|
405
|
-
|
|
556
|
+
const catalogRoot = resolveInstalledCatalogRoot();
|
|
557
|
+
const { dirs, fromCatalog } = resolveAgencySkillDirs({ cliSkillsDir: resolveBundledSkillsDir(), catalogRoot });
|
|
558
|
+
if (dirs.length === 0) {
|
|
406
559
|
log.warn("Bundled skill templates not found — skipping.");
|
|
407
560
|
return;
|
|
408
561
|
}
|
|
409
562
|
const userId = await resolveUserId(cfg);
|
|
410
|
-
|
|
411
|
-
|
|
563
|
+
log.dim(`Seeding ${dirs.length} skill(s)` +
|
|
564
|
+
(fromCatalog.length ? ` (${fromCatalog.length} from ${catalogRoot})` : "") + "…");
|
|
412
565
|
for (const dir of dirs) {
|
|
413
566
|
const parsed = parseSkillDir(dir);
|
|
414
567
|
if (!parsed)
|
|
@@ -559,8 +712,8 @@ async function resolveGoogleConnectionId(cfg, workspaceId) {
|
|
|
559
712
|
const google = containers.find((c) => /google/i.test(c.name ?? ""));
|
|
560
713
|
if (!google?.id)
|
|
561
714
|
return null;
|
|
562
|
-
const connRes = await hubGet(`/capabilities/${google.id}/
|
|
563
|
-
const conns = unwrapList(connRes, ["
|
|
715
|
+
const connRes = await hubGet(`/capabilities/${google.id}/credentials`, {}, cfg);
|
|
716
|
+
const conns = unwrapList(connRes, ["credentials"]);
|
|
564
717
|
if (conns.length === 0)
|
|
565
718
|
return null;
|
|
566
719
|
return (conns.find((c) => c.isDefault) ?? conns[0]).id;
|
|
@@ -570,7 +723,9 @@ async function resolveGoogleConnectionId(cfg, workspaceId) {
|
|
|
570
723
|
}
|
|
571
724
|
}
|
|
572
725
|
/**
|
|
573
|
-
* Apply automation templates
|
|
726
|
+
* Apply the *.automation.json templates: AUTOMATION_TEMPLATES_DIR when set, else
|
|
727
|
+
* the installed catalog package's automations/ when it ships them, else the
|
|
728
|
+
* CLI's templates/automations/.
|
|
574
729
|
*
|
|
575
730
|
* For each file:
|
|
576
731
|
* - Fill placeholders + validate references in the flowDefinition:
|
|
@@ -588,7 +743,11 @@ async function resolveGoogleConnectionId(cfg, workspaceId) {
|
|
|
588
743
|
* - Aborts (never partial) if the prerequisite skills/automations fetch fails.
|
|
589
744
|
*/
|
|
590
745
|
async function applyAutomationTemplates(cfg, workspaceId, feedChannelId) {
|
|
591
|
-
const templatesDir =
|
|
746
|
+
const templatesDir = resolveAutomationTemplatesDir({
|
|
747
|
+
override: process.env.AUTOMATION_TEMPLATES_DIR,
|
|
748
|
+
catalogRoot: resolveInstalledCatalogRoot(),
|
|
749
|
+
cliDir: path.join(CLI_TEMPLATES_DIR, "automations"),
|
|
750
|
+
});
|
|
592
751
|
if (!templatesDir) {
|
|
593
752
|
log.warn("Automation templates dir not found — skipping. " +
|
|
594
753
|
"Set AUTOMATION_TEMPLATES_DIR=<path> to seed from a custom location.");
|
|
@@ -613,6 +772,32 @@ async function applyAutomationTemplates(cfg, workspaceId, feedChannelId) {
|
|
|
613
772
|
log.warn(`Could not fetch existing automations — aborting seeding: ${err.message}`);
|
|
614
773
|
return;
|
|
615
774
|
}
|
|
775
|
+
// Fetch the capability catalog's verb ids once — needed for capability-node
|
|
776
|
+
// validation. This is a DIFFERENT namespace from `/skills`: a catalog verb's
|
|
777
|
+
// backing skill need not appear in the `/skills` list at all. Dogfood: the
|
|
778
|
+
// `ai.generate` verb on "Synap Core" is `enabled: true, runnable: true` and is
|
|
779
|
+
// what every AI automation node uses, yet its backing skill
|
|
780
|
+
// (e441519b-…) is absent from all 50 rows `/skills` returns. Validating verbs
|
|
781
|
+
// against skill NAMES therefore marked every AI automation as missing and
|
|
782
|
+
// silently skipped it.
|
|
783
|
+
let catalogVerbIds = new Set();
|
|
784
|
+
try {
|
|
785
|
+
const res = await hubGet("/capabilities/catalog", { userId, workspaceId }, cfg);
|
|
786
|
+
for (const cap of unwrapList(res, [
|
|
787
|
+
"capabilities",
|
|
788
|
+
])) {
|
|
789
|
+
for (const v of cap.verbs ?? [])
|
|
790
|
+
if (v.verbId)
|
|
791
|
+
catalogVerbIds.add(v.verbId);
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
catch (err) {
|
|
795
|
+
// NON-fatal, unlike the skills fetch: an empty verb set must not skip every
|
|
796
|
+
// AI automation. Fall back to accepting any verbId and let the runtime be the
|
|
797
|
+
// judge — a seeded automation that fails loudly beats one silently not seeded.
|
|
798
|
+
log.warn(`Could not fetch the capability catalog — verb ids will not be pre-validated: ${err.message}`);
|
|
799
|
+
catalogVerbIds = new Set();
|
|
800
|
+
}
|
|
616
801
|
// Fetch existing skills once — needed for skill-node injection.
|
|
617
802
|
let existingSkills = [];
|
|
618
803
|
try {
|
|
@@ -660,11 +845,16 @@ async function applyAutomationTemplates(cfg, workspaceId, feedChannelId) {
|
|
|
660
845
|
missing ??= `skill "${skillName}"`;
|
|
661
846
|
}
|
|
662
847
|
}
|
|
663
|
-
// `capability` node → validate verbId
|
|
664
|
-
//
|
|
848
|
+
// `capability` node → validate verbId against the CAPABILITY CATALOG first
|
|
849
|
+
// (its real home), falling back to seeded skill names for verbs that are
|
|
850
|
+
// skill-backed by name. When the catalog could not be fetched the set is
|
|
851
|
+
// empty and we do not pre-validate at all — see the fetch above.
|
|
665
852
|
if (node.type === "capability") {
|
|
666
853
|
const data = node.data;
|
|
667
|
-
if (data.verbId &&
|
|
854
|
+
if (data.verbId &&
|
|
855
|
+
catalogVerbIds.size > 0 &&
|
|
856
|
+
!catalogVerbIds.has(data.verbId) &&
|
|
857
|
+
!existingSkills.some((s) => s.name === data.verbId)) {
|
|
668
858
|
missing ??= `verb "${data.verbId}"`;
|
|
669
859
|
}
|
|
670
860
|
const sel = data.connectionSelector;
|
|
@@ -800,9 +990,12 @@ async function applyAutomationTemplates(cfg, workspaceId, feedChannelId) {
|
|
|
800
990
|
* @param podUrl Pod base URL.
|
|
801
991
|
* @param operatorKey The operator's raw hub key (authorizes the provisioning).
|
|
802
992
|
* @param workspaceId Workspace to enroll the agent into (omit for all).
|
|
993
|
+
* @param existingContextPath Path to the CONTEXT.md a previous run already
|
|
994
|
+
* wrote — when set, the agent-template wizard is SKIPPED (same reuse-and-say-so
|
|
995
|
+
* contract as the agent key itself). Pass null to run it.
|
|
803
996
|
* @returns the Discord agent key to write as SYNAP_HUB_API_KEY.
|
|
804
997
|
*/
|
|
805
|
-
async function provisionDiscordAgentKey(podUrl, operatorKey, workspaceId) {
|
|
998
|
+
async function provisionDiscordAgentKey(podUrl, operatorKey, workspaceId, existingContextPath) {
|
|
806
999
|
const spinner = ora("Provisioning Discord agent key…").start();
|
|
807
1000
|
let hubApiKey;
|
|
808
1001
|
let agentUserId;
|
|
@@ -833,44 +1026,67 @@ async function provisionDiscordAgentKey(podUrl, operatorKey, workspaceId) {
|
|
|
833
1026
|
// Enroll the agent user into the operator's workspace(s) using the OPERATOR
|
|
834
1027
|
// key (before the bridge ever uses the agent key).
|
|
835
1028
|
await enrollAgentIfNeeded(podUrl, operatorKey, agentUserId, workspaceId);
|
|
836
|
-
// Same CONTEXT.md routing file every other provisioned agent gets
|
|
837
|
-
|
|
838
|
-
|
|
1029
|
+
// Same CONTEXT.md routing file every other provisioned agent gets — written
|
|
1030
|
+
// once. A re-run keeps the template the operator already picked.
|
|
1031
|
+
if (existingContextPath) {
|
|
1032
|
+
log.dim(` [skip] agent template — already configured (${existingContextPath}); --reconfigure to pick another`);
|
|
839
1033
|
}
|
|
840
|
-
|
|
841
|
-
|
|
1034
|
+
else {
|
|
1035
|
+
try {
|
|
1036
|
+
await configureAgentContext(podUrl, operatorKey, "discord", agentUserId);
|
|
1037
|
+
}
|
|
1038
|
+
catch (err) {
|
|
1039
|
+
log.warn(`Agent context wizard failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
1040
|
+
}
|
|
842
1041
|
}
|
|
843
1042
|
return hubApiKey;
|
|
844
1043
|
}
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
1044
|
+
/**
|
|
1045
|
+
* Resolve the OPTIONAL scope the bridge writes into — pod-wide by default.
|
|
1046
|
+
*
|
|
1047
|
+
* Workspace: an explicit --workspace-id is the only way to pin one (advanced/
|
|
1048
|
+
* legacy escape hatch) — never prompted, never required.
|
|
1049
|
+
*
|
|
1050
|
+
* Project: a peer, independent lens. --project-id skips the prompt; otherwise
|
|
1051
|
+
* offer a picker with "Pod-wide (no project)" as the default alongside the
|
|
1052
|
+
* operator's existing projects. No projects on the pod → skip the prompt.
|
|
1053
|
+
*
|
|
1054
|
+
* Re-run: BOTH lenses are inherited from the bridge's own .env instead of being
|
|
1055
|
+
* re-asked. That also fixes a real re-run defect — re-running without
|
|
1056
|
+
* --workspace-id used to silently UNPIN a bridge that had been pinned (the .env
|
|
1057
|
+
* key is purged), which in turn silently skipped every playbook-bundling
|
|
1058
|
+
* capability. --reconfigure restores the ask-me-everything behaviour.
|
|
1059
|
+
*/
|
|
1060
|
+
async function resolveScope(opts, cfg, prior, reconfigure) {
|
|
1061
|
+
let workspaceId = opts.workspaceId;
|
|
1062
|
+
if (!workspaceId && !reconfigure && prior.workspaceId) {
|
|
1063
|
+
workspaceId = prior.workspaceId;
|
|
1064
|
+
log.dim(` [skip] workspace — keeping the pin from your previous run (${workspaceId}); --reconfigure to clear it`);
|
|
1065
|
+
}
|
|
1066
|
+
let projectId = opts.projectId;
|
|
1067
|
+
if (!projectId && !reconfigure && prior.bridgeProvisioned) {
|
|
1068
|
+
projectId = prior.projectId;
|
|
1069
|
+
log.dim(prior.projectId
|
|
1070
|
+
? ` [skip] project — keeping ${prior.projectId} from your previous run (--project-id or --reconfigure to change)`
|
|
1071
|
+
: " [skip] project — you chose pod-wide previously; keeping it (--project-id or --reconfigure to change)");
|
|
1072
|
+
}
|
|
1073
|
+
else if (!projectId) {
|
|
1074
|
+
const projects = await fetchProjects(cfg);
|
|
1075
|
+
if (projects.length > 0) {
|
|
1076
|
+
const { picked } = await prompts({
|
|
1077
|
+
type: "select",
|
|
1078
|
+
name: "picked",
|
|
1079
|
+
message: "Which project should the bridge write into? (optional — pod-wide by default)",
|
|
1080
|
+
initial: 0,
|
|
1081
|
+
choices: [
|
|
1082
|
+
{ title: "Pod-wide (no project)", value: "" },
|
|
1083
|
+
...projects.map((p) => ({ title: p.name, description: p.id, value: p.id })),
|
|
1084
|
+
],
|
|
1085
|
+
});
|
|
1086
|
+
projectId = picked || undefined;
|
|
1087
|
+
}
|
|
857
1088
|
}
|
|
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;
|
|
1089
|
+
return { workspaceId, projectId };
|
|
874
1090
|
}
|
|
875
1091
|
async function resolveBotToken(provided) {
|
|
876
1092
|
if (provided)
|
|
@@ -882,8 +1098,27 @@ async function resolveBotToken(provided) {
|
|
|
882
1098
|
});
|
|
883
1099
|
return token || undefined;
|
|
884
1100
|
}
|
|
885
|
-
|
|
1101
|
+
/**
|
|
1102
|
+
* @param existingVaultRef `vault://<id>` from a previous run. When there is no
|
|
1103
|
+
* NEW token but the credential is already vaulted, the definition is applied
|
|
1104
|
+
* with the vault entry dropped (we must never store a `{{botToken}}`
|
|
1105
|
+
* placeholder) while the tools' credentialRef is REPOINTED at the existing
|
|
1106
|
+
* secret — which is byte-identical to what a token-carrying apply produces,
|
|
1107
|
+
* since createVaultSecret reuses the same secret id. Without this the re-run
|
|
1108
|
+
* would strip the tool's credentialRef and break a working bridge.
|
|
1109
|
+
*/
|
|
1110
|
+
async function applyBridgeCapability(bridgeDir, workspaceId, cfg, botToken, existingVaultRef) {
|
|
886
1111
|
const spinner = ora("Provisioning capability…").start();
|
|
1112
|
+
// What we still know about the vaulted credential if the apply can't run —
|
|
1113
|
+
// returning it keeps the .env + grant + summary truthful on a re-run.
|
|
1114
|
+
const reuse = existingVaultRef
|
|
1115
|
+
? {
|
|
1116
|
+
vaultRef: existingVaultRef,
|
|
1117
|
+
secretId: existingVaultRef.startsWith("vault://")
|
|
1118
|
+
? existingVaultRef.slice("vault://".length)
|
|
1119
|
+
: undefined,
|
|
1120
|
+
}
|
|
1121
|
+
: {};
|
|
887
1122
|
// Read the capability definition from the bridge repo — ONE source of truth.
|
|
888
1123
|
const capPath = path.join(bridgeDir, "src", "synapCapabilities.js");
|
|
889
1124
|
let rawDef;
|
|
@@ -899,11 +1134,11 @@ async function applyBridgeCapability(bridgeDir, workspaceId, cfg, botToken) {
|
|
|
899
1134
|
spinner.fail("Capability definition not found");
|
|
900
1135
|
log.dim(` Looked in: ${capPath}`);
|
|
901
1136
|
log.dim(` ${err.message}`);
|
|
902
|
-
return {};
|
|
1137
|
+
return { ...reuse, error: `capability definition not found at ${capPath}` };
|
|
903
1138
|
}
|
|
904
1139
|
if (!rawDef) {
|
|
905
1140
|
spinner.fail("No CAPABILITY_DEFINITION export in the bridge");
|
|
906
|
-
return {};
|
|
1141
|
+
return { ...reuse, error: "no CAPABILITY_DEFINITION export in the bridge" };
|
|
907
1142
|
}
|
|
908
1143
|
// Deep-clone + inject the bot token into the vault credential. With no token,
|
|
909
1144
|
// drop the vault + credentialRef so we never store an empty/placeholder secret.
|
|
@@ -916,25 +1151,32 @@ async function applyBridgeCapability(bridgeDir, workspaceId, cfg, botToken) {
|
|
|
916
1151
|
}
|
|
917
1152
|
}
|
|
918
1153
|
else {
|
|
919
|
-
// No token → drop the vault
|
|
920
|
-
//
|
|
921
|
-
//
|
|
1154
|
+
// No NEW token → drop the vault entry, so we never store a placeholder
|
|
1155
|
+
// secret. Two cases for the tools that referenced it:
|
|
1156
|
+
// · re-run (existingVaultRef) → REPOINT at the already-vaulted secret,
|
|
1157
|
+
// preserving the exact wiring a token-carrying apply would produce.
|
|
1158
|
+
// · first run with no token → delete the ref (no dangling reference).
|
|
922
1159
|
const vaultRefs = new Set((def.vault ?? []).map((v) => v.ref).filter((r) => Boolean(r)));
|
|
923
1160
|
delete def.vault;
|
|
924
1161
|
for (const t of def.tools ?? []) {
|
|
925
1162
|
if (t.credentialRef &&
|
|
926
1163
|
(vaultRefs.has(t.credentialRef) || t.credentialRef.startsWith("vault://"))) {
|
|
927
|
-
|
|
1164
|
+
if (existingVaultRef)
|
|
1165
|
+
t.credentialRef = existingVaultRef;
|
|
1166
|
+
else
|
|
1167
|
+
delete t.credentialRef;
|
|
928
1168
|
}
|
|
929
1169
|
}
|
|
930
|
-
spinner.text =
|
|
1170
|
+
spinner.text = existingVaultRef
|
|
1171
|
+
? "Provisioning capability (token already vaulted → reusing the credential)…"
|
|
1172
|
+
: "Provisioning capability (no token → structure only)…";
|
|
931
1173
|
}
|
|
932
1174
|
try {
|
|
933
1175
|
const res = (await hubPost("/capabilities/apply", { definition: def, workspaceId }, cfg));
|
|
934
1176
|
const tools = res.created?.tools?.length ?? 0;
|
|
935
1177
|
const skills = res.created?.skills?.length ?? 0;
|
|
936
|
-
const vaultRef = res.created?.vault?.[0]?.vaultRef;
|
|
937
|
-
const secretId = res.created?.vault?.[0]?.secretId;
|
|
1178
|
+
const vaultRef = res.created?.vault?.[0]?.vaultRef ?? reuse.vaultRef;
|
|
1179
|
+
const secretId = res.created?.vault?.[0]?.secretId ?? reuse.secretId;
|
|
938
1180
|
spinner.succeed(`Capability provisioned: ${res.capabilityKey ?? "?"} (${vaultRef ? "vault + " : ""}${tools} tool, ${skills} skill)`);
|
|
939
1181
|
return { vaultRef, secretId };
|
|
940
1182
|
}
|
|
@@ -942,6 +1184,14 @@ async function applyBridgeCapability(bridgeDir, workspaceId, cfg, botToken) {
|
|
|
942
1184
|
const msg = err.message;
|
|
943
1185
|
spinner.fail("Capability apply failed");
|
|
944
1186
|
log.error(` ${msg}`);
|
|
1187
|
+
if (existingVaultRef) {
|
|
1188
|
+
// Re-run: the pod already holds a working credential + tool wiring, so a
|
|
1189
|
+
// failed RE-apply is not fatal — but it is not a "skip" either. Say what
|
|
1190
|
+
// failed, say what was left alone, and carry it into the final summary.
|
|
1191
|
+
log.dim(" The capability was NOT re-applied — the pod's existing wiring is untouched.");
|
|
1192
|
+
log.dim(" Setup continues; nothing below depends on this apply succeeding.");
|
|
1193
|
+
return { ...reuse, error: msg };
|
|
1194
|
+
}
|
|
945
1195
|
if (botToken) {
|
|
946
1196
|
// The bot token MUST be vaulted for the bridge to run — continuing to write
|
|
947
1197
|
// a .env and printing "Done" would leave a crash-looping bridge (redeem
|
|
@@ -954,7 +1204,7 @@ async function applyBridgeCapability(bridgeDir, workspaceId, cfg, botToken) {
|
|
|
954
1204
|
}
|
|
955
1205
|
process.exit(1);
|
|
956
1206
|
}
|
|
957
|
-
return {};
|
|
1207
|
+
return { ...reuse, error: msg };
|
|
958
1208
|
}
|
|
959
1209
|
}
|
|
960
1210
|
/**
|
|
@@ -971,6 +1221,14 @@ async function grantRedeemAccess(secretId, cfg, agentUserId) {
|
|
|
971
1221
|
// with no_grant. Fall back to the operator only if the agent id is unknown.
|
|
972
1222
|
const principal = agentUserId || (await resolveUserId(cfg));
|
|
973
1223
|
const res = (await hubPost(`/vault/secrets/${secretId}/grant`, { grantedTo: principal, scope: "permanent" }, cfg));
|
|
1224
|
+
if (res.status === "proposed") {
|
|
1225
|
+
// An agent key cannot grant vault access by itself: the pod filed it for
|
|
1226
|
+
// the secret owner's approval. Nothing is granted until it is approved.
|
|
1227
|
+
spinner.warn("Redeem access is waiting for your approval");
|
|
1228
|
+
if (res.reviewUrl)
|
|
1229
|
+
log.dim(` Approve it: ${res.reviewUrl}`);
|
|
1230
|
+
return false;
|
|
1231
|
+
}
|
|
974
1232
|
spinner.succeed(`Redeem access granted${res.reused ? " (existing)" : ""} — token stays in the vault`);
|
|
975
1233
|
return Boolean(res.grantId);
|
|
976
1234
|
}
|
|
@@ -1041,7 +1299,7 @@ function upsertEnv(filePath, kv, remove = []) {
|
|
|
1041
1299
|
for (const [k, v] of remaining)
|
|
1042
1300
|
out.push(`${k}=${v}`);
|
|
1043
1301
|
}
|
|
1044
|
-
|
|
1302
|
+
writeSecretFile(filePath, out.join("\n"), { mode: 0o600 });
|
|
1045
1303
|
}
|
|
1046
1304
|
/**
|
|
1047
1305
|
* Data-aware "what you must do next INSIDE Synap" — the discoverability layer:
|
|
@@ -1079,8 +1337,14 @@ function printNextSteps(clientId, bridgeDir, state) {
|
|
|
1079
1337
|
log.blank();
|
|
1080
1338
|
log.heading("Done");
|
|
1081
1339
|
log.blank();
|
|
1082
|
-
if (state.
|
|
1083
|
-
console.log(` ${chalk.
|
|
1340
|
+
if (state.capabilityError) {
|
|
1341
|
+
console.log(` ${chalk.yellow("⚠")} Capability was not re-applied: ${state.capabilityError}`);
|
|
1342
|
+
console.log(` Everything else below still ran; the pod's existing capability wiring was left untouched.`);
|
|
1343
|
+
}
|
|
1344
|
+
if (state.vaultRef && state.granted && state.reusedToken) {
|
|
1345
|
+
console.log(` ${chalk.green("✓")} Bot token reused from the pod vault (never re-entered), redeem access confirmed ${chalk.dim(state.vaultRef)}`);
|
|
1346
|
+
}
|
|
1347
|
+
else if (state.vaultRef && state.granted) {
|
|
1084
1348
|
}
|
|
1085
1349
|
else if (state.vaultRef && state.hasToken) {
|
|
1086
1350
|
console.log(` ${chalk.yellow("⚠")} Token stored in the vault (${chalk.dim(state.vaultRef)}) but the redeem GRANT did not land.`);
|