@victor-software-house/pi-multicodex 2.1.2 → 2.1.4

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.
@@ -153,6 +153,7 @@ export class AccountManager {
153
153
  creds: OAuthCredentials,
154
154
  options?: {
155
155
  importSource?: "pi-openai-codex";
156
+ importMode?: "linked" | "synthetic";
156
157
  importFingerprint?: string;
157
158
  },
158
159
  ): boolean {
@@ -182,6 +183,10 @@ export class AccountManager {
182
183
  account.importSource = options.importSource;
183
184
  changed = true;
184
185
  }
186
+ if (options?.importMode && account.importMode !== options.importMode) {
187
+ account.importMode = options.importMode;
188
+ changed = true;
189
+ }
185
190
  if (
186
191
  options?.importFingerprint &&
187
192
  account.importFingerprint !== options.importFingerprint
@@ -202,6 +207,7 @@ export class AccountManager {
202
207
  creds: OAuthCredentials,
203
208
  options?: {
204
209
  importSource?: "pi-openai-codex";
210
+ importMode?: "linked" | "synthetic";
205
211
  importFingerprint?: string;
206
212
  preserveActive?: boolean;
207
213
  },
@@ -221,7 +227,13 @@ export class AccountManager {
221
227
  ) {
222
228
  changed = this.updateAccountEmail(duplicate, email) || changed;
223
229
  }
224
- changed = this.applyCredentials(target, creds, options) || changed;
230
+ changed =
231
+ this.applyCredentials(target, creds, {
232
+ ...options,
233
+ importMode:
234
+ options?.importMode ??
235
+ (duplicate?.importMode === "synthetic" ? "linked" : undefined),
236
+ }) || changed;
225
237
  } else {
226
238
  target = {
227
239
  email,
@@ -231,6 +243,7 @@ export class AccountManager {
231
243
  accountId:
232
244
  typeof creds.accountId === "string" ? creds.accountId : undefined,
233
245
  importSource: options?.importSource,
246
+ importMode: options?.importMode,
234
247
  importFingerprint: options?.importFingerprint,
235
248
  };
236
249
  this.data.accounts.push(target);
@@ -300,12 +313,33 @@ export class AccountManager {
300
313
  );
301
314
  }
302
315
 
316
+ private clearImportedLink(account: Account): boolean {
317
+ let changed = false;
318
+ if (account.importSource) {
319
+ account.importSource = undefined;
320
+ changed = true;
321
+ }
322
+ if (account.importMode) {
323
+ account.importMode = undefined;
324
+ changed = true;
325
+ }
326
+ if (account.importFingerprint) {
327
+ account.importFingerprint = undefined;
328
+ changed = true;
329
+ }
330
+ return changed;
331
+ }
332
+
303
333
  async syncImportedOpenAICodexAuth(): Promise<boolean> {
304
334
  const imported = await loadImportedOpenAICodexAuth();
305
335
  if (!imported) return false;
306
336
 
307
337
  const existingImported = this.getImportedAccount();
308
- if (existingImported?.importFingerprint === imported.fingerprint) {
338
+ if (
339
+ existingImported?.importFingerprint === imported.fingerprint &&
340
+ (existingImported.importMode !== "synthetic" ||
341
+ existingImported.email === imported.identifier)
342
+ ) {
309
343
  return false;
310
344
  }
311
345
 
@@ -314,25 +348,20 @@ export class AccountManager {
314
348
  existingImported?.email,
315
349
  );
316
350
  if (matchingAccount) {
317
- const wasActiveImported =
318
- existingImported && this.data.activeEmail === existingImported.email;
319
- const wasManualImported =
320
- existingImported && this.manualEmail === existingImported.email;
321
351
  let changed = this.applyCredentials(
322
352
  matchingAccount,
323
353
  imported.credentials,
324
354
  {
325
355
  importSource: "pi-openai-codex",
356
+ importMode: "linked",
326
357
  importFingerprint: imported.fingerprint,
327
358
  },
328
359
  );
329
360
  if (existingImported && existingImported !== matchingAccount) {
330
- changed = this.removeAccountRecord(existingImported) || changed;
331
- if (wasActiveImported) {
332
- this.data.activeEmail = matchingAccount.email;
333
- }
334
- if (wasManualImported) {
335
- this.manualEmail = matchingAccount.email;
361
+ if (existingImported.importMode === "synthetic") {
362
+ changed = this.removeAccountRecord(existingImported) || changed;
363
+ } else {
364
+ changed = this.clearImportedLink(existingImported) || changed;
336
365
  }
337
366
  }
338
367
  if (changed) {
@@ -342,7 +371,7 @@ export class AccountManager {
342
371
  return changed;
343
372
  }
344
373
 
345
- if (existingImported) {
374
+ if (existingImported?.importMode === "synthetic") {
346
375
  const target = this.getAccount(imported.identifier);
347
376
  let changed = false;
348
377
  if (!target && existingImported.email !== imported.identifier) {
@@ -354,6 +383,7 @@ export class AccountManager {
354
383
  changed =
355
384
  this.applyCredentials(existingImported, imported.credentials, {
356
385
  importSource: "pi-openai-codex",
386
+ importMode: "synthetic",
357
387
  importFingerprint: imported.fingerprint,
358
388
  }) || changed;
359
389
  if (changed) {
@@ -363,8 +393,17 @@ export class AccountManager {
363
393
  return changed;
364
394
  }
365
395
 
396
+ if (existingImported) {
397
+ const changed = this.clearImportedLink(existingImported);
398
+ if (changed) {
399
+ this.save();
400
+ this.notifyStateChanged();
401
+ }
402
+ }
403
+
366
404
  this.addOrUpdateAccount(imported.identifier, imported.credentials, {
367
405
  importSource: "pi-openai-codex",
406
+ importMode: "synthetic",
368
407
  importFingerprint: imported.fingerprint,
369
408
  preserveActive: true,
370
409
  });
package/auth.ts CHANGED
@@ -45,7 +45,44 @@ function getRequiredString(
45
45
  return typeof value === "string" && value.trim() ? value.trim() : undefined;
46
46
  }
47
47
 
48
- function createImportedIdentifier(accountId: string): string {
48
+ function decodeJwtPayload(token: string): Record<string, unknown> | undefined {
49
+ const parts = token.split(".");
50
+ if (parts.length !== 3) return undefined;
51
+ const payload = parts[1];
52
+ if (!payload) return undefined;
53
+ try {
54
+ const normalized = payload.replace(/-/g, "+").replace(/_/g, "/");
55
+ const padded = normalized.padEnd(
56
+ normalized.length + ((4 - (normalized.length % 4)) % 4),
57
+ "=",
58
+ );
59
+ const decoded = Buffer.from(padded, "base64").toString("utf8");
60
+ const parsed = JSON.parse(decoded) as unknown;
61
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
62
+ return undefined;
63
+ }
64
+ return parsed as Record<string, unknown>;
65
+ } catch {
66
+ return undefined;
67
+ }
68
+ }
69
+
70
+ function getProfileEmail(accessToken: string): string | undefined {
71
+ const payload = decodeJwtPayload(accessToken);
72
+ const profile = payload?.["https://api.openai.com/profile"];
73
+ if (!profile || typeof profile !== "object" || Array.isArray(profile)) {
74
+ return undefined;
75
+ }
76
+ const email = (profile as Record<string, unknown>).email;
77
+ return typeof email === "string" && email.trim() ? email.trim() : undefined;
78
+ }
79
+
80
+ function createImportedIdentifier(
81
+ accessToken: string,
82
+ accountId: string,
83
+ ): string {
84
+ const email = getProfileEmail(accessToken);
85
+ if (email) return email;
49
86
  return `${IMPORTED_ACCOUNT_PREFIX} ${accountId.slice(0, 8)}`;
50
87
  }
51
88
 
@@ -84,7 +121,7 @@ export function parseImportedOpenAICodexAuth(
84
121
  accountId,
85
122
  };
86
123
  return {
87
- identifier: createImportedIdentifier(accountId ?? "default"),
124
+ identifier: createImportedIdentifier(access, accountId ?? "default"),
88
125
  fingerprint: createFingerprint({ access, refresh, expires, accountId }),
89
126
  credentials,
90
127
  };
package/commands.ts CHANGED
@@ -99,7 +99,7 @@ function formatAccountStatusLine(
99
99
  const quotaHit =
100
100
  account.quotaExhaustedUntil && account.quotaExhaustedUntil > Date.now();
101
101
  const untouched = isUsageUntouched(usage) ? "untouched" : null;
102
- const imported = account.importSource ? "linked-auth" : null;
102
+ const imported = account.importSource ? "pi auth" : null;
103
103
  const reauth = account.needsReauth ? "needs reauth" : null;
104
104
  const tags = [
105
105
  active?.email === account.email ? "active" : null,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@victor-software-house/pi-multicodex",
3
- "version": "2.1.2",
3
+ "version": "2.1.4",
4
4
  "description": "Codex account rotation extension for pi",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/storage.ts CHANGED
@@ -11,6 +11,7 @@ export interface Account {
11
11
  lastUsed?: number;
12
12
  quotaExhaustedUntil?: number;
13
13
  importSource?: "pi-openai-codex";
14
+ importMode?: "linked" | "synthetic";
14
15
  importFingerprint?: string;
15
16
  needsReauth?: boolean;
16
17
  }