@cortexkit/anthropic-auth-core 2.3.0 → 2.4.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.
@@ -107,6 +107,15 @@ export interface EnrollmentAncestorOptions {
107
107
  * sticky ancestors are accepted. Windows uses the shared safe-parent policy.
108
108
  */
109
109
  export declare function refuseWritableAncestor(parent: string, options?: EnrollmentAncestorOptions): Promise<void>;
110
+ /**
111
+ * Create `directory` owner-only, or make an existing one owner-only. POSIX:
112
+ * every component it creates gets mode 0700, a directory another user owns is
113
+ * refused, and one that group or others can reach is tightened to 0700.
114
+ * Windows: private-files creates missing components owner-only and verifies
115
+ * existing ones without modifying them. `what` names the directory in the
116
+ * ownership refusal.
117
+ */
118
+ export declare function ensureClaustrumPrivateDirectory(directory: string, options?: EnrollmentAncestorOptions, what?: string): Promise<void>;
110
119
  export declare function readClaustrumEnrollmentStatus(paths: ClaustrumEnrollmentPaths, proposedName: string): Promise<ClaustrumEnrollmentStatus>;
111
120
  /**
112
121
  * Resolve the host's account mode from its enrollment files. It never changes
@@ -0,0 +1,50 @@
1
+ import type { OpenCodeGeneration, SetupHost } from './types.js';
2
+ export declare const UNAVAILABLE_HOST_MESSAGE = "Requested setup host is unknown or unavailable. Use --host=opencode or --host=pi with a detected host.";
3
+ type SwitchKey = 'yes' | 'dryRun' | 'claustrum' | 'noClaustrum' | 'login' | 'enrollOnly';
4
+ /**
5
+ * One flag the shared setup parser accepts. A switch sets `key` to true; a
6
+ * value flag takes one of `values` as `--name=value`, and also as the next
7
+ * argument (`--name value`) when `spaceForm` is set. `invalid` replaces the
8
+ * unknown-argument refusal when a value flag gets a value it does not accept.
9
+ */
10
+ export type SetupFlag = {
11
+ name: string;
12
+ key: SwitchKey;
13
+ description: string;
14
+ } | {
15
+ name: string;
16
+ key: 'hosts' | 'opencode';
17
+ values: readonly string[];
18
+ spaceForm?: boolean;
19
+ invalid?: string;
20
+ description: string;
21
+ };
22
+ /**
23
+ * Every flag `runSetup` accepts. The parser and `setupUsage` both read this
24
+ * table, so the help a plugin prints cannot list a flag the parser refuses or
25
+ * leave out one it accepts.
26
+ */
27
+ export declare const SETUP_FLAGS: readonly SetupFlag[];
28
+ export interface SetupFlags {
29
+ yes: boolean;
30
+ dryRun: boolean;
31
+ claustrum: boolean;
32
+ noClaustrum: boolean;
33
+ login: boolean;
34
+ enrollOnly: boolean;
35
+ hosts: SetupHost[];
36
+ opencode: OpenCodeGeneration | undefined;
37
+ }
38
+ /** Parse setup's arguments against `SETUP_FLAGS`, refusing anything else. */
39
+ export declare function parseSetupFlags(argv: readonly string[]): SetupFlags;
40
+ /**
41
+ * The flags `runSetup` accepts, one per line, for a plugin's `setup --help`.
42
+ * The shared parser has no `--help` of its own: the plugin's bin checks for
43
+ * `--help` or `-h` before calling `runSetup`, prints this text and any lines of
44
+ * its own, and exits 0. With `command` the text starts with a usage line, such
45
+ * as `Usage: example-auth setup [flags]`.
46
+ */
47
+ export declare function setupUsage(options?: {
48
+ command?: string;
49
+ }): string;
50
+ export {};
@@ -2,6 +2,7 @@ export { AMBIGUOUS_OPENCODE_MESSAGE, runSetup } from './command.js';
2
2
  export { SetupCommandError, setupEnrollment } from './command-error.js';
3
3
  export { detectHosts, detectOpenCodeGeneration, findExecutableOnPath, openCodeDesktopMarkers, } from './detect.js';
4
4
  export { ENROLLMENT_WAIT_MS, HostNotEnrolledError, type HostNotEnrolledReason, UNSUPPORTED_VAULT_MESSAGE, } from './enroll.js';
5
+ export { setupUsage } from './flags.js';
5
6
  export { assertHostsStopped, createProcessFence, listSetupProcesses, ProcessFenceViolationError, ProcessInspectionError, type ProcessInspectionStep, } from './process-fence.js';
6
7
  export { type PlannedWrite, planOpenCodeRegistration, registerOpenCode, registerPi, updatePluginConfig, } from './register.js';
7
8
  export type { CommandResult, CommandRunner, HostDetection, OpenCodeGeneration, ProcessFence, SetupCallbackContext, SetupDetection, SetupEnrollmentOutcome, SetupEnvironment, SetupFileSystem, SetupHost, SetupHostEnrollment, SetupIO, SetupPlugin, SetupResult, } from './types.js';
@@ -61,7 +61,13 @@ export interface SetupPlugin {
61
61
  */
62
62
  beforeSignIn?(host: SetupHost, context: SetupCallbackContext): Promise<void>;
63
63
  signInLocal(host: SetupHost, context: SetupCallbackContext): Promise<void>;
64
- migrate(host: SetupHost, context: SetupCallbackContext): Promise<void>;
64
+ /**
65
+ * Moves the host's existing provider credentials into place after the host
66
+ * is registered. Optional: a plugin with nothing to migrate (one that only
67
+ * reads credentials from the vault) leaves it out, and setup then neither
68
+ * calls it nor mentions migration in its dry-run plan.
69
+ */
70
+ migrate?(host: SetupHost, context: SetupCallbackContext): Promise<void>;
65
71
  /** Reject mixed local/vault credentials and activate provider-specific vault access before registration. */
66
72
  beforeRegister?(host: SetupHost, mode: 'local' | 'claustrum', context: SetupCallbackContext): Promise<void>;
67
73
  }
@@ -13,6 +13,7 @@ import { type NativeLocalPoolBinding } from './pool-binding.ts';
13
13
  import { type NativePoolPaths } from './pool-paths.ts';
14
14
  import type { QuotaRequestDispatch } from './quota-manager.ts';
15
15
  import { type RelayConfig } from './relay.ts';
16
+ import { type RelocationGuard } from './relocation-guard.ts';
16
17
  export type { NativeAccountMetadataPatch, NativeAccountSnapshot, NativeAccountView, } from './native-account-view.ts';
17
18
  export interface NativeAccountRuntimeOptions {
18
19
  paths: NativePoolPaths;
@@ -47,6 +48,12 @@ export type NativeRelayUpdate = Partial<Omit<RelayConfig, 'token'>> & {
47
48
  };
48
49
  export interface NativeAccountRuntime {
49
50
  readonly onLockEvent?: (event: NativeLockEvent) => void;
51
+ /**
52
+ * Refuses once a newer version has moved this runtime's files. Hosts pass
53
+ * it to the routing stores they create, so their writes check it under
54
+ * the routing file's lock.
55
+ */
56
+ readonly relocationGuard?: RelocationGuard;
50
57
  readonly vault: NativeVaultRuntime;
51
58
  readonly quotaObservations: NativeQuotaObservations;
52
59
  /** Waits for pending quota observations, quota submissions, and runtime metadata writes. An accepted pool quota update may not yet be on disk; close() drains the pool's pending quota writes. */
@@ -1,4 +1,5 @@
1
1
  import type { NativePoolPaths } from './pool-paths.ts';
2
+ import { type StateLayout } from './state-layout-paths.ts';
2
3
  /** Vault files have no migration journal: enrollment alone authorizes their use. */
3
4
  export interface NativeVaultPaths {
4
5
  config: string;
@@ -9,5 +10,13 @@ export interface NativeVaultPaths {
9
10
  storageId: string;
10
11
  legacySeedConfig: string;
11
12
  }
12
- /** Keep vault settings, metadata and affinity outside every local recovery file. */
13
- export declare function nativeVaultPaths(local: NativePoolPaths): NativeVaultPaths;
13
+ /**
14
+ * Keep vault settings, metadata and affinity outside every local recovery file.
15
+ *
16
+ * `layout` must be the one `local` was resolved in: the local paths don't say
17
+ * where the vault's state files are. It defaults to this process's layout
18
+ * decision. Vault settings stay beside the account file in every layout, and
19
+ * the storage id is always computed from the vault store state's
20
+ * legacy-layout path.
21
+ */
22
+ export declare function nativeVaultPaths(local: NativePoolPaths, layout?: StateLayout): NativeVaultPaths;
@@ -1,3 +1,4 @@
1
+ import { type StateLayout } from './state-layout-paths.ts';
1
2
  export interface NativePoolPaths {
2
3
  legacyConfig: string;
3
4
  legacyState: string;
@@ -19,5 +20,11 @@ export declare function canonicalPath(path: string): Promise<string>;
19
20
  /**
20
21
  * Separate runtime authority from import inputs so a stale legacy writer cannot
21
22
  * rewrite a stamped credential. Both independently configured paths matter.
23
+ *
24
+ * `layout` places the pool state, runtime, journal and custody roster; the pool
25
+ * config stays beside the account file in every layout. It defaults to this
26
+ * process's layout decision, so a re-resolution matches the first resolution.
27
+ * The storage id is always computed from the pool state's legacy-layout path,
28
+ * so moving files between layouts never changes it.
22
29
  */
23
- export declare function resolveNativePoolPaths(legacyConfig?: string, legacyState?: string): Promise<NativePoolPaths>;
30
+ export declare function resolveNativePoolPaths(legacyConfig?: string, legacyState?: string, layout?: StateLayout): Promise<NativePoolPaths>;
@@ -0,0 +1,48 @@
1
+ import type { NativePoolPaths } from './pool-paths.ts';
2
+ import { type RelocationPointer } from './state-layout-paths.ts';
3
+ /**
4
+ * Fixed text of the refusal a process gives once a newer version has moved
5
+ * the state files it uses. It names no path, because hosts show it on screen
6
+ * and in their own logs; the log line written with it names the folders.
7
+ */
8
+ export declare const RELOCATED_MESSAGE = "Anthropic account state was moved to a new folder by a newer anthropic-auth version. Update anthropic-auth in every OpenCode and Pi that uses this folder, then restart them.";
9
+ /**
10
+ * A runtime file this process uses has been replaced by a relocation
11
+ * tombstone: a newer version moved the dataset elsewhere, and this version
12
+ * can't follow it. Every later use of the old files is refused, so this
13
+ * process never writes beside state it no longer owns.
14
+ */
15
+ export declare class RelocatedError extends Error {
16
+ readonly code = "relocated";
17
+ /** The old runtime path that holds the tombstone. */
18
+ readonly runtimePath: string;
19
+ /** Where the tombstone says the state went. */
20
+ readonly relocatedTo: string;
21
+ constructor(runtimePath: string, pointer: RelocationPointer);
22
+ }
23
+ /**
24
+ * Checks whether a dataset has been moved, by looking for a relocation
25
+ * tombstone over its legacy runtime files. It takes no lock: callers run it
26
+ * inside a callback that already holds the lock a mover must hold to install
27
+ * the tombstone, so the check sees either no move or the tombstone.
28
+ */
29
+ export interface RelocationGuard {
30
+ /** Throws `RelocatedError` when either runtime file is a tombstone. */
31
+ assertNotRelocated(): Promise<void>;
32
+ /** True when either runtime file is a tombstone; never throws for it. */
33
+ isRelocated(): Promise<boolean>;
34
+ }
35
+ /** The first tombstone among `runtimePaths`, with the path that holds it. */
36
+ export declare function findRelocationTombstone(runtimePaths: readonly string[]): Promise<{
37
+ path: string;
38
+ pointer: RelocationPointer;
39
+ } | undefined>;
40
+ /** A guard over the given legacy runtime files. */
41
+ export declare function createRelocationGuard(runtimePaths: readonly string[]): RelocationGuard;
42
+ /**
43
+ * The legacy runtime files of a dataset: its local runtime and its vault
44
+ * runtime. A move replaces both with tombstones before it commits.
45
+ */
46
+ export declare function nativeRelocationRuntimePaths(paths: NativePoolPaths): [string, string];
47
+ /** A guard over a dataset's two legacy runtime files. */
48
+ export declare function nativeRelocationGuard(paths: NativePoolPaths): RelocationGuard;
@@ -0,0 +1,193 @@
1
+ import * as nodePath from 'node:path';
2
+ /**
3
+ * Where a dataset's machine-written files live.
4
+ *
5
+ * - `legacy`: beside the account files in OpenCode's config folder, as in every
6
+ * release so far.
7
+ * - `state-home`: in `dir`, the config folder's own subfolder of the CortexKit
8
+ * state root. `dir` must be absolute and canonical (no symlinked component),
9
+ * because vault paths are derived from it without another realpath pass.
10
+ *
11
+ * The layout decides only where files are. Storage ids never depend on it: they
12
+ * are always computed from where the files would be in the legacy layout, so a
13
+ * file copied between layouts keeps the id it was written with.
14
+ */
15
+ export type StateLayout = {
16
+ readonly kind: 'legacy';
17
+ } | {
18
+ readonly kind: 'state-home';
19
+ readonly dir: string;
20
+ };
21
+ export declare const LEGACY_STATE_LAYOUT: StateLayout;
22
+ /**
23
+ * The layout a resolver uses when its caller passes none. A process decides it
24
+ * once, before it resolves any pool path, and never switches: every
25
+ * re-resolution must use the same layout as the first, or comparisons between
26
+ * them fail. No process can choose the state-home layout yet, so this is
27
+ * always the legacy layout.
28
+ */
29
+ export declare function processStateLayout(): StateLayout;
30
+ /** The `node:path` functions the pure helpers use; tests pass `path.win32`. */
31
+ export type StatePathApi = Pick<typeof nodePath, 'join' | 'isAbsolute'>;
32
+ export interface StateRootInputs {
33
+ /** `XDG_STATE_HOME` exactly as read; undefined or empty means unset. */
34
+ xdgStateHome: string | undefined;
35
+ /** Home directory used when `XDG_STATE_HOME` is unset or not usable. */
36
+ home: string;
37
+ /**
38
+ * Use a relative `XDG_STATE_HOME` as given. The XDG base-directory rules say
39
+ * a relative value is invalid and must be ignored, which is the default and
40
+ * what the CLI diagnostics folder has always done. Host enrollment files and
41
+ * the RPC folder have always accepted a relative value, so they keep doing
42
+ * so: changing it would hide an existing enrollment.
43
+ */
44
+ acceptRelative?: boolean;
45
+ }
46
+ /**
47
+ * The CortexKit state root of anthropic-auth:
48
+ * `<XDG_STATE_HOME or ~/.local/state>/cortexkit/anthropic-auth`. Enrollment
49
+ * files, RPC port files, CLI diagnostics and relocated pool state all live
50
+ * under it.
51
+ */
52
+ export declare function anthropicAuthStateRoot(inputs: StateRootInputs, pathApi?: StatePathApi): string;
53
+ /**
54
+ * The identity of a config folder: `realpathSync.native` of it, compared and
55
+ * hashed exactly as returned, with no case folding on any OS. A folder that
56
+ * doesn't exist has no identity, and its dataset stays in the legacy layout.
57
+ */
58
+ export declare function configFolderIdentity(configDir?: string): string | undefined;
59
+ /** OpenCode's config folder when nothing in the environment overrides it. */
60
+ export declare function homeDefaultConfigDir(home?: string, pathApi?: StatePathApi): string;
61
+ /**
62
+ * The state-root subfolder of a config folder. Only the home directory's
63
+ * default folder gets the plain `opencode` name. That folder is derived from
64
+ * the home directory and never from `XDG_CONFIG_HOME`, so two environments
65
+ * with different `XDG_CONFIG_HOME` values can't both claim `opencode`. Every
66
+ * other folder gets `opencode-<first 16 hex digits of sha256(identity)>`.
67
+ */
68
+ export declare function stateSubfolderName(identity: string, homeDefaultIdentity: string | undefined): string;
69
+ export interface StateHomeTarget {
70
+ /** The config folder's identity (see `configFolderIdentity`). */
71
+ identity: string;
72
+ /** The state root this process uses. */
73
+ stateRoot: string;
74
+ /**
75
+ * The config folder's subfolder of `stateRoot`. It may not exist yet, so it
76
+ * is not canonicalized here.
77
+ */
78
+ dir: string;
79
+ }
80
+ /**
81
+ * Where this process would keep the state of OpenCode's config folder in the
82
+ * state-home layout, or undefined when the config folder has no identity.
83
+ * `XDG_STATE_HOME` must be absolute to count; a relative value is ignored.
84
+ */
85
+ export declare function resolveStateHomeTarget(options?: {
86
+ env?: NodeJS.ProcessEnv;
87
+ home?: string;
88
+ }): StateHomeTarget | undefined;
89
+ /**
90
+ * Names of the files kept in a state-home subfolder. Data files keep their
91
+ * default legacy names so the folder stays readable for manual recovery.
92
+ */
93
+ export declare const STATE_HOME_FILE_NAMES: Readonly<{
94
+ localPoolState: "anthropic-auth-pool-state.json";
95
+ localRuntime: "anthropic-auth-native-state.json";
96
+ journal: "anthropic-auth-migration.json";
97
+ localRoster: "anthropic-auth-custody-roster.json";
98
+ vaultStoreState: "anthropic-auth-vault-store-state.json";
99
+ vaultRuntime: "anthropic-auth-vault-state.json";
100
+ vaultRoster: "anthropic-auth-vault-roster.json";
101
+ vaultRouting: "anthropic-auth-vault-routing-state.json";
102
+ requestGzip: "anthropic-auth-request-gzip.json";
103
+ configDirMarker: "config-dir.json";
104
+ layoutMarker: "layout.json";
105
+ }>;
106
+ export type StateHomeFilePaths = {
107
+ readonly [K in keyof typeof STATE_HOME_FILE_NAMES]: string;
108
+ };
109
+ /**
110
+ * Every file of a state-home subfolder. The account files
111
+ * (`anthropic-auth.json`, `anthropic-auth-state.json`), the pool settings
112
+ * (`anthropic-auth-pool.json`), the vault settings
113
+ * (`anthropic-auth-vault-settings.json`), the local routing file and the TUI
114
+ * preferences stay in the config folder in both layouts and are not listed.
115
+ */
116
+ export declare function stateHomeFilePaths(dir: string, pathApi?: StatePathApi): StateHomeFilePaths;
117
+ /**
118
+ * Files of the state-home layout for a dataset given by its canonical legacy
119
+ * account files. Only the default dataset of a config folder can use that
120
+ * layout: custom file names or a state file away from the config file mean an
121
+ * explicit override, which keeps every file beside the named files.
122
+ */
123
+ export declare function stateHomeDatasetFiles(layout: Extract<StateLayout, {
124
+ kind: 'state-home';
125
+ }>, legacyConfig: string, legacyState: string): StateHomeFilePaths;
126
+ export type StateLayoutFileErrorCode = 'unreadable' | 'invalid';
127
+ /** A state-layout file exists but can't be read or doesn't parse. */
128
+ export declare class StateLayoutFileError extends Error {
129
+ readonly code: StateLayoutFileErrorCode;
130
+ readonly path: string;
131
+ constructor(code: StateLayoutFileErrorCode, path: string, cause?: unknown);
132
+ }
133
+ /** `config-dir.json`: names the config folder a state-home subfolder belongs to. */
134
+ export interface ConfigDirMarker {
135
+ readonly version: 1;
136
+ readonly identity: string;
137
+ }
138
+ /**
139
+ * Read `<dir>/config-dir.json`. Undefined only when the file is absent; any
140
+ * other read error, or content that doesn't parse, throws
141
+ * `StateLayoutFileError`.
142
+ */
143
+ export declare function readConfigDirMarker(dir: string): Promise<ConfigDirMarker | undefined>;
144
+ export declare const STATE_LAYOUT_PHASES: readonly ['preparing', 'committed', 'rolled-back', 'restoring', 'restore-staged', 'restored'];
145
+ export type StateLayoutPhase = (typeof STATE_LAYOUT_PHASES)[number];
146
+ declare const CLEANUP_STATES: readonly ['pending', 'done', 'kept'];
147
+ declare const RESTORE_CLEANUP_STATES: readonly ['pending', 'done'];
148
+ /** The fields of `layout.json` that decide whether a process may use a layout. */
149
+ export interface StateLayoutMarker {
150
+ readonly version: 1;
151
+ readonly phase: StateLayoutPhase;
152
+ readonly attempt: string;
153
+ readonly identity: string;
154
+ readonly stateRoot: string;
155
+ readonly sub: string;
156
+ readonly oldDir: string;
157
+ readonly storageIds: {
158
+ readonly local: string;
159
+ readonly vault: string;
160
+ };
161
+ /** Present once a move has committed. */
162
+ readonly cleanup?: (typeof CLEANUP_STATES)[number];
163
+ /** Present while a restore to the legacy layout is under way or finished. */
164
+ readonly restore?: {
165
+ readonly cleanup?: (typeof RESTORE_CLEANUP_STATES)[number];
166
+ };
167
+ }
168
+ /**
169
+ * Read `<dir>/layout.json`. Undefined only when the file is absent. Any other
170
+ * read error, or content that doesn't parse, throws `StateLayoutFileError`:
171
+ * an unreadable marker must never be taken as "no move" or as a finished one.
172
+ */
173
+ export declare function readStateLayoutMarker(dir: string): Promise<StateLayoutMarker | undefined>;
174
+ /**
175
+ * What a relocation tombstone over an old runtime path says. A move replaces
176
+ * each legacy runtime file with one, so older versions refuse the legacy copy
177
+ * and newer ones can name where the state went.
178
+ */
179
+ export interface RelocationPointer {
180
+ readonly relocatedTo: string;
181
+ readonly stateRoot: string;
182
+ readonly identity: string;
183
+ readonly attempt: string;
184
+ readonly by: string;
185
+ }
186
+ /**
187
+ * Read a legacy runtime path as a relocation tombstone. Undefined when the
188
+ * file is absent, isn't a small regular file, or doesn't parse as a
189
+ * tombstone: such a file is an ordinary runtime, valid or not, and its own
190
+ * reader decides. Read errors other than absence are thrown.
191
+ */
192
+ export declare function readRelocationPointer(runtimePath: string): Promise<RelocationPointer | undefined>;
193
+ export {};
@@ -111,6 +111,14 @@ export declare class StickySessionRouter {
111
111
  vaultSeed?: NativeVaultRoutingSeed;
112
112
  localRecoveryPath?: string;
113
113
  onLockEvent?: (event: NativeLockEvent) => void;
114
+ /**
115
+ * Refuses once a newer version has moved the dataset this routing
116
+ * file belongs to. Every write runs it under the routing file's lock,
117
+ * before any temporary file is created.
118
+ */
119
+ relocationGuard?: {
120
+ assertNotRelocated(): Promise<void>;
121
+ };
114
122
  });
115
123
  private now;
116
124
  private readState;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cortexkit/anthropic-auth-core",
3
- "version": "2.3.0",
3
+ "version": "2.4.0",
4
4
  "type": "module",
5
5
  "repository": {
6
6
  "type": "git",
@@ -30,7 +30,7 @@
30
30
  "prepublishOnly": "bun run build"
31
31
  },
32
32
  "devDependencies": {
33
- "@cortexkit/common-auth": "0.27.1"
33
+ "@cortexkit/common-auth": "0.28.0"
34
34
  },
35
35
  "dependencies": {
36
36
  "@cortexkit/claustrum-client": "0.12.0",