@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,5 +1,5 @@
|
|
|
1
1
|
import type { ICodexIdentity } from './interfaces.js';
|
|
2
|
-
import type { IHarnessAccountStatus, IHarnessStatusSummary } from './interfaces.harness.js';
|
|
2
|
+
import type { IHarnessAccountStatus, IHarnessStatusOptions, IHarnessStatusSummary } from './interfaces.harness.js';
|
|
3
3
|
import { commitinfo } from './00_commitinfo_data.js';
|
|
4
4
|
|
|
5
5
|
// Verified Chromium-compatible billing request profile, independent of any locally installed browser.
|
|
@@ -39,7 +39,7 @@ class StatusRequestError extends Error {}
|
|
|
39
39
|
export class CodexAccountStatus {
|
|
40
40
|
constructor(private readonly fetcher: typeof fetch = globalThis.fetch) {}
|
|
41
41
|
|
|
42
|
-
public async read(rawArg: string, identityArg: ICodexIdentity): Promise<IHarnessAccountStatus> {
|
|
42
|
+
public async read(rawArg: string, identityArg: ICodexIdentity, optionsArg: IHarnessStatusOptions = {}): Promise<IHarnessAccountStatus> {
|
|
43
43
|
if (identityArg.kind !== 'chatgpt') return {
|
|
44
44
|
facts: [{ label: 'Authentication', value: 'API key' }, { label: 'Subscription and usage', value: 'Not exposed by Codex for API-key logins' }], problems: [],
|
|
45
45
|
};
|
|
@@ -47,18 +47,18 @@ export class CodexAccountStatus {
|
|
|
47
47
|
try { accessToken = text(record(record(JSON.parse(rawArg)).tokens).access_token); }
|
|
48
48
|
catch { return { facts: [], problems: ['The login has no usable access token. Log in again with Codex, then save the account.'] }; }
|
|
49
49
|
if (!identityArg.accountId) return { facts: [], problems: ['The login has no account ID for a status lookup.'] };
|
|
50
|
-
return this.readCredential(accessToken, identityArg.accountId, identityArg.planType);
|
|
50
|
+
return this.readCredential(accessToken, identityArg.accountId, identityArg.planType, optionsArg);
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
/** Read the ChatGPT account directly, independent of a harness's on-disk format. */
|
|
54
|
-
public async readCredential(accessToken: string, accountId: string, planType?: string | null): Promise<IHarnessAccountStatus> {
|
|
54
|
+
public async readCredential(accessToken: string, accountId: string, planType?: string | null, optionsArg: IHarnessStatusOptions = {}): Promise<IHarnessAccountStatus> {
|
|
55
55
|
const result: IHarnessAccountStatus = { facts: [], problems: [] };
|
|
56
56
|
const identityArg = { accountId, planType };
|
|
57
57
|
const sections = await Promise.allSettled([
|
|
58
|
-
this.get('wham/usage', accessToken, identityArg.accountId),
|
|
59
|
-
this.get('wham/rate-limit-reset-credits', accessToken, identityArg.accountId),
|
|
60
|
-
this.get('wham/profiles/me', accessToken, identityArg.accountId),
|
|
61
|
-
this.get('accounts/check/v4-2023-04-27', accessToken, identityArg.accountId),
|
|
58
|
+
this.get('wham/usage', accessToken, identityArg.accountId, optionsArg.signal),
|
|
59
|
+
this.get('wham/rate-limit-reset-credits', accessToken, identityArg.accountId, optionsArg.signal),
|
|
60
|
+
this.get('wham/profiles/me', accessToken, identityArg.accountId, optionsArg.signal),
|
|
61
|
+
this.get('accounts/check/v4-2023-04-27', accessToken, identityArg.accountId, optionsArg.signal),
|
|
62
62
|
]);
|
|
63
63
|
const usage = sections[0];
|
|
64
64
|
if (usage.status === 'fulfilled' && usage.value.account_id != null && usage.value.account_id !== identityArg.accountId) {
|
|
@@ -92,8 +92,9 @@ export class CodexAccountStatus {
|
|
|
92
92
|
return result;
|
|
93
93
|
}
|
|
94
94
|
|
|
95
|
-
private async get(pathArg: string, tokenArg: string, accountIdArg: string): Promise<Record<string, unknown>> {
|
|
96
|
-
const
|
|
95
|
+
private async get(pathArg: string, tokenArg: string, accountIdArg: string, cancelArg?: AbortSignal): Promise<Record<string, unknown>> {
|
|
96
|
+
const timeout = AbortSignal.timeout(10_000);
|
|
97
|
+
const signal = cancelArg ? AbortSignal.any([cancelArg, timeout]) : timeout;
|
|
97
98
|
try {
|
|
98
99
|
const response = await this.fetcher(`https://chatgpt.com/backend-api/${pathArg}`, {
|
|
99
100
|
method: 'GET', redirect: 'error', signal,
|
|
@@ -125,7 +126,7 @@ export class CodexAccountStatus {
|
|
|
125
126
|
catch { throw new StatusRequestError('The service returned invalid status data.'); }
|
|
126
127
|
} catch (errorArg) {
|
|
127
128
|
// Never surface remote bodies, request headers, token strings or arbitrary fetch errors.
|
|
128
|
-
if (signal.aborted) throw new Error('The status request timed out.');
|
|
129
|
+
if (signal.aborted) throw new Error(timeout.aborted ? 'The status request timed out.' : 'The status request was cancelled.');
|
|
129
130
|
if (errorArg instanceof StatusRequestError) throw errorArg;
|
|
130
131
|
throw new Error('The status service could not be reached.');
|
|
131
132
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { IAuthHarness, IHarnessAccountStatus, IHarnessCredentialDrift, IHarnessOutcome, IHarnessProcessControl, IHarnessState } from './interfaces.harness.js';
|
|
1
|
+
import type { IAuthHarness, IHarnessAccountStatus, IHarnessCredentialDrift, IHarnessOutcome, IHarnessProcessControl, IHarnessState, IHarnessStatusOptions } from './interfaces.harness.js';
|
|
2
2
|
import { credentialHash, CredentialStore, type IStoredCredential, type ISwitchRecord } from './classes.credentialstore.js';
|
|
3
3
|
import { pauseSynchronously } from './helpers.js';
|
|
4
4
|
import { plainText } from './formatting.js';
|
|
@@ -28,7 +28,7 @@ export abstract class FileHarness implements IAuthHarness {
|
|
|
28
28
|
/** Raw bytes of the native files a snapshot's credentials are taken from, in a fixed order. */
|
|
29
29
|
protected abstract nativeSources(): (string | null)[];
|
|
30
30
|
protected abstract inspect(credential: Record<string, unknown>, slotId: string): IFileAccount;
|
|
31
|
-
public abstract readAccountStatus(id: string): Promise<IHarnessAccountStatus>;
|
|
31
|
+
public abstract readAccountStatus(id: string, optionsArg?: IHarnessStatusOptions): Promise<IHarnessAccountStatus>;
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* A snapshot proven not to have been taken across a native write.
|
package/ts/classes.limits.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
|
-
import { compactSince, compactUntil, credentialDriftNote, orderedUsageWindows } from './accounts.js';
|
|
2
|
+
import { accountTypeText, compactSince, compactUntil, credentialDriftNote, orderedUsageWindows } from './accounts.js';
|
|
3
3
|
import { consoleHeading, consoleTable } from './classes.accountlist.js';
|
|
4
4
|
import type { IAccountList, IAccountLimitRow, IAccountLimits, IActiveAccountDrift, IActiveAccountRow, IActiveAccounts, IHarnessAccountList } from './interfaces.list.js';
|
|
5
|
-
import type { IHarnessCredentialDrift, TUsageSeverity } from './interfaces.harness.js';
|
|
5
|
+
import type { IAccountType, IHarnessCredentialDrift, TUsageSeverity } from './interfaces.harness.js';
|
|
6
6
|
import { dim, plainText } from './formatting.js';
|
|
7
7
|
|
|
8
8
|
type TListedAccount = IHarnessAccountList['accounts'][number];
|
|
@@ -46,6 +46,12 @@ const accountKeys = (harnessArg: IHarnessAccountList, accountArg: TListedAccount
|
|
|
46
46
|
...(accountArg.slotId === undefined ? {} : { slotId: plainText(accountArg.slotId) }),
|
|
47
47
|
});
|
|
48
48
|
|
|
49
|
+
/** The account's plan, sanitised like every other label and copied so that no two rows share one object. */
|
|
50
|
+
const accountType = (accountArg: TListedAccount): IAccountType | null => {
|
|
51
|
+
const subscription = accountArg.status.summary?.subscription;
|
|
52
|
+
return subscription === undefined ? null : { plan: plainText(subscription.plan), source: subscription.source };
|
|
53
|
+
};
|
|
54
|
+
|
|
49
55
|
const harnessProblem = (harnessArg: IHarnessAccountList): string | null =>
|
|
50
56
|
harnessArg.problems.length ? harnessArg.problems.map(plainText).join(' ') : null;
|
|
51
57
|
|
|
@@ -76,6 +82,9 @@ const byProviderThenAccount = <TRow extends TCondensedRow>(rowsArg: TRow[]): TRo
|
|
|
76
82
|
/** One colour line per harness: an OpenCode installation's slots share it. */
|
|
77
83
|
const harnessRuns = (rowArg: TCondensedRow): string => rowArg.harnessId;
|
|
78
84
|
|
|
85
|
+
/** One Provider cell per provider name within a harness; two harnesses that share a label stay apart. */
|
|
86
|
+
const providerRuns = (rowArg: TCondensedRow): string => JSON.stringify([rowArg.harnessId, rowArg.provider]);
|
|
87
|
+
|
|
79
88
|
/** One divider run per account identity; a provider-level row without an account is its own run. */
|
|
80
89
|
const accountRuns = (rowArg: TCondensedRow): string => JSON.stringify([rowArg.harnessId, rowArg.slotId ?? null, rowArg.accountId]);
|
|
81
90
|
|
|
@@ -108,23 +117,23 @@ export const accountLimits = (listArg: IAccountList): IAccountLimits => {
|
|
|
108
117
|
limitType: null, scope: null, windowSeconds: null, usedPercent: null, resetAt: null, resetsIn: UNAVAILABLE, severity: null, headline: false,
|
|
109
118
|
} as const;
|
|
110
119
|
if (problem !== null) {
|
|
111
|
-
rows.push({ harnessId: harness.id, provider: providerName(harness), account: null, accountId: null, isActive: false, ...empty, unavailableReason: problem });
|
|
120
|
+
rows.push({ harnessId: harness.id, provider: providerName(harness), account: null, accountId: null, isActive: false, accountType: null, ...empty, unavailableReason: problem });
|
|
112
121
|
continue;
|
|
113
122
|
}
|
|
114
123
|
if (!harness.accounts.length) {
|
|
115
|
-
rows.push({ harnessId: harness.id, provider: providerName(harness), account: null, accountId: null, isActive: false, ...empty,
|
|
124
|
+
rows.push({ harnessId: harness.id, provider: providerName(harness), account: null, accountId: null, isActive: false, accountType: null, ...empty,
|
|
116
125
|
unavailableReason: harness.saveUnavailableReason === null ? 'No accounts are known for this harness.' : plainText(harness.saveUnavailableReason) });
|
|
117
126
|
continue;
|
|
118
127
|
}
|
|
119
128
|
for (const account of harness.accounts) {
|
|
120
129
|
const windows = orderedUsageWindows(account.status.summary?.usageWindows);
|
|
121
130
|
if (!windows.length) {
|
|
122
|
-
rows.push({ ...accountKeys(harness, account), isActive: account.isActive, ...empty, unavailableReason: missingLimitReason(account) });
|
|
131
|
+
rows.push({ ...accountKeys(harness, account), isActive: account.isActive, accountType: accountType(account), ...empty, unavailableReason: missingLimitReason(account) });
|
|
123
132
|
continue;
|
|
124
133
|
}
|
|
125
134
|
for (const window of windows) {
|
|
126
135
|
rows.push({
|
|
127
|
-
...accountKeys(harness, account), isActive: account.isActive,
|
|
136
|
+
...accountKeys(harness, account), isActive: account.isActive, accountType: accountType(account),
|
|
128
137
|
limitType: plainText(window.label), scope: window.scope ?? 'account', windowSeconds: window.durationSeconds,
|
|
129
138
|
usedPercent: window.usedPercent, resetAt: window.resetAt, resetsIn: compactUntil(window.resetAt, now, UNAVAILABLE),
|
|
130
139
|
severity: window.severity ?? null, headline: window.headline === true, unavailableReason: null,
|
|
@@ -154,14 +163,14 @@ export const activeAccounts = (listArg: IAccountList): IActiveAccounts => {
|
|
|
154
163
|
return index < 0 ? null : activeDrift(harness, drifts.splice(index, 1)[0]);
|
|
155
164
|
};
|
|
156
165
|
if (problem !== null) {
|
|
157
|
-
rows.push({ harnessId: harness.id, provider: providerName(harness), account: null, accountId: null, savedAt: null, savedAgo: UNAVAILABLE, source: 'none', unavailableReason: problem, drift: null });
|
|
166
|
+
rows.push({ harnessId: harness.id, provider: providerName(harness), account: null, accountId: null, accountType: null, savedAt: null, savedAgo: UNAVAILABLE, source: 'none', unavailableReason: problem, drift: null });
|
|
158
167
|
continue;
|
|
159
168
|
}
|
|
160
169
|
const active = harness.accounts.filter(account => account.isActive);
|
|
161
170
|
if (!active.length) {
|
|
162
171
|
const stashed = harness.accounts.filter(account => account.isStashed);
|
|
163
172
|
rows.push({
|
|
164
|
-
harnessId: harness.id, provider: providerName(harness), account: null, accountId: null,
|
|
173
|
+
harnessId: harness.id, provider: providerName(harness), account: null, accountId: null, accountType: null,
|
|
165
174
|
savedAt: null, savedAgo: UNAVAILABLE, source: stashed.length ? 'stash only' : 'none',
|
|
166
175
|
unavailableReason: harness.saveUnavailableReason !== null ? plainText(harness.saveUnavailableReason)
|
|
167
176
|
: stashed.length ? `No login is active; ${stashed.length} saved account(s) can be activated with authswitch ${plainText(harness.id)} use.`
|
|
@@ -171,7 +180,7 @@ export const activeAccounts = (listArg: IAccountList): IActiveAccounts => {
|
|
|
171
180
|
} else {
|
|
172
181
|
for (const account of active) {
|
|
173
182
|
rows.push({
|
|
174
|
-
...accountKeys(harness, account),
|
|
183
|
+
...accountKeys(harness, account), accountType: accountType(account),
|
|
175
184
|
savedAt: account.savedAt, savedAgo: compactSince(account.savedAt, now, UNAVAILABLE),
|
|
176
185
|
source: 'credential file',
|
|
177
186
|
unavailableReason: account.isStashed ? null : 'This active login is not saved yet; authswitch cannot restore it after a switch.',
|
|
@@ -188,6 +197,13 @@ export const activeAccounts = (listArg: IAccountList): IActiveAccounts => {
|
|
|
188
197
|
return { schemaVersion: 1, generatedAt: listArg.generatedAt, complete: listArg.complete, active, notes: [...collectNotes(active), ...driftNotes, ...unattached] };
|
|
189
198
|
};
|
|
190
199
|
|
|
200
|
+
/**
|
|
201
|
+
* The Account cell: the label, and the account type beneath it, dim. An unknown type keeps its line as
|
|
202
|
+
* `n/a`, so every account cell has the same shape; a provider row without an account has no type to name.
|
|
203
|
+
*/
|
|
204
|
+
const accountCell = (rowArg: IAccountLimitRow): plugins.smartconsole.TText => rowArg.account === null ? UNAVAILABLE
|
|
205
|
+
: plugins.smartconsole.color.concat(`${rowArg.account}\n`, plugins.smartconsole.color.text(accountTypeText(rowArg.accountType) ?? UNAVAILABLE, { dim: true, bold: false }));
|
|
206
|
+
|
|
191
207
|
const percent = (rowArg: IAccountLimitRow): string => rowArg.usedPercent === null ? UNAVAILABLE : `${rowArg.usedPercent}%`;
|
|
192
208
|
|
|
193
209
|
const SEVERITY_COLORS: Record<TUsageSeverity, plugins.smartconsole.TColorName | undefined> = { normal: undefined, warning: 'orange', critical: 'red' };
|
|
@@ -209,10 +225,13 @@ export class CondensedRenderer {
|
|
|
209
225
|
|
|
210
226
|
public async renderLimits(limitsArg: IAccountLimits): Promise<void> {
|
|
211
227
|
consoleHeading('Account limits');
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
228
|
+
// The span callbacks record these exact rows, so the table renders this array as it is.
|
|
229
|
+
const rows = limitsArg.limits;
|
|
230
|
+
if (!rows.length) { process.stdout.write(`${dim('No harnesses registered.')}\n`); return; }
|
|
231
|
+
await consoleTable(this.out, rows, [
|
|
232
|
+
{ key: 'provider', title: 'Provider', value: row => row.provider, rowSpan: plugins.smartconsole.rowSpansBy(rows, providerRuns) },
|
|
233
|
+
{ key: 'account', title: 'Account', value: row => row.account ?? UNAVAILABLE, render: accountCell,
|
|
234
|
+
style: row => ({ bold: row.isActive }), rowSpan: plugins.smartconsole.rowSpansBy(rows, accountRuns) },
|
|
216
235
|
{ key: 'limit', title: 'Limit type', value: row => row.limitType ?? UNAVAILABLE },
|
|
217
236
|
{ key: 'used', title: 'Used %', value: percent, align: 'right',
|
|
218
237
|
style: row => ({ foreground: percentColor(row) }) },
|
|
@@ -4,7 +4,7 @@ import { CredentialStore, credentialHash, credentialRecord, credentialText, read
|
|
|
4
4
|
import { HarnessProcesses } from './classes.harnessprocesses.js';
|
|
5
5
|
import { CodexAccountStatus } from './classes.codexstatus.js';
|
|
6
6
|
import { writeSecretFileAtomically } from './helpers.js';
|
|
7
|
-
import type { IHarnessAccountStatus, IHarnessLoginOptions, IHarnessLoginProvider, IHarnessProcessControl } from './interfaces.harness.js';
|
|
7
|
+
import type { IHarnessAccountStatus, IHarnessLoginOptions, IHarnessLoginProvider, IHarnessProcessControl, IHarnessStatusOptions } from './interfaces.harness.js';
|
|
8
8
|
import { beginSavedOpenAiLogin, openCodeCredentialFromLogin } from './classes.login.js';
|
|
9
9
|
import { plainText } from './formatting.js';
|
|
10
10
|
|
|
@@ -100,12 +100,12 @@ export class OpenCodeHarness extends FileHarness {
|
|
|
100
100
|
writeSecretFileAtomically(this.file, raw);
|
|
101
101
|
if (readCredentialDocument(this.file).raw !== raw) throw new Error('Active login verification failed. The outgoing login remains saved.');
|
|
102
102
|
}
|
|
103
|
-
public async readAccountStatus(id: string): Promise<IHarnessAccountStatus> {
|
|
103
|
+
public async readAccountStatus(id: string, optionsArg?: IHarnessStatusOptions): Promise<IHarnessAccountStatus> {
|
|
104
104
|
const account = this.readCredential(id);
|
|
105
105
|
if (account.slotId === 'openai' && account.credential.type === 'oauth') {
|
|
106
106
|
const info = this.openAiInfo(account.credential);
|
|
107
107
|
if (!info.accountId) return { facts: [], problems: ['The OpenAI login has no account ID for a status lookup.'] };
|
|
108
|
-
return this.status.readCredential(credentialText(account.credential.access)!, info.accountId, info.plan);
|
|
108
|
+
return this.status.readCredential(credentialText(account.credential.access)!, info.accountId, info.plan, optionsArg);
|
|
109
109
|
}
|
|
110
110
|
return { facts: [
|
|
111
111
|
{ section: 'Authentication', label: 'Provider', value: account.slotId },
|
package/ts/classes.tui.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as plugins from './plugins.js';
|
|
2
2
|
import type { IAuthHarness, IHarnessOutcome, IHarnessProcess, IHarnessState, IHarnessStopOutcome } from './interfaces.harness.js';
|
|
3
|
-
import { accountDetails, accountPlan, accountQuotaSummary, accountResets, accountState, accountUsageWindows, usageWindowName, until, readAccountRows, type IAccountRow } from './accounts.js';
|
|
3
|
+
import { accountBadge, accountDetails, accountPlan, accountQuotaSummary, accountResets, accountState, accountUsageWindows, usageWindowName, until, readAccountRows, type IAccountRow } from './accounts.js';
|
|
4
4
|
import { plainText } from './formatting.js';
|
|
5
5
|
import { AuthSwitchOperations } from './classes.operations.js';
|
|
6
6
|
import { describeHarnessProcesses, describeStopOutcome } from './classes.harnessprocesses.js';
|
|
@@ -115,11 +115,14 @@ export class AuthSwitchTui {
|
|
|
115
115
|
const verified = await harness.readState();
|
|
116
116
|
if (!verified.accounts.some(account => account.isActive && account.isStashed && account.id === active.id)) { footer.setText('The current login could not be verified as saved. Switching was cancelled.'); return; }
|
|
117
117
|
}
|
|
118
|
-
|
|
118
|
+
// The dashboard already holds this account's status, so the badge costs no further lookup.
|
|
119
|
+
const badge = accountBadge(rows.find(row => row.account.id === target.id)?.status);
|
|
120
|
+
if (!await screen.confirm(`Switch ${plainText(harness.label)} to ${plainText(target.label)}${badge}?`, { confirmLabel: 'Switch' })) return;
|
|
119
121
|
if (screen.signal.aborted) return;
|
|
120
122
|
const outcome = await this.operations.run(harness, { harnessId: harness.id, action: 'switch', accountId: target.id },
|
|
121
123
|
message => screen.confirm(message), settleInstances(screen));
|
|
122
|
-
await refresh(screen);
|
|
124
|
+
await refresh(screen);
|
|
125
|
+
if (showOutcome(outcome)) ui.logs.append(`Now active: ${plainText(target.label)}${badge}`);
|
|
123
126
|
};
|
|
124
127
|
const remove = async (screen: plugins.smartconsole.ITuiContext): Promise<void> => {
|
|
125
128
|
const selected = table.selected;
|
package/ts/interfaces.harness.ts
CHANGED
|
@@ -127,9 +127,20 @@ export interface IHarnessUsageWindow {
|
|
|
127
127
|
headline?: true;
|
|
128
128
|
}
|
|
129
129
|
|
|
130
|
+
/**
|
|
131
|
+
* The provider's own name for an account's plan, and where that name comes from.
|
|
132
|
+
*
|
|
133
|
+
* `live` was read from the provider in this lookup; `stored` is the last name the saved login
|
|
134
|
+
* carries, which is the only answer available while a provider cannot be reached.
|
|
135
|
+
*/
|
|
136
|
+
export interface IAccountType {
|
|
137
|
+
plan: string;
|
|
138
|
+
source: 'live' | 'stored';
|
|
139
|
+
}
|
|
140
|
+
|
|
130
141
|
export interface IHarnessStatusSummary {
|
|
131
142
|
/** A plan name does not imply an active billing status. */
|
|
132
|
-
subscription?:
|
|
143
|
+
subscription?: IAccountType;
|
|
133
144
|
/** Live billing information only. Never infer these fields from a plan or entitlement expiry. */
|
|
134
145
|
billing?: { hasActiveSubscription?: boolean; autoRenew?: boolean; renewsAt?: string; cancelsAt?: string; expiresAt?: string };
|
|
135
146
|
/** Missing windows are unknown, not unused. Times are ISO 8601 UTC. */
|
|
@@ -146,6 +157,12 @@ export interface IHarnessOutcome {
|
|
|
146
157
|
problems: string[];
|
|
147
158
|
}
|
|
148
159
|
|
|
160
|
+
/** A read-only status lookup. */
|
|
161
|
+
export interface IHarnessStatusOptions {
|
|
162
|
+
/** Cancels the lookup's requests; an adapter still bounds each request with its own timeout. */
|
|
163
|
+
signal?: AbortSignal;
|
|
164
|
+
}
|
|
165
|
+
|
|
149
166
|
/** A single text-only inference request. Credentials remain inside the adapter. */
|
|
150
167
|
export interface IHarnessPreuseOptions {
|
|
151
168
|
prompt: string;
|
|
@@ -171,7 +188,7 @@ export interface IAuthHarness {
|
|
|
171
188
|
readState(): THarnessResult<IHarnessState>;
|
|
172
189
|
/** Running instances of this harness, when the adapter owns their lifecycle. */
|
|
173
190
|
readonly processes?: IHarnessProcessControl;
|
|
174
|
-
readAccountStatus(accountIdArg: string): Promise<IHarnessAccountStatus>;
|
|
191
|
+
readAccountStatus(accountIdArg: string, optionsArg?: IHarnessStatusOptions): Promise<IHarnessAccountStatus>;
|
|
175
192
|
readonly loginProviders?: IHarnessLoginProvider[];
|
|
176
193
|
beginLogin?(options: IHarnessLoginOptions): Promise<IHarnessLoginHandle>;
|
|
177
194
|
/** Optional quota-consuming capability; never changes the active login. */
|
package/ts/interfaces.list.ts
CHANGED
|
@@ -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
|
|
|
3
3
|
/** Credential-free, versioned output of list --json. Missing status fields stay omitted. */
|
|
4
4
|
export interface IAccountList {
|
|
@@ -39,6 +39,8 @@ export interface IAccountLimitRow {
|
|
|
39
39
|
accountId: string | null;
|
|
40
40
|
slotId?: string;
|
|
41
41
|
isActive: boolean;
|
|
42
|
+
/** The adapter's plan name (`max`, `pro`), sanitised but not display-formatted; null when none is known or the row names no account. */
|
|
43
|
+
accountType: IAccountType | null;
|
|
42
44
|
/** The provider's own window name, or null when no limit data is available. */
|
|
43
45
|
limitType: string | null;
|
|
44
46
|
scope: 'account' | 'feature' | null;
|
|
@@ -70,6 +72,8 @@ export interface IActiveAccountRow {
|
|
|
70
72
|
account: string | null;
|
|
71
73
|
accountId: string | null;
|
|
72
74
|
slotId?: string;
|
|
75
|
+
/** The adapter's plan name (`max`, `pro`), sanitised but not display-formatted; null when none is known or the row names no account. */
|
|
76
|
+
accountType: IAccountType | null;
|
|
73
77
|
/** When authswitch last saved this login; null when it was never stashed. */
|
|
74
78
|
savedAt: string | null;
|
|
75
79
|
/** Human elapsed time for the same `savedAt`, relative to `generatedAt`. */
|