atrium-bootstrap
- Repo stars 0
- Author updated Live
- Author repo skills-registry
- Domain
- Engineering
- Compatible agents
-
- Claude Code
- Cursor
- Cline
- Codex
- Windsurf
- Gemini CLI
- +20
- Trust score
- 88 / 100 · community maintained
- Author / version / license
- @tomevault-io · no license declared
- Token usage
- Moderate
- Setup complexity
- Manual integration
- External API key
- Required · Vendor-specific
- Operating systems
- Linux · Docker
- Runtime requirements
- Node.js · Python · Docker
- Permissions
-
- Read-only
- Write / modify
- Shell exec
- Env read
- Network behavior
- External requests
- Install commands
- 26 variants
Profile is derived at build time from SKILL.md and install vectors. Subject to drift from author intent.
Heads up: 未限定 allowed-tools,默认拥有全部工具权限。
---
name: atrium-bootstrap
description: Bootstrap a new host application on top of the published atrium base image, or retrofit an exist…
category: engineering
runtime: Node.js / Python / Docker
---
# atrium-bootstrap output preview
## PART A: Task fit
- Use case: Bootstrap a new host application on top of the published atrium base image, or retrofit an existing FastAPI / SQLAlchemy / React app onto atrium. Use when the user says "start a new project on atrium", "atrium scaffold", or "move my app onto atrium". Use when this capability is needed..
- Inputs: target material, constraints, expected output, and acceptance criteria.
- Evidence boundary: follow “Default to the scaffolder / Up-front decisions to confirm with the user / Procedure” and do not present inference as author intent.
## PART B: Execution result
- **01** The card summarizes the use case; runtime output centers on “Bootstrap a new host application on top of the published atrium base image, or retrofit an existing FastAPI / SQLAlchemy / React app onto atrium. Use when the user says "start a new project on atrium", "atrium scaffold", or "move my app onto atrium". Use when this capability is needed.”.
- **02** When the source has headings, the agent prioritizes “Default to the scaffolder / Up-front decisions to confirm with the user / Procedure” so the result follows the author’s structure.
- **03** Typical output includes task judgment, concrete steps, required commands or file edits, validation, and follow-up options.
- **04** Risk context follows the fingerprint: read files, write/modify files, run shell commands, read environment variables; may access external network resources; requires Vendor-specific API keys.
## Running Rules
- read files, write/modify files, run shell commands, read environment variables; may access external network resources; requires Vendor-specific API keys.
- Validate with a small sample before expanding scope.
- Return the result, validation criteria, and next iteration options. The source mentions slash commands such as `/api`, `/app`, `/opt`, `/host`; use them first when your agent supports command triggers.
Name target files or source material, expected output, forbidden changes, and whether network or shell access is allowed. Permission fingerprint: read files, write/modify files, run shell commands, read environment variables.
Start with a small task and check whether the result follows “Default to the scaffolder / Up-front decisions to confirm with the user / Procedure”. Inspect diffs, logs, previews, or tests before expanding scope.
Confirm the final output includes a concrete result, evidence, and next action. If it stays generic, tighten inputs, boundaries, and acceptance criteria.
---
name: atrium-bootstrap
description: Bootstrap a new host application on top of the published atrium base image, or retrofit an exist…
category: engineering
source: tomevault-io/skills-registry
---
# atrium-bootstrap
## When to use
- Bootstrap a new host application on top of the published atrium base image, or retrofit an existing FastAPI / SQLAlche…
- Use it when the task has clear inputs, repeatable steps, and validation criteria.
## What to provide
- Target material, scope, expected result, and forbidden changes.
- Whether network, commands, file writes, or external services are allowed.
## Execution rules
- Organize steps around “Default to the scaffolder / Up-front decisions to confirm with the user / Procedure” and keep inference separate from source facts.
- read files, write/modify files, run shell commands, read environment variables; may access external network resources; requires Vendor-specific API keys.
- Validate with a small sample before expanding the task.
## Output requirements
- Return the deliverable, key evidence, validation method, and next action.
- Mark missing information as unknown; do not invent commands, platforms, or dependencies. The author source anchors workflow facts; repository files anchor sources and commands; Fluxly only adds fit, limitations, and quality judgment.
skill "atrium-bootstrap" {
input -> user goal + target files + boundaries + acceptance criteria
context -> Default to the scaffolder / Up-front decisions to confirm with the user / Procedure
rules -> SKILL.md triggers / order / output contract
runtime -> Node.js / Python / Docker | read files, write/modify files, run shell commands, read environment variables | may access external network resources
guardrails -> requires Vendor-specific API keys + small-sample validation + diff/log review
output -> copyable result + checklist + next iteration
} Bootstrap a new project on top of atrium
Atrium ships as a Docker image (ghcr.io/<org>/atrium:<X.Y>). The host
project lives in its own repo, FROMs the atrium image, adds a backend
Python package + a frontend host bundle, and wires both in through a
narrow contract. Never edit atrium files — the next upgrade replaces
them.
The companion guide README.md in this directory is the
verbose reference (full file templates, retrofit playbook, gotchas). The
canonical worked example is ../../examples/hello-world/.
This SKILL.md is the procedure to follow when an agent is doing the
bootstrap.
Default to the scaffolder
For a vanilla host (single Python module, single frontend bundle, the standard extension shape), run the scaffolder rather than emitting files by hand:
npx @brendanbank/create-atrium-host <project-name> --yes-defaults --no-git
The emitted repo wires every registry slot, ships CI, and uses the
published @brendanbank/atrium-host-* packages — its frontend/src/main.tsx
is the post-foundation shape (~10 lines, no wrapper-element code). Skim
the emitted README and the demo Welcome* components, delete what
you don't need, and start adding your domain code.
Drop down to the manual procedure below only when the user explicitly asks for a non-default shape (multiple host packages in one repo, custom alembic layout, ship-your-own-SPA, retrofit of an existing codebase).
Example version-pinning.
examples/hello-world/tracksmasterand exercises whatever extension surface ships at HEAD. If the user pins their atrium image to an olderX.Ytag (sensible for prod), read the example at the matching git tag —git checkout vX.Y.Z -- examples/hello-world/— before copying snippets. The host registry tolerates calls to slots that don't exist in the running image (logs a console warning, the rest of the bundle continues registering), so a newer-than-image bundle degrades gracefully rather than catastrophically — but the slot still won't render.
Up-front decisions to confirm with the user
Before writing files, confirm:
- Project / image name (e.g.
bookings). Used as the docker image tag and project root. - Python module name (e.g.
bookings). Importable on PYTHONPATH; referenced byATRIUM_HOST_MODULE=<module>.bootstrap. Must not collide withapp(atrium owns it) or any pip-installed package. - Atrium image pin:
ghcr.io/<org>/atrium:<X.Y>for patch uptake,:<X.Y.Z>for fully deterministic deploys.:latestis for tinker only — refuse it for any branch the user calls "prod". - Public hostname + URL for
APP_BASE_URL,WEBAUTHN_RP_ID,WEBAUTHN_ORIGIN. Localhost is fine for local dev. - Whether they're retrofitting an existing app. If yes, ask which concerns atrium can take over (auth, audit, email, jobs, admin shell) so existing code can be deleted rather than ported.
Don't assume answers. Mismatched module names or the wrong image tag fail late and noisily.
Procedure
Below, <your-app> is the project name and <your_pkg> is the Python
module name. Replace both consistently.
1. Repo skeleton
mkdir -p <your-app>/{backend/src/<your_pkg>,backend/alembic/versions,frontend/src}
cd <your-app>
git init
Add a top-level .gitignore covering .env, node_modules, dist/,
__pycache__/, .venv/, *.pyc.
2. Backend host package
Create these files (full contents in README.md, section
Step 2):
backend/pyproject.toml— hatchling, name<your-app>-host, no runtime deps (atrium image provides them).backend/src/<your_pkg>/__init__.py— empty marker.backend/src/<your_pkg>/bootstrap.py— defineinit_app(app: FastAPI) -> Noneand optionallyinit_worker(host: HostWorkerCtx) -> None(from app.host_sdk.worker import HostWorkerCtx).init_appmounts your router.init_workerregisters job handlers viahost.register_job_handler(kind=..., handler=..., description=...)and APScheduler ticks viahost.scheduler.add_job(...). Either may be absent (atrium logshost.init_app.absent); both run loud if the module fails to import. Callregister_namespace(...)at module top-level, not insideinit_app— atrium importsbootstrap.pyfrom both the api and the worker process, but only the api callsinit_app. A namespace registered insideinit_appis invisible to worker handlers and everyget_namespace(session, "ns")from there willKeyError.backend/src/<your_pkg>/models.py— defineclass HostBase(DeclarativeBase)and your tables on it. Never parent host tables onapp.db.Base.backend/src/<your_pkg>/router.py— a normal FastAPI APIRouter. Useprefix="/api/<your_pkg>"so the SPA owns un-prefixed URL space (atrium issue #89). Imports atrium'scurrent_user(auth required) orrequire_perm("...")(permission required) for gating.
3. Alembic chain
backend/alembic.ini— uses%(here)ssoscript_locationresolves to the file's directory, not the caller's CWD.backend/alembic/env.py— copy from atrium'sbackend/alembic/env.pyand adjust:target_metadata = HostBase.metadata(host tables only)version_table = "alembic_version_app"(separate from atrium'salembic_version)- Add
process_revision_directives=emit_host_foreign_keys(fromapp.host_sdk.alembic) so cross-base FKs declared withHostForeignKeyland in autogenerated migrations. - Add an
include_objectfilter so autogenerate doesn't proposedrop_tableops for atrium's tables. See../host-models.mdfor the boilerplate.
backend/alembic/versions/0001_init.py—op.create_table(...)your domain tables, thenseed_permissions_sync(op.get_bind(), [codes], grants={"role": [codes]})for permissions.super_adminis auto-granted; unknown role codes warn and skip.
4. Frontend host bundle
The bundle is a separate Vite project that emits one ES module
(main.js) atrium dynamic-imports on SPA boot. Ship its own React,
ReactDOM, Mantine, TanStack Query — atrium's React stays out of the
host subtree.
frontend/package.json— react/react-dom 19, mantine 9, tanstack query 5, vite 8, typescript 6, tabler icons 3. Single script:vite build. Addvite-plugin-css-injected-by-jsas a devDependency — see vite.config.ts below.frontend/vite.config.ts— library mode (build.lib), single outputmain.js, defineprocess.env.NODE_ENVandprocess.envso React/TanStack don't throw on a missing process shim. Plugvite-plugin-css-injected-by-jsintoplugins: Vite's lib build extracts everyimport 'pkg/styles.css'into a sibling<bundle-name>.css, but atrium dynamic-importsmain.jsonly — the sibling never loads and the page renders unstyled. The plugin rewrites those imports to inject CSS via a runtime<style>tag, so a singlemain.jscarries everything. Add it preemptively even if the bundle has no CSS imports today: the failure mode (silently unstyled widgets) is annoying to diagnose later.frontend/src/main.tsx— the entry. Readswindow.Reactandwindow.__ATRIUM_REGISTRY__(both set by atrium's main.tsx before the dynamic import). Callsreg.register{HomeWidget,Route,NavItem,AdminTab,ProfileItem}.frontend/src/api.ts— plain fetch withcredentials: 'include'so the atrium auth cookie is sent. Default base URL/api.frontend/src/queryClient.ts— a singlenew QueryClient(...)shared across every component the bundle registers.- One
<Component>.tsxper slot — each wraps its tree in<MantineProvider>+<QueryClientProvider client={queryClient}>.
5. Two-React-trees pattern
The wrapper is mandatory:
const AtriumReact = (window as unknown as { React?: { createElement: (...a: unknown[]) => unknown } }).React;
type MountedEl = HTMLElement & { __hostRoot?: Root };
function mountInside(el: HTMLElement | null, child: React.ReactElement): void {
if (!el) return;
const slot = el as MountedEl;
if (slot.__hostRoot) return;
slot.__hostRoot = createRoot(slot);
slot.__hostRoot.render(child);
}
function makeWrapperElement(child: React.ReactElement): unknown {
return AtriumReact!.createElement('div', {
ref: (el: HTMLElement | null) => mountInside(el, child),
});
}
reg.registerHomeWidget({
key: 'your-widget',
render: () => makeWrapperElement(<YourWidget />),
});
Atrium's React creates the wrapper <div>; the bundle's createRoot
mounts inside it via the ref callback. Two reconcilers, no shared
hooks. The __hostRoot guard makes the ref idempotent under
StrictMode / route remounts.
Hooks-free elements (Tabler icons, plain SVG) can be passed directly to
atrium's React via AtriumReact.createElement(IconHome, { size: 18 })
— used for nav-item and admin-tab icons.
6. Dockerfile
Two-stage build: node-builder for the SPA, then FROM ${ATRIUM_IMAGE}
to install the host package and copy the bundle into the static dir:
ARG ATRIUM_IMAGE=ghcr.io/<org>/atrium:0.26
FROM node:25-alpine AS frontend-builder
WORKDIR /app
RUN npm install -g pnpm@10.33.1
COPY frontend/package.json frontend/pnpm-lock.yaml* ./
RUN pnpm install --frozen-lockfile 2>/dev/null || pnpm install
COPY frontend/ ./
ARG VITE_API_BASE_URL="/api"
ENV VITE_API_BASE_URL=${VITE_API_BASE_URL}
RUN pnpm build
FROM ${ATRIUM_IMAGE} AS runtime
USER root
COPY backend /opt/host_app
RUN /opt/venv/bin/python -m ensurepip --upgrade \
&& /opt/venv/bin/python -m pip install --no-cache-dir /opt/host_app
COPY --from=frontend-builder /app/dist /opt/atrium/static/host
USER app
7. docker-compose.yml
Three services: api, worker, mysql. Both api and worker share the
host image (the worker overrides CMD to python -m app.worker).
Required env vars on both api and worker:
ATRIUM_HOST_MODULE=<your_pkg>.bootstrapPYTHONPATH=/appDATABASE_URL,JWT_SECRET,APP_SECRET_KEY,APP_BASE_URLWEBAUTHN_RP_ID,WEBAUTHN_RP_NAME,WEBAUTHN_ORIGINMAIL_BACKEND,MAIL_FROM(andSMTP_*ifMAIL_BACKEND=smtp)
Disable healthcheck on the worker (healthcheck: { disable: true }) —
the shared image's HEALTHCHECK curls /api/healthz, which the worker
has no HTTP port for.
8. .env.example
Copy from README.md section Step 7 and fill secrets
with placeholders. Tell the user to generate APP_SECRET_KEY and
JWT_SECRET with openssl rand -hex 48. Don't commit .env — only
.env.example.
9. First-boot ritual
After the user confirms they've filled in .env:
docker compose up -d --build
docker compose exec api alembic upgrade head # atrium migrations
docker compose exec api alembic -c /opt/host_app/alembic.ini upgrade head # host migrations
docker compose exec api python -m app.scripts.seed_admin \
--email <user-email> --password <password> \
--full-name '<Their Name>' --super-admin
Then write system.host_bundle_url=/host/main.js so atrium loads the
bundle. The simplest approach is to copy
examples/hello-world/backend/src/atrium_hello_world/scripts/seed_host_bundle.py
into the host package as <your_pkg>/scripts/seed_host_bundle.py and
invoke:
docker compose exec api python -m <your_pkg>.scripts.seed_host_bundle /host/main.js
Or do it inline with the Python snippet in README.md
section Step 8.
9b. Backend tests in-container (optional)
The runtime image strips pytest, pytest-asyncio, and httpx. If the
host wants to run backend tests in the api container, install them on
demand or bake a dev Dockerfile stage. See README.md
section Running backend tests in-container for both patterns and a
Make target.
10. Verify
Open http://localhost:8000, log in with the seeded admin, and confirm
the registered widget(s) appear. If the bundle didn't load, check the
browser console for [atrium] host bundle failed to load (the SPA logs
the URL on failure). Common causes:
system.host_bundle_urlmissing or wrong (run the seed script).- Bundle path inside the image is wrong — should be at
/opt/atrium/static/host/main.jsso atrium serves it at/host/main.js. - Bundle threw at import time — check
[your_pkg]console errors; usuallywindow.Reactwas undefined, meaning the bundle script ran before atrium's main.tsx mounted (only happens if you<script>the bundle directly instead of going throughhost_bundle_url).
Retrofit cadence
When the user is bringing an existing app onto atrium (the booking-app-casa case):
- Stand up the empty host repo first. Do steps 1-9 above with a single placeholder model + endpoint, prove the round-trip works (login + bundle loads + endpoint reachable).
- Identify what atrium replaces. Walk the existing codebase with the user and tag every module as delete (atrium owns it) or port (domain code). Concerns atrium owns: auth, RBAC, audit, email pipeline, scheduled jobs, notifications, admin shell, theme, i18n, maintenance mode, account deletion. Don't port any of those.
- Port models module-by-module onto
HostBase. Drop anyusers/roles/permissions/auth_sessions/email_*/audit_log/scheduled_jobs/notifications/app_settingstables — atrium has them. - Port routers. Replace any custom auth middleware with
Depends(current_user)orDepends(require_perm("...")). - Port migrations as a single fresh
0001_init.py. Don't carry the old chain over. Dump existing data withmysqldump, load it afteralembic upgrade headif needed. - Port frontend pages as host-bundle exports. Drop the existing
app shell — atrium ships header, sidebar, login, profile, admin
tabs. Keep the domain components and call them from
registerRoute/registerNavItem/registerHomeWidget/registerAdminTab. - Move config into atrium's KV. Branding, feature flags,
translations, password policy, captcha config — move them into
app_settingsnamespaces (existing or new viaregister_namespace). Anything secret stays in.env.
The retrofit is done when:
- Every route in the existing app either lives in
<your_pkg>/router.pyor has been deleted because atrium covers it. - Every page in the existing SPA is either a host-bundle registration or has been deleted.
- All migrations are on the
alembic_version_appchain; the old chain is gone. - Tests pass against the new shape.
Reference: extension surface
# Backend
from app.auth.users import current_user # auth required
from app.auth.rbac import require_perm # permission required
from app.auth.rbac_seed import seed_permissions_sync # in alembic migration
from app.auth.rbac_seed import seed_permissions # at runtime startup
from app.db import get_session, get_session_factory # async ORM session
from app.models.auth import User # User type
from app.services.audit import record as record_audit # write audit row
from app.services.notifications import notify_user # in-app notification + SSE
from app.services.app_config import register_namespace # admin-tunable namespace
from app.email.sender import send_and_log, enqueue_and_log # email pipeline
from app.host_sdk.worker import HostWorkerCtx # init_worker(host) ctx type
from app.settings import get_settings # env-var settings
from app.logging import log # structlog logger
// Frontend (window.__ATRIUM_REGISTRY__)
registerHomeWidget({ key, render })
registerRoute({ key, path, element, requireAuth?, layout? })
registerNavItem({ key, label, to, icon?, condition? })
registerNavGroup({ key, label, icon?, condition?, order?, children })
registerAdminTab({ key, label, icon?, perm?, section?, order?, render })
registerProfileItem({ key, slot?, render, condition? })
registerNotificationKind({ kind, render, title?, href? })
subscribeEvent(kind, (evt) => { /* qc.invalidateQueries(...) */ })
Hard rules
- Never edit atrium files. Pin a tag, FROM the image, override through extension points only.
- Never share
Basebetween host and atrium. UseHostBase. - Cross-base FKs use
HostForeignKey. Column-levelForeignKey("users.id")(or any atrium-table reference) on aHostBasemodel fails at mapper-init — SQLAlchemy can't resolve across metadata. UseHostForeignKey("users.id", ondelete=...)fromapp.host_sdk.dband wireemit_host_foreign_keysfromapp.host_sdk.alembicinto the host'salembic/env.py. TheForeignKeyConstraintthen lands in autogenerated migrations automatically. See../host-models.mdfor the full pattern. - Never use atrium's alembic version table. Use
alembic_version_app. - Never bake secrets into the host bundle. It's served public. Authentication happens at API call time.
- Never share React, MantineProvider, or QueryClient between atrium and the bundle. Two trees. The wrapper-div pattern is non-negotiable.
- Never reimplement what atrium already ships (auth, RBAC, audit, email, jobs, notifications, admin shell, theme, i18n, maintenance, account deletion). If a concern lives in atrium, the host's only job is to call it.
When to escalate
Stop and ask the user when:
- They want to use a different DB (atrium is hard-wired to MySQL 8 + aiomysql).
- They want to ship a single-page app at a domain root that's not the
atrium SPA. The bundle pattern only handles fragments inside the
atrium shell. A standalone SPA is a separate concern (host project
fronts atrium's API at
/api/*and serves its own SPA). - They want to extend atrium's user model (e.g. add columns to
users). Atrium owns that table; host columns belong on a per-user row in a host-side table joined by user_id. - They want to override atrium's auth (e.g. SSO via OAuth/OIDC). Atrium ships fastapi-users with cookies; an SSO front-end isn't a host extension, it's a fork.
Source: brendanbank/atrium — distributed by TomeVault.
Decide Fit First
Design Intent
How To Use It
Boundaries And Review