@kaitranntt/ccs 5.3.0 → 5.4.0-beta.1

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.
Files changed (47) hide show
  1. package/VERSION +1 -1
  2. package/dist/auth/auth-commands.d.ts +5 -1
  3. package/dist/auth/auth-commands.d.ts.map +1 -1
  4. package/dist/auth/auth-commands.js +146 -99
  5. package/dist/auth/auth-commands.js.map +1 -1
  6. package/dist/ccs.js +7 -7
  7. package/dist/ccs.js.map +1 -1
  8. package/dist/commands/api-command.d.ts +11 -0
  9. package/dist/commands/api-command.d.ts.map +1 -0
  10. package/dist/commands/{profile-command.js → api-command.js} +142 -106
  11. package/dist/commands/api-command.js.map +1 -0
  12. package/dist/commands/help-command.d.ts +1 -1
  13. package/dist/commands/help-command.d.ts.map +1 -1
  14. package/dist/commands/help-command.js +174 -88
  15. package/dist/commands/help-command.js.map +1 -1
  16. package/dist/delegation/delegation-handler.js +2 -2
  17. package/dist/delegation/delegation-handler.js.map +1 -1
  18. package/dist/delegation/headless-executor.d.ts.map +1 -1
  19. package/dist/delegation/headless-executor.js +7 -4
  20. package/dist/delegation/headless-executor.js.map +1 -1
  21. package/dist/delegation/result-formatter.d.ts +6 -25
  22. package/dist/delegation/result-formatter.d.ts.map +1 -1
  23. package/dist/delegation/result-formatter.js +77 -116
  24. package/dist/delegation/result-formatter.js.map +1 -1
  25. package/dist/management/doctor.d.ts.map +1 -1
  26. package/dist/management/doctor.js +127 -105
  27. package/dist/management/doctor.js.map +1 -1
  28. package/dist/types/utils.d.ts +43 -0
  29. package/dist/types/utils.d.ts.map +1 -1
  30. package/dist/utils/error-manager.d.ts +9 -9
  31. package/dist/utils/error-manager.d.ts.map +1 -1
  32. package/dist/utils/error-manager.js +88 -92
  33. package/dist/utils/error-manager.js.map +1 -1
  34. package/dist/utils/helpers.d.ts +1 -0
  35. package/dist/utils/helpers.d.ts.map +1 -1
  36. package/dist/utils/helpers.js +12 -0
  37. package/dist/utils/helpers.js.map +1 -1
  38. package/dist/utils/shell-executor.js +2 -2
  39. package/dist/utils/shell-executor.js.map +1 -1
  40. package/dist/utils/ui.d.ts +147 -0
  41. package/dist/utils/ui.d.ts.map +1 -0
  42. package/dist/utils/ui.js +570 -0
  43. package/dist/utils/ui.js.map +1 -0
  44. package/package.json +5 -1
  45. package/dist/commands/profile-command.d.ts +0 -11
  46. package/dist/commands/profile-command.d.ts.map +0 -1
  47. package/dist/commands/profile-command.js.map +0 -1
@@ -33,8 +33,8 @@ const fs = __importStar(require("fs"));
33
33
  const path = __importStar(require("path"));
34
34
  const os = __importStar(require("os"));
35
35
  const child_process_1 = require("child_process");
36
- const helpers_1 = require("../utils/helpers");
37
36
  const claude_detector_1 = require("../utils/claude-detector");
37
+ const ui_1 = require("../utils/ui");
38
38
  const package_json_1 = __importDefault(require("../../package.json"));
39
39
  const cliproxy_1 = require("../cliproxy");
40
40
  let ora;
@@ -56,8 +56,6 @@ catch (_e) {
56
56
  };
57
57
  };
58
58
  }
59
- // Import cli-table3
60
- const Table = require('cli-table3');
61
59
  /**
62
60
  * Health check results
63
61
  */
