fix(cliproxy): move token files when pausing/resuming accounts

Pausing an account now physically moves the token file to auth/paused/
subdirectory, preventing CLIProxyAPI from discovering and using it.
Resume moves the file back to auth/ directory.

Changes:
- Add getPausedDir() helper for paused tokens location
- Update pauseAccount() to move token to paused/ subdir
- Update resumeAccount() to move token back to auth/
- Update syncRegistryWithTokenFiles() to check both directories
- Update removeAccount() to clean up from both directories
- Update getAccountTokenPath() to return correct path based on state

Fixes #337
This commit is contained in:
kaitranntt
2026-01-14 16:01:56 -05:00
parent 01fe780514
commit 9d2442f9fa
+62 -4
View File
@@ -135,6 +135,14 @@ export function getAccountsRegistryPath(): string {
return path.join(getCliproxyDir(), 'accounts.json');
}
/**
* Get path to paused tokens directory
* Paused tokens are moved here so CLIProxyAPI won't discover them
*/
export function getPausedDir(): string {
return path.join(getAuthDir(), 'paused');
}
/**
* Load accounts registry
*/
@@ -176,10 +184,12 @@ export function saveAccountsRegistry(registry: AccountsRegistry): void {
/**
* Sync registry with actual token files
* Removes stale entries where token file no longer exists
* For paused accounts, checks both auth/ and paused/ directories
* Called automatically when loading accounts
*/
function syncRegistryWithTokenFiles(registry: AccountsRegistry): boolean {
const authDir = getAuthDir();
const pausedDir = getPausedDir();
let modified = false;
for (const [_providerName, providerAccounts] of Object.entries(registry.providers)) {
@@ -189,7 +199,14 @@ function syncRegistryWithTokenFiles(registry: AccountsRegistry): boolean {
for (const [accountId, meta] of Object.entries(providerAccounts.accounts)) {
const tokenPath = path.join(authDir, meta.tokenFile);
if (!fs.existsSync(tokenPath)) {
const pausedPath = path.join(pausedDir, meta.tokenFile);
// For paused accounts, check paused dir; for active accounts, check auth dir
const expectedPath = meta.paused ? pausedPath : tokenPath;
// Also accept if file exists in either location (handles edge cases)
const existsAnywhere = fs.existsSync(tokenPath) || fs.existsSync(pausedPath);
if (!fs.existsSync(expectedPath) && !existsAnywhere) {
staleIds.push(accountId);
}
}
@@ -395,6 +412,7 @@ export function setDefaultAccount(provider: CLIProxyProvider, accountId: string)
/**
* Pause an account (skip in quota rotation)
* Moves token file to paused/ subdir so CLIProxyAPI won't discover it
*/
export function pauseAccount(provider: CLIProxyProvider, accountId: string): boolean {
const registry = loadAccountsRegistry();
@@ -404,6 +422,21 @@ export function pauseAccount(provider: CLIProxyProvider, accountId: string): boo
return false;
}
const accountMeta = providerAccounts.accounts[accountId];
const authDir = getAuthDir();
const pausedDir = getPausedDir();
const tokenPath = path.join(authDir, accountMeta.tokenFile);
const pausedPath = path.join(pausedDir, accountMeta.tokenFile);
// Move token file to paused directory (if it exists in auth dir)
if (fs.existsSync(tokenPath)) {
// Create paused directory if it doesn't exist
if (!fs.existsSync(pausedDir)) {
fs.mkdirSync(pausedDir, { recursive: true, mode: 0o700 });
}
fs.renameSync(tokenPath, pausedPath);
}
providerAccounts.accounts[accountId].paused = true;
providerAccounts.accounts[accountId].pausedAt = new Date().toISOString();
saveAccountsRegistry(registry);
@@ -412,6 +445,7 @@ export function pauseAccount(provider: CLIProxyProvider, accountId: string): boo
/**
* Resume a paused account
* Moves token file back from paused/ to auth/ so CLIProxyAPI can discover it
*/
export function resumeAccount(provider: CLIProxyProvider, accountId: string): boolean {
const registry = loadAccountsRegistry();
@@ -421,6 +455,17 @@ export function resumeAccount(provider: CLIProxyProvider, accountId: string): bo
return false;
}
const accountMeta = providerAccounts.accounts[accountId];
const authDir = getAuthDir();
const pausedDir = getPausedDir();
const tokenPath = path.join(authDir, accountMeta.tokenFile);
const pausedPath = path.join(pausedDir, accountMeta.tokenFile);
// Move token file back from paused directory (if it exists in paused dir)
if (fs.existsSync(pausedPath)) {
fs.renameSync(pausedPath, tokenPath);
}
providerAccounts.accounts[accountId].paused = false;
providerAccounts.accounts[accountId].pausedAt = undefined;
saveAccountsRegistry(registry);
@@ -474,11 +519,12 @@ export function removeAccount(provider: CLIProxyProvider, accountId: string): bo
return false;
}
// Get token file to delete
// Get token file to delete (check both auth and paused directories)
const tokenFile = providerAccounts.accounts[accountId].tokenFile;
const tokenPath = path.join(getAuthDir(), tokenFile);
const pausedPath = path.join(getPausedDir(), tokenFile);
// Delete token file
// Delete token file from auth directory
if (fs.existsSync(tokenPath)) {
try {
fs.unlinkSync(tokenPath);
@@ -487,6 +533,15 @@ export function removeAccount(provider: CLIProxyProvider, accountId: string): bo
}
}
// Also delete from paused directory if it exists there
if (fs.existsSync(pausedPath)) {
try {
fs.unlinkSync(pausedPath);
} catch {
// Ignore deletion errors
}
}
// Remove from registry
delete providerAccounts.accounts[accountId];
@@ -547,6 +602,7 @@ export function touchAccount(provider: CLIProxyProvider, accountId: string): voi
/**
* Get token file path for an account
* Returns path in paused/ dir if account is paused, otherwise auth/
*/
export function getAccountTokenPath(provider: CLIProxyProvider, accountId?: string): string | null {
const account = accountId ? getAccount(provider, accountId) : getDefaultAccount(provider);
@@ -555,7 +611,9 @@ export function getAccountTokenPath(provider: CLIProxyProvider, accountId?: stri
return null;
}
return path.join(getAuthDir(), account.tokenFile);
// Return path from paused directory if account is paused
const baseDir = account.paused ? getPausedDir() : getAuthDir();
return path.join(baseDir, account.tokenFile);
}
/**