agent-orchestrator/test/cli
Harshit Singh Bhandari bc84b6d2a3
feat(cli): ao start fetch/open for Windows and Linux (T6/T7) (#2204)
* feat(start): implement ao start fetch/open for Windows and Linux

Fill in the non-darwin branches of the bootstrapper (T6/T7):

- assetName() selects the per-GOOS stable release asset: windows ->
  agent-orchestrator-win32-x64.exe (NSIS installer), linux ->
  agent-orchestrator-linux-x64.AppImage. amd64-only for now via
  requireAMD64(), which returns a clear unsupported-arch error.
- fetchApp() dispatches per GOOS. Windows downloads the NSIS installer and
  runs it silently (/S) to the default per-user dir, then resolves the
  installed exe under %LOCALAPPDATA%\Programs. Linux downloads the AppImage
  to a stable path under ~/.ao (atomic temp+rename), chmods it executable,
  and skips any install step so re-runs resolve without re-fetching.
- knownAppLocations() scans the per-user and per-machine Windows install
  dirs and the stable Linux AppImage path.
- isUsableBundle() treats a win exe / linux AppImage as a regular file
  (darwin stays a directory).
- openApp() launches win/linux detached via the existing StartProcess seam,
  forwarding --installed-via=npm-bootstrap, and falls back to manual-open on
  spawn failure.

The Windows silent-install path is marked ponytail (untestable on the macOS
build host); a wrong install dir surfaces as a clear not-found error. Tests
cover asset naming, arch gating, scan locations, regular-file vs dir, and
the detached-spawn + fallback paths.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(release): build + publish a Linux AppImage for ao start (T7)

AppImage is the Linux fetch-and-run artifact for the bootstrapper (spec
§11.3): a single self-contained executable ao start downloads and runs
directly, no system package manager.

- makers/maker-appimage.ts: a MakerBase subclass bridging to
  electron-builder's buildForge (appImage target), mirroring maker-nsis.ts,
  since Forge has no first-party AppImage maker. publish:null so Forge owns
  release uploads.
- forge.config.ts: register MakerAppImage for linux; keep deb/rpm for users
  who want a system package.
- frontend-release.yml: on ubuntu-latest, copy the built AppImage to the
  stable, space-free name agent-orchestrator-linux-x64.AppImage and upload
  it to the v<version> release with --clobber, mirroring the Windows step.

Build-untested on this macOS host: the first ubuntu CI run must confirm the
electron-builder AppImage target token and the out/make/*.AppImage output
glob.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(ci): green up ao start Win/Linux PR (lint, cross-OS test, container smoke)

Five golangci-lint findings in start.go, two cross-OS test failures, and the
fresh-install container smoke were broken by the Win/Linux bootstrapper diff.

Go lint (start.go):
- gocritic filepathJoin: build "/Applications/<bundle>" as a literal instead of
  filepath.Join with a separator-bearing arg.
- gocritic httpNoBody: pass http.NoBody, not nil, to NewRequestWithContext.
- gosec G302: annotate the AppImage chmod 0755 with a nolint; an AppImage is a
  self-contained executable and must be executable.
- nilerr: annotate openApp's intentional (false, nil) on launch failure; the
  failure is reported via the bool, not as an error.
- unparam: resolveApp's error result was always nil; drop it and update callers.

Cross-OS tests (start_test.go):
- makeBundle created a directory, which only stats as usable on macOS. Make it a
  regular file on Windows/Linux so the marker/scan resolve tests pass there,
  matching isUsableBundle's per-OS rule.

Container fresh-install smoke (test/cli/install-check.sh, Dockerfile):
- ao start is now the desktop-app launcher and no longer runs a daemon, so the
  old daemon status/shutdown/stop assertions could never pass. Assert instead
  that on a fresh box start reaches the fetch path and exits non-zero with a
  clear error (404 download on amd64, unsupported-arch on arm64). Refresh the
  stale daemon-reaping comments in the Dockerfile.

Verified locally: go build/vet ok, golangci-lint v2.12.2 reports 0 issues,
go test -tags e2e ./internal/cli/... passes (the only remaining failure,
TestE2E_Lifecycle, is a pre-existing daemon-shutdown flake that fails the same
way on upstream/main on this host), and the container smoke passes on both
linux/arm64 and linux/amd64.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:50:30 +05:30
..
Dockerfile feat(cli): ao start fetch/open for Windows and Linux (T6/T7) (#2204) 2026-06-26 20:50:30 +05:30
README.md chore: add prettier config and CI auto-formatter (#166) 2026-06-10 09:09:17 +05:30
install-check.sh feat(cli): ao start fetch/open for Windows and Linux (T6/T7) (#2204) 2026-06-26 20:50:30 +05:30

README.md

ao CLI end-to-end tests

These tests drive the real ao binary the way a user would — start
statusdoctorstop, plus the daemon-control HTTP surface — and assert
the whole thing works. They run against isolated, throwaway state (a per-test
temp run-file + data dir + an OS-assigned free loopback port), so they never
touch a developer's real AO installation.

Two tiers

Tier What Where
Comprehensive (primary) A cross-platform Go suite that builds ao and exercises the full behaviour. Runs natively on ubuntu + macOS + windows — the only way to cover the OS-specific process-detach paths (setsid vs CREATE_NEW_PROCESS_GROUP) and os.UserConfigDir() resolution. backend/internal/cli/e2e_test.go (build tag e2e)
Fresh-install (hardening) Proves a freshly installed binary works on a clean machine with no Go toolchain and no developer state. test/cli/Dockerfile + test/cli/install-check.sh

Run it

The Go suite (fastest, cross-platform):

cd backend
go test -tags e2e ./internal/cli/...              # run it
go test -tags e2e -v -run TestE2E ./internal/cli/...   # verbose: prints every command + output

It builds its own ao binary; git must be on PATH (required by doctor).
-v logs each ao invocation and its full output, which is the audit trail you
get for free from go test.

Fresh-machine install, in a clean container:

docker build -f test/cli/Dockerfile -t ao-cli-smoke .
docker run --rm --init ao-cli-smoke

--init gives the container a real PID-1 reaper (tini) so the daemon the
check starts is reaped after stop instead of lingering as a zombie.

What the Go suite covers

TestE2E_VersionAndHelp (version/--version/help, daemon hidden) ·
TestE2E_DoctorDoesNotTouchTheStore (doctor text + --json; proves it does
not create/migrate ao.db) · TestE2E_StatusStopped (stopped + idempotent
stop) · TestE2E_Lifecycle (start, ready, idempotent, daemon-created store,
/healthz identity, stop, run-file cleanup) · TestE2E_ShutdownGuard (the
/shutdown CSRF + DNS-rebinding 403 guard, daemon survives) ·
TestE2E_StaleRunFile (dead-PID run-file → stale → cleaned) · TestE2E_ExitCodes
(2 usage / 1 runtime / config error) · TestE2E_Completion (all four shells).

Why a Go suite (not bash, not Python)

The bash version grew past the point where bash was a good fit, and a Linux
container can't observe the macOS/Windows code paths at all. A Go os/exec
suite is the right home: it uses the repo's own toolchain (runs under go test),
gives real assertions and structured data, and — critically — runs natively on
the Windows and macOS runners, finally covering the CREATE_NEW_PROCESS_GROUP
detach path and per-OS config-dir resolution. The container stays as a thin
"clean install actually works" check.

Extending

  • Add a case: a new TestE2E_* function (or a t.Run subtest) in
    e2e_test.go. Use newEnv(t) for isolated state and the env.run/httpGet/
    postShutdown helpers.
  • Add an OS: extend the matrix.os list in .github/workflows/cli-e2e.yml.
  • Deeper per-OS path assertions (state resolves under the OS-native config dir
    when AO_RUN_FILE/AO_DATA_DIR are unset) fit best as unit tests in
    internal/config.