@synap-core/cli 1.11.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 (452) 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 +218 -168
  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 +1 -13
  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 +7 -1
  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 +193 -23
  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 +293 -20
  50. package/dist/commands/data.js.map +1 -1
  51. package/dist/commands/diagnose.d.ts +66 -4
  52. package/dist/commands/diagnose.js +285 -28
  53. package/dist/commands/diagnose.js.map +1 -1
  54. package/dist/commands/doc.d.ts +11 -4
  55. package/dist/commands/doc.js +39 -14
  56. package/dist/commands/doc.js.map +1 -1
  57. package/dist/commands/doctor.d.ts +112 -3
  58. package/dist/commands/doctor.js +657 -32
  59. package/dist/commands/doctor.js.map +1 -1
  60. package/dist/commands/explain.js +5 -5
  61. package/dist/commands/explain.js.map +1 -1
  62. package/dist/commands/import.d.ts +368 -13
  63. package/dist/commands/import.js +1064 -54
  64. package/dist/commands/import.js.map +1 -1
  65. package/dist/commands/init.d.ts +20 -9
  66. package/dist/commands/init.js +433 -973
  67. package/dist/commands/init.js.map +1 -1
  68. package/dist/commands/keys.d.ts +1 -0
  69. package/dist/commands/keys.js +15 -2
  70. package/dist/commands/keys.js.map +1 -1
  71. package/dist/commands/knowledge.d.ts +22 -6
  72. package/dist/commands/knowledge.js +117 -55
  73. package/dist/commands/knowledge.js.map +1 -1
  74. package/dist/commands/lens.d.ts +37 -1
  75. package/dist/commands/lens.js +192 -71
  76. package/dist/commands/lens.js.map +1 -1
  77. package/dist/commands/market-authoring.d.ts +29 -2
  78. package/dist/commands/market-authoring.js +348 -7
  79. package/dist/commands/market-authoring.js.map +1 -1
  80. package/dist/commands/market-official.d.ts +58 -0
  81. package/dist/commands/market-official.js +84 -0
  82. package/dist/commands/market-official.js.map +1 -0
  83. package/dist/commands/market.d.ts +400 -16
  84. package/dist/commands/market.js +1802 -146
  85. package/dist/commands/market.js.map +1 -1
  86. package/dist/commands/mcp.d.ts +60 -25
  87. package/dist/commands/mcp.js +251 -64
  88. package/dist/commands/mcp.js.map +1 -1
  89. package/dist/commands/open.d.ts +39 -9
  90. package/dist/commands/open.js +80 -25
  91. package/dist/commands/open.js.map +1 -1
  92. package/dist/commands/playbook.d.ts +42 -0
  93. package/dist/commands/playbook.js +162 -0
  94. package/dist/commands/playbook.js.map +1 -0
  95. package/dist/commands/pod.js +1 -1
  96. package/dist/commands/pod.js.map +1 -1
  97. package/dist/commands/pods.d.ts +5 -0
  98. package/dist/commands/pods.js +97 -116
  99. package/dist/commands/pods.js.map +1 -1
  100. package/dist/commands/proposals-actions.d.ts +62 -0
  101. package/dist/commands/proposals-actions.js +143 -8
  102. package/dist/commands/proposals-actions.js.map +1 -1
  103. package/dist/commands/proposals-await.d.ts +68 -0
  104. package/dist/commands/proposals-await.js +248 -0
  105. package/dist/commands/proposals-await.js.map +1 -0
  106. package/dist/commands/proton-setup.d.ts +73 -0
  107. package/dist/commands/proton-setup.js +577 -0
  108. package/dist/commands/proton-setup.js.map +1 -0
  109. package/dist/commands/providers.d.ts +23 -0
  110. package/dist/commands/providers.js +126 -1
  111. package/dist/commands/providers.js.map +1 -1
  112. package/dist/commands/raycast.js +6 -2
  113. package/dist/commands/raycast.js.map +1 -1
  114. package/dist/commands/rule.d.ts +150 -0
  115. package/dist/commands/rule.js +447 -0
  116. package/dist/commands/rule.js.map +1 -0
  117. package/dist/commands/session-wait.d.ts +111 -0
  118. package/dist/commands/session-wait.js +209 -0
  119. package/dist/commands/session-wait.js.map +1 -0
  120. package/dist/commands/sessions.d.ts +52 -0
  121. package/dist/commands/sessions.js +478 -58
  122. package/dist/commands/sessions.js.map +1 -1
  123. package/dist/commands/skill-manage.d.ts +21 -0
  124. package/dist/commands/skill-manage.js +136 -19
  125. package/dist/commands/skill-manage.js.map +1 -1
  126. package/dist/commands/skills.js +16 -0
  127. package/dist/commands/skills.js.map +1 -1
  128. package/dist/commands/status.d.ts +1 -4
  129. package/dist/commands/status.js +5 -34
  130. package/dist/commands/status.js.map +1 -1
  131. package/dist/commands/statusline.backoff.test.d.ts +13 -0
  132. package/dist/commands/statusline.backoff.test.js +100 -0
  133. package/dist/commands/statusline.backoff.test.js.map +1 -0
  134. package/dist/commands/statusline.js +16 -9
  135. package/dist/commands/statusline.js.map +1 -1
  136. package/dist/commands/sync-status.d.ts +12 -0
  137. package/dist/commands/sync-status.js +125 -0
  138. package/dist/commands/sync-status.js.map +1 -0
  139. package/dist/commands/templates-retire.d.ts +34 -0
  140. package/dist/commands/templates-retire.js +192 -0
  141. package/dist/commands/templates-retire.js.map +1 -0
  142. package/dist/commands/tools.js +6 -21
  143. package/dist/commands/tools.js.map +1 -1
  144. package/dist/commands/tracks.d.ts +51 -0
  145. package/dist/commands/tracks.js +291 -0
  146. package/dist/commands/tracks.js.map +1 -0
  147. package/dist/commands/update.d.ts +3 -11
  148. package/dist/commands/update.js +5 -134
  149. package/dist/commands/update.js.map +1 -1
  150. package/dist/commands/upload.d.ts +41 -3
  151. package/dist/commands/upload.js +120 -18
  152. package/dist/commands/upload.js.map +1 -1
  153. package/dist/commands/vendor.d.ts +28 -0
  154. package/dist/commands/vendor.js +110 -0
  155. package/dist/commands/vendor.js.map +1 -0
  156. package/dist/commands/versions.js +6 -0
  157. package/dist/commands/versions.js.map +1 -1
  158. package/dist/commands/view.js +10 -5
  159. package/dist/commands/view.js.map +1 -1
  160. package/dist/commands/whoami.d.ts +23 -3
  161. package/dist/commands/whoami.js +169 -23
  162. package/dist/commands/whoami.js.map +1 -1
  163. package/dist/commands/worker.d.ts +122 -0
  164. package/dist/commands/worker.js +435 -0
  165. package/dist/commands/worker.js.map +1 -0
  166. package/dist/commands/worker.test.d.ts +1 -0
  167. package/dist/commands/worker.test.js +116 -0
  168. package/dist/commands/worker.test.js.map +1 -0
  169. package/dist/commands/workspace-ops.d.ts +35 -0
  170. package/dist/commands/workspace-ops.js +153 -0
  171. package/dist/commands/workspace-ops.js.map +1 -0
  172. package/dist/index.d.ts +6 -6
  173. package/dist/index.js +900 -180
  174. package/dist/index.js.map +1 -1
  175. package/dist/lib/agent-connections.d.ts +31 -0
  176. package/dist/lib/agent-connections.js +33 -0
  177. package/dist/lib/agent-connections.js.map +1 -0
  178. package/dist/lib/agent-detect.d.ts +27 -0
  179. package/dist/lib/agent-detect.js +71 -0
  180. package/dist/lib/agent-detect.js.map +1 -0
  181. package/dist/lib/agent-door.d.ts +138 -0
  182. package/dist/lib/agent-door.js +426 -0
  183. package/dist/lib/agent-door.js.map +1 -0
  184. package/dist/lib/agents-config.d.ts +2 -0
  185. package/dist/lib/agents-config.js +4 -9
  186. package/dist/lib/agents-config.js.map +1 -1
  187. package/dist/lib/approval-poll.d.ts +31 -2
  188. package/dist/lib/approval-poll.js +20 -7
  189. package/dist/lib/approval-poll.js.map +1 -1
  190. package/dist/lib/atomic-file.d.ts +48 -0
  191. package/dist/lib/atomic-file.js +129 -0
  192. package/dist/lib/atomic-file.js.map +1 -0
  193. package/dist/lib/auth.d.ts +14 -9
  194. package/dist/lib/auth.js +44 -61
  195. package/dist/lib/auth.js.map +1 -1
  196. package/dist/lib/batch-approval.d.ts +85 -0
  197. package/dist/lib/batch-approval.js +170 -0
  198. package/dist/lib/batch-approval.js.map +1 -0
  199. package/dist/lib/capture-lane.d.ts +6 -5
  200. package/dist/lib/capture-lane.js +6 -5
  201. package/dist/lib/capture-lane.js.map +1 -1
  202. package/dist/lib/capture-structure.d.ts +230 -11
  203. package/dist/lib/capture-structure.js +251 -13
  204. package/dist/lib/capture-structure.js.map +1 -1
  205. package/dist/lib/catalog-templates.d.ts +59 -0
  206. package/dist/lib/catalog-templates.js +126 -0
  207. package/dist/lib/catalog-templates.js.map +1 -0
  208. package/dist/lib/cli-build.d.ts +19 -0
  209. package/dist/lib/cli-build.js +37 -0
  210. package/dist/lib/cli-build.js.map +1 -0
  211. package/dist/lib/conversation-headers.d.ts +18 -0
  212. package/dist/lib/conversation-headers.js +32 -0
  213. package/dist/lib/conversation-headers.js.map +1 -0
  214. package/dist/lib/cp-packages.d.ts +167 -11
  215. package/dist/lib/cp-packages.js +253 -21
  216. package/dist/lib/cp-packages.js.map +1 -1
  217. package/dist/lib/credential-class.d.ts +117 -0
  218. package/dist/lib/credential-class.js +116 -0
  219. package/dist/lib/credential-class.js.map +1 -0
  220. package/dist/lib/describe-lens.d.ts +35 -0
  221. package/dist/lib/describe-lens.js +40 -0
  222. package/dist/lib/describe-lens.js.map +1 -1
  223. package/dist/lib/device-connect.d.ts +125 -0
  224. package/dist/lib/device-connect.js +256 -0
  225. package/dist/lib/device-connect.js.map +1 -0
  226. package/dist/lib/directory-lens.d.ts +99 -0
  227. package/dist/lib/directory-lens.js +187 -0
  228. package/dist/lib/directory-lens.js.map +1 -0
  229. package/dist/lib/exporter-coverage.d.ts +71 -0
  230. package/dist/lib/exporter-coverage.js +97 -0
  231. package/dist/lib/exporter-coverage.js.map +1 -0
  232. package/dist/lib/hub-client.d.ts +29 -8
  233. package/dist/lib/hub-client.js +328 -55
  234. package/dist/lib/hub-client.js.map +1 -1
  235. package/dist/lib/init-connect.d.ts +41 -0
  236. package/dist/lib/init-connect.js +75 -0
  237. package/dist/lib/init-connect.js.map +1 -0
  238. package/dist/lib/init-exit.d.ts +24 -0
  239. package/dist/lib/init-exit.js +41 -0
  240. package/dist/lib/init-exit.js.map +1 -0
  241. package/dist/lib/init-flow.d.ts +25 -0
  242. package/dist/lib/init-flow.js +40 -0
  243. package/dist/lib/init-flow.js.map +1 -0
  244. package/dist/lib/init-handoff.d.ts +16 -0
  245. package/dist/lib/init-handoff.js +19 -0
  246. package/dist/lib/init-handoff.js.map +1 -0
  247. package/dist/lib/init-output.d.ts +18 -0
  248. package/dist/lib/init-output.js +43 -0
  249. package/dist/lib/init-output.js.map +1 -0
  250. package/dist/lib/init-pod.d.ts +45 -0
  251. package/dist/lib/init-pod.js +36 -0
  252. package/dist/lib/init-pod.js.map +1 -0
  253. package/dist/lib/installed.d.ts +210 -11
  254. package/dist/lib/installed.js +234 -36
  255. package/dist/lib/installed.js.map +1 -1
  256. package/dist/lib/key-approval.d.ts +57 -0
  257. package/dist/lib/key-approval.js +117 -0
  258. package/dist/lib/key-approval.js.map +1 -0
  259. package/dist/lib/key-diagnosis.d.ts +56 -0
  260. package/dist/lib/key-diagnosis.js +227 -0
  261. package/dist/lib/key-diagnosis.js.map +1 -0
  262. package/dist/lib/key-doctor.d.ts +136 -0
  263. package/dist/lib/key-doctor.js +286 -0
  264. package/dist/lib/key-doctor.js.map +1 -0
  265. package/dist/lib/key-locations.d.ts +120 -0
  266. package/dist/lib/key-locations.js +326 -0
  267. package/dist/lib/key-locations.js.map +1 -0
  268. package/dist/lib/key-source.d.ts +66 -0
  269. package/dist/lib/key-source.js +106 -0
  270. package/dist/lib/key-source.js.map +1 -0
  271. package/dist/lib/key-state.d.ts +70 -0
  272. package/dist/lib/key-state.js +115 -0
  273. package/dist/lib/key-state.js.map +1 -0
  274. package/dist/lib/kind-package.d.ts +181 -0
  275. package/dist/lib/kind-package.js +679 -0
  276. package/dist/lib/kind-package.js.map +1 -0
  277. package/dist/lib/knowledge-title.d.ts +24 -0
  278. package/dist/lib/knowledge-title.js +46 -0
  279. package/dist/lib/knowledge-title.js.map +1 -0
  280. package/dist/lib/knowledge-title.test.d.ts +1 -0
  281. package/dist/lib/knowledge-title.test.js +34 -0
  282. package/dist/lib/knowledge-title.test.js.map +1 -0
  283. package/dist/lib/machine-instance.d.ts +64 -0
  284. package/dist/lib/machine-instance.js +142 -0
  285. package/dist/lib/machine-instance.js.map +1 -0
  286. package/dist/lib/mcp-bridge.d.ts +42 -0
  287. package/dist/lib/mcp-bridge.js +147 -0
  288. package/dist/lib/mcp-bridge.js.map +1 -0
  289. package/dist/lib/mcp-bridge.test.d.ts +1 -0
  290. package/dist/lib/mcp-bridge.test.js +90 -0
  291. package/dist/lib/mcp-bridge.test.js.map +1 -0
  292. package/dist/lib/mcp-call.d.ts +30 -0
  293. package/dist/lib/mcp-call.js +107 -0
  294. package/dist/lib/mcp-call.js.map +1 -0
  295. package/dist/lib/mcp-snippets.d.ts +53 -0
  296. package/dist/lib/mcp-snippets.js +82 -0
  297. package/dist/lib/mcp-snippets.js.map +1 -0
  298. package/dist/lib/next-steps.d.ts +8 -0
  299. package/dist/lib/next-steps.js +31 -1
  300. package/dist/lib/next-steps.js.map +1 -1
  301. package/dist/lib/package-wire.d.ts +179 -0
  302. package/dist/lib/package-wire.js +68 -0
  303. package/dist/lib/package-wire.js.map +1 -0
  304. package/dist/lib/pair-link.d.ts +38 -0
  305. package/dist/lib/pair-link.js +68 -0
  306. package/dist/lib/pair-link.js.map +1 -0
  307. package/dist/lib/pod-wide.d.ts +40 -0
  308. package/dist/lib/pod-wide.js +46 -0
  309. package/dist/lib/pod-wide.js.map +1 -0
  310. package/dist/lib/pod.d.ts +66 -112
  311. package/dist/lib/pod.js +134 -412
  312. package/dist/lib/pod.js.map +1 -1
  313. package/dist/lib/project-ref.d.ts +9 -3
  314. package/dist/lib/project-ref.js +10 -4
  315. package/dist/lib/project-ref.js.map +1 -1
  316. package/dist/lib/review-credential.d.ts +119 -0
  317. package/dist/lib/review-credential.js +147 -0
  318. package/dist/lib/review-credential.js.map +1 -0
  319. package/dist/lib/safe-open.d.ts +49 -0
  320. package/dist/lib/safe-open.js +119 -0
  321. package/dist/lib/safe-open.js.map +1 -0
  322. package/dist/lib/session-lens.d.ts +40 -1
  323. package/dist/lib/session-lens.js +54 -4
  324. package/dist/lib/session-lens.js.map +1 -1
  325. package/dist/lib/superwhisper-import.d.ts +8 -0
  326. package/dist/lib/superwhisper-import.js +14 -7
  327. package/dist/lib/superwhisper-import.js.map +1 -1
  328. package/dist/lib/superwhisper-title.test.d.ts +1 -0
  329. package/dist/lib/superwhisper-title.test.js +18 -0
  330. package/dist/lib/superwhisper-title.test.js.map +1 -0
  331. package/dist/lib/targets.d.ts +134 -10
  332. package/dist/lib/targets.js +962 -258
  333. package/dist/lib/targets.js.map +1 -1
  334. package/dist/lib/template-file.d.ts +4 -0
  335. package/dist/lib/template-file.js.map +1 -1
  336. package/dist/lib/template-lifecycle.d.ts +217 -0
  337. package/dist/lib/template-lifecycle.js +433 -0
  338. package/dist/lib/template-lifecycle.js.map +1 -0
  339. package/dist/lib/template-steps.d.ts +88 -0
  340. package/dist/lib/template-steps.js +118 -0
  341. package/dist/lib/template-steps.js.map +1 -0
  342. package/dist/lib/time-ago.d.ts +2 -0
  343. package/dist/lib/time-ago.js +15 -0
  344. package/dist/lib/time-ago.js.map +1 -0
  345. package/dist/utils/logger.js +5 -2
  346. package/dist/utils/logger.js.map +1 -1
  347. package/package.json +23 -17
  348. package/skills/README.md +12 -10
  349. package/skills/agent-os/SKILL.md +214 -99
  350. package/skills/manifest.json +1 -1
  351. package/skills/onboard/SKILL.md +55 -26
  352. package/skills/synap/README.md +5 -8
  353. package/skills/synap/SKILL.md +1086 -283
  354. package/skills/synap/_order.txt +7 -0
  355. package/skills/synap/_skill.yaml +1 -1
  356. package/skills/synap/automations.md +15 -0
  357. package/skills/synap/capture.md +5 -0
  358. package/skills/synap/cli-operations.md +24 -18
  359. package/skills/synap/concepts.md +28 -0
  360. package/skills/synap/content-os.md +10 -1
  361. package/skills/synap/creative-loop.md +15 -10
  362. package/skills/synap/crm.md +21 -52
  363. package/skills/synap/document-embeds.md +141 -47
  364. package/skills/synap/escalation-ladder.md +20 -10
  365. package/skills/synap/focus-sessions.md +47 -21
  366. package/skills/synap/from-intent.md +142 -0
  367. package/skills/synap/governance-rules.md +4 -4
  368. package/skills/synap/governance.md +10 -7
  369. package/skills/synap/hand-off.md +33 -0
  370. package/skills/synap/inline-patterns.md +15 -7
  371. package/skills/synap/lenses.md +33 -15
  372. package/skills/synap/linking.md +41 -40
  373. package/skills/synap/mcp-remote-servers.md +64 -0
  374. package/skills/synap/mental-model.md +17 -15
  375. package/skills/synap/operating-mode-rules.md +1 -1
  376. package/skills/synap/processes.md +118 -0
  377. package/skills/synap/quick-reference.md +5 -1
  378. package/skills/synap/reading.md +3 -1
  379. package/skills/synap/reflexes.md +12 -42
  380. package/skills/synap/viewframe-cells.md +38 -5
  381. package/skills/synap/work-flow.md +1 -1
  382. package/skills/synap/work-model.md +96 -0
  383. package/skills/synap/worked-examples.md +4 -5
  384. package/skills/synap/workspace-design.md +14 -10
  385. package/skills/synap/writes.md +97 -24
  386. package/skills/synap/writing-titles.md +63 -0
  387. package/skills/synap-app/SKILL.md +2 -2
  388. package/skills/synap-app/_skill.yaml +1 -1
  389. package/skills/synap-app/install-commands.md +1 -1
  390. package/skills/synap-design/SKILL.md +474 -0
  391. package/skills/synap-design/_order.txt +4 -0
  392. package/skills/synap-design/_skill.yaml +21 -0
  393. package/skills/synap-design/loop.md +134 -0
  394. package/skills/synap-design/overview.md +44 -0
  395. package/skills/synap-design/principles.md +130 -0
  396. package/skills/synap-design/system.md +133 -0
  397. package/skills/synap-market/SKILL.md +306 -16
  398. package/skills/synap-market/_skill.yaml +1 -1
  399. package/skills/synap-market/author-and-publish.md +145 -8
  400. package/skills/synap-market/governance-and-catalog.md +7 -0
  401. package/skills/synap-market/install.md +79 -1
  402. package/skills/synap-market/overview.md +74 -6
  403. package/skills/synap-schema/SKILL.md +172 -10
  404. package/skills/synap-schema/_order.txt +2 -0
  405. package/skills/synap-schema/_skill.yaml +4 -2
  406. package/skills/synap-schema/common-mistakes.md +4 -2
  407. package/skills/synap-schema/discover-profiles.md +1 -1
  408. package/skills/synap-schema/entity-scope.md +15 -3
  409. package/skills/synap-schema/extend-first.md +29 -0
  410. package/skills/synap-schema/extend-vs-create.md +1 -1
  411. package/skills/synap-schema/intent-vocabulary-ssot.md +111 -0
  412. package/skills/synap-schema/property-types.md +1 -1
  413. package/skills/synap-schema/relations.md +1 -1
  414. package/skills/synap-ui/SKILL.md +19 -12
  415. package/skills/synap-ui/_skill.yaml +1 -1
  416. package/skills/synap-ui/ai-companion.md +1 -1
  417. package/skills/synap-ui/arranging-bento.md +2 -0
  418. package/skills/synap-ui/bento-layouts.md +10 -10
  419. package/skills/synap-ui/bento-recipes.md +313 -543
  420. package/skills/synap-ui/four-builds.md +5 -0
  421. package/skills/synap-ui/view-types.md +2 -2
  422. package/skills/synap-ui/widget-catalog.md +22 -278
  423. package/templates/automations/bookmark-enrichment.automation.json +290 -13
  424. package/templates/automations/url-bookmark-capture.automation.json +73 -11
  425. package/templates/skills/authoring-playbooks/SKILL.md +6 -3
  426. package/templates/skills/authoring-playbooks/reference/stages-contract.md +15 -0
  427. package/templates/skills/authoring-playbooks/reference/worked-example.md +3 -0
  428. package/dist/commands/infra.d.ts +0 -21
  429. package/dist/commands/infra.js +0 -262
  430. package/dist/commands/infra.js.map +0 -1
  431. package/dist/commands/template.d.ts +0 -42
  432. package/dist/commands/template.js +0 -191
  433. package/dist/commands/template.js.map +0 -1
  434. package/dist/lib/browser-auth.d.ts +0 -35
  435. package/dist/lib/browser-auth.js +0 -170
  436. package/dist/lib/browser-auth.js.map +0 -1
  437. package/dist/lib/openclaw.d.ts +0 -32
  438. package/dist/lib/openclaw.js +0 -164
  439. package/dist/lib/openclaw.js.map +0 -1
  440. package/dist/lib/seed.d.ts +0 -13
  441. package/dist/lib/seed.js +0 -135
  442. package/dist/lib/seed.js.map +0 -1
  443. package/node_modules/@synap/hub-rest-client/dist/index.cjs +0 -1261
  444. package/node_modules/@synap/hub-rest-client/dist/index.d.cts +0 -1647
  445. package/node_modules/@synap/hub-rest-client/dist/index.d.ts +0 -1647
  446. package/node_modules/@synap/hub-rest-client/dist/index.js +0 -1231
  447. package/node_modules/@synap/hub-rest-client/package.json +0 -45
  448. package/node_modules/@synap/hub-rest-client/src/client.ts +0 -1631
  449. package/node_modules/@synap/hub-rest-client/src/errors.ts +0 -30
  450. package/node_modules/@synap/hub-rest-client/src/index.ts +0 -153
  451. package/node_modules/@synap/hub-rest-client/src/setup.ts +0 -133
  452. package/node_modules/@synap/hub-rest-client/src/types.ts +0 -1299
