mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-15 08:16:50 +00:00
fix(cliproxy): use nickname as accountId for kiro/ghcp providers
Kiro/GHCP OAuth tokens have empty email field, causing all accounts to use accountId='default' and overwrite each other. This fix: - Add PROVIDERS_WITHOUT_EMAIL constant for kiro/ghcp identification - Require nickname for kiro/ghcp during registration (CLI + web UI) - Use nickname as accountId instead of email for these providers - Enforce nickname uniqueness to prevent collisions - Update discoverExistingAccounts() to generate unique IDs (kiro-1, etc.) Closes #258, #267
This commit is contained in:
1 parent
497fc2a9c0
commit
d96c67ba81
4 files changed
+271
-14
No files matched your search
@@ -20,6 +20,8 @@ import {
|
||||
getProviderAccounts,
|
||||
getDefaultAccount,
|
||||
touchAccount,
|
||||
PROVIDERS_WITHOUT_EMAIL,
|
||||
validateNickname,
|
||||
} from '../account-manager';
|
||||
import {
|
||||
enhancedPreflightOAuthCheck,
|
||||
@@ -49,6 +51,66 @@ async function promptAddAccount(): Promise<boolean> {
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Prompt user for account nickname (required for kiro/ghcp)
|
||||
* Returns null if user cancels
|
||||
*/
|
||||
async function promptNickname(
|
||||
provider: CLIProxyProvider,
|
||||
existingAccounts: AccountInfo[]
|
||||
): Promise<string | null> {
|
||||
const readline = await import('readline');
|
||||
const rl = readline.createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout,
|
||||
});
|
||||
|
||||
const existingNicknames = existingAccounts.map(
|
||||
(a) => a.nickname?.toLowerCase() || a.id.toLowerCase()
|
||||
);
|
||||
|
||||
console.log('');
|
||||
console.log(info(`${provider} accounts require a unique nickname to distinguish them.`));
|
||||
if (existingNicknames.length > 0) {
|
||||
console.log(` Existing: ${existingNicknames.join(', ')}`);
|
||||
}
|
||||
|
||||
return new Promise<string | null>((resolve) => {
|
||||
// Handle Ctrl+C gracefully
|
||||
rl.on('close', () => resolve(null));
|
||||
|
||||
const askForNickname = () => {
|
||||
rl.question('[?] Enter a nickname for this account: ', (answer) => {
|
||||
const nickname = answer.trim();
|
||||
|
||||
if (!nickname) {
|
||||
console.log(fail('Nickname cannot be empty'));
|
||||
askForNickname();
|
||||
return;
|
||||
}
|
||||
|
||||
const validationError = validateNickname(nickname);
|
||||
if (validationError) {
|
||||
console.log(fail(validationError));
|
||||
askForNickname();
|
||||
return;
|
||||
}
|
||||
|
||||
if (existingNicknames.includes(nickname.toLowerCase())) {
|
||||
console.log(fail(`Nickname "${nickname}" is already in use. Choose a different one.`));
|
||||
askForNickname();
|
||||
return;
|
||||
}
|
||||
|
||||
rl.close();
|
||||
resolve(nickname);
|
||||
});
|
||||
};
|
||||
|
||||
askForNickname();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Run pre-flight OAuth checks
|
||||
*/
|
||||
@@ -127,7 +189,21 @@ export async function triggerOAuth(
|
||||
options: OAuthOptions = {}
|
||||
): Promise<AccountInfo | null> {
|
||||
const oauthConfig = getOAuthConfig(provider);
|
||||
const { verbose = false, add = false, nickname, fromUI = false, noIncognito = true } = options;
|
||||
const { verbose = false, add = false, fromUI = false, noIncognito = true } = options;
|
||||
let { nickname } = options;
|
||||
|
||||
// Check for existing accounts
|
||||
const existingAccounts = getProviderAccounts(provider);
|
||||
|
||||
// For kiro/ghcp: require nickname if not provided (CLI only, not fromUI)
|
||||
if (PROVIDERS_WITHOUT_EMAIL.includes(provider) && !nickname && !fromUI) {
|
||||
const promptedNickname = await promptNickname(provider, existingAccounts);
|
||||
if (!promptedNickname) {
|
||||
console.log(info('Cancelled'));
|
||||
return null;
|
||||
}
|
||||
nickname = promptedNickname;
|
||||
}
|
||||
|
||||
// Handle --import flag: skip OAuth and import from Kiro IDE directly
|
||||
if (options.import && provider === 'kiro') {
|
||||
@@ -144,8 +220,6 @@ export async function triggerOAuth(
|
||||
const headless = options.headless ?? isHeadlessEnvironment();
|
||||
const isDeviceCodeFlow = callbackPort === null;
|
||||
|
||||
// Check for existing accounts
|
||||
const existingAccounts = getProviderAccounts(provider);
|
||||
if (existingAccounts.length > 0 && !add) {
|
||||
console.log('');
|
||||
console.log(
|
||||
|
||||
Reference in new issue
Block a user