#!/bin/sh # Installs noid for this user: a release build, a private copy of Electron, # the `noid` and `noid-mcp` launchers and a desktop entry. # # sh scripts/install-local.sh [--prefix DIR] [--register-mcp] # # DIR defaults to $HOME/.local. Linux only. Re-running swaps in the new # version; a failed build leaves any existing install untouched. set -eu repo=$(cd "$(dirname "$0")/.." && pwd) prefix="$HOME/.local" register=0 while [ $# -gt 0 ]; do case "$1" in --prefix) [ $# -ge 2 ] || { echo "install-local: --prefix needs a directory" >&2; exit 2; } prefix="$2" shift 2 ;; --register-mcp) register=1 shift ;; *) echo "install-local: unknown argument: $1" >&2 echo "usage: sh scripts/install-local.sh [--prefix DIR] [--register-mcp]" >&2 exit 2 ;; esac done mkdir -p "$prefix" prefix=$(cd "$prefix" && pwd) share="$prefix/share" stage="$share/noid.new" cd "$repo" # 1. Release build. Nothing under $prefix is touched until it has succeeded. echo "install-local: release build" rm -rf dist npx shadow-cljs release main renderer mcp # 2. Stage the app and the private Electron. echo "install-local: staging in $stage" rm -rf "$stage" mkdir -p "$stage/app" "$stage/electron" cp -R dist/app/. "$stage/app/" cp index.html preload.js styles.css "$stage/app/" printf '{\n "name": "noid",\n "version": "0.1.0",\n "main": "main.js",\n "private": true\n}\n' > "$stage/app/package.json" # chokidar and everything it needs at runtime: the closure of `dependencies`, # worked out from the package.json files already in node_modules. closure=$(node -e ' const fs = require("fs"), path = require("path"); const seen = new Set(); const visit = (name) => { if (seen.has(name)) return; seen.add(name); const pkg = JSON.parse(fs.readFileSync(path.join("node_modules", name, "package.json"), "utf8")); Object.keys(pkg.dependencies || {}).forEach(visit); }; visit("chokidar"); console.log([...seen].join("\n")); ') for pkg in $closure; do mkdir -p "$stage/app/node_modules/$(dirname "$pkg")" cp -R "node_modules/$pkg" "$stage/app/node_modules/$pkg" done cp -R node_modules/electron/dist/. "$stage/electron/" # 3. Launchers, with the final absolute paths baked in. mkdir -p "$prefix/bin" "$share/applications" cat > "$stage/noid.tpl" <<'EOF' #!/bin/sh # noid: open a folder (default: the current one) in the noid app, and return. usage() { cat <<'USAGE' Usage: noid [--welcome] [folder] Opens a folder as a noid board. With no folder, opens the current one. If noid is already running, the folder opens in that window instead. Options: --welcome Start at the welcome screen instead of opening a folder. -h, --help Show this help and exit. See also: noid-mcp --help The MCP server your agents use to work with noid. USAGE } # Help wins over everything else, wherever it appears. for arg in "$@"; do case "$arg" in -h|--help) usage; exit 0 ;; esac done # Check every argument before launching anything. folder="" for arg in "$@"; do case "$arg" in --welcome) ;; -*) echo "noid: unknown option: $arg" >&2 echo "Try 'noid --help'." >&2 exit 2 ;; *) if [ -n "$folder" ]; then echo "noid: expected at most one folder" >&2 echo "Try 'noid --help'." >&2 exit 2 fi folder="$arg" ;; esac done if [ -n "$folder" ] && [ ! -d "$folder" ]; then echo "noid: not a folder: $folder" >&2 exit 1 fi log="${XDG_STATE_HOME:-$HOME/.local/state}/noid/noid.log" mkdir -p "$(dirname "$log")" setsid -f "@PREFIX@/share/noid/electron/electron" "@PREFIX@/share/noid/app" "$@" >>"$log" 2>&1 "$prefix/bin/noid" rm -f "$stage/noid.tpl" cat > "$stage/noid-mcp.tpl" <<'EOF' #!/bin/sh # noid-mcp: the noid MCP server over stdio, run from the installed copy. ELECTRON_RUN_AS_NODE=1 exec "@PREFIX@/share/noid/electron/electron" "@PREFIX@/share/noid/app/mcp.js" "$@" EOF sed "s|@PREFIX@|$prefix|g" "$stage/noid-mcp.tpl" > "$prefix/bin/noid-mcp" rm -f "$stage/noid-mcp.tpl" chmod 755 "$prefix/bin/noid" "$prefix/bin/noid-mcp" cat > "$share/applications/noid.desktop" </dev/null 2>&1; then claude mcp remove noid --scope user >/dev/null 2>&1 || true claude mcp add noid --scope user -- "$prefix/bin/noid-mcp" --agent claude echo "install-local: registered noid-mcp with Claude Code (user scope)" else echo "install-local: claude is not on PATH; register noid-mcp yourself:" echo " $mcp_cmd" fi fi # 6. Summary. echo echo "noid installed:" echo " app $share/noid" echo " launchers $prefix/bin/noid $prefix/bin/noid-mcp" echo " desktop $share/applications/noid.desktop" if [ "$register" -ne 1 ]; then echo " to register the MCP server: $mcp_cmd" fi case ":$PATH:" in *":$prefix/bin:"*) ;; *) echo "note: $prefix/bin is not on your PATH" ;; esac