@modelprofile.com/authswitch 3.2.0 → 3.3.0
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_ts/00_commitinfo_data.js +1 -1
- package/dist_ts/accounts.d.ts +25 -2
- package/dist_ts/accounts.js +112 -15
- package/dist_ts/classes.accountlist.d.ts +2 -1
- package/dist_ts/classes.accountlist.js +8 -5
- package/dist_ts/classes.claudecodeharness.d.ts +2 -2
- package/dist_ts/classes.claudecodeharness.js +3 -3
- package/dist_ts/classes.claudestatus.d.ts +2 -2
- package/dist_ts/classes.claudestatus.js +7 -6
- package/dist_ts/classes.cli.d.ts +15 -0
- package/dist_ts/classes.cli.js +51 -16
- package/dist_ts/classes.codexharness.d.ts +2 -2
- package/dist_ts/classes.codexharness.js +3 -3
- package/dist_ts/classes.codexstatus.d.ts +3 -3
- package/dist_ts/classes.codexstatus.js +12 -11
- package/dist_ts/classes.fileharness.d.ts +2 -2
- package/dist_ts/classes.limits.js +29 -13
- package/dist_ts/classes.opencodeharness.d.ts +2 -2
- package/dist_ts/classes.opencodeharness.js +3 -3
- package/dist_ts/classes.tui.js +7 -4
- package/dist_ts/interfaces.harness.d.ts +17 -5
- package/dist_ts/interfaces.list.d.ts +5 -1
- package/package.json +2 -2
- package/readme.md +62 -13
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/accounts.ts +105 -12
- package/ts/classes.accountlist.ts +7 -4
- package/ts/classes.claudecodeharness.ts +3 -3
- package/ts/classes.claudestatus.ts +7 -6
- package/ts/classes.cli.ts +56 -15
- package/ts/classes.codexharness.ts +3 -3
- package/ts/classes.codexstatus.ts +12 -11
- package/ts/classes.fileharness.ts +2 -2
- package/ts/classes.limits.ts +32 -13
- package/ts/classes.opencodeharness.ts +3 -3
- package/ts/classes.tui.ts +6 -3
- package/ts/interfaces.harness.ts +19 -2
- package/ts/interfaces.list.ts +5 -1
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IHarnessAccount, IHarnessAccountStatus, IHarnessCredentialDrift, TUsageSeverity } from './interfaces.harness.js';
|
|
1
|
+
import type { IAccountType, IHarnessAccount, IHarnessAccountStatus, IHarnessCredentialDrift, TUsageSeverity } from './interfaces.harness.js';
|
|
2
2
|
/** Credential-free, versioned output of list --json. Missing status fields stay omitted. */
|
|
3
3
|
export interface IAccountList {
|
|
4
4
|
schemaVersion: 2;
|
|
@@ -37,6 +37,8 @@ export interface IAccountLimitRow {
|
|
|
37
37
|
accountId: string | null;
|
|
38
38
|
slotId?: string;
|
|
39
39
|
isActive: boolean;
|
|
40
|
+
/** The adapter's plan name (`max`, `pro`), sanitised but not display-formatted; null when none is known or the row names no account. */
|
|
41
|
+
accountType: IAccountType | null;
|
|
40
42
|
/** The provider's own window name, or null when no limit data is available. */
|
|
41
43
|
limitType: string | null;
|
|
42
44
|
scope: 'account' | 'feature' | null;
|
|
@@ -66,6 +68,8 @@ export interface IActiveAccountRow {
|
|
|
66
68
|
account: string | null;
|
|
67
69
|
accountId: string | null;
|
|
68
70
|
slotId?: string;
|
|
71
|
+
/** The adapter's plan name (`max`, `pro`), sanitised but not display-formatted; null when none is known or the row names no account. */
|
|
72
|
+
accountType: IAccountType | null;
|
|
69
73
|
/** When authswitch last saved this login; null when it was never stashed. */
|
|
70
74
|
savedAt: string | null;
|
|
71
75
|
/** Human elapsed time for the same `savedAt`, relative to `generatedAt`. */
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@modelprofile.com/authswitch",
|
|
3
|
-
"version": "3.
|
|
3
|
+
"version": "3.3.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Manage Codex, OpenCode and Claude Code accounts with guided switching and live usage status",
|
|
6
6
|
"main": "dist_ts/index.js",
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@modelprofile.com/flexharness-models": "^7.0.1",
|
|
33
33
|
"@modelprofile.com/flexharness-providers": "^7.0.1",
|
|
34
|
-
"@push.rocks/smartconsole": "^1.
|
|
34
|
+
"@push.rocks/smartconsole": "^1.3.0"
|
|
35
35
|
},
|
|
36
36
|
"files": [
|
|
37
37
|
"ts/**/*",
|
package/readme.md
CHANGED
|
@@ -307,6 +307,34 @@ authswitch codex use
|
|
|
307
307
|
|
|
308
308
|
The account argument also accepts the directory slug, the local part, or a unique prefix, so `authswitch codex use alice` resolves to `alice@example.com` when that is unambiguous. It is deliberately not a substring match — this argument decides which credential becomes active, and an incidental substring hit should never pick an account you did not name.
|
|
309
309
|
|
|
310
|
+
So you know what you switch to, every harness's switch names the account with a
|
|
311
|
+
badge of its type and its current usage:
|
|
312
|
+
|
|
313
|
+
```
|
|
314
|
+
? Which Claude Code account should be active?
|
|
315
|
+
❯ phil@example.com (active) (type:Max) (5h:12%) (w:75%)
|
|
316
|
+
alice@example.com (type:Pro, unverified) (w:40%)
|
|
317
|
+
...
|
|
318
|
+
Now active: alice@example.com (type:Pro, unverified) (w:40%)
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
The badge appears in the account picker of `use`, in the dashboard's switch
|
|
322
|
+
confirmation and on the `Now active:` line that reports a completed switch (in the
|
|
323
|
+
dashboard, its activity log). `type` is the account type as `limits` shows it; `5h`
|
|
324
|
+
and `w` are the account's five-hour and weekly usage windows, rounded to whole
|
|
325
|
+
percent. Feature limits never appear in it, and when several account windows share a
|
|
326
|
+
period without the provider picking one of them, that part is left out rather than
|
|
327
|
+
guessed.
|
|
328
|
+
|
|
329
|
+
Usage is read live, read-only and once per command. The picker looks up the offered
|
|
330
|
+
accounts, at most four at a time, before it asks; a switch named on the command line
|
|
331
|
+
switches first and then looks up only its target for the `Now active:` line. Whatever
|
|
332
|
+
has not answered after 2.5 seconds is left out and its requests are cancelled, so the
|
|
333
|
+
command still exits at once. Every part the provider did not report is omitted, and
|
|
334
|
+
with none known the account is named alone. Reading usage never delays the switch
|
|
335
|
+
beyond that deadline and never fails it. The dashboard reuses the status it already
|
|
336
|
+
shows and makes no extra lookup.
|
|
337
|
+
|
|
310
338
|
## What a switch actually does
|
|
311
339
|
|
|
312
340
|
`use` runs as one sequence, and steps 1 to 4 stop at the first failure:
|
|
@@ -368,8 +396,9 @@ the later ones (`alice@example.com (2)`), so each account keeps its own column i
|
|
|
368
396
|
the metric comparisons and reads the same everywhere. Provider facts already
|
|
369
397
|
represented by structured fields are not repeated. Narrow terminals use compact
|
|
370
398
|
account sections. Missing
|
|
371
|
-
data stays Unavailable or Not reported; known zeroes remain zero.
|
|
372
|
-
|
|
399
|
+
data stays Unavailable or Not reported; known zeroes remain zero. Plans read as in
|
|
400
|
+
`limits` and the switch badge (`Max`), and stored plans are marked unverified
|
|
401
|
+
(`Plus, unverified`). Renewal/cancellation dates are shown only when an adapter can
|
|
373
402
|
provide live billing data. Codex uses the desktop app's account-check endpoint for
|
|
374
403
|
automatic renewal, explicit renewal/cancellation dates and subscription expiry.
|
|
375
404
|
|
|
@@ -394,19 +423,22 @@ Account limits
|
|
|
394
423
|
│ Provider │ Account │ Limit type │ Used % │ Resets in │
|
|
395
424
|
├──────────────────────┼─────────────────────────┼───────────────────────┼────────┼───────────────┤
|
|
396
425
|
┃ Claude Code │ phil@example.com │ Claude weekly │ 75% │ 4d 3h │
|
|
397
|
-
┃
|
|
398
|
-
┃
|
|
426
|
+
┃ │ Max │ Claude five-hour │ 12% │ 1h 2m │
|
|
427
|
+
┃ │ │ Fable weekly │ 88% │ 2d 5h │
|
|
399
428
|
├──────────────────────┼─────────────────────────┼───────────────────────┼────────┼───────────────┤
|
|
400
429
|
┃ Codex │ alice@example.com │ Codex secondary │ 12% │ 3d 4h │
|
|
401
|
-
┃
|
|
402
|
-
┃
|
|
403
|
-
|
|
404
|
-
┃
|
|
430
|
+
┃ │ Pro │ Codex primary │ 37% │ 2h 13m │
|
|
431
|
+
┃ │ │ Code review secondary │ 100% │ 3h 59m │
|
|
432
|
+
┃ ├─────────────────────────┼───────────────────────┼────────┼───────────────┤
|
|
433
|
+
┃ │ bob@example.com │ n/a │ n/a │ n/a │
|
|
434
|
+
┃ │ Plus, unverified │ │ │ │
|
|
405
435
|
├──────────────────────┼─────────────────────────┼───────────────────────┼────────┼───────────────┤
|
|
406
436
|
┃ OpenCode / anthropic │ anthropic API key 20e75 │ n/a │ n/a │ n/a │
|
|
407
437
|
┃ │ 1c3b707 │ │ │ │
|
|
438
|
+
┃ │ n/a │ │ │ │
|
|
408
439
|
├──────────────────────┼─────────────────────────┼───────────────────────┼────────┼───────────────┤
|
|
409
440
|
┃ OpenCode / openai │ alice@example.com │ Codex primary │ 0% │ not scheduled │
|
|
441
|
+
┃ │ Pro │ │ │ │
|
|
410
442
|
└──────────────────────┴─────────────────────────┴───────────────────────┴────────┴───────────────┘
|
|
411
443
|
|
|
412
444
|
n/a — Codex · bob@example.com: Subscription and limits: Login expired or was rejected.
|
|
@@ -427,6 +459,14 @@ session and the weekly total keeps its kind in parentheses
|
|
|
427
459
|
(`Claude weekly (weekly_oauth)`). The provider column names the harness, qualified by
|
|
428
460
|
the credential slot when a harness owns several (`OpenCode / openai`).
|
|
429
461
|
|
|
462
|
+
Each provider and each account is named once. The **Provider** cell spans all rows of
|
|
463
|
+
its provider, and the **Account** cell spans the rows of its account with two lines:
|
|
464
|
+
the account label, and beneath it, dimmed, the **account type** — the provider's own
|
|
465
|
+
plan name with its first letter capitalised (`Max`, `Pro`, `Prolite`). A type read
|
|
466
|
+
from the stored login because the live lookup failed is marked `unverified` (`Plus, unverified`), and
|
|
467
|
+
an account whose provider reports no plan, such as an API-key login, shows `n/a`. A
|
|
468
|
+
provider row without an account has no type line.
|
|
469
|
+
|
|
430
470
|
**Used %** is coloured by the provider's own reading when it reports one: orange for
|
|
431
471
|
`warning`, red for `critical`, uncoloured for `normal`. Without a reading, `n/a` is
|
|
432
472
|
orange and an exhausted window (100% or more) is red. Colour is omitted when
|
|
@@ -435,7 +475,8 @@ columns.
|
|
|
435
475
|
|
|
436
476
|
Rows are grouped in two independent ways. A divider separates one account from the
|
|
437
477
|
next, so an account's limit types read as one block; a provider row without an
|
|
438
|
-
account is a block of its own.
|
|
478
|
+
account is a block of its own. Inside a provider's span the divider starts at the
|
|
479
|
+
**Account** column, and a change of provider draws a full rule. A line along the left edge (`┃`) marks the harness:
|
|
439
480
|
all its rows share one colour, including every credential slot of an OpenCode
|
|
440
481
|
installation, and the next harness takes the next colour (cyan, orange, green, pink,
|
|
441
482
|
blue, then again from the start). Without colour the `┃` edge still shows where the
|
|
@@ -443,8 +484,9 @@ data rows are. Groups follow harness and account ids, never labels: two accounts
|
|
|
443
484
|
that share an email are separate blocks, ordered by account id, and two harnesses
|
|
444
485
|
that share a label are ordered by harness id. A harness is placed by its first
|
|
445
486
|
provider name, so no other provider sorts between its slots. Below 40 columns the
|
|
446
|
-
table becomes labelled lines;
|
|
447
|
-
colour line is
|
|
487
|
+
table becomes labelled lines; every row repeats its provider and its account with
|
|
488
|
+
the type as a second line, a rule line separates the accounts, and no colour line is
|
|
489
|
+
drawn.
|
|
448
490
|
|
|
449
491
|
`authswitch active` answers "what am I logged in as" for every harness at once:
|
|
450
492
|
|
|
@@ -502,7 +544,10 @@ arguments to these commands exit with 2.
|
|
|
502
544
|
|
|
503
545
|
`limits --json` emits `IAccountLimits` and `active --json` emits `IActiveAccounts`:
|
|
504
546
|
the same rows as the tables, plus the machine-readable fields the tables condense —
|
|
505
|
-
`accountId`, `slotId`, `
|
|
547
|
+
`accountId`, `slotId`, `accountType` (`{ plan, source }` with the adapter's plan
|
|
548
|
+
name, such as `max` or `pro`, sanitised but not display-formatted, and `source` `live`
|
|
549
|
+
or `stored`; null when no type is known or the row names no account; both documents
|
|
550
|
+
carry it), `scope`, `windowSeconds`, the ISO `resetAt` beside the human
|
|
506
551
|
`resetsIn`, the provider's `severity` (`normal`, `warning`, `critical`, or null when
|
|
507
552
|
it gave none) and `headline` (true for the window the provider picks for a
|
|
508
553
|
single-value summary), the ISO `savedAt` beside the human `savedAgo`, the per-row
|
|
@@ -595,7 +640,11 @@ process.exitCode = await cli.run(process.argv.slice(2));
|
|
|
595
640
|
|
|
596
641
|
Each adapter owns discovery, identity matching, save verification, credential
|
|
597
642
|
storage, switching and process lifecycle, live status APIs, and diagnostics.
|
|
598
|
-
`readState()` and `readAccountStatus()` must be read-only. `
|
|
643
|
+
`readState()` and `readAccountStatus()` must be read-only. `readAccountStatus(accountId,
|
|
644
|
+
options)` receives an optional `IHarnessStatusOptions` whose `signal` cancels the
|
|
645
|
+
lookup's requests; authswitch aborts it when a status is no longer wanted, such as a
|
|
646
|
+
switch badge past its deadline or a closed dashboard, so an adapter should pass it to
|
|
647
|
+
every request it makes. `switchAccount()` must
|
|
599
648
|
preserve and verify the outgoing credential without relying on the interactive
|
|
600
649
|
save prompt. `removeAccount()` removes only the saved copy. Status facts carry
|
|
601
650
|
provider-specific labels and units; unsupported capabilities are explicit. Adapter
|
package/ts/00_commitinfo_data.ts
CHANGED
package/ts/accounts.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IAuthHarness, IHarnessAccount, IHarnessAccountStatus, IHarnessCredentialDrift, IHarnessState, IHarnessUsageWindow } from './interfaces.harness.js';
|
|
1
|
+
import type { IAccountType, IAuthHarness, IHarnessAccount, IHarnessAccountStatus, IHarnessCredentialDrift, IHarnessState, IHarnessUsageWindow } from './interfaces.harness.js';
|
|
2
2
|
import { plainText } from './formatting.js';
|
|
3
3
|
|
|
4
4
|
export interface IAccountRow {
|
|
@@ -68,10 +68,24 @@ export const credentialDriftNote = (harnessIdArg: string, driftArg: IHarnessCred
|
|
|
68
68
|
+ (driftArg.expectedLabel === null ? 'Switch to the intended account again.'
|
|
69
69
|
: `Run authswitch ${plainText(harnessIdArg)} use ${plainText(driftArg.expectedLabel)} again.`);
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
71
|
+
/**
|
|
72
|
+
* How every view marks a plan that comes from the saved login because the provider did not confirm it: the
|
|
73
|
+
* word both earlier plan cells used, kept short so the type line of a limits cell does not wrap.
|
|
74
|
+
*/
|
|
75
|
+
const STORED_PLAN_MARK = 'unverified';
|
|
76
|
+
/**
|
|
77
|
+
* How an account type reads in every view: the provider's plan name with its first letter capitalised
|
|
78
|
+
* (`max` reads `Max`), and a plan taken from the stored login marked `Plus, unverified`. The text holds no
|
|
79
|
+
* parentheses, so it reads the same inside a badge part. Nothing is invented: an unknown type is null, and
|
|
80
|
+
* each view decides how to show that.
|
|
81
|
+
*/
|
|
82
|
+
export const accountTypeText = (typeArg: IAccountType | null | undefined): string | null => {
|
|
83
|
+
if (!typeArg) return null;
|
|
84
|
+
const plan = plainText(typeArg.plan);
|
|
85
|
+
return `${plan.charAt(0).toUpperCase()}${plan.slice(1)}${typeArg.source === 'stored' ? `, ${STORED_PLAN_MARK}` : ''}`;
|
|
74
86
|
};
|
|
87
|
+
export const accountPlan = (rowArg: IAccountRow): string =>
|
|
88
|
+
accountTypeText(rowArg.status?.summary?.subscription) ?? (rowArg.status ? 'Unavailable' : 'Loading');
|
|
75
89
|
type TAccountStatusRow = Pick<IAccountRow, 'status'>;
|
|
76
90
|
/**
|
|
77
91
|
* How a single-value summary names a window among the windows it shows. An account window is named by
|
|
@@ -113,6 +127,88 @@ export const accountQuotaSummary = (rowArg: TAccountStatusRow, nowArg: number):
|
|
|
113
127
|
...windows.slice(1).filter(window => window.usedPercent >= 100).map(window => `${usageWindowName(window, windows)} exhausted\nResets in: ${until(window.resetAt, nowArg)}`),
|
|
114
128
|
] : [])].join('\n');
|
|
115
129
|
};
|
|
130
|
+
/** The badge's usage meters: the general account windows a switch decision weighs, never a feature quota. */
|
|
131
|
+
const BADGE_WINDOWS = [['5h', 18000], ['w', 604800]] as const;
|
|
132
|
+
/**
|
|
133
|
+
* The account window a badge part reads: the only account window of that period, or the provider's headline
|
|
134
|
+
* pick among several. Any other choice would be a guess, so the part is left out and no meter reads as another's.
|
|
135
|
+
*/
|
|
136
|
+
const badgeWindow = (windowsArg: readonly IHarnessUsageWindow[], secondsArg: number): IHarnessUsageWindow | undefined => {
|
|
137
|
+
const matches = windowsArg.filter(window => window.scope !== 'feature' && window.durationSeconds === secondsArg);
|
|
138
|
+
const candidates = matches.length > 1 ? matches.filter(window => window.headline === true) : matches;
|
|
139
|
+
return candidates.length === 1 ? candidates[0] : undefined;
|
|
140
|
+
};
|
|
141
|
+
/**
|
|
142
|
+
* What a switch activates, in one line: ` (type:Max) (5h:63%) (w:88%)`, appended to the account label.
|
|
143
|
+
*
|
|
144
|
+
* Each part is the provider's own reading, rounded to a whole percent, and is omitted when it was not
|
|
145
|
+
* reported; with no part known the badge is empty, so a missing reading never reads as zero usage.
|
|
146
|
+
*/
|
|
147
|
+
export const accountBadge = (statusArg: IHarnessAccountStatus | undefined): string => {
|
|
148
|
+
const type = accountTypeText(statusArg?.summary?.subscription);
|
|
149
|
+
const windows = statusArg?.summary?.usageWindows ?? [];
|
|
150
|
+
return [
|
|
151
|
+
...(type === null ? [] : [`type:${type}`]),
|
|
152
|
+
...BADGE_WINDOWS.flatMap(([name, seconds]) => {
|
|
153
|
+
const window = badgeWindow(windows, seconds);
|
|
154
|
+
return window === undefined ? [] : [`${name}:${Math.round(window.usedPercent)}%`];
|
|
155
|
+
}),
|
|
156
|
+
].map(part => ` (${part})`).join('');
|
|
157
|
+
};
|
|
158
|
+
/** How many accounts' statuses are read at once, so a long account list never floods a provider. */
|
|
159
|
+
const STATUS_LOOKUP_CONCURRENCY = 4;
|
|
160
|
+
/**
|
|
161
|
+
* Read-only, bounded status lookups: at most four accounts at a time, started in order, each handed the
|
|
162
|
+
* caller's signal. A lookup that fails reports undefined; once the signal aborts, no further lookup starts.
|
|
163
|
+
* A callback that throws rejects the call, and no other lookup starts after it.
|
|
164
|
+
*/
|
|
165
|
+
const lookupStatuses = async (
|
|
166
|
+
harnessArg: IAuthHarness,
|
|
167
|
+
accountIdsArg: readonly string[],
|
|
168
|
+
optionsArg: { signal?: AbortSignal; onStatus: (indexArg: number, statusArg: IHarnessAccountStatus | undefined) => void | Promise<void> },
|
|
169
|
+
): Promise<void> => {
|
|
170
|
+
let next = 0;
|
|
171
|
+
let failed = false;
|
|
172
|
+
const worker = async (): Promise<void> => {
|
|
173
|
+
while (!failed && next < accountIdsArg.length && !optionsArg.signal?.aborted) {
|
|
174
|
+
const index = next++;
|
|
175
|
+
let status: IHarnessAccountStatus | undefined;
|
|
176
|
+
try { status = await harnessArg.readAccountStatus(accountIdsArg[index], { signal: optionsArg.signal }); }
|
|
177
|
+
catch { status = undefined; }
|
|
178
|
+
try { await optionsArg.onStatus(index, status); }
|
|
179
|
+
catch (error) { failed = true; throw error; }
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
await Promise.all(Array.from({ length: Math.min(STATUS_LOOKUP_CONCURRENCY, accountIdsArg.length) }, worker));
|
|
183
|
+
};
|
|
184
|
+
/** How long a switch waits for the badges of the accounts it offers or activates. */
|
|
185
|
+
const ACCOUNT_BADGE_TIMEOUT_MS = 2500;
|
|
186
|
+
/**
|
|
187
|
+
* The badge of every named account whose status answered before one shared deadline.
|
|
188
|
+
*
|
|
189
|
+
* Read-only and never fatal: a switch must not wait on a provider past this deadline, nor fail because
|
|
190
|
+
* usage could not be read, so a lookup that fails or misses the deadline contributes no badge and the
|
|
191
|
+
* caller shows the label alone. At the deadline every lookup still running is cancelled, so no request
|
|
192
|
+
* outlives the call and keeps the command from exiting.
|
|
193
|
+
*/
|
|
194
|
+
export const readAccountBadges = async (
|
|
195
|
+
harnessArg: IAuthHarness,
|
|
196
|
+
accountIdsArg: readonly string[],
|
|
197
|
+
timeoutMsArg = ACCOUNT_BADGE_TIMEOUT_MS,
|
|
198
|
+
): Promise<Map<string, string>> => {
|
|
199
|
+
const badges = new Map<string, string>();
|
|
200
|
+
if (!accountIdsArg.length) return badges;
|
|
201
|
+
const cancel = new AbortController();
|
|
202
|
+
const lookups = lookupStatuses(harnessArg, accountIdsArg, { signal: cancel.signal, onStatus: (index, status) => {
|
|
203
|
+
if (status !== undefined && !cancel.signal.aborted) badges.set(accountIdsArg[index], accountBadge(status));
|
|
204
|
+
} });
|
|
205
|
+
// A referenced timer: the command must stay alive until the deadline even when a stalled lookup holds nothing open.
|
|
206
|
+
let timer: ReturnType<typeof setTimeout> | undefined;
|
|
207
|
+
const deadline = new Promise<void>(resolve => { timer = setTimeout(resolve, timeoutMsArg); });
|
|
208
|
+
try { await Promise.race([lookups, deadline]); }
|
|
209
|
+
finally { clearTimeout(timer); cancel.abort(); }
|
|
210
|
+
return badges;
|
|
211
|
+
};
|
|
116
212
|
export const accountState = (rowArg: IAccountRow): string => `${rowArg.account.isActive ? '* active, ' : ''}${rowArg.account.isStashed ? 'saved' : 'not saved'}`;
|
|
117
213
|
export const accountDetails = (rowArg: IAccountRow): string => [
|
|
118
214
|
plainText(rowArg.account.label), accountState(rowArg),
|
|
@@ -122,7 +218,7 @@ export const accountDetails = (rowArg: IAccountRow): string => [
|
|
|
122
218
|
...(rowArg.status?.problems.map(problemArg => `Unavailable: ${plainText(problemArg)}`) ?? []),
|
|
123
219
|
].join('\n');
|
|
124
220
|
|
|
125
|
-
/** Read-only, bounded parallel status lookup. No account is switched to inspect it. */
|
|
221
|
+
/** Read-only, bounded parallel status lookup. No account is switched to inspect it; the signal cancels lookups in flight. */
|
|
126
222
|
export const readAccountRows = async (
|
|
127
223
|
harnessArg: IAuthHarness,
|
|
128
224
|
optionsArg: { signal?: AbortSignal; onUpdate?: (rowsArg: IAccountRow[], stateArg: IHarnessState) => void | Promise<void> } = {},
|
|
@@ -130,12 +226,9 @@ export const readAccountRows = async (
|
|
|
130
226
|
const state = await harnessArg.readState();
|
|
131
227
|
const rows = state.accounts.map(account => ({ harness: harnessArg, account } as IAccountRow));
|
|
132
228
|
await optionsArg.onUpdate?.(rows, state);
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
if (!optionsArg.signal?.aborted) await optionsArg.onUpdate?.(rows, state);
|
|
138
|
-
}));
|
|
139
|
-
}
|
|
229
|
+
await lookupStatuses(harnessArg, rows.map(row => row.account.id), { signal: optionsArg.signal, onStatus: async (index, status) => {
|
|
230
|
+
rows[index].status = status ?? { facts: [], problems: ['Could not read account status.'] };
|
|
231
|
+
if (!optionsArg.signal?.aborted) await optionsArg.onUpdate?.(rows, state);
|
|
232
|
+
} });
|
|
140
233
|
return { rows, state };
|
|
141
234
|
};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
|
-
import { accountQuotaSummary, orderedUsageWindows, readAccountRows, duration, until } from './accounts.js';
|
|
2
|
+
import { accountQuotaSummary, accountTypeText, orderedUsageWindows, readAccountRows, duration, until } from './accounts.js';
|
|
3
3
|
import type { IAuthHarness, IHarnessAccountStatus, IHarnessStatusFact } from './interfaces.harness.js';
|
|
4
4
|
import type { IAccountList, IHarnessAccountList } from './interfaces.list.js';
|
|
5
5
|
import { bold, dim, orange, plainText } from './formatting.js';
|
|
@@ -60,7 +60,8 @@ export const consoleHeading = (textArg: string): void => { process.stdout.write(
|
|
|
60
60
|
* `groupsArg` is smartconsole's optional visual grouping: a divider between runs of consecutive rows with
|
|
61
61
|
* different keys, and a colour line along the left edge per run. Keys are identities, never display labels,
|
|
62
62
|
* and the caller orders the rows so that every group is one run. Below 40 columns the fallback separates
|
|
63
|
-
* divider runs with one dim rule line; it has no left edge, so it draws no colour line
|
|
63
|
+
* divider runs with one dim rule line; it has no left edge, so it draws no colour line, and it repeats a
|
|
64
|
+
* spanning cell on every row it covers. A column's `render` content is what both layouts show.
|
|
64
65
|
*/
|
|
65
66
|
export const consoleTable = async <TRow>(
|
|
66
67
|
outArg: plugins.smartconsole.SmartConsole,
|
|
@@ -74,9 +75,11 @@ export const consoleTable = async <TRow>(
|
|
|
74
75
|
// smartconsole's run semantics: null and undefined are the same key.
|
|
75
76
|
const dividerKeys = rowsArg.map(row => groupsArg?.divider?.(row) ?? null);
|
|
76
77
|
const rule = ` ${dim('─'.repeat(Math.max(1, width - 4)))}\n\n`;
|
|
78
|
+
const content = (column: plugins.smartconsole.IBackendTableColumn<TRow>, row: TRow): string => column.render
|
|
79
|
+
? plugins.smartconsole.color.plain(column.render(row)) : String(column.value(row) ?? 'Unavailable');
|
|
77
80
|
rowsArg.forEach((row, index) => {
|
|
78
81
|
if (index > 0 && dividerKeys[index] !== dividerKeys[index - 1]) process.stdout.write(rule);
|
|
79
|
-
process.stdout.write(columnsArg.map(column => ` ${column.title}: ${
|
|
82
|
+
process.stdout.write(columnsArg.map(column => ` ${column.title}: ${content(column, row).replace(/\n/g, '\n ')}`).join('\n') + '\n\n');
|
|
80
83
|
});
|
|
81
84
|
return;
|
|
82
85
|
}
|
|
@@ -179,7 +182,7 @@ export class AccountListRenderer {
|
|
|
179
182
|
private async overview(accountsArg: TNamedAccount[], nowArg: number): Promise<void> {
|
|
180
183
|
const rows = accountsArg;
|
|
181
184
|
const state = (account: TListedAccount) => `${account.isActive ? '* active\n' : ''}${account.isStashed ? 'saved' : 'not saved'}${account.status.problems.length ? '\n! partial status' : ''}`;
|
|
182
|
-
const plan = (account: TListedAccount) => account.status.summary?.subscription
|
|
185
|
+
const plan = (account: TListedAccount) => accountTypeText(account.status.summary?.subscription) ?? 'Unavailable';
|
|
183
186
|
const billing = (account: TListedAccount) => {
|
|
184
187
|
const info = account.status.summary?.billing;
|
|
185
188
|
if (!info) return 'Unavailable';
|
|
@@ -4,7 +4,7 @@ import { CredentialStore, credentialRecord, credentialText, readCredentialDocume
|
|
|
4
4
|
import { HarnessProcesses } from './classes.harnessprocesses.js';
|
|
5
5
|
import { ClaudeAccountStatus } from './classes.claudestatus.js';
|
|
6
6
|
import { writeSecretFileAtomically } from './helpers.js';
|
|
7
|
-
import type { IHarnessAccountStatus, IHarnessProcessControl } from './interfaces.harness.js';
|
|
7
|
+
import type { IHarnessAccountStatus, IHarnessProcessControl, IHarnessStatusOptions } from './interfaces.harness.js';
|
|
8
8
|
|
|
9
9
|
export interface IClaudeCodeHarnessOptions {
|
|
10
10
|
configDir?: string;
|
|
@@ -107,8 +107,8 @@ export class ClaudeCodeHarness extends FileHarness {
|
|
|
107
107
|
throw new Error('Claude Code switch failed while updating account metadata. The outgoing login remains saved; check doctor before restarting.');
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
|
-
public async readAccountStatus(id: string): Promise<IHarnessAccountStatus> {
|
|
110
|
+
public async readAccountStatus(id: string, optionsArg?: IHarnessStatusOptions): Promise<IHarnessAccountStatus> {
|
|
111
111
|
const account = this.readCredential(id);
|
|
112
|
-
return this.status.read(credentialRecord(account.credential.claudeAiOauth), credentialRecord(account.credential.oauthAccount));
|
|
112
|
+
return this.status.read(credentialRecord(account.credential.claudeAiOauth), credentialRecord(account.credential.oauthAccount), optionsArg);
|
|
113
113
|
}
|
|
114
114
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { credentialRecord, credentialText } from './classes.credentialstore.js';
|
|
2
2
|
import { plainText } from './formatting.js';
|
|
3
|
-
import type { IHarnessAccountStatus, IHarnessUsageWindow, TUsageSeverity } from './interfaces.harness.js';
|
|
3
|
+
import type { IHarnessAccountStatus, IHarnessStatusOptions, IHarnessUsageWindow, TUsageSeverity } from './interfaces.harness.js';
|
|
4
4
|
|
|
5
5
|
class ClaudeStatusError extends Error {}
|
|
6
6
|
|
|
@@ -105,8 +105,9 @@ const claudeUsageWindows = (usage: Record<string, unknown>): IClaudeUsageWindows
|
|
|
105
105
|
/** Read-only Claude Code OAuth account endpoints. No refresh, inference, or browser session is used. */
|
|
106
106
|
export class ClaudeAccountStatus {
|
|
107
107
|
constructor(private readonly fetcher: typeof fetch = globalThis.fetch) {}
|
|
108
|
-
private async get(route: 'usage' | 'profile', accessToken: string): Promise<Record<string, unknown>> {
|
|
109
|
-
const
|
|
108
|
+
private async get(route: 'usage' | 'profile', accessToken: string, cancelArg?: AbortSignal): Promise<Record<string, unknown>> {
|
|
109
|
+
const timeout = AbortSignal.timeout(10_000);
|
|
110
|
+
const signal = cancelArg ? AbortSignal.any([cancelArg, timeout]) : timeout;
|
|
110
111
|
try {
|
|
111
112
|
const response = await this.fetcher(`https://api.anthropic.com/api/oauth/${route}`, {
|
|
112
113
|
method: 'GET', redirect: 'error', signal,
|
|
@@ -133,12 +134,12 @@ export class ClaudeAccountStatus {
|
|
|
133
134
|
try { return credentialRecord(JSON.parse(Buffer.concat(chunks).toString('utf8'))); }
|
|
134
135
|
catch { throw new ClaudeStatusError('The Claude account service returned invalid data.'); }
|
|
135
136
|
} catch (error) {
|
|
136
|
-
if (signal.aborted) throw new ClaudeStatusError('The Claude account request timed out.');
|
|
137
|
+
if (signal.aborted) throw new ClaudeStatusError(timeout.aborted ? 'The Claude account request timed out.' : 'The Claude account request was cancelled.');
|
|
137
138
|
if (error instanceof ClaudeStatusError) throw error;
|
|
138
139
|
throw new ClaudeStatusError('The Claude account service could not be reached.');
|
|
139
140
|
}
|
|
140
141
|
}
|
|
141
|
-
public async read(oauth: Record<string, unknown>, account: Record<string, unknown
|
|
142
|
+
public async read(oauth: Record<string, unknown>, account: Record<string, unknown>, optionsArg: IHarnessStatusOptions = {}): Promise<IHarnessAccountStatus> {
|
|
142
143
|
const result: IHarnessAccountStatus = { facts: [], problems: [] };
|
|
143
144
|
const storedPlan = credentialText(oauth.subscriptionType);
|
|
144
145
|
if (storedPlan) result.summary = { subscription: { plan: storedPlan, source: 'stored' } };
|
|
@@ -148,7 +149,7 @@ export class ClaudeAccountStatus {
|
|
|
148
149
|
}
|
|
149
150
|
const token = credentialText(oauth.accessToken);
|
|
150
151
|
if (!token) return { facts: [], problems: ['No usable Claude Code access token is available.'] };
|
|
151
|
-
const [profile, usage] = await Promise.allSettled([this.get('profile', token), this.get('usage', token)]);
|
|
152
|
+
const [profile, usage] = await Promise.allSettled([this.get('profile', token, optionsArg.signal), this.get('usage', token, optionsArg.signal)]);
|
|
152
153
|
if (profile.status === 'fulfilled') {
|
|
153
154
|
try {
|
|
154
155
|
const user = credentialRecord(profile.value.account);
|
package/ts/classes.cli.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { AuthSwitchTui } from './classes.tui.js';
|
|
|
7
7
|
import { AglAuthSwitchCoordinator, AuthSwitchOperations, type TAuthSwitchCoordinator, type TAuthSwitchMutation } from './classes.operations.js';
|
|
8
8
|
import { AccountListRenderer, readAccountList } from './classes.accountlist.js';
|
|
9
9
|
import { accountLimits, activeAccounts, CondensedRenderer } from './classes.limits.js';
|
|
10
|
-
import { credentialDriftNote, duration, orderedUsageWindows, until } from './accounts.js';
|
|
10
|
+
import { credentialDriftNote, duration, orderedUsageWindows, readAccountBadges, until } from './accounts.js';
|
|
11
11
|
import { describeHarnessProcesses, describeStopOutcome } from './classes.harnessprocesses.js';
|
|
12
12
|
import { defaultPreusePrompt, PreuseError, validatePreuseOptions } from './preuse.js';
|
|
13
13
|
import type { CodexSwitcher } from './classes.codexswitcher.js';
|
|
@@ -25,6 +25,18 @@ const STOP_COMMANDS = ['use', 'stash'];
|
|
|
25
25
|
/** Read-only overviews that work across every registered harness and support --json. */
|
|
26
26
|
const OVERVIEW_COMMANDS = ['list', 'ls', 'limits', 'active'];
|
|
27
27
|
|
|
28
|
+
/** An account as prompts and reports name it: its label, qualified by its credential slot when it has one. */
|
|
29
|
+
const accountName = (accountArg: IHarnessAccount): string =>
|
|
30
|
+
`${plainText(accountArg.label)}${accountArg.slotId ? ` (${plainText(accountArg.slotId)})` : ''}`;
|
|
31
|
+
|
|
32
|
+
/** The account a command acts on, named as it was offered, with the badge it was offered with. */
|
|
33
|
+
interface IChosenAccount {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
/** ` (type:Max) (5h:63%) (w:88%)`, empty when no part is known, or null when it was not read. */
|
|
37
|
+
badge: string | null;
|
|
38
|
+
}
|
|
39
|
+
|
|
28
40
|
/** Harness-independent command routing and interactive account management. */
|
|
29
41
|
export class AuthSwitchCli {
|
|
30
42
|
private readonly harnesses = new Map<string, IAuthHarness>();
|
|
@@ -406,7 +418,7 @@ ${bold('Environment')}
|
|
|
406
418
|
for (const current of state.accounts.filter(account => account.isActive && !account.isStashed)) {
|
|
407
419
|
const answer = await this.out.prompts.ask({
|
|
408
420
|
name: 'saveCurrent', type: 'confirm',
|
|
409
|
-
message: `${
|
|
421
|
+
message: `${accountName(current)} is not saved in ${harnessArg.label}. Save this login before switching?`, default: true,
|
|
410
422
|
});
|
|
411
423
|
if (answer !== true) return 'declined';
|
|
412
424
|
if (this.reportOutcome(await this.mutate(harnessArg, { harnessId: harnessArg.id, action: 'save', keepActive: true, accountId: current.id })) !== 0) return 'failed';
|
|
@@ -457,12 +469,12 @@ ${bold('Environment')}
|
|
|
457
469
|
case 'list': code = await this.commandList([harnessArg]); break;
|
|
458
470
|
case 'doctor': code = this.reportOutcome(await harnessArg.diagnose()); break;
|
|
459
471
|
case 'drop': {
|
|
460
|
-
const
|
|
461
|
-
if (!
|
|
472
|
+
const target = await this.promptForAccount(harnessArg, `Which saved ${harnessArg.label} account should be removed?`, { allowBack: true, includeIncomplete: true });
|
|
473
|
+
if (!target) break;
|
|
462
474
|
const confirmation = await this.out.prompts.ask({
|
|
463
|
-
name: 'confirmDrop', type: 'confirm', message: `Remove the saved login for ${plainText(id)}? The active login stays as it is.`, default: false,
|
|
475
|
+
name: 'confirmDrop', type: 'confirm', message: `Remove the saved login for ${plainText(target.id)}? The active login stays as it is.`, default: false,
|
|
464
476
|
});
|
|
465
|
-
if (confirmation === true) code = this.reportOutcome(await harnessArg.removeAccount(id));
|
|
477
|
+
if (confirmation === true) code = this.reportOutcome(await harnessArg.removeAccount(target.id));
|
|
466
478
|
break;
|
|
467
479
|
}
|
|
468
480
|
case 'harness': return null;
|
|
@@ -492,7 +504,7 @@ ${bold('Environment')}
|
|
|
492
504
|
if (accounts.length === 1) return accounts[0];
|
|
493
505
|
if (!accounts.length) { process.stderr.write('No matching active login to save.\n'); return undefined; }
|
|
494
506
|
if (!canPrompt()) { process.stderr.write(`Several logins are active. Use authswitch ${harness.id} stash <account/provider> [--keep].\n`); return undefined; }
|
|
495
|
-
const id = await this.out.prompts.ask({ name: 'activeAccount', type: 'list', message: 'Which active login should be saved?', choices: accounts.map(account => ({ name:
|
|
507
|
+
const id = await this.out.prompts.ask({ name: 'activeAccount', type: 'list', message: 'Which active login should be saved?', choices: accounts.map(account => ({ name: accountName(account), value: account.id })) });
|
|
496
508
|
return accounts.find(account => account.id === id);
|
|
497
509
|
}
|
|
498
510
|
|
|
@@ -538,6 +550,14 @@ ${bold('Environment')}
|
|
|
538
550
|
return null;
|
|
539
551
|
}
|
|
540
552
|
|
|
553
|
+
/**
|
|
554
|
+
* Activate a saved account and report what is now active with its badge.
|
|
555
|
+
*
|
|
556
|
+
* The badge is read once per command through the bounded status read: the chooser reads it before it
|
|
557
|
+
* offers the accounts and the report repeats it, while a target named on the command line is read only
|
|
558
|
+
* after its switch, so nothing waits on it. A read that fails or does not answer in time leaves the name
|
|
559
|
+
* alone and never decides the outcome.
|
|
560
|
+
*/
|
|
541
561
|
private async commandUse(harnessArg: IAuthHarness, referenceArg?: string, allowBackArg = false, stopModeArg?: TStopMode): Promise<number> {
|
|
542
562
|
let id: string | null = null;
|
|
543
563
|
if (referenceArg) {
|
|
@@ -548,21 +568,42 @@ ${bold('Environment')}
|
|
|
548
568
|
const offered = await this.offerToSave(harnessArg);
|
|
549
569
|
if (offered !== 'ready') return offered === 'failed' ? 1 : 0;
|
|
550
570
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
571
|
+
const target = id === null
|
|
572
|
+
? await this.promptForAccount(harnessArg, `Which ${harnessArg.label} account should be active?`, { allowBack: allowBackArg, withBadges: true })
|
|
573
|
+
: await this.namedAccount(harnessArg, id);
|
|
574
|
+
if (target === null) return allowBackArg ? 0 : 1;
|
|
575
|
+
const code = this.reportOutcome(await this.mutate(harnessArg, { harnessId: harnessArg.id, action: 'switch', accountId: target.id }, stopModeArg));
|
|
576
|
+
if (code !== 0) return code;
|
|
577
|
+
const badge = target.badge ?? (await readAccountBadges(harnessArg, [target.id])).get(target.id) ?? '';
|
|
578
|
+
process.stdout.write(`Now active: ${bold(target.name)}${badge}\n`);
|
|
579
|
+
return code;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* A switch target named on the command line; its badge is not read here, so the switch never waits on it.
|
|
584
|
+
*
|
|
585
|
+
* The reference already resolved to this id, so the name is presentation only: a state read that fails
|
|
586
|
+
* names the account by its id and leaves the switch, which reports any real credential problem, to run.
|
|
587
|
+
*/
|
|
588
|
+
private async namedAccount(harnessArg: IAuthHarness, idArg: string): Promise<IChosenAccount> {
|
|
589
|
+
let account: IHarnessAccount | undefined;
|
|
590
|
+
try { account = (await harnessArg.readState()).accounts.find(item => item.id === idArg); }
|
|
591
|
+
catch { account = undefined; }
|
|
592
|
+
return { id: idArg, name: account ? accountName(account) : plainText(idArg), badge: null };
|
|
554
593
|
}
|
|
555
594
|
|
|
556
|
-
private async promptForAccount(harnessArg: IAuthHarness, messageArg: string,
|
|
557
|
-
const accounts = (await harnessArg.readState()).accounts.filter((accountArg) => accountArg.isStashed || (
|
|
595
|
+
private async promptForAccount(harnessArg: IAuthHarness, messageArg: string, optionsArg: { allowBack?: boolean; includeIncomplete?: boolean; withBadges?: boolean } = {}): Promise<IChosenAccount | null> {
|
|
596
|
+
const accounts = (await harnessArg.readState()).accounts.filter((accountArg) => accountArg.isStashed || (optionsArg.includeIncomplete === true && accountArg.savedAt !== null));
|
|
558
597
|
if (accounts.length === 0) { process.stderr.write(`${red('no stashed accounts')} - run \`authswitch ${harnessArg.id} stash\` first\n`); return null; }
|
|
559
598
|
if (!canPrompt()) { process.stderr.write(`${red('an account is required')} without an interactive terminal: authswitch ${harnessArg.id} use <account>\n`); return null; }
|
|
599
|
+
const badges = optionsArg.withBadges ? await readAccountBadges(harnessArg, accounts.map((accountArg) => accountArg.id)) : null;
|
|
600
|
+
const offered = accounts.map((accountArg): IChosenAccount => ({ id: accountArg.id, name: accountName(accountArg), badge: badges === null ? null : badges.get(accountArg.id) ?? '' }));
|
|
560
601
|
const answer = await this.out.prompts.ask({
|
|
561
602
|
name: 'account', type: 'list', message: messageArg,
|
|
562
603
|
default: accounts.find((accountArg) => accountArg.isActive)?.id ?? accounts[0].id,
|
|
563
|
-
choices: [...accounts.map((accountArg) => ({ name: `${
|
|
564
|
-
...(
|
|
604
|
+
choices: [...accounts.map((accountArg, index) => ({ name: `${offered[index].name}${accountArg.isActive ? ' (active)' : ''}${offered[index].badge ?? ''}`, value: accountArg.id })),
|
|
605
|
+
...(optionsArg.allowBack ? [{ name: 'Back', value: null }] : [])],
|
|
565
606
|
});
|
|
566
|
-
return
|
|
607
|
+
return offered.find((accountArg) => accountArg.id === answer) ?? null;
|
|
567
608
|
}
|
|
568
609
|
}
|
|
@@ -2,7 +2,7 @@ import { CodexSwitcher } from './classes.codexswitcher.js';
|
|
|
2
2
|
import { CodexAccountStatus } from './classes.codexstatus.js';
|
|
3
3
|
import { CodexPreuse } from './classes.codexpreuse.js';
|
|
4
4
|
import { PreuseError } from './preuse.js';
|
|
5
|
-
import type { IAuthHarness, IHarnessAccount, IHarnessAccountStatus, IHarnessOutcome, IHarnessState, IHarnessPreuseOptions, IHarnessPreuseResult } from './interfaces.harness.js';
|
|
5
|
+
import type { IAuthHarness, IHarnessAccount, IHarnessAccountStatus, IHarnessOutcome, IHarnessState, IHarnessPreuseOptions, IHarnessPreuseResult, IHarnessStatusOptions } from './interfaces.harness.js';
|
|
6
6
|
import type { IStashListing } from './interfaces.js';
|
|
7
7
|
import { countPaired, enrollmentState } from './helpers.js';
|
|
8
8
|
import { beginSavedOpenAiLogin } from './classes.login.js';
|
|
@@ -70,10 +70,10 @@ export class CodexHarness implements IAuthHarness {
|
|
|
70
70
|
};
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
public async readAccountStatus(accountIdArg: string): Promise<IHarnessAccountStatus> {
|
|
73
|
+
public async readAccountStatus(accountIdArg: string, optionsArg?: IHarnessStatusOptions): Promise<IHarnessAccountStatus> {
|
|
74
74
|
const credential = this.readAccountCredential(accountIdArg);
|
|
75
75
|
if (!credential) return { facts: [], problems: ['No matching credential is available for status lookup.'] };
|
|
76
|
-
return this.accountStatus.read(credential.raw, credential.identity);
|
|
76
|
+
return this.accountStatus.read(credential.raw, credential.identity, optionsArg);
|
|
77
77
|
}
|
|
78
78
|
|
|
79
79
|
public async preuseAccount(accountIdArg: string, optionsArg: IHarnessPreuseOptions): Promise<IHarnessPreuseResult> {
|