@forgeax/engine-devkit 0.1.3 → 0.1.6

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.
Files changed (53) hide show
  1. package/README.md +30 -4
  2. package/dist/.tsbuildinfo +1 -1
  3. package/dist/__tests__/cli-output.unit.test.d.ts +2 -0
  4. package/dist/__tests__/cli-output.unit.test.d.ts.map +1 -0
  5. package/dist/__tests__/gltf-source-key-diagnostics.integration.test.d.ts +2 -0
  6. package/dist/__tests__/gltf-source-key-diagnostics.integration.test.d.ts.map +1 -0
  7. package/dist/__tests__/preview-port.integration.test.d.ts +2 -0
  8. package/dist/__tests__/preview-port.integration.test.d.ts.map +1 -0
  9. package/dist/__tests__/rhi-debug-build-graph.integration.test.d.ts +2 -0
  10. package/dist/__tests__/rhi-debug-build-graph.integration.test.d.ts.map +1 -0
  11. package/dist/__tests__/sdk-update.unit.test.d.ts +2 -0
  12. package/dist/__tests__/sdk-update.unit.test.d.ts.map +1 -0
  13. package/dist/assets.d.ts.map +1 -1
  14. package/dist/bootstrap-commands.d.ts.map +1 -1
  15. package/dist/cli-output.d.ts +4 -0
  16. package/dist/cli-output.d.ts.map +1 -0
  17. package/dist/cli.mjs +728 -641
  18. package/dist/cli.mjs.map +1 -1
  19. package/dist/commands.d.ts.map +1 -1
  20. package/dist/host/__tests__/bootstrap-root-owner.test-d.d.ts +2 -0
  21. package/dist/host/__tests__/bootstrap-root-owner.test-d.d.ts.map +1 -0
  22. package/dist/host/project-bootstrap.d.ts +4 -2
  23. package/dist/host/project-bootstrap.d.ts.map +1 -1
  24. package/dist/host/resource-bootstrap.d.ts +5 -3
  25. package/dist/host/resource-bootstrap.d.ts.map +1 -1
  26. package/dist/host.d.ts +2 -1
  27. package/dist/host.d.ts.map +1 -1
  28. package/dist/index.d.ts +4 -0
  29. package/dist/index.d.ts.map +1 -1
  30. package/dist/index.mjs +662 -655
  31. package/dist/index.mjs.map +1 -1
  32. package/dist/init.d.ts +2 -1
  33. package/dist/init.d.ts.map +1 -1
  34. package/dist/project.d.ts +0 -1
  35. package/dist/project.d.ts.map +1 -1
  36. package/dist/rhi-debug/cli-context.d.ts.map +1 -1
  37. package/dist/sdk-bootstrap.d.ts +34 -0
  38. package/dist/sdk-bootstrap.d.ts.map +1 -0
  39. package/dist/sdk-cli.mjs +412 -66
  40. package/dist/sdk-cli.mjs.map +1 -1
  41. package/dist/sdk-install.d.ts.map +1 -1
  42. package/dist/sdk-update.d.ts +23 -0
  43. package/dist/sdk-update.d.ts.map +1 -0
  44. package/dist/tools/benchmark/__tests__/benchmark-vocabulary-owner.test-d.d.ts +2 -0
  45. package/dist/tools/benchmark/__tests__/benchmark-vocabulary-owner.test-d.d.ts.map +1 -0
  46. package/dist/tools/benchmark/harness.d.ts.map +1 -1
  47. package/dist/tools/browser-host.d.ts.map +1 -1
  48. package/dist/tools/native-preview.d.ts.map +1 -1
  49. package/dist/types.d.ts +7 -0
  50. package/dist/types.d.ts.map +1 -1
  51. package/package.json +29 -30
  52. package/dist/__tests__/tool-migration.test.d.ts +0 -2
  53. package/dist/__tests__/tool-migration.test.d.ts.map +0 -1
package/dist/sdk-cli.mjs CHANGED
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ import { execFile, spawn } from 'child_process';
2
3
  import { resolve, dirname, basename, relative, sep, isAbsolute } from 'path';
3
- import { pathToFileURL, fileURLToPath } from 'url';
4
- import { execFile } from 'child_process';
5
4
  import { readdir, mkdir, mkdtemp, cp, rename, rm, copyFile, readFile, realpath, writeFile, symlink, access, lstat, readlink, rmdir } from 'fs/promises';
6
5
  import { promisify } from 'util';
6
+ import { fileURLToPath } from 'url';
7
+ import { tmpdir } from 'os';
7
8
 
8
9
  var __defProp = Object.defineProperty;
