ccconfig 1.4.1 → 1.4.3
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 +13 -13
- package/README_zh.md +13 -13
- package/ccconfig.js +215 -103
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ ccconfig use personal --permanent # or use -p for short
|
|
|
38
38
|
npm install -g ccconfig
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
### Method 1: Direct Start Mode (Recommended
|
|
41
|
+
### Method 1: Direct Start Mode (Recommended)
|
|
42
42
|
|
|
43
43
|
The easiest way to use ccconfig - directly start Claude Code with a specific profile:
|
|
44
44
|
|
|
@@ -64,21 +64,21 @@ ccconfig safe-start work # Safe mode (requires confirmation for each com
|
|
|
64
64
|
- **`ccconfig start`** - Auto-approve mode
|
|
65
65
|
- Automatically adds `--dangerously-skip-permissions` flag
|
|
66
66
|
- Commands execute without confirmation prompts
|
|
67
|
-
-
|
|
67
|
+
- **Only use with profiles you trust**
|
|
68
68
|
- Perfect for: personal projects, trusted company profiles, rapid development
|
|
69
69
|
|
|
70
70
|
- **`ccconfig safe-start`** - Safe mode
|
|
71
71
|
- Does NOT add `--dangerously-skip-permissions`
|
|
72
72
|
- Requires manual confirmation before executing each command
|
|
73
|
-
-
|
|
73
|
+
- **Recommended for production or untrusted environments**
|
|
74
74
|
- Perfect for: production systems, new profiles, sensitive data
|
|
75
75
|
|
|
76
76
|
**Advantages:**
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
77
|
+
- No shell configuration needed
|
|
78
|
+
- No manual switching required
|
|
79
|
+
- Environment variables automatically injected
|
|
80
|
+
- Works across all shells
|
|
81
|
+
- Pass additional arguments: `ccconfig start work /path/to/project --verbose`
|
|
82
82
|
|
|
83
83
|
### Method 2: Manual Switch Mode
|
|
84
84
|
|
|
@@ -269,11 +269,11 @@ Do you want to set ANTHROPIC_SMALL_FAST_MODEL? (y/N) [n]:
|
|
|
269
269
|
ccconfig supports shell completion for commands, profile names, and options. This makes it easier to discover and use commands.
|
|
270
270
|
|
|
271
271
|
**Features:**
|
|
272
|
-
-
|
|
273
|
-
-
|
|
274
|
-
-
|
|
275
|
-
-
|
|
276
|
-
-
|
|
272
|
+
- Command completion (list, add, update, use, remove, etc.)
|
|
273
|
+
- Profile name completion (dynamically reads from your configurations)
|
|
274
|
+
- Option completion (--permanent, --show-secret, etc.)
|
|
275
|
+
- Mode completion (settings, env)
|
|
276
|
+
- Format completion (bash, zsh, fish, etc.)
|
|
277
277
|
|
|
278
278
|
**Installation:**
|
|
279
279
|
|
package/README_zh.md
CHANGED
|
@@ -38,7 +38,7 @@ ccconfig use personal --permanent # 或使用 -p 简写
|
|
|
38
38
|
npm install -g ccconfig
|
|
39
39
|
```
|
|
40
40
|
|
|
41
|
-
### 方式 1
|
|
41
|
+
### 方式 1:直接启动模式(推荐)
|
|
42
42
|
|
|
43
43
|
最简单的使用方式 - 直接使用指定配置启动 Claude Code:
|
|
44
44
|
|
|
@@ -64,21 +64,21 @@ ccconfig safe-start work # 安全模式(每个命令需要确认)
|
|
|
64
64
|
- **`ccconfig start`** - 自动批准模式
|
|
65
65
|
- 自动添加 `--dangerously-skip-permissions` 标志
|
|
66
66
|
- 命令无需确认直接执行
|
|
67
|
-
-
|
|
67
|
+
- **仅在您信任的配置中使用**
|
|
68
68
|
- 适用场景:个人项目、可信的公司配置、快速开发
|
|
69
69
|
|
|
70
70
|
- **`ccconfig safe-start`** - 安全模式
|
|
71
71
|
- 不添加 `--dangerously-skip-permissions`
|
|
72
72
|
- 执行每个命令前需要手动确认
|
|
73
|
-
-
|
|
73
|
+
- **推荐用于生产环境或不可信环境**
|
|
74
74
|
- 适用场景:生产系统、新配置、敏感数据
|
|
75
75
|
|
|
76
76
|
**优势:**
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
-
|
|
80
|
-
-
|
|
81
|
-
-
|
|
77
|
+
- 无需配置 shell
|
|
78
|
+
- 无需手动切换
|
|
79
|
+
- 自动注入环境变量
|
|
80
|
+
- 支持所有 shell
|
|
81
|
+
- 可传递额外参数:`ccconfig start work /path/to/project --verbose`
|
|
82
82
|
|
|
83
83
|
### 方式 2:手动切换模式
|
|
84
84
|
|
|
@@ -269,11 +269,11 @@ Do you want to set ANTHROPIC_SMALL_FAST_MODEL? (y/N) [n]:
|
|
|
269
269
|
ccconfig 支持命令、配置名称和选项的 shell 自动补全,让您更容易发现和使用命令。
|
|
270
270
|
|
|
271
271
|
**功能:**
|
|
272
|
-
-
|
|
273
|
-
-
|
|
274
|
-
-
|
|
275
|
-
-
|
|
276
|
-
-
|
|
272
|
+
- 命令补全 (list, add, update, use, remove 等)
|
|
273
|
+
- 配置名称补全(动态读取您的配置)
|
|
274
|
+
- 选项补全 (--permanent, --show-secret 等)
|
|
275
|
+
- 模式补全 (settings, env)
|
|
276
|
+
- 格式补全 (bash, zsh, fish 等)
|
|
277
277
|
|
|
278
278
|
**安装:**
|
|
279
279
|
|
package/ccconfig.js
CHANGED
|
@@ -4,7 +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 {
|
|
7
|
+
const {spawn, execSync} = require('child_process');
|
|
8
8
|
|
|
9
9
|
// Configuration file paths
|
|
10
10
|
const CONFIG_DIR = path.join(os.homedir(), '.config', 'ccconfig');
|
|
@@ -52,7 +52,11 @@ function ensureProfilesAvailable({onEmpty} = {}) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
function ensureProfileAvailable(
|
|
55
|
-
name,
|
|
55
|
+
name,
|
|
56
|
+
{allowEmptyEnv = false,
|
|
57
|
+
onEmptyProfiles,
|
|
58
|
+
onMissingProfile,
|
|
59
|
+
onEmptyEnv} = {}) {
|
|
56
60
|
const profiles = ensureProfilesAvailable({onEmpty: onEmptyProfiles});
|
|
57
61
|
const profilesMap = getProfilesMap(profiles);
|
|
58
62
|
const profile = profilesMap[name];
|
|
@@ -68,7 +72,8 @@ function ensureProfileAvailable(
|
|
|
68
72
|
process.exit(1);
|
|
69
73
|
}
|
|
70
74
|
|
|
71
|
-
if (!allowEmptyEnv &&
|
|
75
|
+
if (!allowEmptyEnv &&
|
|
76
|
+
(!profile.env || Object.keys(profile.env).length === 0)) {
|
|
72
77
|
if (typeof onEmptyEnv === 'function') {
|
|
73
78
|
onEmptyEnv();
|
|
74
79
|
} else {
|
|
@@ -83,7 +88,10 @@ function ensureProfileAvailable(
|
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
// All supported commands
|
|
86
|
-
const COMMANDS = [
|
|
91
|
+
const COMMANDS = [
|
|
92
|
+
'list', 'ls', 'add', 'update', 'use', 'start', 'safe-start', 'remove', 'rm',
|
|
93
|
+
'current', 'mode', 'env', 'edit', 'completion'
|
|
94
|
+
];
|
|
87
95
|
|
|
88
96
|
// ccconfig markers for shell config files
|
|
89
97
|
const SHELL_MARKERS = {
|
|
@@ -140,11 +148,16 @@ function printEnvVar(key, value, mask = true) {
|
|
|
140
148
|
* Utility: Display environment variables with consistent formatting
|
|
141
149
|
*/
|
|
142
150
|
function displayEnvVars(envVars, mask = true, indent = ' ') {
|
|
143
|
-
const keys = [
|
|
151
|
+
const keys = [
|
|
152
|
+
ENV_KEYS.BASE_URL, ENV_KEYS.AUTH_TOKEN, ENV_KEYS.API_KEY, ENV_KEYS.MODEL,
|
|
153
|
+
ENV_KEYS.SMALL_FAST_MODEL
|
|
154
|
+
];
|
|
144
155
|
for (const key of keys) {
|
|
145
156
|
if (!(key in envVars)) continue;
|
|
146
157
|
const value = envVars[key];
|
|
147
|
-
if (!value && key !== ENV_KEYS.BASE_URL && key !== ENV_KEYS.AUTH_TOKEN &&
|
|
158
|
+
if (!value && key !== ENV_KEYS.BASE_URL && key !== ENV_KEYS.AUTH_TOKEN &&
|
|
159
|
+
key !== ENV_KEYS.API_KEY)
|
|
160
|
+
continue;
|
|
148
161
|
const displayValue = maskValue(key, value, mask);
|
|
149
162
|
console.log(`${indent}${key}: ${displayValue || '(not set)'}`);
|
|
150
163
|
}
|
|
@@ -160,16 +173,14 @@ class ReadlineHelper {
|
|
|
160
173
|
|
|
161
174
|
ensureInterface() {
|
|
162
175
|
if (!this.rl) {
|
|
163
|
-
this.rl = readline.createInterface(
|
|
164
|
-
|
|
165
|
-
output: process.stdout
|
|
166
|
-
});
|
|
176
|
+
this.rl = readline.createInterface(
|
|
177
|
+
{input: process.stdin, output: process.stdout});
|
|
167
178
|
}
|
|
168
179
|
}
|
|
169
180
|
|
|
170
181
|
async ask(question, defaultValue = '', options = {}) {
|
|
171
182
|
this.ensureInterface();
|
|
172
|
-
const {
|
|
183
|
+
const {brackets = 'parentheses'} = options;
|
|
173
184
|
const left = brackets === 'square' ? '[' : '(';
|
|
174
185
|
const right = brackets === 'square' ? ']' : ')';
|
|
175
186
|
const suffix = defaultValue ? ` ${left}${defaultValue}${right}` : '';
|
|
@@ -184,34 +195,32 @@ class ReadlineHelper {
|
|
|
184
195
|
|
|
185
196
|
async askEnvVars(existingEnv = {}) {
|
|
186
197
|
const baseUrl = await this.ask(
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
);
|
|
198
|
+
'ANTHROPIC_BASE_URL (press Enter to keep current/default)',
|
|
199
|
+
existingEnv.ANTHROPIC_BASE_URL || 'https://api.anthropic.com',
|
|
200
|
+
{brackets: existingEnv.ANTHROPIC_BASE_URL ? 'square' : 'parentheses'});
|
|
191
201
|
|
|
192
202
|
const authToken = await this.ask(
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
203
|
+
'ANTHROPIC_AUTH_TOKEN (press Enter to keep current/set empty)',
|
|
204
|
+
existingEnv.ANTHROPIC_AUTH_TOKEN || '', {
|
|
205
|
+
brackets: existingEnv.ANTHROPIC_AUTH_TOKEN ? 'square' : 'parentheses'
|
|
206
|
+
});
|
|
197
207
|
|
|
198
208
|
const apiKey = await this.ask(
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
);
|
|
209
|
+
'ANTHROPIC_API_KEY (press Enter to keep current/set empty)',
|
|
210
|
+
existingEnv.ANTHROPIC_API_KEY || '',
|
|
211
|
+
{brackets: existingEnv.ANTHROPIC_API_KEY ? 'square' : 'parentheses'});
|
|
203
212
|
|
|
204
213
|
const model = await this.ask(
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
);
|
|
214
|
+
'ANTHROPIC_MODEL (press Enter to skip/keep current)',
|
|
215
|
+
existingEnv.ANTHROPIC_MODEL || '',
|
|
216
|
+
{brackets: existingEnv.ANTHROPIC_MODEL ? 'square' : 'parentheses'});
|
|
209
217
|
|
|
210
218
|
const smallFastModel = await this.ask(
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
219
|
+
'ANTHROPIC_SMALL_FAST_MODEL (press Enter to skip/keep current)',
|
|
220
|
+
existingEnv.ANTHROPIC_SMALL_FAST_MODEL || '', {
|
|
221
|
+
brackets: existingEnv.ANTHROPIC_SMALL_FAST_MODEL ? 'square' :
|
|
222
|
+
'parentheses'
|
|
223
|
+
});
|
|
215
224
|
|
|
216
225
|
const envVars = {
|
|
217
226
|
[ENV_KEYS.BASE_URL]: baseUrl || '',
|
|
@@ -249,14 +258,17 @@ function requireInteractive(commandName) {
|
|
|
249
258
|
* Utility: Display environment variables section for current command
|
|
250
259
|
*/
|
|
251
260
|
function displayEnvSection(envVars, showSecret) {
|
|
252
|
-
if (!envVars ||
|
|
261
|
+
if (!envVars ||
|
|
262
|
+
(!envVars[ENV_KEYS.BASE_URL] && !envVars[ENV_KEYS.AUTH_TOKEN] &&
|
|
263
|
+
!envVars[ENV_KEYS.API_KEY])) {
|
|
253
264
|
console.log(' (not configured)');
|
|
254
265
|
return;
|
|
255
266
|
}
|
|
256
267
|
|
|
257
268
|
const normalizedEnv = {
|
|
258
269
|
[ENV_KEYS.BASE_URL]: envVars[ENV_KEYS.BASE_URL] || '(not set)',
|
|
259
|
-
[ENV_KEYS.AUTH_TOKEN]: envVars[ENV_KEYS.AUTH_TOKEN] ||
|
|
270
|
+
[ENV_KEYS.AUTH_TOKEN]: envVars[ENV_KEYS.AUTH_TOKEN] ||
|
|
271
|
+
envVars[ENV_KEYS.API_KEY] || '(not set)',
|
|
260
272
|
[ENV_KEYS.MODEL]: envVars[ENV_KEYS.MODEL],
|
|
261
273
|
[ENV_KEYS.SMALL_FAST_MODEL]: envVars[ENV_KEYS.SMALL_FAST_MODEL]
|
|
262
274
|
};
|
|
@@ -269,12 +281,14 @@ function displayEnvSection(envVars, showSecret) {
|
|
|
269
281
|
|
|
270
282
|
// Display with aligned columns
|
|
271
283
|
console.log(` ${ENV_KEYS.BASE_URL}: ${normalizedEnv[ENV_KEYS.BASE_URL]}`);
|
|
272
|
-
console.log(
|
|
284
|
+
console.log(
|
|
285
|
+
` ${ENV_KEYS.AUTH_TOKEN}: ${normalizedEnv[ENV_KEYS.AUTH_TOKEN]}`);
|
|
273
286
|
if (normalizedEnv[ENV_KEYS.MODEL]) {
|
|
274
287
|
console.log(` ${ENV_KEYS.MODEL}: ${normalizedEnv[ENV_KEYS.MODEL]}`);
|
|
275
288
|
}
|
|
276
289
|
if (normalizedEnv[ENV_KEYS.SMALL_FAST_MODEL]) {
|
|
277
|
-
console.log(` ${ENV_KEYS.SMALL_FAST_MODEL}: ${
|
|
290
|
+
console.log(` ${ENV_KEYS.SMALL_FAST_MODEL}: ${
|
|
291
|
+
normalizedEnv[ENV_KEYS.SMALL_FAST_MODEL]}`);
|
|
278
292
|
}
|
|
279
293
|
}
|
|
280
294
|
|
|
@@ -315,7 +329,8 @@ function validateConfigName(name, allowEmpty = false) {
|
|
|
315
329
|
// Limit length to prevent issues
|
|
316
330
|
const MAX_NAME_LENGTH = 50;
|
|
317
331
|
if (name.length > MAX_NAME_LENGTH) {
|
|
318
|
-
console.error(`Error: Configuration name too long (max ${
|
|
332
|
+
console.error(`Error: Configuration name too long (max ${
|
|
333
|
+
MAX_NAME_LENGTH} characters)`);
|
|
319
334
|
console.error(`Current length: ${name.length}`);
|
|
320
335
|
process.exit(1);
|
|
321
336
|
}
|
|
@@ -451,10 +466,10 @@ function writeEnvFile(envVars) {
|
|
|
451
466
|
const lines = Object.entries(envVars).map(([key, value]) => {
|
|
452
467
|
// Escape special characters to prevent injection
|
|
453
468
|
const escapedValue = String(value ?? '')
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
469
|
+
.replace(/\\/g, '\\\\')
|
|
470
|
+
.replace(/\n/g, '\\n')
|
|
471
|
+
.replace(/\r/g, '\\r')
|
|
472
|
+
.replace(/\t/g, '\\t');
|
|
458
473
|
return `${key}=${escapedValue}`;
|
|
459
474
|
});
|
|
460
475
|
const content = lines.join('\n') + '\n';
|
|
@@ -481,17 +496,17 @@ function readEnvFile() {
|
|
|
481
496
|
const content = fs.readFileSync(ENV_FILE, 'utf-8');
|
|
482
497
|
const env = {};
|
|
483
498
|
content.split('\n').forEach(line => {
|
|
484
|
-
// Only accept valid environment variable names: starts with letter or
|
|
485
|
-
// followed by letters, numbers, or underscores
|
|
499
|
+
// Only accept valid environment variable names: starts with letter or
|
|
500
|
+
// underscore, followed by letters, numbers, or underscores
|
|
486
501
|
const match = line.match(/^([A-Z_][A-Z0-9_]*)=(.*)$/);
|
|
487
502
|
if (match) {
|
|
488
503
|
// Unescape special characters
|
|
489
504
|
// IMPORTANT: Must unescape \\\\ first to avoid double-unescaping
|
|
490
505
|
const unescapedValue = match[2]
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
506
|
+
.replace(/\\\\/g, '\\')
|
|
507
|
+
.replace(/\\n/g, '\n')
|
|
508
|
+
.replace(/\\r/g, '\r')
|
|
509
|
+
.replace(/\\t/g, '\t');
|
|
495
510
|
env[match[1]] = unescapedValue;
|
|
496
511
|
}
|
|
497
512
|
});
|
|
@@ -631,7 +646,8 @@ function list() {
|
|
|
631
646
|
console.log(` Model: ${profile.env.ANTHROPIC_MODEL}`);
|
|
632
647
|
}
|
|
633
648
|
if (profile.env && profile.env.ANTHROPIC_SMALL_FAST_MODEL) {
|
|
634
|
-
console.log(
|
|
649
|
+
console.log(
|
|
650
|
+
` Small Fast Model: ${profile.env.ANTHROPIC_SMALL_FAST_MODEL}`);
|
|
635
651
|
}
|
|
636
652
|
console.log('');
|
|
637
653
|
}
|
|
@@ -696,7 +712,8 @@ async function add(name) {
|
|
|
696
712
|
console.log('');
|
|
697
713
|
console.log('This information has been saved to:');
|
|
698
714
|
console.log(` ${PROFILES_FILE}`);
|
|
699
|
-
console.log(
|
|
715
|
+
console.log(
|
|
716
|
+
'You can edit this file directly to further customize the profile:');
|
|
700
717
|
console.log(` vim ${PROFILES_FILE}`);
|
|
701
718
|
console.log('Or run ccconfig edit to open it with your preferred editor');
|
|
702
719
|
} finally {
|
|
@@ -730,7 +747,8 @@ async function update(name) {
|
|
|
730
747
|
console.error(`Error: Configuration '${name}' does not exist`);
|
|
731
748
|
console.error('');
|
|
732
749
|
console.error('Run ccconfig list to see available configurations');
|
|
733
|
-
console.error(
|
|
750
|
+
console.error(
|
|
751
|
+
`Or use 'ccconfig add ${name}' to create a new configuration`);
|
|
734
752
|
process.exit(1);
|
|
735
753
|
}
|
|
736
754
|
});
|
|
@@ -738,7 +756,8 @@ async function update(name) {
|
|
|
738
756
|
const existingEnv = profile.env || {};
|
|
739
757
|
|
|
740
758
|
console.log(`Updating configuration '${name}'`);
|
|
741
|
-
console.log(
|
|
759
|
+
console.log(
|
|
760
|
+
'Press Enter to keep current value/default, or enter new value to update');
|
|
742
761
|
console.log('');
|
|
743
762
|
|
|
744
763
|
const envVars = await helper.askEnvVars(existingEnv);
|
|
@@ -812,7 +831,9 @@ const ShellUtils = {
|
|
|
812
831
|
},
|
|
813
832
|
fish: (value) => {
|
|
814
833
|
const str = value == null ? '' : String(value);
|
|
815
|
-
return str.replace(/\\/g, '\\\\')
|
|
834
|
+
return str.replace(/\\/g, '\\\\')
|
|
835
|
+
.replace(/"/g, '\\"')
|
|
836
|
+
.replace(/\$/g, '\\$');
|
|
816
837
|
},
|
|
817
838
|
pwsh: (value) => {
|
|
818
839
|
const str = value == null ? '' : String(value);
|
|
@@ -827,10 +848,12 @@ const ShellUtils = {
|
|
|
827
848
|
if (process.env.FISH_VERSION || shellPath.includes('fish')) {
|
|
828
849
|
return 'fish';
|
|
829
850
|
}
|
|
830
|
-
if (process.env.ZSH_NAME || process.env.ZSH_VERSION ||
|
|
851
|
+
if (process.env.ZSH_NAME || process.env.ZSH_VERSION ||
|
|
852
|
+
shellPath.includes('zsh')) {
|
|
831
853
|
return 'zsh';
|
|
832
854
|
}
|
|
833
|
-
if (process.env.POWERSHELL_DISTRIBUTION_CHANNEL ||
|
|
855
|
+
if (process.env.POWERSHELL_DISTRIBUTION_CHANNEL ||
|
|
856
|
+
shellPath.includes('pwsh') || shellPath.includes('powershell')) {
|
|
834
857
|
return 'powershell';
|
|
835
858
|
}
|
|
836
859
|
if (shellPath.includes('bash')) {
|
|
@@ -851,14 +874,17 @@ const ShellUtils = {
|
|
|
851
874
|
const configs = {
|
|
852
875
|
fish: path.join(homeDir, '.config', 'fish', 'config.fish'),
|
|
853
876
|
zsh: path.join(homeDir, '.zshrc'),
|
|
854
|
-
bash: process.platform === 'darwin'
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
877
|
+
bash: process.platform === 'darwin' ?
|
|
878
|
+
(fs.existsSync(path.join(homeDir, '.bash_profile')) ||
|
|
879
|
+
!fs.existsSync(path.join(homeDir, '.bashrc')) ?
|
|
880
|
+
path.join(homeDir, '.bash_profile') :
|
|
881
|
+
path.join(homeDir, '.bashrc')) :
|
|
882
|
+
path.join(homeDir, '.bashrc'),
|
|
883
|
+
powershell: process.platform === 'win32' ?
|
|
884
|
+
path.join(
|
|
885
|
+
process.env.USERPROFILE || homeDir, 'Documents', 'PowerShell',
|
|
886
|
+
'Microsoft.PowerShell_profile.ps1') :
|
|
887
|
+
path.join(homeDir, '.config', 'powershell', 'profile.ps1')
|
|
862
888
|
};
|
|
863
889
|
return configs[shellType];
|
|
864
890
|
},
|
|
@@ -893,11 +919,10 @@ const ShellUtils = {
|
|
|
893
919
|
break;
|
|
894
920
|
case 'dotenv':
|
|
895
921
|
const renderedValue = value == null ? '' : String(value);
|
|
896
|
-
const escapedValue = renderedValue
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
.replace(/\t/g, '\\t');
|
|
922
|
+
const escapedValue = renderedValue.replace(/\\/g, '\\\\')
|
|
923
|
+
.replace(/\n/g, '\\n')
|
|
924
|
+
.replace(/\r/g, '\\r')
|
|
925
|
+
.replace(/\t/g, '\\t');
|
|
901
926
|
lines.push(`${key}=${escapedValue}`);
|
|
902
927
|
break;
|
|
903
928
|
}
|
|
@@ -907,9 +932,15 @@ const ShellUtils = {
|
|
|
907
932
|
};
|
|
908
933
|
|
|
909
934
|
// Legacy function wrappers for backward compatibility
|
|
910
|
-
function escapePosix(value) {
|
|
911
|
-
|
|
912
|
-
|
|
935
|
+
function escapePosix(value) {
|
|
936
|
+
return ShellUtils.escape.posix(value);
|
|
937
|
+
}
|
|
938
|
+
function escapeFish(value) {
|
|
939
|
+
return ShellUtils.escape.fish(value);
|
|
940
|
+
}
|
|
941
|
+
function escapePwsh(value) {
|
|
942
|
+
return ShellUtils.escape.pwsh(value);
|
|
943
|
+
}
|
|
913
944
|
|
|
914
945
|
/**
|
|
915
946
|
* Detect shell type and config file path
|
|
@@ -950,7 +981,8 @@ async function writePermanentEnv(envVars) {
|
|
|
950
981
|
const maskedEnvLines = ShellUtils.formatEnvVars(maskedEnvVars, shell);
|
|
951
982
|
|
|
952
983
|
const envBlock = `${marker}\n${envLines.join('\n')}\n${markerEnd}\n`;
|
|
953
|
-
const maskedEnvBlock =
|
|
984
|
+
const maskedEnvBlock =
|
|
985
|
+
`${marker}\n${maskedEnvLines.join('\n')}\n${markerEnd}\n`;
|
|
954
986
|
|
|
955
987
|
// Display warning and confirmation
|
|
956
988
|
console.log('');
|
|
@@ -1319,12 +1351,15 @@ function env(format = 'bash') {
|
|
|
1319
1351
|
const envVars = getActiveEnvVars();
|
|
1320
1352
|
|
|
1321
1353
|
if (!envVars || Object.keys(envVars).length === 0) {
|
|
1322
|
-
console.error(
|
|
1323
|
-
|
|
1354
|
+
console.error(
|
|
1355
|
+
'Error: No available environment variable configuration found');
|
|
1356
|
+
console.error(
|
|
1357
|
+
'Please run ccconfig use <name> to select a configuration first');
|
|
1324
1358
|
process.exit(1);
|
|
1325
1359
|
}
|
|
1326
1360
|
|
|
1327
|
-
const supportedFormats =
|
|
1361
|
+
const supportedFormats =
|
|
1362
|
+
['fish', 'bash', 'zsh', 'sh', 'powershell', 'pwsh', 'dotenv'];
|
|
1328
1363
|
if (!supportedFormats.includes(format)) {
|
|
1329
1364
|
console.error(`Error: Unsupported format: ${format}`);
|
|
1330
1365
|
console.error(`Supported formats: ${supportedFormats.join(', ')}`);
|
|
@@ -1343,7 +1378,7 @@ function env(format = 'bash') {
|
|
|
1343
1378
|
* @param {boolean} options.safe - Whether to run in safe mode (default: false)
|
|
1344
1379
|
*/
|
|
1345
1380
|
function startClaude(name, extraArgs = [], options = {}) {
|
|
1346
|
-
const {
|
|
1381
|
+
const {safe = false} = options;
|
|
1347
1382
|
const commandName = safe ? 'safe-start' : 'start';
|
|
1348
1383
|
|
|
1349
1384
|
if (!name) {
|
|
@@ -1373,8 +1408,9 @@ function startClaude(name, extraArgs = [], options = {}) {
|
|
|
1373
1408
|
|
|
1374
1409
|
// Check if claude binary exists before proceeding
|
|
1375
1410
|
try {
|
|
1376
|
-
const command =
|
|
1377
|
-
|
|
1411
|
+
const command =
|
|
1412
|
+
process.platform === 'win32' ? 'where claude' : 'which claude';
|
|
1413
|
+
execSync(command, {stdio: 'pipe'});
|
|
1378
1414
|
} catch (err) {
|
|
1379
1415
|
console.error('Error: Claude Code CLI not found');
|
|
1380
1416
|
console.error('');
|
|
@@ -1395,17 +1431,22 @@ function startClaude(name, extraArgs = [], options = {}) {
|
|
|
1395
1431
|
}
|
|
1396
1432
|
|
|
1397
1433
|
// Build Claude arguments based on mode
|
|
1398
|
-
const claudeArgs =
|
|
1434
|
+
const claudeArgs =
|
|
1435
|
+
safe ? extraArgs : ['--dangerously-skip-permissions', ...extraArgs];
|
|
1399
1436
|
|
|
1400
1437
|
// Display mode-specific notes
|
|
1401
1438
|
console.log('');
|
|
1402
1439
|
if (safe) {
|
|
1403
|
-
console.log(
|
|
1404
|
-
|
|
1440
|
+
console.log(
|
|
1441
|
+
'Note: Running in safe mode (permission confirmation required)');
|
|
1442
|
+
console.log(
|
|
1443
|
+
' Claude Code will ask for confirmation before executing commands');
|
|
1405
1444
|
console.log(' For automatic execution, use "ccconfig start" instead');
|
|
1406
1445
|
} else {
|
|
1407
|
-
console.log(
|
|
1408
|
-
|
|
1446
|
+
console.log(
|
|
1447
|
+
'Note: Starting with --dangerously-skip-permissions flag enabled');
|
|
1448
|
+
console.log(
|
|
1449
|
+
' This allows Claude Code to execute commands without confirmation prompts');
|
|
1409
1450
|
console.log(' Only use this with profiles you trust');
|
|
1410
1451
|
}
|
|
1411
1452
|
console.log('');
|
|
@@ -1420,7 +1461,10 @@ function startClaude(name, extraArgs = [], options = {}) {
|
|
|
1420
1461
|
// Normalize all profile env values to strings (spawn requires string values)
|
|
1421
1462
|
const normalizedEnv = {};
|
|
1422
1463
|
for (const [key, value] of Object.entries(profile.env)) {
|
|
1423
|
-
|
|
1464
|
+
if (value === undefined || value === null) {
|
|
1465
|
+
continue;
|
|
1466
|
+
}
|
|
1467
|
+
normalizedEnv[key] = typeof value === 'string' ? value : String(value);
|
|
1424
1468
|
}
|
|
1425
1469
|
const envVars = {...process.env, ...normalizedEnv};
|
|
1426
1470
|
|
|
@@ -1430,21 +1474,92 @@ function startClaude(name, extraArgs = [], options = {}) {
|
|
|
1430
1474
|
stdio: 'inherit' // Inherit stdin, stdout, stderr from parent process
|
|
1431
1475
|
});
|
|
1432
1476
|
|
|
1477
|
+
// Function to restore terminal state and exit
|
|
1478
|
+
const exitGracefully = (code) => {
|
|
1479
|
+
// Reset terminal to normal mode (in case it was left in raw mode)
|
|
1480
|
+
if (process.stdin.isTTY && process.stdin.setRawMode) {
|
|
1481
|
+
try {
|
|
1482
|
+
process.stdin.setRawMode(false);
|
|
1483
|
+
} catch (e) {
|
|
1484
|
+
// Ignore errors during cleanup
|
|
1485
|
+
}
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
const canResetTerminal = process.platform !== 'win32' &&
|
|
1489
|
+
process.stdin.isTTY && process.stdout.isTTY;
|
|
1490
|
+
|
|
1491
|
+
// Use stty to restore terminal settings on Unix-like systems
|
|
1492
|
+
// This is more comprehensive than just setRawMode
|
|
1493
|
+
if (canResetTerminal) {
|
|
1494
|
+
try {
|
|
1495
|
+
// 'stty sane' restores terminal to sensible settings
|
|
1496
|
+
// Use stdio: 'inherit' to ensure it operates on the same terminal
|
|
1497
|
+
execSync('stty sane', {stdio: 'inherit'});
|
|
1498
|
+
} catch (e) {
|
|
1499
|
+
// If stty fails, try basic echo and icanon reset
|
|
1500
|
+
try {
|
|
1501
|
+
execSync('stty echo icanon', {stdio: 'inherit'});
|
|
1502
|
+
} catch (e2) {
|
|
1503
|
+
try {
|
|
1504
|
+
execSync('stty echo', {stdio: 'inherit'});
|
|
1505
|
+
execSync('stty icanon', {stdio: 'inherit'});
|
|
1506
|
+
} catch (e3) {
|
|
1507
|
+
// Ignore - best effort
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
}
|
|
1512
|
+
|
|
1513
|
+
process.exit(code || 0);
|
|
1514
|
+
};
|
|
1515
|
+
|
|
1516
|
+
// Handle SIGINT (Ctrl+C) and SIGTERM
|
|
1517
|
+
const signalHandler = (signal) => {
|
|
1518
|
+
// Forward signal to child process
|
|
1519
|
+
if (claude && !claude.killed) {
|
|
1520
|
+
claude.kill(signal);
|
|
1521
|
+
}
|
|
1522
|
+
// Don't exit immediately - wait for child to exit
|
|
1523
|
+
};
|
|
1524
|
+
|
|
1525
|
+
process.on('SIGINT', signalHandler);
|
|
1526
|
+
process.on('SIGTERM', signalHandler);
|
|
1527
|
+
|
|
1433
1528
|
// Handle process exit
|
|
1434
1529
|
claude.on('close', (code) => {
|
|
1530
|
+
// Remove signal handlers to avoid duplicate handling
|
|
1531
|
+
process.removeListener('SIGINT', signalHandler);
|
|
1532
|
+
process.removeListener('SIGTERM', signalHandler);
|
|
1533
|
+
|
|
1534
|
+
// Show project promotion message on exit
|
|
1535
|
+
console.log('');
|
|
1536
|
+
console.log('──────────────────────────────────────────');
|
|
1537
|
+
console.log('Thanks for using \x1b[1mccconfig\x1b[0m!');
|
|
1538
|
+
console.log('');
|
|
1539
|
+
console.log('If you find this tool helpful, please consider:');
|
|
1540
|
+
console.log(' ⭐ Star us on GitHub: https://github.com/Danielmelody/ccconfig');
|
|
1541
|
+
console.log(' 📢 Share with others who use Claude Code');
|
|
1542
|
+
console.log('──────────────────────────────────────────');
|
|
1543
|
+
console.log('');
|
|
1544
|
+
|
|
1435
1545
|
if (code !== 0 && code !== null) {
|
|
1436
1546
|
console.error(`Claude Code exited with code ${code}`);
|
|
1437
|
-
|
|
1547
|
+
exitGracefully(code);
|
|
1548
|
+
} else {
|
|
1549
|
+
exitGracefully(0);
|
|
1438
1550
|
}
|
|
1439
|
-
process.exit(0);
|
|
1440
1551
|
});
|
|
1441
1552
|
|
|
1442
1553
|
claude.on('error', (err) => {
|
|
1554
|
+
// Remove signal handlers
|
|
1555
|
+
process.removeListener('SIGINT', signalHandler);
|
|
1556
|
+
process.removeListener('SIGTERM', signalHandler);
|
|
1557
|
+
|
|
1443
1558
|
console.error(`Error starting Claude Code: ${err.message}`);
|
|
1444
1559
|
console.error('');
|
|
1445
1560
|
console.error('Please make sure Claude Code CLI is installed:');
|
|
1446
1561
|
console.error(' npm install -g claude-code');
|
|
1447
|
-
|
|
1562
|
+
exitGracefully(1);
|
|
1448
1563
|
});
|
|
1449
1564
|
}
|
|
1450
1565
|
|
|
@@ -1452,14 +1567,15 @@ function startClaude(name, extraArgs = [], options = {}) {
|
|
|
1452
1567
|
* Start Claude Code with specified profile (auto-approve mode)
|
|
1453
1568
|
*/
|
|
1454
1569
|
function start(name, extraArgs = []) {
|
|
1455
|
-
return startClaude(name, extraArgs, {
|
|
1570
|
+
return startClaude(name, extraArgs, {safe: false});
|
|
1456
1571
|
}
|
|
1457
1572
|
|
|
1458
1573
|
/**
|
|
1459
|
-
* Start Claude Code with specified profile (safe mode - requires permission
|
|
1574
|
+
* Start Claude Code with specified profile (safe mode - requires permission
|
|
1575
|
+
* confirmation)
|
|
1460
1576
|
*/
|
|
1461
1577
|
function safeStart(name, extraArgs = []) {
|
|
1462
|
-
return startClaude(name, extraArgs, {
|
|
1578
|
+
return startClaude(name, extraArgs, {safe: true});
|
|
1463
1579
|
}
|
|
1464
1580
|
|
|
1465
1581
|
/**
|
|
@@ -1473,7 +1589,8 @@ function completion(shell) {
|
|
|
1473
1589
|
console.error('To install:');
|
|
1474
1590
|
console.error(' Bash: ccconfig completion bash >> ~/.bashrc');
|
|
1475
1591
|
console.error(' Zsh: ccconfig completion zsh >> ~/.zshrc');
|
|
1476
|
-
console.error(
|
|
1592
|
+
console.error(
|
|
1593
|
+
' Fish: ccconfig completion fish > ~/.config/fish/completions/ccconfig.fish');
|
|
1477
1594
|
console.error(' PowerShell: ccconfig completion pwsh >> $PROFILE');
|
|
1478
1595
|
process.exit(1);
|
|
1479
1596
|
}
|
|
@@ -1786,8 +1903,7 @@ function help() {
|
|
|
1786
1903
|
' -s, --show-secret Show full token in current command');
|
|
1787
1904
|
console.log('');
|
|
1788
1905
|
console.log('Notes:');
|
|
1789
|
-
console.log(
|
|
1790
|
-
' • Two ways to start Claude Code:');
|
|
1906
|
+
console.log(' • Two ways to start Claude Code:');
|
|
1791
1907
|
console.log(
|
|
1792
1908
|
' - start: Auto-approve mode (adds --dangerously-skip-permissions)');
|
|
1793
1909
|
console.log(
|
|
@@ -1843,8 +1959,10 @@ async function main() {
|
|
|
1843
1959
|
// - Extract flags that appear BEFORE the command
|
|
1844
1960
|
// - Keep command and all arguments after it unchanged (for Claude)
|
|
1845
1961
|
const preCommandArgs = commandIndex >= 0 ? args.slice(0, commandIndex) : [];
|
|
1846
|
-
showSecret = preCommandArgs.includes('--show-secret') ||
|
|
1847
|
-
|
|
1962
|
+
showSecret = preCommandArgs.includes('--show-secret') ||
|
|
1963
|
+
preCommandArgs.includes('-s');
|
|
1964
|
+
permanent =
|
|
1965
|
+
preCommandArgs.includes('--permanent') || preCommandArgs.includes('-p');
|
|
1848
1966
|
|
|
1849
1967
|
// Keep command and all arguments after it (these go to Claude)
|
|
1850
1968
|
filteredArgs = commandIndex >= 0 ? args.slice(commandIndex) : [];
|
|
@@ -1856,16 +1974,10 @@ async function main() {
|
|
|
1856
1974
|
permanent = args.includes('--permanent') || args.includes('-p');
|
|
1857
1975
|
|
|
1858
1976
|
// Filter out all recognized flags
|
|
1859
|
-
filteredArgs = args.filter(
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
arg !== '-p' &&
|
|
1864
|
-
arg !== '--version' &&
|
|
1865
|
-
arg !== '-V' &&
|
|
1866
|
-
arg !== '--help' &&
|
|
1867
|
-
arg !== '-h'
|
|
1868
|
-
);
|
|
1977
|
+
filteredArgs = args.filter(
|
|
1978
|
+
arg => arg !== '--show-secret' && arg !== '-s' &&
|
|
1979
|
+
arg !== '--permanent' && arg !== '-p' && arg !== '--version' &&
|
|
1980
|
+
arg !== '-V' && arg !== '--help' && arg !== '-h');
|
|
1869
1981
|
}
|
|
1870
1982
|
|
|
1871
1983
|
switch (command) {
|