port the agent CLIs, using Anthropic's installer rather than npm

Claude Code goes in through https://claude.ai/install.sh, matching what the
platform already does for members in os-user-claude.ts and chosen there for the
auto-update npm does not give. The comment at os-user-claude.ts:28 claiming
setup.sh already did this was simply wrong — setup-ubuntu.sh used
`npm install -g @anthropic-ai/claude-code`.

Two things that installer insists on, both of which a naive port gets wrong and
both of which os-user-claude.ts had already found:

  It REFUSES to run under sudo from a regular user's shell — it checks for uid 0
  with SUDO_USER set, because everything it writes goes under $HOME and under
  sudo that is root's. This script runs as root, so the install has to be done AS
  the account.

  It declares #!/bin/bash and uses [[ … =~ … ]], so it must be piped to bash. On
  Ubuntu /bin/sh is dash and `| sh` fails.

Two bugs found by running it rather than reading it:

  opencode does not install to ~/.local/bin. It goes to ~/.opencode/bin, which is
  what sidecar/opencode/index.ts:22 hardcodes. The first version looked in the
  wrong place, reported a working install as missing, and installed it again —
  the run said "did not complete" while the installer had plainly succeeded.

  claude on this machine came from npm, so `command -v claude` found it and the
  section would have left a copy that never updates. It now detects an npm
  install by resolving the binary into node_modules, says so, and offers to
  reinstall through the official installer — naming the npm copy and how to
  remove it rather than deleting something it did not put there.

~/.local/bin and ~/.opencode/bin are both added to the account's PATH. The
sidecars do not need it — they check the exact paths — but a user who cannot run
`claude` in their own terminal reasonably concludes it was never installed.

PI stays optional and says outright that nothing in the platform spawns it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-08-12 21:18:10 +00:00
co-authored by Claude Opus 5
parent cb3e7062b9
commit 607a962115
2 changed files with 193 additions and 1 deletions
+115 -1
View File
@@ -2062,13 +2062,127 @@ if ! skip; then
step_ok
fi
# =============================================================================
# 26. Agent CLIs
# =============================================================================
#
# claude and opencode are what the chat sidecars spawn, so they are installed
# rather than offered — for the same reason node and bun are. PI is nobody's
# dependency and is asked for.
step "Agent CLIs"
if ! skip; then
echo ""
info "Agent CLIs — the programs Officer's chat actually runs"
echo " claude $(agent_version claude || echo 'not installed')"
echo " spawned by officer-agent; chat does not work without it."
echo " opencode $(agent_version opencode || echo 'not installed')"
echo " the alternative agent, run by officer-opencode."
echo ""
echo " Installed as ${USERNAME} rather than as root: Anthropic's installer"
echo " refuses to run under sudo, because everything it writes goes under"
echo " \$HOME and under sudo that is root's. They land in different places —"
echo " claude in ~/.local/bin, opencode in ~/.opencode/bin — and the"
echo " sidecars look in exactly those two."
if agent_installed claude && agent_is_npm_install claude; then
# The npm package does not auto-update; Anthropic's installer does, which is
# why the platform uses it for members. Worth offering to switch rather than
# leaving a copy that quietly goes stale.
echo ""
warn "claude here came from npm ($(agent_path claude)) and does not auto-update"
echo " Anthropic's installer puts it in ${USER_HOME}/.local/bin and keeps it"
echo " current. That is what the platform installs for members."
if confirm "Reinstall it with the official installer?"; then
if install_claude_code; then
ok "claude $(agent_version claude) from $(agent_path claude)"
echo " the npm copy is still at /usr/local/bin/claude — remove it with:"
echo " sudo npm uninstall -g @anthropic-ai/claude-code"
SUMMARY+=("Claude Code: reinstalled via the official installer")
else
warn "the Claude Code install did not complete"
ERRORS+=("Claude Code: install failed")
fi
else
echo " left as the npm install"
SUMMARY+=("Claude Code: npm install kept, does not auto-update")
fi
elif agent_installed claude; then
ok "claude $(agent_version claude) already installed"
else
info " installing Claude Code..."
if install_claude_code; then
ok "claude $(agent_version claude)"
SUMMARY+=("Claude Code: $(agent_version claude)")
else
warn "the Claude Code install did not complete"
ERRORS+=("Claude Code: install failed")
fi
fi
if agent_installed opencode; then
ok "opencode $(agent_version opencode) already installed"
else
info " installing opencode..."
if install_opencode; then
ok "opencode $(agent_version opencode)"
SUMMARY+=("opencode: $(agent_version opencode)")
else
warn "the opencode install did not complete"
ERRORS+=("opencode: install failed")
fi
fi
# ~/.local/bin has to be on PATH for the account's own shell. The sidecars find
# claude without it — claude-manager.ts checks that directory explicitly — but
# a user who cannot run `claude` in their own terminal reasonably concludes it
# was never installed.
if id "$USERNAME" &>/dev/null; then
ZSHRC="${USER_HOME}/.zshrc"
touch "$ZSHRC"
chown "$USERNAME:$USERNAME" "$ZSHRC"
# Both directories, because the two installers disagree about where they put
# things. The sidecars find their binaries without this — they check the
# exact paths — but a user who cannot run `claude` in their own terminal
# reasonably concludes it was never installed.
if append_once "$ZSHRC" agent-clis <<'EOF'
export PATH="$HOME/.local/bin:$HOME/.opencode/bin:$PATH"
EOF
then
ok "~/.local/bin and ~/.opencode/bin added to ${USERNAME}'s PATH"
fi
fi
# ── PI, which nothing here uses ──
echo ""
if command -v pi &>/dev/null; then
echo " pi is already installed — left alone"
else
info "PI?"
echo " Another coding agent. Officer does not use it — nothing in the"
echo " platform spawns it — and it is offered because it was in the"
echo " original script and you may want it for your own work."
if confirm "Install PI?" n; then
if install_pi && command -v pi &>/dev/null; then
ok "pi installed"
SUMMARY+=("PI: installed (not used by Officer)")
else
warn "the PI install did not complete"
fi
else
echo " skipped"
fi
fi
step_ok
fi
# =============================================================================
# NOT PORTED YET
# =============================================================================
#
# Sections still to move across from scripts/setup-old/setup-ubuntu.sh, in order:
#
# dev tools · ufw · zshrc
# ufw · zshrc
#
# And one that is new rather than ported, to come last of all:
#