9
10
  var __export = (target, all) => {
@@ -126,6 +127,7 @@ function createInitPlan(facts, sdk) {
126
127
  value: {
127
128
  root: facts.root,
128
129
  version: sdk?.sdkVersion ?? devkitVersion,
130
+ ...sdk === void 0 ? {} : { pnpmVersion: sdk.requirements.pnpm },
129
131
  archiveBacked: sdk !== void 0,
130
132
  dependencyChanges,
131
133
  scriptChanges
@@ -146,23 +148,8 @@ async function applyInitPlan(facts, plan, options) {
146
148
  for (const change of plan.scriptChanges) scripts[change.name] = change.to;
147
149
  manifest.scripts = scripts;
148
150
  if (plan.archiveBacked) {
149
- manifest.packageManager = "pnpm@10.33.2";
150
- const pnpmValue = manifest.pnpm;
151
- const pnpm = pnpmValue !== null && typeof pnpmValue === "object" ? pnpmValue : {};
152
- manifest.pnpm = {
153
- ...pnpm,
154
- onlyBuiltDependencies: [
155
- "@forgeax/engine-codec",
156
- "@forgeax/engine-fbx",
157
- "@forgeax/engine-wgpu-wasm",
158
- "esbuild"
159
- ],
160
- supportedArchitectures: {
161
- os: ["darwin", "linux", "win32"],
162
- cpu: ["x64", "arm64"],
163
- libc: ["glibc", "musl"]
164
- }
165
- };
151
+ if (plan.pnpmVersion === void 0) throw new Error("sdk-pnpm-version-missing");
152
+ manifest.packageManager = `pnpm@${plan.pnpmVersion}`;
166
153
  }
167
154
  await writeFile(resolve(facts.root, "package.json"), `${JSON.stringify(manifest, null, 2)}
168
155
  `);
@@ -4684,7 +4671,7 @@ var coerce = {
4684
4671
  };
4685
4672
  var NEVER = INVALID;
4686
4673
 
4687
- // ../engine-project/dist/index.mjs
4674
+ // ../project/dist/index.mjs
4688
4675
  var GuidString = external_exports.string().refine(
4689
4676
  (val) => AssetGuid.parse(val).ok,
4690
4677
  (val) => {
@@ -4789,7 +4776,7 @@ async function readJson(path) {
4789
4776
  function firstUnsupportedStandaloneRealm(entries, inheritedRealm = "engine") {
4790
4777
  for (const entry of entries) {
4791
4778
  const realm = entry.realm ?? inheritedRealm ?? "engine";
4792
- if (realm === "host") return { id: entry.id, realm };
4779
+ if (realm !== "engine") return { id: entry.id, realm };
4793
4780
  if (entry.group === true) {
4794
4781
  const unsupported = firstUnsupportedStandaloneRealm(
4795
4782
  entry.config,
@@ -4907,13 +4894,10 @@ async function readProjectFacts(rootInput = process.cwd()) {
4907
4894
  }
4908
4895
  };
4909
4896
  }
4910
- function isCommandError(value) {
4911
- if (value === null || typeof value !== "object" || Array.isArray(value)) return false;
4912
- const candidate = value;
4913
- return typeof candidate.code === "string" && typeof candidate.expected === "string" && typeof candidate.hint === "string" && candidate.detail !== null && typeof candidate.detail === "object" && !Array.isArray(candidate.detail);
4914
- }
4915
4897
  function commandError(cause, fallbackCode) {
4916
- if (isCommandError(cause)) return cause;
4898
+ if (cause !== null && typeof cause === "object" && "code" in cause && "expected" in cause && "hint" in cause && "detail" in cause) {
4899
+ return cause;
4900
+ }
4917
4901
  return {
4918
4902
  code: fallbackCode,
4919
4903
  expected: "the ForgeaX command to complete",
@@ -4955,6 +4939,260 @@ async function findSdkContext() {
4955
4939
  cursor = parent;
4956
4940
  }
4957
4941
  }
4942
+ var execFileAsync = promisify(execFile);
4943
+ var SDK_INIT_PATH = [".forgeax", "sdk-init.json"];
4944
+ var SDK_INIT_SCHEMA_VERSION = "1.0.0";
4945
+ function agentOnboarding(sdk, projectRoot) {
4946
+ const root2 = projectRoot ?? sdk.root;
4947
+ return {
4948
+ read: [
4949
+ resolve(root2, "AGENTS.md"),
4950
+ resolve(root2, "skills", "forgeax-engine-sdk", "SKILL.md"),
4951
+ resolve(root2, "skills", "forgeax-engine-sdk", "references", "feature-catalog.md")
4952
+ ],
4953
+ next: projectRoot === void 0 ? {
4954
+ cwd: root2,
4955
+ argv: ["node", "./bin/forgeax.mjs", "new", "../my-game"]
4956
+ } : { cwd: root2, argv: ["pnpm", "exec", "forgeax", "list", "--json"] }
4957
+ };
4958
+ }
4959
+ function sdkInitPath(sdk) {
4960
+ return resolve(sdk.root, ...SDK_INIT_PATH);
4961
+ }
4962
+ function commandFailure(code, expected, hint, detail = {}) {
4963
+ return { ok: false, error: { code, expected, hint, detail } };
4964
+ }
4965
+ function sdkProjectInstallArgs(store) {
4966
+ const common = [
4967
+ "install",
4968
+ "--frozen-lockfile",
4969
+ "--ignore-scripts",
4970
+ "--side-effects-cache=true",
4971
+ "--child-concurrency=1"
4972
+ ];
4973
+ return store === void 0 ? common : [...common, "--offline", "--config.trust-lockfile=true", "--store-dir", store];
4974
+ }
4975
+ function sdkBootstrapInstallArgs(store) {
4976
+ const common = [
4977
+ "install",
4978
+ "--frozen-lockfile",
4979
+ "--child-concurrency=1",
4980
+ "--side-effects-cache=true"
4981
+ ];
4982
+ return store === void 0 ? common : [...common, "--offline", "--config.trust-lockfile=true", "--store-dir", store];
4983
+ }
4984
+ function supportedPnpm(version) {
4985
+ const [major = 0, minor = 0] = version.split(".").map(Number);
4986
+ return major === 11 && minor >= 7;
4987
+ }
4988
+ async function currentPnpm() {
4989
+ try {
4990
+ const result = await execFileAsync("pnpm", ["--version"], { maxBuffer: 1024 * 1024 });
4991
+ const version = result.stdout.trim();
4992
+ if (version.length === 0) throw new Error("pnpm returned an empty version");
4993
+ return { ok: true, value: version };
4994
+ } catch (cause) {
4995
+ return commandFailure(
4996
+ "pnpm-unavailable",
4997
+ "pnpm 11.7.0 or newer in the pnpm 11 line to be available on PATH",
4998
+ "Install or activate pnpm 11, then rerun forgeax init from the SDK root.",
4999
+ { reason: cause instanceof Error ? cause.message : String(cause) }
5000
+ );
5001
+ }
5002
+ }
5003
+ function matchesState(sdk, state) {
5004
+ if (state === null || typeof state !== "object" || Array.isArray(state)) return false;
5005
+ const value = state;
5006
+ return value.schemaVersion === SDK_INIT_SCHEMA_VERSION && value.sdkVersion === sdk.manifest.sdkVersion && value.engineCommit === sdk.manifest.engineCommit && typeof value.pnpm === "string" && supportedPnpm(value.pnpm) && value.node === process.versions.node && value.platform === process.platform && value.arch === process.arch;
5007
+ }
5008
+ async function readSdkInitState(sdk) {
5009
+ try {
5010
+ const value = JSON.parse(await readFile(sdkInitPath(sdk), "utf8"));
5011
+ return matchesState(sdk, value) ? value : void 0;
5012
+ } catch {
5013
+ return void 0;
5014
+ }
5015
+ }
5016
+ async function requireSdkInitialization(sdk) {
5017
+ const state = await readSdkInitState(sdk);
5018
+ if (state === void 0) {
5019
+ return commandFailure(
5020
+ "sdk-not-initialized",
5021
+ "the downloaded SDK to be initialized for this Node/pnpm/platform tuple",
5022
+ "Run ./bin/forgeax init from the SDK root once, then run forgeax new outside the SDK.",
5023
+ {
5024
+ sdkRoot: sdk.root,
5025
+ sdkVersion: sdk.manifest.sdkVersion,
5026
+ pnpm: sdk.manifest.requirements.pnpm,
5027
+ node: process.versions.node,
5028
+ platform: process.platform,
5029
+ arch: process.arch
5030
+ }
5031
+ );
5032
+ }
5033
+ return { ok: true, value: state };
5034
+ }
5035
+ async function copyBootstrapInputs(sdk, root2) {
5036
+ const template2 = sdk.templates.get(sdk.defaultTemplate);
5037
+ if (template2 === void 0) throw new Error("sdk-default-template-missing");
5038
+ for (const name of ["package.json", "pnpm-lock.yaml", "pnpm-workspace.yaml"]) {
5039
+ await cp(resolve(template2, name), resolve(root2, name));
5040
+ }
5041
+ try {
5042
+ await cp(resolve(template2, ".npmrc"), resolve(root2, ".npmrc"));
5043
+ } catch (cause) {
5044
+ if (cause === null || typeof cause !== "object" || !("code" in cause) || cause.code !== "ENOENT") {
5045
+ throw cause;
5046
+ }
5047
+ }
5048
+ }
5049
+ async function sdkInitCommand(sdk, options = {}) {
5050
+ const pnpmResult = await currentPnpm();
5051
+ if (!pnpmResult.ok) return pnpmResult;
5052
+ if (!supportedPnpm(pnpmResult.value)) {
5053
+ return commandFailure(
5054
+ "pnpm-version-unsupported",
5055
+ "pnpm >=11.7.0 <12",
5056
+ "Activate pnpm 11.7.0 or newer in the pnpm 11 line, then rerun forgeax init.",
5057
+ { actual: pnpmResult.value, expected: sdk.manifest.requirements.pnpm }
5058
+ );
5059
+ }
5060
+ const state = {
5061
+ schemaVersion: SDK_INIT_SCHEMA_VERSION,
5062
+ sdkVersion: sdk.manifest.sdkVersion,
5063
+ engineCommit: sdk.manifest.engineCommit,
5064
+ pnpm: pnpmResult.value,
5065
+ node: process.versions.node,
5066
+ platform: process.platform,
5067
+ arch: process.arch
5068
+ };
5069
+ const report = {
5070
+ ...state,
5071
+ root: sdk.root,
5072
+ store: sdk.store === void 0 ? "registry" : "offline",
5073
+ onboarding: agentOnboarding(sdk)
5074
+ };
5075
+ if (options.dryRun === true || options.install === false) return { ok: true, value: report };
5076
+ let staging;
5077
+ try {
5078
+ staging = await mkdtemp(resolve(tmpdir(), "forgeax-sdk-init-"));
5079
+ await copyBootstrapInputs(sdk, staging);
5080
+ await execFileAsync("pnpm", sdkBootstrapInstallArgs(sdk.store), {
5081
+ cwd: staging,
5082
+ env: { ...process.env, CI: "true" },
5083
+ maxBuffer: 16 * 1024 * 1024
5084
+ });
5085
+ await mkdir(resolve(sdk.root, ".forgeax"), { recursive: true });
5086
+ await writeFile(sdkInitPath(sdk), `${JSON.stringify(state, null, 2)}
5087
+ `);
5088
+ return { ok: true, value: report };
5089
+ } catch (cause) {
5090
+ return commandFailure(
5091
+ "sdk-init-failed",
5092
+ "the SDK dependency closure to install and build native packages successfully",
5093
+ "Inspect the pnpm output, repair Node/pnpm or platform permissions, then rerun forgeax init from the SDK root.",
5094
+ { reason: cause instanceof Error ? cause.message : String(cause), sdkRoot: sdk.root }
5095
+ );
5096
+ } finally {
5097
+ if (staging !== void 0) await rm(staging, { recursive: true, force: true });
5098
+ }
5099
+ }
5100
+
5101
+ // src/sdk-update.ts
5102
+ var UPDATE_MIGRATION_RISK = "Install a newer SDK in a separate directory. Existing games remain pinned and are not migrated automatically; review release notes and migrate and test each game before changing its Engine version.";
5103
+ function parseVersion(version) {
5104
+ const match = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/.exec(
5105
+ version
5106
+ );
5107
+ if (match === null) return void 0;
5108
+ const major = match[1];
5109
+ const minor = match[2];
5110
+ const patch = match[3];
5111
+ if (major === void 0 || minor === void 0 || patch === void 0) return void 0;
5112
+ const prerelease = match[4]?.split(".");
5113
+ if (prerelease?.some(
5114
+ (identifier) => identifier.length === 0 || /^\d+$/.test(identifier) && /^0\d+/.test(identifier)
5115
+ )) {
5116
+ return void 0;
5117
+ }
5118
+ return {
5119
+ core: [major, minor, patch],
5120
+ ...prerelease === void 0 ? {} : { prerelease }
5121
+ };
5122
+ }
5123
+ function compareNumericIdentifier(current, latest) {
5124
+ if (current.length !== latest.length) return latest.length > current.length ? 1 : -1;
5125
+ if (current === latest) return 0;
5126
+ return latest > current ? 1 : -1;
5127
+ }
5128
+ function comparePrerelease(current, latest) {
5129
+ const length = Math.max(current.length, latest.length);
5130
+ for (let index = 0; index < length; index += 1) {
5131
+ const currentIdentifier = current[index];
5132
+ const latestIdentifier = latest[index];
5133
+ if (currentIdentifier === void 0) return 1;
5134
+ if (latestIdentifier === void 0) return -1;
5135
+ if (currentIdentifier === latestIdentifier) continue;
5136
+ const currentNumeric = /^\d+$/.test(currentIdentifier);
5137
+ const latestNumeric = /^\d+$/.test(latestIdentifier);
5138
+ if (currentNumeric && latestNumeric) {
5139
+ return compareNumericIdentifier(currentIdentifier, latestIdentifier);
5140
+ }
5141
+ if (currentNumeric !== latestNumeric) return latestNumeric ? -1 : 1;
5142
+ return latestIdentifier > currentIdentifier ? 1 : -1;
5143
+ }
5144
+ return 0;
5145
+ }
5146
+ function newerSdkVersion(currentVersion, latestVersion) {
5147
+ const current = parseVersion(currentVersion);
5148
+ const latest = parseVersion(latestVersion);
5149
+ if (current === void 0 || latest === void 0) return false;
5150
+ for (let index = 0; index < current.core.length; index += 1) {
5151
+ const precedence = compareNumericIdentifier(
5152
+ current.core[index] ?? "0",
5153
+ latest.core[index] ?? "0"
5154
+ );
5155
+ if (precedence !== 0) return precedence > 0;
5156
+ }
5157
+ if (current.prerelease !== void 0 && latest.prerelease === void 0) return true;
5158
+ if (current.prerelease === void 0 || latest.prerelease === void 0) return false;
5159
+ return comparePrerelease(current.prerelease, latest.prerelease) > 0;
5160
+ }
5161
+ function offline() {
5162
+ return ["1", "true"].includes((process.env.npm_config_offline ?? "").toLowerCase());
5163
+ }
5164
+ async function checkSdkUpdate(currentVersion) {
5165
+ if (process.env.FORGEAX_DISABLE_UPDATE_CHECK === "1") {
5166
+ return { status: "skipped", currentVersion, reason: "disabled" };
5167
+ }
5168
+ if (offline()) return { status: "skipped", currentVersion, reason: "offline" };
5169
+ try {
5170
+ const registry = process.env.npm_config_registry ?? "https://registry.npmjs.org/";
5171
+ const response = await fetch(
5172
+ new URL("@forgeax%2Fengine-sdk", `${registry.replace(/\/$/, "")}/`),
5173
+ {
5174
+ headers: { accept: "application/vnd.npm.install-v1+json" },
5175
+ signal: AbortSignal.timeout(2e3)
5176
+ }
5177
+ );
5178
+ if (!response.ok) throw new Error(`registry-http-${response.status}`);
5179
+ const metadata = await response.json();
5180
+ const latestVersion = metadata["dist-tags"]?.latest;
5181
+ if (typeof latestVersion !== "string") throw new Error("registry-latest-version-missing");
5182
+ return newerSdkVersion(currentVersion, latestVersion) ? {
5183
+ status: "available",
5184
+ currentVersion,
5185
+ latestVersion,
5186
+ migrationRisk: UPDATE_MIGRATION_RISK
5187
+ } : { status: "current", currentVersion, latestVersion };
5188
+ } catch (cause) {
5189
+ return {
5190
+ status: "unavailable",
5191
+ currentVersion,
5192
+ reason: cause instanceof Error ? cause.message : String(cause)
5193
+ };
5194
+ }
5195
+ }
4958
5196
  var PROJECT_SKILL_MOUNT_ROOTS = Object.freeze([
4959
5197
  ".codebuddy/skills",
4960
5198
  ".cursor/skills",
@@ -5246,7 +5484,7 @@ async function installProjectSkills(root2, provenance) {
5246
5484
  }
5247
5485
 
5248
5486
  // src/bootstrap-commands.ts
5249
- var execFileAsync = promisify(execFile);
5487
+ var execFileAsync2 = promisify(execFile);
5250
5488
  function isMissingPathError(cause) {
5251
5489
  return cause !== null && typeof cause === "object" && "code" in cause && cause.code === "ENOENT";
5252
5490
  }
@@ -5265,6 +5503,20 @@ async function canonicalProspectivePath(path) {
5265
5503
  }
5266
5504
  }
5267
5505
  }
5506
+ async function configureSdkStore(root2, store) {
5507
+ if (store === void 0) return;
5508
+ const path = resolve(root2, ".npmrc");
5509
+ let content = "";
5510
+ try {
5511
+ content = await readFile(path, "utf8");
5512
+ } catch (cause) {
5513
+ if (!isMissingPathError(cause)) throw cause;
5514
+ }
5515
+ const lines = content.split(/\r?\n/).filter((line) => line.trim().length > 0 && !/^\s*store-dir\s*=/.test(line));
5516
+ lines.push(`store-dir=${store}`);
5517
+ await writeFile(path, `${lines.join("\n")}
5518
+ `);
5519
+ }
5268
5520
  function containsOrEquals(parent, child) {
5269
5521
  const path = relative(parent, child);
5270
5522
  return path === "" || path !== ".." && !path.startsWith(`..${sep}`) && !isAbsolute(path);
@@ -5275,25 +5527,14 @@ function nodeSupported() {
5275
5527
  }
5276
5528
  function pnpmSupported(version) {
5277
5529
  const [major = 0, minor = 0] = version.split(".").map(Number);
5278
- return major === 10 && minor >= 33;
5279
- }
5280
- function sdkInstallArgs(store) {
5281
- if (store === void 0) return ["install", "--frozen-lockfile"];
5282
- return [
5283
- "install",
5284
- "--offline",
5285
- "--frozen-lockfile",
5286
- "--side-effects-cache=false",
5287
- "--store-dir",
5288
- store
5289
- ];
5530
+ return major === 11 && minor >= 7;
5290
5531
  }
5291
5532
  async function doctorCommand(options = {}) {
5292
5533
  const facts = await readProjectFacts(options.root);
5293
5534
  if (!facts.ok) return facts;
5294
5535
  let pnpm;
5295
5536
  try {
5296
- pnpm = (await execFileAsync("pnpm", ["--version"], { cwd: facts.value.root })).stdout.trim();
5537
+ pnpm = (await execFileAsync2("pnpm", ["--version"], { cwd: facts.value.root })).stdout.trim();
5297
5538
  } catch (cause) {
5298
5539
  return {
5299
5540
  ok: false,
@@ -5321,7 +5562,7 @@ async function doctorCommand(options = {}) {
5321
5562
  ok: false,
5322
5563
  error: {
5323
5564
  code: "pnpm-version-unsupported",
5324
- expected: "pnpm >=10.33.0 <11",
5565
+ expected: "pnpm >=11.7.0 <12",
5325
5566
  hint: "Enable the packageManager-declared pnpm version with Corepack and retry.",
5326
5567
  detail: { actual: pnpm }
5327
5568
  }
@@ -5360,12 +5601,20 @@ async function doctorCommand(options = {}) {
5360
5601
  };
5361
5602
  }
5362
5603
  async function initCommand(options = {}) {
5363
- const facts = await readProjectFacts(options.root);
5364
- if (!facts.ok) return facts;
5365
5604
  try {
5366
5605
  const sdk = await findSdkContext();
5606
+ const root2 = await canonicalProspectivePath(options.root ?? process.cwd());
5607
+ if (sdk !== void 0 && root2 === await canonicalProspectivePath(sdk.root)) {
5608
+ return sdkInitCommand(sdk, options);
5609
+ }
5610
+ const facts = await readProjectFacts(root2);
5611
+ if (!facts.ok) return facts;
5367
5612
  const plan = createInitPlan(facts.value, sdk?.manifest);
5368
5613
  if (!plan.ok) return plan;
5614
+ if (sdk !== void 0 && options.dryRun !== true) {
5615
+ const initialized = await requireSdkInitialization(sdk);
5616
+ if (!initialized.ok) return initialized;
5617
+ }
5369
5618
  const applied = await applyInitPlan(facts.value, plan.value, options);
5370
5619
  if (!applied.ok || options.dryRun === true) return applied;
5371
5620
  if (sdk !== void 0) {
@@ -5375,12 +5624,31 @@ async function initCommand(options = {}) {
5375
5624
  resolve(template2, "pnpm-lock.yaml"),
5376
5625
  resolve(facts.value.root, "pnpm-lock.yaml")
5377
5626
  );
5627
+ await copyFile(
5628
+ resolve(template2, "pnpm-workspace.yaml"),
5629
+ resolve(facts.value.root, "pnpm-workspace.yaml")
5630
+ );
5631
+ try {
5632
+ await readFile(resolve(facts.value.root, ".npmrc"), "utf8");
5633
+ } catch (cause) {
5634
+ if (!isMissingPathError(cause)) throw cause;
5635
+ try {
5636
+ await copyFile(resolve(template2, ".npmrc"), resolve(facts.value.root, ".npmrc"));
5637
+ } catch (templateCause) {
5638
+ if (!isMissingPathError(templateCause)) throw templateCause;
5639
+ }
5640
+ }
5378
5641
  await copySdkSkills(sdk, facts.value.root);
5379
5642
  await installProjectSkills(facts.value.root, sdk.manifest);
5643
+ await configureSdkStore(
5644
+ facts.value.root,
5645
+ sdk.store === void 0 ? void 0 : await canonicalProspectivePath(sdk.store)
5646
+ );
5380
5647
  }
5381
5648
  if (options.install === false) return applied;
5382
- const installArgs = sdk === void 0 ? ["install", "--frozen-lockfile=false"] : sdkInstallArgs(sdk.store);
5383
- await execFileAsync("pnpm", installArgs, {
5649
+ const store = sdk === void 0 || sdk.store === void 0 ? void 0 : await canonicalProspectivePath(sdk.store);
5650
+ const installArgs = sdk === void 0 ? ["install", "--frozen-lockfile=false"] : sdkProjectInstallArgs(store);
5651
+ await execFileAsync2("pnpm", installArgs, {
5384
5652
  cwd: facts.value.root,
5385
5653
  env: { ...process.env, CI: "true" },
5386
5654
  maxBuffer: 16 * 1024 * 1024
@@ -5458,12 +5726,15 @@ async function newCommand(options = {}) {
5458
5726
  value: { root: root2, template: templateId, sdkVersion: sdk.manifest.sdkVersion }
5459
5727
  };
5460
5728
  }
5729
+ const initialized = await requireSdkInitialization(sdk);
5730
+ if (!initialized.ok) return initialized;
5461
5731
  const parent = dirname(root2);
5462
5732
  await mkdir(parent, { recursive: true });
5463
5733
  let staging = await mkdtemp(
5464
5734
  resolve(parent, `.${basename(root2)}.forgeax-staging-`)
5465
5735
  );
5466
5736
  const committedNames = [];
5737
+ let committed = false;
5467
5738
  try {
5468
5739
  for (const name of await readdir(template2)) {
5469
5740
  await cp(resolve(template2, name), resolve(staging, name), {
@@ -5474,14 +5745,12 @@ async function newCommand(options = {}) {
5474
5745
  }
5475
5746
  await copySdkSkills(sdk, staging);
5476
5747
  await installProjectSkills(staging, sdk.manifest);
5477
- await execFileAsync("pnpm", sdkInstallArgs(sdk.store), {
5478
- cwd: staging,
5479
- env: { ...process.env, CI: "true" },
5480
- maxBuffer: 16 * 1024 * 1024
5481
- });
5748
+ const store = sdk.store === void 0 ? void 0 : await canonicalProspectivePath(sdk.store);
5749
+ await configureSdkStore(staging, store);
5482
5750
  if (!targetExists) {
5483
5751
  await rename(staging, root2);
5484
5752
  staging = void 0;
5753
+ committedNames.push(...await readdir(root2));
5485
5754
  } else {
5486
5755
  for (const name of await readdir(staging)) {
5487
5756
  await rename(resolve(staging, name), resolve(root2, name));
@@ -5490,15 +5759,41 @@ async function newCommand(options = {}) {
5490
5759
  await rm(staging, { recursive: true, force: true });
5491
5760
  staging = void 0;
5492
5761
  }
5762
+ committed = true;
5763
+ await execFileAsync2("pnpm", sdkProjectInstallArgs(store), {
5764
+ cwd: root2,
5765
+ env: { ...process.env, CI: "true" },
5766
+ maxBuffer: 16 * 1024 * 1024
5767
+ });
5768
+ const sdkUpdate = await checkSdkUpdate(sdk.manifest.sdkVersion);
5493
5769
  return {
5494
5770
  ok: true,
5495
- value: { root: root2, template: templateId, sdkVersion: sdk.manifest.sdkVersion }
5771
+ value: {
5772
+ root: root2,
5773
+ template: templateId,
5774
+ sdkVersion: sdk.manifest.sdkVersion,
5775
+ onboarding: agentOnboarding(sdk, root2),
5776
+ sdkUpdate
5777
+ }
5496
5778
  };
5497
5779
  } catch (cause) {
5498
5780
  if (staging !== void 0) await rm(staging, { recursive: true, force: true });
5499
- await Promise.all(
5500
- committedNames.map((name) => rm(resolve(root2, name), { recursive: true, force: true }))
5501
- );
5781
+ if (committed) {
5782
+ if (!targetExists) {
5783
+ await rm(root2, { recursive: true, force: true });
5784
+ return { ok: false, error: commandError(cause, "project-create-failed") };
5785
+ }
5786
+ const cleanupNames = /* @__PURE__ */ new Set([...committedNames, "node_modules"]);
5787
+ await Promise.all(
5788
+ [...cleanupNames].map(
5789
+ (name) => rm(resolve(root2, name), { recursive: true, force: true })
5790
+ )
5791
+ );
5792
+ } else {
5793
+ await Promise.all(
5794
+ committedNames.map((name) => rm(resolve(root2, name), { recursive: true, force: true }))
5795
+ );
5796
+ }
5502
5797
  return { ok: false, error: commandError(cause, "project-create-failed") };
5503
5798
  }
5504
5799
  } catch (cause) {
@@ -5506,11 +5801,42 @@ async function newCommand(options = {}) {
5506
5801
  }
5507
5802
  }
5508
5803
 
5804
+ // src/cli-output.ts
5805
+ function agentOnboardingLines(value) {
5806
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return [];
5807
+ const onboarding = value.onboarding;
5808
+ if (onboarding === null || typeof onboarding !== "object" || Array.isArray(onboarding)) return [];
5809
+ const candidate = onboarding;
5810
+ const lines = Array.isArray(candidate.read) ? candidate.read.filter((path) => typeof path === "string").map((path) => `[forgeax] read: ${path}`) : [];
5811
+ if (typeof candidate.next?.cwd === "string" && Array.isArray(candidate.next.argv) && candidate.next.argv.every((part) => typeof part === "string")) {
5812
+ lines.push(`[forgeax] next cwd: ${candidate.next.cwd}`);
5813
+ lines.push(`[forgeax] next: ${candidate.next.argv.join(" ")}`);
5814
+ }
5815
+ return lines;
5816
+ }
5817
+ function sdkUpdateLines(value) {
5818
+ if (value === null || typeof value !== "object" || Array.isArray(value)) return [];
5819
+ const update = value.sdkUpdate;
5820
+ if (update === null || typeof update !== "object" || Array.isArray(update)) return [];
5821
+ const candidate = update;
5822
+ if (candidate.status !== "available" || typeof candidate.currentVersion !== "string" || typeof candidate.latestVersion !== "string" || typeof candidate.migrationRisk !== "string") {
5823
+ return [];
5824
+ }
5825
+ return [
5826
+ `[forgeax] update available: @forgeax/engine-sdk ${candidate.currentVersion} -> ${candidate.latestVersion}`,
5827
+ `[forgeax] update note: ${candidate.migrationRisk}`
5828
+ ];
5829
+ }
5830
+ function renderForgeaxUsage() {
5831
+ return "Usage: forgeax new [directory] [--template empty|game-default]\n forgeax <init|doctor|test|dev|build|package|serve|preview> [directory]\n forgeax package [directory] [--output release/game-web.zip]\n forgeax run <rhi.capture|rhi.summary|rhi.inspect> [options]\n forgeax run <operation-id> --input <request.json>\n forgeax asset <add|verify|inspect|list> [subject]\n forgeax shader check [path]\n forgeax plugin <install|uninstall> <module-or-id> [options]\n forgeax skill <install|verify> [--root directory]\n forgeax sdk install <directory> [--version VERSION]\n";
5832
+ }
5833
+
5509
5834
  // src/sdk-cli.ts
5510
- var args = process.argv.slice(2);
5835
+ var rawArgs = process.argv.slice(2);
5836
+ var args = [...rawArgs];
5511
5837
  var commandArg = args.shift();
5512
5838
  var command = commandArg;
5513
- var help = commandArg === "--help" || commandArg === "-h";
5839
+ var help = rawArgs.includes("--help") || rawArgs.includes("-h");
5514
5840
  var json = args.includes("--json");
5515
5841
  var dryRun = args.includes("--dry-run");
5516
5842
  var noInstall = args.includes("--no-install");
@@ -5520,23 +5846,43 @@ var positional = args.find(
5520
5846
  (value, index) => !value.startsWith("--") && (templateIndex < 0 || index !== templateIndex + 1)
5521
5847
  );
5522
5848
  var root = command === "new" || command === "init" || command === "doctor" ? positional ?? process.cwd() : process.cwd();
5523
- if (command === "new" || command === "init" || command === "doctor") {
5849
+ if (help) {
5850
+ process.stdout.write(renderForgeaxUsage());
5851
+ } else if (command === "new" || command === "init" || command === "doctor") {
5524
5852
  const result = await (command === "new" ? newCommand({ root, dryRun, ...template === void 0 ? {} : { template } }) : command === "init" ? initCommand({ root, dryRun, install: !noInstall }) : doctorCommand({ root}));
5525
5853
  const envelope = result.ok ? { schemaVersion: "1.0.0", command, ok: true, value: result.value } : { schemaVersion: "1.0.0", command, ok: false, error: result.error };
5526
5854
  if (json) process.stdout.write(`${JSON.stringify(envelope)}
5527
5855
  `);
5528
- else if (result.ok) process.stdout.write(`[forgeax] ${command} ready
5856
+ else if (result.ok) {
5857
+ process.stdout.write(`[forgeax] ${command} ready
5858
+ `);
5859
+ for (const line of agentOnboardingLines(result.value)) process.stdout.write(`${line}
5860
+ `);
5861
+ for (const line of sdkUpdateLines(result.value)) process.stdout.write(`${line}
5529
5862
  `);
5530
- else process.stderr.write(`[forgeax] ${result.error.code}: ${result.error.hint}
5863
+ } else process.stderr.write(`[forgeax] ${result.error.code}: ${result.error.hint}
5531
5864
  `);
5532
5865
  if (!result.ok) process.exitCode = 1;
5533
- } else if (command !== void 0 && !help) {
5534
- const cli = resolve(root, "node_modules", "@forgeax", "engine-devkit", "dist", "cli.mjs");
5535
- await import(pathToFileURL(cli).href);
5866
+ } else if (command !== void 0) {
5867
+ const localForgeax = resolve(
5868
+ root,
5869
+ "node_modules",
5870
+ ".bin",
5871
+ process.platform === "win32" ? "forgeax.cmd" : "forgeax"
5872
+ );
5873
+ const child = spawn(localForgeax, [command, ...args], {
5874
+ cwd: root,
5875
+ shell: process.platform === "win32",
5876
+ stdio: "inherit"
5877
+ });
5878
+ const exitCode = await new Promise((resolveExit, reject) => {
5879
+ child.once("error", reject);
5880
+ child.once("exit", resolveExit);
5881
+ });
5882
+ if (exitCode !== 0) process.exitCode = exitCode ?? 1;
5536
5883
  } else {
5537
- const output = "Usage: forgeax new [directory] [--template empty|game-default]\n forgeax <init|doctor|test|dev|build|serve|preview> [directory]\n forgeax skill <install|verify> # from a game project\n";
5538
- (help ? process.stdout : process.stderr).write(output);
5539
- if (!help) process.exitCode = 2;
5884
+ process.stderr.write(renderForgeaxUsage());
5885
+ process.exitCode = 2;
5540
5886
  }
5541
5887
  /*! Bundled license information:
5542
5888