@tkpdx01/ccc 1.3.3 → 1.3.5

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 CHANGED
@@ -60,6 +60,7 @@ ccc webdav status # View sync status / 查看同步状态
60
60
  - **Template Support / 模板**: Based on `~/.claude/settings.json`
61
61
  - **Smart Import / 智能导入**: Auto-detect API URL and token
62
62
  - **Sync Settings / 同步**: Update from template, preserve credentials
63
+ - **Claude Env Defaults / Claude 环境变量默认值**: Auto-ensure these values in the `env` section of both `~/.claude/settings.json` and each profile: `CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1`, `CLAUDE_CODE_ATTRIBUTION_HEADER=0`, `DISABLE_INSTALLATION_CHECKS=1`
63
64
  - **WebDAV Cloud Sync / 云同步**: Encrypted sync across devices
64
65
 
65
66
  ## Sync Command / 同步命令
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tkpdx01/ccc",
3
- "version": "1.3.3",
3
+ "version": "1.3.5",
4
4
  "description": "Claude Code Settings Launcher - Manage multiple Claude Code profiles",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -13,7 +13,8 @@ import {
13
13
  resolveProfile,
14
14
  getProfileCredentials,
15
15
  getClaudeSettingsTemplate,
16
- ensureDisableNonessentialTraffic
16
+ ensureRequiredClaudeEnvSettings,
17
+ ensureClaudeSettingsExtras
17
18
  } from '../profiles.js';
18
19
 
19
20
  export function editCommand(program) {
@@ -96,13 +97,12 @@ export function editCommand(program) {
96
97
  currentProfile.env.ANTHROPIC_AUTH_TOKEN = apiKey;
97
98
  currentProfile.env.ANTHROPIC_BASE_URL = apiUrl;
98
99
 
99
- // 确保主配置有 CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC 设置
100
- ensureDisableNonessentialTraffic();
101
-
102
- // 确保影子配置也有该设置
103
- if (currentProfile.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC !== '1') {
104
- currentProfile.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = '1';
105
- }
100
+ // 确保主配置(~/.claude/settings.json)与 profile 都包含必要 env 设置
101
+ ensureRequiredClaudeEnvSettings();
102
+ ensureClaudeSettingsExtras();
103
+ currentProfile.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = '1';
104
+ currentProfile.env.CLAUDE_CODE_ATTRIBUTION_HEADER = '0';
105
+ currentProfile.env.DISABLE_INSTALLATION_CHECKS = '1';
106
106
 
107
107
  // 如果重命名
108
108
  if (newName && newName !== profile) {
@@ -126,4 +126,3 @@ export function editCommand(program) {
126
126
  }
127
127
  });
128
128
  }
129
-
@@ -5,10 +5,42 @@ import {
5
5
  getProfiles,
6
6
  profileExists,
7
7
  createProfileFromTemplate,
8
- setDefaultProfile
8
+ setDefaultProfile,
9
+ ensureClaudeSettingsExtras
9
10
  } from '../profiles.js';
10
11
  import { launchClaude } from '../launch.js';
11
12
 
13
+ const RESERVED_PROFILE_NAMES = [
14
+ 'list',
15
+ 'ls',
16
+ 'use',
17
+ 'show',
18
+ 'import',
19
+ 'if',
20
+ 'new',
21
+ 'edit',
22
+ 'delete',
23
+ 'rm',
24
+ 'sync',
25
+ 'webdav',
26
+ 'help'
27
+ ];
28
+
29
+ function isReservedProfileName(name) {
30
+ return RESERVED_PROFILE_NAMES.includes(name);
31
+ }
32
+
33
+ function validateProfileName(input) {
34
+ const trimmed = input.trim();
35
+ if (!trimmed) {
36
+ return '请输入配置名称';
37
+ }
38
+ if (isReservedProfileName(trimmed)) {
39
+ return '配置名称不能使用命令关键词';
40
+ }
41
+ return true;
42
+ }
43
+
12
44
  export function newCommand(program) {
13
45
  program
14
46
  .command('new [name]')
@@ -21,10 +53,16 @@ export function newCommand(program) {
21
53
  type: 'input',
22
54
  name: 'profileName',
23
55
  message: '配置名称:',
24
- validate: (input) => input.trim() ? true : '请输入配置名称'
56
+ validate: validateProfileName
25
57
  }
26
58
  ]);
27
59
  name = profileName;
60
+ } else {
61
+ const validationResult = validateProfileName(name);
62
+ if (validationResult !== true) {
63
+ console.log(chalk.red(validationResult));
64
+ process.exit(1);
65
+ }
28
66
  }
29
67
 
30
68
  // 检查是否已存在
