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] Verifying checksum...
[INFO] Extracted to ~/.archon/web-dist/v0.2.13/
```
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:
- 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?
- 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?
- Tarball location: GitHub releases vs CDN. For now GitHub is fine; CDN if download volumes get high.
- Air-gapped installs: provide an
archon serve --offline --web-dist=./path escape hatch from the start, or add it later?
- Should we deprecate the clone-and-bun-dev path for end users (keep it for contributors only)? Or document both equally?
Problem
Today's distribution split forces a hard choice on users:
brew install coleam00/archon/archonworkflow run,isolation,setup, etc.)bun install && bun devnode_modules, the whole monorepo, build stepsThere 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 webdist/, noarchon servesubcommand. 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?
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.brew upgradeto get a CSS bug fix.scripts/build-binaries.shdoesn't do.Why is cloning bad as the alternative?
bun install(~2274 packages)Considered alternatives
dist/and server code in the compiledarchonbinary; addarchon serve~/.archon/install/and runsbun install && bun run builddocker run -p 3090:3090 ghcr.io/coleam00/archon:latestNone 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 servesubcommand. 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 serveinvocations skip the download and start immediately.Why this is better than A through D
servebrew install && archon serve— done~/.archon/web-dist/<version>/directory; downgrading the binary uses the matching UIbrew upgrade && archon serve— first run after upgrade fetches the new UIarchon serve --download-onlyfor offline-first or air-gapped installsTradeoffs we're accepting
serve: needs internet at install time. Acceptable for the target audience (developers).Implementation sketch
1. Build pipeline (CI changes)
.github/workflows/release.ymlalready 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 servecommand inpackages/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 webdist/fromimport.meta.dir-relative paths. Add a constructor parameter / config optionwebDistPathso 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.shcurrently compiles onlypackages/cli/src/cli.ts. The cli now needs to importstartServerfrompackages/server/src/index.ts, which means the bun compile pulls in:@archon/server@archon/core(already pulled in)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
archon servethat lazy-fetches. Document in README.archon servegets you the web UI.archon serve --no-downloadfor air-gapped installs that errored out previously.Files likely to change
packages/cli/src/commands/serve.tsarchon servecommand implementationpackages/cli/src/utils/web-dist-fetcher.tspackages/cli/src/cli.tsserveinto the command parserpackages/server/src/index.tsstartServer(opts)functionpackages/server/src/adapters/web/static.ts(or wherever static serving lives)webDistPathas parameter instead of computing fromimport.meta.dirscripts/build-binaries.sh.github/workflows/release.ymlhomebrew/archon.rbpackages/docs-web/.../installation.mdarchon serveas the one-command web UI pathOut of scope
bun dev) — that stays as-is for contributorsSuccess criteria
brew install coleam00/archon/archon && archon serveand within 30 seconds has a working web UI onhttp://localhost:3090curl install.shthenarchon serve)archon workflow run,archon isolation, etc.) see no behavior change and no binary size jump beyond what server bundling addsarchon serveinvocationsbrew upgradefollowed byarchon servecorrectly fetches the new web UI for the new binary versionEffort
Medium-high. ~3-5 days of focused work:
The server refactor is the hard part. Everything else is mechanical.
Related
scripts/install.sh,homebrew/archon.rb) — both work today and should continue workingDiscussion welcome
This is filed as a design proposal, not a directive. Open questions worth discussing before implementing:
index.tsdoes a lot at module-load. Is it worth doing for this feature alone, or should we wait until there's another reason?archon servealways fetch its own version, or should it supportarchon serve --web-version=latestfor users who want UI fixes without binary upgrades?archon serve --offline --web-dist=./pathescape hatch from the start, or add it later?