@stage-labs/metro 0.1.0-beta.7 → 0.1.0-beta.71

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 (279) hide show
  1. package/dist/api.js +14 -0
  2. package/dist/bedrock-eventstream.js +120 -0
  3. package/dist/bedrock-proxy.js +260 -0
  4. package/dist/bedrock-upstream.js +72 -0
  5. package/dist/bedrock.js +37 -0
  6. package/dist/claude-settings.js +40 -0
  7. package/dist/claude.js +107 -0
  8. package/dist/cli.js +101 -0
  9. package/dist/control.js +74 -0
  10. package/dist/hold.js +211 -0
  11. package/dist/local.js +93 -0
  12. package/dist/node-name.js +51 -0
  13. package/dist/plugin.js +80 -0
  14. package/dist/provider-flags.js +9 -0
  15. package/dist/runtime-install.js +84 -0
  16. package/dist/runtime.js +70 -0
  17. package/dist/serve.js +147 -0
  18. package/dist/service.js +224 -0
  19. package/dist/tail.js +119 -0
  20. package/dist/update.js +45 -0
  21. package/dist/version.js +97 -0
  22. package/package.json +14 -25
  23. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  24. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  25. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  26. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  27. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  28. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  29. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  30. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  31. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  32. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  33. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  34. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  35. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  36. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  37. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  38. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  39. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  40. package/runtime/node_modules/@metro-labs/mcp/package.json +64 -0
  41. package/runtime/node_modules/@metro-labs/mcp/src/channels/addressed.ts +25 -0
  42. package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +24 -0
  43. package/runtime/node_modules/@metro-labs/mcp/src/channels/inbound.ts +377 -0
  44. package/runtime/node_modules/@metro-labs/mcp/src/channels/media-note.ts +85 -0
  45. package/runtime/node_modules/@metro-labs/mcp/src/channels/pending.ts +75 -0
  46. package/runtime/node_modules/@metro-labs/mcp/src/channels/relay.ts +129 -0
  47. package/runtime/node_modules/@metro-labs/mcp/src/channels/webhook-note.ts +51 -0
  48. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +311 -0
  49. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +302 -0
  50. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-error.ts +8 -0
  51. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +95 -0
  52. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-grant.ts +99 -0
  53. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-owner.ts +32 -0
  54. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +128 -0
  55. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +280 -0
  56. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +204 -0
  57. package/runtime/node_modules/@metro-labs/mcp/src/daemon/bundle-api.ts +111 -0
  58. package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-api.ts +97 -0
  59. package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-files.ts +329 -0
  60. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connect-hint.ts +39 -0
  61. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +307 -0
  62. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +21 -0
  63. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +157 -0
  64. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +127 -0
  65. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +98 -0
  66. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +70 -0
  67. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +330 -0
  68. package/runtime/node_modules/@metro-labs/mcp/src/daemon/control-api.ts +64 -0
  69. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cors.ts +29 -0
  70. package/runtime/node_modules/@metro-labs/mcp/src/daemon/crash-guard.ts +49 -0
  71. package/runtime/node_modules/@metro-labs/mcp/src/daemon/events.ts +120 -0
  72. package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +390 -0
  73. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-key.ts +33 -0
  74. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-registry.ts +13 -0
  75. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity-routes.ts +54 -0
  76. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +7 -0
  77. package/runtime/node_modules/@metro-labs/mcp/src/daemon/is-record.ts +2 -0
  78. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-cli-api.ts +63 -0
  79. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-mode.ts +187 -0
  80. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-owner.ts +14 -0
  81. package/runtime/node_modules/@metro-labs/mcp/src/daemon/log.ts +42 -0
  82. package/runtime/node_modules/@metro-labs/mcp/src/daemon/machine-api.ts +61 -0
  83. package/runtime/node_modules/@metro-labs/mcp/src/daemon/mode-api.ts +32 -0
  84. package/runtime/node_modules/@metro-labs/mcp/src/daemon/model-api.ts +169 -0
  85. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-client.ts +159 -0
  86. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-discovery.ts +117 -0
  87. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +65 -0
  88. package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +109 -0
  89. package/runtime/node_modules/@metro-labs/mcp/src/daemon/protocol.ts +226 -0
  90. package/runtime/node_modules/@metro-labs/mcp/src/daemon/relay.ts +295 -0
  91. package/runtime/node_modules/@metro-labs/mcp/src/daemon/return-to.ts +21 -0
  92. package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-deps.ts +82 -0
  93. package/runtime/node_modules/@metro-labs/mcp/src/daemon/secure-fs.ts +57 -0
  94. package/runtime/node_modules/@metro-labs/mcp/src/daemon/server-types.ts +21 -0
  95. package/runtime/node_modules/@metro-labs/mcp/src/daemon/servers-api.ts +80 -0
  96. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +29 -0
  97. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +66 -0
  98. package/runtime/node_modules/@metro-labs/mcp/src/daemon/signed-identity.ts +36 -0
  99. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor-io.ts +97 -0
  100. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +320 -0
  101. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-api.ts +71 -0
  102. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-tickets.ts +40 -0
  103. package/runtime/node_modules/@metro-labs/mcp/src/daemon/terminal-ws.ts +114 -0
  104. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-call.ts +22 -0
  105. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-error.ts +53 -0
  106. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +352 -0
  107. package/runtime/node_modules/@metro-labs/mcp/src/daemon/update-api.ts +116 -0
  108. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-api.ts +294 -0
  109. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-store.ts +181 -0
  110. package/runtime/node_modules/@metro-labs/mcp/src/daemon/vault-api.ts +78 -0
  111. package/runtime/node_modules/@metro-labs/mcp/src/daemon/vault-types.ts +12 -0
  112. package/runtime/node_modules/@metro-labs/mcp/src/daemon/version.ts +2 -0
  113. package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +19 -0
  114. package/runtime/node_modules/@metro-labs/mcp/src/db/address.ts +6 -0
  115. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +43 -0
  116. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +84 -0
  117. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-scope.ts +64 -0
  118. package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +32 -0
  119. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-config.ts +144 -0
  120. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-relay.ts +39 -0
  121. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +69 -0
  122. package/runtime/node_modules/@metro-labs/mcp/src/db/file-admin.ts +329 -0
  123. package/runtime/node_modules/@metro-labs/mcp/src/db/file-source.ts +155 -0
  124. package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +21 -0
  125. package/runtime/node_modules/@metro-labs/mcp/src/db/key-map.ts +45 -0
  126. package/runtime/node_modules/@metro-labs/mcp/src/db/local-connectors.ts +277 -0
  127. package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +253 -0
  128. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +33 -0
  129. package/runtime/node_modules/@metro-labs/mcp/src/db/servers.ts +83 -0
  130. package/runtime/node_modules/@metro-labs/mcp/src/db/stations.ts +12 -0
  131. package/runtime/node_modules/@metro-labs/mcp/src/db/vault.ts +109 -0
  132. package/runtime/node_modules/@metro-labs/mcp/src/gateway/bedrock.ts +252 -0
  133. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-auth.ts +231 -0
  134. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-device.ts +91 -0
  135. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-stream.ts +288 -0
  136. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex-translate.ts +203 -0
  137. package/runtime/node_modules/@metro-labs/mcp/src/gateway/codex.ts +211 -0
  138. package/runtime/node_modules/@metro-labs/mcp/src/gateway/eventstream.ts +123 -0
  139. package/runtime/node_modules/@metro-labs/mcp/src/gateway/forward.ts +169 -0
  140. package/runtime/node_modules/@metro-labs/mcp/src/gateway/gateway.ts +193 -0
  141. package/runtime/node_modules/@metro-labs/mcp/src/gateway/model-config.ts +180 -0
  142. package/runtime/node_modules/@metro-labs/mcp/src/mcp/accounts.ts +141 -0
  143. package/runtime/node_modules/@metro-labs/mcp/src/mcp/call-tools.ts +216 -0
  144. package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +29 -0
  145. package/runtime/node_modules/@metro-labs/mcp/src/mcp/ctx.ts +62 -0
  146. package/runtime/node_modules/@metro-labs/mcp/src/mcp/event-store.ts +91 -0
  147. package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +21 -0
  148. package/runtime/node_modules/@metro-labs/mcp/src/mcp/group-tools.ts +129 -0
  149. package/runtime/node_modules/@metro-labs/mcp/src/mcp/index.ts +191 -0
  150. package/runtime/node_modules/@metro-labs/mcp/src/mcp/member-tools.ts +72 -0
  151. package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +66 -0
  152. package/runtime/node_modules/@metro-labs/mcp/src/mcp/raw-get-stream.ts +207 -0
  153. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +47 -0
  154. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +172 -0
  155. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +43 -0
  156. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session.ts +215 -0
  157. package/runtime/node_modules/@metro-labs/mcp/src/mcp/str.ts +5 -0
  158. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-dispatch.ts +215 -0
  159. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +400 -0
  160. package/runtime/node_modules/@metro-labs/mcp/src/mcp/upload-tool.ts +66 -0
  161. package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +237 -0
  162. package/runtime/node_modules/@metro-labs/mcp/src/server.ts +2 -0
  163. package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +60 -0
  164. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-inline.ts +100 -0
  165. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +161 -0
  166. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-resolve.ts +200 -0
  167. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-xmtp.ts +111 -0
  168. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +184 -0
  169. package/runtime/node_modules/@metro-labs/mcp/src/stations/attachments.ts +196 -0
  170. package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +82 -0
  171. package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +88 -0
  172. package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +35 -0
  173. package/runtime/node_modules/@metro-labs/mcp/src/stations/station-runtime.ts +64 -0
  174. package/runtime/node_modules/@metro-labs/mcp/src/stations/types.ts +107 -0
  175. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  176. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  177. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  178. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  179. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  180. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  181. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  182. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  183. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  184. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  185. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  186. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  187. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  188. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  189. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  190. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  191. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  192. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  193. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  194. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  195. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  196. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  197. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  198. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  199. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  200. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  201. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  202. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  203. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  204. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  205. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  206. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  207. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  208. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  209. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  210. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
  211. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  212. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  213. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +327 -0
  214. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  215. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  216. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  217. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  218. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  219. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  220. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  221. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  222. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  223. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  224. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  225. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
  226. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  227. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  228. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  229. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  230. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  231. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  232. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  233. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  234. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  235. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  236. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  237. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  238. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  239. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  240. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  241. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  242. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  243. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  244. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  245. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  246. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  247. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  248. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  249. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  250. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  251. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  252. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  253. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  254. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  255. package/runtime/runtime.json +3 -0
  256. package/runtime/stations.json +36 -0
  257. package/runtime/trains/.keep +0 -0
  258. package/LICENSE +0 -21
  259. package/README.md +0 -193
  260. package/dist/cache.js +0 -75
  261. package/dist/cli/actions.js +0 -139
  262. package/dist/cli/config.js +0 -185
  263. package/dist/cli/index.js +0 -159
  264. package/dist/cli/skill.js +0 -62
  265. package/dist/cli/util.js +0 -72
  266. package/dist/codex-rc.js +0 -236
  267. package/dist/dispatcher.js +0 -95
  268. package/dist/history.js +0 -84
  269. package/dist/ipc.js +0 -72
  270. package/dist/log.js +0 -15
  271. package/dist/paths.js +0 -68
  272. package/dist/stations/discord.js +0 -200
  273. package/dist/stations/index.js +0 -73
  274. package/dist/stations/telegram-md.js +0 -34
  275. package/dist/stations/telegram-upload.js +0 -113
  276. package/dist/stations/telegram.js +0 -213
  277. package/docs/agents.md +0 -185
  278. package/docs/uri-scheme.md +0 -71
  279. package/skills/metro/SKILL.md +0 -241
