@stage-labs/metro 0.1.0-beta.325 → 0.1.0-beta.326

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.
Files changed (175) hide show
  1. package/dist/agent.js +1 -1
  2. package/dist/cli.js +1 -1
  3. package/dist/lock.js +15 -3
  4. package/dist/root-release-io.js +1 -2
  5. package/dist/root-release.js +2 -3
  6. package/dist/route.js +1 -1
  7. package/dist/service.js +4 -4
  8. package/dist/version.js +1 -1
  9. package/package.json +1 -1
  10. package/runtime/node_modules/@metro-labs/core/src/automation-store.ts +1 -1
  11. package/runtime/node_modules/@metro-labs/core/src/automation-types.ts +2 -2
  12. package/runtime/node_modules/@metro-labs/core/src/lines.ts +2 -8
  13. package/runtime/node_modules/@metro-labs/core/src/protocol.ts +0 -6
  14. package/runtime/node_modules/@metro-labs/core/src/skill-source.ts +1 -1
  15. package/runtime/node_modules/@metro-labs/core/src/stations/account-status.ts +1 -1
  16. package/runtime/node_modules/@metro-labs/core/src/stations/attachments.ts +5 -9
  17. package/runtime/node_modules/@metro-labs/core/src/stations/channel-directory.ts +1 -1
  18. package/runtime/node_modules/@metro-labs/core/src/stations/mail-text.ts +4 -4
  19. package/runtime/node_modules/@metro-labs/core/src/stations/mail.ts +3 -2
  20. package/runtime/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +1 -1
  21. package/runtime/node_modules/@metro-labs/core/src/stations/oauth.ts +1 -1
  22. package/runtime/node_modules/@metro-labs/core/src/stations/profile.ts +2 -2
  23. package/runtime/node_modules/@metro-labs/core/src/stations/types.ts +2 -2
  24. package/runtime/node_modules/@metro-labs/core/src/train-error.ts +4 -4
  25. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/files.ts +2 -6
  26. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/schedules.ts +6 -6
  27. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/user.ts +0 -24
  28. package/runtime/node_modules/@metro-labs/daemon/src/agent-user/view.ts +3 -3
  29. package/runtime/node_modules/@metro-labs/daemon/src/agents/bundle.ts +3 -4
  30. package/runtime/node_modules/@metro-labs/daemon/src/agents/file-admin.ts +4 -9
  31. package/runtime/node_modules/@metro-labs/daemon/src/agents/files.ts +1 -6
  32. package/runtime/node_modules/@metro-labs/daemon/src/channels/inbound.ts +3 -3
  33. package/runtime/node_modules/@metro-labs/daemon/src/channels/pending.ts +2 -2
  34. package/runtime/node_modules/@metro-labs/daemon/src/claude/files.ts +1 -1
  35. package/runtime/node_modules/@metro-labs/daemon/src/claude/skills.ts +1 -1
  36. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-grant.ts +3 -2
  37. package/runtime/node_modules/@metro-labs/daemon/src/files/attach-serve.ts +1 -1
  38. package/runtime/node_modules/@metro-labs/daemon/src/files/upload-api.ts +3 -7
  39. package/runtime/node_modules/@metro-labs/daemon/src/mcp/accounts.ts +10 -19
  40. package/runtime/node_modules/@metro-labs/daemon/src/mcp/call-tools.ts +1 -5
  41. package/runtime/node_modules/@metro-labs/daemon/src/mcp/event-store.ts +3 -4
  42. package/runtime/node_modules/@metro-labs/daemon/src/mcp/group-tools.ts +1 -2
  43. package/runtime/node_modules/@metro-labs/daemon/src/mcp/index.ts +2 -4
  44. package/runtime/node_modules/@metro-labs/daemon/src/mcp/policy-gate.ts +5 -6
  45. package/runtime/node_modules/@metro-labs/daemon/src/mcp/server-approvals.ts +2 -5
  46. package/runtime/node_modules/@metro-labs/daemon/src/mcp/session.ts +1 -5
  47. package/runtime/node_modules/@metro-labs/daemon/src/mcp/tool-catalog.ts +2 -4
  48. package/runtime/node_modules/@metro-labs/daemon/src/mcp-server/announced.ts +1 -1
  49. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/helper-script.ts +0 -1
  50. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/install.ts +2 -0
  51. package/runtime/node_modules/@metro-labs/daemon/src/metro-user/tailscale-guard.ts +26 -0
  52. package/runtime/node_modules/@metro-labs/daemon/src/monitor/api.ts +1 -3
  53. package/runtime/node_modules/@metro-labs/daemon/src/routes/gmail-attach.ts +3 -3
  54. package/runtime/node_modules/@metro-labs/daemon/src/routes/http.ts +6 -21
  55. package/runtime/node_modules/@metro-labs/daemon/src/routes/local-mode.ts +1 -5
  56. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach-resolve.ts +6 -6
  57. package/runtime/node_modules/@metro-labs/daemon/src/stations/attach.ts +0 -1
  58. package/runtime/node_modules/@metro-labs/daemon/src/stations/materialize.ts +12 -61
  59. package/runtime/node_modules/@metro-labs/daemon/src/stations/supervisor.ts +13 -25
  60. package/runtime/node_modules/@metro-labs/daemon/src/stations/threema-callbacks.ts +5 -5
  61. package/runtime/node_modules/@metro-labs/daemon/src/stations/webhook-endpoints.ts +2 -7
  62. package/runtime/node_modules/@metro-labs/daemon/src/voice/call.ts +2 -6
  63. package/runtime/node_modules/@metro-labs/daemon/src/voice/calls.ts +12 -12
  64. package/runtime/node_modules/@metro-labs/discord-bot/src/actions.ts +5 -23
  65. package/runtime/node_modules/@metro-labs/discord-bot/src/attachments.ts +1 -7
  66. package/runtime/node_modules/@metro-labs/discord-bot/src/channels.ts +2 -2
  67. package/runtime/node_modules/@metro-labs/discord-bot/src/group.ts +10 -35
  68. package/runtime/node_modules/@metro-labs/discord-bot/src/index.ts +5 -11
  69. package/runtime/node_modules/@metro-labs/discord-bot/src/members.ts +6 -34
  70. package/runtime/node_modules/@metro-labs/discord-bot/src/profile.ts +11 -14
  71. package/runtime/node_modules/@metro-labs/discord-bot/src/send-files.ts +1 -3
  72. package/runtime/node_modules/@metro-labs/discord-bot/src/verify.ts +2 -3
  73. package/runtime/node_modules/@metro-labs/gmail/src/accounts.ts +3 -3
  74. package/runtime/node_modules/@metro-labs/gmail/src/auth.ts +8 -8
  75. package/runtime/node_modules/@metro-labs/gmail/src/managed.ts +3 -2
  76. package/runtime/node_modules/@metro-labs/http/src/agent-signature.ts +1 -1
  77. package/runtime/node_modules/@metro-labs/http/src/api-http.ts +3 -4
  78. package/runtime/node_modules/@metro-labs/http/src/connector-discovery.ts +0 -113
  79. package/runtime/node_modules/@metro-labs/http/src/connector-input.ts +1 -1
  80. package/runtime/node_modules/@metro-labs/http/src/connector-mcp.ts +12 -13
  81. package/runtime/node_modules/@metro-labs/http/src/connector-oauth.ts +1 -1
  82. package/runtime/node_modules/@metro-labs/http/src/connector-policy.ts +1 -6
  83. package/runtime/node_modules/@metro-labs/http/src/connector-public-url.ts +4 -2
  84. package/runtime/node_modules/@metro-labs/http/src/connector-tools.ts +1 -11
  85. package/runtime/node_modules/@metro-labs/http/src/connector-url.ts +0 -6
  86. package/runtime/node_modules/@metro-labs/http/src/mcp-server-protocol.ts +1 -1
  87. package/runtime/node_modules/@metro-labs/http/src/mcp-server-tools.ts +0 -2
  88. package/runtime/node_modules/@metro-labs/http/src/mode-api.ts +1 -1
  89. package/runtime/node_modules/@metro-labs/http/src/return-to.ts +2 -2
  90. package/runtime/node_modules/@metro-labs/http/src/workos-token.ts +6 -7
  91. package/runtime/node_modules/@metro-labs/inference/package.json +1 -0
  92. package/runtime/node_modules/@metro-labs/inference/src/attempts.ts +3 -3
  93. package/runtime/node_modules/@metro-labs/inference/src/bedrock.ts +1 -1
  94. package/runtime/node_modules/@metro-labs/inference/src/codex-translate.ts +2 -2
  95. package/runtime/node_modules/@metro-labs/inference/src/delivered.ts +1 -1
  96. package/runtime/node_modules/@metro-labs/inference/src/effort.ts +0 -6
  97. package/runtime/node_modules/@metro-labs/inference/src/fallback.ts +1 -1
  98. package/runtime/node_modules/@metro-labs/inference/src/forward.ts +5 -5
  99. package/runtime/node_modules/@metro-labs/inference/src/measure.ts +2 -3
  100. package/runtime/node_modules/@metro-labs/inference/src/named.ts +3 -3
  101. package/runtime/node_modules/@metro-labs/inference/src/probes.ts +98 -0
  102. package/runtime/node_modules/@metro-labs/inference/src/usage.ts +5 -5
  103. package/runtime/node_modules/@metro-labs/outlook/src/accounts.ts +6 -8
  104. package/runtime/node_modules/@metro-labs/outlook/src/auth.ts +2 -1
  105. package/runtime/node_modules/@metro-labs/outlook/src/inbound.ts +3 -5
  106. package/runtime/node_modules/@metro-labs/outlook/src/me.ts +6 -6
  107. package/runtime/node_modules/@metro-labs/outlook/src/outbound.ts +10 -13
  108. package/runtime/node_modules/@metro-labs/telegram/src/actions.ts +7 -26
  109. package/runtime/node_modules/@metro-labs/telegram/src/attachments.ts +1 -1
  110. package/runtime/node_modules/@metro-labs/telegram/src/login.ts +3 -6
  111. package/runtime/node_modules/@metro-labs/telegram/src/profile.ts +3 -2
  112. package/runtime/node_modules/@metro-labs/telegram-bot/src/actions.ts +6 -20
  113. package/runtime/node_modules/@metro-labs/telegram-bot/src/attachments.ts +1 -7
  114. package/runtime/node_modules/@metro-labs/telegram-bot/src/format.ts +0 -2
  115. package/runtime/node_modules/@metro-labs/telegram-bot/src/media-actions.ts +1 -16
  116. package/runtime/node_modules/@metro-labs/telegram-bot/src/members.ts +1 -8
  117. package/runtime/node_modules/@metro-labs/telegram-bot/src/profile.ts +2 -1
  118. package/runtime/node_modules/@metro-labs/threema/src/actions.ts +1 -8
  119. package/runtime/node_modules/@metro-labs/threema/src/chats.ts +4 -3
  120. package/runtime/node_modules/@metro-labs/threema/src/format.ts +15 -17
  121. package/runtime/node_modules/@metro-labs/threema/src/groups.ts +2 -4
  122. package/runtime/node_modules/@metro-labs/threema/src/inbound.ts +2 -1
  123. package/runtime/node_modules/@metro-labs/threema/src/messages.ts +2 -1
  124. package/runtime/node_modules/@metro-labs/threema/src/outbound.ts +7 -7
  125. package/runtime/node_modules/@metro-labs/whatsapp/src/actions.ts +1 -3
  126. package/runtime/node_modules/@metro-labs/whatsapp/src/attachments.ts +0 -2
  127. package/runtime/node_modules/@metro-labs/whatsapp/src/client.ts +1 -1
  128. package/runtime/node_modules/@metro-labs/whatsapp/src/login.ts +1 -1
  129. package/runtime/node_modules/@metro-labs/xmtp/src/accounts.ts +7 -0
  130. package/runtime/node_modules/@metro-labs/xmtp/src/actions-conv.ts +4 -10
  131. package/runtime/node_modules/@metro-labs/xmtp/src/actions-meta.ts +4 -5
  132. package/runtime/node_modules/@metro-labs/xmtp/src/actions.ts +14 -26
  133. package/runtime/node_modules/@metro-labs/xmtp/src/attachments.ts +1 -7
  134. package/runtime/node_modules/@metro-labs/xmtp/src/codecs.ts +17 -58
  135. package/runtime/node_modules/@metro-labs/xmtp/src/conv-helpers.ts +1 -6
  136. package/runtime/node_modules/@metro-labs/xmtp/src/emit-payloads.ts +10 -31
  137. package/runtime/node_modules/@metro-labs/xmtp/src/frames.ts +12 -14
  138. package/runtime/node_modules/@metro-labs/xmtp/src/group.ts +2 -3
  139. package/runtime/node_modules/@metro-labs/xmtp/src/identity.ts +3 -9
  140. package/runtime/node_modules/@metro-labs/xmtp/src/labels.ts +8 -9
  141. package/runtime/node_modules/@metro-labs/xmtp/src/names.ts +4 -2
  142. package/runtime/node_modules/@metro-labs/xmtp/src/network.ts +2 -3
  143. package/runtime/node_modules/@metro-labs/xmtp/src/profile.ts +2 -1
  144. package/runtime/node_modules/@metro-labs/xmtp/src/read-attachments.ts +2 -2
  145. package/runtime/node_modules/@metro-labs/xmtp/src/sender.ts +2 -1
  146. package/runtime/node_modules/@metro-labs/xmtp/src/transcribe.ts +23 -46
  147. package/runtime/node_modules/@metro-labs/xmtp/src/wallet.ts +5 -4
  148. package/runtime/node_modules/@metro-labs/xmtp/src/wire.ts +12 -15
  149. package/runtime/runtime.json +1 -1
  150. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/automation-store.ts +1 -1
  151. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/automation-types.ts +2 -2
  152. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/lines.ts +2 -8
  153. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/protocol.ts +0 -6
  154. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/skill-source.ts +1 -1
  155. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/account-status.ts +1 -1
  156. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/attachments.ts +5 -9
  157. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/channel-directory.ts +1 -1
  158. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/mail-text.ts +4 -4
  159. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/mail.ts +3 -2
  160. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/messaging-normalize.ts +1 -1
  161. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/oauth.ts +1 -1
  162. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/profile.ts +2 -2
  163. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/stations/types.ts +2 -2
  164. package/runtime/sdk-runner/node_modules/@metro-labs/core/src/train-error.ts +4 -4
  165. package/runtime/sdk-runner/runner.json +1 -1
  166. package/runtime/sdk-runner/src/approvals.ts +2 -2
  167. package/runtime/sdk-runner/src/automation-command.ts +2 -2
  168. package/runtime/sdk-runner/src/calls.ts +0 -6
  169. package/runtime/sdk-runner/src/errors.ts +1 -1
  170. package/runtime/sdk-runner/src/link.ts +1 -1
  171. package/runtime/sdk-runner/src/runner.ts +2 -2
  172. package/runtime/sdk-runner/src/session-watch.ts +5 -34
  173. package/runtime/sdk-runner/src/task-state.ts +1 -1
  174. package/runtime/sdk-runner/src/tool-proxy.ts +1 -1
  175. package/runtime/node_modules/@metro-labs/xmtp/src/member-args.ts +0 -40
