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

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 (71) hide show
  1. package/dist/cli.js +1 -1
  2. package/package.json +1 -1
  3. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/package.json +1 -1
  4. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/accounts.ts +10 -10
  5. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/actions.ts +1 -1
  6. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/attachments.ts +1 -1
  7. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/format.ts +7 -7
  8. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/group-actions.ts +1 -1
  9. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/index.ts +10 -10
  10. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/station.ts +2 -2
  11. package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/voice-transcribe.ts +6 -6
  12. package/runtime/node_modules/@metro-labs/mcp/package.json +2 -2
  13. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +14 -8
  14. package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +52 -21
  15. package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +5 -2
  16. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +2 -2
  17. package/runtime/node_modules/@metro-labs/mcp/src/mcp/accounts.ts +43 -13
  18. package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +6 -6
  19. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +6 -6
  20. package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +6 -6
  21. package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +2 -2
  22. package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +4 -4
  23. package/runtime/node_modules/@metro-labs/telegram/package.json +5 -2
  24. package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +47 -86
  25. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +210 -251
  26. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +66 -107
  27. package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/client.ts +4 -4
  28. package/runtime/node_modules/@metro-labs/telegram/src/format.ts +110 -141
  29. package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/group.ts +1 -1
  30. package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/history.ts +1 -1
  31. package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/inbound.ts +6 -6
  32. package/runtime/node_modules/@metro-labs/telegram/src/index.ts +35 -92
  33. package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/login.ts +20 -20
  34. package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +65 -151
  35. package/runtime/node_modules/@metro-labs/telegram/src/members.ts +60 -32
  36. package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/reactions.ts +1 -1
  37. package/runtime/node_modules/@metro-labs/telegram/src/station.ts +8 -1
  38. package/runtime/node_modules/@metro-labs/telegram/src/types.ts +6 -47
  39. package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -3
  40. package/runtime/node_modules/@metro-labs/{telegram-user → telegram-bot}/package.json +3 -6
  41. package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
  42. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
  43. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
  44. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +172 -0
  45. package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +103 -0
  46. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
  47. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
  48. package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
  49. package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +48 -0
  50. package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
  51. package/runtime/runtime.json +1 -1
  52. package/runtime/node_modules/@metro-labs/telegram-user/src/accounts.ts +0 -81
  53. package/runtime/node_modules/@metro-labs/telegram-user/src/actions.ts +0 -254
  54. package/runtime/node_modules/@metro-labs/telegram-user/src/attachments.ts +0 -87
  55. package/runtime/node_modules/@metro-labs/telegram-user/src/format.ts +0 -141
  56. package/runtime/node_modules/@metro-labs/telegram-user/src/index.ts +0 -46
  57. package/runtime/node_modules/@metro-labs/telegram-user/src/media-actions.ts +0 -80
  58. package/runtime/node_modules/@metro-labs/telegram-user/src/members.ts +0 -75
  59. package/runtime/node_modules/@metro-labs/telegram-user/src/station.ts +0 -24
  60. package/runtime/node_modules/@metro-labs/telegram-user/src/types.ts +0 -7
  61. package/runtime/node_modules/@metro-labs/telegram-user/src/wire.ts +0 -1
  62. /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/api-base.ts +0 -0
  63. /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/group.ts +0 -0
  64. /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/members.ts +0 -0
  65. /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/send-files.ts +0 -0
  66. /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/verify.ts +0 -0
  67. /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/voice-speak.ts +0 -0
  68. /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/voice.ts +0 -0
  69. /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/wire.ts +0 -0
  70. /package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/normalize.ts +0 -0
  71. /package/runtime/node_modules/@metro-labs/{telegram → telegram-bot}/src/verify.ts +0 -0
