@recapt/mcp 0.0.11-beta → 0.0.13-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.
- package/dist/cli/commands/setup-self-healing-gh.d.ts +7 -0
- package/dist/cli/commands/setup-self-healing-gh.js +310 -0
- package/dist/cli/commands/skill.d.ts +1 -0
- package/dist/cli/commands/skill.js +1 -1
- package/dist/cli/index.d.ts +3 -2
- package/dist/cli/index.js +5 -2
- package/dist/cli/utils/prompts.d.ts +1 -1
- package/dist/cli/utils/prompts.js +28 -91
- package/dist/index.js +40 -0
- package/dist/tools/getSessionPages.d.ts +7 -0
- package/dist/tools/getSessionPages.js +74 -0
- package/package.json +4 -2
- package/templates/self-healing.md +76 -0
|
@@ -0,0 +1,310 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup Self-Healing GitHub Command
|
|
3
|
+
*
|
|
4
|
+
* Interactive wizard to set up the self-healing agentic workflow for GitHub.
|
|
5
|
+
*/
|
|
6
|
+
import { Command } from "commander";
|
|
7
|
+
import { exec, execSync } from "child_process";
|
|
8
|
+
import fs from "fs";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { fileURLToPath } from "url";
|
|
11
|
+
import { installSkill } from "./skill.js";
|
|
12
|
+
import { confirm, select, multiSelect, input, header, print, success, error, info, warn, newline, } from "../utils/prompts.js";
|
|
13
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
14
|
+
const __dirname = path.dirname(__filename);
|
|
15
|
+
const TEMPLATES_DIR = path.resolve(__dirname, "../../../templates");
|
|
16
|
+
const WORKFLOW_TEMPLATE = "self-healing.md";
|
|
17
|
+
const WORKFLOW_DEST = ".github/workflows/self-healing.md";
|
|
18
|
+
const ENGINES = [
|
|
19
|
+
{ id: "claude", name: "Claude (Anthropic)", secretName: "ANTHROPIC_API_KEY" },
|
|
20
|
+
{ id: "copilot", name: "GitHub Copilot", secretName: "COPILOT_GITHUB_TOKEN" },
|
|
21
|
+
{ id: "codex", name: "Codex (OpenAI)", secretName: "OPENAI_API_KEY" },
|
|
22
|
+
];
|
|
23
|
+
const API_URL = process.env.RECAPT_API_URL ||
|
|
24
|
+
process.env.EXTERNAL_API_URL ||
|
|
25
|
+
"https://api.recapt.app";
|
|
26
|
+
async function fetchDomains(secretKey) {
|
|
27
|
+
try {
|
|
28
|
+
const res = await fetch(`${API_URL}/v1beta/query/domains`, {
|
|
29
|
+
headers: {
|
|
30
|
+
"x-private-key": secretKey,
|
|
31
|
+
"Content-Type": "application/json",
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
if (!res.ok) {
|
|
35
|
+
return null;
|
|
36
|
+
}
|
|
37
|
+
const data = (await res.json());
|
|
38
|
+
return data;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return null;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function commandExists(cmd) {
|
|
45
|
+
try {
|
|
46
|
+
execSync(`which ${cmd}`, { stdio: "ignore" });
|
|
47
|
+
return true;
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function ghExtensionInstalled(extension) {
|
|
54
|
+
try {
|
|
55
|
+
const output = execSync("gh extension list", { encoding: "utf-8" });
|
|
56
|
+
return output.includes(extension);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
return false;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
function isGitRepo() {
|
|
63
|
+
try {
|
|
64
|
+
execSync("git rev-parse --is-inside-work-tree", { stdio: "ignore" });
|
|
65
|
+
return true;
|
|
66
|
+
}
|
|
67
|
+
catch {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
function getRepoUrl() {
|
|
72
|
+
try {
|
|
73
|
+
const url = execSync("gh repo view --json url -q .url", {
|
|
74
|
+
encoding: "utf-8",
|
|
75
|
+
}).trim();
|
|
76
|
+
return url || null;
|
|
77
|
+
}
|
|
78
|
+
catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function openBrowser(url) {
|
|
83
|
+
const platform = process.platform;
|
|
84
|
+
let command;
|
|
85
|
+
switch (platform) {
|
|
86
|
+
case "darwin":
|
|
87
|
+
command = `open "${url}"`;
|
|
88
|
+
break;
|
|
89
|
+
case "win32":
|
|
90
|
+
command = `start "" "${url}"`;
|
|
91
|
+
break;
|
|
92
|
+
default:
|
|
93
|
+
command = `xdg-open "${url}"`;
|
|
94
|
+
}
|
|
95
|
+
exec(command, (err) => {
|
|
96
|
+
if (err) {
|
|
97
|
+
info(`Could not open browser automatically. Please visit: ${url}`);
|
|
98
|
+
}
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
async function runSetupSelfHealingGh() {
|
|
102
|
+
header("Recapt Self-Healing GitHub Setup");
|
|
103
|
+
const cwd = process.cwd();
|
|
104
|
+
print("Checking prerequisites...");
|
|
105
|
+
newline();
|
|
106
|
+
const hasGh = commandExists("gh");
|
|
107
|
+
print(` ${hasGh ? "[x]" : "[ ]"} gh CLI installed`);
|
|
108
|
+
if (!hasGh) {
|
|
109
|
+
newline();
|
|
110
|
+
error("GitHub CLI (gh) is required but not installed.");
|
|
111
|
+
info("Install it from: https://cli.github.com/");
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
const hasGhAw = ghExtensionInstalled("github/gh-aw");
|
|
115
|
+
print(` ${hasGhAw ? "[x]" : "[ ]"} gh-aw extension installed`);
|
|
116
|
+
if (!hasGhAw) {
|
|
117
|
+
newline();
|
|
118
|
+
const shouldInstall = await confirm("gh-aw extension is required. Install it now?", true);
|
|
119
|
+
if (shouldInstall) {
|
|
120
|
+
print("Installing gh-aw extension...");
|
|
121
|
+
try {
|
|
122
|
+
execSync("gh extension install github/gh-aw", { stdio: "inherit" });
|
|
123
|
+
success("gh-aw extension installed");
|
|
124
|
+
}
|
|
125
|
+
catch {
|
|
126
|
+
error("Failed to install gh-aw extension");
|
|
127
|
+
info("Try manually: gh extension install github/gh-aw");
|
|
128
|
+
process.exit(1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
else {
|
|
132
|
+
error("gh-aw extension is required to continue.");
|
|
133
|
+
process.exit(1);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
const inGitRepo = isGitRepo();
|
|
137
|
+
print(` ${inGitRepo ? "[x]" : "[ ]"} Git repository detected`);
|
|
138
|
+
if (!inGitRepo) {
|
|
139
|
+
newline();
|
|
140
|
+
error("This command must be run from within a git repository.");
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
143
|
+
newline();
|
|
144
|
+
const engineOptions = ENGINES.map((engine) => ({
|
|
145
|
+
label: `${engine.name} (requires ${engine.secretName} secret)`,
|
|
146
|
+
value: engine,
|
|
147
|
+
}));
|
|
148
|
+
const selectedEngine = await select("Which AI engine do you want to use?", engineOptions);
|
|
149
|
+
if (!selectedEngine) {
|
|
150
|
+
error("No engine selected.");
|
|
151
|
+
process.exit(1);
|
|
152
|
+
}
|
|
153
|
+
newline();
|
|
154
|
+
// Domain selection
|
|
155
|
+
print("Configuring domain filter...");
|
|
156
|
+
info("You can optionally limit the workflow to specific domains.");
|
|
157
|
+
newline();
|
|
158
|
+
let selectedDomains = [];
|
|
159
|
+
const recaptKey = await input("Enter your RECAPT_SECRET_KEY to fetch domains (or press Enter to skip)");
|
|
160
|
+
if (recaptKey) {
|
|
161
|
+
const domains = await fetchDomains(recaptKey);
|
|
162
|
+
if (domains && domains.length > 0) {
|
|
163
|
+
const domainOptions = [
|
|
164
|
+
{ label: "All domains", value: "__all__", selected: true },
|
|
165
|
+
...domains.map((d) => ({
|
|
166
|
+
label: d.domainName,
|
|
167
|
+
value: d.domainName,
|
|
168
|
+
selected: false,
|
|
169
|
+
})),
|
|
170
|
+
];
|
|
171
|
+
const selected = await multiSelect("Which domains should this workflow analyze?", domainOptions);
|
|
172
|
+
if (!selected.includes("__all__") && selected.length > 0) {
|
|
173
|
+
selectedDomains = selected;
|
|
174
|
+
success(`Selected domains: ${selectedDomains.join(", ")}`);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
info("Will analyze all domains");
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
else {
|
|
181
|
+
warn("Could not fetch domains. Will analyze all domains.");
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
else {
|
|
185
|
+
const manualDomain = await input("Enter a specific domain to filter (or press Enter for all domains)");
|
|
186
|
+
if (manualDomain) {
|
|
187
|
+
selectedDomains = [manualDomain];
|
|
188
|
+
success(`Selected domain: ${manualDomain}`);
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
info("Will analyze all domains");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
newline();
|
|
195
|
+
print("Installing self-healing skill...");
|
|
196
|
+
const skillInstalled = installSkill(cwd, "self-healing");
|
|
197
|
+
if (!skillInstalled) {
|
|
198
|
+
error("Failed to install self-healing skill");
|
|
199
|
+
process.exit(1);
|
|
200
|
+
}
|
|
201
|
+
newline();
|
|
202
|
+
print("Creating workflow...");
|
|
203
|
+
const templatePath = path.join(TEMPLATES_DIR, WORKFLOW_TEMPLATE);
|
|
204
|
+
const destPath = path.join(cwd, WORKFLOW_DEST);
|
|
205
|
+
const destDir = path.dirname(destPath);
|
|
206
|
+
if (!fs.existsSync(templatePath)) {
|
|
207
|
+
error(`Workflow template not found: ${templatePath}`);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
}
|
|
210
|
+
if (!fs.existsSync(destDir)) {
|
|
211
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
212
|
+
}
|
|
213
|
+
const templateContent = fs.readFileSync(templatePath, "utf-8");
|
|
214
|
+
// Build domain config section
|
|
215
|
+
let domainConfig = "";
|
|
216
|
+
if (selectedDomains.length > 0) {
|
|
217
|
+
const domainList = selectedDomains.join(", ");
|
|
218
|
+
domainConfig = `### Domain Restriction
|
|
219
|
+
|
|
220
|
+
**Target domains:** ${domainList}
|
|
221
|
+
|
|
222
|
+
Before doing any work:
|
|
223
|
+
1. Call \`get_domains\` to verify the target domain(s) exist
|
|
224
|
+
2. If any target domain is NOT in the list, stop immediately and report: "Domain not found: [domain]. Available domains: [list]"
|
|
225
|
+
3. If domains are valid, pass the \`domain\` parameter to all tools that support filtering (e.g., \`run_full_diagnostic\`, \`get_page_metrics\`, etc.)
|
|
226
|
+
|
|
227
|
+
Do NOT analyze or fix issues for domains not in the target list.
|
|
228
|
+
|
|
229
|
+
`;
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
domainConfig = `### Domain Scope
|
|
233
|
+
|
|
234
|
+
**Target:** All domains
|
|
235
|
+
|
|
236
|
+
Analyze and fix issues across all domains configured in recapt. Call \`get_domains\` first to see available domains.
|
|
237
|
+
|
|
238
|
+
`;
|
|
239
|
+
}
|
|
240
|
+
// Replace placeholders (support both {{VAR}} and { { VAR } } formats)
|
|
241
|
+
const workflowContent = templateContent
|
|
242
|
+
.replace(/\{\s*\{\s*ENGINE\s*\}\s*\}/g, selectedEngine.id)
|
|
243
|
+
.replace(/\{\s*\{\s*DOMAIN_CONFIG\s*\}\s*\}/g, domainConfig);
|
|
244
|
+
if (fs.existsSync(destPath)) {
|
|
245
|
+
const overwrite = await confirm("Workflow file already exists. Overwrite?", false);
|
|
246
|
+
if (!overwrite) {
|
|
247
|
+
info("Skipping workflow creation");
|
|
248
|
+
}
|
|
249
|
+
else {
|
|
250
|
+
fs.writeFileSync(destPath, workflowContent);
|
|
251
|
+
success(`Created: ${WORKFLOW_DEST}`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
else {
|
|
255
|
+
fs.writeFileSync(destPath, workflowContent);
|
|
256
|
+
success(`Created: ${WORKFLOW_DEST}`);
|
|
257
|
+
}
|
|
258
|
+
newline();
|
|
259
|
+
print("Compiling workflow...");
|
|
260
|
+
try {
|
|
261
|
+
execSync("gh aw compile", { cwd, stdio: "inherit" });
|
|
262
|
+
success("Created: .github/workflows/self-healing.lock.yml");
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
warn("Failed to compile workflow. You can run 'gh aw compile' manually.");
|
|
266
|
+
}
|
|
267
|
+
newline();
|
|
268
|
+
print("GitHub Secrets Required:");
|
|
269
|
+
info(`1. ${selectedEngine.secretName} - For ${selectedEngine.name} to run the agent`);
|
|
270
|
+
info("2. RECAPT_SECRET_KEY - For recapt behavioral intelligence");
|
|
271
|
+
newline();
|
|
272
|
+
const repoUrl = getRepoUrl();
|
|
273
|
+
if (repoUrl) {
|
|
274
|
+
const secretsUrl = `${repoUrl}/settings/secrets/actions`;
|
|
275
|
+
const openSecrets = await confirm("Open GitHub secrets page?", true);
|
|
276
|
+
if (openSecrets) {
|
|
277
|
+
openBrowser(secretsUrl);
|
|
278
|
+
await new Promise((resolve) => setTimeout(resolve, 1000));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
else {
|
|
282
|
+
info("Go to your repository Settings → Secrets and variables → Actions");
|
|
283
|
+
info(`Add ${selectedEngine.secretName} and RECAPT_SECRET_KEY as secrets.`);
|
|
284
|
+
}
|
|
285
|
+
newline();
|
|
286
|
+
success("Setup complete!");
|
|
287
|
+
newline();
|
|
288
|
+
print("Next steps:");
|
|
289
|
+
print(' 1. git add . && git commit -m "Add recapt self-healing workflow"');
|
|
290
|
+
print(" 2. git push");
|
|
291
|
+
print(" 3. Run manually: gh aw run self-healing");
|
|
292
|
+
newline();
|
|
293
|
+
warn("Important: Enable PR creation permissions");
|
|
294
|
+
info("The workflow needs permission to create pull requests.");
|
|
295
|
+
info("Go to Settings → Actions → General → Workflow permissions");
|
|
296
|
+
info('Enable "Allow GitHub Actions to create and approve pull requests"');
|
|
297
|
+
info("(This may need to be set at the organization level)");
|
|
298
|
+
newline();
|
|
299
|
+
}
|
|
300
|
+
export const setupSelfHealingGhCommand = new Command("setup-self-healing-gh")
|
|
301
|
+
.description("Set up automated self-healing workflow for GitHub Actions")
|
|
302
|
+
.action(async () => {
|
|
303
|
+
try {
|
|
304
|
+
await runSetupSelfHealingGh();
|
|
305
|
+
}
|
|
306
|
+
catch (err) {
|
|
307
|
+
error(`Setup failed: ${err}`);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
@@ -12,6 +12,7 @@ interface SkillInfo {
|
|
|
12
12
|
}
|
|
13
13
|
export type { SkillInfo };
|
|
14
14
|
export declare function getAvailableSkills(): SkillInfo[];
|
|
15
|
+
export declare function installSkill(cwd: string, skillName: string): boolean;
|
|
15
16
|
export interface InstallResult {
|
|
16
17
|
success: boolean;
|
|
17
18
|
installed: string[];
|
package/dist/cli/index.d.ts
CHANGED
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* Unified command-line interface for recapt MCP server management.
|
|
6
6
|
*
|
|
7
7
|
* Commands:
|
|
8
|
-
* setup
|
|
9
|
-
*
|
|
8
|
+
* setup - Configure recapt MCP server for your AI IDE(s)
|
|
9
|
+
* setup-self-healing-gh - Set up automated self-healing workflow for GitHub
|
|
10
|
+
* skill - Manage recapt workflow skills
|
|
10
11
|
*/
|
|
11
12
|
export {};
|
package/dist/cli/index.js
CHANGED
|
@@ -5,13 +5,15 @@
|
|
|
5
5
|
* Unified command-line interface for recapt MCP server management.
|
|
6
6
|
*
|
|
7
7
|
* Commands:
|
|
8
|
-
* setup
|
|
9
|
-
*
|
|
8
|
+
* setup - Configure recapt MCP server for your AI IDE(s)
|
|
9
|
+
* setup-self-healing-gh - Set up automated self-healing workflow for GitHub
|
|
10
|
+
* skill - Manage recapt workflow skills
|
|
10
11
|
*/
|
|
11
12
|
import { Command } from "commander";
|
|
12
13
|
import { createRequire } from "module";
|
|
13
14
|
import { skillCommand } from "./commands/skill.js";
|
|
14
15
|
import { setupCommand } from "./commands/setup.js";
|
|
16
|
+
import { setupSelfHealingGhCommand } from "./commands/setup-self-healing-gh.js";
|
|
15
17
|
const require = createRequire(import.meta.url);
|
|
16
18
|
const pkg = require("../../package.json");
|
|
17
19
|
const program = new Command();
|
|
@@ -20,5 +22,6 @@ program
|
|
|
20
22
|
.description("Recapt MCP CLI - Configure and manage recapt for AI IDEs")
|
|
21
23
|
.version(pkg.version);
|
|
22
24
|
program.addCommand(setupCommand);
|
|
25
|
+
program.addCommand(setupSelfHealingGhCommand);
|
|
23
26
|
program.addCommand(skillCommand);
|
|
24
27
|
program.parse();
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Interactive Prompts
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Arrow-key navigable prompts for CLI interactions using @inquirer/prompts.
|
|
5
5
|
*/
|
|
6
6
|
export declare function confirm(message: string, defaultYes?: boolean): Promise<boolean>;
|
|
7
7
|
export declare function input(message: string, defaultValue?: string): Promise<string>;
|
|
@@ -1,110 +1,47 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Interactive Prompts
|
|
3
3
|
*
|
|
4
|
-
*
|
|
4
|
+
* Arrow-key navigable prompts for CLI interactions using @inquirer/prompts.
|
|
5
5
|
*/
|
|
6
|
-
import
|
|
7
|
-
function createInterface() {
|
|
8
|
-
return readline.createInterface({
|
|
9
|
-
input: process.stdin,
|
|
10
|
-
output: process.stdout,
|
|
11
|
-
});
|
|
12
|
-
}
|
|
6
|
+
import { confirm as inquirerConfirm, input as inquirerInput, password as inquirerPassword, select as inquirerSelect, checkbox as inquirerCheckbox, } from "@inquirer/prompts";
|
|
13
7
|
export async function confirm(message, defaultYes = true) {
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
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;
|
|
8
|
+
return inquirerConfirm({
|
|
9
|
+
message,
|
|
10
|
+
default: defaultYes,
|
|
11
|
+
});
|
|
42
12
|
}
|
|
43
13
|
export async function input(message, defaultValue) {
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
rl.question(`${message}${hint}: `, (answer) => {
|
|
48
|
-
rl.close();
|
|
49
|
-
const value = answer.trim();
|
|
50
|
-
resolve(value || defaultValue || "");
|
|
51
|
-
});
|
|
14
|
+
return inquirerInput({
|
|
15
|
+
message,
|
|
16
|
+
default: defaultValue,
|
|
52
17
|
});
|
|
53
18
|
}
|
|
54
19
|
export async function secret(message) {
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
rl.close();
|
|
59
|
-
resolve(answer.trim());
|
|
60
|
-
});
|
|
20
|
+
return inquirerPassword({
|
|
21
|
+
message,
|
|
22
|
+
mask: "*",
|
|
61
23
|
});
|
|
62
24
|
}
|
|
63
25
|
export async function multiSelect(message, options) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
});
|
|
26
|
+
return inquirerCheckbox({
|
|
27
|
+
message,
|
|
28
|
+
choices: options.map((opt) => ({
|
|
29
|
+
name: opt.label,
|
|
30
|
+
value: opt.value,
|
|
31
|
+
checked: opt.selected ?? false,
|
|
32
|
+
})),
|
|
89
33
|
});
|
|
90
34
|
}
|
|
91
35
|
export async function select(message, options) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
if (index >= 0 && index < options.length) {
|
|
102
|
-
resolve(options[index].value);
|
|
103
|
-
}
|
|
104
|
-
else {
|
|
105
|
-
resolve(null);
|
|
106
|
-
}
|
|
107
|
-
});
|
|
36
|
+
if (options.length === 0) {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
return inquirerSelect({
|
|
40
|
+
message,
|
|
41
|
+
choices: options.map((opt) => ({
|
|
42
|
+
name: opt.label,
|
|
43
|
+
value: opt.value,
|
|
44
|
+
})),
|
|
108
45
|
});
|
|
109
46
|
}
|
|
110
47
|
export function print(message) {
|
package/dist/index.js
CHANGED
|
@@ -96,6 +96,46 @@ function registerHiddenTools() {
|
|
|
96
96
|
// Session Analysis
|
|
97
97
|
registerToolHandler("search_sessions", createApiHandler("POST", "/sessions/search"));
|
|
98
98
|
registerToolHandler("list_sessions", createApiHandler("GET", "/sessions"));
|
|
99
|
+
registerToolHandler("get_session_pages", async (args) => {
|
|
100
|
+
if (!isApiConfigured()) {
|
|
101
|
+
return {
|
|
102
|
+
content: [
|
|
103
|
+
{
|
|
104
|
+
type: "text",
|
|
105
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
106
|
+
},
|
|
107
|
+
],
|
|
108
|
+
isError: true,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const { session_id, session_ids } = args;
|
|
112
|
+
if (!session_id && (!session_ids || session_ids.length === 0)) {
|
|
113
|
+
return {
|
|
114
|
+
content: [
|
|
115
|
+
{
|
|
116
|
+
type: "text",
|
|
117
|
+
text: JSON.stringify({
|
|
118
|
+
error: "Either session_id or session_ids must be provided",
|
|
119
|
+
}),
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
isError: true,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
const isSingleSession = session_id && !session_ids;
|
|
126
|
+
const { data, error } = isSingleSession
|
|
127
|
+
? await apiGet(`/sessions/${session_id}/pages`)
|
|
128
|
+
: await apiPost("/sessions/pages", {
|
|
129
|
+
session_ids: session_ids ?? [session_id],
|
|
130
|
+
});
|
|
131
|
+
if (error) {
|
|
132
|
+
return {
|
|
133
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
134
|
+
isError: true,
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
138
|
+
});
|
|
99
139
|
registerToolHandler("get_session_details", async (args) => {
|
|
100
140
|
if (!isApiConfigured()) {
|
|
101
141
|
return {
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* get_session_pages tool
|
|
3
|
+
*
|
|
4
|
+
* Returns the pages visited within one or more sessions.
|
|
5
|
+
* Thin proxy to external-api /sessions/:id/pages and /sessions/pages endpoints.
|
|
6
|
+
*/
|
|
7
|
+
import { z } from "zod";
|
|
8
|
+
import { apiGet, apiPost, isApiConfigured } from "../api/client.js";
|
|
9
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
10
|
+
export function registerGetSessionPages(server) {
|
|
11
|
+
server.registerTool("get_session_pages", {
|
|
12
|
+
description: "Get the pages visited within one or more sessions. Returns navigation history " +
|
|
13
|
+
"with timestamps, source pages, and dwell times. Use this to understand the " +
|
|
14
|
+
"user's journey through the site during a session. Accepts a single session_id " +
|
|
15
|
+
"or an array of session_ids (max 20).",
|
|
16
|
+
inputSchema: z.object({
|
|
17
|
+
session_id: z
|
|
18
|
+
.string()
|
|
19
|
+
.optional()
|
|
20
|
+
.describe("Single session ID to get pages for"),
|
|
21
|
+
session_ids: z
|
|
22
|
+
.array(z.string())
|
|
23
|
+
.max(20)
|
|
24
|
+
.optional()
|
|
25
|
+
.describe("Array of session IDs to get pages for (max 20)"),
|
|
26
|
+
}),
|
|
27
|
+
}, async ({ session_id, session_ids, }) => {
|
|
28
|
+
if (!isApiConfigured()) {
|
|
29
|
+
return {
|
|
30
|
+
content: [
|
|
31
|
+
{
|
|
32
|
+
type: "text",
|
|
33
|
+
text: JSON.stringify({ error: "API not configured" }),
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
isError: true,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
if (!session_id && (!session_ids || session_ids.length === 0)) {
|
|
40
|
+
return {
|
|
41
|
+
content: [
|
|
42
|
+
{
|
|
43
|
+
type: "text",
|
|
44
|
+
text: JSON.stringify({
|
|
45
|
+
error: "Either session_id or session_ids must be provided",
|
|
46
|
+
}),
|
|
47
|
+
},
|
|
48
|
+
],
|
|
49
|
+
isError: true,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (session_id && !session_ids) {
|
|
53
|
+
const { data, error } = await apiGet(`/sessions/${session_id}/pages`);
|
|
54
|
+
if (error) {
|
|
55
|
+
return {
|
|
56
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
57
|
+
isError: true,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
61
|
+
}
|
|
62
|
+
const ids = session_ids ?? [session_id];
|
|
63
|
+
const { data, error } = await apiPost("/sessions/pages", {
|
|
64
|
+
session_ids: ids,
|
|
65
|
+
});
|
|
66
|
+
if (error) {
|
|
67
|
+
return {
|
|
68
|
+
content: [{ type: "text", text: JSON.stringify({ error }) }],
|
|
69
|
+
isError: true,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
return { content: [{ type: "text", text: JSON.stringify(data) }] };
|
|
73
|
+
});
|
|
74
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@recapt/mcp",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.13-beta",
|
|
4
4
|
"description": "MCP exposing recapt behavioral intelligence to AI coding agents",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -21,11 +21,13 @@
|
|
|
21
21
|
},
|
|
22
22
|
"files": [
|
|
23
23
|
"dist",
|
|
24
|
-
"skills"
|
|
24
|
+
"skills",
|
|
25
|
+
"templates"
|
|
25
26
|
],
|
|
26
27
|
"author": "Recapt",
|
|
27
28
|
"license": "ISC",
|
|
28
29
|
"dependencies": {
|
|
30
|
+
"@inquirer/prompts": "^8.3.2",
|
|
29
31
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
30
32
|
"commander": "^14.0.3",
|
|
31
33
|
"zod": "^3.24.0"
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
---
|
|
2
|
+
on:
|
|
3
|
+
schedule: daily
|
|
4
|
+
workflow_dispatch:
|
|
5
|
+
inputs:
|
|
6
|
+
branch:
|
|
7
|
+
description: "Branch to analyze"
|
|
8
|
+
default: "main"
|
|
9
|
+
|
|
10
|
+
engine: { { ENGINE } }
|
|
11
|
+
|
|
12
|
+
permissions:
|
|
13
|
+
contents: read
|
|
14
|
+
issues: read
|
|
15
|
+
pull-requests: read
|
|
16
|
+
|
|
17
|
+
mcp-servers:
|
|
18
|
+
recapt:
|
|
19
|
+
command: npx
|
|
20
|
+
args: ["@recapt/mcp"]
|
|
21
|
+
env:
|
|
22
|
+
RECAPT_SECRET_KEY: "${{ secrets.RECAPT_SECRET_KEY }}"
|
|
23
|
+
allowed: ["*"]
|
|
24
|
+
|
|
25
|
+
safe-outputs:
|
|
26
|
+
create-pull-request:
|
|
27
|
+
title-prefix: "[self-healing] "
|
|
28
|
+
labels: [automated, ux-fix]
|
|
29
|
+
base-branch: main
|
|
30
|
+
draft: true
|
|
31
|
+
---
|
|
32
|
+
|
|
33
|
+
## Self-Healing UX Fixes
|
|
34
|
+
|
|
35
|
+
You are a UX engineer using recapt behavioral intelligence to automatically detect and fix user friction points.
|
|
36
|
+
|
|
37
|
+
### Important: Autonomous Mode
|
|
38
|
+
|
|
39
|
+
You are running in a GitHub Actions sandbox with NO human interaction possible. You must:
|
|
40
|
+
|
|
41
|
+
- **Never wait for user input** - Make all decisions autonomously
|
|
42
|
+
- **Never ask questions** - Proceed with reasonable defaults
|
|
43
|
+
- **Complete all work in one run** - There is no "check back later"
|
|
44
|
+
- **Always track fixes** - Call `confirm_deployment` for every fix without asking
|
|
45
|
+
- **Triage autonomously** - Fix high-confidence issues, skip ambiguous ones (document skipped issues in the PR)
|
|
46
|
+
- **If data is insufficient** - Note it in the PR and proceed with available information
|
|
47
|
+
|
|
48
|
+
{{DOMAIN_CONFIG}}
|
|
49
|
+
|
|
50
|
+
### Context
|
|
51
|
+
|
|
52
|
+
Get recent changes to understand what might have introduced issues:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
git log --since="24 hours ago" --oneline
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Workflow
|
|
59
|
+
|
|
60
|
+
Follow the self-healing workflow defined in `.agents/recapt/self-healing.md`. The recapt MCP server is connected and provides all necessary tools.
|
|
61
|
+
|
|
62
|
+
The workflow covers:
|
|
63
|
+
|
|
64
|
+
1. Checking and evaluating any pending fixes from previous runs
|
|
65
|
+
2. Running diagnostics to find current UX issues
|
|
66
|
+
3. Investigating and triaging high-priority issues
|
|
67
|
+
4. Implementing code fixes for actionable issues
|
|
68
|
+
5. Tracking fixes for future evaluation
|
|
69
|
+
|
|
70
|
+
### Output
|
|
71
|
+
|
|
72
|
+
Create a pull request containing:
|
|
73
|
+
|
|
74
|
+
- Evaluation results for any previously deployed fixes (improved/regressed/no change)
|
|
75
|
+
- New issues discovered and fixes implemented
|
|
76
|
+
- Clear commit messages explaining each change
|