@recapt/mcp 0.0.5-beta → 0.0.6-beta

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.
@@ -0,0 +1,133 @@
1
+ /**
2
+ * Interactive Prompts
3
+ *
4
+ * Readline-based prompts for CLI interactions.
5
+ */
6
+ import readline from "readline";
7
+ function createInterface() {
8
+ return readline.createInterface({
9
+ input: process.stdin,
10
+ output: process.stdout,
11
+ });
12
+ }
13
+ export async function confirm(message, defaultYes = true) {
14
+ const hint = defaultYes ? "(Y/n)" : "(y/N)";
15
+ const askOnce = () => {
16
+ const rl = createInterface();
17
+ return new Promise((resolve) => {
18
+ rl.question(`${message} ${hint} `, (answer) => {
19
+ rl.close();
20
+ const normalized = answer.trim().toLowerCase();
21
+ if (normalized === "") {
22
+ resolve(defaultYes);
23
+ }
24
+ else if (normalized === "y" || normalized === "yes") {
25
+ resolve(true);
26
+ }
27
+ else if (normalized === "n" || normalized === "no") {
28
+ resolve(false);
29
+ }
30
+ else {
31
+ resolve(null);
32
+ }
33
+ });
34
+ });
35
+ };
36
+ let result = await askOnce();
37
+ while (result === null) {
38
+ console.log('Please enter "y" or "n"');
39
+ result = await askOnce();
40
+ }
41
+ return result;
42
+ }
43
+ export async function input(message, defaultValue) {
44
+ const rl = createInterface();
45
+ const hint = defaultValue ? ` (${defaultValue})` : "";
46
+ return new Promise((resolve) => {
47
+ rl.question(`${message}${hint}: `, (answer) => {
48
+ rl.close();
49
+ const value = answer.trim();
50
+ resolve(value || defaultValue || "");
51
+ });
52
+ });
53
+ }
54
+ export async function secret(message) {
55
+ const rl = createInterface();
56
+ return new Promise((resolve) => {
57
+ rl.question(`${message}: `, (answer) => {
58
+ rl.close();
59
+ resolve(answer.trim());
60
+ });
61
+ });
62
+ }
63
+ export async function multiSelect(message, options) {
64
+ const rl = createInterface();
65
+ console.log(`\n${message}`);
66
+ console.log("(Enter comma-separated numbers, or 'all' for all options)\n");
67
+ options.forEach((opt, i) => {
68
+ const marker = opt.selected ? "[x]" : "[ ]";
69
+ console.log(` ${i + 1}. ${marker} ${opt.label}`);
70
+ });
71
+ return new Promise((resolve) => {
72
+ rl.question("\nYour selection: ", (answer) => {
73
+ rl.close();
74
+ const normalized = answer.trim().toLowerCase();
75
+ if (normalized === "all" || normalized === "a") {
76
+ resolve(options.map((o) => o.value));
77
+ return;
78
+ }
79
+ if (normalized === "" || normalized === "none" || normalized === "n") {
80
+ resolve(options.filter((o) => o.selected).map((o) => o.value));
81
+ return;
82
+ }
83
+ const indices = normalized
84
+ .split(/[,\s]+/)
85
+ .map((s) => parseInt(s, 10) - 1)
86
+ .filter((i) => i >= 0 && i < options.length);
87
+ resolve(indices.map((i) => options[i].value));
88
+ });
89
+ });
90
+ }
91
+ export async function select(message, options) {
92
+ const rl = createInterface();
93
+ console.log(`\n${message}\n`);
94
+ options.forEach((opt, i) => {
95
+ console.log(` ${i + 1}. ${opt.label}`);
96
+ });
97
+ return new Promise((resolve) => {
98
+ rl.question("\nYour selection: ", (answer) => {
99
+ rl.close();
100
+ const index = parseInt(answer.trim(), 10) - 1;
101
+ if (index >= 0 && index < options.length) {
102
+ resolve(options[index].value);
103
+ }
104
+ else {
105
+ resolve(null);
106
+ }
107
+ });
108
+ });
109
+ }
110
+ export function print(message) {
111
+ console.log(message);
112
+ }
113
+ export function success(message) {
114
+ console.log(`✓ ${message}`);
115
+ }
116
+ export function error(message) {
117
+ console.error(`✗ ${message}`);
118
+ }
119
+ export function info(message) {
120
+ console.log(`ℹ ${message}`);
121
+ }
122
+ export function warn(message) {
123
+ console.log(`⚠ ${message}`);
124
+ }
125
+ export function newline() {
126
+ console.log();
127
+ }
128
+ export function header(title) {
129
+ console.log();
130
+ console.log(title);
131
+ console.log("=".repeat(title.length));
132
+ console.log();
133
+ }
package/dist/index.js CHANGED
@@ -223,29 +223,6 @@ All behavioral scores are 0-1 where higher = more of that signal:
223
223
  - **drop_off_rate** > 0.3: Major leak point in a flow
224
224
  - **spike_ratio** > 2: Critical frustration spike, likely recent regression
225
225
 
