@cortexkit/anthropic-auth-core 2.0.1 → 2.0.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. package/dist/accounts.d.ts +7 -1
  2. package/dist/cache1h.d.ts +2 -0
  3. package/dist/cachekeep-registry.d.ts +3 -0
  4. package/dist/dump.d.ts +2 -0
  5. package/dist/fast.d.ts +2 -0
  6. package/dist/index.d.ts +9 -4
  7. package/dist/index.js +27470 -25081
  8. package/dist/internal-types/auth-menu/terminal.d.ts +43 -0
  9. package/dist/internal-types/claustrum/consumer.d.ts +132 -0
  10. package/dist/internal-types/claustrum/custody.d.ts +178 -0
  11. package/dist/internal-types/claustrum/enrollment.d.ts +164 -0
  12. package/dist/internal-types/claustrum/errors.d.ts +16 -0
  13. package/dist/internal-types/claustrum/host-slot.d.ts +39 -0
  14. package/dist/internal-types/claustrum/index.d.ts +19 -0
  15. package/dist/internal-types/claustrum/insula.d.ts +69 -0
  16. package/dist/internal-types/claustrum/interlock.d.ts +27 -0
  17. package/dist/internal-types/claustrum/roster.d.ts +221 -0
  18. package/dist/internal-types/fs/refresh-file-lock.d.ts +8 -2
  19. package/dist/internal-types/quota/codec.d.ts +2 -0
  20. package/dist/internal-types/quota/format.d.ts +26 -0
  21. package/dist/internal-types/quota/index.d.ts +4 -4
  22. package/dist/internal-types/quota/map.d.ts +11 -0
  23. package/dist/internal-types/quota/merge.d.ts +3 -1
  24. package/dist/internal-types/quota/projection.d.ts +2 -1
  25. package/dist/internal-types/routing/admission.d.ts +93 -0
  26. package/dist/internal-types/routing/index.d.ts +8 -0
  27. package/dist/internal-types/routing/ordered.d.ts +47 -0
  28. package/dist/internal-types/routing/pins.d.ts +22 -0
  29. package/dist/internal-types/routing/sticky.d.ts +158 -0
  30. package/dist/internal-types/setup/command-error.d.ts +17 -0
  31. package/dist/internal-types/setup/command.d.ts +3 -0
  32. package/dist/internal-types/setup/detect.d.ts +6 -0
  33. package/dist/internal-types/setup/index.d.ts +6 -0
  34. package/dist/internal-types/setup/process-fence.d.ts +43 -0
  35. package/dist/internal-types/setup/register.d.ts +11 -0
  36. package/dist/internal-types/setup/types.d.ts +138 -0
  37. package/dist/internal-types/store/pool.d.ts +1 -1
  38. package/dist/internal-types/store/refresh-lock.d.ts +0 -1
  39. package/dist/internal-types/store/schema.d.ts +2 -0
  40. package/dist/logging.d.ts +2 -0
  41. package/dist/mid-conversation-output-config.d.ts +35 -0
  42. package/dist/native-account-runtime.d.ts +6 -2
  43. package/dist/native-account-view.d.ts +22 -4
  44. package/dist/native-custody-activation.d.ts +1 -1
  45. package/dist/native-menu-executor.d.ts +1 -1
  46. package/dist/native-migration.d.ts +25 -4
  47. package/dist/native-quota-observations.d.ts +26 -0
  48. package/dist/native-transient-account.d.ts +3 -4
  49. package/dist/native-ui.d.ts +2 -0
  50. package/dist/native-usage-publication.d.ts +5 -0
  51. package/dist/native-vault-runtime.d.ts +4 -0
  52. package/dist/network-errors.d.ts +1 -0
  53. package/dist/pool-authority.d.ts +26 -8
  54. package/dist/pool-paths.d.ts +2 -0
  55. package/dist/quotas.d.ts +15 -2
  56. package/dist/relay.d.ts +20 -0
  57. package/dist/sticky-routing.d.ts +3 -0
  58. package/package.json +3 -2
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The terminal the menu draws on and reads keys from. The process's own
3
+ * stdin and stdout satisfy it; tests pass a scripted stand-in, so the full
4
+ * key-in, frame-out path runs without a real TTY.
5
+ */
6
+ export interface MenuTerminal {
7
+ input: MenuInput;
8
+ output: MenuOutput;
9
+ /**
10
+ * Where SIGINT and SIGTERM are heard while a selector holds raw mode, so a
11
+ * kill restores the terminal. Defaults to nothing: raw mode turns Ctrl-C
12
+ * into a key the selector already handles.
13
+ */
14
+ signals?: MenuSignals;
15
+ }
16
+ export interface MenuInput {
17
+ /** True only on an interactive terminal; anything else gets a plain list. */
18
+ readonly isTTY?: boolean;
19
+ readonly isRaw?: boolean;
20
+ setRawMode(mode: boolean): unknown;
21
+ resume(): unknown;
22
+ pause(): unknown;
23
+ on(event: 'data', listener: (data: Buffer | string) => void): unknown;
24
+ removeListener(event: 'data', listener: (data: Buffer | string) => void): unknown;
25
+ }
26
+ export interface MenuOutput {
27
+ write(text: string): unknown;
28
+ readonly columns?: number;
29
+ readonly rows?: number;
30
+ }
31
+ export interface MenuSignals {
32
+ once(signal: 'SIGINT' | 'SIGTERM', listener: () => void): unknown;
33
+ removeListener(signal: 'SIGINT' | 'SIGTERM', listener: () => void): unknown;
34
+ }
35
+ /** The current process's terminal, with its signals. */
36
+ export declare function processTerminal(): MenuTerminal;
37
+ /**
38
+ * Whether keys can be read one at a time. Only the input is checked, as the
39
+ * plugins' menus always did: output piped to a file still gets the menu.
40
+ */
41
+ export declare function isInteractive(terminal: MenuTerminal): boolean;
42
+ /** Writes one line of plain output below the menu. */
43
+ export declare function printLine(terminal: MenuTerminal, line?: string): void;
@@ -0,0 +1,132 @@
1
+ import type { ClaustrumReporterSource } from '@cortexkit/claustrum-client';
2
+ import type { QuotaObservation } from '../quota/index.js';
3
+ import type { RoutingRow } from '../routing/index.js';
4
+ import { type ClaustrumFamily, type ClaustrumScopedAttempt, type ClaustrumScopedClient, type IdentityParser } from './custody.js';
5
+ import { type ClaustrumLogger } from './errors.js';
6
+ import { type InsulaQuotaOptions } from './insula.js';
7
+ import { type AccountMapper, type QuotaReceipt, type VaultRosterFile } from './roster.js';
8
+ export interface ClaustrumConsumerOptions<Q = QuotaObservation> {
9
+ /**
10
+ * The roster file: one row per vault account (route id, credential id,
11
+ * account identity, state, quota), plus the accounts the user declined. It
12
+ * never holds a token.
13
+ */
14
+ rosterPath: string;
15
+ /** This host's enrollment token, written by setup. */
16
+ tokenPath: string;
17
+ family: ClaustrumFamily;
18
+ connect: () => Promise<ClaustrumScopedClient>;
19
+ /**
20
+ * Whether the plugin currently takes its accounts from the vault (custody
21
+ * mode) rather than from local logins. Checked before every discovery,
22
+ * commit and dispatch; false closes the vault connection and refuses sends.
23
+ */
24
+ isCustodyActive?: () => boolean | Promise<boolean>;
25
+ /** Ids of the plugin's local pool rows; no vault route id may equal one. */
26
+ reservedRouteIds?: () => Iterable<string>;
27
+ routePrefix?: string;
28
+ mapAccount?: AccountMapper;
29
+ parseIdentity?: IdentityParser;
30
+ /**
31
+ * Issue a receipt only when the vault's served reply itself names the
32
+ * credential id and the roster's account identity (see
33
+ * `ClaustrumScopedCustody`). Set it for providers whose tokens do not reveal
34
+ * their account; leave it unset to accept a reply that asserts no identity.
35
+ */
36
+ requireAssertion?: boolean;
37
+ /**
38
+ * Fired once per change of the vault's view cursor, including the first
39
+ * roster. A poll that sees the same view does not fire, and neither does a
40
+ * routine token refresh, which never moves the view.
41
+ */
42
+ onRoster?: (roster: VaultRosterFile) => void;
43
+ onError?: (error: unknown) => void;
44
+ pollIntervalMs?: number;
45
+ setTimeoutImpl?: typeof setTimeout;
46
+ clearTimeoutImpl?: typeof clearTimeout;
47
+ now?: () => number;
48
+ logger?: ClaustrumLogger;
49
+ /** Optional background quota source. In-band readings still use recordQuota. */
50
+ insula?: InsulaQuotaOptions<Q>;
51
+ }
52
+ export interface SendOptions {
53
+ /**
54
+ * What kind of request this is (for example `model`, `quota`, `profile`),
55
+ * recorded in the 401 retry log line so a retry can be traced to its caller.
56
+ */
57
+ site: string;
58
+ signal?: AbortSignal;
59
+ reporterSource?: ClaustrumReporterSource;
60
+ }
61
+ /**
62
+ * One plugin host's view of its vault accounts. It turns vault credentials
63
+ * into rows for `/routing`, authorizes each physical send with the vault, and
64
+ * reports a 401 against the exact record version that send used. Metadata
65
+ * (the connection and discovery) is shared and coalesced; credential reads
66
+ * never are. It never enrolls: a missing token is an error here, and setup
67
+ * is where enrollment happens.
68
+ */
69
+ export declare class ClaustrumConsumer<Q = QuotaObservation> {
70
+ #private;
71
+ constructor(options: ClaustrumConsumerOptions<Q>);
72
+ /** The last committed roster this instance saw, without any I/O. */
73
+ snapshot(): VaultRosterFile | undefined;
74
+ /** Rows for `/routing`, from the last committed roster. */
75
+ routingRows(): RoutingRow[];
76
+ refresh(): Promise<VaultRosterFile | undefined>;
77
+ start(): void;
78
+ /**
79
+ * Fetch on a background tick, at most once per minute across projects with
80
+ * the same daemon and enrollment token path. Admission must use the cache
81
+ * instead, so an optional quota daemon never delays a provider request.
82
+ */
83
+ refreshInsulaQuota(): Promise<void>;
84
+ /**
85
+ * Cache-only, for unknown-quota admission with a served receipt. Undefined
86
+ * means poll the provider normally. Only get_scoped's asserted account_id
87
+ * joins insula; parsed identities and display metadata cannot substitute.
88
+ * Background polling should use cachedInsulaQuotaForRoute instead.
89
+ */
90
+ cachedInsulaQuota(receipt: Pick<ClaustrumScopedAttempt, 'assertedAccountIdentity'>): Promise<Q | undefined>;
91
+ /**
92
+ * Background cache lookup without fetching a credential. Re-read the roster
93
+ * so a peer's account replacement invalidates the old route immediately.
94
+ * A route without a known provider account cannot join an insula entry.
95
+ */
96
+ cachedInsulaQuotaForRoute(routeId: string): Promise<Q | undefined>;
97
+ /**
98
+ * Authorize one physical send on a vault route. The roster file is re-read
99
+ * first, so a decline committed by another process applies before the next
100
+ * poll, and a route whose credential or account changed is refused.
101
+ */
102
+ authorize(routeId: string, signal?: AbortSignal): Promise<ClaustrumScopedAttempt>;
103
+ reportFailure(attempt: ClaustrumScopedAttempt, status: number, source: ClaustrumReporterSource): Promise<void>;
104
+ /**
105
+ * Send on a vault route. `dispatch` builds and sends the request with the
106
+ * receipt's token and may be called twice, so it must be able to rebuild
107
+ * its body. A 401 is retried once, and only when the vault now serves a new
108
+ * record version of the same credential and account; the final 401 is
109
+ * reported against the version that send actually used. Each call to
110
+ * `dispatch` gets its own receipt.
111
+ */
112
+ send(routeId: string, dispatch: (attempt: ClaustrumScopedAttempt, signal?: AbortSignal) => Promise<Response>, options: SendOptions): Promise<Response>;
113
+ /** Decline a vault account: it stays listed and never routes until accepted. */
114
+ decline(routeId: string): Promise<void>;
115
+ accept(routeId: string): Promise<void>;
116
+ /**
117
+ * Store a quota or profile observation for a vault route. The receipt the
118
+ * reading was taken with is required: the observation lands only while the
119
+ * route still holds the credential and account that receipt was served for
120
+ * (see `recordVaultQuota`), so a reading for a replaced account is dropped.
121
+ */
122
+ recordQuota(routeId: string, observation: QuotaObservation, attempt: QuotaReceipt): Promise<boolean>;
123
+ /**
124
+ * Store a background observation that belongs to an account rather than to
125
+ * a served credential (an insula reading from `cachedInsulaQuotaForRoute`).
126
+ * It lands only while the route is still bound to that known account, so no
127
+ * credential fetch is needed and a replaced account's reading is dropped.
128
+ * The in-memory roster is reloaded from the file, without a vault call.
129
+ */
130
+ recordAccountQuota(routeId: string, accountIdentity: string, observation: QuotaObservation): Promise<boolean>;
131
+ close(): void;
132
+ }
@@ -0,0 +1,178 @@
1
+ import { type ClaustrumClient, type ClaustrumReporterSource, type EnrollmentTokenFile } from '@cortexkit/claustrum-client';
2
+ import { type ClaustrumLogger } from './errors.js';
3
+ import type { InsulaUsageTransport } from './insula.js';
4
+ export type ClaustrumScopedClient = Pick<ClaustrumClient, 'listScoped' | 'getScoped' | 'reportAuthFailureScoped' | 'close'> & {
5
+ insula?: InsulaUsageTransport;
6
+ };
7
+ /** The two credential types a plugin's family can hold; routing differs by type. */
8
+ export type VaultCredentialType = 'oauth' | 'api_key';
9
+ /**
10
+ * Which vault rows belong to this plugin. `refreshAdapter` classifies an OAuth
11
+ * row by the protocol it speaks; `category` is the grant that authorizes this
12
+ * consumer to read it. Static API keys carry no refresh adapter, so they are
13
+ * admitted only when `apiKeys` is set and only by category.
14
+ */
15
+ export interface ClaustrumFamily {
16
+ refreshAdapter: string;
17
+ category: string;
18
+ apiKeys?: boolean;
19
+ }
20
+ /** One vault credential this consumer may serve. Never carries bearer material. */
21
+ export interface VaultCredential {
22
+ readonly credentialId: string;
23
+ readonly credentialType: VaultCredentialType;
24
+ /**
25
+ * The provider account the credential logs into, when the vault's adapter
26
+ * claims one. Absent means the adapter makes no claim, not a mismatch.
27
+ */
28
+ readonly accountIdentity?: string;
29
+ readonly state: string;
30
+ readonly email?: string;
31
+ readonly orgName?: string;
32
+ }
33
+ /**
34
+ * Why a listed vault record could not be used. A closed set of fixed codes, so
35
+ * a log line or a roster file that carries one never echoes vault data.
36
+ */
37
+ export type SkippedVaultReason = 'empty credential id' | 'duplicate credential id' | 'blank account identity' | 'empty state';
38
+ /**
39
+ * A vault record this consumer could not use. `credentialId` is absent when
40
+ * the record's id itself was unusable, so nothing can say which account the
41
+ * record was.
42
+ */
43
+ export interface SkippedVaultRecord {
44
+ readonly credentialId?: string;
45
+ readonly reason: SkippedVaultReason;
46
+ }
47
+ export interface VaultInventory {
48
+ /**
49
+ * The vault's change cursor: a digest over exactly what this consumer can see
50
+ * (ids, grants, state, identity), never over record versions, so a routine
51
+ * token refresh does not move it. Only equality is meaningful.
52
+ */
53
+ readonly view: string;
54
+ readonly credentials: readonly VaultCredential[];
55
+ readonly skipped: readonly SkippedVaultRecord[];
56
+ }
57
+ export interface ClaustrumScopedIdentity {
58
+ readonly credentialId: string;
59
+ readonly credentialType: VaultCredentialType;
60
+ readonly accountIdentity?: string;
61
+ }
62
+ /**
63
+ * Where a receipt's `accountIdentity` came from: the vault asserted it in the
64
+ * served reply, the plugin's `parseIdentity` read it from the served token, or
65
+ * neither did and it is only the roster's expectation (`none`: no identity at
66
+ * all).
67
+ */
68
+ export type AccountIdentitySource = 'asserted' | 'parsed' | 'expected' | 'none';
69
+ /**
70
+ * A receipt: what the vault served for one physical send. Each attempt gets
71
+ * its own. It records the exact record version served, because a 401 for
72
+ * this send is reported to the vault against that version.
73
+ */
74
+ export interface ClaustrumScopedAttempt {
75
+ readonly credentialId: string;
76
+ readonly credentialType: VaultCredentialType;
77
+ /**
78
+ * The account this receipt is bound to: the vault's assertion, else the
79
+ * plugin's parse of the token, else the roster's expectation. Check
80
+ * `accountIdentitySource` before treating it as proof.
81
+ */
82
+ readonly accountIdentity?: string;
83
+ readonly accountIdentitySource: AccountIdentitySource;
84
+ /** The account the roster row named when this receipt was requested. */
85
+ readonly expectedAccountIdentity?: string;
86
+ /** The credential id the vault itself put in the served reply, if any. */
87
+ readonly assertedCredentialId?: string;
88
+ /**
89
+ * The account the vault itself put in the served reply, if any. Never filled
90
+ * in from the roster or from a token parse.
91
+ */
92
+ readonly assertedAccountIdentity?: string;
93
+ /**
94
+ * The non-secret Google Cloud project id the vault served in get_scoped for
95
+ * this attempt, if any. Never filled in from list_scoped, the roster or a
96
+ * token parse. A project change in the vault takes effect on the next send.
97
+ */
98
+ readonly projectId?: string;
99
+ /**
100
+ * Kept in memory only and hidden from JSON.stringify and object spreads, so
101
+ * logging a receipt never leaks it. Authorize again for every dispatch and retry.
102
+ */
103
+ readonly accessToken: string;
104
+ readonly recordVersion: number;
105
+ readonly expiresAtMs: number | null;
106
+ }
107
+ /** Reads the provider identity a served token executes under, when the plugin can tell. */
108
+ export type IdentityParser = (accessToken: string) => string | undefined;
109
+ /** Only a new version of the same account may replace an in-flight 401. */
110
+ export declare function isScopedCredentialRotation(served: ClaustrumScopedAttempt, current: ClaustrumScopedAttempt | undefined): current is ClaustrumScopedAttempt;
111
+ /** Why a scoped 401 did or did not retry. Never carries credential material. */
112
+ export type ScopedRetryReason = 'rotated' | 'reauthorize-failed' | 'credential-changed' | 'account-changed' | 'version-unchanged';
113
+ /**
114
+ * Decide whether a request that got a 401 should retry with the freshly
115
+ * re-authorized receipt, and log the decision. The vault can refresh a
116
+ * credential between the send and the 401; the log line lets that refresh be
117
+ * matched against the consumer's retry. `site` names the kind of request.
118
+ */
119
+ export declare function decideScopedRetryAfter401(site: string, served: ClaustrumScopedAttempt, current: ClaustrumScopedAttempt | undefined, logger?: ClaustrumLogger): current is ClaustrumScopedAttempt;
120
+ /**
121
+ * How long a served OAuth token must stay valid after the vault hands it out,
122
+ * so it cannot expire while a long request is still using it.
123
+ */
124
+ export declare const SERVING_MARGIN_MS = 300000;
125
+ /**
126
+ * Reads this consumer's vault credentials (list, fetch, 401 report), with the
127
+ * enrollment token as authorization. Used by every host of a plugin. There is
128
+ * deliberately no credential cache and no single-flight of credential reads:
129
+ * each physical send is authorized by the vault, so a revoked enrollment or a
130
+ * changed record takes effect on the next send. The enrollment token is
131
+ * re-read per operation so an operator's reissue on disk is picked up.
132
+ */
133
+ export declare class ClaustrumScopedCustody {
134
+ #private;
135
+ constructor(options: {
136
+ client: ClaustrumScopedClient;
137
+ family: ClaustrumFamily;
138
+ tokenPath?: string;
139
+ readToken?: () => Promise<EnrollmentTokenFile>;
140
+ parseIdentity?: IdentityParser;
141
+ /**
142
+ * Issue a receipt only when the vault's served reply itself names the
143
+ * requested credential id and the roster's (known) account identity. For
144
+ * providers whose tokens are opaque, where nothing else can prove which
145
+ * account a token belongs to. Off by default: then an absent assertion is
146
+ * no claim, and the receipt says where its identity came from.
147
+ */
148
+ requireAssertion?: boolean;
149
+ now?: () => number;
150
+ logger?: ClaustrumLogger;
151
+ });
152
+ /**
153
+ * List this consumer's credentials. A record that cannot be used is skipped
154
+ * and warned about rather than failing the whole list, so one bad record
155
+ * never hides every other account. Records outside the family are not
156
+ * skipped records: they simply are not this consumer's.
157
+ */
158
+ discover(signal?: AbortSignal): Promise<VaultInventory>;
159
+ /**
160
+ * Fetch the credential for one physical send and wrap it in a fresh receipt.
161
+ * The vault's served identity (or, without one, the plugin's parse of the
162
+ * token) must equal the roster's when both are present. Without
163
+ * `requireAssertion`, absence on either side proves nothing and does not
164
+ * refuse; the receipt records what the vault asserted separately from what
165
+ * the roster expected. With it, a reply that does not itself name the
166
+ * credential id and the expected account is refused.
167
+ */
168
+ authorize(identity: ClaustrumScopedIdentity, signal?: AbortSignal): Promise<ClaustrumScopedAttempt>;
169
+ /**
170
+ * Report that a send this consumer actually made was rejected. Only a 401
171
+ * is reported (the vault refuses 429, 402 and 5xx reports), addressed by
172
+ * credential id with the exact record version that send was served, and
173
+ * signed with the enrollment token that authorized it. A receipt this
174
+ * custody did not issue is refused rather than reported.
175
+ */
176
+ reportFailure(attempt: ClaustrumScopedAttempt, status: number, reporterSource: ClaustrumReporterSource): Promise<void>;
177
+ close(): void;
178
+ }
@@ -0,0 +1,164 @@
1
+ import { type ClaustrumClientOptions as ClaustrumEnrollmentClientOptions, type EnrollmentPollOutcome, type EnrollmentTokenFile, writeEnrollmentTokenFile } from '@cortexkit/claustrum-client';
2
+ /**
3
+ * Codes the vault's closed enrollment-refusal vocabulary marks permanent. The
4
+ * client labels some module error frames transient/retry regardless, so the
5
+ * producer's code takes precedence over the client's action for these.
6
+ */
7
+ export declare const TERMINAL_ENROLLMENT_CODES: ReadonlySet<string>;
8
+ /** Codes that are always worth another ceremony tick, whatever the action says. */
9
+ export declare const RETRYABLE_ENROLLMENT_CODES: ReadonlySet<string>;
10
+ export type EnrollmentDisposition = 'terminal' | 'retry';
11
+ /**
12
+ * Classify an enrollment transport refusal as `(code, disposition)`. The
13
+ * vault's own code wins: a terminal code is terminal even when the client
14
+ * says retry, queue saturation is retryable even when it says gone, and any
15
+ * other code follows the client's action. Errors that are not producer
16
+ * refusals return undefined and are the caller's to rethrow.
17
+ */
18
+ export declare function classifyEnrollmentError(error: unknown): {
19
+ code: string;
20
+ disposition: EnrollmentDisposition;
21
+ } | undefined;
22
+ /**
23
+ * The name a plugin proposes for one host, for example `openai-auth-opencode`.
24
+ * Each host enrolls separately so the operator can revoke one without the other.
25
+ */
26
+ export declare function enrollmentName(plugin: string, host: string): string;
27
+ export interface ClaustrumEnrollmentClient {
28
+ enrollPropose(input: {
29
+ name: string;
30
+ requestSecretHash: string;
31
+ }): Promise<{
32
+ requestId: string;
33
+ }>;
34
+ enrollPoll(input: {
35
+ requestId: string;
36
+ requestSecret: string;
37
+ }): Promise<EnrollmentPollOutcome>;
38
+ }
39
+ export interface ClaustrumEnrollmentConnection extends ClaustrumEnrollmentClient {
40
+ close(): void;
41
+ }
42
+ /**
43
+ * Connect the enrollment ceremony. Setup calls this; the request path never
44
+ * does. `connectionFile` is required because this library reads no
45
+ * environment and knows no host paths.
46
+ */
47
+ export declare function connectClaustrumEnrollmentClient(options: ClaustrumEnrollmentClientOptions & {
48
+ connectionFile: string;
49
+ }): Promise<ClaustrumEnrollmentConnection>;
50
+ export type ClaustrumEnrollmentStatus = {
51
+ state: 'idle';
52
+ } | {
53
+ state: 'pending';
54
+ proposedName: string;
55
+ requestId?: string;
56
+ retryCode?: string;
57
+ } | {
58
+ state: 'approved';
59
+ proposedName: string;
60
+ approvedName?: string;
61
+ tokenGeneration: number;
62
+ } | {
63
+ state: 'denied';
64
+ proposedName: string;
65
+ } | {
66
+ state: 'blocked';
67
+ proposedName: string;
68
+ code: string;
69
+ } | {
70
+ state: 'unavailable';
71
+ proposedName: string;
72
+ code: string;
73
+ } | {
74
+ state: 'busy';
75
+ };
76
+ export interface ClaustrumEnrollmentPaths {
77
+ statePath: string;
78
+ tokenPath: string;
79
+ }
80
+ /** The ceremony state file sits next to the token: `x.json` pairs with `x-state.json`. */
81
+ export declare function getClaustrumEnrollmentPaths(tokenPath: string): ClaustrumEnrollmentPaths;
82
+ /**
83
+ * One host's token and state paths. Every host gets its own pair under the
84
+ * plugin's state directory, so an OpenCode enrollment and a Pi enrollment can
85
+ * be approved and revoked independently. A plugin-resolved override replaces
86
+ * the default token path; a relative override resolves against `cwd`.
87
+ */
88
+ export declare function hostEnrollmentPaths(input: {
89
+ stateDir: string;
90
+ host: string;
91
+ override?: string;
92
+ cwd?: string;
93
+ }): ClaustrumEnrollmentPaths;
94
+ /** Account-file paths are injectable for isolated setup tests; no environment overrides. */
95
+ export interface EnrollmentAncestorOptions {
96
+ passwdPath?: string;
97
+ groupPath?: string;
98
+ }
99
+ /**
100
+ * Protect setup writes from directory replacement. On POSIX, group write access
101
+ * is safe only when local records prove the effective user's group is private;
102
+ * sticky ancestors are accepted. Windows uses the shared safe-parent policy.
103
+ */
104
+ export declare function refuseWritableAncestor(parent: string, options?: EnrollmentAncestorOptions): Promise<void>;
105
+ export declare function readClaustrumEnrollmentStatus(paths: ClaustrumEnrollmentPaths, proposedName: string): Promise<ClaustrumEnrollmentStatus>;
106
+ /**
107
+ * Resolve the host's account mode from its enrollment files. It never changes
108
+ * their contents or the host's mode. On Windows, reading them may create a
109
+ * missing private state folder and tighten an enrollment file's permissions to
110
+ * owner-only, as every private-file read there does.
111
+ */
112
+ export declare function enrollmentAuthority(paths: ClaustrumEnrollmentPaths, proposedName: string): Promise<'vault' | 'local' | 'unreadable'>;
113
+ /**
114
+ * Read fresh bearer material for one scoped operation; never publish it.
115
+ * Re-reading per operation is what lets an operator reissue a token on disk.
116
+ */
117
+ export declare function readClaustrumEnrollmentToken(tokenPath: string): Promise<EnrollmentTokenFile>;
118
+ /**
119
+ * The enrollment ceremony for one host. Run it from setup only: it proposes,
120
+ * polls and persists, and every step can wait on an operator. The request
121
+ * path reads the resulting token and never calls into this class.
122
+ */
123
+ export declare class ClaustrumEnrollmentManager {
124
+ #private;
125
+ constructor(options: {
126
+ client: ClaustrumEnrollmentClient;
127
+ paths: ClaustrumEnrollmentPaths;
128
+ proposedName: string;
129
+ now?: () => number;
130
+ mintSecret?: () => string;
131
+ writeTokenFile?: typeof writeEnrollmentTokenFile;
132
+ ancestorOptions?: EnrollmentAncestorOptions;
133
+ });
134
+ status(): Promise<ClaustrumEnrollmentStatus>;
135
+ resetTerminal(): Promise<ClaustrumEnrollmentResetResult>;
136
+ reconcile(): Promise<ClaustrumEnrollmentStatus>;
137
+ }
138
+ export type ClaustrumEnrollmentResetResult = 'reset' | 'idle' | 'refused-pending' | 'refused-approved' | 'busy';
139
+ /** Reset local terminal metadata without connecting to the credential daemon. */
140
+ export declare function resetClaustrumEnrollmentState(paths: ClaustrumEnrollmentPaths, proposedName: string): Promise<ClaustrumEnrollmentResetResult>;
141
+ export type ClaustrumEnrollmentDisconnectResult = 'disconnected' | 'idle' | 'busy';
142
+ /**
143
+ * Disconnect this host from the vault on disk: remove its enrollment token,
144
+ * then its ceremony state, so `enrollmentAuthority` reports `local` again.
145
+ * This is the disconnect every plugin uses. Removing only the token is not
146
+ * enough: an approved state without a token is still vault mode and refuses,
147
+ * by design. It never contacts the credential daemon, so the enrollment stays
148
+ * approved in the vault until the operator revokes it there; a later Connect
149
+ * starts a new ceremony. A pending request is abandoned the same way.
150
+ *
151
+ * Takes the ceremony lock `reconcile()` and `resetClaustrumEnrollmentState`
152
+ * take, and returns `busy` without deleting anything while another process
153
+ * holds it. State naming another consumer refuses with `wrong-consumer` and
154
+ * both files stay. The token goes first: a crash between the two deletions
155
+ * leaves an approved state without a token, which is still vault mode (it
156
+ * refuses rather than falls back to local accounts), and calling this again
157
+ * finishes the job. Returns `idle` when neither file exists. Nothing else in
158
+ * the directory is touched. The token is removed without being parsed, so a
159
+ * corrupt token does not stand in the way. A malformed state file (bad JSON
160
+ * or wrong shape) is removed too, since it is what makes `enrollmentAuthority`
161
+ * report `unreadable`; a state file that fails the safety checks (symlink,
162
+ * wrong owner, not owner-only) refuses with `unsafe-file` and both files stay.
163
+ */
164
+ export declare function disconnectClaustrumEnrollment(paths: ClaustrumEnrollmentPaths, proposedName: string): Promise<ClaustrumEnrollmentDisconnectResult>;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Every refusal the `/claustrum` code raises itself. Refusals sent by the
3
+ * vault keep arriving as the client's own `ClaustrumCredentialError` (a
4
+ * `code`, a `class` and an `action`), so callers can tell the vault's verdict
5
+ * apart from a check made on this side.
6
+ */
7
+ export type ClaustrumConsumerFailureKind = 'closed' | 'not-enrolled' | 'invalid-token' | 'unavailable' | 'identity-changed' | 'identity-unasserted' | 'insufficient-validity' | 'invalid-material' | 'not-active' | 'route-unavailable' | 'route-declined' | 'no-receipt' | 'unsafe-file' | 'invalid-state' | 'wrong-consumer' | 'roster-busy' | 'host-slot-login' | 'host-slot-placeholder' | 'placeholder-refresh';
8
+ export declare class ClaustrumConsumerError extends Error {
9
+ readonly kind: ClaustrumConsumerFailureKind;
10
+ constructor(kind: ClaustrumConsumerFailureKind, message: string);
11
+ }
12
+ /** The structural logger every class here accepts; `/logger`'s `createLogger` satisfies it. */
13
+ export interface ClaustrumLogger {
14
+ warn(message: string, data?: unknown): void;
15
+ debug(message: string, data?: unknown): void;
16
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * The host's own auth slot (OpenCode's stored login for the provider, Pi's
3
+ * equivalent) must hold something, or the host drops the provider. Under
4
+ * custody it holds this placeholder: an OAuth-shaped value that is never a
5
+ * credential. Its empty access token also makes the vault's sealer refuse it,
6
+ * should it ever be offered for import.
7
+ */
8
+ export declare const CUSTODY_PLACEHOLDER_PREFIX = "claustrum-tombstone:v1:";
9
+ export declare function custodyPlaceholderKey(provider: string): string;
10
+ export declare function custodyPlaceholder(provider: string): {
11
+ type: 'oauth';
12
+ access: '';
13
+ refresh: string;
14
+ expires: 0;
15
+ };
16
+ export declare function isCustodyPlaceholderValue(value: unknown): value is string;
17
+ export declare function isCustodyPlaceholder(auth: unknown, provider: string): boolean;
18
+ /** What the host slot holds: the custody placeholder, a real login, or nothing usable. */
19
+ export type HostSlotContent = 'placeholder' | 'login' | 'empty';
20
+ export declare function classifyHostSlot(auth: unknown, provider: string): HostSlotContent;
21
+ /**
22
+ * Check the host slot against the plugin's mode before serving anything.
23
+ *
24
+ * - Custody mode with a real login in the slot fails closed: someone signed in
25
+ * through the host while the vault owns the accounts, and serving either the
26
+ * login or the vault would silently pick one. The plugin surfaces the error
27
+ * and the user chooses (leave custody, or remove the login).
28
+ * - Local mode with the placeholder in the slot also fails: there is no local
29
+ * credential to serve, and the user has to sign in.
30
+ *
31
+ * Returns the slot content when the combination is consistent.
32
+ */
33
+ export declare function assertHostSlotMatchesMode(input: {
34
+ mode: 'custody' | 'local';
35
+ auth: unknown;
36
+ provider: string;
37
+ }): HostSlotContent;
38
+ /** Refuse to run a local token refresh with the placeholder as the refresh token. */
39
+ export declare function assertNotCustodyPlaceholder(refreshToken: unknown, provider: string): void;
@@ -0,0 +1,19 @@
1
+ import { type ClaustrumClientOptions } from '@cortexkit/claustrum-client';
2
+ import type { ClaustrumScopedClient } from './custody.js';
3
+ export { ClaustrumCredentialError, type ClaustrumReporterSource, type EnrollmentTokenFile, } from '@cortexkit/claustrum-client';
4
+ export { ClaustrumConsumer, type ClaustrumConsumerOptions, type SendOptions, } from './consumer.js';
5
+ export { type AccountIdentitySource, type ClaustrumFamily, type ClaustrumScopedAttempt, type ClaustrumScopedClient, ClaustrumScopedCustody, type ClaustrumScopedIdentity, decideScopedRetryAfter401, type IdentityParser, isScopedCredentialRotation, type ScopedRetryReason, SERVING_MARGIN_MS, type SkippedVaultReason, type SkippedVaultRecord, type VaultCredential, type VaultCredentialType, type VaultInventory, } from './custody.js';
6
+ export { type ClaustrumEnrollmentClient, type ClaustrumEnrollmentConnection, type ClaustrumEnrollmentDisconnectResult, ClaustrumEnrollmentManager, type ClaustrumEnrollmentPaths, type ClaustrumEnrollmentResetResult, type ClaustrumEnrollmentStatus, classifyEnrollmentError, connectClaustrumEnrollmentClient, disconnectClaustrumEnrollment, type EnrollmentDisposition, enrollmentAuthority, enrollmentName, getClaustrumEnrollmentPaths, hostEnrollmentPaths, RETRYABLE_ENROLLMENT_CODES, readClaustrumEnrollmentStatus, readClaustrumEnrollmentToken, resetClaustrumEnrollmentState, TERMINAL_ENROLLMENT_CODES, } from './enrollment.js';
7
+ export { ClaustrumConsumerError, type ClaustrumConsumerFailureKind, type ClaustrumLogger, } from './errors.js';
8
+ export { assertHostSlotMatchesMode, assertNotCustodyPlaceholder, CUSTODY_PLACEHOLDER_PREFIX, classifyHostSlot, custodyPlaceholder, custodyPlaceholderKey, type HostSlotContent, isCustodyPlaceholder, isCustodyPlaceholderValue, } from './host-slot.js';
9
+ export type { InsulaQuotaContext, InsulaQuotaOptions, InsulaUsageEntry, InsulaUsageTransport, InsulaUsageWindow, } from './insula.js';
10
+ export { acceptAccount, type DeclinedAccount, declineAccount, isDeclined, } from './interlock.js';
11
+ export { type AccountMapper, acceptVaultRoute, DEFAULT_ROUTE_PREFIX, declineVaultRoute, type MutateVaultRosterOptions, mutateVaultRoster, type ProjectionOptions, projectVaultRoster, type QuotaReceipt, readVaultRoster, recordVaultAccountQuota, recordVaultQuota, refreshVaultRoster, resolveVaultPrimary, type VaultPrimary, type VaultPrimaryBinding, type VaultPrimaryUnavailableReason, type VaultRosterFile, type VaultRosterLockContext, type VaultRosterRow, vaultRoutingRows, } from './roster.js';
12
+ /**
13
+ * Connect the client that lists and fetches this consumer's vault credentials
14
+ * on the request path. `connectionFile` is required: this library
15
+ * reads no environment, so the plugin resolves the vault's connection file.
16
+ */
17
+ export declare function connectClaustrumScopedClient(options: ClaustrumClientOptions & {
18
+ connectionFile: string;
19
+ }): Promise<ClaustrumScopedClient>;
@@ -0,0 +1,69 @@
1
+ import type { ClaustrumClientOptions } from '@cortexkit/claustrum-client';
2
+ /** Insula's window fields are open: plugins choose which limits they understand. */
3
+ export interface InsulaUsageWindow {
4
+ usedPercent: number;
5
+ rawUsedPercent?: number;
6
+ resetsAt?: string;
7
+ windowMinutes?: number;
8
+ windowKind?: string;
9
+ [field: string]: unknown;
10
+ }
11
+ export interface InsulaUsageEntry {
12
+ provider: string;
13
+ account?: string;
14
+ fetchedAt?: string;
15
+ source?: string;
16
+ stale?: unknown;
17
+ errorClass?: unknown;
18
+ usage?: {
19
+ primary?: InsulaUsageWindow;
20
+ secondary?: InsulaUsageWindow;
21
+ tertiary?: InsulaUsageWindow;
22
+ extraRateWindows?: Array<{
23
+ id?: string;
24
+ title?: string;
25
+ window?: InsulaUsageWindow;
26
+ }>;
27
+ [field: string]: unknown;
28
+ };
29
+ [field: string]: unknown;
30
+ }
31
+ export interface InsulaQuotaContext {
32
+ /** Milliseconds since the epoch, parsed from the producer's fetchedAt. */
33
+ fetchedAt: number;
34
+ source?: string;
35
+ }
36
+ export interface InsulaQuotaOptions<Q> {
37
+ provider: 'codex' | 'claude' | 'antigravity';
38
+ /**
39
+ * Set every converted window's reading time to context.fetchedAt, not poll
40
+ * time. Return undefined for entries the plugin cannot use. Antigravity can
41
+ * receive its own sidebar reading back from insula: accept it only from the
42
+ * vault or when fetchedAt is newer than the plugin's last reading.
43
+ */
44
+ normalize: (entry: InsulaUsageEntry, context: InsulaQuotaContext) => Q | undefined;
45
+ }
46
+ /** Optional transport for custom scoped clients; getUsage must not retry refusals. */
47
+ export interface InsulaUsageTransport {
48
+ connectionFile: string;
49
+ getUsage(): Promise<unknown>;
50
+ }
51
+ /** Constructing this transport opens no connection; getUsage opens one per call. */
52
+ export declare function insulaUsageTransport(options: ClaustrumClientOptions & {
53
+ connectionFile: string;
54
+ }): InsulaUsageTransport;
55
+ export declare class InsulaQuotaSource {
56
+ #private;
57
+ private readonly transport;
58
+ private readonly key;
59
+ constructor(transport: InsulaUsageTransport, key: string);
60
+ refresh(now: number): Promise<void>;
61
+ current(provider: string, accountId: string, now: number): {
62
+ entry: InsulaUsageEntry;
63
+ context: InsulaQuotaContext;
64
+ } | undefined;
65
+ }
66
+ export declare function acquireInsulaQuotaSource(transport: InsulaUsageTransport, tokenPath: string): {
67
+ source: InsulaQuotaSource;
68
+ release(): void;
69
+ };