@claudexor/cli 3.5.0 → 3.8.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.
Files changed (157) hide show
  1. package/dist/accounts-projection.d.ts +22 -14
  2. package/dist/accounts-projection.d.ts.map +1 -1
  3. package/dist/accounts-projection.js +112 -60
  4. package/dist/accounts-projection.js.map +1 -1
  5. package/dist/accounts-services.d.ts +140 -128
  6. package/dist/accounts-services.d.ts.map +1 -1
  7. package/dist/accounts-services.js +22 -9
  8. package/dist/accounts-services.js.map +1 -1
  9. package/dist/accounts-unified-migration.d.ts +110 -0
  10. package/dist/accounts-unified-migration.d.ts.map +1 -0
  11. package/dist/accounts-unified-migration.js +422 -0
  12. package/dist/accounts-unified-migration.js.map +1 -0
  13. package/dist/agy-quota-source.d.ts +1 -0
  14. package/dist/agy-quota-source.d.ts.map +1 -1
  15. package/dist/agy-quota-source.js +61 -108
  16. package/dist/agy-quota-source.js.map +1 -1
  17. package/dist/auth-login-harnesses.d.ts +7 -5
  18. package/dist/auth-login-harnesses.d.ts.map +1 -1
  19. package/dist/auth-login-harnesses.js +10 -9
  20. package/dist/auth-login-harnesses.js.map +1 -1
  21. package/dist/capabilities.d.ts.map +1 -1
  22. package/dist/capabilities.js +6 -1
  23. package/dist/capabilities.js.map +1 -1
  24. package/dist/claude-oauth-usage.d.ts.map +1 -1
  25. package/dist/claude-oauth-usage.js +9 -3
  26. package/dist/claude-oauth-usage.js.map +1 -1
  27. package/dist/claude-statusline.d.ts.map +1 -1
  28. package/dist/claude-statusline.js +14 -1
  29. package/dist/claude-statusline.js.map +1 -1
  30. package/dist/claudexord-entry.d.ts +7 -2
  31. package/dist/claudexord-entry.d.ts.map +1 -1
  32. package/dist/claudexord-entry.js +33 -11
  33. package/dist/claudexord-entry.js.map +1 -1
  34. package/dist/claudexord.d.ts.map +1 -1
  35. package/dist/claudexord.js +25 -37
  36. package/dist/claudexord.js.map +1 -1
  37. package/dist/cli.js +2 -1
  38. package/dist/cli.js.map +1 -1
  39. package/dist/codex-quota-source.d.ts.map +1 -1
  40. package/dist/codex-quota-source.js +8 -3
  41. package/dist/codex-quota-source.js.map +1 -1
  42. package/dist/command-flags.d.ts.map +1 -1
  43. package/dist/command-flags.js +1 -0
  44. package/dist/command-flags.js.map +1 -1
  45. package/dist/control-services.d.ts +206 -188
  46. package/dist/control-services.d.ts.map +1 -1
  47. package/dist/control-services.js +54 -122
  48. package/dist/control-services.js.map +1 -1
  49. package/dist/credential-commands.d.ts +16 -0
  50. package/dist/credential-commands.d.ts.map +1 -1
  51. package/dist/credential-commands.js +93 -39
  52. package/dist/credential-commands.js.map +1 -1
  53. package/dist/credential-profile-mutations.d.ts +70 -0
  54. package/dist/credential-profile-mutations.d.ts.map +1 -0
  55. package/dist/credential-profile-mutations.js +219 -0
  56. package/dist/credential-profile-mutations.js.map +1 -0
  57. package/dist/credential-status-invalidation.d.ts +11 -0
  58. package/dist/credential-status-invalidation.d.ts.map +1 -0
  59. package/dist/credential-status-invalidation.js +36 -0
  60. package/dist/credential-status-invalidation.js.map +1 -0
  61. package/dist/daemon-admission-runtime.d.ts +5 -0
  62. package/dist/daemon-admission-runtime.d.ts.map +1 -1
  63. package/dist/daemon-admission-runtime.js +1 -0
  64. package/dist/daemon-admission-runtime.js.map +1 -1
  65. package/dist/delegation-belt-descriptor.d.ts +2 -1
  66. package/dist/delegation-belt-descriptor.d.ts.map +1 -1
  67. package/dist/delegation-belt-descriptor.js +2 -1
  68. package/dist/delegation-belt-descriptor.js.map +1 -1
  69. package/dist/harness-command-specs.d.ts.map +1 -1
  70. package/dist/harness-command-specs.js +4 -4
  71. package/dist/harness-command-specs.js.map +1 -1
  72. package/dist/harness-command.d.ts +5 -3
  73. package/dist/harness-command.d.ts.map +1 -1
  74. package/dist/harness-command.js +5 -3
  75. package/dist/harness-command.js.map +1 -1
  76. package/dist/harness-install-lease.d.ts +11 -0
  77. package/dist/harness-install-lease.d.ts.map +1 -0
  78. package/dist/harness-install-lease.js +98 -0
  79. package/dist/harness-install-lease.js.map +1 -0
  80. package/dist/harness-install-proof.d.ts +36 -0
  81. package/dist/harness-install-proof.d.ts.map +1 -0
  82. package/dist/harness-install-proof.js +148 -0
  83. package/dist/harness-install-proof.js.map +1 -0
  84. package/dist/harness-install-recipes.d.ts +80 -0
  85. package/dist/harness-install-recipes.d.ts.map +1 -0
  86. package/dist/harness-install-recipes.js +130 -0
  87. package/dist/harness-install-recipes.js.map +1 -0
  88. package/dist/harness-installer.d.ts +45 -46
  89. package/dist/harness-installer.d.ts.map +1 -1
  90. package/dist/harness-installer.js +248 -156
  91. package/dist/harness-installer.js.map +1 -1
  92. package/dist/mcp-run-projections.d.ts +1 -1
  93. package/dist/native-login.d.ts +7 -0
  94. package/dist/native-login.d.ts.map +1 -1
  95. package/dist/native-login.js +40 -17
  96. package/dist/native-login.js.map +1 -1
  97. package/dist/profile-registration.d.ts +15 -1
  98. package/dist/profile-registration.d.ts.map +1 -1
  99. package/dist/profile-registration.js +124 -7
  100. package/dist/profile-registration.js.map +1 -1
  101. package/dist/quota-services.d.ts +4 -4
  102. package/dist/quota-subject-universe.d.ts.map +1 -1
  103. package/dist/quota-subject-universe.js +6 -0
  104. package/dist/quota-subject-universe.js.map +1 -1
  105. package/dist/remote-command.d.ts +0 -2
  106. package/dist/remote-command.d.ts.map +1 -1
  107. package/dist/remote-command.js +3 -74
  108. package/dist/remote-command.js.map +1 -1
  109. package/dist/run-orchestrator.d.ts +28 -0
  110. package/dist/run-orchestrator.d.ts.map +1 -0
  111. package/dist/run-orchestrator.js +43 -0
  112. package/dist/run-orchestrator.js.map +1 -0
  113. package/dist/settings-service.d.ts +1 -1
  114. package/dist/settings-service.js +3 -3
  115. package/dist/settings-service.js.map +1 -1
  116. package/dist/setup-attach-command.d.ts +30 -0
  117. package/dist/setup-attach-command.d.ts.map +1 -0
  118. package/dist/setup-attach-command.js +118 -0
  119. package/dist/setup-attach-command.js.map +1 -0
  120. package/dist/setup-job-store.d.ts +2 -2
  121. package/dist/setup-job-support.d.ts +36 -9
  122. package/dist/setup-job-support.d.ts.map +1 -1
  123. package/dist/setup-job-support.js +81 -10
  124. package/dist/setup-job-support.js.map +1 -1
  125. package/dist/setup-jobs.d.ts +4 -4
  126. package/dist/setup-jobs.d.ts.map +1 -1
  127. package/dist/setup-jobs.js +50 -47
  128. package/dist/setup-jobs.js.map +1 -1
  129. package/dist/setup-login-capability.d.ts +37 -0
  130. package/dist/setup-login-capability.d.ts.map +1 -0
  131. package/dist/setup-login-capability.js +96 -0
  132. package/dist/setup-login-capability.js.map +1 -0
  133. package/dist/setup-login-io.d.ts +4 -1
  134. package/dist/setup-login-io.d.ts.map +1 -1
  135. package/dist/setup-login-io.js +22 -3
  136. package/dist/setup-login-io.js.map +1 -1
  137. package/dist/setup-login-pty.d.ts +64 -22
  138. package/dist/setup-login-pty.d.ts.map +1 -1
  139. package/dist/setup-login-pty.js +289 -48
  140. package/dist/setup-login-pty.js.map +1 -1
  141. package/dist/setup-login-runner-support.d.ts +12 -0
  142. package/dist/setup-login-runner-support.d.ts.map +1 -0
  143. package/dist/setup-login-runner-support.js +65 -0
  144. package/dist/setup-login-runner-support.js.map +1 -0
  145. package/dist/setup-login-runner.d.ts +2 -0
  146. package/dist/setup-login-runner.d.ts.map +1 -1
  147. package/dist/setup-login-runner.js +119 -116
  148. package/dist/setup-login-runner.js.map +1 -1
  149. package/dist/setup-runner-outcome.d.ts +10 -0
  150. package/dist/setup-runner-outcome.d.ts.map +1 -0
  151. package/dist/setup-runner-outcome.js +49 -0
  152. package/dist/setup-runner-outcome.js.map +1 -0
  153. package/dist/thread-continuity-context.d.ts +24 -0
  154. package/dist/thread-continuity-context.d.ts.map +1 -1
  155. package/dist/thread-continuity-context.js +17 -0
  156. package/dist/thread-continuity-context.js.map +1 -1
  157. package/package.json +24 -24
