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,178 @@
1
+ import { registerTool } from "../registry.js";
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // SQL Formatter — normalize whitespace, add newlines before major clauses,
5
+ // indent columns and conditions
6
+ // ---------------------------------------------------------------------------
7
+
8
+ const MAJOR_CLAUSES = [
9
+ "SELECT",
10
+ "FROM",
11
+ "WHERE",
12
+ "INNER JOIN",
13
+ "LEFT JOIN",
14
+ "RIGHT JOIN",
15
+ "FULL JOIN",
16
+ "CROSS JOIN",
17
+ "JOIN",
18
+ "ON",
19
+ "GROUP BY",
20
+ "ORDER BY",
21
+ "HAVING",
22
+ "LIMIT",
23
+ "OFFSET",
24
+ "INSERT INTO",
25
+ "VALUES",
26
+ "UPDATE",
27
+ "SET",
28
+ "DELETE FROM",
29
+ "WITH",
30
+ "UNION ALL",
31
+ "UNION",
32
+ "EXCEPT",
33
+ "INTERSECT",
34
+ ];
35
+
36
+ function formatSQL(sql: string, _dialect: string): string {
37
+ // Normalize whitespace: collapse runs of whitespace into single space
38
+ let normalized = sql.replace(/\s+/g, " ").trim();
39
+
40
+ // Uppercase major clauses and add newlines before them
41
+ // Sort clauses longest-first so multi-word clauses match before single-word
42
+ const sortedClauses = [...MAJOR_CLAUSES].sort(
43
+ (a, b) => b.length - a.length,
44
+ );
45
+
46
+ for (const clause of sortedClauses) {
47
+ const pattern = new RegExp(`\\b${clause}\\b`, "gi");
48
+ normalized = normalized.replace(pattern, `\n${clause}`);
49
+ }
50
+
51
+ // Clean up: remove leading newline
52
+ normalized = normalized.replace(/^\n/, "");
53
+
54
+ // Split into lines for indentation processing
55
+ const lines = normalized.split("\n").map((l) => l.trim());
56
+ const result: string[] = [];
57
+
58
+ for (const line of lines) {
59
+ if (!line) continue;
60
+
61
+ // Check if this line starts with a major clause
62
+ const upperLine = line.toUpperCase();
63
+ const startsWithClause = sortedClauses.some((c) =>
64
+ upperLine.startsWith(c),
65
+ );
66
+
67
+ if (startsWithClause) {
68
+ // Find where the clause keyword ends
69
+ const matchedClause = sortedClauses.find((c) =>
70
+ upperLine.startsWith(c),
71
+ );
72
+ if (matchedClause) {
73
+ const rest = line.slice(matchedClause.length).trim();
74
+
75
+ if (
76
+ matchedClause === "SELECT" ||
77
+ matchedClause === "GROUP BY" ||
78
+ matchedClause === "ORDER BY"
79
+ ) {
80
+ // Split comma-separated items onto separate indented lines
81
+ if (rest) {
82
+ const items = splitTopLevel(rest, ",");
83
+ if (items.length > 1) {
84
+ result.push(matchedClause);
85
+ for (let i = 0; i < items.length; i++) {
86
+ const comma = i < items.length - 1 ? "," : "";
87
+ result.push(` ${items[i].trim()}${comma}`);
88
+ }
89
+ continue;
90
+ }
91
+ }
92
+ }
93
+
94
+ if (matchedClause === "WHERE" || matchedClause === "HAVING") {
95
+ // Indent AND/OR conditions
96
+ if (rest) {
97
+ const withConditions = rest
98
+ .replace(/\b(AND)\b/gi, "\n AND")
99
+ .replace(/\b(OR)\b/gi, "\n OR");
100
+ const condLines = withConditions
101
+ .split("\n")
102
+ .map((l) => l.trim())
103
+ .filter(Boolean);
104
+ result.push(`${matchedClause}`);
105
+ for (const cl of condLines) {
106
+ result.push(` ${cl}`);
107
+ }
108
+ continue;
109
+ }
110
+ }
111
+
112
+ result.push(line);
113
+ } else {
114
+ result.push(line);
115
+ }
116
+ } else {
117
+ result.push(` ${line}`);
118
+ }
119
+ }
120
+
121
+ return result.join("\n");
122
+ }
123
+
124
+ /**
125
+ * Split a string by a delimiter, but only at the top level (not inside parens).
126
+ */
127
+ function splitTopLevel(s: string, delimiter: string): string[] {
128
+ const parts: string[] = [];
129
+ let depth = 0;
130
+ let current = "";
131
+
132
+ for (let i = 0; i < s.length; i++) {
133
+ const ch = s[i];
134
+ if (ch === "(") depth++;
135
+ else if (ch === ")") depth--;
136
+
137
+ if (ch === delimiter && depth === 0) {
138
+ parts.push(current);
139
+ current = "";
140
+ } else {
141
+ current += ch;
142
+ }
143
+ }
144
+ if (current.trim()) parts.push(current);
145
+ return parts;
146
+ }
147
+
148
+ registerTool({
149
+ name: "sql_format",
150
+ description: "Format and prettify SQL queries with proper indentation and clause separation",
151
+ pro: true,
152
+ inputSchema: {
153
+ type: "object",
154
+ properties: {
155
+ sql: { type: "string", description: "SQL query to format" },
156
+ dialect: {
157
+ type: "string",
158
+ enum: ["standard", "postgresql", "mysql", "sqlite"],
159
+ description: "SQL dialect (default: standard)",
160
+ },
161
+ },
162
+ required: ["sql"],
163
+ },
164
+ handler: async (args) => {
165
+ const sql = args.sql as string;
166
+ const dialect = (args.dialect as string) || "standard";
167
+
168
+ if (!sql.trim()) throw new Error("SQL string is empty");
169
+
170
+ const formatted = formatSQL(sql, dialect);
171
+
172
+ return [
173
+ `=== Formatted SQL (${dialect}) ===`,
174
+ "",
175
+ formatted,
176
+ ].join("\n");
177
+ },
178
+ });
@@ -0,0 +1,74 @@
1
+ import { registerTool } from "../registry.js";
2
+
3
+ function relativeTime(date: Date): string {
4
+ const now = Date.now();
5
+ const diffMs = now - date.getTime();
6
+ const absDiff = Math.abs(diffMs);
7
+ const suffix = diffMs >= 0 ? "ago" : "from now";
8
+
9
+ const seconds = Math.floor(absDiff / 1000);
10
+ if (seconds < 60) return `${seconds} seconds ${suffix}`;
11
+ const minutes = Math.floor(seconds / 60);
12
+ if (minutes < 60) return `${minutes} minutes ${suffix}`;
13
+ const hours = Math.floor(minutes / 60);
14
+ if (hours < 24) return `${hours} hours ${suffix}`;
15
+ const days = Math.floor(hours / 24);
16
+ if (days < 30) return `${days} days ${suffix}`;
17
+ const months = Math.floor(days / 30);
18
+ if (months < 12) return `${months} months ${suffix}`;
19
+ const years = Math.floor(days / 365);
20
+ return `${years} years ${suffix}`;
21
+ }
22
+
23
+ registerTool({
24
+ name: "timestamp",
25
+ description:
26
+ "Convert between timestamp formats: epoch seconds, epoch ms, ISO 8601, human-readable",
27
+ pro: false,
28
+ inputSchema: {
29
+ type: "object",
30
+ properties: {
31
+ value: {
32
+ type: "string",
33
+ description:
34
+ 'Timestamp value: "now", epoch seconds, epoch milliseconds, or ISO 8601 string',
35
+ },
36
+ },
37
+ required: ["value"],
38
+ },
39
+ handler: async (args) => {
40
+ const value = (args.value as string).trim();
41
+ let date: Date;
42
+
43
+ if (value.toLowerCase() === "now") {
44
+ date = new Date();
45
+ } else if (/^\d{10}$/.test(value)) {
46
+ // Epoch seconds
47
+ date = new Date(parseInt(value, 10) * 1000);
48
+ } else if (/^\d{13}$/.test(value)) {
49
+ // Epoch milliseconds
50
+ date = new Date(parseInt(value, 10));
51
+ } else if (value.includes("T") || value.includes("-")) {
52
+ date = new Date(value);
53
+ } else {
54
+ throw new Error(
55
+ `Cannot parse timestamp: "${value}". Use "now", epoch seconds (10 digits), epoch ms (13 digits), or ISO 8601.`
56
+ );
57
+ }
58
+
59
+ if (isNaN(date.getTime())) {
60
+ throw new Error(`Invalid date: "${value}"`);
61
+ }
62
+
63
+ const epochS = Math.floor(date.getTime() / 1000);
64
+ const epochMs = date.getTime();
65
+
66
+ return [
67
+ `epoch_seconds: ${epochS}`,
68
+ `epoch_ms: ${epochMs}`,
69
+ `iso8601: ${date.toISOString()}`,
70
+ `human: ${date.toUTCString()}`,
71
+ `relative: ${relativeTime(date)}`,
72
+ ].join("\n");
73
+ },
74
+ });
@@ -0,0 +1,29 @@
1
+ import { registerTool } from "../registry.js";
2
+
3
+ registerTool({
4
+ name: "url_encode",
5
+ description: "URL encode or decode strings",
6
+ pro: false,
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: {
10
+ text: { type: "string", description: "Text to encode or encoded string to decode" },
11
+ action: {
12
+ type: "string",
13
+ enum: ["encode", "decode"],
14
+ description: "encode or decode",
15
+ },
16
+ },
17
+ required: ["text", "action"],
18
+ },
19
+ handler: async (args) => {
20
+ const text = args.text as string;
21
+ const action = args.action as string;
22
+
23
+ if (action === "encode") {
24
+ return encodeURIComponent(text);
25
+ }
26
+
27
+ return decodeURIComponent(text);
28
+ },
29
+ });
@@ -0,0 +1,25 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { registerTool } from "../registry.js";
3
+
4
+ registerTool({
5
+ name: "uuid_generate",
6
+ description: "Generate one or more v4 UUIDs",
7
+ pro: false,
8
+ inputSchema: {
9
+ type: "object",
10
+ properties: {
11
+ count: {
12
+ type: "number",
13
+ description: "Number of UUIDs to generate (default: 1, max: 10)",
14
+ },
15
+ },
16
+ },
17
+ handler: async (args) => {
18
+ let count = Math.min(Math.max((args.count as number) || 1, 1), 10);
19
+ const uuids: string[] = [];
20
+ for (let i = 0; i < count; i++) {
21
+ uuids.push(randomUUID());
22
+ }
23
+ return uuids.join("\n");
24
+ },
25
+ });
@@ -0,0 +1,383 @@
1
+ import { registerTool } from "../registry.js";
2
+
3
+ // ---------------------------------------------------------------------------
4
+ // Minimal YAML parser
5
+ // ---------------------------------------------------------------------------
6
+
7
+ function parseYaml(text: string): unknown {
8
+ const lines = text.split("\n");
9
+ return parseYamlLines(lines, 0, 0).value;
10
+ }
11
+
12
+ interface ParseResult {
13
+ value: unknown;
14
+ nextLine: number;
15
+ }
16
+
17
+ function getIndent(line: string): number {
18
+ const match = line.match(/^(\s*)/);
19
+ return match ? match[1].length : 0;
20
+ }
21
+
22
+ function parseScalar(val: string): unknown {
23
+ const trimmed = val.trim();
24
+ if (trimmed === "" || trimmed === "null" || trimmed === "~") return null;
25
+ if (trimmed === "true") return true;
26
+ if (trimmed === "false") return false;
27
+ if (/^-?\d+$/.test(trimmed)) return parseInt(trimmed, 10);
28
+ if (/^-?\d+\.\d+$/.test(trimmed)) return parseFloat(trimmed);
29
+ // Strip quotes
30
+ if (
31
+ (trimmed.startsWith('"') && trimmed.endsWith('"')) ||
32
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))
33
+ ) {
34
+ return trimmed.slice(1, -1);
35
+ }
36
+ return trimmed;
37
+ }
38
+
39
+ function parseYamlLines(
40
+ lines: string[],
41
+ startLine: number,
42
+ baseIndent: number,
43
+ ): ParseResult {
44
+ // Skip empty lines and comments
45
+ let i = startLine;
46
+ while (i < lines.length && (lines[i].trim() === "" || lines[i].trim().startsWith("#"))) {
47
+ i++;
48
+ }
49
+ if (i >= lines.length) return { value: null, nextLine: i };
50
+
51
+ const line = lines[i];
52
+ const trimmed = line.trim();
53
+
54
+ // Check if it's a list item
55
+ if (trimmed.startsWith("- ")) {
56
+ const arr: unknown[] = [];
57
+ const listIndent = getIndent(line);
58
+ while (i < lines.length) {
59
+ const cur = lines[i];
60
+ if (cur.trim() === "" || cur.trim().startsWith("#")) { i++; continue; }
61
+ const curIndent = getIndent(cur);
62
+ if (curIndent < listIndent) break;
63
+ if (curIndent === listIndent && cur.trim().startsWith("- ")) {
64
+ const itemText = cur.trim().slice(2);
65
+ // Check if it's a key: value (nested map in list)
66
+ if (itemText.includes(": ")) {
67
+ const obj: Record<string, unknown> = {};
68
+ const colonIdx = itemText.indexOf(": ");
69
+ const key = itemText.slice(0, colonIdx).trim();
70
+ const val = itemText.slice(colonIdx + 2).trim();
71
+ obj[key] = parseScalar(val);
72
+ // Check for continuation lines at deeper indent
73
+ i++;
74
+ while (i < lines.length) {
75
+ const nextLine = lines[i];
76
+ if (nextLine.trim() === "" || nextLine.trim().startsWith("#")) { i++; continue; }
77
+ const nextIndent = getIndent(nextLine);
78
+ if (nextIndent <= listIndent) break;
79
+ if (nextLine.trim().includes(": ")) {
80
+ const ci = nextLine.trim().indexOf(": ");
81
+ const k = nextLine.trim().slice(0, ci).trim();
82
+ const v = nextLine.trim().slice(ci + 2).trim();
83
+ obj[k] = parseScalar(v);
84
+ i++;
85
+ } else {
86
+ break;
87
+ }
88
+ }
89
+ arr.push(obj);
90
+ } else {
91
+ arr.push(parseScalar(itemText));
92
+ i++;
93
+ }
94
+ } else {
95
+ break;
96
+ }
97
+ }
98
+ return { value: arr, nextLine: i };
99
+ }
100
+
101
+ // Check if it's a map (key: value)
102
+ if (trimmed.includes(": ") || trimmed.endsWith(":")) {
103
+ const obj: Record<string, unknown> = {};
104
+ const mapIndent = getIndent(line);
105
+ while (i < lines.length) {
106
+ const cur = lines[i];
107
+ if (cur.trim() === "" || cur.trim().startsWith("#")) { i++; continue; }
108
+ const curIndent = getIndent(cur);
109
+ if (curIndent < mapIndent && i > startLine) break;
110
+ if (curIndent !== mapIndent) break;
111
+ const curTrimmed = cur.trim();
112
+ if (curTrimmed.endsWith(":")) {
113
+ // Block value — next lines are the value
114
+ const key = curTrimmed.slice(0, -1).trim();
115
+ i++;
116
+ const result = parseYamlLines(lines, i, mapIndent + 2);
117
+ obj[key] = result.value;
118
+ i = result.nextLine;
119
+ } else if (curTrimmed.includes(": ")) {
120
+ const colonIdx = curTrimmed.indexOf(": ");
121
+ const key = curTrimmed.slice(0, colonIdx).trim();
122
+ const val = curTrimmed.slice(colonIdx + 2).trim();
123
+ obj[key] = parseScalar(val);
124
+ i++;
125
+ } else {
126
+ break;
127
+ }
128
+ }
129
+ return { value: obj, nextLine: i };
130
+ }
131
+
132
+ // Plain scalar
133
+ return { value: parseScalar(trimmed), nextLine: i + 1 };
134
+ }
135
+
136
+ // ---------------------------------------------------------------------------
137
+ // Minimal TOML parser
138
+ // ---------------------------------------------------------------------------
139
+
140
+ function parseToml(text: string): Record<string, unknown> {
141
+ const result: Record<string, unknown> = {};
142
+ let currentSection = result;
143
+ const lines = text.split("\n");
144
+
145
+ for (const line of lines) {
146
+ const trimmed = line.trim();
147
+ if (trimmed === "" || trimmed.startsWith("#")) continue;
148
+
149
+ // Section header [section]
150
+ const sectionMatch = trimmed.match(/^\[([^\]]+)\]$/);
151
+ if (sectionMatch) {
152
+ const sectionName = sectionMatch[1];
153
+ const parts = sectionName.split(".");
154
+ let target = result;
155
+ for (const part of parts) {
156
+ if (!(part in target)) {
157
+ target[part] = {};
158
+ }
159
+ target = target[part] as Record<string, unknown>;
160
+ }
161
+ currentSection = target;
162
+ continue;
163
+ }
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
+
174
+ return result;
175
+ }
176
+
177
+ function parseTomlValue(val: string): unknown {
178
+ if (val === "true") return true;
179
+ if (val === "false") return false;
180
+ if (/^-?\d+$/.test(val)) return parseInt(val, 10);
181
+ if (/^-?\d+\.\d+$/.test(val)) return parseFloat(val);
182
+ if (
183
+ (val.startsWith('"') && val.endsWith('"')) ||
184
+ (val.startsWith("'") && val.endsWith("'"))
185
+ ) {
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 === "") return [];
192
+ return inner.split(",").map((item) => parseTomlValue(item.trim()));
193
+ }
194
+ return val;
195
+ }
196
+
197
+ // ---------------------------------------------------------------------------
198
+ // Serializers
199
+ // ---------------------------------------------------------------------------
200
+
201
+ function toYaml(data: unknown, indent: number = 0): string {
202
+ const prefix = " ".repeat(indent);
203
+
204
+ if (data === null || data === undefined) return `${prefix}null\n`;
205
+ if (typeof data === "boolean") return `${prefix}${data}\n`;
206
+ if (typeof data === "number") return `${prefix}${data}\n`;
207
+ if (typeof data === "string") {
208
+ if (data.includes("\n") || data.includes(": ") || data.includes("#")) {
209
+ return `${prefix}"${data.replace(/"/g, '\\"')}"\n`;
210
+ }
211
+ return `${prefix}${data}\n`;
212
+ }
213
+
214
+ if (Array.isArray(data)) {
215
+ if (data.length === 0) return `${prefix}[]\n`;
216
+ let out = "";
217
+ for (const item of data) {
218
+ if (typeof item === "object" && item !== null && !Array.isArray(item)) {
219
+ const entries = Object.entries(item as Record<string, unknown>);
220
+ if (entries.length > 0) {
221
+ const [firstKey, firstVal] = entries[0];
222
+ out += `${prefix}- ${firstKey}: ${scalarToYaml(firstVal)}\n`;
223
+ for (let i = 1; i < entries.length; i++) {
224
+ out += `${prefix} ${entries[i][0]}: ${scalarToYaml(entries[i][1])}\n`;
225
+ }
226
+ continue;
227
+ }
228
+ }
229
+ out += `${prefix}- ${scalarToYaml(item)}\n`;
230
+ }
231
+ return out;
232
+ }
233
+
234
+ if (typeof data === "object") {
235
+ const obj = data as Record<string, unknown>;
236
+ const keys = Object.keys(obj);
237
+ if (keys.length === 0) return `${prefix}{}\n`;
238
+ let out = "";
239
+ for (const key of keys) {
240
+ const val = obj[key];
241
+ if (
242
+ typeof val === "object" &&
243
+ val !== null &&
244
+ !Array.isArray(val) &&
245
+ Object.keys(val).length > 0
246
+ ) {
247
+ out += `${prefix}${key}:\n`;
248
+ out += toYaml(val, indent + 1);
249
+ } else if (Array.isArray(val)) {
250
+ out += `${prefix}${key}:\n`;
251
+ out += toYaml(val, indent + 1);
252
+ } else {
253
+ out += `${prefix}${key}: ${scalarToYaml(val)}\n`;
254
+ }
255
+ }
256
+ return out;
257
+ }
258
+
259
+ return `${prefix}${String(data)}\n`;
260
+ }
261
+
262
+ function scalarToYaml(val: unknown): string {
263
+ if (val === null || val === undefined) return "null";
264
+ if (typeof val === "boolean") return String(val);
265
+ if (typeof val === "number") return String(val);
266
+ if (typeof val === "string") {
267
+ if (val.includes(": ") || val.includes("#") || val.includes("\n")) {
268
+ return `"${val.replace(/"/g, '\\"')}"`;
269
+ }
270
+ return val;
271
+ }
272
+ return JSON.stringify(val);
273
+ }
274
+
275
+ function toToml(data: unknown, sectionPath: string = ""): string {
276
+ if (typeof data !== "object" || data === null || Array.isArray(data)) {
277
+ return String(data);
278
+ }
279
+
280
+ const obj = data as Record<string, unknown>;
281
+ let topLevel = "";
282
+ let sections = "";
283
+
284
+ for (const [key, val] of Object.entries(obj)) {
285
+ if (
286
+ typeof val === "object" &&
287
+ val !== null &&
288
+ !Array.isArray(val)
289
+ ) {
290
+ const path = sectionPath ? `${sectionPath}.${key}` : key;
291
+ sections += `[${path}]\n`;
292
+ sections += toToml(val, path);
293
+ sections += "\n";
294
+ } else {
295
+ topLevel += `${key} = ${toTomlValue(val)}\n`;
296
+ }
297
+ }
298
+
299
+ return topLevel + sections;
300
+ }
301
+
302
+ function toTomlValue(val: unknown): string {
303
+ if (val === null || val === undefined) return '""';
304
+ if (typeof val === "boolean") return String(val);
305
+ if (typeof val === "number") return String(val);
306
+ if (typeof val === "string") 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
+
313
+ // ---------------------------------------------------------------------------
314
+ // Tool registration
315
+ // ---------------------------------------------------------------------------
316
+
317
+ type Format = "yaml" | "json" | "toml";
318
+
319
+ function parse(text: string, format: Format): unknown {
320
+ switch (format) {
321
+ case "json":
322
+ return JSON.parse(text);
323
+ case "yaml":
324
+ return parseYaml(text);
325
+ case "toml":
326
+ return parseToml(text);
327
+ default:
328
+ throw new Error(`Unsupported input format: ${format}`);
329
+ }
330
+ }
331
+
332
+ function serialize(data: unknown, format: Format): string {
333
+ switch (format) {
334
+ case "json":
335
+ return JSON.stringify(data, null, 2);
336
+ case "yaml":
337
+ return toYaml(data).trimEnd();
338
+ case "toml":
339
+ return toToml(data).trimEnd();
340
+ default:
341
+ throw new Error(`Unsupported output format: ${format}`);
342
+ }
343
+ }
344
+
345
+ registerTool({
346
+ name: "yaml_convert",
347
+ description:
348
+ "Convert between YAML, JSON, and TOML formats",
349
+ pro: true,
350
+ inputSchema: {
351
+ type: "object",
352
+ properties: {
353
+ text: { type: "string", description: "Input text to convert" },
354
+ from: {
355
+ type: "string",
356
+ enum: ["yaml", "json", "toml"],
357
+ description: "Source format",
358
+ },
359
+ to: {
360
+ type: "string",
361
+ enum: ["yaml", "json", "toml"],
362
+ description: "Target format",
363
+ },
364
+ },
365
+ required: ["text", "from", "to"],
366
+ },
367
+ handler: async (args) => {
368
+ const text = args.text as string;
369
+ const from = args.from as Format;
370
+ const to = args.to as Format;
371
+
372
+ if (!text.trim()) throw new Error("Input text is empty");
373
+
374
+ const data = parse(text, from);
375
+ const output = serialize(data, to);
376
+
377
+ return [
378
+ `=== Converted ${from.toUpperCase()} → ${to.toUpperCase()} ===`,
379
+ "",
380
+ output,
381
+ ].join("\n");
382
+ },
383
+ });
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "Node16",
5
+ "moduleResolution": "Node16",
6
+ "outDir": "./dist",
7
+ "rootDir": "./src",
8
+ "strict": true,
9
+ "esModuleInterop": true,
10
+ "skipLibCheck": true,
11
+ "declaration": true
12
+ },
13
+ "include": ["src/**/*"]
14
+ }