Add agent-friendly Beeper install script; fix README platform support#15
Add agent-friendly Beeper install script; fix README platform support#15Miyamura80 wants to merge 16 commits into
Conversation
Prototype one-command installer that wires Beeper into the Edison Watch MCP gateway on macOS: installs prerequisites, brings up a headless Beeper Server, supervises the stdiod tunnel daemon, registers Beeper's stdio MCP proxy (npx @beeper/desktop-mcp) as a tunnel child, binds the Beeper access token, and prints the Edison MCP URL. Built to be driven by an agent or a human: every input is a flag or env var, missing inputs fail fast with the exact fix, subcommands follow a resource+verb pattern, --help carries examples, and --dry-run/--yes/--json are supported. Interactive prompts only run behind --interactive. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
Ran the script on Linux against the real edison-stdiod binary (built from this checkout) plus stubbed beeper, which surfaced several defects: - add_networks leaked IFS=',' into run()'s "$*" logging, so --dry-run previewed 'beeper accounts add' calls with commas instead of spaces. Split on commas via tr without mutating IFS. - Token-bind curl printed 'http 000000' (its own 000 plus a '|| echo 000' fallback) and had no timeout. Use '|| true' with an empty-check and add --connect-timeout/-m so it fails fast. - --dry-run without --beeper-token aborted at the token step instead of previewing. Add a dry-run branch that reports the intent and continues. - edison-stdiod install failures spilled a raw anyhow backtrace and aborted mid-flow via set -e. Export RUST_BACKTRACE/RUST_LIB_BACKTRACE=0 and wrap each edison-stdiod step so failures produce an actionable message. - Skip the live 'server list' idempotency probe under --dry-run. - The real binary carries a Linux (systemd --user) path, so the hard macOS-only guard was stricter than the daemon. Warn on Linux and let the daemon report capability, block only truly unsupported platforms. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
The daemon's supervisor integration is implemented for macOS (LaunchAgent), Linux (systemd --user), and Windows (Scheduled Task); only genuinely other OSes hit the runtime stub. Update the WARNING, the quickstart install comment, and the CLI table so the README matches the code. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
Qodo reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
There was a problem hiding this comment.
3 issues found across 2 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="README.md">
<violation number="1" location="README.md:116">
P3: The README now advertises Linux and Windows supervisors, but `edison-stdiod install --help` still describes `install` as a macOS LaunchAgent command. Updating the CLI help with this documentation change would keep the advertised interface and the runnable help output consistent.</violation>
</file>
<file name="scripts/install-beeper.sh">
<violation number="1" location="scripts/install-beeper.sh:84">
P2: `uninstall --dry-run` fails with the non-interactive confirmation error instead of previewing the uninstall. Treating dry-run as an automatic confirmation would preserve the documented “change nothing” preview behavior.</violation>
<violation number="2" location="scripts/install-beeper.sh:289">
P2: `install --networks ...` can block in the default agent/non-interactive mode because `beeper accounts add` is invoked without checking `--interactive`. Gating this flow behind `--interactive` or wiring the documented non-browser auth flow would prevent an unattended installer from hanging.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| for net in $nets; do | ||
| [ -z "$net" ] && continue | ||
| log "network: adding '$net' (follow the QR / code prompt in this terminal)" | ||
| run beeper accounts add "$net" |
There was a problem hiding this comment.
P2: install --networks ... can block in the default agent/non-interactive mode because beeper accounts add is invoked without checking --interactive. Gating this flow behind --interactive or wiring the documented non-browser auth flow would prevent an unattended installer from hanging.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/install-beeper.sh, line 289:
<comment>`install --networks ...` can block in the default agent/non-interactive mode because `beeper accounts add` is invoked without checking `--interactive`. Gating this flow behind `--interactive` or wiring the documented non-browser auth flow would prevent an unattended installer from hanging.</comment>
<file context>
@@ -0,0 +1,452 @@
+ for net in $nets; do
+ [ -z "$net" ] && continue
+ log "network: adding '$net' (follow the QR / code prompt in this terminal)"
+ run beeper accounts add "$net"
+ done
+}
</file context>
|
|
||
| confirm() { | ||
| [ "$ASSUME_YES" -eq 1 ] && return 0 | ||
| [ "$INTERACTIVE" -eq 0 ] && die "refusing to run a confirming action non-interactively: $1" \ |
There was a problem hiding this comment.
P2: uninstall --dry-run fails with the non-interactive confirmation error instead of previewing the uninstall. Treating dry-run as an automatic confirmation would preserve the documented “change nothing” preview behavior.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/install-beeper.sh, line 84:
<comment>`uninstall --dry-run` fails with the non-interactive confirmation error instead of previewing the uninstall. Treating dry-run as an automatic confirmation would preserve the documented “change nothing” preview behavior.</comment>
<file context>
@@ -0,0 +1,452 @@
+
+confirm() {
+ [ "$ASSUME_YES" -eq 1 ] && return 0
+ [ "$INTERACTIVE" -eq 0 ] && die "refusing to run a confirming action non-interactively: $1" \
+ "pass --yes to proceed, or --dry-run to preview"
+ printf '%s [y/N] ' "$1" >&2; read -r ans; [ "$ans" = "y" ] || [ "$ans" = "Y" ]
</file context>
| | --- | --- | | ||
| | `login` | Persist credentials + backend URL to `~/.config/edison-stdiod/config.toml` (mode `0600`). Merges on re-run, so you can rotate the API key without re-supplying the backend URL. | | ||
| | `install` | Register the OS supervisor unit (macOS LaunchAgent) so the daemon starts at login and restarts on crash. Requires `login` first. | | ||
| | `install` | Register the OS supervisor unit (LaunchAgent on macOS, systemd `--user` unit on Linux, Scheduled Task on Windows) so the daemon starts at login and restarts on crash. Requires `login` first. | |
There was a problem hiding this comment.
P3: The README now advertises Linux and Windows supervisors, but edison-stdiod install --help still describes install as a macOS LaunchAgent command. Updating the CLI help with this documentation change would keep the advertised interface and the runnable help output consistent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At README.md, line 116:
<comment>The README now advertises Linux and Windows supervisors, but `edison-stdiod install --help` still describes `install` as a macOS LaunchAgent command. Updating the CLI help with this documentation change would keep the advertised interface and the runnable help output consistent.</comment>
<file context>
@@ -112,7 +113,7 @@ TLDR: `edison-stdiod --help` (and `edison-stdiod <command> --help` for any subco
| --- | --- |
| `login` | Persist credentials + backend URL to `~/.config/edison-stdiod/config.toml` (mode `0600`). Merges on re-run, so you can rotate the API key without re-supplying the backend URL. |
-| `install` | Register the OS supervisor unit (macOS LaunchAgent) so the daemon starts at login and restarts on crash. Requires `login` first. |
+| `install` | Register the OS supervisor unit (LaunchAgent on macOS, systemd `--user` unit on Linux, Scheduled Task on Windows) so the daemon starts at login and restarts on crash. Requires `login` first. |
| `uninstall` | Stop and remove the supervisor unit. Pass `--purge` to also delete the persisted config and logs. |
| `run` | Run the daemon in the foreground (normally invoked by the service unit). Reads config or accepts `--backend` / `--api-key` / `--device-id` / `--label` flags (also via `EDISON_*` env vars). |
</file context>
Reported: 'install --dry-run' died on a missing 'beeper' CLI instead of previewing. Rework dependency handling around a single ensure_tool helper: - --dry-run previews the install command for each missing dep and never fails, so you can inspect the whole plan before installing anything. - Auto-install now requires consent (--install-deps or --interactive) AND a confirmation (auto-passed by --yes, prompted under --interactive, refused non-interactively without --yes). - After running an installer, validate the command actually landed on PATH; validate the installer itself (brew/cargo) exists before invoking it. - No consent still fails fast with the exact manual command. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
…tics
Traced the real backend route: POST /api/v1/servers/{name}/env
(UpdateServerEnvRequest) stages the value in the device env_store and
respawns the child. The script's guessed path/body were already correct.
- Drop the 'guessed route' hedging; document the confirmed endpoint.
- The endpoint is admin-only, so surface a clear 401/403 message telling
the user --ew-api-key must belong to an org admin.
- Distinguish 000 (unreachable / daemon not yet connected) from other codes,
each with an actionable next step; keep the failure non-fatal.
- Bump the bind timeout to 30s since the call blocks on a child respawn.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
Add a TTY-aware color layer with semantic helpers (step/ok/warn/info) so both dry-run and real runs read as grouped phases instead of a wall of white text: - Colors auto-disable when stderr is not a TTY, when NO_COLOR is set, or with the new --no-color flag, so piped and agent output stays a clean, parseable ASCII stream (verified: no escape codes leak when piped). - Phase headers (>>), success (+), info (-), warn (!), and error (x) markers. - Result block on stdout is gated separately on stdout being a TTY, and the copy-paste 'claude mcp add' line stays uncolored so it pastes cleanly. - --json output is unchanged and never colorized. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
From a real macOS run: - brew install dumped its full auto-update 'New Formulae' wall. Export HOMEBREW_NO_AUTO_UPDATE=1 and HOMEBREW_NO_ENV_HINTS=1 and pass --quiet so dependency installs are calm. - The token step optimistically tried a nonexistent mint endpoint and then pointed at a GUI-only path. Beeper exposes no headless token mint, so state that plainly: guide the user to create a token once in the app UI and re-run with --beeper-token, noting the re-run is fast because deps and the Beeper Server are already set up and every step is idempotent. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
Beeper has no headless token mint (OAuth is authorization_code + PKCE), but the CLI already holds a valid Desktop API bearer after 'beeper setup'. Add format-agnostic discovery that reuses it so no GUI 'Approved connections' step is needed: - discover_beeper_token gathers candidate strings from 'beeper config path' and the macOS Keychain, then keeps the first that authenticates against the local OAuth userinfo endpoint (discovered from the well-known metadata). - ensure_beeper_token tries discovery before failing; on success it reuses the token silently (only a masked fingerprint is shown, never the secret). - New 'token' subcommand runs discovery standalone for quick verification. - All probes are timeout-bounded so a missing server fails fast instead of hanging. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
Field findings: 'beeper config path' returns a single file (~/.beeper/ config.json), and the Desktop API did not answer on IPv4, suggesting an IPv6-only bind. Make token discovery robust: - Probe the well-known metadata on 127.0.0.1, [::1], and localhost (and BEEPER_API_URL if set), using the first that answers as the validation base. - Scan the whole config directory (dirname of the config file) plus ~/.beeper, not just the single config file. - Report clearly when the Desktop API is unreachable so the cause is obvious. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
…tatus' Root cause of the empty ~/.beeper and dead 23373: 'beeper status' exits 0 even when no server is configured, so ensure_beeper_server printed 'already running' and SKIPPED 'beeper setup --server --install'. The server was never authorized and the Desktop API never started. - Add beeper_api_base(): probe the well-known metadata across ports 23373-23378 on 127.0.0.1/localhost/[::1] (honor BEEPER_API_URL), echo the first reachable base URL. - ensure_beeper_server now runs setup when the API is unreachable and re-probes after, failing with a clear 'finish the browser authorization' message if it still is not up. - discover_beeper_token reuses beeper_api_base for the same port range. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="scripts/install-beeper.sh">
<violation number="1" location="scripts/install-beeper.sh:281">
P2: Token discovery exits silently when a config directory has no token-shaped strings, so the intended Keychain and manual-token fallbacks never run. Make the candidate-extraction pipeline non-fatal when it finds no matches.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| for d in $dirs; do | ||
| [ -d "$d" ] || continue | ||
| cands="$cands | ||
| $(find "$d" -type f -exec cat {} + 2>/dev/null | grep -oE '[A-Za-z0-9._-]{24,}' | sort -u | head -n 80)" |
There was a problem hiding this comment.
P2: Token discovery exits silently when a config directory has no token-shaped strings, so the intended Keychain and manual-token fallbacks never run. Make the candidate-extraction pipeline non-fatal when it finds no matches.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/install-beeper.sh, line 281:
<comment>Token discovery exits silently when a config directory has no token-shaped strings, so the intended Keychain and manual-token fallbacks never run. Make the candidate-extraction pipeline non-fatal when it finds no matches.</comment>
<file context>
@@ -246,19 +246,40 @@ mask_token() {
+ for d in $dirs; do
+ [ -d "$d" ] || continue
+ cands="$cands
+$(find "$d" -type f -exec cat {} + 2>/dev/null | grep -oE '[A-Za-z0-9._-]{24,}' | sort -u | head -n 80)"
+ done
if command -v security >/dev/null 2>&1; then
</file context>
| $(find "$d" -type f -exec cat {} + 2>/dev/null | grep -oE '[A-Za-z0-9._-]{24,}' | sort -u | head -n 80)" | |
| $(find "$d" -type f -exec cat {} + 2>/dev/null | grep -oE '[A-Za-z0-9._-]{24,}' | sort -u | head -n 80 || true)" |
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="scripts/install-beeper.sh">
<violation number="1" location="scripts/install-beeper.sh:227">
P2: A non-Beeper HTTP listener is treated as a running Desktop API, so install can skip server setup and fail later during token discovery. Require successful OAuth metadata (for example HTTP 200) in both probe branches rather than accepting every non-`000` response.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| local wk="/.well-known/oauth-authorization-server" code h p url | ||
| if [ -n "${BEEPER_API_URL:-}" ]; then | ||
| code="$(curl -s -m 3 -o /dev/null -w '%{http_code}' "${BEEPER_API_URL}${wk}" 2>/dev/null || true)" | ||
| [ -n "$code" ] && [ "$code" != "000" ] && { printf '%s' "$BEEPER_API_URL"; return 0; } |
There was a problem hiding this comment.
P2: A non-Beeper HTTP listener is treated as a running Desktop API, so install can skip server setup and fail later during token discovery. Require successful OAuth metadata (for example HTTP 200) in both probe branches rather than accepting every non-000 response.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At scripts/install-beeper.sh, line 227:
<comment>A non-Beeper HTTP listener is treated as a running Desktop API, so install can skip server setup and fail later during token discovery. Require successful OAuth metadata (for example HTTP 200) in both probe branches rather than accepting every non-`000` response.</comment>
<file context>
@@ -217,15 +217,47 @@ ensure_deps() {
+ local wk="/.well-known/oauth-authorization-server" code h p url
+ if [ -n "${BEEPER_API_URL:-}" ]; then
+ code="$(curl -s -m 3 -o /dev/null -w '%{http_code}' "${BEEPER_API_URL}${wk}" 2>/dev/null || true)"
+ [ -n "$code" ] && [ "$code" != "000" ] && { printf '%s' "$BEEPER_API_URL"; return 0; }
+ fi
+ for h in 127.0.0.1 localhost "[::1]"; do
</file context>
Field finding: the CLI stores the bearer verbatim as "accessToken" in ~/.beeper/targets/<name>.json. The previous discovery cat'd the entire ~/.beeper tree (including the 100MB server binary and sqlite DBs), so the real token was crowded out past the candidate cutoff. - Extract "accessToken" straight from targets/*.json and try those first, remembering the first as the canonical token. - Limit the broad fallback scan to JSON files under 1M (skip binaries/DBs/logs). - If no candidate passes the live userinfo check, fall back to the explicit accessToken rather than failing (userinfo can be strict about scopes; a truly wrong token surfaces as a real child auth error later). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
… child Real macOS run got all the way through headless token discovery and daemon install, then failed at 'edison-stdiod server add ... --arg -y': clap reads a hyphen-leading value in the space form as an unknown flag. Use the --arg=-y equals form (verified: parses where the space form errors). Also pass the discovered Desktop API base URL to the child as BEEPER_API_URL and BEEPER_DESKTOP_BASE_URL, because the server may bind a non-default port (23374 here) while @beeper/desktop-mcp defaults to 23373. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
A real run reached the daemon and then failed inside 'server add' with a raw 'HTTP 401 Invalid or inactive API key'. Pre-validate the key against GET /api/v1/servers in ensure_ew_api_key so a bad or wrong-environment key fails early with an actionable message (including the demo backend hint), before login/install/server-add run. Non-401 responses proceed; unreachable backend warns and continues. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
A stale 'beeper' registered under an old device_id caused a 409 (names are unique per org, so the this-device 'server list' check missed it). On a conflict, remove the org-level server (admin-only) and re-add it on this device. If it still fails (e.g. non-admin key), fail with guidance to use --server-name or delete it in the dashboard. Also skip the live probe cleanly under --dry-run. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
There was a problem hiding this comment.
All reported issues were addressed across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
The 409 self-heal never ran: 'out=$(edison-stdiod server add ...)' as a bare assignment fails under set -e when the command returns non-zero, so bash exited silently right after the daemon install (output was captured, nothing printed). Guard both add captures with '&& rc=0 || rc=$?' so the failure is recorded and the conflict handler runs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
Adding a stdio_tunnel server is gated behind interactive step-up re-auth (backend _require_recent_supabase_login). Detect the STEP_UP_REQUIRED response and explain the real cause and unblock (add once in the dashboard, or set STEP_UP_BYPASS_EMAIL_DOMAINS on a non-release backend) instead of the generic admin-key message. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011CRjwx1BeWkTiS97Acyjvx
What
Adds
scripts/install-beeper.sh, a one-command installer that wires Beeper into the Edison Watch MCP gateway, and corrects a stale "macOS only" claim in the README.The install script
Thin orchestrator over the
beeperandedison-stdiodCLIs plus two REST calls.installruns the full flow idempotently:End-to-end path it sets up:
Built to be driven by an agent or a human:
UPPER_SNAKEenv var. Prompts only run behind--interactive.--dry-run,--yes,--json,--verbose,--install-deps.install,doctor,status,network add|list,mcp-url,uninstall.--helpcarries runnable examples; missing inputs fail fast with the exact fix.mcp_url, auth header, ready-to-pasteclaude mcp add ...).Example:
Validated by actually running it
Exercised on Linux against the real
edison-stdiodbinary (built from this checkout) plus a stubbedbeeper. Confirmed every flag the script calls matches the binary's--help(login,install,server add/list/remove), and thatloginwrites a correctconfig.toml. Running it surfaced and fixed several defects: anIFSleak that mangled dry-run logging, a token-bind curl printinghttp 000000with no timeout, a dry-run abort when no token was supplied, and a raw daemon backtrace plus mid-flowset -eabort oninstallfailure (now a clean actionable message).Known seams (need a live backend / Mac to verify)
Three integration points are wrapped defensively and marked, not faked:
--beeper-tokenoverrides).--ew-api-key).POST /api/v1/servers/<name>/envis a best guess (overridable viaEW_SERVER_ENV_PATH); non-fatal with a manual fallback if it 404s. The clean alternative is a smalledison-stdiod server envsubcommand.README fix
The WARNING claimed the daemon is "macOS only," but
src/platform/implements supervisor integration for macOS (LaunchAgent), Linux (systemd--user), and Windows (Scheduled Task); only other OSes hit the runtime stub. Updated the WARNING, the quickstart install comment, and the CLI table to match the code, while noting macOS is the most exercised target.Test plan
bash -n+ repo AI-writing hook passedison-stdiodbuilds; script flags match its--help--json, fail-fast, and clean-failure paths exercised on LinuxbeeperCLI and a live Edison backend🤖 Generated with Claude Code
Generated by Claude Code
Summary by cubic
Adds a one-command, agent-friendly installer to wire Beeper into the Edison Watch MCP gateway, and updates the README for macOS, Linux, and Windows support. Headless setup reuses the Beeper CLI token, validates the Desktop API, and cleanly handles conflicts and step-up reauth when registering the tunnel child.
New Features
scripts/install-beeper.sh: installs deps, starts headlessbeeper, logs in and installsedison-stdiod, registersnpx @beeper/desktop-mcp(uses--arg=-y), binds the Beeper token, and prints the MCP URL or--json.accessTokenfrom~/.beeper/targets/*.json, small JSON files, and macOS Keychain; validates via the local OAuth userinfo; probes127.0.0.1,[::1], andlocalhoston ports 23373–23378; adds atokensubcommand.--dry-run,--yes,--json,--verbose,--install-deps; subcommands:install,doctor,status,network add|list,mcp-url,token,uninstall; TTY-aware colors auto-disable; Linux allowed with a warning.--user), and Windows (Scheduled Task).Bug Fixes
HOMEBREW_NO_AUTO_UPDATE=1,HOMEBREW_NO_ENV_HINTS=1,--quiet.GET /api/v1/servers; clear 401/403 and demo-backend hint; warns on unreachable backends.server addhardening: use--arg=-y; don’t abort on non-zero so conflict handler runs; self-heal 409 by remove+re-add; detect and explainSTEP_UP_REQUIREDwith dashboard unblock guidance; suppress Rust backtraces on failure.POST /api/v1/servers/{name}/env, 30s timeout; clear 401/403 vs 000; non-fatal with dashboard fallback; passesBEEPER_API_URL/BEEPER_DESKTOP_BASE_URL.--dry-runin probes and token step.Written for commit 97cb8ff. Summary will update on new commits.