afterburn-cli 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/LICENSE +21 -0
- package/README.md +281 -0
- package/dist/ai/gemini-client.d.ts +21 -0
- package/dist/ai/gemini-client.js +105 -0
- package/dist/ai/gemini-client.js.map +1 -0
- package/dist/ai/index.d.ts +1 -0
- package/dist/ai/index.js +3 -0
- package/dist/ai/index.js.map +1 -0
- package/dist/analysis/diagnosis-schema.d.ts +106 -0
- package/dist/analysis/diagnosis-schema.js +54 -0
- package/dist/analysis/diagnosis-schema.js.map +1 -0
- package/dist/analysis/error-analyzer.d.ts +9 -0
- package/dist/analysis/error-analyzer.js +573 -0
- package/dist/analysis/error-analyzer.js.map +1 -0
- package/dist/analysis/index.d.ts +4 -0
- package/dist/analysis/index.js +6 -0
- package/dist/analysis/index.js.map +1 -0
- package/dist/analysis/source-mapper.d.ts +19 -0
- package/dist/analysis/source-mapper.js +329 -0
- package/dist/analysis/source-mapper.js.map +1 -0
- package/dist/analysis/ui-auditor.d.ts +9 -0
- package/dist/analysis/ui-auditor.js +104 -0
- package/dist/analysis/ui-auditor.js.map +1 -0
- package/dist/artifacts/artifact-storage.d.ts +44 -0
- package/dist/artifacts/artifact-storage.js +99 -0
- package/dist/artifacts/artifact-storage.js.map +1 -0
- package/dist/artifacts/index.d.ts +1 -0
- package/dist/artifacts/index.js +3 -0
- package/dist/artifacts/index.js.map +1 -0
- package/dist/browser/browser-manager.d.ts +45 -0
- package/dist/browser/browser-manager.js +88 -0
- package/dist/browser/browser-manager.js.map +1 -0
- package/dist/browser/challenge-detector.d.ts +10 -0
- package/dist/browser/challenge-detector.js +58 -0
- package/dist/browser/challenge-detector.js.map +1 -0
- package/dist/browser/cookie-dismisser.d.ts +18 -0
- package/dist/browser/cookie-dismisser.js +76 -0
- package/dist/browser/cookie-dismisser.js.map +1 -0
- package/dist/browser/index.d.ts +4 -0
- package/dist/browser/index.js +6 -0
- package/dist/browser/index.js.map +1 -0
- package/dist/browser/stealth-browser.d.ts +13 -0
- package/dist/browser/stealth-browser.js +59 -0
- package/dist/browser/stealth-browser.js.map +1 -0
- package/dist/cli/commander-cli.d.ts +2 -0
- package/dist/cli/commander-cli.js +150 -0
- package/dist/cli/commander-cli.js.map +1 -0
- package/dist/cli/doctor.d.ts +34 -0
- package/dist/cli/doctor.js +124 -0
- package/dist/cli/doctor.js.map +1 -0
- package/dist/cli/first-run.d.ts +6 -0
- package/dist/cli/first-run.js +58 -0
- package/dist/cli/first-run.js.map +1 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/index.js +5 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/cli/progress.d.ts +11 -0
- package/dist/cli/progress.js +30 -0
- package/dist/cli/progress.js.map +1 -0
- package/dist/core/engine.d.ts +33 -0
- package/dist/core/engine.js +269 -0
- package/dist/core/engine.js.map +1 -0
- package/dist/core/index.d.ts +3 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/validation.d.ts +52 -0
- package/dist/core/validation.js +228 -0
- package/dist/core/validation.js.map +1 -0
- package/dist/discovery/crawler.d.ts +58 -0
- package/dist/discovery/crawler.js +240 -0
- package/dist/discovery/crawler.js.map +1 -0
- package/dist/discovery/discovery-pipeline.d.ts +22 -0
- package/dist/discovery/discovery-pipeline.js +256 -0
- package/dist/discovery/discovery-pipeline.js.map +1 -0
- package/dist/discovery/element-mapper.d.ts +21 -0
- package/dist/discovery/element-mapper.js +422 -0
- package/dist/discovery/element-mapper.js.map +1 -0
- package/dist/discovery/index.d.ts +8 -0
- package/dist/discovery/index.js +8 -0
- package/dist/discovery/index.js.map +1 -0
- package/dist/discovery/link-validator.d.ts +15 -0
- package/dist/discovery/link-validator.js +137 -0
- package/dist/discovery/link-validator.js.map +1 -0
- package/dist/discovery/sitemap-builder.d.ts +19 -0
- package/dist/discovery/sitemap-builder.js +166 -0
- package/dist/discovery/sitemap-builder.js.map +1 -0
- package/dist/discovery/spa-detector.d.ts +12 -0
- package/dist/discovery/spa-detector.js +271 -0
- package/dist/discovery/spa-detector.js.map +1 -0
- package/dist/execution/error-detector.d.ts +10 -0
- package/dist/execution/error-detector.js +87 -0
- package/dist/execution/error-detector.js.map +1 -0
- package/dist/execution/evidence-capture.d.ts +8 -0
- package/dist/execution/evidence-capture.js +37 -0
- package/dist/execution/evidence-capture.js.map +1 -0
- package/dist/execution/index.d.ts +5 -0
- package/dist/execution/index.js +7 -0
- package/dist/execution/index.js.map +1 -0
- package/dist/execution/step-handlers.d.ts +48 -0
- package/dist/execution/step-handlers.js +349 -0
- package/dist/execution/step-handlers.js.map +1 -0
- package/dist/execution/test-data.d.ts +50 -0
- package/dist/execution/test-data.js +160 -0
- package/dist/execution/test-data.js.map +1 -0
- package/dist/execution/workflow-executor.d.ts +56 -0
- package/dist/execution/workflow-executor.js +331 -0
- package/dist/execution/workflow-executor.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/entry.d.ts +2 -0
- package/dist/mcp/entry.js +5 -0
- package/dist/mcp/entry.js.map +1 -0
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +4 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +3 -0
- package/dist/mcp/server.js +19 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools.d.ts +2 -0
- package/dist/mcp/tools.js +162 -0
- package/dist/mcp/tools.js.map +1 -0
- package/dist/planning/heuristic-planner.d.ts +7 -0
- package/dist/planning/heuristic-planner.js +238 -0
- package/dist/planning/heuristic-planner.js.map +1 -0
- package/dist/planning/index.d.ts +3 -0
- package/dist/planning/index.js +5 -0
- package/dist/planning/index.js.map +1 -0
- package/dist/planning/plan-schema.d.ts +74 -0
- package/dist/planning/plan-schema.js +39 -0
- package/dist/planning/plan-schema.js.map +1 -0
- package/dist/planning/workflow-planner.d.ts +39 -0
- package/dist/planning/workflow-planner.js +211 -0
- package/dist/planning/workflow-planner.js.map +1 -0
- package/dist/reports/health-scorer.d.ts +14 -0
- package/dist/reports/health-scorer.js +88 -0
- package/dist/reports/health-scorer.js.map +1 -0
- package/dist/reports/html-generator.d.ts +10 -0
- package/dist/reports/html-generator.js +155 -0
- package/dist/reports/html-generator.js.map +1 -0
- package/dist/reports/index.d.ts +4 -0
- package/dist/reports/index.js +6 -0
- package/dist/reports/index.js.map +1 -0
- package/dist/reports/markdown-generator.d.ts +10 -0
- package/dist/reports/markdown-generator.js +334 -0
- package/dist/reports/markdown-generator.js.map +1 -0
- package/dist/reports/priority-ranker.d.ts +22 -0
- package/dist/reports/priority-ranker.js +608 -0
- package/dist/reports/priority-ranker.js.map +1 -0
- package/dist/screenshots/dual-format.d.ts +14 -0
- package/dist/screenshots/dual-format.js +59 -0
- package/dist/screenshots/dual-format.js.map +1 -0
- package/dist/screenshots/index.d.ts +2 -0
- package/dist/screenshots/index.js +4 -0
- package/dist/screenshots/index.js.map +1 -0
- package/dist/screenshots/screenshot-manager.d.ts +33 -0
- package/dist/screenshots/screenshot-manager.js +86 -0
- package/dist/screenshots/screenshot-manager.js.map +1 -0
- package/dist/testing/accessibility-auditor.d.ts +23 -0
- package/dist/testing/accessibility-auditor.js +44 -0
- package/dist/testing/accessibility-auditor.js.map +1 -0
- package/dist/testing/index.d.ts +4 -0
- package/dist/testing/index.js +5 -0
- package/dist/testing/index.js.map +1 -0
- package/dist/testing/meta-auditor.d.ts +16 -0
- package/dist/testing/meta-auditor.js +268 -0
- package/dist/testing/meta-auditor.js.map +1 -0
- package/dist/testing/performance-monitor.d.ts +15 -0
- package/dist/testing/performance-monitor.js +64 -0
- package/dist/testing/performance-monitor.js.map +1 -0
- package/dist/types/artifacts.d.ts +58 -0
- package/dist/types/artifacts.js +3 -0
- package/dist/types/artifacts.js.map +1 -0
- package/dist/types/discovery.d.ts +124 -0
- package/dist/types/discovery.js +3 -0
- package/dist/types/discovery.js.map +1 -0
- package/dist/types/execution.d.ts +154 -0
- package/dist/types/execution.js +3 -0
- package/dist/types/execution.js.map +1 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/types/index.js +4 -0
- package/dist/types/index.js.map +1 -0
- package/dist/utils/sanitizer.d.ts +25 -0
- package/dist/utils/sanitizer.js +98 -0
- package/dist/utils/sanitizer.js.map +1 -0
- package/package.json +86 -0
- package/templates/report.hbs +202 -0
- package/templates/styles/report.css +607 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { WorkflowPlan } from '../types/discovery.js';
|
|
2
|
+
import type { ExecutionArtifact } from '../types/execution.js';
|
|
3
|
+
export interface ExecutionOptions {
|
|
4
|
+
targetUrl: string;
|
|
5
|
+
sessionId: string;
|
|
6
|
+
workflowPlans: WorkflowPlan[];
|
|
7
|
+
email?: string;
|
|
8
|
+
password?: string;
|
|
9
|
+
headless?: boolean;
|
|
10
|
+
onProgress?: (message: string) => void;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Executes all workflow plans with comprehensive error detection and auditing
|
|
14
|
+
*/
|
|
15
|
+
export declare class WorkflowExecutor {
|
|
16
|
+
private browserManager;
|
|
17
|
+
private screenshotManager;
|
|
18
|
+
private options;
|
|
19
|
+
constructor(options: ExecutionOptions);
|
|
20
|
+
/**
|
|
21
|
+
* Main execution flow: runs all workflows and produces execution artifact
|
|
22
|
+
*/
|
|
23
|
+
execute(): Promise<ExecutionArtifact>;
|
|
24
|
+
/**
|
|
25
|
+
* Execute a single workflow plan
|
|
26
|
+
*/
|
|
27
|
+
private executeWorkflow;
|
|
28
|
+
/**
|
|
29
|
+
* Audit a page with accessibility and performance checks
|
|
30
|
+
*/
|
|
31
|
+
private auditPage;
|
|
32
|
+
/**
|
|
33
|
+
* Find the parent form selector for a field
|
|
34
|
+
*/
|
|
35
|
+
private findFormSelector;
|
|
36
|
+
/**
|
|
37
|
+
* Inject --email and --password into login form fields
|
|
38
|
+
*/
|
|
39
|
+
private injectCredentialsIfNeeded;
|
|
40
|
+
/**
|
|
41
|
+
* Check if selector looks like an email field
|
|
42
|
+
*/
|
|
43
|
+
private isEmailField;
|
|
44
|
+
/**
|
|
45
|
+
* Check if selector looks like a password field
|
|
46
|
+
*/
|
|
47
|
+
private isPasswordField;
|
|
48
|
+
/**
|
|
49
|
+
* Calculate total issues across all results
|
|
50
|
+
*/
|
|
51
|
+
private calculateTotalIssues;
|
|
52
|
+
/**
|
|
53
|
+
* Log message with optional progress callback
|
|
54
|
+
*/
|
|
55
|
+
private log;
|
|
56
|
+
}
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
// Workflow execution orchestrator: runs workflow plans with error capture, evidence collection, and page auditing
|
|
2
|
+
import { BrowserManager } from '../browser/index.js';
|
|
3
|
+
import { ScreenshotManager } from '../screenshots/index.js';
|
|
4
|
+
import { setupErrorListeners } from './error-detector.js';
|
|
5
|
+
import { executeStep, captureClickState, checkDeadButton, detectBrokenForm, DEAD_BUTTON_WAIT } from './step-handlers.js';
|
|
6
|
+
import { captureErrorEvidence } from './evidence-capture.js';
|
|
7
|
+
import { auditAccessibility } from '../testing/accessibility-auditor.js';
|
|
8
|
+
import { capturePerformanceMetrics } from '../testing/performance-monitor.js';
|
|
9
|
+
import { auditMeta } from '../testing/meta-auditor.js';
|
|
10
|
+
/**
|
|
11
|
+
* Executes all workflow plans with comprehensive error detection and auditing
|
|
12
|
+
*/
|
|
13
|
+
export class WorkflowExecutor {
|
|
14
|
+
browserManager;
|
|
15
|
+
screenshotManager;
|
|
16
|
+
options;
|
|
17
|
+
constructor(options) {
|
|
18
|
+
this.options = options;
|
|
19
|
+
this.browserManager = new BrowserManager({ headless: options.headless ?? true });
|
|
20
|
+
this.screenshotManager = new ScreenshotManager();
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Main execution flow: runs all workflows and produces execution artifact
|
|
24
|
+
*/
|
|
25
|
+
async execute() {
|
|
26
|
+
this.log('Launching browser...');
|
|
27
|
+
await this.browserManager.launch();
|
|
28
|
+
const workflowResults = [];
|
|
29
|
+
const pageAudits = [];
|
|
30
|
+
const allDeadButtons = [];
|
|
31
|
+
const allBrokenForms = [];
|
|
32
|
+
try {
|
|
33
|
+
// Execute each workflow sequentially
|
|
34
|
+
for (const plan of this.options.workflowPlans) {
|
|
35
|
+
this.log(`\nExecuting workflow: ${plan.workflowName}`);
|
|
36
|
+
const result = await this.executeWorkflow(plan, pageAudits, allDeadButtons, allBrokenForms);
|
|
37
|
+
workflowResults.push(result);
|
|
38
|
+
this.log(` Status: ${result.overallStatus} (${result.passedSteps}/${result.totalSteps} passed)`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
await this.browserManager.close();
|
|
43
|
+
}
|
|
44
|
+
// Calculate total issues and exit code
|
|
45
|
+
const totalIssues = this.calculateTotalIssues(workflowResults, pageAudits, allDeadButtons, allBrokenForms);
|
|
46
|
+
const exitCode = (workflowResults.some(r => r.overallStatus === 'failed') || totalIssues > 0) ? 1 : 0;
|
|
47
|
+
// Build execution artifact
|
|
48
|
+
const artifact = {
|
|
49
|
+
version: '1.0.0',
|
|
50
|
+
stage: 'execution',
|
|
51
|
+
timestamp: new Date().toISOString(),
|
|
52
|
+
sessionId: this.options.sessionId,
|
|
53
|
+
targetUrl: this.options.targetUrl,
|
|
54
|
+
workflowResults,
|
|
55
|
+
pageAudits,
|
|
56
|
+
deadButtons: allDeadButtons,
|
|
57
|
+
brokenForms: allBrokenForms,
|
|
58
|
+
brokenLinks: [], // Populated by engine from discovery phase
|
|
59
|
+
totalIssues,
|
|
60
|
+
exitCode,
|
|
61
|
+
};
|
|
62
|
+
return artifact;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Execute a single workflow plan
|
|
66
|
+
*/
|
|
67
|
+
async executeWorkflow(plan, pageAudits, allDeadButtons, allBrokenForms) {
|
|
68
|
+
const workflowStart = Date.now();
|
|
69
|
+
// Create a fresh page for this workflow
|
|
70
|
+
const page = await this.browserManager.newPage();
|
|
71
|
+
// Set up error listeners
|
|
72
|
+
const { collector, cleanup } = setupErrorListeners(page);
|
|
73
|
+
// Set up a single persistent dialog handler to auto-dismiss alert/confirm/prompt.
|
|
74
|
+
// This MUST be a single handler per page — stacking page.once('dialog') per step
|
|
75
|
+
// caused crashes when multiple handlers tried to dismiss the same dialog.
|
|
76
|
+
const dialogHandler = async (dialog) => {
|
|
77
|
+
try {
|
|
78
|
+
await dialog.dismiss();
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// Dialog may already be handled — safe to ignore
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
page.on('dialog', dialogHandler);
|
|
85
|
+
const stepResults = [];
|
|
86
|
+
let firstNavUrl = null;
|
|
87
|
+
let lastUrl = '';
|
|
88
|
+
let pageScreenshotPath;
|
|
89
|
+
try {
|
|
90
|
+
// Execute each step sequentially
|
|
91
|
+
for (let i = 0; i < plan.steps.length; i++) {
|
|
92
|
+
const step = plan.steps[i];
|
|
93
|
+
this.log(` Step ${i + 1}/${plan.steps.length}: ${step.action} ${step.selector}`);
|
|
94
|
+
// Inject credentials if this is a login workflow
|
|
95
|
+
const stepWithCreds = this.injectCredentialsIfNeeded(step, plan);
|
|
96
|
+
// Capture pre-click state for dead-button detection (before executeStep clicks)
|
|
97
|
+
let preClickState;
|
|
98
|
+
let networkActivity = false;
|
|
99
|
+
let networkListener;
|
|
100
|
+
if (step.action === 'click') {
|
|
101
|
+
preClickState = await captureClickState(page);
|
|
102
|
+
networkListener = () => { networkActivity = true; };
|
|
103
|
+
page.on('request', networkListener);
|
|
104
|
+
}
|
|
105
|
+
// Execute the step
|
|
106
|
+
const result = await executeStep(page, stepWithCreds, i, this.options.targetUrl);
|
|
107
|
+
// After step, check for dead button using pre/post state comparison (no re-click)
|
|
108
|
+
if (step.action === 'click' && result.status === 'passed' && preClickState) {
|
|
109
|
+
await page.waitForTimeout(DEAD_BUTTON_WAIT);
|
|
110
|
+
const postState = await captureClickState(page);
|
|
111
|
+
postState.hadNetworkActivity = networkActivity;
|
|
112
|
+
if (networkListener)
|
|
113
|
+
page.off('request', networkListener);
|
|
114
|
+
const deadResult = checkDeadButton(step.selector, preClickState, postState);
|
|
115
|
+
if (deadResult.isDead) {
|
|
116
|
+
allDeadButtons.push(deadResult);
|
|
117
|
+
this.log(` Warning: Dead button detected`);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
else if (networkListener) {
|
|
121
|
+
page.off('request', networkListener);
|
|
122
|
+
}
|
|
123
|
+
// Capture evidence on failure
|
|
124
|
+
if (result.status === 'failed') {
|
|
125
|
+
this.log(` Failed: ${result.error}`);
|
|
126
|
+
result.evidence = await captureErrorEvidence(page, collector, this.screenshotManager, i);
|
|
127
|
+
}
|
|
128
|
+
stepResults.push(result);
|
|
129
|
+
// Track first navigation URL for auditing
|
|
130
|
+
if (step.action === 'navigate' && !firstNavUrl) {
|
|
131
|
+
firstNavUrl = page.url();
|
|
132
|
+
}
|
|
133
|
+
// Track last URL
|
|
134
|
+
lastUrl = page.url();
|
|
135
|
+
// Form detection: check if this step interacts with a form
|
|
136
|
+
const isFormFillThenSubmit = step.action === 'fill' &&
|
|
137
|
+
i + 1 < plan.steps.length &&
|
|
138
|
+
plan.steps[i + 1].action === 'click' &&
|
|
139
|
+
plan.steps[i + 1].selector.includes('submit');
|
|
140
|
+
const isClickInsideForm = step.action === 'click' && result.status === 'passed';
|
|
141
|
+
if (isFormFillThenSubmit || isClickInsideForm) {
|
|
142
|
+
const formSelector = await this.findFormSelector(page, step.selector);
|
|
143
|
+
if (formSelector) {
|
|
144
|
+
const brokenResult = await detectBrokenForm(page, formSelector);
|
|
145
|
+
if (brokenResult.isBroken) {
|
|
146
|
+
allBrokenForms.push(brokenResult);
|
|
147
|
+
this.log(` Warning: Broken form detected`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// Audit key pages (first navigation + final URL)
|
|
153
|
+
if (firstNavUrl) {
|
|
154
|
+
await this.auditPage(page, firstNavUrl, pageAudits);
|
|
155
|
+
}
|
|
156
|
+
if (lastUrl && lastUrl !== firstNavUrl) {
|
|
157
|
+
await this.auditPage(page, lastUrl, pageAudits);
|
|
158
|
+
}
|
|
159
|
+
// Capture page-level screenshot for report embedding (before page closes)
|
|
160
|
+
try {
|
|
161
|
+
const pageScreenshot = await this.screenshotManager.capture(page, `workflow-${plan.workflowName.replace(/\s+/g, '-').toLowerCase()}`);
|
|
162
|
+
pageScreenshotPath = pageScreenshot.pngPath;
|
|
163
|
+
}
|
|
164
|
+
catch {
|
|
165
|
+
// Graceful degradation — screenshot failure doesn't break workflow
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
finally {
|
|
169
|
+
// Clean up error listeners and dialog handler
|
|
170
|
+
cleanup();
|
|
171
|
+
page.off('dialog', dialogHandler);
|
|
172
|
+
await page.close();
|
|
173
|
+
}
|
|
174
|
+
// Calculate workflow result
|
|
175
|
+
const passedSteps = stepResults.filter(r => r.status === 'passed').length;
|
|
176
|
+
const failedSteps = stepResults.filter(r => r.status === 'failed').length;
|
|
177
|
+
const skippedSteps = stepResults.filter(r => r.status === 'skipped').length;
|
|
178
|
+
const overallStatus = failedSteps > 0 ? 'failed' : 'passed';
|
|
179
|
+
return {
|
|
180
|
+
workflowName: plan.workflowName,
|
|
181
|
+
description: plan.description,
|
|
182
|
+
totalSteps: plan.steps.length,
|
|
183
|
+
passedSteps,
|
|
184
|
+
failedSteps,
|
|
185
|
+
skippedSteps,
|
|
186
|
+
stepResults,
|
|
187
|
+
errors: collector,
|
|
188
|
+
overallStatus,
|
|
189
|
+
duration: Date.now() - workflowStart,
|
|
190
|
+
pageScreenshotRef: pageScreenshotPath,
|
|
191
|
+
};
|
|
192
|
+
}
|
|
193
|
+
/**
|
|
194
|
+
* Audit a page with accessibility and performance checks
|
|
195
|
+
*/
|
|
196
|
+
async auditPage(page, url, pageAudits) {
|
|
197
|
+
// Check if we already audited this URL
|
|
198
|
+
if (pageAudits.some(a => a.url === url)) {
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
this.log(` Auditing page: ${url}`);
|
|
202
|
+
// Ensure audit data is captured from the URL being recorded.
|
|
203
|
+
if (page.url() !== url) {
|
|
204
|
+
try {
|
|
205
|
+
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 15_000 });
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
this.log(` Skipping audit: unable to load ${url} (${error instanceof Error ? error.message : String(error)})`);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
const [accessibility, performance, metaAudit] = await Promise.all([
|
|
213
|
+
auditAccessibility(page),
|
|
214
|
+
capturePerformanceMetrics(page),
|
|
215
|
+
auditMeta(page),
|
|
216
|
+
]);
|
|
217
|
+
pageAudits.push({
|
|
218
|
+
url,
|
|
219
|
+
accessibility,
|
|
220
|
+
performance,
|
|
221
|
+
metaIssues: metaAudit.issues.length > 0 ? metaAudit.issues : undefined,
|
|
222
|
+
});
|
|
223
|
+
if (accessibility.violationCount > 0) {
|
|
224
|
+
this.log(` Accessibility: ${accessibility.violationCount} violations found`);
|
|
225
|
+
}
|
|
226
|
+
if (metaAudit.issues.length > 0) {
|
|
227
|
+
this.log(` Meta/SEO: ${metaAudit.issues.length} issues found`);
|
|
228
|
+
}
|
|
229
|
+
if (performance.lcp > 0) {
|
|
230
|
+
this.log(` Performance: LCP ${Math.round(performance.lcp)}ms`);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Find the parent form selector for a field
|
|
235
|
+
*/
|
|
236
|
+
async findFormSelector(page, fieldSelector) {
|
|
237
|
+
try {
|
|
238
|
+
return await page.evaluate((selector) => {
|
|
239
|
+
const field = document.querySelector(selector);
|
|
240
|
+
if (!field)
|
|
241
|
+
return null;
|
|
242
|
+
const form = field.closest('form');
|
|
243
|
+
if (!form)
|
|
244
|
+
return null;
|
|
245
|
+
// Try to build a unique selector
|
|
246
|
+
if (form.id)
|
|
247
|
+
return `form#${form.id}`;
|
|
248
|
+
if (form.className)
|
|
249
|
+
return `form.${form.className.split(' ')[0]}`;
|
|
250
|
+
return 'form';
|
|
251
|
+
}, fieldSelector);
|
|
252
|
+
}
|
|
253
|
+
catch {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Inject --email and --password into login form fields
|
|
259
|
+
*/
|
|
260
|
+
injectCredentialsIfNeeded(step, plan) {
|
|
261
|
+
// Only inject if this is a fill action and we have credentials
|
|
262
|
+
if (step.action !== 'fill') {
|
|
263
|
+
return step;
|
|
264
|
+
}
|
|
265
|
+
// Check if this looks like a login workflow
|
|
266
|
+
const isLoginWorkflow = plan.workflowName.toLowerCase().includes('login') ||
|
|
267
|
+
plan.workflowName.toLowerCase().includes('sign in') ||
|
|
268
|
+
plan.description.toLowerCase().includes('authentication');
|
|
269
|
+
if (!isLoginWorkflow) {
|
|
270
|
+
return step;
|
|
271
|
+
}
|
|
272
|
+
// Inject email
|
|
273
|
+
if (this.options.email && this.isEmailField(step.selector)) {
|
|
274
|
+
return { ...step, value: this.options.email };
|
|
275
|
+
}
|
|
276
|
+
// Inject password
|
|
277
|
+
if (this.options.password && this.isPasswordField(step.selector)) {
|
|
278
|
+
return { ...step, value: this.options.password };
|
|
279
|
+
}
|
|
280
|
+
return step;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* Check if selector looks like an email field
|
|
284
|
+
*/
|
|
285
|
+
isEmailField(selector) {
|
|
286
|
+
const lower = selector.toLowerCase();
|
|
287
|
+
return lower.includes('email') || lower.includes('username') || lower.includes('user');
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Check if selector looks like a password field
|
|
291
|
+
*/
|
|
292
|
+
isPasswordField(selector) {
|
|
293
|
+
const lower = selector.toLowerCase();
|
|
294
|
+
return lower.includes('password') || lower.includes('pass');
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Calculate total issues across all results
|
|
298
|
+
*/
|
|
299
|
+
calculateTotalIssues(workflowResults, pageAudits, deadButtons, brokenForms) {
|
|
300
|
+
let total = 0;
|
|
301
|
+
// Failed workflow steps
|
|
302
|
+
total += workflowResults.reduce((sum, r) => sum + r.failedSteps, 0);
|
|
303
|
+
// Console errors
|
|
304
|
+
total += workflowResults.reduce((sum, r) => sum + r.errors.consoleErrors.length, 0);
|
|
305
|
+
// Network failures
|
|
306
|
+
total += workflowResults.reduce((sum, r) => sum + r.errors.networkFailures.length, 0);
|
|
307
|
+
// Broken images
|
|
308
|
+
total += workflowResults.reduce((sum, r) => sum + r.errors.brokenImages.length, 0);
|
|
309
|
+
// Dead buttons
|
|
310
|
+
total += deadButtons.filter(b => b.isDead).length;
|
|
311
|
+
// Broken forms
|
|
312
|
+
total += brokenForms.filter(f => f.isBroken).length;
|
|
313
|
+
// Note: broken links are added post-execution by engine.ts from discovery data
|
|
314
|
+
// Accessibility violations (only critical and serious)
|
|
315
|
+
total += pageAudits.reduce((sum, audit) => {
|
|
316
|
+
if (!audit.accessibility)
|
|
317
|
+
return sum;
|
|
318
|
+
return sum + audit.accessibility.violations.filter(v => v.impact === 'critical' || v.impact === 'serious').length;
|
|
319
|
+
}, 0);
|
|
320
|
+
return total;
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Log message with optional progress callback
|
|
324
|
+
*/
|
|
325
|
+
log(message) {
|
|
326
|
+
if (this.options.onProgress) {
|
|
327
|
+
this.options.onProgress(message);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
//# sourceMappingURL=workflow-executor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"workflow-executor.js","sourceRoot":"","sources":["../../src/execution/workflow-executor.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,4BAA4B;AAC5B,OAAO,EAAE,OAAO,EAAE,MAAM,gBAAgB,CAAC;AAEzC,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"entry.js","sourceRoot":"","sources":["../../src/mcp/entry.ts"],"names":[],"mappings":";AACA,mCAAmC;AACnC,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAE1C,WAAW,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/mcp/index.ts"],"names":[],"mappings":"AAAA,2BAA2B;AAC3B,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AACxD,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// MCP server initialization and transport setup
|
|
2
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
3
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
4
|
+
import { registerTools } from './tools.js';
|
|
5
|
+
export function createServer() {
|
|
6
|
+
const server = new McpServer({ name: 'afterburn-mcp', version: '1.0.0' }, {
|
|
7
|
+
capabilities: {
|
|
8
|
+
tools: {}
|
|
9
|
+
}
|
|
10
|
+
});
|
|
11
|
+
registerTools(server);
|
|
12
|
+
return server;
|
|
13
|
+
}
|
|
14
|
+
export async function startServer() {
|
|
15
|
+
const server = createServer();
|
|
16
|
+
const transport = new StdioServerTransport();
|
|
17
|
+
await server.connect(transport);
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/mcp/server.ts"],"names":[],"mappings":"AAAA,gDAAgD;AAChD,OAAO,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AACpE,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAE3C,MAAM,UAAU,YAAY;IAC1B,MAAM,MAAM,GAAG,IAAI,SAAS,CAC1B,EAAE,IAAI,EAAE,eAAe,EAAE,OAAO,EAAE,OAAO,EAAE,EAC3C;QACE,YAAY,EAAE;YACZ,KAAK,EAAE,EAAE;SACV;KACF,CACF,CAAC;IAEF,aAAa,CAAC,MAAM,CAAC,CAAC;IAEtB,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,WAAW;IAC/B,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;IAC9B,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAC7C,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { runAfterburn } from '../core/engine.js';
|
|
3
|
+
import { validatePublicUrl, validatePath, validateMaxPages } from '../core/validation.js';
|
|
4
|
+
import { sanitizeForMarkdownInline, redactSensitiveData, redactSensitiveUrl } from '../utils/sanitizer.js';
|
|
5
|
+
export function registerTools(server) {
|
|
6
|
+
// Register scan_website tool
|
|
7
|
+
server.registerTool('scan_website', {
|
|
8
|
+
description: 'Scan a website for broken workflows, errors, accessibility issues, and UI/UX problems. Returns both structured JSON data and a markdown summary.',
|
|
9
|
+
inputSchema: z.object({
|
|
10
|
+
url: z.string().describe('The target website URL to scan (required)'),
|
|
11
|
+
source: z.string().optional().describe('Optional path to source code directory for source mapping'),
|
|
12
|
+
email: z.string().optional().describe('Optional email for login workflow testing'),
|
|
13
|
+
password: z.string().optional().describe('Optional password for login workflow testing'),
|
|
14
|
+
outputDir: z.string().optional().describe('Optional custom output directory for reports'),
|
|
15
|
+
maxPages: z.union([z.number(), z.string()]).optional().transform(val => {
|
|
16
|
+
if (val === undefined || val === null)
|
|
17
|
+
return undefined;
|
|
18
|
+
if (typeof val === 'string') {
|
|
19
|
+
const trimmed = val.trim();
|
|
20
|
+
if (!/^\d+$/.test(trimmed)) {
|
|
21
|
+
throw new Error(`Invalid maxPages value: "${val}". Must be a non-negative integer.`);
|
|
22
|
+
}
|
|
23
|
+
const parsed = Number(trimmed);
|
|
24
|
+
return parsed;
|
|
25
|
+
}
|
|
26
|
+
if (typeof val === 'number') {
|
|
27
|
+
if (isNaN(val) || val < 0 || !Number.isInteger(val)) {
|
|
28
|
+
throw new Error(`Invalid maxPages value: ${val}. Must be a non-negative integer.`);
|
|
29
|
+
}
|
|
30
|
+
return val;
|
|
31
|
+
}
|
|
32
|
+
return undefined;
|
|
33
|
+
}).describe('Optional maximum number of pages to crawl (default: 50)')
|
|
34
|
+
})
|
|
35
|
+
}, async (args, extra) => {
|
|
36
|
+
try {
|
|
37
|
+
// Security: validate all string inputs at the MCP boundary
|
|
38
|
+
const validatedUrl = await validatePublicUrl(args.url);
|
|
39
|
+
const validatedSource = args.source ? validatePath(args.source, 'source', process.cwd()) : undefined;
|
|
40
|
+
const validatedOutputDir = args.outputDir ? validatePath(args.outputDir, 'outputDir', process.cwd()) : undefined;
|
|
41
|
+
const validatedMaxPages = validateMaxPages(args.maxPages);
|
|
42
|
+
// Track progress if client supports it
|
|
43
|
+
const progressToken = extra._meta?.progressToken;
|
|
44
|
+
const sendProgress = async (stage, message) => {
|
|
45
|
+
if (progressToken && server.server) {
|
|
46
|
+
try {
|
|
47
|
+
await server.server.notification({
|
|
48
|
+
method: 'notifications/progress',
|
|
49
|
+
params: {
|
|
50
|
+
progressToken,
|
|
51
|
+
progress: 0, // MCP progress is 0-1, but we don't have precise percentages
|
|
52
|
+
total: 1,
|
|
53
|
+
message: `[${stage}] ${message}`
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
// Progress notifications are best-effort, don't fail on errors
|
|
59
|
+
console.error('Failed to send progress notification:', err);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
// Run Afterburn scan with validated inputs
|
|
64
|
+
const result = await runAfterburn({
|
|
65
|
+
targetUrl: validatedUrl,
|
|
66
|
+
sourcePath: validatedSource,
|
|
67
|
+
email: args.email,
|
|
68
|
+
password: args.password,
|
|
69
|
+
outputDir: validatedOutputDir,
|
|
70
|
+
maxPages: validatedMaxPages,
|
|
71
|
+
onProgress: async (stage, message) => {
|
|
72
|
+
await sendProgress(stage, message);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
// Build structured JSON response
|
|
76
|
+
const structuredResult = {
|
|
77
|
+
healthScore: result.healthScore.overall,
|
|
78
|
+
healthLabel: result.healthScore.label,
|
|
79
|
+
totalIssues: result.totalIssues,
|
|
80
|
+
workflowsPassed: result.workflowsPassed,
|
|
81
|
+
workflowsTotal: result.workflowsTotal,
|
|
82
|
+
highPriorityIssues: result.highPriorityCount,
|
|
83
|
+
mediumPriorityIssues: result.mediumPriorityCount,
|
|
84
|
+
lowPriorityIssues: result.lowPriorityCount,
|
|
85
|
+
issues: result.prioritizedIssues.slice(0, 20).map(issue => ({
|
|
86
|
+
priority: issue.priority,
|
|
87
|
+
category: issue.category,
|
|
88
|
+
summary: redactSensitiveData(issue.summary),
|
|
89
|
+
fix: redactSensitiveData(issue.fixSuggestion),
|
|
90
|
+
location: redactSensitiveUrl(issue.location)
|
|
91
|
+
})),
|
|
92
|
+
reportPaths: {
|
|
93
|
+
html: result.htmlReportPath,
|
|
94
|
+
markdown: result.markdownReportPath
|
|
95
|
+
},
|
|
96
|
+
sessionId: result.sessionId
|
|
97
|
+
};
|
|
98
|
+
// Build markdown summary
|
|
99
|
+
const markdownSummary = buildMarkdownSummary(result);
|
|
100
|
+
// Return both JSON and markdown content blocks
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: 'text',
|
|
105
|
+
text: JSON.stringify(structuredResult, null, 2)
|
|
106
|
+
},
|
|
107
|
+
{
|
|
108
|
+
type: 'text',
|
|
109
|
+
text: markdownSummary
|
|
110
|
+
}
|
|
111
|
+
]
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
// Return error as structured response - server stays responsive
|
|
116
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
117
|
+
return {
|
|
118
|
+
content: [
|
|
119
|
+
{
|
|
120
|
+
type: 'text',
|
|
121
|
+
text: JSON.stringify({
|
|
122
|
+
isError: true,
|
|
123
|
+
error: errorMessage,
|
|
124
|
+
message: 'Scan failed. Check the error message for details.'
|
|
125
|
+
}, null, 2)
|
|
126
|
+
}
|
|
127
|
+
],
|
|
128
|
+
isError: true
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
function buildMarkdownSummary(result) {
|
|
134
|
+
const lines = [];
|
|
135
|
+
lines.push('# Afterburn Scan Summary\n');
|
|
136
|
+
lines.push(`**Health Score:** ${result.healthScore.overall}/100 (${result.healthScore.label})\n`);
|
|
137
|
+
lines.push(`**Total Issues:** ${result.totalIssues}`);
|
|
138
|
+
lines.push(`**Workflows:** ${result.workflowsPassed}/${result.workflowsTotal} passed\n`);
|
|
139
|
+
if (result.totalIssues > 0) {
|
|
140
|
+
lines.push('## Top Issues\n');
|
|
141
|
+
const topIssues = result.prioritizedIssues.slice(0, 5);
|
|
142
|
+
topIssues.forEach((issue, idx) => {
|
|
143
|
+
lines.push(`### ${idx + 1}. [${issue.priority.toUpperCase()}] ${issue.category}`);
|
|
144
|
+
lines.push(`**Problem:** ${redactSensitiveData(sanitizeForMarkdownInline(issue.summary))}`);
|
|
145
|
+
lines.push(`**Fix:** ${redactSensitiveData(sanitizeForMarkdownInline(issue.fixSuggestion))}`);
|
|
146
|
+
lines.push(`**Location:** ${sanitizeForMarkdownInline(redactSensitiveUrl(issue.location))}\n`);
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
else {
|
|
150
|
+
lines.push('## No Issues Found\n');
|
|
151
|
+
lines.push('All workflows passed and no errors detected. Great job!\n');
|
|
152
|
+
}
|
|
153
|
+
if (result.htmlReportPath) {
|
|
154
|
+
lines.push(`## Reports\n`);
|
|
155
|
+
lines.push(`- **HTML Report:** ${result.htmlReportPath}`);
|
|
156
|
+
if (result.markdownReportPath) {
|
|
157
|
+
lines.push(`- **Markdown Report:** ${result.markdownReportPath}`);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return lines.join('\n');
|
|
161
|
+
}
|
|
162
|
+
//# sourceMappingURL=tools.js.map
|