@kaitranntt/ccs 7.56.0-dev.1 → 7.56.0-dev.2

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 (160) hide show
  1. package/README.md +2 -0
  2. package/dist/api/services/profile-lifecycle-service.d.ts.map +1 -1
  3. package/dist/api/services/profile-lifecycle-service.js +10 -10
  4. package/dist/api/services/profile-lifecycle-service.js.map +1 -1
  5. package/dist/api/services/profile-writer.d.ts.map +1 -1
  6. package/dist/api/services/profile-writer.js +33 -36
  7. package/dist/api/services/profile-writer.js.map +1 -1
  8. package/dist/auth/profile-registry.d.ts.map +1 -1
  9. package/dist/auth/profile-registry.js +46 -48
  10. package/dist/auth/profile-registry.js.map +1 -1
  11. package/dist/cliproxy/account-manager.d.ts +1 -1
  12. package/dist/cliproxy/account-manager.d.ts.map +1 -1
  13. package/dist/cliproxy/account-manager.js +4 -1
  14. package/dist/cliproxy/account-manager.js.map +1 -1
  15. package/dist/cliproxy/accounts/index.d.ts +1 -1
  16. package/dist/cliproxy/accounts/index.d.ts.map +1 -1
  17. package/dist/cliproxy/accounts/index.js +4 -1
  18. package/dist/cliproxy/accounts/index.js.map +1 -1
  19. package/dist/cliproxy/accounts/query.d.ts.map +1 -1
  20. package/dist/cliproxy/accounts/query.js +4 -6
  21. package/dist/cliproxy/accounts/query.js.map +1 -1
  22. package/dist/cliproxy/accounts/registry.d.ts +2 -2
  23. package/dist/cliproxy/accounts/registry.d.ts.map +1 -1
  24. package/dist/cliproxy/accounts/registry.js +292 -320
  25. package/dist/cliproxy/accounts/registry.js.map +1 -1
  26. package/dist/cliproxy/accounts/token-file-ops.d.ts +16 -1
  27. package/dist/cliproxy/accounts/token-file-ops.d.ts.map +1 -1
  28. package/dist/cliproxy/accounts/token-file-ops.js +55 -3
  29. package/dist/cliproxy/accounts/token-file-ops.js.map +1 -1
  30. package/dist/cliproxy/ai-providers/config-store.d.ts.map +1 -1
  31. package/dist/cliproxy/ai-providers/config-store.js +37 -4
  32. package/dist/cliproxy/ai-providers/config-store.js.map +1 -1
  33. package/dist/cliproxy/ai-providers/service.d.ts +2 -2
  34. package/dist/cliproxy/ai-providers/service.d.ts.map +1 -1
  35. package/dist/cliproxy/ai-providers/service.js +39 -10
  36. package/dist/cliproxy/ai-providers/service.js.map +1 -1
  37. package/dist/cliproxy/ai-providers/types.d.ts +2 -0
  38. package/dist/cliproxy/ai-providers/types.d.ts.map +1 -1
  39. package/dist/cliproxy/ai-providers/types.js.map +1 -1
  40. package/dist/cliproxy/auth/oauth-handler.d.ts +1 -0
  41. package/dist/cliproxy/auth/oauth-handler.d.ts.map +1 -1
  42. package/dist/cliproxy/auth/oauth-handler.js +31 -69
  43. package/dist/cliproxy/auth/oauth-handler.js.map +1 -1
  44. package/dist/cliproxy/auth/oauth-process.d.ts +1 -0
  45. package/dist/cliproxy/auth/oauth-process.d.ts.map +1 -1
  46. package/dist/cliproxy/auth/oauth-process.js +5 -5
  47. package/dist/cliproxy/auth/oauth-process.js.map +1 -1
  48. package/dist/cliproxy/auth/token-manager.d.ts +1 -1
  49. package/dist/cliproxy/auth/token-manager.d.ts.map +1 -1
  50. package/dist/cliproxy/auth/token-manager.js +53 -20
  51. package/dist/cliproxy/auth/token-manager.js.map +1 -1
  52. package/dist/cliproxy/auth-token-manager.d.ts.map +1 -1
  53. package/dist/cliproxy/auth-token-manager.js +47 -50
  54. package/dist/cliproxy/auth-token-manager.js.map +1 -1
  55. package/dist/cliproxy/executor/index.js +1 -1
  56. package/dist/cliproxy/executor/index.js.map +1 -1
  57. package/dist/cliproxy/services/variant-config-adapter.d.ts.map +1 -1
  58. package/dist/cliproxy/services/variant-config-adapter.js +43 -39
  59. package/dist/cliproxy/services/variant-config-adapter.js.map +1 -1
  60. package/dist/commands/cliproxy/proxy-lifecycle-subcommand.d.ts +0 -5
  61. package/dist/commands/cliproxy/proxy-lifecycle-subcommand.d.ts.map +1 -1
  62. package/dist/commands/cliproxy/proxy-lifecycle-subcommand.js +6 -16
  63. package/dist/commands/cliproxy/proxy-lifecycle-subcommand.js.map +1 -1
  64. package/dist/commands/cliproxy/resolve-lifecycle-port.d.ts +6 -0
  65. package/dist/commands/cliproxy/resolve-lifecycle-port.d.ts.map +1 -0
  66. package/dist/commands/cliproxy/resolve-lifecycle-port.js +15 -0
  67. package/dist/commands/cliproxy/resolve-lifecycle-port.js.map +1 -0
  68. package/dist/commands/config-auth/disable-command.d.ts.map +1 -1
  69. package/dist/commands/config-auth/disable-command.js +8 -8
  70. package/dist/commands/config-auth/disable-command.js.map +1 -1
  71. package/dist/commands/config-auth/setup-command.d.ts.map +1 -1
  72. package/dist/commands/config-auth/setup-command.js +9 -11
  73. package/dist/commands/config-auth/setup-command.js.map +1 -1
  74. package/dist/commands/copilot-command.js +11 -11
  75. package/dist/commands/copilot-command.js.map +1 -1
  76. package/dist/commands/cursor-command.d.ts.map +1 -1
  77. package/dist/commands/cursor-command.js +11 -11
  78. package/dist/commands/cursor-command.js.map +1 -1
  79. package/dist/commands/help-command.js +1 -1
  80. package/dist/commands/help-command.js.map +1 -1
  81. package/dist/commands/setup-command.d.ts.map +1 -1
  82. package/dist/commands/setup-command.js +4 -3
  83. package/dist/commands/setup-command.js.map +1 -1
  84. package/dist/config/unified-config-loader.d.ts +1 -1
  85. package/dist/config/unified-config-loader.d.ts.map +1 -1
  86. package/dist/config/unified-config-loader.js +39 -32
  87. package/dist/config/unified-config-loader.js.map +1 -1
  88. package/dist/ui/assets/{accounts-CccaoV-N.js → accounts-BikRyqcT.js} +1 -1
  89. package/dist/ui/assets/{alert-dialog-D838sIju.js → alert-dialog-CwEJfEUX.js} +1 -1
  90. package/dist/ui/assets/api-BYSEdQYh.js +4 -0
  91. package/dist/ui/assets/{auth-section-BGCaHAcN.js → auth-section-CrFrby6w.js} +1 -1
  92. package/dist/ui/assets/{backups-section-CqZN-2Qq.js → backups-section-CdMvSnSm.js} +1 -1
  93. package/dist/ui/assets/{checkbox-BgLi38k2.js → checkbox-CC0rU-9-.js} +1 -1
  94. package/dist/ui/assets/{claude-extension-ClHnH_4i.js → claude-extension-CzGeZYz8.js} +1 -1
  95. package/dist/ui/assets/cliproxy-BSNSGNOv.js +3 -0
  96. package/dist/ui/assets/{cliproxy-ai-providers-EhHqrkRr.js → cliproxy-ai-providers-D-U6NKfV.js} +3 -3
  97. package/dist/ui/assets/{cliproxy-control-panel-lRQBQhPS.js → cliproxy-control-panel-BCexyz40.js} +1 -1
  98. package/dist/ui/assets/{confirm-dialog-t6i94F2B.js → confirm-dialog-Bee0kh6i.js} +1 -1
  99. package/dist/ui/assets/{copilot-DlSkTZ2q.js → copilot-BZRAvPLD.js} +1 -1
  100. package/dist/ui/assets/{cursor-DJvTEVHh.js → cursor-CO2rCNKC.js} +1 -1
  101. package/dist/ui/assets/{droid-wevxXBVG.js → droid-CcKqoQtO.js} +2 -2
  102. package/dist/ui/assets/{globalenv-section-De072K_j.js → globalenv-section-CBRvmZbS.js} +1 -1
  103. package/dist/ui/assets/{health-Wv80MRCF.js → health-DgrTQESI.js} +1 -1
  104. package/dist/ui/assets/{icons-DMeZET56.js → icons-DR-ORtNe.js} +1 -1
  105. package/dist/ui/assets/{index-CxPqnEpn.js → index-BOhcsuQK.js} +1 -1
  106. package/dist/ui/assets/{index-BqdBnk5l.js → index-CursmDny.js} +1 -1
  107. package/dist/ui/assets/{index-C9TiCNLM.js → index-D90gSn57.js} +1 -1
  108. package/dist/ui/assets/{index-BmB4ckDm.js → index-DJCPwAoe.js} +1 -1
  109. package/dist/ui/assets/index-kGiBvBM-.css +1 -0
  110. package/dist/ui/assets/index-sCtK1kDn.js +47 -0
  111. package/dist/ui/assets/{proxy-status-widget-DMe8konR.js → proxy-status-widget-C8cOlsqE.js} +1 -1
  112. package/dist/ui/assets/{searchable-select-C-dUgzQ_.js → searchable-select-CF22qEzz.js} +1 -1
  113. package/dist/ui/assets/{separator-TnQMGoEt.js → separator-CAoIlNuN.js} +1 -1
  114. package/dist/ui/assets/{shared-Cbcz8ZIu.js → shared-zeFjETE6.js} +1 -1
  115. package/dist/ui/assets/{switch-Df15Gmz9.js → switch-BeqpDfK5.js} +1 -1
  116. package/dist/ui/assets/{updates-WOEYNHTJ.js → updates-By9S46EJ.js} +1 -1
  117. package/dist/ui/index.html +3 -3
  118. package/dist/web-server/health-service.d.ts.map +1 -1
  119. package/dist/web-server/health-service.js +2 -3
  120. package/dist/web-server/health-service.js.map +1 -1
  121. package/dist/web-server/middleware/auth-middleware.d.ts +2 -0
  122. package/dist/web-server/middleware/auth-middleware.d.ts.map +1 -1
  123. package/dist/web-server/middleware/auth-middleware.js +23 -1
  124. package/dist/web-server/middleware/auth-middleware.js.map +1 -1
  125. package/dist/web-server/routes/ai-provider-routes.d.ts.map +1 -1
  126. package/dist/web-server/routes/ai-provider-routes.js +19 -13
  127. package/dist/web-server/routes/ai-provider-routes.js.map +1 -1
  128. package/dist/web-server/routes/cliproxy-auth-routes.d.ts +7 -0
  129. package/dist/web-server/routes/cliproxy-auth-routes.d.ts.map +1 -1
  130. package/dist/web-server/routes/cliproxy-auth-routes.js +119 -32
  131. package/dist/web-server/routes/cliproxy-auth-routes.js.map +1 -1
  132. package/dist/web-server/routes/cliproxy-stats-routes.d.ts.map +1 -1
  133. package/dist/web-server/routes/cliproxy-stats-routes.js +6 -0
  134. package/dist/web-server/routes/cliproxy-stats-routes.js.map +1 -1
  135. package/dist/web-server/routes/cliproxy-sync-routes.d.ts.map +1 -1
  136. package/dist/web-server/routes/cliproxy-sync-routes.js +6 -11
  137. package/dist/web-server/routes/cliproxy-sync-routes.js.map +1 -1
  138. package/dist/web-server/routes/cursor-settings-routes.d.ts.map +1 -1
  139. package/dist/web-server/routes/cursor-settings-routes.js +36 -33
  140. package/dist/web-server/routes/cursor-settings-routes.js.map +1 -1
  141. package/dist/web-server/routes/misc-routes.d.ts.map +1 -1
  142. package/dist/web-server/routes/misc-routes.js +26 -19
  143. package/dist/web-server/routes/misc-routes.js.map +1 -1
  144. package/dist/web-server/routes/proxy-routes.d.ts.map +1 -1
  145. package/dist/web-server/routes/proxy-routes.js +6 -0
  146. package/dist/web-server/routes/proxy-routes.js.map +1 -1
  147. package/dist/web-server/routes/settings-routes.d.ts.map +1 -1
  148. package/dist/web-server/routes/settings-routes.js +143 -109
  149. package/dist/web-server/routes/settings-routes.js.map +1 -1
  150. package/dist/web-server/routes/websearch-routes.d.ts.map +1 -1
  151. package/dist/web-server/routes/websearch-routes.js +31 -43
  152. package/dist/web-server/routes/websearch-routes.js.map +1 -1
  153. package/dist/web-server/shared-routes.d.ts.map +1 -1
  154. package/dist/web-server/shared-routes.js +6 -0
  155. package/dist/web-server/shared-routes.js.map +1 -1
  156. package/package.json +1 -1
  157. package/dist/ui/assets/api-IXigV-A_.js +0 -4
  158. package/dist/ui/assets/cliproxy-qX23viWV.js +0 -3
  159. package/dist/ui/assets/index-DRmZP4OP.css +0 -1
  160. package/dist/ui/assets/index-N_jd9sKU.js +0 -47
