From a29f7df16bf7cc1fe5719da8a3e13ff90992b81e Mon Sep 17 00:00:00 2001
From: Mohsen Karimi
Date: Tue, 20 Oct 2026 23:16:01 +0200
Subject: [PATCH 1/5] Turn Electron Detector into a friendly, evidence-based
Mac app census
---
.github/ISSUE_TEMPLATE/bug_report.md | 1 +
.github/banner.svg | 39 +--
.github/preview.svg | 32 ++
CONTRIBUTING.md | 37 +--
README.md | 239 +++++++--------
electron-detector.sh | 438 ++++++++++++---------------
scripts/render-preview.py | 28 ++
tests/test_detector.py | 204 +++++++++++++
8 files changed, 569 insertions(+), 449 deletions(-)
create mode 100644 .github/preview.svg
create mode 100644 scripts/render-preview.py
create mode 100644 tests/test_detector.py
diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index 38139b9..590a473 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -18,6 +18,7 @@ A clear description of what went wrong.
- Is it Electron-based? (yes/no, and how you know)
**Output**
+Remove any app names or other information you do not want to share publicly. Please include the detector version (`bash electron-detector.sh --version`).
```
paste the output of `./electron-detector.sh --json` or the terminal output here
```
diff --git a/.github/banner.svg b/.github/banner.svg
index 139a6ec..c157458 100644
--- a/.github/banner.svg
+++ b/.github/banner.svg
@@ -1,34 +1,5 @@
-
+
\ No newline at end of file
diff --git a/.github/preview.svg b/.github/preview.svg
new file mode 100644
index 0000000..07c4338
--- /dev/null
+++ b/.github/preview.svg
@@ -0,0 +1,32 @@
+
\ No newline at end of file
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index a0e7919..087405f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,38 +1,7 @@
# Contributing
-Thanks for considering a contribution to Electron Detector.
+Keep the scanner compatible with macOS `/bin/bash` 3.2 and system utilities. See README for the JSON contract and test commands. Add regression fixtures for detection, path handling, and output changes. Avoid performance claims that the scanner cannot measure, and keep all scan data local. A missing Electron marker is never proof of native architecture.
-## Ways to help
+When reporting issues, include macOS and script versions and the detection marker you expected. Review reports before posting: app names may reveal personal information.
-- **New detection rules** — if you know of an app that hides its Electron internals differently, open a PR adding a check to `is_electron()` in `electron-detector.sh`, or open an issue with the app name/version and we'll add it.
-- **Bug fixes** — if the script misbehaves on your setup, please include your macOS version and `bash --version`.
-- **README / docs** — typo fixes and clarity improvements are always welcome.
-
-## Development
-
-The project is a single dependency-free Bash script, kept that way on purpose.
-
-```bash
-git clone https://github.com/macnative/electron-detector.git
-cd electron-detector
-chmod +x electron-detector.sh
-./electron-detector.sh
-```
-
-Before opening a PR:
-
-- Run [ShellCheck](https://www.shellcheck.net/) against `electron-detector.sh` (CI runs this automatically on every PR).
-- Test both the default and `--json`/`--user`/`--quiet` flag combinations.
-- Keep the script POSIX-friendly Bash — no external binaries beyond what macOS ships by default (`find`, `du`, `defaults`, `basename`).
-
-## Reporting an issue
-
-Open a [GitHub issue](https://github.com/macnative/electron-detector/issues) with:
-
-- Your macOS version
-- The app you expected to be detected (or misdetected), with its version
-- Output of `./electron-detector.sh --json` if relevant
-
-## Code of conduct
-
-Be respectful and constructive. This is a small tool built for fun and utility — let's keep it a pleasant place to contribute.
+After changing the terminal output, run `python3 scripts/render-preview.py` on macOS to regenerate the README preview from `--demo`. Keep the example label visible. Never use a real user's installed apps in promotional assets.
diff --git a/README.md b/README.md
index bbf1504..f3bc6c0 100644
--- a/README.md
+++ b/README.md
@@ -1,190 +1,165 @@
+A tiny shell script that finds Electron runtime markers in your installed apps. See the lineup, inspect the evidence, and find out how much disk space those **whole app bundles** occupy.
----
+**One file. Runs with the Bash your Mac already has. No Homebrew, account, or admin password.**
-## Why this exists
+
+
+
-[Electron](https://www.electronjs.org/) lets developers ship one app for every platform by bundling a full copy of Chromium and Node.js inside it. That's great for developers — and rough on your Mac. Every Electron app you run is, under the hood, its own private browser tab: its own renderer process, its own ~100–300MB of idle RAM, its own hit to battery life.
+*Fictional apps, real output. Reproduce this preview with `bash electron-detector.sh --demo`.*
-Most people have no idea how many of their everyday apps work this way. **Electron Detector** scans your installed apps and tells you, in a few seconds, exactly which ones are Electron — with sizes, versions, and a straight answer on whether it's worth switching to something native.
+## Try it
-## Quick start
+Open **Terminal** (⌘ Space → Terminal), download the script, then inspect it in your editor:
-```bash
-curl -sL https://raw.githubusercontent.com/macnative/electron-detector/main/electron-detector.sh | bash
+```sh
+curl -fL https://raw.githubusercontent.com/macnative/electron-detector/main/electron-detector.sh -o electron-detector.sh
```
-That's it — no install, no config, nothing left behind. It's a read-only scan of `/Applications`; it doesn't modify anything on your system.
-
-## Example output
+Read [the source](electron-detector.sh), then run:
+```sh
+bash electron-detector.sh --interactive
```
-╔════════════════════════════════════════════════════════════╗
-║ ⚡ Electron Detector for macOS ║
-║ Find which apps are secretly Electron-based ║
-╚════════════════════════════════════════════════════════════╝
-
-🔍 Scanning /Applications...
-
-[▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓] Done!
-
-════════════════════════════════════════════════════════════
-
-⚠️ Found 5 Electron app(s) out of 47 total apps
-
-⚡ Electron Apps Detected:
-App Name Size Version
-────────────────────────────────────────────────────────────────
-Slack 165M 4.35.0
-Discord 156M 0.0.290
-Visual Studio Code 387M 1.85.0
-Postman 312M 10.20.0
-Notion 198M 2.2.3
+The scan checks `/Applications` and `~/Applications`, including subfolders. Apps don't need to be running. At the end, you can print or copy an anonymous summary, learn what the findings mean, or open MacNative to explore alternatives.
-════════════════════════════════════════════════════════════
+Prefer a plain report? Run `bash electron-detector.sh`. Curious about the look before scanning? Add `--demo --interactive`.
-📦 Resource Impact:
+Downloading needs internet. **Scanning works offline.** The script reads bundles and uses a temporary workspace that it removes on exit. It never edits, deletes, or launches your apps. The menu changes the clipboard only when you choose **c**, and opens a website only when you choose **a**. Don't use `sudo`.
-⚠️ High Electron count detected!
- Each Electron app runs a full Chromium browser
+## A census, with a little personality
-════════════════════════════════════════════════════════════
+- **Useful at a glance.** Largest detected bundles first, with app versions and the exact marker found.
+- **A small Mac companion.** A friendly face, a count of matches, and a compact report that stays in your scrollback.
+- **Something to share.** `--share` leaves out app names and folder paths. The interactive menu can copy that summary for you.
+- **Built for real folders.** Subfolders, spaces, Unicode, symlinked apps, and overlapping scan roots; nested helper apps aren't counted again.
+- **Comfortable in a terminal or a pipeline.** JSON output, quiet mode, `NO_COLOR`, and no surprise prompts.
-🚀 Want native alternatives?
- Check out: https://macnative.io
+No purity score. No invented RAM savings. Keep the apps you like.
-✓ Native apps are:
- • Faster and more responsive
- • Use less RAM and battery
- • Better macOS integration
- • Smaller app size
+## Share your census
-💡 Fun Fact:
- Electron apps can use 100-300MB RAM even when idle
- Native apps often use less than 50MB
-
-════════════════════════════════════════════════════════════
+```sh
+bash electron-detector.sh --share
```
-## Features
+```text
+My Mac app census: 3 of 42 inspected apps have Electron markers (7%).
+Detected bundles: 1.5 GiB on disk. Not a RAM or battery benchmark.
+No marker found does not prove an app is native.
+Try it: https://github.com/macnative/electron-detector
+Explore native alternatives: https://macnative.io/categories
+```
-- 🔎 Scans `/Applications` (and optionally `~/Applications`) for Electron apps
-- ⚡ Detects the Electron runtime via its framework bundle, not just an app name guess
-- 📊 Reports app name, disk size, and version for every match
-- 🎨 Clean, colorful terminal output — with a real progress bar, not a spinner
-- 🤖 `--json` mode for scripting, dashboards, or piping into `jq`
-- 🧠 Zero dependencies: pure Bash, nothing to install
-- 🔒 Read-only — it only inspects app bundles, it never modifies or deletes anything
+*Example numbers.* App names and paths are omitted, but your counts and sizes are still in the summary. Nothing is posted automatically. Full reports and screenshots include app names; review those before sharing.
-## Usage
+## Curious about alternatives?
-```bash
-electron-detector.sh [options]
+I also make **[MacNative](https://macnative.io)**, a directory for discovering Mac apps. If a result makes you curious, [browse by category](https://macnative.io/categories) and look for a native app that fits how you work.
-Options:
- -u, --user Also scan ~/Applications
- -j, --json Output machine-readable JSON instead of the report
- -q, --quiet Suppress the banner and tips, print results only
- -v, --version Print the version number
- -h, --help Show this help message
-```
+That's the connection: this tool shows you what's installed; MacNative helps you explore what else is out there. It doesn't guarantee a replacement for every match. There are no network requests during a scan and no per-app referral links. Use `--quiet` to omit the mascot and MacNative promotion.
-**Scan both system and user app directories:**
-
-```bash
-./electron-detector.sh --user
-```
+## Make it yours
-**Get JSON for scripting:**
+| Option | What it does |
+| --- | --- |
+| `-i`, `--interactive` | Offers share, clipboard, explanation, and alternatives actions after scanning |
+| `--path DIRECTORY` | Scans a custom folder; repeat for multiple folders; replaces the defaults |
+| `-j`, `--json` | Emits structured data, with app names but no personal folder paths |
+| `--share` | Prints a summary without app names or paths |
+| `--demo` | Shows fictional results without scanning; works with the interactive menu |
+| `-q`, `--quiet` | Omits the mascot, tips, and MacNative promotion |
+| `--no-color` | Disables colors; `NO_COLOR` is also respected |
+| `-u`, `--user` | Compatibility flag; user apps are now included by default |
+| `-v`, `--version` | Prints the version |
+| `-h`, `--help` | Shows help |
-```bash
-./electron-detector.sh --json | jq '.electron_apps[].name'
-```
+```sh
+# Scan another app folder
+bash electron-detector.sh --path '/Volumes/Work/Applications'
-## Manual installation
+# Save a report for your own tools
+bash electron-detector.sh --json > electron-report.json
-```bash
-git clone https://github.com/macnative/electron-detector.git
-cd electron-detector
-chmod +x electron-detector.sh
-./electron-detector.sh
+# Just the findings
+bash electron-detector.sh --quiet --no-color
```
-## How it works
-
-Electron Detector doesn't guess from an app's name or icon — it looks inside the `.app` bundle for the actual Electron runtime:
+`--interactive` needs terminal input and output. Use it after downloading, rather than piping the script into Bash. `--json` and `--share` are mutually exclusive; `--demo` cannot be combined with `--json` or `--path`.
-1. **Electron Framework** — `Contents/Frameworks/Electron Framework.framework`
-2. **Electron binary** — `Contents/MacOS/Electron`
+## Questions that come up
-If either is present, the app is running on Electron — full stop. This is the same signal macOS itself uses internally to launch the app, so it's accurate even when an app hides its origins well.
+**Does this tell me which apps are running right now?**
+No. It inspects installed bundles. Use Activity Monitor for running processes, CPU, and memory.
-## Why it matters
+**Is the disk total space I can save?**
+No. It's the measured size of the detected bundles, including the apps themselves. Replacement apps also use space, and user data is outside this measurement.
-| | Electron apps | Native apps |
-|---|---|---|
-| Idle RAM | 100–300MB per app | Often under 50MB |
-| Battery impact | Higher — runs a full Chromium instance | Lower — uses native frameworks |
-| Launch time | Slower — boots a browser engine | Near-instant |
-| Disk footprint | Large (bundles Chromium + Node.js) | Small |
-| macOS integration | Approximated | Native |
+**It missed an app.**
+Try `--path` if it's outside the default folders. Renamed or relocated Electron internals can be missed. [Open an issue](https://github.com/macnative/electron-detector/issues) with the app version and expected marker; please leave personal paths out.
-None of this means Electron apps are *bad* — it's a reasonable trade-off for developers shipping cross-platform software fast. But if you're stacking up a dozen of them, your battery and fans will notice. Once you know which apps are Electron, you can make that call for yourself.
+**I see a partial-scan warning.**
+Some folders or bundles couldn't be read. The results describe what was accessible. Try a smaller folder you can read; no admin access is required.
-Found some? Browse [**macnative.io**](https://macnative.io) for a curated directory of native macOS replacements.
+**Can I remove it afterward?**
+Yes. Delete the downloaded script and any report files you saved. There's no installer, background service, or auto-updater.
-## Requirements
+## What a detection means
-- macOS with Bash (ships by default)
-- Apps installed in `/Applications` or `~/Applications`
+The detector checks for either:
-## Troubleshooting
+- `Contents/Frameworks/Electron Framework.framework`
+- `Contents/MacOS/Electron`
-**"Permission denied" when running the script directly**
-```bash
-chmod +x electron-detector.sh
-```
+These are runtime markers, not a benchmark or an executable signature check. Renamed or relocated frameworks can be missed, and a coincidentally named file can be a false positive. A bundle may include Electron for only part of its functionality. Other web wrappers are outside this tool's scope. **No Electron marker does not mean native.**
-**Not finding an app you know is Electron**
-Some apps rename or relocate their internals. Open an issue with the app name and version and we'll add a detection rule.
+Sizes are allocated bundle disk usage from `du -sk`, in KiB (displayed as MiB/GiB). They exclude user data and caches. APFS cloning, compression, and shared files can complicate totals. The report does not estimate RAM, battery life, speed, or space you would save by switching. Apps changing during a scan may yield partial measurements. Inaccessible folders are reported as warnings; an empty folder yields a valid zero-app report. Symlink cycles may also cause traversal warnings.
-**Apps in unusual locations aren't scanned**
-By design, only `/Applications` and (with `--user`) `~/Applications` are scanned. This is deliberate — the script only reads standard, expected locations.
+Electron is a useful cross-platform framework. Keep apps that work for you. If you want software with a more native Mac feel, [browse MacNative by category](https://macnative.io/categories). A native app is not necessarily a feature-for-feature replacement.
-## Contributing
+## JSON schema v2
-Contributions are very welcome — new detection rules, bug fixes, and general polish all help. See [CONTRIBUTING.md](CONTRIBUTING.md) to get started, or just open an issue or PR.
+```json
+{
+ "schema_version": 2,
+ "detector_version": "2.0.0",
+ "total_apps": 1,
+ "electron_count": 1,
+ "total_size_kb": 409600,
+ "unknown_sizes": 0,
+ "scan_warnings": 0,
+ "electron_apps": [
+ { "name": "Example", "size_kb": 409600, "version": "1.0", "evidence": "Electron Framework.framework" }
+ ]
+}
+```
-## Credits
+`size_kb` is `null` when measurement fails. `total_size_kb` sums known sizes only. `total_apps` counts bundles with accessible Contents directories. `scan_warnings` counts traversal diagnostics and inaccessible bundles; it is not a count of missed apps. Version 2 replaces v1's string `size` with numeric `size_kb` and includes the user Applications folder by default. Errors go to stderr; JSON stdout stays machine-readable. The demo cannot export JSON, so fictional data cannot be mistaken for a real scan. Exit 0 means the scan completed (inspect warnings); 2 means invalid arguments or an unsupported platform; 1 means temporary workspace creation failed.
-Electron Detector is built and maintained by [**macnative**](https://macnative.io) — a directory of fast, native, non-Electron alternatives to the apps cluttering your Mac. Visit [macnative.io](https://macnative.io) to find one for every Electron app this tool flags.
+## Contributing & tests
-## License
+Use macOS and `/bin/bash`, not just a newer Homebrew Bash. Runtime dependencies must ship with macOS. Dev tests use Python 3; ShellCheck is optional locally and runs in CI.
-[MIT](LICENSE) — free to use, modify, and share.
+```sh
+/bin/bash -n electron-detector.sh
+python3 tests/test_detector.py
+shellcheck electron-detector.sh
+```
----
+New detection rules need fixtures for both true and false positives. Don't add telemetry or unmeasured performance claims. Please remove personal information before attaching reports to issues.
-
Made with ❤️ by macnative for people who like their Macs fast and their RAM free.
+[MIT license](LICENSE).
diff --git a/electron-detector.sh b/electron-detector.sh
index f377483..e91f278 100755
--- a/electron-detector.sh
+++ b/electron-detector.sh
@@ -1,267 +1,207 @@
#!/bin/bash
-#
-# Electron Detector — find Electron-based apps on macOS
-# https://github.com/macnative/electron-detector
-# https://macnative.io
-#
-# Licensed under the MIT License (see LICENSE).
-
-VERSION="1.0.0"
-
-# ---------------------------------------------------------------------------
-# Colors (disabled automatically when not attached to a terminal, or when
-# NO_COLOR is set — see https://no-color.org)
-# ---------------------------------------------------------------------------
-if [[ -t 1 && -z "${NO_COLOR:-}" ]]; then
- RED='\033[0;31m'
- GREEN='\033[0;32m'
- YELLOW='\033[1;33m'
- BLUE='\033[0;34m'
- PURPLE='\033[0;35m'
- CYAN='\033[0;36m'
- WHITE='\033[1;37m'
- BOLD='\033[1m'
- NC='\033[0m'
-else
- RED=''; GREEN=''; YELLOW=''; BLUE=''; PURPLE=''; CYAN=''; WHITE=''; BOLD=''; NC=''
-fi
-
-ELECTRON="⚡"
-NATIVE="✨"
-SCAN="🔍"
-WARN="⚠️"
-CHECK="✓"
-ROCKET="🚀"
-PACKAGE="📦"
-
-SCAN_PATHS=("/Applications")
-JSON_OUTPUT=false
-QUIET=false
-
+# Electron Detector / MacNative — MIT licensed. Compatible with macOS Bash 3.2.
+# No network calls during scanning. Never modifies application bundles.
+VERSION="2.0.0"
+export LC_ALL=en_US.UTF-8
+json=false quiet=false interactive=false share=false custom=false demo=false
+roots=() apps=() names=() sizes=() versions=() evidence=()
+count=0 total_kb=0 unknown_sizes=0 skipped=0
usage() {
- cat <&2
- usage >&2
- exit 1
- ;;
+ --path) [ "$#" -ge 2 ] && [ -d "$2" ] || { printf 'Expected an existing directory after --path\n' >&2; exit 2; }; custom=true; roots+=("$2"); shift 2 ;;
+ -u|--user) shift ;;
+ -j|--json) json=true; shift ;;
+ -q|--quiet) quiet=true; shift ;;
+ -i|--interactive) interactive=true; shift ;;
+ --share) share=true; shift ;;
+ --demo) demo=true; shift ;;
+ --no-color) NO_COLOR=1; shift ;;
+ -v|--version) printf '%s\n' "$VERSION"; exit 0 ;;
+ -h|--help) usage; exit 0 ;;
+ *) printf 'Unknown option: %s\n' "$1" >&2; exit 2 ;;
esac
done
-
-# Function to check if app is Electron
-is_electron() {
- local app_path="$1"
-
- # Most reliable signal: the Electron Framework bundled inside the app
- if [ -d "$app_path/Contents/Frameworks/Electron Framework.framework" ]; then
- return 0
- fi
-
- # Fallback: an "Electron" binary sitting in the MacOS folder
- if [ -f "$app_path/Contents/MacOS/Electron" ]; then
- return 0
- fi
-
- return 1
-}
-
-get_app_size() {
- du -sh "$1" 2>/dev/null | cut -f1
-}
-
-get_app_version() {
- defaults read "$1/Contents/Info.plist" CFBundleShortVersionString 2>/dev/null || echo "Unknown"
-}
-
-if [[ "$JSON_OUTPUT" != true && "$QUIET" != true ]]; then
- clear
- echo ""
- echo -e "${CYAN}╔════════════════════════════════════════════════════════════╗${NC}"
- echo -e "${CYAN}║${NC} ${ELECTRON} ${BOLD}${WHITE}Electron Detector for macOS${NC}${CYAN} ║${NC}"
- echo -e "${CYAN}║${NC} Find which apps are secretly Electron-based ${CYAN}║${NC}"
- echo -e "${CYAN}╚════════════════════════════════════════════════════════════╝${NC}"
- echo ""
+if $json && $share; then printf 'Choose either --json or --share.\n' >&2; exit 2; fi
+if $demo && { $json || $custom; }; then printf '--demo cannot be combined with --json or --path.\n' >&2; exit 2; fi
+if [ "$(uname -s)" != Darwin ]; then printf 'This detective needs macOS.\n' >&2; exit 2; fi
+if $interactive && { $json || $share || [ ! -t 0 ] || [ ! -t 1 ]; }; then
+ printf -- '--interactive requires a terminal and cannot be combined with --json or --share.\n' >&2; exit 2
fi
-
-apps=()
-while IFS= read -r -d '' app; do
- apps+=("$app")
-done < <(find "${SCAN_PATHS[@]}" -maxdepth 2 -name "*.app" -print0 2>/dev/null)
-
-total_app_count=${#apps[@]}
-
-if [ "$total_app_count" -eq 0 ]; then
- if [[ "$JSON_OUTPUT" == true ]]; then
- echo '{"error":"no applications found","scanned_paths":[]}'
- else
- echo -e "${RED}No applications found in: ${SCAN_PATHS[*]}${NC}"
- fi
- exit 1
+if ! $custom; then
+ roots=(/Applications)
+ [ ! -d "$HOME/Applications" ] || roots+=("$HOME/Applications")
fi
-
-if [[ "$JSON_OUTPUT" != true && "$QUIET" != true ]]; then
- echo -e "${SCAN} ${BOLD}Scanning ${SCAN_PATHS[*]}...${NC}"
- echo ""
+mint='' dim='' reset='' animate=false
+if [ -t 1 ] && [ "${TERM:-dumb}" != dumb ]; then animate=true; fi
+if [ -t 1 ] && [ -z "${NO_COLOR+x}" ] && [ "${TERM:-dumb}" != dumb ]; then
+ mint=$'\033[1;36m'; dim=$'\033[2m'; reset=$'\033[0m'
fi
-
-electron_apps=()
-electron_count=0
-current=0
-bar_length=50
-
-for app in "${apps[@]}"; do
- ((current++))
-
- if [[ "$JSON_OUTPUT" != true && "$QUIET" != true ]]; then
- percent=$((current * 100 / total_app_count))
- filled=$((current * bar_length / total_app_count))
- printf "\r${BLUE}["
- for ((i=0; i "$work/apps" 2> "$work/errors"
+if [ -s "$work/errors" ]; then
+ skipped=$(wc -l < "$work/errors" | tr -d ' ')
+ printf 'Some folders could not be read (%s traversal warnings). Results may be incomplete.\n' "$skipped" >&2
fi
-
-# ---------------------------------------------------------------------------
-# JSON output
-# ---------------------------------------------------------------------------
-if [[ "$JSON_OUTPUT" == true ]]; then
- printf '{"total_apps":%d,"electron_count":%d,"electron_apps":[' "$total_app_count" "$electron_count"
- first=true
- for app_info in "${electron_apps[@]}"; do
- IFS='|' read -r name size version <<< "$app_info"
- $first || printf ','
- first=false
- printf '{"name":"%s","size":"%s","version":"%s"}' \
- "${name//\"/\\\"}" "${size//\"/\\\"}" "${version//\"/\\\"}"
+while IFS= read -r -d '' app; do
+ canonical=$(cd "$app" 2>/dev/null && pwd -P) || { skipped=$((skipped+1)); continue; }
+ duplicate=false
+ for seen in "${apps[@]}"; do [ "$seen" != "$canonical" ] || { duplicate=true; break; }; done
+ $duplicate && continue
+ apps+=("$canonical")
+ if [ ! -r "$app/Contents" ] || [ ! -x "$app/Contents" ]; then skipped=$((skipped+1)); continue; fi
+ count=$((count+1))
+ if ! $json && ! $quiet && ! $share && $animate; then printf '\r Inspecting app %d…' "$count"; fi
+ signal=''
+ if [ -d "$app/Contents/Frameworks/Electron Framework.framework" ]; then signal='Electron Framework.framework';
+ elif [ -f "$app/Contents/MacOS/Electron" ]; then signal='Contents/MacOS/Electron'; fi
+ [ -n "$signal" ] || continue
+ name=${app##*/}; name=${name%.app}
+ version=$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$app/Contents/Info.plist" 2>/dev/null) || version='Unknown'
+ size=$(du -sk "$canonical" 2>/dev/null) && size=${size%%[[:space:]]*} || size=-1
+ case "$size" in ''|*[!0-9-]*) size=-1 ;; esac
+ if [ "$size" -lt 0 ]; then unknown_sizes=$((unknown_sizes+1)); else total_kb=$((total_kb+size)); fi
+ # Insertion order: largest bundles first. Parallel arrays preserve arbitrary names.
+ pos=${#names[@]}
+ while [ "$pos" -gt 0 ] && [ "${sizes[$((pos-1))]}" -lt "$size" ]; do
+ names[$pos]=${names[$((pos-1))]}; sizes[$pos]=${sizes[$((pos-1))]}
+ versions[$pos]=${versions[$((pos-1))]}; evidence[$pos]=${evidence[$((pos-1))]}
+ pos=$((pos-1))
done
- printf ']}\n'
- exit 0
+ names[$pos]=$name; sizes[$pos]=$size; versions[$pos]=$version; evidence[$pos]=$signal
+done < "$work/apps"
fi
-
-# ---------------------------------------------------------------------------
-# Human-readable report
-# ---------------------------------------------------------------------------
-echo -e "${CYAN}════════════════════════════════════════════════════════════${NC}"
-echo ""
-
-if [ "$electron_count" -eq 0 ]; then
- echo -e "${GREEN}${CHECK} ${BOLD}Congratulations!${NC}"
- echo -e "${GREEN}No Electron apps detected on your Mac!${NC}"
- echo ""
- echo -e "${NATIVE} Your Mac is running pure native apps! ${NATIVE}"
+found=${#names[@]}
+app_label=apps; [ "$found" -ne 1 ] || app_label=app
+percent=0; [ "$count" -eq 0 ] || percent=$((found*100/count))
+summary() {
+ if $demo; then printf 'DEMO / fictional results; nothing scanned.\n'; fi
+ printf 'My Mac app census: %d of %d inspected apps have Electron markers (%d%%).\n' "$found" "$count" "$percent"
+ printf 'Detected bundles: %s on disk' "$(pretty_size "$total_kb")"
+ [ "$unknown_sizes" -eq 0 ] || printf ' + %d unmeasured' "$unknown_sizes"
+ printf '. Not a RAM or battery benchmark.\n'
+ [ "$skipped" -eq 0 ] || printf 'Partial scan: %d warnings.\n' "$skipped"
+ printf 'No marker found does not prove an app is native.\n'
+ printf 'Try it: https://github.com/macnative/electron-detector\n'
+ if ! $quiet; then printf 'Explore native alternatives: https://macnative.io/categories\n'; fi
+}
+if $json; then
+ printf '{"schema_version":2,"detector_version":"%s","total_apps":%d,"electron_count":%d,"total_size_kb":%d,"unknown_sizes":%d,"scan_warnings":%d,"electron_apps":[' "$VERSION" "$count" "$found" "$total_kb" "$unknown_sizes" "$skipped"
+ for ((i=0;i '
+ IFS= read -r choice || break
+ case "$choice" in
+ s|S) printf '\n'; summary ;;
+ c|C) if summary | pbcopy; then printf ' Copied. App names and paths are left out.\n';
+ else printf ' Clipboard unavailable. Choose s to print the summary.\n'; fi ;;
+ e|E) printf '\n The clues: an Electron framework or executable in a bundle.\n'
+ printf ' A match means the app contains a runtime marker.\n'
+ printf ' No match does not establish which framework it uses.\n'
+ printf ' Sizes cover whole app bundles, excluding user data and caches.\n'
+ printf ' For RAM and CPU use, inspect running apps in Activity Monitor.\n' ;;
+ a|A) open 'https://macnative.io/categories' || printf ' Visit https://macnative.io/categories in your browser.\n' ;;
+ q|Q|'') break ;;
+ *) printf ' Choose s, c, e, a, or q (Enter finishes).\n' ;;
+ esac
+ done
fi
diff --git a/scripts/render-preview.py b/scripts/render-preview.py
new file mode 100644
index 0000000..5d6ff26
--- /dev/null
+++ b/scripts/render-preview.py
@@ -0,0 +1,28 @@
+"""Render the README's SVG from actual, explicitly fictional CLI output.
+Run on macOS with Python 3. No third-party dependencies.
+"""
+import html
+from pathlib import Path
+import subprocess
+
+root = Path(__file__).resolve().parents[1]
+output = subprocess.check_output(
+ ['/bin/bash', str(root / 'electron-detector.sh'), '--demo', '--no-color'], text=True
+)
+lines = output.strip('\n').splitlines()
+height = 80 + len(lines) * 24
+svg = [
+ f'')
+(root / '.github/preview.svg').write_text('\n'.join(svg))
diff --git a/tests/test_detector.py b/tests/test_detector.py
new file mode 100644
index 0000000..d29cbe1
--- /dev/null
+++ b/tests/test_detector.py
@@ -0,0 +1,204 @@
+"""Integration tests against real macOS utilities and the system Bash 3.2."""
+import json
+import os
+from pathlib import Path
+import plistlib
+import pty
+import select
+import time
+import subprocess
+import tempfile
+import unittest
+
+SCRIPT = Path(__file__).resolve().parents[1] / 'electron-detector.sh'
+
+class DetectorTests(unittest.TestCase):
+ def setUp(self):
+ self.tmp = tempfile.TemporaryDirectory()
+ self.root = Path(self.tmp.name)
+
+ def tearDown(self):
+ self.tmp.cleanup()
+
+ def app(self, name, signal='framework', version='1.2.3', folder=None, payload=0):
+ app = (folder or self.root) / (name + '.app')
+ (app / 'Contents').mkdir(parents=True)
+ with (app / 'Contents/Info.plist').open('wb') as f:
+ plistlib.dump({'CFBundleShortVersionString': version}, f)
+ if signal == 'framework':
+ (app / 'Contents/Frameworks/Electron Framework.framework').mkdir(parents=True)
+ if signal == 'binary':
+ (app / 'Contents/MacOS').mkdir()
+ (app / 'Contents/MacOS/Electron').write_text('fixture')
+ if payload:
+ (app / 'Contents/payload').write_bytes(os.urandom(payload))
+ return app
+
+ def run_script(self, *args):
+ return subprocess.run(['/bin/bash', str(SCRIPT), '--path', str(self.root), *args], text=True, capture_output=True, timeout=30)
+
+ def report(self, *args):
+ result = self.run_script('--json', *args)
+ self.assertEqual(result.returncode, 0, result.stderr)
+ return json.loads(result.stdout)
+
+ def test_signals_and_nested_helpers(self):
+ outer = self.app('Electron app')
+ self.app('Helper', folder=outer / 'Contents')
+ self.app('Binary app', 'binary', folder=self.root / 'Tools' / 'Editors')
+ self.app('Plain app', 'none')
+ data = self.report()
+ self.assertEqual(data['total_apps'], 3)
+ self.assertEqual(data['electron_count'], 2)
+ self.assertEqual({a['name'] for a in data['electron_apps']}, {'Electron app', 'Binary app'})
+
+ def test_json_escapes_unusual_names_and_versions(self):
+ name = 'Café "quoted" \\ path|tab\tline\nname'
+ version = '1"\\\t\n2'
+ self.app(name, version=version)
+ data = self.report()
+ self.assertEqual(data['electron_apps'][0]['name'], name)
+ self.assertEqual(data['electron_apps'][0]['version'], version)
+ self.assertNotIn(str(self.root), json.dumps(data))
+
+ def test_deduplicate_overlapping_paths_and_symlink(self):
+ app = self.app('One')
+ (self.root / 'Alias.app').symlink_to(app, target_is_directory=True)
+ self.assertEqual(self.report('--path', str(self.root))['total_apps'], 1)
+
+ def test_empty_is_valid_report(self):
+ data = self.report()
+ self.assertEqual(data['total_apps'], 0)
+ self.assertEqual(data['electron_apps'], [])
+
+ def test_no_false_native_claim(self):
+ self.app('Another framework', 'none')
+ result = self.run_script()
+ self.assertIn('does not prove an app is native', result.stdout)
+ self.assertEqual(self.report()['electron_count'], 0)
+
+ def test_sizes_are_sorted_and_totaled(self):
+ self.app('Small')
+ self.app('Large', payload=300000)
+ data = self.report()
+ self.assertEqual(data['electron_apps'][0]['name'], 'Large')
+ self.assertEqual(data['total_size_kb'], sum(a['size_kb'] for a in data['electron_apps']))
+
+ def test_share_omits_names_and_quiet_omits_promotion(self):
+ self.app('PrivateWorkApp')
+ shared = self.run_script('--share').stdout
+ self.assertNotIn('PrivateWorkApp', shared)
+ self.assertNotIn(str(self.root), shared)
+ self.assertIn('1 of 1', shared)
+ self.assertNotIn('macnative.io', self.run_script('--quiet').stdout)
+
+ def test_missing_version_and_terminal_escape_sanitizing(self):
+ app = self.app('Escape\x1b[31m')
+ (app / 'Contents/Info.plist').unlink()
+ data = self.report()
+ self.assertEqual(data['electron_apps'][0]['version'], 'Unknown')
+ self.assertNotIn('\x1b', self.run_script('--quiet').stdout)
+
+ def test_invalid_path_and_noninteractive_menu(self):
+ self.assertEqual(self.run_script('--path', '/does-not-exist').returncode, 2)
+ self.assertEqual(self.run_script('--interactive').returncode, 2)
+ self.assertEqual(self.run_script('--bogus').returncode, 2)
+
+
+ def test_demo_is_explicit_and_does_not_scan(self):
+ self.app('PrivateWorkApp')
+ result = subprocess.run(['/bin/bash', str(SCRIPT), '--demo'], text=True, capture_output=True)
+ self.assertEqual(result.returncode, 0)
+ self.assertIn('DEMO / fictional apps and sizes. Nothing scanned.', result.stdout)
+ self.assertIn('Example Editor', result.stdout)
+ self.assertNotIn('PrivateWorkApp', result.stdout)
+ shared = subprocess.check_output(['/bin/bash', str(SCRIPT), '--demo', '--share'], text=True)
+ self.assertTrue(shared.startswith('DEMO /'))
+
+ def test_conflicting_output_modes(self):
+ self.assertEqual(self.run_script('--json', '--share').returncode, 2)
+ self.assertEqual(self.run_script('--demo').returncode, 2) # --path supplied by helper
+ result = subprocess.run(['/bin/bash', str(SCRIPT), '--demo', '--json'], capture_output=True)
+ self.assertEqual(result.returncode, 2)
+ self.assertEqual(result.stdout, b'')
+
+ def test_relative_root_starting_with_dash(self):
+ folder = self.root / '-apps'
+ self.app('Example', folder=folder)
+ result = subprocess.run(['/bin/bash', str(SCRIPT), '--path', '-apps', '--json'],
+ cwd=self.root, capture_output=True, text=True)
+ self.assertEqual(result.returncode, 0, result.stderr)
+ self.assertEqual(json.loads(result.stdout)['electron_count'], 1)
+
+ def test_unknown_size_is_not_zero(self):
+ self.app('Unreadable size')
+ commands = self.root / 'bin'
+ commands.mkdir()
+ du = commands / 'du'
+ du.write_text('#!/bin/sh\nexit 1\n')
+ du.chmod(0o755)
+ result = subprocess.run(['/bin/bash', str(SCRIPT), '--path', str(self.root), '--json'],
+ env={**os.environ, 'PATH': str(commands) + ':' + os.environ['PATH']},
+ text=True, capture_output=True)
+ data = json.loads(result.stdout)
+ self.assertEqual(data['unknown_sizes'], 1)
+ self.assertIsNone(data['electron_apps'][0]['size_kb'])
+ self.assertEqual(data['total_size_kb'], 0)
+
+ def test_symlink_loop_does_not_duplicate_apps(self):
+ self.app('Readable')
+ (self.root / 'loop').symlink_to(self.root, target_is_directory=True)
+ data = self.report()
+ self.assertEqual(data['electron_count'], 1)
+ self.assertEqual(data['total_apps'], 1)
+
+ def test_interactive_menu_in_real_terminal(self):
+ commands = self.root / 'bin'
+ commands.mkdir()
+ for name, command in [('pbcopy', 'cat > "$DETECTOR_TEST_OUTPUT/clipboard"'),
+ ('open', 'printf "%s" "$1" > "$DETECTOR_TEST_OUTPUT/url"')]:
+ stub = commands / name
+ stub.write_text('#!/bin/sh\n' + command + '\n')
+ stub.chmod(0o755)
+ master, slave = pty.openpty()
+ process = subprocess.Popen(['/bin/bash', str(SCRIPT), '--demo', '--interactive', '--no-color'],
+ stdin=slave, stdout=slave, stderr=slave,
+ env={**os.environ, 'TERM': 'dumb', 'DETECTOR_TEST_OUTPUT': str(self.root),
+ 'PATH': str(commands) + ':' + os.environ['PATH']})
+ os.close(slave)
+ output = b''
+ sent = False
+ deadline = time.monotonic() + 10
+ try:
+ while time.monotonic() < deadline:
+ ready, _, _ = select.select([master], [], [], 0.1)
+ if ready:
+ try:
+ chunk = os.read(master, 65536)
+ except OSError:
+ break
+ if not chunk:
+ break
+ output += chunk
+ if b' > ' in output and not sent:
+ os.write(master, b'e\ns\nc\na\nq\n')
+ sent = True
+ elif process.poll() is not None:
+ break
+ process.wait(timeout=2)
+ text = output.decode()
+ self.assertEqual(process.returncode, 0, text)
+ self.assertIn('The clues:', text)
+ self.assertIn('My Mac app census: 3 of 42', text)
+ self.assertNotIn('\x1b', text)
+ self.assertIn('3 of 42', (self.root / 'clipboard').read_text())
+ self.assertNotIn('Example Editor', (self.root / 'clipboard').read_text())
+ self.assertEqual((self.root / 'url').read_text(), 'https://macnative.io/categories')
+ finally:
+ if process.poll() is None:
+ process.kill()
+ process.wait()
+ os.close(master)
+
+if __name__ == '__main__':
+ unittest.main()
From 5006cdde8f3574189313fe4961a60f316a9bdc90 Mon Sep 17 00:00:00 2001
From: Mohsen Karimi
Date: Tue, 20 Oct 2026 23:23:49 +0200
Subject: [PATCH 2/5] Restore the colorful table UI with facts, progress, and
disk insights
---
.github/preview.svg | 61 ++++++++++++---------
README.md | 23 ++++----
electron-detector.sh | 111 +++++++++++++++++++++++++++-----------
scripts/render-preview.py | 8 +--
tests/test_detector.py | 49 +++++++++++++++++
5 files changed, 183 insertions(+), 69 deletions(-)
diff --git a/.github/preview.svg b/.github/preview.svg
index 07c4338..33712e8 100644
--- a/.github/preview.svg
+++ b/.github/preview.svg
@@ -1,32 +1,41 @@
-
-
-# What's inside your Mac apps?
-
-A tiny shell script that finds Electron runtime markers in your installed apps. See a size-sorted table, inspect the evidence, and find out how much disk space those **whole app bundles** occupy.
-
-**One file. Runs with the Bash your Mac already has. No Homebrew, account, or admin password.**
-
-
-
-
-
-*Fictional apps, real output. Reproduce this preview with `bash electron-detector.sh --demo`.*
-
-## Try it
-
-**Preview build:** these commands download the updated script from the PR branch. The `main` branch still has v1 until this release is merged.
-
-Open **Terminal** (⌘ Space → Terminal), download the script, then inspect it in your editor:
-
-```sh
-curl -fL https://raw.githubusercontent.com/macnative/electron-detector/codex/friendly-app-census/electron-detector.sh -o electron-detector.sh
-```
-
-Read [the source](electron-detector.sh), then run:
-
-```sh
-bash electron-detector.sh --version # should print 2.1.0
-bash electron-detector.sh --interactive
-```
-
-The scan checks `/Applications` and `~/Applications`, including subfolders. Apps don't need to be running. At the end, you can print or copy an anonymous summary, learn what the findings mean, or open MacNative to explore alternatives.
-
-Prefer a plain report? Run `bash electron-detector.sh`. Curious about the look before scanning? Add `--demo --interactive`.
-
-Downloading needs internet. **Scanning works offline.** The script reads bundles and uses a temporary workspace that it removes on exit. It never edits, deletes, or launches your apps. The menu changes the clipboard only when you choose **c**, and opens a website only when you choose **a**. Don't use `sudo`.
-
-## The original look, with better answers
+Find Electron apps on your Mac in a colorful table, with sizes, versions, and a few useful facts.
-- **Useful at a glance.** A colorful table of app names, disk sizes, and versions, largest first. The interactive menu and JSON include detection evidence.
-- **A familiar terminal report.** A boxed title, scan progress bar, disk summary, and useful facts. Narrow terminals get a compact layout; long names stay intact.
-- **Something to share.** `--share` leaves out app names and folder paths. The interactive menu can copy that summary for you.
-- **Built for real folders.** Subfolders, spaces, Unicode, symlinked apps, and overlapping scan roots; nested helper apps aren't counted again.
-- **Comfortable in a terminal or a pipeline.** JSON output, quiet mode, `NO_COLOR`, and no surprise prompts.
+**Free & open source. No Homebrew, account, or admin password.**
-No purity score. No invented RAM savings. Keep the apps you like.
+## Copy, paste, scan
-## Share your census
+Open **Terminal** (⌘ Space → Terminal) and paste this whole block:
```sh
-bash electron-detector.sh --share
+curl -fL https://raw.githubusercontent.com/macnative/electron-detector/codex/friendly-app-census/electron-detector.sh -o electron-detector.sh &&
+ bash electron-detector.sh --interactive
```
-```text
-My Mac app census: 3 of 42 inspected apps have Electron markers (7%).
-Detected bundles: 1.5 GiB on disk. Not a RAM or battery benchmark.
-No marker found does not prove an app is native.
-Try it: https://github.com/macnative/electron-detector
-Explore native alternatives: https://macnative.io/categories
-```
+This downloads and runs the script. [Read the source first](electron-detector.sh) if you prefer. The command currently uses the **v2.1.0 preview branch**; `main` still has v1.
-*Example numbers.* App names and paths are omitted, but your counts and sizes are still in the summary. Nothing is posted automatically. Full reports and screenshots include app names; review those before sharing.
+It scans `/Applications` and `~/Applications`, then lets you **copy a summary**, **inspect detection evidence**, or **browse native alternatives**. Scanning works offline and never changes your apps.
-## Curious about alternatives?
+
+
+
-I also make **[MacNative](https://macnative.io)**, a directory for discovering Mac apps. If a result makes you curious, [browse by category](https://macnative.io/categories) and look for a native app that fits how you work.
+*Fictional apps and sizes. Preview it yourself with `bash electron-detector.sh --demo`.*
-That's the connection: this tool shows you what's installed; MacNative helps you explore what else is out there. It doesn't guarantee a replacement for every match. There are no network requests during a scan and no per-app referral links. Use `--quiet` to omit the banner and MacNative promotion.
+## A few handy options
-## Make it yours
+After the first download, run `bash electron-detector.sh` with any of these:
-| Option | What it does |
+| Option | What you get |
| --- | --- |
-| `-i`, `--interactive` | Offers share, clipboard, explanation, and alternatives actions after scanning |
-| `--path DIRECTORY` | Scans a custom folder; repeat for multiple folders; replaces the defaults |
-| `-j`, `--json` | Emits structured data, with app names but no personal folder paths |
-| `--share` | Prints a summary without app names or paths |
-| `--demo` | Shows fictional results without scanning; works with the interactive menu |
-| `-q`, `--quiet` | Omits the banner, tips, and MacNative promotion |
-| `--no-color` | Disables colors; `NO_COLOR` is also respected |
-| `-u`, `--user` | Compatibility flag; user apps are now included by default |
-| `-v`, `--version` | Prints the version |
-| `-h`, `--help` | Shows help |
-
-```sh
-# Scan another app folder
-bash electron-detector.sh --path '/Volumes/Work/Applications'
-
-# Save a report for your own tools
-bash electron-detector.sh --json > electron-report.json
-
-# Just the findings
-bash electron-detector.sh --quiet --no-color
-```
-
-`--interactive` needs terminal input and output. Use it after downloading, rather than piping the script into Bash. `--json` and `--share` are mutually exclusive; `--demo` cannot be combined with `--json` or `--path`.
-
-## Questions that come up
-
-**Does this tell me which apps are running right now?**
-No. It inspects installed bundles. Use Activity Monitor for running processes, CPU, and memory.
-
-**Is the disk total space I can save?**
-No. It's the measured size of the detected bundles, including the apps themselves. Replacement apps also use space, and user data is outside this measurement.
+| `--interactive` | Sharing, clipboard, evidence, and alternatives menu |
+| `--share` | A summary without app names or paths |
+| `--json` | A structured report for your own tools |
+| `--path '/your/folder'` | Scan a different app folder |
+| `--quiet` | Results without the banner, facts, or promotion |
+| `--demo` | Try the UI without scanning |
-**It missed an app.**
-Try `--path` if it's outside the default folders. Renamed or relocated Electron internals can be missed. [Open an issue](https://github.com/macnative/electron-detector/issues) with the app version and expected marker; please leave personal paths out.
+Run `bash electron-detector.sh --help` for all options.
-**I see a partial-scan warning.**
-Some folders or bundles couldn't be read. The results describe what was accessible. Try a smaller folder you can read; no admin access is required.
+## What the results mean
-**Can I remove it afterward?**
-Yes. Delete the downloaded script and any report files you saved. There's no installer, background service, or auto-updater.
+The script looks for Electron's framework or executable inside each app. Renamed internals can be missed; **no match doesn't prove an app is native**.
-## What a detection means
+Sizes are **whole app bundles on disk**, excluding user data and caches—not RAM use or estimated savings. Nothing is uploaded or posted. Full reports include app names; `--share` leaves them out.
-The detector checks for either:
+## Find something more Mac-like
-- `Contents/Frameworks/Electron Framework.framework`
-- `Contents/MacOS/Electron`
+Made by **[MacNative](https://macnative.io)**. Curious about a different app? [Browse native alternatives by category →](https://macnative.io/categories)
-These are runtime markers, not a benchmark or an executable signature check. Renamed or relocated frameworks can be missed, and a coincidentally named file can be a false positive. A bundle may include Electron for only part of its functionality. Other web wrappers are outside this tool's scope. **No Electron marker does not mean native.**
-
-Sizes are allocated bundle disk usage from `du -sk`, in KiB (displayed as MiB/GiB). They exclude user data and caches. APFS cloning, compression, and shared files can complicate totals. The report does not estimate RAM, battery life, speed, or space you would save by switching. Apps changing during a scan may yield partial measurements. Inaccessible folders are reported as warnings; an empty folder yields a valid zero-app report. Symlink cycles may also cause traversal warnings.
-
-[Electron embeds Chromium and Node.js](https://www.electronjs.org/docs/latest/) to support cross-platform desktop applications. This is the basis of the report’s “Did you know?” fact. Keep apps that work for you. If you want software with a more native Mac feel, [browse MacNative by category](https://macnative.io/categories). A native app is not necessarily a feature-for-feature replacement.
-
-## JSON schema v2
-
-```json
-{
- "schema_version": 2,
- "detector_version": "2.1.0",
- "total_apps": 1,
- "electron_count": 1,
- "total_size_kb": 409600,
- "unknown_sizes": 0,
- "scan_warnings": 0,
- "electron_apps": [
- { "name": "Example", "size_kb": 409600, "version": "1.0", "evidence": "Electron Framework.framework" }
- ]
-}
-```
-
-`size_kb` is `null` when measurement fails. `total_size_kb` sums known sizes only. `total_apps` counts bundles with accessible Contents directories. `scan_warnings` counts traversal diagnostics and inaccessible bundles; it is not a count of missed apps. Version 2 replaces v1's string `size` with numeric `size_kb` and includes the user Applications folder by default. Errors go to stderr; JSON stdout stays machine-readable. The demo cannot export JSON, so fictional data cannot be mistaken for a real scan. Exit 0 means the scan completed (inspect warnings); 2 means invalid arguments or an unsupported platform; 1 means temporary workspace creation failed.
-
-## Contributing & tests
-
-Use macOS and `/bin/bash`, not just a newer Homebrew Bash. Runtime dependencies must ship with macOS. Dev tests use Python 3; ShellCheck is optional locally and runs in CI.
-
-```sh
-/bin/bash -n electron-detector.sh
-python3 tests/test_detector.py
-shellcheck electron-detector.sh
-```
+Keep the apps you love. Try something new where it makes sense.
-New detection rules need fixtures for both true and false positives. Don't add telemetry or unmeasured performance claims. Please remove personal information before attaching reports to issues.
+---
-[MIT license](LICENSE).
+[How detection works & JSON reference](docs/reference.md) · [Contributing](CONTRIBUTING.md) · [Report a bug](https://github.com/macnative/electron-detector/issues) · [MIT license](LICENSE)
diff --git a/docs/reference.md b/docs/reference.md
new file mode 100644
index 0000000..5b6232a
--- /dev/null
+++ b/docs/reference.md
@@ -0,0 +1,94 @@
+# Technical reference
+
+[Back to the README](../README.md)
+
+## Make it yours
+
+| Option | What it does |
+| --- | --- |
+| `-i`, `--interactive` | Offers share, clipboard, explanation, and alternatives actions after scanning |
+| `--path DIRECTORY` | Scans a custom folder; repeat for multiple folders; replaces the defaults |
+| `-j`, `--json` | Emits structured data, with app names but no personal folder paths |
+| `--share` | Prints a summary without app names or paths |
+| `--demo` | Shows fictional results without scanning; works with the interactive menu |
+| `-q`, `--quiet` | Omits the banner, tips, and MacNative promotion |
+| `--no-color` | Disables colors; `NO_COLOR` is also respected |
+| `-u`, `--user` | Compatibility flag; user apps are now included by default |
+| `-v`, `--version` | Prints the version |
+| `-h`, `--help` | Shows help |
+
+```sh
+# Scan another app folder
+bash electron-detector.sh --path '/Volumes/Work/Applications'
+
+# Save a report for your own tools
+bash electron-detector.sh --json > electron-report.json
+
+# Just the findings
+bash electron-detector.sh --quiet --no-color
+```
+
+`--interactive` needs terminal input and output. Use it after downloading, rather than piping the script into Bash. `--json` and `--share` are mutually exclusive; `--demo` cannot be combined with `--json` or `--path`.
+
+## Questions that come up
+
+**Does this tell me which apps are running right now?**
+No. It inspects installed bundles. Use Activity Monitor for running processes, CPU, and memory.
+
+**Is the disk total space I can save?**
+No. It's the measured size of the detected bundles, including the apps themselves. Replacement apps also use space, and user data is outside this measurement.
+
+**It missed an app.**
+Try `--path` if it's outside the default folders. Renamed or relocated Electron internals can be missed. [Open an issue](https://github.com/macnative/electron-detector/issues) with the app version and expected marker; please leave personal paths out.
+
+**I see a partial-scan warning.**
+Some folders or bundles couldn't be read. The results describe what was accessible. Try a smaller folder you can read; no admin access is required.
+
+**Can I remove it afterward?**
+Yes. Delete the downloaded script and any report files you saved. There's no installer, background service, or auto-updater.
+
+## What a detection means
+
+The detector checks for either:
+
+- `Contents/Frameworks/Electron Framework.framework`
+- `Contents/MacOS/Electron`
+
+These are runtime markers, not a benchmark or an executable signature check. Renamed or relocated frameworks can be missed, and a coincidentally named file can be a false positive. A bundle may include Electron for only part of its functionality. Other web wrappers are outside this tool's scope. **No Electron marker does not mean native.**
+
+Sizes are allocated bundle disk usage from `du -sk`, in KiB (displayed as MiB/GiB). They exclude user data and caches. APFS cloning, compression, and shared files can complicate totals. The report does not estimate RAM, battery life, speed, or space you would save by switching. Apps changing during a scan may yield partial measurements. Inaccessible folders are reported as warnings; an empty folder yields a valid zero-app report. Symlink cycles may also cause traversal warnings.
+
+[Electron embeds Chromium and Node.js](https://www.electronjs.org/docs/latest/) to support cross-platform desktop applications. This is the basis of the report’s “Did you know?” fact. Keep apps that work for you. If you want software with a more native Mac feel, [browse MacNative by category](https://macnative.io/categories). A native app is not necessarily a feature-for-feature replacement.
+
+## JSON schema v2
+
+```json
+{
+ "schema_version": 2,
+ "detector_version": "2.1.0",
+ "total_apps": 1,
+ "electron_count": 1,
+ "total_size_kb": 409600,
+ "unknown_sizes": 0,
+ "scan_warnings": 0,
+ "electron_apps": [
+ { "name": "Example", "size_kb": 409600, "version": "1.0", "evidence": "Electron Framework.framework" }
+ ]
+}
+```
+
+`size_kb` is `null` when measurement fails. `total_size_kb` sums known sizes only. `total_apps` counts bundles with accessible Contents directories. `scan_warnings` counts traversal diagnostics and inaccessible bundles; it is not a count of missed apps. Version 2 replaces v1's string `size` with numeric `size_kb` and includes the user Applications folder by default. Errors go to stderr; JSON stdout stays machine-readable. The demo cannot export JSON, so fictional data cannot be mistaken for a real scan. Exit 0 means the scan completed (inspect warnings); 2 means invalid arguments or an unsupported platform; 1 means temporary workspace creation failed.
+
+## Contributing & tests
+
+Use macOS and `/bin/bash`, not just a newer Homebrew Bash. Runtime dependencies must ship with macOS. Dev tests use Python 3; ShellCheck is optional locally and runs in CI.
+
+```sh
+/bin/bash -n electron-detector.sh
+python3 tests/test_detector.py
+shellcheck electron-detector.sh
+```
+
+New detection rules need fixtures for both true and false positives. Don't add telemetry or unmeasured performance claims. Please remove personal information before attaching reports to issues.
+
+[MIT license](../LICENSE).
From 48fabd13907d29c49ac09ecf48d41ac5c163c6da Mon Sep 17 00:00:00 2001
From: Mohsen Karimi
Date: Tue, 20 Oct 2026 23:29:11 +0200
Subject: [PATCH 5/5] Point release instructions to main
---
README.md | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index de99677..e6fd7fc 100644
--- a/README.md
+++ b/README.md
@@ -11,11 +11,11 @@ Find Electron apps on your Mac in a colorful table, with sizes, versions, and a
Open **Terminal** (⌘ Space → Terminal) and paste this whole block:
```sh
-curl -fL https://raw.githubusercontent.com/macnative/electron-detector/codex/friendly-app-census/electron-detector.sh -o electron-detector.sh &&
+curl -fL https://raw.githubusercontent.com/macnative/electron-detector/main/electron-detector.sh -o electron-detector.sh &&
bash electron-detector.sh --interactive
```
-This downloads and runs the script. [Read the source first](electron-detector.sh) if you prefer. The command currently uses the **v2.1.0 preview branch**; `main` still has v1.
+This downloads and runs the script. [Read the source first](electron-detector.sh) if you prefer.
It scans `/Applications` and `~/Applications`, then lets you **copy a summary**, **inspect detection evidence**, or **browse native alternatives**. Scanning works offline and never changes your apps.