devknife 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 (52) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/LICENSE +21 -0
  3. package/README.md +217 -0
  4. package/dist/base64-CJAEMNK6.js +2 -0
  5. package/dist/base64-CJAEMNK6.js.map +1 -0
  6. package/dist/chunk-4LTWPAJW.js +2 -0
  7. package/dist/chunk-4LTWPAJW.js.map +1 -0
  8. package/dist/chunk-6UIJWUAN.js +2 -0
  9. package/dist/chunk-6UIJWUAN.js.map +1 -0
  10. package/dist/chunk-AYW5FKAL.js +2 -0
  11. package/dist/chunk-AYW5FKAL.js.map +1 -0
  12. package/dist/chunk-GTYGA6PU.js +2 -0
  13. package/dist/chunk-GTYGA6PU.js.map +1 -0
  14. package/dist/chunk-GZW2QEOC.js +2 -0
  15. package/dist/chunk-GZW2QEOC.js.map +1 -0
  16. package/dist/chunk-NW6U5UEX.js +2 -0
  17. package/dist/chunk-NW6U5UEX.js.map +1 -0
  18. package/dist/chunk-SDQ3PUQH.js +2 -0
  19. package/dist/chunk-SDQ3PUQH.js.map +1 -0
  20. package/dist/chunk-VPRHVS7Z.js +2 -0
  21. package/dist/chunk-VPRHVS7Z.js.map +1 -0
  22. package/dist/chunk-YBYCYSEC.js +2 -0
  23. package/dist/chunk-YBYCYSEC.js.map +1 -0
  24. package/dist/chunk-ZZLUWX3F.js +2 -0
  25. package/dist/chunk-ZZLUWX3F.js.map +1 -0
  26. package/dist/cli.cjs +1444 -0
  27. package/dist/cli.js +1421 -0
  28. package/dist/color-UNOPSQIT.js +2 -0
  29. package/dist/color-UNOPSQIT.js.map +1 -0
  30. package/dist/hash-52EWSWG4.js +2 -0
  31. package/dist/hash-52EWSWG4.js.map +1 -0
  32. package/dist/index.cjs +4 -0
  33. package/dist/index.cjs.map +1 -0
  34. package/dist/index.d.cts +285 -0
  35. package/dist/index.d.ts +285 -0
  36. package/dist/index.js +4 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/json-KEOS7PBI.js +2 -0
  39. package/dist/json-KEOS7PBI.js.map +1 -0
  40. package/dist/jwt-CKRHCFHP.js +2 -0
  41. package/dist/jwt-CKRHCFHP.js.map +1 -0
  42. package/dist/password-2Z5N7KS2.js +2 -0
  43. package/dist/password-2Z5N7KS2.js.map +1 -0
  44. package/dist/text-PAS3LRJD.js +2 -0
  45. package/dist/text-PAS3LRJD.js.map +1 -0
  46. package/dist/time-3XVRD5HD.js +2 -0
  47. package/dist/time-3XVRD5HD.js.map +1 -0
  48. package/dist/url-BRWDEVUS.js +2 -0
  49. package/dist/url-BRWDEVUS.js.map +1 -0
  50. package/dist/uuid-MZ4YSUFH.js +2 -0
  51. package/dist/uuid-MZ4YSUFH.js.map +1 -0
  52. package/package.json +86 -0
