Something went wrong. Try again.
A mental bridge between a hacker and their LLM(s) of choice.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189#!/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 ;; esacdone
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 distnpx 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 ;; esacdone
# 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" ;; esacdoneif [ -n "$folder" ] && [ ! -d "$folder" ]; then echo "noid: not a folder: $folder" >&2 exit 1filog="${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 </dev/nullexit 0EOFsed "s|@PREFIX@|$prefix|g" "$stage/noid.tpl" > "$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" "$@"EOFsed "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" <<EOF[Desktop Entry]Type=ApplicationName=noidComment=A board of your files and the connections between themExec=$prefix/bin/noid --welcomeTerminal=falseCategories=Office;Utility;EOF
# 4. Swap the new version in. Running processes keep their open files.rm -rf "$share/noid.old"if [ -e "$share/noid" ]; then mv "$share/noid" "$share/noid.old"fimv "$share/noid.new" "$share/noid"rm -rf "$share/noid.old"
# 5. Register the MCP server with Claude Code (user scope), only on request.mcp_cmd="claude mcp add noid --scope user -- $prefix/bin/noid-mcp --agent claude"if [ "$register" -eq 1 ]; then if command -v claude >/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" fifi
# 6. Summary.echoecho "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"ficase ":$PATH:" in *":$prefix/bin:"*) ;; *) echo "note: $prefix/bin is not on your PATH" ;;esac