@kaitranntt/ccs 2.4.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/LICENSE +21 -0
- package/README.md +287 -0
- package/README.vi.md +257 -0
- package/VERSION +1 -0
- package/bin/ccs.js +196 -0
- package/bin/claude-detector.js +156 -0
- package/bin/config-manager.js +129 -0
- package/bin/helpers.js +65 -0
- package/config/base-glm.settings.json +10 -0
- package/config/config.example.json +6 -0
- package/lib/ccs +414 -0
- package/lib/ccs.ps1 +493 -0
- package/package.json +52 -0
package/bin/ccs.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const { spawn } = require('child_process');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const fs = require('fs');
|
|
7
|
+
const { showError, colors } = require('./helpers');
|
|
8
|
+
const { detectClaudeCli, validateClaudeCli, showClaudeNotFoundError } = require('./claude-detector');
|
|
9
|
+
const { getSettingsPath } = require('./config-manager');
|
|
10
|
+
|
|
11
|
+
// Version (sync with package.json)
|
|
12
|
+
const CCS_VERSION = require('../package.json').version;
|
|
13
|
+
|
|
14
|
+
// Special command handlers
|
|
15
|
+
function handleVersionCommand() {
|
|
16
|
+
console.log(`CCS (Claude Code Switch) version ${CCS_VERSION}`);
|
|
17
|
+
|
|
18
|
+
// Show install location
|
|
19
|
+
const installLocation = process.argv[1];
|
|
20
|
+
if (installLocation) {
|
|
21
|
+
console.log(`Installed at: ${installLocation}`);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
console.log('https://github.com/kaitranntt/ccs');
|
|
25
|
+
process.exit(0);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function handleHelpCommand(remainingArgs) {
|
|
29
|
+
// Detect and validate Claude CLI
|
|
30
|
+
const claudeCli = detectClaudeCli();
|
|
31
|
+
|
|
32
|
+
if (!claudeCli) {
|
|
33
|
+
showClaudeNotFoundError();
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
validateClaudeCli(claudeCli);
|
|
39
|
+
} catch (e) {
|
|
40
|
+
showError(e.message);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Execute claude --help
|
|
45
|
+
const child = spawn(claudeCli, ['--help', ...remainingArgs], { stdio: 'inherit' });
|
|
46
|
+
|
|
47
|
+
child.on('exit', (code, signal) => {
|
|
48
|
+
if (signal) {
|
|
49
|
+
process.kill(process.pid, signal);
|
|
50
|
+
} else {
|
|
51
|
+
process.exit(code || 0);
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
child.on('error', (err) => {
|
|
56
|
+
console.error(`Error executing claude --help: ${err.message}`);
|
|
57
|
+
process.exit(1);
|
|
58
|
+
});
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function handleInstallCommand() {
|
|
62
|
+
// Implementation for --install (copy commands/skills to ~/.claude)
|
|
63
|
+
console.log('[Installing CCS Commands and Skills]');
|
|
64
|
+
console.log('Feature not yet implemented in Node.js standalone');
|
|
65
|
+
console.log('Use traditional installer for now:');
|
|
66
|
+
console.log(process.platform === 'win32'
|
|
67
|
+
? ' irm ccs.kaitran.ca/install | iex'
|
|
68
|
+
: ' curl -fsSL ccs.kaitran.ca/install | bash');
|
|
69
|
+
process.exit(0);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function handleUninstallCommand() {
|
|
73
|
+
// Implementation for --uninstall (remove commands/skills from ~/.claude)
|
|
74
|
+
console.log('[Uninstalling CCS Commands and Skills]');
|
|
75
|
+
console.log('Feature not yet implemented in Node.js standalone');
|
|
76
|
+
console.log('Use traditional uninstaller for now');
|
|
77
|
+
process.exit(0);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Smart profile detection
|
|
81
|
+
function detectProfile(args) {
|
|
82
|
+
if (args.length === 0 || args[0].startsWith('-')) {
|
|
83
|
+
// No args or first arg is a flag → use default profile
|
|
84
|
+
return { profile: 'default', remainingArgs: args };
|
|
85
|
+
} else {
|
|
86
|
+
// First arg doesn't start with '-' → treat as profile name
|
|
87
|
+
return { profile: args[0], remainingArgs: args.slice(1) };
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Main execution
|
|
92
|
+
function main() {
|
|
93
|
+
const args = process.argv.slice(2);
|
|
94
|
+
|
|
95
|
+
// Special case: version command (check BEFORE profile detection)
|
|
96
|
+
const firstArg = args[0];
|
|
97
|
+
if (firstArg === 'version' || firstArg === '--version' || firstArg === '-v') {
|
|
98
|
+
handleVersionCommand();
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// Special case: help command
|
|
102
|
+
if (firstArg === '--help' || firstArg === '-h' || firstArg === 'help') {
|
|
103
|
+
const remainingArgs = args.slice(1);
|
|
104
|
+
handleHelpCommand(remainingArgs);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Special case: install command
|
|
109
|
+
if (firstArg === '--install') {
|
|
110
|
+
handleInstallCommand();
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// Special case: uninstall command
|
|
115
|
+
if (firstArg === '--uninstall') {
|
|
116
|
+
handleUninstallCommand();
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Detect profile
|
|
121
|
+
const { profile, remainingArgs } = detectProfile(args);
|
|
122
|
+
|
|
123
|
+
// Special case: "default" profile just runs claude directly
|
|
124
|
+
if (profile === 'default') {
|
|
125
|
+
const claudeCli = detectClaudeCli();
|
|
126
|
+
|
|
127
|
+
if (!claudeCli) {
|
|
128
|
+
showClaudeNotFoundError();
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
validateClaudeCli(claudeCli);
|
|
134
|
+
} catch (e) {
|
|
135
|
+
showError(e.message);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Execute claude with args
|
|
140
|
+
const child = spawn(claudeCli, remainingArgs, { stdio: 'inherit' });
|
|
141
|
+
|
|
142
|
+
child.on('exit', (code, signal) => {
|
|
143
|
+
if (signal) {
|
|
144
|
+
process.kill(process.pid, signal);
|
|
145
|
+
} else {
|
|
146
|
+
process.exit(code || 0);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
child.on('error', (err) => {
|
|
151
|
+
console.error(`Error executing claude: ${err.message}`);
|
|
152
|
+
process.exit(1);
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Get settings path for profile
|
|
159
|
+
const settingsPath = getSettingsPath(profile);
|
|
160
|
+
|
|
161
|
+
// Detect Claude CLI
|
|
162
|
+
const claudeCli = detectClaudeCli();
|
|
163
|
+
|
|
164
|
+
if (!claudeCli) {
|
|
165
|
+
showClaudeNotFoundError();
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Validate Claude CLI path
|
|
170
|
+
try {
|
|
171
|
+
validateClaudeCli(claudeCli);
|
|
172
|
+
} catch (e) {
|
|
173
|
+
showError(e.message);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Execute claude with --settings
|
|
178
|
+
const claudeArgs = ['--settings', settingsPath, ...remainingArgs];
|
|
179
|
+
const child = spawn(claudeCli, claudeArgs, { stdio: 'inherit' });
|
|
180
|
+
|
|
181
|
+
child.on('exit', (code, signal) => {
|
|
182
|
+
if (signal) {
|
|
183
|
+
process.kill(process.pid, signal);
|
|
184
|
+
} else {
|
|
185
|
+
process.exit(code || 0);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
child.on('error', (err) => {
|
|
190
|
+
console.error(`Error executing claude: ${err.message}`);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// Run main
|
|
196
|
+
main();
|
|
@@ -0,0 +1,156 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const { execSync } = require('child_process');
|
|
6
|
+
const { showError, expandPath, isPathSafe } = require('./helpers');
|
|
7
|
+
|
|
8
|
+
// Detect Claude CLI executable
|
|
9
|
+
function detectClaudeCli() {
|
|
10
|
+
// Priority 1: CCS_CLAUDE_PATH environment variable
|
|
11
|
+
if (process.env.CCS_CLAUDE_PATH) {
|
|
12
|
+
const ccsPath = expandPath(process.env.CCS_CLAUDE_PATH);
|
|
13
|
+
if (fs.existsSync(ccsPath) && isExecutable(ccsPath)) {
|
|
14
|
+
return ccsPath;
|
|
15
|
+
}
|
|
16
|
+
// Invalid CCS_CLAUDE_PATH - continue to fallbacks
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Priority 2: Check if claude in PATH
|
|
20
|
+
try {
|
|
21
|
+
const claudePath = execSync(
|
|
22
|
+
process.platform === 'win32' ? 'where claude' : 'which claude',
|
|
23
|
+
{ encoding: 'utf8', stdio: ['pipe', 'pipe', 'ignore'] }
|
|
24
|
+
).trim().split('\n')[0];
|
|
25
|
+
|
|
26
|
+
if (claudePath && fs.existsSync(claudePath)) {
|
|
27
|
+
return claudePath;
|
|
28
|
+
}
|
|
29
|
+
} catch (e) {
|
|
30
|
+
// Not in PATH, continue to common locations
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Priority 3: Check common installation locations
|
|
34
|
+
const commonLocations = getCommonLocations();
|
|
35
|
+
|
|
36
|
+
for (const location of commonLocations) {
|
|
37
|
+
const expandedPath = expandPath(location);
|
|
38
|
+
if (fs.existsSync(expandedPath) && isExecutable(expandedPath)) {
|
|
39
|
+
return expandedPath;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Not found
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Get platform-specific common locations
|
|
48
|
+
function getCommonLocations() {
|
|
49
|
+
const home = require('os').homedir();
|
|
50
|
+
|
|
51
|
+
if (process.platform === 'win32') {
|
|
52
|
+
return [
|
|
53
|
+
path.join(process.env.LOCALAPPDATA || '', 'Claude', 'claude.exe'),
|
|
54
|
+
path.join(process.env.PROGRAMFILES || '', 'Claude', 'claude.exe'),
|
|
55
|
+
'C:\\Program Files\\Claude\\claude.exe',
|
|
56
|
+
'D:\\Program Files\\Claude\\claude.exe',
|
|
57
|
+
path.join(home, '.local', 'bin', 'claude.exe')
|
|
58
|
+
];
|
|
59
|
+
} else if (process.platform === 'darwin') {
|
|
60
|
+
return [
|
|
61
|
+
'/usr/local/bin/claude',
|
|
62
|
+
path.join(home, '.local/bin/claude'),
|
|
63
|
+
'/opt/homebrew/bin/claude'
|
|
64
|
+
];
|
|
65
|
+
} else {
|
|
66
|
+
return [
|
|
67
|
+
'/usr/local/bin/claude',
|
|
68
|
+
path.join(home, '.local/bin/claude'),
|
|
69
|
+
'/usr/bin/claude'
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Check if file is executable
|
|
75
|
+
function isExecutable(filePath) {
|
|
76
|
+
try {
|
|
77
|
+
fs.accessSync(filePath, fs.constants.X_OK);
|
|
78
|
+
return true;
|
|
79
|
+
} catch (e) {
|
|
80
|
+
return false;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Validate Claude CLI path
|
|
85
|
+
function validateClaudeCli(claudePath) {
|
|
86
|
+
// Check 1: Empty path
|
|
87
|
+
if (!claudePath) {
|
|
88
|
+
throw new Error('No path provided');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check 2: File exists
|
|
92
|
+
if (!fs.existsSync(claudePath)) {
|
|
93
|
+
throw new Error(`File not found: ${claudePath}`);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Check 3: Is regular file (not directory)
|
|
97
|
+
const stats = fs.statSync(claudePath);
|
|
98
|
+
if (!stats.isFile()) {
|
|
99
|
+
throw new Error(`Path is a directory: ${claudePath}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Check 4: Is executable
|
|
103
|
+
if (!isExecutable(claudePath)) {
|
|
104
|
+
throw new Error(`File is not executable: ${claudePath}\n\nTry: chmod +x ${claudePath}`);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// Check 5: Path safety (prevent injection)
|
|
108
|
+
if (!isPathSafe(claudePath)) {
|
|
109
|
+
throw new Error(`Path contains unsafe characters: ${claudePath}\n\nAllowed: alphanumeric, path separators, spaces, hyphens, underscores, dots`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return true;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// Show Claude not found error
|
|
116
|
+
function showClaudeNotFoundError() {
|
|
117
|
+
const envVarStatus = process.env.CCS_CLAUDE_PATH || '(not set)';
|
|
118
|
+
const isWindows = process.platform === 'win32';
|
|
119
|
+
|
|
120
|
+
const errorMsg = `Claude CLI not found
|
|
121
|
+
|
|
122
|
+
Searched:
|
|
123
|
+
- CCS_CLAUDE_PATH: ${envVarStatus}
|
|
124
|
+
- System PATH: not found
|
|
125
|
+
- Common locations: not found
|
|
126
|
+
|
|
127
|
+
Solutions:
|
|
128
|
+
1. Add Claude CLI to PATH:
|
|
129
|
+
|
|
130
|
+
${isWindows
|
|
131
|
+
? '# Find where Claude is installed\n Get-ChildItem -Path C:\\,D:\\ -Filter claude.exe -Recurse\n\n # Add to PATH\n $env:Path += \';D:\\path\\to\\claude\\directory\'\n [Environment]::SetEnvironmentVariable(\'Path\', $env:Path, \'User\')'
|
|
132
|
+
: '# Find where Claude is installed\n sudo find / -name claude 2>/dev/null\n\n # Add to PATH\n export PATH="/path/to/claude/bin:$PATH"\n echo \'export PATH="/path/to/claude/bin:$PATH"\' >> ~/.bashrc\n source ~/.bashrc'
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
2. Or set custom path:
|
|
136
|
+
|
|
137
|
+
${isWindows
|
|
138
|
+
? '$env:CCS_CLAUDE_PATH = \'D:\\full\\path\\to\\claude.exe\'\n [Environment]::SetEnvironmentVariable(\'CCS_CLAUDE_PATH\', \'D:\\full\\path\\to\\claude.exe\', \'User\')'
|
|
139
|
+
: 'export CCS_CLAUDE_PATH="/full/path/to/claude"\n echo \'export CCS_CLAUDE_PATH="/full/path/to/claude"\' >> ~/.bashrc\n source ~/.bashrc'
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
3. Or install Claude CLI:
|
|
143
|
+
|
|
144
|
+
https://docs.claude.com/en/docs/claude-code/installation
|
|
145
|
+
|
|
146
|
+
Verify installation:
|
|
147
|
+
ccs --version`;
|
|
148
|
+
|
|
149
|
+
showError(errorMsg);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = {
|
|
153
|
+
detectClaudeCli,
|
|
154
|
+
validateClaudeCli,
|
|
155
|
+
showClaudeNotFoundError
|
|
156
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const { showError, expandPath, validateProfileName } = require('./helpers');
|
|
7
|
+
|
|
8
|
+
// Get config file path
|
|
9
|
+
function getConfigPath() {
|
|
10
|
+
return process.env.CCS_CONFIG || path.join(os.homedir(), '.ccs', 'config.json');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Read and parse config
|
|
14
|
+
function readConfig() {
|
|
15
|
+
const configPath = getConfigPath();
|
|
16
|
+
|
|
17
|
+
// Check config exists
|
|
18
|
+
if (!fs.existsSync(configPath)) {
|
|
19
|
+
const isWindows = process.platform === 'win32';
|
|
20
|
+
showError(`Config file not found: ${configPath}
|
|
21
|
+
|
|
22
|
+
Solutions:
|
|
23
|
+
1. Reinstall CCS:
|
|
24
|
+
${isWindows ? 'irm ccs.kaitran.ca/install | iex' : 'curl -fsSL ccs.kaitran.ca/install | bash'}
|
|
25
|
+
|
|
26
|
+
2. Or create config manually:
|
|
27
|
+
mkdir -p ~/.ccs
|
|
28
|
+
cat > ~/.ccs/config.json << 'EOF'
|
|
29
|
+
{
|
|
30
|
+
"profiles": {
|
|
31
|
+
"glm": "~/.ccs/glm.settings.json",
|
|
32
|
+
"default": "~/.claude/settings.json"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
EOF`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Read and parse JSON
|
|
40
|
+
let config;
|
|
41
|
+
try {
|
|
42
|
+
const configContent = fs.readFileSync(configPath, 'utf8');
|
|
43
|
+
config = JSON.parse(configContent);
|
|
44
|
+
} catch (e) {
|
|
45
|
+
const isWindows = process.platform === 'win32';
|
|
46
|
+
showError(`Invalid JSON in ${configPath}
|
|
47
|
+
|
|
48
|
+
Fix the JSON syntax or reinstall:
|
|
49
|
+
${isWindows ? 'irm ccs.kaitran.ca/install | iex' : 'curl -fsSL ccs.kaitran.ca/install | bash'}`);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Validate config has profiles object
|
|
54
|
+
if (!config.profiles || typeof config.profiles !== 'object') {
|
|
55
|
+
const isWindows = process.platform === 'win32';
|
|
56
|
+
showError(`Config must have 'profiles' object
|
|
57
|
+
|
|
58
|
+
See config.example.json for correct format
|
|
59
|
+
Or reinstall:
|
|
60
|
+
${isWindows ? 'irm ccs.kaitran.ca/install | iex' : 'curl -fsSL ccs.kaitran.ca/install | bash'}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return config;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Get settings path for profile
|
|
68
|
+
function getSettingsPath(profile) {
|
|
69
|
+
const config = readConfig();
|
|
70
|
+
|
|
71
|
+
// Validate profile name
|
|
72
|
+
if (!validateProfileName(profile)) {
|
|
73
|
+
showError(`Invalid profile name: ${profile}
|
|
74
|
+
|
|
75
|
+
Use only alphanumeric characters, dash, or underscore.`);
|
|
76
|
+
process.exit(1);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// Get settings path
|
|
80
|
+
const settingsPath = config.profiles[profile];
|
|
81
|
+
|
|
82
|
+
if (!settingsPath) {
|
|
83
|
+
const availableProfiles = Object.keys(config.profiles).map(p => ` - ${p}`).join('\n');
|
|
84
|
+
showError(`Profile '${profile}' not found in ${getConfigPath()}
|
|
85
|
+
|
|
86
|
+
Available profiles:
|
|
87
|
+
${availableProfiles}`);
|
|
88
|
+
process.exit(1);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Expand path
|
|
92
|
+
const expandedPath = expandPath(settingsPath);
|
|
93
|
+
|
|
94
|
+
// Validate settings file exists
|
|
95
|
+
if (!fs.existsSync(expandedPath)) {
|
|
96
|
+
const isWindows = process.platform === 'win32';
|
|
97
|
+
showError(`Settings file not found: ${expandedPath}
|
|
98
|
+
|
|
99
|
+
Solutions:
|
|
100
|
+
1. Create the settings file for profile '${profile}'
|
|
101
|
+
2. Update the path in ${getConfigPath()}
|
|
102
|
+
3. Or reinstall: ${isWindows ? 'irm ccs.kaitran.ca/install | iex' : 'curl -fsSL ccs.kaitran.ca/install | bash'}`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Validate settings file is valid JSON
|
|
107
|
+
try {
|
|
108
|
+
const settingsContent = fs.readFileSync(expandedPath, 'utf8');
|
|
109
|
+
JSON.parse(settingsContent);
|
|
110
|
+
} catch (e) {
|
|
111
|
+
showError(`Invalid JSON in ${expandedPath}
|
|
112
|
+
|
|
113
|
+
Details: ${e.message}
|
|
114
|
+
|
|
115
|
+
Solutions:
|
|
116
|
+
1. Validate JSON at https://jsonlint.com
|
|
117
|
+
2. Or reset to template: echo '{"env":{}}' > ${expandedPath}
|
|
118
|
+
3. Or reinstall CCS`);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return expandedPath;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
module.exports = {
|
|
126
|
+
getConfigPath,
|
|
127
|
+
readConfig,
|
|
128
|
+
getSettingsPath
|
|
129
|
+
};
|
package/bin/helpers.js
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
|
|
7
|
+
// Color formatting (TTY-aware)
|
|
8
|
+
const useColors = process.stderr.isTTY && !process.env.NO_COLOR;
|
|
9
|
+
const colors = useColors ? {
|
|
10
|
+
red: '\x1b[0;31m',
|
|
11
|
+
yellow: '\x1b[1;33m',
|
|
12
|
+
cyan: '\x1b[0;36m',
|
|
13
|
+
green: '\x1b[0;32m',
|
|
14
|
+
bold: '\x1b[1m',
|
|
15
|
+
reset: '\x1b[0m'
|
|
16
|
+
} : { red: '', yellow: '', cyan: '', green: '', bold: '', reset: '' };
|
|
17
|
+
|
|
18
|
+
// Error formatting
|
|
19
|
+
function showError(message) {
|
|
20
|
+
console.error('');
|
|
21
|
+
console.error(colors.red + colors.bold + '╔═════════════════════════════════════════════╗' + colors.reset);
|
|
22
|
+
console.error(colors.red + colors.bold + '║ ERROR ║' + colors.reset);
|
|
23
|
+
console.error(colors.red + colors.bold + '╚═════════════════════════════════════════════╝' + colors.reset);
|
|
24
|
+
console.error('');
|
|
25
|
+
console.error(colors.red + message + colors.reset);
|
|
26
|
+
console.error('');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Path expansion (~ and env vars)
|
|
30
|
+
function expandPath(pathStr) {
|
|
31
|
+
// Handle tilde expansion
|
|
32
|
+
if (pathStr.startsWith('~/') || pathStr.startsWith('~\\')) {
|
|
33
|
+
pathStr = path.join(os.homedir(), pathStr.slice(2));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Expand environment variables (Windows and Unix)
|
|
37
|
+
pathStr = pathStr.replace(/\$\{([^}]+)\}/g, (_, name) => process.env[name] || '');
|
|
38
|
+
pathStr = pathStr.replace(/\$([A-Z_][A-Z0-9_]*)/gi, (_, name) => process.env[name] || '');
|
|
39
|
+
|
|
40
|
+
// Windows %VAR% style
|
|
41
|
+
if (process.platform === 'win32') {
|
|
42
|
+
pathStr = pathStr.replace(/%([^%]+)%/g, (_, name) => process.env[name] || '');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return path.normalize(pathStr);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Validate profile name (alphanumeric, dash, underscore only)
|
|
49
|
+
function validateProfileName(profile) {
|
|
50
|
+
return /^[a-zA-Z0-9_-]+$/.test(profile);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Validate path safety (prevent injection)
|
|
54
|
+
function isPathSafe(pathStr) {
|
|
55
|
+
// Allow: alphanumeric, path separators, space, dash, underscore, dot, colon, tilde
|
|
56
|
+
return !/[;|&<>`$*?\[\]'"()]/.test(pathStr);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = {
|
|
60
|
+
colors,
|
|
61
|
+
showError,
|
|
62
|
+
expandPath,
|
|
63
|
+
validateProfileName,
|
|
64
|
+
isPathSafe
|
|
65
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
{
|
|
2
|
+
"env": {
|
|
3
|
+
"ANTHROPIC_BASE_URL": "https://api.z.ai/api/anthropic",
|
|
4
|
+
"ANTHROPIC_AUTH_TOKEN": "YOUR_GLM_API_KEY_HERE",
|
|
5
|
+
"ANTHROPIC_MODEL": "glm-4.6",
|
|
6
|
+
"ANTHROPIC_DEFAULT_OPUS_MODEL": "glm-4.6",
|
|
7
|
+
"ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-4.6",
|
|
8
|
+
"ANTHROPIC_DEFAULT_HAIKU_MODEL": "glm-4.6"
|
|
9
|
+
}
|
|
10
|
+
}
|