@stage-labs/metro 0.1.0-beta.3 → 0.1.0-beta.30
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 +131 -0
- package/dist/bedrock-eventstream.js +120 -0
- package/dist/bedrock-proxy.js +260 -0
- package/dist/bedrock-upstream.js +72 -0
- package/dist/bedrock.js +104 -0
- package/dist/claude.js +15 -0
- package/dist/cli.js +157 -253
- package/dist/control.js +158 -0
- package/dist/plugin.js +82 -0
- package/dist/prompt.js +61 -0
- package/dist/runtime.js +94 -0
- package/dist/store.js +70 -0
- package/dist/tail.js +120 -0
- package/dist/update.js +33 -0
- package/dist/version.js +97 -0
- package/package.json +34 -21
- package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +119 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +388 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/api-base.ts +1 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +29 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/format.ts +210 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/group-actions.ts +50 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +115 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +127 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +215 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +43 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/station.ts +19 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +82 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice-speak.ts +195 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice-transcribe.ts +292 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/voice.ts +197 -0
- package/runtime/node_modules/@metro-labs/discord-bot/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 +337 -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 +129 -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 +321 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +123 -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 +339 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-json.ts +21 -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 +359 -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/relay.ts +302 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +130 -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 +43 -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/connector-relay.ts +126 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +308 -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 +382 -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 +28 -0
- package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +81 -0
- package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +254 -0
- package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +87 -0
- package/runtime/node_modules/@metro-labs/telegram/src/client.ts +77 -0
- package/runtime/node_modules/@metro-labs/telegram/src/format.ts +141 -0
- package/runtime/node_modules/@metro-labs/telegram/src/group.ts +261 -0
- package/runtime/node_modules/@metro-labs/telegram/src/history.ts +52 -0
- package/runtime/node_modules/@metro-labs/telegram/src/inbound.ts +78 -0
- package/runtime/node_modules/@metro-labs/telegram/src/index.ts +46 -0
- package/runtime/node_modules/@metro-labs/telegram/src/login.ts +166 -0
- package/runtime/node_modules/@metro-labs/telegram/src/media-actions.ts +80 -0
- package/runtime/node_modules/@metro-labs/telegram/src/members.ts +75 -0
- package/runtime/node_modules/@metro-labs/telegram/src/normalize.ts +22 -0
- package/runtime/node_modules/@metro-labs/telegram/src/reactions.ts +167 -0
- package/runtime/node_modules/@metro-labs/telegram/src/station.ts +24 -0
- package/runtime/node_modules/@metro-labs/telegram/src/types.ts +7 -0
- package/runtime/node_modules/@metro-labs/telegram/src/wire.ts +1 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/package.json +25 -0
- 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/verify.ts +38 -0
- package/runtime/node_modules/@metro-labs/telegram-bot/src/wire.ts +3 -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 -114
- package/dist/agents/claude.js +0 -229
- package/dist/agents/codex.js +0 -282
- package/dist/agents/types.js +0 -3
- package/dist/channels/discord.js +0 -142
- package/dist/channels/telegram.js +0 -209
- package/dist/lib/dotenv.js +0 -31
- package/dist/lib/scope-cache.js +0 -85
- package/dist/lib/streaming.js +0 -207
- package/dist/log.js +0 -12
- package/dist/orchestrator.js +0 -399
- package/dist/paths.js +0 -33
package/dist/orchestrator.js
DELETED
|
@@ -1,399 +0,0 @@
|
|
|
1
|
-
// Metro orchestrator — long-running daemon. Owns the Discord gateway and
|
|
2
|
-
// Telegram poller, runs both codex and claude as agent backends, streams
|
|
3
|
-
// per-turn responses back to chat with tool-call status visible.
|
|
4
|
-
//
|
|
5
|
-
// Per-message agent routing: a message ending in "with claude" / "with
|
|
6
|
-
// codex" (any casing) targets that agent. Otherwise, the scope's last-used
|
|
7
|
-
// agent answers; for brand-new scopes, the default is Claude.
|
|
8
|
-
//
|
|
9
|
-
// Scopes:
|
|
10
|
-
// Discord — one per thread (auto-created from an @-mention).
|
|
11
|
-
// Telegram — one per DM, one per forum-topic (auto-created when a user
|
|
12
|
-
// @-mentions in General).
|
|
13
|
-
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';
|
|
14
|
-
import { join } from 'node:path';
|
|
15
|
-
import pkg from '../package.json' with { type: 'json' };
|
|
16
|
-
import * as discord from './channels/discord.js';
|
|
17
|
-
import * as telegram from './channels/telegram.js';
|
|
18
|
-
import { CodexAgent } from './agents/codex.js';
|
|
19
|
-
import { ClaudeAgent } from './agents/claude.js';
|
|
20
|
-
import { discordChannelFromScopeKey, discordScopeKey, getAgentThread, getLastAgent, listScopes, setAgentThread, setLastAgent, setLastSeen, telegramScopeKey, } from './lib/scope-cache.js';
|
|
21
|
-
import { StreamingMessage, StreamScheduler } from './lib/streaming.js';
|
|
22
|
-
import { errMsg, log } from './log.js';
|
|
23
|
-
import { configuredPlatforms, loadMetroEnv, STATE_DIR, requireConfiguredPlatform } from './paths.js';
|
|
24
|
-
loadMetroEnv();
|
|
25
|
-
const platforms = configuredPlatforms();
|
|
26
|
-
requireConfiguredPlatform(platforms);
|
|
27
|
-
// Singleton lockfile. The orchestrator owns the Discord gateway / Telegram
|
|
28
|
-
// poller, so only one instance can run per machine.
|
|
29
|
-
const LOCK_FILE = join(STATE_DIR, '.tail-lock');
|
|
30
|
-
function processIsAlive(pid) {
|
|
31
|
-
try {
|
|
32
|
-
process.kill(pid, 0);
|
|
33
|
-
return true;
|
|
34
|
-
}
|
|
35
|
-
catch {
|
|
36
|
-
return false;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
if (existsSync(LOCK_FILE)) {
|
|
40
|
-
const pid = Number(readFileSync(LOCK_FILE, 'utf8').trim());
|
|
41
|
-
if (Number.isInteger(pid) && pid > 0 && processIsAlive(pid)) {
|
|
42
|
-
log.info({ pid }, 'another `metro` instance is already running; exiting');
|
|
43
|
-
process.exit(0);
|
|
44
|
-
}
|
|
45
|
-
try {
|
|
46
|
-
unlinkSync(LOCK_FILE);
|
|
47
|
-
}
|
|
48
|
-
catch { /* ignore */ }
|
|
49
|
-
}
|
|
50
|
-
mkdirSync(STATE_DIR, { recursive: true });
|
|
51
|
-
writeFileSync(LOCK_FILE, String(process.pid));
|
|
52
|
-
process.on('exit', () => { try {
|
|
53
|
-
if (readFileSync(LOCK_FILE, 'utf8').trim() === String(process.pid))
|
|
54
|
-
unlinkSync(LOCK_FILE);
|
|
55
|
-
}
|
|
56
|
-
catch { /* ignore */ } });
|
|
57
|
-
// Track which agent threads we're actively serving a turn in, so we don't
|
|
58
|
-
// send overlapping turn/start requests on the same thread.
|
|
59
|
-
const inFlight = new Set();
|
|
60
|
-
const queued = new Map();
|
|
61
|
-
// De-dupe the *same* gateway delivery (e.g. on reconnect replay).
|
|
62
|
-
const bootstrapped = new Set();
|
|
63
|
-
// Both agents are instantiated; either can fail start() and be left
|
|
64
|
-
// unavailable. Routing falls back gracefully when an agent isn't usable.
|
|
65
|
-
const codexAgent = new CodexAgent(pkg.version);
|
|
66
|
-
const claudeAgent = new ClaudeAgent();
|
|
67
|
-
const available = {};
|
|
68
|
-
// One scheduler per bot — coalesces streamed edits across every active
|
|
69
|
-
// thread so concurrent turns don't compound the bot's edit cadence.
|
|
70
|
-
const discordScheduler = new StreamScheduler();
|
|
71
|
-
const telegramScheduler = new StreamScheduler();
|
|
72
|
-
async function startAgents() {
|
|
73
|
-
await Promise.allSettled([
|
|
74
|
-
codexAgent.start().then(() => { available.codex = codexAgent; })
|
|
75
|
-
.catch(err => log.warn({ err: errMsg(err) }, "codex unavailable — 'with codex' will fail")),
|
|
76
|
-
claudeAgent.start().then(() => { available.claude = claudeAgent; })
|
|
77
|
-
.catch(err => log.warn({ err: errMsg(err) }, "claude unavailable — 'with claude' will fail")),
|
|
78
|
-
]);
|
|
79
|
-
if (Object.keys(available).length === 0) {
|
|
80
|
-
log.fatal('no agents available — install codex or claude and authenticate');
|
|
81
|
-
process.exit(2);
|
|
82
|
-
}
|
|
83
|
-
log.info({ agents: Object.keys(available) }, 'orchestrator: agents ready');
|
|
84
|
-
}
|
|
85
|
-
async function main() {
|
|
86
|
-
await startAgents();
|
|
87
|
-
if (platforms.discord) {
|
|
88
|
-
await discord.startGateway();
|
|
89
|
-
const me = await discord.getMe();
|
|
90
|
-
log.info({ bot: me.username }, 'discord ready');
|
|
91
|
-
discord.onInbound(m => void onDiscordInbound(m).catch(err => log.warn({ err: errMsg(err) }, 'discord inbound failed')));
|
|
92
|
-
void catchupDiscord().catch(err => log.warn({ err: errMsg(err) }, 'discord catchup failed'));
|
|
93
|
-
}
|
|
94
|
-
if (platforms.telegram) {
|
|
95
|
-
// Identity needed for @-mention detection in groups; populated as a
|
|
96
|
-
// side-effect on the channel module.
|
|
97
|
-
const me = await telegram.getMe();
|
|
98
|
-
log.info({ bot: `@${me.username}` }, 'telegram ready');
|
|
99
|
-
telegram.onInbound(m => void onTelegramInbound(m).catch(err => log.warn({ err: errMsg(err) }, 'telegram inbound failed')));
|
|
100
|
-
await telegram.startPolling();
|
|
101
|
-
}
|
|
102
|
-
log.info('orchestrator ready');
|
|
103
|
-
}
|
|
104
|
-
async function catchupDiscord() {
|
|
105
|
-
const scopes = listScopes();
|
|
106
|
-
for (const { scopeKey, entry } of scopes) {
|
|
107
|
-
const channelId = discordChannelFromScopeKey(scopeKey);
|
|
108
|
-
if (!channelId || !entry.lastSeenMessageId)
|
|
109
|
-
continue;
|
|
110
|
-
try {
|
|
111
|
-
const missed = await discord.fetchMessagesSince(channelId, entry.lastSeenMessageId);
|
|
112
|
-
const humanMissed = missed.filter(m => !m.author_is_bot && m.text);
|
|
113
|
-
if (humanMissed.length === 0)
|
|
114
|
-
continue;
|
|
115
|
-
log.info({ channel: channelId, count: humanMissed.length }, 'discord catchup: replaying missed messages');
|
|
116
|
-
for (const m of humanMissed) {
|
|
117
|
-
await onDiscordInbound({
|
|
118
|
-
channel_id: channelId,
|
|
119
|
-
message_id: m.message_id,
|
|
120
|
-
text: m.text,
|
|
121
|
-
in_guild: true,
|
|
122
|
-
mentions_bot: false,
|
|
123
|
-
});
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
catch (err) {
|
|
127
|
-
log.warn({ err: errMsg(err), channel: channelId }, 'discord catchup: channel skipped');
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
// "with claude" / "with codex" suffix (any casing). Captures the kind and
|
|
132
|
-
// returns the message without the suffix. If no suffix, returns null.
|
|
133
|
-
const SUFFIX_RE = /(?:^|\s)with\s+(claude|codex)\s*$/i;
|
|
134
|
-
function parseAgentSuffix(text) {
|
|
135
|
-
const trimmed = text.trimEnd();
|
|
136
|
-
const m = SUFFIX_RE.exec(trimmed);
|
|
137
|
-
if (!m)
|
|
138
|
-
return { kind: null, cleanText: text };
|
|
139
|
-
const kind = m[1].toLowerCase();
|
|
140
|
-
return { kind, cleanText: trimmed.slice(0, m.index).trimEnd() };
|
|
141
|
-
}
|
|
142
|
-
// Effective agent for this turn: explicit suffix beats lastAgent beats the
|
|
143
|
-
// default (Claude). If the requested kind is unavailable, returns null so
|
|
144
|
-
// the caller can surface an error to the user.
|
|
145
|
-
function pickAgent(scopeKey, requestedKind) {
|
|
146
|
-
if (requestedKind) {
|
|
147
|
-
if (!available[requestedKind])
|
|
148
|
-
return { error: `${requestedKind} is not available on this metro instance` };
|
|
149
|
-
return { kind: requestedKind, explicit: true };
|
|
150
|
-
}
|
|
151
|
-
const last = scopeKey ? getLastAgent(scopeKey) : undefined;
|
|
152
|
-
if (last && available[last])
|
|
153
|
-
return { kind: last, explicit: false };
|
|
154
|
-
if (available.claude)
|
|
155
|
-
return { kind: 'claude', explicit: false };
|
|
156
|
-
if (available.codex)
|
|
157
|
-
return { kind: 'codex', explicit: false };
|
|
158
|
-
return { error: 'no agents available' };
|
|
159
|
-
}
|
|
160
|
-
async function onDiscordInbound(m) {
|
|
161
|
-
if (!m.in_guild) {
|
|
162
|
-
log.debug({ channel: m.channel_id }, 'discord DM ignored (not supported yet)');
|
|
163
|
-
return;
|
|
164
|
-
}
|
|
165
|
-
const cachedScope = discordScopeKey(m.channel_id);
|
|
166
|
-
const cachedHasAnyAgent = !!(getAgentThread(cachedScope, 'codex') ?? getAgentThread(cachedScope, 'claude'));
|
|
167
|
-
// Existing scope (in-thread message): route to the requested or last-used agent.
|
|
168
|
-
if (cachedHasAnyAgent) {
|
|
169
|
-
const parsed = parseAgentSuffix(m.text);
|
|
170
|
-
const choice = pickAgent(cachedScope, parsed.kind);
|
|
171
|
-
if ('error' in choice) {
|
|
172
|
-
await postErrorMessage(m.channel_id, choice.error);
|
|
173
|
-
return;
|
|
174
|
-
}
|
|
175
|
-
setLastSeen(cachedScope, m.message_id);
|
|
176
|
-
let agentThreadId = getAgentThread(cachedScope, choice.kind);
|
|
177
|
-
if (!agentThreadId) {
|
|
178
|
-
// First time using this agent in this scope — fresh session, no prior
|
|
179
|
-
// history shared with the other agent.
|
|
180
|
-
agentThreadId = await available[choice.kind].createThread();
|
|
181
|
-
setAgentThread(cachedScope, choice.kind, agentThreadId);
|
|
182
|
-
log.info({ scope: cachedScope, agent: choice.kind, thread: agentThreadId }, 'allocated new agent session for existing scope');
|
|
183
|
-
}
|
|
184
|
-
else {
|
|
185
|
-
setLastAgent(cachedScope, choice.kind);
|
|
186
|
-
}
|
|
187
|
-
await handleTurn(parsed.cleanText, choice.kind, agentThreadId, discordAdapter(m.channel_id), discordScheduler);
|
|
188
|
-
return;
|
|
189
|
-
}
|
|
190
|
-
// No scope yet — only @-mentions bootstrap a new thread.
|
|
191
|
-
if (!m.mentions_bot) {
|
|
192
|
-
log.debug({ channel: m.channel_id }, 'discord guild msg dropped: no scope, no @-mention');
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
if (bootstrapped.has(m.message_id))
|
|
196
|
-
return;
|
|
197
|
-
bootstrapped.add(m.message_id);
|
|
198
|
-
const parsed = parseAgentSuffix(m.text);
|
|
199
|
-
const choice = pickAgent(null, parsed.kind);
|
|
200
|
-
if ('error' in choice) {
|
|
201
|
-
await postErrorMessage(m.channel_id, choice.error);
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
const agentForBootstrap = available[choice.kind];
|
|
205
|
-
const agentThreadId = await agentForBootstrap.createThread();
|
|
206
|
-
const threadName = makeThreadName(parsed.cleanText, agentThreadId);
|
|
207
|
-
log.info({ parent: m.channel_id, agent: choice.kind, thread: agentThreadId, threadName }, 'discord: bootstrapping new scope from @-mention');
|
|
208
|
-
const threadId = await discord.createThreadFromMessage(m.channel_id, m.message_id, threadName);
|
|
209
|
-
setAgentThread(discordScopeKey(threadId), choice.kind, agentThreadId);
|
|
210
|
-
log.info({ discord: threadId, agent: choice.kind, thread: agentThreadId }, 'scope created');
|
|
211
|
-
await handleTurn(parsed.cleanText, choice.kind, agentThreadId, discordAdapter(threadId), discordScheduler);
|
|
212
|
-
}
|
|
213
|
-
async function onTelegramInbound(m) {
|
|
214
|
-
// Allow DMs and any chat in a forum supergroup (custom topics + General).
|
|
215
|
-
// Plain (non-forum) groups are skipped — no thread boundary.
|
|
216
|
-
if (!m.is_private && !m.in_forum) {
|
|
217
|
-
log.debug({ chat: m.chat_id }, 'telegram: dropped — non-private, non-forum chat');
|
|
218
|
-
return;
|
|
219
|
-
}
|
|
220
|
-
// General topic of a forum is a *launcher*, never a session. Every
|
|
221
|
-
// @-mention spawns a fresh topic + scope. Any stale scope previously
|
|
222
|
-
// bound to General is ignored deliberately.
|
|
223
|
-
if (m.in_forum && !m.is_forum_topic) {
|
|
224
|
-
await bootstrapForumTopic(m);
|
|
225
|
-
return;
|
|
226
|
-
}
|
|
227
|
-
// DM or custom topic: route via the existing scope, allocating fresh
|
|
228
|
-
// agent sessions per-kind on first use.
|
|
229
|
-
const scopeKey = telegramScopeKey(m.chat_id, m.message_thread_id);
|
|
230
|
-
const cachedHasAnyAgent = !!(getAgentThread(scopeKey, 'codex') ?? getAgentThread(scopeKey, 'claude'));
|
|
231
|
-
if (!cachedHasAnyAgent && !m.is_private && !m.mentions_bot) {
|
|
232
|
-
log.debug({ chat: m.chat_id, topic: m.message_thread_id }, 'telegram: dropped — no scope, no @-mention');
|
|
233
|
-
return;
|
|
234
|
-
}
|
|
235
|
-
const parsed = parseAgentSuffix(m.text);
|
|
236
|
-
const choice = pickAgent(cachedHasAnyAgent ? scopeKey : null, parsed.kind);
|
|
237
|
-
if ('error' in choice) {
|
|
238
|
-
await postTelegramError(m.chat_id, m.message_thread_id, choice.error);
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
setLastSeen(scopeKey, String(m.message_id));
|
|
242
|
-
let agentThreadId = getAgentThread(scopeKey, choice.kind);
|
|
243
|
-
if (!agentThreadId) {
|
|
244
|
-
agentThreadId = await available[choice.kind].createThread();
|
|
245
|
-
setAgentThread(scopeKey, choice.kind, agentThreadId);
|
|
246
|
-
log.info({ scope: scopeKey, agent: choice.kind, thread: agentThreadId }, 'telegram: allocated agent session');
|
|
247
|
-
}
|
|
248
|
-
else {
|
|
249
|
-
setLastAgent(scopeKey, choice.kind);
|
|
250
|
-
}
|
|
251
|
-
await handleTurn(parsed.cleanText, choice.kind, agentThreadId, telegramAdapter(m.chat_id, m.message_thread_id), telegramScheduler);
|
|
252
|
-
}
|
|
253
|
-
async function bootstrapForumTopic(m) {
|
|
254
|
-
if (!m.mentions_bot) {
|
|
255
|
-
log.debug({ chat: m.chat_id }, 'telegram: General msg ignored — no @-mention');
|
|
256
|
-
return;
|
|
257
|
-
}
|
|
258
|
-
if (bootstrapped.has(String(m.message_id)))
|
|
259
|
-
return;
|
|
260
|
-
bootstrapped.add(String(m.message_id));
|
|
261
|
-
const parsed = parseAgentSuffix(m.text);
|
|
262
|
-
const choice = pickAgent(null, parsed.kind);
|
|
263
|
-
if ('error' in choice) {
|
|
264
|
-
await postTelegramError(m.chat_id, undefined, choice.error);
|
|
265
|
-
return;
|
|
266
|
-
}
|
|
267
|
-
const topicName = makeThreadName(parsed.cleanText, 'metro');
|
|
268
|
-
let newTopicId;
|
|
269
|
-
try {
|
|
270
|
-
newTopicId = await telegram.createForumTopic(m.chat_id, topicName);
|
|
271
|
-
log.info({ chat: m.chat_id, topic: newTopicId, name: topicName }, 'telegram: created topic from @-mention');
|
|
272
|
-
}
|
|
273
|
-
catch (err) {
|
|
274
|
-
await postTelegramError(m.chat_id, undefined, `couldn't create a new topic — make the bot a forum admin with "Manage Topics" permission. (${errMsg(err)})`);
|
|
275
|
-
return;
|
|
276
|
-
}
|
|
277
|
-
const agentThreadId = await available[choice.kind].createThread();
|
|
278
|
-
const newScopeKey = telegramScopeKey(m.chat_id, newTopicId);
|
|
279
|
-
setAgentThread(newScopeKey, choice.kind, agentThreadId);
|
|
280
|
-
setLastSeen(newScopeKey, String(m.message_id));
|
|
281
|
-
log.info({ scope: newScopeKey, agent: choice.kind, thread: agentThreadId }, 'telegram: scope created');
|
|
282
|
-
await handleTurn(parsed.cleanText, choice.kind, agentThreadId, telegramAdapter(m.chat_id, newTopicId), telegramScheduler);
|
|
283
|
-
}
|
|
284
|
-
async function postTelegramError(chatId, threadId, message) {
|
|
285
|
-
try {
|
|
286
|
-
await telegram.sendMessage(chatId, threadId, `⚠️ ${message}`);
|
|
287
|
-
}
|
|
288
|
-
catch (err) {
|
|
289
|
-
log.warn({ err: errMsg(err) }, 'failed to post telegram error');
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
async function postErrorMessage(channelId, message) {
|
|
293
|
-
try {
|
|
294
|
-
await discord.sendMessage(channelId, `⚠️ ${message}`);
|
|
295
|
-
}
|
|
296
|
-
catch (err) {
|
|
297
|
-
log.warn({ err: errMsg(err) }, 'failed to post error message');
|
|
298
|
-
}
|
|
299
|
-
}
|
|
300
|
-
// Thread/topic names. Strip platform mention syntax (discord <@id>, custom
|
|
301
|
-
// emoji; telegram @username), normalize whitespace, fall back to the given
|
|
302
|
-
// default if nothing usable is left. Telegram caps topic names at 128;
|
|
303
|
-
// Discord at 100 — use 100 so the same value works for both.
|
|
304
|
-
function makeThreadName(rawText, fallback) {
|
|
305
|
-
const cleaned = rawText
|
|
306
|
-
.replace(/<@!?\d+>/g, '')
|
|
307
|
-
.replace(/<@&\d+>/g, '')
|
|
308
|
-
.replace(/<#\d+>/g, '')
|
|
309
|
-
.replace(/<a?:[^:]+:\d+>/g, '')
|
|
310
|
-
.replace(/@\w+/g, '') // telegram @username
|
|
311
|
-
.replace(/\s+/g, ' ')
|
|
312
|
-
.trim();
|
|
313
|
-
if (!cleaned)
|
|
314
|
-
return fallback.slice(0, 100);
|
|
315
|
-
return cleaned.length <= 100 ? cleaned : cleaned.slice(0, 99) + '…';
|
|
316
|
-
}
|
|
317
|
-
/**
|
|
318
|
-
* Run one agent turn against a known agent thread, streaming the response
|
|
319
|
-
* through the provided platform adapter. If a turn is already in flight
|
|
320
|
-
* for this thread, append to the per-thread queue and let the current
|
|
321
|
-
* turn's drain pick it up.
|
|
322
|
-
*/
|
|
323
|
-
async function handleTurn(text, kind, agentThreadId, adapter, scheduler) {
|
|
324
|
-
const agent = available[kind];
|
|
325
|
-
if (!agent) {
|
|
326
|
-
// Shouldn't happen — the caller's pickAgent() already filters this —
|
|
327
|
-
// but log defensively so it's not silent.
|
|
328
|
-
log.warn({ kind, agent: agentThreadId }, 'handleTurn called for unavailable agent');
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
331
|
-
const dispatch = (t) => handleTurn(t, kind, agentThreadId, adapter, scheduler);
|
|
332
|
-
if (inFlight.has(agentThreadId)) {
|
|
333
|
-
const q = queued.get(agentThreadId);
|
|
334
|
-
if (q)
|
|
335
|
-
q.texts.push(text);
|
|
336
|
-
else
|
|
337
|
-
queued.set(agentThreadId, { texts: [text], dispatch });
|
|
338
|
-
log.debug({ agent: agentThreadId, queueDepth: queued.get(agentThreadId).texts.length }, 'queued follow-up turn');
|
|
339
|
-
return;
|
|
340
|
-
}
|
|
341
|
-
inFlight.add(agentThreadId);
|
|
342
|
-
const stream = new StreamingMessage(adapter, scheduler);
|
|
343
|
-
const finishAndDrain = async () => {
|
|
344
|
-
await stream.finalize();
|
|
345
|
-
inFlight.delete(agentThreadId);
|
|
346
|
-
const q = queued.get(agentThreadId);
|
|
347
|
-
if (!q || q.texts.length === 0)
|
|
348
|
-
return;
|
|
349
|
-
queued.delete(agentThreadId);
|
|
350
|
-
const combined = q.texts.join('\n\n');
|
|
351
|
-
log.debug({ agent: agentThreadId, batched: q.texts.length }, 'draining queued follow-ups');
|
|
352
|
-
await q.dispatch(combined).catch(err => log.warn({ err: errMsg(err) }, 'queued turn failed'));
|
|
353
|
-
};
|
|
354
|
-
const callbacks = {
|
|
355
|
-
onDelta: d => stream.appendDelta(d),
|
|
356
|
-
onToolStart: (_kind, summary) => stream.setStatus(summary),
|
|
357
|
-
onToolEnd: () => stream.setStatus(null),
|
|
358
|
-
onComplete: () => { void finishAndDrain(); },
|
|
359
|
-
onError: err => {
|
|
360
|
-
log.warn({ err: errMsg(err) }, 'agent turn failed');
|
|
361
|
-
stream.appendError(errMsg(err) || 'agent turn failed');
|
|
362
|
-
void finishAndDrain();
|
|
363
|
-
},
|
|
364
|
-
};
|
|
365
|
-
await agent.sendTurn(agentThreadId, text, callbacks);
|
|
366
|
-
}
|
|
367
|
-
function discordAdapter(channelId) {
|
|
368
|
-
return {
|
|
369
|
-
send: t => discord.sendMessage(channelId, t),
|
|
370
|
-
edit: async (id, t) => { await discord.editMessage(channelId, id, t); },
|
|
371
|
-
};
|
|
372
|
-
}
|
|
373
|
-
function telegramAdapter(chatId, topicId) {
|
|
374
|
-
return {
|
|
375
|
-
send: async (t) => String(await telegram.sendMessage(chatId, topicId, t)),
|
|
376
|
-
edit: async (id, t) => { await telegram.editMessageText(chatId, Number(id), t); },
|
|
377
|
-
};
|
|
378
|
-
}
|
|
379
|
-
let shuttingDown = false;
|
|
380
|
-
async function shutdown() {
|
|
381
|
-
if (shuttingDown)
|
|
382
|
-
return;
|
|
383
|
-
shuttingDown = true;
|
|
384
|
-
log.info('orchestrator shutting down');
|
|
385
|
-
await Promise.allSettled([
|
|
386
|
-
available.codex?.stop().catch(err => log.warn({ err: errMsg(err) }, 'codex shutdown failed')),
|
|
387
|
-
available.claude?.stop().catch(err => log.warn({ err: errMsg(err) }, 'claude shutdown failed')),
|
|
388
|
-
]);
|
|
389
|
-
if (platforms.discord)
|
|
390
|
-
await discord.shutdownGateway().catch(err => log.warn({ err: errMsg(err) }, 'discord shutdown failed'));
|
|
391
|
-
if (platforms.telegram)
|
|
392
|
-
await telegram.shutdownPolling().catch(err => log.warn({ err: errMsg(err) }, 'telegram shutdown failed'));
|
|
393
|
-
process.exit(0);
|
|
394
|
-
}
|
|
395
|
-
process.stdin.on('end', shutdown);
|
|
396
|
-
process.stdin.on('close', shutdown);
|
|
397
|
-
process.on('SIGINT', shutdown);
|
|
398
|
-
process.on('SIGTERM', shutdown);
|
|
399
|
-
await main();
|
package/dist/paths.js
DELETED
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
import { mkdirSync } from 'node:fs';
|
|
2
|
-
import { homedir } from 'node:os';
|
|
3
|
-
import { join } from 'node:path';
|
|
4
|
-
import { loadDotenvIntoProcess } from './lib/dotenv.js';
|
|
5
|
-
import { log } from './log.js';
|
|
6
|
-
// Lockfile, scope cache, codex app-server socket, telegram poll offset,
|
|
7
|
-
// claude session set. Override with METRO_STATE_DIR.
|
|
8
|
-
export const STATE_DIR = process.env.METRO_STATE_DIR ?? join(homedir(), '.cache', 'metro');
|
|
9
|
-
mkdirSync(STATE_DIR, { recursive: true });
|
|
10
|
-
// Where `metro setup` writes the global .env. Override with METRO_CONFIG_DIR
|
|
11
|
-
// or the standard $XDG_CONFIG_HOME.
|
|
12
|
-
const CONFIG_DIR = process.env.METRO_CONFIG_DIR ??
|
|
13
|
-
join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'metro');
|
|
14
|
-
export const CONFIG_ENV_FILE = join(CONFIG_DIR, '.env');
|
|
15
|
-
// Precedence: process.env (already set) > cwd/.env > <CONFIG_DIR>/.env.
|
|
16
|
-
// loadDotenvIntoProcess only fills vars that aren't already populated, so the
|
|
17
|
-
// first call that defines a key wins.
|
|
18
|
-
export function loadMetroEnv() {
|
|
19
|
-
loadDotenvIntoProcess(join(process.cwd(), '.env'));
|
|
20
|
-
loadDotenvIntoProcess(CONFIG_ENV_FILE);
|
|
21
|
-
}
|
|
22
|
-
export function configuredPlatforms() {
|
|
23
|
-
return {
|
|
24
|
-
telegram: !!process.env.TELEGRAM_BOT_TOKEN,
|
|
25
|
-
discord: !!process.env.DISCORD_BOT_TOKEN,
|
|
26
|
-
};
|
|
27
|
-
}
|
|
28
|
-
export function requireConfiguredPlatform(p) {
|
|
29
|
-
if (p.telegram || p.discord)
|
|
30
|
-
return;
|
|
31
|
-
log.fatal('no platforms configured — run `metro setup telegram <token>` or `metro setup discord <token>`');
|
|
32
|
-
process.exit(2);
|
|
33
|
-
}
|