spawnpack 0.1.25 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/spawnpack.js CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env bun
2
1
  // @bun
3
2
  var __create = Object.create;
4
3
  var __getProtoOf = Object.getPrototypeOf;
@@ -1224,23 +1223,25 @@ ${c}
1224
1223
  // src/index.ts
1225
1224
  var import_picocolors3 = __toESM(require_picocolors(), 1);
1226
1225
 
1227
- // src/engine/generator.ts
1228
- import { mkdir, writeFile } from "fs/promises";
1229
- import { join } from "path";
1230
-
1231
1226
  // src/config.ts
1232
1227
  var VERSIONS = {
1233
- minEngineVersion: [1, 26, 0],
1234
1228
  manifestFormat: 2,
1235
- rolldown: "^1.0.0-beta.56",
1236
- esToolkit: "^1.43.0",
1237
- jsoncParser: "^3.3.1"
1229
+ rolldown: "^1.3.0",
1230
+ esToolkit: "^1.53.2",
1231
+ jsoncParser: "^3.3.1",
1232
+ dprint: "^0.64.3",
1233
+ typescript: "^7.0.2"
1234
+ };
1235
+ var DPRINT_PLUGIN_URLS = {
1236
+ typescript: "https://plugins.dprint.dev/typescript-0.99.1.wasm",
1237
+ json: "https://plugins.dprint.dev/json-0.26.0.wasm",
1238
+ markdown: "https://plugins.dprint.dev/markdown-0.29.0.wasm"
1238
1239
  };
1239
1240
  var DEFAULT_MINECRAFT_DEPENDENCY_VERSIONS = {
1240
- server: "2.6.0",
1241
- serverUi: "2.0.0",
1242
- vanillaData: "1.26.13",
1243
- math: "2.4.0"
1241
+ server: "2.11.0",
1242
+ serverUi: "2.3.0",
1243
+ vanillaData: "1.26.60",
1244
+ math: "2.4.1"
1244
1245
  };
