synabun 2.1.0 → 2.2.1

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 (332) hide show
  1. package/CHANGELOG.md +139 -0
  2. package/README.md +1 -1
  3. package/docs/whatsapp.md +11 -11
  4. package/lib/client-config-repair.js +13 -9
  5. package/lib/external-tools.js +6 -5
  6. package/lib/update-snapshot.js +77 -3
  7. package/lib/virustotal.mjs +12 -1
  8. package/mcp-server/dist/index.js +1 -1
  9. package/mcp-server/dist/index.js.map +1 -1
  10. package/mcp-server/dist/services/budget-judgments.d.ts +1 -1
  11. package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -1
  12. package/mcp-server/dist/services/budget-judgments.js +5 -3
  13. package/mcp-server/dist/services/budget-judgments.js.map +1 -1
  14. package/mcp-server/dist/services/identity.d.ts +2 -2
  15. package/mcp-server/dist/services/identity.js +2 -2
  16. package/mcp-server/dist/services/memory-sessions.d.ts +1 -1
  17. package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -1
  18. package/mcp-server/dist/services/memory-sessions.js +28 -2
  19. package/mcp-server/dist/services/memory-sessions.js.map +1 -1
  20. package/mcp-server/dist/services/neural-interface.d.ts +2 -1
  21. package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
  22. package/mcp-server/dist/services/neural-interface.js.map +1 -1
  23. package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
  24. package/mcp-server/dist/services/server-instructions.js +2 -1
  25. package/mcp-server/dist/services/server-instructions.js.map +1 -1
  26. package/mcp-server/dist/services/tool-catalog.js +1 -1
  27. package/mcp-server/dist/services/tool-catalog.js.map +1 -1
  28. package/mcp-server/dist/services/tool-list.d.ts +1 -1
  29. package/mcp-server/dist/services/tool-list.d.ts.map +1 -1
  30. package/mcp-server/dist/services/tool-runtime.d.ts +2 -2
  31. package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -1
  32. package/mcp-server/dist/services/tool-runtime.js +1 -1
  33. package/mcp-server/dist/services/tool-runtime.js.map +1 -1
  34. package/mcp-server/dist/services/typesafe-config.js +1 -1
  35. package/mcp-server/dist/services/typesafe-config.js.map +1 -1
  36. package/mcp-server/dist/tools/agents.d.ts +8 -8
  37. package/mcp-server/dist/tools/agents.d.ts.map +1 -1
  38. package/mcp-server/dist/tools/agents.js +10 -9
  39. package/mcp-server/dist/tools/agents.js.map +1 -1
  40. package/mcp-server/dist/tools/choice.d.ts +6 -0
  41. package/mcp-server/dist/tools/choice.d.ts.map +1 -1
  42. package/mcp-server/dist/tools/choice.js +18 -13
  43. package/mcp-server/dist/tools/choice.js.map +1 -1
  44. package/mcp-server/dist/tools/git-tools.js +2 -2
  45. package/mcp-server/dist/tools/git-tools.js.map +1 -1
  46. package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
  47. package/mcp-server/dist/tools/morelogin.js +9 -6
  48. package/mcp-server/dist/tools/morelogin.js.map +1 -1
  49. package/mcp-server/dist/tools/skill-tools.d.ts +8 -8
  50. package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -1
  51. package/mcp-server/dist/tools/skill-tools.js +11 -8
  52. package/mcp-server/dist/tools/skill-tools.js.map +1 -1
  53. package/mcp-server/package-lock.json +2 -2
  54. package/mcp-server/package.json +1 -1
  55. package/mcp-server/src/index.ts +1 -1
  56. package/mcp-server/src/services/budget-judgments.ts +5 -3
  57. package/mcp-server/src/services/identity.ts +2 -2
  58. package/mcp-server/src/services/memory-sessions.ts +24 -3
  59. package/mcp-server/src/services/neural-interface.ts +2 -1
  60. package/mcp-server/src/services/server-instructions.ts +2 -1
  61. package/mcp-server/src/services/tool-catalog.ts +1 -1
  62. package/mcp-server/src/services/tool-list.ts +1 -1
  63. package/mcp-server/src/services/tool-runtime.ts +2 -2
  64. package/mcp-server/src/services/typesafe-config.ts +1 -1
  65. package/mcp-server/src/tools/agents.ts +10 -9
  66. package/mcp-server/src/tools/choice.ts +17 -12
  67. package/mcp-server/src/tools/git-tools.ts +2 -2
  68. package/mcp-server/src/tools/morelogin.ts +6 -4
  69. package/mcp-server/src/tools/skill-tools.ts +11 -8
  70. package/neural-interface/i18n/en.json +504 -39
  71. package/neural-interface/i18n/pt-BR.json +504 -39
  72. package/neural-interface/lib/agent-sandbox-elevation.js +6 -0
  73. package/neural-interface/lib/antigravity/acp-client-host.js +554 -0
  74. package/neural-interface/lib/antigravity/acp-connection.js +299 -0
  75. package/neural-interface/lib/antigravity/bypass.js +100 -0
  76. package/neural-interface/lib/antigravity/catalog.js +273 -0
  77. package/neural-interface/lib/antigravity/home.js +260 -0
  78. package/neural-interface/lib/antigravity/migrate-gemini.js +139 -0
  79. package/neural-interface/lib/antigravity/panel-bridge.js +1058 -0
  80. package/neural-interface/lib/antigravity/policy-hook.mjs +78 -0
  81. package/neural-interface/lib/antigravity/policy.js +74 -0
  82. package/neural-interface/lib/antigravity/tool-call.js +178 -0
  83. package/neural-interface/lib/antigravity/transcripts.js +252 -0
  84. package/neural-interface/lib/antigravity/usage.js +43 -0
  85. package/neural-interface/lib/assistant-api.js +1 -0
  86. package/neural-interface/lib/assistant-brains/antigravity.js +423 -0
  87. package/neural-interface/lib/assistant-brains/codex.js +6 -1
  88. package/neural-interface/lib/assistant-budget-planner.js +7 -3
  89. package/neural-interface/lib/assistant-budget-split.js +42 -15
  90. package/neural-interface/lib/assistant-catalog.js +76 -9
  91. package/neural-interface/lib/assistant-config.js +6 -3
  92. package/neural-interface/lib/assistant-dispatch.js +211 -50
  93. package/neural-interface/lib/assistant-envelope.js +153 -1
  94. package/neural-interface/lib/assistant-evidence.js +80 -0
  95. package/neural-interface/lib/assistant-hidden-models.js +7 -5
  96. package/neural-interface/lib/assistant-persona.js +16 -11
  97. package/neural-interface/lib/assistant-plan-permissions.js +15 -0
  98. package/neural-interface/lib/assistant-router.js +237 -35
  99. package/neural-interface/lib/assistant-runtime.js +158 -12
  100. package/neural-interface/lib/assistant-task-prompt.js +10 -8
  101. package/neural-interface/lib/assistant-tools.js +2 -2
  102. package/neural-interface/lib/assistant-usage.js +29 -0
  103. package/neural-interface/lib/backup-service.js +6 -1
  104. package/neural-interface/lib/browser-guard-cdp.js +5 -4
  105. package/neural-interface/lib/browser-network-guard.js +274 -82
  106. package/neural-interface/lib/browser-tool-policy.js +12 -7
  107. package/neural-interface/lib/claude-agent-bridge.js +7 -0
  108. package/neural-interface/lib/claude-history-cache.js +210 -0
  109. package/neural-interface/lib/claude-model-catalog.js +5 -1
  110. package/neural-interface/lib/cli-install-api.js +1 -1
  111. package/neural-interface/lib/cli-install.js +160 -31
  112. package/neural-interface/lib/cli-resolver.js +9 -2
  113. package/neural-interface/lib/cli-update-planner.js +25 -15
  114. package/neural-interface/lib/codex-rollout-reader.js +52 -0
  115. package/neural-interface/lib/codex-worker-app-server.js +25 -6
  116. package/neural-interface/lib/editor-git-api.js +3 -20
  117. package/neural-interface/lib/exec-loop-command.js +9 -2
  118. package/neural-interface/lib/file-explorer-api.js +74 -40
  119. package/neural-interface/lib/file-explorer-watch.js +9 -3
  120. package/neural-interface/lib/git-bash/install.js +52 -6
  121. package/neural-interface/lib/git-bash/manager.js +59 -17
  122. package/neural-interface/lib/git-bash/pins.js +12 -0
  123. package/neural-interface/lib/git-read.js +2 -2
  124. package/neural-interface/lib/git-status-runner.js +390 -0
  125. package/neural-interface/lib/git-status-watchdog.js +40 -0
  126. package/neural-interface/lib/greeting/budgets.js +1 -0
  127. package/neural-interface/lib/greeting/engine.js +2 -2
  128. package/neural-interface/lib/greeting/sources.js +10 -12
  129. package/neural-interface/lib/greeting/writer.js +1 -1
  130. package/neural-interface/lib/mcp-client-config.js +37 -6
  131. package/neural-interface/lib/mcp-external-servers.js +40 -7
  132. package/neural-interface/lib/mcp-installer.js +2 -1
  133. package/neural-interface/lib/memory-session-capture.js +17 -3
  134. package/neural-interface/lib/morelogin.js +147 -25
  135. package/neural-interface/lib/native-loop-providers.js +423 -23
  136. package/neural-interface/lib/native-loop-runtime.js +23 -2
  137. package/neural-interface/lib/opencode-native-boundary.js +165 -22
  138. package/neural-interface/lib/owner-authority.js +1 -1
  139. package/neural-interface/lib/path-confine.js +12 -9
  140. package/neural-interface/lib/peer-identity.js +80 -6
  141. package/neural-interface/lib/pty-host/core.js +28 -1
  142. package/neural-interface/lib/pty-host/lifetime.js +12 -0
  143. package/neural-interface/lib/pty-host/manager.js +12 -1
  144. package/neural-interface/lib/remote-policy.js +1 -1
  145. package/neural-interface/lib/request-gate.js +3 -0
  146. package/neural-interface/lib/rulesets/api.js +6 -5
  147. package/neural-interface/lib/rulesets/fs-safe.js +1 -1
  148. package/neural-interface/lib/rulesets/installer.js +89 -10
  149. package/neural-interface/lib/rulesets/render.js +1 -1
  150. package/neural-interface/lib/session-cache-builder.js +64 -1
  151. package/neural-interface/lib/skills/catalog.js +5 -1
  152. package/neural-interface/lib/skills/creation-brief.js +1 -1
  153. package/neural-interface/lib/skills/creation-sources.js +19 -1
  154. package/neural-interface/lib/skills/deploy.js +9 -4
  155. package/neural-interface/lib/skills/draft-confinement.js +3 -1
  156. package/neural-interface/lib/skills/eval-confinement.js +3 -1
  157. package/neural-interface/lib/skills/eval-trace.js +16 -0
  158. package/neural-interface/lib/skills/export.js +5 -2
  159. package/neural-interface/lib/skills/known-bridge-digests.json +58 -4
  160. package/neural-interface/lib/skills/ownership.js +1 -1
  161. package/neural-interface/lib/skills/roots.js +10 -3
  162. package/neural-interface/lib/skills/runtime-checks.js +6 -3
  163. package/neural-interface/lib/skills/service.js +15 -7
  164. package/neural-interface/lib/skills/telemetry.js +6 -0
  165. package/neural-interface/lib/synabun-version.js +1 -1
  166. package/neural-interface/lib/system-activity.js +1 -1
  167. package/neural-interface/lib/whatsapp/brain.js +7 -3
  168. package/neural-interface/lib/whatsapp/cards.js +22 -9
  169. package/neural-interface/lib/whatsapp/config.js +1 -1
  170. package/neural-interface/lib/worker-confinement.js +77 -1
  171. package/neural-interface/lib/worker-question.js +74 -0
  172. package/neural-interface/lib/worker-resume.js +13 -4
  173. package/neural-interface/lib/worker-sandbox.js +46 -16
  174. package/neural-interface/package-lock.json +18 -2
  175. package/neural-interface/package.json +2 -1
  176. package/neural-interface/public/onboarding.html +1 -1
  177. package/neural-interface/public/shared/agent-runtime-options.js +89 -11
  178. package/neural-interface/public/shared/agy/agy-bypass.js +47 -0
  179. package/neural-interface/public/shared/agy/agy-client.js +266 -0
  180. package/neural-interface/public/shared/agy/agy-commands.js +120 -0
  181. package/neural-interface/public/shared/agy/agy-copy.js +326 -0
  182. package/neural-interface/public/shared/agy/agy-diff.js +161 -0
  183. package/neural-interface/public/shared/agy/agy-events.js +469 -0
  184. package/neural-interface/public/shared/agy/agy-greeting.js +26 -0
  185. package/neural-interface/public/shared/agy/agy-options.js +232 -0
  186. package/neural-interface/public/shared/agy/agy-panel.js +2543 -0
  187. package/neural-interface/public/shared/agy/agy-render.js +392 -0
  188. package/neural-interface/public/shared/agy/agy-setup.js +149 -0
  189. package/neural-interface/public/shared/agy/agy-styles.js +604 -0
  190. package/neural-interface/public/shared/agy/agy-tabs-logic.js +181 -0
  191. package/neural-interface/public/shared/api.js +4 -4
  192. package/neural-interface/public/shared/assistant/asst-agents-dock.js +3 -4
  193. package/neural-interface/public/shared/assistant/asst-api.js +15 -3
  194. package/neural-interface/public/shared/assistant/asst-brain-picker.js +56 -9
  195. package/neural-interface/public/shared/assistant/asst-budget.js +5 -4
  196. package/neural-interface/public/shared/assistant/asst-computer.js +26 -2
  197. package/neural-interface/public/shared/assistant/asst-control.js +48 -0
  198. package/neural-interface/public/shared/assistant/asst-hosts.js +16 -7
  199. package/neural-interface/public/shared/assistant/asst-limit-dock.js +6 -1
  200. package/neural-interface/public/shared/assistant/asst-limit-wait.js +38 -5
  201. package/neural-interface/public/shared/assistant/asst-motion-gate.js +80 -0
  202. package/neural-interface/public/shared/assistant/asst-panel.js +46 -9
  203. package/neural-interface/public/shared/assistant/asst-render.js +112 -18
  204. package/neural-interface/public/shared/assistant/asst-route.js +237 -57
  205. package/neural-interface/public/shared/assistant/asst-routes-editor.js +9 -8
  206. package/neural-interface/public/shared/assistant/asst-scene-styles.js +55 -23
  207. package/neural-interface/public/shared/assistant/asst-scenes.js +279 -80
  208. package/neural-interface/public/shared/assistant/asst-sidepanel-styles.js +4 -4
  209. package/neural-interface/public/shared/assistant/asst-sidepanel.js +51 -7
  210. package/neural-interface/public/shared/assistant/asst-slash.js +8 -2
  211. package/neural-interface/public/shared/assistant/asst-state.js +76 -9
  212. package/neural-interface/public/shared/assistant/asst-style-cache.js +44 -0
  213. package/neural-interface/public/shared/assistant/asst-styles.js +58 -4
  214. package/neural-interface/public/shared/assistant/asst-tool-kinds.js +34 -7
  215. package/neural-interface/public/shared/assistant/asst-usage.js +2 -1
  216. package/neural-interface/public/shared/cdx/cdx-output.js +26 -3
  217. package/neural-interface/public/shared/cdx/cdx-panel.js +14 -2
  218. package/neural-interface/public/shared/cdx/cdx-preview.js +12 -0
  219. package/neural-interface/public/shared/cdx/cdx-protocol.js +11 -10
  220. package/neural-interface/public/shared/cdx/cdx-render.js +10 -9
  221. package/neural-interface/public/shared/cdx/cdx-requests.js +11 -1
  222. package/neural-interface/public/shared/cdx/cdx-styles.js +4 -0
  223. package/neural-interface/public/shared/cdx/cdx-tabs.js +75 -5
  224. package/neural-interface/public/shared/cli-install-controller.js +218 -0
  225. package/neural-interface/public/shared/cli-install-view.js +7 -4
  226. package/neural-interface/public/shared/cli-status.js +9 -8
  227. package/neural-interface/public/shared/constants.js +3 -2
  228. package/neural-interface/public/shared/context-menu-model.js +1 -0
  229. package/neural-interface/public/shared/cp/cp-permissions.js +12 -0
  230. package/neural-interface/public/shared/cp/cp-restore.js +162 -0
  231. package/neural-interface/public/shared/cp/cp-tool-cards.js +10 -3
  232. package/neural-interface/public/shared/desktop-bridge.js +17 -0
  233. package/neural-interface/public/shared/desktop-titlebar-view.js +12 -0
  234. package/neural-interface/public/shared/desktop-titlebar.js +13 -4
  235. package/neural-interface/public/shared/desktop-update-view.js +1 -0
  236. package/neural-interface/public/shared/explorer/fx-api.js +4 -4
  237. package/neural-interface/public/shared/explorer/fx-links.js +3 -3
  238. package/neural-interface/public/shared/explorer/fx-logic.js +1 -1
  239. package/neural-interface/public/shared/explorer/fx-main.js +48 -16
  240. package/neural-interface/public/shared/explorer/fx-tree.js +1 -0
  241. package/neural-interface/public/shared/greeting/greeting-card.js +2 -2
  242. package/neural-interface/public/shared/greeting/greeting-view.js +1 -0
  243. package/neural-interface/public/shared/html-shell.js +21 -4
  244. package/neural-interface/public/shared/loading-teardown.js +34 -0
  245. package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +6 -1
  246. package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +28 -7
  247. package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +13 -4
  248. package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +17 -1
  249. package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +6 -0
  250. package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +101 -68
  251. package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +46 -3
  252. package/neural-interface/public/shared/provider-icons.js +4 -2
  253. package/neural-interface/public/shared/settings/settings-rules.js +2 -2
  254. package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +1 -1
  255. package/neural-interface/public/shared/settings/settings-skill-folders.js +2 -2
  256. package/neural-interface/public/shared/showcase-privacy.js +6 -1
  257. package/neural-interface/public/shared/sidepanel-session-status.js +3 -3
  258. package/neural-interface/public/shared/skills/ss-creation.js +6 -3
  259. package/neural-interface/public/shared/skills/ss-dialog.js +5 -2
  260. package/neural-interface/public/shared/skills/ss-lint.js +9 -1
  261. package/neural-interface/public/shared/skills/ss-profiles.js +44 -6
  262. package/neural-interface/public/shared/skills/ss-review-view.js +2 -2
  263. package/neural-interface/public/shared/skills/studio/sx-actions.js +1 -1
  264. package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +1 -1
  265. package/neural-interface/public/shared/skills/studio/sx-create-ai.js +2 -1
  266. package/neural-interface/public/shared/skills/studio/sx-dom.js +2 -1
  267. package/neural-interface/public/shared/skills/studio/sx-fixes-view.js +359 -0
  268. package/neural-interface/public/shared/skills/studio/sx-fixes.js +327 -0
  269. package/neural-interface/public/shared/skills/studio/sx-inspector.js +1 -1
  270. package/neural-interface/public/shared/skills/studio/sx-install-flow.js +9 -6
  271. package/neural-interface/public/shared/skills/studio/sx-install.js +20 -3
  272. package/neural-interface/public/shared/skills/studio/sx-library-view.js +144 -35
  273. package/neural-interface/public/shared/skills/studio/sx-library.js +6 -2
  274. package/neural-interface/public/shared/skills/studio/sx-main.js +11 -2
  275. package/neural-interface/public/shared/skills/studio/sx-review-view.js +1 -1
  276. package/neural-interface/public/shared/skills/studio/sx-subject.js +7 -6
  277. package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +6 -5
  278. package/neural-interface/public/shared/state.js +1 -1
  279. package/neural-interface/public/shared/storage-retry.js +47 -0
  280. package/neural-interface/public/shared/storage.js +25 -4
  281. package/neural-interface/public/shared/streaming-markdown.js +58 -0
  282. package/neural-interface/public/shared/styles.css +362 -35
  283. package/neural-interface/public/shared/synabun-mascot.js +27 -8
  284. package/neural-interface/public/shared/synabun-ticker.js +147 -12
  285. package/neural-interface/public/shared/text-cache.js +29 -0
  286. package/neural-interface/public/shared/tool-list-picker.js +2 -2
  287. package/neural-interface/public/shared/tool-list-view.js +2 -2
  288. package/neural-interface/public/shared/topright-overflow-logic.js +54 -0
  289. package/neural-interface/public/shared/ui-antigravity-panel.js +19 -0
  290. package/neural-interface/public/shared/ui-assistant.js +2 -0
  291. package/neural-interface/public/shared/ui-automation-studio.js +20 -12
  292. package/neural-interface/public/shared/ui-claude-panel.js +374 -134
  293. package/neural-interface/public/shared/ui-cli-banner.js +23 -167
  294. package/neural-interface/public/shared/ui-file-explorer.js +48 -6
  295. package/neural-interface/public/shared/ui-file-links.js +2 -2
  296. package/neural-interface/public/shared/ui-games.js +2 -2
  297. package/neural-interface/public/shared/ui-judgments.js +1 -1
  298. package/neural-interface/public/shared/ui-keybinds.js +8 -2
  299. package/neural-interface/public/shared/ui-link.js +3 -3
  300. package/neural-interface/public/shared/ui-loading.js +5 -4
  301. package/neural-interface/public/shared/ui-menubar.js +2 -2
  302. package/neural-interface/public/shared/ui-native-loop-router.js +4 -0
  303. package/neural-interface/public/shared/ui-navbar.js +45 -0
  304. package/neural-interface/public/shared/ui-notifications.js +1 -0
  305. package/neural-interface/public/shared/ui-resume.js +9 -7
  306. package/neural-interface/public/shared/ui-schedules-studio.js +41 -10
  307. package/neural-interface/public/shared/ui-scroll-follow.js +16 -5
  308. package/neural-interface/public/shared/ui-sessions.js +22 -12
  309. package/neural-interface/public/shared/ui-settings.js +90 -65
  310. package/neural-interface/public/shared/ui-sidepanel-session-entry.js +9 -2
  311. package/neural-interface/public/shared/ui-sidepanel-sessions.js +3 -3
  312. package/neural-interface/public/shared/ui-sidepanel-tray.js +48 -7
  313. package/neural-interface/public/shared/ui-sidepanel-windows.js +3 -3
  314. package/neural-interface/public/shared/ui-terminal.js +146 -10
  315. package/neural-interface/public/shared/ui-tool-updates.js +3 -3
  316. package/neural-interface/public/shared/ui-tooltip.js +71 -46
  317. package/neural-interface/public/shared/ui-topright-overflow.js +233 -0
  318. package/neural-interface/public/shared/ui-whiteboard.js +1 -1
  319. package/neural-interface/public/shared/ui-worker-end.js +1 -1
  320. package/neural-interface/public/shared/whatsapp/wa-view.js +2 -2
  321. package/neural-interface/public/shared/worker-questions.js +34 -0
  322. package/neural-interface/public/sidepanel-session.html +1 -1
  323. package/neural-interface/server.js +731 -466
  324. package/neural-interface/templates/rulesets/README.md +1 -1
  325. package/neural-interface/templates/rulesets/legacy-hashes.json +5 -5
  326. package/neural-interface/templates/rulesets/manifest.json +1 -1
  327. package/npm-shrinkwrap.json +18 -2
  328. package/package.json +2 -1
  329. package/skills/skill-forge/SKILL.md +4 -4
  330. package/skills/synabun/SKILL.md +1 -1
  331. package/skills/synabun/modules/schedule.md +3 -3
  332. /package/neural-interface/templates/rulesets/{gemini.md → antigravity.md} +0 -0
