myclaw-toolkit 1.0.9 → 1.0.10

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 (2) hide show
  1. package/dist/tools.test.js +239 -0
  2. package/package.json +10 -3
@@ -0,0 +1,239 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Basic tests for local tool implementations.
4
+ * Run: npx vitest run
5
+ */
6
+ import { describe, it, expect } from "vitest";
7
+ import crypto from "node:crypto";
8
+ // ═══════════════════════════════════════════════════════════════════
9
+ // Utility functions (replicated from index.ts for isolated testing)
10
+ // ═══════════════════════════════════════════════════════════════════
11
+ function hexToRgb(hex) {
12
+ const h = hex.replace("#", "");
13
+ return [
14
+ parseInt(h.slice(0, 2), 16),
15
+ parseInt(h.slice(2, 4), 16),
16
+ parseInt(h.slice(4, 6), 16),
17
+ ];
18
+ }
19
+ function rgbToHsl(r, g, b) {
20
+ r /= 255;
21
+ g /= 255;
22
+ b /= 255;
23
+ const max = Math.max(r, g, b), min = Math.min(r, g, b);
24
+ let h = 0, s = 0, l = (max + min) / 2;
25
+ if (max !== min) {
26
+ const d = max - min;
27
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
28
+ switch (max) {
29
+ case r:
30
+ h = ((g - b) / d + (g < b ? 6 : 0)) / 6;
31
+ break;
32
+ case g:
33
+ h = ((b - r) / d + 2) / 6;
34
+ break;
35
+ case b:
36
+ h = ((r - g) / d + 4) / 6;
37
+ break;
38
+ }
39
+ }
40
+ return [Math.round(h * 360), Math.round(s * 100), Math.round(l * 100)];
41
+ }
42
+ function hslToRgb(h, s, l) {
43
+ s /= 100;
44
+ l /= 100;
45
+ const c = (1 - Math.abs(2 * l - 1)) * s;
46
+ const x = c * (1 - Math.abs((h / 60) % 2 - 1));
47
+ const m = l - c / 2;
48
+ let r = 0, g = 0, b = 0;
49
+ if (h < 60) {
50
+ r = c;
51
+ g = x;
52
+ }
53
+ else if (h < 120) {
54
+ r = x;
55
+ g = c;
56
+ }
57
+ else if (h < 180) {
58
+ g = c;
59
+ b = x;
60
+ }
61
+ else if (h < 240) {
62
+ g = x;
63
+ b = c;
64
+ }
65
+ else if (h < 300) {
66
+ r = x;
67
+ b = c;
68
+ }
69
+ else {
70
+ r = c;
71
+ b = x;
72
+ }
73
+ return [Math.round((r + m) * 255), Math.round((g + m) * 255), Math.round((b + m) * 255)];
74
+ }
75
+ // ═══════════════════════════════════════════════════════════════════
76
+ // Tests
77
+ // ═══════════════════════════════════════════════════════════════════
78
+ describe("timestamp", () => {
79
+ it("generates valid timestamps", () => {
80
+ const now = Date.now();
81
+ const iso = new Date().toISOString();
82
+ expect(now).toBeGreaterThan(1700000000000); // after 2023
83
+ expect(iso).toMatch(/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}/);
84
+ });
85
+ });
86
+ describe("uuid", () => {
87
+ it("generates valid UUID v4", () => {
88
+ for (let i = 0; i < 10; i++) {
89
+ const uuid = crypto.randomUUID();
90
+ expect(uuid).toMatch(/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/);
91
+ }
92
+ });
93
+ it("generates unique UUIDs", () => {
94
+ const uuids = new Set(Array.from({ length: 100 }, () => crypto.randomUUID()));
95
+ expect(uuids.size).toBe(100);
96
+ });
97
+ });
98
+ describe("base64", () => {
99
+ it("encodes correctly", () => {
100
+ expect(Buffer.from("hello").toString("base64")).toBe("aGVsbG8=");
101
+ expect(Buffer.from("你好").toString("base64")).toBe("5L2g5aW9");
102
+ });
103
+ it("round-trips", () => {
104
+ const original = "Hello, World! 你好世界";
105
+ const encoded = Buffer.from(original, "utf-8").toString("base64");
106
+ const decoded = Buffer.from(encoded, "base64").toString("utf-8");
107
+ expect(decoded).toBe(original);
108
+ });
109
+ });
110
+ describe("hash", () => {
111
+ it("generates correct SHA256", () => {
112
+ const hash = crypto.createHash("sha256").update("test").digest("hex");
113
+ expect(hash).toBe("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08");
114
+ });
115
+ it("generates correct MD5", () => {
116
+ const hash = crypto.createHash("md5").update("hello").digest("hex");
117
+ expect(hash).toBe("5d41402abc4b2a76b9719d911017c592");
118
+ });
119
+ it("different inputs produce different hashes", () => {
120
+ const h1 = crypto.createHash("sha256").update("a").digest("hex");
121
+ const h2 = crypto.createHash("sha256").update("b").digest("hex");
122
+ expect(h1).not.toBe(h2);
123
+ });
124
+ });
125
+ describe("color_tools", () => {
126
+ it("converts hex to RGB correctly", () => {
127
+ expect(hexToRgb("#ff0000")).toEqual([255, 0, 0]);
128
+ expect(hexToRgb("#00ff00")).toEqual([0, 255, 0]);
129
+ expect(hexToRgb("#0000ff")).toEqual([0, 0, 255]);
130
+ });
131
+ it("converts hex to HSL correctly", () => {
132
+ const hsl = rgbToHsl(255, 0, 0);
133
+ expect(hsl[0]).toBe(0); // hue
134
+ expect(hsl[1]).toBe(100); // saturation
135
+ expect(hsl[2]).toBe(50); // lightness
136
+ });
137
+ it("round-trips hex → RGB → HSL → RGB → hex", () => {
138
+ const originalHex = "#4a90d9";
139
+ const rgb = hexToRgb(originalHex);
140
+ const hsl = rgbToHsl(...rgb);
141
+ const rgb2 = hslToRgb(...hsl);
142
+ // Allow 1-unit rounding difference
143
+ expect(Math.abs(rgb[0] - rgb2[0])).toBeLessThanOrEqual(1);
144
+ expect(Math.abs(rgb[1] - rgb2[1])).toBeLessThanOrEqual(1);
145
+ expect(Math.abs(rgb[2] - rgb2[2])).toBeLessThanOrEqual(1);
146
+ });
147
+ });
148
+ describe("bmi_calculator", () => {
149
+ it("calculates BMI correctly", () => {
150
+ const height = 175, weight = 70;
151
+ const h = height / 100;
152
+ const bmi = weight / (h * h);
153
+ expect(Math.round(bmi * 100) / 100).toBe(22.86);
154
+ });
155
+ it("correctly categorizes BMI", () => {
156
+ const categorize = (bmi) => {
157
+ if (bmi < 18.5)
158
+ return "Underweight";
159
+ if (bmi < 25)
160
+ return "Normal weight";
161
+ if (bmi < 30)
162
+ return "Overweight";
163
+ return "Obese";
164
+ };
165
+ expect(categorize(17)).toBe("Underweight");
166
+ expect(categorize(22)).toBe("Normal weight");
167
+ expect(categorize(27)).toBe("Overweight");
168
+ expect(categorize(32)).toBe("Obese");
169
+ });
170
+ });
171
+ describe("json_formatter", () => {
172
+ it("formats JSON", () => {
173
+ const obj = { a: 1, b: [2, 3] };
174
+ expect(JSON.parse(JSON.stringify(obj))).toEqual(obj);
175
+ });
176
+ it("detects invalid JSON", () => {
177
+ expect(() => JSON.parse("{bad json")).toThrow();
178
+ });
179
+ it("minifies JSON", () => {
180
+ const minified = JSON.stringify({ a: 1, b: 2 });
181
+ expect(minified).toBe('{"a":1,"b":2}');
182
+ expect(minified).not.toContain("\n");
183
+ });
184
+ });
185
+ describe("url_tools", () => {
186
+ it("encodes URL components", () => {
187
+ expect(encodeURIComponent("hello world")).toBe("hello%20world");
188
+ expect(encodeURIComponent("你好")).toBe("%E4%BD%A0%E5%A5%BD");
189
+ });
190
+ it("decodes URL components", () => {
191
+ expect(decodeURIComponent("hello%20world")).toBe("hello world");
192
+ expect(decodeURIComponent("%E4%BD%A0%E5%A5%BD")).toBe("你好");
193
+ });
194
+ it("round-trips", () => {
195
+ const original = "https://example.com?q=你好世界";
196
+ expect(decodeURIComponent(encodeURIComponent(original))).toBe(original);
197
+ });
198
+ });
199
+ describe("text_tools", () => {
200
+ it("counts characters, words, lines", () => {
201
+ const text = "Hello world\nHow are you?";
202
+ expect(text.length).toBe(24);
203
+ expect(text.trim().split(/\s+/).length).toBe(5);
204
+ expect(text.split("\n").length).toBe(2);
205
+ });
206
+ it("reverses text", () => {
207
+ expect("hello".split("").reverse().join("")).toBe("olleh");
208
+ });
209
+ it("converts case", () => {
210
+ expect("Hello".toUpperCase()).toBe("HELLO");
211
+ expect("Hello".toLowerCase()).toBe("hello");
212
+ expect("hello world".replace(/\w\S*/g, w => w.charAt(0).toUpperCase() + w.slice(1).toLowerCase())).toBe("Hello World");
213
+ });
214
+ });
215
+ describe("vcard_generator", () => {
216
+ it("generates valid vCard", () => {
217
+ const vcard = [
218
+ "BEGIN:VCARD",
219
+ "VERSION:3.0",
220
+ "FN:John Doe",
221
+ "ORG:ACME Inc",
222
+ "TEL:+1234567890",
223
+ "EMAIL:john@example.com",
224
+ "END:VCARD",
225
+ ].join("\n");
226
+ expect(vcard).toContain("BEGIN:VCARD");
227
+ expect(vcard).toContain("FN:John Doe");
228
+ expect(vcard).toContain("END:VCARD");
229
+ });
230
+ });
231
+ describe("wifi_qrcode format", () => {
232
+ it("generates correct WiFi string format", () => {
233
+ const ssid = "MyNetwork";
234
+ const password = "secret123";
235
+ const security = "WPA";
236
+ const wifiString = `WIFI:S:${ssid};T:${security};P:${password};;`;
237
+ expect(wifiString).toBe("WIFI:S:MyNetwork;T:WPA;P:secret123;;");
238
+ });
239
+ });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myclaw-toolkit",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
4
  "description": "23-in-1 developer utility toolkit as an MCP server — search, exchange rates, crypto, QR codes, translation, and more",
5
5
  "mcpName": "io.github.Dusheh/myclaw-toolkit",
6
6
  "type": "module",
@@ -14,7 +14,10 @@
14
14
  "scripts": {
15
15
  "build": "tsc",
16
16
  "start": "node dist/index.js",
17
- "dev": "tsx src/index.ts"
17
+ "dev": "tsx src/index.ts",
18
+ "test": "vitest run",
19
+ "lint": "eslint src/",
20
+ "test:watch": "vitest"
18
21
  },
19
22
  "keywords": [
20
23
  "mcp",
@@ -60,7 +63,11 @@
60
63
  "devDependencies": {
61
64
  "@types/node": "^22.19.21",
62
65
  "@types/qrcode": "^1.5.6",
66
+ "@typescript-eslint/eslint-plugin": "^8.62.0",
67
+ "@typescript-eslint/parser": "^8.62.0",
68
+ "eslint": "^10.5.0",
63
69
  "tsx": "^4.22.4",
64
- "typescript": "^5.9.3"
70
+ "typescript": "^5.9.3",
71
+ "vitest": "^4.1.9"
65
72
  }
66
73
  }