@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 +94 -0
  2. package/dist/index.js +564 -0
  3. package/package.json +43 -0
package/README.md ADDED
@@ -0,0 +1,94 @@
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
+ # Install the qrdx library package (includes the CLI binary)
9
+ npm install -g qrdx
10
+
11
+ # Or install the standalone CLI package
12
+ npm install -g qrdx-cli
13
+ ```
14
+
15
+ Or run without installing:
16
+
17
+ ```bash
18
+ npx qrdx generate "https://example.com" -o qr.svg
19
+ ```
20
+
21
+ ## Usage
22
+
23
+ ```bash
24
+ qrdx generate <data> [options]
25
+ ```
26
+
27
+ **Options**
28
+
29
+ | Flag | Short | Default | Description |
30
+ |------|-------|---------|-------------|
31
+ | `--output` | `-o` | — | Output path — extension sets format (`.svg` or `.png`) |
32
+ | `--size` | | `512` | Output pixel size |
33
+ | `--level` | `-l` | `Q` | Error correction: `L` `M` `Q` `H` |
34
+ | `--body` | | `square` | Body dot pattern |
35
+ | `--eye` | | `square` | Corner eye pattern |
36
+ | `--dot` | | `square` | Corner dot pattern |
37
+ | `--fg` | | `#000000` | Foreground color |
38
+ | `--bg` | | `#ffffff` | Background color |
39
+ | `--eye-color` | | — | Corner eye color override |
40
+ | `--dot-color` | | — | Corner dot color override |
41
+ | `--logo` | | — | Center logo URL |
42
+ | `--margin` | | `0` | Quiet zone margin (modules) |
43
+
44
+ ## Examples
45
+
46
+ ```bash
47
+ # Basic SVG
48
+ qrdx generate "https://qrdx.dev" -o qr.svg
49
+
50
+ # High-res PNG for print
51
+ qrdx generate "https://qrdx.dev" -o qr.png --size 2048 --level H
52
+
53
+ # Branded style
54
+ qrdx generate "https://qrdx.dev" \
55
+ --body circle --eye rounded \
56
+ --fg "#1a1a2e" --bg "#f5f5f5" \
57
+ -o branded.svg
58
+
59
+ # With center logo
60
+ qrdx generate "https://qrdx.dev" \
61
+ --logo "https://qrdx.dev/logo.png" --level H \
62
+ -o logo-qr.svg
63
+
64
+ # Wi-Fi QR
65
+ qrdx generate "WIFI:T:WPA;S:MyNetwork;P:MyPassword;;" -o wifi.svg
66
+
67
+ # Interactive mode (TTY)
68
+ qrdx generate
69
+ ```
70
+
71
+ ## Patterns
72
+
73
+ **Body** (`--body`): `square` `circle` `circle-large` `diamond` `circle-mixed` `pacman` `rounded` `small-square` `vertical-line`
74
+
75
+ **Eye** (`--eye`): `square` `rounded` `gear` `circle` `diya` `cushion` `boxy` `pointed`
76
+
77
+ **Dot** (`--dot`): `square` `rounded` `circle` `heart` `diamond` `star`
78
+
79
+ ## Interactive mode
80
+
81
+ When run in a TTY without flags, `qrdx generate` starts a guided prompt flow — output path, then optionally advanced customizations (patterns, colors, logo).
82
+
83
+ ## Requirements
84
+
85
+ Node.js ≥ 18
86
+
87
+ ## Links
88
+
89
+ - [qrdx.dev](https://qrdx.dev) — web app
90
+ - [github.com/qrdx/qrdx](https://github.com/qrdx/qrdx) — source
91
+
92
+ ## License
93
+
94
+ 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.0.0",
16
+ "react-dom": "^19.0.0",
17
+ "sharp": "^0.34.2",
18
+ "qrdx": "0.0.43"
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
+ }