package/dist/index.js CHANGED
@@ -1,32 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * @synap/cli — Connect AI agents to your Synap data pod
3
+ * @synap-core/cli — Connect AI agents to your Synap data pod
4
4
  *
5
5
  * Usage:
6
- * npx @synap/cli init Full setup: detect, harden, connect, install
7
- * npx @synap/cli connect Connect an AI surface to a Synap pod
8
- * npx @synap/cli status Show pod health and API status
9
- * npx @synap/cli update Update skill + check for CLI updates
6
+ * npx @synap-core/cli init Full setup: detect, harden, connect, install
7
+ * npx @synap-core/cli connect Connect an AI surface to a Synap pod
8
+ * npx @synap-core/cli status Show pod health and API status
9
+ * npx @synap-core/cli update Show how to update the CLI
10
10
  *
11
11
  * Or install globally:
12
- * npm i -g @synap/cli
12
+ * npm i -g @synap-core/cli
13
13
  * synap init
14
14
  */
15
- import { Command } from "commander";
16
- import { readFileSync } from "fs";
17
- import { fileURLToPath } from "url";
18
- import { dirname, join } from "path";
15
+ import { Command, Option } from "commander";
19
16
  import { bootstrapPodOverride } from "./lib/pod.js";
20
- const __filename = fileURLToPath(import.meta.url);
21
- const __dirname = dirname(__filename);
22
- let version = "0.1.0";
23
- try {
24
- const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
25
- version = pkg.version;
26
- }
27
- catch {
28
- // use default
29
- }
17
+ import { INIT_EXIT_HELP } from "./lib/init-exit.js";
18
+ import { cliBuild, formatCliBuild } from "./lib/cli-build.js";
19
+ // The version AND the commit this build came from — a stale build is visible.
20
+ const version = formatCliBuild(cliBuild());
30
21
  const program = new Command();
31
22
  program
32
23
  .name("synap")
@@ -42,7 +33,12 @@ program
42
33
  .description("Full setup: connect to a Synap pod and install the skill")
43
34
  .option("--pod-url <url>", "Synap pod URL (skip pod choice prompt)")
44
35
  .option("--api-key <key>", "Hub Protocol API key (skip key generation)")
