@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.
- package/dist/api.js +103 -0
- package/dist/cli.js +134 -565
- package/dist/control.js +102 -0
- package/dist/prompt.js +10 -0
- package/dist/runtime.js +94 -0
- package/dist/store.js +70 -0
- package/dist/update.js +33 -0
- package/dist/version.js +97 -0
- package/package.json +34 -22
- package/runtime/node_modules/@metro-labs/discord/package.json +28 -0
- package/runtime/node_modules/@metro-labs/discord/src/accounts.ts +119 -0
- package/runtime/node_modules/@metro-labs/discord/src/actions.ts +388 -0
- package/runtime/node_modules/@metro-labs/discord/src/api-base.ts +1 -0
- package/runtime/node_modules/@metro-labs/discord/src/attachments.ts +29 -0
- package/runtime/node_modules/@metro-labs/discord/src/format.ts +210 -0
- package/runtime/node_modules/@metro-labs/discord/src/group-actions.ts +50 -0
- package/runtime/node_modules/@metro-labs/discord/src/group.ts +115 -0
- package/runtime/node_modules/@metro-labs/discord/src/index.ts +127 -0
- package/runtime/node_modules/@metro-labs/discord/src/members.ts +215 -0
- package/runtime/node_modules/@metro-labs/discord/src/send-files.ts +43 -0
- package/runtime/node_modules/@metro-labs/discord/src/station.ts +19 -0
- package/runtime/node_modules/@metro-labs/discord/src/verify.ts +82 -0
- package/runtime/node_modules/@metro-labs/discord/src/voice-speak.ts +195 -0
- package/runtime/node_modules/@metro-labs/discord/src/voice-transcribe.ts +292 -0
- package/runtime/node_modules/@metro-labs/discord/src/voice.ts +197 -0
- package/runtime/node_modules/@metro-labs/discord/src/wire.ts +5 -0
- package/runtime/node_modules/@metro-labs/mcp/package.json +66 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/dedupe.ts +24 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/inbound.ts +367 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/media-note.ts +85 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/pending.ts +75 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/relay.ts +129 -0
- package/runtime/node_modules/@metro-labs/mcp/src/channels/webhook-note.ts +51 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +311 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +335 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-error.ts +8 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +129 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-grant.ts +99 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-owner.ts +32 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +124 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +278 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +322 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +111 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair.ts +20 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/collection-api.ts +202 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +338 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +31 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +157 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +127 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-tools.ts +100 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +57 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +333 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/cors.ts +29 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/crash-guard.ts +49 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/events.ts +117 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-auth.ts +164 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-oauth.ts +208 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +357 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +79 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/log.ts +42 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-client.ts +162 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-discovery.ts +120 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +65 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/pair-codes.ts +58 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +134 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +214 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/protocol.ts +224 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +125 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-pair.ts +18 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/runtime-source.ts +105 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/secure-fs.ts +57 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +27 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +41 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +218 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor-io.ts +97 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +323 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-call.ts +22 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/train-error.ts +53 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +169 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-api.ts +295 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/upload-store.ts +181 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/account-attach.ts +91 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +215 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +71 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-scope.ts +64 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/client.ts +37 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/connector-collections.ts +218 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/connector-config.ts +147 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +329 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +19 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/key-map.ts +45 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +364 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +300 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +31 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/runtimes.ts +105 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +131 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/token-carry.ts +45 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +57 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/whatsapp-login.ts +18 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/accounts.ts +145 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/call-tools.ts +203 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/channel-owner.ts +29 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/ctx.ts +62 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/event-store.ts +91 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/frame-scope.ts +21 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/group-tools.ts +129 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/index.ts +192 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/member-tools.ts +72 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/permission-relay.ts +66 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/raw-get-stream.ts +207 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +73 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-registry.ts +172 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +43 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/session.ts +211 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/str.ts +5 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-dispatch.ts +212 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/tool-schemas.ts +397 -0
- package/runtime/node_modules/@metro-labs/mcp/src/mcp/upload-tool.ts +66 -0
- package/runtime/node_modules/@metro-labs/mcp/src/monitor/api.ts +240 -0
- package/runtime/node_modules/@metro-labs/mcp/src/server.ts +2 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/account-store.ts +80 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-inline.ts +100 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-interactive.ts +161 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-resolve.ts +200 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach-xmtp.ts +111 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attach.ts +182 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/attachments.ts +196 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/lines.ts +82 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/messaging-normalize.ts +88 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/registry.ts +35 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/station-runtime.ts +64 -0
- package/runtime/node_modules/@metro-labs/mcp/src/stations/types.ts +107 -0
- package/runtime/node_modules/@metro-labs/telegram/package.json +25 -0
- package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +120 -0
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +295 -0
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +128 -0
- package/runtime/node_modules/@metro-labs/telegram/src/format.ts +172 -0
- package/runtime/node_modules/@metro-labs/telegram/src/index.ts +103 -0
- package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram/src/members.ts +47 -0
- package/runtime/node_modules/@metro-labs/telegram/src/station.ts +17 -0
- package/runtime/node_modules/@metro-labs/telegram/src/types.ts +48 -0
- package/runtime/node_modules/@metro-labs/telegram/src/verify.ts +38 -0
- package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +3 -0
- package/runtime/node_modules/@metro-labs/telegram-user/package.json +28 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/accounts.ts +81 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/actions.ts +254 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/attachments.ts +87 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/client.ts +77 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/format.ts +141 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/group.ts +261 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/history.ts +52 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/inbound.ts +78 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/index.ts +46 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/login.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/media-actions.ts +80 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/members.ts +75 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/reactions.ts +167 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/station.ts +24 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/types.ts +7 -0
- package/runtime/node_modules/@metro-labs/telegram-user/src/wire.ts +1 -0
- package/runtime/node_modules/@metro-labs/webhook/package.json +23 -0
- package/runtime/node_modules/@metro-labs/webhook/src/station.ts +40 -0
- package/runtime/node_modules/@metro-labs/whatsapp/package.json +29 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +67 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/ack.ts +101 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +178 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +72 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/auth-state.ts +129 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +318 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/delivery.ts +49 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/format.ts +132 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/inbound.ts +57 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/index.ts +40 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/keys.ts +59 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/logger.ts +60 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +176 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/media.ts +84 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/outbox.ts +38 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/parse.ts +141 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/station.ts +17 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/token-store.ts +98 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/types.ts +6 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/wire.ts +5 -0
- package/runtime/node_modules/@metro-labs/xmtp/package.json +33 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +113 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-close.ts +110 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +299 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +157 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions-push.ts +87 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +375 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +107 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +171 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +239 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-core.ts +55 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +333 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/emit.ts +78 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group-members.ts +34 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +56 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +33 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/index.ts +125 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +157 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +40 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/push.ts +349 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/station.ts +13 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools-handlers.ts +126 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/tools.ts +212 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +90 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/verify.ts +84 -0
- package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +39 -0
- package/runtime/runtime.json +3 -0
- package/runtime/trains/.keep +0 -0
- package/LICENSE +0 -21
- package/README.md +0 -78
- package/dist/channels/discord.js +0 -148
- package/dist/channels/telegram.js +0 -137
- package/dist/lib/address.js +0 -21
- package/dist/lib/codex-rc.js +0 -274
- package/dist/lib/dotenv.js +0 -31
- package/dist/log.js +0 -12
- package/dist/paths.js +0 -45
- package/dist/tail.js +0 -161
- package/skills/metro/SKILL.md +0 -122
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Client,
|
|
3
|
+
type ClientOptions,
|
|
4
|
+
type Conversation,
|
|
5
|
+
} from '@xmtp/node-sdk';
|
|
6
|
+
import { homedir } from 'node:os';
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import { join } from 'node:path';
|
|
9
|
+
import { CODECS } from './codecs.js';
|
|
10
|
+
import { expandHome, signerFor, XMTP_ENV } from './identity.js';
|
|
11
|
+
import {
|
|
12
|
+
makeAccountStore,
|
|
13
|
+
resolveAccountId,
|
|
14
|
+
} from '@metro-labs/mcp/stations/account-store';
|
|
15
|
+
import { Line } from '@metro-labs/mcp/lines';
|
|
16
|
+
|
|
17
|
+
const ACCOUNTS_FILE =
|
|
18
|
+
process.env.XMTP_ACCOUNTS_FILE ??
|
|
19
|
+
join(homedir(), '.metro', 'xmtp-accounts.json');
|
|
20
|
+
|
|
21
|
+
export interface AccountConfig {
|
|
22
|
+
id: string;
|
|
23
|
+
privateKey?: string;
|
|
24
|
+
owner?: string;
|
|
25
|
+
dbPath?: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export const { loadAccounts } = makeAccountStore<AccountConfig>({
|
|
29
|
+
prefix: 'xmtp',
|
|
30
|
+
file: ACCOUNTS_FILE,
|
|
31
|
+
allowlistEnv: ['XMTP_ONLY_ACCOUNTS', 'XMTP_ACCOUNTS'],
|
|
32
|
+
validate(raw, die) {
|
|
33
|
+
const seen = new Set<string>();
|
|
34
|
+
for (const a of raw) {
|
|
35
|
+
if (!a.id) die('account missing id');
|
|
36
|
+
if (!a.privateKey) die(`account '${a.id}' needs a privateKey`);
|
|
37
|
+
if (seen.has(a.id)) die(`duplicate account id '${a.id}'`);
|
|
38
|
+
seen.add(a.id);
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
function resolvePrivateKey(cfg: AccountConfig): string {
|
|
44
|
+
const privateKey = cfg.privateKey?.trim();
|
|
45
|
+
if (!privateKey) throw new Error(`account '${cfg.id}' has no privateKey`);
|
|
46
|
+
return privateKey;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface Account {
|
|
50
|
+
cfg: AccountConfig;
|
|
51
|
+
client: Client<unknown>;
|
|
52
|
+
inboxId: string;
|
|
53
|
+
address: string;
|
|
54
|
+
}
|
|
55
|
+
export const accounts = new Map<string, Account>();
|
|
56
|
+
|
|
57
|
+
export async function bootAccount(cfg: AccountConfig): Promise<void> {
|
|
58
|
+
const { signer, address } = signerFor(resolvePrivateKey(cfg));
|
|
59
|
+
const dbPath = expandHome(
|
|
60
|
+
cfg.dbPath ?? join(homedir(), '.metro', `xmtp-${XMTP_ENV}-${cfg.id}.db3`),
|
|
61
|
+
);
|
|
62
|
+
const fresh = !existsSync(dbPath);
|
|
63
|
+
const options: ClientOptions = { env: XMTP_ENV, codecs: CODECS(), dbPath };
|
|
64
|
+
const client: Client<unknown> = await Client.create(signer, options);
|
|
65
|
+
accounts.set(cfg.id, { cfg, client, inboxId: client.inboxId, address });
|
|
66
|
+
if (fresh)
|
|
67
|
+
process.stderr.write(
|
|
68
|
+
`xmtp[${cfg.id}] registered a NEW installation for this machine — ` +
|
|
69
|
+
'an inbox allows 10, and each machine you run metro on spends one\n',
|
|
70
|
+
);
|
|
71
|
+
process.stderr.write(
|
|
72
|
+
`xmtp[${cfg.id}] ready — inbox ${client.inboxId} (${address}, owner=${cfg.owner ?? '(broadcast)'})\n`,
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function lineOf(accountId: string, convId: string): string {
|
|
77
|
+
return `metro://xmtp/${accountId}/${convId}`;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function parseLine(
|
|
81
|
+
line: string,
|
|
82
|
+
): { accountId: string; convId: string } | null {
|
|
83
|
+
const p = Line.parseXmtp(line);
|
|
84
|
+
return p ? { accountId: p.accountId, convId: p.resource } : null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function accountForCall(args: {
|
|
88
|
+
account?: string;
|
|
89
|
+
line?: string;
|
|
90
|
+
}): Account {
|
|
91
|
+
const id = resolveAccountId(
|
|
92
|
+
accounts,
|
|
93
|
+
args,
|
|
94
|
+
(line) => parseLine(line)?.accountId,
|
|
95
|
+
);
|
|
96
|
+
const acct = accounts.get(id);
|
|
97
|
+
if (!acct) throw new Error(`unknown account '${id}'`);
|
|
98
|
+
return acct;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
export async function convOf(
|
|
102
|
+
line: string,
|
|
103
|
+
): Promise<{ acct: Account; conv: Conversation | undefined }> {
|
|
104
|
+
const parsed = parseLine(line);
|
|
105
|
+
if (!parsed) throw new Error(`bad xmtp line: ${line}`);
|
|
106
|
+
const acct = accounts.get(parsed.accountId);
|
|
107
|
+
if (!acct)
|
|
108
|
+
throw new Error(`unknown account '${parsed.accountId}' in line ${line}`);
|
|
109
|
+
return {
|
|
110
|
+
acct,
|
|
111
|
+
conv: await acct.client.conversations.getConversationById(parsed.convId),
|
|
112
|
+
};
|
|
113
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { errMsg } from '@metro-labs/mcp/log';
|
|
2
|
+
import { accountForCall, convOf, lineOf, type Account } from './accounts.js';
|
|
3
|
+
import { respond } from './wire.js';
|
|
4
|
+
import type { GroupLike } from './labels.js';
|
|
5
|
+
|
|
6
|
+
type Args = Record<string, unknown>;
|
|
7
|
+
|
|
8
|
+
async function removeSelfFromGroup(
|
|
9
|
+
id: string,
|
|
10
|
+
group: GroupLike & { removeMembers: (ids: string[]) => Promise<void> },
|
|
11
|
+
acct: Account,
|
|
12
|
+
resolvedLine: string,
|
|
13
|
+
removed: string[],
|
|
14
|
+
): Promise<boolean> {
|
|
15
|
+
try {
|
|
16
|
+
await group.removeMembers([acct.inboxId]);
|
|
17
|
+
return true;
|
|
18
|
+
} catch (e) {
|
|
19
|
+
respond(id, {
|
|
20
|
+
result: {
|
|
21
|
+
line: resolvedLine,
|
|
22
|
+
id: group.id,
|
|
23
|
+
account: acct.cfg.id,
|
|
24
|
+
removed,
|
|
25
|
+
leftSelf: false,
|
|
26
|
+
selfRemovalError: `self-removal not supported: ${errMsg(e)}`,
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
type ClosableGroup = GroupLike & {
|
|
34
|
+
removeMembers: (ids: string[]) => Promise<void>;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
async function resolveCloseTarget(
|
|
38
|
+
id: string,
|
|
39
|
+
args: Args,
|
|
40
|
+
acct: Account,
|
|
41
|
+
): Promise<{ group: ClosableGroup; resolvedLine: string } | null> {
|
|
42
|
+
const { line, groupId } = args as { line?: string; groupId?: string };
|
|
43
|
+
const resolvedLine =
|
|
44
|
+
line ?? (groupId ? lineOf(acct.cfg.id, groupId) : undefined);
|
|
45
|
+
if (!resolvedLine) {
|
|
46
|
+
respond(id, { error: 'closeGroup requires `line` or `groupId`' });
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
const { conv } = await convOf(resolvedLine);
|
|
50
|
+
if (!conv) {
|
|
51
|
+
respond(id, { error: `conversation not found for ${resolvedLine}` });
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
const group = conv as unknown as ClosableGroup;
|
|
55
|
+
if (typeof group.removeMembers !== 'function') {
|
|
56
|
+
respond(id, {
|
|
57
|
+
error: 'closeGroup target is not a group (no removeMembers)',
|
|
58
|
+
});
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
await group.sync?.().catch(() => undefined);
|
|
62
|
+
return { group, resolvedLine };
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function closeGroup(id: string, args: Args): Promise<void> {
|
|
66
|
+
const { removeInboxIds, removeSelf } = args as {
|
|
67
|
+
removeInboxIds?: string[];
|
|
68
|
+
removeSelf?: boolean;
|
|
69
|
+
};
|
|
70
|
+
const acct = accountForCall(args);
|
|
71
|
+
const target = await resolveCloseTarget(id, args, acct);
|
|
72
|
+
if (!target) return;
|
|
73
|
+
const { group, resolvedLine } = target;
|
|
74
|
+
const removeMembers = group.removeMembers.bind(group);
|
|
75
|
+
|
|
76
|
+
const others = (removeInboxIds ?? []).filter(
|
|
77
|
+
(iid) => typeof iid === 'string' && iid && iid !== acct.inboxId,
|
|
78
|
+
);
|
|
79
|
+
const removed: string[] = [];
|
|
80
|
+
let leftSelf = false;
|
|
81
|
+
try {
|
|
82
|
+
if (others.length) {
|
|
83
|
+
await removeMembers(others);
|
|
84
|
+
removed.push(...others);
|
|
85
|
+
}
|
|
86
|
+
if (removeSelf) {
|
|
87
|
+
leftSelf = await removeSelfFromGroup(
|
|
88
|
+
id,
|
|
89
|
+
group,
|
|
90
|
+
acct,
|
|
91
|
+
resolvedLine,
|
|
92
|
+
removed,
|
|
93
|
+
);
|
|
94
|
+
if (!leftSelf) return;
|
|
95
|
+
}
|
|
96
|
+
} catch (err) {
|
|
97
|
+
respond(id, { error: errMsg(err) });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
respond(id, {
|
|
102
|
+
result: {
|
|
103
|
+
line: resolvedLine,
|
|
104
|
+
id: group.id,
|
|
105
|
+
account: acct.cfg.id,
|
|
106
|
+
removed,
|
|
107
|
+
leftSelf,
|
|
108
|
+
},
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
import { IdentifierKind } from '@xmtp/node-sdk';
|
|
2
|
+
import {
|
|
3
|
+
accounts,
|
|
4
|
+
accountForCall,
|
|
5
|
+
convOf,
|
|
6
|
+
lineOf,
|
|
7
|
+
parseLine,
|
|
8
|
+
type Account,
|
|
9
|
+
} from './accounts.js';
|
|
10
|
+
import { respond } from './wire.js';
|
|
11
|
+
import { TrainError } from '@metro-labs/mcp/train-error';
|
|
12
|
+
import { pushHandlers } from './actions-push.js';
|
|
13
|
+
import { cleanLabels, labelsBlob } from './labels.js';
|
|
14
|
+
import {
|
|
15
|
+
applyMemberOp,
|
|
16
|
+
buildGroupInfo,
|
|
17
|
+
buildMemberList,
|
|
18
|
+
createGroupWithMembers,
|
|
19
|
+
ethIdentifiers,
|
|
20
|
+
parseMemberArgs,
|
|
21
|
+
resolveMembers,
|
|
22
|
+
warmGroupName,
|
|
23
|
+
} from './conv-helpers.js';
|
|
24
|
+
import { closeGroup } from './actions-close.js';
|
|
25
|
+
import {
|
|
26
|
+
groupAddMembers,
|
|
27
|
+
groupCreate,
|
|
28
|
+
groupRemoveMembers,
|
|
29
|
+
} from './group.js';
|
|
30
|
+
import {
|
|
31
|
+
updateChannelMeta,
|
|
32
|
+
applyChannelMeta,
|
|
33
|
+
resolveLine,
|
|
34
|
+
setGithub,
|
|
35
|
+
setPreview,
|
|
36
|
+
} from './actions-meta.js';
|
|
37
|
+
|
|
38
|
+
type Args = Record<string, unknown>;
|
|
39
|
+
type Handler = (id: string, args: Args) => Promise<void>;
|
|
40
|
+
type Conv = Awaited<ReturnType<typeof convOf>>['conv'] & object;
|
|
41
|
+
|
|
42
|
+
async function newDm(id: string, args: Args): Promise<void> {
|
|
43
|
+
const { address } = args as { address: string };
|
|
44
|
+
const acct = accountForCall(args);
|
|
45
|
+
const dm = await acct.client.conversations.createDmWithIdentifier({
|
|
46
|
+
identifier: address,
|
|
47
|
+
identifierKind: IdentifierKind.Ethereum,
|
|
48
|
+
});
|
|
49
|
+
respond(id, {
|
|
50
|
+
result: {
|
|
51
|
+
line: lineOf(acct.cfg.id, dm.id),
|
|
52
|
+
id: dm.id,
|
|
53
|
+
account: acct.cfg.id,
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
async function newGroup(id: string, args: Args): Promise<void> {
|
|
59
|
+
const { addresses, name, permissions } = args as {
|
|
60
|
+
addresses: string[];
|
|
61
|
+
name?: string;
|
|
62
|
+
permissions?: 'admin-only' | 'default';
|
|
63
|
+
};
|
|
64
|
+
const acct = accountForCall(args);
|
|
65
|
+
const opts: { groupName?: string; permissions?: number } = {};
|
|
66
|
+
if (name) opts.groupName = name;
|
|
67
|
+
if (permissions === 'admin-only') opts.permissions = 1;
|
|
68
|
+
const group = await acct.client.conversations.createGroupWithIdentifiers(
|
|
69
|
+
ethIdentifiers(addresses),
|
|
70
|
+
opts,
|
|
71
|
+
);
|
|
72
|
+
warmGroupName(group.id, name);
|
|
73
|
+
respond(id, {
|
|
74
|
+
result: {
|
|
75
|
+
line: lineOf(acct.cfg.id, group.id),
|
|
76
|
+
id: group.id,
|
|
77
|
+
account: acct.cfg.id,
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async function createRequestGroup(id: string, args: Args): Promise<void> {
|
|
83
|
+
const { name, description, labels } = args as {
|
|
84
|
+
name: string;
|
|
85
|
+
description?: string;
|
|
86
|
+
labels?: string[];
|
|
87
|
+
};
|
|
88
|
+
const acct = accountForCall(args);
|
|
89
|
+
if (!name || typeof name !== 'string')
|
|
90
|
+
throw new TrainError('INVALID_ARGS', 'createRequestGroup requires a `name`');
|
|
91
|
+
const { addrs, inboxes } = resolveMembers(args);
|
|
92
|
+
if (addrs.length === 0 && inboxes.length === 0)
|
|
93
|
+
throw new TrainError(
|
|
94
|
+
'INVALID_ARGS',
|
|
95
|
+
'createRequestGroup requires addresses[] or inboxIds[]',
|
|
96
|
+
);
|
|
97
|
+
const opts: {
|
|
98
|
+
groupName: string;
|
|
99
|
+
groupDescription?: string;
|
|
100
|
+
appData?: string;
|
|
101
|
+
} = { groupName: name };
|
|
102
|
+
if (description) opts.groupDescription = description;
|
|
103
|
+
if (Array.isArray(labels) && labels.length)
|
|
104
|
+
opts.appData = labelsBlob(undefined, labels);
|
|
105
|
+
|
|
106
|
+
const group = await createGroupWithMembers(acct, addrs, inboxes, opts);
|
|
107
|
+
warmGroupName(group.id, name);
|
|
108
|
+
respond(id, {
|
|
109
|
+
result: {
|
|
110
|
+
line: lineOf(acct.cfg.id, group.id),
|
|
111
|
+
id: group.id,
|
|
112
|
+
account: acct.cfg.id,
|
|
113
|
+
name,
|
|
114
|
+
description: description ?? '',
|
|
115
|
+
labels: cleanLabels(labels ?? []),
|
|
116
|
+
},
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
async function setLabels(id: string, args: Args): Promise<void> {
|
|
121
|
+
const { labels, setName, setDescription, setGithub } = args as {
|
|
122
|
+
line?: string;
|
|
123
|
+
groupId?: string;
|
|
124
|
+
labels: string[];
|
|
125
|
+
setName?: string;
|
|
126
|
+
setDescription?: string;
|
|
127
|
+
setGithub?: string;
|
|
128
|
+
};
|
|
129
|
+
const resolvedLine = resolveLine(args, 'setLabels');
|
|
130
|
+
if (!Array.isArray(labels))
|
|
131
|
+
throw new TrainError('INVALID_ARGS', 'setLabels requires a `labels` array');
|
|
132
|
+
const appData: Record<string, unknown> = { labels };
|
|
133
|
+
if (typeof setGithub === 'string') appData.github = setGithub;
|
|
134
|
+
const result = await applyChannelMeta(
|
|
135
|
+
{ line: resolvedLine, name: setName, description: setDescription, appData },
|
|
136
|
+
'setLabels',
|
|
137
|
+
);
|
|
138
|
+
respond(id, {
|
|
139
|
+
result: {
|
|
140
|
+
line: result.line,
|
|
141
|
+
id: result.id,
|
|
142
|
+
account: result.account,
|
|
143
|
+
labels: result.labels,
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async function query(id: string, args: Args): Promise<void> {
|
|
149
|
+
const { line, limit } = args as { line: string; limit?: number };
|
|
150
|
+
const { conv } = await convOf(line);
|
|
151
|
+
if (!conv)
|
|
152
|
+
throw new TrainError('NOT_FOUND', `conversation not found for ${line}`);
|
|
153
|
+
const lim = Math.min(Math.max(1, limit ?? 20), 200);
|
|
154
|
+
await conv.sync().catch(() => undefined);
|
|
155
|
+
const all = await conv.messages();
|
|
156
|
+
const slice = all.slice(-lim);
|
|
157
|
+
const parsed = parseLine(line);
|
|
158
|
+
if (!parsed)
|
|
159
|
+
throw new TrainError('NOT_FOUND', `could not parse line ${line}`);
|
|
160
|
+
const acctId = parsed.accountId;
|
|
161
|
+
const messages = slice.map((m) => {
|
|
162
|
+
let text = '';
|
|
163
|
+
try {
|
|
164
|
+
const cc: unknown = m.content;
|
|
165
|
+
text =
|
|
166
|
+
typeof cc === 'string' ? cc : `[${m.contentType?.typeId ?? 'unknown'}]`;
|
|
167
|
+
} catch {
|
|
168
|
+
text = `[${m.contentType?.typeId ?? 'unknown'}]`;
|
|
169
|
+
}
|
|
170
|
+
return {
|
|
171
|
+
id: m.id,
|
|
172
|
+
ts: new Date(Number(m.sentAtNs / 1_000_000n)).toISOString(),
|
|
173
|
+
from: `metro://xmtp/${acctId}/user/${m.senderInboxId}`,
|
|
174
|
+
text,
|
|
175
|
+
contentType: m.contentType?.typeId ?? 'unknown',
|
|
176
|
+
};
|
|
177
|
+
});
|
|
178
|
+
respond(id, { result: { line, count: messages.length, messages } });
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
async function addMembers(id: string, args: Args): Promise<void> {
|
|
182
|
+
const { line, addrs, inboxes } = parseMemberArgs(args, 'addMembers');
|
|
183
|
+
const { acct, conv } = await convOf(line);
|
|
184
|
+
if (!conv)
|
|
185
|
+
throw new TrainError('NOT_FOUND', `conversation not found for ${line}`);
|
|
186
|
+
await applyMemberOp(conv, addrs, inboxes, 'add');
|
|
187
|
+
const info = await buildGroupInfo(line, acct, conv);
|
|
188
|
+
respond(id, {
|
|
189
|
+
result: { ...info, added: { addresses: addrs, inboxIds: inboxes } },
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
async function removeMembers(id: string, args: Args): Promise<void> {
|
|
194
|
+
const { line, addrs, inboxes } = parseMemberArgs(args, 'removeMembers');
|
|
195
|
+
const { acct, conv } = await convOf(line);
|
|
196
|
+
if (!conv)
|
|
197
|
+
throw new TrainError('NOT_FOUND', `conversation not found for ${line}`);
|
|
198
|
+
await applyMemberOp(conv, addrs, inboxes, 'remove');
|
|
199
|
+
const info = await buildGroupInfo(line, acct, conv);
|
|
200
|
+
respond(id, {
|
|
201
|
+
result: { ...info, removed: { addresses: addrs, inboxIds: inboxes } },
|
|
202
|
+
});
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function groupInfo(id: string, args: Args): Promise<void> {
|
|
206
|
+
const { line } = args as { line: string };
|
|
207
|
+
const { acct, conv } = await convOf(line);
|
|
208
|
+
if (!conv)
|
|
209
|
+
throw new TrainError('NOT_FOUND', `conversation not found for ${line}`);
|
|
210
|
+
respond(id, { result: await buildGroupInfo(line, acct, conv) });
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
async function listMembers(id: string, args: Args): Promise<void> {
|
|
214
|
+
const { line } = args as { line: string };
|
|
215
|
+
const { acct, conv } = await convOf(line);
|
|
216
|
+
if (!conv)
|
|
217
|
+
throw new TrainError('NOT_FOUND', `conversation not found for ${line}`);
|
|
218
|
+
respond(id, { result: await buildMemberList(acct, conv) });
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
async function summarizeConv(acct: Account, c: Conv): Promise<unknown> {
|
|
222
|
+
const recent = await c.messages({ limit: 1, direction: 1 }).catch(() => []);
|
|
223
|
+
const last = recent[0];
|
|
224
|
+
let preview = '';
|
|
225
|
+
if (last) {
|
|
226
|
+
const cc: unknown = last.content;
|
|
227
|
+
preview =
|
|
228
|
+
typeof cc === 'string'
|
|
229
|
+
? cc.slice(0, 80)
|
|
230
|
+
: `[${last.contentType?.typeId ?? 'unknown'}]`;
|
|
231
|
+
}
|
|
232
|
+
const isDm =
|
|
233
|
+
typeof (c as unknown as { peerInboxId?: unknown }).peerInboxId ===
|
|
234
|
+
'function';
|
|
235
|
+
const gn = (c as unknown as { name?: string | (() => Promise<string>) }).name;
|
|
236
|
+
const resolvedName =
|
|
237
|
+
typeof gn === 'function' ? await gn().catch(() => '') : (gn ?? '');
|
|
238
|
+
return {
|
|
239
|
+
line: lineOf(acct.cfg.id, c.id),
|
|
240
|
+
id: c.id,
|
|
241
|
+
account: acct.cfg.id,
|
|
242
|
+
version: isDm ? 'dm' : 'group',
|
|
243
|
+
name: resolvedName ?? '',
|
|
244
|
+
lastTs: last
|
|
245
|
+
? new Date(Number(last.sentAtNs / 1_000_000n)).toISOString()
|
|
246
|
+
: null,
|
|
247
|
+
lastPreview: preview,
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async function listConvs(id: string, args: Args): Promise<void> {
|
|
252
|
+
const { limit, account } = args as { limit?: number; account?: string };
|
|
253
|
+
const lim = Math.min(Math.max(1, limit ?? 50), 200);
|
|
254
|
+
const targets = account
|
|
255
|
+
? [accounts.get(account)].filter((a): a is Account => a !== undefined)
|
|
256
|
+
: [...accounts.values()];
|
|
257
|
+
const summaries: unknown[] = [];
|
|
258
|
+
for (const acct of targets) {
|
|
259
|
+
await acct.client.conversations.syncAll();
|
|
260
|
+
const all = await acct.client.conversations.list();
|
|
261
|
+
for (const c of all.slice(0, lim))
|
|
262
|
+
summaries.push(await summarizeConv(acct, c));
|
|
263
|
+
}
|
|
264
|
+
summaries.sort((a, b) =>
|
|
265
|
+
((b as { lastTs?: string }).lastTs ?? '').localeCompare(
|
|
266
|
+
(a as { lastTs?: string }).lastTs ?? '',
|
|
267
|
+
),
|
|
268
|
+
);
|
|
269
|
+
respond(id, {
|
|
270
|
+
result: { count: summaries.length, conversations: summaries.slice(0, lim) },
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
export const convHandlers: Record<string, Handler> = {
|
|
275
|
+
newDm,
|
|
276
|
+
newGroup,
|
|
277
|
+
createRequestGroup,
|
|
278
|
+
addMembers,
|
|
279
|
+
removeMembers,
|
|
280
|
+
setLabels,
|
|
281
|
+
setGithub,
|
|
282
|
+
setPreview,
|
|
283
|
+
updateChannelMeta,
|
|
284
|
+
closeGroup,
|
|
285
|
+
query,
|
|
286
|
+
groupInfo,
|
|
287
|
+
listMembers,
|
|
288
|
+
listConvs,
|
|
289
|
+
groupCreate: async (id, args) => {
|
|
290
|
+
respond(id, { result: await groupCreate(args) });
|
|
291
|
+
},
|
|
292
|
+
groupAddMembers: async (id, args) => {
|
|
293
|
+
respond(id, { result: await groupAddMembers(args) });
|
|
294
|
+
},
|
|
295
|
+
groupRemoveMembers: async (id, args) => {
|
|
296
|
+
respond(id, { result: await groupRemoveMembers(args) });
|
|
297
|
+
},
|
|
298
|
+
...pushHandlers,
|
|
299
|
+
};
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { accountForCall, convOf, lineOf } from './accounts.js';
|
|
2
|
+
import { respond } from './wire.js';
|
|
3
|
+
import { warmGroupName } from './conv-helpers.js';
|
|
4
|
+
import { mergeAppData, readAppData, type GroupLike } from './labels.js';
|
|
5
|
+
import { TrainError } from '@metro-labs/mcp/train-error';
|
|
6
|
+
|
|
7
|
+
type Args = Record<string, unknown>;
|
|
8
|
+
|
|
9
|
+
export function resolveLine(args: Args, verb: string): string {
|
|
10
|
+
const line = (args as { line?: string }).line;
|
|
11
|
+
if (line) return line;
|
|
12
|
+
const groupId = (args as { groupId?: string }).groupId;
|
|
13
|
+
if (groupId) {
|
|
14
|
+
const acct = accountForCall(args);
|
|
15
|
+
return lineOf(acct.cfg.id, groupId);
|
|
16
|
+
}
|
|
17
|
+
throw new TrainError(
|
|
18
|
+
'INVALID_ARGS',
|
|
19
|
+
`${verb} requires \`line\` or \`groupId\``,
|
|
20
|
+
);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async function applyNameAndDescription(
|
|
24
|
+
group: GroupLike,
|
|
25
|
+
name: string | undefined,
|
|
26
|
+
description: string | undefined,
|
|
27
|
+
): Promise<void> {
|
|
28
|
+
if (
|
|
29
|
+
typeof name === 'string' &&
|
|
30
|
+
name &&
|
|
31
|
+
typeof group.updateName === 'function'
|
|
32
|
+
) {
|
|
33
|
+
await group.updateName(name);
|
|
34
|
+
warmGroupName(group.id, name);
|
|
35
|
+
}
|
|
36
|
+
if (
|
|
37
|
+
typeof description === 'string' &&
|
|
38
|
+
typeof group.updateDescription === 'function'
|
|
39
|
+
) {
|
|
40
|
+
await group.updateDescription(description);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function applyMergedAppData(
|
|
45
|
+
group: GroupLike & { updateAppData: (s: string) => Promise<void> },
|
|
46
|
+
appData: Record<string, unknown> | undefined,
|
|
47
|
+
): Promise<Record<string, unknown>> {
|
|
48
|
+
if (appData && typeof appData === 'object' && !Array.isArray(appData)) {
|
|
49
|
+
const res = mergeAppData(group.appData, appData);
|
|
50
|
+
await group.updateAppData(res.blob);
|
|
51
|
+
return res.merged;
|
|
52
|
+
}
|
|
53
|
+
return readAppData(group.appData);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export async function applyChannelMeta(
|
|
57
|
+
args: {
|
|
58
|
+
line: string;
|
|
59
|
+
name?: string;
|
|
60
|
+
description?: string;
|
|
61
|
+
appData?: Record<string, unknown>;
|
|
62
|
+
},
|
|
63
|
+
verb: string,
|
|
64
|
+
): Promise<Record<string, unknown>> {
|
|
65
|
+
const { line, name, description, appData } = args;
|
|
66
|
+
const { acct, conv } = await convOf(line);
|
|
67
|
+
if (!conv)
|
|
68
|
+
throw new TrainError('NOT_FOUND', `conversation not found for ${line}`);
|
|
69
|
+
const group = conv as unknown as GroupLike;
|
|
70
|
+
if (typeof group.updateAppData !== 'function') {
|
|
71
|
+
throw new TrainError(
|
|
72
|
+
'INVALID_ARGS',
|
|
73
|
+
`${verb} target is not a group (no updateAppData)`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
await group.sync?.().catch(() => undefined);
|
|
77
|
+
|
|
78
|
+
await applyNameAndDescription(group, name, description);
|
|
79
|
+
const merged = await applyMergedAppData(
|
|
80
|
+
group as GroupLike & { updateAppData: (s: string) => Promise<void> },
|
|
81
|
+
appData,
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
line,
|
|
86
|
+
id: group.id,
|
|
87
|
+
account: acct.cfg.id,
|
|
88
|
+
...(typeof name === 'string' && name ? { name } : {}),
|
|
89
|
+
...metaFields(merged),
|
|
90
|
+
appData: merged,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function metaFields(merged: Record<string, unknown>): {
|
|
95
|
+
labels: string[];
|
|
96
|
+
github: string | undefined;
|
|
97
|
+
preview: string | undefined;
|
|
98
|
+
} {
|
|
99
|
+
return {
|
|
100
|
+
labels: Array.isArray(merged.labels) ? (merged.labels as string[]) : [],
|
|
101
|
+
github: typeof merged.github === 'string' ? merged.github : undefined,
|
|
102
|
+
preview: typeof merged.preview === 'string' ? merged.preview : undefined,
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export async function updateChannelMeta(id: string, args: Args): Promise<void> {
|
|
107
|
+
const line = resolveLine(args, 'updateChannelMeta');
|
|
108
|
+
const { name, description, appData } = args as {
|
|
109
|
+
name?: string;
|
|
110
|
+
description?: string;
|
|
111
|
+
appData?: Record<string, unknown>;
|
|
112
|
+
};
|
|
113
|
+
const result = await applyChannelMeta(
|
|
114
|
+
{ line, name, description, appData },
|
|
115
|
+
'updateChannelMeta',
|
|
116
|
+
);
|
|
117
|
+
respond(id, { result });
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export async function setGithub(id: string, args: Args): Promise<void> {
|
|
121
|
+
const line = resolveLine(args, 'setGithub');
|
|
122
|
+
const { url } = args as { url: string };
|
|
123
|
+
if (typeof url !== 'string')
|
|
124
|
+
throw new Error('setGithub requires a `url` string');
|
|
125
|
+
const result = await applyChannelMeta(
|
|
126
|
+
{ line, appData: { github: url } },
|
|
127
|
+
'setGithub',
|
|
128
|
+
);
|
|
129
|
+
respond(id, {
|
|
130
|
+
result: {
|
|
131
|
+
line: result.line,
|
|
132
|
+
id: result.id,
|
|
133
|
+
account: result.account,
|
|
134
|
+
github: result.github,
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function setPreview(id: string, args: Args): Promise<void> {
|
|
140
|
+
const line = resolveLine(args, 'setPreview');
|
|
141
|
+
const a = args as { preview?: unknown; url?: unknown };
|
|
142
|
+
const value = typeof a.preview === 'string' ? a.preview : a.url;
|
|
143
|
+
if (typeof value !== 'string')
|
|
144
|
+
throw new Error('setPreview requires a `preview` string');
|
|
145
|
+
const result = await applyChannelMeta(
|
|
146
|
+
{ line, appData: { preview: value } },
|
|
147
|
+
'setPreview',
|
|
148
|
+
);
|
|
149
|
+
respond(id, {
|
|
150
|
+
result: {
|
|
151
|
+
line: result.line,
|
|
152
|
+
id: result.id,
|
|
153
|
+
account: result.account,
|
|
154
|
+
preview: result.preview,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
}
|