Skip to content

feat(init): scaffolded apps carry their own backend — init, npm start, done - #219

Merged
AbirAbbas merged 5 commits into
mainfrom
feat/init-bundled-backend
Sep 2, 2026
Merged

feat(init): scaffolded apps carry their own backend — init, npm start, done#219
AbirAbbas merged 5 commits into
mainfrom
feat/init-bundled-backend

Conversation

@AbirAbbas

Copy link
Copy Markdown
Contributor

Summary

af-stack init my-ai-product then npm start still failed after #217: the scaffolded app had no backend of its own, and nothing in the docs said where one should come from. The starter only ever called a BackAI; on a machine where port 8080 belongs to something else (an AgentField control plane, in the report) it got that server's 404. The product promise is one bundled thing: init, start, done.

Now the scaffold carries its backend. af-stack init <name> writes a docker-compose.yml and a backend/ directory that boot BackAI from the published release images pinned to the CLI's own version (Postgres, MinIO, LiteLLM, the AgentField control plane, the runtime, the operator dashboard, and the supportdesk demo agent). af-stack dev runs inside that app: it allocates free host ports when the defaults are busy, runs docker compose up -d, waits for the runtime's /ready and the demo agent's registration, and writes AF_STACK_URL into the app's .env. npm start runs it first through a prestart hook, then the app lists the registered agents and calls supportdesk.echo. No clone is involved; Docker is the only prerequisite.

Changes

  • starter package (services/cli/internal/starter): the compose stack and the two files it mounts (postgres-init.sh, litellm-config.yaml, copied from the repo and pinned to it by a drift test), the image tag mapping (released CLI → its version, dev build → latest), a Go port of the checkout's port preflight (busy foreign port moves up, a port bound by our own compose project stays, COMPOSE_PROJECT_NAME from the directory), .env read/write that seeds from .env.example, and the readiness waits.
  • af-stack dev in a scaffolded app: when no checkout encloses the directory but it is an app written by init <name>, dev boots the bundled backend instead of demanding a clone. Apps scaffolded by an older CLI get the backend files written on first dev. Always detached; it returns once the backend is ready, which is what the npm hook needs. A runtime that never becomes ready is reported with its last /ready status and the log command.
  • Scaffolds: both templates carry the backend; node gets prestart/backend/backend:stop scripts and its starter now calls supportdesk.echo and prints the reply; saas gets a predev hook and VITE_AF_STACK_URL is kept in step. READMEs, CLAUDE.md, .env.example, and the printed next steps describe the self-contained path. The starter's failure text now says to run af-stack dev here.
  • Release workflow: publishes a fourth image, af-stack-supportdesk-agent, and adds a second smoke that builds the CLI at the release version, scaffolds an app, runs npm start, and refuses to publish unless the echo reply comes back. Before that it asserts each image is pullable without a registry login, and logs out of GHCR so the smoke pulls the way a user does.
  • Docs: README, dx hub, run.md, cli-distribution.md, cli-admin.md, and the skill now describe the two init forms accurately: positional init <name> is a complete app with a bundled backend; init --name brands a fork inside a clone.

Verification

  • Contract tests: support files match the repo's; compose is valid YAML, every entry in the port table is published through its .env key, every service is image-based; EnsureBackend is idempotent and never overwrites an edited compose file; port allocation moves only foreign conflicts and never hands two services the same port; .env writes replace in place and seed from the example; WaitReady/WaitAgent against fake runtimes. dev in a scaffolded app is driven with a stub docker on PATH and an httptest runtime: writes the backend, runs compose up -d, keeps the port our project holds, writes AF_STACK_URL, prints the map; the unready case, the no-docker case, and the Vite key are pinned. The node starter is run with real node against a fake runtime that serves the runtime's {"agents":[…]} shape and the echo endpoint.
  • Live, with real Docker on a machine whose 8080 and 8081 are held by a foreign AgentField control plane and whose 5432 is busy: af-stack init my-ai-product && cd my-ai-product && npm install && npm start boots the stack (ports moved to 8082/8083/5434), prints Registered agents: supportdesk and Echo agent replied: {"message":"hello from http://localhost:8082"}; a second npm start is a 2-second no-op; the dashboard answers on 33000. Run in an app scaffolded by the released 0.12.6, af-stack dev writes the backend files and boots it. Local copies of the runtime/dashboard/agent images stood in for the registry (see below).
  • go build, go vet, full go test ./services/cli/...; golangci-lint v2.13.1 in CI's new-issues mode: 0 issues. Prettier warnings on five touched docs files are pre-existing at main (identical output on the HEAD copies).

