@synap-core/cli 1.11.0 → 1.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (533) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +40 -198
  3. package/dist/build-info.json +6 -0
  4. package/dist/commands/agent-run.js +3 -2
  5. package/dist/commands/agent-run.js.map +1 -1
  6. package/dist/commands/agents.d.ts +32 -17
  7. package/dist/commands/agents.js +203 -237
  8. package/dist/commands/agents.js.map +1 -1
  9. package/dist/commands/app.d.ts +143 -0
  10. package/dist/commands/app.js +524 -0
  11. package/dist/commands/app.js.map +1 -0
  12. package/dist/commands/automate.d.ts +21 -0
  13. package/dist/commands/automate.js +42 -0
  14. package/dist/commands/automate.js.map +1 -0
  15. package/dist/commands/automation-schema.test.d.ts +16 -0
  16. package/dist/commands/automation-schema.test.js +77 -0
  17. package/dist/commands/automation-schema.test.js.map +1 -0
  18. package/dist/commands/automation.d.ts +12 -0
  19. package/dist/commands/automation.js +116 -66
  20. package/dist/commands/automation.js.map +1 -1
  21. package/dist/commands/box.d.ts +42 -0
  22. package/dist/commands/box.js +89 -0
  23. package/dist/commands/box.js.map +1 -0
  24. package/dist/commands/bridge-setup.d.ts +14 -1
  25. package/dist/commands/bridge-setup.js +395 -129
  26. package/dist/commands/bridge-setup.js.map +1 -1
  27. package/dist/commands/cap-connections.d.ts +4 -4
  28. package/dist/commands/cap-connections.js +14 -14
  29. package/dist/commands/cap-connections.js.map +1 -1
  30. package/dist/commands/capability.d.ts +3 -1
  31. package/dist/commands/capability.js +249 -176
  32. package/dist/commands/capability.js.map +1 -1
  33. package/dist/commands/catalog.d.ts +275 -0
  34. package/dist/commands/catalog.js +481 -0
  35. package/dist/commands/catalog.js.map +1 -0
  36. package/dist/commands/cell.d.ts +155 -8
  37. package/dist/commands/cell.js +416 -40
  38. package/dist/commands/cell.js.map +1 -1
  39. package/dist/commands/centrality.js +1 -13
  40. package/dist/commands/centrality.js.map +1 -1
  41. package/dist/commands/close-questions.d.ts +27 -0
  42. package/dist/commands/close-questions.js +49 -0
  43. package/dist/commands/close-questions.js.map +1 -0
  44. package/dist/commands/close-questions.test.d.ts +1 -0
  45. package/dist/commands/close-questions.test.js +52 -0
  46. package/dist/commands/close-questions.test.js.map +1 -0
  47. package/dist/commands/connect.d.ts +36 -13
  48. package/dist/commands/connect.js +68 -167
  49. package/dist/commands/connect.js.map +1 -1
  50. package/dist/commands/connections.d.ts +3 -2
  51. package/dist/commands/connections.js +95 -25
  52. package/dist/commands/connections.js.map +1 -1
  53. package/dist/commands/context.js +79 -44
  54. package/dist/commands/context.js.map +1 -1
  55. package/dist/commands/data-extra.d.ts +34 -0
  56. package/dist/commands/data-extra.js +203 -25
  57. package/dist/commands/data-extra.js.map +1 -1
  58. package/dist/commands/data.d.ts +120 -2
  59. package/dist/commands/data.js +295 -21
  60. package/dist/commands/data.js.map +1 -1
  61. package/dist/commands/diagnose.d.ts +66 -4
  62. package/dist/commands/diagnose.js +285 -28
  63. package/dist/commands/diagnose.js.map +1 -1
  64. package/dist/commands/doc.d.ts +11 -4
  65. package/dist/commands/doc.js +39 -14
  66. package/dist/commands/doc.js.map +1 -1
  67. package/dist/commands/doctor.d.ts +133 -3
  68. package/dist/commands/doctor.js +761 -41
  69. package/dist/commands/doctor.js.map +1 -1
  70. package/dist/commands/explain.js +5 -5
  71. package/dist/commands/explain.js.map +1 -1
  72. package/dist/commands/hooks.d.ts +19 -0
  73. package/dist/commands/hooks.js +70 -0
  74. package/dist/commands/hooks.js.map +1 -0
  75. package/dist/commands/import.d.ts +393 -13
  76. package/dist/commands/import.js +1150 -54
  77. package/dist/commands/import.js.map +1 -1
  78. package/dist/commands/init.d.ts +34 -9
  79. package/dist/commands/init.js +481 -981
  80. package/dist/commands/init.js.map +1 -1
  81. package/dist/commands/keys.d.ts +1 -0
  82. package/dist/commands/keys.js +15 -2
  83. package/dist/commands/keys.js.map +1 -1
  84. package/dist/commands/knowledge.d.ts +22 -6
  85. package/dist/commands/knowledge.js +121 -57
  86. package/dist/commands/knowledge.js.map +1 -1
  87. package/dist/commands/lens.d.ts +37 -1
  88. package/dist/commands/lens.js +194 -72
  89. package/dist/commands/lens.js.map +1 -1
  90. package/dist/commands/market-authoring.d.ts +29 -2
  91. package/dist/commands/market-authoring.js +348 -7
  92. package/dist/commands/market-authoring.js.map +1 -1
  93. package/dist/commands/market-official.d.ts +58 -0
  94. package/dist/commands/market-official.js +84 -0
  95. package/dist/commands/market-official.js.map +1 -0
  96. package/dist/commands/market.d.ts +400 -16
  97. package/dist/commands/market.js +1802 -146
  98. package/dist/commands/market.js.map +1 -1
  99. package/dist/commands/mcp.d.ts +60 -25
  100. package/dist/commands/mcp.js +251 -64
  101. package/dist/commands/mcp.js.map +1 -1
  102. package/dist/commands/observe.category.test.d.ts +1 -0
  103. package/dist/commands/observe.category.test.js +59 -0
  104. package/dist/commands/observe.category.test.js.map +1 -0
  105. package/dist/commands/observe.d.ts +3 -0
  106. package/dist/commands/observe.js +7 -5
  107. package/dist/commands/observe.js.map +1 -1
  108. package/dist/commands/observe.title.test.d.ts +1 -0
  109. package/dist/commands/observe.title.test.js +37 -0
  110. package/dist/commands/observe.title.test.js.map +1 -0
  111. package/dist/commands/open.d.ts +39 -9
  112. package/dist/commands/open.js +80 -25
  113. package/dist/commands/open.js.map +1 -1
  114. package/dist/commands/playbook.d.ts +42 -0
  115. package/dist/commands/playbook.js +171 -0
  116. package/dist/commands/playbook.js.map +1 -0
  117. package/dist/commands/pod.js +1 -1
  118. package/dist/commands/pod.js.map +1 -1
  119. package/dist/commands/pods.d.ts +11 -1
  120. package/dist/commands/pods.js +129 -131
  121. package/dist/commands/pods.js.map +1 -1
  122. package/dist/commands/proposals-actions.d.ts +62 -0
  123. package/dist/commands/proposals-actions.js +143 -8
  124. package/dist/commands/proposals-actions.js.map +1 -1
  125. package/dist/commands/proposals-await.d.ts +68 -0
  126. package/dist/commands/proposals-await.js +248 -0
  127. package/dist/commands/proposals-await.js.map +1 -0
  128. package/dist/commands/proton-setup.d.ts +73 -0
  129. package/dist/commands/proton-setup.js +579 -0
  130. package/dist/commands/proton-setup.js.map +1 -0
  131. package/dist/commands/providers.d.ts +23 -0
  132. package/dist/commands/providers.js +126 -1
  133. package/dist/commands/providers.js.map +1 -1
  134. package/dist/commands/raycast.js +6 -2
  135. package/dist/commands/raycast.js.map +1 -1
  136. package/dist/commands/rule.d.ts +150 -0
  137. package/dist/commands/rule.js +447 -0
  138. package/dist/commands/rule.js.map +1 -0
  139. package/dist/commands/session-feed-wait.d.ts +48 -0
  140. package/dist/commands/session-feed-wait.js +261 -0
  141. package/dist/commands/session-feed-wait.js.map +1 -0
  142. package/dist/commands/session-post.d.ts +40 -0
  143. package/dist/commands/session-post.js +96 -0
  144. package/dist/commands/session-post.js.map +1 -0
  145. package/dist/commands/session-wait.d.ts +116 -0
  146. package/dist/commands/session-wait.js +224 -0
  147. package/dist/commands/session-wait.js.map +1 -0
  148. package/dist/commands/sessions.d.ts +54 -0
  149. package/dist/commands/sessions.js +501 -58
  150. package/dist/commands/sessions.js.map +1 -1
  151. package/dist/commands/skill-manage.d.ts +21 -0
  152. package/dist/commands/skill-manage.js +136 -19
  153. package/dist/commands/skill-manage.js.map +1 -1
  154. package/dist/commands/skills-commands.d.ts +2 -0
  155. package/dist/commands/skills-commands.js +9 -1
  156. package/dist/commands/skills-commands.js.map +1 -1
  157. package/dist/commands/skills-commands.test.d.ts +1 -0
  158. package/dist/commands/skills-commands.test.js +57 -0
  159. package/dist/commands/skills-commands.test.js.map +1 -0
  160. package/dist/commands/skills.js +16 -0
  161. package/dist/commands/skills.js.map +1 -1
  162. package/dist/commands/status.d.ts +1 -4
  163. package/dist/commands/status.js +6 -35
  164. package/dist/commands/status.js.map +1 -1
  165. package/dist/commands/statusline.backoff.test.d.ts +13 -0
  166. package/dist/commands/statusline.backoff.test.js +100 -0
  167. package/dist/commands/statusline.backoff.test.js.map +1 -0
  168. package/dist/commands/statusline.js +16 -9
  169. package/dist/commands/statusline.js.map +1 -1
  170. package/dist/commands/sync-status.d.ts +12 -0
  171. package/dist/commands/sync-status.js +125 -0
  172. package/dist/commands/sync-status.js.map +1 -0
  173. package/dist/commands/templates-retire.d.ts +34 -0
  174. package/dist/commands/templates-retire.js +192 -0
  175. package/dist/commands/templates-retire.js.map +1 -0
  176. package/dist/commands/tools.js +6 -21
  177. package/dist/commands/tools.js.map +1 -1
  178. package/dist/commands/tracks.d.ts +112 -0
  179. package/dist/commands/tracks.js +543 -0
  180. package/dist/commands/tracks.js.map +1 -0
  181. package/dist/commands/update.d.ts +3 -11
  182. package/dist/commands/update.js +5 -134
  183. package/dist/commands/update.js.map +1 -1
  184. package/dist/commands/upload.d.ts +41 -3
  185. package/dist/commands/upload.js +120 -18
  186. package/dist/commands/upload.js.map +1 -1
  187. package/dist/commands/vendor.d.ts +28 -0
  188. package/dist/commands/vendor.js +110 -0
  189. package/dist/commands/vendor.js.map +1 -0
  190. package/dist/commands/versions.js +6 -0
  191. package/dist/commands/versions.js.map +1 -1
  192. package/dist/commands/view.js +10 -5
  193. package/dist/commands/view.js.map +1 -1
  194. package/dist/commands/whoami.d.ts +30 -3
  195. package/dist/commands/whoami.js +186 -23
  196. package/dist/commands/whoami.js.map +1 -1
  197. package/dist/commands/worker-accounts.d.ts +149 -0
  198. package/dist/commands/worker-accounts.js +339 -0
  199. package/dist/commands/worker-accounts.js.map +1 -0
  200. package/dist/commands/worker-accounts.test.d.ts +1 -0
  201. package/dist/commands/worker-accounts.test.js +224 -0
  202. package/dist/commands/worker-accounts.test.js.map +1 -0
  203. package/dist/commands/worker.d.ts +331 -0
  204. package/dist/commands/worker.js +1067 -0
  205. package/dist/commands/worker.js.map +1 -0
  206. package/dist/commands/worker.test.d.ts +1 -0
  207. package/dist/commands/worker.test.js +427 -0
  208. package/dist/commands/worker.test.js.map +1 -0
  209. package/dist/commands/workspace-ops.d.ts +35 -0
  210. package/dist/commands/workspace-ops.js +153 -0
  211. package/dist/commands/workspace-ops.js.map +1 -0
  212. package/dist/index.d.ts +6 -6
  213. package/dist/index.js +1043 -186
  214. package/dist/index.js.map +1 -1
  215. package/dist/lib/agent-connections.d.ts +31 -0
  216. package/dist/lib/agent-connections.js +33 -0
  217. package/dist/lib/agent-connections.js.map +1 -0
  218. package/dist/lib/agent-detect.d.ts +27 -0
  219. package/dist/lib/agent-detect.js +71 -0
  220. package/dist/lib/agent-detect.js.map +1 -0
  221. package/dist/lib/agent-door.d.ts +136 -0
  222. package/dist/lib/agent-door.js +429 -0
  223. package/dist/lib/agent-door.js.map +1 -0
  224. package/dist/lib/agents-config.d.ts +2 -0
  225. package/dist/lib/agents-config.js +4 -9
  226. package/dist/lib/agents-config.js.map +1 -1
  227. package/dist/lib/approval-poll.d.ts +31 -2
  228. package/dist/lib/approval-poll.js +20 -7
  229. package/dist/lib/approval-poll.js.map +1 -1
  230. package/dist/lib/atomic-file.d.ts +48 -0
  231. package/dist/lib/atomic-file.js +129 -0
  232. package/dist/lib/atomic-file.js.map +1 -0
  233. package/dist/lib/auth.d.ts +17 -11
  234. package/dist/lib/auth.js +47 -64
  235. package/dist/lib/auth.js.map +1 -1
  236. package/dist/lib/batch-approval.d.ts +85 -0
  237. package/dist/lib/batch-approval.js +170 -0
  238. package/dist/lib/batch-approval.js.map +1 -0
  239. package/dist/lib/box/egress.d.ts +82 -0
  240. package/dist/lib/box/egress.js +264 -0
  241. package/dist/lib/box/egress.js.map +1 -0
  242. package/dist/lib/box/evidence.d.ts +86 -0
  243. package/dist/lib/box/evidence.js +121 -0
  244. package/dist/lib/box/evidence.js.map +1 -0
  245. package/dist/lib/box/job-server.d.ts +72 -0
  246. package/dist/lib/box/job-server.js +246 -0
  247. package/dist/lib/box/job-server.js.map +1 -0
  248. package/dist/lib/box/provisioner.d.ts +83 -0
  249. package/dist/lib/box/provisioner.js +312 -0
  250. package/dist/lib/box/provisioner.js.map +1 -0
  251. package/dist/lib/box/proxmox.d.ts +112 -0
  252. package/dist/lib/box/proxmox.js +211 -0
  253. package/dist/lib/box/proxmox.js.map +1 -0
  254. package/dist/lib/box/wire.d.ts +98 -0
  255. package/dist/lib/box/wire.js +70 -0
  256. package/dist/lib/box/wire.js.map +1 -0
  257. package/dist/lib/capture-lane.d.ts +6 -5
  258. package/dist/lib/capture-lane.js +6 -5
  259. package/dist/lib/capture-lane.js.map +1 -1
  260. package/dist/lib/capture-structure.d.ts +230 -11
  261. package/dist/lib/capture-structure.js +251 -13
  262. package/dist/lib/capture-structure.js.map +1 -1
  263. package/dist/lib/catalog-templates.d.ts +59 -0
  264. package/dist/lib/catalog-templates.js +126 -0
  265. package/dist/lib/catalog-templates.js.map +1 -0
  266. package/dist/lib/cli-build.d.ts +19 -0
  267. package/dist/lib/cli-build.js +37 -0
  268. package/dist/lib/cli-build.js.map +1 -0
  269. package/dist/lib/client-header.test.d.ts +1 -0
  270. package/dist/lib/client-header.test.js +85 -0
  271. package/dist/lib/client-header.test.js.map +1 -0
  272. package/dist/lib/conversation-headers.d.ts +33 -0
  273. package/dist/lib/conversation-headers.js +69 -0
  274. package/dist/lib/conversation-headers.js.map +1 -0
  275. package/dist/lib/cp-packages.d.ts +167 -11
  276. package/dist/lib/cp-packages.js +253 -21
  277. package/dist/lib/cp-packages.js.map +1 -1
  278. package/dist/lib/credential-class.d.ts +144 -0
  279. package/dist/lib/credential-class.js +138 -0
  280. package/dist/lib/credential-class.js.map +1 -0
  281. package/dist/lib/describe-lens.d.ts +35 -0
  282. package/dist/lib/describe-lens.js +40 -0
  283. package/dist/lib/describe-lens.js.map +1 -1
  284. package/dist/lib/device-connect.d.ts +125 -0
  285. package/dist/lib/device-connect.js +256 -0
  286. package/dist/lib/device-connect.js.map +1 -0
  287. package/dist/lib/directory-lens.d.ts +99 -0
  288. package/dist/lib/directory-lens.js +187 -0
  289. package/dist/lib/directory-lens.js.map +1 -0
  290. package/dist/lib/exporter-coverage.d.ts +71 -0
  291. package/dist/lib/exporter-coverage.js +97 -0
  292. package/dist/lib/exporter-coverage.js.map +1 -0
  293. package/dist/lib/goal-words.d.ts +38 -0
  294. package/dist/lib/goal-words.js +112 -0
  295. package/dist/lib/goal-words.js.map +1 -0
  296. package/dist/lib/hub-client.d.ts +57 -15
  297. package/dist/lib/hub-client.js +396 -82
  298. package/dist/lib/hub-client.js.map +1 -1
  299. package/dist/lib/init-connect.d.ts +41 -0
  300. package/dist/lib/init-connect.js +75 -0
  301. package/dist/lib/init-connect.js.map +1 -0
  302. package/dist/lib/init-exit.d.ts +24 -0
  303. package/dist/lib/init-exit.js +41 -0
  304. package/dist/lib/init-exit.js.map +1 -0
  305. package/dist/lib/init-flow.d.ts +25 -0
  306. package/dist/lib/init-flow.js +40 -0
  307. package/dist/lib/init-flow.js.map +1 -0
  308. package/dist/lib/init-handoff.d.ts +24 -0
  309. package/dist/lib/init-handoff.js +26 -0
  310. package/dist/lib/init-handoff.js.map +1 -0
  311. package/dist/lib/init-output.d.ts +18 -0
  312. package/dist/lib/init-output.js +43 -0
  313. package/dist/lib/init-output.js.map +1 -0
  314. package/dist/lib/init-pod.d.ts +45 -0
  315. package/dist/lib/init-pod.js +36 -0
  316. package/dist/lib/init-pod.js.map +1 -0
  317. package/dist/lib/installed.d.ts +210 -11
  318. package/dist/lib/installed.js +234 -36
  319. package/dist/lib/installed.js.map +1 -1
  320. package/dist/lib/key-approval.d.ts +57 -0
  321. package/dist/lib/key-approval.js +117 -0
  322. package/dist/lib/key-approval.js.map +1 -0
  323. package/dist/lib/key-diagnosis.d.ts +56 -0
  324. package/dist/lib/key-diagnosis.js +227 -0
  325. package/dist/lib/key-diagnosis.js.map +1 -0
  326. package/dist/lib/key-doctor.d.ts +136 -0
  327. package/dist/lib/key-doctor.js +286 -0
  328. package/dist/lib/key-doctor.js.map +1 -0
  329. package/dist/lib/key-locations.d.ts +120 -0
  330. package/dist/lib/key-locations.js +326 -0
  331. package/dist/lib/key-locations.js.map +1 -0
  332. package/dist/lib/key-source.d.ts +68 -0
  333. package/dist/lib/key-source.js +109 -0
  334. package/dist/lib/key-source.js.map +1 -0
  335. package/dist/lib/key-state.d.ts +70 -0
  336. package/dist/lib/key-state.js +115 -0
  337. package/dist/lib/key-state.js.map +1 -0
  338. package/dist/lib/kind-package.d.ts +181 -0
  339. package/dist/lib/kind-package.js +679 -0
  340. package/dist/lib/kind-package.js.map +1 -0
  341. package/dist/lib/knowledge-title.d.ts +25 -0
  342. package/dist/lib/knowledge-title.js +47 -0
  343. package/dist/lib/knowledge-title.js.map +1 -0
  344. package/dist/lib/knowledge-title.test.d.ts +1 -0
  345. package/dist/lib/knowledge-title.test.js +34 -0
  346. package/dist/lib/knowledge-title.test.js.map +1 -0
  347. package/dist/lib/machine-instance.d.ts +64 -0
  348. package/dist/lib/machine-instance.js +142 -0
  349. package/dist/lib/machine-instance.js.map +1 -0
  350. package/dist/lib/mcp-bridge.d.ts +49 -0
  351. package/dist/lib/mcp-bridge.js +180 -0
  352. package/dist/lib/mcp-bridge.js.map +1 -0
  353. package/dist/lib/mcp-bridge.test.d.ts +1 -0
  354. package/dist/lib/mcp-bridge.test.js +118 -0
  355. package/dist/lib/mcp-bridge.test.js.map +1 -0
  356. package/dist/lib/mcp-call.d.ts +30 -0
  357. package/dist/lib/mcp-call.js +107 -0
  358. package/dist/lib/mcp-call.js.map +1 -0
  359. package/dist/lib/mcp-snippets.d.ts +53 -0
  360. package/dist/lib/mcp-snippets.js +82 -0
  361. package/dist/lib/mcp-snippets.js.map +1 -0
  362. package/dist/lib/next-steps.d.ts +8 -0
  363. package/dist/lib/next-steps.js +31 -1
  364. package/dist/lib/next-steps.js.map +1 -1
  365. package/dist/lib/openclaw.js +3 -2
  366. package/dist/lib/openclaw.js.map +1 -1
  367. package/dist/lib/package-wire.d.ts +179 -0
  368. package/dist/lib/package-wire.js +68 -0
  369. package/dist/lib/package-wire.js.map +1 -0
  370. package/dist/lib/pair-link.d.ts +38 -0
  371. package/dist/lib/pair-link.js +68 -0
  372. package/dist/lib/pair-link.js.map +1 -0
  373. package/dist/lib/pod-moved.d.ts +70 -0
  374. package/dist/lib/pod-moved.js +146 -0
  375. package/dist/lib/pod-moved.js.map +1 -0
  376. package/dist/lib/pod-reach.d.ts +39 -0
  377. package/dist/lib/pod-reach.js +117 -0
  378. package/dist/lib/pod-reach.js.map +1 -0
  379. package/dist/lib/pod-reach.test.d.ts +1 -0
  380. package/dist/lib/pod-reach.test.js +67 -0
  381. package/dist/lib/pod-reach.test.js.map +1 -0
  382. package/dist/lib/pod-wide.d.ts +40 -0
  383. package/dist/lib/pod-wide.js +46 -0
  384. package/dist/lib/pod-wide.js.map +1 -0
  385. package/dist/lib/pod.d.ts +74 -112
  386. package/dist/lib/pod.js +146 -415
  387. package/dist/lib/pod.js.map +1 -1
  388. package/dist/lib/project-ref.d.ts +9 -3
  389. package/dist/lib/project-ref.js +10 -4
  390. package/dist/lib/project-ref.js.map +1 -1
  391. package/dist/lib/register-claude-code-mcp.test.d.ts +1 -0
  392. package/dist/lib/register-claude-code-mcp.test.js +78 -0
  393. package/dist/lib/register-claude-code-mcp.test.js.map +1 -0
  394. package/dist/lib/review-credential.d.ts +119 -0
  395. package/dist/lib/review-credential.js +149 -0
  396. package/dist/lib/review-credential.js.map +1 -0
  397. package/dist/lib/safe-open.d.ts +49 -0
  398. package/dist/lib/safe-open.js +119 -0
  399. package/dist/lib/safe-open.js.map +1 -0
  400. package/dist/lib/session-feed.d.ts +185 -0
  401. package/dist/lib/session-feed.js +257 -0
  402. package/dist/lib/session-feed.js.map +1 -0
  403. package/dist/lib/session-lens.d.ts +40 -1
  404. package/dist/lib/session-lens.js +54 -4
  405. package/dist/lib/session-lens.js.map +1 -1
  406. package/dist/lib/superwhisper-import.d.ts +8 -0
  407. package/dist/lib/superwhisper-import.js +14 -7
  408. package/dist/lib/superwhisper-import.js.map +1 -1
  409. package/dist/lib/superwhisper-title.test.d.ts +1 -0
  410. package/dist/lib/superwhisper-title.test.js +18 -0
  411. package/dist/lib/superwhisper-title.test.js.map +1 -0
  412. package/dist/lib/targets.d.ts +152 -10
  413. package/dist/lib/targets.js +974 -325
  414. package/dist/lib/targets.js.map +1 -1
  415. package/dist/lib/template-file.d.ts +4 -0
  416. package/dist/lib/template-file.js.map +1 -1
  417. package/dist/lib/template-lifecycle.d.ts +217 -0
  418. package/dist/lib/template-lifecycle.js +433 -0
  419. package/dist/lib/template-lifecycle.js.map +1 -0
  420. package/dist/lib/template-steps.d.ts +88 -0
  421. package/dist/lib/template-steps.js +118 -0
  422. package/dist/lib/template-steps.js.map +1 -0
  423. package/dist/lib/time-ago.d.ts +2 -0
  424. package/dist/lib/time-ago.js +15 -0
  425. package/dist/lib/time-ago.js.map +1 -0
  426. package/dist/lib/wake-hook-settings.d.ts +57 -0
  427. package/dist/lib/wake-hook-settings.js +153 -0
  428. package/dist/lib/wake-hook-settings.js.map +1 -0
  429. package/dist/lib/wake-state.d.ts +65 -0
  430. package/dist/lib/wake-state.js +122 -0
  431. package/dist/lib/wake-state.js.map +1 -0
  432. package/dist/utils/logger.js +5 -2
  433. package/dist/utils/logger.js.map +1 -1
  434. package/package.json +26 -17
  435. package/skills/README.md +13 -11
  436. package/skills/agent-os/SKILL.md +214 -99
  437. package/skills/manifest.json +1 -1
  438. package/skills/onboard/SKILL.md +55 -26
  439. package/skills/synap/README.md +5 -8
  440. package/skills/synap/SKILL.md +1149 -289
  441. package/skills/synap/_order.txt +7 -0
  442. package/skills/synap/_skill.yaml +1 -1
  443. package/skills/synap/automations.md +15 -0
  444. package/skills/synap/capture.md +5 -0
  445. package/skills/synap/cli-operations.md +24 -18
  446. package/skills/synap/code-skills.md +70 -0
  447. package/skills/synap/concepts.md +36 -0
  448. package/skills/synap/content-os.md +10 -1
  449. package/skills/synap/creative-loop.md +15 -10
  450. package/skills/synap/crm.md +21 -52
  451. package/skills/synap/document-embeds.md +143 -47
  452. package/skills/synap/escalation-ladder.md +20 -10
  453. package/skills/synap/focus-sessions.md +56 -21
  454. package/skills/synap/from-intent.md +158 -0
  455. package/skills/synap/governance-rules.md +5 -5
  456. package/skills/synap/governance.md +10 -7
  457. package/skills/synap/hand-off.md +33 -0
  458. package/skills/synap/inline-patterns.md +15 -7
  459. package/skills/synap/lenses.md +34 -16
  460. package/skills/synap/linking.md +42 -41
  461. package/skills/synap/mcp-remote-servers.md +64 -0
  462. package/skills/synap/mental-model.md +17 -15
  463. package/skills/synap/operating-mode-rules.md +2 -2
  464. package/skills/synap/processes.md +118 -0
  465. package/skills/synap/quick-reference.md +7 -2
  466. package/skills/synap/reading.md +3 -1
  467. package/skills/synap/reflexes.md +12 -42
  468. package/skills/synap/viewframe-cells.md +38 -5
  469. package/skills/synap/work-flow.md +1 -1
  470. package/skills/synap/work-model.md +110 -0
  471. package/skills/synap/worked-examples.md +4 -5
  472. package/skills/synap/workspace-design.md +15 -11
  473. package/skills/synap/writes.md +105 -25
  474. package/skills/synap/writing-titles.md +63 -0
  475. package/skills/synap-app/SKILL.md +149 -67
  476. package/skills/synap-app/_skill.yaml +1 -1
  477. package/skills/synap-app/creating-the-base-app.md +12 -7
  478. package/skills/synap-app/governance.md +27 -16
  479. package/skills/synap-app/install-commands.md +18 -9
  480. package/skills/synap-app/overview.md +16 -9
  481. package/skills/synap-app/using-the-sdk.md +75 -25
  482. package/skills/synap-design/SKILL.md +474 -0
  483. package/skills/synap-design/_order.txt +4 -0
  484. package/skills/synap-design/_skill.yaml +21 -0
  485. package/skills/synap-design/loop.md +134 -0
  486. package/skills/synap-design/overview.md +44 -0
  487. package/skills/synap-design/principles.md +130 -0
  488. package/skills/synap-design/system.md +133 -0
  489. package/skills/synap-market/SKILL.md +306 -16
  490. package/skills/synap-market/_skill.yaml +1 -1
  491. package/skills/synap-market/author-and-publish.md +145 -8
  492. package/skills/synap-market/governance-and-catalog.md +7 -0
  493. package/skills/synap-market/install.md +79 -1
  494. package/skills/synap-market/overview.md +74 -6
  495. package/skills/synap-schema/SKILL.md +172 -10
  496. package/skills/synap-schema/_order.txt +2 -0
  497. package/skills/synap-schema/_skill.yaml +4 -2
  498. package/skills/synap-schema/common-mistakes.md +4 -2
  499. package/skills/synap-schema/discover-profiles.md +1 -1
  500. package/skills/synap-schema/entity-scope.md +15 -3
  501. package/skills/synap-schema/extend-first.md +29 -0
  502. package/skills/synap-schema/extend-vs-create.md +1 -1
  503. package/skills/synap-schema/intent-vocabulary-ssot.md +111 -0
  504. package/skills/synap-schema/property-types.md +1 -1
  505. package/skills/synap-schema/relations.md +1 -1
  506. package/skills/synap-ui/SKILL.md +19 -12
  507. package/skills/synap-ui/_skill.yaml +1 -1
  508. package/skills/synap-ui/ai-companion.md +1 -1
  509. package/skills/synap-ui/arranging-bento.md +2 -0
  510. package/skills/synap-ui/bento-layouts.md +10 -10
  511. package/skills/synap-ui/bento-recipes.md +313 -543
  512. package/skills/synap-ui/four-builds.md +5 -0
  513. package/skills/synap-ui/view-types.md +2 -2
  514. package/skills/synap-ui/widget-catalog.md +22 -278
  515. package/templates/automations/bookmark-enrichment.automation.json +290 -13
  516. package/templates/automations/url-bookmark-capture.automation.json +73 -11
  517. package/templates/box/runner.py +206 -0
  518. package/templates/skills/authoring-playbooks/SKILL.md +6 -3
  519. package/templates/skills/authoring-playbooks/reference/stages-contract.md +15 -0
  520. package/templates/skills/authoring-playbooks/reference/worked-example.md +3 -0
  521. package/dist/commands/infra.d.ts +0 -21
  522. package/dist/commands/infra.js +0 -262
  523. package/dist/commands/infra.js.map +0 -1
  524. package/node_modules/@synap/hub-rest-client/dist/index.cjs +0 -1261
  525. package/node_modules/@synap/hub-rest-client/dist/index.d.cts +0 -1647
  526. package/node_modules/@synap/hub-rest-client/dist/index.d.ts +0 -1647
  527. package/node_modules/@synap/hub-rest-client/dist/index.js +0 -1231
  528. package/node_modules/@synap/hub-rest-client/package.json +0 -45
  529. package/node_modules/@synap/hub-rest-client/src/client.ts +0 -1631
  530. package/node_modules/@synap/hub-rest-client/src/errors.ts +0 -30
  531. package/node_modules/@synap/hub-rest-client/src/index.ts +0 -153
  532. package/node_modules/@synap/hub-rest-client/src/setup.ts +0 -133
  533. package/node_modules/@synap/hub-rest-client/src/types.ts +0 -1299