1245
1246
  var DEFAULT_SCRIPT_PACKAGES = {
1246
1247
  server: true,
@@ -1266,88 +1267,6 @@ function getScriptDirectorySegments(config) {
1266
1267
  function getScriptEntryPath(config) {
1267
1268
  return [...getScriptDirectorySegments(config), "main.js"].join("/");
1268
1269
  }
1269
- function getAiDocFilename(aiSetup) {
1270
- if (aiSetup === "claude") {
1271
- return "CLAUDE.md";
1272
- }
1273
- if (aiSetup === "other") {
1274
- return "AGENTS.md";
1275
- }
1276
- return null;
1277
- }
1278
-
1279
- // src/engine/manifests.ts
1280
- var PACK_VERSION = [1, 0, 0];
1281
- function getAuthors(author) {
1282
- return author.trim() === "" ? [] : [author];
1283
- }
1284
- function getManifestScriptDependencies(config, versions) {
1285
- const dependencies = [];
1286
- if (config.scriptPackages.server) {
1287
- dependencies.push({ module_name: "@minecraft/server", version: versions.server });
1288
- }
1289
- if (config.scriptPackages.serverUi) {
1290
- dependencies.push({ module_name: "@minecraft/server-ui", version: versions.serverUi });
1291
- }
1292
- return dependencies;
1293
- }
1294
- function getBpModules(config) {
1295
- const modules = [{ type: "data", uuid: crypto.randomUUID(), version: PACK_VERSION }];
1296
- if (config.scripting !== "none") {
1297
- modules.push({
1298
- type: "script",
1299
- uuid: crypto.randomUUID(),
1300
- version: PACK_VERSION,
1301
- language: "javascript",
1302
- entry: getScriptEntryPath(config)
1303
- });
1304
- }
1305
- return modules;
1306
- }
1307
- function generateBpManifest(config, bpUuid, rpUuid, versions) {
1308
- const dependencies = [{ uuid: rpUuid, version: PACK_VERSION }];
1309
- if (config.scripting !== "none") {
1310
- dependencies.push(...getManifestScriptDependencies(config, versions));
1311
- }
1312
- return {
1313
- format_version: VERSIONS.manifestFormat,
1314
- metadata: {
1315
- authors: getAuthors(config.author),
1316
- product_type: "addon"
1317
- },
1318
- header: {
1319
- name: "pack.name",
1320
- description: "pack.description",
1321
- pack_scope: "world",
1322
- uuid: bpUuid,
1323
- version: PACK_VERSION,
1324
- min_engine_version: VERSIONS.minEngineVersion
1325
- },
1326
- capabilities: ["pbr"],
1327
- modules: getBpModules(config),
1328
- dependencies
1329
- };
1330
- }
1331
- function generateRpManifest(config, rpUuid, bpUuid) {
1332
- return {
1333
- format_version: VERSIONS.manifestFormat,
1334
- metadata: {
1335
- authors: getAuthors(config.author),
1336
- product_type: "addon"
1337
- },
1338
- header: {
1339
- name: "pack.name",
1340
- description: "pack.description",
1341
- pack_scope: "world",
1342
- uuid: rpUuid,
1343
- version: PACK_VERSION,
1344
- min_engine_version: VERSIONS.minEngineVersion
1345
- },
1346
- capabilities: ["pbr"],
1347
- modules: [{ type: "resources", uuid: crypto.randomUUID(), version: PACK_VERSION }],
1348
- dependencies: [{ uuid: bpUuid, version: PACK_VERSION }]
1349
- };
1350
- }
1351
1270
 
1352
1271
  // src/engine/minecraft-package-versions.ts
1353
1272
  var PACKAGE_NAMES = {
@@ -1415,8 +1334,179 @@ async function getMinecraftDependencyVersions() {
1415
1334
  return await cachedVersionsPromise;
1416
1335
  }
1417
1336
 
1337
+ // src/engine/json.ts
1338
+ function stringifyJson(value) {
1339
+ return `${JSON.stringify(value, null, "\t")}
1340
+ `;
1341
+ }
1342
+
1343
+ // src/engine/manifests.ts
1344
+ var PACK_VERSION = [1, 0, 0];
1345
+ function getAuthors(author) {
1346
+ return author.trim() === "" ? [] : [author];
1347
+ }
1348
+ function getMinEngineVersion(versions) {
1349
+ const [major = 1, minor = 0, patch = 0] = versions.vanillaData.split(/[.-]/).map(Number).filter(Number.isFinite);
1350
+ return [major, minor, patch];
1351
+ }
1352
+ function getManifestScriptDependencies(config, versions) {
1353
+ const dependencies = [];
1354
+ if (config.scriptPackages.server) {
1355
+ dependencies.push({ module_name: "@minecraft/server", version: versions.server });
1356
+ }
1357
+ if (config.scriptPackages.serverUi) {
1358
+ dependencies.push({ module_name: "@minecraft/server-ui", version: versions.serverUi });
1359
+ }
1360
+ return dependencies;
1361
+ }
1362
+ function getBpModules(config) {
1363
+ const modules = [{ type: "data", uuid: crypto.randomUUID(), version: PACK_VERSION }];
1364
+ if (config.scripting === "typescript") {
1365
+ modules.push({
1366
+ type: "script",
1367
+ uuid: crypto.randomUUID(),
1368
+ version: PACK_VERSION,
1369
+ language: "javascript",
1370
+ entry: getScriptEntryPath(config)
1371
+ });
1372
+ }
1373
+ return modules;
1374
+ }
1375
+ function getHeaderPackFields(uuid, versions) {
1376
+ return {
1377
+ name: "pack.name",
1378
+ description: "pack.description",
1379
+ pack_scope: "world",
1380
+ uuid,
1381
+ version: PACK_VERSION,
1382
+ min_engine_version: getMinEngineVersion(versions)
1383
+ };
1384
+ }
1385
+ function generateBpManifest(config, bpUuid, rpUuid, versions) {
1386
+ const dependencies = [{ uuid: rpUuid, version: PACK_VERSION }];
1387
+ if (config.scripting === "typescript") {
1388
+ dependencies.push(...getManifestScriptDependencies(config, versions));
1389
+ }
1390
+ return {
1391
+ format_version: VERSIONS.manifestFormat,
1392
+ metadata: {
1393
+ authors: getAuthors(config.author),
1394
+ product_type: "addon"
1395
+ },
1396
+ header: getHeaderPackFields(bpUuid, versions),
1397
+ modules: getBpModules(config),
1398
+ dependencies
1399
+ };
1400
+ }
1401
+ function generateRpManifest(config, rpUuid, bpUuid, versions) {
1402
+ return {
1403
+ format_version: VERSIONS.manifestFormat,
1404
+ metadata: {
1405
+ authors: getAuthors(config.author),
1406
+ product_type: "addon"
1407
+ },
1408
+ header: getHeaderPackFields(rpUuid, versions),
1409
+ capabilities: ["pbr"],
1410
+ modules: [{ type: "resources", uuid: crypto.randomUUID(), version: PACK_VERSION }],
1411
+ dependencies: [{ uuid: bpUuid, version: PACK_VERSION }]
1412
+ };
1413
+ }
1414
+
1415
+ // templates/AGENTS.md
1416
+ var AGENTS_default = "## Project layout\n\n- When the project has scripts, TypeScript source lives in `data/scripts/`: `main.ts` is the entry, `lib/` holds shared helpers imported as `~/lib/...`, and `modules/<feature>/` holds features imported by `main.ts`.\n- rgl builds the add-on from `config.json`: the `typegen` filter writes `data/scripts/generated_types.ts`, and the `rolldown` filter bundles `data/scripts/` into the exported behavior pack's `scripts/` folder. Never edit generated output by hand; edit the source or the filter.\n- Inspect build output through `.regolith/tmp` without resolving its symlinks.\n\n## Documentation lookup\n\n- Before relying on version-sensitive or unfamiliar Minecraft Bedrock behavior, verify the relevant current documentation for the project's target Minecraft and Script API versions and stable/preview status. This includes Script API, BP/RP JSON formats, components, events, schemas, MoLang, manifests, commands, animations, and pack behavior.\n- Prefer Bedrock-specific tools when the harness provides them, such as documentation search, Script API or component lookup, and vanilla examples. Otherwise use web search.\n- Prefer current Microsoft Creator documentation and versioned references on `bedrock.dev`. For Script API, also use `https://jaylydev.github.io/scriptapi-docs/latest/`. Use `wiki.bedrock.dev` for established concepts and proven implementation patterns.\n- For other libraries, SDKs, APIs, and CLI tools, verify current official documentation before relying on unfamiliar or version-sensitive behavior.\n\n## Technical judgment\n\n- Do not assume the user or the agent is correct. Verify assumptions against current documentation, repository evidence, established patterns, or a working project sample.\n- If those sources do not resolve a choice that materially affects the result, ask the user for a reference, pattern, or sample project.\n- If evidence shows the requested approach is incorrect, unsafe, incompatible, unnecessarily complex, or slower than a supported alternative, explain why and recommend the better implementation. Follow the user's final choice unless it violates safety or repository constraints.\n\n## Verification\n\n- After TypeScript changes, run the project's `typecheck` script.\n- After BP/RP changes, validate the packs with the harness's pack validator or a Bedrock Dedicated Server load when available; otherwise check them against the project's configured Bedrock schemas.\n- Do not create or run tests unless the user asks or an existing repository policy requires them.\n- If a check is unavailable or fails for a pre-existing reason, say so plainly. Never claim a check you did not run.\n\n## Code rules\n\n- Do not write comments in code. Use clear naming and straightforward structure so the code is self-explanatory.\n- Validate predictable invalid states before calling an API. Use `try-catch` only for a documented or observed unguardable failure when the catch performs meaningful recovery, cleanup, translation, or contextual rethrow. Keep the `try` block narrow; never silently swallow errors.\n\n## Minecraft Bedrock conventions\n\n- Guard before Script API calls that can throw: check `entity.isValid` (players are valid only while loaded) before `getComponent`/`getComponents`; confirm the chunk is loaded before `dimension.getBlock`/`spawnEntity`; and use containers only after re-fetching them from a current block or entity rather than holding a stale reference. Defer world-mutating work from read-only or early-execution callbacks with `system.run`. Reserve `try-catch` for calls whose validity cannot be checked in advance, such as `runCommand` throwing `CommandError`.\n- Prefer `@minecraft/vanilla-data` typed identifiers over raw component, entity, block, item, and effect strings, and the generated `BlockId`/`EntityId`/`ItemId` enums for this add-on's own content.\n- Check `@minecraft/math` before writing vector, matrix, clamp, lerp, or similar math helpers.\n- Never reference another add-on's content IDs in data-driven JSON such as `minecraft:break_blocks`; Bedrock validates those IDs at pack load and errors when that add-on is absent. Handle optional cross-add-on content in scripts.\n\n## Performance preferences\n\n- Before adding `system.runInterval` polling, check the project's Script API version and current documentation for a matching event. Prefer the event when it provides the required data and timing, such as `PlayerInventoryItemChangeAfterEventSignal` instead of repeatedly scanning every player's inventory or hotbar. Poll only when no suitable event exists; then minimize the frequency, players, slots, and work performed.\n- In hot loops, iterate maps directly with `for (const value of map.values())`. Do not spread maps into temporary arrays or iterate entry pairs when values or keys are enough.\n- Cache stable configuration in memory and persist only when values actually change.\n\n## Repository hygiene\n\n- Never discard, overwrite, or revert unrelated user changes.\n- Do not create repository files for agent memory, lessons, session notes, plans, or debugging logs.\n- Do not create or update documentation, changelogs, or generated artifacts unless requested or required by the changed contract.\n- Do not commit, push, publish, or delete unrelated files unless explicitly requested.\n- Commit messages, when requested, use short natural subjects without Conventional Commit prefixes, AI/tool attribution, or structured report trailers. Split unrelated logical changes into separate commits.\n\n## Communication\n\n- Be direct and concise. State assumptions only when they materially affect the implementation.\n- After changes, report what changed, the verification evidence, and any real blocker or residual risk. Do not add ritual sections for untouched files.\n";
1417
+
1418
+ // templates/filters/typegen/main.js
1419
+ var main_default = `import { pascalCase } from "es-toolkit";
1420
+ import * as JSONC from "jsonc-parser";
1421
+ import { glob, readFile, writeFile } from "node:fs/promises";
1422
+ import { matchesGlob } from "node:path";
1423
+
1424
+ const entries = [
1425
+ {
1426
+ type: "BlockId",
1427
+ pattern: "BP/blocks/**/*.json",
1428
+ jsonPath: ["minecraft:block", "description", "identifier"],
1429
+ identifiers: [],
1430
+ },
1431
+ {
1432
+ type: "EntityId",
1433
+ pattern: "BP/entities/**/*.json",
1434
+ jsonPath: ["minecraft:entity", "description", "identifier"],
1435
+ identifiers: [],
1436
+ },
1437
+ {
1438
+ type: "ItemId",
1439
+ pattern: "BP/items/**/*.json",
1440
+ jsonPath: ["minecraft:item", "description", "identifier"],
1441
+ identifiers: [],
1442
+ },
1443
+ ];
1444
+
1445
+ for (const dir of ["BP", "RP"]) {
1446
+ for await (const path of glob(\`\${dir}/**/*.json\`)) {
1447
+ const entry = entries.find((f) => matchesGlob(path, f.pattern));
1448
+ if (!entry) continue;
1449
+
1450
+ let found = false;
1451
+ const text = await readFile(path, "utf8");
1452
+ JSONC.visit(text, {
1453
+ onArrayBegin() {
1454
+ return !found;
1455
+ },
1456
+ onObjectBegin() {
1457
+ return !found;
1458
+ },
1459
+ onLiteralValue(value, _offset, _length, _startLine, _startCharacter, pathSupplier) {
1460
+ if (typeof value !== "string") return;
1461
+ if (!pathSupplier().every((segment, i) => segment === entry.jsonPath[i])) return;
1462
+
1463
+ found = true;
1464
+ entry.identifiers.push(value);
1465
+ },
1466
+ });
1467
+ }
1468
+ }
1469
+
1470
+ const lines = ["// Auto-generated file. DO NOT EDIT MANUALLY!\\n"];
1471
+
1472
+ for (const entry of entries) {
1473
+ if (entry.identifiers.length === 0) continue;
1474
+
1475
+ lines.push(\`export const enum \${entry.type} {\`);
1476
+ for (const id of entry.identifiers) {
1477
+ const colIndex = id.indexOf(":");
1478
+ if (colIndex === -1) {
1479
+ console.warn(\`Invalid identifier: \${id}\`);
1480
+ continue;
1481
+ }
1482
+ const name = id.slice(colIndex + 1).trim();
1483
+ if (name === "") {
1484
+ console.warn(\`Invalid identifier: \${id}\`);
1485
+ continue;
1486
+ }
1487
+ lines.push(\`\\t\${pascalCase(name)} = "\${id}",\`);
1488
+ }
1489
+ lines.push(\`}\\n\`);
1490
+ }
1491
+
1492
+ const res = lines.join("\\n");
1493
+
1494
+ const ROOT_DIR = process.env.ROOT_DIR;
1495
+
1496
+ const current = await readFile(\`\${ROOT_DIR}/data/scripts/generated_types.ts\`, "utf8")
1497
+ .catch(() => null);
1498
+
1499
+ if (current !== res) {
1500
+ await Promise.all([
1501
+ writeFile(\`\${ROOT_DIR}/data/scripts/generated_types.ts\`, res),
1502
+ writeFile("./data/scripts/generated_types.ts", res),
1503
+ ]);
1504
+ }
1505
+ `;
1506
+
1418
1507
  // src/engine/templates.ts
1419
1508
  var PACK_VERSION2 = "1.0.0";
1509
+ var BINARY_EXTENSIONS = ["png", "jpg", "jpeg", "tga", "ogg", "wav", "mp3", "fsb"];
1420
1510
  function getSelectedScriptDependencies(config, versions) {
1421
1511
  const dependencies = {};
1422
1512
  if (config.scriptPackages.server) {
@@ -1433,65 +1523,35 @@ function getSelectedScriptDependencies(config, versions) {
1433
1523
  }
1434
1524
  return dependencies;
1435
1525
  }
1526
+ var RGL_WORKFLOW_SECTION = `## Build workflow
1527
+
1528
+ - \`rgl watch\` rebuilds the script bundle and exports the add-on while you work.
1529
+ - \`rgl run\` builds a development export once.
1530
+ - \`rgl run build\` creates a production build.
1531
+ - \`typegen\` writes typed custom IDs (\`BlockId\`, \`EntityId\`, \`ItemId\`) to \`data/scripts/generated_types.ts\` on every build.
1532
+ - \`rolldown\` bundles \`data/scripts/main.ts\` and everything it imports into the behavior pack.
1533
+
1534
+ `;
1436
1535
  function generatePackageJson(config, versions) {
1437
- const scripts = {};
1438
- if (config.scripting === "typescript") {
1439
- if (!config.useRgl) {
1440
- scripts.build = "tsc --project tsconfig.json";
1441
- }
1442
- scripts.typecheck = config.useRgl ? "tsc --project data/scripts/tsconfig.json --noEmit" : "tsc --project tsconfig.json --noEmit";
1443
- scripts.format = "dprint fmt";
1444
- }
1445
- if (config.scripting === "javascript") {
1446
- scripts.check = "bun --smol packs/BP/scripts";
1447
- }
1448
- const packageJson = {
1536
+ return stringifyJson({
1449
1537
  name: `${config.identifier}-${config.projectId}`,
1450
1538
  version: PACK_VERSION2,
1451
1539
  private: true,
1452
- ...config.useRgl ? { workspaces: ["./filters/*"] } : {},
1453
1540
  type: "module",
1454
- scripts,
1455
- dependencies: getSelectedScriptDependencies(config, versions)
1456
- };
1457
- if (config.scripting === "typescript") {
1458
- packageJson.devDependencies = {
1459
- dprint: "^0.50.2",
1460
- typescript: "^5.7.3"
1461
- };
1462
- }
1463
- return packageJson;
1464
- }
1465
- function generateTsConfig(config) {
1466
- const compilerOptions = {
1467
- target: "ES2020",
1468
- module: "ES2020",
1469
- moduleResolution: "bundler",
1470
- strict: true,
1471
- noImplicitAny: true,
1472
- strictNullChecks: true,
1473
- noImplicitReturns: true,
1474
- noFallthroughCasesInSwitch: true,
1475
- noUnusedLocals: true,
1476
- noUnusedParameters: true,
1477
- isolatedModules: true,
1478
- skipLibCheck: true,
1479
- forceConsistentCasingInFileNames: true,
1480
- rootDir: "data/scripts",
1481
- types: []
1482
- };
1483
- if (config.useRgl) {
1484
- compilerOptions.noEmit = true;
1485
- } else {
1486
- compilerOptions.outDir = `packs/BP/${getScriptEntryPath(config).replace(/\/main\.js$/, "")}`;
1487
- }
1488
- return {
1489
- compilerOptions,
1490
- include: ["data/scripts/**/*.ts"]
1491
- };
1541
+ workspaces: ["./filters/*"],
1542
+ scripts: {
1543
+ typecheck: "tsc --project data/scripts/tsconfig.json --noEmit",
1544
+ format: "dprint fmt"
1545
+ },
1546
+ dependencies: getSelectedScriptDependencies(config, versions),
1547
+ devDependencies: {
1548
+ dprint: VERSIONS.dprint,
1549
+ typescript: VERSIONS.typescript
1550
+ }
1551
+ });
1492
1552
  }
1493
1553
  function generateDprintConfig() {
1494
- return {
1554
+ return stringifyJson({
1495
1555
  typescript: {
1496
1556
  deno: true,
1497
1557
  lineWidth: 100,
@@ -1505,14 +1565,21 @@ function generateDprintConfig() {
1505
1565
  },
1506
1566
  excludes: ["**/node_modules", "**/*-lock.json"],
1507
1567
  plugins: [
1508
- "https://plugins.dprint.dev/typescript-0.95.13.wasm",
1509
- "https://plugins.dprint.dev/json-0.21.0.wasm",
1510
- "https://plugins.dprint.dev/markdown-0.20.0.wasm"
1568
+ DPRINT_PLUGIN_URLS.typescript,
1569
+ DPRINT_PLUGIN_URLS.json,
1570
+ DPRINT_PLUGIN_URLS.markdown
1511
1571
  ]
1512
- };
1572
+ });
1573
+ }
1574
+ function generateVscodeExtensions(config) {
1575
+ const recommendations = ["rockide.rockide-vscode"];
1576
+ if (config.scripting === "typescript") {
1577
+ recommendations.push("dprint.dprint");
1578
+ }
1579
+ return stringifyJson({ recommendations });
1513
1580
  }
1514
1581
  function generateRglConfig(config) {
1515
- return {
1582
+ return stringifyJson({
1516
1583
  $schema: "https://raw.githubusercontent.com/ink0rr/rgl-schemas/main/config/v1.1.json",
1517
1584
  author: config.author,
1518
1585
  name: config.projectName,
@@ -1555,7 +1622,7 @@ function generateRglConfig(config) {
1555
1622
  }
1556
1623
  }
1557
1624
  }
1558
- };
1625
+ });
1559
1626
  }
1560
1627
  function getRolldownExternalModules(config) {
1561
1628
  return ["@minecraft/server", ...config.scriptPackages.serverUi ? ["@minecraft/server-ui"] : []];
@@ -1590,17 +1657,17 @@ await build(config);
1590
1657
  `;
1591
1658
  }
1592
1659
  function generateRolldownFilterPackageJson() {
1593
- return {
1660
+ return stringifyJson({
1594
1661
  name: "rolldown-filter",
1595
1662
  private: true,
1596
1663
  type: "module",
1597
1664
  dependencies: {
1598
1665
  rolldown: VERSIONS.rolldown
1599
1666
  }
1600
- };
1667
+ });
1601
1668
  }
1602
1669
  function generateTypegenFilterPackageJson() {
1603
- return {
1670
+ return stringifyJson({
1604
1671
  name: "typegen",
1605
1672
  private: true,
1606
1673
  type: "module",
@@ -1608,21 +1675,19 @@ function generateTypegenFilterPackageJson() {
1608
1675
  "es-toolkit": VERSIONS.esToolkit,
1609
1676
  "jsonc-parser": VERSIONS.jsoncParser
1610
1677
  }
1611
- };
1678
+ });
1612
1679
  }
1613
- function generateMainTs(config) {
1614
- return `import { world } from "@minecraft/server";
1615
-
1616
- world.afterEvents.worldLoad.subscribe(() => {
1617
- console.warn("[${config.identifier}:${config.projectId}] Addon loaded!");
1618
- });
1619
- `;
1680
+ function generateTypegenFilterMain() {
1681
+ return main_default;
1682
+ }
1683
+ function generateAgentsDoc() {
1684
+ return AGENTS_default;
1620
1685
  }
1621
- function generateMainJs(config) {
1686
+ function generateMainTs(config) {
1622
1687
  return `import { world } from "@minecraft/server";
1623
1688
 
1624
1689
  world.afterEvents.worldLoad.subscribe(() => {
1625
- console.warn("[${config.identifier}:${config.projectId}] Addon loaded!");
1690
+ world.sendMessage("[${config.identifier}:${config.projectId}] Addon loaded!");
1626
1691
  });
1627
1692
  `;
1628
1693
  }
@@ -1633,7 +1698,7 @@ pack.description=Generated with Spawnpack
1633
1698
  `;
1634
1699
  }
1635
1700
  function generateScriptsTsConfig() {
1636
- return {
1701
+ return stringifyJson({
1637
1702
  compilerOptions: {
1638
1703
  lib: [
1639
1704
  "ES2015",
@@ -1688,108 +1753,137 @@ function generateScriptsTsConfig() {
1688
1753
  noUnusedParameters: true,
1689
1754
  paths: { "~/*": ["./*"] }
1690
1755
  }
1691
- };
1756
+ });
1692
1757
  }
1693
1758
  function generateGitignore(config) {
1694
1759
  const lines = ["node_modules/", "dist/", ".logs/", "*.zip"];
1695
- if (config.useRgl) {
1696
- lines.push("/.regolith", "/build");
1697
- }
1698
- if (config.useRgl && config.scripting === "typescript") {
1699
- lines.push("data/scripts/generated_types.ts");
1760
+ if (config.scripting === "typescript") {
1761
+ lines.push("/.regolith", "/build", "data/scripts/generated_types.ts");
1700
1762
  }
1701
1763
  return `${lines.join(`
1702
1764
  `)}
1703
1765
  `;
1704
1766
  }
1767
+ function generateGitattributes() {
1768
+ return ["* text=auto eol=lf", ...BINARY_EXTENSIONS.map((extension) => `*.${extension} binary`)].join(`
1769
+ `) + `
1770
+ `;
1771
+ }
1705
1772
  function generateReadme(config) {
1706
- const scriptSource = config.scripting === "none" ? "None" : config.scripting === "typescript" ? "data/scripts/main.ts" : `packs/BP/${getScriptEntryPath(config)}`;
1707
- const runtimeEntry = config.scripting === "none" ? "None" : `packs/BP/${getScriptEntryPath(config)}`;
1708
- const installStep = config.scripting !== "none" ? "1. Install dependencies with `bun install` or `npm install`." : "1. No package install is required for this scaffold.";
1709
- const buildSteps = config.scripting === "none" ? "2. Edit your BP/RP JSON files directly inside `packs/`." : config.useRgl ? "2. Run `rgl watch` while developing to rebuild and export your add-on automatically.\n3. Run `rgl run build` when you want a production build." : config.scripting === "typescript" ? "2. Write your gameplay scripts in `data/scripts/`.\n3. Run `bun run build` or `npm run build` to compile TypeScript into `packs/BP/scripts/`." : "2. Write your gameplay scripts in `packs/BP/scripts/` and let Minecraft copy the pack into `com.mojang`.";
1710
- const aiDocFilename = getAiDocFilename(config.aiSetup);
1711
- const ponytailAiLine = config.installPonytail ? config.aiSetup === "other" ? "Ponytail is enabled via `opencode.json`.\n\n" : "To enable ponytail in Claude Code, run `/plugin marketplace add DietrichGebert/ponytail` then `/plugin install ponytail@ponytail`.\n\n" : "";
1712
- const aiSection = aiDocFilename !== null ? `## AI Tooling
1773
+ const scopeSection = config.useMarketplaceStructure ? `## Marketplace structure
1774
+
1775
+ Content that Minecraft resolves by bare path is namespaced under \`${getMarketplaceScopeLabel(config)}\` inside the resource pack only:
1713
1776
 
1714
- Spawnpack generated \`${aiDocFilename}\` and \`.mcp.json\` for AI-assisted development. The configured MCP servers work out of the box \u2014 no API keys required.
1777
+ - \`packs/RP/textures/${getMarketplaceScopeLabel(config)}/\` for block, entity, and item textures
1778
+ - \`packs/RP/sounds/${getMarketplaceScopeLabel(config)}/\` for sound files
1715
1779
 
1716
- Exa's hosted MCP runs on a rate-limited free tier. If you need higher limits, you can add your own Exa API key to \`.mcp.json\` (optional): https://dashboard.exa.ai/api-keys
1780
+ Behavior pack content stays flat, and resource pack JSON still references namespaced textures and paths relative to \`packs/RP/\`.
1717
1781
 
1718
- ${ponytailAiLine}` : "";
1719
- const scriptPackageSection = config.scripting !== "none" ? `## Script Packages
1782
+ ` : "";
1783
+ const agentsSection = config.generateAgentsDoc ? `## AI tooling
1720
1784
 
1721
- Selected npm packages are installed through \`package.json\`. Only runtime Script API modules such as \`@minecraft/server\` and \`@minecraft/server-ui\` are written to \`packs/BP/manifest.json\`; npm-side libraries such as \`@minecraft/vanilla-data\` and \`@minecraft/math\` are imported and bundled from \`package.json\`.
1785
+ \`AGENTS.md\` holds the working rules this project gives to coding agents.
1722
1786
 
1723
1787
  ` : "";
1788
+ if (config.scripting === "none") {
1789
+ return `# ${config.projectName}
1790
+
1791
+ ## Overview
1792
+
1793
+ - Behavior Pack: \`packs/BP\`
1794
+ - Resource Pack: \`packs/RP\`
1795
+ - Script API setup: none
1796
+
1797
+ ## Getting started
1798
+
1799
+ Edit the pack JSON directly inside \`packs/BP\` and \`packs/RP\`, then copy or export both packs into Minecraft. The scaffold has no build step and no npm dependencies.
1800
+
1801
+ ## Project layout
1802
+
1803
+ - \`packs/BP/manifest.json\` pairs the behavior pack with the resource pack through their UUIDs.
1804
+ - \`packs/BP/texts/en_US.lang\` defines \`pack.name\` and \`pack.description\` for the behavior pack.
1805
+ - \`packs/RP/manifest.json\` pairs the resource pack with the behavior pack.
1806
+ - \`packs/RP/textures/item_texture.json\` and \`terrain_texture.json\` register item and block texture atlases.
1807
+ - \`packs/RP/sounds/sound_definitions.json\` registers sound events.
1808
+ - \`.vscode/extensions.json\` recommends the Rockide extension for pack JSON validation.
1809
+
1810
+ ${scopeSection}${agentsSection}---
1811
+
1812
+ <sub>Generated with Spawnpack</sub>
1813
+ `;
1814
+ }
1724
1815
  return `# ${config.projectName}
1725
1816
 
1726
1817
  ## Overview
1727
1818
 
1728
1819
  - Behavior Pack: \`packs/BP\`
1729
1820
  - Resource Pack: \`packs/RP\`
1730
- - Script API setup: ${config.scripting}
1731
- - Script source: ${scriptSource}
1732
- - Runtime entry: ${runtimeEntry}
1821
+ - Script source: \`data/scripts\`
1822
+ - Bundle entry: \`packs/BP/${getScriptEntryPath(config)}\`
1733
1823
 
1734
- ## Getting Started
1824
+ ## Getting started
1735
1825
 
1736
- ${installStep}
1737
- ${buildSteps}
1826
+ 1. Install rgl if you have not already: \`curl -fsSL rgl.ink0rr.dev/install.sh | sh\` on macOS or Linux, \`irm rgl.ink0rr.dev/install.ps1 | iex\` in PowerShell on Windows.
1827
+ 2. Install dependencies with \`bun install\` or \`npm install\`.
1828
+ 3. Run \`rgl watch\` while you work to rebuild and export the add-on automatically.
1829
+ 4. Run \`rgl run build\` for a production build.
1738
1830
 
1739
- ## Working with Scripts
1831
+ ## Project layout
1740
1832
 
1741
- ${config.scripting === "none" ? "This project was generated without Script API support. Add scripting later if you need gameplay logic." : config.scripting === "typescript" ? `- Write your TypeScript entrypoint in \`data/scripts/main.ts\`.
1742
- - Add more TypeScript files anywhere under \`data/scripts/\` and import them from \`main.ts\`.
1743
- - rgl compiles your TypeScript from \`data/scripts/\` into \`${runtimeEntry}\`.` : `- Write your JavaScript entrypoint directly in \`${runtimeEntry}\`.
1744
- - Add additional JavaScript files beside \`main.js\` and import them from there.
1745
- - Minecraft copies the BP/RP content directly into \`com.mojang\`, so there is no TypeScript compile step.`}
1833
+ - \`data/scripts/main.ts\` is the script entrypoint.
1834
+ - \`data/scripts/lib/\` holds shared helpers, imported as \`~/lib/...\`.
1835
+ - \`data/scripts/modules/<feature>/\` holds feature modules imported by \`main.ts\`.
1836
+ - \`data/scripts/tsconfig.json\` sets the \`~/*\` path alias and type-checks without emitting.
1837
+ - \`filters/typegen/\` writes typed custom IDs to \`data/scripts/generated_types.ts\`.
1838
+ - \`filters/rolldown/\` bundles \`data/scripts/\` into \`packs/BP/scripts/\`.
1839
+ - \`config.json\` defines the rgl filters and the \`default\`, \`build\`, and \`preview\` profiles.
1840
+ - \`dprint.json\` formats TypeScript, JSON, and Markdown with tabs and a 100-column width.
1841
+ - \`packs/BP/manifest.json\` pairs the packs and declares the script module.
1842
+ - \`packs/RP/manifest.json\` pairs the resource pack with the behavior pack and enables PBR capabilities.
1843
+ - \`.vscode/extensions.json\` recommends the Rockide extension and dprint.
1746
1844
 
1747
- ${scriptPackageSection}${config.useRgl ? `## rgl Workflow
1845
+ ${RGL_WORKFLOW_SECTION}## Script packages
1748
1846
 
1749
- - \`rgl watch\` rebuilds your scripts and exports your add-on while you work.
1750
- - \`rgl run\` builds a development export once.
1751
- - \`rgl run build\` creates a production-ready build.
1752
- - \`typegen\` auto-generates typed custom IDs (\`BlockId\`/\`EntityId\`/\`ItemId\`) into \`data/scripts/generated_types.ts\` on each build.
1847
+ Packages in \`package.json\` supply imports for your scripts. Only runtime Script API modules such as \`@minecraft/server\` and \`@minecraft/server-ui\` are declared in \`packs/BP/manifest.json\`; npm-only libraries such as \`@minecraft/vanilla-data\` and \`@minecraft/math\` are imported and bundled by rolldown.
1753
1848
 
1754
- The generated \`config.json\` already points the script bundle to \`${runtimeEntry}\`.
1849
+ ## Commands
1755
1850
 
1756
- ` : ""}${aiSection}---
1851
+ - \`bun run typecheck\` or \`npm run typecheck\` checks the TypeScript source.
1852
+ - \`bun run format\` or \`npm run format\` formats with dprint.
1853
+
1854
+ ${scopeSection}${agentsSection}---
1757
1855
 
1758
1856
  <sub>Generated with Spawnpack</sub>
1759
1857
  `;
1760
1858
  }
1761
1859
  function generateBlocksJson() {
1762
- return { format_version: "1.21.40" };
1860
+ return stringifyJson({ format_version: "1.21.40" });
1763
1861
  }
1764
1862
  function generateSoundsJson() {
1765
- return { entity_sounds: {} };
1863
+ return stringifyJson({ entity_sounds: {} });
1766
1864
  }
1767
1865
  function generateSoundDefinitions() {
1768
- return { format_version: "1.14.0", sound_definitions: {} };
1866
+ return stringifyJson({ format_version: "1.14.0", sound_definitions: {} });
1769
1867
  }
1770
- function generateItemTexture(config) {
1771
- return {
1772
- resource_pack_name: config.projectName,
1868
+ function generateItemTexture() {
1869
+ return stringifyJson({
1870
+ resource_pack_name: "pack.name",
1773
1871
  texture_name: "atlas.items",
1774
1872
  texture_data: {}
1775
- };
1873
+ });
1776
1874
  }
1777
- function generateTerrainTexture(config) {
1778
- return {
1779
- resource_pack_name: config.projectName,
1875
+ function generateTerrainTexture() {
1876
+ return stringifyJson({
1877
+ resource_pack_name: "pack.name",
1780
1878
  texture_name: "atlas.terrain",
1781
1879
  texture_data: {},
1782
1880
  num_mip_levels: 4,
1783
1881
  padding: 8
1784
- };
1882
+ });
1785
1883
  }
1786
1884
 
1787
- // src/engine/generator.ts
1788
- var TYPEGEN_FILTER_TEMPLATE_URLS = [
1789
- new URL("../../templates/filters/typegen/main.js", import.meta.url),
1790
- new URL("../templates/filters/typegen/main.js", import.meta.url)
1791
- ];
1792
- var BP_NAMESPACED_DIRECTORIES = [
1885
+ // src/engine/plan.ts
1886
+ var BP_DIRECTORIES = [
1793
1887
  "animation_controllers",
1794
1888
  "animations",
1795
1889
  "blocks",
@@ -1799,381 +1893,118 @@ var BP_NAMESPACED_DIRECTORIES = [
1799
1893
  "recipes",
1800
1894
  "spawn_rules",
1801
1895
  "structures",
1896
+ "texts",
1802
1897
  "trading"
1803
1898
  ];
1804
- var RP_NAMESPACED_DIRECTORIES = [
1899
+ var RP_DIRECTORIES = [
1805
1900
  "animation_controllers",
1806
1901
  "animations",
1807
1902
  "attachables",
1808
1903
  "entity",
1809
1904
  "fogs",
1905
+ "models",
1906
+ "models/entity",
1810
1907
  "particles",
1811
1908
  "render_controllers",
1812
1909
  "sounds",
1910
+ "texts",
1911
+ "textures",
1813
1912
  "ui"
1814
1913
  ];
1815
- var RP_NAMESPACED_NESTED_DIRECTORIES = [
1816
- ["models", "entity"],
1817
- ["textures", "blocks"],
1818
- ["textures", "entity"],
1819
- ["textures", "items"]
1914
+ var RP_TEXTURE_DIRECTORIES = ["blocks", "entity", "items"];
1915
+ var TYPESCRIPT_DIRECTORIES = [
1916
+ "data/scripts",
1917
+ "data/scripts/lib",
1918
+ "data/scripts/modules",
1919
+ "filters",
1920
+ "filters/rolldown",
1921
+ "filters/typegen"
1820
1922
  ];
1821
- function stringifyJson(value) {
1822
- return `${JSON.stringify(value, null, "\t")}
1823
- `;
1824
- }
1825
- async function writeJsonFile(filePath, value) {
1826
- await writeFile(filePath, stringifyJson(value), "utf8");
1827
- }
1828
- async function writeTextFile(filePath, value) {
1829
- await writeFile(filePath, value, "utf8");
1830
- }
1831
- async function createTypegenFilterMain() {
1832
- for (const templateUrl of TYPEGEN_FILTER_TEMPLATE_URLS) {
1833
- const templateFile = Bun.file(templateUrl);
1834
- const exists = await templateFile.exists().then((value) => value, () => false);
1835
- if (exists) {
1836
- return await templateFile.text();
1837
- }
1923
+ function planProject(config, versions) {
1924
+ const directories = new Set(["packs", "packs/BP", "packs/RP"]);
1925
+ const files = [];
1926
+ const marketplaceScope = getMarketplaceScopeSegments(config).join("/");
1927
+ for (const directory of BP_DIRECTORIES) {
1928
+ directories.add(`packs/BP/${directory}`);
1838
1929
  }
1839
- return await Bun.file(TYPEGEN_FILTER_TEMPLATE_URLS[0]).text();
1840
- }
1841
- function getScopedContentDirectory(basePath, folder, config) {
1842
- return join(basePath, folder, ...getMarketplaceScopeSegments(config));
1843
- }
1844
- function getScopedNestedContentDirectory(basePath, segments, config) {
1845
- return join(basePath, ...segments, ...getMarketplaceScopeSegments(config));
1846
- }
1847
- function shouldCreateDirectory(directory) {
1848
- return directory !== "." && directory !== "./" && directory !== ".\\";
1849
- }
1850
- async function generateProject(config) {
1851
- const destination = config.destination;
1852
- const bpPath = join(destination, "packs", "BP");
1853
- const rpPath = join(destination, "packs", "RP");
1854
- const bpScriptPath = join(bpPath, ...getScriptDirectorySegments(config));
1855
- const dataScriptsPath = join(destination, "data", "scripts");
1856
- const directories = [
1857
- destination,
1858
- join(destination, "packs"),
1859
- bpPath,
1860
- join(bpPath, "animation_controllers"),
1861
- join(bpPath, "animations"),
1862
- join(bpPath, "blocks"),
1863
- join(bpPath, "entities"),
1864
- join(bpPath, "items"),
1865
- join(bpPath, "loot_tables"),
1866
- join(bpPath, "recipes"),
1867
- join(bpPath, "spawn_rules"),
1868
- join(bpPath, "structures"),
1869
- join(bpPath, "texts"),
1870
- join(bpPath, "trading"),
1871
- rpPath,
1872
- join(rpPath, "animation_controllers"),
1873
- join(rpPath, "animations"),
1874
- join(rpPath, "attachables"),
1875
- join(rpPath, "entity"),
1876
- join(rpPath, "fogs"),
1877
- join(rpPath, "models"),
1878
- join(rpPath, "models", "entity"),
1879
- join(rpPath, "particles"),
1880
- join(rpPath, "render_controllers"),
1881
- join(rpPath, "sounds"),
1882
- join(rpPath, "texts"),
1883
- join(rpPath, "textures"),
1884
- join(rpPath, "textures", "blocks"),
1885
- join(rpPath, "textures", "entity"),
1886
- join(rpPath, "textures", "items"),
1887
- join(rpPath, "ui")
1888
- ];
1889
- if (config.useMarketplaceStructure) {
1890
- directories.push(...BP_NAMESPACED_DIRECTORIES.map((folder) => getScopedContentDirectory(bpPath, folder, config)), ...RP_NAMESPACED_DIRECTORIES.map((folder) => getScopedContentDirectory(rpPath, folder, config)), ...RP_NAMESPACED_NESTED_DIRECTORIES.map((segments) => getScopedNestedContentDirectory(rpPath, segments, config)));
1930
+ for (const directory of RP_DIRECTORIES) {
1931
+ directories.add(`packs/RP/${directory}`);
1891
1932
  }
1892
- if (config.scripting === "javascript") {
1893
- directories.push(join(bpPath, "scripts"), join(bpPath, "scripts", config.identifier), bpScriptPath);
1894
- }
1895
- if (config.scripting === "typescript") {
1896
- directories.push(join(destination, "data"), dataScriptsPath);
1897
- if (!config.useRgl) {
1898
- directories.push(join(bpPath, "scripts"), join(bpPath, "scripts", config.identifier), bpScriptPath);
1933
+ if (config.useMarketplaceStructure) {
1934
+ for (const directory of RP_TEXTURE_DIRECTORIES) {
1935
+ directories.add(`packs/RP/textures/${marketplaceScope}/${directory}`);
1936
+ }
1937
+ directories.add(`packs/RP/sounds/${marketplaceScope}`);
1938
+ } else {
1939
+ for (const directory of RP_TEXTURE_DIRECTORIES) {
1940
+ directories.add(`packs/RP/textures/${directory}`);
1899
1941
  }
1900
1942
  }
1901
- if (config.useRgl && config.scripting !== "none") {
1902
- directories.push(join(destination, "filters"), join(destination, "filters", "rolldown"), join(destination, "filters", "typegen"));
1903
- }
1904
- await Promise.all(directories.filter(shouldCreateDirectory).map((directory) => mkdir(directory, { recursive: true })));
1905
1943
  const bpUuid = crypto.randomUUID();
1906
1944
  const rpUuid = crypto.randomUUID();
1907
- const versions = await getMinecraftDependencyVersions();
1908
- const writes = [
1909
- writeJsonFile(join(bpPath, "manifest.json"), generateBpManifest(config, bpUuid, rpUuid, versions)),
1910
- writeJsonFile(join(rpPath, "manifest.json"), generateRpManifest(config, rpUuid, bpUuid)),
1911
- writeTextFile(join(bpPath, "texts", "en_US.lang"), generateLangFile(config.projectName, "behavior")),
1912
- writeTextFile(join(rpPath, "texts", "en_US.lang"), generateLangFile(config.projectName, "resource")),
1913
- writeJsonFile(join(rpPath, "blocks.json"), generateBlocksJson()),
1914
- writeJsonFile(join(rpPath, "sounds.json"), generateSoundsJson()),
1915
- writeJsonFile(join(rpPath, "sounds", "sound_definitions.json"), generateSoundDefinitions()),
1916
- writeJsonFile(join(rpPath, "textures", "item_texture.json"), generateItemTexture(config)),
1917
- writeJsonFile(join(rpPath, "textures", "terrain_texture.json"), generateTerrainTexture(config)),
1918
- writeJsonFile(join(rpPath, "textures", "flipbook_textures.json"), []),
1919
- writeJsonFile(join(bpPath, "texts", "languages.json"), ["en_US"]),
1920
- writeJsonFile(join(rpPath, "texts", "languages.json"), ["en_US"]),
1921
- writeTextFile(join(destination, ".gitignore"), generateGitignore(config)),
1922
- writeTextFile(join(destination, "README.md"), generateReadme(config))
1923
- ];
1924
- if (config.scripting !== "none") {
1925
- writes.push(writeJsonFile(join(destination, "package.json"), generatePackageJson(config, versions)));
1926
- }
1927
- if (config.scripting === "javascript") {
1928
- writes.push(writeTextFile(join(bpScriptPath, "main.js"), generateMainJs(config)));
1945
+ files.push({ path: "packs/BP/manifest.json", content: stringifyJson(generateBpManifest(config, bpUuid, rpUuid, versions)) }, { path: "packs/RP/manifest.json", content: stringifyJson(generateRpManifest(config, rpUuid, bpUuid, versions)) }, { path: "packs/BP/texts/en_US.lang", content: generateLangFile(config.projectName, "behavior") }, { path: "packs/BP/texts/languages.json", content: stringifyJson(["en_US"]) }, { path: "packs/RP/texts/en_US.lang", content: generateLangFile(config.projectName, "resource") }, { path: "packs/RP/texts/languages.json", content: stringifyJson(["en_US"]) }, { path: "packs/RP/blocks.json", content: generateBlocksJson() }, { path: "packs/RP/sounds.json", content: generateSoundsJson() }, { path: "packs/RP/sounds/sound_definitions.json", content: generateSoundDefinitions() }, { path: "packs/RP/textures/item_texture.json", content: generateItemTexture() }, { path: "packs/RP/textures/terrain_texture.json", content: generateTerrainTexture() }, { path: "packs/RP/textures/flipbook_textures.json", content: stringifyJson([]) }, { path: ".gitignore", content: generateGitignore(config) }, { path: ".gitattributes", content: generateGitattributes() }, { path: ".vscode/extensions.json", content: generateVscodeExtensions(config) }, { path: "README.md", content: generateReadme(config) });
1946
+ if (config.generateAgentsDoc) {
1947
+ files.push({ path: "AGENTS.md", content: generateAgentsDoc() });
1929
1948
  }
1930
1949
  if (config.scripting === "typescript") {
1931
- const tsconfigWrite = config.useRgl ? writeJsonFile(join(dataScriptsPath, "tsconfig.json"), generateScriptsTsConfig()) : writeJsonFile(join(destination, "tsconfig.json"), generateTsConfig(config));
1932
- writes.push(tsconfigWrite, writeJsonFile(join(destination, "dprint.json"), generateDprintConfig()), writeTextFile(join(dataScriptsPath, "main.ts"), generateMainTs(config)));
1933
- }
1934
- if (config.scripting !== "none" && config.useRgl) {
1935
- writes.push(writeJsonFile(join(destination, "config.json"), generateRglConfig(config)), writeTextFile(join(destination, "filters", "rolldown", "main.js"), generateRolldownFilterMain(config)), writeJsonFile(join(destination, "filters", "rolldown", "package.json"), generateRolldownFilterPackageJson()), writeJsonFile(join(destination, "filters", "typegen", "package.json"), generateTypegenFilterPackageJson()), writeTextFile(join(destination, "filters", "typegen", "main.js"), await createTypegenFilterMain()));
1936
- }
1937
- await Promise.all(writes);
1938
- }
1939
-
1940
- // src/provisioning/ai.ts
1941
- import { mkdir as mkdir2 } from "fs/promises";
1942
- import { join as join3 } from "path";
1943
-
1944
- // src/provisioning/mcp.ts
1945
- import { join as join2 } from "path";
1946
- function buildMcpServers() {
1947
- return {
1948
- exa: {
1949
- url: "https://mcp.exa.ai/mcp"
1950
- },
1951
- grep_app: {
1952
- command: "npx",
1953
- args: ["-y", "grep-mcp"]
1954
- },
1955
- context7: {
1956
- command: "npx",
1957
- args: ["-y", "@upstash/context7-mcp"]
1958
- }
1959
- };
1960
- }
1961
- async function generateMcpConfig(projectPath) {
1962
- const mcpPath = join2(projectPath, ".mcp.json");
1963
- const config = { mcpServers: buildMcpServers() };
1964
- await Bun.write(mcpPath, JSON.stringify(config, null, 2) + `
1965
- `).then(() => {
1966
- return;
1967
- }, () => {
1968
- return;
1969
- });
1970
- }
1971
-
1972
- // src/provisioning/ai.ts
1973
- var AI_EXCLUDE_LINES = [
1974
- "# AI tool files",
1975
- "/CLAUDE.md",
1976
- "/AGENTS.md",
1977
- "/.mcp.json",
1978
- "/opencode.json",
1979
- "/.claude/"
1980
- ];
1981
- function getTemplateUrls() {
1982
- return [
1983
- new URL("../templates/AGENTS.md", import.meta.url),
1984
- new URL("../../templates/AGENTS.md", import.meta.url)
1985
- ];
1986
- }
1987
- async function createAiDocContent() {
1988
- const templateUrls = getTemplateUrls();
1989
- for (const templateUrl of templateUrls) {
1990
- const templateFile = Bun.file(templateUrl);
1991
- const exists = await templateFile.exists().then((value) => value, () => false);
1992
- if (exists) {
1993
- return await templateFile.text();
1950
+ for (const directory of TYPESCRIPT_DIRECTORIES) {
1951
+ directories.add(directory);
1994
1952
  }
1953
+ files.push({ path: "package.json", content: generatePackageJson(config, versions) }, { path: "config.json", content: generateRglConfig(config) }, { path: "dprint.json", content: generateDprintConfig() }, { path: "data/scripts/main.ts", content: generateMainTs(config) }, { path: "data/scripts/tsconfig.json", content: generateScriptsTsConfig() }, { path: "filters/rolldown/main.js", content: generateRolldownFilterMain(config) }, { path: "filters/rolldown/package.json", content: generateRolldownFilterPackageJson() }, { path: "filters/typegen/main.js", content: generateTypegenFilterMain() }, { path: "filters/typegen/package.json", content: generateTypegenFilterPackageJson() });
1995
1954
  }
1996
- return await Bun.file(templateUrls[0]).text();
1997
- }
1998
- async function generateAiDoc(projectPath, aiSetup) {
1999
- const filename = getAiDocFilename(aiSetup);
2000
- if (filename === null) {
2001
- return;
2002
- }
2003
- const outputPath = join3(projectPath, filename);
2004
- const content = await createAiDocContent();
2005
- await Bun.write(outputPath, content).then(() => {
2006
- return;
2007
- }, () => {
2008
- return;
2009
- });
2010
- }
2011
- async function setupGitExcludes(projectPath) {
2012
- const gitInfoPath = join3(projectPath, ".git", "info");
2013
- const excludePath = join3(gitInfoPath, "exclude");
2014
- const directoryReady = await mkdir2(gitInfoPath, { recursive: true }).then(() => true, () => false);
2015
- if (!directoryReady) {
2016
- return;
2017
- }
2018
- const excludeFile = Bun.file(excludePath);
2019
- const excludeExists = await excludeFile.exists().then((exists) => exists, () => false);
2020
- const existingContent = excludeExists ? await excludeFile.text().then((content) => content, () => "") : "";
2021
- const existingLines = new Set(existingContent.split(/\r?\n/));
2022
- const missingLines = AI_EXCLUDE_LINES.filter((line) => !existingLines.has(line));
2023
- if (missingLines.length === 0) {
2024
- return;
2025
- }
2026
- const separator = existingContent.length > 0 && !existingContent.endsWith(`
2027
- `) ? `
2028
- ` : "";
2029
- const nextContent = `${existingContent}${separator}${missingLines.join(`
2030
- `)}
2031
- `;
2032
- await Bun.write(excludePath, nextContent).then(() => {
2033
- return;
2034
- }, () => {
2035
- return;
2036
- });
2037
- }
2038
- async function writeOpencodePluginConfig(projectPath) {
2039
- const configPath = join3(projectPath, "opencode.json");
2040
- const pluginId = "@dietrichgebert/ponytail";
2041
- const file = Bun.file(configPath);
2042
- const exists = await file.exists().then((value) => value, () => false);
2043
- let nextConfig = {
2044
- $schema: "https://opencode.ai/config.json",
2045
- plugin: [pluginId]
2046
- };
2047
- if (exists) {
2048
- try {
2049
- const parsed = await file.json();
2050
- const plugins = Array.isArray(parsed.plugin) ? [...parsed.plugin] : [];
2051
- if (!plugins.includes(pluginId)) {
2052
- plugins.push(pluginId);
2053
- }
2054
- nextConfig = { ...parsed, plugin: plugins };
2055
- } catch {}
2056
- }
2057
- await Bun.write(configPath, `${JSON.stringify(nextConfig, null, 4)}
2058
- `).then(() => {
2059
- return;
2060
- }, () => {
2061
- return;
2062
- });
1955
+ return { directories: [...directories].sort(), files };
2063
1956
  }
2064
- async function setupAi(projectPath, aiSetup, installPonytail) {
2065
- if (aiSetup === "none") {
2066
- return;
1957
+
1958
+ // src/engine/writer.ts
1959
+ import { mkdir, writeFile } from "fs/promises";
1960
+ import { dirname, join, resolve } from "path";
1961
+ async function writeProject(plan, destination) {
1962
+ const root = resolve(destination);
1963
+ await mkdir(root, { recursive: true });
1964
+ for (const directory of plan.directories) {
1965
+ await mkdir(join(root, directory), { recursive: true });
2067
1966
  }
2068
- await generateAiDoc(projectPath, aiSetup);
2069
- await setupGitExcludes(projectPath);
2070
- await generateMcpConfig(projectPath);
2071
- if (installPonytail && aiSetup === "other") {
2072
- await writeOpencodePluginConfig(projectPath);
1967
+ for (const file of plan.files) {
1968
+ const filePath = join(root, file.path);
1969
+ await mkdir(dirname(filePath), { recursive: true });
1970
+ await writeFile(filePath, file.content, "utf8");
2073
1971
  }
2074
1972
  }
2075
1973
 
2076
1974
  // src/provisioning/tools.ts
2077
- import { join as join4 } from "path";
2078
- function runCommand(command, cwd) {
2079
- return Promise.resolve().then(() => Bun.spawn({
2080
- cmd: command,
2081
- cwd,
2082
- stdout: "pipe",
2083
- stderr: "pipe"
2084
- })).then((process2) => process2.exited.then((code) => code === 0, () => false), () => false);
2085
- }
2086
- async function isCommandAvailable(command) {
2087
- const lookupCommand = process.platform === "win32" ? ["where", command] : ["which", command];
2088
- return runCommand(lookupCommand);
2089
- }
2090
- async function installRgl() {
2091
- if (await isCommandAvailable("rgl")) {
2092
- return true;
2093
- }
2094
- if (process.platform !== "win32") {
2095
- return false;
2096
- }
2097
- const installed = await runCommand([
2098
- "powershell",
2099
- "-Command",
2100
- "irm rgl.ink0rr.dev/install.ps1 | iex"
2101
- ]);
2102
- if (!installed) {
1975
+ async function runCommand(command, cwd) {
1976
+ try {
1977
+ const child = Bun.spawn({
1978
+ cmd: command,
1979
+ cwd,
1980
+ stdout: "ignore",
1981
+ stderr: "ignore"
1982
+ });
1983
+ const exitCode = await child.exited;
1984
+ return exitCode === 0;
1985
+ } catch {
2103
1986
  return false;
2104
1987
  }
2105
- return isCommandAvailable("rgl");
2106
1988
  }
2107
- async function installRockide() {
2108
- if (await isCommandAvailable("code")) {
2109
- return runCommand([
2110
- "code",
2111
- "--install-extension",
2112
- "rockide.rockide-vscode"
2113
- ]);
2114
- }
2115
- if (await isCommandAvailable("cursor")) {
2116
- return runCommand([
2117
- "cursor",
2118
- "--install-extension",
2119
- "rockide.rockide-vscode"
2120
- ]);
2121
- }
2122
- return false;
1989
+ function hasRgl() {
1990
+ return Bun.which("rgl") !== null;
2123
1991
  }
2124
1992
  async function installDependencies(projectPath) {
2125
- const pkgPath = join4(projectPath, "package.json");
2126
- const pkgExists = await Bun.file(pkgPath).exists();
2127
- if (!pkgExists) {
2128
- return true;
2129
- }
2130
- const command = await isCommandAvailable("bun") ? ["bun", "install"] : ["npm", "install"];
2131
- return runCommand(command, projectPath);
2132
- }
2133
- async function runProvisioning(config) {
2134
- const results = {
2135
- rgl: null,
2136
- rockide: null,
2137
- dependencies: null
2138
- };
2139
- if (config.useRgl) {
2140
- results.rgl = await installRgl();
2141
- }
2142
- if (config.installRockide) {
2143
- results.rockide = await installRockide();
2144
- }
2145
- if (config.scripting !== "none") {
2146
- results.dependencies = await installDependencies(config.destination);
2147
- }
2148
- return results;
1993
+ const command = Bun.which("bun") === null ? ["npm", "install"] : ["bun", "install"];
1994
+ return await runCommand(command, projectPath);
2149
1995
  }
2150
1996
 
2151
1997
  // src/tui/display.ts
2152
1998
  var import_picocolors = __toESM(require_picocolors(), 1);
1999
+
2000
+ // src/tui/theme.ts
2153
2001
  var teal = (value) => `\x1B[38;2;47;208;181m${value}\x1B[39m`;
2154
- function bgTeal(value) {
2155
- return `\x1B[48;2;47;208;181m${value}\x1B[49m`;
2156
- }
2157
- function whiteOnTeal(value) {
2158
- return bgTeal(`\x1B[97m${value}\x1B[39m`);
2159
- }
2002
+ var bgTeal = (value) => `\x1B[48;2;47;208;181m${value}\x1B[49m`;
2003
+ var whiteOnTeal = (value) => bgTeal(`\x1B[97m${value}\x1B[39m`);
2004
+
2005
+ // src/tui/display.ts
2160
2006
  function getScriptingLabel(choice) {
2161
- if (choice === "typescript") {
2162
- return "TypeScript";
2163
- }
2164
- if (choice === "javascript") {
2165
- return "JavaScript";
2166
- }
2167
- return "None";
2168
- }
2169
- function getAiSetupLabel(choice) {
2170
- if (choice === "claude") {
2171
- return "Claude";
2172
- }
2173
- if (choice === "other") {
2174
- return "Other";
2175
- }
2176
- return "None";
2007
+ return choice === "typescript" ? "TypeScript" : "None";
2177
2008
  }
2178
2009
  function getSelectedPackages(scriptPackages) {
2179
2010
  const packages = [];
@@ -2207,172 +2038,103 @@ function getTreeRoot(config) {
2207
2038
  }
2208
2039
  return destination.replace(/[\\/]+$/, "");
2209
2040
  }
2210
- function pushScopedFolder(lines, folderPrefix, scopePrefix, folder, config) {
2211
- lines.push(import_picocolors.default.dim(`${folderPrefix}${folder}/`));
2212
- if (config.useMarketplaceStructure) {
2213
- lines.push(import_picocolors.default.dim(`${scopePrefix}${getMarketplaceScopeLabel(config)}/`));
2214
- }
2215
- }
2216
- function showFolderTree(config) {
2217
- const lines = [import_picocolors.default.bold(import_picocolors.default.white(getTreeRoot(config)))];
2218
- lines.push(import_picocolors.default.dim("\u251C\u2500 packs/"));
2219
- lines.push(import_picocolors.default.dim("\u2502 \u251C\u2500 BP/"));
2220
- pushScopedFolder(lines, "\u2502 \u2502 \u251C\u2500 ", "\u2502 \u2502 \u2502 \u2514\u2500 ", "animation_controllers", config);
2221
- pushScopedFolder(lines, "\u2502 \u2502 \u251C\u2500 ", "\u2502 \u2502 \u2502 \u2514\u2500 ", "animations", config);
2222
- pushScopedFolder(lines, "\u2502 \u2502 \u251C\u2500 ", "\u2502 \u2502 \u2502 \u2514\u2500 ", "blocks", config);
2223
- pushScopedFolder(lines, "\u2502 \u2502 \u251C\u2500 ", "\u2502 \u2502 \u2502 \u2514\u2500 ", "entities", config);
2224
- pushScopedFolder(lines, "\u2502 \u2502 \u251C\u2500 ", "\u2502 \u2502 \u2502 \u2514\u2500 ", "items", config);
2225
- pushScopedFolder(lines, "\u2502 \u2502 \u251C\u2500 ", "\u2502 \u2502 \u2502 \u2514\u2500 ", "loot_tables", config);
2226
- pushScopedFolder(lines, "\u2502 \u2502 \u251C\u2500 ", "\u2502 \u2502 \u2502 \u2514\u2500 ", "recipes", config);
2227
- pushScopedFolder(lines, "\u2502 \u2502 \u251C\u2500 ", "\u2502 \u2502 \u2502 \u2514\u2500 ", "spawn_rules", config);
2228
- pushScopedFolder(lines, "\u2502 \u2502 \u251C\u2500 ", "\u2502 \u2502 \u2502 \u2514\u2500 ", "structures", config);
2229
- lines.push(import_picocolors.default.dim("\u2502 \u2502 \u251C\u2500 texts/"));
2230
- pushScopedFolder(lines, "\u2502 \u2502 \u251C\u2500 ", "\u2502 \u2502 \u2502 \u2514\u2500 ", "trading", config);
2231
- if (config.scripting === "javascript" || config.scripting === "typescript" && !config.useRgl) {
2232
- lines.push(import_picocolors.default.dim(`\u2502 \u2502 \u251C\u2500 scripts/${config.identifier}/${config.projectId}/`));
2233
- }
2234
- lines.push(import_picocolors.default.dim("\u2502 \u2502 \u2514\u2500 manifest.json"));
2235
- lines.push(import_picocolors.default.dim("\u2502 \u2514\u2500 RP/"));
2236
- pushScopedFolder(lines, "\u2502 \u251C\u2500 ", "\u2502 \u2502 \u2514\u2500 ", "animation_controllers", config);
2237
- pushScopedFolder(lines, "\u2502 \u251C\u2500 ", "\u2502 \u2502 \u2514\u2500 ", "animations", config);
2238
- pushScopedFolder(lines, "\u2502 \u251C\u2500 ", "\u2502 \u2502 \u2514\u2500 ", "attachables", config);
2239
- pushScopedFolder(lines, "\u2502 \u251C\u2500 ", "\u2502 \u2502 \u2514\u2500 ", "entity", config);
2240
- pushScopedFolder(lines, "\u2502 \u251C\u2500 ", "\u2502 \u2502 \u2514\u2500 ", "fogs", config);
2241
- lines.push(import_picocolors.default.dim("\u2502 \u251C\u2500 models/entity/"));
2242
- if (config.useMarketplaceStructure) {
2243
- lines.push(import_picocolors.default.dim(`\u2502 \u2502 \u2514\u2500 ${getMarketplaceScopeLabel(config)}/`));
2244
- }
2245
- pushScopedFolder(lines, "\u2502 \u251C\u2500 ", "\u2502 \u2502 \u2514\u2500 ", "particles", config);
2246
- pushScopedFolder(lines, "\u2502 \u251C\u2500 ", "\u2502 \u2502 \u2514\u2500 ", "render_controllers", config);
2247
- pushScopedFolder(lines, "\u2502 \u251C\u2500 ", "\u2502 \u2502 \u2514\u2500 ", "sounds", config);
2248
- lines.push(import_picocolors.default.dim("\u2502 \u251C\u2500 texts/"));
2249
- lines.push(import_picocolors.default.dim("\u2502 \u251C\u2500 textures/blocks/"));
2250
- if (config.useMarketplaceStructure) {
2251
- lines.push(import_picocolors.default.dim(`\u2502 \u2502 \u2514\u2500 ${getMarketplaceScopeLabel(config)}/`));
2252
- }
2253
- lines.push(import_picocolors.default.dim("\u2502 \u251C\u2500 textures/entity/"));
2254
- if (config.useMarketplaceStructure) {
2255
- lines.push(import_picocolors.default.dim(`\u2502 \u2502 \u2514\u2500 ${getMarketplaceScopeLabel(config)}/`));
2256
- }
2257
- lines.push(import_picocolors.default.dim("\u2502 \u251C\u2500 textures/items/"));
2258
- if (config.useMarketplaceStructure) {
2259
- lines.push(import_picocolors.default.dim(`\u2502 \u2502 \u2514\u2500 ${getMarketplaceScopeLabel(config)}/`));
2260
- }
2261
- pushScopedFolder(lines, "\u2502 \u251C\u2500 ", "\u2502 \u2502 \u2514\u2500 ", "ui", config);
2262
- lines.push(import_picocolors.default.dim("\u2502 \u251C\u2500 blocks.json"));
2263
- lines.push(import_picocolors.default.dim("\u2502 \u251C\u2500 sounds.json"));
2264
- lines.push(import_picocolors.default.dim("\u2502 \u251C\u2500 sound_definitions.json"));
2265
- lines.push(import_picocolors.default.dim("\u2502 \u251C\u2500 item_texture.json"));
2266
- lines.push(import_picocolors.default.dim("\u2502 \u251C\u2500 terrain_texture.json"));
2267
- lines.push(import_picocolors.default.dim("\u2502 \u251C\u2500 flipbook_textures.json"));
2268
- lines.push(import_picocolors.default.dim("\u2502 \u2514\u2500 manifest.json"));
2269
- if (config.scripting === "typescript") {
2270
- lines.push(import_picocolors.default.dim("\u251C\u2500 data/scripts/"));
2271
- if (config.useRgl) {
2272
- lines.push(import_picocolors.default.dim("\u2502 \u251C\u2500 main.ts"));
2273
- lines.push(import_picocolors.default.dim("\u2502 \u2514\u2500 tsconfig.json"));
2274
- lines.push(import_picocolors.default.dim("\u251C\u2500 filters/"));
2275
- lines.push(import_picocolors.default.dim("\u2502 \u251C\u2500 rolldown/"));
2276
- lines.push(import_picocolors.default.dim("\u2502 \u2502 \u251C\u2500 main.js"));
2277
- lines.push(import_picocolors.default.dim("\u2502 \u2502 \u2514\u2500 package.json"));
2278
- lines.push(import_picocolors.default.dim("\u2502 \u2514\u2500 typegen/"));
2279
- lines.push(import_picocolors.default.dim("\u2502 \u251C\u2500 main.js"));
2280
- lines.push(import_picocolors.default.dim("\u2502 \u2514\u2500 package.json"));
2281
- } else {
2282
- lines.push(import_picocolors.default.dim("\u2502 \u2514\u2500 main.ts"));
2283
- lines.push(import_picocolors.default.dim("\u251C\u2500 tsconfig.json"));
2041
+ function createTreeNode() {
2042
+ return { directories: new Map, files: [] };
2043
+ }
2044
+ function addPlanPath(root, path, isDirectory) {
2045
+ const segments = path.split("/").filter((segment) => segment.length > 0);
2046
+ const directorySegments = isDirectory ? segments : segments.slice(0, -1);
2047
+ let current = root;
2048
+ for (const segment of directorySegments) {
2049
+ let child = current.directories.get(segment);
2050
+ if (child === undefined) {
2051
+ child = createTreeNode();
2052
+ current.directories.set(segment, child);
2284
2053
  }
2285
- lines.push(import_picocolors.default.dim("\u251C\u2500 dprint.json"));
2286
- }
2287
- if (config.scripting === "javascript") {
2288
- lines.push(import_picocolors.default.dim(`\u251C\u2500 packs/BP/scripts/${config.identifier}/${config.projectId}/main.js`));
2289
- }
2290
- if (config.scripting !== "none") {
2291
- lines.push(import_picocolors.default.dim("\u251C\u2500 package.json"));
2292
- }
2293
- if (config.useRgl) {
2294
- lines.push(import_picocolors.default.dim("\u251C\u2500 config.json"));
2295
- }
2296
- const aiDocFilename = getAiDocFilename(config.aiSetup);
2297
- if (aiDocFilename !== null) {
2298
- lines.push(import_picocolors.default.dim(`\u251C\u2500 ${aiDocFilename}`));
2299
- lines.push(import_picocolors.default.dim("\u251C\u2500 .mcp.json"));
2300
- if (config.installPonytail && config.aiSetup === "other") {
2301
- lines.push(import_picocolors.default.dim("\u251C\u2500 opencode.json"));
2054
+ current = child;
2055
+ }
2056
+ const fileName = isDirectory ? undefined : segments.at(-1);
2057
+ if (fileName !== undefined) {
2058
+ current.files.push(fileName);
2059
+ }
2060
+ }
2061
+ function renderTreeNode(node, prefix) {
2062
+ const directoryEntries = [...node.directories.entries()].sort(([left], [right]) => left.localeCompare(right)).map(([name, child]) => ({ label: `${name}/`, child }));
2063
+ const fileEntries = [...node.files].sort((left, right) => left.localeCompare(right)).map((fileName) => ({ label: fileName, child: undefined }));
2064
+ const entries = [...directoryEntries, ...fileEntries];
2065
+ const lines = [];
2066
+ entries.forEach((entry, index) => {
2067
+ const isLast = index === entries.length - 1;
2068
+ lines.push(import_picocolors.default.dim(`${prefix}${isLast ? "\u2514\u2500 " : "\u251C\u2500 "}${entry.label}`));
2069
+ if (entry.child !== undefined) {
2070
+ lines.push(...renderTreeNode(entry.child, `${prefix}${isLast ? " " : "\u2502 "}`));
2302
2071
  }
2072
+ });
2073
+ return lines;
2074
+ }
2075
+ function renderPlanTree(plan, rootLabel) {
2076
+ const root = createTreeNode();
2077
+ for (const directory of plan.directories) {
2078
+ addPlanPath(root, directory, true);
2303
2079
  }
2304
- lines.push(import_picocolors.default.dim("\u251C\u2500 .gitignore"));
2305
- lines.push(import_picocolors.default.dim("\u2514\u2500 README.md"));
2306
- return lines.join(`
2080
+ for (const file of plan.files) {
2081
+ addPlanPath(root, file.path, false);
2082
+ }
2083
+ return [import_picocolors.default.bold(import_picocolors.default.white(rootLabel)), ...renderTreeNode(root, "")].join(`
2307
2084
  `);
2308
2085
  }
2309
- function showReview(config) {
2086
+ function showReview(config, plan) {
2310
2087
  const border = teal("\u2502");
2311
- const packages = config.scripting === "none" ? import_picocolors.default.dim("None") : getSelectedPackages(config.scriptPackages).length > 0 ? formatValue(getSelectedPackages(config.scriptPackages).join(", ")) : import_picocolors.default.dim("None");
2088
+ const packages = getSelectedPackages(config.scriptPackages);
2089
+ const scriptingRows = config.scripting === "typescript" && packages.length > 0 ? [
2090
+ `${border} ${import_picocolors.default.dim(padLabel("Scripting:"))} ${formatValue(getScriptingLabel(config.scripting))}`,
2091
+ `${border} ${import_picocolors.default.dim(padLabel("Packages:"))} ${formatValue(packages.join(", "))}`
2092
+ ] : [`${border} ${import_picocolors.default.dim(padLabel("Scripting:"))} ${formatValue(getScriptingLabel(config.scripting))}`];
2093
+ const scopeRows = config.useMarketplaceStructure ? [
2094
+ `${border} ${import_picocolors.default.dim(padLabel("Publisher ID:"))} ${formatValue(config.identifier)}`,
2095
+ `${border} ${import_picocolors.default.dim(padLabel("Project ID:"))} ${formatValue(config.projectId)}`,
2096
+ `${border} ${import_picocolors.default.dim(padLabel("Marketplace structure:"))} ${formatValue(getMarketplaceScopeLabel(config))}`
2097
+ ] : [`${border} ${import_picocolors.default.dim(padLabel("Marketplace structure:"))} ${import_picocolors.default.dim("Disabled")}`];
2312
2098
  const lines = [
2313
2099
  `${whiteOnTeal(" Spawnpack ")} ${import_picocolors.default.dim("Review your Bedrock addon scaffold")}`,
2314
2100
  "",
2315
2101
  `${border} ${import_picocolors.default.dim(padLabel("Project:"))} ${formatValue(config.projectName)}`,
2316
2102
  `${border} ${import_picocolors.default.dim(padLabel("Author:"))} ${config.author ? formatValue(config.author) : import_picocolors.default.dim("\u2014")}`,
2317
- ...config.useMarketplaceStructure ? [
2318
- `${border} ${import_picocolors.default.dim(padLabel("Publisher ID:"))} ${formatValue(config.identifier)}`,
2319
- `${border} ${import_picocolors.default.dim(padLabel("Project ID:"))} ${formatValue(config.projectId)}`,
2320
- `${border} ${import_picocolors.default.dim(padLabel("Marketplace structure:"))} ${formatValue(getMarketplaceScopeLabel(config))}`
2321
- ] : [
2322
- `${border} ${import_picocolors.default.dim(padLabel("Marketplace structure:"))} ${import_picocolors.default.dim("Disabled")}`
2323
- ],
2103
+ ...scopeRows,
2324
2104
  `${border} ${import_picocolors.default.dim(padLabel("Destination:"))} ${formatValue(config.destination)}`,
2325
- `${border} ${import_picocolors.default.dim(padLabel("Scripting:"))} ${formatValue(getScriptingLabel(config.scripting))}`,
2326
- `${border} ${import_picocolors.default.dim(padLabel("Packages:"))} ${packages}`,
2327
- `${border} ${import_picocolors.default.dim(padLabel("rgl:"))} ${formatToggle(config.useRgl)}`,
2328
- `${border} ${import_picocolors.default.dim(padLabel("Rockide:"))} ${formatToggle(config.installRockide)}`,
2329
- `${border} ${import_picocolors.default.dim(padLabel("AI Setup:"))} ${formatValue(getAiSetupLabel(config.aiSetup))}`,
2330
- ...config.aiSetup !== "none" ? [`${border} ${import_picocolors.default.dim(padLabel("Ponytail:"))} ${formatToggle(config.installPonytail)}`] : [],
2105
+ ...scriptingRows,
2106
+ `${border} ${import_picocolors.default.dim(padLabel("AGENTS.md:"))} ${formatToggle(config.generateAgentsDoc)}`,
2331
2107
  "",
2332
2108
  `${border} ${teal(import_picocolors.default.bold("Planned structure"))}`,
2333
- ...showFolderTree(config).split(`
2109
+ ...renderPlanTree(plan, getTreeRoot(config)).split(`
2334
2110
  `).map((line) => `${border} ${line}`)
2335
2111
  ];
2336
2112
  O2.message(lines.join(`
2337
2113
  `), { symbol: teal("\u25C6") });
2338
2114
  }
2339
- function showPostGeneration(config) {
2115
+ function showPostGeneration(config, state) {
2340
2116
  const nextSteps = [teal(import_picocolors.default.bold("Next steps"))];
2117
+ const usefulCommands = [teal(import_picocolors.default.bold("Useful commands"))];
2341
2118
  if (config.destination !== "." && config.destination !== "./") {
2342
2119
  nextSteps.push(` ${import_picocolors.default.cyan("cd")} ${formatValue(config.destination)}`);
2343
2120
  }
2344
- if (config.scripting !== "none") {
2345
- nextSteps.push(` ${import_picocolors.default.cyan("bun install")} ${import_picocolors.default.dim("or")} ${import_picocolors.default.cyan("npm install")} ${import_picocolors.default.dim("Install project dependencies")}`);
2346
- }
2347
- if (config.useRgl) {
2121
+ if (config.scripting === "typescript") {
2122
+ if (state.dependenciesInstalled === false) {
2123
+ nextSteps.push(` ${import_picocolors.default.yellow("Dependency install failed.")} Run ${import_picocolors.default.cyan("bun install")} ${import_picocolors.default.dim("or")} ${import_picocolors.default.cyan("npm install")} in the project folder.`);
2124
+ }
2125
+ if (!state.rglAvailable) {
2126
+ nextSteps.push(` ${import_picocolors.default.yellow("rgl was not found on your PATH.")} Install it with ${import_picocolors.default.cyan("curl -fsSL rgl.ink0rr.dev/install.sh | sh")} ${import_picocolors.default.dim("(Windows:")} ${import_picocolors.default.cyan("irm rgl.ink0rr.dev/install.ps1 | iex")}${import_picocolors.default.dim(")")}`);
2127
+ }
2348
2128
  nextSteps.push(` ${import_picocolors.default.cyan("rgl watch")} ${import_picocolors.default.dim("Watch files and rebuild/export while you work")}`);
2349
- }
2350
- const usefulCommands = [teal(import_picocolors.default.bold("Useful commands"))];
2351
- if (config.useRgl) {
2352
2129
  usefulCommands.push(` ${import_picocolors.default.cyan("rgl run")} ${import_picocolors.default.dim("Build for development")}`);
2353
2130
  usefulCommands.push(` ${import_picocolors.default.cyan("rgl run build")} ${import_picocolors.default.dim("Build for distribution")}`);
2354
- }
2355
- if (config.scripting === "typescript") {
2356
2131
  usefulCommands.push(` ${import_picocolors.default.cyan("bun run typecheck")} ${import_picocolors.default.dim("or")} ${import_picocolors.default.cyan("npm run typecheck")} ${import_picocolors.default.dim("Check TypeScript types")}`);
2132
+ usefulCommands.push(` ${import_picocolors.default.cyan("bun run format")} ${import_picocolors.default.dim("or")} ${import_picocolors.default.cyan("npm run format")} ${import_picocolors.default.dim("Format with dprint")}`);
2133
+ } else {
2134
+ nextSteps.push(` ${import_picocolors.default.dim("No dependency install or build step is required. Edit the pack JSON in packs/.")}`);
2357
2135
  }
2358
- if (config.scripting === "none" && !config.useRgl) {
2359
- usefulCommands.push(` ${import_picocolors.default.dim("No extra build commands needed for this scaffold")}`);
2360
- }
2361
- if (config.aiSetup !== "none") {
2362
- usefulCommands.push("");
2363
- usefulCommands.push(`${teal(import_picocolors.default.bold("Note"))} ${import_picocolors.default.dim(".mcp.json is ready to use \u2014 the configured MCP servers need no API keys.")}`);
2364
- }
2365
- if (config.installPonytail) {
2366
- usefulCommands.push("");
2367
- if (config.aiSetup === "claude") {
2368
- usefulCommands.push(`${teal(import_picocolors.default.bold("Ponytail"))} ${import_picocolors.default.dim("Run inside Claude Code:")}`);
2369
- usefulCommands.push(` ${import_picocolors.default.cyan("/plugin marketplace add DietrichGebert/ponytail")}`);
2370
- usefulCommands.push(` ${import_picocolors.default.cyan("/plugin install ponytail@ponytail")}`);
2371
- } else {
2372
- usefulCommands.push(`${teal(import_picocolors.default.bold("Ponytail"))} ${import_picocolors.default.dim("Added to opencode.json \u2014 installs on next opencode run.")}`);
2373
- }
2374
- }
2375
- O2.message([...nextSteps, "", ...usefulCommands].join(`
2136
+ const sections = usefulCommands.length > 1 ? [...nextSteps, "", ...usefulCommands] : nextSteps;
2137
+ O2.message(sections.join(`
2376
2138
  `), {
2377
2139
  symbol: teal("\u25B2")
2378
2140
  });
@@ -2380,12 +2142,6 @@ function showPostGeneration(config) {
2380
2142
 
2381
2143
  // src/tui/wizard.ts
2382
2144
  var import_picocolors2 = __toESM(require_picocolors(), 1);
2383
- var teal2 = (value) => `\x1B[38;2;47;208;181m${value}\x1B[39m`;
2384
- var bgTeal2 = (value) => `\x1B[48;2;47;208;181m${value}\x1B[49m`;
2385
- function abortWizard() {
2386
- pt(import_picocolors2.default.red("Spawnpack cancelled."));
2387
- return null;
2388
- }
2389
2145
  function toScriptPackages(selected) {
2390
2146
  return {
2391
2147
  server: selected.includes("server"),
@@ -2394,8 +2150,12 @@ function toScriptPackages(selected) {
2394
2150
  math: selected.includes("math")
2395
2151
  };
2396
2152
  }
2153
+ function abortWizard() {
2154
+ pt(import_picocolors2.default.red("Spawnpack cancelled."));
2155
+ return null;
2156
+ }
2397
2157
  async function runWizard() {
2398
- mt(`${bgTeal2(import_picocolors2.default.bold(import_picocolors2.default.white(" Spawnpack ")))} ${teal2("Initialize your Minecraft Bedrock addon")}`);
2158
+ mt(`${whiteOnTeal(import_picocolors2.default.bold(" Spawnpack "))} ${teal("Initialize your Minecraft Bedrock addon")}`);
2399
2159
  const projectName = await Ot({
2400
2160
  message: import_picocolors2.default.bold("Project name"),
2401
2161
  placeholder: "My Cool Addon",
@@ -2426,7 +2186,7 @@ async function runWizard() {
2426
2186
  return abortWizard();
2427
2187
  }
2428
2188
  const useMarketplaceStructure = await ot2({
2429
- message: `${import_picocolors2.default.bold("Marketplace Add-On structure?")} ${import_picocolors2.default.dim("Nest BP/RP content under publisher_id/project_id for better coexistence")}`,
2189
+ message: `${import_picocolors2.default.bold("Marketplace Add-On structure?")} ${import_picocolors2.default.dim("Nest RP textures and sounds under publisher_id/project_id")}`,
2430
2190
  initialValue: false
2431
2191
  });
2432
2192
  if (q(useMarketplaceStructure)) {
@@ -2460,16 +2220,15 @@ async function runWizard() {
2460
2220
  message: import_picocolors2.default.bold("Scripting API"),
2461
2221
  initialValue: "typescript",
2462
2222
  options: [
2463
- { value: "none", label: "None", hint: "manifest-only addon scaffold" },
2464
- { value: "javascript", label: "JavaScript", hint: "script entry with JS tooling" },
2465
- { value: "typescript", label: "TypeScript", hint: "recommended" }
2223
+ { value: "none", label: "None", hint: "manifest-only addon, no build step" },
2224
+ { value: "typescript", label: "TypeScript", hint: "recommended, built with rgl" }
2466
2225
  ]
2467
2226
  });
2468
2227
  if (q(scripting)) {
2469
2228
  return abortWizard();
2470
2229
  }
2471
2230
  if (useMarketplaceStructure || scripting !== "none") {
2472
- const publisherHint = useMarketplaceStructure ? "used for scoped folders like publisher_id/project_id" : "used for script folders and package naming";
2231
+ const publisherHint = useMarketplaceStructure ? "used for marketplace folders like publisher_id/project_id" : "used for script output paths and package naming";
2473
2232
  const publisherId = await Ot({
2474
2233
  message: `${import_picocolors2.default.bold("Publisher ID")} ${import_picocolors2.default.dim(publisherHint)}`,
2475
2234
  placeholder: defaultPublisherId,
@@ -2507,7 +2266,6 @@ async function runWizard() {
2507
2266
  projectIdValue = projectId.trim();
2508
2267
  }
2509
2268
  let scriptPackages = { ...DEFAULT_SCRIPT_PACKAGES };
2510
- let useRgl = false;
2511
2269
  if (scripting !== "none") {
2512
2270
  const selectedPackages = await yt({
2513
2271
  message: import_picocolors2.default.bold("Script packages"),
@@ -2524,46 +2282,15 @@ async function runWizard() {
2524
2282
  return abortWizard();
2525
2283
  }
2526
2284
  scriptPackages = toScriptPackages(Array.from(new Set(["server", ...selectedPackages])));
2527
- const rglChoice = await ot2({
2528
- message: `${import_picocolors2.default.bold("Use rgl?")} ${import_picocolors2.default.dim("fast Bedrock addon compiler, 16x faster than Regolith")}`,
2529
- initialValue: true
2530
- });
2531
- if (q(rglChoice)) {
2532
- return abortWizard();
2533
- }
2534
- useRgl = rglChoice;
2535
2285
  }
2536
- const installRockide = await ot2({
2537
- message: `${import_picocolors2.default.bold("Install Rockide?")} ${import_picocolors2.default.dim("VSCode extension for Bedrock JSON autocompletion")}`,
2286
+ const generateAgentsDoc = await ot2({
2287
+ message: `${import_picocolors2.default.bold("Generate AGENTS.md?")} ${import_picocolors2.default.dim("working rules for AI coding agents")}`,
2538
2288
  initialValue: true
2539
2289
  });
2540
- if (q(installRockide)) {
2541
- return abortWizard();
2542
- }
2543
- const aiSetup = await _t({
2544
- message: import_picocolors2.default.bold("AI setup"),
2545
- initialValue: "none",
2546
- options: [
2547
- { value: "none", label: "None", hint: "No AI configuration setup" },
2548
- { value: "claude", label: "Claude", hint: "generate CLAUDE.md and .mcp.json" },
2549
- { value: "other", label: "Other", hint: "generate AGENTS.md and .mcp.json" }
2550
- ]
2551
- });
2552
- if (q(aiSetup)) {
2290
+ if (q(generateAgentsDoc)) {
2553
2291
  return abortWizard();
2554
2292
  }
2555
- let installPonytail = false;
2556
- if (aiSetup !== "none") {
2557
- const ponytailChoice = await ot2({
2558
- message: `${import_picocolors2.default.bold("Install ponytail plugin?")} ${import_picocolors2.default.dim("Makes your AI agent think like the laziest senior dev in the room. The best code is the code you never wrote. https://github.com/DietrichGebert/ponytail")}`,
2559
- initialValue: true
2560
- });
2561
- if (q(ponytailChoice)) {
2562
- return abortWizard();
2563
- }
2564
- installPonytail = ponytailChoice;
2565
- }
2566
- const config = {
2293
+ return {
2567
2294
  projectName: trimmedProjectName,
2568
2295
  author: author.trim(),
2569
2296
  identifier: identifierValue,
@@ -2572,41 +2299,39 @@ async function runWizard() {
2572
2299
  scripting,
2573
2300
  scriptPackages: scripting === "none" ? { ...DEFAULT_SCRIPT_PACKAGES } : scriptPackages,
2574
2301
  useMarketplaceStructure,
2575
- useRgl: scripting === "none" ? false : useRgl,
2576
- aiSetup,
2577
- installRockide,
2578
- installPonytail
2302
+ generateAgentsDoc
2579
2303
  };
2580
- showReview(config);
2581
- const shouldGenerate = await ot2({
2582
- message: `${import_picocolors2.default.bold("Generate addon scaffold?")} ${import_picocolors2.default.dim("create the project files now")}`,
2583
- initialValue: true
2584
- });
2585
- if (q(shouldGenerate) || !shouldGenerate) {
2586
- return abortWizard();
2587
- }
2588
- return config;
2589
2304
  }
2590
2305
 
2591
2306
  // src/index.ts
2592
- var teal3 = (value) => `\x1B[38;2;47;208;181m${value}\x1B[39m`;
2593
2307
  var args = new Set(process.argv.slice(2));
2594
2308
  if (args.has("-v") || args.has("--version")) {
2595
2309
  const packageJson = await Bun.file(new URL("../package.json", import.meta.url)).json();
2596
2310
  console.log(packageJson.version);
2597
2311
  process.exit(0);
2598
2312
  }
2313
+ getMinecraftDependencyVersions();
2599
2314
  var config = await runWizard();
2600
2315
  if (config === null) {
2601
2316
  process.exit(0);
2602
2317
  }
2603
- var generation = fe();
2604
- generation.start(teal3("Generating your Bedrock addon scaffold..."));
2605
- await generateProject(config);
2606
- if (config.aiSetup !== "none") {
2607
- await setupAi(config.destination, config.aiSetup, config.installPonytail);
2318
+ var plan = planProject(config, await getMinecraftDependencyVersions());
2319
+ showReview(config, plan);
2320
+ var shouldGenerate = await ot2({
2321
+ message: `${import_picocolors3.default.bold("Generate addon scaffold?")} ${import_picocolors3.default.dim("create the project files now")}`,
2322
+ initialValue: true
2323
+ });
2324
+ if (q(shouldGenerate) || !shouldGenerate) {
2325
+ abortWizard();
2326
+ process.exit(0);
2608
2327
  }
2609
- await runProvisioning(config);
2328
+ var generation = fe();
2329
+ generation.start(teal("Generating your Bedrock addon scaffold..."));
2330
+ await writeProject(plan, config.destination);
2331
+ var dependenciesInstalled = config.scripting === "typescript" ? await installDependencies(config.destination) : null;
2610
2332
  generation.stop(import_picocolors3.default.green("Spawnpack scaffold generated."));
2611
- showPostGeneration(config);
2612
- gt(teal3("Your addon is ready."));
2333
+ showPostGeneration(config, {
2334
+ dependenciesInstalled,
2335
+ rglAvailable: hasRgl()
2336
+ });
2337
+ gt(teal("Your addon is ready."));