Blocker for the released path (owner action)

The GHCR packages af-stack-runtime, af-stack-dashboard, af-stack-customer-app are private: an anonymous manifest fetch returns 403 while a public image on the same registry returns 200, and docker pull is denied. Nothing scaffolded by a released CLI can pull them until each package is set to Public in the org's package settings (GitHub has no API for this). docker-compose.prod.yml has had the same problem all along. The new release gate fails with that instruction until it is done; the supportdesk-agent package will be created private by the first release that builds it and needs the same flip, then a re-run of the smoke job.

🤖 Generated with Claude Code

AbirAbbas and others added 5 commits September 2, 2026 14:16
A compose stack that boots BackAI from the published release images,
pinned to the CLI's own version (a dev build pins :latest), plus what
running it outside a checkout needs: a Go port of the port preflight
(foreign conflicts move up, ports our own compose project holds stay),
.env read/write seeded from .env.example, and readiness waits on the
runtime and the demo agent. The two mounted files are copies of the
repo's, pinned to it by a drift test. buildinfo carries the version
main sets from its ldflag so init can pin images to the binary.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Abir Abbas <abirabbas1998@gmail.com>
… app

When no checkout encloses the directory but it is an app written by
`af-stack init <name>`, dev no longer demands a clone: it writes any
missing backend files, allocates free host ports into .env, runs
docker compose up -d, waits for /ready and for the supportdesk agent
to register, writes AF_STACK_URL (and VITE_AF_STACK_URL for the saas
template) into .env, and prints where things are. Always detached, so
npm's prestart hook can run it. A runtime that never becomes ready is
reported with its last status and the log command; a missing docker
says what to install.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Abir Abbas <abirabbas1998@gmail.com>
…start

Both templates get docker-compose.yml + backend/ from the starter
package. The node app runs af-stack dev through a prestart hook (plus
backend / backend:stop scripts) and its starter now calls
supportdesk.echo and prints the reply; the saas app gets a predev hook.
.env.example, README, CLAUDE.md, the failure text, and the printed next
steps describe the self-contained path instead of a backend from a
clone. The node starter tests use the runtime's {"agents":[...]} shape
and an echo endpoint.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Abir Abbas <abirabbas1998@gmail.com>
…lded app

The images matrix gains af-stack-supportdesk-agent (its Dockerfile needs
its own directory as context). A second smoke builds the CLI at the
release version, scaffolds an app, runs npm start, and refuses to
publish unless the echo reply comes back. Before it, an anonymous
manifest fetch asserts each image is pullable with no registry login,
because GHCR packages are private by default and a scaffold from a
released CLI pulls exactly that way; the runner logs out of GHCR so the
smoke pulls like a user.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Abir Abbas <abirabbas1998@gmail.com>
README, the dx hub and run.md, cli-distribution, cli-admin, and the
skill describe the two init forms as they now behave: the positional
form scaffolds an app that boots BackAI from the release images (no
clone; Docker required), the flag form brands a fork inside a clone.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Signed-off-by: Abir Abbas <abirabbas1998@gmail.com>
@AbirAbbas
AbirAbbas merged commit e989626 into main Sep 2, 2026
30 checks passed
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.

1 participant