@@ -8,22 +8,278 @@
8
8
  * (b) the key authenticates against that pod.
9
9
  * (c) the configured workspace (and project) actually exist on THIS pod —
10
10
  * via `GET /orient`, which is the pod's own answer to "what's valid here".
11
- * (d) prints the active lens (pod, workspace, project) so the user sees
11
+ * (d) the Intelligence Service is reachable — the dependency that capture,
12
+ * import and all AI structuring run through. Without it doctor once
13
+ * printed "All checks passed" while every import was 502-ing.
14
+ * (e) the connection broker answers — and when it does not, WHY: broker
15
+ * kind, relay credential presence + expiry, control-plane issuer trust
16
+ * and the owner's identity link, with the one fix for that reason.
17
+ * (f) prints the active lens (pod, workspace, project) so the user sees
12
18
  * exactly where they're pointed.
13
19
  *
14
- * Degrades gracefully on older pods (missing /orient, no release/status route)
15
- * — a check that can't run says so and moves on, it never throws.
20
+ * Degrades gracefully on older pods (missing /orient, no dependency probe) — a
21
+ * check that can't run says so and moves on, it never throws. Crucially, a
22
+ * check that could not run reports "could not determine" and is NOT green: an
23
+ * unknown is not a pass. Only a PROVEN failure sets a non-zero exit code.
16
24
  */
