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,173 @@
1
+ import { registerTool } from "../registry.js";
2
+ const PATTERNS = [
3
+ {
4
+ type: "AWS Access Key",
5
+ pattern: /AKIA[0-9A-Z]{16}/g,
6
+ severity: "critical",
7
+ recommendation: "Rotate this AWS access key immediately in IAM console",
8
+ },
9
+ {
10
+ type: "AWS Secret Key",
11
+ pattern: /(?:aws_secret_access_key|secret_access_key|aws_secret)\s*[=:]\s*["']?([A-Za-z0-9/+=]{40})["']?/gi,
12
+ severity: "critical",
13
+ recommendation: "Rotate AWS credentials and revoke the exposed secret key",
14
+ },
15
+ {
16
+ type: "GitHub Token",
17
+ pattern: /gh[pors]_[A-Za-z0-9_]{36,255}/g,
18
+ severity: "critical",
19
+ recommendation: "Revoke this GitHub token at github.com/settings/tokens",
20
+ },
21
+ {
22
+ type: "Stripe Secret Key",
23
+ pattern: /sk_live_[A-Za-z0-9]{24,}/g,
24
+ severity: "critical",
25
+ recommendation: "Roll this Stripe key in the Dashboard immediately",
26
+ },
27
+ {
28
+ type: "Stripe Publishable Key",
29
+ pattern: /pk_live_[A-Za-z0-9]{24,}/g,
30
+ severity: "high",
31
+ recommendation: "While publishable, review if this key should be public",
32
+ },
33
+ {
34
+ type: "Slack Token",
35
+ pattern: /xox[bpors]-[A-Za-z0-9-]{10,}/g,
36
+ severity: "critical",
37
+ recommendation: "Revoke this Slack token in your workspace admin settings",
38
+ },
39
+ {
40
+ type: "Private Key",
41
+ pattern: /-----BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY-----/g,
42
+ severity: "critical",
43
+ recommendation: "Remove this private key and generate a new key pair",
44
+ },
45
+ {
46
+ type: "JWT Token",
47
+ pattern: /eyJ[A-Za-z0-9_-]{10,}\.eyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g,
48
+ severity: "high",
49
+ recommendation: "Revoke this JWT and rotate the signing secret",
50
+ },
51
+ {
52
+ type: "Anthropic API Key",
53
+ pattern: /sk-ant-[A-Za-z0-9_-]{20,}/g,
54
+ severity: "critical",
55
+ recommendation: "Rotate this Anthropic API key in your account settings",
56
+ },
57
+ {
58
+ type: "OpenAI API Key",
59
+ pattern: /sk-[A-Za-z0-9]{20,}/g,
60
+ severity: "critical",
61
+ recommendation: "Rotate this OpenAI API key at platform.openai.com",
62
+ },
63
+ {
64
+ type: "Generic API Key/Token",
65
+ pattern: /(?:api[_-]?key|api[_-]?token|access[_-]?token|auth[_-]?token)\s*[=:]\s*["']?([A-Za-z0-9_\-/.+=]{16,})["']?/gi,
66
+ severity: "medium",
67
+ recommendation: "Review if this API key/token should be in source code",
68
+ },
69
+ {
70
+ type: "Generic Password",
71
+ pattern: /(?:password|passwd|pwd)\s*[=:]\s*["']([^"'\s]{8,})["']/gi,
72
+ severity: "high",
73
+ recommendation: "Move this password to a secrets manager or environment variable",
74
+ },
75
+ {
76
+ type: "Generic Secret",
77
+ pattern: /(?:secret|secret_key)\s*[=:]\s*["']([^"'\s]{8,})["']/gi,
78
+ severity: "high",
79
+ recommendation: "Move this secret to a secrets manager or environment variable",
80
+ },
81
+ {
82
+ type: "Connection String",
83
+ pattern: /(?:postgres|postgresql|mysql|mongodb|redis):\/\/[^\s"']{10,}/gi,
84
+ severity: "critical",
85
+ recommendation: "Move this connection string to an environment variable. It may contain credentials.",
86
+ },
87
+ ];
88
+ function maskSecret(secret) {
89
+ if (secret.length <= 12)
90
+ return secret.slice(0, 4) + "****";
91
+ return secret.slice(0, 8) + "..." + secret.slice(-4);
92
+ }
93
+ function scanText(text) {
94
+ const lines = text.split("\n");
95
+ const findings = [];
96
+ const seen = new Set();
97
+ for (const patternDef of PATTERNS) {
98
+ // Reset regex lastIndex for global patterns
99
+ patternDef.pattern.lastIndex = 0;
100
+ let match;
101
+ while ((match = patternDef.pattern.exec(text)) !== null) {
102
+ const fullMatch = match[0];
103
+ const secret = match[1] || fullMatch;
104
+ // Deduplicate
105
+ const key = `${patternDef.type}:${fullMatch}`;
106
+ if (seen.has(key))
107
+ continue;
108
+ seen.add(key);
109
+ // Find line number
110
+ const offset = match.index;
111
+ let lineNum = 1;
112
+ for (let i = 0; i < offset && i < text.length; i++) {
113
+ if (text[i] === "\n")
114
+ lineNum++;
115
+ }
116
+ findings.push({
117
+ type: patternDef.type,
118
+ masked: maskSecret(secret),
119
+ line: lineNum,
120
+ severity: patternDef.severity,
121
+ recommendation: patternDef.recommendation,
122
+ });
123
+ }
124
+ }
125
+ // Sort by severity then line number
126
+ const severityOrder = { critical: 0, high: 1, medium: 2 };
127
+ findings.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity] || a.line - b.line);
128
+ return findings;
129
+ }
130
+ registerTool({
131
+ name: "secrets_scan",
132
+ description: "Scan text for leaked secrets, API keys, tokens, passwords, and connection strings",
133
+ pro: true,
134
+ inputSchema: {
135
+ type: "object",
136
+ properties: {
137
+ text: {
138
+ type: "string",
139
+ description: "Text content to scan for secrets",
140
+ },
141
+ },
142
+ required: ["text"],
143
+ },
144
+ handler: async (args) => {
145
+ const text = args.text;
146
+ if (!text.trim())
147
+ throw new Error("Text is empty");
148
+ const findings = scanText(text);
149
+ if (findings.length === 0) {
150
+ return "No secrets detected. The text appears clean.";
151
+ }
152
+ const critical = findings.filter((f) => f.severity === "critical").length;
153
+ const high = findings.filter((f) => f.severity === "high").length;
154
+ const medium = findings.filter((f) => f.severity === "medium").length;
155
+ const lines = [
156
+ `=== Secrets Scan: Found ${findings.length} secret${findings.length === 1 ? "" : "s"}: ${critical} critical, ${high} high, ${medium} medium ===`,
157
+ "",
158
+ ];
159
+ for (const finding of findings) {
160
+ const severityLabel = finding.severity === "critical"
161
+ ? "CRITICAL"
162
+ : finding.severity === "high"
163
+ ? "HIGH"
164
+ : "MEDIUM";
165
+ lines.push(`[${severityLabel}] ${finding.type}`);
166
+ lines.push(` Line: ${finding.line}`);
167
+ lines.push(` Preview: ${finding.masked}`);
168
+ lines.push(` Action: ${finding.recommendation}`);
169
+ lines.push("");
170
+ }
171
+ return lines.join("\n").trimEnd();
172
+ },
173
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,157 @@
1
+ import { registerTool } from "../registry.js";
2
+ // ---------------------------------------------------------------------------
3
+ // SQL Formatter — normalize whitespace, add newlines before major clauses,
4
+ // indent columns and conditions
5
+ // ---------------------------------------------------------------------------
6
+ const MAJOR_CLAUSES = [
7
+ "SELECT",
8
+ "FROM",
9
+ "WHERE",
10
+ "INNER JOIN",
11
+ "LEFT JOIN",
12
+ "RIGHT JOIN",
13
+ "FULL JOIN",
14
+ "CROSS JOIN",
15
+ "JOIN",
16
+ "ON",
17
+ "GROUP BY",
18
+ "ORDER BY",
19
+ "HAVING",
20
+ "LIMIT",
21
+ "OFFSET",
22
+ "INSERT INTO",
23
+ "VALUES",
24
+ "UPDATE",
25
+ "SET",
26
+ "DELETE FROM",
27
+ "WITH",
28
+ "UNION ALL",
29
+ "UNION",
30
+ "EXCEPT",
31
+ "INTERSECT",
32
+ ];
33
+ function formatSQL(sql, _dialect) {
34
+ // Normalize whitespace: collapse runs of whitespace into single space
35
+ let normalized = sql.replace(/\s+/g, " ").trim();
36
+ // Uppercase major clauses and add newlines before them
37
+ // Sort clauses longest-first so multi-word clauses match before single-word
38
+ const sortedClauses = [...MAJOR_CLAUSES].sort((a, b) => b.length - a.length);
39
+ for (const clause of sortedClauses) {
40
+ const pattern = new RegExp(`\\b${clause}\\b`, "gi");
41
+ normalized = normalized.replace(pattern, `\n${clause}`);
42
+ }
43
+ // Clean up: remove leading newline
44
+ normalized = normalized.replace(/^\n/, "");
45
+ // Split into lines for indentation processing
46
+ const lines = normalized.split("\n").map((l) => l.trim());
47
+ const result = [];
48
+ for (const line of lines) {
49
+ if (!line)
50
+ continue;
51
+ // Check if this line starts with a major clause
52
+ const upperLine = line.toUpperCase();
53
+ const startsWithClause = sortedClauses.some((c) => upperLine.startsWith(c));
54
+ if (startsWithClause) {
55
+ // Find where the clause keyword ends
56
+ const matchedClause = sortedClauses.find((c) => upperLine.startsWith(c));
57
+ if (matchedClause) {
58
+ const rest = line.slice(matchedClause.length).trim();
59
+ if (matchedClause === "SELECT" ||
60
+ matchedClause === "GROUP BY" ||
61
+ matchedClause === "ORDER BY") {
62
+ // Split comma-separated items onto separate indented lines
63
+ if (rest) {
64
+ const items = splitTopLevel(rest, ",");
65
+ if (items.length > 1) {
66
+ result.push(matchedClause);
67
+ for (let i = 0; i < items.length; i++) {
68
+ const comma = i < items.length - 1 ? "," : "";
69
+ result.push(` ${items[i].trim()}${comma}`);
70
+ }
71
+ continue;
72
+ }
73
+ }
74
+ }
75
+ if (matchedClause === "WHERE" || matchedClause === "HAVING") {
76
+ // Indent AND/OR conditions
77
+ if (rest) {
78
+ const withConditions = rest
79
+ .replace(/\b(AND)\b/gi, "\n AND")
80
+ .replace(/\b(OR)\b/gi, "\n OR");
81
+ const condLines = withConditions
82
+ .split("\n")
83
+ .map((l) => l.trim())
84
+ .filter(Boolean);
85
+ result.push(`${matchedClause}`);
86
+ for (const cl of condLines) {
87
+ result.push(` ${cl}`);
88
+ }
89
+ continue;
90
+ }
91
+ }
92
+ result.push(line);
93
+ }
94
+ else {
95
+ result.push(line);
96
+ }
97
+ }
98
+ else {
99
+ result.push(` ${line}`);
100
+ }
101
+ }
102
+ return result.join("\n");
103
+ }
104
+ /**
105
+ * Split a string by a delimiter, but only at the top level (not inside parens).
106
+ */
107
+ function splitTopLevel(s, delimiter) {
108
+ const parts = [];
109
+ let depth = 0;
110
+ let current = "";
111
+ for (let i = 0; i < s.length; i++) {
112
+ const ch = s[i];
113
+ if (ch === "(")
114
+ depth++;
115
+ else if (ch === ")")
116
+ depth--;
117
+ if (ch === delimiter && depth === 0) {
118
+ parts.push(current);
119
+ current = "";
120
+ }
121
+ else {
122
+ current += ch;
123
+ }
124
+ }
125
+ if (current.trim())
126
+ parts.push(current);
127
+ return parts;
128
+ }
129
+ registerTool({
130
+ name: "sql_format",
131
+ description: "Format and prettify SQL queries with proper indentation and clause separation",
132
+ pro: true,
133
+ inputSchema: {
134
+ type: "object",
135
+ properties: {
136
+ sql: { type: "string", description: "SQL query to format" },
137
+ dialect: {
138
+ type: "string",
139
+ enum: ["standard", "postgresql", "mysql", "sqlite"],
140
+ description: "SQL dialect (default: standard)",
141
+ },
142
+ },
143
+ required: ["sql"],
144
+ },
145
+ handler: async (args) => {
146
+ const sql = args.sql;
147
+ const dialect = args.dialect || "standard";
148
+ if (!sql.trim())
149
+ throw new Error("SQL string is empty");
150
+ const formatted = formatSQL(sql, dialect);
151
+ return [
152
+ `=== Formatted SQL (${dialect}) ===`,
153
+ "",
154
+ formatted,
155
+ ].join("\n");
156
+ },
157
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,72 @@
1
+ import { registerTool } from "../registry.js";
2
+ function relativeTime(date) {
3
+ const now = Date.now();
4
+ const diffMs = now - date.getTime();
5
+ const absDiff = Math.abs(diffMs);
6
+ const suffix = diffMs >= 0 ? "ago" : "from now";
7
+ const seconds = Math.floor(absDiff / 1000);
8
+ if (seconds < 60)
9
+ return `${seconds} seconds ${suffix}`;
10
+ const minutes = Math.floor(seconds / 60);
11
+ if (minutes < 60)
12
+ return `${minutes} minutes ${suffix}`;
13
+ const hours = Math.floor(minutes / 60);
14
+ if (hours < 24)
15
+ return `${hours} hours ${suffix}`;
16
+ const days = Math.floor(hours / 24);
17
+ if (days < 30)
18
+ return `${days} days ${suffix}`;
19
+ const months = Math.floor(days / 30);
20
+ if (months < 12)
21
+ return `${months} months ${suffix}`;
22
+ const years = Math.floor(days / 365);
23
+ return `${years} years ${suffix}`;
24
+ }
25
+ registerTool({
26
+ name: "timestamp",
27
+ description: "Convert between timestamp formats: epoch seconds, epoch ms, ISO 8601, human-readable",
28
+ pro: false,
29
+ inputSchema: {
30
+ type: "object",
31
+ properties: {
32
+ value: {
33
+ type: "string",
34
+ description: '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.trim();
41
+ let date;
42
+ if (value.toLowerCase() === "now") {
43
+ date = new Date();
44
+ }
45
+ else if (/^\d{10}$/.test(value)) {
46
+ // Epoch seconds
47
+ date = new Date(parseInt(value, 10) * 1000);
48
+ }
49
+ else if (/^\d{13}$/.test(value)) {
50
+ // Epoch milliseconds
51
+ date = new Date(parseInt(value, 10));
52
+ }
53
+ else if (value.includes("T") || value.includes("-")) {
54
+ date = new Date(value);
55
+ }
56
+ else {
57
+ throw new Error(`Cannot parse timestamp: "${value}". Use "now", epoch seconds (10 digits), epoch ms (13 digits), or ISO 8601.`);
58
+ }
59
+ if (isNaN(date.getTime())) {
60
+ throw new Error(`Invalid date: "${value}"`);
61
+ }
62
+ const epochS = Math.floor(date.getTime() / 1000);
63
+ const epochMs = date.getTime();
64
+ return [
65
+ `epoch_seconds: ${epochS}`,
66
+ `epoch_ms: ${epochMs}`,
67
+ `iso8601: ${date.toISOString()}`,
68
+ `human: ${date.toUTCString()}`,
69
+ `relative: ${relativeTime(date)}`,
70
+ ].join("\n");
71
+ },
72
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,26 @@
1
+ import { registerTool } from "../registry.js";
2
+ registerTool({
3
+ name: "url_encode",
4
+ description: "URL encode or decode strings",
5
+ pro: false,
6
+ inputSchema: {
7
+ type: "object",
8
+ properties: {
9
+ text: { type: "string", description: "Text to encode or encoded 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 encodeURIComponent(text);
23
+ }
24
+ return decodeURIComponent(text);
25
+ },
26
+ });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,24 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { registerTool } from "../registry.js";
3
+ registerTool({
4
+ name: "uuid_generate",
5
+ description: "Generate one or more v4 UUIDs",
6
+ pro: false,
7
+ inputSchema: {
8
+ type: "object",
9
+ properties: {
10
+ count: {
11
+ type: "number",
12
+ description: "Number of UUIDs to generate (default: 1, max: 10)",
13
+ },
14
+ },
15
+ },
16
+ handler: async (args) => {
17
+ let count = Math.min(Math.max(args.count || 1, 1), 10);
18
+ const uuids = [];
19
+ for (let i = 0; i < count; i++) {
20
+ uuids.push(randomUUID());
21
+ }
22
+ return uuids.join("\n");
23
+ },
24
+ });
@@ -0,0 +1 @@
1
+ export {};