45
- .option("--skip-is", "Skip Intelligence Service provider setup")
36
+ // Deprecated no-op, kept so old scripts do not fail on an unknown option.
37
+ .addOption(new Option("--skip-is", "deprecated, ignored").hideHelp())
38
+ .option("-y, --yes", "Accept the defaults, no prompts (connects the agents found on this machine)")
39
+ .option("--agents <list>", "Agents to connect, comma-separated: claude-code,codex,cursor")
40
+ .option("--json", "Print a JSON summary on stdout: {podUrl, connected, notApproved, failed, pairLink, exitCode}")
41
+ .addHelpText("after", INIT_EXIT_HELP)
46
42
  .action(async (opts) => {
47
43
  const { init } = await import("./commands/init.js");
48
44
  await init(opts);
@@ -71,31 +67,61 @@ program
71
67
  .option("--bot-token <token>", "Discord bot token — provisioned into the pod vault")
72
68
  .option("--guild-id <id>", "Discord server (guild) id — written to the bridge .env")
73
69
  .option("--bridge-dir <path>", "Path to the telegram-discord-bridge repo")
74
- .option("--workspace-id <uuid>", "Target workspace (default: pod profile's)")
70
+ .option("--workspace-id <uuid>", "Advanced/legacy: pin the bridge to one workspace (default: pod-wide, never prompted)")
71
+ .option("--project-id <uuid>", "Scope the bridge to one project (default: prompt, pod-wide if declined)")
75
72
  .option("--proactive-channel <id>", "Discord channel id for the agent's proactive posts")
76
73
  .option("--enable-react", "Turn on ✅-react capture")
74
+ // Re-running bridge-setup on an already-provisioned pod now DETECTS what is
75
+ // configured and skips it (the bot token is already in the vault — re-asking
76
+ // for a secret the pod already holds was the sharpest instance). This flag
77
+ // restores the old behaviour for a genuine rotation or repoint.
78
+ .option("--reconfigure", "Re-ask every question and re-provision from scratch (ignore what a previous run configured)")
77
79
  .option("--pod <name>", "Pod profile to connect the bridge to (default: prompt)")
78
- .option("--governance <mode>", "Agent governance preset: safe | normal | crazy (default: prompt)")
80
+ .option("--governance <mode>", "Agent approval preset: safe | creates | normal | crazy (default: prompt; \"follow the pod default\" writes nothing)")
79
81
  .option("--pod-url <url>", "Synap pod URL (override)")
80
82
  .option("--api-key <key>", "Hub Protocol API key (override)")
81
83
  .action(async (opts) => {
82
84
  const { bridgeSetup } = await import("./commands/bridge-setup.js");
83
85
  await bridgeSetup(opts);
84
86
  });
87
+ // Hidden: one-shot Proton Mail↔Synap bridge provisioning (dogfood/dev convenience).
88
+ // Fork of bridge-setup for the sovereign self-host Proton Bridge path — see
89
+ // the catalog capability synap-app/packages/workspace-templates/capabilities/proton.capability.json.
85
90
  program
86
- .command("connect")
87
- .description("Connect an AI surface (Claude Code, Claude Desktop, Cursor, Raycast) to a Synap pod")
88
- .option("--target <name>", "AI surface: claude-code | claude-desktop | cursor | raycast | custom")
91
+ .command("proton-setup", { hidden: true })
92
+ .description("(hidden) One-shot Proton Mail↔Synap bridge provisioning")
93
+ .option("--proton-email <email>", "Proton account email — provisioned into the pod vault")
94
+ .option("--proton-password <password>", "Proton account password (not a bridge-generated value) — provisioned into the pod vault")
95
+ .option("--proton-totp-seed <seed>", "Proton account 2FA/TOTP seed, base32 (optional) — provisioned into the pod vault")
96
+ .option("--proton-mailbox-password <password>", "Proton mailbox password — ONLY for two-password-mode accounts (optional) — provisioned into the pod vault")
97
+ .option("--bridge-dir <path>", "Path to the synap-proton-bridge repo")
98
+ .option("--workspace-id <uuid>", "Advanced/legacy: pin the bridge to one workspace (default: pod-wide, never prompted)")
99
+ .option("--project-id <uuid>", "Scope the bridge to one project (default: prompt, pod-wide if declined)")
100
+ .option("--pod <name>", "Pod profile to connect the bridge to (default: prompt)")
101
+ .option("--governance <mode>", "Agent approval preset: safe | creates | normal | crazy (default: prompt; \"follow the pod default\" writes nothing)")
102
+ .option("--pod-url <url>", "Synap pod URL (override)")
103
+ .option("--api-key <key>", "Hub Protocol API key (override)")
104
+ .action(async (opts) => {
105
+ const { protonSetup } = await import("./commands/proton-setup.js");
106
+ await protonSetup(opts);
107
+ });
108
+ program
109
+ .command("connect [thing]")
110
+ .description("Plug something into your pod: an AI agent (claude-code, cursor, codex, …) or a tool/account (gmail, github, …)")
111
+ .option("--tool", "Treat <thing> as a tool/account even if it is also an agent name (no <thing>: list connectable services)")
112
+ .option("--workspace <id>", "Focus on one workspace (default: pod-wide)")
113
+ .option("--project <id>", "Focus on one project (agents only; composable with --workspace)")
89
114
  .option("--pod-url <url>", "Synap pod URL")
90
115
  .option("--api-key <key>", "Hub Protocol API key")
91
- .option("--name <name>", "Pod profile name to save credentials under (default: 'default')")
92
- .option("--list", "List supported targets and exit")
93
- .option("--manual-key", "Skip the browser approval flow and paste a key (or provisioning token)")
94
- .option("--pin-workspace <id>", "Pin the connection to one workspace (default: pod-wide lens)")
95
- .option("--pin-project <id>", "Pin the connection to one project (composable with --pin-workspace)")
96
- .action(async (opts) => {
116
+ .option("--name <name>", "Pod profile to use (default: active pod)")
117
+ .option("--list", "List what can be connected and exit")
118
+ .option("--with-mcp", "Raycast only: also install the full 56-tool MCP server via mcp-remote (names overlap @synap)")
119
+ .option("--target <name>", "Legacy spelling of <thing>")
120
+ .option("--pin-workspace <id>", "Legacy spelling of --workspace")
121
+ .option("--pin-project <id>", "Legacy spelling of --project")
122
+ .action(async (thing, opts) => {
97
123
  const { connect } = await import("./commands/connect.js");
98
- await connect(opts);
124
+ await connect(thing, opts);
99
125
  });
100
126
  // ─── pods ─────────────────────────────────────────────────────────────────────
101
127
  const pods = program
@@ -158,8 +184,10 @@ program
158
184
  });
159
185
  program
160
186
  .command("doctor")
161
- .description("Coherence preflight: pod host resolves, key authenticates, workspace/project exist on THIS pod — catches config drift in one call")
187
+ .description("Coherence preflight: pod host resolves, key authenticates, workspace/project exist on THIS pod, and why each configured key does or does not work — catches config drift in one call")
162
188
  .option("--json", "Output as JSON")
189
+ .option("--fix", "Fix key problems: finish a pending approval, give this computer its own key, or rewrite stale copies — then verify over MCP")
190
+ .option("-v, --verbose", "Every check with its details, ids and addresses")
163
191
  .action(async (opts) => {
164
192
  const { doctor } = await import("./commands/doctor.js");
165
193
  await doctor(opts);
@@ -174,10 +202,11 @@ program
174
202
  });
175
203
  program
176
204
  .command("whoami")
177
- .description("Show the winning API key's identity/scopes/workspace and flag env-vs-surface key divergence")
178
- .action(async () => {
205
+ .description("Show key owner vs EFFECTIVE user (+ isAgent), scopes/workspace, and flag env-vs-surface key divergence")
206
+ .option("--json", "Output as JSON")
207
+ .action(async (opts) => {
179
208
  const { whoami } = await import("./commands/whoami.js");
180
- await whoami();
209
+ await whoami(opts);
181
210
  });
182
211
  // Hidden: compact ANSI line for the Claude Code statusLine (reads stdin JSON).
183
212
  program
@@ -190,59 +219,10 @@ program
190
219
  });
191
220
  program
192
221
  .command("update")
193
- .description("Update synap skill and check for CLI updates")
194
- .option("--server", "Also trigger a Dokploy redeploy of the control-plane")
195
- .option("--all", "Trigger a Dokploy redeploy of all registered services")
196
- .action(async (opts) => {
197
- const { update } = await import("./commands/update.js");
198
- await update(opts);
199
- });
200
- // ─── infra ────────────────────────────────────────────────────────────────────
201
- const infra = program
202
- .command("infra")
203
- .description("Manage servers and deployments via Dokploy");
204
- infra
205
- .command("status", { isDefault: true })
206
- .description("Overview: all servers and services status")
222
+ .description("Show how to update the CLI")
207
223
  .action(async () => {
208
- const { infraStatus } = await import("./commands/infra.js");
209
- await infraStatus();
210
- });
211
- infra
212
- .command("deploy <app>")
213
- .description("Trigger a redeploy for a named service")
214
- .action(async (app) => {
215
- const { infraDeploy } = await import("./commands/infra.js");
216
- await infraDeploy(app);
217
- });
218
- infra
219
- .command("logs <app>")
220
- .description("Tail logs for a service")
221
- .option("-n, --lines <n>", "Number of log lines", "100")
222
- .option("-f, --follow", "Follow log output")
223
- .action(async (app, opts) => {
224
- const { infraLogs } = await import("./commands/infra.js");
225
- await infraLogs(app, { lines: opts.lines ? parseInt(opts.lines, 10) : 100, follow: opts.follow });
226
- });
227
- infra
228
- .command("sync")
229
- .description("Pull Dokploy state into Synap entities (server + deployment profiles)")
230
- .option("--pod-url <url>", "Synap pod URL", process.env.SYNAP_POD_URL)
231
- .option("--api-key <key>", "Hub Protocol API key", process.env.SYNAP_API_KEY)
232
- .action(async (opts) => {
233
- if (!opts.podUrl || !opts.apiKey) {
234
- console.error(" --pod-url and --api-key are required (or set SYNAP_POD_URL / SYNAP_API_KEY)");
235
- process.exit(1);
236
- }
237
- const { infraSync } = await import("./commands/infra.js");
238
- await infraSync(opts.podUrl, opts.apiKey);
239
- });
240
- infra
241
- .command("open")
242
- .description("Open the Dokploy dashboard in your browser")
243
- .action(async () => {
244
- const { infraOpen } = await import("./commands/infra.js");
245
- infraOpen();
224
+ const { update } = await import("./commands/update.js");
225
+ await update();
246
226
  });
247
227
  program
248
228
  .command("switch [name]")
@@ -254,11 +234,31 @@ program
254
234
  program
255
235
  .command("connections")
256
236
  .alias("conn")
257
- .description("Show which pod each agent surface (Claude Code, Desktop, Cursor, Raycast) is connected to")
237
+ .description("Show everything connected: which pod each agent points at, and which tools the pod has connected")
258
238
  .action(async () => {
259
239
  const { connections } = await import("./commands/connections.js");
260
240
  await connections();
261
241
  });
242
+ // ─── app ────────────────────────────────────────────────────────────────────
243
+ // The App Connect flow: a `synap.app.json` in your app repo → one command wires
244
+ // the app's key into its deploy target. See APP-CONNECT-SPEC §5.
245
+ const app = program
246
+ .command("app")
247
+ .description("Connect an app in this repo to your pod (synap.app.json)");
248
+ app
249
+ .command("init")
250
+ .description("Write synap.app.json (asks name, pod, permissions) and register the app")
251
+ .action(async () => {
252
+ const { appInitCommand } = await import("./commands/app.js");
253
+ await appInitCommand();
254
+ });
255
+ app
256
+ .command("connect")
257
+ .description("Request the app's access, await approval, then write its key into the deploy target")
258
+ .action(async () => {
259
+ const { appConnectCommand } = await import("./commands/app.js");
260
+ await appConnectCommand();
261
+ });
262
262
  // ─── mcp ────────────────────────────────────────────────────────────────────
263
263
  // The MCP front door: point any AI client at the pod's /mcp server.
264
264
  const mcp = program
@@ -288,13 +288,45 @@ mcp
288
288
  const { mcpVerify } = await import("./commands/mcp.js");
289
289
  await mcpVerify(opts);
290
290
  });
291
+ mcp
292
+ .command("bridge")
293
+ .description("Stdio MCP server for Claude Code: forwards to the pod and names the conversation (spawned by Claude Code, not run by hand)")
294
+ .option("--url <url>", "The pod MCP URL to forward to")
295
+ .action(async (opts) => {
296
+ const { mcpBridge } = await import("./commands/mcp.js");
297
+ await mcpBridge(opts);
298
+ });
299
+ mcp
300
+ .command("use-bridge")
301
+ .description("Connect Claude Code through the stdio bridge, so work can be traced to and reopened in its conversation")
302
+ .option("--off", "Switch back to the direct http connection")
303
+ .action(async (opts) => {
304
+ const { mcpUseBridge } = await import("./commands/mcp.js");
305
+ await mcpUseBridge(opts);
306
+ });
291
307
  mcp
292
308
  .command("connect [client]")
293
- .description("Write the MCP config for a file-configurable client (Claude Code, Cursor, Desktop, …) — alias of `synap connect`")
309
+ .description("Write the MCP config for a file-configurable client (Claude Code, Cursor, Desktop, ChatGPT, …) — alias of `synap agents add <client>`")
294
310
  .option("--name <name>", "Pod profile to connect")
295
311
  .action(async (client, opts) => {
296
312
  const { connect } = await import("./commands/connect.js");
297
- await connect({ target: client, name: opts.name });
313
+ await connect(client, { name: opts.name });
314
+ });
315
+ mcp
316
+ .command("connect-chatgpt")
317
+ .description("Connect ChatGPT (web) to Synap — API-key connector to your pod")
318
+ .option("--name <name>", "Pod profile to connect")
319
+ .action(async (opts) => {
320
+ const { connect } = await import("./commands/connect.js");
321
+ await connect("chatgpt", { name: opts.name });
322
+ });
323
+ mcp
324
+ .command("connect-chatgpt-oauth")
325
+ .description("Connect ChatGPT (web) via Control Plane OAuth — multi-pod, no key to paste")
326
+ .option("--name <name>", "Pod profile to connect")
327
+ .action(async (opts) => {
328
+ const { connect } = await import("./commands/connect.js");
329
+ await connect("chatgpt-oauth", { name: opts.name });
298
330
  });
299
331
  mcp
300
332
  .command("connect-claude")
@@ -307,6 +339,17 @@ mcp
307
339
  const { mcpConnectClaudeWeb } = await import("./commands/mcp.js");
308
340
  await mcpConnectClaudeWeb(opts);
309
341
  });
342
+ mcp
343
+ .command("ui-setup <client>")
344
+ .description("Print the exact form fields to fill in the client's MCP/Connector UI (ChatGPT, Codex, Cursor, etc.)")
345
+ .option("--name <name>", "Pod profile to use (default: active)")
346
+ .option("--workspace <id>", "Scope to a specific workspace")
347
+ .option("--project <id>", "Focus the agent on a project")
348
+ .option("--json", "Output as JSON")
349
+ .action(async (client, opts) => {
350
+ const { mcpUiSetup } = await import("./commands/mcp.js");
351
+ await mcpUiSetup(client, opts);
352
+ });
310
353
  program
311
354
  .command("login")
312
355
  .description("Connect your Synap account (private templates) + your pods")
@@ -323,11 +366,9 @@ program
323
366
  }
324
367
  const profiles = listPodProfiles();
325
368
  if (profiles.length === 0) {
326
- // No pod saved — guide to pods add
327
- console.log(chalk.dim(" No pod configured yet."));
328
- console.log(chalk.blue(" Adding a new pod...\n"));
329
- const { podsAdd } = await import("./commands/pods.js");
330
- await podsAdd();
369
+ // No pod saved: sign in to the account first, it finds the pods.
370
+ const { podsFirstLogin } = await import("./commands/pods.js");
371
+ await podsFirstLogin();
331
372
  return;
332
373
  }
333
374
  // Already have pods — show status and offer reconnect if any are unreachable
@@ -366,8 +407,23 @@ program
366
407
  console.log(` ${active}${chalk.bold(p.name.padEnd(14))} ${status} ${chalk.dim(p.config.podUrl)}`);
367
408
  }
368
409
  console.log();
410
+ // A computer key (device flow) that expires within 7 days, or has: connect
411
+ // this computer again now — the pod offers the same computer, so its
412
+ // agents, history and rules are kept. Never without a person to approve.
413
+ const { computerKeyNeedsRenewal, connectThisComputer } = await import("./lib/device-connect.js");
414
+ const renewed = new Set();
415
+ for (const p of checks.filter((c) => c.healthy && computerKeyNeedsRenewal(c.config))) {
416
+ const when = `this computer's key ${p.keyValid ? "expires" : "expired"} ${p.config.keyExpiresAt}`;
417
+ if (!process.stdin.isTTY) {
418
+ console.log(chalk.yellow(` ${p.name}: ${when}. Run synap login in a terminal to renew it.`));
419
+ continue;
420
+ }
421
+ console.log(chalk.yellow(` ${p.name}: ${when}. Renewing it.`));
422
+ if (await connectThisComputer(p.config.podUrl, { name: p.name }))
423
+ renewed.add(p.name);
424
+ }
369
425
  // Anything that won't authenticate — unreachable OR a revoked/expired key.
370
- const needsReconnect = checks.filter((p) => !p.healthy || !p.keyValid);
426
+ const needsReconnect = checks.filter((p) => !renewed.has(p.name) && (!p.healthy || !p.keyValid));
371
427
  if (needsReconnect.length > 0) {
372
428
  console.log(chalk.yellow(` ${needsReconnect.length} pod(s) need re-authentication. Refresh credentials:`));
373
429
  for (const p of needsReconnect) {
@@ -424,6 +480,7 @@ program
424
480
  .description("Show a lightweight lens map: who you are, your projects (companies/initiatives), and workspaces (operational domains)")
425
481
  .option("--json", "Output as JSON")
426
482
  .option("--details", "Include per-workspace profiles + entity counts (heavier)")
483
+ .option("--workspace <space>", "Orient ONE space by name or id, for this call only — shows its brief (purpose, kinds, rules, skills) without changing your pinned lens")
427
484
  .option("--pod-url <url>", "Pod URL override")
428
485
  .option("--api-key <key>", "API key override")
429
486
  .action(async (opts) => {
@@ -449,9 +506,9 @@ program
449
506
  // See what an AI did across flows: the unified run feed, or one run's activity
450
507
  // timeline (for a capture: WHY a facet/entity dropped, with a fixHint).
451
508
  program
452
- .command("diagnose [runId]")
453
- .description("See what an AI did — the run feed, or one run's activity (why it did what it did)")
454
- .option("--flow <automation|playbook|capture|session>", "Restrict the feed, or the run's flow (default capture for a runId)")
509
+ .command("diagnose [run]")
510
+ .description("See what an AI did — the run feed, or one run's nodes (by id or flow name)")
511
+ .option("--flow <automation|playbook|capture|capability|session|chat>", "Restrict the feed, or narrow the run lookup to one flow")
455
512
  .option("--flow-id <id>", "Restrict the feed to one flow's runs (automationId / playbookId)")
456
513
  .option("--limit <n>", "Max runs in the feed")
457
514
  .option("--json", "Output raw JSON")
@@ -464,7 +521,9 @@ program
464
521
  // ─── use ──────────────────────────────────────────────────────────────────────
465
522
  program
466
523
  .command("use <workspace>")
467
- .description("Set the active workspace (by ID or name) — persisted to ~/.synap/config.json")
524
+ .description("Set the active workspace (by ID or name) — short form of `synap workspace use`. Default scope: this directory")
525
+ .option("--session", "Ephemeral: scope only this Claude Code session, don't persist")
526
+ .option("--global", "Durable machine-wide default (~/.synap/config.json) instead of this directory's .synap/lens.json")
468
527
  .option("--json", "Output as JSON")
469
528
  .option("--pod-url <url>", "Pod URL override")
470
529
  .option("--api-key <key>", "API key override")
@@ -475,7 +534,7 @@ program
475
534
  // ─── project / lens (per-Claude-session scoping) ──────────────────────────────
476
535
  const project = program
477
536
  .command("project")
478
- .description("Manage + focus the cross-cutting project lens (a company/initiative): list, new, use, clear");
537
+ .description("Manage + focus the project lens (a commitment, spanning workspaces): list, new, use, clear");
479
538
  project
480
539
  .command("list", { isDefault: true })
481
540
  .description("List projects on the active pod, with the pinned one marked (bare `synap project` runs this)")
@@ -488,7 +547,7 @@ project
488
547
  });
489
548
  project
490
549
  .command("new <name>")
491
- .description("Create a project (a company/initiative) on the active pod, then guide you to pin + add to it")
550
+ .description("Create a project (a commitment you are driving, with whom) on the active pod, then guide you to pin + add to it")
492
551
  .option("--description <text>", "Optional one-line description")
493
552
  .option("--json", "Output as JSON (incl. nextSteps for agents)")
494
553
  .option("--pod-url <url>", "Pod URL override")
@@ -501,6 +560,7 @@ project
501
560
  .command("use <ref>")
502
561
  .description("Pin the active project (durable, like `synap use <workspace>`). Ref = uuid, slug, or <pod>/<slug> — cross-pod refs switch the active pod too")
503
562
  .option("--session", "Ephemeral: scope only this Claude Code session, don't persist (cross-pod refs refused)")
563
+ .option("--global", "Durable machine-wide default (~/.synap/config.json) instead of this directory's .synap/lens.json")
504
564
  .option("--json", "Output as JSON")
505
565
  .option("--pod-url <url>", "Pod URL override")
506
566
  .option("--api-key <key>", "API key override")
@@ -531,8 +591,10 @@ project
531
591
  });
