@cortexkit/common-auth 0.2.3 → 0.2.4

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.
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ // Placeholder so the export resolves until this subpath is built.
2
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ // Placeholder so the export resolves until this subpath is built.
2
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ // Placeholder so the export resolves until this subpath is built.
2
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ // Placeholder so the export resolves until this subpath is built.
2
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ // Placeholder so the export resolves until this subpath is built.
2
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,2 @@
1
+ // Placeholder so the export resolves until this subpath is built.
2
+ export {};
@@ -1,6 +1,6 @@
1
1
  import type { StoredCredential } from './schema.js';
2
2
  /** Every library operation that can fail, as named in the failure value. */
3
- export type PoolOperation = 'initialize' | 'add' | 'replace' | 'rotate' | 'disable' | 'enable' | 'remove' | 'recordIdentity' | 'refresh' | 'pull';
3
+ export type PoolOperation = 'initialize' | 'add' | 'replace' | 'rotate' | 'disable' | 'enable' | 'remove' | 'reorder' | 'recordIdentity' | 'refresh' | 'pull';
4
4
  /**
5
5
  * How far an operation got before it failed.
6
6
  *
@@ -16,7 +16,7 @@ export type PoolFailurePhase = 'before-first-write' | 'after-first-write' | 'pul
16
16
  * lock outcomes (a wait that ran out, and a lease found lost); the rest are
17
17
  * refusals and failures of the operation itself.
18
18
  */
19
- export type PoolFailureKind = 'lock-contention' | 'lock-ownership' | 'pending-migration' | 'load-error' | 'unknown-row' | 'invalid-row' | 'invalid-input' | 'id-exists' | 'id-removed' | 'type-mismatch' | 'no-credential' | 'row-disabled' | 'row-protected' | 'duplicate-identity' | 'row-key-changed' | 'refresh-stamp-ahead' | 'attribution' | 'provider' | 'pull' | 'invalid-quota' | 'after-persist-hook' | 'unexpected';
19
+ export type PoolFailureKind = 'lock-contention' | 'lock-ownership' | 'pending-migration' | 'load-error' | 'unknown-row' | 'invalid-row' | 'invalid-input' | 'id-exists' | 'id-removed' | 'type-mismatch' | 'no-credential' | 'row-disabled' | 'row-protected' | 'duplicate-identity' | 'row-key-changed' | 'invalid-order' | 'refresh-stamp-ahead' | 'attribution' | 'provider' | 'pull' | 'invalid-quota' | 'after-persist-hook' | 'unexpected';
20
20
  /**
21
21
  * The single failure value of every store operation. `committed` is present
22
22
  * only when the operation had already written a credential to the state file
@@ -9,4 +9,4 @@ export interface PoolLogger {
9
9
  * Runs a failure hook. A hook that throws never replaces the failure it was
10
10
  * handed: its exception is logged and discarded.
11
11
  */
12
- export declare function callFailureHook<E>(operation: PoolOperation, hook: ((rowId: string, error: E) => void | Promise<void>) | undefined, rowId: string, error: E, logger: PoolLogger | undefined): Promise<void>;
12
+ export declare function callFailureHook<E, R extends string | undefined>(operation: PoolOperation, hook: ((rowId: R, error: E) => void | Promise<void>) | undefined, rowId: R, error: E, logger: PoolLogger | undefined): Promise<void>;
@@ -10,7 +10,7 @@ export type { PullHook, PullRequest } from './pull.js';
10
10
  export type { ProviderRefresh, ProviderRefreshResult, RefreshOptions, RefreshOutcome, } from './refresh.js';
11
11
  export type { LockEvent, PoolLockOptions, PoolLockSpec, } from './refresh-lock.js';
12
12
  export { POOL_LOCK_DEFAULTS } from './refresh-lock.js';
13
- export type { AddInput, AddResult, FailureHook, RemoveOptions, RemoveResult, RemoveView, RowOperationOptions, RowToggleOptions, } from './rows.js';
13
+ export type { AddInput, AddResult, FailureHook, RemoveOptions, RemoveResult, RemoveView, ReorderOptions, ReorderResult, RowOperationOptions, RowToggleOptions, } from './rows.js';
14
14
  export type { PullReason } from './runtime.js';
