@odavl/guardian 0.2.0 → 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.
- package/CHANGELOG.md +86 -2
- package/README.md +155 -97
- package/bin/guardian.js +1345 -60
- package/config/README.md +59 -0
- package/config/profiles/landing-demo.yaml +16 -0
- package/package.json +21 -11
- package/policies/landing-demo.json +22 -0
- package/src/enterprise/audit-logger.js +166 -0
- package/src/enterprise/pdf-exporter.js +267 -0
- package/src/enterprise/rbac-gate.js +142 -0
- package/src/enterprise/rbac.js +239 -0
- package/src/enterprise/site-manager.js +180 -0
- package/src/founder/feedback-system.js +156 -0
- package/src/founder/founder-tracker.js +213 -0
- package/src/founder/usage-signals.js +141 -0
- package/src/guardian/alert-ledger.js +121 -0
- package/src/guardian/attempt-engine.js +568 -7
- package/src/guardian/attempt-registry.js +42 -1
- package/src/guardian/attempt-relevance.js +106 -0
- package/src/guardian/attempt.js +24 -0
- package/src/guardian/baseline.js +12 -4
- package/src/guardian/breakage-intelligence.js +1 -0
- package/src/guardian/ci-cli.js +121 -0
- package/src/guardian/ci-output.js +4 -3
- package/src/guardian/cli-summary.js +79 -92
- package/src/guardian/config-loader.js +162 -0
- package/src/guardian/drift-detector.js +100 -0
- package/src/guardian/enhanced-html-reporter.js +221 -4
- package/src/guardian/env-guard.js +127 -0
- package/src/guardian/failure-intelligence.js +173 -0
- package/src/guardian/first-run-profile.js +89 -0
- package/src/guardian/first-run.js +6 -1
- package/src/guardian/flag-validator.js +17 -3
- package/src/guardian/html-reporter.js +2 -0
- package/src/guardian/human-reporter.js +431 -0
- package/src/guardian/index.js +22 -19
- package/src/guardian/init-command.js +9 -5
- package/src/guardian/intent-detector.js +146 -0
- package/src/guardian/journey-definitions.js +132 -0
- package/src/guardian/journey-scan-cli.js +145 -0
- package/src/guardian/journey-scanner.js +583 -0
- package/src/guardian/junit-reporter.js +18 -1
- package/src/guardian/live-cli.js +95 -0
- package/src/guardian/live-scheduler-runner.js +137 -0
- package/src/guardian/live-scheduler.js +146 -0
- package/src/guardian/market-reporter.js +341 -81
- package/src/guardian/pattern-analyzer.js +348 -0
- package/src/guardian/policy.js +80 -3
- package/src/guardian/preset-loader.js +9 -6
- package/src/guardian/reality.js +1278 -117
- package/src/guardian/reporter.js +27 -41
- package/src/guardian/run-artifacts.js +212 -0
- package/src/guardian/run-cleanup.js +207 -0
- package/src/guardian/run-latest.js +90 -0
- package/src/guardian/run-list.js +211 -0
- package/src/guardian/scan-presets.js +100 -11
- package/src/guardian/selector-fallbacks.js +394 -0
- package/src/guardian/semantic-contact-finder.js +2 -1
- package/src/guardian/site-introspection.js +257 -0
- package/src/guardian/smoke.js +2 -2
- package/src/guardian/snapshot-schema.js +25 -1
- package/src/guardian/snapshot.js +46 -2
- package/src/guardian/stability-scorer.js +169 -0
- package/src/guardian/template-command.js +184 -0
- package/src/guardian/text-formatters.js +426 -0
- package/src/guardian/verdict.js +320 -0
- package/src/guardian/verdicts.js +74 -0
- package/src/guardian/watch-runner.js +3 -7
- package/src/payments/stripe-checkout.js +169 -0
- package/src/plans/plan-definitions.js +148 -0
- package/src/plans/plan-manager.js +211 -0
- package/src/plans/usage-tracker.js +210 -0
- package/src/recipes/recipe-engine.js +188 -0
- package/src/recipes/recipe-failure-analysis.js +159 -0
- package/src/recipes/recipe-registry.js +134 -0
- package/src/recipes/recipe-runtime.js +507 -0
- package/src/recipes/recipe-store.js +410 -0
- package/guardian-contract-v1.md +0 -149
- /package/{guardian.config.json → config/guardian.config.json} +0 -0
- /package/{guardian.policy.json → config/guardian.policy.json} +0 -0
- /package/{guardian.profile.docs.yaml → config/profiles/docs.yaml} +0 -0
- /package/{guardian.profile.ecommerce.yaml → config/profiles/ecommerce.yaml} +0 -0
- /package/{guardian.profile.marketing.yaml → config/profiles/marketing.yaml} +0 -0
- /package/{guardian.profile.saas.yaml → config/profiles/saas.yaml} +0 -0
package/bin/guardian.js
CHANGED
|
@@ -5,6 +5,65 @@ if (process.platform === 'win32') {
|
|
|
5
5
|
process.stderr.setEncoding('utf-8');
|
|
6
6
|
}
|
|
7
7
|
|
|
8
|
+
// Minimal DX: handle --version/-v immediately (before any other work)
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
if (args.length === 1 && (args[0] === '--version' || args[0] === '-v')) {
|
|
11
|
+
try {
|
|
12
|
+
const pkg = require('../package.json');
|
|
13
|
+
console.log(pkg.version);
|
|
14
|
+
process.exit(0);
|
|
15
|
+
} catch (e) {
|
|
16
|
+
console.error('Version unavailable');
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// GLOBAL HELP (Level 1): provide a real, working guardian --help
|
|
22
|
+
function printGlobalHelp() {
|
|
23
|
+
console.log(`
|
|
24
|
+
ODAVL Guardian — Level 1
|
|
25
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
26
|
+
|
|
27
|
+
Golden Path (Reality Check)
|
|
28
|
+
guardian --url <url>
|
|
29
|
+
guardian reality --url <url>
|
|
30
|
+
|
|
31
|
+
What happens
|
|
32
|
+
- Opens a real browser
|
|
33
|
+
- Runs curated reality attempts
|
|
34
|
+
- Writes reports to ./.odavlguardian/<run>/
|
|
35
|
+
|
|
36
|
+
Reports (Level 1)
|
|
37
|
+
- decision.json (finalVerdict, exitCode, explanation)
|
|
38
|
+
- summary.md (human-readable summary)
|
|
39
|
+
|
|
40
|
+
Canonical Verdicts
|
|
41
|
+
READY | FRICTION | DO_NOT_LAUNCH
|
|
42
|
+
|
|
43
|
+
VS Code (Level 1)
|
|
44
|
+
Command Palette → "Guardian: Run Reality Check"
|
|
45
|
+
- Uses ./.odavlguardian as artifacts directory
|
|
46
|
+
- Shows verdict and opens summary.md
|
|
47
|
+
|
|
48
|
+
Advanced (Level 2+)
|
|
49
|
+
guardian scan <url> One-command full scan (advanced)
|
|
50
|
+
guardian journey-scan <url> Single flow journey (advanced)
|
|
51
|
+
guardian smoke <url> Fast sanity (advanced)
|
|
52
|
+
guardian baseline <save|check> Baselines (advanced)
|
|
53
|
+
guardian list|cleanup Artifacts mgmt (advanced)
|
|
54
|
+
|
|
55
|
+
Tips
|
|
56
|
+
- Pass --artifacts <dir> to override artifacts directory
|
|
57
|
+
- Local config: guardian.config.json (CLI only)
|
|
58
|
+
`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Handle global --help (or no args) before heavy loads
|
|
62
|
+
if (args.length === 0 || (args.length === 1 && (args[0] === '--help' || args[0] === '-h'))) {
|
|
63
|
+
printGlobalHelp();
|
|
64
|
+
process.exit(0);
|
|
65
|
+
}
|
|
66
|
+
|
|
8
67
|
// PHASE 6: Early flag validation (before heavy module loads)
|
|
9
68
|
const { validateFlags, reportFlagError } = require('../src/guardian/flag-validator');
|
|
10
69
|
const validation = validateFlags(process.argv);
|
|
@@ -14,16 +73,41 @@ if (!validation.valid) {
|
|
|
14
73
|
}
|
|
15
74
|
|
|
16
75
|
// PHASE 6: First-run detection (lightweight)
|
|
17
|
-
const { isFirstRun, markAsRun, printWelcome } = require('../src/guardian/first-run');
|
|
76
|
+
const { isFirstRun, markAsRun, printWelcome, printFirstRunHint } = require('../src/guardian/first-run');
|
|
18
77
|
|
|
19
78
|
const { runAttemptCLI } = require('../src/guardian/attempt');
|
|
20
79
|
const { runRealityCLI } = require('../src/guardian/reality');
|
|
21
80
|
const { runSmokeCLI } = require('../src/guardian/smoke');
|
|
22
81
|
const { runGuardian } = require('../src/guardian');
|
|
82
|
+
const { runJourneyScanCLI } = require('../src/guardian/journey-scan-cli');
|
|
83
|
+
const { runLiveCLI } = require('../src/guardian/live-cli');
|
|
23
84
|
const { saveBaseline, checkBaseline } = require('../src/guardian/baseline');
|
|
24
85
|
const { getDefaultAttemptIds } = require('../src/guardian/attempt-registry');
|
|
25
86
|
const { initGuardian } = require('../src/guardian/init-command');
|
|
26
87
|
const { printPresets } = require('../src/guardian/preset-loader');
|
|
88
|
+
const { listRuns } = require('../src/guardian/run-list');
|
|
89
|
+
const { cleanup } = require('../src/guardian/run-cleanup');
|
|
90
|
+
const { generateTemplate, listTemplates } = require('../src/guardian/template-command');
|
|
91
|
+
|
|
92
|
+
// Phase 8: Plan enforcement
|
|
93
|
+
const { checkCanScan, performScan, checkFeatureAllowed, getPlanSummary, getUpgradeMessage } = require('../src/plans/plan-manager');
|
|
94
|
+
|
|
95
|
+
// Phase 10: Founder tracking and feedback
|
|
96
|
+
const { registerUser, getFounderMessage, isFoundingUser } = require('../src/founder/founder-tracker');
|
|
97
|
+
const { runFeedbackSession } = require('../src/founder/feedback-system');
|
|
98
|
+
const { recordFirstScan, recordFirstLive, recordFirstUpgrade } = require('../src/founder/usage-signals');
|
|
99
|
+
|
|
100
|
+
// Phase 11: Enterprise features
|
|
101
|
+
const { addSite, removeSite, getSite, getSites, getSitesByProject, listProjects } = require('../src/enterprise/site-manager');
|
|
102
|
+
const { addUser, removeUser, getUsers, getCurrentUser, requirePermission, listRoles } = require('../src/enterprise/rbac');
|
|
103
|
+
const { logAudit, readAuditLogs, getAuditSummary, AUDIT_ACTIONS } = require('../src/enterprise/audit-logger');
|
|
104
|
+
const { exportReportToPDF, listAvailableReports } = require('../src/enterprise/pdf-exporter');
|
|
105
|
+
|
|
106
|
+
// Phase 12.1: Recipes
|
|
107
|
+
const { getAllRecipes, getRecipe, getRecipesByPlatform, addRecipe, removeRecipe, importRecipes, exportRecipes, exportRecipeWithMetadata, importRecipeWithMetadata } = require('../src/recipes/recipe-store');
|
|
108
|
+
const { validateRecipe, formatRecipe } = require('../src/recipes/recipe-engine');
|
|
109
|
+
const { getRegistryEntry, computeRecipeChecksum } = require('../src/recipes/recipe-registry');
|
|
110
|
+
const { resolveScanPreset } = require('../src/guardian/scan-presets');
|
|
27
111
|
|
|
28
112
|
function parseArgs(argv) {
|
|
29
113
|
const args = argv.slice(2);
|
|
@@ -44,6 +128,18 @@ function parseArgs(argv) {
|
|
|
44
128
|
return { subcommand: 'presets', config: {} };
|
|
45
129
|
}
|
|
46
130
|
|
|
131
|
+
if (subcommand === 'template') {
|
|
132
|
+
return { subcommand: 'template', config: parseTemplateArgs(args.slice(1)) };
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (subcommand === 'list') {
|
|
136
|
+
return { subcommand: 'list', config: parseListArgs(args.slice(1)) };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
if (subcommand === 'cleanup') {
|
|
140
|
+
return { subcommand: 'cleanup', config: parseCleanupArgs(args.slice(1)) };
|
|
141
|
+
}
|
|
142
|
+
|
|
47
143
|
if (subcommand === 'attempt') {
|
|
48
144
|
return { subcommand: 'attempt', config: parseAttemptArgs(args.slice(1)) };
|
|
49
145
|
}
|
|
@@ -56,6 +152,10 @@ function parseArgs(argv) {
|
|
|
56
152
|
return { subcommand: 'smoke', config: parseSmokeArgs(args.slice(1)) };
|
|
57
153
|
}
|
|
58
154
|
|
|
155
|
+
if (subcommand === 'check') {
|
|
156
|
+
return { subcommand: 'smoke', config: parseSmokeArgs(args.slice(1)) };
|
|
157
|
+
}
|
|
158
|
+
|
|
59
159
|
if (subcommand === 'baseline') {
|
|
60
160
|
const action = args[1];
|
|
61
161
|
if (action === 'save') {
|
|
@@ -68,13 +168,87 @@ function parseArgs(argv) {
|
|
|
68
168
|
process.exit(0);
|
|
69
169
|
}
|
|
70
170
|
|
|
171
|
+
// MVP: Human Journey Scan
|
|
172
|
+
if (subcommand === 'journey-scan' || subcommand === 'journey') {
|
|
173
|
+
return { subcommand: 'journey-scan', config: parseJourneyScanArgs(args.slice(1)) };
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (subcommand === 'live') {
|
|
177
|
+
// Support scheduler subcommands: start|stop|status
|
|
178
|
+
const action = args[1];
|
|
179
|
+
if (action === 'start') {
|
|
180
|
+
return { subcommand: 'live-start', config: parseLiveStartArgs(args.slice(2)) };
|
|
181
|
+
}
|
|
182
|
+
if (action === 'stop') {
|
|
183
|
+
return { subcommand: 'live-stop', config: parseLiveStopArgs(args.slice(2)) };
|
|
184
|
+
}
|
|
185
|
+
if (action === 'status') {
|
|
186
|
+
return { subcommand: 'live-status', config: {} };
|
|
187
|
+
}
|
|
188
|
+
return { subcommand: 'live', config: parseLiveArgs(args.slice(1)) };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (subcommand === 'ci') {
|
|
192
|
+
return { subcommand: 'ci', config: parseJourneyScanArgs(args.slice(1)) };
|
|
193
|
+
}
|
|
194
|
+
|
|
71
195
|
// Phase 6: Productized one-command scan
|
|
72
196
|
if (subcommand === 'scan') {
|
|
73
197
|
return { subcommand: 'scan', config: parseScanArgs(args.slice(1)) };
|
|
74
198
|
}
|
|
75
199
|
|
|
76
|
-
//
|
|
77
|
-
|
|
200
|
+
// Phase 8: Plan management
|
|
201
|
+
if (subcommand === 'plan') {
|
|
202
|
+
return { subcommand: 'plan', config: {} };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
if (subcommand === 'upgrade') {
|
|
206
|
+
const targetPlan = args[1];
|
|
207
|
+
return { subcommand: 'upgrade', config: { plan: targetPlan } };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Phase 10: Feedback command
|
|
211
|
+
if (subcommand === 'feedback') {
|
|
212
|
+
return { subcommand: 'feedback', config: {} };
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
// Phase 11: Enterprise commands
|
|
216
|
+
if (subcommand === 'sites') {
|
|
217
|
+
return { subcommand: 'sites', config: parseSitesArgs(args.slice(1)) };
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
if (subcommand === 'users') {
|
|
221
|
+
return { subcommand: 'users', config: parseUsersArgs(args.slice(1)) };
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
if (subcommand === 'audit') {
|
|
225
|
+
return { subcommand: 'audit', config: parseAuditArgs(args.slice(1)) };
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (subcommand === 'export') {
|
|
229
|
+
return { subcommand: 'export', config: parseExportArgs(args.slice(1)) };
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Phase 12.1: Recipes
|
|
233
|
+
if (subcommand === 'recipe') {
|
|
234
|
+
return { subcommand: 'recipe', config: parseRecipeArgs(args.slice(1)) };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// LEVEL 1 GOLDEN PATH: guardian --url routes to Reality (not legacy crawl)
|
|
238
|
+
// If first arg is --url, treat it as guardian reality --url
|
|
239
|
+
if (args.length > 0 && args[0] === '--url') {
|
|
240
|
+
return { subcommand: 'reality', config: parseRealityArgs(args) };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Legacy crawl command (explicit only)
|
|
244
|
+
if (subcommand === 'crawl') {
|
|
245
|
+
return { subcommand: 'crawl', config: parseCrawlArgs(args.slice(1)) };
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Unknown command
|
|
249
|
+
console.error(`Unknown command: ${subcommand}`);
|
|
250
|
+
console.error('Run "guardian --help" for Level 1 usage.');
|
|
251
|
+
process.exit(2);
|
|
78
252
|
}
|
|
79
253
|
|
|
80
254
|
function parseCrawlArgs(args) {
|
|
@@ -82,7 +256,7 @@ function parseCrawlArgs(args) {
|
|
|
82
256
|
maxPages: 25,
|
|
83
257
|
maxDepth: 3,
|
|
84
258
|
timeout: 20000,
|
|
85
|
-
artifactsDir: '
|
|
259
|
+
artifactsDir: './.odavlguardian'
|
|
86
260
|
};
|
|
87
261
|
|
|
88
262
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -121,12 +295,162 @@ function parseCrawlArgs(args) {
|
|
|
121
295
|
return config;
|
|
122
296
|
}
|
|
123
297
|
|
|
298
|
+
// Apply preset configuration deterministically across commands
|
|
299
|
+
function applyPresetConfig(config) {
|
|
300
|
+
const presetName = config.preset || 'landing';
|
|
301
|
+
let preset;
|
|
302
|
+
try {
|
|
303
|
+
preset = resolveScanPreset(presetName);
|
|
304
|
+
} catch (err) {
|
|
305
|
+
console.error(`Error: ${err.message}`);
|
|
306
|
+
process.exit(2);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const applied = { ...config };
|
|
310
|
+
applied.preset = preset.id;
|
|
311
|
+
applied.attempts = preset.attempts;
|
|
312
|
+
applied.disabledAttempts = preset.disabledAttempts || [];
|
|
313
|
+
applied.flows = preset.flows;
|
|
314
|
+
applied.policy = applied.policy || preset.policy;
|
|
315
|
+
if (applied.failFast === undefined) {
|
|
316
|
+
applied.failFast = preset.failFast;
|
|
317
|
+
}
|
|
318
|
+
applied.evidencePreset = preset.evidence || {};
|
|
319
|
+
return applied;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// MVP: Journey Scan command parser
|
|
323
|
+
function parseJourneyScanArgs(args) {
|
|
324
|
+
const config = {
|
|
325
|
+
baseUrl: undefined,
|
|
326
|
+
preset: 'saas',
|
|
327
|
+
artifactsDir: './.odavlguardian',
|
|
328
|
+
headless: true,
|
|
329
|
+
timeout: 20000,
|
|
330
|
+
presetProvided: false
|
|
331
|
+
};
|
|
332
|
+
|
|
333
|
+
// First arg is URL if it doesn't start with --
|
|
334
|
+
if (args.length > 0 && !args[0].startsWith('--')) {
|
|
335
|
+
config.baseUrl = args[0];
|
|
336
|
+
args = args.slice(1);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
for (let i = 0; i < args.length; i++) {
|
|
340
|
+
const a = args[i];
|
|
341
|
+
if (a === '--url' && args[i + 1]) { config.baseUrl = args[i + 1]; i++; }
|
|
342
|
+
else if (a === '--preset' && args[i + 1]) { config.preset = args[i + 1]; config.presetProvided = true; i++; }
|
|
343
|
+
else if (a === '--out' && args[i + 1]) { config.artifactsDir = args[i + 1]; i++; }
|
|
344
|
+
else if (a === '--artifacts' && args[i + 1]) { config.artifactsDir = args[i + 1]; i++; }
|
|
345
|
+
else if (a === '--timeout' && args[i + 1]) { config.timeout = parseInt(args[i + 1], 10); i++; }
|
|
346
|
+
else if (a === '--headful') { config.headless = false; }
|
|
347
|
+
else if (a === '--help' || a === '-h') { printHelpJourneyScan(); process.exit(0); }
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
if (!config.baseUrl) {
|
|
351
|
+
console.error('Error: <url> is required');
|
|
352
|
+
console.error('Usage: guardian journey-scan <url> [--preset saas|shop|landing] [--out dir]');
|
|
353
|
+
process.exit(2);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
return config;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
function parseLiveArgs(args) {
|
|
360
|
+
const config = {
|
|
361
|
+
baseUrl: undefined,
|
|
362
|
+
artifactsDir: './.odavlguardian',
|
|
363
|
+
headless: true,
|
|
364
|
+
timeout: 20000,
|
|
365
|
+
intervalMinutes: null,
|
|
366
|
+
preset: 'saas',
|
|
367
|
+
presetProvided: false,
|
|
368
|
+
cooldownMinutes: 60
|
|
369
|
+
};
|
|
370
|
+
|
|
371
|
+
if (args.length > 0 && !args[0].startsWith('--')) {
|
|
372
|
+
config.baseUrl = args[0];
|
|
373
|
+
args = args.slice(1);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
for (let i = 0; i < args.length; i++) {
|
|
377
|
+
const a = args[i];
|
|
378
|
+
if (a === '--url' && args[i + 1]) { config.baseUrl = args[i + 1]; i++; }
|
|
379
|
+
else if (a === '--out' && args[i + 1]) { config.artifactsDir = args[i + 1]; i++; }
|
|
380
|
+
else if (a === '--artifacts' && args[i + 1]) { config.artifactsDir = args[i + 1]; i++; }
|
|
381
|
+
else if (a === '--interval' && args[i + 1]) { config.intervalMinutes = parseFloat(args[i + 1]); i++; }
|
|
382
|
+
else if (a === '--cooldown' && args[i + 1]) { config.cooldownMinutes = parseFloat(args[i + 1]); i++; }
|
|
383
|
+
else if (a === '--timeout' && args[i + 1]) { config.timeout = parseInt(args[i + 1], 10); i++; }
|
|
384
|
+
else if (a === '--preset' && args[i + 1]) { config.preset = args[i + 1]; config.presetProvided = true; i++; }
|
|
385
|
+
else if (a === '--headful') { config.headless = false; }
|
|
386
|
+
else if (a === '--help' || a === '-h') { printHelpLive(); process.exit(0); }
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
if (!config.baseUrl) {
|
|
390
|
+
console.error('Error: <url> is required');
|
|
391
|
+
console.error('Usage: guardian live <url> [--interval <minutes>] [--out dir]');
|
|
392
|
+
process.exit(2);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return config;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// Scheduler start args
|
|
399
|
+
function parseLiveStartArgs(args) {
|
|
400
|
+
const config = {
|
|
401
|
+
baseUrl: undefined,
|
|
402
|
+
preset: 'saas',
|
|
403
|
+
intervalMinutes: undefined,
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
// First arg is URL if present
|
|
407
|
+
if (args.length > 0 && !String(args[0]).startsWith('--')) {
|
|
408
|
+
config.baseUrl = args[0];
|
|
409
|
+
args = args.slice(1);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
for (let i = 0; i < args.length; i++) {
|
|
413
|
+
const a = args[i];
|
|
414
|
+
if (a === '--url' && args[i + 1]) { config.baseUrl = args[i + 1]; i++; }
|
|
415
|
+
else if (a === '--preset' && args[i + 1]) { config.preset = args[i + 1]; i++; }
|
|
416
|
+
else if (a === '--interval' && args[i + 1]) { config.intervalMinutes = parseFloat(args[i + 1]); i++; }
|
|
417
|
+
else if (a === '--help' || a === '-h') {
|
|
418
|
+
console.log('\nUsage: guardian live start --url <url> --interval <minutes> [--preset saas|shop|landing]');
|
|
419
|
+
process.exit(0);
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (!config.baseUrl || !config.intervalMinutes || config.intervalMinutes <= 0) {
|
|
424
|
+
console.error('Error: --url and --interval <minutes> are required');
|
|
425
|
+
process.exit(2);
|
|
426
|
+
}
|
|
427
|
+
return config;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
// Scheduler stop args
|
|
431
|
+
function parseLiveStopArgs(args) {
|
|
432
|
+
const config = { id: undefined };
|
|
433
|
+
if (args.length > 0) {
|
|
434
|
+
config.id = args[0];
|
|
435
|
+
}
|
|
436
|
+
for (let i = 0; i < args.length; i++) {
|
|
437
|
+
const a = args[i];
|
|
438
|
+
if ((a === '--id' || a === '-i') && args[i + 1]) { config.id = args[i + 1]; i++; }
|
|
439
|
+
}
|
|
440
|
+
if (!config.id) {
|
|
441
|
+
console.error('Error: schedule id is required');
|
|
442
|
+
console.error('Usage: guardian live stop <id>');
|
|
443
|
+
process.exit(2);
|
|
444
|
+
}
|
|
445
|
+
return config;
|
|
446
|
+
}
|
|
447
|
+
|
|
124
448
|
// Phase 6: Scan command (one-command value)
|
|
125
449
|
function parseScanArgs(args) {
|
|
126
450
|
const config = {
|
|
127
451
|
// core
|
|
128
452
|
baseUrl: undefined,
|
|
129
|
-
artifactsDir: '
|
|
453
|
+
artifactsDir: './.odavlguardian',
|
|
130
454
|
// enable full pipeline
|
|
131
455
|
enableCrawl: true,
|
|
132
456
|
enableDiscovery: true,
|
|
@@ -182,46 +506,100 @@ function parseScanArgs(args) {
|
|
|
182
506
|
|
|
183
507
|
if (!config.baseUrl) {
|
|
184
508
|
console.error('Error: <url> is required');
|
|
185
|
-
console.error('Usage: guardian scan <url> [--preset <landing|saas|shop>]');
|
|
509
|
+
console.error('Usage: guardian scan <url> [--preset <landing|landing-demo|saas|shop>]');
|
|
186
510
|
process.exit(2);
|
|
187
511
|
}
|
|
188
512
|
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
513
|
+
return applyPresetConfig(config);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
function parseListArgs(args) {
|
|
517
|
+
const config = {
|
|
518
|
+
artifactsDir: './.odavlguardian',
|
|
519
|
+
filters: {}
|
|
520
|
+
};
|
|
521
|
+
|
|
522
|
+
for (let i = 0; i < args.length; i++) {
|
|
523
|
+
if (args[i] === '--artifacts' && args[i + 1]) {
|
|
524
|
+
config.artifactsDir = args[i + 1];
|
|
525
|
+
i++;
|
|
526
|
+
}
|
|
527
|
+
if (args[i] === '--failed') {
|
|
528
|
+
config.filters.failed = true;
|
|
529
|
+
}
|
|
530
|
+
if (args[i] === '--site' && args[i + 1]) {
|
|
531
|
+
config.filters.site = args[i + 1];
|
|
532
|
+
i++;
|
|
533
|
+
}
|
|
534
|
+
if (args[i] === '--limit' && args[i + 1]) {
|
|
535
|
+
config.filters.limit = parseInt(args[i + 1], 10);
|
|
536
|
+
i++;
|
|
537
|
+
}
|
|
538
|
+
if (args[i] === '--help' || args[i] === '-h') {
|
|
539
|
+
printHelpList();
|
|
540
|
+
process.exit(0);
|
|
198
541
|
}
|
|
199
|
-
} catch (e) {
|
|
200
|
-
// If presets not available, proceed with defaults
|
|
201
542
|
}
|
|
202
543
|
|
|
203
544
|
return config;
|
|
204
545
|
}
|
|
205
546
|
|
|
206
|
-
function
|
|
547
|
+
function parseCleanupArgs(args) {
|
|
207
548
|
const config = {
|
|
208
549
|
artifactsDir: './artifacts',
|
|
550
|
+
olderThan: null,
|
|
551
|
+
keepLatest: null,
|
|
552
|
+
failedOnly: false
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
for (let i = 0; i < args.length; i++) {
|
|
556
|
+
if (args[i] === '--artifacts' && args[i + 1]) {
|
|
557
|
+
config.artifactsDir = args[i + 1];
|
|
558
|
+
i++;
|
|
559
|
+
}
|
|
560
|
+
if (args[i] === '--older-than' && args[i + 1]) {
|
|
561
|
+
config.olderThan = args[i + 1];
|
|
562
|
+
i++;
|
|
563
|
+
}
|
|
564
|
+
if (args[i] === '--keep-latest' && args[i + 1]) {
|
|
565
|
+
config.keepLatest = parseInt(args[i + 1], 10);
|
|
566
|
+
i++;
|
|
567
|
+
}
|
|
568
|
+
if (args[i] === '--failed-only') {
|
|
569
|
+
config.failedOnly = true;
|
|
570
|
+
}
|
|
571
|
+
if (args[i] === '--help' || args[i] === '-h') {
|
|
572
|
+
printHelpCleanup();
|
|
573
|
+
process.exit(0);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
return config;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
function parseRealityArgs(args) {
|
|
581
|
+
const config = {
|
|
582
|
+
artifactsDir: undefined,
|
|
209
583
|
attempts: getDefaultAttemptIds(),
|
|
584
|
+
disabledAttempts: [],
|
|
210
585
|
headful: false,
|
|
211
586
|
enableTrace: true,
|
|
212
587
|
enableScreenshots: true,
|
|
213
588
|
enableDiscovery: false,
|
|
214
589
|
includeUniversal: false,
|
|
590
|
+
preset: 'landing',
|
|
215
591
|
policy: null,
|
|
216
592
|
webhook: null,
|
|
217
593
|
watch: false,
|
|
218
594
|
// Phase 7.1: Performance modes
|
|
595
|
+
|
|
219
596
|
timeoutProfile: 'default',
|
|
220
597
|
failFast: false,
|
|
221
598
|
fast: false,
|
|
222
599
|
attemptsFilter: null,
|
|
223
600
|
// Phase 7.2: Parallel execution
|
|
224
|
-
parallel: 1
|
|
601
|
+
parallel: 1,
|
|
602
|
+
_cliSource: {}
|
|
225
603
|
};
|
|
226
604
|
|
|
227
605
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -236,6 +614,26 @@ function parseRealityArgs(args) {
|
|
|
236
614
|
}
|
|
237
615
|
if (args[i] === '--artifacts' && args[i + 1]) {
|
|
238
616
|
config.artifactsDir = args[i + 1];
|
|
617
|
+
config._cliSource.artifactsDir = true;
|
|
618
|
+
i++;
|
|
619
|
+
}
|
|
620
|
+
if (args[i] === '--max-pages' && args[i + 1]) {
|
|
621
|
+
config.maxPages = parseInt(args[i + 1], 10);
|
|
622
|
+
config._cliSource.maxPages = true;
|
|
623
|
+
i++;
|
|
624
|
+
}
|
|
625
|
+
if (args[i] === '--max-depth' && args[i + 1]) {
|
|
626
|
+
config.maxDepth = parseInt(args[i + 1], 10);
|
|
627
|
+
config._cliSource.maxDepth = true;
|
|
628
|
+
i++;
|
|
629
|
+
}
|
|
630
|
+
if (args[i] === '--timeout' && args[i + 1]) {
|
|
631
|
+
config.timeout = parseInt(args[i + 1], 10);
|
|
632
|
+
config._cliSource.timeout = true;
|
|
633
|
+
i++;
|
|
634
|
+
}
|
|
635
|
+
if (args[i] === '--preset' && args[i + 1]) {
|
|
636
|
+
config.preset = args[i + 1];
|
|
239
637
|
i++;
|
|
240
638
|
}
|
|
241
639
|
if (args[i] === '--policy' && args[i + 1]) {
|
|
@@ -294,7 +692,7 @@ function parseRealityArgs(args) {
|
|
|
294
692
|
process.exit(2);
|
|
295
693
|
}
|
|
296
694
|
|
|
297
|
-
return config;
|
|
695
|
+
return applyPresetConfig(config);
|
|
298
696
|
}
|
|
299
697
|
|
|
300
698
|
function parseSmokeArgs(args) {
|
|
@@ -346,14 +744,156 @@ function parseInitArgs(args) {
|
|
|
346
744
|
return config;
|
|
347
745
|
}
|
|
348
746
|
|
|
747
|
+
function parseTemplateArgs(args) {
|
|
748
|
+
const config = {
|
|
749
|
+
template: args[0] || null,
|
|
750
|
+
output: null
|
|
751
|
+
};
|
|
752
|
+
|
|
753
|
+
for (let i = 0; i < args.length; i++) {
|
|
754
|
+
if (args[i] === '--output' && args[i + 1]) {
|
|
755
|
+
config.output = args[i + 1];
|
|
756
|
+
i++;
|
|
757
|
+
}
|
|
758
|
+
if (args[i] === '--help' || args[i] === '-h') {
|
|
759
|
+
printHelpTemplate();
|
|
760
|
+
process.exit(0);
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
return config;
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// Phase 11: Enterprise command parsers
|
|
768
|
+
function parseSitesArgs(args) {
|
|
769
|
+
const config = {
|
|
770
|
+
action: args[0] || 'list',
|
|
771
|
+
name: null,
|
|
772
|
+
url: null,
|
|
773
|
+
project: 'default'
|
|
774
|
+
};
|
|
775
|
+
|
|
776
|
+
if (config.action === 'add') {
|
|
777
|
+
config.name = args[1];
|
|
778
|
+
config.url = args[2];
|
|
779
|
+
for (let i = 3; i < args.length; i++) {
|
|
780
|
+
if (args[i] === '--project' && args[i + 1]) {
|
|
781
|
+
config.project = args[i + 1];
|
|
782
|
+
i++;
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
} else if (config.action === 'remove') {
|
|
786
|
+
config.name = args[1];
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
return config;
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function parseUsersArgs(args) {
|
|
793
|
+
const config = {
|
|
794
|
+
action: args[0] || 'list',
|
|
795
|
+
username: args[1] || null,
|
|
796
|
+
role: args[2] || 'VIEWER'
|
|
797
|
+
};
|
|
798
|
+
|
|
799
|
+
return config;
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
function parseAuditArgs(args) {
|
|
803
|
+
const config = {
|
|
804
|
+
action: args[0] || 'list',
|
|
805
|
+
limit: 100,
|
|
806
|
+
actionFilter: null,
|
|
807
|
+
user: null
|
|
808
|
+
};
|
|
809
|
+
|
|
810
|
+
for (let i = 1; i < args.length; i++) {
|
|
811
|
+
if (args[i] === '--limit' && args[i + 1]) {
|
|
812
|
+
config.limit = parseInt(args[i + 1], 10);
|
|
813
|
+
i++;
|
|
814
|
+
}
|
|
815
|
+
if (args[i] === '--action' && args[i + 1]) {
|
|
816
|
+
config.actionFilter = args[i + 1];
|
|
817
|
+
i++;
|
|
818
|
+
}
|
|
819
|
+
if (args[i] === '--user' && args[i + 1]) {
|
|
820
|
+
config.user = args[i + 1];
|
|
821
|
+
i++;
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
|
|
825
|
+
return config;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
function parseExportArgs(args) {
|
|
829
|
+
const config = {
|
|
830
|
+
reportId: args[0] || null,
|
|
831
|
+
format: 'pdf',
|
|
832
|
+
output: null
|
|
833
|
+
};
|
|
834
|
+
|
|
835
|
+
for (let i = 1; i < args.length; i++) {
|
|
836
|
+
if (args[i] === '--format' && args[i + 1]) {
|
|
837
|
+
config.format = args[i + 1];
|
|
838
|
+
i++;
|
|
839
|
+
}
|
|
840
|
+
if (args[i] === '--output' && args[i + 1]) {
|
|
841
|
+
config.output = args[i + 1];
|
|
842
|
+
i++;
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
return config;
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
// Phase 12.1: Recipe parser
|
|
850
|
+
function parseRecipeArgs(args) {
|
|
851
|
+
const config = {
|
|
852
|
+
action: args[0] || 'list',
|
|
853
|
+
id: args[1] || null,
|
|
854
|
+
url: null,
|
|
855
|
+
file: null,
|
|
856
|
+
out: null,
|
|
857
|
+
force: false,
|
|
858
|
+
};
|
|
859
|
+
|
|
860
|
+
// Positional file support for import
|
|
861
|
+
if (config.action === 'import' && config.id && !config.id.startsWith('--')) {
|
|
862
|
+
config.file = config.id;
|
|
863
|
+
config.id = null;
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
for (let i = 2; i < args.length; i++) {
|
|
867
|
+
if (args[i] === '--url' && args[i + 1]) {
|
|
868
|
+
config.url = args[i + 1];
|
|
869
|
+
i++;
|
|
870
|
+
}
|
|
871
|
+
if (args[i] === '--file' && args[i + 1]) {
|
|
872
|
+
config.file = args[i + 1];
|
|
873
|
+
i++;
|
|
874
|
+
}
|
|
875
|
+
if (args[i] === '--out' && args[i + 1]) {
|
|
876
|
+
config.out = args[i + 1];
|
|
877
|
+
i++;
|
|
878
|
+
}
|
|
879
|
+
if (args[i] === '--force') {
|
|
880
|
+
config.force = true;
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
return config;
|
|
885
|
+
}
|
|
886
|
+
|
|
349
887
|
function parseProtectArgs(args) {
|
|
350
888
|
const config = {
|
|
351
889
|
artifactsDir: './artifacts',
|
|
352
890
|
attempts: getDefaultAttemptIds(),
|
|
891
|
+
disabledAttempts: [],
|
|
353
892
|
headful: false,
|
|
354
893
|
enableTrace: true,
|
|
355
894
|
enableScreenshots: true,
|
|
356
|
-
policy:
|
|
895
|
+
policy: null,
|
|
896
|
+
preset: 'startup',
|
|
357
897
|
webhook: null,
|
|
358
898
|
watch: false,
|
|
359
899
|
// Phase 7.1: Performance modes
|
|
@@ -376,6 +916,10 @@ function parseProtectArgs(args) {
|
|
|
376
916
|
config.baseUrl = args[i + 1];
|
|
377
917
|
i++;
|
|
378
918
|
}
|
|
919
|
+
if (args[i] === '--preset' && args[i + 1]) {
|
|
920
|
+
config.preset = args[i + 1];
|
|
921
|
+
i++;
|
|
922
|
+
}
|
|
379
923
|
if (args[i] === '--policy' && args[i + 1]) {
|
|
380
924
|
config.policy = args[i + 1];
|
|
381
925
|
i++;
|
|
@@ -421,7 +965,7 @@ function parseProtectArgs(args) {
|
|
|
421
965
|
process.exit(2);
|
|
422
966
|
}
|
|
423
967
|
|
|
424
|
-
return config;
|
|
968
|
+
return applyPresetConfig(config);
|
|
425
969
|
}
|
|
426
970
|
|
|
427
971
|
function parseAttemptArgs(args) {
|
|
@@ -565,9 +1109,9 @@ PERFORMANCE (Phase 7.1):
|
|
|
565
1109
|
--help Show this help message
|
|
566
1110
|
|
|
567
1111
|
EXIT CODES:
|
|
568
|
-
0
|
|
569
|
-
1
|
|
570
|
-
2
|
|
1112
|
+
0 READY
|
|
1113
|
+
1 FRICTION
|
|
1114
|
+
2 DO_NOT_LAUNCH
|
|
571
1115
|
|
|
572
1116
|
EXAMPLES:
|
|
573
1117
|
First run (baseline auto-created):
|
|
@@ -587,7 +1131,7 @@ Usage: guardian init [options]
|
|
|
587
1131
|
|
|
588
1132
|
WHAT IT DOES:
|
|
589
1133
|
Initialize Guardian in the current directory:
|
|
590
|
-
- Creates guardian.policy.json (default: startup preset)
|
|
1134
|
+
- Creates config/guardian.policy.json (default: startup preset)
|
|
591
1135
|
- Updates .gitignore to exclude Guardian artifacts
|
|
592
1136
|
- Prints next steps
|
|
593
1137
|
|
|
@@ -602,13 +1146,100 @@ EXAMPLE:
|
|
|
602
1146
|
`);
|
|
603
1147
|
}
|
|
604
1148
|
|
|
1149
|
+
function printHelpTemplate() {
|
|
1150
|
+
console.log(`
|
|
1151
|
+
Usage: guardian template [template] [options]
|
|
1152
|
+
|
|
1153
|
+
WHAT IT DOES:
|
|
1154
|
+
Generate a minimal config template for common site types.
|
|
1155
|
+
Templates include sample journeys and policy settings.
|
|
1156
|
+
|
|
1157
|
+
AVAILABLE TEMPLATES:
|
|
1158
|
+
saas SaaS startup flow (signup, login, dashboard)
|
|
1159
|
+
shop E-commerce shop flow (browse, cart, checkout)
|
|
1160
|
+
landing Landing page flow (load, CTA validation)
|
|
1161
|
+
|
|
1162
|
+
OPTIONS:
|
|
1163
|
+
--output <file> Output file name (default: guardian-<template>.json)
|
|
1164
|
+
--help Show this help message
|
|
1165
|
+
|
|
1166
|
+
EXAMPLES:
|
|
1167
|
+
guardian template saas
|
|
1168
|
+
guardian template shop --output my-config.json
|
|
1169
|
+
guardian template landing
|
|
1170
|
+
`);
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
function printHelpList() {
|
|
1174
|
+
console.log(`
|
|
1175
|
+
Usage: guardian list [options]
|
|
1176
|
+
|
|
1177
|
+
WHAT IT DOES:
|
|
1178
|
+
List all completed Guardian runs with metadata.
|
|
1179
|
+
Scans the artifacts directory for runs with META.json files and displays
|
|
1180
|
+
them in a table sorted by most recent first.
|
|
1181
|
+
|
|
1182
|
+
OPTIONS:
|
|
1183
|
+
--artifacts <dir> Path to artifacts directory
|
|
1184
|
+
Default: ./.odavlguardian
|
|
1185
|
+
--help Show this help message
|
|
1186
|
+
|
|
1187
|
+
OUTPUT COLUMNS:
|
|
1188
|
+
Time Run execution timestamp (YYYY-MM-DD HH:MM:SS)
|
|
1189
|
+
Site Target site slug (extracted from URL)
|
|
1190
|
+
Policy Policy/profile used for the run
|
|
1191
|
+
Result Run result: PASSED, FAILED, or WARN
|
|
1192
|
+
Duration Wall-clock execution time
|
|
1193
|
+
Path Run directory name
|
|
1194
|
+
|
|
1195
|
+
EXAMPLE:
|
|
1196
|
+
guardian list
|
|
1197
|
+
guardian list --artifacts ./.odavlguardian
|
|
1198
|
+
guardian list --failed
|
|
1199
|
+
guardian list --site github-com --limit 5
|
|
1200
|
+
`);
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
function printHelpCleanup() {
|
|
1204
|
+
console.log(`
|
|
1205
|
+
Usage: guardian cleanup [options]
|
|
1206
|
+
|
|
1207
|
+
WHAT IT DOES:
|
|
1208
|
+
Manage and delete old or failed Guardian runs.
|
|
1209
|
+
Safely removes run directories while optionally preserving recent runs.
|
|
1210
|
+
|
|
1211
|
+
OPTIONS:
|
|
1212
|
+
--artifacts <dir> Path to artifacts directory
|
|
1213
|
+
Default: ./.odavlguardian
|
|
1214
|
+
--older-than <duration> Delete runs older than duration
|
|
1215
|
+
Format: <num>[d|h|m] (e.g., 7d, 24h, 30m)
|
|
1216
|
+
--keep-latest <num> Keep the N most recent runs per site
|
|
1217
|
+
Applied per site, deletes older runs
|
|
1218
|
+
--failed-only Only delete failed runs (result === FAILED)
|
|
1219
|
+
--help Show this help message
|
|
1220
|
+
|
|
1221
|
+
BEHAVIOR:
|
|
1222
|
+
- Filters are applied independently and compose
|
|
1223
|
+
- --failed-only filters to only FAILED status before other filters
|
|
1224
|
+
- --keep-latest keeps N newest per site, regardless of status
|
|
1225
|
+
- Combine flags: guardian cleanup --older-than 7d --failed-only
|
|
1226
|
+
- Deletes using real fs.rmSync() with recursive flag
|
|
1227
|
+
|
|
1228
|
+
EXAMPLE:
|
|
1229
|
+
guardian cleanup --older-than 7d
|
|
1230
|
+
guardian cleanup --keep-latest 3
|
|
1231
|
+
guardian cleanup --older-than 30d --failed-only
|
|
1232
|
+
guardian cleanup --artifacts ./.odavlguardian --keep-latest 5
|
|
1233
|
+
`);
|
|
1234
|
+
}
|
|
1235
|
+
|
|
605
1236
|
function printHelpProtect() {
|
|
606
1237
|
console.log(`
|
|
607
1238
|
Usage: guardian protect <url> [options]
|
|
608
1239
|
|
|
609
1240
|
WHAT IT DOES:
|
|
610
|
-
|
|
611
|
-
|
|
1241
|
+
Full market reality test with startup policy.
|
|
1242
|
+
Deeper than smoke; runs full discovery, attempts, and baseline comparison.
|
|
612
1243
|
|
|
613
1244
|
OPTIONS:
|
|
614
1245
|
<url> Target URL (required)
|
|
@@ -635,7 +1266,7 @@ function printHelpSmoke() {
|
|
|
635
1266
|
Usage: guardian smoke <url>
|
|
636
1267
|
|
|
637
1268
|
WHAT IT DOES:
|
|
638
|
-
Fast
|
|
1269
|
+
Fast market sanity check (<30s).
|
|
639
1270
|
Runs only critical paths: homepage reachability, navigation probe,
|
|
640
1271
|
auth (login or signup), and contact/support if present.
|
|
641
1272
|
|
|
@@ -677,7 +1308,7 @@ Usage: guardian attempt --url <baseUrl> --attempt <id> [options]
|
|
|
677
1308
|
Options:
|
|
678
1309
|
--url <url> Target URL (required)
|
|
679
1310
|
--attempt <id> Attempt ID (default: contact_form)
|
|
680
|
-
--artifacts <dir> Artifacts directory (default:
|
|
1311
|
+
--artifacts <dir> Artifacts directory (default: ./.odavlguardian)
|
|
681
1312
|
--headful Run with visible browser (default: headless)
|
|
682
1313
|
--no-trace Disable trace recording
|
|
683
1314
|
--no-screenshots Disable screenshot capture
|
|
@@ -690,6 +1321,56 @@ Exit Codes:
|
|
|
690
1321
|
`);
|
|
691
1322
|
}
|
|
692
1323
|
|
|
1324
|
+
function printHelpJourneyScan() {
|
|
1325
|
+
console.log(`
|
|
1326
|
+
Usage: guardian journey-scan <url> [options]
|
|
1327
|
+
|
|
1328
|
+
WHAT IT DOES:
|
|
1329
|
+
Human journey scan — Tests a single critical user flow end-to-end.
|
|
1330
|
+
Opens a real browser, follows a predetermined journey, captures evidence,
|
|
1331
|
+
and outputs a human-readable report with a single decision:
|
|
1332
|
+
|
|
1333
|
+
✅ SAFE (all steps succeeded)
|
|
1334
|
+
⚠️ RISK (partial success with failures)
|
|
1335
|
+
🚫 DO_NOT_LAUNCH (complete failure)
|
|
1336
|
+
|
|
1337
|
+
OPTIONS:
|
|
1338
|
+
<url> Target URL (required)
|
|
1339
|
+
--preset <name> saas | shop | landing (default: saas)
|
|
1340
|
+
--out <dir> Output directory (default: ./.odavlguardian)
|
|
1341
|
+
--timeout <ms> Step timeout in milliseconds (default: 20000)
|
|
1342
|
+
--headful Run headed browser (show UI)
|
|
1343
|
+
--help Show help
|
|
1344
|
+
|
|
1345
|
+
EXAMPLES:
|
|
1346
|
+
guardian journey-scan https://example.com
|
|
1347
|
+
guardian journey-scan https://example.com --preset shop --out ./results
|
|
1348
|
+
guardian journey-scan https://example.com --preset landing --headful
|
|
1349
|
+
|
|
1350
|
+
OUTPUT:
|
|
1351
|
+
SUMMARY.txt Human-readable summary
|
|
1352
|
+
summary.md Markdown summary
|
|
1353
|
+
report.json Full journey results
|
|
1354
|
+
screenshots/ Evidence screenshots per step
|
|
1355
|
+
metadata.json Scan metadata
|
|
1356
|
+
|
|
1357
|
+
EXIT CODES:
|
|
1358
|
+
0 READY (all steps succeeded)
|
|
1359
|
+
1 FRICTION (partial failures)
|
|
1360
|
+
2 DO_NOT_LAUNCH (complete failure)
|
|
1361
|
+
`);
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
function printHelpLive() {
|
|
1365
|
+
console.log('Usage: guardian live <url> [options]');
|
|
1366
|
+
console.log('Options:');
|
|
1367
|
+
console.log(' --interval <minutes> Run periodically; omit to run once');
|
|
1368
|
+
console.log(' --out <dir> Output directory');
|
|
1369
|
+
console.log(' --preset <name> Override journey preset');
|
|
1370
|
+
console.log(' --headful Run with visible browser');
|
|
1371
|
+
console.log(' --timeout <ms> Step timeout');
|
|
1372
|
+
}
|
|
1373
|
+
|
|
693
1374
|
function printHelpScan() {
|
|
694
1375
|
console.log(`
|
|
695
1376
|
Usage: guardian scan <url> [options]
|
|
@@ -704,7 +1385,7 @@ WHAT IT DOES:
|
|
|
704
1385
|
|
|
705
1386
|
OPTIONS:
|
|
706
1387
|
<url> Target URL (required)
|
|
707
|
-
--preset <name> landing | saas | shop (opinionated defaults)
|
|
1388
|
+
--preset <name> landing | landing-demo | saas | shop (opinionated defaults)
|
|
708
1389
|
--policy <path|preset> Override policy file or preset:name
|
|
709
1390
|
--artifacts <dir> Artifacts directory (default: ./artifacts)
|
|
710
1391
|
--headful Run headed browser
|
|
@@ -721,6 +1402,7 @@ PERFORMANCE (Phase 7.1):
|
|
|
721
1402
|
|
|
722
1403
|
EXAMPLES:
|
|
723
1404
|
guardian scan https://example.com --preset landing
|
|
1405
|
+
guardian scan https://example.com --preset landing-demo
|
|
724
1406
|
guardian scan https://example.com --preset saas
|
|
725
1407
|
guardian scan https://example.com --fast --fail-fast
|
|
726
1408
|
`);
|
|
@@ -746,7 +1428,7 @@ Options:
|
|
|
746
1428
|
--url <url> Target URL (required)
|
|
747
1429
|
--attempts <id1,id2> Comma-separated attempt IDs (default: curated 3)
|
|
748
1430
|
--name <baselineName> Baseline name (default: baseline)
|
|
749
|
-
--artifacts <dir> Artifacts directory (default:
|
|
1431
|
+
--artifacts <dir> Artifacts directory (default: ./.odavlguardian)
|
|
750
1432
|
--headful Run headed browser (default: headless)
|
|
751
1433
|
--no-trace Disable trace recording
|
|
752
1434
|
--no-screenshots Disable screenshots
|
|
@@ -765,7 +1447,7 @@ Options:
|
|
|
765
1447
|
--url <url> Target URL (required)
|
|
766
1448
|
--name <baselineName> Baseline name to compare against (required)
|
|
767
1449
|
--attempts <id1,id2> Comma-separated attempt IDs (default: curated 3)
|
|
768
|
-
--artifacts <dir> Artifacts directory (default:
|
|
1450
|
+
--artifacts <dir> Artifacts directory (default: ./.odavlguardian)
|
|
769
1451
|
--headful Run headed browser (default: headless)
|
|
770
1452
|
--no-trace Disable trace recording
|
|
771
1453
|
--no-screenshots Disable screenshots
|
|
@@ -783,22 +1465,103 @@ Exit Codes:
|
|
|
783
1465
|
async function main() {
|
|
784
1466
|
const args = process.argv.slice(2);
|
|
785
1467
|
|
|
786
|
-
//
|
|
787
|
-
|
|
1468
|
+
// Phase 8: Helper to check plan before scan
|
|
1469
|
+
function checkPlanBeforeScan(config, options = {}) {
|
|
1470
|
+
const { recordUsage = true } = options;
|
|
788
1471
|
try {
|
|
789
|
-
const
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
1472
|
+
const url = config.url || config.baseUrl || '';
|
|
1473
|
+
if (!url) return; // No URL to check
|
|
1474
|
+
|
|
1475
|
+
// Phase 10: Register user on first scan
|
|
1476
|
+
registerUser();
|
|
1477
|
+
|
|
1478
|
+
const check = checkCanScan(url);
|
|
1479
|
+
if (!check.allowed) {
|
|
1480
|
+
// LEVEL 1 TRANSPARENT GATING: emit clear message + artifacts
|
|
1481
|
+
const fs = require('fs');
|
|
1482
|
+
const path = require('path');
|
|
1483
|
+
const artifactsDir = config.artifactsDir || './.odavlguardian';
|
|
1484
|
+
try { if (!fs.existsSync(artifactsDir)) fs.mkdirSync(artifactsDir, { recursive: true }); } catch (_) {}
|
|
1485
|
+
|
|
1486
|
+
const now = new Date().toISOString().replace(/[:\-]/g, '').substring(0, 15).replace('T', '-');
|
|
1487
|
+
const runId = `gated-${now}`;
|
|
1488
|
+
const runDir = path.join(artifactsDir, runId);
|
|
1489
|
+
try { fs.mkdirSync(runDir, { recursive: true }); } catch (_) {}
|
|
1490
|
+
|
|
1491
|
+
const upgradeMsg = getUpgradeMessage();
|
|
1492
|
+
|
|
1493
|
+
// Write decision.json with canonical verdict and next steps
|
|
1494
|
+
const decision = {
|
|
1495
|
+
runId,
|
|
1496
|
+
url,
|
|
1497
|
+
timestamp: new Date().toISOString(),
|
|
1498
|
+
preset: config.preset || 'default',
|
|
1499
|
+
policyName: 'Plan Gate',
|
|
1500
|
+
finalVerdict: 'FRICTION',
|
|
1501
|
+
exitCode: 1,
|
|
1502
|
+
reasons: [
|
|
1503
|
+
{ code: 'PLAN_GATE', message: check.message },
|
|
1504
|
+
{ code: 'NEXT_STEPS', message: upgradeMsg }
|
|
1505
|
+
],
|
|
1506
|
+
gating: {
|
|
1507
|
+
reason: check.message,
|
|
1508
|
+
nextSteps: upgradeMsg
|
|
1509
|
+
}
|
|
1510
|
+
};
|
|
1511
|
+
try { fs.writeFileSync(path.join(runDir, 'decision.json'), JSON.stringify(decision, null, 2), 'utf8'); } catch (_) {}
|
|
1512
|
+
|
|
1513
|
+
// Write summary.md with friendly next steps
|
|
1514
|
+
const lines = [];
|
|
1515
|
+
lines.push('# Guardian Reality Summary');
|
|
1516
|
+
lines.push('');
|
|
1517
|
+
lines.push('## Final Verdict');
|
|
1518
|
+
lines.push('- Verdict: FRICTION (exit 1)');
|
|
1519
|
+
lines.push('- Why this verdict: Reality run was gated by plan limits.');
|
|
1520
|
+
lines.push('');
|
|
1521
|
+
lines.push('## What Happened');
|
|
1522
|
+
lines.push(`- ${check.message}`);
|
|
1523
|
+
lines.push('');
|
|
1524
|
+
lines.push('## What To Do Next');
|
|
1525
|
+
lines.push(`- ${upgradeMsg}`);
|
|
1526
|
+
try { fs.writeFileSync(path.join(runDir, 'summary.md'), lines.join('\n'), 'utf8'); } catch (_) {}
|
|
1527
|
+
|
|
1528
|
+
// Crystal-clear CLI message
|
|
1529
|
+
console.log('\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
|
1530
|
+
console.log('Guardian Reality was gated by plan limits');
|
|
1531
|
+
console.log('');
|
|
1532
|
+
console.log(`Why: ${check.message}`);
|
|
1533
|
+
console.log(`Next steps: ${upgradeMsg}`);
|
|
1534
|
+
console.log('');
|
|
1535
|
+
console.log(`Artifacts: ${runDir}`);
|
|
1536
|
+
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
|
1537
|
+
process.exit(1);
|
|
1538
|
+
}
|
|
1539
|
+
|
|
1540
|
+
// Show usage info
|
|
1541
|
+
if (check.usage && check.usage.scansRemaining !== 'Unlimited') {
|
|
1542
|
+
console.log(`ℹ️ Scans remaining this month: ${check.usage.scansRemaining}\n`);
|
|
1543
|
+
}
|
|
1544
|
+
|
|
1545
|
+
// Record the scan
|
|
1546
|
+
if (recordUsage) {
|
|
1547
|
+
performScan(url);
|
|
1548
|
+
|
|
1549
|
+
// Phase 10: Track first scan signal
|
|
1550
|
+
recordFirstScan();
|
|
1551
|
+
}
|
|
1552
|
+
} catch (error) {
|
|
1553
|
+
console.error(`\n❌ ${error.message}`);
|
|
1554
|
+
console.log(getUpgradeMessage());
|
|
794
1555
|
process.exit(1);
|
|
795
1556
|
}
|
|
796
1557
|
}
|
|
797
1558
|
|
|
1559
|
+
// Minimal release flag: print version and exit
|
|
798
1560
|
// PHASE 6: First-run welcome (only once)
|
|
799
|
-
if (args.length > 0 && !['--help', '-h', 'init', 'presets'].includes(args[0])) {
|
|
1561
|
+
if (args.length > 0 && !['--help', '-h', 'init', 'presets', 'template'].includes(args[0])) {
|
|
800
1562
|
if (isFirstRun('.odavl-guardian')) {
|
|
801
1563
|
printWelcome('ODAVL Guardian');
|
|
1564
|
+
printFirstRunHint();
|
|
802
1565
|
markAsRun('.odavl-guardian');
|
|
803
1566
|
}
|
|
804
1567
|
}
|
|
@@ -809,29 +1572,49 @@ async function main() {
|
|
|
809
1572
|
|
|
810
1573
|
Usage: guardian <subcommand> [options]
|
|
811
1574
|
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
reality
|
|
1575
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1576
|
+
LEVEL 1 — GOLDEN PATH (Reality Only)
|
|
1577
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1578
|
+
|
|
1579
|
+
reality --url <URL> Full Market Reality Check
|
|
1580
|
+
→ Canonical Verdicts: READY / FRICTION / DO_NOT_LAUNCH
|
|
1581
|
+
→ Outputs: summary.md, decision.json, market-report.html
|
|
1582
|
+
→ Artifacts: ./.odavlguardian/
|
|
817
1583
|
|
|
818
|
-
|
|
1584
|
+
--url <URL> Alias for: guardian reality --url <URL>
|
|
1585
|
+
|
|
1586
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1587
|
+
QUICK START
|
|
1588
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1589
|
+
|
|
1590
|
+
# Run Reality Check (Level 1 Golden Path)
|
|
1591
|
+
guardian reality --url https://example.com
|
|
1592
|
+
|
|
1593
|
+
# Same, using alias
|
|
1594
|
+
guardian --url https://example.com
|
|
1595
|
+
|
|
1596
|
+
# List completed runs
|
|
1597
|
+
guardian list
|
|
1598
|
+
|
|
1599
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1600
|
+
ADVANCED COMMANDS (Level 2+)
|
|
1601
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
1602
|
+
|
|
1603
|
+
scan <url> One-command product scan
|
|
1604
|
+
journey-scan <url> Human journey scan
|
|
819
1605
|
attempt Execute a single user attempt
|
|
820
|
-
|
|
821
|
-
baseline check
|
|
1606
|
+
smoke <url> Fast market sanity check (<30s)
|
|
1607
|
+
baseline save/check Baseline management
|
|
1608
|
+
list List completed runs
|
|
1609
|
+
cleanup Manage and delete old runs
|
|
1610
|
+
init Initialize Guardian
|
|
1611
|
+
template <type> Generate config template
|
|
1612
|
+
plan / upgrade Show plan or upgrade
|
|
1613
|
+
sites / users / audit Enterprise management
|
|
1614
|
+
export Export reports (PDF)
|
|
822
1615
|
presets List available policy presets
|
|
823
1616
|
|
|
824
|
-
|
|
825
|
-
# Initialize Guardian
|
|
826
|
-
guardian init
|
|
827
|
-
|
|
828
|
-
# Quick protect (uses startup policy)
|
|
829
|
-
guardian protect https://example.com
|
|
830
|
-
|
|
831
|
-
# Full reality check with policy
|
|
832
|
-
guardian reality --url https://example.com --policy preset:saas
|
|
833
|
-
|
|
834
|
-
Run 'guardian <subcommand> --help' for more information.
|
|
1617
|
+
Run 'guardian <subcommand> --help' for detailed command help.
|
|
835
1618
|
`);
|
|
836
1619
|
process.exit(0);
|
|
837
1620
|
}
|
|
@@ -847,15 +1630,517 @@ Run 'guardian <subcommand> --help' for more information.
|
|
|
847
1630
|
} else if (parsed.subcommand === 'presets') {
|
|
848
1631
|
printPresets();
|
|
849
1632
|
process.exit(0);
|
|
1633
|
+
} else if (parsed.subcommand === 'template') {
|
|
1634
|
+
const template = config.template;
|
|
1635
|
+
if (!template) {
|
|
1636
|
+
console.log('\n📋 Guardian Templates\n');
|
|
1637
|
+
const templates = listTemplates();
|
|
1638
|
+
templates.forEach(t => {
|
|
1639
|
+
console.log(` ${t.name}: ${t.description} (${t.journeys} journeys)`);
|
|
1640
|
+
});
|
|
1641
|
+
console.log('\nUsage: guardian template <saas|shop|landing> [--output file.json]\n');
|
|
1642
|
+
process.exit(0);
|
|
1643
|
+
}
|
|
1644
|
+
try {
|
|
1645
|
+
const result = generateTemplate(template, { output: config.output });
|
|
1646
|
+
console.log(`\n✅ ${result.message}`);
|
|
1647
|
+
console.log(` Generated config ready to use with: guardian reality --config ${result.outputPath}\n`);
|
|
1648
|
+
process.exit(0);
|
|
1649
|
+
} catch (err) {
|
|
1650
|
+
console.error(`\n❌ ${err.message}\n`);
|
|
1651
|
+
process.exit(1);
|
|
1652
|
+
}
|
|
1653
|
+
} else if (parsed.subcommand === 'plan') {
|
|
1654
|
+
// Phase 8: Show current plan and usage
|
|
1655
|
+
const summary = getPlanSummary();
|
|
1656
|
+
console.log(`\n🛡️ ODAVL Guardian Plan\n`);
|
|
1657
|
+
|
|
1658
|
+
// Phase 10: Show founder status
|
|
1659
|
+
const founderMsg = getFounderMessage();
|
|
1660
|
+
if (founderMsg) {
|
|
1661
|
+
console.log(founderMsg);
|
|
1662
|
+
console.log();
|
|
1663
|
+
}
|
|
1664
|
+
|
|
1665
|
+
console.log(`Current Plan: ${summary.plan.name.toUpperCase()}`);
|
|
1666
|
+
if (summary.plan.price > 0) {
|
|
1667
|
+
console.log(`Price: $${summary.plan.price}/month`);
|
|
1668
|
+
}
|
|
1669
|
+
console.log();
|
|
1670
|
+
console.log(`Usage This Month:`);
|
|
1671
|
+
console.log(` Scans: ${summary.limits.scans.used}/${summary.limits.scans.max === -1 ? 'Unlimited' : summary.limits.scans.max} (${summary.limits.scans.remaining} remaining)`);
|
|
1672
|
+
console.log(` Sites: ${summary.limits.sites.used}/${summary.limits.sites.max === -1 ? 'Unlimited' : summary.limits.sites.max}`);
|
|
1673
|
+
console.log();
|
|
1674
|
+
console.log(`Features:`);
|
|
1675
|
+
console.log(` Live Guardian: ${summary.features.liveGuardian ? '✅' : '❌'}`);
|
|
1676
|
+
console.log(` CI/CD Mode: ${summary.features.ciMode ? '✅' : '❌'}`);
|
|
1677
|
+
console.log(` Alerts: ${summary.features.alerts ? '✅' : '❌'}`);
|
|
1678
|
+
console.log();
|
|
1679
|
+
if (summary.plan.id === 'free') {
|
|
1680
|
+
console.log(`Upgrade to Pro for unlimited scans and advanced features.`);
|
|
1681
|
+
console.log(`Run: guardian upgrade pro\n`);
|
|
1682
|
+
}
|
|
1683
|
+
process.exit(0);
|
|
1684
|
+
} else if (parsed.subcommand === 'upgrade') {
|
|
1685
|
+
// Phase 8: Upgrade to a plan
|
|
1686
|
+
const targetPlan = config.plan;
|
|
1687
|
+
if (!targetPlan || !['pro', 'business'].includes(targetPlan.toLowerCase())) {
|
|
1688
|
+
console.error('\n❌ Please specify a valid plan: pro or business');
|
|
1689
|
+
console.log('\nUsage: guardian upgrade <pro|business>\n');
|
|
1690
|
+
process.exit(1);
|
|
1691
|
+
}
|
|
1692
|
+
const { getCheckoutUrl } = require('../src/payments/stripe-checkout');
|
|
1693
|
+
const checkoutUrl = getCheckoutUrl(targetPlan);
|
|
1694
|
+
console.log(`\n🚀 Upgrade to ${targetPlan.toUpperCase()}\n`);
|
|
1695
|
+
console.log(`Open this URL to complete your upgrade:\n`);
|
|
1696
|
+
console.log(` ${checkoutUrl}\n`);
|
|
1697
|
+
console.log(`After payment, your plan will be automatically activated.\n`);
|
|
1698
|
+
|
|
1699
|
+
// Phase 10: Record upgrade signal
|
|
1700
|
+
recordFirstUpgrade(targetPlan.toLowerCase());
|
|
1701
|
+
|
|
1702
|
+
process.exit(0);
|
|
1703
|
+
} else if (parsed.subcommand === 'feedback') {
|
|
1704
|
+
// Phase 10: Feedback session
|
|
1705
|
+
try {
|
|
1706
|
+
await runFeedbackSession();
|
|
1707
|
+
process.exit(0);
|
|
1708
|
+
} catch (err) {
|
|
1709
|
+
console.error(`\n❌ Feedback error: ${err.message}\n`);
|
|
1710
|
+
process.exit(1);
|
|
1711
|
+
}
|
|
1712
|
+
} else if (parsed.subcommand === 'sites') {
|
|
1713
|
+
// Phase 11: Multi-site management
|
|
1714
|
+
try {
|
|
1715
|
+
requirePermission('site:view', 'manage sites');
|
|
1716
|
+
|
|
1717
|
+
if (config.action === 'add') {
|
|
1718
|
+
requirePermission('site:add', 'add sites');
|
|
1719
|
+
if (!config.name || !config.url) {
|
|
1720
|
+
console.error('Usage: guardian sites add <name> <url> [--project <name>]');
|
|
1721
|
+
process.exit(2);
|
|
1722
|
+
}
|
|
1723
|
+
const site = addSite(config.name, config.url, config.project);
|
|
1724
|
+
console.log(`✓ Site added: ${site.name} (${site.project})`);
|
|
1725
|
+
logAudit(AUDIT_ACTIONS.SITE_ADD, { name: site.name, url: site.url, project: site.project });
|
|
1726
|
+
} else if (config.action === 'remove') {
|
|
1727
|
+
requirePermission('site:remove', 'remove sites');
|
|
1728
|
+
if (!config.name) {
|
|
1729
|
+
console.error('Usage: guardian sites remove <name>');
|
|
1730
|
+
process.exit(2);
|
|
1731
|
+
}
|
|
1732
|
+
const site = removeSite(config.name);
|
|
1733
|
+
console.log(`✓ Site removed: ${site.name}`);
|
|
1734
|
+
logAudit(AUDIT_ACTIONS.SITE_REMOVE, { name: site.name });
|
|
1735
|
+
} else {
|
|
1736
|
+
// List sites
|
|
1737
|
+
const data = getSites();
|
|
1738
|
+
if (data.sites.length === 0) {
|
|
1739
|
+
console.log('No sites registered yet.');
|
|
1740
|
+
} else {
|
|
1741
|
+
console.log(`\nTotal sites: ${data.sites.length}\n`);
|
|
1742
|
+
const projects = listProjects();
|
|
1743
|
+
for (const proj of projects) {
|
|
1744
|
+
console.log(`📁 ${proj.name} (${proj.siteCount} site(s))`);
|
|
1745
|
+
const sites = getSitesByProject(proj.name);
|
|
1746
|
+
for (const site of sites) {
|
|
1747
|
+
console.log(` - ${site.name}: ${site.url}`);
|
|
1748
|
+
if (site.lastScannedAt) {
|
|
1749
|
+
console.log(` Last scan: ${site.lastScannedAt} (${site.scanCount} total)`);
|
|
1750
|
+
}
|
|
1751
|
+
}
|
|
1752
|
+
}
|
|
1753
|
+
}
|
|
1754
|
+
}
|
|
1755
|
+
process.exit(0);
|
|
1756
|
+
} catch (err) {
|
|
1757
|
+
console.error(`\n❌ Sites error: ${err.message}\n`);
|
|
1758
|
+
process.exit(1);
|
|
1759
|
+
}
|
|
1760
|
+
} else if (parsed.subcommand === 'users') {
|
|
1761
|
+
// Phase 11: User/role management
|
|
1762
|
+
try {
|
|
1763
|
+
requirePermission('user:view', 'manage users');
|
|
1764
|
+
|
|
1765
|
+
if (config.action === 'add') {
|
|
1766
|
+
requirePermission('user:add', 'add users');
|
|
1767
|
+
if (!config.username) {
|
|
1768
|
+
console.error('Usage: guardian users add <username> [role]');
|
|
1769
|
+
process.exit(2);
|
|
1770
|
+
}
|
|
1771
|
+
const user = addUser(config.username, config.role);
|
|
1772
|
+
console.log(`✓ User added: ${user.username} (${user.role})`);
|
|
1773
|
+
logAudit(AUDIT_ACTIONS.USER_ADD, { username: user.username, role: user.role });
|
|
1774
|
+
} else if (config.action === 'remove') {
|
|
1775
|
+
requirePermission('user:remove', 'remove users');
|
|
1776
|
+
if (!config.username) {
|
|
1777
|
+
console.error('Usage: guardian users remove <username>');
|
|
1778
|
+
process.exit(2);
|
|
1779
|
+
}
|
|
1780
|
+
const user = removeUser(config.username);
|
|
1781
|
+
console.log(`✓ User removed: ${user.username}`);
|
|
1782
|
+
logAudit(AUDIT_ACTIONS.USER_REMOVE, { username: user.username });
|
|
1783
|
+
} else if (config.action === 'roles') {
|
|
1784
|
+
// List roles
|
|
1785
|
+
const roles = listRoles();
|
|
1786
|
+
console.log('\nAvailable roles:\n');
|
|
1787
|
+
for (const role of roles) {
|
|
1788
|
+
console.log(`${role.name}:`);
|
|
1789
|
+
console.log(` Permissions: ${role.permissions.join(', ')}`);
|
|
1790
|
+
}
|
|
1791
|
+
} else {
|
|
1792
|
+
// List users
|
|
1793
|
+
const data = getUsers();
|
|
1794
|
+
console.log(`\nTotal users: ${data.users.length}\n`);
|
|
1795
|
+
for (const user of data.users) {
|
|
1796
|
+
const current = user.username === getCurrentUser().username ? ' (current)' : '';
|
|
1797
|
+
console.log(`- ${user.username}: ${user.role}${current}`);
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
process.exit(0);
|
|
1801
|
+
} catch (err) {
|
|
1802
|
+
console.error(`\n❌ Users error: ${err.message}\n`);
|
|
1803
|
+
process.exit(1);
|
|
1804
|
+
}
|
|
1805
|
+
} else if (parsed.subcommand === 'audit') {
|
|
1806
|
+
// Phase 11: Audit log viewing
|
|
1807
|
+
try {
|
|
1808
|
+
requirePermission('audit:view', 'view audit logs');
|
|
1809
|
+
|
|
1810
|
+
if (config.action === 'summary') {
|
|
1811
|
+
const summary = getAuditSummary();
|
|
1812
|
+
console.log('\nAudit Summary:\n');
|
|
1813
|
+
console.log(`Total logs: ${summary.totalLogs}`);
|
|
1814
|
+
console.log(`First log: ${summary.firstLog || 'N/A'}`);
|
|
1815
|
+
console.log(`Last log: ${summary.lastLog || 'N/A'}`);
|
|
1816
|
+
console.log('\nActions:');
|
|
1817
|
+
for (const [action, count] of Object.entries(summary.actionCounts)) {
|
|
1818
|
+
console.log(` ${action}: ${count}`);
|
|
1819
|
+
}
|
|
1820
|
+
console.log('\nUsers:');
|
|
1821
|
+
for (const [user, count] of Object.entries(summary.userCounts)) {
|
|
1822
|
+
console.log(` ${user}: ${count}`);
|
|
1823
|
+
}
|
|
1824
|
+
} else {
|
|
1825
|
+
// List logs
|
|
1826
|
+
const logs = readAuditLogs({
|
|
1827
|
+
limit: config.limit,
|
|
1828
|
+
action: config.actionFilter,
|
|
1829
|
+
user: config.user
|
|
1830
|
+
});
|
|
1831
|
+
|
|
1832
|
+
if (logs.length === 0) {
|
|
1833
|
+
console.log('No audit logs found.');
|
|
1834
|
+
} else {
|
|
1835
|
+
console.log(`\nShowing ${logs.length} log(s):\n`);
|
|
1836
|
+
for (const log of logs) {
|
|
1837
|
+
console.log(`[${log.timestamp}] ${log.user} → ${log.action}`);
|
|
1838
|
+
if (Object.keys(log.details).length > 0) {
|
|
1839
|
+
console.log(` Details: ${JSON.stringify(log.details)}`);
|
|
1840
|
+
}
|
|
1841
|
+
}
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1844
|
+
process.exit(0);
|
|
1845
|
+
} catch (err) {
|
|
1846
|
+
console.error(`\n❌ Audit error: ${err.message}\n`);
|
|
1847
|
+
process.exit(1);
|
|
1848
|
+
}
|
|
1849
|
+
} else if (parsed.subcommand === 'export') {
|
|
1850
|
+
// Phase 11: PDF export
|
|
1851
|
+
try {
|
|
1852
|
+
requirePermission('export:pdf', 'export reports');
|
|
1853
|
+
|
|
1854
|
+
if (!config.reportId) {
|
|
1855
|
+
// List available reports
|
|
1856
|
+
const reports = listAvailableReports();
|
|
1857
|
+
if (reports.length === 0) {
|
|
1858
|
+
console.log('No reports available for export.');
|
|
1859
|
+
} else {
|
|
1860
|
+
console.log(`\nAvailable reports:\n`);
|
|
1861
|
+
for (const report of reports.slice(0, 10)) {
|
|
1862
|
+
console.log(`- ${report.id}`);
|
|
1863
|
+
console.log(` Modified: ${report.modifiedAt}`);
|
|
1864
|
+
}
|
|
1865
|
+
console.log('\nUsage: guardian export <report-id> [--output <path>]');
|
|
1866
|
+
}
|
|
1867
|
+
} else {
|
|
1868
|
+
const result = exportReportToPDF(config.reportId, config.output);
|
|
1869
|
+
console.log(`✓ Report exported to: ${result.outputPath}`);
|
|
1870
|
+
console.log(` Size: ${result.size} bytes`);
|
|
1871
|
+
logAudit(AUDIT_ACTIONS.EXPORT_PDF, { reportId: config.reportId, output: result.outputPath });
|
|
1872
|
+
}
|
|
1873
|
+
process.exit(0);
|
|
1874
|
+
} catch (err) {
|
|
1875
|
+
console.error(`\n❌ Export error: ${err.message}\n`);
|
|
1876
|
+
process.exit(1);
|
|
1877
|
+
}
|
|
1878
|
+
} else if (parsed.subcommand === 'recipe') {
|
|
1879
|
+
// Phase 12.1: Recipes
|
|
1880
|
+
try {
|
|
1881
|
+
const action = config.action;
|
|
1882
|
+
// Ensure registry includes built-ins before any trust checks
|
|
1883
|
+
getAllRecipes();
|
|
1884
|
+
|
|
1885
|
+
if (action === 'list') {
|
|
1886
|
+
const recipes = getAllRecipes();
|
|
1887
|
+
console.log(`\n📚 Available Recipes (${recipes.length} total)\n`);
|
|
1888
|
+
|
|
1889
|
+
// Group by platform
|
|
1890
|
+
const byPlatform = {};
|
|
1891
|
+
for (const recipe of recipes) {
|
|
1892
|
+
if (!byPlatform[recipe.platform]) {
|
|
1893
|
+
byPlatform[recipe.platform] = [];
|
|
1894
|
+
}
|
|
1895
|
+
byPlatform[recipe.platform].push(recipe);
|
|
1896
|
+
}
|
|
1897
|
+
|
|
1898
|
+
for (const platform of Object.keys(byPlatform).sort()) {
|
|
1899
|
+
console.log(`🏪 ${platform.toUpperCase()}`);
|
|
1900
|
+
for (const recipe of byPlatform[platform]) {
|
|
1901
|
+
const reg = getRegistryEntry(recipe.id);
|
|
1902
|
+
const checksum = computeRecipeChecksum(recipe);
|
|
1903
|
+
const mismatch = reg && reg.checksum && reg.checksum !== checksum;
|
|
1904
|
+
const sourceLabel = reg ? reg.source : 'unknown';
|
|
1905
|
+
const trustNote = mismatch ? ' [checksum mismatch]' : '';
|
|
1906
|
+
console.log(` • ${recipe.id} - ${recipe.name} [${sourceLabel}]${trustNote}`);
|
|
1907
|
+
}
|
|
1908
|
+
console.log();
|
|
1909
|
+
}
|
|
1910
|
+
} else if (action === 'show') {
|
|
1911
|
+
if (!config.id) {
|
|
1912
|
+
console.error('Usage: guardian recipe show <id>');
|
|
1913
|
+
process.exit(2);
|
|
1914
|
+
}
|
|
1915
|
+
|
|
1916
|
+
const recipe = getRecipe(config.id);
|
|
1917
|
+
if (!recipe) {
|
|
1918
|
+
console.error(`Recipe not found: ${config.id}`);
|
|
1919
|
+
process.exit(1);
|
|
1920
|
+
}
|
|
1921
|
+
|
|
1922
|
+
console.log();
|
|
1923
|
+
console.log(formatRecipe(recipe));
|
|
1924
|
+
const reg = getRegistryEntry(recipe.id);
|
|
1925
|
+
const checksum = computeRecipeChecksum(recipe);
|
|
1926
|
+
const mismatch = reg && reg.checksum && reg.checksum !== checksum;
|
|
1927
|
+
if (reg) {
|
|
1928
|
+
console.log(`Source: ${reg.source}`);
|
|
1929
|
+
console.log(`Version: ${reg.version}`);
|
|
1930
|
+
console.log(`Checksum: ${reg.checksum}`);
|
|
1931
|
+
if (mismatch) {
|
|
1932
|
+
console.log('⚠️ Warning: checksum mismatch — recipe may have been modified');
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
} else if (action === 'run') {
|
|
1936
|
+
if (!config.id || !config.url) {
|
|
1937
|
+
console.error('Usage: guardian recipe run <id> --url <url>');
|
|
1938
|
+
process.exit(2);
|
|
1939
|
+
}
|
|
1940
|
+
|
|
1941
|
+
const recipe = getRecipe(config.id);
|
|
1942
|
+
if (!recipe) {
|
|
1943
|
+
console.error(`Recipe not found: ${config.id}`);
|
|
1944
|
+
process.exit(1);
|
|
1945
|
+
}
|
|
1946
|
+
|
|
1947
|
+
// Enforce plan limits without counting recipe execution as a scan
|
|
1948
|
+
checkPlanBeforeScan({ url: config.url }, { recordUsage: false });
|
|
1949
|
+
|
|
1950
|
+
// Log recipe execution
|
|
1951
|
+
logAudit('recipe:run', { recipeId: recipe.id, url: config.url });
|
|
1952
|
+
|
|
1953
|
+
// Phase B: Execute recipe as enforced runtime
|
|
1954
|
+
(async () => {
|
|
1955
|
+
const { executeRecipeRuntime } = require('../src/recipes/recipe-runtime');
|
|
1956
|
+
const { recipeFailureToAttempt, assessRecipeImpact } = require('../src/recipes/recipe-failure-analysis');
|
|
1957
|
+
|
|
1958
|
+
console.log(`\n▶️ Executing recipe: ${recipe.name}`);
|
|
1959
|
+
console.log(` URL: ${config.url}`);
|
|
1960
|
+
console.log(` Steps: ${recipe.steps.length}\n`);
|
|
1961
|
+
|
|
1962
|
+
try {
|
|
1963
|
+
const result = await executeRecipeRuntime(config.id, config.url, { timeout: 20000 });
|
|
1964
|
+
|
|
1965
|
+
if (result.success) {
|
|
1966
|
+
console.log(`✅ RECIPE PASSED: ${recipe.name}`);
|
|
1967
|
+
console.log(` Goal reached: ${recipe.expectedGoal}`);
|
|
1968
|
+
console.log(` Duration: ${result.duration}s`);
|
|
1969
|
+
process.exit(0);
|
|
1970
|
+
} else {
|
|
1971
|
+
console.log(`❌ RECIPE FAILED: ${recipe.name}`);
|
|
1972
|
+
console.log(` Reason: ${result.failureReason}`);
|
|
1973
|
+
if (result.failedStep) {
|
|
1974
|
+
const stepNum = parseInt(result.failedStep.split('-').pop(), 10) + 1;
|
|
1975
|
+
console.log(` Failed at step ${stepNum} of ${result.steps.length}`);
|
|
1976
|
+
}
|
|
1977
|
+
console.log(` Duration: ${result.duration}s\n`);
|
|
1978
|
+
|
|
1979
|
+
// Integrate failure into decision engine
|
|
1980
|
+
const attemptForm = recipeFailureToAttempt(result);
|
|
1981
|
+
const impact = assessRecipeImpact(result);
|
|
1982
|
+
|
|
1983
|
+
console.log(` Risk Assessment: ${impact.severity} (score: ${impact.riskScore}/100)`);
|
|
1984
|
+
console.log(` Impact: ${impact.message}\n`);
|
|
1985
|
+
|
|
1986
|
+
process.exit(1);
|
|
1987
|
+
}
|
|
1988
|
+
} catch (err) {
|
|
1989
|
+
console.error(`\n❌ Recipe execution error: ${err.message}\n`);
|
|
1990
|
+
process.exit(2);
|
|
1991
|
+
}
|
|
1992
|
+
})();
|
|
1993
|
+
} else if (action === 'export') {
|
|
1994
|
+
if (!config.id || !config.out) {
|
|
1995
|
+
console.error('Usage: guardian recipe export <id> --out <file>');
|
|
1996
|
+
process.exit(2);
|
|
1997
|
+
}
|
|
1998
|
+
requirePermission('recipe:manage', 'export recipes');
|
|
1999
|
+
const result = exportRecipeWithMetadata(config.id, config.out);
|
|
2000
|
+
logAudit(AUDIT_ACTIONS.RECIPE_EXPORT, { recipeId: config.id, output: config.out, checksum: result.checksum });
|
|
2001
|
+
console.log(`\n✓ Recipe exported`);
|
|
2002
|
+
console.log(` File: ${config.out}`);
|
|
2003
|
+
console.log(` Checksum: ${result.checksum}`);
|
|
2004
|
+
} else if (action === 'import') {
|
|
2005
|
+
if (!config.file) {
|
|
2006
|
+
console.error('Usage: guardian recipe import <file>');
|
|
2007
|
+
process.exit(2);
|
|
2008
|
+
}
|
|
2009
|
+
requirePermission('recipe:manage', 'import recipes');
|
|
2010
|
+
const result = importRecipeWithMetadata(config.file, { force: config.force });
|
|
2011
|
+
logAudit(AUDIT_ACTIONS.RECIPE_IMPORT, { recipeId: result.recipe.id, file: config.file, checksum: result.checksum, force: config.force });
|
|
2012
|
+
console.log(`\n✓ Import complete`);
|
|
2013
|
+
console.log(` Recipe: ${result.recipe.id}`);
|
|
2014
|
+
console.log(` Checksum: ${result.checksum}`);
|
|
2015
|
+
} else {
|
|
2016
|
+
console.error('Unknown recipe action. Use: list, show <id>, run <id> --url <url>, export <id> --out <file>, import <file> [--force]');
|
|
2017
|
+
process.exit(2);
|
|
2018
|
+
}
|
|
2019
|
+
|
|
2020
|
+
process.exit(0);
|
|
2021
|
+
} catch (err) {
|
|
2022
|
+
console.error(`\n❌ Recipe error: ${err.message}\n`);
|
|
2023
|
+
process.exit(1);
|
|
2024
|
+
}
|
|
2025
|
+
} else if (parsed.subcommand === 'list') {
|
|
2026
|
+
const exitCode = listRuns(config.artifactsDir, config.filters);
|
|
2027
|
+
process.exit(exitCode);
|
|
2028
|
+
} else if (parsed.subcommand === 'cleanup') {
|
|
2029
|
+
const result = await cleanup(
|
|
2030
|
+
config.artifactsDir,
|
|
2031
|
+
{
|
|
2032
|
+
olderThan: config.olderThan,
|
|
2033
|
+
keepLatest: config.keepLatest,
|
|
2034
|
+
failedOnly: config.failedOnly
|
|
2035
|
+
}
|
|
2036
|
+
);
|
|
2037
|
+
|
|
2038
|
+
console.log(`\n✓ Cleanup completed`);
|
|
2039
|
+
console.log(` Deleted: ${result.deleted} run(s)`);
|
|
2040
|
+
console.log(` Kept: ${result.kept} run(s)`);
|
|
2041
|
+
|
|
2042
|
+
if (result.errors && result.errors.length > 0) {
|
|
2043
|
+
console.log(` Errors: ${result.errors.length}`);
|
|
2044
|
+
result.errors.forEach(err => {
|
|
2045
|
+
console.log(` - ${err}`);
|
|
2046
|
+
});
|
|
2047
|
+
process.exit(1);
|
|
2048
|
+
}
|
|
2049
|
+
process.exit(0);
|
|
850
2050
|
} else if (parsed.subcommand === 'protect') {
|
|
2051
|
+
// Phase 8: Check plan limits before scan
|
|
2052
|
+
checkPlanBeforeScan(config);
|
|
851
2053
|
await runRealityCLI(config);
|
|
852
2054
|
} else if (parsed.subcommand === 'smoke') {
|
|
2055
|
+
// Phase 8: Check plan limits before scan
|
|
2056
|
+
checkPlanBeforeScan(config);
|
|
853
2057
|
await runSmokeCLI(config);
|
|
854
2058
|
} else if (parsed.subcommand === 'attempt') {
|
|
2059
|
+
// Phase 8: Check plan limits before scan
|
|
2060
|
+
checkPlanBeforeScan(config);
|
|
855
2061
|
await runAttemptCLI(config);
|
|
2062
|
+
} else if (parsed.subcommand === 'journey-scan') {
|
|
2063
|
+
// Phase 8: Check plan limits before scan
|
|
2064
|
+
checkPlanBeforeScan(config);
|
|
2065
|
+
await runJourneyScanCLI(config);
|
|
2066
|
+
} else if (parsed.subcommand === 'live') {
|
|
2067
|
+
// Phase 8: Check feature allowed for live guardian
|
|
2068
|
+
const liveCheck = checkFeatureAllowed('liveGuardian');
|
|
2069
|
+
if (!liveCheck.allowed) {
|
|
2070
|
+
console.error(`\n❌ ${liveCheck.message}`);
|
|
2071
|
+
console.log(getUpgradeMessage());
|
|
2072
|
+
process.exit(1);
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
// Phase 10: Track first live session
|
|
2076
|
+
recordFirstLive();
|
|
2077
|
+
|
|
2078
|
+
await runLiveCLI(config);
|
|
2079
|
+
} else if (parsed.subcommand === 'live-start') {
|
|
2080
|
+
// Feature check
|
|
2081
|
+
const liveCheck = checkFeatureAllowed('liveGuardian');
|
|
2082
|
+
if (!liveCheck.allowed) {
|
|
2083
|
+
console.error(`\n❌ ${liveCheck.message}`);
|
|
2084
|
+
console.log(getUpgradeMessage());
|
|
2085
|
+
process.exit(1);
|
|
2086
|
+
}
|
|
2087
|
+
// RBAC permission
|
|
2088
|
+
try { requirePermission('live:run', 'start live schedule'); } catch (e) { console.error(`\n❌ ${e.message}`); process.exit(1); }
|
|
2089
|
+
const { createSchedule, startBackgroundRunner } = require('../src/guardian/live-scheduler');
|
|
2090
|
+
const entry = createSchedule({ url: config.baseUrl, preset: config.preset, intervalMinutes: config.intervalMinutes });
|
|
2091
|
+
const runner = startBackgroundRunner();
|
|
2092
|
+
console.log('\n🟢 Live schedule started');
|
|
2093
|
+
console.log(` id: ${entry.id}`);
|
|
2094
|
+
console.log(` url: ${entry.url}`);
|
|
2095
|
+
console.log(` preset: ${entry.preset}`);
|
|
2096
|
+
console.log(` every: ${entry.intervalMinutes} min`);
|
|
2097
|
+
console.log(` nextRunAt: ${entry.nextRunAt}`);
|
|
2098
|
+
console.log(` runnerPid: ${runner.pid}`);
|
|
2099
|
+
process.exit(0);
|
|
2100
|
+
} else if (parsed.subcommand === 'live-stop') {
|
|
2101
|
+
// RBAC permission
|
|
2102
|
+
try { requirePermission('live:run', 'stop live schedule'); } catch (e) { console.error(`\n❌ ${e.message}`); process.exit(1); }
|
|
2103
|
+
const { stopSchedule } = require('../src/guardian/live-scheduler');
|
|
2104
|
+
try {
|
|
2105
|
+
const s = stopSchedule(config.id);
|
|
2106
|
+
console.log(`\n🛑 Schedule stopped: ${s.id}`);
|
|
2107
|
+
process.exit(0);
|
|
2108
|
+
} catch (err) {
|
|
2109
|
+
console.error(`\n❌ ${err.message}`);
|
|
2110
|
+
process.exit(1);
|
|
2111
|
+
}
|
|
2112
|
+
} else if (parsed.subcommand === 'live-status') {
|
|
2113
|
+
const { listSchedules, loadState } = require('../src/guardian/live-scheduler');
|
|
2114
|
+
const state = loadState();
|
|
2115
|
+
const schedules = listSchedules();
|
|
2116
|
+
console.log('\n📋 Live schedules:');
|
|
2117
|
+
for (const s of schedules) {
|
|
2118
|
+
console.log(` - ${s.id} | ${s.status} | every ${s.intervalMinutes} min`);
|
|
2119
|
+
console.log(` url: ${s.url} | preset: ${s.preset}`);
|
|
2120
|
+
console.log(` lastRunAt: ${s.lastRunAt || 'n/a'} | nextRunAt: ${s.nextRunAt || 'n/a'}`);
|
|
2121
|
+
}
|
|
2122
|
+
const pid = state.runner?.pid;
|
|
2123
|
+
console.log(`\nRunner: ${pid ? `pid ${pid}` : 'not running'}`);
|
|
2124
|
+
process.exit(0);
|
|
2125
|
+
} else if (parsed.subcommand === 'ci') {
|
|
2126
|
+
// Phase 8: Check feature allowed for CI mode
|
|
2127
|
+
const ciCheck = checkFeatureAllowed('ciMode');
|
|
2128
|
+
if (!ciCheck.allowed) {
|
|
2129
|
+
console.error(`\n❌ ${ciCheck.message}`);
|
|
2130
|
+
console.log(getUpgradeMessage());
|
|
2131
|
+
process.exit(1);
|
|
2132
|
+
}
|
|
2133
|
+
// Phase 4: CI gate mode
|
|
2134
|
+
const { runCIGate } = require('../src/guardian/ci-cli');
|
|
2135
|
+
const exitCode = await runCIGate(config);
|
|
2136
|
+
process.exit(exitCode);
|
|
856
2137
|
} else if (parsed.subcommand === 'reality') {
|
|
2138
|
+
// Phase 8: Check plan limits before scan
|
|
2139
|
+
checkPlanBeforeScan(config);
|
|
857
2140
|
await runRealityCLI(config);
|
|
858
2141
|
} else if (parsed.subcommand === 'scan') {
|
|
2142
|
+
// Phase 8: Check plan limits before scan
|
|
2143
|
+
checkPlanBeforeScan(config);
|
|
859
2144
|
// Phase 6: First-run concise guidance
|
|
860
2145
|
try {
|
|
861
2146
|
const { baselineExists } = require('../src/guardian/baseline-storage');
|