@stage-labs/metro 0.1.0-beta.21 → 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.
- package/package.json +1 -1
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/package.json +1 -1
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/accounts.ts +10 -10
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/actions.ts +1 -1
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/attachments.ts +1 -1
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/format.ts +7 -7
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/group-actions.ts +1 -1
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/index.ts +10 -10
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/station.ts +2 -2
- package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/voice-transcribe.ts +6 -6
- package/runtime/node_modules/@metro-labs/mcp/package.json +2 -2
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +14 -8
- package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +17 -10
- package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +2 -2
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +6 -6
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +6 -6
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +6 -6
- package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +2 -2
- package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +4 -4
- package/runtime/node_modules/@metro-labs/telegram/package.json +5 -2
- package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +47 -86
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +210 -251
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +66 -107
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/client.ts +4 -4
- package/runtime/node_modules/@metro-labs/telegram/src/format.ts +110 -141
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/group.ts +1 -1
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/history.ts +1 -1
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/inbound.ts +6 -6
- package/runtime/node_modules/@metro-labs/telegram/src/index.ts +35 -92
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/login.ts +20 -20
- package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +65 -151
- package/runtime/node_modules/@metro-labs/telegram/src/members.ts +60 -32
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/reactions.ts +1 -1
- package/runtime/node_modules/@metro-labs/telegram/src/station.ts +8 -1
- package/runtime/node_modules/@metro-labs/telegram/src/types.ts +6 -47
- package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -3
- package/runtime/node_modules/@metro-labs/{telegram-user → telegram-bot}/package.json +3 -6
- package/runtime/node_modules/@metro-labs/telegram-bot/src/accounts.ts +120 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +295 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +128 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +172 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/index.ts +103 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +47 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/station.ts +17 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/types.ts +48 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -0
- package/runtime/runtime.json +1 -1
- package/runtime/node_modules/@metro-labs/telegram-user/src/accounts.ts +0 -81
- package/runtime/node_modules/@metro-labs/telegram-user/src/actions.ts +0 -254
- package/runtime/node_modules/@metro-labs/telegram-user/src/attachments.ts +0 -87
- package/runtime/node_modules/@metro-labs/telegram-user/src/format.ts +0 -141
- package/runtime/node_modules/@metro-labs/telegram-user/src/index.ts +0 -46
- package/runtime/node_modules/@metro-labs/telegram-user/src/media-actions.ts +0 -80
- package/runtime/node_modules/@metro-labs/telegram-user/src/members.ts +0 -75
- package/runtime/node_modules/@metro-labs/telegram-user/src/station.ts +0 -24
- package/runtime/node_modules/@metro-labs/telegram-user/src/types.ts +0 -7
- package/runtime/node_modules/@metro-labs/telegram-user/src/wire.ts +0 -1
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/api-base.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/group.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/members.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/send-files.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/verify.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/voice-speak.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/voice.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{discord → discord-bot}/src/wire.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{telegram-user → telegram}/src/normalize.ts +0 -0
- /package/runtime/node_modules/@metro-labs/{telegram → telegram-bot}/src/verify.ts +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stage-labs/metro",
|
|
3
|
-
"version": "0.1.0-beta.
|
|
3
|
+
"version": "0.1.0-beta.22",
|
|
4
4
|
"description": "The metro command line. Sign in once per machine, then hand your MCP connector list to Claude Code without the credentials touching disk, argv or shell history.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -10,8 +10,8 @@ import { emit } from './wire.js';
|
|
|
10
10
|
import { API } from './api-base.js';
|
|
11
11
|
|
|
12
12
|
const ACCOUNTS_FILE =
|
|
13
|
-
process.env.
|
|
14
|
-
join(homedir(), '.metro', 'discord-accounts.json');
|
|
13
|
+
process.env.DISCORD_BOT_ACCOUNTS_FILE ??
|
|
14
|
+
join(homedir(), '.metro', 'discord-bot-accounts.json');
|
|
15
15
|
|
|
16
16
|
export interface AccountConfig {
|
|
17
17
|
id: string;
|
|
@@ -20,9 +20,9 @@ export interface AccountConfig {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
export const { loadAccounts } = makeAccountStore<AccountConfig>({
|
|
23
|
-
prefix: 'discord',
|
|
23
|
+
prefix: 'discord-bot',
|
|
24
24
|
file: ACCOUNTS_FILE,
|
|
25
|
-
allowlistEnv: ['
|
|
25
|
+
allowlistEnv: ['DISCORD_BOT_ONLY_ACCOUNTS', 'DISCORD_BOT_ACCOUNTS'],
|
|
26
26
|
validate(raw, die) {
|
|
27
27
|
const seen = new Set<string>();
|
|
28
28
|
for (const a of raw) {
|
|
@@ -67,11 +67,11 @@ export async function rest<T = unknown>(
|
|
|
67
67
|
if (!acct) throw new Error(`unknown account '${accountId}'`);
|
|
68
68
|
const headers: Record<string, string> = {
|
|
69
69
|
Authorization: `Bot ${acct.cfg.token}`,
|
|
70
|
-
'User-Agent': 'metro-discord-train (https://github.com/bonustrack/stage)',
|
|
70
|
+
'User-Agent': 'metro-discord-bot-train (https://github.com/bonustrack/stage)',
|
|
71
71
|
};
|
|
72
72
|
if (body !== undefined && !isForm)
|
|
73
73
|
headers['Content-Type'] = 'application/json';
|
|
74
|
-
emit({ op: 'log', text: `discord[${accountId}] api ${method} ${path}` });
|
|
74
|
+
emit({ op: 'log', text: `discord-bot[${accountId}] api ${method} ${path}` });
|
|
75
75
|
const res = await fetch(`${API}${path}`, {
|
|
76
76
|
method,
|
|
77
77
|
headers,
|
|
@@ -80,17 +80,17 @@ export async function rest<T = unknown>(
|
|
|
80
80
|
});
|
|
81
81
|
emit({
|
|
82
82
|
op: 'log',
|
|
83
|
-
text: `discord[${accountId}] api ${method} ${path} -> ${res.status}`,
|
|
83
|
+
text: `discord-bot[${accountId}] api ${method} ${path} -> ${res.status}`,
|
|
84
84
|
});
|
|
85
85
|
if (!res.ok) {
|
|
86
86
|
const text = await res.text().catch(() => '');
|
|
87
|
-
throw new Error(`discord ${method} ${path}: ${res.status} ${text}`);
|
|
87
|
+
throw new Error(`discord-bot ${method} ${path}: ${res.status} ${text}`);
|
|
88
88
|
}
|
|
89
89
|
return restResult<T>(res);
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
export function lineOf(accountId: string, channelId: string): string {
|
|
93
|
-
return `metro://discord/${accountId}/${channelId}`;
|
|
93
|
+
return `metro://discord-bot/${accountId}/${channelId}`;
|
|
94
94
|
}
|
|
95
95
|
|
|
96
96
|
export function parseLine(
|
|
@@ -109,7 +109,7 @@ export function routeOf(
|
|
|
109
109
|
account?: string,
|
|
110
110
|
): { accountId: string; channelId: string } {
|
|
111
111
|
const parsed = parseLine(line);
|
|
112
|
-
if (!parsed) throw new Error(`bad discord line: ${line}`);
|
|
112
|
+
if (!parsed) throw new Error(`bad discord-bot line: ${line}`);
|
|
113
113
|
const accountId = account ?? parsed.accountId;
|
|
114
114
|
if (!accounts.has(accountId))
|
|
115
115
|
throw new Error(`unknown account '${accountId}' in line ${line}`);
|
|
@@ -267,7 +267,7 @@ async function download(
|
|
|
267
267
|
for (const att of msg.attachments) {
|
|
268
268
|
const res = await fetch(att.url, { signal: AbortSignal.timeout(60_000) });
|
|
269
269
|
if (!res.ok)
|
|
270
|
-
throw new Error(`discord download ${res.status} for ${att.url}`);
|
|
270
|
+
throw new Error(`discord-bot download ${res.status} for ${att.url}`);
|
|
271
271
|
assertContentLength(res.headers.get('content-length'));
|
|
272
272
|
const buf = await res.arrayBuffer();
|
|
273
273
|
const path = `${outDir}/${messageId}-${att.filename}`;
|
|
@@ -19,7 +19,7 @@ export async function saveDiscordAttachment(
|
|
|
19
19
|
): Promise<SavedAttachment> {
|
|
20
20
|
const res = await fetch(a.url, { signal: AbortSignal.timeout(60_000) });
|
|
21
21
|
if (!res.ok)
|
|
22
|
-
throw new Error(`discord attachment fetch ${res.status} for ${a.url}`);
|
|
22
|
+
throw new Error(`discord-bot attachment fetch ${res.status} for ${a.url}`);
|
|
23
23
|
assertContentLength(res.headers.get('content-length'));
|
|
24
24
|
const data = new Uint8Array(await res.arrayBuffer());
|
|
25
25
|
return saveBufferToCache(data, messageId, index, {
|
|
@@ -29,7 +29,7 @@ function emitAttachmentSaved(
|
|
|
29
29
|
kind: 'inbound',
|
|
30
30
|
id: mintId(),
|
|
31
31
|
ts: new Date().toISOString(),
|
|
32
|
-
station: 'discord',
|
|
32
|
+
station: 'discord-bot',
|
|
33
33
|
line,
|
|
34
34
|
from: SELF_URI,
|
|
35
35
|
text: `📎 saved: ${saved.path}`,
|
|
@@ -46,7 +46,7 @@ function emitAttachmentSaved(
|
|
|
46
46
|
})
|
|
47
47
|
.catch((err: unknown) =>
|
|
48
48
|
process.stderr.write(
|
|
49
|
-
`discord attachment save failed: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
49
|
+
`discord-bot attachment save failed: ${err instanceof Error ? err.message : String(err)}\n`,
|
|
50
50
|
),
|
|
51
51
|
);
|
|
52
52
|
}
|
|
@@ -102,10 +102,10 @@ export function messageEnvelope(
|
|
|
102
102
|
kind: 'inbound',
|
|
103
103
|
id: envId,
|
|
104
104
|
ts: new Date(m.createdTimestamp).toISOString(),
|
|
105
|
-
station: 'discord',
|
|
105
|
+
station: 'discord-bot',
|
|
106
106
|
line,
|
|
107
107
|
line_name: 'name' in m.channel ? m.channel.name : undefined,
|
|
108
|
-
from: `metro://discord/${accountId}/user/${m.author.id}`,
|
|
108
|
+
from: `metro://discord-bot/${accountId}/user/${m.author.id}`,
|
|
109
109
|
from_name: m.author.username,
|
|
110
110
|
from_display_name: m.author.globalName ?? undefined,
|
|
111
111
|
message_id: m.id,
|
|
@@ -129,9 +129,9 @@ export function reactionEnvelope(
|
|
|
129
129
|
kind: 'react',
|
|
130
130
|
id: mintId(),
|
|
131
131
|
ts: new Date().toISOString(),
|
|
132
|
-
station: 'discord',
|
|
132
|
+
station: 'discord-bot',
|
|
133
133
|
line: lineOf(accountId, r.message.channelId),
|
|
134
|
-
from: `metro://discord/${accountId}/user/${u.id}`,
|
|
134
|
+
from: `metro://discord-bot/${accountId}/user/${u.id}`,
|
|
135
135
|
from_name: u.username,
|
|
136
136
|
from_display_name: u.globalName ?? undefined,
|
|
137
137
|
message_id: r.message.id,
|
|
@@ -162,7 +162,7 @@ function outbound(
|
|
|
162
162
|
kind,
|
|
163
163
|
id: mintId(),
|
|
164
164
|
ts: new Date().toISOString(),
|
|
165
|
-
station: 'discord',
|
|
165
|
+
station: 'discord-bot',
|
|
166
166
|
line,
|
|
167
167
|
from: SELF_URI,
|
|
168
168
|
to: line,
|
|
@@ -17,7 +17,7 @@ export async function groupCreateHandler(
|
|
|
17
17
|
capability: {
|
|
18
18
|
supported: false,
|
|
19
19
|
reason:
|
|
20
|
-
'discord create_group requires `parent` (the metro:// line of the channel to open the thread under)',
|
|
20
|
+
'discord-bot create_group requires `parent` (the metro:// line of the channel to open the thread under)',
|
|
21
21
|
},
|
|
22
22
|
},
|
|
23
23
|
});
|
|
@@ -19,7 +19,7 @@ import { mintId } from './wire.js';
|
|
|
19
19
|
import { readCalls } from '@metro-labs/mcp/trains/protocol';
|
|
20
20
|
import { handleCall } from './actions.js';
|
|
21
21
|
|
|
22
|
-
readCalls('discord', handleCall);
|
|
22
|
+
readCalls('discord-bot', handleCall);
|
|
23
23
|
|
|
24
24
|
function makeClient(): Client {
|
|
25
25
|
return new Client({
|
|
@@ -47,9 +47,9 @@ function onEdit(accountId: string, m: Message): void {
|
|
|
47
47
|
kind: 'edit',
|
|
48
48
|
id: mintId(),
|
|
49
49
|
ts: new Date(m.editedTimestamp ?? Date.now()).toISOString(),
|
|
50
|
-
station: 'discord',
|
|
50
|
+
station: 'discord-bot',
|
|
51
51
|
line: lineOf(accountId, m.channelId),
|
|
52
|
-
from: `metro://discord/${accountId}/user/${m.author.id}`,
|
|
52
|
+
from: `metro://discord-bot/${accountId}/user/${m.author.id}`,
|
|
53
53
|
from_name: m.author.username,
|
|
54
54
|
from_display_name: m.author.globalName ?? undefined,
|
|
55
55
|
message_id: m.id,
|
|
@@ -80,7 +80,7 @@ async function bootAccount(cfg: AccountConfig): Promise<void> {
|
|
|
80
80
|
if (env) emitInbound(accountId, env);
|
|
81
81
|
})().catch((err: unknown) => {
|
|
82
82
|
process.stderr.write(
|
|
83
|
-
`discord[${accountId}] reaction handler failed: ${errMsg(err)}\n`,
|
|
83
|
+
`discord-bot[${accountId}] reaction handler failed: ${errMsg(err)}\n`,
|
|
84
84
|
);
|
|
85
85
|
});
|
|
86
86
|
});
|
|
@@ -91,12 +91,12 @@ async function bootAccount(cfg: AccountConfig): Promise<void> {
|
|
|
91
91
|
onEdit(accountId, _new.partial ? await _new.fetch() : _new);
|
|
92
92
|
} catch (err) {
|
|
93
93
|
process.stderr.write(
|
|
94
|
-
`discord[${accountId}] message update fetch failed: ${errMsg(err)}\n`,
|
|
94
|
+
`discord-bot[${accountId}] message update fetch failed: ${errMsg(err)}\n`,
|
|
95
95
|
);
|
|
96
96
|
}
|
|
97
97
|
})().catch((err: unknown) => {
|
|
98
98
|
process.stderr.write(
|
|
99
|
-
`discord[${accountId}] message update handler failed: ${errMsg(err)}\n`,
|
|
99
|
+
`discord-bot[${accountId}] message update handler failed: ${errMsg(err)}\n`,
|
|
100
100
|
);
|
|
101
101
|
});
|
|
102
102
|
});
|
|
@@ -104,7 +104,7 @@ async function bootAccount(cfg: AccountConfig): Promise<void> {
|
|
|
104
104
|
accounts.set(accountId, { cfg, client });
|
|
105
105
|
await client.login(cfg.token);
|
|
106
106
|
process.stderr.write(
|
|
107
|
-
`discord[${accountId}] ready — ${client.user?.tag ?? '?'} (owner=${cfg.owner ?? '(broadcast)'})\n`,
|
|
107
|
+
`discord-bot[${accountId}] ready — ${client.user?.tag ?? '?'} (owner=${cfg.owner ?? '(broadcast)'})\n`,
|
|
108
108
|
);
|
|
109
109
|
}
|
|
110
110
|
|
|
@@ -114,14 +114,14 @@ for (const cfg of cfgs) {
|
|
|
114
114
|
await bootAccount(cfg);
|
|
115
115
|
} catch (err) {
|
|
116
116
|
process.stderr.write(
|
|
117
|
-
`discord[${cfg.id}] boot FAILED: ${errMsg(err)}\n`,
|
|
117
|
+
`discord-bot[${cfg.id}] boot FAILED: ${errMsg(err)}\n`,
|
|
118
118
|
);
|
|
119
119
|
}
|
|
120
120
|
}
|
|
121
121
|
if (accounts.size === 0) {
|
|
122
|
-
process.stderr.write('discord: no accounts booted, exiting\n');
|
|
122
|
+
process.stderr.write('discord-bot: no accounts booted, exiting\n');
|
|
123
123
|
process.exit(2);
|
|
124
124
|
}
|
|
125
125
|
process.stderr.write(
|
|
126
|
-
`discord train ready — ${accounts.size} account(s): ${[...accounts.keys()].join(', ')}\n`,
|
|
126
|
+
`discord-bot train ready — ${accounts.size} account(s): ${[...accounts.keys()].join(', ')}\n`,
|
|
127
127
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { GroupOp, Station, Verb } from '@metro-labs/mcp/stations/types';
|
|
2
2
|
|
|
3
|
-
export const
|
|
4
|
-
name: 'discord',
|
|
3
|
+
export const discordBotStation: Station = {
|
|
4
|
+
name: 'discord-bot',
|
|
5
5
|
hasAccounts: true,
|
|
6
6
|
hasTrain: true,
|
|
7
7
|
messageVerbs: new Set<Verb>([
|
|
@@ -178,10 +178,10 @@ function emitTranscript(
|
|
|
178
178
|
kind: 'inbound',
|
|
179
179
|
id: mintId(),
|
|
180
180
|
ts: new Date().toISOString(),
|
|
181
|
-
station: 'discord',
|
|
182
|
-
line: `metro://discord/${s.accountId}/voice/${s.channelId}`,
|
|
181
|
+
station: 'discord-bot',
|
|
182
|
+
line: `metro://discord-bot/${s.accountId}/voice/${s.channelId}`,
|
|
183
183
|
line_name: 'voice',
|
|
184
|
-
from: `metro://discord/${s.accountId}/user/${userId}`,
|
|
184
|
+
from: `metro://discord-bot/${s.accountId}/user/${userId}`,
|
|
185
185
|
from_name: speakerName(s, userId),
|
|
186
186
|
message_id: stamp,
|
|
187
187
|
text,
|
|
@@ -228,7 +228,7 @@ async function handleSpeaker(s: Session, userId: string): Promise<void> {
|
|
|
228
228
|
await transcribeUtterance(s, userId, pcmPath, wavPath, stamp);
|
|
229
229
|
} catch (err) {
|
|
230
230
|
process.stderr.write(
|
|
231
|
-
`discord voice transcribe error (${userId}): ${errMsg(err)}\n`,
|
|
231
|
+
`discord-bot voice transcribe error (${userId}): ${errMsg(err)}\n`,
|
|
232
232
|
);
|
|
233
233
|
} finally {
|
|
234
234
|
s.active.delete(userId);
|
|
@@ -258,14 +258,14 @@ export function startTranscription(
|
|
|
258
258
|
s.onStart = (userId: string) => {
|
|
259
259
|
handleSpeaker(s, userId).catch((err: unknown) => {
|
|
260
260
|
process.stderr.write(
|
|
261
|
-
`discord[${accountId}] voice speaker failed: ${errMsg(err)}\n`,
|
|
261
|
+
`discord-bot[${accountId}] voice speaker failed: ${errMsg(err)}\n`,
|
|
262
262
|
);
|
|
263
263
|
});
|
|
264
264
|
};
|
|
265
265
|
receiver.speaking.on('start', s.onStart);
|
|
266
266
|
sessions.set(guildId, s);
|
|
267
267
|
process.stderr.write(
|
|
268
|
-
`discord[${accountId}] voice transcription armed on ${channelId}\n`,
|
|
268
|
+
`discord-bot[${accountId}] voice transcription armed on ${channelId}\n`,
|
|
269
269
|
);
|
|
270
270
|
}
|
|
271
271
|
|
|
@@ -42,9 +42,9 @@
|
|
|
42
42
|
"test": "tsc --noEmit && DYLD_FALLBACK_LIBRARY_PATH=/usr/lib METRO_STATE_DIR=\"$(mktemp -d /tmp/metro-test.XXXXXX)\" bun test test/"
|
|
43
43
|
},
|
|
44
44
|
"dependencies": {
|
|
45
|
-
"@metro-labs/discord": "workspace:*",
|
|
45
|
+
"@metro-labs/discord-bot": "workspace:*",
|
|
46
46
|
"@metro-labs/telegram": "workspace:*",
|
|
47
|
-
"@metro-labs/telegram-
|
|
47
|
+
"@metro-labs/telegram-bot": "workspace:*",
|
|
48
48
|
"@metro-labs/webhook": "workspace:*",
|
|
49
49
|
"@metro-labs/whatsapp": "workspace:*",
|
|
50
50
|
"@metro-labs/xmtp": "workspace:*",
|
|
@@ -102,18 +102,23 @@ interface KeyPayload {
|
|
|
102
102
|
command: string | null;
|
|
103
103
|
}
|
|
104
104
|
|
|
105
|
-
|
|
105
|
+
const LOCAL_MCP_ENDPOINT = 'http://127.0.0.1:8420/mcp';
|
|
106
|
+
|
|
107
|
+
function credentials(key: string, heldLocally: boolean): KeyPayload {
|
|
108
|
+
const url = heldLocally
|
|
109
|
+
? `${LOCAL_MCP_ENDPOINT}?token=${key}`
|
|
110
|
+
: `${mcpEndpoint()}?token=${key}`;
|
|
106
111
|
return {
|
|
107
112
|
key,
|
|
108
|
-
endpoint:
|
|
109
|
-
command:
|
|
113
|
+
endpoint: url,
|
|
114
|
+
command: `claude mcp add --transport http ${SERVER_NAME} "${url}"`,
|
|
110
115
|
};
|
|
111
116
|
}
|
|
112
117
|
|
|
113
|
-
function keyPayload(agent: AgentSummary): KeyPayload {
|
|
118
|
+
function keyPayload(agent: AgentSummary, heldLocally: boolean): KeyPayload {
|
|
114
119
|
const value = agent.owned ? agent.key : null;
|
|
115
120
|
if (value === null) return { key: null, endpoint: null, command: null };
|
|
116
|
-
return credentials(value);
|
|
121
|
+
return credentials(value, heldLocally);
|
|
117
122
|
}
|
|
118
123
|
|
|
119
124
|
function livenessPayload(
|
|
@@ -139,7 +144,7 @@ function agentPayload(
|
|
|
139
144
|
owned: agent.owned,
|
|
140
145
|
runtime: agent.owned ? (runtimes.get(agent.id) ?? null) : null,
|
|
141
146
|
...livenessPayload(agent, live),
|
|
142
|
-
...keyPayload(agent),
|
|
147
|
+
...keyPayload(agent, agent.owned && runtimes.has(agent.id)),
|
|
143
148
|
};
|
|
144
149
|
}
|
|
145
150
|
|
|
@@ -219,7 +224,7 @@ async function handleCreate(
|
|
|
219
224
|
sendJson(req, res, 201, {
|
|
220
225
|
id: created.id,
|
|
221
226
|
name: created.name,
|
|
222
|
-
...credentials(created.key),
|
|
227
|
+
...credentials(created.key, false),
|
|
223
228
|
});
|
|
224
229
|
}
|
|
225
230
|
|
|
@@ -231,6 +236,7 @@ async function handleResetKey(
|
|
|
231
236
|
id: string,
|
|
232
237
|
): Promise<void> {
|
|
233
238
|
const reset = await deps.resetKey(session.email, id);
|
|
239
|
+
const held = (await deps.runtimes([id])).has(id);
|
|
234
240
|
log.info(
|
|
235
241
|
{ agent: reset.name, id: reset.id, owner: session.email },
|
|
236
242
|
'agent-api: reset agent key',
|
|
@@ -239,7 +245,7 @@ async function handleResetKey(
|
|
|
239
245
|
id: reset.id,
|
|
240
246
|
name: reset.name,
|
|
241
247
|
reset: true,
|
|
242
|
-
...credentials(reset.key),
|
|
248
|
+
...credentials(reset.key, held),
|
|
243
249
|
});
|
|
244
250
|
}
|
|
245
251
|
|
|
@@ -52,20 +52,20 @@ const STATION_TARGETS: Record<StationName, StationTarget> = {
|
|
|
52
52
|
fileEnv: 'XMTP_ACCOUNTS_FILE',
|
|
53
53
|
trainImport: '@metro-labs/xmtp/train',
|
|
54
54
|
},
|
|
55
|
-
telegram: {
|
|
55
|
+
'telegram-bot': {
|
|
56
|
+
file: 'telegram-bot-accounts.json',
|
|
57
|
+
fileEnv: 'TELEGRAM_BOT_ACCOUNTS_FILE',
|
|
58
|
+
trainImport: '@metro-labs/telegram-bot/train',
|
|
59
|
+
},
|
|
60
|
+
'telegram': {
|
|
56
61
|
file: 'telegram-accounts.json',
|
|
57
62
|
fileEnv: 'TELEGRAM_ACCOUNTS_FILE',
|
|
58
63
|
trainImport: '@metro-labs/telegram/train',
|
|
59
64
|
},
|
|
60
|
-
'
|
|
61
|
-
file: '
|
|
62
|
-
fileEnv: '
|
|
63
|
-
trainImport: '@metro-labs/
|
|
64
|
-
},
|
|
65
|
-
discord: {
|
|
66
|
-
file: 'discord-accounts.json',
|
|
67
|
-
fileEnv: 'DISCORD_ACCOUNTS_FILE',
|
|
68
|
-
trainImport: '@metro-labs/discord/train',
|
|
65
|
+
'discord-bot': {
|
|
66
|
+
file: 'discord-bot-accounts.json',
|
|
67
|
+
fileEnv: 'DISCORD_BOT_ACCOUNTS_FILE',
|
|
68
|
+
trainImport: '@metro-labs/discord-bot/train',
|
|
69
69
|
},
|
|
70
70
|
whatsapp: {
|
|
71
71
|
file: 'whatsapp-accounts.json',
|
|
@@ -247,6 +247,13 @@ function writeStations(list: LoadedAgent[]): Map<StationName, number> {
|
|
|
247
247
|
for (const agent of list) {
|
|
248
248
|
names[agent.id] = agent.name;
|
|
249
249
|
for (const a of agent.accounts) {
|
|
250
|
+
if (!(a.station in STATION_TARGETS)) {
|
|
251
|
+
log.error(
|
|
252
|
+
{ station: a.station, id: a.id },
|
|
253
|
+
'unknown station — this metro is older or newer than the row; skipping it',
|
|
254
|
+
);
|
|
255
|
+
continue;
|
|
256
|
+
}
|
|
250
257
|
map[`${a.station}/${a.id}`] = agent.id;
|
|
251
258
|
if (a.allowlist) allow[`${a.station}/${a.id}`] = a.allowlist;
|
|
252
259
|
if (stationRunsHere(a.station)) {
|
|
@@ -235,7 +235,7 @@ export const COMMON_TOOLS = [
|
|
|
235
235
|
'limit?. The station is derived from the line. Returns {line, station, memberCount, ' +
|
|
236
236
|
'members:[{id, name?, display_name?, address?, roles?, is_admin?, is_bot?}], capability:' +
|
|
237
237
|
'{supported, complete, reason?, total?}}. Each station fills the fields it has (xmtp: id=' +
|
|
238
|
-
'inboxId + address; discord: id + username/nick + roles + is_bot; telegram/telegram
|
|
238
|
+
'inboxId + address; discord-bot: id + username/nick + roles + is_bot; telegram-bot/telegram: ' +
|
|
239
239
|
'id + username/first_name). Never throws on "not supported": stations that cannot ' +
|
|
240
240
|
'enumerate (e.g. the Telegram Bot API) return an empty or partial list with a reason in ' +
|
|
241
241
|
'`capability`. Check `capability.complete` before assuming the roster is exhaustive.',
|
|
@@ -255,9 +255,9 @@ export const COMMON_TOOLS = [
|
|
|
255
255
|
name: 'create_group',
|
|
256
256
|
description:
|
|
257
257
|
'Create a new group/channel on a station and add members, in a station-agnostic shape. ' +
|
|
258
|
-
'Args: station (required, xmtp|telegram
|
|
259
|
-
'(string[]; xmtp: 0x address or inboxId, telegram
|
|
260
|
-
'user id), account? (which station account), parent? (discord only: the metro:// line of ' +
|
|
258
|
+
'Args: station (required, xmtp|telegram|discord-bot|telegram-bot), name (required), members? ' +
|
|
259
|
+
'(string[]; xmtp: 0x address or inboxId, telegram: @username or user id, discord-bot: ' +
|
|
260
|
+
'user id), account? (which station account), parent? (discord-bot only: the metro:// line of ' +
|
|
261
261
|
'the channel to open the thread under). Returns {op, line (the NEW group line), station, ' +
|
|
262
262
|
'supported, reason?, id?, name?, members:[{id, status: added|invited|removed|failed, ' +
|
|
263
263
|
'reason?}], inviteLink?}. Never throws on "not supported": stations that lack the op ' +
|
|
@@ -269,7 +269,7 @@ export const COMMON_TOOLS = [
|
|
|
269
269
|
properties: {
|
|
270
270
|
station: {
|
|
271
271
|
type: 'string',
|
|
272
|
-
description: 'The station to create the group on (xmtp|telegram
|
|
272
|
+
description: 'The station to create the group on (xmtp|telegram|discord-bot).',
|
|
273
273
|
},
|
|
274
274
|
name: { type: 'string', description: 'The group/channel name.' },
|
|
275
275
|
members: {
|
|
@@ -357,7 +357,7 @@ export const COMMON_TOOLS = [
|
|
|
357
357
|
description:
|
|
358
358
|
'Get a join/invite link for a Metro group, for platforms where direct-add is not always ' +
|
|
359
359
|
'permitted. Args: line (required). The station is derived from the line. Returns {op, ' +
|
|
360
|
-
'line, station, supported, reason?, inviteLink?}. Only telegram
|
|
360
|
+
'line, station, supported, reason?, inviteLink?}. Only telegram supports this today; ' +
|
|
361
361
|
'other stations return {supported:false, reason}. Never throws on "not supported".',
|
|
362
362
|
inputSchema: {
|
|
363
363
|
type: 'object',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { StationAttachError } from './attach.js';
|
|
2
2
|
|
|
3
|
-
export const INTERACTIVE_STATIONS = ['telegram
|
|
3
|
+
export const INTERACTIVE_STATIONS = ['telegram', 'whatsapp'] as const;
|
|
4
4
|
|
|
5
5
|
export type InteractiveStation = (typeof INTERACTIVE_STATIONS)[number];
|
|
6
6
|
|
|
@@ -61,12 +61,12 @@ async function startTelegramUser(
|
|
|
61
61
|
input: Record<string, unknown>,
|
|
62
62
|
hooks: DriverHooks,
|
|
63
63
|
): Promise<StartedAttach> {
|
|
64
|
-
const {
|
|
65
|
-
await import('@metro-labs/telegram
|
|
66
|
-
const isOwn = (e: unknown): boolean => e instanceof
|
|
64
|
+
const { TelegramLogin, TelegramLoginError, validateCredentials } =
|
|
65
|
+
await import('@metro-labs/telegram/login');
|
|
66
|
+
const isOwn = (e: unknown): boolean => e instanceof TelegramLoginError;
|
|
67
67
|
let login;
|
|
68
68
|
try {
|
|
69
|
-
login = new
|
|
69
|
+
login = new TelegramLogin(
|
|
70
70
|
validateCredentials({
|
|
71
71
|
apiId: input.apiId,
|
|
72
72
|
apiHash: input.apiHash,
|
|
@@ -155,7 +155,7 @@ export async function startInteractiveAttach(
|
|
|
155
155
|
input: Record<string, unknown>,
|
|
156
156
|
hooks: DriverHooks,
|
|
157
157
|
): Promise<StartedAttach> {
|
|
158
|
-
return station === 'telegram
|
|
158
|
+
return station === 'telegram'
|
|
159
159
|
? startTelegramUser(input, hooks)
|
|
160
160
|
: startWhatsapp(input, hooks);
|
|
161
161
|
}
|
|
@@ -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 {
|
|
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
|
-
|
|
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
|
-
"./
|
|
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
|
}
|