226
- ## Common Workflows
227
-
228
- ### Starting point - understand the landscape
229
- 1. \`get_domains\` to see tracked domains
230
- 2. \`run_full_diagnostic\` for comprehensive overview
231
- 3. Or search: "site overview" → scan_site, list_pages, get_ux_health_report
232
-
233
- ### Diagnosing a problematic page
234
- Search: "page analysis" or "element friction" → get_page_metrics, get_element_friction, get_dead_clicks, get_console_errors, get_form_friction
235
-
236
- ### Understanding user flows
237
- Search: "user navigation" or "funnel" → analyze_flow, analyze_funnel, get_journey_patterns, get_flow_friction
238
-
239
- ### Finding issues to fix
240
- 1. \`run_full_diagnostic\` (always available)
241
- 2. Or search: "issues" → get_issues, get_actionable_issues, get_anomalies, detect_regressions
242
-
243
- ### Comparing segments
244
- Search: "compare users" or "cohorts" → compare_cohorts, compare_periods, discover_personas
245
-
246
- ### Deep-diving a session
247
- Search: "session details" → search_sessions, list_sessions, get_session_details, predict_outcomes
248
-
249
226
  ## Reasoning Tips
250
227
 
251
228
  - High rage clicks on body/root elements often indicate JS errors - search "console errors"
@@ -254,17 +231,14 @@ Search: "session details" → search_sessions, list_sessions, get_session_detail
254
231
  - High frustration + low confusion = users know what to do but can't (broken UI)
255
232
  - Use \`memory_save\` to store intermediate results when doing multi-step analysis
256
233
 
257
- ## Self-Healing Workflow
234
+ ## Installable Skills
258
235
 
259
- When asked to "fix all issues" or "heal the site":
260
-
261
- 1. **Diagnose**: \`run_full_diagnostic\` (always available)
262
- 2. **Investigate**: search "investigate issue" → investigate_issue, validate_issue
263
- 3. **Triage**: search "dismiss issue" → dismiss_issue, mark_intended_behavior
264
- (you should wait here for user input, recommend continuing the workflow if the user is happy)
265
- 4. **Fix**: search "propose fix" → propose_fix, get_similar_fixes, get_fix_history
266
- 5. **Track**: search "deployment" → confirm_deployment, evaluate_fix, list_pending_fixes
267
- 6. **Learn**: search "site knowledge" → get_site_knowledge, add_site_knowledge`;
236
+ For guided workflows (self-healing, deep-dive analysis, regression hunting), install recapt skills:
237
+ \`\`\`bash
238
+ npx @recapt/mcp skill list
239
+ npx @recapt/mcp skill install self-healing
240
+ \`\`\`
241
+ Skills provide step-by-step guidance for complex multi-tool workflows.`;
268
242
  // ─────────────────────────────────────────────────────────────────────────────
269
243
  // Main
270
244
  // ─────────────────────────────────────────────────────────────────────────────
@@ -38,20 +38,65 @@ function cosineSimilarity(a, b) {
38
38
  }
39
39
  function searchByKeyword(query, limit) {
40
40
  const catalog = loadCatalog();
41
- const queryWords = query
42
- .toLowerCase()
43
- .split(/\s+/)
44
- .filter((w) => w.length > 2);
41
+ const normalizedQuery = query.toLowerCase();
42
+ // Extract words, keeping short ones that might be meaningful (e.g., "ux", "js")
43
+ const queryWords = normalizedQuery
44
+ .split(/[\s_-]+/)
45
+ .filter((w) => w.length >= 2);
46
+ // Also check for the full query as a phrase
47
+ const queryPhrases = [normalizedQuery];
48
+ // Common synonyms and related terms
49
+ const synonyms = {
50
+ error: ["console", "js", "javascript", "bug", "crash", "exception"],
51
+ page: ["pages", "route", "url", "path"],
52
+ user: ["users", "session", "visitor"],
53
+ click: ["clicks", "tap", "press", "rage"],
54
+ form: ["forms", "input", "field", "submit"],
55
+ flow: ["flows", "journey", "funnel", "navigation", "path"],
56
+ issue: ["issues", "problem", "bug", "friction"],
57
+ fix: ["fixes", "remediation", "repair", "resolve"],
58
+ compare: ["comparison", "diff", "versus", "cohort"],
59
+ health: ["score", "metrics", "ux"],
60
+ dead: ["unresponsive", "broken", "stuck"],
61
+ rage: ["angry", "frustrated", "frustration"],
62
+ };
63
+ // Expand query words with synonyms
64
+ const expandedWords = new Set(queryWords);
65
+ for (const word of queryWords) {
66
+ if (synonyms[word]) {
67
+ synonyms[word].forEach((syn) => expandedWords.add(syn));
68
+ }
69
+ // Also check if query word is a synonym value
70
+ for (const [key, values] of Object.entries(synonyms)) {
71
+ if (values.includes(word)) {
72
+ expandedWords.add(key);
73
+ }
74
+ }
75
+ }
45
76
  return catalog
46
77
  .map((tool) => {
47
- const text = `${tool.name} ${tool.description} ${tool.category}`.toLowerCase();
78
+ const toolName = tool.name.toLowerCase().replace(/_/g, " ");
79
+ const toolNameParts = tool.name.toLowerCase().split("_");
80
+ const text = `${toolName} ${tool.description} ${tool.category}`.toLowerCase();
48
81
  let score = 0;
49
- for (const word of queryWords) {
50
- if (text.includes(word))
51
- score += 1;
52
- if (tool.name.toLowerCase().includes(word))
53
- score += 2;
82
+ // Phrase match in description (highest value)
83
+ for (const phrase of queryPhrases) {
84
+ if (phrase.length > 3 && text.includes(phrase))
85
+ score += 5;
86
+ }
87
+ // Word matches
88
+ for (const word of expandedWords) {
89
+ // Exact word in tool name parts (e.g., "page" matches "get_page_metrics")
90
+ if (toolNameParts.includes(word))
91
+ score += 4;
92
+ // Word appears in tool name
93
+ if (toolName.includes(word))
94
+ score += 3;
95
+ // Category match
54
96
  if (tool.category.toLowerCase() === word)
97
+ score += 2;
98
+ // Word in description
99
+ if (text.includes(word))
55
100
  score += 1;
56
101
  }
57
102
  return { tool, score };