@@ -104,34 +102,36 @@ class Doctor {
104
102
  * Run all health checks
105
103
  */
106
104
  async runAllChecks() {
107
- console.log((0, helpers_1.colored)('Running CCS Health Check...', 'cyan'));
105
+ await (0, ui_1.initUI)();
106
+ // Hero header box
107
+ console.log((0, ui_1.box)(`CCS Health Check v${this.ccsVersion}`, { borderStyle: 'round', padding: 0 }));
108
108
  console.log('');
109
109
  // Store CCS version in details
110
110
  this.results.details['CCS Version'] = { status: 'OK', info: `v${this.ccsVersion}` };
111
111
  // Group 1: System
112
- console.log((0, helpers_1.colored)('System:', 'bold'));
112
+ console.log((0, ui_1.header)('SYSTEM'));
113
113
  await this.checkClaudeCli();
114
114
  this.checkCcsDirectory();
115
115
  console.log('');
116
116
  // Group 2: Configuration
117
- console.log((0, helpers_1.colored)('Configuration:', 'bold'));
117
+ console.log((0, ui_1.header)('CONFIGURATION'));
118
118
  this.checkConfigFiles();
119
119
  this.checkClaudeSettings();
120
120
  console.log('');
121
121
  // Group 3: Profiles & Delegation
122
- console.log((0, helpers_1.colored)('Profiles & Delegation:', 'bold'));
122
+ console.log((0, ui_1.header)('PROFILES & DELEGATION'));
123
123
  this.checkProfiles();
124
124
  this.checkInstances();
125
125
  this.checkDelegation();
126
126
  console.log('');
127
127
  // Group 4: System Health
128
- console.log((0, helpers_1.colored)('System Health:', 'bold'));
128
+ console.log((0, ui_1.header)('SYSTEM HEALTH'));
129
129
  this.checkPermissions();
130
130
  this.checkCcsSymlinks();
131
131
  this.checkSettingsSymlinks();
132
132
  console.log('');
133
133
  // Group 5: CLIProxy (OAuth profiles)
134
- console.log((0, helpers_1.colored)('CLIProxy (OAuth Profiles):', 'bold'));
134
+ console.log((0, ui_1.header)('CLIPROXY (OAUTH PROFILES)'));
135
135
  await this.checkCLIProxy();
136
136
  console.log('');
137
137
  this.showReport();
@@ -144,7 +144,8 @@ class Doctor {
144
144
  const spinner = ora('Checking Claude CLI').start();
145
145
  const claudeCli = (0, claude_detector_1.detectClaudeCli)();
146
146
  if (!claudeCli) {
147
- spinner.fail(` ${'Claude CLI'.padEnd(26)}${(0, helpers_1.colored)('[X]', 'red')} Not found in PATH`);
147
+ spinner.fail();
148
+ console.log(` ${(0, ui_1.fail)('Claude CLI'.padEnd(22))} Not found in PATH`);
148
149
  this.results.addCheck('Claude CLI', 'error', 'Claude CLI not found in PATH', 'Install from: https://docs.claude.com/en/docs/claude-code/installation', { status: 'ERROR', info: 'Not installed' });
149
150
  return;
150
151
  }
@@ -169,14 +170,16 @@ class Doctor {
169
170
  // Extract version from output
170
171
  const versionMatch = result.match(/(\d+\.\d+\.\d+)/);
171
172
  const version = versionMatch ? versionMatch[1] : 'unknown';
172
- spinner.succeed(` ${'Claude CLI'.padEnd(26)}${(0, helpers_1.colored)('[OK]', 'green')} ${claudeCli} (v${version})`);
173
+ spinner.succeed();
174
+ console.log(` ${(0, ui_1.ok)('Claude CLI'.padEnd(22))} v${version} (${claudeCli})`);
173
175
  this.results.addCheck('Claude CLI', 'success', `Found: ${claudeCli}`, undefined, {
174
176
  status: 'OK',
175
177
  info: `v${version} (${claudeCli})`,
176
178
  });
177
179
  }
178
180
  catch (_err) {
179
- spinner.fail(` ${'Claude CLI'.padEnd(26)}${(0, helpers_1.colored)('[X]', 'red')} Not found or not working`);
181
+ spinner.fail();
182
+ console.log(` ${(0, ui_1.fail)('Claude CLI'.padEnd(22))} Not found or not working`);
180
183
  this.results.addCheck('Claude CLI', 'error', 'Claude CLI not found or not working', 'Install from: https://docs.claude.com/en/docs/claude-code/installation', { status: 'ERROR', info: 'Not installed' });
181
184
  }
182
185
  }
@@ -186,14 +189,16 @@ class Doctor {
186
189
  checkCcsDirectory() {
187
190
  const spinner = ora('Checking ~/.ccs/ directory').start();
188
191
  if (fs.existsSync(this.ccsDir)) {
189
- spinner.succeed(` ${'CCS Directory'.padEnd(26)}${(0, helpers_1.colored)('[OK]', 'green')} ~/.ccs/`);
192
+ spinner.succeed();
193
+ console.log(` ${(0, ui_1.ok)('CCS Directory'.padEnd(22))} ~/.ccs/`);
190
194
  this.results.addCheck('CCS Directory', 'success', undefined, undefined, {
191
195
  status: 'OK',
192
196
  info: '~/.ccs/',
193
197
  });
194
198
  }
195
199
  else {
196
- spinner.fail(` ${'CCS Directory'.padEnd(26)}${(0, helpers_1.colored)('[X]', 'red')} Not found`);
200
+ spinner.fail();
201
+ console.log(` ${(0, ui_1.fail)('CCS Directory'.padEnd(22))} Not found`);
197
202
  this.results.addCheck('CCS Directory', 'error', '~/.ccs/ directory not found', 'Run: npm install -g @kaitranntt/ccs --force', { status: 'ERROR', info: 'Not found' });
198
203
  }
199
204
  }
@@ -220,7 +225,8 @@ class Doctor {
220
225
  for (const file of files) {
221
226
  const spinner = ora(`Checking ${file.name}`).start();
222
227
  if (!fs.existsSync(file.path)) {
223
- spinner.fail(` ${file.name.padEnd(26)}${(0, helpers_1.colored)('[X]', 'red')} Not found`);
228
+ spinner.fail();
229
+ console.log(` ${(0, ui_1.fail)(file.name.padEnd(22))} Not found`);
224
230
  this.results.addCheck(file.name, 'error', `${file.name} not found`, 'Run: npm install -g @kaitranntt/ccs --force', { status: 'ERROR', info: 'Not found' });
225
231
  continue;
226
232
  }
@@ -229,38 +235,40 @@ class Doctor {
229
235
  const content = fs.readFileSync(file.path, 'utf8');
230
236
  JSON.parse(content);
231
237
  // Extract useful info based on file type
232
- let info = 'Valid';
238
+ let fileInfo = 'Valid';
233
239
  let status = 'OK';
234
240
  if (file.profile) {
235
241
  // For settings files, check if API key is configured
236
242
  const validation = DelegationValidator.validate(file.profile);
237
243
  if (validation.valid) {
238
- info = 'Key configured';
244
+ fileInfo = 'Key configured';
239
245
  status = 'OK';
240
246
  }
241
247
  else if (validation.error && validation.error.includes('placeholder')) {
242
- info = 'Placeholder key (not configured)';
248
+ fileInfo = 'Placeholder key';
243
249
  status = 'WARN';
244
250
  }
245
251
  else {
246
- info = 'Valid JSON';
252
+ fileInfo = 'Valid JSON';
247
253
  status = 'OK';
248
254
  }
249
255
  }
250
- const statusIcon = status === 'OK' ? (0, helpers_1.colored)('[OK]', 'green') : (0, helpers_1.colored)('[!]', 'yellow');
251
256
  if (status === 'WARN') {
252
- spinner.warn(` ${file.name.padEnd(26)}${statusIcon} ${info}`);
257
+ spinner.warn();
258
+ console.log(` ${(0, ui_1.warn)(file.name.padEnd(22))} ${fileInfo}`);
253
259
  }
254
260
  else {
255
- spinner.succeed(` ${file.name.padEnd(26)}${statusIcon} ${info}`);
261
+ spinner.succeed();
262
+ console.log(` ${(0, ui_1.ok)(file.name.padEnd(22))} ${fileInfo}`);
256
263
  }
257
264
  this.results.addCheck(file.name, status === 'OK' ? 'success' : 'warning', undefined, undefined, {
258
265
  status: status,
259
- info: info,
266
+ info: fileInfo,
260
267
  });
261
268
  }
262
269
  catch (e) {
263
- spinner.fail(` ${file.name.padEnd(26)}${(0, helpers_1.colored)('[X]', 'red')} Invalid JSON`);
270
+ spinner.fail();
271
+ console.log(` ${(0, ui_1.fail)(file.name.padEnd(22))} Invalid JSON`);
264
272
  this.results.addCheck(file.name, 'error', `Invalid JSON: ${e.message}`, `Backup and recreate: mv ${file.path} ${file.path}.backup && npm install -g @kaitranntt/ccs --force`, { status: 'ERROR', info: 'Invalid JSON' });
265
273
  }
266
274
  }
@@ -271,8 +279,10 @@ class Doctor {
271
279
  checkClaudeSettings() {
272
280
  const spinner = ora('Checking ~/.claude/settings.json').start();
273
281
  const settingsPath = path.join(this.claudeDir, 'settings.json');
282
+ const settingsName = '~/.claude/settings.json';
274
283
  if (!fs.existsSync(settingsPath)) {
275
- spinner.warn(` ${'~/.claude/settings.json'.padEnd(26)}${(0, helpers_1.colored)('[!]', 'yellow')} Not found`);
284
+ spinner.warn();
285
+ console.log(` ${(0, ui_1.warn)(settingsName.padEnd(22))} Not found`);
276
286
  this.results.addCheck('Claude Settings', 'warning', '~/.claude/settings.json not found', 'Run: claude /login');
277
287
  return;
278
288
  }
@@ -280,11 +290,13 @@ class Doctor {
280
290
  try {
281
291
  const content = fs.readFileSync(settingsPath, 'utf8');
282
292
  JSON.parse(content);
283
- spinner.succeed(` ${'~/.claude/settings.json'.padEnd(26)}${(0, helpers_1.colored)('[OK]', 'green')}`);
293
+ spinner.succeed();
294
+ console.log(` ${(0, ui_1.ok)(settingsName.padEnd(22))} Valid`);
284
295
  this.results.addCheck('Claude Settings', 'success');
285
296
  }
286
297
  catch (e) {
287
- spinner.warn(` ${'~/.claude/settings.json'.padEnd(26)}${(0, helpers_1.colored)('[!]', 'yellow')} Invalid JSON`);
298
+ spinner.warn();
299
+ console.log(` ${(0, ui_1.warn)(settingsName.padEnd(22))} Invalid JSON`);
288
300
  this.results.addCheck('Claude Settings', 'warning', `Invalid JSON: ${e.message}`, 'Run: claude /login');
289
301
  }
290
302
  }
@@ -295,26 +307,30 @@ class Doctor {
295
307
  const spinner = ora('Checking profiles').start();
296
308
  const configPath = path.join(this.ccsDir, 'config.json');
297
309
  if (!fs.existsSync(configPath)) {
298
- spinner.info(` ${'Profiles'.padEnd(26)}${(0, helpers_1.colored)('[SKIP]', 'cyan')} config.json not found`);
310
+ spinner.info();
311
+ console.log(` ${(0, ui_1.info)('Profiles'.padEnd(22))} config.json not found`);
299
312
  return;
300
313
  }
301
314
  try {
302
315
  const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
303
316
  if (!config.profiles || typeof config.profiles !== 'object') {
304
- spinner.fail(` ${'Profiles'.padEnd(26)}${(0, helpers_1.colored)('[X]', 'red')} Missing profiles object`);
317
+ spinner.fail();
318
+ console.log(` ${(0, ui_1.fail)('Profiles'.padEnd(22))} Missing profiles object`);
305
319
  this.results.addCheck('Profiles', 'error', 'config.json missing profiles object', 'Run: npm install -g @kaitranntt/ccs --force', { status: 'ERROR', info: 'Missing profiles object' });
306
320
  return;
307
321
  }
308
322
  const profileCount = Object.keys(config.profiles).length;
309
323
  const profileNames = Object.keys(config.profiles).join(', ');
310
- spinner.succeed(` ${'Profiles'.padEnd(26)}${(0, helpers_1.colored)('[OK]', 'green')} ${profileCount} configured (${profileNames})`);
324
+ spinner.succeed();
325
+ console.log(` ${(0, ui_1.ok)('Profiles'.padEnd(22))} ${profileCount} configured (${profileNames})`);
311
326
  this.results.addCheck('Profiles', 'success', `${profileCount} profiles configured`, undefined, {
312
327
  status: 'OK',
313
328
  info: `${profileCount} configured (${profileNames.length > 30 ? profileNames.substring(0, 27) + '...' : profileNames})`,
314
329
  });
315
330
  }
316
331
  catch (e) {
317
- spinner.fail(` ${'Profiles'.padEnd(26)}${(0, helpers_1.colored)('[X]', 'red')} ${e.message}`);
332
+ spinner.fail();
333
+ console.log(` ${(0, ui_1.fail)('Profiles'.padEnd(22))} ${e.message}`);
318
334
  this.results.addCheck('Profiles', 'error', e.message, undefined, {
319
335
  status: 'ERROR',
320
336
  info: e.message,
@@ -328,7 +344,8 @@ class Doctor {
328
344
  const spinner = ora('Checking instances').start();
329
345
  const instancesDir = path.join(this.ccsDir, 'instances');
330
346
  if (!fs.existsSync(instancesDir)) {
331
- spinner.info(` ${'Instances'.padEnd(26)}${(0, helpers_1.colored)('[i]', 'cyan')} No account profiles`);
347
+ spinner.info();
348
+ console.log(` ${(0, ui_1.info)('Instances'.padEnd(22))} No account profiles`);
332
349
  this.results.addCheck('Instances', 'success', 'No account profiles configured');
333
350
  return;
334
351
  }
@@ -336,11 +353,13 @@ class Doctor {
336
353
  return fs.statSync(path.join(instancesDir, name)).isDirectory();
337
354
  });
338
355
  if (instances.length === 0) {
339
- spinner.info(` ${'Instances'.padEnd(26)}${(0, helpers_1.colored)('[i]', 'cyan')} No account profiles`);
356
+ spinner.info();
357
+ console.log(` ${(0, ui_1.info)('Instances'.padEnd(22))} No account profiles`);
340
358
  this.results.addCheck('Instances', 'success', 'No account profiles');
341
359
  return;
342
360
  }
343
- spinner.succeed(` ${'Instances'.padEnd(26)}${(0, helpers_1.colored)('[OK]', 'green')} ${instances.length} account profiles`);
361
+ spinner.succeed();
362
+ console.log(` ${(0, ui_1.ok)('Instances'.padEnd(22))} ${instances.length} account profiles`);
344
363
  this.results.addCheck('Instances', 'success', `${instances.length} account profiles`);
345
364
  }
346
365
  /**
@@ -353,7 +372,8 @@ class Doctor {
353
372
  const hasCcsCommand = fs.existsSync(path.join(ccsClaudeCommandsDir, 'ccs.md'));
354
373
  const hasContinueCommand = fs.existsSync(path.join(ccsClaudeCommandsDir, 'ccs', 'continue.md'));
355
374
  if (!hasCcsCommand || !hasContinueCommand) {
356
- spinner.warn(` ${'Delegation'.padEnd(26)}${(0, helpers_1.colored)('[!]', 'yellow')} Not installed`);
375
+ spinner.warn();
376
+ console.log(` ${(0, ui_1.warn)('Delegation'.padEnd(22))} Not installed`);
357
377
  this.results.addCheck('Delegation', 'warning', 'Delegation commands not found', 'Install with: npm install -g @kaitranntt/ccs --force', { status: 'WARN', info: 'Not installed' });
358
378
  return;
359
379
  }
@@ -367,11 +387,13 @@ class Doctor {
367
387
  }
368
388
  }
369
389
  if (readyProfiles.length === 0) {
370
- spinner.warn(` ${'Delegation'.padEnd(26)}${(0, helpers_1.colored)('[!]', 'yellow')} No profiles ready`);
390
+ spinner.warn();
391
+ console.log(` ${(0, ui_1.warn)('Delegation'.padEnd(22))} No profiles ready`);
371
392
  this.results.addCheck('Delegation', 'warning', 'Delegation installed but no profiles configured', 'Configure profiles with valid API keys (not placeholders)', { status: 'WARN', info: 'No profiles ready' });
372
393
  return;
373
394
  }
374
- spinner.succeed(` ${'Delegation'.padEnd(26)}${(0, helpers_1.colored)('[OK]', 'green')} ${readyProfiles.length} profiles ready (${readyProfiles.join(', ')})`);
395
+ spinner.succeed();
396
+ console.log(` ${(0, ui_1.ok)('Delegation'.padEnd(22))} ${readyProfiles.length} profiles ready (${readyProfiles.join(', ')})`);
375
397
  this.results.addCheck('Delegation', 'success', `${readyProfiles.length} profile(s) ready: ${readyProfiles.join(', ')}`, undefined, { status: 'OK', info: `${readyProfiles.length} profiles ready` });
376
398
  }
377
399
  /**
@@ -383,14 +405,16 @@ class Doctor {
383
405
  try {
384
406
  fs.writeFileSync(testFile, 'test', 'utf8');
385
407
  fs.unlinkSync(testFile);
386
- spinner.succeed(` ${'Permissions'.padEnd(26)}${(0, helpers_1.colored)('[OK]', 'green')} Write access verified`);
408
+ spinner.succeed();
409
+ console.log(` ${(0, ui_1.ok)('Permissions'.padEnd(22))} Write access verified`);
387
410
  this.results.addCheck('Permissions', 'success', undefined, undefined, {
388
411
  status: 'OK',
389
412
  info: 'Write access verified',
390
413
  });
391
414
  }
392
415
  catch (_e) {
393
- spinner.fail(` ${'Permissions'.padEnd(26)}${(0, helpers_1.colored)('[X]', 'red')} Cannot write to ~/.ccs/`);
416
+ spinner.fail();
417
+ console.log(` ${(0, ui_1.fail)('Permissions'.padEnd(22))} Cannot write to ~/.ccs/`);
394
418
  this.results.addCheck('Permissions', 'error', 'Cannot write to ~/.ccs/', 'Fix: sudo chown -R $USER ~/.ccs ~/.claude && chmod 755 ~/.ccs ~/.claude', { status: 'ERROR', info: 'Cannot write to ~/.ccs/' });
395
419
  }
396
420
  }
@@ -405,19 +429,22 @@ class Doctor {
405
429
  const health = manager.checkHealth();
406
430
  if (health.healthy) {
407
431
  const itemCount = manager.ccsItems.length;
408
- spinner.succeed(` ${'CCS Symlinks'.padEnd(26)}${(0, helpers_1.colored)('[OK]', 'green')} ${itemCount}/${itemCount} items linked`);
432
+ spinner.succeed();
433
+ console.log(` ${(0, ui_1.ok)('CCS Symlinks'.padEnd(22))} ${itemCount}/${itemCount} items linked`);
409
434
  this.results.addCheck('CCS Symlinks', 'success', 'All CCS items properly symlinked', undefined, {
410
435
  status: 'OK',
411
436
  info: `${itemCount}/${itemCount} items synced`,
412
437
  });
413
438
  }
414
439
  else {
415
- spinner.warn(` ${'CCS Symlinks'.padEnd(26)}${(0, helpers_1.colored)('[!]', 'yellow')} ${health.issues.length} issues found`);
440
+ spinner.warn();
441
+ console.log(` ${(0, ui_1.warn)('CCS Symlinks'.padEnd(22))} ${health.issues.length} issues found`);
416
442
  this.results.addCheck('CCS Symlinks', 'warning', health.issues.join(', '), 'Run: ccs sync', { status: 'WARN', info: `${health.issues.length} issues` });
417
443
  }
418
444
  }
419
445
  catch (e) {
420
- spinner.warn(` ${'CCS Symlinks'.padEnd(26)}${(0, helpers_1.colored)('[!]', 'yellow')} Could not check`);
446
+ spinner.warn();
447
+ console.log(` ${(0, ui_1.warn)('CCS Symlinks'.padEnd(22))} Could not check`);
421
448
  this.results.addCheck('CCS Symlinks', 'warning', 'Could not check CCS symlinks: ' + e.message, 'Run: ccs sync', { status: 'WARN', info: 'Could not check' });
422
449
  }
423
450
  }
@@ -426,33 +453,38 @@ class Doctor {
426
453
  */
427
454
  checkSettingsSymlinks() {
428
455
  const spinner = ora('Checking settings.json symlinks').start();
456
+ const settingsLabel = 'settings.json';
429
457
  try {
430
458
  const sharedDir = path.join(this.homedir, '.ccs', 'shared');
431
459
  const sharedSettings = path.join(sharedDir, 'settings.json');
432
460
  const claudeSettings = path.join(this.claudeDir, 'settings.json');
433
461
  // Check shared settings exists and points to ~/.claude/
434
462
  if (!fs.existsSync(sharedSettings)) {
435
- spinner.warn(` ${'settings.json (shared)'.padEnd(26)}${(0, helpers_1.colored)('[!]', 'yellow')} Not found`);
463
+ spinner.warn();
464
+ console.log(` ${(0, ui_1.warn)(settingsLabel.padEnd(22))} Not found (shared)`);
436
465
  this.results.addCheck('Settings Symlinks', 'warning', 'Shared settings.json not found', 'Run: ccs sync');
437
466
  return;
438
467
  }
439
468
  const sharedStats = fs.lstatSync(sharedSettings);
440
469
  if (!sharedStats.isSymbolicLink()) {
441
- spinner.warn(` ${'settings.json (shared)'.padEnd(26)}${(0, helpers_1.colored)('[!]', 'yellow')} Not a symlink`);
470
+ spinner.warn();
471
+ console.log(` ${(0, ui_1.warn)(settingsLabel.padEnd(22))} Not a symlink (shared)`);
442
472
  this.results.addCheck('Settings Symlinks', 'warning', 'Shared settings.json is not a symlink', 'Run: ccs sync');
443
473
  return;
444
474
  }
445
475
  const sharedTarget = fs.readlinkSync(sharedSettings);
446
476
  const resolvedShared = path.resolve(path.dirname(sharedSettings), sharedTarget);
447
477
  if (resolvedShared !== claudeSettings) {
448
- spinner.warn(` ${'settings.json (shared)'.padEnd(26)}${(0, helpers_1.colored)('[!]', 'yellow')} Wrong target`);
478
+ spinner.warn();
479
+ console.log(` ${(0, ui_1.warn)(settingsLabel.padEnd(22))} Wrong target (shared)`);
449
480
  this.results.addCheck('Settings Symlinks', 'warning', `Points to ${resolvedShared} instead of ${claudeSettings}`, 'Run: ccs sync');
450
481
  return;
451
482
  }
452
483
  // Check each instance
453
484
  const instancesDir = path.join(this.ccsDir, 'instances');
454
485
  if (!fs.existsSync(instancesDir)) {
455
- spinner.succeed(` ${'settings.json'.padEnd(26)}${(0, helpers_1.colored)('[OK]', 'green')} Shared symlink valid`);
486
+ spinner.succeed();
487
+ console.log(` ${(0, ui_1.ok)(settingsLabel.padEnd(22))} Shared symlink valid`);
456
488
  this.results.addCheck('Settings Symlinks', 'success', 'Shared symlink valid', undefined, {
457
489
  status: 'OK',
458
490
  info: 'Shared symlink valid',
@@ -487,11 +519,13 @@ class Doctor {
487
519
  }
488
520
  }
489
521
  if (broken > 0) {
490
- spinner.warn(` ${'settings.json'.padEnd(26)}${(0, helpers_1.colored)('[!]', 'yellow')} ${broken} broken instance(s)`);
522
+ spinner.warn();
523
+ console.log(` ${(0, ui_1.warn)(settingsLabel.padEnd(22))} ${broken} broken instance(s)`);
491
524
  this.results.addCheck('Settings Symlinks', 'warning', `${broken} instance(s) have broken symlinks`, 'Run: ccs sync', { status: 'WARN', info: `${broken} broken instance(s)` });
492
525
  }
493
526
  else {
494
- spinner.succeed(` ${'settings.json'.padEnd(26)}${(0, helpers_1.colored)('[OK]', 'green')} ${instances.length} instance(s) valid`);
527
+ spinner.succeed();
528
+ console.log(` ${(0, ui_1.ok)(settingsLabel.padEnd(22))} ${instances.length} instance(s) valid`);
495
529
  this.results.addCheck('Settings Symlinks', 'success', 'All instance symlinks valid', undefined, {
496
530
  status: 'OK',
497
531
  info: `${instances.length} instance(s) valid`,
@@ -499,7 +533,8 @@ class Doctor {
499
533
  }
500
534
  }
501
535
  catch (err) {
502
- spinner.warn(` ${'settings.json'.padEnd(26)}${(0, helpers_1.colored)('[!]', 'yellow')} Check failed`);
536
+ spinner.warn();
537
+ console.log(` ${(0, ui_1.warn)(settingsLabel.padEnd(22))} Check failed`);
503
538
  this.results.addCheck('Settings Symlinks', 'warning', `Failed to check: ${err.message}`, 'Run: ccs sync', { status: 'WARN', info: 'Check failed' });
504
539
  }
505
540
  }
@@ -511,28 +546,32 @@ class Doctor {
511
546
  const binarySpinner = ora('Checking CLIProxy binary').start();
512
547
  if ((0, cliproxy_1.isCLIProxyInstalled)()) {
513
548
  const binaryPath = (0, cliproxy_1.getCLIProxyPath)();
514
- binarySpinner.succeed(` ${'CLIProxy Binary'.padEnd(26)}${(0, helpers_1.colored)('[OK]', 'green')} v${cliproxy_1.CLIPROXY_VERSION}`);
549
+ binarySpinner.succeed();
550
+ console.log(` ${(0, ui_1.ok)('CLIProxy Binary'.padEnd(22))} v${cliproxy_1.CLIPROXY_VERSION}`);
515
551
  this.results.addCheck('CLIProxy Binary', 'success', undefined, undefined, {
516
552
  status: 'OK',
517
553
  info: `v${cliproxy_1.CLIPROXY_VERSION} (${binaryPath})`,
518
554
  });
519
555
  }
520
556
  else {
521
- binarySpinner.info(` ${'CLIProxy Binary'.padEnd(26)}${(0, helpers_1.colored)('[i]', 'cyan')} Not installed (downloads on first use)`);
557
+ binarySpinner.info();
558
+ console.log(` ${(0, ui_1.info)('CLIProxy Binary'.padEnd(22))} Not installed (downloads on first use)`);
522
559
  this.results.addCheck('CLIProxy Binary', 'success', 'Not installed yet', 'Run: ccs gemini "test" (will download automatically)', { status: 'OK', info: 'Not installed (downloads on first use)' });
523
560
  }
524
561
  // 2. Config file exists?
525
562
  const configSpinner = ora('Checking CLIProxy config').start();
526
563
  const configPath = (0, cliproxy_1.getConfigPath)();
527
564
  if (fs.existsSync(configPath)) {
528
- configSpinner.succeed(` ${'CLIProxy Config'.padEnd(26)}${(0, helpers_1.colored)('[OK]', 'green')} cliproxy/config.yaml`);
565
+ configSpinner.succeed();
566
+ console.log(` ${(0, ui_1.ok)('CLIProxy Config'.padEnd(22))} cliproxy/config.yaml`);
529
567
  this.results.addCheck('CLIProxy Config', 'success', undefined, undefined, {
530
568
  status: 'OK',
531
569
  info: 'cliproxy/config.yaml',
532
570
  });
533
571
  }
534
572
  else {
535
- configSpinner.info(` ${'CLIProxy Config'.padEnd(26)}${(0, helpers_1.colored)('[i]', 'cyan')} Not created (generated on first use)`);
573
+ configSpinner.info();
574
+ console.log(` ${(0, ui_1.info)('CLIProxy Config'.padEnd(22))} Not created (on first use)`);
536
575
  this.results.addCheck('CLIProxy Config', 'success', 'Not created yet', undefined, {
537
576
  status: 'OK',
538
577
  info: 'Generated on first use',
@@ -545,14 +584,16 @@ class Doctor {
545
584
  const providerName = status.provider.charAt(0).toUpperCase() + status.provider.slice(1);
546
585
  if (status.authenticated) {
547
586
  const lastAuth = status.lastAuth ? ` (${status.lastAuth.toLocaleDateString()})` : '';
548
- authSpinner.succeed(` ${`${providerName} Auth`.padEnd(26)}${(0, helpers_1.colored)('[OK]', 'green')} Authenticated${lastAuth}`);
587
+ authSpinner.succeed();
588
+ console.log(` ${(0, ui_1.ok)(`${providerName} Auth`.padEnd(22))} Authenticated${lastAuth}`);
549
589
  this.results.addCheck(`${providerName} Auth`, 'success', undefined, undefined, {
550
590
  status: 'OK',
551
591
  info: `Authenticated${lastAuth}`,
552
592
  });
553
593
  }
554
594
  else {
555
- authSpinner.info(` ${`${providerName} Auth`.padEnd(26)}${(0, helpers_1.colored)('[i]', 'cyan')} Not authenticated`);
595
+ authSpinner.info();
596
+ console.log(` ${(0, ui_1.info)(`${providerName} Auth`.padEnd(22))} Not authenticated`);
556
597
  this.results.addCheck(`${providerName} Auth`, 'success', 'Not authenticated', `Run: ccs ${status.provider} --auth`, { status: 'OK', info: 'Not authenticated (run ccs <profile> to login)' });
557
598
  }
558
599
  }
@@ -560,14 +601,16 @@ class Doctor {
560
601
  const portSpinner = ora(`Checking port ${cliproxy_1.CLIPROXY_DEFAULT_PORT}`).start();
561
602
  const portAvailable = await (0, cliproxy_1.isPortAvailable)(cliproxy_1.CLIPROXY_DEFAULT_PORT);
562
603
  if (portAvailable) {
563
- portSpinner.succeed(` ${'CLIProxy Port'.padEnd(26)}${(0, helpers_1.colored)('[OK]', 'green')} ${cliproxy_1.CLIPROXY_DEFAULT_PORT} available`);
604
+ portSpinner.succeed();
605
+ console.log(` ${(0, ui_1.ok)('CLIProxy Port'.padEnd(22))} ${cliproxy_1.CLIPROXY_DEFAULT_PORT} available`);
564
606
  this.results.addCheck('CLIProxy Port', 'success', undefined, undefined, {
565
607
  status: 'OK',
566
608
  info: `Port ${cliproxy_1.CLIPROXY_DEFAULT_PORT} available`,
567
609
  });
568
610
  }
569
611
  else {
570
- portSpinner.warn(` ${'CLIProxy Port'.padEnd(26)}${(0, helpers_1.colored)('[!]', 'yellow')} ${cliproxy_1.CLIPROXY_DEFAULT_PORT} in use`);
612
+ portSpinner.warn();
613
+ console.log(` ${(0, ui_1.warn)('CLIProxy Port'.padEnd(22))} ${cliproxy_1.CLIPROXY_DEFAULT_PORT} in use`);
571
614
  this.results.addCheck('CLIProxy Port', 'warning', `Port ${cliproxy_1.CLIPROXY_DEFAULT_PORT} is in use`, `Check: lsof -i :${cliproxy_1.CLIPROXY_DEFAULT_PORT}`, { status: 'WARN', info: `Port ${cliproxy_1.CLIPROXY_DEFAULT_PORT} in use` });
572
615
  }
573
616
  }
@@ -576,74 +619,53 @@ class Doctor {
576
619
  */
577
620
  showReport() {
578
621
  console.log('');
579
- // Calculate exact table width to match header bars
580
- // colWidths: [20, 10, 35] = 65 + borders (4) = 69 total
581
- const tableWidth = 69;
582
- const headerBar = '═'.repeat(tableWidth);
583
- console.log((0, helpers_1.colored)(headerBar, 'cyan'));
584
- console.log((0, helpers_1.colored)(' Health Check Summary', 'bold'));
585
- console.log((0, helpers_1.colored)(headerBar, 'cyan'));
586
- // Create summary table with detailed information
587
- const table = new Table({
588
- head: [(0, helpers_1.colored)('Component', 'cyan'), (0, helpers_1.colored)('Status', 'cyan'), (0, helpers_1.colored)('Details', 'cyan')],
589
- colWidths: [20, 10, 35],
590
- wordWrap: true,
591
- chars: {
592
- top: '═',
593
- 'top-mid': '╤',
594
- 'top-left': '╔',
595
- 'top-right': '╗',
596
- bottom: '═',
597
- 'bottom-mid': '╧',
598
- 'bottom-left': '╚',
599
- 'bottom-right': '╝',
600
- left: '║',
601
- 'left-mid': '╟',
602
- mid: '─',
603
- 'mid-mid': '┼',
604
- right: '║',
605
- 'right-mid': '╢',
606
- middle: '│',
607
- },
622
+ console.log((0, ui_1.header)('HEALTH CHECK SUMMARY'));
623
+ console.log('');
624
+ // Build summary table rows
625
+ const rows = Object.entries(this.results.details).map(([component, detail]) => {
626
+ const statusIndicator = detail.status === 'OK'
627
+ ? (0, ui_1.color)('[OK]', 'success')
628
+ : detail.status === 'ERROR'
629
+ ? (0, ui_1.color)('[X]', 'error')
630
+ : (0, ui_1.color)('[!]', 'warning');
631
+ return [component, statusIndicator, detail.info || ''];
608
632
  });
609
- // Populate table with collected details
610
- for (const [component, detail] of Object.entries(this.results.details)) {
611
- const statusColor = detail.status === 'OK' ? 'green' : detail.status === 'ERROR' ? 'red' : 'yellow';
612
- table.push([component, (0, helpers_1.colored)(detail.status, statusColor), detail.info || '']);
613
- }
614
- console.log(table.toString());
633
+ console.log((0, ui_1.table)(rows, {
634
+ head: ['Component', 'Status', 'Details'],
635
+ colWidths: [20, 12, 35],
636
+ }));
615
637
  console.log('');
616
- // Show errors and warnings if present
638
+ // Show errors if present
617
639
  if (this.results.hasErrors()) {
618
- console.log((0, helpers_1.colored)('Errors:', 'red'));
640
+ console.log((0, ui_1.header)('ERRORS'));
619
641
  this.results.errors.forEach((err) => {
620
- console.log(` [X] ${err.name}: ${err.message}`);
642
+ console.log(` ${(0, ui_1.fail)(err.name)}: ${err.message}`);
621
643
  if (err.fix) {
622
- console.log(` Fix: ${err.fix}`);
644
+ console.log(` Fix: ${(0, ui_1.color)(err.fix, 'command')}`);
623
645
  }
624
646
  });
625
647
  console.log('');
626
648
  }
649
+ // Show warnings if present
627
650
  if (this.results.hasWarnings()) {
628
- console.log((0, helpers_1.colored)('Warnings:', 'yellow'));
629
- this.results.warnings.forEach((warn) => {
630
- console.log(` [!] ${warn.name}: ${warn.message}`);
631
- if (warn.fix) {
632
- console.log(` Fix: ${warn.fix}`);
651
+ console.log((0, ui_1.header)('WARNINGS'));
652
+ this.results.warnings.forEach((w) => {
653
+ console.log(` ${(0, ui_1.warn)(w.name)}: ${w.message}`);
654
+ if (w.fix) {
655
+ console.log(` Fix: ${(0, ui_1.color)(w.fix, 'command')}`);
633
656
  }
634
657
  });
635
658
  console.log('');
636
659
  }
637
660
  // Final status
638
661
  if (this.results.isHealthy() && !this.results.hasWarnings()) {
639
- console.log((0, helpers_1.colored)('[OK] All checks passed! Your CCS installation is healthy.', 'green'));
662
+ console.log((0, ui_1.ok)('All checks passed! Installation is healthy.'));
640
663
  }
641
664
  else if (this.results.hasErrors()) {
642
- console.log((0, helpers_1.colored)('[X] Status: Installation has errors', 'red'));
643
- console.log('Run suggested fixes above to resolve issues.');
665
+ console.log((0, ui_1.fail)('Installation has errors. Run suggested fixes above.'));
644
666
  }
645
667
  else {
646
- console.log((0, helpers_1.colored)('[OK] Status: Installation healthy (warnings only)', 'green'));
668
+ console.log((0, ui_1.ok)(`Installation healthy (${this.results.warnings.length} warning${this.results.warnings.length !== 1 ? 's' : ''})`));
647
669
  }
648
670
  console.log('');
649
671
  }