@stage-labs/metro 0.1.0-beta.15 → 0.1.0-beta.151

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 (327) hide show
  1. package/dist/api.js +9 -0
  2. package/dist/claude-settings.js +40 -0
  3. package/dist/claude.js +182 -0
  4. package/dist/cli.js +86 -0
  5. package/dist/control.js +74 -0
  6. package/dist/hold.js +211 -0
  7. package/dist/local.js +77 -0
  8. package/dist/mcp-config.js +25 -0
  9. package/dist/node-name.js +71 -0
  10. package/dist/onboarding.js +43 -0
  11. package/dist/provider-flags.js +9 -0
  12. package/dist/route.js +50 -0
  13. package/dist/runtime-install.js +97 -0
  14. package/dist/runtime.js +74 -0
  15. package/dist/serve.js +155 -0
  16. package/dist/service.js +242 -0
  17. package/dist/tail.js +119 -0
  18. package/dist/update.js +45 -0
  19. package/dist/version.js +97 -0
  20. package/package.json +14 -28
  21. package/runtime/marketplace/.claude-plugin/marketplace.json +12 -0
  22. package/runtime/marketplace/plugin/.claude-plugin/plugin.json +18 -0
  23. package/runtime/marketplace/plugin/.mcp.json +1 -0
  24. package/runtime/marketplace/plugin/bin/guard.mjs +55 -0
  25. package/runtime/marketplace/plugin/bin/metro-plugin.mjs +40 -0
  26. package/runtime/marketplace/plugin/bin/session-start.mjs +31 -0
  27. package/runtime/marketplace/plugin/hooks/hooks.json +25 -0
  28. package/runtime/marketplace/plugin/orchestrator.md +16 -0
  29. package/runtime/node_modules/@metro-labs/core/package.json +43 -0
  30. package/runtime/node_modules/@metro-labs/core/src/address.ts +6 -0
  31. package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
  32. package/runtime/node_modules/@metro-labs/core/src/events.ts +120 -0
  33. package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
  34. package/runtime/node_modules/@metro-labs/core/src/ids.ts +21 -0
  35. package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
  36. package/runtime/node_modules/@metro-labs/core/src/lines.ts +87 -0
  37. package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
  38. package/runtime/node_modules/@metro-labs/core/src/protocol.ts +226 -0
  39. package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +57 -0
  40. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +13 -0
  41. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
  42. package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
  43. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
  44. package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +64 -0
  45. package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +108 -0
  46. package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
  47. package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
  48. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  49. package/runtime/node_modules/@metro-labs/daemon/package.json +47 -0
  50. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  51. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-routes.ts +70 -0
  52. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +365 -0
  53. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +39 -0
  54. package/runtime/node_modules/@metro-labs/daemon/src/agents/allowlist.ts +37 -0
  55. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +225 -0
  56. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +122 -0
  57. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +364 -0
  58. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +185 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +40 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +95 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/agents/senders.ts +38 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +200 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +30 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +380 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +354 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +358 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +169 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/claude/onboarding.ts +56 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/claude/plugin-install.ts +140 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/claude/session-files.ts +90 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/claude/session.ts +253 -0
  82. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
  83. package/runtime/node_modules/@metro-labs/daemon/src/claude/setup.ts +263 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +118 -0
  85. package/runtime/node_modules/@metro-labs/daemon/src/claude/version.ts +109 -0
  86. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +313 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +157 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/connectors/health.ts +18 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +69 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +166 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +139 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
  93. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +171 -0
  94. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +155 -0
  95. package/runtime/node_modules/@metro-labs/daemon/src/connectors/plugin-sync.ts +122 -0
  96. package/runtime/node_modules/@metro-labs/daemon/src/connectors/reach.ts +27 -0
  97. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
  98. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +304 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +299 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +108 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +59 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +101 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +259 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +288 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +231 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  115. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +187 -0
  116. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +247 -0
  117. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-auth.ts +182 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-client.ts +46 -0
  119. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-setup.ts +150 -0
  120. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-stream.ts +148 -0
  121. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini-translate.ts +201 -0
  122. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gemini.ts +257 -0
  123. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +353 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +232 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +90 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/gateway/provider-models.ts +65 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/gateway/usage.ts +242 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +151 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
  134. package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
  135. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
  136. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +186 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
  144. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
  145. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
  146. package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
  147. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
  148. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
  149. package/runtime/node_modules/@metro-labs/daemon/src/routes/bearer.ts +35 -0
  150. package/runtime/node_modules/@metro-labs/daemon/src/routes/body.ts +24 -0
  151. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +370 -0
  152. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +210 -0
  153. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +58 -0
  154. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  155. package/runtime/node_modules/@metro-labs/daemon/src/routes/threema-callback.ts +103 -0
  156. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +65 -0
  157. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +80 -0
  158. package/runtime/node_modules/@metro-labs/daemon/src/server/owner.ts +48 -0
  159. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +117 -0
  160. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  161. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
  162. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
  163. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
  164. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
  165. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
  166. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +248 -0
  167. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +261 -0
  168. package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +37 -0
  169. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  170. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
  171. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
  172. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +32 -0
  173. package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
  174. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +97 -0
  175. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
  176. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
  177. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  178. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  179. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  180. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  181. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  182. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  183. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  184. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  185. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  186. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  187. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  188. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  189. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  190. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  191. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  192. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  193. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  194. package/runtime/node_modules/@metro-labs/http/package.json +29 -0
  195. package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
  196. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +106 -0
  197. package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
  198. package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
  199. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  200. package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +150 -0
  201. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  202. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  203. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  204. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  205. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  206. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  207. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  208. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  209. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  210. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  211. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  212. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  213. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  214. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  215. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  216. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  217. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  218. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  219. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  220. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  221. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  222. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  223. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  224. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  225. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  226. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  227. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  228. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  229. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  230. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  231. package/runtime/node_modules/@metro-labs/threema/package.json +26 -0
  232. package/runtime/node_modules/@metro-labs/threema/src/accounts.ts +100 -0
  233. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +223 -0
  234. package/runtime/node_modules/@metro-labs/threema/src/api.ts +130 -0
  235. package/runtime/node_modules/@metro-labs/threema/src/crypto.ts +132 -0
  236. package/runtime/node_modules/@metro-labs/threema/src/format.ts +123 -0
  237. package/runtime/node_modules/@metro-labs/threema/src/ids.ts +21 -0
  238. package/runtime/node_modules/@metro-labs/threema/src/index.ts +29 -0
  239. package/runtime/node_modules/@metro-labs/threema/src/station.ts +10 -0
  240. package/runtime/node_modules/@metro-labs/threema/src/verify.ts +117 -0
  241. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  242. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  243. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  244. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  245. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  246. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +190 -0
  247. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  248. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  249. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +347 -0
  250. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  251. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  252. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  253. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  254. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  255. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  256. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  257. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  258. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  259. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  260. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  261. package/runtime/node_modules/@metro-labs/whatsapp/src/resolve.ts +52 -0
  262. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +18 -0
  263. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  264. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  265. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  266. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  267. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  268. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  269. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  270. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  271. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  272. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  273. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  274. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  275. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  276. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  277. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  278. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  279. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  280. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  281. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  282. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  283. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  284. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  285. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  286. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  287. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  288. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  289. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  290. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  291. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  292. package/runtime/runtime.json +3 -0
  293. package/runtime/server.ts +1 -0
  294. package/runtime/stations.json +37 -0
  295. package/runtime/trains/.keep +0 -0
  296. package/LICENSE +0 -21
  297. package/README.md +0 -139
  298. package/dist/broker/claims.js +0 -144
  299. package/dist/broker/history-stream.js +0 -149
  300. package/dist/cli/config.js +0 -179
  301. package/dist/cli/index.js +0 -175
  302. package/dist/cli/messenger-api.js +0 -214
  303. package/dist/cli/messenger-transcribe.js +0 -43
  304. package/dist/cli/messenger-uploads.js +0 -116
  305. package/dist/cli/monitor-api.js +0 -205
  306. package/dist/cli/tail.js +0 -95
  307. package/dist/cli/util.js +0 -72
  308. package/dist/cli/webhook.js +0 -181
  309. package/dist/codex-rc/client.js +0 -225
  310. package/dist/codex-rc/protocol.js +0 -38
  311. package/dist/dispatcher/server.js +0 -122
  312. package/dist/dispatcher.js +0 -97
  313. package/dist/history.js +0 -143
  314. package/dist/ipc.js +0 -90
  315. package/dist/lines.js +0 -54
  316. package/dist/local-identity.js +0 -80
  317. package/dist/log.js +0 -15
  318. package/dist/paths.js +0 -114
  319. package/dist/trains/protocol.js +0 -99
  320. package/dist/trains/supervisor.js +0 -210
  321. package/dist/tunnel.js +0 -102
  322. package/docs/broker.md +0 -166
  323. package/docs/monitor.md +0 -241
  324. package/docs/uri-scheme.md +0 -127
  325. package/examples/README.md +0 -32
  326. package/examples/telegram.ts +0 -121
  327. package/skills/metro/SKILL.md +0 -110
