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.
- package/dist/cli.d.ts +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +130 -78
- package/dist/cli.js.map +1 -1
- package/dist/codex-device-login.d.ts +2 -1
- package/dist/codex-device-login.d.ts.map +1 -1
- package/dist/codex-device-login.js +6 -4
- package/dist/codex-device-login.js.map +1 -1
- package/dist/config.d.ts +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +3 -2
- package/dist/config.js.map +1 -1
- package/dist/hub-auth.d.ts +12 -14
- package/dist/hub-auth.d.ts.map +1 -1
- package/dist/hub-auth.js +232 -47
- package/dist/hub-auth.js.map +1 -1
- package/dist/hub-sync.d.ts +14 -13
- package/dist/hub-sync.d.ts.map +1 -1
- package/dist/hub-sync.js +139 -18
- package/dist/hub-sync.js.map +1 -1
- package/dist/hub.d.ts +6 -3
- package/dist/hub.d.ts.map +1 -1
- package/dist/hub.js +47 -11
- package/dist/hub.js.map +1 -1
- package/dist/ingest.d.ts.map +1 -1
- package/dist/ingest.js +16 -21
- package/dist/ingest.js.map +1 -1
- package/dist/session.d.ts +16 -8
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +144 -31
- package/dist/session.js.map +1 -1
- package/dist/statusline.d.ts +1 -1
- package/dist/statusline.d.ts.map +1 -1
- package/dist/statusline.js +10 -8
- package/dist/statusline.js.map +1 -1
- package/dist/terminal-backup.d.ts +5 -0
- package/dist/terminal-backup.d.ts.map +1 -0
- package/dist/terminal-backup.js +88 -0
- package/dist/terminal-backup.js.map +1 -0
- package/dist/terminal-launcher.d.ts +12 -0
- package/dist/terminal-launcher.d.ts.map +1 -0
- package/dist/terminal-launcher.js +208 -0
- package/dist/terminal-launcher.js.map +1 -0
- package/dist/terminal-toml.d.ts +38 -0
- package/dist/terminal-toml.d.ts.map +1 -0
- package/dist/terminal-toml.js +357 -0
- package/dist/terminal-toml.js.map +1 -0
- package/dist/terminal.d.ts +10 -37
- package/dist/terminal.d.ts.map +1 -1
- package/dist/terminal.js +390 -619
- package/dist/terminal.js.map +1 -1
- 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":"AAAA,OAAO,
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
1356
|
-
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1513
|
+
add("Already signed in as " + existing.accountLabel + ".");
|
|
1499
1514
|
return lines;
|
|
1500
1515
|
}
|
|
1501
1516
|
if (!hubConfigured(dependencies.environment)) {
|
|
1502
|
-
|
|
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
|
-
|
|
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
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
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
|
-
|
|
1541
|
+
add("Cancelled.");
|
|
1542
|
+
}
|
|
1543
|
+
else if (outcome.kind === "storage_error") {
|
|
1544
|
+
add(outcome.message);
|
|
1529
1545
|
}
|
|
1530
1546
|
else {
|
|
1531
|
-
|
|
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 =
|
|
1638
|
-
|
|
1639
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
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
|
-
|
|
1683
|
-
return succeed(
|
|
1721
|
+
dependencies.emit(renderTable(snapshots, now, dependencies.colorOutput));
|
|
1722
|
+
return succeed("");
|
|
1684
1723
|
}
|
|
1685
1724
|
export async function runCli(argumentsList, overrides = {}) {
|
|
1686
|
-
const
|
|
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
|
-
|
|
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
|
}
|