localpeek 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/GUIDE.md ADDED
@@ -0,0 +1,128 @@
1
+ # LocalPeek Guide
2
+
3
+ Simple words. No jargon. Read this if something is confusing.
4
+
5
+ ## What LocalPeek do
6
+
7
+ You build website on computer. Computer show website at `localhost`. Phone cannot see `localhost` — phone not the same computer.
8
+
9
+ LocalPeek find real address of your computer on your Wi-Fi (like `192.168.1.5`). LocalPeek make QR code with that address. You scan QR with phone. Phone open website. Done.
10
+
11
+ No cloud. No upload. No account. Just computer and phone talking on same Wi-Fi, direct.
12
+
13
+ ## Install
14
+
15
+ Global install (use anywhere):
16
+
17
+ ```bash
18
+ npm install -g localpeek
19
+ ```
20
+
21
+ Or skip install, run once:
22
+
23
+ ```bash
24
+ npx localpeek
25
+ ```
26
+
27
+ ## Basic use
28
+
29
+ Go to your project folder. Run command:
30
+
31
+ ```bash
32
+ localpeek
33
+ ```
34
+
35
+ or short version:
36
+
37
+ ```bash
38
+ lp
39
+ ```
40
+
41
+ LocalPeek do rest:
42
+
43
+ 1. Look at folder. Guess what kind of project (plain HTML? Vite? Next.js? Astro? something else?).
44
+ 2. Start dev server, correct way, so phone can reach it.
45
+ 3. Find your computer's Wi-Fi address.
46
+ 4. Print QR code in terminal.
47
+ 5. Show URL too, in case QR scan hard.
48
+
49
+ Scan QR with phone camera. Website open.
50
+
51
+ Stop anytime: press `Ctrl+C`.
52
+
53
+ ## Supported project types
54
+
55
+ - Plain HTML/CSS/JS (just an `index.html`)
56
+ - Vite
57
+ - Next.js
58
+ - Astro
59
+ - Vue, Nuxt, Svelte, SvelteKit, Angular, Remix — LocalPeek use your project's own `dev` script and try to point it at right address
60
+ - Any other npm project with a `dev` script in `package.json`
61
+
62
+ LocalPeek never change your project files. LocalPeek only read, never write.
63
+
64
+ ## LAN requirement (important!)
65
+
66
+ Computer and phone must be on **same network**.
67
+
68
+ Good:
69
+ - Both on same Wi-Fi router.
70
+ - Phone connect to computer's Mobile Hotspot, computer use that hotspot's network.
71
+
72
+ Bad (will not work):
73
+ - Computer on Wi-Fi, phone on mobile data (4G/5G). Different network, no connection possible.
74
+ - Computer connected through VPN. VPN can hide or change your real address. Try disconnecting VPN if QR doesn't work.
75
+ - Phone and computer on same Wi-Fi, but router use "client isolation" or "AP isolation" (common on public/office/guest Wi-Fi, and even some home routers). This blocks devices from seeing each other even on same network. Try a different network, or check router settings.
76
+
77
+ ## Phone hotspot behavior
78
+
79
+ Using phone as hotspot for the computer? That's fine — same rule applies, both devices need to be on the same network. When phone is the hotspot, computer connects to phone's network, and things should just work the same way.
80
+
81
+ ## Firewall
82
+
83
+ Your computer's firewall might block incoming connections on the dev server's port. LocalPeek does **not** touch your firewall automatically (that would be risky to do blindly).
84
+
85
+ If your phone times out trying to connect:
86
+
87
+ - **Windows**: Windows Defender Firewall may ask permission the first time Node.js tries to accept a network connection. Allow it for Private networks.
88
+ - **macOS**: System Settings → Network → Firewall may block incoming connections to `node`. Allow it, or temporarily turn off firewall to test.
89
+ - **Linux**: check `ufw` or your distro's firewall tool; you may need to allow the port.
90
+
91
+ ## Common errors, explained
92
+
93
+ **"No project found in [folder]"**
94
+ LocalPeek looked in that folder and found no `index.html` and no usable `package.json`. Make sure you're in the right folder.
95
+
96
+ **"This project has a package.json but no installed dependencies"**
97
+ Your project needs `npm install` (or `yarn`/`pnpm install`) run first. LocalPeek never installs things for you — that's your call.
98
+
99
+ **"No usable LAN network address was found on this computer"**
100
+ LocalPeek couldn't find a normal Wi-Fi/Ethernet address. Usually means you're not connected to any network, or you're only connected through virtual adapters (VPN, Docker, VM). Connect to real Wi-Fi/Ethernet and try again.
101
+
102
+ **"Port already in use" / "Port X was busy — using Y instead"**
103
+ Something else is already using that port. LocalPeek just picks the next free one automatically. If LocalPeek instead says it's *reusing* a server on that port, that's because something already running there looks like your project — LocalPeek shows you the QR for it instead of starting a duplicate.
104
+
105
+ **Phone scans QR but page never loads**
106
+ Almost always one of: different networks, VPN in the way, router client isolation, or firewall blocking the port. See sections above.
107
+
108
+ ## How framework detection works
109
+
110
+ LocalPeek looks (only reads, never edits) at:
111
+
112
+ - `package.json` — dependencies, devDependencies, and the `scripts.dev` entry
113
+ - Known config files — `vite.config.*`, `next.config.*`, `astro.config.*`, `nuxt.config.*`, `angular.json`, `svelte.config.js`, etc.
114
+ - Whether `node_modules` exists
115
+
116
+ It checks specific frameworks first (Next.js, Astro, SvelteKit, etc.), then falls back to Vite, then to any project with a `dev` script, then to plain HTML if there's an `index.html`.
117
+
118
+ LocalPeek always prefers your existing `dev` script over inventing its own command — it just adds flags (like `--host`) so the server is reachable from your phone, and picks a port.
119
+
120
+ ## Publishing / updating this package (for maintainers)
121
+
122
+ 1. Make your changes.
123
+ 2. Bump the version in `package.json` (`npm version patch` / `minor` / `major`).
124
+ 3. Log in once: `npm login`.
125
+ 4. Publish: `npm publish`.
126
+ 5. Tag the release in git and push tags: `git push --follow-tags`.
127
+
128
+ That's it. No build step — LocalPeek ships as plain CommonJS.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Dipto Thakur
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,125 @@
1
+ <div align="center">
2
+
3
+ # LocalPeek
4
+
5
+ **See your local dev project on your phone in 5 seconds zero cloud, zero tunnels, zero setup.**
6
+ One command turns any `localhost` project into a scannable QR code on your own Wi-Fi.
7
+
8
+ [![npm version](https://img.shields.io/npm/v/localpeek.svg)](https://www.npmjs.com/package/localpeek)
9
+ [![npm downloads](https://img.shields.io/npm/dm/localpeek.svg)](https://www.npmjs.com/package/localpeek)
10
+ [![license](https://img.shields.io/npm/l/localpeek.svg)](./LICENSE)
11
+ [![node](https://img.shields.io/node/v/localpeek.svg)](https://nodejs.org)
12
+
13
+ ```bash
14
+ npx localpeek
15
+ ```
16
+
17
+ **That's it.** Scan the QR code. Your project opens on your phone.
18
+
19
+ </div>
20
+
21
+ ---
22
+
23
+ ## Why LocalPeek
24
+
25
+ Testing responsive design usually means deploying, tunneling through ngrok, or typing your laptop's IP address into your phone by hand. LocalPeek skips all of that.
26
+
27
+ - ⚡ **One command, zero config** `npx localpeek` and you're done, no flags required
28
+ - 🔒 **100% local, always** traffic never leaves your Wi-Fi network. No cloud relay, no account, no telemetry, no data collection
29
+ - 📷 **Real terminal QR code** scan and go, no typing IP addresses on a phone keyboard
30
+ - 🧠 **Auto-detects your stack** Plain HTML, Vite, Next.js, Astro, Vue, Nuxt, Svelte, SvelteKit, Angular, Remix
31
+ - 🛡️ **Never touches your project files** read-only detection, your `dev` script stays the source of truth
32
+ - 🖥️ **Cross-platform** Windows, macOS, Linux, WSL, and phone hotspots all work
33
+ - 🪶 **Tiny footprint** one dependency, no bloated framework underneath
34
+
35
+ If that saved you a `ngrok` signup or a "what's my IP" search, a ⭐ on the repo goes a long way it's how other developers find this instead of a heavier alternative.
36
+
37
+ ---
38
+
39
+ ## Install
40
+
41
+ The whole point is that you usually don't need to install anything:
42
+
43
+ ```bash
44
+ npx localpeek
45
+ ```
46
+
47
+ Prefer it always available as a command:
48
+
49
+ ```bash
50
+ npm install -g localpeek
51
+ ```
52
+
53
+ ```bash
54
+ localpeek # full command
55
+ lp # short alias
56
+ ```
57
+
58
+ ---
59
+
60
+ ## Usage
61
+
62
+ ```bash
63
+ cd your-project
64
+ localpeek
65
+ ```
66
+
67
+ ```bash
68
+ localpeek # detect and run the current project
69
+ localpeek --port 5000 # ask for a specific port
70
+ localpeek --iface eth0 # prefer a specific network interface
71
+ localpeek --dir ../app # point at a different project folder
72
+ localpeek --help # see all options
73
+ ```
74
+
75
+ ## What happens under the hood
76
+
77
+ 1. Reads the current folder and figures out what kind of project it is.
78
+ 2. Starts (or reuses) the dev server, bound so your **phone** can reach it not just `localhost`.
79
+ 3. Finds your computer's real LAN IP address (skipping VPNs, Docker, and virtual adapters).
80
+ 4. Renders a QR code directly in your terminal no image file, no upload, no third-party API.
81
+ 5. You scan it. Your phone opens the project. Live-reload, dev tools, everything works as normal.
82
+
83
+ Nothing here talks to the internet. LocalPeek never sends your project, your code, your IP, or anything else off of your machine.
84
+
85
+ ---
86
+
87
+ ## Supported projects
88
+
89
+ | Project type | Support |
90
+ |---|---|
91
+ | Plain HTML/CSS/JS | ✅ built-in static server |
92
+ | Vite | ✅ native adapter |
93
+ | Next.js | ✅ native adapter |
94
+ | Astro | ✅ native adapter |
95
+ | Vue, Nuxt, Svelte, SvelteKit, Angular, Remix | ✅ via generic adapter (reuses your `dev` script) |
96
+ | Anything else with a `dev` script | ✅ generic fallback |
97
+
98
+ ## Specs
99
+
100
+ | | |
101
+ |---|---|
102
+ | **Dependencies** | 1 (`qrcode-terminal`) |
103
+ | **Requires** | Node.js ≥ 16 |
104
+ | **Platforms** | Windows · macOS · Linux · WSL |
105
+ | **Network** | Same-LAN only Wi-Fi, Ethernet, or phone hotspot |
106
+ | **Cloud/telemetry** | None. Ever. |
107
+ | **Project files** | Read-only never modified |
108
+
109
+ Full setup requirements, firewall notes, and troubleshooting: see **[GUIDE.md](./GUIDE.md)**.
110
+
111
+ ---
112
+
113
+ ## Support the project
114
+
115
+ If LocalPeek saved you time:
116
+
117
+ - ⭐ **[Star the repo](https://github.com/dipto-thakur/localpeek)** the easiest way to help others find it
118
+ - 🐛 Open an issue if something breaks on your setup
119
+ - 👀 **[Follow @dipto-thakur](https://github.com/dipto-thakur)** for more small, focused dev tools like this one
120
+
121
+ ## License
122
+
123
+ MIT see [LICENSE](./LICENSE).
124
+
125
+ Built by [Dipto Thakur](https://github.com/dipto-thakur).
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+
3
+ 'use strict';
4
+
5
+ // CAVEMAN: This file just hands off to src/cli.js.
6
+ // Keep it tiny so `require` overhead on startup stays low.
7
+
8
+ const { run } = require('../src/cli.js');
9
+
10
+ run(process.argv.slice(2)).catch((err) => {
11
+ // Last-resort catch. Anything expected should already be handled
12
+ // inside cli.js with a friendly message. This is only for truly
13
+ // unexpected crashes.
14
+ // eslint-disable-next-line no-console
15
+ console.error('\nLocalPeek hit an unexpected error:\n');
16
+ // eslint-disable-next-line no-console
17
+ console.error(err && err.stack ? err.stack : err);
18
+ process.exitCode = 1;
19
+ });
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "localpeek",
3
+ "version": "1.0.0",
4
+ "description": "Open your local dev project on your phone over LAN. Zero cloud, zero tunnels, zero setup.",
5
+ "keywords": [
6
+ "cli",
7
+ "lan",
8
+ "dev-server",
9
+ "qr-code",
10
+ "mobile",
11
+ "localhost",
12
+ "vite",
13
+ "nextjs"
14
+ ],
15
+ "homepage": "https://github.com/dipto-thakur/localpeek",
16
+ "bugs": {
17
+ "url": "https://github.com/dipto-thakur/localpeek/issues"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/dipto-thakur/localpeek.git"
22
+ },
23
+ "license": "MIT",
24
+ "author": "Dipto Thakur (https://github.com/dipto-thakur)",
25
+ "type": "commonjs",
26
+ "main": "src/cli.js",
27
+ "bin": {
28
+ "localpeek": "bin/localpeek.js",
29
+ "lp": "bin/localpeek.js"
30
+ },
31
+ "directories": {
32
+ "test": "test"
33
+ },
34
+ "files": [
35
+ "bin",
36
+ "src",
37
+ "GUIDE.md",
38
+ "README.md",
39
+ "LICENSE"
40
+ ],
41
+ "scripts": {
42
+ "test": "node test/run.js"
43
+ },
44
+ "dependencies": {
45
+ "qrcode-terminal": "^0.12.0"
46
+ },
47
+ "devDependencies": {},
48
+ "engines": {
49
+ "node": ">=16"
50
+ }
51
+ }
@@ -0,0 +1,27 @@
1
+ 'use strict';
2
+
3
+ // CAVEMAN: Astro's dev CLI takes --host and --port, same shape as Vite
4
+ // (Astro is built on Vite under the hood).
5
+
6
+ const DEFAULT_PORT = 4321;
7
+
8
+ function buildCommand({ project, port, host }) {
9
+ const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
10
+ const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
11
+
12
+ if (project.devScript) {
13
+ return {
14
+ command: npmCmd,
15
+ args: ['run', 'dev', '--', '--host', host, '--port', String(port)],
16
+ env: process.env,
17
+ };
18
+ }
19
+
20
+ return {
21
+ command: npxCmd,
22
+ args: ['astro', 'dev', '--host', host, '--port', String(port)],
23
+ env: process.env,
24
+ };
25
+ }
26
+
27
+ module.exports = { buildCommand, DEFAULT_PORT };
@@ -0,0 +1,38 @@
1
+ 'use strict';
2
+
3
+ // CAVEMAN: Generic fallback for any framework we don't special-case
4
+ // (Vue, Nuxt, Svelte/SvelteKit, Angular, Remix, or literally anything
5
+ // with a "dev" script). We never invent a command that doesn't exist
6
+ // in the project — we reuse `scripts.dev` and just try to steer it
7
+ // onto the right host/port using common conventions.
8
+ //
9
+ // Strategy, in order of harmlessness:
10
+ // 1. Set PORT and HOST env vars (respected by a lot of tooling).
11
+ // 2. Append framework-appropriate CLI flags after `--`, if the
12
+ // caller supplied any (see detect.js `defaultFlags`).
13
+ //
14
+ // If none of this works for a particular tool, LocalPeek still shows
15
+ // the correct LAN URL — worst case the user needs to pass their own
16
+ // host flag, which the terminal output points them toward.
17
+
18
+ function buildCommand({ project, port, host }) {
19
+ const extraFlags = project.defaultFlags || [];
20
+ const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
21
+
22
+ const args = ['run', 'dev'];
23
+ if (extraFlags.length > 0) {
24
+ args.push('--', ...extraFlags, '--port', String(port));
25
+ }
26
+
27
+ return {
28
+ command: npmCmd,
29
+ args,
30
+ env: {
31
+ ...process.env,
32
+ PORT: String(port),
33
+ HOST: host,
34
+ },
35
+ };
36
+ }
37
+
38
+ module.exports = { buildCommand };
@@ -0,0 +1,97 @@
1
+ 'use strict';
2
+
3
+ const http = require('http');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ // CAVEMAN: This is a plain, dependency-free static file server.
8
+ // Only used when there is no framework and no package.json dev
9
+ // script — just an index.html sitting in a folder.
10
+
11
+ const MIME_TYPES = {
12
+ '.html': 'text/html; charset=utf-8',
13
+ '.htm': 'text/html; charset=utf-8',
14
+ '.js': 'text/javascript; charset=utf-8',
15
+ '.mjs': 'text/javascript; charset=utf-8',
16
+ '.css': 'text/css; charset=utf-8',
17
+ '.json': 'application/json; charset=utf-8',
18
+ '.png': 'image/png',
19
+ '.jpg': 'image/jpeg',
20
+ '.jpeg': 'image/jpeg',
21
+ '.gif': 'image/gif',
22
+ '.svg': 'image/svg+xml',
23
+ '.ico': 'image/x-icon',
24
+ '.webp': 'image/webp',
25
+ '.woff': 'font/woff',
26
+ '.woff2': 'font/woff2',
27
+ '.ttf': 'font/ttf',
28
+ '.txt': 'text/plain; charset=utf-8',
29
+ '.map': 'application/json; charset=utf-8',
30
+ };
31
+
32
+ function safeJoin(root, urlPath) {
33
+ const decoded = decodeURIComponent(urlPath.split('?')[0]);
34
+ const resolved = path.normalize(path.join(root, decoded));
35
+ // CAVEMAN: Block path traversal (../../etc/passwd style requests).
36
+ if (!resolved.startsWith(path.normalize(root))) {
37
+ return null;
38
+ }
39
+ return resolved;
40
+ }
41
+
42
+ /**
43
+ * Starts a static file server rooted at `dir`, listening on
44
+ * `host:port`. Resolves with the http.Server instance once listening.
45
+ */
46
+ function startStaticServer(dir, port, host) {
47
+ const server = http.createServer((req, res) => {
48
+ let filePath = safeJoin(dir, req.url || '/');
49
+
50
+ if (!filePath) {
51
+ res.writeHead(400);
52
+ res.end('Bad request');
53
+ return;
54
+ }
55
+
56
+ fs.stat(filePath, (err, stats) => {
57
+ if (!err && stats.isDirectory()) {
58
+ filePath = path.join(filePath, 'index.html');
59
+ }
60
+
61
+ fs.readFile(filePath, (readErr, data) => {
62
+ if (readErr) {
63
+ // Fall back to root index.html for simple client-side
64
+ // routing setups; otherwise a plain 404.
65
+ const fallback = path.join(dir, 'index.html');
66
+ if (filePath !== fallback) {
67
+ fs.readFile(fallback, (fallbackErr, fallbackData) => {
68
+ if (fallbackErr) {
69
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
70
+ res.end('404 Not Found');
71
+ } else {
72
+ res.writeHead(200, { 'Content-Type': MIME_TYPES['.html'] });
73
+ res.end(fallbackData);
74
+ }
75
+ });
76
+ return;
77
+ }
78
+ res.writeHead(404, { 'Content-Type': 'text/plain' });
79
+ res.end('404 Not Found');
80
+ return;
81
+ }
82
+
83
+ const ext = path.extname(filePath).toLowerCase();
84
+ const mime = MIME_TYPES[ext] || 'application/octet-stream';
85
+ res.writeHead(200, { 'Content-Type': mime });
86
+ res.end(data);
87
+ });
88
+ });
89
+ });
90
+
91
+ return new Promise((resolve, reject) => {
92
+ server.once('error', reject);
93
+ server.listen(port, host, () => resolve(server));
94
+ });
95
+ }
96
+
97
+ module.exports = { startStaticServer };
@@ -0,0 +1,28 @@
1
+ 'use strict';
2
+
3
+ // CAVEMAN: Next's dev CLI takes -H (hostname) and -p (port).
4
+ // Binding -H 0.0.0.0 is what actually makes it reachable from
5
+ // another device on the LAN — the default is often localhost-only.
6
+
7
+ const DEFAULT_PORT = 3000;
8
+
9
+ function buildCommand({ project, port, host }) {
10
+ const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
11
+ const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
12
+
13
+ if (project.devScript) {
14
+ return {
15
+ command: npmCmd,
16
+ args: ['run', 'dev', '--', '-H', host, '-p', String(port)],
17
+ env: process.env,
18
+ };
19
+ }
20
+
21
+ return {
22
+ command: npxCmd,
23
+ args: ['next', 'dev', '-H', host, '-p', String(port)],
24
+ env: process.env,
25
+ };
26
+ }
27
+
28
+ module.exports = { buildCommand, DEFAULT_PORT };
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ // CAVEMAN: Vite already understands --host and --port natively.
4
+ // Prefer the project's own "dev" script (it may set up plugins,
5
+ // env files, etc.) and just append flags after `--`.
6
+
7
+ const DEFAULT_PORT = 5173;
8
+
9
+ function buildCommand({ project, port, host }) {
10
+ const npmCmd = process.platform === 'win32' ? 'npm.cmd' : 'npm';
11
+ const npxCmd = process.platform === 'win32' ? 'npx.cmd' : 'npx';
12
+
13
+ if (project.devScript) {
14
+ return {
15
+ command: npmCmd,
16
+ args: ['run', 'dev', '--', '--host', host, '--port', String(port), '--strictPort'],
17
+ env: process.env,
18
+ };
19
+ }
20
+
21
+ // No dev script (unlikely for a real Vite project, but possible
22
+ // for a minimal setup) — call the Vite CLI directly.
23
+ return {
24
+ command: npxCmd,
25
+ args: ['vite', '--host', host, '--port', String(port), '--strictPort'],
26
+ env: process.env,
27
+ };
28
+ }
29
+
30
+ module.exports = { buildCommand, DEFAULT_PORT };