@kaitranntt/ccs 3.4.5 → 3.5.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/VERSION CHANGED
@@ -1 +1 @@
1
- 3.4.5
1
+ 3.5.0
@@ -1,10 +1,14 @@
1
1
  'use strict';
2
2
 
3
3
  const { spawn } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
4
6
  const ProfileRegistry = require('./profile-registry');
5
7
  const InstanceManager = require('../management/instance-manager');
6
8
  const { colored } = require('../utils/helpers');
7
9
  const { detectClaudeCli } = require('../utils/claude-detector');
10
+ const { InteractivePrompt } = require('../utils/prompt');
11
+ const CCS_VERSION = require('../../package.json').version;
8
12
 
9
13
  /**
10
14
  * Auth Commands (Simplified)
@@ -45,7 +49,10 @@ class AuthCommands {
45
49
  console.log(` ${colored('ccs "review code"', 'yellow')} # Use default profile`);
46
50
  console.log('');
47
51
  console.log(colored('Options:', 'cyan'));
48
- console.log(` ${colored('--force', 'yellow')} Allow overwriting existing profile`);
52
+ console.log(` ${colored('--force', 'yellow')} Allow overwriting existing profile (create)`);
53
+ console.log(` ${colored('--yes, -y', 'yellow')} Skip confirmation prompts (remove)`);
54
+ console.log(` ${colored('--json', 'yellow')} Output in JSON format (list, show)`);
55
+ console.log(` ${colored('--verbose', 'yellow')} Show additional details (list)`);
49
56
  console.log('');
50
57
  console.log(colored('Note:', 'cyan'));
51
58
  console.log(` By default, ${colored('ccs', 'yellow')} uses Claude CLI defaults from ~/.claude/`);
@@ -159,12 +166,37 @@ class AuthCommands {
159
166
  */
