@stage-labs/metro 0.1.0-beta.2 → 0.1.0-beta.21

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 (225) hide show
  1. package/dist/api.js +103 -0
  2. package/dist/cli.js +134 -565
  3. package/dist/control.js +102 -0
  4. package/dist/prompt.js +10 -0
  5. package/dist/runtime.js +94 -0
  6. package/dist/store.js +70 -0
  7. package/dist/update.js +33 -0
  8. package/dist/version.js +97 -0
  9. package/package.json +34 -22
  10. package/runtime/node_modules/@metro-labs/discord/package.json +28 -0
  11. package/runtime/node_modules/@metro-labs/discord/src/accounts.ts +119 -0
  12. package/runtime/node_modules/@metro-labs/discord/src/actions.ts +388 -0
  13. package/runtime/node_modules/@metro-labs/discord/src/api-base.ts +1 -0
  14. package/runtime/node_modules/@metro-labs/discord/src/attachments.ts +29 -0
  15. package/runtime/node_modules/@metro-labs/discord/src/format.ts +210 -0
  16. package/runtime/node_modules/@metro-labs/discord/src/group-actions.ts +50 -0
  17. package/runtime/node_modules/@metro-labs/discord/src/group.ts +115 -0
  18. package/runtime/node_modules/@metro-labs/discord/src/index.ts +127 -0
  19. package/runtime/node_modules/@metro-labs/discord/src/members.ts +215 -0
  20. package/runtime/node_modules/@metro-labs/discord/src/send-files.ts +43 -0
  21. package/runtime/node_modules/@metro-labs/discord/src/station.ts +19 -0
  22. package/runtime/node_modules/@metro-labs/discord/src/verify.ts +82 -0
  23. package/runtime/node_modules/@metro-labs/discord/src/voice-speak.ts +195 -0
  24. package/runtime/node_modules/@metro-labs/discord/src/voice-transcribe.ts +292 -0
  25. package/runtime/node_modules/@metro-labs/discord/src/voice.ts +197 -0
  26. package/runtime/node_modules/@metro-labs/discord/src/wire.ts +5 -0
  27. package/runtime/node_modules/@metro-labs/mcp/package.json +66 -0
  28. package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +24 -0
  29. package/runtime/node_modules/@metro-labs/mcp/src/channels/inbound.ts +367 -0
  30. package/runtime/node_modules/@metro-labs/mcp/src/channels/media-note.ts +85 -0
  31. package/runtime/node_modules/@metro-labs/mcp/src/channels/pending.ts +75 -0
  32. package/runtime/node_modules/@metro-labs/mcp/src/channels/relay.ts +129 -0
  33. package/runtime/node_modules/@metro-labs/mcp/src/channels/webhook-note.ts +51 -0
  34. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +311 -0
  35. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +335 -0
  36. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-error.ts +8 -0
  37. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +129 -0
  38. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-grant.ts +99 -0
  39. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-owner.ts +32 -0
  40. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +124 -0
  41. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +278 -0
  42. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +322 -0
  43. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +111 -0
  44. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair.ts +20 -0
  45. package/runtime/node_modules/@metro-labs/mcp/src/daemon/collection-api.ts +202 -0
  46. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +338 -0
  47. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +31 -0
  48. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +157 -0
  49. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +127 -0
  50. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +100 -0
  51. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +57 -0
  52. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +333 -0
  53. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cors.ts +29 -0
  54. package/runtime/node_modules/@metro-labs/mcp/src/daemon/crash-guard.ts +49 -0
  55. package/runtime/node_modules/@metro-labs/mcp/src/daemon/events.ts +117 -0
  56. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-auth.ts +164 -0
  57. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-oauth.ts +208 -0
  58. package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +357 -0
  59. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +79 -0
  60. package/runtime/node_modules/@metro-labs/mcp/src/daemon/log.ts +42 -0
  61. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-client.ts +162 -0
  62. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-discovery.ts +120 -0
  63. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +65 -0
  64. package/runtime/node_modules/@metro-labs/mcp/src/daemon/pair-codes.ts +58 -0
  65. package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +134 -0
  66. package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +214 -0
  67. package/runtime/node_modules/@metro-labs/mcp/src/daemon/protocol.ts +224 -0
  68. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +125 -0
  69. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-pair.ts +18 -0
  70. package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-source.ts +105 -0
  71. package/runtime/node_modules/@metro-labs/mcp/src/daemon/secure-fs.ts +57 -0
  72. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +27 -0
  73. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +41 -0
  74. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +218 -0
  75. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor-io.ts +97 -0
  76. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +323 -0
  77. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-call.ts +22 -0
  78. package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-error.ts +53 -0
  79. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +169 -0
  80. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-api.ts +295 -0
  81. package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-store.ts +181 -0
  82. package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +91 -0
  83. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +215 -0
  84. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +71 -0
  85. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-scope.ts +64 -0
  86. package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +37 -0
  87. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-collections.ts +218 -0
  88. package/runtime/node_modules/@metro-labs/mcp/src/db/connector-config.ts +147 -0
  89. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +329 -0
  90. package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +19 -0
  91. package/runtime/node_modules/@metro-labs/mcp/src/db/key-map.ts +45 -0
  92. package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +364 -0
  93. package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +300 -0
  94. package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +31 -0
  95. package/runtime/node_modules/@metro-labs/mcp/src/db/runtimes.ts +105 -0
  96. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +131 -0
  97. package/runtime/node_modules/@metro-labs/mcp/src/db/token-carry.ts +45 -0
  98. package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +57 -0
  99. package/runtime/node_modules/@metro-labs/mcp/src/db/whatsapp-login.ts +18 -0
  100. package/runtime/node_modules/@metro-labs/mcp/src/mcp/accounts.ts +145 -0
  101. package/runtime/node_modules/@metro-labs/mcp/src/mcp/call-tools.ts +203 -0
  102. package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +29 -0
  103. package/runtime/node_modules/@metro-labs/mcp/src/mcp/ctx.ts +62 -0
  104. package/runtime/node_modules/@metro-labs/mcp/src/mcp/event-store.ts +91 -0
  105. package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +21 -0
  106. package/runtime/node_modules/@metro-labs/mcp/src/mcp/group-tools.ts +129 -0
  107. package/runtime/node_modules/@metro-labs/mcp/src/mcp/index.ts +192 -0
  108. package/runtime/node_modules/@metro-labs/mcp/src/mcp/member-tools.ts +72 -0
  109. package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +66 -0
  110. package/runtime/node_modules/@metro-labs/mcp/src/mcp/raw-get-stream.ts +207 -0
  111. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +73 -0
  112. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +172 -0
  113. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +43 -0
  114. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session.ts +211 -0
  115. package/runtime/node_modules/@metro-labs/mcp/src/mcp/str.ts +5 -0
  116. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-dispatch.ts +212 -0
  117. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +397 -0
  118. package/runtime/node_modules/@metro-labs/mcp/src/mcp/upload-tool.ts +66 -0
  119. package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +240 -0
  120. package/runtime/node_modules/@metro-labs/mcp/src/server.ts +2 -0
  121. package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +80 -0
  122. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-inline.ts +100 -0
  123. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +161 -0
  124. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-resolve.ts +200 -0
  125. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-xmtp.ts +111 -0
  126. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +182 -0
  127. package/runtime/node_modules/@metro-labs/mcp/src/stations/attachments.ts +196 -0
  128. package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +82 -0
  129. package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +88 -0
  130. package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +35 -0
  131. package/runtime/node_modules/@metro-labs/mcp/src/stations/station-runtime.ts +64 -0
  132. package/runtime/node_modules/@metro-labs/mcp/src/stations/types.ts +107 -0
  133. package/runtime/node_modules/@metro-labs/telegram/package.json +25 -0
  134. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +120 -0
  135. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +295 -0
  136. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +128 -0
  137. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +172 -0
  138. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +103 -0
  139. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +166 -0
  140. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +47 -0
  141. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +17 -0
  142. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +48 -0
  143. package/runtime/node_modules/@metro-labs/telegram/src/verify.ts +38 -0
  144. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +3 -0
  145. package/runtime/node_modules/@metro-labs/telegram-user/package.json +28 -0
  146. package/runtime/node_modules/@metro-labs/telegram-user/src/accounts.ts +81 -0
  147. package/runtime/node_modules/@metro-labs/telegram-user/src/actions.ts +254 -0
  148. package/runtime/node_modules/@metro-labs/telegram-user/src/attachments.ts +87 -0
  149. package/runtime/node_modules/@metro-labs/telegram-user/src/client.ts +77 -0
  150. package/runtime/node_modules/@metro-labs/telegram-user/src/format.ts +141 -0
  151. package/runtime/node_modules/@metro-labs/telegram-user/src/group.ts +261 -0
  152. package/runtime/node_modules/@metro-labs/telegram-user/src/history.ts +52 -0
  153. package/runtime/node_modules/@metro-labs/telegram-user/src/inbound.ts +78 -0
  154. package/runtime/node_modules/@metro-labs/telegram-user/src/index.ts +46 -0
  155. package/runtime/node_modules/@metro-labs/telegram-user/src/login.ts +166 -0
  156. package/runtime/node_modules/@metro-labs/telegram-user/src/media-actions.ts +80 -0
  157. package/runtime/node_modules/@metro-labs/telegram-user/src/members.ts +75 -0
  158. package/runtime/node_modules/@metro-labs/telegram-user/src/normalize.ts +22 -0
  159. package/runtime/node_modules/@metro-labs/telegram-user/src/reactions.ts +167 -0
  160. package/runtime/node_modules/@metro-labs/telegram-user/src/station.ts +24 -0
  161. package/runtime/node_modules/@metro-labs/telegram-user/src/types.ts +7 -0
  162. package/runtime/node_modules/@metro-labs/telegram-user/src/wire.ts +1 -0
  163. package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
  164. package/runtime/node_modules/@metro-labs/webhook/src/station.ts +40 -0
  165. package/runtime/node_modules/@metro-labs/whatsapp/package.json +29 -0
  166. package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +67 -0
  167. package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
  168. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
  169. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
  170. package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
  171. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +318 -0
  172. package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
  173. package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +132 -0
  174. package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
  175. package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
  176. package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
  177. package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
  178. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
  179. package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
  180. package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
  181. package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
  182. package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +141 -0
  183. package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
  184. package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
  185. package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
  186. package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
  187. package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
  188. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +113 -0
  189. package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
  190. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
  191. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
  192. package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
  193. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
  194. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
  195. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
  196. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
  197. package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
  198. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +333 -0
  199. package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +78 -0
  200. package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
  201. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
  202. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
  203. package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
  204. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
  205. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
  206. package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
  207. package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
  208. package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
  209. package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
  210. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
  211. package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
  212. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +39 -0
  213. package/runtime/runtime.json +3 -0
  214. package/runtime/trains/.keep +0 -0
  215. package/LICENSE +0 -21
  216. package/README.md +0 -78
  217. package/dist/channels/discord.js +0 -148
  218. package/dist/channels/telegram.js +0 -137
  219. package/dist/lib/address.js +0 -21
  220. package/dist/lib/codex-rc.js +0 -274
  221. package/dist/lib/dotenv.js +0 -31
  222. package/dist/log.js +0 -12
  223. package/dist/paths.js +0 -45
  224. package/dist/tail.js +0 -161
  225. package/skills/metro/SKILL.md +0 -122
