@stage-labs/metro 0.1.0-beta.38 → 0.1.0-beta.39

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stage-labs/metro",
3
- "version": "0.1.0-beta.38",
3
+ "version": "0.1.0-beta.39",
4
4
  "description": "The metro command line. Sign in once per machine, then hand your MCP connector list to Claude Code without the credentials touching disk, argv or shell history.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,4 +1,8 @@
1
+ import { mkdirSync } from 'node:fs';
2
+ import { join } from 'node:path';
1
3
  import { ApiError } from './api-error.js';
4
+ import { configDir } from './paths.js';
5
+ import { writeSecure } from './secure-fs.js';
2
6
  import { carriesSecretsSafely, httpSource } from './runtime-source.js';
3
7
  import type { LoadedAgent } from '../db/materialize.js';
4
8
 
@@ -49,6 +53,14 @@ async function claim(base: string, code: string, label: string): Promise<string>
49
53
  return token;
50
54
  }
51
55
 
56
+ export function storeRunToken(agentId: string, token: string, url: string): string {
57
+ const dir = configDir();
58
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
59
+ const path = join(dir, `runtime-${agentId}.json`);
60
+ writeSecure(path, `${JSON.stringify({ token, url }, null, 2)}\n`);
61
+ return path;
62
+ }
63
+
52
64
  export async function fetchAgentWithCode(
53
65
  code: string,
54
66
  label: string,
@@ -59,5 +71,6 @@ export async function fetchAgentWithCode(
59
71
  const agent = agents[0];
60
72
  if (agent === undefined || agents.length !== 1)
61
73
  throw new ApiError('metro returned no agent for that code', 502);
74
+ storeRunToken(agent.id, token, base);
62
75
  return agent;
63
76
  }
@@ -17,9 +17,10 @@ export const STATE_DIR =
17
17
  process.env.METRO_STATE_DIR ?? join(homedir(), '.cache', 'metro');
18
18
  mkdirSync(STATE_DIR, { recursive: true });
19
19
 
20
- export const CONFIG_DIR =
20
+ export const configDir = (): string =>
21
21
  process.env.METRO_CONFIG_DIR ??
22
22
  join(process.env.XDG_CONFIG_HOME ?? join(homedir(), '.config'), 'metro');
23
+ export const CONFIG_DIR = configDir();
23
24
  export const CONFIG_ENV_FILE = join(CONFIG_DIR, '.env');
24
25
 
25
26
  export const TRAINS_ENV_FILE = join(homedir(), '.metro', '.env');
@@ -45,6 +45,19 @@ export function agentForLine(line: string): string | undefined {
45
45
  return id === undefined ? undefined : agentNames[id];
46
46
  }
47
47
 
48
+ export interface KnownAccount {
49
+ station: string;
50
+ id: string;
51
+ agentId: string;
52
+ }
53
+
54
+ export function knownAccounts(): KnownAccount[] {
55
+ return Object.entries(agentMap).flatMap(([key, agentId]) => {
56
+ const cut = key.indexOf('/');
57
+ return cut === -1 ? [] : [{ station: key.slice(0, cut), id: key.slice(cut + 1), agentId }];
58
+ });
59
+ }
60
+
48
61
  export function stationAgentIds(station: string): string[] {
49
62
  const prefix = `${station}/`;
50
63
  return Object.entries(agentMap)
@@ -108,24 +108,6 @@ export function unmovableStations(list: LoadedAccount[]): StationName[] {
108
108
  ];
109
109
  }
110
110
 
111
- export interface StationRecord {
112
- id: string;
113
- agentId: string;
114
- station: StationName;
115
- }
116
-
117
- export async function listAllStations(): Promise<StationRecord[]> {
118
- if (!databaseUrl()) return [];
119
- const rows = await getDb()
120
- .select({
121
- id: stations.id,
122
- agentId: stations.agentId,
123
- station: stations.station,
124
- })
125
- .from(stations);
126
- return rows;
127
- }
128
-
129
111
  export async function loadAllStationsFor(
130
112
  agentId: string,
131
113
  ): Promise<LoadedAccount[]> {
@@ -5,12 +5,8 @@ import {
5
5
  } from '../stations/registry.js';
6
6
  import { listEndpoints } from '../daemon/tunnel.js';
7
7
  import { hookUrl } from '../stations/attach.js';
8
- import { agentIdForAccount } from '../db/agent-map.js';
9
- import {
10
- listAllStations,
11
- stationRunsHere,
12
- type StationRecord,
13
- } from '../db/materialize.js';
8
+ import { agentIdForAccount, knownAccounts, type KnownAccount } from '../db/agent-map.js';
9
+ import { stationRunsHere } from '../db/materialize.js';
14
10
  import { type StationName } from '../db/schema.js';
15
11
 
16
12
  const accountId = (acc: unknown): string | undefined => {
@@ -99,7 +95,7 @@ async function liveAccounts(
99
95
  function mergeKnown(
100
96
  station: string,
101
97
  live: unknown[],
102
- known: StationRecord[],
98
+ known: KnownAccount[],
103
99
  ): unknown[] {
104
100
  const seen = new Set(
105
101
  live.flatMap((row) => {
@@ -116,12 +112,12 @@ function mergeKnown(
116
112
  async function loadStations(): Promise<ScopedAccounts> {
117
113
  const unavailable: string[] = [];
118
114
  const accounts: Record<string, unknown[]> = {};
119
- const known = await listAllStations();
115
+ const known = knownAccounts();
120
116
  await Promise.all(
121
117
  accountStationNames().map(async (station) => {
122
118
  const { rows, reachable } = await liveAccounts(station);
123
119
  if (!reachable) unavailable.push(station);
124
- accounts[station] = mergeKnown(station, rows, known);
120
+ accounts[station] = hasTrain(station) ? mergeKnown(station, rows, known) : rows;
125
121
  }),
126
122
  );
127
123
  return { accounts, unavailable };
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "0.1.0-beta.38"
2
+ "version": "0.1.0-beta.39"
3
3
  }