@@ -0,0 +1,280 @@
1
+ import { randomBytes } from 'node:crypto';
2
+ import { ApiError } from './api-error.js';
3
+ import { errMsg, log } from './log.js';
4
+ import type { StationName } from '../db/stations.js';
5
+ import { ensureStationDeps } from './runtime-deps.js';
6
+ import {
7
+ startInteractiveAttach,
8
+ type AttachDriver,
9
+ type AttachOutcome,
10
+ type AttachPrompt,
11
+ type AttachStep,
12
+ type DriverHooks,
13
+ type InteractiveStation,
14
+ type StartedAttach,
15
+ } from '../stations/attach-interactive.js';
16
+
17
+ export type StartAttach = (
18
+ station: InteractiveStation,
19
+ input: Record<string, unknown>,
20
+ hooks: DriverHooks,
21
+ ) => Promise<StartedAttach>;
22
+
23
+ export const ATTACH_ID_RE = /^as_[A-Za-z0-9_-]{22}$/;
24
+
25
+ const PENDING_TTL_MS = 5 * 60_000;
26
+ const SETTLED_TTL_MS = 60_000;
27
+ const SWEEP_MS = 15_000;
28
+ const MAX_PER_AGENT = 2;
29
+ const MAX_TOTAL = 40;
30
+
31
+ export interface AttachOwner {
32
+ subject: string;
33
+ agentId: string;
34
+ }
35
+
36
+ export interface AttachView {
37
+ attachId: string;
38
+ station: string;
39
+ status: 'pending' | 'done' | 'failed';
40
+ step: AttachStep | null;
41
+ prompt: string;
42
+ qr: string | null;
43
+ pairingCode: string | null;
44
+ accountId: string | null;
45
+ identity: Record<string, string>;
46
+ activated: boolean;
47
+ error: string | null;
48
+ expiresAt: number;
49
+ }
50
+
51
+ export type CompleteAttach = (
52
+ owner: AttachOwner,
53
+ station: StationName,
54
+ config: Record<string, unknown>,
55
+ ) => Promise<{ accountId: string; activated: boolean }>;
56
+
57
+ export type AuthorizeAttach = (owner: AttachOwner) => Promise<void>;
58
+
59
+ export interface AttachSessionDeps {
60
+ authorize: AuthorizeAttach;
61
+ complete: CompleteAttach;
62
+ start?: StartAttach;
63
+ }
64
+
65
+ interface Session {
66
+ owner: AttachOwner;
67
+ driver: AttachDriver | null;
68
+ view: AttachView;
69
+ }
70
+
71
+ const missing = (): ApiError => new ApiError('no such attach session', 404);
72
+
73
+ function newAttachId(): string {
74
+ return `as_${randomBytes(16).toString('base64url')}`;
75
+ }
76
+
77
+ function blankView(attachId: string, station: string): AttachView {
78
+ return {
79
+ attachId,
80
+ station,
81
+ status: 'pending',
82
+ step: null,
83
+ prompt: '',
84
+ qr: null,
85
+ pairingCode: null,
86
+ accountId: null,
87
+ identity: {},
88
+ activated: false,
89
+ error: null,
90
+ expiresAt: Date.now() + PENDING_TTL_MS,
91
+ };
92
+ }
93
+
94
+ export class AttachSessions {
95
+ private sessions = new Map<string, Session>();
96
+ private sweeper: ReturnType<typeof setInterval> | null = null;
97
+
98
+ constructor(private deps: AttachSessionDeps) {}
99
+
100
+ private ensureSweeper(): void {
101
+ if (this.sweeper) return;
102
+ this.sweeper = setInterval(() => {
103
+ this.sweep().catch((err: unknown) => {
104
+ log.warn({ err: errMsg(err) }, 'attach-session: sweep failed');
105
+ });
106
+ }, SWEEP_MS);
107
+ this.sweeper.unref();
108
+ }
109
+
110
+ private own(owner: AttachOwner, attachId: string): Session {
111
+ const session = this.sessions.get(attachId);
112
+ if (
113
+ session?.owner.subject !== owner.subject ||
114
+ session.owner.agentId !== owner.agentId
115
+ )
116
+ throw missing();
117
+ return session;
118
+ }
119
+
120
+ private admit(owner: AttachOwner): void {
121
+ if (this.sessions.size >= MAX_TOTAL)
122
+ throw new ApiError(
123
+ 'too many sign-ins are in flight on this daemon, try again shortly',
124
+ 429,
125
+ );
126
+ const mine = [...this.sessions.values()].filter(
127
+ (s) => s.owner.agentId === owner.agentId && s.view.status === 'pending',
128
+ );
129
+ if (mine.length >= MAX_PER_AGENT)
130
+ throw new ApiError(
131
+ 'this agent already has a sign-in in progress, finish or cancel it first',
132
+ 409,
133
+ );
134
+ }
135
+
136
+ private settle(session: Session, patch: Partial<AttachView>): void {
137
+ Object.assign(session.view, patch, {
138
+ qr: null,
139
+ pairingCode: null,
140
+ step: null,
141
+ expiresAt: Date.now() + SETTLED_TTL_MS,
142
+ });
143
+ session.driver = null;
144
+ }
145
+
146
+ private async finish(
147
+ attachId: string,
148
+ station: StationName,
149
+ outcome: AttachOutcome,
150
+ ): Promise<void> {
151
+ const session = this.sessions.get(attachId);
152
+ if (session?.view.status !== 'pending') return;
153
+ try {
154
+ const landed = await this.deps.complete(
155
+ session.owner,
156
+ station,
157
+ outcome.config,
158
+ );
159
+ this.settle(session, {
160
+ status: 'done',
161
+ prompt: '',
162
+ identity: outcome.identity,
163
+ accountId: landed.accountId,
164
+ activated: landed.activated,
165
+ });
166
+ log.info(
167
+ { agentId: session.owner.agentId, station, account: landed.accountId },
168
+ 'attach-session: signed in and attached a station account',
169
+ );
170
+ } catch (err) {
171
+ const reason =
172
+ err instanceof ApiError ? err.message : 'could not store the account';
173
+ log.warn({ station, err: errMsg(err) }, 'attach-session: store failed');
174
+ this.settle(session, { status: 'failed', prompt: '', error: reason });
175
+ }
176
+ }
177
+
178
+ private hooksFor(attachId: string, station: StationName) {
179
+ return {
180
+ prompt: (p: AttachPrompt): void => {
181
+ const session = this.sessions.get(attachId);
182
+ if (session?.view.status !== 'pending') return;
183
+ session.view.step = p.step;
184
+ session.view.prompt = p.prompt;
185
+ session.view.qr = p.qr ?? null;
186
+ session.view.pairingCode = p.pairingCode ?? null;
187
+ },
188
+ done: (o: AttachOutcome): void => {
189
+ this.finish(attachId, station, o).catch((err: unknown) => {
190
+ log.warn(
191
+ { attachId, station, err: errMsg(err) },
192
+ 'attach-session: completion failed',
193
+ );
194
+ });
195
+ },
196
+ fail: (message: string): void => {
197
+ const session = this.sessions.get(attachId);
198
+ if (session?.view.status !== 'pending') return;
199
+ this.settle(session, { status: 'failed', prompt: '', error: message });
200
+ },
201
+ };
202
+ }
203
+
204
+ async start(
205
+ owner: AttachOwner,
206
+ station: InteractiveStation,
207
+ input: Record<string, unknown>,
208
+ ): Promise<AttachView> {
209
+ await this.deps.authorize(owner);
210
+ this.admit(owner);
211
+ this.ensureSweeper();
212
+ const attachId = newAttachId();
213
+ const session: Session = {
214
+ owner,
215
+ driver: null,
216
+ view: blankView(attachId, station),
217
+ };
218
+ this.sessions.set(attachId, session);
219
+ try {
220
+ ensureStationDeps(station);
221
+ const started = await (this.deps.start ?? startInteractiveAttach)(
222
+ station,
223
+ input,
224
+ this.hooksFor(attachId, station),
225
+ );
226
+ session.driver = started.driver;
227
+ this.hooksFor(attachId, station).prompt(started.prompt);
228
+ } catch (err) {
229
+ this.sessions.delete(attachId);
230
+ throw err;
231
+ }
232
+ log.info(
233
+ { agentId: owner.agentId, station, attachId },
234
+ 'attach-session: started',
235
+ );
236
+ return { ...session.view };
237
+ }
238
+
239
+ view(owner: AttachOwner, attachId: string): AttachView {
240
+ return { ...this.own(owner, attachId).view };
241
+ }
242
+
243
+ async submit(
244
+ owner: AttachOwner,
245
+ attachId: string,
246
+ input: { code?: unknown; password?: unknown },
247
+ ): Promise<AttachView> {
248
+ const session = this.own(owner, attachId);
249
+ const driver = session.driver;
250
+ if (session.view.status !== 'pending' || !driver)
251
+ throw new ApiError('this sign-in is already finished', 409);
252
+ await driver.submit(input);
253
+ return { ...session.view };
254
+ }
255
+
256
+ async cancel(owner: AttachOwner, attachId: string): Promise<void> {
257
+ const session = this.own(owner, attachId);
258
+ this.sessions.delete(attachId);
259
+ await session.driver?.cancel().catch(() => undefined);
260
+ log.info({ attachId }, 'attach-session: cancelled');
261
+ }
262
+
263
+ async sweep(now = Date.now()): Promise<void> {
264
+ for (const [attachId, session] of [...this.sessions]) {
265
+ if (session.view.expiresAt > now) continue;
266
+ this.sessions.delete(attachId);
267
+ const driver = session.driver;
268
+ session.driver = null;
269
+ if (!driver) continue;
270
+ log.info({ attachId }, 'attach-session: expired, credentials discarded');
271
+ await driver.cancel().catch(() => undefined);
272
+ }
273
+ }
274
+
275
+ async stop(): Promise<void> {
276
+ if (this.sweeper) clearInterval(this.sweeper);
277
+ this.sweeper = null;
278
+ await this.sweep(Number.MAX_SAFE_INTEGER);
279
+ }
280
+ }
@@ -0,0 +1,204 @@
1
+ import { join } from 'node:path';
2
+ import { type Server } from 'node:http';
3
+ import { userSelf } from './events.js';
4
+ import { setTrainCallBackend } from './train-call.js';
5
+ import { errMsg, log, logFatalSync } from './log.js';
6
+ import { acquireLock, isLocalMode, STATE_DIR, trainsDir } from './paths.js';
7
+ import { installCrashGuard, markDaemonReady } from './crash-guard.js';
8
+ import { METRO_VERSION } from './version.js';
9
+ import { funnelDriver, Tunnel, tunnelWanted, webhookPort } from './tunnel.js';
10
+ import {
11
+ localConnectHint,
12
+ publicConnectHint,
13
+ tunnelPendingHint,
14
+ } from './connect-hint.js';
15
+ import { TrainSupervisor } from './supervisor.js';
16
+ import {
17
+ makeEmit,
18
+ startWebhookServer,
19
+ trainEventToMetroEvent,
20
+ } from './http.js';
21
+ import { localAgentKey } from '../db/materialize.js';
22
+ import { fileSource } from '../db/file-source.js';
23
+ import { applyLocalOwner } from './local-owner.js';
24
+ import { localOwner } from '../db/file-admin.js';
25
+ import { ensureStationDeps } from './runtime-deps.js';
26
+ import { localSessionApis } from './local-mode.js';
27
+ import type { ModeInfo } from './mode-api.js';
28
+ import type { SessionApis } from './session-apis.js';
29
+ import {
30
+ agentLiveness,
31
+ closeAgentSession,
32
+ createMetroMcp,
33
+ } from '../mcp/index.js';
34
+ import { metroCall } from '../mcp/ctx.js';
35
+ import { gatherAccountsForAgents } from '../mcp/accounts.js';
36
+ import {
37
+ accountStationCapabilities,
38
+ stationByName,
39
+ } from '../stations/registry.js';
40
+ import { prepareAccount } from '../stations/attach.js';
41
+ import { materializeFrom, reloadFrom } from '../db/materialize.js';
42
+ import type { StationName } from '../db/stations.js';
43
+ import { startUploadReaper } from './upload-store.js';
44
+
45
+ installCrashGuard();
46
+ acquireLock(join(STATE_DIR, '.tail-lock'));
47
+
48
+ log.info({ self: userSelf() }, 'user identity');
49
+
50
+ process.stdout.on('error', (err) => {
51
+ if ((err as NodeJS.ErrnoException).code !== 'EPIPE')
52
+ log.warn({ err: errMsg(err) }, 'stdout error');
53
+ });
54
+
55
+ const supervisor = new TrainSupervisor();
56
+ const emit = makeEmit();
57
+
58
+ supervisor.onTrainEvent((env, train) => {
59
+ const entry = trainEventToMetroEvent(env, train);
60
+ if (entry) emit(entry);
61
+ });
62
+
63
+ let webhookServer: Server | null = null;
64
+ const tunnel = tunnelWanted() ? new Tunnel(funnelDriver(webhookPort()), announcePublic) : null;
65
+
66
+ function announcePublic(url: string): void {
67
+ process.stderr.write(`\n${publicConnectHint(url, localOwner())}`);
68
+ }
69
+
70
+ setTrainCallBackend((train, action, args) =>
71
+ supervisor.call(train, action, args),
72
+ );
73
+
74
+ function announceLocalEndpoint(): void {
75
+ process.stderr.write(
76
+ `\n${tunnel === null ? localConnectHint(webhookPort(), localOwner()) : tunnelPendingHint()}`,
77
+ );
78
+ const key = localAgentKey();
79
+ if (key === null) {
80
+ log.info('no agent on this machine yet');
81
+ return;
82
+ }
83
+ const url = `http://127.0.0.1:${String(webhookPort())}/mcp?token=${key}`;
84
+ process.stderr.write(
85
+ `\nConnect an agent on this machine:\n\n claude mcp add --transport http metro "${url}"\n\n`,
86
+ );
87
+ }
88
+
89
+ async function syncStations(station: StationName): Promise<void> {
90
+ const { removed } = await reloadFrom(fileSource);
91
+ if (stationByName(station)?.hasTrain === false) return;
92
+ if (removed.includes(station)) await supervisor.stopTrain(station);
93
+ else {
94
+ ensureStationDeps(station);
95
+ supervisor.requestReload(station);
96
+ }
97
+ }
98
+
99
+ const hostedMode = (): ModeInfo => ({
100
+ mode: 'hosted',
101
+ owner: null,
102
+ project: null,
103
+ version: METRO_VERSION,
104
+ });
105
+
106
+ async function sessionApis(): Promise<SessionApis> {
107
+ if (isLocalMode())
108
+ return localSessionApis({
109
+ syncStations,
110
+ restart: () => {
111
+ exitCode = RESTART_EXIT;
112
+ onShutdown();
113
+ },
114
+ stop: () => {
115
+ exitCode = HOLD_EXIT;
116
+ onShutdown();
117
+ },
118
+ closeAgentSession,
119
+ gatherAccounts: gatherAccountsForAgents,
120
+ capabilities: accountStationCapabilities,
121
+ liveness: agentLiveness,
122
+ prepareAccount,
123
+ });
124
+ const vault = await import('../db/vault.js');
125
+ const servers = await import('../db/servers.js');
126
+ return {
127
+ vaultApi: {
128
+ list: vault.listVaultForOwner,
129
+ put: vault.putVaultForOwner,
130
+ get: vault.getVaultForOwner,
131
+ remove: vault.deleteVaultForOwner,
132
+ },
133
+ serversApi: {
134
+ list: servers.listServersForOwner,
135
+ add: servers.addServerForOwner,
136
+ rename: servers.renameServerForOwner,
137
+ remove: servers.deleteServerForOwner,
138
+ },
139
+ mode: hostedMode,
140
+ };
141
+ }
142
+
143
+ async function main(): Promise<void> {
144
+ if (isLocalMode()) {
145
+ applyLocalOwner();
146
+ await materializeFrom(fileSource, { allowEmpty: true });
147
+ }
148
+ supervisor.start();
149
+ const metroMcp = await createMetroMcp();
150
+ webhookServer = await startWebhookServer(
151
+ emit,
152
+ await sessionApis(),
153
+ metroMcp.httpHandler,
154
+ metroCall,
155
+ );
156
+ metroMcp.startInbound();
157
+ startUploadReaper();
158
+ if (isLocalMode()) announceLocalEndpoint();
159
+ tunnel?.start();
160
+ log.info(
161
+ { tunnel: !!tunnel, trainsDir: trainsDir(), mcp: '/', version: METRO_VERSION },
162
+ 'dispatcher ready',
163
+ );
164
+ markDaemonReady();
165
+ }
166
+
167
+ const SHUTDOWN_TIMEOUT_MS = 3_000;
168
+ const RESTART_EXIT = 75;
169
+ const HOLD_EXIT = 76;
170
+ let exitCode = 0;
171
+ let shuttingDown = false;
172
+ async function shutdown(): Promise<void> {
173
+ if (shuttingDown) return;
174
+ shuttingDown = true;
175
+ log.info('dispatcher shutting down');
176
+ tunnel?.stop();
177
+ if (webhookServer) {
178
+ const server = webhookServer;
179
+ await Promise.race([
180
+ new Promise<void>((r) => {
181
+ server.close(() => {
182
+ r();
183
+ });
184
+ }),
185
+ new Promise<void>((r) => {
186
+ setTimeout(r, SHUTDOWN_TIMEOUT_MS).unref();
187
+ }),
188
+ ]);
189
+ }
190
+ await supervisor.stop();
191
+ process.exit(exitCode);
192
+ }
193
+ const onShutdown = (): void => {
194
+ shutdown().catch((err: unknown) => {
195
+ log.error({ err: errMsg(err) }, 'dispatcher: shutdown failed');
196
+ process.exit(1);
197
+ });
198
+ };
199
+ for (const sig of ['SIGINT', 'SIGTERM'] as const) process.on(sig, onShutdown);
200
+
201
+ await main().catch((err: unknown) => {
202
+ logFatalSync({ err: errMsg(err) }, 'dispatcher failed to start');
203
+ process.exit(1);
204
+ });
@@ -0,0 +1,111 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { errMsg, log } from './log.js';
3
+ import { apiFailure, apiSession, cors, readJsonBody, sendJson } from './api-http.js';
4
+ import { ApiError } from './api-error.js';
5
+ import { isRecord } from './is-record.js';
6
+ import { parseId } from '../db/ids.js';
7
+ import type { LoadedAccount, LoadedAgent, LoadedConnector } from '../db/materialize.js';
8
+ import { STATIONS } from '../db/stations.js';
9
+
10
+ const AGENTS = '/api/agents';
11
+ const RESTORE_PATH = `${AGENTS}/restore`;
12
+ const BUNDLE_MAX = 2 * 1024 * 1024;
13
+ const STATION_NAMES = new Set<string>(STATIONS);
14
+
15
+ export interface AgentBundle {
16
+ version: 1;
17
+ agent: { id: string; name: string; key: string; stations: LoadedAccount[] };
18
+ connectors: LoadedConnector[];
19
+ }
20
+
21
+ export const loadedAgentOf = (bundle: AgentBundle): LoadedAgent => ({
22
+ id: bundle.agent.id,
23
+ name: bundle.agent.name,
24
+ key: bundle.agent.key,
25
+ accounts: bundle.agent.stations,
26
+ connectors: [],
27
+ });
28
+
29
+ export interface RestoredAgent {
30
+ id: string;
31
+ name: string;
32
+ stations: number;
33
+ connectors: number;
34
+ }
35
+
36
+ export interface BundleApiDeps {
37
+ bundle: (subject: string, agentId: string) => Promise<AgentBundle>;
38
+ restore: (subject: string, bundle: AgentBundle) => Promise<RestoredAgent>;
39
+ }
40
+
41
+ const bad = (what: string): ApiError => new ApiError(`bundle: ${what}`, 400);
42
+
43
+ function stationOf(raw: unknown): LoadedAccount {
44
+ if (!isRecord(raw) || typeof raw.station !== 'string' || !STATION_NAMES.has(raw.station))
45
+ throw bad('a station is not a known station');
46
+ if (typeof raw.id !== 'string' || !isRecord(raw.config)) throw bad('a station has no id or config');
47
+ const allowlist = Array.isArray(raw.allowlist)
48
+ ? raw.allowlist.filter((s): s is string => typeof s === 'string')
49
+ : null;
50
+ return { station: raw.station as LoadedAccount['station'], id: raw.id, allowlist, config: raw.config };
51
+ }
52
+
53
+ function connectorOf(raw: unknown): LoadedConnector {
54
+ if (!isRecord(raw) || typeof raw.id !== 'string' || typeof raw.name !== 'string' || typeof raw.url !== 'string')
55
+ throw bad('a connector is missing its id, name or url');
56
+ if (!isRecord(raw.config)) throw bad('a connector has no config');
57
+ return { id: raw.id, name: raw.name, url: raw.url, transport: 'http', config: raw.config };
58
+ }
59
+
60
+ function agentOf(raw: unknown): AgentBundle['agent'] {
61
+ if (!isRecord(raw)) throw bad('not a v1 agent bundle');
62
+ const { id, name, key, stations } = raw;
63
+ if (typeof id !== 'string' || parseId(id) === null) throw bad('agent id is not an id');
64
+ if (typeof name !== 'string' || typeof key !== 'string' || key === '') throw bad('agent has no name or key');
65
+ if (!Array.isArray(stations)) throw bad('agent has no station list');
66
+ return { id, name, key, stations: stations.map(stationOf) };
67
+ }
68
+
69
+ export function parseBundle(raw: unknown): AgentBundle {
70
+ if (!isRecord(raw) || raw.version !== 1) throw bad('not a v1 agent bundle');
71
+ const connectors = Array.isArray(raw.connectors) ? raw.connectors : [];
72
+ return { version: 1, agent: agentOf(raw.agent), connectors: connectors.map(connectorOf) };
73
+ }
74
+
75
+ async function answer(req: IncomingMessage, deps: BundleApiDeps, subject: string, path: string): Promise<unknown> {
76
+ if (path === RESTORE_PATH) return deps.restore(subject, parseBundle(await readJsonBody(req, BUNDLE_MAX)));
77
+ const id = parseId(path.slice(AGENTS.length + 1).split('/')[0] ?? '');
78
+ if (id === null) throw new ApiError('no such agent', 404);
79
+ return deps.bundle(subject, id);
80
+ }
81
+
82
+ export function handleBundleRequest(req: IncomingMessage, res: ServerResponse, deps: BundleApiDeps): boolean {
83
+ const path = (req.url ?? '').split('?')[0] ?? '';
84
+ const isRestore = path === RESTORE_PATH;
85
+ const isBundle = /^\/api\/agents\/[^/]+\/bundle$/.test(path);
86
+ if (!isRestore && !isBundle) return false;
87
+ if (req.method === 'OPTIONS') {
88
+ res.writeHead(204, cors(req)).end();
89
+ return true;
90
+ }
91
+ if (req.method !== (isRestore ? 'POST' : 'GET')) {
92
+ sendJson(req, res, 405, { error: 'method not allowed' });
93
+ return true;
94
+ }
95
+ apiSession(req)
96
+ .then((session) => {
97
+ if (!session) throw new ApiError('unauthorized', 401);
98
+ return answer(req, deps, session.subject, path);
99
+ })
100
+ .then((body) => {
101
+ sendJson(req, res, isRestore ? 201 : 200, body);
102
+ })
103
+ .catch((err: unknown) => {
104
+ if (err instanceof ApiError) apiFailure(req, res, err, 'bundle-api');
105
+ else {
106
+ log.warn({ err: errMsg(err) }, 'bundle-api: request failed');
107
+ if (!res.headersSent) sendJson(req, res, 500, { error: 'bundle api failed' });
108
+ }
109
+ });
110
+ return true;
111
+ }
@@ -0,0 +1,97 @@
1
+ import type { IncomingMessage, ServerResponse } from 'node:http';
2
+ import { errMsg, log } from './log.js';
3
+ import { apiFailure, apiSession, cors, sendJson } from './api-http.js';
4
+ import { ApiError } from './api-error.js';
5
+ import {
6
+ claudeDir,
7
+ deleteClaudeSession,
8
+ listClaudeProjects,
9
+ listClaudeSessions,
10
+ listMemory,
11
+ readMemoryFile,
12
+ readTranscript,
13
+ } from './claude-files.js';
14
+
15
+ const PREFIX = '/api/claude';
16
+ const PAGE = 100;
17
+ const PAGE_MAX = 500;
18
+
19
+ export interface ClaudeApiDeps {
20
+ authorize: (subject: string) => void;
21
+ dir?: () => string;
22
+ }
23
+
24
+ function projectOf(query: URLSearchParams): string {
25
+ const project = query.get('project');
26
+ if (project === null || project === '') throw new ApiError('project is required', 400);
27
+ return project;
28
+ }
29
+
30
+ function pageOf(query: URLSearchParams): { offset: number; limit: number } {
31
+ const offset = Math.max(0, Math.floor(Number(query.get('offset') ?? '0')) || 0);
32
+ const limit = Math.min(PAGE_MAX, Math.max(1, Math.floor(Number(query.get('limit') ?? String(PAGE))) || PAGE));
33
+ return { offset, limit };
34
+ }
35
+
36
+ type Handler = (query: URLSearchParams, dir: string, item: string) => unknown;
37
+
38
+ const COLLECTIONS: Record<string, Handler> = {
39
+ projects: (_query, dir) => ({ projects: listClaudeProjects(dir) }),
40
+ sessions: (query, dir) => ({ sessions: listClaudeSessions(projectOf(query), dir) }),
41
+ memory: (query, dir) => listMemory(projectOf(query), dir),
42
+ };
43
+
44
+ const ITEMS: Record<string, Handler> = {
45
+ sessions: (query, dir, id) => {
46
+ const { offset, limit } = pageOf(query);
47
+ return readTranscript(projectOf(query), id, offset, limit, dir);
48
+ },
49
+ memory: (query, dir, name) => ({ name, content: readMemoryFile(projectOf(query), name, dir) }),
50
+ };
51
+
52
+ function answer(method: string, path: string, query: URLSearchParams, dir: string): unknown {
53
+ const rest = path.slice(PREFIX.length + 1).split('/').filter(Boolean);
54
+ const [head = '', item] = rest;
55
+ if (method === 'DELETE') {
56
+ if (rest.length !== 2 || head !== 'sessions') throw new ApiError('method not allowed', 405);
57
+ deleteClaudeSession(projectOf(query), item ?? '', dir);
58
+ return { deleted: item };
59
+ }
60
+ const handler = rest.length === 1 ? COLLECTIONS[head] : rest.length === 2 ? ITEMS[head] : undefined;
61
+ if (handler === undefined) throw new ApiError('no such route', 404);
62
+ return handler(query, dir, item ?? '');
63
+ }
64
+
65
+ export function handleClaudeRequest(
66
+ req: IncomingMessage,
67
+ res: ServerResponse,
68
+ deps: ClaudeApiDeps,
69
+ ): boolean {
70
+ const [path = '', search = ''] = (req.url ?? '').split('?');
71
+ if (path !== PREFIX && !path.startsWith(`${PREFIX}/`)) return false;
72
+ if (req.method === 'OPTIONS') {
73
+ res.writeHead(204, cors(req)).end();
74
+ return true;
75
+ }
76
+ if (req.method !== 'GET' && req.method !== 'DELETE') {
77
+ sendJson(req, res, 405, { error: 'method not allowed' });
78
+ return true;
79
+ }
80
+ apiSession(req)
81
+ .then((session) => {
82
+ if (!session) throw new ApiError('unauthorized', 401);
83
+ deps.authorize(session.subject);
84
+ return answer(req.method ?? 'GET', path, new URLSearchParams(search), (deps.dir ?? claudeDir)());
85
+ })
86
+ .then((body) => {
87
+ sendJson(req, res, 200, body);
88
+ })
89
+ .catch((err: unknown) => {
90
+ if (err instanceof ApiError) apiFailure(req, res, err, 'claude-api');
91
+ else {
92
+ log.warn({ err: errMsg(err) }, 'claude-api: request failed');
93
+ if (!res.headersSent) sendJson(req, res, 500, { error: 'claude-api failed' });
94
+ }
95
+ });
96
+ return true;
97
+ }