@kaitranntt/ccs 3.0.1 → 3.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -0
- package/VERSION +1 -1
- package/bin/auth-commands.js +10 -1
- package/bin/ccs.js +14 -24
- package/bin/instance-manager.js +9 -20
- package/bin/profile-registry.js +3 -4
- package/bin/shared-manager.js +164 -0
- package/lib/ccs +102 -39
- package/lib/ccs.ps1 +129 -42
- package/package.json +1 -1
- package/scripts/postinstall.js +17 -0
package/README.md
CHANGED
|
@@ -92,6 +92,16 @@ $env:CCS_CLAUDE_PATH = "D:\Tools\Claude\claude.exe" # Windows
|
|
|
92
92
|
|
|
93
93
|
**See [Troubleshooting Guide](./docs/en/troubleshooting.md#claude-cli-in-non-standard-location) for detailed setup instructions.**
|
|
94
94
|
|
|
95
|
+
### Windows Symlink Support (Developer Mode)
|
|
96
|
+
|
|
97
|
+
**Windows users**: Enable Developer Mode for true symlinks (better performance, instant sync):
|
|
98
|
+
|
|
99
|
+
1. Open **Settings** → **Privacy & Security** → **For developers**
|
|
100
|
+
2. Enable **Developer Mode**
|
|
101
|
+
3. Reinstall CCS: `npm install -g @kaitranntt/ccs`
|
|
102
|
+
|
|
103
|
+
**Without Developer Mode**: CCS automatically falls back to copying directories (works but no instant sync across profiles).
|
|
104
|
+
|
|
95
105
|
---
|
|
96
106
|
|
|
97
107
|
### Your First Switch
|
|
@@ -153,6 +163,44 @@ ccs work-2 # Switch to second company account
|
|
|
153
163
|
|
|
154
164
|
---
|
|
155
165
|
|
|
166
|
+
## 📁 Shared Data Architecture
|
|
167
|
+
|
|
168
|
+
**v3.1 Shared Global Data**: Commands and skills are symlinked across all profiles via `~/.ccs/shared/`, eliminating duplication.
|
|
169
|
+
|
|
170
|
+
**Directory Structure**:
|
|
171
|
+
```
|
|
172
|
+
~/.ccs/
|
|
173
|
+
├── shared/ # Shared across all profiles
|
|
174
|
+
│ ├── commands/ # Custom slash commands
|
|
175
|
+
│ └── skills/ # Claude Code skills
|
|
176
|
+
├── instances/ # Profile-specific data
|
|
177
|
+
│ ├── work/
|
|
178
|
+
│ │ ├── commands@ → ~/.ccs/shared/commands/ # Symlink
|
|
179
|
+
│ │ ├── skills@ → ~/.ccs/shared/skills/ # Symlink
|
|
180
|
+
│ │ ├── settings.json # Profile-specific config
|
|
181
|
+
│ │ └── sessions/ # Profile-specific sessions
|
|
182
|
+
│ └── personal/
|
|
183
|
+
│ └── ...
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
**Benefits**:
|
|
187
|
+
- No duplication of commands/skills across profiles
|
|
188
|
+
- Single source of truth for shared resources
|
|
189
|
+
- Automatic migration from v3.0 (runs on first use)
|
|
190
|
+
- Windows fallback: copies if symlinks unavailable (enable Developer Mode for true symlinks)
|
|
191
|
+
|
|
192
|
+
**What's Shared**:
|
|
193
|
+
- `.claude/commands/` - Custom slash commands
|
|
194
|
+
- `.claude/skills/` - Claude Code skills
|
|
195
|
+
|
|
196
|
+
**What's Profile-Specific**:
|
|
197
|
+
- `settings.json` - API keys, credentials
|
|
198
|
+
- `sessions/` - Conversation history
|
|
199
|
+
- `todolists/` - Task tracking
|
|
200
|
+
- `logs/` - Profile-specific logs
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
156
204
|
## 🏗️ Architecture Overview
|
|
157
205
|
|
|
158
206
|
**v3.0 Login-Per-Profile Model**: Each profile is an isolated Claude instance where users login directly. No credential copying or vault encryption.
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.0
|
|
1
|
+
3.1.0
|
package/bin/auth-commands.js
CHANGED
|
@@ -39,12 +39,18 @@ class AuthCommands {
|
|
|
39
39
|
console.log('');
|
|
40
40
|
console.log(colored('Examples:', 'cyan'));
|
|
41
41
|
console.log(` ${colored('ccs auth create work', 'yellow')} # Create & login to work profile`);
|
|
42
|
+
console.log(` ${colored('ccs auth default work', 'yellow')} # Set work as default`);
|
|
42
43
|
console.log(` ${colored('ccs auth list', 'yellow')} # List all profiles`);
|
|
43
44
|
console.log(` ${colored('ccs work "review code"', 'yellow')} # Use work profile`);
|
|
45
|
+
console.log(` ${colored('ccs "review code"', 'yellow')} # Use default profile`);
|
|
44
46
|
console.log('');
|
|
45
47
|
console.log(colored('Options:', 'cyan'));
|
|
46
48
|
console.log(` ${colored('--force', 'yellow')} Allow overwriting existing profile`);
|
|
47
49
|
console.log('');
|
|
50
|
+
console.log(colored('Note:', 'cyan'));
|
|
51
|
+
console.log(` By default, ${colored('ccs', 'yellow')} uses Claude CLI defaults from ~/.claude/`);
|
|
52
|
+
console.log(` Use ${colored('ccs auth default <profile>', 'yellow')} to change the default profile.`);
|
|
53
|
+
console.log('');
|
|
48
54
|
}
|
|
49
55
|
|
|
50
56
|
/**
|
|
@@ -119,7 +125,10 @@ class AuthCommands {
|
|
|
119
125
|
console.log(` Instance: ${instancePath}`);
|
|
120
126
|
console.log('');
|
|
121
127
|
console.log('Usage:');
|
|
122
|
-
console.log(` ${colored(`ccs ${profileName} "your prompt here"`, 'yellow')}`);
|
|
128
|
+
console.log(` ${colored(`ccs ${profileName} "your prompt here"`, 'yellow')} # Use this specific profile`);
|
|
129
|
+
console.log('');
|
|
130
|
+
console.log('To set as default (so you can use just "ccs"):');
|
|
131
|
+
console.log(` ${colored(`ccs auth default ${profileName}`, 'yellow')}`);
|
|
123
132
|
console.log('');
|
|
124
133
|
process.exit(0);
|
|
125
134
|
} else {
|
package/bin/ccs.js
CHANGED
|
@@ -114,14 +114,9 @@ function handleHelpCommand() {
|
|
|
114
114
|
|
|
115
115
|
// Account Management
|
|
116
116
|
console.log(colored('Account Management:', 'cyan'));
|
|
117
|
-
console.log(` ${colored('ccs auth
|
|
118
|
-
console.log(` ${colored('ccs auth list', 'yellow')} List all saved profiles`);
|
|
119
|
-
console.log(` ${colored('ccs auth show <profile>', 'yellow')} Show profile details`);
|
|
120
|
-
console.log(` ${colored('ccs auth remove <profile>', 'yellow')} Remove profile (requires --force)`);
|
|
121
|
-
console.log(` ${colored('ccs auth default <profile>', 'yellow')} Set default profile`);
|
|
117
|
+
console.log(` ${colored('ccs auth --help', 'yellow')} Manage multiple Claude accounts`);
|
|
122
118
|
console.log(` ${colored('ccs work', 'yellow')} Switch to work account`);
|
|
123
119
|
console.log(` ${colored('ccs personal', 'yellow')} Switch to personal account`);
|
|
124
|
-
console.log(` ${colored('ccs work', 'yellow')} "review code" Run command with work account`);
|
|
125
120
|
console.log('');
|
|
126
121
|
|
|
127
122
|
// Diagnostics
|
|
@@ -138,28 +133,18 @@ function handleHelpCommand() {
|
|
|
138
133
|
// Configuration
|
|
139
134
|
console.log(colored('Configuration:', 'cyan'));
|
|
140
135
|
console.log(' Config File: ~/.ccs/config.json');
|
|
136
|
+
console.log(' Profiles: ~/.ccs/profiles.json');
|
|
137
|
+
console.log(' Instances: ~/.ccs/instances/');
|
|
141
138
|
console.log(' Settings: ~/.ccs/*.settings.json');
|
|
142
139
|
console.log(' Environment: CCS_CONFIG (override config path)');
|
|
143
140
|
console.log('');
|
|
144
141
|
|
|
145
|
-
//
|
|
146
|
-
console.log(colored('
|
|
147
|
-
console.log('
|
|
148
|
-
console.log(
|
|
149
|
-
console.log(
|
|
150
|
-
console.log('');
|
|
151
|
-
console.log(' # Use default Claude subscription');
|
|
152
|
-
console.log(` ${colored('ccs', 'yellow')} "Review this architecture"`);
|
|
153
|
-
console.log('');
|
|
154
|
-
console.log(' # Switch to GLM for cost-effective tasks');
|
|
155
|
-
console.log(` ${colored('ccs glm', 'yellow')} "Write unit tests"`);
|
|
156
|
-
console.log('');
|
|
157
|
-
console.log(' # Switch to Kimi for alternative option');
|
|
158
|
-
console.log(` ${colored('ccs kimi', 'yellow')} "Write integration tests"`);
|
|
159
|
-
console.log('');
|
|
160
|
-
console.log(' # Use with verbose output');
|
|
161
|
-
console.log(` ${colored('ccs glm', 'yellow')} --verbose "Debug error"`);
|
|
162
|
-
console.log(` ${colored('ccs kimi', 'yellow')} --verbose "Review code"`);
|
|
142
|
+
// Shared Data
|
|
143
|
+
console.log(colored('Shared Data:', 'cyan'));
|
|
144
|
+
console.log(' Commands: ~/.ccs/shared/commands/');
|
|
145
|
+
console.log(' Skills: ~/.ccs/shared/skills/');
|
|
146
|
+
console.log(' Agents: ~/.ccs/shared/agents/');
|
|
147
|
+
console.log(' Note: Commands, skills, and agents are symlinked across all profiles');
|
|
163
148
|
console.log('');
|
|
164
149
|
|
|
165
150
|
// Uninstall
|
|
@@ -277,6 +262,11 @@ async function main() {
|
|
|
277
262
|
recovery.showRecoveryHints();
|
|
278
263
|
}
|
|
279
264
|
|
|
265
|
+
// Run migration to shared structure (Phase 1: idempotent)
|
|
266
|
+
const SharedManager = require('./shared-manager');
|
|
267
|
+
const sharedManager = new SharedManager();
|
|
268
|
+
sharedManager.migrateToSharedStructure();
|
|
269
|
+
|
|
280
270
|
// Detect profile
|
|
281
271
|
const { profile, remainingArgs } = detectProfile(args);
|
|
282
272
|
|
package/bin/instance-manager.js
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
const fs = require('fs');
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
|
+
const SharedManager = require('./shared-manager');
|
|
6
7
|
|
|
7
8
|
/**
|
|
8
9
|
* Instance Manager (Simplified)
|
|
@@ -14,6 +15,7 @@ const os = require('os');
|
|
|
14
15
|
class InstanceManager {
|
|
15
16
|
constructor() {
|
|
16
17
|
this.instancesDir = path.join(os.homedir(), '.ccs', 'instances');
|
|
18
|
+
this.sharedManager = new SharedManager();
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
/**
|
|
@@ -56,7 +58,7 @@ class InstanceManager {
|
|
|
56
58
|
// Create base directory
|
|
57
59
|
fs.mkdirSync(instancePath, { recursive: true, mode: 0o700 });
|
|
58
60
|
|
|
59
|
-
// Create Claude-expected subdirectories
|
|
61
|
+
// Create Claude-expected subdirectories (profile-specific only)
|
|
60
62
|
const subdirs = [
|
|
61
63
|
'session-env',
|
|
62
64
|
'todos',
|
|
@@ -64,9 +66,7 @@ class InstanceManager {
|
|
|
64
66
|
'file-history',
|
|
65
67
|
'shell-snapshots',
|
|
66
68
|
'debug',
|
|
67
|
-
'.anthropic'
|
|
68
|
-
'commands',
|
|
69
|
-
'skills'
|
|
69
|
+
'.anthropic'
|
|
70
70
|
];
|
|
71
71
|
|
|
72
72
|
subdirs.forEach(dir => {
|
|
@@ -76,7 +76,10 @@ class InstanceManager {
|
|
|
76
76
|
}
|
|
77
77
|
});
|
|
78
78
|
|
|
79
|
-
//
|
|
79
|
+
// Symlink shared directories (Phase 1: commands, skills)
|
|
80
|
+
this.sharedManager.linkSharedDirectories(instancePath);
|
|
81
|
+
|
|
82
|
+
// Copy global configs if exist (settings.json only)
|
|
80
83
|
this._copyGlobalConfigs(instancePath);
|
|
81
84
|
} catch (error) {
|
|
82
85
|
throw new Error(`Failed to initialize instance for ${profileName}: ${error.message}`);
|
|
@@ -158,26 +161,12 @@ class InstanceManager {
|
|
|
158
161
|
_copyGlobalConfigs(instancePath) {
|
|
159
162
|
const globalConfigDir = path.join(os.homedir(), '.claude');
|
|
160
163
|
|
|
161
|
-
// Copy settings.json
|
|
164
|
+
// Copy settings.json only (commands/skills are now symlinked to shared/)
|
|
162
165
|
const globalSettings = path.join(globalConfigDir, 'settings.json');
|
|
163
166
|
if (fs.existsSync(globalSettings)) {
|
|
164
167
|
const instanceSettings = path.join(instancePath, 'settings.json');
|
|
165
168
|
fs.copyFileSync(globalSettings, instanceSettings);
|
|
166
169
|
}
|
|
167
|
-
|
|
168
|
-
// Copy commands directory if exists
|
|
169
|
-
const globalCommands = path.join(globalConfigDir, 'commands');
|
|
170
|
-
if (fs.existsSync(globalCommands)) {
|
|
171
|
-
const instanceCommands = path.join(instancePath, 'commands');
|
|
172
|
-
this._copyDirectory(globalCommands, instanceCommands);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
// Copy skills directory if exists
|
|
176
|
-
const globalSkills = path.join(globalConfigDir, 'skills');
|
|
177
|
-
if (fs.existsSync(globalSkills)) {
|
|
178
|
-
const instanceSkills = path.join(instancePath, 'skills');
|
|
179
|
-
this._copyDirectory(globalSkills, instanceSkills);
|
|
180
|
-
}
|
|
181
170
|
}
|
|
182
171
|
|
|
183
172
|
/**
|
package/bin/profile-registry.js
CHANGED
|
@@ -94,10 +94,9 @@ class ProfileRegistry {
|
|
|
94
94
|
last_used: null
|
|
95
95
|
};
|
|
96
96
|
|
|
97
|
-
//
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
}
|
|
97
|
+
// Note: No longer auto-set as default
|
|
98
|
+
// Users must explicitly run: ccs auth default <profile>
|
|
99
|
+
// Default always stays on implicit 'default' profile (uses ~/.claude/)
|
|
101
100
|
|
|
102
101
|
this._write(data);
|
|
103
102
|
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* SharedManager - Manages symlinked shared directories for CCS
|
|
9
|
+
* Phase 1: Shared Global Data via Symlinks
|
|
10
|
+
*
|
|
11
|
+
* Purpose: Eliminates duplication of commands/skills across profile instances
|
|
12
|
+
* by symlinking to a single ~/.ccs/shared/ directory.
|
|
13
|
+
*/
|
|
14
|
+
class SharedManager {
|
|
15
|
+
constructor() {
|
|
16
|
+
this.homeDir = os.homedir();
|
|
17
|
+
this.sharedDir = path.join(this.homeDir, '.ccs', 'shared');
|
|
18
|
+
this.instancesDir = path.join(this.homeDir, '.ccs', 'instances');
|
|
19
|
+
this.sharedDirs = ['commands', 'skills', 'agents'];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Ensure shared directories exist
|
|
24
|
+
*/
|
|
25
|
+
ensureSharedDirectories() {
|
|
26
|
+
// Create shared directory
|
|
27
|
+
if (!fs.existsSync(this.sharedDir)) {
|
|
28
|
+
fs.mkdirSync(this.sharedDir, { recursive: true, mode: 0o700 });
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Create shared subdirectories
|
|
32
|
+
for (const dir of this.sharedDirs) {
|
|
33
|
+
const dirPath = path.join(this.sharedDir, dir);
|
|
34
|
+
if (!fs.existsSync(dirPath)) {
|
|
35
|
+
fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Link shared directories to instance
|
|
42
|
+
* @param {string} instancePath - Path to instance directory
|
|
43
|
+
*/
|
|
44
|
+
linkSharedDirectories(instancePath) {
|
|
45
|
+
this.ensureSharedDirectories();
|
|
46
|
+
|
|
47
|
+
for (const dir of this.sharedDirs) {
|
|
48
|
+
const linkPath = path.join(instancePath, dir);
|
|
49
|
+
const targetPath = path.join(this.sharedDir, dir);
|
|
50
|
+
|
|
51
|
+
// Remove existing directory/link
|
|
52
|
+
if (fs.existsSync(linkPath)) {
|
|
53
|
+
fs.rmSync(linkPath, { recursive: true, force: true });
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// Create symlink
|
|
57
|
+
try {
|
|
58
|
+
fs.symlinkSync(targetPath, linkPath, 'dir');
|
|
59
|
+
} catch (err) {
|
|
60
|
+
// Windows fallback: copy directory if symlink fails
|
|
61
|
+
if (process.platform === 'win32') {
|
|
62
|
+
this._copyDirectory(targetPath, linkPath);
|
|
63
|
+
console.log(`[!] Symlink failed for ${dir}, copied instead (enable Developer Mode)`);
|
|
64
|
+
} else {
|
|
65
|
+
throw err;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Migrate existing instances to shared structure
|
|
73
|
+
* Idempotent: Safe to run multiple times
|
|
74
|
+
*/
|
|
75
|
+
migrateToSharedStructure() {
|
|
76
|
+
// Check if migration is needed (shared dirs exist but are empty)
|
|
77
|
+
const needsMigration = !fs.existsSync(this.sharedDir) ||
|
|
78
|
+
this.sharedDirs.every(dir => {
|
|
79
|
+
const dirPath = path.join(this.sharedDir, dir);
|
|
80
|
+
if (!fs.existsSync(dirPath)) return true;
|
|
81
|
+
try {
|
|
82
|
+
const files = fs.readdirSync(dirPath);
|
|
83
|
+
return files.length === 0; // Empty directory needs migration
|
|
84
|
+
} catch (err) {
|
|
85
|
+
return true; // If we can't read it, assume it needs migration
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
if (!needsMigration) {
|
|
90
|
+
return; // Already migrated with content
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Create shared directories
|
|
94
|
+
this.ensureSharedDirectories();
|
|
95
|
+
|
|
96
|
+
// Copy from ~/.claude/ (actual Claude CLI directory)
|
|
97
|
+
const claudeDir = path.join(this.homeDir, '.claude');
|
|
98
|
+
|
|
99
|
+
if (fs.existsSync(claudeDir)) {
|
|
100
|
+
// Copy commands to shared (if exists)
|
|
101
|
+
const commandsPath = path.join(claudeDir, 'commands');
|
|
102
|
+
if (fs.existsSync(commandsPath)) {
|
|
103
|
+
this._copyDirectory(commandsPath, path.join(this.sharedDir, 'commands'));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Copy skills to shared (if exists)
|
|
107
|
+
const skillsPath = path.join(claudeDir, 'skills');
|
|
108
|
+
if (fs.existsSync(skillsPath)) {
|
|
109
|
+
this._copyDirectory(skillsPath, path.join(this.sharedDir, 'skills'));
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Copy agents to shared (if exists)
|
|
113
|
+
const agentsPath = path.join(claudeDir, 'agents');
|
|
114
|
+
if (fs.existsSync(agentsPath)) {
|
|
115
|
+
this._copyDirectory(agentsPath, path.join(this.sharedDir, 'agents'));
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Update all instances to use symlinks
|
|
120
|
+
if (fs.existsSync(this.instancesDir)) {
|
|
121
|
+
const instances = fs.readdirSync(this.instancesDir);
|
|
122
|
+
|
|
123
|
+
for (const instance of instances) {
|
|
124
|
+
const instancePath = path.join(this.instancesDir, instance);
|
|
125
|
+
if (fs.statSync(instancePath).isDirectory()) {
|
|
126
|
+
this.linkSharedDirectories(instancePath);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
console.log('[OK] Migrated to shared structure');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Copy directory recursively (fallback for Windows)
|
|
136
|
+
* @param {string} src - Source directory
|
|
137
|
+
* @param {string} dest - Destination directory
|
|
138
|
+
* @private
|
|
139
|
+
*/
|
|
140
|
+
_copyDirectory(src, dest) {
|
|
141
|
+
if (!fs.existsSync(src)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (!fs.existsSync(dest)) {
|
|
146
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
150
|
+
|
|
151
|
+
for (const entry of entries) {
|
|
152
|
+
const srcPath = path.join(src, entry.name);
|
|
153
|
+
const destPath = path.join(dest, entry.name);
|
|
154
|
+
|
|
155
|
+
if (entry.isDirectory()) {
|
|
156
|
+
this._copyDirectory(srcPath, destPath);
|
|
157
|
+
} else {
|
|
158
|
+
fs.copyFileSync(srcPath, destPath);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
module.exports = SharedManager;
|
package/lib/ccs
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
set -euo pipefail
|
|
3
3
|
|
|
4
4
|
# Version (updated by scripts/bump-version.sh)
|
|
5
|
-
CCS_VERSION="3.0
|
|
5
|
+
CCS_VERSION="3.1.0"
|
|
6
6
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
7
7
|
readonly CONFIG_FILE="${CCS_CONFIG:-$HOME/.ccs/config.json}"
|
|
8
8
|
readonly PROFILES_JSON="$HOME/.ccs/profiles.json"
|
|
@@ -55,14 +55,9 @@ show_help() {
|
|
|
55
55
|
echo -e " ${YELLOW}ccs glm${RESET} \"debug this code\" Use GLM and run command"
|
|
56
56
|
echo ""
|
|
57
57
|
echo -e "${CYAN}Account Management:${RESET}"
|
|
58
|
-
echo -e " ${YELLOW}ccs auth
|
|
59
|
-
echo -e " ${YELLOW}ccs auth list${RESET} List all profiles"
|
|
60
|
-
echo -e " ${YELLOW}ccs auth show <profile>${RESET} Show profile details"
|
|
61
|
-
echo -e " ${YELLOW}ccs auth remove <profile>${RESET} Remove profile (requires --force)"
|
|
62
|
-
echo -e " ${YELLOW}ccs auth default <profile>${RESET} Set default profile"
|
|
58
|
+
echo -e " ${YELLOW}ccs auth --help${RESET} Manage multiple Claude accounts"
|
|
63
59
|
echo -e " ${YELLOW}ccs work${RESET} Switch to work account"
|
|
64
60
|
echo -e " ${YELLOW}ccs personal${RESET} Switch to personal account"
|
|
65
|
-
echo -e " ${YELLOW}ccs work${RESET} \"review code\" Run command with work account"
|
|
66
61
|
echo ""
|
|
67
62
|
echo -e "${CYAN}Diagnostics:${RESET}"
|
|
68
63
|
echo -e " ${YELLOW}ccs doctor${RESET} Run health check and diagnostics"
|
|
@@ -72,19 +67,15 @@ show_help() {
|
|
|
72
67
|
echo -e " ${YELLOW}-v, --version${RESET} Show version and installation info"
|
|
73
68
|
echo ""
|
|
74
69
|
echo -e "${CYAN}Configuration:${RESET}"
|
|
75
|
-
echo -e " Config:
|
|
76
|
-
echo -e " Profiles:
|
|
77
|
-
echo -e "
|
|
70
|
+
echo -e " Config: ~/.ccs/config.json"
|
|
71
|
+
echo -e " Profiles: ~/.ccs/profiles.json"
|
|
72
|
+
echo -e " Instances: ~/.ccs/instances/"
|
|
73
|
+
echo -e " Settings: ~/.ccs/*.settings.json"
|
|
78
74
|
echo ""
|
|
79
|
-
echo -e "${CYAN}
|
|
80
|
-
echo -e "
|
|
81
|
-
echo -e "
|
|
82
|
-
echo ""
|
|
83
|
-
echo -e " # Use work account"
|
|
84
|
-
echo -e " ${YELLOW}ccs work${RESET} \"Review architecture\""
|
|
85
|
-
echo ""
|
|
86
|
-
echo -e " # Switch to GLM for cost-effective tasks"
|
|
87
|
-
echo -e " ${YELLOW}ccs glm${RESET} \"Write unit tests\""
|
|
75
|
+
echo -e "${CYAN}Shared Data:${RESET}"
|
|
76
|
+
echo -e " Commands: ~/.ccs/shared/commands/"
|
|
77
|
+
echo -e " Skills: ~/.ccs/shared/skills/"
|
|
78
|
+
echo -e " Note: Commands, skills, and agents are symlinked across all profiles"
|
|
88
79
|
echo ""
|
|
89
80
|
echo -e "${CYAN}Documentation:${RESET}"
|
|
90
81
|
echo -e " GitHub: ${CYAN}https://github.com/kaitranntt/ccs${RESET}"
|
|
@@ -428,11 +419,9 @@ register_profile() {
|
|
|
428
419
|
"last_used": null
|
|
429
420
|
}')
|
|
430
421
|
|
|
431
|
-
#
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
data=$(echo "$data" | jq --arg name "$profile_name" '.default = $name')
|
|
435
|
-
fi
|
|
422
|
+
# Note: No longer auto-set as default
|
|
423
|
+
# Users must explicitly run: ccs auth default <profile>
|
|
424
|
+
# Default always stays on implicit 'default' profile (uses ~/.claude/)
|
|
436
425
|
|
|
437
426
|
write_profiles_json "$data"
|
|
438
427
|
}
|
|
@@ -510,6 +499,73 @@ sanitize_profile_name() {
|
|
|
510
499
|
echo "$name" | tr '[:upper:]' '[:lower:]' | sed 's/[^a-z0-9_-]/-/g'
|
|
511
500
|
}
|
|
512
501
|
|
|
502
|
+
# Link shared directories (Phase 1: Shared Global Data)
|
|
503
|
+
link_shared_directories() {
|
|
504
|
+
local instance_path="$1"
|
|
505
|
+
local shared_dir="$HOME/.ccs/shared"
|
|
506
|
+
|
|
507
|
+
# Ensure shared directories exist
|
|
508
|
+
mkdir -p "$shared_dir"/{commands,skills,agents}
|
|
509
|
+
|
|
510
|
+
# Create symlinks (remove existing first if present)
|
|
511
|
+
for dir in commands skills agents; do
|
|
512
|
+
local link_path="$instance_path/$dir"
|
|
513
|
+
local target_path="$shared_dir/$dir"
|
|
514
|
+
|
|
515
|
+
# Remove existing directory/link
|
|
516
|
+
[[ -e "$link_path" ]] && rm -rf "$link_path"
|
|
517
|
+
|
|
518
|
+
# Create symlink
|
|
519
|
+
ln -sf "$target_path" "$link_path"
|
|
520
|
+
done
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
# Migrate to shared structure (Phase 1: Auto-migration)
|
|
524
|
+
migrate_to_shared_structure() {
|
|
525
|
+
local shared_dir="$HOME/.ccs/shared"
|
|
526
|
+
|
|
527
|
+
# Check if migration is needed (shared dirs exist but are empty)
|
|
528
|
+
if [[ -d "$shared_dir" ]]; then
|
|
529
|
+
local needs_migration=false
|
|
530
|
+
for dir in commands skills agents; do
|
|
531
|
+
if [[ ! -d "$shared_dir/$dir" ]] || [[ -z "$(ls -A "$shared_dir/$dir" 2>/dev/null)" ]]; then
|
|
532
|
+
needs_migration=true
|
|
533
|
+
break
|
|
534
|
+
fi
|
|
535
|
+
done
|
|
536
|
+
|
|
537
|
+
[[ "$needs_migration" == "false" ]] && return 0
|
|
538
|
+
fi
|
|
539
|
+
|
|
540
|
+
# Create shared directory
|
|
541
|
+
mkdir -p "$shared_dir"/{commands,skills,agents}
|
|
542
|
+
|
|
543
|
+
# Copy from ~/.claude/ (actual Claude CLI directory)
|
|
544
|
+
local claude_dir="$HOME/.claude"
|
|
545
|
+
|
|
546
|
+
if [[ -d "$claude_dir" ]]; then
|
|
547
|
+
# Copy commands to shared (if exists)
|
|
548
|
+
[[ -d "$claude_dir/commands" ]] && \
|
|
549
|
+
cp -r "$claude_dir/commands"/* "$shared_dir/commands/" 2>/dev/null || true
|
|
550
|
+
|
|
551
|
+
# Copy skills to shared (if exists)
|
|
552
|
+
[[ -d "$claude_dir/skills" ]] && \
|
|
553
|
+
cp -r "$claude_dir/skills"/* "$shared_dir/skills/" 2>/dev/null || true
|
|
554
|
+
|
|
555
|
+
# Copy agents to shared (if exists)
|
|
556
|
+
[[ -d "$claude_dir/agents" ]] && \
|
|
557
|
+
cp -r "$claude_dir/agents"/* "$shared_dir/agents/" 2>/dev/null || true
|
|
558
|
+
fi
|
|
559
|
+
|
|
560
|
+
# Update all instances to use symlinks
|
|
561
|
+
for instance_path in "$INSTANCES_DIR"/*; do
|
|
562
|
+
[[ -d "$instance_path" ]] || continue
|
|
563
|
+
link_shared_directories "$instance_path"
|
|
564
|
+
done
|
|
565
|
+
|
|
566
|
+
echo "[OK] Migrated to shared structure"
|
|
567
|
+
}
|
|
568
|
+
|
|
513
569
|
# Initialize new instance directory
|
|
514
570
|
initialize_instance() {
|
|
515
571
|
local instance_path="$1"
|
|
@@ -517,12 +573,15 @@ initialize_instance() {
|
|
|
517
573
|
# Create base directory
|
|
518
574
|
mkdir -m 0700 -p "$instance_path"
|
|
519
575
|
|
|
520
|
-
# Create subdirectories
|
|
521
|
-
local subdirs=(session-env todos logs file-history shell-snapshots debug .anthropic
|
|
576
|
+
# Create subdirectories (profile-specific only)
|
|
577
|
+
local subdirs=(session-env todos logs file-history shell-snapshots debug .anthropic)
|
|
522
578
|
for dir in "${subdirs[@]}"; do
|
|
523
579
|
mkdir -m 0700 -p "$instance_path/$dir"
|
|
524
580
|
done
|
|
525
581
|
|
|
582
|
+
# Symlink shared directories
|
|
583
|
+
link_shared_directories "$instance_path"
|
|
584
|
+
|
|
526
585
|
# Copy global configs (optional)
|
|
527
586
|
copy_global_configs "$instance_path"
|
|
528
587
|
}
|
|
@@ -544,17 +603,9 @@ copy_global_configs() {
|
|
|
544
603
|
local instance_path="$1"
|
|
545
604
|
local global_claude="$HOME/.claude"
|
|
546
605
|
|
|
547
|
-
# Copy settings.json
|
|
606
|
+
# Copy settings.json only (commands/skills are now symlinked to shared/)
|
|
548
607
|
[[ -f "$global_claude/settings.json" ]] && \
|
|
549
608
|
cp "$global_claude/settings.json" "$instance_path/settings.json" 2>/dev/null || true
|
|
550
|
-
|
|
551
|
-
# Copy commands/
|
|
552
|
-
[[ -d "$global_claude/commands" ]] && \
|
|
553
|
-
cp -r "$global_claude/commands" "$instance_path/" 2>/dev/null || true
|
|
554
|
-
|
|
555
|
-
# Copy skills/
|
|
556
|
-
[[ -d "$global_claude/skills" ]] && \
|
|
557
|
-
cp -r "$global_claude/skills" "$instance_path/" 2>/dev/null || true
|
|
558
609
|
}
|
|
559
610
|
|
|
560
611
|
# Ensure instance exists (lazy initialization)
|
|
@@ -687,9 +738,15 @@ auth_help() {
|
|
|
687
738
|
echo -e " ${YELLOW}default <profile>${RESET} Set default profile"
|
|
688
739
|
echo ""
|
|
689
740
|
echo -e "${CYAN}Examples:${RESET}"
|
|
690
|
-
echo -e " ${YELLOW}ccs auth create work${RESET}"
|
|
691
|
-
echo -e " ${YELLOW}ccs auth
|
|
692
|
-
echo -e " ${YELLOW}ccs auth
|
|
741
|
+
echo -e " ${YELLOW}ccs auth create work${RESET} # Create & login to work profile"
|
|
742
|
+
echo -e " ${YELLOW}ccs auth default work${RESET} # Set work as default"
|
|
743
|
+
echo -e " ${YELLOW}ccs auth list${RESET} # List all profiles"
|
|
744
|
+
echo -e " ${YELLOW}ccs work \"review code\"${RESET} # Use work profile"
|
|
745
|
+
echo -e " ${YELLOW}ccs \"review code\"${RESET} # Use default profile"
|
|
746
|
+
echo ""
|
|
747
|
+
echo -e "${CYAN}Note:${RESET}"
|
|
748
|
+
echo -e " By default, ${YELLOW}ccs${RESET} uses Claude CLI defaults from ~/.claude/"
|
|
749
|
+
echo -e " Use ${YELLOW}ccs auth default <profile>${RESET} to change the default profile."
|
|
693
750
|
echo ""
|
|
694
751
|
}
|
|
695
752
|
|
|
@@ -749,7 +806,10 @@ auth_create() {
|
|
|
749
806
|
echo " Instance: $instance_path"
|
|
750
807
|
echo ""
|
|
751
808
|
echo "Usage:"
|
|
752
|
-
echo " ${YELLOW}ccs $profile_name \"your prompt here\"${RESET}"
|
|
809
|
+
echo " ${YELLOW}ccs $profile_name \"your prompt here\"${RESET} # Use this specific profile"
|
|
810
|
+
echo ""
|
|
811
|
+
echo "To set as default (so you can use just \"ccs\"):"
|
|
812
|
+
echo " ${YELLOW}ccs auth default $profile_name${RESET}"
|
|
753
813
|
echo ""
|
|
754
814
|
}
|
|
755
815
|
|
|
@@ -950,6 +1010,9 @@ auto_recover || {
|
|
|
950
1010
|
exit 1
|
|
951
1011
|
}
|
|
952
1012
|
|
|
1013
|
+
# Run migration to shared structure (Phase 1: idempotent)
|
|
1014
|
+
migrate_to_shared_structure
|
|
1015
|
+
|
|
953
1016
|
# Smart profile detection: if first arg starts with '-', it's a flag not a profile
|
|
954
1017
|
if [[ $# -eq 0 ]] || [[ "${1}" =~ ^- ]]; then
|
|
955
1018
|
# No args or first arg is a flag → use default profile
|
package/lib/ccs.ps1
CHANGED
|
@@ -12,7 +12,7 @@ param(
|
|
|
12
12
|
$ErrorActionPreference = "Stop"
|
|
13
13
|
|
|
14
14
|
# Version (updated by scripts/bump-version.sh)
|
|
15
|
-
$CcsVersion = "3.0
|
|
15
|
+
$CcsVersion = "3.1.0"
|
|
16
16
|
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
|
17
17
|
$ConfigFile = if ($env:CCS_CONFIG) { $env:CCS_CONFIG } else { "$env:USERPROFILE\.ccs\config.json" }
|
|
18
18
|
$ProfilesJson = "$env:USERPROFILE\.ccs\profiles.json"
|
|
@@ -111,14 +111,9 @@ function Show-Help {
|
|
|
111
111
|
Write-ColorLine " ccs glm 'debug this code' Use GLM and run command" "Yellow"
|
|
112
112
|
Write-Host ""
|
|
113
113
|
Write-ColorLine "Account Management:" "Cyan"
|
|
114
|
-
Write-ColorLine " ccs auth
|
|
115
|
-
Write-ColorLine " ccs auth list List all profiles" "Yellow"
|
|
116
|
-
Write-ColorLine " ccs auth show <profile> Show profile details" "Yellow"
|
|
117
|
-
Write-ColorLine " ccs auth remove <profile> Remove profile (requires --force)" "Yellow"
|
|
118
|
-
Write-ColorLine " ccs auth default <profile> Set default profile" "Yellow"
|
|
114
|
+
Write-ColorLine " ccs auth --help Manage multiple Claude accounts" "Yellow"
|
|
119
115
|
Write-ColorLine " ccs work Switch to work account" "Yellow"
|
|
120
116
|
Write-ColorLine " ccs personal Switch to personal account" "Yellow"
|
|
121
|
-
Write-ColorLine " ccs work 'review code' Run command with work account" "Yellow"
|
|
122
117
|
Write-Host ""
|
|
123
118
|
Write-ColorLine "Diagnostics:" "Cyan"
|
|
124
119
|
Write-ColorLine " ccs doctor Run health check and diagnostics" "Yellow"
|
|
@@ -128,19 +123,15 @@ function Show-Help {
|
|
|
128
123
|
Write-ColorLine " -v, --version Show version and installation info" "Yellow"
|
|
129
124
|
Write-Host ""
|
|
130
125
|
Write-ColorLine "Configuration:" "Cyan"
|
|
131
|
-
Write-Host " Config:
|
|
132
|
-
Write-Host " Profiles:
|
|
133
|
-
Write-Host "
|
|
126
|
+
Write-Host " Config: ~/.ccs/config.json"
|
|
127
|
+
Write-Host " Profiles: ~/.ccs/profiles.json"
|
|
128
|
+
Write-Host " Instances: ~/.ccs/instances/"
|
|
129
|
+
Write-Host " Settings: ~/.ccs/*.settings.json"
|
|
134
130
|
Write-Host ""
|
|
135
|
-
Write-ColorLine "
|
|
136
|
-
Write-Host "
|
|
137
|
-
Write-
|
|
138
|
-
Write-Host ""
|
|
139
|
-
Write-Host " # Use work account"
|
|
140
|
-
Write-ColorLine " ccs work 'Review architecture'" "Yellow"
|
|
141
|
-
Write-Host ""
|
|
142
|
-
Write-Host " # Switch to GLM for cost-effective tasks"
|
|
143
|
-
Write-ColorLine " ccs glm 'Write unit tests'" "Yellow"
|
|
131
|
+
Write-ColorLine "Shared Data:" "Cyan"
|
|
132
|
+
Write-Host " Commands: ~/.ccs/shared/commands/"
|
|
133
|
+
Write-Host " Skills: ~/.ccs/shared/skills/"
|
|
134
|
+
Write-Host " Note: Commands, skills, and agents are symlinked across all profiles"
|
|
144
135
|
Write-Host ""
|
|
145
136
|
Write-ColorLine "Documentation:" "Cyan"
|
|
146
137
|
Write-Host " GitHub: https://github.com/kaitranntt/ccs"
|
|
@@ -350,10 +341,9 @@ function Register-Profile {
|
|
|
350
341
|
last_used = $null
|
|
351
342
|
})
|
|
352
343
|
|
|
353
|
-
#
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
}
|
|
344
|
+
# Note: No longer auto-set as default
|
|
345
|
+
# Users must explicitly run: ccs auth default <profile>
|
|
346
|
+
# Default always stays on implicit 'default' profile (uses ~/.claude/)
|
|
357
347
|
|
|
358
348
|
Write-ProfilesJson $Data
|
|
359
349
|
}
|
|
@@ -438,28 +428,106 @@ function Set-InstancePermissions {
|
|
|
438
428
|
Set-Acl -Path $Path -AclObject $Acl
|
|
439
429
|
}
|
|
440
430
|
|
|
431
|
+
function Link-SharedDirectories {
|
|
432
|
+
param([string]$InstancePath)
|
|
433
|
+
|
|
434
|
+
$SharedDir = "$env:USERPROFILE\.ccs\shared"
|
|
435
|
+
|
|
436
|
+
# Ensure shared directories exist
|
|
437
|
+
@('commands', 'skills', 'agents') | ForEach-Object {
|
|
438
|
+
$Dir = Join-Path $SharedDir $_
|
|
439
|
+
if (-not (Test-Path $Dir)) {
|
|
440
|
+
New-Item -ItemType Directory -Path $Dir -Force | Out-Null
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
# Create symlinks (requires Windows Developer Mode or admin)
|
|
445
|
+
@('commands', 'skills', 'agents') | ForEach-Object {
|
|
446
|
+
$LinkPath = Join-Path $InstancePath $_
|
|
447
|
+
$TargetPath = Join-Path $SharedDir $_
|
|
448
|
+
|
|
449
|
+
# Remove existing directory/link
|
|
450
|
+
if (Test-Path $LinkPath) {
|
|
451
|
+
Remove-Item $LinkPath -Recurse -Force
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
# Try creating symlink (requires privileges)
|
|
455
|
+
try {
|
|
456
|
+
New-Item -ItemType SymbolicLink -Path $LinkPath -Target $TargetPath -Force | Out-Null
|
|
457
|
+
} catch {
|
|
458
|
+
# Fallback: Copy directory instead (suboptimal but functional)
|
|
459
|
+
Copy-Item $TargetPath -Destination $LinkPath -Recurse -Force
|
|
460
|
+
Write-Host "[!] Symlink failed for $_, copied instead (enable Developer Mode)" -ForegroundColor Yellow
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
function Migrate-SharedStructure {
|
|
466
|
+
$SharedDir = "$env:USERPROFILE\.ccs\shared"
|
|
467
|
+
|
|
468
|
+
# Check if migration is needed (shared dirs exist but are empty)
|
|
469
|
+
if (Test-Path $SharedDir) {
|
|
470
|
+
$NeedsMigration = $false
|
|
471
|
+
foreach ($Dir in @('commands', 'skills', 'agents')) {
|
|
472
|
+
$DirPath = Join-Path $SharedDir $Dir
|
|
473
|
+
if (-not (Test-Path $DirPath) -or (Get-ChildItem $DirPath -ErrorAction SilentlyContinue).Count -eq 0) {
|
|
474
|
+
$NeedsMigration = $true
|
|
475
|
+
break
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
if (-not $NeedsMigration) { return }
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
# Create shared directory
|
|
483
|
+
@('commands', 'skills', 'agents') | ForEach-Object {
|
|
484
|
+
$Dir = Join-Path $SharedDir $_
|
|
485
|
+
New-Item -ItemType Directory -Path $Dir -Force | Out-Null
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
# Copy from ~/.claude/ (actual Claude CLI directory)
|
|
489
|
+
$ClaudeDir = "$env:USERPROFILE\.claude"
|
|
490
|
+
|
|
491
|
+
if (Test-Path $ClaudeDir) {
|
|
492
|
+
# Copy commands to shared (if exists)
|
|
493
|
+
$CommandsPath = Join-Path $ClaudeDir "commands"
|
|
494
|
+
if (Test-Path $CommandsPath) {
|
|
495
|
+
Copy-Item "$CommandsPath\*" -Destination "$SharedDir\commands\" -Recurse -ErrorAction SilentlyContinue
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
# Copy skills to shared (if exists)
|
|
499
|
+
$SkillsPath = Join-Path $ClaudeDir "skills"
|
|
500
|
+
if (Test-Path $SkillsPath) {
|
|
501
|
+
Copy-Item "$SkillsPath\*" -Destination "$SharedDir\skills\" -Recurse -ErrorAction SilentlyContinue
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
# Copy agents to shared (if exists)
|
|
505
|
+
$AgentsPath = Join-Path $ClaudeDir "agents"
|
|
506
|
+
if (Test-Path $AgentsPath) {
|
|
507
|
+
Copy-Item "$AgentsPath\*" -Destination "$SharedDir\agents\" -Recurse -ErrorAction SilentlyContinue
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
# Update all instances to use symlinks
|
|
512
|
+
if (Test-Path $InstancesDir) {
|
|
513
|
+
Get-ChildItem $InstancesDir -Directory | ForEach-Object {
|
|
514
|
+
Link-SharedDirectories $_.FullName
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
Write-Host "[OK] Migrated to shared structure"
|
|
519
|
+
}
|
|
520
|
+
|
|
441
521
|
function Copy-GlobalConfigs {
|
|
442
522
|
param([string]$InstancePath)
|
|
443
523
|
|
|
444
524
|
$GlobalClaude = "$env:USERPROFILE\.claude"
|
|
445
525
|
|
|
446
|
-
# Copy settings.json
|
|
526
|
+
# Copy settings.json only (commands/skills are now symlinked to shared/)
|
|
447
527
|
$GlobalSettings = Join-Path $GlobalClaude "settings.json"
|
|
448
528
|
if (Test-Path $GlobalSettings) {
|
|
449
529
|
Copy-Item $GlobalSettings -Destination (Join-Path $InstancePath "settings.json") -ErrorAction SilentlyContinue
|
|
450
530
|
}
|
|
451
|
-
|
|
452
|
-
# Copy commands/
|
|
453
|
-
$GlobalCommands = Join-Path $GlobalClaude "commands"
|
|
454
|
-
if (Test-Path $GlobalCommands) {
|
|
455
|
-
Copy-Item $GlobalCommands -Destination $InstancePath -Recurse -ErrorAction SilentlyContinue
|
|
456
|
-
}
|
|
457
|
-
|
|
458
|
-
# Copy skills/
|
|
459
|
-
$GlobalSkills = Join-Path $GlobalClaude "skills"
|
|
460
|
-
if (Test-Path $GlobalSkills) {
|
|
461
|
-
Copy-Item $GlobalSkills -Destination $InstancePath -Recurse -ErrorAction SilentlyContinue
|
|
462
|
-
}
|
|
463
531
|
}
|
|
464
532
|
|
|
465
533
|
function Initialize-Instance {
|
|
@@ -469,15 +537,18 @@ function Initialize-Instance {
|
|
|
469
537
|
New-Item -ItemType Directory -Path $InstancePath -Force | Out-Null
|
|
470
538
|
Set-InstancePermissions $InstancePath
|
|
471
539
|
|
|
472
|
-
# Create subdirectories
|
|
540
|
+
# Create subdirectories (profile-specific only)
|
|
473
541
|
$Subdirs = @('session-env', 'todos', 'logs', 'file-history',
|
|
474
|
-
'shell-snapshots', 'debug', '.anthropic'
|
|
542
|
+
'shell-snapshots', 'debug', '.anthropic')
|
|
475
543
|
|
|
476
544
|
foreach ($Dir in $Subdirs) {
|
|
477
545
|
$DirPath = Join-Path $InstancePath $Dir
|
|
478
546
|
New-Item -ItemType Directory -Path $DirPath -Force | Out-Null
|
|
479
547
|
}
|
|
480
548
|
|
|
549
|
+
# Symlink shared directories
|
|
550
|
+
Link-SharedDirectories $InstancePath
|
|
551
|
+
|
|
481
552
|
# Copy global configs
|
|
482
553
|
Copy-GlobalConfigs $InstancePath
|
|
483
554
|
}
|
|
@@ -640,9 +711,19 @@ function Show-AuthHelp {
|
|
|
640
711
|
Write-Host " default <profile> Set default profile" -ForegroundColor Yellow
|
|
641
712
|
Write-Host ""
|
|
642
713
|
Write-Host "Examples:" -ForegroundColor Cyan
|
|
643
|
-
Write-Host " ccs auth create work" -ForegroundColor Yellow
|
|
644
|
-
Write-Host " ccs auth
|
|
645
|
-
Write-Host " ccs auth
|
|
714
|
+
Write-Host " ccs auth create work # Create & login to work profile" -ForegroundColor Yellow
|
|
715
|
+
Write-Host " ccs auth default work # Set work as default" -ForegroundColor Yellow
|
|
716
|
+
Write-Host " ccs auth list # List all profiles" -ForegroundColor Yellow
|
|
717
|
+
Write-Host ' ccs work "review code" # Use work profile' -ForegroundColor Yellow
|
|
718
|
+
Write-Host ' ccs "review code" # Use default profile' -ForegroundColor Yellow
|
|
719
|
+
Write-Host ""
|
|
720
|
+
Write-Host "Note:" -ForegroundColor Cyan
|
|
721
|
+
Write-Host " By default, " -NoNewline
|
|
722
|
+
Write-Host "ccs" -ForegroundColor Yellow -NoNewline
|
|
723
|
+
Write-Host " uses Claude CLI defaults from ~/.claude/"
|
|
724
|
+
Write-Host " Use " -NoNewline
|
|
725
|
+
Write-Host "ccs auth default <profile>" -ForegroundColor Yellow -NoNewline
|
|
726
|
+
Write-Host " to change the default profile."
|
|
646
727
|
Write-Host ""
|
|
647
728
|
}
|
|
648
729
|
|
|
@@ -703,7 +784,10 @@ function Invoke-AuthCreate {
|
|
|
703
784
|
Write-Host " Instance: $InstancePath"
|
|
704
785
|
Write-Host ""
|
|
705
786
|
Write-Host "Usage:"
|
|
706
|
-
Write-Host " ccs $ProfileName `"your prompt here`"" -ForegroundColor Yellow
|
|
787
|
+
Write-Host " ccs $ProfileName `"your prompt here`" # Use this specific profile" -ForegroundColor Yellow
|
|
788
|
+
Write-Host ""
|
|
789
|
+
Write-Host 'To set as default (so you can use just "ccs"):'
|
|
790
|
+
Write-Host " ccs auth default $ProfileName" -ForegroundColor Yellow
|
|
707
791
|
Write-Host ""
|
|
708
792
|
}
|
|
709
793
|
|
|
@@ -916,6 +1000,9 @@ if (-not (Invoke-AutoRecovery)) {
|
|
|
916
1000
|
exit 1
|
|
917
1001
|
}
|
|
918
1002
|
|
|
1003
|
+
# Run migration to shared structure (Phase 1: idempotent)
|
|
1004
|
+
Migrate-SharedStructure
|
|
1005
|
+
|
|
919
1006
|
# Smart profile detection: if first arg starts with '-', it's a flag not a profile
|
|
920
1007
|
if ($RemainingArgs.Count -eq 0 -or $RemainingArgs[0] -match '^-') {
|
|
921
1008
|
# No args or first arg is a flag → use default profile
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -72,6 +72,23 @@ function createConfigFiles() {
|
|
|
72
72
|
console.log('[OK] Created directory: ~/.ccs/');
|
|
73
73
|
}
|
|
74
74
|
|
|
75
|
+
// Create ~/.ccs/shared/ directory structure (Phase 1)
|
|
76
|
+
const sharedDir = path.join(ccsDir, 'shared');
|
|
77
|
+
if (!fs.existsSync(sharedDir)) {
|
|
78
|
+
fs.mkdirSync(sharedDir, { recursive: true, mode: 0o755 });
|
|
79
|
+
console.log('[OK] Created directory: ~/.ccs/shared/');
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// Create shared subdirectories
|
|
83
|
+
const sharedSubdirs = ['commands', 'skills', 'agents'];
|
|
84
|
+
for (const subdir of sharedSubdirs) {
|
|
85
|
+
const subdirPath = path.join(sharedDir, subdir);
|
|
86
|
+
if (!fs.existsSync(subdirPath)) {
|
|
87
|
+
fs.mkdirSync(subdirPath, { recursive: true, mode: 0o755 });
|
|
88
|
+
console.log(`[OK] Created directory: ~/.ccs/shared/${subdir}/`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
75
92
|
// Create config.json if missing
|
|
76
93
|
const configPath = path.join(ccsDir, 'config.json');
|
|
77
94
|
if (!fs.existsSync(configPath)) {
|