openlimiter 1.3.1 → 1.3.3

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.d.ts +1 -1
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +130 -78
  4. package/dist/cli.js.map +1 -1
  5. package/dist/codex-device-login.d.ts +2 -1
  6. package/dist/codex-device-login.d.ts.map +1 -1
  7. package/dist/codex-device-login.js +6 -4
  8. package/dist/codex-device-login.js.map +1 -1
  9. package/dist/config.d.ts +1 -0
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +3 -2
  12. package/dist/config.js.map +1 -1
  13. package/dist/hub-auth.d.ts +12 -14
  14. package/dist/hub-auth.d.ts.map +1 -1
  15. package/dist/hub-auth.js +232 -47
  16. package/dist/hub-auth.js.map +1 -1
  17. package/dist/hub-sync.d.ts +14 -13
  18. package/dist/hub-sync.d.ts.map +1 -1
  19. package/dist/hub-sync.js +139 -18
  20. package/dist/hub-sync.js.map +1 -1
  21. package/dist/hub.d.ts +6 -3
  22. package/dist/hub.d.ts.map +1 -1
  23. package/dist/hub.js +47 -11
  24. package/dist/hub.js.map +1 -1
  25. package/dist/ingest.d.ts.map +1 -1
  26. package/dist/ingest.js +16 -21
  27. package/dist/ingest.js.map +1 -1
  28. package/dist/session.d.ts +16 -8
  29. package/dist/session.d.ts.map +1 -1
  30. package/dist/session.js +144 -31
  31. package/dist/session.js.map +1 -1
  32. package/dist/statusline.d.ts +1 -1
  33. package/dist/statusline.d.ts.map +1 -1
  34. package/dist/statusline.js +10 -8
  35. package/dist/statusline.js.map +1 -1
  36. package/dist/terminal-backup.d.ts +5 -0
  37. package/dist/terminal-backup.d.ts.map +1 -0
  38. package/dist/terminal-backup.js +88 -0
  39. package/dist/terminal-backup.js.map +1 -0
  40. package/dist/terminal-launcher.d.ts +12 -0
  41. package/dist/terminal-launcher.d.ts.map +1 -0
  42. package/dist/terminal-launcher.js +208 -0
  43. package/dist/terminal-launcher.js.map +1 -0
  44. package/dist/terminal-toml.d.ts +38 -0
  45. package/dist/terminal-toml.d.ts.map +1 -0
  46. package/dist/terminal-toml.js +357 -0
  47. package/dist/terminal-toml.js.map +1 -0
  48. package/dist/terminal.d.ts +10 -37
  49. package/dist/terminal.d.ts.map +1 -1
  50. package/dist/terminal.js +390 -619
  51. package/dist/terminal.js.map +1 -1
  52. package/package.json +4 -4
