@forgeax/game 0.3.5 → 0.3.7

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/main.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/main.ts
4
- import { resolve as resolve15 } from "node:path";
4
+ import { resolve as resolve17 } from "node:path";
5
5
 
6
6
  // src/mcp/protocol.ts
7
7
  var MCP_PROTOCOL_VERSION = "2024-11-05";
@@ -427,7 +427,8 @@ reasoning and game-code edits; the plugin owns the exact Engine Preview adapter.
427
427
 
428
428
  In an empty standalone directory, \`forgeax-game init\` creates the released Engine
429
429
  game and installs this guidance. In an existing exact Engine game it refreshes the
430
- binding idempotently. Standalone game source belongs in \`<game-root>/src\`; Studio's
430
+ binding idempotently. Follow \`forge.json\` and the installed Engine authoring layout
431
+ (v2 uses \`assets/\` and \`plugins[]\`), rather than prescribing \`src/\`. Studio's
431
432
  \`.forgeax/games/<slug>\` layout is a different hosted-project boundary and must not be
432
433
  invented here. Before claiming a requested game complete, replace the Empty template
433
434
  identity in \`forge.json\`, \`package.json\`, and README, keep tests/controls accurate,
@@ -438,7 +439,12 @@ transition or rule; renaming the Empty template test is not completion.
438
439
  Any run-tool error means the game is not previewed: never curl, reuse, open, or report
439
440
  an existing localhost port. HTTP 200 is not ownership evidence. Only a successful tool
440
441
  result containing \`preview.status: ready\`, root, build digest, instance ID, and its own
441
- \`preview_url\` authorizes a Preview claim.
442
+ \`preview_url\` authorizes a Preview claim, not a gameplay-verification claim.
443
+ For playable-game requests, check the requested interactions and visible feedback
444
+ with an available browser tool. If unavailable, report gameplay as unverified and
445
+ provide manual checks; do not stop useful implementation or invent test results.
446
+ Use the installed Engine contract for Host access and UI lifecycle, and verify
447
+ requested UI is mounted rather than silently skipping it when a Host lookup fails.
442
448
  Never substitute Studio, Editor, PATH, or a source checkout for the installed Engine
443
449
  release.`;
444
450
 
@@ -2269,6 +2275,7 @@ not running check requested; no Engine child was launched.`;
2269
2275
  `preview.state_file: ${result.paths.state}`,
2270
2276
  `preview.stdout_log: ${result.paths.stdout}`,
2271
2277
  `preview.stderr_log: ${result.paths.stderr}`,
2278
+ "This tool verifies Engine build and Preview ownership only; gameplay, input, and visible UI have not been tested by this tool.",
2272
2279
  "Open only this returned loopback URL. HTTP availability without this exact verified identity is not Preview evidence."
2273
2280
  ].join(`