package/dist/cli.js ADDED
@@ -0,0 +1,1421 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+
4
+ // src/core/parser.ts
5
+ function parse(rawArgv) {
6
+ const argv = rawArgv !== void 0 && rawArgv !== null ? rawArgv : process.argv.slice(2);
7
+ const args = [];
8
+ const flags = {};
9
+ for (let i = 0; i < argv.length; i++) {
10
+ const arg = argv[i];
11
+ if (arg === "--help" || arg === "-h") {
12
+ flags["help"] = true;
13
+ } else if (arg === "--version" || arg === "-v") {
14
+ flags["version"] = true;
15
+ } else if (arg === "--interactive" || arg === "-i") {
16
+ flags["interactive"] = true;
17
+ } else if (arg === "--stdin" || arg === "-s") {
18
+ flags["stdin"] = true;
19
+ } else if (arg.startsWith("--no-")) {
20
+ const key = arg.slice(5);
21
+ flags[key] = false;
22
+ } else if (arg.startsWith("--")) {
23
+ const eqIndex = arg.indexOf("=");
24
+ if (eqIndex !== -1) {
25
+ const key = arg.slice(2, eqIndex);
26
+ const value = arg.slice(eqIndex + 1);
27
+ flags[key] = value;
28
+ } else {
29
+ const key = arg.slice(2);
30
+ const next = argv[i + 1];
31
+ if (next && !next.startsWith("-")) {
32
+ flags[key] = next;
33
+ i++;
34
+ } else {
35
+ flags[key] = true;
36
+ }
37
+ }
38
+ } else {
39
+ args.push(arg);
40
+ }
41
+ }
42
+ const command = args[0] ?? "";
43
+ const subcommand = args[1] ?? "";
44
+ return {
45
+ command,
46
+ subcommand,
47
+ args: args.slice(2),
48
+ flags,
49
+ raw: argv
50
+ };
51
+ }
52
+
53
+ // src/core/logger.ts
54
+ var ANSI = {
55
+ reset: "\x1B[0m",
56
+ bold: "\x1B[1m",
57
+ dim: "\x1B[2m",
58
+ red: "\x1B[31m",
59
+ green: "\x1B[32m",
60
+ yellow: "\x1B[33m",
61
+ blue: "\x1B[34m",
62
+ magenta: "\x1B[35m",
63
+ cyan: "\x1B[36m",
64
+ white: "\x1B[37m",
65
+ gray: "\x1B[90m",
66
+ redBright: "\x1B[91m",
67
+ greenBright: "\x1B[92m",
68
+ yellowBright: "\x1B[93m",
69
+ blueBright: "\x1B[94m",
70
+ magentaBright: "\x1B[95m",
71
+ cyanBright: "\x1B[96m",
72
+ bgRed: "\x1B[41m",
73
+ bgGreen: "\x1B[42m",
74
+ bgBlue: "\x1B[44m"
75
+ };
76
+ function createLogger(write = process.stdout.write.bind(process.stdout)) {
77
+ function colorize(text, color) {
78
+ return `${ANSI[color]}${text}${ANSI.reset}`;
79
+ }
80
+ return {
81
+ info(msg) {
82
+ write(`${colorize("\u2139", "blue")} ${msg}
83
+ `);
84
+ },
85
+ success(msg) {
86
+ write(`${colorize("\u2714", "green")} ${msg}
87
+ `);
88
+ },
89
+ warn(msg) {
90
+ write(`${colorize("\u26A0", "yellow")} ${msg}
91
+ `);
92
+ },
93
+ error(msg) {
94
+ write(`${colorize("\u2716", "red")} ${msg}
95
+ `);
96
+ },
97
+ raw(msg) {
98
+ write(`${msg}
99
+ `);
100
+ },
101
+ title(msg) {
102
+ write(`
103
+ ${colorize(colorize(msg, "bold"), "cyan")}
104
+ `);
105
+ },
106
+ table(rows) {
107
+ if (rows.length === 0) return;
108
+ const colWidths = [];
109
+ const maxCols = rows[0].length;
110
+ for (let col = 0; col < maxCols; col++) {
111
+ let max = 0;
112
+ for (const row of rows) {
113
+ const cell = col < row.length ? row[col] : "";
114
+ const cellLen = cell.length;
115
+ if (cellLen > max) max = cellLen;
116
+ }
117
+ colWidths.push(max);
118
+ }
119
+ for (const row of rows) {
120
+ const padded = [...row];
121
+ while (padded.length < maxCols) padded.push("");
122
+ const line = padded.map((cell, idx) => cell.padEnd(colWidths[idx])).join(" ");
123
+ write(` ${line}
124
+ `);
125
+ }
126
+ },
127
+ newline() {
128
+ write("\n");
129
+ },
130
+ color(text, color) {
131
+ return colorize(text, color);
132
+ },
133
+ bold(text) {
134
+ return colorize(text, "bold");
135
+ },
136
+ dim(text) {
137
+ return colorize(text, "dim");
138
+ }
139
+ };
140
+ }
141
+
142
+ // src/core/errors.ts
143
+ var DevKnifeError = class extends Error {
144
+ constructor(message, code) {
145
+ super(message);
146
+ this.code = code;
147
+ this.name = "DevKnifeError";
148
+ }
149
+ code;
150
+ };
151
+ var ToolNotFoundError = class extends DevKnifeError {
152
+ constructor(toolName) {
153
+ super(`Tool not found: ${toolName}. Run 'devknife --help' to see available tools.`, "TOOL_NOT_FOUND");
154
+ this.name = "ToolNotFoundError";
155
+ }
156
+ };
157
+ var SubcommandNotFoundError = class extends DevKnifeError {
158
+ constructor(tool, subcommand) {
159
+ super(
160
+ `Unknown subcommand '${subcommand}' for tool '${tool}'. Run 'devknife ${tool} --help' for usage.`,
161
+ "SUBCOMMAND_NOT_FOUND"
162
+ );
163
+ this.name = "SubcommandNotFoundError";
164
+ }
165
+ };
166
+ function handleError(error, write = process.stderr.write.bind(process.stderr)) {
167
+ if (error instanceof DevKnifeError) {
168
+ write(`\x1B[31m\u2716 Error: ${error.message}\x1B[0m
169
+ `);
170
+ process.exitCode = 1;
171
+ } else if (error instanceof Error) {
172
+ write(`\x1B[31m\u2716 Unexpected Error: ${error.message}\x1B[0m
173
+ `);
174
+ process.exitCode = 1;
175
+ } else {
176
+ write(`\x1B[31m\u2716 Unknown Error\x1B[0m
177
+ `);
178
+ process.exitCode = 1;
179
+ }
180
+ }
181
+
182
+ // src/core/interactive.ts
183
+ import * as readline from "readline";
184
+ async function startInteractive(config) {
185
+ const { logger, menuTitle, options, onExit, onSelect } = config;
186
+ const rl = readline.createInterface({
187
+ input: process.stdin,
188
+ output: process.stdout
189
+ });
190
+ const promptInputFn = (msg) => new Promise((resolve) => {
191
+ rl.question(msg, (answer) => {
192
+ resolve(answer);
193
+ });
194
+ });
195
+ function showMenu() {
196
+ logger.newline();
197
+ logger.title(menuTitle);
198
+ logger.newline();
199
+ for (let i = 0; i < options.length; i++) {
200
+ const option = options[i];
201
+ const num = logger.color(`${i + 1}.`, "cyan");
202
+ const label = logger.bold(option.label);
203
+ const desc = logger.dim(`\u2014 ${option.description}`);
204
+ logger.raw(` ${num} ${label} ${desc}`);
205
+ }
206
+ logger.raw(` ${logger.color("0.", "cyan")} ${logger.bold("Exit")} ${logger.dim("\u2014 Quit devknife")}`);
207
+ logger.newline();
208
+ }
209
+ async function run() {
210
+ showMenu();
211
+ const answer = await promptInputFn(logger.color("Select a tool (0-" + options.length + "): ", "cyan"));
212
+ const num = parseInt(answer, 10);
213
+ if (answer.toLowerCase() === "q" || answer === "0" || isNaN(num) || num < 0 || num > options.length) {
214
+ logger.info("Goodbye!");
215
+ rl.close();
216
+ onExit();
217
+ return;
218
+ }
219
+ const selected = options[num - 1];
220
+ if (selected) {
221
+ await onSelect(selected.value, promptInputFn);
222
+ await run();
223
+ } else {
224
+ logger.error("Invalid selection. Please try again.");
225
+ await run();
226
+ }
227
+ }
228
+ await run();
229
+ }
230
+
231
+ // src/tools/crypto/hash.ts
232
+ import { createHash } from "crypto";
233
+ var SUPPORTED_ALGORITHMS = ["md5", "sha1", "sha256", "sha512"];
234
+ function hash(input, algorithm = "sha256") {
235
+ return createHash(algorithm).update(input).digest("hex");
236
+ }
237
+ function isSupportedAlgorithm(algo) {
238
+ return SUPPORTED_ALGORITHMS.includes(algo);
239
+ }
240
+ function getSupportedAlgorithms() {
241
+ return SUPPORTED_ALGORITHMS;
242
+ }
243
+
244
+ // src/tools/crypto/password.ts
245
+ import { randomBytes, webcrypto } from "crypto";
246
+ var UPPERCASE = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
247
+ var LOWERCASE = "abcdefghijklmnopqrstuvwxyz";
248
+ var NUMBERS = "0123456789";
249
+ var SYMBOLS = "!@#$%^&*()_+-=[]{}|;:,.<>?";
250
+ var AMBIGUOUS = "Il1O0";
251
+ var DEFAULT_OPTIONS = {
252
+ length: 16,
253
+ uppercase: true,
254
+ lowercase: true,
255
+ numbers: true,
256
+ symbols: false,
257
+ excludeAmbiguous: false
258
+ };
259
+ function filterAmbiguous(chars, exclude) {
260
+ if (!exclude) return chars;
261
+ return chars.split("").filter((c) => !AMBIGUOUS.includes(c)).join("");
262
+ }
263
+ function secureRandomIndex(max) {
264
+ const array = new Uint32Array(1);
265
+ webcrypto.getRandomValues(array);
266
+ return array[0] % max;
267
+ }
268
+ function generatePassword(options = {}) {
269
+ const opts = { ...DEFAULT_OPTIONS, ...options };
270
+ let charset = "";
271
+ if (opts.uppercase) charset += filterAmbiguous(UPPERCASE, opts.excludeAmbiguous);
272
+ if (opts.lowercase) charset += filterAmbiguous(LOWERCASE, opts.excludeAmbiguous);
273
+ if (opts.numbers) charset += filterAmbiguous(NUMBERS, opts.excludeAmbiguous);
274
+ if (opts.symbols) charset += SYMBOLS;
275
+ if (charset.length === 0) {
276
+ charset = LOWERCASE;
277
+ }
278
+ let password = "";
279
+ if (opts.uppercase) {
280
+ const pool = filterAmbiguous(UPPERCASE, opts.excludeAmbiguous);
281
+ password += pool[secureRandomIndex(pool.length)];
282
+ }
283
+ if (opts.lowercase) {
284
+ const pool = filterAmbiguous(LOWERCASE, opts.excludeAmbiguous);
285
+ password += pool[secureRandomIndex(pool.length)];
286
+ }
287
+ if (opts.numbers) {
288
+ const pool = filterAmbiguous(NUMBERS, opts.excludeAmbiguous);
289
+ password += pool[secureRandomIndex(pool.length)];
290
+ }
291
+ if (opts.symbols) {
292
+ const pool = SYMBOLS;
293
+ password += pool[secureRandomIndex(pool.length)];
294
+ }
295
+ const remaining = opts.length - password.length;
296
+ for (let i = 0; i < remaining; i++) {
297
+ password += charset[secureRandomIndex(charset.length)];
298
+ }
299
+ const arr = password.split("");
300
+ for (let i = arr.length - 1; i > 0; i--) {
301
+ const j = secureRandomIndex(i + 1);
302
+ [arr[i], arr[j]] = [arr[j], arr[i]];
303
+ }
304
+ return arr.join("");
305
+ }
306
+ function calculateEntropy(password) {
307
+ if (password.length === 0) return 0;
308
+ const uniqueChars = new Set(password.split("")).size;
309
+ return Math.floor(password.length * Math.log2(uniqueChars));
310
+ }
311
+ function estimateStrength(password) {
312
+ const entropy = calculateEntropy(password);
313
+ if (entropy < 28) return "weak";
314
+ if (entropy < 36) return "fair";
315
+ if (entropy < 60) return "strong";
316
+ return "very-strong";
317
+ }
318
+
319
+ // src/tools/generators/uuid.ts
320
+ import { randomBytes as randomBytes2 } from "crypto";
321
+ function generateUUID() {
322
+ const bytes = randomBytes2(16);
323
+ bytes[6] = bytes[6] & 15 | 64;
324
+ bytes[8] = bytes[8] & 63 | 128;
325
+ const hex = Array.from(bytes, (b) => b.toString(16).padStart(2, "0")).join("");
326
+ return [
327
+ hex.slice(0, 8),
328
+ hex.slice(8, 12),
329
+ hex.slice(12, 16),
330
+ hex.slice(16, 20),
331
+ hex.slice(20, 32)
332
+ ].join("-");
333
+ }
334
+ function generateUUIDs(count) {
335
+ if (count < 1) throw new Error("Count must be at least 1");
336
+ return Array.from({ length: count }, () => generateUUID());
337
+ }
338
+
339
+ // src/tools/generators/nanoid.ts
340
+ import { webcrypto as webcrypto2 } from "crypto";
341
+ var DEFAULT_ALPHABET = "-_0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
342
+ var DEFAULT_SIZE = 21;
343
+ function secureRandom(max) {
344
+ const array = new Uint32Array(1);
345
+ webcrypto2.getRandomValues(array);
346
+ return array[0] % max;
347
+ }
348
+ function generateNanoID(options = {}) {
349
+ const size = options.size ?? DEFAULT_SIZE;
350
+ const alphabet = options.alphabet ?? DEFAULT_ALPHABET;
351
+ if (alphabet.length === 0) {
352
+ throw new Error("Alphabet must not be empty");
353
+ }
354
+ let id = "";
355
+ for (let i = 0; i < size; i++) {
356
+ id += alphabet[secureRandom(alphabet.length)];
357
+ }
358
+ return id;
359
+ }
360
+
361
+ // src/tools/generators/lorem.ts
362
+ var LOREM_WORDS = [
363
+ "lorem",
364
+ "ipsum",
365
+ "dolor",
366
+ "sit",
367
+ "amet",
368
+ "consectetur",
369
+ "adipiscing",
370
+ "elit",
371
+ "sed",
372
+ "do",
373
+ "eiusmod",
374
+ "tempor",
375
+ "incididunt",
376
+ "ut",
377
+ "labore",
378
+ "et",
379
+ "dolore",
380
+ "magna",
381
+ "aliqua",
382
+ "enim",
383
+ "ad",
384
+ "minim",
385
+ "veniam",
386
+ "quis",
387
+ "nostrud",
388
+ "exercitation",
389
+ "ullamco",
390
+ "laboris",
391
+ "nisi",
392
+ "aliquip",
393
+ "ex",
394
+ "ea",
395
+ "commodo",
396
+ "consequat",
397
+ "duis",
398
+ "aute",
399
+ "irure",
400
+ "in",
401
+ "reprehenderit",
402
+ "voluptate",
403
+ "velit",
404
+ "esse",
405
+ "cillum",
406
+ "fugiat",
407
+ "nulla",
408
+ "pariatur",
409
+ "excepteur",
410
+ "sint",
411
+ "occaecat",
412
+ "cupidatat",
413
+ "non",
414
+ "proident",
415
+ "sunt",
416
+ "culpa",
417
+ "qui",
418
+ "officia",
419
+ "deserunt",
420
+ "mollit",
421
+ "anim",
422
+ "id",
423
+ "est",
424
+ "laborum",
425
+ "perspiciatis",
426
+ "unde",
427
+ "omnis",
428
+ "iste",
429
+ "natus",
430
+ "error",
431
+ "voluptatem",
432
+ "accusantium",
433
+ "doloremque",
434
+ "laudantium",
435
+ "totam",
436
+ "rem",
437
+ "aperiam",
438
+ "eaque",
439
+ "ipsa",
440
+ "quae",
441
+ "ab",
442
+ "illo",
443
+ "inventore",
444
+ "veritatis",
445
+ "quasi",
446
+ "architecto",
447
+ "beatae",
448
+ "vitae",
449
+ "dicta",
450
+ "explicabo",
451
+ "nemo",
452
+ "ipsam",
453
+ "voluptas",
454
+ "aspernatur",
455
+ "aut",
456
+ "odit",
457
+ "fugit",
458
+ "consequuntur",
459
+ "magni",
460
+ "dolores",
461
+ "eos",
462
+ "ratione",
463
+ "sequi",
464
+ "nesciunt"
465
+ ];
466
+ function randomIndex(max) {
467
+ return Math.floor(Math.random() * max);
468
+ }
469
+ function capitalize(str) {
470
+ return str.charAt(0).toUpperCase() + str.slice(1);
471
+ }
472
+ function word(count = 1) {
473
+ const words = [];
474
+ for (let i = 0; i < count; i++) {
475
+ words.push(LOREM_WORDS[randomIndex(LOREM_WORDS.length)]);
476
+ }
477
+ return words.join(" ");
478
+ }
479
+ function sentence(wordCount) {
480
+ const count = wordCount ?? 8 + Math.floor(Math.random() * 8);
481
+ const words = word(count);
482
+ return capitalize(words) + ".";
483
+ }
484
+ function paragraph(sentenceCount) {
485
+ const count = sentenceCount ?? 4 + Math.floor(Math.random() * 4);
486
+ const sentences = [];
487
+ for (let i = 0; i < count; i++) {
488
+ sentences.push(sentence());
489
+ }
490
+ return sentences.join(" ");
491
+ }
492
+ function paragraphs(count = 1, separator = "\n\n") {
493
+ const result = [];
494
+ for (let i = 0; i < count; i++) {
495
+ result.push(paragraph());
496
+ }
497
+ return result.join(separator);
498
+ }
499
+ function title(wordCount) {
500
+ const count = wordCount ?? 3 + Math.floor(Math.random() * 4);
501
+ const words = word(count);
502
+ return words.split(" ").map((w) => capitalize(w)).join(" ");
503
+ }
504
+
505
+ // src/tools/encoders/base64.ts
506
+ function encode(input) {
507
+ return Buffer.from(input, "utf-8").toString("base64");
508
+ }
509
+ function decode(input) {
510
+ return Buffer.from(input, "base64").toString("utf-8");
511
+ }
512
+
513
+ // src/tools/encoders/url.ts
514
+ function encode2(input) {
515
+ return encodeURIComponent(input);
516
+ }
517
+ function decode2(input) {
518
+ return decodeURIComponent(input);
519
+ }
520
+
521
+ // src/tools/encoders/html.ts
522
+ var ENTITY_MAP = {
523
+ "&": "&amp;",
524
+ "<": "&lt;",
525
+ ">": "&gt;",
526
+ '"': "&quot;",
527
+ "'": "&#39;",
528
+ "/": "&#x2F;",
529
+ "`": "&#x60;",
530
+ "=": "&#x3D;"
531
+ };
532
+ var DECODE_MAP = {
533
+ "&amp;": "&",
534
+ "&lt;": "<",
535
+ "&gt;": ">",
536
+ "&quot;": '"',
537
+ "&#39;": "'",
538
+ "&#x27;": "'",
539
+ "&#x2F;": "/",
540
+ "&#x60;": "`",
541
+ "&#x3D;": "=",
542
+ "&apos;": "'"
543
+ };
544
+ var ENTITY_REGEX = /[&<>"'`=/]/g;
545
+ var DECODE_REGEX = /&(?:amp|lt|gt|quot|#39|#x27|#x2F|#x60|#x3D|apos);/g;
546
+ function encode3(input) {
547
+ return input.replace(ENTITY_REGEX, (char) => ENTITY_MAP[char]);
548
+ }
549
+ function decode3(input) {
550
+ return input.replace(DECODE_REGEX, (entity) => DECODE_MAP[entity]);
551
+ }
552
+
553
+ // src/tools/encoders/jwt.ts
554
+ function base64UrlDecode(str) {
555
+ let base64 = str.replace(/-/g, "+").replace(/_/g, "/");
556
+ while (base64.length % 4 !== 0) {
557
+ base64 += "=";
558
+ }
559
+ return Buffer.from(base64, "base64").toString("utf-8");
560
+ }
561
+ function decode4(token) {
562
+ const parts = token.split(".");
563
+ if (parts.length !== 3) {
564
+ throw new Error("Invalid JWT: token must have 3 parts separated by dots");
565
+ }
566
+ const [headerB64, payloadB64, signature] = parts;
567
+ let header;
568
+ let payload;
569
+ try {
570
+ header = JSON.parse(base64UrlDecode(headerB64));
571
+ } catch {
572
+ throw new Error("Invalid JWT: header is not valid JSON");
573
+ }
574
+ try {
575
+ payload = JSON.parse(base64UrlDecode(payloadB64));
576
+ } catch {
577
+ throw new Error("Invalid JWT: payload is not valid JSON");
578
+ }
579
+ return {
580
+ header,
581
+ payload,
582
+ signature
583
+ };
584
+ }
585
+
586
+ // src/tools/formatters/json.ts
587
+ function format(input, indent = 2) {
588
+ const parsed = JSON.parse(input);
589
+ return JSON.stringify(parsed, null, indent);
590
+ }
591
+ function minify(input) {
592
+ const parsed = JSON.parse(input);
593
+ return JSON.stringify(parsed);
594
+ }
595
+ function validate(input) {
596
+ try {
597
+ const data = JSON.parse(input);
598
+ return { valid: true, data };
599
+ } catch (err) {
600
+ const message = err.message;
601
+ return { valid: false, error: message };
602
+ }
603
+ }
604
+ function sortByKeys(input, indent = 2) {
605
+ const parsed = JSON.parse(input);
606
+ const sorted = {};
607
+ for (const key of Object.keys(parsed).sort()) {
608
+ sorted[key] = parsed[key];
609
+ }
610
+ return JSON.stringify(sorted, null, indent);
611
+ }
612
+
613
+ // src/tools/formatters/text.ts
614
+ function camelCase(input) {
615
+ return input.replace(/[-_\s]+(.)?/g, (_, c) => c ? c.toUpperCase() : "").replace(/^[A-Z]/, (c) => c.toLowerCase());
616
+ }
617
+ function snakeCase(input) {
618
+ return input.replace(/([A-Z])/g, "_$1").replace(/[-\s]+/g, "_").replace(/^_/, "").toLowerCase();
619
+ }
620
+ function kebabCase(input) {
621
+ return input.replace(/([A-Z])/g, "-$1").replace(/[_\s]+/g, "-").replace(/^-/, "").toLowerCase();
622
+ }
623
+ function pascalCase(input) {
624
+ const camel = camelCase(input);
625
+ return camel.charAt(0).toUpperCase() + camel.slice(1);
626
+ }
627
+ function constantCase(input) {
628
+ return snakeCase(input).toUpperCase();
629
+ }
630
+ function dotCase(input) {
631
+ return input.replace(/([A-Z])/g, ".$1").replace(/[-_\s]+/g, ".").replace(/^\.|\.+$/g, "").toLowerCase();
632
+ }
633
+ function titleCase(input) {
634
+ return input.split(/[-_\s]+/).map((word2) => word2.charAt(0).toUpperCase() + word2.slice(1).toLowerCase()).join(" ");
635
+ }
636
+ function sentenceCase(input) {
637
+ const result = input.replace(/[-_\s]+/g, " ").trim();
638
+ return result.charAt(0).toUpperCase() + result.slice(1).toLowerCase();
639
+ }
640
+ function upperCase(input) {
641
+ return input.toUpperCase();
642
+ }
643
+ function lowerCase(input) {
644
+ return input.toLowerCase();
645
+ }
646
+ function reverseCase(input) {
647
+ return input.split("").map((c) => c === c.toUpperCase() ? c.toLowerCase() : c.toUpperCase()).join("");
648
+ }
649
+ function slugify(input) {
650
+ return input.toLowerCase().trim().replace(/[^\w\s-]/g, "").replace(/[-_\s]+/g, "-").replace(/^-+|-+$/g, "");
651
+ }
652
+
653
+ // src/tools/converters/color.ts
654
+ function hexToRgb(hex) {
655
+ const clean = hex.replace("#", "");
656
+ if (clean.length !== 3 && clean.length !== 6) {
657
+ throw new Error("Invalid HEX color: must be 3 or 6 characters");
658
+ }
659
+ const full = clean.length === 3 ? clean.split("").map((c) => c + c).join("") : clean;
660
+ const r = parseInt(full.slice(0, 2), 16);
661
+ const g = parseInt(full.slice(2, 4), 16);
662
+ const b = parseInt(full.slice(4, 6), 16);
663
+ if (isNaN(r) || isNaN(g) || isNaN(b)) {
664
+ throw new Error("Invalid HEX color: contains non-hex characters");
665
+ }
666
+ return { r, g, b };
667
+ }
668
+ function rgbToHex(r, g, b) {
669
+ const clamp = (n) => Math.max(0, Math.min(255, Math.round(n)));
670
+ const toHex = (n) => clamp(n).toString(16).padStart(2, "0");
671
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
672
+ }
673
+ function rgbToHsl(r, g, b) {
674
+ const rn = r / 255;
675
+ const gn = g / 255;
676
+ const bn = b / 255;
677
+ const max = Math.max(rn, gn, bn);
678
+ const min = Math.min(rn, gn, bn);
679
+ const l = (max + min) / 2;
680
+ let h = 0;
681
+ let s = 0;
682
+ if (max !== min) {
683
+ const d = max - min;
684
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
685
+ switch (max) {
686
+ case rn:
687
+ h = ((gn - bn) / d + (gn < bn ? 6 : 0)) / 6;
688
+ break;
689
+ case gn:
690
+ h = ((bn - rn) / d + 2) / 6;
691
+ break;
692
+ case bn:
693
+ h = ((rn - gn) / d + 4) / 6;
694
+ break;
695
+ }
696
+ }
697
+ return {
698
+ h: Math.round(h * 360),
699
+ s: Math.round(s * 100),
700
+ l: Math.round(l * 100)
701
+ };
702
+ }
703
+ function hslToRgb(h, s, l) {
704
+ const sn = s / 100;
705
+ const ln = l / 100;
706
+ if (sn === 0) {
707
+ const val = Math.round(ln * 255);
708
+ return { r: val, g: val, b: val };
709
+ }
710
+ const hue2rgb = (p2, q2, t) => {
711
+ let tn = t;
712
+ if (tn < 0) tn += 1;
713
+ if (tn > 1) tn -= 1;
714
+ if (tn < 1 / 6) return p2 + (q2 - p2) * 6 * tn;
715
+ if (tn < 1 / 2) return q2;
716
+ if (tn < 2 / 3) return p2 + (q2 - p2) * (2 / 3 - tn) * 6;
717
+ return p2;
718
+ };
719
+ const q = ln < 0.5 ? ln * (1 + sn) : ln + sn - ln * sn;
720
+ const p = 2 * ln - q;
721
+ const hNorm = h / 360;
722
+ return {
723
+ r: Math.round(hue2rgb(p, q, hNorm + 1 / 3) * 255),
724
+ g: Math.round(hue2rgb(p, q, hNorm) * 255),
725
+ b: Math.round(hue2rgb(p, q, hNorm - 1 / 3) * 255)
726
+ };
727
+ }
728
+ function hexToHsl(hex) {
729
+ const { r, g, b } = hexToRgb(hex);
730
+ return rgbToHsl(r, g, b);
731
+ }
732
+ function hslToHex(h, s, l) {
733
+ const { r, g, b } = hslToRgb(h, s, l);
734
+ return rgbToHex(r, g, b);
735
+ }
736
+ function isLightColor(hex) {
737
+ const { r, g, b } = hexToRgb(hex);
738
+ const luminance = (0.299 * r + 0.587 * g + 0.114 * b) / 255;
739
+ return luminance > 0.5;
740
+ }
741
+
742
+ // src/tools/converters/time.ts
743
+ function epochToIso(epoch) {
744
+ return new Date(epoch * 1e3).toISOString();
745
+ }
746
+ function isoToEpoch(iso) {
747
+ const ms = new Date(iso).getTime();
748
+ if (isNaN(ms)) {
749
+ throw new Error("Invalid ISO date string");
750
+ }
751
+ return Math.floor(ms / 1e3);
752
+ }
753
+ function nowEpoch() {
754
+ return Math.floor(Date.now() / 1e3);
755
+ }
756
+ function nowIso() {
757
+ return (/* @__PURE__ */ new Date()).toISOString();
758
+ }
759
+ function timeAgo(epoch) {
760
+ const now = Math.floor(Date.now() / 1e3);
761
+ const diff = now - epoch;
762
+ if (diff < 0) return "in the future";
763
+ const intervals = [
764
+ ["year", 31536e3],
765
+ ["month", 2592e3],
766
+ ["week", 604800],
767
+ ["day", 86400],
768
+ ["hour", 3600],
769
+ ["minute", 60],
770
+ ["second", 1]
771
+ ];
772
+ for (const [label, seconds] of intervals) {
773
+ const count = Math.floor(diff / seconds);
774
+ if (count >= 1) {
775
+ return `${count} ${label}${count > 1 ? "s" : ""} ago`;
776
+ }
777
+ }
778
+ return "just now";
779
+ }
780
+ function formatDate(iso, format2) {
781
+ const date = new Date(iso);
782
+ if (isNaN(date.getTime())) throw new Error("Invalid date string");
783
+ return format2.replace("YYYY", date.getFullYear().toString()).replace("MM", String(date.getMonth() + 1).padStart(2, "0")).replace("DD", String(date.getDate()).padStart(2, "0")).replace("HH", String(date.getHours()).padStart(2, "0")).replace("mm", String(date.getMinutes()).padStart(2, "0")).replace("ss", String(date.getSeconds()).padStart(2, "0"));
784
+ }
785
+ function getWeekNumber(iso) {
786
+ const date = new Date(iso);
787
+ if (isNaN(date.getTime())) throw new Error("Invalid date string");
788
+ const d = new Date(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()));
789
+ const dayNum = d.getUTCDay() || 7;
790
+ d.setUTCDate(d.getUTCDate() + 4 - dayNum);
791
+ const yearStart = new Date(Date.UTC(d.getUTCFullYear(), 0, 1));
792
+ return Math.ceil(((d.getTime() - yearStart.getTime()) / 864e5 + 1) / 7);
793
+ }
794
+
795
+ // src/tools/converters/number.ts
796
+ function decimalToBinary(decimal) {
797
+ if (!Number.isInteger(decimal)) throw new Error("Input must be an integer");
798
+ return decimal.toString(2);
799
+ }
800
+ function binaryToDecimal(binary) {
801
+ if (!/^[01]+$/.test(binary)) throw new Error("Invalid binary string");
802
+ return parseInt(binary, 2);
803
+ }
804
+ function decimalToHex(decimal) {
805
+ if (!Number.isInteger(decimal)) throw new Error("Input must be an integer");
806
+ return decimal.toString(16);
807
+ }
808
+ function hexToDecimal(hex) {
809
+ if (!/^[0-9a-fA-F]+$/.test(hex)) throw new Error("Invalid hexadecimal string");
810
+ return parseInt(hex, 16);
811
+ }
812
+ function decimalToOctal(decimal) {
813
+ if (!Number.isInteger(decimal)) throw new Error("Input must be an integer");
814
+ return parseInt(decimal.toString(8), 10);
815
+ }
816
+ function octalToDecimal(octal) {
817
+ if (!/^[0-7]+$/.test(octal)) throw new Error("Invalid octal string");
818
+ return parseInt(octal, 8);
819
+ }
820
+ function hexToBinary(hex) {
821
+ const decimal = hexToDecimal(hex);
822
+ return decimalToBinary(decimal);
823
+ }
824
+ function binaryToHex(binary) {
825
+ const decimal = binaryToDecimal(binary);
826
+ return decimalToHex(decimal);
827
+ }
828
+ function formatBytes(bytes, decimals = 2) {
829
+ if (bytes === 0) return "0 Bytes";
830
+ if (bytes < 0) throw new Error("Bytes cannot be negative");
831
+ const k = 1024;
832
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"];
833
+ const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1);
834
+ const sizeStr = sizes[i];
835
+ return `${parseFloat((bytes / Math.pow(k, i)).toFixed(decimals))} ${sizeStr}`;
836
+ }
837
+
838
+ // src/tools/network/ip.ts
839
+ import * as os from "os";
840
+ var IPv4_REGEX = /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/;
841
+ function isValidIPv4(ip) {
842
+ return IPv4_REGEX.test(ip);
843
+ }
844
+ var IPv6_REGEX = /^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
845
+ function isValidIPv6(ip) {
846
+ if (ip.includes("::")) {
847
+ const parts = ip.split("::");
848
+ if (parts.length !== 2) return false;
849
+ const left = parts[0].split(":").filter(Boolean);
850
+ const right = parts[1].split(":").filter(Boolean);
851
+ return left.length + right.length <= 7 && left.length + right.length >= 1;
852
+ }
853
+ return IPv6_REGEX.test(ip);
854
+ }
855
+ function getLocalIPs() {
856
+ const interfaces = os.networkInterfaces();
857
+ const ips = [];
858
+ for (const name of Object.keys(interfaces)) {
859
+ const iface = interfaces[name];
860
+ if (iface === void 0) continue;
861
+ for (const addr of iface) {
862
+ if (addr.family === "IPv4" && !addr.internal) {
863
+ ips.push(addr.address);
864
+ }
865
+ }
866
+ }
867
+ return ips;
868
+ }
869
+ function isPrivateIP(ip) {
870
+ if (!isValidIPv4(ip)) return false;
871
+ const parts = ip.split(".").map(Number);
872
+ return parts[0] === 10 || parts[0] === 172 && parts[1] >= 16 && parts[1] <= 31 || parts[0] === 192 && parts[1] === 168 || parts[0] === 127;
873
+ }
874
+ function isReservedIP(ip) {
875
+ if (!isValidIPv4(ip)) return false;
876
+ const parts = ip.split(".").map(Number);
877
+ if (parts[0] === 0) return true;
878
+ if (parts[0] === 127) return true;
879
+ if (parts[0] === 169 && parts[1] === 254) return true;
880
+ if (parts[0] >= 224) return true;
881
+ return false;
882
+ }
883
+ function getHostname() {
884
+ return os.hostname();
885
+ }
886
+
887
+ // src/tools/network/mac.ts
888
+ import { randomBytes as randomBytes3 } from "crypto";
889
+ var MAC_REGEX = /^([0-9A-Fa-f]{2}[:-]){5}[0-9A-Fa-f]{2}$/;
890
+ function isValidMAC(mac) {
891
+ return MAC_REGEX.test(mac);
892
+ }
893
+ function generateMAC(separator = ":") {
894
+ const bytes = Array.from(randomBytes3(6), (b) => b.toString(16).padStart(2, "0").toUpperCase());
895
+ bytes[0] = (parseInt(bytes[0], 16) & 254 | 2).toString(16).padStart(2, "0").toUpperCase();
896
+ return bytes.join(separator);
897
+ }
898
+ function normalizeMAC(mac, separator = ":") {
899
+ const clean = mac.replace(/[-:]/g, "").toUpperCase();
900
+ if (clean.length !== 12) throw new Error("Invalid MAC address length");
901
+ return clean.match(/.{2}/g).join(separator);
902
+ }
903
+
904
+ // src/cli.ts
905
+ var VERSION = "1.0.0";
906
+ var log = createLogger();
907
+ var HELP_TEXT = `
908
+ ${log.color("\u2554\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2557", "cyan")}
909
+ ${log.color("\u2551", "cyan")} ${log.bold(log.color("devknife", "green"))} \u2014 Developer Swiss Army Knife ${log.color("\u2551", "cyan")}
910
+ ${log.color("\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u2550\u255D", "cyan")}
911
+
912
+ ${log.bold("Usage:")}
913
+ ${log.color("devknife", "green")} <${log.color("tool", "cyan")}> <${log.color("subcommand", "yellow")}> [${log.color("args", "gray")}] [${log.color("--flag", "magenta")}]
914
+
915
+ ${log.bold("Tools:")}
916
+
917
+ ${log.color(" Crypto:", "cyan")}
918
+ ${log.color("hash", "green")} ${log.dim("\u2014 Generate hash (md5, sha1, sha256, sha512)")}
919
+ ${log.color("password", "green")} ${log.dim("\u2014 Generate secure password")}
920
+
921
+ ${log.color(" Generators:", "cyan")}
922
+ ${log.color("uuid", "green")} ${log.dim("\u2014 Generate UUID v4")}
923
+ ${log.color("nanoid", "green")} ${log.dim("\u2014 Generate NanoID string")}
924
+ ${log.color("lorem", "green")} ${log.dim("\u2014 Generate Lorem Ipsum text")}
925
+
926
+ ${log.color(" Encoders:", "cyan")}
927
+ ${log.color("base64", "green")} ${log.dim("\u2014 Base64 encode/decode")}
928
+ ${log.color("url", "green")} ${log.dim("\u2014 URL encode/decode")}
929
+ ${log.color("html", "green")} ${log.dim("\u2014 HTML entity encode/decode")}
930
+ ${log.color("jwt", "green")} ${log.dim("\u2014 Decode JWT token")}
931
+
932
+ ${log.color(" Formatters:", "cyan")}
933
+ ${log.color("json", "green")} ${log.dim("\u2014 Format/minify JSON")}
934
+ ${log.color("text", "green")} ${log.dim("\u2014 Convert text case (camel, snake, kebab...)")}
935
+
936
+ ${log.color(" Converters:", "cyan")}
937
+ ${log.color("color", "green")} ${log.dim("\u2014 Convert colors (hex, rgb, hsl)")}
938
+ ${log.color("time", "green")} ${log.dim("\u2014 Convert timestamps")}
939
+ ${log.color("number", "green")} ${log.dim("\u2014 Convert number bases (bin, hex, oct)")}
940
+
941
+ ${log.color(" Network:", "cyan")}
942
+ ${log.color("ip", "green")} ${log.dim("\u2014 Validate/show IP addresses")}
943
+ ${log.color("mac", "green")} ${log.dim("\u2014 Generate/validate MAC addresses")}
944
+
945
+ ${log.bold("Flags:")}
946
+ ${log.color("-h, --help", "yellow")} ${log.dim("Show this help message")}
947
+ ${log.color("-v, --version", "yellow")} ${log.dim("Show version")}
948
+ ${log.color("-i, --interactive", "yellow")} ${log.dim("Start interactive mode")}
949
+ ${log.color("-s, --stdin", "yellow")} ${log.dim("Read input from stdin")}
950
+
951
+ ${log.bold("Examples:")}
952
+ ${log.color("$ devknife uuid", "gray")}
953
+ ${log.color('$ devknife hash sha256 "Hello World"', "gray")}
954
+ ${log.color('$ devknife base64 encode "hello"', "gray")}
955
+ ${log.color("$ devknife jwt decode eyJhbGci...", "gray")}
956
+ ${log.color(`$ echo '{"a":1}' | devknife json format`, "gray")}
957
+ ${log.color('$ devknife color hex-to-rgb "#ff0000"', "gray")}
958
+ `;
959
+ function getStdin() {
960
+ return new Promise((resolve) => {
961
+ if (process.stdin.isTTY) {
962
+ resolve("");
963
+ return;
964
+ }
965
+ let data = "";
966
+ process.stdin.setEncoding("utf-8");
967
+ process.stdin.on("data", (chunk) => {
968
+ data += chunk;
969
+ });
970
+ process.stdin.on("end", () => {
971
+ resolve(data.trim());
972
+ });
973
+ });
974
+ }
975
+ var tools = {
976
+ hash: {
977
+ description: "Generate hash (md5, sha1, sha256, sha512)",
978
+ usage: "hash <algorithm> <text>",
979
+ examples: ['hash sha256 "Hello World"', 'hash md5 "test"'],
980
+ run: (args) => {
981
+ const algo = args[0] ?? "";
982
+ const input = args[1] ?? "";
983
+ if (!algo || !input) {
984
+ return `Usage: devknife hash <algorithm> <text>
985
+ Algorithms: ${getSupportedAlgorithms().join(", ")}`;
986
+ }
987
+ if (!isSupportedAlgorithm(algo)) {
988
+ throw new Error(`Unsupported algorithm: ${algo}. Use: ${getSupportedAlgorithms().join(", ")}`);
989
+ }
990
+ return hash(input, algo);
991
+ }
992
+ },
993
+ password: {
994
+ description: "Generate secure password",
995
+ usage: "password [--length N] [--symbols] [--no-ambiguous]",
996
+ examples: ["password", "password --length 32 --symbols"],
997
+ run: (args, _flags) => {
998
+ const lengthIdx = args.indexOf("--length");
999
+ const length = lengthIdx !== -1 ? parseInt(args[lengthIdx + 1] ?? "16", 10) : 16;
1000
+ const hasSymbols = args.includes("--symbols");
1001
+ const noAmbiguous = args.includes("--no-ambiguous");
1002
+ const pw = generatePassword({ length, numbers: true, symbols: hasSymbols, excludeAmbiguous: noAmbiguous });
1003
+ const entropy = calculateEntropy(pw);
1004
+ const strength = estimateStrength(pw);
1005
+ return `Password: ${pw}
1006
+ Length: ${pw.length}
1007
+ Entropy: ${entropy} bits
1008
+ Strength: ${strength}`;
1009
+ }
1010
+ },
1011
+ uuid: {
1012
+ description: "Generate UUID v4",
1013
+ usage: "uuid [--count N]",
1014
+ examples: ["uuid", "uuid --count 5"],
1015
+ run: (args) => {
1016
+ const countIdx = args.indexOf("--count");
1017
+ const count = countIdx !== -1 ? parseInt(args[countIdx + 1] ?? "1", 10) : 1;
1018
+ if (count === 1) return generateUUID();
1019
+ return generateUUIDs(count).join("\n");
1020
+ }
1021
+ },
1022
+ nanoid: {
1023
+ description: "Generate NanoID string",
1024
+ usage: "nanoid [--size N]",
1025
+ examples: ["nanoid", "nanoid --size 10"],
1026
+ run: (args) => {
1027
+ const sizeIdx = args.indexOf("--size");
1028
+ const size = sizeIdx !== -1 ? parseInt(args[sizeIdx + 1] ?? "21", 10) : 21;
1029
+ return generateNanoID({ size });
1030
+ }
1031
+ },
1032
+ lorem: {
1033
+ description: "Generate Lorem Ipsum text",
1034
+ usage: "lorem <paragraphs|sentences|words> [count]",
1035
+ examples: ["lorem paragraphs 2", "lorem sentences 3", "lorem words 10"],
1036
+ run: (args) => {
1037
+ const type = args[0] ?? "paragraph";
1038
+ const count = parseInt(args[1] ?? "1", 10);
1039
+ switch (type) {
1040
+ case "paragraph":
1041
+ case "paragraphs":
1042
+ return paragraphs(count);
1043
+ case "sentence":
1044
+ case "sentences": {
1045
+ const sentences = [];
1046
+ for (let i = 0; i < count; i++) sentences.push(sentence());
1047
+ return sentences.join(" ");
1048
+ }
1049
+ case "word":
1050
+ case "words":
1051
+ return word(count);
1052
+ case "title":
1053
+ return title();
1054
+ default:
1055
+ return paragraphs(count);
1056
+ }
1057
+ }
1058
+ },
1059
+ base64: {
1060
+ description: "Base64 encode/decode",
1061
+ usage: "base64 <encode|decode> <text>",
1062
+ examples: ['base64 encode "hello"', 'base64 decode "aGVsbG8="'],
1063
+ run: (args, stdinData) => {
1064
+ const subcommand = args[0] ?? "";
1065
+ const input = args[1] ?? stdinData ?? "";
1066
+ if (!subcommand || !input && subcommand) {
1067
+ return "Usage: devknife base64 <encode|decode> <text>";
1068
+ }
1069
+ if (subcommand === "encode") return encode(input);
1070
+ if (subcommand === "decode") return decode(input);
1071
+ throw new SubcommandNotFoundError("base64", subcommand);
1072
+ }
1073
+ },
1074
+ url: {
1075
+ description: "URL encode/decode",
1076
+ usage: "url <encode|decode> <text>",
1077
+ examples: ['url encode "hello world"', 'url decode "hello%20world"'],
1078
+ run: (args) => {
1079
+ const subcommand = args[0] ?? "";
1080
+ const input = args[1] ?? "";
1081
+ if (!subcommand || !input) return "Usage: devknife url <encode|decode> <text>";
1082
+ if (subcommand === "encode") return encode2(input);
1083
+ if (subcommand === "decode") return decode2(input);
1084
+ throw new SubcommandNotFoundError("url", subcommand);
1085
+ }
1086
+ },
1087
+ html: {
1088
+ description: "HTML entity encode/decode",
1089
+ usage: "html <encode|decode> <text>",
1090
+ examples: ['html encode "<div>"', 'html decode "&lt;div&gt;"'],
1091
+ run: (args) => {
1092
+ const subcommand = args[0] ?? "";
1093
+ const input = args[1] ?? "";
1094
+ if (!subcommand || !input) return "Usage: devknife html <encode|decode> <text>";
1095
+ if (subcommand === "encode") return encode3(input);
1096
+ if (subcommand === "decode") return decode3(input);
1097
+ throw new SubcommandNotFoundError("html", subcommand);
1098
+ }
1099
+ },
1100
+ jwt: {
1101
+ description: "Decode JWT token",
1102
+ usage: "jwt decode <token>",
1103
+ examples: ["jwt decode eyJhbGciOiJIUzI1NiJ9..."],
1104
+ run: (args) => {
1105
+ const subcommand = args[0] ?? "";
1106
+ const token = args[1] ?? "";
1107
+ if (!subcommand || !token) return "Usage: devknife jwt decode <token>";
1108
+ if (subcommand === "decode") {
1109
+ try {
1110
+ const result = decode4(token);
1111
+ return `Header:
1112
+ ${JSON.stringify(result.header, null, 2)}
1113
+
1114
+ Payload:
1115
+ ${JSON.stringify(result.payload, null, 2)}`;
1116
+ } catch (err) {
1117
+ return `Error: ${err instanceof Error ? err.message : "Invalid token"}`;
1118
+ }
1119
+ }
1120
+ throw new SubcommandNotFoundError("jwt", subcommand);
1121
+ }
1122
+ },
1123
+ json: {
1124
+ description: "Format/minify JSON",
1125
+ usage: "json <format|minify|validate> [json]",
1126
+ examples: [`json format '{"a":1}'`, `json minify '{"a": 1}'`],
1127
+ run: (args, stdinData) => {
1128
+ const subcommand = args[0] ?? "";
1129
+ const input = args[1] ?? stdinData ?? "";
1130
+ if (!subcommand || !input && subcommand) {
1131
+ return "Usage: devknife json <format|minify|validate|sort> [json]";
1132
+ }
1133
+ try {
1134
+ if (subcommand === "format" || subcommand === "prettify") return format(input);
1135
+ if (subcommand === "minify" || subcommand === "compact") return minify(input);
1136
+ if (subcommand === "validate") {
1137
+ const result = validate(input);
1138
+ if (result.valid) return "\u2713 Valid JSON";
1139
+ return `\u2717 Invalid JSON: ${result.error}`;
1140
+ }
1141
+ if (subcommand === "sort") return sortByKeys(input);
1142
+ throw new SubcommandNotFoundError("json", subcommand);
1143
+ } catch (err) {
1144
+ return `Error: ${err instanceof Error ? err.message : "Invalid JSON"}`;
1145
+ }
1146
+ }
1147
+ },
1148
+ text: {
1149
+ description: "Convert text case",
1150
+ usage: "text <case> <text>",
1151
+ examples: ['text camel "hello world"', 'text snake "helloWorld"', 'text kebab "hello World"'],
1152
+ run: (args) => {
1153
+ const caseType = args[0] ?? "";
1154
+ const input = args[1] ?? "";
1155
+ if (!caseType || !input) return "Usage: devknife text <camel|snake|kebab|pascal|constant|title|upper|lower|sentence|reverse> <text>";
1156
+ const cases = {
1157
+ camel: camelCase,
1158
+ snake: snakeCase,
1159
+ kebab: kebabCase,
1160
+ pascal: pascalCase,
1161
+ constant: constantCase,
1162
+ title: titleCase,
1163
+ upper: upperCase,
1164
+ lower: lowerCase,
1165
+ sentence: sentenceCase,
1166
+ reverse: reverseCase,
1167
+ dot: dotCase,
1168
+ slug: slugify
1169
+ };
1170
+ const fn = cases[caseType];
1171
+ if (!fn) throw new SubcommandNotFoundError("text", caseType);
1172
+ return fn(input);
1173
+ }
1174
+ },
1175
+ color: {
1176
+ description: "Convert colors (hex, rgb, hsl)",
1177
+ usage: "color <conversion> <value>",
1178
+ examples: ['color hex-to-rgb "#ff0000"', "color rgb-to-hex 255 0 0"],
1179
+ run: (args) => {
1180
+ const conversion = args[0] ?? "";
1181
+ if (!conversion) return "Usage: devknife color <hex-to-rgb|rgb-to-hex|rgb-to-hsl|hsl-to-hex|is-light> <value>";
1182
+ try {
1183
+ switch (conversion) {
1184
+ case "hex-to-rgb": {
1185
+ const result = hexToRgb(args[1] ?? "");
1186
+ return `RGB: r=${result.r}, g=${result.g}, b=${result.b}`;
1187
+ }
1188
+ case "rgb-to-hex":
1189
+ return rgbToHex(
1190
+ parseInt(args[1] ?? "0", 10),
1191
+ parseInt(args[2] ?? "0", 10),
1192
+ parseInt(args[3] ?? "0", 10)
1193
+ );
1194
+ case "rgb-to-hsl": {
1195
+ const result = rgbToHsl(
1196
+ parseInt(args[1] ?? "0", 10),
1197
+ parseInt(args[2] ?? "0", 10),
1198
+ parseInt(args[3] ?? "0", 10)
1199
+ );
1200
+ return `HSL: h=${result.h}\xB0, s=${result.s}%, l=${result.l}%`;
1201
+ }
1202
+ case "hex-to-hsl": {
1203
+ const result = hexToHsl(args[1] ?? "");
1204
+ return `HSL: h=${result.h}\xB0, s=${result.s}%, l=${result.l}%`;
1205
+ }
1206
+ case "hsl-to-hex":
1207
+ return hslToHex(
1208
+ parseInt(args[1] ?? "0", 10),
1209
+ parseInt(args[2] ?? "0", 10),
1210
+ parseInt(args[3] ?? "0", 10)
1211
+ );
1212
+ case "hsl-to-rgb": {
1213
+ const result = hslToRgb(
1214
+ parseInt(args[1] ?? "0", 10),
1215
+ parseInt(args[2] ?? "0", 10),
1216
+ parseInt(args[3] ?? "0", 10)
1217
+ );
1218
+ return `RGB: r=${result.r}, g=${result.g}, b=${result.b}`;
1219
+ }
1220
+ case "is-light": {
1221
+ const light = isLightColor(args[1] ?? "");
1222
+ return light ? "Light color" : "Dark color";
1223
+ }
1224
+ default:
1225
+ throw new SubcommandNotFoundError("color", conversion);
1226
+ }
1227
+ } catch (err) {
1228
+ return `Error: ${err instanceof Error ? err.message : "Invalid input"}`;
1229
+ }
1230
+ }
1231
+ },
1232
+ time: {
1233
+ description: "Convert timestamps",
1234
+ usage: "time <conversion> <value>",
1235
+ examples: ["time epoch-to-iso 1672531200", 'time iso-to-epoch "2023-01-01T00:00:00Z"', "time now"],
1236
+ run: (args) => {
1237
+ const conversion = args[0] ?? "";
1238
+ if (!conversion) return "Usage: devknife time <epoch-to-iso|iso-to-epoch|now|time-ago|format> <value>";
1239
+ try {
1240
+ switch (conversion) {
1241
+ case "epoch-to-iso":
1242
+ return epochToIso(parseInt(args[1] ?? "0", 10));
1243
+ case "iso-to-epoch":
1244
+ return isoToEpoch(args[1] ?? "").toString();
1245
+ case "now":
1246
+ return `${nowIso()} (epoch: ${nowEpoch()})`;
1247
+ case "time-ago":
1248
+ return timeAgo(parseInt(args[1] ?? "0", 10));
1249
+ case "format": {
1250
+ const fmt = args[2] ?? "YYYY-MM-DD HH:mm:ss";
1251
+ return formatDate(args[1] ?? "", fmt);
1252
+ }
1253
+ case "week":
1254
+ return `Week ${getWeekNumber(args[1] ?? (/* @__PURE__ */ new Date()).toISOString())}`;
1255
+ default:
1256
+ throw new SubcommandNotFoundError("time", conversion);
1257
+ }
1258
+ } catch (err) {
1259
+ return `Error: ${err instanceof Error ? err.message : "Invalid input"}`;
1260
+ }
1261
+ }
1262
+ },
1263
+ number: {
1264
+ description: "Convert number bases",
1265
+ usage: "number <conversion> <value>",
1266
+ examples: ["number dec-to-bin 42", "number bin-to-dec 101010", "number dec-to-hex 255"],
1267
+ run: (args) => {
1268
+ const conversion = args[0] ?? "";
1269
+ if (!conversion) return "Usage: devknife number <dec-to-bin|bin-to-dec|dec-to-hex|hex-to-dec|dec-to-oct|oct-to-dec|format-bytes> <value>";
1270
+ try {
1271
+ switch (conversion) {
1272
+ case "dec-to-bin":
1273
+ return decimalToBinary(parseInt(args[1] ?? "0", 10));
1274
+ case "bin-to-dec":
1275
+ return binaryToDecimal(args[1] ?? "0").toString();
1276
+ case "dec-to-hex":
1277
+ return decimalToHex(parseInt(args[1] ?? "0", 10));
1278
+ case "hex-to-dec":
1279
+ return hexToDecimal(args[1] ?? "0").toString();
1280
+ case "dec-to-oct":
1281
+ return decimalToOctal(parseInt(args[1] ?? "0", 10)).toString();
1282
+ case "oct-to-dec":
1283
+ return octalToDecimal(args[1] ?? "0").toString();
1284
+ case "hex-to-bin":
1285
+ return hexToBinary(args[1] ?? "0");
1286
+ case "bin-to-hex":
1287
+ return binaryToHex(args[1] ?? "0");
1288
+ case "format-bytes":
1289
+ return formatBytes(parseInt(args[1] ?? "0", 10));
1290
+ default:
1291
+ throw new SubcommandNotFoundError("number", conversion);
1292
+ }
1293
+ } catch (err) {
1294
+ return `Error: ${err instanceof Error ? err.message : "Invalid input"}`;
1295
+ }
1296
+ }
1297
+ },
1298
+ ip: {
1299
+ description: "Validate/show IP addresses",
1300
+ usage: "ip <validate|local|private|reserved|hostname>",
1301
+ examples: ["ip validate 192.168.1.1", "ip local", "ip hostname"],
1302
+ run: (args) => {
1303
+ const subcommand = args[0] ?? "";
1304
+ if (!subcommand) return "Usage: devknife ip <validate|local|private|reserved|hostname> [address]";
1305
+ switch (subcommand) {
1306
+ case "validate":
1307
+ case "is-valid": {
1308
+ const ip = args[1] ?? "";
1309
+ if (!ip) return "Please provide an IP address";
1310
+ if (isValidIPv4(ip)) return `${ip} is a valid IPv4 address`;
1311
+ if (isValidIPv6(ip)) return `${ip} is a valid IPv6 address`;
1312
+ return `${ip} is not a valid IP address`;
1313
+ }
1314
+ case "local": {
1315
+ const ips = getLocalIPs();
1316
+ return ips.length > 0 ? ips.join("\n") : "No local IPs found";
1317
+ }
1318
+ case "private": {
1319
+ const ip = args[1] ?? "";
1320
+ if (!ip) return "Please provide an IP address";
1321
+ return isPrivateIP(ip) ? `${ip} is a private IP` : `${ip} is a public IP`;
1322
+ }
1323
+ case "reserved": {
1324
+ const ip = args[1] ?? "";
1325
+ if (!ip) return "Please provide an IP address";
1326
+ return isReservedIP(ip) ? `${ip} is reserved` : `${ip} is not reserved`;
1327
+ }
1328
+ case "hostname":
1329
+ return getHostname();
1330
+ default:
1331
+ throw new SubcommandNotFoundError("ip", subcommand);
1332
+ }
1333
+ }
1334
+ },
1335
+ mac: {
1336
+ description: "Generate/validate MAC address",
1337
+ usage: "mac <generate|validate|normalize> [address]",
1338
+ examples: ["mac generate", 'mac validate "00:1A:2B:3C:4D:5E"'],
1339
+ run: (args) => {
1340
+ const subcommand = args[0] ?? "";
1341
+ if (!subcommand) return "Usage: devknife mac <generate|validate|normalize> [address]";
1342
+ switch (subcommand) {
1343
+ case "generate": {
1344
+ const sep = args[1] ?? ":";
1345
+ return generateMAC(sep);
1346
+ }
1347
+ case "validate": {
1348
+ const mac = args[1] ?? "";
1349
+ if (!mac) return "Please provide a MAC address";
1350
+ return isValidMAC(mac) ? `${mac} is a valid MAC address` : `${mac} is not a valid MAC address`;
1351
+ }
1352
+ case "normalize": {
1353
+ const mac = args[1] ?? "";
1354
+ const sep = args[2] ?? ":";
1355
+ if (!mac) return "Please provide a MAC address";
1356
+ return normalizeMAC(mac, sep);
1357
+ }
1358
+ default:
1359
+ throw new SubcommandNotFoundError("mac", subcommand);
1360
+ }
1361
+ }
1362
+ }
1363
+ };
1364
+ async function main() {
1365
+ const parsed = parse();
1366
+ if (parsed.flags["help"] || parsed.flags["h"]) {
1367
+ log.raw(HELP_TEXT);
1368
+ return;
1369
+ }
1370
+ if (parsed.flags["version"] || parsed.flags["v"]) {
1371
+ log.raw(`devknife v${VERSION}`);
1372
+ return;
1373
+ }
1374
+ if (parsed.flags["interactive"] || parsed.flags["i"] || !parsed.command) {
1375
+ const options = Object.entries(tools).map(([name, tool2]) => ({
1376
+ label: name,
1377
+ value: name,
1378
+ description: tool2.description
1379
+ }));
1380
+ await startInteractive({
1381
+ logger: log,
1382
+ menuTitle: "\u{1F52A} devknife \u2014 Interactive Mode",
1383
+ options,
1384
+ onExit: () => {
1385
+ },
1386
+ onSelect: async (value, promptInput) => {
1387
+ const tool2 = tools[value];
1388
+ if (!tool2) {
1389
+ log.error(`Tool not found: ${value}`);
1390
+ return;
1391
+ }
1392
+ log.newline();
1393
+ log.info(`Tool: ${value}`);
1394
+ log.dim(`Usage: ${tool2.usage}`);
1395
+ log.dim(`Examples: ${tool2.examples.join(", ")}`);
1396
+ log.newline();
1397
+ if (promptInput) {
1398
+ const input = await promptInput(log.color("Enter input (or press Enter to skip): ", "cyan"));
1399
+ if (input.trim()) {
1400
+ const result2 = tool2.run([input.trim()], "");
1401
+ if (result2) log.raw(result2);
1402
+ }
1403
+ }
1404
+ }
1405
+ });
1406
+ return;
1407
+ }
1408
+ const tool = tools[parsed.command];
1409
+ if (!tool) {
1410
+ throw new ToolNotFoundError(parsed.command);
1411
+ }
1412
+ const stdinData = await getStdin();
1413
+ const args = parsed.subcommand ? [parsed.subcommand, ...parsed.args] : [...parsed.args];
1414
+ const result = tool.run(args, stdinData);
1415
+ if (result) {
1416
+ log.raw(result);
1417
+ }
1418
+ }
1419
+ main().catch((error) => {
1420
+ handleError(error);
1421
+ });