package/dist/cli.d.ts CHANGED
@@ -100,7 +100,7 @@ export interface CliDependencies {
100
100
  */
101
101
  openBrowser: (url: string) => void;
102
102
  /** An injectable delay, so a poll loop never makes a test wait in real time. */
103
- sleep: (milliseconds: number) => Promise<void>;
103
+ sleep: (milliseconds: number, signal?: AbortSignal) => Promise<void>;
104
104
  /**
105
105
  * Progress a long running command wants seen before it returns.
106
106
  *
package/dist/cli.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
package/dist/cli.js CHANGED
@@ -1,4 +1,4 @@
1
- import { ACQUISITION_OUTCOME_SENTENCE, CREDENTIAL_FAILURE_SENTENCE, PROVIDER_CODES, acquireRefreshLock, clearRefreshSpawnFailure, mergeAcquiredSnapshots, createFetchTransport, readRefreshSpawnFailure, antigravitySpec, buildAdvice, canonicalJson, claudeSpec, codexSpec, dedupeFailures, desktopHoldsCache, failureFromConnectorReason, freshness, geminiCliSpec, grokSpec, isProviderDue, kimiSpec, mergeSnapshots, normalizeMeters, normalizeMetersReport, openrouterSpec, readAcquisitionCredential, readAcquisitionSchedule, readSnapshotCache, readWindowsCredentialWith, resolveStateDirectory, runAcquisition, spawnDetachedRefresh, writeAcquisitionSchedule, probeAntigravity, resolveAgyExecutablePath } from "@openlimiter/core";
1
+ import { ACQUISITION_OUTCOME_SENTENCE, ACQUISITION_PROVIDERS, registerManagedCodexAccount, CREDENTIAL_FAILURE_SENTENCE, PROVIDER_CODES, acquireRefreshLock, clearRefreshSpawnFailure, mergeAcquiredSnapshots, createFetchTransport, readRefreshSpawnFailure, antigravitySpec, buildAdvice, canonicalJson, claudeSpec, codexSpec, dedupeFailures, desktopHoldsCache, failureFromConnectorReason, freshness, geminiCliSpec, grokSpec, isProviderDue, kimiSpec, mergeSnapshots, normalizeMeters, normalizeMetersReport, openrouterSpec, readAcquisitionCredential, readAcquisitionSchedule, readSnapshotCache, readWindowsCredentialWith, resolveStateDirectory, runAcquisition, spawnDetachedRefresh, writeAcquisitionSchedule, probeAntigravity, resolveAgyExecutablePath } from "@openlimiter/core";
2
2
  import { antigravityFixture, claudeFixture, codexFixture, grokFixture, kimiFixture, connectors, manualFixture, opencodeFixture, openrouterFixture, parseAntigravityCodeAssistPayload, parseAntigravityPayload, parseClaudePayload, parseCodexPayload, parseGeminiCliPayload, parseGrokPayload, parseKimiPayload, parseManualPayload, parseOpencodePayload, parseOpenrouterPayload } from "@openlimiter/connectors";
3
3
  import { AGENT_COMPATIBILITY, agentContextFromCache, agentContextSpillFromCache, agentVersionCompatibility, changeAgentHook, detectAgentInstallation, readAgentHookStatus, runAgentHook, validateAgentExecutableStamp, writeAgentContextSnapshot, loadHostedContextTrust, renderClaudeStatusline } from "@openlimiter/adapters";
4
4
  import { execFile, spawn } from "node:child_process";
@@ -11,9 +11,9 @@ import { failureLine, failureLines, renderTable, supportsColor } from "./render.
11
11
  import { isStatuslineHost, renderStatuslineLayout, statuslineColor } from "./statusline.js";
12
12
  import { TERMINAL_HOST_NAMES, installHost, terminalHide, terminalShow, terminalStatusTable, uninstallHost } from "./terminal.js";
13
13
  import { createFetchHubTransport, hubConfigured } from "./hub.js";
14
- import { REVOKED_SENTENCE, ensureFreshSession, isAborted, runDeviceLogin } from "./hub-auth.js";
15
- import { runSync } from "./hub-sync.js";
16
- import { deleteSession, readSession, writeSession } from "./session.js";
14
+ import { DELIVERY_UNCONFIRMED_SENTENCE, REVOKED_SENTENCE, ensureFreshSession, isAborted, runDeviceLogin } from "./hub-auth.js";
15
+ import { runSync, syncIsDue, recordSyncAttempt } from "./hub-sync.js";
16
+ import { deleteSession, readSession, StorageDiagnosticError, writeSession, withSessionLock } from "./session.js";
17
17
  import { DeviceLoginError, LOGIN_TIMEOUT_MILLISECONDS, SystemDeviceLoginRunner, managedCodexHome, startCodexDeviceLogin, versionIsSupported as codexVersionIsSupported } from "./codex-device-login.js";
18
18
  /** Exit codes. Enumerated so a script can tell these cases apart. */
19
19
  export const EXIT_OK = 0;
@@ -137,7 +137,7 @@ export function runtimeDependencies() {
137
137
  emit: (line) => {
138
138
  process.stdout.write(line + "\n");
139
139
  },
140
- codexDeviceLoginRunnerFactory: (executable) => new SystemDeviceLoginRunner(executable)
140
+ codexDeviceLoginRunnerFactory: (executable) => new SystemDeviceLoginRunner(executable, process.platform)
141
141
  };
142
142
  }
143
143
  function succeed(stdout) {
@@ -305,6 +305,7 @@ function credentialReader(dependencies) {
305
305
  };
306
306
  }
