Skip to content

feat(cli): first-run install UX — post-install smoke test + connect panel - #272

Merged
aterrylu merged 2 commits into
mainfrom
terry/server-install-ux
Jun 29, 2026
Merged

feat(cli): first-run install UX — post-install smoke test + connect panel#272
aterrylu merged 2 commits into
mainfrom
terry/server-install-ux

Conversation

@aterrylu

Copy link
Copy Markdown
Owner

Problem

PR1 (#271, ADR-050) moved supervision to launchd/systemd-user, but install-service still ended with an un-verified line — "daemon should be running shortly" — and never told the user where to reach the dashboard. A first-time curl install.sh | sh user got a binary and a service file, but no confirmation it worked and no URL/token. That's the exact onboarding friction this initiative set out to remove (the deferred "Tier-1 install UX").

Solution (ADR-052)

A shared post-install verifier that turns a silent install into a confirmed one.

graph TD
    subgraph OLD["❌ before"]
        A1["install-service loads the unit"] --> A2["print: 'should be running shortly'"]
        A2 --> A3["exit 0 — no verification, no URL, no token"]
        A3 --> A4["user: is it up? what's the URL? 🤷"]
    end
    subgraph NEW["✅ after"]
        B1["install-service loads the unit"] --> B2["poll pid-file until daemon responds<br/>(real smoke test, ~12s budget)"]
        B2 -->|"responsive"| B3["connect panel:<br/>URL (actual bound port) · token · auth link"]
        B3 --> B4["--open → browser (no-op on headless / CI)"]
        B4 --> B5["exit 0"]
        B2 -->|"timeout"| B6["⚠️ warn: check autonomos logs"]
        B6 --> B7["exit 3 — curl install reports failure,<br/>no false success banner"]
    end
    OLD ==> NEW
Loading

What changed

  • packages/cli/src/lib/post-install.ts (new) — verifyAndReportInstall() polls until the daemon is actually responsive (pid file present + pid alive + port answers /api/system/version), then prints the connect panel. Reads the actual bound port from the pid file (a default install binds :3000; only make prod forces :3100) and the token from $configDir/token (with the same ~/.autonomos/token legacy fallback the server uses). Never throws.
  • install-service.ts — calls the verifier after activation (both platforms) behind a new --open flag. Returns exit 3 (activated-but-unresponsive) on timeout so the failure is honest.
  • install.sh — passes --open on an interactive terminal ([ -t 1 ], opt out AUTONOMOS_NO_OPEN=1), and branches its closing message on the exit code — "installed and running" (URL/token shown above) vs. "installed but not responding — check autonomos logs" (and exits non-zero). Previously a piped install printed a success banner even over a down daemon.

Why the exit-code matters (review catch)

The silent-failure review found that with the verifier returning void, a curl install.sh (which has set -e and no smoke gate of its own) would print "⚠️ not responsive" immediately followed by "✓ installed / URL shown above" — a contradictory success banner over a dead daemon. make prod was safe (its install-prod-service.sh has a hard smoke gate), but the curl path wasn't. Fixed by the boolean return → exit 3 → install.sh branch.

Testing

  • Unit (3, all pass): connect panel (URL + token + auth link), env-token fallback, and the no-daemon path (returns false + warns, doesn't throw). Full make check green — server/cli + 227 dashboard tests.
  • QA (isolated config dir + port 3199, never touched :3100): real install-service polled the daemon up and printed the correct URL + real on-disk token + click-to-auth link; clean launchd teardown.

Notes

  • Stacked: PR2 of 2, follows the merged feat(server): retire pm2 from operator path — launchd/systemd-user supervision + rotating logs #271 (ADR-050). Touches install-service.ts — a different region than the concurrent ElectronCleanup PR's Desktop-detection removal (low conflict; later merger rebases).
  • Deferred (deliberately): auto-appending export PATH=… to the user's shell rc — editing an rc from a piped curl | sh is an invasive footgun; the existing PATH hint stays.
  • ADR-052 in docs/DECISIONS.md (051 reserved for ElectronCleanup per the later-merger convention).

🤖 Generated with Claude Code

…anel

install-service now polls until the daemon is actually responsive (a real smoke
test, not a guess), then prints a connect panel: dashboard URL (from the pid
file's actual bound port — a default install is :3000, make prod forces :3100),
the auth token (from $configDir/token), and a click-to-auth link. A --open flag
opens the dashboard in a browser, no-opping on headless boxes and in CI;
install.sh passes it on an interactive terminal (opt out AUTONOMOS_NO_OPEN=1).

On a verify timeout install-service returns exit 3 (activated-but-unresponsive)
so a piped curl install reports the failure instead of printing a success banner
over a down daemon. ADR-052.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S7yopZT2FmLCSboCAMAXxb
@aterrylu
aterrylu enabled auto-merge (squash) June 29, 2026 08:15
Comment thread packages/cli/src/__tests__/post-install.test.ts

@nox-0x nox-0x left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving — the post-install smoke-test + connect panel is a clean win, the pid-file-as-source-of-truth choice keeps the URL correct across :3000/:3100/OS-assigned ports, and the --open headless/CI guards are right.

Verified:

  • migrate-from-pm2 forwards its argv into runInstallServiceCommand, so the --open flag from install.sh survives the pm2 migration path (packages/cli/src/commands/migrate-from-pm2.ts:38).
  • install.sh's RC=$? plumbing surfaces the new exit-code 3 ("activated but daemon not responsive") honestly instead of papering it over.
  • openBrowser is properly detached/unref'd with an error handler, so a missing xdg-open can't fail the install.
  • readToken()'s fallback ordering matches resolveAuthToken() (per-config-dir → legacy ~/.autonomos/token), so the panel agrees with what the daemon will actually accept.

One non-blocking 🟡 inline: the new test only isolates AUTONOMOS_CONFIG_DIR, not HOME, so the legacy ~/.autonomos/token fallback can read the developer's real prod token in the "no token file" case — passes on CI, can fail locally for anyone who has autonomOS installed. Fixable in a follow-up by also pinning process.env.HOME = TEST_DIR.

…lback

Addresses review (nox-0x): the test only overrode AUTONOMOS_CONFIG_DIR, not HOME,
so readToken()'s legacy $HOME/.autonomos/token fallback would read (and leak) the
maintainer's real token on a machine with autonomOS installed — green on CI, broken
locally. Pin HOME=TEST_DIR and add a hermetic test that exercises the fallback branch.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S7yopZT2FmLCSboCAMAXxb
@aterrylu
aterrylu merged commit 131fa2b into main Jun 29, 2026
10 checks passed
@aterrylu
aterrylu deleted the terry/server-install-ux branch June 29, 2026 08:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants