图像迁移
- 作者仓库星标 0
- 作者更新于 实时读取
- 作者仓库 skills-registry
- 领域
- 工程开发
- 兼容 Agent
-
- Claude Code
- Cursor
- Cline
- Codex
- Windsurf
- Gemini CLI
- +20
- 信任分
- 88 / 100 · 社区维护
- 作者 / 版本 / 许可
- @tomevault-io · 未声明 license
- Token 消耗评级
- 中等消耗
- 接入复杂程度
- 需手动接入
- 是否需要外部 API Key
- 需要 · Vendor-specific
- 兼容的系统
- Linux · Docker
- 底层运行要求
- Node.js · Python · Docker
- 文件与系统权限
-
- 只读
- 允许写入 / 修改
- Shell 执行
- 读取环境变量
- 网络行为
- 允许外网请求
- 安装命令数
- 26 条
档案由构建时根据 SKILL.md 与安装命令自动衍生,可能与作者实际意图存在差异。
需要注意: 未限定 allowed-tools,默认拥有全部工具权限。
---
name: atrium-bootstrap
description: Bootstrap a new host application on top of the published atrium base image, or retrofit an exist…
category: 工程开发
runtime: Node.js / Python / Docker
---
# atrium-bootstrap 输出预览
## PART A: 任务判断
- 适用问题:代码实现、重构、调试或代码审查。
- 输入要求:目标材料、限制条件、期望输出和验收方式。
- 证据边界:围绕“Default to the scaffolder / Up-front decisions to confirm with the user / Procedure”读取原文规则,不把推断写成作者承诺。
## PART B: 执行结果
- **01** 任务判断:确认你的需求是否属于代码实现、重构、调试或代码审查,并标出输入、限制和预期结果。
- **02** 执行计划:优先按“Default to the scaffolder / Up-front decisions to confirm with the user / Procedure”拆成步骤,说明每一步会读取什么、修改什么、产出什么。
- **03** 交付结果:给出可复制的命令、文件改动、检查清单或内容草稿,并说明如何继续迭代。
- **04** 风险边界:结合 读取文件、写入/修改文件、执行终端命令、读取环境变量、会按任务需要访问外部网络、需要准备 Vendor-specific API Key 给出执行前确认项。
## Running Rules
- 读取文件、写入/修改文件、执行终端命令、读取环境变量;会按任务需要访问外部网络;需要准备 Vendor-specific API Key。
- 先小样例验证,再放大到真实任务。
- 交付时同时给结果、检查口径和下一步迭代建议。 原文出现了 `/api`、`/app`、`/opt`、`/host` 这类斜杠命令;如果你的 Agent 支持命令触发,优先用命令开场,再补充目标和边界。
告诉 Agent 目标文件或材料、期望结果、不可改范围、是否允许联网或执行命令。本 Skill 的权限画像是:读取文件、写入/修改文件、执行终端命令、读取环境变量。
先用一个小任务确认它会围绕“Default to the scaffolder / Up-front decisions to confirm with the user / Procedure”工作;涉及文件或命令时,先看 diff、日志、预览或测试结果。
检查最终产物是否包含明确结果、必要证据和下一步动作;如果输出泛泛而谈,就补充输入、边界和验收标准后重跑。
---
name: atrium-bootstrap
description: Bootstrap a new host application on top of the published atrium base image, or retrofit an exist…
category: 工程开发
source: tomevault-io/skills-registry
---
# atrium-bootstrap
## 什么时候使用
- 把工程方向的常用动作沉淀成 Agent 可调用的技能 适合处理工程开发场景下的代码实现、调试、重构、测试或代码审查,核心价值是把输入、判断、执行、验证和交付边界固定下来,避免 Agent 泛泛回答。 把任务拆成可执行、可检查、可继续迭代…
- 面向代码实现、重构、调试或代码审查,优先处理能明确输入、步骤和验收标准的工作。
## 需要提供什么
- 目标材料、目录范围、期望结果和不可改动内容。
- 是否允许联网、执行命令、读写文件或调用外部服务。
## 执行规则
- 围绕「Default to the scaffolder / Up-front decisions to confirm with the user / Procedure」组织步骤,不把推断写成作者事实。
- 读取文件、写入/修改文件、执行终端命令、读取环境变量;会按任务需要访问外部网络;需要准备 Vendor-specific API Key。
- 先跑小样例,确认结果可检查后再扩大任务范围。
## 输出要求
- 给出最终产物、关键证据、验证方式和下一步动作。
- 信息不足时标记 unknown,不编造命令、平台或依赖。 作者原文负责流程事实;仓库文件负责来源和命令;流狐只补充适用场景、限制和质量判断。
skill "atrium-bootstrap" {
输入层 -> 用户目标 + 目标文件 + 禁止范围 + 验收标准
上下文层 -> Default to the scaffolder / Up-front decisions to confirm with the user / Procedure
规则层 -> SKILL.md 触发条件 / 执行顺序 / 输出格式
运行层 -> Node.js / Python / Docker | 读取文件、写入/修改文件、执行终端命令、读取环境变量 | 会按任务需要访问外部网络
安全层 -> 需要准备 Vendor-specific API Key + 小任务验证 + diff / 日志复核
输出层 -> 可复制结果 + 检查清单 + 下一步迭代
} 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.
先判断是否适合
作者设计意图
作者的方法与取舍
边界和复核