307
307
  return await readAcquisitionCredential(provider, {
308
+ ...(dependencies.stateDirectory === undefined ? {} : { stateDirectory: dependencies.stateDirectory }),
308
309
  platform: dependencies.platform,
309
310
  environment: dependencies.environment,
310
311
  homeDirectory: dependencies.homeDirectory,
@@ -459,6 +460,8 @@ const ACQUISITION_HEADER = "PROVIDER DETECTED STATUS NEXT NOTE";
459
460
  async function refreshCommand(dependencies, now) {
460
461
  const lock = await acquireRefreshLock(dependencies.stateDirectory);
461
462
  if (!lock.ok) {
463
+ if (lock.reason === "unavailable")
464
+ return fail(EXIT_FAILURE, "openlimiter refresh: the refresh did not complete.");
462
465
  return succeed([
463
466
  ACQUISITION_HEADER,
464
467
  "SKIPPED another refresh is already running on this machine"
@@ -473,7 +476,10 @@ async function refreshCommand(dependencies, now) {
473
476
  * lock where the answer cannot change underneath it.
474
477
  */
475
478
  const settled = await readSnapshotCache(dependencies.stateDirectory);
476
- if (desktopHoldsCache(settled.ok ? settled.snapshots : [], now)) {
479
+ const providers = await readProvidersConfig(dependencies.stateDirectory);
480
+ const specs = acquisitionSpecs(providers).filter((spec) => !desktopHoldsCache(settled.ok ? settled.snapshots.filter((row) => row.provider === spec.provider) : [], now));
481
+ if (specs.length === 0) {
482
+ await triggerSyncAfterRefresh(dependencies, now);
477
483
  return succeed([
478
484
  ACQUISITION_HEADER,
479
485
  "SKIPPED the desktop app refreshed this cache inside the last interval"
@@ -481,9 +487,8 @@ async function refreshCommand(dependencies, now) {
481
487
  }
482
488
  /* A round that got this far is proof a refresh can start on this machine. */
483
489
  await clearRefreshSpawnFailure(dependencies.stateDirectory);
484
- const providers = await readProvidersConfig(dependencies.stateDirectory);
485
490
  const schedule = await readAcquisitionSchedule(dependencies.stateDirectory);
486
- const result = await runAcquisition(acquisitionSpecs(providers), {
491
+ const result = await runAcquisition(specs, {
487
492
  transport: dependencies.acquisitionTransport,
488
493
  now,
489
494
  schedule,
@@ -561,8 +566,10 @@ async function refreshCommand(dependencies, now) {
561
566
  */
562
567
  async function startRefreshBehind(dependencies, snapshots, now) {
563
568
  try {
569
+ const directory = dependencies.stateDirectory ?? resolveStateDirectory();
570
+ const syncDue = await readSession(directory) !== null && await syncIsDue(directory, now);
564
571
  await spawnDetachedRefresh({
565
- snapshots,
572
+ snapshots: syncDue ? [] : snapshots,
566
573
  now,
567
574
  ...(dependencies.stateDirectory === undefined
568
575
  ? {}
@@ -1242,13 +1249,20 @@ const terminalUsage = [
1242
1249
  /** The provider ids this machine has a login or a key for, right now. */
1243
1250
  async function detectedProviderIds(dependencies) {
1244
1251
  const environment = await environmentWithLocalMarkers(dependencies.environment, dependencies.stateDirectory);
1245
- return connectors
1252
+ const detected = new Set(connectors
1246
1253
  .filter((connector) => connector.detect(environment))
1247
- .map((connector) => connector.id);
1254
+ .map((connector) => connector.id));
1255
+ const reader = credentialReader(dependencies);
1256
+ for (const provider of ACQUISITION_PROVIDERS) {
1257
+ if ((await reader(provider)).ok)
1258
+ detected.add(provider.toLowerCase());
1259
+ }
1260
+ return [...detected];
1248
1261
  }
1249
1262
  function terminalContext(dependencies, detected) {
1250
1263
  return {
1251
1264
  homeDirectory: dependencies.homeDirectory,
1265
+ environment: dependencies.environment,
1252
1266
  ...(dependencies.stateDirectory === undefined
1253
1267
  ? {}
1254
1268
  : { stateDirectory: dependencies.stateDirectory }),
@@ -1332,6 +1346,14 @@ async function terminalCommand(dependencies, argumentsList) {
1332
1346
  return fail(EXIT_USAGE, terminalUsage);
1333
1347
  }
1334
1348
  /* ------------------------------------------------------------------- hub */
1349
+ async function persistLoginSession(dependencies, session) {
1350
+ const directory = dependencies.stateDirectory ?? resolveStateDirectory();
1351
+ await withSessionLock(directory, async () => await writeSession(session, {
1352
+ directory,
1353
+ platform: dependencies.platform,
1354
+ ...(dependencies.windowsAclRunner === undefined ? {} : { windowsAclRunner: dependencies.windowsAclRunner })
1355
+ }));
1356
+ }
1335
1357
  /**
1336
1358
  * Sign in to the hub through the device code flow.
1337
1359
  *
@@ -1349,31 +1371,36 @@ async function loginCommand(dependencies, argumentsList) {
1349
1371
  emit: dependencies.emit,
1350
1372
  ...(dependencies.interruptSignal === undefined ? {} : { interruptSignal: dependencies.interruptSignal }),
1351
1373
  openBrowser: dependencies.openBrowser,
1352
- open: argumentsList.includes("--open")
1374
+ open: argumentsList.includes("--open"),
1375
+ storeSession: async (session) => await persistLoginSession(dependencies, session)
1353
1376
  });
1354
1377
  if (outcome.kind === "signed_in") {
1355
- await writeSession(outcome.session, {
1356
- ...(dependencies.stateDirectory === undefined ? {} : { directory: dependencies.stateDirectory }),
1357
- platform: dependencies.platform,
1358
- ...(dependencies.windowsAclRunner === undefined ? {} : { windowsAclRunner: dependencies.windowsAclRunner })
1359
- });
1378
+ if (!outcome.deliveryConfirmed)
1379
+ return succeed(DELIVERY_UNCONFIRMED_SENTENCE);
1360
1380
  return succeed("Signed in as " + outcome.session.accountLabel + ".");
1361
1381
  }
1362
1382
  if (outcome.kind === "cancelled")
1363
1383
  return fail(EXIT_FAILURE, "openlimiter login: cancelled.");
1364
- if (outcome.kind === "denied")
1365
- return fail(EXIT_FAILURE, "openlimiter login: the sign in was denied.");
1384
+ if (outcome.kind === "denied") {
1385
+ return fail(EXIT_FAILURE, outcome.message === undefined
1386
+ ? "openlimiter login: the sign in was denied."
1387
+ : "openlimiter login: " + outcome.message);
1388
+ }
1366
1389
  if (outcome.kind === "expired") {
1367
1390
  return fail(EXIT_FAILURE, outcome.message ?? "openlimiter login: the code expired before it was approved.");
1368
1391
  }
1369
1392
  if (outcome.kind === "not_configured") {
1370
1393
  return fail(EXIT_FAILURE, "openlimiter login: the hub is not configured on this build.");
1371
1394
  }
1395
+ if (outcome.kind === "storage_error") {
1396
+ return fail(EXIT_FAILURE, "openlimiter login: " + outcome.message);
1397
+ }
1372
1398
  return fail(EXIT_FAILURE, "openlimiter login: " + outcome.message + ".");
1373
1399
  }
1374
1400
  /** Forget the stored session. Nothing on the hub is asked to do anything. */
1375
1401
  async function logoutCommand(dependencies) {
1376
- await deleteSession(dependencies.stateDirectory);
1402
+ const directory = dependencies.stateDirectory ?? resolveStateDirectory();
1403
+ await withSessionLock(directory, async () => await deleteSession(directory));
1377
1404
  return succeed("Signed out.");
1378
1405
  }
1379
1406
  /** Print who is signed in, or say plainly that nobody is. */
@@ -1400,11 +1427,15 @@ async function persistRenewedSession(dependencies, session) {
1400
1427
  * being treated as an ordinary network failure.
1401
1428
  */
1402
1429
  async function syncCommand(dependencies, now) {
1430
+ return await withSessionLock(dependencies.stateDirectory ?? resolveStateDirectory(), async () => await syncCommandLocked(dependencies, now));
1431
+ }
1432
+ async function syncCommandLocked(dependencies, now) {
1403
1433
  const directory = dependencies.stateDirectory ?? resolveStateDirectory();
1404
1434
  const session = await readSession(directory);
1405
1435
  if (session === null) {
1406
1436
  return fail(EXIT_FAILURE, "openlimiter sync: not signed in, run openlimiter login.");
1407
1437
  }
1438
+ await recordSyncAttempt(directory, now);
1408
1439
  const renewal = await ensureFreshSession(session, now, dependencies.environment, dependencies.hubTransport);
1409
1440
  if (renewal.kind === "revoked") {
1410
1441
  await deleteSession(directory);
@@ -1424,7 +1455,8 @@ async function syncCommand(dependencies, now) {
1424
1455
  now,
1425
1456
  token: active.token,
1426
1457
  deviceId: active.deviceId,
1427
- snapshots
1458
+ snapshots,
1459
+ sessionLockHeld: true
1428
1460
  });
1429
1461
  if (result.kind === "accepted") {
1430
1462
  return succeed("Synced: accepted, tier " + (result.tier ?? "free") + ", rows " + String(result.rows) + ".");
@@ -1438,6 +1470,9 @@ async function syncCommand(dependencies, now) {
1438
1470
  if (result.kind === "rejected") {
1439
1471
  return fail(EXIT_FAILURE, "openlimiter sync: the hub rejected the upload.");
1440
1472
  }
1473
+ if (result.kind === "sequence_mismatch") {
1474
+ return fail(EXIT_FAILURE, "openlimiter sync: the hub returned a sequence mismatch.");
1475
+ }
1441
1476
  return fail(EXIT_FAILURE, "openlimiter sync: the hub could not be reached, try again.");
1442
1477
  }
1443
1478
  /**
@@ -1453,32 +1488,7 @@ async function syncCommand(dependencies, now) {
1453
1488
  */
1454
1489
  async function triggerSyncAfterRefresh(dependencies, now) {
1455
1490
  try {
1456
- const directory = dependencies.stateDirectory ?? resolveStateDirectory();
1457
- const session = await readSession(directory);
1458
- if (session === null)
1459
- return;
1460
- const renewal = await ensureFreshSession(session, now, dependencies.environment, dependencies.hubTransport);
1461
- if (renewal.kind === "revoked") {
1462
- await deleteSession(directory);
1463
- return;
1464
- }
1465
- if (renewal.kind === "error")
1466
- return;
1467
- if (renewal.kind === "renewed")
1468
- await persistRenewedSession(dependencies, renewal.session);
1469
- const active = renewal.session;
1470
- const snapshots = await cachedSnapshots(dependencies.stateDirectory);
1471
- const outcome = await runSync({
1472
- directory,
1473
- environment: dependencies.environment,
1474
- transport: dependencies.hubTransport,
1475
- now,
1476
- token: active.token,
1477
- deviceId: active.deviceId,
1478
- snapshots
1479
- });
1480
- if (outcome.kind === "revoked")
1481
- await deleteSession(directory);
1491
+ await syncCommand(dependencies, now);
1482
1492
  }
1483
1493
  catch {
1484
1494
  /* A refresh that could not sync still refreshed. */
@@ -1493,18 +1503,23 @@ async function promptOrSkip(dependencies, question) {
1493
1503
  /** Step one: sign in, or say plainly why this machine did not. */
1494
1504
  async function setupSignInStep(dependencies) {
1495
1505
  const lines = ["1. Sign in", SETUP_SIGN_IN_PROMPT];
1506
+ lines.forEach(dependencies.emit);
1507
+ const add = (line) => {
1508
+ lines.push(line);
1509
+ dependencies.emit(line);
1510
+ };
1496
1511
  const existing = await readSession(dependencies.stateDirectory);
1497
1512
  if (existing !== null) {
1498
- lines.push("Already signed in as " + existing.accountLabel + ".");
1513
+ add("Already signed in as " + existing.accountLabel + ".");
1499
1514
  return lines;
1500
1515
  }
1501
1516
  if (!hubConfigured(dependencies.environment)) {
1502
- lines.push("Skipped: the hub is not configured on this build.");
1517
+ add("Skipped: the hub is not configured on this build.");
1503
1518
  return lines;
1504
1519
  }
1505
1520
  const proceed = await promptOrSkip(dependencies, "Enter to sign in, S to skip: ");
1506
1521
  if (!proceed) {
1507
- lines.push("Skipped.");
1522
+ add("Skipped.");
1508
1523
  return lines;
1509
1524
  }
1510
1525
  const outcome = await runDeviceLogin({
@@ -1514,21 +1529,22 @@ async function setupSignInStep(dependencies) {
1514
1529
  emit: dependencies.emit,
1515
1530
  ...(dependencies.interruptSignal === undefined ? {} : { interruptSignal: dependencies.interruptSignal }),
1516
1531
  openBrowser: dependencies.openBrowser,
1517
- open: false
1532
+ open: false,
1533
+ storeSession: async (session) => await persistLoginSession(dependencies, session)
1518
1534
  });
1519
1535
  if (outcome.kind === "signed_in") {
1520
- await writeSession(outcome.session, {
1521
- ...(dependencies.stateDirectory === undefined ? {} : { directory: dependencies.stateDirectory }),
1522
- platform: dependencies.platform,
1523
- ...(dependencies.windowsAclRunner === undefined ? {} : { windowsAclRunner: dependencies.windowsAclRunner })
1524
- });
1525
- lines.push("Signed in as " + outcome.session.accountLabel + ".");
1536
+ add(outcome.deliveryConfirmed
1537
+ ? "Signed in as " + outcome.session.accountLabel + "."
1538
+ : DELIVERY_UNCONFIRMED_SENTENCE);
1526
1539
  }
1527
1540
  else if (outcome.kind === "cancelled") {
1528
- lines.push("Cancelled.");
1541
+ add("Cancelled.");
1542
+ }
1543
+ else if (outcome.kind === "storage_error") {
1544
+ add(outcome.message);
1529
1545
  }
1530
1546
  else {
1531
- lines.push("Could not sign in this time. Run openlimiter login later.");
1547
+ add("Could not sign in this time. Run openlimiter login later.");
1532
1548
  }
1533
1549
  return lines;
1534
1550
  }
@@ -1610,8 +1626,13 @@ async function runCodexDeviceSignIn(dependencies, installed) {
1610
1626
  return "Codex: sign in cancelled.";
1611
1627
  }
1612
1628
  const state = await session.state(Date.now());
1613
- if (state.kind === "complete")
1629
+ if (state.kind === "complete") {
1630
+ if (!(await registerManagedCodexAccount(stateDirectory, sessionId, dependencies.now())) ||
1631
+ !(await credentialReader(dependencies)("CODEX")).ok) {
1632
+ return "Codex: " + codexFailureSentence("storage") + ".";
1633
+ }
1614
1634
  return "Codex: signed in.";
1635
+ }
1615
1636
  if (state.kind === "cancelled")
1616
1637
  return "Codex: sign in cancelled.";
1617
1638
  if (state.kind === "timed_out")
@@ -1629,17 +1650,24 @@ async function runCodexDeviceSignIn(dependencies, installed) {
1629
1650
  /** Step two: detect installed agent CLIs and their logins, one row each. */
1630
1651
  async function setupConnectStep(dependencies) {
1631
1652
  const lines = ["2. Connect"];
1653
+ dependencies.emit(lines[0]);
1654
+ const add = (line) => {
1655
+ lines.push(line);
1656
+ dependencies.emit(line);
1657
+ };
1632
1658
  const environment = await environmentWithLocalMarkers(dependencies.environment, dependencies.stateDirectory);
1633
1659
  const readCredential = credentialReader(dependencies);
1634
1660
  let codexInstalled = null;
1635
1661
  let codexNeedsSignIn = false;
1636
1662
  for (const agent of CONNECT_AGENT_IDS) {
1637
- const installed = await detectAgentInstallation(agent, {
1638
- environment,
1639
- platform: dependencies.platform
1640
- });
1663
+ const installed = Object.prototype.hasOwnProperty.call(dependencies.detectedAgentInstallations, agent)
1664
+ ? dependencies.detectedAgentInstallations[agent] ?? null
1665
+ : await detectAgentInstallation(agent, {
1666
+ environment,
1667
+ platform: dependencies.platform
1668
+ });
1641
1669
  const state = await connectRowState(agent, installed, readCredential);
1642
- lines.push(agent + ": " + CONNECT_ROW_LABEL[state]);
1670
+ add(agent + ": " + CONNECT_ROW_LABEL[state]);
1643
1671
  if (agent === "codex") {
1644
1672
  codexInstalled = installed;
1645
1673
  codexNeedsSignIn = state === "sign_in";
@@ -1648,7 +1676,7 @@ async function setupConnectStep(dependencies) {
1648
1676
  if (codexNeedsSignIn && codexInstalled !== null) {
1649
1677
  const proceed = await promptOrSkip(dependencies, "Codex has no login yet. Sign in now? Enter to start, S to skip: ");
1650
1678
  if (proceed)
1651
- lines.push(await runCodexDeviceSignIn(dependencies, codexInstalled));
1679
+ add(await runCodexDeviceSignIn(dependencies, codexInstalled));
1652
1680
  }
1653
1681
  else {
1654
1682
  await promptOrSkip(dependencies, "Enter to accept: ");
@@ -1664,31 +1692,52 @@ const CONNECT_AGENT_IDS = [
1664
1692
  "kimi",
1665
1693
  "opencode"
1666
1694
  ];
1667
- /** Step three: the existing terminal checklist, unchanged. */
1695
+ /** Step three: show the checklist and install each host the person selects. */
1668
1696
  async function setupShowBarsStep(dependencies) {
1669
1697
  const result = await terminalCommand(dependencies, ["terminal"]);
1670
- return ["3. Show bars in", result.stdout];
1698
+ const lines = ["3. Show bars in", result.stdout];
1699
+ lines.forEach(dependencies.emit);
1700
+ for (const host of TERMINAL_HOST_NAMES) {
1701
+ if (await promptOrSkip(dependencies, host + ": Enter to install, S to skip: ")) {
1702
+ const installed = await terminalCommand(dependencies, ["terminal", "install", host]);
1703
+ lines.push(installed.stdout || installed.stderr);
1704
+ dependencies.emit(lines[lines.length - 1]);
1705
+ }
1706
+ }
1707
+ return lines;
1671
1708
  }
1672
1709
  /**
1673
1710
  * The three step first run: sign in, connect, show bars in, then the bars
1674
1711
  * themselves, once.
1675
1712
  */
1676
1713
  async function setupCommand(dependencies, now) {
1677
- const sections = [];
1678
- sections.push(...(await setupSignInStep(dependencies)));
1679
- sections.push(...(await setupConnectStep(dependencies)));
1680
- sections.push(...(await setupShowBarsStep(dependencies)));
1714
+ await setupSignInStep(dependencies);
1715
+ await setupConnectStep(dependencies);
1716
+ const collected = await refreshCommand(dependencies, now);
1717
+ if (collected.exitCode !== 0 && collected.stderr !== "")
1718
+ dependencies.emit(collected.stderr);
1719
+ await setupShowBarsStep(dependencies);
1681
1720
  const snapshots = await cachedSnapshots(dependencies.stateDirectory);
1682
- sections.push(renderTable(snapshots, now, dependencies.colorOutput));
1683
- return succeed(sections.join(NEWLINE));
1721
+ dependencies.emit(renderTable(snapshots, now, dependencies.colorOutput));
1722
+ return succeed("");
1684
1723
  }
1685
1724
  export async function runCli(argumentsList, overrides = {}) {
1686
- const dependencies = { ...defaults(), ...overrides };
1725
+ const setupOutput = [];
1726
+ const hasOutputSink = overrides.emit !== undefined;
1727
+ const dependencies = {
1728
+ ...defaults(),
1729
+ ...overrides,
1730
+ ...(hasOutputSink ? {} : { emit: (line) => setupOutput.push(line) })
1731
+ };
1687
1732
  const command = argumentsList[0] ?? "setup";
1688
1733
  const now = dependencies.now();
1689
1734
  try {
1690
- if (command === "setup")
1691
- return await setupCommand(dependencies, now);
1735
+ if (command === "setup") {
1736
+ const result = await setupCommand(dependencies, now);
1737
+ if (hasOutputSink || setupOutput.length === 0)
1738
+ return result;
1739
+ return { ...result, stdout: [setupOutput.join(NEWLINE), result.stdout].filter(Boolean).join(NEWLINE) };
1740
+ }
1692
1741
  if (command === "login")
1693
1742
  return await loginCommand(dependencies, argumentsList);
1694
1743
  if (command === "logout")
@@ -1736,7 +1785,7 @@ export async function runCli(argumentsList, overrides = {}) {
1736
1785
  }
1737
1786
  return fail(EXIT_USAGE, "openlimiter: unknown command.", help);
1738
1787
  }
1739
- catch {
1788
+ catch (error) {
1740
1789
  /*
1741
1790
  * The hook and statusline paths are invoked by another tool. They report
1742
1791
  * nothing rather than breaking their host. Every other command surfaces the
@@ -1751,6 +1800,9 @@ export async function runCli(argumentsList, overrides = {}) {
1751
1800
  if (command === "statusline") {
1752
1801
  return { exitCode: EXIT_OK, stdout: "OpenLimiter UNKNOWN", stderr: "" };
1753
1802
  }
1803
+ if (error instanceof StorageDiagnosticError) {
1804
+ return fail(EXIT_FAILURE, error.message);
1805
+ }
1754
1806
  return fail(EXIT_FAILURE, "openlimiter: the command did not complete.");
1755
1807
  }
1756
1808
  }