@kaitranntt/ccs 3.4.6 → 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/README.ja.md +470 -146
- package/README.md +338 -151
- package/README.vi.md +484 -157
- package/VERSION +1 -1
- package/bin/auth/auth-commands.js +98 -13
- package/bin/auth/profile-detector.js +11 -6
- package/bin/ccs.js +87 -2
- package/bin/utils/error-codes.js +59 -0
- package/bin/utils/error-manager.js +38 -32
- package/bin/utils/helpers.js +65 -1
- package/bin/utils/progress-indicator.js +111 -0
- package/bin/utils/prompt.js +134 -0
- package/bin/utils/shell-completion.js +234 -0
- package/lib/ccs +541 -25
- package/lib/ccs.ps1 +381 -20
- package/lib/error-codes.ps1 +55 -0
- package/lib/error-codes.sh +63 -0
- package/lib/progress-indicator.ps1 +120 -0
- package/lib/progress-indicator.sh +117 -0
- package/lib/prompt.ps1 +109 -0
- package/lib/prompt.sh +99 -0
- package/package.json +1 -1
- package/scripts/completion/README.md +308 -0
- package/scripts/completion/ccs.bash +81 -0
- package/scripts/completion/ccs.fish +92 -0
- package/scripts/completion/ccs.ps1 +157 -0
- package/scripts/completion/ccs.zsh +130 -0
- package/scripts/postinstall.js +24 -0
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
3.
|
|
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: ${
|
|
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> [--
|
|
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
|
-
|
|
292
|
-
|
|
293
|
-
|
|
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
|
-
|
|
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
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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
|
+
};
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
3
|
const { colored } = require('./helpers');
|
|
4
|
+
const { ERROR_CODES, getErrorDocUrl } = require('./error-codes');
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
|
-
* Error types with structured messages
|
|
7
|
+
* Error types with structured messages (Legacy - kept for compatibility)
|
|
7
8
|
*/
|
|
8
9
|
const ErrorTypes = {
|
|
9
10
|
NO_CLAUDE_CLI: 'NO_CLAUDE_CLI',
|
|
@@ -18,18 +19,26 @@ const ErrorTypes = {
|
|
|
18
19
|
* Enhanced error manager with context-aware messages
|
|
19
20
|
*/
|
|
20
21
|
class ErrorManager {
|
|
22
|
+
/**
|
|
23
|
+
* Show error code and documentation URL
|
|
24
|
+
* @param {string} errorCode - Error code (e.g., E301)
|
|
25
|
+
*/
|
|
26
|
+
static showErrorCode(errorCode) {
|
|
27
|
+
console.error(colored(`Error: ${errorCode}`, 'yellow'));
|
|
28
|
+
console.error(colored(getErrorDocUrl(errorCode), 'yellow'));
|
|
29
|
+
console.error('');
|
|
30
|
+
}
|
|
31
|
+
|
|
21
32
|
/**
|
|
22
33
|
* Show Claude CLI not found error
|
|
23
34
|
*/
|
|
24
35
|
static showClaudeNotFound() {
|
|
25
36
|
console.error('');
|
|
26
|
-
console.error(colored('
|
|
27
|
-
console.error(colored('║ ERROR: Claude CLI not found ║', 'red'));
|
|
28
|
-
console.error(colored('╚══════════════════════════════════════════════════════════╝', 'red'));
|
|
37
|
+
console.error(colored('[X] Claude CLI not found', 'red'));
|
|
29
38
|
console.error('');
|
|
30
|
-
console.error('CCS requires Claude CLI to be installed.');
|
|
39
|
+
console.error('CCS requires Claude CLI to be installed and available in PATH.');
|
|
31
40
|
console.error('');
|
|
32
|
-
console.error(colored('
|
|
41
|
+
console.error(colored('Solutions:', 'yellow'));
|
|
33
42
|
console.error(' 1. Install Claude CLI:');
|
|
34
43
|
console.error(' https://docs.claude.com/en/docs/claude-code/installation');
|
|
35
44
|
console.error('');
|
|
@@ -40,8 +49,7 @@ class ErrorManager {
|
|
|
40
49
|
console.error(' 3. Custom path (if installed elsewhere):');
|
|
41
50
|
console.error(' export CCS_CLAUDE_PATH="/path/to/claude"');
|
|
42
51
|
console.error('');
|
|
43
|
-
|
|
44
|
-
console.error('');
|
|
52
|
+
this.showErrorCode(ERROR_CODES.CLAUDE_NOT_FOUND);
|
|
45
53
|
}
|
|
46
54
|
|
|
47
55
|
/**
|
|
@@ -52,9 +60,7 @@ class ErrorManager {
|
|
|
52
60
|
const isClaudeSettings = settingsPath.includes('.claude') && settingsPath.endsWith('settings.json');
|
|
53
61
|
|
|
54
62
|
console.error('');
|
|
55
|
-
console.error(colored('
|
|
56
|
-
console.error(colored('║ ERROR: Settings file not found ║', 'red'));
|
|
57
|
-
console.error(colored('╚══════════════════════════════════════════════════════════╝', 'red'));
|
|
63
|
+
console.error(colored('[X] Settings file not found', 'red'));
|
|
58
64
|
console.error('');
|
|
59
65
|
console.error(`File: ${settingsPath}`);
|
|
60
66
|
console.error('');
|
|
@@ -62,19 +68,20 @@ class ErrorManager {
|
|
|
62
68
|
if (isClaudeSettings) {
|
|
63
69
|
console.error('This file is auto-created when you login to Claude CLI.');
|
|
64
70
|
console.error('');
|
|
65
|
-
console.error(colored('
|
|
71
|
+
console.error(colored('Solutions:', 'yellow'));
|
|
66
72
|
console.error(` echo '{}' > ${settingsPath}`);
|
|
67
73
|
console.error(' claude /login');
|
|
68
74
|
console.error('');
|
|
69
75
|
console.error('Why: Newer Claude CLI versions require explicit login.');
|
|
70
76
|
} else {
|
|
71
|
-
console.error(colored('
|
|
77
|
+
console.error(colored('Solutions:', 'yellow'));
|
|
72
78
|
console.error(' npm install -g @kaitranntt/ccs --force');
|
|
73
79
|
console.error('');
|
|
74
80
|
console.error('This will recreate missing profile settings.');
|
|
75
81
|
}
|
|
76
82
|
|
|
77
83
|
console.error('');
|
|
84
|
+
this.showErrorCode(ERROR_CODES.CONFIG_INVALID_PROFILE);
|
|
78
85
|
}
|
|
79
86
|
|
|
80
87
|
/**
|
|
@@ -84,14 +91,12 @@ class ErrorManager {
|
|
|
84
91
|
*/
|
|
85
92
|
static showInvalidConfig(configPath, errorDetail) {
|
|
86
93
|
console.error('');
|
|
87
|
-
console.error(colored('
|
|
88
|
-
console.error(colored('║ ERROR: Configuration invalid ║', 'red'));
|
|
89
|
-
console.error(colored('╚══════════════════════════════════════════════════════════╝', 'red'));
|
|
94
|
+
console.error(colored('[X] Configuration invalid', 'red'));
|
|
90
95
|
console.error('');
|
|
91
96
|
console.error(`File: ${configPath}`);
|
|
92
97
|
console.error(`Issue: ${errorDetail}`);
|
|
93
98
|
console.error('');
|
|
94
|
-
console.error(colored('
|
|
99
|
+
console.error(colored('Solutions:', 'yellow'));
|
|
95
100
|
console.error(' # Backup corrupted file');
|
|
96
101
|
console.error(` mv ${configPath} ${configPath}.backup`);
|
|
97
102
|
console.error('');
|
|
@@ -100,35 +105,37 @@ class ErrorManager {
|
|
|
100
105
|
console.error('');
|
|
101
106
|
console.error('Your profile settings will be preserved.');
|
|
102
107
|
console.error('');
|
|
108
|
+
this.showErrorCode(ERROR_CODES.CONFIG_INVALID_JSON);
|
|
103
109
|
}
|
|
104
110
|
|
|
105
111
|
/**
|
|
106
112
|
* Show profile not found error
|
|
107
113
|
* @param {string} profileName - Requested profile name
|
|
108
114
|
* @param {string[]} availableProfiles - List of available profiles
|
|
109
|
-
* @param {string}
|
|
115
|
+
* @param {string[]} suggestions - Suggested profile names (fuzzy match)
|
|
110
116
|
*/
|
|
111
|
-
static showProfileNotFound(profileName, availableProfiles,
|
|
117
|
+
static showProfileNotFound(profileName, availableProfiles, suggestions = []) {
|
|
112
118
|
console.error('');
|
|
113
|
-
console.error(colored('
|
|
114
|
-
console.error(colored(`║ ERROR: Profile '${profileName}' not found${' '.repeat(Math.max(0, 35 - profileName.length))}║`, 'red'));
|
|
115
|
-
console.error(colored('╚══════════════════════════════════════════════════════════╝', 'red'));
|
|
119
|
+
console.error(colored(`[X] Profile '${profileName}' not found`, 'red'));
|
|
116
120
|
console.error('');
|
|
121
|
+
|
|
122
|
+
if (suggestions && suggestions.length > 0) {
|
|
123
|
+
console.error(colored('Did you mean:', 'yellow'));
|
|
124
|
+
suggestions.forEach(s => console.error(` ${s}`));
|
|
125
|
+
console.error('');
|
|
126
|
+
}
|
|
127
|
+
|
|
117
128
|
console.error(colored('Available profiles:', 'cyan'));
|
|
118
129
|
availableProfiles.forEach(line => console.error(` ${line}`));
|
|
119
130
|
console.error('');
|
|
120
|
-
console.error(colored('
|
|
131
|
+
console.error(colored('Solutions:', 'yellow'));
|
|
121
132
|
console.error(' # Use existing profile');
|
|
122
133
|
console.error(' ccs <profile> "your prompt"');
|
|
123
134
|
console.error('');
|
|
124
135
|
console.error(' # Create new account profile');
|
|
125
136
|
console.error(' ccs auth create <name>');
|
|
126
137
|
console.error('');
|
|
127
|
-
|
|
128
|
-
if (suggestion) {
|
|
129
|
-
console.error(colored(`Did you mean: ${suggestion}`, 'yellow'));
|
|
130
|
-
console.error('');
|
|
131
|
-
}
|
|
138
|
+
this.showErrorCode(ERROR_CODES.PROFILE_NOT_FOUND);
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
/**
|
|
@@ -137,13 +144,11 @@ class ErrorManager {
|
|
|
137
144
|
*/
|
|
138
145
|
static showPermissionDenied(path) {
|
|
139
146
|
console.error('');
|
|
140
|
-
console.error(colored('
|
|
141
|
-
console.error(colored('║ ERROR: Permission denied ║', 'red'));
|
|
142
|
-
console.error(colored('╚══════════════════════════════════════════════════════════╝', 'red'));
|
|
147
|
+
console.error(colored('[X] Permission denied', 'red'));
|
|
143
148
|
console.error('');
|
|
144
149
|
console.error(`Cannot write to: ${path}`);
|
|
145
150
|
console.error('');
|
|
146
|
-
console.error(colored('
|
|
151
|
+
console.error(colored('Solutions:', 'yellow'));
|
|
147
152
|
console.error(' # Fix ownership');
|
|
148
153
|
console.error(' sudo chown -R $USER ~/.ccs ~/.claude');
|
|
149
154
|
console.error('');
|
|
@@ -153,6 +158,7 @@ class ErrorManager {
|
|
|
153
158
|
console.error(' # Retry installation');
|
|
154
159
|
console.error(' npm install -g @kaitranntt/ccs --force');
|
|
155
160
|
console.error('');
|
|
161
|
+
this.showErrorCode(ERROR_CODES.FS_CANNOT_WRITE_FILE);
|
|
156
162
|
}
|
|
157
163
|
}
|
|
158
164
|
|
package/bin/utils/helpers.js
CHANGED
|
@@ -63,10 +63,74 @@ function expandPath(pathStr) {
|
|
|
63
63
|
return path.normalize(pathStr);
|
|
64
64
|
}
|
|
65
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Calculate Levenshtein distance between two strings
|
|
68
|
+
* @param {string} a - First string
|
|
69
|
+
* @param {string} b - Second string
|
|
70
|
+
* @returns {number} Edit distance
|
|
71
|
+
*/
|
|
72
|
+
function levenshteinDistance(a, b) {
|
|
73
|
+
if (a.length === 0) return b.length;
|
|
74
|
+
if (b.length === 0) return a.length;
|
|
75
|
+
|
|
76
|
+
const matrix = [];
|
|
77
|
+
|
|
78
|
+
// Initialize first row and column
|
|
79
|
+
for (let i = 0; i <= b.length; i++) {
|
|
80
|
+
matrix[i] = [i];
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
for (let j = 0; j <= a.length; j++) {
|
|
84
|
+
matrix[0][j] = j;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Fill in the rest of the matrix
|
|
88
|
+
for (let i = 1; i <= b.length; i++) {
|
|
89
|
+
for (let j = 1; j <= a.length; j++) {
|
|
90
|
+
if (b.charAt(i - 1) === a.charAt(j - 1)) {
|
|
91
|
+
matrix[i][j] = matrix[i - 1][j - 1];
|
|
92
|
+
} else {
|
|
93
|
+
matrix[i][j] = Math.min(
|
|
94
|
+
matrix[i - 1][j - 1] + 1, // substitution
|
|
95
|
+
matrix[i][j - 1] + 1, // insertion
|
|
96
|
+
matrix[i - 1][j] + 1 // deletion
|
|
97
|
+
);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return matrix[b.length][a.length];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Find similar strings using fuzzy matching
|
|
107
|
+
* @param {string} target - Target string
|
|
108
|
+
* @param {string[]} candidates - List of candidate strings
|
|
109
|
+
* @param {number} maxDistance - Maximum edit distance (default: 2)
|
|
110
|
+
* @returns {string[]} Similar strings sorted by distance
|
|
111
|
+
*/
|
|
112
|
+
function findSimilarStrings(target, candidates, maxDistance = 2) {
|
|
113
|
+
const targetLower = target.toLowerCase();
|
|
114
|
+
|
|
115
|
+
const matches = candidates
|
|
116
|
+
.map(candidate => ({
|
|
117
|
+
name: candidate,
|
|
118
|
+
distance: levenshteinDistance(targetLower, candidate.toLowerCase())
|
|
119
|
+
}))
|
|
120
|
+
.filter(item => item.distance <= maxDistance && item.distance > 0)
|
|
121
|
+
.sort((a, b) => a.distance - b.distance)
|
|
122
|
+
.slice(0, 3) // Show at most 3 suggestions
|
|
123
|
+
.map(item => item.name);
|
|
124
|
+
|
|
125
|
+
return matches;
|
|
126
|
+
}
|
|
127
|
+
|
|
66
128
|
|
|
67
129
|
module.exports = {
|
|
68
130
|
colors,
|
|
69
131
|
colored,
|
|
70
132
|
error,
|
|
71
|
-
expandPath
|
|
133
|
+
expandPath,
|
|
134
|
+
levenshteinDistance,
|
|
135
|
+
findSimilarStrings
|
|
72
136
|
};
|