sidekick-shared 0.27.4 → 0.27.6

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 (64) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/README.md +92 -45
  3. package/dist/accountHealth.d.ts +14 -0
  4. package/dist/accountHealth.js +61 -14
  5. package/dist/accountLaunch.d.ts +6 -0
  6. package/dist/accountLaunch.js +61 -4
  7. package/dist/accountLease.d.ts +5 -0
  8. package/dist/accountLease.js +98 -0
  9. package/dist/accountManager.d.ts +7 -0
  10. package/dist/accountManager.js +226 -89
  11. package/dist/accountSync.d.ts +1 -5
  12. package/dist/accountSync.js +22 -6
  13. package/dist/accountSyncTypes.d.ts +9 -0
  14. package/dist/accounts.d.ts +75 -18
  15. package/dist/accounts.js +712 -189
  16. package/dist/claudeCredentialDoctor.d.ts +50 -0
  17. package/dist/claudeCredentialDoctor.js +277 -0
  18. package/dist/claudeCredentialLedger.d.ts +15 -0
  19. package/dist/claudeCredentialLedger.js +130 -0
  20. package/dist/claudeCredentials.d.ts +20 -3
  21. package/dist/claudeCredentials.js +50 -3
  22. package/dist/claudeLiveGuard.d.ts +33 -0
  23. package/dist/claudeLiveGuard.js +46 -0
  24. package/dist/claudeProfiles.d.ts +14 -0
  25. package/dist/claudeProfiles.js +22 -0
  26. package/dist/claudeRefreshLock.d.ts +15 -0
  27. package/dist/claudeRefreshLock.js +145 -0
  28. package/dist/codexAuth.d.ts +4 -0
  29. package/dist/codexAuth.js +18 -0
  30. package/dist/codexCredentialDoctor.d.ts +33 -0
  31. package/dist/codexCredentialDoctor.js +302 -0
  32. package/dist/codexCredentialStore.d.ts +29 -0
  33. package/dist/codexCredentialStore.js +213 -0
  34. package/dist/codexKeepAlive.d.ts +2 -0
  35. package/dist/codexKeepAlive.js +116 -0
  36. package/dist/codexPaths.d.ts +8 -2
  37. package/dist/codexPaths.js +51 -7
  38. package/dist/codexProfiles.d.ts +37 -31
  39. package/dist/codexProfiles.js +842 -671
  40. package/dist/credentialIO.d.ts +67 -7
  41. package/dist/credentialIO.js +211 -47
  42. package/dist/ensureDefaultAccounts.js +1 -8
  43. package/dist/index.d.ts +17 -4
  44. package/dist/index.js +36 -11
  45. package/dist/keepAlive.d.ts +2 -1
  46. package/dist/keepAlive.js +116 -29
  47. package/dist/liveCredentialGuard.d.ts +28 -0
  48. package/dist/liveCredentialGuard.js +36 -0
  49. package/dist/node.d.ts +3 -0
  50. package/dist/node.js +4 -2
  51. package/dist/processDetection.d.ts +10 -3
  52. package/dist/processDetection.js +44 -11
  53. package/dist/promptHistory.d.ts +66 -0
  54. package/dist/promptHistory.js +37 -18
  55. package/dist/schemas/accountManager.d.ts +22 -0
  56. package/dist/schemas/accountManager.js +4 -0
  57. package/dist/schemas/assistantTurn.d.ts +2 -2
  58. package/dist/sessionPromptHistory.d.ts +120 -0
  59. package/dist/sessionPromptHistory.js +278 -0
  60. package/dist/sessionPromptSignals.d.ts +56 -0
  61. package/dist/sessionPromptSignals.js +355 -0
  62. package/dist/terminalSync.d.ts +12 -2
  63. package/dist/terminalSync.js +27 -2
  64. package/package.json +1 -1
@@ -37,6 +37,7 @@ exports.syncLiveAccountStateSync = syncLiveAccountStateSync;
37
37
  exports.syncLiveAccountState = syncLiveAccountState;
38
38
  exports._resetSyncFingerprints = _resetSyncFingerprints;
39
39
  exports.cleanupAbandonedLogins = cleanupAbandonedLogins;
