@modelprofile.com/authswitch 8.0.0 → 8.2.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/authority-contract.d.ts +56 -0
- package/dist_ts/authority-contract.js +54 -2
- package/dist_ts/authority-import-contract.d.ts +211 -0
- package/dist_ts/authority-import-contract.js +17 -0
- package/dist_ts/classes.authoritybroker.js +3 -2
- package/dist_ts/classes.authoritycli.d.ts +4 -0
- package/dist_ts/classes.authoritycli.js +148 -11
- package/dist_ts/classes.authorityclient.d.ts +24 -11
- package/dist_ts/classes.authorityclient.js +40 -22
- package/dist_ts/classes.authoritydaemon.d.ts +12 -0
- package/dist_ts/classes.authoritydaemon.js +123 -36
- package/dist_ts/classes.authoritydatabase.d.ts +14 -1
- package/dist_ts/classes.authoritydatabase.js +48 -16
- package/dist_ts/classes.authorityimport.d.ts +176 -0
- package/dist_ts/classes.authorityimport.js +664 -0
- package/dist_ts/classes.authoritymodels.d.ts +16 -1
- package/dist_ts/classes.authoritymodels.js +31 -3
- package/dist_ts/classes.authorityservice.d.ts +10 -0
- package/dist_ts/classes.authorityservice.js +24 -1
- package/dist_ts/classes.claudeauthority.js +19 -11
- package/dist_ts/classes.claudenative.d.ts +63 -3
- package/dist_ts/classes.claudenative.js +68 -8
- package/dist_ts/index.d.ts +1 -0
- package/dist_ts/index.js +6 -3
- package/dist_ts/ts_migration/0003_claude_handoff_proof.d.ts +13 -0
- package/dist_ts/ts_migration/0003_claude_handoff_proof.js +20 -0
- package/dist_ts/ts_migration/index.js +3 -1
- package/dist_ts/ts_migration/legacysources/authswitchstores.d.ts +2 -0
- package/dist_ts/ts_migration/legacysources/authswitchstores.js +244 -0
- package/dist_ts/ts_migration/legacysources/index.d.ts +18 -0
- package/dist_ts/ts_migration/legacysources/index.js +24 -0
- package/dist_ts/ts_migration/legacysources/material.d.ts +37 -0
- package/dist_ts/ts_migration/legacysources/material.js +120 -0
- package/dist_ts/ts_migration/legacysources/nativestores.d.ts +2 -0
- package/dist_ts/ts_migration/legacysources/nativestores.js +214 -0
- package/dist_ts/ts_migration/legacysources/shared.d.ts +125 -0
- package/dist_ts/ts_migration/legacysources/shared.js +140 -0
- package/package.json +9 -1
- package/readme.md +111 -2
- package/ts/00_commitinfo_data.ts +1 -1
- package/ts/authority-contract.ts +106 -0
- package/ts/authority-import-contract.ts +217 -0
- package/ts/classes.authoritybroker.ts +2 -1
- package/ts/classes.authoritycli.ts +164 -10
- package/ts/classes.authorityclient.ts +65 -21
- package/ts/classes.authoritydaemon.ts +121 -34
- package/ts/classes.authoritydatabase.ts +49 -15
- package/ts/classes.authorityimport.ts +746 -0
- package/ts/classes.authoritymodels.ts +32 -2
- package/ts/classes.authorityservice.ts +27 -0
- package/ts/classes.claudeauthority.ts +22 -10
- package/ts/classes.claudenative.ts +104 -10
- package/ts/index.ts +5 -2
- package/ts/ts_migration/0003_claude_handoff_proof.ts +19 -0
- package/ts/ts_migration/index.ts +2 -0
- package/ts/ts_migration/legacysources/authswitchstores.ts +211 -0
- package/ts/ts_migration/legacysources/index.ts +35 -0
- package/ts/ts_migration/legacysources/material.ts +134 -0
- package/ts/ts_migration/legacysources/nativestores.ts +212 -0
- package/ts/ts_migration/legacysources/shared.ts +218 -0
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
import type { IAuthSwitchDoctorPage, IAuthSwitchOperation, IAuthSwitchPreuseOperation,
|
|
2
2
|
IAuthSwitchSnapshot, IReq_AuthSwitchDoctor, IReq_AuthSwitchStartPreuse } from './authority-contract.js';
|
|
3
|
+
import { isAuthSwitchImportRefusal } from './authority-import-contract.js';
|
|
4
|
+
import type { IAuthSwitchImportInventory, IAuthSwitchImportResult, IAuthSwitchImportStatus,
|
|
5
|
+
IReq_AuthSwitchImportStatus, TAuthSwitchImportAction } from './authority-import-contract.js';
|
|
3
6
|
import { AuthSwitchClient } from './classes.authorityclient.js';
|
|
4
7
|
import { resolveAuthSwitchAuthorityPaths } from './classes.authorityservice.js';
|
|
5
8
|
import { defaultPreusePrompt, validatePreuseOptions } from './preuse.js';
|
|
@@ -9,6 +12,9 @@ import * as plugins from './plugins.js';
|
|
|
9
12
|
export interface IAuthSwitchAuthorityCliClient {
|
|
10
13
|
snapshotAll(): Promise<IAuthSwitchSnapshot>;
|
|
11
14
|
doctorPage(request: IReq_AuthSwitchDoctor['request']): Promise<IAuthSwitchDoctorPage>;
|
|
15
|
+
importInventory(): Promise<IAuthSwitchImportInventory>;
|
|
16
|
+
importStatus(request: IReq_AuthSwitchImportStatus['request']): Promise<IAuthSwitchImportStatus>;
|
|
17
|
+
submitImport(sourceId: string, sourceDigest: string): Promise<IAuthSwitchImportResult>;
|
|
12
18
|
beginAddOpenAi(operationId: string): Promise<IAuthSwitchOperation>;
|
|
13
19
|
beginReauthOpenAi(operationId: string, accountId: string, loginId: string,
|
|
14
20
|
purpose: 'openai_managed'): Promise<IAuthSwitchOperation>;
|
|
@@ -33,6 +39,9 @@ export interface IAuthSwitchAuthorityCliOptions {
|
|
|
33
39
|
type TAuthorityCliCommand =
|
|
34
40
|
| { kind: 'list'; json: boolean }
|
|
35
41
|
| { kind: 'doctor'; json: boolean }
|
|
42
|
+
| { kind: 'importInventory'; json: boolean }
|
|
43
|
+
| { kind: 'importStatus'; json: boolean }
|
|
44
|
+
| { kind: 'importSubmit'; sourceId: string; sourceDigest: string; json: boolean }
|
|
36
45
|
| { kind: 'add'; json: boolean }
|
|
37
46
|
| { kind: 'reauth'; accountId: string; loginId: string; json: boolean }
|
|
38
47
|
| { kind: 'get'; operationId: string; json: boolean }
|
|
@@ -51,9 +60,11 @@ class AuthorityCliFailure extends Error {
|
|
|
51
60
|
}
|
|
52
61
|
}
|
|
53
62
|
|
|
54
|
-
const usage = 'Usage: authswitch account list [--json] | authswitch account add openai [--json] | authswitch account reauth <account-id> <login-id> [--json] | authswitch account operation get|cancel|resume <operation-id> [--json] | authswitch account preuse <account-id> <login-id>|--all [--prompt <text>] [--model <id>] [--json] | authswitch account preuse operation get|cancel|resume <operation-id> [--json] | authswitch authority doctor [--json]';
|
|
63
|
+
const usage = 'Usage: authswitch account list [--json] | authswitch account add openai [--json] | authswitch account reauth <account-id> <login-id> [--json] | authswitch account operation get|cancel|resume <operation-id> [--json] | authswitch account preuse <account-id> <login-id>|--all [--prompt <text>] [--model <id>] [--json] | authswitch account preuse operation get|cancel|resume <operation-id> [--json] | authswitch authority doctor [--json] | authswitch authority import inventory [--json] | authswitch authority import status [--json] | authswitch authority import submit <source-id> --digest <source-digest> [--json]';
|
|
55
64
|
const safe = (value: string | number | null): string => plainText(value === null ? 'none' : String(value));
|
|
56
65
|
const isId = (value: string | undefined): value is string => Boolean(value && !value.startsWith('-'));
|
|
66
|
+
const isHash = (value: string | undefined): value is string =>
|
|
67
|
+
typeof value === 'string' && /^[a-f0-9]{64}$/.test(value);
|
|
57
68
|
|
|
58
69
|
const parsePreuse = (argv: readonly string[]): TAuthorityCliCommand | null => {
|
|
59
70
|
if (argv[2] === 'operation') {
|
|
@@ -109,6 +120,21 @@ const parse = (argv: readonly string[]): TAuthorityCliCommand | null => {
|
|
|
109
120
|
&& (argv.length === 2 || (argv.length === 3 && argv[2] === '--json'))) {
|
|
110
121
|
return { kind: 'doctor', json: argv[2] === '--json' };
|
|
111
122
|
}
|
|
123
|
+
if (argv[0] === 'authority' && argv[1] === 'import' && argv[2] === 'inventory'
|
|
124
|
+
&& (argv.length === 3 || (argv.length === 4 && argv[3] === '--json'))) {
|
|
125
|
+
return { kind: 'importInventory', json: argv[3] === '--json' };
|
|
126
|
+
}
|
|
127
|
+
if (argv[0] === 'authority' && argv[1] === 'import' && argv[2] === 'status'
|
|
128
|
+
&& (argv.length === 3 || (argv.length === 4 && argv[3] === '--json'))) {
|
|
129
|
+
return { kind: 'importStatus', json: argv[3] === '--json' };
|
|
130
|
+
}
|
|
131
|
+
if (argv[0] === 'authority' && argv[1] === 'import' && argv[2] === 'submit') {
|
|
132
|
+
// The digest the owner read in the inventory is part of the command: the daemon refuses a source whose
|
|
133
|
+
// bytes have changed since, so approving one source can never import a different one.
|
|
134
|
+
if (!isHash(argv[3]) || argv[4] !== '--digest' || !isHash(argv[5])
|
|
135
|
+
|| (argv.length !== 6 && (argv.length !== 7 || argv[6] !== '--json'))) return null;
|
|
136
|
+
return { kind: 'importSubmit', sourceId: argv[3], sourceDigest: argv[5], json: argv[6] === '--json' };
|
|
137
|
+
}
|
|
112
138
|
if (argv[0] === 'account' && argv[1] === 'add' && argv[2] === 'openai'
|
|
113
139
|
&& (argv.length === 3 || (argv.length === 4 && argv[3] === '--json'))) {
|
|
114
140
|
return { kind: 'add', json: argv[3] === '--json' };
|
|
@@ -143,6 +169,18 @@ class ScopedAuthorityCliClient implements IAuthSwitchAuthorityCliClient {
|
|
|
143
169
|
return this.client.doctorPage(request, this.controller.signal);
|
|
144
170
|
}
|
|
145
171
|
|
|
172
|
+
public importInventory(): Promise<IAuthSwitchImportInventory> {
|
|
173
|
+
return this.client.importInventory(this.controller.signal);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
public importStatus(request: IReq_AuthSwitchImportStatus['request']): Promise<IAuthSwitchImportStatus> {
|
|
177
|
+
return this.client.importStatus(request, this.controller.signal);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
public submitImport(sourceId: string, sourceDigest: string): Promise<IAuthSwitchImportResult> {
|
|
181
|
+
return this.client.submitImport({ kind: 'local', sourceId, sourceDigest }, this.controller.signal);
|
|
182
|
+
}
|
|
183
|
+
|
|
146
184
|
public beginAddOpenAi(operationId: string): Promise<IAuthSwitchOperation> {
|
|
147
185
|
return this.client.beginAddOpenAi(operationId, this.controller.signal);
|
|
148
186
|
}
|
|
@@ -201,6 +239,91 @@ const renderList = (snapshot: IAuthSwitchSnapshot): string => {
|
|
|
201
239
|
return lines.join('\n') + '\n';
|
|
202
240
|
};
|
|
203
241
|
|
|
242
|
+
/**
|
|
243
|
+
* The legacy-source report.
|
|
244
|
+
*
|
|
245
|
+
* The notice comes first and is never abbreviated: an import is a one-way transfer of ownership, and this is
|
|
246
|
+
* the only place an owner reads what that costs before approving it.
|
|
247
|
+
*/
|
|
248
|
+
const renderInventory = (inventory: IAuthSwitchImportInventory): string => {
|
|
249
|
+
const lines = [`Legacy account sources — generated ${safe(inventory.generatedAt)}, environment ${safe(inventory.environmentId)}`,
|
|
250
|
+
'Before you import:'];
|
|
251
|
+
for (const notice of inventory.notice) lines.push(` - ${safe(notice)}`);
|
|
252
|
+
lines.push('');
|
|
253
|
+
if (inventory.sources.length === 0) lines.push('No legacy credential source was found.');
|
|
254
|
+
for (const source of inventory.sources) {
|
|
255
|
+
lines.push(`${safe(source.label ?? source.email ?? source.sourceKind)} [${safe(source.providerId)}]`,
|
|
256
|
+
` source ${safe(source.id)}; kind ${safe(source.sourceKind)}; treatment ${safe(source.treatment)}; proof ${safe(source.proof)}`,
|
|
257
|
+
` account ${safe(source.accountId)}; plan ${safe(source.plan)}; access token expires ${safe(source.accessExpiresAt)}`,
|
|
258
|
+
` enrollments ${source.enrollmentCount === null ? 'unknown' : source.enrollmentCount}; ledger ${safe(source.ledgerStatus)}; digest ${safe(source.sourceDigest)}`);
|
|
259
|
+
for (const problem of source.problems) lines.push(` problem: ${safe(problem)}`);
|
|
260
|
+
}
|
|
261
|
+
if (inventory.problems.length) {
|
|
262
|
+
lines.push('', 'Locations that could not be read:');
|
|
263
|
+
for (const problem of inventory.problems) lines.push(` - ${safe(problem)}`);
|
|
264
|
+
}
|
|
265
|
+
return lines.join('\n') + '\n';
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
/**
|
|
269
|
+
* What the owner does next with a source, in words rather than a contract value. An action this version does
|
|
270
|
+
* not recognise is read as the careful one: never invite a second submission on a guess.
|
|
271
|
+
*/
|
|
272
|
+
const actionText = (action: TAuthSwitchImportAction): string =>
|
|
273
|
+
action === 'none' ? 'nothing to do'
|
|
274
|
+
: action === 'resume' ? 'submit this source again to continue where it stopped'
|
|
275
|
+
: 'sign in to this account again; this source cannot be imported a second time';
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* Every page of the ledger, as one report.
|
|
279
|
+
*
|
|
280
|
+
* The owner acts on this read, so it must not stop at a page boundary and leave a quarantined source
|
|
281
|
+
* unmentioned. A cursor that does not advance is a broken answer rather than a reason to keep asking.
|
|
282
|
+
*/
|
|
283
|
+
const collectImportStatus = async (client: IAuthSwitchAuthorityCliClient): Promise<IAuthSwitchImportStatus> => {
|
|
284
|
+
const first = await client.importStatus({ limit: 128 });
|
|
285
|
+
const entries = [...first.entries];
|
|
286
|
+
let cursor = first.nextCursor;
|
|
287
|
+
while (cursor !== null) {
|
|
288
|
+
const page = await client.importStatus({ after: cursor, limit: 128 });
|
|
289
|
+
if (page.entries.length === 0 || page.nextCursor === cursor) throw new AuthorityCliFailure('response');
|
|
290
|
+
entries.push(...page.entries);
|
|
291
|
+
cursor = page.nextCursor;
|
|
292
|
+
}
|
|
293
|
+
return { ...first, entries, nextCursor: null };
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
const renderImportStatus = (status: IAuthSwitchImportStatus): string => {
|
|
297
|
+
const lines = [`Import status — generated ${safe(status.generatedAt)}`];
|
|
298
|
+
if (status.entries.length === 0) {
|
|
299
|
+
lines.push('No source has been submitted yet. Run `authswitch authority import inventory` to see what this host holds.');
|
|
300
|
+
}
|
|
301
|
+
for (const entry of status.entries) {
|
|
302
|
+
lines.push(`${safe(entry.sourceKind)} ${safe(entry.sourceId)}`,
|
|
303
|
+
` state ${safe(entry.status)}; refresher ${safe(entry.owner)}; recorded ${safe(entry.statusObservedAt)}`,
|
|
304
|
+
` account ${safe(entry.accountId)}; login ${safe(entry.loginId)}`,
|
|
305
|
+
` next: ${actionText(entry.action)}`);
|
|
306
|
+
}
|
|
307
|
+
// A source that was never submitted has no ledger row, so it is the inventory's subject, not this read's.
|
|
308
|
+
lines.push('Sources that were never submitted are listed by `authswitch authority import inventory`.');
|
|
309
|
+
return lines.join('\n') + '\n';
|
|
310
|
+
};
|
|
311
|
+
|
|
312
|
+
const renderImportResult = (result: IAuthSwitchImportResult): string => {
|
|
313
|
+
const settled = result.status === 'complete';
|
|
314
|
+
const lines = [`${settled ? 'Imported' : 'Import did not settle'}: ${safe(result.sourceKind)} ${safe(result.sourceId)}`,
|
|
315
|
+
` treatment ${safe(result.treatment)}; state ${safe(result.status)}`,
|
|
316
|
+
` account ${safe(result.accountId)}; login ${safe(result.loginId)}`];
|
|
317
|
+
for (const problem of result.problems) lines.push(` problem: ${safe(problem)}`);
|
|
318
|
+
if (settled && result.treatment === 'import') {
|
|
319
|
+
lines.push(' The authority now refreshes this login. Its legacy copy holds a token the provider has rotated away.');
|
|
320
|
+
}
|
|
321
|
+
if (settled && result.treatment === 'project') {
|
|
322
|
+
lines.push(' The native tool still refreshes this login. The authority holds no copy of it.');
|
|
323
|
+
}
|
|
324
|
+
return lines.join('\n') + '\n';
|
|
325
|
+
};
|
|
326
|
+
|
|
204
327
|
const terminalOperation = (operation: IAuthSwitchOperation): boolean =>
|
|
205
328
|
operation.state === 'complete' || operation.state === 'failed' || operation.state === 'cancelled'
|
|
206
329
|
|| operation.state === 'interrupted' || operation.state === 'outcome_unknown';
|
|
@@ -443,6 +566,7 @@ export const runAuthSwitchAuthorityCli = async (argv: readonly string[],
|
|
|
443
566
|
let preuseHint = false;
|
|
444
567
|
let beginAttempted = false;
|
|
445
568
|
let beginResponded = false;
|
|
569
|
+
let submitAttempted = false;
|
|
446
570
|
const operationController = new AbortController();
|
|
447
571
|
const closeClient = (reason?: Error): void => {
|
|
448
572
|
if (clientClosed || !client) return;
|
|
@@ -579,6 +703,24 @@ export const runAuthSwitchAuthorityCli = async (argv: readonly string[],
|
|
|
579
703
|
await runDoctor(activeClient, command.json, writeOutput);
|
|
580
704
|
return 0;
|
|
581
705
|
}
|
|
706
|
+
if (command.kind === 'importInventory') {
|
|
707
|
+
const inventory = await activeClient.importInventory();
|
|
708
|
+
await writeOutput(command.json ? JSON.stringify(inventory) + '\n' : renderInventory(inventory));
|
|
709
|
+
return 0;
|
|
710
|
+
}
|
|
711
|
+
if (command.kind === 'importStatus') {
|
|
712
|
+
const status = await collectImportStatus(activeClient);
|
|
713
|
+
await writeOutput(command.json ? JSON.stringify(status) + '\n' : renderImportStatus(status));
|
|
714
|
+
return 0;
|
|
715
|
+
}
|
|
716
|
+
if (command.kind === 'importSubmit') {
|
|
717
|
+
// A lost response leaves the outcome unknown to this process but never to the authority, so the
|
|
718
|
+
// failure path below points at the status read rather than inviting a second submission.
|
|
719
|
+
submitAttempted = true;
|
|
720
|
+
const result = await activeClient.submitImport(command.sourceId, command.sourceDigest);
|
|
721
|
+
await writeOutput(command.json ? JSON.stringify(result) + '\n' : renderImportResult(result));
|
|
722
|
+
return result.status === 'complete' ? 0 : 1;
|
|
723
|
+
}
|
|
582
724
|
if (command.kind === 'preuseGet' || command.kind === 'preuseCancel') {
|
|
583
725
|
const operation = validatePreuseOperation(command.kind === 'preuseGet'
|
|
584
726
|
? await activeClient.getPreuse(command.operationId)
|
|
@@ -692,15 +834,27 @@ export const runAuthSwitchAuthorityCli = async (argv: readonly string[],
|
|
|
692
834
|
}
|
|
693
835
|
return 0;
|
|
694
836
|
} catch (error) {
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
837
|
+
// A marked refusal is the daemon's answer to the command, not a fault: it names what the owner does
|
|
838
|
+
// instead and nothing was left half-done, so it is shown as written rather than replaced by a generic
|
|
839
|
+
// line. Any other failure -- including the transport's own sanitised text -- stays a diagnostic.
|
|
840
|
+
const refusal = error instanceof Error && isAuthSwitchImportRefusal(error) && !cancelled && !timedOut
|
|
841
|
+
? safe(error.message) : null;
|
|
842
|
+
let message = refusal !== null ? `${refusal}\n`
|
|
843
|
+
: cancelled ? 'Authswitch authority request was cancelled.\n'
|
|
844
|
+
: timedOut ? 'Authswitch authority operation timed out.\n'
|
|
845
|
+
: beginAttempted && !beginResponded
|
|
846
|
+
? 'Authswitch authority operation start response was lost.\n'
|
|
847
|
+
: error instanceof AuthorityCliFailure && error.kind === 'output'
|
|
848
|
+
? 'Authswitch authority output failed.\n'
|
|
849
|
+
: error instanceof AuthorityCliFailure && error.kind === 'response'
|
|
850
|
+
? 'Authswitch authority daemon returned an invalid response.\n'
|
|
851
|
+
: 'Authswitch authority daemon is unavailable.\n';
|
|
852
|
+
if (submitAttempted && refusal === null) {
|
|
853
|
+
// The authority may have settled this source even though this process never learned the answer; never
|
|
854
|
+
// submit it again blindly, because a rotating refresh token is sent at most once.
|
|
855
|
+
message += 'The outcome of this import is unknown to this command. Run `authswitch authority import '
|
|
856
|
+
+ 'status` before doing anything else with this source.\n';
|
|
857
|
+
}
|
|
704
858
|
if (operationIdForHint !== null) {
|
|
705
859
|
const resume = preuseHint ? preuseResumeCommand(operationIdForHint, command.json)
|
|
706
860
|
: resumeCommand(operationIdForHint, command.json);
|
|
@@ -11,6 +11,8 @@ import type {
|
|
|
11
11
|
} from './authority-contract.js';
|
|
12
12
|
import type { IReq_AuthSwitchBindAccount, IReq_AuthSwitchReleaseBinding,
|
|
13
13
|
IReq_AuthSwitchResolveAccess } from './authority-runtime-contract.js';
|
|
14
|
+
import type { IReq_AuthSwitchImportInventory, IReq_AuthSwitchImportStatus,
|
|
15
|
+
IReq_AuthSwitchImportSubmit } from './authority-import-contract.js';
|
|
14
16
|
import { AuthSwitchAuthorityFrameReader, authSwitchAuthorityFrameBytes,
|
|
15
17
|
maxAuthSwitchAuthorityFrameBytes } from './classes.authorityframing.js';
|
|
16
18
|
|
|
@@ -66,7 +68,13 @@ export interface IAuthSwitchSubscriptionOptions {
|
|
|
66
68
|
heartbeatMs?: number;
|
|
67
69
|
}
|
|
68
70
|
|
|
69
|
-
/**
|
|
71
|
+
/**
|
|
72
|
+
* Node-only client for the per-user daemon. Each request has its own bounded Unix connection.
|
|
73
|
+
*
|
|
74
|
+
* Every method takes an optional `AbortSignal`. A referenced socket with a 35 second timeout outlives a
|
|
75
|
+
* consumer's own stop unless that consumer can cancel the read, so the signal is what releases it; it
|
|
76
|
+
* detaches this request only and never cancels work the daemon already owns.
|
|
77
|
+
*/
|
|
70
78
|
export class AuthSwitchClient {
|
|
71
79
|
private readonly target: plugins.typedrequest.TypedTarget;
|
|
72
80
|
private readonly runtimeTarget: plugins.typedrequest.TypedTarget;
|
|
@@ -97,9 +105,30 @@ export class AuthSwitchClient {
|
|
|
97
105
|
return (await this.request<IReq_AuthSwitchDoctor>('authswitch.authority.doctor', options, 35_000, false, signal)).page;
|
|
98
106
|
}
|
|
99
107
|
|
|
100
|
-
|
|
108
|
+
/** The credential-free legacy-source report of the one-time import. It changes nothing. */
|
|
109
|
+
public async importInventory(signal?: AbortSignal): Promise<IReq_AuthSwitchImportInventory['response']['inventory']> {
|
|
110
|
+
return (await this.request<IReq_AuthSwitchImportInventory>('authswitch.authority.import.inventory',
|
|
111
|
+
{}, 35_000, false, signal)).inventory;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Where every source the authority knows about stands. Durable records only; no store is opened. */
|
|
115
|
+
public async importStatus(request: IReq_AuthSwitchImportStatus['request'] = {},
|
|
116
|
+
signal?: AbortSignal): Promise<IReq_AuthSwitchImportStatus['response']['status']> {
|
|
117
|
+
return (await this.request<IReq_AuthSwitchImportStatus>('authswitch.authority.import.status',
|
|
118
|
+
request, 35_000, false, signal)).status;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Backend-only. One source, one ownership decision; both acknowledgements are required. */
|
|
122
|
+
public async submitImport(submission: IReq_AuthSwitchImportSubmit['request']['submission'],
|
|
123
|
+
signal?: AbortSignal): Promise<IReq_AuthSwitchImportSubmit['response']['result']> {
|
|
124
|
+
return (await this.request<IReq_AuthSwitchImportSubmit>('authswitch.authority.import.submit',
|
|
125
|
+
{ submission, callerQuiescent: true, acknowledgeRunOrder: true }, 120_000, true, signal)).result;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
public async getUsage(accountId: string, loginId: string, force = false,
|
|
129
|
+
signal?: AbortSignal): Promise<IReq_AuthSwitchUsage['response']['usage']> {
|
|
101
130
|
return (await this.request<IReq_AuthSwitchUsage>('authswitch.authority.usage',
|
|
102
|
-
{ accountId, loginId, force })).usage;
|
|
131
|
+
{ accountId, loginId, force }, 35_000, false, signal)).usage;
|
|
103
132
|
}
|
|
104
133
|
|
|
105
134
|
/** Assemble a consistent view from bounded database pages; restart if a writer changes the revision. */
|
|
@@ -236,8 +265,10 @@ export class AuthSwitchClient {
|
|
|
236
265
|
return (await this.request<IReq_AuthSwitchBeginReauth>('authswitch.authority.reauth',
|
|
237
266
|
{ operationId, accountId, loginId, purpose, flow: 'device' }, 35_000, false, signal)).operation;
|
|
238
267
|
}
|
|
239
|
-
public listOperations(after?: string, limit?: number
|
|
240
|
-
|
|
268
|
+
public listOperations(after?: string, limit?: number,
|
|
269
|
+
signal?: AbortSignal): Promise<IReq_AuthSwitchListOperations['response']> {
|
|
270
|
+
return this.request<IReq_AuthSwitchListOperations>('authswitch.authority.operations',
|
|
271
|
+
{ after, limit }, 35_000, false, signal);
|
|
241
272
|
}
|
|
242
273
|
public async getOperation(operationId: string, signal?: AbortSignal): Promise<IReq_AuthSwitchGetOperation['response']['operation']> {
|
|
243
274
|
return (await this.request<IReq_AuthSwitchGetOperation>('authswitch.authority.operation',
|
|
@@ -262,27 +293,39 @@ export class AuthSwitchClient {
|
|
|
262
293
|
return (await this.request<IReq_AuthSwitchCancelPreuse>(
|
|
263
294
|
'authswitch.authority.preuse.cancel', { operationId }, 35_000, false, signal)).operation;
|
|
264
295
|
}
|
|
265
|
-
public async renameAccount(accountId: string, expectedRevision: number, label: string
|
|
266
|
-
|
|
296
|
+
public async renameAccount(accountId: string, expectedRevision: number, label: string,
|
|
297
|
+
signal?: AbortSignal): Promise<IReq_AuthSwitchRenameAccount['response']['account']> {
|
|
298
|
+
return (await this.request<IReq_AuthSwitchRenameAccount>('authswitch.authority.rename',
|
|
299
|
+
{ accountId, expectedRevision, label }, 35_000, false, signal)).account;
|
|
267
300
|
}
|
|
268
|
-
public async removeAccount(accountId: string, expectedRevision: number
|
|
269
|
-
|
|
301
|
+
public async removeAccount(accountId: string, expectedRevision: number,
|
|
302
|
+
signal?: AbortSignal): Promise<IReq_AuthSwitchRemoveAccount['response']['account']> {
|
|
303
|
+
return (await this.request<IReq_AuthSwitchRemoveAccount>('authswitch.authority.remove',
|
|
304
|
+
{ accountId, expectedRevision }, 35_000, false, signal)).account;
|
|
270
305
|
}
|
|
271
|
-
public async switchClaudeNative(accountId: string, loginId: string
|
|
306
|
+
public async switchClaudeNative(accountId: string, loginId: string,
|
|
307
|
+
signal?: AbortSignal): Promise<IReq_AuthSwitchSwitchClaudeNative['response']['handoff']> {
|
|
272
308
|
return (await this.request<IReq_AuthSwitchSwitchClaudeNative>('authswitch.authority.claude.switch',
|
|
273
|
-
{ accountId, loginId, purpose: 'claude_host_native' })).handoff;
|
|
309
|
+
{ accountId, loginId, purpose: 'claude_host_native' }, 35_000, false, signal)).handoff;
|
|
274
310
|
}
|
|
275
|
-
public async getClaudeNativeHandoff(operationId: string
|
|
276
|
-
|
|
311
|
+
public async getClaudeNativeHandoff(operationId: string,
|
|
312
|
+
signal?: AbortSignal): Promise<IReq_AuthSwitchClaudeNativeHandoff['response']['handoff']> {
|
|
313
|
+
return (await this.request<IReq_AuthSwitchClaudeNativeHandoff>('authswitch.authority.claude.handoff',
|
|
314
|
+
{ operationId }, 35_000, false, signal)).handoff;
|
|
277
315
|
}
|
|
278
|
-
public listClaudeNativeHandoffs(after?: string, limit?: number
|
|
279
|
-
|
|
316
|
+
public listClaudeNativeHandoffs(after?: string, limit?: number,
|
|
317
|
+
signal?: AbortSignal): Promise<IReq_AuthSwitchClaudeNativeHandoffs['response']> {
|
|
318
|
+
return this.request<IReq_AuthSwitchClaudeNativeHandoffs>('authswitch.authority.claude.handoffs',
|
|
319
|
+
{ after, limit }, 35_000, false, signal);
|
|
280
320
|
}
|
|
281
|
-
public bindAccount(input: IReq_AuthSwitchBindAccount['request']
|
|
282
|
-
|
|
321
|
+
public bindAccount(input: IReq_AuthSwitchBindAccount['request'],
|
|
322
|
+
signal?: AbortSignal): Promise<IReq_AuthSwitchBindAccount['response']> {
|
|
323
|
+
return this.request<IReq_AuthSwitchBindAccount>('authswitch.authority.bind', input, 35_000, false, signal);
|
|
283
324
|
}
|
|
284
|
-
public resolveAccess(input: IReq_AuthSwitchResolveAccess['request']
|
|
285
|
-
|
|
325
|
+
public resolveAccess(input: IReq_AuthSwitchResolveAccess['request'],
|
|
326
|
+
signal?: AbortSignal): Promise<IReq_AuthSwitchResolveAccess['response']> {
|
|
327
|
+
return this.request<IReq_AuthSwitchResolveAccess>('authswitch.authority.resolveAccess',
|
|
328
|
+
input, 35_000, true, signal);
|
|
286
329
|
}
|
|
287
330
|
public releaseBinding(input: IReq_AuthSwitchReleaseBinding['request'], signal?: AbortSignal): Promise<IReq_AuthSwitchReleaseBinding['response']> {
|
|
288
331
|
return this.request<IReq_AuthSwitchReleaseBinding>('authswitch.authority.release', input, 35_000, true, signal);
|
|
@@ -298,9 +341,10 @@ export class AuthSwitchRuntimeClient {
|
|
|
298
341
|
postMethod: (payload, options) => post(runtimeSocketPath, payload, options?.signal),
|
|
299
342
|
});
|
|
300
343
|
}
|
|
301
|
-
public resolveAccess(input: IReq_AuthSwitchResolveAccess['request']
|
|
344
|
+
public resolveAccess(input: IReq_AuthSwitchResolveAccess['request'],
|
|
345
|
+
signal?: AbortSignal): Promise<IReq_AuthSwitchResolveAccess['response']> {
|
|
302
346
|
return new plugins.typedrequest.TypedRequest<IReq_AuthSwitchResolveAccess>(this.target,
|
|
303
|
-
'authswitch.authority.resolveAccess').fire(input, { timeoutMs: 35_000, maxRetries: 0 });
|
|
347
|
+
'authswitch.authority.resolveAccess').fire(input, { timeoutMs: 35_000, maxRetries: 0, abortSignal: signal });
|
|
304
348
|
}
|
|
305
349
|
public releaseBinding(input: IReq_AuthSwitchReleaseBinding['request'], signal?: AbortSignal): Promise<IReq_AuthSwitchReleaseBinding['response']> {
|
|
306
350
|
return new plugins.typedrequest.TypedRequest<IReq_AuthSwitchReleaseBinding>(this.target,
|