# create-excalibur ## Scaffolding for ExcaliburJS projects With NPM: ``` npx create-excalibur@latest ``` ![example running create excalibur](./create-excalibur.gif) ## The `ex` CLI Installing the package globally also gives you the `ex` command: ``` npm i -g create-excalibur # installs `ex` (and an `excalibur` alias) ex # interactive menu (same as create-excalibur) ex create # scaffold a game from a template ex sample # scaffold a sample project ex inspect # download a showcase game ex docs # search the Excalibur docs & API ex generate # generate an actor, label, scene, resource, engine settings, material, spritesheet, or animation — or update an actor's options (alias: ex g) ex doctor # type-aware diagnostics: actors never added to a scene, unnamed actors ex upgrade # migrate your game to a newer Excalibur version, codemod-style (alias: ex up) ex mcp # MCP server over stdio (docs + codegen tools for AI agents) ``` ### `ex docs` — search the docs from your terminal ``` ex docs # type-as-you-search prompt ex docs actor collision # search, pick a result, read it in the terminal ex docs Vector.distance -1 # open the top result immediately (no picker) ex docs actor collision --list # just print the matches + links (pipe friendly) ex docs vector --json # machine-readable results ex docs --help # all options ``` Search is powered by the excaliburjs.com DocSearch (Algolia) index. Pages are rendered as markdown in the terminal with links back to the online docs; long pages open in `$PAGER`/`less`. **Version aware:** when run inside a project, `ex docs` detects the installed `excalibur` version (from `node_modules` or `package.json`) and renders pages from that release's docs (`--ref v0.32.0` to override; `--ref main` for the latest). **Offline:** ``` ex docs offline # download the docs for your Excalibur version (~1 MB) + plugin READMEs + build a local index ex docs actor --offline # search the downloaded docs only ex docs offline --status # what's cached and where (~/.excalibur/docs, or $EXCALIBUR_HOME) ex docs offline --clear # remove the cache ``` When the network is unavailable, `ex docs` falls back to the offline index automatically. **Plugins:** `ex docs offline` also indexes the `@excaliburjs/plugin-*` READMEs (Tiled, Aseprite, LDtk, perlin, …) from npm, so plugin usage is searchable too — filter with `--kind plugin`. ### `ex doctor` — check your game for common mistakes ```bash ex doctor # human-readable report, exits 1 when problems are found ex doctor --json # machine-readable findings (CI friendly) ``` Type-aware diagnostics powered by your project's own TypeScript and excalibur's type declarations (run `npm install` first). Twelve rules, each grounded in bugs found in shipped games: - `actor-not-added` — an Actor-derived `new` that never reaches `.add`/`.addChild` - `unnamed-actor` — an Actor constructed without a name, harder to spot in debugging tools - `dont-shadow-excalibur-internals` — a field like `isActive` on an Entity subclass shadows engine state and silently kills the entity (tip: set `"noImplicitOverride": true`) - `leaked-subscription` — `.on()` to an engine-lifetime emitter with no cleanup; handlers compound across scene restarts - `dead-collision-hooks` — collision handlers while the Engine has `physics: false` - `dont-mutate-shared-graphics` — writes to cached `getAnimation()`/`getSpriteSheet()` results; `.clone()` first - `unknown-scene-key` — `goToScene` typos checked against the `scenes:` map - `dont-call-lifecycle-hooks` — calling an engine lifecycle hook directly instead of letting the engine invoke it - `camera-pos-aliasing` — `camera.pos = actor.pos` writes through to the live vector - `no-reserved-tags` — engine-owned `ex.*` tags added via `addTag`/`removeTag` - `no-reserved-uniforms` — a Material/ScreenShader source declares a built-in like `u_time_ms` or `v_uv` with a conflicting GLSL type; the engine sets it by name at draw time, so it silently reads as zeros or fails to link - `prefer-seeded-random` — `Math.random()`, unseeded `new Random()`, and duplicate seeds that correlate streams Run `ex doctor --help` for the list. Doctor scans `src/` plus any `example/`, `examples/`, `demo/`, `sample/` or `sandbox/` directory that exists (plugins keep their public-API usage there), or the package root when there is no `src/`; `--scope ` (repeatable) overrides. `prefer-seeded-random` is also the cheapest pre-check before recording screenshot baselines — unseeded randomness is what makes them flaky. Ignore a finding case-by-case with eslint-style comments — after a report, an interactive prompt offers to insert them for you: ```ts // ex-doctor-ignore-next-line actor-not-added new OffscreenHelper(); new Cursor(); // ex-doctor-ignore-line unnamed-actor ``` Omit the rule list to ignore every rule on that line. ### `ex upgrade` — codemod-style version migrations ```bash ex upgrade --dry-run # preview the full migration plan, write nothing ex upgrade # plan preview + one confirm, then apply + bump package.json ex upgrade --to next # target v1 (the `next` prerelease); default is latest ex upgrade --migrate-only # rewrite code but leave package.json alone ``` Chained migrations (v0.29.3 onward, ng-update style) rewrite your source with formatting-preserving splices, classified against your project's *installed* excalibur types — so run it **before** installing the new version. Every migration is one of three types: **Automated** — rewritten for you, no review needed: - `ex.Input.*` namespace flattened into `ex.*` - event `.delta` → `.elapsed` - `Engine.goto(...)` → `goToScene(...)` - `GraphicsComponent.show(...)` → `use(...)` - `Vector.size` → `magnitude` - `getGlobalPos()/getGlobalRotation()/getGlobalScale()` → the equivalent accessors - `EventDispatcher` → `EventEmitter` - `Engine.get/setAntialiasing()` → `engine.screen.antialiasing` - `Particle`/`ParticleEmitter` option renames (emitters gained a nested particle config) - `easeTo`/`easeBy` actions → `moveTo`/`moveBy` with easing - `Timer` now only takes the option-bag constructor - `ScreenShader` `v_texcoord` → `v_uv` - `BoundingBox.draw(...)` → `debug(...)` - `TileMap`'s default `compositeStrategy` changed to `'separate'` — pins the old default **Manual** — needs human judgment, so `// ex-upgrade(): …` breadcrumb comments are inserted at each site with a link and recipe: - `ex.Physics.*` statics were removed — configure physics in the Engine constructor - collision events now target `Collider` (was sometimes `Entity`) - `System.priority` is now static - Trigger API changed (action signature, target vs filter) - legacy `EasingFunctions.*` are deprecated — use the simple `(t) => number` forms - screen space is now rooted at the content area (v1's `worldToScreenCoordinates`/`contentArea` change) **Notification** — no code changes, just a heads-up about a behavior change: - `Vector.normalize()` on a zero vector now returns `(0,0)` (was `(0,1)`) - realistic physics bodies now sleep by default - Font/Text render slightly differently in v1 Requires a clean git tree (your undo) unless `--allow-dirty`; never runs `npm install` for you. ### `ex mcp` — MCP server for AI agents Exposes the CLI's capabilities as [Model Context Protocol](https://modelcontextprotocol.io) tools over stdio, so agents like Claude Code and OpenCode can search the Excalibur docs, scaffold projects, and generate code in your project. Claude Code (add `-s user` to register it globally instead of per-project): ``` claude mcp add excalibur -- npx -y create-excalibur mcp ``` OpenCode — add to `opencode.json` in your project (or `~/.config/opencode/opencode.json`): ```json { "$schema": "https://opencode.ai/config.json", "mcp": { "excalibur": { "type": "local", "command": ["npx", "-y", "create-excalibur", "mcp"], "enabled": true } } } ``` ``` ex mcp # serve, tools operate on the current directory by default ex mcp --project # point the tools at a specific project ex mcp --help ``` **Docs** — also cover the `@excaliburjs/plugin-*` READMEs (`kind: "plugin"`, `/plugins/` slugs): - `docs_search` — search guides, API reference, and plugin READMEs; live by default, falls back to the offline cache - `docs_get_page` — fetch a docs page or plugin README (or one section) as markdown - `docs_sync` — download docs + plugin READMEs into the local cache for offline/version-pinned search **Generate** — scaffold new code into the project (accept `dryRun` to preview): - `analyze_project` — inspect scenes, actors, resources, spritesheets, installed version/plugins - `generate_actor` — new Actor class, optionally wired into a scene - `generate_label` — new Label (text) class, optionally wired into a scene - `generate_scene` — new Scene class, registered in the scenes map by default - `generate_resource` — register an image/sound/font/other asset in the resource loader - `generate_material` — new WebGL shader Material, optionally assigned to an actor - `generate_spritesheet` — slice a sheet image into an `ex.SpriteSheet` - `generate_animation` — build an `ex.Animation` from an existing spritesheet **Update** — edit existing code in place, preserving untouched options and comments: - `update_actor` — change an Actor's `super({ ... })` ActorArgs - `update_engine` — change the project's `new Engine(...)` options **Project lifecycle:** - `list_templates` — list templates and sample projects usable with `create_project` - `create_project` — scaffold a new game from a template (skips `npm install`/`git init` unless asked) **Diagnostics:** - `doctor` — type-aware lint for common Excalibur mistakes (14 rules); `scope` narrows the scan - `preflight` — read-only readiness check before `upgrade`: git state, where excalibur is declared (incl. `peerDependencies`), whether its types resolve, scan roots, what `to` resolves to, and how many migrations are in the path - `upgrade` — chained codemod migrations to a newer Excalibur version. `to: "next"` resolves the dist-tag to an exact version and pins it (a floating tag makes installs non-reproducible); libraries get `peerDependencies.excalibur` bumped too; the result's `backlog` lists older migrations the code still matches (re-run with `from`); `include` runs a single migration id, `scope` narrows the scan Errors come back with actionable hints so agents can self-correct. > Note: `ex` shadows the rarely-used system `ex` (vi's line-editor mode) while the > npm global bin dir is first on your PATH. Use the `excalibur` alias if that bothers you. ## Architecture How the pieces fit together. Everything is TypeScript ESM; each command is a "flow" registered in `src/constants.ts` and dispatched from `index.ts`. Development runs the sources directly (Node's type stripping — no build step in the dev loop); publishing compiles to `dist/` via `tsc`, which is what the bins run on end-user machines. ### Command dispatch Both bins point at the compiled `dist/index.js`. Dispatch is persona-aware: the create persona treats a bare positional as a project name, while `ex`/`excalibur` stay strict so a typo never scaffolds. ```mermaid flowchart LR A["npm create excalibur my-game"] --> D B["create-excalibur bin"] --> D C["ex / excalibur bins"] --> D D["resolveInvocation
src/dispatch.ts"] D -->|"no args"| MENU["interactive menu
FLOW_CHOICES"] D -->|"known command"| FLOWS["FLOWS lookup
src/constants.ts"] D -->|"create persona + positional"| CREATE["create flow
name pre-filled"] D -->|"ex persona + unknown"| ERR["error: unknown command"] MENU --> FLOWS FLOWS --> F1["create / sample / inspect"] FLOWS --> F2["docs"] FLOWS --> F3["generate"] FLOWS --> F4["doctor"] FLOWS --> F5["mcp
dynamic import, stdout = protocol only"] F5 -.->|"16 tools reuse the same cores:
search, apply, doctor, scaffold"| F2 ``` ### `ex docs` — search and the offline index Searches hit the site's Algolia index first and fall back to a locally built index; `ex docs offline` builds that index straight from the Excalibur repo's docs source, pinned to your installed version. ```mermaid flowchart TD Q["ex docs query"] --> RS["runDocsSearch
src/docs/search.ts"] RS -->|"online"| ALG["Algolia DocSearch
public search-only key"] RS -->|"--offline"| LOCAL["MiniSearch index
one doc per page section"] ALG -->|"network error"| LOCAL ALG --> MERGE["merge plugin README hits
up to 3 tail slots"] LOCAL --> MERGE MERGE --> RENDER["markdown to ANSI renderer
pager for long pages"] subgraph SYNC["ex docs offline — sync and indexing"] V["detect installed excalibur
node_modules or package.json"] --> REF["pick ref: release tag v0.32.0
or main for old/no version"] REF --> TREE["GitHub trees API, one call
list site/docs/**"] TREE --> RAW["fetch raw files by commit sha
raw.githubusercontent.com"] RAW --> MDX["mdx.ts: frontmatter slugs, admonitions,
playground embeds, wiki links"] MDX --> IDX["cache ~/.excalibur/docs/ref:
index.json + slugs.json + manifest"] NPM["npm registry:
@excaliburjs/plugin-* readmes"] --> PIDX["plugin index
sibling plugins/ cache"] ALG2["Algolia symbol sweep"] --> SYM["api-symbols.json
resolves wiki links"] end IDX --> LOCAL PIDX --> MERGE ``` ### `ex generate` — what it looks for in your TypeScript Generation is a wizard/apply split: the wizard only builds an option model, and `apply*()` does the edits. Edits are minimal text splices validated by re-parsing — never a full AST reprint, so your formatting and comments survive. It uses **your project's own TypeScript** (never bundled; TypeScript 7 removed the compiler API, so it asks for 5.x/6.x). ```mermaid flowchart TD G["ex generate actor / label / scene / resource /
engine / material / update-actor / spritesheet / animation"] --> AP["analyzeProject"] AP --> TSL["load the project's TypeScript
from its node_modules"] AP --> SCAN subgraph SCAN["syntactic AST scan of src/**/*.ts"] S1["new Engine and its
scenes map keys"] S2["Resources literal:
keys + asset paths"] S3["classes extending Scene"] S4["classes extending
Actor / Label / ScreenElement"] S5["SpriteSheet consts:
grid, spacing, image key"] S6["package.json:
@excaliburjs/* plugins"] end SCAN --> W["wizard prompts
build an option model"] W --> APPLY["apply: minimal text splices
insert option property, add import,
add to a scene's onInitialize"] APPLY --> VAL["re-parse: zero syntax errors
or the edit is abandoned"] VAL -->|"ok"| WRITE["write files"] VAL -->|"seam not found"| MANUAL["print a manual snippet
instead of guessing"] ``` ### `ex doctor` — type-aware diagnostics Doctor is the one place a full `ts.Program` + TypeChecker is used (generate stays syntactic): the checker is what catches `class Boss extends Monster extends Actor`. Rules are kind-keyed listeners over a single AST walk per file, the same shape typescript-eslint uses. ```mermaid flowchart LR D["ex doctor --json"] --> AP["analyzeProject"] AP --> PROG["ts.createProgram + TypeChecker
tsconfig.json or vite-flavored defaults"] PROG --> PROBE["probe: excalibur .d.ts resolvable?
no: run npm install"] PROBE --> WALK["one AST walk per src file
dispatch to rule listeners"] WALK --> R1["actor-not-added:
Actor-derived new never traced
to add or addChild"] WALK --> R2["unnamed-actor:
super options or new Actor
without a name"] R1 --> OUT["findings: rule, file:line, message, hint
exit 1 when any are found"] R2 --> OUT ``` ## Running this project locally Run `npm run dev`, or `node index.ts docs ` (Node 22.18+/24 — the dev loop runs the TypeScript sources directly via type stripping; end users only ever run compiled JS). Tests: `npm test` · Typecheck: `npm run typecheck` · Build: `npm run build` · Publish smoke test: `npm run smoke:pack`