@meistrari/remy-cli 1.4.0 → 1.4.2
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/README.md +2 -2
- package/dist/remy.js +180 -125
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -58,7 +58,7 @@ Drag to select visible text in any Remy view; Remy copies it to the local clipbo
|
|
|
58
58
|
| `/exit` | Leave Remy. |
|
|
59
59
|
| `Ctrl+O` | Expand or collapse activity details. |
|
|
60
60
|
|
|
61
|
-
After a session becomes terminal, press `Esc` to return to the dashboard or `n` to start new work. From the dashboard, press `q` to quit. `Shift+L` in the dashboard and `/logout` in a session ask for confirmation; press `y` to sign out or `Esc` to cancel. `Ctrl+C` exits Remy from the dashboard, wizard, or session view.
|
|
61
|
+
After a session becomes terminal, press `Esc` to return to the dashboard or `n` to start new work. From the dashboard, press `q` to quit; in an open session, `q` is ordinary composer text. `Shift+L` in the dashboard and `/logout` in a session ask for confirmation; press `y` to sign out or `Esc` to cancel. `Ctrl+C` exits Remy from the dashboard, wizard, or session view.
|
|
62
62
|
|
|
63
63
|
## Commands for direct or scripted use
|
|
64
64
|
|
|
@@ -116,7 +116,7 @@ remy login \
|
|
|
116
116
|
--target-application-id <target-application-uuid>
|
|
117
117
|
```
|
|
118
118
|
|
|
119
|
-
Remy stores endpoint configuration in `~/.config/remy/config.json` and tokens in `~/.local/state/remy/tokens.json`. Set `XDG_CONFIG_HOME` or `XDG_STATE_HOME` to use different base directories.
|
|
119
|
+
Remy stores endpoint configuration in `~/.config/remy/config.json` and tokens in `~/.local/state/remy/tokens.json`. Set `XDG_CONFIG_HOME` or `XDG_STATE_HOME` to use different base directories. Before each Coding Agent API request, it reloads the shared token file and refreshes an expired access token. When multiple Remy processes run at once, only one rotates and saves the refresh-token pair; the others wait and then use that newest saved pair. Long-running sessions and reconnects therefore stay authenticated after another process refreshes. `remy logout` removes tokens but retains endpoint configuration; pass `--api-url` to require that the supplied URL matches the active API host before logging out.
|
|
120
120
|
|
|
121
121
|
## Complete command reference
|
|
122
122
|
|
package/dist/remy.js
CHANGED
|
@@ -33234,16 +33234,21 @@ function loginUsage() {
|
|
|
33234
33234
|
}
|
|
33235
33235
|
|
|
33236
33236
|
// src/credential-state.ts
|
|
33237
|
-
import { randomUUID as randomUUID2 } from "crypto";
|
|
33238
|
-
import { lstat, mkdir as mkdir3, open as open3, readFile as readFile3, rename as rename2, unlink as unlink2 } from "fs/promises";
|
|
33237
|
+
import { createHash as createHash2, randomUUID as randomUUID2 } from "crypto";
|
|
33238
|
+
import { lstat, mkdir as mkdir3, open as open3, readFile as readFile3, rename as rename2, rmdir, unlink as unlink2 } from "fs/promises";
|
|
33239
33239
|
import { dirname as dirname3, join as join2 } from "path";
|
|
33240
|
+
import { setTimeout as delay } from "timers/promises";
|
|
33240
33241
|
var lockName = ".credential-state.lock";
|
|
33242
|
+
var lockOwnerName = "owner.json";
|
|
33241
33243
|
var journalName = ".credential-promotion.journal.json";
|
|
33244
|
+
var lockRetryMilliseconds = 25;
|
|
33242
33245
|
async function withCredentialState({
|
|
33243
33246
|
stateDirectory,
|
|
33247
|
+
signal,
|
|
33248
|
+
lockFilesystem,
|
|
33244
33249
|
action
|
|
33245
33250
|
}) {
|
|
33246
|
-
const owner = await acquireCredentialStateLock(stateDirectory);
|
|
33251
|
+
const owner = await acquireCredentialStateLock({ stateDirectory, signal, renamePath: lockFilesystem?.renamePath });
|
|
33247
33252
|
try {
|
|
33248
33253
|
return await action();
|
|
33249
33254
|
} finally {
|
|
@@ -33318,51 +33323,146 @@ async function removeCredentialTokenWhileLocked({
|
|
|
33318
33323
|
function lockPath(stateDirectory) {
|
|
33319
33324
|
return join2(stateDirectory, lockName);
|
|
33320
33325
|
}
|
|
33321
|
-
async function acquireCredentialStateLock(
|
|
33326
|
+
async function acquireCredentialStateLock({
|
|
33327
|
+
stateDirectory,
|
|
33328
|
+
signal,
|
|
33329
|
+
renamePath = rename2
|
|
33330
|
+
}) {
|
|
33322
33331
|
await mkdir3(stateDirectory, { recursive: true, mode: 448 });
|
|
33323
33332
|
const owner = { ownerId: randomUUID2(), pid: process.pid };
|
|
33324
33333
|
const path = lockPath(stateDirectory);
|
|
33334
|
+
const candidatePath = join2(stateDirectory, `.credential-state.candidate.${owner.ownerId}`);
|
|
33335
|
+
await mkdir3(candidatePath, { mode: 448 });
|
|
33325
33336
|
try {
|
|
33326
|
-
await writeExclusivePrivateFile({ path, contents: `${JSON.stringify(owner)}
|
|
33337
|
+
await writeExclusivePrivateFile({ path: join2(candidatePath, lockOwnerName), contents: `${JSON.stringify(owner)}
|
|
33327
33338
|
` });
|
|
33328
|
-
|
|
33339
|
+
while (true) {
|
|
33340
|
+
throwIfAborted(signal);
|
|
33341
|
+
const recordedState = await readLockOwnerState(path);
|
|
33342
|
+
if (recordedState.status === "missing") {
|
|
33343
|
+
try {
|
|
33344
|
+
await renamePath(candidatePath, path);
|
|
33345
|
+
return owner;
|
|
33346
|
+
} catch (error93) {
|
|
33347
|
+
if (!isLockContentionError(error93))
|
|
33348
|
+
throw error93;
|
|
33349
|
+
continue;
|
|
33350
|
+
}
|
|
33351
|
+
}
|
|
33352
|
+
if (recordedState.status === "malformed")
|
|
33353
|
+
throw new Error("another Remy credential operation is active");
|
|
33354
|
+
const recordedOwner = recordedState.owner;
|
|
33355
|
+
if (!isPidAbsent(recordedOwner.pid)) {
|
|
33356
|
+
await waitForCredentialStateLock(signal);
|
|
33357
|
+
continue;
|
|
33358
|
+
}
|
|
33359
|
+
const confirmedState = await readLockOwnerState(path);
|
|
33360
|
+
if (confirmedState.status === "missing")
|
|
33361
|
+
continue;
|
|
33362
|
+
if (confirmedState.status === "malformed")
|
|
33363
|
+
throw new Error("another Remy credential operation is active");
|
|
33364
|
+
const confirmedOwner = confirmedState.owner;
|
|
33365
|
+
if (!sameLockOwner(confirmedOwner, recordedOwner) || !isPidAbsent(confirmedOwner.pid)) {
|
|
33366
|
+
await waitForCredentialStateLock(signal);
|
|
33367
|
+
continue;
|
|
33368
|
+
}
|
|
33369
|
+
await moveStaleLockAside({ stateDirectory, path, owner: recordedOwner, signal, renamePath });
|
|
33370
|
+
}
|
|
33329
33371
|
} catch (error93) {
|
|
33330
|
-
|
|
33331
|
-
|
|
33372
|
+
await removeLockDirectory({ path: candidatePath });
|
|
33373
|
+
throw error93;
|
|
33332
33374
|
}
|
|
33333
|
-
|
|
33334
|
-
|
|
33335
|
-
|
|
33336
|
-
|
|
33375
|
+
}
|
|
33376
|
+
async function moveStaleLockAside({
|
|
33377
|
+
stateDirectory,
|
|
33378
|
+
path,
|
|
33379
|
+
owner,
|
|
33380
|
+
signal,
|
|
33381
|
+
renamePath
|
|
33382
|
+
}) {
|
|
33383
|
+
const stalePath = join2(stateDirectory, `.credential-state.stale.${lockOwnerFingerprint(owner)}`);
|
|
33337
33384
|
try {
|
|
33338
|
-
await
|
|
33339
|
-
|
|
33340
|
-
|
|
33341
|
-
|
|
33385
|
+
await renamePath(path, stalePath);
|
|
33386
|
+
} catch (error93) {
|
|
33387
|
+
if (isLockContentionError(error93)) {
|
|
33388
|
+
await waitForCredentialStateLock(signal);
|
|
33389
|
+
return;
|
|
33390
|
+
}
|
|
33391
|
+
if (isMissingFileError(error93))
|
|
33392
|
+
return;
|
|
33393
|
+
throw error93;
|
|
33342
33394
|
}
|
|
33395
|
+
}
|
|
33396
|
+
function lockOwnerFingerprint(owner) {
|
|
33397
|
+
return createHash2("sha256").update(`${owner.ownerId}\x00${owner.pid}`).digest("hex");
|
|
33398
|
+
}
|
|
33399
|
+
function sameLockOwner(left, right) {
|
|
33400
|
+
return left?.ownerId === right.ownerId && left.pid === right.pid;
|
|
33401
|
+
}
|
|
33402
|
+
async function waitForCredentialStateLock(signal) {
|
|
33403
|
+
throwIfAborted(signal);
|
|
33343
33404
|
try {
|
|
33344
|
-
await
|
|
33345
|
-
|
|
33346
|
-
|
|
33347
|
-
|
|
33348
|
-
throw
|
|
33405
|
+
await delay(lockRetryMilliseconds, undefined, signal ? { signal } : undefined);
|
|
33406
|
+
} catch (error93) {
|
|
33407
|
+
if (signal?.aborted)
|
|
33408
|
+
throw signal.reason ?? error93;
|
|
33409
|
+
throw error93;
|
|
33349
33410
|
}
|
|
33350
33411
|
}
|
|
33412
|
+
function throwIfAborted(signal) {
|
|
33413
|
+
if (signal?.aborted)
|
|
33414
|
+
throw signal.reason ?? new Error("Credential operation aborted.");
|
|
33415
|
+
}
|
|
33351
33416
|
async function releaseCredentialStateLock({ stateDirectory, owner }) {
|
|
33352
33417
|
const path = lockPath(stateDirectory);
|
|
33353
33418
|
const currentOwner = await readLockOwner(path);
|
|
33354
33419
|
if (!currentOwner || currentOwner.ownerId !== owner.ownerId)
|
|
33355
33420
|
return;
|
|
33356
|
-
|
|
33421
|
+
const releasedPath = join2(stateDirectory, `.credential-state.released.${owner.ownerId}.${randomUUID2()}`);
|
|
33422
|
+
try {
|
|
33423
|
+
await rename2(path, releasedPath);
|
|
33424
|
+
} catch (error93) {
|
|
33425
|
+
if (isMissingFileError(error93))
|
|
33426
|
+
return;
|
|
33427
|
+
throw error93;
|
|
33428
|
+
}
|
|
33429
|
+
await removeLockDirectory({ path: releasedPath });
|
|
33357
33430
|
}
|
|
33358
33431
|
async function readLockOwner(path) {
|
|
33432
|
+
const state = await readLockOwnerState(path);
|
|
33433
|
+
return state.status === "valid" ? state.owner : undefined;
|
|
33434
|
+
}
|
|
33435
|
+
async function readLockOwnerState(path) {
|
|
33436
|
+
const firstObservation = await observeLockOwner(path);
|
|
33437
|
+
if (firstObservation.status !== "owner-missing")
|
|
33438
|
+
return firstObservation;
|
|
33439
|
+
const secondObservation = await observeLockOwner(path);
|
|
33440
|
+
return secondObservation.status === "owner-missing" ? { status: "malformed" } : secondObservation;
|
|
33441
|
+
}
|
|
33442
|
+
async function observeLockOwner(path) {
|
|
33443
|
+
let ownerPath;
|
|
33359
33444
|
try {
|
|
33360
|
-
const
|
|
33445
|
+
const metadata = await lstat(path);
|
|
33446
|
+
ownerPath = metadata.isDirectory() ? join2(path, lockOwnerName) : path;
|
|
33447
|
+
} catch (error93) {
|
|
33448
|
+
return isMissingFileError(error93) ? { status: "missing" } : { status: "malformed" };
|
|
33449
|
+
}
|
|
33450
|
+
try {
|
|
33451
|
+
const parsed = JSON.parse(await readFile3(ownerPath, "utf8"));
|
|
33361
33452
|
if (!isLockOwner(parsed))
|
|
33362
|
-
return;
|
|
33363
|
-
return parsed;
|
|
33364
|
-
} catch {
|
|
33365
|
-
return;
|
|
33453
|
+
return { status: "malformed" };
|
|
33454
|
+
return { status: "valid", owner: parsed };
|
|
33455
|
+
} catch (error93) {
|
|
33456
|
+
return isMissingFileError(error93) ? { status: "owner-missing" } : { status: "malformed" };
|
|
33457
|
+
}
|
|
33458
|
+
}
|
|
33459
|
+
async function removeLockDirectory({ path }) {
|
|
33460
|
+
await removeIfPresent({ path: join2(path, lockOwnerName) });
|
|
33461
|
+
try {
|
|
33462
|
+
await rmdir(path);
|
|
33463
|
+
} catch (error93) {
|
|
33464
|
+
if (!isMissingFileError(error93))
|
|
33465
|
+
throw error93;
|
|
33366
33466
|
}
|
|
33367
33467
|
}
|
|
33368
33468
|
function isLockOwner(value) {
|
|
@@ -33474,8 +33574,8 @@ async function syncParentDirectory(path) {
|
|
|
33474
33574
|
await handle.close();
|
|
33475
33575
|
}
|
|
33476
33576
|
}
|
|
33477
|
-
function
|
|
33478
|
-
return error93 instanceof Error && "code" in error93 && error93.code === "EEXIST";
|
|
33577
|
+
function isLockContentionError(error93) {
|
|
33578
|
+
return error93 instanceof Error && "code" in error93 && (error93.code === "EEXIST" || error93.code === "ENOTEMPTY" || error93.code === "EISDIR" || error93.code === "ENOTDIR");
|
|
33479
33579
|
}
|
|
33480
33580
|
function isMissingFileError(error93) {
|
|
33481
33581
|
return error93 instanceof Error && "code" in error93 && error93.code === "ENOENT";
|
|
@@ -36398,11 +36498,6 @@ async function createSessionTui({
|
|
|
36398
36498
|
finish({ kind: "new", composer: { text: "", attachments: [] } });
|
|
36399
36499
|
return;
|
|
36400
36500
|
}
|
|
36401
|
-
if (key.name === "q" && (latestState.aggregateStatus !== "open" || composer.plainText.length === 0)) {
|
|
36402
|
-
key.preventDefault();
|
|
36403
|
-
finish({ kind: "exit" });
|
|
36404
|
-
return;
|
|
36405
|
-
}
|
|
36406
36501
|
if (key.name === "o" && key.ctrl) {
|
|
36407
36502
|
key.preventDefault();
|
|
36408
36503
|
activityExpanded = !activityExpanded;
|
|
@@ -37197,7 +37292,7 @@ var compactMarkRows = 9;
|
|
|
37197
37292
|
var compactMinWidth = 48;
|
|
37198
37293
|
var compactMinHeight = 20;
|
|
37199
37294
|
var markBrightnessGain = 4.2;
|
|
37200
|
-
var remyCliVersion = "1.4.
|
|
37295
|
+
var remyCliVersion = "1.4.2";
|
|
37201
37296
|
async function showRemySplash({
|
|
37202
37297
|
createRenderer = createRemyRenderer,
|
|
37203
37298
|
durationMs = splashDurationMs,
|
|
@@ -37610,7 +37705,7 @@ function createCliShutdown({ abortSignal }) {
|
|
|
37610
37705
|
function exitCodeForSignal(signal) {
|
|
37611
37706
|
return signal === "SIGINT" ? 130 : 143;
|
|
37612
37707
|
}
|
|
37613
|
-
function
|
|
37708
|
+
function throwIfAborted2(signal) {
|
|
37614
37709
|
if (signal?.aborted)
|
|
37615
37710
|
throw signal.reason ?? new CliInterruptedError("SIGINT");
|
|
37616
37711
|
}
|
|
@@ -37678,19 +37773,20 @@ ${approvalUrl}
|
|
|
37678
37773
|
let journalPrepared = false;
|
|
37679
37774
|
try {
|
|
37680
37775
|
const tokens = await flow.authenticate();
|
|
37681
|
-
|
|
37776
|
+
throwIfAborted2(dependencies.abortSignal);
|
|
37682
37777
|
const candidateAuthConfiguration = toAuthConfiguration({ configuration, tokenPath: candidateTokenPath });
|
|
37683
37778
|
const candidateClient = createCodingAgentClient({
|
|
37684
37779
|
apiUrl: configuration.apiUrl,
|
|
37685
37780
|
getAccessToken: async (signal) => await requireAccessToken(candidateAuthConfiguration, signal)
|
|
37686
37781
|
});
|
|
37687
37782
|
const identity = await materializeRemoteCurrentUser({ client: candidateClient, signal: dependencies.abortSignal });
|
|
37688
|
-
|
|
37783
|
+
throwIfAborted2(dependencies.abortSignal);
|
|
37689
37784
|
await onCandidateIdentityValidated?.({ email: identity.email });
|
|
37690
|
-
|
|
37691
|
-
|
|
37785
|
+
throwIfAborted2(dependencies.abortSignal);
|
|
37786
|
+
throwIfAborted2(dependencies.abortSignal);
|
|
37692
37787
|
await withCredentialState({
|
|
37693
37788
|
stateDirectory: dirname6(paths.tokenPath),
|
|
37789
|
+
signal: dependencies.abortSignal,
|
|
37694
37790
|
action: async () => {
|
|
37695
37791
|
await recoverCredentialPromotionWhileLocked({
|
|
37696
37792
|
stateDirectory: dirname6(paths.tokenPath),
|
|
@@ -37762,7 +37858,7 @@ ${approvalUrl}
|
|
|
37762
37858
|
});
|
|
37763
37859
|
}
|
|
37764
37860
|
});
|
|
37765
|
-
|
|
37861
|
+
throwIfAborted2(dependencies.abortSignal);
|
|
37766
37862
|
dependencies.output.writeStderr(`Signed in as ${tokens.user.email ?? tokens.user.name ?? "authenticated user"}.
|
|
37767
37863
|
`);
|
|
37768
37864
|
} catch (error93) {
|
|
@@ -37782,6 +37878,7 @@ async function logout({ dependencies, flags }) {
|
|
|
37782
37878
|
const removeTokenFile = dependencies.removeTokenFile ?? unlink3;
|
|
37783
37879
|
await withCredentialState({
|
|
37784
37880
|
stateDirectory: dirname6(paths.tokenPath),
|
|
37881
|
+
signal: dependencies.abortSignal,
|
|
37785
37882
|
action: async () => {
|
|
37786
37883
|
await recoverCredentialPromotionWhileLocked({
|
|
37787
37884
|
stateDirectory: dirname6(paths.tokenPath),
|
|
@@ -37838,6 +37935,7 @@ async function createAuthenticatedClient(dependencies) {
|
|
|
37838
37935
|
const paths = resolveCliPaths(dependencies.environment);
|
|
37839
37936
|
const snapshot = await withCredentialState({
|
|
37840
37937
|
stateDirectory: dirname6(paths.tokenPath),
|
|
37938
|
+
signal: dependencies.abortSignal,
|
|
37841
37939
|
action: async () => {
|
|
37842
37940
|
await recoverCredentialPromotionWhileLocked({
|
|
37843
37941
|
stateDirectory: dirname6(paths.tokenPath),
|
|
@@ -37846,64 +37944,33 @@ async function createAuthenticatedClient(dependencies) {
|
|
|
37846
37944
|
...dependencies.credentialStateFilesystem
|
|
37847
37945
|
});
|
|
37848
37946
|
const configuration = await readCliConfiguration(paths.configPath);
|
|
37849
|
-
|
|
37850
|
-
return { configuration, tokenGeneration };
|
|
37947
|
+
return { configuration };
|
|
37851
37948
|
}
|
|
37852
37949
|
});
|
|
37853
37950
|
const authConfiguration = toAuthConfiguration({ configuration: snapshot.configuration, tokenPath: paths.tokenPath });
|
|
37854
37951
|
const identity = await loadStoredIdentity(authConfiguration);
|
|
37855
37952
|
const getAccessToken = createRefreshingAccessTokenProvider({ dependencies, paths, snapshot });
|
|
37856
|
-
|
|
37857
|
-
|
|
37858
|
-
|
|
37859
|
-
|
|
37860
|
-
|
|
37861
|
-
|
|
37862
|
-
|
|
37863
|
-
|
|
37864
|
-
|
|
37865
|
-
|
|
37866
|
-
|
|
37867
|
-
authenticatedEmail: remoteIdentity.email
|
|
37868
|
-
};
|
|
37869
|
-
} catch (error93) {
|
|
37870
|
-
if (error93 instanceof LoginRequiredError) {
|
|
37871
|
-
await withCredentialState({
|
|
37872
|
-
stateDirectory: dirname6(paths.tokenPath),
|
|
37873
|
-
action: async () => {
|
|
37874
|
-
await recoverCredentialPromotionWhileLocked({
|
|
37875
|
-
stateDirectory: dirname6(paths.tokenPath),
|
|
37876
|
-
configPath: paths.configPath,
|
|
37877
|
-
tokenPath: paths.tokenPath,
|
|
37878
|
-
...dependencies.credentialStateFilesystem
|
|
37879
|
-
});
|
|
37880
|
-
await assertCredentialSnapshotCurrent({ paths, snapshot });
|
|
37881
|
-
await removeCredentialTokenWhileLocked({
|
|
37882
|
-
tokenPath: paths.tokenPath,
|
|
37883
|
-
removeFile: dependencies.credentialStateFilesystem?.removeFile,
|
|
37884
|
-
syncDirectory: dependencies.credentialStateFilesystem?.syncDirectory
|
|
37885
|
-
});
|
|
37886
|
-
}
|
|
37887
|
-
});
|
|
37888
|
-
}
|
|
37889
|
-
throw error93;
|
|
37890
|
-
}
|
|
37953
|
+
const client = createCodingAgentClient({
|
|
37954
|
+
apiUrl: snapshot.configuration.apiUrl,
|
|
37955
|
+
getAccessToken
|
|
37956
|
+
});
|
|
37957
|
+
const remoteIdentity = await materializeRemoteCurrentUser({ client, signal: dependencies.abortSignal });
|
|
37958
|
+
return {
|
|
37959
|
+
authConfiguration,
|
|
37960
|
+
client,
|
|
37961
|
+
identity,
|
|
37962
|
+
authenticatedEmail: remoteIdentity.email
|
|
37963
|
+
};
|
|
37891
37964
|
}
|
|
37892
37965
|
function createRefreshingAccessTokenProvider({
|
|
37893
37966
|
dependencies,
|
|
37894
37967
|
paths,
|
|
37895
37968
|
snapshot
|
|
37896
37969
|
}) {
|
|
37897
|
-
let tokenGeneration = snapshot.tokenGeneration;
|
|
37898
37970
|
let pendingAccessToken;
|
|
37899
37971
|
return async (signal) => {
|
|
37900
|
-
if (!pendingAccessToken)
|
|
37901
|
-
|
|
37902
|
-
pendingAccessToken = refreshAccessToken({ dependencies, paths, configuration: snapshot.configuration, expectedTokenGeneration, signal }).then(({ accessToken: accessToken2, tokenGeneration: refreshedTokenGeneration }) => {
|
|
37903
|
-
tokenGeneration = refreshedTokenGeneration;
|
|
37904
|
-
return accessToken2;
|
|
37905
|
-
});
|
|
37906
|
-
}
|
|
37972
|
+
if (!pendingAccessToken)
|
|
37973
|
+
pendingAccessToken = refreshAccessToken({ dependencies, paths, configuration: snapshot.configuration, signal });
|
|
37907
37974
|
const accessToken = pendingAccessToken;
|
|
37908
37975
|
try {
|
|
37909
37976
|
return await accessToken;
|
|
@@ -37917,13 +37984,13 @@ async function refreshAccessToken({
|
|
|
37917
37984
|
dependencies,
|
|
37918
37985
|
paths,
|
|
37919
37986
|
configuration,
|
|
37920
|
-
expectedTokenGeneration,
|
|
37921
37987
|
signal
|
|
37922
37988
|
}) {
|
|
37923
37989
|
const candidateTokenPath = await createCandidateTokenPath(paths.tokenPath);
|
|
37924
37990
|
try {
|
|
37925
|
-
await withCredentialState({
|
|
37991
|
+
return await withCredentialState({
|
|
37926
37992
|
stateDirectory: dirname6(paths.tokenPath),
|
|
37993
|
+
signal,
|
|
37927
37994
|
action: async () => {
|
|
37928
37995
|
await recoverCredentialPromotionWhileLocked({
|
|
37929
37996
|
stateDirectory: dirname6(paths.tokenPath),
|
|
@@ -37931,31 +37998,28 @@ async function refreshAccessToken({
|
|
|
37931
37998
|
tokenPath: paths.tokenPath,
|
|
37932
37999
|
...dependencies.credentialStateFilesystem
|
|
37933
38000
|
});
|
|
37934
|
-
await
|
|
37935
|
-
|
|
37936
|
-
|
|
37937
|
-
|
|
37938
|
-
if (
|
|
37939
|
-
await writeFile4(candidateTokenPath,
|
|
37940
|
-
|
|
37941
|
-
|
|
37942
|
-
|
|
37943
|
-
|
|
37944
|
-
|
|
37945
|
-
|
|
37946
|
-
|
|
37947
|
-
|
|
37948
|
-
|
|
37949
|
-
|
|
37950
|
-
|
|
37951
|
-
|
|
37952
|
-
|
|
37953
|
-
}
|
|
37954
|
-
await
|
|
37955
|
-
|
|
37956
|
-
snapshot: { configuration, tokenGeneration: expectedTokenGeneration }
|
|
37957
|
-
});
|
|
37958
|
-
if (candidateTokenGeneration !== expectedTokenGeneration) {
|
|
38001
|
+
const currentConfiguration = await readCliConfiguration(paths.configPath);
|
|
38002
|
+
if (!sameCliConfiguration(currentConfiguration, configuration))
|
|
38003
|
+
throw new Error("Credential configuration changed while preparing authentication. Retry the command.");
|
|
38004
|
+
const currentTokenGeneration = await readOptionalTokenFile(paths.tokenPath);
|
|
38005
|
+
if (currentTokenGeneration !== undefined)
|
|
38006
|
+
await writeFile4(candidateTokenPath, currentTokenGeneration, { mode: 384 });
|
|
38007
|
+
const candidateAuthConfiguration = toAuthConfiguration({ configuration, tokenPath: candidateTokenPath });
|
|
38008
|
+
let accessToken;
|
|
38009
|
+
try {
|
|
38010
|
+
accessToken = await requireAccessToken(candidateAuthConfiguration, signal);
|
|
38011
|
+
} catch (error93) {
|
|
38012
|
+
if (error93 instanceof LoginRequiredError) {
|
|
38013
|
+
await removeCredentialTokenWhileLocked({
|
|
38014
|
+
tokenPath: paths.tokenPath,
|
|
38015
|
+
removeFile: dependencies.credentialStateFilesystem?.removeFile,
|
|
38016
|
+
syncDirectory: dependencies.credentialStateFilesystem?.syncDirectory
|
|
38017
|
+
});
|
|
38018
|
+
}
|
|
38019
|
+
throw error93;
|
|
38020
|
+
}
|
|
38021
|
+
const candidateTokenGeneration = await readFile5(candidateTokenPath, "utf8");
|
|
38022
|
+
if (candidateTokenGeneration !== currentTokenGeneration) {
|
|
37959
38023
|
await replaceCredentialTokenWhileLocked({
|
|
37960
38024
|
sourcePath: candidateTokenPath,
|
|
37961
38025
|
tokenPath: paths.tokenPath,
|
|
@@ -37963,22 +38027,13 @@ async function refreshAccessToken({
|
|
|
37963
38027
|
syncDirectory: dependencies.credentialStateFilesystem?.syncDirectory
|
|
37964
38028
|
});
|
|
37965
38029
|
}
|
|
38030
|
+
return accessToken;
|
|
37966
38031
|
}
|
|
37967
38032
|
});
|
|
37968
|
-
return { accessToken, tokenGeneration: candidateTokenGeneration };
|
|
37969
38033
|
} finally {
|
|
37970
38034
|
await removeCandidateToken({ path: candidateTokenPath, removeTokenFile: unlink3 });
|
|
37971
38035
|
}
|
|
37972
38036
|
}
|
|
37973
|
-
async function assertCredentialSnapshotCurrent({
|
|
37974
|
-
paths,
|
|
37975
|
-
snapshot
|
|
37976
|
-
}) {
|
|
37977
|
-
const currentConfiguration = await readCliConfiguration(paths.configPath);
|
|
37978
|
-
const currentTokenGeneration = await readOptionalTokenFile(paths.tokenPath);
|
|
37979
|
-
if (!sameCliConfiguration(currentConfiguration, snapshot.configuration) || currentTokenGeneration !== snapshot.tokenGeneration)
|
|
37980
|
-
throw new Error("Credential state changed while preparing authentication. Retry the command.");
|
|
37981
|
-
}
|
|
37982
38037
|
async function readOptionalTokenFile(path) {
|
|
37983
38038
|
try {
|
|
37984
38039
|
return await readFile5(path, "utf8");
|
|
@@ -38007,7 +38062,7 @@ async function dashboard({
|
|
|
38007
38062
|
bootstrap
|
|
38008
38063
|
}) {
|
|
38009
38064
|
assertInteractiveDashboard(dependencies);
|
|
38010
|
-
|
|
38065
|
+
throwIfAborted2(dependencies.abortSignal);
|
|
38011
38066
|
const { operations, repositories, initialSessions } = bootstrap;
|
|
38012
38067
|
const openDashboardTui = dependencies.openDashboardTui ?? createDashboardTui;
|
|
38013
38068
|
const pages = [initialSessions];
|
|
@@ -38188,7 +38243,7 @@ async function authenticateBareDashboard({
|
|
|
38188
38243
|
splash?.completeAuthentication({ email: email5 });
|
|
38189
38244
|
}
|
|
38190
38245
|
});
|
|
38191
|
-
|
|
38246
|
+
throwIfAborted2(dependencies.abortSignal);
|
|
38192
38247
|
return await awaitWithAbort({
|
|
38193
38248
|
value: prepareDashboardBootstrap({
|
|
38194
38249
|
dependencies,
|