17
25
  import chalk from "chalk";
18
- import { log, banner } from "../utils/logger.js";
19
- import { getActivePodConfig, listPodProfiles, checkPodHealth } from "../lib/pod.js";
26
+ import { log } from "../utils/logger.js";
27
+ import { reachChecklist } from "../lib/pod-reach.js";
28
+ import { getActivePodConfig, getActiveWorkspaceId, getActiveProjectId, listPodProfiles, checkPodHealth, findPodNameByUrl } from "../lib/pod.js";
20
29
  import { resolveHubConfig, hubGet, HubError } from "../lib/hub-client.js";
30
+ import { formatKeySource, resolveKeySource } from "../lib/key-source.js";
31
+ import { resolveLensProvenance } from "../lib/describe-lens.js";
32
+ import { samePodOrigin } from "../lib/project-ref.js";
33
+ import { fetchKeyState } from "../lib/key-state.js";
34
+ import { describeKeyState } from "../lib/key-diagnosis.js";
35
+ import { isFixable } from "../lib/key-doctor.js";
36
+ import { connectionLines } from "../lib/agent-connections.js";
37
+ import { thisComputerInstanceIds } from "../lib/machine-instance.js";
38
+ import { tildify } from "../lib/key-locations.js";
39
+ import { cliBuild, formatCliBuild } from "../lib/cli-build.js";
40
+ import { CP_URL, getStoredToken } from "../lib/auth.js";
41
+ import { askControlPlaneWherePodIs, POD_MOVED_HINT } from "../lib/pod-moved.js";
42
+ import os from "node:os";
43
+ import path from "node:path";
44
+ import { unwrapList } from "../lib/unwrapList.js";
45
+ /**
46
+ * Each agent with a live connection, and the computers it is connected from —
47
+ * one line per agent. Agents with none are ONE summary line (OMIT, with the
48
+ * door to the full list). A failed read says so — never an empty list; an older
49
+ * pod that sends no connections is "cannot tell".
50
+ */
51
+ async function printAgentConnections(cfg) {
52
+ let rows;
53
+ try {
54
+ rows = unwrapList(await hubGet("/agent-users?mine=1", {}, cfg));
55
+ }
56
+ catch (err) {
57
+ console.log(` ${chalk.yellow("?")} Could not read your agents' connections: ${err instanceof Error ? err.message : String(err)}`);
58
+ return;
59
+ }
60
+ if (rows.length === 0) {
61
+ log.dim(" No agents on this pod for you yet — connect one: synap connect");
62
+ return;
63
+ }
64
+ const live = [];
65
+ let idle = 0;
66
+ let olderPod = false;
67
+ for (const row of rows) {
68
+ // Keys are minted with the SURFACE's hash: compare against every surface
69
+ // minting under this agentType, and only when the pod named the type.
70
+ const mine = row.agentType ? thisComputerInstanceIds(row.agentType) : null;
71
+ const lines = connectionLines(row.connections, { isThisComputer: (id) => !!mine?.has(id) });
72
+ if (lines === null)
73
+ olderPod = true;
74
+ else if (lines.length === 0)
75
+ idle++;
76
+ else
77
+ live.push({ name: row.name ?? row.agentType ?? "Agent", lines });
78
+ }
79
+ const width = Math.min(18, Math.max(0, ...live.map((r) => r.name.length)) + 2);
80
+ for (const r of live)
81
+ console.log(` ${r.name.padEnd(width)}${r.lines.join(chalk.dim(" · "))}`);
82
+ if (idle)
83
+ console.log(chalk.dim(` +${idle} agent${idle === 1 ? "" : "s"} with no live connection — synap agents list`));
84
+ if (olderPod)
85
+ console.log(chalk.dim(" (this pod predates per-computer connections — update it to see them)"));
86
+ }
87
+ /** Hosted pods (the only ones the Control Plane can move) live under synap.live. */
88
+ function isSynapHosted(podUrl) {
89
+ try {
90
+ const host = new URL(podUrl).hostname;
91
+ return host === "synap.live" || host.endsWith(".synap.live");
92
+ }
93
+ catch {
94
+ return false;
95
+ }
96
+ }
97
+ /**
98
+ * Why a pod host did not answer, when the reason is a move. Null when it is not
99
+ * one (or nothing suggests it), so the caller keeps its generic message. Asks
100
+ * the Control Plane only on a signal: a redirect to another host, or a hosted
101
+ * address that stopped answering.
102
+ */
103
+ export async function diagnosePodMove(podUrl, health, activeName) {
104
+ if (!health.moved && !isSynapHosted(podUrl))
105
+ return null;
106
+ const answer = await askControlPlaneWherePodIs({
107
+ podUrl,
108
+ cpUrl: CP_URL,
109
+ cpToken: getStoredToken()?.token,
110
+ });
111
+ const name = "Pod host resolves";
112
+ if (answer.kind === "moved") {
113
+ return {
114
+ check: {
115
+ name,
116
+ ok: false,
117
+ detail: `${podUrl} has a new address: ${answer.podUrl}`,
118
+ fix: POD_MOVED_HINT,
119
+ },
120
+ move: { from: podUrl, to: answer.podUrl, profileName: findPodNameByUrl(podUrl) },
121
+ };
122
+ }
123
+ if (!health.moved)
124
+ return null;
125
+ return {
126
+ check: {
127
+ name,
128
+ ok: false,
129
+ detail: `${podUrl} sends its requests to another address, and Synap does not follow it`,
130
+ fix: answer.kind === "signed-out"
131
+ ? "Sign in so Synap can look up the new address: synap login — then run: synap doctor --fix"
132
+ : `Find the pod's address in your account, then run: synap pods update ${activeName} <new-url>`,
133
+ },
134
+ };
135
+ }
136
+ /** `doctor --fix` for a moved pod: the same door as `synap pods update`. */
137
+ async function fixPodMove(move) {
138
+ if (!move.profileName) {
139
+ console.log(` ${chalk.red("✗")} No saved pod uses ${move.from}, so there is nothing to update here.`);
140
+ console.log(chalk.dim(` → Set SYNAP_POD_URL to ${move.to}, or save it: synap pods add`));
141
+ return false;
142
+ }
143
+ const { podsUpdate } = await import("./pods.js");
144
+ return (await podsUpdate(move.profileName, move.to)) && findPodNameByUrl(move.to) === move.profileName;
145
+ }
146
+ /**
147
+ * Read the Intelligence-Service entry out of `GET /api/hub/health/dependencies`.
148
+ *
149
+ * Keys off the pod's real contract: `{ dependencies: [{ name:
150
+ * "intelligence-service", state: "reachable"|"unreachable"|"unresolved",
151
+ * reachable: boolean, reason?, endpoint?, httpStatus?, latencyMs? }] }`.
152
+ *
153
+ * Anything this cannot positively recognise degrades to `unknown` — the CLI
154
+ * ships independently of the pod, so a future field rename must surface as
155
+ * "could not determine", never as a silent green.
156
+ */
157
+ /**
158
+ * Describe which lens the pod resolved the IS under, from its `resolution`
159
+ * echo. Silent when the pod did not say — never guess a lens.
160
+ */
161
+ function readLens(resolution) {
162
+ if (!resolution || typeof resolution !== "object")
163
+ return "";
164
+ const ws = resolution.workspaceId;
165
+ if (typeof ws === "string" && ws)
166
+ return ` (checked as workspace ${ws})`;
167
+ if (ws === null)
168
+ return " (checked pod-level)";
169
+ return "";
170
+ }
171
+ export function readIntelligenceHealth(payload) {
172
+ if (!payload || typeof payload !== "object") {
173
+ return { state: "unknown", detail: "the pod returned no dependency payload" };
174
+ }
175
+ const root = payload;
176
+ const deps = root.dependencies;
177
+ if (!Array.isArray(deps)) {
178
+ return { state: "unknown", detail: "the dependency report carried no `dependencies` array" };
179
+ }
180
+ // The pod echoes back the lens it actually resolved under. Report it: a
181
+ // pod-level "reachable" does not prove the workspace the user imports into
182
+ // routes to a live service, and vice versa.
183
+ const lens = readLens(root.resolution);
184
+ const entry = deps.find((d) => Boolean(d) && typeof d === "object" && d.name === "intelligence-service");
185
+ if (!entry) {
186
+ return {
187
+ state: "unknown",
188
+ detail: "the pod's dependency report listed no intelligence-service entry",
189
+ };
190
+ }
191
+ const reason = typeof entry.reason === "string" ? entry.reason : undefined;
192
+ const where = typeof entry.endpoint === "string" ? entry.endpoint : undefined;
193
+ const suffix = reason ? ` — ${reason}` : "";
194
+ switch (entry.state) {
195
+ case "reachable": {
196
+ const ms = typeof entry.latencyMs === "number" ? ` in ${entry.latencyMs}ms` : "";
197
+ return {
198
+ state: "up",
199
+ detail: `intelligence-service answered${where ? ` at ${where}` : ""}${ms}${lens}`,
200
+ };
201
+ }
202
+ case "unreachable":
203
+ return {
204
+ state: "down",
205
+ detail: `intelligence-service${where ? ` at ${where}` : ""} did not answer${lens}${suffix}`,
206
+ };
207
+ case "unresolved":
208
+ // Determinate, not undetermined: the pod positively told us resolution
209
+ // failed, and a real capture hits that same failure. It is a proven
210
+ // break with a different CAUSE than "the service is down".
211
+ return {
212
+ state: "unresolved",
213
+ detail: `the pod could not determine which Intelligence Service it routes to${lens}${suffix}`,
214
+ };
215
+ default:
216
+ // No readable `state`. Fall back to the boolean mirror ONLY when it is a
217
+ // real boolean — never treat a missing field as healthy.
218
+ if (entry.reachable === true)
219
+ return { state: "up", detail: "intelligence-service answered" };
220
+ if (entry.reachable === false) {
221
+ return { state: "down", detail: `intelligence-service did not answer${suffix}` };
222
+ }
223
+ return { state: "unknown", detail: "the intelligence-service entry carried no readable state" };
224
+ }
225
+ }
226
+ const IS_CHECK_NAME = "Intelligence Service reachable";
227
+ /**
228
+ * Turn an IS verdict into a check row. Names the CONSEQUENCE, not just the
229
+ * dependency: the incident this fixes had a green doctor while every capture /
230
+ * import was 502-ing, so "IS unreachable" alone does not tell the user that the
231
+ * command they are about to run cannot work.
232
+ */
233
+ export function intelligenceCheck(verdict) {
234
+ const consequence = "capture / import / AI structuring WILL FAIL (files import as 0 entities, reported degraded)";
235
+ switch (verdict.state) {
236
+ case "up":
237
+ return { name: IS_CHECK_NAME, ok: true, detail: verdict.detail };
238
+ case "down":
239
+ return {
240
+ name: IS_CHECK_NAME,
241
+ ok: false,
242
+ detail: `${verdict.detail} — ${consequence}`,
243
+ fix: "The pod is up but its Intelligence Service is not. Restart/redeploy the IS, then re-run: synap doctor",
244
+ };
245
+ case "unresolved":
246
+ return {
247
+ name: IS_CHECK_NAME,
248
+ ok: false,
249
+ detail: `${verdict.detail} — ${consequence}`,
250
+ // A different fault from "down": nothing to restart, the routing itself
251
+ // is empty/misconfigured, so the fix points at configuration.
252
+ fix: "No Intelligence Service is configured for this pod/workspace. Check the pod's intelligence-service registry, then re-run: synap doctor",
253
+ };
254
+ default:
255
+ return {
256
+ name: IS_CHECK_NAME,
257
+ ok: false,
258
+ unknown: true,
259
+ detail: `could not determine — ${verdict.detail}. If it is down, ${consequence}.`,
260
+ // `import --dry-run` calls POST /capture/structure and stops before
261
+ // /capture/execute — it exercises the exact door that 502s when the IS
262
+ // is down, and writes nothing. (`capture` has no --dry-run.)
263
+ fix: "This pod build may predate GET /api/hub/health/dependencies. Probe by hand (writes nothing): synap import <a-file.md> --dry-run",
264
+ };
265
+ }
266
+ }
21
267
  export async function doctor(opts = {}) {
22
268
  const checks = [];
23
269
  const activeName = listPodProfiles().find((p) => p.active)?.name ?? "default";
270
+ // Resolve lens FIRST so `--pod` / env / profile agree on URL + workspace
271
+ // (host check used to use only active profile → `synap --pod team doctor`
272
+ // reported perso's host while Active Lens showed team).
273
+ let cfgResolved;
274
+ try {
275
+ cfgResolved = await resolveHubConfig();
276
+ }
277
+ catch {
278
+ /* fall through to profile/env */
279
+ }
24
280
  // ── (a) Host resolves — the pod is actually served at this URL ───────────
25
281
  const localConfig = getActivePodConfig();
26
- const podUrl = localConfig?.podUrl ?? process.env.SYNAP_POD_URL;
282
+ const podUrl = opts.podUrl ?? cfgResolved?.podUrl ?? localConfig?.podUrl ?? process.env.SYNAP_POD_URL;
27
283
  if (!podUrl) {
28
284
  checks.push({
29
285
  name: "Pod URL configured",
@@ -34,7 +290,10 @@ export async function doctor(opts = {}) {
34
290
  return finish(checks, opts, { podUrl: undefined, workspaceId: undefined, projectId: undefined });
35
291
  }
36
292
  let hostServed = false;
293
+ let podMove;
294
+ const names = {};
37
295
  const health = await checkPodHealth(podUrl);
296
+ names.podVersion = health.version;
38
297
  if (health.healthy) {
39
298
  hostServed = true;
40
299
  checks.push({
@@ -44,19 +303,38 @@ export async function doctor(opts = {}) {
44
303
  });
45
304
  }
46
305
  else {
47
- // The classic failure this check exists for: a saved profile URL that no
48
- // longer resolves to a live pod — most often a stale/migrated domain, not
49
- // a transient network blip (checkPodHealth already retried the timeout).
50
- checks.push({
51
- name: "Pod host resolves",
52
- ok: false,
53
- detail: `${podUrl} did not answer /health`,
54
- fix: `Your profile URL may be stale. Run: synap pods list (see saved pods) — or: synap pods update ${activeName} <new-url>`,
55
- });
306
+ // A moved pod: the old name redirects elsewhere, or (after the redirect
307
+ // window) no longer answers. Only the Control Plane says where it went.
308
+ const diagnosis = await diagnosePodMove(podUrl, health, activeName);
309
+ if (diagnosis) {
310
+ checks.push(diagnosis.check);
311
+ podMove = diagnosis.move;
312
+ }
313
+ else {
314
+ // The classic failure this check exists for: a saved profile URL that no
315
+ // longer resolves to a live pod — most often a stale/migrated domain, not
316
+ // a transient network blip (checkPodHealth already retried the timeout).
317
+ const f = health.failure;
318
+ const networkSide = f?.kind === "proxy" || f?.kind === "network";
319
+ checks.push({
320
+ name: "Pod host resolves",
321
+ ok: false,
322
+ detail: f
323
+ ? `${podUrl}: ${f.detail}${f.proxy ? ` — via ${f.proxy.env}=${f.proxy.url}` : ""}`
324
+ : `${podUrl} did not answer /health`,
325
+ fix: networkSide
326
+ ? reachChecklist(f).join("\n")
327
+ : `Your profile URL may be stale. Run: synap pods list (see saved pods) — or: synap pods update ${activeName} <new-url>`,
328
+ });
329
+ }
330
+ }
331
+ // An explicit --pod-url is a reachability probe of THAT pod: the configured
332
+ // key and lens belong to another pod, so do not judge them against it.
333
+ if (opts.podUrl) {
334
+ return finish(checks, opts, { podUrl, workspaceId: undefined, projectId: undefined, podVersion: names.podVersion }, undefined, undefined, podMove);
56
335
  }
57
336
  // ── (b) Key authenticates against this pod ────────────────────────────────
58
337
  let keyValid = false;
59
- let cfgResolved;
60
338
  if (!hostServed) {
61
339
  checks.push({
62
340
  name: "API key authenticates",
@@ -67,7 +345,7 @@ export async function doctor(opts = {}) {
67
345
  }
68
346
  else {
69
347
  try {
70
- const cfg = await resolveHubConfig();
348
+ const cfg = cfgResolved ?? (await resolveHubConfig());
71
349
  cfgResolved = cfg;
72
350
  await hubGet("/users/me", {}, cfg);
73
351
  keyValid = true;
@@ -75,11 +353,13 @@ export async function doctor(opts = {}) {
75
353
  }
76
354
  catch (err) {
77
355
  const is401 = err instanceof HubError ? err.status === 401 : false;
356
+ // Say WHY — awaiting approval, replaced, expired — never a guess.
357
+ const why = is401 && cfgResolved ? describeKeyState(await fetchKeyState(cfgResolved.podUrl, cfgResolved.apiKey)) : null;
78
358
  checks.push({
79
359
  name: "API key authenticates",
80
360
  ok: false,
81
- detail: is401 ? "the pod rejected this key (expired or revoked)" : `could not verify (${err instanceof Error ? err.message : String(err)})`,
82
- fix: `Run: synap login --reconnect ${activeName}`,
361
+ detail: is401 ? `the pod rejected this key: it ${why}` : `could not verify (${err instanceof Error ? err.message : String(err)})`,
362
+ fix: is401 ? "See the key health below — run: synap doctor --fix" : `Run: synap login --reconnect ${activeName}`,
83
363
  });
84
364
  }
85
365
  }
@@ -102,6 +382,7 @@ export async function doctor(opts = {}) {
102
382
  if (cfgResolved.workspaceId) {
103
383
  const match = workspaces.find((w) => w.id === cfgResolved.workspaceId);
104
384
  workspaceOk = Boolean(match);
385
+ names.workspace = match?.name;
105
386
  checks.push({
106
387
  name: "Configured workspace exists on this pod",
107
388
  ok: workspaceOk,
@@ -121,6 +402,7 @@ export async function doctor(opts = {}) {
121
402
  if (cfgResolved.projectId) {
122
403
  const match = projects.find((p) => p.id === cfgResolved.projectId);
123
404
  projectOk = Boolean(match);
405
+ names.project = match?.name;
124
406
  checks.push({
125
407
  name: "Configured project exists on this pod",
126
408
  ok: projectOk,
@@ -143,47 +425,485 @@ export async function doctor(opts = {}) {
143
425
  });
144
426
  }
145
427
  }
428
+ // ── (d) The Intelligence Service the capture/import pipeline depends on ───
429
+ // Doctor used to stop at config coherence and print "All checks passed"
430
+ // while POST /api/hub/capture/structure was 502-ing — a diagnostic that
431
+ // reports all-clear over a dead core dependency actively misdirects.
432
+ if (!keyValid || !cfgResolved) {
433
+ checks.push({
434
+ name: "Intelligence Service reachable",
435
+ ok: false,
436
+ unknown: true,
437
+ detail: "could not determine — the key did not authenticate, so the pod's dependency probe was not called",
438
+ fix: "Fix the API key check above first.",
439
+ });
440
+ }
441
+ else {
442
+ checks.push(intelligenceCheck(await probeIntelligence(cfgResolved)));
443
+ }
444
+ // ── (e) The connection broker, and why it fails when it does ─────────────
445
+ if (!keyValid || !cfgResolved) {
446
+ checks.push({
447
+ name: BROKER_CHECK_NAME,
448
+ ok: false,
449
+ unknown: true,
450
+ detail: "could not determine — the key did not authenticate",
451
+ fix: "Fix the API key check above first.",
452
+ });
453
+ }
454
+ else {
455
+ const [providers, diagnostics] = await Promise.all([
456
+ probeBrokerProviders(cfgResolved),
457
+ probeBrokerDiagnostics(cfgResolved),
458
+ ]);
459
+ checks.push(brokerCheck(providers, diagnostics));
460
+ }
461
+ // ── (f) Every key configured for this pod, and why each one fails ───────
462
+ let keyHealth;
463
+ if (hostServed) {
464
+ const { checkKeyHealth, keyProblems } = await import("../lib/key-doctor.js");
465
+ keyHealth = await checkKeyHealth(cfgResolved?.podUrl ?? podUrl);
466
+ const problems = keyProblems(keyHealth);
467
+ const failing = problems.filter((d) => d.severity === "error");
468
+ const unverifiable = problems.filter((d) => d.code === "unverifiable");
469
+ const why = [...new Set(unverifiable.map((d) => (d.key && keyHealth.states.get(d.key)?.error) || "no answer"))].join("; ");
470
+ checks.push({
471
+ name: "Keys on this computer",
472
+ ok: problems.length === 0,
473
+ unknown: problems.length > 0 && failing.length === 0 && problems.every((d) => d.code === "unverifiable"),
474
+ detail: problems.length === 0
475
+ ? `${keyHealth.locations.length} configured cop${keyHealth.locations.length === 1 ? "y" : "ies"}, all working`
476
+ : unverifiable.length === problems.length
477
+ ? `${unverifiable.length} key${unverifiable.length === 1 ? "" : "s"} could not be checked (${why})`
478
+ : `${problems.length} problem${problems.length === 1 ? "" : "s"}`,
479
+ // Only a problem --fix can act on earns the --fix hint.
480
+ fix: problems.some(isFixable) ? "Run: synap doctor --fix" : undefined,
481
+ });
482
+ }
146
483
  await finish(checks, opts, {
147
- podUrl,
484
+ // Prefer the pod resolveHubConfig actually hit — health-check URL can lag
485
+ // the active profile when the shell is env-pinned to another pod.
486
+ podUrl: cfgResolved?.podUrl ?? podUrl,
148
487
  workspaceId: cfgResolved?.workspaceId,
149
488
  projectId: cfgResolved?.projectId,
150
- });
489
+ ...names,
490
+ }, keyHealth, cfgResolved, podMove);
491
+ }
492
+ /**
493
+ * Call the pod's dependency probe. Every failure mode that is not a positive
494
+ * "the IS is down" answer maps to `unknown` — an older pod that 404s the door
495
+ * must never read as healthy.
496
+ */
497
+ async function probeIntelligence(cfg) {
498
+ try {
499
+ // Pass the configured workspace so the pod resolves the IS exactly as a
500
+ // capture FROM THAT WORKSPACE would (resolution is capability → workspace →
501
+ // user → pod default). A pod-level probe could pass while the workspace the
502
+ // user actually imports into routes to a dead service.
503
+ return readIntelligenceHealth(await hubGet("/health/dependencies", { workspaceId: cfg.workspaceId }, cfg));
504
+ }
505
+ catch (err) {
506
+ if (err instanceof HubError && err.status === 404) {
507
+ return {
508
+ state: "unknown",
509
+ detail: "this pod does not expose GET /api/hub/health/dependencies (older build)",
510
+ };
511
+ }
512
+ if (err instanceof HubError && err.status === 403) {
513
+ return {
514
+ state: "unknown",
515
+ detail: "this key lacks the hub-protocol.read scope needed to read the dependency probe",
516
+ };
517
+ }
518
+ // The probe itself failing is not proof the IS is down — say only that.
519
+ return {
520
+ state: "unknown",
521
+ detail: `the dependency probe did not answer (${err instanceof Error ? err.message : String(err)})`,
522
+ };
523
+ }
524
+ }
525
+ const BROKER_CHECK_NAME = "Connection broker";
526
+ export function readProvidersEnvelope(payload) {
527
+ if (!payload || typeof payload !== "object") {
528
+ return { state: "unknown", detail: "the providers door returned no payload" };
529
+ }
530
+ const p = payload;
531
+ if (p.nangoStatus === "ok")
532
+ return { state: "ok" };
533
+ if (p.nangoStatus === "error") {
534
+ return {
535
+ state: "error",
536
+ reason: typeof p.nangoError?.reason === "string" ? p.nangoError.reason : "unknown",
537
+ message: typeof p.nangoError?.message === "string" ? p.nangoError.message : undefined,
538
+ };
539
+ }
540
+ return { state: "unknown", detail: "the providers door carried no readable nangoStatus" };
541
+ }
542
+ export function readBrokerDiagnostics(payload) {
543
+ const r = payload;
544
+ const readable = !!r &&
545
+ typeof r.cpIssuer?.present === "boolean" &&
546
+ typeof r.cpIssuer?.hasSourceConfigWrite === "boolean" &&
547
+ typeof r.ownerIdentityLink?.present === "boolean" &&
548
+ typeof r.relayCredential?.present === "boolean" &&
549
+ typeof r.broker?.kind === "string";
550
+ if (!readable) {
551
+ return { state: "unknown", detail: "the broker diagnostics payload was not readable" };
552
+ }
553
+ return { state: "read", report: r };
151
554
  }
152
- async function finish(checks, opts, lens) {
555
+ /** The one fix for a broker fault, sharpened by the trust diagnostics when readable. */
556
+ export function brokerFixHint(reason, diagnostics, message) {
557
+ if (reason === "broker-credential-missing") {
558
+ if (diagnostics.state !== "read") {
559
+ return "The pod holds no usable relay key from its control plane. Its trust state is unreadable here (needs a pod owner/admin key on a pod serving /connectors/broker-diagnostics) — check the control plane log for the relay-key delivery refusal, then rotate the pod's relay key from the control plane.";
560
+ }
561
+ return credentialMissingHint(diagnostics.report);
562
+ }
563
+ switch (reason) {
564
+ case "unauthenticated":
565
+ return "The broker rejected this pod's credential. Rotate the pod's relay key from the control plane (or, for a self-brokered pod, re-save its Nango key).";
566
+ case "unreachable":
567
+ return "The broker did not answer. Check that the control plane / Nango host is up and reachable from the pod.";
568
+ case "vault-unreadable":
569
+ return "The pod could not decrypt its broker credential. Check VAULT_SERVER_KEY on the pod.";
570
+ case "vault-unresolved":
571
+ return "The pod holds a relay key from its control plane but cannot read it from its vault. Rotate the pod's relay key from the control plane — it re-delivers a readable key.";
572
+ case "db-unavailable":
573
+ return "The pod could not read its database. Check the pod's health and logs.";
574
+ default:
575
+ return message ?? `The broker reported ${reason}.`;
576
+ }
577
+ }
578
+ function credentialMissingHint(r) {
579
+ if (!r.cpIssuer.present) {
580
+ return "The pod has no control plane issuer, so it cannot accept a relay key. Check CONTROL_PLANE_URL on the pod and restart it, then rotate the relay key from the control plane.";
581
+ }
582
+ if (r.cpIssuer.status !== "approved" || !r.cpIssuer.hasSourceConfigWrite) {
583
+ return "Approve the Synap Control Plane issuer with 'Configure data sources' (source-config:write) in pod-admin → Trust & Keys → Trusted issuers, then rotate the relay key from the control plane.";
584
+ }
585
+ if (!r.ownerIdentityLink.present) {
586
+ return "The pod owner's control plane identity is not linked on this pod, so relay-key delivery is refused. Link it (sign in to this pod through the control plane), then rotate the relay key from the control plane.";
587
+ }
588
+ return "Trust is in place but no valid relay key has been delivered. Rotate the pod's relay key from the control plane.";
589
+ }
590
+ /** Human summary of the trust facts; "unknown" for any part that could not be read. */
591
+ function describeBrokerTrust(diagnostics) {
592
+ if (diagnostics.state !== "read") {
593
+ return `broker kind unknown; credential unknown; issuer unknown; owner link unknown (${diagnostics.detail})`;
594
+ }
595
+ const r = diagnostics.report;
596
+ const credential = !r.relayCredential.present
597
+ ? "credential missing"
598
+ : r.relayCredential.resolvable === false
599
+ ? "credential present but unreadable"
600
+ : `credential present, valid until ${r.relayCredential.validUntil ?? "unknown"}`;
601
+ if (r.broker.kind !== "control-plane") {
602
+ return `broker ${r.broker.kind}`;
603
+ }
604
+ const issuer = !r.cpIssuer.present
605
+ ? "CP issuer missing"
606
+ : r.cpIssuer.status === "approved" && r.cpIssuer.hasSourceConfigWrite
607
+ ? "CP issuer ok"
608
+ : `CP issuer ${r.cpIssuer.status ?? "unknown"}${r.cpIssuer.hasSourceConfigWrite ? "" : " without source-config:write"}`;
609
+ const link = r.ownerIdentityLink.present ? "owner link ok" : "owner link missing";
610
+ return `broker ${r.broker.kind}; ${credential}; ${issuer}; ${link}`;
611
+ }
612
+ export function brokerCheck(providers, diagnostics) {
613
+ const trust = describeBrokerTrust(diagnostics);
614
+ if (providers.state === "unknown") {
615
+ return {
616
+ name: BROKER_CHECK_NAME,
617
+ ok: false,
618
+ unknown: true,
619
+ detail: `could not determine — ${providers.detail}; ${trust}`,
620
+ fix: "This pod build may predate GET /api/hub/connectors/providers, or the key lacks hub-protocol.read.",
621
+ };
622
+ }
623
+ if (providers.state === "not-configured") {
624
+ return {
625
+ name: BROKER_CHECK_NAME,
626
+ ok: true,
627
+ detail: "no connection broker is configured on this pod — connectors are unavailable",
628
+ };
629
+ }
630
+ if (providers.state === "ok") {
631
+ return { name: BROKER_CHECK_NAME, ok: true, detail: `broker answered; ${trust}` };
632
+ }
633
+ return {
634
+ name: BROKER_CHECK_NAME,
635
+ ok: false,
636
+ detail: `${providers.reason}; ${trust}`,
637
+ fix: brokerFixHint(providers.reason, diagnostics, providers.message),
638
+ };
639
+ }
640
+ async function probeBrokerProviders(cfg) {
641
+ try {
642
+ return readProvidersEnvelope(await hubGet("/connectors/providers", {}, cfg));
643
+ }
644
+ catch (err) {
645
+ const body = err instanceof HubError ? err.body : undefined;
646
+ if (err instanceof HubError && err.status === 503 && body?.error === "Nango not configured") {
647
+ return { state: "not-configured" };
648
+ }
649
+ return {
650
+ state: "unknown",
651
+ detail: `the providers door did not answer (${err instanceof Error ? err.message : String(err)})`,
652
+ };
653
+ }
654
+ }
655
+ async function probeBrokerDiagnostics(cfg) {
656
+ try {
657
+ return readBrokerDiagnostics(await hubGet("/connectors/broker-diagnostics", {}, cfg));
658
+ }
659
+ catch (err) {
660
+ if (err instanceof HubError && err.status === 404) {
661
+ return { state: "unknown", detail: "this pod does not expose broker diagnostics (older build)" };
662
+ }
663
+ if (err instanceof HubError && err.status === 403) {
664
+ return { state: "unknown", detail: "broker diagnostics need a pod owner/admin key" };
665
+ }
666
+ return {
667
+ state: "unknown",
668
+ detail: `broker diagnostics did not answer (${err instanceof Error ? err.message : String(err)})`,
669
+ };
670
+ }
671
+ }
672
+ /**
673
+ * Build the same provenance rungs as `synap lens` (session › directory › env ›
674
+ * global), pod-qualified against the active pod so a cross-pod directory lens
675
+ * is not claimed as the origin.
676
+ */
677
+ async function activeLensProvenance(podUrl) {
678
+ const { getClaudeSessionId, resolveActiveLensForPod } = await import("../lib/session-lens.js");
679
+ const { resolveDirectoryLensForPod, effectiveDirectoryLens } = await import("../lib/directory-lens.js");
680
+ const session = getClaudeSessionId();
681
+ const sessionLens = resolveActiveLensForPod(podUrl);
682
+ const dir = resolveDirectoryLensForPod(podUrl);
683
+ const rungs = [
684
+ { source: "session", detail: session ? session.slice(0, 8) : undefined, ...(sessionLens ?? {}) },
685
+ { source: "directory", detail: dir?.file, ...(dir ? effectiveDirectoryLens(dir.lens) : {}) },
686
+ {
687
+ source: "env",
688
+ detail: "SYNAP_WORKSPACE_ID",
689
+ workspaceId: process.env.SYNAP_WORKSPACE_ID,
690
+ projectId: process.env.SYNAP_PROJECT_ID,
691
+ },
692
+ {
693
+ source: "global",
694
+ detail: "~/.synap/config.json",
695
+ workspaceId: getActiveWorkspaceId(),
696
+ projectId: getActiveProjectId(),
697
+ },
698
+ ];
699
+ return resolveLensProvenance(rungs);
700
+ }
701
+ /** A diagnosis for JSON output — never a key's plaintext. */
702
+ function publicDiagnosis(d) {
703
+ return {
704
+ code: d.code,
705
+ severity: d.severity,
706
+ surface: d.surface,
707
+ title: d.title,
708
+ lines: d.lines,
709
+ command: d.command,
710
+ action: d.action,
711
+ approvalUrl: d.approvalUrl,
712
+ locations: d.locations.map((l) => l.label),
713
+ };
714
+ }
715
+ /** One word per check for the collapsed "✓ Pod · key · …" line. */
716
+ const SHORT_NAME = {
717
+ "Pod URL configured": "pod URL",
718
+ "Pod host resolves": "Pod",
719
+ "API key authenticates": "key",
720
+ "Configured workspace exists on this pod": "workspace",
721
+ "Configured project exists on this pod": "project",
722
+ "Workspace + project exist on this pod": "workspace",
723
+ [IS_CHECK_NAME]: "intelligence",
724
+ [BROKER_CHECK_NAME]: "broker",
725
+ "Keys on this computer": "agent keys",
726
+ };
727
+ const UUID = /\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b/gi;
728
+ const ISO = /\b(\d{4}-\d{2}-\d{2})T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:?\d{2})?/g;
729
+ const INTERNAL_URL = /\s*(?:at\s+)?\bhttps?:\/\/(?:localhost|127\.\d+\.\d+\.\d+|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(?:1[6-9]|2\d|3[01])\.\d+\.\d+|[a-z0-9-]+(?::\d+)?(?=[\s/)]|$))[^\s)]*/gi;
730
+ /**
731
+ * Default output never shows internal addresses, ids or raw timestamps — they
732
+ * are noise to a person and stay one `-v` away. A parenthetical that only
733
+ * carried an id ("(checked as workspace <uuid>)") goes entirely.
734
+ */
735
+ export function forDisplay(text, verbose = false) {
736
+ if (verbose)
737
+ return text;
738
+ return text
739
+ .replace(/\s*\([^()]*\b[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\b[^()]*\)/gi, "")
740
+ .replace(UUID, "…")
741
+ .replace(INTERNAL_URL, "")
742
+ .replace(ISO, "$1")
743
+ .replace(/\s{2,}/g, " ")
744
+ .trim();
745
+ }
746
+ /** Where a lens field came from, short: "./.synap/lens.json", "SYNAP_WORKSPACE_ID", "this Claude session". */
747
+ function lensOrigin(p) {
748
+ if (!p)
749
+ return undefined;
750
+ if (p.source === "session")
751
+ return "this Claude session";
752
+ if (p.source === "env")
753
+ return p.detail ?? "environment";
754
+ if (p.source === "global")
755
+ return "~/.synap/config.json";
756
+ if (p.detail) {
757
+ const rel = path.relative(process.cwd(), p.detail);
758
+ return rel && !rel.startsWith("..") && !path.isAbsolute(rel) ? `./${rel}` : tildify(p.detail, os.homedir());
759
+ }
760
+ return p.source;
761
+ }
762
+ async function finish(checks, opts, lens, keyHealth, hubCfg, podMove) {
763
+ // Green requires every check to have VERIFIED something. An undetermined
764
+ // check suppresses green (it is not a pass) but does not fail the run — only
765
+ // a proven failure sets a non-zero exit code.
153
766
  const allOk = checks.every((c) => c.ok);
767
+ const anyFailed = checks.some((c) => !c.ok && !c.unknown);
768
+ const anyUnknown = checks.some((c) => c.unknown);
154
769
  const activeName = listPodProfiles().find((p) => p.active)?.name ?? "default";
770
+ const keySource = resolveKeySource();
771
+ const provenance = await activeLensProvenance(lens.podUrl);
155
772
  if (opts.json) {
156
773
  console.log(JSON.stringify({
157
774
  ok: allOk,
775
+ undetermined: anyUnknown,
158
776
  checks,
159
- lens: { pod: activeName, podUrl: lens.podUrl, workspaceId: lens.workspaceId, projectId: lens.projectId },
777
+ keyHealth: keyHealth ? keyHealth.diagnoses.map(publicDiagnosis) : null,
778
+ keysNotChecked: keyHealth
779
+ ? {
780
+ unreadable: keyHealth.unchecked ?? [],
781
+ notSentToThisPod: (keyHealth.notSent ?? []).map((l) => l.label),
782
+ }
783
+ : null,
784
+ cli: cliBuild(),
785
+ lens: {
786
+ pod: activeName,
787
+ podUrl: lens.podUrl,
788
+ workspaceId: lens.workspaceId,
789
+ projectId: lens.projectId,
790
+ keySource,
791
+ provenance,
792
+ },
160
793
  }, null, 2));
161
- if (!allOk)
794
+ if (anyFailed)
162
795
  process.exitCode = 1;
163
796
  return;
164
797
  }
165
- banner();
166
- log.heading("Coherence Preflight");
167
- for (const c of checks) {
168
- const mark = c.ok ? chalk.green("✓") : chalk.red("✗");
169
- console.log(` ${mark} ${chalk.bold(c.name)}`);
170
- log.dim(` ${c.detail}`);
171
- if (!c.ok && c.fix)
172
- log.dim(` → ${c.fix}`);
798
+ const v = !!opts.verbose;
799
+ const show = (t) => forDisplay(t, v);
800
+ // ── Header: which pod, which lens — names, not ids ──────────────────────
801
+ const lensPodName = (lens.podUrl &&
802
+ listPodProfiles().find((p) => p.config.podUrl.replace(/\/$/, "") === lens.podUrl.replace(/\/$/, ""))?.name) ||
803
+ activeName;
804
+ let host = lens.podUrl ?? "no pod";
805
+ try {
806
+ if (lens.podUrl)
807
+ host = new URL(lens.podUrl).host;
808
+ }
809
+ catch {
810
+ /* keep raw */
173
811
  }
174
812
  log.blank();
175
- log.heading("Active Lens");
176
- console.log(` Pod : ${chalk.bold(activeName)}${lens.podUrl ? ` ${chalk.dim(lens.podUrl)}` : ""}`);
177
- console.log(` Workspace : ${lens.workspaceId ? lens.workspaceId : chalk.dim("none (pod-wide)")}`);
178
- console.log(` Project : ${lens.projectId ? lens.projectId : chalk.dim("none")}`);
813
+ console.log(`${chalk.bold("synap doctor")} ${chalk.dim("·")} ${lensPodName} ${chalk.dim("·")} ${host}${lens.podVersion ? chalk.dim(` (v${lens.podVersion})`) : ""}`);
814
+ const wsName = lens.workspaceId ? lens.workspace ?? (v ? lens.workspaceId : "a workspace this pod does not list") : "pod-wide";
815
+ const lensText = lens.projectId ? `${wsName} › ${lens.project ?? (v ? lens.projectId : "a project this pod does not list")}` : wsName;
816
+ const origin = lensOrigin(provenance.project ?? provenance.workspace);
817
+ console.log(`${chalk.dim("Lens")} ${lensText}${origin && (lens.workspaceId || lens.projectId) ? chalk.dim(` (from ${origin})`) : ""}`);
818
+ // Which build is talking: a stale CLI is the first thing to rule out.
819
+ console.log(`${chalk.dim("CLI")} ${formatCliBuild(cliBuild())}`);
820
+ if (v) {
821
+ console.log(`${chalk.dim("Key")} ${formatKeySource(keySource)}`);
822
+ if (lens.podUrl)
823
+ console.log(`${chalk.dim("URL")} ${lens.podUrl}`);
824
+ }
179
825
  log.blank();
180
- if (allOk) {
181
- log.success("All checks passed.");
826
+ // ── Checks: passes collapse to one line, failures/unknowns in full ───────
827
+ const keyRow = checks.find((c) => c.name === "Keys on this computer");
828
+ const printRow = (c) => {
829
+ const mark = c.ok ? chalk.green("✓") : c.unknown ? chalk.yellow("?") : chalk.red("✗");
830
+ console.log(`${mark} ${chalk.bold(c.name)} — ${show(c.detail)}`);
831
+ if (!c.ok && c.fix)
832
+ console.log(chalk.dim(` → ${show(c.fix)}`));
833
+ };
834
+ const passed = checks.filter((c) => c.ok);
835
+ if (v)
836
+ for (const c of checks)
837
+ printRow(c);
838
+ else if (passed.length) {
839
+ const words = [...new Set(passed.map((c) => SHORT_NAME[c.name] ?? c.name))];
840
+ console.log(`${chalk.green("✓")} ${words.join(" · ")}${chalk.dim(" (details with -v)")}`);
182
841
  }
183
- else {
184
- log.warn("One or more checks failed — see the fixes above.");
185
- process.exitCode = 1;
842
+ if (!v)
843
+ for (const c of checks.filter((c) => !c.ok && c !== keyRow))
844
+ printRow(c);
845
+ // The key row's problems are the key-health lines themselves — no second copy.
846
+ if (keyHealth) {
847
+ const { printKeyHealth, fixKeyHealth, keyProblems } = await import("../lib/key-doctor.js");
848
+ printKeyHealth(keyHealth, { fixHint: !opts.fix, verbose: v, showOk: v, indent: "" });
849
+ if (opts.fix && keyProblems(keyHealth).some(isFixable)) {
850
+ log.blank();
851
+ console.log(chalk.bold("Fixing"));
852
+ const outcomes = await fixKeyHealth(keyHealth);
853
+ for (const o of outcomes)
854
+ console.log(` ${o.ok ? chalk.green("✓") : chalk.red("✗")} ${o.message}`);
855
+ // Every problem fixed AND verified live over MCP: the key row is green now.
856
+ if (keyRow && outcomes.length > 0 && outcomes.every((o) => o.ok)) {
857
+ keyRow.ok = true;
858
+ keyRow.unknown = false;
859
+ keyRow.detail = "fixed and verified (MCP initialize)";
860
+ }
861
+ }
186
862
  }
863
+ // ── A moved pod: rewrite the saved address through the one door ──────────
864
+ if (podMove && opts.fix) {
865
+ log.blank();
866
+ console.log(chalk.bold("Fixing"));
867
+ const hostRow = checks.find((c) => c.name === "Pod host resolves");
868
+ if (await fixPodMove(podMove)) {
869
+ if (hostRow) {
870
+ hostRow.ok = true;
871
+ hostRow.detail = `updated to ${podMove.to} — run synap doctor again to check the rest`;
872
+ }
873
+ }
874
+ }
875
+ // ── One agent, several computers ─────────────────────────────────────────
876
+ if (hubCfg) {
877
+ log.blank();
878
+ console.log(chalk.bold("Connected agents"));
879
+ await printAgentConnections(hubCfg);
880
+ }
881
+ // Env pin beats the active profile in resolveHubConfig — same divergence
882
+ // `synap pods list` already names. Surface it here so doctor alone is enough
883
+ // to catch "profile says perso, every write goes to team".
884
+ const envPod = process.env.SYNAP_POD_URL?.trim();
885
+ const activeProfile = listPodProfiles().find((p) => p.active);
886
+ if (envPod && activeProfile && !samePodOrigin(envPod, activeProfile.config.podUrl)) {
887
+ log.blank();
888
+ log.warn(`SYNAP_POD_URL overrides the active profile — commands are talking to ${envPod}, not ${activeProfile.config.podUrl}.`);
889
+ log.dim(" The env var wins over the ● active profile. Unset it (or open a fresh shell) for the profile to apply.");
890
+ }
891
+ // ── Verdict: the last line. Recomputed: --fix may have turned keys green.
892
+ const finalOk = checks.every((c) => c.ok);
893
+ const finalFailed = checks.some((c) => !c.ok && !c.unknown);
187
894
  log.blank();
895
+ if (finalOk) {
896
+ console.log(chalk.green("✓ All checks passed."));
897
+ }
898
+ else if (finalFailed) {
899
+ const n = checks.filter((c) => !c.ok && !c.unknown).length;
900
+ console.log(chalk.red(`✗ ${n} check${n === 1 ? "" : "s"} failed — the actions above fix ${n === 1 ? "it" : "them"}.`));
901
+ process.exitCode = 1;
902
+ }
903
+ else {
904
+ // Not green: something could not be determined. Saying "all clear" here is
905
+ // the failure mode this command exists to avoid.
906
+ console.log(chalk.yellow("? Nothing failed, but not everything could be checked — see the ? lines."));
907
+ }
188
908
  }
189
909
  //# sourceMappingURL=doctor.js.map