@stage-labs/metro 0.1.0-beta.8 → 0.1.0-beta.80

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 (282) hide show
  1. package/dist/api.js +14 -0
  2. package/dist/claude-settings.js +40 -0
  3. package/dist/claude.js +107 -0
  4. package/dist/cli.js +101 -0
  5. package/dist/control.js +74 -0
  6. package/dist/hold.js +211 -0
  7. package/dist/local.js +93 -0
  8. package/dist/node-name.js +71 -0
  9. package/dist/plugin.js +80 -0
  10. package/dist/provider-flags.js +9 -0
  11. package/dist/runtime-install.js +87 -0
  12. package/dist/runtime.js +74 -0
  13. package/dist/serve.js +146 -0
  14. package/dist/service.js +242 -0
  15. package/dist/tail.js +119 -0
  16. package/dist/update.js +45 -0
  17. package/dist/version.js +97 -0
  18. package/package.json +14 -25
  19. package/runtime/node_modules/@metro-labs/core/package.json +43 -0
  20. package/runtime/node_modules/@metro-labs/core/src/address.ts +6 -0
  21. package/runtime/node_modules/@metro-labs/core/src/endpoints.ts +7 -0
  22. package/runtime/node_modules/@metro-labs/core/src/events.ts +120 -0
  23. package/runtime/node_modules/@metro-labs/core/src/identity.ts +7 -0
  24. package/runtime/node_modules/@metro-labs/core/src/ids.ts +21 -0
  25. package/runtime/node_modules/@metro-labs/core/src/is-record.ts +2 -0
  26. package/runtime/node_modules/@metro-labs/core/src/lines.ts +82 -0
  27. package/runtime/node_modules/@metro-labs/core/src/log.ts +42 -0
  28. package/runtime/node_modules/@metro-labs/core/src/protocol.ts +226 -0
  29. package/runtime/node_modules/@metro-labs/core/src/secure-fs.ts +57 -0
  30. package/runtime/node_modules/@metro-labs/core/src/station-names.ts +12 -0
  31. package/runtime/node_modules/@metro-labs/core/src/stations/account-store.ts +60 -0
  32. package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +196 -0
  33. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +88 -0
  34. package/runtime/node_modules/@metro-labs/core/src/stations/station-runtime.ts +64 -0
  35. package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +107 -0
  36. package/runtime/node_modules/@metro-labs/core/src/str.ts +5 -0
  37. package/runtime/node_modules/@metro-labs/core/src/train-error.ts +53 -0
  38. package/runtime/node_modules/@metro-labs/core/src/version.ts +2 -0
  39. package/runtime/node_modules/@metro-labs/daemon/package.json +47 -0
  40. package/runtime/node_modules/@metro-labs/daemon/src/agents/account-attach.ts +19 -0
  41. package/runtime/node_modules/@metro-labs/daemon/src/agents/accounts-api.ts +311 -0
  42. package/runtime/node_modules/@metro-labs/daemon/src/agents/admin.ts +43 -0
  43. package/runtime/node_modules/@metro-labs/daemon/src/agents/api.ts +302 -0
  44. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +111 -0
  45. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +329 -0
  46. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +155 -0
  47. package/runtime/node_modules/@metro-labs/daemon/src/agents/keys.ts +45 -0
  48. package/runtime/node_modules/@metro-labs/daemon/src/agents/map.ts +84 -0
  49. package/runtime/node_modules/@metro-labs/daemon/src/agents/scope.ts +64 -0
  50. package/runtime/node_modules/@metro-labs/daemon/src/boot/boot.ts +176 -0
  51. package/runtime/node_modules/@metro-labs/daemon/src/boot/connect-hint.ts +39 -0
  52. package/runtime/node_modules/@metro-labs/daemon/src/boot/crash-guard.ts +49 -0
  53. package/runtime/node_modules/@metro-labs/daemon/src/boot/local-owner.ts +14 -0
  54. package/runtime/node_modules/@metro-labs/daemon/src/boot/paths.ts +106 -0
  55. package/runtime/node_modules/@metro-labs/daemon/src/channels/addressed.ts +25 -0
  56. package/runtime/node_modules/@metro-labs/daemon/src/channels/dedupe.ts +24 -0
  57. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +377 -0
  58. package/runtime/node_modules/@metro-labs/daemon/src/channels/media-note.ts +85 -0
  59. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +75 -0
  60. package/runtime/node_modules/@metro-labs/daemon/src/channels/relay.ts +129 -0
  61. package/runtime/node_modules/@metro-labs/daemon/src/channels/webhook-note.ts +51 -0
  62. package/runtime/node_modules/@metro-labs/daemon/src/claude/api.ts +148 -0
  63. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +329 -0
  64. package/runtime/node_modules/@metro-labs/daemon/src/claude/login.ts +167 -0
  65. package/runtime/node_modules/@metro-labs/daemon/src/claude/settings.ts +117 -0
  66. package/runtime/node_modules/@metro-labs/daemon/src/connectors/api.ts +307 -0
  67. package/runtime/node_modules/@metro-labs/daemon/src/connectors/cli-api.ts +63 -0
  68. package/runtime/node_modules/@metro-labs/daemon/src/connectors/config.ts +144 -0
  69. package/runtime/node_modules/@metro-labs/daemon/src/connectors/json.ts +21 -0
  70. package/runtime/node_modules/@metro-labs/daemon/src/connectors/model.ts +69 -0
  71. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-client.ts +159 -0
  72. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-discovery.ts +117 -0
  73. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-pending.ts +65 -0
  74. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth-routes.ts +157 -0
  75. package/runtime/node_modules/@metro-labs/daemon/src/connectors/oauth.ts +127 -0
  76. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay-target.ts +39 -0
  77. package/runtime/node_modules/@metro-labs/daemon/src/connectors/relay.ts +295 -0
  78. package/runtime/node_modules/@metro-labs/daemon/src/connectors/store.ts +277 -0
  79. package/runtime/node_modules/@metro-labs/daemon/src/connectors/tools.ts +98 -0
  80. package/runtime/node_modules/@metro-labs/daemon/src/connectors/url.ts +70 -0
  81. package/runtime/node_modules/@metro-labs/daemon/src/connectors/verify.ts +330 -0
  82. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +99 -0
  83. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-owner.ts +32 -0
  84. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +121 -0
  85. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +294 -0
  86. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-store.ts +181 -0
  87. package/runtime/node_modules/@metro-labs/daemon/src/gateway/bedrock.ts +252 -0
  88. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-auth.ts +231 -0
  89. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-device.ts +91 -0
  90. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-stream.ts +288 -0
  91. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex-translate.ts +203 -0
  92. package/runtime/node_modules/@metro-labs/daemon/src/gateway/codex.ts +220 -0
  93. package/runtime/node_modules/@metro-labs/daemon/src/gateway/eventstream.ts +123 -0
  94. package/runtime/node_modules/@metro-labs/daemon/src/gateway/forward.ts +182 -0
  95. package/runtime/node_modules/@metro-labs/daemon/src/gateway/gateway.ts +197 -0
  96. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-api.ts +195 -0
  97. package/runtime/node_modules/@metro-labs/daemon/src/gateway/model-config.ts +198 -0
  98. package/runtime/node_modules/@metro-labs/daemon/src/gateway/openrouter.ts +40 -0
  99. package/runtime/node_modules/@metro-labs/daemon/src/gateway/served.ts +17 -0
  100. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +137 -0
  101. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +216 -0
  102. package/runtime/node_modules/@metro-labs/daemon/src/mcp/channel-owner.ts +29 -0
  103. package/runtime/node_modules/@metro-labs/daemon/src/mcp/ctx.ts +62 -0
  104. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +91 -0
  105. package/runtime/node_modules/@metro-labs/daemon/src/mcp/frame-scope.ts +21 -0
  106. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +129 -0
  107. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +191 -0
  108. package/runtime/node_modules/@metro-labs/daemon/src/mcp/member-tools.ts +72 -0
  109. package/runtime/node_modules/@metro-labs/daemon/src/mcp/permission-relay.ts +66 -0
  110. package/runtime/node_modules/@metro-labs/daemon/src/mcp/raw-get-stream.ts +207 -0
  111. package/runtime/node_modules/@metro-labs/daemon/src/mcp/request-identity.ts +47 -0
  112. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-registry.ts +172 -0
  113. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session-route.ts +43 -0
  114. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +215 -0
  115. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-dispatch.ts +215 -0
  116. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-schemas.ts +400 -0
  117. package/runtime/node_modules/@metro-labs/daemon/src/mcp/upload-tool.ts +66 -0
  118. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +237 -0
  119. package/runtime/node_modules/@metro-labs/daemon/src/net/tunnel.ts +389 -0
  120. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +390 -0
  121. package/runtime/node_modules/@metro-labs/daemon/src/routes/identity.ts +54 -0
  122. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +187 -0
  123. package/runtime/node_modules/@metro-labs/daemon/src/routes/session-apis.ts +60 -0
  124. package/runtime/node_modules/@metro-labs/daemon/src/routes/session.ts +29 -0
  125. package/runtime/node_modules/@metro-labs/daemon/src/server/control.ts +64 -0
  126. package/runtime/node_modules/@metro-labs/daemon/src/server/machine.ts +61 -0
  127. package/runtime/node_modules/@metro-labs/daemon/src/server/update.ts +116 -0
  128. package/runtime/node_modules/@metro-labs/daemon/src/server.ts +2 -0
  129. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-inline.ts +100 -0
  130. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-interactive.ts +161 -0
  131. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +202 -0
  132. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-session.ts +280 -0
  133. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-xmtp.ts +111 -0
  134. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +184 -0
  135. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +248 -0
  136. package/runtime/node_modules/@metro-labs/daemon/src/stations/registry.ts +35 -0
  137. package/runtime/node_modules/@metro-labs/daemon/src/stations/runtime-deps.ts +82 -0
  138. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor-io.ts +97 -0
  139. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +320 -0
  140. package/runtime/node_modules/@metro-labs/daemon/src/stations/train-call.ts +22 -0
  141. package/runtime/node_modules/@metro-labs/daemon/src/terminal/api.ts +71 -0
  142. package/runtime/node_modules/@metro-labs/daemon/src/terminal/socket.ts +123 -0
  143. package/runtime/node_modules/@metro-labs/daemon/src/terminal/tickets.ts +40 -0
  144. package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
  145. package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -0
  146. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
  147. package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
  148. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
  149. package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +220 -0
  150. package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
  151. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
  152. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
  153. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
  154. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
  155. package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
  156. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
  157. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
  158. package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
  159. package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
  160. package/runtime/node_modules/@metro-labs/discord-bot/src/wire.ts +5 -0
  161. package/runtime/node_modules/@metro-labs/http/package.json +33 -0
  162. package/runtime/node_modules/@metro-labs/http/src/api-error.ts +8 -0
  163. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +95 -0
  164. package/runtime/node_modules/@metro-labs/http/src/cors.ts +29 -0
  165. package/runtime/node_modules/@metro-labs/http/src/identity-key.ts +33 -0
  166. package/runtime/node_modules/@metro-labs/http/src/identity-registry.ts +13 -0
  167. package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +32 -0
  168. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +21 -0
  169. package/runtime/node_modules/@metro-labs/http/src/signed-identity.ts +36 -0
  170. package/runtime/node_modules/@metro-labs/telegram/package.json +26 -0
  171. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -0
  172. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
  173. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
  174. package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
  175. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +156 -0
  176. package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
  177. package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
  178. package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
  179. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
  180. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
  181. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
  182. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
  183. package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
  184. package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
  185. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
  186. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
  187. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
  188. package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
  189. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  190. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  191. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  192. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +201 -0
  193. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +109 -0
  194. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  195. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  196. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  197. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +58 -0
  198. package/runtime/node_modules/@metro-labs/telegram-bot/src/verify.ts +38 -0
  199. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  200. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  201. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +37 -0
  202. package/runtime/node_modules/@metro-labs/whatsapp/package.json +27 -0
  203. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -0
  204. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  205. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
  206. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  207. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  208. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +327 -0
  209. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  210. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +138 -0
  211. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  212. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  213. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  214. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  215. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  216. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  217. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  218. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  219. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +185 -0
  220. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
  221. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  222. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  223. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  224. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  225. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +112 -0
  226. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  227. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  228. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  229. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  230. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  231. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  232. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  233. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  234. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  235. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +335 -0
  236. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +80 -0
  237. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  238. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  239. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  240. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  241. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  242. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  243. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  244. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  245. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  246. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  247. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  248. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  249. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +53 -0
  250. package/runtime/runtime.json +3 -0
  251. package/runtime/server.ts +1 -0
  252. package/runtime/stations.json +36 -0
  253. package/runtime/trains/.keep +0 -0
  254. package/LICENSE +0 -21
  255. package/README.md +0 -249
  256. package/dist/cache.js +0 -75
  257. package/dist/cli/actions.js +0 -139
  258. package/dist/cli/config.js +0 -185
  259. package/dist/cli/index.js +0 -177
  260. package/dist/cli/skill.js +0 -62
  261. package/dist/cli/util.js +0 -72
  262. package/dist/cli/webhook.js +0 -83
  263. package/dist/codex-rc.js +0 -245
  264. package/dist/dispatcher.js +0 -122
  265. package/dist/history.js +0 -99
  266. package/dist/ipc.js +0 -72
  267. package/dist/log.js +0 -15
  268. package/dist/paths.js +0 -68
  269. package/dist/registry.js +0 -48
  270. package/dist/stations/claude.js +0 -45
  271. package/dist/stations/codex.js +0 -68
  272. package/dist/stations/discord.js +0 -200
  273. package/dist/stations/index.js +0 -133
  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/dist/stations/webhook.js +0 -100
  278. package/dist/tunnel.js +0 -49
  279. package/dist/webhooks.js +0 -40
  280. package/docs/agents.md +0 -216
  281. package/docs/uri-scheme.md +0 -125
  282. package/skills/metro/SKILL.md +0 -249
