qr-terminal 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,159 @@
1
+ # QR Terminal
2
+
3
+ A cinematic, interactive QR code generator that runs entirely in your terminal.
4
+
5
+ Generate QR codes for URLs, WiFi networks, contacts, and secrets — with live preview, animated rendering, gradient colors, phone handshake, and one-click export.
6
+
7
+ ---
8
+
9
+ ## Quick Start
10
+
11
+ ```bash
12
+ git clone https://github.com/Boweii22/QR_Terminal.git
13
+ cd QR_Terminal
14
+ npm install
15
+ npm start
16
+ ```
17
+
18
+ ### Install globally (run `qr` from anywhere)
19
+
20
+ ```bash
21
+ npm link
22
+ qr
23
+ ```
24
+
25
+ **Requires:** Node.js 18 or higher
26
+
27
+ ---
28
+
29
+ ## What It Does
30
+
31
+ Launch it and follow the prompts. You'll pick:
32
+
33
+ 1. **What to encode** — URL, WiFi, contact card, or secret text
34
+ 2. **Error correction** — how much damage the QR can survive
35
+ 3. **Color theme** — 10 gradient options
36
+ 4. **Render engine** — how the QR appears on screen
37
+
38
+ Then after it renders, you can copy it, save it as an image, or share it.
39
+
40
+ ---
41
+
42
+ ## Input Types
43
+
44
+ ### URL
45
+ Type any web address and watch the QR code build itself in real time as you type. Optionally shorten long URLs via TinyURL to keep the QR small and clean.
46
+
47
+ ### WiFi
48
+ Generates a tap-to-join QR code. Point your phone camera at it — no app needed — and it connects automatically.
49
+
50
+ Supports WPA/WPA2, WEP, and open networks.
51
+
52
+ ### vCard Contact
53
+ Share your contact info as a QR code. Anyone who scans it gets a prompt to save you directly to their address book.
54
+
55
+ Fields: name, phone, email, organisation, website.
56
+
57
+ ### Secret Text
58
+ Encode anything sensitive — API keys, passwords, tokens. Input is masked while you type.
59
+
60
+ ---
61
+
62
+ ## Render Engines
63
+
64
+ ### Half-Block (default)
65
+ Uses Unicode characters (`▀`, `▄`, `█`) to fit a full QR code in half the usual vertical space. Renders with a cyan scanline sweep animation followed by a brightness pulse.
66
+
67
+ ### Braille — Retina Mode
68
+ Each terminal character cell encodes a 2×4 dot grid, giving 4× the visual resolution of standard terminal QR tools. The QR looks noticeably sharper.
69
+
70
+ ### Particle — Transformer Effect
71
+ Every block in the QR starts at a random position on screen and flies into its correct place simultaneously. The code assembles itself out of chaos over about one second.
72
+
73
+ ### Matrix Rain
74
+ A cascade of green characters rains down the columns of the QR code before fading out to reveal your chosen color theme underneath.
75
+
76
+ ### Device Link — Phone Handshake
77
+ The most interactive mode:
78
+
79
+ 1. Scan the QR with your phone
80
+ 2. A page opens in your phone's browser
81
+ 3. The terminal instantly shows **DEVICE CONNECTED ✓**
82
+ 4. Type on your phone and hit transmit — the text appears in your terminal
83
+
84
+ Everything runs over your local network. No internet required, no account needed.
85
+
86
+ ---
87
+
88
+ ## Color Themes
89
+
90
+ | Theme | Style |
91
+ |---|---|
92
+ | Classic | Crisp white — maximum scanner reliability |
93
+ | Retro | Coral red fading to warm amber |
94
+ | Ocean | Deep navy sweeping to electric cyan |
95
+ | Neon | Sine-wave ripple: green · cyan · magenta |
96
+ | Sunset | Hot red through orange to gold |
97
+ | Galaxy | 4-corner blend: purple · blue · pink · teal |
98
+ | Cherry | Radial bloom: soft white centre to deep rose |
99
+ | Matrix | Dark green cascading to electric green |
100
+ | Fire | Blood red through ember to gold |
101
+ | Vaporwave | 4-corner: magenta · cyan · purple · blue |
102
+
103
+ All themes use 24-bit TrueColor applied per character — not per row — so gradients wash diagonally, radially, or in waves across the entire code.
104
+
105
+ ---
106
+
107
+ ## Export Options
108
+
109
+ After any QR renders, choose what to do with it:
110
+
111
+ | Option | What you get |
112
+ |---|---|
113
+ | Copy to clipboard | Plain Unicode characters — paste into Slack, a README, or any terminal |
114
+ | Save as PNG | High-resolution image file (600px) |
115
+ | Save as SVG | Scalable vector — perfect for print or presentations |
116
+ | Save as TXT | Plain text half-block file |
117
+ | Self-destruct share | One-time public link for a file (see below) |
118
+
119
+ ---
120
+
121
+ ## Self-Destructing File Share
122
+
123
+ Pick **Self-destruct share** from the export menu and point it at any file on your machine:
124
+
125
+ - The file is loaded into memory and hosted via a public tunnel
126
+ - A QR code is generated for the public URL
127
+ - The first person to scan and download it triggers the shutdown
128
+ - The tunnel closes, the server stops, and an ASCII explosion plays in the terminal
129
+ - Anyone who tries the link afterwards gets a `410 Gone` response
130
+
131
+ The file is never written anywhere — it lives in RAM only.
132
+
133
+ ---
134
+
135
+ ## Auto Theme Detection
136
+
137
+ When the tool starts, it silently queries your terminal's actual background color using the OSC 11 escape sequence. If you're on a light-background theme (Solarized Light, GitHub Light, etc.) it detects this automatically and warns you so you can choose a high-contrast color theme.
138
+
139
+ Works with: iTerm2, Windows Terminal, Terminal.app, Kitty, Alacritty, xterm-compatible emulators.
140
+
141
+ ---
142
+
143
+ ## Requirements
144
+
145
+ - Node.js 18+
146
+ - A terminal with 24-bit TrueColor support
147
+ - Windows Terminal ✓
148
+ - iTerm2 ✓
149
+ - Kitty ✓
150
+ - Alacritty ✓
151
+ - VS Code integrated terminal ✓
152
+ - Phone on the same WiFi network (Device Link mode only)
153
+ - Internet connection (TinyURL shortening and self-destruct tunnel only)
154
+
155
+ ---
156
+
157
+ ## License
158
+
159
+ MIT
package/index.js ADDED
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * ╔══════════════════════════════════════════════════════════════════════╗
4
+ * ║ QR TERMINAL v1.0.0 ║
5
+ * ║ High-Density Half-Block QR Code Generator for the Command Line ║
6
+ * ║ ║
7
+ * ║ Engine: Unicode ▀ ▄ █ bitmasking — 50% vertical compression ║
8
+ * ║ Colors: chalk 24-bit TrueColor + multi-stop gradient rendering ║
9
+ * ║ Formats: URL · WiFi · vCard · Secret ║
10
+ * ║ Export: PNG · SVG · TXT · Clipboard ║
11
+ * ╚══════════════════════════════════════════════════════════════════════╝
12
+ */
13
+
14
+ import chalk from 'chalk';
15
+ import { printHeader, log } from './src/ui.js';
16
+ import { runMainFlow } from './src/prompts.js';
17
+ import { detectTerminalBgColor } from './src/luma.js';
18
+
19
+ // Ensure stdout supports TrueColor
20
+ process.env.FORCE_COLOR = process.env.FORCE_COLOR ?? '3';
21
+
22
+ async function main() {
23
+ // ── Splash ──────────────────────────────────────────────────────────────
24
+ process.stdout.write('\x1Bc'); // Full terminal clear (preserves scroll buffer)
25
+
26
+ printHeader();
27
+
28
+ // Auto-detect terminal background before any prompts
29
+ const bgResult = await detectTerminalBgColor();
30
+ if (bgResult?.isLight) {
31
+ log.warn('Light terminal background detected — QR colors auto-adjusted for readability.');
32
+ }
33
+ // Store as global for engine to use
34
+ process.env._QR_LIGHT_BG = bgResult?.isLight ? '1' : '0';
35
+
36
+ // ── Main flow ────────────────────────────────────────────────────────────
37
+ try {
38
+ await runMainFlow();
39
+ } catch (err) {
40
+ if (err?.message === 'Cancelled' || err?.name === 'ExitPromptError') {
41
+ console.log('\n' + chalk.dim(' Aborted. Goodbye.') + '\n');
42
+ process.exit(0);
43
+ }
44
+
45
+ log.error('Unexpected error: ' + (err?.message ?? String(err)));
46
+
47
+ if (process.env.DEBUG) {
48
+ console.error(err);
49
+ } else {
50
+ log.info('Set DEBUG=1 to see the full stack trace.');
51
+ }
52
+ process.exit(1);
53
+ }
54
+
55
+ // ── Sign-off ─────────────────────────────────────────────────────────────
56
+ console.log(
57
+ chalk.dim(' ─── Made with ') +
58
+ chalk.red('♥') +
59
+ chalk.dim(' using Node.js · qr-terminal ───\n')
60
+ );
61
+ }
62
+
63
+ main();
package/package.json ADDED
@@ -0,0 +1,47 @@
1
+ {
2
+ "name": "qr-terminal",
3
+ "version": "1.0.0",
4
+ "description": "Cinematic terminal QR code generator — half-block, braille, particle fly-in, matrix rain, device handshake",
5
+ "type": "module",
6
+ "main": "index.js",
7
+ "bin": {
8
+ "qr-terminal": "./index.js",
9
+ "qr": "./index.js"
10
+ },
11
+ "scripts": {
12
+ "start": "node index.js"
13
+ },
14
+ "preferGlobal": true,
15
+ "engines": {
16
+ "node": ">=18.0.0"
17
+ },
18
+ "keywords": ["qr", "qr-code", "terminal", "cli", "tui", "unicode", "half-block", "braille", "gradient", "websocket", "generator"],
19
+ "author": "Boweii22",
20
+ "license": "MIT",
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://github.com/Boweii22/QR_Terminal.git"
24
+ },
25
+ "homepage": "https://github.com/Boweii22/QR_Terminal#readme",
26
+ "bugs": {
27
+ "url": "https://github.com/Boweii22/QR_Terminal/issues"
28
+ },
29
+ "files": [
30
+ "index.js",
31
+ "src/",
32
+ "README.md"
33
+ ],
34
+ "dependencies": {
35
+ "@inquirer/prompts": "^7.0.0",
36
+ "axios": "^1.7.7",
37
+ "boxen": "^7.1.1",
38
+ "chalk": "^5.3.0",
39
+ "clipboardy": "^4.0.0",
40
+ "figlet": "^1.7.0",
41
+ "gradient-string": "^2.0.2",
42
+ "localtunnel": "^2.0.2",
43
+ "ora": "^8.1.0",
44
+ "qrcode": "^1.5.4",
45
+ "ws": "^8.18.0"
46
+ }
47
+ }
package/src/braille.js ADDED
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Braille-Block Hybrid Renderer
3
+ *
4
+ * Maps 2×4 blocks of QR modules onto Unicode Braille patterns (U+2800–U+28FF).
5
+ * Gives 4× vertical resolution vs standard half-block rendering.
6
+ *
7
+ * Braille dot layout in one terminal cell:
8
+ * col+0 col+1
9
+ * dot1 dot4 (row+0)
10
+ * dot2 dot5 (row+1)
11
+ * dot3 dot6 (row+2)
12
+ * dot7 dot8 (row+3)
13
+ *
14
+ * Bit weights: dot1=1, dot2=2, dot3=4, dot4=8, dot5=16, dot6=32, dot7=64, dot8=128
15
+ */
16
+
17
+ import chalk from 'chalk';
18
+ import { GRADIENT_PRESETS, computeColor } from './engine.js';
19
+
20
+ const BRAILLE_BASE = 0x2800;
21
+
22
+ // Map from (leftCol, rightCol) dot rows → bit positions
23
+ const DOT_BITS = [
24
+ [1, 8], // row+0: dot1, dot4
25
+ [2, 16], // row+1: dot2, dot5
26
+ [4, 32], // row+2: dot3, dot6
27
+ [64, 128], // row+3: dot7, dot8
28
+ ];
29
+
30
+ export function renderBraille(modules, options = {}) {
31
+ const { gradient = 'none', quietZone = 2 } = options;
32
+ const { size, data } = modules;
33
+ const preset = GRADIENT_PRESETS[gradient] ?? GRADIENT_PRESETS.none;
34
+
35
+ const colStart = -quietZone, colEnd = size + quietZone;
36
+ const rowStart = -quietZone, rowEnd = size + quietZone;
37
+ const totalCols = colEnd - colStart;
38
+ const totalRows = rowEnd - rowStart;
39
+
40
+ // Braille: 2 QR cols per char, 4 QR rows per char
41
+ const charCols = Math.ceil(totalCols / 2);
42
+ const charRows = Math.ceil(totalRows / 4);
43
+
44
+ const px = (r, c) => {
45
+ if (r < 0 || r >= size || c < 0 || c >= size) return 0;
46
+ return data[r * size + c] ? 1 : 0;
47
+ };
48
+
49
+ const output = [];
50
+
51
+ for (let ci = 0; ci < charRows; ci++) {
52
+ const ty = charRows > 1 ? ci / (charRows - 1) : 0;
53
+ let coloredLine = '';
54
+ let runColor = null, runChars = '';
55
+
56
+ const flush = () => {
57
+ if (!runChars) return;
58
+ coloredLine += runColor
59
+ ? chalk.rgb(runColor[0], runColor[1], runColor[2])(runChars)
60
+ : runChars;
61
+ runChars = ''; runColor = null;
62
+ };
63
+
64
+ for (let cj = 0; cj < charCols; cj++) {
65
+ const tx = charCols > 1 ? cj / (charCols - 1) : 0;
66
+ const baseRow = rowStart + ci * 4;
67
+ const baseCol = colStart + cj * 2;
68
+
69
+ // Build 8-bit braille bitmask from the 2×4 QR module block
70
+ let bitmask = 0;
71
+ let hasDark = false;
72
+ for (let dr = 0; dr < 4; dr++) {
73
+ const [leftBit, rightBit] = DOT_BITS[dr];
74
+ if (px(baseRow + dr, baseCol)) { bitmask |= leftBit; hasDark = true; }
75
+ if (px(baseRow + dr, baseCol + 1)) { bitmask |= rightBit; hasDark = true; }
76
+ }
77
+
78
+ const char = String.fromCodePoint(BRAILLE_BASE + bitmask);
79
+
80
+ if (!hasDark || bitmask === 0) {
81
+ flush();
82
+ coloredLine += ' ';
83
+ } else {
84
+ const color = computeColor(preset, tx, ty);
85
+ if (runColor && colorsClose(runColor, color, 8)) {
86
+ runChars += char;
87
+ } else {
88
+ flush();
89
+ runColor = color;
90
+ runChars = char;
91
+ }
92
+ }
93
+ }
94
+
95
+ flush();
96
+ output.push(coloredLine);
97
+ }
98
+
99
+ return output;
100
+ }
101
+
102
+ function colorsClose([r1,g1,b1],[r2,g2,b2],t){
103
+ return Math.abs(r1-r2)<t && Math.abs(g1-g2)<t && Math.abs(b1-b2)<t;
104
+ }
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Self-Destructing Session QR
3
+ *
4
+ * Hosts a file over an HTTP server tunneled via localtunnel.
5
+ * The QR code encodes the public tunnel URL.
6
+ * After the file is downloaded once, the tunnel closes and an ASCII
7
+ * "explosion" animation plays.
8
+ */
9
+
10
+ import http from 'http';
11
+ import { readFile } from 'fs/promises';
12
+ import { existsSync } from 'fs';
13
+ import { basename } from 'path';
14
+
15
+ /**
16
+ * Create a self-destructing file share.
17
+ *
18
+ * @param {string} filePath - Path to the file to share
19
+ * @returns {Promise<{ url, waitForDownload, close }>}
20
+ */
21
+ export async function createDestructQR(filePath) {
22
+ if (!existsSync(filePath)) throw new Error(`File not found: ${filePath}`);
23
+
24
+ const fileBuffer = await readFile(filePath);
25
+ const fileName = basename(filePath);
26
+ const port = 7654 + Math.floor(Math.random() * 200);
27
+ let downloaded = false;
28
+ let downloadResolve;
29
+ const downloadPromise = new Promise(r => { downloadResolve = r; });
30
+
31
+ const server = http.createServer((req, res) => {
32
+ if (req.url === '/favicon.ico') { res.writeHead(404); res.end(); return; }
33
+
34
+ if (downloaded) {
35
+ res.writeHead(410, { 'Content-Type': 'text/html' });
36
+ res.end('<html><body style="background:#0d0d0d;color:#ff0040;font-family:monospace;padding:40px"><h1>☠ FILE DESTROYED</h1><p>This QR code was single-use. The file no longer exists.</p></body></html>');
37
+ return;
38
+ }
39
+
40
+ downloaded = true;
41
+ res.writeHead(200, {
42
+ 'Content-Type': 'application/octet-stream',
43
+ 'Content-Disposition': `attachment; filename="${fileName}"`,
44
+ 'Content-Length': fileBuffer.length,
45
+ });
46
+ res.end(fileBuffer);
47
+
48
+ // Signal after response is sent
49
+ setTimeout(() => {
50
+ downloadResolve();
51
+ server.close();
52
+ }, 500);
53
+ });
54
+
55
+ await new Promise((res, rej) => server.listen(port, (e) => e ? rej(e) : res()));
56
+
57
+ // Try to set up localtunnel
58
+ let tunnelUrl;
59
+ try {
60
+ const { default: localtunnel } = await import('localtunnel');
61
+ const tunnel = await localtunnel({ port });
62
+ tunnelUrl = tunnel.url;
63
+
64
+ downloadPromise.then(() => tunnel.close()).catch(() => {});
65
+ } catch {
66
+ // Fall back to local IP if localtunnel unavailable
67
+ const { networkInterfaces } = await import('os');
68
+ const nets = networkInterfaces();
69
+ let ip = '127.0.0.1';
70
+ for (const name of Object.keys(nets)) {
71
+ for (const iface of nets[name]) {
72
+ if (iface.family === 'IPv4' && !iface.internal) { ip = iface.address; break; }
73
+ }
74
+ }
75
+ tunnelUrl = `http://${ip}:${port}`;
76
+ }
77
+
78
+ return {
79
+ url: tunnelUrl,
80
+ waitForDownload: downloadPromise,
81
+ close: () => server.close(),
82
+ };
83
+ }
84
+
85
+ /**
86
+ * ASCII explosion animation. Call after the file is consumed.
87
+ */
88
+ export async function showExplosion() {
89
+ const sleep = ms => new Promise(r => setTimeout(r, ms));
90
+ const UP = n => `\x1b[${n}A`;
91
+
92
+ const frames = [
93
+ [' ', ' ☠ ', ' '],
94
+ [' · ', ' ✦☠✦ ', ' · '],
95
+ [' ·✦· ', '✦✦☠✦✦ ', ' ·✦· '],
96
+ ['✦·✦·✦ ', '·✦ ✦· ', '✦·✦·✦ '],
97
+ ['✧ ✦ ✧ ', ' ✦ ✦ ', '✧ ✦ ✧ '],
98
+ [' ✧ ✧', ' ', ' ✧ ✧'],
99
+ [' ', ' ', ' '],
100
+ ];
101
+
102
+ const colors = [
103
+ [255, 255, 0],
104
+ [255, 200, 0],
105
+ [255, 140, 0],
106
+ [255, 80, 0],
107
+ [255, 40, 0],
108
+ [200, 20, 0],
109
+ [100, 0, 0],
110
+ ];
111
+
112
+ // Print initial blank frame
113
+ for (let i = 0; i < 3; i++) process.stdout.write('\n');
114
+
115
+ for (let f = 0; f < frames.length; f++) {
116
+ const [r, g, b] = colors[f];
117
+ process.stdout.write(UP(3));
118
+ for (const line of frames[f]) {
119
+ process.stdout.write(` \x1b[38;2;${r};${g};${b}m${line.padEnd(20)}\x1b[0m\n`);
120
+ }
121
+ await sleep(80);
122
+ }
123
+ }