40
+ exports.cleanupAbandonedLoginsAsync = cleanupAbandonedLoginsAsync;
40
41
  /**
41
42
  * Continuous reconciliation between the live provider logins and sidekick's
42
43
  * saved profiles. One engine replaces the old bootstrap/reconcile/backup
@@ -50,6 +51,7 @@ const accounts_1 = require("./accounts");
50
51
  const claudeProfiles_1 = require("./claudeProfiles");
51
52
  const codexProfiles_1 = require("./codexProfiles");
52
53
  const codexPaths_1 = require("./codexPaths");
54
+ const processDetection_1 = require("./processDetection");
53
55
  const accountSyncTypes_1 = require("./accountSyncTypes");
54
56
  const DEFAULT_KEYCHAIN_FOLD_INTERVAL_MS = 10 * 60 * 1000;
55
57
  const fingerprints = {
@@ -89,7 +91,7 @@ function sourceForReason(reason) {
89
91
  return reason === 'pre-switch' ? 'switch' : 'live-sync';
90
92
  }
91
93
  function shouldSync(provider, fingerprint, options) {
92
- if (!options.onlyIfChanged)
94
+ if (!options.onlyIfChanged || (provider === 'codex' && (0, codexProfiles_1.hasPendingCodexRepair)()))
93
95
  return true;
94
96
  const state = fingerprints[provider];
95
97
  if (state.fingerprint !== fingerprint)
@@ -101,7 +103,7 @@ function shouldSync(provider, fingerprint, options) {
101
103
  return false;
102
104
  }
103
105
  /** Sync one provider under its swap lock; never throws. */
