Skip to content

Commit 5e0bcc1

Browse files
committed
feat!: rename package and migrate runtime storage
1 parent 2306188 commit 5e0bcc1

23 files changed

Lines changed: 223 additions & 140 deletions

.codex-plugin/plugin.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
2-
"name": "oc-chatgpt-multi-auth",
2+
"name": "oc-codex-multi-auth",
33
"version": "5.4.9",
4-
"description": "Install and operate oc-chatgpt-multi-auth for OpenCode with ChatGPT Plus/Pro OAuth, GPT-5 and Codex model presets, and multi-account failover.",
4+
"description": "Install and operate oc-codex-multi-auth for OpenCode with Codex-first GPT-5 workflows, ChatGPT Plus/Pro OAuth, and multi-account failover.",
55
"skills": "./skills/"
66
}

config/minimal-opencode.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"$schema": "https://opencode.ai/config.json",
3-
"plugin": ["oc-chatgpt-multi-auth"],
3+
"plugin": ["oc-codex-multi-auth"],
44

55
"provider": {
66
"openai": {

config/opencode-legacy.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://opencode.ai/config.json",
33
"plugin": [
4-
"oc-chatgpt-multi-auth"
4+
"oc-codex-multi-auth"
55
],
66
"provider": {
77
"openai": {

config/opencode-modern.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://opencode.ai/config.json",
33
"plugin": [
4-
"oc-chatgpt-multi-auth"
4+
"oc-codex-multi-auth"
55
],
66
"provider": {
77
"openai": {

index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
*
2020
* @license MIT (see LICENSE file)
2121
* @author numman-ali
22-
* @repository https://github.com/ndycode/oc-chatgpt-multi-auth
22+
* @repository https://github.com/ndycode/oc-codex-multi-auth
2323
2424
*/
2525

@@ -240,7 +240,7 @@ function upsertFlaggedAccountRecord(
240240
* @example
241241
* ```json
242242
* {
243-
* "plugin": ["oc-chatgpt-multi-auth"],
243+
* "plugin": ["oc-codex-multi-auth"],
244244
245245
* "model": "openai/gpt-5-codex"
246246
* }

lib/auto-update-checker.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { createLogger } from "./logger.js";
55

66
const log = createLogger("update-checker");
77

8-
const PACKAGE_NAME = "oc-chatgpt-multi-auth";
8+
const PACKAGE_NAME = "oc-codex-multi-auth";
99
const NPM_REGISTRY_URL = `https://registry.npmjs.org/${PACKAGE_NAME}/latest`;
1010
const CACHE_DIR = join(homedir(), ".opencode", "cache");
1111
const CACHE_FILE = join(CACHE_DIR, "update-check-cache.json");

lib/constants.ts

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,21 @@
33
* Centralized for easy maintenance and configuration
44
*/
55

6+
/** Published package identifier used across runtime messages and install flows */
7+
export const PACKAGE_NAME = "oc-codex-multi-auth";
8+
9+
/** Previous published package identifier kept for installer and storage migration */
10+
export const LEGACY_PACKAGE_NAME = "oc-chatgpt-multi-auth";
11+
612
/** Plugin identifier for logging and error messages */
7-
export const PLUGIN_NAME = "openai-codex-plugin";
13+
export const PLUGIN_NAME = PACKAGE_NAME;
14+
15+
/** Storage file names for active and legacy account data */
16+
export const ACCOUNTS_FILE_NAME = "oc-codex-multi-auth-accounts.json";
17+
export const LEGACY_ACCOUNTS_FILE_NAME = "openai-codex-accounts.json";
18+
export const FLAGGED_ACCOUNTS_FILE_NAME = "oc-codex-multi-auth-flagged-accounts.json";
19+
export const LEGACY_FLAGGED_ACCOUNTS_FILE_NAME = "openai-codex-flagged-accounts.json";
20+
export const LEGACY_BLOCKED_ACCOUNTS_FILE_NAME = "openai-codex-blocked-accounts.json";
821

922
/** Base URL for ChatGPT backend API */
1023
export const CODEX_BASE_URL = "https://chatgpt.com/backend-api";
@@ -74,10 +87,10 @@ export const PLATFORM_OPENERS = {
7487

7588
/** OAuth authorization labels */
7689
export const AUTH_LABELS = {
77-
OAUTH: "ChatGPT Plus/Pro MULTI (Codex Subscription)",
78-
OAUTH_DEVICE_CODE: "ChatGPT Plus/Pro MULTI (Device Code)",
79-
OAUTH_MANUAL: "ChatGPT Plus/Pro MULTI (Manual URL Paste)",
80-
API_KEY: "Manually enter API Key MULTI",
90+
OAUTH: "Codex OAuth (ChatGPT Plus/Pro)",
91+
OAUTH_DEVICE_CODE: "Codex OAuth (Device Code)",
92+
OAUTH_MANUAL: "Codex OAuth (Manual URL Paste)",
93+
API_KEY: "Manual API Key (Advanced)",
8194
INSTRUCTIONS:
8295
"A browser window should open. If it doesn't, copy the URL and open it manually.",
8396
INSTRUCTIONS_MANUAL:

lib/oauth-success.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@
548548
<div class="terminal-button red"></div>
549549
<div class="terminal-button yellow"></div>
550550
<div class="terminal-button green"></div>
551-
<div class="terminal-title">oc-chatgpt-multi-auth — OAuth Authentication</div>
551+
<div class="terminal-title">oc-codex-multi-auth — OAuth Authentication</div>
552552
</div>
553553

554554
<div class="status-box">

lib/recovery/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/**
2-
* Session recovery module for oc-chatgpt-multi-auth.
2+
* Session recovery module for oc-codex-multi-auth.
33
*
44
* Provides recovery from:
55
* - tool_result_missing: Interrupted tool executions

lib/storage.ts

Lines changed: 106 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,14 @@
11
import { promises as fs, existsSync } from "node:fs";
22
import { randomBytes } from "node:crypto";
33
import { basename, dirname, join } from "node:path";
4-
import { ACCOUNT_LIMITS } from "./constants.js";
4+
import {
5+
ACCOUNT_LIMITS,
6+
ACCOUNTS_FILE_NAME,
7+
FLAGGED_ACCOUNTS_FILE_NAME,
8+
LEGACY_ACCOUNTS_FILE_NAME,
9+
LEGACY_BLOCKED_ACCOUNTS_FILE_NAME,
10+
LEGACY_FLAGGED_ACCOUNTS_FILE_NAME,
11+
} from "./constants.js";
512
import { createLogger } from "./logger.js";
613
import { MODEL_FAMILIES, type ModelFamily } from "./prompts/codex.js";
714
import { AnyAccountStorageSchema, getValidationErrors } from "./schemas.js";
@@ -19,10 +26,6 @@ import {
1926
export type { CooldownReason, RateLimitStateV3, AccountMetadataV1, AccountStorageV1, AccountMetadataV3, AccountStorageV3 };
2027

2128
const log = createLogger("storage");
22-
const ACCOUNTS_FILE_NAME = "openai-codex-accounts.json";
23-
const FLAGGED_ACCOUNTS_FILE_NAME = "openai-codex-flagged-accounts.json";
24-
const LEGACY_FLAGGED_ACCOUNTS_FILE_NAME = "openai-codex-blocked-accounts.json";
25-
2629
export interface FlaggedAccountMetadataV1 extends AccountMetadataV3 {
2730
flaggedAt: number;
2831
flaggedReason?: string;
@@ -283,7 +286,7 @@ export function setStoragePath(projectPath: string | null): void {
283286
if (projectRoot) {
284287
currentProjectRoot = projectRoot;
285288
currentStoragePath = join(getProjectGlobalConfigDir(projectRoot), ACCOUNTS_FILE_NAME);
286-
currentLegacyProjectStoragePath = join(getProjectConfigDir(projectRoot), ACCOUNTS_FILE_NAME);
289+
currentLegacyProjectStoragePath = join(getProjectConfigDir(projectRoot), LEGACY_ACCOUNTS_FILE_NAME);
287290
} else {
288291
currentStoragePath = null;
289292
currentLegacyProjectStoragePath = null;
@@ -316,55 +319,66 @@ function getLegacyFlaggedAccountsPath(): string {
316319
return join(dirname(getStoragePath()), LEGACY_FLAGGED_ACCOUNTS_FILE_NAME);
317320
}
318321

319-
async function migrateLegacyProjectStorageIfNeeded(
320-
persist: (storage: AccountStorageV3) => Promise<void> = saveAccounts,
322+
function getLegacyBlockedAccountsPath(): string {
323+
return join(dirname(getStoragePath()), LEGACY_BLOCKED_ACCOUNTS_FILE_NAME);
324+
}
325+
326+
async function migrateStorageFileIfNeeded(
327+
legacyPath: string | null,
328+
nextPath: string,
329+
persist: (storage: AccountStorageV3) => Promise<void>,
330+
label: string,
321331
): Promise<AccountStorageV3 | null> {
322-
if (
323-
!currentStoragePath ||
324-
!currentLegacyProjectStoragePath ||
325-
currentLegacyProjectStoragePath === currentStoragePath ||
326-
!existsSync(currentLegacyProjectStoragePath)
327-
) {
332+
if (!legacyPath || legacyPath === nextPath || !existsSync(legacyPath)) {
328333
return null;
329334
}
330335

331336
try {
332-
const legacyContent = await fs.readFile(currentLegacyProjectStoragePath, "utf-8");
337+
const legacyContent = await fs.readFile(legacyPath, "utf-8");
333338
const legacyData = JSON.parse(legacyContent) as unknown;
334339
const normalized = normalizeAccountStorage(legacyData);
335340
if (!normalized) return null;
336341

337342
await persist(normalized);
338343
try {
339-
await fs.unlink(currentLegacyProjectStoragePath);
340-
log.info("Removed legacy project account storage file after migration", {
341-
path: currentLegacyProjectStoragePath,
342-
});
344+
await fs.unlink(legacyPath);
345+
log.info(`Removed legacy ${label} after migration`, { path: legacyPath });
343346
} catch (unlinkError) {
344347
const code = (unlinkError as NodeJS.ErrnoException).code;
345348
if (code !== "ENOENT") {
346-
log.warn("Failed to remove legacy project account storage file after migration", {
347-
path: currentLegacyProjectStoragePath,
349+
log.warn(`Failed to remove legacy ${label} after migration`, {
350+
path: legacyPath,
348351
error: String(unlinkError),
349352
});
350353
}
351354
}
352-
log.info("Migrated legacy project account storage", {
353-
from: currentLegacyProjectStoragePath,
354-
to: currentStoragePath,
355+
log.info(`Migrated legacy ${label}`, {
356+
from: legacyPath,
357+
to: nextPath,
355358
accounts: normalized.accounts.length,
356359
});
357360
return normalized;
358361
} catch (error) {
359-
log.warn("Failed to migrate legacy project account storage", {
360-
from: currentLegacyProjectStoragePath,
361-
to: currentStoragePath,
362+
log.warn(`Failed to migrate legacy ${label}`, {
363+
from: legacyPath,
364+
to: nextPath,
362365
error: String(error),
363366
});
364367
return null;
365368
}
366369
}
367370

371+
async function migrateLegacyProjectStorageIfNeeded(
372+
persist: (storage: AccountStorageV3) => Promise<void> = saveAccounts,
373+
): Promise<AccountStorageV3 | null> {
374+
return migrateStorageFileIfNeeded(
375+
currentLegacyProjectStoragePath,
376+
getStoragePath(),
377+
persist,
378+
"project account storage",
379+
);
380+
}
381+
368382
function selectNewestAccount<T extends AccountLike>(
369383
current: T | undefined,
370384
candidate: T,
@@ -708,6 +722,24 @@ function getGlobalAccountsStoragePath(): string {
708722
return join(getConfigDir(), ACCOUNTS_FILE_NAME);
709723
}
710724

725+
function getLegacyGlobalAccountsStoragePath(): string {
726+
return join(getConfigDir(), LEGACY_ACCOUNTS_FILE_NAME);
727+
}
728+
729+
async function migrateLegacyGlobalStorageIfNeeded(): Promise<AccountStorageV3 | null> {
730+
const nextPath = getGlobalAccountsStoragePath();
731+
const persistGlobalStorage = async (storage: AccountStorageV3): Promise<void> => {
732+
await writeAccountsToPathUnlocked(nextPath, storage);
733+
};
734+
735+
return migrateStorageFileIfNeeded(
736+
getLegacyGlobalAccountsStoragePath(),
737+
nextPath,
738+
persistGlobalStorage,
739+
"global account storage",
740+
);
741+
}
742+
711743
/**
712744
* Returns true when project-scoped storage is active and a global fallback is meaningful.
713745
*/
@@ -724,6 +756,11 @@ async function loadGlobalAccountsFallback(): Promise<AccountStorageV3 | null> {
724756
return null;
725757
}
726758

759+
const migrated = await migrateLegacyGlobalStorageIfNeeded();
760+
if (migrated) {
761+
return migrated;
762+
}
763+
727764
const globalStoragePath = getGlobalAccountsStoragePath();
728765
if (globalStoragePath === currentStoragePath) {
729766
return null;
@@ -802,6 +839,13 @@ async function loadAccountsInternal(
802839
? await migrateLegacyProjectStorageIfNeeded(persistMigration)
803840
: null;
804841
if (migrated) return migrated;
842+
if (!shouldUseProjectGlobalFallback()) {
843+
const migratedGlobal = persistMigration
844+
? await migrateLegacyGlobalStorageIfNeeded()
845+
: null;
846+
if (migratedGlobal) return migratedGlobal;
847+
return null;
848+
}
805849
const globalFallback = await loadGlobalAccountsFallback();
806850
if (!globalFallback) return null;
807851

@@ -847,8 +891,7 @@ async function loadAccountsInternal(
847891
* Writes account storage without acquiring the outer storage mutex.
848892
* Callers must already be inside withStorageLock when using this helper directly.
849893
*/
850-
async function saveAccountsUnlocked(storage: AccountStorageV3): Promise<void> {
851-
const path = getStoragePath();
894+
async function writeAccountsToPathUnlocked(path: string, storage: AccountStorageV3): Promise<void> {
852895
const uniqueSuffix = `${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
853896
const tempPath = `${path}.${uniqueSuffix}.tmp`;
854897

@@ -897,6 +940,10 @@ async function saveAccountsUnlocked(storage: AccountStorageV3): Promise<void> {
897940
}
898941
}
899942

943+
async function saveAccountsUnlocked(storage: AccountStorageV3): Promise<void> {
944+
await writeAccountsToPathUnlocked(getStoragePath(), storage);
945+
}
946+
900947
/**
901948
* Executes a read-modify-write transaction under the storage lock and exposes
902949
* an unlocked persist callback so nested save operations do not deadlock.
@@ -1057,37 +1104,40 @@ async function loadFlaggedAccountsUnlocked(
10571104
}
10581105
}
10591106

1060-
const legacyPath = getLegacyFlaggedAccountsPath();
1061-
if (!existsSync(legacyPath)) {
1062-
return empty;
1063-
}
1064-
1065-
try {
1066-
const legacyContent = await fs.readFile(legacyPath, "utf-8");
1067-
const legacyData = JSON.parse(legacyContent) as unknown;
1068-
const migrated = normalizeFlaggedStorage(legacyData);
1069-
if (migrated.accounts.length > 0) {
1070-
await saveUnlocked(migrated);
1107+
for (const legacyPath of [getLegacyFlaggedAccountsPath(), getLegacyBlockedAccountsPath()]) {
1108+
if (!existsSync(legacyPath)) {
1109+
continue;
10711110
}
1111+
10721112
try {
1073-
await fs.unlink(legacyPath);
1074-
} catch {
1075-
// Best effort cleanup.
1113+
const legacyContent = await fs.readFile(legacyPath, "utf-8");
1114+
const legacyData = JSON.parse(legacyContent) as unknown;
1115+
const migrated = normalizeFlaggedStorage(legacyData);
1116+
if (migrated.accounts.length > 0) {
1117+
await saveUnlocked(migrated);
1118+
}
1119+
try {
1120+
await fs.unlink(legacyPath);
1121+
} catch {
1122+
// Best effort cleanup.
1123+
}
1124+
log.info("Migrated legacy flagged account storage", {
1125+
from: legacyPath,
1126+
to: path,
1127+
accounts: migrated.accounts.length,
1128+
});
1129+
return migrated;
1130+
} catch (error) {
1131+
log.error("Failed to migrate legacy flagged account storage", {
1132+
from: legacyPath,
1133+
to: path,
1134+
error: String(error),
1135+
});
1136+
return empty;
10761137
}
1077-
log.info("Migrated legacy flagged account storage", {
1078-
from: legacyPath,
1079-
to: path,
1080-
accounts: migrated.accounts.length,
1081-
});
1082-
return migrated;
1083-
} catch (error) {
1084-
log.error("Failed to migrate legacy flagged account storage", {
1085-
from: legacyPath,
1086-
to: path,
1087-
error: String(error),
1088-
});
1089-
return empty;
10901138
}
1139+
1140+
return empty;
10911141
}
10921142

10931143
export async function loadFlaggedAccounts(): Promise<FlaggedAccountStorageV1> {

0 commit comments

Comments
 (0)