revxl-devtools 1.0.2 → 1.0.3
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/README.md +2 -2
- package/dist/auth.js +7 -6
- package/dist/index.js +1 -1
- package/docs/index.html +495 -0
- package/landing/index.html +495 -0
- package/package.json +1 -1
- package/dist/auth.d.ts +0 -3
- package/dist/codegen/cron-codegen.d.ts +0 -1
- package/dist/codegen/regex-codegen.d.ts +0 -1
- package/dist/index.d.ts +0 -22
- package/dist/registry.d.ts +0 -10
- package/dist/tools/base64.d.ts +0 -1
- package/dist/tools/batch.d.ts +0 -1
- package/dist/tools/chmod.d.ts +0 -1
- package/dist/tools/cron.d.ts +0 -1
- package/dist/tools/hash.d.ts +0 -1
- package/dist/tools/http-status.d.ts +0 -1
- package/dist/tools/json-diff.d.ts +0 -1
- package/dist/tools/json-format.d.ts +0 -1
- package/dist/tools/json-query.d.ts +0 -1
- package/dist/tools/jwt.d.ts +0 -1
- package/dist/tools/regex.d.ts +0 -1
- package/dist/tools/secrets-scan.d.ts +0 -1
- package/dist/tools/sql-format.d.ts +0 -1
- package/dist/tools/timestamp.d.ts +0 -1
- package/dist/tools/url-encode.d.ts +0 -1
- package/dist/tools/uuid.d.ts +0 -1
- package/dist/tools/yaml-convert.d.ts +0 -1
- package/src/auth.ts +0 -99
- package/src/codegen/cron-codegen.ts +0 -66
- package/src/codegen/regex-codegen.ts +0 -132
- package/src/index.ts +0 -134
- package/src/registry.ts +0 -25
- package/src/tools/base64.ts +0 -32
- package/src/tools/batch.ts +0 -69
- package/src/tools/chmod.ts +0 -133
- package/src/tools/cron.ts +0 -365
- package/src/tools/hash.ts +0 -26
- package/src/tools/http-status.ts +0 -63
- package/src/tools/json-diff.ts +0 -153
- package/src/tools/json-format.ts +0 -43
- package/src/tools/json-query.ts +0 -126
- package/src/tools/jwt.ts +0 -193
- package/src/tools/regex.ts +0 -131
- package/src/tools/secrets-scan.ts +0 -212
- package/src/tools/sql-format.ts +0 -178
- package/src/tools/timestamp.ts +0 -74
- package/src/tools/url-encode.ts +0 -29
- package/src/tools/uuid.ts +0 -25
- package/src/tools/yaml-convert.ts +0 -383
- package/tsconfig.json +0 -14
package/src/auth.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
-
import { homedir } from "node:os";
|
|
3
|
-
import { join } from "node:path";
|
|
4
|
-
|
|
5
|
-
const CACHE_PATH = join(homedir(), ".revxl-devtools-cache.json");
|
|
6
|
-
const CACHE_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
7
|
-
const MAX_TRIAL_USES = 3;
|
|
8
|
-
|
|
9
|
-
interface Cache {
|
|
10
|
-
proValidated: boolean;
|
|
11
|
-
validatedAt: number;
|
|
12
|
-
trialUses: Record<string, number>;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function loadCache(): Cache {
|
|
16
|
-
try {
|
|
17
|
-
const raw = readFileSync(CACHE_PATH, "utf-8");
|
|
18
|
-
return JSON.parse(raw) as Cache;
|
|
19
|
-
} catch {
|
|
20
|
-
return { proValidated: false, validatedAt: 0, trialUses: {} };
|
|
21
|
-
}
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
function saveCache(cache: Cache): void {
|
|
25
|
-
try {
|
|
26
|
-
writeFileSync(CACHE_PATH, JSON.stringify(cache, null, 2), "utf-8");
|
|
27
|
-
} catch {
|
|
28
|
-
// Silently fail — cache is best-effort
|
|
29
|
-
}
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
async function validateKeyWithSupabase(key: string): Promise<boolean> {
|
|
33
|
-
const supabaseUrl = process.env.SUPABASE_URL;
|
|
34
|
-
const supabaseAnonKey = process.env.SUPABASE_ANON_KEY;
|
|
35
|
-
|
|
36
|
-
if (!supabaseUrl || !supabaseAnonKey) {
|
|
37
|
-
// Dev mode: accept any RX-prefixed key
|
|
38
|
-
return key.startsWith("RX.");
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
try {
|
|
42
|
-
const res = await fetch(
|
|
43
|
-
`${supabaseUrl}/rest/v1/pro_keys?key=eq.${encodeURIComponent(key)}&select=active`,
|
|
44
|
-
{
|
|
45
|
-
headers: {
|
|
46
|
-
apikey: supabaseAnonKey,
|
|
47
|
-
Authorization: `Bearer ${supabaseAnonKey}`,
|
|
48
|
-
},
|
|
49
|
-
}
|
|
50
|
-
);
|
|
51
|
-
|
|
52
|
-
if (!res.ok) {
|
|
53
|
-
// Supabase down — fall back to format check
|
|
54
|
-
return key.startsWith("RX.");
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const rows = (await res.json()) as Array<{ active: boolean }>;
|
|
58
|
-
return rows.length > 0 && rows[0].active === true;
|
|
59
|
-
} catch {
|
|
60
|
-
// Network error — fall back to format check
|
|
61
|
-
return key.startsWith("RX.");
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
export async function checkProAccess(): Promise<boolean> {
|
|
66
|
-
const key = process.env.MCP_DEVTOOLS_KEY;
|
|
67
|
-
if (!key || !key.startsWith("RX.")) {
|
|
68
|
-
return false;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const cache = loadCache();
|
|
72
|
-
|
|
73
|
-
// Check cache validity
|
|
74
|
-
const cacheAge = Date.now() - cache.validatedAt;
|
|
75
|
-
if (cache.proValidated && cacheAge < CACHE_TTL_MS) {
|
|
76
|
-
return true;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// Validate against Supabase (or dev mode fallback)
|
|
80
|
-
const valid = await validateKeyWithSupabase(key);
|
|
81
|
-
|
|
82
|
-
cache.proValidated = valid;
|
|
83
|
-
cache.validatedAt = Date.now();
|
|
84
|
-
saveCache(cache);
|
|
85
|
-
|
|
86
|
-
return valid;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
export function getTrialUsesRemaining(toolName: string): number {
|
|
90
|
-
const cache = loadCache();
|
|
91
|
-
const used = cache.trialUses[toolName] ?? 0;
|
|
92
|
-
return Math.max(0, MAX_TRIAL_USES - used);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
export function incrementTrialUse(toolName: string): void {
|
|
96
|
-
const cache = loadCache();
|
|
97
|
-
cache.trialUses[toolName] = (cache.trialUses[toolName] ?? 0) + 1;
|
|
98
|
-
saveCache(cache);
|
|
99
|
-
}
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Cron code generation — crontab, systemd timer, and node-cron snippets
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
|
|
5
|
-
export function generateCronCode(expression: string): string {
|
|
6
|
-
const crontab = `# Crontab entry
|
|
7
|
-
${expression} /path/to/command`;
|
|
8
|
-
|
|
9
|
-
const systemd = generateSystemdTimer(expression);
|
|
10
|
-
const nodeCron = generateNodeCron(expression);
|
|
11
|
-
|
|
12
|
-
return [crontab, systemd, nodeCron].join("\n\n---\n\n");
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
function cronToOnCalendar(expression: string): string {
|
|
16
|
-
const parts = expression.split(/\s+/);
|
|
17
|
-
if (parts.length !== 5) return expression;
|
|
18
|
-
|
|
19
|
-
const [minute, hour, dayOfMonth, month, dayOfWeek] = parts;
|
|
20
|
-
|
|
21
|
-
const dayMap: Record<string, string> = {
|
|
22
|
-
"0": "Sun", "1": "Mon", "2": "Tue", "3": "Wed",
|
|
23
|
-
"4": "Thu", "5": "Fri", "6": "Sat", "7": "Sun",
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
let dow = "*";
|
|
27
|
-
if (dayOfWeek !== "*") {
|
|
28
|
-
dow = dayOfWeek
|
|
29
|
-
.split(",")
|
|
30
|
-
.map((d) => dayMap[d] || d)
|
|
31
|
-
.join(",");
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const dom = dayOfMonth === "*" ? "*" : dayOfMonth;
|
|
35
|
-
const mon = month === "*" ? "*" : month.padStart(2, "0");
|
|
36
|
-
const h = hour === "*" ? "*" : hour.padStart(2, "0");
|
|
37
|
-
const m = minute === "*" ? "*" : minute.padStart(2, "0");
|
|
38
|
-
|
|
39
|
-
return `${dow} *-${mon}-${dom} ${h}:${m}:00`;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function generateSystemdTimer(expression: string): string {
|
|
43
|
-
const onCalendar = cronToOnCalendar(expression);
|
|
44
|
-
return `# systemd timer unit — save as /etc/systemd/system/mytask.timer
|
|
45
|
-
[Unit]
|
|
46
|
-
Description=My scheduled task
|
|
47
|
-
|
|
48
|
-
[Timer]
|
|
49
|
-
OnCalendar=${onCalendar}
|
|
50
|
-
Persistent=true
|
|
51
|
-
|
|
52
|
-
[Install]
|
|
53
|
-
WantedBy=timers.target`;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
function generateNodeCron(expression: string): string {
|
|
57
|
-
return `// Node.js — npm install cron
|
|
58
|
-
import { CronJob } from 'cron';
|
|
59
|
-
|
|
60
|
-
const job = new CronJob('${expression}', () => {
|
|
61
|
-
console.log('Task executed at', new Date().toISOString());
|
|
62
|
-
// your logic here
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
job.start();`;
|
|
66
|
-
}
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Regex code generation — produces working snippets in 5 languages
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
|
|
5
|
-
type Language = "javascript" | "python" | "go" | "rust" | "java";
|
|
6
|
-
|
|
7
|
-
const ALL_LANGUAGES: Language[] = ["javascript", "python", "go", "rust", "java"];
|
|
8
|
-
|
|
9
|
-
function jsFlags(flags: string): string {
|
|
10
|
-
return flags || "";
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
function pyFlags(flags: string): string {
|
|
14
|
-
const parts: string[] = [];
|
|
15
|
-
if (flags.includes("i")) parts.push("re.IGNORECASE");
|
|
16
|
-
if (flags.includes("m")) parts.push("re.MULTILINE");
|
|
17
|
-
if (flags.includes("s")) parts.push("re.DOTALL");
|
|
18
|
-
return parts.length ? ", " + parts.join(" | ") : "";
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function generateJS(pattern: string, flags: string): string {
|
|
22
|
-
const f = jsFlags(flags);
|
|
23
|
-
return `// JavaScript
|
|
24
|
-
const regex = /${pattern}/${f};
|
|
25
|
-
const text = "your text here";
|
|
26
|
-
|
|
27
|
-
const matches = text.match(regex);
|
|
28
|
-
if (matches) {
|
|
29
|
-
console.log("Matches:", matches);
|
|
30
|
-
} else {
|
|
31
|
-
console.log("No matches found");
|
|
32
|
-
}`;
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function generatePython(pattern: string, flags: string): string {
|
|
36
|
-
const f = pyFlags(flags);
|
|
37
|
-
return `# Python
|
|
38
|
-
import re
|
|
39
|
-
|
|
40
|
-
pattern = re.compile(r'${pattern}'${f})
|
|
41
|
-
text = "your text here"
|
|
42
|
-
|
|
43
|
-
matches = pattern.findall(text)
|
|
44
|
-
print("Matches:", matches)`;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function generateGo(pattern: string, flags: string): string {
|
|
48
|
-
// Go uses inline flags (?i) etc.
|
|
49
|
-
let goFlags = "";
|
|
50
|
-
if (flags.includes("i")) goFlags += "i";
|
|
51
|
-
if (flags.includes("m")) goFlags += "m";
|
|
52
|
-
if (flags.includes("s")) goFlags += "s";
|
|
53
|
-
const prefix = goFlags ? `(?${goFlags})` : "";
|
|
54
|
-
return `// Go
|
|
55
|
-
package main
|
|
56
|
-
|
|
57
|
-
import (
|
|
58
|
-
\t"fmt"
|
|
59
|
-
\t"regexp"
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
func main() {
|
|
63
|
-
\tre := regexp.MustCompile(\`${prefix}${pattern}\`)
|
|
64
|
-
\ttext := "your text here"
|
|
65
|
-
|
|
66
|
-
\tmatches := re.FindAllString(text, -1)
|
|
67
|
-
\tfmt.Println("Matches:", matches)
|
|
68
|
-
}`;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function generateRust(pattern: string, flags: string): string {
|
|
72
|
-
let rustFlags = "";
|
|
73
|
-
if (flags.includes("i")) rustFlags += "(?i)";
|
|
74
|
-
if (flags.includes("m")) rustFlags += "(?m)";
|
|
75
|
-
if (flags.includes("s")) rustFlags += "(?s)";
|
|
76
|
-
return `// Rust — add \`regex = "1"\` to Cargo.toml [dependencies]
|
|
77
|
-
use regex::Regex;
|
|
78
|
-
|
|
79
|
-
fn main() {
|
|
80
|
-
let re = Regex::new(r"${rustFlags}${pattern}").unwrap();
|
|
81
|
-
let text = "your text here";
|
|
82
|
-
|
|
83
|
-
for m in re.find_iter(text) {
|
|
84
|
-
println!("Match: {}", m.as_str());
|
|
85
|
-
}
|
|
86
|
-
}`;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function generateJava(pattern: string, flags: string): string {
|
|
90
|
-
const javaFlags: string[] = [];
|
|
91
|
-
if (flags.includes("i")) javaFlags.push("Pattern.CASE_INSENSITIVE");
|
|
92
|
-
if (flags.includes("m")) javaFlags.push("Pattern.MULTILINE");
|
|
93
|
-
if (flags.includes("s")) javaFlags.push("Pattern.DOTALL");
|
|
94
|
-
const flagArg = javaFlags.length ? ", " + javaFlags.join(" | ") : "";
|
|
95
|
-
return `// Java
|
|
96
|
-
import java.util.regex.Pattern;
|
|
97
|
-
import java.util.regex.Matcher;
|
|
98
|
-
|
|
99
|
-
public class RegexDemo {
|
|
100
|
-
public static void main(String[] args) {
|
|
101
|
-
Pattern pattern = Pattern.compile("${pattern}"${flagArg});
|
|
102
|
-
String text = "your text here";
|
|
103
|
-
Matcher matcher = pattern.matcher(text);
|
|
104
|
-
|
|
105
|
-
while (matcher.find()) {
|
|
106
|
-
System.out.println("Match: " + matcher.group());
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}`;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const generators: Record<Language, (pattern: string, flags: string) => string> = {
|
|
113
|
-
javascript: generateJS,
|
|
114
|
-
python: generatePython,
|
|
115
|
-
go: generateGo,
|
|
116
|
-
rust: generateRust,
|
|
117
|
-
java: generateJava,
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
export function generateRegexCode(
|
|
121
|
-
pattern: string,
|
|
122
|
-
flags: string,
|
|
123
|
-
languages?: string[],
|
|
124
|
-
): string {
|
|
125
|
-
const langs: Language[] = languages && languages.length
|
|
126
|
-
? (languages.map((l) => l.toLowerCase()) as Language[]).filter(
|
|
127
|
-
(l) => l in generators,
|
|
128
|
-
)
|
|
129
|
-
: ALL_LANGUAGES;
|
|
130
|
-
|
|
131
|
-
return langs.map((lang) => generators[lang](pattern, flags)).join("\n\n---\n\n");
|
|
132
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,134 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
import { Server } from "@modelcontextprotocol/sdk/server/index.js";
|
|
4
|
-
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
5
|
-
import {
|
|
6
|
-
CallToolRequestSchema,
|
|
7
|
-
ListToolsRequestSchema,
|
|
8
|
-
} from "@modelcontextprotocol/sdk/types.js";
|
|
9
|
-
import {
|
|
10
|
-
checkProAccess,
|
|
11
|
-
getTrialUsesRemaining,
|
|
12
|
-
incrementTrialUse,
|
|
13
|
-
} from "./auth.js";
|
|
14
|
-
import {
|
|
15
|
-
registerTool,
|
|
16
|
-
getToolByName,
|
|
17
|
-
getAllTools,
|
|
18
|
-
} from "./registry.js";
|
|
19
|
-
import type { ToolDefinition } from "./registry.js";
|
|
20
|
-
|
|
21
|
-
// Re-export for tool files that import from index
|
|
22
|
-
export { registerTool, getToolByName, getAllTools };
|
|
23
|
-
export type { ToolDefinition };
|
|
24
|
-
|
|
25
|
-
// ---------------------------------------------------------------------------
|
|
26
|
-
// MCP Server
|
|
27
|
-
// ---------------------------------------------------------------------------
|
|
28
|
-
|
|
29
|
-
const PURCHASE_URL = "https://buy.stripe.com/28E14pfy1g5X1nz9HHgbm0s";
|
|
30
|
-
|
|
31
|
-
const server = new Server(
|
|
32
|
-
{ name: "@revxl/devtools", version: "1.0.0" },
|
|
33
|
-
{ capabilities: { tools: {} } }
|
|
34
|
-
);
|
|
35
|
-
|
|
36
|
-
// --- ListTools ------------------------------------------------------------
|
|
37
|
-
|
|
38
|
-
server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
39
|
-
const toolList = getAllTools().map((t) => ({
|
|
40
|
-
name: t.name,
|
|
41
|
-
description: t.pro
|
|
42
|
-
? `${t.description} [PRO - 3 free trials]`
|
|
43
|
-
: t.description,
|
|
44
|
-
inputSchema: t.inputSchema,
|
|
45
|
-
}));
|
|
46
|
-
return { tools: toolList };
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
// --- CallTool -------------------------------------------------------------
|
|
50
|
-
|
|
51
|
-
server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
52
|
-
const { name, arguments: args } = request.params;
|
|
53
|
-
|
|
54
|
-
const tool = getToolByName(name);
|
|
55
|
-
if (!tool) {
|
|
56
|
-
return {
|
|
57
|
-
content: [{ type: "text", text: `Unknown tool: ${name}` }],
|
|
58
|
-
isError: true,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// Pro gate
|
|
63
|
-
if (tool.pro) {
|
|
64
|
-
const isPro = await checkProAccess();
|
|
65
|
-
if (!isPro) {
|
|
66
|
-
const remaining = getTrialUsesRemaining(name);
|
|
67
|
-
if (remaining <= 0) {
|
|
68
|
-
return {
|
|
69
|
-
content: [
|
|
70
|
-
{
|
|
71
|
-
type: "text",
|
|
72
|
-
text: `⚡ ${name} is a Pro tool and you've used all 3 free trials.\n\nUpgrade for $7 one-time at ${PURCHASE_URL} to unlock unlimited access to all Pro tools.`,
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
isError: false,
|
|
76
|
-
};
|
|
77
|
-
}
|
|
78
|
-
incrementTrialUse(name);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
const result = await tool.handler((args ?? {}) as Record<string, unknown>);
|
|
84
|
-
const text =
|
|
85
|
-
typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
86
|
-
return { content: [{ type: "text", text }] };
|
|
87
|
-
} catch (err: unknown) {
|
|
88
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
89
|
-
return {
|
|
90
|
-
content: [{ type: "text", text: `Error in ${name}: ${message}` }],
|
|
91
|
-
isError: true,
|
|
92
|
-
};
|
|
93
|
-
}
|
|
94
|
-
});
|
|
95
|
-
|
|
96
|
-
// ---------------------------------------------------------------------------
|
|
97
|
-
// Tool imports
|
|
98
|
-
// ---------------------------------------------------------------------------
|
|
99
|
-
|
|
100
|
-
// Free tools
|
|
101
|
-
import "./tools/json-format.js";
|
|
102
|
-
import "./tools/base64.js";
|
|
103
|
-
import "./tools/url-encode.js";
|
|
104
|
-
import "./tools/uuid.js";
|
|
105
|
-
import "./tools/hash.js";
|
|
106
|
-
import "./tools/timestamp.js";
|
|
107
|
-
import "./tools/http-status.js";
|
|
108
|
-
|
|
109
|
-
// Pro tools
|
|
110
|
-
import "./tools/jwt.js";
|
|
111
|
-
import "./tools/regex.js";
|
|
112
|
-
import "./tools/cron.js";
|
|
113
|
-
import "./tools/json-diff.js";
|
|
114
|
-
import "./tools/json-query.js";
|
|
115
|
-
import "./tools/batch.js";
|
|
116
|
-
import "./tools/sql-format.js";
|
|
117
|
-
import "./tools/yaml-convert.js";
|
|
118
|
-
import "./tools/chmod.js";
|
|
119
|
-
import "./tools/secrets-scan.js";
|
|
120
|
-
|
|
121
|
-
// ---------------------------------------------------------------------------
|
|
122
|
-
// Start
|
|
123
|
-
// ---------------------------------------------------------------------------
|
|
124
|
-
|
|
125
|
-
async function main(): Promise<void> {
|
|
126
|
-
const transport = new StdioServerTransport();
|
|
127
|
-
await server.connect(transport);
|
|
128
|
-
console.error("@revxl/devtools MCP server running on stdio");
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
main().catch((err) => {
|
|
132
|
-
console.error("Fatal error:", err);
|
|
133
|
-
process.exit(1);
|
|
134
|
-
});
|
package/src/registry.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
// ---------------------------------------------------------------------------
|
|
2
|
-
// Tool registry — separated to avoid circular import issues with ESM hoisting
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
|
|
5
|
-
export interface ToolDefinition {
|
|
6
|
-
name: string;
|
|
7
|
-
description: string;
|
|
8
|
-
pro: boolean;
|
|
9
|
-
inputSchema: Record<string, unknown>;
|
|
10
|
-
handler: (args: Record<string, unknown>) => Promise<unknown>;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const tools: Map<string, ToolDefinition> = new Map();
|
|
14
|
-
|
|
15
|
-
export function registerTool(tool: ToolDefinition): void {
|
|
16
|
-
tools.set(tool.name, tool);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export function getToolByName(name: string): ToolDefinition | undefined {
|
|
20
|
-
return tools.get(name);
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export function getAllTools(): ToolDefinition[] {
|
|
24
|
-
return Array.from(tools.values());
|
|
25
|
-
}
|
package/src/tools/base64.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { registerTool } from "../registry.js";
|
|
2
|
-
|
|
3
|
-
registerTool({
|
|
4
|
-
name: "base64",
|
|
5
|
-
description: "Encode or decode Base64 strings",
|
|
6
|
-
pro: false,
|
|
7
|
-
inputSchema: {
|
|
8
|
-
type: "object",
|
|
9
|
-
properties: {
|
|
10
|
-
text: { type: "string", description: "Text to encode or Base64 string to decode" },
|
|
11
|
-
action: {
|
|
12
|
-
type: "string",
|
|
13
|
-
enum: ["encode", "decode"],
|
|
14
|
-
description: "encode or decode",
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
required: ["text", "action"],
|
|
18
|
-
},
|
|
19
|
-
handler: async (args) => {
|
|
20
|
-
const text = args.text as string;
|
|
21
|
-
const action = args.action as string;
|
|
22
|
-
|
|
23
|
-
if (action === "encode") {
|
|
24
|
-
return Buffer.from(text, "utf-8").toString("base64");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// Handle URL-safe Base64 variant
|
|
28
|
-
const normalized = text.replace(/-/g, "+").replace(/_/g, "/");
|
|
29
|
-
const decoded = Buffer.from(normalized, "base64").toString("utf-8");
|
|
30
|
-
return decoded;
|
|
31
|
-
},
|
|
32
|
-
});
|
package/src/tools/batch.ts
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
import { registerTool, getToolByName } from "../registry.js";
|
|
2
|
-
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
// Batch runner — execute a free tool across multiple inputs
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
|
|
7
|
-
const MAX_ITEMS = 500;
|
|
8
|
-
|
|
9
|
-
registerTool({
|
|
10
|
-
name: "batch",
|
|
11
|
-
description:
|
|
12
|
-
"Run any free tool across multiple inputs in one call — up to 500 items. Only works with free (non-Pro) tools.",
|
|
13
|
-
pro: true,
|
|
14
|
-
inputSchema: {
|
|
15
|
-
type: "object",
|
|
16
|
-
properties: {
|
|
17
|
-
tool: {
|
|
18
|
-
type: "string",
|
|
19
|
-
description: "Name of the tool to run (must be a free tool)",
|
|
20
|
-
},
|
|
21
|
-
items: {
|
|
22
|
-
type: "array",
|
|
23
|
-
items: { type: "object" },
|
|
24
|
-
description: "Array of argument objects — each is passed to the tool's handler (max 500)",
|
|
25
|
-
},
|
|
26
|
-
},
|
|
27
|
-
required: ["tool", "items"],
|
|
28
|
-
},
|
|
29
|
-
handler: async (args) => {
|
|
30
|
-
const toolName = args.tool as string;
|
|
31
|
-
const items = args.items as Record<string, unknown>[];
|
|
32
|
-
|
|
33
|
-
if (!Array.isArray(items) || items.length === 0) {
|
|
34
|
-
throw new Error("items must be a non-empty array");
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
if (items.length > MAX_ITEMS) {
|
|
38
|
-
throw new Error(`Maximum ${MAX_ITEMS} items per batch (got ${items.length})`);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const tool = getToolByName(toolName);
|
|
42
|
-
if (!tool) {
|
|
43
|
-
throw new Error(`Unknown tool: ${toolName}`);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
if (tool.pro) {
|
|
47
|
-
throw new Error(
|
|
48
|
-
`Batch only works with free tools. "${toolName}" is a Pro tool.`,
|
|
49
|
-
);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const results: string[] = [];
|
|
53
|
-
|
|
54
|
-
for (let i = 0; i < items.length; i++) {
|
|
55
|
-
try {
|
|
56
|
-
const result = await tool.handler(items[i]);
|
|
57
|
-
const text =
|
|
58
|
-
typeof result === "string" ? result : JSON.stringify(result, null, 2);
|
|
59
|
-
results.push(`[${i}] ${text}`);
|
|
60
|
-
} catch (err: unknown) {
|
|
61
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
62
|
-
results.push(`[${i}] ERROR: ${message}`);
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const header = `Batch ${toolName}: ${items.length} items`;
|
|
67
|
-
return `${header}\n${"=".repeat(header.length)}\n\n${results.join("\n\n")}`;
|
|
68
|
-
},
|
|
69
|
-
});
|
package/src/tools/chmod.ts
DELETED
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import { registerTool } from "../registry.js";
|
|
2
|
-
|
|
3
|
-
// ---------------------------------------------------------------------------
|
|
4
|
-
// Chmod converter — numeric ↔ symbolic with human-readable explanation
|
|
5
|
-
// ---------------------------------------------------------------------------
|
|
6
|
-
|
|
7
|
-
const PERM_MAP: Record<number, string> = {
|
|
8
|
-
0: "---",
|
|
9
|
-
1: "--x",
|
|
10
|
-
2: "-w-",
|
|
11
|
-
3: "-wx",
|
|
12
|
-
4: "r--",
|
|
13
|
-
5: "r-x",
|
|
14
|
-
6: "rw-",
|
|
15
|
-
7: "rwx",
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const ROLE_NAMES = ["Owner", "Group", "Others"] as const;
|
|
19
|
-
|
|
20
|
-
const PERM_LABELS: Record<string, string> = {
|
|
21
|
-
r: "read",
|
|
22
|
-
w: "write",
|
|
23
|
-
x: "execute",
|
|
24
|
-
};
|
|
25
|
-
|
|
26
|
-
function numericToSymbolic(mode: string): string {
|
|
27
|
-
const digits = mode.split("").map(Number);
|
|
28
|
-
return digits.map((d) => PERM_MAP[d]).join("");
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
function symbolicToNumeric(mode: string): string {
|
|
32
|
-
// Expect 9-char symbolic like "rwxr-xr-x"
|
|
33
|
-
const clean = mode.replace(/^-/, ""); // strip leading - if present (like -rwxr-xr-x)
|
|
34
|
-
const chars = clean.length >= 9 ? clean.slice(0, 9) : clean;
|
|
35
|
-
let result = "";
|
|
36
|
-
for (let i = 0; i < 3; i++) {
|
|
37
|
-
const group = chars.slice(i * 3, i * 3 + 3);
|
|
38
|
-
let val = 0;
|
|
39
|
-
if (group[0] === "r") val += 4;
|
|
40
|
-
if (group[1] === "w") val += 2;
|
|
41
|
-
if (group[2] === "x") val += 1;
|
|
42
|
-
result += val;
|
|
43
|
-
}
|
|
44
|
-
return result;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
function describePermissions(numericMode: string): string[] {
|
|
48
|
-
const digits = numericMode.split("").map(Number);
|
|
49
|
-
const descriptions: string[] = [];
|
|
50
|
-
|
|
51
|
-
for (let i = 0; i < 3; i++) {
|
|
52
|
-
const symbolic = PERM_MAP[digits[i]];
|
|
53
|
-
const perms: string[] = [];
|
|
54
|
-
for (const ch of symbolic) {
|
|
55
|
-
if (ch !== "-" && PERM_LABELS[ch]) {
|
|
56
|
-
perms.push(PERM_LABELS[ch]);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
const permStr = perms.length > 0 ? perms.join(", ") : "none";
|
|
60
|
-
descriptions.push(`${ROLE_NAMES[i]}: ${permStr}`);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return descriptions;
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
function isNumericMode(mode: string): boolean {
|
|
67
|
-
return /^[0-7]{3,4}$/.test(mode);
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
function isSymbolicMode(mode: string): boolean {
|
|
71
|
-
// Accept rwxr-xr-x or -rwxr-xr-x
|
|
72
|
-
return /^-?[rwx-]{9}$/.test(mode);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
registerTool({
|
|
76
|
-
name: "chmod",
|
|
77
|
-
description:
|
|
78
|
-
"Convert between numeric (755) and symbolic (rwxr-xr-x) chmod permissions with explanation",
|
|
79
|
-
pro: true,
|
|
80
|
-
inputSchema: {
|
|
81
|
-
type: "object",
|
|
82
|
-
properties: {
|
|
83
|
-
mode: {
|
|
84
|
-
type: "string",
|
|
85
|
-
description:
|
|
86
|
-
'Permission mode — numeric (e.g. "755") or symbolic (e.g. "rwxr-xr-x")',
|
|
87
|
-
},
|
|
88
|
-
},
|
|
89
|
-
required: ["mode"],
|
|
90
|
-
},
|
|
91
|
-
handler: async (args) => {
|
|
92
|
-
const mode = (args.mode as string).trim();
|
|
93
|
-
|
|
94
|
-
if (!mode) throw new Error("mode is required");
|
|
95
|
-
|
|
96
|
-
if (isNumericMode(mode)) {
|
|
97
|
-
// Take last 3 digits (ignore leading 0 in 0755)
|
|
98
|
-
const digits = mode.slice(-3);
|
|
99
|
-
const symbolic = numericToSymbolic(digits);
|
|
100
|
-
const explanation = describePermissions(digits);
|
|
101
|
-
|
|
102
|
-
return [
|
|
103
|
-
"=== Chmod: Numeric → Symbolic ===",
|
|
104
|
-
"",
|
|
105
|
-
`Numeric: ${digits}`,
|
|
106
|
-
`Symbolic: ${symbolic}`,
|
|
107
|
-
"",
|
|
108
|
-
"--- Permissions ---",
|
|
109
|
-
...explanation.map((e) => ` ${e}`),
|
|
110
|
-
].join("\n");
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
if (isSymbolicMode(mode)) {
|
|
114
|
-
const clean = mode.startsWith("-") ? mode.slice(1) : mode;
|
|
115
|
-
const numeric = symbolicToNumeric(clean);
|
|
116
|
-
const explanation = describePermissions(numeric);
|
|
117
|
-
|
|
118
|
-
return [
|
|
119
|
-
"=== Chmod: Symbolic → Numeric ===",
|
|
120
|
-
"",
|
|
121
|
-
`Symbolic: ${clean}`,
|
|
122
|
-
`Numeric: ${numeric}`,
|
|
123
|
-
"",
|
|
124
|
-
"--- Permissions ---",
|
|
125
|
-
...explanation.map((e) => ` ${e}`),
|
|
126
|
-
].join("\n");
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
throw new Error(
|
|
130
|
-
`Invalid mode: "${mode}". Use numeric (e.g. "755") or symbolic (e.g. "rwxr-xr-x").`,
|
|
131
|
-
);
|
|
132
|
-
},
|
|
133
|
-
});
|