@recapt/mcp 0.0.5-beta → 0.0.7-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,249 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Recapt Skills CLI
4
+ *
5
+ * Installs recapt workflow skills to .agents/recapt/ and manages AGENTS.md references.
6
+ * Works across all AI IDEs that support the AGENTS.md standard.
7
+ *
8
+ * Usage:
9
+ * npx @recapt/mcp skill list
10
+ * npx @recapt/mcp skill install self-healing
11
+ * npx @recapt/mcp skill install --all
12
+ * npx @recapt/mcp skill uninstall self-healing
13
+ */
14
+ import fs from "fs";
15
+ import path from "path";
16
+ import { fileURLToPath } from "url";
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = path.dirname(__filename);
19
+ const SKILLS_DIR = path.resolve(__dirname, "../../skills");
20
+ const AGENTS_DIR = ".agents/recapt";
21
+ const AGENTS_MD = "AGENTS.md";
22
+ const MARKER_START = "<!-- recapt:skills -->";
23
+ const MARKER_END = "<!-- /recapt:skills -->";
24
+ function getAvailableSkills() {
25
+ if (!fs.existsSync(SKILLS_DIR)) {
26
+ console.error(`Skills directory not found: ${SKILLS_DIR}`);
27
+ process.exit(1);
28
+ }
29
+ const files = fs.readdirSync(SKILLS_DIR).filter((f) => f.endsWith(".md"));
30
+ return files.map((file) => {
31
+ const content = fs.readFileSync(path.join(SKILLS_DIR, file), "utf-8");
32
+ const firstLine = content.split("\n")[0];
33
+ const description = firstLine.startsWith("# ")
34
+ ? firstLine.slice(2).trim()
35
+ : file.replace(".md", "");
36
+ return {
37
+ name: file.replace(".md", ""),
38
+ file,
39
+ description,
40
+ };
41
+ });
42
+ }
43
+ function getInstalledSkills(cwd) {
44
+ const agentsDir = path.join(cwd, AGENTS_DIR);
45
+ if (!fs.existsSync(agentsDir)) {
46
+ return [];
47
+ }
48
+ return fs
49
+ .readdirSync(agentsDir)
50
+ .filter((f) => f.endsWith(".md"))
51
+ .map((f) => f.replace(".md", ""));
52
+ }
53
+ function ensureAgentsDir(cwd) {
54
+ const agentsDir = path.join(cwd, AGENTS_DIR);
55
+ if (!fs.existsSync(agentsDir)) {
56
+ fs.mkdirSync(agentsDir, { recursive: true });
57
+ console.log(`Created ${AGENTS_DIR}/`);
58
+ }
59
+ }
60
+ function readAgentsMd(cwd) {
61
+ const agentsMdPath = path.join(cwd, AGENTS_MD);
62
+ if (!fs.existsSync(agentsMdPath)) {
63
+ return "";
64
+ }
65
+ return fs.readFileSync(agentsMdPath, "utf-8");
66
+ }
67
+ function writeAgentsMd(cwd, content) {
68
+ const agentsMdPath = path.join(cwd, AGENTS_MD);
69
+ fs.writeFileSync(agentsMdPath, content);
70
+ }
71
+ function getSkillDisplayName(skillName) {
72
+ const skills = getAvailableSkills();
73
+ const skill = skills.find((s) => s.name === skillName);
74
+ return skill?.description ?? skillName;
75
+ }
76
+ function updateAgentsMdReferences(cwd) {
77
+ const installed = getInstalledSkills(cwd);
78
+ let content = readAgentsMd(cwd);
79
+ if (installed.length === 0) {
80
+ if (content.includes(MARKER_START)) {
81
+ const regex = new RegExp(`\\n?${MARKER_START}[\\s\\S]*?${MARKER_END}\\n?`, "g");
82
+ content = content.replace(regex, "").trim();
83
+ if (content) {
84
+ writeAgentsMd(cwd, content + "\n");
85
+ }
86
+ else {
87
+ const agentsMdPath = path.join(cwd, AGENTS_MD);
88
+ if (fs.existsSync(agentsMdPath)) {
89
+ fs.unlinkSync(agentsMdPath);
90
+ console.log(`Removed empty ${AGENTS_MD}`);
91
+ }
92
+ }
93
+ }
94
+ return;
95
+ }
96
+ const skillLinks = installed
97
+ .map((name) => `- [${getSkillDisplayName(name)}](${AGENTS_DIR}/${name}.md)`)
98
+ .join("\n");
99
+ const newBlock = `${MARKER_START}
100
+ For recapt behavioral intelligence workflows, see:
101
+ ${skillLinks}
102
+ ${MARKER_END}`;
103
+ if (content.includes(MARKER_START)) {
104
+ const regex = new RegExp(`${MARKER_START}[\\s\\S]*?${MARKER_END}`, "g");
105
+ content = content.replace(regex, newBlock);
106
+ }
107
+ else {
108
+ if (content.trim()) {
109
+ content = content.trim() + "\n\n" + newBlock;
110
+ }
111
+ else {
112
+ content = `# Project Agent Instructions\n\n${newBlock}`;
113
+ }
114
+ }
115
+ writeAgentsMd(cwd, content + "\n");
116
+ }
117
+ function installSkill(cwd, skillName) {
118
+ const skills = getAvailableSkills();
119
+ const skill = skills.find((s) => s.name === skillName);
120
+ if (!skill) {
121
+ console.error(`Unknown skill: ${skillName}`);
122
+ console.error(`Available skills: ${skills.map((s) => s.name).join(", ")}`);
123
+ return false;
124
+ }
125
+ ensureAgentsDir(cwd);
126
+ const sourcePath = path.join(SKILLS_DIR, skill.file);
127
+ const destPath = path.join(cwd, AGENTS_DIR, skill.file);
128
+ if (fs.existsSync(destPath)) {
129
+ console.log(`Skill already installed: ${skillName}`);
130
+ return true;
131
+ }
132
+ fs.copyFileSync(sourcePath, destPath);
133
+ console.log(`Installed: ${skillName} → ${AGENTS_DIR}/${skill.file}`);
134
+ updateAgentsMdReferences(cwd);
135
+ return true;
136
+ }
137
+ function uninstallSkill(cwd, skillName) {
138
+ const skillPath = path.join(cwd, AGENTS_DIR, `${skillName}.md`);
139
+ if (!fs.existsSync(skillPath)) {
140
+ console.error(`Skill not installed: ${skillName}`);
141
+ return false;
142
+ }
143
+ fs.unlinkSync(skillPath);
144
+ console.log(`Uninstalled: ${skillName}`);
145
+ updateAgentsMdReferences(cwd);
146
+ const agentsDir = path.join(cwd, AGENTS_DIR);
147
+ const remaining = fs.readdirSync(agentsDir);
148
+ if (remaining.length === 0) {
149
+ fs.rmdirSync(agentsDir);
150
+ const parentDir = path.dirname(agentsDir);
151
+ if (fs.existsSync(parentDir) &&
152
+ fs.readdirSync(parentDir).length === 0) {
153
+ fs.rmdirSync(parentDir);
154
+ }
155
+ console.log(`Removed empty ${AGENTS_DIR}/`);
156
+ }
157
+ return true;
158
+ }
159
+ function listSkills(cwd) {
160
+ const available = getAvailableSkills();
161
+ const installed = new Set(getInstalledSkills(cwd));
162
+ console.log("\nAvailable recapt skills:\n");
163
+ for (const skill of available) {
164
+ const status = installed.has(skill.name) ? "[installed]" : "";
165
+ console.log(` ${skill.name.padEnd(20)} ${skill.description} ${status}`);
166
+ }
167
+ console.log("\nUsage:");
168
+ console.log(" npx @recapt/mcp skill install <name>");
169
+ console.log(" npx @recapt/mcp skill install --all");
170
+ console.log(" npx @recapt/mcp skill uninstall <name>\n");
171
+ }
172
+ function printHelp() {
173
+ console.log(`
174
+ Recapt Skills CLI
175
+
176
+ Installs workflow skills to .agents/recapt/ for use with any AI IDE.
177
+
178
+ Commands:
179
+ list Show available skills
180
+ install <name> Install a skill
181
+ install --all Install all skills
182
+ uninstall <name> Remove a skill
183
+
184
+ Examples:
185
+ npx @recapt/mcp skill list
186
+ npx @recapt/mcp skill install self-healing
187
+ npx @recapt/mcp skill install --all
188
+ npx @recapt/mcp skill uninstall deep-dive
189
+ `);
190
+ }
191
+ function main() {
192
+ const args = process.argv.slice(2);
193
+ const cwd = process.cwd();
194
+ if (args[0] === "skill") {
195
+ args.shift();
196
+ }
197
+ const command = args[0];
198
+ switch (command) {
199
+ case "list":
200
+ listSkills(cwd);
201
+ break;
202
+ case "install": {
203
+ const target = args[1];
204
+ if (!target) {
205
+ console.error("Usage: npx @recapt/mcp skill install <name|--all>");
206
+ process.exit(1);
207
+ }
208
+ if (target === "--all") {
209
+ const skills = getAvailableSkills();
210
+ let success = true;
211
+ for (const skill of skills) {
212
+ if (!installSkill(cwd, skill.name)) {
213
+ success = false;
214
+ }
215
+ }
216
+ if (success) {
217
+ console.log(`\nAll skills installed. See ${AGENTS_MD} for references.`);
218
+ }
219
+ }
220
+ else {
221
+ if (installSkill(cwd, target)) {
222
+ console.log(`\nSkill reference added to ${AGENTS_MD}`);
223
+ }
224
+ }
225
+ break;
226
+ }
227
+ case "uninstall": {
228
+ const target = args[1];
229
+ if (!target) {
230
+ console.error("Usage: npx @recapt/mcp skill uninstall <name>");
231
+ process.exit(1);
232
+ }
233
+ uninstallSkill(cwd, target);
234
+ break;
235
+ }
236
+ case "help":
237
+ case "--help":
238
+ case "-h":
239
+ printHelp();
240
+ break;
241
+ default:
242
+ if (command) {
243
+ console.error(`Unknown command: ${command}`);
244
+ }
245
+ printHelp();
246
+ process.exit(command ? 1 : 0);
247
+ }
248
+ }
249
+ main();
@@ -0,0 +1,31 @@
1
+ /**
2
+ * IDE Configuration Utilities
3
+ *
4
+ * Handles detection and configuration of MCP servers across different AI IDEs.
5
+ */
6
+ export type ConfigFormat = "json" | "toml";
7
+ export interface IdeConfig {
8
+ name: string;
9
+ globalPath: string | null;
10
+ projectPath: string;
11
+ rootKey: string;
12
+ format: ConfigFormat;
13
+ }
14
+ export declare const IDE_CONFIGS: IdeConfig[];
15
+ export interface DetectedIde {
16
+ config: IdeConfig;
17
+ detected: boolean;
18
+ globalPathResolved: string | null;
19
+ }
20
+ export declare function detectInstalledIdes(): DetectedIde[];
21
+ export interface McpServerConfig {
22
+ type?: string;
23
+ command: string;
24
+ args: string[];
25
+ env: Record<string, string>;
26
+ }
27
+ export declare function readIdeConfig(configPath: string, format?: ConfigFormat): Record<string, unknown>;
28
+ export declare function writeIdeConfig(configPath: string, config: Record<string, unknown>, format?: ConfigFormat): void;
29
+ export declare function addRecaptToIdeConfig(ide: IdeConfig, secretKey: string, useGlobal?: boolean): string;
30
+ export declare function removeRecaptFromIdeConfig(ide: IdeConfig, useGlobal?: boolean): boolean;
31
+ export declare function isRecaptConfigured(ide: IdeConfig): boolean;
@@ -0,0 +1,294 @@
1
+ /**
2
+ * IDE Configuration Utilities
3
+ *
4
+ * Handles detection and configuration of MCP servers across different AI IDEs.
5
+ */
6
+ import fs from "fs";
7
+ import path from "path";
8
+ import os from "os";
9
+ export const IDE_CONFIGS = [
10
+ {
11
+ name: "Cursor",
12
+ globalPath: "~/.cursor/mcp.json",
13
+ projectPath: ".cursor/mcp.json",
14
+ rootKey: "mcpServers",
15
+ format: "json",
16
+ },
17
+ {
18
+ name: "Claude Code",
19
+ globalPath: "~/.claude.json",
20
+ projectPath: ".mcp.json",
21
+ rootKey: "mcpServers",
22
+ format: "json",
23
+ },
24
+ {
25
+ name: "Windsurf",
26
+ globalPath: "~/.windsurf/mcp.json",
27
+ projectPath: ".windsurf/mcp.json",
28
+ rootKey: "mcpServers",
29
+ format: "json",
30
+ },
31
+ {
32
+ name: "VS Code",
33
+ globalPath: null,
34
+ projectPath: ".vscode/mcp.json",
35
+ rootKey: "servers",
36
+ format: "json",
37
+ },
38
+ {
39
+ name: "Codex CLI",
40
+ globalPath: "~/.codex/config.toml",
41
+ projectPath: ".codex/config.toml",
42
+ rootKey: "mcp_servers",
43
+ format: "toml",
44
+ },
45
+ {
46
+ name: "Zed",
47
+ globalPath: "~/.config/zed/settings.json",
48
+ projectPath: ".zed/settings.json",
49
+ rootKey: "context_servers",
50
+ format: "json",
51
+ },
52
+ {
53
+ name: "GitHub Copilot",
54
+ globalPath: null,
55
+ projectPath: ".github/mcp.json",
56
+ rootKey: "servers",
57
+ format: "json",
58
+ },
59
+ ];
60
+ function expandPath(p) {
61
+ if (p.startsWith("~/")) {
62
+ return path.join(os.homedir(), p.slice(2));
63
+ }
64
+ return p;
65
+ }
66
+ function ideExists(config) {
67
+ if (!config.globalPath) {
68
+ return false;
69
+ }
70
+ const resolved = expandPath(config.globalPath);
71
+ const dir = path.dirname(resolved);
72
+ if (config.name === "Claude Code") {
73
+ return (fs.existsSync(resolved) ||
74
+ fs.existsSync(path.join(os.homedir(), ".claude")));
75
+ }
76
+ if (config.name === "Zed") {
77
+ return fs.existsSync(path.join(os.homedir(), ".config", "zed"));
78
+ }
79
+ if (config.name === "Codex CLI") {
80
+ return fs.existsSync(path.join(os.homedir(), ".codex"));
81
+ }
82
+ return fs.existsSync(dir);
83
+ }
84
+ export function detectInstalledIdes() {
85
+ return IDE_CONFIGS.map((config) => ({
86
+ config,
87
+ detected: ideExists(config),
88
+ globalPathResolved: config.globalPath
89
+ ? expandPath(config.globalPath)
90
+ : null,
91
+ }));
92
+ }
93
+ function getRecaptServerConfig(secretKey) {
94
+ return {
95
+ type: "stdio",
96
+ command: "npx",
97
+ args: ["-y", "@recapt/mcp@latest"],
98
+ env: {
99
+ RECAPT_SECRET_KEY: secretKey,
100
+ },
101
+ };
102
+ }
103
+ export function readIdeConfig(configPath, format = "json") {
104
+ const resolved = expandPath(configPath);
105
+ if (!fs.existsSync(resolved)) {
106
+ return {};
107
+ }
108
+ try {
109
+ const content = fs.readFileSync(resolved, "utf-8");
110
+ if (format === "toml") {
111
+ return parseToml(content);
112
+ }
113
+ return JSON.parse(content);
114
+ }
115
+ catch {
116
+ return {};
117
+ }
118
+ }
119
+ function parseToml(content) {
120
+ const result = {};
121
+ let currentSection = "";
122
+ let currentSubSection = "";
123
+ for (const line of content.split("\n")) {
124
+ const trimmed = line.trim();
125
+ if (!trimmed || trimmed.startsWith("#")) {
126
+ continue;
127
+ }
128
+ const sectionMatch = trimmed.match(/^\[([^\]]+)\]$/);
129
+ if (sectionMatch) {
130
+ const section = sectionMatch[1];
131
+ const parts = section.split(".");
132
+ if (parts.length === 1) {
133
+ currentSection = parts[0];
134
+ currentSubSection = "";
135
+ if (!result[currentSection]) {
136
+ result[currentSection] = {};
137
+ }
138
+ }
139
+ else if (parts.length === 2) {
140
+ currentSection = parts[0];
141
+ currentSubSection = parts[1];
142
+ if (!result[currentSection]) {
143
+ result[currentSection] = {};
144
+ }
145
+ const sectionObj = result[currentSection];
146
+ if (!sectionObj[currentSubSection]) {
147
+ sectionObj[currentSubSection] = {};
148
+ }
149
+ }
150
+ continue;
151
+ }
152
+ const kvMatch = trimmed.match(/^(\w+)\s*=\s*(.+)$/);
153
+ if (kvMatch) {
154
+ const [, key, rawValue] = kvMatch;
155
+ const value = parseTomlValue(rawValue);
156
+ if (currentSubSection) {
157
+ const sectionObj = result[currentSection];
158
+ const subSectionObj = sectionObj[currentSubSection];
159
+ subSectionObj[key] = value;
160
+ }
161
+ else if (currentSection) {
162
+ const sectionObj = result[currentSection];
163
+ sectionObj[key] = value;
164
+ }
165
+ else {
166
+ result[key] = value;
167
+ }
168
+ }
169
+ }
170
+ return result;
171
+ }
172
+ function parseTomlValue(raw) {
173
+ const trimmed = raw.trim();
174
+ if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
175
+ return trimmed.slice(1, -1);
176
+ }
177
+ if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
178
+ return trimmed.slice(1, -1);
179
+ }
180
+ if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
181
+ const inner = trimmed.slice(1, -1);
182
+ if (!inner.trim())
183
+ return [];
184
+ return inner.split(",").map((item) => {
185
+ const t = item.trim();
186
+ if ((t.startsWith('"') && t.endsWith('"')) ||
187
+ (t.startsWith("'") && t.endsWith("'"))) {
188
+ return t.slice(1, -1);
189
+ }
190
+ return t;
191
+ });
192
+ }
193
+ if (trimmed === "true")
194
+ return true;
195
+ if (trimmed === "false")
196
+ return false;
197
+ const num = Number(trimmed);
198
+ if (!isNaN(num))
199
+ return num;
200
+ return trimmed;
201
+ }
202
+ function stringifyToml(obj, prefix = "") {
203
+ const lines = [];
204
+ for (const [key, value] of Object.entries(obj)) {
205
+ if (value === null || value === undefined)
206
+ continue;
207
+ if (typeof value === "object" && !Array.isArray(value)) {
208
+ const sectionKey = prefix ? `${prefix}.${key}` : key;
209
+ const nested = value;
210
+ const hasNestedObjects = Object.values(nested).some((v) => typeof v === "object" && !Array.isArray(v));
211
+ if (hasNestedObjects) {
212
+ for (const [subKey, subValue] of Object.entries(nested)) {
213
+ if (typeof subValue === "object" && !Array.isArray(subValue)) {
214
+ lines.push(`[${sectionKey}.${subKey}]`);
215
+ for (const [k, v] of Object.entries(subValue)) {
216
+ lines.push(`${k} = ${tomlValue(v)}`);
217
+ }
218
+ lines.push("");
219
+ }
220
+ }
221
+ }
222
+ else {
223
+ lines.push(`[${sectionKey}]`);
224
+ for (const [k, v] of Object.entries(nested)) {
225
+ lines.push(`${k} = ${tomlValue(v)}`);
226
+ }
227
+ lines.push("");
228
+ }
229
+ }
230
+ }
231
+ return lines.join("\n");
232
+ }
233
+ function tomlValue(value) {
234
+ if (typeof value === "string") {
235
+ return `"${value}"`;
236
+ }
237
+ if (Array.isArray(value)) {
238
+ return `[${value.map((v) => (typeof v === "string" ? `"${v}"` : String(v))).join(", ")}]`;
239
+ }
240
+ return String(value);
241
+ }
242
+ export function writeIdeConfig(configPath, config, format = "json") {
243
+ const resolved = expandPath(configPath);
244
+ const dir = path.dirname(resolved);
245
+ if (!fs.existsSync(dir)) {
246
+ fs.mkdirSync(dir, { recursive: true });
247
+ }
248
+ if (format === "toml") {
249
+ fs.writeFileSync(resolved, stringifyToml(config));
250
+ }
251
+ else {
252
+ fs.writeFileSync(resolved, JSON.stringify(config, null, 2) + "\n");
253
+ }
254
+ }
255
+ export function addRecaptToIdeConfig(ide, secretKey, useGlobal = true) {
256
+ const configPath = useGlobal && ide.globalPath ? ide.globalPath : ide.projectPath;
257
+ const resolved = expandPath(configPath);
258
+ const existing = readIdeConfig(configPath, ide.format);
259
+ const rootKey = ide.rootKey;
260
+ const servers = existing[rootKey] || {};
261
+ servers["recapt"] = getRecaptServerConfig(secretKey);
262
+ existing[rootKey] = servers;
263
+ writeIdeConfig(configPath, existing, ide.format);
264
+ return resolved;
265
+ }
266
+ export function removeRecaptFromIdeConfig(ide, useGlobal = true) {
267
+ const configPath = useGlobal && ide.globalPath ? ide.globalPath : ide.projectPath;
268
+ const resolved = expandPath(configPath);
269
+ if (!fs.existsSync(resolved)) {
270
+ return false;
271
+ }
272
+ const existing = readIdeConfig(configPath, ide.format);
273
+ const rootKey = ide.rootKey;
274
+ const servers = existing[rootKey] || {};
275
+ if (!("recapt" in servers)) {
276
+ return false;
277
+ }
278
+ delete servers["recapt"];
279
+ existing[rootKey] = servers;
280
+ writeIdeConfig(configPath, existing, ide.format);
281
+ return true;
282
+ }
283
+ export function isRecaptConfigured(ide) {
284
+ if (ide.globalPath) {
285
+ const globalConfig = readIdeConfig(ide.globalPath, ide.format);
286
+ const servers = globalConfig[ide.rootKey] || {};
287
+ if ("recapt" in servers) {
288
+ return true;
289
+ }
290
+ }
291
+ const projectConfig = readIdeConfig(ide.projectPath, ide.format);
292
+ const servers = projectConfig[ide.rootKey] || {};
293
+ return "recapt" in servers;
294
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Interactive Prompts
3
+ *
4
+ * Readline-based prompts for CLI interactions.
5
+ */
6
+ export declare function confirm(message: string, defaultYes?: boolean): Promise<boolean>;
7
+ export declare function input(message: string, defaultValue?: string): Promise<string>;
8
+ export declare function secret(message: string): Promise<string>;
9
+ export interface SelectOption<T = string> {
10
+ label: string;
11
+ value: T;
12
+ selected?: boolean;
13
+ }
14
+ export declare function multiSelect<T = string>(message: string, options: SelectOption<T>[]): Promise<T[]>;
15
+ export declare function select<T = string>(message: string, options: SelectOption<T>[]): Promise<T | null>;
16
+ export declare function print(message: string): void;
17
+ export declare function success(message: string): void;
18
+ export declare function error(message: string): void;
19
+ export declare function info(message: string): void;
20
+ export declare function warn(message: string): void;
21
+ export declare function newline(): void;
22
+ export declare function header(title: string): void;