praxis-sec 1.0.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 (183) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +170 -0
  3. package/ai-defense/cost-protection.md +292 -0
  4. package/ai-defense/llm-security-checklist.md +324 -0
  5. package/ai-defense/prompt-injection-patterns.js +283 -0
  6. package/ai-defense/system-prompt-armor.md +327 -0
  7. package/checklists/launch-day.md +168 -0
  8. package/cli/agents/abom-generator.js +225 -0
  9. package/cli/agents/agent-attestation-agent.js +318 -0
  10. package/cli/agents/agent-config-scanner.js +787 -0
  11. package/cli/agents/agent-telemetry-agent.js +415 -0
  12. package/cli/agents/agentic-security-agent.js +296 -0
  13. package/cli/agents/agentic-supply-chain-agent.js +463 -0
  14. package/cli/agents/ai-infra-inventory-agent.js +449 -0
  15. package/cli/agents/api-fuzzer.js +345 -0
  16. package/cli/agents/auth-bypass-agent.js +348 -0
  17. package/cli/agents/base-agent.js +280 -0
  18. package/cli/agents/cicd-scanner.js +300 -0
  19. package/cli/agents/config-auditor.js +757 -0
  20. package/cli/agents/deep-analyzer.js +776 -0
  21. package/cli/agents/endpoint-agent-abuse-agent.js +404 -0
  22. package/cli/agents/exception-handler-agent.js +187 -0
  23. package/cli/agents/git-history-scanner.js +169 -0
  24. package/cli/agents/governance-audits.js +138 -0
  25. package/cli/agents/hermes-security-agent.js +536 -0
  26. package/cli/agents/html-reporter.js +1125 -0
  27. package/cli/agents/index.js +147 -0
  28. package/cli/agents/injection-tester.js +502 -0
  29. package/cli/agents/legal-risk-agent.js +328 -0
  30. package/cli/agents/llm-redteam.js +199 -0
  31. package/cli/agents/managed-agent-scanner.js +333 -0
  32. package/cli/agents/mcp-security-agent.js +588 -0
  33. package/cli/agents/memory-poisoning-agent.js +305 -0
  34. package/cli/agents/mobile-scanner.js +231 -0
  35. package/cli/agents/model-file-scanner.js +259 -0
  36. package/cli/agents/orchestrator.js +355 -0
  37. package/cli/agents/pii-compliance-agent.js +301 -0
  38. package/cli/agents/policy-engine.js +229 -0
  39. package/cli/agents/prompt-injection-prober.js +224 -0
  40. package/cli/agents/rag-security-agent.js +204 -0
  41. package/cli/agents/recon-agent.js +207 -0
  42. package/cli/agents/sbom-generator.js +265 -0
  43. package/cli/agents/scoring-engine.js +273 -0
  44. package/cli/agents/ssrf-prober.js +130 -0
  45. package/cli/agents/stateful-watcher.js +238 -0
  46. package/cli/agents/supabase-rls-agent.js +154 -0
  47. package/cli/agents/supply-chain-agent.js +857 -0
  48. package/cli/agents/swarm-orchestrator.js +200 -0
  49. package/cli/agents/verifier-agent.js +303 -0
  50. package/cli/agents/vibe-coding-agent.js +250 -0
  51. package/cli/bin/praxis.js +866 -0
  52. package/cli/commands/abom.js +73 -0
  53. package/cli/commands/agent-fix.js +1245 -0
  54. package/cli/commands/audit.js +1180 -0
  55. package/cli/commands/autofix.js +383 -0
  56. package/cli/commands/baseline.js +193 -0
  57. package/cli/commands/benchmark.js +327 -0
  58. package/cli/commands/checklist.js +223 -0
  59. package/cli/commands/ci.js +403 -0
  60. package/cli/commands/deps.js +516 -0
  61. package/cli/commands/diff.js +200 -0
  62. package/cli/commands/doctor.js +195 -0
  63. package/cli/commands/env-audit.js +349 -0
  64. package/cli/commands/fix.js +218 -0
  65. package/cli/commands/guard.js +396 -0
  66. package/cli/commands/hooks.js +278 -0
  67. package/cli/commands/init.js +514 -0
  68. package/cli/commands/legal.js +158 -0
  69. package/cli/commands/live-advisories.js +241 -0
  70. package/cli/commands/mcp.js +660 -0
  71. package/cli/commands/openclaw.js +386 -0
  72. package/cli/commands/red-team.js +350 -0
  73. package/cli/commands/redteam.js +78 -0
  74. package/cli/commands/remediate.js +797 -0
  75. package/cli/commands/rotate.js +768 -0
  76. package/cli/commands/rules.js +196 -0
  77. package/cli/commands/scan-mcp.js +534 -0
  78. package/cli/commands/scan-skill.js +588 -0
  79. package/cli/commands/scan-standard.js +251 -0
  80. package/cli/commands/scan.js +524 -0
  81. package/cli/commands/score.js +449 -0
  82. package/cli/commands/shell.js +514 -0
  83. package/cli/commands/team-report.js +398 -0
  84. package/cli/commands/undo.js +161 -0
  85. package/cli/commands/update-intel.js +126 -0
  86. package/cli/commands/vibe-check.js +276 -0
  87. package/cli/commands/watch.js +757 -0
  88. package/cli/commands/web.js +63 -0
  89. package/cli/core/ast/guardrail-detector.js +141 -0
  90. package/cli/core/ast/index.js +22 -0
  91. package/cli/core/ast/parser.js +676 -0
  92. package/cli/core/ast/scope-tree.js +287 -0
  93. package/cli/core/ast/taint-tracker.js +158 -0
  94. package/cli/core/branding.js +37 -0
  95. package/cli/core/env.js +38 -0
  96. package/cli/core/errors.js +61 -0
  97. package/cli/core/fs.js +62 -0
  98. package/cli/core/output/compliance.js +90 -0
  99. package/cli/core/output/html-theme.js +158 -0
  100. package/cli/core/output/index.js +57 -0
  101. package/cli/core/output/json.js +48 -0
  102. package/cli/core/output/sarif.js +240 -0
  103. package/cli/core/version.js +67 -0
  104. package/cli/core/web/jobs.js +183 -0
  105. package/cli/core/web/projects.js +146 -0
  106. package/cli/core/web/server.js +439 -0
  107. package/cli/data/atlas-knowledge.json +5640 -0
  108. package/cli/data/eaa-catalog.json +39 -0
  109. package/cli/data/known-mcps.json +26 -0
  110. package/cli/data/probes/prompt-injection-corpus.json +271 -0
  111. package/cli/data/threat-intel.json +85 -0
  112. package/cli/data/threatpacks/latest.json +41 -0
  113. package/cli/hooks/patterns.js +313 -0
  114. package/cli/hooks/post-tool-use.js +140 -0
  115. package/cli/hooks/pre-tool-use.js +186 -0
  116. package/cli/index.js +90 -0
  117. package/cli/providers/llm-provider.js +766 -0
  118. package/cli/utils/autofix-rules.js +74 -0
  119. package/cli/utils/cache-manager.js +310 -0
  120. package/cli/utils/compliance-map.js +191 -0
  121. package/cli/utils/entropy.js +132 -0
  122. package/cli/utils/fix-ledger.js +127 -0
  123. package/cli/utils/hermes-tool-registry.js +252 -0
  124. package/cli/utils/intel/cache.js +61 -0
  125. package/cli/utils/intel/http.js +88 -0
  126. package/cli/utils/intel/index.js +235 -0
  127. package/cli/utils/intel/merge.js +229 -0
  128. package/cli/utils/intel/sources/epss.js +54 -0
  129. package/cli/utils/intel/sources/ghsa.js +81 -0
  130. package/cli/utils/intel/sources/gitguardian.js +40 -0
  131. package/cli/utils/intel/sources/gitleaks.js +101 -0
  132. package/cli/utils/intel/sources/kev.js +38 -0
  133. package/cli/utils/intel/sources/nvd.js +84 -0
  134. package/cli/utils/intel/sources/osv.js +132 -0
  135. package/cli/utils/intel/sources/phylum.js +44 -0
  136. package/cli/utils/intel/sources/snyk.js +46 -0
  137. package/cli/utils/intel/sources/socket.js +69 -0
  138. package/cli/utils/intel/sources/sonatype.js +84 -0
  139. package/cli/utils/intel/sources/threatpack.js +69 -0
  140. package/cli/utils/mcp-trust.js +60 -0
  141. package/cli/utils/output.js +251 -0
  142. package/cli/utils/patterns.js +1130 -0
  143. package/cli/utils/pdf-generator.js +94 -0
  144. package/cli/utils/plugin-loader.js +364 -0
  145. package/cli/utils/rule-import.js +228 -0
  146. package/cli/utils/rule-registry.js +426 -0
  147. package/cli/utils/scan-fingerprint.js +109 -0
  148. package/cli/utils/scan-playbook.js +312 -0
  149. package/cli/utils/score-history.js +119 -0
  150. package/cli/utils/secrets-verifier.js +247 -0
  151. package/cli/utils/security-memory.js +296 -0
  152. package/cli/utils/standards/atlas-knowledge.js +87 -0
  153. package/cli/utils/standards/index.js +127 -0
  154. package/cli/utils/standards/sources/avid.js +45 -0
  155. package/cli/utils/standards/sources/eu-ai-act.js +89 -0
  156. package/cli/utils/standards/sources/google-saif.js +39 -0
  157. package/cli/utils/standards/sources/iso-42001.js +94 -0
  158. package/cli/utils/standards/sources/mitre-atlas.js +54 -0
  159. package/cli/utils/standards/sources/nist-ai-600-1.js +45 -0
  160. package/cli/utils/standards/sources/owasp-llm.js +45 -0
  161. package/cli/utils/standards/sources/owasp-ml.js +45 -0
  162. package/cli/utils/threat-intel.js +265 -0
  163. package/configs/firebase/firestore-rules.txt +215 -0
  164. package/configs/firebase/security-checklist.md +236 -0
  165. package/configs/firebase/storage-rules.txt +206 -0
  166. package/configs/gitignore-template +258 -0
  167. package/configs/nextjs-security-headers.js +220 -0
  168. package/configs/praxisignore-template +50 -0
  169. package/configs/supabase/secure-client.ts +225 -0
  170. package/configs/supabase/security-checklist.md +278 -0
  171. package/docs/THIRD_PARTY_NOTICES.md +26 -0
  172. package/docs/THREAT_INTEL.md +292 -0
  173. package/docs/USAGE.md +1205 -0
  174. package/docs/design/WEB-UI.md +82 -0
  175. package/package.json +71 -0
  176. package/scripts/check-determinism.mjs +119 -0
  177. package/snippets/README.md +122 -0
  178. package/snippets/api-security/api-security-checklist.md +412 -0
  179. package/snippets/api-security/cors-config.ts +322 -0
  180. package/snippets/api-security/input-validation.ts +430 -0
  181. package/snippets/auth/jwt-checklist.md +322 -0
  182. package/snippets/rate-limiting/nextjs-middleware.ts +211 -0
  183. package/snippets/rate-limiting/upstash-ratelimit.ts +229 -0
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Diff Command
3
+ * =============
4
+ *
5
+ * Scan only changed files (git diff) for security issues.
6
+ * Much faster than a full audit — perfect for pre-commit hooks and PR reviews.
7
+ *
8
+ * USAGE:
9
+ * praxis diff Scan uncommitted changes (staged + unstaged)
10
+ * praxis diff --staged Scan only staged changes
11
+ * praxis diff HEAD~3 Scan changes in last 3 commits
12
+ * praxis diff main Scan changes since branching from main
13
+ */
14
+
15
+ import { execFileSync } from 'child_process';
16
+ import path from 'path';
17
+ import chalk from 'chalk';
18
+ import ora from 'ora';
19
+ import { SECRET_PATTERNS, SECURITY_PATTERNS, SKIP_EXTENSIONS, SKIP_FILENAMES } from '../utils/patterns.js';
20
+ import { buildOrchestrator } from '../agents/index.js';
21
+ import { ScoringEngine } from '../agents/scoring-engine.js';
22
+
23
+ // =============================================================================
24
+ // DIFF COMMAND
25
+ // =============================================================================
26
+
27
+ export async function diffCommand(ref, options) {
28
+ const targetPath = options.path || process.cwd();
29
+ const absolutePath = path.resolve(targetPath);
30
+
31
+ // ── Get changed files from git ────────────────────────────────────────────
32
+ const spinner = ora(chalk.white('Getting changed files from git...')).start();
33
+
34
+ let gitArgs;
35
+ if (options.staged) {
36
+ gitArgs = ['diff', '--cached', '--name-only', '--diff-filter=ACMR'];
37
+ } else if (ref) {
38
+ gitArgs = ['diff', '--name-only', '--diff-filter=ACMR', ref];
39
+ } else {
40
+ // Uncommitted changes: both staged and unstaged
41
+ gitArgs = ['diff', '--name-only', '--diff-filter=ACMR', 'HEAD'];
42
+ }
43
+
44
+ let changedFiles;
45
+ try {
46
+ const output = execFileSync('git', gitArgs, {
47
+ cwd: absolutePath,
48
+ encoding: 'utf-8',
49
+ timeout: 10_000,
50
+ }).trim();
51
+
52
+ if (!output) {
53
+ spinner.succeed(chalk.green('No changed files detected'));
54
+ console.log(chalk.gray('\n Nothing to scan. Your working tree is clean.\n'));
55
+ return;
56
+ }
57
+
58
+ changedFiles = output
59
+ .split('\n')
60
+ .map(f => f.trim())
61
+ .filter(f => f.length > 0)
62
+ .map(f => path.resolve(absolutePath, f))
63
+ .filter(f => {
64
+ const ext = path.extname(f).toLowerCase();
65
+ if (SKIP_EXTENSIONS.has(ext)) return false;
66
+ const basename = path.basename(f);
67
+ if (SKIP_FILENAMES.has(basename)) return false;
68
+ return true;
69
+ });
70
+
71
+ spinner.succeed(chalk.white(`${changedFiles.length} changed file(s) to scan`));
72
+ } catch (err) {
73
+ // Fallback for repos with no commits yet
74
+ if (err.message?.includes('unknown revision')) {
75
+ try {
76
+ const output = execFileSync('git', ['diff', '--cached', '--name-only', '--diff-filter=ACMR'], {
77
+ cwd: absolutePath,
78
+ encoding: 'utf-8',
79
+ timeout: 10_000,
80
+ }).trim();
81
+
82
+ changedFiles = output
83
+ .split('\n')
84
+ .map(f => f.trim())
85
+ .filter(f => f.length > 0)
86
+ .map(f => path.resolve(absolutePath, f))
87
+ .filter(f => {
88
+ const ext = path.extname(f).toLowerCase();
89
+ if (SKIP_EXTENSIONS.has(ext)) return false;
90
+ const basename = path.basename(f);
91
+ if (SKIP_FILENAMES.has(basename)) return false;
92
+ return true;
93
+ });
94
+
95
+ if (!changedFiles.length) {
96
+ spinner.succeed(chalk.green('No changed files detected'));
97
+ return;
98
+ }
99
+ spinner.succeed(chalk.white(`${changedFiles.length} changed file(s) to scan`));
100
+ } catch {
101
+ spinner.fail(chalk.red('Not a git repository or git not available'));
102
+ process.exit(1);
103
+ }
104
+ } else {
105
+ spinner.fail(chalk.red('Failed to get changed files from git'));
106
+ console.error(chalk.gray(` ${err.message}`));
107
+ process.exit(1);
108
+ }
109
+ }
110
+
111
+ if (changedFiles.length === 0) {
112
+ console.log(chalk.gray('\n No scannable files in the diff.\n'));
113
+ return;
114
+ }
115
+
116
+ // ── Print header ──────────────────────────────────────────────────────────
117
+ console.log();
118
+ console.log(chalk.cyan('═'.repeat(60)));
119
+ console.log(chalk.cyan.bold(' Praxis — Diff Scan'));
120
+ console.log(chalk.cyan('═'.repeat(60)));
121
+ console.log();
122
+ console.log(chalk.gray(` Scanning ${changedFiles.length} changed file(s):`));
123
+ for (const f of changedFiles.slice(0, 10)) {
124
+ console.log(chalk.gray(` ${path.relative(absolutePath, f)}`));
125
+ }
126
+ if (changedFiles.length > 10) {
127
+ console.log(chalk.gray(` ... and ${changedFiles.length - 10} more`));
128
+ }
129
+ console.log();
130
+
131
+ // ── Run agents on changed files only ──────────────────────────────────────
132
+ const agentSpinner = ora(chalk.white('Running security agents on diff...')).start();
133
+
134
+ const orchestrator = buildOrchestrator();
135
+ const results = await orchestrator.runAll(absolutePath, {
136
+ timeout: options.timeout ?? 30_000,
137
+ changedFiles,
138
+ });
139
+
140
+ const findings = results.findings || [];
141
+ agentSpinner.succeed(
142
+ findings.length === 0
143
+ ? chalk.green('No security issues in changed files')
144
+ : chalk.yellow(`${findings.length} finding(s) in changed files`)
145
+ );
146
+
147
+ // ── Score ─────────────────────────────────────────────────────────────────
148
+ if (findings.length > 0) {
149
+ const scoringEngine = new ScoringEngine();
150
+ const scoreResult = scoringEngine.compute(findings, []);
151
+ scoreResult.score = Math.round(scoreResult.score * 10) / 10;
152
+
153
+ const scoreColor = scoreResult.score >= 75 ? chalk.green.bold : scoreResult.score >= 60 ? chalk.yellow.bold : chalk.red.bold;
154
+
155
+ console.log();
156
+ console.log(chalk.cyan(' ' + '─'.repeat(56)));
157
+
158
+ // Print findings
159
+ let shown = 0;
160
+ for (const f of findings) {
161
+ if (shown >= 20) {
162
+ console.log(chalk.gray(`\n ... and ${findings.length - 20} more findings`));
163
+ break;
164
+ }
165
+ const sevColor = f.severity === 'critical' ? chalk.red :
166
+ f.severity === 'high' ? chalk.yellow :
167
+ f.severity === 'medium' ? chalk.cyan : chalk.gray;
168
+ const relPath = path.relative(absolutePath, f.file);
169
+ console.log(` ${sevColor(`[${f.severity.toUpperCase()}]`)} ${chalk.white(f.title)}`);
170
+ console.log(chalk.gray(` ${relPath}:${f.line} → ${f.fix || f.description}`));
171
+ shown++;
172
+ }
173
+
174
+ console.log();
175
+ console.log(chalk.cyan(' ' + '─'.repeat(56)));
176
+ console.log(
177
+ chalk.white.bold(' Diff Score: ') +
178
+ scoreColor(`${scoreResult.score}/100 ${scoreResult.grade.letter}`)
179
+ );
180
+ console.log(chalk.cyan(' ' + '─'.repeat(56)));
181
+ }
182
+
183
+ // ── JSON output ───────────────────────────────────────────────────────────
184
+ if (options.json) {
185
+ const output = {
186
+ command: 'diff',
187
+ ref: ref || (options.staged ? '--staged' : 'HEAD'),
188
+ changedFiles: changedFiles.map(f => path.relative(absolutePath, f)),
189
+ findings,
190
+ totalFindings: findings.length,
191
+ };
192
+ console.log(JSON.stringify(output, null, 2));
193
+ }
194
+
195
+ console.log();
196
+ console.log(chalk.cyan('═'.repeat(60)));
197
+ console.log();
198
+
199
+ process.exit(findings.length > 0 ? 1 : 0);
200
+ }
@@ -0,0 +1,195 @@
1
+ /**
2
+ * Doctor Command — Environment Diagnostics
3
+ * ==========================================
4
+ *
5
+ * Checks Node.js version, git, npm, API keys, ignore files,
6
+ * cache directory, and package version.
7
+ *
8
+ * USAGE:
9
+ * npx praxis-sec doctor
10
+ */
11
+
12
+ import { execFileSync } from 'child_process';
13
+ import fs from 'fs';
14
+ import path from 'path';
15
+ import chalk from 'chalk';
16
+ import { buildOrchestratorAsync } from '../agents/index.js';
17
+ import { autoDetectProvider } from '../providers/llm-provider.js';
18
+ import { toolVersion, isNewerVersion } from '../core/version.js';
19
+
20
+ const PACKAGE_VERSION = toolVersion();
21
+
22
+ export async function doctorCommand() {
23
+ console.log();
24
+ console.log(chalk.cyan.bold(' Praxis Doctor'));
25
+ console.log(chalk.gray(' ' + '─'.repeat(50)));
26
+ console.log();
27
+
28
+ let allGood = true;
29
+
30
+ // 1. Node.js version
31
+ const nodeVersion = process.version;
32
+ const nodeMajor = parseInt(nodeVersion.slice(1), 10);
33
+ if (nodeMajor >= 18) {
34
+ pass(`Node.js ${nodeVersion} (requires ≥18)`);
35
+ } else {
36
+ fail(`Node.js ${nodeVersion} — requires ≥18`);
37
+ allGood = false;
38
+ }
39
+
40
+ // 2. Git
41
+ try {
42
+ const gitVersion = execFileSync('git', ['--version'], { encoding: 'utf-8' }).trim();
43
+ pass(gitVersion.replace('git version ', 'git v'));
44
+ } catch {
45
+ fail('git not found (needed for guard, git-history-scanner)');
46
+ allGood = false;
47
+ }
48
+
49
+ // 3. Package manager
50
+ const managers = ['npm', 'yarn', 'pnpm'];
51
+ let foundPm = false;
52
+ for (const pm of managers) {
53
+ try {
54
+ const ver = execFileSync(pm, ['--version'], { encoding: 'utf-8', shell: true }).trim();
55
+ pass(`${pm} v${ver}`);
56
+ foundPm = true;
57
+ break;
58
+ } catch { /* try next */ }
59
+ }
60
+ if (!foundPm) {
61
+ fail('No package manager found (npm/yarn/pnpm)');
62
+ allGood = false;
63
+ }
64
+
65
+ // 4. API keys
66
+ const apiKeys = [
67
+ { name: 'Anthropic API key', env: 'ANTHROPIC_API_KEY', required: false }, // praxis-ignore — env var names in diagnostic check, no key values
68
+ { name: 'OpenAI API key', env: 'OPENAI_API_KEY', required: false }, // praxis-ignore — env var name in diagnostic check, no key value
69
+ { name: 'Google AI API key', env: 'GOOGLE_API_KEY', required: false },
70
+ ];
71
+ for (const key of apiKeys) {
72
+ if (process.env[key.env]) {
73
+ pass(`${key.name} configured`);
74
+ } else {
75
+ info(`${key.name} not set (optional — for AI classification)`);
76
+ }
77
+ }
78
+
79
+ // 5. .praxisignore
80
+ const cwd = process.cwd();
81
+ const ignorePath = path.join(cwd, '.praxisignore');
82
+ if (fs.existsSync(ignorePath)) {
83
+ try {
84
+ const patterns = fs.readFileSync(ignorePath, 'utf-8')
85
+ .split('\n').filter(l => l.trim() && !l.startsWith('#')).length;
86
+ pass(`.praxisignore found (${patterns} patterns)`);
87
+ } catch {
88
+ pass('.praxisignore found');
89
+ }
90
+ } else {
91
+ info('.praxisignore not found (run: praxis init)');
92
+ }
93
+
94
+ // 6. Cache directory
95
+ const cacheDir = path.join(cwd, '.praxis');
96
+ if (fs.existsSync(cacheDir)) {
97
+ try {
98
+ const testFile = path.join(cacheDir, '.doctor-test');
99
+ fs.writeFileSync(testFile, 'test');
100
+ fs.unlinkSync(testFile);
101
+ pass('Cache directory writable');
102
+ } catch {
103
+ fail('Cache directory not writable');
104
+ allGood = false;
105
+ }
106
+ } else {
107
+ info('Cache directory does not exist yet (created on first scan)');
108
+ }
109
+
110
+ // 7. Built-in agents & custom plugins
111
+ try {
112
+ const orchestrator = await buildOrchestratorAsync(cwd, { quiet: true });
113
+ const count = orchestrator.agents?.length ?? 0;
114
+ pass(`All ${count} built-in and plugin agents loaded successfully`);
115
+ } catch (err) {
116
+ fail(`Failed to load agents or plugins: ${err.message}`);
117
+ allGood = false;
118
+ }
119
+
120
+ // 8. Intel sources connectivity
121
+ try {
122
+ const res = await fetch('https://api.osv.dev/v1/vulns/test', {
123
+ signal: AbortSignal.timeout(3000)
124
+ });
125
+ if (res.status === 200 || res.status === 404) {
126
+ pass('Intel source (OSV) is reachable');
127
+ } else {
128
+ fail(`Intel source (OSV) returned HTTP ${res.status}`);
129
+ allGood = false;
130
+ }
131
+ } catch (err) {
132
+ fail(`Intel source (OSV) is unreachable: ${err.message}`);
133
+ allGood = false;
134
+ }
135
+
136
+ // 9. LLM connection test (if configured)
137
+ const provider = autoDetectProvider(cwd);
138
+ if (provider) {
139
+ try {
140
+ const response = await provider.complete(
141
+ "Connection check",
142
+ "Say: connection ok"
143
+ );
144
+ if (response && response.toLowerCase().includes("ok")) {
145
+ pass(`${provider.name} LLM responding successfully`);
146
+ } else {
147
+ fail(`${provider.name} LLM returned unexpected response: ${response}`);
148
+ allGood = false;
149
+ }
150
+ } catch (err) {
151
+ fail(`${provider.name} LLM connection check failed: ${err.message}`);
152
+ allGood = false;
153
+ }
154
+ } else {
155
+ info('No LLM provider configured. Skipping LLM connectivity test.');
156
+ }
157
+
158
+ // 10. Version check
159
+ pass(`praxis v${PACKAGE_VERSION}`);
160
+ try {
161
+ const latest = execFileSync('npm', ['view', 'praxis', 'version'], {
162
+ encoding: 'utf-8', timeout: 5000, shell: true,
163
+ }).trim();
164
+ if (latest && latest !== PACKAGE_VERSION && isNewerVersion(latest, PACKAGE_VERSION)) {
165
+ const msg = ['v', latest, ' available (current: v', PACKAGE_VERSION, ')'].join('');
166
+ info(msg);
167
+ } else if (latest) {
168
+ pass('Up to date');
169
+ }
170
+ } catch {
171
+ // Skip version check if npm view fails
172
+ }
173
+
174
+ console.log();
175
+ if (allGood) {
176
+ console.log(chalk.green.bold(' All checks passed!'));
177
+ } else {
178
+ console.log(chalk.yellow.bold(' Some checks failed. See above for details.'));
179
+ }
180
+ console.log();
181
+ }
182
+
183
+ function pass(msg) {
184
+ console.log(chalk.green(' ✔ ') + chalk.white(msg));
185
+ }
186
+
187
+ function fail(msg) {
188
+ console.log(chalk.red(' ✗ ') + chalk.red(msg));
189
+ }
190
+
191
+ function info(msg) {
192
+ console.log(chalk.gray(' ○ ') + chalk.gray(msg));
193
+ }
194
+
195
+ export default doctorCommand;