Skip to content

Commit 329fca0

Browse files
authored
Merge pull request #78 from kaitranntt/dev
feat: unified config architecture and profile management UX improvements
2 parents 0e46b6b + f7bded6 commit 329fca0

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

43 files changed

+5176
-473
lines changed

README.md

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,10 +120,19 @@ ccs gemini --auth --add
120120
# Add with nickname for easy identification
121121
ccs gemini --auth --add --nickname work
122122

123-
# List all accounts
123+
# Codex provider examples
124+
ccs codex --auth # First account for Codex
125+
ccs codex --auth --add # Add another Codex account
126+
ccs codex --auth --add --nickname work # Named account
127+
128+
# List all accounts (any provider)
129+
ccs gemini --accounts
130+
ccs codex --accounts
124131
ccs agy --accounts
125132

126133
# Switch to a different account
134+
ccs gemini --use work
135+
ccs codex --use work
127136
ccs agy --use work
128137
```
129138

VERSION

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
5.14.0
1+
5.14.0-dev.3

bun.lock

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
"express": "^4.18.2",
1212
"get-port": "^7.0.0",
1313
"gradient-string": "^3.0.0",
14+
"js-yaml": "^4.1.1",
1415
"listr2": "^9.0.5",
1516
"open": "^10.1.0",
1617
"ora": "^9.0.0",
@@ -24,6 +25,7 @@
2425
"@tailwindcss/vite": "^4.1.17",
2526
"@types/chokidar": "^2.1.7",
2627
"@types/express": "^4.17.21",
28+
"@types/js-yaml": "^4.0.9",
2729
"@types/node": "^20.19.25",
2830
"@types/ws": "^8.5.10",
2931
"@typescript-eslint/eslint-plugin": "^8.48.0",
@@ -365,6 +367,8 @@
365367

366368
"@types/http-errors": ["@types/[email protected]", "", {}, "sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg=="],
367369

370+
"@types/js-yaml": ["@types/[email protected]", "", {}, "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg=="],
371+
368372
"@types/json-schema": ["@types/[email protected]", "", {}, "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA=="],
369373

370374
"@types/mime": ["@types/[email protected]", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="],

package.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@kaitranntt/ccs",
3-
"version": "5.14.0",
3+
"version": "5.14.0-dev.3",
44
"description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
55
"keywords": [
66
"cli",
@@ -87,6 +87,7 @@
8787
"express": "^4.18.2",
8888
"get-port": "^7.0.0",
8989
"gradient-string": "^3.0.0",
90+
"js-yaml": "^4.1.1",
9091
"listr2": "^9.0.5",
9192
"open": "^10.1.0",
9293
"ora": "^9.0.0",
@@ -100,6 +101,7 @@
100101
"@tailwindcss/vite": "^4.1.17",
101102
"@types/chokidar": "^2.1.7",
102103
"@types/express": "^4.17.21",
104+
"@types/js-yaml": "^4.0.9",
103105
"@types/node": "^20.19.25",
104106
"@types/ws": "^8.5.10",
105107
"@typescript-eslint/eslint-plugin": "^8.48.0",

src/auth/auth-commands.ts

Lines changed: 53 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,10 @@
66
*
77
* Login-per-profile model: Each profile is an isolated Claude instance.
88
* Users login directly in each instance (no credential copying).
9+
*
10+
* Supports dual-mode configuration:
11+
* - Unified YAML format (config.yaml) when CCS_UNIFIED_CONFIG=1 or config.yaml exists
12+
* - Legacy JSON format (profiles.json) as fallback
913
*/
1014

1115
import { spawn, ChildProcess } from 'child_process';
@@ -29,6 +33,8 @@ import {
2933
import { detectClaudeCli } from '../utils/claude-detector';
3034
import { InteractivePrompt } from '../utils/prompt';
3135
import packageJson from '../../package.json';
36+
import { hasUnifiedConfig } from '../config/unified-config-loader';
37+
import { isUnifiedConfigEnabled } from '../config/feature-flags';
3238

3339
interface AuthCommandArgs {
3440
profileName?: string;
@@ -66,6 +72,13 @@ class AuthCommands {
6672
this.instanceMgr = new InstanceManager();
6773
}
6874

75+
/**
76+
* Check if unified config mode is active
77+
*/
78+
private isUnifiedMode(): boolean {
79+
return hasUnifiedConfig() || isUnifiedConfigEnabled();
80+
}
81+
6982
/**
7083
* Show help for auth commands
7184
*/
@@ -152,8 +165,10 @@ class AuthCommands {
152165
process.exit(1);
153166
}
154167

155-
// Check if profile already exists
156-
if (!force && this.registry.hasProfile(profileName)) {
168+
// Check if profile already exists (check both legacy and unified)
169+
const existsLegacy = this.registry.hasProfile(profileName);
170+
const existsUnified = this.registry.hasAccountUnified(profileName);
171+
if (!force && (existsLegacy || existsUnified)) {
157172
console.log(fail(`Profile already exists: ${profileName}`));
158173
console.log(` Use ${color('--force', 'command')} to overwrite`);
159174
process.exit(1);
@@ -164,15 +179,25 @@ class AuthCommands {
164179
console.log(info(`Creating profile: ${profileName}`));
165180
const instancePath = this.instanceMgr.ensureInstance(profileName);
166181

167-
// Create/update profile entry
168-
if (this.registry.hasProfile(profileName)) {
169-
this.registry.updateProfile(profileName, {
170-
type: 'account',
171-
});
182+
// Create/update profile entry based on config mode
183+
if (this.isUnifiedMode()) {
184+
// Use unified config (config.yaml)
185+
if (existsUnified) {
186+
this.registry.touchAccountUnified(profileName);
187+
} else {
188+
this.registry.createAccountUnified(profileName);
189+
}
172190
} else {
173-
this.registry.createProfile(profileName, {
174-
type: 'account',
175-
});
191+
// Use legacy profiles.json
192+
if (existsLegacy) {
193+
this.registry.updateProfile(profileName, {
194+
type: 'account',
195+
});
196+
} else {
197+
this.registry.createProfile(profileName, {
198+
type: 'account',
199+
});
200+
}
176201
}
177202

178203
console.log(info(`Instance directory: ${instancePath}`));
@@ -458,7 +483,11 @@ class AuthCommands {
458483
process.exit(1);
459484
}
460485

461-
if (!this.registry.hasProfile(profileName)) {
486+
// Check existence in both legacy and unified
487+
const existsLegacy = this.registry.hasProfile(profileName);
488+
const existsUnified = this.registry.hasAccountUnified(profileName);
489+
490+
if (!existsLegacy && !existsUnified) {
462491
console.log(fail(`Profile not found: ${profileName}`));
463492
process.exit(1);
464493
}
@@ -501,8 +530,13 @@ class AuthCommands {
501530
// Delete instance
502531
this.instanceMgr.deleteInstance(profileName);
503532

504-
// Delete profile
505-
this.registry.deleteProfile(profileName);
533+
// Delete profile from appropriate config
534+
if (this.isUnifiedMode() && existsUnified) {
535+
this.registry.removeAccountUnified(profileName);
536+
}
537+
if (existsLegacy) {
538+
this.registry.deleteProfile(profileName);
539+
}
506540

507541
console.log(ok(`Profile removed: ${profileName}`));
508542
console.log('');
@@ -527,7 +561,12 @@ class AuthCommands {
527561
}
528562

529563
try {
530-
this.registry.setDefaultProfile(profileName);
564+
// Use unified or legacy based on config mode
565+
if (this.isUnifiedMode()) {
566+
this.registry.setDefaultUnified(profileName);
567+
} else {
568+
this.registry.setDefaultProfile(profileName);
569+
}
531570

532571
console.log(ok(`Default profile set: ${profileName}`));
533572
console.log('');

0 commit comments

Comments
 (0)