160
167
  async handleList(args) {
161
168
  const verbose = args.includes('--verbose');
169
+ const json = args.includes('--json');
162
170
 
163
171
  try {
164
172
  const profiles = this.registry.getAllProfiles();
165
173
  const defaultProfile = this.registry.getDefaultProfile();
166
174
  const profileNames = Object.keys(profiles);
167
175
 
176
+ // JSON output mode
177
+ if (json) {
178
+ const output = {
179
+ version: CCS_VERSION,
180
+ profiles: profileNames.map(name => {
181
+ const profile = profiles[name];
182
+ const isDefault = name === defaultProfile;
183
+ const instancePath = this.instanceMgr.getInstancePath(name);
184
+
185
+ return {
186
+ name: name,
187
+ type: profile.type || 'account',
188
+ is_default: isDefault,
189
+ created: profile.created,
190
+ last_used: profile.last_used || null,
191
+ instance_path: instancePath
192
+ };
193
+ })
194
+ };
195
+ console.log(JSON.stringify(output, null, 2));
196
+ return;
197
+ }
198
+
199
+ // Human-readable output
168
200
  if (profileNames.length === 0) {
169
201
  console.log(colored('No account profiles found', 'yellow'));
170
202
  console.log('');
@@ -234,11 +266,12 @@ class AuthCommands {
234
266
  */
235
267
  async handleShow(args) {
236
268
  const profileName = args.find(arg => !arg.startsWith('--'));
269
+ const json = args.includes('--json');
237
270
 
238
271
  if (!profileName) {
239
272
  console.error('[X] Profile name is required');
240
273
  console.log('');
241
- console.log(`Usage: ${colored('ccs auth show <profile>', 'yellow')}`);
274
+ console.log(`Usage: ${colored('ccs auth show <profile> [--json]', 'yellow')}`);
242
275
  process.exit(1);
243
276
  }
244
277
 
@@ -246,12 +279,41 @@ class AuthCommands {
246
279
  const profile = this.registry.getProfile(profileName);
247
280
  const defaultProfile = this.registry.getDefaultProfile();
248
281
  const isDefault = profileName === defaultProfile;
282
+ const instancePath = this.instanceMgr.getInstancePath(profileName);
283
+
284
+ // Count sessions
285
+ let sessionCount = 0;
286
+ try {
287
+ const sessionsDir = path.join(instancePath, 'session-env');
288
+ if (fs.existsSync(sessionsDir)) {
289
+ const files = fs.readdirSync(sessionsDir);
290
+ sessionCount = files.filter(f => f.endsWith('.json')).length;
291
+ }
292
+ } catch (e) {
293
+ // Ignore errors counting sessions
294
+ }
249
295
 
296
+ // JSON output mode
297
+ if (json) {
298
+ const output = {
299
+ name: profileName,
300
+ type: profile.type || 'account',
301
+ is_default: isDefault,
302
+ created: profile.created,
303
+ last_used: profile.last_used || null,
304
+ instance_path: instancePath,
305
+ session_count: sessionCount
306
+ };
307
+ console.log(JSON.stringify(output, null, 2));
308
+ return;
309
+ }
310
+
311
+ // Human-readable output
250
312
  console.log(colored(`Profile: ${profileName}`, 'bold'));
251
313
  console.log('');
252
314
  console.log(` Type: ${profile.type || 'account'}`);
253
315
  console.log(` Default: ${isDefault ? 'Yes' : 'No'}`);
254
- console.log(` Instance: ${this.instanceMgr.getInstancePath(profileName)}`);
316
+ console.log(` Instance: ${instancePath}`);
255
317
  console.log(` Created: ${new Date(profile.created).toLocaleString()}`);
256
318
 
257
319
  if (profile.last_used) {
@@ -273,13 +335,12 @@ class AuthCommands {
273
335
  * @param {Array} args - Command arguments
274
336
  */
275
337
  async handleRemove(args) {
276
- const profileName = args.find(arg => !arg.startsWith('--'));
277
- const force = args.includes('--force');
338
+ const profileName = args.find(arg => !arg.startsWith('--') && !arg.startsWith('-'));
278
339
 
279
340
  if (!profileName) {
280
341
  console.error('[X] Profile name is required');
281
342
  console.log('');
282
- console.log(`Usage: ${colored('ccs auth remove <profile> [--force]', 'yellow')}`);
343
+ console.log(`Usage: ${colored('ccs auth remove <profile> [--yes]', 'yellow')}`);
283
344
  process.exit(1);
284
345
  }
285
346
 
@@ -288,15 +349,39 @@ class AuthCommands {
288
349
  process.exit(1);
289
350
  }
290
351
 
291
- // Require --force for safety
292
- if (!force) {
293
- console.error('[X] Removal requires --force flag for safety');
352
+ try {
353
+ // Get instance path and session count for impact display
354
+ const instancePath = this.instanceMgr.getInstancePath(profileName);
355
+ let sessionCount = 0;
356
+
357
+ try {
358
+ const sessionsDir = path.join(instancePath, 'session-env');
359
+ if (fs.existsSync(sessionsDir)) {
360
+ const files = fs.readdirSync(sessionsDir);
361
+ sessionCount = files.filter(f => f.endsWith('.json')).length;
362
+ }
363
+ } catch (e) {
364
+ // Ignore errors counting sessions
365
+ }
366
+
367
+ // Display impact
368
+ console.log('');
369
+ console.log(`Profile '${colored(profileName, 'cyan')}' will be permanently deleted.`);
370
+ console.log(` Instance path: ${instancePath}`);
371
+ console.log(` Sessions: ${sessionCount} conversation${sessionCount !== 1 ? 's' : ''}`);
294
372
  console.log('');
295
- console.log(`Run: ${colored(`ccs auth remove ${profileName} --force`, 'yellow')}`);
296
- process.exit(1);
297
- }
298
373
 
299
- try {
374
+ // Interactive confirmation (or --yes flag)
375
+ const confirmed = await InteractivePrompt.confirm(
376
+ 'Delete this profile?',
377
+ { default: false } // Default to NO (safe)
378
+ );
379
+
380
+ if (!confirmed) {
381
+ console.log('[i] Cancelled');
382
+ process.exit(0);
383
+ }
384
+
300
385
  // Delete instance
301
386
  this.instanceMgr.deleteInstance(profileName);
302
387
 
@@ -3,6 +3,7 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const os = require('os');
6
+ const { findSimilarStrings } = require('../utils/helpers');
6
7
 
7
8
  /**
8
9
  * Profile Detector
@@ -85,12 +86,16 @@ class ProfileDetector {
85
86
  };
86
87
  }
87
88
 
88
- // Not found
89
- throw new Error(
90
- `Profile not found: ${profileName}\n` +
91
- `Available profiles:\n` +
92
- this._listAvailableProfiles()
93
- );
89
+ // Not found - generate suggestions
90
+ const allProfiles = this.getAllProfiles();
91
+ const allProfileNames = [...allProfiles.settings, ...allProfiles.accounts];
92
+ const suggestions = findSimilarStrings(profileName, allProfileNames);
93
+
94
+ const error = new Error(`Profile not found: ${profileName}`);
95
+ error.profileName = profileName;
96
+ error.suggestions = suggestions;
97
+ error.availableProfiles = this._listAvailableProfiles();
98
+ throw error;
94
99
  }
95
100
 
96
101
  /**
package/bin/ccs.js CHANGED
@@ -130,6 +130,7 @@ function handleHelpCommand() {
130
130
  console.log(colored('Flags:', 'cyan'));
131
131
  console.log(` ${colored('-h, --help', 'yellow')} Show this help message`);
132
132
  console.log(` ${colored('-v, --version', 'yellow')} Show version and installation info`);
133
+ console.log(` ${colored('--shell-completion', 'yellow')} Install shell auto-completion`);
133
134
  console.log('');
134
135
 
135
136
  // Configuration
@@ -149,6 +150,19 @@ function handleHelpCommand() {
149
150
  console.log(' Note: Commands, skills, and agents are symlinked across all profiles');
150
151
  console.log('');
151
152
 
153
+ // Examples
154
+ console.log(colored('Examples:', 'cyan'));
155
+ console.log(' Quick start:');
156
+ console.log(` ${colored('$ ccs', 'yellow')} # Use default account`);
157
+ console.log(` ${colored('$ ccs glm "implement API"', 'yellow')} # Cost-optimized model`);
158
+ console.log('');
159
+ console.log(' Multi-account workflow:');
160
+ console.log(` ${colored('$ ccs auth create work', 'yellow')} # Create work profile`);
161
+ console.log(` ${colored('$ ccs work "review PR"', 'yellow')} # Use work account`);
162
+ console.log('');
163
+ console.log(` For more: ${colored('https://github.com/kaitranntt/ccs#usage', 'cyan')}`);
164
+ console.log('');
165
+
152
166
  // Uninstall
153
167
  console.log(colored('Uninstall:', 'yellow'));
154
168
  console.log(' npm: npm uninstall -g @kaitranntt/ccs');
@@ -241,6 +255,10 @@ async function execClaudeWithProxy(claudeCli, profileName, args) {
241
255
  });
242
256
 
243
257
  // 3. Wait for proxy ready signal (with timeout)
258
+ const { ProgressIndicator } = require('./utils/progress-indicator');
259
+ const spinner = new ProgressIndicator('Starting GLMT proxy');
260
+ spinner.start();
261
+
244
262
  let port;
245
263
  try {
246
264
  port = await new Promise((resolve, reject) => {
@@ -268,8 +286,11 @@ async function execClaudeWithProxy(claudeCli, profileName, args) {
268
286
  }
269
287
  });
270
288
  });
289
+
290
+ spinner.succeed(`GLMT proxy ready on port ${port}`);
271
291
  } catch (error) {
272
- console.error('[X] Failed to start GLMT proxy:', error.message);
292
+ spinner.fail('Failed to start GLMT proxy');
293
+ console.error('[X] Error:', error.message);
273
294
  console.error('');
274
295
  console.error('Possible causes:');
275
296
  console.error(' 1. Port conflict (unlikely with random port)');
@@ -341,6 +362,58 @@ async function execClaudeWithProxy(claudeCli, profileName, args) {
341
362
  });
342
363
  }
343
364
 
365
+ /**
366
+ * Handle shell completion installation
367
+ */
368
+ async function handleShellCompletionCommand(args) {
369
+ const { ShellCompletionInstaller } = require('./utils/shell-completion');
370
+ const { colored } = require('./utils/helpers');
371
+
372
+ console.log(colored('Shell Completion Installer', 'bold'));
373
+ console.log('');
374
+
375
+ // Parse flags
376
+ let targetShell = null;
377
+ if (args.includes('--bash')) targetShell = 'bash';
378
+ else if (args.includes('--zsh')) targetShell = 'zsh';
379
+ else if (args.includes('--fish')) targetShell = 'fish';
380
+ else if (args.includes('--powershell')) targetShell = 'powershell';
381
+
382
+ try {
383
+ const installer = new ShellCompletionInstaller();
384
+ const result = installer.install(targetShell);
385
+
386
+ if (result.alreadyInstalled) {
387
+ console.log(colored('[OK] Shell completion already installed', 'green'));
388
+ console.log('');
389
+ return;
390
+ }
391
+
392
+ console.log(colored('[OK] Shell completion installed successfully!', 'green'));
393
+ console.log('');
394
+ console.log(result.message);
395
+ console.log('');
396
+ console.log(colored('To activate:', 'cyan'));
397
+ console.log(` ${result.reload}`);
398
+ console.log('');
399
+ console.log(colored('Then test:', 'cyan'));
400
+ console.log(' ccs <TAB> # See available profiles');
401
+ console.log(' ccs auth <TAB> # See auth subcommands');
402
+ console.log('');
403
+ } catch (error) {
404
+ console.error(colored('[X] Error:', 'red'), error.message);
405
+ console.error('');
406
+ console.error(colored('Usage:', 'yellow'));
407
+ console.error(' ccs --shell-completion # Auto-detect shell');
408
+ console.error(' ccs --shell-completion --bash # Install for bash');
409
+ console.error(' ccs --shell-completion --zsh # Install for zsh');
410
+ console.error(' ccs --shell-completion --fish # Install for fish');
411
+ console.error(' ccs --shell-completion --powershell # Install for PowerShell');
412
+ console.error('');
413
+ process.exit(1);
414
+ }
415
+ }
416
+
344
417
  // Main execution
345
418
  async function main() {
346
419
  const args = process.argv.slice(2);
@@ -369,6 +442,12 @@ async function main() {
369
442
  return;
370
443
  }
371
444
 
445
+ // Special case: shell completion installer
446
+ if (firstArg === '--shell-completion') {
447
+ await handleShellCompletionCommand(args.slice(1));
448
+ return;
449
+ }
450
+
372
451
  // Special case: doctor command
373
452
  if (firstArg === 'doctor' || firstArg === '--doctor') {
374
453
  await handleDoctorCommand();
@@ -443,7 +522,13 @@ async function main() {
443
522
  execClaude(claudeCli, remainingArgs);
444
523
  }
445
524
  } catch (error) {
446
- console.error(`[X] ${error.message}`);
525
+ // Check if this is a profile not found error with suggestions
526
+ if (error.profileName && error.availableProfiles !== undefined) {
527
+ const allProfiles = error.availableProfiles.split('\n');
528
+ ErrorManager.showProfileNotFound(error.profileName, allProfiles, error.suggestions);
529
+ } else {
530
+ console.error(`[X] ${error.message}`);
531
+ }
447
532
  process.exit(1);
448
533
  }
449
534
  }
@@ -8,6 +8,7 @@ const os = require('os');
8
8
  const SSEParser = require('./sse-parser');
9
9
  const DeltaAccumulator = require('./delta-accumulator');
10
10
  const LocaleEnforcer = require('./locale-enforcer');
11
+ const ReasoningEnforcer = require('./reasoning-enforcer');
11
12
 
12
13
  /**
13
14
  * GlmtTransformer - Convert between Anthropic and OpenAI formats with thinking and tool support
@@ -54,6 +55,11 @@ class GlmtTransformer {
54
55
 
55
56
  // Initialize locale enforcer (always enforce English)
56
57
  this.localeEnforcer = new LocaleEnforcer();
58
+
59
+ // Initialize reasoning enforcer (enabled by default for all GLMT usage)
60
+ this.reasoningEnforcer = new ReasoningEnforcer({
61
+ enabled: config.explicitReasoning ?? true
62
+ });
57
63
  }
58
64
 
59
65
  /**
@@ -104,10 +110,16 @@ class GlmtTransformer {
104
110
  anthropicRequest.messages || []
105
111
  );
106
112
 
113
+ // 4.5. Inject reasoning instruction (if enabled or thinking requested)
114
+ const messagesWithReasoning = this.reasoningEnforcer.injectInstruction(
115
+ messagesWithLocale,
116
+ thinkingConfig
117
+ );
118
+
107
119
  // 5. Convert to OpenAI format
108
120
  const openaiRequest = {
109
121
  model: glmModel,
110
- messages: this._sanitizeMessages(messagesWithLocale),
122
+ messages: this._sanitizeMessages(messagesWithReasoning),
111
123
  max_tokens: this._getMaxTokens(glmModel),
112
124
  stream: anthropicRequest.stream ?? false
113
125
  };
@@ -0,0 +1,173 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ /**
5
+ * ReasoningEnforcer - Inject explicit reasoning instructions into prompts
6
+ *
7
+ * Purpose: Force GLM models to use structured reasoning output format (<reasoning_content>)
8
+ * This complements API parameters (reasoning: true) with explicit prompt instructions.
9
+ *
10
+ * Usage:
11
+ * const enforcer = new ReasoningEnforcer({ enabled: true });
12
+ * const modifiedMessages = enforcer.injectInstruction(messages, thinkingConfig);
13
+ *
14
+ * Strategy:
15
+ * 1. If system prompt exists: Prepend reasoning instruction
16
+ * 2. If no system prompt: Prepend to first user message
17
+ * 3. Select prompt template based on effort level (low/medium/high/max)
18
+ * 4. Preserve message structure (string vs array content)
19
+ */
20
+
21
+ class ReasoningEnforcer {
22
+ constructor(options = {}) {
23
+ this.enabled = options.enabled ?? false; // Opt-in by default
24
+ this.prompts = options.prompts || this._getDefaultPrompts();
25
+ }
26
+
27
+ /**
28
+ * Inject reasoning instruction into messages
29
+ * @param {Array} messages - Messages array to modify
30
+ * @param {Object} thinkingConfig - { thinking: boolean, effort: string }
31
+ * @returns {Array} Modified messages array
32
+ */
33
+ injectInstruction(messages, thinkingConfig = {}) {
34
+ // Only inject if enabled or thinking explicitly requested
35
+ if (!this.enabled && !thinkingConfig.thinking) {
36
+ return messages;
37
+ }
38
+
39
+ // Clone messages to avoid mutation
40
+ const modifiedMessages = JSON.parse(JSON.stringify(messages));
41
+
42
+ // Select prompt based on effort level
43
+ const prompt = this._selectPrompt(thinkingConfig.effort || 'medium');
44
+
45
+ // Strategy 1: Inject into system prompt (preferred)
46
+ const systemIndex = modifiedMessages.findIndex(m => m.role === 'system');
47
+ if (systemIndex >= 0) {
48
+ const systemMsg = modifiedMessages[systemIndex];
49
+
50
+ if (typeof systemMsg.content === 'string') {
51
+ systemMsg.content = `${prompt}\n\n${systemMsg.content}`;
52
+ } else if (Array.isArray(systemMsg.content)) {
53
+ systemMsg.content.unshift({
54
+ type: 'text',
55
+ text: prompt
56
+ });
57
+ }
58
+
59
+ return modifiedMessages;
60
+ }
61
+
62
+ // Strategy 2: Prepend to first user message
63
+ const userIndex = modifiedMessages.findIndex(m => m.role === 'user');
64
+ if (userIndex >= 0) {
65
+ const userMsg = modifiedMessages[userIndex];
66
+
67
+ if (typeof userMsg.content === 'string') {
68
+ userMsg.content = `${prompt}\n\n${userMsg.content}`;
69
+ } else if (Array.isArray(userMsg.content)) {
70
+ userMsg.content.unshift({
71
+ type: 'text',
72
+ text: prompt
73
+ });
74
+ }
75
+
76
+ return modifiedMessages;
77
+ }
78
+
79
+ // No system or user messages found (edge case)
80
+ return modifiedMessages;
81
+ }
82
+
83
+ /**
84
+ * Select prompt template based on effort level
85
+ * @param {string} effort - 'low', 'medium', 'high', or 'max'
86
+ * @returns {string} Prompt template
87
+ * @private
88
+ */
89
+ _selectPrompt(effort) {
90
+ const normalizedEffort = effort.toLowerCase();
91
+ return this.prompts[normalizedEffort] || this.prompts.medium;
92
+ }
93
+
94
+ /**
95
+ * Get default prompt templates
96
+ * @returns {Object} Map of effort levels to prompts
97
+ * @private
98
+ */
99
+ _getDefaultPrompts() {
100
+ return {
101
+ low: `You are an expert reasoning model using GLM-4.6 architecture.
102
+
103
+ CRITICAL: Before answering, write 2-3 sentences of reasoning in <reasoning_content> tags.
104
+
105
+ OUTPUT FORMAT:
106
+ <reasoning_content>
107
+ (Brief analysis: what is the problem? what's the approach?)
108
+ </reasoning_content>
109
+
110
+ (Write your final answer here)`,
111
+
112
+ medium: `You are an expert reasoning model using GLM-4.6 architecture.
113
+
114
+ CRITICAL REQUIREMENTS:
115
+ 1. Always think step-by-step before answering
116
+ 2. Write your reasoning process explicitly in <reasoning_content> tags
117
+ 3. Never skip your chain of thought, even for simple problems
118
+
119
+ OUTPUT FORMAT:
120
+ <reasoning_content>
121
+ (Write your detailed thinking here: analyze the problem, explore approaches,
122
+ evaluate trade-offs, and arrive at a conclusion)
123
+ </reasoning_content>
124
+
125
+ (Write your final answer here based on your reasoning above)`,
126
+
127
+ high: `You are an expert reasoning model using GLM-4.6 architecture.
128
+
129
+ CRITICAL REQUIREMENTS:
130
+ 1. Think deeply and systematically before answering
131
+ 2. Write comprehensive reasoning in <reasoning_content> tags
132
+ 3. Explore multiple approaches and evaluate trade-offs
133
+ 4. Show all steps in your problem-solving process
134
+
135
+ OUTPUT FORMAT:
136
+ <reasoning_content>
137
+ (Write exhaustive analysis here:
138
+ - Problem decomposition
139
+ - Multiple approach exploration
140
+ - Trade-off analysis for each approach
141
+ - Edge case consideration
142
+ - Final conclusion with justification)
143
+ </reasoning_content>
144
+
145
+ (Write your final answer here based on your systematic reasoning above)`,
146
+
147
+ max: `You are an expert reasoning model using GLM-4.6 architecture.
148
+
149
+ CRITICAL REQUIREMENTS:
150
+ 1. Think exhaustively from first principles
151
+ 2. Write extremely detailed reasoning in <reasoning_content> tags
152
+ 3. Analyze ALL possible angles, approaches, and edge cases
153
+ 4. Challenge your own assumptions and explore alternatives
154
+ 5. Provide rigorous justification for every claim
155
+
156
+ OUTPUT FORMAT:
157
+ <reasoning_content>
158
+ (Write comprehensive analysis here:
159
+ - First principles breakdown
160
+ - Exhaustive approach enumeration
161
+ - Comparative analysis of all approaches
162
+ - Edge case and failure mode analysis
163
+ - Assumption validation
164
+ - Counter-argument consideration
165
+ - Final conclusion with rigorous justification)
166
+ </reasoning_content>
167
+
168
+ (Write your final answer here based on your exhaustive reasoning above)`
169
+ };
170
+ }
171
+ }
172
+
173
+ module.exports = ReasoningEnforcer;
@@ -0,0 +1,59 @@
1
+ // CCS Error Codes
2
+ // Documentation: ../../docs/errors/README.md
3
+
4
+ const ERROR_CODES = {
5
+ // Configuration Errors (E100-E199)
6
+ CONFIG_MISSING: 'E101',
7
+ CONFIG_INVALID_JSON: 'E102',
8
+ CONFIG_INVALID_PROFILE: 'E103',
9
+
10
+ // Profile Management Errors (E200-E299)
11
+ PROFILE_NOT_FOUND: 'E104',
12
+ PROFILE_ALREADY_EXISTS: 'E105',
13
+ PROFILE_CANNOT_DELETE_DEFAULT: 'E106',
14
+ PROFILE_INVALID_NAME: 'E107',
15
+
16
+ // Claude CLI Detection Errors (E300-E399)
17
+ CLAUDE_NOT_FOUND: 'E301',
18
+ CLAUDE_VERSION_INCOMPATIBLE: 'E302',
19
+ CLAUDE_EXECUTION_FAILED: 'E303',
20
+
21
+ // Network/API Errors (E400-E499)
22
+ GLMT_PROXY_TIMEOUT: 'E401',
23
+ API_KEY_MISSING: 'E402',
24
+ API_AUTH_FAILED: 'E403',
25
+ API_RATE_LIMIT: 'E404',
26
+
27
+ // File System Errors (E500-E599)
28
+ FS_CANNOT_CREATE_DIR: 'E501',
29
+ FS_CANNOT_WRITE_FILE: 'E502',
30
+ FS_CANNOT_READ_FILE: 'E503',
31
+ FS_INSTANCE_NOT_FOUND: 'E504',
32
+
33
+ // Internal Errors (E900-E999)
34
+ INTERNAL_ERROR: 'E900',
35
+ INVALID_STATE: 'E901'
36
+ };
37
+
38
+ // Error code documentation URL generator
39
+ function getErrorDocUrl(errorCode) {
40
+ return `https://github.com/kaitranntt/ccs/blob/main/docs/errors/README.md#${errorCode.toLowerCase()}`;
41
+ }
42
+
43
+ // Get error category from code
44
+ function getErrorCategory(errorCode) {
45
+ const code = parseInt(errorCode.substring(1));
46
+ if (code >= 100 && code < 200) return 'Configuration';
47
+ if (code >= 200 && code < 300) return 'Profile Management';
48
+ if (code >= 300 && code < 400) return 'Claude CLI Detection';
49
+ if (code >= 400 && code < 500) return 'Network/API';
50
+ if (code >= 500 && code < 600) return 'File System';
51
+ if (code >= 900 && code < 1000) return 'Internal';
52
+ return 'Unknown';
53
+ }
54
+
55
+ module.exports = {
56
+ ERROR_CODES,
57
+ getErrorDocUrl,
58
+ getErrorCategory
59
+ };