import type { ArtStyle } from '@bitmagic/asset-core/art-style'; import { type ProjectMetadata, type SkillRenderer } from './project-files.js'; import { type AgentId } from './agent-targets.js'; export interface TemplateEntry { id: string; genre: string; /** Both optional: only the suggester reads them, and an older bundle's index may omit either. */ name?: string; description?: string; recommended?: boolean; } /** * The CLI's default, deliberately NOT the index's `recommended` entry. * * `recommended` is standard-3d, a scene pre-populated with demo objects — the right first impression * in the web Creator, where someone wants something to look at immediately. In the CLI lane a coding * agent builds the scene from GAME-DESIGN.md, so those objects are furniture it has to delete first. * Same third-person controller, empty world. * * `recommended` stays where it is: flipping it in templates/index.json would also move the Creator UI * and the lab's `loadRecommendedTemplateId`. */ export declare const DEFAULT_TEMPLATE_ID = "empty-3d"; /** * Templates `--idea` may not be matched to, though `--template` still scaffolds them. * * standard-3d IS the default template plus demo objects — same genre, same third-person controller. * Offering both to the suggester asks it to choose on a difference that does not matter here (the * creator's agent builds the scene either way) and, observed live, it picks standard-3d for ordinary * third-person ideas on the strength of its name and "the default Bitmagic scene template" * description. Withholding it makes the choice purely about camera and controls, which is the only * thing the templates really differ on. */ export declare const SUGGESTION_EXCLUDED_TEMPLATE_IDS: string[]; /** * Directories moved out of the extracted tree into the project's vendored engine/. * * genres/ is vendored here for the engine's own debug tooling (see aliases.ts), which is a * separate concern from src/ below, which is *derived* from genres/ for the creator's game. * Both exist side by side, same as the real game project's src/genres/ and src/work/. * * Exported so tests can build their fixture from this list instead of hand-maintaining a * parallel copy that silently goes stale when an entry is added here. */ export declare const VENDORED_DIRS: string[]; /** * Vendored the same way as VENDORED_DIRS, but copied only when the extracted tree actually has * them — a missing one is not a corrupt tarball. * * That distinction is not stylistic. The engine tarball is built by the NIGHTLY workflow, while * the CLI publishes to npm on its own schedule, so there is always a window in which a freshly * published CLI runs against the previous night's tarball. A required entry would turn that window * into a hard `CliError` on every single `bitmagic init` and `bitmagic upgrade` — the whole * product, down, over a directory of markdown. `sw-cache-buster.js` already carries this reasoning * for the same reason; this is the directory-shaped version of it. * * `agent-docs` is the shared docs corpus: the generated engine-api/ signature digest, ~83 feature * and genre guides, and compile-checked samples/. It is what stops an agent reading 242k lines of * engine source to find out what exists, so a project without it is degraded — but degraded is * survivable and a hard failure is not. */ export declare const OPTIONAL_VENDORED_DIRS: string[]; /** * Single files vendored into `engine/` alongside the directories above: the terms the engine is * licensed under, and the third-party notices published games have to carry. * * Optional for the same reason as OPTIONAL_VENDORED_DIRS — a CLI newer than the tarball must not * fail — but the consequence of absence is different in kind. A project scaffolded from a tarball * predating these files has an engine with no licence text next to it; `bitmagic upgrade` puts it * there as soon as the project moves to a version that ships it. See docs/engine-license.md. */ export declare const OPTIONAL_VENDORED_FILES: string[]; /** * Throws unless the extracted tree carries every VENDORED_DIRS entry. * * Shared by `vendorEngine` and `upgradeProject` so the two cannot come to disagree on what counts * as a complete tarball, or word the same failure two ways. Both run it as a pass of its own, * BEFORE anything is written: for `upgradeProject` that ordering is load-bearing — it clears a * live project's engine/ before copying, so validating inside the copy loop would demolish the * tree and then fail partway through repopulating it — and for a brand new project it costs * nothing. */ export declare function assertEngineBundleComplete(extractedEngineDir: string): void; /** * Copies every OPTIONAL_VENDORED_DIRS / OPTIONAL_VENDORED_FILES entry the extracted tree happens * to carry into the project's `engine/`, and silently skips the ones it does not. * * `scaffoldProject` and `upgradeProject` share this so the "missing is not a corrupt tarball" * allowance — and the nightly-tarball-vs-npm-release window that forces it, documented on the two * constants above — is implemented once rather than twice. `cpSync`'s `recursive` flag is * meaningless for a single file, so directories and files need no separate passes. */ export declare function copyOptionalEngineEntries(extractedEngineDir: string, engineDir: string): void; /** * Copies the engine's service worker to the project ROOT when the tarball carries one, and reports * whether it did. * * AutoCacheBuster.ts registers it at the absolute path /sw-cache-buster.js, so it cannot be * vendored under `engine/` — a service worker cannot control scopes above its own path. Absence is * the same "not a corrupt tarball" allowance as `copyOptionalEngineEntries`: every tarball * published before this feature shipped lacks it, and init and upgrade must both keep working * against those. */ export declare function copyServiceWorker(extractedEngineDir: string, root: string): boolean; /** * Writes one file into a project, creating its parent directories first. * * The mkdir is load-bearing for the nested entries — `//SKILL.md` — in a * project that predates them: without it `writeFileSync` ENOENTs, and in `upgradeProject` it does * so AFTER `engine/` has been replaced, leaving the project half-upgraded. mkdir on an existing * directory is a no-op, so the top-level entries pay nothing for it. */ export declare function writeProjectFile(root: string, relativePath: string, contents: string): void; /** * Every skill this CLI deploys, ONCE, as a path relative to a skills directory. * * **Every skill this CLI deploys belongs here.** A skill documents what the CLI can do — not the * creator's game — so a project stuck on the version that shipped with its scaffold is a project * whose agent cannot discover anything added since. That is not hypothetical: `bitmagic cover` * shipped and was invisible in every existing project, because the skill listing the generate * commands was written once at init and never revisited. Whatever we add next would repeat it. * Add a new skill to this list and existing projects receive it on their next `bitmagic upgrade`. * * Relative rather than rooted because a project holds more than one copy: agents do not agree on * where project skills live, and `agent-targets.ts` argues why every one of them gets a real copy * rather than a symlink or a single directory nobody all reads. The bodies are agent-independent — * only `renderSkillsReadme` differs per directory, which is why the renderer signature takes the * directory rather than each renderer being written twice. */ export declare const SHIPPED_SKILLS: Array<[string, SkillRenderer]>; /** * The platform-generated files that are not skills, and so sit at one fixed path whatever agents * the project is set up for. */ export declare const PLATFORM_CONFIG_FILES: Array<[string, () => string]>; /** * The generated files that are pure platform output: regenerated byte-for-byte from these * renderers with no creator content ever entering them. `bitmagic upgrade` overwrites exactly this * list (plus bitmagic.json, handled separately since only its engineVersion field is * platform-owned, and engine/ + sw-cache-buster.js, handled separately since they are copied, not * rendered). * * Only the paths named here are rewritten; anything else under a skills directory — a skill the * creator wrote themselves — is never opened. The trade is deliberate and one-directional: an edit * to one of OUR skill files is lost on upgrade, which is the price of those files staying current. * * Deliberately excludes package.json (creators add dependencies to it), AGENTS.md/CLAUDE.md and * GAME-DESIGN.md (the creator's own guidance and design — ours to seed once, theirs to own), and * .gitignore (same reasoning) — those are generated once by `scaffoldProject` but are creator-owned * from that point on. * * A function rather than a constant so `scaffoldProject` and `upgradeProject` still share one * definition instead of two lists that can drift — the exact failure that motivated the constant it * replaced: `vite.publish.config.js` was added to init's inline list with no equivalent path for * existing projects to ever receive it, because `upgrade` didn't exist yet. */ export declare function platformGeneratedFiles(agents?: readonly AgentId[]): Array<[string, () => string]>; /** * Every template the downloaded bundle carries, in index order. * * Separate from `resolveTemplate` so `init` can hand the list to the template suggester without * parsing the file a second time — and so the two "this bundle is unusable" errors stay in one place. */ export declare function readTemplateIndex(templatesDir: string): TemplateEntry[]; export declare function resolveTemplate(templatesDir: string, requestedId?: string): TemplateEntry; export interface ScaffoldOptions { targetDir: string; /** Where extractEngine unpacked game/src/* and game/docs. */ extractedEngineDir: string; templatesDir: string; template: TemplateEntry; metadata: ProjectMetadata; projectName: string; /** `bitmagic init --idea`, seeding GAME-DESIGN.md's Pitch. Absent leaves the skeleton empty. */ idea?: string; /** * `bitmagic init --art-style`: stamped into `src/work/game.json` and pre-filled under * GAME-DESIGN.md's Art direction. Absent writes nothing, so a voxel project's game.json is the * template's own — the field's absence means voxel everywhere it is read. */ artStyle?: ArtStyle; } /** * Puts the platform's own code into the project: the vendored `engine/` tree and the service * worker that has to sit beside it at the root. * * Split out of `scaffoldProject` so `importProject` gets the identical tree — including the * "missing is a corrupt tarball" / "missing is just an older tarball" distinction the two * constant lists above spend most of their comments on. A second copy of this loop would be a * second place for that distinction to be got wrong. */ export declare function vendorEngine(extractedEngineDir: string, targetDir: string): void; export interface ProjectFileOptions { targetDir: string; metadata: ProjectMetadata; projectName: string; /** `bitmagic init --idea`, seeding GAME-DESIGN.md's Pitch. Ignored when `gameDesign` is 'keep'. */ idea?: string; /** Pre-fills GAME-DESIGN.md's Art direction. Ignored when `gameDesign` is 'keep'. */ artStyle?: ArtStyle; /** * Whether to write GAME-DESIGN.md at all. * * 'keep' is for `import`, which lifts the creator's own design document out of the export and * must not then overwrite it with an empty skeleton. Everything else about the file set is * identical, which is why this is a flag rather than a second function. */ gameDesign?: 'render' | 'keep'; } /** * Writes every generated file a Bitmagic project has — the platform-owned ones, and the * creator-owned ones seeded once. * * Shared by `scaffoldProject` and `importProject` so the two lanes cannot drift on what a project * contains. What belongs in PLATFORM_GENERATED_FILES, and what deliberately stays out of it, is * argued on that constant. */ export declare function writeProjectFiles(options: ProjectFileOptions): void; export declare function scaffoldProject(options: ScaffoldOptions): void;