@@ -60,7 +98,8 @@ export function newCommand(program) {
60
98
  type: 'input',
61
99
  name: 'finalName',
62
100
  message: 'Profile 名称:',
63
- default: name
101
+ default: name,
102
+ validate: validateProfileName
64
103
  }
65
104
  ]);
66
105
 
@@ -81,6 +120,7 @@ export function newCommand(program) {
81
120
  }
82
121
 
83
122
  ensureDirs();
123
+ ensureClaudeSettingsExtras();
84
124
  createProfileFromTemplate(finalName, apiUrl, apiKey);
85
125
  console.log(chalk.green(`\n✓ 配置 "${finalName}" 已创建(基于 ~/.claude/settings.json)`));
86
126
 
package/src/profiles.js CHANGED
@@ -1,7 +1,13 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
+ import os from 'os';
3
4
  import { CONFIG_DIR, PROFILES_DIR, DEFAULT_FILE, CLAUDE_SETTINGS_PATH } from './config.js';
4
5
 
6
+ function stringifyClaudeSettings(settings) {
7
+ // Claude Code 默认 settings.json 使用 2 空格缩进,并以换行结尾(便于 diff/兼容各平台编辑器)
8
+ return `${JSON.stringify(settings, null, 2)}\n`;
9
+ }
10
+
5
11
  // 确保目录存在
