#!/bin/sh # Cogent installer — Git-free, no Xcode. https://cogent.tools/install.sh # ONE command for BOTH agents: sets up Claude Code (plugin) AND/OR Codex (cogent-codex), # whichever CLIs are present. Everything installs into your HOME — never needs root. set -eu say() { printf '\033[1;36m[cogent]\033[0m %s\n' "$1"; } # Put a dir on PATH now + persist it to the shell profile (idempotent, no root). # # 🔴 THREE WAYS THE OLD VERSION LIED. It looped over .zshrc/.bashrc/.profile and returned at # the FIRST one that existed: # 1. On a fresh macOS account NONE of them exist (macOS ships no default rc; zsh reads # /etc/zshrc). Every test failed, the loop ended, and the function returned 0 having # written NOTHING. The in-process export still made `command -v cogent-codex` succeed # INSIDE the installer, so it printed "Codex: start with cogent-codex" and "Done." — then # the user opened a new terminal and got `command not found`. Completely silent. # 2. First-match-wins ignored $SHELL: a zsh user who happens to have a ~/.bashrc (nvm, conda, # pyenv and rustup all create one) got the line written where zsh never reads it, plus a # confident message naming the wrong file. # 3. The append was unguarded under `set -e`, so a read-only ~/.zshrc (nix home-manager, # chezmoi, stow, or a root-owned dotfile) aborted the ENTIRE installer at step 1 — before # either agent branch ran. # Now: write to EVERY rc that exists (the grep guard keeps that idempotent), create the login # shell's rc when none exist, never die on an unwritable file, and if nothing could be # persisted, SAY SO with the exact line to add by hand. add_to_path() { case ":$PATH:" in *":$1:"*) return 0 ;; esac PATH="$1:$PATH"; export PATH _line="export PATH=\"$1:\$PATH\"" _wrote=0 # The rc the user's LOGIN SHELL actually reads. This is targeted UNCONDITIONALLY — # creating it if absent — because "some rc file got the line" is not the property # that matters. A zsh user with a ~/.bashrc (nvm/conda/pyenv/rustup all create one) # would otherwise have PATH written only where zsh never looks. case "${SHELL:-}" in *zsh) _login_rc="$HOME/.zshrc" ;; *bash) _login_rc="$HOME/.bashrc" ;; *) _login_rc="$HOME/.profile" ;; esac # Every rc that already exists, PLUS the login-shell rc. Duplicates are harmless: # the grep guard below makes each write idempotent. for rc in "$HOME/.zshrc" "$HOME/.bashrc" "$HOME/.profile" "$_login_rc"; do # Skip files that don't exist UNLESS this is the login rc, which we create. if [ ! -f "$rc" ] && [ "$rc" != "$_login_rc" ]; then continue; fi if grep -qsF "$1" "$rc" 2>/dev/null; then _wrote=1; continue; fi if printf '\n%s\n' "$_line" >> "$rc" 2>/dev/null; then _wrote=1; say "Added $1 to PATH in $rc" else say "WARNING: $rc is not writable — skipping it" fi done if [ "$_wrote" -eq 1 ]; then say "Open a new terminal (or run: exec \$SHELL -l) to pick up the new PATH" else say "WARNING: could not persist PATH. Add this line to your shell profile by hand:" say " $_line" fi return 0 } # 1) Node (from nodejs.org prebuilt — NOT Homebrew, which needs the Xcode CLT) if ! command -v node >/dev/null 2>&1 || ! command -v npm >/dev/null 2>&1; then say "Node not found — installing prebuilt Node v22.14.0 (no Xcode needed)" os=$(uname -s); arch=$(uname -m) case "$os" in Darwin) plat=darwin;; Linux) plat=linux;; *) say "Unsupported OS: $os — install Node from https://nodejs.org and re-run"; exit 1;; esac case "$arch" in arm64|aarch64) narch=arm64;; x86_64|amd64) narch=x64;; *) say "Unsupported arch: $arch — install Node from https://nodejs.org and re-run"; exit 1;; esac dir="$HOME/.cogent/node" mkdir -p "$dir" url="https://nodejs.org/dist/v22.14.0/node-v22.14.0-${plat}-${narch}.tar.gz" say "Downloading $url" curl -fsSL "$url" | tar -xz -C "$dir" --strip-components=1 add_to_path "$dir/bin" || true fi # 2) Need at least one supported agent CLI (Claude Code or Codex) have_claude=0; command -v claude >/dev/null 2>&1 && have_claude=1 have_codex=0; command -v codex >/dev/null 2>&1 && have_codex=1 if [ "$have_claude" -eq 0 ] && [ "$have_codex" -eq 0 ]; then say "No agent CLI found. Install Claude Code (https://claude.com/claude-code) or Codex, then re-run." exit 1 fi # 3) Claude Code — Git-free plugin (HTTPS marketplace + npm plugin source) # # 🔴 EVERY step below is failure-TOLERANT on purpose. This script runs under `set -e`, so a # single non-zero exit used to abort the WHOLE installer — and because Claude Code is set up # BEFORE Codex, a Claude-side failure silently skipped the Codex branch entirely. A user who # ran this to get `cogent-codex` got no Codex install and no explanation (2026-09-01). # One agent's problem must never deny the user the other agent. claude_ok=0 if [ "$have_claude" -eq 1 ]; then say "Adding the Cogent marketplace (no Git)" # Idempotent: a marketplace named "cogent" may ALREADY be declared from a different source # — notably the older GitHub Git URL we used to document. Claude Code refuses to redeclare # a name whose source differs ("its network source differs from the one declared for it in # settings"), which is a hard wall for anyone who ever followed the old instructions. # Replace the declaration instead of dying on it. mkt_ok=1 if ! claude plugin marketplace add https://cogent.tools/marketplace.json 2>/dev/null; then say "A 'cogent' marketplace already exists from another source — replacing it" claude plugin marketplace remove cogent >/dev/null 2>&1 || true if ! claude plugin marketplace add https://cogent.tools/marketplace.json; then mkt_ok=0 say "WARNING: could not add the Cogent marketplace for Claude Code — skipping Claude setup" fi fi if [ "$mkt_ok" -eq 1 ]; then say "Installing the Cogent plugin for Claude Code" if claude plugin install cogent@cogent; then claude_ok=1 else say "WARNING: 'claude plugin install cogent@cogent' failed — continuing with Codex setup" fi fi fi # 4) Codex — install the bridge into your HOME (never root); its postinstall wires up # Codex's managed build + the cogent plugin and provides the 'cogent-codex' launcher. # --prefix into HOME is what dodges the root-owned global-prefix EACCES on system Node. codex_ok=0 if [ "$have_codex" -eq 1 ]; then say "Installing the Cogent bridge for Codex (cogent-codex)" prefix="$HOME/.cogent/npm" mkdir -p "$prefix" # npm 11+ BLOCKS lifecycle scripts by default. Our postinstall provisions codex's managed # build and the codex plugin, so a blocked run leaves a half-configured install with only a # warning. Opt in explicitly when the flag exists; npm 10 and older do not know it, so fall # back rather than fail the whole install. if npm install -g @essentialai/cogent-bridge --prefix "$prefix" --allow-scripts=@essentialai/cogent-bridge 2>/dev/null; then codex_ok=1 elif npm install -g @essentialai/cogent-bridge --prefix "$prefix"; then codex_ok=1 else # Tolerant for the same reason as the Claude branch: report and keep going so the # user still gets the summary telling them exactly what did and did not install. say "WARNING: 'npm install -g @essentialai/cogent-bridge' failed — Codex setup incomplete" fi add_to_path "$prefix/bin" || true fi # Nothing installed at all is a real failure — say so and exit non-zero, rather than # printing a cheerful "Done." over a machine where nothing happened. if [ "$claude_ok" -eq 0 ] && [ "$codex_ok" -eq 0 ]; then say "FAILED: neither the Claude Code plugin nor the Codex bridge could be installed." say " Re-run with the output above, or report it at https://cogent.tools" exit 1 fi # 5) Next steps if [ "$have_claude" -eq 1 ]; then say "Claude Code: run /cogent:register inside Claude Code."; fi if [ "$have_codex" -eq 1 ]; then if command -v cogent-codex >/dev/null 2>&1; then say "Codex: start with cogent-codex then register on your channel." else # The binary exists at $prefix/bin but this shell has not picked up the new PATH. A piped # installer cannot export into the parent shell — so tell the user the exact next command # instead of letting them hit 'command not found'. say "" say "==============================================================" say " ONE MORE STEP — reload your shell so cogent-codex is on PATH:" say "" say " exec $SHELL -l" say "" say " then start Codex with: cogent-codex" say "==============================================================" fi fi say "Done."