qrdx-cli 0.0.1

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 (3) hide show
  1. package/README.md +90 -0
  2. package/dist/index.js +564 -0
  3. package/package.json +43 -0
package/README.md ADDED
@@ -0,0 +1,90 @@
1
+ # qrdx-cli
2
+
3
+ QR code generator CLI for developers. Generate styled, scannable QR codes as SVG or PNG — fully non-interactive, CI-friendly.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ npm install -g qrdx-cli
9
+ ```
10
+
11
+ Or run without installing:
12
+
13
+ ```bash
14
+ npx qrdx-cli generate "https://example.com" -o qr.svg
15
+ ```
16
+
17
+ ## Usage
18
+
19
+ ```bash
20
+ qrdx generate <data> [options]
21
+ ```
22
+
23
+ **Options**
24
+
25
+ | Flag | Short | Default | Description |
26
+ |------|-------|---------|-------------|
27
+ | `--output` | `-o` | — | Output path — extension sets format (`.svg` or `.png`) |
28
+ | `--size` | | `512` | Output pixel size |
29
+ | `--level` | `-l` | `Q` | Error correction: `L` `M` `Q` `H` |
30
+ | `--body` | | `square` | Body dot pattern |
31
+ | `--eye` | | `square` | Corner eye pattern |
32
+ | `--dot` | | `square` | Corner dot pattern |
33
+ | `--fg` | | `#000000` | Foreground color |
34
+ | `--bg` | | `#ffffff` | Background color |
35
+ | `--eye-color` | | — | Corner eye color override |
36
+ | `--dot-color` | | — | Corner dot color override |
37
+ | `--logo` | | — | Center logo URL |
38
+ | `--margin` | | `0` | Quiet zone margin (modules) |
39
+
40
+ ## Examples
41
+
42
+ ```bash
43
+ # Basic SVG
44
+ qrdx generate "https://qrdx.dev" -o qr.svg
45
+
46
+ # High-res PNG for print
47
+ qrdx generate "https://qrdx.dev" -o qr.png --size 2048 --level H
48
+
49
+ # Branded style
50
+ qrdx generate "https://qrdx.dev" \
51
+ --body circle --eye rounded \
52
+ --fg "#1a1a2e" --bg "#f5f5f5" \
53
+ -o branded.svg
54
+
55
+ # With center logo
56
+ qrdx generate "https://qrdx.dev" \
57
+ --logo "https://qrdx.dev/logo.png" --level H \
58
+ -o logo-qr.svg
59
+
60
+ # Wi-Fi QR
61
+ qrdx generate "WIFI:T:WPA;S:MyNetwork;P:MyPassword;;" -o wifi.svg
62
+
63
+ # Interactive mode (TTY)
64
+ qrdx generate
65
+ ```
66
+
67
+ ## Patterns
68
+
69
+ **Body** (`--body`): `square` `circle` `circle-large` `diamond` `circle-mixed` `pacman` `rounded` `small-square` `vertical-line`
70
+
71
+ **Eye** (`--eye`): `square` `rounded` `gear` `circle` `diya` `cushion` `boxy` `pointed`
72
+
73
+ **Dot** (`--dot`): `square` `rounded` `circle` `heart` `diamond` `star`
74
+
75
+ ## Interactive mode
76
+
77
+ When run in a TTY without flags, `qrdx generate` starts a guided prompt flow — output path, then optionally advanced customizations (patterns, colors, logo).
78
+
79
+ ## Requirements
80
+
81
+ Node.js ≥ 18
82
+
83
+ ## Links
84
+
85
+ - [qrdx.dev](https://qrdx.dev) — web app
86
+ - [github.com/qrdx/qrdx](https://github.com/qrdx/qrdx) — source
87
+
88
+ ## License
89
+
90
+ MIT
package/dist/index.js ADDED
@@ -0,0 +1,564 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ var fs = require('fs');
5
+ var path = require('path');
6
+ var p = require('@clack/prompts');
7
+ var qrdx = require('qrdx');
8
+ var react = require('react');
9
+ var server = require('react-dom/server');
10
+
11
+ function _interopNamespace(e) {
12
+ if (e && e.__esModule) return e;
13
+ var n = Object.create(null);
14
+ if (e) {
15
+ Object.keys(e).forEach(function (k) {
16
+ if (k !== 'default') {
17
+ var d = Object.getOwnPropertyDescriptor(e, k);
18
+ Object.defineProperty(n, k, d.get ? d : {
19
+ enumerable: true,
20
+ get: function () { return e[k]; }
21
+ });
22
+ }
23
+ });
24
+ }
25
+ n.default = e;
26
+ return Object.freeze(n);
27
+ }
28
+
29
+ var p__namespace = /*#__PURE__*/_interopNamespace(p);
30
+
31
+ function buildSVGString(props) {
32
+ const element = react.createElement(qrdx.QRCodeSVG, {
33
+ size: props.size ?? 512,
34
+ ...props
35
+ });
36
+ return server.renderToStaticMarkup(element);
37
+ }
38
+ async function saveOutput(options) {
39
+ const { outputPath, format, ...qrProps } = options;
40
+ const svgString = buildSVGString(qrProps);
41
+ if (format === "svg") {
42
+ const svgContent = `<?xml version="1.0" encoding="UTF-8"?>
43
+ ${svgString}`;
44
+ fs.writeFileSync(outputPath, svgContent, "utf-8");
45
+ return;
46
+ }
47
+ const sharp = (await import('sharp')).default;
48
+ const svgBuffer = Buffer.from(svgString, "utf-8");
49
+ const size = qrProps.size ?? 512;
50
+ await sharp(svgBuffer).resize(size, size).png().toFile(outputPath);
51
+ }
52
+
53
+ // src/commands/generate.ts
54
+ var RESET = "\x1B[0m";
55
+ var TEXT = "\x1B[38;5;145m";
56
+ var GREEN = "\x1B[38;5;114m";
57
+ var DIM = "\x1B[38;5;102m";
58
+ var IS_TTY = Boolean(process.stdin.isTTY);
59
+ function parseFlags(args) {
60
+ const flags = {};
61
+ let i = 0;
62
+ while (i < args.length) {
63
+ const arg = args[i];
64
+ if (!arg.startsWith("-")) {
65
+ if (!flags.value) {
66
+ flags.value = arg;
67
+ }
68
+ i++;
69
+ continue;
70
+ }
71
+ const next = args[i + 1];
72
+ switch (arg) {
73
+ case "-o":
74
+ case "--output":
75
+ flags.output = next;
76
+ i += 2;
77
+ break;
78
+ case "-l":
79
+ case "--level":
80
+ flags.level = next;
81
+ i += 2;
82
+ break;
83
+ case "--body":
84
+ flags.body = next;
85
+ i += 2;
86
+ break;
87
+ case "--eye":
88
+ flags.eye = next;
89
+ i += 2;
90
+ break;
91
+ case "--dot":
92
+ flags.dot = next;
93
+ i += 2;
94
+ break;
95
+ case "--fg":
96
+ flags.fg = next;
97
+ i += 2;
98
+ break;
99
+ case "--bg":
100
+ flags.bg = next;
101
+ i += 2;
102
+ break;
103
+ case "--eye-color":
104
+ flags.eyeColor = next;
105
+ i += 2;
106
+ break;
107
+ case "--dot-color":
108
+ flags.dotColor = next;
109
+ i += 2;
110
+ break;
111
+ case "--logo":
112
+ flags.logo = next;
113
+ i += 2;
114
+ break;
115
+ case "--margin":
116
+ flags.margin = next ? Number.parseInt(next, 10) : 0;
117
+ i += 2;
118
+ break;
119
+ case "--size":
120
+ flags.size = next ? Number.parseInt(next, 10) : 512;
121
+ i += 2;
122
+ break;
123
+ case "--no-logo":
124
+ flags.noLogo = true;
125
+ i++;
126
+ break;
127
+ default:
128
+ i++;
129
+ }
130
+ }
131
+ return flags;
132
+ }
133
+ function isCancel2(value) {
134
+ return p__namespace.isCancel(value);
135
+ }
136
+ function handleCancel() {
137
+ p__namespace.cancel("Cancelled.");
138
+ process.exit(0);
139
+ }
140
+ function inferFormat(outputPath) {
141
+ const ext = path.extname(outputPath).toLowerCase();
142
+ if (ext === ".png") {
143
+ return "png";
144
+ }
145
+ return "svg";
146
+ }
147
+ async function runGenerate(args) {
148
+ const flags = parseFlags(args);
149
+ const nonInteractive = !IS_TTY;
150
+ const hasAdvancedFlags = Boolean(
151
+ flags.body || flags.eye || flags.dot || flags.fg || flags.bg || flags.eyeColor || flags.dotColor || flags.logo || flags.level || flags.margin !== void 0
152
+ );
153
+ p__namespace.intro(`${TEXT}qrdx${RESET} ${DIM}\u2014 QR Code Generator${RESET}`);
154
+ let value = flags.value;
155
+ if (!value) {
156
+ if (nonInteractive) {
157
+ p__namespace.log.error("No data provided. Pass a URL or text as an argument.");
158
+ process.exit(1);
159
+ }
160
+ const res = await p__namespace.text({
161
+ message: "URL or text to encode",
162
+ placeholder: "https://qrdx.dev",
163
+ validate(v) {
164
+ if (!v.trim()) {
165
+ return "Please enter a value to encode.";
166
+ }
167
+ }
168
+ });
169
+ if (isCancel2(res)) {
170
+ handleCancel();
171
+ }
172
+ value = res;
173
+ }
174
+ let outputPath = flags.output;
175
+ const size = flags.size ?? 512;
176
+ if (!(outputPath || nonInteractive)) {
177
+ const pathRes = await p__namespace.text({
178
+ message: "Output file (.svg or .png)",
179
+ placeholder: "qr.svg",
180
+ defaultValue: "qr.svg",
181
+ validate(v) {
182
+ if (!v.trim()) {
183
+ return "Please enter an output path.";
184
+ }
185
+ }
186
+ });
187
+ if (isCancel2(pathRes)) {
188
+ handleCancel();
189
+ }
190
+ outputPath = pathRes || "qr.svg";
191
+ }
192
+ let wantAdvanced = hasAdvancedFlags;
193
+ if (!(hasAdvancedFlags || nonInteractive)) {
194
+ const res = await p__namespace.confirm({
195
+ message: "Advanced customizations?",
196
+ initialValue: false
197
+ });
198
+ if (isCancel2(res)) {
199
+ handleCancel();
200
+ }
201
+ wantAdvanced = res;
202
+ }
203
+ let level = flags.level ?? "Q";
204
+ let body = flags.body ?? "square";
205
+ let eye = flags.eye ?? "square";
206
+ let dot = flags.dot ?? "square";
207
+ let fg = flags.fg ?? "#000000";
208
+ let bg = flags.bg ?? "#ffffff";
209
+ let eyeColor = flags.eyeColor;
210
+ let dotColor = flags.dotColor;
211
+ let logo = flags.logo;
212
+ let margin = flags.margin ?? 0;
213
+ if (wantAdvanced && !nonInteractive) {
214
+ if (!flags.level) {
215
+ const res = await p__namespace.select({
216
+ message: "Error correction level",
217
+ options: [
218
+ {
219
+ value: "Q",
220
+ label: "Q \u2013 Quartile (recommended)",
221
+ hint: "~25% data recovery"
222
+ },
223
+ { value: "L", label: "L \u2013 Low", hint: "~7% data recovery" },
224
+ { value: "M", label: "M \u2013 Medium", hint: "~15% data recovery" },
225
+ { value: "H", label: "H \u2013 High", hint: "~30% data recovery" }
226
+ ],
227
+ initialValue: "Q"
228
+ });
229
+ if (isCancel2(res)) {
230
+ handleCancel();
231
+ }
232
+ level = res;
233
+ }
234
+ if (!flags.body) {
235
+ const res = await p__namespace.select({
236
+ message: "Body dot pattern",
237
+ options: qrdx.BODY_PATTERN.map((b) => ({ value: b, label: b })),
238
+ initialValue: "square"
239
+ });
240
+ if (isCancel2(res)) {
241
+ handleCancel();
242
+ }
243
+ body = res;
244
+ }
245
+ if (!flags.eye) {
246
+ const res = await p__namespace.select({
247
+ message: "Corner eye pattern",
248
+ options: qrdx.CORNER_EYE_PATTERNS.map((e) => ({ value: e, label: e })),
249
+ initialValue: "square"
250
+ });
251
+ if (isCancel2(res)) {
252
+ handleCancel();
253
+ }
254
+ eye = res;
255
+ }
256
+ if (!flags.dot) {
257
+ const res = await p__namespace.select({
258
+ message: "Corner dot pattern",
259
+ options: qrdx.CORNER_EYE_DOT_PATTERNS.map((d) => ({ value: d, label: d })),
260
+ initialValue: "square"
261
+ });
262
+ if (isCancel2(res)) {
263
+ handleCancel();
264
+ }
265
+ dot = res;
266
+ }
267
+ if (!flags.fg) {
268
+ const res = await p__namespace.text({
269
+ message: "Foreground color",
270
+ placeholder: "#000000",
271
+ defaultValue: "#000000",
272
+ validate(v) {
273
+ if (v && !/^#[0-9a-fA-F]{3,8}$/.test(v)) {
274
+ return "Enter a valid hex color (e.g. #000000)";
275
+ }
276
+ }
277
+ });
278
+ if (isCancel2(res)) {
279
+ handleCancel();
280
+ }
281
+ fg = res || "#000000";
282
+ }
283
+ if (!flags.bg) {
284
+ const res = await p__namespace.text({
285
+ message: "Background color",
286
+ placeholder: "#ffffff",
287
+ defaultValue: "#ffffff",
288
+ validate(v) {
289
+ if (v && !/^#[0-9a-fA-F]{3,8}$/.test(v)) {
290
+ return "Enter a valid hex color (e.g. #ffffff)";
291
+ }
292
+ }
293
+ });
294
+ if (isCancel2(res)) {
295
+ handleCancel();
296
+ }
297
+ bg = res || "#ffffff";
298
+ }
299
+ if (eyeColor === void 0) {
300
+ const res = await p__namespace.text({
301
+ message: `Corner eye color ${DIM}(leave blank to match foreground)${RESET}`,
302
+ placeholder: fg,
303
+ validate(v) {
304
+ if (v && !/^#[0-9a-fA-F]{3,8}$/.test(v)) {
305
+ return "Enter a valid hex color or leave blank";
306
+ }
307
+ }
308
+ });
309
+ if (isCancel2(res)) {
310
+ handleCancel();
311
+ }
312
+ eyeColor = res || void 0;
313
+ }
314
+ if (dotColor === void 0) {
315
+ const res = await p__namespace.text({
316
+ message: `Corner dot color ${DIM}(leave blank to match foreground)${RESET}`,
317
+ placeholder: fg,
318
+ validate(v) {
319
+ if (v && !/^#[0-9a-fA-F]{3,8}$/.test(v)) {
320
+ return "Enter a valid hex color or leave blank";
321
+ }
322
+ }
323
+ });
324
+ if (isCancel2(res)) {
325
+ handleCancel();
326
+ }
327
+ dotColor = res || void 0;
328
+ }
329
+ if (logo === void 0 && !flags.noLogo) {
330
+ const addLogo = await p__namespace.confirm({
331
+ message: "Add a logo to the center?",
332
+ initialValue: false
333
+ });
334
+ if (isCancel2(addLogo)) {
335
+ handleCancel();
336
+ }
337
+ if (addLogo) {
338
+ const res = await p__namespace.text({
339
+ message: "Logo URL",
340
+ placeholder: "https://qrdx.dev/logo.png",
341
+ validate(v) {
342
+ if (!v.trim()) {
343
+ return "Please enter a URL for the logo.";
344
+ }
345
+ try {
346
+ new URL(v);
347
+ } catch {
348
+ return "Please enter a valid URL.";
349
+ }
350
+ }
351
+ });
352
+ if (isCancel2(res)) {
353
+ handleCancel();
354
+ }
355
+ logo = res;
356
+ }
357
+ }
358
+ if (flags.margin === void 0) {
359
+ const res = await p__namespace.text({
360
+ message: "Quiet zone margin (modules)",
361
+ placeholder: "0",
362
+ defaultValue: "0",
363
+ validate(v) {
364
+ if (v && Number.isNaN(Number.parseInt(v, 10))) {
365
+ return "Enter a number";
366
+ }
367
+ if (v && Number.parseInt(v, 10) < 0) {
368
+ return "Must be 0 or greater";
369
+ }
370
+ }
371
+ });
372
+ if (isCancel2(res)) {
373
+ handleCancel();
374
+ }
375
+ margin = Number.parseInt(res || "0", 10);
376
+ }
377
+ }
378
+ const spinner2 = p__namespace.spinner();
379
+ spinner2.start("Generating QR code...");
380
+ try {
381
+ const qrProps = {
382
+ value,
383
+ level,
384
+ bodyPattern: body,
385
+ cornerEyePattern: eye,
386
+ cornerEyeDotPattern: dot,
387
+ fgColor: fg,
388
+ bgColor: bg,
389
+ ...eyeColor ? { eyeColor } : {},
390
+ ...dotColor ? { dotColor } : {},
391
+ margin,
392
+ size,
393
+ ...logo ? {
394
+ imageSettings: {
395
+ src: logo,
396
+ height: Math.round(size * 0.2),
397
+ width: Math.round(size * 0.2),
398
+ excavate: true
399
+ }
400
+ } : {}
401
+ };
402
+ if (outputPath) {
403
+ const format = inferFormat(outputPath);
404
+ await saveOutput({ ...qrProps, outputPath, format });
405
+ spinner2.stop(
406
+ `${GREEN}\u2714${RESET} Saved to ${TEXT}${outputPath}${RESET} ${DIM}(${size}\xD7${size})${RESET}`
407
+ );
408
+ } else {
409
+ spinner2.stop("QR code ready");
410
+ }
411
+ } catch (err) {
412
+ spinner2.stop("Failed to generate QR code");
413
+ const msg = err instanceof Error ? err.message : String(err);
414
+ p__namespace.log.error(msg);
415
+ process.exit(1);
416
+ }
417
+ p__namespace.outro(`${DIM}Done!${RESET}`);
418
+ }
419
+
420
+ // src/index.ts
421
+ var RESET2 = "\x1B[0m";
422
+ var BOLD = "\x1B[1m";
423
+ var DIM2 = "\x1B[38;5;102m";
424
+ var TEXT2 = "\x1B[38;5;145m";
425
+ var GRAYS = [
426
+ "\x1B[38;5;250m",
427
+ "\x1B[38;5;248m",
428
+ "\x1B[38;5;245m",
429
+ "\x1B[38;5;243m",
430
+ "\x1B[38;5;240m",
431
+ "\x1B[38;5;238m"
432
+ ];
433
+ var LOGO_LINES = [
434
+ " \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2557",
435
+ "\u2588\u2588\u2554\u2550\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u255A\u2588\u2588\u2557\u2588\u2588\u2554\u255D",
436
+ "\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2554\u255D ",
437
+ "\u2588\u2588\u2551\u2584\u2584 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2551 \u2588\u2588\u2551 \u2588\u2588\u2554\u2588\u2588\u2557 ",
438
+ "\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2554\u255D\u2588\u2588\u2554\u255D \u2588\u2588\u2557",
439
+ " \u255A\u2550\u2550\u2550\u2588\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u255D"
440
+ ];
441
+ function getVersion() {
442
+ try {
443
+ const pkgPath = path.join(__dirname, "..", "package.json");
444
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf-8"));
445
+ return pkg.version;
446
+ } catch {
447
+ return "0.0.1";
448
+ }
449
+ }
450
+ var VERSION = getVersion();
451
+ function showLogo() {
452
+ console.log();
453
+ LOGO_LINES.forEach((line, i) => {
454
+ console.log(`${GRAYS[i] ?? GRAYS[5]}${line}${RESET2}`);
455
+ });
456
+ }
457
+ function showBanner() {
458
+ showLogo();
459
+ console.log();
460
+ console.log(`${DIM2}QR code generator for developers${RESET2}`);
461
+ console.log();
462
+ console.log(
463
+ ` ${DIM2}$${RESET2} ${TEXT2}qrdx generate${RESET2} ${DIM2}Generate a QR code interactively${RESET2}`
464
+ );
465
+ console.log(
466
+ ` ${DIM2}$${RESET2} ${TEXT2}qrdx generate${RESET2} ${DIM2}<url>${RESET2} ${DIM2}Generate from URL${RESET2}`
467
+ );
468
+ console.log();
469
+ console.log(
470
+ ` ${DIM2}$${RESET2} ${TEXT2}qrdx --help${RESET2} ${DIM2}Show all commands and options${RESET2}`
471
+ );
472
+ console.log(
473
+ ` ${DIM2}$${RESET2} ${TEXT2}qrdx --version${RESET2} ${DIM2}Show version${RESET2}`
474
+ );
475
+ console.log();
476
+ console.log(` ${DIM2}try:${RESET2} npx @qrdx/cli generate "https://qrdx.dev"`);
477
+ console.log();
478
+ }
479
+ function showHelp() {
480
+ showLogo();
481
+ console.log(`
482
+ ${BOLD}Usage:${RESET2} qrdx <command> [options]
483
+
484
+ ${BOLD}Commands:${RESET2}
485
+ generate, gen, g Generate a QR code (fully interactive or via flags)
486
+
487
+ ${BOLD}Generate Options:${RESET2}
488
+ <data> URL or text to encode (positional)
489
+ -o, --output Output file path (.svg or .png)
490
+ -l, --level Error correction: L, M, Q, H ${DIM2}(default: Q)${RESET2}
491
+ --body Body dot pattern:
492
+ ${DIM2}square, circle, circle-large, diamond,${RESET2}
493
+ ${DIM2}circle-mixed, pacman, rounded, small-square, vertical-line${RESET2}
494
+ --eye Corner eye pattern:
495
+ ${DIM2}square, rounded, gear, circle, diya,${RESET2}
496
+ ${DIM2}extra-rounded, message, pointy, curly${RESET2}
497
+ --dot Corner dot pattern:
498
+ ${DIM2}square, rounded, circle, diamond, message,${RESET2}
499
+ ${DIM2}message-reverse, diya, diya-reverse,${RESET2}
500
+ ${DIM2}rounded-triangle, star, banner${RESET2}
501
+ --fg Foreground color ${DIM2}(default: #000000)${RESET2}
502
+ --bg Background color ${DIM2}(default: #ffffff)${RESET2}
503
+ --eye-color Corner eye color ${DIM2}(default: matches --fg)${RESET2}
504
+ --dot-color Corner dot color ${DIM2}(default: matches --fg)${RESET2}
505
+ --logo Logo URL for center image
506
+ --margin Quiet zone margin in modules ${DIM2}(default: 0)${RESET2}
507
+ --size Output pixel size for SVG/PNG ${DIM2}(default: 512)${RESET2}
508
+
509
+ ${BOLD}Global Options:${RESET2}
510
+ --help, -h Show this help message
511
+ --version, -v Show version number
512
+
513
+ ${BOLD}Examples:${RESET2}
514
+ ${DIM2}$${RESET2} qrdx generate
515
+ ${DIM2}$${RESET2} qrdx generate "https://qrdx.dev"
516
+ ${DIM2}$${RESET2} qrdx generate "https://qrdx.dev" -o qr.svg
517
+ ${DIM2}$${RESET2} qrdx generate "https://qrdx.dev" -o qr.png --body circle --eye gear --dot star
518
+ ${DIM2}$${RESET2} qrdx generate "https://qrdx.dev" --fg "#ff0000" --bg "#ffffff" --size 1024
519
+ ${DIM2}$${RESET2} qrdx generate "https://qrdx.dev" --logo "https://qrdx.dev/logo.png" -o branded.svg
520
+
521
+ Visit ${TEXT2}https://qrdx.dev${RESET2} for more.
522
+ `);
523
+ }
524
+ async function main() {
525
+ const args = process.argv.slice(2);
526
+ if (args.length === 0) {
527
+ showBanner();
528
+ return;
529
+ }
530
+ const command = args[0];
531
+ const restArgs = args.slice(1);
532
+ switch (command) {
533
+ case "generate":
534
+ case "gen":
535
+ case "g":
536
+ showLogo();
537
+ console.log();
538
+ await runGenerate(restArgs);
539
+ break;
540
+ case "--help":
541
+ case "-h":
542
+ case "help":
543
+ showHelp();
544
+ break;
545
+ case "--version":
546
+ case "-v":
547
+ console.log(VERSION);
548
+ break;
549
+ default:
550
+ if (command.startsWith("-")) {
551
+ console.log(`Unknown command: ${command}`);
552
+ console.log(`Run ${BOLD}qrdx --help${RESET2} for usage.`);
553
+ } else {
554
+ showLogo();
555
+ console.log();
556
+ await runGenerate(args);
557
+ }
558
+ }
559
+ }
560
+ main().catch((err) => {
561
+ const msg = err instanceof Error ? err.message : String(err);
562
+ console.error(`\x1B[38;5;203m\u2717 ${msg}${RESET2}`);
563
+ process.exit(1);
564
+ });
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "qrdx-cli",
3
+ "version": "0.0.1",
4
+ "description": "QR code generator CLI for developers",
5
+ "bin": {
6
+ "qrdx": "./dist/index.js"
7
+ },
8
+ "files": [
9
+ "dist",
10
+ "README.md"
11
+ ],
12
+ "dependencies": {
13
+ "@clack/prompts": "^0.10.0",
14
+ "picocolors": "^1.1.1",
15
+ "react": "19.2.3",
16
+ "react-dom": "19.2.3",
17
+ "sharp": "^0.34.2",
18
+ "qrdx": "0.0.1"
19
+ },
20
+ "devDependencies": {
21
+ "@types/node": "^25.1.0",
22
+ "@types/react": "19.2.7",
23
+ "@types/react-dom": "19.2.3",
24
+ "tsup": "^8.5.1",
25
+ "typescript": "5.9.3",
26
+ "vitest": "^4.0.18",
27
+ "@repo/typescript-config": "0.0.0"
28
+ },
29
+ "engines": {
30
+ "node": ">=18"
31
+ },
32
+ "publishConfig": {
33
+ "access": "public"
34
+ },
35
+ "scripts": {
36
+ "build": "tsup",
37
+ "dev": "tsup --watch",
38
+ "test": "vitest run",
39
+ "test:watch": "vitest",
40
+ "clean": "git clean -xdf .cache .turbo dist node_modules",
41
+ "typecheck": "tsc --noEmit"
42
+ }
43
+ }