package/dist/agent.js CHANGED
@@ -9,7 +9,7 @@ import { withLock } from './lock.js';
9
9
  import { currentModel, permissionMode, systemPrompt } from './route.js';
10
10
  import { installDependencies, ranges, readOrNull } from './runtime-install.js';
11
11
  import { findBun, localPort, runtimeDir } from './runtime.js';
12
- export const RUNNER_SOURCES = 'sdk-runner';
12
+ const RUNNER_SOURCES = 'sdk-runner';
13
13
  const STAMP = 'runner.json';
14
14
  const ENTRY = join('src', 'main.ts');
15
15
  const METRO_MODULES = join('node_modules', '@metro-labs');
package/dist/cli.js CHANGED
@@ -93,7 +93,7 @@ async function run(command) {
93
93
  if (handler !== undefined)
94
94
  return handler();
95
95
  process.stderr.write(USAGE);
96
- return Promise.resolve(HELP.has(command) ? 0 : 1);
96
+ return HELP.has(command) ? 0 : 1;
97
97
  }
98
98
  const code = await run(process.argv[2]).catch((err) => {
99
99
  const message = err instanceof Error ? err.message : String(err);
package/dist/lock.js CHANGED
@@ -2,6 +2,7 @@ import { closeSync, mkdirSync, openSync, readFileSync, rmSync, statSync, writeSy
2
2
  import { dirname } from 'node:path';
3
3
  import { readOrNull } from './runtime-install.js';
4
4
  const pause = new Int32Array(new SharedArrayBuffer(4));
5
+ const EMPTY_GRACE_MS = 5_000;
5
6
  export function processAlive(pid) {
6
7
  try {
7
8
  process.kill(pid, 0);
@@ -13,9 +14,13 @@ export function processAlive(pid) {
13
14
  }
14
15
  function lockHeld(path, now, staleMs) {
15
16
  try {
16
- if (now - statSync(path).mtimeMs >= staleMs)
17
+ const age = now - statSync(path).mtimeMs;
18
+ if (age >= staleMs)
17
19
  return false;
18
- const pid = Number(readFileSync(path, 'utf8').trim());
20
+ const text = readFileSync(path, 'utf8').trim();
21
+ if (text === '')
22
+ return age < EMPTY_GRACE_MS;
23
+ const pid = Number(text);
19
24
  return !Number.isInteger(pid) || pid <= 0 || processAlive(pid);
20
25
  }
21
26
  catch (err) {
@@ -28,7 +33,14 @@ export function takeLock(path, staleMs, now = Date.now(), retry = true) {
28
33
  try {
29
34
  mkdirSync(dirname(path), { recursive: true });
30
35
  const fd = openSync(path, 'wx');
31
- writeSync(fd, String(process.pid));
36
+ try {
37
+ writeSync(fd, String(process.pid));
38
+ }
39
+ catch (err) {
40
+ closeSync(fd);
41
+ rmSync(path, { force: true });
42
+ throw err;
43
+ }
32
44
  closeSync(fd);
33
45
  return true;
34
46
  }
@@ -5,8 +5,7 @@ import { createRequire } from 'node:module';
5
5
  import { tmpdir } from 'node:os';
6
6
  import { dirname, join } from 'node:path';
7
7
  import { ISSUER, isRecord, isVersion, PUBLISHER, RELEASE_DIR } from './root-release.js';
8
- import { PACKAGE_NAME } from './version.js';
9
- const REGISTRY = 'https://registry.npmjs.org';
8
+ import { PACKAGE_NAME, REGISTRY } from './version.js';
10
9
  const ENCODED = PACKAGE_NAME.replace('/', '%2F');
11
10
  const ROOT_PATH = '/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
12
11
  const SETUP = join('runtime', 'node_modules', '@metro-labs', 'daemon', 'src', 'metro-user', 'install.ts');
@@ -1,9 +1,8 @@
1
1
  import { createHash } from 'node:crypto';
2
- import { compareVersions, PACKAGE_NAME } from './version.js';
2
+ import { compareVersions, PACKAGE_NAME, REGISTRY } from './version.js';
3
3
  export const RELEASE_DIR = '/usr/local/lib/metro/release';
4
4
  export const PUBLISHER = 'https://github.com/bonustrack/metro/.github/workflows/publish-cli.yml@refs/heads/main';
5
5
  export const ISSUER = 'https://token.actions.githubusercontent.com';
6
- const REGISTRY = 'https://registry.npmjs.org';
7
6
  const PROVENANCE = 'https://slsa.dev/provenance/v1';
8
7
  const IN_TOTO = 'application/vnd.in-toto+json';
9
8
  const VERSION_RE = /^\d{1,9}\.\d{1,9}\.\d{1,9}(?:-[0-9A-Za-z]{1,20}(?:\.[0-9A-Za-z]{1,20}){0,4})?$/;
@@ -27,7 +26,7 @@ function statementOf(bundle) {
27
26
  throw new Error('the provenance statement is unreadable; refused');
28
27
  return statement;
29
28
  }
30
- export function provenanceNames(statement, version, sha512) {
29
+ function provenanceNames(statement, version, sha512) {
31
30
  const subjects = Array.isArray(statement.subject) ? statement.subject : [];
32
31
  return (statement.predicateType === PROVENANCE &&
33
32
  subjects.some((s) => isRecord(s) && s.name === subjectName(version) && isRecord(s.digest) && s.digest.sha512 === sha512));
package/dist/route.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync, readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { agentsDir } from './local.js';
4
- const PROVIDERS = new Set(['bedrock', 'openrouter', 'codex', 'gemini', 'routeway']);
4
+ const PROVIDERS = new Set(['bedrock', 'openrouter', 'codex', 'routeway']);
5
5
  const ANTHROPIC = 'anthropic';
6
6
  const text = (value) => (typeof value === 'string' ? value : '');
7
7
  function routedIn(cfg) {
package/dist/service.js CHANGED
@@ -126,7 +126,8 @@ function systemdPlan(host, serveArgs, runAs) {
126
126
  ],
127
127
  };
128
128
  }
129
- function launchdPlan(host, exec) {
129
+ function launchdPlan(host, serveArgs) {
130
+ const exec = [host.node, host.cli, 'serve', ...serveArgs];
130
131
  const file = join(host.home, 'Library', 'LaunchAgents', `${LABEL}.plist`);
131
132
  const domain = `gui/${String(host.uid)}`;
132
133
  const logFile = join(cacheDir(host), 'metro', 'serve', 'service.log');
@@ -146,13 +147,12 @@ function launchdPlan(host, exec) {
146
147
  };
147
148
  }
148
149
  export function servicePlan(host, serveArgs, runAs) {
149
- const exec = [host.node, host.cli, 'serve', ...serveArgs];
150
150
  if (runAs !== undefined && (host.platform !== 'linux' || host.uid !== 0))
151
151
  throw new Error('--user installs a system service for another user: run it as root on Linux');
152
152
  if (host.platform === 'linux')
153
153
  return systemdPlan(host, serveArgs, runAs);
154
154
  if (host.platform === 'darwin')
155
- return launchdPlan(host, exec);
155
+ return launchdPlan(host, serveArgs);
156
156
  throw new Error(`metro service supports Linux (systemd) and macOS (launchd), not ${host.platform}`);
157
157
  }
158
158
  function splitUser(argv) {
@@ -287,7 +287,7 @@ function realDeps() {
287
287
  const said = [result.stderr, result.stdout, result.error?.message].find((t) => typeof t === 'string' && t.trim() !== '') ?? '';
288
288
  throw new Error(`${command.args.join(' ')} failed (exit ${String(code)}): ${said.trim()}`);
289
289
  }
290
- return { status: code, output: `${result.stdout}${result.stderr}` };
290
+ return { status: code };
291
291
  },
292
292
  running: serveLockedBy,
293
293
  account: lookupAccount,
package/dist/version.js CHANGED
@@ -1,6 +1,6 @@
1
1
  import { readFileSync } from 'node:fs';
2
2
  export const PACKAGE_NAME = '@stage-labs/metro';
3
- const REGISTRY = 'https://registry.npmjs.org';
3
+ export const REGISTRY = 'https://registry.npmjs.org';
4
4
  const TIMEOUT_MS = 15_000;
5
5
  export function parseVersion(raw) {
6
6
  const match = /^(\d+)\.(\d+)\.(\d+)(?:-([0-9A-Za-z.-]+))?$/.exec(raw.trim());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage-labs/metro",
3
- "version": "0.1.0-beta.325",
3
+ "version": "0.1.0-beta.326",
4
4
  "description": "The metro command line: run your agent on this machine, relaying its chat channels and connectors to Claude Code.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -8,7 +8,7 @@ import {
8
8
  } from './automation-types.js';
9
9
  import { isRecord } from './is-record.js';
10
10
 
11
- export type { AutomationOutcome, AutomationRequest, AutomationResolution, AutomationStatus, AutomationState, AutomationUuid } from './automation-types.js';
11
+ export type { AutomationOutcome, AutomationRequest, AutomationResolution, AutomationStatus } from './automation-types.js';
12
12
 
13
13
  function payload<T extends { uuid: string }>(row: unknown, parse: (value: unknown) => T): T {
14
14
  try {
@@ -5,7 +5,7 @@ export const AUTOMATION_PROMPT_MAX = 64 * 1024;
5
5
  const PROMPT_JSON_MAX = 128 * 1024 + 2;
6
6
  export const AUTOMATION_REQUEST_MAX = 512;
7
7
  export const AUTOMATION_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
8
- export const AUTOMATION_ROUTINE_RE = /^[a-z][a-z0-9-]{0,63}$/;
8
+ const AUTOMATION_ROUTINE_RE = /^[a-z][a-z0-9-]{0,63}$/;
9
9
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/;
10
10
  const STATES = ['dispatched', 'consumed', 'awaiting-completion', 'completed', 'failed', 'interrupted', 'coalesced', 'cancelled'] as const;
11
11
  const DETAILS = ['sdk-completed', 'sdk-failed', 'runner-interrupted', 'newer-slot', 'owner-cancelled', 'worker-blocked', 'worker-completed'] as const;
@@ -29,7 +29,7 @@ const ERRORS = {
29
29
 
30
30
  export type AutomationUuid = `${string}-${string}-${string}-${string}-${string}`;
31
31
  export type AutomationState = (typeof STATES)[number];
32
- export type AutomationDetail = (typeof DETAILS)[number];
32
+ type AutomationDetail = (typeof DETAILS)[number];
33
33
  export type AutomationOutcome = 'completed' | 'blocked';
34
34
  export type AutomationErrorCode = keyof typeof ERRORS;
35
35
 
@@ -2,8 +2,6 @@ export type Line = string & { readonly __line: unique symbol };
2
2
  export const asLine = (s: string): Line => s as Line;
3
3
 
4
4
  const PREFIX = 'metro://';
5
- const build = (station: string, ...seg: (string | number)[]): Line =>
6
- asLine(`${PREFIX}${station}/${seg.map(String).join('/')}`);
7
5
 
8
6
  function parseAccountScoped(
9
7
  line: Line | string,
@@ -42,11 +40,7 @@ function parseTelegramLine(
42
40
  }
43
41
 
44
42
  export const Line = {
45
- claude: (orgId: string, sessionId: string): Line =>
46
- build('claude', orgId, sessionId),
47
- webhook: (endpointId: string): Line => build('webhook', endpointId),
48
- user: (station: string, id: string | number): Line =>
49
- build(station, 'user', id),
43
+ webhook: (endpointId: string): Line => asLine(`${PREFIX}webhook/${endpointId}`),
50
44
 
51
45
  parse(line: Line | string): { station: string; path: string[] } | null {
52
46
  if (!line.startsWith(PREFIX)) return null;
@@ -68,7 +62,7 @@ export const Line = {
68
62
  parseDiscord: (line: Line | string) =>
69
63
  parseAccountScoped(line, 'discord-bot', isSnowflake),
70
64
 
71
- parseTelegram: (line: Line | string) => parseTelegramLine(line),
65
+ parseTelegram: parseTelegramLine,
72
66
 
73
67
  parseThreema: (line: Line | string) =>
74
68
  parseAccountScoped(line, 'threema', isThreemaResource),
@@ -5,12 +5,6 @@ import { coerceErrorInfo, type TrainErrorInfo } from './train-error.js';
5
5
  import type { StructuredEvent } from './events.js';
6
6
  import type { CallMsg } from './stations/station-runtime.js';
7
7
 
8
- export {
9
- TrainError,
10
- serializeTrainError,
11
- type TrainErrorInfo,
12
- } from './train-error.js';
13
-
14
8
  const CALL_TIMEOUT_MS = 60_000;
15
9
 
16
10
  export interface Pending {
@@ -15,7 +15,7 @@ export interface SkillSource {
15
15
  folder: string;
16
16
  }
17
17
 
18
- export interface SkillFile {
18
+ interface SkillFile {
19
19
  path: string;
20
20
  size: number;
21
21
  sha256: string;
@@ -1,6 +1,6 @@
1
1
  import { isRecord } from '../is-record.js';
2
2
 
3
- export const ACCOUNT_STATES = ['connected', 'connecting', 'logged_out', 'disconnected'] as const;
3
+ const ACCOUNT_STATES = ['connected', 'connecting', 'logged_out', 'disconnected'] as const;
4
4
 
5
5
  export type AccountState = (typeof ACCOUNT_STATES)[number];
6
6
 
@@ -1,3 +1,7 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdir, open, rename, rm, writeFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+
1
5
  export const attachDir = (): string =>
2
6
  process.env.METRO_XMTP_ATTACH_DIR ??
3
7
  `${process.env.HOME}/.cache/metro/messenger-uploads`;
@@ -89,14 +93,9 @@ const cachePathFor = async (
89
93
  index: number,
90
94
  meta: { mime?: string; name?: string },
91
95
  ): Promise<string> => {
92
- const { mkdir } = await import('node:fs/promises');
93
- const nodePath = await import('node:path');
94
96
  const dir = attachDir();
95
97
  await mkdir(dir, { recursive: true });
96
- return nodePath.join(
97
- dir,
98
- cacheFileName(messageId, index, meta.name, meta.mime),
99
- );
98
+ return join(dir, cacheFileName(messageId, index, meta.name, meta.mime));
100
99
  };
101
100
 
102
101
  export const saveBufferToCache = async (
@@ -106,8 +105,6 @@ export const saveBufferToCache = async (
106
105
  meta: { mime?: string; name?: string },
107
106
  ): Promise<SavedAttachment> => {
108
107
  assertAttachmentSize(data.length);
109
- const { writeFile, rename, rm } = await import('node:fs/promises');
110
- const { randomUUID } = await import('node:crypto');
111
108
  const path = await cachePathFor(messageId, index, meta);
112
109
  const part = `${await cachePathFor(randomUUID(), index, meta)}.part`;
113
110
  try {
@@ -125,7 +122,6 @@ export const saveStreamToCache = async (
125
122
  index: number,
126
123
  meta: { mime?: string; name?: string },
127
124
  ): Promise<SavedAttachment> => {
128
- const { open, rename, rm } = await import('node:fs/promises');
129
125
  const path = await cachePathFor(messageId, index, meta);
130
126
  const part = `${path}.part`;
131
127
  const handle = await open(part, 'w');
@@ -14,7 +14,7 @@ export interface ChannelEntry {
14
14
  kind: 'direct' | 'group' | 'channel' | 'thread';
15
15
  }
16
16
 
17
- export interface ChannelCapability {
17
+ interface ChannelCapability {
18
18
  supported: boolean;
19
19
  complete: boolean;
20
20
  source: 'remote' | 'local' | 'mixed' | 'unsupported';
@@ -1,4 +1,4 @@
1
- export const MAX_TEXT_CHARS = 20_000;
1
+ const MAX_TEXT_CHARS = 20_000;
2
2
 
3
3
  const ENTITIES: Record<string, string> = {
4
4
  amp: '&',
@@ -52,9 +52,9 @@ export function tidy(text: string): string {
52
52
  .trim();
53
53
  }
54
54
 
55
- export function capText(text: string, max = MAX_TEXT_CHARS): string {
56
- if (text.length <= max) return text;
57
- return `${text.slice(0, max)}\n[... ${String(text.length - max)} more characters]`;
55
+ export function capText(text: string): string {
56
+ if (text.length <= MAX_TEXT_CHARS) return text;
57
+ return `${text.slice(0, MAX_TEXT_CHARS)}\n[... ${String(text.length - MAX_TEXT_CHARS)} more characters]`;
58
58
  }
59
59
 
60
60
  export function bodyText(body: { contentType?: unknown; content?: unknown } | undefined): string {
@@ -1,4 +1,5 @@
1
1
  import { mintId } from '../ids.js';
2
+ import { isRecord } from '../is-record.js';
2
3
  import { Line } from '../lines.js';
3
4
  import { TrainError } from '../train-error.js';
4
5
  import { capText } from './mail-text.js';
@@ -47,7 +48,7 @@ export interface ReadQuery {
47
48
  limit: number;
48
49
  }
49
50
 
50
- export const DEFAULT_LIMIT = 20;
51
+ const DEFAULT_LIMIT = 20;
51
52
  export const MAX_LIMIT = 50;
52
53
  const SEEN_MAX = 500;
53
54
  const SUBJECT_MAX = 255;
@@ -160,7 +161,7 @@ export function subjectOf(station: MailStation, subject: string, text: string):
160
161
 
161
162
  export const filesOf = (raw: unknown): OutgoingFile[] =>
162
163
  (Array.isArray(raw) ? raw : [])
163
- .map((a) => (a ?? {}) as Record<string, unknown>)
164
+ .filter(isRecord)
164
165
  .filter((a) => typeof a.path === 'string' && a.path !== '')
165
166
  .map((a) => ({
166
167
  path: String(a.path),
@@ -1,6 +1,6 @@
1
1
  type Args = Record<string, unknown>;
2
2
 
3
- export interface Normalized {
3
+ interface Normalized {
4
4
  action: string;
5
5
  args: Args;
6
6
  }
@@ -36,7 +36,7 @@ export function pkcePair(verifier = randomBytes(32).toString('base64url')): Pkce
36
36
  return { verifier, challenge: createHash('sha256').update(verifier).digest('base64url') };
37
37
  }
38
38
 
39
- export const newState = (): string => randomBytes(24).toString('base64url');
39
+ const newState = (): string => randomBytes(24).toString('base64url');
40
40
 
41
41
  export function parseMailbox(value: unknown): string | null {
42
42
  const mailbox = (typeof value === 'string' ? value.trim() : '').toLowerCase();
@@ -3,8 +3,8 @@ import { TrainError } from '../train-error.js';
3
3
  export const PROFILE_FIELDS = ['name', 'bio', 'avatar'] as const;
4
4
  export type ProfileField = (typeof PROFILE_FIELDS)[number];
5
5
 
6
- export const NAME_MAX = 64;
7
- export const BIO_MAX = 512;
6
+ const NAME_MAX = 64;
7
+ const BIO_MAX = 512;
8
8
 
9
9
  export interface ProfileAvatar {
10
10
  path: string;
@@ -82,7 +82,7 @@ export interface MemberOutcome {
82
82
  reason?: string;
83
83
  }
84
84
 
85
- export interface GroupCapability {
85
+ interface GroupCapability {
86
86
  supported: boolean;
87
87
  reason?: string;
88
88
  }
@@ -98,7 +98,7 @@ export interface GroupResult {
98
98
 
99
99
  export type ReadFilter = 'account' | 'query' | 'from' | 'until' | 'unread_only' | 'message_id';
100
100
 
101
- export type AttachmentMode = 'canonical' | 'native' | 'none';
101
+ type AttachmentMode = 'canonical' | 'native' | 'none';
102
102
 
103
103
  export interface Station {
104
104
  name: string;
@@ -1,3 +1,5 @@
1
+ import { isRecord } from './is-record.js';
2
+
1
3
  export interface TrainErrorInfo {
2
4
  code: string;
3
5
  message: string;
@@ -41,10 +43,8 @@ export function serializeTrainError(err: unknown): {
41
43
  return { error: err instanceof Error ? err.message : String(err) };
42
44
  }
43
45
 
44
- export function coerceErrorInfo(v: unknown): TrainErrorInfo | undefined {
45
- if (!v || typeof v !== 'object') return undefined;
46
- const o = v as Record<string, unknown>;
47
- if (typeof o.code !== 'string' || typeof o.message !== 'string')
46
+ export function coerceErrorInfo(o: unknown): TrainErrorInfo | undefined {
47
+ if (!isRecord(o) || typeof o.code !== 'string' || typeof o.message !== 'string')
48
48
  return undefined;
49
49
  const info: TrainErrorInfo = { code: o.code, message: o.message };
50
50
  if (typeof o.retryable === 'boolean') info.retryable = o.retryable;
@@ -69,7 +69,8 @@ function parseFile(root: string, path: string, out: Buffer): FilesAnswer {
69
69
  return { root, path, kind: 'file', bytes, modifiedAt: stamp(Number(mtime)), ...preview(out.subarray(end + 1), bytes) };
70
70
  }
71
71
 
72
- function readAsAgent(user: AgentUser, path: string): FilesAnswer {
72
+ export function readAgentPath(user: AgentUser | null, path: string): FilesAnswer {
73
+ if (user === null) throw new ApiError('Claude Code does not run as its own user on this machine, so there is no agent folder to show', 409);
73
74
  const target = join(user.home, ...segmentsOf(path));
74
75
  const [file, argv] = asUser(user, 'sh', ['-c', SCRIPT, 'metro', target, String(PREVIEW_BYTES + 1)]);
75
76
  const run = spawnSync(file, argv, { stdio: ['ignore', 'pipe', 'pipe'], maxBuffer: 64 * 1024 * 1024, timeout: 20_000 });
@@ -82,8 +83,3 @@ export function answerOf(root: string, path: string, out: Buffer): FilesAnswer {
82
83
  const body = out.subarray(2);
83
84
  return out.subarray(0, 1).toString() === 'D' ? folderAnswer(root, path, parseListing(body)) : parseFile(root, path, body);
84
85
  }
85
-
86
- export function readAgentPath(user: AgentUser | null, path: string): FilesAnswer {
87
- if (user === null) throw new ApiError('Claude Code does not run as its own user on this machine, so there is no agent folder to show', 409);
88
- return readAsAgent(user, path);
89
- }
@@ -1,6 +1,7 @@
1
1
  import { spawnSync } from 'node:child_process';
2
2
  import { createHash } from 'node:crypto';
3
3
  import { isRecord } from '@metro-labs/core/is-record';
4
+ import { stringOf } from '@metro-labs/http/api-http';
4
5
  import type { AgentUser } from './user.js';
5
6
  import { helperRun, type RunResult } from './unit-files.js';
6
7
 
@@ -64,22 +65,21 @@ function listTimers(runner: Runner): Record<string, unknown>[] {
64
65
  }
65
66
  }
66
67
 
67
- const text = (v: unknown): string => (typeof v === 'string' ? v : '');
68
68
  const orRoot = (user: string | undefined): string => (user === undefined || user === '' ? 'root' : user);
69
69
 
70
70
  function timerJob(runner: Runner, t: Record<string, unknown>): ScheduledJob | null {
71
- const unit = text(t.unit);
72
- const service = text(t.activates);
71
+ const unit = stringOf(t.unit);
72
+ const service = stringOf(t.activates);
73
73
  if (unit === '' || service === '' || OWN_UNITS.test(service)) return null;
74
74
  const timer = showProps(runner, unit, ['FragmentPath', 'TimersCalendar', 'TimersMonotonic']);
75
- if (!text(timer.FragmentPath).startsWith(`${UNIT_DIR}/`)) return null;
75
+ if (!stringOf(timer.FragmentPath).startsWith(`${UNIT_DIR}/`)) return null;
76
76
  const svc = showProps(runner, service, ['ExecStart', 'User', 'Result']);
77
- const command = execArgv(text(svc.ExecStart)).join(' ');
77
+ const command = execArgv(stringOf(svc.ExecStart)).join(' ');
78
78
  return {
79
79
  id: `timer:${unit}`,
80
80
  kind: 'timer',
81
81
  name: unit.replace(/\.timer$/, ''),
82
- schedule: calendarOf(`${text(timer.TimersCalendar)} ${text(timer.TimersMonotonic)}`),
82
+ schedule: calendarOf(`${stringOf(timer.TimersCalendar)} ${stringOf(timer.TimersMonotonic)}`),
83
83
  command,
84
84
  runsAs: orRoot(svc.User),
85
85
  next: stamp(t.next),
@@ -1,6 +1,4 @@
1
1
  import { spawnSync } from 'node:child_process';
2
- import { existsSync } from 'node:fs';
3
- import { homedir } from 'node:os';
4
2
  import { join } from 'node:path';
5
3
  import { runningAsMetro } from '../metro-user/privilege.js';
6
4
 
@@ -74,28 +72,6 @@ export const agentCommand = (command: readonly string[], extra: Record<string, s
74
72
  return [bin, ...argv];
75
73
  };
76
74
 
77
- const SDK_PACKAGE = '@anthropic-ai/claude-agent-sdk';
78
-
79
- export const CLAUDE_ENV: Readonly<Record<string, string>> = { DISABLE_AUTOUPDATER: '1' };
80
-
81
- let musl: boolean | null = null;
82
-
83
- function muslFirst(): boolean {
84
- if (process.platform !== 'linux') return false;
85
- if (musl === null) {
86
- const report = process.report.getReport() as { header?: { glibcVersionRuntime?: string } };
87
- musl = report.header?.glibcVersionRuntime === undefined;
88
- }
89
- return musl;
90
- }
91
-
92
- export function claudeBin(user = agentUser()): string {
93
- const name = `${SDK_PACKAGE}-${process.platform}-${process.arch}`;
94
- const packages = muslFirst() ? [`${name}-musl`, name] : [name, `${name}-musl`];
95
- const [first = '', other = ''] = packages.map((pkg) => join(user?.home ?? homedir(), '.metro', 'sdk-runner', 'node_modules', pkg, 'claude'));
96
- return existsSync(first) || !existsSync(other) ? first : other;
97
- }
98
-
99
75
  export const claudeHome = (user = agentUser()): string | null => (user === null ? null : user.home);
100
76
 
101
77
  export const agentViewDir = (user: AgentUser): string => join(user.home, '.metro', 'agents');
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync } from 'node:fs';
1
+ import { readFileSync } from 'node:fs';
2
2
  import { join } from 'node:path';
3
3
  import { errMsg, log } from '@metro-labs/core/log';
4
4
  import { isRecord } from '@metro-labs/core/is-record';
@@ -45,7 +45,7 @@ export function viewFiles(dir = agentsDir(), model: () => string | null = () =>
45
45
  ['agent.json', agentFile(dir)],
46
46
  ['model.json', model()],
47
47
  ]);
48
- for (const name of COPIED) files.set(name, existsSync(join(dir, name)) ? readText(join(dir, name)) : null);
48
+ for (const name of COPIED) files.set(name, readText(join(dir, name)));
49
49
  for (const name of NEVER_COPIED) files.set(name, null);
50
50
  return files;
51
51
  }
@@ -58,7 +58,7 @@ export function syncAgentView(user: AgentUser | null = agentUser(), dir = agents
58
58
  let changed = 0;
59
59
  for (const [name, text] of viewFiles(dir)) {
60
60
  const path = join(target, name);
61
- if (written.has(path) && written.get(path) === text) continue;
61
+ if (written.get(path) === text) continue;
62
62
  if (text === null) removeHome(path, false, user);
63
63
  else writeHomeText(path, text, 0o600, undefined, user);
64
64
  written.set(path, text);
@@ -6,13 +6,12 @@ import { ApiError } from '@metro-labs/http/api-error';
6
6
  import { isRecord } from '@metro-labs/core/is-record';
7
7
  import { parseId } from '@metro-labs/core/ids';
8
8
  import type { LoadedAccount, LoadedAgent } from '../stations/materialize.js';
9
- import { STATIONS } from '@metro-labs/core/station-names';
10
9
  import { approversOf } from './files.js';
10
+ import { isStationName } from './account-attach.js';
11
11
 
12
12
  const AGENTS = '/api/agents';
13
13
  const RESTORE_PATH = `${AGENTS}/restore`;
14
14
  const BUNDLE_MAX = 2 * 1024 * 1024;
15
- const STATION_NAMES = new Set<string>(STATIONS);
16
15
 
17
16
  export interface AgentBundle {
18
17
  version: 1;
@@ -50,7 +49,7 @@ function parseMode(raw: unknown): ImportMode {
50
49
  const bad = (what: string): ApiError => new ApiError(`bundle: ${what}`, 400);
51
50
 
52
51
  function stationOf(raw: unknown): LoadedAccount {
53
- if (!isRecord(raw) || typeof raw.station !== 'string' || !STATION_NAMES.has(raw.station))
52
+ if (!isRecord(raw) || !isStationName(raw.station))
54
53
  throw bad('a station is not a known station');
55
54
  if (typeof raw.id !== 'string' || !isRecord(raw.config)) throw bad('a station has no id or config');
56
55
  const allowlist = Array.isArray(raw.allowlist)
@@ -58,7 +57,7 @@ function stationOf(raw: unknown): LoadedAccount {
58
57
  : null;
59
58
  const policy = parsePolicy(raw.policy, `bundle ${raw.station}/${raw.id}`);
60
59
  return {
61
- station: raw.station as LoadedAccount['station'],
60
+ station: raw.station,
62
61
  id: raw.id,
63
62
  allowlist,
64
63
  ...approversOf(raw.approvers),
@@ -33,8 +33,6 @@ interface Stored {
33
33
  file: AgentFile;
34
34
  }
35
35
 
36
- const missing = (): AgentAdminError => new AgentAdminError('no such agent', 404);
37
-
38
36
  function parseOwner(raw: string): string | null {
39
37
  const text = raw.trim();
40
38
  return isOrganizationId(text) ? text : null;
@@ -66,7 +64,7 @@ function save(stored: Stored): void {
66
64
 
67
65
  function agentOrThrow(id: string, dir: string): Stored {
68
66
  const found = storedAgents(dir).find((s) => s.file.id === id);
69
- if (found === undefined) throw missing();
67
+ if (found === undefined) throw new AgentAdminError('no such agent', 404);
70
68
  return found;
71
69
  }
72
70
 
@@ -84,9 +82,8 @@ function freshId(taken: Set<string>): string {
84
82
 
85
83
  export async function localCreateAgent(rawName?: string, dir = agentsDir()): Promise<CreatedAgent> {
86
84
  const name = rawName === undefined ? null : normalizeAgentName(rawName);
87
- const existing = storedAgents(dir);
88
- if (existing.length > 0) throw new AgentAdminError('this box already has its agent', 409);
89
- const id = freshId(new Set());
85
+ if (storedAgents(dir).length > 0) throw new AgentAdminError('this box already has its agent', 409);
86
+ const id = newId();
90
87
  const key = newApiKey();
91
88
  ensureSecureDir(dir);
92
89
  save({
@@ -228,9 +225,7 @@ export function localPairAgain(agentId: string, station: StationName, accountId:
228
225
  }
229
226
 
230
227
  export function readLocalAgentFile(agentId: string, dir = agentsDir()): AgentFile {
231
- const found = storedAgents(dir).find((s) => s.file.id === agentId);
232
- if (found === undefined) throw missing();
233
- return found.file;
228
+ return agentOrThrow(agentId, dir).file;
234
229
  }
235
230
 
236
231
  const UNIQUE_CREDENTIALS: Record<string, string> = {
@@ -2,15 +2,14 @@ import { existsSync, readFileSync } from 'node:fs';
2
2
  import { homedir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import { AGENT_NAME_RE, ID_RE } from '@metro-labs/core/ids';
5
- import { STATIONS, type StationName } from '@metro-labs/core/station-names';
6
5
  import type { LoadedAccount, LoadedAgent, StationSource } from '../stations/materialize.js';
7
6
  import { isRecord } from '@metro-labs/core/is-record';
8
7
  import { parsePolicy } from '../policy/policy.js';
8
+ import { isStationName } from './account-attach.js';
9
9
 
10
10
  const AGENT_FILE = 'agent.json';
11
11
 
12
12
  const KEY_RE = /^[A-Za-z0-9_-]{16,128}$/;
13
- const STATION_NAMES = new Set<string>(STATIONS);
14
13
 
15
14
  export class AgentFileError extends Error {}
16
15
 
@@ -35,10 +34,6 @@ function fail(path: string, reason: string): never {
35
34
  throw new AgentFileError(`${path}: ${reason}`);
36
35
  }
37
36
 
38
- function isStationName(value: unknown): value is StationName {
39
- return typeof value === 'string' && STATION_NAMES.has(value);
40
- }
41
-
42
37
  function allowlistOf(raw: unknown, path: string, where: string): string[] | null {
43
38
  if (raw === null || raw === undefined) return null;
44
39
  if (!Array.isArray(raw)) fail(path, `${where}.allowlist is not a list of strings`);