userflow-mcp 0.2.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 +177 -0
- package/dist/analysis/clarity.d.ts +7 -0
- package/dist/analysis/clarity.d.ts.map +1 -0
- package/dist/analysis/clarity.js +114 -0
- package/dist/analysis/clarity.js.map +1 -0
- package/dist/analysis/cognitive-load.d.ts +7 -0
- package/dist/analysis/cognitive-load.d.ts.map +1 -0
- package/dist/analysis/cognitive-load.js +45 -0
- package/dist/analysis/cognitive-load.js.map +1 -0
- package/dist/analysis/emotional-arc.d.ts +10 -0
- package/dist/analysis/emotional-arc.d.ts.map +1 -0
- package/dist/analysis/emotional-arc.js +95 -0
- package/dist/analysis/emotional-arc.js.map +1 -0
- package/dist/analysis/friction.d.ts +23 -0
- package/dist/analysis/friction.d.ts.map +1 -0
- package/dist/analysis/friction.js +61 -0
- package/dist/analysis/friction.js.map +1 -0
- package/dist/feedback/comparison.d.ts +11 -0
- package/dist/feedback/comparison.d.ts.map +1 -0
- package/dist/feedback/comparison.js +156 -0
- package/dist/feedback/comparison.js.map +1 -0
- package/dist/feedback/generator.d.ts +19 -0
- package/dist/feedback/generator.d.ts.map +1 -0
- package/dist/feedback/generator.js +139 -0
- package/dist/feedback/generator.js.map +1 -0
- package/dist/feedback/report.d.ts +10 -0
- package/dist/feedback/report.d.ts.map +1 -0
- package/dist/feedback/report.js +123 -0
- package/dist/feedback/report.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -0
- package/dist/personas/engine.d.ts +39 -0
- package/dist/personas/engine.d.ts.map +1 -0
- package/dist/personas/engine.js +58 -0
- package/dist/personas/engine.js.map +1 -0
- package/dist/personas/presets.d.ts +11 -0
- package/dist/personas/presets.d.ts.map +1 -0
- package/dist/personas/presets.js +251 -0
- package/dist/personas/presets.js.map +1 -0
- package/dist/personas/types.d.ts +11 -0
- package/dist/personas/types.d.ts.map +1 -0
- package/dist/personas/types.js +23 -0
- package/dist/personas/types.js.map +1 -0
- package/dist/server.d.ts +3 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +288 -0
- package/dist/server.js.map +1 -0
- package/dist/session/session-manager.d.ts +54 -0
- package/dist/session/session-manager.d.ts.map +1 -0
- package/dist/session/session-manager.js +228 -0
- package/dist/session/session-manager.js.map +1 -0
- package/dist/session/types.d.ts +35 -0
- package/dist/session/types.d.ts.map +1 -0
- package/dist/session/types.js +2 -0
- package/dist/session/types.js.map +1 -0
- package/dist/types.d.ts +284 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/actions.d.ts +17 -0
- package/dist/utils/actions.d.ts.map +1 -0
- package/dist/utils/actions.js +89 -0
- package/dist/utils/actions.js.map +1 -0
- package/dist/utils/browser.d.ts +22 -0
- package/dist/utils/browser.d.ts.map +1 -0
- package/dist/utils/browser.js +122 -0
- package/dist/utils/browser.js.map +1 -0
- package/dist/utils/page-snapshot.d.ts +11 -0
- package/dist/utils/page-snapshot.d.ts.map +1 -0
- package/dist/utils/page-snapshot.js +102 -0
- package/dist/utils/page-snapshot.js.map +1 -0
- package/dist/walker/action-planner.d.ts +15 -0
- package/dist/walker/action-planner.d.ts.map +1 -0
- package/dist/walker/action-planner.js +236 -0
- package/dist/walker/action-planner.js.map +1 -0
- package/dist/walker/flow-walker.d.ts +10 -0
- package/dist/walker/flow-walker.d.ts.map +1 -0
- package/dist/walker/flow-walker.js +107 -0
- package/dist/walker/flow-walker.js.map +1 -0
- package/dist/walker/session-recorder.d.ts +28 -0
- package/dist/walker/session-recorder.d.ts.map +1 -0
- package/dist/walker/session-recorder.js +90 -0
- package/dist/walker/session-recorder.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
import { getDiscoveryProbability } from "../personas/engine.js";
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
/**
|
|
4
|
+
* Score how relevant an element is to the persona's goals.
|
|
5
|
+
* Returns 0-100 relevance score.
|
|
6
|
+
*/
|
|
7
|
+
function scoreElementRelevance(element, goals) {
|
|
8
|
+
const elementText = `${element.text} ${element.ariaLabel ?? ""} ${element.placeholder ?? ""} ${element.href ?? ""}`.toLowerCase();
|
|
9
|
+
let score = 0;
|
|
10
|
+
for (const goal of goals) {
|
|
11
|
+
const goalWords = goal.toLowerCase().split(/\s+/);
|
|
12
|
+
for (const word of goalWords) {
|
|
13
|
+
if (word.length > 2 && elementText.includes(word)) {
|
|
14
|
+
score += 20;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
// Boost for common action patterns
|
|
19
|
+
const ctaPatterns = ["sign up", "get started", "try", "start", "create", "login", "log in", "register", "submit", "continue", "next", "buy", "purchase", "subscribe", "pricing", "plans"];
|
|
20
|
+
for (const pattern of ctaPatterns) {
|
|
21
|
+
if (elementText.includes(pattern)) {
|
|
22
|
+
score += 15;
|
|
23
|
+
break;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
// Boost for prominent elements (buttons > links)
|
|
27
|
+
if (element.tagName === "button" || element.type === "submit")
|
|
28
|
+
score += 10;
|
|
29
|
+
if (element.tagName === "a" && element.href)
|
|
30
|
+
score += 5;
|
|
31
|
+
return Math.min(100, score);
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Determine what emotional state this persona would feel on this page.
|
|
35
|
+
*/
|
|
36
|
+
export function assessEmotion(persona, page, previousSteps, frictionPoints) {
|
|
37
|
+
const stepCount = previousSteps.length;
|
|
38
|
+
const recentFriction = frictionPoints.filter((f) => f.severity === "high" || f.severity === "critical");
|
|
39
|
+
const prevEmotion = previousSteps[previousSteps.length - 1]?.emotionalState ?? "curious";
|
|
40
|
+
// First page — usually curious
|
|
41
|
+
if (stepCount === 0)
|
|
42
|
+
return "curious";
|
|
43
|
+
// High friction → frustrated or confused
|
|
44
|
+
if (recentFriction.length >= 2)
|
|
45
|
+
return "frustrated";
|
|
46
|
+
if (recentFriction.length === 1)
|
|
47
|
+
return "confused";
|
|
48
|
+
// Error messages on page → anxious
|
|
49
|
+
if (page.errorMessages.length > 0)
|
|
50
|
+
return "anxious";
|
|
51
|
+
// Too many steps without progress → bored or frustrated
|
|
52
|
+
const patienceMap = { very_low: 3, low: 5, moderate: 8, high: 12, very_high: 18 };
|
|
53
|
+
const patienceThreshold = patienceMap[persona.traits.patience] ?? 8;
|
|
54
|
+
if (stepCount > patienceThreshold * 0.7)
|
|
55
|
+
return "frustrated";
|
|
56
|
+
if (stepCount > patienceThreshold * 0.5)
|
|
57
|
+
return "bored";
|
|
58
|
+
// Page loads fast and has clear content → confident
|
|
59
|
+
if (page.loadTimeMs < 2000 && page.headings.length > 0 && page.buttons.length > 0)
|
|
60
|
+
return "confident";
|
|
61
|
+
// Default: carry forward or neutral
|
|
62
|
+
if (prevEmotion === "frustrated" && recentFriction.length === 0)
|
|
63
|
+
return "neutral";
|
|
64
|
+
return prevEmotion === "curious" ? "neutral" : prevEmotion;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Detect friction points on the current page for this persona.
|
|
68
|
+
*/
|
|
69
|
+
export function detectFriction(persona, page, stepIndex) {
|
|
70
|
+
const frictionPoints = [];
|
|
71
|
+
const createFriction = (severity, description, suggestion) => {
|
|
72
|
+
frictionPoints.push({
|
|
73
|
+
id: randomUUID(),
|
|
74
|
+
severity,
|
|
75
|
+
description,
|
|
76
|
+
location: page.url,
|
|
77
|
+
suggestion,
|
|
78
|
+
stepIndex,
|
|
79
|
+
});
|
|
80
|
+
};
|
|
81
|
+
// Slow load time
|
|
82
|
+
if (page.loadTimeMs > 5000) {
|
|
83
|
+
createFriction("high", `Page took ${(page.loadTimeMs / 1000).toFixed(1)}s to load`, "Optimize page load performance — aim for under 3 seconds");
|
|
84
|
+
}
|
|
85
|
+
else if (page.loadTimeMs > 3000) {
|
|
86
|
+
createFriction("medium", `Page took ${(page.loadTimeMs / 1000).toFixed(1)}s to load`, "Consider lazy loading or code splitting to improve load time");
|
|
87
|
+
}
|
|
88
|
+
// No clear headings
|
|
89
|
+
if (page.headings.length === 0) {
|
|
90
|
+
createFriction("medium", "No headings found — page purpose is unclear", "Add a clear H1 heading that explains what this page is about");
|
|
91
|
+
}
|
|
92
|
+
// Too many interactive elements (cognitive overload)
|
|
93
|
+
const interactiveCount = page.interactiveElements.length;
|
|
94
|
+
if (interactiveCount > 20 && persona.traits.techLiteracy === "novice") {
|
|
95
|
+
createFriction("high", `Page has ${interactiveCount} interactive elements — overwhelming for this user`, "Simplify the page or use progressive disclosure to reduce cognitive load");
|
|
96
|
+
}
|
|
97
|
+
else if (interactiveCount > 30) {
|
|
98
|
+
createFriction("medium", `Page has ${interactiveCount} interactive elements — high cognitive load`, "Consider grouping related actions or using progressive disclosure");
|
|
99
|
+
}
|
|
100
|
+
// Error messages visible
|
|
101
|
+
if (page.errorMessages.length > 0) {
|
|
102
|
+
createFriction("high", `Error messages visible: "${page.errorMessages[0]}"`, "Ensure error messages are clear, actionable, and help the user recover");
|
|
103
|
+
}
|
|
104
|
+
// Too many form fields at once
|
|
105
|
+
if (page.formFields.length > 6 && persona.traits.patience !== "very_high") {
|
|
106
|
+
createFriction("high", `Form has ${page.formFields.length} fields — likely to cause abandonment`, "Break the form into smaller steps or make non-essential fields optional");
|
|
107
|
+
}
|
|
108
|
+
else if (page.formFields.length > 4) {
|
|
109
|
+
createFriction("low", `Form has ${page.formFields.length} fields`, "Consider which fields are truly required at this stage");
|
|
110
|
+
}
|
|
111
|
+
// No clear CTA
|
|
112
|
+
if (page.buttons.length === 0 && page.links.length > 0) {
|
|
113
|
+
createFriction("medium", "No buttons found — unclear what action to take", "Add a prominent call-to-action button");
|
|
114
|
+
}
|
|
115
|
+
// Empty state (no content)
|
|
116
|
+
if (page.mainText.trim().length < 50 && page.buttons.length === 0 && page.links.length < 3) {
|
|
117
|
+
createFriction("critical", "Page appears empty — no guidance for the user", "Add an empty state with clear next steps or a getting-started guide");
|
|
118
|
+
}
|
|
119
|
+
// Accessibility friction for users with needs
|
|
120
|
+
if (persona.traits.accessibilityNeeds.includes("screen_reader")) {
|
|
121
|
+
const unlabeledButtons = page.buttons.filter((b) => !b.ariaLabel && !b.text.trim());
|
|
122
|
+
if (unlabeledButtons.length > 0) {
|
|
123
|
+
createFriction("critical", `${unlabeledButtons.length} buttons have no accessible label`, "Add aria-label or visible text to all interactive elements");
|
|
124
|
+
}
|
|
125
|
+
const unlabeledInputs = page.formFields.filter((f) => !f.ariaLabel && !f.placeholder);
|
|
126
|
+
if (unlabeledInputs.length > 0) {
|
|
127
|
+
createFriction("high", `${unlabeledInputs.length} form fields have no label or placeholder`, "Add labels to all form fields for screen reader accessibility");
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
if (persona.traits.accessibilityNeeds.includes("low_vision") && page.formFields.length > 0) {
|
|
131
|
+
createFriction("low", "Verify form field labels have sufficient size and contrast for low-vision users", "Ensure text is at least 16px and has 4.5:1 contrast ratio");
|
|
132
|
+
}
|
|
133
|
+
return frictionPoints;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Plan the next action for a persona on the current page.
|
|
137
|
+
* Uses heuristic scoring based on persona goals and traits.
|
|
138
|
+
*/
|
|
139
|
+
export function planNextAction(persona, page, previousSteps) {
|
|
140
|
+
const discoveryProb = getDiscoveryProbability(persona);
|
|
141
|
+
const stepCount = previousSteps.length;
|
|
142
|
+
// If this is the first step, the action is just "read" — absorb the page
|
|
143
|
+
if (stepCount === 0) {
|
|
144
|
+
return {
|
|
145
|
+
type: "read",
|
|
146
|
+
reasoning: `${persona.name} is seeing this page for the first time and taking a moment to understand what it is.`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
// Check if we're stuck in a loop (same URL visited 3+ times)
|
|
150
|
+
const recentUrls = previousSteps.slice(-5).map((s) => s.page.url);
|
|
151
|
+
const currentUrlCount = recentUrls.filter((u) => u === page.url).length;
|
|
152
|
+
if (currentUrlCount >= 2) {
|
|
153
|
+
return {
|
|
154
|
+
type: "give_up",
|
|
155
|
+
reasoning: `${persona.name} is going in circles and getting frustrated — they've been on this same page multiple times.`,
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
// Score all interactive elements
|
|
159
|
+
const allElements = [...page.buttons, ...page.links, ...page.formFields];
|
|
160
|
+
const scoredElements = allElements
|
|
161
|
+
.filter((el) => el.isVisible)
|
|
162
|
+
.map((el) => ({
|
|
163
|
+
element: el,
|
|
164
|
+
score: scoreElementRelevance(el, persona.goals),
|
|
165
|
+
}))
|
|
166
|
+
.sort((a, b) => b.score - a.score);
|
|
167
|
+
// Apply discovery probability — low-tech users may not find non-obvious elements
|
|
168
|
+
const discoverable = scoredElements.filter((item) => {
|
|
169
|
+
if (item.score > 30)
|
|
170
|
+
return true; // Obvious elements always found
|
|
171
|
+
return Math.random() < discoveryProb;
|
|
172
|
+
});
|
|
173
|
+
// If there are form fields, try to fill them (users expect to fill forms)
|
|
174
|
+
const emptyFormField = page.formFields.find((f) => f.isVisible);
|
|
175
|
+
if (emptyFormField && page.formFields.length <= 6) {
|
|
176
|
+
const fieldType = emptyFormField.type ?? "text";
|
|
177
|
+
const fillValue = generateFieldValue(fieldType, emptyFormField.placeholder);
|
|
178
|
+
return {
|
|
179
|
+
type: "type",
|
|
180
|
+
target: emptyFormField.selector,
|
|
181
|
+
value: fillValue,
|
|
182
|
+
reasoning: `${persona.name} sees a form field (${emptyFormField.placeholder ?? emptyFormField.ariaLabel ?? fieldType}) and fills it in.`,
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
// Click the highest-scored discoverable element
|
|
186
|
+
if (discoverable.length > 0) {
|
|
187
|
+
const bestMatch = discoverable[0];
|
|
188
|
+
const elementDesc = bestMatch.element.text || bestMatch.element.ariaLabel || bestMatch.element.selector;
|
|
189
|
+
return {
|
|
190
|
+
type: "click",
|
|
191
|
+
target: bestMatch.element.selector,
|
|
192
|
+
reasoning: `${persona.name} clicks "${elementDesc}" — it looks like the most relevant action for their goal.`,
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
// Nothing relevant found — scroll to discover more
|
|
196
|
+
if (stepCount < 3) {
|
|
197
|
+
return {
|
|
198
|
+
type: "scroll",
|
|
199
|
+
reasoning: `${persona.name} doesn't see anything obvious to do, so they scroll down to see more content.`,
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
// Give up
|
|
203
|
+
return {
|
|
204
|
+
type: "give_up",
|
|
205
|
+
reasoning: `${persona.name} can't figure out what to do next and gives up. The page doesn't provide clear guidance.`,
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
/**
|
|
209
|
+
* Generate a plausible fill value for a form field type.
|
|
210
|
+
*/
|
|
211
|
+
function generateFieldValue(fieldType, placeholder) {
|
|
212
|
+
const typeValues = {
|
|
213
|
+
email: "testuser@example.com",
|
|
214
|
+
password: "SecurePass123!",
|
|
215
|
+
tel: "555-0123",
|
|
216
|
+
number: "42",
|
|
217
|
+
url: "https://example.com",
|
|
218
|
+
search: "test search",
|
|
219
|
+
text: "Test User",
|
|
220
|
+
};
|
|
221
|
+
if (placeholder) {
|
|
222
|
+
const lower = placeholder.toLowerCase();
|
|
223
|
+
if (lower.includes("name"))
|
|
224
|
+
return "Alex Johnson";
|
|
225
|
+
if (lower.includes("email"))
|
|
226
|
+
return "testuser@example.com";
|
|
227
|
+
if (lower.includes("phone"))
|
|
228
|
+
return "555-0123";
|
|
229
|
+
if (lower.includes("company") || lower.includes("organization"))
|
|
230
|
+
return "Acme Corp";
|
|
231
|
+
if (lower.includes("url") || lower.includes("website"))
|
|
232
|
+
return "https://example.com";
|
|
233
|
+
}
|
|
234
|
+
return typeValues[fieldType] ?? "test input";
|
|
235
|
+
}
|
|
236
|
+
//# sourceMappingURL=action-planner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"action-planner.js","sourceRoot":"","sources":["../../src/walker/action-planner.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import type { Persona, UserSession } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Walk through a web application as a specific persona (legacy heuristic walker).
|
|
4
|
+
* Autonomously navigates, interacts, and records observations.
|
|
5
|
+
* Used by the `auto_walk` tool as a fast fallback.
|
|
6
|
+
*/
|
|
7
|
+
export declare function walkFlow(startUrl: string, persona: Persona, options?: {
|
|
8
|
+
readonly maxSteps?: number;
|
|
9
|
+
}): Promise<UserSession>;
|
|
10
|
+
//# sourceMappingURL=flow-walker.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flow-walker.d.ts","sourceRoot":"","sources":["../../src/walker/flow-walker.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAYxD;;;;GAIG;AACH,wBAAsB,QAAQ,CAC5B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,OAAO,EAChB,OAAO,CAAC,EAAE;IAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE,GACvC,OAAO,CAAC,WAAW,CAAC,CAiEtB"}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { getBrowser } from "../utils/browser.js";
|
|
2
|
+
import { extractPageSnapshot } from "../utils/page-snapshot.js";
|
|
3
|
+
import { executeAction } from "../utils/actions.js";
|
|
4
|
+
import { getMaxSteps, getViewport } from "../personas/engine.js";
|
|
5
|
+
import { planNextAction, assessEmotion, detectFriction } from "./action-planner.js";
|
|
6
|
+
import { SessionRecorder } from "./session-recorder.js";
|
|
7
|
+
const NAVIGATION_TIMEOUT = 30_000;
|
|
8
|
+
const DEFAULT_SCALE_FACTOR = 2;
|
|
9
|
+
/**
|
|
10
|
+
* Walk through a web application as a specific persona (legacy heuristic walker).
|
|
11
|
+
* Autonomously navigates, interacts, and records observations.
|
|
12
|
+
* Used by the `auto_walk` tool as a fast fallback.
|
|
13
|
+
*/
|
|
14
|
+
export async function walkFlow(startUrl, persona, options) {
|
|
15
|
+
const maxSteps = options?.maxSteps ?? getMaxSteps(persona);
|
|
16
|
+
const viewport = getViewport(persona);
|
|
17
|
+
const recorder = new SessionRecorder(persona, startUrl);
|
|
18
|
+
const browser = await getBrowser();
|
|
19
|
+
const page = await browser.newPage();
|
|
20
|
+
await page.setViewport({ width: viewport.width, height: viewport.height, deviceScaleFactor: DEFAULT_SCALE_FACTOR });
|
|
21
|
+
let goalAchieved = false;
|
|
22
|
+
let goalAchievedStep;
|
|
23
|
+
try {
|
|
24
|
+
await page.goto(startUrl, { waitUntil: "networkidle2", timeout: NAVIGATION_TIMEOUT });
|
|
25
|
+
for (let step = 0; step < maxSteps; step++) {
|
|
26
|
+
const stepStart = Date.now();
|
|
27
|
+
const snapshot = await extractPageSnapshot(page);
|
|
28
|
+
const friction = detectFriction(persona, snapshot, step);
|
|
29
|
+
const action = planNextAction(persona, snapshot, recorder.getSteps());
|
|
30
|
+
const emotion = assessEmotion(persona, snapshot, recorder.getSteps(), friction);
|
|
31
|
+
const thought = generateThought(persona, snapshot, action, emotion, step);
|
|
32
|
+
recorder.recordStep({
|
|
33
|
+
page: snapshot,
|
|
34
|
+
action,
|
|
35
|
+
thought,
|
|
36
|
+
emotionalState: emotion,
|
|
37
|
+
frictionPoints: friction,
|
|
38
|
+
timeSpentMs: Date.now() - stepStart,
|
|
39
|
+
});
|
|
40
|
+
if (action.type === "give_up") {
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
const result = await executeAction(page, { type: action.type, target: action.target, value: action.value });
|
|
44
|
+
if (!result.success) {
|
|
45
|
+
const failSnapshot = await extractPageSnapshot(page);
|
|
46
|
+
recorder.recordStep({
|
|
47
|
+
page: failSnapshot,
|
|
48
|
+
action: { type: "give_up", reasoning: `${persona.name}'s intended action failed — this would confuse a real user.` },
|
|
49
|
+
thought: `"That didn't work... I clicked something and nothing happened. This is broken."`,
|
|
50
|
+
emotionalState: "frustrated",
|
|
51
|
+
frictionPoints: [{
|
|
52
|
+
id: `friction-fail-${step}`,
|
|
53
|
+
severity: "high",
|
|
54
|
+
description: `Action failed: tried to ${action.type} on ${action.target ?? "element"} but it didn't respond`,
|
|
55
|
+
location: failSnapshot.url,
|
|
56
|
+
suggestion: "Ensure all interactive elements respond to user interaction and provide visual feedback",
|
|
57
|
+
stepIndex: step + 1,
|
|
58
|
+
}],
|
|
59
|
+
timeSpentMs: Date.now() - stepStart,
|
|
60
|
+
});
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
finally {
|
|
67
|
+
try {
|
|
68
|
+
if (!page.isClosed())
|
|
69
|
+
await page.close();
|
|
70
|
+
}
|
|
71
|
+
catch { /* ignore */ }
|
|
72
|
+
}
|
|
73
|
+
return recorder.finalize(goalAchieved, goalAchievedStep);
|
|
74
|
+
}
|
|
75
|
+
function generateThought(persona, page, action, emotion, stepIndex) {
|
|
76
|
+
if (stepIndex === 0) {
|
|
77
|
+
if (page.headings.length > 0) {
|
|
78
|
+
return `"${page.headings[0]}... okay, let me see what this is about."`;
|
|
79
|
+
}
|
|
80
|
+
return `"Hmm, I'm not immediately sure what this site does. Let me look around."`;
|
|
81
|
+
}
|
|
82
|
+
if (emotion === "frustrated") {
|
|
83
|
+
const reasons = [
|
|
84
|
+
`"I've been trying to figure this out and it's not obvious what I should do next."`,
|
|
85
|
+
`"Why is this so complicated? I just want to ${persona.goals[0] ?? 'get something done'}."`,
|
|
86
|
+
`"I'm about to give up. Nothing here is clicking."`,
|
|
87
|
+
];
|
|
88
|
+
return reasons[stepIndex % reasons.length];
|
|
89
|
+
}
|
|
90
|
+
if (emotion === "confused") {
|
|
91
|
+
if (page.errorMessages.length > 0)
|
|
92
|
+
return `"Something went wrong... '${page.errorMessages[0]}' — what does that mean?"`;
|
|
93
|
+
return `"I'm not sure what I'm supposed to do here. The page doesn't guide me."`;
|
|
94
|
+
}
|
|
95
|
+
if (emotion === "delighted")
|
|
96
|
+
return `"Oh nice, this is exactly what I was looking for!"`;
|
|
97
|
+
if (action.type === "give_up")
|
|
98
|
+
return `"I can't figure this out. A real person would leave at this point."`;
|
|
99
|
+
if (action.type === "click")
|
|
100
|
+
return `"Let me try clicking that — it looks like it might help me ${persona.goals[0] ?? 'move forward'}."`;
|
|
101
|
+
if (action.type === "type")
|
|
102
|
+
return `"Alright, filling in this form field..."`;
|
|
103
|
+
if (action.type === "scroll")
|
|
104
|
+
return `"Let me scroll down to see if there's more content below."`;
|
|
105
|
+
return `"Okay, let me see what's here..."`;
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=flow-walker.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"flow-walker.js","sourceRoot":"","sources":["../../src/walker/flow-walker.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AACjD,OAAO,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAChE,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AACjE,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACpF,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAGxD,MAAM,kBAAkB,GAAG,MAAM,CAAC;AAClC,MAAM,oBAAoB,GAAG,CAAC,CAAC;AAE/B;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,QAAQ,CAC5B,QAAgB,EAChB,OAAgB,EAChB,OAAwC;IAExC,MAAM,QAAQ,GAAG,OAAO,EAAE,QAAQ,IAAI,WAAW,CAAC,OAAO,CAAC,CAAC;IAC3D,MAAM,QAAQ,GAAG,WAAW,CAAC,OAAO,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,IAAI,eAAe,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC;IAExD,MAAM,OAAO,GAAG,MAAM,UAAU,EAAE,CAAC;IACnC,MAAM,IAAI,GAAG,MAAM,OAAO,CAAC,OAAO,EAAE,CAAC;IACrC,MAAM,IAAI,CAAC,WAAW,CAAC,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,CAAC,CAAC;IAEpH,IAAI,YAAY,GAAG,KAAK,CAAC;IACzB,IAAI,gBAAoC,CAAC;IAEzC,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,cAAc,EAAE,OAAO,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAEtF,KAAK,IAAI,IAAI,GAAG,CAAC,EAAE,IAAI,GAAG,QAAQ,EAAE,IAAI,EAAE,EAAE,CAAC;YAC3C,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;YAE7B,MAAM,QAAQ,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC;YACjD,MAAM,QAAQ,GAAG,cAAc,CAAC,OAAO,EAAE,QAAQ,EAAE,IAAI,CAAC,CAAC;YACzD,MAAM,MAAM,GAAG,cAAc,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,CAAC,CAAC;YACtE,MAAM,OAAO,GAAG,aAAa,CAAC,OAAO,EAAE,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,EAAE,QAAQ,CAAC,CAAC;YAChF,MAAM,OAAO,GAAG,eAAe,CAAC,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC,CAAC;YAE1E,QAAQ,CAAC,UAAU,CAAC;gBAClB,IAAI,EAAE,QAAQ;gBACd,MAAM;gBACN,OAAO;gBACP,cAAc,EAAE,OAAO;gBACvB,cAAc,EAAE,QAAQ;gBACxB,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;aACpC,CAAC,CAAC;YAEH,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;gBAC9B,MAAM;YACR,CAAC;YAED,MAAM,MAAM,GAAG,MAAM,aAAa,CAAC,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC;YAC5G,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;gBACpB,MAAM,YAAY,GAAG,MAAM,mBAAmB,CAAC,IAAI,CAAC,CAAC;gBACrD,QAAQ,CAAC,UAAU,CAAC;oBAClB,IAAI,EAAE,YAAY;oBAClB,MAAM,EAAE,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS,EAAE,GAAG,OAAO,CAAC,IAAI,6DAA6D,EAAE;oBACpH,OAAO,EAAE,iFAAiF;oBAC1F,cAAc,EAAE,YAAY;oBAC5B,cAAc,EAAE,CAAC;4BACf,EAAE,EAAE,iBAAiB,IAAI,EAAE;4BAC3B,QAAQ,EAAE,MAAM;4BAChB,WAAW,EAAE,2BAA2B,MAAM,CAAC,IAAI,OAAO,MAAM,CAAC,MAAM,IAAI,SAAS,wBAAwB;4BAC5G,QAAQ,EAAE,YAAY,CAAC,GAAG;4BAC1B,UAAU,EAAE,yFAAyF;4BACrG,SAAS,EAAE,IAAI,GAAG,CAAC;yBACpB,CAAC;oBACF,WAAW,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;iBACpC,CAAC,CAAC;gBACH,MAAM;YACR,CAAC;YAED,MAAM,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,GAAG,CAAC,CAAC,CAAC;QAC3D,CAAC;IACH,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YAAC,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE;gBAAE,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;QAAC,CAAC;QAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;IAC1E,CAAC;IAED,OAAO,QAAQ,CAAC,QAAQ,CAAC,YAAY,EAAE,gBAAgB,CAAC,CAAC;AAC3D,CAAC;AAED,SAAS,eAAe,CACtB,OAAgB,EAChB,IAAkB,EAClB,MAA6D,EAC7D,OAAe,EACf,SAAiB;IAEjB,IAAI,SAAS,KAAK,CAAC,EAAE,CAAC;QACpB,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;YAC7B,OAAO,IAAI,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,2CAA2C,CAAC;QACzE,CAAC;QACD,OAAO,0EAA0E,CAAC;IACpF,CAAC;IACD,IAAI,OAAO,KAAK,YAAY,EAAE,CAAC;QAC7B,MAAM,OAAO,GAAG;YACd,mFAAmF;YACnF,+CAA+C,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,oBAAoB,IAAI;YAC3F,mDAAmD;SACpD,CAAC;QACF,OAAO,OAAO,CAAC,SAAS,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IAC7C,CAAC;IACD,IAAI,OAAO,KAAK,UAAU,EAAE,CAAC;QAC3B,IAAI,IAAI,CAAC,aAAa,CAAC,MAAM,GAAG,CAAC;YAAE,OAAO,6BAA6B,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,2BAA2B,CAAC;QACxH,OAAO,yEAAyE,CAAC;IACnF,CAAC;IACD,IAAI,OAAO,KAAK,WAAW;QAAE,OAAO,oDAAoD,CAAC;IACzF,IAAI,MAAM,CAAC,IAAI,KAAK,SAAS;QAAE,OAAO,qEAAqE,CAAC;IAC5G,IAAI,MAAM,CAAC,IAAI,KAAK,OAAO;QAAE,OAAO,8DAA8D,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,cAAc,IAAI,CAAC;IACzI,IAAI,MAAM,CAAC,IAAI,KAAK,MAAM;QAAE,OAAO,0CAA0C,CAAC;IAC9E,IAAI,MAAM,CAAC,IAAI,KAAK,QAAQ;QAAE,OAAO,4DAA4D,CAAC;IAClG,OAAO,mCAAmC,CAAC;AAC7C,CAAC"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { SessionStep, StepAction, PageSnapshot, FrictionPoint, EmotionalState, UserSession, Persona } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Mutable session builder that accumulates steps during a flow walk.
|
|
4
|
+
* Finalized into an immutable UserSession at the end.
|
|
5
|
+
*/
|
|
6
|
+
export declare class SessionRecorder {
|
|
7
|
+
private readonly persona;
|
|
8
|
+
private readonly startUrl;
|
|
9
|
+
private readonly steps;
|
|
10
|
+
private readonly startedAt;
|
|
11
|
+
constructor(persona: Persona, startUrl: string);
|
|
12
|
+
recordStep(params: {
|
|
13
|
+
readonly page: PageSnapshot;
|
|
14
|
+
readonly action: StepAction;
|
|
15
|
+
readonly thought?: string;
|
|
16
|
+
readonly emotionalState?: EmotionalState;
|
|
17
|
+
readonly frictionPoints?: readonly FrictionPoint[];
|
|
18
|
+
readonly timeSpentMs: number;
|
|
19
|
+
}): SessionStep;
|
|
20
|
+
getSteps(): readonly SessionStep[];
|
|
21
|
+
getStepCount(): number;
|
|
22
|
+
getLastStep(): SessionStep | undefined;
|
|
23
|
+
/**
|
|
24
|
+
* Finalize the session into an immutable UserSession with computed summary.
|
|
25
|
+
*/
|
|
26
|
+
finalize(goalAchieved: boolean, goalAchievedStep?: number): UserSession;
|
|
27
|
+
}
|
|
28
|
+
//# sourceMappingURL=session-recorder.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-recorder.d.ts","sourceRoot":"","sources":["../../src/walker/session-recorder.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,cAAc,EAAkB,WAAW,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAG9I;;;GAGG;AACH,qBAAa,eAAe;IAKxB,OAAO,CAAC,QAAQ,CAAC,OAAO;IACxB,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAL3B,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAqB;IAC3C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;gBAGhB,OAAO,EAAE,OAAO,EAChB,QAAQ,EAAE,MAAM;IAKnC,UAAU,CAAC,MAAM,EAAE;QACjB,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;QAC5B,QAAQ,CAAC,MAAM,EAAE,UAAU,CAAC;QAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;QAC1B,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;QACzC,QAAQ,CAAC,cAAc,CAAC,EAAE,SAAS,aAAa,EAAE,CAAC;QACnD,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;KAC9B,GAAG,WAAW;IAef,QAAQ,IAAI,SAAS,WAAW,EAAE;IAIlC,YAAY,IAAI,MAAM;IAItB,WAAW,IAAI,WAAW,GAAG,SAAS;IAItC;;OAEG;IACH,QAAQ,CAAC,YAAY,EAAE,OAAO,EAAE,gBAAgB,CAAC,EAAE,MAAM,GAAG,WAAW;CAwDxE"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
/**
|
|
3
|
+
* Mutable session builder that accumulates steps during a flow walk.
|
|
4
|
+
* Finalized into an immutable UserSession at the end.
|
|
5
|
+
*/
|
|
6
|
+
export class SessionRecorder {
|
|
7
|
+
persona;
|
|
8
|
+
startUrl;
|
|
9
|
+
steps = [];
|
|
10
|
+
startedAt;
|
|
11
|
+
constructor(persona, startUrl) {
|
|
12
|
+
this.persona = persona;
|
|
13
|
+
this.startUrl = startUrl;
|
|
14
|
+
this.startedAt = new Date().toISOString();
|
|
15
|
+
}
|
|
16
|
+
recordStep(params) {
|
|
17
|
+
const step = {
|
|
18
|
+
index: this.steps.length,
|
|
19
|
+
timestamp: new Date().toISOString(),
|
|
20
|
+
page: params.page,
|
|
21
|
+
action: params.action,
|
|
22
|
+
thought: params.thought ?? "",
|
|
23
|
+
emotionalState: params.emotionalState ?? "neutral",
|
|
24
|
+
frictionPoints: [...(params.frictionPoints ?? [])],
|
|
25
|
+
timeSpentMs: params.timeSpentMs,
|
|
26
|
+
};
|
|
27
|
+
this.steps.push(step);
|
|
28
|
+
return step;
|
|
29
|
+
}
|
|
30
|
+
getSteps() {
|
|
31
|
+
return [...this.steps];
|
|
32
|
+
}
|
|
33
|
+
getStepCount() {
|
|
34
|
+
return this.steps.length;
|
|
35
|
+
}
|
|
36
|
+
getLastStep() {
|
|
37
|
+
return this.steps[this.steps.length - 1];
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Finalize the session into an immutable UserSession with computed summary.
|
|
41
|
+
*/
|
|
42
|
+
finalize(goalAchieved, goalAchievedStep) {
|
|
43
|
+
const completedAt = new Date().toISOString();
|
|
44
|
+
const allFriction = this.steps.flatMap((s) => s.frictionPoints);
|
|
45
|
+
const totalTimeMs = this.steps.reduce((sum, s) => sum + s.timeSpentMs, 0);
|
|
46
|
+
// Friction score: weighted sum of friction points normalized to 0-10
|
|
47
|
+
const frictionWeights = { low: 1, medium: 2, high: 4, critical: 7 };
|
|
48
|
+
const rawFriction = allFriction.reduce((sum, f) => sum + (frictionWeights[f.severity] ?? 1), 0);
|
|
49
|
+
const frictionScore = Math.min(10, rawFriction / Math.max(1, this.steps.length) * 2);
|
|
50
|
+
// Emotional journey
|
|
51
|
+
const emotionalJourney = this.steps.map((s) => s.emotionalState);
|
|
52
|
+
// Drop-off risk analysis
|
|
53
|
+
const highFrictionSteps = this.steps.filter((s) => s.frictionPoints.some((f) => f.severity === "high" || f.severity === "critical"));
|
|
54
|
+
const dropOffRisk = highFrictionSteps.length > 0
|
|
55
|
+
? `High risk at step${highFrictionSteps.length > 1 ? "s" : ""} ${highFrictionSteps.map((s) => s.index).join(", ")} — ${highFrictionSteps[0].frictionPoints[0]?.description ?? "friction detected"}`
|
|
56
|
+
: frictionScore > 5 ? "Moderate risk — cumulative friction may cause abandonment"
|
|
57
|
+
: "Low risk — flow is relatively smooth";
|
|
58
|
+
// Top friction points (sorted by severity)
|
|
59
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3 };
|
|
60
|
+
const topFrictionPoints = [...allFriction]
|
|
61
|
+
.sort((a, b) => (severityOrder[a.severity] ?? 3) - (severityOrder[b.severity] ?? 3))
|
|
62
|
+
.slice(0, 5);
|
|
63
|
+
// Generate recommendations from friction points
|
|
64
|
+
const recommendations = topFrictionPoints.map((f) => f.suggestion);
|
|
65
|
+
if (!goalAchieved) {
|
|
66
|
+
recommendations.push("User could not complete their goal — review the entire flow for blockers");
|
|
67
|
+
}
|
|
68
|
+
const summary = {
|
|
69
|
+
totalSteps: this.steps.length,
|
|
70
|
+
totalTimeMs,
|
|
71
|
+
frictionScore: Math.round(frictionScore * 10) / 10,
|
|
72
|
+
dropOffRisk,
|
|
73
|
+
emotionalJourney,
|
|
74
|
+
topFrictionPoints,
|
|
75
|
+
recommendations,
|
|
76
|
+
goalAchieved,
|
|
77
|
+
goalAchievedStep,
|
|
78
|
+
};
|
|
79
|
+
return {
|
|
80
|
+
id: randomUUID(),
|
|
81
|
+
persona: this.persona,
|
|
82
|
+
startUrl: this.startUrl,
|
|
83
|
+
startedAt: this.startedAt,
|
|
84
|
+
completedAt,
|
|
85
|
+
steps: [...this.steps],
|
|
86
|
+
summary,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=session-recorder.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session-recorder.js","sourceRoot":"","sources":["../../src/walker/session-recorder.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;;GAGG;AACH,MAAM,OAAO,eAAe;IAKP;IACA;IALF,KAAK,GAAkB,EAAE,CAAC;IAC1B,SAAS,CAAS;IAEnC,YACmB,OAAgB,EAChB,QAAgB;QADhB,YAAO,GAAP,OAAO,CAAS;QAChB,aAAQ,GAAR,QAAQ,CAAQ;QAEjC,IAAI,CAAC,SAAS,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC5C,CAAC;IAED,UAAU,CAAC,MAOV;QACC,MAAM,IAAI,GAAgB;YACxB,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM;YACxB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;YACnC,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,MAAM,EAAE,MAAM,CAAC,MAAM;YACrB,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,EAAE;YAC7B,cAAc,EAAE,MAAM,CAAC,cAAc,IAAI,SAAS;YAClD,cAAc,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,cAAc,IAAI,EAAE,CAAC,CAAC;YAClD,WAAW,EAAE,MAAM,CAAC,WAAW;SAChC,CAAC;QACF,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACtB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,QAAQ;QACN,OAAO,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC;IAED,YAAY;QACV,OAAO,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC;IAC3B,CAAC;IAED,WAAW;QACT,OAAO,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;IAC3C,CAAC;IAED;;OAEG;IACH,QAAQ,CAAC,YAAqB,EAAE,gBAAyB;QACvD,MAAM,WAAW,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC7C,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;QAChE,MAAM,WAAW,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;QAE1E,qEAAqE;QACrE,MAAM,eAAe,GAA2B,EAAE,GAAG,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,CAAC;QAC5F,MAAM,WAAW,GAAG,WAAW,CAAC,MAAM,CAAC,CAAC,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAChG,MAAM,aAAa,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,WAAW,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;QAErF,oBAAoB;QACpB,MAAM,gBAAgB,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC;QAEjE,yBAAyB;QACzB,MAAM,iBAAiB,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAChD,CAAC,CAAC,cAAc,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,KAAK,MAAM,IAAI,CAAC,CAAC,QAAQ,KAAK,UAAU,CAAC,CACjF,CAAC;QACF,MAAM,WAAW,GAAG,iBAAiB,CAAC,MAAM,GAAG,CAAC;YAC9C,CAAC,CAAC,oBAAoB,iBAAiB,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,IAAI,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,iBAAiB,CAAC,CAAC,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,EAAE,WAAW,IAAI,mBAAmB,EAAE;YACnM,CAAC,CAAC,aAAa,GAAG,CAAC,CAAC,CAAC,CAAC,2DAA2D;gBACjF,CAAC,CAAC,sCAAsC,CAAC;QAE3C,2CAA2C;QAC3C,MAAM,aAAa,GAA2B,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,EAAE,CAAC;QAC1F,MAAM,iBAAiB,GAAG,CAAC,GAAG,WAAW,CAAC;aACvC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC;aACnF,KAAK,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QAEf,gDAAgD;QAChD,MAAM,eAAe,GAAG,iBAAiB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,CAAC;QACnE,IAAI,CAAC,YAAY,EAAE,CAAC;YAClB,eAAe,CAAC,IAAI,CAAC,0EAA0E,CAAC,CAAC;QACnG,CAAC;QAED,MAAM,OAAO,GAAmB;YAC9B,UAAU,EAAE,IAAI,CAAC,KAAK,CAAC,MAAM;YAC7B,WAAW;YACX,aAAa,EAAE,IAAI,CAAC,KAAK,CAAC,aAAa,GAAG,EAAE,CAAC,GAAG,EAAE;YAClD,WAAW;YACX,gBAAgB;YAChB,iBAAiB;YACjB,eAAe;YACf,YAAY;YACZ,gBAAgB;SACjB,CAAC;QAEF,OAAO;YACL,EAAE,EAAE,UAAU,EAAE;YAChB,OAAO,EAAE,IAAI,CAAC,OAAO;YACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;YACvB,SAAS,EAAE,IAAI,CAAC,SAAS;YACzB,WAAW;YACX,KAAK,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;YACtB,OAAO;SACR,CAAC;IACJ,CAAC;CACF"}
|
package/package.json
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "userflow-mcp",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"mcpName": "io.github.prembobby39-gif/userflow-mcp",
|
|
5
|
+
"description": "UserFlow MCP — Simulates real users navigating your app with different personas and delivers qualitative UX feedback. Free for Pro plan users.",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"main": "dist/index.js",
|
|
8
|
+
"bin": {
|
|
9
|
+
"userflow-mcp": "dist/index.js"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist/**/*",
|
|
13
|
+
"!dist/__tests__/**"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"dev": "tsc --watch",
|
|
18
|
+
"start": "node dist/index.js",
|
|
19
|
+
"prepare": "npm run build",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:watch": "vitest",
|
|
22
|
+
"test:coverage": "vitest run --coverage"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"mcp",
|
|
26
|
+
"model-context-protocol",
|
|
27
|
+
"claude",
|
|
28
|
+
"claude-code",
|
|
29
|
+
"user-testing",
|
|
30
|
+
"ux-feedback",
|
|
31
|
+
"persona-testing",
|
|
32
|
+
"user-flow",
|
|
33
|
+
"user-simulation",
|
|
34
|
+
"usability-testing",
|
|
35
|
+
"puppeteer",
|
|
36
|
+
"developer-tools",
|
|
37
|
+
"ux-review",
|
|
38
|
+
"friction-detection",
|
|
39
|
+
"onboarding-testing",
|
|
40
|
+
"cognitive-load",
|
|
41
|
+
"user-experience",
|
|
42
|
+
"synthetic-users",
|
|
43
|
+
"flow-analysis",
|
|
44
|
+
"userflow"
|
|
45
|
+
],
|
|
46
|
+
"homepage": "https://github.com/prembobby39-gif/userflow-mcp",
|
|
47
|
+
"bugs": {
|
|
48
|
+
"url": "https://github.com/prembobby39-gif/userflow-mcp/issues"
|
|
49
|
+
},
|
|
50
|
+
"repository": {
|
|
51
|
+
"type": "git",
|
|
52
|
+
"url": "https://github.com/prembobby39-gif/userflow-mcp.git"
|
|
53
|
+
},
|
|
54
|
+
"license": "MIT",
|
|
55
|
+
"engines": {
|
|
56
|
+
"node": ">=18.0.0"
|
|
57
|
+
},
|
|
58
|
+
"dependencies": {
|
|
59
|
+
"@modelcontextprotocol/sdk": "^1.28.0",
|
|
60
|
+
"puppeteer-core": "^24.0.0",
|
|
61
|
+
"typescript": "^5.9.3",
|
|
62
|
+
"zod": "^3.24.0"
|
|
63
|
+
},
|
|
64
|
+
"devDependencies": {
|
|
65
|
+
"@types/node": "^22.0.0",
|
|
66
|
+
"@vitest/coverage-v8": "^4.1.1",
|
|
67
|
+
"tsx": "^4.21.0",
|
|
68
|
+
"typescript": "^5.9.3",
|
|
69
|
+
"vitest": "^4.1.1"
|
|
70
|
+
}
|
|
71
|
+
}
|