@neurcode-ai/cli 0.9.42 โ†’ 0.9.43

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.
@@ -1,132 +1,513 @@
1
1
  "use strict";
2
2
  /**
3
- * Doctor Command - Health Check & Connectivity Diagnostics
3
+ * Doctor Command - Enterprise Readiness Diagnostics
4
4
  *
5
- * Verifies API connectivity and reports system configuration
5
+ * Verifies:
6
+ * - CLI configuration and auth
7
+ * - Project/workspace wiring
8
+ * - Deterministic governance artifacts
9
+ * - API health + runtime compatibility handshake
10
+ * - Notifications stream CORS preflight (dashboard critical path)
6
11
  */
7
12
  var __importDefault = (this && this.__importDefault) || function (mod) {
8
13
  return (mod && mod.__esModule) ? mod : { "default": mod };
9
14
  };
10
15
  Object.defineProperty(exports, "__esModule", { value: true });
11
16
  exports.doctorCommand = doctorCommand;
12
- const config_1 = require("../config");
13
- const api_client_1 = require("../api-client");
17
+ const fs_1 = require("fs");
18
+ const path_1 = require("path");
14
19
  const chalk_1 = __importDefault(require("chalk"));
20
+ const contracts_1 = require("@neurcode-ai/contracts");
21
+ const api_client_1 = require("../api-client");
22
+ const config_1 = require("../config");
23
+ const change_contract_1 = require("../utils/change-contract");
24
+ const policy_compiler_1 = require("../utils/policy-compiler");
25
+ const policy_packs_1 = require("../utils/policy-packs");
26
+ const project_root_1 = require("../utils/project-root");
27
+ const runtime_guard_1 = require("../utils/runtime-guard");
15
28
  const messages_1 = require("../utils/messages");
