@synap-core/cli 1.7.0 → 1.10.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 (371) hide show
  1. package/dist/commands/agent-chat.d.ts +47 -0
  2. package/dist/commands/agent-chat.js +250 -0
  3. package/dist/commands/agent-chat.js.map +1 -0
  4. package/dist/commands/agent-run.d.ts +47 -0
  5. package/dist/commands/agent-run.js +301 -0
  6. package/dist/commands/agent-run.js.map +1 -0
  7. package/dist/commands/agents.js +85 -46
  8. package/dist/commands/agents.js.map +1 -1
  9. package/dist/commands/artifact.d.ts +26 -0
  10. package/dist/commands/artifact.js +76 -0
  11. package/dist/commands/artifact.js.map +1 -0
  12. package/dist/commands/automation.d.ts +47 -0
  13. package/dist/commands/automation.js +601 -0
  14. package/dist/commands/automation.js.map +1 -0
  15. package/dist/commands/bridge-setup.d.ts +66 -0
  16. package/dist/commands/bridge-setup.js +1112 -0
  17. package/dist/commands/bridge-setup.js.map +1 -0
  18. package/dist/commands/cap-connections.d.ts +28 -0
  19. package/dist/commands/cap-connections.js +224 -0
  20. package/dist/commands/cap-connections.js.map +1 -0
  21. package/dist/commands/capability.d.ts +89 -0
  22. package/dist/commands/capability.js +1523 -0
  23. package/dist/commands/capability.js.map +1 -0
  24. package/dist/commands/cell.d.ts +55 -0
  25. package/dist/commands/cell.js +277 -0
  26. package/dist/commands/cell.js.map +1 -0
  27. package/dist/commands/centrality.d.ts +20 -0
  28. package/dist/commands/centrality.js +94 -0
  29. package/dist/commands/centrality.js.map +1 -0
  30. package/dist/commands/connect.d.ts +2 -0
  31. package/dist/commands/connect.js +25 -10
  32. package/dist/commands/connect.js.map +1 -1
  33. package/dist/commands/connections.js +0 -16
  34. package/dist/commands/connections.js.map +1 -1
  35. package/dist/commands/context.d.ts +22 -0
  36. package/dist/commands/context.js +149 -0
  37. package/dist/commands/context.js.map +1 -0
  38. package/dist/commands/data-extra.d.ts +14 -0
  39. package/dist/commands/data-extra.js +185 -0
  40. package/dist/commands/data-extra.js.map +1 -0
  41. package/dist/commands/data.d.ts +14 -10
  42. package/dist/commands/data.js +353 -192
  43. package/dist/commands/data.js.map +1 -1
  44. package/dist/commands/diagnose.d.ts +20 -0
  45. package/dist/commands/diagnose.js +143 -0
  46. package/dist/commands/diagnose.js.map +1 -0
  47. package/dist/commands/digest.d.ts +17 -0
  48. package/dist/commands/digest.js +121 -0
  49. package/dist/commands/digest.js.map +1 -0
  50. package/dist/commands/discover.d.ts +2 -0
  51. package/dist/commands/discover.js +25 -8
  52. package/dist/commands/discover.js.map +1 -1
  53. package/dist/commands/doc.d.ts +39 -0
  54. package/dist/commands/doc.js +130 -0
  55. package/dist/commands/doc.js.map +1 -0
  56. package/dist/commands/events.d.ts +26 -0
  57. package/dist/commands/events.js +69 -0
  58. package/dist/commands/events.js.map +1 -0
  59. package/dist/commands/explain.d.ts +21 -0
  60. package/dist/commands/explain.js +251 -0
  61. package/dist/commands/explain.js.map +1 -0
  62. package/dist/commands/facet.d.ts +29 -0
  63. package/dist/commands/facet.js +113 -0
  64. package/dist/commands/facet.js.map +1 -0
  65. package/dist/commands/graph.d.ts +18 -0
  66. package/dist/commands/graph.js +74 -0
  67. package/dist/commands/graph.js.map +1 -0
  68. package/dist/commands/home.d.ts +12 -0
  69. package/dist/commands/home.js +54 -0
  70. package/dist/commands/home.js.map +1 -0
  71. package/dist/commands/import.d.ts +38 -0
  72. package/dist/commands/import.js +440 -0
  73. package/dist/commands/import.js.map +1 -0
  74. package/dist/commands/init.d.ts +0 -2
  75. package/dist/commands/init.js +97 -118
  76. package/dist/commands/init.js.map +1 -1
  77. package/dist/commands/keys.d.ts +9 -0
  78. package/dist/commands/keys.js +78 -0
  79. package/dist/commands/keys.js.map +1 -0
  80. package/dist/commands/knowledge.d.ts +34 -18
  81. package/dist/commands/knowledge.js +355 -57
  82. package/dist/commands/knowledge.js.map +1 -1
  83. package/dist/commands/launch.d.ts +50 -0
  84. package/dist/commands/launch.js +790 -0
  85. package/dist/commands/launch.js.map +1 -0
  86. package/dist/commands/lens.d.ts +45 -0
  87. package/dist/commands/lens.js +386 -0
  88. package/dist/commands/lens.js.map +1 -0
  89. package/dist/commands/market.d.ts +209 -0
  90. package/dist/commands/market.js +1368 -0
  91. package/dist/commands/market.js.map +1 -0
  92. package/dist/commands/mcp.d.ts +42 -0
  93. package/dist/commands/mcp.js +184 -0
  94. package/dist/commands/mcp.js.map +1 -0
  95. package/dist/commands/observe.d.ts +9 -0
  96. package/dist/commands/observe.js +93 -0
  97. package/dist/commands/observe.js.map +1 -0
  98. package/dist/commands/open.d.ts +32 -0
  99. package/dist/commands/open.js +85 -0
  100. package/dist/commands/open.js.map +1 -0
  101. package/dist/commands/pod.d.ts +100 -0
  102. package/dist/commands/pod.js +359 -0
  103. package/dist/commands/pod.js.map +1 -0
  104. package/dist/commands/pods.d.ts +6 -0
  105. package/dist/commands/pods.js +160 -50
  106. package/dist/commands/pods.js.map +1 -1
  107. package/dist/commands/proposals-actions.d.ts +7 -0
  108. package/dist/commands/proposals-actions.js +77 -0
  109. package/dist/commands/proposals-actions.js.map +1 -0
  110. package/dist/commands/providers.d.ts +38 -0
  111. package/dist/commands/providers.js +170 -0
  112. package/dist/commands/providers.js.map +1 -0
  113. package/dist/commands/purge.d.ts +23 -0
  114. package/dist/commands/purge.js +148 -0
  115. package/dist/commands/purge.js.map +1 -0
  116. package/dist/commands/raycast.d.ts +15 -0
  117. package/dist/commands/raycast.js +153 -0
  118. package/dist/commands/raycast.js.map +1 -0
  119. package/dist/commands/sessions.d.ts +31 -0
  120. package/dist/commands/sessions.js +248 -0
  121. package/dist/commands/sessions.js.map +1 -0
  122. package/dist/commands/skill-manage.d.ts +21 -0
  123. package/dist/commands/skill-manage.js +195 -0
  124. package/dist/commands/skill-manage.js.map +1 -0
  125. package/dist/commands/skills-commands.d.ts +26 -0
  126. package/dist/commands/skills-commands.js +187 -0
  127. package/dist/commands/skills-commands.js.map +1 -0
  128. package/dist/commands/skills.d.ts +26 -0
  129. package/dist/commands/skills.js +196 -0
  130. package/dist/commands/skills.js.map +1 -0
  131. package/dist/commands/status.d.ts +3 -1
  132. package/dist/commands/status.js +231 -41
  133. package/dist/commands/status.js.map +1 -1
  134. package/dist/commands/statusline.d.ts +24 -0
  135. package/dist/commands/statusline.js +450 -0
  136. package/dist/commands/statusline.js.map +1 -0
  137. package/dist/commands/subscribe.d.ts +19 -0
  138. package/dist/commands/subscribe.js +91 -0
  139. package/dist/commands/subscribe.js.map +1 -0
  140. package/dist/commands/tools.d.ts +30 -0
  141. package/dist/commands/tools.js +315 -0
  142. package/dist/commands/tools.js.map +1 -0
  143. package/dist/commands/versions.d.ts +27 -0
  144. package/dist/commands/versions.js +277 -0
  145. package/dist/commands/versions.js.map +1 -0
  146. package/dist/commands/view.d.ts +57 -0
  147. package/dist/commands/view.js +195 -0
  148. package/dist/commands/view.js.map +1 -0
  149. package/dist/commands/whoami.d.ts +21 -0
  150. package/dist/commands/whoami.js +129 -0
  151. package/dist/commands/whoami.js.map +1 -0
  152. package/dist/index.d.ts +3 -4
  153. package/dist/index.js +1524 -202
  154. package/dist/index.js.map +1 -1
  155. package/dist/lib/agent-templates.d.ts +25 -0
  156. package/dist/lib/agent-templates.js +148 -0
  157. package/dist/lib/agent-templates.js.map +1 -0
  158. package/dist/lib/agents-config.d.ts +4 -1
  159. package/dist/lib/agents-config.js +19 -2
  160. package/dist/lib/agents-config.js.map +1 -1
  161. package/dist/lib/approval-poll.d.ts +42 -0
  162. package/dist/lib/approval-poll.js +52 -0
  163. package/dist/lib/approval-poll.js.map +1 -0
  164. package/dist/lib/auth.d.ts +29 -6
  165. package/dist/lib/auth.js +105 -94
  166. package/dist/lib/auth.js.map +1 -1
  167. package/dist/lib/bundle-version.d.ts +11 -0
  168. package/dist/lib/bundle-version.js +33 -0
  169. package/dist/lib/bundle-version.js.map +1 -0
  170. package/dist/lib/capture-lane.d.ts +67 -0
  171. package/dist/lib/capture-lane.js +136 -0
  172. package/dist/lib/capture-lane.js.map +1 -0
  173. package/dist/lib/cp-packages.d.ts +121 -0
  174. package/dist/lib/cp-packages.js +218 -0
  175. package/dist/lib/cp-packages.js.map +1 -0
  176. package/dist/lib/describe-lens.d.ts +46 -0
  177. package/dist/lib/describe-lens.js +33 -0
  178. package/dist/lib/describe-lens.js.map +1 -0
  179. package/dist/lib/hub-client.d.ts +84 -1
  180. package/dist/lib/hub-client.js +502 -22
  181. package/dist/lib/hub-client.js.map +1 -1
  182. package/dist/lib/installed.d.ts +51 -0
  183. package/dist/lib/installed.js +70 -0
  184. package/dist/lib/installed.js.map +1 -0
  185. package/dist/lib/next-steps.d.ts +61 -0
  186. package/dist/lib/next-steps.js +146 -0
  187. package/dist/lib/next-steps.js.map +1 -0
  188. package/dist/lib/pod.d.ts +104 -19
  189. package/dist/lib/pod.js +245 -57
  190. package/dist/lib/pod.js.map +1 -1
  191. package/dist/lib/project-ref.d.ts +95 -0
  192. package/dist/lib/project-ref.js +144 -0
  193. package/dist/lib/project-ref.js.map +1 -0
  194. package/dist/lib/project.d.ts +35 -0
  195. package/dist/lib/project.js +39 -0
  196. package/dist/lib/project.js.map +1 -0
  197. package/dist/lib/session-lens.d.ts +33 -0
  198. package/dist/lib/session-lens.js +76 -0
  199. package/dist/lib/session-lens.js.map +1 -0
  200. package/dist/lib/skill-import.d.ts +40 -0
  201. package/dist/lib/skill-import.js +203 -0
  202. package/dist/lib/skill-import.js.map +1 -0
  203. package/dist/lib/skills-installer.d.ts +16 -5
  204. package/dist/lib/skills-installer.js +132 -18
  205. package/dist/lib/skills-installer.js.map +1 -1
  206. package/dist/lib/suite.d.ts +28 -0
  207. package/dist/lib/suite.js +29 -0
  208. package/dist/lib/suite.js.map +1 -0
  209. package/dist/lib/superwhisper-import.d.ts +40 -0
  210. package/dist/lib/superwhisper-import.js +389 -0
  211. package/dist/lib/superwhisper-import.js.map +1 -0
  212. package/dist/lib/targets.d.ts +66 -2
  213. package/dist/lib/targets.js +914 -132
  214. package/dist/lib/targets.js.map +1 -1
  215. package/dist/lib/unwrapList.d.ts +1 -0
  216. package/dist/lib/unwrapList.js +20 -0
  217. package/dist/lib/unwrapList.js.map +1 -0
  218. package/dist/utils/logger.d.ts +2 -0
  219. package/dist/utils/logger.js +10 -2
  220. package/dist/utils/logger.js.map +1 -1
  221. package/package.json +10 -4
  222. package/skills/agent-os/SKILL.md +212 -0
  223. package/skills/manifest.json +5 -0
  224. package/skills/onboard/SKILL.md +186 -0
  225. package/skills/synap/SKILL.md +1524 -104
  226. package/skills/synap/_order.txt +37 -0
  227. package/skills/synap/_skill.yaml +24 -0
  228. package/skills/synap/authentication.md +10 -0
  229. package/skills/synap/automations.md +264 -0
  230. package/skills/synap/capabilities.md +107 -0
  231. package/skills/synap/capture.md +10 -0
  232. package/skills/synap/cli-operations.md +107 -0
  233. package/skills/synap/common-mistakes.md +12 -0
  234. package/skills/synap/creative-loop.md +43 -0
  235. package/skills/synap/crm.md +117 -0
  236. package/skills/synap/diagnostics.md +35 -0
  237. package/skills/synap/digesting-a-channel.md +38 -0
  238. package/skills/synap/document-embeds.md +123 -0
  239. package/skills/synap/escalation-ladder.md +71 -0
  240. package/skills/synap/focus-sessions.md +47 -0
  241. package/skills/synap/governance-rules.md +64 -0
  242. package/skills/synap/governance.md +6 -6
  243. package/skills/synap/graph-gardening.md +21 -0
  244. package/skills/synap/inline-patterns.md +38 -0
  245. package/skills/synap/lenses.md +38 -0
  246. package/skills/synap/linking-principle.md +39 -0
  247. package/skills/synap/linking.md +1 -0
  248. package/skills/synap/mental-model.md +45 -0
  249. package/skills/synap/more.md +7 -0
  250. package/skills/synap/multi-entity-capture.md +10 -0
  251. package/skills/synap/next-action.md +37 -0
  252. package/skills/synap/north-star.md +17 -0
  253. package/skills/synap/operating-mode-rules.md +45 -0
  254. package/skills/synap/operating-mode.md +9 -0
  255. package/skills/synap/quick-reference.md +67 -0
  256. package/skills/synap/reading.md +43 -0
  257. package/skills/synap/recap.md +21 -0
  258. package/skills/synap/reflexes.md +46 -0
  259. package/skills/synap/response-style.md +21 -0
  260. package/skills/synap/scope.md +13 -0
  261. package/skills/synap/showing.md +17 -0
  262. package/skills/synap/user-model.md +25 -0
  263. package/skills/synap/viewframe-cells.md +290 -0
  264. package/skills/synap/work-flow.md +113 -0
  265. package/skills/synap/worked-examples.md +53 -0
  266. package/skills/synap/workspace-design.md +45 -0
  267. package/skills/synap/workspace-edges.md +49 -0
  268. package/skills/synap/writes.md +156 -0
  269. package/skills/synap-schema/SKILL.md +97 -6
  270. package/skills/synap-schema/_order.txt +13 -0
  271. package/skills/synap-schema/_skill.yaml +22 -0
  272. package/skills/synap-schema/before-you-touch.md +14 -0
  273. package/skills/synap-schema/common-mistakes.md +9 -0
  274. package/skills/synap-schema/creating-profiles.md +17 -0
  275. package/skills/synap-schema/discover-profiles.md +24 -0
  276. package/skills/synap-schema/entity-scope.md +8 -0
  277. package/skills/synap-schema/extend-vs-create.md +31 -0
  278. package/skills/synap-schema/more.md +6 -0
  279. package/skills/synap-schema/overlay-properties.md +22 -0
  280. package/skills/synap-schema/profiles.md +3 -0
  281. package/skills/synap-schema/property-defs.md +29 -0
  282. package/skills/synap-schema/read-before-write.md +50 -0
  283. package/skills/synap-schema/relations.md +18 -0
  284. package/skills/synap-schema/worked-example.md +30 -0
  285. package/skills/synap-ui/SKILL.md +38 -11
  286. package/skills/synap-ui/_order.txt +13 -0
  287. package/skills/synap-ui/_skill.yaml +23 -0
  288. package/skills/synap-ui/ai-companion.md +27 -0
  289. package/skills/synap-ui/arranging-bento.md +8 -0
  290. package/skills/synap-ui/before-you-build.md +20 -0
  291. package/skills/synap-ui/bento-layouts.md +35 -0
  292. package/skills/synap-ui/common-mistakes.md +10 -0
  293. package/skills/synap-ui/creating-views.md +26 -0
  294. package/skills/synap-ui/four-builds.md +24 -0
  295. package/skills/synap-ui/more.md +7 -0
  296. package/skills/synap-ui/proposing-vs-extending.md +11 -0
  297. package/skills/synap-ui/view-list.md +24 -0
  298. package/skills/synap-ui/views.md +3 -0
  299. package/skills/synap-ui/worked-example.md +18 -0
  300. package/skills/synap-ui/workspaces.md +47 -0
  301. package/templates/automations/bookmark-enrichment.automation.json +44 -0
  302. package/templates/automations/mail-feed.automation.json +86 -0
  303. package/templates/automations/url-bookmark-capture.automation.json +70 -0
  304. package/templates/skills/README.md +21 -0
  305. package/templates/skills/authoring-automations/SKILL.md +95 -0
  306. package/templates/skills/authoring-automations/reference/node-matrix.md +49 -0
  307. package/templates/skills/authoring-capabilities/SKILL.md +93 -0
  308. package/templates/skills/authoring-capabilities/reference/template-anatomy.md +70 -0
  309. package/templates/skills/authoring-playbooks/SKILL.md +102 -0
  310. package/templates/skills/authoring-playbooks/reference/stages-contract.md +35 -0
  311. package/templates/skills/authoring-playbooks/reference/worked-example.md +64 -0
  312. package/templates/skills/bookmark-enrichment/SKILL.md +40 -0
  313. package/templates/skills/coordinating-agents/SKILL.md +32 -0
  314. package/templates/skills/coordinating-branches/SKILL.md +27 -0
  315. package/templates/skills/mapping-the-server/SKILL.md +54 -0
  316. package/templates/skills/reading-client-progress/SKILL.md +65 -0
  317. package/templates/skills/routing-to-channels/SKILL.md +54 -0
  318. package/templates/skills/stellar-scf-grants-advisor/ASSOCIATE-CHEATSHEET.md +133 -0
  319. package/templates/skills/stellar-scf-grants-advisor/ASSOCIATE-ONBOARDING-GUIDE.md +761 -0
  320. package/templates/skills/stellar-scf-grants-advisor/CLIENT-FAQ.md +181 -0
  321. package/templates/skills/stellar-scf-grants-advisor/PARTNER-HANDOVER.md +130 -0
  322. package/templates/skills/stellar-scf-grants-advisor/SKILL.md +102 -0
  323. package/templates/skills/stellar-scf-grants-advisor/examples/README.md +43 -0
  324. package/templates/skills/stellar-scf-grants-advisor/examples/chat-summary-templates.md +158 -0
  325. package/templates/skills/stellar-scf-grants-advisor/examples/corpus-comparable-rows.md +136 -0
  326. package/templates/skills/stellar-scf-grants-advisor/examples/q10-team-rewrites.md +109 -0
  327. package/templates/skills/stellar-scf-grants-advisor/examples/q2-description-rewrites.md +88 -0
  328. package/templates/skills/stellar-scf-grants-advisor/examples/q6-integration-rewrites.md +107 -0
  329. package/templates/skills/stellar-scf-grants-advisor/examples/tranche-structures.md +176 -0
  330. package/templates/skills/stellar-scf-grants-advisor/reference/01-methodology.md +96 -0
  331. package/templates/skills/stellar-scf-grants-advisor/reference/02-scoring-framework.md +199 -0
  332. package/templates/skills/stellar-scf-grants-advisor/reference/03-rejection-patterns.md +241 -0
  333. package/templates/skills/stellar-scf-grants-advisor/reference/04-winner-patterns.md +120 -0
  334. package/templates/skills/stellar-scf-grants-advisor/reference/05-comeback-patterns.md +92 -0
  335. package/templates/skills/stellar-scf-grants-advisor/reference/06-budget-benchmarks.md +125 -0
  336. package/templates/skills/stellar-scf-grants-advisor/reference/07-integration-list.md +147 -0
  337. package/templates/skills/stellar-scf-grants-advisor/reference/08-abstract-fields.md +258 -0
  338. package/templates/skills/stellar-scf-grants-advisor/reference/09-build-fields.md +259 -0
  339. package/templates/skills/stellar-scf-grants-advisor/reference/10-corpus-winners.md +135 -0
  340. package/templates/skills/stellar-scf-grants-advisor/reference/11-corpus-rejects.md +267 -0
  341. package/templates/skills/stellar-scf-grants-advisor/reference/12-client-communication.md +131 -0
  342. package/templates/skills/stellar-scf-grants-advisor/reference/13-excel-output-spec.md +199 -0
  343. package/templates/skills/stellar-scf-grants-advisor/reference/14-worked-examples.md +160 -0
  344. package/templates/skills/stellar-scf-grants-advisor/templates/abstract-review-builder.py +253 -0
  345. package/templates/skills/stellar-scf-grants-advisor/templates/build-review-builder.py +213 -0
  346. package/dist/commands/finish.d.ts +0 -25
  347. package/dist/commands/finish.js +0 -274
  348. package/dist/commands/finish.js.map +0 -1
  349. package/dist/commands/openclaw.d.ts +0 -40
  350. package/dist/commands/openclaw.js +0 -1148
  351. package/dist/commands/openclaw.js.map +0 -1
  352. package/dist/commands/security-audit.d.ts +0 -12
  353. package/dist/commands/security-audit.js +0 -100
  354. package/dist/commands/security-audit.js.map +0 -1
  355. package/dist/lib/hardening.d.ts +0 -18
  356. package/dist/lib/hardening.js +0 -214
  357. package/dist/lib/hardening.js.map +0 -1
  358. package/dist/lib/templates.d.ts +0 -11
  359. package/dist/lib/templates.js +0 -13
  360. package/dist/lib/templates.js.map +0 -1
  361. package/dist/templates/agent-os.json +0 -3090
  362. package/node_modules/@synap/hub-rest-client/dist/index.cjs +0 -881
  363. package/node_modules/@synap/hub-rest-client/dist/index.d.cts +0 -907
  364. package/node_modules/@synap/hub-rest-client/dist/index.d.ts +0 -907
  365. package/node_modules/@synap/hub-rest-client/dist/index.js +0 -851
  366. package/node_modules/@synap/hub-rest-client/package.json +0 -45
  367. package/node_modules/@synap/hub-rest-client/src/client.ts +0 -1143
  368. package/node_modules/@synap/hub-rest-client/src/errors.ts +0 -30
  369. package/node_modules/@synap/hub-rest-client/src/index.ts +0 -111
  370. package/node_modules/@synap/hub-rest-client/src/setup.ts +0 -77
  371. package/node_modules/@synap/hub-rest-client/src/types.ts +0 -639
