ccconfig 1.3.0 → 1.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/ccconfig.js +769 -470
- package/package.json +2 -2
- package//345/260/217/347/272/242/344/271/246/346/226/207/347/253/240_KAT-Coder.md +177 -0
- package//345/260/217/347/272/242/344/271/246/346/226/207/347/253/240_KAT-Coder_/347/237/255/347/211/210.md +120 -0
- package//345/260/217/347/272/242/344/271/246/346/226/207/347/253/240_KAT-Coder_/350/266/205/347/237/255/347/211/210.txt +31 -0
package/ccconfig.js
CHANGED
|
@@ -4,6 +4,7 @@ const fs = require('fs');
|
|
|
4
4
|
const path = require('path');
|
|
5
5
|
const os = require('os');
|
|
6
6
|
const readline = require('readline');
|
|
7
|
+
const { spawn, execSync } = require('child_process');
|
|
7
8
|
|
|
8
9
|
// Configuration file paths
|
|
9
10
|
const CONFIG_DIR = path.join(os.homedir(), '.config', 'ccconfig');
|
|
@@ -16,6 +17,80 @@ const MODE_FILE = path.join(CONFIG_DIR, 'mode');
|
|
|
16
17
|
const MODE_SETTINGS = 'settings'; // Directly modify ~/.claude/settings.json
|
|
17
18
|
const MODE_ENV = 'env'; // Use environment variable files
|
|
18
19
|
|
|
20
|
+
// Environment variable keys
|
|
21
|
+
const ENV_KEYS = {
|
|
22
|
+
BASE_URL: 'ANTHROPIC_BASE_URL',
|
|
23
|
+
AUTH_TOKEN: 'ANTHROPIC_AUTH_TOKEN',
|
|
24
|
+
API_KEY: 'ANTHROPIC_API_KEY',
|
|
25
|
+
MODEL: 'ANTHROPIC_MODEL',
|
|
26
|
+
SMALL_FAST_MODEL: 'ANTHROPIC_SMALL_FAST_MODEL'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// Sensitive keys that should be masked
|
|
30
|
+
const SENSITIVE_KEYS = [ENV_KEYS.AUTH_TOKEN, ENV_KEYS.API_KEY];
|
|
31
|
+
|
|
32
|
+
function getProfilesMap(profiles) {
|
|
33
|
+
return profiles && profiles.profiles ? profiles.profiles : {};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isProfilesEmpty(profiles) {
|
|
37
|
+
return !profiles || Object.keys(getProfilesMap(profiles)).length === 0;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function ensureProfilesAvailable({onEmpty} = {}) {
|
|
41
|
+
const profiles = loadProfiles();
|
|
42
|
+
if (isProfilesEmpty(profiles)) {
|
|
43
|
+
if (typeof onEmpty === 'function') {
|
|
44
|
+
onEmpty();
|
|
45
|
+
} else {
|
|
46
|
+
console.error('Error: No configurations found');
|
|
47
|
+
console.error('Please add a configuration first: ccconfig add <name>');
|
|
48
|
+
}
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
return profiles;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function ensureProfileAvailable(
|
|
55
|
+
name, {allowEmptyEnv = false, onEmptyProfiles, onMissingProfile, onEmptyEnv} = {}) {
|
|
56
|
+
const profiles = ensureProfilesAvailable({onEmpty: onEmptyProfiles});
|
|
57
|
+
const profilesMap = getProfilesMap(profiles);
|
|
58
|
+
const profile = profilesMap[name];
|
|
59
|
+
|
|
60
|
+
if (!profile) {
|
|
61
|
+
if (typeof onMissingProfile === 'function') {
|
|
62
|
+
onMissingProfile();
|
|
63
|
+
} else {
|
|
64
|
+
console.error(`Error: Configuration '${name}' does not exist`);
|
|
65
|
+
console.error('');
|
|
66
|
+
console.error('Run ccconfig list to see available configurations');
|
|
67
|
+
}
|
|
68
|
+
process.exit(1);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!allowEmptyEnv && (!profile.env || Object.keys(profile.env).length === 0)) {
|
|
72
|
+
if (typeof onEmptyEnv === 'function') {
|
|
73
|
+
onEmptyEnv();
|
|
74
|
+
} else {
|
|
75
|
+
console.error(
|
|
76
|
+
`Error: Configuration '${name}' has empty environment variables`);
|
|
77
|
+
console.error('Please edit the configuration file to add env field');
|
|
78
|
+
}
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return {profile, profiles};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// All supported commands
|
|
86
|
+
const COMMANDS = ['list', 'ls', 'add', 'update', 'use', 'start', 'safe-start', 'remove', 'rm', 'current', 'mode', 'env', 'edit', 'completion'];
|
|
87
|
+
|
|
88
|
+
// ccconfig markers for shell config files
|
|
89
|
+
const SHELL_MARKERS = {
|
|
90
|
+
start: '# >>> ccconfig >>>',
|
|
91
|
+
end: '# <<< ccconfig <<<'
|
|
92
|
+
};
|
|
93
|
+
|
|
19
94
|
let PACKAGE_VERSION = 'unknown';
|
|
20
95
|
try {
|
|
21
96
|
const packageJson = require('./package.json');
|
|
@@ -27,12 +102,225 @@ try {
|
|
|
27
102
|
}
|
|
28
103
|
|
|
29
104
|
/**
|
|
30
|
-
* Ensure directory exists
|
|
105
|
+
* Ensure directory exists with secure permissions
|
|
31
106
|
*/
|
|
32
107
|
function ensureDir(dir) {
|
|
33
108
|
if (!fs.existsSync(dir)) {
|
|
34
|
-
fs.mkdirSync(dir, {recursive: true});
|
|
109
|
+
fs.mkdirSync(dir, {recursive: true, mode: 0o700});
|
|
110
|
+
} else if (os.platform() !== 'win32') {
|
|
111
|
+
// Ensure existing directory has correct permissions
|
|
112
|
+
try {
|
|
113
|
+
fs.chmodSync(dir, 0o700);
|
|
114
|
+
} catch (e) {
|
|
115
|
+
// Ignore permission errors - may not own the directory
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Utility: Mask sensitive value for display
|
|
122
|
+
*/
|
|
123
|
+
function maskValue(key, value, shouldMask = true) {
|
|
124
|
+
const v = String(value ?? '');
|
|
125
|
+
if (!v || v === '(not set)') return v;
|
|
126
|
+
if (!shouldMask || !SENSITIVE_KEYS.includes(key)) return v;
|
|
127
|
+
return v.length > 20 ? v.substring(0, 20) + '...' : v;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Utility: Print environment variable value (with optional masking)
|
|
132
|
+
*/
|
|
133
|
+
function printEnvVar(key, value, mask = true) {
|
|
134
|
+
const v = String(value ?? '');
|
|
135
|
+
const displayValue = v ? maskValue(key, v, mask) : '(not set)';
|
|
136
|
+
console.log(` ${key}: ${displayValue}`);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Utility: Display environment variables with consistent formatting
|
|
141
|
+
*/
|
|
142
|
+
function displayEnvVars(envVars, mask = true, indent = ' ') {
|
|
143
|
+
const keys = [ENV_KEYS.BASE_URL, ENV_KEYS.AUTH_TOKEN, ENV_KEYS.API_KEY, ENV_KEYS.MODEL, ENV_KEYS.SMALL_FAST_MODEL];
|
|
144
|
+
for (const key of keys) {
|
|
145
|
+
if (!(key in envVars)) continue;
|
|
146
|
+
const value = envVars[key];
|
|
147
|
+
if (!value && key !== ENV_KEYS.BASE_URL && key !== ENV_KEYS.AUTH_TOKEN && key !== ENV_KEYS.API_KEY) continue;
|
|
148
|
+
const displayValue = maskValue(key, value, mask);
|
|
149
|
+
console.log(`${indent}${key}: ${displayValue || '(not set)'}`);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Utility: Interactive readline helper
|
|
155
|
+
*/
|
|
156
|
+
class ReadlineHelper {
|
|
157
|
+
constructor() {
|
|
158
|
+
this.rl = null;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
ensureInterface() {
|
|
162
|
+
if (!this.rl) {
|
|
163
|
+
this.rl = readline.createInterface({
|
|
164
|
+
input: process.stdin,
|
|
165
|
+
output: process.stdout
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
async ask(question, defaultValue = '', options = {}) {
|
|
171
|
+
this.ensureInterface();
|
|
172
|
+
const { brackets = 'parentheses' } = options;
|
|
173
|
+
const left = brackets === 'square' ? '[' : '(';
|
|
174
|
+
const right = brackets === 'square' ? ']' : ')';
|
|
175
|
+
const suffix = defaultValue ? ` ${left}${defaultValue}${right}` : '';
|
|
176
|
+
|
|
177
|
+
return new Promise(resolve => {
|
|
178
|
+
this.rl.question(`${question}${suffix}: `, answer => {
|
|
179
|
+
const trimmed = answer.trim();
|
|
180
|
+
resolve(trimmed || defaultValue);
|
|
181
|
+
});
|
|
182
|
+
});
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
async askEnvVars(existingEnv = {}) {
|
|
186
|
+
const baseUrl = await this.ask(
|
|
187
|
+
'ANTHROPIC_BASE_URL (press Enter to keep current/default)',
|
|
188
|
+
existingEnv.ANTHROPIC_BASE_URL || 'https://api.anthropic.com',
|
|
189
|
+
{ brackets: existingEnv.ANTHROPIC_BASE_URL ? 'square' : 'parentheses' }
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
const authToken = await this.ask(
|
|
193
|
+
'ANTHROPIC_AUTH_TOKEN (press Enter to keep current/set empty)',
|
|
194
|
+
existingEnv.ANTHROPIC_AUTH_TOKEN || '',
|
|
195
|
+
{ brackets: existingEnv.ANTHROPIC_AUTH_TOKEN ? 'square' : 'parentheses' }
|
|
196
|
+
);
|
|
197
|
+
|
|
198
|
+
const apiKey = await this.ask(
|
|
199
|
+
'ANTHROPIC_API_KEY (press Enter to keep current/set empty)',
|
|
200
|
+
existingEnv.ANTHROPIC_API_KEY || '',
|
|
201
|
+
{ brackets: existingEnv.ANTHROPIC_API_KEY ? 'square' : 'parentheses' }
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const model = await this.ask(
|
|
205
|
+
'ANTHROPIC_MODEL (press Enter to skip/keep current)',
|
|
206
|
+
existingEnv.ANTHROPIC_MODEL || '',
|
|
207
|
+
{ brackets: existingEnv.ANTHROPIC_MODEL ? 'square' : 'parentheses' }
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
const smallFastModel = await this.ask(
|
|
211
|
+
'ANTHROPIC_SMALL_FAST_MODEL (press Enter to skip/keep current)',
|
|
212
|
+
existingEnv.ANTHROPIC_SMALL_FAST_MODEL || '',
|
|
213
|
+
{ brackets: existingEnv.ANTHROPIC_SMALL_FAST_MODEL ? 'square' : 'parentheses' }
|
|
214
|
+
);
|
|
215
|
+
|
|
216
|
+
const envVars = {
|
|
217
|
+
[ENV_KEYS.BASE_URL]: baseUrl || '',
|
|
218
|
+
[ENV_KEYS.AUTH_TOKEN]: authToken || '',
|
|
219
|
+
[ENV_KEYS.API_KEY]: apiKey || ''
|
|
220
|
+
};
|
|
221
|
+
|
|
222
|
+
if (model) envVars[ENV_KEYS.MODEL] = model;
|
|
223
|
+
if (smallFastModel) envVars[ENV_KEYS.SMALL_FAST_MODEL] = smallFastModel;
|
|
224
|
+
|
|
225
|
+
return envVars;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
close() {
|
|
229
|
+
if (this.rl) {
|
|
230
|
+
this.rl.close();
|
|
231
|
+
this.rl = null;
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Utility: Check if terminal is interactive
|
|
238
|
+
*/
|
|
239
|
+
function requireInteractive(commandName) {
|
|
240
|
+
const isInteractive = process.stdin.isTTY && process.stdout.isTTY;
|
|
241
|
+
if (!isInteractive) {
|
|
242
|
+
console.error(`Error: Interactive mode required for ${commandName}`);
|
|
243
|
+
console.error('This command must be run in an interactive terminal');
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/**
|
|
249
|
+
* Utility: Display environment variables section for current command
|
|
250
|
+
*/
|
|
251
|
+
function displayEnvSection(envVars, showSecret) {
|
|
252
|
+
if (!envVars || (!envVars[ENV_KEYS.BASE_URL] && !envVars[ENV_KEYS.AUTH_TOKEN] && !envVars[ENV_KEYS.API_KEY])) {
|
|
253
|
+
console.log(' (not configured)');
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const normalizedEnv = {
|
|
258
|
+
[ENV_KEYS.BASE_URL]: envVars[ENV_KEYS.BASE_URL] || '(not set)',
|
|
259
|
+
[ENV_KEYS.AUTH_TOKEN]: envVars[ENV_KEYS.AUTH_TOKEN] || envVars[ENV_KEYS.API_KEY] || '(not set)',
|
|
260
|
+
[ENV_KEYS.MODEL]: envVars[ENV_KEYS.MODEL],
|
|
261
|
+
[ENV_KEYS.SMALL_FAST_MODEL]: envVars[ENV_KEYS.SMALL_FAST_MODEL]
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
// Mask token if needed
|
|
265
|
+
if (normalizedEnv[ENV_KEYS.AUTH_TOKEN] !== '(not set)' && !showSecret) {
|
|
266
|
+
const token = normalizedEnv[ENV_KEYS.AUTH_TOKEN];
|
|
267
|
+
normalizedEnv[ENV_KEYS.AUTH_TOKEN] = token.substring(0, 20) + '...';
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// Display with aligned columns
|
|
271
|
+
console.log(` ${ENV_KEYS.BASE_URL}: ${normalizedEnv[ENV_KEYS.BASE_URL]}`);
|
|
272
|
+
console.log(` ${ENV_KEYS.AUTH_TOKEN}: ${normalizedEnv[ENV_KEYS.AUTH_TOKEN]}`);
|
|
273
|
+
if (normalizedEnv[ENV_KEYS.MODEL]) {
|
|
274
|
+
console.log(` ${ENV_KEYS.MODEL}: ${normalizedEnv[ENV_KEYS.MODEL]}`);
|
|
275
|
+
}
|
|
276
|
+
if (normalizedEnv[ENV_KEYS.SMALL_FAST_MODEL]) {
|
|
277
|
+
console.log(` ${ENV_KEYS.SMALL_FAST_MODEL}: ${normalizedEnv[ENV_KEYS.SMALL_FAST_MODEL]}`);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Validate configuration name
|
|
283
|
+
* @param {string} name - Configuration name to validate
|
|
284
|
+
* @param {boolean} allowEmpty - Whether to allow empty names (default: false)
|
|
285
|
+
* @returns {boolean} - Returns true if valid, exits process if invalid
|
|
286
|
+
*/
|
|
287
|
+
function validateConfigName(name, allowEmpty = false) {
|
|
288
|
+
if (!name || name.trim() === '') {
|
|
289
|
+
if (allowEmpty) {
|
|
290
|
+
return true;
|
|
291
|
+
}
|
|
292
|
+
console.error('Error: Configuration name cannot be empty');
|
|
293
|
+
process.exit(1);
|
|
35
294
|
}
|
|
295
|
+
|
|
296
|
+
// Allow only alphanumeric characters, hyphens, and underscores
|
|
297
|
+
const CONFIG_NAME_REGEX = /^[a-zA-Z0-9_-]+$/;
|
|
298
|
+
if (!CONFIG_NAME_REGEX.test(name)) {
|
|
299
|
+
console.error(`Error: Invalid configuration name '${name}'`);
|
|
300
|
+
console.error('');
|
|
301
|
+
console.error('Configuration names can only contain:');
|
|
302
|
+
console.error(' • Letters (a-z, A-Z)');
|
|
303
|
+
console.error(' • Numbers (0-9)');
|
|
304
|
+
console.error(' • Hyphens (-)');
|
|
305
|
+
console.error(' • Underscores (_)');
|
|
306
|
+
console.error('');
|
|
307
|
+
console.error('Examples of valid names:');
|
|
308
|
+
console.error(' • work');
|
|
309
|
+
console.error(' • personal');
|
|
310
|
+
console.error(' • project-1');
|
|
311
|
+
console.error(' • staging_env');
|
|
312
|
+
process.exit(1);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Limit length to prevent issues
|
|
316
|
+
const MAX_NAME_LENGTH = 50;
|
|
317
|
+
if (name.length > MAX_NAME_LENGTH) {
|
|
318
|
+
console.error(`Error: Configuration name too long (max ${MAX_NAME_LENGTH} characters)`);
|
|
319
|
+
console.error(`Current length: ${name.length}`);
|
|
320
|
+
process.exit(1);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
return true;
|
|
36
324
|
}
|
|
37
325
|
|
|
38
326
|
/**
|
|
@@ -144,11 +432,9 @@ function updateClaudeSettings(envVars) {
|
|
|
144
432
|
}
|
|
145
433
|
|
|
146
434
|
// Clear old related environment variables
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
delete settings.env.ANTHROPIC_MODEL;
|
|
151
|
-
delete settings.env.ANTHROPIC_SMALL_FAST_MODEL;
|
|
435
|
+
for (const key of Object.values(ENV_KEYS)) {
|
|
436
|
+
delete settings.env[key];
|
|
437
|
+
}
|
|
152
438
|
|
|
153
439
|
// Set new environment variables
|
|
154
440
|
Object.assign(settings.env, envVars);
|
|
@@ -162,8 +448,15 @@ function updateClaudeSettings(envVars) {
|
|
|
162
448
|
function writeEnvFile(envVars) {
|
|
163
449
|
try {
|
|
164
450
|
ensureDir(CONFIG_DIR);
|
|
165
|
-
const lines =
|
|
166
|
-
|
|
451
|
+
const lines = Object.entries(envVars).map(([key, value]) => {
|
|
452
|
+
// Escape special characters to prevent injection
|
|
453
|
+
const escapedValue = String(value ?? '')
|
|
454
|
+
.replace(/\\/g, '\\\\')
|
|
455
|
+
.replace(/\n/g, '\\n')
|
|
456
|
+
.replace(/\r/g, '\\r')
|
|
457
|
+
.replace(/\t/g, '\\t');
|
|
458
|
+
return `${key}=${escapedValue}`;
|
|
459
|
+
});
|
|
167
460
|
const content = lines.join('\n') + '\n';
|
|
168
461
|
fs.writeFileSync(ENV_FILE, content, 'utf-8');
|
|
169
462
|
|
|
@@ -188,9 +481,18 @@ function readEnvFile() {
|
|
|
188
481
|
const content = fs.readFileSync(ENV_FILE, 'utf-8');
|
|
189
482
|
const env = {};
|
|
190
483
|
content.split('\n').forEach(line => {
|
|
191
|
-
|
|
484
|
+
// Only accept valid environment variable names: starts with letter or underscore,
|
|
485
|
+
// followed by letters, numbers, or underscores
|
|
486
|
+
const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
192
487
|
if (match) {
|
|
193
|
-
|
|
488
|
+
// Unescape special characters
|
|
489
|
+
// IMPORTANT: Must unescape \\\\ first to avoid double-unescaping
|
|
490
|
+
const unescapedValue = match[2]
|
|
491
|
+
.replace(/\\\\/g, '\\')
|
|
492
|
+
.replace(/\\n/g, '\n')
|
|
493
|
+
.replace(/\\r/g, '\r')
|
|
494
|
+
.replace(/\\t/g, '\t');
|
|
495
|
+
env[match[1]] = unescapedValue;
|
|
194
496
|
}
|
|
195
497
|
});
|
|
196
498
|
return env;
|
|
@@ -352,249 +654,109 @@ function list() {
|
|
|
352
654
|
* Add new configuration
|
|
353
655
|
*/
|
|
354
656
|
async function add(name) {
|
|
355
|
-
// Auto-initialize if needed
|
|
356
657
|
initIfNeeded();
|
|
658
|
+
requireInteractive('adding configurations');
|
|
357
659
|
|
|
358
|
-
const
|
|
359
|
-
|
|
360
|
-
if (!isInteractive) {
|
|
361
|
-
console.error('Error: Interactive mode required for adding configurations');
|
|
362
|
-
console.error('This command must be run in an interactive terminal');
|
|
363
|
-
process.exit(1);
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
let rl = null;
|
|
367
|
-
|
|
368
|
-
const askQuestion = (question, defaultValue = '') => {
|
|
369
|
-
if (!rl) {
|
|
370
|
-
rl = readline.createInterface(
|
|
371
|
-
{input: process.stdin, output: process.stdout});
|
|
372
|
-
}
|
|
373
|
-
return new Promise(resolve => {
|
|
374
|
-
const suffix = defaultValue ? ` (${defaultValue})` : '';
|
|
375
|
-
rl.question(`${question}${suffix}: `, answer => {
|
|
376
|
-
const trimmed = answer.trim();
|
|
377
|
-
resolve(trimmed ? trimmed : defaultValue.trim());
|
|
378
|
-
});
|
|
379
|
-
});
|
|
380
|
-
};
|
|
381
|
-
|
|
382
|
-
let baseUrl, authToken, apiKey, model, smallFastModel;
|
|
383
|
-
let profiles;
|
|
660
|
+
const helper = new ReadlineHelper();
|
|
384
661
|
|
|
385
662
|
try {
|
|
386
663
|
if (!name) {
|
|
387
|
-
name = await
|
|
664
|
+
name = await helper.ask('Please enter configuration name (e.g., work)');
|
|
388
665
|
}
|
|
389
666
|
|
|
390
|
-
|
|
391
|
-
console.error('Error: Configuration name cannot be empty');
|
|
392
|
-
process.exit(1);
|
|
393
|
-
}
|
|
667
|
+
validateConfigName(name);
|
|
394
668
|
|
|
395
|
-
|
|
396
|
-
|
|
669
|
+
const profiles = loadProfiles() || {profiles: {}};
|
|
670
|
+
const profilesMap = getProfilesMap(profiles);
|
|
397
671
|
|
|
398
|
-
if (
|
|
672
|
+
if (profilesMap[name]) {
|
|
399
673
|
console.error(`Error: Configuration '${name}' already exists`);
|
|
400
|
-
console.error('
|
|
674
|
+
console.error('');
|
|
675
|
+
console.error('To modify this configuration, use one of:');
|
|
676
|
+
console.error(` ccconfig update ${name} # Interactive update`);
|
|
677
|
+
console.error(` ccconfig edit # Manual edit`);
|
|
401
678
|
process.exit(1);
|
|
402
679
|
}
|
|
403
680
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
'https://api.anthropic.com');
|
|
407
|
-
|
|
408
|
-
authToken =
|
|
409
|
-
await askQuestion('Please enter ANTHROPIC_AUTH_TOKEN (press Enter to set as empty)');
|
|
681
|
+
console.log('Please enter the following information:');
|
|
682
|
+
console.log('');
|
|
410
683
|
|
|
411
|
-
|
|
684
|
+
const envVars = await helper.askEnvVars();
|
|
412
685
|
|
|
413
|
-
|
|
686
|
+
profiles.profiles[name] = {env: envVars};
|
|
687
|
+
saveProfiles(profiles);
|
|
414
688
|
|
|
415
|
-
|
|
689
|
+
console.log(`✓ Configuration '${name}' added`);
|
|
690
|
+
console.log('');
|
|
691
|
+
console.log('Run the following command to activate:');
|
|
692
|
+
console.log(` ccconfig use ${name}`);
|
|
693
|
+
console.log('');
|
|
694
|
+
console.log('Saved environment variables:');
|
|
695
|
+
displayEnvVars(envVars);
|
|
696
|
+
console.log('');
|
|
697
|
+
console.log('This information has been saved to:');
|
|
698
|
+
console.log(` ${PROFILES_FILE}`);
|
|
699
|
+
console.log('You can edit this file directly to further customize the profile:');
|
|
700
|
+
console.log(` vim ${PROFILES_FILE}`);
|
|
701
|
+
console.log('Or run ccconfig edit to open it with your preferred editor');
|
|
416
702
|
} finally {
|
|
417
|
-
|
|
418
|
-
rl.close();
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
const envVars = {
|
|
423
|
-
ANTHROPIC_BASE_URL: baseUrl || '',
|
|
424
|
-
ANTHROPIC_AUTH_TOKEN: authToken || '',
|
|
425
|
-
ANTHROPIC_API_KEY: apiKey || ''
|
|
426
|
-
};
|
|
427
|
-
|
|
428
|
-
// Add optional model variables if provided
|
|
429
|
-
if (model) {
|
|
430
|
-
envVars.ANTHROPIC_MODEL = model;
|
|
431
|
-
}
|
|
432
|
-
if (smallFastModel) {
|
|
433
|
-
envVars.ANTHROPIC_SMALL_FAST_MODEL = smallFastModel;
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
profiles.profiles[name] = {env: envVars};
|
|
437
|
-
|
|
438
|
-
saveProfiles(profiles);
|
|
439
|
-
console.log(`✓ Configuration '${name}' added`);
|
|
440
|
-
console.log('');
|
|
441
|
-
console.log('Run the following command to activate:');
|
|
442
|
-
console.log(` ccconfig use ${name}`);
|
|
443
|
-
console.log('');
|
|
444
|
-
console.log('Saved environment variables:');
|
|
445
|
-
const safePrint = (key, value, mask = true) => {
|
|
446
|
-
if (!value) {
|
|
447
|
-
console.log(` ${key}: (not set)`);
|
|
448
|
-
return;
|
|
449
|
-
}
|
|
450
|
-
if (!mask) {
|
|
451
|
-
console.log(` ${key}: ${value}`);
|
|
452
|
-
return;
|
|
453
|
-
}
|
|
454
|
-
const masked = value.length > 20 ? value.substring(0, 20) + '...' : value;
|
|
455
|
-
console.log(` ${key}: ${masked}`);
|
|
456
|
-
};
|
|
457
|
-
safePrint('ANTHROPIC_BASE_URL', envVars.ANTHROPIC_BASE_URL, false);
|
|
458
|
-
safePrint('ANTHROPIC_AUTH_TOKEN', envVars.ANTHROPIC_AUTH_TOKEN);
|
|
459
|
-
safePrint('ANTHROPIC_API_KEY', envVars.ANTHROPIC_API_KEY);
|
|
460
|
-
if (envVars.ANTHROPIC_MODEL) {
|
|
461
|
-
safePrint('ANTHROPIC_MODEL', envVars.ANTHROPIC_MODEL, false);
|
|
703
|
+
helper.close();
|
|
462
704
|
}
|
|
463
|
-
if (envVars.ANTHROPIC_SMALL_FAST_MODEL) {
|
|
464
|
-
safePrint('ANTHROPIC_SMALL_FAST_MODEL', envVars.ANTHROPIC_SMALL_FAST_MODEL, false);
|
|
465
|
-
}
|
|
466
|
-
console.log('');
|
|
467
|
-
console.log('This information has been saved to:');
|
|
468
|
-
console.log(` ${PROFILES_FILE}`);
|
|
469
|
-
console.log(
|
|
470
|
-
'You can edit this file directly to further customize the profile:');
|
|
471
|
-
console.log(` vim ${PROFILES_FILE}`);
|
|
472
|
-
console.log('Or run ccconfig edit to open it with your preferred editor');
|
|
473
705
|
}
|
|
474
706
|
|
|
475
707
|
/**
|
|
476
708
|
* Update existing configuration
|
|
477
709
|
*/
|
|
478
710
|
async function update(name) {
|
|
479
|
-
// Auto-initialize if needed
|
|
480
711
|
initIfNeeded();
|
|
712
|
+
requireInteractive('updating configurations');
|
|
481
713
|
|
|
482
|
-
const
|
|
483
|
-
|
|
484
|
-
if (!isInteractive) {
|
|
485
|
-
console.error('Error: Interactive mode required for updating configurations');
|
|
486
|
-
console.error('This command must be run in an interactive terminal');
|
|
487
|
-
process.exit(1);
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
let rl = null;
|
|
491
|
-
|
|
492
|
-
const askQuestion = (question, defaultValue = '') => {
|
|
493
|
-
if (!rl) {
|
|
494
|
-
rl = readline.createInterface(
|
|
495
|
-
{input: process.stdin, output: process.stdout});
|
|
496
|
-
}
|
|
497
|
-
return new Promise(resolve => {
|
|
498
|
-
const suffix = defaultValue ? ` [${defaultValue}]` : '';
|
|
499
|
-
rl.question(`${question}${suffix}: `, answer => {
|
|
500
|
-
const trimmed = answer.trim();
|
|
501
|
-
resolve(trimmed ? trimmed : defaultValue);
|
|
502
|
-
});
|
|
503
|
-
});
|
|
504
|
-
};
|
|
505
|
-
|
|
506
|
-
let baseUrl, authToken, apiKey, model, smallFastModel;
|
|
507
|
-
let profiles;
|
|
714
|
+
const helper = new ReadlineHelper();
|
|
508
715
|
|
|
509
716
|
try {
|
|
510
717
|
if (!name) {
|
|
511
|
-
name = await
|
|
512
|
-
}
|
|
513
|
-
|
|
514
|
-
if (!name) {
|
|
515
|
-
console.error('Error: Configuration name cannot be empty');
|
|
516
|
-
process.exit(1);
|
|
718
|
+
name = await helper.ask('Please enter configuration name to update');
|
|
517
719
|
}
|
|
518
720
|
|
|
519
|
-
|
|
520
|
-
profiles = loadProfiles() || {profiles: {}};
|
|
721
|
+
validateConfigName(name);
|
|
521
722
|
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
723
|
+
const {profile, profiles} = ensureProfileAvailable(name, {
|
|
724
|
+
allowEmptyEnv: true,
|
|
725
|
+
onEmptyProfiles: () => {
|
|
726
|
+
console.error('Error: Configuration file does not exist');
|
|
727
|
+
process.exit(1);
|
|
728
|
+
},
|
|
729
|
+
onMissingProfile: () => {
|
|
730
|
+
console.error(`Error: Configuration '${name}' does not exist`);
|
|
731
|
+
console.error('');
|
|
732
|
+
console.error('Run ccconfig list to see available configurations');
|
|
733
|
+
console.error(`Or use 'ccconfig add ${name}' to create a new configuration`);
|
|
734
|
+
process.exit(1);
|
|
735
|
+
}
|
|
736
|
+
});
|
|
528
737
|
|
|
529
|
-
const
|
|
530
|
-
const existingEnv = existingProfile.env || {};
|
|
738
|
+
const existingEnv = profile.env || {};
|
|
531
739
|
|
|
532
740
|
console.log(`Updating configuration '${name}'`);
|
|
533
741
|
console.log('Press Enter to keep current value/default, or enter new value to update');
|
|
534
742
|
console.log('');
|
|
535
743
|
|
|
536
|
-
|
|
537
|
-
'ANTHROPIC_BASE_URL (press Enter to keep current/default)',
|
|
538
|
-
existingEnv.ANTHROPIC_BASE_URL || 'https://api.anthropic.com');
|
|
539
|
-
|
|
540
|
-
authToken =
|
|
541
|
-
await askQuestion('ANTHROPIC_AUTH_TOKEN (press Enter to keep current/set empty)', existingEnv.ANTHROPIC_AUTH_TOKEN || '');
|
|
744
|
+
const envVars = await helper.askEnvVars(existingEnv);
|
|
542
745
|
|
|
543
|
-
|
|
746
|
+
const profilesMap = getProfilesMap(profiles);
|
|
747
|
+
profilesMap[name] = {env: envVars};
|
|
748
|
+
saveProfiles(profiles);
|
|
544
749
|
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
750
|
+
console.log(`✓ Configuration '${name}' updated`);
|
|
751
|
+
console.log('');
|
|
752
|
+
console.log('Updated environment variables:');
|
|
753
|
+
displayEnvVars(envVars);
|
|
754
|
+
console.log('');
|
|
755
|
+
console.log('Run the following command to activate:');
|
|
756
|
+
console.log(` ccconfig use ${name}`);
|
|
548
757
|
} finally {
|
|
549
|
-
|
|
550
|
-
rl.close();
|
|
551
|
-
}
|
|
552
|
-
}
|
|
553
|
-
|
|
554
|
-
const envVars = {
|
|
555
|
-
ANTHROPIC_BASE_URL: baseUrl || '',
|
|
556
|
-
ANTHROPIC_AUTH_TOKEN: authToken || '',
|
|
557
|
-
ANTHROPIC_API_KEY: apiKey || ''
|
|
558
|
-
};
|
|
559
|
-
|
|
560
|
-
// Add optional model variables if provided
|
|
561
|
-
if (model) {
|
|
562
|
-
envVars.ANTHROPIC_MODEL = model;
|
|
758
|
+
helper.close();
|
|
563
759
|
}
|
|
564
|
-
if (smallFastModel) {
|
|
565
|
-
envVars.ANTHROPIC_SMALL_FAST_MODEL = smallFastModel;
|
|
566
|
-
}
|
|
567
|
-
|
|
568
|
-
profiles.profiles[name] = {env: envVars};
|
|
569
|
-
|
|
570
|
-
saveProfiles(profiles);
|
|
571
|
-
console.log(`✓ Configuration '${name}' updated`);
|
|
572
|
-
console.log('');
|
|
573
|
-
console.log('Updated environment variables:');
|
|
574
|
-
const safePrint = (key, value, mask = true) => {
|
|
575
|
-
if (!value) {
|
|
576
|
-
console.log(` ${key}: (not set)`);
|
|
577
|
-
return;
|
|
578
|
-
}
|
|
579
|
-
if (!mask) {
|
|
580
|
-
console.log(` ${key}: ${value}`);
|
|
581
|
-
return;
|
|
582
|
-
}
|
|
583
|
-
const masked = value.length > 20 ? value.substring(0, 20) + '...' : value;
|
|
584
|
-
console.log(` ${key}: ${masked}`);
|
|
585
|
-
};
|
|
586
|
-
safePrint('ANTHROPIC_BASE_URL', envVars.ANTHROPIC_BASE_URL, false);
|
|
587
|
-
safePrint('ANTHROPIC_AUTH_TOKEN', envVars.ANTHROPIC_AUTH_TOKEN);
|
|
588
|
-
safePrint('ANTHROPIC_API_KEY', envVars.ANTHROPIC_API_KEY);
|
|
589
|
-
if (envVars.ANTHROPIC_MODEL) {
|
|
590
|
-
safePrint('ANTHROPIC_MODEL', envVars.ANTHROPIC_MODEL, false);
|
|
591
|
-
}
|
|
592
|
-
if (envVars.ANTHROPIC_SMALL_FAST_MODEL) {
|
|
593
|
-
safePrint('ANTHROPIC_SMALL_FAST_MODEL', envVars.ANTHROPIC_SMALL_FAST_MODEL, false);
|
|
594
|
-
}
|
|
595
|
-
console.log('');
|
|
596
|
-
console.log('Run the following command to activate:');
|
|
597
|
-
console.log(` ccconfig use ${name}`);
|
|
598
760
|
}
|
|
599
761
|
|
|
600
762
|
/**
|
|
@@ -607,19 +769,20 @@ function remove(name) {
|
|
|
607
769
|
process.exit(1);
|
|
608
770
|
}
|
|
609
771
|
|
|
610
|
-
|
|
772
|
+
// Validate configuration name
|
|
773
|
+
validateConfigName(name);
|
|
611
774
|
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
}
|
|
775
|
+
const {profiles} = ensureProfileAvailable(name, {
|
|
776
|
+
allowEmptyEnv: true,
|
|
777
|
+
onEmptyProfiles: () => {
|
|
778
|
+
console.error('Error: Configuration file does not exist');
|
|
779
|
+
},
|
|
780
|
+
onMissingProfile: () => {
|
|
781
|
+
console.error(`Error: Configuration '${name}' does not exist`);
|
|
782
|
+
}
|
|
783
|
+
});
|
|
621
784
|
|
|
622
|
-
delete profiles
|
|
785
|
+
delete getProfilesMap(profiles)[name];
|
|
623
786
|
saveProfiles(profiles);
|
|
624
787
|
console.log(`✓ Configuration '${name}' removed`);
|
|
625
788
|
}
|
|
@@ -628,94 +791,136 @@ function remove(name) {
|
|
|
628
791
|
* Detect current shell and return recommended activation command
|
|
629
792
|
*/
|
|
630
793
|
function detectShellCommand() {
|
|
631
|
-
const
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
return {shell: 'fish', command: 'ccconfig env fish | source'};
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
if (process.env.ZSH_NAME || process.env.ZSH_VERSION ||
|
|
638
|
-
shellPath.includes('zsh')) {
|
|
639
|
-
return {shell: 'zsh', command: 'eval $(ccconfig env bash)'};
|
|
794
|
+
const shellType = ShellUtils.detectType();
|
|
795
|
+
if (!shellType) {
|
|
796
|
+
return {shell: null, command: null};
|
|
640
797
|
}
|
|
798
|
+
const command = ShellUtils.getActivationCommand(shellType);
|
|
799
|
+
const shellName = shellType === 'powershell' ? 'PowerShell' : shellType;
|
|
800
|
+
return {shell: shellName, command};
|
|
801
|
+
}
|
|
641
802
|
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
803
|
+
/**
|
|
804
|
+
* Shell utilities - unified shell detection, escaping, and formatting
|
|
805
|
+
*/
|
|
806
|
+
const ShellUtils = {
|
|
807
|
+
// Escape functions for different shells
|
|
808
|
+
escape: {
|
|
809
|
+
posix: (value) => {
|
|
810
|
+
const str = value == null ? '' : String(value);
|
|
811
|
+
return `'${str.replace(/'/g, `'"'"'`)}'`;
|
|
812
|
+
},
|
|
813
|
+
fish: (value) => {
|
|
814
|
+
const str = value == null ? '' : String(value);
|
|
815
|
+
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '\\$');
|
|
816
|
+
},
|
|
817
|
+
pwsh: (value) => {
|
|
818
|
+
const str = value == null ? '' : String(value);
|
|
819
|
+
return `'${str.replace(/'/g, `''`)}'`;
|
|
820
|
+
}
|
|
821
|
+
},
|
|
646
822
|
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
823
|
+
// Detect current shell type
|
|
824
|
+
detectType: () => {
|
|
825
|
+
const shellPath = (process.env.SHELL || '').toLowerCase();
|
|
650
826
|
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
if (comSpec.includes('powershell')) {
|
|
654
|
-
return {shell: 'PowerShell', command: 'ccconfig env pwsh | iex'};
|
|
827
|
+
if (process.env.FISH_VERSION || shellPath.includes('fish')) {
|
|
828
|
+
return 'fish';
|
|
655
829
|
}
|
|
830
|
+
if (process.env.ZSH_NAME || process.env.ZSH_VERSION || shellPath.includes('zsh')) {
|
|
831
|
+
return 'zsh';
|
|
832
|
+
}
|
|
833
|
+
if (process.env.POWERSHELL_DISTRIBUTION_CHANNEL || shellPath.includes('pwsh') || shellPath.includes('powershell')) {
|
|
834
|
+
return 'powershell';
|
|
835
|
+
}
|
|
836
|
+
if (shellPath.includes('bash')) {
|
|
837
|
+
return 'bash';
|
|
838
|
+
}
|
|
839
|
+
if (process.platform === 'win32') {
|
|
840
|
+
const comSpec = (process.env.ComSpec || '').toLowerCase();
|
|
841
|
+
if (comSpec.includes('powershell')) {
|
|
842
|
+
return 'powershell';
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
return null;
|
|
846
|
+
},
|
|
847
|
+
|
|
848
|
+
// Get shell config file path
|
|
849
|
+
getConfigPath: (shellType) => {
|
|
850
|
+
const homeDir = os.homedir();
|
|
851
|
+
const configs = {
|
|
852
|
+
fish: path.join(homeDir, '.config', 'fish', 'config.fish'),
|
|
853
|
+
zsh: path.join(homeDir, '.zshrc'),
|
|
854
|
+
bash: process.platform === 'darwin'
|
|
855
|
+
? (fs.existsSync(path.join(homeDir, '.bash_profile')) || !fs.existsSync(path.join(homeDir, '.bashrc'))
|
|
856
|
+
? path.join(homeDir, '.bash_profile')
|
|
857
|
+
: path.join(homeDir, '.bashrc'))
|
|
858
|
+
: path.join(homeDir, '.bashrc'),
|
|
859
|
+
powershell: process.platform === 'win32'
|
|
860
|
+
? path.join(process.env.USERPROFILE || homeDir, 'Documents', 'PowerShell', 'Microsoft.PowerShell_profile.ps1')
|
|
861
|
+
: path.join(homeDir, '.config', 'powershell', 'profile.ps1')
|
|
862
|
+
};
|
|
863
|
+
return configs[shellType];
|
|
864
|
+
},
|
|
865
|
+
|
|
866
|
+
// Get activation command for specific shell
|
|
867
|
+
getActivationCommand: (shellType) => {
|
|
868
|
+
const commands = {
|
|
869
|
+
fish: 'ccconfig env fish | source',
|
|
870
|
+
zsh: 'eval $(ccconfig env bash)',
|
|
871
|
+
bash: 'eval $(ccconfig env bash)',
|
|
872
|
+
powershell: 'ccconfig env pwsh | iex'
|
|
873
|
+
};
|
|
874
|
+
return commands[shellType];
|
|
875
|
+
},
|
|
876
|
+
|
|
877
|
+
// Format environment variables for specific shell
|
|
878
|
+
formatEnvVars: (envVars, format) => {
|
|
879
|
+
const lines = [];
|
|
880
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
881
|
+
switch (format) {
|
|
882
|
+
case 'fish':
|
|
883
|
+
lines.push(`set -gx ${key} "${ShellUtils.escape.fish(value)}"`);
|
|
884
|
+
break;
|
|
885
|
+
case 'bash':
|
|
886
|
+
case 'zsh':
|
|
887
|
+
case 'sh':
|
|
888
|
+
lines.push(`export ${key}=${ShellUtils.escape.posix(value)}`);
|
|
889
|
+
break;
|
|
890
|
+
case 'powershell':
|
|
891
|
+
case 'pwsh':
|
|
892
|
+
lines.push(`$env:${key}=${ShellUtils.escape.pwsh(value)}`);
|
|
893
|
+
break;
|
|
894
|
+
case 'dotenv':
|
|
895
|
+
const renderedValue = value == null ? '' : String(value);
|
|
896
|
+
const escapedValue = renderedValue
|
|
897
|
+
.replace(/\\/g, '\\\\')
|
|
898
|
+
.replace(/\n/g, '\\n')
|
|
899
|
+
.replace(/\r/g, '\\r')
|
|
900
|
+
.replace(/\t/g, '\\t');
|
|
901
|
+
lines.push(`${key}=${escapedValue}`);
|
|
902
|
+
break;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
return lines;
|
|
656
906
|
}
|
|
907
|
+
};
|
|
657
908
|
|
|
658
|
-
|
|
659
|
-
}
|
|
660
|
-
|
|
661
|
-
function
|
|
662
|
-
const str = value == null ? '' : String(value);
|
|
663
|
-
return `'${str.replace(/'/g, `'"'"'`)}'`;
|
|
664
|
-
}
|
|
665
|
-
|
|
666
|
-
function escapeFish(value) {
|
|
667
|
-
const str = value == null ? '' : String(value);
|
|
668
|
-
return str.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\$/g, '\\$');
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
function escapePwsh(value) {
|
|
672
|
-
const str = value == null ? '' : String(value);
|
|
673
|
-
return `'${str.replace(/'/g, `''`)}'`;
|
|
674
|
-
}
|
|
909
|
+
// Legacy function wrappers for backward compatibility
|
|
910
|
+
function escapePosix(value) { return ShellUtils.escape.posix(value); }
|
|
911
|
+
function escapeFish(value) { return ShellUtils.escape.fish(value); }
|
|
912
|
+
function escapePwsh(value) { return ShellUtils.escape.pwsh(value); }
|
|
675
913
|
|
|
676
914
|
/**
|
|
677
915
|
* Detect shell type and config file path
|
|
678
916
|
*/
|
|
679
917
|
function detectShellConfig() {
|
|
680
|
-
const
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
if (process.env.FISH_VERSION || shellPath.includes('fish')) {
|
|
684
|
-
const configPath = path.join(homeDir, '.config', 'fish', 'config.fish');
|
|
685
|
-
return {shell: 'fish', configPath, detected: true};
|
|
686
|
-
}
|
|
687
|
-
|
|
688
|
-
if (process.env.ZSH_NAME || process.env.ZSH_VERSION ||
|
|
689
|
-
shellPath.includes('zsh')) {
|
|
690
|
-
const configPath = path.join(homeDir, '.zshrc');
|
|
691
|
-
return {shell: 'zsh', configPath, detected: true};
|
|
918
|
+
const shellType = ShellUtils.detectType();
|
|
919
|
+
if (!shellType) {
|
|
920
|
+
return {shell: null, configPath: null, detected: false};
|
|
692
921
|
}
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
if (process.platform === 'darwin') {
|
|
696
|
-
const bashProfile = path.join(homeDir, '.bash_profile');
|
|
697
|
-
const bashrc = path.join(homeDir, '.bashrc');
|
|
698
|
-
const configPath = fs.existsSync(bashProfile) || !fs.existsSync(bashrc) ?
|
|
699
|
-
bashProfile :
|
|
700
|
-
bashrc;
|
|
701
|
-
return {shell: 'bash', configPath, detected: true};
|
|
702
|
-
}
|
|
703
|
-
const configPath = path.join(homeDir, '.bashrc');
|
|
704
|
-
return {shell: 'bash', configPath, detected: true};
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
if (process.env.POWERSHELL_DISTRIBUTION_CHANNEL ||
|
|
708
|
-
shellPath.includes('pwsh') || shellPath.includes('powershell')) {
|
|
709
|
-
// PowerShell profile path varies by OS
|
|
710
|
-
const configPath = process.platform === 'win32' ?
|
|
711
|
-
path.join(
|
|
712
|
-
process.env.USERPROFILE || homeDir, 'Documents', 'PowerShell',
|
|
713
|
-
'Microsoft.PowerShell_profile.ps1') :
|
|
714
|
-
path.join(homeDir, '.config', 'powershell', 'profile.ps1');
|
|
715
|
-
return {shell: 'powershell', configPath, detected: true};
|
|
716
|
-
}
|
|
717
|
-
|
|
718
|
-
return {shell: null, configPath: null, detected: false};
|
|
922
|
+
const configPath = ShellUtils.getConfigPath(shellType);
|
|
923
|
+
return {shell: shellType, configPath, detected: true};
|
|
719
924
|
}
|
|
720
925
|
|
|
721
926
|
/**
|
|
@@ -732,37 +937,20 @@ async function writePermanentEnv(envVars) {
|
|
|
732
937
|
}
|
|
733
938
|
|
|
734
939
|
const {shell, configPath} = shellConfig;
|
|
735
|
-
const marker =
|
|
736
|
-
const markerEnd =
|
|
940
|
+
const marker = SHELL_MARKERS.start;
|
|
941
|
+
const markerEnd = SHELL_MARKERS.end;
|
|
737
942
|
|
|
738
|
-
// Generate environment variable lines
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
for (const [key, value] of Object.entries(envVars)) {
|
|
744
|
-
envBlock += `set -gx ${key} "${escapeFish(value)}"\n`;
|
|
745
|
-
}
|
|
746
|
-
envBlock += `${markerEnd}\n`;
|
|
747
|
-
break;
|
|
943
|
+
// Generate environment variable lines (real and masked)
|
|
944
|
+
const maskedEnvVars = {};
|
|
945
|
+
for (const [key, value] of Object.entries(envVars)) {
|
|
946
|
+
maskedEnvVars[key] = maskValue(key, value, true);
|
|
947
|
+
}
|
|
748
948
|
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
envBlock = `${marker}\n`;
|
|
752
|
-
for (const [key, value] of Object.entries(envVars)) {
|
|
753
|
-
envBlock += `export ${key}=${escapePosix(value)}\n`;
|
|
754
|
-
}
|
|
755
|
-
envBlock += `${markerEnd}\n`;
|
|
756
|
-
break;
|
|
949
|
+
const envLines = ShellUtils.formatEnvVars(envVars, shell);
|
|
950
|
+
const maskedEnvLines = ShellUtils.formatEnvVars(maskedEnvVars, shell);
|
|
757
951
|
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
for (const [key, value] of Object.entries(envVars)) {
|
|
761
|
-
envBlock += `$env:${key}=${escapePwsh(value)}\n`;
|
|
762
|
-
}
|
|
763
|
-
envBlock += `${markerEnd}\n`;
|
|
764
|
-
break;
|
|
765
|
-
}
|
|
952
|
+
const envBlock = `${marker}\n${envLines.join('\n')}\n${markerEnd}\n`;
|
|
953
|
+
const maskedEnvBlock = `${marker}\n${maskedEnvLines.join('\n')}\n${markerEnd}\n`;
|
|
766
954
|
|
|
767
955
|
// Display warning and confirmation
|
|
768
956
|
console.log('');
|
|
@@ -773,7 +961,7 @@ async function writePermanentEnv(envVars) {
|
|
|
773
961
|
console.log('');
|
|
774
962
|
console.log('The following block will be added/updated:');
|
|
775
963
|
console.log('───────────────────────────────────────────');
|
|
776
|
-
console.log(
|
|
964
|
+
console.log(maskedEnvBlock.trim());
|
|
777
965
|
console.log('───────────────────────────────────────────');
|
|
778
966
|
console.log('');
|
|
779
967
|
console.log('What this does:');
|
|
@@ -891,29 +1079,25 @@ async function writePermanentEnv(envVars) {
|
|
|
891
1079
|
* Switch configuration
|
|
892
1080
|
*/
|
|
893
1081
|
async function use(name, options = {}) {
|
|
894
|
-
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
`Error: Configuration '${name}' has empty environment variables`);
|
|
914
|
-
console.error('Please edit the configuration file to add env field');
|
|
915
|
-
process.exit(1);
|
|
916
|
-
}
|
|
1082
|
+
// Validate configuration name
|
|
1083
|
+
validateConfigName(name);
|
|
1084
|
+
|
|
1085
|
+
const {profile} = ensureProfileAvailable(name, {
|
|
1086
|
+
onEmptyProfiles: () => {
|
|
1087
|
+
console.error('Error: No configurations found');
|
|
1088
|
+
console.error('Please add a configuration first: ccconfig add <name>');
|
|
1089
|
+
},
|
|
1090
|
+
onMissingProfile: () => {
|
|
1091
|
+
console.error(`Error: Configuration '${name}' does not exist`);
|
|
1092
|
+
console.error('');
|
|
1093
|
+
console.error('Run ccconfig list to see available configurations');
|
|
1094
|
+
},
|
|
1095
|
+
onEmptyEnv: () => {
|
|
1096
|
+
console.error(
|
|
1097
|
+
`Error: Configuration '${name}' has empty environment variables`);
|
|
1098
|
+
console.error('Please edit the configuration file to add env field');
|
|
1099
|
+
}
|
|
1100
|
+
});
|
|
917
1101
|
|
|
918
1102
|
const mode = getMode();
|
|
919
1103
|
const permanent = options.permanent || false;
|
|
@@ -924,11 +1108,7 @@ async function use(name, options = {}) {
|
|
|
924
1108
|
|
|
925
1109
|
console.log(`✓ Switched to configuration: ${name} (settings mode)`);
|
|
926
1110
|
console.log(` Environment variables:`);
|
|
927
|
-
|
|
928
|
-
const displayValue =
|
|
929
|
-
value.length > 20 ? value.substring(0, 20) + '...' : value;
|
|
930
|
-
console.log(` ${key}: ${displayValue}`);
|
|
931
|
-
}
|
|
1111
|
+
displayEnvVars(profile.env, true, ' ');
|
|
932
1112
|
console.log('');
|
|
933
1113
|
console.log('Configuration written to ~/.claude/settings.json');
|
|
934
1114
|
console.log('Restart Claude Code to make configuration take effect');
|
|
@@ -944,11 +1124,7 @@ async function use(name, options = {}) {
|
|
|
944
1124
|
|
|
945
1125
|
console.log(`✓ Switched to configuration: ${name} (env mode)`);
|
|
946
1126
|
console.log(` Environment variables:`);
|
|
947
|
-
|
|
948
|
-
const displayValue =
|
|
949
|
-
value.length > 20 ? value.substring(0, 20) + '...' : value;
|
|
950
|
-
console.log(` ${key}: ${displayValue}`);
|
|
951
|
-
}
|
|
1127
|
+
displayEnvVars(profile.env, true, ' ');
|
|
952
1128
|
console.log('');
|
|
953
1129
|
console.log(`Environment variable file updated: ${ENV_FILE}`);
|
|
954
1130
|
|
|
@@ -1029,74 +1205,17 @@ function current(showSecret = false) {
|
|
|
1029
1205
|
|
|
1030
1206
|
// Display settings.json configuration
|
|
1031
1207
|
console.log('【1】~/.claude/settings.json:');
|
|
1032
|
-
|
|
1033
|
-
(settings.env.ANTHROPIC_BASE_URL || settings.env.ANTHROPIC_AUTH_TOKEN)) {
|
|
1034
|
-
const baseUrl = settings.env.ANTHROPIC_BASE_URL || '(not set)';
|
|
1035
|
-
const authToken = settings.env.ANTHROPIC_AUTH_TOKEN || '(not set)';
|
|
1036
|
-
const maskedToken = (authToken === '(not set)' || showSecret) ?
|
|
1037
|
-
authToken :
|
|
1038
|
-
authToken.substring(0, 20) + '...';
|
|
1039
|
-
|
|
1040
|
-
console.log(` ANTHROPIC_BASE_URL: ${baseUrl}`);
|
|
1041
|
-
console.log(` ANTHROPIC_AUTH_TOKEN: ${maskedToken}`);
|
|
1042
|
-
if (settings.env.ANTHROPIC_MODEL) {
|
|
1043
|
-
console.log(` ANTHROPIC_MODEL: ${settings.env.ANTHROPIC_MODEL}`);
|
|
1044
|
-
}
|
|
1045
|
-
if (settings.env.ANTHROPIC_SMALL_FAST_MODEL) {
|
|
1046
|
-
console.log(` ANTHROPIC_SMALL_FAST_MODEL: ${settings.env.ANTHROPIC_SMALL_FAST_MODEL}`);
|
|
1047
|
-
}
|
|
1048
|
-
} else {
|
|
1049
|
-
console.log(' (not configured)');
|
|
1050
|
-
}
|
|
1208
|
+
displayEnvSection(settings.env, showSecret);
|
|
1051
1209
|
console.log('');
|
|
1052
1210
|
|
|
1053
1211
|
// Display environment variable file configuration
|
|
1054
1212
|
console.log(`【2】Environment Variables File (${ENV_FILE}):`);
|
|
1055
|
-
|
|
1056
|
-
(envFile.ANTHROPIC_BASE_URL || envFile.ANTHROPIC_AUTH_TOKEN ||
|
|
1057
|
-
envFile.ANTHROPIC_API_KEY)) {
|
|
1058
|
-
const baseUrl = envFile.ANTHROPIC_BASE_URL || '(not set)';
|
|
1059
|
-
const authToken = envFile.ANTHROPIC_AUTH_TOKEN ||
|
|
1060
|
-
envFile.ANTHROPIC_API_KEY || '(not set)';
|
|
1061
|
-
const maskedToken = (authToken === '(not set)' || showSecret) ?
|
|
1062
|
-
authToken :
|
|
1063
|
-
authToken.substring(0, 20) + '...';
|
|
1064
|
-
|
|
1065
|
-
console.log(` ANTHROPIC_BASE_URL: ${baseUrl}`);
|
|
1066
|
-
console.log(` ANTHROPIC_AUTH_TOKEN: ${maskedToken}`);
|
|
1067
|
-
if (envFile.ANTHROPIC_MODEL) {
|
|
1068
|
-
console.log(` ANTHROPIC_MODEL: ${envFile.ANTHROPIC_MODEL}`);
|
|
1069
|
-
}
|
|
1070
|
-
if (envFile.ANTHROPIC_SMALL_FAST_MODEL) {
|
|
1071
|
-
console.log(` ANTHROPIC_SMALL_FAST_MODEL: ${envFile.ANTHROPIC_SMALL_FAST_MODEL}`);
|
|
1072
|
-
}
|
|
1073
|
-
} else {
|
|
1074
|
-
console.log(' (not configured)');
|
|
1075
|
-
}
|
|
1213
|
+
displayEnvSection(envFile, showSecret);
|
|
1076
1214
|
console.log('');
|
|
1077
1215
|
|
|
1078
1216
|
// Display current process environment variables
|
|
1079
1217
|
console.log('【3】Current Process Environment Variables:');
|
|
1080
|
-
|
|
1081
|
-
processEnv.ANTHROPIC_API_KEY) {
|
|
1082
|
-
const baseUrl = processEnv.ANTHROPIC_BASE_URL || '(not set)';
|
|
1083
|
-
const authToken = processEnv.ANTHROPIC_AUTH_TOKEN ||
|
|
1084
|
-
processEnv.ANTHROPIC_API_KEY || '(not set)';
|
|
1085
|
-
const maskedToken = (authToken === '(not set)' || showSecret) ?
|
|
1086
|
-
authToken :
|
|
1087
|
-
authToken.substring(0, 20) + '...';
|
|
1088
|
-
|
|
1089
|
-
console.log(` ANTHROPIC_BASE_URL: ${baseUrl}`);
|
|
1090
|
-
console.log(` ANTHROPIC_AUTH_TOKEN: ${maskedToken}`);
|
|
1091
|
-
if (processEnv.ANTHROPIC_MODEL) {
|
|
1092
|
-
console.log(` ANTHROPIC_MODEL: ${processEnv.ANTHROPIC_MODEL}`);
|
|
1093
|
-
}
|
|
1094
|
-
if (processEnv.ANTHROPIC_SMALL_FAST_MODEL) {
|
|
1095
|
-
console.log(` ANTHROPIC_SMALL_FAST_MODEL: ${processEnv.ANTHROPIC_SMALL_FAST_MODEL}`);
|
|
1096
|
-
}
|
|
1097
|
-
} else {
|
|
1098
|
-
console.log(' (not set)');
|
|
1099
|
-
}
|
|
1218
|
+
displayEnvSection(processEnv, showSecret);
|
|
1100
1219
|
console.log('');
|
|
1101
1220
|
|
|
1102
1221
|
// Display notes
|
|
@@ -1200,45 +1319,147 @@ function env(format = 'bash') {
|
|
|
1200
1319
|
const envVars = getActiveEnvVars();
|
|
1201
1320
|
|
|
1202
1321
|
if (!envVars || Object.keys(envVars).length === 0) {
|
|
1203
|
-
console.error(
|
|
1204
|
-
|
|
1205
|
-
console.error(
|
|
1206
|
-
'Please run ccconfig use <name> to select a configuration first');
|
|
1322
|
+
console.error('Error: No available environment variable configuration found');
|
|
1323
|
+
console.error('Please run ccconfig use <name> to select a configuration first');
|
|
1207
1324
|
process.exit(1);
|
|
1208
1325
|
}
|
|
1209
1326
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1327
|
+
const supportedFormats = ['fish', 'bash', 'zsh', 'sh', 'powershell', 'pwsh', 'dotenv'];
|
|
1328
|
+
if (!supportedFormats.includes(format)) {
|
|
1329
|
+
console.error(`Error: Unsupported format: ${format}`);
|
|
1330
|
+
console.error(`Supported formats: ${supportedFormats.join(', ')}`);
|
|
1331
|
+
process.exit(1);
|
|
1332
|
+
}
|
|
1333
|
+
|
|
1334
|
+
const lines = ShellUtils.formatEnvVars(envVars, format);
|
|
1335
|
+
lines.forEach(line => console.log(line));
|
|
1336
|
+
}
|
|
1337
|
+
|
|
1338
|
+
/**
|
|
1339
|
+
* Start Claude Code with specified profile (internal implementation)
|
|
1340
|
+
* @param {string} name - Profile name
|
|
1341
|
+
* @param {Array} extraArgs - Additional arguments to pass to Claude
|
|
1342
|
+
* @param {Object} options - Options object
|
|
1343
|
+
* @param {boolean} options.safe - Whether to run in safe mode (default: false)
|
|
1344
|
+
*/
|
|
1345
|
+
function startClaude(name, extraArgs = [], options = {}) {
|
|
1346
|
+
const { safe = false } = options;
|
|
1347
|
+
const commandName = safe ? 'safe-start' : 'start';
|
|
1348
|
+
|
|
1349
|
+
if (!name) {
|
|
1350
|
+
console.error('Error: Missing configuration name');
|
|
1351
|
+
console.error(`Usage: ccconfig ${commandName} <name> [claude-args...]`);
|
|
1352
|
+
process.exit(1);
|
|
1353
|
+
}
|
|
1354
|
+
|
|
1355
|
+
// Validate configuration name
|
|
1356
|
+
validateConfigName(name);
|
|
1357
|
+
|
|
1358
|
+
const {profile} = ensureProfileAvailable(name, {
|
|
1359
|
+
onEmptyProfiles: () => {
|
|
1360
|
+
console.error('Error: No configurations found');
|
|
1361
|
+
console.error('Please add a configuration first: ccconfig add <name>');
|
|
1362
|
+
},
|
|
1363
|
+
onMissingProfile: () => {
|
|
1364
|
+
console.error(`Error: Configuration '${name}' does not exist`);
|
|
1365
|
+
console.error('Run ccconfig list to see available configurations');
|
|
1366
|
+
},
|
|
1367
|
+
onEmptyEnv: () => {
|
|
1238
1368
|
console.error(
|
|
1239
|
-
|
|
1240
|
-
|
|
1369
|
+
`Error: Configuration '${name}' has empty environment variables`);
|
|
1370
|
+
console.error('Please edit the configuration file to add env field');
|
|
1371
|
+
}
|
|
1372
|
+
});
|
|
1373
|
+
|
|
1374
|
+
// Check if claude binary exists before proceeding
|
|
1375
|
+
try {
|
|
1376
|
+
const command = process.platform === 'win32' ? 'where claude' : 'which claude';
|
|
1377
|
+
execSync(command, { stdio: 'pipe' });
|
|
1378
|
+
} catch (err) {
|
|
1379
|
+
console.error('Error: Claude Code CLI not found');
|
|
1380
|
+
console.error('');
|
|
1381
|
+
console.error('Please make sure Claude Code CLI is installed:');
|
|
1382
|
+
console.error(' npm install -g claude-code');
|
|
1383
|
+
process.exit(1);
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
// Display startup message
|
|
1387
|
+
const modeLabel = safe ? ' (safe mode)' : '';
|
|
1388
|
+
console.log(`Starting Claude Code with profile: ${name}${modeLabel}`);
|
|
1389
|
+
console.log('Environment variables:');
|
|
1390
|
+
for (const [key, value] of Object.entries(profile.env)) {
|
|
1391
|
+
const strValue = String(value ?? '');
|
|
1392
|
+
const displayValue =
|
|
1393
|
+
strValue.length > 20 ? strValue.substring(0, 20) + '...' : strValue;
|
|
1394
|
+
console.log(` ${key}: ${displayValue}`);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
// Build Claude arguments based on mode
|
|
1398
|
+
const claudeArgs = safe ? extraArgs : ['--dangerously-skip-permissions', ...extraArgs];
|
|
1399
|
+
|
|
1400
|
+
// Display mode-specific notes
|
|
1401
|
+
console.log('');
|
|
1402
|
+
if (safe) {
|
|
1403
|
+
console.log('Note: Running in safe mode (permission confirmation required)');
|
|
1404
|
+
console.log(' Claude Code will ask for confirmation before executing commands');
|
|
1405
|
+
console.log(' For automatic execution, use "ccconfig start" instead');
|
|
1406
|
+
} else {
|
|
1407
|
+
console.log('Note: Starting with --dangerously-skip-permissions flag enabled');
|
|
1408
|
+
console.log(' This allows Claude Code to execute commands without confirmation prompts');
|
|
1409
|
+
console.log(' Only use this with profiles you trust');
|
|
1241
1410
|
}
|
|
1411
|
+
console.log('');
|
|
1412
|
+
|
|
1413
|
+
if (extraArgs.length > 0) {
|
|
1414
|
+
const argsLabel = safe ? 'Arguments' : 'Additional arguments';
|
|
1415
|
+
console.log(`${argsLabel}: ${extraArgs.join(' ')}`);
|
|
1416
|
+
console.log('');
|
|
1417
|
+
}
|
|
1418
|
+
|
|
1419
|
+
// Merge profile env vars with current process env
|
|
1420
|
+
// Normalize all profile env values to strings (spawn requires string values)
|
|
1421
|
+
const normalizedEnv = {};
|
|
1422
|
+
for (const [key, value] of Object.entries(profile.env)) {
|
|
1423
|
+
normalizedEnv[key] = String(value ?? '');
|
|
1424
|
+
}
|
|
1425
|
+
const envVars = {...process.env, ...normalizedEnv};
|
|
1426
|
+
|
|
1427
|
+
// Spawn claude process
|
|
1428
|
+
const claude = spawn('claude', claudeArgs, {
|
|
1429
|
+
env: envVars,
|
|
1430
|
+
stdio: 'inherit' // Inherit stdin, stdout, stderr from parent process
|
|
1431
|
+
});
|
|
1432
|
+
|
|
1433
|
+
// Handle process exit
|
|
1434
|
+
claude.on('close', (code) => {
|
|
1435
|
+
if (code !== 0 && code !== null) {
|
|
1436
|
+
console.error(`Claude Code exited with code ${code}`);
|
|
1437
|
+
process.exit(code);
|
|
1438
|
+
}
|
|
1439
|
+
process.exit(0);
|
|
1440
|
+
});
|
|
1441
|
+
|
|
1442
|
+
claude.on('error', (err) => {
|
|
1443
|
+
console.error(`Error starting Claude Code: ${err.message}`);
|
|
1444
|
+
console.error('');
|
|
1445
|
+
console.error('Please make sure Claude Code CLI is installed:');
|
|
1446
|
+
console.error(' npm install -g claude-code');
|
|
1447
|
+
process.exit(1);
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
/**
|
|
1452
|
+
* Start Claude Code with specified profile (auto-approve mode)
|
|
1453
|
+
*/
|
|
1454
|
+
function start(name, extraArgs = []) {
|
|
1455
|
+
return startClaude(name, extraArgs, { safe: false });
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
/**
|
|
1459
|
+
* Start Claude Code with specified profile (safe mode - requires permission confirmation)
|
|
1460
|
+
*/
|
|
1461
|
+
function safeStart(name, extraArgs = []) {
|
|
1462
|
+
return startClaude(name, extraArgs, { safe: true });
|
|
1242
1463
|
}
|
|
1243
1464
|
|
|
1244
1465
|
/**
|
|
@@ -1257,7 +1478,7 @@ function completion(shell) {
|
|
|
1257
1478
|
process.exit(1);
|
|
1258
1479
|
}
|
|
1259
1480
|
|
|
1260
|
-
const commands = '
|
|
1481
|
+
const commands = COMMANDS.join(' ');
|
|
1261
1482
|
|
|
1262
1483
|
switch (shell) {
|
|
1263
1484
|
case 'bash':
|
|
@@ -1280,7 +1501,7 @@ _ccconfig_completions() {
|
|
|
1280
1501
|
;;
|
|
1281
1502
|
2)
|
|
1282
1503
|
case "\${prev}" in
|
|
1283
|
-
use|update|remove|rm)
|
|
1504
|
+
use|start|safe-start|update|remove|rm)
|
|
1284
1505
|
COMPREPLY=( $(compgen -W "\${profiles}" -- \${cur}) )
|
|
1285
1506
|
;;
|
|
1286
1507
|
mode)
|
|
@@ -1318,6 +1539,8 @@ _ccconfig() {
|
|
|
1318
1539
|
'add:Add new configuration'
|
|
1319
1540
|
'update:Update existing configuration'
|
|
1320
1541
|
'use:Switch to specified configuration'
|
|
1542
|
+
'start:Start Claude Code (auto-approve mode)'
|
|
1543
|
+
'safe-start:Start Claude Code (safe mode, requires confirmation)'
|
|
1321
1544
|
'remove:Remove configuration'
|
|
1322
1545
|
'rm:Remove configuration'
|
|
1323
1546
|
'current:Display current configuration'
|
|
@@ -1340,7 +1563,7 @@ _ccconfig() {
|
|
|
1340
1563
|
;;
|
|
1341
1564
|
3)
|
|
1342
1565
|
case $words[2] in
|
|
1343
|
-
use|update|remove|rm)
|
|
1566
|
+
use|start|safe-start|update|remove|rm)
|
|
1344
1567
|
_describe 'profile' profiles
|
|
1345
1568
|
;;
|
|
1346
1569
|
mode)
|
|
@@ -1377,6 +1600,8 @@ complete -c ccconfig -f -n "__fish_use_subcommand" -a "ls" -d "List all configur
|
|
|
1377
1600
|
complete -c ccconfig -f -n "__fish_use_subcommand" -a "add" -d "Add new configuration"
|
|
1378
1601
|
complete -c ccconfig -f -n "__fish_use_subcommand" -a "update" -d "Update existing configuration"
|
|
1379
1602
|
complete -c ccconfig -f -n "__fish_use_subcommand" -a "use" -d "Switch to specified configuration"
|
|
1603
|
+
complete -c ccconfig -f -n "__fish_use_subcommand" -a "start" -d "Start Claude Code (auto-approve mode)"
|
|
1604
|
+
complete -c ccconfig -f -n "__fish_use_subcommand" -a "safe-start" -d "Start Claude Code (safe mode, requires confirmation)"
|
|
1380
1605
|
complete -c ccconfig -f -n "__fish_use_subcommand" -a "remove" -d "Remove configuration"
|
|
1381
1606
|
complete -c ccconfig -f -n "__fish_use_subcommand" -a "rm" -d "Remove configuration"
|
|
1382
1607
|
complete -c ccconfig -f -n "__fish_use_subcommand" -a "current" -d "Display current configuration"
|
|
@@ -1391,8 +1616,8 @@ function __ccconfig_profiles
|
|
|
1391
1616
|
end
|
|
1392
1617
|
end
|
|
1393
1618
|
|
|
1394
|
-
# Profile name completion for use, update, remove
|
|
1395
|
-
complete -c ccconfig -f -n "__fish_seen_subcommand_from use update remove rm" -a "(__ccconfig_profiles)"
|
|
1619
|
+
# Profile name completion for use, start, safe-start, update, remove
|
|
1620
|
+
complete -c ccconfig -f -n "__fish_seen_subcommand_from use start safe-start update remove rm" -a "(__ccconfig_profiles)"
|
|
1396
1621
|
|
|
1397
1622
|
# Mode options
|
|
1398
1623
|
complete -c ccconfig -f -n "__fish_seen_subcommand_from mode" -a "settings env"
|
|
@@ -1434,7 +1659,7 @@ function Get-CconfigProfiles {
|
|
|
1434
1659
|
Register-ArgumentCompleter -Native -CommandName ccconfig -ScriptBlock {
|
|
1435
1660
|
param($wordToComplete, $commandAst, $cursorPosition)
|
|
1436
1661
|
|
|
1437
|
-
$commands = @('list', 'ls', 'add', 'update', 'use', 'remove', 'rm', 'current', 'mode', 'env', 'edit', 'completion')
|
|
1662
|
+
$commands = @('list', 'ls', 'add', 'update', 'use', 'start', 'safe-start', 'remove', 'rm', 'current', 'mode', 'env', 'edit', 'completion')
|
|
1438
1663
|
$modes = @('settings', 'env')
|
|
1439
1664
|
$formats = @('bash', 'zsh', 'fish', 'sh', 'powershell', 'pwsh', 'dotenv')
|
|
1440
1665
|
|
|
@@ -1456,7 +1681,7 @@ Register-ArgumentCompleter -Native -CommandName ccconfig -ScriptBlock {
|
|
|
1456
1681
|
# Second argument completions based on command
|
|
1457
1682
|
if ($position -eq 2 -or ($position -eq 3 -and $wordToComplete)) {
|
|
1458
1683
|
switch ($command) {
|
|
1459
|
-
{ $_ -in 'use', 'update', 'remove', 'rm' } {
|
|
1684
|
+
{ $_ -in 'use', 'start', 'safe-start', 'update', 'remove', 'rm' } {
|
|
1460
1685
|
Get-CconfigProfiles | Where-Object { $_ -like "$wordToComplete*" } | ForEach-Object {
|
|
1461
1686
|
[System.Management.Automation.CompletionResult]::new($_, $_, 'ParameterValue', $_)
|
|
1462
1687
|
}
|
|
@@ -1535,6 +1760,10 @@ function help() {
|
|
|
1535
1760
|
' update [name] Update existing configuration (interactive)');
|
|
1536
1761
|
console.log(
|
|
1537
1762
|
' use <name> [-p|--permanent] Switch to specified configuration');
|
|
1763
|
+
console.log(
|
|
1764
|
+
' start <name> [claude-args...] Start Claude Code (auto-approve mode)');
|
|
1765
|
+
console.log(
|
|
1766
|
+
' safe-start <name> [claude-args...] Start Claude Code (safe mode, requires confirmation)');
|
|
1538
1767
|
console.log(
|
|
1539
1768
|
' remove|rm <name> Remove configuration');
|
|
1540
1769
|
console.log(
|
|
@@ -1556,6 +1785,18 @@ function help() {
|
|
|
1556
1785
|
console.log(
|
|
1557
1786
|
' -s, --show-secret Show full token in current command');
|
|
1558
1787
|
console.log('');
|
|
1788
|
+
console.log('Notes:');
|
|
1789
|
+
console.log(
|
|
1790
|
+
' • Two ways to start Claude Code:');
|
|
1791
|
+
console.log(
|
|
1792
|
+
' - start: Auto-approve mode (adds --dangerously-skip-permissions)');
|
|
1793
|
+
console.log(
|
|
1794
|
+
' Fast and convenient, but use only with profiles you trust');
|
|
1795
|
+
console.log(
|
|
1796
|
+
' - safe-start: Safe mode (requires manual confirmation for each command)');
|
|
1797
|
+
console.log(
|
|
1798
|
+
' Recommended for production or untrusted environments');
|
|
1799
|
+
console.log('');
|
|
1559
1800
|
console.log('Configuration file locations:');
|
|
1560
1801
|
console.log(` Configuration list: ${PROFILES_FILE}`);
|
|
1561
1802
|
console.log(` Claude settings: ${CLAUDE_SETTINGS}`);
|
|
@@ -1566,7 +1807,7 @@ function help() {
|
|
|
1566
1807
|
async function main() {
|
|
1567
1808
|
const args = process.argv.slice(2);
|
|
1568
1809
|
|
|
1569
|
-
// Handle global flags first (
|
|
1810
|
+
// Handle global flags first (can appear anywhere)
|
|
1570
1811
|
if (args.includes('--version') || args.includes('-V')) {
|
|
1571
1812
|
showVersion();
|
|
1572
1813
|
return;
|
|
@@ -1577,15 +1818,55 @@ async function main() {
|
|
|
1577
1818
|
return;
|
|
1578
1819
|
}
|
|
1579
1820
|
|
|
1580
|
-
//
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1821
|
+
// Find the command (first non-flag argument)
|
|
1822
|
+
let commandIndex = -1;
|
|
1823
|
+
for (let i = 0; i < args.length; i++) {
|
|
1824
|
+
if (!args[i].startsWith('-')) {
|
|
1825
|
+
commandIndex = i;
|
|
1826
|
+
break;
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
// If no command found, default to 'list'
|
|
1831
|
+
const command = commandIndex >= 0 ? args[commandIndex] : null;
|
|
1832
|
+
|
|
1833
|
+
// Extract flags and arguments based on command type
|
|
1834
|
+
let showSecret = false;
|
|
1835
|
+
let permanent = false;
|
|
1836
|
+
let filteredArgs = [];
|
|
1837
|
+
|
|
1838
|
+
// Commands that pass through arguments to Claude Code
|
|
1839
|
+
const passThruCommands = ['start', 'safe-start'];
|
|
1840
|
+
|
|
1841
|
+
if (passThruCommands.includes(command)) {
|
|
1842
|
+
// For pass-through commands:
|
|
1843
|
+
// - Extract flags that appear BEFORE the command
|
|
1844
|
+
// - Keep command and all arguments after it unchanged (for Claude)
|
|
1845
|
+
const preCommandArgs = commandIndex >= 0 ? args.slice(0, commandIndex) : [];
|
|
1846
|
+
showSecret = preCommandArgs.includes('--show-secret') || preCommandArgs.includes('-s');
|
|
1847
|
+
permanent = preCommandArgs.includes('--permanent') || preCommandArgs.includes('-p');
|
|
1587
1848
|
|
|
1588
|
-
|
|
1849
|
+
// Keep command and all arguments after it (these go to Claude)
|
|
1850
|
+
filteredArgs = commandIndex >= 0 ? args.slice(commandIndex) : [];
|
|
1851
|
+
} else {
|
|
1852
|
+
// For normal commands:
|
|
1853
|
+
// - Extract flags from anywhere in the arguments
|
|
1854
|
+
// - Remove all recognized flags from arguments
|
|
1855
|
+
showSecret = args.includes('--show-secret') || args.includes('-s');
|
|
1856
|
+
permanent = args.includes('--permanent') || args.includes('-p');
|
|
1857
|
+
|
|
1858
|
+
// Filter out all recognized flags
|
|
1859
|
+
filteredArgs = args.filter(arg =>
|
|
1860
|
+
arg !== '--show-secret' &&
|
|
1861
|
+
arg !== '-s' &&
|
|
1862
|
+
arg !== '--permanent' &&
|
|
1863
|
+
arg !== '-p' &&
|
|
1864
|
+
arg !== '--version' &&
|
|
1865
|
+
arg !== '-V' &&
|
|
1866
|
+
arg !== '--help' &&
|
|
1867
|
+
arg !== '-h'
|
|
1868
|
+
);
|
|
1869
|
+
}
|
|
1589
1870
|
|
|
1590
1871
|
switch (command) {
|
|
1591
1872
|
case 'list':
|
|
@@ -1622,6 +1903,24 @@ async function main() {
|
|
|
1622
1903
|
case 'edit':
|
|
1623
1904
|
edit();
|
|
1624
1905
|
break;
|
|
1906
|
+
case 'start':
|
|
1907
|
+
if (!filteredArgs[1]) {
|
|
1908
|
+
console.error('Error: Missing configuration name');
|
|
1909
|
+
console.error('Usage: ccconfig start <name> [claude-args...]');
|
|
1910
|
+
process.exit(1);
|
|
1911
|
+
}
|
|
1912
|
+
// Pass all arguments after the profile name to Claude
|
|
1913
|
+
start(filteredArgs[1], filteredArgs.slice(2));
|
|
1914
|
+
break;
|
|
1915
|
+
case 'safe-start':
|
|
1916
|
+
if (!filteredArgs[1]) {
|
|
1917
|
+
console.error('Error: Missing configuration name');
|
|
1918
|
+
console.error('Usage: ccconfig safe-start <name> [claude-args...]');
|
|
1919
|
+
process.exit(1);
|
|
1920
|
+
}
|
|
1921
|
+
// Pass all arguments after the profile name to Claude
|
|
1922
|
+
safeStart(filteredArgs[1], filteredArgs.slice(2));
|
|
1923
|
+
break;
|
|
1625
1924
|
case 'completion':
|
|
1626
1925
|
completion(filteredArgs[1]);
|
|
1627
1926
|
break;
|