@kaitranntt/ccs 5.11.0 → 5.12.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.
Files changed (46) hide show
  1. package/README.md +2 -1
  2. package/VERSION +1 -1
  3. package/dist/cliproxy/model-catalog.d.ts.map +1 -1
  4. package/dist/cliproxy/model-catalog.js +11 -15
  5. package/dist/cliproxy/model-catalog.js.map +1 -1
  6. package/dist/commands/update-command.d.ts.map +1 -1
  7. package/dist/commands/update-command.js +7 -8
  8. package/dist/commands/update-command.js.map +1 -1
  9. package/dist/commands/version-command.d.ts.map +1 -1
  10. package/dist/commands/version-command.js +2 -3
  11. package/dist/commands/version-command.js.map +1 -1
  12. package/dist/ui/assets/{form-utils-Bjfa1DVH.js → form-utils-qlCULW73.js} +1 -1
  13. package/dist/ui/assets/icons-DOhZYYMX.js +1 -0
  14. package/dist/ui/assets/index-C5_mBoh5.js +72 -0
  15. package/dist/ui/assets/index-FXq1wR_S.css +1 -0
  16. package/dist/ui/assets/{radix-ui-Ba6LUgyw.js → radix-ui-CBdrr74O.js} +1 -1
  17. package/dist/ui/assets/react-vendor-B3ahKmcC.js +3 -0
  18. package/dist/ui/assets/{tanstack-MD0629v8.js → tanstack-D9KW3ciX.js} +1 -1
  19. package/dist/ui/index.html +15 -7
  20. package/dist/utils/version.d.ts +8 -0
  21. package/dist/utils/version.d.ts.map +1 -0
  22. package/dist/utils/version.js +51 -0
  23. package/dist/utils/version.js.map +1 -0
  24. package/dist/web-server/health-service.d.ts +14 -3
  25. package/dist/web-server/health-service.d.ts.map +1 -1
  26. package/dist/web-server/health-service.js +545 -66
  27. package/dist/web-server/health-service.js.map +1 -1
  28. package/dist/web-server/index.d.ts.map +1 -1
  29. package/dist/web-server/index.js +9 -0
  30. package/dist/web-server/index.js.map +1 -1
  31. package/dist/web-server/overview-routes.d.ts.map +1 -1
  32. package/dist/web-server/overview-routes.js +18 -4
  33. package/dist/web-server/overview-routes.js.map +1 -1
  34. package/dist/web-server/routes.js +11 -4
  35. package/dist/web-server/routes.js.map +1 -1
  36. package/dist/web-server/shared-routes.js +3 -2
  37. package/dist/web-server/shared-routes.js.map +1 -1
  38. package/dist/web-server/usage-routes.d.ts +27 -0
  39. package/dist/web-server/usage-routes.d.ts.map +1 -0
  40. package/dist/web-server/usage-routes.js +459 -0
  41. package/dist/web-server/usage-routes.js.map +1 -0
  42. package/package.json +2 -1
  43. package/dist/ui/assets/icons-D-Y22K9Z.js +0 -1
  44. package/dist/ui/assets/index-M5ru6OPu.js +0 -9
  45. package/dist/ui/assets/index-ZXu5MvSE.css +0 -1
  46. package/dist/ui/assets/react-vendor-CW-MU7-e.js +0 -3
@@ -2,7 +2,8 @@
2
2
  /**
3
3
  * Health Check Service (Phase 06)
4
4
  *
5
- * Runs health checks for CCS dashboard: Claude CLI, config files, CLIProxy binary.
5
+ * Runs comprehensive health checks for CCS dashboard matching `ccs doctor` output.
6
+ * Groups: System, Configuration, Profiles & Delegation, System Health, CLIProxy
6
7
  */
7
8
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
8
9
  if (k2 === undefined) k2 = k;
@@ -27,54 +28,124 @@ var __importStar = (this && this.__importStar) || function (mod) {
27
28
  __setModuleDefault(result, mod);
28
29
  return result;
29
30
  };
