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.
Files changed (70) hide show
  1. package/README.md +84 -0
  2. package/checkout/index.html +195 -0
  3. package/dist/auth.d.ts +3 -0
  4. package/dist/auth.js +77 -0
  5. package/dist/codegen/cron-codegen.d.ts +1 -0
  6. package/dist/codegen/cron-codegen.js +56 -0
  7. package/dist/codegen/regex-codegen.d.ts +1 -0
  8. package/dist/codegen/regex-codegen.js +125 -0
  9. package/dist/index.d.ts +22 -0
  10. package/dist/index.js +100 -0
  11. package/dist/registry.d.ts +10 -0
  12. package/dist/registry.js +13 -0
  13. package/dist/tools/base64.d.ts +1 -0
  14. package/dist/tools/base64.js +29 -0
  15. package/dist/tools/batch.d.ts +1 -0
  16. package/dist/tools/batch.js +56 -0
  17. package/dist/tools/chmod.d.ts +1 -0
  18. package/dist/tools/chmod.js +115 -0
  19. package/dist/tools/cron.d.ts +1 -0
  20. package/dist/tools/cron.js +311 -0
  21. package/dist/tools/hash.d.ts +1 -0
  22. package/dist/tools/hash.js +25 -0
  23. package/dist/tools/http-status.d.ts +1 -0
  24. package/dist/tools/http-status.js +59 -0
  25. package/dist/tools/json-diff.d.ts +1 -0
  26. package/dist/tools/json-diff.js +131 -0
  27. package/dist/tools/json-format.d.ts +1 -0
  28. package/dist/tools/json-format.js +38 -0
  29. package/dist/tools/json-query.d.ts +1 -0
  30. package/dist/tools/json-query.js +114 -0
  31. package/dist/tools/jwt.d.ts +1 -0
  32. package/dist/tools/jwt.js +177 -0
  33. package/dist/tools/regex.d.ts +1 -0
  34. package/dist/tools/regex.js +116 -0
  35. package/dist/tools/secrets-scan.d.ts +1 -0
  36. package/dist/tools/secrets-scan.js +173 -0
  37. package/dist/tools/sql-format.d.ts +1 -0
  38. package/dist/tools/sql-format.js +157 -0
  39. package/dist/tools/timestamp.d.ts +1 -0
  40. package/dist/tools/timestamp.js +72 -0
  41. package/dist/tools/url-encode.d.ts +1 -0
  42. package/dist/tools/url-encode.js +26 -0
  43. package/dist/tools/uuid.d.ts +1 -0
  44. package/dist/tools/uuid.js +24 -0
  45. package/dist/tools/yaml-convert.d.ts +1 -0
  46. package/dist/tools/yaml-convert.js +371 -0
  47. package/package.json +29 -0
  48. package/src/auth.ts +99 -0
  49. package/src/codegen/cron-codegen.ts +66 -0
  50. package/src/codegen/regex-codegen.ts +132 -0
  51. package/src/index.ts +134 -0
  52. package/src/registry.ts +25 -0
  53. package/src/tools/base64.ts +32 -0
  54. package/src/tools/batch.ts +69 -0
  55. package/src/tools/chmod.ts +133 -0
  56. package/src/tools/cron.ts +365 -0
  57. package/src/tools/hash.ts +26 -0
  58. package/src/tools/http-status.ts +63 -0
  59. package/src/tools/json-diff.ts +153 -0
  60. package/src/tools/json-format.ts +43 -0
  61. package/src/tools/json-query.ts +126 -0
  62. package/src/tools/jwt.ts +193 -0
  63. package/src/tools/regex.ts +131 -0
  64. package/src/tools/secrets-scan.ts +212 -0
  65. package/src/tools/sql-format.ts +178 -0
  66. package/src/tools/timestamp.ts +74 -0
  67. package/src/tools/url-encode.ts +29 -0
  68. package/src/tools/uuid.ts +25 -0
  69. package/src/tools/yaml-convert.ts +383 -0
  70. 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
+ }