@@ -30,48 +30,100 @@ Object.defineProperty(exports, "__esModule", { value: true });
30
30
  exports.discoverExistingAccounts = exports.setAccountTier = exports.touchAccount = exports.renameAccount = exports.removeAccount = exports.resumeAccount = exports.pauseAccount = exports.setDefaultAccount = exports.registerAccount = exports.syncRegistryWithTokenFiles = exports.saveAccountsRegistry = exports.loadAccountsRegistry = void 0;
31
31
  const fs = __importStar(require("fs"));
32
32
  const path = __importStar(require("path"));
33
+ const lockfile = __importStar(require("proper-lockfile"));
33
34
  const auth_types_1 = require("../auth/auth-types");
34
35
  const config_generator_1 = require("../config-generator");
35
36
  const types_1 = require("./types");
36
37
  const token_file_ops_1 = require("./token-file-ops");
37
38
  /** Default registry structure */
38
- const DEFAULT_REGISTRY = {
39
- version: 1,
40
- providers: {},
41
- };
42
- /**
43
- * Load accounts registry
44
- */
45
- function loadAccountsRegistry() {
39
+ function createDefaultRegistry() {
40
+ return {
41
+ version: 1,
42
+ providers: {},
43
+ };
44
+ }
45
+ function withAccountsRegistryLock(callback) {
46
+ const lockTarget = (0, config_generator_1.getCliproxyDir)();
47
+ let release;
48
+ if (!fs.existsSync(lockTarget)) {
49
+ fs.mkdirSync(lockTarget, { recursive: true, mode: 0o700 });
50
+ }
51
+ try {
52
+ release = lockfile.lockSync(lockTarget, { stale: 10000 });
53
+ return callback();
54
+ }
55
+ finally {
56
+ if (release) {
57
+ try {
58
+ release();
59
+ }
60
+ catch {
61
+ // Best-effort release
62
+ }
63
+ }
64
+ }
65
+ }
66
+ function readAccountsRegistryFromDisk() {
46
67
  const registryPath = (0, token_file_ops_1.getAccountsRegistryPath)();
47
68
  if (!fs.existsSync(registryPath)) {
48
- return { ...DEFAULT_REGISTRY };
69
+ return createDefaultRegistry();
49
70
  }
71
+ const content = fs.readFileSync(registryPath, 'utf-8');
72
+ let data;
50
73
  try {
51
- const content = fs.readFileSync(registryPath, 'utf-8');
52
- const data = JSON.parse(content);
53
- return {
54
- version: data.version || 1,
55
- providers: data.providers || {},
56
- };
74
+ data = JSON.parse(content);
57
75
  }
58
- catch {
59
- return { ...DEFAULT_REGISTRY };
76
+ catch (error) {
77
+ throw new Error(`Accounts registry is corrupted: ${error.message}`);
60
78
  }
79
+ if (!data || typeof data !== 'object') {
80
+ throw new Error('Accounts registry is corrupted: expected object');
81
+ }
82
+ const parsed = data;
83
+ return {
84
+ version: typeof parsed.version === 'number' ? parsed.version : 1,
85
+ providers: parsed.providers && typeof parsed.providers === 'object'
86
+ ? parsed.providers
87
+ : {},
88
+ };
61
89
  }
62
- exports.loadAccountsRegistry = loadAccountsRegistry;
63
- /**
64
- * Save accounts registry
65
- */
66
- function saveAccountsRegistry(registry) {
90
+ function writeAccountsRegistryToDisk(registry) {
67
91
  const registryPath = (0, token_file_ops_1.getAccountsRegistryPath)();
68
92
  const dir = path.dirname(registryPath);
69
93
  if (!fs.existsSync(dir)) {
70
94
  fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
71
95
  }
72
- fs.writeFileSync(registryPath, JSON.stringify(registry, null, 2) + '\n', {
96
+ const tempPath = `${registryPath}.tmp.${process.pid}`;
97
+ fs.writeFileSync(tempPath, JSON.stringify(registry, null, 2) + '\n', {
73
98
  mode: 0o600,
74
99
  });
100
+ fs.renameSync(tempPath, registryPath);
101
+ }
102
+ function mutateAccountsRegistry(mutator) {
103
+ return withAccountsRegistryLock(() => {
104
+ const registry = readAccountsRegistryFromDisk();
105
+ const initialSnapshot = JSON.stringify(registry);
106
+ const result = mutator(registry);
107
+ if (JSON.stringify(registry) !== initialSnapshot) {
108
+ writeAccountsRegistryToDisk(registry);
109
+ }
110
+ return result;
111
+ });
112
+ }
113
+ /**
114
+ * Load accounts registry
115
+ */
116
+ function loadAccountsRegistry() {
117
+ return readAccountsRegistryFromDisk();
118
+ }
119
+ exports.loadAccountsRegistry = loadAccountsRegistry;
120
+ /**
121
+ * Save accounts registry
122
+ */
123
+ function saveAccountsRegistry(registry) {
124
+ withAccountsRegistryLock(() => {
125
+ writeAccountsRegistryToDisk(registry);
126
+ });
75
127
  }
76
128
  exports.saveAccountsRegistry = saveAccountsRegistry;
77
129
  /**
@@ -118,99 +170,96 @@ exports.syncRegistryWithTokenFiles = syncRegistryWithTokenFiles;
118
170
  * Called after successful OAuth to record the account
119
171
  *
120
172
  * For providers without email (kiro, ghcp):
121
- * - nickname is REQUIRED and used as accountId
122
- * - Uniqueness is enforced to prevent overwriting
173
+ * - internal accountId is derived from token metadata
174
+ * - nickname is optional metadata
123
175
  *
124
176
  * For providers with email:
125
177
  * - email is used as accountId
126
178
  * - nickname is auto-generated from email if not provided
127
179
  */
128
180
  function registerAccount(provider, tokenFile, email, nickname, projectId) {
129
- const registry = loadAccountsRegistry();
130
- // Initialize provider section if needed
131
- if (!registry.providers[provider]) {
132
- registry.providers[provider] = {
133
- default: 'default',
134
- accounts: {},
135
- };
136
- }
137
- const providerAccounts = registry.providers[provider];
138
- if (!providerAccounts) {
139
- throw new Error('Failed to initialize provider accounts');
140
- }
141
- // Determine account ID based on provider type
142
- let accountId;
143
- let accountNickname;
144
- if (types_1.PROVIDERS_WITHOUT_EMAIL.includes(provider)) {
145
- // For kiro/ghcp: nickname is REQUIRED and used as accountId
146
- if (!nickname || nickname === 'default') {
147
- throw new Error(`Nickname is required when adding ${provider} accounts. ` +
148
- `Use --nickname <name> or provide a nickname in the UI.`);
181
+ return mutateAccountsRegistry((registry) => {
182
+ syncRegistryWithTokenFiles(registry);
183
+ if (!registry.providers[provider]) {
184
+ registry.providers[provider] = {
185
+ default: 'default',
186
+ accounts: {},
187
+ };
149
188
  }
150
- // Validate nickname format
151
- const validationError = (0, token_file_ops_1.validateNickname)(nickname);
152
- if (validationError) {
153
- throw new Error(validationError);
189
+ const providerAccounts = registry.providers[provider];
190
+ if (!providerAccounts) {
191
+ throw new Error('Failed to initialize provider accounts');
154
192
  }
155
- // Check uniqueness
156
- for (const [existingId, _account] of Object.entries(providerAccounts.accounts)) {
157
- if (existingId.toLowerCase() === nickname.toLowerCase()) {
158
- throw new Error(`An account with nickname "${nickname}" already exists for ${provider}. ` +
159
- `Choose a different nickname.`);
193
+ let accountId;
194
+ let accountNickname;
195
+ if (types_1.PROVIDERS_WITHOUT_EMAIL.includes(provider)) {
196
+ accountId = email
197
+ ? (0, token_file_ops_1.extractAccountIdFromTokenFile)(tokenFile, email)
198
+ : (0, token_file_ops_1.deriveNoEmailProviderAccountId)(provider, tokenFile, providerAccounts.accounts);
199
+ const existingAccount = providerAccounts.accounts[accountId];
200
+ if (nickname) {
201
+ const validationError = (0, token_file_ops_1.validateNickname)(nickname);
202
+ if (validationError) {
203
+ throw new Error(validationError);
204
+ }
205
+ const existingAccounts = Object.entries(providerAccounts.accounts).map(([id, account]) => ({
206
+ id,
207
+ nickname: account.nickname,
208
+ }));
209
+ if ((0, token_file_ops_1.hasAccountNameConflict)(existingAccounts, nickname, accountId)) {
210
+ throw new Error(`An account with nickname "${nickname}" already exists for ${provider}. ` +
211
+ `Choose a different nickname.`);
212
+ }
160
213
  }
214
+ accountNickname =
215
+ nickname || existingAccount?.nickname || (email ? (0, token_file_ops_1.generateNickname)(email) : accountId);
161
216
  }
162
- accountId = nickname;
163
- accountNickname = nickname;
164
- }
165
- else {
166
- // For other providers: use email as accountId, fallback to filename extraction
167
- accountId = (0, token_file_ops_1.extractAccountIdFromTokenFile)(tokenFile, email);
168
- accountNickname = nickname || (0, token_file_ops_1.generateNickname)(email);
169
- }
170
- const isFirstAccount = Object.keys(providerAccounts.accounts).length === 0;
171
- // Create or update account
172
- const accountMeta = {
173
- email,
174
- nickname: accountNickname,
175
- tokenFile,
176
- createdAt: new Date().toISOString(),
177
- lastUsedAt: new Date().toISOString(),
178
- };
179
- // Include projectId for Antigravity accounts
180
- if (provider === 'agy' && projectId) {
181
- accountMeta.projectId = projectId;
182
- }
183
- providerAccounts.accounts[accountId] = accountMeta;
184
- // Set as default if first account
185
- if (isFirstAccount) {
186
- providerAccounts.default = accountId;
187
- }
188
- saveAccountsRegistry(registry);
189
- return {
190
- id: accountId,
191
- provider,
192
- isDefault: accountId === providerAccounts.default,
193
- email,
194
- nickname: accountNickname,
195
- tokenFile,
196
- createdAt: providerAccounts.accounts[accountId].createdAt,
197
- lastUsedAt: providerAccounts.accounts[accountId].lastUsedAt,
198
- projectId: providerAccounts.accounts[accountId].projectId,
199
- };
217
+ else {
218
+ accountId = (0, token_file_ops_1.extractAccountIdFromTokenFile)(tokenFile, email);
219
+ accountNickname = nickname || (0, token_file_ops_1.generateNickname)(email);
220
+ }
221
+ const isFirstAccount = Object.keys(providerAccounts.accounts).length === 0;
222
+ const existingAccount = providerAccounts.accounts[accountId];
223
+ const accountMeta = {
224
+ email,
225
+ nickname: accountNickname,
226
+ tokenFile,
227
+ createdAt: existingAccount?.createdAt || new Date().toISOString(),
228
+ lastUsedAt: new Date().toISOString(),
229
+ };
230
+ if (provider === 'agy' && projectId) {
231
+ accountMeta.projectId = projectId;
232
+ }
233
+ providerAccounts.accounts[accountId] = accountMeta;
234
+ if (isFirstAccount) {
235
+ providerAccounts.default = accountId;
236
+ }
237
+ return {
238
+ id: accountId,
239
+ provider,
240
+ isDefault: accountId === providerAccounts.default,
241
+ email,
242
+ nickname: accountNickname,
243
+ tokenFile,
244
+ createdAt: providerAccounts.accounts[accountId].createdAt,
245
+ lastUsedAt: providerAccounts.accounts[accountId].lastUsedAt,
246
+ projectId: providerAccounts.accounts[accountId].projectId,
247
+ };
248
+ });
200
249
  }
201
250
  exports.registerAccount = registerAccount;
202
251
  /**
203
252
  * Set default account for a provider
204
253
  */
205
254
  function setDefaultAccount(provider, accountId) {
206
- const registry = loadAccountsRegistry();
207
- const providerAccounts = registry.providers[provider];
208
- if (!providerAccounts || !providerAccounts.accounts[accountId]) {
209
- return false;
210
- }
211
- providerAccounts.default = accountId;
212
- saveAccountsRegistry(registry);
213
- return true;
255
+ return mutateAccountsRegistry((registry) => {
256
+ const providerAccounts = registry.providers[provider];
257
+ if (!providerAccounts || !providerAccounts.accounts[accountId]) {
258
+ return false;
259
+ }
260
+ providerAccounts.default = accountId;
261
+ return true;
262
+ });
214
263
  }
215
264
  exports.setDefaultAccount = setDefaultAccount;
216
265
  /**
@@ -218,22 +267,22 @@ exports.setDefaultAccount = setDefaultAccount;
218
267
  * Moves token file to paused/ subdir so CLIProxyAPI won't discover it
219
268
  */
220
269
  function pauseAccount(provider, accountId) {
221
- const registry = loadAccountsRegistry();
222
- const providerAccounts = registry.providers[provider];
223
- if (!providerAccounts?.accounts[accountId]) {
224
- return false;
225
- }
226
- const accountMeta = providerAccounts.accounts[accountId];
227
- // Skip if already paused (idempotent)
228
- if (accountMeta.paused) {
270
+ return mutateAccountsRegistry((registry) => {
271
+ const providerAccounts = registry.providers[provider];
272
+ if (!providerAccounts?.accounts[accountId]) {
273
+ return false;
274
+ }
275
+ const accountMeta = providerAccounts.accounts[accountId];
276
+ if (accountMeta.paused) {
277
+ return true;
278
+ }
279
+ if (!(0, token_file_ops_1.moveTokenToPaused)(accountMeta.tokenFile)) {
280
+ return false;
281
+ }
282
+ providerAccounts.accounts[accountId].paused = true;
283
+ providerAccounts.accounts[accountId].pausedAt = new Date().toISOString();
229
284
  return true;
230
- }
231
- // Move token file to paused directory (if it exists in auth dir)
232
- (0, token_file_ops_1.moveTokenToPaused)(accountMeta.tokenFile);
233
- providerAccounts.accounts[accountId].paused = true;
234
- providerAccounts.accounts[accountId].pausedAt = new Date().toISOString();
235
- saveAccountsRegistry(registry);
236
- return true;
285
+ });
237
286
  }
238
287
  exports.pauseAccount = pauseAccount;
239
288
  /**
@@ -241,45 +290,44 @@ exports.pauseAccount = pauseAccount;
241
290
  * Moves token file back from paused/ to auth/ so CLIProxyAPI can discover it
242
291
  */
243
292
  function resumeAccount(provider, accountId) {
244
- const registry = loadAccountsRegistry();
245
- const providerAccounts = registry.providers[provider];
246
- if (!providerAccounts?.accounts[accountId]) {
247
- return false;
248
- }
249
- const accountMeta = providerAccounts.accounts[accountId];
250
- // Skip if already active (idempotent)
251
- if (!accountMeta.paused) {
293
+ return mutateAccountsRegistry((registry) => {
294
+ const providerAccounts = registry.providers[provider];
295
+ if (!providerAccounts?.accounts[accountId]) {
296
+ return false;
297
+ }
298
+ const accountMeta = providerAccounts.accounts[accountId];
299
+ if (!accountMeta.paused) {
300
+ return true;
301
+ }
302
+ if (!(0, token_file_ops_1.moveTokenFromPaused)(accountMeta.tokenFile)) {
303
+ return false;
304
+ }
305
+ providerAccounts.accounts[accountId].paused = false;
306
+ providerAccounts.accounts[accountId].pausedAt = undefined;
252
307
  return true;
253
- }
254
- // Move token file back from paused directory (if it exists in paused dir)
255
- (0, token_file_ops_1.moveTokenFromPaused)(accountMeta.tokenFile);
256
- providerAccounts.accounts[accountId].paused = false;
257
- providerAccounts.accounts[accountId].pausedAt = undefined;
258
- saveAccountsRegistry(registry);
259
- return true;
308
+ });
260
309
  }
261
310
  exports.resumeAccount = resumeAccount;
262
311
  /**
263
312
  * Remove an account
264
313
  */
265
314
  function removeAccount(provider, accountId) {
266
- const registry = loadAccountsRegistry();
267
- const providerAccounts = registry.providers[provider];
268
- if (!providerAccounts || !providerAccounts.accounts[accountId]) {
269
- return false;
270
- }
271
- // Delete token file from both auth and paused directories
272
- const tokenFile = providerAccounts.accounts[accountId].tokenFile;
273
- (0, token_file_ops_1.deleteTokenFile)(tokenFile);
274
- // Remove from registry
275
- delete providerAccounts.accounts[accountId];
276
- // Update default if needed
277
- const remainingAccounts = Object.keys(providerAccounts.accounts);
278
- if (providerAccounts.default === accountId && remainingAccounts.length > 0) {
279
- providerAccounts.default = remainingAccounts[0];
280
- }
281
- saveAccountsRegistry(registry);
282
- return true;
315
+ return mutateAccountsRegistry((registry) => {
316
+ const providerAccounts = registry.providers[provider];
317
+ if (!providerAccounts || !providerAccounts.accounts[accountId]) {
318
+ return false;
319
+ }
320
+ const tokenFile = providerAccounts.accounts[accountId].tokenFile;
321
+ if (!(0, token_file_ops_1.deleteTokenFile)(tokenFile)) {
322
+ return false;
323
+ }
324
+ delete providerAccounts.accounts[accountId];
325
+ const remainingAccounts = Object.keys(providerAccounts.accounts);
326
+ if (providerAccounts.default === accountId && remainingAccounts.length > 0) {
327
+ providerAccounts.default = remainingAccounts[0];
328
+ }
329
+ return true;
330
+ });
283
331
  }
284
332
  exports.removeAccount = removeAccount;
285
333
  /**
@@ -290,46 +338,47 @@ function renameAccount(provider, accountId, newNickname) {
290
338
  if (validationError) {
291
339
  throw new Error(validationError);
292
340
  }
293
- const registry = loadAccountsRegistry();
294
- const providerAccounts = registry.providers[provider];
295
- if (!providerAccounts?.accounts[accountId]) {
296
- return false;
297
- }
298
- // Check if nickname is already used by another account
299
- for (const [id, account] of Object.entries(providerAccounts.accounts)) {
300
- if (id !== accountId && account.nickname?.toLowerCase() === newNickname.toLowerCase()) {
341
+ return mutateAccountsRegistry((registry) => {
342
+ const providerAccounts = registry.providers[provider];
343
+ if (!providerAccounts?.accounts[accountId]) {
344
+ return false;
345
+ }
346
+ const existingAccounts = Object.entries(providerAccounts.accounts).map(([id, account]) => ({
347
+ id,
348
+ nickname: account.nickname,
349
+ }));
350
+ if ((0, token_file_ops_1.hasAccountNameConflict)(existingAccounts, newNickname, accountId)) {
301
351
  throw new Error(`Nickname "${newNickname}" is already used by another account`);
302
352
  }
303
- }
304
- providerAccounts.accounts[accountId].nickname = newNickname;
305
- saveAccountsRegistry(registry);
306
- return true;
353
+ providerAccounts.accounts[accountId].nickname = newNickname;
354
+ return true;
355
+ });
307
356
  }
308
357
  exports.renameAccount = renameAccount;
309
358
  /**
310
359
  * Update last used timestamp for an account
311
360
  */
312
361
  function touchAccount(provider, accountId) {
313
- const registry = loadAccountsRegistry();
314
- const providerAccounts = registry.providers[provider];
315
- if (providerAccounts?.accounts[accountId]) {
316
- providerAccounts.accounts[accountId].lastUsedAt = new Date().toISOString();
317
- saveAccountsRegistry(registry);
318
- }
362
+ mutateAccountsRegistry((registry) => {
363
+ const providerAccounts = registry.providers[provider];
364
+ if (providerAccounts?.accounts[accountId]) {
365
+ providerAccounts.accounts[accountId].lastUsedAt = new Date().toISOString();
366
+ }
367
+ });
319
368
  }
320
369
  exports.touchAccount = touchAccount;
321
370
  /**
322
371
  * Update account tier
323
372
  */
324
373
  function setAccountTier(provider, accountId, tier) {
325
- const registry = loadAccountsRegistry();
326
- const providerAccounts = registry.providers[provider];
327
- if (!providerAccounts?.accounts[accountId]) {
328
- return false;
329
- }
330
- providerAccounts.accounts[accountId].tier = tier;
331
- saveAccountsRegistry(registry);
332
- return true;
374
+ return mutateAccountsRegistry((registry) => {
375
+ const providerAccounts = registry.providers[provider];
376
+ if (!providerAccounts?.accounts[accountId]) {
377
+ return false;
378
+ }
379
+ providerAccounts.accounts[accountId].tier = tier;
380
+ return true;
381
+ });
333
382
  }
334
383
  exports.setAccountTier = setAccountTier;
335
384
  /**
@@ -345,166 +394,89 @@ function discoverExistingAccounts() {
345
394
  if (!fs.existsSync(authDir)) {
346
395
  return;
347
396
  }
348
- const registry = loadAccountsRegistry();
349
397
  const files = fs.readdirSync(authDir);
350
- // Track whether any accounts were discovered (to avoid saving empty registry)
351
- let discoveredCount = 0;
352
- for (const file of files) {
353
- if (!file.endsWith('.json'))
354
- continue;
355
- const filePath = path.join(authDir, file);
356
- try {
357
- const content = fs.readFileSync(filePath, 'utf-8');
358
- const data = JSON.parse(content);
359
- // Skip if no type field
360
- if (!data.type)
398
+ mutateAccountsRegistry((registry) => {
399
+ syncRegistryWithTokenFiles(registry);
400
+ for (const file of files) {
401
+ if (!file.endsWith('.json'))
361
402
  continue;
362
- // Build reverse mapping from PROVIDER_TYPE_VALUES (type value -> provider)
363
- // e.g., "antigravity" -> "agy", "kiro" -> "kiro", "codewhisperer" -> "kiro"
364
- const typeValue = data.type.toLowerCase();
365
- let provider;
366
- for (const [prov, typeValues] of Object.entries(auth_types_1.PROVIDER_TYPE_VALUES)) {
367
- if (typeValues.includes(typeValue)) {
368
- provider = prov;
369
- break;
403
+ const filePath = path.join(authDir, file);
404
+ try {
405
+ const content = fs.readFileSync(filePath, 'utf-8');
406
+ const data = JSON.parse(content);
407
+ if (!data.type)
408
+ continue;
409
+ const typeValue = data.type.toLowerCase();
410
+ let provider;
411
+ for (const [prov, typeValues] of Object.entries(auth_types_1.PROVIDER_TYPE_VALUES)) {
412
+ if (typeValues.includes(typeValue)) {
413
+ provider = prov;
414
+ break;
415
+ }
370
416
  }
371
- }
372
- // Skip if unknown provider type
373
- if (!provider) {
374
- continue;
375
- }
376
- // Extract email if available, fallback to filename-based ID
377
- let email = data.email || undefined;
378
- // Fallback: extract email from filename (e.g., "kiro-google-user@example.com.json")
379
- if (!email && file.includes('@')) {
380
- const match = file.match(/([^-]+@[^.]+\.[^.]+)(?=\.json$)/);
381
- if (match) {
382
- email = match[1];
417
+ if (!provider) {
418
+ continue;
383
419
  }
384
- }
385
- // Initialize provider section if needed
386
- if (!registry.providers[provider]) {
387
- registry.providers[provider] = {
388
- default: 'default',
389
- accounts: {},
390
- };
391
- }
392
- const providerAccounts = registry.providers[provider];
393
- if (!providerAccounts)
394
- continue;
395
- // Skip if token file already registered (under any accountId)
396
- const existingTokenFiles = Object.values(providerAccounts.accounts).map((a) => a.tokenFile);
397
- if (existingTokenFiles.includes(file)) {
398
- // Token file exists - check if we need to update projectId for agy accounts
399
- const projectIdValue = typeof data.project_id === 'string' && data.project_id.trim()
400
- ? data.project_id.trim()
401
- : null;
402
- if (provider === 'agy' && projectIdValue) {
403
- const existingEntry = Object.entries(providerAccounts.accounts).find(([, meta]) => meta.tokenFile === file);
404
- // Update if missing or changed
405
- if (existingEntry && existingEntry[1].projectId !== projectIdValue) {
406
- existingEntry[1].projectId = projectIdValue;
407
- discoveredCount++; // Count projectId updates as changes
420
+ let email = data.email || undefined;
421
+ if (!email && file.includes('@')) {
422
+ const match = file.match(/([^-]+@[^.]+\.[^.]+)(?=\.json$)/);
423
+ if (match) {
424
+ email = match[1];
408
425
  }
409
426
  }
410
- continue;
411
- }
412
- // Determine accountId based on provider type
413
- let accountId;
414
- if (types_1.PROVIDERS_WITHOUT_EMAIL.includes(provider) && !email) {
415
- // For kiro/ghcp without email: extract from filename or generate unique
416
- // Pattern: kiro-github-ABC123.json -> github-ABC123
417
- const filenameId = (0, token_file_ops_1.extractAccountIdFromTokenFile)(file, undefined);
418
- if (filenameId !== 'default') {
419
- accountId = filenameId;
427
+ if (!registry.providers[provider]) {
428
+ registry.providers[provider] = {
429
+ default: 'default',
430
+ accounts: {},
431
+ };
420
432
  }
421
- else {
422
- // Generate unique ID: provider + incrementing index
423
- let index = 1;
424
- while (providerAccounts.accounts[`${provider}-${index}`]) {
425
- index++;
433
+ const providerAccounts = registry.providers[provider];
434
+ if (!providerAccounts)
435
+ continue;
436
+ const existingTokenFiles = Object.values(providerAccounts.accounts).map((a) => a.tokenFile);
437
+ if (existingTokenFiles.includes(file)) {
438
+ const projectIdValue = typeof data.project_id === 'string' && data.project_id.trim()
439
+ ? data.project_id.trim()
440
+ : null;
441
+ if (provider === 'agy' && projectIdValue) {
442
+ const existingEntry = Object.entries(providerAccounts.accounts).find(([, meta]) => meta.tokenFile === file);
443
+ if (existingEntry && existingEntry[1].projectId !== projectIdValue) {
444
+ existingEntry[1].projectId = projectIdValue;
445
+ }
426
446
  }
427
- accountId = `${provider}-${index}`;
447
+ continue;
428
448
  }
429
- }
430
- else {
431
- // For providers with email: use email or filename extraction
432
- accountId = (0, token_file_ops_1.extractAccountIdFromTokenFile)(file, email);
433
- }
434
- // Skip if account already registered
435
- if (providerAccounts.accounts[accountId]) {
436
- continue;
437
- }
438
- // Set as default if first account
439
- if (Object.keys(providerAccounts.accounts).length === 0) {
440
- providerAccounts.default = accountId;
441
- }
442
- // Get file stats for creation time
443
- const stats = fs.statSync(filePath);
444
- // Register account with auto-generated nickname
445
- // Use mtime as lastUsedAt (when token was last modified = last auth/refresh)
446
- const lastModified = stats.mtime || stats.birthtime || new Date();
447
- const accountMeta = {
448
- email,
449
- nickname: (0, token_file_ops_1.generateNickname)(email),
450
- tokenFile: file,
451
- createdAt: stats.birthtime?.toISOString() || new Date().toISOString(),
452
- lastUsedAt: lastModified.toISOString(),
453
- };
454
- // Read project_id for Antigravity accounts (read-only field from auth token)
455
- const discoveredProjectId = typeof data.project_id === 'string' && data.project_id.trim()
456
- ? data.project_id.trim()
457
- : null;
458
- if (provider === 'agy' && discoveredProjectId) {
459
- accountMeta.projectId = discoveredProjectId;
460
- }
461
- providerAccounts.accounts[accountId] = accountMeta;
462
- discoveredCount++;
463
- }
464
- catch {
465
- // Skip invalid files
466
- continue;
467
- }
468
- }
469
- // Only save if at least one account was discovered or updated
470
- // This prevents creating accounts.json with empty provider sections
471
- if (discoveredCount === 0) {
472
- return;
473
- }
474
- // Reload-merge pattern: reduce race condition with concurrent OAuth registration
475
- // Reload fresh registry and merge discovered accounts (fresh registry wins on conflicts)
476
- const freshRegistry = loadAccountsRegistry();
477
- for (const [providerName, discovered] of Object.entries(registry.providers)) {
478
- if (!discovered)
479
- continue;
480
- // Skip empty provider sections (no accounts discovered)
481
- if (Object.keys(discovered.accounts).length === 0)
482
- continue;
483
- const prov = providerName;
484
- if (!freshRegistry.providers[prov]) {
485
- freshRegistry.providers[prov] = discovered;
486
- }
487
- else {
488
- // Merge accounts, preferring fresh registry's existing entries but updating projectId
489
- const freshProviderAccounts = freshRegistry.providers[prov];
490
- if (!freshProviderAccounts)
491
- continue;
492
- for (const [id, meta] of Object.entries(discovered.accounts)) {
493
- if (!freshProviderAccounts.accounts[id]) {
494
- freshProviderAccounts.accounts[id] = meta;
495
- // Set default if none exists
496
- if (!freshProviderAccounts.default || freshProviderAccounts.default === 'default') {
497
- freshProviderAccounts.default = id;
498
- }
449
+ const accountId = types_1.PROVIDERS_WITHOUT_EMAIL.includes(provider) && !email
450
+ ? (0, token_file_ops_1.deriveNoEmailProviderAccountId)(provider, file, providerAccounts.accounts)
451
+ : (0, token_file_ops_1.extractAccountIdFromTokenFile)(file, email);
452
+ if (providerAccounts.accounts[accountId]) {
453
+ continue;
499
454
  }
500
- else if (meta.projectId && !freshProviderAccounts.accounts[id].projectId) {
501
- // Update existing account with projectId if discovered from auth file
502
- freshProviderAccounts.accounts[id].projectId = meta.projectId;
455
+ if (Object.keys(providerAccounts.accounts).length === 0) {
456
+ providerAccounts.default = accountId;
503
457
  }
458
+ const stats = fs.statSync(filePath);
459
+ const lastModified = stats.mtime || stats.birthtime || new Date();
460
+ const accountMeta = {
461
+ email,
462
+ nickname: email ? (0, token_file_ops_1.generateNickname)(email) : accountId,
463
+ tokenFile: file,
464
+ createdAt: stats.birthtime?.toISOString() || new Date().toISOString(),
465
+ lastUsedAt: lastModified.toISOString(),
466
+ };
467
+ const discoveredProjectId = typeof data.project_id === 'string' && data.project_id.trim()
468
+ ? data.project_id.trim()
469
+ : null;
470
+ if (provider === 'agy' && discoveredProjectId) {
471
+ accountMeta.projectId = discoveredProjectId;
472
+ }
473
+ providerAccounts.accounts[accountId] = accountMeta;
474
+ }
475
+ catch {
476
+ continue;
504
477
  }
505
478
  }
506
- }
507
- saveAccountsRegistry(freshRegistry);
479
+ });
508
480
  }
509
481
  exports.discoverExistingAccounts = discoverExistingAccounts;
510
482
  //# sourceMappingURL=registry.js.map