532
592
  program
533
593
  .command("lens")
534
- .description("Show this Claude session's lens (workspace + project + focus session)")
594
+ .description("Show the effective lens (workspace + project + focus session) and WHICH rung set each one")
535
595
  .option("--json", "Output as JSON")
596
+ .option("--pod-url <url>", "Pod URL override")
597
+ .option("--api-key <key>", "API key override")
536
598
  .action(async (opts) => {
537
599
  const { showLens } = await import("./commands/lens.js");
538
600
  await showLens(opts);
@@ -542,11 +604,12 @@ program
542
604
  // procedural (how-to docs), episodic (captures) — and returns a glass-box answer.
543
605
  program
544
606
  .command("ask <query>")
545
- .description("Ask your pod anything — routes to the right knowledge substrate(s) and shows which answered")
607
+ .description("Ask your pod anything — returns a synthesized answer with its sources")
546
608
  .option("--workspace <id>", "Scope to a specific workspace (omit for pod-wide)")
547
609
  .option("--limit <n>", "Max results per substrate", "10")
548
610
  .option("--json", "Output as JSON")
549
611
  .option("--session", "Scope retrieval to the active session context")
612
+ .option("--raw", "Skip synthesis: show the raw per-substrate matches instead")
550
613
  .option("--compare", "A/B diagnostic: show baseline vs Horizon rankers side-by-side (read-only, does not change the answer)")
551
614
  .option("--pod-url <url>", "Pod URL override")
552
615
  .option("--api-key <key>", "API key override")
@@ -555,21 +618,25 @@ program
555
618
  await askKnowledge(query, opts);
556
619
  });
557
620
  // ─── capture ──────────────────────────────────────────────────────────────────
558
- // Uses the active pod + workspace from config (set via `synap pods use` /
559
- // `synap use` / `synap workspace provision-agent`). No auth flags needed.
621
+ // Uses the active pod from config. Work-lane knowledge does NOT default-pin
622
+ // the active workspace (omit workspaceId → server-derived / pod-wide preferred);
623
+ // pin only with --workspace or --team.
560
624
  program
561
625
  .command("capture [text]")
562
- .description("Capture knowledge: smart `capture \"<free text>\"` (AI structures it) OR typed `capture --type gotcha --claim \"…\"`")
563
- .option("--type <type>", "Typed mode: gotcha|lesson|decision|reference")
626
+ .description("Capture knowledge: smart `capture \"<free text>\"` (AI structures it). For canonical typed Knowledge, use `create entity --profile=knowledge --props='{\"knowledgeForm\":\"insight|caution\"}'`.")
627
+ .option("--type <type>", "Legacy compatibility typed mode (gotcha|lesson|decision|reference); new automation should use knowledgeForm")
564
628
  .option("--claim <text>", "Typed mode: one-line assertion (required with --type)")
629
+ .option("--title <text>", "Typed mode: short name, 80 characters or less (default: the claim's first sentence, never cut)")
630
+ .option("--content <markdown>", "Typed mode: full Markdown explanation, stored as the linked Knowledge note")
565
631
  .option("--why <text>", "Reasoning or context")
566
632
  .option("--evidence <text>", "Supporting file path, URL, or code snippet")
567
633
  .option("--tags <csv>", "Comma-separated tags e.g. repo:synap-backend,layer:migrations")
568
634
  .option("--session", "Link captured knowledge to the active session")
569
- .option("--global", "GLOBAL lane: pod-wide cross-cutting runbook (→ knowledge_keys), visible in every workspace")
570
- .option("--key <ns:slug>", "Stable key for a --global runbook (derived from --type + --claim if omitted)")
571
- .option("--team", "Write to the first product workspace instead of the active workspace")
572
- .option("--workspace <id>", "Explicit workspace override")
635
+ .option("--pod-wide", "GLOBAL lane: pod-wide cross-cutting runbook (→ knowledge_keys), visible in every workspace")
636
+ .option("--global", "Alias for --pod-wide")
637
+ .option("--key <ns:slug>", "Stable key for a --pod-wide runbook (derived from --type + --claim if omitted)")
638
+ .option("--team", "Pin to the first product workspace (default: omit workspaceId — server-derived / pod-wide preferred for knowledge)")
639
+ .option("--workspace <id>", "Explicit workspace pin (otherwise workspaceId is omitted for knowledge)")
573
640
  .option("--project <id|name>", "Pin the capture to a project (id or name) — overrides SYNAP_PROJECT_ID / the session lens")
574
641
  .option("--yes", "Skip the y/N confirmation prompt (smart mode)")
575
642
  .option("--json", "Output as JSON")
@@ -582,10 +649,14 @@ program
582
649
  // the SAME AI capture pipeline (/capture/structure → /capture/execute), so it
583
650
  // inherits entity extraction, the relation graph, and workspace/project routing.
584
651
  program
585
- .command("import <inputs...>")
652
+ .command("import [inputs...]")
586
653
  .description("Import files, folders, or URLs into the pod (AI capture pipeline, or Superwhisper store-first)")
587
654
  .option("--workspace <id|name>", "Override AI workspace routing")
588
655
  .option("--project <id|name>", "Override AI project routing")
656
+ .option("--home-map <pairs>", "Multi-home path map: pathSubstring=workspaceNameOrId pairs " +
657
+ '(e.g. "Projects=Builder,Posts=Content OS"). Matched batch paths are ' +
658
+ "rewritten with the workspace name as the first segment so placement " +
659
+ "heuristics pin the right home. No product defaults.")
589
660
  .option("--dry-run", "Preview only — do not write")
590
661
  .option("--yes", "Skip confirmation / auto-confirm store-first")
591
662
  .option("--session", "Link created entities to the active session")
@@ -594,14 +665,20 @@ program
594
665
  .option("--api-key <key>", "API key override")
595
666
  .option("--source <name>", "Adapter: superwhisper (paired meta.json+output.wav store-first)")
596
667
  .option("--store-first", "Skip AI structure; store units as pod-wide notes (+ optional audio)")
668
+ .option("--keep-raw", "Keep the original file and link it to the created entity (default: extract, then discard the bytes)")
597
669
  .option("--with-audio", "Upload WAV provenance (default on for --source superwhisper --store-first)")
598
670
  .option("--no-with-audio", "Transcript-only (no WAV upload)")
599
671
  .option("--limit <n>", "Max Superwhisper units to process", (v) => parseInt(v, 10))
600
672
  .option("--concurrency <n>", "Parallel unit uploads (1–4)", (v) => parseInt(v, 10))
601
673
  .option("--no-resume", "Ignore local import ledger (re-import all)")
674
+ .option("--job-status <jobId>", "Re-poll a background corpus import started by an earlier run (no waiting)")
602
675
  .action(async (inputs, opts) => {
603
- const { importData } = await import("./commands/import.js");
604
- await importData(inputs, opts);
676
+ const mod = await import("./commands/import.js");
677
+ if (opts.jobStatus) {
678
+ await mod.importJobStatus(opts.jobStatus, opts);
679
+ return;
680
+ }
681
+ await mod.importData(inputs ?? [], opts);
605
682
  });
606
683
  // ─── upload ───────────────────────────────────────────────────────────────────
607
684
  // Store a real file (arbitrary bytes) on the pod — the door an AI/CLI needs to
@@ -609,8 +686,10 @@ program
609
686
  // which mints a `file` entity for the bytes.
610
687
  program
611
688
  .command("upload <path>")
612
- .description("Upload a file (≤10MB) to the pod as a file entity")
613
- .option("--workspace <id>", "Workspace to store the file in")
689
+ .description("Upload a file to the pod as an entity (>10MB goes direct to storage: video ≤500MB, audio ≤100MB, zip ≤200MB)")
690
+ .option("--workspace <id>", "Pin the entity to this workspace (explicit placement)")
691
+ .option("--profile <slug>", "Entity kind to create (default: file), e.g. brand-asset")
692
+ .option("--prop <key=value>", "Entity property (repeatable)", (v, acc = []) => [...acc, v])
614
693
  .option("--attach <entityId>", "After upload, link the document to this entity (references relation)")
615
694
  .option("--title <title>", "Title for the created document")
616
695
  .option("--open", "Open in the Synap desktop app after upload")
@@ -622,6 +701,8 @@ Examples:
622
701
  synap upload ./report.pdf
623
702
  synap upload ./logo.png --title "Brand logo" --workspace <id>
624
703
  synap upload ./spec.pdf --attach <entityId>
704
+ synap upload ./logo.svg --profile brand-asset --prop variant=dark --workspace <id>
705
+ synap upload ./launch.mp4 --workspace <id>
625
706
  synap upload ./report.pdf --open
