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

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 +340 -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 +28 -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 +115 -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,171 @@
1
+ import type {
2
+ ContentTypeId,
3
+ ContentCodec,
4
+ EncodedContent,
5
+ } from '@xmtp/content-type-primitives';
6
+
7
+ const enc = (o: unknown): Uint8Array =>
8
+ new TextEncoder().encode(JSON.stringify(o));
9
+ const dec = (e: EncodedContent): unknown =>
10
+ JSON.parse(new TextDecoder().decode(e.content));
11
+
12
+ const makeJsonCodec = <T>(
13
+ contentType: ContentTypeId,
14
+ fallbackFn: (c: T) => string,
15
+ ) =>
16
+ class {
17
+ get contentType() {
18
+ return contentType;
19
+ }
20
+ encode(c: T): EncodedContent {
21
+ return {
22
+ type: contentType,
23
+ parameters: {},
24
+ fallback: fallbackFn(c),
25
+ content: enc(c),
26
+ };
27
+ }
28
+ decode(e: EncodedContent): T {
29
+ return dec(e) as T;
30
+ }
31
+ fallback(c: T) {
32
+ return fallbackFn(c);
33
+ }
34
+ shouldPush() {
35
+ return true;
36
+ }
37
+ };
38
+
39
+ export const ContentTypePoll: ContentTypeId = {
40
+ authorityId: 'metro.box',
41
+ typeId: 'poll',
42
+ versionMajor: 1,
43
+ versionMinor: 0,
44
+ };
45
+ export interface PollOption {
46
+ label: string;
47
+ description?: string;
48
+ }
49
+ export interface PollQuestion {
50
+ question: string;
51
+ header?: string;
52
+ multiSelect?: boolean;
53
+ open?: boolean;
54
+ options?: (PollOption | string)[];
55
+ }
56
+ export interface PollContent {
57
+ questions?: PollQuestion[];
58
+ question?: string;
59
+ header?: string;
60
+ multiSelect?: boolean;
61
+ pollId?: string;
62
+ options?: (PollOption | string)[];
63
+ [k: string]: unknown;
64
+ }
65
+ const pollTitle = (c: PollContent): string =>
66
+ c.questions?.[0]?.question ?? c.question ?? 'Poll';
67
+ export const PollCodec = makeJsonCodec<PollContent>(
68
+ ContentTypePoll,
69
+ (c) => `📊 Poll: ${pollTitle(c)}`,
70
+ );
71
+
72
+ const normOpts = (o: (string | PollOption)[]): PollOption[] =>
73
+ o.map((x) =>
74
+ typeof x === 'string'
75
+ ? { label: x }
76
+ : { label: x.label, description: x.description },
77
+ );
78
+
79
+ export function buildPollContent(
80
+ args: Record<string, unknown>,
81
+ pollId: string,
82
+ ): { poll: PollContent; title: string } {
83
+ const { question, options, header, multiSelect, questions } = args as {
84
+ question?: string;
85
+ options?: (string | PollOption)[];
86
+ header?: string;
87
+ multiSelect?: boolean;
88
+ questions?: PollQuestion[];
89
+ };
90
+ if (Array.isArray(questions) && questions.length > 0) {
91
+ const norm: PollQuestion[] = questions.map((q, i) => {
92
+ if (!q || typeof q.question !== 'string' || !q.question)
93
+ throw new Error(`ask questions[${i}] requires a question`);
94
+ const open = q.open === true;
95
+ const opts = Array.isArray(q.options) ? q.options : [];
96
+ if (!open && opts.length === 0)
97
+ throw new Error(
98
+ `ask questions[${i}] requires a non-empty options array (or open:true for free-text)`,
99
+ );
100
+ return {
101
+ question: q.question,
102
+ options: normOpts(opts),
103
+ multiSelect: !!q.multiSelect,
104
+ ...(open ? { open: true } : {}),
105
+ ...(q.header ? { header: q.header } : {}),
106
+ };
107
+ });
108
+ const first = norm[0];
109
+ if (!first) throw new Error('ask requires at least one question');
110
+ return { poll: { questions: norm, pollId }, title: first.question };
111
+ }
112
+ if (!question || typeof question !== 'string')
113
+ throw new Error('ask requires a question (or a questions[] array)');
114
+ if (!Array.isArray(options) || options.length === 0)
115
+ throw new Error('ask requires a non-empty options array');
116
+ return {
117
+ poll: {
118
+ question,
119
+ options: normOpts(options),
120
+ multiSelect: !!multiSelect,
121
+ pollId,
122
+ ...(header ? { header } : {}),
123
+ },
124
+ title: question,
125
+ };
126
+ }
127
+
128
+ export const ContentTypeSignatureRequest: ContentTypeId = {
129
+ authorityId: 'metro.box',
130
+ typeId: 'signatureRequest',
131
+ versionMajor: 1,
132
+ versionMinor: 0,
133
+ };
134
+ export interface SignatureRequestContent {
135
+ id?: string;
136
+ kind?: 'eip712' | 'personal';
137
+ eip712?: unknown;
138
+ message?: string;
139
+ description?: string;
140
+ [k: string]: unknown;
141
+ }
142
+ export const SignatureRequestCodec = makeJsonCodec<SignatureRequestContent>(
143
+ ContentTypeSignatureRequest,
144
+ (c) =>
145
+ c.description
146
+ ? `[Signature request] ${c.description}`
147
+ : '[Signature request]',
148
+ );
149
+
150
+ export const ContentTypeSignatureReference: ContentTypeId = {
151
+ authorityId: 'metro.box',
152
+ typeId: 'signatureReference',
153
+ versionMajor: 1,
154
+ versionMinor: 0,
155
+ };
156
+ export interface SignatureReferenceContent {
157
+ requestId?: string;
158
+ signature: string;
159
+ signer?: string;
160
+ [k: string]: unknown;
161
+ }
162
+ export const SignatureReferenceCodec =
163
+ makeJsonCodec<SignatureReferenceContent>(ContentTypeSignatureReference, (c) =>
164
+ c.signature ? `[Signature] ${c.signature}` : '[Signature]',
165
+ );
166
+
167
+ export const CODECS = (): ContentCodec[] => [
168
+ new PollCodec(),
169
+ new SignatureRequestCodec(),
170
+ new SignatureReferenceCodec(),
171
+ ];
@@ -0,0 +1,239 @@
1
+ import { IdentifierKind } from '@xmtp/node-sdk';
2
+ import { convOf, type Account } from './accounts.js';
3
+ import { inboxEthCache, cacheInboxEth } from './wire.js';
4
+ import { TrainError } from '@metro-labs/mcp/train-error';
5
+ import type { MemberList, MetroMember } from '@metro-labs/mcp/stations/types';
6
+ import { readAppData, type GroupLike } from './labels.js';
7
+
8
+ export { parseMemberArgs, resolveMembers } from './member-args.js';
9
+
10
+ type Conv = Awaited<ReturnType<typeof convOf>>['conv'] & object;
11
+ export interface EthId {
12
+ identifier: string;
13
+ identifierKind: IdentifierKind;
14
+ }
15
+
16
+ export function ethIdentifiers(addrs: string[]): EthId[] {
17
+ return addrs.map((a) => ({
18
+ identifier: a,
19
+ identifierKind: IdentifierKind.Ethereum,
20
+ }));
21
+ }
22
+
23
+ export async function createGroupWithMembers(
24
+ acct: Account,
25
+ addrs: string[],
26
+ inboxes: string[],
27
+ opts: { groupName: string; groupDescription?: string; appData?: string },
28
+ ): Promise<GroupLike> {
29
+ if (!addrs.length)
30
+ return acct.client.conversations.createGroup(inboxes, opts);
31
+ const created = await acct.client.conversations.createGroupWithIdentifiers(
32
+ ethIdentifiers(addrs),
33
+ opts,
34
+ );
35
+ if (inboxes.length)
36
+ await (
37
+ created as unknown as { addMembers: (ids: string[]) => Promise<unknown> }
38
+ ).addMembers(inboxes);
39
+ return created;
40
+ }
41
+
42
+ async function resolveAddresses(
43
+ acct: Account,
44
+ inboxIds: string[],
45
+ ): Promise<Record<string, string>> {
46
+ const addresses: Record<string, string> = {};
47
+ const missing = inboxIds.filter((iid) => {
48
+ const cached = inboxEthCache.get(iid);
49
+ if (cached) {
50
+ addresses[iid] = cached;
51
+ return false;
52
+ }
53
+ return true;
54
+ });
55
+ if (!missing.length) return addresses;
56
+ try {
57
+ const states = await acct.client.preferences.fetchInboxStates(missing);
58
+ for (let i = 0; i < missing.length; i++) {
59
+ const key = missing[i];
60
+ if (key === undefined) continue;
61
+ const eth = states[i]?.identifiers.find(
62
+ (it: { identifierKind: IdentifierKind }) =>
63
+ it.identifierKind === IdentifierKind.Ethereum,
64
+ );
65
+ if (eth?.identifier) {
66
+ addresses[key] = eth.identifier;
67
+ cacheInboxEth(key, eth.identifier);
68
+ }
69
+ }
70
+ } catch {
71
+ return addresses;
72
+ }
73
+ return addresses;
74
+ }
75
+
76
+ export async function buildGroupInfo(
77
+ line: string,
78
+ acct: Account,
79
+ conv: Conv,
80
+ ): Promise<Record<string, unknown>> {
81
+ const inboxIds = (await conv.members()).map((m) => m.inboxId);
82
+ const addresses = await resolveAddresses(acct, inboxIds);
83
+ const isDm =
84
+ typeof (conv as unknown as { peerInboxId?: unknown }).peerInboxId ===
85
+ 'function';
86
+ const gn = (conv as unknown as { name?: string | (() => Promise<string>) })
87
+ .name;
88
+ const resolvedName = typeof gn === 'function' ? await gn() : (gn ?? '');
89
+ const { labels, github, preview } = readAppData(
90
+ (conv as unknown as GroupLike).appData,
91
+ );
92
+ return {
93
+ line,
94
+ id: conv.id,
95
+ account: acct.cfg.id,
96
+ version: isDm ? 'dm' : 'group',
97
+ name: resolvedName ?? '',
98
+ memberCount: inboxIds.length,
99
+ labels,
100
+ github,
101
+ preview,
102
+ members: inboxIds.map((iid) => ({
103
+ inboxId: iid,
104
+ address: addresses[iid] ?? null,
105
+ })),
106
+ };
107
+ }
108
+
109
+ interface RawMember {
110
+ inboxId: string;
111
+ permissionLevel?: number;
112
+ }
113
+
114
+ function toMetroMember(
115
+ m: RawMember,
116
+ addresses: Record<string, string>,
117
+ ): MetroMember {
118
+ const member: MetroMember = { id: m.inboxId };
119
+ const address = addresses[m.inboxId];
120
+ if (address) member.address = address;
121
+ if (typeof m.permissionLevel === 'number')
122
+ member.is_admin = m.permissionLevel >= 1;
123
+ return member;
124
+ }
125
+
126
+ export async function buildMemberList(
127
+ acct: Account,
128
+ conv: Conv,
129
+ ): Promise<MemberList> {
130
+ const raw = (await conv.members()) as RawMember[];
131
+ const addresses = await resolveAddresses(
132
+ acct,
133
+ raw.map((m) => m.inboxId),
134
+ );
135
+ return {
136
+ members: raw.map((m) => toMetroMember(m, addresses)),
137
+ capability: { supported: true, complete: true, total: raw.length },
138
+ };
139
+ }
140
+
141
+ interface MemberGroup {
142
+ sync?: () => Promise<unknown>;
143
+ addMembers?: (ids: string[]) => Promise<unknown>;
144
+ addMembersByIdentifiers?: (ids: EthId[]) => Promise<unknown>;
145
+ removeMembers?: (ids: string[]) => Promise<unknown>;
146
+ removeMembersByIdentifiers?: (ids: EthId[]) => Promise<unknown>;
147
+ }
148
+
149
+ async function applyByIdentifiers(
150
+ group: MemberGroup,
151
+ byIdent: ((ids: EthId[]) => Promise<unknown>) | undefined,
152
+ addrs: string[],
153
+ verb: string,
154
+ ): Promise<void> {
155
+ if (!addrs.length) return;
156
+ if (typeof byIdent !== 'function')
157
+ throw new TrainError(
158
+ 'INVALID_ARGS',
159
+ `group does not support ${verb}ByIdentifiers; pass inboxIds instead`,
160
+ );
161
+ await byIdent.call(group, ethIdentifiers(addrs));
162
+ }
163
+
164
+ async function applyByInboxId(
165
+ group: MemberGroup,
166
+ byId: ((ids: string[]) => Promise<unknown>) | undefined,
167
+ inboxes: string[],
168
+ verb: string,
169
+ ): Promise<void> {
170
+ if (!inboxes.length) return;
171
+ if (typeof byId !== 'function')
172
+ throw new TrainError(
173
+ 'INVALID_ARGS',
174
+ `group does not support ${verb} by inboxId`,
175
+ );
176
+ await byId.call(group, inboxes);
177
+ }
178
+
179
+ export async function applyMemberOp(
180
+ conv: Conv,
181
+ addrs: string[],
182
+ inboxes: string[],
183
+ mode: 'add' | 'remove',
184
+ ): Promise<void> {
185
+ const group = conv as unknown as MemberGroup;
186
+ const byId = mode === 'add' ? group.addMembers : group.removeMembers;
187
+ const byIdent =
188
+ mode === 'add'
189
+ ? group.addMembersByIdentifiers
190
+ : group.removeMembersByIdentifiers;
191
+ const verb = mode === 'add' ? 'addMembers' : 'removeMembers';
192
+ if (typeof byId !== 'function' && typeof byIdent !== 'function')
193
+ throw new TrainError(
194
+ 'INVALID_ARGS',
195
+ `${verb} target is not a group (no ${verb})`,
196
+ );
197
+ await group.sync?.().catch(() => undefined);
198
+ await applyByIdentifiers(group, byIdent, addrs, verb);
199
+ await applyByInboxId(group, byId, inboxes, verb);
200
+ }
201
+
202
+ const NAME_CACHE_MAX = 5000;
203
+ const convNameCache = new Map<string, string>();
204
+
205
+ function remember(convId: string, name: string): void {
206
+ convNameCache.delete(convId);
207
+ convNameCache.set(convId, name);
208
+ if (convNameCache.size > NAME_CACHE_MAX) {
209
+ const oldest = convNameCache.keys().next().value;
210
+ if (oldest !== undefined) convNameCache.delete(oldest);
211
+ }
212
+ }
213
+
214
+ async function readName(conv: unknown): Promise<string> {
215
+ try {
216
+ if (typeof (conv as { peerInboxId?: unknown }).peerInboxId === 'function')
217
+ return '';
218
+ const n = (conv as { name?: string | (() => Promise<string>) }).name;
219
+ const resolved = typeof n === 'function' ? await n() : n;
220
+ return typeof resolved === 'string' ? resolved : '';
221
+ } catch {
222
+ return '';
223
+ }
224
+ }
225
+
226
+ export async function groupNameFor(
227
+ convId: string,
228
+ conv: unknown,
229
+ ): Promise<string> {
230
+ const cached = convNameCache.get(convId);
231
+ if (cached !== undefined) return cached;
232
+ const name = await readName(conv);
233
+ remember(convId, name);
234
+ return name;
235
+ }
236
+
237
+ export function warmGroupName(convId: string, name: string | undefined): void {
238
+ if (typeof name === 'string' && name) remember(convId, name);
239
+ }
@@ -0,0 +1,55 @@
1
+ import { accounts } from './accounts.js';
2
+ import { emit, mintId, SELF_URI } from './wire.js';
3
+
4
+ export function emitInbound(
5
+ accountId: string,
6
+ e: Record<string, unknown>,
7
+ ): void {
8
+ const acct = accounts.get(accountId);
9
+ const owner = acct?.cfg.owner;
10
+ const payload = {
11
+ ...(e.payload as Record<string, unknown> | undefined),
12
+ account: accountId,
13
+ };
14
+ emit({
15
+ kind: 'inbound',
16
+ ...e,
17
+ ...(owner ? { to: owner } : {}),
18
+ account: accountId,
19
+ payload,
20
+ });
21
+ }
22
+
23
+ export function emitAttachmentSaved(
24
+ accountId: string,
25
+ line: string,
26
+ sourceMsgId: string,
27
+ index: number,
28
+ save: Promise<{ path: string; mime?: string; name?: string }>,
29
+ ): void {
30
+ void save
31
+ .then((saved) => {
32
+ emitInbound(accountId, {
33
+ id: mintId(),
34
+ ts: new Date().toISOString(),
35
+ station: 'xmtp',
36
+ line,
37
+ from: SELF_URI,
38
+ text: `📎 saved: ${saved.path}`,
39
+ payload: {
40
+ contentType: 'attachmentSaved',
41
+ attachmentFor: sourceMsgId,
42
+ index,
43
+ attachmentPath: saved.path,
44
+ localPath: saved.path,
45
+ mime: saved.mime,
46
+ name: saved.name,
47
+ },
48
+ });
49
+ })
50
+ .catch((err: unknown) =>
51
+ process.stderr.write(
52
+ `xmtp attachment save failed: ${err instanceof Error ? err.message : String(err)}\n`,
53
+ ),
54
+ );
55
+ }