@@ -0,0 +1,349 @@
1
+ import { errMsg } from '@metro-labs/core/log';
2
+ import { type DecodedMessage } from '@xmtp/node-sdk';
3
+ import { existsSync, readFileSync, mkdirSync } from 'node:fs';
4
+ import { dirname } from 'node:path';
5
+ import { readJson, writeJson } from '@metro-labs/core/secure-fs';
6
+ import { parseLine } from './accounts.js';
7
+
8
+ const FCM_SVC_PATH = `${process.env.HOME}/.config/metro/firebase-service-account.json`;
9
+ const FCM_TOKENS_PATH = `${process.env.HOME}/.cache/metro/xmtp-push-tokens.json`;
10
+ interface FcmServiceAccount {
11
+ client_email: string;
12
+ private_key: string;
13
+ project_id: string;
14
+ token_uri: string;
15
+ }
16
+
17
+ interface StoredPushToken {
18
+ token: string;
19
+ registeredAt: string;
20
+ account?: string;
21
+ inboxId?: string;
22
+ inboxIds?: string[];
23
+ platform?: string;
24
+ lastSeenAt?: string;
25
+ }
26
+
27
+ function loadFcmSvc(): FcmServiceAccount | null {
28
+ if (!existsSync(FCM_SVC_PATH)) return null;
29
+ try {
30
+ return JSON.parse(readFileSync(FCM_SVC_PATH, 'utf8')) as FcmServiceAccount;
31
+ } catch (err) {
32
+ process.stderr.write(
33
+ `fcm: bad service account: ${errMsg(err)}\n`,
34
+ );
35
+ return null;
36
+ }
37
+ }
38
+ export function loadPushTokens(): StoredPushToken[] {
39
+ return readJson<StoredPushToken[]>(FCM_TOKENS_PATH, []);
40
+ }
41
+ export function savePushTokens(tokens: StoredPushToken[]): void {
42
+ mkdirSync(dirname(FCM_TOKENS_PATH), { recursive: true });
43
+ writeJson(FCM_TOKENS_PATH, tokens);
44
+ }
45
+
46
+ function collectInboxIds(
47
+ existing: StoredPushToken | undefined,
48
+ entryInboxId: string | undefined,
49
+ ): Set<string> {
50
+ const inboxIds = new Set<string>(existing?.inboxIds ?? []);
51
+ if (existing?.inboxId) inboxIds.add(existing.inboxId);
52
+ if (entryInboxId) inboxIds.add(entryInboxId);
53
+ return inboxIds;
54
+ }
55
+
56
+ export function storePushToken(entry: {
57
+ token: string;
58
+ account?: string;
59
+ inboxId?: string;
60
+ platform?: string;
61
+ }): number {
62
+ const now = new Date().toISOString();
63
+ const all = loadPushTokens();
64
+ const existing = all.find((t) => t.token === entry.token);
65
+ const remaining = all.filter((t) => t.token !== entry.token);
66
+ const inboxIds = collectInboxIds(existing, entry.inboxId);
67
+ const row: StoredPushToken = {
68
+ token: entry.token,
69
+ registeredAt: existing?.registeredAt ?? now,
70
+ lastSeenAt: now,
71
+ };
72
+ if (entry.account) row.account = entry.account;
73
+ if (entry.inboxId) row.inboxId = entry.inboxId;
74
+ if (inboxIds.size) row.inboxIds = [...inboxIds];
75
+ if (entry.platform) row.platform = entry.platform;
76
+ remaining.push(row);
77
+ savePushTokens(remaining);
78
+ return remaining.length;
79
+ }
80
+
81
+ let cachedAccessToken: { token: string; expiresAt: number } | null = null;
82
+ async function fcmAccessToken(): Promise<string | null> {
83
+ const svc = loadFcmSvc();
84
+ if (!svc) return null;
85
+ if (cachedAccessToken && cachedAccessToken.expiresAt > Date.now() + 60_000)
86
+ return cachedAccessToken.token;
87
+ const { createSign } = await import('node:crypto');
88
+ const enc = (o: unknown): string =>
89
+ Buffer.from(JSON.stringify(o)).toString('base64url');
90
+ const now = Math.floor(Date.now() / 1000);
91
+ const header = { alg: 'RS256', typ: 'JWT' };
92
+ const payload = {
93
+ iss: svc.client_email,
94
+ scope: 'https://www.googleapis.com/auth/firebase.messaging',
95
+ aud: svc.token_uri,
96
+ iat: now,
97
+ exp: now + 3600,
98
+ };
99
+ const sigInput = `${enc(header)}.${enc(payload)}`;
100
+ const signer = createSign('RSA-SHA256');
101
+ signer.update(sigInput);
102
+ signer.end();
103
+ const sig = signer.sign(svc.private_key).toString('base64url');
104
+ const jwt = `${sigInput}.${sig}`;
105
+ const grant = encodeURIComponent(
106
+ 'urn:ietf:params:oauth:grant-type:jwt-bearer',
107
+ );
108
+ const res = await fetch(svc.token_uri, {
109
+ method: 'POST',
110
+ headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
111
+ body: `grant_type=${grant}&assertion=${jwt}`,
112
+ signal: AbortSignal.timeout(15_000),
113
+ });
114
+ if (!res.ok) {
115
+ process.stderr.write(
116
+ `fcm token exchange ${res.status}: ${await res.text()}\n`,
117
+ );
118
+ return null;
119
+ }
120
+ const json = (await res.json()) as {
121
+ access_token: string;
122
+ expires_in: number;
123
+ };
124
+ cachedAccessToken = {
125
+ token: json.access_token,
126
+ expiresAt: Date.now() + json.expires_in * 1000,
127
+ };
128
+ return cachedAccessToken.token;
129
+ }
130
+
131
+ async function fcmPushTo(
132
+ deviceToken: string,
133
+ data: Record<string, string> = {},
134
+ ): Promise<void> {
135
+ const svc = loadFcmSvc();
136
+ if (!svc) return;
137
+ const at = await fcmAccessToken();
138
+ if (!at) return;
139
+ const url = `https://fcm.googleapis.com/v1/projects/${svc.project_id}/messages:send`;
140
+ const payloadData: Record<string, string> = { channelId: 'xmtp', ...data };
141
+ const res = await fetch(url, {
142
+ method: 'POST',
143
+ headers: {
144
+ 'Content-Type': 'application/json',
145
+ Authorization: `Bearer ${at}`,
146
+ },
147
+ body: JSON.stringify({
148
+ message: {
149
+ token: deviceToken,
150
+ android: { priority: 'HIGH' },
151
+ data: payloadData,
152
+ },
153
+ }),
154
+ signal: AbortSignal.timeout(15_000),
155
+ });
156
+ if (!res.ok) {
157
+ const txt = await res.text().catch(() => '');
158
+ const dead =
159
+ res.status === 404 ||
160
+ txt.includes('UNREGISTERED') ||
161
+ txt.includes('NOT_FOUND') ||
162
+ txt.includes('registration-token-not-registered') ||
163
+ txt.includes('NotRegistered');
164
+ if (dead) {
165
+ removePushToken(deviceToken);
166
+ process.stderr.write(
167
+ `fcm: pruned stale token ${deviceToken.slice(0, 12)}…\n`,
168
+ );
169
+ return;
170
+ }
171
+ process.stderr.write(`fcm push ${res.status}: ${txt}\n`);
172
+ }
173
+ }
174
+
175
+ function freshestPerAccount(tokens: StoredPushToken[]): StoredPushToken[] {
176
+ const freshness = (t: StoredPushToken): number =>
177
+ new Date(t.lastSeenAt ?? t.registeredAt ?? 0).getTime();
178
+ const byAccount = new Map<string, StoredPushToken>();
179
+ const unscoped: StoredPushToken[] = [];
180
+ for (const t of tokens) {
181
+ if (!t.account) {
182
+ unscoped.push(t);
183
+ continue;
184
+ }
185
+ const cur = byAccount.get(t.account);
186
+ if (!cur || freshness(t) >= freshness(cur)) byAccount.set(t.account, t);
187
+ }
188
+ return [...byAccount.values(), ...unscoped];
189
+ }
190
+
191
+ export async function fcmPushToAll(
192
+ accountId: string,
193
+ data: Record<string, string> = {},
194
+ excludeInboxId?: string,
195
+ ): Promise<void> {
196
+ const tokens = loadPushTokens().filter((t) => {
197
+ if (t.account && t.account !== accountId) return false;
198
+ if (
199
+ excludeInboxId &&
200
+ (t.inboxId === excludeInboxId || t.inboxIds?.includes(excludeInboxId))
201
+ )
202
+ return false;
203
+ return true;
204
+ });
205
+ const deduped = freshestPerAccount(tokens);
206
+ if (deduped.length === 0) return;
207
+ await Promise.all(
208
+ deduped.map((t) =>
209
+ fcmPushTo(t.token, { ...data, account: accountId }).catch(
210
+ () => undefined,
211
+ ),
212
+ ),
213
+ );
214
+ }
215
+
216
+ const METRO_CTRL_PREFIX = 'METRO_CTRL:';
217
+ const CTRL_REGISTER_PUSH = 'register-push';
218
+ const CTRL_DISABLE_PUSH = 'disable-push';
219
+
220
+ export function removePushToken(token: string): number {
221
+ const all = loadPushTokens();
222
+ const remaining = all.filter((t) => t.token !== token);
223
+ if (remaining.length === all.length) return -1;
224
+ savePushTokens(remaining);
225
+ return remaining.length;
226
+ }
227
+
228
+ function isMetroControlBody(text: unknown): text is string {
229
+ return typeof text === 'string' && text.startsWith(METRO_CTRL_PREFIX);
230
+ }
231
+
232
+ interface RegisterPushPayload {
233
+ v?: number;
234
+ token: string;
235
+ platform?: string;
236
+ address?: string;
237
+ inboxId?: string;
238
+ }
239
+ interface DisablePushPayload {
240
+ v?: number;
241
+ token: string;
242
+ address?: string;
243
+ inboxId?: string;
244
+ }
245
+
246
+ function isValidPushToken(token: unknown): token is string {
247
+ return typeof token === 'string' && token.length >= 20;
248
+ }
249
+
250
+ function handleRegisterPushCtrl(
251
+ accountId: string,
252
+ msg: DecodedMessage,
253
+ arg: string,
254
+ ): void {
255
+ const obj = JSON.parse(arg) as Partial<RegisterPushPayload>;
256
+ if (!obj || !isValidPushToken(obj.token)) {
257
+ process.stderr.write(
258
+ `xmtp[${accountId}]: register-push (ctrl-dm) ignored — bad/short token\n`,
259
+ );
260
+ return;
261
+ }
262
+ const total = storePushToken({
263
+ token: obj.token,
264
+ account: accountId,
265
+ inboxId: msg.senderInboxId,
266
+ platform: typeof obj.platform === 'string' ? obj.platform : undefined,
267
+ });
268
+ process.stderr.write(
269
+ `xmtp[${accountId}]: register-push (ctrl-dm) stored token ${obj.token.slice(0, 12)}… ` +
270
+ `from inbox ${msg.senderInboxId.slice(0, 10)}… (v=${obj.v ?? '?'}, ${total} total)\n`,
271
+ );
272
+ }
273
+
274
+ function handleDisablePushCtrl(accountId: string, arg: string): void {
275
+ const obj = JSON.parse(arg) as Partial<DisablePushPayload>;
276
+ if (!obj || !isValidPushToken(obj.token)) {
277
+ process.stderr.write(
278
+ `xmtp[${accountId}]: disable-push (ctrl-dm) ignored — bad/short token\n`,
279
+ );
280
+ return;
281
+ }
282
+ const remaining = removePushToken(obj.token);
283
+ process.stderr.write(
284
+ `xmtp[${accountId}]: disable-push (ctrl-dm) token ${obj.token.slice(0, 12)}… ` +
285
+ (remaining === -1
286
+ ? 'not found (already gone)\n'
287
+ : `removed (v=${obj.v ?? '?'}, ${remaining} remain)\n`),
288
+ );
289
+ }
290
+
291
+ export function handleControlDm(
292
+ accountId: string,
293
+ msg: DecodedMessage,
294
+ ): boolean {
295
+ const body = msg.content;
296
+ if (!isMetroControlBody(body)) return false;
297
+ const rest = body.slice(METRO_CTRL_PREFIX.length);
298
+ const sep = rest.indexOf(':');
299
+ const verb = sep === -1 ? rest : rest.slice(0, sep);
300
+ const arg = sep === -1 ? '' : rest.slice(sep + 1);
301
+ try {
302
+ if (verb === CTRL_REGISTER_PUSH) {
303
+ handleRegisterPushCtrl(accountId, msg, arg);
304
+ } else if (verb === CTRL_DISABLE_PUSH) {
305
+ handleDisablePushCtrl(accountId, arg);
306
+ } else {
307
+ process.stderr.write(
308
+ `xmtp[${accountId}]: unknown control verb '${verb}' — swallowed\n`,
309
+ );
310
+ }
311
+ } catch (err) {
312
+ process.stderr.write(
313
+ `xmtp[${accountId}]: control DM '${verb}' FAILED: ${errMsg(err)}\n`,
314
+ );
315
+ }
316
+ return true;
317
+ }
318
+
319
+ export function pushInbound(
320
+ accountId: string,
321
+ env: Record<string, unknown>,
322
+ msg: DecodedMessage,
323
+ conv?: unknown,
324
+ ): void {
325
+ const line = typeof env.line === 'string' ? env.line : '';
326
+ const messageId =
327
+ typeof env.message_id === 'string'
328
+ ? env.message_id
329
+ : typeof env.id === 'string'
330
+ ? env.id
331
+ : '';
332
+ void (async (): Promise<void> => {
333
+ const data: Record<string, string> = {
334
+ line,
335
+ messageId,
336
+ account: accountId,
337
+ };
338
+ {
339
+ const p = parseLine(line);
340
+ if (p) data.convId = p.convId.toLowerCase();
341
+ }
342
+ if (conv) {
343
+ const isDm =
344
+ typeof (conv as { peerInboxId?: unknown }).peerInboxId === 'function';
345
+ if (!isDm) data.isGroup = 'true';
346
+ }
347
+ await fcmPushToAll(accountId, data, msg.senderInboxId);
348
+ })().catch(() => undefined);
349
+ }
@@ -0,0 +1,13 @@
1
+ import type { GroupOp, Station, Verb } from '@metro-labs/core/stations/types';
2
+ import { XMTP_TOOLS, xmtpSendAttachments } from './tools.js';
3
+
4
+ export const xmtpStation: Station = {
5
+ name: 'xmtp',
6
+ hasAccounts: true,
7
+ hasTrain: true,
8
+ messageVerbs: new Set<Verb>(['send', 'reply', 'react', 'unreact', 'read']),
9
+ groupOps: new Set<GroupOp>(['create_group', 'add_members', 'remove_members']),
10
+ attachmentMode: 'native',
11
+ sendAttachments: xmtpSendAttachments,
12
+ tools: XMTP_TOOLS,
13
+ };
@@ -0,0 +1,126 @@
1
+ import type { CanonicalAttachment, ToolContext } from '@metro-labs/core/stations/types';
2
+ import { TrainError } from '@metro-labs/core/train-error';
3
+ import {
4
+ guessMime,
5
+ isImageMime,
6
+ isImageExt,
7
+ kindOf,
8
+ } from '@metro-labs/core/stations/attachments';
9
+
10
+ export const str = (v: unknown): string => (typeof v === 'string' ? v : '');
11
+
12
+ function strList(value: unknown): string[] {
13
+ return (value as unknown[] | undefined)?.map(String).filter(Boolean) ?? [];
14
+ }
15
+
16
+ interface CreatedGroup {
17
+ line?: string;
18
+ id?: string;
19
+ account?: string;
20
+ }
21
+
22
+ async function applyCreateLabels(
23
+ line: string,
24
+ labels: string[],
25
+ ctx: ToolContext,
26
+ ): Promise<unknown> {
27
+ if (!labels.length) return undefined;
28
+ return ctx.call('updateChannelMeta', { line, appData: { labels } });
29
+ }
30
+
31
+ export async function createChannel(
32
+ a: Record<string, unknown>,
33
+ ctx: ToolContext,
34
+ ) {
35
+ const addresses = strList(a.addresses);
36
+ const channelName = str(a.name);
37
+ const labels = strList(a.labels);
38
+ const account = str(a.account) || undefined;
39
+ if (!addresses.length)
40
+ return ctx.err('create_channel requires a non-empty `addresses` array');
41
+ if (!channelName) return ctx.err('create_channel requires `name`');
42
+ const groupArgs: Record<string, unknown> = { addresses, name: channelName };
43
+ if (account) groupArgs.account = account;
44
+ const { result } = (await ctx.call('newGroup', groupArgs)) as {
45
+ result: CreatedGroup | null;
46
+ };
47
+ const newLine = result?.line ?? '';
48
+ const labelResult = newLine.length
49
+ ? await applyCreateLabels(newLine, labels, ctx)
50
+ : undefined;
51
+ return ctx.okJson({
52
+ line: newLine,
53
+ convId: result?.id,
54
+ account: result?.account,
55
+ labels: labelResult,
56
+ });
57
+ }
58
+
59
+ export async function setChannelMetadata(
60
+ a: Record<string, unknown>,
61
+ ctx: ToolContext,
62
+ ) {
63
+ const line = str(a.line);
64
+ if (!line) return ctx.err('set_channel_metadata requires `line`');
65
+ const labels = a.labels as unknown[] | undefined;
66
+ const github = a.github as string | undefined;
67
+ const preview = a.preview as string | undefined;
68
+ const metaName = a.name as string | undefined;
69
+ const appData: Record<string, unknown> = {};
70
+ if (Array.isArray(labels)) appData.labels = labels.map(String);
71
+ if (typeof github === 'string') appData.github = github;
72
+ if (typeof preview === 'string') appData.preview = preview;
73
+ const hasName = typeof metaName === 'string' && metaName.length > 0;
74
+ if (!hasName && Object.keys(appData).length === 0)
75
+ return ctx.err(
76
+ 'set_channel_metadata requires at least one of `labels`, `github`, `preview`, `name`',
77
+ );
78
+ const callArgs: Record<string, unknown> = { line, appData };
79
+ if (hasName) callArgs.name = metaName;
80
+ return ctx.okJson(await ctx.call('updateChannelMeta', callArgs));
81
+ }
82
+
83
+ const XMTP_ATTACH_MAX_BYTES = 190 * 1024;
84
+
85
+ async function sendFileAttachment(
86
+ line: string,
87
+ a: CanonicalAttachment,
88
+ src: string,
89
+ ctx: ToolContext,
90
+ ): Promise<void> {
91
+ const buf = await ctx.readFile(src);
92
+ if (buf.byteLength > XMTP_ATTACH_MAX_BYTES) {
93
+ throw new TrainError(
94
+ 'attachment_too_large',
95
+ `attachment '${src}' is ${(buf.byteLength / 1024).toFixed(0)} KiB; xmtp non-image files ` +
96
+ 'over ~190 KiB (256 KiB once base64-encoded) cannot be sent via this MCP path. ' +
97
+ 'Send it as an image, host it elsewhere, or use the metro CLI directly.',
98
+ );
99
+ }
100
+ await ctx.call('sendAttachment', {
101
+ line,
102
+ name: a.name ?? src.split('/').pop() ?? 'attachment',
103
+ mime: a.mime ?? guessMime(src),
104
+ dataB64: buf.toString('base64'),
105
+ });
106
+ }
107
+
108
+ export async function xmtpSendAttachments(
109
+ line: string,
110
+ atts: CanonicalAttachment[],
111
+ ctx: ToolContext,
112
+ ): Promise<string[]> {
113
+ const sent: string[] = [];
114
+ for (const a of atts) {
115
+ const src = a.path ?? a.url ?? '';
116
+ if (!src) continue;
117
+ const mime = a.mime ?? guessMime(src);
118
+ if (isImageMime(mime) || isImageExt(src)) {
119
+ await ctx.call('sendImage', { line, path: src });
120
+ } else {
121
+ await sendFileAttachment(line, a, src, ctx);
122
+ }
123
+ sent.push(kindOf(mime, src));
124
+ }
125
+ return sent;
126
+ }
@@ -0,0 +1,212 @@
1
+ import type { StationTool } from '@metro-labs/core/stations/types';
2
+ import {
3
+ str,
4
+ createChannel,
5
+ setChannelMetadata,
6
+ xmtpSendAttachments,
7
+ } from './tools-handlers.js';
8
+
9
+ export { xmtpSendAttachments };
10
+
11
+ const lineProp = {
12
+ type: 'string',
13
+ description:
14
+ 'The metro:// line (from the inbound <channel> tag). The station is derived from it.',
15
+ } as const;
16
+
17
+ export const XMTP_TOOLS: StationTool[] = [
18
+ {
19
+ name: 'create_channel',
20
+ description:
21
+ 'Create a new XMTP group conversation (channel). Args: addresses (required, array of ' +
22
+ 'Ethereum 0x addresses to add as members), name (required, the group name), labels? ' +
23
+ '(optional string[] status labels applied after creation), account? ' +
24
+ '(defaults to your only XMTP account). Calls the daemon xmtp `newGroup`, then ' +
25
+ '`updateChannelMeta` if labels ' +
26
+ 'are given. Returns the new metro:// line and convId. This is an xmtp-only operation. ' +
27
+ 'NOTE: there is no add-members verb on the daemon, so members must be supplied at ' +
28
+ 'creation time.',
29
+ inputSchema: {
30
+ type: 'object',
31
+ properties: {
32
+ addresses: {
33
+ type: 'array',
34
+ description: 'Ethereum 0x addresses to add as group members.',
35
+ items: { type: 'string' },
36
+ },
37
+ name: { type: 'string', description: 'The group/channel name.' },
38
+ labels: {
39
+ type: 'array',
40
+ description: 'Optional status labels to apply after creation.',
41
+ items: { type: 'string' },
42
+ },
43
+ account: {
44
+ type: 'string',
45
+ description:
46
+ 'XMTP account to create under. Omit when you have only one.',
47
+ },
48
+ },
49
+ required: ['addresses', 'name'],
50
+ },
51
+ handle: (a, ctx) => createChannel(a, ctx),
52
+ },
53
+ {
54
+ name: 'ask',
55
+ description:
56
+ 'Ask a question as a poll in an XMTP conversation (mirrors Claude AskUserQuestion). ' +
57
+ 'Single-question form: question (required), options? (string[]), header?, multiSelect?, ' +
58
+ 'open? (true => free-text answer, options optional). Multi-question form: questions ' +
59
+ '(array of {question, options?, header?, multiSelect?, open?}). Args: line (required) + ' +
60
+ 'the above. xmtp-only (the daemon `ask` action). Returns the poll messageId + pollId.',
61
+ inputSchema: {
62
+ type: 'object',
63
+ properties: {
64
+ line: lineProp,
65
+ question: {
66
+ type: 'string',
67
+ description: 'The question text (single-question form).',
68
+ },
69
+ options: {
70
+ type: 'array',
71
+ description: 'Answer options for a single question.',
72
+ items: { type: 'string' },
73
+ },
74
+ header: {
75
+ type: 'string',
76
+ description: 'Optional header/title for the poll.',
77
+ },
78
+ multiSelect: {
79
+ type: 'boolean',
80
+ description: 'Allow selecting multiple options.',
81
+ },
82
+ open: {
83
+ type: 'boolean',
84
+ description: 'Free-text answer (options optional).',
85
+ },
86
+ questions: {
87
+ type: 'array',
88
+ description:
89
+ 'Multiple questions (multi-question form). Each is {question, options?, header?, multiSelect?, open?}.',
90
+ items: { type: 'object' },
91
+ },
92
+ },
93
+ required: ['line'],
94
+ },
95
+ async handle(a, ctx) {
96
+ const line = str(a.line);
97
+ if (!line) return ctx.err('ask requires `line`');
98
+ const args: Record<string, unknown> = { line };
99
+ for (const k of [
100
+ 'question',
101
+ 'options',
102
+ 'header',
103
+ 'multiSelect',
104
+ 'open',
105
+ 'questions',
106
+ ] as const) {
107
+ if (a[k] !== undefined) args[k] = a[k];
108
+ }
109
+ if (a.question === undefined && a.questions === undefined) {
110
+ return ctx.err(
111
+ 'ask requires `question` (single) or `questions` (multi)',
112
+ );
113
+ }
114
+ return ctx.okJson(await ctx.call('ask', args));
115
+ },
116
+ },
117
+ {
118
+ name: 'dm',
119
+ description:
120
+ 'Open (or reuse) a 1:1 XMTP DM with an Ethereum address. Args: address (required, 0x...), ' +
121
+ 'account? (defaults to your only XMTP account). Returns the new metro:// line and ' +
122
+ 'convId. xmtp-only ' +
123
+ '(daemon `newDm`). Use this instead of create_channel when there is a single recipient.',
124
+ inputSchema: {
125
+ type: 'object',
126
+ properties: {
127
+ address: {
128
+ type: 'string',
129
+ description: 'Recipient Ethereum 0x address.',
130
+ },
131
+ account: {
132
+ type: 'string',
133
+ description: 'XMTP account to DM from. Omit when you have only one.',
134
+ },
135
+ },
136
+ required: ['address'],
137
+ },
138
+ async handle(a, ctx) {
139
+ const address = str(a.address);
140
+ if (!address) return ctx.err('dm requires `address`');
141
+ const dmArgs: Record<string, unknown> = { address };
142
+ if (a.account) dmArgs.account = str(a.account);
143
+ return ctx.okJson(await ctx.call('newDm', dmArgs));
144
+ },
145
+ },
146
+ {
147
+ name: 'group_info',
148
+ description:
149
+ "Read an XMTP channel's current metadata + membership. Args: line (required). Returns " +
150
+ '{line, id, account, version (dm|group), name, memberCount, labels, github, preview, ' +
151
+ 'members:[{inboxId, address}]}. xmtp-only (daemon `groupInfo`). Use before ' +
152
+ 'set_channel_metadata/add_members to see current state.',
153
+ inputSchema: {
154
+ type: 'object',
155
+ properties: { line: lineProp },
156
+ required: ['line'],
157
+ },
158
+ async handle(a, ctx) {
159
+ const line = str(a.line);
160
+ if (!line) return ctx.err('group_info requires `line`');
161
+ return ctx.okJson(await ctx.call('groupInfo', { line }));
162
+ },
163
+ },
164
+ {
165
+ name: 'close_channel',
166
+ description:
167
+ 'Archive/close an XMTP group (removes members). Args: line (required). xmtp-only ' +
168
+ '(daemon `closeGroup`). Irreversible-ish: members are removed from the group.',
169
+ inputSchema: {
170
+ type: 'object',
171
+ properties: { line: lineProp },
172
+ required: ['line'],
173
+ },
174
+ async handle(a, ctx) {
175
+ const line = str(a.line);
176
+ if (!line) return ctx.err('close_channel requires `line`');
177
+ return ctx.okJson(await ctx.call('closeGroup', { line }));
178
+ },
179
+ },
180
+ {
181
+ name: 'set_channel_metadata',
182
+ description:
183
+ "Update an existing channel's metadata. Args: line (required, the metro:// line), and " +
184
+ 'any of labels? (string[]), github? (url), preview? (url), name? (string). All provided ' +
185
+ 'fields are applied together in one atomic updateChannelMeta daemon call (a single ' +
186
+ 'appData merge plus name update). xmtp-only ' +
187
+ '(channel metadata lives on xmtp groups). Returns the updated channel info.',
188
+ inputSchema: {
189
+ type: 'object',
190
+ properties: {
191
+ line: lineProp,
192
+ labels: {
193
+ type: 'array',
194
+ description: 'Status labels to set.',
195
+ items: { type: 'string' },
196
+ },
197
+ github: {
198
+ type: 'string',
199
+ description: 'Linked GitHub URL ("" to clear).',
200
+ },
201
+ preview: {
202
+ type: 'string',
203
+ description: 'Linked preview URL ("" to clear).',
204
+ },
205
+ name: { type: 'string', description: 'New channel name.' },
206
+ },
207
+ required: ['line'],
208
+ },
209
+ handle: (a, ctx) => setChannelMetadata(a, ctx),
210
+ },
211
+ ];
212
+