@synap-core/cli 1.8.0 → 1.11.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/dist/commands/agent-chat.d.ts +47 -0
- package/dist/commands/agent-chat.js +250 -0
- package/dist/commands/agent-chat.js.map +1 -0
- package/dist/commands/agent-run.js +3 -4
- package/dist/commands/agent-run.js.map +1 -1
- package/dist/commands/agents.js +85 -46
- package/dist/commands/agents.js.map +1 -1
- package/dist/commands/automation.js +2 -1
- package/dist/commands/automation.js.map +1 -1
- package/dist/commands/bridge-setup.d.ts +66 -0
- package/dist/commands/bridge-setup.js +1112 -0
- package/dist/commands/bridge-setup.js.map +1 -0
- package/dist/commands/cap-connections.d.ts +28 -0
- package/dist/commands/cap-connections.js +224 -0
- package/dist/commands/cap-connections.js.map +1 -0
- package/dist/commands/capability.d.ts +89 -0
- package/dist/commands/capability.js +1594 -0
- package/dist/commands/capability.js.map +1 -0
- package/dist/commands/centrality.d.ts +20 -0
- package/dist/commands/centrality.js +95 -0
- package/dist/commands/centrality.js.map +1 -0
- package/dist/commands/connect.d.ts +2 -0
- package/dist/commands/connect.js +16 -35
- package/dist/commands/connect.js.map +1 -1
- package/dist/commands/context.js +6 -7
- package/dist/commands/context.js.map +1 -1
- package/dist/commands/data-extra.d.ts +1 -0
- package/dist/commands/data-extra.js +41 -14
- package/dist/commands/data-extra.js.map +1 -1
- package/dist/commands/data.d.ts +4 -4
- package/dist/commands/data.js +254 -187
- package/dist/commands/data.js.map +1 -1
- package/dist/commands/diagnose.d.ts +20 -0
- package/dist/commands/diagnose.js +186 -0
- package/dist/commands/diagnose.js.map +1 -0
- package/dist/commands/digest.d.ts +17 -0
- package/dist/commands/digest.js +122 -0
- package/dist/commands/digest.js.map +1 -0
- package/dist/commands/discover.d.ts +2 -0
- package/dist/commands/discover.js +25 -8
- package/dist/commands/discover.js.map +1 -1
- package/dist/commands/doc.d.ts +14 -0
- package/dist/commands/doc.js +61 -4
- package/dist/commands/doc.js.map +1 -1
- package/dist/commands/doctor.d.ts +21 -0
- package/dist/commands/doctor.js +189 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/events.js +5 -2
- package/dist/commands/events.js.map +1 -1
- package/dist/commands/explain.d.ts +1 -1
- package/dist/commands/explain.js +18 -15
- package/dist/commands/explain.js.map +1 -1
- package/dist/commands/facet.d.ts +29 -0
- package/dist/commands/facet.js +113 -0
- package/dist/commands/facet.js.map +1 -0
- package/dist/commands/graph.js +7 -4
- package/dist/commands/graph.js.map +1 -1
- package/dist/commands/import.d.ts +38 -0
- package/dist/commands/import.js +471 -0
- package/dist/commands/import.js.map +1 -0
- package/dist/commands/init.js +35 -16
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/keys.js +3 -4
- package/dist/commands/keys.js.map +1 -1
- package/dist/commands/knowledge.d.ts +8 -0
- package/dist/commands/knowledge.js +308 -33
- package/dist/commands/knowledge.js.map +1 -1
- package/dist/commands/launch.d.ts +50 -0
- package/dist/commands/launch.js +790 -0
- package/dist/commands/launch.js.map +1 -0
- package/dist/commands/lens.d.ts +45 -0
- package/dist/commands/lens.js +386 -0
- package/dist/commands/lens.js.map +1 -0
- package/dist/commands/market-authoring.d.ts +45 -0
- package/dist/commands/market-authoring.js +225 -0
- package/dist/commands/market-authoring.js.map +1 -0
- package/dist/commands/market.d.ts +224 -0
- package/dist/commands/market.js +1643 -0
- package/dist/commands/market.js.map +1 -0
- package/dist/commands/mcp.d.ts +64 -0
- package/dist/commands/mcp.js +265 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/observe.js +14 -11
- package/dist/commands/observe.js.map +1 -1
- package/dist/commands/open.d.ts +21 -4
- package/dist/commands/open.js +45 -4
- package/dist/commands/open.js.map +1 -1
- package/dist/commands/pod.d.ts +100 -0
- package/dist/commands/pod.js +359 -0
- package/dist/commands/pod.js.map +1 -0
- package/dist/commands/pods.d.ts +15 -0
- package/dist/commands/pods.js +233 -17
- package/dist/commands/pods.js.map +1 -1
- package/dist/commands/proposals-actions.js +43 -3
- package/dist/commands/proposals-actions.js.map +1 -1
- package/dist/commands/purge.d.ts +23 -0
- package/dist/commands/purge.js +148 -0
- package/dist/commands/purge.js.map +1 -0
- package/dist/commands/raycast.d.ts +15 -0
- package/dist/commands/raycast.js +153 -0
- package/dist/commands/raycast.js.map +1 -0
- package/dist/commands/sessions.js +47 -22
- package/dist/commands/sessions.js.map +1 -1
- package/dist/commands/skill-manage.d.ts +21 -0
- package/dist/commands/skill-manage.js +195 -0
- package/dist/commands/skill-manage.js.map +1 -0
- package/dist/commands/skills-commands.js +2 -1
- package/dist/commands/skills-commands.js.map +1 -1
- package/dist/commands/skills.js +7 -4
- package/dist/commands/skills.js.map +1 -1
- package/dist/commands/status.d.ts +3 -1
- package/dist/commands/status.js +166 -14
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/statusline.d.ts +24 -0
- package/dist/commands/statusline.js +450 -0
- package/dist/commands/statusline.js.map +1 -0
- package/dist/commands/subscribe.js +2 -2
- package/dist/commands/subscribe.js.map +1 -1
- package/dist/commands/template.d.ts +42 -0
- package/dist/commands/template.js +191 -0
- package/dist/commands/template.js.map +1 -0
- package/dist/commands/tools.d.ts +30 -0
- package/dist/commands/tools.js +315 -0
- package/dist/commands/tools.js.map +1 -0
- package/dist/commands/upload.d.ts +30 -0
- package/dist/commands/upload.js +155 -0
- package/dist/commands/upload.js.map +1 -0
- package/dist/commands/versions.d.ts +27 -0
- package/dist/commands/versions.js +277 -0
- package/dist/commands/versions.js.map +1 -0
- package/dist/commands/view.js +3 -2
- package/dist/commands/view.js.map +1 -1
- package/dist/commands/whoami.d.ts +21 -0
- package/dist/commands/whoami.js +129 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/index.js +894 -60
- package/dist/index.js.map +1 -1
- package/dist/lib/approval-poll.d.ts +42 -0
- package/dist/lib/approval-poll.js +52 -0
- package/dist/lib/approval-poll.js.map +1 -0
- package/dist/lib/auth.d.ts +29 -6
- package/dist/lib/auth.js +105 -94
- package/dist/lib/auth.js.map +1 -1
- package/dist/lib/bundle-version.d.ts +11 -0
- package/dist/lib/bundle-version.js +33 -0
- package/dist/lib/bundle-version.js.map +1 -0
- package/dist/lib/capture-lane.d.ts +30 -12
- package/dist/lib/capture-lane.js +73 -14
- package/dist/lib/capture-lane.js.map +1 -1
- package/dist/lib/capture-structure.d.ts +123 -0
- package/dist/lib/capture-structure.js +57 -0
- package/dist/lib/capture-structure.js.map +1 -0
- package/dist/lib/cp-packages.d.ts +177 -0
- package/dist/lib/cp-packages.js +389 -0
- package/dist/lib/cp-packages.js.map +1 -0
- package/dist/lib/describe-lens.d.ts +46 -0
- package/dist/lib/describe-lens.js +33 -0
- package/dist/lib/describe-lens.js.map +1 -0
- package/dist/lib/hub-client.d.ts +91 -1
- package/dist/lib/hub-client.js +565 -22
- 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/installed.d.ts +51 -0
- package/dist/lib/installed.js +70 -0
- package/dist/lib/installed.js.map +1 -0
- package/dist/lib/next-steps.d.ts +61 -0
- package/dist/lib/next-steps.js +146 -0
- package/dist/lib/next-steps.js.map +1 -0
- package/dist/lib/pod.d.ts +144 -22
- package/dist/lib/pod.js +302 -64
- package/dist/lib/pod.js.map +1 -1
- package/dist/lib/project-ref.d.ts +95 -0
- package/dist/lib/project-ref.js +144 -0
- package/dist/lib/project-ref.js.map +1 -0
- package/dist/lib/project.d.ts +35 -0
- package/dist/lib/project.js +39 -0
- package/dist/lib/project.js.map +1 -0
- package/dist/lib/session-lens.d.ts +33 -0
- package/dist/lib/session-lens.js +76 -0
- package/dist/lib/session-lens.js.map +1 -0
- package/dist/lib/skill-import.d.ts +40 -0
- package/dist/lib/skill-import.js +203 -0
- package/dist/lib/skill-import.js.map +1 -0
- package/dist/lib/skills-installer.d.ts +17 -6
- package/dist/lib/skills-installer.js +133 -19
- package/dist/lib/skills-installer.js.map +1 -1
- package/dist/lib/suite.d.ts +28 -0
- package/dist/lib/suite.js +29 -0
- package/dist/lib/suite.js.map +1 -0
- package/dist/lib/superwhisper-import.d.ts +40 -0
- package/dist/lib/superwhisper-import.js +389 -0
- package/dist/lib/superwhisper-import.js.map +1 -0
- package/dist/lib/targets.d.ts +49 -1
- package/dist/lib/targets.js +420 -288
- package/dist/lib/targets.js.map +1 -1
- package/dist/lib/template-file.d.ts +74 -0
- package/dist/lib/template-file.js +140 -0
- package/dist/lib/template-file.js.map +1 -0
- package/dist/lib/unwrapList.d.ts +1 -0
- package/dist/lib/unwrapList.js +20 -0
- package/dist/lib/unwrapList.js.map +1 -0
- package/dist/utils/logger.d.ts +2 -0
- package/dist/utils/logger.js +10 -2
- package/dist/utils/logger.js.map +1 -1
- package/node_modules/@synap/hub-rest-client/dist/index.cjs +414 -34
- package/node_modules/@synap/hub-rest-client/dist/index.d.cts +762 -22
- package/node_modules/@synap/hub-rest-client/dist/index.d.ts +762 -22
- package/node_modules/@synap/hub-rest-client/dist/index.js +414 -34
- package/node_modules/@synap/hub-rest-client/package.json +1 -1
- package/node_modules/@synap/hub-rest-client/src/client.ts +525 -37
- package/node_modules/@synap/hub-rest-client/src/index.ts +42 -0
- package/node_modules/@synap/hub-rest-client/src/setup.ts +57 -1
- package/node_modules/@synap/hub-rest-client/src/types.ts +697 -37
- package/package.json +11 -5
- package/skills/agent-os/SKILL.md +218 -0
- package/skills/manifest.json +5 -0
- package/skills/onboard/SKILL.md +193 -0
- package/skills/synap/SKILL.md +1284 -58
- package/skills/synap/_order.txt +37 -0
- package/skills/synap/_skill.yaml +24 -0
- package/skills/synap/authentication.md +10 -0
- package/skills/synap/automations.md +264 -0
- package/skills/synap/capabilities.md +107 -0
- package/skills/synap/capture.md +50 -142
- package/skills/synap/cli-operations.md +107 -0
- package/skills/synap/common-mistakes.md +13 -0
- package/skills/synap/content-os.md +83 -0
- package/skills/synap/creative-loop.md +43 -0
- package/skills/synap/crm.md +117 -0
- package/skills/synap/diagnostics.md +35 -0
- package/skills/synap/digesting-a-channel.md +63 -0
- package/skills/synap/document-embeds.md +123 -0
- package/skills/synap/escalation-ladder.md +71 -0
- package/skills/synap/focus-sessions.md +47 -0
- package/skills/synap/governance-rules.md +64 -0
- package/skills/synap/governance.md +6 -6
- package/skills/synap/graph-gardening.md +21 -0
- package/skills/synap/inline-patterns.md +38 -0
- package/skills/synap/lenses.md +38 -0
- package/skills/synap/linking-principle.md +39 -0
- package/skills/synap/linking.md +1 -0
- package/skills/synap/mental-model.md +45 -0
- package/skills/synap/more.md +7 -0
- package/skills/synap/multi-entity-capture.md +12 -0
- package/skills/synap/next-action.md +37 -0
- package/skills/synap/north-star.md +17 -0
- package/skills/synap/operating-mode-rules.md +45 -0
- package/skills/synap/operating-mode.md +9 -0
- package/skills/synap/quick-reference.md +67 -0
- package/skills/synap/reading.md +43 -0
- package/skills/synap/recap.md +21 -0
- package/skills/synap/reflexes.md +46 -0
- package/skills/synap/response-style.md +21 -0
- package/skills/synap/scope.md +13 -0
- package/skills/synap/showing.md +17 -0
- package/skills/synap/user-model.md +25 -0
- package/skills/synap/viewframe-cells.md +290 -0
- package/skills/synap/work-flow.md +113 -0
- package/skills/synap/worked-examples.md +74 -0
- package/skills/synap/workspace-design.md +45 -0
- package/skills/synap/workspace-edges.md +49 -0
- package/skills/synap/writes.md +213 -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-market/SKILL.md +335 -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 +75 -0
- package/skills/synap-market/discover-and-fetch.md +59 -0
- package/skills/synap-market/governance-and-catalog.md +56 -0
- package/skills/synap-market/install.md +55 -0
- package/skills/synap-market/overview.md +51 -0
- package/skills/synap-schema/SKILL.md +97 -6
- package/skills/synap-schema/_order.txt +13 -0
- package/skills/synap-schema/_skill.yaml +22 -0
- package/skills/synap-schema/before-you-touch.md +14 -0
- package/skills/synap-schema/common-mistakes.md +9 -0
- package/skills/synap-schema/creating-profiles.md +17 -0
- package/skills/synap-schema/discover-profiles.md +24 -0
- package/skills/synap-schema/entity-scope.md +8 -0
- package/skills/synap-schema/extend-vs-create.md +31 -0
- package/skills/synap-schema/more.md +6 -0
- package/skills/synap-schema/overlay-properties.md +22 -0
- package/skills/synap-schema/profiles.md +3 -0
- package/skills/synap-schema/property-defs.md +29 -0
- package/skills/synap-schema/read-before-write.md +50 -0
- package/skills/synap-schema/relations.md +18 -0
- package/skills/synap-schema/worked-example.md +30 -0
- package/skills/synap-ui/SKILL.md +26 -2
- package/skills/synap-ui/_order.txt +13 -0
- package/skills/synap-ui/_skill.yaml +23 -0
- package/skills/synap-ui/ai-companion.md +27 -0
- package/skills/synap-ui/arranging-bento.md +8 -0
- package/skills/synap-ui/before-you-build.md +20 -0
- package/skills/synap-ui/bento-layouts.md +35 -0
- package/skills/synap-ui/common-mistakes.md +10 -0
- package/skills/synap-ui/creating-views.md +26 -0
- package/skills/synap-ui/four-builds.md +24 -0
- package/skills/synap-ui/more.md +7 -0
- package/skills/synap-ui/proposing-vs-extending.md +11 -0
- package/skills/synap-ui/view-list.md +24 -0
- package/skills/synap-ui/views.md +3 -0
- package/skills/synap-ui/worked-example.md +18 -0
- package/skills/synap-ui/workspaces.md +47 -0
- package/templates/automations/bookmark-enrichment.automation.json +44 -0
- package/templates/automations/mail-feed.automation.json +86 -0
- package/templates/automations/url-bookmark-capture.automation.json +70 -0
- package/templates/skills/README.md +21 -0
- package/templates/skills/authoring-automations/SKILL.md +95 -0
- package/templates/skills/authoring-automations/reference/node-matrix.md +49 -0
- package/templates/skills/authoring-capabilities/SKILL.md +93 -0
- package/templates/skills/authoring-capabilities/reference/template-anatomy.md +70 -0
- package/templates/skills/authoring-playbooks/SKILL.md +102 -0
- package/templates/skills/authoring-playbooks/reference/stages-contract.md +35 -0
- package/templates/skills/authoring-playbooks/reference/worked-example.md +64 -0
- package/templates/skills/bookmark-enrichment/SKILL.md +40 -0
- package/templates/skills/coordinating-agents/SKILL.md +32 -0
- package/templates/skills/coordinating-branches/SKILL.md +27 -0
- package/templates/skills/mapping-the-server/SKILL.md +54 -0
- package/templates/skills/reading-client-progress/SKILL.md +65 -0
- package/templates/skills/routing-to-channels/SKILL.md +54 -0
- package/templates/skills/stellar-scf-grants-advisor/ASSOCIATE-CHEATSHEET.md +133 -0
- package/templates/skills/stellar-scf-grants-advisor/ASSOCIATE-ONBOARDING-GUIDE.md +761 -0
- package/templates/skills/stellar-scf-grants-advisor/CLIENT-FAQ.md +181 -0
- package/templates/skills/stellar-scf-grants-advisor/PARTNER-HANDOVER.md +130 -0
- package/templates/skills/stellar-scf-grants-advisor/SKILL.md +102 -0
- package/templates/skills/stellar-scf-grants-advisor/examples/README.md +43 -0
- package/templates/skills/stellar-scf-grants-advisor/examples/chat-summary-templates.md +158 -0
- package/templates/skills/stellar-scf-grants-advisor/examples/corpus-comparable-rows.md +136 -0
- package/templates/skills/stellar-scf-grants-advisor/examples/q10-team-rewrites.md +109 -0
- package/templates/skills/stellar-scf-grants-advisor/examples/q2-description-rewrites.md +88 -0
- package/templates/skills/stellar-scf-grants-advisor/examples/q6-integration-rewrites.md +107 -0
- package/templates/skills/stellar-scf-grants-advisor/examples/tranche-structures.md +176 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/01-methodology.md +96 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/02-scoring-framework.md +199 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/03-rejection-patterns.md +241 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/04-winner-patterns.md +120 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/05-comeback-patterns.md +92 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/06-budget-benchmarks.md +125 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/07-integration-list.md +147 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/08-abstract-fields.md +258 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/09-build-fields.md +259 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/10-corpus-winners.md +135 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/11-corpus-rejects.md +267 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/12-client-communication.md +131 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/13-excel-output-spec.md +199 -0
- package/templates/skills/stellar-scf-grants-advisor/reference/14-worked-examples.md +160 -0
- package/templates/skills/stellar-scf-grants-advisor/templates/abstract-review-builder.py +253 -0
- package/templates/skills/stellar-scf-grants-advisor/templates/build-review-builder.py +213 -0
- package/dist/commands/connectors.d.ts +0 -31
- package/dist/commands/connectors.js +0 -276
- package/dist/commands/connectors.js.map +0 -1
- package/dist/commands/finish.d.ts +0 -25
- package/dist/commands/finish.js +0 -274
- package/dist/commands/finish.js.map +0 -1
- package/dist/commands/openclaw.d.ts +0 -40
- package/dist/commands/openclaw.js +0 -1148
- package/dist/commands/openclaw.js.map +0 -1
- package/dist/commands/security-audit.d.ts +0 -12
- package/dist/commands/security-audit.js +0 -100
- package/dist/commands/security-audit.js.map +0 -1
- package/dist/lib/hardening.d.ts +0 -18
- package/dist/lib/hardening.js +0 -214
- package/dist/lib/hardening.js.map +0 -1
|
@@ -0,0 +1,790 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `synap launch` — nothing → a working company.
|
|
3
|
+
*
|
|
4
|
+
* This is NOT a template browser. It is the opinionated, guided, run-once
|
|
5
|
+
* command that turns an empty pod into a company: one PROJECT (the cross-cutting
|
|
6
|
+
* lens) + N domain WORKSPACES, provisioned from templates and linked together.
|
|
7
|
+
* Browsing "what else could I add?" lives in the marketplace UI; `synap orient`
|
|
8
|
+
* shows what you already have.
|
|
9
|
+
*
|
|
10
|
+
* Interactive flow:
|
|
11
|
+
* 1. Ask the company/project name → attach to an existing PROJECT or create one
|
|
12
|
+
* 2. Describe the company → suggest domains → pick/adjust the set
|
|
13
|
+
* 3. Disclose the FULL plan (incl. dependencies) → confirm
|
|
14
|
+
* 4. For each template → POST /packages/apply → report the real outcome
|
|
15
|
+
*
|
|
16
|
+
* Discovery is TWO-SOURCE, merged through the ONE `mergeCatalog` door:
|
|
17
|
+
* • PUBLIC templates ship BUNDLED in @synap-core/workspace-templates — always
|
|
18
|
+
* present, zero network, offline.
|
|
19
|
+
* • PRIVATE templates live in the control plane behind the user's login and are
|
|
20
|
+
* fetched from `GET /api/packages/mine` (Bearer). Logged out or CP-unreachable
|
|
21
|
+
* degrades HONESTLY to bundled-only + a "private needs login" note — it never
|
|
22
|
+
* fails open empty.
|
|
23
|
+
* The bundle still carries the private YAMLs today, so a private template that
|
|
24
|
+
* arrives as a remote entry is installed from the identical, offline bundled
|
|
25
|
+
* definition; once a later wave drops those YAMLs it is installed from the CP.
|
|
26
|
+
*
|
|
27
|
+
* Dependencies are resolved SERVER-side: `toPackageDefinition` carries the
|
|
28
|
+
* template's `dependencies` into the body, and the apply endpoint ensures each
|
|
29
|
+
* one is present first (installing missing bases topologically) and LAYERS a
|
|
30
|
+
* `compose` template onto its base instead of creating a second workspace. So
|
|
31
|
+
* this command's job is to DISCLOSE the graph before the user commits — never to
|
|
32
|
+
* install the extras itself.
|
|
33
|
+
*
|
|
34
|
+
* Installs are GOVERNED: /packages/apply answers 202 `{status:"proposed"}` when
|
|
35
|
+
* the write needs review. That is normal, not a failure — but it means nothing is
|
|
36
|
+
* live until the proposal is approved, so we never print "ready" for it.
|
|
37
|
+
*/
|
|
38
|
+
import prompts from "prompts";
|
|
39
|
+
import ora from "ora";
|
|
40
|
+
import chalk from "chalk";
|
|
41
|
+
import { listPublicTemplates, listWorkspaceTemplates, mergeCatalog, toPackageDefinition, } from "@synap-core/workspace-templates";
|
|
42
|
+
import { log, banner } from "../utils/logger.js";
|
|
43
|
+
import { resolveHubConfig, hubPost, renderHubError, } from "../lib/hub-client.js";
|
|
44
|
+
import { writeGovernance } from "../lib/capture-lane.js";
|
|
45
|
+
import { fetchProjects } from "../lib/project.js";
|
|
46
|
+
import { renderNextSteps, FLOW } from "../lib/next-steps.js";
|
|
47
|
+
import { getStoredToken } from "../lib/auth.js";
|
|
48
|
+
import { fetchRemoteCatalog, fetchPackageDefinition, } from "../lib/cp-packages.js";
|
|
49
|
+
import { fetchInstalledSlugs } from "../lib/installed.js";
|
|
50
|
+
import { isSuite } from "../lib/suite.js";
|
|
51
|
+
import { bundledTemplatesVersion } from "../lib/bundle-version.js";
|
|
52
|
+
// Provisioning a template writes profiles, properties, views, bento layouts and
|
|
53
|
+
// seed entities in one call — `life-os` alone carries 21 profiles and 25 views.
|
|
54
|
+
// The 15s CRUD default turns a legitimately-slow apply into a phantom failure,
|
|
55
|
+
// so this door opts into the longer budget (same precedent as `cap run`).
|
|
56
|
+
const APPLY_TIMEOUT_MS = 120_000;
|
|
57
|
+
async function buildCatalog(filters) {
|
|
58
|
+
// Installed-awareness is best-effort and runs alongside the CP fetch: a pod
|
|
59
|
+
// that is unconfigured/unreachable degrades to an empty set (no markers),
|
|
60
|
+
// never a failure — discovery works offline.
|
|
61
|
+
const [remote, installed] = await Promise.all([
|
|
62
|
+
fetchRemoteCatalog(filters),
|
|
63
|
+
fetchInstalledSlugs(),
|
|
64
|
+
]);
|
|
65
|
+
const merged = mergeCatalog({
|
|
66
|
+
// PUBLIC = bundle; PRIVATE = CP. Passing the public-only list (18) is what
|
|
67
|
+
// makes this survive a later wave that drops the private YAMLs: they simply
|
|
68
|
+
// arrive as remote entries instead of vanishing.
|
|
69
|
+
bundled: listPublicTemplates(),
|
|
70
|
+
remote: remote.rows,
|
|
71
|
+
remoteStatus: remote.status,
|
|
72
|
+
bundleVersion: bundledTemplatesVersion(),
|
|
73
|
+
});
|
|
74
|
+
return {
|
|
75
|
+
entries: merged.entries,
|
|
76
|
+
entryMap: new Map(merged.entries.map((e) => [e.slug, e])),
|
|
77
|
+
sources: merged.sources,
|
|
78
|
+
remote,
|
|
79
|
+
yaml: new Map(listWorkspaceTemplates().map((t) => [t.meta.slug, t])),
|
|
80
|
+
installed,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
/** Is this entry already installed on the active pod? (B3) */
|
|
84
|
+
function isInstalled(slug, cat) {
|
|
85
|
+
return cat.installed.has(slug);
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Client-side filter for the merged catalog. The CP browse route already applies
|
|
89
|
+
* `search`/`category` to REMOTE rows; bundled entries (all `workspace` category)
|
|
90
|
+
* need the same filter applied here so `--list --type`/`--search` behaves
|
|
91
|
+
* uniformly across both sources.
|
|
92
|
+
*/
|
|
93
|
+
function matchesFilters(entry, filters) {
|
|
94
|
+
if (!filters)
|
|
95
|
+
return true;
|
|
96
|
+
if (filters.category && (entry.category ?? "workspace") !== filters.category)
|
|
97
|
+
return false;
|
|
98
|
+
if (filters.search) {
|
|
99
|
+
const q = filters.search.toLowerCase();
|
|
100
|
+
const hay = `${entry.slug} ${entry.name} ${entry.description ?? ""}`.toLowerCase();
|
|
101
|
+
if (!hay.includes(q))
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
/** The `compose` dependency, if any — the base this template LAYERS onto. */
|
|
107
|
+
function composeBase(y) {
|
|
108
|
+
return y.dependencies?.find((d) => d.relation === "compose")?.slug;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* An overlay adds no workspace of its own — it layers onto its base. Only the
|
|
112
|
+
* bundled YAML declares this; a remote-only entry has no local graph, so it is
|
|
113
|
+
* treated as a plain workspace (the apply resolver figures out the rest).
|
|
114
|
+
*/
|
|
115
|
+
function isOverlay(slug, cat) {
|
|
116
|
+
const y = cat.yaml.get(slug);
|
|
117
|
+
return y ? composeBase(y) !== undefined : false;
|
|
118
|
+
}
|
|
119
|
+
function isPrivate(entry) {
|
|
120
|
+
return entry.isPrivate;
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Picker-facing display labels (user-ratified 2026-07-18) — presentation-only
|
|
124
|
+
* overrides; the template package's own names change at its next publish.
|
|
125
|
+
* `--list` intentionally keeps showing the raw catalog names.
|
|
126
|
+
*/
|
|
127
|
+
const DISPLAY_LABELS = {
|
|
128
|
+
foundation: "Strategy & Identity",
|
|
129
|
+
"internal-runbook": "Operations Manual",
|
|
130
|
+
social: "Social Media",
|
|
131
|
+
};
|
|
132
|
+
function displayName(slug, cat) {
|
|
133
|
+
return (DISPLAY_LABELS[slug] ??
|
|
134
|
+
cat.entryMap.get(slug)?.name ??
|
|
135
|
+
cat.yaml.get(slug)?.meta.name ??
|
|
136
|
+
slug);
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Transitive closure of every template a selection drags in, excluding the
|
|
140
|
+
* selection itself. Walks BOTH `compose` and `require` edges, because the apply
|
|
141
|
+
* resolver installs the whole graph — e.g. `blockchain-ecosystem` composes onto
|
|
142
|
+
* `ecosystem`, which in turn requires `foundation`. Only bundled YAML carries
|
|
143
|
+
* dependency edges; remote-only entries contribute none (server resolves them).
|
|
144
|
+
*/
|
|
145
|
+
function resolveExtras(selected, cat) {
|
|
146
|
+
const seen = new Set();
|
|
147
|
+
const walk = (slug) => {
|
|
148
|
+
for (const dep of cat.yaml.get(slug)?.dependencies ?? []) {
|
|
149
|
+
if (seen.has(dep.slug))
|
|
150
|
+
continue;
|
|
151
|
+
seen.add(dep.slug);
|
|
152
|
+
walk(dep.slug);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
for (const s of selected)
|
|
156
|
+
walk(s);
|
|
157
|
+
for (const s of selected)
|
|
158
|
+
seen.delete(s);
|
|
159
|
+
return [...seen];
|
|
160
|
+
}
|
|
161
|
+
/** What picking this template actually does, in the user's words. */
|
|
162
|
+
function consequence(slug, cat) {
|
|
163
|
+
const y = cat.yaml.get(slug);
|
|
164
|
+
if (!y)
|
|
165
|
+
return "";
|
|
166
|
+
const parts = [];
|
|
167
|
+
const base = composeBase(y);
|
|
168
|
+
if (base)
|
|
169
|
+
parts.push(`layers onto ${displayName(base, cat)}`);
|
|
170
|
+
const extras = resolveExtras([slug], cat).filter((s) => s !== base);
|
|
171
|
+
if (extras.length > 0)
|
|
172
|
+
parts.push(`also sets up: ${extras.map((s) => displayName(s, cat)).join(", ")}`);
|
|
173
|
+
return parts.join(" · ");
|
|
174
|
+
}
|
|
175
|
+
/** Terse "what you get" line. Derived from the bundled YAML when we have it. */
|
|
176
|
+
function contents(entry, cat) {
|
|
177
|
+
const y = cat.yaml.get(entry.slug);
|
|
178
|
+
if (!y)
|
|
179
|
+
return "workspace template"; // remote-only — counts unknown until install
|
|
180
|
+
const p = y.profiles?.length ?? 0;
|
|
181
|
+
const v = y.views?.length ?? 0;
|
|
182
|
+
return `${p} profile${p === 1 ? "" : "s"} · ${v} view${v === 1 ? "" : "s"}`;
|
|
183
|
+
}
|
|
184
|
+
function clip(s, n) {
|
|
185
|
+
return s.length <= n ? s : s.slice(0, n - 1).trimEnd() + "…";
|
|
186
|
+
}
|
|
187
|
+
/**
|
|
188
|
+
* Suggest domains from a free-text company description. Matches the user's words
|
|
189
|
+
* against each merged entry's `tags` — no hardcoded keyword catalog, so new
|
|
190
|
+
* templates (bundled OR private) become suggestable the moment they declare tags.
|
|
191
|
+
*/
|
|
192
|
+
function inferDomains(description, cat) {
|
|
193
|
+
const text = description.toLowerCase();
|
|
194
|
+
const matched = [];
|
|
195
|
+
for (const entry of cat.entries) {
|
|
196
|
+
const tags = (entry.tags ?? []).filter((k) => k.length >= 3);
|
|
197
|
+
const hit = tags.some((k) => new RegExp(`\\b${k.toLowerCase().replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`).test(text));
|
|
198
|
+
if (hit)
|
|
199
|
+
matched.push(entry.slug);
|
|
200
|
+
}
|
|
201
|
+
// Always land on something sensible rather than an empty picker.
|
|
202
|
+
return matched.length > 0 ? matched : ["project-management"];
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* The ONE honest line about the remote (private) source's health — the reason
|
|
206
|
+
* the merge door returns `sources`. `null` when everything resolved cleanly and
|
|
207
|
+
* there is nothing to disclose. NEVER silence a degradation (the fails-open-empty
|
|
208
|
+
* trap where an agent sees an empty catalog and thinks the pod is empty).
|
|
209
|
+
*/
|
|
210
|
+
function remoteNote(remote) {
|
|
211
|
+
switch (remote.status) {
|
|
212
|
+
case "ok":
|
|
213
|
+
return remote.privateCount > 0
|
|
214
|
+
? `${remote.privateCount} private template${remote.privateCount === 1 ? "" : "s"} from your account${remote.email ? ` (${remote.email})` : ""}.`
|
|
215
|
+
: null;
|
|
216
|
+
case "unauthenticated":
|
|
217
|
+
return remote.loggedIn
|
|
218
|
+
? "Your session expired — run 'synap login' to see your private templates."
|
|
219
|
+
: "Private templates are available after 'synap login'.";
|
|
220
|
+
case "unreachable":
|
|
221
|
+
return "Couldn't reach the control plane — showing bundled templates only. Your private templates need it.";
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
/**
|
|
225
|
+
* Status badges for an entry — data-driven, no hardcoded slugs. `suite` from the
|
|
226
|
+
* tag (`isSuite`); `installed` from the pod (B3); `requiredTier`/price and
|
|
227
|
+
* `locked` from the CP browse row (B4).
|
|
228
|
+
*/
|
|
229
|
+
function entryBadges(entry, cat) {
|
|
230
|
+
const badges = [];
|
|
231
|
+
if (isSuite(entry))
|
|
232
|
+
badges.push(chalk.magenta("suite"));
|
|
233
|
+
if (isPrivate(entry))
|
|
234
|
+
badges.push(chalk.yellow("private"));
|
|
235
|
+
if (isInstalled(entry.slug, cat))
|
|
236
|
+
badges.push(chalk.green("installed"));
|
|
237
|
+
const tier = cat.remote.tierBySlug.get(entry.slug);
|
|
238
|
+
if (tier?.requiredTier)
|
|
239
|
+
badges.push(chalk.cyan(tier.requiredTier));
|
|
240
|
+
else if (tier?.pricingModel && tier.pricingModel !== "free")
|
|
241
|
+
badges.push(chalk.cyan(tier.pricingModel));
|
|
242
|
+
if (cat.remote.lockedSlugs.has(entry.slug))
|
|
243
|
+
badges.push(chalk.red("locked"));
|
|
244
|
+
return badges;
|
|
245
|
+
}
|
|
246
|
+
// ── `synap launch --list` — what CAN be launched, without committing ─────────
|
|
247
|
+
export async function launchList(opts) {
|
|
248
|
+
const filters = opts.search || opts.type ? { search: opts.search, category: opts.type } : undefined;
|
|
249
|
+
const cat = await buildCatalog(filters);
|
|
250
|
+
const { remote } = cat;
|
|
251
|
+
const entries = cat.entries.filter((e) => matchesFilters(e, filters));
|
|
252
|
+
if (opts.json) {
|
|
253
|
+
console.log(JSON.stringify({
|
|
254
|
+
entries: entries.map((e) => {
|
|
255
|
+
const y = cat.yaml.get(e.slug);
|
|
256
|
+
const tier = remote.tierBySlug.get(e.slug);
|
|
257
|
+
return {
|
|
258
|
+
slug: e.slug,
|
|
259
|
+
name: e.name,
|
|
260
|
+
description: e.description,
|
|
261
|
+
tags: e.tags,
|
|
262
|
+
isPrivate: e.isPrivate,
|
|
263
|
+
suite: isSuite(e),
|
|
264
|
+
installed: isInstalled(e.slug, cat),
|
|
265
|
+
requiredTier: tier?.requiredTier ?? null,
|
|
266
|
+
pricingModel: tier?.pricingModel ?? null,
|
|
267
|
+
locked: remote.lockedSlugs.has(e.slug),
|
|
268
|
+
source: e.source,
|
|
269
|
+
kind: isOverlay(e.slug, cat) ? "overlay" : "workspace",
|
|
270
|
+
profiles: y?.profiles?.length ?? null,
|
|
271
|
+
views: y?.views?.length ?? null,
|
|
272
|
+
dependencies: y?.dependencies ?? [],
|
|
273
|
+
};
|
|
274
|
+
}),
|
|
275
|
+
sources: cat.sources,
|
|
276
|
+
remote: {
|
|
277
|
+
status: remote.status,
|
|
278
|
+
loggedIn: remote.loggedIn,
|
|
279
|
+
privateCount: remote.privateCount,
|
|
280
|
+
},
|
|
281
|
+
}, null, 2));
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
const render = (entry) => {
|
|
285
|
+
const badges = entryBadges(entry, cat);
|
|
286
|
+
console.log(" " +
|
|
287
|
+
chalk.cyan(entry.slug.padEnd(22)) +
|
|
288
|
+
chalk.bold(entry.name) +
|
|
289
|
+
(badges.length ? " " + badges.join(" ") : ""));
|
|
290
|
+
console.log(" " + " ".repeat(22) + chalk.dim(clip(entry.description ?? "", 68)));
|
|
291
|
+
const why = consequence(entry.slug, cat);
|
|
292
|
+
console.log(" " +
|
|
293
|
+
" ".repeat(22) +
|
|
294
|
+
chalk.dim(contents(entry, cat)) +
|
|
295
|
+
(why ? chalk.dim(" · " + why) : ""));
|
|
296
|
+
log.blank();
|
|
297
|
+
};
|
|
298
|
+
if (entries.length === 0) {
|
|
299
|
+
log.warn("No templates match that filter.");
|
|
300
|
+
log.dim("Try 'synap launch --list' with no filter, or 'synap market --list' for all package types.");
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const workspaces = entries.filter((e) => !isOverlay(e.slug, cat));
|
|
304
|
+
const overlays = entries.filter((e) => isOverlay(e.slug, cat));
|
|
305
|
+
if (workspaces.length > 0) {
|
|
306
|
+
log.heading(" Domain workspaces — each creates its own workspace");
|
|
307
|
+
log.blank();
|
|
308
|
+
workspaces.forEach(render);
|
|
309
|
+
}
|
|
310
|
+
if (overlays.length > 0) {
|
|
311
|
+
log.heading(" Overlays — layer onto an existing workspace (no new workspace)");
|
|
312
|
+
log.blank();
|
|
313
|
+
overlays.forEach(render);
|
|
314
|
+
}
|
|
315
|
+
log.dim(`${entries.length} template${entries.length === 1 ? "" : "s"} available.`);
|
|
316
|
+
const note = remoteNote(remote);
|
|
317
|
+
if (note)
|
|
318
|
+
log.dim(note);
|
|
319
|
+
log.dim("Run 'synap launch' to set one up · 'synap orient' to see what you already have.");
|
|
320
|
+
}
|
|
321
|
+
// ── Intent pickers — the flow shows the RIGHT thing per intent ───────────────
|
|
322
|
+
// Not a flat wall of 24 templates. A SUITE is the headline for a company; its
|
|
323
|
+
// constituent lenses are opt-OUT customization UNDER it, never flat peers; bases
|
|
324
|
+
// (foundation) and overlays (grants, internal-runbook) are implementation
|
|
325
|
+
// details a suite pulls in — they surface only in "Custom".
|
|
326
|
+
/**
|
|
327
|
+
* The preferred public headline suite for a company — a TIEBREAKER, not an
|
|
328
|
+
* allowlist. "Is this a suite?" is derived from the `suite` tag via `isSuite()`
|
|
329
|
+
* (data-driven); this only decides WHICH public suite headlines when several are
|
|
330
|
+
* tagged. enterprise-os stays the canonical company OS.
|
|
331
|
+
*/
|
|
332
|
+
const PREFERRED_COMPANY_SUITE = "enterprise-os";
|
|
333
|
+
/** Personal-knowledge default (falls back to life-os if personal isn't bundled). */
|
|
334
|
+
const PERSONAL_SLUGS = ["personal", "life-os"];
|
|
335
|
+
/**
|
|
336
|
+
* The public headline suite for a company — a suite-tagged, public entry
|
|
337
|
+
* (`isSuite && !isPrivate`), preferring `enterprise-os` when several are tagged.
|
|
338
|
+
* Replaces the hardcoded `COMPANY_SUITE` slug.
|
|
339
|
+
*/
|
|
340
|
+
function publicCompanySuite(cat) {
|
|
341
|
+
const suites = cat.entries.filter((e) => isSuite(e) && !isPrivate(e));
|
|
342
|
+
return suites.find((e) => e.slug === PREFERRED_COMPANY_SUITE) ?? suites[0];
|
|
343
|
+
}
|
|
344
|
+
/**
|
|
345
|
+
* The private flagship suite offered on top — any suite-tagged PRIVATE entry
|
|
346
|
+
* (resolves only when the account owns it). Replaces the hardcoded
|
|
347
|
+
* `PRIVATE_SUITE` slug.
|
|
348
|
+
*/
|
|
349
|
+
function privateSuite(cat) {
|
|
350
|
+
return cat.entries.find((e) => isSuite(e) && isPrivate(e));
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Company intent → the suite is the headline. "Use the full suite" installs it
|
|
354
|
+
* as ONE apply (the pod resolves its lenses); "pick lenses" installs the chosen
|
|
355
|
+
* constituents directly. Required bases (foundation) are pulled back in by the
|
|
356
|
+
* resolver and shown in the disclose step, so a de-selected base is never lost.
|
|
357
|
+
* If the private flagship suite is actually available to this account, offer it.
|
|
358
|
+
*/
|
|
359
|
+
async function pickCompany(cat) {
|
|
360
|
+
const suite = publicCompanySuite(cat);
|
|
361
|
+
if (!suite)
|
|
362
|
+
return [];
|
|
363
|
+
const suiteSlug = suite.slug;
|
|
364
|
+
const suiteName = suite.name;
|
|
365
|
+
const lenses = (cat.yaml.get(suiteSlug)?.dependencies ?? [])
|
|
366
|
+
.filter((d) => (d.relation ?? "require") === "require")
|
|
367
|
+
.map((d) => d.slug);
|
|
368
|
+
const { scope } = await prompts({
|
|
369
|
+
type: "select",
|
|
370
|
+
name: "scope",
|
|
371
|
+
message: `${suiteName} — ${lenses.map((s) => displayName(s, cat)).join(", ")}`,
|
|
372
|
+
choices: [
|
|
373
|
+
{
|
|
374
|
+
title: `Use the full ${suiteName}`,
|
|
375
|
+
description: "Everything, wired together — recommended",
|
|
376
|
+
value: "full",
|
|
377
|
+
},
|
|
378
|
+
{
|
|
379
|
+
title: "Pick which lenses",
|
|
380
|
+
description: "Choose which parts to install",
|
|
381
|
+
value: "pick",
|
|
382
|
+
},
|
|
383
|
+
],
|
|
384
|
+
});
|
|
385
|
+
if (!scope)
|
|
386
|
+
return [];
|
|
387
|
+
let selected;
|
|
388
|
+
if (scope === "full") {
|
|
389
|
+
selected = [suiteSlug];
|
|
390
|
+
}
|
|
391
|
+
else {
|
|
392
|
+
const { picked } = await prompts({
|
|
393
|
+
type: "multiselect",
|
|
394
|
+
name: "picked",
|
|
395
|
+
message: "Which lenses? (space to toggle)",
|
|
396
|
+
choices: lenses.map((s) => ({
|
|
397
|
+
title: displayName(s, cat) +
|
|
398
|
+
chalk.dim(` — ${clip(cat.yaml.get(s)?.meta?.description ?? "", 56)}`),
|
|
399
|
+
value: s,
|
|
400
|
+
selected: true,
|
|
401
|
+
})),
|
|
402
|
+
hint: "- Space to toggle. Return to submit. Required bases are added automatically.",
|
|
403
|
+
});
|
|
404
|
+
selected = picked ?? [];
|
|
405
|
+
if (selected.length === 0)
|
|
406
|
+
return [];
|
|
407
|
+
}
|
|
408
|
+
// Private flagship layer — offered ONLY when a suite-tagged PRIVATE entry
|
|
409
|
+
// actually resolved for this account (CP-logged-in as its owner). Absent
|
|
410
|
+
// otherwise, honestly. Detected by tag, not a hardcoded slug.
|
|
411
|
+
const arch = privateSuite(cat);
|
|
412
|
+
if (arch) {
|
|
413
|
+
const { addArch } = await prompts({
|
|
414
|
+
type: "confirm",
|
|
415
|
+
name: "addArch",
|
|
416
|
+
message: `Add your private flagships — ${arch.name}?`,
|
|
417
|
+
initial: false,
|
|
418
|
+
});
|
|
419
|
+
if (addArch)
|
|
420
|
+
selected.push(arch.slug);
|
|
421
|
+
}
|
|
422
|
+
return selected;
|
|
423
|
+
}
|
|
424
|
+
/**
|
|
425
|
+
* Custom intent → the full flat list (the power-user escape hatch). Keeps the
|
|
426
|
+
* describe-inference suggestion and the overlay-last ordering the flow had.
|
|
427
|
+
*/
|
|
428
|
+
async function pickCustom(cat, description) {
|
|
429
|
+
const suggested = inferDomains(description, cat);
|
|
430
|
+
log.blank();
|
|
431
|
+
log.info(`Based on that, I suggest: ${chalk.cyan(suggested.map((s) => displayName(s, cat)).join(", "))}`);
|
|
432
|
+
// `prompts` only renders `description` for the FOCUSED choice — so what you
|
|
433
|
+
// get, and what it drags in, goes in the always-visible title.
|
|
434
|
+
const choice = (entry) => {
|
|
435
|
+
const why = consequence(entry.slug, cat);
|
|
436
|
+
return {
|
|
437
|
+
title: `${DISPLAY_LABELS[entry.slug] ?? entry.name}${isPrivate(entry) ? chalk.yellow(" (private)") : ""}` +
|
|
438
|
+
`${isInstalled(entry.slug, cat) ? chalk.green(" [installed]") : ""}` +
|
|
439
|
+
chalk.dim(` — ${contents(entry, cat)}`) +
|
|
440
|
+
(why ? chalk.dim(` · ${why}`) : ""),
|
|
441
|
+
description: clip(entry.description ?? "", 72),
|
|
442
|
+
value: entry.slug,
|
|
443
|
+
selected: suggested.includes(entry.slug),
|
|
444
|
+
};
|
|
445
|
+
};
|
|
446
|
+
const { domains } = await prompts({
|
|
447
|
+
type: "multiselect",
|
|
448
|
+
name: "domains",
|
|
449
|
+
message: "Which workspaces do you want? (space to toggle, enter to confirm)",
|
|
450
|
+
choices: [
|
|
451
|
+
...cat.entries.filter((e) => !isOverlay(e.slug, cat)).map(choice),
|
|
452
|
+
...cat.entries.filter((e) => isOverlay(e.slug, cat)).map(choice),
|
|
453
|
+
],
|
|
454
|
+
hint: "- Space to select. Return to submit",
|
|
455
|
+
});
|
|
456
|
+
return domains ?? [];
|
|
457
|
+
}
|
|
458
|
+
// ── `synap launch` — the guided flow ─────────────────────────────────────────
|
|
459
|
+
export async function launch(opts) {
|
|
460
|
+
// Discovery answers "what can I launch?" — bundled public always, plus the
|
|
461
|
+
// user's private CP templates when logged in. Never touches the pod.
|
|
462
|
+
if (opts.list) {
|
|
463
|
+
await launchList(opts);
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
if (!opts.json)
|
|
467
|
+
banner();
|
|
468
|
+
const cat = await buildCatalog();
|
|
469
|
+
if (!opts.json) {
|
|
470
|
+
const note = remoteNote(cat.remote);
|
|
471
|
+
if (note)
|
|
472
|
+
log.dim(note);
|
|
473
|
+
}
|
|
474
|
+
let cfg;
|
|
475
|
+
try {
|
|
476
|
+
cfg = await resolveHubConfig(opts);
|
|
477
|
+
}
|
|
478
|
+
catch (e) {
|
|
479
|
+
renderHubError(e);
|
|
480
|
+
process.exit(1);
|
|
481
|
+
}
|
|
482
|
+
// ── 1. Where should this live? Project is OPTIONAL ──────────────────────
|
|
483
|
+
// Launching an OS provisions its WORKSPACES pod-wide, and their contents are
|
|
484
|
+
// workspace-level — so a project is NOT required (on a team/company pod, the
|
|
485
|
+
// pod IS the company). Default is pod-wide; a project is an opt-in
|
|
486
|
+
// cross-cutting lens the user can attach or create, never mandatory.
|
|
487
|
+
const existingProjects = await fetchProjects(cfg);
|
|
488
|
+
const POD_WIDE = "__pod_wide__";
|
|
489
|
+
const NEW_PROJECT = "__new__";
|
|
490
|
+
let attachProjectId;
|
|
491
|
+
let projectName;
|
|
492
|
+
const { where } = await prompts({
|
|
493
|
+
type: "select",
|
|
494
|
+
name: "where",
|
|
495
|
+
message: "Where should this live?",
|
|
496
|
+
choices: [
|
|
497
|
+
{
|
|
498
|
+
title: "Pod-wide — the whole pod (recommended)",
|
|
499
|
+
description: "Best for a company/team pod, where the pod IS the company",
|
|
500
|
+
value: POD_WIDE,
|
|
501
|
+
},
|
|
502
|
+
...existingProjects.map((p) => ({
|
|
503
|
+
title: `Project: ${p.name} ${chalk.dim(p.id.slice(0, 8))}`,
|
|
504
|
+
value: p.id,
|
|
505
|
+
})),
|
|
506
|
+
{
|
|
507
|
+
title: chalk.cyan("+ Create a new project"),
|
|
508
|
+
description: "An optional cross-cutting lens over what you install",
|
|
509
|
+
value: NEW_PROJECT,
|
|
510
|
+
},
|
|
511
|
+
],
|
|
512
|
+
initial: 0,
|
|
513
|
+
});
|
|
514
|
+
if (!where) {
|
|
515
|
+
log.warn("Cancelled.");
|
|
516
|
+
return;
|
|
517
|
+
}
|
|
518
|
+
if (where === NEW_PROJECT) {
|
|
519
|
+
const { name } = await prompts({
|
|
520
|
+
type: "text",
|
|
521
|
+
name: "name",
|
|
522
|
+
message: "New project name:",
|
|
523
|
+
});
|
|
524
|
+
if (!name) {
|
|
525
|
+
log.warn("Cancelled.");
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
projectName = name; // created below
|
|
529
|
+
}
|
|
530
|
+
else if (where !== POD_WIDE) {
|
|
531
|
+
attachProjectId = where;
|
|
532
|
+
const chosen = existingProjects.find((p) => p.id === attachProjectId);
|
|
533
|
+
projectName = chosen?.name;
|
|
534
|
+
log.blank();
|
|
535
|
+
log.info(`Attaching new workspaces to ${chalk.bold(chosen?.name ?? attachProjectId)}.`);
|
|
536
|
+
}
|
|
537
|
+
// ── 2. Describe → infer domains ─────────────────────────────────────────
|
|
538
|
+
const { description } = await prompts({
|
|
539
|
+
type: "text",
|
|
540
|
+
name: "description",
|
|
541
|
+
message: "Describe what you do (one sentence):",
|
|
542
|
+
});
|
|
543
|
+
// ── 3. Intent — what are you setting up? ────────────────────────────────
|
|
544
|
+
// Intent-first, not a flat wall of 24 templates. A company gets the suite as
|
|
545
|
+
// the headline; personal gets a knowledge base; custom is the full list for
|
|
546
|
+
// power users. This is where foundation/overlays/niche standalones stop being
|
|
547
|
+
// confusing flat peers — they only appear under "Custom".
|
|
548
|
+
const companyAvailable = publicCompanySuite(cat) !== undefined;
|
|
549
|
+
const { intent } = await prompts({
|
|
550
|
+
type: "select",
|
|
551
|
+
name: "intent",
|
|
552
|
+
message: "What are you setting up?",
|
|
553
|
+
choices: [
|
|
554
|
+
...(companyAvailable
|
|
555
|
+
? [
|
|
556
|
+
{
|
|
557
|
+
title: "A company / full operation",
|
|
558
|
+
description: "Enterprise OS — strategy, market, operations, CRM, and content in one",
|
|
559
|
+
value: "company",
|
|
560
|
+
},
|
|
561
|
+
]
|
|
562
|
+
: []),
|
|
563
|
+
{
|
|
564
|
+
title: "Personal knowledge base",
|
|
565
|
+
description: "A private space for your notes, ideas, and thinking",
|
|
566
|
+
value: "personal",
|
|
567
|
+
},
|
|
568
|
+
{
|
|
569
|
+
title: "Custom — browse every workspace",
|
|
570
|
+
description: "Pick individual workspaces and overlays yourself",
|
|
571
|
+
value: "custom",
|
|
572
|
+
},
|
|
573
|
+
],
|
|
574
|
+
});
|
|
575
|
+
if (!intent) {
|
|
576
|
+
log.warn("Cancelled.");
|
|
577
|
+
return;
|
|
578
|
+
}
|
|
579
|
+
let selected;
|
|
580
|
+
if (intent === "personal") {
|
|
581
|
+
const slug = PERSONAL_SLUGS.find((s) => cat.yaml.has(s) || cat.entryMap.has(s));
|
|
582
|
+
if (!slug) {
|
|
583
|
+
log.warn("No personal-knowledge template is available.");
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
selected = [slug];
|
|
587
|
+
}
|
|
588
|
+
else if (intent === "company") {
|
|
589
|
+
selected = await pickCompany(cat);
|
|
590
|
+
if (selected.length === 0) {
|
|
591
|
+
log.warn("Cancelled.");
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
else {
|
|
596
|
+
selected = await pickCustom(cat, description ?? "");
|
|
597
|
+
if (selected.length === 0) {
|
|
598
|
+
log.warn("No workspaces selected. Cancelled.");
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
// ── 3b. DISCLOSE the whole plan before the user commits ──────────────────
|
|
603
|
+
// Picking "Grants" silently provisions Operations AND Foundation — the user
|
|
604
|
+
// must see that BEFORE confirming, not discover it afterwards.
|
|
605
|
+
const extras = resolveExtras(selected, cat);
|
|
606
|
+
log.blank();
|
|
607
|
+
log.heading(" This will set up:");
|
|
608
|
+
log.blank();
|
|
609
|
+
for (const slug of selected) {
|
|
610
|
+
const entry = cat.entryMap.get(slug);
|
|
611
|
+
if (!entry)
|
|
612
|
+
continue;
|
|
613
|
+
const why = consequence(slug, cat);
|
|
614
|
+
console.log(" " +
|
|
615
|
+
chalk.bold(displayName(slug, cat).padEnd(22)) +
|
|
616
|
+
chalk.dim(isOverlay(slug, cat) ? "overlay" : "new workspace"));
|
|
617
|
+
if (why)
|
|
618
|
+
console.log(" " + " ".repeat(22) + chalk.dim(why));
|
|
619
|
+
}
|
|
620
|
+
if (extras.length > 0) {
|
|
621
|
+
log.blank();
|
|
622
|
+
log.info(`Dependencies provisioned automatically: ${chalk.cyan(extras.map((s) => displayName(s, cat)).join(", "))}`);
|
|
623
|
+
}
|
|
624
|
+
log.blank();
|
|
625
|
+
log.dim("Installs are governed — some may need your approval before they go live.");
|
|
626
|
+
log.blank();
|
|
627
|
+
const { proceed } = await prompts({
|
|
628
|
+
type: "confirm",
|
|
629
|
+
name: "proceed",
|
|
630
|
+
message: "Proceed?",
|
|
631
|
+
initial: true,
|
|
632
|
+
});
|
|
633
|
+
if (!proceed) {
|
|
634
|
+
log.warn("Cancelled.");
|
|
635
|
+
return;
|
|
636
|
+
}
|
|
637
|
+
// ── 4. Resolve the project — POD-WIDE unless one was chosen/created ───────
|
|
638
|
+
// projectId stays undefined for a pod-wide launch (the default); the workspaces
|
|
639
|
+
// provision pod-wide and /packages/apply skips the entity→project link.
|
|
640
|
+
let projectId;
|
|
641
|
+
if (attachProjectId) {
|
|
642
|
+
projectId = attachProjectId;
|
|
643
|
+
}
|
|
644
|
+
else if (projectName) {
|
|
645
|
+
// "Create a new project" was chosen — mint it, then link the workspaces.
|
|
646
|
+
const spinner = ora("Creating project…").start();
|
|
647
|
+
try {
|
|
648
|
+
const project = (await hubPost("/projects", { name: projectName, description: description || undefined, status: "active" }, cfg));
|
|
649
|
+
// Governance may return a proposal instead of creating directly.
|
|
650
|
+
if (!project.id) {
|
|
651
|
+
spinner.stop();
|
|
652
|
+
const pid = project.proposalId;
|
|
653
|
+
log.warn("Project not created yet — it needs your approval first.");
|
|
654
|
+
if (pid)
|
|
655
|
+
log.hint(`Proposal ${pid} — approve it, then re-run 'synap launch'.`);
|
|
656
|
+
log.hint("Review it with: synap proposals list");
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
projectId = project.id;
|
|
660
|
+
spinner.succeed(`Project created: ${chalk.bold(projectName)}`);
|
|
661
|
+
}
|
|
662
|
+
catch (e) {
|
|
663
|
+
spinner.stop();
|
|
664
|
+
renderHubError(e);
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
const results = [];
|
|
669
|
+
// Authed detail fetches (private CP-only templates) need the Bearer token.
|
|
670
|
+
const cpToken = getStoredToken()?.token;
|
|
671
|
+
for (const slug of selected) {
|
|
672
|
+
const name = displayName(slug, cat);
|
|
673
|
+
const s = ora(`Provisioning ${name}…`).start();
|
|
674
|
+
// Install-source decision, per entry:
|
|
675
|
+
// • slug IS in the bundle → use the OFFLINE bundled definition. It is
|
|
676
|
+
// byte-for-byte the install payload the CP would serve, needs no network,
|
|
677
|
+
// and no extra authed round-trip — so we prefer it EVEN for a private
|
|
678
|
+
// template that arrived as a remote entry (its YAML still ships today).
|
|
679
|
+
// • slug is CP-only (third-party, or a future build that dropped the YAML)
|
|
680
|
+
// → the CP is the sole source; fetch the full definition (authed, so a
|
|
681
|
+
// private one resolves).
|
|
682
|
+
let pkg;
|
|
683
|
+
let source;
|
|
684
|
+
if (cat.yaml.has(slug)) {
|
|
685
|
+
try {
|
|
686
|
+
pkg = toPackageDefinition(slug);
|
|
687
|
+
source = "bundled";
|
|
688
|
+
}
|
|
689
|
+
catch {
|
|
690
|
+
s.fail(`${name} — template not found`);
|
|
691
|
+
results.push({ slug, name, outcome: "failed", error: "template not found" });
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
const def = await fetchPackageDefinition(slug, cpToken);
|
|
697
|
+
if (!def) {
|
|
698
|
+
s.fail(`${name} — couldn't fetch its definition from the control plane`);
|
|
699
|
+
results.push({
|
|
700
|
+
slug,
|
|
701
|
+
name,
|
|
702
|
+
outcome: "failed",
|
|
703
|
+
error: "remote definition unavailable",
|
|
704
|
+
});
|
|
705
|
+
continue;
|
|
706
|
+
}
|
|
707
|
+
pkg = def;
|
|
708
|
+
source = "remote";
|
|
709
|
+
}
|
|
710
|
+
try {
|
|
711
|
+
// projectId links the workspace's seed entities to the project
|
|
712
|
+
// (belongs_to_project) — this is what unifies the OS. The body carries the
|
|
713
|
+
// template's `dependencies`; the endpoint resolves them.
|
|
714
|
+
const res = (await hubPost("/packages/apply", { ...pkg, projectId }, cfg, APPLY_TIMEOUT_MS));
|
|
715
|
+
// `writeGovernance` is the one door for "did this apply, or was it queued?"
|
|
716
|
+
if (writeGovernance(res) === "proposed") {
|
|
717
|
+
const proposalId = res.proposalId ? String(res.proposalId) : undefined;
|
|
718
|
+
s.stop();
|
|
719
|
+
log.info(`${name} — proposed (under review)`);
|
|
720
|
+
results.push({ slug, name, outcome: "proposed", source, proposalId });
|
|
721
|
+
continue;
|
|
722
|
+
}
|
|
723
|
+
const ws = res.workspace;
|
|
724
|
+
if (ws?.status === "composed") {
|
|
725
|
+
const onto = ws.onto ? String(ws.onto) : undefined;
|
|
726
|
+
s.succeed(`${name} — layered onto its base workspace`);
|
|
727
|
+
results.push({ slug, name, outcome: "composed", source, workspaceId: ws.workspaceId, onto });
|
|
728
|
+
}
|
|
729
|
+
else if (ws?.status === "created") {
|
|
730
|
+
s.succeed(`${name} ready`);
|
|
731
|
+
results.push({ slug, name, outcome: "created", source, workspaceId: ws.workspaceId });
|
|
732
|
+
}
|
|
733
|
+
else {
|
|
734
|
+
// No workspace in the response and not proposed — report it, don't
|
|
735
|
+
// silently count it as a success.
|
|
736
|
+
s.fail(`${name} — pod returned no workspace`);
|
|
737
|
+
results.push({ slug, name, outcome: "failed", source, error: "no workspace in response" });
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
catch (e) {
|
|
741
|
+
s.stop();
|
|
742
|
+
log.error(`${name} failed`);
|
|
743
|
+
renderHubError(e);
|
|
744
|
+
results.push({ slug, name, outcome: "failed", source, error: e.message });
|
|
745
|
+
}
|
|
746
|
+
}
|
|
747
|
+
// ── 6. Honest summary ───────────────────────────────────────────────────
|
|
748
|
+
const projectLabel = attachProjectId
|
|
749
|
+
? existingProjects.find((p) => p.id === attachProjectId)?.name ?? projectName
|
|
750
|
+
: projectName;
|
|
751
|
+
if (opts.json) {
|
|
752
|
+
console.log(JSON.stringify({
|
|
753
|
+
projectId,
|
|
754
|
+
projectName: projectLabel,
|
|
755
|
+
attached: Boolean(attachProjectId),
|
|
756
|
+
dependencies: extras,
|
|
757
|
+
results,
|
|
758
|
+
nextSteps: FLOW.afterLaunch(projectLabel),
|
|
759
|
+
}, null, 2));
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
const live = results.filter((r) => r.outcome === "created" || r.outcome === "composed");
|
|
763
|
+
const proposed = results.filter((r) => r.outcome === "proposed");
|
|
764
|
+
const failed = results.filter((r) => r.outcome === "failed");
|
|
765
|
+
log.blank();
|
|
766
|
+
if (live.length > 0) {
|
|
767
|
+
log.success(projectLabel
|
|
768
|
+
? `${live.length}/${results.length} live under project ${chalk.bold(projectLabel)}.`
|
|
769
|
+
: `${live.length}/${results.length} live (pod-wide).`);
|
|
770
|
+
}
|
|
771
|
+
if (proposed.length > 0) {
|
|
772
|
+
// A proposal is a PR under review, not a failure — but it is NOT live, and
|
|
773
|
+
// saying "ready" here is the lie this command used to tell.
|
|
774
|
+
log.blank();
|
|
775
|
+
log.warn(`${proposed.length} ${proposed.length === 1 ? "workspace is" : "workspaces are"} NOT live yet — governance queued ${proposed.length === 1 ? "it" : "them"} for your approval:`);
|
|
776
|
+
for (const r of proposed) {
|
|
777
|
+
log.hint(`${r.name}${r.proposalId ? ` — proposal ${r.proposalId}` : ""}`);
|
|
778
|
+
}
|
|
779
|
+
log.hint("Approve with: synap proposals list → synap proposals approve <id>");
|
|
780
|
+
log.hint("(or review them in the browser under Proposals)");
|
|
781
|
+
}
|
|
782
|
+
if (failed.length > 0) {
|
|
783
|
+
log.blank();
|
|
784
|
+
log.warn(`${failed.length} failed: ${failed.map((r) => r.name).join(", ")}`);
|
|
785
|
+
}
|
|
786
|
+
if (live.length > 0 && proposed.length === 0 && failed.length === 0) {
|
|
787
|
+
renderNextSteps(FLOW.afterLaunch(projectLabel));
|
|
788
|
+
}
|
|
789
|
+
}
|
|
790
|
+
//# sourceMappingURL=launch.js.map
|