@kaitranntt/ccs 3.1.1 → 3.3.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.
@@ -6,33 +6,119 @@ const os = require('os');
6
6
 
7
7
  /**
8
8
  * SharedManager - Manages symlinked shared directories for CCS
9
- * Phase 1: Shared Global Data via Symlinks
9
+ * v3.2.0: Symlink-based architecture
10
10
  *
11
- * Purpose: Eliminates duplication of commands/skills across profile instances
12
- * by symlinking to a single ~/.ccs/shared/ directory.
11
+ * Purpose: Eliminates duplication by symlinking:
12
+ * ~/.claude/ ~/.ccs/shared/ ← instance/
13
13
  */
14
14
  class SharedManager {
15
15
  constructor() {
16
16
  this.homeDir = os.homedir();
17
17
  this.sharedDir = path.join(this.homeDir, '.ccs', 'shared');
18
+ this.claudeDir = path.join(this.homeDir, '.claude');
18
19
  this.instancesDir = path.join(this.homeDir, '.ccs', 'instances');
19
20
  this.sharedDirs = ['commands', 'skills', 'agents'];
20
21
  }
21
22
 
22
23
  /**
23
- * Ensure shared directories exist
24
+ * Detect circular symlink before creation
25
+ * @param {string} target - Target path to link to
26
+ * @param {string} linkPath - Path where symlink will be created
27
+ * @returns {boolean} True if circular
28
+ * @private
29
+ */
30
+ _detectCircularSymlink(target, linkPath) {
31
+ // Check if target exists and is symlink
32
+ if (!fs.existsSync(target)) {
33
+ return false;
34
+ }
35
+
36
+ try {
37
+ const stats = fs.lstatSync(target);
38
+ if (!stats.isSymbolicLink()) {
39
+ return false;
40
+ }
41
+
42
+ // Resolve target's link
43
+ const targetLink = fs.readlinkSync(target);
44
+ const resolvedTarget = path.resolve(path.dirname(target), targetLink);
45
+
46
+ // Check if target points back to our shared dir or link path
47
+ const sharedDir = path.join(this.homeDir, '.ccs', 'shared');
48
+ if (resolvedTarget.startsWith(sharedDir) || resolvedTarget === linkPath) {
49
+ console.log(`[!] Circular symlink detected: ${target} → ${resolvedTarget}`);
50
+ return true;
51
+ }
52
+ } catch (err) {
53
+ // If can't read, assume not circular
54
+ return false;
55
+ }
56
+
57
+ return false;
58
+ }
59
+
60
+ /**
61
+ * Ensure shared directories exist as symlinks to ~/.claude/
62
+ * Creates ~/.claude/ structure if missing
24
63
  */
25
64
  ensureSharedDirectories() {
65
+ // Create ~/.claude/ if missing
66
+ if (!fs.existsSync(this.claudeDir)) {
67
+ console.log('[i] Creating ~/.claude/ directory structure');
68
+ fs.mkdirSync(this.claudeDir, { recursive: true, mode: 0o700 });
69
+ }
70
+
26
71
  // Create shared directory
27
72
  if (!fs.existsSync(this.sharedDir)) {
28
73
  fs.mkdirSync(this.sharedDir, { recursive: true, mode: 0o700 });
29
74
  }
30
75
 
31
- // Create shared subdirectories
76
+ // Create symlinks ~/.ccs/shared/* → ~/.claude/*
32
77
  for (const dir of this.sharedDirs) {
33
- const dirPath = path.join(this.sharedDir, dir);
34
- if (!fs.existsSync(dirPath)) {
35
- fs.mkdirSync(dirPath, { recursive: true, mode: 0o700 });
78
+ const claudePath = path.join(this.claudeDir, dir);
79
+ const sharedPath = path.join(this.sharedDir, dir);
80
+
81
+ // Create directory in ~/.claude/ if missing
82
+ if (!fs.existsSync(claudePath)) {
83
+ fs.mkdirSync(claudePath, { recursive: true, mode: 0o700 });
84
+ }
85
+
86
+ // Check for circular symlink
87
+ if (this._detectCircularSymlink(claudePath, sharedPath)) {
88
+ console.log(`[!] Skipping ${dir}: circular symlink detected`);
89
+ continue;
90
+ }
91
+
92
+ // If already a symlink pointing to correct target, skip
93
+ if (fs.existsSync(sharedPath)) {
94
+ try {
95
+ const stats = fs.lstatSync(sharedPath);
96
+ if (stats.isSymbolicLink()) {
97
+ const currentTarget = fs.readlinkSync(sharedPath);
98
+ const resolvedTarget = path.resolve(path.dirname(sharedPath), currentTarget);
99
+ if (resolvedTarget === claudePath) {
100
+ continue; // Already correct
101
+ }
102
+ }
103
+ } catch (err) {
104
+ // Continue to recreate
105
+ }
106
+
107
+ // Remove existing directory/link
108
+ fs.rmSync(sharedPath, { recursive: true, force: true });
109
+ }
110
+
111
+ // Create symlink
112
+ try {
113
+ fs.symlinkSync(claudePath, sharedPath, 'dir');
114
+ } catch (err) {
115
+ // Windows fallback: copy directory
116
+ if (process.platform === 'win32') {
117
+ this._copyDirectoryFallback(claudePath, sharedPath);
118
+ console.log(`[!] Symlink failed for ${dir}, copied instead (enable Developer Mode)`);
119
+ } else {
120
+ throw err;
121
+ }
36
122
  }
37
123
  }
38
124
  }
@@ -57,9 +143,9 @@ class SharedManager {
57
143
  try {
58
144
  fs.symlinkSync(targetPath, linkPath, 'dir');
59
145
  } catch (err) {
60
- // Windows fallback: copy directory if symlink fails
146
+ // Windows fallback
61
147
  if (process.platform === 'win32') {
62
- this._copyDirectory(targetPath, linkPath);
148
+ this._copyDirectoryFallback(targetPath, linkPath);
63
149
  console.log(`[!] Symlink failed for ${dir}, copied instead (enable Developer Mode)`);
64
150
  } else {
65
151
  throw err;
@@ -69,155 +155,130 @@ class SharedManager {
69
155
  }
70
156
 
71
157
  /**
72
- * Check if migration is needed
73
- * @returns {boolean}
74
- * @private
158
+ * Migrate from v3.1.1 (copied data in ~/.ccs/shared/) to v3.2.0 (symlinks to ~/.claude/)
159
+ * Runs once on upgrade
75
160
  */
76
- _needsMigration() {
77
- // If shared dir doesn't exist, migration needed
78
- if (!fs.existsSync(this.sharedDir)) {
79
- return true;
80
- }
81
-
82
- // Check if ALL shared directories are empty
83
- const allEmpty = this.sharedDirs.every(dir => {
84
- const dirPath = path.join(this.sharedDir, dir);
85
- if (!fs.existsSync(dirPath)) return true;
161
+ migrateFromV311() {
162
+ // Check if migration already done (shared dirs are symlinks)
163
+ const commandsPath = path.join(this.sharedDir, 'commands');
164
+ if (fs.existsSync(commandsPath)) {
86
165
  try {
87
- const files = fs.readdirSync(dirPath);
88
- return files.length === 0;
166
+ if (fs.lstatSync(commandsPath).isSymbolicLink()) {
167
+ return; // Already migrated
168
+ }
89
169
  } catch (err) {
90
- return true; // If can't read, assume empty
170
+ // Continue with migration
91
171
  }
92
- });
93
-
94
- return allEmpty;
95
- }
172
+ }
96
173
 
97
- /**
98
- * Perform migration from ~/.claude/ to ~/.ccs/shared/
99
- * @returns {object} { commands: N, skills: N, agents: N }
100
- * @private
101
- */
102
- _performMigration() {
103
- const stats = { commands: 0, skills: 0, agents: 0 };
104
- const claudeDir = path.join(this.homeDir, '.claude');
174
+ console.log('[i] Migrating from v3.1.1 to v3.2.0...');
105
175
 
106
- if (!fs.existsSync(claudeDir)) {
107
- return stats; // No content to migrate
176
+ // Ensure ~/.claude/ exists
177
+ if (!fs.existsSync(this.claudeDir)) {
178
+ fs.mkdirSync(this.claudeDir, { recursive: true, mode: 0o700 });
108
179
  }
109
180
 
110
- // Migrate commands
111
- const commandsPath = path.join(claudeDir, 'commands');
112
- if (fs.existsSync(commandsPath)) {
113
- const result = this._copyDirectory(commandsPath, path.join(this.sharedDir, 'commands'));
114
- stats.commands = result.copied;
115
- }
181
+ // Copy user modifications from ~/.ccs/shared/ to ~/.claude/
182
+ for (const dir of this.sharedDirs) {
183
+ const sharedPath = path.join(this.sharedDir, dir);
184
+ const claudePath = path.join(this.claudeDir, dir);
116
185
 
117
- // Migrate skills
118
- const skillsPath = path.join(claudeDir, 'skills');
119
- if (fs.existsSync(skillsPath)) {
120
- const result = this._copyDirectory(skillsPath, path.join(this.sharedDir, 'skills'));
121
- stats.skills = result.copied;
122
- }
186
+ if (!fs.existsSync(sharedPath)) continue;
123
187
 
124
- // Migrate agents
125
- const agentsPath = path.join(claudeDir, 'agents');
126
- if (fs.existsSync(agentsPath)) {
127
- const result = this._copyDirectory(agentsPath, path.join(this.sharedDir, 'agents'));
128
- stats.agents = result.copied;
129
- }
188
+ try {
189
+ const stats = fs.lstatSync(sharedPath);
190
+ if (!stats.isDirectory()) continue;
191
+ } catch (err) {
192
+ continue;
193
+ }
130
194
 
131
- return stats;
132
- }
195
+ // Create claude dir if missing
196
+ if (!fs.existsSync(claudePath)) {
197
+ fs.mkdirSync(claudePath, { recursive: true, mode: 0o700 });
198
+ }
133
199
 
134
- /**
135
- * Migrate existing instances to shared structure
136
- * Idempotent: Safe to run multiple times
137
- */
138
- migrateToSharedStructure() {
139
- console.log('[i] Checking for content migration...');
200
+ // Copy files from shared to claude (preserve user modifications)
201
+ try {
202
+ const entries = fs.readdirSync(sharedPath, { withFileTypes: true });
203
+ let copied = 0;
204
+
205
+ for (const entry of entries) {
206
+ const src = path.join(sharedPath, entry.name);
207
+ const dest = path.join(claudePath, entry.name);
208
+
209
+ // Skip if already exists in claude
210
+ if (fs.existsSync(dest)) continue;
211
+
212
+ if (entry.isDirectory()) {
213
+ fs.cpSync(src, dest, { recursive: true });
214
+ } else {
215
+ fs.copyFileSync(src, dest);
216
+ }
217
+ copied++;
218
+ }
140
219
 
141
- // Check if migration is needed
142
- if (!this._needsMigration()) {
143
- console.log('[OK] Migration not needed (shared dirs have content)');
144
- return;
220
+ if (copied > 0) {
221
+ console.log(`[OK] Migrated ${copied} ${dir} to ~/.claude/${dir}`);
222
+ }
223
+ } catch (err) {
224
+ console.log(`[!] Failed to migrate ${dir}: ${err.message}`);
225
+ }
145
226
  }
146
227
 
147
- console.log('[i] Migrating ~/.claude/ content to ~/.ccs/shared/...');
148
-
149
- // Create shared directories
228
+ // Now run ensureSharedDirectories to create symlinks
150
229
  this.ensureSharedDirectories();
151
230
 
152
- // Perform migration
153
- const stats = this._performMigration();
154
-
155
- // Show results
156
- const total = stats.commands + stats.skills + stats.agents;
157
- if (total === 0) {
158
- console.log('[OK] No content to migrate (empty ~/.claude/)');
159
- } else {
160
- const parts = [];
161
- if (stats.commands > 0) parts.push(`${stats.commands} commands`);
162
- if (stats.skills > 0) parts.push(`${stats.skills} skills`);
163
- if (stats.agents > 0) parts.push(`${stats.agents} agents`);
164
- console.log(`[OK] Migrated ${parts.join(', ')}`);
165
- }
166
-
167
- // Update all instances to use symlinks
231
+ // Update all instances to use new symlinks
168
232
  if (fs.existsSync(this.instancesDir)) {
169
- const instances = fs.readdirSync(this.instancesDir);
170
-
171
- for (const instance of instances) {
172
- const instancePath = path.join(this.instancesDir, instance);
173
- if (fs.statSync(instancePath).isDirectory()) {
174
- this.linkSharedDirectories(instancePath);
233
+ try {
234
+ const instances = fs.readdirSync(this.instancesDir);
235
+
236
+ for (const instance of instances) {
237
+ const instancePath = path.join(this.instancesDir, instance);
238
+ try {
239
+ if (fs.statSync(instancePath).isDirectory()) {
240
+ this.linkSharedDirectories(instancePath);
241
+ }
242
+ } catch (err) {
243
+ console.log(`[!] Failed to update instance ${instance}: ${err.message}`);
244
+ }
175
245
  }
246
+ } catch (err) {
247
+ // No instances to update
176
248
  }
177
249
  }
250
+
251
+ console.log('[OK] Migration to v3.2.0 complete');
178
252
  }
179
253
 
180
254
  /**
181
- * Copy directory recursively (SAFE: preserves existing files)
255
+ * Copy directory as fallback (Windows without Developer Mode)
182
256
  * @param {string} src - Source directory
183
257
  * @param {string} dest - Destination directory
184
- * @returns {object} { copied: N, skipped: N }
185
258
  * @private
186
259
  */
187
- _copyDirectory(src, dest) {
260
+ _copyDirectoryFallback(src, dest) {
188
261
  if (!fs.existsSync(src)) {
189
- return { copied: 0, skipped: 0 };
262
+ fs.mkdirSync(src, { recursive: true, mode: 0o700 });
263
+ return;
190
264
  }
191
265
 
192
266
  if (!fs.existsSync(dest)) {
193
- fs.mkdirSync(dest, { recursive: true });
267
+ fs.mkdirSync(dest, { recursive: true, mode: 0o700 });
194
268
  }
195
269
 
196
270
  const entries = fs.readdirSync(src, { withFileTypes: true });
197
- let copied = 0;
198
- let skipped = 0;
199
271
 
200
272
  for (const entry of entries) {
201
273
  const srcPath = path.join(src, entry.name);
202
274
  const destPath = path.join(dest, entry.name);
203
275
 
204
- // SAFETY: Skip if destination exists (preserve user modifications)
205
- if (fs.existsSync(destPath)) {
206
- skipped++;
207
- continue;
208
- }
209
-
210
276
  if (entry.isDirectory()) {
211
- const stats = this._copyDirectory(srcPath, destPath);
212
- copied += stats.copied;
213
- skipped += stats.skipped;
277
+ this._copyDirectoryFallback(srcPath, destPath);
214
278
  } else {
215
279
  fs.copyFileSync(srcPath, destPath);
216
- copied++;
217
280
  }
218
281
  }
219
-
220
- return { copied, skipped };
221
282
  }
222
283
  }
223
284
 
@@ -0,0 +1,17 @@
1
+ {
2
+ "env": {
3
+ "ANTHROPIC_BASE_URL": "https://api.z.ai/api/coding/paas/v4/chat/completions",
4
+ "ANTHROPIC_AUTH_TOKEN": "YOUR_GLM_API_KEY_HERE",
5
+ "ANTHROPIC_MODEL": "glm-4.6",
6
+ "ANTHROPIC_DEFAULT_OPUS_MODEL": "glm-4.6",
7
+ "ANTHROPIC_DEFAULT_SONNET_MODEL": "glm-4.6",
8
+ "ANTHROPIC_DEFAULT_HAIKU_MODEL": "glm-4.6",
9
+ "ANTHROPIC_TEMPERATURE": "0.2",
10
+ "ANTHROPIC_MAX_TOKENS": "65536",
11
+ "MAX_THINKING_TOKENS": "32768",
12
+ "ENABLE_STREAMING": "true",
13
+ "ANTHROPIC_SAFE_MODE": "false",
14
+ "API_TIMEOUT_MS": "3000000"
15
+ },
16
+ "alwaysThinkingEnabled": true
17
+ }
package/lib/ccs CHANGED
@@ -2,7 +2,7 @@
2
2
  set -euo pipefail
3
3
 
4
4
  # Version (updated by scripts/bump-version.sh)
5
- CCS_VERSION="3.1.1"
5
+ CCS_VERSION="3.3.0"
6
6
  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
7
7
  readonly CONFIG_FILE="${CCS_CONFIG:-$HOME/.ccs/config.json}"
8
8
  readonly PROFILES_JSON="$HOME/.ccs/profiles.json"
@@ -51,6 +51,7 @@ show_help() {
51
51
  echo -e "${CYAN}Model Switching:${RESET}"
52
52
  echo -e " ${YELLOW}ccs${RESET} Use default Claude account"
53
53
  echo -e " ${YELLOW}ccs glm${RESET} Switch to GLM 4.6 model"
54
+ echo -e " ${YELLOW}ccs glmt${RESET} Switch to GLM with thinking mode"
54
55
  echo -e " ${YELLOW}ccs kimi${RESET} Switch to Kimi for Coding"
55
56
  echo -e " ${YELLOW}ccs glm${RESET} \"debug this code\" Use GLM and run command"
56
57
  echo ""
package/lib/ccs.ps1 CHANGED
@@ -12,7 +12,7 @@ param(
12
12
  $ErrorActionPreference = "Stop"
13
13
 
14
14
  # Version (updated by scripts/bump-version.sh)
15
- $CcsVersion = "3.1.1"
15
+ $CcsVersion = "3.3.0"
16
16
  $ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
17
17
  $ConfigFile = if ($env:CCS_CONFIG) { $env:CCS_CONFIG } else { "$env:USERPROFILE\.ccs\config.json" }
18
18
  $ProfilesJson = "$env:USERPROFILE\.ccs\profiles.json"
@@ -107,6 +107,7 @@ function Show-Help {
107
107
  Write-ColorLine "Model Switching:" "Cyan"
108
108
  Write-ColorLine " ccs Use default Claude account" "Yellow"
109
109
  Write-ColorLine " ccs glm Switch to GLM 4.6 model" "Yellow"
110
+ Write-ColorLine " ccs glmt Switch to GLM with thinking mode" "Yellow"
110
111
  Write-ColorLine " ccs kimi Switch to Kimi for Coding" "Yellow"
111
112
  Write-ColorLine " ccs glm 'debug this code' Use GLM and run command" "Yellow"
112
113
  Write-Host ""
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaitranntt/ccs",
3
- "version": "3.1.1",
3
+ "version": "3.3.0",
4
4
  "description": "Claude Code Switch - Instant profile switching between Claude Sonnet 4.5 and GLM 4.6",
5
5
  "keywords": [
6
6
  "cli",
@@ -33,6 +33,7 @@ function validateConfiguration() {
33
33
  const requiredFiles = [
34
34
  { path: path.join(ccsDir, 'config.json'), name: 'config.json' },
35
35
  { path: path.join(ccsDir, 'glm.settings.json'), name: 'glm.settings.json' },
36
+ { path: path.join(ccsDir, 'glmt.settings.json'), name: 'glmt.settings.json' },
36
37
  { path: path.join(ccsDir, 'kimi.settings.json'), name: 'kimi.settings.json' }
37
38
  ];
38
39
 
@@ -89,15 +90,16 @@ function createConfigFiles() {
89
90
  }
90
91
  }
91
92
 
92
- // Migrate from ~/.claude/ to ~/.ccs/shared/ (v3.1.1)
93
+ // Migrate from v3.1.1 to v3.2.0 (symlink architecture)
93
94
  console.log('');
94
95
  try {
95
96
  const SharedManager = require('../bin/shared-manager');
96
97
  const sharedManager = new SharedManager();
97
- sharedManager.migrateToSharedStructure();
98
+ sharedManager.migrateFromV311();
99
+ sharedManager.ensureSharedDirectories();
98
100
  } catch (err) {
99
101
  console.warn('[!] Migration warning:', err.message);
100
- console.warn(' You can manually copy files from ~/.claude/ to ~/.ccs/shared/');
102
+ console.warn(' Migration will retry on next run');
101
103
  }
102
104
  console.log('');
103
105
 
@@ -107,6 +109,7 @@ function createConfigFiles() {
107
109
  const config = {
108
110
  profiles: {
109
111
  glm: '~/.ccs/glm.settings.json',
112
+ glmt: '~/.ccs/glmt.settings.json',
110
113
  kimi: '~/.ccs/kimi.settings.json',
111
114
  default: '~/.claude/settings.json'
112
115
  }
@@ -119,7 +122,21 @@ function createConfigFiles() {
119
122
 
120
123
  console.log('[OK] Created config: ~/.ccs/config.json');
121
124
  } else {
122
- console.log('[OK] Config exists: ~/.ccs/config.json (preserved)');
125
+ // Update existing config with glmt if missing (migration for v3.x users)
126
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
127
+ // Ensure profiles object exists
128
+ if (!config.profiles) {
129
+ config.profiles = {};
130
+ }
131
+ if (!config.profiles.glmt) {
132
+ config.profiles.glmt = '~/.ccs/glmt.settings.json';
133
+ const tmpPath = `${configPath}.tmp`;
134
+ fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2) + '\n', 'utf8');
135
+ fs.renameSync(tmpPath, configPath);
136
+ console.log('[OK] Updated config with GLMT profile');
137
+ } else {
138
+ console.log('[OK] Config exists: ~/.ccs/config.json (preserved)');
139
+ }
123
140
  }
124
141
 
125
142
  // Create glm.settings.json if missing
@@ -151,6 +168,94 @@ function createConfigFiles() {
151
168
  console.log('[OK] GLM profile exists: ~/.ccs/glm.settings.json (preserved)');
152
169
  }
153
170
 
171
+ // Create glmt.settings.json if missing
172
+ const glmtSettingsPath = path.join(ccsDir, 'glmt.settings.json');
173
+ if (!fs.existsSync(glmtSettingsPath)) {
174
+ const glmtSettings = {
175
+ env: {
176
+ ANTHROPIC_BASE_URL: 'https://api.z.ai/api/coding/paas/v4/chat/completions',
177
+ ANTHROPIC_AUTH_TOKEN: 'YOUR_GLM_API_KEY_HERE',
178
+ ANTHROPIC_MODEL: 'glm-4.6',
179
+ ANTHROPIC_DEFAULT_OPUS_MODEL: 'glm-4.6',
180
+ ANTHROPIC_DEFAULT_SONNET_MODEL: 'glm-4.6',
181
+ ANTHROPIC_DEFAULT_HAIKU_MODEL: 'glm-4.6',
182
+ ANTHROPIC_TEMPERATURE: '0.2',
183
+ ANTHROPIC_MAX_TOKENS: '65536',
184
+ MAX_THINKING_TOKENS: '32768',
185
+ ENABLE_STREAMING: 'true',
186
+ ANTHROPIC_SAFE_MODE: 'false',
187
+ API_TIMEOUT_MS: '3000000'
188
+ },
189
+ alwaysThinkingEnabled: true
190
+ };
191
+
192
+ // Atomic write
193
+ const tmpPath = `${glmtSettingsPath}.tmp`;
194
+ fs.writeFileSync(tmpPath, JSON.stringify(glmtSettings, null, 2) + '\n', 'utf8');
195
+ fs.renameSync(tmpPath, glmtSettingsPath);
196
+
197
+ console.log('[OK] Created GLMT profile: ~/.ccs/glmt.settings.json');
198
+ console.log('');
199
+ console.log(' [!] Configure GLMT API key:');
200
+ console.log(' 1. Get key from: https://api.z.ai');
201
+ console.log(' 2. Edit: ~/.ccs/glmt.settings.json');
202
+ console.log(' 3. Replace: YOUR_GLM_API_KEY_HERE');
203
+ console.log(' Note: GLMT enables GLM thinking mode (reasoning)');
204
+ console.log(' Defaults: Temperature 0.2, thinking enabled, 50min timeout');
205
+ } else {
206
+ console.log('[OK] GLMT profile exists: ~/.ccs/glmt.settings.json (preserved)');
207
+ }
208
+
209
+ // Migrate existing GLMT configs to include new defaults (v3.3.0)
210
+ if (fs.existsSync(glmtSettingsPath)) {
211
+ try {
212
+ const existing = JSON.parse(fs.readFileSync(glmtSettingsPath, 'utf8'));
213
+ let updated = false;
214
+
215
+ // Ensure env object exists
216
+ if (!existing.env) {
217
+ existing.env = {};
218
+ updated = true;
219
+ }
220
+
221
+ // Add missing env vars (preserve existing values)
222
+ const envDefaults = {
223
+ ANTHROPIC_TEMPERATURE: '0.2',
224
+ ANTHROPIC_MAX_TOKENS: '65536',
225
+ MAX_THINKING_TOKENS: '32768',
226
+ ENABLE_STREAMING: 'true',
227
+ ANTHROPIC_SAFE_MODE: 'false',
228
+ API_TIMEOUT_MS: '3000000'
229
+ };
230
+
231
+ for (const [key, value] of Object.entries(envDefaults)) {
232
+ if (existing.env[key] === undefined) {
233
+ existing.env[key] = value;
234
+ updated = true;
235
+ }
236
+ }
237
+
238
+ // Add alwaysThinkingEnabled if missing
239
+ if (existing.alwaysThinkingEnabled === undefined) {
240
+ existing.alwaysThinkingEnabled = true;
241
+ updated = true;
242
+ }
243
+
244
+ // Write back if updated
245
+ if (updated) {
246
+ const tmpPath = `${glmtSettingsPath}.tmp`;
247
+ fs.writeFileSync(tmpPath, JSON.stringify(existing, null, 2) + '\n', 'utf8');
248
+ fs.renameSync(tmpPath, glmtSettingsPath);
249
+ console.log('[OK] Migrated GLMT config with new defaults (v3.3.0)');
250
+ console.log(' Added: temperature, max_tokens, thinking settings, alwaysThinkingEnabled');
251
+ }
252
+ } catch (err) {
253
+ console.warn('[!] GLMT config migration failed:', err.message);
254
+ console.warn(' Existing config preserved, may be missing new defaults');
255
+ console.warn(' You can manually add fields or delete file to regenerate');
256
+ }
257
+ }
258
+
154
259
  // Create kimi.settings.json if missing
155
260
  const kimiSettingsPath = path.join(ccsDir, 'kimi.settings.json');
156
261
  if (!fs.existsSync(kimiSettingsPath)) {