@forkpoint/agent-lighthouse 3.1.0 โ†’ 4.1.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/dist/main.js CHANGED
@@ -66,7 +66,14 @@ function createProgressRenderer(options) {
66
66
  lastRender = t;
67
67
  spinnerIndex += 1;
68
68
  write(
69
- "\r" + formatStatusLine({ spinnerIndex, label, completed, total, fraction, elapsedMs }) + "\x1B[K"
69
+ "\r" + formatStatusLine({
70
+ spinnerIndex,
71
+ label,
72
+ completed,
73
+ total,
74
+ fraction,
75
+ elapsedMs
76
+ }) + "\x1B[K"
70
77
  );
71
78
  stickyShown = true;
72
79
  };
@@ -113,6 +120,10 @@ function createProgressRenderer(options) {
113
120
 
114
121
  // src/options.ts
115
122
  var import_agent_lighthouse_core = require("@forkpoint/agent-lighthouse-core");
123
+ var PAGE_TYPE_IDS = Object.keys(import_agent_lighthouse_core.PAGE_TYPE_LABELS);
124
+ function isPageType(value) {
125
+ return PAGE_TYPE_IDS.includes(value);
126
+ }
116
127
  var DEFAULT_TRACE_FILE = "agent-lighthouse-trace.ndjson";
117
128
  function getArgValue(args2, shortFlag, longFlag) {
118
129
  for (const arg of args2) {
@@ -142,7 +153,7 @@ function resolveUrl(positional, fileConfig) {
142
153
  }
143
154
  function isValidUrl(url) {
144
155
  try {
145
- new URL(url);
156
+ void new URL(url);
146
157
  return true;
147
158
  } catch {
148
159
  return false;
@@ -151,6 +162,11 @@ function isValidUrl(url) {
151
162
  function parseCliOptions(args2, positionalUrl, fileConfig = {}) {
152
163
  const categories = splitList(getArgValue(args2, "", "--categories"));
153
164
  const minScoreArg = getArgValue(args2, "", "--min-score");
165
+ const pageTypeArg = getArgValue(args2, "", "--page-type");
166
+ const timeoutArg = getArgValue(args2, "", "--timeout");
167
+ const timeoutValid = timeoutArg !== void 0 && timeoutArg.trim() !== "" && Number.isFinite(Number(timeoutArg)) && Number(timeoutArg) >= 0;
168
+ const fileTimeout = fileConfig.timeout;
169
+ const fileTimeoutValid = fileTimeout === void 0 || typeof fileTimeout === "number" && Number.isFinite(fileTimeout) && fileTimeout >= 0;
154
170
  return {
155
171
  url: resolveUrl(positionalUrl, fileConfig),
156
172
  configPath: getArgValue(args2, "-c", "--config"),
@@ -159,26 +175,35 @@ function parseCliOptions(args2, positionalUrl, fileConfig = {}) {
159
175
  outputDir: getArgValue(args2, "-d", "--output-dir") || fileConfig.outputDir || "./reports",
160
176
  outputFormats: splitList(getArgValue(args2, "-o", "--output")) ?? fileConfig.output ?? ["terminal", "html", "json"],
161
177
  categories,
162
- unknownCategories: (categories ?? []).filter((c) => !import_agent_lighthouse_core.CATEGORY_IDS.includes(c)),
178
+ unknownCategories: (categories ?? []).filter(
179
+ (c) => !import_agent_lighthouse_core.CATEGORY_IDS.includes(c)
180
+ ),
163
181
  includeExperimental: args2.includes("--experimental"),
164
182
  isSilent: args2.includes("--silent"),
165
183
  progressJson: args2.includes("--progress-json"),
166
184
  shouldView: args2.includes("-v") || args2.includes("--view"),
167
185
  debugAudit: getArgValue(args2, "", "--debug-audit"),
186
+ pageType: pageTypeArg && isPageType(pageTypeArg) ? pageTypeArg : void 0,
187
+ invalidPageType: pageTypeArg && !isPageType(pageTypeArg) ? pageTypeArg : void 0,
168
188
  // A bare `--trace` with no path is still a request to trace, so it gets
169
189
  // the default file rather than being read as "no trace".
170
- tracePath: args2.includes("--trace") ? getArgValue(args2, "", "--trace") ?? DEFAULT_TRACE_FILE : getArgValue(args2, "", "--trace")
190
+ tracePath: args2.includes("--trace") ? getArgValue(args2, "", "--trace") ?? DEFAULT_TRACE_FILE : getArgValue(args2, "", "--trace"),
191
+ timeoutSeconds: timeoutValid ? Number(timeoutArg) : fileTimeoutValid ? fileTimeout : void 0,
192
+ invalidTimeout: timeoutArg !== void 0 && !timeoutValid ? timeoutArg : args2.includes("--timeout") && timeoutArg === void 0 ? "" : !timeoutValid && !fileTimeoutValid ? `${String(fileTimeout)} (config file)` : void 0
171
193
  };
172
194
  }
173
195
  function resolveCommand(args2) {
174
196
  const command = args2[0];
175
- if (!command || command === "-h" || command === "--help") return { action: "help" };
197
+ if (!command || command === "-h" || command === "--help")
198
+ return { action: "help" };
176
199
  if (command === "audit") return { action: "audit", url: args2[1] };
177
200
  if (!command.startsWith("-")) return { action: "audit", url: command };
178
201
  return { action: "audit" };
179
202
  }
180
203
  function parseCategoryAssertions(args2, fileConfig = {}) {
181
- const out = { ...fileConfig.assertCategories ?? {} };
204
+ const out = {
205
+ ...fileConfig.assertCategories
206
+ };
182
207
  const record = (pair) => {
183
208
  if (!pair) return;
184
209
  const [catId, min] = pair.split(":");
@@ -186,7 +211,8 @@ function parseCategoryAssertions(args2, fileConfig = {}) {
186
211
  };
187
212
  for (let i = 0; i < args2.length; i++) {
188
213
  const arg = args2[i];
189
- if (arg.startsWith("--assert-category=")) record(arg.slice("--assert-category=".length));
214
+ if (arg.startsWith("--assert-category="))
215
+ record(arg.slice("--assert-category=".length));
190
216
  else if (arg === "--assert-category") record(args2[i + 1]);
191
217
  }
192
218
  return out;
@@ -207,7 +233,9 @@ function selectDebugChecks(checks, debugAudit) {
207
233
  return checks.filter((c) => c.status === "fail" || c.status === "warn");
208
234
  }
209
235
  const needle = debugAudit.toLowerCase();
210
- return checks.filter((c) => c.id === debugAudit || c.title.toLowerCase().includes(needle));
236
+ return checks.filter(
237
+ (c) => c.id === debugAudit || c.title.toLowerCase().includes(needle)
238
+ );
211
239
  }
212
240
  function openCommand(platform, filePath) {
213
241
  if (platform === "darwin") return `open "${filePath}"`;
@@ -260,6 +288,9 @@ Options:
260
288
  that were skipped or errored. Defaults to
261
289
  ./agent-lighthouse-trace.ndjson
262
290
  --categories <list> Comma-separated list of categories to audit
291
+ --page-type <type> Declare what the target URL is: homepage, category,
292
+ product or content. Page-typed audits score only a
293
+ declared type; a detected one runs them as informative
263
294
  (access-crawl-control, content-extraction, machine-discovery,
264
295
  structured-data, answer-readiness, agent-interfaces,
265
296
  agentic-commerce, operability-safety)
@@ -268,6 +299,8 @@ Options:
268
299
  -o, --output <formats> Output formats (comma-separated: terminal, html, json, md) [default: terminal,html,json]
269
300
  -d, --output-dir <path> Output directory for generated reports [default: ./reports]
270
301
  -v, --view Automatically open the generated HTML report in your browser
302
+ --timeout <seconds> Wall-clock budget for the scan [default: 180]. When it runs out the
303
+ scan finishes with what it has; 0 disables it
271
304
  --min-score <number> Minimum score (0-100) required to pass CI assertions
272
305
  --assert-category <id:min> Per-category assertions (e.g. --assert-category structured-data:90)
273
306
  --silent Suppress progress output
@@ -302,11 +335,28 @@ async function audit(targetUrl) {
302
335
  console.error(`\x1B[31mInvalid URL:\x1B[0m ${url}`);
303
336
  process.exit(1);
304
337
  }
305
- const { isSilent, progressJson, shouldView, debugAudit, minScore, outputDir, tracePath } = opts;
338
+ const {
339
+ isSilent,
340
+ progressJson,
341
+ shouldView,
342
+ debugAudit,
343
+ minScore,
344
+ outputDir,
345
+ tracePath
346
+ } = opts;
306
347
  if (progressJson) import_agent_lighthouse_core2.logger.level = "silent";
307
348
  const presetName = opts.presetName;
308
349
  const preset = (0, import_agent_lighthouse_core2.getPreset)(presetName);
309
- const { categories, unknownCategories, includeExperimental, outputFormats } = opts;
350
+ const {
351
+ categories,
352
+ unknownCategories,
353
+ includeExperimental,
354
+ outputFormats,
355
+ pageType,
356
+ invalidPageType,
357
+ timeoutSeconds,
358
+ invalidTimeout
359
+ } = opts;
310
360
  if (unknownCategories.length > 0) {
311
361
  console.error(
312
362
  `\x1B[31mUnknown category: ${unknownCategories.join(", ")}\x1B[0m
@@ -314,6 +364,21 @@ Valid categories: ${import_agent_lighthouse_core2.CATEGORY_IDS.join(", ")}`
314
364
  );
315
365
  process.exit(1);
316
366
  }
367
+ if (invalidPageType !== void 0) {
368
+ console.error(
369
+ `\x1B[31mUnknown page type: ${invalidPageType}\x1B[0m
370
+ Valid page types: ${PAGE_TYPE_IDS.join(", ")}`
371
+ );
372
+ process.exit(1);
373
+ }
374
+ if (invalidTimeout !== void 0) {
375
+ const what = invalidTimeout === "" ? "no value given (write --timeout=<seconds> for a value that starts with -)" : invalidTimeout;
376
+ console.error(
377
+ `\x1B[31mInvalid --timeout: ${what}\x1B[0m
378
+ Give a number of seconds; 0 disables the budget.`
379
+ );
380
+ process.exit(1);
381
+ }
317
382
  if (!isSilent) {
318
383
  printBanner();
319
384
  console.log(
@@ -331,8 +396,10 @@ Valid categories: ${import_agent_lighthouse_core2.CATEGORY_IDS.join(", ")}`
331
396
  const report = await (0, import_agent_lighthouse_core2.runScan)(url, {
332
397
  onEvent,
333
398
  ...categories ? { categories } : {},
399
+ ...pageType ? { pageType } : {},
334
400
  includeExperimental,
335
- ...onAuditTrace ? { onAuditTrace } : {}
401
+ ...onAuditTrace ? { onAuditTrace } : {},
402
+ ...timeoutSeconds !== void 0 ? { timeoutMs: timeoutSeconds * 1e3 } : {}
336
403
  });
337
404
  const view = (0, import_agent_lighthouse_report.buildReportView)(report);
338
405
  if (outputFormats.includes("terminal") && !isSilent) {
@@ -345,6 +412,20 @@ Valid categories: ${import_agent_lighthouse_core2.CATEGORY_IDS.join(", ")}`
345
412
  console.log(
346
413
  `Target: ${report.url} | Preset: ${preset.name} | Pages: ${view.pagesScanned.length} | Duration: ${(view.durationMs / 1e3).toFixed(1)}s`
347
414
  );
415
+ if (view.conditions) {
416
+ const cond = view.conditions;
417
+ const pct = cond.coverage.registryMass > 0 ? Math.round(
418
+ cond.coverage.assessedMass / cond.coverage.registryMass * 100
419
+ ) : 0;
420
+ console.log(
421
+ `Conditions: Page: \x1B[1m${cond.pageType.type}\x1B[0m (${cond.pageType.source}) | Origin: \x1B[1m${cond.origin.cached ? "cached" : "fresh"}\x1B[0m | Coverage: \x1B[1m${cond.coverage.assessedMass}/${cond.coverage.registryMass}\x1B[0m mass (${pct}%) | Unscored: \x1B[1m${cond.unscored.totalCount}\x1B[0m (${cond.unscored.informativeCount} advisory, ${cond.unscored.gatedCount} gated)`
422
+ );
423
+ if (cond.budget?.exhausted) {
424
+ console.log(
425
+ `\x1B[33mScan budget of ${(0, import_agent_lighthouse_core2.formatBudget)(cond.budget.limitMs)} ran out:\x1B[0m ${cond.budget.skippedCount} audit(s) not assessed. Raise it with --timeout <seconds>.`
426
+ );
427
+ }
428
+ }
348
429
  if (view.coverage.skippedNoEvidence > 0) {
349
430
  console.log(
350
431
  `\x1B[33m${view.coverage.skippedNoEvidence} audit(s) not assessed:\x1B[0m this scan did not obtain the evidence they need. ${view.coverage.noEvidenceReasons.join(" ")}`
package/dist/main.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/main.ts","../src/progress-renderer.ts","../src/options.ts","../src/tier-marker.ts"],"sourcesContent":["import {\n runScan,\n loadConfigFile,\n getPreset,\n logger,\n CATEGORY_IDS,\n type ScanEvent,\n type AuditTrace,\n} from \"@forkpoint/agent-lighthouse-core\";\nimport { createProgressRenderer } from \"./progress-renderer\";\nimport {\n parseCliOptions,\n resolveCommand,\n isValidUrl,\n parseCategoryAssertions,\n failedAssertion,\n selectDebugChecks,\n openCommand,\n} from \"./options\";\nimport { tierMarker } from \"./tier-marker\";\nimport {\n buildReportView,\n generateHtmlReport,\n generateMarkdownSummary,\n} from \"@forkpoint/agent-lighthouse-report\";\nimport { writeFileSync, mkdirSync, readFileSync, appendFileSync, rmSync } from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { exec } from \"node:child_process\";\n\nconst args = process.argv.slice(2);\n\nfunction getPackageVersion() {\n try {\n const pkg = JSON.parse(\n readFileSync(resolve(__dirname, \"../package.json\"), \"utf8\"),\n ) as { version?: string };\n return pkg.version || \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\nfunction printBanner() {\n console.log(`\n\\x1b[1m\\x1b[36m๐Ÿ—ผ Agent Lighthouse\\x1b[0m \\x1b[90mv${getPackageVersion()}\\x1b[0m\n\\x1b[90mThe Open-Source Lighthouse for the Agentic Web\\x1b[0m\n`);\n}\n\nfunction usage(): never {\n printBanner();\n console.log(`Usage:\n agent-lighthouse <url> [options]\n agent-lighthouse audit <url> [options]\n\nOptions:\n -p, --preset <name> Audit preset (ecommerce, saas, content, quick, full) [default: full]\n -c, --config <path> Path to configuration file (e.g. agent-lighthouse.config.json)\n --debug-audit <id|fails> Print deep diagnostic breakdown for a specific audit ID\n (e.g. structured-data/faqpage-schema) or all fails\n --trace [path] Write one NDJSON record per audit โ€” outcome, status, score,\n duration and the evidence behind it โ€” including the audits\n that were skipped or errored. Defaults to\n ./agent-lighthouse-trace.ndjson\n --categories <list> Comma-separated list of categories to audit\n (access-crawl-control, content-extraction, machine-discovery,\n structured-data, answer-readiness, agent-interfaces,\n agentic-commerce, operability-safety)\n --experimental Also run experimental-tier audits (excluded by default;\n they are reported but never scored)\n -o, --output <formats> Output formats (comma-separated: terminal, html, json, md) [default: terminal,html,json]\n -d, --output-dir <path> Output directory for generated reports [default: ./reports]\n -v, --view Automatically open the generated HTML report in your browser\n --min-score <number> Minimum score (0-100) required to pass CI assertions\n --assert-category <id:min> Per-category assertions (e.g. --assert-category structured-data:90)\n --silent Suppress progress output\n --progress-json Stream scan progress as NDJSON (one ScanEvent per line) to stderr\n and suppress the interactive progress display. Stderr is used so\n NDJSON never interleaves with the terminal report on stdout;\n all scanner logs (including error logs) are silenced to keep the\n stream clean โ€” audit errors still appear in the report itself.\n\nExamples:\n npx @forkpoint/agent-lighthouse https://yourstore.com\n npx @forkpoint/agent-lighthouse https://yourstore.com --preset ecommerce\n npx @forkpoint/agent-lighthouse https://yourstore.com --debug-audit structured-data/faqpage-schema\n npx @forkpoint/agent-lighthouse https://staging.yourstore.com --min-score 85\n`);\n process.exit(1);\n}\n\nfunction openInBrowser(filePath: string) {\n exec(openCommand(process.platform, filePath), () => {});\n}\n\nasync function audit(targetUrl?: string) {\n const configPath = parseCliOptions(args, targetUrl).configPath;\n const fileConfig = loadConfigFile(configPath);\n const opts = parseCliOptions(args, targetUrl, fileConfig);\n\n const url = opts.url;\n if (!url) {\n console.error(\"\\x1b[31mError:\\x1b[0m No target URL specified.\");\n usage();\n }\n\n if (!isValidUrl(url)) {\n console.error(`\\x1b[31mInvalid URL:\\x1b[0m ${url}`);\n process.exit(1);\n }\n\n const { isSilent, progressJson, shouldView, debugAudit, minScore, outputDir, tracePath } = opts;\n // Keep the NDJSON stream clean: scanner logs also go to stderr.\n if (progressJson) logger.level = \"silent\";\n\n const presetName = opts.presetName;\n const preset = getPreset(presetName);\n\n const { categories, unknownCategories, includeExperimental, outputFormats } = opts;\n if (unknownCategories.length > 0) {\n console.error(\n `\\x1b[31mUnknown category: ${unknownCategories.join(\", \")}\\x1b[0m\\nValid categories: ${CATEGORY_IDS.join(\", \")}`,\n );\n process.exit(1);\n }\n\n if (!isSilent) {\n printBanner();\n console.log(\n `Auditing \\x1b[1m${url}\\x1b[0m using \\x1b[36m${preset.name}\\x1b[0m preset ...\\n`,\n );\n }\n\n // Progress: --progress-json streams raw ScanEvents as NDJSON to stderr (kept\n // off stdout so it can't interleave with the terminal report). Otherwise the\n // interactive renderer animates on a TTY and prints plain phase summaries in\n // CI (non-TTY). --silent suppresses all progress output as before.\n const onEvent = progressJson\n ? (event: ScanEvent) => {\n process.stderr.write(JSON.stringify(event) + \"\\n\");\n }\n : isSilent\n ? undefined\n : createProgressRenderer({ tty: Boolean(process.stdout.isTTY) });\n\n // One NDJSON record per audit, appended as the scan runs so a crash still\n // leaves the trace up to the point it stopped. Truncated first: a trace that\n // silently appended to the previous run's would read as one impossible scan.\n const traceFile = tracePath ? resolve(tracePath) : undefined;\n if (traceFile) rmSync(traceFile, { force: true });\n const onAuditTrace = traceFile\n ? (trace: AuditTrace) => appendFileSync(traceFile, `${JSON.stringify(trace)}\\n`)\n : undefined;\n\n const report = await runScan(url, {\n onEvent,\n ...(categories ? { categories } : {}),\n includeExperimental,\n ...(onAuditTrace ? { onAuditTrace } : {}),\n });\n\n const view = buildReportView(report);\n\n // Terminal Output\n if (outputFormats.includes(\"terminal\") && !isSilent) {\n console.log(\n `\\x1b[1mโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\\x1b[0m`,\n );\n console.log(\n view.overallScore === null\n ? `\\x1b[1mOVERALL AGENT READINESS:\\x1b[0m \\x1b[33mNOT SCORED\\x1b[0m โ€” ${\n view.unscoredReason ?? 'this scan obtained too little evidence to judge the site.'\n }`\n : `\\x1b[1mOVERALL AGENT READINESS:\\x1b[0m \\x1b[1m${view.overallScore}/100\\x1b[0m (${view.scoreTier?.toUpperCase()})`,\n );\n console.log(\n `Target: ${report.url} | Preset: ${preset.name} | Pages: ${view.pagesScanned.length} | Duration: ${(view.durationMs / 1000).toFixed(1)}s`,\n );\n if (view.coverage.skippedNoEvidence > 0) {\n // The count alone reads as a broken scanner; the reason makes it a fact\n // about the scan.\n console.log(\n `\\x1b[33m${view.coverage.skippedNoEvidence} audit(s) not assessed:\\x1b[0m ` +\n `this scan did not obtain the evidence they need. ${view.coverage.noEvidenceReasons.join(' ')}`,\n );\n }\n console.log(\n `\\x1b[1mโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\\x1b[0m\\n`,\n );\n\n if (report.wafProtection?.isBlocked) {\n console.log(\n ` \\x1b[41m\\x1b[37m\\x1b[1m ๐Ÿ›ก๏ธ BOT PROTECTION WALL DETECTED: ${report.wafProtection.name.toUpperCase()} \\x1b[0m`,\n );\n console.log(\n ` \\x1b[31mโš ๏ธ Diagnosis: ${report.wafProtection.reason}\\x1b[0m`,\n );\n console.log(\n ` \\x1b[90mThis storefront is actively dropping or challenging automated crawler connections.\\x1b[0m`,\n );\n console.log(\n ` \\x1b[90mAI agents (GPTBot, Claude, Perplexity) cannot index or interact with this catalog.\\x1b[0m\\n`,\n );\n }\n\n console.log(`\\x1b[1m๐Ÿ“Š CATEGORIES:\\x1b[0m`);\n for (const group of view.groups) {\n console.log(\n `\\n \\x1b[1m${group.label}\\x1b[0m \\x1b[90mโ€”\\x1b[0m ${group.score}/100`,\n );\n for (const cat of group.categories) {\n const c = cat.counts;\n const scoreColor =\n cat.score >= 90\n ? \"\\x1b[32m\"\n : cat.score >= 70\n ? \"\\x1b[34m\"\n : cat.score >= 50\n ? \"\\x1b[33m\"\n : \"\\x1b[31m\";\n console.log(\n ` ${scoreColor}โ€ข\\x1b[0m ${cat.name.padEnd(36)} : ${scoreColor}${cat.score\n .toString()\n .padStart(\n 3,\n )}/100\\x1b[0m \\x1b[90m(${c.pass}โœ“ ${c.warn}! ${c.fail}โœ—${c.advisory > 0 ? ` ${c.advisory} advisory` : \"\"})\\x1b[0m`,\n );\n }\n }\n console.log();\n }\n\n // Audit Debugger Output\n if (debugAudit) {\n const allChecks = view.groups.flatMap((g) =>\n g.categories.flatMap((c) => [...c.checks, ...c.notApplicable]),\n );\n const targetChecks = selectDebugChecks(allChecks, debugAudit);\n\n if (targetChecks.length === 0) {\n console.log(\n `\\x1b[33m[debugger] No audits found matching: ${debugAudit}\\x1b[0m\\n`,\n );\n } else {\n console.log(\n `\\x1b[1m๐Ÿ” AUDIT DEBUGGER DIAGNOSTICS (${targetChecks.length} checks):\\x1b[0m`,\n );\n console.log(\n `\\x1b[1mโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\\x1b[0m`,\n );\n\n for (const check of targetChecks) {\n const statusBadge =\n check.status === \"pass\"\n ? \"\\x1b[32m[PASS]\\x1b[0m\"\n : check.status === \"warn\"\n ? \"\\x1b[33m[WARN]\\x1b[0m\"\n : check.status === \"fail\"\n ? \"\\x1b[31m[FAIL]\\x1b[0m\"\n : \"\\x1b[90m[N/A]\\x1b[0m\";\n\n console.log(\n `\\n${statusBadge} \\x1b[1m[${check.id}] ${check.title}\\x1b[0m (Score: ${check.score})${tierMarker(check.tier)}`,\n );\n if (check.pageUrl)\n console.log(` \\x1b[90mPage:\\x1b[0m ${check.pageUrl}`);\n if (check.displayValue)\n console.log(` \\x1b[90mFound:\\x1b[0m ${check.displayValue}`);\n if (check.details?.expected)\n console.log(\n ` \\x1b[90mExpected:\\x1b[0m ${check.details.expected}`,\n );\n if (check.explanation)\n console.log(` \\x1b[90mExplanation:\\x1b[0m ${check.explanation}`);\n if (check.impact)\n console.log(` \\x1b[90mImpact:\\x1b[0m ${check.impact}`);\n if (check.fix)\n console.log(` \\x1b[90mFix:\\x1b[0m ${check.fix}`);\n if (check.details?.code) {\n console.log(` \\x1b[90mCode Example:\\x1b[0m`);\n console.log(\n check.details.code\n .split(\"\\n\")\n .map((line: string) => ` \\x1b[36m${line}\\x1b[0m`)\n .join(\"\\n\"),\n );\n }\n }\n console.log(\n `\\x1b[1mโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\\x1b[0m\\n`,\n );\n }\n }\n\n // Ensure output directory exists\n mkdirSync(resolve(outputDir), { recursive: true });\n\n // JSON Report\n if (outputFormats.includes(\"json\")) {\n const jsonPath = resolve(outputDir, \"agent-lighthouse-report.json\");\n writeFileSync(jsonPath, JSON.stringify(report, null, 2));\n if (!isSilent)\n console.log(` \\x1b[90mโ€ข JSON Report:\\x1b[0m ${jsonPath}`);\n }\n\n // HTML Report\n let htmlPath = \"\";\n if (outputFormats.includes(\"html\")) {\n htmlPath = resolve(outputDir, \"agent-lighthouse-report.html\");\n const htmlContent = generateHtmlReport(report);\n writeFileSync(htmlPath, htmlContent);\n if (!isSilent)\n console.log(` \\x1b[90mโ€ข HTML Report:\\x1b[0m ${htmlPath}`);\n }\n\n // Markdown Summary\n if (outputFormats.includes(\"md\") || outputFormats.includes(\"markdown\")) {\n const mdPath = resolve(outputDir, \"agent-lighthouse-report.md\");\n const mdContent = generateMarkdownSummary(report);\n writeFileSync(mdPath, mdContent);\n if (!isSilent) console.log(` \\x1b[90mโ€ข Markdown Report:\\x1b[0m ${mdPath}`);\n }\n\n if (traceFile && !isSilent) {\n console.log(` \\x1b[90mโ€ข Audit trace:\\x1b[0m ${traceFile}`);\n }\n\n if (shouldView && htmlPath) {\n openInBrowser(htmlPath);\n }\n\n // Overall Score Assertion. An unscored scan fails it: the assertion asks for\n // proof the site clears a bar, and a scan that saw too little proves nothing.\n if (minScore > 0 && view.overallScore === null) {\n console.error(\n `\\n\\x1b[31mโœ– CI Assertion Failed:\\x1b[0m The scan produced no score, so it cannot clear ${minScore}. ` +\n (view.unscoredReason ?? 'It obtained too little evidence to judge the site.'),\n );\n process.exit(1);\n }\n if (minScore > 0 && view.overallScore !== null && view.overallScore < minScore) {\n console.error(\n `\\n\\x1b[31mโœ– CI Assertion Failed:\\x1b[0m Overall score ${view.overallScore} is below minimum threshold ${minScore}`,\n );\n process.exit(1);\n }\n\n // Per-category Assertions\n const failed = failedAssertion(\n view.groups.flatMap((g) => g.categories),\n parseCategoryAssertions(args, fileConfig),\n );\n if (failed) {\n console.error(\n `\\n\\x1b[31mโœ– Category Assertion Failed:\\x1b[0m Category '${failed.name}' scored ${failed.score} (threshold: ${failed.threshold})`,\n );\n process.exit(1);\n }\n}\n\nasync function main() {\n const resolved = resolveCommand(args);\n if (resolved.action === \"help\") usage();\n await audit(resolved.url);\n}\n\nmain().catch((err) => {\n console.error(\"\\x1b[31mFatal error:\\x1b[0m\", err.message ?? err);\n process.exit(1);\n});\n","import type { PhaseId, ScanEvent } from \"@forkpoint/agent-lighthouse-core\";\n\nexport const PHASE_LABELS: Record<PhaseId, string> = {\n \"fetch-root\": \"Root files\",\n \"fetch-pages\": \"Pages\",\n analyze: \"Page analysis\",\n audits: \"Audits\",\n report: \"Report\",\n};\n\nconst SPINNER = [\"|\", \"/\", \"-\", \"\\\\\"];\nconst BAR_WIDTH = 20;\nconst ETA_MIN_FRACTION = 0.05;\nconst ETA_MAX_MS = 5 * 60 * 1000;\n\nexport interface PhaseDoneInfo {\n phase: PhaseId;\n completed: number;\n total: number;\n durationMs: number;\n failures?: number;\n color?: boolean;\n}\n\n/** Permanent one-line summary printed when a phase finishes. */\nexport function formatPhaseDone(info: PhaseDoneInfo): string {\n const color = info.color ?? true;\n const seconds = (info.durationMs / 1000).toFixed(1);\n const check = color ? \"\\x1b[32mโœ“\\x1b[0m\" : \"โœ“\";\n let line = `${check} ${PHASE_LABELS[info.phase]} ${info.completed}/${info.total} ยท ${seconds}s`;\n if ((info.failures ?? 0) > 0) {\n const suffix = `ยท ${info.failures} errored`;\n line += color ? ` \\x1b[33m${suffix}\\x1b[0m` : ` ${suffix}`;\n }\n return line;\n}\n\n/** Human ETA from overall scan fraction, or null when unreliable/absurd. */\nexport function formatEta(fraction: number, elapsedMs: number): string | null {\n if (fraction <= ETA_MIN_FRACTION || fraction >= 1) return null;\n const etaMs = (elapsedMs * (1 - fraction)) / fraction;\n if (!Number.isFinite(etaMs) || etaMs < 0 || etaMs > ETA_MAX_MS) return null;\n return `~${Math.ceil(etaMs / 1000)}s left`;\n}\n\nexport interface StatusLineInfo {\n spinnerIndex: number;\n label: string;\n completed: number;\n total: number;\n fraction: number;\n elapsedMs: number;\n}\n\n/** The sticky overwriting status line shown while a phase is active. */\nexport function formatStatusLine(info: StatusLineInfo): string {\n const spinner = SPINNER[info.spinnerIndex % SPINNER.length];\n const fraction = Math.min(1, Math.max(0, info.fraction));\n const filled = Math.round(fraction * BAR_WIDTH);\n const bar = \"โ–ˆ\".repeat(filled) + \"โ–‘\".repeat(BAR_WIDTH - filled);\n const pct = Math.round(fraction * 100);\n const eta = formatEta(info.fraction, info.elapsedMs);\n const counts = info.total > 0 ? ` ${info.completed}/${info.total}` : \"\";\n return ` \\x1b[36m${spinner}\\x1b[0m ${info.label}${counts} [${bar}] ${pct}%${eta ? ` ${eta}` : \"\"}`;\n}\n\nexport interface ProgressRendererOptions {\n /** TTY: animate a sticky status line. Non-TTY: phase summaries only, no ANSI. */\n tty: boolean;\n write?: (text: string) => void;\n now?: () => number;\n minRenderIntervalMs?: number;\n}\n\n/**\n * Stateful ScanEvent consumer. Returns the `onEvent` handler for runScan.\n * Sticky-line renders are throttled; phase:done lines are always written;\n * scan:done only erases the sticky line (the report output follows).\n */\nexport function createProgressRenderer(\n options: ProgressRendererOptions,\n): (event: ScanEvent) => void {\n const write = options.write ?? ((text: string) => process.stdout.write(text));\n const now = options.now ?? (() => Date.now());\n const minInterval = options.minRenderIntervalMs ?? 30;\n\n let spinnerIndex = 0;\n let lastRender = Number.NEGATIVE_INFINITY;\n let stickyShown = false;\n let label = \"\";\n let completed = 0;\n let total = 0;\n let failures = 0;\n\n const clearSticky = () => {\n if (stickyShown) {\n write(\"\\r\\x1b[K\");\n stickyShown = false;\n }\n };\n\n const renderSticky = (fraction: number, elapsedMs: number) => {\n const t = now();\n if (t - lastRender < minInterval) return;\n lastRender = t;\n spinnerIndex += 1;\n write(\n \"\\r\" +\n formatStatusLine({ spinnerIndex, label, completed, total, fraction, elapsedMs }) +\n \"\\x1b[K\",\n );\n stickyShown = true;\n };\n\n return (event) => {\n switch (event.type) {\n case \"scan:start\":\n break;\n case \"phase:start\":\n label = PHASE_LABELS[event.phase];\n completed = 0;\n total = event.totalUnits;\n failures = 0;\n break;\n case \"unit:done\":\n completed = event.completed;\n total = event.total;\n if (options.tty) renderSticky(event.fraction, event.elapsedMs);\n break;\n case \"unit:fail\":\n // A failed unit still counts as settled work (see ProgressTracker).\n completed += 1;\n failures += 1;\n if (options.tty) renderSticky(event.fraction, event.elapsedMs);\n break;\n case \"phase:done\":\n clearSticky();\n write(\n formatPhaseDone({\n phase: event.phase,\n completed,\n total,\n durationMs: event.durationMs,\n failures,\n color: options.tty,\n }) + \"\\n\",\n );\n failures = 0;\n break;\n case \"scan:done\":\n clearSticky();\n break;\n }\n };\n}\n","import { CATEGORY_IDS, type PresetName } from \"@forkpoint/agent-lighthouse-core\";\n\n/**\n * Argument parsing, lifted out of `main.ts`.\n *\n * `main.ts` reads `process.argv` at module scope and calls `main()` on import,\n * so nothing in it could be exercised by a test. Everything here is a pure\n * function of the argv array and the config file, which is where every flag\n * bug this CLI has shipped actually lived โ€” `--categories` was in the help text\n * for a whole major version without being parsed at all.\n *\n * Effects stay in `main.ts`: this module never writes to a stream and never\n * calls `process.exit`.\n */\n\n/** Where `--trace` writes when it is given no path of its own. */\nexport const DEFAULT_TRACE_FILE = \"agent-lighthouse-trace.ndjson\";\n\n/** The subset of a config file that the flags override. */\nexport interface FileConfig {\n url?: string;\n preset?: string;\n minScore?: number;\n outputDir?: string;\n output?: string[];\n}\n\nexport interface CliOptions {\n url: string | undefined;\n configPath: string | undefined;\n presetName: PresetName;\n minScore: number;\n outputDir: string;\n outputFormats: string[];\n categories: string[] | undefined;\n /** Names passed to `--categories` that no category answers to. */\n unknownCategories: string[];\n includeExperimental: boolean;\n isSilent: boolean;\n progressJson: boolean;\n shouldView: boolean;\n debugAudit: string | undefined;\n /** Where to write the per-audit NDJSON trace, if `--trace` was given. */\n tracePath: string | undefined;\n}\n\n/**\n * Read one flag's value, in either `--flag=value` or `--flag value` form.\n *\n * A following token that starts with `-` is treated as the next flag rather\n * than as this one's value, so `--preset --silent` reports no preset instead of\n * silently scanning with a preset named \"--silent\".\n */\nexport function getArgValue(\n args: string[],\n shortFlag: string,\n longFlag: string,\n): string | undefined {\n for (const arg of args) {\n if (shortFlag && arg.startsWith(`${shortFlag}=`)) {\n return arg.slice(shortFlag.length + 1);\n }\n if (longFlag && arg.startsWith(`${longFlag}=`)) {\n return arg.slice(longFlag.length + 1);\n }\n }\n const shortIdx = shortFlag ? args.indexOf(shortFlag) : -1;\n if (shortIdx !== -1 && args[shortIdx + 1] && !args[shortIdx + 1]!.startsWith(\"-\")) {\n return args[shortIdx + 1];\n }\n const longIdx = longFlag ? args.indexOf(longFlag) : -1;\n if (longIdx !== -1 && args[longIdx + 1] && !args[longIdx + 1]!.startsWith(\"-\")) {\n return args[longIdx + 1];\n }\n return undefined;\n}\n\n/** Split a comma-separated flag value, dropping empty entries. */\nexport function splitList(value: string | undefined): string[] | undefined {\n if (value === undefined) return undefined;\n return value\n .split(\",\")\n .map((part) => part.trim())\n .filter(Boolean);\n}\n\n/** The target URL: the positional argument wins over the config file. */\nexport function resolveUrl(positional: string | undefined, fileConfig: FileConfig): string | undefined {\n return positional || fileConfig.url;\n}\n\n/** Whether a string parses as an absolute URL. */\nexport function isValidUrl(url: string): boolean {\n try {\n new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve every option from argv and the config file.\n *\n * Precedence is flag, then config file, then default โ€” the order the help text\n * documents.\n */\nexport function parseCliOptions(\n args: string[],\n positionalUrl: string | undefined,\n fileConfig: FileConfig = {},\n): CliOptions {\n const categories = splitList(getArgValue(args, \"\", \"--categories\"));\n const minScoreArg = getArgValue(args, \"\", \"--min-score\");\n\n return {\n url: resolveUrl(positionalUrl, fileConfig),\n configPath: getArgValue(args, \"-c\", \"--config\"),\n presetName: (getArgValue(args, \"-p\", \"--preset\") || fileConfig.preset || \"full\") as PresetName,\n minScore: minScoreArg ? Number(minScoreArg) : (fileConfig.minScore ?? 0),\n outputDir: getArgValue(args, \"-d\", \"--output-dir\") || fileConfig.outputDir || \"./reports\",\n outputFormats:\n splitList(getArgValue(args, \"-o\", \"--output\")) ??\n fileConfig.output ?? [\"terminal\", \"html\", \"json\"],\n categories,\n unknownCategories: (categories ?? []).filter((c) => !CATEGORY_IDS.includes(c)),\n includeExperimental: args.includes(\"--experimental\"),\n isSilent: args.includes(\"--silent\"),\n progressJson: args.includes(\"--progress-json\"),\n shouldView: args.includes(\"-v\") || args.includes(\"--view\"),\n debugAudit: getArgValue(args, \"\", \"--debug-audit\"),\n // A bare `--trace` with no path is still a request to trace, so it gets\n // the default file rather than being read as \"no trace\".\n tracePath: args.includes(\"--trace\")\n ? (getArgValue(args, \"\", \"--trace\") ?? DEFAULT_TRACE_FILE)\n : getArgValue(args, \"\", \"--trace\"),\n };\n}\n\n/**\n * Which subcommand form was used.\n *\n * `al audit <url>`, `al <url>` and a bare `al` with a config file all reach the\n * same scan; anything starting with `-` is a flag, never a URL.\n */\nexport function resolveCommand(args: string[]): { action: \"help\" | \"audit\"; url?: string } {\n const command = args[0];\n if (!command || command === \"-h\" || command === \"--help\") return { action: \"help\" };\n if (command === \"audit\") return { action: \"audit\", url: args[1] };\n if (!command.startsWith(\"-\")) return { action: \"audit\", url: command };\n return { action: \"audit\" };\n}\n\n/**\n * Per-category thresholds, from `--assert-category id:min` and the config file.\n *\n * The flag repeats, so this cannot go through `getArgValue`, which returns the\n * first occurrence only. A fresh object is returned rather than the config\n * file's own: merging into `fileConfig.assertCategories` mutated the loaded\n * config, which the caller may still read.\n */\nexport function parseCategoryAssertions(\n args: string[],\n fileConfig: FileConfig & { assertCategories?: Record<string, number> } = {},\n): Record<string, number> {\n const out: Record<string, number> = { ...(fileConfig.assertCategories ?? {}) };\n\n const record = (pair: string | undefined) => {\n if (!pair) return;\n const [catId, min] = pair.split(\":\");\n if (catId && min) out[catId] = Number(min);\n };\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n if (arg.startsWith(\"--assert-category=\")) record(arg.slice(\"--assert-category=\".length));\n else if (arg === \"--assert-category\") record(args[i + 1]);\n }\n return out;\n}\n\n/** A category as the assertions see it. */\nexport interface AssertableCategory {\n id: string;\n name: string;\n score: number;\n}\n\nexport interface FailedAssertion {\n name: string;\n score: number;\n threshold: number;\n}\n\n/**\n * The first assertion the scan does not meet, or undefined if it meets all.\n *\n * A threshold naming a category that did not run is not a failure: `--preset`\n * and `--categories` both narrow the scan, and failing CI over a category the\n * operator deliberately excluded would make the two flags unusable together.\n */\nexport function failedAssertion(\n categories: AssertableCategory[],\n assertions: Record<string, number>,\n): FailedAssertion | undefined {\n for (const [catId, threshold] of Object.entries(assertions)) {\n const matched = categories.find(\n (c) => c.id === catId || c.name.toLowerCase().includes(catId.toLowerCase()),\n );\n if (matched && matched.score < threshold) {\n return { name: matched.name, score: matched.score, threshold };\n }\n }\n return undefined;\n}\n\n/** A check as the debugger selects it. */\nexport interface DebuggableCheck {\n id: string;\n title: string;\n status: string;\n}\n\n/**\n * The checks `--debug-audit` should print.\n *\n * `fails` is a reserved value meaning \"everything that is not clean\"; anything\n * else matches an audit id exactly or a title substring, so an operator can\n * type `faqpage` instead of the full id.\n */\nexport function selectDebugChecks<T extends DebuggableCheck>(checks: T[], debugAudit: string): T[] {\n if (debugAudit === \"fails\") {\n return checks.filter((c) => c.status === \"fail\" || c.status === \"warn\");\n }\n const needle = debugAudit.toLowerCase();\n return checks.filter((c) => c.id === debugAudit || c.title.toLowerCase().includes(needle));\n}\n\n/** The shell command that opens a file in the platform's default application. */\nexport function openCommand(platform: NodeJS.Platform, filePath: string): string {\n if (platform === \"darwin\") return `open \"${filePath}\"`;\n if (platform === \"win32\") return `start \"\" \"${filePath}\"`;\n return `xdg-open \"${filePath}\"`;\n}\n","import type { AuditTier } from \"@forkpoint/agent-lighthouse-core\";\n\n/**\n * A check whose tier is not `scored` is reported but never moves a score.\n * Without a marker, a failing advisory reads as work the operator owes.\n */\nexport function tierMarker(tier?: AuditTier): string {\n if (tier === \"informative\") return \" \\x1b[36m(advisory)\\x1b[0m\";\n if (tier === \"experimental\") return \" \\x1b[36m(experimental)\\x1b[0m\";\n return \"\";\n}\n"],"mappings":";;;;AAAA,IAAAA,gCAQO;;;ACNA,IAAM,eAAwC;AAAA,EACnD,cAAc;AAAA,EACd,eAAe;AAAA,EACf,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,QAAQ;AACV;AAEA,IAAM,UAAU,CAAC,KAAK,KAAK,KAAK,IAAI;AACpC,IAAM,YAAY;AAClB,IAAM,mBAAmB;AACzB,IAAM,aAAa,IAAI,KAAK;AAYrB,SAAS,gBAAgB,MAA6B;AAC3D,QAAM,QAAQ,KAAK,SAAS;AAC5B,QAAM,WAAW,KAAK,aAAa,KAAM,QAAQ,CAAC;AAClD,QAAM,QAAQ,QAAQ,0BAAqB;AAC3C,MAAI,OAAO,GAAG,KAAK,IAAI,aAAa,KAAK,KAAK,CAAC,IAAI,KAAK,SAAS,IAAI,KAAK,KAAK,SAAM,OAAO;AAC5F,OAAK,KAAK,YAAY,KAAK,GAAG;AAC5B,UAAM,SAAS,QAAK,KAAK,QAAQ;AACjC,YAAQ,QAAQ,YAAY,MAAM,YAAY,IAAI,MAAM;AAAA,EAC1D;AACA,SAAO;AACT;AAGO,SAAS,UAAU,UAAkB,WAAkC;AAC5E,MAAI,YAAY,oBAAoB,YAAY,EAAG,QAAO;AAC1D,QAAM,QAAS,aAAa,IAAI,YAAa;AAC7C,MAAI,CAAC,OAAO,SAAS,KAAK,KAAK,QAAQ,KAAK,QAAQ,WAAY,QAAO;AACvE,SAAO,IAAI,KAAK,KAAK,QAAQ,GAAI,CAAC;AACpC;AAYO,SAAS,iBAAiB,MAA8B;AAC7D,QAAM,UAAU,QAAQ,KAAK,eAAe,QAAQ,MAAM;AAC1D,QAAM,WAAW,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,QAAQ,CAAC;AACvD,QAAM,SAAS,KAAK,MAAM,WAAW,SAAS;AAC9C,QAAM,MAAM,SAAI,OAAO,MAAM,IAAI,SAAI,OAAO,YAAY,MAAM;AAC9D,QAAM,MAAM,KAAK,MAAM,WAAW,GAAG;AACrC,QAAM,MAAM,UAAU,KAAK,UAAU,KAAK,SAAS;AACnD,QAAM,SAAS,KAAK,QAAQ,IAAI,IAAI,KAAK,SAAS,IAAI,KAAK,KAAK,KAAK;AACrE,SAAO,aAAa,OAAO,WAAW,KAAK,KAAK,GAAG,MAAM,KAAK,GAAG,KAAK,GAAG,IAAI,MAAM,IAAI,GAAG,KAAK,EAAE;AACnG;AAeO,SAAS,uBACd,SAC4B;AAC5B,QAAM,QAAQ,QAAQ,UAAU,CAAC,SAAiB,QAAQ,OAAO,MAAM,IAAI;AAC3E,QAAM,MAAM,QAAQ,QAAQ,MAAM,KAAK,IAAI;AAC3C,QAAM,cAAc,QAAQ,uBAAuB;AAEnD,MAAI,eAAe;AACnB,MAAI,aAAa,OAAO;AACxB,MAAI,cAAc;AAClB,MAAI,QAAQ;AACZ,MAAI,YAAY;AAChB,MAAI,QAAQ;AACZ,MAAI,WAAW;AAEf,QAAM,cAAc,MAAM;AACxB,QAAI,aAAa;AACf,YAAM,UAAU;AAChB,oBAAc;AAAA,IAChB;AAAA,EACF;AAEA,QAAM,eAAe,CAAC,UAAkB,cAAsB;AAC5D,UAAM,IAAI,IAAI;AACd,QAAI,IAAI,aAAa,YAAa;AAClC,iBAAa;AACb,oBAAgB;AAChB;AAAA,MACE,OACE,iBAAiB,EAAE,cAAc,OAAO,WAAW,OAAO,UAAU,UAAU,CAAC,IAC/E;AAAA,IACJ;AACA,kBAAc;AAAA,EAChB;AAEA,SAAO,CAAC,UAAU;AAChB,YAAQ,MAAM,MAAM;AAAA,MAClB,KAAK;AACH;AAAA,MACF,KAAK;AACH,gBAAQ,aAAa,MAAM,KAAK;AAChC,oBAAY;AACZ,gBAAQ,MAAM;AACd,mBAAW;AACX;AAAA,MACF,KAAK;AACH,oBAAY,MAAM;AAClB,gBAAQ,MAAM;AACd,YAAI,QAAQ,IAAK,cAAa,MAAM,UAAU,MAAM,SAAS;AAC7D;AAAA,MACF,KAAK;AAEH,qBAAa;AACb,oBAAY;AACZ,YAAI,QAAQ,IAAK,cAAa,MAAM,UAAU,MAAM,SAAS;AAC7D;AAAA,MACF,KAAK;AACH,oBAAY;AACZ;AAAA,UACE,gBAAgB;AAAA,YACd,OAAO,MAAM;AAAA,YACb;AAAA,YACA;AAAA,YACA,YAAY,MAAM;AAAA,YAClB;AAAA,YACA,OAAO,QAAQ;AAAA,UACjB,CAAC,IAAI;AAAA,QACP;AACA,mBAAW;AACX;AAAA,MACF,KAAK;AACH,oBAAY;AACZ;AAAA,IACJ;AAAA,EACF;AACF;;;AC1JA,mCAA8C;AAgBvC,IAAM,qBAAqB;AAqC3B,SAAS,YACdC,OACA,WACA,UACoB;AACpB,aAAW,OAAOA,OAAM;AACtB,QAAI,aAAa,IAAI,WAAW,GAAG,SAAS,GAAG,GAAG;AAChD,aAAO,IAAI,MAAM,UAAU,SAAS,CAAC;AAAA,IACvC;AACA,QAAI,YAAY,IAAI,WAAW,GAAG,QAAQ,GAAG,GAAG;AAC9C,aAAO,IAAI,MAAM,SAAS,SAAS,CAAC;AAAA,IACtC;AAAA,EACF;AACA,QAAM,WAAW,YAAYA,MAAK,QAAQ,SAAS,IAAI;AACvD,MAAI,aAAa,MAAMA,MAAK,WAAW,CAAC,KAAK,CAACA,MAAK,WAAW,CAAC,EAAG,WAAW,GAAG,GAAG;AACjF,WAAOA,MAAK,WAAW,CAAC;AAAA,EAC1B;AACA,QAAM,UAAU,WAAWA,MAAK,QAAQ,QAAQ,IAAI;AACpD,MAAI,YAAY,MAAMA,MAAK,UAAU,CAAC,KAAK,CAACA,MAAK,UAAU,CAAC,EAAG,WAAW,GAAG,GAAG;AAC9E,WAAOA,MAAK,UAAU,CAAC;AAAA,EACzB;AACA,SAAO;AACT;AAGO,SAAS,UAAU,OAAiD;AACzE,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,MACJ,MAAM,GAAG,EACT,IAAI,CAAC,SAAS,KAAK,KAAK,CAAC,EACzB,OAAO,OAAO;AACnB;AAGO,SAAS,WAAW,YAAgC,YAA4C;AACrG,SAAO,cAAc,WAAW;AAClC;AAGO,SAAS,WAAW,KAAsB;AAC/C,MAAI;AACF,QAAI,IAAI,GAAG;AACX,WAAO;AAAA,EACT,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAQO,SAAS,gBACdA,OACA,eACA,aAAyB,CAAC,GACd;AACZ,QAAM,aAAa,UAAU,YAAYA,OAAM,IAAI,cAAc,CAAC;AAClE,QAAM,cAAc,YAAYA,OAAM,IAAI,aAAa;AAEvD,SAAO;AAAA,IACL,KAAK,WAAW,eAAe,UAAU;AAAA,IACzC,YAAY,YAAYA,OAAM,MAAM,UAAU;AAAA,IAC9C,YAAa,YAAYA,OAAM,MAAM,UAAU,KAAK,WAAW,UAAU;AAAA,IACzE,UAAU,cAAc,OAAO,WAAW,IAAK,WAAW,YAAY;AAAA,IACtE,WAAW,YAAYA,OAAM,MAAM,cAAc,KAAK,WAAW,aAAa;AAAA,IAC9E,eACE,UAAU,YAAYA,OAAM,MAAM,UAAU,CAAC,KAC7C,WAAW,UAAU,CAAC,YAAY,QAAQ,MAAM;AAAA,IAClD;AAAA,IACA,oBAAoB,cAAc,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC,0CAAa,SAAS,CAAC,CAAC;AAAA,IAC7E,qBAAqBA,MAAK,SAAS,gBAAgB;AAAA,IACnD,UAAUA,MAAK,SAAS,UAAU;AAAA,IAClC,cAAcA,MAAK,SAAS,iBAAiB;AAAA,IAC7C,YAAYA,MAAK,SAAS,IAAI,KAAKA,MAAK,SAAS,QAAQ;AAAA,IACzD,YAAY,YAAYA,OAAM,IAAI,eAAe;AAAA;AAAA;AAAA,IAGjD,WAAWA,MAAK,SAAS,SAAS,IAC7B,YAAYA,OAAM,IAAI,SAAS,KAAK,qBACrC,YAAYA,OAAM,IAAI,SAAS;AAAA,EACrC;AACF;AAQO,SAAS,eAAeA,OAA4D;AACzF,QAAM,UAAUA,MAAK,CAAC;AACtB,MAAI,CAAC,WAAW,YAAY,QAAQ,YAAY,SAAU,QAAO,EAAE,QAAQ,OAAO;AAClF,MAAI,YAAY,QAAS,QAAO,EAAE,QAAQ,SAAS,KAAKA,MAAK,CAAC,EAAE;AAChE,MAAI,CAAC,QAAQ,WAAW,GAAG,EAAG,QAAO,EAAE,QAAQ,SAAS,KAAK,QAAQ;AACrE,SAAO,EAAE,QAAQ,QAAQ;AAC3B;AAUO,SAAS,wBACdA,OACA,aAAyE,CAAC,GAClD;AACxB,QAAM,MAA8B,EAAE,GAAI,WAAW,oBAAoB,CAAC,EAAG;AAE7E,QAAM,SAAS,CAAC,SAA6B;AAC3C,QAAI,CAAC,KAAM;AACX,UAAM,CAAC,OAAO,GAAG,IAAI,KAAK,MAAM,GAAG;AACnC,QAAI,SAAS,IAAK,KAAI,KAAK,IAAI,OAAO,GAAG;AAAA,EAC3C;AAEA,WAAS,IAAI,GAAG,IAAIA,MAAK,QAAQ,KAAK;AACpC,UAAM,MAAMA,MAAK,CAAC;AAClB,QAAI,IAAI,WAAW,oBAAoB,EAAG,QAAO,IAAI,MAAM,qBAAqB,MAAM,CAAC;AAAA,aAC9E,QAAQ,oBAAqB,QAAOA,MAAK,IAAI,CAAC,CAAC;AAAA,EAC1D;AACA,SAAO;AACT;AAsBO,SAAS,gBACd,YACA,YAC6B;AAC7B,aAAW,CAAC,OAAO,SAAS,KAAK,OAAO,QAAQ,UAAU,GAAG;AAC3D,UAAM,UAAU,WAAW;AAAA,MACzB,CAAC,MAAM,EAAE,OAAO,SAAS,EAAE,KAAK,YAAY,EAAE,SAAS,MAAM,YAAY,CAAC;AAAA,IAC5E;AACA,QAAI,WAAW,QAAQ,QAAQ,WAAW;AACxC,aAAO,EAAE,MAAM,QAAQ,MAAM,OAAO,QAAQ,OAAO,UAAU;AAAA,IAC/D;AAAA,EACF;AACA,SAAO;AACT;AAgBO,SAAS,kBAA6C,QAAa,YAAyB;AACjG,MAAI,eAAe,SAAS;AAC1B,WAAO,OAAO,OAAO,CAAC,MAAM,EAAE,WAAW,UAAU,EAAE,WAAW,MAAM;AAAA,EACxE;AACA,QAAM,SAAS,WAAW,YAAY;AACtC,SAAO,OAAO,OAAO,CAAC,MAAM,EAAE,OAAO,cAAc,EAAE,MAAM,YAAY,EAAE,SAAS,MAAM,CAAC;AAC3F;AAGO,SAAS,YAAY,UAA2B,UAA0B;AAC/E,MAAI,aAAa,SAAU,QAAO,SAAS,QAAQ;AACnD,MAAI,aAAa,QAAS,QAAO,aAAa,QAAQ;AACtD,SAAO,aAAa,QAAQ;AAC9B;;;AC7OO,SAAS,WAAW,MAA0B;AACnD,MAAI,SAAS,cAAe,QAAO;AACnC,MAAI,SAAS,eAAgB,QAAO;AACpC,SAAO;AACT;;;AHUA,qCAIO;AACP,qBAA+E;AAC/E,uBAAwB;AACxB,gCAAqB;AAErB,IAAM,OAAO,QAAQ,KAAK,MAAM,CAAC;AAEjC,SAAS,oBAAoB;AAC3B,MAAI;AACF,UAAM,MAAM,KAAK;AAAA,UACf,iCAAa,0BAAQ,WAAW,iBAAiB,GAAG,MAAM;AAAA,IAC5D;AACA,WAAO,IAAI,WAAW;AAAA,EACxB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAEA,SAAS,cAAc;AACrB,UAAQ,IAAI;AAAA,4DACuC,kBAAkB,CAAC;AAAA;AAAA,CAEvE;AACD;AAEA,SAAS,QAAe;AACtB,cAAY;AACZ,UAAQ,IAAI;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAoCb;AACC,UAAQ,KAAK,CAAC;AAChB;AAEA,SAAS,cAAc,UAAkB;AACvC,sCAAK,YAAY,QAAQ,UAAU,QAAQ,GAAG,MAAM;AAAA,EAAC,CAAC;AACxD;AAEA,eAAe,MAAM,WAAoB;AACvC,QAAM,aAAa,gBAAgB,MAAM,SAAS,EAAE;AACpD,QAAM,iBAAa,8CAAe,UAAU;AAC5C,QAAM,OAAO,gBAAgB,MAAM,WAAW,UAAU;AAExD,QAAM,MAAM,KAAK;AACjB,MAAI,CAAC,KAAK;AACR,YAAQ,MAAM,gDAAgD;AAC9D,UAAM;AAAA,EACR;AAEA,MAAI,CAAC,WAAW,GAAG,GAAG;AACpB,YAAQ,MAAM,+BAA+B,GAAG,EAAE;AAClD,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,QAAM,EAAE,UAAU,cAAc,YAAY,YAAY,UAAU,WAAW,UAAU,IAAI;AAE3F,MAAI,aAAc,sCAAO,QAAQ;AAEjC,QAAM,aAAa,KAAK;AACxB,QAAM,aAAS,yCAAU,UAAU;AAEnC,QAAM,EAAE,YAAY,mBAAmB,qBAAqB,cAAc,IAAI;AAC9E,MAAI,kBAAkB,SAAS,GAAG;AAChC,YAAQ;AAAA,MACN,6BAA6B,kBAAkB,KAAK,IAAI,CAAC;AAAA,oBAA8B,2CAAa,KAAK,IAAI,CAAC;AAAA,IAChH;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAEA,MAAI,CAAC,UAAU;AACb,gBAAY;AACZ,YAAQ;AAAA,MACN,mBAAmB,GAAG,yBAAyB,OAAO,IAAI;AAAA;AAAA,IAC5D;AAAA,EACF;AAMA,QAAM,UAAU,eACZ,CAAC,UAAqB;AACpB,YAAQ,OAAO,MAAM,KAAK,UAAU,KAAK,IAAI,IAAI;AAAA,EACnD,IACA,WACE,SACA,uBAAuB,EAAE,KAAK,QAAQ,QAAQ,OAAO,KAAK,EAAE,CAAC;AAKnE,QAAM,YAAY,gBAAY,0BAAQ,SAAS,IAAI;AACnD,MAAI,UAAW,4BAAO,WAAW,EAAE,OAAO,KAAK,CAAC;AAChD,QAAM,eAAe,YACjB,CAAC,cAAsB,+BAAe,WAAW,GAAG,KAAK,UAAU,KAAK,CAAC;AAAA,CAAI,IAC7E;AAEJ,QAAM,SAAS,UAAM,uCAAQ,KAAK;AAAA,IAChC;AAAA,IACA,GAAI,aAAa,EAAE,WAAW,IAAI,CAAC;AAAA,IACnC;AAAA,IACA,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;AAAA,EACzC,CAAC;AAED,QAAM,WAAO,gDAAgB,MAAM;AAGnC,MAAI,cAAc,SAAS,UAAU,KAAK,CAAC,UAAU;AACnD,YAAQ;AAAA,MACN;AAAA,IACF;AACA,YAAQ;AAAA,MACN,KAAK,iBAAiB,OAClB,2EACE,KAAK,kBAAkB,2DACzB,KACA,iDAAiD,KAAK,YAAY,gBAAgB,KAAK,WAAW,YAAY,CAAC;AAAA,IACrH;AACA,YAAQ;AAAA,MACN,WAAW,OAAO,GAAG,cAAc,OAAO,IAAI,aAAa,KAAK,aAAa,MAAM,iBAAiB,KAAK,aAAa,KAAM,QAAQ,CAAC,CAAC;AAAA,IACxI;AACA,QAAI,KAAK,SAAS,oBAAoB,GAAG;AAGvC,cAAQ;AAAA,QACN,WAAW,KAAK,SAAS,iBAAiB,mFACY,KAAK,SAAS,kBAAkB,KAAK,GAAG,CAAC;AAAA,MACjG;AAAA,IACF;AACA,YAAQ;AAAA,MACN;AAAA;AAAA,IACF;AAEA,QAAI,OAAO,eAAe,WAAW;AACnC,cAAQ;AAAA,QACN,4EAAgE,OAAO,cAAc,KAAK,YAAY,CAAC;AAAA,MACzG;AACA,cAAQ;AAAA,QACN,sCAA4B,OAAO,cAAc,MAAM;AAAA,MACzD;AACA,cAAQ;AAAA,QACN;AAAA,MACF;AACA,cAAQ;AAAA,QACN;AAAA;AAAA,MACF;AAAA,IACF;AAEA,YAAQ,IAAI,qCAA8B;AAC1C,eAAW,SAAS,KAAK,QAAQ;AAC/B,cAAQ;AAAA,QACN;AAAA,WAAc,MAAM,KAAK,iCAA4B,MAAM,KAAK;AAAA,MAClE;AACA,iBAAW,OAAO,MAAM,YAAY;AAClC,cAAM,IAAI,IAAI;AACd,cAAM,aACJ,IAAI,SAAS,KACT,aACA,IAAI,SAAS,KACX,aACA,IAAI,SAAS,KACX,aACA;AACV,gBAAQ;AAAA,UACN,OAAO,UAAU,iBAAY,IAAI,KAAK,OAAO,EAAE,CAAC,MAAM,UAAU,GAAG,IAAI,MACpE,SAAS,EACT;AAAA,YACC;AAAA,UACF,CAAC,yBAAyB,EAAE,IAAI,UAAK,EAAE,IAAI,KAAK,EAAE,IAAI,SAAI,EAAE,WAAW,IAAI,IAAI,EAAE,QAAQ,cAAc,EAAE;AAAA,QAC7G;AAAA,MACF;AAAA,IACF;AACA,YAAQ,IAAI;AAAA,EACd;AAGA,MAAI,YAAY;AACd,UAAM,YAAY,KAAK,OAAO;AAAA,MAAQ,CAAC,MACrC,EAAE,WAAW,QAAQ,CAAC,MAAM,CAAC,GAAG,EAAE,QAAQ,GAAG,EAAE,aAAa,CAAC;AAAA,IAC/D;AACA,UAAM,eAAe,kBAAkB,WAAW,UAAU;AAE5D,QAAI,aAAa,WAAW,GAAG;AAC7B,cAAQ;AAAA,QACN,gDAAgD,UAAU;AAAA;AAAA,MAC5D;AAAA,IACF,OAAO;AACL,cAAQ;AAAA,QACN,gDAAyC,aAAa,MAAM;AAAA,MAC9D;AACA,cAAQ;AAAA,QACN;AAAA,MACF;AAEA,iBAAW,SAAS,cAAc;AAChC,cAAM,cACJ,MAAM,WAAW,SACb,0BACA,MAAM,WAAW,SACf,0BACA,MAAM,WAAW,SACf,0BACA;AAEV,gBAAQ;AAAA,UACN;AAAA,EAAK,WAAW,YAAY,MAAM,EAAE,KAAK,MAAM,KAAK,mBAAmB,MAAM,KAAK,IAAI,WAAW,MAAM,IAAI,CAAC;AAAA,QAC9G;AACA,YAAI,MAAM;AACR,kBAAQ,IAAI,iCAAiC,MAAM,OAAO,EAAE;AAC9D,YAAI,MAAM;AACR,kBAAQ,IAAI,iCAAiC,MAAM,YAAY,EAAE;AACnE,YAAI,MAAM,SAAS;AACjB,kBAAQ;AAAA,YACN,iCAAiC,MAAM,QAAQ,QAAQ;AAAA,UACzD;AACF,YAAI,MAAM;AACR,kBAAQ,IAAI,iCAAiC,MAAM,WAAW,EAAE;AAClE,YAAI,MAAM;AACR,kBAAQ,IAAI,iCAAiC,MAAM,MAAM,EAAE;AAC7D,YAAI,MAAM;AACR,kBAAQ,IAAI,iCAAiC,MAAM,GAAG,EAAE;AAC1D,YAAI,MAAM,SAAS,MAAM;AACvB,kBAAQ,IAAI,gCAAgC;AAC5C,kBAAQ;AAAA,YACN,MAAM,QAAQ,KACX,MAAM,IAAI,EACV,IAAI,CAAC,SAAiB,eAAe,IAAI,SAAS,EAClD,KAAK,IAAI;AAAA,UACd;AAAA,QACF;AAAA,MACF;AACA,cAAQ;AAAA,QACN;AAAA;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAGA,oCAAU,0BAAQ,SAAS,GAAG,EAAE,WAAW,KAAK,CAAC;AAGjD,MAAI,cAAc,SAAS,MAAM,GAAG;AAClC,UAAM,eAAW,0BAAQ,WAAW,8BAA8B;AAClE,sCAAc,UAAU,KAAK,UAAU,QAAQ,MAAM,CAAC,CAAC;AACvD,QAAI,CAAC;AACH,cAAQ,IAAI,2CAAsC,QAAQ,EAAE;AAAA,EAChE;AAGA,MAAI,WAAW;AACf,MAAI,cAAc,SAAS,MAAM,GAAG;AAClC,mBAAW,0BAAQ,WAAW,8BAA8B;AAC5D,UAAM,kBAAc,mDAAmB,MAAM;AAC7C,sCAAc,UAAU,WAAW;AACnC,QAAI,CAAC;AACH,cAAQ,IAAI,2CAAsC,QAAQ,EAAE;AAAA,EAChE;AAGA,MAAI,cAAc,SAAS,IAAI,KAAK,cAAc,SAAS,UAAU,GAAG;AACtE,UAAM,aAAS,0BAAQ,WAAW,4BAA4B;AAC9D,UAAM,gBAAY,wDAAwB,MAAM;AAChD,sCAAc,QAAQ,SAAS;AAC/B,QAAI,CAAC,SAAU,SAAQ,IAAI,4CAAuC,MAAM,EAAE;AAAA,EAC5E;AAEA,MAAI,aAAa,CAAC,UAAU;AAC1B,YAAQ,IAAI,2CAAsC,SAAS,EAAE;AAAA,EAC/D;AAEA,MAAI,cAAc,UAAU;AAC1B,kBAAc,QAAQ;AAAA,EACxB;AAIA,MAAI,WAAW,KAAK,KAAK,iBAAiB,MAAM;AAC9C,YAAQ;AAAA,MACN;AAAA,4FAA0F,QAAQ,QAC/F,KAAK,kBAAkB;AAAA,IAC5B;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACA,MAAI,WAAW,KAAK,KAAK,iBAAiB,QAAQ,KAAK,eAAe,UAAU;AAC9E,YAAQ;AAAA,MACN;AAAA,2DAAyD,KAAK,YAAY,+BAA+B,QAAQ;AAAA,IACnH;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AAGA,QAAM,SAAS;AAAA,IACb,KAAK,OAAO,QAAQ,CAAC,MAAM,EAAE,UAAU;AAAA,IACvC,wBAAwB,MAAM,UAAU;AAAA,EAC1C;AACA,MAAI,QAAQ;AACV,YAAQ;AAAA,MACN;AAAA,6DAA2D,OAAO,IAAI,YAAY,OAAO,KAAK,gBAAgB,OAAO,SAAS;AAAA,IAChI;AACA,YAAQ,KAAK,CAAC;AAAA,EAChB;AACF;AAEA,eAAe,OAAO;AACpB,QAAM,WAAW,eAAe,IAAI;AACpC,MAAI,SAAS,WAAW,OAAQ,OAAM;AACtC,QAAM,MAAM,SAAS,GAAG;AAC1B;AAEA,KAAK,EAAE,MAAM,CAAC,QAAQ;AACpB,UAAQ,MAAM,+BAA+B,IAAI,WAAW,GAAG;AAC/D,UAAQ,KAAK,CAAC;AAChB,CAAC;","names":["import_agent_lighthouse_core","args"]}
1
+ {"version":3,"sources":["../src/main.ts","../src/progress-renderer.ts","../src/options.ts","../src/tier-marker.ts"],"sourcesContent":["import {\n runScan,\n formatBudget,\n loadConfigFile,\n getPreset,\n logger,\n CATEGORY_IDS,\n type ScanEvent,\n type AuditTrace,\n} from \"@forkpoint/agent-lighthouse-core\";\nimport { createProgressRenderer } from \"./progress-renderer\";\nimport {\n parseCliOptions,\n resolveCommand,\n isValidUrl,\n parseCategoryAssertions,\n failedAssertion,\n selectDebugChecks,\n openCommand,\n PAGE_TYPE_IDS,\n} from \"./options\";\nimport { tierMarker } from \"./tier-marker\";\nimport {\n buildReportView,\n generateHtmlReport,\n generateMarkdownSummary,\n} from \"@forkpoint/agent-lighthouse-report\";\nimport {\n writeFileSync,\n mkdirSync,\n readFileSync,\n appendFileSync,\n rmSync,\n} from \"node:fs\";\nimport { resolve } from \"node:path\";\nimport { exec } from \"node:child_process\";\n\nconst args = process.argv.slice(2);\n\nfunction getPackageVersion() {\n try {\n const pkg = JSON.parse(\n readFileSync(resolve(__dirname, \"../package.json\"), \"utf8\"),\n ) as { version?: string };\n return pkg.version || \"unknown\";\n } catch {\n return \"unknown\";\n }\n}\n\nfunction printBanner() {\n console.log(`\n\\x1b[1m\\x1b[36m๐Ÿ—ผ Agent Lighthouse\\x1b[0m \\x1b[90mv${getPackageVersion()}\\x1b[0m\n\\x1b[90mThe Open-Source Lighthouse for the Agentic Web\\x1b[0m\n`);\n}\n\nfunction usage(): never {\n printBanner();\n console.log(`Usage:\n agent-lighthouse <url> [options]\n agent-lighthouse audit <url> [options]\n\nOptions:\n -p, --preset <name> Audit preset (ecommerce, saas, content, quick, full) [default: full]\n -c, --config <path> Path to configuration file (e.g. agent-lighthouse.config.json)\n --debug-audit <id|fails> Print deep diagnostic breakdown for a specific audit ID\n (e.g. structured-data/faqpage-schema) or all fails\n --trace [path] Write one NDJSON record per audit โ€” outcome, status, score,\n duration and the evidence behind it โ€” including the audits\n that were skipped or errored. Defaults to\n ./agent-lighthouse-trace.ndjson\n --categories <list> Comma-separated list of categories to audit\n --page-type <type> Declare what the target URL is: homepage, category,\n product or content. Page-typed audits score only a\n declared type; a detected one runs them as informative\n (access-crawl-control, content-extraction, machine-discovery,\n structured-data, answer-readiness, agent-interfaces,\n agentic-commerce, operability-safety)\n --experimental Also run experimental-tier audits (excluded by default;\n they are reported but never scored)\n -o, --output <formats> Output formats (comma-separated: terminal, html, json, md) [default: terminal,html,json]\n -d, --output-dir <path> Output directory for generated reports [default: ./reports]\n -v, --view Automatically open the generated HTML report in your browser\n --timeout <seconds> Wall-clock budget for the scan [default: 180]. When it runs out the\n scan finishes with what it has; 0 disables it\n --min-score <number> Minimum score (0-100) required to pass CI assertions\n --assert-category <id:min> Per-category assertions (e.g. --assert-category structured-data:90)\n --silent Suppress progress output\n --progress-json Stream scan progress as NDJSON (one ScanEvent per line) to stderr\n and suppress the interactive progress display. Stderr is used so\n NDJSON never interleaves with the terminal report on stdout;\n all scanner logs (including error logs) are silenced to keep the\n stream clean โ€” audit errors still appear in the report itself.\n\nExamples:\n npx @forkpoint/agent-lighthouse https://yourstore.com\n npx @forkpoint/agent-lighthouse https://yourstore.com --preset ecommerce\n npx @forkpoint/agent-lighthouse https://yourstore.com --debug-audit structured-data/faqpage-schema\n npx @forkpoint/agent-lighthouse https://staging.yourstore.com --min-score 85\n`);\n process.exit(1);\n}\n\nfunction openInBrowser(filePath: string) {\n exec(openCommand(process.platform, filePath), () => {});\n}\n\nasync function audit(targetUrl?: string) {\n const configPath = parseCliOptions(args, targetUrl).configPath;\n const fileConfig = loadConfigFile(configPath);\n const opts = parseCliOptions(args, targetUrl, fileConfig);\n\n const url = opts.url;\n if (!url) {\n console.error(\"\\x1b[31mError:\\x1b[0m No target URL specified.\");\n usage();\n }\n\n if (!isValidUrl(url)) {\n console.error(`\\x1b[31mInvalid URL:\\x1b[0m ${url}`);\n process.exit(1);\n }\n\n const {\n isSilent,\n progressJson,\n shouldView,\n debugAudit,\n minScore,\n outputDir,\n tracePath,\n } = opts;\n // Keep the NDJSON stream clean: scanner logs also go to stderr.\n if (progressJson) logger.level = \"silent\";\n\n const presetName = opts.presetName;\n const preset = getPreset(presetName);\n\n const {\n categories,\n unknownCategories,\n includeExperimental,\n outputFormats,\n pageType,\n invalidPageType,\n timeoutSeconds,\n invalidTimeout,\n } = opts;\n if (unknownCategories.length > 0) {\n console.error(\n `\\x1b[31mUnknown category: ${unknownCategories.join(\", \")}\\x1b[0m\\nValid categories: ${CATEGORY_IDS.join(\", \")}`,\n );\n process.exit(1);\n }\n if (invalidPageType !== undefined) {\n console.error(\n `\\x1b[31mUnknown page type: ${invalidPageType}\\x1b[0m\\nValid page types: ${PAGE_TYPE_IDS.join(\", \")}`,\n );\n process.exit(1);\n }\n if (invalidTimeout !== undefined) {\n // A bare flag, or one followed by a token that starts with \"-\": the\n // parser reads that token as the next flag, so the value never arrives.\n const what =\n invalidTimeout === \"\"\n ? \"no value given (write --timeout=<seconds> for a value that starts with -)\"\n : invalidTimeout;\n console.error(\n `\\x1b[31mInvalid --timeout: ${what}\\x1b[0m\\nGive a number of seconds; 0 disables the budget.`,\n );\n process.exit(1);\n }\n\n if (!isSilent) {\n printBanner();\n console.log(\n `Auditing \\x1b[1m${url}\\x1b[0m using \\x1b[36m${preset.name}\\x1b[0m preset ...\\n`,\n );\n }\n\n // Progress: --progress-json streams raw ScanEvents as NDJSON to stderr (kept\n // off stdout so it can't interleave with the terminal report). Otherwise the\n // interactive renderer animates on a TTY and prints plain phase summaries in\n // CI (non-TTY). --silent suppresses all progress output as before.\n const onEvent = progressJson\n ? (event: ScanEvent) => {\n process.stderr.write(JSON.stringify(event) + \"\\n\");\n }\n : isSilent\n ? undefined\n : createProgressRenderer({ tty: Boolean(process.stdout.isTTY) });\n\n // One NDJSON record per audit, appended as the scan runs so a crash still\n // leaves the trace up to the point it stopped. Truncated first: a trace that\n // silently appended to the previous run's would read as one impossible scan.\n const traceFile = tracePath ? resolve(tracePath) : undefined;\n if (traceFile) rmSync(traceFile, { force: true });\n const onAuditTrace = traceFile\n ? (trace: AuditTrace) =>\n appendFileSync(traceFile, `${JSON.stringify(trace)}\\n`)\n : undefined;\n\n const report = await runScan(url, {\n onEvent,\n ...(categories ? { categories } : {}),\n ...(pageType ? { pageType } : {}),\n includeExperimental,\n ...(onAuditTrace ? { onAuditTrace } : {}),\n ...(timeoutSeconds !== undefined\n ? { timeoutMs: timeoutSeconds * 1000 }\n : {}),\n });\n\n const view = buildReportView(report);\n\n // Terminal Output\n if (outputFormats.includes(\"terminal\") && !isSilent) {\n console.log(\n `\\x1b[1mโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\\x1b[0m`,\n );\n console.log(\n view.overallScore === null\n ? `\\x1b[1mOVERALL AGENT READINESS:\\x1b[0m \\x1b[33mNOT SCORED\\x1b[0m โ€” ${\n view.unscoredReason ??\n \"this scan obtained too little evidence to judge the site.\"\n }`\n : `\\x1b[1mOVERALL AGENT READINESS:\\x1b[0m \\x1b[1m${view.overallScore}/100\\x1b[0m (${view.scoreTier?.toUpperCase()})`,\n );\n console.log(\n `Target: ${report.url} | Preset: ${preset.name} | Pages: ${view.pagesScanned.length} | Duration: ${(view.durationMs / 1000).toFixed(1)}s`,\n );\n if (view.conditions) {\n const cond = view.conditions;\n const pct =\n cond.coverage.registryMass > 0\n ? Math.round(\n (cond.coverage.assessedMass / cond.coverage.registryMass) * 100,\n )\n : 0;\n console.log(\n `Conditions: Page: \\x1b[1m${cond.pageType.type}\\x1b[0m (${cond.pageType.source}) | ` +\n `Origin: \\x1b[1m${cond.origin.cached ? \"cached\" : \"fresh\"}\\x1b[0m | ` +\n `Coverage: \\x1b[1m${cond.coverage.assessedMass}/${cond.coverage.registryMass}\\x1b[0m mass (${pct}%) | ` +\n `Unscored: \\x1b[1m${cond.unscored.totalCount}\\x1b[0m (${cond.unscored.informativeCount} advisory, ${cond.unscored.gatedCount} gated)`,\n );\n if (cond.budget?.exhausted) {\n console.log(\n `\\x1b[33mScan budget of ${formatBudget(cond.budget.limitMs)} ran out:\\x1b[0m ` +\n `${cond.budget.skippedCount} audit(s) not assessed. Raise it with --timeout <seconds>.`,\n );\n }\n }\n if (view.coverage.skippedNoEvidence > 0) {\n // The count alone reads as a broken scanner; the reason makes it a fact\n // about the scan.\n console.log(\n `\\x1b[33m${view.coverage.skippedNoEvidence} audit(s) not assessed:\\x1b[0m ` +\n `this scan did not obtain the evidence they need. ${view.coverage.noEvidenceReasons.join(\" \")}`,\n );\n }\n console.log(\n `\\x1b[1mโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\\x1b[0m\\n`,\n );\n\n if (report.wafProtection?.isBlocked) {\n console.log(\n ` \\x1b[41m\\x1b[37m\\x1b[1m ๐Ÿ›ก๏ธ BOT PROTECTION WALL DETECTED: ${report.wafProtection.name.toUpperCase()} \\x1b[0m`,\n );\n console.log(\n ` \\x1b[31mโš ๏ธ Diagnosis: ${report.wafProtection.reason}\\x1b[0m`,\n );\n console.log(\n ` \\x1b[90mThis storefront is actively dropping or challenging automated crawler connections.\\x1b[0m`,\n );\n console.log(\n ` \\x1b[90mAI agents (GPTBot, Claude, Perplexity) cannot index or interact with this catalog.\\x1b[0m\\n`,\n );\n }\n\n console.log(`\\x1b[1m๐Ÿ“Š CATEGORIES:\\x1b[0m`);\n for (const group of view.groups) {\n console.log(\n `\\n \\x1b[1m${group.label}\\x1b[0m \\x1b[90mโ€”\\x1b[0m ${group.score}/100`,\n );\n for (const cat of group.categories) {\n const c = cat.counts;\n const scoreColor =\n cat.score >= 90\n ? \"\\x1b[32m\"\n : cat.score >= 70\n ? \"\\x1b[34m\"\n : cat.score >= 50\n ? \"\\x1b[33m\"\n : \"\\x1b[31m\";\n console.log(\n ` ${scoreColor}โ€ข\\x1b[0m ${cat.name.padEnd(36)} : ${scoreColor}${cat.score\n .toString()\n .padStart(\n 3,\n )}/100\\x1b[0m \\x1b[90m(${c.pass}โœ“ ${c.warn}! ${c.fail}โœ—${c.advisory > 0 ? ` ${c.advisory} advisory` : \"\"})\\x1b[0m`,\n );\n }\n }\n console.log();\n }\n\n // Audit Debugger Output\n if (debugAudit) {\n const allChecks = view.groups.flatMap((g) =>\n g.categories.flatMap((c) => [...c.checks, ...c.notApplicable]),\n );\n const targetChecks = selectDebugChecks(allChecks, debugAudit);\n\n if (targetChecks.length === 0) {\n console.log(\n `\\x1b[33m[debugger] No audits found matching: ${debugAudit}\\x1b[0m\\n`,\n );\n } else {\n console.log(\n `\\x1b[1m๐Ÿ” AUDIT DEBUGGER DIAGNOSTICS (${targetChecks.length} checks):\\x1b[0m`,\n );\n console.log(\n `\\x1b[1mโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\\x1b[0m`,\n );\n\n for (const check of targetChecks) {\n const statusBadge =\n check.status === \"pass\"\n ? \"\\x1b[32m[PASS]\\x1b[0m\"\n : check.status === \"warn\"\n ? \"\\x1b[33m[WARN]\\x1b[0m\"\n : check.status === \"fail\"\n ? \"\\x1b[31m[FAIL]\\x1b[0m\"\n : \"\\x1b[90m[N/A]\\x1b[0m\";\n\n console.log(\n `\\n${statusBadge} \\x1b[1m[${check.id}] ${check.title}\\x1b[0m (Score: ${check.score})${tierMarker(check.tier)}`,\n );\n if (check.pageUrl)\n console.log(` \\x1b[90mPage:\\x1b[0m ${check.pageUrl}`);\n if (check.displayValue)\n console.log(` \\x1b[90mFound:\\x1b[0m ${check.displayValue}`);\n if (check.details?.expected)\n console.log(\n ` \\x1b[90mExpected:\\x1b[0m ${check.details.expected}`,\n );\n if (check.explanation)\n console.log(` \\x1b[90mExplanation:\\x1b[0m ${check.explanation}`);\n if (check.impact)\n console.log(` \\x1b[90mImpact:\\x1b[0m ${check.impact}`);\n if (check.fix)\n console.log(` \\x1b[90mFix:\\x1b[0m ${check.fix}`);\n if (check.details?.code) {\n console.log(` \\x1b[90mCode Example:\\x1b[0m`);\n console.log(\n check.details.code\n .split(\"\\n\")\n .map((line: string) => ` \\x1b[36m${line}\\x1b[0m`)\n .join(\"\\n\"),\n );\n }\n }\n console.log(\n `\\x1b[1mโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€\\x1b[0m\\n`,\n );\n }\n }\n\n // Ensure output directory exists\n mkdirSync(resolve(outputDir), { recursive: true });\n\n // JSON Report\n if (outputFormats.includes(\"json\")) {\n const jsonPath = resolve(outputDir, \"agent-lighthouse-report.json\");\n writeFileSync(jsonPath, JSON.stringify(report, null, 2));\n if (!isSilent)\n console.log(` \\x1b[90mโ€ข JSON Report:\\x1b[0m ${jsonPath}`);\n }\n\n // HTML Report\n let htmlPath = \"\";\n if (outputFormats.includes(\"html\")) {\n htmlPath = resolve(outputDir, \"agent-lighthouse-report.html\");\n const htmlContent = generateHtmlReport(report);\n writeFileSync(htmlPath, htmlContent);\n if (!isSilent)\n console.log(` \\x1b[90mโ€ข HTML Report:\\x1b[0m ${htmlPath}`);\n }\n\n // Markdown Summary\n if (outputFormats.includes(\"md\") || outputFormats.includes(\"markdown\")) {\n const mdPath = resolve(outputDir, \"agent-lighthouse-report.md\");\n const mdContent = generateMarkdownSummary(report);\n writeFileSync(mdPath, mdContent);\n if (!isSilent) console.log(` \\x1b[90mโ€ข Markdown Report:\\x1b[0m ${mdPath}`);\n }\n\n if (traceFile && !isSilent) {\n console.log(` \\x1b[90mโ€ข Audit trace:\\x1b[0m ${traceFile}`);\n }\n\n if (shouldView && htmlPath) {\n openInBrowser(htmlPath);\n }\n\n // Overall Score Assertion. An unscored scan fails it: the assertion asks for\n // proof the site clears a bar, and a scan that saw too little proves nothing.\n if (minScore > 0 && view.overallScore === null) {\n console.error(\n `\\n\\x1b[31mโœ– CI Assertion Failed:\\x1b[0m The scan produced no score, so it cannot clear ${minScore}. ` +\n (view.unscoredReason ??\n \"It obtained too little evidence to judge the site.\"),\n );\n process.exit(1);\n }\n if (\n minScore > 0 &&\n view.overallScore !== null &&\n view.overallScore < minScore\n ) {\n console.error(\n `\\n\\x1b[31mโœ– CI Assertion Failed:\\x1b[0m Overall score ${view.overallScore} is below minimum threshold ${minScore}`,\n );\n process.exit(1);\n }\n\n // Per-category Assertions\n const failed = failedAssertion(\n view.groups.flatMap((g) => g.categories),\n parseCategoryAssertions(args, fileConfig),\n );\n if (failed) {\n console.error(\n `\\n\\x1b[31mโœ– Category Assertion Failed:\\x1b[0m Category '${failed.name}' scored ${failed.score} (threshold: ${failed.threshold})`,\n );\n process.exit(1);\n }\n}\n\nasync function main() {\n const resolved = resolveCommand(args);\n if (resolved.action === \"help\") usage();\n await audit(resolved.url);\n}\n\nmain().catch((err) => {\n console.error(\"\\x1b[31mFatal error:\\x1b[0m\", err.message ?? err);\n process.exit(1);\n});\n","import type { PhaseId, ScanEvent } from \"@forkpoint/agent-lighthouse-core\";\n\nexport const PHASE_LABELS: Record<PhaseId, string> = {\n \"fetch-root\": \"Root files\",\n \"fetch-pages\": \"Pages\",\n analyze: \"Page analysis\",\n audits: \"Audits\",\n report: \"Report\",\n};\n\nconst SPINNER = [\"|\", \"/\", \"-\", \"\\\\\"];\nconst BAR_WIDTH = 20;\nconst ETA_MIN_FRACTION = 0.05;\nconst ETA_MAX_MS = 5 * 60 * 1000;\n\nexport interface PhaseDoneInfo {\n phase: PhaseId;\n completed: number;\n total: number;\n durationMs: number;\n failures?: number;\n color?: boolean;\n}\n\n/** Permanent one-line summary printed when a phase finishes. */\nexport function formatPhaseDone(info: PhaseDoneInfo): string {\n const color = info.color ?? true;\n const seconds = (info.durationMs / 1000).toFixed(1);\n const check = color ? \"\\x1b[32mโœ“\\x1b[0m\" : \"โœ“\";\n let line = `${check} ${PHASE_LABELS[info.phase]} ${info.completed}/${info.total} ยท ${seconds}s`;\n if ((info.failures ?? 0) > 0) {\n const suffix = `ยท ${info.failures} errored`;\n line += color ? ` \\x1b[33m${suffix}\\x1b[0m` : ` ${suffix}`;\n }\n return line;\n}\n\n/** Human ETA from overall scan fraction, or null when unreliable/absurd. */\nexport function formatEta(fraction: number, elapsedMs: number): string | null {\n if (fraction <= ETA_MIN_FRACTION || fraction >= 1) return null;\n const etaMs = (elapsedMs * (1 - fraction)) / fraction;\n if (!Number.isFinite(etaMs) || etaMs < 0 || etaMs > ETA_MAX_MS) return null;\n return `~${Math.ceil(etaMs / 1000)}s left`;\n}\n\nexport interface StatusLineInfo {\n spinnerIndex: number;\n label: string;\n completed: number;\n total: number;\n fraction: number;\n elapsedMs: number;\n}\n\n/** The sticky overwriting status line shown while a phase is active. */\nexport function formatStatusLine(info: StatusLineInfo): string {\n const spinner = SPINNER[info.spinnerIndex % SPINNER.length];\n const fraction = Math.min(1, Math.max(0, info.fraction));\n const filled = Math.round(fraction * BAR_WIDTH);\n const bar = \"โ–ˆ\".repeat(filled) + \"โ–‘\".repeat(BAR_WIDTH - filled);\n const pct = Math.round(fraction * 100);\n const eta = formatEta(info.fraction, info.elapsedMs);\n const counts = info.total > 0 ? ` ${info.completed}/${info.total}` : \"\";\n return ` \\x1b[36m${spinner}\\x1b[0m ${info.label}${counts} [${bar}] ${pct}%${eta ? ` ${eta}` : \"\"}`;\n}\n\nexport interface ProgressRendererOptions {\n /** TTY: animate a sticky status line. Non-TTY: phase summaries only, no ANSI. */\n tty: boolean;\n write?: (text: string) => void;\n now?: () => number;\n minRenderIntervalMs?: number;\n}\n\n/**\n * Stateful ScanEvent consumer. Returns the `onEvent` handler for runScan.\n * Sticky-line renders are throttled; phase:done lines are always written;\n * scan:done only erases the sticky line (the report output follows).\n */\nexport function createProgressRenderer(\n options: ProgressRendererOptions,\n): (event: ScanEvent) => void {\n const write = options.write ?? ((text: string) => process.stdout.write(text));\n const now = options.now ?? (() => Date.now());\n const minInterval = options.minRenderIntervalMs ?? 30;\n\n let spinnerIndex = 0;\n let lastRender = Number.NEGATIVE_INFINITY;\n let stickyShown = false;\n let label = \"\";\n let completed = 0;\n let total = 0;\n let failures = 0;\n\n const clearSticky = () => {\n if (stickyShown) {\n write(\"\\r\\x1b[K\");\n stickyShown = false;\n }\n };\n\n const renderSticky = (fraction: number, elapsedMs: number) => {\n const t = now();\n if (t - lastRender < minInterval) return;\n lastRender = t;\n spinnerIndex += 1;\n write(\n \"\\r\" +\n formatStatusLine({\n spinnerIndex,\n label,\n completed,\n total,\n fraction,\n elapsedMs,\n }) +\n \"\\x1b[K\",\n );\n stickyShown = true;\n };\n\n return (event) => {\n switch (event.type) {\n case \"scan:start\":\n break;\n case \"phase:start\":\n label = PHASE_LABELS[event.phase];\n completed = 0;\n total = event.totalUnits;\n failures = 0;\n break;\n case \"unit:done\":\n completed = event.completed;\n total = event.total;\n if (options.tty) renderSticky(event.fraction, event.elapsedMs);\n break;\n case \"unit:fail\":\n // A failed unit still counts as settled work (see ProgressTracker).\n completed += 1;\n failures += 1;\n if (options.tty) renderSticky(event.fraction, event.elapsedMs);\n break;\n case \"phase:done\":\n clearSticky();\n write(\n formatPhaseDone({\n phase: event.phase,\n completed,\n total,\n durationMs: event.durationMs,\n failures,\n color: options.tty,\n }) + \"\\n\",\n );\n failures = 0;\n break;\n case \"scan:done\":\n clearSticky();\n break;\n }\n };\n}\n","import {\n CATEGORY_IDS,\n PAGE_TYPE_LABELS,\n type PresetName,\n type PageType,\n} from \"@forkpoint/agent-lighthouse-core\";\n\n/** Every value `--page-type` accepts, in the order the help text lists them. */\nexport const PAGE_TYPE_IDS = Object.keys(PAGE_TYPE_LABELS) as PageType[];\n\nfunction isPageType(value: string): value is PageType {\n return (PAGE_TYPE_IDS as string[]).includes(value);\n}\n\n/**\n * Argument parsing, lifted out of `main.ts`.\n *\n * `main.ts` reads `process.argv` at module scope and calls `main()` on import,\n * so nothing in it could be exercised by a test. Everything here is a pure\n * function of the argv array and the config file, which is where every flag\n * bug this CLI has shipped actually lived โ€” `--categories` was in the help text\n * for a whole major version without being parsed at all.\n *\n * Effects stay in `main.ts`: this module never writes to a stream and never\n * calls `process.exit`.\n */\n\n/** Where `--trace` writes when it is given no path of its own. */\nexport const DEFAULT_TRACE_FILE = \"agent-lighthouse-trace.ndjson\";\n\n/** The subset of a config file that the flags override. */\nexport interface FileConfig {\n url?: string;\n preset?: string;\n minScore?: number;\n outputDir?: string;\n output?: string[];\n /** Scan budget in seconds; the flag `--timeout` overrides it. */\n timeout?: number;\n}\n\nexport interface CliOptions {\n url: string | undefined;\n configPath: string | undefined;\n presetName: PresetName;\n minScore: number;\n outputDir: string;\n outputFormats: string[];\n categories: string[] | undefined;\n /** Names passed to `--categories` that no category answers to. */\n unknownCategories: string[];\n includeExperimental: boolean;\n isSilent: boolean;\n progressJson: boolean;\n shouldView: boolean;\n debugAudit: string | undefined;\n /** The page type declared with `--page-type`, once it passed the enum. */\n pageType: PageType | undefined;\n /** A `--page-type` value that names no page type; `main` refuses it. */\n invalidPageType: string | undefined;\n /** Where to write the per-audit NDJSON trace, if `--trace` was given. */\n tracePath: string | undefined;\n /** Scan budget in seconds from `--timeout` or the config file; unset means the default. */\n timeoutSeconds: number | undefined;\n /** A `--timeout` value that is not a non-negative number; `main` refuses it. */\n invalidTimeout: string | undefined;\n}\n\n/**\n * Read one flag's value, in either `--flag=value` or `--flag value` form.\n *\n * A following token that starts with `-` is treated as the next flag rather\n * than as this one's value, so `--preset --silent` reports no preset instead of\n * silently scanning with a preset named \"--silent\".\n */\nexport function getArgValue(\n args: string[],\n shortFlag: string,\n longFlag: string,\n): string | undefined {\n for (const arg of args) {\n if (shortFlag && arg.startsWith(`${shortFlag}=`)) {\n return arg.slice(shortFlag.length + 1);\n }\n if (longFlag && arg.startsWith(`${longFlag}=`)) {\n return arg.slice(longFlag.length + 1);\n }\n }\n const shortIdx = shortFlag ? args.indexOf(shortFlag) : -1;\n if (\n shortIdx !== -1 &&\n args[shortIdx + 1] &&\n !args[shortIdx + 1]!.startsWith(\"-\")\n ) {\n return args[shortIdx + 1];\n }\n const longIdx = longFlag ? args.indexOf(longFlag) : -1;\n if (\n longIdx !== -1 &&\n args[longIdx + 1] &&\n !args[longIdx + 1]!.startsWith(\"-\")\n ) {\n return args[longIdx + 1];\n }\n return undefined;\n}\n\n/** Split a comma-separated flag value, dropping empty entries. */\nexport function splitList(value: string | undefined): string[] | undefined {\n if (value === undefined) return undefined;\n return value\n .split(\",\")\n .map((part) => part.trim())\n .filter(Boolean);\n}\n\n/** The target URL: the positional argument wins over the config file. */\nexport function resolveUrl(\n positional: string | undefined,\n fileConfig: FileConfig,\n): string | undefined {\n return positional || fileConfig.url;\n}\n\n/** Whether a string parses as an absolute URL. */\nexport function isValidUrl(url: string): boolean {\n try {\n void new URL(url);\n return true;\n } catch {\n return false;\n }\n}\n\n/**\n * Resolve every option from argv and the config file.\n *\n * Precedence is flag, then config file, then default โ€” the order the help text\n * documents.\n */\nexport function parseCliOptions(\n args: string[],\n positionalUrl: string | undefined,\n fileConfig: FileConfig = {},\n): CliOptions {\n const categories = splitList(getArgValue(args, \"\", \"--categories\"));\n const minScoreArg = getArgValue(args, \"\", \"--min-score\");\n const pageTypeArg = getArgValue(args, \"\", \"--page-type\");\n const timeoutArg = getArgValue(args, \"\", \"--timeout\");\n // Number(\"\") is 0, and 0 means \"no budget\", so a bare --timeout must not\n // read as \"run without a budget\"; it is refused like any other bad value.\n const timeoutValid =\n timeoutArg !== undefined &&\n timeoutArg.trim() !== \"\" &&\n Number.isFinite(Number(timeoutArg)) &&\n Number(timeoutArg) >= 0;\n // The config file is JSON, so its value can be anything; a negative or\n // non-numeric one must not reach the scan as \"no budget\".\n const fileTimeout: unknown = fileConfig.timeout;\n const fileTimeoutValid =\n fileTimeout === undefined ||\n (typeof fileTimeout === \"number\" &&\n Number.isFinite(fileTimeout) &&\n fileTimeout >= 0);\n\n return {\n url: resolveUrl(positionalUrl, fileConfig),\n configPath: getArgValue(args, \"-c\", \"--config\"),\n presetName: (getArgValue(args, \"-p\", \"--preset\") ||\n fileConfig.preset ||\n \"full\") as PresetName,\n minScore: minScoreArg ? Number(minScoreArg) : (fileConfig.minScore ?? 0),\n outputDir:\n getArgValue(args, \"-d\", \"--output-dir\") ||\n fileConfig.outputDir ||\n \"./reports\",\n outputFormats: splitList(getArgValue(args, \"-o\", \"--output\")) ??\n fileConfig.output ?? [\"terminal\", \"html\", \"json\"],\n categories,\n unknownCategories: (categories ?? []).filter(\n (c) => !CATEGORY_IDS.includes(c),\n ),\n includeExperimental: args.includes(\"--experimental\"),\n isSilent: args.includes(\"--silent\"),\n progressJson: args.includes(\"--progress-json\"),\n shouldView: args.includes(\"-v\") || args.includes(\"--view\"),\n debugAudit: getArgValue(args, \"\", \"--debug-audit\"),\n pageType: pageTypeArg && isPageType(pageTypeArg) ? pageTypeArg : undefined,\n invalidPageType:\n pageTypeArg && !isPageType(pageTypeArg) ? pageTypeArg : undefined,\n // A bare `--trace` with no path is still a request to trace, so it gets\n // the default file rather than being read as \"no trace\".\n tracePath: args.includes(\"--trace\")\n ? (getArgValue(args, \"\", \"--trace\") ?? DEFAULT_TRACE_FILE)\n : getArgValue(args, \"\", \"--trace\"),\n timeoutSeconds: timeoutValid\n ? Number(timeoutArg)\n : fileTimeoutValid\n ? (fileTimeout as number | undefined)\n : undefined,\n invalidTimeout:\n timeoutArg !== undefined && !timeoutValid\n ? timeoutArg\n : args.includes(\"--timeout\") && timeoutArg === undefined\n ? \"\"\n : !timeoutValid && !fileTimeoutValid\n ? `${String(fileTimeout)} (config file)`\n : undefined,\n };\n}\n\n/**\n * Which subcommand form was used.\n *\n * `al audit <url>`, `al <url>` and a bare `al` with a config file all reach the\n * same scan; anything starting with `-` is a flag, never a URL.\n */\nexport function resolveCommand(args: string[]): {\n action: \"help\" | \"audit\";\n url?: string;\n} {\n const command = args[0];\n if (!command || command === \"-h\" || command === \"--help\")\n return { action: \"help\" };\n if (command === \"audit\") return { action: \"audit\", url: args[1] };\n if (!command.startsWith(\"-\")) return { action: \"audit\", url: command };\n return { action: \"audit\" };\n}\n\n/**\n * Per-category thresholds, from `--assert-category id:min` and the config file.\n *\n * The flag repeats, so this cannot go through `getArgValue`, which returns the\n * first occurrence only. A fresh object is returned rather than the config\n * file's own: merging into `fileConfig.assertCategories` mutated the loaded\n * config, which the caller may still read.\n */\nexport function parseCategoryAssertions(\n args: string[],\n fileConfig: FileConfig & { assertCategories?: Record<string, number> } = {},\n): Record<string, number> {\n const out: Record<string, number> = {\n ...fileConfig.assertCategories,\n };\n\n const record = (pair: string | undefined) => {\n if (!pair) return;\n const [catId, min] = pair.split(\":\");\n if (catId && min) out[catId] = Number(min);\n };\n\n for (let i = 0; i < args.length; i++) {\n const arg = args[i]!;\n if (arg.startsWith(\"--assert-category=\"))\n record(arg.slice(\"--assert-category=\".length));\n else if (arg === \"--assert-category\") record(args[i + 1]);\n }\n return out;\n}\n\n/** A category as the assertions see it. */\nexport interface AssertableCategory {\n id: string;\n name: string;\n score: number;\n}\n\nexport interface FailedAssertion {\n name: string;\n score: number;\n threshold: number;\n}\n\n/**\n * The first assertion the scan does not meet, or undefined if it meets all.\n *\n * A threshold naming a category that did not run is not a failure: `--preset`\n * and `--categories` both narrow the scan, and failing CI over a category the\n * operator deliberately excluded would make the two flags unusable together.\n */\nexport function failedAssertion(\n categories: AssertableCategory[],\n assertions: Record<string, number>,\n): FailedAssertion | undefined {\n for (const [catId, threshold] of Object.entries(assertions)) {\n const matched = categories.find(\n (c) =>\n c.id === catId || c.name.toLowerCase().includes(catId.toLowerCase()),\n );\n if (matched && matched.score < threshold) {\n return { name: matched.name, score: matched.score, threshold };\n }\n }\n return undefined;\n}\n\n/** A check as the debugger selects it. */\nexport interface DebuggableCheck {\n id: string;\n title: string;\n status: string;\n}\n\n/**\n * The checks `--debug-audit` should print.\n *\n * `fails` is a reserved value meaning \"everything that is not clean\"; anything\n * else matches an audit id exactly or a title substring, so an operator can\n * type `faqpage` instead of the full id.\n */\nexport function selectDebugChecks<T extends DebuggableCheck>(\n checks: T[],\n debugAudit: string,\n): T[] {\n if (debugAudit === \"fails\") {\n return checks.filter((c) => c.status === \"fail\" || c.status === \"warn\");\n }\n const needle = debugAudit.toLowerCase();\n return checks.filter(\n (c) => c.id === debugAudit || c.title.toLowerCase().includes(needle),\n );\n}\n\n/** The shell command that opens a file in the platform's default application. */\nexport function openCommand(\n platform: NodeJS.Platform,\n filePath: string,\n): string {\n if (platform === \"darwin\") return `open \"${filePath}\"`;\n if (platform === \"win32\") return `start \"\" \"${filePath}\"`;\n return `xdg-open \"${filePath}\"`;\n}\n","import type { AuditTier } from \"@forkpoint/agent-lighthouse-core\";\n\n/**\n * A check whose tier is not `scored` is reported but never moves a score.\n * Without a marker, a failing advisory reads as work the operator owes.\n */\nexport function tierMarker(tier?: AuditTier): string {\n if (tier === \"informative\") return \" \\x1b[36m(advisory)\\x1b[0m\";\n if (tier === \"experimental\") return \" \\x1b[36m(experimental)\\x1b[0m\";\n return \"\";\n}\n"],"mappings":"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","names":["import_agent_lighthouse_core","args"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forkpoint/agent-lighthouse",
3
- "version": "3.1.0",
3
+ "version": "4.1.0",
4
4
  "description": "Lighthouse-style CLI for auditing websites for AI agents, LLM crawlers, MCP clients, llms.txt, WebMCP, OpenAPI, and Schema.org readiness",
5
5
  "author": "ForkPoint",
6
6
  "license": "Apache-2.0",
@@ -47,8 +47,8 @@
47
47
  "access": "public"
48
48
  },
49
49
  "dependencies": {
50
- "@forkpoint/agent-lighthouse-core": "3.1.0",
51
- "@forkpoint/agent-lighthouse-report": "3.1.0"
50
+ "@forkpoint/agent-lighthouse-core": "4.1.0",
51
+ "@forkpoint/agent-lighthouse-report": "4.1.0"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@types/node": "^22.10.5",