@synap-core/cli 1.10.0 → 1.12.2

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