mirror of
https://github.com/tiennm99/ccs.git
synced 2026-07-16 14:16:43 +00:00
feat(cliproxy): add --use and --accounts flags for multi-account switching
- Add findAccountByQuery() to search accounts by nickname/email/id - Add --accounts flag to list all accounts for a provider - Add --use <name> flag to switch between accounts - Filter CCS-specific flags from Claude CLI args - Update help documentation with new multi-account commands
This commit is contained in:
@@ -106,6 +106,29 @@ ccs agy --headless # Displays URL, paste in browser elsewhere
|
||||
ccs gemini --logout
|
||||
```
|
||||
|
||||
### Multi-Account for OAuth Providers
|
||||
|
||||
Use multiple accounts per provider (work + personal):
|
||||
|
||||
```bash
|
||||
# First account (default)
|
||||
ccs gemini --auth
|
||||
|
||||
# Add another account
|
||||
ccs gemini --auth --add
|
||||
|
||||
# Add with nickname for easy identification
|
||||
ccs gemini --auth --add --nickname work
|
||||
|
||||
# List all accounts
|
||||
ccs agy --accounts
|
||||
|
||||
# Switch to a different account
|
||||
ccs agy --use work
|
||||
```
|
||||
|
||||
Accounts are stored in `~/.ccs/cliproxy/accounts.json` and can be managed via web dashboard (`ccs config`).
|
||||
|
||||
### OAuth vs API Key Models
|
||||
|
||||
| Feature | OAuth Providers<br>(gemini, codex, agy) | API Key Models<br>(glm, kimi) |
|
||||
|
||||
@@ -165,6 +165,32 @@ export function getAccount(provider: CLIProxyProvider, accountId: string): Accou
|
||||
return accounts.find((a) => a.id === accountId) || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Find account by query (nickname, email, or id)
|
||||
* Supports partial matching for convenience
|
||||
*/
|
||||
export function findAccountByQuery(provider: CLIProxyProvider, query: string): AccountInfo | null {
|
||||
const accounts = getProviderAccounts(provider);
|
||||
const lowerQuery = query.toLowerCase();
|
||||
|
||||
// Exact match first (id, email, nickname)
|
||||
const exactMatch = accounts.find(
|
||||
(a) =>
|
||||
a.id === query ||
|
||||
a.email?.toLowerCase() === lowerQuery ||
|
||||
a.nickname?.toLowerCase() === lowerQuery
|
||||
);
|
||||
if (exactMatch) return exactMatch;
|
||||
|
||||
// Partial match on nickname or email prefix
|
||||
const partialMatch = accounts.find(
|
||||
(a) =>
|
||||
a.nickname?.toLowerCase().startsWith(lowerQuery) ||
|
||||
a.email?.toLowerCase().startsWith(lowerQuery)
|
||||
);
|
||||
return partialMatch || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a new account
|
||||
* Called after successful OAuth to record the account
|
||||
|
||||
@@ -28,6 +28,12 @@ import { isAuthenticated } from './auth-handler';
|
||||
import { CLIProxyProvider, ExecutorConfig } from './types';
|
||||
import { configureProviderModel, getCurrentModel } from './model-config';
|
||||
import { supportsModelConfig, isModelBroken, getModelIssueUrl, findModel } from './model-catalog';
|
||||
import {
|
||||
findAccountByQuery,
|
||||
getProviderAccounts,
|
||||
setDefaultAccount,
|
||||
touchAccount,
|
||||
} from './account-manager';
|
||||
|
||||
/** Default executor configuration */
|
||||
const DEFAULT_CONFIG: ExecutorConfig = {
|
||||
@@ -124,6 +130,52 @@ export async function execClaudeWithCLIProxy(
|
||||
const forceLogout = args.includes('--logout');
|
||||
const forceConfig = args.includes('--config');
|
||||
const addAccount = args.includes('--add');
|
||||
const showAccounts = args.includes('--accounts');
|
||||
|
||||
// Parse --use <account> flag
|
||||
let useAccount: string | undefined;
|
||||
const useIdx = args.indexOf('--use');
|
||||
if (useIdx !== -1 && args[useIdx + 1] && !args[useIdx + 1].startsWith('-')) {
|
||||
useAccount = args[useIdx + 1];
|
||||
}
|
||||
|
||||
// Handle --accounts: list accounts and exit
|
||||
if (showAccounts) {
|
||||
const accounts = getProviderAccounts(provider);
|
||||
if (accounts.length === 0) {
|
||||
console.log(`[i] No accounts registered for ${providerConfig.displayName}`);
|
||||
console.log(` Run "ccs ${provider} --auth" to add an account`);
|
||||
} else {
|
||||
console.log(`\n${providerConfig.displayName} Accounts:\n`);
|
||||
for (const acct of accounts) {
|
||||
const defaultMark = acct.isDefault ? ' (default)' : '';
|
||||
const nickname = acct.nickname ? `[${acct.nickname}]` : '';
|
||||
console.log(` ${nickname.padEnd(12)} ${acct.email || acct.id}${defaultMark}`);
|
||||
}
|
||||
console.log(`\n Use "ccs ${provider} --use <nickname>" to switch accounts`);
|
||||
}
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
// Handle --use: switch to specified account
|
||||
if (useAccount) {
|
||||
const account = findAccountByQuery(provider, useAccount);
|
||||
if (!account) {
|
||||
console.error(`[X] Account not found: "${useAccount}"`);
|
||||
const accounts = getProviderAccounts(provider);
|
||||
if (accounts.length > 0) {
|
||||
console.error(` Available accounts:`);
|
||||
for (const acct of accounts) {
|
||||
console.error(` - ${acct.nickname || acct.id} (${acct.email || 'no email'})`);
|
||||
}
|
||||
}
|
||||
process.exit(1);
|
||||
}
|
||||
// Set as default for this and future sessions
|
||||
setDefaultAccount(provider, account.id);
|
||||
touchAccount(provider, account.id);
|
||||
console.log(`[OK] Switched to account: ${account.nickname || account.email || account.id}`);
|
||||
}
|
||||
|
||||
// Handle --config: configure model selection and exit
|
||||
// Pass customSettingsPath for CLIProxy variants to save to correct file
|
||||
@@ -262,8 +314,14 @@ 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'];
|
||||
const claudeArgs = args.filter((arg) => !ccsFlags.includes(arg));
|
||||
const ccsFlags = ['--auth', '--headless', '--logout', '--config', '--add', '--accounts', '--use'];
|
||||
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;
|
||||
return true;
|
||||
});
|
||||
|
||||
const isWindows = process.platform === 'win32';
|
||||
const needsShell = isWindows && /\.(cmd|bat|ps1)$/i.test(claudeCli);
|
||||
|
||||
@@ -656,6 +656,21 @@ async function showHelp(): Promise<void> {
|
||||
}
|
||||
console.log('');
|
||||
|
||||
// Multi-Account Commands
|
||||
console.log(subheader('Multi-Account Commands:'));
|
||||
const multiAcctCmds: [string, string][] = [
|
||||
['--auth', 'Authenticate with a provider (first account)'],
|
||||
['--auth --add', 'Add another account to a provider'],
|
||||
['--nickname <name>', 'Set friendly name for account'],
|
||||
['--accounts', 'List all accounts for a provider'],
|
||||
['--use <name>', 'Switch to account by nickname/email'],
|
||||
];
|
||||
const maxMultiLen = Math.max(...multiAcctCmds.map(([cmd]) => cmd.length));
|
||||
for (const [cmd, desc] of multiAcctCmds) {
|
||||
console.log(` ${color(cmd.padEnd(maxMultiLen + 2), 'command')} ${desc}`);
|
||||
}
|
||||
console.log('');
|
||||
|
||||
// Create Options
|
||||
console.log(subheader('Create Options:'));
|
||||
const createOpts: [string, string][] = [
|
||||
@@ -690,6 +705,23 @@ async function showHelp(): Promise<void> {
|
||||
` $ ${color('ccs cliproxy --latest', 'command')} ${dim('# Update binary')}`
|
||||
);
|
||||
console.log('');
|
||||
console.log(subheader('Multi-Account Examples:'));
|
||||
console.log(
|
||||
` $ ${color('ccs gemini --auth', 'command')} ${dim('# First account')}`
|
||||
);
|
||||
console.log(
|
||||
` $ ${color('ccs gemini --auth --add', 'command')} ${dim('# Add second account')}`
|
||||
);
|
||||
console.log(
|
||||
` $ ${color('ccs gemini --auth --add --nickname work', 'command')} ${dim('# With nickname')}`
|
||||
);
|
||||
console.log(
|
||||
` $ ${color('ccs agy --accounts', 'command')} ${dim('# List accounts')}`
|
||||
);
|
||||
console.log(
|
||||
` $ ${color('ccs agy --use work', 'command')} ${dim('# Switch account')}`
|
||||
);
|
||||
console.log('');
|
||||
|
||||
// Notes
|
||||
console.log(subheader('Notes:'));
|
||||
|
||||
@@ -162,6 +162,8 @@ Claude Code Profile & Model Switcher`.trim();
|
||||
['', ''], // Spacer
|
||||
['ccs <provider> --auth', 'Authenticate only'],
|
||||
['ccs <provider> --auth --add', 'Add another account'],
|
||||
['ccs <provider> --accounts', 'List all accounts'],
|
||||
['ccs <provider> --use <name>', 'Switch to account'],
|
||||
['ccs <provider> --config', 'Change model (agy, gemini)'],
|
||||
['ccs <provider> --logout', 'Clear authentication'],
|
||||
['ccs <provider> --headless', 'Headless auth (for SSH)'],
|
||||
|
||||
Reference in New Issue
Block a user