mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-12 20:15:40 +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:
1 parent
1dbd7229a5
commit
0d70708658
3 files changed
+93
-9
No files matched your search
@@ -304,6 +304,38 @@ export function removeAccount(provider: CLIProxyProvider, accountId: string): bo
|
|||||||
return true;
|
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
|
* Update last used timestamp for an account
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -435,10 +435,16 @@ export function clearAuth(provider: CLIProxyProvider): boolean {
|
|||||||
*/
|
*/
|
||||||
export async function triggerOAuth(
|
export async function triggerOAuth(
|
||||||
provider: CLIProxyProvider,
|
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> {
|
): Promise<AccountInfo | null> {
|
||||||
const oauthConfig = getOAuthConfig(provider);
|
const oauthConfig = getOAuthConfig(provider);
|
||||||
const { verbose = false, add = false } = options;
|
const { verbose = false, add = false, nickname } = options;
|
||||||
|
|
||||||
// Auto-detect headless if not explicitly set
|
// Auto-detect headless if not explicitly set
|
||||||
const headless = options.headless ?? isHeadlessEnvironment();
|
const headless = options.headless ?? isHeadlessEnvironment();
|
||||||
@@ -615,7 +621,7 @@ export async function triggerOAuth(
|
|||||||
console.log('[OK] Authentication successful');
|
console.log('[OK] Authentication successful');
|
||||||
|
|
||||||
// Register the account in accounts registry
|
// Register the account in accounts registry
|
||||||
const account = registerAccountFromToken(provider, tokenDir);
|
const account = registerAccountFromToken(provider, tokenDir, nickname);
|
||||||
resolve(account);
|
resolve(account);
|
||||||
} else {
|
} else {
|
||||||
if (!headless) spinner.fail('Authentication incomplete');
|
if (!headless) spinner.fail('Authentication incomplete');
|
||||||
@@ -658,10 +664,14 @@ export async function triggerOAuth(
|
|||||||
/**
|
/**
|
||||||
* Register account from newly created token file
|
* Register account from newly created token file
|
||||||
* Scans auth directory for new token and extracts email
|
* 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(
|
function registerAccountFromToken(
|
||||||
provider: CLIProxyProvider,
|
provider: CLIProxyProvider,
|
||||||
tokenDir: string
|
tokenDir: string,
|
||||||
|
nickname?: string
|
||||||
): AccountInfo | null {
|
): AccountInfo | null {
|
||||||
try {
|
try {
|
||||||
const files = fs.readdirSync(tokenDir);
|
const files = fs.readdirSync(tokenDir);
|
||||||
@@ -692,8 +702,8 @@ function registerAccountFromToken(
|
|||||||
const data = JSON.parse(content);
|
const data = JSON.parse(content);
|
||||||
const email = data.email || undefined;
|
const email = data.email || undefined;
|
||||||
|
|
||||||
// Register the account with auto-generated nickname
|
// Register the account (use provided nickname or auto-generate from email)
|
||||||
return registerAccount(provider, newestFile, email, generateNickname(email));
|
return registerAccount(provider, newestFile, email, nickname || generateNickname(email));
|
||||||
} catch {
|
} catch {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,6 +33,8 @@ import {
|
|||||||
getProviderAccounts,
|
getProviderAccounts,
|
||||||
setDefaultAccount,
|
setDefaultAccount,
|
||||||
touchAccount,
|
touchAccount,
|
||||||
|
renameAccount,
|
||||||
|
getDefaultAccount,
|
||||||
} from './account-manager';
|
} from './account-manager';
|
||||||
|
|
||||||
/** Default executor configuration */
|
/** Default executor configuration */
|
||||||
@@ -139,6 +141,13 @@ export async function execClaudeWithCLIProxy(
|
|||||||
useAccount = args[useIdx + 1];
|
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
|
// Handle --accounts: list accounts and exit
|
||||||
if (showAccounts) {
|
if (showAccounts) {
|
||||||
const accounts = getProviderAccounts(provider);
|
const accounts = getProviderAccounts(provider);
|
||||||
@@ -177,6 +186,29 @@ export async function execClaudeWithCLIProxy(
|
|||||||
console.log(`[OK] Switched to account: ${account.nickname || account.email || account.id}`);
|
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
|
// Handle --config: configure model selection and exit
|
||||||
// Pass customSettingsPath for CLIProxy variants to save to correct file
|
// Pass customSettingsPath for CLIProxy variants to save to correct file
|
||||||
if (forceConfig && supportsModelConfig(provider)) {
|
if (forceConfig && supportsModelConfig(provider)) {
|
||||||
@@ -206,6 +238,7 @@ export async function execClaudeWithCLIProxy(
|
|||||||
verbose,
|
verbose,
|
||||||
add: addAccount,
|
add: addAccount,
|
||||||
...(forceHeadless ? { headless: true } : {}),
|
...(forceHeadless ? { headless: true } : {}),
|
||||||
|
...(setNickname ? { nickname: setNickname } : {}),
|
||||||
});
|
});
|
||||||
if (!authSuccess) {
|
if (!authSuccess) {
|
||||||
throw new Error(`Authentication required for ${providerConfig.displayName}`);
|
throw new Error(`Authentication required for ${providerConfig.displayName}`);
|
||||||
@@ -314,12 +347,21 @@ export async function execClaudeWithCLIProxy(
|
|||||||
log(`Claude env: ANTHROPIC_MODEL=${envVars.ANTHROPIC_MODEL}`);
|
log(`Claude env: ANTHROPIC_MODEL=${envVars.ANTHROPIC_MODEL}`);
|
||||||
|
|
||||||
// Filter out CCS-specific flags before passing to Claude CLI
|
// 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) => {
|
const claudeArgs = args.filter((arg, idx) => {
|
||||||
// Filter out CCS flags
|
// Filter out CCS flags
|
||||||
if (ccsFlags.includes(arg)) return false;
|
if (ccsFlags.includes(arg)) return false;
|
||||||
// Filter out value after --use
|
// Filter out value after --use or --nickname
|
||||||
if (args[idx - 1] === '--use') return false;
|
if (args[idx - 1] === '--use' || args[idx - 1] === '--nickname') return false;
|
||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user