True zero-dependency (download one thing, double-click, works) requires Electron or Tauri — 2–3 days of engineering + $99/year Apple Developer ID for macOS signing. That’s overkill for a free Threads giveaway.
The practical path: publish to npm, swap cloudflared for a zero-account tunnel, and ship it as a 2-command install. The photographer runs brew install node once, then npx photodrop /path every time. Everything else auto-downloads.
The content angle — “your photos never leave your machine” — is the viral hook. The 2-command install is simple enough for a Threads post. Don’t over-engineer the distribution before validating the content resonance.
npx photodrop first. If Threads traction validates demand, upgrade to Electron .app. Don't build the installer before you know anyone wants it.
PhotoDrop works. It serves photos from an SD card over a Cloudflare Tunnel so recipients browse and download without uploading to any cloud. But right now it requires: Node.js, npm, 4 npm packages (including native binaries), and cloudflared with a Cloudflare account. That’s fine for Eric. It’s not distributable to a photographer who just wants to share hackathon photos.
Every packaging decision flows from what PhotoDrop actually depends on:
| Dependency | Type | Size | Packaging Difficulty |
|---|---|---|---|
| Node.js | Runtime | ~40 MB | Embeddable in Electron/Tauri; or user installs via Homebrew |
| sharp | Native C++ (libvips) | ~8 MB | HARD — prebuilt binaries auto-download via npm, but cannot be embedded in SEA/Bun/Deno compile123 |
| exiftool-vendored | Perl binary bundle | ~20 MB | Works in Electron; auto-downloads via npm4 |
| archiver | Pure JS | <1 MB | No issue |
| express | Pure JS | <1 MB | No issue |
| cloudflared | Go binary | ~50 MB | Requires Cloudflare account for named tunnels; separate install5 |
sharp is the structural blocker. It’s what makes single-binary compilation impossible across every tool tested.
| Dimension | Rating | Evidence |
|---|---|---|
| Single-binary Node.js (SEA) | FAILS | Node.js 25.5 --build-sea cannot bundle native .node files1 |
| Bun compile | FAILS | Sharp regression in v1.3.5, cross-compile broken, still requires node_modules alongside binary2 |
| Deno compile | FAILS | Sharp “could not load module” error in compiled binaries; open issue, no fix3 |
| pkg (Vercel) | DEPRECATED | Archived Jan 2024. Final version 5.8.1.6 |
| Electron | WORKS | electron-rebuild handles sharp; extraResources for cloudflared7 |
| Tauri | COMPLEX | Sidecar pattern works but requires rewriting UI in web + Rust bridge8 |
| npm / npx | WORKS | sharp prebuilds auto-download per platform; exiftool-vendored self-contained9 |
npx photodrop)| Dimension | Assessment |
|---|---|
| User effort | brew install node once, then npx photodrop /path each time |
| Dependencies | Node.js only. Everything else auto-downloads. |
| Download size | ~70 MB (node_modules cached after first run) |
| Platforms | macOS (arm64 + x64), Windows, Linux |
| Engineering effort | ~2–4 hours (swap cloudflared → localtunnel, publish to npm) |
| Tunnel | localtunnel (npm, zero account) or cloudflared (if installed) |
brew install node is one command. The Threads post can include a 3-line code block that looks clean and impressive. Ship this first, validate demand, then upgrade if warranted.
| Dimension | Assessment |
|---|---|
| User effort | Download .dmg, drag to Applications, double-click |
| Dependencies | None |
| Download size | ~200–300 MB (Chromium + Node + sharp + exiftool + cloudflared) |
| Platforms | macOS arm64, macOS x64, Windows x64 (separate builds) |
| Engineering effort | 2–3 days. Plus: Apple Developer ID ($99/year), code signing, notarization11 |
| Maintenance | Ongoing: Electron version updates, dependency rebuilds per platform |
| Dimension | Assessment |
|---|---|
| User effort | Download + install |
| Download size | ~30–80 MB (system WebView, no Chromium) |
| Engineering effort | 3–5 days. Requires sidecar pattern for sharp + exiftool + cloudflared.8 UI rewrite for Tauri IPC. |
| Maturity | Sidecar pattern documented but complex. Node.js-as-sidecar requires compiling Node server into binary first — circular problem with sharp. |
| Dimension | Assessment |
|---|---|
| User effort | brew install ericsan/tools/photodrop |
| Dependencies | Homebrew (macOS only) |
| Engineering effort | ~1 day. Formula + GitHub Actions for bottles.12 |
| Limitation | macOS only. Formulas with native modules are tied to Node major version.12 |
curl | bash)| Dimension | Assessment |
|---|---|
| User effort | Paste one line in Terminal |
| What it does | Checks for Node, installs if missing, runs npm install -g photodrop |
| Engineering effort | ~2 hours |
| Limitation | Requires terminal comfort. “Pipe to bash” pattern is controversial. |
Cloudflared requires a Cloudflare account and configuration. For a distributable tool, this is a non-starter. Alternatives:
| Tunnel | Account Required? | Install Method | Reliability | Status |
|---|---|---|---|---|
| localtunnel | No | npm (bundled) | 503 errors reported; unmaintained13 | RISKY |
| bore | No | brew install bore | Stable, 10.8K GitHub stars14 | GOOD |
| cloudflared (quick) | No* | brew install cloudflared | Enterprise-grade5 | BEST |
| serveo | No | SSH (built-in) | Intermittent | FRAGILE |
* cloudflared tunnel --url http://localhost:3000 creates a free ephemeral tunnel with a random trycloudflare.com URL. No account, no login, no config. Named tunnels (custom domains) require an account, but the ephemeral mode is perfect for distribution.5
https://xxx.trycloudflare.com URL. Enterprise-grade infrastructure. Already built into PhotoDrop’s startTunnel() function — it falls back to ephemeral mode when no cloudflared.yml exists.
Sharp is PhotoDrop’s heaviest dependency. It compiles libvips (a C library) into platform-specific .node binaries. This is what kills every single-binary packager. But there’s nuance:
@electron/rebuild recompiles sharp for Electron’s ABI. Works but adds build complexity.7For the npm distribution path, sharp is a non-issue — it just works. It only becomes a problem if we pursue single-binary or Electron.
exiftool-vendored bundles a Perl runtime + ExifTool script (~20 MB, 292 files).4 It auto-downloads via npm and works cross-platform. For Electron, it’s documented to work with electron-builder.16 Not a blocking issue for any path.
Honest answer: most don’t. But:
brew install node is literally one commandThe Threads post is content marketing, not product distribution. The value is the story (“your photos never leave your machine”), not the install experience. A 2-command install is simple enough to screenshot in a code block. If 500 people see the post, maybe 20 try it, and those 20 are technical enough to run terminal commands. Optimizing for the other 480 (who will never install anything) is wasted effort.
True. 503 errors documented, project barely maintained.13 But cloudflared’s ephemeral mode (cloudflared tunnel --url) is enterprise-grade, requires zero account, and is already in PhotoDrop. The user just needs brew install cloudflared. Two installs total: brew install node cloudflared. Still one line.
Counter: Homebrew has 620K+ GitHub stars. npx has millions of daily executions. The Mac photography community already uses Terminal for ffmpeg, ImageMagick, tethering tools. The audience Eric is targeting (HK/TW photographers who shoot RAW) are not grandmothers — they’re people who chose to shoot RAW over JPEG, which is itself a technical decision.
Checked. gallery-server (npm) does photo serving but no RAW support, no tunneling, no embedded preview extraction.17 ezshare does LAN file sharing but no gallery UI, no internet access.18 Nothing combines RAW preview extraction + gallery UI + tunnel in one command. PhotoDrop is genuinely novel.
Changes needed to make PhotoDrop distributable via npx:
| Change | Effort | Why |
|---|---|---|
| Keep cloudflared ephemeral mode as default | 0 (already built) | Zero-account tunnel, enterprise-grade |
| Add fallback if cloudflared not installed | 30 min | Print install instructions instead of crashing |
| Clean up CLI arguments | 30 min | Nice --help, version flag, clear error messages |
Add #!/usr/bin/env node shebang | Already there | Makes npx work |
Publish to npm as photodrop | 15 min | npm publish |
| Add README with install instructions | 30 min | npm page + GitHub |
Result: Any Mac user runs:
brew install node cloudflared # one-time setup npx photodrop /Volumes/UNTITLED # each time
That’s it. Server starts, tunnel opens, URL prints. Copy URL, send on WhatsApp.
Post to HK + TW Threads. Hook: “built a tool for photographers who are tired of uploading RAW files to random cloud services. Your photos stay on YOUR machine. Tunnel creates a link. Recipient browses + downloads.” Include screenshot of gallery UI + the 2-line install.
| Signal | Action |
|---|---|
| Threads post gets >50 saves/bookmarks | Build Homebrew tap (brew install photodrop = single command) |
| Threads post gets >200 saves + DM requests | Build Electron .app for true zero-dep. Invest $99 in Apple Developer ID. |
| Threads post gets <20 engagement | Ship to Eric’s personal workflow. Don’t invest further packaging. |
npx photodrop#!/usr/bin/env node at top of server.js"bin": { "photodrop": "./server.js" } in package.json (already done)"keywords", "repository", "license" to package.jsonnpm login + npm publishnpx photodrop ~/Desktop/test-photos from a clean machinephotodrop is taken on npmjs.com. If so, use @ericsan/photodrop (scoped).npx downloads ~70 MB on first run (node_modules). Subsequent runs use cache. Add a “first run may take 30–60 seconds” note./Volumes/<name>. Windows uses drive letters. Linux varies. The CLI should accept any path.TRIAL — npm publish first, Electron only if validated.
The honest answer to “can we ship zero-dep?” is: yes, but it costs 2–3 days + $99/year and the engineering is not trivial (Electron + code signing + notarization + platform-specific builds). That’s disproportionate to the goal of a free Threads giveaway.
The npm path gets 90% of the way there with 10% of the effort. The remaining 10% (installing Node.js) is one Homebrew command. For a HK/TW photographer audience reached via Threads, this is sufficient.
What would change the verdict:
bun build --compile becomes the simplest zero-dep pathcloudflared tunnel --url, no account required