@@ -0,0 +1,87 @@
1
+ import { accounts } from './accounts.js';
2
+ import { respond } from './wire.js';
3
+ import {
4
+ fcmPushToAll,
5
+ loadPushTokens,
6
+ removePushToken,
7
+ savePushTokens,
8
+ storePushToken,
9
+ } from './push.js';
10
+
11
+ type Args = Record<string, unknown>;
12
+ type Handler = (id: string, args: Args) => Promise<void>;
13
+
14
+ function registerPush(id: string, args: Args): Promise<void> {
15
+ const { token, account, platform, inboxId } = args as {
16
+ token?: string;
17
+ account?: string;
18
+ platform?: string;
19
+ inboxId?: string;
20
+ };
21
+ if (!token || typeof token !== 'string' || token.length < 20) {
22
+ throw new Error('register-push requires a non-empty FCM device token');
23
+ }
24
+ const total = storePushToken({ token, account, platform, inboxId });
25
+ respond(id, { result: { stored: true, total, account: account ?? null } });
26
+ return Promise.resolve();
27
+ }
28
+
29
+ function listPush(id: string): Promise<void> {
30
+ const tokens = loadPushTokens();
31
+ respond(id, {
32
+ result: {
33
+ count: tokens.length,
34
+ tokens: tokens.map((t) => ({
35
+ token: `${t.token.slice(0, 12)}…${t.token.slice(-6)}`,
36
+ registeredAt: t.registeredAt,
37
+ lastSeenAt: t.lastSeenAt ?? null,
38
+ account: t.account ?? null,
39
+ platform: t.platform ?? null,
40
+ inboxId: t.inboxId ?? null,
41
+ })),
42
+ },
43
+ });
44
+ return Promise.resolve();
45
+ }
46
+
47
+ async function testPush(id: string, args: Args): Promise<void> {
48
+ const { account } = args as { account?: string };
49
+ const acctId =
50
+ account ??
51
+ (accounts.size === 1 ? [...accounts.keys()][0] : 'default') ??
52
+ 'default';
53
+ await fcmPushToAll(acctId, { channelId: 'xmtp', source: 'test-push' });
54
+ const sent = loadPushTokens().filter(
55
+ (t) => !t.account || t.account === acctId,
56
+ ).length;
57
+ respond(id, { result: { sent, account: acctId } });
58
+ }
59
+
60
+ function unregisterPush(id: string, args: Args): Promise<void> {
61
+ const { token } = args as { token: string };
62
+ savePushTokens(loadPushTokens().filter((t) => t.token !== token));
63
+ respond(id, { result: { removed: true } });
64
+ return Promise.resolve();
65
+ }
66
+
67
+ function disablePush(id: string, args: Args): Promise<void> {
68
+ const { token } = args as { token?: string };
69
+ if (!token || typeof token !== 'string')
70
+ throw new Error('disable-push requires a token');
71
+ const remaining = removePushToken(token);
72
+ respond(id, {
73
+ result: {
74
+ removed: remaining !== -1,
75
+ remaining: remaining === -1 ? null : remaining,
76
+ },
77
+ });
78
+ return Promise.resolve();
79
+ }
80
+
81
+ export const pushHandlers: Record<string, Handler> = {
82
+ 'register-push': registerPush,
83
+ 'list-push': (id) => listPush(id),
84
+ 'test-push': testPush,
85
+ 'unregister-push': unregisterPush,
86
+ 'disable-push': disablePush,
87
+ };
@@ -0,0 +1,375 @@
1
+ import { ReactionAction, ReactionSchema } from '@xmtp/node-sdk';
2
+ import type { Reply } from '@xmtp/node-bindings';
3
+ import {
4
+ AttachmentCodec,
5
+ type Attachment,
6
+ } from '@xmtp/content-type-remote-attachment';
7
+ import {
8
+ WalletSendCallsCodec,
9
+ type WalletSendCallsParams,
10
+ } from '@xmtp/content-type-wallet-send-calls';
11
+ import { toHex } from 'viem';
12
+ import { convOf } from './accounts.js';
13
+ import { resolveMsgId, respond } from './wire.js';
14
+ import { emitOutbound } from './emit.js';
15
+ import {
16
+ PollCodec,
17
+ buildPollContent,
18
+ SignatureRequestCodec,
19
+ type SignatureRequestContent,
20
+ } from './codecs.js';
21
+ import { convHandlers } from './actions-conv.js';
22
+ import { normalizeXmtp } from '@metro-labs/mcp/stations/messaging-normalize';
23
+ import { TrainError } from '@metro-labs/mcp/train-error';
24
+ import { makeStation, type CallMsg } from '@metro-labs/mcp/stations/station-runtime';
25
+
26
+ type Args = Record<string, unknown>;
27
+
28
+ const noConv = (line: string): TrainError =>
29
+ new TrainError('NOT_FOUND', `conversation not found for ${line}`);
30
+ const badArgs = (message: string): TrainError =>
31
+ new TrainError('INVALID_ARGS', message);
32
+
33
+ async function send(id: string, args: Args): Promise<void> {
34
+ const { line, text } = args as { line: string; text: string };
35
+ const { acct, conv } = await convOf(line);
36
+ if (!conv) throw noConv(line);
37
+ const messageId = await conv.sendText(text);
38
+ emitOutbound(acct.cfg.id, line, messageId, text);
39
+ respond(id, { result: { messageId } });
40
+ }
41
+
42
+ async function ask(id: string, args: Args): Promise<void> {
43
+ const { line, pollId } = args as { line: string; pollId?: string };
44
+ const { acct, conv } = await convOf(line);
45
+ if (!conv) throw noConv(line);
46
+ const fallbackId = `poll_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
47
+ const mintedId =
48
+ pollId ??
49
+ (typeof crypto?.randomUUID === 'function'
50
+ ? crypto.randomUUID()
51
+ : fallbackId);
52
+ const { poll, title } = buildPollContent(args, mintedId);
53
+ const sentId = await conv.send(new PollCodec().encode(poll));
54
+ emitOutbound(acct.cfg.id, line, sentId, `📊 Poll: ${title}`);
55
+ respond(id, { result: { messageId: sentId, pollId: mintedId } });
56
+ }
57
+
58
+ async function react(id: string, args: Args): Promise<void> {
59
+ const {
60
+ line,
61
+ messageId,
62
+ emoji,
63
+ action: reactAction,
64
+ schema: reactSchema,
65
+ } = args as {
66
+ line: string;
67
+ messageId: string;
68
+ emoji: string;
69
+ action?: 'added' | 'removed';
70
+ schema?: string;
71
+ referenceInboxId?: string;
72
+ };
73
+ const { acct, conv } = await convOf(line);
74
+ if (!conv) throw noConv(line);
75
+ const xmtpMsgId = resolveMsgId(messageId);
76
+ let refInbox = (args as { referenceInboxId?: string }).referenceInboxId;
77
+ if (!refInbox) {
78
+ const recent = await conv.messages({ limit: 200, direction: 1 });
79
+ refInbox = recent.find((m) => m.id === xmtpMsgId)?.senderInboxId;
80
+ if (!refInbox)
81
+ throw new TrainError(
82
+ 'NOT_FOUND',
83
+ `could not resolve referenceInboxId for ${xmtpMsgId}`,
84
+ );
85
+ }
86
+ const schemaEnum =
87
+ reactSchema === 'custom'
88
+ ? ReactionSchema.Custom
89
+ : reactSchema === 'shortcode'
90
+ ? ReactionSchema.Shortcode
91
+ : ReactionSchema.Unicode;
92
+ const sentId = await conv.sendReaction({
93
+ reference: xmtpMsgId,
94
+ referenceInboxId: refInbox,
95
+ action:
96
+ reactAction === 'removed' ? ReactionAction.Removed : ReactionAction.Added,
97
+ content: emoji,
98
+ schema: schemaEnum,
99
+ });
100
+ emitOutbound(
101
+ acct.cfg.id,
102
+ line,
103
+ sentId,
104
+ `[react ${emoji}${reactAction === 'removed' ? ' (removed)' : ''}]`,
105
+ { type: 'react', emoji, targetId: xmtpMsgId },
106
+ );
107
+ respond(id, { result: { messageId: sentId } });
108
+ }
109
+
110
+ async function reply(id: string, args: Args): Promise<void> {
111
+ const { line, replyTo, text } = args as {
112
+ line: string;
113
+ replyTo: string;
114
+ text: string;
115
+ };
116
+ const { acct, conv } = await convOf(line);
117
+ if (!conv) throw noConv(line);
118
+ const { encodeText } = await import('@xmtp/node-bindings');
119
+ const xmtpReplyTo = resolveMsgId(replyTo);
120
+ const sentId = await conv.sendReply({
121
+ reference: xmtpReplyTo,
122
+ content: encodeText(text),
123
+ contentType: {
124
+ authorityId: 'xmtp.org',
125
+ typeId: 'text',
126
+ versionMajor: 1,
127
+ versionMinor: 0,
128
+ },
129
+ } as unknown as Reply);
130
+ emitOutbound(acct.cfg.id, line, sentId, text, {
131
+ type: 'reply',
132
+ replyTo: xmtpReplyTo,
133
+ });
134
+ respond(id, { result: { messageId: sentId } });
135
+ }
136
+
137
+ async function sendAttachment(id: string, args: Args): Promise<void> {
138
+ const { line, name, mime, dataB64 } = args as {
139
+ line: string;
140
+ name: string;
141
+ mime: string;
142
+ dataB64: string;
143
+ };
144
+ const { acct, conv } = await convOf(line);
145
+ if (!conv) throw noConv(line);
146
+ const sentId = await conv.sendAttachment({
147
+ filename: name,
148
+ mimeType: mime,
149
+ content: new Uint8Array(Buffer.from(dataB64, 'base64')),
150
+ });
151
+ emitOutbound(acct.cfg.id, line, sentId, `[${mime.split('/')[0]}: ${name}]`);
152
+ respond(id, { result: { messageId: sentId } });
153
+ }
154
+
155
+ const IMG_MIME_BY_EXT: Record<string, string> = {
156
+ jpg: 'image/jpeg',
157
+ jpeg: 'image/jpeg',
158
+ gif: 'image/gif',
159
+ webp: 'image/webp',
160
+ };
161
+
162
+ async function loadImageBytes(
163
+ path: string | undefined,
164
+ dataB64: string | undefined,
165
+ ): Promise<Uint8Array> {
166
+ if (path) {
167
+ const { readFileSync } = await import('node:fs');
168
+ return new Uint8Array(readFileSync(path));
169
+ }
170
+ if (dataB64) return new Uint8Array(Buffer.from(dataB64, 'base64'));
171
+ throw badArgs('sendImage requires path or dataB64');
172
+ }
173
+
174
+ function imageMime(
175
+ mimeType: string | undefined,
176
+ filename: string | undefined,
177
+ path: string | undefined,
178
+ ): string {
179
+ if (mimeType) return mimeType;
180
+ const ext = (filename ?? path ?? '').toLowerCase().split('.').pop() ?? '';
181
+ return IMG_MIME_BY_EXT[ext] ?? 'image/png';
182
+ }
183
+
184
+ function imageFilename(
185
+ filename: string | undefined,
186
+ path: string | undefined,
187
+ ): string {
188
+ if (filename) return filename;
189
+ const baseName = path ? path.split('/').pop() : undefined;
190
+ return baseName != null && baseName !== '' ? baseName : 'image.png';
191
+ }
192
+
193
+ async function sendImage(id: string, args: Args): Promise<void> {
194
+ const { line, path, dataB64, filename, mimeType } = args as {
195
+ line: string;
196
+ path?: string;
197
+ dataB64?: string;
198
+ filename?: string;
199
+ mimeType?: string;
200
+ };
201
+ const { acct, conv } = await convOf(line);
202
+ if (!conv) throw noConv(line);
203
+ const bytes = await loadImageBytes(path, dataB64);
204
+ const mime = imageMime(mimeType, filename, path);
205
+ const fname = imageFilename(filename, path);
206
+ const attachment: Attachment = {
207
+ filename: fname,
208
+ mimeType: mime,
209
+ data: bytes,
210
+ };
211
+ const sentId = await conv.send(new AttachmentCodec().encode(attachment));
212
+ emitOutbound(acct.cfg.id, line, sentId, `[${mime.split('/')[0]}: ${fname}]`);
213
+ respond(id, { result: { messageId: sentId } });
214
+ }
215
+
216
+ interface TxRequestArgs {
217
+ to: string;
218
+ amountEth?: number;
219
+ data?: string;
220
+ note?: string;
221
+ chainId?: number;
222
+ }
223
+
224
+ function validateTxAmount(amountEth: number | undefined, hasData: boolean): void {
225
+ if (!hasData && (typeof amountEth !== 'number' || !(amountEth > 0))) {
226
+ throw badArgs(
227
+ 'sendTxRequest requires a positive `amountEth` (or `data` for a contract call)',
228
+ );
229
+ }
230
+ if (amountEth != null && (typeof amountEth !== 'number' || amountEth < 0)) {
231
+ throw badArgs('sendTxRequest `amountEth` must be a non-negative number');
232
+ }
233
+ }
234
+
235
+ function validateTxRequest(a: TxRequestArgs): boolean {
236
+ const { to, amountEth, data } = a;
237
+ if (!to || typeof to !== 'string' || !/^0x[0-9a-fA-F]{40}$/.test(to)) {
238
+ throw badArgs('sendTxRequest requires a valid 0x `to` address');
239
+ }
240
+ const hasData = data != null;
241
+ if (
242
+ hasData &&
243
+ (typeof data !== 'string' || !/^0x([0-9a-fA-F]{2})*$/.test(data))
244
+ ) {
245
+ throw badArgs('sendTxRequest `data` must be 0x-prefixed hex calldata');
246
+ }
247
+ validateTxAmount(amountEth, hasData);
248
+ return hasData;
249
+ }
250
+
251
+ function buildTxContent(
252
+ a: TxRequestArgs,
253
+ from: string,
254
+ hasData: boolean,
255
+ ): WalletSendCallsParams {
256
+ const { to, amountEth, data, note, chainId } = a;
257
+ const weiHex = amountEth
258
+ ? '0x' + BigInt(Math.round(amountEth * 1e18)).toString(16)
259
+ : '0x0';
260
+ return {
261
+ version: '1.0',
262
+ chainId: toHex(chainId ?? 1),
263
+ from: from as `0x${string}`,
264
+ calls: [
265
+ {
266
+ to: to as `0x${string}`,
267
+ value: weiHex as `0x${string}`,
268
+ ...(hasData ? { data: data as `0x${string}` } : {}),
269
+ metadata: {
270
+ description: note ?? (hasData ? 'Contract call' : 'Payment request'),
271
+ transactionType: 'transfer',
272
+ },
273
+ },
274
+ ],
275
+ };
276
+ }
277
+
278
+ function txRequestLabel(a: TxRequestArgs, hasData: boolean): string {
279
+ const { amountEth, note } = a;
280
+ return hasData
281
+ ? `📝 ${note ?? 'Contract call'}${amountEth ? ` (${amountEth} ETH)` : ''}`
282
+ : `💸 ${note ?? 'Payment request'} (${amountEth} ETH)`;
283
+ }
284
+
285
+ async function sendTxRequest(id: string, args: Args): Promise<void> {
286
+ const { line } = args as { line: string };
287
+ const txArgs = args as Args & TxRequestArgs;
288
+ const { acct, conv } = await convOf(line);
289
+ if (!conv) throw noConv(line);
290
+ const hasData = validateTxRequest(txArgs);
291
+ const content = buildTxContent(txArgs, acct.address, hasData);
292
+ const sentId = await conv.send(new WalletSendCallsCodec().encode(content));
293
+ emitOutbound(acct.cfg.id, line, sentId, txRequestLabel(txArgs, hasData));
294
+ respond(id, { result: { messageId: sentId } });
295
+ }
296
+
297
+ async function sendSignatureRequest(id: string, args: Args): Promise<void> {
298
+ const { line, kind, eip712, message, description } = args as {
299
+ line: string;
300
+ kind?: 'eip712' | 'personal';
301
+ eip712?: unknown;
302
+ message?: string;
303
+ description?: string;
304
+ };
305
+ const { acct, conv } = await convOf(line);
306
+ if (!conv) throw noConv(line);
307
+ const k: 'eip712' | 'personal' = kind === 'eip712' ? 'eip712' : 'personal';
308
+ if (k === 'eip712' && !eip712)
309
+ throw badArgs(
310
+ 'sendSignatureRequest eip712 requires an `eip712` typed-data object',
311
+ );
312
+ if (k === 'personal' && (!message || typeof message !== 'string')) {
313
+ throw badArgs('sendSignatureRequest personal requires a `message` string');
314
+ }
315
+ const content: SignatureRequestContent = {
316
+ id: 'sig_' + Date.now().toString(36),
317
+ kind: k,
318
+ ...(k === 'eip712' ? { eip712 } : { message }),
319
+ description,
320
+ };
321
+ const sentId = await conv.send(new SignatureRequestCodec().encode(content));
322
+ emitOutbound(
323
+ acct.cfg.id,
324
+ line,
325
+ sentId,
326
+ `✍️ ${description ?? 'Signature request'}`,
327
+ );
328
+ respond(id, { result: { messageId: sentId } });
329
+ }
330
+
331
+ async function accountsAction(id: string): Promise<void> {
332
+ const { accounts } = await import('./accounts.js');
333
+ respond(id, {
334
+ result: {
335
+ accounts: [...accounts.values()].map((a) => ({
336
+ id: a.cfg.id,
337
+ handle: a.address,
338
+ url: `https://etherscan.io/address/${a.address}`,
339
+ address: a.address,
340
+ inboxId: a.inboxId,
341
+ env: 'production',
342
+ owner: a.cfg.owner ?? null,
343
+ keySource: 'privateKey',
344
+ })),
345
+ },
346
+ });
347
+ }
348
+
349
+ function unsupportedVerb(id: string, verb: string): Promise<void> {
350
+ respond(id, { error: `unsupported verb '${verb}' on xmtp` });
351
+ return Promise.resolve();
352
+ }
353
+
354
+ const handlers: Record<string, (id: string, args: Args) => Promise<void>> = {
355
+ accounts: (id) => accountsAction(id),
356
+ send,
357
+ ask,
358
+ sendPoll: ask,
359
+ react,
360
+ reply,
361
+ sendAttachment,
362
+ sendImage,
363
+ sendTxRequest,
364
+ sendSignatureRequest,
365
+ edit: (id) => unsupportedVerb(id, 'edit'),
366
+ delete: (id) => unsupportedVerb(id, 'delete'),
367
+ ...convHandlers,
368
+ };
369
+
370
+ export type { CallMsg };
371
+
372
+ export const handleCall = makeStation({
373
+ handlers,
374
+ normalize: normalizeXmtp,
375
+ });
@@ -0,0 +1,107 @@
1
+ import {
2
+ AttachmentCodec,
3
+ RemoteAttachmentCodec,
4
+ ContentTypeAttachment,
5
+ } from '@xmtp/content-type-remote-attachment';
6
+ import {
7
+ saveBufferToCache,
8
+ assertAttachmentSize,
9
+ } from '@metro-labs/mcp/stations/attachments';
10
+ import type { SavedAttachment } from '@metro-labs/mcp/stations/attachments';
11
+ import { errMsg } from '@metro-labs/mcp/log';
12
+
13
+ export type { SavedAttachment };
14
+
15
+ export const REMOTE_FETCH_ATTEMPTS = 3;
16
+ const REMOTE_FETCH_BACKOFF_MS = 400;
17
+
18
+ const sleep = (ms: number): Promise<void> =>
19
+ new Promise((resolve) => setTimeout(resolve, ms));
20
+
21
+ const attachmentCodec = new AttachmentCodec();
22
+ const loadRegistry = {
23
+ codecFor: (ct: { typeId?: string }) =>
24
+ ct.typeId === ContentTypeAttachment.typeId ? attachmentCodec : undefined,
25
+ };
26
+
27
+ export interface RemoteEntry {
28
+ url: string;
29
+ filename?: string;
30
+ contentDigest?: string;
31
+ nonce?: Uint8Array;
32
+ salt?: Uint8Array;
33
+ secret?: Uint8Array;
34
+ scheme?: string;
35
+ contentLength?: number;
36
+ }
37
+
38
+ export async function saveInlineAttachment(
39
+ a: { filename?: string; mimeType?: string; content: Uint8Array },
40
+ messageId: string,
41
+ index = 0,
42
+ ): Promise<SavedAttachment> {
43
+ return saveBufferToCache(a.content, messageId, index, {
44
+ mime: a.mimeType,
45
+ name: a.filename,
46
+ });
47
+ }
48
+
49
+ function toRemoteDescriptor(r: RemoteEntry): {
50
+ url: string;
51
+ contentDigest: string;
52
+ salt: Uint8Array;
53
+ nonce: Uint8Array;
54
+ secret: Uint8Array;
55
+ scheme: string;
56
+ contentLength: number;
57
+ filename: string;
58
+ } {
59
+ return {
60
+ url: r.url,
61
+ contentDigest: r.contentDigest ?? '',
62
+ salt: r.salt ?? new Uint8Array(),
63
+ nonce: r.nonce ?? new Uint8Array(),
64
+ secret: r.secret ?? new Uint8Array(),
65
+ scheme: r.scheme ?? 'https://',
66
+ contentLength: r.contentLength ?? 0,
67
+ filename: r.filename ?? '',
68
+ };
69
+ }
70
+
71
+ interface DecodedAttachment {
72
+ filename?: string;
73
+ mimeType?: string;
74
+ data: Uint8Array;
75
+ }
76
+
77
+ async function loadRemote(r: RemoteEntry): Promise<DecodedAttachment> {
78
+ let last = '';
79
+ for (let attempt = 1; attempt <= REMOTE_FETCH_ATTEMPTS; attempt += 1) {
80
+ try {
81
+ return await RemoteAttachmentCodec.load<DecodedAttachment>(
82
+ toRemoteDescriptor(r),
83
+ loadRegistry,
84
+ );
85
+ } catch (err) {
86
+ last = errMsg(err);
87
+ if (attempt < REMOTE_FETCH_ATTEMPTS)
88
+ await sleep(REMOTE_FETCH_BACKOFF_MS * attempt);
89
+ }
90
+ }
91
+ throw new Error(
92
+ `xmtp remote attachment fetch failed after ${REMOTE_FETCH_ATTEMPTS} attempts: ${last}`,
93
+ );
94
+ }
95
+
96
+ export async function saveRemoteAttachment(
97
+ r: RemoteEntry,
98
+ messageId: string,
99
+ index = 0,
100
+ ): Promise<SavedAttachment> {
101
+ if (r.contentLength) assertAttachmentSize(r.contentLength);
102
+ const decoded = await loadRemote(r);
103
+ return saveBufferToCache(decoded.data, messageId, index, {
104
+ mime: decoded.mimeType,
105
+ name: decoded.filename ?? r.filename,
106
+ });
107
+ }