626
707
  `)
627
708
  .action(async (path, opts) => {
@@ -682,7 +763,42 @@ program
682
763
  // commands (capture, recall, search, …) inherit the active pod + workspace.
683
764
  const workspace = program
684
765
  .command("workspace")
685
- .description("Set up and switch workspace context (run once — persisted to ~/.synap/config.json)");
766
+ .description("Manage + focus the workspace lens (an operational domain): list, use, clear");
767
+ // `workspace use|list|clear` mirror `project use|list|clear` so the two
768
+ // composable lenses read as siblings. ADDITIVE: the original short form
769
+ // `synap use <workspace>` and `synap list workspaces` are untouched and keep
770
+ // working exactly as before — these are aliases, not replacements.
771
+ workspace
772
+ .command("use <workspace>")
773
+ .description("Pin the active workspace by ID or name (durable, like `synap use <workspace>`). Default scope: this directory")
774
+ .option("--session", "Ephemeral: scope only this Claude Code session, don't persist")
775
+ .option("--global", "Durable machine-wide default (~/.synap/config.json) instead of this directory's .synap/lens.json")
776
+ .option("--json", "Output as JSON")
777
+ .option("--pod-url <url>", "Pod URL override")
778
+ .option("--api-key <key>", "API key override")
779
+ .action(async (ws, opts) => {
780
+ const { useWorkspace } = await import("./commands/data.js");
781
+ await useWorkspace(ws, opts);
782
+ });
783
+ workspace
784
+ .command("list", { isDefault: true })
785
+ .description("List workspaces you belong to (bare `synap workspace` runs this)")
786
+ .option("--json", "Output as JSON")
787
+ .option("--pod-url <url>", "Pod URL override")
788
+ .option("--api-key <key>", "API key override")
789
+ .action(async (opts) => {
790
+ const { listWorkspaces } = await import("./commands/data.js");
791
+ await listWorkspaces(opts);
792
+ });
793
+ workspace
794
+ .command("clear")
795
+ .description("Clear the active workspace focus (durable by default)")
796
+ .option("--session", "Ephemeral: clear only this Claude Code session's workspace")
797
+ .option("--json", "Output as JSON")
798
+ .action(async (opts) => {
799
+ const { clearWorkspace } = await import("./commands/data.js");
800
+ await clearWorkspace(opts);
801
+ });
686
802
  workspace
687
803
  .command("provision-agent")
688
804
  .description("Provision an agent-owned workspace and set it as active (idempotent)")
@@ -706,6 +822,54 @@ workspace
706
822
  const { workspacePurge } = await import("./commands/purge.js");
707
823
  await workspacePurge(target, opts);
708
824
  });
825
+ // R8a — governed lifecycle: an agent key gets a proposal, an owner key applies.
826
+ workspace
827
+ .command("archive <workspace>")
828
+ .description("Archive a workspace (hides it, pauses its workspace automations). Governed: agents propose")
829
+ .option("--reason <text>", "Why — shown to the reviewer")
830
+ .option("--json", "Output as JSON")
831
+ .option("--pod-url <url>", "Pod URL override")
832
+ .option("--api-key <key>", "API key override")
833
+ .action(async (target, opts) => {
834
+ const { workspaceArchive } = await import("./commands/workspace-ops.js");
835
+ await workspaceArchive(target, opts);
836
+ });
837
+ workspace
838
+ .command("restore <workspace>")
839
+ .description("Restore an archived workspace (by id). Re-enables nothing; lists automations still paused")
840
+ .option("--reason <text>", "Why — shown to the reviewer")
841
+ .option("--json", "Output as JSON")
842
+ .option("--pod-url <url>", "Pod URL override")
843
+ .option("--api-key <key>", "API key override")
844
+ .action(async (target, opts) => {
845
+ const { workspaceRestore } = await import("./commands/workspace-ops.js");
846
+ await workspaceRestore(target, opts);
847
+ });
848
+ workspace
849
+ .command("rename <workspace> <newName>")
850
+ .description("Rename a workspace. Governed: agents propose")
851
+ .option("--json", "Output as JSON")
852
+ .option("--pod-url <url>", "Pod URL override")
853
+ .option("--api-key <key>", "API key override")
854
+ .action(async (target, newName, opts) => {
855
+ const { workspaceRename } = await import("./commands/workspace-ops.js");
856
+ await workspaceRename(target, newName, opts);
857
+ });
858
+ // R8a — `synap entity move`. (`get/create/set entity` stay under their verbs.)
859
+ program
860
+ .command("entity")
861
+ .description("Entity operations: move")
862
+ .command("move <entityIds...>")
863
+ .description("Move entities into another workspace. Governed per entity: moved / proposed / errors")
864
+ .requiredOption("--to <workspace>", "Destination workspace id or name")
865
+ .option("--reason <text>", "Why they belong there — shown to the reviewer")
866
+ .option("--json", "Output as JSON")
867
+ .option("--pod-url <url>", "Pod URL override")
868
+ .option("--api-key <key>", "API key override")
869
+ .action(async (entityIds, opts) => {
870
+ const { entityMove } = await import("./commands/workspace-ops.js");
871
+ await entityMove(entityIds, opts);
872
+ });
709
873
  // ─── list ─────────────────────────────────────────────────────────────────────
710
874
  const list = program
711
875
  .command("list")
@@ -819,8 +983,10 @@ const proposals = program
819
983
  .description("Review and act on governance proposals");
820
984
  proposals
821
985
  .command("list", { isDefault: true })
822
- .description("List pending governance proposals for this agent")
986
+ .description("List governance proposals (default: the pending queue)")
987
+ .option("--status <status>", "pending (default) | approved | auto_approved (what the agent already did) | rejected | expired (never decided) | all", "pending")
823
988
  .option("--workspace <id>", "Scope to a specific workspace")
989
+ .option("--session <id>", "Scope to one focus session (the agent run that filed them)")
824
990
  .option("--limit <n>", "Max results", "20")
825
991
  .option("--json", "Output as JSON")
826
992
  .option("--pod-url <url>", "Pod URL override")
@@ -831,8 +997,11 @@ proposals
831
997
  });
832
998
  proposals
833
999
  .command("approve <id>")
834
- .description("Approve a pending proposal")
1000
+ .description("Approve a pending proposal (interactive only — requires a TTY and a typed confirmation)")
835
1001
  .option("--reason <text>", "Optional approval note")
1002
+ .option("--verb <verb>", "Same-name choice: fill_empty | keep_existing | use_capture | separate")
1003
+ .option("--existing <uuid>", "Existing record id — required for fill_empty, keep_existing, and use_capture")
1004
+ .option("--yes", "Accepted for symmetry with other commands, but does NOT skip the typed confirmation — approval is always deliberate")
836
1005
  .option("--json", "Output as JSON")
837
1006
  .option("--pod-url <url>", "Pod URL override")
838
1007
  .option("--api-key <key>", "API key override")
@@ -851,6 +1020,39 @@ proposals
851
1020
  const { rejectProposal } = await import("./commands/proposals-actions.js");
852
1021
  await rejectProposal(id, opts);
853
1022
  });
1023
+ proposals
1024
+ .command("await <id>")
1025
+ .description("Block until a proposal is decided. Exit 0 approved · 1 rejected/expired/withdrawn · 2 timeout")
1026
+ .option("--timeout <duration>", "Give up after this long (e.g. 30m, 2h)", "30m")
1027
+ .option("--interval <duration>", "Delay between checks (e.g. 10s)", "10s")
1028
+ .option("--json", "Output the final status as JSON")
1029
+ .option("--pod-url <url>", "Pod URL override")
1030
+ .option("--api-key <key>", "API key override")
1031
+ .action(async (id, opts) => {
1032
+ const { awaitProposalCommand } = await import("./commands/proposals-await.js");
1033
+ await awaitProposalCommand(id, opts);
1034
+ });
1035
+ // ─── worker ───────────────────────────────────────────────────────────────────
1036
+ // Run Claude Code unattended on THIS machine for the sessions handed to an agent
1037
+ // (the session roster → orient handedToYou). See src/commands/worker.ts.
1038
+ program
1039
+ .command("worker")
1040
+ .description("Run Claude Code unattended here for the sessions handed to an agent")
1041
+ .requiredOption("--agent <name>", "The named agent this machine works as (its key comes from ~/.synap)")
1042
+ .option("--interval <dur>", "How often to check for handed sessions (e.g. 30s, 2m)", "30s")
1043
+ .option("--once", "Check once, run what is handed, wait for it, exit")
1044
+ .option("--cwd <dir>", "Where the launched Claude Code works (default ~/Documents/Code/synap)")
1045
+ .option("--max-parallel <n>", "Runs at the same time", "1")
1046
+ .option("--claude-bin <path>", "The claude binary", "claude")
1047
+ .option("--claude-config-dir <dir>", "CLAUDE_CONFIG_DIR for launched runs — picks which Claude subscription they use")
1048
+ .option("--mcp-server <name>", "Claude Code MCP server name for this pod (detected from ~/.claude.json by default)")
1049
+ .option("--allowed-tools <list>", "Comma-separated Claude tools allowed without asking (the pod MCP server is always added)")
1050
+ .option("--skip-permissions", "Launch with --dangerously-skip-permissions instead of an allow-list")
1051
+ .option("--dry-run", "Claim and log what would launch, without launching Claude")
1052
+ .action(async (opts) => {
1053
+ const { workerCommand } = await import("./commands/worker.js");
1054
+ await workerCommand(opts);
1055
+ });
854
1056
  // ─── session ──────────────────────────────────────────────────────────────────
855
1057
  // Focus sessions — goal-bound work rooms tracked on the pod.
856
1058
  // AI agents: use start_session to begin, update_session to report progress,
@@ -864,7 +1066,14 @@ session
864
1066
  .description("Start a new focus session and return its ID")
865
1067
  .requiredOption("--goal <text>", "What this session is working toward")
866
1068
  .option("--workspace <id>", "Workspace that owns the session")
1069
+ .option("--project <id>", "Project that owns the session (defaults to the active project lens; the pod accepts a workspace OR a project)")
867
1070
  .option("--task-id <id>", "Link session to an existing task entity")
1071
+ .option("--track <id>", "Born inside a track (a project's lasting goal) — implies its project")
1072
+ .option("--template <id>", "Start from a playbook (seeds currentStage from its stages)")
1073
+ .option("--no-template", "Opt out of auto-matching a playbook — start ad-hoc")
1074
+ .option("--parent <id>", "Push from another session — records session --spawned_from--> session (the parent stays open)")
1075
+ .option("--suspended-intent <text>", "With --parent: this session BLOCKS the parent. One line naming what the parent was about to do; the parent waits until this session closes")
1076
+ .option("--criteria <file.json>", "Path to a JSON file of binary acceptance criteria (SessionCriterion[])")
868
1077
  .option("--json", "Output as JSON")
869
1078
  .option("--pod-url <url>", "Pod URL override")
870
1079
  .option("--api-key <key>", "API key override")
@@ -876,7 +1085,9 @@ session
876
1085
  .command("list", { isDefault: true })
877
1086
  .description("List focus sessions in the active workspace")
878
1087
  .option("--workspace <id>", "Scope to a specific workspace")
1088
+ .option("--project <id>", "Scope to a project (its sessions across spaces)")
879
1089
  .option("--status <status>", "Filter by status: active | paused | closed")
1090
+ .option("--kind <kind>", "Filter by population: work | run | receipt | all", "all")
880
1091
  .option("--limit <n>", "Max results", "20")
881
1092
  .option("--json", "Output as JSON")
882
1093
  .option("--pod-url <url>", "Pod URL override")
@@ -887,8 +1098,8 @@ session
887
1098
  });
888
1099
  session
889
1100
  .command("get <id>")
890
- .description("Show details for a specific focus session")
891
- .option("--workspace <id>", "Workspace that owns the session (required for scoped fetch)")
1101
+ .description("Show details for a specific focus session (works for project-scoped sessions)")
1102
+ .option("--workspace <id>", "Workspace hint if the pod still requires it for GET")
892
1103
  .option("--json", "Output as JSON")
893
1104
  .option("--pod-url <url>", "Pod URL override")
894
1105
  .option("--api-key <key>", "API key override")
@@ -898,10 +1109,14 @@ session
898
1109
  });
899
1110
  session
900
1111
  .command("update <id>")
901
- .description("Update progress or status of a focus session")
902
- .requiredOption("--workspace <id>", "Workspace that owns the session")
903
- .option("--progress <0-100>", "Progress percentage")
1112
+ .description("Update a focus session's progress, status, stage or goal")
1113
+ // NOT required: `workspaceId` is `.optional()` on the pod's UpdateBodySchema
1114
+ // and the session is addressed by <id>. Demanding it made an already-attached
1115
+ // or project-scoped session un-updatable without restating a known lens.
1116
+ .option("--workspace <id>", "Workspace that owns the session (optional; inferred from the active lens)")
1117
+ .option("--progress <0-100>", "Progress percentage — a whole number 0-100, not prose")
904
1118
  .option("--status <status>", "New status: active | paused")
1119
+ .option("--stage <key>", "Advance the session's current stage (playbook stage key)")
905
1120
  .option("--goal <text>", "Revised goal")
906
1121
  .option("--json", "Output as JSON")
907
1122
  .option("--pod-url <url>", "Pod URL override")
@@ -912,10 +1127,16 @@ session
912
1127
  });
913
1128
  session
914
1129
  .command("close <id>")
915
- .description("Close a focus session and optionally attach a recap")
916
- .requiredOption("--workspace <id>", "Workspace that owns the session")
917
- .option("--recap <text>", "Short summary of what was accomplished")
918
- .option("--json", "Output as JSON")
1130
+ .description("End a focus session (proposal pack) and optionally attach a recap")
1131
+ .option("--workspace <id>", "Workspace hint (optional; not required for complete)")
1132
+ .option("--recap <text>", "Short summary of what was accomplished (maps to summary)")
1133
+ // Deliberately NOT validated here, unlike --progress. A bad --progress
1134
+ // serialized to NaN and came back as an unreadable zod echo; a bad --as comes
1135
+ // back naming the three legal values, because the pod's enum derives from
1136
+ // TERMINAL_SESSION_STATUSES. Re-listing them here would be a second copy of
1137
+ // an enum this CLI cannot import — the hand-mirrored-vocabulary defect.
1138
+ .option("--as <status>", "How it ended: closed (default, finished) | cancelled (abandoned) | failed (could not complete)")
1139
+ .option("--json", "Output full pack (or close result) as JSON")
919
1140
  .option("--pod-url <url>", "Pod URL override")
920
1141
  .option("--api-key <key>", "API key override")
921
1142
  .action(async (id, opts) => {
@@ -942,12 +1163,162 @@ session
942
1163
  });
943
1164
  session
944
1165
  .command("current")
945
- .description("Show the active session for this terminal (from .synap-session or SYNAP_SESSION_ID)")
1166
+ .description("Show the active session for this terminal (SYNAP_SESSION_ID, Claude-session lens, or .synap/lens.json)")
946
1167
  .option("--json", "Output as JSON")
947
1168
  .action(async (opts) => {
948
1169
  const { sessionStatus } = await import("./commands/sessions.js");
949
1170
  sessionStatus(opts);
950
1171
  });
1172
+ session
1173
+ .command("evidence <key>")
1174
+ .description("Post your own pass/fail report for one evidence-checked acceptance criterion")
1175
+ .option("--session <id>", "Focus session ID (defaults to this terminal's active session)")
1176
+ .option("--passed", "The criterion is satisfied")
1177
+ .option("--failed", "The criterion is not satisfied")
1178
+ .option("--detail <text>", "Optional note explaining the verdict")
1179
+ .option("--json", "Output as JSON")
1180
+ .option("--pod-url <url>", "Pod URL override")
1181
+ .option("--api-key <key>", "API key override")
1182
+ .action(async (key, opts) => {
1183
+ const { evidenceSession } = await import("./commands/sessions.js");
1184
+ await evidenceSession(key, opts);
1185
+ });
1186
+ session
1187
+ .command("evaluate")
1188
+ .description("Run every pending acceptance-criterion check (evidence, capability, judge) and print the verdicts")
1189
+ .option("--session <id>", "Focus session ID (defaults to this terminal's active session)")
1190
+ .option("--json", "Output as JSON")
1191
+ .option("--pod-url <url>", "Pod URL override")
1192
+ .option("--api-key <key>", "API key override")
1193
+ .action(async (opts) => {
1194
+ const { evaluateSession } = await import("./commands/sessions.js");
1195
+ await evaluateSession(opts);
1196
+ });
1197
+ session
1198
+ .command("wait <sessionId>")
1199
+ .description("Block until the human replies in the session room, then print the reply. Exit 0 replied · 1 timeout · 2 failure (auth/network/broken read)")
1200
+ .option("--since <iso>", "Only answers strictly after this ISO timestamp (default: now — skips anything that predates this wait)")
1201
+ .option("--timeout <duration>", "Give up after this long (e.g. 30m, 2h)", "30m")
1202
+ .option("--interval <duration>", "Delay between checks (e.g. 10s)", "10s")
1203
+ .option("--limit <n>", "Max answers per page, 1-100", "20")
1204
+ .option("--json", "Output the reply(ies) — or the timeout — as JSON")
1205
+ .option("--pod-url <url>", "Pod URL override")
1206
+ .option("--api-key <key>", "API key override")
1207
+ .action(async (sessionId, opts) => {
1208
+ const { waitSessionCommand } = await import("./commands/session-wait.js");
1209
+ await waitSessionCommand(sessionId, opts);
1210
+ });
1211
+ // ─── playbook ─────────────────────────────────────────────────────────────────
1212
+ // Start a governed process in one step — the Hub door MCP synap_run_playbook
1213
+ // shares. A proposal ("queued for your review") is success.
1214
+ const playbookCmd = program
1215
+ .command("playbook")
1216
+ .description("Run playbooks — governed, reusable processes");
1217
+ playbookCmd
1218
+ .command("run <name-or-id>")
1219
+ .description("Run a playbook by name or id, optionally on a subject entity")
1220
+ .option("--subject <entityId>", "The entity the playbook runs on")
1221
+ .option("--param <key=value>", "Answer a declared param (repeatable; typed by the playbook)", collect, [])
1222
+ .option("--workspace <id>", "Write home for the run (defaults to the playbook's own workspace)")
1223
+ .option("--reason <text>", "Why — shown to the reviewer")
1224
+ .option("--json", "Output as JSON")
1225
+ .option("--pod-url <url>", "Pod URL override")
1226
+ .option("--api-key <key>", "API key override")
1227
+ .action(async (ref, opts) => {
1228
+ const { runPlaybook } = await import("./commands/playbook.js");
1229
+ await runPlaybook(ref, opts);
1230
+ });
1231
+ // ─── track ────────────────────────────────────────────────────────────────────
1232
+ // A LASTING goal inside a project, worked in sessions (`session start --track`).
1233
+ // Legacy tracks also run a method with steps. Writes are governed —
1234
+ // "queued for your review" is success.
1235
+ const track = program
1236
+ .command("track")
1237
+ .description("Manage tracks — the lasting goals a project pursues");
1238
+ track
1239
+ .command("list")
1240
+ .description("List a project's tracks (defaults to the active project lens)")
1241
+ .option("--project <id>", "Project id (defaults to the active project lens)")
1242
+ .option("--all", "Include archived tracks")
1243
+ .option("--json", "Output as JSON")
1244
+ .option("--pod-url <url>", "Pod URL override")
1245
+ .option("--api-key <key>", "API key override")
1246
+ .action(async (opts) => {
1247
+ const { listTracks } = await import("./commands/tracks.js");
1248
+ await listTracks(opts);
1249
+ });
1250
+ track
1251
+ .command("show <id>")
1252
+ .description("Show one track: its steps and where it stands")
1253
+ .option("--json", "Output as JSON")
1254
+ .option("--pod-url <url>", "Pod URL override")
1255
+ .option("--api-key <key>", "API key override")
1256
+ .action(async (id, opts) => {
1257
+ const { showTrack } = await import("./commands/tracks.js");
1258
+ await showTrack(id, opts);
1259
+ });
1260
+ track
1261
+ .command("start")
1262
+ .description("Start a track — a lasting goal — on a project")
1263
+ .option("--name <name>", "The track's name (required without --playbook)")
1264
+ .option("--direction <text>", "Where it is heading, one line")
1265
+ .option("--playbook <id>", "Legacy: a track template (project-scoped playbook with steps)")
1266
+ .option("--project <id>", "Project id (defaults to the active project lens)")
1267
+ .option("--param <key=value>", "Answer a declared param (repeatable)", collect, [])
1268
+ .option("--reason <text>", "Why — shown to the reviewer")
1269
+ .option("--json", "Output as JSON")
1270
+ .option("--pod-url <url>", "Pod URL override")
1271
+ .option("--api-key <key>", "API key override")
1272
+ .action(async (opts) => {
1273
+ const { startTrack } = await import("./commands/tracks.js");
1274
+ await startTrack(opts);
1275
+ });
1276
+ track
1277
+ .command("advance <id> <stage>")
1278
+ .description("Legacy (tracks with steps): move a track to a step; prints the session that step offers")
1279
+ .option("--reason <text>", "Why — shown to the reviewer")
1280
+ .option("--json", "Output as JSON")
1281
+ .option("--pod-url <url>", "Pod URL override")
1282
+ .option("--api-key <key>", "API key override")
1283
+ .action(async (id, stage, opts) => {
1284
+ const { advanceTrack } = await import("./commands/tracks.js");
1285
+ await advanceTrack(id, stage, opts);
1286
+ });
1287
+ track
1288
+ .command("status <id> <status>")
1289
+ .description("Pause, resume or archive a track (active|paused|archived)")
1290
+ .option("--reason <text>", "Why — shown to the reviewer")
1291
+ .option("--json", "Output as JSON")
1292
+ .option("--pod-url <url>", "Pod URL override")
1293
+ .option("--api-key <key>", "API key override")
1294
+ .action(async (id, status, opts) => {
1295
+ const { setTrackStatus } = await import("./commands/tracks.js");
1296
+ await setTrackStatus(id, status, opts);
1297
+ });
1298
+ track
1299
+ .command("params <id> <pairs...>")
1300
+ .description("Answer the method's params as key=value (merged; key=null clears one)")
1301
+ .option("--reason <text>", "Why — shown to the reviewer")
1302
+ .option("--json", "Output as JSON")
1303
+ .option("--pod-url <url>", "Pod URL override")
1304
+ .option("--api-key <key>", "API key override")
1305
+ .action(async (id, pairs, opts) => {
1306
+ const { setTrackParams } = await import("./commands/tracks.js");
1307
+ await setTrackParams(id, pairs, opts);
1308
+ });
1309
+ track
1310
+ .command("start-step <id>")
1311
+ .description("Legacy (tracks with steps): start one step's session — on a track with no steps, use 'session start --track'")
1312
+ .option("--stage <key>", "Step key (defaults to the current step)")
1313
+ .option("--title <text>", "Session name (defaults to the step's name)")
1314
+ .option("--goal <text>", "Outcome (defaults to the step's goal)")
1315
+ .option("--json", "Output as JSON")
1316
+ .option("--pod-url <url>", "Pod URL override")
1317
+ .option("--api-key <key>", "API key override")
1318
+ .action(async (id, opts) => {
1319
+ const { startStep } = await import("./commands/tracks.js");
1320
+ await startStep(id, opts);
1321
+ });
951
1322
  // ─── skill ────────────────────────────────────────────────────────────────────
952
1323
  // Autonomous workspace session skills — context gathering, task planning, etc.
953
1324
  const skillCmd = program
@@ -1007,6 +1378,16 @@ skillCmd
1007
1378
  const { listSkills } = await import("./commands/skill-manage.js");
1008
1379
  await listSkills(opts);
1009
1380
  });
1381
+ skillCmd
1382
+ .command("approve <slug-or-id>")
1383
+ .description("Approve a skill so it loads (agent-authored skills are born unapproved)")
1384
+ .option("--revoke", "Revoke approval instead of granting it")
1385
+ .option("--pod-url <url>", "Pod URL override")
1386
+ .option("--api-key <key>", "API key override")
1387
+ .action(async (ref, opts) => {
1388
+ const { approveSkill } = await import("./commands/skill-manage.js");
1389
+ await approveSkill(ref, opts);
1390
+ });
1010
1391
  skillCmd
1011
1392
  .command("remove <slug>")
1012
1393
  .alias("rm")
@@ -1069,6 +1450,28 @@ observe
1069
1450
  .option("--pod-url <url>", "Pod URL override")
1070
1451
  .option("--api-key <key>", "API key override")
1071
1452
  .action(async (text, opts) => {
1453
+ // `write` is the DEFAULT subcommand, so an unrecognized token lands here as
1454
+ // `text` instead of erroring — and this default WRITES. `synap observe recal`
1455
+ // therefore recorded an observation literally reading "recal" into the
1456
+ // AI-maintained user model, silently. Every other `isDefault` group in this
1457
+ // file has a zero-arg default, which commander rejects on its own; this is
1458
+ // the only one that can absorb a typo, and the only one where absorbing it
1459
+ // is a mutation.
1460
+ //
1461
+ // The guard keys off whether `write` was named EXPLICITLY, not off the text:
1462
+ // a mistyped subcommand is always a single bare token, and a real
1463
+ // observation is a sentence. Going through `observe write <text>` skips the
1464
+ // check entirely, so the escape hatch is real rather than circular.
1465
+ const argv = process.argv;
1466
+ const observeAt = argv.indexOf("observe");
1467
+ const explicitWrite = observeAt !== -1 && argv[observeAt + 1] === "write";
1468
+ if (!explicitWrite && !/\s/.test(text.trim())) {
1469
+ const siblings = observe.commands.map((c) => c.name()).filter((n) => n !== "write");
1470
+ console.error(`error: '${text}' looks like a subcommand, not observation text — nothing was recorded.\n` +
1471
+ ` Available: ${siblings.map((n) => `synap observe ${n}`).join(", ")}\n` +
1472
+ ` To record it as an observation anyway: synap observe write ${JSON.stringify(text)}`);
1473
+ process.exit(1);
1474
+ }
1072
1475
  const { observeWrite } = await import("./commands/observe.js");
1073
1476
  await observeWrite(text, opts);
1074
1477
  });
@@ -1108,6 +1511,7 @@ create
1108
1511
  .description("Create a new entity")
1109
1512
  .requiredOption("--profile <slug>", "Profile slug (e.g. person, note, task)")
1110
1513
  .requiredOption("--name <name>", "Entity name")
1514
+ .option("--content <markdown>", "Long-form Markdown body, stored as the entity's linked document")
1111
1515
  .option("--workspace <id>", "Create in this workspace")
1112
1516
  .option("--props <json>", "Properties as JSON string", "{}")
1113
1517
  .option("--json", "Output as JSON")
@@ -1150,43 +1554,48 @@ set
1150
1554
  // ─── agents ───────────────────────────────────────────────────────────────────
1151
1555
  const agents = program
1152
1556
  .command("agents")
1153
- .description("Manage named agent identities (different keys, different pod access)");
1557
+ .description("Manage agent principals on your pod (one per type for you; local keys are a cache)");
1154
1558
  agents
1155
1559
  .command("list", { isDefault: true })
1156
- .description("List configured agent identities")
1560
+ .description("List your agents on this pod (one per type). Use --local for ~/.synap cache only.")
1157
1561
  .option("--json", "Output as JSON")
1562
+ .option("--local", "List only the local ~/.synap agent cache")
1158
1563
  .action(async (opts) => {
1159
1564
  const { agentsList } = await import("./commands/agents.js");
1160
- agentsList(opts);
1565
+ await agentsList(opts);
1161
1566
  });
1162
1567
  agents
1163
- .command("add")
1164
- .description("Register a pre-existing agent credential locally. Use `synap agents create` for new agents.")
1165
- .option("--name <name>", "Agent name (e.g. researcher, builder)")
1166
- .option("--api-key <key>", "Hub Protocol API key for this agent")
1167
- .option("--pod <name>", "Pod profile to link (default: active pod)")
1168
- .option("--workspace <id>", "Default workspace for this agent")
1568
+ .command("add [kind]")
1569
+ .description("Bring an agent into the pod: a client (claude-code, cursor, codex, …), your own (--name → URL + key), or pick from this machine (no kind)")
1570
+ .option("--name <name>", "Name your own agent (e.g. researcher) — prints a URL + key to paste")
1571
+ .option("--template <tmpl>", "Your own agent's template: twin | assistant | custom (default: custom)")
1572
+ .option("--type <type>", "Explicit agent type for your own agent (default: a slug of --name)")
1573
+ .option("--role <role>", "Workspace role: admin | editor | viewer (default: editor)")
1574
+ .option("--workspace <id>", "Focus on one workspace (default: pod-wide)")
1575
+ .option("--project <id>", "Focus on one project (composable with --workspace)")
1576
+ .option("--with-mcp", "Raycast only: also install the full MCP server via mcp-remote")
1577
+ .option("--api-key <key>", "Register a key you already hold, on this machine only (mints nothing)")
1169
1578
  .option("--label <label>", "Human-readable description")
1170
- .action(async (opts) => {
1579
+ .action(async (kind, opts) => {
1171
1580
  const { agentsAdd } = await import("./commands/agents.js");
1172
- await agentsAdd(opts);
1581
+ await agentsAdd(kind, opts);
1173
1582
  });
1174
1583
  agents
1175
1584
  .command("create")
1176
- .description("Create a new agent on the pod (template-aware). Replaces `add` for new agents.")
1585
+ .description("Deprecated — use `synap agents add` (same flags)")
1177
1586
  .option("--template <tmpl>", "Agent template: twin | assistant | custom (default: custom)")
1178
1587
  .option("--name <name>", "Agent name (not needed for twin — auto-generated)")
1179
1588
  .option("--type <type>", "Agent type string for custom agents (default: matches template)")
1180
- .option("--role <role>", "Workspace role: admin | editor | viewer (default: editor; twin auto-inherits)")
1181
- .option("--workspace <id>", "Workspace ID to add the agent to (auto-detected if omitted)")
1182
- .option("--pod <name>", "Pod profile to use (default: active pod)")
1589
+ .option("--role <role>", "Workspace role: admin | editor | viewer (default: editor)")
1590
+ .option("--workspace <id>", "Focus on one workspace (default: pod-wide)")
1591
+ .option("--project <id>", "Focus on one project")
1183
1592
  .action(async (opts) => {
1184
1593
  const { agentsCreate } = await import("./commands/agents.js");
1185
1594
  await agentsCreate(opts);
1186
1595
  });
1187
1596
  agents
1188
1597
  .command("rotate-key <name-or-id>")
1189
- .description("Rotate the Hub Protocol API key for an agent (invalidates the current key)")
1598
+ .description("Rotate the Hub Protocol API key for an agent (local cache only)")
1190
1599
  .action(async (nameOrId) => {
1191
1600
  const { agentsRotateKey } = await import("./commands/agents.js");
1192
1601
  await agentsRotateKey(nameOrId);
@@ -1194,17 +1603,19 @@ agents
1194
1603
  agents
1195
1604
  .command("remove <name>")
1196
1605
  .alias("rm")
1197
- .description("Remove a named agent identity")
1606
+ .description("Remove a named agent identity (local cache only)")
1198
1607
  .action(async (name) => {
1199
1608
  const { agentsRemove } = await import("./commands/agents.js");
1200
1609
  agentsRemove(name);
1201
1610
  });
1202
1611
  agents
1203
1612
  .command("info <name>")
1204
- .description("Show details for a named agent identity")
1205
- .action(async (name) => {
1613
+ .description("Show details for a named agent identity (local cache only)")
1614
+ .option("--show-key", "Print the MCP URL and the full API key")
1615
+ .option("--snippets", "Print paste-ready configs (Claude Code, Cursor, stdio clients)")
1616
+ .action(async (name, opts) => {
1206
1617
  const { agentsInfo } = await import("./commands/agents.js");
1207
- agentsInfo(name);
1618
+ await agentsInfo(name, opts);
1208
1619
  });
1209
1620
  // ─── agent (autonomous runner) ───────────────────────────────────────────────
1210
1621
  const agent = program
@@ -1282,6 +1693,52 @@ agent
1282
1693
  const { agentTick } = await import("./commands/agent-run.js");
1283
1694
  await agentTick(opts);
1284
1695
  });
1696
+ // ─── automate ───────────────────────────────────────────────────────────────
1697
+ // The natural-language automation door. It stays deliberately separate from
1698
+ // `automation create`, which remains the expert, explicit-DSL path.
1699
+ program
1700
+ .command("automate <instruction>")
1701
+ .description("Ask the workspace agent to prepare a governed automation proposal")
1702
+ .option("--workspace <id|name>", "Workspace to use (uses active workspace/lens if omitted)")
1703
+ .option("--thread <id>", "Continue an existing agent thread/channel")
1704
+ .option("--new", "Start a fresh agent thread")
1705
+ .option("--timeout <seconds>", "How long to wait for the agent's reply (default: 90)")
1706
+ .option("--json", "Machine-readable output { ok, reply, threadId, workspaceId }")
1707
+ .option("--pod-url <url>", "Override pod URL")
1708
+ .option("--api-key <key>", "Override API key")
1709
+ .action(async (instruction, opts) => {
1710
+ const { automate } = await import("./commands/automate.js");
1711
+ await automate(instruction, opts);
1712
+ });
1713
+ // ─── rule ────────────────────────────────────────────────────────────────────
1714
+ // The fourth door, beside `ask` (recall), `capture` (write) and `diagnose`
1715
+ // (inspect): a STANDING instruction. It classifies the sentence and shows the
1716
+ // reading before writing anything — and it records the rule only. Building the
1717
+ // behaviour half is still `synap automate`.
1718
+ const ruleCmd = program
1719
+ .command("rule [text]")
1720
+ .description("State a standing rule in plain language — classified, shown, then governed-created")
1721
+ .option("--workspace <id>", "Workspace to scope the rule to (uses active workspace if omitted)")
1722
+ .option("--pod-wide", "Scope the rule to the whole pod, ignoring the active workspace")
1723
+ .option("--yes", "Skip the one-off confirmation (never skips a clarification)")
1724
+ .option("--json", "Output raw JSON")
1725
+ .option("--pod-url <url>", "Pod URL override")
1726
+ .option("--api-key <key>", "API key override")
1727
+ .action(async (text, opts) => {
1728
+ const { rule } = await import("./commands/rule.js");
1729
+ await rule(text ?? "", opts);
1730
+ });
1731
+ ruleCmd
1732
+ .command("list")
1733
+ .description("List rules with their intent, scope, and whether a behaviour is attached")
1734
+ .option("--workspace <id>", "Filter by workspace")
1735
+ .option("--json", "Output raw JSON")
1736
+ .option("--pod-url <url>", "Pod URL override")
1737
+ .option("--api-key <key>", "API key override")
1738
+ .action(async (opts) => {
1739
+ const { ruleList } = await import("./commands/rule.js");
1740
+ await ruleList(opts);
1741
+ });
1285
1742
  // ─── automation ──────────────────────────────────────────────────────────────
1286
1743
  function collect(val, prev) { return [...prev, val]; }
1287
1744
  const automation = program
@@ -1473,12 +1930,11 @@ program
1473
1930
  .description("Open in the Synap desktop app. Use: open <kind> <id> or open <id> (auto-resolves type)")
1474
1931
  .action(async (kindOrId, id) => {
1475
1932
  if (id) {
1476
- // open entity|view|cell|document|proposal <id>
1477
- const validKinds = ["entity", "view", "cell", "document", "proposal"];
1478
- if (!validKinds.includes(kindOrId)) {
1479
- console.error(`Unknown kind: "${kindOrId}". Use: ${validKinds.join(" | ")}`);
1480
- process.exit(1);
1481
- }
1933
+ // open <kind> <id> — the browser's object-nav.ts is the one route
1934
+ // table for openable kinds (21 and growing); the CLI doesn't
1935
+ // duplicate it here. An unroutable kind still opens the app and
1936
+ // is reported via the printed deep link (objectNavTarget decides
1937
+ // routability on the other side).
1482
1938
  const { openInBrowser } = await import("./commands/open.js");
1483
1939
  await openInBrowser({ kind: kindOrId, id });
1484
1940
  }
@@ -1515,10 +1971,12 @@ Examples:
1515
1971
  });
1516
1972
  doc
1517
1973
  .command("update <documentId>")
1518
- .description("Update a document's content (full replacement, proposal-gated)")
1974
+ .description("Update a document's content: the whole body, or one section (--section). Governed: an agent's edit is proposed.")
1519
1975
  .option("--content <text>", "New markdown content (inline)")
1520
1976
  .option("--file <path>", "Read new content from a file")
1521
- .option("--title <title>", "Update document title (requires --content too)")
1977
+ .option("--section <id>", "Write ONE section by id (content = the section body) instead of the whole document")
1978
+ .option("--section-title <title>", "The section heading (required with --section)")
1979
+ .option("--base-revision <n>", "The revision you read; refused if the document changed since")
1522
1980
  .option("--open", "Open in the Synap desktop app after update")
1523
1981
  .option("--json", "JSON output")
1524
1982
  .option("--pod-url <url>", "Pod URL override")
@@ -1528,6 +1986,7 @@ Examples:
1528
1986
  synap doc update <id> --content "# Updated"
1529
1987
  synap doc update <id> --file ./notes.md
1530
1988
  cat notes.md | synap doc update <id>
1989
+ synap doc update <id> --section risks --section-title "Risks" --file ./risks.md
1531
1990
  `)
