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,13 @@
1
+ // ---------------------------------------------------------------------------
2
+ // Tool registry — separated to avoid circular import issues with ESM hoisting
3
+ // ---------------------------------------------------------------------------
4
+ const tools = new Map();
5
+ export function registerTool(tool) {
6
+ tools.set(tool.name, tool);
7
+ }
8
+ export function getToolByName(name) {
9
+ return tools.get(name);
10
+ }
11
+ export function getAllTools() {
12
+ return Array.from(tools.values());
13
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,29 @@
1
+ import { registerTool } from "../registry.js";
2
+ registerTool({
3
+ name: "base64",
4
+ description: "Encode or decode Base64 strings",
5
+ pro: false,
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {
9
+ text: { type: "string", description: "Text to encode or Base64 string to decode" },
10
+ action: {
11
+ type: "string",
12
+ enum: ["encode", "decode"],
13
+ description: "encode or decode",
14
+ },
15
+ },
16
+ required: ["text", "action"],
17
+ },
18
+ handler: async (args) => {
19
+ const text = args.text;
20
+ const action = args.action;
21
+ if (action === "encode") {
22
+ return Buffer.from(text, "utf-8").toString("base64");
23
+ }
24
+ // Handle URL-safe Base64 variant
25
+ const normalized = text.replace(/-/g, "+").replace(/_/g, "/");
26
+ const decoded = Buffer.from(normalized, "base64").toString("utf-8");
27
+ return decoded;
28
+ },
29
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,56 @@
1
+ import { registerTool, getToolByName } from "../registry.js";
2
+ // ---------------------------------------------------------------------------
3
+ // Batch runner — execute a free tool across multiple inputs
4
+ // ---------------------------------------------------------------------------
5
+ const MAX_ITEMS = 500;
6
+ registerTool({
7
+ name: "batch",
8
+ description: "Run any free tool across multiple inputs in one call — up to 500 items. Only works with free (non-Pro) tools.",
9
+ pro: true,
10
+ inputSchema: {
11
+ type: "object",
12
+ properties: {
13
+ tool: {
14
+ type: "string",
15
+ description: "Name of the tool to run (must be a free tool)",
16
+ },
17
+ items: {
18
+ type: "array",
19
+ items: { type: "object" },
20
+ description: "Array of argument objects — each is passed to the tool's handler (max 500)",
21
+ },
22
+ },
23
+ required: ["tool", "items"],
24
+ },
25
+ handler: async (args) => {
26
+ const toolName = args.tool;
27
+ const items = args.items;
28
+ if (!Array.isArray(items) || items.length === 0) {
29
+ throw new Error("items must be a non-empty array");
30
+ }
31
+ if (items.length > MAX_ITEMS) {
32
+ throw new Error(`Maximum ${MAX_ITEMS} items per batch (got ${items.length})`);
33
+ }
34
+ const tool = getToolByName(toolName);
35
+ if (!tool) {
36
+ throw new Error(`Unknown tool: ${toolName}`);
37
+ }
38
+ if (tool.pro) {
39
+ throw new Error(`Batch only works with free tools. "${toolName}" is a Pro tool.`);
40
+ }
41
+ const results = [];
42
+ for (let i = 0; i < items.length; i++) {
43
+ try {
44
+ const result = await tool.handler(items[i]);
45
+ const text = typeof result === "string" ? result : JSON.stringify(result, null, 2);
46
+ results.push(`[${i}] ${text}`);
47
+ }
48
+ catch (err) {
49
+ const message = err instanceof Error ? err.message : String(err);
50
+ results.push(`[${i}] ERROR: ${message}`);
51
+ }
52
+ }
53
+ const header = `Batch ${toolName}: ${items.length} items`;
54
+ return `${header}\n${"=".repeat(header.length)}\n\n${results.join("\n\n")}`;
55
+ },
56
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,115 @@
1
+ import { registerTool } from "../registry.js";
2
+ // ---------------------------------------------------------------------------
3
+ // Chmod converter — numeric ↔ symbolic with human-readable explanation
4
+ // ---------------------------------------------------------------------------
5
+ const PERM_MAP = {
6
+ 0: "---",
7
+ 1: "--x",
8
+ 2: "-w-",
9
+ 3: "-wx",
10
+ 4: "r--",
11
+ 5: "r-x",
12
+ 6: "rw-",
13
+ 7: "rwx",
14
+ };
15
+ const ROLE_NAMES = ["Owner", "Group", "Others"];
16
+ const PERM_LABELS = {
17
+ r: "read",
18
+ w: "write",
19
+ x: "execute",
20
+ };
21
+ function numericToSymbolic(mode) {
22
+ const digits = mode.split("").map(Number);
23
+ return digits.map((d) => PERM_MAP[d]).join("");
24
+ }
25
+ function symbolicToNumeric(mode) {
26
+ // Expect 9-char symbolic like "rwxr-xr-x"
27
+ const clean = mode.replace(/^-/, ""); // strip leading - if present (like -rwxr-xr-x)
28
+ const chars = clean.length >= 9 ? clean.slice(0, 9) : clean;
29
+ let result = "";
30
+ for (let i = 0; i < 3; i++) {
31
+ const group = chars.slice(i * 3, i * 3 + 3);
32
+ let val = 0;
33
+ if (group[0] === "r")
34
+ val += 4;
35
+ if (group[1] === "w")
36
+ val += 2;
37
+ if (group[2] === "x")
38
+ val += 1;
39
+ result += val;
40
+ }
41
+ return result;
42
+ }
43
+ function describePermissions(numericMode) {
44
+ const digits = numericMode.split("").map(Number);
45
+ const descriptions = [];
46
+ for (let i = 0; i < 3; i++) {
47
+ const symbolic = PERM_MAP[digits[i]];
48
+ const perms = [];
49
+ for (const ch of symbolic) {
50
+ if (ch !== "-" && PERM_LABELS[ch]) {
51
+ perms.push(PERM_LABELS[ch]);
52
+ }
53
+ }
54
+ const permStr = perms.length > 0 ? perms.join(", ") : "none";
55
+ descriptions.push(`${ROLE_NAMES[i]}: ${permStr}`);
56
+ }
57
+ return descriptions;
58
+ }
59
+ function isNumericMode(mode) {
60
+ return /^[0-7]{3,4}$/.test(mode);
61
+ }
62
+ function isSymbolicMode(mode) {
63
+ // Accept rwxr-xr-x or -rwxr-xr-x
64
+ return /^-?[rwx-]{9}$/.test(mode);
65
+ }
66
+ registerTool({
67
+ name: "chmod",
68
+ description: "Convert between numeric (755) and symbolic (rwxr-xr-x) chmod permissions with explanation",
69
+ pro: true,
70
+ inputSchema: {
71
+ type: "object",
72
+ properties: {
73
+ mode: {
74
+ type: "string",
75
+ description: 'Permission mode — numeric (e.g. "755") or symbolic (e.g. "rwxr-xr-x")',
76
+ },
77
+ },
78
+ required: ["mode"],
79
+ },
80
+ handler: async (args) => {
81
+ const mode = args.mode.trim();
82
+ if (!mode)
83
+ throw new Error("mode is required");
84
+ if (isNumericMode(mode)) {
85
+ // Take last 3 digits (ignore leading 0 in 0755)
86
+ const digits = mode.slice(-3);
87
+ const symbolic = numericToSymbolic(digits);
88
+ const explanation = describePermissions(digits);
89
+ return [
90
+ "=== Chmod: Numeric → Symbolic ===",
91
+ "",
92
+ `Numeric: ${digits}`,
93
+ `Symbolic: ${symbolic}`,
94
+ "",
95
+ "--- Permissions ---",
96
+ ...explanation.map((e) => ` ${e}`),
97
+ ].join("\n");
98
+ }
99
+ if (isSymbolicMode(mode)) {
100
+ const clean = mode.startsWith("-") ? mode.slice(1) : mode;
101
+ const numeric = symbolicToNumeric(clean);
102
+ const explanation = describePermissions(numeric);
103
+ return [
104
+ "=== Chmod: Symbolic → Numeric ===",
105
+ "",
106
+ `Symbolic: ${clean}`,
107
+ `Numeric: ${numeric}`,
108
+ "",
109
+ "--- Permissions ---",
110
+ ...explanation.map((e) => ` ${e}`),
111
+ ].join("\n");
112
+ }
113
+ throw new Error(`Invalid mode: "${mode}". Use numeric (e.g. "755") or symbolic (e.g. "rwxr-xr-x").`);
114
+ },
115
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,311 @@
1
+ import { registerTool } from "../registry.js";
2
+ import { generateCronCode } from "../codegen/cron-codegen.js";
3
+ // ---------------------------------------------------------------------------
4
+ // Cron expression explainer, generator, and next-run calculator
5
+ // ---------------------------------------------------------------------------
6
+ const FIELD_NAMES = ["minute", "hour", "day of month", "month", "day of week"];
7
+ const MONTH_NAMES = [
8
+ "", "January", "February", "March", "April", "May", "June",
9
+ "July", "August", "September", "October", "November", "December",
10
+ ];
11
+ const DOW_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
12
+ function parseField(raw, min, max) {
13
+ if (raw === "*")
14
+ return { type: "any", raw };
15
+ // Step: */N or N-M/S
16
+ if (raw.includes("/")) {
17
+ const [base, stepStr] = raw.split("/");
18
+ const step = parseInt(stepStr, 10);
19
+ if (base === "*") {
20
+ return { type: "step", raw, step, rangeStart: min, rangeEnd: max };
21
+ }
22
+ if (base.includes("-")) {
23
+ const [s, e] = base.split("-").map(Number);
24
+ return { type: "step", raw, step, rangeStart: s, rangeEnd: e };
25
+ }
26
+ return { type: "step", raw, step, rangeStart: parseInt(base, 10), rangeEnd: max };
27
+ }
28
+ // Range: N-M
29
+ if (raw.includes("-") && !raw.includes(",")) {
30
+ const [s, e] = raw.split("-").map(Number);
31
+ return { type: "range", raw, rangeStart: s, rangeEnd: e };
32
+ }
33
+ // List: N,M,O
34
+ if (raw.includes(",")) {
35
+ const values = raw.split(",").map(Number);
36
+ return { type: "list", raw, values };
37
+ }
38
+ // Single value
39
+ return { type: "value", raw, values: [parseInt(raw, 10)] };
40
+ }
41
+ function expandField(field, min, max) {
42
+ switch (field.type) {
43
+ case "any": {
44
+ const vals = [];
45
+ for (let i = min; i <= max; i++)
46
+ vals.push(i);
47
+ return vals;
48
+ }
49
+ case "value":
50
+ case "list":
51
+ return field.values;
52
+ case "range": {
53
+ const vals = [];
54
+ for (let i = field.rangeStart; i <= field.rangeEnd; i++)
55
+ vals.push(i);
56
+ return vals;
57
+ }
58
+ case "step": {
59
+ const vals = [];
60
+ for (let i = field.rangeStart; i <= field.rangeEnd; i += field.step)
61
+ vals.push(i);
62
+ return vals;
63
+ }
64
+ }
65
+ }
66
+ function describeField(field, index) {
67
+ const name = FIELD_NAMES[index];
68
+ switch (field.type) {
69
+ case "any":
70
+ return `${name}: every ${name}`;
71
+ case "value": {
72
+ const v = field.values[0];
73
+ if (index === 3)
74
+ return `${name}: ${MONTH_NAMES[v] || v}`;
75
+ if (index === 4)
76
+ return `${name}: ${DOW_NAMES[v] || v}`;
77
+ return `${name}: ${v}`;
78
+ }
79
+ case "list": {
80
+ const labels = field.values.map((v) => {
81
+ if (index === 3)
82
+ return MONTH_NAMES[v] || String(v);
83
+ if (index === 4)
84
+ return DOW_NAMES[v] || String(v);
85
+ return String(v);
86
+ });
87
+ return `${name}: ${labels.join(", ")}`;
88
+ }
89
+ case "range":
90
+ return `${name}: ${field.rangeStart} through ${field.rangeEnd}`;
91
+ case "step":
92
+ return `${name}: every ${field.step} starting at ${field.rangeStart}`;
93
+ }
94
+ }
95
+ // ---------------------------------------------------------------------------
96
+ // Next run calculator
97
+ // ---------------------------------------------------------------------------
98
+ function fieldMatches(value, allowed) {
99
+ return allowed.includes(value);
100
+ }
101
+ function getNextRuns(expression, count, fromDate) {
102
+ const parts = expression.split(/\s+/);
103
+ if (parts.length !== 5)
104
+ throw new Error("Invalid cron: expected 5 fields");
105
+ const ranges = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 6]];
106
+ const allowedSets = parts.map((p, i) => {
107
+ const field = parseField(p, ranges[i][0], ranges[i][1]);
108
+ return expandField(field, ranges[i][0], ranges[i][1]);
109
+ });
110
+ const [allowedMin, allowedHour, allowedDom, allowedMonth, allowedDow] = allowedSets;
111
+ const results = [];
112
+ const start = fromDate ? new Date(fromDate) : new Date();
113
+ // Round up to next minute
114
+ start.setSeconds(0, 0);
115
+ start.setMinutes(start.getMinutes() + 1);
116
+ const maxIterations = count * 1440 * 32; // generous limit
117
+ const current = new Date(start);
118
+ for (let i = 0; i < maxIterations && results.length < count; i++) {
119
+ const min = current.getMinutes();
120
+ const hour = current.getHours();
121
+ const dom = current.getDate();
122
+ const month = current.getMonth() + 1;
123
+ const dow = current.getDay();
124
+ if (fieldMatches(min, allowedMin) &&
125
+ fieldMatches(hour, allowedHour) &&
126
+ fieldMatches(dom, allowedDom) &&
127
+ fieldMatches(month, allowedMonth) &&
128
+ fieldMatches(dow, allowedDow)) {
129
+ results.push(new Date(current));
130
+ }
131
+ current.setMinutes(current.getMinutes() + 1);
132
+ }
133
+ return results;
134
+ }
135
+ // ---------------------------------------------------------------------------
136
+ // Natural language -> cron
137
+ // ---------------------------------------------------------------------------
138
+ function naturalToCron(text) {
139
+ const lower = text.toLowerCase().trim();
140
+ // "every N minutes"
141
+ const everyMinMatch = lower.match(/every\s+(\d+)\s+minutes?/);
142
+ if (everyMinMatch)
143
+ return `*/${everyMinMatch[1]} * * * *`;
144
+ // "every N hours"
145
+ const everyHourMatch = lower.match(/every\s+(\d+)\s+hours?/);
146
+ if (everyHourMatch)
147
+ return `0 */${everyHourMatch[1]} * * *`;
148
+ // "every minute"
149
+ if (/every\s+minute/.test(lower))
150
+ return "* * * * *";
151
+ // "every hour"
152
+ if (/every\s+hour/.test(lower))
153
+ return "0 * * * *";
154
+ // "midnight"
155
+ if (/midnight/.test(lower))
156
+ return "0 0 * * *";
157
+ // "noon"
158
+ if (/noon/.test(lower))
159
+ return "0 12 * * *";
160
+ // "every day at Xam/pm" or "daily at X"
161
+ const dailyMatch = lower.match(/(?:every\s+day|daily)\s+at\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?/);
162
+ if (dailyMatch) {
163
+ let hour = parseInt(dailyMatch[1], 10);
164
+ const min = dailyMatch[2] ? parseInt(dailyMatch[2], 10) : 0;
165
+ const ampm = dailyMatch[3];
166
+ if (ampm === "pm" && hour < 12)
167
+ hour += 12;
168
+ if (ampm === "am" && hour === 12)
169
+ hour = 0;
170
+ return `${min} ${hour} * * *`;
171
+ }
172
+ // "every weekday at X"
173
+ const weekdayMatch = lower.match(/every\s+weekday\s+at\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?/);
174
+ if (weekdayMatch) {
175
+ let hour = parseInt(weekdayMatch[1], 10);
176
+ const min = weekdayMatch[2] ? parseInt(weekdayMatch[2], 10) : 0;
177
+ const ampm = weekdayMatch[3];
178
+ if (ampm === "pm" && hour < 12)
179
+ hour += 12;
180
+ if (ampm === "am" && hour === 12)
181
+ hour = 0;
182
+ return `${min} ${hour} * * 1-5`;
183
+ }
184
+ // "every monday/tuesday/..." with optional "at X"
185
+ const dayNames = {
186
+ sunday: 0, monday: 1, tuesday: 2, wednesday: 3,
187
+ thursday: 4, friday: 5, saturday: 6,
188
+ };
189
+ const dayMatch = lower.match(/every\s+(sunday|monday|tuesday|wednesday|thursday|friday|saturday)(?:\s+at\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?)?/);
190
+ if (dayMatch) {
191
+ const dow = dayNames[dayMatch[1]];
192
+ let hour = dayMatch[2] ? parseInt(dayMatch[2], 10) : 0;
193
+ const min = dayMatch[3] ? parseInt(dayMatch[3], 10) : 0;
194
+ const ampm = dayMatch[4];
195
+ if (ampm === "pm" && hour < 12)
196
+ hour += 12;
197
+ if (ampm === "am" && hour === 12)
198
+ hour = 0;
199
+ return `${min} ${hour} * * ${dow}`;
200
+ }
201
+ throw new Error(`Could not parse: "${text}". Try formats like "every 5 minutes", "every day at 3pm", "every monday at 9am", "midnight", "noon"`);
202
+ }
203
+ // ---------------------------------------------------------------------------
204
+ // Tool registration
205
+ // ---------------------------------------------------------------------------
206
+ registerTool({
207
+ name: "cron",
208
+ description: "Explain cron expressions in plain English, generate cron from natural language, and compute next run times with code snippets",
209
+ pro: true,
210
+ inputSchema: {
211
+ type: "object",
212
+ properties: {
213
+ action: {
214
+ type: "string",
215
+ enum: ["explain", "generate", "next_runs"],
216
+ description: "explain: parse and describe a cron expression | generate: natural language to cron | next_runs: compute upcoming run times",
217
+ },
218
+ expression: {
219
+ type: "string",
220
+ description: "(explain, next_runs) A 5-field cron expression like '*/5 * * * *'",
221
+ },
222
+ text: {
223
+ type: "string",
224
+ description: '(generate) Natural language schedule like "every 5 minutes" or "every monday at 9am"',
225
+ },
226
+ count: {
227
+ type: "number",
228
+ description: "(next_runs) Number of upcoming runs to compute (default: 5)",
229
+ },
230
+ },
231
+ required: ["action"],
232
+ },
233
+ handler: async (args) => {
234
+ const action = args.action;
235
+ if (action === "explain") {
236
+ const expression = args.expression;
237
+ if (!expression)
238
+ throw new Error("expression is required for explain action");
239
+ const parts = expression.split(/\s+/);
240
+ if (parts.length !== 5)
241
+ throw new Error("Invalid cron expression: expected 5 space-separated fields");
242
+ const ranges = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 6]];
243
+ const fields = parts.map((p, i) => parseField(p, ranges[i][0], ranges[i][1]));
244
+ const sections = [
245
+ `=== Cron: ${expression} ===`,
246
+ "",
247
+ "--- Fields ---",
248
+ ...fields.map((f, i) => ` ${parts[i].padEnd(6)} ${describeField(f, i)}`),
249
+ "",
250
+ ];
251
+ // Next 3 runs
252
+ const nextRuns = getNextRuns(expression, 3);
253
+ sections.push("--- Next 3 Runs ---");
254
+ for (const run of nextRuns) {
255
+ sections.push(` ${run.toISOString()}`);
256
+ }
257
+ sections.push("");
258
+ sections.push("--- Code ---");
259
+ sections.push(generateCronCode(expression));
260
+ return sections.join("\n");
261
+ }
262
+ if (action === "generate") {
263
+ const text = args.text;
264
+ if (!text)
265
+ throw new Error("text is required for generate action");
266
+ const expression = naturalToCron(text);
267
+ const parts = expression.split(/\s+/);
268
+ const ranges = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 6]];
269
+ const fields = parts.map((p, i) => parseField(p, ranges[i][0], ranges[i][1]));
270
+ const sections = [
271
+ `=== Generated Cron ===`,
272
+ "",
273
+ `Input: "${text}"`,
274
+ `Expression: ${expression}`,
275
+ "",
276
+ "--- Fields ---",
277
+ ...fields.map((f, i) => ` ${parts[i].padEnd(6)} ${describeField(f, i)}`),
278
+ "",
279
+ ];
280
+ const nextRuns = getNextRuns(expression, 3);
281
+ sections.push("--- Next 3 Runs ---");
282
+ for (const run of nextRuns) {
283
+ sections.push(` ${run.toISOString()}`);
284
+ }
285
+ sections.push("");
286
+ sections.push("--- Code ---");
287
+ sections.push(generateCronCode(expression));
288
+ return sections.join("\n");
289
+ }
290
+ if (action === "next_runs") {
291
+ const expression = args.expression;
292
+ if (!expression)
293
+ throw new Error("expression is required for next_runs action");
294
+ const count = args.count || 5;
295
+ const runs = getNextRuns(expression, count);
296
+ const sections = [
297
+ `=== Next ${count} Runs for: ${expression} ===`,
298
+ "",
299
+ ];
300
+ for (let i = 0; i < runs.length; i++) {
301
+ sections.push(` ${i + 1}. ${runs[i].toISOString()}`);
302
+ }
303
+ if (runs.length < count) {
304
+ sections.push("");
305
+ sections.push(`(Only found ${runs.length} matching times within search window)`);
306
+ }
307
+ return sections.join("\n");
308
+ }
309
+ throw new Error(`Unknown action: ${action}. Use "explain", "generate", or "next_runs".`);
310
+ },
311
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,25 @@
1
+ import { createHash } from "node:crypto";
2
+ import { registerTool } from "../registry.js";
3
+ registerTool({
4
+ name: "hash_text",
5
+ description: "Hash text with MD5, SHA-256, or SHA-512",
6
+ pro: false,
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: {
10
+ text: { type: "string", description: "Text to hash" },
11
+ algorithm: {
12
+ type: "string",
13
+ enum: ["md5", "sha256", "sha512"],
14
+ description: "Hash algorithm (default: sha256)",
15
+ },
16
+ },
17
+ required: ["text"],
18
+ },
19
+ handler: async (args) => {
20
+ const text = args.text;
21
+ const algorithm = args.algorithm || "sha256";
22
+ const digest = createHash(algorithm).update(text, "utf-8").digest("hex");
23
+ return `${algorithm}: ${digest}`;
24
+ },
25
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,59 @@
1
+ import { registerTool } from "../registry.js";
2
+ const STATUS_CODES = {
3
+ 100: ["Continue", "Informational"],
4
+ 101: ["Switching Protocols", "Informational"],
5
+ 200: ["OK", "Success"],
6
+ 201: ["Created", "Success"],
7
+ 202: ["Accepted", "Success"],
8
+ 204: ["No Content", "Success"],
9
+ 206: ["Partial Content", "Success"],
10
+ 301: ["Moved Permanently", "Redirection"],
11
+ 302: ["Found", "Redirection"],
12
+ 303: ["See Other", "Redirection"],
13
+ 304: ["Not Modified", "Redirection"],
14
+ 307: ["Temporary Redirect", "Redirection"],
15
+ 308: ["Permanent Redirect", "Redirection"],
16
+ 400: ["Bad Request", "Client Error"],
17
+ 401: ["Unauthorized", "Client Error"],
18
+ 403: ["Forbidden", "Client Error"],
19
+ 404: ["Not Found", "Client Error"],
20
+ 405: ["Method Not Allowed", "Client Error"],
21
+ 406: ["Not Acceptable", "Client Error"],
22
+ 408: ["Request Timeout", "Client Error"],
23
+ 409: ["Conflict", "Client Error"],
24
+ 410: ["Gone", "Client Error"],
25
+ 411: ["Length Required", "Client Error"],
26
+ 412: ["Precondition Failed", "Client Error"],
27
+ 413: ["Payload Too Large", "Client Error"],
28
+ 414: ["URI Too Long", "Client Error"],
29
+ 415: ["Unsupported Media Type", "Client Error"],
30
+ 418: ["I'm a Teapot", "Client Error"],
31
+ 422: ["Unprocessable Entity", "Client Error"],
32
+ 429: ["Too Many Requests", "Client Error"],
33
+ 500: ["Internal Server Error", "Server Error"],
34
+ 501: ["Not Implemented", "Server Error"],
35
+ 502: ["Bad Gateway", "Server Error"],
36
+ 503: ["Service Unavailable", "Server Error"],
37
+ 504: ["Gateway Timeout", "Server Error"],
38
+ };
39
+ registerTool({
40
+ name: "http_status",
41
+ description: "Look up HTTP status code name and category",
42
+ pro: false,
43
+ inputSchema: {
44
+ type: "object",
45
+ properties: {
46
+ code: { type: "number", description: "HTTP status code (e.g. 404)" },
47
+ },
48
+ required: ["code"],
49
+ },
50
+ handler: async (args) => {
51
+ const code = args.code;
52
+ const entry = STATUS_CODES[code];
53
+ if (!entry) {
54
+ return `Unknown HTTP status code: ${code}`;
55
+ }
56
+ const [name, category] = entry;
57
+ return `${code} ${name} (${category})`;
58
+ },
59
+ });
@@ -0,0 +1 @@
1
+ export {};