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.
Files changed (51) hide show
  1. package/README.md +2 -2
  2. package/dist/auth.js +7 -6
  3. package/dist/index.js +1 -1
  4. package/docs/index.html +495 -0
  5. package/landing/index.html +495 -0
  6. package/package.json +1 -1
  7. package/dist/auth.d.ts +0 -3
  8. package/dist/codegen/cron-codegen.d.ts +0 -1
  9. package/dist/codegen/regex-codegen.d.ts +0 -1
  10. package/dist/index.d.ts +0 -22
  11. package/dist/registry.d.ts +0 -10
  12. package/dist/tools/base64.d.ts +0 -1
  13. package/dist/tools/batch.d.ts +0 -1
  14. package/dist/tools/chmod.d.ts +0 -1
  15. package/dist/tools/cron.d.ts +0 -1
  16. package/dist/tools/hash.d.ts +0 -1
  17. package/dist/tools/http-status.d.ts +0 -1
  18. package/dist/tools/json-diff.d.ts +0 -1
  19. package/dist/tools/json-format.d.ts +0 -1
  20. package/dist/tools/json-query.d.ts +0 -1
  21. package/dist/tools/jwt.d.ts +0 -1
  22. package/dist/tools/regex.d.ts +0 -1
  23. package/dist/tools/secrets-scan.d.ts +0 -1
  24. package/dist/tools/sql-format.d.ts +0 -1
  25. package/dist/tools/timestamp.d.ts +0 -1
  26. package/dist/tools/url-encode.d.ts +0 -1
  27. package/dist/tools/uuid.d.ts +0 -1
  28. package/dist/tools/yaml-convert.d.ts +0 -1
  29. package/src/auth.ts +0 -99
  30. package/src/codegen/cron-codegen.ts +0 -66
  31. package/src/codegen/regex-codegen.ts +0 -132
  32. package/src/index.ts +0 -134
  33. package/src/registry.ts +0 -25
  34. package/src/tools/base64.ts +0 -32
  35. package/src/tools/batch.ts +0 -69
  36. package/src/tools/chmod.ts +0 -133
  37. package/src/tools/cron.ts +0 -365
  38. package/src/tools/hash.ts +0 -26
  39. package/src/tools/http-status.ts +0 -63
  40. package/src/tools/json-diff.ts +0 -153
  41. package/src/tools/json-format.ts +0 -43
  42. package/src/tools/json-query.ts +0 -126
  43. package/src/tools/jwt.ts +0 -193
  44. package/src/tools/regex.ts +0 -131
  45. package/src/tools/secrets-scan.ts +0 -212
  46. package/src/tools/sql-format.ts +0 -178
  47. package/src/tools/timestamp.ts +0 -74
  48. package/src/tools/url-encode.ts +0 -29
  49. package/src/tools/uuid.ts +0 -25
  50. package/src/tools/yaml-convert.ts +0 -383
  51. package/tsconfig.json +0 -14
