@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.
Files changed (372) hide show
  1. package/dist/commands/agent-chat.d.ts +47 -0
  2. package/dist/commands/agent-chat.js +250 -0
  3. package/dist/commands/agent-chat.js.map +1 -0
  4. package/dist/commands/agent-run.js +3 -4
  5. package/dist/commands/agent-run.js.map +1 -1
  6. package/dist/commands/agents.js +85 -46
  7. package/dist/commands/agents.js.map +1 -1
  8. package/dist/commands/automation.js +2 -1
  9. package/dist/commands/automation.js.map +1 -1
  10. package/dist/commands/bridge-setup.d.ts +66 -0
  11. package/dist/commands/bridge-setup.js +1112 -0
  12. package/dist/commands/bridge-setup.js.map +1 -0
  13. package/dist/commands/cap-connections.d.ts +28 -0
  14. package/dist/commands/cap-connections.js +224 -0
  15. package/dist/commands/cap-connections.js.map +1 -0
  16. package/dist/commands/capability.d.ts +89 -0
  17. package/dist/commands/capability.js +1594 -0
  18. package/dist/commands/capability.js.map +1 -0
  19. package/dist/commands/centrality.d.ts +20 -0
  20. package/dist/commands/centrality.js +95 -0
  21. package/dist/commands/centrality.js.map +1 -0
  22. package/dist/commands/connect.d.ts +2 -0
  23. package/dist/commands/connect.js +16 -35
  24. package/dist/commands/connect.js.map +1 -1
  25. package/dist/commands/context.js +6 -7
  26. package/dist/commands/context.js.map +1 -1
  27. package/dist/commands/data-extra.d.ts +1 -0
  28. package/dist/commands/data-extra.js +41 -14
  29. package/dist/commands/data-extra.js.map +1 -1
  30. package/dist/commands/data.d.ts +4 -4
  31. package/dist/commands/data.js +254 -187
  32. package/dist/commands/data.js.map +1 -1
  33. package/dist/commands/diagnose.d.ts +20 -0
  34. package/dist/commands/diagnose.js +186 -0
  35. package/dist/commands/diagnose.js.map +1 -0
  36. package/dist/commands/digest.d.ts +17 -0
  37. package/dist/commands/digest.js +122 -0
  38. package/dist/commands/digest.js.map +1 -0
  39. package/dist/commands/discover.d.ts +2 -0
  40. package/dist/commands/discover.js +25 -8
  41. package/dist/commands/discover.js.map +1 -1
  42. package/dist/commands/doc.d.ts +14 -0
  43. package/dist/commands/doc.js +61 -4
  44. package/dist/commands/doc.js.map +1 -1
  45. package/dist/commands/doctor.d.ts +21 -0
  46. package/dist/commands/doctor.js +189 -0
  47. package/dist/commands/doctor.js.map +1 -0
  48. package/dist/commands/events.js +5 -2
  49. package/dist/commands/events.js.map +1 -1
  50. package/dist/commands/explain.d.ts +1 -1
  51. package/dist/commands/explain.js +18 -15
  52. package/dist/commands/explain.js.map +1 -1
  53. package/dist/commands/facet.d.ts +29 -0
  54. package/dist/commands/facet.js +113 -0
  55. package/dist/commands/facet.js.map +1 -0
  56. package/dist/commands/graph.js +7 -4
  57. package/dist/commands/graph.js.map +1 -1
  58. package/dist/commands/import.d.ts +38 -0
  59. package/dist/commands/import.js +471 -0
  60. package/dist/commands/import.js.map +1 -0
  61. package/dist/commands/init.js +35 -16
  62. package/dist/commands/init.js.map +1 -1
  63. package/dist/commands/keys.js +3 -4
  64. package/dist/commands/keys.js.map +1 -1
  65. package/dist/commands/knowledge.d.ts +8 -0
  66. package/dist/commands/knowledge.js +308 -33
  67. package/dist/commands/knowledge.js.map +1 -1
  68. package/dist/commands/launch.d.ts +50 -0
  69. package/dist/commands/launch.js +790 -0
  70. package/dist/commands/launch.js.map +1 -0
  71. package/dist/commands/lens.d.ts +45 -0
  72. package/dist/commands/lens.js +386 -0
  73. package/dist/commands/lens.js.map +1 -0
  74. package/dist/commands/market-authoring.d.ts +45 -0
  75. package/dist/commands/market-authoring.js +225 -0
  76. package/dist/commands/market-authoring.js.map +1 -0
  77. package/dist/commands/market.d.ts +224 -0
  78. package/dist/commands/market.js +1643 -0
  79. package/dist/commands/market.js.map +1 -0
  80. package/dist/commands/mcp.d.ts +64 -0
  81. package/dist/commands/mcp.js +265 -0
  82. package/dist/commands/mcp.js.map +1 -0
  83. package/dist/commands/observe.js +14 -11
  84. package/dist/commands/observe.js.map +1 -1
  85. package/dist/commands/open.d.ts +21 -4
  86. package/dist/commands/open.js +45 -4
  87. package/dist/commands/open.js.map +1 -1
  88. package/dist/commands/pod.d.ts +100 -0
  89. package/dist/commands/pod.js +359 -0
  90. package/dist/commands/pod.js.map +1 -0
  91. package/dist/commands/pods.d.ts +15 -0
  92. package/dist/commands/pods.js +233 -17
  93. package/dist/commands/pods.js.map +1 -1
  94. package/dist/commands/proposals-actions.js +43 -3
  95. package/dist/commands/proposals-actions.js.map +1 -1
  96. package/dist/commands/purge.d.ts +23 -0
  97. package/dist/commands/purge.js +148 -0
  98. package/dist/commands/purge.js.map +1 -0
  99. package/dist/commands/raycast.d.ts +15 -0
  100. package/dist/commands/raycast.js +153 -0
  101. package/dist/commands/raycast.js.map +1 -0
  102. package/dist/commands/sessions.js +47 -22
  103. package/dist/commands/sessions.js.map +1 -1
  104. package/dist/commands/skill-manage.d.ts +21 -0
  105. package/dist/commands/skill-manage.js +195 -0
  106. package/dist/commands/skill-manage.js.map +1 -0
  107. package/dist/commands/skills-commands.js +2 -1
  108. package/dist/commands/skills-commands.js.map +1 -1
  109. package/dist/commands/skills.js +7 -4
  110. package/dist/commands/skills.js.map +1 -1
  111. package/dist/commands/status.d.ts +3 -1
  112. package/dist/commands/status.js +166 -14
  113. package/dist/commands/status.js.map +1 -1
  114. package/dist/commands/statusline.d.ts +24 -0
  115. package/dist/commands/statusline.js +450 -0
  116. package/dist/commands/statusline.js.map +1 -0
  117. package/dist/commands/subscribe.js +2 -2
  118. package/dist/commands/subscribe.js.map +1 -1
  119. package/dist/commands/template.d.ts +42 -0
  120. package/dist/commands/template.js +191 -0
  121. package/dist/commands/template.js.map +1 -0
  122. package/dist/commands/tools.d.ts +30 -0
  123. package/dist/commands/tools.js +315 -0
  124. package/dist/commands/tools.js.map +1 -0
  125. package/dist/commands/upload.d.ts +30 -0
  126. package/dist/commands/upload.js +155 -0
  127. package/dist/commands/upload.js.map +1 -0
  128. package/dist/commands/versions.d.ts +27 -0
  129. package/dist/commands/versions.js +277 -0
  130. package/dist/commands/versions.js.map +1 -0
  131. package/dist/commands/view.js +3 -2
  132. package/dist/commands/view.js.map +1 -1
  133. package/dist/commands/whoami.d.ts +21 -0
  134. package/dist/commands/whoami.js +129 -0
  135. package/dist/commands/whoami.js.map +1 -0
  136. package/dist/index.js +894 -60
  137. package/dist/index.js.map +1 -1
  138. package/dist/lib/approval-poll.d.ts +42 -0
  139. package/dist/lib/approval-poll.js +52 -0
  140. package/dist/lib/approval-poll.js.map +1 -0
  141. package/dist/lib/auth.d.ts +29 -6
  142. package/dist/lib/auth.js +105 -94
  143. package/dist/lib/auth.js.map +1 -1
  144. package/dist/lib/bundle-version.d.ts +11 -0
  145. package/dist/lib/bundle-version.js +33 -0
  146. package/dist/lib/bundle-version.js.map +1 -0
  147. package/dist/lib/capture-lane.d.ts +30 -12
  148. package/dist/lib/capture-lane.js +73 -14
  149. package/dist/lib/capture-lane.js.map +1 -1
  150. package/dist/lib/capture-structure.d.ts +123 -0
  151. package/dist/lib/capture-structure.js +57 -0
  152. package/dist/lib/capture-structure.js.map +1 -0
  153. package/dist/lib/cp-packages.d.ts +177 -0
  154. package/dist/lib/cp-packages.js +389 -0
  155. package/dist/lib/cp-packages.js.map +1 -0
  156. package/dist/lib/describe-lens.d.ts +46 -0
  157. package/dist/lib/describe-lens.js +33 -0
  158. package/dist/lib/describe-lens.js.map +1 -0
  159. package/dist/lib/hub-client.d.ts +91 -1
  160. package/dist/lib/hub-client.js +565 -22
  161. package/dist/lib/hub-client.js.map +1 -1
  162. package/dist/lib/id.d.ts +23 -0
  163. package/dist/lib/id.js +35 -0
  164. package/dist/lib/id.js.map +1 -0
  165. package/dist/lib/installed.d.ts +51 -0
  166. package/dist/lib/installed.js +70 -0
  167. package/dist/lib/installed.js.map +1 -0
  168. package/dist/lib/next-steps.d.ts +61 -0
  169. package/dist/lib/next-steps.js +146 -0
  170. package/dist/lib/next-steps.js.map +1 -0
  171. package/dist/lib/pod.d.ts +144 -22
  172. package/dist/lib/pod.js +302 -64
  173. package/dist/lib/pod.js.map +1 -1
  174. package/dist/lib/project-ref.d.ts +95 -0
  175. package/dist/lib/project-ref.js +144 -0
  176. package/dist/lib/project-ref.js.map +1 -0
  177. package/dist/lib/project.d.ts +35 -0
  178. package/dist/lib/project.js +39 -0
  179. package/dist/lib/project.js.map +1 -0
  180. package/dist/lib/session-lens.d.ts +33 -0
  181. package/dist/lib/session-lens.js +76 -0
  182. package/dist/lib/session-lens.js.map +1 -0
  183. package/dist/lib/skill-import.d.ts +40 -0
  184. package/dist/lib/skill-import.js +203 -0
  185. package/dist/lib/skill-import.js.map +1 -0
  186. package/dist/lib/skills-installer.d.ts +17 -6
  187. package/dist/lib/skills-installer.js +133 -19
  188. package/dist/lib/skills-installer.js.map +1 -1
  189. package/dist/lib/suite.d.ts +28 -0
  190. package/dist/lib/suite.js +29 -0
  191. package/dist/lib/suite.js.map +1 -0
  192. package/dist/lib/superwhisper-import.d.ts +40 -0
  193. package/dist/lib/superwhisper-import.js +389 -0
  194. package/dist/lib/superwhisper-import.js.map +1 -0
  195. package/dist/lib/targets.d.ts +49 -1
  196. package/dist/lib/targets.js +420 -288
  197. package/dist/lib/targets.js.map +1 -1
  198. package/dist/lib/template-file.d.ts +74 -0
  199. package/dist/lib/template-file.js +140 -0
  200. package/dist/lib/template-file.js.map +1 -0
  201. package/dist/lib/unwrapList.d.ts +1 -0
  202. package/dist/lib/unwrapList.js +20 -0
  203. package/dist/lib/unwrapList.js.map +1 -0
  204. package/dist/utils/logger.d.ts +2 -0
  205. package/dist/utils/logger.js +10 -2
  206. package/dist/utils/logger.js.map +1 -1
  207. package/node_modules/@synap/hub-rest-client/dist/index.cjs +414 -34
  208. package/node_modules/@synap/hub-rest-client/dist/index.d.cts +762 -22
  209. package/node_modules/@synap/hub-rest-client/dist/index.d.ts +762 -22
  210. package/node_modules/@synap/hub-rest-client/dist/index.js +414 -34
  211. package/node_modules/@synap/hub-rest-client/package.json +1 -1
  212. package/node_modules/@synap/hub-rest-client/src/client.ts +525 -37
  213. package/node_modules/@synap/hub-rest-client/src/index.ts +42 -0
  214. package/node_modules/@synap/hub-rest-client/src/setup.ts +57 -1
  215. package/node_modules/@synap/hub-rest-client/src/types.ts +697 -37
  216. package/package.json +11 -5
  217. package/skills/agent-os/SKILL.md +218 -0
  218. package/skills/manifest.json +5 -0
  219. package/skills/onboard/SKILL.md +193 -0
  220. package/skills/synap/SKILL.md +1284 -58
  221. package/skills/synap/_order.txt +37 -0
  222. package/skills/synap/_skill.yaml +24 -0
  223. package/skills/synap/authentication.md +10 -0
  224. package/skills/synap/automations.md +264 -0
  225. package/skills/synap/capabilities.md +107 -0
  226. package/skills/synap/capture.md +50 -142
  227. package/skills/synap/cli-operations.md +107 -0
  228. package/skills/synap/common-mistakes.md +13 -0
  229. package/skills/synap/content-os.md +83 -0
  230. package/skills/synap/creative-loop.md +43 -0
  231. package/skills/synap/crm.md +117 -0
  232. package/skills/synap/diagnostics.md +35 -0
  233. package/skills/synap/digesting-a-channel.md +63 -0
  234. package/skills/synap/document-embeds.md +123 -0
  235. package/skills/synap/escalation-ladder.md +71 -0
  236. package/skills/synap/focus-sessions.md +47 -0
  237. package/skills/synap/governance-rules.md +64 -0
  238. package/skills/synap/governance.md +6 -6
  239. package/skills/synap/graph-gardening.md +21 -0
  240. package/skills/synap/inline-patterns.md +38 -0
  241. package/skills/synap/lenses.md +38 -0
  242. package/skills/synap/linking-principle.md +39 -0
  243. package/skills/synap/linking.md +1 -0
  244. package/skills/synap/mental-model.md +45 -0
  245. package/skills/synap/more.md +7 -0
  246. package/skills/synap/multi-entity-capture.md +12 -0
  247. package/skills/synap/next-action.md +37 -0
  248. package/skills/synap/north-star.md +17 -0
  249. package/skills/synap/operating-mode-rules.md +45 -0
  250. package/skills/synap/operating-mode.md +9 -0
  251. package/skills/synap/quick-reference.md +67 -0
  252. package/skills/synap/reading.md +43 -0
  253. package/skills/synap/recap.md +21 -0
  254. package/skills/synap/reflexes.md +46 -0
  255. package/skills/synap/response-style.md +21 -0
  256. package/skills/synap/scope.md +13 -0
  257. package/skills/synap/showing.md +17 -0
  258. package/skills/synap/user-model.md +25 -0
  259. package/skills/synap/viewframe-cells.md +290 -0
  260. package/skills/synap/work-flow.md +113 -0
  261. package/skills/synap/worked-examples.md +74 -0
  262. package/skills/synap/workspace-design.md +45 -0
  263. package/skills/synap/workspace-edges.md +49 -0
  264. package/skills/synap/writes.md +213 -0
  265. package/skills/synap-app/SKILL.md +319 -0
  266. package/skills/synap-app/_order.txt +5 -0
  267. package/skills/synap-app/_skill.yaml +20 -0
  268. package/skills/synap-app/creating-the-base-app.md +64 -0
  269. package/skills/synap-app/governance.md +42 -0
  270. package/skills/synap-app/install-commands.md +55 -0
  271. package/skills/synap-app/overview.md +50 -0
  272. package/skills/synap-app/using-the-sdk.md +73 -0
  273. package/skills/synap-market/SKILL.md +335 -0
  274. package/skills/synap-market/_order.txt +5 -0
  275. package/skills/synap-market/_skill.yaml +24 -0
  276. package/skills/synap-market/author-and-publish.md +75 -0
  277. package/skills/synap-market/discover-and-fetch.md +59 -0
  278. package/skills/synap-market/governance-and-catalog.md +56 -0
  279. package/skills/synap-market/install.md +55 -0
  280. package/skills/synap-market/overview.md +51 -0
  281. package/skills/synap-schema/SKILL.md +97 -6
  282. package/skills/synap-schema/_order.txt +13 -0
  283. package/skills/synap-schema/_skill.yaml +22 -0
  284. package/skills/synap-schema/before-you-touch.md +14 -0
  285. package/skills/synap-schema/common-mistakes.md +9 -0
  286. package/skills/synap-schema/creating-profiles.md +17 -0
  287. package/skills/synap-schema/discover-profiles.md +24 -0
  288. package/skills/synap-schema/entity-scope.md +8 -0
  289. package/skills/synap-schema/extend-vs-create.md +31 -0
  290. package/skills/synap-schema/more.md +6 -0
  291. package/skills/synap-schema/overlay-properties.md +22 -0
  292. package/skills/synap-schema/profiles.md +3 -0
  293. package/skills/synap-schema/property-defs.md +29 -0
  294. package/skills/synap-schema/read-before-write.md +50 -0
  295. package/skills/synap-schema/relations.md +18 -0
  296. package/skills/synap-schema/worked-example.md +30 -0
  297. package/skills/synap-ui/SKILL.md +26 -2
  298. package/skills/synap-ui/_order.txt +13 -0
  299. package/skills/synap-ui/_skill.yaml +23 -0
  300. package/skills/synap-ui/ai-companion.md +27 -0
  301. package/skills/synap-ui/arranging-bento.md +8 -0
  302. package/skills/synap-ui/before-you-build.md +20 -0
  303. package/skills/synap-ui/bento-layouts.md +35 -0
  304. package/skills/synap-ui/common-mistakes.md +10 -0
  305. package/skills/synap-ui/creating-views.md +26 -0
  306. package/skills/synap-ui/four-builds.md +24 -0
  307. package/skills/synap-ui/more.md +7 -0
  308. package/skills/synap-ui/proposing-vs-extending.md +11 -0
  309. package/skills/synap-ui/view-list.md +24 -0
  310. package/skills/synap-ui/views.md +3 -0
  311. package/skills/synap-ui/worked-example.md +18 -0
  312. package/skills/synap-ui/workspaces.md +47 -0
  313. package/templates/automations/bookmark-enrichment.automation.json +44 -0
  314. package/templates/automations/mail-feed.automation.json +86 -0
  315. package/templates/automations/url-bookmark-capture.automation.json +70 -0
  316. package/templates/skills/README.md +21 -0
  317. package/templates/skills/authoring-automations/SKILL.md +95 -0
  318. package/templates/skills/authoring-automations/reference/node-matrix.md +49 -0
  319. package/templates/skills/authoring-capabilities/SKILL.md +93 -0
  320. package/templates/skills/authoring-capabilities/reference/template-anatomy.md +70 -0
  321. package/templates/skills/authoring-playbooks/SKILL.md +102 -0
  322. package/templates/skills/authoring-playbooks/reference/stages-contract.md +35 -0
  323. package/templates/skills/authoring-playbooks/reference/worked-example.md +64 -0
  324. package/templates/skills/bookmark-enrichment/SKILL.md +40 -0
  325. package/templates/skills/coordinating-agents/SKILL.md +32 -0
  326. package/templates/skills/coordinating-branches/SKILL.md +27 -0
  327. package/templates/skills/mapping-the-server/SKILL.md +54 -0
  328. package/templates/skills/reading-client-progress/SKILL.md +65 -0
  329. package/templates/skills/routing-to-channels/SKILL.md +54 -0
  330. package/templates/skills/stellar-scf-grants-advisor/ASSOCIATE-CHEATSHEET.md +133 -0
  331. package/templates/skills/stellar-scf-grants-advisor/ASSOCIATE-ONBOARDING-GUIDE.md +761 -0
  332. package/templates/skills/stellar-scf-grants-advisor/CLIENT-FAQ.md +181 -0
  333. package/templates/skills/stellar-scf-grants-advisor/PARTNER-HANDOVER.md +130 -0
  334. package/templates/skills/stellar-scf-grants-advisor/SKILL.md +102 -0
  335. package/templates/skills/stellar-scf-grants-advisor/examples/README.md +43 -0
  336. package/templates/skills/stellar-scf-grants-advisor/examples/chat-summary-templates.md +158 -0
  337. package/templates/skills/stellar-scf-grants-advisor/examples/corpus-comparable-rows.md +136 -0
  338. package/templates/skills/stellar-scf-grants-advisor/examples/q10-team-rewrites.md +109 -0
  339. package/templates/skills/stellar-scf-grants-advisor/examples/q2-description-rewrites.md +88 -0
  340. package/templates/skills/stellar-scf-grants-advisor/examples/q6-integration-rewrites.md +107 -0
  341. package/templates/skills/stellar-scf-grants-advisor/examples/tranche-structures.md +176 -0
  342. package/templates/skills/stellar-scf-grants-advisor/reference/01-methodology.md +96 -0
  343. package/templates/skills/stellar-scf-grants-advisor/reference/02-scoring-framework.md +199 -0
  344. package/templates/skills/stellar-scf-grants-advisor/reference/03-rejection-patterns.md +241 -0
  345. package/templates/skills/stellar-scf-grants-advisor/reference/04-winner-patterns.md +120 -0
  346. package/templates/skills/stellar-scf-grants-advisor/reference/05-comeback-patterns.md +92 -0
  347. package/templates/skills/stellar-scf-grants-advisor/reference/06-budget-benchmarks.md +125 -0
  348. package/templates/skills/stellar-scf-grants-advisor/reference/07-integration-list.md +147 -0
  349. package/templates/skills/stellar-scf-grants-advisor/reference/08-abstract-fields.md +258 -0
  350. package/templates/skills/stellar-scf-grants-advisor/reference/09-build-fields.md +259 -0
  351. package/templates/skills/stellar-scf-grants-advisor/reference/10-corpus-winners.md +135 -0
  352. package/templates/skills/stellar-scf-grants-advisor/reference/11-corpus-rejects.md +267 -0
  353. package/templates/skills/stellar-scf-grants-advisor/reference/12-client-communication.md +131 -0
  354. package/templates/skills/stellar-scf-grants-advisor/reference/13-excel-output-spec.md +199 -0
  355. package/templates/skills/stellar-scf-grants-advisor/reference/14-worked-examples.md +160 -0
  356. package/templates/skills/stellar-scf-grants-advisor/templates/abstract-review-builder.py +253 -0
  357. package/templates/skills/stellar-scf-grants-advisor/templates/build-review-builder.py +213 -0
  358. package/dist/commands/connectors.d.ts +0 -31
  359. package/dist/commands/connectors.js +0 -276
  360. package/dist/commands/connectors.js.map +0 -1
  361. package/dist/commands/finish.d.ts +0 -25
  362. package/dist/commands/finish.js +0 -274
  363. package/dist/commands/finish.js.map +0 -1
  364. package/dist/commands/openclaw.d.ts +0 -40
  365. package/dist/commands/openclaw.js +0 -1148
  366. package/dist/commands/openclaw.js.map +0 -1
  367. package/dist/commands/security-audit.d.ts +0 -12
  368. package/dist/commands/security-audit.js +0 -100
  369. package/dist/commands/security-audit.js.map +0 -1
  370. package/dist/lib/hardening.d.ts +0 -18
  371. package/dist/lib/hardening.js +0 -214
  372. 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