revxl-devtools 1.0.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/README.md +84 -0
- package/checkout/index.html +195 -0
- package/dist/auth.d.ts +3 -0
- package/dist/auth.js +77 -0
- package/dist/codegen/cron-codegen.d.ts +1 -0
- package/dist/codegen/cron-codegen.js +56 -0
- package/dist/codegen/regex-codegen.d.ts +1 -0
- package/dist/codegen/regex-codegen.js +125 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.js +100 -0
- package/dist/registry.d.ts +10 -0
- package/dist/registry.js +13 -0
- package/dist/tools/base64.d.ts +1 -0
- package/dist/tools/base64.js +29 -0
- package/dist/tools/batch.d.ts +1 -0
- package/dist/tools/batch.js +56 -0
- package/dist/tools/chmod.d.ts +1 -0
- package/dist/tools/chmod.js +115 -0
- package/dist/tools/cron.d.ts +1 -0
- package/dist/tools/cron.js +311 -0
- package/dist/tools/hash.d.ts +1 -0
- package/dist/tools/hash.js +25 -0
- package/dist/tools/http-status.d.ts +1 -0
- package/dist/tools/http-status.js +59 -0
- package/dist/tools/json-diff.d.ts +1 -0
- package/dist/tools/json-diff.js +131 -0
- package/dist/tools/json-format.d.ts +1 -0
- package/dist/tools/json-format.js +38 -0
- package/dist/tools/json-query.d.ts +1 -0
- package/dist/tools/json-query.js +114 -0
- package/dist/tools/jwt.d.ts +1 -0
- package/dist/tools/jwt.js +177 -0
- package/dist/tools/regex.d.ts +1 -0
- package/dist/tools/regex.js +116 -0
- package/dist/tools/secrets-scan.d.ts +1 -0
- package/dist/tools/secrets-scan.js +173 -0
- package/dist/tools/sql-format.d.ts +1 -0
- package/dist/tools/sql-format.js +157 -0
- package/dist/tools/timestamp.d.ts +1 -0
- package/dist/tools/timestamp.js +72 -0
- package/dist/tools/url-encode.d.ts +1 -0
- package/dist/tools/url-encode.js +26 -0
- package/dist/tools/uuid.d.ts +1 -0
- package/dist/tools/uuid.js +24 -0
- package/dist/tools/yaml-convert.d.ts +1 -0
- package/dist/tools/yaml-convert.js +371 -0
- package/package.json +29 -0
- package/src/auth.ts +99 -0
- package/src/codegen/cron-codegen.ts +66 -0
- package/src/codegen/regex-codegen.ts +132 -0
- package/src/index.ts +134 -0
- package/src/registry.ts +25 -0
- package/src/tools/base64.ts +32 -0
- package/src/tools/batch.ts +69 -0
- package/src/tools/chmod.ts +133 -0
- package/src/tools/cron.ts +365 -0
- package/src/tools/hash.ts +26 -0
- package/src/tools/http-status.ts +63 -0
- package/src/tools/json-diff.ts +153 -0
- package/src/tools/json-format.ts +43 -0
- package/src/tools/json-query.ts +126 -0
- package/src/tools/jwt.ts +193 -0
- package/src/tools/regex.ts +131 -0
- package/src/tools/secrets-scan.ts +212 -0
- package/src/tools/sql-format.ts +178 -0
- package/src/tools/timestamp.ts +74 -0
- package/src/tools/url-encode.ts +29 -0
- package/src/tools/uuid.ts +25 -0
- package/src/tools/yaml-convert.ts +383 -0
- package/tsconfig.json +14 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
import { registerTool } from "../registry.js";
|
|
2
|
+
// ---------------------------------------------------------------------------
|
|
3
|
+
// Minimal YAML parser
|
|
4
|
+
// ---------------------------------------------------------------------------
|
|
5
|
+
function parseYaml(text) {
|
|
6
|
+
const lines = text.split("\n");
|
|
7
|
+
return parseYamlLines(lines, 0, 0).value;
|
|
8
|
+
}
|
|
9
|
+
function getIndent(line) {
|
|
10
|
+
const match = line.match(/^(\s*)/);
|
|
11
|
+
return match ? match[1].length : 0;
|
|
12
|
+
}
|
|
13
|
+
function parseScalar(val) {
|
|
14
|
+
const trimmed = val.trim();
|
|
15
|
+
if (trimmed === "" || trimmed === "null" || trimmed === "~")
|
|
16
|
+
return null;
|
|
17
|
+
if (trimmed === "true")
|
|
18
|
+
return true;
|
|
19
|
+
if (trimmed === "false")
|
|
20
|
+
return false;
|
|
21
|
+
if (/^-?\d+$/.test(trimmed))
|
|
22
|
+
return parseInt(trimmed, 10);
|
|
23
|
+
if (/^-?\d+\.\d+$/.test(trimmed))
|
|
24
|
+
return parseFloat(trimmed);
|
|
25
|
+
// Strip quotes
|
|
26
|
+
if ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
|
|
27
|
+
(trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
28
|
+
return trimmed.slice(1, -1);
|
|
29
|
+
}
|
|
30
|
+
return trimmed;
|
|
31
|
+
}
|
|
32
|
+
function parseYamlLines(lines, startLine, baseIndent) {
|
|
33
|
+
// Skip empty lines and comments
|
|
34
|
+
let i = startLine;
|
|
35
|
+
while (i < lines.length && (lines[i].trim() === "" || lines[i].trim().startsWith("#"))) {
|
|
36
|
+
i++;
|
|
37
|
+
}
|
|
38
|
+
if (i >= lines.length)
|
|
39
|
+
return { value: null, nextLine: i };
|
|
40
|
+
const line = lines[i];
|
|
41
|
+
const trimmed = line.trim();
|
|
42
|
+
// Check if it's a list item
|
|
43
|
+
if (trimmed.startsWith("- ")) {
|
|
44
|
+
const arr = [];
|
|
45
|
+
const listIndent = getIndent(line);
|
|
46
|
+
while (i < lines.length) {
|
|
47
|
+
const cur = lines[i];
|
|
48
|
+
if (cur.trim() === "" || cur.trim().startsWith("#")) {
|
|
49
|
+
i++;
|
|
50
|
+
continue;
|
|
51
|
+
}
|
|
52
|
+
const curIndent = getIndent(cur);
|
|
53
|
+
if (curIndent < listIndent)
|
|
54
|
+
break;
|
|
55
|
+
if (curIndent === listIndent && cur.trim().startsWith("- ")) {
|
|
56
|
+
const itemText = cur.trim().slice(2);
|
|
57
|
+
// Check if it's a key: value (nested map in list)
|
|
58
|
+
if (itemText.includes(": ")) {
|
|
59
|
+
const obj = {};
|
|
60
|
+
const colonIdx = itemText.indexOf(": ");
|
|
61
|
+
const key = itemText.slice(0, colonIdx).trim();
|
|
62
|
+
const val = itemText.slice(colonIdx + 2).trim();
|
|
63
|
+
obj[key] = parseScalar(val);
|
|
64
|
+
// Check for continuation lines at deeper indent
|
|
65
|
+
i++;
|
|
66
|
+
while (i < lines.length) {
|
|
67
|
+
const nextLine = lines[i];
|
|
68
|
+
if (nextLine.trim() === "" || nextLine.trim().startsWith("#")) {
|
|
69
|
+
i++;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
const nextIndent = getIndent(nextLine);
|
|
73
|
+
if (nextIndent <= listIndent)
|
|
74
|
+
break;
|
|
75
|
+
if (nextLine.trim().includes(": ")) {
|
|
76
|
+
const ci = nextLine.trim().indexOf(": ");
|
|
77
|
+
const k = nextLine.trim().slice(0, ci).trim();
|
|
78
|
+
const v = nextLine.trim().slice(ci + 2).trim();
|
|
79
|
+
obj[k] = parseScalar(v);
|
|
80
|
+
i++;
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
arr.push(obj);
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
arr.push(parseScalar(itemText));
|
|
90
|
+
i++;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
break;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return { value: arr, nextLine: i };
|
|
98
|
+
}
|
|
99
|
+
// Check if it's a map (key: value)
|
|
100
|
+
if (trimmed.includes(": ") || trimmed.endsWith(":")) {
|
|
101
|
+
const obj = {};
|
|
102
|
+
const mapIndent = getIndent(line);
|
|
103
|
+
while (i < lines.length) {
|
|
104
|
+
const cur = lines[i];
|
|
105
|
+
if (cur.trim() === "" || cur.trim().startsWith("#")) {
|
|
106
|
+
i++;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
const curIndent = getIndent(cur);
|
|
110
|
+
if (curIndent < mapIndent && i > startLine)
|
|
111
|
+
break;
|
|
112
|
+
if (curIndent !== mapIndent)
|
|
113
|
+
break;
|
|
114
|
+
const curTrimmed = cur.trim();
|
|
115
|
+
if (curTrimmed.endsWith(":")) {
|
|
116
|
+
// Block value — next lines are the value
|
|
117
|
+
const key = curTrimmed.slice(0, -1).trim();
|
|
118
|
+
i++;
|
|
119
|
+
const result = parseYamlLines(lines, i, mapIndent + 2);
|
|
120
|
+
obj[key] = result.value;
|
|
121
|
+
i = result.nextLine;
|
|
122
|
+
}
|
|
123
|
+
else if (curTrimmed.includes(": ")) {
|
|
124
|
+
const colonIdx = curTrimmed.indexOf(": ");
|
|
125
|
+
const key = curTrimmed.slice(0, colonIdx).trim();
|
|
126
|
+
const val = curTrimmed.slice(colonIdx + 2).trim();
|
|
127
|
+
obj[key] = parseScalar(val);
|
|
128
|
+
i++;
|
|
129
|
+
}
|
|
130
|
+
else {
|
|
131
|
+
break;
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return { value: obj, nextLine: i };
|
|
135
|
+
}
|
|
136
|
+
// Plain scalar
|
|
137
|
+
return { value: parseScalar(trimmed), nextLine: i + 1 };
|
|
138
|
+
}
|
|
139
|
+
// ---------------------------------------------------------------------------
|
|
140
|
+
// Minimal TOML parser
|
|
141
|
+
// ---------------------------------------------------------------------------
|
|
142
|
+
function parseToml(text) {
|
|
143
|
+
const result = {};
|
|
144
|
+
let currentSection = result;
|
|
145
|
+
const lines = text.split("\n");
|
|
146
|
+
for (const line of lines) {
|
|
147
|
+
const trimmed = line.trim();
|
|
148
|
+
if (trimmed === "" || trimmed.startsWith("#"))
|
|
149
|
+
continue;
|
|
150
|
+
// Section header [section]
|
|
151
|
+
const sectionMatch = trimmed.match(/^\[([^\]]+)\]$/);
|
|
152
|
+
if (sectionMatch) {
|
|
153
|
+
const sectionName = sectionMatch[1];
|
|
154
|
+
const parts = sectionName.split(".");
|
|
155
|
+
let target = result;
|
|
156
|
+
for (const part of parts) {
|
|
157
|
+
if (!(part in target)) {
|
|
158
|
+
target[part] = {};
|
|
159
|
+
}
|
|
160
|
+
target = target[part];
|
|
161
|
+
}
|
|
162
|
+
currentSection = target;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
// key = value
|
|
166
|
+
const kvMatch = trimmed.match(/^([^=]+)=\s*(.*)$/);
|
|
167
|
+
if (kvMatch) {
|
|
168
|
+
const key = kvMatch[1].trim();
|
|
169
|
+
const val = kvMatch[2].trim();
|
|
170
|
+
currentSection[key] = parseTomlValue(val);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
function parseTomlValue(val) {
|
|
176
|
+
if (val === "true")
|
|
177
|
+
return true;
|
|
178
|
+
if (val === "false")
|
|
179
|
+
return false;
|
|
180
|
+
if (/^-?\d+$/.test(val))
|
|
181
|
+
return parseInt(val, 10);
|
|
182
|
+
if (/^-?\d+\.\d+$/.test(val))
|
|
183
|
+
return parseFloat(val);
|
|
184
|
+
if ((val.startsWith('"') && val.endsWith('"')) ||
|
|
185
|
+
(val.startsWith("'") && val.endsWith("'"))) {
|
|
186
|
+
return val.slice(1, -1);
|
|
187
|
+
}
|
|
188
|
+
// Arrays
|
|
189
|
+
if (val.startsWith("[") && val.endsWith("]")) {
|
|
190
|
+
const inner = val.slice(1, -1).trim();
|
|
191
|
+
if (inner === "")
|
|
192
|
+
return [];
|
|
193
|
+
return inner.split(",").map((item) => parseTomlValue(item.trim()));
|
|
194
|
+
}
|
|
195
|
+
return val;
|
|
196
|
+
}
|
|
197
|
+
// ---------------------------------------------------------------------------
|
|
198
|
+
// Serializers
|
|
199
|
+
// ---------------------------------------------------------------------------
|
|
200
|
+
function toYaml(data, indent = 0) {
|
|
201
|
+
const prefix = " ".repeat(indent);
|
|
202
|
+
if (data === null || data === undefined)
|
|
203
|
+
return `${prefix}null\n`;
|
|
204
|
+
if (typeof data === "boolean")
|
|
205
|
+
return `${prefix}${data}\n`;
|
|
206
|
+
if (typeof data === "number")
|
|
207
|
+
return `${prefix}${data}\n`;
|
|
208
|
+
if (typeof data === "string") {
|
|
209
|
+
if (data.includes("\n") || data.includes(": ") || data.includes("#")) {
|
|
210
|
+
return `${prefix}"${data.replace(/"/g, '\\"')}"\n`;
|
|
211
|
+
}
|
|
212
|
+
return `${prefix}${data}\n`;
|
|
213
|
+
}
|
|
214
|
+
if (Array.isArray(data)) {
|
|
215
|
+
if (data.length === 0)
|
|
216
|
+
return `${prefix}[]\n`;
|
|
217
|
+
let out = "";
|
|
218
|
+
for (const item of data) {
|
|
219
|
+
if (typeof item === "object" && item !== null && !Array.isArray(item)) {
|
|
220
|
+
const entries = Object.entries(item);
|
|
221
|
+
if (entries.length > 0) {
|
|
222
|
+
const [firstKey, firstVal] = entries[0];
|
|
223
|
+
out += `${prefix}- ${firstKey}: ${scalarToYaml(firstVal)}\n`;
|
|
224
|
+
for (let i = 1; i < entries.length; i++) {
|
|
225
|
+
out += `${prefix} ${entries[i][0]}: ${scalarToYaml(entries[i][1])}\n`;
|
|
226
|
+
}
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
out += `${prefix}- ${scalarToYaml(item)}\n`;
|
|
231
|
+
}
|
|
232
|
+
return out;
|
|
233
|
+
}
|
|
234
|
+
if (typeof data === "object") {
|
|
235
|
+
const obj = data;
|
|
236
|
+
const keys = Object.keys(obj);
|
|
237
|
+
if (keys.length === 0)
|
|
238
|
+
return `${prefix}{}\n`;
|
|
239
|
+
let out = "";
|
|
240
|
+
for (const key of keys) {
|
|
241
|
+
const val = obj[key];
|
|
242
|
+
if (typeof val === "object" &&
|
|
243
|
+
val !== null &&
|
|
244
|
+
!Array.isArray(val) &&
|
|
245
|
+
Object.keys(val).length > 0) {
|
|
246
|
+
out += `${prefix}${key}:\n`;
|
|
247
|
+
out += toYaml(val, indent + 1);
|
|
248
|
+
}
|
|
249
|
+
else if (Array.isArray(val)) {
|
|
250
|
+
out += `${prefix}${key}:\n`;
|
|
251
|
+
out += toYaml(val, indent + 1);
|
|
252
|
+
}
|
|
253
|
+
else {
|
|
254
|
+
out += `${prefix}${key}: ${scalarToYaml(val)}\n`;
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
return out;
|
|
258
|
+
}
|
|
259
|
+
return `${prefix}${String(data)}\n`;
|
|
260
|
+
}
|
|
261
|
+
function scalarToYaml(val) {
|
|
262
|
+
if (val === null || val === undefined)
|
|
263
|
+
return "null";
|
|
264
|
+
if (typeof val === "boolean")
|
|
265
|
+
return String(val);
|
|
266
|
+
if (typeof val === "number")
|
|
267
|
+
return String(val);
|
|
268
|
+
if (typeof val === "string") {
|
|
269
|
+
if (val.includes(": ") || val.includes("#") || val.includes("\n")) {
|
|
270
|
+
return `"${val.replace(/"/g, '\\"')}"`;
|
|
271
|
+
}
|
|
272
|
+
return val;
|
|
273
|
+
}
|
|
274
|
+
return JSON.stringify(val);
|
|
275
|
+
}
|
|
276
|
+
function toToml(data, sectionPath = "") {
|
|
277
|
+
if (typeof data !== "object" || data === null || Array.isArray(data)) {
|
|
278
|
+
return String(data);
|
|
279
|
+
}
|
|
280
|
+
const obj = data;
|
|
281
|
+
let topLevel = "";
|
|
282
|
+
let sections = "";
|
|
283
|
+
for (const [key, val] of Object.entries(obj)) {
|
|
284
|
+
if (typeof val === "object" &&
|
|
285
|
+
val !== null &&
|
|
286
|
+
!Array.isArray(val)) {
|
|
287
|
+
const path = sectionPath ? `${sectionPath}.${key}` : key;
|
|
288
|
+
sections += `[${path}]\n`;
|
|
289
|
+
sections += toToml(val, path);
|
|
290
|
+
sections += "\n";
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
topLevel += `${key} = ${toTomlValue(val)}\n`;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
return topLevel + sections;
|
|
297
|
+
}
|
|
298
|
+
function toTomlValue(val) {
|
|
299
|
+
if (val === null || val === undefined)
|
|
300
|
+
return '""';
|
|
301
|
+
if (typeof val === "boolean")
|
|
302
|
+
return String(val);
|
|
303
|
+
if (typeof val === "number")
|
|
304
|
+
return String(val);
|
|
305
|
+
if (typeof val === "string")
|
|
306
|
+
return `"${val.replace(/"/g, '\\"')}"`;
|
|
307
|
+
if (Array.isArray(val)) {
|
|
308
|
+
return `[${val.map((v) => toTomlValue(v)).join(", ")}]`;
|
|
309
|
+
}
|
|
310
|
+
return JSON.stringify(val);
|
|
311
|
+
}
|
|
312
|
+
function parse(text, format) {
|
|
313
|
+
switch (format) {
|
|
314
|
+
case "json":
|
|
315
|
+
return JSON.parse(text);
|
|
316
|
+
case "yaml":
|
|
317
|
+
return parseYaml(text);
|
|
318
|
+
case "toml":
|
|
319
|
+
return parseToml(text);
|
|
320
|
+
default:
|
|
321
|
+
throw new Error(`Unsupported input format: ${format}`);
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
function serialize(data, format) {
|
|
325
|
+
switch (format) {
|
|
326
|
+
case "json":
|
|
327
|
+
return JSON.stringify(data, null, 2);
|
|
328
|
+
case "yaml":
|
|
329
|
+
return toYaml(data).trimEnd();
|
|
330
|
+
case "toml":
|
|
331
|
+
return toToml(data).trimEnd();
|
|
332
|
+
default:
|
|
333
|
+
throw new Error(`Unsupported output format: ${format}`);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
registerTool({
|
|
337
|
+
name: "yaml_convert",
|
|
338
|
+
description: "Convert between YAML, JSON, and TOML formats",
|
|
339
|
+
pro: true,
|
|
340
|
+
inputSchema: {
|
|
341
|
+
type: "object",
|
|
342
|
+
properties: {
|
|
343
|
+
text: { type: "string", description: "Input text to convert" },
|
|
344
|
+
from: {
|
|
345
|
+
type: "string",
|
|
346
|
+
enum: ["yaml", "json", "toml"],
|
|
347
|
+
description: "Source format",
|
|
348
|
+
},
|
|
349
|
+
to: {
|
|
350
|
+
type: "string",
|
|
351
|
+
enum: ["yaml", "json", "toml"],
|
|
352
|
+
description: "Target format",
|
|
353
|
+
},
|
|
354
|
+
},
|
|
355
|
+
required: ["text", "from", "to"],
|
|
356
|
+
},
|
|
357
|
+
handler: async (args) => {
|
|
358
|
+
const text = args.text;
|
|
359
|
+
const from = args.from;
|
|
360
|
+
const to = args.to;
|
|
361
|
+
if (!text.trim())
|
|
362
|
+
throw new Error("Input text is empty");
|
|
363
|
+
const data = parse(text, from);
|
|
364
|
+
const output = serialize(data, to);
|
|
365
|
+
return [
|
|
366
|
+
`=== Converted ${from.toUpperCase()} → ${to.toUpperCase()} ===`,
|
|
367
|
+
"",
|
|
368
|
+
output,
|
|
369
|
+
].join("\n");
|
|
370
|
+
},
|
|
371
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "revxl-devtools",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "17 developer tools for AI agents. JSON, JWT, regex, cron, secrets scanner. Free core + Pro ($7).",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"revxl-devtools": "./dist/index.js"
|
|
8
|
+
},
|
|
9
|
+
"main": "./dist/index.js",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"build": "tsc",
|
|
12
|
+
"start": "node dist/index.js",
|
|
13
|
+
"dev": "tsx src/index.ts"
|
|
14
|
+
},
|
|
15
|
+
"keywords": ["mcp", "developer-tools", "json", "jwt", "regex", "claude", "cursor", "ai-tools", "mcp-server"],
|
|
16
|
+
"author": "RevXL <john@revxl.net>",
|
|
17
|
+
"license": "MIT",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@modelcontextprotocol/sdk": "^1.0.0"
|
|
20
|
+
},
|
|
21
|
+
"devDependencies": {
|
|
22
|
+
"typescript": "^5.7.0",
|
|
23
|
+
"tsx": "^4.19.0",
|
|
24
|
+
"@types/node": "^22.10.0"
|
|
25
|
+
},
|
|
26
|
+
"engines": {
|
|
27
|
+
"node": ">=18"
|
|
28
|
+
}
|
|
29
|
+
}
|
package/src/auth.ts
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
}
|