1532
1991
  .action(async (documentId, opts) => {
1533
1992
  const { docUpdate } = await import("./commands/doc.js");
@@ -1560,17 +2019,30 @@ cell
1560
2019
  .requiredOption("--name <name>", "Cell display name")
1561
2020
  .option("--file <path>", "ESM source file (or pipe stdin)")
1562
2021
  .option("--type-key <key>", "Explicit typeKey (default: generated:<slug>)")
1563
- .option("--deps <json>", 'Dependency map JSON, e.g. \'{"recharts":"2.12.0"}\' (sent in payload; backend support pending)')
1564
- .option("--workspace <id>", "Workspace to scope the cell to (omit for pod-global)")
2022
+ .option("--deps <json>", 'Dependency map JSON, e.g. \'{"recharts":"2.12.0"}\'')
2023
+ .option("--view-types <list>", 'Comma-separated view types this cell can render as a VIEW RENDERER, e.g. "list,table,kanban". Omit to leave an existing affinity untouched; pass "" to clear it')
2024
+ .option("--content-kind <kind>", "What this cell renders — entity-detail | entity-card | entity-profile | collection | widget (default: widget, which is placeable but never offered as a profile renderer)")
2025
+ // NOT "omit for pod-wide": `cell.ts` resolves `opts.workspace ?? cfg.workspaceId`,
2026
+ // and `cfg.workspaceId` is the resolved lens (session › directory › env ›
2027
+ // profile default). Omitting therefore scopes the cell to whatever workspace
2028
+ // your lens currently points at — which is pod-wide ONLY when nothing resolves.
2029
+ // Verified 2026-09-05: run from a directory with no `.synap/lens.json` and the
2030
+ // cell silently lands in the pod profile's default workspace.
2031
+ .option("--workspace <id>", "Workspace to scope the cell to (default: your active workspace lens — run `synap lens` to see it)")
1565
2032
  .option("--description <text>", "Short description")
1566
2033
  .option("--open", "Open in the Synap desktop app after define")
1567
2034
  .option("--json", "JSON output")
1568
2035
  .option("--pod-url <url>", "Pod URL override")
1569
2036
  .option("--api-key <key>", "API key override")
1570
2037
  .addHelpText("after", `
2038
+ Without --view-types / --content-kind a cell installs cleanly but can never be
2039
+ PICKED as a view or profile renderer — it is only placeable on a bento.
2040
+
1571
2041
  Examples:
1572
2042
  synap cell define --name "Revenue Chart" --file ./chart.js
1573
2043
  synap cell define --name "Revenue Chart" --file ./chart.js --deps '{"recharts":"2.12.0"}'
2044
+ synap cell define --name "Deal Board" --file ./board.js --view-types kanban,table --content-kind collection
2045
+ synap cell define --name "Contact Page" --file ./contact.js --content-kind entity-detail
1574
2046
  cat chart.js | synap cell define --name "Revenue Chart"
1575
2047
  `)
1576
2048
  .action(async (opts) => {
@@ -1581,10 +2053,16 @@ cell
1581
2053
  .command("build <entry>")
1582
2054
  .description("Bundle a multi-file cell/app into a single ESM module the runtime expects")
1583
2055
  .option("--out <file>", "Output file path (default: <entry>.bundle.js)")
2056
+ .option("--bundle-deps", "Inline all third-party deps into the bundle (only react/react-dom stay external) — output has deps={} and the runtime CSP drops esm.sh entirely")
2057
+ .option("--self-contained", 'Build ONE complete HTML document (React, CSS, brand fonts inlined; nothing external) — the code of a rendererType "mcp-app" cell')
2058
+ .option("--fonts", "With --self-contained: require the inlined brand fonts (default: include when @synap-core/design-tokens resolves)")
2059
+ .option("--no-fonts", "With --self-contained: skip the inlined brand fonts")
1584
2060
  .option("--define", "Chain into cell define after bundling (requires --name)")
1585
2061
  .option("--name <name>", "Cell name (required with --define)")
1586
2062
  .option("--type-key <key>", "Explicit typeKey (used with --define)")
1587
2063
  .option("--deps <json>", "Override dependency versions as JSON, e.g. '{\"react\":\"18.3.0\"}'")
2064
+ .option("--view-types <list>", 'Comma-separated view-renderer affinity (used with --define), e.g. "list,table"')
2065
+ .option("--content-kind <kind>", "entity-detail | entity-card | entity-profile | collection | widget (used with --define)")
1588
2066
  .option("--workspace <id>", "Workspace (used with --define)")
1589
2067
  .option("--description <text>", "Description (used with --define)")
1590
2068
  .option("--open", "Open in browser after define (used with --define)")
@@ -1592,12 +2070,20 @@ cell
1592
2070
  .option("--pod-url <url>", "Pod URL override")
1593
2071
  .option("--api-key <key>", "API key override")
1594
2072
  .addHelpText("after", `
1595
- Output: a single ESM file. Bare imports (react, recharts, …) are externalized
1596
- and emitted as a deps map. At runtime Synap resolves them via esm.sh importmap.
2073
+ Output: a single ESM file.
2074
+
2075
+ Default mode: bare imports (react, recharts, …) are externalized and emitted
2076
+ as a deps map. At runtime Synap resolves them via esm.sh importmap.
2077
+
2078
+ --bundle-deps mode: only react/react-dom are externalized (React is
2079
+ host-inlined at runtime, never via esm.sh); everything else is bundled into
2080
+ the output, so the deps map is empty. A cell built this way reaches no esm.sh
2081
+ origin at runtime — the runtime CSP collapses to drop it entirely.
1597
2082
 
1598
2083
  Examples:
1599
2084
  synap cell build ./src/chart.tsx --out ./dist/chart.js
1600
2085
  synap cell build ./src/chart.tsx --out ./dist/chart.js --define --name "Revenue Chart"
2086
+ synap cell build ./src/chart.tsx --out ./dist/chart.js --bundle-deps
1601
2087
  `)
1602
2088
  .action(async (entry, opts) => {
1603
2089
  const { cellBuild } = await import("./commands/cell.js");
@@ -1647,7 +2133,7 @@ tools
1647
2133
  });
1648
2134
  tools
1649
2135
  .command("connect <service>")
1650
- .description("Connect a credential to a tool (via Nango OAuth or vault)")
2136
+ .description("Connect a credential to a tool (OAuth via the connection broker, or vault)")
1651
2137
  .option("--workspace <id>", "Workspace context")
1652
2138
  .option("--pod-url <url>", "Pod URL override")
1653
2139
  .option("--api-key <key>", "API key override")
@@ -1712,6 +2198,33 @@ podCmd
1712
2198
  const { podAdopt } = await import("./commands/pod.js");
1713
2199
  await podAdopt(workspaceNameOrId, opts);
1714
2200
  });
2201
+ // ─── vendor — the author's publisher profile ─────────────────────────────────
2202
+ // Attribution is write-time-only at the CP: a package published before a vendor
2203
+ // exists is orphaned from its author forever. See src/commands/vendor.ts.
2204
+ const vendor = program
2205
+ .command("vendor")
2206
+ .description("Your marketplace publisher profile (what packages are attributed to)");
2207
+ vendor
2208
+ .command("create <slug>")
2209
+ .description("Create your publisher profile — do this BEFORE your first publish")
2210
+ .option("--name <name>", "Display name (defaults to a title-cased slug)")
2211
+ .option("--description <text>", "Short description of you or your studio")
2212
+ .option("--website <url>", "Website URL (must be a full URL)")
2213
+ .option("--json", "Output as JSON")
2214
+ .action(async (slug, _opts, cmd) => {
2215
+ const opts = cmd.optsWithGlobals();
2216
+ const { vendorCreate } = await import("./commands/vendor.js");
2217
+ await vendorCreate(slug, opts);
2218
+ });
2219
+ vendor
2220
+ .command("show")
2221
+ .description("Show whether you have a publisher profile")
2222
+ .option("--json", "Output as JSON")
2223
+ .action(async (_opts, cmd) => {
2224
+ const opts = cmd.optsWithGlobals();
2225
+ const { vendorShow } = await import("./commands/vendor.js");
2226
+ await vendorShow(opts);
2227
+ });
1715
2228
  // ─── market — discover + install ANY package type ────────────────────────────
1716
2229
  // launch is workspace-first (bundled, offline); market reaches every CP package
1717
2230
  // type (capability/skill/workflow/view/cell/workspace), public or private.
@@ -1730,6 +2243,7 @@ market
1730
2243
  .command("install <slug>")
1731
2244
  .description("Install a package by slug into a project — workspaces install now; other types route you to the right surface")
1732
2245
  .option("--project <id>", "Optionally tag the seeded entities to a project (installs are pod-wide by default)")
2246
+ .option("--workspace <id>", "Acting workspace (UUID) a workflow/automation package is added to (required for that kind; defaults to your active lens). Ignored for pod-wide kinds")
1733
2247
  .option("--onto <workspaceId>", "Reconcile this template ONTO an existing workspace (additive) instead of creating a new one")
1734
2248
  .option("--dry-run", "Preview the create path write-free — report would-create / reuse / conflicts, install nothing")
1735
2249
  .option("--timeout <seconds>", "How long to wait for the apply to finish (default: 120)")
@@ -1750,7 +2264,8 @@ market
1750
2264
  market
1751
2265
  .command("update [slugs...]")
1752
2266
  .description("Check installed packages for drift against the catalog, and re-apply the ones that are stale (all with --yes, specific ones with explicit slugs)")
1753
- .option("--yes", "Apply every available update without prompting (no slugs required)")
2267
+ .option("--yes", "Apply every available update without prompting (no slugs required), agreeing to every data change it lists")
2268
+ .option("--accept-data-changes <opKeys>", "Agree to exactly these data changes (comma-separated opKeys from the preview)")
1754
2269
  .option("--dry-run", "Force the preview path — never apply, even with --yes")
1755
2270
  .option("--timeout <seconds>", "How long to wait for each package's apply to finish (default: 120)")
1756
2271
  .option("--json", "Output as JSON")
@@ -1775,10 +2290,113 @@ market
1775
2290
  const { marketInstalled } = await import("./commands/market.js");
1776
2291
  await marketInstalled(opts);
1777
2292
  });