31
+ var __importDefault = (this && this.__importDefault) || function (mod) {
32
+ return (mod && mod.__esModule) ? mod : { "default": mod };
33
+ };
30
34
  Object.defineProperty(exports, "__esModule", { value: true });
31
35
  exports.fixHealthIssue = exports.runHealthChecks = void 0;
32
36
  const fs = __importStar(require("fs"));
33
37
  const path = __importStar(require("path"));
38
+ const os = __importStar(require("os"));
34
39
  const child_process_1 = require("child_process");
35
40
  const config_manager_1 = require("../utils/config-manager");
36
41
  const cliproxy_1 = require("../cliproxy");
42
+ const claude_detector_1 = require("../utils/claude-detector");
43
+ const port_utils_1 = require("../utils/port-utils");
44
+ const package_json_1 = __importDefault(require("../../package.json"));
37
45
  /**
38
46
  * Run all health checks and return report
39
47
  */
40
- function runHealthChecks() {
41
- const checks = [];
42
- // Check 1: Claude CLI
43
- checks.push(checkClaudeCli());
44
- // Check 2: Config file
45
- checks.push(checkConfigFile());
46
- // Check 3: Profiles file
47
- checks.push(checkProfilesFile());
48
- // Check 4: CLIProxy binary
49
- checks.push(checkCliproxy());
50
- // Check 5: CCS directory
51
- checks.push(checkCcsDirectory());
48
+ async function runHealthChecks() {
49
+ const homedir = os.homedir();
50
+ const ccsDir = (0, config_manager_1.getCcsDir)();
51
+ const claudeDir = path.join(homedir, '.claude');
52
+ const version = package_json_1.default.version;
53
+ const groups = [];
54
+ // Group 1: System
55
+ const systemChecks = [];
56
+ systemChecks.push(await checkClaudeCli());
57
+ systemChecks.push(checkCcsDirectory(ccsDir));
58
+ groups.push({ id: 'system', name: 'System', icon: 'Monitor', checks: systemChecks });
59
+ // Group 2: Configuration
60
+ const configChecks = [];
61
+ configChecks.push(checkConfigFile());
62
+ configChecks.push(...checkSettingsFiles(ccsDir));
63
+ configChecks.push(checkClaudeSettings(claudeDir));
64
+ groups.push({
65
+ id: 'configuration',
66
+ name: 'Configuration',
67
+ icon: 'Settings',
68
+ checks: configChecks,
69
+ });
70
+ // Group 3: Profiles & Delegation
71
+ const profileChecks = [];
72
+ profileChecks.push(checkProfiles(ccsDir));
73
+ profileChecks.push(checkInstances(ccsDir));
74
+ profileChecks.push(checkDelegation(ccsDir));
75
+ groups.push({
76
+ id: 'profiles',
77
+ name: 'Profiles & Delegation',
78
+ icon: 'Users',
79
+ checks: profileChecks,
80
+ });
81
+ // Group 4: System Health
82
+ const healthChecks = [];
83
+ healthChecks.push(checkPermissions(ccsDir));
84
+ healthChecks.push(checkCcsSymlinks());
85
+ healthChecks.push(checkSettingsSymlinks(homedir, ccsDir, claudeDir));
86
+ groups.push({
87
+ id: 'system-health',
88
+ name: 'System Health',
89
+ icon: 'Shield',
90
+ checks: healthChecks,
91
+ });
92
+ // Group 5: CLIProxy
93
+ const cliproxyChecks = [];
94
+ cliproxyChecks.push(checkCliproxyBinary());
95
+ cliproxyChecks.push(checkCliproxyConfig());
96
+ cliproxyChecks.push(...checkOAuthProviders());
97
+ cliproxyChecks.push(await checkCliproxyPort());
98
+ groups.push({
99
+ id: 'cliproxy',
100
+ name: 'CLIProxy (OAuth)',
101
+ icon: 'Zap',
102
+ checks: cliproxyChecks,
103
+ });
104
+ // Flatten all checks for backward compatibility
105
+ const allChecks = groups.flatMap((g) => g.checks);
52
106
  // Calculate summary
53
107
  const summary = {
54
- total: checks.length,
55
- passed: checks.filter((c) => c.status === 'ok').length,
56
- warnings: checks.filter((c) => c.status === 'warning').length,
57
- errors: checks.filter((c) => c.status === 'error').length,
108
+ total: allChecks.length,
109
+ passed: allChecks.filter((c) => c.status === 'ok').length,
110
+ warnings: allChecks.filter((c) => c.status === 'warning').length,
111
+ errors: allChecks.filter((c) => c.status === 'error').length,
112
+ info: allChecks.filter((c) => c.status === 'info').length,
58
113
  };
59
114
  return {
60
115
  timestamp: Date.now(),
61
- checks,
116
+ version,
117
+ groups,
118
+ checks: allChecks,
62
119
  summary,
63
120
  };
64
121
  }
65
122
  exports.runHealthChecks = runHealthChecks;
66
- function checkClaudeCli() {
123
+ // Check 1: Claude CLI
124
+ async function checkClaudeCli() {
125
+ const cliInfo = (0, claude_detector_1.getClaudeCliInfo)();
126
+ if (!cliInfo) {
127
+ return {
128
+ id: 'claude-cli',
129
+ name: 'Claude CLI',
130
+ status: 'error',
131
+ message: 'Not found in PATH',
132
+ fix: 'Install: npm install -g @anthropic-ai/claude-code',
133
+ };
134
+ }
67
135
  try {
68
136
  const version = (0, child_process_1.execSync)('claude --version', {
69
137
  encoding: 'utf8',
70
138
  timeout: 5000,
71
139
  stdio: ['pipe', 'pipe', 'pipe'],
72
140
  }).trim();
141
+ const versionMatch = version.match(/(\d+\.\d+\.\d+)/);
142
+ const versionStr = versionMatch ? versionMatch[1] : 'unknown';
73
143
  return {
74
144
  id: 'claude-cli',
75
145
  name: 'Claude CLI',
76
146
  status: 'ok',
77
- message: `Installed: ${version}`,
147
+ message: `v${versionStr}`,
148
+ details: cliInfo.path,
78
149
  };
79
150
  }
80
151
  catch {
@@ -82,17 +153,40 @@ function checkClaudeCli() {
82
153
  id: 'claude-cli',
83
154
  name: 'Claude CLI',
84
155
  status: 'error',
85
- message: 'Not found in PATH',
86
- details: 'Install: npm install -g @anthropic-ai/claude-code',
156
+ message: 'Not working',
157
+ details: cliInfo.path,
158
+ fix: 'Reinstall Claude CLI',
159
+ };
160
+ }
161
+ }
162
+ // Check 2: CCS Directory
163
+ function checkCcsDirectory(ccsDir) {
164
+ if (fs.existsSync(ccsDir)) {
165
+ return {
166
+ id: 'ccs-dir',
167
+ name: 'CCS Directory',
168
+ status: 'ok',
169
+ message: 'Exists',
170
+ details: '~/.ccs/',
87
171
  };
88
172
  }
173
+ return {
174
+ id: 'ccs-dir',
175
+ name: 'CCS Directory',
176
+ status: 'error',
177
+ message: 'Not found',
178
+ details: ccsDir,
179
+ fix: 'Run: npm install -g @kaitranntt/ccs --force',
180
+ fixable: true,
181
+ };
89
182
  }
183
+ // Check 3: Config file
90
184
  function checkConfigFile() {
91
185
  const configPath = (0, config_manager_1.getConfigPath)();
92
186
  if (!fs.existsSync(configPath)) {
93
187
  return {
94
188
  id: 'config-file',
95
- name: 'Config File',
189
+ name: 'config.json',
96
190
  status: 'warning',
97
191
  message: 'Not found',
98
192
  details: configPath,
@@ -104,94 +198,479 @@ function checkConfigFile() {
104
198
  JSON.parse(content);
105
199
  return {
106
200
  id: 'config-file',
107
- name: 'Config File',
201
+ name: 'config.json',
108
202
  status: 'ok',
109
- message: 'Valid JSON',
203
+ message: 'Valid',
110
204
  details: configPath,
111
205
  };
112
206
  }
113
207
  catch {
114
208
  return {
115
209
  id: 'config-file',
116
- name: 'Config File',
210
+ name: 'config.json',
117
211
  status: 'error',
118
212
  message: 'Invalid JSON',
119
213
  details: configPath,
120
214
  };
121
215
  }
122
216
  }
123
- function checkProfilesFile() {
124
- const ccsDir = (0, config_manager_1.getCcsDir)();
125
- const profilesPath = path.join(ccsDir, 'profiles.json');
126
- if (!fs.existsSync(profilesPath)) {
217
+ // Check 4: Settings files (glm, kimi)
218
+ function checkSettingsFiles(ccsDir) {
219
+ const checks = [];
220
+ const files = [
221
+ { name: 'glm.settings.json', profile: 'glm' },
222
+ { name: 'kimi.settings.json', profile: 'kimi' },
223
+ ];
224
+ const { DelegationValidator } = require('../utils/delegation-validator');
225
+ for (const file of files) {
226
+ const filePath = path.join(ccsDir, file.name);
227
+ if (!fs.existsSync(filePath)) {
228
+ checks.push({
229
+ id: `settings-${file.profile}`,
230
+ name: file.name,
231
+ status: 'info',
232
+ message: 'Not configured',
233
+ details: filePath,
234
+ });
235
+ continue;
236
+ }
237
+ try {
238
+ const content = fs.readFileSync(filePath, 'utf8');
239
+ JSON.parse(content);
240
+ const validation = DelegationValidator.validate(file.profile);
241
+ if (validation.valid) {
242
+ checks.push({
243
+ id: `settings-${file.profile}`,
244
+ name: file.name,
245
+ status: 'ok',
246
+ message: 'Key configured',
247
+ details: filePath,
248
+ });
249
+ }
250
+ else if (validation.error && validation.error.includes('placeholder')) {
251
+ checks.push({
252
+ id: `settings-${file.profile}`,
253
+ name: file.name,
254
+ status: 'warning',
255
+ message: 'Placeholder key',
256
+ details: filePath,
257
+ });
258
+ }
259
+ else {
260
+ checks.push({
261
+ id: `settings-${file.profile}`,
262
+ name: file.name,
263
+ status: 'ok',
264
+ message: 'Valid JSON',
265
+ details: filePath,
266
+ });
267
+ }
268
+ }
269
+ catch {
270
+ checks.push({
271
+ id: `settings-${file.profile}`,
272
+ name: file.name,
273
+ status: 'error',
274
+ message: 'Invalid JSON',
275
+ details: filePath,
276
+ });
277
+ }
278
+ }
279
+ return checks;
280
+ }
281
+ // Check 5: Claude settings
282
+ function checkClaudeSettings(claudeDir) {
283
+ const settingsPath = path.join(claudeDir, 'settings.json');
284
+ if (!fs.existsSync(settingsPath)) {
127
285
  return {
128
- id: 'profiles-file',
129
- name: 'Profiles Registry',
286
+ id: 'claude-settings',
287
+ name: '~/.claude/settings.json',
130
288
  status: 'warning',
131
- message: 'Not found (will be created on first account)',
132
- details: profilesPath,
133
- fixable: true,
289
+ message: 'Not found',
290
+ fix: 'Run: claude /login',
134
291
  };
135
292
  }
136
293
  try {
137
- const content = fs.readFileSync(profilesPath, 'utf8');
294
+ const content = fs.readFileSync(settingsPath, 'utf8');
138
295
  JSON.parse(content);
139
296
  return {
140
- id: 'profiles-file',
141
- name: 'Profiles Registry',
297
+ id: 'claude-settings',
298
+ name: '~/.claude/settings.json',
142
299
  status: 'ok',
143
300
  message: 'Valid',
144
- details: profilesPath,
145
301
  };
146
302
  }
147
303
  catch {
148
304
  return {
149
- id: 'profiles-file',
150
- name: 'Profiles Registry',
151
- status: 'error',
305
+ id: 'claude-settings',
306
+ name: '~/.claude/settings.json',
307
+ status: 'warning',
152
308
  message: 'Invalid JSON',
153
- details: profilesPath,
309
+ fix: 'Run: claude /login',
310
+ };
311
+ }
312
+ }
313
+ // Check 6: Profiles
314
+ function checkProfiles(ccsDir) {
315
+ const configPath = path.join(ccsDir, 'config.json');
316
+ if (!fs.existsSync(configPath)) {
317
+ return {
318
+ id: 'profiles',
319
+ name: 'Profiles',
320
+ status: 'info',
321
+ message: 'config.json not found',
322
+ };
323
+ }
324
+ try {
325
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
326
+ if (!config.profiles || typeof config.profiles !== 'object') {
327
+ return {
328
+ id: 'profiles',
329
+ name: 'Profiles',
330
+ status: 'error',
331
+ message: 'Missing profiles object',
332
+ fix: 'Run: npm install -g @kaitranntt/ccs --force',
333
+ };
334
+ }
335
+ const profileCount = Object.keys(config.profiles).length;
336
+ const profileNames = Object.keys(config.profiles).join(', ');
337
+ return {
338
+ id: 'profiles',
339
+ name: 'Profiles',
340
+ status: 'ok',
341
+ message: `${profileCount} configured`,
342
+ details: profileNames.length > 40 ? profileNames.substring(0, 37) + '...' : profileNames,
343
+ };
344
+ }
345
+ catch (e) {
346
+ return {
347
+ id: 'profiles',
348
+ name: 'Profiles',
349
+ status: 'error',
350
+ message: e.message,
351
+ };
352
+ }
353
+ }
354
+ // Check 7: Instances
355
+ function checkInstances(ccsDir) {
356
+ const instancesDir = path.join(ccsDir, 'instances');
357
+ if (!fs.existsSync(instancesDir)) {
358
+ return {
359
+ id: 'instances',
360
+ name: 'Instances',
361
+ status: 'ok',
362
+ message: 'No account profiles',
363
+ };
364
+ }
365
+ const instances = fs.readdirSync(instancesDir).filter((name) => {
366
+ return fs.statSync(path.join(instancesDir, name)).isDirectory();
367
+ });
368
+ if (instances.length === 0) {
369
+ return {
370
+ id: 'instances',
371
+ name: 'Instances',
372
+ status: 'ok',
373
+ message: 'No account profiles',
374
+ };
375
+ }
376
+ return {
377
+ id: 'instances',
378
+ name: 'Instances',
379
+ status: 'ok',
380
+ message: `${instances.length} account profile${instances.length !== 1 ? 's' : ''}`,
381
+ };
382
+ }
383
+ // Check 8: Delegation
384
+ function checkDelegation(ccsDir) {
385
+ const ccsClaudeCommandsDir = path.join(ccsDir, '.claude', 'commands');
386
+ const hasCcsCommand = fs.existsSync(path.join(ccsClaudeCommandsDir, 'ccs.md'));
387
+ const hasContinueCommand = fs.existsSync(path.join(ccsClaudeCommandsDir, 'ccs', 'continue.md'));
388
+ if (!hasCcsCommand || !hasContinueCommand) {
389
+ return {
390
+ id: 'delegation',
391
+ name: 'Delegation',
392
+ status: 'warning',
393
+ message: 'Not installed',
394
+ fix: 'Run: npm install -g @kaitranntt/ccs --force',
395
+ };
396
+ }
397
+ const { DelegationValidator } = require('../utils/delegation-validator');
398
+ const readyProfiles = [];
399
+ for (const profile of ['glm', 'kimi']) {
400
+ const validation = DelegationValidator.validate(profile);
401
+ if (validation.valid) {
402
+ readyProfiles.push(profile);
403
+ }
404
+ }
405
+ if (readyProfiles.length === 0) {
406
+ return {
407
+ id: 'delegation',
408
+ name: 'Delegation',
409
+ status: 'warning',
410
+ message: 'No profiles ready',
411
+ fix: 'Configure profiles with valid API keys',
412
+ };
413
+ }
414
+ return {
415
+ id: 'delegation',
416
+ name: 'Delegation',
417
+ status: 'ok',
418
+ message: `${readyProfiles.length} profiles ready`,
419
+ details: readyProfiles.join(', '),
420
+ };
421
+ }
422
+ // Check 9: Permissions
423
+ function checkPermissions(ccsDir) {
424
+ const testFile = path.join(ccsDir, '.permission-test');
425
+ try {
426
+ fs.writeFileSync(testFile, 'test', 'utf8');
427
+ fs.unlinkSync(testFile);
428
+ return {
429
+ id: 'permissions',
430
+ name: 'Permissions',
431
+ status: 'ok',
432
+ message: 'Write access verified',
433
+ };
434
+ }
435
+ catch {
436
+ return {
437
+ id: 'permissions',
438
+ name: 'Permissions',
439
+ status: 'error',
440
+ message: 'Cannot write to ~/.ccs/',
441
+ fix: 'sudo chown -R $USER ~/.ccs ~/.claude && chmod 755 ~/.ccs ~/.claude',
442
+ };
443
+ }
444
+ }
445
+ // Check 10: CCS Symlinks
446
+ function checkCcsSymlinks() {
447
+ try {
448
+ const { ClaudeSymlinkManager } = require('../utils/claude-symlink-manager');
449
+ const manager = new ClaudeSymlinkManager();
450
+ const health = manager.checkHealth();
451
+ if (health.healthy) {
452
+ const itemCount = manager.ccsItems.length;
453
+ return {
454
+ id: 'ccs-symlinks',
455
+ name: 'CCS Symlinks',
456
+ status: 'ok',
457
+ message: `${itemCount}/${itemCount} items linked`,
458
+ };
459
+ }
460
+ return {
461
+ id: 'ccs-symlinks',
462
+ name: 'CCS Symlinks',
463
+ status: 'warning',
464
+ message: `${health.issues.length} issues found`,
465
+ fix: 'Run: ccs sync',
466
+ };
467
+ }
468
+ catch (e) {
469
+ return {
470
+ id: 'ccs-symlinks',
471
+ name: 'CCS Symlinks',
472
+ status: 'warning',
473
+ message: 'Could not check',
474
+ details: e.message,
475
+ fix: 'Run: ccs sync',
476
+ };
477
+ }
478
+ }
479
+ // Check 11: Settings Symlinks
480
+ function checkSettingsSymlinks(homedir, ccsDir, claudeDir) {
481
+ try {
482
+ const sharedDir = path.join(homedir, '.ccs', 'shared');
483
+ const sharedSettings = path.join(sharedDir, 'settings.json');
484
+ const claudeSettings = path.join(claudeDir, 'settings.json');
485
+ if (!fs.existsSync(sharedSettings)) {
486
+ return {
487
+ id: 'settings-symlinks',
488
+ name: 'settings.json',
489
+ status: 'warning',
490
+ message: 'Shared not found',
491
+ fix: 'Run: ccs sync',
492
+ };
493
+ }
494
+ const sharedStats = fs.lstatSync(sharedSettings);
495
+ if (!sharedStats.isSymbolicLink()) {
496
+ return {
497
+ id: 'settings-symlinks',
498
+ name: 'settings.json',
499
+ status: 'warning',
500
+ message: 'Not a symlink',
501
+ fix: 'Run: ccs sync',
502
+ };
503
+ }
504
+ const sharedTarget = fs.readlinkSync(sharedSettings);
505
+ const resolvedShared = path.resolve(path.dirname(sharedSettings), sharedTarget);
506
+ if (resolvedShared !== claudeSettings) {
507
+ return {
508
+ id: 'settings-symlinks',
509
+ name: 'settings.json',
510
+ status: 'warning',
511
+ message: 'Wrong target',
512
+ fix: 'Run: ccs sync',
513
+ };
514
+ }
515
+ // Check instances
516
+ const instancesDir = path.join(ccsDir, 'instances');
517
+ if (!fs.existsSync(instancesDir)) {
518
+ return {
519
+ id: 'settings-symlinks',
520
+ name: 'settings.json',
521
+ status: 'ok',
522
+ message: 'Shared symlink valid',
523
+ };
524
+ }
525
+ const instances = fs.readdirSync(instancesDir).filter((name) => {
526
+ return fs.statSync(path.join(instancesDir, name)).isDirectory();
527
+ });
528
+ let broken = 0;
529
+ for (const instance of instances) {
530
+ const instanceSettings = path.join(instancesDir, instance, 'settings.json');
531
+ if (!fs.existsSync(instanceSettings)) {
532
+ broken++;
533
+ continue;
534
+ }
535
+ try {
536
+ const stats = fs.lstatSync(instanceSettings);
537
+ if (!stats.isSymbolicLink()) {
538
+ broken++;
539
+ continue;
540
+ }
541
+ const target = fs.readlinkSync(instanceSettings);
542
+ const resolved = path.resolve(path.dirname(instanceSettings), target);
543
+ if (resolved !== sharedSettings) {
544
+ broken++;
545
+ }
546
+ }
547
+ catch {
548
+ broken++;
549
+ }
550
+ }
551
+ if (broken > 0) {
552
+ return {
553
+ id: 'settings-symlinks',
554
+ name: 'settings.json',
555
+ status: 'warning',
556
+ message: `${broken} broken instance(s)`,
557
+ fix: 'Run: ccs sync',
558
+ };
559
+ }
560
+ return {
561
+ id: 'settings-symlinks',
562
+ name: 'settings.json',
563
+ status: 'ok',
564
+ message: `${instances.length} instance(s) valid`,
565
+ };
566
+ }
567
+ catch (e) {
568
+ return {
569
+ id: 'settings-symlinks',
570
+ name: 'settings.json',
571
+ status: 'warning',
572
+ message: 'Check failed',
573
+ details: e.message,
574
+ fix: 'Run: ccs sync',
154
575
  };
155
576
  }
156
577
  }
157
- function checkCliproxy() {
578
+ // Check 12: CLIProxy Binary
579
+ function checkCliproxyBinary() {
158
580
  if ((0, cliproxy_1.isCLIProxyInstalled)()) {
159
581
  const version = (0, cliproxy_1.getInstalledCliproxyVersion)();
160
582
  const binaryPath = (0, cliproxy_1.getCLIProxyPath)();
161
583
  return {
162
- id: 'cliproxy',
163
- name: 'CLIProxy',
584
+ id: 'cliproxy-binary',
585
+ name: 'CLIProxy Binary',
164
586
  status: 'ok',
165
- message: `Installed: ${version}`,
587
+ message: `v${version}`,
166
588
  details: binaryPath,
167
589
  };
168
590
  }
169
591
  return {
170
- id: 'cliproxy',
171
- name: 'CLIProxy',
172
- status: 'warning',
173
- message: 'Not installed (optional)',
174
- details: 'Required for gemini/codex/agy providers. Install: ccs cliproxy --latest',
592
+ id: 'cliproxy-binary',
593
+ name: 'CLIProxy Binary',
594
+ status: 'info',
595
+ message: 'Not installed',
596
+ details: 'Downloads on first use',
175
597
  };
176
598
  }
177
- function checkCcsDirectory() {
178
- const ccsDir = (0, config_manager_1.getCcsDir)();
179
- if (!fs.existsSync(ccsDir)) {
599
+ // Check 13: CLIProxy Config
600
+ function checkCliproxyConfig() {
601
+ const configPath = (0, cliproxy_1.getConfigPath)();
602
+ if (fs.existsSync(configPath)) {
180
603
  return {
181
- id: 'ccs-dir',
182
- name: 'CCS Directory',
183
- status: 'warning',
184
- message: 'Not found',
185
- details: ccsDir,
186
- fixable: true,
604
+ id: 'cliproxy-config',
605
+ name: 'CLIProxy Config',
606
+ status: 'ok',
607
+ message: 'cliproxy/config.yaml',
187
608
  };
188
609
  }
189
610
  return {
190
- id: 'ccs-dir',
191
- name: 'CCS Directory',
192
- status: 'ok',
193
- message: 'Exists',
194
- details: ccsDir,
611
+ id: 'cliproxy-config',
612
+ name: 'CLIProxy Config',
613
+ status: 'info',
614
+ message: 'Not created',
615
+ details: 'Generated on first use',
616
+ };
617
+ }
618
+ // Check 14: OAuth Providers
619
+ function checkOAuthProviders() {
620
+ const authStatuses = (0, cliproxy_1.getAllAuthStatus)();
621
+ const checks = [];
622
+ for (const status of authStatuses) {
623
+ const providerName = status.provider.charAt(0).toUpperCase() + status.provider.slice(1);
624
+ if (status.authenticated) {
625
+ const lastAuth = status.lastAuth ? status.lastAuth.toLocaleDateString() : '';
626
+ checks.push({
627
+ id: `oauth-${status.provider}`,
628
+ name: `${providerName} Auth`,
629
+ status: 'ok',
630
+ message: 'Authenticated',
631
+ details: lastAuth,
632
+ });
633
+ }
634
+ else {
635
+ checks.push({
636
+ id: `oauth-${status.provider}`,
637
+ name: `${providerName} Auth`,
638
+ status: 'info',
639
+ message: 'Not authenticated',
640
+ fix: `Run: ccs ${status.provider} --auth`,
641
+ });
642
+ }
643
+ }
644
+ return checks;
645
+ }
646
+ // Check 15: CLIProxy Port
647
+ async function checkCliproxyPort() {
648
+ const portProcess = await (0, port_utils_1.getPortProcess)(cliproxy_1.CLIPROXY_DEFAULT_PORT);
649
+ if (!portProcess) {
650
+ return {
651
+ id: 'cliproxy-port',
652
+ name: 'CLIProxy Port',
653
+ status: 'info',
654
+ message: `${cliproxy_1.CLIPROXY_DEFAULT_PORT} free`,
655
+ details: 'Proxy not running',
656
+ };
657
+ }
658
+ if ((0, port_utils_1.isCLIProxyProcess)(portProcess)) {
659
+ return {
660
+ id: 'cliproxy-port',
661
+ name: 'CLIProxy Port',
662
+ status: 'ok',
663
+ message: 'CLIProxy running',
664
+ details: `PID ${portProcess.pid}`,
665
+ };
666
+ }
667
+ return {
668
+ id: 'cliproxy-port',
669
+ name: 'CLIProxy Port',
670
+ status: 'warning',
671
+ message: `Occupied by ${portProcess.processName}`,
672
+ details: `PID ${portProcess.pid}`,
673
+ fix: `Kill process: kill ${portProcess.pid}`,
195
674
  };
196
675
  }
197
676
  /**