create-ailk
v0.6.1
Published
Scaffolder for AI Launch Kit — `pnpm create ailk@latest <dir>` produces a starter repo consuming @working-theory/* from npm (per ADR 0007).
Maintainers
Readme
create-ailk
Scaffolder for AI Launch Kit. Produces a working
starter repo that consumes versioned @working-theory/* packages from npm — the
canonical install path per ADR 0007.
Usage
pnpm create ailk@latest my-site
cd my-site
pnpm install
pnpm devCompose the site from modules with --modules. The recognized vocabulary is
RECOGNIZED_MODULES in src/parse-args.ts — marketing, docs-blog,
multi-tenant, workspace, scheduling, signal-collectors, commerce —
and the default when the flag is absent is marketing (DEFAULT_MODULES, same
file):
pnpm create ailk@latest my-site --modules marketing,docs-blogEvery recognized module except marketing is a paid module, delivered under a
license key rather than shipped inside this package — see
Paid modules require AILK_LICENSE_KEY.
Sync architecture.yaml after adding a module
When a module is added to an already-scaffolded repo, regenerate its
architecture.yaml in place with --sync-architecture <csv> (recognized
modules are the same set as --modules):
# in an already-scaffolded repo (default target: cwd)
create-ailk --sync-architecture marketing,docs-blog
# or point at the repo explicitly
create-ailk --sync-architecture marketing,docs-blog ./my-siteSync mode is data-only and non-destructive: it rewrites only
architecture.yaml from the module set, re-copies no template tree, and runs
no install. Because deriveArchitecture is byte-deterministic, the synced file
is identical to a fresh scaffold's for the same module set — the idempotency
the deferred-sync signal contract depends on.
Sync template routes into an existing site
The template is copied once, at scaffold time. When a later release adds a
route directory (0.14.0 added /thanks/{slug} and the flow results page, for
example), a site scaffolded earlier does not pick it up on its own.
sync-routes reports the route directories a site is missing and, with
--apply, copies exactly those in:
# check — exit 1 if any template route is missing, 0 otherwise (default target: cwd)
create-ailk sync-routes ./my-site
# copy the missing route directories in
create-ailk sync-routes ./my-site --applyCheck mode lists each missing route with the surface that owns it and the
release it was added in (thanks/[slug] (surface: core, added in 0.14.0)).
It also lists routes modified locally — a route directory the site has,
whose files differ from what the template would write (an edited, added, or
deleted file). Modified routes are your edits: they are reported and left
as-is, they never fail the check, and --apply never overwrites them.
--apply only creates route directories that are absent, through the same
copier a fresh scaffold uses (so the copied files are byte-identical to a fresh
scaffold's). It never rewrites or deletes an existing file, never writes
outside apps/web/app/[locale]/, runs no install, and a second run is a
no-op. --apply exits 0 on success.
Scope. Every core route (about, contact, faq, thanks/[slug],
sign-in) is always checked, and so is the (authed) route GROUP —
layout.tsx + sign-in-gate.tsx, account/, and waitlist/ — even though
it is not itself a --modules-gated surface (#5014): a fresh scaffold ships
it unconditionally, the same as core, so sync-routes checks it
unconditionally too. It is reported under its own authed identity rather
than lumped in with core, so sync-routes's output distinguishes "always
present, and this is the sign-in/account shell" from "always present, and
this is the marketing core." A gateable surface's routes (blog, docs,
flows, …) are checked only when the site carries that surface; a surface
gated off at scaffold time is listed as skipped, not missing — its absence is
deliberate, not drift. Add a surface with --sync-architecture and the
scaffold-page skill, not with sync-routes.
The lib-and-messages dependency contract (#5014). Some routes import
files OUTSIDE their own route directory — sign-in/page.tsx imports
apps/web/lib/auth-env.ts and lib/site-brand.tsx; the (authed) group's
layout.tsx imports apps/web/auth.ts; (authed)/account/page.tsx imports
lib/account-config.ts, lib/server-api.ts, and two files from its
(authed)/waitlist sibling (components/ForbiddenState.tsx, format.ts).
Copying a route's directory alone would leave those imports dangling in an
older consumer that never had them. ROUTE_DEPENDENCIES in src/surfaces.ts
declares them per route; sync-routes copies each one --apply finds
missing (never overwriting — the same rule routes themselves follow) and
reports it in check mode under an Also required heading. ROUTE_MESSAGE_NAMESPACES
does the same for the i18n namespaces a route's copy needs
(signIn, account, waitlistDashboard.gate) — but CHECK-ONLY: a
consumer's apps/web/messages/<locale>.json already exists and may carry
hand-authored translations, so sync-routes reports a missing namespace
rather than attempting to merge JSON keys into it. Adding it is a manual
step, same as reading any other reported gap.
The manifest. ROUTE_SINCE in src/surfaces.ts maps every template route
directory (relative to apps/web/app/[locale]) to the @working-theory/*
fixed-group release line that first shipped it — the version a site pins, so
the report can say "added in 0.14.0". create-ailk's own version is not the
axis. 0.0.0 means the route predates the manifest and its first release is
not recoverable. Surface membership is not in the manifest: it is derived from
the route-surface registry in the same file.
Maintainer rule: adding a route directory to the template MUST add a
ROUTE_SINCE entry — __tests__/sync-routes.test.ts fails otherwise (and it
fails on a stale entry for a route the template no longer ships).
Paid modules require AILK_LICENSE_KEY
src/lib/module-tier.ts splits a requested module set into its free and paid
subsets. FREE_MODULES is the one hand-authored list — today marketing
alone — and PAID_MODULES is derived from it as every other
RECOGNIZED_MODULES entry, so the paid set is currently docs-blog,
multi-tenant, workspace, scheduling, signal-collectors and commerce.
A module added to the vocabulary is paid until someone deliberately adds it to
FREE_MODULES.
Paid modules are not published inside this npm package: their bundles are
fetched at scaffold time. resolvePaidModules in
src/lib/module-license-gate.ts reads AILK_LICENSE_KEY from the environment
(or an injected licenseKey option) and refuses the run when it is unset or
empty, naming the paid modules requested and pointing at
https://ailaunchkit.ai/pricing. An invalid or lapsed key is refused the same way,
from the module distribution's own 401/403 (src/lib/fetch-module.ts). The
gate runs before anything is copied (scaffold() in src/programmatic.ts), so
a refused run leaves no partial scaffold behind.
AILK_MODULE_REGISTRY_URL is optional: fetchModule uses an explicitly passed
registryUrl, then that variable, then the built-in
DEFAULT_AILK_MODULE_REGISTRY_URL (src/lib/fetch-module.ts).
# free-only — no key read, no network call
pnpm create ailk@latest my-site --modules marketing
# paid — the key is read from the environment
AILK_LICENSE_KEY=... pnpm create ailk@latest my-site --modules marketing,docs-blogA request that names no paid module never reaches the key check at all:
resolvePaidModules returns an empty map on its first branch, before touching
process.env or the network, so a free-only --modules request reads no env
var and makes no network call (src/lib/module-license-gate.ts).
What it does
- Validates the target directory does not exist OR is empty.
- Copies the curated template tree (
apps/{web,api,mcp}+database/+content/+ root config files) from the package'stemplates/directory. - Substitutes
workspace:*references in everypackage.jsonwith the matching@working-theory/*semver from npm — versions are pinned at npm-publish time. - Substitutes
{{PROJECT_NAME}},{{COMPANY_NAME}},{{COMPANY_DOMAIN}}markers intemplates/README.mdfrom the target directory name (and CLI flags / interactive prompts in future versions). - Derives and writes
architecture.yaml(the site's code-boundary config) from the selected module set — see below. - Prints next steps:
cd <dir> && pnpm install && pnpm dev.
architecture.yaml is derived from the module set
The scaffolded site's architecture.yaml (the apps/packages/namespaces the
architecture linter walks + the import rules it enforces) is derived from the
selected --modules, not copied from a fixed template. The module set is the
single source of truth for the site's code-architecture boundaries:
- A module → architecture-slice registry (
src/module-architecture.ts) maps each recognized module to the boundary fragment it contributes. deriveArchitecture(modules)(src/derive-architecture.ts, also exported from the package index) composes a module-independent base with the selected slices into one deterministic document. Same module set → byte-identical output.- The scaffolder writes the derived file at the target root, satisfying the
scaffolded
project.yaml'sarchitecture_rules: architecture.yamlreference.
Sync on module-add is the same derivation re-run over the current module set
— idempotent (re-deriving an unchanged set is a no-op), so adding a module later
and re-deriving updates architecture.yaml to match. deriveArchitecture is
exported for that re-derivation. MODULE_SLICES in src/module-architecture.ts
declares a slice for every recognized module.
Templates are snapshotted at build time
The prepublishOnly script (scripts/build-templates.mjs) reads the canonical
sources from this monorepo (apps/, database/, content/, root config) and
populates templates/. The npm tarball ships the populated templates via the
files field. A free-only scaffold performs no network access at scaffold
time; only pnpm install (run by the user after scaffolding) hits the npm
registry. A run naming a paid module fetches that module's bundle — see
Paid modules require AILK_LICENSE_KEY.
Repository
Source: https://github.com/working-theory-labs/ai-launch-kit
Issues: https://github.com/working-theory-labs/ai-launch-kit/issues
License
Apache-2.0. See LICENSE.