@@ -10,7 +10,9 @@ const object = value => value && typeof value === 'object' && !Array.isArray(val
10
10
  const canonical = path => { try { return realpathSync(path); } catch { return resolve(path); } };
11
11
  const claudeProject = (user, cwd) => user.projects?.[canonical(cwd)] || user.projects?.[resolve(cwd)] || {};
12
12
  const external = name => name !== 'SynaBun';
13
- export const EXTERNAL_SERVER_LIMITATIONS = Object.freeze({ codex: 'Plugin-provided MCP servers cannot be enumerated from config.toml', 'claude-code': 'claude.ai connectors are unavailable under strict MCP configuration' });
13
+ export const EXTERNAL_SERVER_LIMITATIONS = Object.freeze({ codex: 'Plugin-provided MCP servers cannot be enumerated from config.toml', 'claude-code': 'claude.ai connectors are unavailable under strict MCP configuration', antigravity: 'A project\'s own .agents/mcp_config.json still loads into an Antigravity session; managed brains, workers and restricted panel sessions refuse enabled project servers before starting agy' });
14
+ /** Where Antigravity keeps its MCP servers: { mcpServers: { name: { command, args, env, disabled } | { serverUrl, headers, disabled } } }. */
15
+ export function antigravityMcpConfigPath(home = homedir()) { return join(home, '.gemini', 'config', 'mcp_config.json'); }
14
16
  export const externalServerName = name => typeof name === 'string' && /^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/.test(name) && external(name);
15
17
  function read(path, fallback = null) {
16
18
  try { return readFileSync(path, 'utf8'); }
@@ -144,7 +146,7 @@ export function claudePluginDefinitions(pluginRoot, manifest = document(join(plu
144
146
  function claudeEntry(entry) { return structuredClone(entry); }
145
147
 
146
148
  /** This is the only filesystem discovery boundary; definitions never escape through servers/catalogView. */
147
- export function externalServerInventory({ provider, cwd = process.cwd(), codexHome, home = homedir(), dataHome, registry, opencodeConfig, readProject = true, env = process.env } = {}) {
149
+ export function externalServerInventory({ provider, cwd = process.cwd(), codexHome, home = homedir(), dataHome, registry, opencodeConfig, antigravityConfigPath = antigravityMcpConfigPath(home), readProject = true, env = process.env } = {}) {
148
150
  const reg = registry || (dataHome ? document(join(dataHome, 'data', 'mcp-registry.json')) : {});
149
151
  const access = agentAccessFrom(reg), definitions = new Map(), rows = new Map();
150
152
  function add(name, entry, source, deliverable = true, reason = null, kind = null) {
@@ -172,6 +174,13 @@ export function externalServerInventory({ provider, cwd = process.cwd(), codexHo
172
174
  for (const [name, entry] of Object.entries(mcpServersOf(base))) add(name, entry, 'user');
173
175
  if (env.OPENCODE_CONFIG) for (const [name, entry] of Object.entries(mcpServersOf(document(resolve(env.OPENCODE_CONFIG))))) add(name, entry, 'user-override');
174
176
  if (readProject) for (const path of projectOpenCodeConfigs(cwd)) for (const [name, entry] of Object.entries(mcpServersOf(document(path)))) add(name, entry, 'project');
177
+ } else if (provider === 'antigravity') {
178
+ // A remote server names its address `serverUrl` there; `disabled: true` turns it off in Antigravity itself.
179
+ for (const [name, entry] of Object.entries(object(document(antigravityConfigPath).mcpServers))) {
180
+ const own = object(entry);
181
+ const url = typeof own.serverUrl === 'string' ? own.serverUrl : own.url;
182
+ add(name, url !== undefined && own.url === undefined ? { ...own, url } : own, 'user', true, null, url !== undefined ? 'http' : 'stdio');
183
+ }
175
184
  } else if (provider === 'claude-code') {
176
185
  const configDir = env.CLAUDE_CONFIG_DIR || join(home, '.claude');
177
186
  const settings = { ...document(join(configDir, 'settings.json')), ...document(join(cwd, '.claude', 'settings.json')), ...document(join(cwd, '.claude', 'settings.local.json')) };
@@ -247,8 +256,8 @@ export function selectExternalServers(tools, inventory) {
247
256
  }
248
257
  export function externalCatalog(options = {}) {
249
258
  const rows = new Map();
250
- for (const provider of ['codex', 'opencode', 'claude-code']) for (const row of discoverExternalServers({ ...options, provider })) {
251
- if (!rows.has(row.name)) rows.set(row.name, { ref: `mcp:${row.name}`, label: row.name, selectable: row.selectable, providers: { codex: 'absent', opencode: 'absent', 'claude-code': 'absent' }, blocked: row.blocked });
259
+ for (const provider of ['codex', 'opencode', 'claude-code', 'antigravity']) for (const row of discoverExternalServers({ ...options, provider })) {
260
+ if (!rows.has(row.name)) rows.set(row.name, { ref: `mcp:${row.name}`, label: row.name, selectable: row.selectable, providers: { codex: 'absent', opencode: 'absent', 'claude-code': 'absent', antigravity: 'absent' }, blocked: row.blocked });
252
261
  rows.get(row.name).providers[provider] = row.blocked ? 'blocked' : row.deliverable ? 'deliverable' : 'unavailable';
253
262
  }
254
263
  return [...rows.values()].sort((a, b) => a.ref.localeCompare(b.ref));
@@ -310,14 +319,38 @@ export async function waitForClaudeMcpScope(q, names, { timeoutMs = 30000 } = {}
310
319
  }
311
320
  throw Object.assign(new Error('Claude MCP scope could not be enforced before the first turn'), { code: 'MCP_SCOPE_UNENFORCED' });
312
321
  }
322
+ /** Values shorter than this are never redacted: a short or common word would be erased from ordinary output. */
323
+ export const MIN_MCP_SECRET_LENGTH = 8;
313
324
  export function redactMcpValues(value, values) {
314
- if (typeof value === 'string') { for (const secret of [...new Set(values)].filter(Boolean).sort((a, b) => b.length - a.length)) value = value.split(secret).join('[MCP value]'); return value; }
325
+ if (typeof value === 'string') { for (const secret of [...new Set(values)].filter(item => typeof item === 'string' && item.length >= MIN_MCP_SECRET_LENGTH).sort((a, b) => b.length - a.length)) value = value.split(secret).join('[MCP value]'); return value; }
315
326
  if (Array.isArray(value)) return value.map(item => redactMcpValues(item, values));
316
327
  if (value && typeof value === 'object') return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, redactMcpValues(item, values)]));
317
328
  return value;
318
329
  }
319
330
 
320
- export function mcpDefinitionValues(servers) {
331
+ const SECRET_QUERY_NAME = /token|key|secret|auth|pass|pwd|sig|credential|session|code/i;
332
+ /** The secrets a URL carries (userinfo, credential-named query values), never the URL itself. */
333
+ export function urlSecretValues(url) {
334
+ if (typeof url !== 'string' || !url) return [];
335
+ let parsed; try { parsed = new URL(url); } catch { return []; }
336
+ const out = [];
337
+ for (const part of [parsed.username, parsed.password]) if (part) { out.push(part); try { out.push(decodeURIComponent(part)); } catch {} }
338
+ for (const [name, value] of parsed.searchParams) if (value && SECRET_QUERY_NAME.test(name)) { out.push(value); out.push(encodeURIComponent(value)); }
339
+ return out;
340
+ }
341
+
342
+ /**
343
+ * The real secrets of MCP server definitions: env / environment and header values, authToken, oauth strings
344
+ * and the secrets inside a URL. Never command, args, cwd, type or a whole URL, and never SynaBun's own entry
345
+ * (its headers name the project, terminal and role: ordinary words). `${VAR}` / `{env:VAR}` references are
346
+ * expanded through `env` when given. Values under MIN_MCP_SECRET_LENGTH are dropped.
347
+ */
348
+ export function mcpSecretValues(servers, { env = null, exclude = ['SynaBun'] } = {}) {
321
349
  const strings = value => typeof value === 'string' ? [value] : Array.isArray(value) ? value.flatMap(strings) : value && typeof value === 'object' ? Object.values(value).flatMap(strings) : [];
322
- return [...new Set(Object.values(servers || {}).flatMap(entry => ['command', 'args', 'cwd', 'env', 'environment', 'headers', 'url', 'oauth', 'headersHelper', 'authToken'].flatMap(field => strings(entry[field]))))].filter(Boolean);
350
+ const expand = value => env ? value.replace(/\{env:([A-Za-z_][A-Za-z0-9_]*)\}|\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (whole, a, b) => env[a || b] ?? whole) : value;
351
+ const values = Object.entries(servers || {}).filter(([name]) => !exclude.includes(name)).flatMap(([, entry]) => entry && typeof entry === 'object' ? [
352
+ ...['env', 'environment', 'headers', 'oauth', 'authToken'].flatMap(field => strings(entry[field])).flatMap(value => { const bare = /^(?:Bearer|Basic|Token|Bot)\s+(\S.*)$/i.exec(value); return bare ? [value, bare[1]] : [value]; }),
353
+ ...urlSecretValues(entry.url),
354
+ ] : []).map(expand);
355
+ return [...new Set(values)].filter(value => value.length >= MIN_MCP_SECRET_LENGTH);
323
356
  }
@@ -422,7 +422,8 @@ function scanHints(repoPath) {
422
422
  const hints = [];
423
423
  if (existsSync(join(repoPath, '.claude-plugin', 'plugin.json'))) hints.push('claude-plugin');
424
424
  if (existsSync(join(repoPath, '.codex'))) hints.push('codex');
425
- if (existsSync(join(repoPath, 'gemini-extension.json'))) hints.push('gemini');
425
+ // Antigravity's plugins: its own manifests, and the Gemini CLI extensions `agy plugin install` imports.
426
+ if (['gemini-extension.json', join('.antigravity-plugin', 'plugin.json'), join('.gemini-plugin', 'plugin.json')].some((file) => existsSync(join(repoPath, file)))) hints.push('antigravity');
426
427
  if (existsSync(join(repoPath, '.cursor'))) hints.push('cursor');
427
428
  if (existsSync(join(repoPath, '.windsurf'))) hints.push('windsurf');
428
429
  return hints;
@@ -4,10 +4,24 @@ import { detectProject } from '../../mcp-server/dist/config.js';
4
4
  import { normalizeMemoryEvents, ingestMemoryEvents } from '../../mcp-server/dist/services/memory-sessions.js';
5
5
  import { maintenancePaused } from './memory-timers.js';
6
6
 
7
- export function queueMemoryCapture({host,sessionId,filePath,project,cwd}) {
8
- if(!filePath || !sessionId || !['claude-code','codex'].includes(host))return;
7
+ /** The hosts whose transcripts are captured from a file (OpenCode's rows are taken in session-cache-builder.js). */
8
+ export const CAPTURE_HOSTS=Object.freeze(['claude-code','codex','antigravity']);
9
+
10
+ /**
11
+ * `size`: the transcript's size now, when the caller knows it. A file already read to that size is left as it is
12
+ * (a pass over every Antigravity conversation would otherwise mark each one pending again).
13
+ */
14
+ export function queueMemoryCapture({host,sessionId,filePath,project,cwd,size=null}) {
15
+ if(!filePath || !sessionId || !CAPTURE_HOSTS.includes(host))return;
16
+ if(size!=null) {
17
+ const row=getDb().prepare('SELECT file_path,byte_offset,status,cwd FROM memory_capture_files WHERE host=? AND session_id=?').get(host,sessionId);
18
+ if(row && row.status==='complete' && row.file_path===filePath && Number(row.byte_offset)===Number(size) && (row.cwd || null)===(cwd || null))return;
19
+ }
20
+ // An Antigravity conversation whose folder agy named only later is read again from its start: its events take the
21
+ // project (each step keeps its source id, so nothing is stored twice).
9
22
  getDb().prepare(`INSERT INTO memory_capture_files(host,session_id,file_path,project,cwd) VALUES(?,?,?,?,?)
10
- ON CONFLICT(host,session_id) DO UPDATE SET file_path=excluded.file_path,project=excluded.project,cwd=excluded.cwd,status='pending',error=NULL`)
23
+ ON CONFLICT(host,session_id) DO UPDATE SET file_path=excluded.file_path,project=excluded.project,cwd=excluded.cwd,status='pending',error=NULL,
24
+ byte_offset=CASE WHEN excluded.host='antigravity' AND memory_capture_files.cwd IS NOT excluded.cwd THEN 0 ELSE memory_capture_files.byte_offset END`)
11
25
  .run(host,sessionId,filePath,cwd ? detectProject(cwd) : project || 'global',cwd || null);
12
26
  }
13
27
 
@@ -47,7 +47,8 @@ export function isMoreLoginInstalled() {
47
47
  } else if (process.platform === 'win32') {
48
48
  const { LOCALAPPDATA, ProgramFiles } = process.env;
49
49
  const PF86 = process.env['ProgramFiles(x86)'];
50
- if (LOCALAPPDATA) candidates.push(`${LOCALAPPDATA}\\Programs\\MoreLogin\\MoreLogin.exe`, `${LOCALAPPDATA}\\MoreLogin\\MoreLogin.exe`);
50
+ if (process.env.MORELOGIN_APP_PATH) candidates.push(process.env.MORELOGIN_APP_PATH);
51
+ if (LOCALAPPDATA) candidates.push(`${LOCALAPPDATA}\\Programs\\MoreLogin\\MoreLogin.exe`, `${LOCALAPPDATA}\\Programs\\morelogin\\MoreLogin.exe`, `${LOCALAPPDATA}\\MoreLogin\\MoreLogin.exe`);
51
52
  if (ProgramFiles) candidates.push(`${ProgramFiles}\\MoreLogin\\MoreLogin.exe`);
52
53
  if (PF86) candidates.push(`${PF86}\\MoreLogin\\MoreLogin.exe`);
53
54
  } else {
@@ -100,40 +101,147 @@ function buildHeaders(opts = {}) {
100
101
 
101
102
  /**
102
103
  * Low-level POST to the MoreLogin local API. Returns the `data` payload.
103
- * Throws with a `.code` of MORELOGIN_UNREACHABLE | MORELOGIN_HTTP | MORELOGIN_API.
104
+ * Throws with a `.code` of MORELOGIN_UNREACHABLE | MORELOGIN_HTTP | MORELOGIN_API | MORELOGIN_SHAPE
105
+ * (answered 2xx with a body that is not JSON).
106
+ */
107
+ // Loopback hosts to try, in order. MoreLogin's local server may listen on IPv6 only
108
+ // (an Electron server bound to "localhost" resolves to ::1 first on Windows), so a
109
+ // refused 127.0.0.1 is retried on [::1]. The host that answered is remembered.
110
+ const LOOPBACK_HOSTS = ['127.0.0.1', '[::1]'];
111
+ let _preferredHost = LOOPBACK_HOSTS[0];
112
+ let _lastAnswerAt = 0; // when the local API last answered anything (any status, any body)
113
+ export function _resetMoreLoginHostForTests() { _preferredHost = LOOPBACK_HOSTS[0]; _lastAnswerAt = 0; }
114
+ export function _preferredMoreLoginHostForTests() { return _preferredHost; }
115
+
116
+ /** Whether the local API answered within the last `withinMs` (no request is made). */
117
+ export function moreLoginApiAnsweredRecently(withinMs = 120000) {
118
+ return _lastAnswerAt > 0 && Date.now() - _lastAnswerAt <= withinMs;
119
+ }
120
+
121
+ /**
122
+ * Cheap, synchronous "is MoreLogin here": the exe is in a known folder (or
123
+ * MORELOGIN_APP_PATH) or its local API answered recently. Callers that need a
124
+ * fresh answer probe first (probeMoreLoginApi).
125
+ */
126
+ export function isMoreLoginPresent(withinMs = 120000) {
127
+ let exe = false;
128
+ try { exe = isMoreLoginInstalled().installed; } catch { exe = false; }
129
+ return exe || moreLoginApiAnsweredRecently(withinMs);
130
+ }
131
+
132
+ // Error codes that prove the connection never opened, so the request was never
133
+ // delivered and the other loopback host may be tried even for a call that changes
134
+ // state (create, start). Anything else (a reset after sending, a malformed answer)
135
+ // may have reached MoreLogin: it is thrown, never sent a second time.
136
+ const NEVER_CONNECTED_CODES = new Set([
137
+ 'ECONNREFUSED', 'EHOSTUNREACH', 'ENETUNREACH', 'EADDRNOTAVAIL', 'EAFNOSUPPORT', 'ENOTFOUND',
138
+ 'EAI_AGAIN', 'UND_ERR_CONNECT_TIMEOUT', 'UND_ERR_CONNECT',
139
+ ]);
140
+ export function _neverConnected(err) {
141
+ for (let e = err, i = 0; e && i < 4; e = e.cause, i++) {
142
+ if (NEVER_CONNECTED_CODES.has(e.code)) return true;
143
+ if (Array.isArray(e.errors) && e.errors.some(x => NEVER_CONNECTED_CODES.has(x?.code))) return true;
144
+ }
145
+ return false;
146
+ }
147
+
148
+ /**
149
+ * One deadline for the whole call (opts.timeout, default 15 s): each loopback host
150
+ * gets what is left of it, so two hosts never double the budget. The other host is
151
+ * tried while time remains when the first one provably never connected; a timeout
152
+ * is tried on the other host only for a read (opts.idempotent), since a timed-out
153
+ * write may have been received.
104
154
  */
105
155
  async function request(path, body, opts = {}) {
106
156
  const port = opts.port || getMoreLoginPort(opts.savedCfg);
107
- const url = `http://127.0.0.1:${port}${path}`;
157
+ const fetchImpl = opts.fetch || fetch;
158
+ const idempotent = opts.idempotent ?? (path === '/api/env/page' || path === '/api/env/status');
159
+ const budget = opts.timeout || 15000;
160
+ const deadline = Date.now() + budget;
161
+ const hosts = [_preferredHost, ...LOOPBACK_HOSTS.filter(h => h !== _preferredHost)];
108
162
  let res;
109
- try {
110
- res = await fetch(url, {
111
- method: 'POST',
112
- headers: buildHeaders(opts),
113
- body: JSON.stringify(body || {}),
114
- signal: AbortSignal.timeout(opts.timeout || 15000),
115
- });
116
- } catch (err) {
117
- const e = new Error(`MoreLogin API unreachable on :${port} (${err?.message || err})`);
163
+ let lastErr = null;
164
+ for (const host of hosts) {
165
+ const left = deadline - Date.now();
166
+ if (left <= 0) break;
167
+ try {
168
+ res = await fetchImpl(`http://${host}:${port}${path}`, {
169
+ method: 'POST',
170
+ headers: buildHeaders(opts),
171
+ body: JSON.stringify(body || {}),
172
+ signal: AbortSignal.timeout(left),
173
+ });
174
+ _preferredHost = host;
175
+ _lastAnswerAt = Date.now();
176
+ break;
177
+ } catch (err) {
178
+ lastErr = err;
179
+ const timedOut = err?.name === 'TimeoutError' || err?.name === 'AbortError';
180
+ const retry = _neverConnected(err) || (timedOut && idempotent);
181
+ if (!retry) break;
182
+ }
183
+ }
184
+ if (!res) {
185
+ const err = lastErr;
186
+ const reason = !err ? `no time left of ${budget} ms` : (err?.cause?.code || err?.code || err?.message || err);
187
+ const e = new Error(`MoreLogin API unreachable on :${port} (${reason})`);
118
188
  e.code = 'MORELOGIN_UNREACHABLE';
119
189
  throw e;
120
190
  }
121
191
  let json = null;
192
+ let unparsable = false;
122
193
  try { json = await res.json(); }
123
- catch { try { await res.body?.cancel(); } catch { /* ignore */ } }
194
+ catch { unparsable = true; try { await res.body?.cancel(); } catch { /* ignore */ } }
124
195
  if (!res.ok) {
125
- const e = new Error(`MoreLogin API ${path} → HTTP ${res.status}`);
196
+ const detail = json && (json.msg || json.message);
197
+ const e = new Error(`MoreLogin API ${path} → HTTP ${res.status}${detail ? ` (${detail})` : ''}${res.status === 401 || res.status === 403 ? ' (set the API ID and API Key from MoreLogin → Settings → API in SynaBun Settings → MoreLogin)' : ''}`);
126
198
  e.code = 'MORELOGIN_HTTP';
127
199
  e.status = res.status;
128
200
  throw e;
129
201
  }
130
202
  if (json && json.code !== undefined && json.code !== 0) {
131
- const e = new Error(`MoreLogin API ${path}: ${json.msg || `code ${json.code}`}`);
203
+ const msg = json.msg || json.message || `code ${json.code}`;
204
+ const e = new Error(`MoreLogin API ${path}: ${msg}${looksLikeAuthError(json.code, msg) ? ' (set the API ID and API Key from MoreLogin → Settings → API in SynaBun Settings → MoreLogin)' : ''}`);
132
205
  e.code = 'MORELOGIN_API';
133
206
  e.apiCode = json.code;
134
207
  throw e;
135
208
  }
136
- return json ? json.data : null;
209
+ if (unparsable || !json || typeof json !== 'object') {
210
+ const type = res.headers?.get?.('content-type') || 'no content type';
211
+ const e = new Error(`MoreLogin API ${path} answered HTTP ${res.status} with a body that is not JSON (${type}); is something else listening on port ${port}?`);
212
+ e.code = 'MORELOGIN_SHAPE';
213
+ throw e;
214
+ }
215
+ return json.data;
216
+ }
217
+
218
+ function looksLikeAuthError(code, msg) {
219
+ return /auth|sign|api.?key|api.?id|nonce|unauthori[sz]ed|forbidden|permission|token/i.test(String(msg || ''))
220
+ || code === 401 || code === 403;
221
+ }
222
+
223
+ const PROFILE_LIST_KEYS = ['dataList', 'list', 'records', 'rows'];
224
+
225
+ /** Profile rows across response shapes: data.dataList | data.list | data.records | data.rows | data (array). A non-empty array wins over an empty one. */
226
+ export function profileRows(data) {
227
+ if (Array.isArray(data)) return data;
228
+ if (!data || typeof data !== 'object') return [];
229
+ const arrays = PROFILE_LIST_KEYS.map(k => data[k]).filter(Array.isArray);
230
+ return arrays.find(a => a.length) || arrays[0] || [];
231
+ }
232
+
233
+ /** Whether a profile-page answer has a shape SynaBun reads (an array, a known list key, or a total). */
234
+ export function hasProfileShape(data) {
235
+ if (Array.isArray(data)) return true;
236
+ if (!data || typeof data !== 'object') return false;
237
+ return PROFILE_LIST_KEYS.some(k => Array.isArray(data[k])) || (data.total != null && Number.isFinite(Number(data.total)));
238
+ }
239
+
240
+ function shapeError(data) {
241
+ const keys = data && typeof data === 'object' ? Object.keys(data).slice(0, 8).join(', ') : typeof data;
242
+ const e = new Error(`MoreLogin API /api/env/page answered in a shape SynaBun does not read (data: ${keys || 'empty'}); a newer MoreLogin may have changed its API`);
243
+ e.code = 'MORELOGIN_SHAPE';
244
+ return e;
137
245
  }
138
246
 
139
247
  /**
@@ -171,19 +279,29 @@ function normalizeProfile(e = {}) {
171
279
  /** List browser profiles (environments). Returns normalized rows. */
172
280
  export async function listProfiles(opts = {}) {
173
281
  const data = await request('/api/env/page', { pageNo: opts.pageNo || 1, pageSize: opts.pageSize || 100 }, opts);
174
- const list = (data && (data.dataList || data.list)) || [];
175
- return list.map(normalizeProfile).filter(p => p.id);
282
+ if (!hasProfileShape(data)) throw shapeError(data);
283
+ return profileRows(data).map(normalizeProfile).filter(p => p.id);
176
284
  }
177
285
 
178
- /** Lightweight reachability probe. Never throws — returns {running, count}. */
286
+ /**
287
+ * Lightweight reachability probe. Never throws — returns {running, count}.
288
+ * `running` means the local API answered at all: an answer that is an HTTP or API
289
+ * error (signature required, API disabled, ...) still proves MoreLogin is up, and its
290
+ * reason is returned in `apiError` instead of being reported as "not running".
291
+ */
179
292
  export async function probeMoreLoginApi(opts = {}) {
180
293
  try {
181
294
  const data = await request('/api/env/page', { pageNo: 1, pageSize: 1 }, { ...opts, timeout: opts.timeout || 4000 });
182
- const total = data && data.total != null ? Number(data.total)
183
- : (data && (data.dataList || data.list) ? (data.dataList || data.list).length : 0);
295
+ if (!hasProfileShape(data)) throw shapeError(data);
296
+ const total = data && !Array.isArray(data) && data.total != null ? Number(data.total) : profileRows(data).length;
184
297
  return { running: true, count: Number.isFinite(total) ? total : 0 };
185
298
  } catch (err) {
186
- return { running: false, count: 0, error: err?.message || String(err), errCode: err?.code || null };
299
+ const answered = err?.code === 'MORELOGIN_HTTP' || err?.code === 'MORELOGIN_API' || err?.code === 'MORELOGIN_SHAPE';
300
+ return {
301
+ running: answered, count: 0,
302
+ error: err?.message || String(err), errCode: err?.code || null,
303
+ ...(answered ? { apiError: err?.message || String(err) } : {}),
304
+ };
187
305
  }
188
306
  }
189
307
 
@@ -221,13 +339,17 @@ export async function getMoreLoginStatus(opts = {}) {
221
339
  const port = getMoreLoginPort(opts.savedCfg);
222
340
  const probe = await probeMoreLoginApi({ ...opts, port });
223
341
  return {
224
- installed: inst.installed,
342
+ // An answering local API is proof of an install, wherever it lives (a custom
343
+ // install folder on Windows is not among the known paths).
344
+ installed: inst.installed || probe.running,
225
345
  appPath: inst.path,
226
346
  port,
227
347
  running: probe.running,
348
+ apiError: probe.apiError || null,
228
349
  profileCount: probe.count || 0,
229
350
  signed: !!((opts.appId || process.env.MORELOGIN_API_ID) && (opts.secretKey || process.env.MORELOGIN_SECRET)),
230
- error: probe.error || null,
351
+ // Transport failures only: an API that answered with an error is running, and says why in apiError.
352
+ error: probe.running ? null : (probe.error || null),
231
353
  };
232
354
  }
233
355
 
@@ -259,7 +381,7 @@ export async function startProfile(envId, opts = {}) {
259
381
  try {
260
382
  data = await request('/api/env/start', { envId: String(envId), ...(opts.startArgs || {}) }, opts);
261
383
  } catch (err) {
262
- if (err?.code === 'MORELOGIN_UNREACHABLE' || err?.code === 'MORELOGIN_HTTP') throw err;
384
+ if (err?.code === 'MORELOGIN_UNREACHABLE' || err?.code === 'MORELOGIN_HTTP' || err?.code === 'MORELOGIN_SHAPE') throw err;
263
385
  // MORELOGIN_API (e.g. "already running") → fall through to status recovery.
264
386
  }
265
387
  let port = extractDebugPort(data);