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,866 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Praxis CLI
5
+ * ==========
6
+ *
7
+ * Canonical entry point. The 38 legacy `praxis` commands are reorganized
8
+ * into six verb-led groups so a new user reading `--help` builds an accurate
9
+ * mental model in 30 seconds:
10
+ *
11
+ * praxis scan run security scans
12
+ * praxis fix apply remediations
13
+ * praxis agents audit AI/agent surface (skills, MCP, attestation, BOM)
14
+ * praxis intel threat-feed + advisory operations
15
+ * praxis report format/diff/share existing scan results
16
+ * praxis project init, hooks, watch, doctor, baseline, plugins, etc.
17
+ *
18
+ * Plus three top-level shortcuts that preserve the indie/vibe-coder voice:
19
+ * praxis vibe emoji-graded score (A=immaculate, F=cooked)
20
+ * praxis score numeric 0-100 score
21
+ * praxis (no args) drops into the interactive REPL
22
+ *
23
+ * The legacy `praxis` binary continues to expose all 38 original commands
24
+ * unchanged for back-compat — that surface lives in `cli/bin/praxis.js`.
25
+ */
26
+
27
+ import { program } from 'commander';
28
+ import chalk from 'chalk';
29
+ import { fileURLToPath } from 'url';
30
+ import { dirname, resolve } from 'path';
31
+
32
+ import { printBanner, TAGLINE, SUBTITLE } from '../core/branding.js';
33
+ import { toolVersion } from '../core/version.js';
34
+ import { loadDotEnv } from '../core/env.js';
35
+
36
+ // Load .env from the working directory early so API keys and LLM settings
37
+ // (OPENAI_API_KEY, OPENAI_BASE_URL, PRAXIS_LLM_MODEL, ...) are available to
38
+ // every command. Real environment variables always take precedence.
39
+ loadDotEnv(process.cwd());
40
+
41
+ // Existing command implementations (unchanged — praxis is a new surface,
42
+ // not a reimplementation).
43
+ import { scanCommand } from '../commands/scan.js';
44
+ import { checklistCommand } from '../commands/checklist.js';
45
+ import { initCommand } from '../commands/init.js';
46
+ import { fixCommand as legacyFixCommand } from '../commands/fix.js';
47
+ import { guardCommand } from '../commands/guard.js';
48
+ import { mcpCommand } from '../commands/mcp.js';
49
+ import { remediateCommand } from '../commands/remediate.js';
50
+ import { rotateCommand } from '../commands/rotate.js';
51
+ import { agentFixCommand } from '../commands/agent-fix.js';
52
+ import { undoCommand } from '../commands/undo.js';
53
+ import { shellCommand } from '../commands/shell.js';
54
+ import { depsCommand } from '../commands/deps.js';
55
+ import { scoreCommand } from '../commands/score.js';
56
+ import { redTeamCommand } from '../commands/red-team.js';
57
+ import { watchCommand } from '../commands/watch.js';
58
+ import { auditCommand } from '../commands/audit.js';
59
+ import { doctorCommand } from '../commands/doctor.js';
60
+ import { baselineCommand } from '../commands/baseline.js';
61
+ import { ciCommand } from '../commands/ci.js';
62
+ import { diffCommand } from '../commands/diff.js';
63
+ import { vibeCheckCommand } from '../commands/vibe-check.js';
64
+ import { webCommand } from '../commands/web.js';
65
+ import { rulesListCommand, rulesExportCommand, rulesImportCommand } from '../commands/rules.js';
66
+ import { benchmarkCommand } from '../commands/benchmark.js';
67
+ import { openclawCommand } from '../commands/openclaw.js';
68
+ import { scanSkillCommand } from '../commands/scan-skill.js';
69
+ import { scanMcpCommand } from '../commands/scan-mcp.js';
70
+ import { scanStandardCommand } from '../commands/scan-standard.js';
71
+ import { abomCommand } from '../commands/abom.js';
72
+ import { updateIntelCommand } from '../commands/update-intel.js';
73
+ import { hooksCommand } from '../commands/hooks.js';
74
+ import { legalCommand } from '../commands/legal.js';
75
+ import { runLiveAdvisories } from '../commands/live-advisories.js';
76
+ import { envAuditCommand } from '../commands/env-audit.js';
77
+ import { autofixCommand } from '../commands/autofix.js';
78
+ import { teamReportCommand } from '../commands/team-report.js';
79
+ import { redteamCommand } from '../commands/redteam.js';
80
+ import { memoryCommand } from '../utils/security-memory.js';
81
+ import { playbookCommand } from '../utils/scan-playbook.js';
82
+ import { listPluginFiles, scaffoldPlugin } from '../utils/plugin-loader.js';
83
+ import { SBOMGenerator } from '../agents/sbom-generator.js';
84
+ import { PolicyEngine } from '../agents/policy-engine.js';
85
+
86
+ // =============================================================================
87
+ // PROGRAM SETUP
88
+ // =============================================================================
89
+
90
+ const __filename = fileURLToPath(import.meta.url);
91
+ const __dirname = dirname(__filename);
92
+ const VERSION = toolVersion();
93
+
94
+ program
95
+ .name('praxis')
96
+ .description(`${TAGLINE} ${SUBTITLE}`)
97
+ .version(VERSION)
98
+ .addHelpText('before', renderHelpBanner());
99
+
100
+ function renderHelpBanner() {
101
+ return [
102
+ '',
103
+ chalk.cyan('██████╗ ██████╗ █████╗ ██╗ ██╗██╗███████╗'),
104
+ chalk.cyan('██╔══██╗██╔══██╗██╔══██╗╚██╗██╔╝██║██╔════╝'),
105
+ chalk.cyan('██████╔╝██████╔╝███████║ ╚███╔╝ ██║███████╗'),
106
+ chalk.cyan('██╔═══╝ ██╔══██╗██╔══██║ ██╔██╗ ██║╚════██║'),
107
+ chalk.cyan('██║ ██║ ██║██║ ██║██╔╝ ██╗██║███████║'),
108
+ chalk.cyan('╚═╝ ╚═╝ ╚═╝╚═╝ ╚═╝╚═╝ ╚═╝╚═╝╚══════╝'),
109
+ '',
110
+ chalk.white.bold(` ${TAGLINE}`),
111
+ chalk.gray(` ${SUBTITLE}`),
112
+ '',
113
+ ].join('\n');
114
+ }
115
+
116
+ // =============================================================================
117
+ // scan — run security scans
118
+ // =============================================================================
119
+
120
+ const scan = program
121
+ .command('scan')
122
+ .description('Run security scans (default = full audit)');
123
+
124
+ scan
125
+ .command('full [path]', { isDefault: true })
126
+ .description('Full audit: secrets + 28 agents + deps + score + remediation plan')
127
+ .option('--json', 'Output results as JSON')
128
+ .option('--sarif', 'Output results in SARIF format')
129
+ .option('--csv', 'Output results as CSV')
130
+ .option('--md', 'Output results as Markdown')
131
+ .option('--html [file]', 'HTML report path (default: praxis-report.html)')
132
+ .option('--html-dir [dir]', 'Generate granular multi-page report suite in directory')
133
+ .option('--compare', 'Show detailed comparison with last scan')
134
+ .option('--timeout <ms>', 'Per-agent timeout in milliseconds (default: 30000)', parseInt)
135
+ .option('--no-deps', 'Skip dependency audit')
136
+ .option('--no-ai', 'Skip AI classification')
137
+ .option('--no-cache', 'Force full rescan')
138
+ .option('--baseline', 'Only show findings not in the baseline')
139
+ .option('--pdf [file]', 'Generate PDF report (requires Chrome/Chromium)')
140
+ .option('--deep', 'LLM-powered taint analysis for critical/high findings')
141
+ .option('--think', 'Enable extended thinking mode')
142
+ .option('--local', 'Use local Ollama model for deep analysis')
143
+ .option('--model <model>', 'LLM model for deep/AI analysis')
144
+ .option('--provider <name>', 'LLM provider')
145
+ .option('--base-url <url>', 'Custom OpenAI-compatible endpoint')
146
+ .option('--budget <cents>', 'Max spend in cents for deep analysis (default: 50)', parseInt)
147
+ .option('--verify', 'Check if leaked secrets are still active')
148
+ .option('--include-legal', 'Also run the legal risk scan')
149
+ .option('--agentic [iterations]', 'Agentic scan→fix→verify loop', (v) => v ? parseInt(v) : true)
150
+ .option('--agentic-target <score>', 'Target security score for agentic loop', parseInt)
151
+ .option('--hermes-only', 'Run only Hermes-relevant agents')
152
+ .option('--fail-below <threshold>', 'Exit 1 if score is below threshold')
153
+ .option('-v, --verbose', 'Verbose output')
154
+ .action(auditCommand);
155
+
156
+ scan
157
+ .command('secrets [path]')
158
+ .description('Fast pattern-based secret scan only (no agents)')
159
+ .option('-v, --verbose', 'Show all files being scanned')
160
+ .option('--no-color', 'Disable colored output')
161
+ .option('--json', 'Output results as JSON')
162
+ .option('--sarif', 'Output results in SARIF format')
163
+ .option('--include-tests', 'Also scan test files')
164
+ .option('--no-cache', 'Force full rescan')
165
+ .action(scanCommand);
166
+
167
+ scan
168
+ .command('changed [ref]')
169
+ .description('Scan only files changed since <ref> (default: HEAD)')
170
+ .option('--staged', 'Scan only staged changes')
171
+ .option('--json', 'Output results as JSON')
172
+ .option('-p, --path <path>', 'Project path (default: cwd)')
173
+ .option('--timeout <ms>', 'Per-agent timeout in milliseconds', parseInt)
174
+ .action(diffCommand);
175
+
176
+ scan
177
+ .command('env [path]')
178
+ .description('Credential health check: .env coverage, source cross-ref, git history')
179
+ .option('--json', 'Output results as JSON')
180
+ .action(envAuditCommand);
181
+
182
+ scan
183
+ .command('redteam [path]')
184
+ .description('28 agents in parallel — adversarial scan of 80+ attack classes')
185
+ .option('--agents <list>', 'Comma-separated list of agents to run')
186
+ .option('--json', 'Output results as JSON')
187
+ .option('--sarif', 'Output results in SARIF format')
188
+ .option('--html [file]', 'Generate HTML security report')
189
+ .option('--sbom [file]', 'Generate CycloneDX SBOM')
190
+ .option('--no-deps', 'Skip dependency audit')
191
+ .option('--no-ai', 'Skip AI classification')
192
+ .option('--deep', 'LLM-powered taint analysis')
193
+ .option('--swarm', 'AI swarm mode — 23 parallel agents via DeepSeek/Kimi')
194
+ .option('--think', 'Enable extended thinking mode')
195
+ .option('--local', 'Use local Ollama model')
196
+ .option('--model <model>', 'LLM model')
197
+ .option('--provider <name>', 'LLM provider')
198
+ .option('--base-url <url>', 'Custom OpenAI-compatible endpoint')
199
+ .option('--budget <cents>', 'Max spend in cents', parseInt)
200
+ .option('-v, --verbose', 'Verbose output')
201
+ .action(redTeamCommand);
202
+
203
+ scan
204
+ .command('standard [name] [path]')
205
+ .description('Filter findings by AI-security standard (owasp-llm, mitre-atlas, ...)')
206
+ .option('--list', 'List available standards and exit')
207
+ .option('--control <id>', 'Filter to a single control within the standard (e.g., LLM01)')
208
+ .option('--json', 'Output results as JSON')
209
+ .option('--sarif', 'Output results in SARIF format')
210
+ .option('--format <name>', 'Output format from the registry (json, sarif)')
211
+ .action((name, targetPath, options) => scanStandardCommand(name, targetPath, options));
212
+
213
+ scan
214
+ .command('ci [path]')
215
+ .description('CI/CD pipeline mode: scan, score, exit 1 on failure')
216
+ .option('--threshold <score>', 'Minimum passing score (default: 75)', parseInt)
217
+ .option('--fail-on <severity>', 'Fail on findings at this severity or above')
218
+ .option('--always-fail-on <severity>', 'Severity floor that even an accepted baseline cannot suppress')
219
+ .option('--include-findings', 'Include the finding list (file/rule/severity) in JSON output for diffing')
220
+ .option('--sarif <file>', 'Write SARIF output for GitHub Code Scanning')
221
+ .option('--json', 'JSON output')
222
+ .option('--no-deps', 'Skip dependency audit')
223
+ .option('--baseline', 'Only check new findings (not in baseline)')
224
+ .option('--github-pr', 'Post findings as a GitHub PR comment')
225
+ .option('--strict-intel', 'Fail if threat-intel feed is stale')
226
+ .option('--max-intel-age <duration>', 'Max acceptable intel age', '7d')
227
+ .action(ciCommand);
228
+
229
+ // =============================================================================
230
+ // fix — apply remediations
231
+ // =============================================================================
232
+
233
+ const fix = program
234
+ .command('fix')
235
+ .description('Apply remediations (LLM-guided, deterministic, or PR-based)');
236
+
237
+ fix
238
+ .command('interactive [path]', { isDefault: true })
239
+ .description('Interactive LLM-guided fixes: scan → plan → diff → ask → apply → verify')
240
+ .option('--plan-only', 'Generate plans for review but never write changes')
241
+ .option('--severity <level>', 'Minimum severity to fix', 'low')
242
+ .option('--provider <name>', 'LLM provider')
243
+ .option('--model <model>', 'Specific model name')
244
+ .option('--think', 'Enable extended thinking')
245
+ .option('--allow-dirty', 'Allow running with uncommitted changes')
246
+ .option('--branch [name]', 'Create a branch and commit one fix per file')
247
+ .option('--pr', 'Push branch and open a PR via gh CLI (requires --branch)')
248
+ .option('--yolo', 'Auto-accept every plan without prompting')
249
+ .option('--auto-low', 'Auto-accept plans marked risk:low')
250
+ .option('--sandbox', 'Verify each fix in a Docker sandbox')
251
+ .option('--max-attempts <n>', 'Max plan attempts per file (verification ladder retries)', parseInt)
252
+ .option('--ci', 'Non-interactive CI/CD mode (auto-accept fixes)')
253
+ .action(agentFixCommand);
254
+
255
+ fix
256
+ .command('quick [path]')
257
+ .description('Deterministic secret fixer: rewrite source + write .env (no LLM)')
258
+ .option('--dry-run', 'Preview changes without writing')
259
+ .option('--yes', 'Apply all fixes without prompting')
260
+ .option('--stage', 'Run git add on modified files')
261
+ .option('--all', 'Also fix common agent findings (debug, TLS bypass, shell injection)')
262
+ .action(remediateCommand);
263
+
264
+ fix
265
+ .command('from-report [path]')
266
+ .description('Apply LLM fixes from a deep-analysis JSON report and open a PR')
267
+ .option('--report <file>', 'Path to praxis JSON report')
268
+ .option('--severity <level>', 'Minimum severity to fix')
269
+ .option('--dry-run', 'Preview without applying')
270
+ .option('--yes', 'Skip confirmation')
271
+ .action((targetPath, options) => autofixCommand({ ...options, path: targetPath }));
272
+
273
+ fix
274
+ .command('rotate [path]')
275
+ .description('Open provider dashboards to revoke exposed secrets')
276
+ .option('--provider <name>', 'Only rotate secrets for a specific provider')
277
+ .option('--plan <file>', 'Execute a rotation plan')
278
+ .action(rotateCommand);
279
+
280
+ fix
281
+ .command('undo [path]')
282
+ .description('Revert the last fix applied by `praxis fix interactive` (or all with --all)')
283
+ .option('--all', 'Revert every fix in the log')
284
+ .option('--dry-run', 'Show what would be reverted')
285
+ .action(undoCommand);
286
+
287
+ fix
288
+ .command('env-template')
289
+ .description('Generate a .env.example with placeholder values from found secrets')
290
+ .option('--dry-run', 'Preview without writing')
291
+ .action(legacyFixCommand);
292
+
293
+ // =============================================================================
294
+ // agents — AI-agent surface security
295
+ // =============================================================================
296
+
297
+ const agents = program
298
+ .command('agents')
299
+ .description('Audit the AI agent surface (configs, skills, MCP, attestation)');
300
+
301
+ agents
302
+ .command('audit [path]', { isDefault: true })
303
+ .description('Audit agent configs (CLAUDE.md, .cursorrules, MCP servers, skills)')
304
+ .option('--fix', 'Auto-harden agent configurations')
305
+ .option('--preflight', 'Exit non-zero on critical findings (for CI)')
306
+ .option('--red-team', 'Simulate adversarial attacks against agent configs')
307
+ .option('--json', 'Output results as JSON')
308
+ .action(openclawCommand);
309
+
310
+ agents
311
+ .command('skill [target]')
312
+ .description('Vet an AI agent skill before installing it')
313
+ .option('--all', 'Scan all skills defined in openclaw.json')
314
+ .option('--json', 'Output results as JSON')
315
+ .action(scanSkillCommand);
316
+
317
+ agents
318
+ .command('mcp [target]')
319
+ .description("Vet an MCP server's tool manifest before connecting")
320
+ .option('--test-live', 'Perform runtime JSON-RPC handshakes and tool fuzzing')
321
+ .option('--json', 'Output results as JSON')
322
+ .action(scanMcpCommand);
323
+
324
+ agents
325
+ .command('bom [path]')
326
+ .description('Generate Agent Bill of Materials (CycloneDX ABOM)')
327
+ .option('-o, --output <file>', 'Output file path', 'abom.json')
328
+ .option('--json', 'Output to stdout as JSON')
329
+ .action(abomCommand);
330
+
331
+ agents
332
+ .command('serve')
333
+ .description('Start praxis as an MCP server (Claude Desktop, Cursor, Windsurf)')
334
+ .action(mcpCommand);
335
+
336
+ program
337
+ .command('redteam [endpoint]')
338
+ .description('Dynamic AI red teaming & behavioral prober for live LLM endpoints (DAST)')
339
+ .option('--header <header>', 'Custom HTTP header (can specify multiple)')
340
+ .option('--timeout <ms>', 'Timeout in ms (default: 10000)')
341
+ .option('--json', 'Output results as JSON')
342
+ .action(redteamCommand);
343
+
344
+ // =============================================================================
345
+ // intel — threat-intelligence operations
346
+ // =============================================================================
347
+
348
+ const intel = program
349
+ .command('intel')
350
+ .description('Threat-intelligence: feed updates, advisories, dependency CVEs');
351
+
352
+ intel
353
+ .command('update')
354
+ .description('Refresh threat intel: OSV, GHSA, KEV, EPSS, NVD, Gitleaks, +paid')
355
+ .option('--only <sources>', 'Comma-separated subset (e.g. osv,kev,epss)')
356
+ .option('--force', 'Ignore per-source TTL caches')
357
+ .option('--list', 'Print available sources and exit')
358
+ .action(updateIntelCommand);
359
+
360
+ intel
361
+ .command('deps [path]')
362
+ .description('Audit deps via package manager (npm/yarn/pnpm/pip-audit/bundler-audit)')
363
+ .option('--fix', 'Run package manager fix command after auditing')
364
+ .action(depsCommand);
365
+
366
+ intel
367
+ .command('advisories [path]')
368
+ .description('Check deps against live advisory feeds (OSV.dev, GitHub Advisories)')
369
+ .option('--ecosystem <type>', 'Filter by ecosystem (npm, PyPI)')
370
+ .option('--json', 'Output as JSON')
371
+ .action(async (targetPath = '.', options) => {
372
+ const absolutePath = resolve(targetPath);
373
+ try {
374
+ const result = await runLiveAdvisories(absolutePath, options);
375
+ if (options.json) {
376
+ console.log(JSON.stringify(result, null, 2));
377
+ return;
378
+ }
379
+ console.log();
380
+ console.log(chalk.cyan.bold(' Praxis — Live Advisories'));
381
+ console.log(chalk.gray(` Checked ${result.checked} dependencies against OSV.dev`));
382
+ console.log();
383
+ if (result.advisories.length === 0) {
384
+ console.log(chalk.green(' ✔ No known advisories for your current dependency versions.\n'));
385
+ } else {
386
+ const malware = result.advisories.filter(a => a.isMalware);
387
+ const vulns = result.advisories.filter(a => !a.isMalware);
388
+ if (malware.length > 0) {
389
+ console.log(chalk.red.bold(` !! ${malware.length} MALWARE ADVISORY(S) FOUND`));
390
+ for (const a of malware) {
391
+ console.log(chalk.red(` ${a.package}@${a.version} — ${a.id}: ${a.summary.slice(0, 80)}`));
392
+ }
393
+ console.log();
394
+ }
395
+ if (vulns.length > 0) {
396
+ console.log(chalk.yellow(` ${vulns.length} vulnerability advisory(s):`));
397
+ for (const a of vulns) {
398
+ const sev = a.severity === 'critical' ? chalk.red.bold(a.severity)
399
+ : a.severity === 'high' ? chalk.yellow(a.severity)
400
+ : chalk.blue(a.severity);
401
+ console.log(` ${sev} ${a.package}@${a.version} — ${a.id}`);
402
+ }
403
+ console.log();
404
+ }
405
+ }
406
+ } catch (err) {
407
+ console.error(chalk.red(` Error: ${err.message}\n`));
408
+ process.exit(1);
409
+ }
410
+ });
411
+
412
+ // =============================================================================
413
+ // report — format & share scan results
414
+ // =============================================================================
415
+
416
+ const report = program
417
+ .command('report')
418
+ .description('Format, diff, or share existing scan results');
419
+
420
+ report
421
+ .command('team [file]')
422
+ .description('Convert Hermes Agent team output into a Praxis report')
423
+ .option('--html [path]', 'Save as HTML report')
424
+ .option('--json', 'JSON output')
425
+ .action(teamReportCommand);
426
+
427
+ report
428
+ .command('legal [path]')
429
+ .description('Legal risk audit: DMCA, leaked-source derivatives, IP disputes')
430
+ .option('--json', 'Output results as JSON')
431
+ .action(legalCommand);
432
+
433
+ report
434
+ .command('checklist')
435
+ .description('Run the launch-day security checklist interactively')
436
+ .option('--no-interactive', 'Print checklist without prompts')
437
+ .action(checklistCommand);
438
+
439
+ report
440
+ .command('sbom [path]')
441
+ .description('Generate Software Bill of Materials (CycloneDX SBOM)')
442
+ .option('-o, --output <file>', 'Output file path', 'sbom.json')
443
+ .action((targetPath = '.', options) => {
444
+ const absolutePath = resolve(targetPath);
445
+ const sbom = new SBOMGenerator();
446
+ sbom.generateToFile(absolutePath, options.output);
447
+ console.log(chalk.green(`✔ SBOM saved to ${options.output}`));
448
+ });
449
+
450
+ report
451
+ .command('benchmark [path]')
452
+ .description('Compare your security score against industry averages')
453
+ .option('--json', 'Output results as JSON')
454
+ .action(benchmarkCommand);
455
+
456
+ // =============================================================================
457
+ // project — setup & state
458
+ // =============================================================================
459
+
460
+ const project = program
461
+ .command('project')
462
+ .description('Project setup, hooks, watch, doctor, baseline, plugins, policies');
463
+
464
+ project
465
+ .command('init')
466
+ .description('Initialize security configs in your project')
467
+ .option('-f, --force', 'Overwrite existing files')
468
+ .option('--gitignore', 'Only copy .gitignore')
469
+ .option('--headers', 'Only copy security headers config')
470
+ .option('--agents', 'Only add security rules to AI agent instruction files')
471
+ .option('--openclaw', 'Generate a hardened openclaw.json template')
472
+ .option('--hermes', 'Bootstrap Hermes Agent security config')
473
+ .option('--from <url>', 'Fetch a pre-built Hermes config bundle from a setup URL')
474
+ .action(initCommand);
475
+
476
+ project
477
+ .command('doctor')
478
+ .description('Diagnose environment: Node.js, git, API keys, cache, dependencies')
479
+ .action(doctorCommand);
480
+
481
+ project
482
+ .command('hooks [action]')
483
+ .description('Manage Claude Code hooks — real-time security gate on tool calls')
484
+ .action(hooksCommand);
485
+
486
+ project
487
+ .command('guard [action]')
488
+ .description('Install pre-commit/pre-push git hook to block secret commits')
489
+ .option('--pre-commit', 'Install as pre-commit hook instead of pre-push')
490
+ .option('--generate-hooks', 'Generate defensive Claude Code hooks')
491
+ .action(guardCommand);
492
+
493
+ project
494
+ .command('watch [path]')
495
+ .description('Continuous monitoring: watch files for security issues in real-time')
496
+ .option('--poll', 'Use polling mode')
497
+ .option('--configs', 'Watch only agent config files')
498
+ .option('--deep', 'Run full agent scanning on changes')
499
+ .option('--stateful', 'Keep Kimi K2.6 conversation context between scans')
500
+ .option('--model <model>', 'LLM model for stateful watch')
501
+ .option('--provider <name>', 'LLM provider for stateful watch')
502
+ .option('--status', 'Show current watch status and exit')
503
+ .option('--threshold <score>', 'Alert when score drops below threshold', parseInt)
504
+ .option('--debounce <ms>', 'Debounce interval in ms', parseInt)
505
+ .option('--slack [webhook]', 'Post findings to Slack webhook URL')
506
+ .option('--pr-comment', 'Post inline findings as GitHub PR review comments')
507
+ .action(watchCommand);
508
+
509
+ project
510
+ .command('baseline [path]')
511
+ .description('Create/manage a findings baseline — only report new findings')
512
+ .option('--diff', 'Show what changed since baseline')
513
+ .option('--clear', 'Remove the baseline')
514
+ .action(baselineCommand);
515
+
516
+ project
517
+ .command('memory [subcommand]')
518
+ .description('Manage false-positive memory (list / forget <key> / clear)')
519
+ .argument('[args...]')
520
+ .action((subcommand, args, options) => memoryCommand(subcommand, args, options));
521
+
522
+ project
523
+ .command('playbook [subcommand]')
524
+ .description('Manage repo-specific LLM context playbook (show / add-note "text")')
525
+ .argument('[args...]')
526
+ .action((subcommand, args, options) => playbookCommand(subcommand, args, options));
527
+
528
+ project
529
+ .command('plugins [action]')
530
+ .description('Manage custom security agent plugins from .praxis/agents/')
531
+ .argument('[args...]')
532
+ .action((action, args, options) => {
533
+ const rootPath = resolve(process.cwd());
534
+ if (action === 'new') {
535
+ const pluginName = args?.[0] || 'my-rule';
536
+ try {
537
+ const filePath = scaffoldPlugin(rootPath, pluginName);
538
+ console.log(chalk.green(` ✔ Plugin scaffolded: ${filePath}`));
539
+ } catch (err) {
540
+ console.error(chalk.red(` Error: ${err.message}`));
541
+ process.exit(1);
542
+ }
543
+ } else {
544
+ const plugins = listPluginFiles(rootPath);
545
+ if (plugins.length === 0) {
546
+ console.log('\n No custom plugins found in .praxis/agents/');
547
+ console.log(chalk.gray(' Create one with: praxis project plugins new my-rule\n'));
548
+ } else {
549
+ console.log(`\n ${chalk.cyan.bold('Custom Plugins')} — ${plugins.length} found\n`);
550
+ for (const p of plugins) {
551
+ console.log(` ${chalk.white(p.name)} ${chalk.gray(`(${(p.size / 1024).toFixed(1)} KB) ${p.path}`)}`);
552
+ }
553
+ console.log();
554
+ }
555
+ }
556
+ });
557
+
558
+ project
559
+ .command('policy <action>')
560
+ .description('Manage security policies (init: create policy template)')
561
+ .action((action) => {
562
+ if (action === 'init') {
563
+ const policyPath = PolicyEngine.generateTemplate(process.cwd());
564
+ console.log(chalk.green(`✔ Policy template created: ${policyPath}`));
565
+ console.log(chalk.gray(' Edit .praxis.policy.json to configure your security policy.'));
566
+ } else {
567
+ console.log(chalk.yellow(`Unknown policy action: ${action}. Use: policy init`));
568
+ }
569
+ });
570
+
571
+ // =============================================================================
572
+ // Top-level shortcuts (preserve indie voice)
573
+ // =============================================================================
574
+
575
+ const rules = program.command('rules').description('Inspect and export Praxis detection rules');
576
+
577
+ rules
578
+ .command('list')
579
+ .description('Summarise the rule inventory by source, severity and portability')
580
+ .option('--json', 'Output results as JSON')
581
+ .action(rulesListCommand);
582
+
583
+ rules
584
+ .command('export')
585
+ .description('Write a Semgrep-compatible rule bundle plus a portability manifest')
586
+ .option('-o, --out <dir>', 'Output directory', 'praxis-rules')
587
+ .option('--allow-invalid', 'Export even if some patterns fail validation', false)
588
+ .action(rulesExportCommand);
589
+
590
+ rules
591
+ .command('import <bundle>')
592
+ .description('Load a Praxis portable rule bundle (JSON) and optionally emit a runnable plugin')
593
+ .option('--write-plugin <dir>', 'Write a runnable plugin to this directory (e.g. .praxis/agents)')
594
+ .option('--name <name>', 'Plugin class name prefix', 'PortableRules')
595
+ .option('--json', 'Output results as JSON')
596
+ .action(rulesImportCommand);
597
+
598
+ program
599
+ .command('web')
600
+ .description('Local web UI for scans and scan projects (read-only; loopback by default)')
601
+ .option('--port <port>', 'Port to listen on', '7317')
602
+ .option('--host <host>', 'Host to bind (non-loopback requires --allow-remote and --token)', '127.0.0.1')
603
+ .option('--allow-remote', 'Permit a non-loopback bind (requires --token)', false)
604
+ .option('--token <token>', 'Bearer token required for every request when remotely bound')
605
+ .action(webCommand);
606
+
607
+ program
608
+ .command('vibe [path]')
609
+ .description('Vibe-graded security score with emoji and shareable badge')
610
+ .option('--badge', 'Generate a shields.io markdown badge for your README')
611
+ .action(vibeCheckCommand);
612
+
613
+ program
614
+ .command('score [path]')
615
+ .description('Compute a 0-100 security health score for your project')
616
+ .option('--no-deps', 'Skip dependency audit')
617
+ .action(scoreCommand);
618
+
619
+ program
620
+ .command('hooks [action]')
621
+ .description('Manage Claude Code hooks — real-time security gate on tool calls')
622
+ .action(hooksCommand);
623
+
624
+ // =============================================================================
625
+ // Legacy top-level surface (back-compat)
626
+ // =============================================================================
627
+ // The original flat `praxis` command set is re-exposed here so existing docs,
628
+ // scripts, GitHub Actions, and Claude Code skills keep working unchanged.
629
+
630
+ const legacy = (name, description, flags) => {
631
+ const cmd = program.command(name).description(description);
632
+ for (const [flag, help, parse] of flags) {
633
+ if (parse) cmd.option(flag, help, parse);
634
+ else cmd.option(flag, help);
635
+ }
636
+ return cmd;
637
+ };
638
+
639
+ legacy('ci [path]', 'CI/CD mode: scan, score, exit 1 on failure (alias of `scan ci`)', [
640
+ ['--threshold <score>', 'Minimum passing score (default: 75)', parseInt],
641
+ ['--fail-on <severity>', 'Fail on findings at this severity or above'],
642
+ ['--always-fail-on <severity>', 'Severity floor that even an accepted baseline cannot suppress'],
643
+ ['--include-findings', 'Include the finding list (file/rule/severity) in JSON output for diffing'],
644
+ ['--sarif <file>', 'Write SARIF output for GitHub Code Scanning'],
645
+ ['--json', 'JSON output'],
646
+ ['--no-deps', 'Skip dependency audit'],
647
+ ['--baseline', 'Only check new findings (not in baseline)'],
648
+ ['--github-pr', 'Post findings as a GitHub PR comment'],
649
+ ['--strict-intel', 'Fail if threat-intel feed is stale'],
650
+ ['--max-intel-age <duration>', 'Max acceptable intel age', undefined],
651
+ ]).action(ciCommand);
652
+
653
+ legacy('audit [path]', 'Audit agent configs (CLAUDE.md, .cursorrules, MCP, skills) — alias of `agents audit`', [
654
+ ['--fix', 'Auto-harden agent configurations'],
655
+ ['--preflight', 'Exit non-zero on critical findings (for CI)'],
656
+ ['--red-team', 'Simulate adversarial attacks against agent configs'],
657
+ ['--json', 'Output results as JSON'],
658
+ ]).action(openclawCommand);
659
+
660
+ legacy('openclaw [path]', 'Agent-config security audit (alias of `agents audit`)', [
661
+ ['--fix', 'Auto-harden agent configurations'],
662
+ ['--preflight', 'Exit non-zero on critical findings (for CI)'],
663
+ ['--red-team', 'Simulate adversarial attacks against agent configs'],
664
+ ['--json', 'Output results as JSON'],
665
+ ]).action(openclawCommand);
666
+
667
+ legacy('scan-mcp [target]', 'Vet an MCP server tool manifest (alias of `agents mcp`)', [
668
+ ['--json', 'Output results as JSON'],
669
+ ]).action(scanMcpCommand);
670
+
671
+ legacy('mcp', 'Start praxis as an MCP server over stdio (alias of `agents serve`)', [])
672
+ .action(mcpCommand);
673
+
674
+ legacy('scan-skill [target]', 'Vet an AI agent skill (alias of `agents skill`)', [
675
+ ['--all', 'Scan all skills defined in openclaw.json'],
676
+ ['--json', 'Output results as JSON'],
677
+ ]).action(scanSkillCommand);
678
+
679
+ legacy('scan-standard [name] [path]', 'Filter findings by AI-security standard (alias of `scan standard`)', [
680
+ ['--list', 'List available standards and exit'],
681
+ ['--control <id>', 'Filter to a single control within the standard'],
682
+ ['--json', 'Output results as JSON'],
683
+ ['--sarif', 'Output results in SARIF format'],
684
+ ['--format <name>', 'Output format from the registry (json, sarif)'],
685
+ ]).action((name, targetPath, options) => scanStandardCommand(name, targetPath, options));
686
+
687
+ legacy('update-intel', 'Refresh threat intel feeds (alias of `intel update`)', [
688
+ ['--only <sources>', 'Comma-separated subset (e.g. osv,kev,epss)'],
689
+ ['--force', 'Ignore per-source TTL caches'],
690
+ ['--list', 'Print available sources and exit'],
691
+ ]).action(updateIntelCommand);
692
+
693
+ legacy('deps [path]', 'Audit dependency CVEs (alias of `intel deps`)', [
694
+ ['--fix', 'Run package manager fix command after auditing'],
695
+ ]).action(depsCommand);
696
+
697
+ legacy('advisories [path]', 'Check deps against live advisory feeds (alias of `intel advisories`)', [
698
+ ['--ecosystem <type>', 'Filter by ecosystem (npm, PyPI)'],
699
+ ['--json', 'Output as JSON'],
700
+ ]).action(async (targetPath = '.', options) => {
701
+ const absolutePath = resolve(targetPath);
702
+ try {
703
+ const result = await runLiveAdvisories(absolutePath, options);
704
+ if (options.json) {
705
+ console.log(JSON.stringify(result, null, 2));
706
+ return;
707
+ }
708
+ console.log();
709
+ console.log(chalk.cyan.bold(' Praxis — Live Advisories'));
710
+ console.log(chalk.gray(` Checked ${result.checked} dependencies against OSV.dev`));
711
+ console.log();
712
+ if (result.advisories.length === 0) {
713
+ console.log(chalk.green(' ✔ No known advisories for your current dependency versions.\n'));
714
+ } else {
715
+ const malware = result.advisories.filter(a => a.isMalware);
716
+ const vulns = result.advisories.filter(a => !a.isMalware);
717
+ if (malware.length > 0) {
718
+ console.log(chalk.red.bold(` !! ${malware.length} MALWARE ADVISORY(S) FOUND`));
719
+ for (const a of malware) {
720
+ console.log(chalk.red(` ${a.package}@${a.version} — ${a.id}: ${a.summary.slice(0, 80)}`));
721
+ }
722
+ console.log();
723
+ }
724
+ if (vulns.length > 0) {
725
+ console.log(chalk.yellow(` ${vulns.length} vulnerability advisory(s):`));
726
+ for (const a of vulns) {
727
+ const sev = a.severity === 'critical' ? chalk.red.bold(a.severity)
728
+ : a.severity === 'high' ? chalk.yellow(a.severity)
729
+ : chalk.blue(a.severity);
730
+ console.log(` ${sev} ${a.package}@${a.version} — ${a.id}`);
731
+ }
732
+ console.log();
733
+ }
734
+ }
735
+ } catch (err) {
736
+ console.error(chalk.red(` Error: ${err.message}\n`));
737
+ process.exit(1);
738
+ }
739
+ });
740
+
741
+ legacy('remediate [path]', 'Deterministic secret fixer: rewrite source + write .env (alias of `fix quick`)', [
742
+ ['--dry-run', 'Preview changes without writing'],
743
+ ['--yes', 'Apply all fixes without prompting'],
744
+ ['--stage', 'Run git add on modified files'],
745
+ ['--all', 'Also fix common agent findings'],
746
+ ]).action(remediateCommand);
747
+
748
+ legacy('rotate [path]', 'Open provider dashboards to revoke exposed secrets (alias of `fix rotate`)', [
749
+ ['--provider <name>', 'Only rotate secrets for a specific provider'],
750
+ ['--plan <file>', 'Execute a rotation plan'],
751
+ ]).action(rotateCommand);
752
+
753
+ legacy('undo [path]', 'Revert the last fix (alias of `fix undo`)', [
754
+ ['--all', 'Revert every fix in the log'],
755
+ ['--dry-run', 'Show what would be reverted'],
756
+ ]).action(undoCommand);
757
+
758
+ legacy('env-template', 'Generate .env.example from found secrets (alias of `fix env-template`)', [
759
+ ['--dry-run', 'Preview without writing'],
760
+ ]).action(legacyFixCommand);
761
+
762
+ legacy('red-team [path]', 'Adversarial agent pack scan (alias of `scan redteam`)', [
763
+ ['--agents <list>', 'Comma-separated list of agents to run'],
764
+ ['--json', 'Output results as JSON'],
765
+ ['--sarif', 'Output results in SARIF format'],
766
+ ['--html [file]', 'Generate HTML security report'],
767
+ ['--sbom [file]', 'Generate CycloneDX SBOM'],
768
+ ['--no-deps', 'Skip dependency audit'],
769
+ ['--no-ai', 'Skip AI classification'],
770
+ ['--deep', 'LLM-powered taint analysis'],
771
+ ['--swarm', 'AI swarm mode via DeepSeek/Kimi'],
772
+ ['--think', 'Enable extended thinking mode'],
773
+ ['--local', 'Use local Ollama model'],
774
+ ['--model <model>', 'LLM model'],
775
+ ['--provider <name>', 'LLM provider'],
776
+ ['--base-url <url>', 'Custom OpenAI-compatible endpoint'],
777
+ ['--budget <cents>', 'Max spend in cents', parseInt],
778
+ ['-v, --verbose', 'Verbose output'],
779
+ ]).action(redTeamCommand);
780
+
781
+ legacy('abom [path]', 'Generate Agent Bill of Materials (alias of `agents bom`)', [
782
+ ['-o, --output <file>', 'Output file path', undefined],
783
+ ['--json', 'Output to stdout as JSON'],
784
+ ]).action(abomCommand);
785
+
786
+ legacy('legal [path]', 'Legal risk audit (alias of `report legal`)', [
787
+ ['--json', 'Output results as JSON'],
788
+ ]).action(legalCommand);
789
+
790
+ legacy('team [file]', 'Convert Hermes Agent output into a Praxis report (alias of `report team`)', [
791
+ ['--html [path]', 'Save as HTML report'],
792
+ ['--json', 'JSON output'],
793
+ ]).action(teamReportCommand);
794
+
795
+ legacy('checklist', 'Run the launch-day security checklist (alias of `report checklist`)', [
796
+ ['--no-interactive', 'Print checklist without prompts'],
797
+ ]).action(checklistCommand);
798
+
799
+ legacy('benchmark [path]', 'Compare your score against industry averages (alias of `report benchmark`)', [
800
+ ['--json', 'Output results as JSON'],
801
+ ]).action(benchmarkCommand);
802
+
803
+ legacy('init', 'Initialize security configs in your project (alias of `project init`)', [
804
+ ['-f, --force', 'Overwrite existing files'],
805
+ ['--gitignore', 'Only copy .gitignore'],
806
+ ['--headers', 'Only copy security headers config'],
807
+ ['--agents', 'Only add security rules to AI agent instruction files'],
808
+ ['--openclaw', 'Generate a hardened openclaw.json template'],
809
+ ['--hermes', 'Bootstrap Hermes Agent security config'],
810
+ ['--from <url>', 'Fetch a pre-built Hermes config bundle from a setup URL'],
811
+ ]).action(initCommand);
812
+
813
+ legacy('doctor', 'Diagnose environment (alias of `project doctor`)', []).action(doctorCommand);
814
+
815
+ legacy('baseline [path]', 'Manage findings baseline (alias of `project baseline`)', [
816
+ ['--diff', 'Show what changed since baseline'],
817
+ ['--clear', 'Remove the baseline'],
818
+ ]).action(baselineCommand);
819
+
820
+ legacy('guard [action]', 'Install pre-commit/pre-push secret guard (alias of `project guard`)', [
821
+ ['--pre-commit', 'Install as pre-commit hook instead of pre-push'],
822
+ ['--generate-hooks', 'Generate defensive Claude Code hooks'],
823
+ ]).action(guardCommand);
824
+
825
+ legacy('watch [path]', 'Continuous security monitoring (alias of `project watch`)', [
826
+ ['--poll', 'Use polling mode'],
827
+ ['--configs', 'Watch only agent config files'],
828
+ ['--deep', 'Run full agent scanning on changes'],
829
+ ['--stateful', 'Keep Kimi K2.6 conversation context between scans'],
830
+ ['--model <model>', 'LLM model for stateful watch'],
831
+ ['--provider <name>', 'LLM provider for stateful watch'],
832
+ ['--status', 'Show current watch status and exit'],
833
+ ['--threshold <score>', 'Alert when score drops below threshold', parseInt],
834
+ ['--debounce <ms>', 'Debounce interval in ms', parseInt],
835
+ ['--slack [webhook]', 'Post findings to Slack webhook URL'],
836
+ ['--pr-comment', 'Post inline findings as GitHub PR review comments'],
837
+ ]).action(watchCommand);
838
+
839
+ legacy('shell', 'Interactive REPL (same as `praxis` with no args on a TTY)', [])
840
+ .action(() => shellCommand('.', {}).then(() => process.exit(0)).catch(() => process.exit(1)));
841
+
842
+ // =============================================================================
843
+ // PARSE AND RUN
844
+ // =============================================================================
845
+
846
+ if (process.argv.length === 2 && process.stdin.isTTY) {
847
+ // No args + TTY → interactive REPL.
848
+ shellCommand('.', {}).then(() => process.exit(0)).catch(() => process.exit(1));
849
+ } else if (process.argv.length === 2) {
850
+ printBanner(VERSION);
851
+ console.log(chalk.yellow('Quick start:\n'));
852
+ console.log(chalk.white(' praxis scan . ') + chalk.gray('# Full audit'));
853
+ console.log(chalk.white(' praxis fix . ') + chalk.gray('# Interactive LLM-guided fixes'));
854
+ console.log(chalk.white(' praxis agents audit . ') + chalk.gray('# Audit AI agent surface'));
855
+ console.log(chalk.white(' praxis intel update ') + chalk.gray('# Refresh threat intel'));
856
+ console.log(chalk.white(' praxis project init ') + chalk.gray('# Add security configs'));
857
+ console.log(chalk.white(' praxis vibe . ') + chalk.gray('# Emoji-graded score'));
858
+ console.log(chalk.white(' praxis --help ') + chalk.gray('# Show all groups'));
859
+ console.log();
860
+ console.log(chalk.gray('Migrating from `praxis`? Any legacy command (audit, ci, mcp, ...) still —'));
861
+ console.log(chalk.gray('the `praxis` binary continues to work with full back-compat.'));
862
+ console.log();
863
+ process.exit(0);
864
+ } else {
865
+ program.parse();
866
+ }