16
- async function doctorCommand() {
17
- const userInfo = await (0, messages_1.getUserInfo)();
18
- const greeting = userInfo ? `, ${userInfo.displayName}` : '';
19
- await (0, messages_1.printSuccessBanner)('Neurcode CLI Health Check', `Running diagnostics${greeting}...`);
20
- let allChecksPassed = true;
21
- // Check 1: Configuration
22
- console.log(chalk_1.default.bold.white('๐Ÿ“‹ Configuration Check:'));
29
+ function summarizeChecks(checks) {
30
+ const summary = {
31
+ total: checks.length,
32
+ passed: 0,
33
+ warned: 0,
34
+ failed: 0,
35
+ skipped: 0,
36
+ };
37
+ for (const check of checks) {
38
+ if (check.status === 'pass')
39
+ summary.passed += 1;
40
+ if (check.status === 'warn')
41
+ summary.warned += 1;
42
+ if (check.status === 'fail')
43
+ summary.failed += 1;
44
+ if (check.status === 'skip')
45
+ summary.skipped += 1;
46
+ }
47
+ return summary;
48
+ }
49
+ function iconForStatus(status) {
50
+ if (status === 'pass')
51
+ return 'โœ…';
52
+ if (status === 'warn')
53
+ return 'โš ๏ธ ';
54
+ if (status === 'fail')
55
+ return 'โŒ';
56
+ return 'โญ๏ธ ';
57
+ }
58
+ function colorizeStatus(status, text) {
59
+ if (status === 'pass')
60
+ return chalk_1.default.green(text);
61
+ if (status === 'warn')
62
+ return chalk_1.default.yellow(text);
63
+ if (status === 'fail')
64
+ return chalk_1.default.red(text);
65
+ return chalk_1.default.dim(text);
66
+ }
67
+ function normalizeUrl(input) {
68
+ return input.replace(/\/+$/, '');
69
+ }
70
+ function parseJsonObject(value) {
71
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
72
+ return null;
73
+ }
74
+ return value;
75
+ }
76
+ async function fetchWithTimeout(input, init, timeoutMs) {
77
+ const controller = new AbortController();
78
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
79
+ try {
80
+ return await fetch(input, {
81
+ ...init,
82
+ signal: controller.signal,
83
+ });
84
+ }
85
+ finally {
86
+ clearTimeout(timer);
87
+ }
88
+ }
89
+ async function probeHealth(apiUrl) {
90
+ const healthUrl = `${normalizeUrl(apiUrl)}/health`;
91
+ try {
92
+ const response = await fetchWithTimeout(healthUrl, {
93
+ method: 'GET',
94
+ headers: {
95
+ 'User-Agent': 'neurcode-cli-doctor',
96
+ },
97
+ }, 5000);
98
+ if (!response.ok) {
99
+ return {
100
+ ok: false,
101
+ statusCode: response.status,
102
+ apiVersion: null,
103
+ compatibility: null,
104
+ error: `Health endpoint returned HTTP ${response.status}`,
105
+ };
106
+ }
107
+ const payload = await response.json().catch(() => null);
108
+ const record = parseJsonObject(payload);
109
+ const compatibility = record ? parseJsonObject(record.compatibility) : null;
110
+ return {
111
+ ok: true,
112
+ statusCode: response.status,
113
+ apiVersion: record && typeof record.version === 'string' ? record.version : null,
114
+ compatibility,
115
+ error: null,
116
+ };
117
+ }
118
+ catch (error) {
119
+ const message = error instanceof Error && error.name === 'AbortError'
120
+ ? 'Health endpoint timed out after 5s'
121
+ : error instanceof Error
122
+ ? error.message
123
+ : String(error);
124
+ return {
125
+ ok: false,
126
+ statusCode: null,
127
+ apiVersion: null,
128
+ compatibility: null,
129
+ error: message,
130
+ };
131
+ }
132
+ }
133
+ async function probeNotificationsCors(apiUrl) {
134
+ const streamUrl = `${normalizeUrl(apiUrl)}/api/v1/notifications/stream`;
135
+ try {
136
+ const response = await fetchWithTimeout(streamUrl, {
137
+ method: 'OPTIONS',
138
+ headers: {
139
+ Origin: 'https://www.neurcode.com',
140
+ 'Access-Control-Request-Method': 'GET',
141
+ 'Access-Control-Request-Headers': 'authorization,content-type,x-org-id',
142
+ 'User-Agent': 'neurcode-cli-doctor',
143
+ },
144
+ }, 5000);
145
+ const allowedOrigin = response.headers.get('access-control-allow-origin');
146
+ const allowedHeaders = response.headers.get('access-control-allow-headers');
147
+ const headersNormalized = (allowedHeaders || '').toLowerCase();
148
+ const hasRequiredHeaders = headersNormalized.includes('authorization')
149
+ && headersNormalized.includes('content-type')
150
+ && headersNormalized.includes('x-org-id');
151
+ const ok = response.ok
152
+ && allowedOrigin === 'https://www.neurcode.com'
153
+ && hasRequiredHeaders;
154
+ return {
155
+ ok,
156
+ statusCode: response.status,
157
+ allowedOrigin,
158
+ allowedHeaders,
159
+ error: ok
160
+ ? null
161
+ : `Preflight missing required CORS headers (origin=${allowedOrigin || 'none'}, headers=${allowedHeaders || 'none'})`,
162
+ };
163
+ }
164
+ catch (error) {
165
+ return {
166
+ ok: false,
167
+ statusCode: null,
168
+ allowedOrigin: null,
169
+ allowedHeaders: null,
170
+ error: error instanceof Error ? error.message : String(error),
171
+ };
172
+ }
173
+ }
174
+ function readProjectConfig(projectRoot) {
175
+ const path = (0, path_1.join)(projectRoot, '.neurcode', 'config.json');
176
+ if (!(0, fs_1.existsSync)(path)) {
177
+ return {
178
+ exists: false,
179
+ projectId: null,
180
+ orgId: null,
181
+ path,
182
+ };
183
+ }
184
+ try {
185
+ const parsed = JSON.parse((0, fs_1.readFileSync)(path, 'utf-8'));
186
+ return {
187
+ exists: true,
188
+ projectId: typeof parsed.projectId === 'string' && parsed.projectId.trim() ? parsed.projectId : null,
189
+ orgId: typeof parsed.orgId === 'string' && parsed.orgId.trim() ? parsed.orgId : null,
190
+ path,
191
+ };
192
+ }
193
+ catch (error) {
194
+ return {
195
+ exists: true,
196
+ projectId: null,
197
+ orgId: null,
198
+ error: error instanceof Error ? error.message : 'Failed to parse config.json',
199
+ path,
200
+ };
201
+ }
202
+ }
203
+ function printCheck(check) {
204
+ const head = `${iconForStatus(check.status)} ${check.label}`;
205
+ console.log(colorizeStatus(check.status, head));
206
+ console.log(chalk_1.default.dim(` ${check.message}`));
207
+ if (check.details && check.details.length > 0) {
208
+ for (const line of check.details) {
209
+ console.log(chalk_1.default.dim(` โ€ข ${line}`));
210
+ }
211
+ }
212
+ if (check.recommendation) {
213
+ console.log(chalk_1.default.dim(` โ†ณ ${check.recommendation}`));
214
+ }
215
+ console.log('');
216
+ }
217
+ async function doctorCommand(options = {}) {
218
+ const startedAt = new Date().toISOString();
23
219
  const config = (0, config_1.loadConfig)();
24
- const apiUrl = config.apiUrl || config_1.DEFAULT_API_URL;
220
+ const apiUrl = normalizeUrl(config.apiUrl || config_1.DEFAULT_API_URL);
25
221
  const apiKey = (0, config_1.getApiKey)();
26
- console.log(chalk_1.default.dim(` API URL: ${apiUrl}`));
27
- console.log(chalk_1.default.dim(` API Key: ${apiKey ? 'โœ… Set' : 'โŒ Not set'}`));
28
- console.log(chalk_1.default.dim(` Default URL: ${config_1.DEFAULT_API_URL}`));
29
- if (process.env.NEURCODE_API_URL) {
30
- console.log(chalk_1.default.dim(` Env Var NEURCODE_API_URL: ${process.env.NEURCODE_API_URL}`));
222
+ const cliVersion = (options.cliVersion || 'unknown').trim() || 'unknown';
223
+ const localCompatibility = (0, contracts_1.buildRuntimeCompatibilityDescriptor)('cli', cliVersion);
224
+ const checks = [];
225
+ if (!options.json) {
226
+ const userInfo = await (0, messages_1.getUserInfo)();
227
+ const greeting = userInfo ? `, ${userInfo.displayName}` : '';
228
+ await (0, messages_1.printSuccessBanner)('Neurcode Enterprise Doctor', `Running diagnostics${greeting}...`);
31
229
  }
32
- console.log('');
33
- // Check 2: API Connectivity
34
- console.log(chalk_1.default.bold.white('๐ŸŒ Connectivity Check:'));
35
- try {
36
- const healthUrl = `${apiUrl}/health`;
37
- console.log(chalk_1.default.dim(` Testing: ${healthUrl}`));
38
- const controller = new AbortController();
39
- const timeoutId = setTimeout(() => controller.abort(), 5000); // 5 second timeout
40
- try {
41
- const response = await fetch(healthUrl, {
42
- method: 'GET',
43
- signal: controller.signal,
44
- headers: {
45
- 'User-Agent': 'neurcode-cli-doctor',
46
- },
47
- });
48
- clearTimeout(timeoutId);
49
- if (response.ok) {
50
- const data = await response.json().catch(() => ({}));
51
- console.log(chalk_1.default.green(' โœ… API is reachable'));
52
- if (data.status) {
53
- console.log(chalk_1.default.dim(` Status: ${data.status}`));
54
- }
55
- if (data.version) {
56
- console.log(chalk_1.default.dim(` Version: ${data.version}`));
57
- }
230
+ checks.push({
231
+ id: 'config.api_url',
232
+ label: 'API URL',
233
+ status: 'pass',
234
+ message: `Using ${apiUrl}`,
235
+ details: process.env.NEURCODE_API_URL ? [`env override: ${process.env.NEURCODE_API_URL}`] : undefined,
236
+ });
237
+ checks.push({
238
+ id: 'config.auth',
239
+ label: 'Authentication key',
240
+ status: apiKey ? 'pass' : 'fail',
241
+ message: apiKey ? 'API key is configured.' : 'No API key configured.',
242
+ recommendation: apiKey ? undefined : 'Run `neurcode login`.',
243
+ });
244
+ const rootTrace = (0, project_root_1.resolveNeurcodeProjectRootWithTrace)(process.cwd());
245
+ const projectConfig = readProjectConfig(rootTrace.projectRoot);
246
+ checks.push({
247
+ id: 'workspace.root',
248
+ label: 'Workspace root',
249
+ status: projectConfig.exists ? 'pass' : 'warn',
250
+ message: projectConfig.exists
251
+ ? `Resolved root: ${rootTrace.projectRoot}`
252
+ : `No linked workspace found at ${rootTrace.projectRoot}`,
253
+ details: [
254
+ `git root: ${rootTrace.gitRoot || 'not in git repo'}`,
255
+ `override status: ${rootTrace.overrideStatus}`,
256
+ ],
257
+ recommendation: projectConfig.exists ? undefined : 'Run `neurcode init` in your repository root.',
258
+ });
259
+ checks.push({
260
+ id: 'workspace.project_config',
261
+ label: 'Project binding',
262
+ status: projectConfig.exists && !projectConfig.error && projectConfig.projectId ? 'pass' : 'warn',
263
+ message: projectConfig.exists && !projectConfig.error && projectConfig.projectId
264
+ ? `Project bound (${projectConfig.projectId})${projectConfig.orgId ? ` in org ${projectConfig.orgId}` : ''}.`
265
+ : projectConfig.error
266
+ ? `Project config parse error (${projectConfig.error}).`
267
+ : 'Project ID not found in .neurcode/config.json.',
268
+ details: [`path: ${projectConfig.path}`],
269
+ recommendation: projectConfig.exists && !projectConfig.error && projectConfig.projectId
270
+ ? undefined
271
+ : 'Run `neurcode init` to bind this repo to a project.',
272
+ });
273
+ const policyLock = (0, policy_packs_1.readPolicyLockFile)(rootTrace.projectRoot);
274
+ checks.push({
275
+ id: 'artifact.policy_lock',
276
+ label: 'Policy lock artifact',
277
+ status: policyLock.lock ? 'pass' : policyLock.exists ? 'fail' : 'warn',
278
+ message: policyLock.lock
279
+ ? `Policy lock present (effective rules: ${policyLock.lock.effective.ruleCount}).`
280
+ : policyLock.exists
281
+ ? `Policy lock invalid (${policyLock.error || 'parse failure'}).`
282
+ : 'Policy lock artifact not found.',
283
+ details: [`path: ${policyLock.path}`],
284
+ recommendation: policyLock.lock || !projectConfig.exists
285
+ ? undefined
286
+ : 'Run `neurcode policy install soc2 && neurcode policy compile`.',
287
+ });
288
+ const compiledPolicy = (0, policy_compiler_1.readCompiledPolicyArtifact)(rootTrace.projectRoot);
289
+ checks.push({
290
+ id: 'artifact.compiled_policy',
291
+ label: 'Compiled policy artifact',
292
+ status: compiledPolicy.artifact ? 'pass' : compiledPolicy.exists ? 'fail' : 'warn',
293
+ message: compiledPolicy.artifact
294
+ ? `Compiled policy present (deterministic rules: ${compiledPolicy.artifact.compilation.deterministicRuleCount}).`
295
+ : compiledPolicy.exists
296
+ ? `Compiled policy invalid (${compiledPolicy.error || 'parse failure'}).`
297
+ : 'Compiled policy artifact not found.',
298
+ details: [`path: ${compiledPolicy.path}`],
299
+ recommendation: compiledPolicy.artifact || !projectConfig.exists
300
+ ? undefined
301
+ : 'Run `neurcode policy compile --include-dashboard --out neurcode.policy.compiled.json`.',
302
+ });
303
+ const changeContract = (0, change_contract_1.readChangeContract)(rootTrace.projectRoot);
304
+ checks.push({
305
+ id: 'artifact.change_contract',
306
+ label: 'Change contract artifact',
307
+ status: changeContract.contract ? 'pass' : changeContract.exists ? 'fail' : 'warn',
308
+ message: changeContract.contract
309
+ ? `Change contract present (plan ${changeContract.contract.planId}, files ${changeContract.contract.expectedFiles.length}).`
310
+ : changeContract.exists
311
+ ? `Change contract invalid (${changeContract.error || 'parse failure'}).`
312
+ : 'Change contract artifact not found.',
313
+ details: [`path: ${changeContract.path}`],
314
+ recommendation: changeContract.contract || !projectConfig.exists
315
+ ? undefined
316
+ : 'Run `neurcode plan ...` or `neurcode contract import ...` to create .neurcode/change-contract.json.',
317
+ });
318
+ const runtimeGuard = (0, runtime_guard_1.readRuntimeGuardArtifact)(rootTrace.projectRoot);
319
+ checks.push({
320
+ id: 'artifact.runtime_guard',
321
+ label: 'Runtime guard artifact',
322
+ status: runtimeGuard.artifact ? (runtimeGuard.artifact.active ? 'pass' : 'warn') : runtimeGuard.exists ? 'fail' : 'warn',
323
+ message: runtimeGuard.artifact
324
+ ? runtimeGuard.artifact.active
325
+ ? `Runtime guard active (${runtimeGuard.artifact.mode}, checks: ${runtimeGuard.artifact.stats.checksRun}).`
326
+ : 'Runtime guard artifact exists but is inactive.'
327
+ : runtimeGuard.exists
328
+ ? `Runtime guard invalid (${runtimeGuard.error || 'parse failure'}).`
329
+ : 'Runtime guard artifact not found.',
330
+ details: [`path: ${runtimeGuard.path}`],
331
+ recommendation: runtimeGuard.artifact && runtimeGuard.artifact.active
332
+ ? undefined
333
+ : 'Run `neurcode guard start --strict` before coding sessions.',
334
+ });
335
+ const healthProbe = await probeHealth(apiUrl);
336
+ checks.push({
337
+ id: 'api.health',
338
+ label: 'API health endpoint',
339
+ status: healthProbe.ok ? 'pass' : 'fail',
340
+ message: healthProbe.ok
341
+ ? `API reachable (${apiUrl}/health).`
342
+ : `API health probe failed (${healthProbe.error || 'unknown error'}).`,
343
+ details: [
344
+ `http status: ${healthProbe.statusCode ?? 'n/a'}`,
345
+ `api version: ${healthProbe.apiVersion || 'unknown'}`,
346
+ ],
347
+ recommendation: healthProbe.ok ? undefined : 'Verify API availability and `NEURCODE_API_URL`.',
348
+ });
349
+ if (healthProbe.ok && healthProbe.compatibility) {
350
+ const apiCompat = healthProbe.compatibility;
351
+ const contractId = typeof apiCompat.contractId === 'string' ? apiCompat.contractId.trim() : '';
352
+ const runtimeContractVersion = typeof apiCompat.runtimeContractVersion === 'string'
353
+ ? apiCompat.runtimeContractVersion.trim()
354
+ : '';
355
+ const minimumPeers = parseJsonObject(apiCompat.minimumPeerVersions);
356
+ const requiredCli = minimumPeers && typeof minimumPeers.cli === 'string'
357
+ ? minimumPeers.cli.trim()
358
+ : null;
359
+ const requiredApi = localCompatibility.minimumPeerVersions.api || null;
360
+ const contractOk = contractId === contracts_1.RUNTIME_COMPATIBILITY_CONTRACT_ID
361
+ && runtimeContractVersion === contracts_1.RUNTIME_COMPATIBILITY_CONTRACT_VERSION;
362
+ const details = [
363
+ `api contract: ${contractId || 'missing'}@${runtimeContractVersion || 'missing'}`,
364
+ `expected contract: ${contracts_1.RUNTIME_COMPATIBILITY_CONTRACT_ID}@${contracts_1.RUNTIME_COMPATIBILITY_CONTRACT_VERSION}`,
365
+ ];
366
+ let status = contractOk ? 'pass' : 'fail';
367
+ let message = contractOk
368
+ ? 'Runtime compatibility contract handshake passed.'
369
+ : 'Runtime compatibility contract mismatch.';
370
+ let recommendation;
371
+ if (requiredCli) {
372
+ details.push(`api requires cli >= ${requiredCli}`);
373
+ const cliCompat = (0, contracts_1.isSemverAtLeast)(cliVersion, requiredCli);
374
+ if (cliCompat === false) {
375
+ status = 'fail';
376
+ message = `CLI version ${cliVersion} is below API minimum ${requiredCli}.`;
377
+ recommendation = 'Upgrade CLI to a compatible release.';
58
378
  }
59
- else {
60
- console.log(chalk_1.default.yellow(` โš ๏ธ API responded with status ${response.status}`));
61
- console.log(chalk_1.default.dim(` This may indicate a server error`));
62
- allChecksPassed = false;
379
+ else if (cliCompat === null) {
380
+ status = status === 'fail' ? 'fail' : 'warn';
381
+ details.push(`unable to parse semver for CLI version "${cliVersion}"`);
63
382
  }
64
383
  }
65
- catch (error) {
66
- clearTimeout(timeoutId);
67
- if (error instanceof Error && error.name === 'AbortError') {
68
- console.log(chalk_1.default.red(' โŒ Connection timeout (5s)'));
69
- console.log(chalk_1.default.dim(' The API may be unreachable or very slow'));
384
+ if (requiredApi && healthProbe.apiVersion) {
385
+ details.push(`cli expects api >= ${requiredApi}`);
386
+ const apiCompatResult = (0, contracts_1.isSemverAtLeast)(healthProbe.apiVersion, requiredApi);
387
+ if (apiCompatResult === false) {
388
+ status = 'fail';
389
+ message = `API version ${healthProbe.apiVersion} is below CLI minimum ${requiredApi}.`;
390
+ recommendation = 'Deploy compatible API version before running strict verify gates.';
70
391
  }
71
- else {
72
- console.log(chalk_1.default.red(' โŒ Connection failed'));
73
- console.error(chalk_1.default.dim(` Error: ${error instanceof Error ? error.message : String(error)}`));
392
+ else if (apiCompatResult === null) {
393
+ status = status === 'fail' ? 'fail' : 'warn';
394
+ details.push(`unable to parse semver for API version "${healthProbe.apiVersion}"`);
74
395
  }
75
- allChecksPassed = false;
76
396
  }
397
+ checks.push({
398
+ id: 'api.runtime_compatibility',
399
+ label: 'Runtime compatibility handshake',
400
+ status,
401
+ message,
402
+ details,
403
+ recommendation,
404
+ });
77
405
  }
78
- catch (error) {
79
- console.log(chalk_1.default.red(' โŒ Health check failed'));
80
- console.error(chalk_1.default.dim(` Error: ${error instanceof Error ? error.message : String(error)}`));
81
- allChecksPassed = false;
406
+ else {
407
+ checks.push({
408
+ id: 'api.runtime_compatibility',
409
+ label: 'Runtime compatibility handshake',
410
+ status: healthProbe.ok ? 'warn' : 'skip',
411
+ message: healthProbe.ok
412
+ ? 'API health payload is missing compatibility metadata.'
413
+ : 'Skipped because API health probe failed.',
414
+ recommendation: healthProbe.ok
415
+ ? 'Deploy API build that exposes compatibility metadata on /health.'
416
+ : undefined,
417
+ });
82
418
  }
83
- console.log('');
84
- // Check 3: Authenticated Endpoint (if API key is available)
419
+ const corsProbe = await probeNotificationsCors(apiUrl);
420
+ checks.push({
421
+ id: 'api.notifications_cors',
422
+ label: 'Notifications stream CORS preflight',
423
+ status: corsProbe.ok ? 'pass' : 'warn',
424
+ message: corsProbe.ok
425
+ ? 'CORS preflight allows dashboard stream headers.'
426
+ : `Preflight check reported issues (${corsProbe.error || 'unknown'}).`,
427
+ details: [
428
+ `http status: ${corsProbe.statusCode ?? 'n/a'}`,
429
+ `allow-origin: ${corsProbe.allowedOrigin || 'missing'}`,
430
+ `allow-headers: ${corsProbe.allowedHeaders || 'missing'}`,
431
+ ],
432
+ recommendation: corsProbe.ok ? undefined : 'Ensure CORS allowlist includes dashboard origin and x-org-id header.',
433
+ });
85
434
  if (apiKey) {
86
- console.log(chalk_1.default.bold.white('๐Ÿ” Authentication Check:'));
87
435
  try {
88
436
  const client = new api_client_1.ApiClient(config);
89
- // Try a simple authenticated request
90
- console.log(chalk_1.default.dim(' Testing authenticated endpoint...'));
91
- // Try to get projects list (lightweight endpoint)
92
437
  const projects = await client.getProjects();
93
- console.log(chalk_1.default.green(' โœ… Authentication successful'));
94
- console.log(chalk_1.default.dim(` Found ${projects.length} project(s)`));
438
+ checks.push({
439
+ id: 'api.authenticated_request',
440
+ label: 'Authenticated API request',
441
+ status: 'pass',
442
+ message: `Authentication valid (projects visible: ${projects.length}).`,
443
+ });
95
444
  }
96
445
  catch (error) {
97
- console.log(chalk_1.default.red(' โŒ Authentication failed'));
98
- console.error(chalk_1.default.dim(` Error: ${error instanceof Error ? error.message : String(error)}`));
99
- if (error instanceof Error && error.message.includes('401')) {
100
- console.log(chalk_1.default.yellow('\n ๐Ÿ’ก Your API key may be invalid. Run: neurcode login'));
101
- }
102
- else if (error instanceof Error && error.message.includes('403')) {
103
- console.log(chalk_1.default.yellow('\n ๐Ÿ’ก Your API key may not have proper permissions.'));
104
- }
105
- allChecksPassed = false;
446
+ checks.push({
447
+ id: 'api.authenticated_request',
448
+ label: 'Authenticated API request',
449
+ status: 'fail',
450
+ message: `Authenticated request failed (${error instanceof Error ? error.message : String(error)}).`,
451
+ recommendation: 'Run `neurcode login` and verify org/project permissions.',
452
+ });
106
453
  }
107
- console.log('');
108
454
  }
109
455
  else {
110
- console.log(chalk_1.default.bold.white('๐Ÿ” Authentication Check:'));
111
- console.log(chalk_1.default.yellow(' โš ๏ธ Skipped (no API key found)'));
112
- console.log(chalk_1.default.dim(' Run: neurcode login'));
113
- console.log('');
114
- allChecksPassed = false;
456
+ checks.push({
457
+ id: 'api.authenticated_request',
458
+ label: 'Authenticated API request',
459
+ status: 'skip',
460
+ message: 'Skipped because API key is not configured.',
461
+ recommendation: 'Run `neurcode login`.',
462
+ });
115
463
  }
116
- // Summary
117
- if (allChecksPassed) {
118
- await (0, messages_1.printSuccessBanner)('All Checks Passed!', 'Your Neurcode CLI is configured correctly and ready to use');
464
+ const summary = summarizeChecks(checks);
465
+ const recommendations = [...new Set(checks
466
+ .filter((check) => check.status === 'warn' || check.status === 'fail')
467
+ .map((check) => check.recommendation)
468
+ .filter((value) => typeof value === 'string' && value.trim().length > 0))];
469
+ const payload = {
470
+ success: summary.failed === 0,
471
+ timestamp: startedAt,
472
+ cliVersion,
473
+ apiUrl,
474
+ projectRoot: rootTrace.projectRoot,
475
+ summary,
476
+ checks,
477
+ recommendations,
478
+ };
479
+ if (options.json) {
480
+ console.log(JSON.stringify(payload, null, 2));
119
481
  }
120
482
  else {
121
- (0, messages_1.printSection)('Summary');
122
- (0, messages_1.printWarning)('Some Checks Failed', 'Please review the issues above and follow the suggestions');
123
- (0, messages_1.printInfo)('Troubleshooting Tips', [
124
- 'If API is unreachable, check your internet connection',
125
- 'Verify the API URL is correct (should be https://api.neurcode.com)',
126
- 'Run: neurcode login (to authenticate)',
127
- 'Set NEURCODE_API_URL env var to override default URL',
128
- 'Check firewall/proxy settings if connection issues persist'
129
- ].join('\n โ€ข '));
483
+ console.log(chalk_1.default.bold.white('\n๐Ÿ”Ž Diagnostics\n'));
484
+ checks.forEach(printCheck);
485
+ const summaryLine = `Pass ${summary.passed} ยท Warn ${summary.warned} ยท Fail ${summary.failed} ยท Skip ${summary.skipped}`;
486
+ if (summary.failed > 0) {
487
+ console.log(chalk_1.default.red(`โŒ ${summaryLine}`));
488
+ }
489
+ else if (summary.warned > 0) {
490
+ console.log(chalk_1.default.yellow(`โš ๏ธ ${summaryLine}`));
491
+ }
492
+ else {
493
+ console.log(chalk_1.default.green(`โœ… ${summaryLine}`));
494
+ }
495
+ console.log('');
496
+ if (recommendations.length > 0) {
497
+ console.log(chalk_1.default.bold.white('Next actions:'));
498
+ for (const recommendation of recommendations) {
499
+ console.log(chalk_1.default.dim(` โ€ข ${recommendation}`));
500
+ }
501
+ console.log('');
502
+ }
503
+ if (summary.failed === 0) {
504
+ await (0, messages_1.printSuccessBanner)('Doctor Complete', summary.warned > 0
505
+ ? 'Core checks passed with advisory warnings.'
506
+ : 'All enterprise readiness checks passed.');
507
+ }
508
+ }
509
+ if (summary.failed > 0) {
510
+ process.exitCode = 1;
130
511
  }
131
512
  }
132
513
  //# sourceMappingURL=doctor.js.map