package/src/tools/cron.ts DELETED
@@ -1,365 +0,0 @@
1
- import { registerTool } from "../registry.js";
2
- import { generateCronCode } from "../codegen/cron-codegen.js";
3
-
4
- // ---------------------------------------------------------------------------
5
- // Cron expression explainer, generator, and next-run calculator
6
- // ---------------------------------------------------------------------------
7
-
8
- const FIELD_NAMES = ["minute", "hour", "day of month", "month", "day of week"] as const;
9
-
10
- const MONTH_NAMES = [
11
- "", "January", "February", "March", "April", "May", "June",
12
- "July", "August", "September", "October", "November", "December",
13
- ];
14
-
15
- const DOW_NAMES = ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"];
16
-
17
- // ---------------------------------------------------------------------------
18
- // Parsing helpers
19
- // ---------------------------------------------------------------------------
20
-
21
- interface CronField {
22
- type: "any" | "value" | "range" | "list" | "step";
23
- raw: string;
24
- values?: number[];
25
- step?: number;
26
- rangeStart?: number;
27
- rangeEnd?: number;
28
- }
29
-
30
- function parseField(raw: string, min: number, max: number): CronField {
31
- if (raw === "*") return { type: "any", raw };
32
-
33
- // Step: */N or N-M/S
34
- if (raw.includes("/")) {
35
- const [base, stepStr] = raw.split("/");
36
- const step = parseInt(stepStr, 10);
37
- if (base === "*") {
38
- return { type: "step", raw, step, rangeStart: min, rangeEnd: max };
39
- }
40
- if (base.includes("-")) {
41
- const [s, e] = base.split("-").map(Number);
42
- return { type: "step", raw, step, rangeStart: s, rangeEnd: e };
43
- }
44
- return { type: "step", raw, step, rangeStart: parseInt(base, 10), rangeEnd: max };
45
- }
46
-
47
- // Range: N-M
48
- if (raw.includes("-") && !raw.includes(",")) {
49
- const [s, e] = raw.split("-").map(Number);
50
- return { type: "range", raw, rangeStart: s, rangeEnd: e };
51
- }
52
-
53
- // List: N,M,O
54
- if (raw.includes(",")) {
55
- const values = raw.split(",").map(Number);
56
- return { type: "list", raw, values };
57
- }
58
-
59
- // Single value
60
- return { type: "value", raw, values: [parseInt(raw, 10)] };
61
- }
62
-
63
- function expandField(field: CronField, min: number, max: number): number[] {
64
- switch (field.type) {
65
- case "any": {
66
- const vals: number[] = [];
67
- for (let i = min; i <= max; i++) vals.push(i);
68
- return vals;
69
- }
70
- case "value":
71
- case "list":
72
- return field.values!;
73
- case "range": {
74
- const vals: number[] = [];
75
- for (let i = field.rangeStart!; i <= field.rangeEnd!; i++) vals.push(i);
76
- return vals;
77
- }
78
- case "step": {
79
- const vals: number[] = [];
80
- for (let i = field.rangeStart!; i <= field.rangeEnd!; i += field.step!) vals.push(i);
81
- return vals;
82
- }
83
- }
84
- }
85
-
86
- function describeField(field: CronField, index: number): string {
87
- const name = FIELD_NAMES[index];
88
- switch (field.type) {
89
- case "any":
90
- return `${name}: every ${name}`;
91
- case "value": {
92
- const v = field.values![0];
93
- if (index === 3) return `${name}: ${MONTH_NAMES[v] || v}`;
94
- if (index === 4) return `${name}: ${DOW_NAMES[v] || v}`;
95
- return `${name}: ${v}`;
96
- }
97
- case "list": {
98
- const labels = field.values!.map((v) => {
99
- if (index === 3) return MONTH_NAMES[v] || String(v);
100
- if (index === 4) return DOW_NAMES[v] || String(v);
101
- return String(v);
102
- });
103
- return `${name}: ${labels.join(", ")}`;
104
- }
105
- case "range":
106
- return `${name}: ${field.rangeStart} through ${field.rangeEnd}`;
107
- case "step":
108
- return `${name}: every ${field.step} starting at ${field.rangeStart}`;
109
- }
110
- }
111
-
112
- // ---------------------------------------------------------------------------
113
- // Next run calculator
114
- // ---------------------------------------------------------------------------
115
-
116
- function fieldMatches(value: number, allowed: number[]): boolean {
117
- return allowed.includes(value);
118
- }
119
-
120
- function getNextRuns(expression: string, count: number, fromDate?: Date): Date[] {
121
- const parts = expression.split(/\s+/);
122
- if (parts.length !== 5) throw new Error("Invalid cron: expected 5 fields");
123
-
124
- const ranges: [number, number][] = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 6]];
125
- const allowedSets = parts.map((p, i) => {
126
- const field = parseField(p, ranges[i][0], ranges[i][1]);
127
- return expandField(field, ranges[i][0], ranges[i][1]);
128
- });
129
-
130
- const [allowedMin, allowedHour, allowedDom, allowedMonth, allowedDow] = allowedSets;
131
-
132
- const results: Date[] = [];
133
- const start = fromDate ? new Date(fromDate) : new Date();
134
- // Round up to next minute
135
- start.setSeconds(0, 0);
136
- start.setMinutes(start.getMinutes() + 1);
137
-
138
- const maxIterations = count * 1440 * 32; // generous limit
139
- const current = new Date(start);
140
-
141
- for (let i = 0; i < maxIterations && results.length < count; i++) {
142
- const min = current.getMinutes();
143
- const hour = current.getHours();
144
- const dom = current.getDate();
145
- const month = current.getMonth() + 1;
146
- const dow = current.getDay();
147
-
148
- if (
149
- fieldMatches(min, allowedMin) &&
150
- fieldMatches(hour, allowedHour) &&
151
- fieldMatches(dom, allowedDom) &&
152
- fieldMatches(month, allowedMonth) &&
153
- fieldMatches(dow, allowedDow)
154
- ) {
155
- results.push(new Date(current));
156
- }
157
-
158
- current.setMinutes(current.getMinutes() + 1);
159
- }
160
-
161
- return results;
162
- }
163
-
164
- // ---------------------------------------------------------------------------
165
- // Natural language -> cron
166
- // ---------------------------------------------------------------------------
167
-
168
- function naturalToCron(text: string): string {
169
- const lower = text.toLowerCase().trim();
170
-
171
- // "every N minutes"
172
- const everyMinMatch = lower.match(/every\s+(\d+)\s+minutes?/);
173
- if (everyMinMatch) return `*/${everyMinMatch[1]} * * * *`;
174
-
175
- // "every N hours"
176
- const everyHourMatch = lower.match(/every\s+(\d+)\s+hours?/);
177
- if (everyHourMatch) return `0 */${everyHourMatch[1]} * * *`;
178
-
179
- // "every minute"
180
- if (/every\s+minute/.test(lower)) return "* * * * *";
181
-
182
- // "every hour"
183
- if (/every\s+hour/.test(lower)) return "0 * * * *";
184
-
185
- // "midnight"
186
- if (/midnight/.test(lower)) return "0 0 * * *";
187
-
188
- // "noon"
189
- if (/noon/.test(lower)) return "0 12 * * *";
190
-
191
- // "every day at Xam/pm" or "daily at X"
192
- const dailyMatch = lower.match(/(?:every\s+day|daily)\s+at\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?/);
193
- if (dailyMatch) {
194
- let hour = parseInt(dailyMatch[1], 10);
195
- const min = dailyMatch[2] ? parseInt(dailyMatch[2], 10) : 0;
196
- const ampm = dailyMatch[3];
197
- if (ampm === "pm" && hour < 12) hour += 12;
198
- if (ampm === "am" && hour === 12) hour = 0;
199
- return `${min} ${hour} * * *`;
200
- }
201
-
202
- // "every weekday at X"
203
- const weekdayMatch = lower.match(/every\s+weekday\s+at\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?/);
204
- if (weekdayMatch) {
205
- let hour = parseInt(weekdayMatch[1], 10);
206
- const min = weekdayMatch[2] ? parseInt(weekdayMatch[2], 10) : 0;
207
- const ampm = weekdayMatch[3];
208
- if (ampm === "pm" && hour < 12) hour += 12;
209
- if (ampm === "am" && hour === 12) hour = 0;
210
- return `${min} ${hour} * * 1-5`;
211
- }
212
-
213
- // "every monday/tuesday/..." with optional "at X"
214
- const dayNames: Record<string, number> = {
215
- sunday: 0, monday: 1, tuesday: 2, wednesday: 3,
216
- thursday: 4, friday: 5, saturday: 6,
217
- };
218
- const dayMatch = lower.match(
219
- /every\s+(sunday|monday|tuesday|wednesday|thursday|friday|saturday)(?:\s+at\s+(\d{1,2})(?::(\d{2}))?\s*(am|pm)?)?/,
220
- );
221
- if (dayMatch) {
222
- const dow = dayNames[dayMatch[1]];
223
- let hour = dayMatch[2] ? parseInt(dayMatch[2], 10) : 0;
224
- const min = dayMatch[3] ? parseInt(dayMatch[3], 10) : 0;
225
- const ampm = dayMatch[4];
226
- if (ampm === "pm" && hour < 12) hour += 12;
227
- if (ampm === "am" && hour === 12) hour = 0;
228
- return `${min} ${hour} * * ${dow}`;
229
- }
230
-
231
- throw new Error(
232
- `Could not parse: "${text}". Try formats like "every 5 minutes", "every day at 3pm", "every monday at 9am", "midnight", "noon"`,
233
- );
234
- }
235
-
236
- // ---------------------------------------------------------------------------
237
- // Tool registration
238
- // ---------------------------------------------------------------------------
239
-
240
- registerTool({
241
- name: "cron",
242
- description:
243
- "Explain cron expressions in plain English, generate cron from natural language, and compute next run times with code snippets",
244
- pro: true,
245
- inputSchema: {
246
- type: "object",
247
- properties: {
248
- action: {
249
- type: "string",
250
- enum: ["explain", "generate", "next_runs"],
251
- description:
252
- "explain: parse and describe a cron expression | generate: natural language to cron | next_runs: compute upcoming run times",
253
- },
254
- expression: {
255
- type: "string",
256
- description: "(explain, next_runs) A 5-field cron expression like '*/5 * * * *'",
257
- },
258
- text: {
259
- type: "string",
260
- description: '(generate) Natural language schedule like "every 5 minutes" or "every monday at 9am"',
261
- },
262
- count: {
263
- type: "number",
264
- description: "(next_runs) Number of upcoming runs to compute (default: 5)",
265
- },
266
- },
267
- required: ["action"],
268
- },
269
- handler: async (args) => {
270
- const action = args.action as string;
271
-
272
- if (action === "explain") {
273
- const expression = args.expression as string | undefined;
274
- if (!expression) throw new Error("expression is required for explain action");
275
-
276
- const parts = expression.split(/\s+/);
277
- if (parts.length !== 5)
278
- throw new Error("Invalid cron expression: expected 5 space-separated fields");
279
-
280
- const ranges: [number, number][] = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 6]];
281
- const fields = parts.map((p, i) => parseField(p, ranges[i][0], ranges[i][1]));
282
-
283
- const sections: string[] = [
284
- `=== Cron: ${expression} ===`,
285
- "",
286
- "--- Fields ---",
287
- ...fields.map((f, i) => ` ${parts[i].padEnd(6)} ${describeField(f, i)}`),
288
- "",
289
- ];
290
-
291
- // Next 3 runs
292
- const nextRuns = getNextRuns(expression, 3);
293
- sections.push("--- Next 3 Runs ---");
294
- for (const run of nextRuns) {
295
- sections.push(` ${run.toISOString()}`);
296
- }
297
-
298
- sections.push("");
299
- sections.push("--- Code ---");
300
- sections.push(generateCronCode(expression));
301
-
302
- return sections.join("\n");
303
- }
304
-
305
- if (action === "generate") {
306
- const text = args.text as string | undefined;
307
- if (!text) throw new Error("text is required for generate action");
308
-
309
- const expression = naturalToCron(text);
310
-
311
- const parts = expression.split(/\s+/);
312
- const ranges: [number, number][] = [[0, 59], [0, 23], [1, 31], [1, 12], [0, 6]];
313
- const fields = parts.map((p, i) => parseField(p, ranges[i][0], ranges[i][1]));
314
-
315
- const sections: string[] = [
316
- `=== Generated Cron ===`,
317
- "",
318
- `Input: "${text}"`,
319
- `Expression: ${expression}`,
320
- "",
321
- "--- Fields ---",
322
- ...fields.map((f, i) => ` ${parts[i].padEnd(6)} ${describeField(f, i)}`),
323
- "",
324
- ];
325
-
326
- const nextRuns = getNextRuns(expression, 3);
327
- sections.push("--- Next 3 Runs ---");
328
- for (const run of nextRuns) {
329
- sections.push(` ${run.toISOString()}`);
330
- }
331
-
332
- sections.push("");
333
- sections.push("--- Code ---");
334
- sections.push(generateCronCode(expression));
335
-
336
- return sections.join("\n");
337
- }
338
-
339
- if (action === "next_runs") {
340
- const expression = args.expression as string | undefined;
341
- if (!expression) throw new Error("expression is required for next_runs action");
342
-
343
- const count = (args.count as number) || 5;
344
- const runs = getNextRuns(expression, count);
345
-
346
- const sections: string[] = [
347
- `=== Next ${count} Runs for: ${expression} ===`,
348
- "",
349
- ];
350
-
351
- for (let i = 0; i < runs.length; i++) {
352
- sections.push(` ${i + 1}. ${runs[i].toISOString()}`);
353
- }
354
-
355
- if (runs.length < count) {
356
- sections.push("");
357
- sections.push(`(Only found ${runs.length} matching times within search window)`);
358
- }
359
-
360
- return sections.join("\n");
361
- }
362
-
363
- throw new Error(`Unknown action: ${action}. Use "explain", "generate", or "next_runs".`);
364
- },
365
- });
package/src/tools/hash.ts DELETED
@@ -1,26 +0,0 @@
1
- import { createHash } from "node:crypto";
2
- import { registerTool } from "../registry.js";
3
-
4
- registerTool({
5
- name: "hash_text",
6
- description: "Hash text with MD5, SHA-256, or SHA-512",
7
- pro: false,
8
- inputSchema: {
9
- type: "object",
10
- properties: {
11
- text: { type: "string", description: "Text to hash" },
12
- algorithm: {
13
- type: "string",
14
- enum: ["md5", "sha256", "sha512"],
15
- description: "Hash algorithm (default: sha256)",
16
- },
17
- },
18
- required: ["text"],
19
- },
20
- handler: async (args) => {
21
- const text = args.text as string;
22
- const algorithm = (args.algorithm as string) || "sha256";
23
- const digest = createHash(algorithm).update(text, "utf-8").digest("hex");
24
- return `${algorithm}: ${digest}`;
25
- },
26
- });
@@ -1,63 +0,0 @@
1
- import { registerTool } from "../registry.js";
2
-
3
- const STATUS_CODES: Record<number, [string, string]> = {
4
- 100: ["Continue", "Informational"],
5
- 101: ["Switching Protocols", "Informational"],
6
- 200: ["OK", "Success"],
7
- 201: ["Created", "Success"],
8
- 202: ["Accepted", "Success"],
9
- 204: ["No Content", "Success"],
10
- 206: ["Partial Content", "Success"],
11
- 301: ["Moved Permanently", "Redirection"],
12
- 302: ["Found", "Redirection"],
13
- 303: ["See Other", "Redirection"],
14
- 304: ["Not Modified", "Redirection"],
15
- 307: ["Temporary Redirect", "Redirection"],
16
- 308: ["Permanent Redirect", "Redirection"],
17
- 400: ["Bad Request", "Client Error"],
18
- 401: ["Unauthorized", "Client Error"],
19
- 403: ["Forbidden", "Client Error"],
20
- 404: ["Not Found", "Client Error"],
21
- 405: ["Method Not Allowed", "Client Error"],
22
- 406: ["Not Acceptable", "Client Error"],
23
- 408: ["Request Timeout", "Client Error"],
24
- 409: ["Conflict", "Client Error"],
25
- 410: ["Gone", "Client Error"],
26
- 411: ["Length Required", "Client Error"],
27
- 412: ["Precondition Failed", "Client Error"],
28
- 413: ["Payload Too Large", "Client Error"],
29
- 414: ["URI Too Long", "Client Error"],
30
- 415: ["Unsupported Media Type", "Client Error"],
31
- 418: ["I'm a Teapot", "Client Error"],
32
- 422: ["Unprocessable Entity", "Client Error"],
33
- 429: ["Too Many Requests", "Client Error"],
34
- 500: ["Internal Server Error", "Server Error"],
35
- 501: ["Not Implemented", "Server Error"],
36
- 502: ["Bad Gateway", "Server Error"],
37
- 503: ["Service Unavailable", "Server Error"],
38
- 504: ["Gateway Timeout", "Server Error"],
39
- };
40
-
41
- registerTool({
42
- name: "http_status",
43
- description: "Look up HTTP status code name and category",
44
- pro: false,
45
- inputSchema: {
46
- type: "object",
47
- properties: {
48
- code: { type: "number", description: "HTTP status code (e.g. 404)" },
49
- },
50
- required: ["code"],
51
- },
52
- handler: async (args) => {
53
- const code = args.code as number;
54
- const entry = STATUS_CODES[code];
55
-
56
- if (!entry) {
57
- return `Unknown HTTP status code: ${code}`;
58
- }
59
-
60
- const [name, category] = entry;
61
- return `${code} ${name} (${category})`;
62
- },
63
- });
@@ -1,153 +0,0 @@
1
- import { registerTool } from "../registry.js";
2
-
3
- // ---------------------------------------------------------------------------
4
- // Deep JSON diff — compares two JSON values recursively
5
- // ---------------------------------------------------------------------------
6
-
7
- interface DiffEntry {
8
- path: string;
9
- type: "added" | "removed" | "changed";
10
- oldValue?: unknown;
11
- newValue?: unknown;
12
- }
13
-
14
- function deepDiff(a: unknown, b: unknown, path: string, results: DiffEntry[]): void {
15
- if (a === b) return;
16
-
17
- // Both null/undefined
18
- if (a == null && b == null) return;
19
-
20
- // Type mismatch or primitive change
21
- if (
22
- typeof a !== typeof b ||
23
- a === null ||
24
- b === null ||
25
- typeof a !== "object" ||
26
- typeof b !== "object"
27
- ) {
28
- results.push({ path: path || "$", type: "changed", oldValue: a, newValue: b });
29
- return;
30
- }
31
-
32
- const aIsArray = Array.isArray(a);
33
- const bIsArray = Array.isArray(b);
34
-
35
- // Array vs object mismatch
36
- if (aIsArray !== bIsArray) {
37
- results.push({ path: path || "$", type: "changed", oldValue: a, newValue: b });
38
- return;
39
- }
40
-
41
- if (aIsArray && bIsArray) {
42
- const maxLen = Math.max(a.length, b.length);
43
- for (let i = 0; i < maxLen; i++) {
44
- const itemPath = `${path}[${i}]`;
45
- if (i >= a.length) {
46
- results.push({ path: itemPath, type: "added", newValue: b[i] });
47
- } else if (i >= b.length) {
48
- results.push({ path: itemPath, type: "removed", oldValue: a[i] });
49
- } else {
50
- deepDiff(a[i], b[i], itemPath, results);
51
- }
52
- }
53
- return;
54
- }
55
-
56
- // Both objects
57
- const aObj = a as Record<string, unknown>;
58
- const bObj = b as Record<string, unknown>;
59
- const allKeys = new Set([...Object.keys(aObj), ...Object.keys(bObj)]);
60
-
61
- for (const key of allKeys) {
62
- const keyPath = path ? `${path}.${key}` : key;
63
- if (!(key in aObj)) {
64
- results.push({ path: keyPath, type: "added", newValue: bObj[key] });
65
- } else if (!(key in bObj)) {
66
- results.push({ path: keyPath, type: "removed", oldValue: aObj[key] });
67
- } else {
68
- deepDiff(aObj[key], bObj[key], keyPath, results);
69
- }
70
- }
71
- }
72
-
73
- function formatValue(val: unknown): string {
74
- if (typeof val === "string") return JSON.stringify(val);
75
- if (val === undefined) return "undefined";
76
- return JSON.stringify(val);
77
- }
78
-
79
- // ---------------------------------------------------------------------------
80
- // Tool registration
81
- // ---------------------------------------------------------------------------
82
-
83
- registerTool({
84
- name: "json_diff",
85
- description:
86
- "Deep-compare two JSON objects and show all differences — added, removed, and changed values with paths",
87
- pro: true,
88
- inputSchema: {
89
- type: "object",
90
- properties: {
91
- a: {
92
- type: "string",
93
- description: "First JSON string",
94
- },
95
- b: {
96
- type: "string",
97
- description: "Second JSON string",
98
- },
99
- },
100
- required: ["a", "b"],
101
- },
102
- handler: async (args) => {
103
- const aStr = args.a as string;
104
- const bStr = args.b as string;
105
-
106
- let aVal: unknown;
107
- let bVal: unknown;
108
- try {
109
- aVal = JSON.parse(aStr);
110
- } catch {
111
- throw new Error("Invalid JSON in 'a'");
112
- }
113
- try {
114
- bVal = JSON.parse(bStr);
115
- } catch {
116
- throw new Error("Invalid JSON in 'b'");
117
- }
118
-
119
- const diffs: DiffEntry[] = [];
120
- deepDiff(aVal, bVal, "", diffs);
121
-
122
- if (diffs.length === 0) {
123
- return "No differences — the two JSON values are identical.";
124
- }
125
-
126
- const added = diffs.filter((d) => d.type === "added").length;
127
- const removed = diffs.filter((d) => d.type === "removed").length;
128
- const changed = diffs.filter((d) => d.type === "changed").length;
129
-
130
- const lines: string[] = [
131
- `=== JSON Diff: ${diffs.length} difference${diffs.length === 1 ? "" : "s"} ===`,
132
- `${added} added, ${removed} removed, ${changed} changed`,
133
- "",
134
- ];
135
-
136
- for (const diff of diffs) {
137
- const p = diff.path || "$";
138
- switch (diff.type) {
139
- case "added":
140
- lines.push(`+ ${p}: ${formatValue(diff.newValue)}`);
141
- break;
142
- case "removed":
143
- lines.push(`- ${p}: ${formatValue(diff.oldValue)}`);
144
- break;
145
- case "changed":
146
- lines.push(`~ ${p}: ${formatValue(diff.oldValue)} → ${formatValue(diff.newValue)}`);
147
- break;
148
- }
149
- }
150
-
151
- return lines.join("\n");
152
- },
153
- });
@@ -1,43 +0,0 @@
1
- import { registerTool } from "../registry.js";
2
-
3
- registerTool({
4
- name: "json_format",
5
- description: "Format, minify, or validate JSON strings",
6
- pro: false,
7
- inputSchema: {
8
- type: "object",
9
- properties: {
10
- text: { type: "string", description: "JSON string to process" },
11
- action: {
12
- type: "string",
13
- enum: ["format", "minify", "validate"],
14
- description: "Action to perform (default: format)",
15
- },
16
- indent: {
17
- type: "number",
18
- description: "Number of spaces for indentation (default: 2)",
19
- },
20
- },
21
- required: ["text"],
22
- },
23
- handler: async (args) => {
24
- const text = args.text as string;
25
- const action = (args.action as string) || "format";
26
- const indent = (args.indent as number) || 2;
27
-
28
- const parsed = JSON.parse(text);
29
-
30
- if (action === "validate") {
31
- const type = Array.isArray(parsed)
32
- ? `array[${parsed.length}]`
33
- : `object{${Object.keys(parsed).length} keys}`;
34
- return `Valid JSON: ${type}`;
35
- }
36
-
37
- if (action === "minify") {
38
- return JSON.stringify(parsed);
39
- }
40
-
41
- return JSON.stringify(parsed, null, indent);
42
- },
43
- });