@@ -2,11 +2,11 @@ import { randomBytes } from 'node:crypto';
2
2
  import {
3
3
  DiscordTokenError,
4
4
  verifyDiscordBotToken,
5
- } from '@metro-labs/discord/verify';
5
+ } from '@metro-labs/discord-bot/verify';
6
6
  import {
7
7
  TelegramTokenError,
8
8
  verifyTelegramBotToken,
9
- } from '@metro-labs/telegram/verify';
9
+ } from '@metro-labs/telegram-bot/verify';
10
10
  import { ApiError } from '../daemon/api-error.js';
11
11
  import { publicBaseOrDefault } from '../daemon/attach-serve.js';
12
12
  import {
@@ -18,8 +18,8 @@ import {
18
18
  } from './attach-xmtp.js';
19
19
 
20
20
  export const ATTACHABLE_STATIONS = [
21
- 'discord',
22
- 'telegram',
21
+ 'discord-bot',
22
+ 'telegram-bot',
23
23
  'xmtp',
24
24
  'webhook',
25
25
  ] as const;
@@ -175,8 +175,8 @@ export async function prepareAccount(
175
175
  input: AttachInput,
176
176
  verify: VerifyXmtpKey = verifyXmtpKeyOutOfProcess,
177
177
  ): Promise<PreparedAccount> {
178
- if (input.station === 'discord') return prepareDiscord(input.token);
179
- if (input.station === 'telegram') return prepareTelegram(input.token);
178
+ if (input.station === 'discord-bot') return prepareDiscord(input.token);
179
+ if (input.station === 'telegram-bot') return prepareTelegram(input.token);
180
180
  if (input.station === 'webhook') return prepareWebhook();
181
181
  return prepareXmtp(verify);
182
182
  }
@@ -38,7 +38,7 @@ function parseTelegramLine(
38
38
  line: Line | string,
39
39
  ): { accountId: string; chatId: number; topicId?: number } | null {
40
40
  const p = Line.parse(line);
41
- if (p?.station !== 'telegram') return null;
41
+ if (p?.station !== 'telegram-bot') return null;
42
42
  const { accountId, rest } = splitTelegramAccount(p.path);
43
43
  const [chatId, topicId] = rest;
44
44
  if (rest.length < 1 || rest.length > 2 || chatId === undefined || !isSignedInt(chatId))
@@ -76,7 +76,7 @@ export const Line = {
76
76
  parseXmtp: (line: Line | string) => parseAccountScoped(line, 'xmtp'),
77
77
 
78
78
  parseDiscord: (line: Line | string) =>
79
- parseAccountScoped(line, 'discord', isSnowflake),
79
+ parseAccountScoped(line, 'discord-bot', isSnowflake),
80
80
 
81
81
  parseTelegram: (line: Line | string) => parseTelegramLine(line),
82
82
  };
@@ -1,17 +1,17 @@
1
1
  import type { Station } from './types.js';
2
2
  import { Line } from './lines.js';
3
3
  import { xmtpStation } from '@metro-labs/xmtp';
4
+ import { telegramBotStation } from '@metro-labs/telegram-bot';
4
5
  import { telegramStation } from '@metro-labs/telegram';
5
- import { telegramUserStation } from '@metro-labs/telegram-user';
6
- import { discordStation } from '@metro-labs/discord';
6
+ import { discordBotStation } from '@metro-labs/discord-bot';
7
7
  import { whatsappStation } from '@metro-labs/whatsapp';
8
8
  import { webhookStation } from '@metro-labs/webhook';
9
9
 
10
10
  export const STATIONS: readonly Station[] = [
11
11
  xmtpStation,
12
+ telegramBotStation,
12
13
  telegramStation,
13
- telegramUserStation,
14
- discordStation,
14
+ discordBotStation,
15
15
  whatsappStation,
16
16
  webhookStation,
17
17
  ];
@@ -6,20 +6,23 @@
6
6
  "exports": {
7
7
  ".": "./src/station.ts",
8
8
  "./train": "./src/index.ts",
9
- "./verify": "./src/verify.ts"
9
+ "./login": "./src/login.ts"
10
10
  },
11
11
  "scripts": {
12
12
  "build": "tsc",
13
13
  "test": "tsc --noEmit && bun test test/"
14
14
  },
15
15
  "dependencies": {
16
- "@metro-labs/mcp": "workspace:*"
16
+ "@metro-labs/mcp": "workspace:*",
17
+ "@mtcute/bun": "0.30.1"
17
18
  },
18
19
  "devDependencies": {
19
20
  "@stage-labs/config": "0.1.0-beta.2",
21
+ "@types/qrcode-terminal": "^0.12.2",
20
22
  "eslint": "^10.3.0",
21
23
  "knip": "^6.15.0",
22
24
  "madge": "^8.0.0",
25
+ "qrcode-terminal": "^0.12.0",
23
26
  "typescript": "^5"
24
27
  }
25
28
  }
@@ -3,102 +3,43 @@ import { join } from 'node:path';
3
3
  import {
4
4
  makeAccountStore,
5
5
  resolveAccountId,
6
+ type Die,
6
7
  } from '@metro-labs/mcp/stations/account-store';
7
- import { Line } from '@metro-labs/mcp/lines';
8
+ import type { UserAccount } from './types.js';
8
9
 
9
10
  const ACCOUNTS_FILE =
10
11
  process.env.TELEGRAM_ACCOUNTS_FILE ??
11
12
  join(homedir(), '.metro', 'telegram-accounts.json');
12
13
 
13
- export interface AccountConfig {
14
- id: string;
15
- token: string;
16
- owner?: string;
14
+ const isSignedInt = (s: string): boolean => /^-?\d+$/.test(s);
15
+ const isTopic = (s: string): boolean => /^\d+$/.test(s);
16
+
17
+ function validateAccount(a: UserAccount, seen: Set<string>, die: Die): void {
18
+ if (!a.id) die('account missing id');
19
+ if (!a.session || typeof a.session !== 'string')
20
+ die(`account '${a.id}' missing session`);
21
+ if (!Number.isInteger(a.apiId) || (a.apiId ?? 0) <= 0)
22
+ die(`account '${a.id}' missing apiId`);
23
+ if (!a.apiHash || typeof a.apiHash !== 'string')
24
+ die(`account '${a.id}' missing apiHash`);
25
+ if (seen.has(a.id)) die(`duplicate account id '${a.id}'`);
26
+ seen.add(a.id);
17
27
  }
18
28
 
19
- export const { loadAccounts } = makeAccountStore<AccountConfig>({
29
+ export const { loadAccounts } = makeAccountStore<UserAccount>({
20
30
  prefix: 'telegram',
21
31
  file: ACCOUNTS_FILE,
22
32
  allowlistEnv: ['TELEGRAM_ONLY_ACCOUNTS', 'TELEGRAM_ACCOUNTS'],
23
33
  validate(raw, die) {
24
- const seenId = new Set<string>();
25
- const seenTok = new Set<string>();
26
- for (const a of raw) {
27
- if (!a.id) die('account missing id');
28
- if (!a.token || typeof a.token !== 'string')
29
- die(`account '${a.id}' missing token`);
30
- if (seenId.has(a.id)) die(`duplicate account id '${a.id}'`);
31
- if (seenTok.has(a.token))
32
- die(
33
- `account '${a.id}' reuses a token used by another account (409 on getUpdates)`,
34
- );
35
- seenId.add(a.id);
36
- seenTok.add(a.token);
37
- }
34
+ const seen = new Set<string>();
35
+ for (const a of raw) validateAccount(a, seen, die);
38
36
  },
39
37
  });
40
38
 
41
- export interface Account {
42
- cfg: AccountConfig;
43
- api: string;
44
- fileApi: string;
45
- offset: number;
46
- }
47
- export const accounts = new Map<string, Account>();
48
-
49
- export async function tg<T>(
50
- accountId: string,
51
- method: string,
52
- body: unknown,
53
- timeoutMs = 30_000,
54
- ): Promise<T> {
55
- const acct = accounts.get(accountId);
56
- if (!acct) throw new Error(`unknown account '${accountId}'`);
57
- const res = await fetch(`${acct.api}/${method}`, {
58
- method: 'POST',
59
- headers: { 'Content-Type': 'application/json' },
60
- body: JSON.stringify(body),
61
- signal: AbortSignal.timeout(timeoutMs),
62
- });
63
- const json = (await res.json()) as {
64
- ok: boolean;
65
- description?: string;
66
- result?: T;
67
- };
68
- if (!json.ok)
69
- throw new Error(`telegram ${method}: ${json.description ?? 'unknown'}`);
70
- return json.result as T;
71
- }
72
-
73
- export async function tgForm<T>(
74
- accountId: string,
75
- method: string,
76
- form: FormData,
77
- timeoutMs = 60_000,
78
- ): Promise<T> {
79
- const acct = accounts.get(accountId);
80
- if (!acct) throw new Error(`unknown account '${accountId}'`);
81
- const res = await fetch(`${acct.api}/${method}`, {
82
- method: 'POST',
83
- body: form,
84
- signal: AbortSignal.timeout(timeoutMs),
85
- });
86
- const json = (await res.json()) as {
87
- ok: boolean;
88
- description?: string;
89
- result?: T;
90
- };
91
- if (!json.ok)
92
- throw new Error(`telegram ${method}: ${json.description ?? 'unknown'}`);
93
- return json.result as T;
94
- }
39
+ export const accounts = new Map<string, UserAccount>();
95
40
 
96
41
  export function accountFor(args: { account?: string; line?: string }): string {
97
- return resolveAccountId(
98
- accounts,
99
- args,
100
- (line) => Line.parseTelegram(line)?.accountId,
101
- );
42
+ return resolveAccountId(accounts, args, (line) => targetOf(line)?.accountId);
102
43
  }
103
44
 
104
45
  export function lineOf(
@@ -110,11 +51,31 @@ export function lineOf(
110
51
  return `metro://telegram/${accountId}/${tail}`;
111
52
  }
112
53
 
113
- export function targetOf(
114
- line: string,
115
- accountOverride?: string,
116
- ): { accountId: string; chatId: number; topicId?: number } {
117
- const parsed = Line.parseTelegram(line);
118
- if (!parsed) throw new Error(`bad telegram line: ${line}`);
119
- return { ...parsed, accountId: accountOverride ?? parsed.accountId };
54
+ interface Target {
55
+ accountId: string;
56
+ chatId: number;
57
+ topicId?: number;
58
+ }
59
+
60
+ function splitScoped(path: string[]): { accountId: string; rest: string[] } {
61
+ const first = path[0];
62
+ if (path.length >= 2 && first !== undefined && !isSignedInt(first))
63
+ return { accountId: first, rest: path.slice(1) };
64
+ return { accountId: 'default', rest: path };
65
+ }
66
+
67
+ export function targetOf(line: string): Target | undefined {
68
+ const prefix = 'metro://telegram/';
69
+ if (!line.startsWith(prefix)) return undefined;
70
+ const path = line.slice(prefix.length).split('/').filter(Boolean);
71
+ const { accountId, rest } = splitScoped(path);
72
+ const [chatId, topicId] = rest;
73
+ if (rest.length < 1 || rest.length > 2 || chatId === undefined) return undefined;
74
+ if (!isSignedInt(chatId)) return undefined;
75
+ if (topicId !== undefined && !isTopic(topicId)) return undefined;
76
+ return {
77
+ accountId,
78
+ chatId: Number(chatId),
79
+ ...(topicId !== undefined ? { topicId: Number(topicId) } : {}),
80
+ };
120
81
  }