From 00b5ba8190de75c5ca7f611c480fbadb45529329 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alejandro=20Guti=C3=A9rrez?= <35082514+alezmad@users.noreply.github.com> Date: Sun, 5 Apr 2026 23:37:39 +0100 Subject: [PATCH] feat(web): /install shell script + real curl one-liner on landing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Landing page showed \`curl -fsSL claudemesh.sh/install | bash\` but the domain didn't resolve, so anyone copy-pasting got a DNS error. Ship: - apps/web/src/app/install/route.ts: GET returns an auditable bash installer (Node preflight, npm install -g claudemesh-cli, runs claudemesh install, prints next steps, colored output). No Node auto-install — fails clean if missing with a pointer. - apps/web/src/proxy.ts: exclude /install from the i18n matcher so Next.js returns the shell script unmangled. - hero.tsx + features.tsx: swap claudemesh.sh → claudemesh.com. Test: curl http://localhost:3000/install | bash -n → OK. Content-Type: text/x-shellscript; charset=utf-8. Co-Authored-By: Claude Opus 4.6 (1M context) --- apps/web/src/app/install/route.ts | 100 ++++++++++++++++++ .../src/modules/marketing/home/features.tsx | 2 +- apps/web/src/modules/marketing/home/hero.tsx | 2 +- apps/web/src/proxy.ts | 2 +- 4 files changed, 103 insertions(+), 3 deletions(-) create mode 100644 apps/web/src/app/install/route.ts diff --git a/apps/web/src/app/install/route.ts b/apps/web/src/app/install/route.ts new file mode 100644 index 0000000..57721d5 --- /dev/null +++ b/apps/web/src/app/install/route.ts @@ -0,0 +1,100 @@ +/** + * GET /install — serves a shell installer for claudemesh-cli. + * + * Intended to be piped into bash: + * curl -fsSL https://claudemesh.com/install | bash + * + * The script is kept short + auditable. It does not try to install + * Node for the user — it checks for a compatible Node + npm and + * directs them to install Node themselves if missing. Running `bash` + * against a domain you do not fully trust is always a risk; publishing + * the script this way (rather than obfuscating it behind a binary + * blob) lets security-conscious users inspect before executing. + */ + +const SCRIPT = `#!/usr/bin/env bash +# claudemesh-cli installer +# Source: https://claudemesh.com/install +# Audit: curl -fsSL https://claudemesh.com/install | less +set -euo pipefail + +RED=$'\\033[31m'; GREEN=$'\\033[32m'; DIM=$'\\033[2m'; BOLD=$'\\033[1m'; RESET=$'\\033[0m' + +say() { printf "%s\\n" "$*"; } +ok() { printf "%s✓%s %s\\n" "\${GREEN}" "\${RESET}" "$*"; } +err() { printf "%s✗%s %s\\n" "\${RED}" "\${RESET}" "$*" >&2; } + +say "" +say "\${BOLD}claudemesh-cli installer\${RESET}" +say "$(printf '%.0s─' {1..40})" + +# --- preflight ------------------------------------------------------ + +if ! command -v node >/dev/null 2>&1; then + err "Node.js is not installed." + say " Install Node.js 20 or newer: \${BOLD}https://nodejs.org\${RESET}" + say " Or via nvm: \${DIM}curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash\${RESET}" + exit 1 +fi + +NODE_MAJOR=$(node -p "process.versions.node.split('.')[0]") +if [ "$NODE_MAJOR" -lt 20 ]; then + err "Node.js $(node -v) is too old — claudemesh-cli needs >= 20." + say " Upgrade: \${BOLD}https://nodejs.org\${RESET}" + exit 1 +fi +ok "Node.js $(node -v)" + +if ! command -v npm >/dev/null 2>&1; then + err "npm is not installed (usually ships with Node)." + exit 1 +fi +ok "npm $(npm -v)" + +# --- install -------------------------------------------------------- + +say "" +say "Installing \${BOLD}claudemesh-cli\${RESET} from npm…" +if ! npm install -g claudemesh-cli; then + err "npm install failed." + say " If this is a permissions error on macOS/Linux, try:" + say " \${DIM}sudo npm install -g claudemesh-cli\${RESET}" + say " or configure npm to use a user-owned prefix:" + say " \${DIM}https://docs.npmjs.com/resolving-eacces-permissions-errors\${RESET}" + exit 1 +fi +ok "claudemesh-cli installed ($(claudemesh --version))" + +# --- register MCP + hooks ------------------------------------------ + +say "" +say "Registering Claude Code MCP server + status hooks…" +if ! claudemesh install; then + err "claudemesh install failed — run it manually to see the error." + exit 1 +fi + +# --- done ----------------------------------------------------------- + +say "" +say "\${GREEN}\${BOLD}Done.\${RESET}" +say "" +say "Next steps:" +say " 1. Restart Claude Code so the MCP tools appear." +say " 2. Join a mesh: \${BOLD}claudemesh join \${RESET}" +say " 3. Launch with push: \${BOLD}claudemesh launch\${RESET}" +say "" +say "Need an invite? Visit \${BOLD}https://claudemesh.com\${RESET}" +say "" +`; + +export function GET(): Response { + return new Response(SCRIPT, { + status: 200, + headers: { + "Content-Type": "text/x-shellscript; charset=utf-8", + "Cache-Control": "public, max-age=300, s-maxage=600", + "X-Content-Type-Options": "nosniff", + }, + }); +} diff --git a/apps/web/src/modules/marketing/home/features.tsx b/apps/web/src/modules/marketing/home/features.tsx index d908aee..78ca656 100644 --- a/apps/web/src/modules/marketing/home/features.tsx +++ b/apps/web/src/modules/marketing/home/features.tsx @@ -45,7 +45,7 @@ export const Features = () => { style={{ fontFamily: "var(--cm-font-mono)" }} > $ - curl -fsSL claudemesh.sh/install | bash + curl -fsSL claudemesh.com/install | bash