AI_PROJECT / src /scripts /migrate-codex-acc-pool.mjs
chenchenaoyang's picture
Deploy HF Space with dataset backend
476094d verified
#!/usr/bin/env node
import fs from "node:fs/promises";
import path from "node:path";
const DEFAULT_TOKENS_DIR = path.resolve(process.cwd(), "acc_pool");
function parseArgs(argv) {
const args = {};
for (const part of argv) {
if (!part.startsWith("--")) continue;
const raw = part.slice(2);
const idx = raw.indexOf("=");
if (idx === -1) {
args[raw] = "true";
continue;
}
args[raw.slice(0, idx)] = raw.slice(idx + 1);
}
return args;
}
function nowStamp() {
const date = new Date();
const pad = (value) => String(value).padStart(2, "0");
return [
date.getFullYear(),
pad(date.getMonth() + 1),
pad(date.getDate()),
"-",
pad(date.getHours()),
pad(date.getMinutes()),
pad(date.getSeconds()),
].join("");
}
function normalizeEntries(raw) {
if (Array.isArray(raw)) {
return raw.filter((entry) => entry && typeof entry === "object");
}
if (raw && typeof raw === "object") {
return [raw];
}
return [];
}
function getTokenSource(entry) {
return entry?.tokens && typeof entry.tokens === "object" ? entry.tokens : entry;
}
function isUsableCodexEntry(entry) {
const tokenSource = getTokenSource(entry);
return Boolean(
(entry.type || "codex") === "codex" &&
tokenSource.access_token &&
tokenSource.account_id &&
tokenSource.id_token &&
tokenSource.refresh_token,
);
}
function normalizeCodexEntry(entry) {
const tokenSource = getTokenSource(entry);
return {
OPENAI_API_KEY: entry.OPENAI_API_KEY || "",
auth_mode: entry.auth_mode || "chatgpt",
type: entry.type || "codex",
disabled: Boolean(entry.disabled),
email: entry.email || "",
name: entry.name || "",
last_refresh: entry.last_refresh || new Date().toISOString(),
expired: entry.expired || null,
tokens: {
access_token: tokenSource.access_token || "",
account_id: tokenSource.account_id || "",
id_token: tokenSource.id_token || "",
refresh_token: tokenSource.refresh_token || "",
},
};
}
export async function migrateCodexAccountPool({ tokensDir = DEFAULT_TOKENS_DIR } = {}) {
const dirEntries = await fs.readdir(tokensDir, { withFileTypes: true });
const files = dirEntries
.filter((entry) => entry.isFile() && entry.name.endsWith(".json") && entry.name !== "pool.json")
.map((entry) => path.join(tokensDir, entry.name))
.sort((left, right) => left.localeCompare(right));
const merged = [];
const seen = new Set();
for (const filePath of files) {
let raw;
try {
raw = JSON.parse(await fs.readFile(filePath, "utf8"));
} catch {
continue;
}
for (const entry of normalizeEntries(raw)) {
if (!isUsableCodexEntry(entry)) continue;
const normalized = normalizeCodexEntry(entry);
const accountId = normalized.tokens.account_id;
if (!accountId || seen.has(accountId)) continue;
seen.add(accountId);
merged.push(normalized);
}
}
const poolPath = path.join(tokensDir, "pool.json");
await fs.writeFile(poolPath, `${JSON.stringify(merged, null, 2)}\n`, "utf8");
let backupDir = null;
if (files.length > 0) {
backupDir = path.join(tokensDir, "_backup", nowStamp());
await fs.mkdir(backupDir, { recursive: true });
for (const filePath of files) {
await fs.rename(filePath, path.join(backupDir, path.basename(filePath)));
}
}
return {
poolPath,
backupDir,
count: merged.length,
};
}
async function main() {
const args = parseArgs(process.argv.slice(2));
const tokensDir = path.resolve(args["tokens-dir"] || DEFAULT_TOKENS_DIR);
const result = await migrateCodexAccountPool({ tokensDir });
console.log(`Pool written: ${result.poolPath}`);
console.log(`Accounts merged: ${result.count}`);
console.log(`Backup dir: ${result.backupDir || "(none)"}`);
}
const directRun =
process.argv[1] && path.resolve(process.argv[1]) === path.resolve(new URL(import.meta.url).pathname);
if (directRun) {
main().catch((error) => {
console.error(error?.message || error);
process.exitCode = 1;
});
}