6
12
  export function ensureDirs() {
7
13
  if (!fs.existsSync(CONFIG_DIR)) {
@@ -86,24 +92,109 @@ export function getClaudeSettingsTemplate() {
86
92
  return null;
87
93
  }
88
94
 
89
- // 确保主配置中有 CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC 设置
90
- // 如果没有则添加,并返回更新后的模板
91
- export function ensureDisableNonessentialTraffic() {
95
+ function ensureClaudeEnvSettings(envUpdates) {
92
96
  const template = getClaudeSettingsTemplate();
93
97
  if (!template) {
94
98
  return null;
95
99
  }
96
100
 
97
101
  // 确保 env 对象存在
98
- if (!template.env) {
102
+ if (!template.env || typeof template.env !== 'object' || Array.isArray(template.env)) {
99
103
  template.env = {};
100
104
  }
101
105
 
102
- // 检查是否已有该设置
103
- if (template.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC !== '1') {
104
- template.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = '1';
106
+ let changed = false;
107
+ for (const [key, value] of Object.entries(envUpdates)) {
108
+ if (template.env[key] !== value) {
109
+ template.env[key] = value;
110
+ changed = true;
111
+ }
112
+ }
113
+
114
+ if (changed) {
105
115
  // 保存回主配置
106
- fs.writeFileSync(CLAUDE_SETTINGS_PATH, JSON.stringify(template, null, 2));
116
+ fs.writeFileSync(CLAUDE_SETTINGS_PATH, stringifyClaudeSettings(template));
117
+ }
118
+
119
+ return template;
120
+ }
121
+
122
+ // 确保主配置中有 CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC 设置
123
+ // 如果没有则添加,并返回更新后的模板
124
+ export function ensureDisableNonessentialTraffic() {
125
+ return ensureClaudeEnvSettings({ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1' });
126
+ }
127
+
128
+ // 确保主配置中禁用 Attribution Header(Claude Code env 变量)
129
+ export function ensureDisableAttributionHeader() {
130
+ return ensureClaudeEnvSettings({ CLAUDE_CODE_ATTRIBUTION_HEADER: '0' });
131
+ }
132
+
133
+ // 一次性确保主配置包含本工具需要的 env 设置
134
+ export function ensureRequiredClaudeEnvSettings() {
135
+ return ensureClaudeEnvSettings({
136
+ CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC: '1',
137
+ CLAUDE_CODE_ATTRIBUTION_HEADER: '0',
138
+ DISABLE_INSTALLATION_CHECKS: '1'
139
+ });
140
+ }
141
+
142
+ // 获取 statusLine 的 ccline 路径(适配不同操作系统)
143
+ function getCclineCommand() {
144
+ const platform = os.platform();
145
+ if (platform === 'win32') {
146
+ return '%USERPROFILE%\\.claude\\ccline\\ccline.exe';
147
+ } else {
148
+ // Linux 和 macOS
149
+ return '~/.claude/ccline/ccline';
150
+ }
151
+ }
152
+
153
+ // 确保主配置包含 attribution/includeCoAuthoredBy 和 statusLine 设置
154
+ export function ensureClaudeSettingsExtras() {
155
+ const template = getClaudeSettingsTemplate();
156
+ if (!template) {
157
+ return null;
158
+ }
159
+
160
+ let changed = false;
161
+
162
+ // 确保 attribution 禁用(commit/pr 为空字符串)
163
+ if (!template.attribution || typeof template.attribution !== 'object' || Array.isArray(template.attribution)) {
164
+ template.attribution = { commit: '', pr: '' };
165
+ changed = true;
166
+ } else {
167
+ if (template.attribution.commit !== '' || template.attribution.pr !== '') {
168
+ template.attribution.commit = '';
169
+ template.attribution.pr = '';
170
+ changed = true;
171
+ }
172
+ }
173
+
174
+ // 兼容旧版本:确保 includeCoAuthoredBy: false
175
+ if (template.includeCoAuthoredBy !== false) {
176
+ template.includeCoAuthoredBy = false;
177
+ changed = true;
178
+ }
179
+
180
+ // 确保 statusLine 配置
181
+ const expectedCommand = getCclineCommand();
182
+ const expectedStatusLine = {
183
+ type: 'command',
184
+ command: expectedCommand,
185
+ padding: 0
186
+ };
187
+
188
+ if (!template.statusLine ||
189
+ template.statusLine.type !== 'command' ||
190
+ template.statusLine.command !== expectedCommand ||
191
+ template.statusLine.padding !== 0) {
192
+ template.statusLine = expectedStatusLine;
193
+ changed = true;
194
+ }
195
+
196
+ if (changed) {
197
+ fs.writeFileSync(CLAUDE_SETTINGS_PATH, stringifyClaudeSettings(template));
107
198
  }
108
199
 
109
200
  return template;
@@ -126,21 +217,25 @@ export function readProfile(name) {
126
217
  export function saveProfile(name, settings) {
127
218
  ensureDirs();
128
219
  const profilePath = getProfilePath(name);
129
- fs.writeFileSync(profilePath, JSON.stringify(settings, null, 2));
220
+ fs.writeFileSync(profilePath, stringifyClaudeSettings(settings));
130
221
  }
131
222
 
132
223
  // 创建基于主配置的 profile(复制 ~/.claude/settings.json 并设置 env)
133
224
  export function createProfileFromTemplate(name, apiUrl, apiKey) {
134
- // 先确保主配置有 CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC 设置
135
- ensureDisableNonessentialTraffic();
136
-
137
- const template = getClaudeSettingsTemplate() || {};
225
+ // 先确保主配置包含必要 env 设置(也会写回 ~/.claude/settings.json)
226
+ const ensuredTemplate = ensureRequiredClaudeEnvSettings();
227
+ const template = ensuredTemplate || getClaudeSettingsTemplate() || {};
138
228
 
139
229
  // 确保 env 对象存在
140
230
  if (!template.env) {
141
231
  template.env = {};
142
232
  }
143
233
 
234
+ // 确保 profile 也包含相同设置
235
+ template.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = '1';
236
+ template.env.CLAUDE_CODE_ATTRIBUTION_HEADER = '0';
237
+ template.env.DISABLE_INSTALLATION_CHECKS = '1';
238
+
144
239
  // 只设置 API 凭证到 env
145
240
  template.env.ANTHROPIC_AUTH_TOKEN = apiKey;
146
241
  template.env.ANTHROPIC_BASE_URL = apiUrl;
@@ -151,10 +246,8 @@ export function createProfileFromTemplate(name, apiUrl, apiKey) {
151
246
 
152
247
  // 同步主配置到 profile(保留 profile 的 API 凭证)
153
248
  export function syncProfileWithTemplate(name) {
154
- // 先确保主配置有 CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC 设置
155
- ensureDisableNonessentialTraffic();
156
-
157
- const template = getClaudeSettingsTemplate();
249
+ // 先确保主配置包含必要 env 设置(也会写回 ~/.claude/settings.json)
250
+ const template = ensureRequiredClaudeEnvSettings() || getClaudeSettingsTemplate();
158
251
  if (!template) {
159
252
  return null;
160
253
  }
@@ -175,6 +268,11 @@ export function syncProfileWithTemplate(name) {
175
268
  // 确保 env 对象存在并保留 API 凭证
176
269
  newProfile.env = { ...(template.env || {}), ANTHROPIC_AUTH_TOKEN: apiKey, ANTHROPIC_BASE_URL: apiUrl };
177
270
 
271
+ // 确保 profile 也包含相同设置
272
+ newProfile.env.CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC = '1';
273
+ newProfile.env.CLAUDE_CODE_ATTRIBUTION_HEADER = '0';
274
+ newProfile.env.DISABLE_INSTALLATION_CHECKS = '1';
275
+
178
276
  saveProfile(name, newProfile);
179
277
  return newProfile;
180
278
  }
@@ -208,4 +306,3 @@ export function clearDefaultProfile() {
208
306
  fs.unlinkSync(DEFAULT_FILE);
209
307
  }
210
308
  }
211
-