@@ -1,75 +1,128 @@
1
1
  import chalk from "chalk";
2
2
  import { log } from "../utils/logger.js";
3
- import { resolveHubConfig, resolveUserId, hubGet, hubPost, hubPatch } from "../lib/hub-client.js";
4
- // ─── orient ───────────────────────────────────────────────────────────────────
3
+ import { resolveHubConfig, resolveUserId, hubGet, hubPost, hubPatch, readActiveSessionId, renderHubError } from "../lib/hub-client.js";
4
+ import { unwrapList } from "../lib/unwrapList.js";
5
+ import { reportWrite } from "../lib/capture-lane.js";
6
+ import { renderNextSteps, FLOW } from "../lib/next-steps.js";
7
+ export const parseLimit = (s, def) => s ? parseInt(s, 10) : def;
8
+ export function formatHit(hit) {
9
+ const doc = (hit.document ?? hit);
10
+ return {
11
+ title: String(doc.title ?? doc.id ?? hit.id ?? ""),
12
+ // `type` is the entity's profile (entities carry `type`; search hits carry
13
+ // `entityType`/`collection`). Include `doc.type` or the badge renders blank.
14
+ type: String(doc.entityType ?? doc.profileSlug ?? doc.type ?? hit.collection ?? ""),
15
+ id: String(doc.id ?? hit.id ?? "").slice(0, 8),
16
+ };
17
+ }
5
18
  export async function orient(opts) {
6
19
  try {
7
20
  const cfg = await resolveHubConfig(opts);
8
- const [workspacesRes, meRes] = await Promise.all([
9
- hubGet("/workspaces", {}, cfg),
10
- hubGet("/users/me", {}, cfg),
11
- ]);
12
- const workspaces = workspacesRes.workspaces ?? workspacesRes;
13
- const me = meRes;
14
- const userId = String(me.id ?? cfg.userId);
15
- const wsList = Array.isArray(workspaces) ? workspaces : [];
16
- // For each workspace, fetch profiles + entity count in parallel
17
- const wsDetails = await Promise.all(wsList.map(async (ws) => {
18
- const wsId = String(ws.id ?? "");
19
- const [profilesRes, entitiesRes] = await Promise.allSettled([
20
- hubGet("/profiles", { userId, workspaceId: wsId }, cfg),
21
- hubGet("/entities", { userId, workspaceId: wsId, limit: 1 }, cfg),
22
- ]);
23
- const profiles = profilesRes.status === "fulfilled"
24
- ? (profilesRes.value.profiles ?? profilesRes.value)
25
- : [];
26
- const profileList = Array.isArray(profiles) ? profiles : [];
27
- const entityTotal = entitiesRes.status === "fulfilled"
28
- ? Number(entitiesRes.value.total ?? 0)
29
- : 0;
30
- return { ws, profileList, entityTotal };
31
- }));
21
+ // ONE canonical shape: the backend `discover()` service (via GET /orient)
22
+ // assembles the lens map — workspaces + projects + profile sample +
23
+ // disclosure (onboarding→goal, entity counts, per-ws profiles under full).
24
+ // The CLI only RENDERS it; `--details` maps to detail:'full'. No client-side
25
+ // assembly, no N+1 fanout — the server does the shaping.
26
+ const res = (await hubGet("/orient", opts.details ? { detail: "full" } : {}, cfg));
27
+ const userId = String(res.me?.userId ?? cfg.userId);
28
+ const projList = res.projects ?? [];
29
+ const wsList = res.workspaces ?? [];
32
30
  if (opts.json) {
33
- console.log(JSON.stringify({ userId, podUrl: cfg.podUrl, workspaceId: cfg.workspaceId, workspaces: wsDetails, me }, null, 2));
31
+ console.log(JSON.stringify({ ...res, podUrl: cfg.podUrl, workspaceId: cfg.workspaceId, nextSteps: FLOW.afterOrient() }, null, 2));
34
32
  return;
35
33
  }
34
+ // Render content rows with console.log (default terminal fg) rather than
35
+ // log.info — log.info wraps every line in chalk.blue, and that blue fg
36
+ // persists through inner bold/dim (they reset weight, not color), flattening
37
+ // the hierarchy. We keep only targeted accents: bold=name, dim=metadata,
38
+ // green ▶ = active.
36
39
  log.heading("Pod Orientation");
37
- log.info(`User : ${String(me.email ?? me.name ?? userId)}`);
38
- log.info(`Pod URL : ${cfg.podUrl}`);
39
- if (cfg.workspaceId)
40
- log.info(`Workspace: ${cfg.workspaceId} ${chalk.dim("(active — use 'synap use <id>' to change)")}`);
40
+ console.log(` User : ${userId}`);
41
+ console.log(` Pod URL : ${cfg.podUrl}`);
42
+ // The per-Claude-session lens (project + workspace + session) — "where am I".
43
+ // Full ids so they can be pasted into 'synap project use' / 'synap use'.
44
+ const { getClaudeSessionId, resolveActiveLens } = await import("../lib/session-lens.js");
45
+ let activeProjectId;
46
+ if (getClaudeSessionId()) {
47
+ const lens = resolveActiveLens();
48
+ activeProjectId = lens?.projectId ?? undefined;
49
+ const proj = lens?.projectId ?? chalk.dim("none");
50
+ const ws = lens?.workspaceId ?? cfg.workspaceId ?? chalk.dim("pod-wide");
51
+ const sess = lens?.focusSessionId ?? chalk.dim("none");
52
+ console.log(` Lens : ${chalk.dim("project")} ${proj} · ${chalk.dim("ws")} ${ws} · ${chalk.dim("session")} ${sess}`);
53
+ }
54
+ else if (cfg.workspaceId) {
55
+ console.log(` Workspace: ${cfg.workspaceId} ${chalk.dim("(active — use 'synap use <id>' to change)")}`);
56
+ }
57
+ // ── Projects (companies / initiatives) — the primary lens ───────────────
58
+ log.blank();
59
+ log.heading("Projects");
60
+ if (projList.length === 0) {
61
+ log.dim("No projects yet — a project is a company/initiative that ties workspaces together.");
62
+ }
63
+ else {
64
+ for (const p of projList) {
65
+ const isActive = p.id === activeProjectId;
66
+ const marker = isActive ? chalk.green("▶ ") : " ";
67
+ const name = chalk.bold(String(p.name ?? p.id));
68
+ const id = chalk.dim(String(p.id ?? ""));
69
+ const status = p.status ? chalk.dim(String(p.status)) : "";
70
+ const homePart = p.homeWorkspace ? chalk.dim(`· ${p.homeWorkspace}`) : "";
71
+ console.log(` ${marker}${name} ${id} ${status}${homePart ? " " + homePart : ""}`);
72
+ }
73
+ }
74
+ // ── Workspaces (operational domains) ────────────────────────────────────
41
75
  log.blank();
42
76
  log.heading("Workspaces");
43
- if (wsDetails.length === 0) {
77
+ if (wsList.length === 0) {
44
78
  log.dim("No workspaces found.");
45
79
  }
46
- else {
47
- for (const { ws, profileList, entityTotal } of wsDetails) {
80
+ else if (opts.details) {
81
+ for (const ws of wsList) {
48
82
  const isActive = ws.id === cfg.workspaceId;
49
83
  const marker = isActive ? chalk.green("▶ ") : " ";
50
84
  const name = chalk.bold(String(ws.name ?? ws.id));
51
- const id = chalk.dim(String(ws.id ?? "").slice(0, 8) + "…");
52
- const count = entityTotal > 0 ? chalk.dim(`${entityTotal} entities`) : chalk.dim("empty");
53
- log.info(`${marker}${name} ${id} ${count}`);
85
+ const id = chalk.dim(String(ws.id ?? ""));
86
+ const count = ws.entityCount > 0
87
+ ? chalk.dim(`${ws.entityCount} entities`)
88
+ : chalk.dim("empty");
89
+ console.log(` ${marker}${name} ${id} ${count}`);
54
90
  if (ws.description)
55
91
  log.dim(` ${String(ws.description)}`);
56
- if (profileList.length > 0) {
57
- const slugs = profileList
58
- .map((p) => String(p.slug ?? p.name ?? ""))
92
+ if (ws.onboarding?.goal && ws.entityCount === 0) {
93
+ log.dim(` ${chalk.cyan("→ onboard:")} ${String(ws.onboarding.goal)}`);
94
+ }
95
+ const profiles = ws.profiles ?? [];
96
+ if (profiles.length > 0) {
97
+ const slugs = profiles
98
+ .map((p) => p.slug)
59
99
  .filter(Boolean)
60
100
  .slice(0, 8)
61
101
  .join(chalk.dim(", "));
62
- log.dim(` profiles: ${slugs}${profileList.length > 8 ? chalk.dim(" …") : ""}`);
102
+ log.dim(` profiles: ${slugs}${profiles.length > 8 ? chalk.dim(" …") : ""}`);
63
103
  }
64
104
  }
65
- if (!cfg.workspaceId) {
66
- log.blank();
67
- log.dim("Tip: run 'synap use <workspaceId>' to set a default workspace.");
105
+ }
106
+ else {
107
+ // Light: name + id + domain (operational-domain label = workspaceSubtype
108
+ // falling back to workspaceType, resolved server-side).
109
+ for (const ws of wsList) {
110
+ const isActive = ws.id === cfg.workspaceId;
111
+ const marker = isActive ? chalk.green("▶ ") : " ";
112
+ const name = chalk.bold(String(ws.name ?? ws.id));
113
+ const id = chalk.dim(String(ws.id ?? ""));
114
+ const domain = ws.domain ? chalk.dim(String(ws.domain)) : "";
115
+ console.log(` ${marker}${name} ${id} ${domain}`);
68
116
  }
117
+ log.blank();
118
+ log.dim("Workspaces are operational domains; run 'synap orient --details' for profiles + entity counts.");
69
119
  }
120
+ log.blank();
121
+ log.dim("Lenses compose — a project spans workspaces; a workspace spans projects. Omit them to stay pod-wide.");
122
+ renderNextSteps(FLOW.afterOrient());
70
123
  }
71
124
  catch (e) {
72
- console.error(chalk.red("Error: " + e.message));
125
+ renderHubError(e);
73
126
  process.exit(1);
74
127
  }
75
128
  }
@@ -80,8 +133,7 @@ export async function useWorkspace(workspaceId, opts) {
80
133
  const userId = await resolveUserId(cfg);
81
134
  // Verify the workspace exists and is accessible
82
135
  const res = await hubGet("/workspaces", {}, cfg);
83
- const workspaces = res.workspaces ?? res;
84
- const list = Array.isArray(workspaces) ? workspaces : [];
136
+ const list = unwrapList(res, ["workspaces"]);
85
137
  const match = list.find((w) => w.id === workspaceId || String(w.name).toLowerCase() === workspaceId.toLowerCase());
86
138
  if (!match) {
87
139
  const names = list.map((w) => `${String(w.name ?? w.id)}`).join(", ");
@@ -90,17 +142,26 @@ export async function useWorkspace(workspaceId, opts) {
90
142
  log.dim(`Available: ${names}`);
91
143
  process.exit(1);
92
144
  }
93
- const { setActiveWorkspaceId } = await import("../lib/pod.js");
94
- setActiveWorkspaceId(String(match.id));
145
+ // In a Claude Code session, scope to THIS session's lens (so concurrent
146
+ // sessions can be on different workspaces). Otherwise set the global default.
147
+ const { getClaudeSessionId, writeLens } = await import("../lib/session-lens.js");
148
+ const claudeSession = getClaudeSessionId();
149
+ if (claudeSession) {
150
+ writeLens(claudeSession, { workspaceId: String(match.id) });
151
+ }
152
+ else {
153
+ const { setActiveWorkspaceId } = await import("../lib/pod.js");
154
+ setActiveWorkspaceId(String(match.id));
155
+ }
95
156
  if (opts.json) {
96
- console.log(JSON.stringify({ workspaceId: match.id, name: match.name }, null, 2));
157
+ console.log(JSON.stringify({ workspaceId: match.id, name: match.name, scope: claudeSession ? "session" : "global" }, null, 2));
97
158
  return;
98
159
  }
99
- log.success(`Now in workspace: ${chalk.bold(String(match.name ?? match.id))} ${chalk.dim(String(match.id ?? ""))}`);
100
- void userId; // consumed by resolveUserId side-effect
160
+ const scopeNote = claudeSession ? chalk.dim(" (this session)") : "";
161
+ log.success(`Now in workspace: ${chalk.bold(String(match.name ?? match.id))} ${chalk.dim(String(match.id ?? ""))}${scopeNote}`);
101
162
  }
102
163
  catch (e) {
103
- console.error(chalk.red("Error: " + e.message));
164
+ renderHubError(e);
104
165
  process.exit(1);
105
166
  }
106
167
  }
@@ -109,8 +170,7 @@ export async function listWorkspaces(opts) {
109
170
  try {
110
171
  const cfg = await resolveHubConfig(opts);
111
172
  const res = await hubGet("/workspaces", {}, cfg);
112
- const workspaces = res.workspaces ?? res;
113
- const list = Array.isArray(workspaces) ? workspaces : [];
173
+ const list = unwrapList(res, ["workspaces"]);
114
174
  if (opts.json) {
115
175
  console.log(JSON.stringify({ workspaces: list }, null, 2));
116
176
  return;
@@ -125,7 +185,7 @@ export async function listWorkspaces(opts) {
125
185
  }
126
186
  }
127
187
  catch (e) {
128
- console.error(chalk.red("Error: " + e.message));
188
+ renderHubError(e);
129
189
  process.exit(1);
130
190
  }
131
191
  }
@@ -134,21 +194,53 @@ export async function listEntities(opts) {
134
194
  try {
135
195
  const cfg = await resolveHubConfig(opts);
136
196
  const userId = await resolveUserId(cfg);
137
- const limit = parseInt(opts.limit ?? "20", 10);
197
+ const limit = parseLimit(opts.limit, 20);
198
+ const workspaceId = opts.workspace ?? cfg.workspaceId;
199
+ // --role needs a workspace to resolve facet scope (see browseEntities);
200
+ // without one the filter is silently dropped and "no entities" would lie.
201
+ if (opts.role && !workspaceId) {
202
+ throw new Error("--role needs an active workspace to resolve the role. Run 'synap use <workspace>' or pass --workspace <id>.");
203
+ }
138
204
  const res = await hubGet("/entities", {
139
205
  userId,
140
- workspaceId: opts.workspace ?? cfg.workspaceId,
206
+ workspaceId,
141
207
  profileSlug: opts.profile,
208
+ // Browse-by-role: the list route resolves facet scope from the
209
+ // `workspaceId` QUERY PARAM (already threaded above), not the
210
+ // X-Workspace-Id header — verified against the live pod.
211
+ facetSlug: opts.role,
142
212
  limit,
143
213
  }, cfg);
144
- const entities = res.entities ?? res;
145
- const list = Array.isArray(entities) ? entities : [];
214
+ const list = unwrapList(res, ["entities"]);
146
215
  if (opts.json) {
147
- console.log(JSON.stringify({ entities: list }, null, 2));
216
+ console.log(JSON.stringify({ entities: list, count: list.length, limit, truncated: list.length === limit }, null, 2));
148
217
  return;
149
218
  }
150
219
  if (list.length === 0) {
151
220
  log.dim("No entities found.");
221
+ // Whiteboards, kanbans, calendars… are VIEWS, not entity profiles.
222
+ // If the requested profile is actually a view type, point there.
223
+ if (opts.profile) {
224
+ try {
225
+ const viewsRes = await hubGet("/views", {
226
+ userId,
227
+ workspaceId: opts.workspace ?? cfg.workspaceId,
228
+ type: opts.profile,
229
+ }, cfg);
230
+ const views = unwrapList(viewsRes, ["views"]);
231
+ if (views.length > 0) {
232
+ log.blank();
233
+ log.info(`'${opts.profile}' is a ${chalk.bold("view type")}, not an entity profile — found ${views.length} view${views.length !== 1 ? "s" : ""}.`);
234
+ log.dim(` Run: synap list views --type ${opts.profile}`);
235
+ return;
236
+ }
237
+ }
238
+ catch {
239
+ // best-effort hint only — never fail the empty-result path
240
+ }
241
+ log.dim(` Profiles in this workspace: synap list profiles`);
242
+ log.dim(` Views (whiteboard, kanban, …): synap list views`);
243
+ }
152
244
  return;
153
245
  }
154
246
  for (const entity of list) {
@@ -158,9 +250,12 @@ export async function listEntities(opts) {
158
250
  const id = String(e.id ?? "").slice(0, 8);
159
251
  log.info(`${name} ${chalk.dim(`(${profile})`)} — ${chalk.dim(id)}`);
160
252
  }
253
+ if (list.length === limit) {
254
+ log.dim(`${list.length} shown (hit --limit ${limit}; more may exist — raise --limit or filter with --profile)`);
255
+ }
161
256
  }
162
257
  catch (e) {
163
- console.error(chalk.red("Error: " + e.message));
258
+ renderHubError(e);
164
259
  process.exit(1);
165
260
  }
166
261
  }
@@ -188,130 +283,178 @@ export async function getEntity(id, opts) {
188
283
  }
189
284
  }
190
285
  catch (e) {
191
- console.error(chalk.red("Error: " + e.message));
286
+ renderHubError(e);
192
287
  process.exit(1);
193
288
  }
194
289
  }
195
- // ─── searchData ───────────────────────────────────────────────────────────────
196
- export async function searchData(query, opts) {
290
+ /** Render one item according to its substrate. */
291
+ function formatAskItem(substrate, item) {
292
+ if (substrate === "procedural") {
293
+ // knowledge_keys rows: addressed by `key`, body in `value`.
294
+ const key = (item.key ?? item.slug ?? item.namespace);
295
+ const value = (item.value ?? "");
296
+ const snippet = value
297
+ ? " — " + (value.length > 80 ? value.slice(0, 77) + "…" : value).replace(/\s+/g, " ")
298
+ : "";
299
+ return `${key ?? "(untitled)"}${chalk.dim(snippet)}`;
300
+ }
301
+ if (substrate === "episodic") {
302
+ // knowledge_facts rows: text is in `fact` (content/text are wire fallbacks).
303
+ const content = (item.fact ?? item.content ?? item.text ?? "");
304
+ return content.length > 120 ? content.slice(0, 117) + "…" : content || "(empty)";
305
+ }
306
+ // semantic — an entity. Surface a body snippet (not just a pointer): the
307
+ // entity carries its content in `preview` or a description-ish property, so a
308
+ // "how do I…" answer shows the gist inline instead of only an entity title.
309
+ const { title, type, id } = formatHit(item);
310
+ const props = (item.properties ?? {});
311
+ const desc = String(item.preview ??
312
+ props.description ??
313
+ props.recipeDescription ??
314
+ props.summary ??
315
+ props.body ??
316
+ props.content ??
317
+ "");
318
+ const snippet = desc
319
+ ? chalk.dim(" — " +
320
+ (desc.length > 90 ? desc.slice(0, 87) + "…" : desc).replace(/\s+/g, " "))
321
+ : "";
322
+ return `${title} ${chalk.dim(`[${type}]`)} ${chalk.dim(id)}${snippet}`;
323
+ }
324
+ /**
325
+ * Render the A/B ranker diagnostic: baseline vs Horizon top-N side-by-side,
326
+ * then the diff. Comparison is by RANK POSITION (not score magnitude) — the two
327
+ * strategies score on different scales, so only the ordering is comparable.
328
+ */
329
+ function renderComparison(query, comparison) {
330
+ if (!comparison) {
331
+ log.dim(`"${query}" → no ranker comparison returned (semantic pool empty?).`);
332
+ return;
333
+ }
334
+ const { baseline, horizon, diff } = comparison;
335
+ const n = Math.max(baseline.length, horizon.length);
336
+ log.dim(`"${query}" → A/B ranker comparison (by RANK, not score magnitude)`);
337
+ log.blank();
338
+ // Side-by-side: rank · title for each column.
339
+ const trunc = (s, w) => (s.length > w ? s.slice(0, w - 1) + "…" : s).padEnd(w, " ");
340
+ const COL = 38;
341
+ log.info(" " + chalk.bold("BASELINE".padEnd(COL + 5)) + chalk.bold("HORIZON"));
342
+ for (let i = 0; i < n; i++) {
343
+ const b = baseline[i];
344
+ const h = horizon[i];
345
+ const bCell = b ? `${chalk.dim(`#${b.rank}`)} ${trunc(b.title, COL)}` : "".padEnd(COL + 4);
346
+ const hCell = h ? `${chalk.dim(`#${h.rank}`)} ${trunc(h.title, COL)}` : "";
347
+ log.info(" " + bCell + " " + hCell);
348
+ }
349
+ log.blank();
350
+ // Diff — overlap@N + items that moved rank between the two strategies.
351
+ const topN = Math.max(baseline.length, horizon.length);
352
+ log.info(chalk.bold("DIFF") +
353
+ chalk.dim(` · overlap@${topN}: ${diff.overlapAtN}/${topN} shared ids`));
354
+ if (diff.moved.length === 0) {
355
+ log.dim(" No shared item changed rank between the two rankers.");
356
+ }
357
+ else {
358
+ for (const m of diff.moved) {
359
+ const dir = m.horizonRank < m.baselineRank ? chalk.green("↑") : chalk.red("↓");
360
+ log.info(` ${dir} ${m.title} ${chalk.dim(`(baseline #${m.baselineRank} → horizon #${m.horizonRank})`)}`);
361
+ }
362
+ }
363
+ log.blank();
364
+ }
365
+ export async function askKnowledge(query, opts) {
197
366
  try {
198
367
  const cfg = await resolveHubConfig(opts);
199
- const userId = await resolveUserId(cfg);
200
- const limit = parseInt(opts.limit ?? "20", 10);
201
- let collections;
202
- if (opts.type === "entity")
203
- collections = "entities";
204
- else if (opts.type === "doc")
205
- collections = "documents";
206
- const res = await hubGet("/search", {
207
- userId,
368
+ const limit = parseLimit(opts.limit, 10);
369
+ const workspaceId = opts.workspace ?? cfg.workspaceId;
370
+ const sessionId = opts.session ? readActiveSessionId() : undefined;
371
+ const res = (await hubPost("/knowledge/ask", {
208
372
  query,
209
- workspaceId: opts.workspace ?? cfg.workspaceId,
373
+ ...(workspaceId ? { workspaceId } : {}),
374
+ ...(sessionId ? { sessionId } : {}),
210
375
  limit,
211
- collections,
212
- }, cfg);
376
+ ...(opts.compare ? { compare: true } : {}),
377
+ }, cfg));
213
378
  if (opts.json) {
214
379
  console.log(JSON.stringify(res, null, 2));
215
380
  return;
216
381
  }
217
- const data = res;
218
- // hits may live at .hits, .results, or top-level array
219
- const rawHits = data.hits ??
220
- data.results ??
221
- (Array.isArray(res) ? res : []);
222
- if (rawHits.length === 0) {
223
- const workspaceId = opts.workspace ?? cfg.workspaceId;
224
- log.dim(`No results for "${query}".`);
225
- if (workspaceId) {
226
- // Show what IS in the workspace so the user isn't left with a blank wall
227
- const [profilesRes, entitiesRes] = await Promise.allSettled([
228
- hubGet("/profiles", { userId, workspaceId }, cfg),
229
- hubGet("/entities", { userId, workspaceId, limit: 5 }, cfg),
230
- ]);
231
- if (profilesRes.status === "fulfilled") {
232
- const profiles = profilesRes.value.profiles ?? profilesRes.value;
233
- const pList = Array.isArray(profiles) ? profiles : [];
234
- if (pList.length > 0) {
235
- const total = (entitiesRes.status === "fulfilled")
236
- ? Number(entitiesRes.value.total ?? 0)
237
- : 0;
238
- log.blank();
239
- log.dim(`This workspace has ${total > 0 ? `${total} entities` : "no entities yet"} across ${pList.length} profile${pList.length !== 1 ? "s" : ""}:`);
240
- for (const p of pList.slice(0, 6)) {
241
- log.dim(` · ${String(p.name ?? p.slug ?? "")} ${chalk.dim(`--profile=${String(p.slug ?? "")}`)}`);
242
- }
243
- log.blank();
244
- log.dim(`Try: synap search <term> --workspace ${workspaceId}`);
245
- log.dim(` or synap list --workspace ${workspaceId} --profile <slug>`);
246
- }
247
- }
248
- }
382
+ // A/B ranker diagnostic — render the comparison and STOP. This is a
383
+ // ranking diff (baseline vs Horizon on the same candidate pool), not the
384
+ // normal answer, so it replaces the substrate listing when requested.
385
+ if (opts.compare) {
386
+ renderComparison(query, res.comparison);
249
387
  return;
250
388
  }
251
- for (const hit of rawHits) {
252
- const h = hit;
253
- // Search hits: { id, collection, document: { id, title, entityType, ... } }
254
- const doc = (h.document ?? h);
255
- const name = String(doc.title ?? doc.id ?? h.id ?? "");
256
- const type = String(doc.entityType ?? doc.profileSlug ?? h.collection ?? "");
257
- const id = String(doc.id ?? h.id ?? "").slice(0, 8);
258
- log.info(`${name} ${chalk.dim(`[${type}]`)} ${chalk.dim(id)}`);
259
- }
260
- }
261
- catch (e) {
262
- console.error(chalk.red("Error: " + e.message));
263
- process.exit(1);
264
- }
265
- }
266
- // ─── rememberData ─────────────────────────────────────────────────────────────
267
- export async function rememberData(text, opts) {
268
- try {
269
- const cfg = await resolveHubConfig(opts);
270
- const userId = await resolveUserId(cfg);
271
- const res = await hubPost("/memory", {
272
- userId,
273
- fact: text,
274
- sourceEntityId: opts.context,
275
- }, cfg);
276
- const data = res;
277
- const id = String(data.id ?? "");
278
- if (opts.json) {
279
- console.log(JSON.stringify({ id, stored: true }, null, 2));
389
+ const routed = (res.routedTo ?? []).join(", ");
390
+ const verdict = res.verdict ? chalk.dim(` · ${res.verdict}`) : "";
391
+ // Honest routing: if the query's cue suggested one substrate but another
392
+ // actually answered (e.g. "how to…" cued procedural but the runbook is an
393
+ // entity), say so instead of dangling a "primary" that returned nothing.
394
+ const routeNote = res.intent && res.intent !== res.primary
395
+ ? `${chalk.bold(routed)} · asked-for ${res.intent}, answered by ${chalk.bold(res.primary)}`
396
+ : `${chalk.bold(routed)} (primary: ${chalk.bold(res.primary)})`;
397
+ log.dim(`"${query}" → routed to ${routeNote}${verdict}`);
398
+ log.blank();
399
+ const answers = res.answers ?? [];
400
+ const total = answers.reduce((n, a) => n + (a.items?.length ?? 0), 0);
401
+ const anyErrored = (res.degraded?.length ?? 0) > 0;
402
+ // Only claim a clean "no results" when every store actually answered. If one
403
+ // errored, fall through to the per-substrate loop so the outage is shown.
404
+ if (total === 0 && !anyErrored) {
405
+ log.dim(`No results across ${routed || "any substrate"}.`);
406
+ log.dim(` Try broader terms, or capture knowledge with: synap capture`);
280
407
  return;
281
408
  }
282
- log.success(`Stored: "${text.slice(0, 80)}"`);
409
+ for (const answer of answers) {
410
+ const items = answer.items ?? [];
411
+ // Glass-box: an errored store is NOT "found nothing" — say so, never hide it.
412
+ if (answer.status === "error") {
413
+ log.info(chalk.bold(answer.substrate.toUpperCase()) +
414
+ chalk.yellow(" — store unavailable (not searched)"));
415
+ log.blank();
416
+ continue;
417
+ }
418
+ if (items.length === 0)
419
+ continue;
420
+ log.info(chalk.bold(answer.substrate.toUpperCase()) + chalk.dim(` (${items.length})`));
421
+ for (const item of items) {
422
+ log.info(" " + formatAskItem(answer.substrate, item));
423
+ }
424
+ log.blank();
425
+ }
426
+ // res.degraded was only ever checked as a boolean (anyErrored) above —
427
+ // surface which substrate(s) actually degraded, verbatim, so the answer's
428
+ // incompleteness is visible rather than just its existence.
429
+ if (res.degraded && res.degraded.length > 0) {
430
+ log.dim(`Degraded: ${res.degraded.join(", ")}`);
431
+ }
283
432
  }
284
433
  catch (e) {
285
- console.error(chalk.red("Error: " + e.message));
434
+ renderHubError(e);
286
435
  process.exit(1);
287
436
  }
288
437
  }
289
- // ─── recallData ───────────────────────────────────────────────────────────────
290
- export async function recallData(query, opts) {
291
- try {
292
- const cfg = await resolveHubConfig(opts);
293
- const userId = await resolveUserId(cfg);
294
- const limit = parseInt(opts.limit ?? "20", 10);
295
- const res = await hubGet("/memory", { userId, q: query, limit }, cfg);
296
- const memories = res.memories ?? res;
297
- const list = Array.isArray(memories) ? memories : [];
298
- if (opts.json) {
299
- console.log(JSON.stringify({ memories: list }, null, 2));
300
- return;
301
- }
302
- if (list.length === 0) {
303
- log.dim("No memories found.");
304
- return;
305
- }
306
- list.forEach((mem, i) => {
307
- const m = mem;
308
- const content = String(m.fact ?? m.content ?? "").slice(0, 120);
309
- log.info(`${i + 1}. ${content}`);
310
- });
438
+ const refLabel = (r) => `${String(r.name ?? r.id ?? "?")}${r.profileSlug ? ` [${r.profileSlug}]` : ""}`;
439
+ /** Print 1-3 dim graph-impact lines for a create response. No-op if absent. */
440
+ function reportResolution(res) {
441
+ const resolution = res.resolution;
442
+ if (!resolution)
443
+ return;
444
+ const existing = resolution.existingSameProfile;
445
+ if (existing) {
446
+ log.dim(`⚠ an entity named "${String(existing.name ?? "?")}" already exists as profile ${String(existing.profileSlug ?? "?")} (${String(existing.id ?? "?").slice(0, 8)}) — consider updating it instead of duplicating`);
311
447
  }
312
- catch (e) {
313
- console.error(chalk.red("Error: " + e.message));
314
- process.exit(1);
448
+ const autoConnected = resolution.autoConnected ?? [];
449
+ if (autoConnected.length > 0) {
450
+ const names = autoConnected.map(refLabel).join(", ");
451
+ log.dim(`→ auto-linked to ${autoConnected.length} existing same-name entit${autoConnected.length === 1 ? "y" : "ies"}: ${names}`);
452
+ }
453
+ // suggestions beyond what was already auto-connected
454
+ const autoIds = new Set(autoConnected.map((r) => r.id).filter(Boolean));
455
+ const suggestions = (resolution.suggestions ?? []).filter((s) => !autoIds.has(s.id));
456
+ if (suggestions.length > 0) {
457
+ log.dim(`· you may want to link: ${suggestions.map(refLabel).join(", ")}`);
315
458
  }
316
459
  }
317
460
  // ─── createEntity ─────────────────────────────────────────────────────────────
@@ -319,7 +462,6 @@ export async function createEntity(opts) {
319
462
  try {
320
463
  const cfg = await resolveHubConfig(opts);
321
464
  const userId = await resolveUserId(cfg);
322
- // parse props as unknown then cast — user-supplied JSON
323
465
  const properties = JSON.parse(opts.props ?? "{}");
324
466
  const res = await hubPost("/entities", {
325
467
  userId,
@@ -329,19 +471,20 @@ export async function createEntity(opts) {
329
471
  properties,
330
472
  }, cfg);
331
473
  const entity = res;
332
- if (opts.json) {
333
- console.log(JSON.stringify(entity, null, 2));
334
- return;
335
- }
336
- if (entity.status === "proposed") {
337
- log.info(`Proposed: ${opts.name} (proposal: ${String(entity.proposalId ?? "")})`);
338
- }
339
- else {
340
- log.success(`Created: ${opts.name} (${String(entity.id ?? "")})`);
341
- }
474
+ await reportWrite(entity, {
475
+ label: `Created: ${opts.name}`,
476
+ lane: "work",
477
+ workspaceId: opts.workspace ?? cfg.workspaceId,
478
+ cfg,
479
+ json: opts.json,
480
+ });
481
+ // Graph-impact feedback — human output only (the `--json` path already
482
+ // passed the raw response, including `resolution`, through reportWrite).
483
+ if (!opts.json)
484
+ reportResolution(entity);
342
485
  }
343
486
  catch (e) {
344
- console.error(chalk.red("Error: " + e.message));
487
+ renderHubError(e);
345
488
  process.exit(1);
346
489
  }
347
490
  }
@@ -355,8 +498,8 @@ export async function listProfiles(opts) {
355
498
  console.error(chalk.red("No workspace set. Run: synap use <workspace-id>"));
356
499
  process.exit(1);
357
500
  }
358
- const res = await hubGet("/profiles", { userId, workspaceId }, cfg);
359
- const profiles = (res.profiles ?? (Array.isArray(res) ? res : []));
501
+ const res = await hubGet("/profiles", { userId, workspaceId, ...(opts.json ? { detail: "full" } : {}) }, cfg);
502
+ const profiles = unwrapList(res, ["profiles"]);
360
503
  if (opts.json) {
361
504
  console.log(JSON.stringify(profiles, null, 2));
362
505
  return;
@@ -369,11 +512,18 @@ export async function listProfiles(opts) {
369
512
  for (const p of profiles) {
370
513
  const scope = String(p.entityScope ?? "workspace");
371
514
  const desc = p.description ? chalk.dim(` — ${String(p.description).slice(0, 60)}`) : "";
372
- log.info(`${chalk.bold(String(p.slug ?? ""))} ${chalk.dim(scope)}${desc}`);
515
+ // A role-profile (client, partner, …) is a hat an existing entity wears
516
+ // via `synap facet attach` — never created as its own entity. Label it
517
+ // distinctly so it's never confused with a primary kind.
518
+ const applicableKinds = p.applicableKinds;
519
+ const kindLabel = p.profileKind === "role"
520
+ ? chalk.magenta(`[Role${applicableKinds?.length ? ` of ${applicableKinds.join("/")}` : ""}]`)
521
+ : chalk.green("[Kind]");
522
+ log.info(`${chalk.bold(String(p.slug ?? ""))} ${kindLabel} ${chalk.dim(scope)}${desc}`);
373
523
  }
374
524
  }
375
525
  catch (e) {
376
- console.error(chalk.red("Error: " + e.message));
526
+ renderHubError(e);
377
527
  process.exit(1);
378
528
  }
379
529
  }
@@ -390,14 +540,16 @@ export async function createRelation(opts) {
390
540
  type: opts.type,
391
541
  }, cfg);
392
542
  const relation = res;
393
- if (opts.json) {
394
- console.log(JSON.stringify(relation, null, 2));
395
- return;
396
- }
397
- log.success(`Created relation: ${opts.type} (${opts.source} → ${opts.target})`);
543
+ await reportWrite(relation, {
544
+ label: `Relation: ${opts.type} (${opts.source} → ${opts.target})`,
545
+ lane: "work",
546
+ workspaceId: opts.workspace ?? cfg.workspaceId,
547
+ cfg,
548
+ json: opts.json,
549
+ });
398
550
  }
399
551
  catch (e) {
400
- console.error(chalk.red("Error: " + e.message));
552
+ renderHubError(e);
401
553
  process.exit(1);
402
554
  }
403
555
  }
@@ -405,21 +557,30 @@ export async function createRelation(opts) {
405
557
  export async function updateEntity(id, opts) {
406
558
  try {
407
559
  const cfg = await resolveHubConfig(opts);
560
+ const workspaceId = opts.workspace ?? cfg.workspaceId;
408
561
  // parse props as unknown then cast — user-supplied JSON
409
562
  const metadata = JSON.parse(opts.props);
410
- const res = await hubPatch(`/entities/${id}`, {
411
- userId: cfg.userId,
412
- metadata,
413
- }, cfg);
563
+ const body = { userId: cfg.userId, metadata };
564
+ // backend reads body.workspaceId to scope automation matching — without it
565
+ // the update lands pod-wide and workspace-gated automations never fire
566
+ if (workspaceId)
567
+ body.workspaceId = workspaceId;
568
+ const res = await hubPatch(`/entities/${id}`, body, cfg);
414
569
  const entity = res;
415
570
  if (opts.json) {
416
571
  console.log(JSON.stringify(entity, null, 2));
417
572
  return;
418
573
  }
419
574
  log.success(`Updated: ${id}`);
575
+ // Graph-impact feedback — shallow immediate neighbors the edit may touch.
576
+ // Additive + graceful: absent on older backends, so output is unchanged.
577
+ const impact = entity.impact;
578
+ if (Array.isArray(impact) && impact.length > 0) {
579
+ log.dim(`→ ${impact.length} connected entit${impact.length === 1 ? "y" : "ies"} may be affected: ${impact.map(refLabel).join(", ")}`);
580
+ }
420
581
  }
421
582
  catch (e) {
422
- console.error(chalk.red("Error: " + e.message));
583
+ renderHubError(e);
423
584
  process.exit(1);
424
585
  }
425
586
  }