()
| 236 | } |
| 237 | |
| 238 | export async function login(): Promise<void> { |
| 239 | const config = loadConfig(); |
| 240 | |
| 241 | // Preflight: hit the deployment's public info endpoint before opening |
| 242 | // the browser. Two wins — |
| 243 | // 1. Fast-fail when the server is unreachable instead of opening a |
| 244 | // browser tab that loads nothing and timing out 2 minutes later. |
| 245 | // 2. Captures shared_domain in the same request so we don't need a |
| 246 | // separate fetch after login completes. |
| 247 | // `/v1/info` is unauthenticated and login runs before we have a key, so we |
| 248 | // probe it with a raw fetch rather than the SDK client (which requires an |
| 249 | // apiKey to construct). Distinguish a connection failure (refused, DNS, |
| 250 | // timeout) — which should abort — from an HTTP-level error (server up, /info |
| 251 | // absent on an older deployment), which should let login continue. |
| 252 | let discoveredSharedDomain: string | undefined; |
| 253 | try { |
| 254 | const resp = await fetch(`${config.api_url.replace(/\/+$/, "")}/v1/info`); |
| 255 | if (resp.ok) { |
| 256 | const info = (await resp.json()) as { shared_domain?: string }; |
| 257 | if (info.shared_domain) { |
| 258 | discoveredSharedDomain = info.shared_domain; |
| 259 | } |
| 260 | } |
| 261 | // A non-ok response means the server is up but /info is unavailable |
| 262 | // (older deployment) — continue with login without shared_domain. |
| 263 | } catch (err) { |
| 264 | const detail = err instanceof Error ? err.message : String(err); |
| 265 | throw new Error( |
| 266 | `could not reach ${config.api_url}: ${detail}. ` + |
| 267 | `Check the server is running and E2A_URL is correct.`, |
| 268 | ); |
| 269 | } |
| 270 | |
| 271 | const { apiKey, agentEmail } = await waitForBrowserLogin(config.api_url); |
| 272 | |
| 273 | saveConfig({ |
| 274 | api_key: apiKey, |
| 275 | agent_email: agentEmail, |
| 276 | ...(discoveredSharedDomain ? { shared_domain: discoveredSharedDomain } : {}), |
| 277 | }); |
| 278 | |
| 279 | process.stdout.write(`Logged in to ${hostLabel(config.api_url)}.\n`); |
| 280 | if (discoveredSharedDomain) { |
| 281 | process.stdout.write(` Shared domain: ${discoveredSharedDomain}\n`); |
| 282 | } |
| 283 | process.stdout.write("Config saved to ~/.e2a/config.json\n"); |
| 284 | if (agentEmail) { |
| 285 | process.stdout.write(`Active agent: ${agentEmail}\n`); |
| 286 | } else { |
| 287 | process.stderr.write("No agents found yet. Run: e2a agents register <slug>\n"); |
| 288 | } |
| 289 | } |
no test coverage detected