2293
+ market
2294
+ .command("workspaces")
2295
+ .description("List your workspaces with their template-attachment status — find the one that isn't attached yet (pure read)")
2296
+ .option("--unattached", "Only show workspaces that need attention (unattached or missing a version stamp)")
2297
+ .option("--json", "Output as JSON")
2298
+ .action(async (_opts, cmd) => {
2299
+ // Same parent/child `--json` collision as `market install` — see its comment.
2300
+ const opts = cmd.optsWithGlobals();
2301
+ const { marketWorkspaces } = await import("./commands/market.js");
2302
+ await marketWorkspaces(opts);
2303
+ });
2304
+ market
2305
+ .command("attach <workspaceId>")
2306
+ .description("Reconcile a marketplace template onto an existing workspace + version-stamp it — how you relink a workspace installed before the marketplace")
2307
+ .option("--slug <slug>", "Template to attach (inferred from the workspace's existing attachment or domain when omitted)")
2308
+ .option("--project <id>", "Optionally tag the reconciled entities to a project")
2309
+ .option("--timeout <seconds>", "How long to wait for the apply to finish (default: 120)")
2310
+ .option("--json", "Output as JSON")
2311
+ .option("--pod-url <url>", "Pod URL override")
2312
+ .option("--api-key <key>", "API key override")
2313
+ .action(async (workspaceId, _opts, cmd) => {
2314
+ // Same parent/child `--json` collision as `market install` — see its comment.
2315
+ const opts = cmd.optsWithGlobals();
2316
+ const { marketAttach } = await import("./commands/market.js");
2317
+ await marketAttach(workspaceId, opts);
2318
+ });
2319
+ // ── `synap templates` — the ONE home for workspace ↔ template health ─────────
2320
+ // Consolidates `market update`/`workspaces`/`installed`: one grouped, action-
2321
+ // ranked view where an UNATTACHED workspace is visible (it wasn't in `update`),
2322
+ // with a suggested template and a one-word attach. The `market *` subcommands
2323
+ // still work — this is the front door, not a breaking rename.
2324
+ const templates = program
2325
+ .command("templates")
2326
+ .description("See every workspace's template status (attached, drifted, or unattached) and connect them — the one home for template health")
2327
+ .option("--needs-attention", "Show only workspaces that need action (drifted / unstamped / unattached-with-suggestion)")
2328
+ .option("--json", "Output as JSON")
2329
+ .option("--pod-url <url>", "Pod URL override")
2330
+ .option("--api-key <key>", "API key override")
2331
+ .action(async (_opts, cmd) => {
2332
+ const opts = cmd.optsWithGlobals();
2333
+ const { marketTemplates } = await import("./commands/market.js");
2334
+ await marketTemplates(opts);
2335
+ });
2336
+ templates
2337
+ .command("attach <workspace>")
2338
+ .description("Connect a workspace (by NAME or id) to a template — suggests the right one so you don't guess a slug")
2339
+ .option("--slug <slug>", "Force a specific template instead of the suggestion")
2340
+ .option("--yes", "Accept a confident (domain-match) suggestion without prompting — for scripts")
2341
+ .option("--project <id>", "Optionally tag the reconciled entities to a project")
2342
+ .option("--timeout <seconds>", "How long to wait for the apply to finish (default: 120)")
2343
+ .option("--json", "Output as JSON")
2344
+ .option("--pod-url <url>", "Pod URL override")
2345
+ .option("--api-key <key>", "API key override")
2346
+ .action(async (workspace, _opts, cmd) => {
2347
+ const opts = cmd.optsWithGlobals();
2348
+ const { templatesAttach } = await import("./commands/market.js");
2349
+ await templatesAttach(workspace, opts);
2350
+ });
2351
+ templates
2352
+ .command("update [slugs...]")
2353
+ .alias("updates")
2354
+ .description("Apply template updates — 'update all' (or --yes) for every drifted workspace, or name specific slugs. Bare 'update' previews what's available.")
2355
+ .option("--yes", "Apply every available update without prompting, agreeing to every data change it lists")
2356
+ .option("--accept-data-changes <opKeys>", "Agree to exactly these data changes (comma-separated opKeys from the preview)")
2357
+ .option("--dry-run", "Preview only — never apply")
2358
+ .option("--timeout <seconds>", "How long to wait for each apply (default: 120)")
2359
+ .option("--json", "Output as JSON")
2360
+ .option("--pod-url <url>", "Pod URL override")
2361
+ .option("--api-key <key>", "API key override")
2362
+ .action(async (slugs, _opts, cmd) => {
2363
+ const opts = cmd.optsWithGlobals();
2364
+ // `update all` reads more naturally than `--yes` and is what people type —
2365
+ // treat the literal word "all" as "apply every available update". It is NOT
2366
+ // `--yes`: data changes still need the person's agreement (asked, or flagged).
2367
+ const wantsAll = !!slugs?.some((s) => s.toLowerCase() === "all");
2368
+ const explicit = (slugs ?? []).filter((s) => s.toLowerCase() !== "all");
2369
+ const { marketUpdate } = await import("./commands/market.js");
2370
+ await marketUpdate(explicit.length > 0 ? explicit : undefined, wantsAll ? { ...opts, all: true } : opts);
2371
+ });
2372
+ templates
2373
+ .command("retire <target>")
2374
+ .description("Move a retired template's space into its replacement (by template slug, workspace name or id). Shows the plan, asks, then moves through the pod — never deletes.")
2375
+ .option("--dry-run", "Show the plan only — move nothing")
2376
+ .option("--yes", "Move without asking (for scripts)")
2377
+ .option("--json", "Output as JSON")
2378
+ .option("--pod-url <url>", "Pod URL override")
2379
+ .option("--api-key <key>", "API key override")
2380
+ .action(async (target, _opts, cmd) => {
2381
+ const opts = cmd.optsWithGlobals();
2382
+ const { templatesRetire } = await import("./commands/templates-retire.js");
2383
+ await templatesRetire(target, opts);
2384
+ });
1778
2385
  // ── Authoring loop: scaffold → validate → publish → unpublish ────────────────
