@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/claude.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
const CHANNEL_FLAGS = ['--dangerously-load-development-channels', 'server:metro'];
|
|
3
|
+
export const claudeArgs = (extra) => [
|
|
4
|
+
...CHANNEL_FLAGS,
|
|
5
|
+
...extra,
|
|
6
|
+
];
|
|
7
|
+
export function launchClaude(extra) {
|
|
8
|
+
const leaveToChild = () => undefined;
|
|
9
|
+
process.on('SIGINT', leaveToChild);
|
|
10
|
+
process.on('SIGTERM', leaveToChild);
|
|
11
|
+
const res = spawnSync('claude', claudeArgs(extra), { stdio: 'inherit' });
|
|
12
|
+
if (res.error !== undefined)
|
|
13
|
+
throw new Error('the `claude` command is not on PATH — install Claude Code first');
|
|
14
|
+
return Promise.resolve(res.status ?? 1);
|
|
15
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -1,266 +1,170 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import {
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
|
|
2
|
+
import { hostname } from 'node:os';
|
|
3
|
+
import { claimCode, claimRuntime, mcpServers, NotSignedIn, whoisAuthorized, } from './api.js';
|
|
4
|
+
import { askSecret } from './prompt.js';
|
|
5
|
+
import { clearToken, credentialsPath, metroUrl, metroWebUrl, writeToken, } from './store.js';
|
|
6
|
+
import { detach, lockedBy, probe, runningPid, stopAll, tail } from './control.js';
|
|
7
|
+
import { tailEvents } from './tail.js';
|
|
8
|
+
import { assertAgentId, daemonPlan, localUrl, readRunToken, runDaemon, runtimeDir, writeRunToken, } from './runtime.js';
|
|
9
|
+
import { launchClaude } from './claude.js';
|
|
10
|
+
import { bedrock } from './bedrock.js';
|
|
11
|
+
import { installPlugin, syncPluginServers } from './plugin.js';
|
|
12
|
+
import { update } from './update.js';
|
|
13
|
+
import { currentVersion } from './version.js';
|
|
14
|
+
const USAGE = `metro — the command line for your MCP connectors
|
|
12
15
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
metro
|
|
18
|
-
|
|
19
|
-
metro
|
|
20
|
-
|
|
21
|
-
metro
|
|
22
|
-
|
|
23
|
-
metro
|
|
24
|
-
metro
|
|
16
|
+
metro start <agent-id> [--detach]
|
|
17
|
+
run that agent's stations on this machine
|
|
18
|
+
metro stop [agent-id]
|
|
19
|
+
stop every metro daemon on this machine, however it was started
|
|
20
|
+
metro status <agent-id>
|
|
21
|
+
is it running, and is it healthy
|
|
22
|
+
metro logs <agent-id> [-f]
|
|
23
|
+
show the detached daemon's log
|
|
24
|
+
metro tail <agent-id>
|
|
25
|
+
follow this machine's inbound events, one JSON line each
|
|
26
|
+
metro login authorize a connector collection with a code from the web UI
|
|
27
|
+
metro logout forget this machine's sign-in
|
|
28
|
+
metro whoami print the account and collection this machine may read
|
|
29
|
+
metro mcp print the mcpServers block for the authorized collection
|
|
30
|
+
metro plugin set up the Claude Code plugin (connector servers + /metro:login)
|
|
31
|
+
metro claude [args...]
|
|
32
|
+
open Claude Code with the metro channel; every argument is passed through
|
|
33
|
+
metro bedrock [args...]
|
|
34
|
+
the same, with inference on Amazon Bedrock through a local proxy so the
|
|
35
|
+
channel still works (needs AWS_BEARER_TOKEN_BEDROCK and AWS_REGION)
|
|
36
|
+
metro update update to the newest published version
|
|
37
|
+
metro version print this CLI's version
|
|
25
38
|
|
|
26
|
-
|
|
27
|
-
|
|
39
|
+
Start Claude Code with all of them, without writing them to disk:
|
|
40
|
+
|
|
41
|
+
claude --mcp-config <(metro mcp)
|
|
42
|
+
|
|
43
|
+
METRO_URL the metro to talk to (default https://mcp.metro.box)
|
|
44
|
+
METRO_UI_URL where the web UI lives (default https://metro.box)
|
|
45
|
+
METRO_TOKEN use this connector sign-in instead of the stored one
|
|
46
|
+
METRO_RUN_TOKEN use this runtime authorization instead of the stored one
|
|
47
|
+
METRO_BEDROCK_MODEL
|
|
48
|
+
send every request to this Bedrock model id (default: derive from the
|
|
49
|
+
model Claude Code asks for, e.g. eu.anthropic.claude-sonnet-4-6)
|
|
50
|
+
METRO_RUNTIME_DIR run the daemon from this directory instead of the bundled one
|
|
28
51
|
`;
|
|
29
|
-
function
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const flags = {};
|
|
33
|
-
for (let i = 0; i < argv.length; i++) {
|
|
34
|
-
const a = argv[i];
|
|
35
|
-
if (!a.startsWith('--')) {
|
|
36
|
-
positional.push(a);
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
const eq = a.indexOf('=');
|
|
40
|
-
if (eq !== -1) {
|
|
41
|
-
flags[a.slice(2, eq)] = a.slice(eq + 1);
|
|
42
|
-
continue;
|
|
43
|
-
}
|
|
44
|
-
const key = a.slice(2);
|
|
45
|
-
const next = argv[i + 1];
|
|
46
|
-
if (next !== undefined && !next.startsWith('--')) {
|
|
47
|
-
flags[key] = next;
|
|
48
|
-
i++;
|
|
49
|
-
}
|
|
50
|
-
else
|
|
51
|
-
flags[key] = true;
|
|
52
|
-
}
|
|
53
|
-
return { positional, flags };
|
|
52
|
+
function hostLabel() {
|
|
53
|
+
const name = hostname().trim();
|
|
54
|
+
return name === '' ? 'unnamed machine' : name;
|
|
54
55
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
return `${t.slice(0, 6)}…${t.slice(-2)}`;
|
|
56
|
+
async function authorizeRuntime(agentId) {
|
|
57
|
+
process.stderr.write(`Authorize this machine at ${metroWebUrl()}/#/authorize/${agentId}\n`);
|
|
58
|
+
const code = (await askSecret('Paste the code (input is hidden): ')).trim();
|
|
59
|
+
if (code === '')
|
|
60
|
+
throw new Error('no code given');
|
|
61
|
+
const claimed = await claimRuntime(code, hostLabel());
|
|
62
|
+
writeRunToken(claimed.agent, claimed.token);
|
|
63
|
+
process.stderr.write(`Authorized as '${claimed.label}'.\n`);
|
|
64
|
+
return claimed.token;
|
|
65
65
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
delete process.env[TOKEN_KEYS[sub]];
|
|
83
|
-
throw exitErr(`token rejected by ${sub}: ${errMsg(err)} (use --no-validate to save anyway)`, 3);
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
const env = readDotenv(CONFIG_ENV_FILE);
|
|
87
|
-
env[TOKEN_KEYS[sub]] = trimmed;
|
|
88
|
-
writeDotenv(CONFIG_ENV_FILE, env);
|
|
89
|
-
const human = identity
|
|
90
|
-
? `saved ${TOKEN_KEYS[sub]} (verified as ${identity}) to ${CONFIG_ENV_FILE} (chmod 0600)`
|
|
91
|
-
: `saved ${TOKEN_KEYS[sub]} to ${CONFIG_ENV_FILE} (chmod 0600)`;
|
|
92
|
-
emitResult(flags, human, { ok: true, saved: TOKEN_KEYS[sub], verified_as: identity ?? null });
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
if (sub === 'clear') {
|
|
96
|
-
const target = value ?? 'all';
|
|
97
|
-
const env = readDotenv(CONFIG_ENV_FILE);
|
|
98
|
-
if (target === 'all') {
|
|
99
|
-
delete env.TELEGRAM_BOT_TOKEN;
|
|
100
|
-
delete env.DISCORD_BOT_TOKEN;
|
|
101
|
-
}
|
|
102
|
-
else if (target === 'telegram' || target === 'discord')
|
|
103
|
-
delete env[TOKEN_KEYS[target]];
|
|
104
|
-
else
|
|
105
|
-
throw new Error(`metro setup clear <telegram|discord|all> — got '${target}'`);
|
|
106
|
-
writeDotenv(CONFIG_ENV_FILE, env);
|
|
107
|
-
emitResult(flags, `cleared ${target === 'all' ? 'all metro tokens' : TOKEN_KEYS[target]}`, { ok: true, cleared: target });
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
throw new Error(`unknown setup subcommand '${sub}' (try: telegram, discord, clear)`);
|
|
66
|
+
async function start(argv) {
|
|
67
|
+
const agentId = assertAgentId(argv[0]);
|
|
68
|
+
if (runningPid(agentId) !== null || lockedBy() !== null)
|
|
69
|
+
throw new Error('a metro daemon is already running on this machine. ' +
|
|
70
|
+
'Stop it first: metro stop');
|
|
71
|
+
const dir = runtimeDir();
|
|
72
|
+
const token = readRunToken(agentId) ?? (await authorizeRuntime(agentId));
|
|
73
|
+
const detached = argv.includes('--detach');
|
|
74
|
+
const plan = daemonPlan({ agentId, token, dir });
|
|
75
|
+
if (!detached)
|
|
76
|
+
return runDaemon(agentId, plan);
|
|
77
|
+
const pid = detach({ agentId, ...plan });
|
|
78
|
+
process.stderr.write(`metro is running for ${agentId} (pid ${String(pid)})\n` +
|
|
79
|
+
` logs: metro logs ${agentId} -f\n` +
|
|
80
|
+
` stop: metro stop ${agentId}\n`);
|
|
81
|
+
return 0;
|
|
111
82
|
}
|
|
112
|
-
async function
|
|
113
|
-
|
|
114
|
-
const
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
discord: { set: !!dc, masked: maskToken(dc) },
|
|
123
|
-
},
|
|
124
|
-
}) + '\n');
|
|
125
|
-
return;
|
|
126
|
-
}
|
|
127
|
-
process.stdout.write(`metro ${pkg.version}\n\n` +
|
|
128
|
-
`config: ${CONFIG_ENV_FILE}${existsSync(CONFIG_ENV_FILE) ? '' : ' (not yet written)'}\n\n` +
|
|
129
|
-
` TELEGRAM_BOT_TOKEN ${tg ? `set (${maskToken(tg)})` : 'not set'}\n` +
|
|
130
|
-
` DISCORD_BOT_TOKEN ${dc ? `set (${maskToken(dc)})` : 'not set'}\n\n`);
|
|
131
|
-
if (!tg && !dc) {
|
|
132
|
-
process.stdout.write('Get started:\n' +
|
|
133
|
-
' 1. metro setup telegram <token> # https://t.me/BotFather\n' +
|
|
134
|
-
' metro setup discord <token> # https://discord.com/developers/applications\n' +
|
|
135
|
-
' 2. metro doctor # verify\n' +
|
|
136
|
-
' 3. metro # run the orchestrator\n');
|
|
137
|
-
}
|
|
138
|
-
else {
|
|
139
|
-
process.stdout.write('Run `metro` to start the orchestrator, or `metro doctor` to verify.\n');
|
|
140
|
-
}
|
|
83
|
+
async function stopDaemon(argv) {
|
|
84
|
+
const agentId = argv[0] === undefined ? undefined : assertAgentId(argv[0]);
|
|
85
|
+
const stopped = await stopAll(agentId);
|
|
86
|
+
if (stopped.length === 0) {
|
|
87
|
+
process.stderr.write('no metro daemon is running on this machine\n');
|
|
88
|
+
return 1;
|
|
89
|
+
}
|
|
90
|
+
for (const d of stopped)
|
|
91
|
+
process.stderr.write(`Stopped metro (pid ${String(d.pid)}, via ${d.via})\n`);
|
|
92
|
+
return 0;
|
|
141
93
|
}
|
|
142
|
-
async function
|
|
143
|
-
|
|
144
|
-
const
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
checks.push({ name: platform, ok: null, detail: 'not configured' });
|
|
156
|
-
continue;
|
|
157
|
-
}
|
|
158
|
-
try {
|
|
159
|
-
const me = await getMe();
|
|
160
|
-
checks.push({ name: platform, ok: true, detail: `getMe → ${platform === 'telegram' ? '@' : ''}${me.username}` });
|
|
161
|
-
}
|
|
162
|
-
catch (err) {
|
|
163
|
-
checks.push({ name: platform, ok: false, detail: errMsg(err) });
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
const lockFile = join(STATE_DIR, '.tail-lock');
|
|
167
|
-
if (!existsSync(lockFile)) {
|
|
168
|
-
checks.push({ name: 'orchestrator', ok: null, detail: 'not running' });
|
|
169
|
-
}
|
|
170
|
-
else {
|
|
171
|
-
try {
|
|
172
|
-
const pid = Number(readFileSync(lockFile, 'utf8').trim());
|
|
173
|
-
if (!Number.isInteger(pid) || pid <= 0)
|
|
174
|
-
throw new Error('invalid pid');
|
|
175
|
-
process.kill(pid, 0);
|
|
176
|
-
checks.push({ name: 'orchestrator', ok: true, detail: `running (pid ${pid})` });
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
checks.push({ name: 'orchestrator', ok: null, detail: 'stale lockfile (will auto-reclaim on next start)' });
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
if (isJson(flags)) {
|
|
183
|
-
process.stdout.write(JSON.stringify({ checks }) + '\n');
|
|
184
|
-
}
|
|
185
|
-
else {
|
|
186
|
-
process.stdout.write('metro doctor\n\n');
|
|
187
|
-
for (const c of checks) {
|
|
188
|
-
const icon = c.ok === true ? '✓' : c.ok === false ? '✗' : '–';
|
|
189
|
-
process.stdout.write(` ${icon} ${c.name.padEnd(15)} ${c.detail}\n`);
|
|
190
|
-
}
|
|
191
|
-
process.stdout.write('\n');
|
|
192
|
-
}
|
|
193
|
-
if (checks.some(c => c.ok === false))
|
|
194
|
-
throw exitErr('one or more checks failed', 3);
|
|
94
|
+
async function status(argv) {
|
|
95
|
+
const agentId = assertAgentId(argv[0]);
|
|
96
|
+
const pid = runningPid(agentId);
|
|
97
|
+
if (pid === null) {
|
|
98
|
+
process.stdout.write(`${agentId}: not running\n`);
|
|
99
|
+
return 1;
|
|
100
|
+
}
|
|
101
|
+
const health = await probe(localUrl());
|
|
102
|
+
process.stdout.write(`${agentId}: running (pid ${String(pid)}) · ` +
|
|
103
|
+
(health === null
|
|
104
|
+
? `not answering on ${localUrl()}\n`
|
|
105
|
+
: `healthy, up ${String(health.uptime)}s\n`));
|
|
106
|
+
return health === null ? 1 : 0;
|
|
195
107
|
}
|
|
196
|
-
|
|
197
|
-
const
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
const
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
if (
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
|
|
212
|
-
: argv1.includes('/pnpm/') || argv1.includes('\\pnpm\\') ? ['pnpm', 'add', '-g', spec]
|
|
213
|
-
: ['npm', 'install', '-g', spec];
|
|
214
|
-
if (isJson(flags)) {
|
|
215
|
-
process.stdout.write(JSON.stringify({ ok: true, current: pkg.version, latest, command: argv.join(' ') }) + '\n');
|
|
216
|
-
}
|
|
217
|
-
else {
|
|
218
|
-
process.stdout.write(`metro ${pkg.version} → ${latest}\n$ ${argv.join(' ')}\n`);
|
|
219
|
-
}
|
|
220
|
-
await new Promise((resolve, reject) => {
|
|
221
|
-
const child = spawn(argv[0], argv.slice(1), { stdio: isJson(flags) ? 'ignore' : 'inherit' });
|
|
222
|
-
child.on('exit', code => (code === 0 ? resolve() : reject(new Error(`${argv[0]} exited ${code}`))));
|
|
223
|
-
child.on('error', reject);
|
|
224
|
-
});
|
|
108
|
+
function logs(argv) {
|
|
109
|
+
const agentId = assertAgentId(argv[0]);
|
|
110
|
+
return tail(agentId, argv.includes('-f') || argv.includes('--follow'));
|
|
111
|
+
}
|
|
112
|
+
async function login() {
|
|
113
|
+
process.stderr.write(`Choose a connector collection at ${metroWebUrl()}/#/authorize\n`);
|
|
114
|
+
const { token, email, collection } = await claimCode(await askSecret('Paste the code here (input is hidden): '));
|
|
115
|
+
writeToken(token);
|
|
116
|
+
process.stderr.write(`Authorized '${collection}' for ${email}. Stored in ${credentialsPath()}\n`);
|
|
117
|
+
if (syncPluginServers())
|
|
118
|
+
process.stderr.write('Claude Code plugin refreshed — new sessions have the connectors; ' +
|
|
119
|
+
'run /reload-plugins in any session already open.\n');
|
|
120
|
+
}
|
|
121
|
+
async function whoami() {
|
|
122
|
+
const { email, collection } = await whoisAuthorized();
|
|
123
|
+
process.stdout.write(`${email} · collection '${collection}' on ${metroUrl()}\n`);
|
|
225
124
|
}
|
|
125
|
+
const HELP = new Set([undefined, 'help', '--help', '-h']);
|
|
226
126
|
const COMMANDS = {
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
127
|
+
login: async () => {
|
|
128
|
+
await login();
|
|
129
|
+
return 0;
|
|
130
|
+
},
|
|
131
|
+
logout: async () => {
|
|
132
|
+
clearToken();
|
|
133
|
+
process.stderr.write('Signed out.\n');
|
|
134
|
+
return Promise.resolve(0);
|
|
135
|
+
},
|
|
136
|
+
whoami: async () => {
|
|
137
|
+
await whoami();
|
|
138
|
+
return 0;
|
|
139
|
+
},
|
|
140
|
+
start: () => start(process.argv.slice(3)),
|
|
141
|
+
stop: () => stopDaemon(process.argv.slice(3)),
|
|
142
|
+
status: () => status(process.argv.slice(3)),
|
|
143
|
+
logs: () => logs(process.argv.slice(3)),
|
|
144
|
+
tail: () => tailEvents(process.argv.slice(3)),
|
|
145
|
+
mcp: async () => {
|
|
146
|
+
process.stdout.write(`${await mcpServers()}\n`);
|
|
147
|
+
return 0;
|
|
148
|
+
},
|
|
149
|
+
plugin: installPlugin,
|
|
150
|
+
claude: () => launchClaude(process.argv.slice(3)),
|
|
151
|
+
bedrock: () => bedrock(process.argv.slice(3)),
|
|
152
|
+
update,
|
|
153
|
+
version: async () => {
|
|
154
|
+
process.stdout.write(`${currentVersion()}\n`);
|
|
155
|
+
return Promise.resolve(0);
|
|
156
|
+
},
|
|
230
157
|
};
|
|
231
|
-
async function
|
|
232
|
-
const
|
|
233
|
-
if (
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
if (cmd === '--help' || cmd === '-h') {
|
|
238
|
-
process.stdout.write(USAGE);
|
|
239
|
-
return;
|
|
240
|
-
}
|
|
241
|
-
// Bare `metro` runs the orchestrator (the daemon's the default).
|
|
242
|
-
if (!cmd) {
|
|
243
|
-
await import('./orchestrator.js');
|
|
244
|
-
return;
|
|
245
|
-
}
|
|
246
|
-
const handler = COMMANDS[cmd];
|
|
247
|
-
if (!handler) {
|
|
248
|
-
process.stderr.write(`unknown command '${cmd}'\n\n${USAGE}`);
|
|
249
|
-
process.exit(1);
|
|
250
|
-
}
|
|
251
|
-
const { positional, flags } = parseArgs(process.argv.slice(3));
|
|
252
|
-
try {
|
|
253
|
-
await handler(positional, flags);
|
|
254
|
-
}
|
|
255
|
-
catch (err) {
|
|
256
|
-
const code = err.code;
|
|
257
|
-
if (isJson(flags)) {
|
|
258
|
-
process.stdout.write(JSON.stringify({ ok: false, error: errMsg(err), code: code ?? 1 }) + '\n');
|
|
259
|
-
}
|
|
260
|
-
else {
|
|
261
|
-
process.stderr.write(`error: ${errMsg(err)}\n`);
|
|
262
|
-
}
|
|
263
|
-
process.exit(typeof code === 'number' ? code : 1);
|
|
264
|
-
}
|
|
158
|
+
async function run(command) {
|
|
159
|
+
const handler = command === undefined ? undefined : COMMANDS[command];
|
|
160
|
+
if (handler !== undefined)
|
|
161
|
+
return handler();
|
|
162
|
+
process.stderr.write(USAGE);
|
|
163
|
+
return Promise.resolve(HELP.has(command) ? 0 : 1);
|
|
265
164
|
}
|
|
266
|
-
await
|
|
165
|
+
const code = await run(process.argv[2]).catch((err) => {
|
|
166
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
167
|
+
process.stderr.write(`metro: ${message}\n`);
|
|
168
|
+
return err instanceof NotSignedIn ? 2 : 1;
|
|
169
|
+
});
|
|
170
|
+
process.exit(code);
|
package/dist/control.js
ADDED
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import { execFileSync, spawn } from 'node:child_process';
|
|
2
|
+
import { existsSync, mkdirSync, openSync, readdirSync, readFileSync, rmSync, writeFileSync, } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
const STATE = () => join(homedir(), '.metro');
|
|
6
|
+
const pidPath = (agentId) => join(STATE(), `run-${agentId}.pid`);
|
|
7
|
+
export const logPath = (agentId) => join(STATE(), 'logs', `${agentId}.log`);
|
|
8
|
+
function ensureStateDirs() {
|
|
9
|
+
mkdirSync(STATE(), { recursive: true, mode: 0o700 });
|
|
10
|
+
mkdirSync(join(STATE(), 'logs'), { recursive: true, mode: 0o700 });
|
|
11
|
+
}
|
|
12
|
+
function readPid(agentId) {
|
|
13
|
+
try {
|
|
14
|
+
const pid = Number(readFileSync(pidPath(agentId), 'utf8').trim());
|
|
15
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
function isAlive(pid) {
|
|
22
|
+
try {
|
|
23
|
+
process.kill(pid, 0);
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function runningPid(agentId) {
|
|
31
|
+
const pid = readPid(agentId);
|
|
32
|
+
if (pid === null)
|
|
33
|
+
return null;
|
|
34
|
+
if (isAlive(pid))
|
|
35
|
+
return pid;
|
|
36
|
+
rmSync(pidPath(agentId), { force: true });
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
function writePid(agentId, pid) {
|
|
40
|
+
writeFileSync(pidPath(agentId), String(pid));
|
|
41
|
+
}
|
|
42
|
+
function clearPid(agentId) {
|
|
43
|
+
rmSync(pidPath(agentId), { force: true });
|
|
44
|
+
}
|
|
45
|
+
export function detach(opts) {
|
|
46
|
+
ensureStateDirs();
|
|
47
|
+
const out = openSync(logPath(opts.agentId), 'a');
|
|
48
|
+
const child = spawn(opts.command, opts.args, {
|
|
49
|
+
cwd: opts.cwd,
|
|
50
|
+
env: opts.env,
|
|
51
|
+
detached: true,
|
|
52
|
+
stdio: ['ignore', out, out],
|
|
53
|
+
});
|
|
54
|
+
child.unref();
|
|
55
|
+
const pid = child.pid ?? 0;
|
|
56
|
+
if (pid === 0)
|
|
57
|
+
throw new Error('could not start the metro daemon');
|
|
58
|
+
writePid(opts.agentId, pid);
|
|
59
|
+
return pid;
|
|
60
|
+
}
|
|
61
|
+
async function stopPid(pid, waitMs) {
|
|
62
|
+
process.kill(pid, 'SIGTERM');
|
|
63
|
+
const until = Date.now() + waitMs;
|
|
64
|
+
while (Date.now() < until) {
|
|
65
|
+
if (!isAlive(pid))
|
|
66
|
+
return;
|
|
67
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
68
|
+
}
|
|
69
|
+
process.kill(pid, 'SIGKILL');
|
|
70
|
+
}
|
|
71
|
+
const lockPath = () => process.env.METRO_STATE_DIR?.trim()
|
|
72
|
+
? join(process.env.METRO_STATE_DIR.trim(), '.tail-lock')
|
|
73
|
+
: join(homedir(), '.cache', 'metro', '.tail-lock');
|
|
74
|
+
function commandOf(pid) {
|
|
75
|
+
try {
|
|
76
|
+
return execFileSync('ps', ['-o', 'command=', '-p', String(pid)], {
|
|
77
|
+
encoding: 'utf8',
|
|
78
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return '';
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
const looksLikeMetro = (command) => command.includes('server.ts') || command.includes('metro');
|
|
86
|
+
export function lockedBy() {
|
|
87
|
+
let pid;
|
|
88
|
+
try {
|
|
89
|
+
pid = Number(readFileSync(lockPath(), 'utf8').trim());
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return null;
|
|
93
|
+
}
|
|
94
|
+
if (!Number.isInteger(pid) || pid <= 0 || !isAlive(pid))
|
|
95
|
+
return null;
|
|
96
|
+
return looksLikeMetro(commandOf(pid)) ? pid : null;
|
|
97
|
+
}
|
|
98
|
+
function trackedAgents(agentId) {
|
|
99
|
+
const tracked = new Set();
|
|
100
|
+
if (agentId !== undefined)
|
|
101
|
+
tracked.add(agentId);
|
|
102
|
+
try {
|
|
103
|
+
for (const name of readdirSync(STATE())) {
|
|
104
|
+
const found = /^run-(.+)\.pid$/.exec(name);
|
|
105
|
+
if (found?.[1] !== undefined)
|
|
106
|
+
tracked.add(found[1]);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
return tracked;
|
|
111
|
+
}
|
|
112
|
+
return tracked;
|
|
113
|
+
}
|
|
114
|
+
export async function stopAll(agentId, waitMs = 10_000) {
|
|
115
|
+
const stopped = [];
|
|
116
|
+
const seen = new Set();
|
|
117
|
+
for (const id of trackedAgents(agentId)) {
|
|
118
|
+
const pid = runningPid(id);
|
|
119
|
+
if (pid === null || seen.has(pid))
|
|
120
|
+
continue;
|
|
121
|
+
seen.add(pid);
|
|
122
|
+
await stopPid(pid, waitMs);
|
|
123
|
+
clearPid(id);
|
|
124
|
+
stopped.push({ pid, via: id });
|
|
125
|
+
}
|
|
126
|
+
const holder = lockedBy();
|
|
127
|
+
if (holder !== null && !seen.has(holder)) {
|
|
128
|
+
await stopPid(holder, waitMs);
|
|
129
|
+
rmSync(lockPath(), { force: true });
|
|
130
|
+
stopped.push({ pid: holder, via: 'the machine lock' });
|
|
131
|
+
}
|
|
132
|
+
return stopped;
|
|
133
|
+
}
|
|
134
|
+
export function tail(agentId, follow) {
|
|
135
|
+
const path = logPath(agentId);
|
|
136
|
+
if (!existsSync(path))
|
|
137
|
+
throw new Error(`no log for ${agentId} yet — it is only written when started with --detach`);
|
|
138
|
+
const args = follow ? ['-n', '200', '-f', path] : ['-n', '200', path];
|
|
139
|
+
const child = spawn('tail', args, { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
140
|
+
return new Promise((resolve) => {
|
|
141
|
+
child.on('exit', (code) => {
|
|
142
|
+
resolve(code ?? 0);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
export async function probe(url) {
|
|
147
|
+
try {
|
|
148
|
+
const res = await fetch(`${url}/health`, {
|
|
149
|
+
signal: AbortSignal.timeout(3_000),
|
|
150
|
+
});
|
|
151
|
+
if (!res.ok)
|
|
152
|
+
return null;
|
|
153
|
+
return (await res.json());
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
}
|