@stage-labs/metro 0.1.0-beta.31 → 0.1.0-beta.33

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 (52) hide show
  1. package/dist/cli.js +7 -0
  2. package/dist/runtime.js +4 -1
  3. package/dist/serve.js +73 -0
  4. package/package.json +1 -1
  5. package/runtime/node_modules/@metro-labs/mcp/package.json +1 -0
  6. package/runtime/node_modules/@metro-labs/mcp/src/daemon/account-api.ts +5 -5
  7. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-api.ts +16 -15
  8. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-connector-api.ts +9 -9
  9. package/runtime/node_modules/@metro-labs/mcp/src/daemon/agent-pair.ts +1 -1
  10. package/runtime/node_modules/@metro-labs/mcp/src/daemon/api-http.ts +8 -7
  11. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-serve.ts +9 -5
  12. package/runtime/node_modules/@metro-labs/mcp/src/daemon/attach-session.ts +2 -2
  13. package/runtime/node_modules/@metro-labs/mcp/src/daemon/boot.ts +124 -73
  14. package/runtime/node_modules/@metro-labs/mcp/src/daemon/cli-pair-api.ts +16 -7
  15. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connect-hint.ts +33 -0
  16. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-api.ts +14 -14
  17. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth-routes.ts +8 -8
  18. package/runtime/node_modules/@metro-labs/mcp/src/daemon/connector-oauth.ts +3 -3
  19. package/runtime/node_modules/@metro-labs/mcp/src/daemon/http.ts +12 -2
  20. package/runtime/node_modules/@metro-labs/mcp/src/daemon/identity.ts +11 -4
  21. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-mode.ts +144 -0
  22. package/runtime/node_modules/@metro-labs/mcp/src/daemon/local-secret.ts +21 -0
  23. package/runtime/node_modules/@metro-labs/mcp/src/daemon/mode-api.ts +31 -0
  24. package/runtime/node_modules/@metro-labs/mcp/src/daemon/oauth-pending.ts +1 -1
  25. package/runtime/node_modules/@metro-labs/mcp/src/daemon/paths.ts +10 -0
  26. package/runtime/node_modules/@metro-labs/mcp/src/daemon/project-api.ts +17 -17
  27. package/runtime/node_modules/@metro-labs/mcp/src/daemon/run-api.ts +2 -2
  28. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-api.ts +1 -1
  29. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-apis.ts +4 -0
  30. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session-config.ts +27 -0
  31. package/runtime/node_modules/@metro-labs/mcp/src/daemon/session.ts +9 -9
  32. package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-auth.ts +82 -0
  33. package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-nonces.ts +43 -0
  34. package/runtime/node_modules/@metro-labs/mcp/src/daemon/siwe-routes.ts +89 -0
  35. package/runtime/node_modules/@metro-labs/mcp/src/daemon/supervisor.ts +2 -5
  36. package/runtime/node_modules/@metro-labs/mcp/src/daemon/tunnel.ts +120 -32
  37. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-admin.ts +15 -16
  38. package/runtime/node_modules/@metro-labs/mcp/src/db/agent-connectors.ts +9 -9
  39. package/runtime/node_modules/@metro-labs/mcp/src/db/connectors.ts +31 -31
  40. package/runtime/node_modules/@metro-labs/mcp/src/db/file-admin.ts +250 -0
  41. package/runtime/node_modules/@metro-labs/mcp/src/db/file-source.ts +152 -0
  42. package/runtime/node_modules/@metro-labs/mcp/src/db/ids.ts +2 -0
  43. package/runtime/node_modules/@metro-labs/mcp/src/db/materialize.ts +16 -7
  44. package/runtime/node_modules/@metro-labs/mcp/src/db/projects.ts +48 -39
  45. package/runtime/node_modules/@metro-labs/mcp/src/db/runtime-admin.ts +7 -7
  46. package/runtime/node_modules/@metro-labs/mcp/src/db/schema.ts +2 -1
  47. package/runtime/node_modules/@metro-labs/mcp/src/db/users.ts +22 -12
  48. package/runtime/node_modules/@metro-labs/mcp/src/mcp/request-identity.ts +4 -4
  49. package/runtime/node_modules/@metro-labs/mcp/src/mcp/session-route.ts +1 -1
  50. package/runtime/runtime.json +1 -1
  51. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-auth.ts +0 -164
  52. package/runtime/node_modules/@metro-labs/mcp/src/daemon/google-oauth.ts +0 -208
@@ -4,15 +4,27 @@ import { type Server } from 'node:http';
4
4
  import { selfLine, userSelf } from './events.js';
5
5
  import { setTrainCallBackend } from './train-call.js';
6
6
  import { errMsg, log, logFatalSync } from './log.js';
7
- import { acquireLock, loadMetroEnv, STATE_DIR } from './paths.js';
7
+ import {
8
+ acquireLock,
9
+ isLocalMode,
10
+ loadMetroEnv,
11
+ STATE_DIR,
12
+ trainsDir,
13
+ } from './paths.js';
8
14
  import { installCrashGuard, markDaemonReady } from './crash-guard.js';
9
15
  import {
10
16
  loadTunnelConfig,
11
17
  Tunnel,
18
+ tunnelConfigFromEnv,
12
19
  warnOnLegacyWebhooks,
13
20
  webhookPort,
14
21
  } from './tunnel.js';
15
- import { TrainSupervisor, TRAINS_DIR } from './supervisor.js';
22
+ import {
23
+ localConnectHint,
24
+ publicConnectHint,
25
+ tunnelPendingHint,
26
+ } from './connect-hint.js';
27
+ import { TrainSupervisor } from './supervisor.js';
16
28
  import {
17
29
  makeEmit,
18
30
  startWebhookServer,
@@ -27,10 +39,15 @@ import {
27
39
  } from '../db/runtimes.js';
28
40
  import {
29
41
  blockedStationsFor,
30
- mintAgentCodeForEmail,
31
- releaseRuntimeForEmail,
42
+ mintAgentCodeForUser,
43
+ releaseRuntimeForUser,
32
44
  } from '../db/runtime-admin.js';
33
45
  import { loadAgentForRuntime, localAgentKey } from '../db/materialize.js';
46
+ import { fileSource } from '../db/file-source.js';
47
+ import { ensureLocalSessionSecret } from './local-secret.js';
48
+ import { localSessionApis } from './local-mode.js';
49
+ import type { ModeInfo } from './mode-api.js';
50
+ import type { SessionApis } from './session-apis.js';
34
51
  import {
35
52
  agentLiveness,
36
53
  closeAgentSession,
@@ -48,45 +65,46 @@ import {
48
65
  materializeFromDb,
49
66
  reloadAccountsFromDb,
50
67
  reloadFrom,
68
+ type ReloadedStations,
51
69
  } from '../db/materialize.js';
52
70
  import {
53
71
  httpSource,
54
72
  runtimeConfigFromEnv,
55
73
  startRuntimePoller,
56
74
  } from './runtime-source.js';
57
- import { createAgentForEmail, deleteAgentForEmail, listAgentsForEmail, ownedAgentOrThrow, resetAgentKeyForEmail, type ResetAgentKey,} from '../db/agent-admin.js';
75
+ import { createAgentForUser, deleteAgentForUser, listAgentsForUser, ownedAgentOrThrow, resetAgentKeyForUser, type ResetAgentKey,} from '../db/agent-admin.js';
58
76
  import {
59
77
  attachAccountToAgent,
60
78
  detachAccountFromAgent,
61
79
  } from '../db/account-attach.js';
62
80
  import {
63
- createConnectorForEmail,
64
- createPendingConnectorForEmail,
65
- deleteConnectorForEmail,
66
- disconnectConnectorForEmail,
81
+ createConnectorForUser,
82
+ createPendingConnectorForUser,
83
+ deleteConnectorForUser,
84
+ disconnectConnectorForUser,
67
85
  connectorNamesByIds,
68
- getConnectorForEmail,
69
- listConnectorsForEmail,
70
- renameConnectorForEmail,
71
- reconnectConnectorForEmail,
72
- verifyConnectorForEmail,
86
+ getConnectorForUser,
87
+ listConnectorsForUser,
88
+ renameConnectorForUser,
89
+ reconnectConnectorForUser,
90
+ verifyConnectorForUser,
73
91
  } from '../db/connectors.js';
74
92
  import { relayTarget } from '../db/connector-relay.js';
75
93
  import {
76
- addMemberForEmail,
77
- createProjectForEmail,
78
- deleteProjectForEmail,
79
- listMembersForEmail,
80
- listProjectsForEmail,
81
- removeMemberForEmail,
82
- renameProjectForEmail,
83
- setMemberRoleForEmail,
94
+ addMemberForUser,
95
+ createProjectForUser,
96
+ deleteProjectForUser,
97
+ listMembersForUser,
98
+ listProjectsForUser,
99
+ removeMemberForUser,
100
+ renameProjectForUser,
101
+ setMemberRoleForUser,
84
102
  } from '../db/projects.js';
85
103
  import {
86
- addConnectorToAgentForEmail,
87
- agentConnectorsForEmail,
104
+ addConnectorToAgentForUser,
105
+ agentConnectorsForUser,
88
106
  connectorIdsByAgent,
89
- removeConnectorFromAgentForEmail,
107
+ removeConnectorFromAgentForUser,
90
108
  } from '../db/agent-connectors.js';
91
109
  import type { AgentConnectorApiDeps } from './agent-connector-api.js';
92
110
  import type { StationName } from '../db/schema.js';
@@ -116,20 +134,30 @@ supervisor.onTrainEvent((env, train) => {
116
134
  });
117
135
 
118
136
  let webhookServer: Server | null = null;
119
- const tunnelCfg = loadTunnelConfig();
120
- const tunnel = tunnelCfg ? new Tunnel(tunnelCfg, webhookPort()) : null;
137
+ const tunnelCfg = loadTunnelConfig() ?? tunnelConfigFromEnv();
138
+ const tunnel = tunnelCfg ? new Tunnel(tunnelCfg, webhookPort(), announcePublic) : null;
139
+
140
+ function announcePublic(url: string): void {
141
+ process.stderr.write(`\n${publicConnectHint(url)}`);
142
+ }
121
143
 
122
144
  setTrainCallBackend((train, action, args) =>
123
145
  supervisor.call(train, action, args),
124
146
  );
125
147
 
126
148
  const runtime = runtimeConfigFromEnv();
127
- const localSource = runtime === null ? null : httpSource(runtime);
149
+ const linkedSource = runtime === null ? null : httpSource(runtime);
150
+ const localSource = linkedSource ?? (isLocalMode() ? fileSource : null);
128
151
 
129
152
  function announceLocalEndpoint(): void {
153
+ if (isLocalMode())
154
+ process.stderr.write(
155
+ `\n${tunnel === null ? localConnectHint(webhookPort()) : tunnelPendingHint()}`,
156
+ );
130
157
  const key = localAgentKey();
131
158
  if (key === null) {
132
- log.warn('this agent has no key — reset it in the web UI to connect an agent');
159
+ if (isLocalMode()) log.info('no agent on this machine yet');
160
+ else log.warn('this agent has no key — reset it in the web UI to connect an agent');
133
161
  return;
134
162
  }
135
163
  const url = `http://127.0.0.1:${String(webhookPort())}/mcp?token=${key}`;
@@ -153,8 +181,11 @@ async function stopLocalStations(): Promise<void> {
153
181
  for (const station of supervisor.running()) await supervisor.stopTrain(station);
154
182
  }
155
183
 
184
+ const reloadStations = (): Promise<ReloadedStations> =>
185
+ localSource === null ? reloadAccountsFromDb() : reloadFrom(localSource);
186
+
156
187
  async function syncStations(station: StationName): Promise<void> {
157
- const { removed } = await reloadAccountsFromDb();
188
+ const { removed } = await reloadStations();
158
189
  if (stationByName(station)?.hasTrain === false) return;
159
190
  if (removed.includes(station)) await supervisor.stopTrain(station);
160
191
  else supervisor.requestReload(station);
@@ -162,11 +193,11 @@ async function syncStations(station: StationName): Promise<void> {
162
193
 
163
194
  const attachSessions = new AttachSessions({
164
195
  authorize: async (owner) => {
165
- await ownedAgentOrThrow(owner.email, owner.agentId);
196
+ await ownedAgentOrThrow(owner.subject, owner.agentId);
166
197
  },
167
198
  complete: async (owner, station, config) => {
168
199
  const ref = await attachAccountToAgent(
169
- owner.email,
200
+ owner.subject,
170
201
  owner.agentId,
171
202
  station,
172
203
  config,
@@ -186,10 +217,10 @@ const attachSessions = new AttachSessions({
186
217
  });
187
218
 
188
219
  async function resetAgentKey(
189
- email: string,
220
+ subject: string,
190
221
  id: string,
191
222
  ): Promise<ResetAgentKey> {
192
- const reset = await resetAgentKeyForEmail(email, id);
223
+ const reset = await resetAgentKeyForUser(subject, id);
193
224
  const closed = await closeAgentSession(id);
194
225
  log.info(
195
226
  { agent: reset.name, id: reset.id, sessionClosed: closed },
@@ -200,14 +231,14 @@ async function resetAgentKey(
200
231
 
201
232
  const agentApi: AgentApiDeps = {
202
233
  attachSessions,
203
- listAgents: listAgentsForEmail,
204
- createAgent: createAgentForEmail,
205
- deleteAgent: deleteAgentForEmail,
234
+ listAgents: listAgentsForUser,
235
+ createAgent: createAgentForUser,
236
+ deleteAgent: deleteAgentForUser,
206
237
  resetKey: resetAgentKey,
207
238
  gatherAccounts: gatherAccountsForAgents,
208
239
  capabilities: accountStationCapabilities,
209
240
  liveness: agentLiveness,
210
- releaseRuntime: releaseRuntimeForEmail,
241
+ releaseRuntime: releaseRuntimeForUser,
211
242
  connectorIds: connectorIdsByAgent,
212
243
  runtimes: runtimeLabels,
213
244
  prepareAccount,
@@ -225,66 +256,86 @@ const runApi: RunApiDeps = {
225
256
  };
226
257
 
227
258
  const agentConnectorApi: AgentConnectorApiDeps = {
228
- agentConnectors: agentConnectorsForEmail,
229
- addConnector: addConnectorToAgentForEmail,
230
- removeConnector: removeConnectorFromAgentForEmail,
231
- mintCode: mintAgentCodeForEmail,
259
+ agentConnectors: agentConnectorsForUser,
260
+ addConnector: addConnectorToAgentForUser,
261
+ removeConnector: removeConnectorFromAgentForUser,
262
+ mintCode: mintAgentCodeForUser,
232
263
  };
233
264
 
234
265
  const projectApi: ProjectApiDeps = {
235
- listProjects: listProjectsForEmail,
236
- createProject: createProjectForEmail,
237
- renameProject: renameProjectForEmail,
238
- deleteProject: deleteProjectForEmail,
239
- listMembers: listMembersForEmail,
240
- addMember: addMemberForEmail,
241
- setMemberRole: setMemberRoleForEmail,
242
- removeMember: removeMemberForEmail,
266
+ listProjects: listProjectsForUser,
267
+ createProject: createProjectForUser,
268
+ renameProject: renameProjectForUser,
269
+ deleteProject: deleteProjectForUser,
270
+ listMembers: listMembersForUser,
271
+ addMember: addMemberForUser,
272
+ setMemberRole: setMemberRoleForUser,
273
+ removeMember: removeMemberForUser,
243
274
  };
244
275
 
245
276
  const connectorApi: ConnectorApiDeps = {
246
- listConnectors: listConnectorsForEmail,
277
+ listConnectors: listConnectorsForUser,
247
278
  connectorNamesByIds,
248
- agentConnectors: agentConnectorsForEmail,
279
+ agentConnectors: agentConnectorsForUser,
249
280
  fenceRuntime,
250
- renameConnector: renameConnectorForEmail,
251
- createConnector: createConnectorForEmail,
252
- createPendingConnector: createPendingConnectorForEmail,
253
- reconnectConnector: reconnectConnectorForEmail,
254
- getConnector: getConnectorForEmail,
255
- verifyConnector: verifyConnectorForEmail,
256
- disconnectConnector: disconnectConnectorForEmail,
257
- deleteConnector: deleteConnectorForEmail,
281
+ renameConnector: renameConnectorForUser,
282
+ createConnector: createConnectorForUser,
283
+ createPendingConnector: createPendingConnectorForUser,
284
+ reconnectConnector: reconnectConnectorForUser,
285
+ getConnector: getConnectorForUser,
286
+ verifyConnector: verifyConnectorForUser,
287
+ disconnectConnector: disconnectConnectorForUser,
288
+ deleteConnector: deleteConnectorForUser,
258
289
  };
259
290
 
291
+ const hostedMode = (): ModeInfo => ({
292
+ mode: linkedSource === null ? 'hosted' : 'linked',
293
+ owner: null,
294
+ project: null,
295
+ });
296
+
297
+ function sessionApis(): SessionApis {
298
+ if (isLocalMode())
299
+ return localSessionApis({
300
+ syncStations,
301
+ closeAgentSession,
302
+ gatherAccounts: gatherAccountsForAgents,
303
+ capabilities: accountStationCapabilities,
304
+ liveness: agentLiveness,
305
+ prepareAccount,
306
+ });
307
+ return {
308
+ agentApi,
309
+ agentConnectorApi,
310
+ connectorApi,
311
+ projectApi,
312
+ runApi,
313
+ relayApi: { target: relayTarget, fence: fenceRuntime },
314
+ mode: hostedMode,
315
+ };
316
+ }
317
+
260
318
  async function main(): Promise<void> {
319
+ if (isLocalMode()) ensureLocalSessionSecret();
261
320
  if (localSource === null) await materializeFromDb();
262
- else await materializeFrom(localSource);
321
+ else await materializeFrom(localSource, { allowEmpty: linkedSource === null });
263
322
  warnOnLegacyWebhooks();
264
323
  supervisor.start();
265
324
  const metroMcp = await createMetroMcp();
266
325
  webhookServer = await startWebhookServer(
267
326
  emit,
268
- {
269
- agentApi,
270
- agentConnectorApi,
271
- connectorApi,
272
- projectApi,
273
- runApi,
274
- relayApi: { target: relayTarget, fence: fenceRuntime },
275
- },
327
+ sessionApis(),
276
328
  metroMcp.httpHandler,
277
329
  metroCall,
278
330
  );
279
331
  metroMcp.startInbound();
280
332
  startUploadReaper();
281
- if (localSource !== null) {
333
+ if (linkedSource !== null)
282
334
  startRuntimePoller({ sync: syncLocal, stopAll: stopLocalStations });
283
- announceLocalEndpoint();
284
- }
335
+ if (localSource !== null) announceLocalEndpoint();
285
336
  tunnel?.start();
286
337
  log.info(
287
- { tunnel: !!tunnel, trainsDir: TRAINS_DIR, mcp: '/' },
338
+ { tunnel: !!tunnel, trainsDir: trainsDir(), mcp: '/' },
288
339
  'dispatcher ready',
289
340
  );
290
341
  markDaemonReady();
@@ -13,7 +13,7 @@ import { extractToken } from '../mcp/request-identity.js';
13
13
  import { publicBaseOrDefault } from './attach-serve.js';
14
14
  import { AGENT_CODE_RE, takeAgentCode } from './agent-pair.js';
15
15
  import { relayServersJson } from './connector-json.js';
16
- import { sessionTtlFromEnv } from './google-oauth.js';
16
+ import { sessionTtlFromEnv } from './session-config.js';
17
17
  import { signAgentToken } from './session.js';
18
18
  import type { ConnectorApiDeps } from './connector-api.js';
19
19
 
@@ -58,14 +58,19 @@ async function handleClaim(
58
58
  });
59
59
  return;
60
60
  }
61
- const agent = await deps.agentConnectors(taken.email, taken.agentId);
61
+ const agent = await deps.agentConnectors(taken.subject, taken.agentId);
62
62
  const token = signAgentToken(
63
- { email: taken.email, agentId: agent.id },
63
+ { subject: taken.subject, agentId: agent.id },
64
64
  secret,
65
65
  { ttlSec: sessionTtlFromEnv() },
66
66
  );
67
- log.info({ email: taken.email, agent: agent.id }, 'cli: code claimed');
68
- sendJson(req, res, 200, { token, email: taken.email, agent: agent.name });
67
+ log.info({ subject: taken.subject, agent: agent.id }, 'cli: code claimed');
68
+ sendJson(req, res, 200, {
69
+ token,
70
+ email: taken.subject,
71
+ subject: taken.subject,
72
+ agent: agent.name,
73
+ });
69
74
  }
70
75
 
71
76
  async function handleRead(
@@ -80,9 +85,13 @@ async function handleRead(
80
85
  return;
81
86
  }
82
87
  await assertLease(who, deps.fenceRuntime);
83
- const agent = await deps.agentConnectors(who.email, who.agentId);
88
+ const agent = await deps.agentConnectors(who.subject, who.agentId);
84
89
  if (path === SESSION_PATH) {
85
- sendJson(req, res, 200, { email: who.email, agent: agent.name });
90
+ sendJson(req, res, 200, {
91
+ email: who.subject,
92
+ subject: who.subject,
93
+ agent: agent.name,
94
+ });
86
95
  return;
87
96
  }
88
97
  const entries = await deps.connectorNamesByIds(agent.connectorIds);
@@ -0,0 +1,33 @@
1
+ import { hostname } from 'node:os';
2
+
3
+ const uiBase = (): string => process.env.METRO_UI_URL ?? 'https://metro.box';
4
+
5
+ export const connectLink = (base: string): string =>
6
+ `${uiBase()}/#/connect/${encodeURIComponent(base)}`;
7
+
8
+ export function localConnectHint(port: number): string {
9
+ const here = `http://127.0.0.1:${String(port)}`;
10
+ const forward = `ssh -L ${String(port)}:127.0.0.1:${String(port)} ${hostname()}`;
11
+ return [
12
+ 'Manage this machine from the web UI:',
13
+ '',
14
+ ` ${connectLink(here)}`,
15
+ '',
16
+ `From another computer, forward the port first: ${forward}`,
17
+ '',
18
+ ].join('\n');
19
+ }
20
+
21
+ export function publicConnectHint(url: string): string {
22
+ return [
23
+ 'Manage this machine from the web UI, from anywhere:',
24
+ '',
25
+ ` ${connectLink(url)}`,
26
+ '',
27
+ 'Sign in right away: the first wallet to sign in owns this machine.',
28
+ '',
29
+ ].join('\n');
30
+ }
31
+
32
+ export const tunnelPendingHint = (): string =>
33
+ 'Bringing up a public address through a Cloudflare quick tunnel…\n';
@@ -35,23 +35,23 @@ const asText = (value: unknown): string =>
35
35
  typeof value === 'string' ? value : '';
36
36
 
37
37
  export interface ConnectorApiDeps extends OAuthRouteDeps {
38
- listConnectors: (email: string, project: string) => Promise<Connector[]>;
38
+ listConnectors: (subject: string, project: string) => Promise<Connector[]>;
39
39
  connectorNamesByIds: (ids: string[]) => Promise<RelayServerEntry[]>;
40
- agentConnectors: (email: string, agentId: string) => Promise<AgentConnectors>;
40
+ agentConnectors: (subject: string, agentId: string) => Promise<AgentConnectors>;
41
41
  fenceRuntime: Fence;
42
42
  createConnector: (
43
- email: string,
43
+ subject: string,
44
44
  project: string,
45
45
  input: ConnectorInput,
46
46
  ) => Promise<Connector>;
47
- verifyConnector: (email: string, id: string) => Promise<ConnectorCheck>;
48
- disconnectConnector: (email: string, id: string) => Promise<Connector>;
47
+ verifyConnector: (subject: string, id: string) => Promise<ConnectorCheck>;
48
+ disconnectConnector: (subject: string, id: string) => Promise<Connector>;
49
49
  renameConnector: (
50
- email: string,
50
+ subject: string,
51
51
  id: string,
52
52
  name: string,
53
53
  ) => Promise<Connector>;
54
- deleteConnector: (email: string, id: string) => Promise<DeletedConnector>;
54
+ deleteConnector: (subject: string, id: string) => Promise<DeletedConnector>;
55
55
  }
56
56
 
57
57
  type Routable =
@@ -115,7 +115,7 @@ async function handleList(
115
115
  sendJson(req, res, 400, { error: 'a project is required' });
116
116
  return;
117
117
  }
118
- const rows = await deps.listConnectors(session.email, project);
118
+ const rows = await deps.listConnectors(session.subject, project);
119
119
  sendJson(req, res, 200, {
120
120
  connectors: rows.map((row) => connectorPayload(row)),
121
121
  });
@@ -135,7 +135,7 @@ async function handleCreate(
135
135
  }
136
136
  const offered = asText(bodyField(body, 'value')).trim() !== '';
137
137
  try {
138
- const created = await deps.createConnector(session.email, project, {
138
+ const created = await deps.createConnector(session.subject, project, {
139
139
  name: bodyField(body, 'name'),
140
140
  url: bodyField(body, 'url'),
141
141
  header: bodyField(body, 'header'),
@@ -161,7 +161,7 @@ async function handleVerify(
161
161
  session: ApiSession,
162
162
  id: string,
163
163
  ): Promise<void> {
164
- const check = await deps.verifyConnector(session.email, id);
164
+ const check = await deps.verifyConnector(session.subject, id);
165
165
  log.info(
166
166
  { id: check.id, name: check.name, ok: check.ok },
167
167
  'connector-api: re-verified connector',
@@ -176,7 +176,7 @@ async function handleDisconnect(
176
176
  session: ApiSession,
177
177
  id: string,
178
178
  ): Promise<void> {
179
- const row = await deps.disconnectConnector(session.email, id);
179
+ const row = await deps.disconnectConnector(session.subject, id);
180
180
  log.info(
181
181
  { id: row.id, name: row.name },
182
182
  'connector-api: signed the connector out',
@@ -191,7 +191,7 @@ async function handleDelete(
191
191
  session: ApiSession,
192
192
  id: string,
193
193
  ): Promise<void> {
194
- const gone = await deps.deleteConnector(session.email, id);
194
+ const gone = await deps.deleteConnector(session.subject, id);
195
195
  log.info(
196
196
  { id: gone.id, name: gone.name },
197
197
  'connector-api: deleted connector',
@@ -210,7 +210,7 @@ async function handleConnector(
210
210
  await handleDelete(req, res, deps, session, id);
211
211
  return;
212
212
  }
213
- const row = await deps.getConnector(session.email, id);
213
+ const row = await deps.getConnector(session.subject, id);
214
214
  sendJson(req, res, 200, connectorPayload(row, true));
215
215
  }
216
216
 
@@ -226,7 +226,7 @@ async function handleRename(
226
226
  sendJson(req, res, 400, { error: 'name is required' });
227
227
  return;
228
228
  }
229
- const row = await deps.renameConnector(session.email, id, name);
229
+ const row = await deps.renameConnector(session.subject, id, name);
230
230
  log.info({ id: row.id, name: row.name }, 'connector-api: renamed connector');
231
231
  sendJson(req, res, 200, connectorPayload(row));
232
232
  }
@@ -24,16 +24,16 @@ export function hostOf(url: string): string {
24
24
 
25
25
  export interface OAuthRouteDeps {
26
26
  createPendingConnector: (
27
- email: string,
27
+ subject: string,
28
28
  project: string,
29
29
  input: { name: unknown; url: unknown },
30
30
  ) => Promise<Connector>;
31
31
  reconnectConnector: (
32
- email: string,
32
+ subject: string,
33
33
  id: string,
34
34
  auth: OAuthAuth,
35
35
  ) => Promise<Connector>;
36
- getConnector: (email: string, id: string) => Promise<Connector>;
36
+ getConnector: (subject: string, id: string) => Promise<Connector>;
37
37
  }
38
38
 
39
39
  export async function startOAuth(
@@ -46,12 +46,12 @@ export async function startOAuth(
46
46
  payload: (row: Connector) => Record<string, unknown>,
47
47
  ): Promise<void> {
48
48
  const url = parseConnectorUrl(bodyField(body, 'url'));
49
- const row = await deps.createPendingConnector(session.email, project, {
49
+ const row = await deps.createPendingConnector(session.subject, project, {
50
50
  name: bodyField(body, 'name'),
51
51
  url: bodyField(body, 'url'),
52
52
  });
53
53
  const authorize = await beginOAuth({
54
- email: session.email,
54
+ subject: session.subject,
55
55
  name: row.name,
56
56
  url,
57
57
  returnTo: asText(bodyField(body, 'returnTo')),
@@ -71,11 +71,11 @@ export async function handleConnect(
71
71
  session: ApiSession,
72
72
  id: string,
73
73
  ): Promise<void> {
74
- const row = await deps.getConnector(session.email, id);
74
+ const row = await deps.getConnector(session.subject, id);
75
75
  const url = parseConnectorUrl(row.url);
76
76
  const body = await readJsonBody(req);
77
77
  const authorize = await beginOAuth({
78
- email: session.email,
78
+ subject: session.subject,
79
79
  name: row.name,
80
80
  url,
81
81
  returnTo: asText(bodyField(body, 'returnTo')),
@@ -110,7 +110,7 @@ async function saveOAuth(
110
110
  entry: PendingAuth,
111
111
  auth: OAuthAuth,
112
112
  ): Promise<Connector> {
113
- return deps.reconnectConnector(entry.email, entry.connectorId, auth);
113
+ return deps.reconnectConnector(entry.subject, entry.connectorId, auth);
114
114
  }
115
115
 
116
116
  async function settleCallback(
@@ -4,7 +4,7 @@ import {
4
4
  type OAuthAuth,
5
5
  type OAuthTokens,
6
6
  } from './connector-verify.js';
7
- import { validateReturnTo } from './google-oauth.js';
7
+ import { validateReturnTo } from './session-config.js';
8
8
  import {
9
9
  authorizeUrl,
10
10
  exchangeCode,
@@ -32,7 +32,7 @@ const resourceOf = (url: URL): string => url.toString();
32
32
 
33
33
  export interface BeginInput {
34
34
  connectorId: string;
35
- email: string;
35
+ subject: string;
36
36
  name: string;
37
37
  url: URL;
38
38
  returnTo: string;
@@ -47,7 +47,7 @@ export async function beginOAuth(input: BeginInput): Promise<string> {
47
47
  const verifier = newVerifier();
48
48
  const resource = resourceOf(input.url);
49
49
  const state = startPending({
50
- email: input.email,
50
+ subject: input.subject,
51
51
  name: input.name,
52
52
  url: input.url.toString(),
53
53
  resource,
@@ -27,7 +27,8 @@ import {
27
27
  } from './tunnel.js';
28
28
  import { attachmentEventUrl, handleAttachRequest } from './attach-serve.js';
29
29
  import { webhookEntry } from '@metro-labs/webhook';
30
- import { handleGoogleAuthRequest } from './google-oauth.js';
30
+ import { handleSiweAuthRequest } from './siwe-routes.js';
31
+ import { handleModeRequest } from './mode-api.js';
31
32
  import { handleUploadRequest } from './upload-api.js';
32
33
  import {
33
34
  handleMonitorRequest,
@@ -323,6 +324,15 @@ async function handleWebhookRoute(
323
324
  return true;
324
325
  }
325
326
 
327
+ function handleSignInRoutes(
328
+ req: IncomingMessage,
329
+ res: ServerResponse,
330
+ apis: SessionApis,
331
+ ): boolean {
332
+ if (handleSiweAuthRequest(req, res, apis.siwe)) return true;
333
+ return apis.mode !== undefined && handleModeRequest(req, res, apis.mode);
334
+ }
335
+
326
336
  async function handlePreMcpRoutes(
327
337
  req: IncomingMessage,
328
338
  res: ServerResponse,
@@ -331,7 +341,7 @@ async function handlePreMcpRoutes(
331
341
  monitorCall?: MonitorCall,
332
342
  ): Promise<boolean> {
333
343
  if (handleHealth(req, res)) return true;
334
- if (await handleGoogleAuthRequest(req, res)) return true;
344
+ if (handleSignInRoutes(req, res, apis)) return true;
335
345
  if (handleSessionApis(req, res, apis)) return true;
336
346
  if (handleUploadRequest(req, res)) return true;
337
347
  if (handleAttachRequest(req, res)) return true;
@@ -71,9 +71,16 @@ export function daemonSelf(): Line {
71
71
  }
72
72
 
73
73
  export function selfLine(): Line | null {
74
- if (process.env.CLAUDECODE) {
75
- const s = claudeSessionId();
76
- return s ? Line.claude(claudeUserId(), s) : null;
74
+ if (!process.env.CLAUDECODE) return null;
75
+ const s = claudeSessionId();
76
+ if (!s) return null;
77
+ try {
78
+ return Line.claude(claudeUserId(), s);
79
+ } catch (e) {
80
+ log.warn(
81
+ { reason: errMsg(e) },
82
+ 'could not resolve a Claude account id; relaying with no self line',
83
+ );
84
+ return null;
77
85
  }
78
- return null;
79
86
  }