104
- function syncProvider(provider, reason, options) {
106
+ function syncProvider(provider, reason, options, scan) {
105
107
  const fingerprint = provider === 'codex' ? codexFingerprint() : claudeFingerprint();
106
108
  if (!shouldSync(provider, fingerprint, options)) {
107
109
  return { ...(0, accountSyncTypes_1.emptyProviderSyncReport)(), skipped: 'unchanged' };
@@ -109,9 +111,11 @@ function syncProvider(provider, reason, options) {
109
111
  const source = sourceForReason(reason);
110
112
  let report;
111
113
  try {
114
+ if (provider === 'codex')
115
+ scan ??= (0, processDetection_1.inspectRunningAccountConsumersSync)('codex');
112
116
  report =
113
117
  provider === 'codex'
114
- ? (0, codexProfiles_1.withCodexAuthSwapLock)(() => (0, codexProfiles_1.syncCodexLiveStateUnlocked)(source))
118
+ ? (0, codexProfiles_1.withCodexAuthSwapLock)(() => (0, codexProfiles_1.syncCodexLiveStateUnlocked)(source, { scan }))
115
119
  : (0, accounts_1.withClaudeAuthSwapLock)(() => (0, accounts_1.syncClaudeLiveStateUnlocked)(source));
116
120
  }
117
121
  catch (err) {
@@ -130,7 +134,7 @@ function syncProvider(provider, reason, options) {
130
134
  * Safe to call from watchers: re-entrant calls return an empty report, and
131
135
  * `onlyIfChanged` short-circuits when the live files are unchanged.
132
136
  */
133
- function syncLiveAccountStateSync(options = {}) {
137
+ function syncLiveAccountStateCore(options = {}, scan) {
134
138
  const reason = options.reason ?? 'manual';
135
139
  const now = options.now ?? Date.now();
136
140
  const resolved = {
@@ -155,16 +159,22 @@ function syncLiveAccountStateSync(options = {}) {
155
159
  if (providers.includes('claude-code'))
156
160
  report.claude = syncProvider('claude-code', reason, resolved);
157
161
  if (providers.includes('codex'))
158
- report.codex = syncProvider('codex', reason, resolved);
162
+ report.codex = syncProvider('codex', reason, resolved, scan);
159
163
  }
160
164
  finally {
161
165
  syncInProgress = false;
162
166
  }
163
167
  return report;
164
168
  }
169
+ function syncLiveAccountStateSync(options = {}) {
170
+ return syncLiveAccountStateCore(options);
171
+ }
165
172
  /** Promise-returning twin of {@link syncLiveAccountStateSync} for async hosts. */
166
173
  async function syncLiveAccountState(options = {}) {
167
- return syncLiveAccountStateSync(options);
174
+ const scan = !options.providers || options.providers.includes('codex')
175
+ ? await (0, processDetection_1.inspectRunningAccountConsumers)('codex')
176
+ : undefined;
177
+ return syncLiveAccountStateCore(options, scan);
168
178
  }
169
179
  /** Forget the change fingerprints (tests, or after an external reset). */
170
180
  function _resetSyncFingerprints() {
@@ -178,3 +188,9 @@ function cleanupAbandonedLogins(olderThanMs = 6 * 60 * 60 * 1000, now = Date.now
178
188
  codex: (0, codexProfiles_1.cleanupAbandonedCodexLogins)(olderThanMs, now),
179
189
  };
180
190
  }
191
+ async function cleanupAbandonedLoginsAsync(olderThanMs = 24 * 60 * 60 * 1000, now = Date.now()) {
192
+ return {
193
+ claude: (0, accounts_1.cleanupAbandonedClaudeLogins)(olderThanMs, now),
194
+ codex: await (0, codexProfiles_1.cleanupAbandonedCodexLoginsAsync)(olderThanMs, now),
195
+ };
196
+ }
@@ -11,6 +11,15 @@ export interface ProviderSyncReport {
11
11
  repointed?: string;
12
12
  /** Duplicate profiles merged away (ids of the removed losers). */
13
13
  merged?: string[];
14
+ /**
15
+ * The live account's profile home held a CLI-usable copy of its login; it
16
+ * was removed (the account that is live keeps exactly one copy: live).
17
+ */
18
+ demoted?: string;
19
+ /** That copy was strictly newer than live, so it replaced the dying live login first. */
20
+ repaired?: string;
21
+ /** A switch that crashed mid-way was finished or rolled back from its journal. */
22
+ journal?: 'finished' | 'rolled-back' | 'cleared';
14
23
  /** Why nothing was synced (`logged-out`, `keyring`, `no-identity`, `unchanged`). */
15
24
  skipped?: string;
16
25
  warnings: string[];
@@ -4,10 +4,17 @@
4
4
  * Handles saving, listing, switching, syncing, and removing Claude accounts.
5
5
  * Both the VS Code extension and CLI consume this module.
6
6
  *
7
- * Model: the live home (`CLAUDE_CONFIG_DIR` or `~/.claude`) is the source of
8
- * truth for whichever account is logged in right now; every saved profile is a
9
- * backup that the sync core keeps fresh, because Claude Code rotates the
10
- * refresh token on every refresh and a stale snapshot is a dead login.
7
+ * Model: Claude Code rotates the refresh token on every refresh, so every
8
+ * refresh-token chain may have exactly one copy a `claude` process can use.
9
+ *
10
+ * - The live home (`CLAUDE_CONFIG_DIR` or `~/.claude`) is the only CLI-usable
11
+ * copy of the account that is logged in right now. Its profile home holds
12
+ * identity and health metadata but never a credential; Sidekick mirrors the
13
+ * live credential into a private backup Claude Code never reads.
14
+ * - An inactive account's profile home holds its single CLI-usable copy (the
15
+ * Claude login only; MCP tokens follow the live store).
16
+ * - Writes into the live store go through one guarded installer that refuses
17
+ * downgrades, and a switch saves the outgoing login before replacing it.
11
18
  */
12
19
  import { type ResolvedActiveAccount } from './accountRegistry';
13
20
  import { type AccountHealthSource } from './accountHealth';
@@ -39,11 +46,21 @@ export interface AccountManagerResult {
39
46
  /** Store a profile's identity (`oauthAccount`) in its home and the flat backup. */
40
47
  export declare function storeClaudeProfileIdentity(uuid: string, oauthAccount: unknown): void;
41
48
  /**
42
- * The single chokepoint for storing a Claude credential in a profile: profile
43
- * home store (suffixed Keychain item on macOS, file elsewhere), the flat
44
- * backup, and the secret-free health sidecar.
49
+ * The single chokepoint for storing a Claude credential in a profile. Always
50
+ * writes the private backup and the secret-free health sidecar. Writes the
51
+ * profile-home store (suffixed Keychain item on macOS, file elsewhere) only
52
+ * when the account is NOT the live login: the live store is that account's one
53
+ * CLI-usable copy, and a second one would fight it over the refresh token.
54
+ * Caller holds the swap lock.
45
55
  */
56
+ export declare function storeClaudeProfileCredentialsUnlocked(uuid: string, credentials: unknown, source: AccountHealthSource): void;
57
+ /** {@link storeClaudeProfileCredentialsUnlocked} under the swap lock. */
46
58
  export declare function storeClaudeProfileCredentials(uuid: string, credentials: unknown, source: AccountHealthSource): void;
59
+ /**
60
+ * The saved credential and identity of a profile: the fresher of its
61
+ * profile-home store and its private backup. Read-only — it never
62
+ * re-materialises a backup into a CLI-visible place.
63
+ */
47
64
  export declare function readStoredClaudeAccount(uuid: string): {
48
65
  credentials: unknown;
49
66
  oauthAccount: unknown;
@@ -54,37 +71,77 @@ export declare function readActiveClaudeAccount(): ActiveAccountInfo | null;
54
71
  export declare function addCurrentAccount(label?: string): AccountManagerResult;
55
72
  /**
56
73
  * Serializes live Claude credential swaps across processes so two concurrent
57
- * switchers cannot interleave their backup/write/rollback sequences. It cannot
58
- * exclude Claude Code's own writes to the live home — only our switchers. Lock
59
- * ordering: this lock first, the registry lock (inside setActiveSavedAccount)
60
- * inside it — never the reverse. Never spawn a child process while holding it.
74
+ * switchers cannot interleave their backup/write/rollback sequences. Claude
75
+ * Code's own refreshes are kept out separately by borrowing its refresh lock
76
+ * (claudeRefreshLock.ts). Lock ordering: this lock first, then the refresh
77
+ * locks, the registry lock (inside setActiveSavedAccount), and the ledger
78
+ * lock — never the reverse. Never spawn a `claude` process while holding it.
61
79
  */
62
80
  export declare function withClaudeAuthSwapLock<T>(operation: () => T): T;
63
81
  /**
64
- * Fold the live login into the saved profiles: register an unknown login
65
- * (label = email), refresh the matching profile's backup when the live
66
- * credential is newer, and re-point the active pointer at it. Caller holds the
67
- * swap lock. Never throws; problems land in `warnings`.
82
+ * Reconcile the live login with the saved profiles: finish an interrupted
83
+ * switch, register an unknown login (label = email), refresh the matching
84
+ * profile's backup when the live credential is newer, remove any CLI-usable
85
+ * copy of the live account from its profile home, and re-point the active
86
+ * pointer. Caller holds the swap lock. Never throws; problems land in
87
+ * `warnings`.
68
88
  */
69
89
  export declare function syncClaudeLiveStateUnlocked(source?: AccountHealthSource): ProviderSyncReport;
70
90
  /** {@link syncClaudeLiveStateUnlocked} under the swap lock. */
71
91
  export declare function syncClaudeLiveState(source?: AccountHealthSource): ProviderSyncReport;
72
92
  /**
73
- * Remove isolated login homes that never authenticated (the user closed the
74
- * browser, the login timed out) once they are older than `olderThanMs`.
93
+ * Remove isolated login homes that never finished: ones that never
94
+ * authenticated (the user closed the browser, the login timed out) once they
95
+ * are older than `olderThanMs`, and ones that authenticated but were never
96
+ * finalized (the host quit) after a day. Their Keychain item goes with them.
75
97
  * Returns the removed login ids.
76
98
  */
77
99
  export declare function cleanupAbandonedClaudeLogins(olderThanMs?: number, now?: number): string[];
100
+ /** {@link beginClaudeKeepAlive} without taking the lease (dry runs). */
101
+ export declare function getClaudeKeepAliveBlocker(accountId: string): string | null;
102
+ /**
103
+ * Check a Claude profile for keep-alive and take its lease, both under the
104
+ * swap lock, so no switch can make it live (or copy it into live) while the
105
+ * CLI refreshes it. Pair with {@link finishClaudeKeepAlive}.
106
+ */
107
+ export declare function beginClaudeKeepAlive(accountId: string, leaseMs: number): {
108
+ ok: true;
109
+ } | {
110
+ ok: false;
111
+ reason: string;
112
+ };
113
+ /** Record what the CLI refreshed (backup + sidecar) and release the lease. */
114
+ export declare function finishClaudeKeepAlive(accountId: string): void;
115
+ /**
116
+ * Give an inactive profile a CLI-usable copy from its backup when its profile
117
+ * home has none (it was live when it was last seen, or a switch could not
118
+ * write it). User-initiated launches only; keep-alive never does this.
119
+ */
120
+ export declare function restoreClaudeProfileFromBackup(accountId: string): 'restored' | 'present' | 'live' | 'none';
78
121
  export declare function switchToAccount(uuid: string, options?: SwitchAccountOptions): SwitchAccountResult;
79
122
  /** {@link switchToAccount} with the process probe off the event loop. */
80
123
  export declare function switchToAccountAsync(uuid: string, options?: SwitchAccountOptions): Promise<SwitchAccountResult>;
81
124
  /**
82
125
  * Install a saved profile's credentials into the live home without changing
83
126
  * which account is active: used right after a re-login of the live account,
84
- * whose freshly minted token must replace the (possibly dead) live one.
127
+ * whose freshly minted token must replace the (possibly dead) live one. The
128
+ * guard only lets a strictly fresher credential of the same account through;
129
+ * when live is already at least as new, nothing is written.
85
130
  */
86
131
  export declare function applyClaudeProfileToLiveHome(uuid: string): AccountManagerResult;
132
+ /**
133
+ * @deprecated The home `claude` should use for the active account. The live
134
+ * account's profile home holds no credential, so this is the live home for the
135
+ * live account; kept for API compatibility.
136
+ */
87
137
  export declare function resolveActiveClaudeHome(): string;
138
+ /**
139
+ * @deprecated Re-install the active profile's credential into the live home.
140
+ * It never follows the registry pointer across accounts (after a native
141
+ * `claude /login` the pointer can be stale): when the live login is another
142
+ * account it refuses — use {@link switchToAccount}. For the same account it is
143
+ * a guarded re-login that writes only a strictly fresher credential.
144
+ */
88
145
  export declare function applyActiveClaudeToLiveHome(): AccountManagerResult;
89
146
  export declare function reconcileClaudeAuthState(): void;
90
147
  export declare function removeAccount(uuid: string): AccountManagerResult;