@t00lzcom/dev-tools 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.
package/README.md ADDED
@@ -0,0 +1,86 @@
1
+ # T00lz Dev Tools — OpenClaw Plugin
2
+
3
+ > 8 essential developer utilities from [t00lz.com](https://t00lz.com), available directly in your OpenClaw AI assistant.
4
+
5
+ All processing is **100% local** — no data ever leaves your machine.
6
+
7
+ ---
8
+
9
+ ## Tools Included
10
+
11
+ | Tool | What you say | What it does |
12
+ |------|-------------|--------------|
13
+ | `json_format` | "format this JSON" / "minify this JSON" | Beautify or minify JSON with error reporting |
14
+ | `json_validate` | "validate this JSON" | Check JSON syntax with exact line/col errors |
15
+ | `base64` | "encode this to base64" / "decode this base64" | Base64 encode or decode any string |
16
+ | `generate_password` | "generate a 24-char password with symbols" | Cryptographically secure password generator |
17
+ | `markdown_to_html` | "convert this markdown to HTML" | Full Markdown → HTML conversion |
18
+ | `word_count` | "count words in this text" | Words, chars, sentences, reading time |
19
+ | `case_convert` | "convert to snake_case" / "make this title case" | 7 case formats including camel, kebab, snake |
20
+ | `text_diff` | "diff these two texts" | Line-by-line diff with +/- summary |
21
+
22
+ ---
23
+
24
+ ## Install
25
+
26
+ ```bash
27
+ openclaw plugins install @t00lzcom/dev-tools
28
+ ```
29
+
30
+ Or from source:
31
+
32
+ ```bash
33
+ openclaw plugins install ./t00lz-dev-tools
34
+ ```
35
+
36
+ ## Configuration
37
+
38
+ Add to your OpenClaw config (optional):
39
+
40
+ ```json5
41
+ {
42
+ plugins: {
43
+ entries: {
44
+ "t00lz-dev-tools": {
45
+ enabled: true,
46
+ config: {
47
+ passwordDefaultLength: 24, // default: 20
48
+ passwordDefaultSymbols: true // default: true
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }
54
+ ```
55
+
56
+ ## Example Usage
57
+
58
+ Once installed, just talk to your OpenClaw agent naturally:
59
+
60
+ ```
61
+ You: format this JSON for me: {"name":"alice","age":30}
62
+ Agent: {
63
+ "name": "alice",
64
+ "age": 30
65
+ }
66
+
67
+ You: generate a strong 32-character password
68
+ Agent: Password: kR7!mXq2#vLp9@wNsZ4$bJcTdYeF8&uA
69
+ Strength: 💪 Strong
70
+
71
+ You: convert "hello world from t00lz" to camelCase
72
+ Agent: helloWorldFromT00lz
73
+
74
+ You: how many words in this paragraph? [paste text]
75
+ Agent: Words: 142 | Characters: 891 | Reading time: 43s
76
+ ```
77
+
78
+ ## Contributing
79
+
80
+ Issues and PRs welcome at [github.com/t00lz/openclaw-dev-tools](https://github.com/t00lz/openclaw-dev-tools).
81
+
82
+ For more free developer tools, visit **[t00lz.com](https://t00lz.com)**.
83
+
84
+ ## License
85
+
86
+ MIT
@@ -0,0 +1,37 @@
1
+ {
2
+ "id": "t00lz-dev-tools",
3
+ "name": "T00lz Dev Tools",
4
+ "version": "1.0.0",
5
+ "description": "Essential developer utilities from T00lz.com — format JSON, encode/decode Base64, generate secure passwords, convert Markdown to HTML, count words, and more. All processing is local; no data leaves your machine.",
6
+ "author": "T00LZ <t000lz.com@gmail.com>",
7
+ "homepage": "https://t00lz.com",
8
+ "license": "MIT",
9
+ "openclaw": {
10
+ "extensions": ["./src/tools.ts"],
11
+ "tools": true
12
+ },
13
+ "configSchema": {
14
+ "type": "object",
15
+ "properties": {
16
+ "passwordDefaultLength": {
17
+ "type": "number",
18
+ "default": 20,
19
+ "description": "Default length for generated passwords"
20
+ },
21
+ "passwordDefaultSymbols": {
22
+ "type": "boolean",
23
+ "default": true,
24
+ "description": "Include symbols in generated passwords by default"
25
+ }
26
+ }
27
+ },
28
+ "uiHints": {
29
+ "plugins.entries.t00lz-dev-tools.config.passwordDefaultLength": {
30
+ "label": "Default Password Length",
31
+ "placeholder": "20"
32
+ },
33
+ "plugins.entries.t00lz-dev-tools.config.passwordDefaultSymbols": {
34
+ "label": "Include Symbols by Default"
35
+ }
36
+ }
37
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@t00lzcom/dev-tools",
3
+ "version": "1.0.0",
4
+ "description": "Essential developer utilities for OpenClaw — powered by T00lz.com",
5
+ "main": "src/tools.ts",
6
+ "keywords": [
7
+ "openclaw",
8
+ "plugin",
9
+ "json",
10
+ "base64",
11
+ "password",
12
+ "markdown",
13
+ "text",
14
+ "developer",
15
+ "utilities",
16
+ "tools"
17
+ ],
18
+ "author": "T00LZ <t000lz.com@gmail.com>",
19
+ "license": "MIT",
20
+ "homepage": "https://t00lz.com",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/t00lz/openclaw-dev-tools"
24
+ },
25
+ "dependencies": {
26
+ "marked": "^12.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "typescript": "^5.4.0"
30
+ },
31
+ "engines": {
32
+ "node": ">=18"
33
+ }
34
+ }
package/src/tools.ts ADDED
@@ -0,0 +1,421 @@
1
+ /**
2
+ * T00lz Dev Tools — OpenClaw Plugin
3
+ * https://t00lz.com
4
+ *
5
+ * Registers 8 developer utility tools directly into your OpenClaw agent.
6
+ * All processing is local — no data is sent anywhere.
7
+ */
8
+
9
+ import { marked } from "marked";
10
+
11
+ // ─── Plugin Registration ──────────────────────────────────────────────────────
12
+
13
+ export function register(api: any) {
14
+ const cfg = api.config?.plugins?.entries?.["t00lz-dev-tools"]?.config ?? {};
15
+ const defaultPasswordLength: number = cfg.passwordDefaultLength ?? 20;
16
+ const defaultPasswordSymbols: boolean = cfg.passwordDefaultSymbols ?? true;
17
+
18
+ // ── 1. JSON Formatter ───────────────────────────────────────────────────────
19
+ api.registerTool({
20
+ name: "json_format",
21
+ description:
22
+ "Beautify or minify a JSON string. Use action='beautify' (default) or action='minify'. " +
23
+ "Returns formatted JSON or a clear error message with the problem line.",
24
+ inputSchema: {
25
+ type: "object",
26
+ required: ["json"],
27
+ properties: {
28
+ json: { type: "string", description: "The raw JSON string to process" },
29
+ action: {
30
+ type: "string",
31
+ enum: ["beautify", "minify"],
32
+ default: "beautify",
33
+ description: "Whether to beautify or minify",
34
+ },
35
+ indent: {
36
+ type: "number",
37
+ default: 2,
38
+ description: "Indentation spaces (beautify only)",
39
+ },
40
+ },
41
+ },
42
+ async execute({ json, action = "beautify", indent = 2 }: any) {
43
+ try {
44
+ const parsed = JSON.parse(json);
45
+ if (action === "minify") {
46
+ return { result: JSON.stringify(parsed), action: "minified" };
47
+ }
48
+ return {
49
+ result: JSON.stringify(parsed, null, indent),
50
+ action: "beautified",
51
+ };
52
+ } catch (err: any) {
53
+ return { error: `Invalid JSON: ${err.message}` };
54
+ }
55
+ },
56
+ });
57
+
58
+ // ── 2. JSON Validator ───────────────────────────────────────────────────────
59
+ api.registerTool({
60
+ name: "json_validate",
61
+ description:
62
+ "Validate a JSON string and report any syntax errors with the exact position.",
63
+ inputSchema: {
64
+ type: "object",
65
+ required: ["json"],
66
+ properties: {
67
+ json: { type: "string", description: "The JSON string to validate" },
68
+ },
69
+ },
70
+ async execute({ json }: any) {
71
+ try {
72
+ JSON.parse(json);
73
+ return { valid: true, message: "✅ Valid JSON — no errors found." };
74
+ } catch (err: any) {
75
+ // Extract line/col from error message where available
76
+ const match = err.message.match(/position (\d+)/i);
77
+ let hint = "";
78
+ if (match) {
79
+ const pos = parseInt(match[1], 10);
80
+ const before = json.slice(0, pos);
81
+ const line = (before.match(/\n/g) ?? []).length + 1;
82
+ const col = pos - before.lastIndexOf("\n");
83
+ hint = ` (line ${line}, col ${col})`;
84
+ }
85
+ return {
86
+ valid: false,
87
+ error: `❌ Invalid JSON${hint}: ${err.message}`,
88
+ };
89
+ }
90
+ },
91
+ });
92
+
93
+ // ── 3. Base64 Encoder / Decoder ─────────────────────────────────────────────
94
+ api.registerTool({
95
+ name: "base64",
96
+ description:
97
+ "Encode a plain string to Base64, or decode a Base64 string back to plain text.",
98
+ inputSchema: {
99
+ type: "object",
100
+ required: ["text", "action"],
101
+ properties: {
102
+ text: { type: "string", description: "The string to process" },
103
+ action: {
104
+ type: "string",
105
+ enum: ["encode", "decode"],
106
+ description: "'encode' converts plain text → Base64; 'decode' reverses it",
107
+ },
108
+ },
109
+ },
110
+ async execute({ text, action }: any) {
111
+ try {
112
+ if (action === "encode") {
113
+ const result = Buffer.from(text, "utf8").toString("base64");
114
+ return { result, action: "encoded" };
115
+ } else {
116
+ const result = Buffer.from(text, "base64").toString("utf8");
117
+ return { result, action: "decoded" };
118
+ }
119
+ } catch (err: any) {
120
+ return { error: `Base64 ${action} failed: ${err.message}` };
121
+ }
122
+ },
123
+ });
124
+
125
+ // ── 4. Secure Password Generator ───────────────────────────────────────────
126
+ api.registerTool({
127
+ name: "generate_password",
128
+ description:
129
+ "Generate a cryptographically secure random password. Fully local — never transmitted anywhere.",
130
+ inputSchema: {
131
+ type: "object",
132
+ properties: {
133
+ length: {
134
+ type: "number",
135
+ default: defaultPasswordLength,
136
+ description: "Password length (8–128)",
137
+ },
138
+ symbols: {
139
+ type: "boolean",
140
+ default: defaultPasswordSymbols,
141
+ description: "Include symbols like !@#$%^&*()",
142
+ },
143
+ numbers: {
144
+ type: "boolean",
145
+ default: true,
146
+ description: "Include digits 0–9",
147
+ },
148
+ uppercase: {
149
+ type: "boolean",
150
+ default: true,
151
+ description: "Include uppercase letters A–Z",
152
+ },
153
+ },
154
+ },
155
+ async execute({
156
+ length = defaultPasswordLength,
157
+ symbols = defaultPasswordSymbols,
158
+ numbers = true,
159
+ uppercase = true,
160
+ }: any) {
161
+ const clampedLength = Math.min(128, Math.max(8, length));
162
+ let chars = "abcdefghijklmnopqrstuvwxyz";
163
+ if (uppercase) chars += "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
164
+ if (numbers) chars += "0123456789";
165
+ if (symbols) chars += "!@#$%^&*()-_=+[]{}|;:,.<>?";
166
+
167
+ const { randomBytes } = await import("crypto");
168
+ let password = "";
169
+ while (password.length < clampedLength) {
170
+ const byte = randomBytes(1)[0];
171
+ if (byte < 256 - (256 % chars.length)) {
172
+ password += chars[byte % chars.length];
173
+ }
174
+ }
175
+ return {
176
+ password,
177
+ length: clampedLength,
178
+ strength:
179
+ clampedLength >= 20 && symbols && numbers
180
+ ? "💪 Strong"
181
+ : clampedLength >= 12
182
+ ? "✅ Good"
183
+ : "⚠️ Weak — consider increasing length",
184
+ };
185
+ },
186
+ });
187
+
188
+ // ── 5. Markdown → HTML ──────────────────────────────────────────────────────
189
+ api.registerTool({
190
+ name: "markdown_to_html",
191
+ description:
192
+ "Convert a Markdown string to clean HTML. Returns the full HTML output.",
193
+ inputSchema: {
194
+ type: "object",
195
+ required: ["markdown"],
196
+ properties: {
197
+ markdown: { type: "string", description: "The Markdown source text" },
198
+ wrap: {
199
+ type: "boolean",
200
+ default: false,
201
+ description: "Wrap output in a basic <!DOCTYPE html> page shell",
202
+ },
203
+ },
204
+ },
205
+ async execute({ markdown, wrap = false }: any) {
206
+ const body = await marked.parse(markdown);
207
+ if (!wrap) return { html: body };
208
+ const full = `<!DOCTYPE html>
209
+ <html lang="en">
210
+ <head>
211
+ <meta charset="UTF-8" />
212
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
213
+ <title>Document</title>
214
+ </head>
215
+ <body>
216
+ ${body}
217
+ </body>
218
+ </html>`;
219
+ return { html: full };
220
+ },
221
+ });
222
+
223
+ // ── 6. Word & Character Counter ─────────────────────────────────────────────
224
+ api.registerTool({
225
+ name: "word_count",
226
+ description:
227
+ "Count words, characters, sentences, paragraphs, and estimated reading time for any text.",
228
+ inputSchema: {
229
+ type: "object",
230
+ required: ["text"],
231
+ properties: {
232
+ text: { type: "string", description: "The text to analyse" },
233
+ wpm: {
234
+ type: "number",
235
+ default: 200,
236
+ description: "Reading speed in words per minute (default 200)",
237
+ },
238
+ },
239
+ },
240
+ async execute({ text, wpm = 200 }: any) {
241
+ const words = text.trim() === "" ? 0 : text.trim().split(/\s+/).length;
242
+ const chars = text.length;
243
+ const charsNoSpaces = text.replace(/\s/g, "").length;
244
+ const sentences = (text.match(/[.!?]+/g) ?? []).length;
245
+ const paragraphs = text
246
+ .split(/\n\s*\n/)
247
+ .filter((p: string) => p.trim().length > 0).length;
248
+ const readingTimeSec = Math.ceil((words / wpm) * 60);
249
+ const minutes = Math.floor(readingTimeSec / 60);
250
+ const seconds = readingTimeSec % 60;
251
+ const readingTime =
252
+ minutes > 0 ? `${minutes}m ${seconds}s` : `${seconds}s`;
253
+
254
+ return {
255
+ words,
256
+ characters: chars,
257
+ charactersNoSpaces: charsNoSpaces,
258
+ sentences,
259
+ paragraphs,
260
+ readingTime,
261
+ };
262
+ },
263
+ });
264
+
265
+ // ── 7. Case Converter ───────────────────────────────────────────────────────
266
+ api.registerTool({
267
+ name: "case_convert",
268
+ description:
269
+ "Convert text between cases: uppercase, lowercase, title, sentence, camelCase, snake_case, kebab-case.",
270
+ inputSchema: {
271
+ type: "object",
272
+ required: ["text", "case"],
273
+ properties: {
274
+ text: { type: "string", description: "The text to convert" },
275
+ case: {
276
+ type: "string",
277
+ enum: [
278
+ "uppercase",
279
+ "lowercase",
280
+ "title",
281
+ "sentence",
282
+ "camel",
283
+ "snake",
284
+ "kebab",
285
+ ],
286
+ description: "Target case format",
287
+ },
288
+ },
289
+ },
290
+ async execute({ text, case: targetCase }: any) {
291
+ let result: string;
292
+ switch (targetCase) {
293
+ case "uppercase":
294
+ result = text.toUpperCase();
295
+ break;
296
+ case "lowercase":
297
+ result = text.toLowerCase();
298
+ break;
299
+ case "title":
300
+ result = text
301
+ .toLowerCase()
302
+ .replace(/\b\w/g, (c: string) => c.toUpperCase());
303
+ break;
304
+ case "sentence":
305
+ result = text
306
+ .toLowerCase()
307
+ .replace(/(^\s*\w|[.!?]\s+\w)/g, (c: string) => c.toUpperCase());
308
+ break;
309
+ case "camel":
310
+ result = text
311
+ .toLowerCase()
312
+ .replace(/[^a-zA-Z0-9]+(.)/g, (_: string, c: string) =>
313
+ c.toUpperCase()
314
+ );
315
+ break;
316
+ case "snake":
317
+ result = text
318
+ .toLowerCase()
319
+ .replace(/\s+/g, "_")
320
+ .replace(/[^a-z0-9_]/g, "");
321
+ break;
322
+ case "kebab":
323
+ result = text
324
+ .toLowerCase()
325
+ .replace(/\s+/g, "-")
326
+ .replace(/[^a-z0-9-]/g, "");
327
+ break;
328
+ default:
329
+ result = text;
330
+ }
331
+ return { result, case: targetCase };
332
+ },
333
+ });
334
+
335
+ // ── 8. Text Diff ────────────────────────────────────────────────────────────
336
+ api.registerTool({
337
+ name: "text_diff",
338
+ description:
339
+ "Compare two texts line-by-line and return added, removed, and unchanged lines.",
340
+ inputSchema: {
341
+ type: "object",
342
+ required: ["text_a", "text_b"],
343
+ properties: {
344
+ text_a: { type: "string", description: "Original text" },
345
+ text_b: { type: "string", description: "New/modified text" },
346
+ context: {
347
+ type: "number",
348
+ default: 3,
349
+ description: "Lines of context around each change",
350
+ },
351
+ },
352
+ },
353
+ async execute({ text_a, text_b, context = 3 }: any) {
354
+ const linesA = text_a.split("\n");
355
+ const linesB = text_b.split("\n");
356
+
357
+ // Simple LCS-based line diff
358
+ const diff: Array<{ type: "add" | "remove" | "same"; line: string }> = [];
359
+ let i = 0,
360
+ j = 0;
361
+
362
+ // Build LCS table
363
+ const m = linesA.length,
364
+ n = linesB.length;
365
+ const dp: number[][] = Array.from({ length: m + 1 }, () =>
366
+ new Array(n + 1).fill(0)
367
+ );
368
+ for (let r = 1; r <= m; r++) {
369
+ for (let c = 1; c <= n; c++) {
370
+ dp[r][c] =
371
+ linesA[r - 1] === linesB[c - 1]
372
+ ? dp[r - 1][c - 1] + 1
373
+ : Math.max(dp[r - 1][c], dp[r][c - 1]);
374
+ }
375
+ }
376
+
377
+ // Trace back
378
+ const trace: Array<{ type: "add" | "remove" | "same"; line: string }> =
379
+ [];
380
+ let r = m,
381
+ c = n;
382
+ while (r > 0 || c > 0) {
383
+ if (r > 0 && c > 0 && linesA[r - 1] === linesB[c - 1]) {
384
+ trace.push({ type: "same", line: linesA[r - 1] });
385
+ r--;
386
+ c--;
387
+ } else if (c > 0 && (r === 0 || dp[r][c - 1] >= dp[r - 1][c])) {
388
+ trace.push({ type: "add", line: linesB[c - 1] });
389
+ c--;
390
+ } else {
391
+ trace.push({ type: "remove", line: linesA[r - 1] });
392
+ r--;
393
+ }
394
+ }
395
+ trace.reverse();
396
+
397
+ const added = trace.filter((d) => d.type === "add").length;
398
+ const removed = trace.filter((d) => d.type === "remove").length;
399
+ const unchanged = trace.filter((d) => d.type === "same").length;
400
+
401
+ // Format as unified-diff-style string
402
+ const formatted = trace
403
+ .map((d) => {
404
+ if (d.type === "add") return `+ ${d.line}`;
405
+ if (d.type === "remove") return `- ${d.line}`;
406
+ return ` ${d.line}`;
407
+ })
408
+ .join("\n");
409
+
410
+ return {
411
+ summary: `+${added} added, -${removed} removed, ${unchanged} unchanged`,
412
+ diff: formatted,
413
+ hasChanges: added > 0 || removed > 0,
414
+ };
415
+ },
416
+ });
417
+
418
+ api.log?.info(
419
+ "[t00lz-dev-tools] 8 tools registered: json_format, json_validate, base64, generate_password, markdown_to_html, word_count, case_convert, text_diff"
420
+ );
421
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,14 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "module": "CommonJS",
5
+ "lib": ["ES2020"],
6
+ "strict": true,
7
+ "esModuleInterop": true,
8
+ "skipLibCheck": true,
9
+ "outDir": "dist",
10
+ "rootDir": "src"
11
+ },
12
+ "include": ["src/**/*.ts"],
13
+ "exclude": ["node_modules", "dist"]
14
+ }