Skip to content

feat(distribution): one-command web UI install via lazy-fetch from release tarball #978

@Wirasm

Description

@Wirasm

Problem

Today's distribution split forces a hard choice on users:

Option What you get What you need to install
brew install coleam00/archon/archon CLI only (workflow run, isolation, setup, etc.) Nothing else
Clone + bun install && bun dev CLI and web UI Bun, Node toolchain, node_modules, the whole monorepo, build steps

There is no path to "install and immediately get the web UI in one command". The compiled CLI binary only includes packages/cli/src/cli.ts — no server, no web dist/, no archon serve subcommand. Users who want to evaluate Archon by clicking around the web UI must clone the entire monorepo. That's a big step for "I just want to try this".

The web UI is the most discoverable part of the product. Burying it behind a clone-and-build step is a significant adoption tax.

First-principles framing

Why is the web UI not in the binary?

  • Size: the Vite-built packages/web/dist/ is ~1-2 MB. The server code is another ~5-10 MB compiled. Bundling everything would push the binary from ~50 MB → ~70 MB. Acceptable but not free.
  • Update cadence: web UI fixes would be locked to binary releases. Users would have to brew upgrade to get a CSS bug fix.
  • Build complexity: embedding Vite output into a Bun-compiled binary requires asset bundling that the current scripts/build-binaries.sh doesn't do.

Why is cloning bad as the alternative?

  • Requires Bun installed at the right version
  • Requires network for bun install (~2274 packages)
  • Requires successful build step
  • Requires the user to understand monorepos
  • Failure modes: lockfile drift, bun version mismatch, transient registry errors, disk space, etc.
  • The user just wanted to look at the UI

Considered alternatives

Option Approach Verdict
A. Bundle everything in one binary Embed web dist/ and server code in the compiled archon binary; add archon serve Bloats CLI-only users; ties web UI to binary version; complicates the build
B. `archon install web` clones + builds New CLI command that clones the repo to ~/.archon/install/ and runs bun install && bun run build Same problems as the current clone path, just hidden behind a CLI command. Doesn't actually solve the friction.
C. Two binaries (`archon`, `archon-server`) Separate compiled targets for CLI vs full-stack Doubles release artifacts; user has to choose; clean separation but heavy maintenance
D. Docker only docker run -p 3090:3090 ghcr.io/coleam00/archon:latest Already exists; great for VPS deploys; overkill for local Mac use

None of these are clearly right. CLI-only users get penalized by A. B doesn't solve anything. C doubles the maintenance burden. D doesn't help non-Docker users.

Proposed approach — Option E: lazy-fetch web UI on first `serve`

Keep the CLI binary small. Add an archon serve subcommand. The first time the user runs it, the binary checks if ~/.archon/web-dist/<binary-version>/ exists. If not, it downloads the pre-built web UI tarball from the same GitHub release as the binary, extracts it, and starts the server.

```bash
brew install coleam00/archon/archon
archon serve

[INFO] Web UI not yet downloaded — fetching from release v0.2.13...

[INFO] Downloading https://github.com/coleam00/Archon/releases/download/v0.2.13/web-dist-v0.2.13.tar.gz (1.4 MB)

[INFO] Verifying checksum...

[INFO] Extracted to ~/.archon/web-dist/v0.2.13/

[INFO] Server starting on http://localhost:3090

```

Subsequent archon serve invocations skip the download and start immediately.

Why this is better than A through D

  • CLI users pay nothing: binary stays ~50 MB, no embedded assets, no startup cost if you never run serve
  • Web UI users get one command: brew install && archon serve — done
  • Web UI version stays in sync with binary version: no drift, no "my CLI is v0.3 but my UI is v0.2" debugging
  • Easy rollback: each binary version has its own ~/.archon/web-dist/<version>/ directory; downgrading the binary uses the matching UI
  • Easy upgrade: brew upgrade && archon serve — first run after upgrade fetches the new UI
  • Can pre-cache: archon serve --download-only for offline-first or air-gapped installs
  • No clone, no Bun, no node_modules: dependency-free for users
  • Network failure is recoverable: clear error "failed to download web UI", user retries; no half-broken state
  • Honest about cost: the latency happens once and is visible

Tradeoffs we're accepting

  • First-run latency: ~2-5 seconds to download + extract a 1-2 MB tarball. One time per binary version. Acceptable.
  • Network dependency on first serve: needs internet at install time. Acceptable for the target audience (developers).
  • Needs to publish web UI tarball alongside binaries in each release. Small CI change.
  • Still need to embed a small server in the binary. The static file serving + the existing API routes need to be compiled in. This is the actual engineering work — see implementation sketch.

Implementation sketch

1. Build pipeline (CI changes)

.github/workflows/release.yml already builds 5 platform binaries on tag push. Add:

```yaml

  • name: Build web UI
    run: bun --filter @archon/web run build

  • name: Package web dist
    run: tar czf web-dist-${{ github.ref_name }}.tar.gz -C packages/web dist

  • name: Compute checksum
    run: shasum -a 256 web-dist-${{ github.ref_name }}.tar.gz >> checksums.txt

  • name: Upload to release
    uses: softprops/action-gh-release@v2
    with:
    files: |
    web-dist-${{ github.ref_name }}.tar.gz
    ```

This produces a single platform-independent web UI tarball per release. Same artifact for all OSes (web is just static files).

2. CLI binary changes

Add archon serve command in packages/cli/src/commands/serve.ts:

```typescript
export async function serveCommand(opts: { port?: number, downloadOnly?: boolean }) {
const version = BUNDLED_VERSION;
const webDistDir = join(getArchonHome(), 'web-dist', version);

if (!existsSync(webDistDir)) {
await downloadWebDist(version, webDistDir);
}

if (opts.downloadOnly) {
log.info({ webDistDir }, 'web_dist_ready');
return;
}

// Start the server pointing at the downloaded web-dist
await startServer({ webDistPath: webDistDir, port: opts.port ?? 3090 });
}
```

3. Server changes

The current server (packages/server/src/index.ts) reads its web dist/ from import.meta.dir-relative paths. Add a constructor parameter / config option webDistPath so the same server code can be invoked from the CLI binary pointing at any directory.

This is the biggest engineering chunk — the server needs to be importable as a library, not just runnable as a script. It currently does a lot of work at module-load time (reading config, setting up adapters, etc.). Refactor into a startServer(opts) function.

4. Bundle the server into the CLI binary

scripts/build-binaries.sh currently compiles only packages/cli/src/cli.ts. The cli now needs to import startServer from packages/server/src/index.ts, which means the bun compile pulls in:

  • All of @archon/server
  • All of @archon/core (already pulled in)
  • Hono, Pino, all the SSE infrastructure
  • The platform adapters (or make those optional via lazy import — probably should be lazy)

Estimated binary size growth: from ~50 MB → ~60-65 MB. Significant but acceptable.

5. Download + checksum logic

```typescript
async function downloadWebDist(version: string, targetDir: string) {
const baseUrl = https://github.com/coleam00/Archon/releases/download/${version};
const tarballUrl = ${baseUrl}/web-dist-${version}.tar.gz;
const checksumsUrl = ${baseUrl}/checksums.txt;

// Download tarball + checksums
// Verify SHA256
// Extract to ~/.archon/web-dist//
// Atomic move (write to .tmp, rename on success)
}
```

Reuse the same checksum verification pattern as scripts/install.sh.

6. Migration / rollout

  • Phase 1 (this issue): ship archon serve that lazy-fetches. Document in README.
  • Phase 2: update Homebrew formula to mention that archon serve gets you the web UI.
  • Phase 3 (optional): add a archon serve --no-download for air-gapped installs that errored out previously.

Files likely to change

File Change
New: packages/cli/src/commands/serve.ts archon serve command implementation
New: packages/cli/src/utils/web-dist-fetcher.ts Download + extract + verify logic
packages/cli/src/cli.ts Wire serve into the command parser
packages/server/src/index.ts Refactor into importable startServer(opts) function
packages/server/src/adapters/web/static.ts (or wherever static serving lives) Accept webDistPath as parameter instead of computing from import.meta.dir
scripts/build-binaries.sh No change — the bun compile picks up new imports automatically
.github/workflows/release.yml Add web UI build + tarball upload steps
homebrew/archon.rb Add release note in formula description; no formula change
packages/docs-web/.../installation.md Document archon serve as the one-command web UI path
Tests Cover download success, checksum failure, extraction, version mismatch, network error retry

Out of scope

  • Bundling the server code into the binary as a library — that's the core engineering work and is part of this issue, not deferred
  • Updating the dev clone path (bun dev) — that stays as-is for contributors
  • Removing the existing curl-installer / Homebrew formula — they continue to work
  • Auto-update of cached web-dist when a new binary version is released — the version-keyed directory handles this naturally
  • Mirroring the web tarball to a CDN — GitHub releases is sufficient for now
  • Bundling the database migrations differently — they're already in the binary

Success criteria

  • New macOS user runs brew install coleam00/archon/archon && archon serve and within 30 seconds has a working web UI on http://localhost:3090
  • Same flow works on Linux (curl install.sh then archon serve)
  • CLI-only users (archon workflow run, archon isolation, etc.) see no behavior change and no binary size jump beyond what server bundling adds
  • Cached web-dist persists across archon serve invocations
  • brew upgrade followed by archon serve correctly fetches the new web UI for the new binary version
  • Network failure during download produces an actionable error message and clean retry

Effort

Medium-high. ~3-5 days of focused work:

  • Server library refactor: 1-2 days (the biggest unknown)
  • Download + checksum logic: 0.5 day
  • CLI wiring: 0.5 day
  • CI release pipeline changes: 0.5 day
  • Tests: 1 day
  • Docs + smoke tests across platforms: 0.5 day

The server refactor is the hard part. Everything else is mechanical.

Related

Discussion welcome

This is filed as a design proposal, not a directive. Open questions worth discussing before implementing:

  1. Server-as-library refactor: how invasive will it be? The current index.ts does a lot at module-load. Is it worth doing for this feature alone, or should we wait until there's another reason?
  2. Versioned vs latest tarball: should archon serve always fetch its own version, or should it support archon serve --web-version=latest for users who want UI fixes without binary upgrades?
  3. Tarball location: GitHub releases vs CDN. For now GitHub is fine; CDN if download volumes get high.
  4. Air-gapped installs: provide an archon serve --offline --web-dist=./path escape hatch from the start, or add it later?
  5. Should we deprecate the clone-and-bun-dev path for end users (keep it for contributors only)? Or document both equally?

Metadata

Metadata

Assignees

No one assigned

    Labels

    P3Low priority - Nice to have, consider closing if stalearchitectureArchitectural changes and designfeatureNew functionality (planned)

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions