mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 14:16:43 +00:00
feat(cliproxy): implement --nickname flag for account management
- Add renameAccount function to account-manager.ts with validation - Support --nickname <name> to rename current default account - Support --nickname with --auth --add to set nickname during OAuth - Add getDefaultAccount import for nickname operations - Filter --nickname flag from Claude CLI args
This commit is contained in:
@@ -304,6 +304,38 @@ export function removeAccount(provider: CLIProxyProvider, accountId: string): bo
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rename an account's nickname
|
||||
*/
|
||||
export function renameAccount(
|
||||
provider: CLIProxyProvider,
|
||||
accountId: string,
|
||||
newNickname: string
|
||||
): boolean {
|
||||
const validationError = validateNickname(newNickname);
|
||||
if (validationError) {
|
||||
throw new Error(validationError);
|
||||
}
|
||||
|
||||
const registry = loadAccountsRegistry();
|
||||
const providerAccounts = registry.providers[provider];
|
||||
|
||||
if (!providerAccounts?.accounts[accountId]) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check if nickname is already used by another account
|
||||
for (const [id, account] of Object.entries(providerAccounts.accounts)) {
|
||||
if (id !== accountId && account.nickname?.toLowerCase() === newNickname.toLowerCase()) {
|
||||
throw new Error(`Nickname "${newNickname}" is already used by another account`);
|
||||
}
|
||||
}
|
||||
|
||||
providerAccounts.accounts[accountId].nickname = newNickname;
|
||||
saveAccountsRegistry(registry);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update last used timestamp for an account
|
||||
*/
|
||||
|
||||
@@ -435,10 +435,16 @@ export function clearAuth(provider: CLIProxyProvider): boolean {
|
||||
*/
|
||||
export async function triggerOAuth(
|
||||
provider: CLIProxyProvider,
|
||||
options: { verbose?: boolean; headless?: boolean; account?: string; add?: boolean } = {}
|
||||
options: {
|
||||
verbose?: boolean;
|
||||
headless?: boolean;
|
||||
account?: string;
|
||||
add?: boolean;
|
||||
nickname?: string;
|
||||
} = {}
|
||||
): Promise<AccountInfo | null> {
|
||||
const oauthConfig = getOAuthConfig(provider);
|
||||
const { verbose = false, add = false } = options;
|
||||
const { verbose = false, add = false, nickname } = options;
|
||||
|
||||
// Auto-detect headless if not explicitly set
|
||||
const headless = options.headless ?? isHeadlessEnvironment();
|
||||
@@ -615,7 +621,7 @@ export async function triggerOAuth(
|
||||
console.log('[OK] Authentication successful');
|
||||
|
||||
// Register the account in accounts registry
|
||||
const account = registerAccountFromToken(provider, tokenDir);
|
||||
const account = registerAccountFromToken(provider, tokenDir, nickname);
|
||||
resolve(account);
|
||||
} else {
|
||||
if (!headless) spinner.fail('Authentication incomplete');
|
||||
@@ -658,10 +664,14 @@ export async function triggerOAuth(
|
||||
/**
|
||||
* Register account from newly created token file
|
||||
* Scans auth directory for new token and extracts email
|
||||
* @param provider - The CLIProxy provider
|
||||
* @param tokenDir - Directory containing token files
|
||||
* @param nickname - Optional nickname (uses auto-generated from email if not provided)
|
||||
*/
|
||||
function registerAccountFromToken(
|
||||
provider: CLIProxyProvider,
|
||||
tokenDir: string
|
||||
tokenDir: string,
|
||||
nickname?: string
|
||||
): AccountInfo | null {
|
||||
try {
|
||||
const files = fs.readdirSync(tokenDir);
|
||||
@@ -692,8 +702,8 @@ function registerAccountFromToken(
|
||||
const data = JSON.parse(content);
|
||||
const email = data.email || undefined;
|
||||
|
||||
// Register the account with auto-generated nickname
|
||||
return registerAccount(provider, newestFile, email, generateNickname(email));
|
||||
// Register the account (use provided nickname or auto-generate from email)
|
||||
return registerAccount(provider, newestFile, email, nickname || generateNickname(email));
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,8 @@ import {
|
||||
getProviderAccounts,
|
||||
setDefaultAccount,
|
||||
touchAccount,
|
||||
renameAccount,
|
||||
getDefaultAccount,
|
||||
} from './account-manager';
|
||||
|
||||
/** Default executor configuration */
|
||||
@@ -139,6 +141,13 @@ export async function execClaudeWithCLIProxy(
|
||||
useAccount = args[useIdx + 1];
|
||||
}
|
||||
|
||||
// Parse --nickname <name> flag
|
||||
let setNickname: string | undefined;
|
||||
const nicknameIdx = args.indexOf('--nickname');
|
||||
if (nicknameIdx !== -1 && args[nicknameIdx + 1] && !args[nicknameIdx + 1].startsWith('-')) {
|
||||
setNickname = args[nicknameIdx + 1];
|
||||
}
|
||||
|
||||
// Handle --accounts: list accounts and exit
|
||||
if (showAccounts) {
|
||||
const accounts = getProviderAccounts(provider);
|
||||
@@ -177,6 +186,29 @@ export async function execClaudeWithCLIProxy(
|
||||
console.log(`[OK] Switched to account: ${account.nickname || account.email || account.id}`);
|
||||
}
|
||||
|
||||
// Handle --nickname: rename account and exit (unless used with --auth --add)
|
||||
if (setNickname && !addAccount) {
|
||||
const defaultAccount = getDefaultAccount(provider);
|
||||
if (!defaultAccount) {
|
||||
console.error(`[X] No account found for ${providerConfig.displayName}`);
|
||||
console.error(` Run "ccs ${provider} --auth" to add an account first`);
|
||||
process.exit(1);
|
||||
}
|
||||
try {
|
||||
const success = renameAccount(provider, defaultAccount.id, setNickname);
|
||||
if (success) {
|
||||
console.log(`[OK] Renamed account to: ${setNickname}`);
|
||||
} else {
|
||||
console.error(`[X] Failed to rename account`);
|
||||
process.exit(1);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(`[X] ${err instanceof Error ? err.message : 'Failed to rename account'}`);
|
||||
process.exit(1);
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Handle --config: configure model selection and exit
|
||||
// Pass customSettingsPath for CLIProxy variants to save to correct file
|
||||
if (forceConfig && supportsModelConfig(provider)) {
|
||||
@@ -206,6 +238,7 @@ export async function execClaudeWithCLIProxy(
|
||||
verbose,
|
||||
add: addAccount,
|
||||
...(forceHeadless ? { headless: true } : {}),
|
||||
...(setNickname ? { nickname: setNickname } : {}),
|
||||
});
|
||||
if (!authSuccess) {
|
||||
throw new Error(`Authentication required for ${providerConfig.displayName}`);
|
||||
@@ -314,12 +347,21 @@ export async function execClaudeWithCLIProxy(
|
||||
log(`Claude env: ANTHROPIC_MODEL=${envVars.ANTHROPIC_MODEL}`);
|
||||
|
||||
// Filter out CCS-specific flags before passing to Claude CLI
|
||||
const ccsFlags = ['--auth', '--headless', '--logout', '--config', '--add', '--accounts', '--use'];
|
||||
const ccsFlags = [
|
||||
'--auth',
|
||||
'--headless',
|
||||
'--logout',
|
||||
'--config',
|
||||
'--add',
|
||||
'--accounts',
|
||||
'--use',
|
||||
'--nickname',
|
||||
];
|
||||
const claudeArgs = args.filter((arg, idx) => {
|
||||
// Filter out CCS flags
|
||||
if (ccsFlags.includes(arg)) return false;
|
||||
// Filter out value after --use
|
||||
if (args[idx - 1] === '--use') return false;
|
||||
// Filter out value after --use or --nickname
|
||||
if (args[idx - 1] === '--use' || args[idx - 1] === '--nickname') return false;
|
||||
return true;
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user