sidekick-shared 0.27.5 → 0.28.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 (314) hide show
  1. package/CHANGELOG.md +58 -0
  2. package/README.md +93 -52
  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/browser.d.ts +13 -2
  17. package/dist/browser.js +30 -2
  18. package/dist/claudeCredentialDoctor.d.ts +50 -0
  19. package/dist/claudeCredentialDoctor.js +277 -0
  20. package/dist/claudeCredentialLedger.d.ts +15 -0
  21. package/dist/claudeCredentialLedger.js +130 -0
  22. package/dist/claudeCredentials.d.ts +20 -3
  23. package/dist/claudeCredentials.js +50 -3
  24. package/dist/claudeLiveGuard.d.ts +33 -0
  25. package/dist/claudeLiveGuard.js +46 -0
  26. package/dist/claudePaths.d.ts +11 -0
  27. package/dist/claudePaths.js +61 -0
  28. package/dist/claudeProfiles.d.ts +15 -8
  29. package/dist/claudeProfiles.js +30 -23
  30. package/dist/claudeRefreshLock.d.ts +15 -0
  31. package/dist/claudeRefreshLock.js +145 -0
  32. package/dist/codexAuth.d.ts +4 -0
  33. package/dist/codexAuth.js +18 -0
  34. package/dist/codexCredentialDoctor.d.ts +33 -0
  35. package/dist/codexCredentialDoctor.js +302 -0
  36. package/dist/codexCredentialStore.d.ts +29 -0
  37. package/dist/codexCredentialStore.js +213 -0
  38. package/dist/codexKeepAlive.d.ts +2 -0
  39. package/dist/codexKeepAlive.js +116 -0
  40. package/dist/codexPaths.d.ts +30 -2
  41. package/dist/codexPaths.js +118 -7
  42. package/dist/codexProfiles.d.ts +38 -35
  43. package/dist/codexProfiles.js +848 -715
  44. package/dist/credentialIO.d.ts +67 -7
  45. package/dist/credentialIO.js +211 -47
  46. package/dist/doctor.js +2 -2
  47. package/dist/ensureDefaultAccounts.js +1 -8
  48. package/dist/esm/accountChangeSignal.js +15 -0
  49. package/dist/esm/accountChanges.js +152 -0
  50. package/dist/esm/accountHealth.js +288 -0
  51. package/dist/esm/accountLaunch.js +145 -0
  52. package/dist/esm/accountLease.js +60 -0
  53. package/dist/esm/accountManager.js +606 -0
  54. package/dist/esm/accountRegistry.js +214 -0
  55. package/dist/esm/accountStatus.js +51 -0
  56. package/dist/esm/accountSwitch.js +74 -0
  57. package/dist/esm/accountSync.js +156 -0
  58. package/dist/esm/accountSyncTypes.js +4 -0
  59. package/dist/esm/accounts.js +1291 -0
  60. package/dist/esm/aggregation/EventAggregator.js +2034 -0
  61. package/dist/esm/aggregation/FrequencyTracker.js +69 -0
  62. package/dist/esm/aggregation/HeatmapTracker.js +89 -0
  63. package/dist/esm/aggregation/PatternExtractor.js +168 -0
  64. package/dist/esm/aggregation/costProvenance.js +31 -0
  65. package/dist/esm/aggregation/snapshot.js +161 -0
  66. package/dist/esm/aggregation/types.js +5 -0
  67. package/dist/esm/analytics/codeImpact.js +18 -0
  68. package/dist/esm/analytics/compactionLedger.js +37 -0
  69. package/dist/esm/analytics/qualityScore.js +112 -0
  70. package/dist/esm/autoSwitch.js +185 -0
  71. package/dist/esm/browser.js +34 -0
  72. package/dist/esm/claudeCredentialDoctor.js +239 -0
  73. package/dist/esm/claudeCredentialLedger.js +91 -0
  74. package/dist/esm/claudeCredentials.js +111 -0
  75. package/dist/esm/claudeLiveGuard.js +43 -0
  76. package/dist/esm/claudePaths.js +22 -0
  77. package/dist/esm/claudeProfiles.js +142 -0
  78. package/dist/esm/claudeRefreshLock.js +106 -0
  79. package/dist/esm/codexAuth.js +101 -0
  80. package/dist/esm/codexCredentialDoctor.js +265 -0
  81. package/dist/esm/codexCredentialStore.js +164 -0
  82. package/dist/esm/codexKeepAlive.js +112 -0
  83. package/dist/esm/codexPaths.js +172 -0
  84. package/dist/esm/codexProfiles.js +1251 -0
  85. package/dist/esm/codexQuota.js +564 -0
  86. package/dist/esm/codexQuotaWatcher.js +363 -0
  87. package/dist/esm/context/composer.js +71 -0
  88. package/dist/esm/context/sessionContext.js +546 -0
  89. package/dist/esm/credentialIO.js +283 -0
  90. package/dist/esm/credentials.js +49 -0
  91. package/dist/esm/doctor.js +305 -0
  92. package/dist/esm/ensureDefaultAccounts.js +62 -0
  93. package/dist/esm/errorHistory.js +56 -0
  94. package/dist/esm/extractors/errorTaxonomy.js +43 -0
  95. package/dist/esm/extractors/gatherAssets.js +32 -0
  96. package/dist/esm/extractors/sessionAssets.js +176 -0
  97. package/dist/esm/extractors/sources/claudeAssets.js +158 -0
  98. package/dist/esm/extractors/sources/codexAssets.js +234 -0
  99. package/dist/esm/extractors/tokenUsage.js +41 -0
  100. package/dist/esm/extractors/toolCall.js +142 -0
  101. package/dist/esm/failingTools.js +65 -0
  102. package/dist/esm/fileHead.js +27 -0
  103. package/dist/esm/formatters/eventHighlighter.js +332 -0
  104. package/dist/esm/formatters/noiseClassifier.js +209 -0
  105. package/dist/esm/formatters/sessionDump.js +328 -0
  106. package/dist/esm/formatters/toolSummary.js +226 -0
  107. package/dist/esm/formatting.js +82 -0
  108. package/dist/esm/handoffUrl.js +23 -0
  109. package/dist/esm/historicalStore.js +305 -0
  110. package/dist/esm/humanPrompt.js +88 -0
  111. package/dist/esm/index.js +213 -0
  112. package/dist/esm/keepAlive.js +189 -0
  113. package/dist/esm/liveCredentialGuard.js +33 -0
  114. package/dist/esm/modelAliases.js +51 -0
  115. package/dist/esm/modelCatalog.js +139 -0
  116. package/dist/esm/modelContext.js +280 -0
  117. package/dist/esm/modelInfo.js +832 -0
  118. package/dist/esm/multiProviderQuotaService.js +450 -0
  119. package/dist/esm/node.js +46 -0
  120. package/dist/esm/observedContextWindows.js +165 -0
  121. package/dist/esm/observedSessionCollector.js +939 -0
  122. package/dist/esm/parsers/changelogParser.js +71 -0
  123. package/dist/esm/parsers/codexHistory.js +89 -0
  124. package/dist/esm/parsers/codexParser.js +906 -0
  125. package/dist/esm/parsers/debugLogParser.js +126 -0
  126. package/dist/esm/parsers/jsonl.js +66 -0
  127. package/dist/esm/parsers/mcpToolName.js +28 -0
  128. package/dist/esm/parsers/openCodeParser.js +625 -0
  129. package/dist/esm/parsers/planExtractor.js +335 -0
  130. package/dist/esm/parsers/sessionActivityDetector.js +246 -0
  131. package/dist/esm/parsers/sessionPathResolver.js +713 -0
  132. package/dist/esm/parsers/subagentScanner.js +360 -0
  133. package/dist/esm/parsers/subagentStatsCache.js +45 -0
  134. package/dist/esm/parsers/subagentTraceParser.js +325 -0
  135. package/dist/esm/paths.js +134 -0
  136. package/dist/esm/peakHours.js +194 -0
  137. package/dist/esm/phrases.js +1330 -0
  138. package/dist/esm/pricingCatalog.js +247 -0
  139. package/dist/esm/processDetection.js +200 -0
  140. package/dist/esm/projectMigration.js +59 -0
  141. package/dist/esm/promptHistory.js +1221 -0
  142. package/dist/esm/providerFailure.js +362 -0
  143. package/dist/esm/providerIds.js +6 -0
  144. package/dist/esm/providerQuota.js +1 -0
  145. package/dist/esm/providerServiceStatus.js +145 -0
  146. package/dist/esm/providerServiceStatusTypes.js +1 -0
  147. package/dist/esm/providerStatus.js +117 -0
  148. package/dist/esm/providers/claudeCode.js +575 -0
  149. package/dist/esm/providers/claudeListing.js +121 -0
  150. package/dist/esm/providers/codex.js +1034 -0
  151. package/dist/esm/providers/codexDatabase.js +297 -0
  152. package/dist/esm/providers/codexRollouts.js +215 -0
  153. package/dist/esm/providers/detect.js +185 -0
  154. package/dist/esm/providers/diagnostics.js +83 -0
  155. package/dist/esm/providers/directoryListingCache.js +206 -0
  156. package/dist/esm/providers/factory.js +54 -0
  157. package/dist/esm/providers/openCode.js +2008 -0
  158. package/dist/esm/providers/openCodeDatabase.js +419 -0
  159. package/dist/esm/providers/rolloutWalker.js +252 -0
  160. package/dist/esm/providers/types.js +12 -0
  161. package/dist/esm/quota.js +182 -0
  162. package/dist/esm/quotaHistory.js +364 -0
  163. package/dist/esm/quotaPoller.js +230 -0
  164. package/dist/esm/quotaPresentation.js +97 -0
  165. package/dist/esm/quotaResolver.js +231 -0
  166. package/dist/esm/quotaSnapshots.js +130 -0
  167. package/dist/esm/quotaThresholds.js +85 -0
  168. package/dist/esm/readers/decisions.js +24 -0
  169. package/dist/esm/readers/handoff.js +20 -0
  170. package/dist/esm/readers/helpers.js +27 -0
  171. package/dist/esm/readers/history.js +9 -0
  172. package/dist/esm/readers/notes.js +48 -0
  173. package/dist/esm/readers/plans.js +225 -0
  174. package/dist/esm/readers/tasks.js +19 -0
  175. package/dist/esm/report/htmlHelpers.js +280 -0
  176. package/dist/esm/report/htmlReportGenerator.js +704 -0
  177. package/dist/esm/report/index.js +7 -0
  178. package/dist/esm/report/logo.js +2 -0
  179. package/dist/esm/report/openBrowser.js +19 -0
  180. package/dist/esm/report/sessionReportInputs.js +33 -0
  181. package/dist/esm/report/transcriptParser.js +83 -0
  182. package/dist/esm/report/types.js +4 -0
  183. package/dist/esm/schemas/accountManager.js +204 -0
  184. package/dist/esm/schemas/accountStatus.js +25 -0
  185. package/dist/esm/schemas/assistantTurn.js +74 -0
  186. package/dist/esm/schemas/index.js +25 -0
  187. package/dist/esm/schemas/observedSessionV1.js +77 -0
  188. package/dist/esm/schemas/providerFailure.js +37 -0
  189. package/dist/esm/schemas/quota.js +132 -0
  190. package/dist/esm/schemas/quotaHistory.js +40 -0
  191. package/dist/esm/schemas/sessionEvent.js +216 -0
  192. package/dist/esm/schemas/stateFile.js +75 -0
  193. package/dist/esm/schemas/tokenEstimation.js +7 -0
  194. package/dist/esm/schemas/transcript.js +86 -0
  195. package/dist/esm/schemas/usageNormalization.js +38 -0
  196. package/dist/esm/search/advancedFilter.js +197 -0
  197. package/dist/esm/search/sessionSearch.js +66 -0
  198. package/dist/esm/sessionFingerprint.js +40 -0
  199. package/dist/esm/sessionMonitor.js +198 -0
  200. package/dist/esm/sessionPreviews.js +522 -0
  201. package/dist/esm/sessionPromptHistory.js +254 -0
  202. package/dist/esm/sessionPromptSignals.js +364 -0
  203. package/dist/esm/sessionStats.js +160 -0
  204. package/dist/esm/sessionTokenTotals.js +98 -0
  205. package/dist/esm/sessionTranscripts.js +49 -0
  206. package/dist/esm/stateFile.js +119 -0
  207. package/dist/esm/statusline/BurnRateCalculator.js +40 -0
  208. package/dist/esm/statusline/claudeStatuslinePayload.js +148 -0
  209. package/dist/esm/statusline/formatter.js +122 -0
  210. package/dist/esm/statusline/index.js +8 -0
  211. package/dist/esm/terminalSync.js +95 -0
  212. package/dist/esm/testing/credentialGuard.js +197 -0
  213. package/dist/esm/testing/index.js +12 -0
  214. package/dist/esm/testing/sessionLogFixtures.js +128 -0
  215. package/dist/esm/tokenEstimation.js +67 -0
  216. package/dist/esm/tokenSummary.js +113 -0
  217. package/dist/esm/toolCategory.js +82 -0
  218. package/dist/esm/transcript.js +257 -0
  219. package/dist/esm/turns/assistantTurn.js +373 -0
  220. package/dist/esm/types/codex.js +23 -0
  221. package/dist/esm/types/decisionLog.js +5 -0
  222. package/dist/esm/types/historicalData.js +47 -0
  223. package/dist/esm/types/knowledgeNote.js +15 -0
  224. package/dist/esm/types/observedSessionV1.js +257 -0
  225. package/dist/esm/types/opencode.js +12 -0
  226. package/dist/esm/types/plan.js +7 -0
  227. package/dist/esm/types/sessionEvent.js +10 -0
  228. package/dist/esm/types/taskPersistence.js +12 -0
  229. package/dist/esm/usage/billingBlocks.js +148 -0
  230. package/dist/esm/usage/claudeUsageDedupe.js +189 -0
  231. package/dist/esm/usage/importSessionHistory.js +84 -0
  232. package/dist/esm/usage/subagentUsage.js +18 -0
  233. package/dist/esm/usage/usageEvents.js +402 -0
  234. package/dist/esm/usage/usageReports.js +202 -0
  235. package/dist/esm/usageNormalization.js +95 -0
  236. package/dist/esm/watchers/eventBridge.js +219 -0
  237. package/dist/esm/watchers/factory.js +63 -0
  238. package/dist/esm/watchers/jsonlTail.js +289 -0
  239. package/dist/esm/watchers/jsonlWatcher.js +435 -0
  240. package/dist/esm/watchers/providerReaderWatcher.js +118 -0
  241. package/dist/esm/watchers/sqliteWatcher.js +285 -0
  242. package/dist/esm/watchers/types.js +4 -0
  243. package/dist/esm/workspaceRoots.js +56 -0
  244. package/dist/esm/writers/atomic.js +375 -0
  245. package/dist/esm/writers/decisions.js +51 -0
  246. package/dist/esm/writers/notes.js +46 -0
  247. package/dist/esm/writers/tasks.js +63 -0
  248. package/dist/esm/zaiQuota.js +323 -0
  249. package/dist/esm/zaiQuotaApi.js +271 -0
  250. package/dist/esm/zaiQuotaWatcher.js +265 -0
  251. package/dist/fileHead.d.ts +22 -0
  252. package/dist/fileHead.js +33 -0
  253. package/dist/index.d.ts +30 -11
  254. package/dist/index.js +72 -15
  255. package/dist/keepAlive.d.ts +2 -1
  256. package/dist/keepAlive.js +116 -29
  257. package/dist/liveCredentialGuard.d.ts +28 -0
  258. package/dist/liveCredentialGuard.js +36 -0
  259. package/dist/node.d.ts +13 -1
  260. package/dist/node.js +43 -2
  261. package/dist/parsers/codexHistory.js +2 -2
  262. package/dist/parsers/codexParser.d.ts +12 -0
  263. package/dist/parsers/codexParser.js +34 -12
  264. package/dist/parsers/debugLogParser.js +2 -2
  265. package/dist/parsers/sessionPathResolver.js +5 -4
  266. package/dist/processDetection.d.ts +10 -3
  267. package/dist/processDetection.js +44 -11
  268. package/dist/promptHistory.d.ts +32 -0
  269. package/dist/promptHistory.js +115 -59
  270. package/dist/providerFailure.d.ts +12 -6
  271. package/dist/providerFailure.js +45 -0
  272. package/dist/providers/claudeCode.js +7 -99
  273. package/dist/providers/claudeListing.d.ts +22 -0
  274. package/dist/providers/claudeListing.js +158 -0
  275. package/dist/providers/codex.d.ts +3 -1
  276. package/dist/providers/codex.js +26 -227
  277. package/dist/providers/codexRollouts.d.ts +44 -0
  278. package/dist/providers/codexRollouts.js +261 -0
  279. package/dist/providers/detect.js +4 -4
  280. package/dist/readers/plans.js +3 -3
  281. package/dist/schemas/accountManager.d.ts +22 -0
  282. package/dist/schemas/accountManager.js +4 -0
  283. package/dist/schemas/assistantTurn.d.ts +2 -2
  284. package/dist/schemas/index.d.ts +2 -0
  285. package/dist/schemas/index.js +7 -2
  286. package/dist/schemas/providerFailure.d.ts +154 -0
  287. package/dist/schemas/providerFailure.js +40 -0
  288. package/dist/sessionPromptHistory.d.ts +12 -7
  289. package/dist/sessionPromptHistory.js +15 -3
  290. package/dist/sessionPromptSignals.d.ts +9 -0
  291. package/dist/sessionPromptSignals.js +13 -0
  292. package/dist/terminalSync.d.ts +12 -2
  293. package/dist/terminalSync.js +27 -2
  294. package/dist/testing/credentialGuard.d.ts +23 -0
  295. package/dist/testing/credentialGuard.js +233 -0
  296. package/dist/testing/index.d.ts +14 -0
  297. package/dist/testing/index.js +22 -0
  298. package/dist/testing/sessionLogFixtures.d.ts +28 -0
  299. package/dist/testing/sessionLogFixtures.js +169 -0
  300. package/dist/tokenEstimation.d.ts +2 -0
  301. package/dist/tokenEstimation.js +21 -11
  302. package/dist/toolCategory.d.ts +18 -0
  303. package/dist/toolCategory.js +86 -0
  304. package/dist/types/codex.d.ts +4 -0
  305. package/dist/usage/usageEvents.d.ts +16 -0
  306. package/dist/usage/usageEvents.js +53 -9
  307. package/dist/watchers/jsonlTail.d.ts +26 -1
  308. package/dist/watchers/jsonlTail.js +136 -28
  309. package/dist/watchers/jsonlWatcher.js +7 -0
  310. package/dist/watchers/providerReaderWatcher.js +1 -0
  311. package/dist/watchers/types.d.ts +6 -0
  312. package/dist/workspaceRoots.d.ts +23 -0
  313. package/dist/workspaceRoots.js +93 -0
  314. package/package.json +22 -2