@@ -6,12 +6,14 @@ import { noProjectRepoRoot } from "@claudexor/util";
6
6
  import { validateModel } from "@claudexor/core";
7
7
  import { withQuotaAvailability } from "@claudexor/schema";
8
8
  import { vendorVerifiedProfileStatus } from "@claudexor/orchestrator";
9
- import { harnessAccountsProjection, profileAccountProjection } from "./accounts-projection.js";
10
- import { buildGateway, harnessModels } from "./registry.js";
9
+ import { accountPoolsProjection, profileAccountProjection } from "./accounts-projection.js";
10
+ import { buildGateway, buildRegistry, harnessModels } from "./registry.js";
11
11
  import { delegationCapabilityFor } from "./delegation-capability.js";
12
+ import { effectiveSetupLoginCapability } from "./setup-login-capability.js";
12
13
  const NO_PROJECT_ROOT = noProjectRepoRoot();
13
14
  export async function projectHarnessStatuses(statuses) {
14
15
  const cfg = loadConfig(NO_PROJECT_ROOT);
16
+ const adapters = buildRegistry({ includeFakes: false });
15
17
  return Promise.all(statuses.map(async (status) => {
16
18
  const configured = cfg.global.harnesses[status.id]?.default_model ?? null;
17
19
  let check = null;
@@ -24,6 +26,9 @@ export async function projectHarnessStatuses(statuses) {
24
26
  configuredModel: configured,
25
27
  configuredModelCheck: check,
26
28
  delegation: delegationCapabilityFor(status.manifest),
29
+ setupLogin: await effectiveSetupLoginCapability(status.id, {
30
+ getAdapter: (id) => adapters.get(id),
31
+ }),
27
32
  readiness: normalizeReadiness({
28
33
  checks: status.checks,
29
34
  authSources: status.authSources,
@@ -35,11 +40,13 @@ export async function projectHarnessStatuses(statuses) {
35
40
  }
36
41
  /** One server-owned Accounts response builder. The opt-in form refreshes quota
37
42
  * first and then derives next_up from that exact returned response; no client
38
- * can accidentally pair a newer quota card with an older routing identity. */
43
+ * can accidentally pair a newer quota card with an older routing identity.
44
+ * Returns the listing service plus the pool-authority read
45
+ * (`GET /v2/account-pools`) so both share one cached projection. */
39
46
  export function createCredentialProfilesService(quotaRegistry) {
40
47
  const projectProfiles = () => {
41
48
  const profiles = loadConfig(NO_PROJECT_ROOT).global.credential_profiles;
42
- return Promise.all(profiles.map(profileAccountProjection));
49
+ return Promise.all(profiles.map((profile) => profileAccountProjection(profile, profiles)));
43
50
  };
44
51
  // The plain (non-snapshot) form is the UI's poll target: without a cache it
45
52
  // ran a doctor probe per registered profile plus a full harness sweep
@@ -53,7 +60,10 @@ export function createCredentialProfilesService(quotaRegistry) {
53
60
  const out = withVendorVerification(await projectProfiles(), quota);
54
61
  return {
55
62
  profiles: out,
56
- harnessAccounts: await harnessAccountsProjection(NO_PROJECT_ROOT, quota.snapshots, {
63
+ // Unified account model: the legacy carrier stays PRESENT and empty for
64
+ // strict old clients; routing facts ride accountPools.
65
+ harnessAccounts: [],
66
+ accountPools: await accountPoolsProjection(NO_PROJECT_ROOT, quota.snapshots, {
57
67
  profiles: out,
58
68
  }),
59
69
  };
@@ -61,7 +71,7 @@ export function createCredentialProfilesService(quotaRegistry) {
61
71
  const pollCache = new StatusProjectionCache({
62
72
  versionOf: globalConfigVersion,
63
73
  });
64
- return async (input) => {
74
+ const credentialProfiles = async (input) => {
65
75
  if (input?.snapshot === true) {
66
76
  const [probed, accountStatuses, git, fencedQuota] = await Promise.all([
67
77
  projectProfiles(),
@@ -73,7 +83,6 @@ export function createCredentialProfilesService(quotaRegistry) {
73
83
  quotaRegistry().refreshWithCursor(),
74
84
  ]);
75
85
  const statuses = accountStatuses.map((receipt) => receipt.status);
76
- const accountIdentities = new Map(accountStatuses.map((receipt) => [receipt.status.id, receipt.identity]));
77
86
  const rawQuota = fencedQuota.response;
78
87
  const out = withVendorVerification(probed, rawQuota);
79
88
  // The explicit refresh proved the live state — drop the stale poll
@@ -82,10 +91,10 @@ export function createCredentialProfilesService(quotaRegistry) {
82
91
  pollCache.invalidate();
83
92
  return {
84
93
  profiles: out,
85
- harnessAccounts: await harnessAccountsProjection(NO_PROJECT_ROOT, rawQuota.snapshots, {
94
+ harnessAccounts: [],
95
+ accountPools: await accountPoolsProjection(NO_PROJECT_ROOT, rawQuota.snapshots, {
86
96
  profiles: out,
87
97
  statuses,
88
- accountIdentities,
89
98
  }),
90
99
  harnesses: await projectHarnessStatuses(statuses),
91
100
  git,
@@ -95,6 +104,10 @@ export function createCredentialProfilesService(quotaRegistry) {
95
104
  }
96
105
  return pollCache.read(buildPollResponse);
97
106
  };
107
+ const accountPools = async () => ({
108
+ accountPools: (await pollCache.read(buildPollResponse)).accountPools,
109
+ });
110
+ return { credentialProfiles, accountPools };
98
111
  }
99
112
  /**
100
113
  * Replace each profile's LOCAL verification verdict with the vendor's, wherever
@@ -1 +1 @@
1
- {"version":3,"file":"accounts-services.js","sourceRoot":"","sources":["../src/accounts-services.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"accounts-services.js","sourceRoot":"","sources":["../src/accounts-services.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,110 @@
1
+ import type { AccountsUnifiedMigrationFile } from "@claudexor/schema";
2
+ /** The harnesses whose legacy default stores auto-register (cursor's host
3
+ * Keychain login is deliberately NOT migrated — owner decision D-U3: host
4
+ * logins disappear; new cursor logins are ordinary isolated file-store rows). */
5
+ export declare const ACCOUNTS_MIGRATION_HARNESSES: readonly ["claude", "codex"];
6
+ export type AccountsMigrationHarness = (typeof ACCOUNTS_MIGRATION_HARNESSES)[number];
7
+ export declare function accountsMigrationFilePath(): string;
8
+ /** Lenient read for projection/mutation consumers: any unreadable state reads
9
+ * as "no records" (those surfaces fail safe on their own rules). The RUN GATE
10
+ * and the migration itself use the strict variant below — a corrupt phase
11
+ * file must fail CLOSED there, never silently pass runs against unknown
12
+ * migration state or overwrite the evidence with a fresh reservation. */
13
+ export declare function readAccountsMigrationFile(): AccountsUnifiedMigrationFile;
14
+ /** ENOENT/ENOTDIR = honestly no record ({}); anything else — an unreadable
15
+ * file, a JSON parse failure, a schema violation — THROWS a typed error so
16
+ * the gate and the migration fail closed instead of treating corrupt state
17
+ * as absent. */
18
+ export declare function readAccountsMigrationFileStrict(): AccountsUnifiedMigrationFile;
19
+ /**
20
+ * The daemon startup wrapper: run (or crash-resume) the migration, invalidate
21
+ * status projections when anything changed, and NEVER crash startup — a
22
+ * failed pass leaves the phase file mid-state, the affected harness refuses
23
+ * runs typed (accountsMigrationGate), and the next start resumes the
24
+ * remaining phases.
25
+ */
26
+ export declare function runStartupAccountsMigration(threads: AccountsMigrationStores["threads"], quota: AccountsMigrationStores["quota"] & {
27
+ noteCredentialChange(): void;
28
+ }, log: (message: string) => void): void;
29
+ /** Retire one harness's migration record (its row was deleted with its
30
+ * material, so a later start detects no legacy login and fabricates nothing). */
31
+ export declare function removeAccountsMigrationRecord(harnessId: string): void;
32
+ /**
33
+ * Typed per-harness run refusal while this harness's migration is incomplete
34
+ * (plan §L.3): a crash between phases must not let runs route against
35
+ * half-migrated continuity state. Complete (or absent) records never block.
36
+ */
37
+ export declare function accountsMigrationGate(harnessId: string): {
38
+ reason: string;
39
+ } | null;
40
+ /** The harness's exact Claudexor-owned default native store dir, or null for
41
+ * harnesses without one (D-U3: cursor has no default store; agy is named-only). */
42
+ export declare function defaultNativeStoreLocator(harnessId: string): string | null;
43
+ /**
44
+ * The ONE structural "this row IS the harness's default native store" rule,
45
+ * shared by the delete fence and the `native_credentials_enabled` downgrade
46
+ * mirror: true when the locator resolves to the harness's exact
47
+ * defaultNative*Dir() — the same rule the startup migration matches rows by.
48
+ * Bootstrap rows (ensureBootstrapProfile) sit at this locator BEFORE any
49
+ * migration record exists, so record presence must never be the only key.
50
+ */
51
+ export declare function locatorIsDefaultNativeStore(harnessId: string, locator: string | null | undefined): boolean;
52
+ /** The stores the migration mutates, injected so tests drive temp roots. */
53
+ export interface AccountsMigrationStores {
54
+ threads: {
55
+ migrateNullProfileContinuity(harnessId: string, rowId: string): {
56
+ sessions: number;
57
+ checkpoints: number;
58
+ skippedPartitions: string[];
59
+ };
60
+ rollbackProfileContinuity(harnessId: string, rowId: string): {
61
+ sessions: number;
62
+ checkpoints: number;
63
+ skippedPartitions: string[];
64
+ };
65
+ listThreads(): Array<{
66
+ repo?: {
67
+ root: string;
68
+ } | null;
69
+ }>;
70
+ };
71
+ quota: {
72
+ removeSubject(harness: string, subjectId: string | null): number;
73
+ };
74
+ log?: (message: string) => void;
75
+ }
76
+ export interface AccountsMigrationReceipt {
77
+ harness_id: string;
78
+ row_id: string;
79
+ outcome: "migrated" | "resumed" | "already_completed";
80
+ sessions: number;
81
+ checkpoints: number;
82
+ lanes: number;
83
+ skipped_partitions: string[];
84
+ }
85
+ /**
86
+ * Run (or crash-resume) the unified-accounts migration for every harness with
87
+ * a detected legacy login. Idempotent: the second and later starts find
88
+ * completed records (or nothing to detect) and mutate nothing (§K.6 case 6).
89
+ */
90
+ export declare function runAccountsUnifiedMigration(stores: AccountsMigrationStores): AccountsMigrationReceipt[];
91
+ export interface AccountsRollbackReceipt {
92
+ harness_id: string;
93
+ row_id: string;
94
+ sessions: number;
95
+ checkpoints: number;
96
+ lanes: number;
97
+ skipped_partitions: string[];
98
+ }
99
+ /**
100
+ * The supported downgrade path (run BEFORE installing 3.5.0): surgically
101
+ * reverses every migration phase — sessions/checkpoints/lane homes return to
102
+ * the null engine-default keys, the auto-registered row leaves the registry
103
+ * (its `enabled` state returns to the `native_credentials_enabled` mirror),
104
+ * the row's quota subject retires, and the harness's migration record is
105
+ * removed so a later new-engine start re-migrates cleanly. The pre-migration
106
+ * config backup (`backup_ref`) stays on disk as manual-recovery evidence.
107
+ * Credential bytes were never moved, so nothing is copied back.
108
+ */
109
+ export declare function rollbackAccountsUnifiedMigration(stores: AccountsMigrationStores, harnessId?: string): AccountsRollbackReceipt[];
110
+ //# sourceMappingURL=accounts-unified-migration.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"accounts-unified-migration.d.ts","sourceRoot":"","sources":["../src/accounts-unified-migration.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,422 @@
1
+ /**
2
+ * Unified-accounts startup migration (INV-135 rewrite, plan §L.3).
3
+ *
4
+ * At daemon start, a DETECTED legacy default-store login (claude/codex) is
5
+ * auto-registered as an ordinary registry row — reserved machine id
6
+ * `claude-default` / `codex-default`, locator = the existing Claudexor-owned
7
+ * native dir, bytes never move — and the null engine-default subject's
8
+ * continuity state (thread sessions, lane checkpoints, durable lane homes)
9
+ * migrates onto that row id as ONE unit (INV-137). Progress is a
10
+ * crash-recoverable per-harness state machine persisted OUTSIDE config.yaml
11
+ * (`<config>/migration/accounts-unified.json`; a downgraded 3.5.0 engine never
12
+ * reads it): `reserved → registry_written → continuity_migrated → completed`,
13
+ * every phase restart-idempotent. While a harness's record is incomplete, that
14
+ * harness's runs refuse typed (`accountsMigrationGate`) — other harnesses keep
15
+ * working. `rollbackAccountsUnifiedMigration` is the supported downgrade
16
+ * path's first step: it surgically reverses every phase (run it BEFORE
17
+ * installing an engine whose canonicalizers refuse the native locator).
18
+ */
19
+ import { copyFileSync, existsSync, mkdirSync, readFileSync, renameSync, writeFileSync, } from "node:fs";
20
+ import { join } from "node:path";
21
+ import { loadConfig, updateGlobalConfig } from "@claudexor/config";
22
+ import { normalizeThroughExistingAncestor } from "@claudexor/core";
23
+ import { claudeAccountIdentity, defaultNativeClaudeConfigDir } from "@claudexor/harness-claude";
24
+ import { codexAccountIdentity, defaultNativeCodexHome } from "@claudexor/harness-codex";
25
+ import { AccountsUnifiedMigrationFile as MigrationFileSchema } from "@claudexor/schema";
26
+ import { migrateDefaultLanes, rollbackProfileLanes } from "@claudexor/workspace";
27
+ import { invalidateStatusProjections } from "./status-projection-cache.js";
28
+ import { noProjectRepoRoot, nowIso, userConfigDir } from "@claudexor/util";
29
+ /** The harnesses whose legacy default stores auto-register (cursor's host
30
+ * Keychain login is deliberately NOT migrated — owner decision D-U3: host
31
+ * logins disappear; new cursor logins are ordinary isolated file-store rows). */
32
+ export const ACCOUNTS_MIGRATION_HARNESSES = ["claude", "codex"];
33
+ export function accountsMigrationFilePath() {
34
+ return join(userConfigDir(), "migration", "accounts-unified.json");
35
+ }
36
+ /** Lenient read for projection/mutation consumers: any unreadable state reads
37
+ * as "no records" (those surfaces fail safe on their own rules). The RUN GATE
38
+ * and the migration itself use the strict variant below — a corrupt phase
39
+ * file must fail CLOSED there, never silently pass runs against unknown
40
+ * migration state or overwrite the evidence with a fresh reservation. */
41
+ export function readAccountsMigrationFile() {
42
+ try {
43
+ return readAccountsMigrationFileStrict();
44
+ }
45
+ catch {
46
+ return {};
47
+ }
48
+ }
49
+ /** ENOENT/ENOTDIR = honestly no record ({}); anything else — an unreadable
50
+ * file, a JSON parse failure, a schema violation — THROWS a typed error so
51
+ * the gate and the migration fail closed instead of treating corrupt state
52
+ * as absent. */
53
+ export function readAccountsMigrationFileStrict() {
54
+ const path = accountsMigrationFilePath();
55
+ let raw;
56
+ try {
57
+ raw = readFileSync(path, "utf8");
58
+ }
59
+ catch (error) {
60
+ const code = error.code;
61
+ if (code === "ENOENT" || code === "ENOTDIR")
62
+ return {};
63
+ throw Object.assign(new Error(`unified-accounts migration record ${path} is unreadable (${code ?? "io_error"})`), { code: "accounts_migration_record_unreadable" });
64
+ }
65
+ try {
66
+ return MigrationFileSchema.parse(JSON.parse(raw));
67
+ }
68
+ catch (error) {
69
+ throw Object.assign(new Error(`unified-accounts migration record ${path} is corrupt: ${error instanceof Error ? error.message : String(error)}; fix or remove the file before runs of migrated harnesses can proceed`), { code: "accounts_migration_record_unreadable" });
70
+ }
71
+ }
72
+ function writeAccountsMigrationFile(file) {
73
+ const path = accountsMigrationFilePath();
74
+ mkdirSync(join(path, ".."), { recursive: true, mode: 0o700 });
75
+ const tmp = `${path}.tmp.${process.pid}`;
76
+ writeFileSync(tmp, `${JSON.stringify(file, null, 2)}\n`, { mode: 0o600 });
77
+ renameSync(tmp, path);
78
+ }
79
+ /**
80
+ * The daemon startup wrapper: run (or crash-resume) the migration, invalidate
81
+ * status projections when anything changed, and NEVER crash startup — a
82
+ * failed pass leaves the phase file mid-state, the affected harness refuses
83
+ * runs typed (accountsMigrationGate), and the next start resumes the
84
+ * remaining phases.
85
+ */
86
+ export function runStartupAccountsMigration(threads, quota, log) {
87
+ try {
88
+ if (runAccountsUnifiedMigration({ threads, quota, log }).length > 0) {
89
+ invalidateStatusProjections();
90
+ quota.noteCredentialChange();
91
+ }
92
+ }
93
+ catch (error) {
94
+ log(`unified-accounts migration pass failed (will resume on the next start): ${error instanceof Error ? error.message : String(error)}`);
95
+ }
96
+ }
97
+ /** Retire one harness's migration record (its row was deleted with its
98
+ * material, so a later start detects no legacy login and fabricates nothing). */
99
+ export function removeAccountsMigrationRecord(harnessId) {
100
+ const file = { ...readAccountsMigrationFile() };
101
+ if (!(harnessId in file))
102
+ return;
103
+ delete file[harnessId];
104
+ writeAccountsMigrationFile(file);
105
+ }
106
+ /**
107
+ * Typed per-harness run refusal while this harness's migration is incomplete
108
+ * (plan §L.3): a crash between phases must not let runs route against
109
+ * half-migrated continuity state. Complete (or absent) records never block.
110
+ */
111
+ export function accountsMigrationGate(harnessId) {
112
+ let record;
113
+ try {
114
+ record = readAccountsMigrationFileStrict()[harnessId];
115
+ }
116
+ catch (error) {
117
+ // A CORRUPT phase file fails the gate CLOSED: runs must not route
118
+ // against unknown migration state (an absent file passes above via the
119
+ // strict reader's ENOENT branch).
120
+ return { reason: error instanceof Error ? error.message : String(error) };
121
+ }
122
+ if (!record || record.phase === "completed")
123
+ return null;
124
+ if (record.phase === "rolling_back") {
125
+ return {
126
+ reason: `${harnessId} is blocked by an interrupted unified-accounts rollback; ` +
127
+ `re-run the rollback command (\`claudexor accounts-migration-rollback\`) to finish it`,
128
+ };
129
+ }
130
+ return {
131
+ reason: `${harnessId} is blocked by an incomplete unified-accounts migration ` +
132
+ `(phase: ${record.phase}); restart the daemon (\`claudexor daemon stop\`, then any command) ` +
133
+ `to finish it, or roll it back (\`claudexor accounts-migration-rollback\`)`,
134
+ };
135
+ }
136
+ /** Only ACTUALLY DETECTED logins register — an empty default store writes no
137
+ * record and fabricates no row (§K.6 case 1). */
138
+ function detectLegacyLogin(harnessId) {
139
+ if (harnessId === "claude") {
140
+ const dir = defaultNativeClaudeConfigDir();
141
+ const identity = claudeAccountIdentity(dir);
142
+ // macOS keeps the token in the Keychain (identity in .claude.json);
143
+ // Linux/Windows keep .credentials.json in the dir. Either proves a login.
144
+ if (identity === null && !existsSync(join(dir, ".credentials.json")))
145
+ return null;
146
+ return { locator: dir, displayName: identity?.email ?? null };
147
+ }
148
+ const home = defaultNativeCodexHome();
149
+ try {
150
+ const parsed = JSON.parse(readFileSync(join(home, "auth.json"), "utf8"));
151
+ // Only a subscription login migrates; an api-key auth.json can not exist
152
+ // in the native store (ensureCodexApiAuth refuses to write it there).
153
+ if (parsed.auth_mode !== "chatgpt")
154
+ return null;
155
+ }
156
+ catch {
157
+ return null;
158
+ }
159
+ return { locator: home, displayName: codexAccountIdentity(home)?.email ?? null };
160
+ }
161
+ function sameLocator(a, b) {
162
+ if (!a)
163
+ return false;
164
+ try {
165
+ return normalizeThroughExistingAncestor(a) === normalizeThroughExistingAncestor(b);
166
+ }
167
+ catch {
168
+ return a === b;
169
+ }
170
+ }
171
+ /** The harness's exact Claudexor-owned default native store dir, or null for
172
+ * harnesses without one (D-U3: cursor has no default store; agy is named-only). */
173
+ export function defaultNativeStoreLocator(harnessId) {
174
+ if (harnessId === "claude")
175
+ return defaultNativeClaudeConfigDir();
176
+ if (harnessId === "codex")
177
+ return defaultNativeCodexHome();
178
+ return null;
179
+ }
180
+ /**
181
+ * The ONE structural "this row IS the harness's default native store" rule,
182
+ * shared by the delete fence and the `native_credentials_enabled` downgrade
183
+ * mirror: true when the locator resolves to the harness's exact
184
+ * defaultNative*Dir() — the same rule the startup migration matches rows by.
185
+ * Bootstrap rows (ensureBootstrapProfile) sit at this locator BEFORE any
186
+ * migration record exists, so record presence must never be the only key.
187
+ */
188
+ export function locatorIsDefaultNativeStore(harnessId, locator) {
189
+ const native = defaultNativeStoreLocator(harnessId);
190
+ return native !== null && sameLocator(locator, native);
191
+ }
192
+ /** Reserved machine id (§L.3): `<harness>-default`, deterministic `-2`, `-3`…
193
+ * suffix on collision. CROSS-HARNESS unique — the wire's scalar
194
+ * `credentialProfileId` must never match rows of two harnesses. */
195
+ function reserveRowId(registry, harnessId) {
196
+ const base = `${harnessId}-default`;
197
+ let candidate = base;
198
+ for (let i = 2; registry.some((p) => p.profile_id === candidate); i += 1) {
199
+ candidate = `${base}-${i}`;
200
+ }
201
+ return candidate;
202
+ }
203
+ function backupConfig(harnessId) {
204
+ try {
205
+ const source = join(userConfigDir(), "config.yaml");
206
+ if (!existsSync(source))
207
+ return null;
208
+ const dir = join(userConfigDir(), "migration", `backup-${harnessId}-${Date.now()}`);
209
+ mkdirSync(dir, { recursive: true, mode: 0o700 });
210
+ copyFileSync(source, join(dir, "config.yaml"));
211
+ return dir;
212
+ }
213
+ catch {
214
+ // Fail-soft: the backup is the manual-recovery half of the downgrade
215
+ // path; the supported rollback command reverses phases surgically and
216
+ // does not depend on it. A null backup_ref is disclosed in the record.
217
+ return null;
218
+ }
219
+ }
220
+ function laneRootsOf(stores) {
221
+ const roots = new Set([noProjectRepoRoot()]);
222
+ try {
223
+ for (const thread of stores.threads.listThreads()) {
224
+ if (thread.repo?.root)
225
+ roots.add(thread.repo.root);
226
+ }
227
+ }
228
+ catch {
229
+ /* listing failure bounds the rename to the no-project root; sessions are
230
+ * already migrated by the journal step, so an unrenamed lane degrades to
231
+ * a fresh session with the standard continuity disclosure */
232
+ }
233
+ return [...roots];
234
+ }
235
+ /**
236
+ * Run (or crash-resume) the unified-accounts migration for every harness with
237
+ * a detected legacy login. Idempotent: the second and later starts find
238
+ * completed records (or nothing to detect) and mutate nothing (§K.6 case 6).
239
+ */
240
+ export function runAccountsUnifiedMigration(stores) {
241
+ const receipts = [];
242
+ for (const harnessId of ACCOUNTS_MIGRATION_HARNESSES) {
243
+ // STRICT read: a corrupt phase file must never be treated as "no record"
244
+ // here — a fresh reservation would overwrite the crash evidence. The
245
+ // throw is caught by runStartupAccountsMigration; the run gate keeps the
246
+ // affected harnesses refusing typed until the file is fixed or removed.
247
+ const file = readAccountsMigrationFileStrict();
248
+ let record = file[harnessId];
249
+ const resumed = record !== undefined && record.phase !== "completed";
250
+ if (record?.phase === "completed")
251
+ continue;
252
+ if (record?.phase === "rolling_back") {
253
+ // An interrupted ROLLBACK never resumes forward — re-running the
254
+ // rollback command finishes it; the run gate names that instruction.
255
+ stores.log?.(`unified-accounts migration: ${harnessId} has an interrupted rollback; ` +
256
+ `re-run \`claudexor accounts-migration-rollback\` to finish it`);
257
+ continue;
258
+ }
259
+ if (record?.phase === "reserved" && detectLegacyLogin(harnessId) === null) {
260
+ // Crash-resume of a RESERVED record whose login has since vanished:
261
+ // the reserved phase mutated nothing but the record itself, and
262
+ // registering a row now would fabricate a cold account for a login
263
+ // that no longer exists. Skip (and clear the record) — logged.
264
+ const remaining = { ...file };
265
+ delete remaining[harnessId];
266
+ writeAccountsMigrationFile(remaining);
267
+ stores.log?.(`unified-accounts migration skipped: ${harnessId} reserved row "${record.row_id}" ` +
268
+ `has no detectable legacy login anymore; the reservation was cleared`);
269
+ continue;
270
+ }
271
+ if (!record) {
272
+ const detected = detectLegacyLogin(harnessId);
273
+ if (!detected)
274
+ continue;
275
+ const registry = loadConfig(noProjectRepoRoot()).global.credential_profiles;
276
+ const sameLocatorRow = registry.find((p) => p.harness_id === harnessId && sameLocator(p.isolation_locator, detected.locator));
277
+ // Phase RESERVED: id + backup persisted BEFORE any mutation, so a crash
278
+ // can never re-reserve a different id (§K.3). A row already registered
279
+ // at this exact locator is reused as-is (D-U2 no-op reuse).
280
+ record = {
281
+ phase: "reserved",
282
+ row_id: sameLocatorRow?.profile_id ?? reserveRowId(registry, harnessId),
283
+ legacy_aliases: [null],
284
+ locator: detected.locator,
285
+ backup_ref: backupConfig(harnessId),
286
+ };
287
+ writeAccountsMigrationFile({ ...file, [harnessId]: record });
288
+ }
289
+ const rowId = record.row_id;
290
+ if (record.phase === "reserved") {
291
+ registerMigratedRow(harnessId, record);
292
+ record = { ...record, phase: "registry_written" };
293
+ writeAccountsMigrationFile({ ...readAccountsMigrationFileStrict(), [harnessId]: record });
294
+ }
295
+ let continuity = { sessions: 0, checkpoints: 0, skippedPartitions: [] };
296
+ let lanes = 0;
297
+ if (record.phase === "registry_written") {
298
+ continuity = stores.threads.migrateNullProfileContinuity(harnessId, rowId);
299
+ for (const root of laneRootsOf(stores))
300
+ lanes += migrateDefaultLanes(root, harnessId, rowId);
301
+ record = { ...record, phase: "continuity_migrated" };
302
+ writeAccountsMigrationFile({ ...readAccountsMigrationFileStrict(), [harnessId]: record });
303
+ }
304
+ if (record.phase === "continuity_migrated") {
305
+ // No replay alias (§K.3): the legacy null subject is retired here; the
306
+ // new row's subject refreshes fresh on the next quota cycle.
307
+ stores.quota.removeSubject(harnessId, null);
308
+ record = { ...record, phase: "completed" };
309
+ writeAccountsMigrationFile({ ...readAccountsMigrationFileStrict(), [harnessId]: record });
310
+ }
311
+ const receipt = {
312
+ harness_id: harnessId,
313
+ row_id: rowId,
314
+ outcome: resumed ? "resumed" : "migrated",
315
+ sessions: continuity.sessions,
316
+ checkpoints: continuity.checkpoints,
317
+ lanes,
318
+ skipped_partitions: continuity.skippedPartitions,
319
+ };
320
+ receipts.push(receipt);
321
+ stores.log?.(`unified-accounts migration ${receipt.outcome}: ${harnessId} → row "${rowId}" ` +
322
+ `(${receipt.sessions} sessions, ${receipt.checkpoints} checkpoints, ${receipt.lanes} lane homes` +
323
+ (receipt.skipped_partitions.length > 0
324
+ ? `; SKIPPED quarantined partition(s) ${receipt.skipped_partitions.join(", ")} — their sessions migrate after recovery`
325
+ : "") +
326
+ `)`);
327
+ }
328
+ return receipts;
329
+ }
330
+ /** Phase REGISTRY_WRITTEN's one locked config write. Idempotent: an existing
331
+ * (harness, row_id) row — or a row already holding the locator — is adopted
332
+ * untouched; `native_credentials_enabled=false` maps to `enabled=false`
333
+ * (the deprecated key stays in config as the downgrade-window mirror). */
334
+ function registerMigratedRow(harnessId, record) {
335
+ const detected = detectLegacyLogin(harnessId);
336
+ updateGlobalConfig((config) => {
337
+ const existing = config.credential_profiles.find((p) => (p.harness_id === harnessId && p.profile_id === record.row_id) ||
338
+ (p.harness_id === harnessId && sameLocator(p.isolation_locator, record.locator)));
339
+ if (existing)
340
+ return config;
341
+ const nativeEnabled = config.harnesses[harnessId]?.native_credentials_enabled !== false;
342
+ const entry = {
343
+ profile_id: record.row_id,
344
+ harness_id: harnessId,
345
+ display_name: detected?.displayName ?? `${harnessId} default login`,
346
+ credential_kind: "config_dir_login",
347
+ isolation_locator: record.locator,
348
+ secret_ref: null,
349
+ enabled: nativeEnabled,
350
+ created_at: nowIso(),
351
+ };
352
+ return { ...config, credential_profiles: [...config.credential_profiles, entry] };
353
+ });
354
+ }
355
+ /**
356
+ * The supported downgrade path (run BEFORE installing 3.5.0): surgically
357
+ * reverses every migration phase — sessions/checkpoints/lane homes return to
358
+ * the null engine-default keys, the auto-registered row leaves the registry
359
+ * (its `enabled` state returns to the `native_credentials_enabled` mirror),
360
+ * the row's quota subject retires, and the harness's migration record is
361
+ * removed so a later new-engine start re-migrates cleanly. The pre-migration
362
+ * config backup (`backup_ref`) stays on disk as manual-recovery evidence.
363
+ * Credential bytes were never moved, so nothing is copied back.
364
+ */
365
+ export function rollbackAccountsUnifiedMigration(stores, harnessId) {
366
+ const receipts = [];
367
+ const targets = harnessId ? [harnessId] : Object.keys(readAccountsMigrationFileStrict());
368
+ for (const target of targets) {
369
+ const file = readAccountsMigrationFileStrict();
370
+ const record = file[target];
371
+ if (!record)
372
+ continue;
373
+ const rowId = record.row_id;
374
+ // Phase ROLLING_BACK persists BEFORE the first reverse step: a crash
375
+ // mid-rollback must not leave a record whose phase claims a completed
376
+ // forward migration while continuity is half-reversed. The startup
377
+ // migration never resumes a rolling_back record forward; every reverse
378
+ // step below is idempotent, so re-running this command finishes the job.
379
+ if (record.phase !== "rolling_back") {
380
+ writeAccountsMigrationFile({ ...file, [target]: { ...record, phase: "rolling_back" } });
381
+ }
382
+ const continuity = stores.threads.rollbackProfileContinuity(target, rowId);
383
+ let lanes = 0;
384
+ for (const root of laneRootsOf(stores))
385
+ lanes += rollbackProfileLanes(root, target, rowId);
386
+ stores.quota.removeSubject(target, rowId);
387
+ updateGlobalConfig((config) => {
388
+ const row = config.credential_profiles.find((p) => p.harness_id === target && p.profile_id === rowId);
389
+ // Only the row this migration created leaves the registry — matched by
390
+ // the record's exact locator, so a later hand-registered same-id row
391
+ // pointing elsewhere is never silently deleted.
392
+ if (!row || !sameLocator(row.isolation_locator, record.locator))
393
+ return config;
394
+ return {
395
+ ...config,
396
+ harnesses: {
397
+ ...config.harnesses,
398
+ [target]: {
399
+ ...config.harnesses[target],
400
+ native_credentials_enabled: row.enabled,
401
+ },
402
+ },
403
+ credential_profiles: config.credential_profiles.filter((p) => p !== row),
404
+ };
405
+ });
406
+ const remaining = { ...readAccountsMigrationFileStrict() };
407
+ delete remaining[target];
408
+ writeAccountsMigrationFile(remaining);
409
+ receipts.push({
410
+ harness_id: target,
411
+ row_id: rowId,
412
+ sessions: continuity.sessions,
413
+ checkpoints: continuity.checkpoints,
414
+ lanes,
415
+ skipped_partitions: continuity.skippedPartitions,
416
+ });
417
+ stores.log?.(`unified-accounts migration ROLLED BACK: ${target} row "${rowId}" ` +
418
+ `(${continuity.sessions} sessions, ${continuity.checkpoints} checkpoints, ${lanes} lane homes returned to the engine-default keys)`);
419
+ }
420
+ return receipts;
421
+ }
422
+ //# sourceMappingURL=accounts-unified-migration.js.map