15
15
  export type { ApiKeyCredential, OAuthCredential, PoolCredential, PoolRow, QuotaCodec, StoredCredential, } from './schema.js';
16
16
  export { fingerprintOf, LEGACY_STORE_VERSION, POOL_KEY, POOL_ROWS_KEY, POOL_SCHEMA_VERSION, REFRESH_STAMP_TOLERANCE_MS, rowLockKey, } from './schema.js';
@@ -127,6 +127,7 @@ export declare function toFailure(error: unknown, operation: PoolOperation, rowI
127
127
  * The frame every lock-holding operation runs in: failures are mapped onto
128
128
  * the failure value, handed to the failure hook while the outer locks are
129
129
  * still held (the store locks are already released), and rethrown; every
130
- * lock is released afterwards.
130
+ * lock is released afterwards. `rowId` is undefined for an operation that
131
+ * names no row (`reorder`).
131
132
  */
132
- export declare function runOperation<T>(ctx: StoreContext, operation: PoolOperation, rowId: string, onFailure: ((rowId: string, error: PoolOperationError) => void | Promise<void>) | undefined, body: (locks: LockStack, progress: Progress) => Promise<T>): Promise<T>;
133
+ export declare function runOperation<T, R extends string | undefined = string>(ctx: StoreContext, operation: PoolOperation, rowId: R, onFailure: ((rowId: R, error: PoolOperationError) => void | Promise<void>) | undefined, body: (locks: LockStack, progress: Progress) => Promise<T>): Promise<T>;
@@ -335,7 +335,8 @@ export function toFailure(error, operation, rowId, progress) {
335
335
  * The frame every lock-holding operation runs in: failures are mapped onto
336
336
  * the failure value, handed to the failure hook while the outer locks are
337
337
  * still held (the store locks are already released), and rethrown; every
338
- * lock is released afterwards.
338
+ * lock is released afterwards. `rowId` is undefined for an operation that
339
+ * names no row (`reorder`).
339
340
  */
340
341
  export async function runOperation(ctx, operation, rowId, onFailure, body) {
341
342
  const locks = new LockStack(ctx.lockDefaults, ctx.lockEnv);
@@ -5,7 +5,7 @@ import { type HoldPoint, type InitializeOutcome, type StoreContext } from './mut
5
5
  import { type PullHook } from './pull.js';
6
6
  import { type ProviderRefresh, type RefreshOptions, type RefreshOutcome } from './refresh.js';
7
7
  import { type LockEnvironment, type PoolLockOptions, type PoolLockSpec } from './refresh-lock.js';
8
- import { type AddInput, type AddResult, type RemoveOptions, type RemoveResult, type RowOperationOptions, type RowToggleOptions } from './rows.js';
8
+ import { type AddInput, type AddResult, type RemoveOptions, type RemoveResult, type ReorderOptions, type ReorderResult, type RowOperationOptions, type RowToggleOptions } from './rows.js';
9
9
  import { type PoolCredential, type PoolRow, type QuotaCodec, type StoredCredential } from './schema.js';
10
10
  export interface OpenPoolStoreOptions {
11
11
  /** The provider every row of this pool belongs to; keys the provider-wide lock. */
@@ -101,6 +101,13 @@ export interface PoolStore {
101
101
  * (since 0.2.3). Locks as `disable`; `protect` can refuse the id.
102
102
  */
103
103
  remove(id: string, options?: RemoveOptions): Promise<RemoveResult>;
104
+ /**
105
+ * Sets the roster order (since 0.2.4) in one config write. `ids` must name
106
+ * every roster id exactly once; anything else refuses with `invalid-order`
107
+ * and writes nothing. Takes `extraLocks`, then the store locks; no row or
108
+ * provider-wide lock. Roster rows and their entries are left unchanged.
109
+ */
110
+ reorder(ids: readonly string[], options?: ReorderOptions): Promise<ReorderResult>;
104
111
  recordIdentity(id: string, identity: string, options?: RowOperationOptions): Promise<{
105
112
  id: string;
106
113
  disabled: string[];
@@ -3,7 +3,7 @@ import { initializePool, readPool, } from './mutate.js';
3
3
  import { PullScheduler } from './pull.js';
4
4
  import { refreshRow, } from './refresh.js';
5
5
  import { POOL_LOCK_DEFAULTS, } from './refresh-lock.js';
6
- import { addRow, disableRow, enableRow, recordRowIdentity, removeRow, replaceRow, rotateRow, } from './rows.js';
6
+ import { addRow, disableRow, enableRow, recordRowIdentity, removeRow, reorderRows, replaceRow, rotateRow, } from './rows.js';
7
7
  import { POOL_SCHEMA_VERSION, } from './schema.js';
8
8
  /**
9
9
  * Process-wide memory per config file: ids whose per-row entry a library
@@ -94,6 +94,7 @@ export function openPoolStore(options) {
94
94
  disable: (id, reason, callOptions) => disableRow(rt, id, reason, callOptions),
95
95
  enable: (id, callOptions) => enableRow(rt, id, callOptions),
96
96
  remove: (id, callOptions) => removeRow(rt, id, callOptions),
97
+ reorder: (ids, callOptions) => reorderRows(rt, ids, callOptions),
97
98
  recordIdentity: (id, identity, callOptions) => recordRowIdentity(rt, id, identity, callOptions),
98
99
  refresh: (id, provider, callOptions) => refreshRow(rt, id, provider, callOptions),
99
100
  recordQuota: (id, attribution, observation) => recordQuota(rt, id, attribution, observation),
@@ -1,4 +1,4 @@
1
- import type { PoolOperationError } from './errors.js';
1
+ import { PoolOperationError } from './errors.js';
2
2
  import { type Transaction } from './mutate.js';
3
3
  import type { PoolLockSpec } from './refresh-lock.js';
4
4
  import { type StoreRuntime } from './runtime.js';
@@ -57,6 +57,23 @@ export type RemoveResult = {
57
57
  */
58
58
  outcome: 'removed' | 'completed';
59
59
  };
60
+ /**
61
+ * Options of `reorder`. It names no row, so its failure hook is handed only
62
+ * the failure; it takes no row lock and no provider-wide lock, so the extra
63
+ * locks are taken first, then the store locks.
64
+ */
65
+ export interface ReorderOptions {
66
+ /** Called once, awaited, on every non-success path, before the extra locks release. */
67
+ onFailure?: (error: PoolOperationError) => void | Promise<void>;
68
+ /** Locks taken, in this order, before the store locks. */
69
+ extraLocks?: readonly PoolLockSpec[];
70
+ }
71
+ export type ReorderResult = {
72
+ /** The roster order now on disk. */
73
+ ids: string[];
74
+ /** `unchanged` when the roster was already in this order; nothing was written. */
75
+ outcome: 'reordered' | 'unchanged';
76
+ };
60
77
  export interface AddInput {
61
78
  id: string;
62
79
  credential: PoolCredential;
@@ -119,6 +136,16 @@ export declare function enableRow(rt: StoreRuntime, id: string, options?: RowTog
119
136
  * not reused by `add` in this process.
120
137
  */
121
138
  export declare function removeRow(rt: StoreRuntime, id: string, options?: RemoveOptions): Promise<RemoveResult>;
139
+ /**
140
+ * Sets the roster order in one config write. `ids` must name every roster id
141
+ * exactly once; anything else refuses (`invalid-order`) before writing. The
142
+ * roster rows, the per-row entries and the state file are left as they are:
143
+ * only the order of the legacy `accounts` array changes, which older readers
144
+ * load as is. It takes the extra locks, then the store locks, and no row or
145
+ * provider-wide lock, since no row's credential, identity or quota changes.
146
+ * An order equal to the current one writes nothing.
147
+ */
148
+ export declare function reorderRows(rt: StoreRuntime, ids: readonly string[], options?: ReorderOptions): Promise<ReorderResult>;
122
149
  export declare function recordRowIdentity(rt: StoreRuntime, id: string, identity: string, options?: RowOperationOptions): Promise<{
123
150
  id: string;
124
151
  disabled: string[];
@@ -1,3 +1,4 @@
1
+ import { PoolOperationError } from './errors.js';
1
2
  import { assertNotInsideHook, runInsideHook } from './hooks.js';
2
3
  import { DUPLICATE_IDENTITY_REASON, disableIdentityDuplicates, disableIn, recordIdentityIn, } from './identity.js';
3
4
  import { notReadyError, readPool, runOperation, withTransaction, } from './mutate.js';
@@ -355,6 +356,98 @@ function hasStateAccount(state, id) {
355
356
  const accounts = state.accounts;
356
357
  return isRecord(accounts) && Object.hasOwn(accounts, id);
357
358
  }
359
+ /** The id a roster row carries, or undefined for a row that names none. */
360
+ function rosterIdOf(raw) {
361
+ return isRecord(raw) && typeof raw.id === 'string' ? raw.id : undefined;
362
+ }
363
+ /**
364
+ * Why `ids` is not an order of this roster: it must name every distinct
365
+ * roster id exactly once and nothing else.
366
+ */
367
+ function orderProblem(roster, ids) {
368
+ if (!Array.isArray(ids))
369
+ return 'ids must be an array of roster ids';
370
+ const rosterIds = new Set();
371
+ for (const raw of roster) {
372
+ const id = rosterIdOf(raw);
373
+ if (id !== undefined)
374
+ rosterIds.add(id);
375
+ }
376
+ const given = new Set();
377
+ for (const id of ids) {
378
+ if (typeof id !== 'string')
379
+ return 'ids must be an array of roster ids';
380
+ if (given.has(id))
381
+ return `id ${id} appears more than once`;
382
+ if (!rosterIds.has(id))
383
+ return `id ${id} is not in the roster`;
384
+ given.add(id);
385
+ }
386
+ const missing = [...rosterIds].filter((id) => !given.has(id));
387
+ if (missing.length > 0)
388
+ return `the order leaves out roster id(s) ${missing.join(', ')}`;
389
+ return undefined;
390
+ }
391
+ /**
392
+ * The roster in the new order. Every roster row is kept as the same object,
393
+ * so its serialized bytes are unchanged. Rows that carry an id fill the
394
+ * positions such rows held before, in the order of `ids`; a second row with
395
+ * an already-seen id (invalid, but preserved) travels right after the first.
396
+ * A row that names no id cannot be ordered by id, so it keeps its position.
397
+ */
398
+ function reorderedRoster(roster, ids) {
399
+ const byId = new Map();
400
+ for (const raw of roster) {
401
+ const id = rosterIdOf(raw);
402
+ if (id === undefined)
403
+ continue;
404
+ const group = byId.get(id);
405
+ if (group)
406
+ group.push(raw);
407
+ else
408
+ byId.set(id, [raw]);
409
+ }
410
+ const sequence = ids.flatMap((id) => byId.get(id) ?? []);
411
+ let next = 0;
412
+ return roster.map((raw) => rosterIdOf(raw) === undefined ? raw : sequence[next++]);
413
+ }
414
+ /**
415
+ * Sets the roster order in one config write. `ids` must name every roster id
416
+ * exactly once; anything else refuses (`invalid-order`) before writing. The
417
+ * roster rows, the per-row entries and the state file are left as they are:
418
+ * only the order of the legacy `accounts` array changes, which older readers
419
+ * load as is. It takes the extra locks, then the store locks, and no row or
420
+ * provider-wide lock, since no row's credential, identity or quota changes.
421
+ * An order equal to the current one writes nothing.
422
+ */
423
+ export async function reorderRows(rt, ids, options = {}) {
424
+ assertNotInsideHook('reorder');
425
+ const { ctx } = rt;
426
+ const onFailure = options.onFailure;
427
+ return runOperation(ctx, 'reorder', undefined, onFailure && ((_rowId, error) => onFailure(error)), async (locks, progress) => {
428
+ for (const extra of options.extraLocks ?? [])
429
+ await locks.acquire(extra);
430
+ return withTransaction(ctx, locks, progress, { operation: 'reorder', rowId: undefined }, async (tx) => {
431
+ const roster = tx.roster();
432
+ const problem = orderProblem(roster, ids);
433
+ if (problem)
434
+ throw new PoolOperationError({
435
+ operation: 'reorder',
436
+ phase: 'before-first-write',
437
+ retryable: false,
438
+ kind: 'invalid-order',
439
+ message: problem,
440
+ });
441
+ const order = [...ids];
442
+ const next = reorderedRoster(roster, order);
443
+ if (next.every((raw, index) => raw === roster[index]))
444
+ return { ids: order, outcome: 'unchanged' };
445
+ tx.config.accounts = next;
446
+ await tx.commitConfig();
447
+ return { ids: order, outcome: 'reordered' };
448
+ });
449
+ });
450
+ }
358
451
  export async function recordRowIdentity(rt, id, identity, options = {}) {
359
452
  assertNotInsideHook('recordIdentity');
360
453
  return runOperation(rt.ctx, 'recordIdentity', id, options.onFailure, async (locks, progress) => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cortexkit/common-auth",
3
- "version": "0.2.3",
4
- "description": "Shared plumbing for the CortexKit auth plugins: loopback RPC, file locks and atomic writes, logger, sidebar state file, TUI preferences and TUI build.",
3
+ "version": "0.2.4",
4
+ "description": "Shared code for the CortexKit auth plugins: account pool, quota and routing, commands and auth menu, OpenCode 2 hooks, Claustrum custody, and plumbing (loopback RPC, file locks, logger, sidebar state, TUI preferences and build).",
5
5
  "license": "MIT",
6
6
  "repository": {
7
7
  "type": "git",
@@ -51,6 +51,30 @@
51
51
  "./routing": {
52
52
  "types": "./dist/routing/index.d.ts",
53
53
  "import": "./dist/routing/index.js"
54
+ },
55
+ "./commands": {
56
+ "types": "./dist/commands/index.d.ts",
57
+ "import": "./dist/commands/index.js"
58
+ },
59
+ "./auth-menu": {
60
+ "types": "./dist/auth-menu/index.d.ts",
61
+ "import": "./dist/auth-menu/index.js"
62
+ },
63
+ "./opencode2": {
64
+ "types": "./dist/opencode2/index.d.ts",
65
+ "import": "./dist/opencode2/index.js"
66
+ },
67
+ "./claustrum": {
68
+ "types": "./dist/claustrum/index.d.ts",
69
+ "import": "./dist/claustrum/index.js"
70
+ },
71
+ "./cachekeep": {
72
+ "types": "./dist/cachekeep/index.d.ts",
73
+ "import": "./dist/cachekeep/index.js"
74
+ },
75
+ "./dump": {
76
+ "types": "./dist/dump/index.d.ts",
77
+ "import": "./dist/dump/index.js"
54
78
  }
55
79
  },
56
80
  "files": [
@@ -73,7 +97,9 @@
73
97
  "peerDependencies": {
74
98
  "@opentui/core": ">=0.5.12",
75
99
  "@opentui/solid": ">=0.5.12",
76
- "solid-js": "1.9.12"
100
+ "solid-js": "1.9.12",
101
+ "@cortexkit/claustrum-client": ">=0.5.0",
102
+ "@opencode/plugin": ">=2.0.21"
77
103
  },
78
104
  "peerDependenciesMeta": {
79
105
  "@opentui/core": {
@@ -84,10 +110,18 @@
84
110
  },
85
111
  "solid-js": {
86
112
  "optional": true
113
+ },
114
+ "@cortexkit/claustrum-client": {
115
+ "optional": true
116
+ },
117
+ "@opencode/plugin": {
118
+ "optional": true
87
119
  }
88
120
  },
89
121
  "devDependencies": {
90
122
  "@biomejs/biome": "2.5.14",
123
+ "@cortexkit/claustrum-client": "0.5.0",
124
+ "@opencode/plugin": "2.0.21",
91
125
  "@opentui/core": ">=0.5.12",
92
126
  "@opentui/solid": ">=0.5.12",
93
127
  "@tsconfig/bun": "1.0.11",