1779
2386
  market
1780
2387
  .command("scaffold <slug>")
1781
2388
  .description("Write a minimal valid <slug>.template.yaml to edit in place (refuses to overwrite)")
2389
+ // This list MUST equal `SCAFFOLDABLE_KINDS` (src/lib/kind-package.ts). It is
2390
+ // written out rather than imported because index.ts keeps its eager module
2391
+ // graph to commander + one lib — importing kind-package.ts here would pull
2392
+ // `yaml` into the startup path of every `synap` invocation. What keeps the two
2393
+ // honest is `test/scaffold-kind-help-parity.test.ts`, which source-scans both.
2394
+ //
2395
+ // It advertised `skill` — which `marketScaffold` refuses, since the CP package
2396
+ // schema has no standalone slot for one — and omitted `workflow`, which
2397
+ // `market validate` has always accepted. A help string that offers a refused
2398
+ // option and hides a working one is the documented-loop defect class.
2399
+ .option("--kind <kind>", "Scaffold a standalone package instead of a workspace template: cell, view, workflow, capability, skill")
1782
2400
  .option("--json", "Output as JSON")
1783
2401
  .action(async (slug, _opts, cmd) => {
1784
2402
  // Same parent/child `--json` collision as `market install` — see its comment.
@@ -1797,19 +2415,44 @@ market
1797
2415
  await marketValidate(file, opts);
1798
2416
  });
1799
2417
  market
1800
- .command("publish [file]")
1801
- .description("Validate then publish a template to the marketplace (private by default) — pass a file, or --from-workspace <id>")
2418
+ .command("publish [targets...]")
2419
+ .description("Validate then publish a template (or a standalone cell/view package) to the marketplace (private by default) — pass a file, --from-workspace <id>, or --from-project <id>. Non-official publish never uses the synap-official vendor. Founder: --official [slug...] publishes the official catalog from the monorepo checkout (alias of `synap catalog publish --official --kind workspace`)")
2420
+ .option("--official", "Publish the OFFICIAL Synap catalog (all templates, or the named slugs) from the monorepo checkout — requires a `synap login` session that owns the synap-official vendor; previews and asks first")
2421
+ .option("--templates-dir <dir>", "With --official: path to the catalog package — synap-app/packages/catalog or workspace-templates (default: found by walking up from cwd)")
2422
+ .option("--dry-run", "With --official: preview what would change against the live catalog, publish nothing")
2423
+ .option("--yes", "With --official: skip the confirmation prompt")
1802
2424
  .option("--public", "Publish as PUBLIC (default is private)")
1803
2425
  .option("--private", "Publish as private (the default — stated explicitly)")
1804
2426
  .option("--from-workspace <id>", "Serialize a live workspace into a template and publish it (instead of a file)")
2427
+ .option("--from-project <id>", "Serialize a live project's used workspaces into a suite package (tags: suite, require deps) and publish it")
2428
+ .option("--price <usd>", "Stamp pricingModel=one_time + priceUsd (dollars→cents). Settlement/Stripe is not this wave — columns only.")
1805
2429
  .option("--json", "Output as JSON")
1806
- .option("--pod-url <url>", "Pod URL override (for --from-workspace)")
1807
- .option("--api-key <key>", "API key override (for --from-workspace)")
1808
- .action(async (file, _opts, cmd) => {
2430
+ .option("--pod-url <url>", "Pod URL override (for --from-workspace / --from-project)")
2431
+ .option("--api-key <key>", "API key override (for --from-workspace / --from-project)")
2432
+ // Default ON for a cell package's `codeFile` resolution — see `cell build
2433
+ // --bundle-deps`'s doc comment for why: a published cell's `deps` map is
2434
+ // otherwise resolved through esm.sh at RUNTIME, so the artifact reviewed at
2435
+ // publish time isn't what actually executes. `synap cell build` invoked
2436
+ // directly is unaffected — its own default stays external-deps-via-esm.sh.
2437
+ .option("--no-bundle-deps", "Publish a cell's codeFile with external deps via esm.sh instead of bundling them in (default: bundled)")
2438
+ .action(async (targets, _opts, cmd) => {
1809
2439
  // Same parent/child `--json` collision as `market install` — see its comment.
1810
2440
  const opts = cmd.optsWithGlobals();
2441
+ const args = targets ?? [];
2442
+ if (opts.official) {
2443
+ const { marketPublishOfficial } = await import("./commands/market-official.js");
2444
+ process.exitCode = await marketPublishOfficial(args, opts);
2445
+ return;
2446
+ }
2447
+ const { nonOfficialUsageError } = await import("./commands/market-official.js");
2448
+ const usage = nonOfficialUsageError(args, opts);
2449
+ if (usage) {
2450
+ console.error(` ✗ ${usage}`);
2451
+ process.exitCode = 2;
2452
+ return;
2453
+ }
1811
2454
  const { marketPublish } = await import("./commands/market-authoring.js");
1812
- await marketPublish(file, opts);
2455
+ await marketPublish(args[0], opts);
1813
2456
  });
1814
2457
  market
1815
2458
  .command("unpublish <slug>")
@@ -1821,6 +2464,35 @@ market
1821
2464
  const { marketUnpublish } = await import("./commands/market-authoring.js");
1822
2465
  await marketUnpublish(slug, opts);
1823
2466
  });
2467
+ // ─── catalog ──────────────────────────────────────────────────────────────────
2468
+ // The team's ONE door to the official catalog (every installable kind). Thin
2469
+ // wiring: `runCatalog` (commands/catalog.ts) calls the ONE catalog lib in the
2470
+ // monorepo checkout. `market publish --official` is its alias (kind workspace).
2471
+ const catalog = program
2472
+ .command("catalog")
2473
+ .description("Validate, diff and publish the OFFICIAL template catalog (workspaces, capabilities, skills, packs, automations) from the monorepo checkout");
2474
+ for (const verb of ["validate", "diff", "publish"]) {
2475
+ const sub = catalog
2476
+ .command(`${verb} [slugs...]`)
2477
+ .description(verb === "validate"
2478
+ ? "Validate the catalog (per-kind schema, cross-kind refs, goal grammar, duplicates) — offline"
2479
+ : verb === "diff"
2480
+ ? "Show what would change against the live control-plane catalog (new / changed / unchanged)"
2481
+ : "Validate, preview the diff, confirm, then publish to the control plane — founder only: --official + a `synap login` session that owns synap-official")
2482
+ .option("--kind <kind>", "Only this kind: workspace | capability | skill | pack | automation")
2483
+ .option("--catalog-dir <dir>", "Path to the catalog package (default: found by walking up from cwd)")
2484
+ .option("--json", "Output as JSON");
2485
+ if (verb === "publish") {
2486
+ sub
2487
+ .option("--official", "Publish as the synap-official vendor (required)")
2488
+ .option("--dry-run", "Preview the diff only — publish nothing")
2489
+ .option("--yes", "Skip the confirmation prompt");
2490
+ }
2491
+ sub.action(async (slugs, opts) => {
2492
+ const { runCatalog } = await import("./commands/catalog.js");
2493
+ process.exitCode = await runCatalog(verb, slugs ?? [], opts);
2494
+ });
2495
+ }
1824
2496
  // ─── capability ───────────────────────────────────────────────────────────────
1825
2497
  const capability = program
1826
2498
  .command("capability")
@@ -1873,6 +2545,7 @@ capability
1873
2545
  .command("connect [name]")
1874
2546
  .description("Connect a service — omit the name to pick from connectable services")
1875
2547
  .option("--workspace <id>", "Workspace context")
2548
+ .option("--reconnect", "Force a fresh sign-in even if a connection already exists — fixes an expired or revoked token")
1876
2549
  .action(async (name, opts) => {
1877
2550
  const { capabilityConnect } = await import("./commands/capability.js");
1878
2551
  await capabilityConnect(name, opts);
@@ -1969,13 +2642,37 @@ capConnections
1969
2642
  const { capabilityConnectionsRemove } = await import("./commands/cap-connections.js");
1970
2643
  await capabilityConnectionsRemove(capability, connectionId, opts);
1971
2644
  });
1972
- // ─── raycast ──────────────────────────────────────────────────────────────────
2645
+ capability
2646
+ .command("sync-status [provider]")
2647
+ .description("Show sync status per provider × kind × connection (phase, counts, errors) — omit provider to see all")
2648
+ .option("--workspace <id>", "Workspace context")
2649
+ .option("--json", "Output as JSON")
2650
+ .action(async (provider, opts) => {
2651
+ const { capabilitySyncStatus } = await import("./commands/sync-status.js");
2652
+ await capabilitySyncStatus(provider, opts);
2653
+ });
2654
+ // ─── sync (thin alias root for `cap sync-status`) ──────────────────────────────
2655
+ const sync = program
2656
+ .command("sync")
2657
+ .description("Sync status for connected providers — see `cap sync-status`");
2658
+ sync
2659
+ .command("status [provider]")
2660
+ .description("Show sync status per provider × kind × connection (phase, counts, errors) — omit provider to see all. Alias of `synap cap sync-status`.")
2661
+ .option("--workspace <id>", "Workspace context")
2662
+ .option("--json", "Output as JSON")
2663
+ .action(async (provider, opts) => {
2664
+ const { capabilitySyncStatus } = await import("./commands/sync-status.js");
2665
+ await capabilitySyncStatus(provider, opts);
2666
+ });
2667
+ // ─── raycast (hidden power-user escape) ───────────────────────────────────────
2668
+ // Prefer the native Synap Raycast extension (@synap). Capability verbs there go
2669
+ // through list-actions / run-action. Script Commands are a per-verb escape hatch.
1973
2670
  const raycast = program
1974
- .command("raycast")
1975
- .description("Generate Raycast Script Commands from your enabled capabilities");
2671
+ .command("raycast", { hidden: true })
2672
+ .description("(hidden) Power-user escape: generate Raycast Script Commands per capability verb");
1976
2673
  raycast
1977
2674
  .command("generate")
1978
- .description("Emit a Raycast Script Command per enabled runnable verb")
2675
+ .description("Power-user escape: emit a Script Command per enabled verb (extension uses list-actions/run-action)")
1979
2676
  .option("--out <dir>", "Output directory (default: ~/.synap/raycast-commands)")
1980
2677
  .option("--workspace <id>", "Workspace context")
1981
2678
  .action(async (opts) => {
@@ -1985,7 +2682,7 @@ raycast
1985
2682
  // ─── providers ───────────────────────────────────────────────────────────────
1986
2683
  const providers = program
1987
2684
  .command("providers")
1988
- .description("Discover AI providers from the pod and configure local tools");
2685
+ .description("LLM providers on the pod (OpenAI/Anthropic/…) for local coding tools — not capability packs");
1989
2686
  providers
1990
2687
  .command("list", { isDefault: true })
1991
2688
  .description("Show enabled AI providers and their models")
@@ -1998,14 +2695,36 @@ providers
1998
2695
  });
1999
2696
  providers
2000
2697
  .command("pull")
2001
- .description("Write pod provider config to a local AI tool (opencode, aider)")
2698
+ .description("Write pod LLM credentials into opencode or aider, or print the OpenAI-compatible {pod}/v1 endpoint")
2002
2699
  .option("--target <name>", "Target tool: opencode | aider")
2003
2700
  .option("--pod-url <url>", "Pod URL override")
2004
2701
  .option("--api-key <key>", "API key override")
2702
+ .option("--show-key", "With --target show: print the full API key (default: redacted)")
2005
2703
  .action(async (opts) => {
2006
2704
  const { providersPull } = await import("./commands/providers.js");
2007
2705
  await providersPull(opts);
2008
2706
  });
2707
+ providers
2708
+ .command("set-key <providerId>")
2709
+ .description("Store your own API key override for a provider (BYOI)")
2710
+ .option("--workspace <id>", "Workspace ID for workspace-scoped override")
2711
+ .option("--api-key <key>", "API key (prompted if omitted)")
2712
+ .option("--pod-url <url>", "Pod URL override")
2713
+ .option("--hub-api-key <key>", "Hub API key override")
2714
+ .action(async (opts) => {
2715
+ const { providersSetKey } = await import("./commands/providers.js");
2716
+ await providersSetKey({ ...opts, providerId: opts.providerId });
2717
+ });
2718
+ providers
2719
+ .command("list-keys")
2720
+ .description("List user/workspace-level provider key overrides")
2721
+ .option("--workspace <id>", "Workspace ID to check")
2722
+ .option("--pod-url <url>", "Pod URL override")
2723
+ .option("--hub-api-key <key>", "Hub API key override")
2724
+ .action(async (opts) => {
2725
+ const { providersListKeys } = await import("./commands/providers.js");
2726
+ await providersListKeys(opts);
2727
+ });
2009
2728
  // ─── keys ────────────────────────────────────────────────────────────────────
2010
2729
  const keys = program
2011
2730
  .command("keys")
@@ -2013,9 +2732,10 @@ const keys = program
2013
2732
  keys
2014
2733
  .command("rotate")
2015
2734
  .description("Rotate your CLI key to pick up the latest scope set")
2016
- .action(async () => {
2735
+ .option("--show-key", "Print the new key in full if it could not be saved to the config")
2736
+ .action(async (opts) => {
2017
2737
  const { keysRotate } = await import("./commands/keys.js");
2018
- await keysRotate({});
2738
+ await keysRotate({ showKey: opts.showKey });
2019
2739
  });
2020
2740
  // Resolve a global `--pod <name>` before commander parses (it isn't registered
2021
2741
  // per-command). Fails fast with a clear message on an unknown profile name.