@@ -0,0 +1,75 @@
1
+ import { str } from '@metro-labs/core/str';
2
+
3
+ export interface PendingAtt {
4
+ kind?: string;
5
+ name?: string;
6
+ }
7
+
8
+ export interface PendingMsg {
9
+ line: string;
10
+ from: string;
11
+ station: string;
12
+ text: string;
13
+ ts: string;
14
+ messageId: string;
15
+ lineName: string;
16
+ fromName: string;
17
+ fromDisplayName: string;
18
+ attachments: PendingAtt[];
19
+ saved: Set<number>;
20
+ timer: ReturnType<typeof setTimeout>;
21
+ }
22
+
23
+ export interface MediaCtx {
24
+ line: string;
25
+ from: string;
26
+ station: string;
27
+ text?: string;
28
+ ts?: string;
29
+ messageId?: string;
30
+ lineName?: string;
31
+ fromName?: string;
32
+ fromDisplayName?: string;
33
+ }
34
+
35
+ export function capSet(set: Set<string>, max: number): void {
36
+ while (set.size > max) {
37
+ const oldest = set.values().next();
38
+ if (oldest.done) break;
39
+ set.delete(oldest.value);
40
+ }
41
+ }
42
+
43
+ export const tsMeta = (v: unknown): Record<string, string> => {
44
+ const ts = str(v);
45
+ return ts ? { ts } : {};
46
+ };
47
+
48
+ export const displayNameMeta = (v: unknown): Record<string, string> => {
49
+ const name = str(v);
50
+ return name ? { from_display_name: name } : {};
51
+ };
52
+
53
+ export const senderMeta = (c: MediaCtx): Record<string, string> => ({
54
+ ...tsMeta(c.ts),
55
+ ...(c.messageId ? { message_id: c.messageId } : {}),
56
+ ...(c.lineName ? { line_name: c.lineName } : {}),
57
+ ...(c.fromName ? { from_name: c.fromName } : {}),
58
+ ...displayNameMeta(c.fromDisplayName),
59
+ });
60
+
61
+ export function takeMediaCtx(buf: PendingMsg): MediaCtx {
62
+ const ctx: MediaCtx = {
63
+ line: buf.line,
64
+ from: buf.from,
65
+ station: buf.station,
66
+ text: buf.text,
67
+ ts: buf.ts,
68
+ messageId: buf.messageId,
69
+ lineName: buf.lineName,
70
+ fromName: buf.fromName,
71
+ fromDisplayName: buf.fromDisplayName,
72
+ };
73
+ buf.text = '';
74
+ return ctx;
75
+ }
@@ -0,0 +1,129 @@
1
+ import {
2
+ bufferedSince,
3
+ currentBusSeq,
4
+ subscribeEvents,
5
+ type MetroEvent,
6
+ } from '@metro-labs/core/events';
7
+ import type { InboundRelay } from './inbound.js';
8
+
9
+ export interface ReplayLedger {
10
+ startAt: number;
11
+ delivered: Set<number>;
12
+ }
13
+
14
+ export const newReplayLedger = (startAt = -1): ReplayLedger => ({
15
+ startAt,
16
+ delivered: new Set<number>(),
17
+ });
18
+
19
+ interface ChannelRelayDeps {
20
+ relay: InboundRelay;
21
+ log: (...a: unknown[]) => void;
22
+ inScope: (line: string) => boolean;
23
+ ledger?: ReplayLedger;
24
+ }
25
+
26
+ const PENDING_MAX = 2000;
27
+
28
+ export class ChannelRelay {
29
+ private readonly deps: ChannelRelayDeps;
30
+ private readonly ledger: ReplayLedger;
31
+ private chain: Promise<void> = Promise.resolve();
32
+
33
+ constructor(deps: ChannelRelayDeps) {
34
+ this.deps = deps;
35
+ this.ledger = deps.ledger ?? newReplayLedger();
36
+ }
37
+
38
+ start(): () => void {
39
+ if (this.ledger.startAt < 0) this.ledger.startAt = currentBusSeq();
40
+ return subscribeEvents((event, busSeq) => {
41
+ this.enqueue(event, busSeq, false);
42
+ });
43
+ }
44
+
45
+ replayMissed(): void {
46
+ const missed = bufferedSince(this.ledger.startAt).filter(
47
+ (b) => !this.ledger.delivered.has(b.busSeq),
48
+ );
49
+ const first = missed.at(0);
50
+ const last = missed.at(-1);
51
+ if (!first || !last) return;
52
+ this.deps.log(
53
+ 'relay: replay',
54
+ `(${missed.length} events)`,
55
+ 'range',
56
+ `${first.busSeq}..${last.busSeq}`,
57
+ );
58
+ for (const b of missed) this.enqueue(b.event, b.busSeq, true);
59
+ }
60
+
61
+ private enqueue(event: MetroEvent, busSeq: number, replay: boolean): void {
62
+ const { delivered } = this.ledger;
63
+ if (delivered.has(busSeq)) return;
64
+ delivered.add(busSeq);
65
+ if (delivered.size > PENDING_MAX) {
66
+ const cutoff = busSeq - PENDING_MAX;
67
+ for (const s of delivered) if (s <= cutoff) delivered.delete(s);
68
+ }
69
+ this.chain = this.chain.then(() =>
70
+ this.deliver(event, busSeq, replay).catch((err: unknown) => {
71
+ this.deps.log(
72
+ 'relay: delivery step failed; chain kept alive',
73
+ 'busSeq',
74
+ busSeq,
75
+ 'line',
76
+ event.line,
77
+ err,
78
+ );
79
+ }),
80
+ );
81
+ }
82
+
83
+ private withhold(event: MetroEvent, busSeq: number): void {
84
+ this.ledger.delivered.delete(busSeq);
85
+ this.deps.log(
86
+ 'relay: withheld (line outside the channel session scope)',
87
+ 'busSeq',
88
+ busSeq,
89
+ 'line',
90
+ event.line,
91
+ );
92
+ }
93
+
94
+ private async deliver(
95
+ event: MetroEvent,
96
+ busSeq: number,
97
+ replay: boolean,
98
+ ): Promise<void> {
99
+ if (!this.deps.inScope(event.line)) {
100
+ this.withhold(event, busSeq);
101
+ return;
102
+ }
103
+ this.deps.log(
104
+ 'relay: notify',
105
+ 'busSeq',
106
+ busSeq,
107
+ 'id',
108
+ event.id,
109
+ 'replay',
110
+ replay,
111
+ );
112
+ try {
113
+ await this.deps.relay.handleEvent(
114
+ event as unknown as Record<string, unknown>,
115
+ replay,
116
+ );
117
+ } catch (err) {
118
+ this.ledger.delivered.delete(busSeq);
119
+ this.deps.log(
120
+ 'channel delivery failed; bounded replay on reconnect',
121
+ 'busSeq',
122
+ busSeq,
123
+ 'line',
124
+ event.line,
125
+ err,
126
+ );
127
+ }
128
+ }
129
+ }
@@ -0,0 +1,51 @@
1
+ const MAX_BODY_CHARS = 8 * 1024;
2
+
3
+ const HEADERS_KEPT = [
4
+ 'x-github-event',
5
+ 'x-github-delivery',
6
+ 'x-intercom-topic',
7
+ 'x-request-id',
8
+ 'user-agent',
9
+ ];
10
+
11
+ interface WebhookPayload {
12
+ headers?: Record<string, string>;
13
+ body?: unknown;
14
+ }
15
+
16
+ function renderBody(body: unknown): string {
17
+ if (body === undefined || body === null) return '';
18
+ const text =
19
+ typeof body === 'string' ? body : JSON.stringify(body, null, 2);
20
+ if (text.length <= MAX_BODY_CHARS) return text;
21
+ return (
22
+ text.slice(0, MAX_BODY_CHARS) +
23
+ `\n[truncated: ${String(text.length - MAX_BODY_CHARS)} more characters]`
24
+ );
25
+ }
26
+
27
+ function renderHeaders(headers: Record<string, string> | undefined): string {
28
+ if (!headers) return '';
29
+ const kept = HEADERS_KEPT.flatMap((k) =>
30
+ headers[k] === undefined ? [] : [`${k}: ${headers[k]}`],
31
+ );
32
+ return kept.length === 0 ? '' : kept.join('\n') + '\n';
33
+ }
34
+
35
+ export function buildWebhookNote(
36
+ summary: string,
37
+ lineName: string | undefined,
38
+ payload: unknown,
39
+ ): string {
40
+ const p = (
41
+ payload !== null && typeof payload === 'object' ? payload : {}
42
+ ) as WebhookPayload;
43
+ const label = lineName ? `${lineName}: ` : '';
44
+ const body = renderBody(p.body);
45
+ return (
46
+ `[webhook received] ${label}${summary}\n` +
47
+ renderHeaders(p.headers) +
48
+ (body ? `\n${body}\n` : '') +
49
+ 'Inbound only: this line takes no send, reply or react.'
50
+ );
51
+ }
@@ -0,0 +1,354 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { errMsg, log } from '@metro-labs/core/log';
3
+ import { apiFailure, apiSession, requireAdmin, cors, readJsonBody, sendJson } from '@metro-labs/http/api-http';
4
+ import { ApiError } from '@metro-labs/http/api-error';
5
+ import { isRecord } from '@metro-labs/core/is-record';
6
+ import { listClaudeSettings, SETTINGS_MAX, writeClaudeSettings } from './settings.js';
7
+ import {
8
+ createClaudeSkill,
9
+ deleteClaudeSkill,
10
+ listClaudeSkills,
11
+ readClaudeSkill,
12
+ writeClaudeSkill,
13
+ } from './skills.js';
14
+ import {
15
+ answerClaudeLogin,
16
+ claudeAccount,
17
+ claudeInstalled,
18
+ claudeLoginView,
19
+ endClaudeLogin,
20
+ startClaudeLogin,
21
+ type LoginDeps,
22
+ } from './login.js';
23
+ import {
24
+ claudeSetupStatus,
25
+ ensureClaudeSetup,
26
+ isPermissionMode,
27
+ permissionMode,
28
+ setPermissionMode,
29
+ setPrivacy,
30
+ setSystemPrompt,
31
+ systemPrompt,
32
+ SYSTEM_PROMPT_MAX,
33
+ type PermissionMode,
34
+ type SetupDeps,
35
+ } from './setup.js';
36
+ import {
37
+ ensureSession,
38
+ sessionRunning,
39
+ sessionStatus,
40
+ setAutostart,
41
+ startSession,
42
+ stopSession,
43
+ type SessionDeps,
44
+ } from './session.js';
45
+ import { claudeVersion, updateClaude, type VersionDeps } from './version.js';
46
+ import { readSessionFile, receiveSessionFile, SESSION_FILE_MAX, sessionFilePath, writeSessionFile } from './session-files.js';
47
+ import { createReadStream } from 'node:fs';
48
+ import { pipeline } from 'node:stream/promises';
49
+ import {
50
+ claudeDir,
51
+ deleteClaudeSession,
52
+ listClaudeProjects,
53
+ listClaudeSessions,
54
+ listMemory,
55
+ readMemoryFile,
56
+ readTranscript,
57
+ writeMemoryFile,
58
+ } from './files.js';
59
+
60
+ const PREFIX = '/api/claude';
61
+ const BODY_MAX = SETTINGS_MAX + 4096;
62
+ const SESSION_BODY_MAX = SESSION_FILE_MAX + 4096;
63
+ const WRITABLE = new Set(['GET', 'DELETE', 'PUT', 'POST']);
64
+ const PAGE = 100;
65
+ const PAGE_MAX = 500;
66
+
67
+ export interface ClaudeApiDeps {
68
+ authorize: (subject: string) => void;
69
+ dir?: () => string;
70
+ login?: LoginDeps;
71
+ session?: SessionDeps;
72
+ setup?: SetupDeps;
73
+ version?: VersionDeps;
74
+ }
75
+
76
+ function projectOf(query: URLSearchParams): string {
77
+ const project = query.get('project');
78
+ if (project === null || project === '') throw new ApiError('project is required', 400);
79
+ return project;
80
+ }
81
+
82
+ function pageOf(query: URLSearchParams): { offset: number; limit: number } {
83
+ const offset = Math.max(0, Math.floor(Number(query.get('offset') ?? '0')) || 0);
84
+ const limit = Math.min(PAGE_MAX, Math.max(1, Math.floor(Number(query.get('limit') ?? String(PAGE))) || PAGE));
85
+ return { offset, limit };
86
+ }
87
+
88
+ type Handler = (query: URLSearchParams, dir: string, item: string) => unknown;
89
+
90
+ const COLLECTIONS: Record<string, Handler> = {
91
+ projects: (_query, dir) => ({ projects: listClaudeProjects(dir) }),
92
+ sessions: (query, dir) => ({ sessions: listClaudeSessions(projectOf(query), dir) }),
93
+ memory: (query, dir) => listMemory(projectOf(query), dir),
94
+ settings: (_query, dir) => ({ files: listClaudeSettings(dir) }),
95
+ skills: (_query, dir) => ({ skills: listClaudeSkills(dir) }),
96
+ };
97
+
98
+ const ITEMS: Record<string, Handler> = {
99
+ sessions: (query, dir, id) => {
100
+ if (query.get('raw') === '1') return { id, text: readSessionFile(projectOf(query), id, dir) };
101
+ const { offset, limit } = pageOf(query);
102
+ return readTranscript(projectOf(query), id, offset, limit, dir);
103
+ },
104
+ memory: (query, dir, name) => ({ name, content: readMemoryFile(projectOf(query), name, dir) }),
105
+ skills: (_query, dir, id) => readClaudeSkill(decodeURIComponent(id), dir),
106
+ };
107
+
108
+ const parts = (path: string): string[] => path.slice(PREFIX.length + 1).split('/').filter(Boolean);
109
+
110
+ const seenIn = (body: Record<string, unknown>): string | null | undefined =>
111
+ 'seenAt' in body ? (typeof body.seenAt === 'string' ? body.seenAt : null) : undefined;
112
+
113
+ const WRITE_HEADS = new Set(['settings', 'skills', 'memory', 'sessions']);
114
+
115
+ interface Write {
116
+ text: string;
117
+ seen: string | null | undefined;
118
+ modifiedAt: string | undefined;
119
+ }
120
+
121
+ function writeItem(head: string, item: string, write: Write, search: string, dir: string): unknown {
122
+ if (head === 'skills') return writeClaudeSkill(decodeURIComponent(item), write.text, write.seen, dir);
123
+ if (head === 'sessions') return writeSessionFile(projectOf(new URLSearchParams(search)), item, write.text, dir);
124
+ if (head === 'memory') return writeMemoryFile(projectOf(new URLSearchParams(search)), decodeURIComponent(item), write.text, dir, write.modifiedAt);
125
+ return writeClaudeSettings(item, write.text, write.seen, dir);
126
+ }
127
+
128
+ async function writeAnswer(req: IncomingMessage, path: string, search: string, dir: string): Promise<unknown> {
129
+ const [head = '', item = ''] = parts(path);
130
+ if (!WRITE_HEADS.has(head) || item === '') throw new ApiError('method not allowed', 405);
131
+ const body = await readJsonBody(req, head === 'sessions' ? SESSION_BODY_MAX : BODY_MAX);
132
+ if (!isRecord(body) || typeof body.text !== 'string') throw new ApiError('text is required', 400);
133
+ const modifiedAt = typeof body.modifiedAt === 'string' ? body.modifiedAt : undefined;
134
+ return writeItem(head, item, { text: body.text, seen: seenIn(body), modifiedAt }, search, dir);
135
+ }
136
+
137
+ async function created(req: IncomingMessage, path: string, dir: string): Promise<unknown> {
138
+ const rest = parts(path);
139
+ if (rest.length !== 1 || rest[0] !== 'skills') throw new ApiError('method not allowed', 405);
140
+ const body = await readJsonBody(req, BODY_MAX);
141
+ if (!isRecord(body) || typeof body.name !== 'string') throw new ApiError('name is required', 400);
142
+ return createClaudeSkill(body.name, typeof body.text === 'string' ? body.text : undefined, dir);
143
+ }
144
+
145
+ const ADMIN_ONLY = /^\/(login|session|version|setup)(\/|$)/;
146
+ const LOGIN = 'login';
147
+ const SESSION = 'session';
148
+ const SETUP = 'setup';
149
+ const SETUP_BODY_MAX = SYSTEM_PROMPT_MAX * 2;
150
+ const VERSION = 'version';
151
+
152
+ interface SetupChange {
153
+ privacy?: boolean;
154
+ permissionMode?: PermissionMode;
155
+ systemPrompt?: string;
156
+ }
157
+
158
+ function promptChange(raw: unknown): string | undefined {
159
+ if (raw === undefined) return undefined;
160
+ if (typeof raw !== 'string') throw new ApiError('systemPrompt must be text', 400);
161
+ if (Buffer.byteLength(raw) > SYSTEM_PROMPT_MAX) throw new ApiError(`systemPrompt must be under ${String(SYSTEM_PROMPT_MAX / 1024)} KiB`, 400);
162
+ return raw;
163
+ }
164
+
165
+ function privacyChange(raw: unknown): boolean | undefined {
166
+ if (raw === undefined) return undefined;
167
+ if (typeof raw !== 'boolean') throw new ApiError('privacy must be true or false', 400);
168
+ return raw;
169
+ }
170
+
171
+ function modeChange(raw: unknown): PermissionMode | undefined {
172
+ if (raw === undefined) return undefined;
173
+ if (!isPermissionMode(raw)) throw new ApiError('permissionMode must be auto or bypass', 400);
174
+ return raw;
175
+ }
176
+
177
+ function setupChange(body: unknown): SetupChange {
178
+ if (!isRecord(body)) throw new ApiError('a body is required', 400);
179
+ const privacy = privacyChange(body.privacy);
180
+ const permissionMode = modeChange(body.permissionMode);
181
+ const systemPrompt = promptChange(body.systemPrompt);
182
+ if (privacy === undefined && permissionMode === undefined && systemPrompt === undefined) throw new ApiError('nothing to change', 400);
183
+ return {
184
+ ...(privacy === undefined ? {} : { privacy }),
185
+ ...(permissionMode === undefined ? {} : { permissionMode }),
186
+ ...(systemPrompt === undefined ? {} : { systemPrompt }),
187
+ };
188
+ }
189
+
190
+ function restartsSession(change: SetupChange, agents: string | undefined): boolean {
191
+ let restart = false;
192
+ if (change.permissionMode !== undefined && change.permissionMode !== permissionMode(agents)) {
193
+ setPermissionMode(change.permissionMode, agents);
194
+ restart = true;
195
+ }
196
+ if (change.systemPrompt !== undefined && change.systemPrompt.trim() !== systemPrompt(agents)) {
197
+ setSystemPrompt(change.systemPrompt, agents);
198
+ restart = true;
199
+ }
200
+ return restart;
201
+ }
202
+
203
+ function applySetupChange(change: SetupChange, deps: ClaudeApiDeps): void {
204
+ const setup = deps.setup ?? {};
205
+ if (change.privacy !== undefined) setPrivacy(change.privacy, setup.agents);
206
+ if (restartsSession(change, setup.agents) && sessionRunning(deps.session?.tmux ?? 'tmux')) stopSession(deps.session ?? {});
207
+ ensureClaudeSetup(setup);
208
+ }
209
+
210
+ async function setupAnswer(req: IncomingMessage, deps: ClaudeApiDeps): Promise<unknown> {
211
+ const setup = deps.setup ?? {};
212
+ const method = req.method ?? 'GET';
213
+ if (method === 'GET') return claudeSetupStatus(setup);
214
+ if (method !== 'POST') throw new ApiError('method not allowed', 405);
215
+ applySetupChange(setupChange(await readJsonBody(req, SETUP_BODY_MAX)), deps);
216
+ return claudeSetupStatus(setup);
217
+ }
218
+
219
+ function sessionCommand(body: Record<string, unknown>, session: SessionDeps): unknown {
220
+ if (body.action === 'start') {
221
+ const status = sessionStatus(session);
222
+ if (status.running) return status;
223
+ if (status.blocked !== null) throw new ApiError(`cannot start a Claude session: ${status.blocked}`, 409);
224
+ return startSession(session);
225
+ }
226
+ if (body.action === 'stop') return stopSession(session);
227
+ if (body.action !== undefined) throw new ApiError('action must be start or stop', 400);
228
+ if (body.autostart === true) ensureSession(session);
229
+ return sessionStatus(session);
230
+ }
231
+
232
+ async function sessionAnswer(req: IncomingMessage, deps: ClaudeApiDeps): Promise<unknown> {
233
+ const session = deps.session ?? {};
234
+ const method = req.method ?? 'GET';
235
+ if (method === 'GET') return sessionStatus(session);
236
+ if (method !== 'POST') throw new ApiError('method not allowed', 405);
237
+ const body = await readJsonBody(req);
238
+ if (!isRecord(body)) throw new ApiError('a body is required', 400);
239
+ if (typeof body.autostart === 'boolean') setAutostart(body.autostart, session.agents);
240
+ return sessionCommand(body, session);
241
+ }
242
+
243
+ async function loginAnswer(req: IncomingMessage, id: string, deps: ClaudeApiDeps): Promise<unknown> {
244
+ const method = req.method ?? 'GET';
245
+ if (id === '') {
246
+ if (method === 'POST') return startClaudeLogin(deps.login);
247
+ if (method === 'GET') return { available: claudeInstalled(), ...claudeAccount() };
248
+ throw new ApiError('method not allowed', 405);
249
+ }
250
+ if (method === 'GET') return claudeLoginView(id);
251
+ if (method === 'DELETE') return endClaudeLogin(id);
252
+ if (method !== 'POST') throw new ApiError('method not allowed', 405);
253
+ const body = await readJsonBody(req);
254
+ if (!isRecord(body) || typeof body.text !== 'string') throw new ApiError('text is required', 400);
255
+ return answerClaudeLogin(id, body.text);
256
+ }
257
+
258
+ function removed(rest: string[], query: URLSearchParams, dir: string): unknown {
259
+ const [head = '', item = ''] = rest;
260
+ if (rest.length !== 2) throw new ApiError('method not allowed', 405);
261
+ if (head === 'skills') return { deleted: deleteClaudeSkill(decodeURIComponent(item), dir) };
262
+ if (head !== 'sessions') throw new ApiError('method not allowed', 405);
263
+ deleteClaudeSession(projectOf(query), item, dir);
264
+ return { deleted: item };
265
+ }
266
+
267
+ function answer(method: string, path: string, query: URLSearchParams, dir: string): unknown {
268
+ const rest = parts(path);
269
+ const [head = '', item = ''] = rest;
270
+ if (method === 'DELETE') return removed(rest, query, dir);
271
+ if (method !== 'GET') throw new ApiError('method not allowed', 405);
272
+ const handler = rest.length === 1 ? COLLECTIONS[head] : rest.length === 2 ? ITEMS[head] : undefined;
273
+ if (handler === undefined) throw new ApiError('no such route', 404);
274
+ return handler(query, dir, item);
275
+ }
276
+
277
+ function versionAnswer(req: IncomingMessage, deps: ClaudeApiDeps): Promise<unknown> {
278
+ const version: VersionDeps = { ...deps.version, session: deps.version?.session ?? deps.session };
279
+ const method = req.method ?? 'GET';
280
+ if (method === 'GET') return claudeVersion(version);
281
+ if (method !== 'POST') throw new ApiError('method not allowed', 405);
282
+ return updateClaude(version);
283
+ }
284
+
285
+ const SINGLETONS: Record<string, (req: IncomingMessage, deps: ClaudeApiDeps) => Promise<unknown>> = {
286
+ [SESSION]: sessionAnswer,
287
+ [SETUP]: setupAnswer,
288
+ [VERSION]: versionAnswer,
289
+ };
290
+
291
+ function routed(req: IncomingMessage, path: string, search: string, deps: ClaudeApiDeps): unknown {
292
+ const dir = (deps.dir ?? claudeDir)();
293
+ const [head = '', item = ''] = parts(path);
294
+ if (head === LOGIN) return loginAnswer(req, item, deps);
295
+ const single = item === '' ? SINGLETONS[head] : undefined;
296
+ if (single !== undefined) return single(req, deps);
297
+ if (req.method === 'PUT') return writeAnswer(req, path, search, dir);
298
+ if (req.method === 'POST') return created(req, path, dir);
299
+ return answer(req.method ?? 'GET', path, new URLSearchParams(search), dir);
300
+ }
301
+
302
+ const TEXT = 'text/plain; charset=utf-8';
303
+
304
+ async function streamed(req: IncomingMessage, res: ServerResponse, path: string, search: string, dir: string): Promise<boolean> {
305
+ const [head = '', item = ''] = parts(path);
306
+ if (head !== 'sessions' || item === '') return false;
307
+ const query = new URLSearchParams(search);
308
+ if (req.method === 'GET' && query.get('raw') === '1') {
309
+ const file = sessionFilePath(projectOf(query), item, dir);
310
+ res.writeHead(200, { 'content-type': TEXT, 'cache-control': 'no-store', ...cors(req) });
311
+ await pipeline(createReadStream(file), res).catch(() => {
312
+ res.destroy();
313
+ });
314
+ return true;
315
+ }
316
+ if (req.method === 'PUT' && !(req.headers['content-type'] ?? '').includes('json')) {
317
+ sendJson(req, res, 200, await receiveSessionFile(projectOf(query), item, req, dir));
318
+ return true;
319
+ }
320
+ return false;
321
+ }
322
+
323
+ export function handleClaudeRequest(
324
+ req: IncomingMessage,
325
+ res: ServerResponse,
326
+ deps: ClaudeApiDeps,
327
+ ): boolean {
328
+ const [path = '', search = ''] = (req.url ?? '').split('?');
329
+ if (path !== PREFIX && !path.startsWith(`${PREFIX}/`)) return false;
330
+ if (req.method === 'OPTIONS') {
331
+ res.writeHead(204, cors(req)).end();
332
+ return true;
333
+ }
334
+ if (!WRITABLE.has(req.method ?? '')) {
335
+ sendJson(req, res, 405, { error: 'method not allowed' });
336
+ return true;
337
+ }
338
+ apiSession(req)
339
+ .then(async (session) => {
340
+ if (!session) throw new ApiError('unauthorized', 401);
341
+ deps.authorize(session.subject);
342
+ if (req.method !== 'GET' && ADMIN_ONLY.test(path)) requireAdmin(session);
343
+ if (await streamed(req, res, path, search, (deps.dir ?? claudeDir)())) return;
344
+ sendJson(req, res, 200, await routed(req, path, search, deps));
345
+ })
346
+ .catch((err: unknown) => {
347
+ if (err instanceof ApiError) apiFailure(req, res, err, 'claude-api');
348
+ else {
349
+ log.warn({ err: errMsg(err) }, 'claude-api: request failed');
350
+ if (!res.headersSent) sendJson(req, res, 500, { error: 'claude-api failed' });
351
+ }
352
+ });
353
+ return true;
354
+ }