2274
2281
  `);
@@ -2938,6 +2945,74 @@ function gameFileTools() {
2938
2945
  }
2939
2946
  ];
2940
2947
  }
2948
+ // package.json
2949
+ var package_default = {
2950
+ packageManager: "bun@1.4.0",
2951
+ name: "@forgeax/game",
2952
+ version: "0.3.7",
2953
+ private: false,
2954
+ type: "module",
2955
+ description: "@forgeax/game — an MCP/CLI connector for exact released ForgeaX Engine games and Engine-owned Preview.",
2956
+ main: "./dist/main.js",
2957
+ bin: {
2958
+ "forgeax-game": "dist/main.js",
2959
+ game: "dist/main.js"
2960
+ },
2961
+ engines: {
2962
+ node: ">=22.13.0"
2963
+ },
2964
+ publishConfig: {
2965
+ access: "public",
2966
+ registry: "https://registry.npmjs.org/"
2967
+ },
2968
+ files: [
2969
+ "dist",
2970
+ "assets",
2971
+ "docs/asset3d.md",
2972
+ "docs/release-0.3.0.md",
2973
+ "docs/release-0.3.1.md",
2974
+ "docs/release-0.3.2.md",
2975
+ "docs/release-0.3.3.md",
2976
+ "docs/release-0.3.4.md",
2977
+ "docs/release-0.3.5.md",
2978
+ "README.md"
2979
+ ],
2980
+ scripts: {
2981
+ build: "bun build.mjs",
2982
+ "release:check": "bun scripts/check-package-artifact.ts",
2983
+ acceptance: "bun scripts/accept-packed-consumer.ts",
2984
+ "acceptance:asset3d": "bun scripts/accept-asset3d-packed-consumer.ts",
2985
+ "verify:asset3d-linux": "bun scripts/verify-linux-asset3d-provider.ts",
2986
+ "release:publish": "bun scripts/publish-package.ts",
2987
+ prepack: "bun build.mjs",
2988
+ typecheck: "tsc --noEmit",
2989
+ test: "bun test test/*.test.ts",
2990
+ start: "bun src/main.ts",
2991
+ "mcp:inspect": "npx @modelcontextprotocol/inspector node dist/main.js"
2992
+ },
2993
+ keywords: [
2994
+ "forgeax",
2995
+ "mcp",
2996
+ "model-context-protocol",
2997
+ "game-development",
2998
+ "codex",
2999
+ "claude-code"
3000
+ ],
3001
+ license: "MIT",
3002
+ dependencies: {
3003
+ "@forgeax/engine-sdk": "0.1.26",
3004
+ pnpm: "11.7.0"
3005
+ },
3006
+ devDependencies: {
3007
+ "@types/bun": "^1.2.0",
3008
+ typescript: "^5.9.2"
3009
+ },
3010
+ directories: {
3011
+ doc: "docs",
3012
+ test: "test"
3013
+ },
3014
+ author: ""
3015
+ };
2941
3016
 
2942
3017
  // src/install/release-manifest.ts
2943
3018
  var RELEASE_IDENTITY_SCHEMA = "forgeax.game.release-identity/1";
@@ -2946,7 +3021,7 @@ var RELEASE_IDENTITY_MIME = "application/vnd.forgeax.game-release-identity+json"
2946
3021
  var RELEASE_IDENTITY = Object.freeze({
2947
3022
  schema: RELEASE_IDENTITY_SCHEMA,
2948
3023
  gamePackage: "@forgeax/game",
2949
- gameVersion: "0.3.5",
3024
+ gameVersion: package_default.version,
2950
3025
  gameBin: "forgeax-game",
2951
3026
  engineSdkPackage: ENGINE_SDK_PACKAGE,
2952
3027
  engineSdkVersion: ENGINE_VERSION,
@@ -3226,9 +3301,9 @@ async function startHttpMcpServer(spec, options) {
3226
3301
  }
3227
3302
 
3228
3303
  // src/cli/dispatch.ts
3229
- import { existsSync as existsSync12, readFileSync as readFileSync15, writeFileSync as writeFileSync11 } from "node:fs";
3304
+ import { existsSync as existsSync12, readFileSync as readFileSync17, writeFileSync as writeFileSync12 } from "node:fs";
3230
3305
  import { arch as arch2, homedir as homedir5, platform as platform2 } from "node:os";
3231
- import { join as join11, resolve as resolve14 } from "node:path";
3306
+ import { join as join12, resolve as resolve16 } from "node:path";
3232
3307
 
3233
3308
  // src/install/clients.ts
3234
3309
  import { homedir as homedir2 } from "node:os";
@@ -3356,7 +3431,7 @@ function launchSpec(mode) {
3356
3431
  }
3357
3432
  return {
3358
3433
  command: "npx",
3359
- args: ["-y", "-p", "@forgeax/game@0.3.5", "forgeax-game", "mcp"]
3434
+ args: ["-y", "-p", `@forgeax/game@${RELEASE_IDENTITY.gameVersion}`, "forgeax-game", "mcp"]
3360
3435
  };
3361
3436
  }
3362
3437
  function asset3dLaunchSpec(mode) {
@@ -4270,17 +4345,17 @@ var PROVIDER_BUNDLE_SCHEMA = "forgeax.asset3d-provider-bundle/1.0.0";
4270
4345
  var PROVIDER_RESULT_SCHEMA = "forgeax.asset3d-search-result/1.0.0";
4271
4346
  var PROVIDER_RECEIPT_SCHEMA = "forgeax.asset3d-search-receipt/1.0.0";
4272
4347
  var K0_PACKAGE_SHA256 = "4937fe00e2b919319c70c82db53ebe359b275ed662c026153ecb97f7601262b4";
4273
- var RESULT_SCHEMA_SHA256 = "ab6e5e1e794d5428efef362fe32d4c54b6541e12539bae5f6ff27680c9f380ba";
4348
+ var RESULT_SCHEMA_SHA256 = "f97a9aa8b2ce0662c37d314211aae52951cbd0b76b3a7c140dc608be4712c415";
4274
4349
  var RECEIPT_SCHEMA_SHA256 = "e0dc9e9fe9872f09af9fca6d0c17d22aca63c2b546c55c0b669c581f7059b9c4";
4275
- var ASSET3D_PROVIDER_COMMIT = "c181c48fbffc933a7ce9a0836f7878ca5e6d77e1";
4350
+ var ASSET3D_PROVIDER_COMMIT = "a274238712c1dced99e4aa3148a0d15904ce2a90";
4276
4351
  var BUNDLED_ASSET3D_PROVIDERS = Object.freeze({
4277
4352
  "darwin-arm64": {
4278
- sha256: "9492c507a3afe5cafcd50b5f782b313c8716c2e52f5def06592c6a100c0459db",
4279
- relativePath: "asset3d/provider/asset3d-search-provider-c181c48fbffc933a7ce9a0836f7878ca5e6d77e1-darwin-arm64.tar.gz"
4353
+ sha256: "4f2d971c037a32eaed20e51e13a88bff91d2ea8f4d37894526c089376c3bacba",
4354
+ relativePath: "asset3d/provider/asset3d-search-provider-a274238712c1dced99e4aa3148a0d15904ce2a90-darwin-arm64.tar.gz"
4280
4355
  },
4281
4356
  "linux-x64": {
4282
- sha256: "e6ac3df76c8b82f9fe2c063dd3f221324a9dad5176fd52535a5144b0ff6002d8",
4283
- relativePath: "asset3d/provider/asset3d-search-provider-c181c48fbffc933a7ce9a0836f7878ca5e6d77e1-linux-x64.tar.gz"
4357
+ sha256: "af97e7e267b558960156175c840c3c2d2cd7aabf68f1f72c7d64b6d50742a79e",
4358
+ relativePath: "asset3d/provider/asset3d-search-provider-a274238712c1dced99e4aa3148a0d15904ce2a90-linux-x64.tar.gz"
4284
4359
  }
4285
4360
  });
4286
4361
  var INSTALL_SCHEMA = "forgeax.asset3d-install/1.0.0";
@@ -4675,7 +4750,8 @@ function installSkills(projectRoot, tgz, clients, previous) {
4675
4750
  for (const client of clients) {
4676
4751
  const mount = SKILL_MOUNTS[client];
4677
4752
  const destination = resolve10(projectRoot, mount, SKILL_ID);
4678
- const projected = [...K0_FILES.map((file) => [file, extractK0(tgz, file)]), ["SKILL.md", extractK0(tgz, "PLATFORM.md")]];
4753
+ const adapter = readFileSync12(resolve10(dirname10(tgz), "adapter", "SKILL.md"));
4754
+ const projected = [...K0_FILES.map((file) => [`legacy/${file}`, extractK0(tgz, file)]), ["SKILL.md", adapter]];
4679
4755
  if (existsSync8(destination)) {
4680
4756
  const priorOwned = new Set(previous?.skills.filter((entry) => confined3(destination, entry.path)).map((entry) => entry.path) ?? []);
4681
4757
  const unexpected = filesUnder2(destination).filter((path) => !priorOwned.has(path));
@@ -4957,19 +5033,19 @@ import {
4957
5033
  cpSync as cpSync2,
4958
5034
  existsSync as existsSync9,
4959
5035
  fstatSync,
4960
- lstatSync as lstatSync8,
5036
+ lstatSync as lstatSync9,
4961
5037
  mkdirSync as mkdirSync10,
4962
5038
  openSync as openSync4,
4963
- readFileSync as readFileSync13,
5039
+ readFileSync as readFileSync14,
4964
5040
  readdirSync as readdirSync7,
4965
- realpathSync as realpathSync8,
5041
+ realpathSync as realpathSync9,
4966
5042
  renameSync as renameSync7,
4967
5043
  rmSync as rmSync6,
4968
5044
  statSync as statSync6,
4969
5045
  unlinkSync as unlinkSync3,
4970
5046
  writeFileSync as writeFileSync10
4971
5047
  } from "node:fs";
4972
- import { isAbsolute as isAbsolute4, relative as relative9, resolve as resolve11, sep as sep6 } from "node:path";
5048
+ import { isAbsolute as isAbsolute5, relative as relative10, resolve as resolve12, sep as sep7 } from "node:path";
4973
5049
 
4974
5050
  // src/asset3d/schema.ts
4975
5051
  var ID = /^[A-Za-z0-9._-]{1,128}$/;
@@ -4988,7 +5064,7 @@ var ERROR_CODES = new Set([
4988
5064
  "batch_timeout",
4989
5065
  "internal_error"
4990
5066
  ]);
4991
- var ROLES = new Set(["primary-model", "animation", "auxiliary-model", "texture", "metadata"]);
5067
+ var ROLES = new Set(["primary-pack", "auxiliary-pack", "primary-model", "animation", "auxiliary-model", "texture", "metadata"]);
4992
5068
  function exactKeys(record, allowed, field) {
4993
5069
  const extras = Object.keys(record).filter((key) => !allowed.includes(key));
4994
5070
  if (extras.length)
@@ -5078,9 +5154,12 @@ function parseProviderResult(input, expectedCommit, expectedOriginSetDigest) {
5078
5154
  }
5079
5155
  if (row.status !== "ok")
5080
5156
  throw new Error("provider_result_invalid: status");
5081
- exactKeys(row, ["status", "queryIndex", "query", "provider", "providerAssetId", "assetName", "deliveredFormat", "sha256", "bytes", "primaryModel", "manifest", "originSetDigest", "downloaded_to"], "success");
5157
+ const isPack = row.deliveredFormat === "pack";
5158
+ const primaryKey = isPack ? "primaryPack" : "primaryModel";
5159
+ const primaryRole = isPack ? "primary-pack" : "primary-model";
5160
+ exactKeys(row, ["status", "queryIndex", "query", "provider", "providerAssetId", "assetName", "deliveredFormat", "sha256", "bytes", primaryKey, "manifest", "originSetDigest", "downloaded_to"], "success");
5082
5161
  okCount++;
5083
- if (row.provider !== "ea-3d" || row.deliveredFormat !== "glb" || typeof row.providerAssetId !== "string" || !ID.test(row.providerAssetId) || row.providerAssetId === "." || row.providerAssetId === "..") {
5162
+ if (row.provider !== "ea-3d" || row.deliveredFormat !== "glb" && !isPack || typeof row.providerAssetId !== "string" || !ID.test(row.providerAssetId) || row.providerAssetId === "." || row.providerAssetId === "..") {
5084
5163
  throw new Error("provider_result_invalid: success identity");
5085
5164
  }
5086
5165
  const assetName = text(row.assetName, 1, 128, "assetName");
@@ -5108,13 +5187,18 @@ function parseProviderResult(input, expectedCommit, expectedOriginSetDigest) {
5108
5187
  if ((role === "primary-model" || role === "animation" || role === "auxiliary-model") && !path.toLowerCase().endsWith(".glb")) {
5109
5188
  throw new Error("provider_result_invalid: model entry must be GLB");
5110
5189
  }
5190
+ if ((role === "primary-pack" || role === "auxiliary-pack") && (!isPack || !/\.pack\.(json|ts)$/i.test(path))) {
5191
+ throw new Error("provider_result_invalid: pack entry must be authored Pack");
5192
+ }
5193
+ if (isPack && role === "primary-model")
5194
+ throw new Error("provider_result_invalid: multiple primary formats");
5111
5195
  return { path, role, bytes: entryBytes, sha256: item.sha256 };
5112
5196
  });
5113
5197
  const sorted = [...manifest].sort((left, right) => Buffer.from(left.path).compare(Buffer.from(right.path)));
5114
5198
  if (manifest.some((entry, index) => entry.path !== sorted[index].path))
5115
5199
  throw new Error("provider_result_invalid: manifest must be UTF-8 path sorted");
5116
- const primary = manifest.filter((entry) => entry.role === "primary-model");
5117
- if (primary.length !== 1 || row.primaryModel !== primary[0].path)
5200
+ const primary = manifest.filter((entry) => entry.role === primaryRole);
5201
+ if (primary.length !== 1 || row[primaryKey] !== primary[0].path)
5118
5202
  throw new Error("provider_result_invalid: primary model");
5119
5203
  if (manifest.reduce((sum, entry) => sum + entry.bytes, 0) !== itemBytes || aggregate(manifest) !== row.sha256) {
5120
5204
  throw new Error("provider_result_invalid: byte or aggregate digest mismatch");
@@ -5128,10 +5212,9 @@ function parseProviderResult(input, expectedCommit, expectedOriginSetDigest) {
5128
5212
  provider: "ea-3d",
5129
5213
  providerAssetId: row.providerAssetId,
5130
5214
  assetName,
5131
- deliveredFormat: "glb",
5132
5215
  sha256: row.sha256,
5133
5216
  bytes: itemBytes,
5134
- primaryModel: row.primaryModel,
5217
+ ...isPack ? { deliveredFormat: "pack", primaryPack: row.primaryPack } : { deliveredFormat: "glb", primaryModel: row.primaryModel },
5135
5218
  manifest,
5136
5219
  originSetDigest: expectedOriginSetDigest,
5137
5220
  ...row.downloaded_to === undefined ? {} : { downloaded_to: row.downloaded_to }
@@ -5142,24 +5225,117 @@ function parseProviderResult(input, expectedCommit, expectedOriginSetDigest) {
5142
5225
  return { schemaVersion: PROVIDER_RESULT_SCHEMA, total, succeeded, failed, results, ...receipt ? { receipt } : {} };
5143
5226
  }
5144
5227
 
5228
+ // src/asset3d/pack-readback.ts
5229
+ import { lstatSync as lstatSync8, readFileSync as readFileSync13, realpathSync as realpathSync8 } from "node:fs";
5230
+ import { isAbsolute as isAbsolute4, relative as relative9, resolve as resolve11, sep as sep6 } from "node:path";
5231
+ var UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
5232
+ function object(value) {
5233
+ if (!value || typeof value !== "object" || Array.isArray(value))
5234
+ throw new Error("engine_pack_readback_invalid");
5235
+ return value;
5236
+ }
5237
+ function readPackBuildCatalog(projectRoot, sourcePaths, buildValue) {
5238
+ const build = object(buildValue);
5239
+ const runtime = object(build.runtime);
5240
+ if (build.schemaVersion !== "1.0.0" || !Array.isArray(build.artifacts) || build.artifacts.length > 1e4) {
5241
+ throw new Error("engine_pack_build_manifest_invalid");
5242
+ }
5243
+ const dist = resolve11(projectRoot, "dist");
5244
+ const canonicalDist = realpathSync8(dist);
5245
+ if (canonicalDist !== resolve11(realpathSync8(projectRoot), "dist") || lstatSync8(dist).isSymbolicLink()) {
5246
+ throw new Error("engine_pack_build_path_escape");
5247
+ }
5248
+ const artifacts = new Map;
5249
+ let total = 0;
5250
+ for (const raw of build.artifacts) {
5251
+ const entry = object(raw);
5252
+ const path = safeRelativePath(entry.path);
5253
+ if (artifacts.has(path) || !Number.isSafeInteger(entry.bytes) || entry.bytes < 0 || entry.bytes > 256 * 1024 * 1024 || typeof entry.sha256 !== "string" || !/^[a-f0-9]{64}$/.test(entry.sha256)) {
5254
+ throw new Error("engine_pack_build_manifest_invalid");
5255
+ }
5256
+ total += entry.bytes;
5257
+ if (total > 1024 * 1024 * 1024)
5258
+ throw new Error("engine_pack_build_too_large");
5259
+ const file = resolve11(dist, path);
5260
+ const rel = relative9(canonicalDist, realpathSync8(file));
5261
+ if (isAbsolute4(rel) || rel === ".." || rel.startsWith(`..${sep6}`))
5262
+ throw new Error("engine_pack_build_path_escape");
5263
+ const stat = lstatSync8(file);
5264
+ if (!stat.isFile() || stat.isSymbolicLink() || stat.size !== entry.bytes)
5265
+ throw new Error("engine_pack_build_file_invalid");
5266
+ const bytes = readFileSync13(file);
5267
+ if (sha2562(bytes) !== entry.sha256)
5268
+ throw new Error("engine_pack_build_digest_mismatch");
5269
+ artifacts.set(path, path.endsWith(".json") ? bytes : Buffer.alloc(0));
5270
+ }
5271
+ const indexPath = safeRelativePath(runtime.packIndexUrl);
5272
+ const indexBytes = artifacts.get(indexPath);
5273
+ if (!indexBytes || indexBytes.length > 16 * 1024 * 1024)
5274
+ throw new Error("engine_pack_catalog_missing");
5275
+ const index = JSON.parse(indexBytes.toString("utf8"));
5276
+ if (!Array.isArray(index))
5277
+ throw new Error("engine_pack_catalog_invalid");
5278
+ const sources = new Set(sourcePaths);
5279
+ const rows = [];
5280
+ const seen = new Set;
5281
+ const inspect = [];
5282
+ for (const raw of index) {
5283
+ const row = object(raw);
5284
+ if (typeof row.sourcePath !== "string" || !sources.has(row.sourcePath))
5285
+ continue;
5286
+ if (typeof row.guid !== "string" || !UUID.test(row.guid) || seen.has(row.guid) || typeof row.kind !== "string" || typeof row.packageUrl !== "string" || !row.packageUrl.startsWith("/") || row.packageUrl.startsWith("//")) {
5287
+ throw new Error("engine_pack_catalog_invalid");
5288
+ }
5289
+ seen.add(row.guid);
5290
+ const packagePath = safeRelativePath(row.packageUrl.slice(1));
5291
+ const bytes = artifacts.get(packagePath);
5292
+ if (!bytes || bytes.length > 16 * 1024 * 1024)
5293
+ throw new Error("engine_pack_product_missing");
5294
+ const pack = object(JSON.parse(bytes.toString("utf8")));
5295
+ if (!Array.isArray(pack.assets))
5296
+ throw new Error("engine_pack_product_invalid");
5297
+ const matches = pack.assets.map(object).filter((asset) => asset.guid === row.guid && asset.kind === row.kind);
5298
+ if (matches.length !== 1)
5299
+ throw new Error("engine_pack_product_guid_missing");
5300
+ rows.push({
5301
+ guid: row.guid,
5302
+ kind: row.kind,
5303
+ source: row.sourcePath,
5304
+ sourcePath: row.sourcePath,
5305
+ ...typeof row.name === "string" ? { name: row.name } : {},
5306
+ packageUrl: row.packageUrl
5307
+ });
5308
+ inspect.push({ guid: row.guid, kind: row.kind, packagePath, packageSha256: sha2562(bytes), verified: true });
5309
+ }
5310
+ if (!rows.some((row) => row.kind === "scene" || row.kind === "mesh"))
5311
+ throw new Error("engine_pack_readback_missing_renderable");
5312
+ return {
5313
+ authority: "engine-build-catalog",
5314
+ verify: { artifacts: artifacts.size, bytes: total, catalogSha256: sha2562(indexBytes) },
5315
+ list: rows,
5316
+ rows,
5317
+ inspect
5318
+ };
5319
+ }
5320
+
5145
5321
  // src/asset3d/transaction.ts
5146
5322
  function confined4(root, candidate) {
5147
- const rel = relative9(root, candidate);
5148
- return rel === "" || !isAbsolute4(rel) && rel !== ".." && !rel.startsWith(`..${sep6}`);
5323
+ const rel = relative10(root, candidate);
5324
+ return rel === "" || !isAbsolute5(rel) && rel !== ".." && !rel.startsWith(`..${sep7}`);
5149
5325
  }
5150
5326
  function forgeaxRoot(projectRoot) {
5151
- return resolve11(projectRoot, ".forgeax");
5327
+ return resolve12(projectRoot, ".forgeax");
5152
5328
  }
5153
5329
  function journalPath(projectRoot, execution) {
5154
- return resolve11(forgeaxRoot(projectRoot), "asset3d-transactions", `${execution}.json`);
5330
+ return resolve12(forgeaxRoot(projectRoot), "asset3d-transactions", `${execution}.json`);
5155
5331
  }
5156
5332
  function providerResultPath(projectRoot, execution) {
5157
- return resolve11(forgeaxRoot(projectRoot), "asset3d-results", `${execution}.json`);
5333
+ return resolve12(forgeaxRoot(projectRoot), "asset3d-results", `${execution}.json`);
5158
5334
  }
5159
5335
  function readInstall(projectRoot) {
5160
5336
  let manifest;
5161
5337
  try {
5162
- manifest = JSON.parse(readFileSync13(resolve11(forgeaxRoot(projectRoot), "asset3d-install.json"), "utf8"));
5338
+ manifest = JSON.parse(readFileSync14(resolve12(forgeaxRoot(projectRoot), "asset3d-install.json"), "utf8"));
5163
5339
  } catch {
5164
5340
  throw new Error("asset3d_not_installed");
5165
5341
  }
@@ -5172,7 +5348,7 @@ function readJournal(projectRoot, execution) {
5172
5348
  throw new Error("asset3d_execution_invalid");
5173
5349
  let journal;
5174
5350
  try {
5175
- journal = JSON.parse(readFileSync13(journalPath(projectRoot, execution), "utf8"));
5351
+ journal = JSON.parse(readFileSync14(journalPath(projectRoot, execution), "utf8"));
5176
5352
  } catch {
5177
5353
  throw new Error("asset3d_execution_not_found");
5178
5354
  }
@@ -5187,7 +5363,7 @@ function updateJournal(projectRoot, journal, patch) {
5187
5363
  return next;
5188
5364
  }
5189
5365
  function beginAsset3d(projectRootInput, queries, options = {}) {
5190
- const projectRoot = realpathSync8(projectRootInput);
5366
+ const projectRoot = realpathSync9(projectRootInput);
5191
5367
  if (queries.length < 1 || queries.length > 16 || queries.some((query) => [...query].length < 1 || [...query].length > 200)) {
5192
5368
  throw new Error("asset3d_queries_invalid: expected 1..16 queries of 1..200 characters");
5193
5369
  }
@@ -5195,9 +5371,9 @@ function beginAsset3d(projectRootInput, queries, options = {}) {
5195
5371
  const release = resolveEngineRelease(projectRoot, options.carrierPluginRoot === undefined ? {} : { pluginRoot: options.carrierPluginRoot });
5196
5372
  const execution = randomUUID4();
5197
5373
  const relativeOutput = `workspace/asset3d/${execution}`;
5198
- const quarantine = resolve11(projectRoot, ".forgeax", "asset3d-quarantine");
5199
- const allowedQuarantineRoot = resolve11(quarantine, relativeOutput);
5200
- ensurePrivateDir(resolve11(projectRoot, ".forgeax", "asset3d-transactions"));
5374
+ const quarantine = resolve12(projectRoot, ".forgeax", "asset3d-quarantine");
5375
+ const allowedQuarantineRoot = resolve12(quarantine, relativeOutput);
5376
+ ensurePrivateDir(resolve12(projectRoot, ".forgeax", "asset3d-transactions"));
5201
5377
  ensurePrivateDir(allowedQuarantineRoot);
5202
5378
  const journal = {
5203
5379
  schemaVersion: TRANSACTION_SCHEMA,
@@ -5216,7 +5392,7 @@ function beginAsset3d(projectRootInput, queries, options = {}) {
5216
5392
  return { execution, output_dir: relativeOutput };
5217
5393
  }
5218
5394
  function asset3dSearchOutputDir(projectRootInput, execution, queries) {
5219
- const projectRoot = realpathSync8(projectRootInput);
5395
+ const projectRoot = realpathSync9(projectRootInput);
5220
5396
  const journal = readJournal(projectRoot, execution);
5221
5397
  if (journal.state !== "begun") {
5222
5398
  throw new Error(`asset3d_execution_state_invalid: ${journal.state}`);
@@ -5225,14 +5401,14 @@ function asset3dSearchOutputDir(projectRootInput, execution, queries) {
5225
5401
  throw new Error("asset3d_search_query_identity_mismatch");
5226
5402
  }
5227
5403
  const outputDir = `workspace/asset3d/${execution}`;
5228
- const expectedRoot = resolve11(forgeaxRoot(projectRoot), "asset3d-quarantine", outputDir);
5229
- if (journal.allowedQuarantineRoot !== expectedRoot || !existsSync9(expectedRoot) || realpathSync8(expectedRoot) !== expectedRoot) {
5404
+ const expectedRoot = resolve12(forgeaxRoot(projectRoot), "asset3d-quarantine", outputDir);
5405
+ if (journal.allowedQuarantineRoot !== expectedRoot || !existsSync9(expectedRoot) || realpathSync9(expectedRoot) !== expectedRoot) {
5230
5406
  throw new Error("asset3d_search_output_identity_mismatch");
5231
5407
  }
5232
5408
  return outputDir;
5233
5409
  }
5234
5410
  function recordAsset3dProviderResult(projectRootInput, execution, providerResult) {
5235
- const projectRoot = realpathSync8(projectRootInput);
5411
+ const projectRoot = realpathSync9(projectRootInput);
5236
5412
  const journal = readJournal(projectRoot, execution);
5237
5413
  if (journal.state !== "begun") {
5238
5414
  throw new Error(`asset3d_execution_state_invalid: ${journal.state}`);
@@ -5241,19 +5417,19 @@ function recordAsset3dProviderResult(projectRootInput, execution, providerResult
5241
5417
  if (bytes < 1 || bytes > MAX_JSON_BYTES) {
5242
5418
  throw new Error("provider_result_too_large: expected 1 byte..1 MiB");
5243
5419
  }
5244
- ensurePrivateDir(resolve11(forgeaxRoot(projectRoot), "asset3d-results"));
5420
+ ensurePrivateDir(resolve12(forgeaxRoot(projectRoot), "asset3d-results"));
5245
5421
  atomicWrite(providerResultPath(projectRoot, execution), providerResult, 384);
5246
5422
  updateJournal(projectRoot, journal, { state: "provider_complete" });
5247
5423
  }
5248
5424
  function fileBytesChecked(root, entry) {
5249
- const source = resolve11(root, entry.path);
5425
+ const source = resolve12(root, entry.path);
5250
5426
  if (!confined4(root, source))
5251
5427
  throw new Error("asset_manifest_escape");
5252
- const canonicalRoot = realpathSync8(root);
5253
- const canonicalSource = realpathSync8(source);
5428
+ const canonicalRoot = realpathSync9(root);
5429
+ const canonicalSource = realpathSync9(source);
5254
5430
  if (!confined4(canonicalRoot, canonicalSource))
5255
5431
  throw new Error("asset_manifest_escape");
5256
- const before = lstatSync8(source);
5432
+ const before = lstatSync9(source);
5257
5433
  if (!before.isFile() || before.isSymbolicLink())
5258
5434
  throw new Error("asset_manifest_not_regular");
5259
5435
  const fd = openSync4(source, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
@@ -5261,7 +5437,7 @@ function fileBytesChecked(root, entry) {
5261
5437
  const opened = fstatSync(fd);
5262
5438
  if (!opened.isFile() || opened.size !== entry.bytes)
5263
5439
  throw new Error("asset_manifest_bytes_mismatch");
5264
- const bytes = readFileSync13(fd);
5440
+ const bytes = readFileSync14(fd);
5265
5441
  if (sha2562(bytes) !== entry.sha256)
5266
5442
  throw new Error("asset_manifest_digest_mismatch");
5267
5443
  return bytes;
@@ -5270,9 +5446,9 @@ function fileBytesChecked(root, entry) {
5270
5446
  }
5271
5447
  }
5272
5448
  function acquireLock2(projectRoot, assetId, timeoutMs) {
5273
- const root = resolve11(forgeaxRoot(projectRoot), "asset3d-locks");
5449
+ const root = resolve12(forgeaxRoot(projectRoot), "asset3d-locks");
5274
5450
  ensurePrivateDir(root);
5275
- const path = resolve11(root, `${assetId}.lock`);
5451
+ const path = resolve12(root, `${assetId}.lock`);
5276
5452
  const token = randomUUID4();
5277
5453
  const deadline = Date.now() + timeoutMs;
5278
5454
  for (;; ) {
@@ -5283,7 +5459,7 @@ function acquireLock2(projectRoot, assetId, timeoutMs) {
5283
5459
  closeSync3(fd);
5284
5460
  return () => {
5285
5461
  try {
5286
- const current = JSON.parse(readFileSync13(path, "utf8"));
5462
+ const current = JSON.parse(readFileSync14(path, "utf8"));
5287
5463
  if (current.token === token)
5288
5464
  unlinkSync3(path);
5289
5465
  } catch {}
@@ -5332,17 +5508,17 @@ function engineAddedRows(projectRoot, asset) {
5332
5508
  if (!Array.isArray(subAssets)) {
5333
5509
  if (typeof asset.metaPath !== "string")
5334
5510
  throw new Error("engine_readback_missing_meta");
5335
- const metaPath = resolve11(projectRoot, asset.metaPath);
5511
+ const metaPath = resolve12(projectRoot, asset.metaPath);
5336
5512
  if (!confined4(projectRoot, metaPath))
5337
5513
  throw new Error("engine_readback_meta_escape");
5338
- const canonical = realpathSync8(metaPath);
5339
- const info = lstatSync8(metaPath);
5514
+ const canonical = realpathSync9(metaPath);
5515
+ const info = lstatSync9(metaPath);
5340
5516
  if (!confined4(projectRoot, canonical) || !info.isFile() || info.isSymbolicLink()) {
5341
5517
  throw new Error("engine_readback_meta_invalid");
5342
5518
  }
5343
5519
  let meta;
5344
5520
  try {
5345
- meta = JSON.parse(readFileSync13(metaPath, "utf8"));
5521
+ meta = JSON.parse(readFileSync14(metaPath, "utf8"));
5346
5522
  } catch {
5347
5523
  throw new Error("engine_readback_meta_invalid");
5348
5524
  }
@@ -5353,10 +5529,10 @@ function engineAddedRows(projectRoot, asset) {
5353
5529
  const projectPath = (value) => {
5354
5530
  if (typeof value !== "string")
5355
5531
  return value;
5356
- const absolute = resolve11(projectRoot, value);
5532
+ const absolute = resolve12(projectRoot, value);
5357
5533
  if (!confined4(projectRoot, absolute))
5358
5534
  throw new Error("engine_readback_path_escape");
5359
- return relative9(projectRoot, absolute).split(sep6).join("/");
5535
+ return relative10(projectRoot, absolute).split(sep7).join("/");
5360
5536
  };
5361
5537
  return subAssets.map((row) => ({ source: projectPath(asset.source), metaPath: projectPath(asset.metaPath), reused: asset.reused, ...row }));
5362
5538
  }
@@ -5369,24 +5545,38 @@ function engineReadback(projectRoot, assetRelative, add, carrierPluginRoot) {
5369
5545
  if (add && guids.length === 0)
5370
5546
  throw new Error("engine_readback_missing_guid");
5371
5547
  const inspect = guids.map((guid) => engineCommand(projectRoot, ["asset", "inspect", guid, "--json"], carrierPluginRoot).value);
5372
- const listed = Array.isArray(list.value) ? list.value : [];
5548
+ const listed = Array.isArray(list.value) ? list.value : Array.isArray(list.value?.assets) ? list.value.assets : [];
5373
5549
  for (const guid of guids)
5374
5550
  if (!listed.some((entry) => entry.guid === guid))
5375
5551
  throw new Error("engine_catalog_readback_missing");
5376
- return { ...add ? { add: add.value } : {}, verify: verify.value, list: list.value, inspect, rows };
5552
+ return { ...add ? { add: add.value } : {}, verify: verify.value, list: listed, inspect, rows };
5553
+ }
5554
+ function packReadback(projectRoot, item, carrierPluginRoot) {
5555
+ const release = resolveEngineRelease(projectRoot, carrierPluginRoot === undefined ? {} : { pluginRoot: carrierPluginRoot });
5556
+ const result = spawnSync4(process.execPath, [release.cliPath, "build", "--json"], {
5557
+ cwd: projectRoot,
5558
+ encoding: "utf8",
5559
+ timeout: 150000,
5560
+ maxBuffer: 8 * 1024 * 1024
5561
+ });
5562
+ if (result.error || result.status !== 0)
5563
+ throw new Error("engine_pack_build_failed");
5564
+ const envelope = parseEnvelope(result.stdout, "build");
5565
+ const prefix = `assets/3d/ea-3d/${item.providerAssetId}/`;
5566
+ return readPackBuildCatalog(projectRoot, item.manifest.map((entry) => prefix + entry.path), envelope.value);
5377
5567
  }
5378
5568
  function priorProvenance(destination) {
5379
5569
  try {
5380
- return JSON.parse(readFileSync13(resolve11(destination, ".forgeax-asset.json"), "utf8"));
5570
+ return JSON.parse(readFileSync14(resolve12(destination, ".forgeax-asset.json"), "utf8"));
5381
5571
  } catch {
5382
5572
  return;
5383
5573
  }
5384
5574
  }
5385
5575
  function liveFilesMatch(destination, item) {
5386
5576
  return item.manifest.every((entry) => {
5387
- const path = resolve11(destination, entry.path);
5577
+ const path = resolve12(destination, entry.path);
5388
5578
  try {
5389
- return confined4(destination, path) && statSync6(path).isFile() && statSync6(path).size === entry.bytes && sha2562(readFileSync13(path)) === entry.sha256;
5579
+ return confined4(destination, path) && statSync6(path).isFile() && statSync6(path).size === entry.bytes && sha2562(readFileSync14(path)) === entry.sha256;
5390
5580
  } catch {
5391
5581
  return false;
5392
5582
  }
@@ -5395,21 +5585,21 @@ function liveFilesMatch(destination, item) {
5395
5585
  function quarantineFiles(root, directory = root) {
5396
5586
  const files = [];
5397
5587
  for (const name of readdirSync7(directory)) {
5398
- const path = resolve11(directory, name);
5399
- const info = lstatSync8(path);
5588
+ const path = resolve12(directory, name);
5589
+ const info = lstatSync9(path);
5400
5590
  if (info.isSymbolicLink())
5401
5591
  throw new Error("asset_quarantine_symlink_rejected");
5402
5592
  if (info.isDirectory())
5403
5593
  files.push(...quarantineFiles(root, path));
5404
5594
  else if (info.isFile())
5405
- files.push(relative9(root, path).split(sep6).join("/"));
5595
+ files.push(relative10(root, path).split(sep7).join("/"));
5406
5596
  else
5407
5597
  throw new Error("asset_quarantine_special_file_rejected");
5408
5598
  }
5409
5599
  return files;
5410
5600
  }
5411
5601
  function validateQuarantine(journal, items) {
5412
- const root = realpathSync8(journal.allowedQuarantineRoot);
5602
+ const root = realpathSync9(journal.allowedQuarantineRoot);
5413
5603
  const expected = items.flatMap((item) => item.manifest.map((entry) => entry.path)).sort();
5414
5604
  if (new Set(expected).size !== expected.length)
5415
5605
  throw new Error("provider_result_invalid: cross-item manifest collision");
@@ -5418,10 +5608,10 @@ function validateQuarantine(journal, items) {
5418
5608
  throw new Error("asset_quarantine_undeclared_file");
5419
5609
  }
5420
5610
  function stageItem(projectRoot, journal, item, destination, prior) {
5421
- const root = realpathSync8(journal.allowedQuarantineRoot);
5422
- const stageRoot = resolve11(forgeaxRoot(projectRoot), "asset3d-staging", journal.execution, item.providerAssetId);
5611
+ const root = realpathSync9(journal.allowedQuarantineRoot);
5612
+ const stageRoot = resolve12(forgeaxRoot(projectRoot), "asset3d-staging", journal.execution, item.providerAssetId);
5423
5613
  rmSync6(stageRoot, { recursive: true, force: true });
5424
- ensurePrivateDir(resolve11(stageRoot, ".."));
5614
+ ensurePrivateDir(resolve12(stageRoot, ".."));
5425
5615
  if (existsSync9(destination))
5426
5616
  cpSync2(destination, stageRoot, { recursive: true, errorOnExist: true, force: false });
5427
5617
  else
@@ -5431,17 +5621,17 @@ function stageItem(projectRoot, journal, item, destination, prior) {
5431
5621
  for (const previous of previousFiles) {
5432
5622
  if (typeof previous.relativePath !== "string" || nextPaths.has(previous.relativePath))
5433
5623
  continue;
5434
- const stale = resolve11(stageRoot, previous.relativePath);
5624
+ const stale = resolve12(stageRoot, previous.relativePath);
5435
5625
  if (!confined4(stageRoot, stale))
5436
5626
  throw new Error("asset_provenance_path_escape");
5437
5627
  rmSync6(stale, { force: true });
5438
5628
  rmSync6(`${stale}.meta.json`, { force: true });
5439
5629
  }
5440
5630
  for (const entry of item.manifest) {
5441
- const target = resolve11(stageRoot, entry.path);
5631
+ const target = resolve12(stageRoot, entry.path);
5442
5632
  if (!confined4(stageRoot, target))
5443
5633
  throw new Error("asset_manifest_escape");
5444
- mkdirSync10(resolve11(target, ".."), { recursive: true, mode: 448 });
5634
+ mkdirSync10(resolve12(target, ".."), { recursive: true, mode: 448 });
5445
5635
  atomicWrite(target, fileBytesChecked(root, entry), 384);
5446
5636
  }
5447
5637
  return stageRoot;
@@ -5457,7 +5647,8 @@ function provenance(item, install, readback, refreshed) {
5457
5647
  originSetDigest: install.originSetDigest,
5458
5648
  aggregateSha256: item.sha256,
5459
5649
  bytes: item.bytes,
5460
- primaryModel: item.primaryModel,
5650
+ deliveredFormat: item.deliveredFormat,
5651
+ ...item.deliveredFormat === "pack" ? { primaryPack: item.primaryPack } : { primaryModel: item.primaryModel },
5461
5652
  engine: { version: ENGINE_VERSION, commit: ENGINE_COMMIT },
5462
5653
  refreshed,
5463
5654
  files: item.manifest.map((entry) => ({
@@ -5471,8 +5662,8 @@ function provenance(item, install, readback, refreshed) {
5471
5662
  };
5472
5663
  }
5473
5664
  function commitItem(projectRoot, initialJournal, install, item, refresh, lockTimeoutMs, carrierPluginRoot) {
5474
- const destination = resolve11(projectRoot, "assets", "3d", "ea-3d", item.providerAssetId);
5475
- const assetRoot = resolve11(projectRoot, "assets", "3d", "ea-3d");
5665
+ const destination = resolve12(projectRoot, "assets", "3d", "ea-3d", item.providerAssetId);
5666
+ const assetRoot = resolve12(projectRoot, "assets", "3d", "ea-3d");
5476
5667
  ensurePrivateDir(assetRoot);
5477
5668
  if (!confined4(assetRoot, destination))
5478
5669
  throw new Error("asset_destination_escape");
@@ -5483,29 +5674,30 @@ function commitItem(projectRoot, initialJournal, install, item, refresh, lockTim
5483
5674
  const priorDigest = typeof prior?.aggregateSha256 === "string" ? prior.aggregateSha256 : undefined;
5484
5675
  if (priorDigest === item.sha256 && liveFilesMatch(destination, item)) {
5485
5676
  const priorRows = Array.isArray(prior?.files) ? prior.files.flatMap((entry) => entry.engineRows ?? []) : [];
5486
- const baseReadback = engineReadback(projectRoot, `assets/3d/ea-3d/${item.providerAssetId}`, undefined, carrierPluginRoot);
5677
+ const baseReadback = item.deliveredFormat === "pack" ? packReadback(projectRoot, item, carrierPluginRoot) : engineReadback(projectRoot, `assets/3d/ea-3d/${item.providerAssetId}`, undefined, carrierPluginRoot);
5487
5678
  const guids = priorRows.flatMap((row) => typeof row.guid === "string" ? [row.guid] : []);
5488
5679
  const listed = Array.isArray(baseReadback.list) ? baseReadback.list : [];
5489
5680
  if (guids.length === 0 || guids.some((guid) => !listed.some((entry) => entry.guid === guid)))
5490
5681
  throw new Error("asset_reuse_readback_failed");
5491
- const readback = { ...baseReadback, inspect: guids.map((guid) => engineCommand(projectRoot, ["asset", "inspect", guid, "--json"], carrierPluginRoot).value) };
5682
+ const readback = item.deliveredFormat === "pack" ? baseReadback : { ...baseReadback, inspect: guids.map((guid) => engineCommand(projectRoot, ["asset", "inspect", guid, "--json"], carrierPluginRoot).value) };
5492
5683
  journal = updateJournal(projectRoot, journal, { state: "committed", previousDigest: priorDigest, newDigest: item.sha256 });
5493
- return { providerAssetId: item.providerAssetId, digest: item.sha256, bytes: item.bytes, reused: true, refreshed: false, sourcePath: relative9(projectRoot, destination), provenancePath: relative9(projectRoot, resolve11(destination, ".forgeax-asset.json")), engine: { version: ENGINE_VERSION, commit: ENGINE_COMMIT }, catalog: readback, rows: priorRows };
5684
+ return { providerAssetId: item.providerAssetId, digest: item.sha256, bytes: item.bytes, reused: true, refreshed: false, sourcePath: relative10(projectRoot, destination), provenancePath: relative10(projectRoot, resolve12(destination, ".forgeax-asset.json")), engine: { version: ENGINE_VERSION, commit: ENGINE_COMMIT }, catalog: readback, rows: priorRows };
5494
5685
  }
5495
5686
  if (priorDigest && priorDigest !== item.sha256 && !refresh)
5496
5687
  throw new Error("asset_changed");
5497
5688
  const stage = stageItem(projectRoot, journal, item, destination, prior);
5498
- const backup = resolve11(forgeaxRoot(projectRoot), "asset3d-backups", journal.execution, item.providerAssetId);
5499
- ensurePrivateDir(resolve11(backup, ".."));
5689
+ const backup = resolve12(forgeaxRoot(projectRoot), "asset3d-backups", journal.execution, item.providerAssetId);
5690
+ ensurePrivateDir(resolve12(backup, ".."));
5500
5691
  journal = updateJournal(projectRoot, journal, { state: "committing", destination, backupPath: existsSync9(destination) ? backup : undefined, previousDigest: priorDigest, newDigest: item.sha256 });
5501
5692
  if (existsSync9(destination))
5502
5693
  renameSync7(destination, backup);
5503
5694
  renameSync7(stage, destination);
5504
5695
  try {
5505
5696
  const assetRelative = `assets/3d/ea-3d/${item.providerAssetId}`;
5506
- const add = engineCommand(projectRoot, ["asset", "add", assetRelative, "--reimport-policy", "semantic-only", "--json"], carrierPluginRoot);
5507
- const readback = engineReadback(projectRoot, assetRelative, add, carrierPluginRoot);
5508
- atomicWrite(resolve11(destination, ".forgeax-asset.json"), `${JSON.stringify(provenance(item, install, readback, priorDigest !== undefined), null, 2)}
5697
+ const readback = item.deliveredFormat === "pack" ? packReadback(projectRoot, item, carrierPluginRoot) : engineReadback(projectRoot, assetRelative, engineCommand(projectRoot, ["asset", "add", assetRelative, "--reimport-policy", "semantic-only", "--json"], carrierPluginRoot), carrierPluginRoot);
5698
+ if (item.deliveredFormat === "pack" && !liveFilesMatch(destination, item))
5699
+ throw new Error("engine_pack_source_changed");
5700
+ atomicWrite(resolve12(destination, ".forgeax-asset.json"), `${JSON.stringify(provenance(item, install, readback, priorDigest !== undefined), null, 2)}
5509
5701
  `);
