@stage-labs/metro 0.1.0-beta.5 → 0.1.0-beta.50
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 +148 -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 +106 -0
- package/dist/claude.js +15 -0
- package/dist/cli.js +198 -0
- package/dist/control.js +173 -0
- package/dist/local.js +67 -0
- package/dist/plugin.js +82 -0
- package/dist/prompt.js +61 -0
- package/dist/runtime.js +117 -0
- package/dist/serve.js +109 -0
- package/dist/store.js +93 -0
- package/dist/tail.js +120 -0
- package/dist/update.js +33 -0
- package/dist/version.js +97 -0
- package/package.json +34 -23
- package/runtime/node_modules/@metro-labs/discord-bot/package.json +28 -0
- package/runtime/node_modules/@metro-labs/discord-bot/src/accounts.ts +118 -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 +63 -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 +334 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-connector-api.ts +150 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-import.ts +76 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-pair.ts +20 -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 +152 -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 +130 -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 +374 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/bundle-api.ts +112 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-api.ts +101 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/claude-files.ts +329 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +141 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connect-hint.ts +39 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +315 -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 +98 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-url.ts +70 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-verify.ts +330 -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/http.ts +368 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +7 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/import-api.ts +68 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/is-record.ts +2 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-cli-api.ts +70 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-mode.ts +232 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-owner.ts +14 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-secret.ts +21 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/log.ts +42 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/mode-api.ts +32 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-client.ts +159 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-discovery.ts +117 -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 +112 -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 +304 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +139 -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 +66 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-config.ts +22 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +177 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-auth.ts +73 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-nonces.ts +43 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-routes.ts +89 -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 +320 -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 +175 -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/daemon/vault-api.ts +78 -0
- package/runtime/node_modules/@metro-labs/mcp/src/daemon/version.ts +2 -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 +214 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-connectors.ts +143 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/agent-map.ts +84 -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-config.ts +144 -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 +336 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/file-admin.ts +375 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/file-source.ts +161 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +21 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/key-map.ts +45 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/local-connectors.ts +322 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +391 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +309 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +24 -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 +123 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +72 -0
- package/runtime/node_modules/@metro-labs/mcp/src/db/vault.ts +119 -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 +141 -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 +60 -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 +26 -0
- package/runtime/node_modules/@metro-labs/telegram/src/accounts.ts +80 -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 +119 -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 +37 -0
- package/runtime/node_modules/@metro-labs/whatsapp/package.json +29 -0
- package/runtime/node_modules/@metro-labs/whatsapp/src/accounts.ts +66 -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 +112 -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 -277
- package/dist/cli/index.js +0 -236
- package/dist/cli/lines.js +0 -40
- package/dist/cli/update.js +0 -27
- package/dist/dispatcher.js +0 -203
- package/dist/helpers/async-queue.js +0 -41
- package/dist/helpers/scope-cache.js +0 -67
- package/dist/helpers/streaming.js +0 -219
- package/dist/helpers/turn.js +0 -62
- package/dist/log.js +0 -15
- package/dist/paths.js +0 -68
- package/dist/stations/claude/index.js +0 -224
- package/dist/stations/codex/index.js +0 -226
- package/dist/stations/discord/index.js +0 -160
- package/dist/stations/github/index.js +0 -135
- package/dist/stations/line.js +0 -54
- package/dist/stations/listing.js +0 -16
- package/dist/stations/send.js +0 -19
- package/dist/stations/telegram/files.js +0 -31
- package/dist/stations/telegram/format.js +0 -39
- package/dist/stations/telegram/index.js +0 -209
- package/dist/stations/types.js +0 -2
- package/docs/agents.md +0 -92
- package/docs/uri-scheme.md +0 -52
package/dist/bedrock.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { randomBytes } from 'node:crypto';
|
|
3
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { claudeArgs } from './claude.js';
|
|
7
|
+
import { bedrockConfigFromEnv, startBedrockProxy } from './bedrock-proxy.js';
|
|
8
|
+
export const PROVIDER_FLAGS = [
|
|
9
|
+
'CLAUDE_CODE_USE_BEDROCK',
|
|
10
|
+
'CLAUDE_CODE_USE_MANTLE',
|
|
11
|
+
'CLAUDE_CODE_USE_VERTEX',
|
|
12
|
+
'CLAUDE_CODE_USE_FOUNDRY',
|
|
13
|
+
'CLAUDE_CODE_USE_ANTHROPIC_AWS',
|
|
14
|
+
'CLAUDE_CODE_USE_ANTHROPIC_GOOGLE_CLOUD',
|
|
15
|
+
'CLAUDE_CODE_USE_GATEWAY',
|
|
16
|
+
];
|
|
17
|
+
const CONFLICTING = [
|
|
18
|
+
...PROVIDER_FLAGS,
|
|
19
|
+
'ANTHROPIC_BASE_URL',
|
|
20
|
+
'ANTHROPIC_API_KEY',
|
|
21
|
+
'ANTHROPIC_AUTH_TOKEN',
|
|
22
|
+
];
|
|
23
|
+
const SCRUBBED = [...PROVIDER_FLAGS, 'ANTHROPIC_API_KEY', 'AWS_BEARER_TOKEN_BEDROCK'];
|
|
24
|
+
export function settingsFiles(cwd = process.cwd(), env = process.env) {
|
|
25
|
+
const explicit = env.CLAUDE_CONFIG_DIR?.trim() ?? '';
|
|
26
|
+
const configDir = explicit === '' ? join(homedir(), '.claude') : explicit;
|
|
27
|
+
return [
|
|
28
|
+
...new Set([
|
|
29
|
+
join(configDir, 'settings.json'),
|
|
30
|
+
join(cwd, '.claude', 'settings.json'),
|
|
31
|
+
join(cwd, '.claude', 'settings.local.json'),
|
|
32
|
+
]),
|
|
33
|
+
];
|
|
34
|
+
}
|
|
35
|
+
function envBlock(file) {
|
|
36
|
+
if (!existsSync(file))
|
|
37
|
+
return {};
|
|
38
|
+
try {
|
|
39
|
+
const parsed = JSON.parse(readFileSync(file, 'utf8'));
|
|
40
|
+
const block = parsed.env;
|
|
41
|
+
return typeof block === 'object' && block !== null
|
|
42
|
+
? block
|
|
43
|
+
: {};
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return {};
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function settingsConflicts(files) {
|
|
50
|
+
const out = [];
|
|
51
|
+
for (const file of files) {
|
|
52
|
+
const block = envBlock(file);
|
|
53
|
+
for (const key of CONFLICTING) {
|
|
54
|
+
const value = block[key];
|
|
55
|
+
if (typeof value === 'string' && value.trim() !== '')
|
|
56
|
+
out.push(`${file}: ${key}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out;
|
|
60
|
+
}
|
|
61
|
+
export function firstPartyModelId(bedrockId) {
|
|
62
|
+
return bedrockId.replace(/^(?:[a-z-]+\.)?anthropic\./, '').replace(/-v\d+:\d+$/, '');
|
|
63
|
+
}
|
|
64
|
+
export function claudeEnv(base, port, token, pinned = null) {
|
|
65
|
+
const kept = Object.entries(base).filter(([key]) => !SCRUBBED.includes(key));
|
|
66
|
+
const model = (base.ANTHROPIC_MODEL ?? '').trim();
|
|
67
|
+
return {
|
|
68
|
+
...Object.fromEntries(kept),
|
|
69
|
+
...(pinned !== null && model === '' && { ANTHROPIC_MODEL: firstPartyModelId(pinned) }),
|
|
70
|
+
ANTHROPIC_BASE_URL: `http://127.0.0.1:${String(port)}`,
|
|
71
|
+
ANTHROPIC_AUTH_TOKEN: token,
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
function runClaude(args, env) {
|
|
75
|
+
return new Promise((resolve, reject) => {
|
|
76
|
+
const leaveToChild = () => undefined;
|
|
77
|
+
process.on('SIGINT', leaveToChild);
|
|
78
|
+
process.on('SIGTERM', leaveToChild);
|
|
79
|
+
const child = spawn('claude', args, { stdio: 'inherit', env });
|
|
80
|
+
child.on('error', (err) => {
|
|
81
|
+
reject(new Error(err.code === 'ENOENT'
|
|
82
|
+
? 'the `claude` command is not on PATH — install Claude Code first'
|
|
83
|
+
: err.message));
|
|
84
|
+
});
|
|
85
|
+
child.on('exit', (code) => {
|
|
86
|
+
resolve(code ?? 1);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
export async function bedrock(argv) {
|
|
91
|
+
const cfg = bedrockConfigFromEnv();
|
|
92
|
+
const conflicts = settingsConflicts(settingsFiles());
|
|
93
|
+
if (conflicts.length > 0)
|
|
94
|
+
throw new Error('metro bedrock: these settings would put Claude Code back on a third-party provider, or override the proxy. Remove them, or point CLAUDE_CONFIG_DIR at a separate profile:\n ' +
|
|
95
|
+
conflicts.join('\n '));
|
|
96
|
+
const token = `mb_${randomBytes(18).toString('base64url')}`;
|
|
97
|
+
const proxy = await startBedrockProxy(cfg, { token });
|
|
98
|
+
const pinned = cfg.model === null ? '' : ` (${cfg.model})`;
|
|
99
|
+
process.stderr.write(`metro bedrock: Claude Code → http://127.0.0.1:${String(proxy.port)} → Bedrock ${cfg.region}${pinned}\n`);
|
|
100
|
+
try {
|
|
101
|
+
return await runClaude(claudeArgs(argv), claudeEnv(process.env, proxy.port, token, cfg.model));
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
await proxy.close();
|
|
105
|
+
}
|
|
106
|
+
}
|
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
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
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, 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, runTokenState, 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 { serve } from './serve.js';
|
|
14
|
+
import { currentVersion } from './version.js';
|
|
15
|
+
const USAGE = `metro — the command line for your MCP connectors
|
|
16
|
+
|
|
17
|
+
metro start <agent-id> [--detach]
|
|
18
|
+
run that agent's stations on this machine
|
|
19
|
+
metro serve [--port <n>] [--tunnel] [--owner <address>]
|
|
20
|
+
run a daemon of your own: its agents and stations live in ~/.metro/agents
|
|
21
|
+
here, and metro.box manages it through the link it prints; --tunnel adds
|
|
22
|
+
a public https address through a Cloudflare quick tunnel (needs
|
|
23
|
+
cloudflared, no account) so the link works from anywhere
|
|
24
|
+
metro stop [agent-id]
|
|
25
|
+
stop every metro daemon on this machine, however it was started
|
|
26
|
+
metro status <agent-id>
|
|
27
|
+
is it running, and is it healthy
|
|
28
|
+
metro logs <agent-id> [-f]
|
|
29
|
+
show the detached daemon's log
|
|
30
|
+
metro tail <agent-id>
|
|
31
|
+
follow this machine's inbound events, one JSON line each
|
|
32
|
+
metro login [code]
|
|
33
|
+
authorize this machine for one agent with a code from its page
|
|
34
|
+
metro logout forget this machine's sign-in
|
|
35
|
+
metro whoami [agent]
|
|
36
|
+
print the account and agent this machine may read
|
|
37
|
+
metro mcp [agent]
|
|
38
|
+
print the mcpServers block of the agent's connectors; with a local daemon
|
|
39
|
+
running, its agent's connectors (name one if several)
|
|
40
|
+
metro plugin set up the Claude Code plugin (connector servers + /metro:login)
|
|
41
|
+
metro claude [args...]
|
|
42
|
+
open Claude Code with the metro channel; every argument is passed through
|
|
43
|
+
metro bedrock [args...]
|
|
44
|
+
the same, with inference on Amazon Bedrock through a local proxy so the
|
|
45
|
+
channel still works (needs AWS_BEARER_TOKEN_BEDROCK and AWS_REGION)
|
|
46
|
+
metro update update to the newest published version
|
|
47
|
+
metro version print this CLI's version
|
|
48
|
+
|
|
49
|
+
Start Claude Code with all of them, without writing them to disk:
|
|
50
|
+
|
|
51
|
+
claude --mcp-config <(metro mcp)
|
|
52
|
+
|
|
53
|
+
METRO_URL the metro to talk to (default https://mcp.metro.box)
|
|
54
|
+
METRO_UI_URL where the web UI lives (default https://metro.box)
|
|
55
|
+
METRO_TOKEN use this agent sign-in instead of the stored one; with neither,
|
|
56
|
+
a machine running metro start uses its run token
|
|
57
|
+
METRO_RUN_TOKEN use this runtime authorization instead of the stored one
|
|
58
|
+
METRO_BEDROCK_MODEL
|
|
59
|
+
send every request to this Bedrock model id (default: derive from the
|
|
60
|
+
model Claude Code asks for, e.g. eu.anthropic.claude-sonnet-4-6)
|
|
61
|
+
METRO_RUNTIME_DIR run the daemon from this directory instead of the bundled one
|
|
62
|
+
`;
|
|
63
|
+
function hostLabel() {
|
|
64
|
+
const name = hostname().trim();
|
|
65
|
+
return name === '' ? 'unnamed machine' : name;
|
|
66
|
+
}
|
|
67
|
+
async function authorizeRuntime(agentId) {
|
|
68
|
+
process.stderr.write(`Authorize this machine with a pairing code for agent ${agentId} from ${metroWebUrl()}\n`);
|
|
69
|
+
const code = (await askSecret('Paste the code (input is hidden): ')).trim();
|
|
70
|
+
if (code === '')
|
|
71
|
+
throw new Error('no code given');
|
|
72
|
+
const claimed = await claimRuntime(code, hostLabel());
|
|
73
|
+
writeRunToken(claimed.agent, claimed.token);
|
|
74
|
+
process.stderr.write(`Authorized as '${claimed.label}'.\n`);
|
|
75
|
+
return claimed.token;
|
|
76
|
+
}
|
|
77
|
+
async function usableRunToken(agentId) {
|
|
78
|
+
const stored = readRunToken(agentId);
|
|
79
|
+
if (stored === null)
|
|
80
|
+
return authorizeRuntime(agentId);
|
|
81
|
+
const state = await runTokenState(stored);
|
|
82
|
+
if (state !== 'stale')
|
|
83
|
+
return stored;
|
|
84
|
+
process.stderr.write(`metro no longer recognises this machine as the holder of ${agentId}: ` +
|
|
85
|
+
'it was taken back, or another machine claimed it since. Authorize it again.\n');
|
|
86
|
+
return authorizeRuntime(agentId);
|
|
87
|
+
}
|
|
88
|
+
async function start(argv) {
|
|
89
|
+
const agentId = assertAgentId(argv[0]);
|
|
90
|
+
if (runningPid(agentId) !== null || lockedBy() !== null)
|
|
91
|
+
throw new Error('a metro daemon is already running on this machine. ' +
|
|
92
|
+
'Stop it first: metro stop');
|
|
93
|
+
const dir = runtimeDir();
|
|
94
|
+
const token = await usableRunToken(agentId);
|
|
95
|
+
const detached = argv.includes('--detach');
|
|
96
|
+
const plan = daemonPlan({ agentId, token, dir });
|
|
97
|
+
if (!detached)
|
|
98
|
+
return runDaemon(agentId, plan);
|
|
99
|
+
const pid = detach({ agentId, ...plan });
|
|
100
|
+
process.stderr.write(`metro is running for ${agentId} (pid ${String(pid)})\n` +
|
|
101
|
+
` logs: metro logs ${agentId} -f\n` +
|
|
102
|
+
` stop: metro stop ${agentId}\n`);
|
|
103
|
+
return 0;
|
|
104
|
+
}
|
|
105
|
+
async function stopDaemon(argv) {
|
|
106
|
+
const agentId = argv[0] === undefined ? undefined : assertAgentId(argv[0]);
|
|
107
|
+
const stopped = await stopAll(agentId);
|
|
108
|
+
if (stopped.length === 0) {
|
|
109
|
+
process.stderr.write('no metro daemon is running on this machine\n');
|
|
110
|
+
return 1;
|
|
111
|
+
}
|
|
112
|
+
for (const d of stopped)
|
|
113
|
+
process.stderr.write(`Stopped metro (pid ${String(d.pid)}, via ${d.via})\n`);
|
|
114
|
+
return 0;
|
|
115
|
+
}
|
|
116
|
+
async function status(argv) {
|
|
117
|
+
const agentId = assertAgentId(argv[0]);
|
|
118
|
+
const pid = runningPid(agentId);
|
|
119
|
+
if (pid === null) {
|
|
120
|
+
process.stdout.write(`${agentId}: not running\n`);
|
|
121
|
+
return 1;
|
|
122
|
+
}
|
|
123
|
+
const health = await probe(localUrl());
|
|
124
|
+
process.stdout.write(`${agentId}: running (pid ${String(pid)}) · ` +
|
|
125
|
+
(health === null
|
|
126
|
+
? `not answering on ${localUrl()}\n`
|
|
127
|
+
: `healthy, up ${String(health.uptime)}s\n`));
|
|
128
|
+
return health === null ? 1 : 0;
|
|
129
|
+
}
|
|
130
|
+
function logs(argv) {
|
|
131
|
+
const agentId = assertAgentId(argv[0]);
|
|
132
|
+
return tail(agentId, argv.includes('-f') || argv.includes('--follow'));
|
|
133
|
+
}
|
|
134
|
+
async function pastedCode(given) {
|
|
135
|
+
if (given !== undefined && given.trim() !== '')
|
|
136
|
+
return given.trim();
|
|
137
|
+
process.stderr.write(`Get a pairing code for the agent from ${metroWebUrl()}\n`);
|
|
138
|
+
return askSecret('Paste the code here (input is hidden): ');
|
|
139
|
+
}
|
|
140
|
+
async function login(given) {
|
|
141
|
+
const { token, subject, agent } = await claimCode(await pastedCode(given));
|
|
142
|
+
writeToken(token);
|
|
143
|
+
process.stderr.write(`Authorized '${agent}' for ${subject}. Stored in ${credentialsPath()}\n`);
|
|
144
|
+
if (syncPluginServers())
|
|
145
|
+
process.stderr.write('Claude Code plugin refreshed — new sessions have the connectors; ' +
|
|
146
|
+
'run /reload-plugins in any session already open.\n');
|
|
147
|
+
}
|
|
148
|
+
async function whoami(wanted) {
|
|
149
|
+
const { subject, agent, where } = await whoisAuthorized(wanted);
|
|
150
|
+
process.stdout.write(`${subject} · agent '${agent}' on ${where}\n`);
|
|
151
|
+
}
|
|
152
|
+
const HELP = new Set([undefined, 'help', '--help', '-h']);
|
|
153
|
+
const COMMANDS = {
|
|
154
|
+
login: async () => {
|
|
155
|
+
await login(process.argv[3]);
|
|
156
|
+
return 0;
|
|
157
|
+
},
|
|
158
|
+
logout: async () => {
|
|
159
|
+
clearToken();
|
|
160
|
+
process.stderr.write('Signed out.\n');
|
|
161
|
+
return Promise.resolve(0);
|
|
162
|
+
},
|
|
163
|
+
whoami: async () => {
|
|
164
|
+
await whoami(process.argv[3]);
|
|
165
|
+
return 0;
|
|
166
|
+
},
|
|
167
|
+
start: () => start(process.argv.slice(3)),
|
|
168
|
+
serve: () => serve(process.argv.slice(3)),
|
|
169
|
+
stop: () => stopDaemon(process.argv.slice(3)),
|
|
170
|
+
status: () => status(process.argv.slice(3)),
|
|
171
|
+
logs: () => logs(process.argv.slice(3)),
|
|
172
|
+
tail: () => tailEvents(process.argv.slice(3)),
|
|
173
|
+
mcp: async () => {
|
|
174
|
+
process.stdout.write(`${await mcpServers(process.argv[3])}\n`);
|
|
175
|
+
return 0;
|
|
176
|
+
},
|
|
177
|
+
plugin: installPlugin,
|
|
178
|
+
claude: () => launchClaude(process.argv.slice(3)),
|
|
179
|
+
bedrock: () => bedrock(process.argv.slice(3)),
|
|
180
|
+
update,
|
|
181
|
+
version: async () => {
|
|
182
|
+
process.stdout.write(`${currentVersion()}\n`);
|
|
183
|
+
return Promise.resolve(0);
|
|
184
|
+
},
|
|
185
|
+
};
|
|
186
|
+
async function run(command) {
|
|
187
|
+
const handler = command === undefined ? undefined : COMMANDS[command];
|
|
188
|
+
if (handler !== undefined)
|
|
189
|
+
return handler();
|
|
190
|
+
process.stderr.write(USAGE);
|
|
191
|
+
return Promise.resolve(HELP.has(command) ? 0 : 1);
|
|
192
|
+
}
|
|
193
|
+
const code = await run(process.argv[2]).catch((err) => {
|
|
194
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
195
|
+
process.stderr.write(`metro: ${message}\n`);
|
|
196
|
+
return err instanceof NotSignedIn ? 2 : 1;
|
|
197
|
+
});
|
|
198
|
+
process.exit(code);
|
package/dist/control.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
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
|
+
export function serveStateDir() {
|
|
7
|
+
const xdg = process.env.XDG_CACHE_HOME?.trim();
|
|
8
|
+
const base = xdg === undefined || xdg === '' ? join(homedir(), '.cache') : xdg;
|
|
9
|
+
return join(base, 'metro', 'serve');
|
|
10
|
+
}
|
|
11
|
+
const serveLockPath = () => join(serveStateDir(), '.tail-lock');
|
|
12
|
+
const pidPath = (agentId) => join(STATE(), `run-${agentId}.pid`);
|
|
13
|
+
export const logPath = (agentId) => join(STATE(), 'logs', `${agentId}.log`);
|
|
14
|
+
function ensureStateDirs() {
|
|
15
|
+
mkdirSync(STATE(), { recursive: true, mode: 0o700 });
|
|
16
|
+
mkdirSync(join(STATE(), 'logs'), { recursive: true, mode: 0o700 });
|
|
17
|
+
}
|
|
18
|
+
function readPid(agentId) {
|
|
19
|
+
try {
|
|
20
|
+
const pid = Number(readFileSync(pidPath(agentId), 'utf8').trim());
|
|
21
|
+
return Number.isInteger(pid) && pid > 0 ? pid : null;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function isAlive(pid) {
|
|
28
|
+
try {
|
|
29
|
+
process.kill(pid, 0);
|
|
30
|
+
return true;
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
export function runningPid(agentId) {
|
|
37
|
+
const pid = readPid(agentId);
|
|
38
|
+
if (pid === null)
|
|
39
|
+
return null;
|
|
40
|
+
if (isAlive(pid))
|
|
41
|
+
return pid;
|
|
42
|
+
rmSync(pidPath(agentId), { force: true });
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
function writePid(agentId, pid) {
|
|
46
|
+
writeFileSync(pidPath(agentId), String(pid));
|
|
47
|
+
}
|
|
48
|
+
function clearPid(agentId) {
|
|
49
|
+
rmSync(pidPath(agentId), { force: true });
|
|
50
|
+
}
|
|
51
|
+
export function detach(opts) {
|
|
52
|
+
ensureStateDirs();
|
|
53
|
+
const out = openSync(logPath(opts.agentId), 'a');
|
|
54
|
+
const child = spawn(opts.command, opts.args, {
|
|
55
|
+
cwd: opts.cwd,
|
|
56
|
+
env: opts.env,
|
|
57
|
+
detached: true,
|
|
58
|
+
stdio: ['ignore', out, out],
|
|
59
|
+
});
|
|
60
|
+
child.unref();
|
|
61
|
+
const pid = child.pid ?? 0;
|
|
62
|
+
if (pid === 0)
|
|
63
|
+
throw new Error('could not start the metro daemon');
|
|
64
|
+
writePid(opts.agentId, pid);
|
|
65
|
+
return pid;
|
|
66
|
+
}
|
|
67
|
+
async function stopPid(pid, waitMs) {
|
|
68
|
+
process.kill(pid, 'SIGTERM');
|
|
69
|
+
const until = Date.now() + waitMs;
|
|
70
|
+
while (Date.now() < until) {
|
|
71
|
+
if (!isAlive(pid))
|
|
72
|
+
return;
|
|
73
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
74
|
+
}
|
|
75
|
+
process.kill(pid, 'SIGKILL');
|
|
76
|
+
}
|
|
77
|
+
const lockPath = () => process.env.METRO_STATE_DIR?.trim()
|
|
78
|
+
? join(process.env.METRO_STATE_DIR.trim(), '.tail-lock')
|
|
79
|
+
: join(homedir(), '.cache', 'metro', '.tail-lock');
|
|
80
|
+
function commandOf(pid) {
|
|
81
|
+
try {
|
|
82
|
+
return execFileSync('ps', ['-o', 'command=', '-p', String(pid)], {
|
|
83
|
+
encoding: 'utf8',
|
|
84
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return '';
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
const looksLikeMetro = (command) => command.includes('server.ts') || command.includes('metro');
|
|
92
|
+
function holderOf(lockFile) {
|
|
93
|
+
let pid;
|
|
94
|
+
try {
|
|
95
|
+
pid = Number(readFileSync(lockFile, 'utf8').trim());
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return null;
|
|
99
|
+
}
|
|
100
|
+
if (!Number.isInteger(pid) || pid <= 0 || !isAlive(pid))
|
|
101
|
+
return null;
|
|
102
|
+
return looksLikeMetro(commandOf(pid)) ? pid : null;
|
|
103
|
+
}
|
|
104
|
+
export const lockedBy = () => holderOf(lockPath());
|
|
105
|
+
export const serveLockedBy = () => holderOf(serveLockPath());
|
|
106
|
+
function trackedAgents(agentId) {
|
|
107
|
+
const tracked = new Set();
|
|
108
|
+
if (agentId !== undefined)
|
|
109
|
+
tracked.add(agentId);
|
|
110
|
+
try {
|
|
111
|
+
for (const name of readdirSync(STATE())) {
|
|
112
|
+
const found = /^run-(.+)\.pid$/.exec(name);
|
|
113
|
+
if (found?.[1] !== undefined)
|
|
114
|
+
tracked.add(found[1]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
return tracked;
|
|
119
|
+
}
|
|
120
|
+
return tracked;
|
|
121
|
+
}
|
|
122
|
+
export async function stopAll(agentId, waitMs = 10_000) {
|
|
123
|
+
const stopped = [];
|
|
124
|
+
const seen = new Set();
|
|
125
|
+
for (const id of trackedAgents(agentId)) {
|
|
126
|
+
const pid = runningPid(id);
|
|
127
|
+
if (pid === null || seen.has(pid))
|
|
128
|
+
continue;
|
|
129
|
+
seen.add(pid);
|
|
130
|
+
await stopPid(pid, waitMs);
|
|
131
|
+
clearPid(id);
|
|
132
|
+
stopped.push({ pid, via: id });
|
|
133
|
+
}
|
|
134
|
+
const holder = lockedBy();
|
|
135
|
+
if (holder !== null && !seen.has(holder)) {
|
|
136
|
+
seen.add(holder);
|
|
137
|
+
await stopPid(holder, waitMs);
|
|
138
|
+
rmSync(lockPath(), { force: true });
|
|
139
|
+
stopped.push({ pid: holder, via: 'the machine lock' });
|
|
140
|
+
}
|
|
141
|
+
const served = serveLockedBy();
|
|
142
|
+
if (served !== null && !seen.has(served)) {
|
|
143
|
+
await stopPid(served, waitMs);
|
|
144
|
+
rmSync(serveLockPath(), { force: true });
|
|
145
|
+
stopped.push({ pid: served, via: 'metro serve' });
|
|
146
|
+
}
|
|
147
|
+
return stopped;
|
|
148
|
+
}
|
|
149
|
+
export function tail(agentId, follow) {
|
|
150
|
+
const path = logPath(agentId);
|
|
151
|
+
if (!existsSync(path))
|
|
152
|
+
throw new Error(`no log for ${agentId} yet — it is only written when started with --detach`);
|
|
153
|
+
const args = follow ? ['-n', '200', '-f', path] : ['-n', '200', path];
|
|
154
|
+
const child = spawn('tail', args, { stdio: ['ignore', 'inherit', 'inherit'] });
|
|
155
|
+
return new Promise((resolve) => {
|
|
156
|
+
child.on('exit', (code) => {
|
|
157
|
+
resolve(code ?? 0);
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
export async function probe(url) {
|
|
162
|
+
try {
|
|
163
|
+
const res = await fetch(`${url}/health`, {
|
|
164
|
+
signal: AbortSignal.timeout(3_000),
|
|
165
|
+
});
|
|
166
|
+
if (!res.ok)
|
|
167
|
+
return null;
|
|
168
|
+
return (await res.json());
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
return null;
|
|
172
|
+
}
|
|
173
|
+
}
|
package/dist/local.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { homedir } from 'node:os';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { localUrl } from './runtime.js';
|
|
5
|
+
const PROBE_MS = 3_000;
|
|
6
|
+
const READ_MS = 20_000;
|
|
7
|
+
export function agentsDir() {
|
|
8
|
+
const explicit = process.env.METRO_AGENTS_DIR?.trim();
|
|
9
|
+
return explicit !== undefined && explicit !== '' ? explicit : join(homedir(), '.metro', 'agents');
|
|
10
|
+
}
|
|
11
|
+
export function localAgents(dir = agentsDir()) {
|
|
12
|
+
if (!existsSync(dir))
|
|
13
|
+
return [];
|
|
14
|
+
const out = [];
|
|
15
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
16
|
+
if (!entry.isDirectory())
|
|
17
|
+
continue;
|
|
18
|
+
const path = join(dir, entry.name, 'agent.json');
|
|
19
|
+
if (!existsSync(path))
|
|
20
|
+
continue;
|
|
21
|
+
try {
|
|
22
|
+
const file = JSON.parse(readFileSync(path, 'utf8'));
|
|
23
|
+
if (typeof file.id === 'string' && typeof file.name === 'string' && typeof file.key === 'string')
|
|
24
|
+
out.push({ id: file.id, name: file.name, key: file.key });
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return out.sort((a, b) => a.name.localeCompare(b.name));
|
|
31
|
+
}
|
|
32
|
+
export function pickLocalAgent(agents, wanted) {
|
|
33
|
+
if (wanted !== undefined && wanted !== '') {
|
|
34
|
+
const found = agents.find((a) => a.name === wanted || a.id === wanted);
|
|
35
|
+
if (found === undefined)
|
|
36
|
+
throw new Error(`no local agent named '${wanted}'`);
|
|
37
|
+
return found;
|
|
38
|
+
}
|
|
39
|
+
const only = agents[0];
|
|
40
|
+
if (only !== undefined && agents.length === 1)
|
|
41
|
+
return only;
|
|
42
|
+
throw new Error(agents.length === 0
|
|
43
|
+
? 'no agent on this machine yet'
|
|
44
|
+
: `several agents on this machine — name one: ${agents.map((a) => a.name).join(', ')}`);
|
|
45
|
+
}
|
|
46
|
+
export async function localDaemonUp(base = localUrl()) {
|
|
47
|
+
try {
|
|
48
|
+
const res = await fetch(`${base}/api/mode`, { signal: AbortSignal.timeout(PROBE_MS) });
|
|
49
|
+
const body = (await res.json());
|
|
50
|
+
return res.ok && body.mode === 'local';
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export async function localMcpServers(agent, base = localUrl()) {
|
|
57
|
+
const res = await fetch(`${base}/api/cli/mcp`, {
|
|
58
|
+
headers: { authorization: `Bearer ${agent.key}` },
|
|
59
|
+
signal: AbortSignal.timeout(READ_MS),
|
|
60
|
+
});
|
|
61
|
+
if (!res.ok)
|
|
62
|
+
throw new Error(`the local daemon answered ${String(res.status)} for the connectors`);
|
|
63
|
+
const body = (await res.json());
|
|
64
|
+
if (typeof body.json !== 'string')
|
|
65
|
+
throw new Error('the local daemon returned an unexpected response');
|
|
66
|
+
return body.json;
|
|
67
|
+
}
|
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process';
|
|
2
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
3
|
+
import { homedir } from 'node:os';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { readToken } from './store.js';
|
|
6
|
+
const MARKETPLACE_REPO = 'bonustrack/metro';
|
|
7
|
+
const PLUGIN_SPEC = 'metro@metro';
|
|
8
|
+
export function installPathFrom(raw) {
|
|
9
|
+
let parsed;
|
|
10
|
+
try {
|
|
11
|
+
parsed = JSON.parse(raw);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
if (typeof parsed !== 'object' || parsed === null)
|
|
17
|
+
return null;
|
|
18
|
+
const plugins = parsed.plugins;
|
|
19
|
+
if (typeof plugins !== 'object' || plugins === null)
|
|
20
|
+
return null;
|
|
21
|
+
const entries = plugins[PLUGIN_SPEC];
|
|
22
|
+
if (!Array.isArray(entries))
|
|
23
|
+
return null;
|
|
24
|
+
const first = entries[0];
|
|
25
|
+
return typeof first?.installPath === 'string' ? first.installPath : null;
|
|
26
|
+
}
|
|
27
|
+
export function pluginInstallPath() {
|
|
28
|
+
const registry = join(homedir(), '.claude', 'plugins', 'installed_plugins.json');
|
|
29
|
+
let raw;
|
|
30
|
+
try {
|
|
31
|
+
raw = readFileSync(registry, 'utf8');
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
const path = installPathFrom(raw);
|
|
37
|
+
if (path === null)
|
|
38
|
+
return null;
|
|
39
|
+
return existsSync(join(path, 'bin', 'metro-plugin.mjs')) ? path : null;
|
|
40
|
+
}
|
|
41
|
+
function runClaude(args) {
|
|
42
|
+
const res = spawnSync('claude', args, { encoding: 'utf8' });
|
|
43
|
+
if (res.error !== undefined)
|
|
44
|
+
throw new Error('the `claude` command is not on PATH — install Claude Code first');
|
|
45
|
+
const output = `${res.stdout ?? ''}${res.stderr ?? ''}`;
|
|
46
|
+
return { ok: res.status === 0, output };
|
|
47
|
+
}
|
|
48
|
+
const tolerable = (output) => /already|exists|latest version/i.test(output);
|
|
49
|
+
export function syncPluginServers() {
|
|
50
|
+
const path = pluginInstallPath();
|
|
51
|
+
if (path === null || readToken() === null)
|
|
52
|
+
return false;
|
|
53
|
+
const script = join(path, 'bin', 'metro-plugin.mjs');
|
|
54
|
+
const refreshed = spawnSync(process.execPath, [script, 'refresh'], {
|
|
55
|
+
stdio: ['ignore', 'inherit', 'inherit'],
|
|
56
|
+
});
|
|
57
|
+
if (refreshed.status !== 0)
|
|
58
|
+
return false;
|
|
59
|
+
runClaude(['plugin', 'update', PLUGIN_SPEC]);
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
export async function installPlugin() {
|
|
63
|
+
const added = runClaude(['plugin', 'marketplace', 'add', MARKETPLACE_REPO]);
|
|
64
|
+
if (!added.ok && !tolerable(added.output)) {
|
|
65
|
+
process.stderr.write(added.output);
|
|
66
|
+
return 1;
|
|
67
|
+
}
|
|
68
|
+
const installed = runClaude(['plugin', 'install', PLUGIN_SPEC]);
|
|
69
|
+
if (!installed.ok && !tolerable(installed.output)) {
|
|
70
|
+
process.stderr.write(installed.output);
|
|
71
|
+
return 1;
|
|
72
|
+
}
|
|
73
|
+
process.stderr.write('Claude Code plugin installed.\n');
|
|
74
|
+
if (syncPluginServers()) {
|
|
75
|
+
process.stderr.write('Connector servers loaded — new Claude sessions have them; ' +
|
|
76
|
+
'run /reload-plugins in any session already open.\n');
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
process.stderr.write('Now sign in: metro login (with a pairing code from https://metro.box), ' +
|
|
80
|
+
'or run /metro:login <code> inside Claude Code.\n');
|
|
81
|
+
return Promise.resolve(0);
|
|
82
|
+
}
|