@@ -39,6 +39,7 @@ exports.getAccountLoginStatus = getAccountLoginStatus;
39
39
  exports.getAccountLoginStatusAsync = getAccountLoginStatusAsync;
40
40
  exports.finalizeAccountLogin = finalizeAccountLogin;
41
41
  exports.finalizeAccountLoginAsync = finalizeAccountLoginAsync;
42
+ exports.discardAccountLogin = discardAccountLogin;
42
43
  exports.spawnAccountLogin = spawnAccountLogin;
43
44
  exports.switchAccount = switchAccount;
44
45
  exports.switchAccountAsync = switchAccountAsync;
@@ -54,7 +55,9 @@ const atomic_1 = require("./writers/atomic");
54
55
  const accounts_1 = require("./accounts");
55
56
  const claudeProfiles_1 = require("./claudeProfiles");
56
57
  const credentialIO_1 = require("./credentialIO");
58
+ const claudeCredentialLedger_1 = require("./claudeCredentialLedger");
57
59
  const codexProfiles_1 = require("./codexProfiles");
60
+ const codexPaths_1 = require("./codexPaths");
58
61
  const accountSwitch_1 = require("./accountSwitch");
59
62
  function getPendingClaudeProfilePath(loginId) {
60
63
  return path.join((0, claudeProfiles_1.getClaudeProfileDir)(loginId), 'profile.json');
@@ -109,7 +112,16 @@ function copyClaudeProfileToCanonicalHome(sourceHome, accountUuid, credentials,
109
112
  fs.mkdirSync(canonicalHome, { recursive: true, mode: 0o700 });
110
113
  if (path.resolve(sourceHome) !== path.resolve(canonicalHome)) {
111
114
  try {
112
- fs.cpSync(sourceHome, canonicalHome, { recursive: true, force: true });
115
+ // Credentials go only through the chokepoint below: when this account is
116
+ // the live one its profile home must not receive a CLI-usable copy.
117
+ fs.cpSync(sourceHome, canonicalHome, {
118
+ recursive: true,
119
+ force: true,
120
+ filter: (src) => {
121
+ const base = path.basename(src);
122
+ return base !== '.credentials.json' && !base.startsWith('.oauth_refresh.lock');
123
+ },
124
+ });
113
125
  }
114
126
  catch {
115
127
  // The credential and identity files are re-written below when possible.
@@ -148,7 +160,7 @@ function beginAccountLogin(provider, label, opts = {}) {
148
160
  const effectiveLabel = existing
149
161
  ? `${existing.label ?? existing.email ?? 'codex'} (re-login ${(0, crypto_1.randomUUID)().slice(0, 8)})`
150
162
  : label.trim() || `Codex ${(0, codexProfiles_1.listCodexAccounts)().length + 1}`;
151
- const prepared = (0, codexProfiles_1.prepareCodexAccount)(effectiveLabel);
163
+ const prepared = (0, codexProfiles_1.prepareCodexAccount)(effectiveLabel, { importCurrent: false });
152
164
  if (!prepared.success || !prepared.profileId || !prepared.codexHome) {
153
165
  return { success: false, error: prepared.error ?? 'Could not prepare Codex account login.' };
154
166
  }
@@ -186,6 +198,9 @@ function beginAccountLogin(provider, label, opts = {}) {
186
198
  existingAccountId: existing?.id,
187
199
  });
188
200
  seedClaudeLoginHome(home);
201
+ // Claude Code creates this home's Keychain item itself; record the home
202
+ // first so the item is provably Sidekick's even if everything else fails.
203
+ (0, claudeCredentialLedger_1.recordClaudeCredentialHome)(home, 'login');
189
204
  const loginCommand = resolveClaudeLoginCommand(opts);
190
205
  return {
191
206
  success: true,
@@ -207,7 +222,7 @@ function getClaudeAccountLoginStatus(loginId) {
207
222
  }
208
223
  function getAccountLoginStatus(provider, loginId) {
209
224
  if (provider === 'codex') {
210
- const codexHome = (0, codexProfiles_1.getCodexProfileHome)(loginId);
225
+ const codexHome = (0, codexPaths_1.getCodexLoginHome)(loginId);
211
226
  if (!(0, codexProfiles_1.isCodexProfileAuthenticated)(codexHome)) {
212
227
  return { state: 'pending' };
213
228
  }
@@ -226,7 +241,7 @@ function getAccountLoginStatus(provider, loginId) {
226
241
  */
227
242
  async function getAccountLoginStatusAsync(provider, loginId) {
228
243
  if (provider === 'codex') {
229
- const codexHome = (0, codexProfiles_1.getCodexProfileHome)(loginId);
244
+ const codexHome = (0, codexPaths_1.getCodexLoginHome)(loginId);
230
245
  if (!(await (0, codexProfiles_1.isCodexProfileAuthenticatedAsync)(codexHome))) {
231
246
  return { state: 'pending' };
232
247
  }
@@ -257,44 +272,55 @@ function finalizeClaudeAccountLogin(loginId, opts = {}) {
257
272
  if (!identity) {
258
273
  return { success: false, error: 'Claude profile is not authenticated yet.' };
259
274
  }
260
- const credentials = (0, credentialIO_1.readActiveCredentials)(home);
261
- if (!credentials) {
262
- return { success: false, error: 'Could not read Claude profile credentials.' };
263
- }
264
- const oauthAccount = readClaudeOauthAccount(home);
265
- if (!oauthAccount) {
266
- return { success: false, error: 'Could not read Claude profile config.' };
275
+ const read = (0, credentialIO_1.readClaudeCredentialStore)(home);
276
+ if (read.status === 'error') {
277
+ // Transient (locked Keychain, timeout): keep the login home for a retry.
278
+ return { success: false, error: 'Could not read the new Claude login yet; try again.' };
267
279
  }
268
- const pending = readPendingClaudeProfile(loginId);
269
- const existing = (0, accountRegistry_1.listSavedAccountProfiles)('claude-code').find((account) => (account.providerAccountId ?? account.id) === identity.uuid);
270
- copyClaudeProfileToCanonicalHome(home, identity.uuid, credentials, oauthAccount);
271
280
  const warnings = [];
272
- if (pending?.existingAccountId && pending.existingAccountId !== identity.uuid) {
273
- warnings.push(`You signed in as ${identity.email}, which is a different account from the one you chose to re-authenticate; it was saved separately.`);
274
- }
275
- (0, accountRegistry_1.upsertSavedAccountProfile)({
276
- id: existing?.id ?? identity.uuid,
277
- providerId: 'claude-code',
278
- providerAccountId: identity.uuid,
279
- email: identity.email,
280
- label: existing?.label ?? (pending?.label || identity.email),
281
- addedAt: existing?.addedAt ?? pending?.addedAt ?? new Date().toISOString(),
282
- metadata: {
283
- ...existing?.metadata,
284
- email: identity.email,
285
- origin: 'login',
286
- },
287
- });
288
- removePendingClaudeProfile(loginId);
289
- if (loginId !== identity.uuid) {
290
- removePendingClaudeProfile(identity.uuid);
291
- // The temporary home's keychain item holds live tokens; nothing references it now.
292
- (0, credentialIO_1.deleteStoredCredentials)((0, claudeProfiles_1.getClaudeProfileHome)(loginId));
293
- try {
294
- fs.rmSync((0, claudeProfiles_1.getClaudeProfileDir)(loginId), { recursive: true, force: true });
281
+ try {
282
+ if (read.status !== 'ok') {
283
+ return {
284
+ success: false,
285
+ error: 'The sign-in finished without a readable credential; sign in again.',
286
+ };
295
287
  }
296
- catch {
297
- /* the temporary login home is disposable */
288
+ const oauthAccount = readClaudeOauthAccount(home);
289
+ if (!oauthAccount) {
290
+ return { success: false, error: 'Could not read Claude profile config.' };
291
+ }
292
+ const pending = readPendingClaudeProfile(loginId);
293
+ const existing = (0, accountRegistry_1.listSavedAccountProfiles)('claude-code').find((account) => (account.providerAccountId ?? account.id) === identity.uuid);
294
+ // The chokepoint writes the private backup first, so a failure below
295
+ // still leaves the new login saved.
296
+ copyClaudeProfileToCanonicalHome(home, identity.uuid, read.blob, oauthAccount);
297
+ if (pending?.existingAccountId && pending.existingAccountId !== identity.uuid) {
298
+ warnings.push(`You signed in as ${identity.email}, which is a different account from the one you chose to re-authenticate; it was saved separately.`);
299
+ }
300
+ (0, accountRegistry_1.upsertSavedAccountProfile)({
301
+ id: existing?.id ?? identity.uuid,
302
+ providerId: 'claude-code',
303
+ providerAccountId: identity.uuid,
304
+ email: identity.email,
305
+ label: existing?.label ?? (pending?.label || identity.email),
306
+ addedAt: existing?.addedAt ?? pending?.addedAt ?? new Date().toISOString(),
307
+ metadata: {
308
+ ...existing?.metadata,
309
+ email: identity.email,
310
+ origin: 'login',
311
+ },
312
+ });
313
+ }
314
+ catch (err) {
315
+ return { success: false, error: `Could not save the new Claude login: ${err}` };
316
+ }
317
+ finally {
318
+ // Every exit after authentication: the temporary home's Keychain item
319
+ // holds a live refresh token that nothing will ever reference again.
320
+ removePendingClaudeProfile(loginId);
321
+ if (loginId !== identity.uuid) {
322
+ removePendingClaudeProfile(identity.uuid);
323
+ discardClaudeLoginHome(loginId);
298
324
  }
299
325
  }
300
326
  if (opts.activate === false) {
@@ -306,6 +332,8 @@ function finalizeClaudeAccountLogin(loginId, opts = {}) {
306
332
  const applied = (0, accounts_1.applyClaudeProfileToLiveHome)(identity.uuid);
307
333
  if (!applied.success)
308
334
  return applied;
335
+ if (applied.warning)
336
+ warnings.push(applied.warning);
309
337
  }
310
338
  const switched = (0, accounts_1.switchToAccount)(identity.uuid);
311
339
  return warnings.length
@@ -356,22 +384,82 @@ function latestHomeActivity(home) {
356
384
  }
357
385
  return latest;
358
386
  }
387
+ /** Delete a temporary Claude login home: its Keychain item first, then the directory. */
388
+ function discardClaudeLoginHome(loginId) {
389
+ const dir = (0, claudeProfiles_1.getClaudeProfileDir)(loginId);
390
+ if (!path.resolve(dir).startsWith(path.resolve((0, accountRegistry_1.getAccountsDir)()) + path.sep))
391
+ return;
392
+ (0, credentialIO_1.deleteStoredCredentials)((0, claudeProfiles_1.getClaudeProfileHome)(loginId));
393
+ try {
394
+ fs.rmSync(dir, { recursive: true, force: true });
395
+ }
396
+ catch {
397
+ /* the temporary login home is disposable */
398
+ }
399
+ }
359
400
  function discardPendingLogin(provider, loginId) {
360
401
  try {
361
- const dir = provider === 'codex'
362
- ? path.dirname((0, codexProfiles_1.getCodexProfileHome)(loginId))
363
- : (0, claudeProfiles_1.getClaudeProfileDir)(loginId);
364
- if (!dir.startsWith((0, accountRegistry_1.getAccountsDir)()))
402
+ if (provider === 'claude-code') {
403
+ discardClaudeLoginHome(loginId);
365
404
  return;
366
- if (provider === 'claude-code')
367
- (0, credentialIO_1.deleteStoredCredentials)((0, claudeProfiles_1.getClaudeProfileHome)(loginId));
368
- fs.rmSync(dir, { recursive: true, force: true });
405
+ }
406
+ (0, codexProfiles_1.discardCodexLogin)(loginId);
369
407
  }
370
408
  catch {
371
409
  /* best effort */
372
410
  }
373
411
  }
412
+ /**
413
+ * Abandon a login started with {@link beginAccountLogin} (cancelled, timed
414
+ * out, or failed): removes the temporary home and, for Claude, its Keychain
415
+ * item. Refuses ids that belong to a saved profile. Returns true when
416
+ * something was discarded.
417
+ */
418
+ function discardAccountLogin(provider, loginId) {
419
+ const registered = provider === 'codex'
420
+ ? (0, codexProfiles_1.listCodexAccounts)().some((account) => account.id === loginId)
421
+ : (0, accountRegistry_1.listSavedAccountProfiles)('claude-code').some((account) => account.id === loginId || account.providerAccountId === loginId);
422
+ if (registered)
423
+ return false;
424
+ if (provider === 'claude-code' && !readPendingClaudeProfile(loginId)) {
425
+ // Not a pending login; still drop a stray item for a home we created.
426
+ (0, credentialIO_1.deleteStoredCredentials)((0, claudeProfiles_1.getClaudeProfileHome)(loginId));
427
+ return false;
428
+ }
429
+ discardPendingLogin(provider, loginId);
430
+ return true;
431
+ }
432
+ class AccountLoginWriterActiveError extends Error {
433
+ }
374
434
  async function spawnAccountLogin(provider, label, opts = {}) {
435
+ let loginId;
436
+ let result;
437
+ let writerActive = false;
438
+ try {
439
+ result = await runAccountLogin(provider, label, opts, (id) => {
440
+ loginId = id;
441
+ });
442
+ return result;
443
+ }
444
+ catch (error) {
445
+ if (error instanceof AccountLoginWriterActiveError) {
446
+ writerActive = true;
447
+ return {
448
+ success: false,
449
+ error: 'The Codex login process could not be stopped. Its temporary home was preserved; close that process before retrying.',
450
+ };
451
+ }
452
+ throw error;
453
+ }
454
+ finally {
455
+ // Every exit that did not end in a saved login (spawn error, early exit,
456
+ // abort, timeout, a throw) removes the temporary home and its Keychain item.
457
+ if (!writerActive && loginId && (provider === 'codex' || !result?.success)) {
458
+ discardPendingLogin(provider, loginId);
459
+ }
460
+ }
461
+ }
462
+ async function runAccountLogin(provider, label, opts, onBegin) {
375
463
  if (opts.signal?.aborted) {
376
464
  return { success: false, error: 'Account login aborted.' };
377
465
  }
@@ -381,6 +469,7 @@ async function spawnAccountLogin(provider, label, opts = {}) {
381
469
  });
382
470
  if (!begin.success)
383
471
  return { success: false, error: begin.error };
472
+ onBegin(begin.loginId);
384
473
  if (begin.alreadyComplete) {
385
474
  return finalizeAccountLoginAsync(provider, begin.loginId, { activate: opts.activate ?? true });
386
475
  }
@@ -419,57 +508,105 @@ async function spawnAccountLogin(provider, label, opts = {}) {
419
508
  childExited = true;
420
509
  childState.spawnError = error;
421
510
  });
422
- const abort = () => {
423
- child.kill();
511
+ const stopCodexChild = async () => {
512
+ if (provider !== 'codex' || childExited)
513
+ return;
514
+ const waitForExit = (ms) => new Promise((resolve) => {
515
+ if (childExited) {
516
+ resolve();
517
+ return;
518
+ }
519
+ const done = () => {
520
+ clearTimeout(timer);
521
+ child.off('exit', done);
522
+ child.off('error', done);
523
+ resolve();
524
+ };
525
+ const timer = setTimeout(done, ms);
526
+ child.once('exit', done);
527
+ child.once('error', done);
528
+ });
529
+ await waitForExit(100);
530
+ try {
531
+ if (!childExited) {
532
+ child.kill();
533
+ await waitForExit(1000);
534
+ }
535
+ if (!childExited) {
536
+ child.kill('SIGKILL');
537
+ await waitForExit(1000);
538
+ }
539
+ }
540
+ catch {
541
+ throw new AccountLoginWriterActiveError();
542
+ }
543
+ if (!childExited)
544
+ throw new AccountLoginWriterActiveError();
545
+ };
546
+ const abort = async () => {
547
+ if (provider === 'codex')
548
+ await stopCodexChild();
549
+ else
550
+ child.kill();
424
551
  discardPendingLogin(provider, begin.loginId);
425
552
  emitStatus(opts, { state: 'failed', error: 'Account login aborted.' });
426
553
  return { success: false, error: 'Account login aborted.' };
427
554
  };
428
- while (true) {
429
- if (opts.signal?.aborted)
430
- return abort();
431
- const status = emitStatus(opts, await getAccountLoginStatusAsync(provider, begin.loginId));
432
- if (status.state === 'authenticated') {
433
- return finalizeAccountLoginAsync(provider, begin.loginId, {
434
- activate: opts.activate ?? true,
435
- });
436
- }
437
- if (childExited) {
438
- if (childState.spawnError) {
439
- const error = `Could not spawn account login: ${childState.spawnError.message}`;
440
- discardPendingLogin(provider, begin.loginId);
441
- emitStatus(opts, { state: 'failed', error });
442
- return { success: false, error };
443
- }
444
- const finalStatus = emitStatus(opts, await getAccountLoginStatusAsync(provider, begin.loginId));
445
- if (finalStatus.state === 'authenticated') {
446
- return finalizeAccountLoginAsync(provider, begin.loginId, {
555
+ try {
556
+ while (true) {
557
+ if (opts.signal?.aborted)
558
+ return await abort();
559
+ const status = emitStatus(opts, await getAccountLoginStatusAsync(provider, begin.loginId));
560
+ if (status.state === 'authenticated') {
561
+ await stopCodexChild();
562
+ return await finalizeAccountLoginAsync(provider, begin.loginId, {
447
563
  activate: opts.activate ?? true,
448
564
  });
449
565
  }
450
- discardPendingLogin(provider, begin.loginId);
451
- emitStatus(opts, {
452
- state: 'failed',
453
- error: `Account login exited before authentication completed${childExitCode === null ? '.' : ` (exit ${childExitCode}).`}`,
454
- });
455
- return { success: false, error: 'Account login did not complete.' };
456
- }
457
- const now = Date.now();
458
- const mtime = latestHomeActivity(begin.configDir);
459
- if (mtime > lastSeenMtime) {
460
- lastSeenMtime = mtime;
461
- lastActivity = now;
462
- }
463
- const remainingMs = Math.min(inactivityMs - (now - lastActivity), maxMs - (now - startedAt));
464
- if (remainingMs <= 0) {
465
- child.kill();
466
- discardPendingLogin(provider, begin.loginId);
467
- emitStatus(opts, { state: 'failed', error: 'Account login timed out.' });
468
- return { success: false, error: 'Account login timed out.' };
566
+ if (childExited) {
567
+ if (childState.spawnError) {
568
+ const error = `Could not spawn account login: ${childState.spawnError.message}`;
569
+ discardPendingLogin(provider, begin.loginId);
570
+ emitStatus(opts, { state: 'failed', error });
571
+ return { success: false, error };
572
+ }
573
+ const finalStatus = emitStatus(opts, await getAccountLoginStatusAsync(provider, begin.loginId));
574
+ if (finalStatus.state === 'authenticated') {
575
+ await stopCodexChild();
576
+ return await finalizeAccountLoginAsync(provider, begin.loginId, {
577
+ activate: opts.activate ?? true,
578
+ });
579
+ }
580
+ discardPendingLogin(provider, begin.loginId);
581
+ emitStatus(opts, {
582
+ state: 'failed',
583
+ error: `Account login exited before authentication completed${childExitCode === null ? '.' : ` (exit ${childExitCode}).`}`,
584
+ });
585
+ return { success: false, error: 'Account login did not complete.' };
586
+ }
587
+ const now = Date.now();
588
+ const mtime = latestHomeActivity(begin.configDir);
589
+ if (mtime > lastSeenMtime) {
590
+ lastSeenMtime = mtime;
591
+ lastActivity = now;
592
+ }
593
+ const remainingMs = Math.min(inactivityMs - (now - lastActivity), maxMs - (now - startedAt));
594
+ if (remainingMs <= 0) {
595
+ if (provider === 'codex')
596
+ await stopCodexChild();
597
+ else
598
+ child.kill();
599
+ discardPendingLogin(provider, begin.loginId);
600
+ emitStatus(opts, { state: 'failed', error: 'Account login timed out.' });
601
+ return { success: false, error: 'Account login timed out.' };
602
+ }
603
+ const shouldContinue = await waitForNextPoll(Math.min(2_000, remainingMs), opts.signal);
604
+ if (!shouldContinue)
605
+ return await abort();
469
606
  }
470
- const shouldContinue = await waitForNextPoll(Math.min(2_000, remainingMs), opts.signal);
471
- if (!shouldContinue)
472
- return abort();
607
+ }
608
+ finally {
609
+ await stopCodexChild();
473
610
  }
474
611
  }
475
612
  function switchAccount(provider, id, options = {}) {
@@ -12,11 +12,6 @@ export interface SyncLiveAccountStateOptions {
12
12
  keychainFoldIntervalMs?: number;
13
13
  now?: number;
14
14
  }
15
- /**
16
- * Reconcile the live logins with the saved profiles for both providers.
17
- * Safe to call from watchers: re-entrant calls return an empty report, and
18
- * `onlyIfChanged` short-circuits when the live files are unchanged.
19
- */
20
15
  export declare function syncLiveAccountStateSync(options?: SyncLiveAccountStateOptions): SyncReport;
21
16
  /** Promise-returning twin of {@link syncLiveAccountStateSync} for async hosts. */
22
17
  export declare function syncLiveAccountState(options?: SyncLiveAccountStateOptions): Promise<SyncReport>;
@@ -28,3 +23,4 @@ export interface CleanupAbandonedLoginsResult {
28
23
  }
29
24
  /** Remove isolated login homes that never authenticated (both providers). */
30
25
  export declare function cleanupAbandonedLogins(olderThanMs?: number, now?: number): CleanupAbandonedLoginsResult;
26
+ export declare function cleanupAbandonedLoginsAsync(olderThanMs?: number, now?: number): Promise<CleanupAbandonedLoginsResult>;
@@ -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[];