5510
5702
  journal = updateJournal(projectRoot, journal, { state: "engine_verified" });
5511
5703
  if (existsSync9(backup))
@@ -5524,7 +5716,7 @@ function commitItem(projectRoot, initialJournal, install, item, refresh, lockTim
5524
5716
  }
5525
5717
  }
5526
5718
  function commitAsset3d(options) {
5527
- const projectRoot = realpathSync8(options.projectRoot);
5719
+ const projectRoot = realpathSync9(options.projectRoot);
5528
5720
  const install = readInstall(projectRoot);
5529
5721
  let journal = readJournal(projectRoot, options.execution);
5530
5722
  if (journal.state !== "begun" && journal.state !== "provider_complete" && journal.state !== "validated") {
@@ -5536,7 +5728,7 @@ function commitAsset3d(options) {
5536
5728
  let providerResult = options.providerResult;
5537
5729
  if (providerResult === undefined) {
5538
5730
  try {
5539
- providerResult = readFileSync13(providerResultPath(projectRoot, options.execution));
5731
+ providerResult = readFileSync14(providerResultPath(projectRoot, options.execution));
5540
5732
  } catch {
5541
5733
  throw new Error("asset3d_provider_result_missing: call search_asset for this execution first");
5542
5734
  }
@@ -5587,7 +5779,7 @@ function commitAsset3d(options) {
5587
5779
  };
5588
5780
  }
5589
5781
  function abortAsset3d(projectRootInput, execution) {
5590
- const projectRoot = realpathSync8(projectRootInput);
5782
+ const projectRoot = realpathSync9(projectRootInput);
5591
5783
  const journal = readJournal(projectRoot, execution);
5592
5784
  if (journal.state === "committed" || journal.state === "complete" || journal.state === "engine_verified") {
5593
5785
  throw new Error("asset3d_execution_already_committed");
@@ -5598,20 +5790,20 @@ function abortAsset3d(projectRootInput, execution) {
5598
5790
  return { execution, aborted: true };
5599
5791
  }
5600
5792
  function doctorAsset3d(projectRootInput, options = {}) {
5601
- const projectRoot = realpathSync8(projectRootInput);
5793
+ const projectRoot = realpathSync9(projectRootInput);
5602
5794
  const install = readInstall(projectRoot);
5603
5795
  const release = resolveEngineRelease(projectRoot, options.carrierPluginRoot === undefined ? {} : { pluginRoot: options.carrierPluginRoot });
5604
5796
  const recovered = [];
5605
- const root = resolve11(forgeaxRoot(projectRoot), "asset3d-transactions");
5797
+ const root = resolve12(forgeaxRoot(projectRoot), "asset3d-transactions");
5606
5798
  if (existsSync9(root))
5607
5799
  for (const file of readdirSync7(root).filter((name) => name.endsWith(".json"))) {
5608
5800
  try {
5609
- const path = resolve11(root, file);
5610
- const journal = JSON.parse(readFileSync13(path, "utf8"));
5801
+ const path = resolve12(root, file);
5802
+ const journal = JSON.parse(readFileSync14(path, "utf8"));
5611
5803
  if (journal.schemaVersion !== TRANSACTION_SCHEMA || journal.state !== "committing" || !journal.destination)
5612
5804
  continue;
5613
- const assetsRoot = resolve11(projectRoot, "assets", "3d", "ea-3d");
5614
- const backupsRoot = resolve11(forgeaxRoot(projectRoot), "asset3d-backups");
5805
+ const assetsRoot = resolve12(projectRoot, "assets", "3d", "ea-3d");
5806
+ const backupsRoot = resolve12(forgeaxRoot(projectRoot), "asset3d-backups");
5615
5807
  if (!confined4(assetsRoot, journal.destination) || journal.backupPath && !confined4(backupsRoot, journal.backupPath))
5616
5808
  continue;
5617
5809
  rmSync6(journal.destination, { recursive: true, force: true });
@@ -5621,13 +5813,13 @@ function doctorAsset3d(projectRootInput, options = {}) {
5621
5813
  recovered.push(journal.execution);
5622
5814
  } catch {}
5623
5815
  }
5624
- return { installed: true, recovered, engine: { version: release.version, commit: release.commit }, provider: { commit: install.providerCommit, cache: install.providerCache, live: existsSync9(resolve11(install.providerCache, "venv", "bin", "python")) } };
5816
+ return { installed: true, recovered, engine: { version: release.version, commit: release.commit }, provider: { commit: install.providerCommit, cache: install.providerCache, live: existsSync9(resolve12(install.providerCache, "venv", "bin", "python")) } };
5625
5817
  }
5626
5818
 
5627
5819
  // src/asset3d/aw-access.ts
5628
5820
  import { spawnSync as spawnSync5 } from "node:child_process";
5629
5821
  import { existsSync as existsSync10 } from "node:fs";
5630
- import { resolve as resolve12 } from "node:path";
5822
+ import { resolve as resolve13 } from "node:path";
5631
5823
  var AW_SERVICE_PATH = "/trpc.oasismetric.omcontentserver.http";
5632
5824
  var ACCESS_CHECK_SCHEMA = "forgeax.asset3d-access-check/1.0.0";
5633
5825
  var DEFAULT_AW_PUBLIC_SERVICE_ROOT = "http://lb-pl74wsqg-5wi8ujmy1fq2746r.clb.usw-tencentclb.com:8008/trpc.oasismetric.omcontentserver.http";
@@ -5660,7 +5852,7 @@ function resolveAssetLibrarySelection(options = {}) {
5660
5852
  return { library, serviceRoot: normalizeAssetLibraryServiceRoot(configured) };
5661
5853
  }
5662
5854
  function checkAssetLibraryProviderAccess(options) {
5663
- const command = resolve12(options.providerCache, "bin", "asset3d-search");
5855
+ const command = resolve13(options.providerCache, "bin", "asset3d-search");
5664
5856
  if (!existsSync10(command))
5665
5857
  throw new Error("asset3d_provider_not_prepared");
5666
5858
  const result = spawnSync5(command, ["--check-aw-access"], {
@@ -5672,7 +5864,7 @@ function checkAssetLibraryProviderAccess(options) {
5672
5864
  ASSET3D_CATALOG_BASE_URL: "",
5673
5865
  AW_API_BASE_URL: options.serviceRoot,
5674
5866
  AW_API_DEPOT_NAME: options.depotName,
5675
- AW_API_CREDENTIAL_FILE: resolve12(options.credentialFile),
5867
+ AW_API_CREDENTIAL_FILE: resolve13(options.credentialFile),
5676
5868
  AW_API_SANDBOX_KEY: ""
5677
5869
  }
5678
5870
  });
@@ -5698,9 +5890,9 @@ function checkAssetLibraryProviderAccess(options) {
5698
5890
  }
5699
5891
 
5700
5892
  // src/asset3d/credentials.ts
5701
- import { chmodSync as chmodSync5, existsSync as existsSync11, lstatSync as lstatSync9, readFileSync as readFileSync14, statSync as statSync7, unlinkSync as unlinkSync4 } from "node:fs";
5893
+ import { chmodSync as chmodSync5, existsSync as existsSync11, lstatSync as lstatSync10, readFileSync as readFileSync15, statSync as statSync7, unlinkSync as unlinkSync4 } from "node:fs";
5702
5894
  import { homedir as homedir4 } from "node:os";
5703
- import { dirname as dirname11, isAbsolute as isAbsolute5, resolve as resolve13 } from "node:path";
5895
+ import { dirname as dirname11, isAbsolute as isAbsolute6, resolve as resolve14 } from "node:path";
5704
5896
  var AW_CREDENTIAL_SCHEMA = "forgeax.asset3d-credential/1.0.0";
5705
5897
  var AW_KEY_ENV = "FORGEAX_ASSET3D_AW_SANDBOX_KEY";
5706
5898
  var MAX_CREDENTIAL_BYTES = 4096;
@@ -5715,22 +5907,22 @@ function validateKey(value) {
5715
5907
  }
5716
5908
  function defaultAwCredentialFile() {
5717
5909
  const configured = process.env.FORGEAX_ASSET3D_CREDENTIAL_FILE;
5718
- return resolve13(configured || resolve13(homedir4(), ".forgeax", "credentials", "asset3d-aw.json"));
5910
+ return resolve14(configured || resolve14(homedir4(), ".forgeax", "credentials", "asset3d-aw.json"));
5719
5911
  }
5720
5912
  function readAwCredential(pathInput) {
5721
- const path = resolve13(pathInput);
5722
- if (!isAbsolute5(pathInput))
5913
+ const path = resolve14(pathInput);
5914
+ if (!isAbsolute6(pathInput))
5723
5915
  throw new Error("asset3d_credential_path_invalid: absolute path required");
5724
5916
  if (!existsSync11(path))
5725
5917
  return;
5726
5918
  try {
5727
- const metadata = lstatSync9(path);
5919
+ const metadata = lstatSync10(path);
5728
5920
  const wrongOwner = typeof process.getuid === "function" && metadata.uid !== process.getuid();
5729
5921
  if (!metadata.isFile() || metadata.isSymbolicLink() || wrongOwner)
5730
5922
  throw new Error;
5731
5923
  if ((metadata.mode & 63) !== 0 || metadata.size < 1 || metadata.size > MAX_CREDENTIAL_BYTES)
5732
5924
  throw new Error;
5733
- const parsed = JSON.parse(readFileSync14(path, "utf8"));
5925
+ const parsed = JSON.parse(readFileSync15(path, "utf8"));
5734
5926
  if (Object.keys(parsed).sort().join(",") !== "provider,sandboxKey,schemaVersion")
5735
5927
  throw new Error;
5736
5928
  if (parsed.schemaVersion !== AW_CREDENTIAL_SCHEMA || parsed.provider !== "aw" || typeof parsed.sandboxKey !== "string")
@@ -5792,13 +5984,13 @@ async function acquireAwKey(path) {
5792
5984
  return { key: await promptAwKey(), source: "prompt" };
5793
5985
  }
5794
5986
  function writeAwCredential(pathInput, keyInput) {
5795
- if (!isAbsolute5(pathInput))
5987
+ if (!isAbsolute6(pathInput))
5796
5988
  throw new Error("asset3d_credential_path_invalid: absolute path required");
5797
- const path = resolve13(pathInput);
5989
+ const path = resolve14(pathInput);
5798
5990
  const key = validateKey(keyInput);
5799
5991
  const parent = dirname11(path);
5800
5992
  ensurePrivateDir(parent);
5801
- const parentMetadata = lstatSync9(parent);
5993
+ const parentMetadata = lstatSync10(parent);
5802
5994
  const wrongParentOwner = typeof process.getuid === "function" && parentMetadata.uid !== process.getuid();
5803
5995
  if (!parentMetadata.isDirectory() || parentMetadata.isSymbolicLink() || wrongParentOwner) {
5804
5996
  throw new Error("asset3d_credential_path_invalid: parent must be an owned regular directory");
@@ -5807,7 +5999,7 @@ function writeAwCredential(pathInput, keyInput) {
5807
5999
  const existed = existsSync11(path);
5808
6000
  if (existed)
5809
6001
  readAwCredential(path);
5810
- const previous = existed ? readFileSync14(path) : undefined;
6002
+ const previous = existed ? readFileSync15(path) : undefined;
5811
6003
  const previousMode = existed ? statSync7(path).mode & 511 : undefined;
5812
6004
  const bytes = `${JSON.stringify({ schemaVersion: AW_CREDENTIAL_SCHEMA, provider: "aw", sandboxKey: key }, null, 2)}
5813
6005
  `;
@@ -5872,7 +6064,7 @@ async function runDormantAsset3dMcp() {
5872
6064
  result: {
5873
6065
  protocolVersion,
5874
6066
  capabilities: { tools: {} },
5875
- serverInfo: { name: "asset3d-search", version: "0.3.5" }
6067
+ serverInfo: { name: "asset3d-search", version: RELEASE_IDENTITY.gameVersion }
5876
6068
  }
5877
6069
  };
5878
6070
  } else if (request2.method === "tools/list") {
@@ -5914,7 +6106,7 @@ function rewriteSearchTool(tool) {
5914
6106
  const required = Array.isArray(inputSchema.required) ? inputSchema.required.filter((field) => typeof field === "string" && field !== "output_dir") : [];
5915
6107
  return {
5916
6108
  ...tool,
5917
- description: `${typeof tool.description === "string" ? `${tool.description} ` : ""}Run forgeax-game asset3d begin first and pass its execution ID. The bridge injects the transaction-owned output directory.`,
6109
+ description: `${typeof tool.description === "string" ? `${tool.description} ` : ""}Run forgeax-game asset3d begin first and pass its execution ID. The bridge injects the transaction-owned output directory. For native Pack preservation select output_format=asset when advertised; glb explicitly requests conversion.`,
5918
6110
  inputSchema: {
5919
6111
  ...inputSchema,
5920
6112
  properties: {
@@ -5961,10 +6153,21 @@ function transformAsset3dSearchCall(projectRoot, request2) {
5961
6153
  if (typeof args.execution !== "string") {
5962
6154
  throw new Error("asset3d_execution_required: run `forgeax-game asset3d begin` first");
5963
6155
  }
5964
- if (!Array.isArray(args.queries) || args.queries.some((query) => typeof query !== "string")) {
6156
+ if (!Array.isArray(args.queries)) {
5965
6157
  throw new Error("asset3d_queries_invalid");
5966
6158
  }
5967
- const outputDir = asset3dSearchOutputDir(projectRoot, args.execution, args.queries);
6159
+ const queries = args.queries.map((query) => {
6160
+ if (typeof query === "string")
6161
+ return query;
6162
+ if (query && typeof query === "object" && !Array.isArray(query)) {
6163
+ const selected = query;
6164
+ if (Object.keys(selected).every((key) => key === "content" || key === "assetId") && typeof selected.content === "string" && typeof selected.assetId === "string" && /^[A-Za-z0-9._-]{1,128}$/.test(selected.assetId) && ![".", ".."].includes(selected.assetId)) {
6165
+ return selected.content;
6166
+ }
6167
+ }
6168
+ throw new Error("asset3d_queries_invalid");
6169
+ });
6170
+ const outputDir = asset3dSearchOutputDir(projectRoot, args.execution, queries);
5968
6171
  const { execution: _execution, output_dir: _callerOutput, ...providerArguments } = args;
5969
6172
  return {
5970
6173
  ...request2,
@@ -6080,20 +6283,27 @@ async function runAsset3dMcpProxy(projectRoot, launch) {
6080
6283
  };
6081
6284
  process.once("SIGINT", stopChild);
6082
6285
  process.once("SIGTERM", stopChild);
6083
- return await new Promise((resolve14, reject) => {
6286
+ return await new Promise((resolve15, reject) => {
6084
6287
  child.once("error", reject);
6085
- child.once("close", (code, signal) => resolve14(code ?? (signal ? 1 : 0)));
6288
+ child.once("close", (code, signal) => resolve15(code ?? (signal ? 1 : 0)));
6086
6289
  });
6087
6290
  }
6088
6291
 
6089
6292
  // src/asset3d/host-config.ts
6090
- function inspectAsset3dHost(client, root) {
6091
- const launch = asset3dLaunchSpec("npx");
6293
+ function asset3dHostMode(client, root) {
6294
+ return inspectConfig(client, root, launchSpec("local")).state === "current" ? "local" : "npx";
6295
+ }
6296
+ function inspectAsset3dHost(client, root, mode = asset3dHostMode(client, root)) {
6297
+ const launch = asset3dLaunchSpec(mode);
6092
6298
  const status = inspectConfig(client, root, launch, ASSET3D_SERVER_KEY);
6093
6299
  if (status.state !== "different")
6094
6300
  return status;
6301
+ const published = asset3dLaunchSpec("npx");
6302
+ if (mode === "local" && inspectConfig(client, root, published, ASSET3D_SERVER_KEY).state === "current") {
6303
+ return { ...status, state: "outdated" };
6304
+ }
6095
6305
  const previous = configuredGameVersion(client, root, ASSET3D_SERVER_KEY);
6096
- const current = launch.args.find((arg) => arg.startsWith("@forgeax/game@"))?.slice("@forgeax/game@".length);
6306
+ const current = published.args.find((arg) => arg.startsWith("@forgeax/game@"))?.slice("@forgeax/game@".length);
6097
6307
  if (!previous || !current || !/^\d+\.\d+\.\d+$/.test(previous) || !/^\d+\.\d+\.\d+$/.test(current))
6098
6308
  return status;
6099
6309
  const oldParts = previous.split(".").map(Number);
@@ -6101,7 +6311,7 @@ function inspectAsset3dHost(client, root) {
6101
6311
  const differing = oldParts.findIndex((part, index) => part !== newParts[index]);
6102
6312
  if (differing < 0 || oldParts[differing] >= newParts[differing])
6103
6313
  return status;
6104
- const oldLaunch = { ...launch, args: launch.args.map((arg) => arg === `@forgeax/game@${current}` ? `@forgeax/game@${previous}` : arg) };
6314
+ const oldLaunch = { ...published, args: published.args.map((arg) => arg === `@forgeax/game@${current}` ? `@forgeax/game@${previous}` : arg) };
6105
6315
  if (inspectConfig(client, root, oldLaunch, ASSET3D_SERVER_KEY).state !== "current")
6106
6316
  return status;
6107
6317
  return { ...status, state: "outdated" };
@@ -6109,7 +6319,7 @@ function inspectAsset3dHost(client, root) {
6109
6319
  function refreshAsset3dHost(client, root) {
6110
6320
  const status = inspectAsset3dHost(client, root);
6111
6321
  if (status.state === "outdated") {
6112
- const result = applyConfig(client, root, asset3dLaunchSpec("npx"), ASSET3D_SERVER_KEY);
6322
+ const result = applyConfig(client, root, asset3dLaunchSpec(asset3dHostMode(client, root)), ASSET3D_SERVER_KEY);
6113
6323
  process.stdout.write(`UPDATED ${client.label} Asset3D bridge: ${result.path}
6114
6324
  `);
6115
6325
  } else if (status.state === "different" || status.state === "invalid") {
@@ -6118,6 +6328,85 @@ function refreshAsset3dHost(client, root) {
6118
6328
  }
6119
6329
  }
6120
6330
 
6331
+ // src/devkit/engine-mounts.ts
6332
+ import { lstatSync as lstatSync11, readFileSync as readFileSync16, readdirSync as readdirSync8, readlinkSync as readlinkSync2, rmdirSync, unlinkSync as unlinkSync5, writeFileSync as writeFileSync11 } from "node:fs";
6333
+ import { dirname as dirname12, join as join11, resolve as resolve15 } from "node:path";
6334
+ var HOSTS = {
6335
+ ".agents/skills": ["codex"],
6336
+ ".claude/skills": ["claude"],
6337
+ ".cursor/skills": ["cursor"],
6338
+ ".codebuddy/skills": ["codebuddy", "workbuddy"],
6339
+ ".workbuddy/skills": ["workbuddy"]
6340
+ };
6341
+ function pruneUnselectedEngineMounts(root, clients) {
6342
+ const manifestPath = join11(root, ".forgeax", "skill-install-manifest.json");
6343
+ const regular = (path) => {
6344
+ try {
6345
+ const stat = lstatSync11(path);
6346
+ return stat.isFile() && !stat.isSymbolicLink();
6347
+ } catch {
6348
+ return false;
6349
+ }
6350
+ };
6351
+ const directory = (path) => {
6352
+ try {
6353
+ const stat = lstatSync11(path);
6354
+ return stat.isDirectory() && !stat.isSymbolicLink();
6355
+ } catch {
6356
+ return false;
6357
+ }
6358
+ };
6359
+ if (!directory(join11(root, ".forgeax")) || !regular(manifestPath))
6360
+ return [];
6361
+ let manifest;
6362
+ try {
6363
+ manifest = JSON.parse(readFileSync16(manifestPath, "utf8"));
6364
+ } catch {
6365
+ return [];
6366
+ }
6367
+ if (!manifest || manifest.schemaVersion !== "1.0.0" || manifest.sourceRoot !== "skills" || !Array.isArray(manifest.mounts))
6368
+ return [];
6369
+ const removed = [];
6370
+ for (const mount of manifest.mounts) {
6371
+ if (!mount || typeof mount.root !== "string" || !Object.hasOwn(HOSTS, mount.root))
6372
+ continue;
6373
+ const hosts = HOSTS[mount.root];
6374
+ if (!hosts || hosts.some((host) => clients.includes(host)) || !Array.isArray(mount.skills))
6375
+ continue;
6376
+ if (!mount.skills.length || !mount.skills.every((id) => /^forgeax-engine-[a-z0-9-]+$/.test(id)) || new Set(mount.skills).size !== mount.skills.length)
6377
+ continue;
6378
+ const path = join11(root, mount.root);
6379
+ if (!directory(dirname12(path)) || !directory(path))
6380
+ continue;
6381
+ const expected = [".gitignore", ...mount.skills].sort();
6382
+ if (JSON.stringify(readdirSync8(path).sort()) !== JSON.stringify(expected))
6383
+ continue;
6384
+ const ignore = join11(path, ".gitignore");
6385
+ const expectedIgnore = ["# BEGIN FORGEAX MANAGED SKILLS", ...mount.skills.map((id) => `/${id}`), "# END FORGEAX MANAGED SKILLS", ""].join(`
6386
+ `);
6387
+ if (!regular(ignore) || readFileSync16(ignore, "utf8") !== expectedIgnore)
6388
+ continue;
6389
+ if (!mount.skills.every((id) => {
6390
+ const link = join11(path, id);
6391
+ return lstatSync11(link).isSymbolicLink() && resolve15(path, readlinkSync2(link)) === resolve15(root, "skills", id);
6392
+ }))
6393
+ continue;
6394
+ for (const id of mount.skills)
6395
+ unlinkSync5(join11(path, id));
6396
+ unlinkSync5(ignore);
6397
+ rmdirSync(path);
6398
+ if (readdirSync8(dirname12(path)).length === 0)
6399
+ rmdirSync(dirname12(path));
6400
+ removed.push(mount.root);
6401
+ }
6402
+ if (removed.length) {
6403
+ manifest.mounts = manifest.mounts.filter((mount) => !mount || !removed.includes(mount.root));
6404
+ writeFileSync11(manifestPath, `${JSON.stringify(manifest, null, 2)}
6405
+ `);
6406
+ }
6407
+ return removed;
6408
+ }
6409
+
6121
6410
  // src/cli/dispatch.ts
6122
6411
  var HELP = `ForgeaX game development plugin
6123
6412
 
@@ -6184,23 +6473,23 @@ function requireProject() {
6184
6473
  return project.root;
6185
6474
  }
6186
6475
  function updateAgentsFile(root) {
6187
- const path = join11(root, "AGENTS.md");
6188
- const existing = existsSync12(path) ? readFileSync15(path, "utf8") : undefined;
6476
+ const path = join12(root, "AGENTS.md");
6477
+ const existing = existsSync12(path) ? readFileSync17(path, "utf8") : undefined;
6189
6478
  const content = upsertBlock(existing, ROUTING_TEXT);
6190
6479
  if (content === existing)
6191
6480
  return { path, changed: false };
6192
- writeFileSync11(path, content);
6481
+ writeFileSync12(path, content);
6193
6482
  return { path, changed: true };
6194
6483
  }
6195
6484
  function removeAgentsBlock(root) {
6196
- const path = join11(root, "AGENTS.md");
6485
+ const path = join12(root, "AGENTS.md");
6197
6486
  if (!existsSync12(path))
6198
6487
  return { path, changed: false };
6199
- const existing = readFileSync15(path, "utf8");
6488
+ const existing = readFileSync17(path, "utf8");
6200
6489
  const content = removeBlock(existing);
6201
6490
  if (content === existing)
6202
6491
  return { path, changed: false };
6203
- writeFileSync11(path, content);
6492
+ writeFileSync12(path, content);
6204
6493
  return { path, changed: true };
6205
6494
  }
6206
6495
  async function installCommand(args) {
@@ -6270,6 +6559,10 @@ async function initCommand(args) {
6270
6559
  reportMissingClients(selection.missing);
6271
6560
  const hosts = selection.selected;
6272
6561
  const devkit = installDevKit(root, hosts);
6562
+ const removedMounts = pruneUnselectedEngineMounts(root, hosts);
6563
+ if (removedMounts.length)
6564
+ process.stdout.write(`Removed unused Engine-generated skill mounts: ${removedMounts.join(", ")}.
6565
+ `);
6273
6566
  process.stdout.write(`Bound Engine game ${slug} at ${selectedGame2}.
6274
6567
  `);
6275
6568
  process.stdout.write(`Engine ${release.version} (${release.commit}).
@@ -6300,7 +6593,7 @@ async function useCommand(args) {
6300
6593
  throw new Error(`game ${JSON.stringify(slug)} not found. Available: ${listGames(root).join(", ") || "(none)"}`);
6301
6594
  }
6302
6595
  if (listGames(root).length > 1) {
6303
- writeFileSync11(join11(root, ".forgeax", "active-game.json"), `${JSON.stringify({ version: 1, slug }, null, 2)}
6596
+ writeFileSync12(join12(root, ".forgeax", "active-game.json"), `${JSON.stringify({ version: 1, slug }, null, 2)}
6304
6597
  `, "utf8");
6305
6598
  }
6306
6599
  process.stdout.write(`Active game: ${slug}
@@ -6402,7 +6695,7 @@ async function uninstallCommand(args) {
6402
6695
  const agents = removeAgentsBlock(root);
6403
6696
  process.stdout.write(`${agents.changed ? "REMOVED" : "ABSENT "} routing block: ${agents.path}
6404
6697
  `);
6405
- process.stdout.write(`KEPT your games and project metadata: ${join11(root, ".forgeax")}
6698
+ process.stdout.write(`KEPT your games and project metadata: ${join12(root, ".forgeax")}
6406
6699
  `);
6407
6700
  } else {
6408
6701
  process.stdout.write(`INFO no ForgeaX project bound; only client configuration was touched.
@@ -6720,7 +7013,7 @@ async function asset3dCommand(args) {
6720
7013
  if (selection.selected.length === 0) {
6721
7014
  throw new Error("asset3d_client_missing: run `forgeax-game install --ide <client>` before enabling Asset3D");
6722
7015
  }
6723
- const bridge = asset3dLaunchSpec("npx");
7016
+ const localMode = selection.selected.some((id) => asset3dHostMode(findClient(id), projectRoot) === "local");
6724
7017
  for (const id of selection.selected) {
6725
7018
  const client = findClient(id);
6726
7019
  if (!client)
@@ -6743,7 +7036,7 @@ async function asset3dCommand(args) {
6743
7036
  const bundled = BUNDLED_ASSET3D_PROVIDERS[target];
6744
7037
  if (!bundled)
6745
7038
  throw new Error(`asset3d_provider_target_unreleased: ${target}`);
6746
- const providerCache = resolve14(homedir5(), ".forgeax", "providers", "asset3d-search", bundled.sha256);
7039
+ const providerCache = resolve16(homedir5(), ".forgeax", "providers", "asset3d-search", bundled.sha256);
6747
7040
  let preparedCache = providerCache;
6748
7041
  if (!existsSync12(providerCache)) {
6749
7042
  const providerBundle = packagedAsset3dProvider(bundled.relativePath);
@@ -6769,14 +7062,14 @@ async function asset3dCommand(args) {
6769
7062
  awDepotName: library.library,
6770
7063
  awCredentialFile: credentialFile,
6771
7064
  clients,
6772
- gamePluginLaunch: launchSpec("npx"),
7065
+ gamePluginLaunch: launchSpec(localMode ? "local" : "npx"),
6773
7066
  replaceOwned: true
6774
7067
  });
6775
7068
  for (const id of selection.selected) {
6776
7069
  const client = findClient(id);
6777
7070
  if (!client)
6778
7071
  throw new Error(`asset3d_client_invalid: ${id}`);
6779
- const applied = applyConfig(client, projectRoot, bridge, ASSET3D_SERVER_KEY);
7072
+ const applied = applyConfig(client, projectRoot, asset3dLaunchSpec(asset3dHostMode(client, projectRoot)), ASSET3D_SERVER_KEY);
6780
7073
  process.stdout.write(`${applied.changed ? "UPDATED" : "CURRENT"} ${client.label} Asset3D bridge: ${applied.path}
6781
7074
  `);
6782
7075
  if (applied.changed && client.postInstallNote)
@@ -6825,7 +7118,7 @@ async function asset3dCommand(args) {
6825
7118
  else if (arg === "--aw-depot-name")
6826
7119
  awDepotName = parseAssetLibraryId(value);
6827
7120
  else if (arg === "--aw-credential-file")
6828
- awCredentialFile = resolve14(value);
7121
+ awCredentialFile = resolve16(value);
6829
7122
  else
6830
7123
  clients = canonicalAsset3dClients(value);
6831
7124
  continue;
@@ -7029,7 +7322,7 @@ function parseMcpArgs(args) {
7029
7322
  } else
7030
7323
  throw new Error(`unknown MCP option: ${arg}`);
7031
7324
  }
7032
- return { transport, host, port, root: resolve15(root), requireAuth, allowedOrigins };
7325
+ return { transport, host, port, root: resolve17(root), requireAuth, allowedOrigins };
7033
7326
  }
7034
7327
  async function runMcp(args) {
7035
7328
  const options = parseMcpArgs(args);