unity-agentic-tools 0.3.3 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli.js CHANGED
@@ -2299,7 +2299,7 @@ var require_package = __commonJS((exports2, module2) => {
2299
2299
  module2.exports = {
2300
2300
  name: "unity-agentic-tools",
2301
2301
  packageManager: "bun@latest",
2302
- version: "0.3.3",
2302
+ version: "0.4.1",
2303
2303
  description: "Fast, token-efficient Unity YAML parser for AI agents",
2304
2304
  exports: {
2305
2305
  ".": {
@@ -2595,18 +2595,10 @@ function removeDirectoryRecursive(dir) {
2595
2595
  }
2596
2596
 
2597
2597
  // src/cmd-create.ts
2598
- var import_fs16 = require("fs");
2598
+ var import_fs17 = require("fs");
2599
2599
  var import_path7 = require("path");
2600
2600
  var import_crypto2 = require("crypto");
2601
2601
 
2602
- // src/editor/create.ts
2603
- var import_fs8 = require("fs");
2604
- var path3 = __toESM(require("path"));
2605
-
2606
- // src/guid-cache.ts
2607
- var import_fs5 = require("fs");
2608
- var import_path5 = require("path");
2609
-
2610
2602
  // src/utils.ts
2611
2603
  var import_fs4 = require("fs");
2612
2604
  var import_path4 = require("path");
@@ -2627,6 +2619,12 @@ function find_unity_project_root(startDir) {
2627
2619
  }
2628
2620
  return null;
2629
2621
  }
2622
+ function ensure_parent_dir(file_path) {
2623
+ const dir = import_path4.dirname(file_path);
2624
+ if (dir && dir !== "." && !import_fs4.existsSync(dir)) {
2625
+ import_fs4.mkdirSync(dir, { recursive: true });
2626
+ }
2627
+ }
2630
2628
  function atomicWrite(filePath, content) {
2631
2629
  if (import_fs4.existsSync(filePath)) {
2632
2630
  try {
@@ -2762,7 +2760,13 @@ function validate_file_path(file_path, operation) {
2762
2760
  return null;
2763
2761
  }
2764
2762
 
2763
+ // src/editor/create.ts
2764
+ var import_fs8 = require("fs");
2765
+ var path3 = __toESM(require("path"));
2766
+
2765
2767
  // src/guid-cache.ts
2768
+ var import_fs5 = require("fs");
2769
+ var import_path5 = require("path");
2766
2770
  var _cache_store = new Map;
2767
2771
  function build_guid_cache(project_path, raw) {
2768
2772
  return {
@@ -3164,6 +3168,7 @@ function resolveScriptGuid(script, projectPath) {
3164
3168
  }
3165
3169
  }
3166
3170
  if (matches[0].filePath?.endsWith(".dll")) {
3171
+ const classNameLower = targetName.toLowerCase();
3167
3172
  const dllBasename = path.basename(matches[0].filePath).toLowerCase();
3168
3173
  for (const cacheName of ["package-cache.json", "local-package-cache.json"]) {
3169
3174
  const cachePath = path.join(projectPath, ".unity-agentic", cacheName);
@@ -3171,6 +3176,11 @@ function resolveScriptGuid(script, projectPath) {
3171
3176
  continue;
3172
3177
  try {
3173
3178
  const cache = JSON.parse(import_fs6.readFileSync(cachePath, "utf-8"));
3179
+ for (const [guid, assetPath] of Object.entries(cache)) {
3180
+ if (assetPath.endsWith(".cs") && path.basename(assetPath, ".cs").toLowerCase() === classNameLower) {
3181
+ return { guid, path: assetPath };
3182
+ }
3183
+ }
3174
3184
  for (const [guid, assetPath] of Object.entries(cache)) {
3175
3185
  if (assetPath.toLowerCase().endsWith(".dll") && path.basename(assetPath).toLowerCase() === dllBasename) {
3176
3186
  return { guid, path: assetPath };
@@ -3196,6 +3206,7 @@ function resolveScriptGuid(script, projectPath) {
3196
3206
  }
3197
3207
  }
3198
3208
  if (!guid && m.filePath?.endsWith(".dll")) {
3209
+ const clsLower = (m.name ?? targetName).toLowerCase();
3199
3210
  const dllBase = path.basename(m.filePath).toLowerCase();
3200
3211
  for (const cn of ["package-cache.json", "local-package-cache.json"]) {
3201
3212
  const cp = path.join(projectPath, ".unity-agentic", cn);
@@ -3204,11 +3215,19 @@ function resolveScriptGuid(script, projectPath) {
3204
3215
  try {
3205
3216
  const c = JSON.parse(import_fs6.readFileSync(cp, "utf-8"));
3206
3217
  for (const [g, p] of Object.entries(c)) {
3207
- if (p.toLowerCase().endsWith(".dll") && path.basename(p).toLowerCase() === dllBase) {
3218
+ if (p.endsWith(".cs") && path.basename(p, ".cs").toLowerCase() === clsLower) {
3208
3219
  guid = g;
3209
3220
  break;
3210
3221
  }
3211
3222
  }
3223
+ if (!guid) {
3224
+ for (const [g, p] of Object.entries(c)) {
3225
+ if (p.toLowerCase().endsWith(".dll") && path.basename(p).toLowerCase() === dllBase) {
3226
+ guid = g;
3227
+ break;
3228
+ }
3229
+ }
3230
+ }
3212
3231
  } catch {}
3213
3232
  if (guid)
3214
3233
  break;
@@ -3239,7 +3258,11 @@ function resolveScriptGuid(script, projectPath) {
3239
3258
  throw new Error(`Ambiguous type "${script}": found ${allResolved.length} matches (${paths}). ` + `Use a qualified name (e.g., "Namespace.${targetName}") or provide the full path.`);
3240
3259
  }
3241
3260
  }
3242
- } catch {}
3261
+ } catch (err) {
3262
+ if (err instanceof Error && err.message.startsWith("Ambiguous type")) {
3263
+ throw err;
3264
+ }
3265
+ }
3243
3266
  }
3244
3267
  const scriptNameLower = script.toLowerCase().replace(/\.cs$/, "");
3245
3268
  const dotIdx = scriptNameLower.lastIndexOf(".");
@@ -3266,6 +3289,54 @@ function resolveScriptGuid(script, projectPath) {
3266
3289
  }
3267
3290
  return null;
3268
3291
  }
3292
+ var ASSET_PPTR_MAP = {
3293
+ ".inputactions": { fileID: 11400000, type: 2 },
3294
+ ".asset": { fileID: 11400000, type: 2 },
3295
+ ".mat": { fileID: 2100000, type: 2 },
3296
+ ".prefab": { fileID: 100100000, type: 2 },
3297
+ ".controller": { fileID: 9100000, type: 2 },
3298
+ ".anim": { fileID: 7400000, type: 2 }
3299
+ };
3300
+ var ASSET_EXTENSIONS = new Set(Object.keys(ASSET_PPTR_MAP));
3301
+ function resolveAssetPathToPPtr(value, file_path, project_path) {
3302
+ if (value.startsWith("{fileID:") || value.startsWith("{"))
3303
+ return;
3304
+ const ext = path.extname(value).toLowerCase();
3305
+ if (!ASSET_EXTENSIONS.has(ext))
3306
+ return;
3307
+ const resolved_project = project_path || find_unity_project_root(path.dirname(file_path));
3308
+ if (!resolved_project)
3309
+ return null;
3310
+ const mapping = ASSET_PPTR_MAP[ext];
3311
+ let guid = null;
3312
+ const basename_val = path.basename(value, ext);
3313
+ if (value.includes("/") || value.includes("\\")) {
3314
+ const abs = path.isAbsolute(value) ? value : path.join(resolved_project, value);
3315
+ guid = extractGuidFromMeta(abs + ".meta");
3316
+ }
3317
+ if (!guid) {
3318
+ const cache = load_guid_cache_for_file(file_path, resolved_project);
3319
+ if (cache) {
3320
+ const found = cache.find_by_name(basename_val, ext);
3321
+ if (found)
3322
+ guid = found.guid;
3323
+ }
3324
+ }
3325
+ if (!guid) {
3326
+ const candidates = [
3327
+ path.join(resolved_project, "Assets", value),
3328
+ path.join(resolved_project, value)
3329
+ ];
3330
+ for (const candidate of candidates) {
3331
+ guid = extractGuidFromMeta(candidate + ".meta");
3332
+ if (guid)
3333
+ break;
3334
+ }
3335
+ }
3336
+ if (!guid)
3337
+ return null;
3338
+ return `{fileID: ${mapping.fileID}, guid: ${guid}, type: ${mapping.type}}`;
3339
+ }
3269
3340
  function resolve_script_with_fields(script, project_path) {
3270
3341
  const resolved = resolveScriptGuid(script, project_path);
3271
3342
  if (!resolved)
@@ -3822,7 +3893,9 @@ ${element_indent2}- ${value}`);
3822
3893
  if (!header) {
3823
3894
  throw new Error(`Invalid Unity YAML block header in replacement text: "${new_text.slice(0, 80)}"`);
3824
3895
  }
3825
- this._raw = new_text;
3896
+ this._raw = new_text.endsWith(`
3897
+ `) ? new_text : new_text + `
3898
+ `;
3826
3899
  this._header = header;
3827
3900
  this._dirty = true;
3828
3901
  this._format_cache.clear();
@@ -3832,6 +3905,11 @@ ${element_indent2}- ${value}`);
3832
3905
  }
3833
3906
  _check_dirty(old_raw) {
3834
3907
  if (this._raw !== old_raw) {
3908
+ if (!this._raw.endsWith(`
3909
+ `)) {
3910
+ this._raw += `
3911
+ `;
3912
+ }
3835
3913
  this._dirty = true;
3836
3914
  return true;
3837
3915
  }
@@ -4914,14 +4992,82 @@ function get_project_info(projectPath) {
4914
4992
  }
4915
4993
 
4916
4994
  // src/editor/create.ts
4995
+ function md4(data) {
4996
+ function F(x, y, z) {
4997
+ return x & y | ~x & z;
4998
+ }
4999
+ function G(x, y, z) {
5000
+ return x & y | x & z | y & z;
5001
+ }
5002
+ function H(x, y, z) {
5003
+ return x ^ y ^ z;
5004
+ }
5005
+ function rotl(x, n) {
5006
+ return (x << n | x >>> 32 - n) >>> 0;
5007
+ }
5008
+ const len = data.length;
5009
+ const bitLen = len * 8;
5010
+ const padded_len = len + 9 + 63 & ~63;
5011
+ const buf = Buffer.alloc(padded_len);
5012
+ data.copy(buf);
5013
+ buf[len] = 128;
5014
+ buf.writeUInt32LE(bitLen >>> 0, padded_len - 8);
5015
+ buf.writeUInt32LE(Math.floor(bitLen / 4294967296) >>> 0, padded_len - 4);
5016
+ let a0 = 1732584193;
5017
+ let b0 = 4023233417;
5018
+ let c0 = 2562383102;
5019
+ let d0 = 271733878;
5020
+ const R1_K = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15];
5021
+ const R1_S = [3, 7, 11, 19, 3, 7, 11, 19, 3, 7, 11, 19, 3, 7, 11, 19];
5022
+ const R2_K = [0, 4, 8, 12, 1, 5, 9, 13, 2, 6, 10, 14, 3, 7, 11, 15];
5023
+ const R2_S = [3, 5, 9, 13, 3, 5, 9, 13, 3, 5, 9, 13, 3, 5, 9, 13];
5024
+ const R3_K = [0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15];
5025
+ const R3_S = [3, 9, 11, 15, 3, 9, 11, 15, 3, 9, 11, 15, 3, 9, 11, 15];
5026
+ for (let i = 0;i < padded_len; i += 64) {
5027
+ const X = [];
5028
+ for (let j = 0;j < 16; j++)
5029
+ X[j] = buf.readUInt32LE(i + j * 4);
5030
+ let a = a0, b = b0, c = c0, d = d0;
5031
+ for (let j = 0;j < 16; j++) {
5032
+ const t = a + F(b, c, d) + X[R1_K[j]] >>> 0;
5033
+ a = d;
5034
+ d = c;
5035
+ c = b;
5036
+ b = rotl(t, R1_S[j]);
5037
+ }
5038
+ for (let j = 0;j < 16; j++) {
5039
+ const t = a + G(b, c, d) + X[R2_K[j]] + 1518500249 >>> 0;
5040
+ a = d;
5041
+ d = c;
5042
+ c = b;
5043
+ b = rotl(t, R2_S[j]);
5044
+ }
5045
+ for (let j = 0;j < 16; j++) {
5046
+ const t = a + H(b, c, d) + X[R3_K[j]] + 1859775393 >>> 0;
5047
+ a = d;
5048
+ d = c;
5049
+ c = b;
5050
+ b = rotl(t, R3_S[j]);
5051
+ }
5052
+ a0 = a0 + a >>> 0;
5053
+ b0 = b0 + b >>> 0;
5054
+ c0 = c0 + c >>> 0;
5055
+ d0 = d0 + d >>> 0;
5056
+ }
5057
+ const result = Buffer.alloc(16);
5058
+ result.writeUInt32LE(a0, 0);
5059
+ result.writeUInt32LE(b0, 4);
5060
+ result.writeUInt32LE(c0, 8);
5061
+ result.writeUInt32LE(d0, 12);
5062
+ return result;
5063
+ }
4917
5064
  function compute_dll_script_file_id(namespace, class_name) {
4918
- const { createHash } = require("crypto");
4919
5065
  const full_name = namespace ? `${namespace}.${class_name}` : class_name;
4920
5066
  const name_bytes = Buffer.from(full_name, "utf-8");
4921
5067
  const input = Buffer.alloc(4 + name_bytes.length);
4922
5068
  input.writeInt32LE(114, 0);
4923
5069
  name_bytes.copy(input, 4);
4924
- const hash = createHash("md4").update(input).digest();
5070
+ const hash = md4(input);
4925
5071
  const a = hash.readInt32LE(0);
4926
5072
  const b = hash.readInt32LE(4);
4927
5073
  const c = hash.readInt32LE(8);
@@ -4934,13 +5080,13 @@ function get_layer_from_parent(doc, parentTransformId) {
4934
5080
  const parentTransform = doc.find_by_file_id(parentTransformId);
4935
5081
  if (!parentTransform)
4936
5082
  return 0;
4937
- const goMatch = parentTransform.raw.match(/m_GameObject:\s*\{fileID:\s*(\d+)\}/);
5083
+ const goMatch = parentTransform.raw.match(/m_GameObject:[ \t]*\{fileID:[ \t]*(\d+)\}/);
4938
5084
  if (!goMatch)
4939
5085
  return 0;
4940
5086
  const parentGo = doc.find_by_file_id(goMatch[1]);
4941
5087
  if (!parentGo)
4942
5088
  return 0;
4943
- const layerMatch = parentGo.raw.match(/m_Layer:\s*(\d+)/);
5089
+ const layerMatch = parentGo.raw.match(/m_Layer:[ \t]*(\d+)/);
4944
5090
  return layerMatch ? parseInt(layerMatch[1], 10) : 0;
4945
5091
  }
4946
5092
  function createGameObjectYAML(gameObjectId, transformId, name, parentTransformId = "0", rootOrder = 0, layer = 0) {
@@ -5261,11 +5407,14 @@ ${defaults}`;
5261
5407
  function addComponentToGameObject(doc, gameObjectId, componentId) {
5262
5408
  const goBlock = doc.find_by_file_id(gameObjectId);
5263
5409
  if (!goBlock)
5264
- return;
5265
- let raw = goBlock.raw;
5266
- raw = raw.replace(/(m_Component:\s*\n(?:\s*-\s*component:\s*\{fileID:\s*\d+\}\s*\n)*)/, `$1 - component: {fileID: ${componentId}}
5410
+ return false;
5411
+ const raw = goBlock.raw;
5412
+ const updated = raw.replace(/(m_Component:[ \t]*\n(?:[ \t]*-[ \t]*component:[ \t]*\{fileID:[ \t]*\d+\}[ \t]*\n)*)/, `$1 - component: {fileID: ${componentId}}
5267
5413
  `);
5268
- goBlock.replace_raw(raw);
5414
+ if (updated === raw)
5415
+ return false;
5416
+ goBlock.replace_raw(updated);
5417
+ return true;
5269
5418
  }
5270
5419
  function createMonoBehaviourYAML(componentId, gameObjectId, scriptGuid, fields, version, scriptFileId = 11500000, type_lookup) {
5271
5420
  const field_yaml = fields && fields.length > 0 ? generate_field_yaml(fields, version, " ", type_lookup) : `
@@ -5337,7 +5486,7 @@ function createGameObject(options) {
5337
5486
  if (parentBlock.class_id === 4) {
5338
5487
  parentTransformIdStr = parentIdStr;
5339
5488
  } else if (parentBlock.class_id === 1) {
5340
- const compMatch = parentBlock.raw.match(/m_Component:\s*\n\s*-\s*component:\s*\{fileID:\s*(\d+)\}/);
5489
+ const compMatch = parentBlock.raw.match(/m_Component:[ \t]*\n[ \t]*-[ \t]*component:[ \t]*\{fileID:[ \t]*(\d+)\}/);
5341
5490
  if (compMatch) {
5342
5491
  parentTransformIdStr = compMatch[1];
5343
5492
  } else {
@@ -5751,6 +5900,7 @@ Light:
5751
5900
  `;
5752
5901
  }
5753
5902
  try {
5903
+ ensure_parent_dir(output_path);
5754
5904
  import_fs8.writeFileSync(output_path, yaml, "utf-8");
5755
5905
  } catch (err) {
5756
5906
  return {
@@ -5889,6 +6039,7 @@ PrefabInstance:
5889
6039
  m_SourcePrefab: {fileID: 100100000, guid: ${sourceGuid}, type: 3}
5890
6040
  `;
5891
6041
  try {
6042
+ ensure_parent_dir(output_path);
5892
6043
  import_fs8.writeFileSync(output_path, variantYaml, "utf-8");
5893
6044
  } catch (err) {
5894
6045
  return {
@@ -5979,7 +6130,7 @@ function createPrefabInstance(options) {
5979
6130
  if (parentBlock.class_id === 4) {
5980
6131
  parentTransformIdStr = parentIdStr;
5981
6132
  } else if (parentBlock.class_id === 1) {
5982
- const compMatch = parentBlock.raw.match(/m_Component:\s*\n\s*-\s*component:\s*\{fileID:\s*(\d+)\}/);
6133
+ const compMatch = parentBlock.raw.match(/m_Component:[ \t]*\n[ \t]*-[ \t]*component:[ \t]*\{fileID:[ \t]*(\d+)\}/);
5983
6134
  if (compMatch) {
5984
6135
  parentTransformIdStr = compMatch[1];
5985
6136
  } else {
@@ -6213,6 +6364,7 @@ ${yaml_lines.join(`
6213
6364
  }
6214
6365
  }
6215
6366
  try {
6367
+ ensure_parent_dir(output_path);
6216
6368
  import_fs8.writeFileSync(output_path, assetYaml, "utf-8");
6217
6369
  } catch (err) {
6218
6370
  return { success: false, output_path, error: `Failed to write asset file: ${err instanceof Error ? err.message : String(err)}` };
@@ -6408,7 +6560,7 @@ function addComponent(options) {
6408
6560
  let duplicateWarning;
6409
6561
  const goBlock = doc.find_by_file_id(gameObjectIdStr);
6410
6562
  if (goBlock && classId !== null) {
6411
- const compRefs = [...goBlock.raw.matchAll(/component:\s*\{fileID:\s*(\d+)\}/g)].map((m) => m[1]);
6563
+ const compRefs = [...goBlock.raw.matchAll(/component:[ \t]*\{fileID:[ \t]*(\d+)\}/g)].map((m) => m[1]);
6412
6564
  for (const refId of compRefs) {
6413
6565
  const compBlock = doc.find_by_file_id(refId);
6414
6566
  if (compBlock && compBlock.class_id === classId) {
@@ -6440,12 +6592,16 @@ function addComponent(options) {
6440
6592
  if (!resolved) {
6441
6593
  const hints = [];
6442
6594
  if (project_path) {
6595
+ const agenticDir = path3.join(project_path, ".unity-agentic");
6443
6596
  const cacheExists = load_guid_cache(project_path) !== null;
6444
- const registryExists = import_fs8.existsSync(path3.join(project_path, ".unity-agentic", "type-registry.json"));
6597
+ const registryExists = import_fs8.existsSync(path3.join(agenticDir, "type-registry.json"));
6598
+ const pkgCacheExists = import_fs8.existsSync(path3.join(agenticDir, "package-cache.json"));
6445
6599
  if (!cacheExists && !registryExists) {
6446
- hints.push(`No GUID cache or type registry found at ${path3.join(project_path, ".unity-agentic/")}. Run "unity-agentic-tools setup" first.`);
6600
+ hints.push(`No GUID cache or type registry found at ${agenticDir}/. Run "unity-agentic-tools setup" first.`);
6447
6601
  } else if (!registryExists) {
6448
6602
  hints.push('Type registry not found. Re-run "unity-agentic-tools setup" to rebuild.');
6603
+ } else if (!pkgCacheExists) {
6604
+ hints.push('Package cache not found. Re-run "unity-agentic-tools setup" to index package scripts.');
6449
6605
  }
6450
6606
  } else {
6451
6607
  hints.push("No Unity project detected. Provide --project or run from inside a Unity project directory.");
@@ -6489,9 +6645,33 @@ function addComponent(options) {
6489
6645
  if (variantInfo) {
6490
6646
  appendToAddedComponents(doc, variantInfo.prefab_instance_id, variantInfo.source_ref, componentIdStr);
6491
6647
  } else {
6492
- addComponentToGameObject(doc, gameObjectIdStr, componentIdStr);
6648
+ const added = addComponentToGameObject(doc, gameObjectIdStr, componentIdStr);
6649
+ if (!added) {
6650
+ return {
6651
+ success: false,
6652
+ file_path,
6653
+ error: `Failed to add component reference to GameObject ${gameObjectIdStr}: m_Component array not found or malformed`
6654
+ };
6655
+ }
6493
6656
  }
6494
6657
  doc.append_raw(componentYAML);
6658
+ if (!doc.validate()) {
6659
+ return {
6660
+ success: false,
6661
+ file_path,
6662
+ error: "Validation failed after adding component. The generated YAML may be malformed."
6663
+ };
6664
+ }
6665
+ if (!variantInfo) {
6666
+ const goBlockAfter = doc.find_by_file_id(gameObjectIdStr);
6667
+ if (!goBlockAfter || !goBlockAfter.raw.includes(`component: {fileID: ${componentIdStr}}`)) {
6668
+ return {
6669
+ success: false,
6670
+ file_path,
6671
+ error: `Component block appended but reference not found in GameObject ${gameObjectIdStr}'s m_Component list`
6672
+ };
6673
+ }
6674
+ }
6495
6675
  const saveResult = doc.save();
6496
6676
  if (!saveResult.success) {
6497
6677
  return {
@@ -6547,8 +6727,11 @@ function copyComponent(options) {
6547
6727
  const newIdStr = doc.generate_file_id();
6548
6728
  const newId = parseInt(newIdStr, 10);
6549
6729
  let clonedBlock = sourceBlock.raw.replace(new RegExp(`^(--- !u!${sourceBlock.class_id} &)${source_file_id}`), `$1${newId}`);
6550
- clonedBlock = clonedBlock.replace(/m_GameObject:\s*\{fileID:\s*\d+\}/, `m_GameObject: {fileID: ${targetGoId}}`);
6551
- addComponentToGameObject(doc, targetGoIdStr, newIdStr);
6730
+ clonedBlock = clonedBlock.replace(/m_GameObject:[ \t]*\{fileID:[ \t]*\d+\}/, `m_GameObject: {fileID: ${targetGoId}}`);
6731
+ const added = addComponentToGameObject(doc, targetGoIdStr, newIdStr);
6732
+ if (!added) {
6733
+ return { success: false, file_path, error: `Failed to wire component to target GameObject ${targetGoIdStr}: m_Component array not found or malformed` };
6734
+ }
6552
6735
  doc.append_raw(clonedBlock);
6553
6736
  if (!doc.validate()) {
6554
6737
  return { success: false, file_path, error: "Validation failed after copying component" };
@@ -7205,7 +7388,17 @@ function validate_value_type(current_value, new_value) {
7205
7388
  const incoming = new_value.trim();
7206
7389
  if (/^\{fileID:/.test(current)) {
7207
7390
  if (/^\{fileID:[ \t]*0[ \t]*\}$/.test(current)) {
7208
- return null;
7391
+ if (/^\{fileID:/.test(incoming))
7392
+ return null;
7393
+ if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(incoming))
7394
+ return null;
7395
+ if (/^(true|false|yes|no|on|off)$/i.test(incoming))
7396
+ return null;
7397
+ if (/^'[^']*'$/.test(incoming) || /^"[^"]*"$/.test(incoming))
7398
+ return null;
7399
+ if (/^\{.*:.*\}$/.test(incoming))
7400
+ return null;
7401
+ return `Current value is a null reference ({fileID: 0}). New value "${incoming}" is not a valid reference, number, boolean, quoted string, or compound value.`;
7209
7402
  }
7210
7403
  if (!/^\{fileID:/.test(incoming)) {
7211
7404
  return `Expected a reference value ({fileID: ...}), got "${incoming}"`;
@@ -7415,7 +7608,19 @@ function editProperty(options) {
7415
7608
  return result;
7416
7609
  }
7417
7610
  function editComponentByFileId(options) {
7418
- const { file_path, file_id, property, new_value } = options;
7611
+ const { file_path, file_id, property, project_path } = options;
7612
+ let { new_value } = options;
7613
+ const resolved = resolveAssetPathToPPtr(new_value, file_path, project_path);
7614
+ if (resolved === null) {
7615
+ return {
7616
+ success: false,
7617
+ file_path,
7618
+ error: `Could not resolve "${new_value}" to asset reference. Ensure GUID cache exists (run "setup <project>" first).`
7619
+ };
7620
+ }
7621
+ if (resolved !== undefined) {
7622
+ new_value = resolved;
7623
+ }
7419
7624
  const pathError = validate_file_path(file_path, "write");
7420
7625
  if (pathError) {
7421
7626
  return { success: false, file_path, error: pathError };
@@ -8598,239 +8803,572 @@ ${fieldIndent} - rid: ${nextRid}`;
8598
8803
  return { success: true, file_path, rid: nextRid, type_info: typeInfo };
8599
8804
  }
8600
8805
  // src/editor/delete.ts
8806
+ var import_fs13 = require("fs");
8807
+
8808
+ // src/project-search.ts
8809
+ init_scanner();
8601
8810
  var import_fs12 = require("fs");
8602
- function removeComponent(options) {
8603
- const { file_path, file_id } = options;
8604
- const pathError = validate_file_path(file_path, "write");
8605
- if (pathError) {
8606
- return { success: false, file_path, error: pathError };
8607
- }
8608
- if (!import_fs12.existsSync(file_path)) {
8609
- return { success: false, file_path, error: `File not found: ${file_path}` };
8610
- }
8611
- let doc;
8612
- try {
8613
- doc = UnityDocument.from_file(file_path);
8614
- } catch (err) {
8615
- return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
8616
- }
8617
- const found = doc.find_by_file_id(file_id);
8618
- if (!found) {
8619
- return { success: false, file_path, error: `Component with file ID ${file_id} not found` };
8620
- }
8621
- if (found.class_id === 1) {
8622
- return { success: false, file_path, error: "Cannot remove a GameObject with remove-component. Use delete instead." };
8623
- }
8624
- if (found.class_id === 4) {
8625
- return { success: false, file_path, error: "Cannot remove a Transform with remove-component. Use delete to remove the entire GameObject." };
8626
- }
8627
- const goMatch = found.raw.match(/m_GameObject:\s*\{fileID:\s*(-?\d+)\}/);
8628
- if (goMatch) {
8629
- const parentGoId = goMatch[1];
8630
- const goBlock = doc.find_by_file_id(parentGoId);
8631
- if (goBlock) {
8632
- const escaped = file_id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8633
- const compLinePattern = new RegExp(`^[ \\t]*- component: \\{fileID: ${escaped}\\}[ \\t]*\\n`, "m");
8634
- const modifiedRaw = goBlock.raw.replace(compLinePattern, "");
8635
- goBlock.replace_raw(modifiedRaw);
8636
- }
8637
- }
8638
- doc.remove_block(file_id);
8639
- if (!doc.validate()) {
8640
- return { success: false, file_path, error: "Validation failed after removing component" };
8641
- }
8642
- const writeResult = doc.save();
8643
- if (!writeResult.success) {
8644
- return { success: false, file_path, error: writeResult.error };
8811
+ var path5 = __toESM(require("path"));
8812
+ var BINARY_EXTENSIONS = new Set([
8813
+ ".png",
8814
+ ".jpg",
8815
+ ".jpeg",
8816
+ ".gif",
8817
+ ".bmp",
8818
+ ".tga",
8819
+ ".psd",
8820
+ ".tif",
8821
+ ".tiff",
8822
+ ".fbx",
8823
+ ".obj",
8824
+ ".dae",
8825
+ ".blend",
8826
+ ".3ds",
8827
+ ".max",
8828
+ ".dll",
8829
+ ".so",
8830
+ ".dylib",
8831
+ ".exe",
8832
+ ".a",
8833
+ ".lib",
8834
+ ".mp3",
8835
+ ".wav",
8836
+ ".ogg",
8837
+ ".aif",
8838
+ ".aiff",
8839
+ ".mp4",
8840
+ ".mov",
8841
+ ".avi",
8842
+ ".wmv",
8843
+ ".zip",
8844
+ ".gz",
8845
+ ".tar",
8846
+ ".rar",
8847
+ ".7z",
8848
+ ".ttf",
8849
+ ".otf",
8850
+ ".woff",
8851
+ ".woff2",
8852
+ ".bank",
8853
+ ".bytes",
8854
+ ".db"
8855
+ ]);
8856
+ var SKIP_DIRS = new Set(["Library", "Temp", "obj", "Logs", ".git", ".unity-agentic", "node_modules"]);
8857
+ function walk_project_files(project_path, extensions, exclude_dirs) {
8858
+ const nativeWalk = getNativeWalkProjectFiles();
8859
+ if (nativeWalk) {
8860
+ try {
8861
+ return nativeWalk(project_path, extensions, exclude_dirs ?? null);
8862
+ } catch {}
8645
8863
  }
8646
- return {
8647
- success: true,
8648
- file_path,
8649
- removed_file_id: file_id,
8650
- removed_class_id: found.class_id
8651
- };
8864
+ return walk_project_files_js(project_path, extensions, exclude_dirs);
8652
8865
  }
8653
- function deleteGameObject(options) {
8654
- const { file_path, object_name } = options;
8655
- const pathError = validate_file_path(file_path, "write");
8656
- if (pathError) {
8657
- return { success: false, file_path, error: pathError };
8658
- }
8659
- if (!import_fs12.existsSync(file_path)) {
8660
- return { success: false, file_path, error: `File not found: ${file_path}` };
8661
- }
8662
- let doc;
8663
- try {
8664
- doc = UnityDocument.from_file(file_path);
8665
- } catch (err) {
8666
- return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
8667
- }
8668
- const goResult = doc.require_unique_game_object(object_name);
8669
- if ("error" in goResult) {
8670
- return { success: false, file_path, error: goResult.error };
8671
- }
8672
- const goBlock = goResult;
8673
- const goId = goBlock.file_id;
8674
- const componentIds = new Set;
8675
- const compMatches = goBlock.raw.matchAll(/component:\s*\{fileID:\s*(-?\d+)\}/g);
8676
- for (const cm of compMatches) {
8677
- componentIds.add(cm[1]);
8678
- }
8679
- let transformId = null;
8680
- let fatherId = "0";
8681
- for (const compId of componentIds) {
8682
- const block = doc.find_by_file_id(compId);
8683
- if (block && block.class_id === 4) {
8684
- transformId = compId;
8685
- const fatherMatch = block.raw.match(/m_Father:\s*\{fileID:\s*(-?\d+)\}/);
8686
- if (fatherMatch) {
8687
- fatherId = fatherMatch[1];
8866
+ function walk_project_files_js(project_path, extensions, exclude_dirs) {
8867
+ const result = [];
8868
+ const skipSet = new Set([...SKIP_DIRS, ...exclude_dirs || []]);
8869
+ const extSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
8870
+ function walk(dir) {
8871
+ let entries;
8872
+ try {
8873
+ entries = import_fs12.readdirSync(dir);
8874
+ } catch {
8875
+ return;
8876
+ }
8877
+ for (const entry of entries) {
8878
+ const full = path5.join(dir, entry);
8879
+ let stat;
8880
+ try {
8881
+ stat = import_fs12.statSync(full);
8882
+ } catch {
8883
+ continue;
8884
+ }
8885
+ if (stat.isDirectory()) {
8886
+ if (!skipSet.has(entry)) {
8887
+ walk(full);
8888
+ }
8889
+ } else if (stat.isFile()) {
8890
+ const ext = path5.extname(entry).toLowerCase();
8891
+ if (extSet.has(ext)) {
8892
+ result.push(full);
8893
+ }
8688
8894
  }
8689
- break;
8690
8895
  }
8691
8896
  }
8692
- const allIds = new Set([goId]);
8693
- for (const id of componentIds) {
8694
- allIds.add(id);
8897
+ const assetsDir = path5.join(project_path, "Assets");
8898
+ if (import_fs12.existsSync(assetsDir)) {
8899
+ walk(assetsDir);
8900
+ } else {
8901
+ walk(project_path);
8695
8902
  }
8696
- if (transformId !== null) {
8697
- const descendants = doc.collect_hierarchy(transformId);
8698
- for (const id of descendants) {
8699
- allIds.add(id);
8903
+ if (extSet.has(".asset")) {
8904
+ const settingsDir = path5.join(project_path, "ProjectSettings");
8905
+ if (import_fs12.existsSync(settingsDir)) {
8906
+ walk(settingsDir);
8700
8907
  }
8701
8908
  }
8702
- if (fatherId !== "0" && transformId !== null) {
8703
- doc.remove_child_from_parent(fatherId, transformId);
8704
- }
8705
- doc.remove_blocks(allIds);
8706
- if (!doc.validate()) {
8707
- return { success: false, file_path, error: "Validation failed after deleting GameObject" };
8708
- }
8709
- const writeResult = doc.save();
8710
- if (!writeResult.success) {
8711
- return { success: false, file_path, error: writeResult.error };
8712
- }
8713
- return {
8714
- success: true,
8715
- file_path,
8716
- deleted_count: allIds.size
8717
- };
8718
- }
8719
- function findPrefabInstanceBlock2(doc, identifier) {
8720
- const asId = doc.find_by_file_id(identifier);
8721
- if (asId && asId.class_id === 1001)
8722
- return asId;
8723
- const piBlocks = doc.find_by_class_id(1001);
8724
- const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8725
- const namePattern = new RegExp(`propertyPath:\\s*m_Name\\s+value:\\s*${escaped}\\s`);
8726
- for (const block of piBlocks) {
8727
- if (namePattern.test(block.raw))
8728
- return block;
8729
- }
8730
- return null;
8909
+ return result;
8731
8910
  }
8732
- function deletePrefabInstance(options) {
8733
- const { file_path, prefab_instance } = options;
8734
- const pathError = validate_file_path(file_path, "write");
8735
- if (pathError) {
8736
- return { success: false, file_path, error: pathError };
8737
- }
8738
- if (!import_fs12.existsSync(file_path)) {
8739
- return { success: false, file_path, error: `File not found: ${file_path}` };
8911
+ function search_project(options) {
8912
+ const {
8913
+ project_path,
8914
+ name,
8915
+ exact = false,
8916
+ component,
8917
+ tag,
8918
+ layer,
8919
+ file_type = "all",
8920
+ max_matches
8921
+ } = options;
8922
+ if (max_matches !== undefined && max_matches < 1) {
8923
+ return {
8924
+ success: false,
8925
+ project_path,
8926
+ total_files_scanned: 0,
8927
+ total_matches: 0,
8928
+ cursor: 0,
8929
+ truncated: false,
8930
+ matches: [],
8931
+ error: "--max-matches must be a positive integer (>= 1)"
8932
+ };
8740
8933
  }
8741
- let doc;
8742
- try {
8743
- doc = UnityDocument.from_file(file_path);
8744
- } catch (err) {
8745
- return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
8934
+ if (name !== undefined && name.trim() === "") {
8935
+ return {
8936
+ success: false,
8937
+ project_path,
8938
+ total_files_scanned: 0,
8939
+ total_matches: 0,
8940
+ cursor: 0,
8941
+ truncated: false,
8942
+ matches: [],
8943
+ error: "Name pattern must not be empty"
8944
+ };
8746
8945
  }
8747
- const piBlock = findPrefabInstanceBlock2(doc, prefab_instance);
8748
- if (!piBlock) {
8749
- return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
8946
+ if (!import_fs12.existsSync(project_path)) {
8947
+ return {
8948
+ success: false,
8949
+ project_path,
8950
+ total_files_scanned: 0,
8951
+ total_matches: 0,
8952
+ cursor: 0,
8953
+ truncated: false,
8954
+ matches: [],
8955
+ error: `Project path not found: ${project_path}`
8956
+ };
8750
8957
  }
8751
- const piId = piBlock.file_id;
8752
- const allToRemove = new Set([piId]);
8753
- const piRefPattern = new RegExp(`m_PrefabInstance:\\s*\\{fileID:\\s*${piId}\\}`);
8754
- for (const block of doc.blocks) {
8755
- if (block.is_stripped && piRefPattern.test(block.raw)) {
8756
- allToRemove.add(block.file_id);
8958
+ const ASSET_TYPE_EXTENSIONS = {
8959
+ mat: [".mat"],
8960
+ anim: [".anim"],
8961
+ controller: [".controller"],
8962
+ asset: [".asset"]
8963
+ };
8964
+ const isAssetType = file_type in ASSET_TYPE_EXTENSIONS;
8965
+ if (isAssetType) {
8966
+ const extensions2 = ASSET_TYPE_EXTENSIONS[file_type];
8967
+ const files2 = walk_project_files(project_path, extensions2);
8968
+ const matches2 = [];
8969
+ for (const file of files2) {
8970
+ const relPath = path5.relative(project_path, file);
8971
+ const fileName = path5.basename(file, path5.extname(file));
8972
+ if (name) {
8973
+ const nameLower = name.toLowerCase();
8974
+ const hasWildcard = name.includes("*") || name.includes("?");
8975
+ if (hasWildcard) {
8976
+ if (!glob_match(name, fileName))
8977
+ continue;
8978
+ } else if (exact) {
8979
+ if (fileName !== name)
8980
+ continue;
8981
+ } else {
8982
+ if (!fileName.toLowerCase().includes(nameLower))
8983
+ continue;
8984
+ }
8985
+ }
8986
+ let display_name = fileName;
8987
+ try {
8988
+ const content = import_fs12.readFileSync(file, "utf-8");
8989
+ const nameMatch = /^\s*m_Name:\s*(.+)$/m.exec(content.slice(0, 2000));
8990
+ if (nameMatch)
8991
+ display_name = nameMatch[1].trim();
8992
+ } catch {}
8993
+ matches2.push({
8994
+ file: relPath,
8995
+ game_object: display_name,
8996
+ file_id: "0"
8997
+ });
8998
+ if (max_matches !== undefined && matches2.length >= max_matches)
8999
+ break;
8757
9000
  }
9001
+ return {
9002
+ success: true,
9003
+ project_path,
9004
+ total_files_scanned: files2.length,
9005
+ total_matches: matches2.length,
9006
+ cursor: 0,
9007
+ truncated: max_matches !== undefined && matches2.length >= max_matches,
9008
+ matches: matches2
9009
+ };
8758
9010
  }
8759
- const addedGOPattern = /m_AddedGameObjects:\s*\n((?:\s*- \{fileID: \d+\}\n)*)/m;
8760
- const addedGOMatch = piBlock.raw.match(addedGOPattern);
8761
- if (addedGOMatch) {
8762
- const addedGOSection = addedGOMatch[1];
8763
- const goMatches = addedGOSection.matchAll(/fileID:\s*(\d+)/g);
8764
- for (const match of goMatches) {
8765
- const goId = match[1];
8766
- allToRemove.add(goId);
8767
- const goBlock = doc.find_by_file_id(goId);
8768
- if (goBlock) {
8769
- const compMatch = goBlock.raw.match(/component:\s*\{fileID:\s*(\d+)\}/);
8770
- if (compMatch) {
8771
- const transformId = compMatch[1];
8772
- allToRemove.add(transformId);
8773
- const descendants = doc.collect_hierarchy(transformId);
8774
- for (const id of descendants) {
8775
- allToRemove.add(id);
9011
+ if (!isNativeModuleAvailable()) {
9012
+ return {
9013
+ success: false,
9014
+ project_path,
9015
+ total_files_scanned: 0,
9016
+ total_matches: 0,
9017
+ cursor: 0,
9018
+ truncated: false,
9019
+ matches: [],
9020
+ error: "Native scanner module not available. Run bun install in the project root."
9021
+ };
9022
+ }
9023
+ const extensions = [];
9024
+ if (file_type === "scene" || file_type === "all")
9025
+ extensions.push(".unity");
9026
+ if (file_type === "prefab" || file_type === "all")
9027
+ extensions.push(".prefab");
9028
+ const files = walk_project_files(project_path, extensions);
9029
+ const scanner = new UnityScanner;
9030
+ const matches = [];
9031
+ let files_with_errors = 0;
9032
+ for (const file of files) {
9033
+ try {
9034
+ let gameObjects;
9035
+ const needComponents = !!component;
9036
+ const needMetadata = !!(tag || layer !== undefined);
9037
+ if (name && !needMetadata && !needComponents) {
9038
+ gameObjects = scanner.find_by_name(file, name, !exact);
9039
+ } else if (needComponents) {
9040
+ gameObjects = scanner.scan_scene_with_components(file);
9041
+ if (name && !is_match_all(name)) {
9042
+ const nameLower = name.toLowerCase();
9043
+ const hasWildcard = name.includes("*") || name.includes("?");
9044
+ gameObjects = gameObjects.filter((go) => {
9045
+ if (!go.name)
9046
+ return false;
9047
+ if (hasWildcard) {
9048
+ return glob_match(name, go.name);
9049
+ }
9050
+ if (exact) {
9051
+ return go.name === name;
9052
+ }
9053
+ return go.name.toLowerCase().includes(nameLower);
9054
+ });
9055
+ }
9056
+ } else if (needMetadata) {
9057
+ gameObjects = scanner.scan_scene_metadata(file);
9058
+ if (name && !is_match_all(name)) {
9059
+ const nameLower = name.toLowerCase();
9060
+ const hasWildcard = name.includes("*") || name.includes("?");
9061
+ gameObjects = gameObjects.filter((go) => {
9062
+ if (!go.name)
9063
+ return false;
9064
+ if (hasWildcard) {
9065
+ return glob_match(name, go.name);
9066
+ }
9067
+ if (exact) {
9068
+ return go.name === name;
9069
+ }
9070
+ return go.name.toLowerCase().includes(nameLower);
9071
+ });
9072
+ }
9073
+ } else {
9074
+ gameObjects = scanner.scan_scene_minimal(file);
9075
+ }
9076
+ for (const go of gameObjects) {
9077
+ const isFindResult = "resultType" in go;
9078
+ const isPrefab = isFindResult && go.resultType === "PrefabInstance";
9079
+ if (isPrefab && (component || tag || layer !== undefined)) {
9080
+ continue;
9081
+ }
9082
+ if (component) {
9083
+ if ("components" in go && go.components) {
9084
+ const hasComponent = go.components.some((c) => glob_match(component, c.type));
9085
+ if (!hasComponent)
9086
+ continue;
9087
+ } else {
9088
+ continue;
8776
9089
  }
8777
9090
  }
9091
+ if (tag) {
9092
+ if (!("tag" in go) || go.tag !== tag)
9093
+ continue;
9094
+ }
9095
+ if (layer !== undefined) {
9096
+ if (!("layer" in go) || go.layer !== layer)
9097
+ continue;
9098
+ }
9099
+ const relPath = path5.relative(project_path, file);
9100
+ const fileId = isFindResult ? go.fileId : go.file_id;
9101
+ const match = {
9102
+ file: relPath,
9103
+ game_object: go.name,
9104
+ file_id: fileId,
9105
+ tag: "tag" in go ? go.tag : undefined,
9106
+ layer: "layer" in go ? go.layer : undefined
9107
+ };
9108
+ if ("components" in go && go.components) {
9109
+ match.components = go.components.map((c) => c.type);
9110
+ }
9111
+ matches.push(match);
9112
+ if (max_matches !== undefined && matches.length >= max_matches)
9113
+ break;
8778
9114
  }
9115
+ if (max_matches !== undefined && matches.length >= max_matches)
9116
+ break;
9117
+ } catch {
9118
+ files_with_errors++;
9119
+ continue;
8779
9120
  }
8780
9121
  }
8781
- const addedCompPattern = /m_AddedComponents:\s*\n((?:\s*- \{fileID: \d+\}\n)*)/m;
8782
- const addedCompMatch = piBlock.raw.match(addedCompPattern);
8783
- if (addedCompMatch) {
8784
- const addedCompSection = addedCompMatch[1];
8785
- const compMatches = addedCompSection.matchAll(/fileID:\s*(\d+)/g);
8786
- for (const match of compMatches) {
8787
- allToRemove.add(match[1]);
8788
- }
8789
- }
8790
- const parentMatch = piBlock.raw.match(/m_TransformParent:\s*\{fileID:\s*(\d+)\}/);
8791
- const parentTransformId = parentMatch ? parentMatch[1] : "0";
8792
- let strippedRootTransformId = null;
8793
- for (const id of allToRemove) {
8794
- const block = doc.find_by_file_id(id);
8795
- if (block && block.class_id === 4 && block.is_stripped) {
8796
- strippedRootTransformId = id;
8797
- break;
8798
- }
9122
+ return {
9123
+ success: true,
9124
+ project_path,
9125
+ total_files_scanned: files.length,
9126
+ total_matches: matches.length,
9127
+ files_with_errors: files_with_errors > 0 ? files_with_errors : undefined,
9128
+ cursor: 0,
9129
+ truncated: max_matches !== undefined && matches.length >= max_matches,
9130
+ matches
9131
+ };
9132
+ }
9133
+ function grep_project(options) {
9134
+ const nativeGrep = getNativeGrepProject();
9135
+ if (nativeGrep) {
9136
+ try {
9137
+ const nativeResult = nativeGrep({
9138
+ projectPath: options.project_path,
9139
+ pattern: options.pattern,
9140
+ fileType: options.file_type,
9141
+ maxResults: options.max_results,
9142
+ contextLines: options.context_lines
9143
+ });
9144
+ return {
9145
+ success: nativeResult.success,
9146
+ project_path: nativeResult.projectPath,
9147
+ pattern: nativeResult.pattern,
9148
+ total_files_scanned: nativeResult.totalFilesScanned,
9149
+ total_matches: nativeResult.totalMatches,
9150
+ truncated: nativeResult.truncated,
9151
+ error: nativeResult.error,
9152
+ matches: nativeResult.matches.map((m) => ({
9153
+ file: m.file,
9154
+ line_number: m.lineNumber,
9155
+ line: m.line,
9156
+ context_before: m.contextBefore,
9157
+ context_after: m.contextAfter
9158
+ }))
9159
+ };
9160
+ } catch {}
8799
9161
  }
8800
- if (parentTransformId !== "0" && strippedRootTransformId !== null) {
8801
- doc.remove_child_from_parent(parentTransformId, strippedRootTransformId);
9162
+ return grep_project_js(options);
9163
+ }
9164
+ function grep_project_js(options) {
9165
+ const {
9166
+ project_path,
9167
+ pattern,
9168
+ file_type = "all",
9169
+ max_results = 100,
9170
+ context_lines = 0
9171
+ } = options;
9172
+ if (!import_fs12.existsSync(project_path)) {
9173
+ return {
9174
+ success: false,
9175
+ project_path,
9176
+ pattern,
9177
+ total_files_scanned: 0,
9178
+ total_matches: 0,
9179
+ truncated: false,
9180
+ matches: [],
9181
+ error: `Project path not found: ${project_path}`
9182
+ };
8802
9183
  }
8803
- doc.remove_blocks(allToRemove);
8804
- if (!doc.validate()) {
8805
- return { success: false, file_path, error: "Validation failed after deleting PrefabInstance" };
9184
+ let regex;
9185
+ try {
9186
+ regex = new RegExp(pattern, "i");
9187
+ } catch (err) {
9188
+ return {
9189
+ success: false,
9190
+ project_path,
9191
+ pattern,
9192
+ total_files_scanned: 0,
9193
+ total_matches: 0,
9194
+ truncated: false,
9195
+ matches: [],
9196
+ error: `Invalid regex pattern: ${err instanceof Error ? err.message : String(err)}`
9197
+ };
8806
9198
  }
8807
- const writeResult = doc.save();
8808
- if (!writeResult.success) {
8809
- return { success: false, file_path, error: writeResult.error };
9199
+ const EXTENSION_MAP = {
9200
+ cs: [".cs"],
9201
+ yaml: [
9202
+ ".yaml",
9203
+ ".yml",
9204
+ ".unity",
9205
+ ".prefab",
9206
+ ".asset",
9207
+ ".mat",
9208
+ ".anim",
9209
+ ".controller",
9210
+ ".overrideController",
9211
+ ".mask",
9212
+ ".mixer",
9213
+ ".lighting",
9214
+ ".preset",
9215
+ ".signal",
9216
+ ".playable",
9217
+ ".renderTexture",
9218
+ ".flare",
9219
+ ".guiskin",
9220
+ ".terrainlayer",
9221
+ ".cubemap"
9222
+ ],
9223
+ unity: [".unity"],
9224
+ prefab: [".prefab"],
9225
+ asset: [".asset"],
9226
+ mat: [".mat"],
9227
+ anim: [".anim"],
9228
+ controller: [".controller"],
9229
+ all: [
9230
+ ".cs",
9231
+ ".unity",
9232
+ ".prefab",
9233
+ ".asset",
9234
+ ".mat",
9235
+ ".anim",
9236
+ ".controller",
9237
+ ".yaml",
9238
+ ".yml",
9239
+ ".txt",
9240
+ ".json",
9241
+ ".xml",
9242
+ ".shader",
9243
+ ".cginc",
9244
+ ".hlsl",
9245
+ ".compute",
9246
+ ".asmdef",
9247
+ ".asmref"
9248
+ ]
9249
+ };
9250
+ const extensions = EXTENSION_MAP[file_type] || EXTENSION_MAP.all;
9251
+ const files = walk_project_files(project_path, extensions);
9252
+ const matches = [];
9253
+ let totalFilesScanned = 0;
9254
+ let truncated = false;
9255
+ for (const file of files) {
9256
+ const ext = path5.extname(file).toLowerCase();
9257
+ if (BINARY_EXTENSIONS.has(ext))
9258
+ continue;
9259
+ totalFilesScanned++;
9260
+ let content;
9261
+ try {
9262
+ content = import_fs12.readFileSync(file, "utf-8");
9263
+ } catch {
9264
+ continue;
9265
+ }
9266
+ const lines = content.split(`
9267
+ `);
9268
+ const relPath = path5.relative(project_path, file);
9269
+ for (let i = 0;i < lines.length; i++) {
9270
+ if (regex.test(lines[i])) {
9271
+ let line = lines[i];
9272
+ if (line.length > 200) {
9273
+ line = line.substring(0, 200) + "...";
9274
+ }
9275
+ const match = {
9276
+ file: relPath,
9277
+ line_number: i + 1,
9278
+ line
9279
+ };
9280
+ if (context_lines > 0) {
9281
+ match.context_before = [];
9282
+ match.context_after = [];
9283
+ for (let j = Math.max(0, i - context_lines);j < i; j++) {
9284
+ let ctxLine = lines[j];
9285
+ if (ctxLine.length > 200)
9286
+ ctxLine = ctxLine.substring(0, 200) + "...";
9287
+ match.context_before.push(ctxLine);
9288
+ }
9289
+ for (let j = i + 1;j <= Math.min(lines.length - 1, i + context_lines); j++) {
9290
+ let ctxLine = lines[j];
9291
+ if (ctxLine.length > 200)
9292
+ ctxLine = ctxLine.substring(0, 200) + "...";
9293
+ match.context_after.push(ctxLine);
9294
+ }
9295
+ }
9296
+ matches.push(match);
9297
+ if (matches.length >= max_results) {
9298
+ truncated = true;
9299
+ break;
9300
+ }
9301
+ }
9302
+ }
9303
+ if (truncated)
9304
+ break;
8810
9305
  }
8811
9306
  return {
8812
9307
  success: true,
8813
- file_path,
8814
- deleted_count: allToRemove.size
9308
+ project_path,
9309
+ pattern,
9310
+ total_files_scanned: totalFilesScanned,
9311
+ total_matches: matches.length,
9312
+ truncated,
9313
+ matches
8815
9314
  };
8816
9315
  }
8817
- // src/editor/duplicate.ts
8818
- var import_fs13 = require("fs");
8819
- var path5 = __toESM(require("path"));
8820
- init_scanner();
8821
- function findPrefabInstanceByName(doc, name) {
8822
- const piBlocks = doc.find_by_class_id(1001);
8823
- const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8824
- const namePattern = new RegExp(`propertyPath:\\s*m_Name\\s+value:\\s*${escapedName}\\s`);
8825
- for (const block of piBlocks) {
8826
- if (namePattern.test(block.raw)) {
8827
- return { id: parseInt(block.file_id, 10) };
9316
+
9317
+ // src/editor/delete.ts
9318
+ function find_component_by_type(doc, type_name, game_object, project_path) {
9319
+ let scope_blocks;
9320
+ if (game_object) {
9321
+ const goResult = doc.require_unique_game_object(game_object);
9322
+ if ("error" in goResult)
9323
+ return goResult;
9324
+ const comp_ids = [];
9325
+ const matches = goResult.raw.matchAll(/component:[ \t]*\{fileID:[ \t]*(-?\d+)\}/g);
9326
+ for (const m of matches)
9327
+ comp_ids.push(m[1]);
9328
+ scope_blocks = comp_ids.map((id) => doc.find_by_file_id(id)).filter((b) => b !== null);
9329
+ } else {
9330
+ scope_blocks = doc.blocks.filter((b) => b.class_id !== 1 && b.class_id !== 4 && b.class_id !== 224);
9331
+ }
9332
+ const candidates = [];
9333
+ const class_id = get_class_id(type_name);
9334
+ if (class_id !== null) {
9335
+ for (const block of scope_blocks) {
9336
+ if (block.class_id === class_id)
9337
+ candidates.push(block);
9338
+ }
9339
+ } else {
9340
+ let resolved = null;
9341
+ try {
9342
+ resolved = resolveScriptGuid(type_name, project_path);
9343
+ } catch {}
9344
+ if (resolved) {
9345
+ for (const block of scope_blocks) {
9346
+ if (block.class_id !== 114)
9347
+ continue;
9348
+ const scriptMatch = block.raw.match(/m_Script:[ \t]*\{[^}]*guid:[ \t]*([a-f0-9]+)/);
9349
+ if (scriptMatch && scriptMatch[1] === resolved.guid) {
9350
+ candidates.push(block);
9351
+ }
9352
+ }
9353
+ } else {
9354
+ return { error: `Component type "${type_name}" not found. For MonoBehaviour scripts, ensure "unity-agentic-tools setup" has been run.` };
8828
9355
  }
8829
9356
  }
8830
- return null;
9357
+ if (candidates.length === 0) {
9358
+ return { error: `No "${type_name}" component found${game_object ? ` on "${game_object}"` : ""} in ${doc.file_path}` };
9359
+ }
9360
+ if (candidates.length > 1) {
9361
+ const ids = candidates.map((b) => b.file_id).join(", ");
9362
+ return { error: `Multiple "${type_name}" components found (fileIDs: ${ids}). Use a numeric fileID to specify which one${game_object ? "" : ", or use --on <gameobject> to scope the search"}.` };
9363
+ }
9364
+ return candidates[0];
8831
9365
  }
8832
- function duplicateGameObject(options) {
8833
- const { file_path, object_name, new_name } = options;
9366
+ function removeComponent(options) {
9367
+ const { file_path, file_id, game_object, project_path } = options;
9368
+ const pathError = validate_file_path(file_path, "write");
9369
+ if (pathError) {
9370
+ return { success: false, file_path, error: pathError };
9371
+ }
8834
9372
  if (!import_fs13.existsSync(file_path)) {
8835
9373
  return { success: false, file_path, error: `File not found: ${file_path}` };
8836
9374
  }
@@ -8840,24 +9378,306 @@ function duplicateGameObject(options) {
8840
9378
  } catch (err) {
8841
9379
  return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
8842
9380
  }
8843
- const goResult = doc.require_unique_game_object(object_name);
8844
- if ("error" in goResult) {
8845
- const piMatch = findPrefabInstanceByName(doc, object_name);
8846
- if (piMatch) {
8847
- return { success: false, file_path, error: `"${object_name}" is a PrefabInstance (fileID: ${piMatch.id}). Cloning PrefabInstances is not yet supported. Consider unpacking it first with \`update prefab\`.` };
9381
+ let found;
9382
+ if (/^-?\d+$/.test(file_id)) {
9383
+ found = doc.find_by_file_id(file_id);
9384
+ if (!found) {
9385
+ return { success: false, file_path, error: `Component with file ID ${file_id} not found` };
8848
9386
  }
8849
- return { success: false, file_path, error: goResult.error };
9387
+ } else {
9388
+ const result = find_component_by_type(doc, file_id, game_object, project_path);
9389
+ if ("error" in result) {
9390
+ return { success: false, file_path, error: result.error };
9391
+ }
9392
+ found = result;
8850
9393
  }
8851
- const goBlock = goResult;
8852
- const goFileId = goBlock.file_id;
8853
- const componentIds = [];
8854
- const compMatches = goBlock.raw.matchAll(/component:\s*\{fileID:\s*(\d+)\}/g);
8855
- for (const cm of compMatches) {
8856
- componentIds.push(cm[1]);
9394
+ if (found.class_id === 1) {
9395
+ return { success: false, file_path, error: "Cannot remove a GameObject with remove-component. Use delete instead." };
8857
9396
  }
8858
- let transformId = null;
8859
- let fatherId = "0";
8860
- for (const compId of componentIds) {
9397
+ if (found.class_id === 4) {
9398
+ return { success: false, file_path, error: "Cannot remove a Transform with remove-component. Use delete to remove the entire GameObject." };
9399
+ }
9400
+ const resolved_file_id = found.file_id;
9401
+ const goMatch = found.raw.match(/m_GameObject:[ \t]*\{fileID:[ \t]*(-?\d+)\}/);
9402
+ if (goMatch) {
9403
+ const parentGoId = goMatch[1];
9404
+ const goBlock = doc.find_by_file_id(parentGoId);
9405
+ if (goBlock) {
9406
+ const escaped = resolved_file_id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9407
+ const compLinePattern = new RegExp(`^[ \\t]*- component: \\{fileID: ${escaped}\\}[ \\t]*\\n`, "m");
9408
+ const modifiedRaw = goBlock.raw.replace(compLinePattern, "");
9409
+ if (modifiedRaw === goBlock.raw) {}
9410
+ goBlock.replace_raw(modifiedRaw);
9411
+ }
9412
+ }
9413
+ const dangling = [];
9414
+ for (const block of doc.blocks) {
9415
+ if (block.file_id === resolved_file_id)
9416
+ continue;
9417
+ if (block.raw.includes(`{fileID: ${resolved_file_id}}`)) {
9418
+ dangling.push(block.file_id);
9419
+ }
9420
+ }
9421
+ doc.remove_block(resolved_file_id);
9422
+ if (!doc.validate()) {
9423
+ return { success: false, file_path, error: "Validation failed after removing component" };
9424
+ }
9425
+ const writeResult = doc.save();
9426
+ if (!writeResult.success) {
9427
+ return { success: false, file_path, error: writeResult.error };
9428
+ }
9429
+ return {
9430
+ success: true,
9431
+ file_path,
9432
+ removed_file_id: resolved_file_id,
9433
+ removed_class_id: found.class_id,
9434
+ warning: dangling.length > 0 ? `Dangling references to deleted component found in blocks: ${dangling.join(", ")}` : undefined
9435
+ };
9436
+ }
9437
+ function removeComponentBatch(options) {
9438
+ const { project_path, component_type, game_object } = options;
9439
+ const files = walk_project_files(project_path, [".unity", ".prefab"]);
9440
+ const removals = [];
9441
+ const errors = [];
9442
+ let skipped = 0;
9443
+ for (const file of files) {
9444
+ const result = removeComponent({
9445
+ file_path: file,
9446
+ file_id: component_type,
9447
+ game_object,
9448
+ project_path
9449
+ });
9450
+ if (result.success && result.removed_file_id) {
9451
+ removals.push({
9452
+ file,
9453
+ file_id: result.removed_file_id,
9454
+ class_id: result.removed_class_id ?? 0
9455
+ });
9456
+ } else if (result.error?.includes("not found") || result.error?.startsWith('No "')) {
9457
+ skipped++;
9458
+ } else if (result.error) {
9459
+ errors.push({ file, error: result.error });
9460
+ }
9461
+ }
9462
+ return {
9463
+ success: errors.length === 0,
9464
+ project_path,
9465
+ component: component_type,
9466
+ files_scanned: files.length,
9467
+ files_modified: removals.length,
9468
+ removals,
9469
+ skipped,
9470
+ errors
9471
+ };
9472
+ }
9473
+ function deleteGameObject(options) {
9474
+ const { file_path, object_name } = options;
9475
+ const pathError = validate_file_path(file_path, "write");
9476
+ if (pathError) {
9477
+ return { success: false, file_path, error: pathError };
9478
+ }
9479
+ if (!import_fs13.existsSync(file_path)) {
9480
+ return { success: false, file_path, error: `File not found: ${file_path}` };
9481
+ }
9482
+ let doc;
9483
+ try {
9484
+ doc = UnityDocument.from_file(file_path);
9485
+ } catch (err) {
9486
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
9487
+ }
9488
+ const goResult = doc.require_unique_game_object(object_name);
9489
+ if ("error" in goResult) {
9490
+ return { success: false, file_path, error: goResult.error };
9491
+ }
9492
+ const goBlock = goResult;
9493
+ const goId = goBlock.file_id;
9494
+ const componentIds = new Set;
9495
+ const compMatches = goBlock.raw.matchAll(/component:[ \t]*\{fileID:[ \t]*(-?\d+)\}/g);
9496
+ for (const cm of compMatches) {
9497
+ componentIds.add(cm[1]);
9498
+ }
9499
+ let transformId = null;
9500
+ let fatherId = "0";
9501
+ for (const compId of componentIds) {
9502
+ const block = doc.find_by_file_id(compId);
9503
+ if (block && block.class_id === 4) {
9504
+ transformId = compId;
9505
+ const fatherMatch = block.raw.match(/m_Father:[ \t]*\{fileID:[ \t]*(-?\d+)\}/);
9506
+ if (fatherMatch) {
9507
+ fatherId = fatherMatch[1];
9508
+ }
9509
+ break;
9510
+ }
9511
+ }
9512
+ const allIds = new Set([goId]);
9513
+ for (const id of componentIds) {
9514
+ allIds.add(id);
9515
+ }
9516
+ if (transformId !== null) {
9517
+ const descendants = doc.collect_hierarchy(transformId);
9518
+ for (const id of descendants) {
9519
+ allIds.add(id);
9520
+ }
9521
+ }
9522
+ if (fatherId !== "0" && transformId !== null) {
9523
+ doc.remove_child_from_parent(fatherId, transformId);
9524
+ }
9525
+ doc.remove_blocks(allIds);
9526
+ if (!doc.validate()) {
9527
+ return { success: false, file_path, error: "Validation failed after deleting GameObject" };
9528
+ }
9529
+ const writeResult = doc.save();
9530
+ if (!writeResult.success) {
9531
+ return { success: false, file_path, error: writeResult.error };
9532
+ }
9533
+ return {
9534
+ success: true,
9535
+ file_path,
9536
+ deleted_count: allIds.size
9537
+ };
9538
+ }
9539
+ function findPrefabInstanceBlock2(doc, identifier) {
9540
+ const asId = doc.find_by_file_id(identifier);
9541
+ if (asId && asId.class_id === 1001)
9542
+ return asId;
9543
+ const piBlocks = doc.find_by_class_id(1001);
9544
+ const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9545
+ const namePattern = new RegExp(`propertyPath:\\s*m_Name\\s+value:\\s*${escaped}\\s`);
9546
+ for (const block of piBlocks) {
9547
+ if (namePattern.test(block.raw))
9548
+ return block;
9549
+ }
9550
+ return null;
9551
+ }
9552
+ function deletePrefabInstance(options) {
9553
+ const { file_path, prefab_instance } = options;
9554
+ const pathError = validate_file_path(file_path, "write");
9555
+ if (pathError) {
9556
+ return { success: false, file_path, error: pathError };
9557
+ }
9558
+ if (!import_fs13.existsSync(file_path)) {
9559
+ return { success: false, file_path, error: `File not found: ${file_path}` };
9560
+ }
9561
+ let doc;
9562
+ try {
9563
+ doc = UnityDocument.from_file(file_path);
9564
+ } catch (err) {
9565
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
9566
+ }
9567
+ const piBlock = findPrefabInstanceBlock2(doc, prefab_instance);
9568
+ if (!piBlock) {
9569
+ return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
9570
+ }
9571
+ const piId = piBlock.file_id;
9572
+ const allToRemove = new Set([piId]);
9573
+ const piRefPattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${piId}\\}`);
9574
+ for (const block of doc.blocks) {
9575
+ if (block.is_stripped && piRefPattern.test(block.raw)) {
9576
+ allToRemove.add(block.file_id);
9577
+ }
9578
+ }
9579
+ const addedGOPattern = /m_AddedGameObjects:\s*\n((?:\s*- \{fileID: \d+\}\n)*)/m;
9580
+ const addedGOMatch = piBlock.raw.match(addedGOPattern);
9581
+ if (addedGOMatch) {
9582
+ const addedGOSection = addedGOMatch[1];
9583
+ const goMatches = addedGOSection.matchAll(/fileID:[ \t]*(\d+)/g);
9584
+ for (const match of goMatches) {
9585
+ const goId = match[1];
9586
+ allToRemove.add(goId);
9587
+ const goBlock = doc.find_by_file_id(goId);
9588
+ if (goBlock) {
9589
+ const compMatch = goBlock.raw.match(/component:[ \t]*\{fileID:[ \t]*(\d+)\}/);
9590
+ if (compMatch) {
9591
+ const transformId = compMatch[1];
9592
+ allToRemove.add(transformId);
9593
+ const descendants = doc.collect_hierarchy(transformId);
9594
+ for (const id of descendants) {
9595
+ allToRemove.add(id);
9596
+ }
9597
+ }
9598
+ }
9599
+ }
9600
+ }
9601
+ const addedCompPattern = /m_AddedComponents:\s*\n((?:\s*- \{fileID: \d+\}\n)*)/m;
9602
+ const addedCompMatch = piBlock.raw.match(addedCompPattern);
9603
+ if (addedCompMatch) {
9604
+ const addedCompSection = addedCompMatch[1];
9605
+ const compMatches = addedCompSection.matchAll(/fileID:[ \t]*(\d+)/g);
9606
+ for (const match of compMatches) {
9607
+ allToRemove.add(match[1]);
9608
+ }
9609
+ }
9610
+ const parentMatch = piBlock.raw.match(/m_TransformParent:[ \t]*\{fileID:[ \t]*(\d+)\}/);
9611
+ const parentTransformId = parentMatch ? parentMatch[1] : "0";
9612
+ let strippedRootTransformId = null;
9613
+ for (const id of allToRemove) {
9614
+ const block = doc.find_by_file_id(id);
9615
+ if (block && block.class_id === 4 && block.is_stripped) {
9616
+ strippedRootTransformId = id;
9617
+ break;
9618
+ }
9619
+ }
9620
+ if (parentTransformId !== "0" && strippedRootTransformId !== null) {
9621
+ doc.remove_child_from_parent(parentTransformId, strippedRootTransformId);
9622
+ }
9623
+ doc.remove_blocks(allToRemove);
9624
+ if (!doc.validate()) {
9625
+ return { success: false, file_path, error: "Validation failed after deleting PrefabInstance" };
9626
+ }
9627
+ const writeResult = doc.save();
9628
+ if (!writeResult.success) {
9629
+ return { success: false, file_path, error: writeResult.error };
9630
+ }
9631
+ return {
9632
+ success: true,
9633
+ file_path,
9634
+ deleted_count: allToRemove.size
9635
+ };
9636
+ }
9637
+ // src/editor/duplicate.ts
9638
+ var import_fs14 = require("fs");
9639
+ var path6 = __toESM(require("path"));
9640
+ init_scanner();
9641
+ function findPrefabInstanceByName(doc, name) {
9642
+ const piBlocks = doc.find_by_class_id(1001);
9643
+ const escapedName = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
9644
+ const namePattern = new RegExp(`propertyPath:\\s*m_Name\\s+value:\\s*${escapedName}\\s`);
9645
+ for (const block of piBlocks) {
9646
+ if (namePattern.test(block.raw)) {
9647
+ return { id: parseInt(block.file_id, 10) };
9648
+ }
9649
+ }
9650
+ return null;
9651
+ }
9652
+ function duplicateGameObject(options) {
9653
+ const { file_path, object_name, new_name } = options;
9654
+ if (!import_fs14.existsSync(file_path)) {
9655
+ return { success: false, file_path, error: `File not found: ${file_path}` };
9656
+ }
9657
+ let doc;
9658
+ try {
9659
+ doc = UnityDocument.from_file(file_path);
9660
+ } catch (err) {
9661
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
9662
+ }
9663
+ const goResult = doc.require_unique_game_object(object_name);
9664
+ if ("error" in goResult) {
9665
+ const piMatch = findPrefabInstanceByName(doc, object_name);
9666
+ if (piMatch) {
9667
+ return { success: false, file_path, error: `"${object_name}" is a PrefabInstance (fileID: ${piMatch.id}). Cloning PrefabInstances is not yet supported. Consider unpacking it first with \`update prefab\`.` };
9668
+ }
9669
+ return { success: false, file_path, error: goResult.error };
9670
+ }
9671
+ const goBlock = goResult;
9672
+ const goFileId = goBlock.file_id;
9673
+ const componentIds = [];
9674
+ const compMatches = goBlock.raw.matchAll(/component:\s*\{fileID:\s*(\d+)\}/g);
9675
+ for (const cm of compMatches) {
9676
+ componentIds.push(cm[1]);
9677
+ }
9678
+ let transformId = null;
9679
+ let fatherId = "0";
9680
+ for (const compId of componentIds) {
8861
9681
  const compBlock = doc.find_by_file_id(compId);
8862
9682
  if (compBlock && compBlock.class_id === 4) {
8863
9683
  transformId = compId;
@@ -8964,7 +9784,7 @@ function duplicateGameObject(options) {
8964
9784
  }
8965
9785
  function unpackPrefab(options) {
8966
9786
  const { file_path, prefab_instance, project_path } = options;
8967
- if (!import_fs13.existsSync(file_path)) {
9787
+ if (!import_fs14.existsSync(file_path)) {
8968
9788
  return { success: false, file_path, error: `File not found: ${file_path}` };
8969
9789
  }
8970
9790
  let sceneDoc;
@@ -9010,25 +9830,25 @@ function unpackPrefab(options) {
9010
9830
  sourcePrefabPath = resolvedPath;
9011
9831
  }
9012
9832
  }
9013
- if (!sourcePrefabPath || !import_fs13.existsSync(sourcePrefabPath)) {
9014
- const inferredProject = project_path || find_unity_project_root(path5.dirname(file_path));
9833
+ if (!sourcePrefabPath || !import_fs14.existsSync(sourcePrefabPath)) {
9834
+ const inferredProject = project_path || find_unity_project_root(path6.dirname(file_path));
9015
9835
  if (inferredProject) {
9016
9836
  const nativeBuild = getNativeBuildGuidCache();
9017
9837
  if (nativeBuild) {
9018
9838
  try {
9019
9839
  const freshCache = nativeBuild(inferredProject);
9020
9840
  if (freshCache[sourcePrefabGuid]) {
9021
- sourcePrefabPath = path5.join(inferredProject, freshCache[sourcePrefabGuid]);
9841
+ sourcePrefabPath = path6.join(inferredProject, freshCache[sourcePrefabGuid]);
9022
9842
  }
9023
9843
  } catch {}
9024
9844
  }
9025
9845
  }
9026
9846
  }
9027
- if (!sourcePrefabPath || !import_fs13.existsSync(sourcePrefabPath)) {
9847
+ if (!sourcePrefabPath || !import_fs14.existsSync(sourcePrefabPath)) {
9028
9848
  const searchedPaths = [];
9029
9849
  if (project_path)
9030
- searchedPaths.push(`GUID cache: ${path5.join(project_path, ".unity-agentic", "guid-cache.json")}`);
9031
- const inferredProject = project_path || find_unity_project_root(path5.dirname(file_path));
9850
+ searchedPaths.push(`GUID cache: ${path6.join(project_path, ".unity-agentic", "guid-cache.json")}`);
9851
+ const inferredProject = project_path || find_unity_project_root(path6.dirname(file_path));
9032
9852
  if (inferredProject)
9033
9853
  searchedPaths.push(`Native GUID rebuild from: ${inferredProject}`);
9034
9854
  const searchInfo = searchedPaths.length > 0 ? ` Searched: ${searchedPaths.join("; ")}` : "";
@@ -9156,11 +9976,11 @@ function unpackPrefab(options) {
9156
9976
  }
9157
9977
  // src/build-editor.ts
9158
9978
  var fs3 = __toESM(require("fs"));
9159
- var path7 = __toESM(require("path"));
9979
+ var path8 = __toESM(require("path"));
9160
9980
 
9161
9981
  // src/build-settings.ts
9162
9982
  var fs2 = __toESM(require("fs"));
9163
- var path6 = __toESM(require("path"));
9983
+ var path7 = __toESM(require("path"));
9164
9984
  function parse_editor_build_settings(filePath) {
9165
9985
  if (!fs2.existsSync(filePath)) {
9166
9986
  throw new Error(`EditorBuildSettings.asset not found: ${filePath}`);
@@ -9191,7 +10011,7 @@ function parse_build_profile(filePath) {
9191
10011
  throw new Error(`Build profile not found: ${filePath}`);
9192
10012
  }
9193
10013
  const content = fs2.readFileSync(filePath, "utf-8");
9194
- const name = path6.basename(filePath, ".asset");
10014
+ const name = path7.basename(filePath, ".asset");
9195
10015
  const profile = {
9196
10016
  name,
9197
10017
  path: filePath
@@ -9244,7 +10064,7 @@ function get_platform_name(buildTarget) {
9244
10064
  return platforms[buildTarget] || `Unknown(${buildTarget})`;
9245
10065
  }
9246
10066
  function list_build_profiles(projectPath) {
9247
- const profilesPath = path6.join(projectPath, "Assets", "Settings", "Build Profiles");
10067
+ const profilesPath = path7.join(projectPath, "Assets", "Settings", "Build Profiles");
9248
10068
  if (!fs2.existsSync(profilesPath)) {
9249
10069
  return [];
9250
10070
  }
@@ -9252,7 +10072,7 @@ function list_build_profiles(projectPath) {
9252
10072
  const files = fs2.readdirSync(profilesPath);
9253
10073
  for (const file of files) {
9254
10074
  if (file.endsWith(".asset")) {
9255
- const filePath = path6.join(profilesPath, file);
10075
+ const filePath = path7.join(profilesPath, file);
9256
10076
  try {
9257
10077
  profiles.push(parse_build_profile(filePath));
9258
10078
  } catch (e) {}
@@ -9262,7 +10082,7 @@ function list_build_profiles(projectPath) {
9262
10082
  }
9263
10083
  function get_build_settings(projectPath) {
9264
10084
  const projectInfo = get_project_info(projectPath);
9265
- const editorBuildSettingsPath = path6.join(projectPath, "ProjectSettings", "EditorBuildSettings.asset");
10085
+ const editorBuildSettingsPath = path7.join(projectPath, "ProjectSettings", "EditorBuildSettings.asset");
9266
10086
  const editorBuildSettings = parse_editor_build_settings(editorBuildSettingsPath);
9267
10087
  const buildProfiles = list_build_profiles(projectPath);
9268
10088
  return {
@@ -9274,11 +10094,11 @@ function get_build_settings(projectPath) {
9274
10094
 
9275
10095
  // src/build-editor.ts
9276
10096
  function get_build_settings_path(projectPath) {
9277
- return path7.join(projectPath, "ProjectSettings", "EditorBuildSettings.asset");
10097
+ return path8.join(projectPath, "ProjectSettings", "EditorBuildSettings.asset");
9278
10098
  }
9279
10099
  function bootstrap_build_settings(projectPath) {
9280
10100
  const filePath = get_build_settings_path(projectPath);
9281
- const dir = path7.dirname(filePath);
10101
+ const dir = path8.dirname(filePath);
9282
10102
  if (!fs3.existsSync(dir)) {
9283
10103
  fs3.mkdirSync(dir, { recursive: true });
9284
10104
  }
@@ -9318,7 +10138,7 @@ ${scenesYaml}`);
9318
10138
  fs3.renameSync(tempPath, filePath);
9319
10139
  }
9320
10140
  function get_scene_guid(projectPath, scenePath) {
9321
- const fullPath = path7.join(projectPath, scenePath);
10141
+ const fullPath = path8.join(projectPath, scenePath);
9322
10142
  const metaPath = fullPath + ".meta";
9323
10143
  if (!fs3.existsSync(metaPath)) {
9324
10144
  return null;
@@ -9330,7 +10150,7 @@ function get_scene_guid(projectPath, scenePath) {
9330
10150
  function add_scene(projectPath, scenePath, options) {
9331
10151
  const enabled = options?.enabled ?? true;
9332
10152
  const position = options?.position;
9333
- const fullScenePath = path7.join(projectPath, scenePath);
10153
+ const fullScenePath = path8.join(projectPath, scenePath);
9334
10154
  if (!fs3.existsSync(fullScenePath)) {
9335
10155
  return { success: false, message: `Scene file not found: ${scenePath}` };
9336
10156
  }
@@ -9457,15 +10277,15 @@ function move_scene(projectPath, scenePath, newPosition) {
9457
10277
  }
9458
10278
 
9459
10279
  // src/packages.ts
9460
- var import_fs14 = require("fs");
10280
+ var import_fs15 = require("fs");
9461
10281
  var import_path6 = require("path");
9462
10282
  function load_manifest(project_path) {
9463
10283
  const manifest_path = import_path6.join(project_path, "Packages", "manifest.json");
9464
- if (!import_fs14.existsSync(manifest_path)) {
10284
+ if (!import_fs15.existsSync(manifest_path)) {
9465
10285
  return { error: `manifest.json not found at ${manifest_path}` };
9466
10286
  }
9467
10287
  try {
9468
- const raw = import_fs14.readFileSync(manifest_path, "utf-8");
10288
+ const raw = import_fs15.readFileSync(manifest_path, "utf-8");
9469
10289
  const parsed = JSON.parse(raw);
9470
10290
  if (!parsed.dependencies || typeof parsed.dependencies !== "object") {
9471
10291
  return { error: `Invalid manifest.json: missing "dependencies" object` };
@@ -9476,12 +10296,7 @@ function load_manifest(project_path) {
9476
10296
  }
9477
10297
  }
9478
10298
  function save_manifest(manifest_path, manifest) {
9479
- const sorted_deps = {};
9480
- for (const key of Object.keys(manifest.dependencies).sort()) {
9481
- sorted_deps[key] = manifest.dependencies[key];
9482
- }
9483
- const output = { ...manifest, dependencies: sorted_deps };
9484
- import_fs14.writeFileSync(manifest_path, JSON.stringify(output, null, 2) + `
10299
+ import_fs15.writeFileSync(manifest_path, JSON.stringify(manifest, null, 2) + `
9485
10300
  `, "utf-8");
9486
10301
  }
9487
10302
  function list_packages(project_path, search) {
@@ -9535,7 +10350,7 @@ function remove_package(project_path, name) {
9535
10350
  }
9536
10351
 
9537
10352
  // src/input-actions.ts
9538
- var import_fs15 = require("fs");
10353
+ var import_fs16 = require("fs");
9539
10354
  var import_crypto = require("crypto");
9540
10355
  function generate_action_id() {
9541
10356
  const bytes = import_crypto.randomBytes(16);
@@ -9549,18 +10364,19 @@ function generate_action_id() {
9549
10364
  ].join("-");
9550
10365
  }
9551
10366
  function load_input_actions(file) {
9552
- if (!import_fs15.existsSync(file)) {
10367
+ if (!import_fs16.existsSync(file)) {
9553
10368
  return { error: `File not found: ${file}` };
9554
10369
  }
9555
10370
  try {
9556
- const raw = import_fs15.readFileSync(file, "utf-8");
10371
+ const raw = import_fs16.readFileSync(file, "utf-8");
9557
10372
  return JSON.parse(raw);
9558
10373
  } catch (err) {
9559
10374
  return { error: `Failed to parse input actions: ${err instanceof Error ? err.message : String(err)}` };
9560
10375
  }
9561
10376
  }
9562
10377
  function save_input_actions(file, data) {
9563
- import_fs15.writeFileSync(file, JSON.stringify(data, null, 4) + `
10378
+ ensure_parent_dir(file);
10379
+ import_fs16.writeFileSync(file, JSON.stringify(data, null, 4) + `
9564
10380
  `, "utf-8");
9565
10381
  }
9566
10382
  function add_map(data, name) {
@@ -9816,7 +10632,7 @@ function build_create_command() {
9816
10632
  console.log(JSON.stringify({ success: false, error: "--shader <guid> is required" }, null, 2));
9817
10633
  process.exit(1);
9818
10634
  }
9819
- if (import_fs16.existsSync(output_path)) {
10635
+ if (import_fs17.existsSync(output_path)) {
9820
10636
  console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
9821
10637
  process.exit(1);
9822
10638
  }
@@ -9888,7 +10704,8 @@ Material:
9888
10704
  m_Colors: ${color_section}
9889
10705
  m_BuildTextureStacks: []
9890
10706
  `;
9891
- import_fs16.writeFileSync(output_path, mat_content, "utf-8");
10707
+ ensure_parent_dir(output_path);
10708
+ import_fs17.writeFileSync(output_path, mat_content, "utf-8");
9892
10709
  const guid = import_crypto2.randomBytes(16).toString("hex");
9893
10710
  const meta_content = `fileFormatVersion: 2
9894
10711
  guid: ${guid}
@@ -9899,7 +10716,7 @@ NativeFormatImporter:
9899
10716
  assetBundleName:
9900
10717
  assetBundleVariant:
9901
10718
  `;
9902
- import_fs16.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
10719
+ import_fs17.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
9903
10720
  console.log(JSON.stringify({
9904
10721
  success: true,
9905
10722
  file: output_path,
@@ -9929,7 +10746,7 @@ NativeFormatImporter:
9929
10746
  process.exitCode = 1;
9930
10747
  return;
9931
10748
  }
9932
- if (import_fs16.existsSync(output_path)) {
10749
+ if (import_fs17.existsSync(output_path)) {
9933
10750
  console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
9934
10751
  process.exitCode = 1;
9935
10752
  return;
@@ -9956,7 +10773,7 @@ ScriptedImporter:
9956
10773
  wrapperClassName:
9957
10774
  wrapperCodeNamespace:
9958
10775
  `;
9959
- import_fs16.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
10776
+ import_fs17.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
9960
10777
  console.log(JSON.stringify({
9961
10778
  success: true,
9962
10779
  file: output_path,
@@ -9972,7 +10789,7 @@ ScriptedImporter:
9972
10789
  return;
9973
10790
  }
9974
10791
  const name = name_arg || import_path7.basename(output_path).replace(/\.anim$/i, "");
9975
- if (import_fs16.existsSync(output_path)) {
10792
+ if (import_fs17.existsSync(output_path)) {
9976
10793
  console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
9977
10794
  process.exitCode = 1;
9978
10795
  return;
@@ -10039,7 +10856,8 @@ AnimationClip:
10039
10856
  m_HasMotionFloatCurves: 0
10040
10857
  m_Events: []
10041
10858
  `;
10042
- import_fs16.writeFileSync(output_path, anim_content, "utf-8");
10859
+ ensure_parent_dir(output_path);
10860
+ import_fs17.writeFileSync(output_path, anim_content, "utf-8");
10043
10861
  const guid = import_crypto2.randomBytes(16).toString("hex");
10044
10862
  const meta_content = `fileFormatVersion: 2
10045
10863
  guid: ${guid}
@@ -10050,7 +10868,7 @@ NativeFormatImporter:
10050
10868
  assetBundleName:
10051
10869
  assetBundleVariant:
10052
10870
  `;
10053
- import_fs16.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
10871
+ import_fs17.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
10054
10872
  console.log(JSON.stringify({
10055
10873
  success: true,
10056
10874
  file: output_path,
@@ -10068,7 +10886,7 @@ NativeFormatImporter:
10068
10886
  process.exitCode = 1;
10069
10887
  return;
10070
10888
  }
10071
- if (import_fs16.existsSync(output_path)) {
10889
+ if (import_fs17.existsSync(output_path)) {
10072
10890
  console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
10073
10891
  process.exitCode = 1;
10074
10892
  return;
@@ -10118,7 +10936,8 @@ AnimatorStateMachine:
10118
10936
  m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
10119
10937
  m_DefaultState: {fileID: 0}
10120
10938
  `;
10121
- import_fs16.writeFileSync(output_path, ctrl_content, "utf-8");
10939
+ ensure_parent_dir(output_path);
10940
+ import_fs17.writeFileSync(output_path, ctrl_content, "utf-8");
10122
10941
  const guid = import_crypto2.randomBytes(16).toString("hex");
10123
10942
  const meta_content = `fileFormatVersion: 2
10124
10943
  guid: ${guid}
@@ -10129,7 +10948,7 @@ NativeFormatImporter:
10129
10948
  assetBundleName:
10130
10949
  assetBundleVariant:
10131
10950
  `;
10132
- import_fs16.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
10951
+ import_fs17.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
10133
10952
  console.log(JSON.stringify({
10134
10953
  success: true,
10135
10954
  file: output_path,
@@ -10146,7 +10965,7 @@ NativeFormatImporter:
10146
10965
  process.exitCode = 1;
10147
10966
  return;
10148
10967
  }
10149
- if (import_fs16.existsSync(output_path)) {
10968
+ if (import_fs17.existsSync(output_path)) {
10150
10969
  console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
10151
10970
  process.exitCode = 1;
10152
10971
  return;
@@ -10185,7 +11004,8 @@ Transform:
10185
11004
  m_Father: {fileID: 0}
10186
11005
  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
10187
11006
  `;
10188
- import_fs16.writeFileSync(output_path, prefab_content, "utf-8");
11007
+ ensure_parent_dir(output_path);
11008
+ import_fs17.writeFileSync(output_path, prefab_content, "utf-8");
10189
11009
  const guid = import_crypto2.randomBytes(16).toString("hex");
10190
11010
  const meta_content = `fileFormatVersion: 2
10191
11011
  guid: ${guid}
@@ -10195,7 +11015,7 @@ PrefabImporter:
10195
11015
  assetBundleName:
10196
11016
  assetBundleVariant:
10197
11017
  `;
10198
- import_fs16.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
11018
+ import_fs17.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
10199
11019
  console.log(JSON.stringify({
10200
11020
  success: true,
10201
11021
  file: output_path,
@@ -10209,7 +11029,7 @@ PrefabImporter:
10209
11029
 
10210
11030
  // src/cmd-read.ts
10211
11031
  init_scanner();
10212
- var import_fs17 = require("fs");
11032
+ var import_fs18 = require("fs");
10213
11033
  var import_path8 = require("path");
10214
11034
  var import_os = require("os");
10215
11035
  function parse_material_yaml(content) {
@@ -10456,7 +11276,7 @@ function categorize_asset(filePath) {
10456
11276
  function find_project_root_from_file(filePath) {
10457
11277
  let dir = import_path8.dirname(import_path8.resolve(filePath));
10458
11278
  for (let i = 0;i < 20; i++) {
10459
- if (import_fs17.existsSync(import_path8.join(dir, "Assets")) && import_fs17.existsSync(import_path8.join(dir, "ProjectSettings"))) {
11279
+ if (import_fs18.existsSync(import_path8.join(dir, "Assets")) && import_fs18.existsSync(import_path8.join(dir, "ProjectSettings"))) {
10460
11280
  return dir;
10461
11281
  }
10462
11282
  const parent = import_path8.dirname(dir);
@@ -10491,10 +11311,12 @@ function parse_log_line_timestamp(line) {
10491
11311
  }
10492
11312
  function parse_log_entries(lines) {
10493
11313
  const entries = [];
10494
- const error_re = /^(error|exception|Error|Exception|ERROR)/i;
10495
- const warning_re = /^(warning|Warning|WARNING)/i;
11314
+ const error_re = /\b(error|exception)\b/i;
11315
+ const runtime_error_re = /\b(NullReferenceException|IndexOutOfRangeException|ArgumentException|MissingReferenceException|StackOverflowException|DivideByZeroException|InvalidOperationException|KeyNotFoundException|FormatException|ObjectDisposedException|MissingComponentException|UnassignedReferenceException)\b/;
11316
+ const warning_re = /\bwarning\b/i;
10496
11317
  const compile_re = /Assets\/.*\.cs\(\d+,\d+\):\s*error\s+CS/;
10497
11318
  const import_error_re = /Failed to import|Error while importing|Could not create asset|Unable to import|Shader error in|Import of asset .* failed/i;
11319
+ const assertion_re = /^Assertion failed/;
10498
11320
  const stack_re = /^\s+at\s+|^\s*\(Filename:/;
10499
11321
  for (let i = 0;i < lines.length; i++) {
10500
11322
  const line = lines[i];
@@ -10503,7 +11325,9 @@ function parse_log_entries(lines) {
10503
11325
  let level = "info";
10504
11326
  if (import_error_re.test(line))
10505
11327
  level = "import_error";
10506
- else if (error_re.test(line) || compile_re.test(line))
11328
+ else if (compile_re.test(line))
11329
+ level = "error";
11330
+ else if (error_re.test(line) || runtime_error_re.test(line) || assertion_re.test(line))
10507
11331
  level = "error";
10508
11332
  else if (warning_re.test(line))
10509
11333
  level = "warning";
@@ -10802,7 +11626,7 @@ function walk_files(dir, extensions) {
10802
11626
  const current = stack.pop();
10803
11627
  let entries;
10804
11628
  try {
10805
- entries = import_fs17.readdirSync(current);
11629
+ entries = import_fs18.readdirSync(current);
10806
11630
  } catch {
10807
11631
  continue;
10808
11632
  }
@@ -10811,7 +11635,7 @@ function walk_files(dir, extensions) {
10811
11635
  continue;
10812
11636
  const full = import_path8.join(current, entry);
10813
11637
  try {
10814
- const stat = import_fs17.statSync(full);
11638
+ const stat = import_fs18.statSync(full);
10815
11639
  if (stat.isDirectory()) {
10816
11640
  stack.push(full);
10817
11641
  } else if (extensions.has(import_path8.extname(full).toLowerCase())) {
@@ -10825,7 +11649,7 @@ function walk_files(dir, extensions) {
10825
11649
  return results;
10826
11650
  }
10827
11651
  function validate_unity_yaml(file) {
10828
- if (!import_fs17.existsSync(file)) {
11652
+ if (!import_fs18.existsSync(file)) {
10829
11653
  return `File not found: ${file}`;
10830
11654
  }
10831
11655
  try {
@@ -10956,7 +11780,7 @@ function build_read_command(getScanner) {
10956
11780
  }
10957
11781
  if (result.total === 0) {
10958
11782
  try {
10959
- const fileSize = import_fs17.statSync(file).size;
11783
+ const fileSize = import_fs18.statSync(file).size;
10960
11784
  if (fileSize > 100) {
10961
11785
  const corruptWarning = "File has valid Unity YAML header but contains no parseable GameObjects -- file may be corrupt or malformed";
10962
11786
  result.warning = result.warning ? `${result.warning}; ${corruptWarning}` : corruptWarning;
@@ -11069,7 +11893,7 @@ function build_read_command(getScanner) {
11069
11893
  console.log(JSON.stringify({ error: matValidationError }, null, 2));
11070
11894
  process.exit(1);
11071
11895
  }
11072
- const content = import_fs17.readFileSync(file, "utf-8");
11896
+ const content = import_fs18.readFileSync(file, "utf-8");
11073
11897
  if (!content.includes("Material:")) {
11074
11898
  console.log(JSON.stringify({ error: `File "${file}" does not contain a Material block. Use 'read asset' for generic Unity YAML files.` }, null, 2));
11075
11899
  process.exit(1);
@@ -11111,13 +11935,13 @@ function build_read_command(getScanner) {
11111
11935
  }, null, 2));
11112
11936
  });
11113
11937
  cmd.command("dependencies <file>").description("List asset dependencies (GUIDs referenced by this file)").option("--project <path>", "Unity project root (for GUID resolution)").option("--unresolved", "Show only GUIDs that could not be resolved").option("--recursive [depth]", "Follow dependency chain N levels deep (default: 3)").option("-j, --json", "Output as JSON").action((file, options) => {
11114
- if (!import_fs17.existsSync(file)) {
11938
+ if (!import_fs18.existsSync(file)) {
11115
11939
  console.log(JSON.stringify({ error: `File not found: ${file}` }, null, 2));
11116
11940
  process.exit(1);
11117
11941
  }
11118
11942
  let content;
11119
11943
  try {
11120
- content = import_fs17.readFileSync(file, "utf-8");
11944
+ content = import_fs18.readFileSync(file, "utf-8");
11121
11945
  } catch {
11122
11946
  console.log(JSON.stringify({ error: `Cannot read file: ${file}` }, null, 2));
11123
11947
  process.exit(1);
@@ -11158,7 +11982,7 @@ function build_read_command(getScanner) {
11158
11982
  const result = [];
11159
11983
  let fileContent;
11160
11984
  try {
11161
- fileContent = import_fs17.readFileSync(filePath, "utf-8");
11985
+ fileContent = import_fs18.readFileSync(filePath, "utf-8");
11162
11986
  } catch {
11163
11987
  return [];
11164
11988
  }
@@ -11175,7 +11999,7 @@ function build_read_command(getScanner) {
11175
11999
  const rAbsPath = cache.resolve_absolute(g);
11176
12000
  const rType = rPath ? categorize_asset(rPath) : "unknown";
11177
12001
  const dep = { guid: g, path: rPath, type: rType, depth };
11178
- if (rAbsPath && depth < max_depth && import_fs17.existsSync(rAbsPath)) {
12002
+ if (rAbsPath && depth < max_depth && import_fs18.existsSync(rAbsPath)) {
11179
12003
  const subs = traverse_file(rAbsPath, depth + 1);
11180
12004
  if (subs.length > 0)
11181
12005
  dep.sub_dependencies = subs;
@@ -11204,7 +12028,7 @@ function build_read_command(getScanner) {
11204
12028
  const rDep = { guid: dep.guid, path: dep.path, type: dep.type, depth: 0 };
11205
12029
  if (dep.path) {
11206
12030
  const absPath = cache.resolve_absolute(dep.guid);
11207
- if (absPath && import_fs17.existsSync(absPath)) {
12031
+ if (absPath && import_fs18.existsSync(absPath)) {
11208
12032
  const subs = traverse_file(absPath, 1);
11209
12033
  if (subs.length > 0)
11210
12034
  rDep.sub_dependencies = subs;
@@ -11389,7 +12213,7 @@ function build_read_command(getScanner) {
11389
12213
  });
11390
12214
  cmd.command("target <file> <gameobject_name> [component_type]").description("Build a --target reference string for prefab override commands").option("-p, --project <path>", "Unity project path").option("-j, --json", "Output as JSON").action((file, gameobject_name, component_type, options) => {
11391
12215
  try {
11392
- if (!import_fs17.existsSync(file)) {
12216
+ if (!import_fs18.existsSync(file)) {
11393
12217
  console.log(JSON.stringify({ error: `File not found: ${file}` }, null, 2));
11394
12218
  process.exit(1);
11395
12219
  }
@@ -11509,7 +12333,7 @@ function build_read_command(getScanner) {
11509
12333
  }
11510
12334
  });
11511
12335
  cmd.command("script <file>").description("Extract C# type declarations from a .cs file or .NET DLL").option("-j, --json", "Output as JSON").action((file, _options) => {
11512
- if (!import_fs17.existsSync(file)) {
12336
+ if (!import_fs18.existsSync(file)) {
11513
12337
  console.log(JSON.stringify({ error: `File not found: ${file}` }, null, 2));
11514
12338
  process.exit(1);
11515
12339
  }
@@ -11598,11 +12422,11 @@ function build_read_command(getScanner) {
11598
12422
  if (projectPath && !options.project)
11599
12423
  options.project = projectPath;
11600
12424
  const logPath = options.path || get_editor_log_path();
11601
- if (!logPath || !import_fs17.existsSync(logPath)) {
12425
+ if (!logPath || !import_fs18.existsSync(logPath)) {
11602
12426
  console.log(JSON.stringify({ error: `Editor.log not found${logPath ? `: ${logPath}` : ". Could not detect platform log path."}` }, null, 2));
11603
12427
  process.exit(1);
11604
12428
  }
11605
- const content = import_fs17.readFileSync(logPath, "utf-8");
12429
+ const content = import_fs18.readFileSync(logPath, "utf-8");
11606
12430
  let lines = content.split(/\r?\n/);
11607
12431
  if (options.project) {
11608
12432
  const projectPath2 = import_path8.resolve(options.project);
@@ -11675,10 +12499,6 @@ function build_read_command(getScanner) {
11675
12499
  }
11676
12500
  }
11677
12501
  }
11678
- if (options.search) {
11679
- const re = new RegExp(options.search, "i");
11680
- lines = lines.filter((l) => re.test(l));
11681
- }
11682
12502
  if (options.errors || options.warnings || options.compileErrors || options.importErrors) {
11683
12503
  const entries = parse_log_entries(lines);
11684
12504
  let filtered = entries;
@@ -11692,6 +12512,10 @@ function build_read_command(getScanner) {
11692
12512
  } else if (options.warnings) {
11693
12513
  filtered = entries.filter((e) => e.level === "warning");
11694
12514
  }
12515
+ if (options.search) {
12516
+ const re = new RegExp(options.search, "i");
12517
+ filtered = filtered.filter((e) => re.test(e.message) || e.stack_trace && e.stack_trace.some((s) => re.test(s)));
12518
+ }
11695
12519
  const tail_filtered = parseInt(options.tail, 10);
11696
12520
  if (isNaN(tail_filtered) || tail_filtered < 1) {
11697
12521
  console.log(JSON.stringify({ error: `Invalid --tail value "${options.tail}". Must be a positive integer.` }, null, 2));
@@ -11707,6 +12531,10 @@ function build_read_command(getScanner) {
11707
12531
  }, null, 2));
11708
12532
  return;
11709
12533
  }
12534
+ if (options.search) {
12535
+ const re = new RegExp(options.search, "i");
12536
+ lines = lines.filter((l) => re.test(l));
12537
+ }
11710
12538
  const tail = parseInt(options.tail, 10);
11711
12539
  if (isNaN(tail) || tail < 1) {
11712
12540
  console.log(JSON.stringify({ error: `Invalid --tail value "${options.tail}". Must be a positive integer.` }, null, 2));
@@ -11723,11 +12551,11 @@ function build_read_command(getScanner) {
11723
12551
  });
11724
12552
  cmd.command("meta <file>").description("Read a Unity .meta file and show importer settings").option("--summary", "Show importer type and key settings only").option("-j, --json", "Output as JSON").action((file, options) => {
11725
12553
  const metaPath = file.endsWith(".meta") ? file : `${file}.meta`;
11726
- if (!import_fs17.existsSync(metaPath)) {
12554
+ if (!import_fs18.existsSync(metaPath)) {
11727
12555
  console.log(JSON.stringify({ error: `Meta file not found: ${metaPath}` }, null, 2));
11728
12556
  process.exit(1);
11729
12557
  }
11730
- const content = import_fs17.readFileSync(metaPath, "utf-8");
12558
+ const content = import_fs18.readFileSync(metaPath, "utf-8");
11731
12559
  const lines = content.split(/\r?\n/);
11732
12560
  let guid = "";
11733
12561
  let importer_type = "Unknown";
@@ -11815,7 +12643,7 @@ function build_read_command(getScanner) {
11815
12643
  console.log(JSON.stringify({ error: animValidationError }, null, 2));
11816
12644
  process.exit(1);
11817
12645
  }
11818
- const content = import_fs17.readFileSync(file, "utf-8");
12646
+ const content = import_fs18.readFileSync(file, "utf-8");
11819
12647
  const clip = parse_animation_yaml(content, options.curves === true);
11820
12648
  if (!clip) {
11821
12649
  console.log(JSON.stringify({ error: `No AnimationClip found in "${file}". Is this an .anim file?` }, null, 2));
@@ -11876,7 +12704,7 @@ function build_read_command(getScanner) {
11876
12704
  console.log(JSON.stringify({ error: ctrlValidationError }, null, 2));
11877
12705
  process.exit(1);
11878
12706
  }
11879
- const content = import_fs17.readFileSync(file, "utf-8");
12707
+ const content = import_fs18.readFileSync(file, "utf-8");
11880
12708
  const blocks = split_yaml_blocks(content);
11881
12709
  const controller_block = blocks.find((b) => b.class_id === 91);
11882
12710
  if (!controller_block) {
@@ -12106,7 +12934,7 @@ function build_read_command(getScanner) {
12106
12934
  process.exit(1);
12107
12935
  }
12108
12936
  const assetsDir = import_path8.join(import_path8.resolve(project_path), "Assets");
12109
- if (!import_fs17.existsSync(assetsDir)) {
12937
+ if (!import_fs18.existsSync(assetsDir)) {
12110
12938
  console.log(JSON.stringify({ error: `Assets directory not found in "${project_path}"` }, null, 2));
12111
12939
  process.exit(1);
12112
12940
  }
@@ -12135,7 +12963,7 @@ function build_read_command(getScanner) {
12135
12963
  const guid_pattern = `guid: ${guid}`;
12136
12964
  for (const f of files) {
12137
12965
  try {
12138
- const fc = import_fs17.readFileSync(f, "utf-8");
12966
+ const fc = import_fs18.readFileSync(f, "utf-8");
12139
12967
  if (fc.includes(guid_pattern)) {
12140
12968
  const rel = import_path8.relative(import_path8.resolve(project_path), f);
12141
12969
  const ftype = categorize_asset(f);
@@ -12165,7 +12993,7 @@ function build_read_command(getScanner) {
12165
12993
  cmd.command("unused <project_path>").description("Find potentially unused assets (zero inbound GUID references)").option("--type <type>", "Filter to specific asset types").option("--ignore <glob>", "Exclude paths matching this pattern").option("--max <n>", "Maximum results to return (default 200)", "200").option("-j, --json", "Output as JSON").action((project_path, options) => {
12166
12994
  const resolvedProject = import_path8.resolve(project_path);
12167
12995
  const assetsDir = import_path8.join(resolvedProject, "Assets");
12168
- if (!import_fs17.existsSync(assetsDir)) {
12996
+ if (!import_fs18.existsSync(assetsDir)) {
12169
12997
  console.log(JSON.stringify({ error: `Assets directory not found in "${project_path}"` }, null, 2));
12170
12998
  process.exit(1);
12171
12999
  }
@@ -12193,7 +13021,7 @@ function build_read_command(getScanner) {
12193
13021
  const guid_re = /guid:\s*([a-f0-9]{32})/g;
12194
13022
  for (const f of files) {
12195
13023
  try {
12196
- const fc = import_fs17.readFileSync(f, "utf-8");
13024
+ const fc = import_fs18.readFileSync(f, "utf-8");
12197
13025
  let gm;
12198
13026
  while ((gm = guid_re.exec(fc)) !== null) {
12199
13027
  referenced_guids.add(gm[1]);
@@ -12204,8 +13032,8 @@ function build_read_command(getScanner) {
12204
13032
  }
12205
13033
  const buildSettingsPath = import_path8.join(resolvedProject, "ProjectSettings", "EditorBuildSettings.asset");
12206
13034
  const build_scene_guids = new Set;
12207
- if (import_fs17.existsSync(buildSettingsPath)) {
12208
- const bsc = import_fs17.readFileSync(buildSettingsPath, "utf-8");
13035
+ if (import_fs18.existsSync(buildSettingsPath)) {
13036
+ const bsc = import_fs18.readFileSync(buildSettingsPath, "utf-8");
12209
13037
  let bm;
12210
13038
  while ((bm = guid_re.exec(bsc)) !== null) {
12211
13039
  build_scene_guids.add(bm[1]);
@@ -12327,7 +13155,7 @@ function build_read_command(getScanner) {
12327
13155
  }
12328
13156
 
12329
13157
  // src/cmd-update.ts
12330
- var import_fs18 = require("fs");
13158
+ var import_fs19 = require("fs");
12331
13159
  var import_path9 = require("path");
12332
13160
 
12333
13161
  // src/animator-utils.ts
@@ -12494,7 +13322,7 @@ function build_update_command(getScanner) {
12494
13322
  if (!result.success)
12495
13323
  process.exitCode = 1;
12496
13324
  });
12497
- cmd.command("component <file> <file_id> <property> <value>").description("Edit any component property by file ID. Supports dotted paths (m_LocalPosition.x) and array paths (m_Materials.Array.data[0]). Quote paths with brackets or use dot notation (data.0) to avoid shell glob expansion").option("-j, --json", "Output as JSON").action((file, file_id, property, value, _options) => {
13325
+ cmd.command("component <file> <file_id> <property> <value>").description("Edit any component property by file ID. Supports dotted paths (m_LocalPosition.x) and array paths (m_Materials.Array.data[0]). Quote paths with brackets or use dot notation (data.0) to avoid shell glob expansion").option("-j, --json", "Output as JSON").option("-p, --project <path>", "Unity project path (for asset reference resolution)").action((file, file_id, property, value, options) => {
12498
13326
  if (property === "m_RootOrder") {
12499
13327
  const num = Number(value);
12500
13328
  if (!Number.isInteger(num) || num < 0) {
@@ -12507,7 +13335,8 @@ function build_update_command(getScanner) {
12507
13335
  file_path: file,
12508
13336
  file_id,
12509
13337
  property,
12510
- new_value: value
13338
+ new_value: value,
13339
+ project_path: options.project
12511
13340
  });
12512
13341
  console.log(JSON.stringify(result, null, 2));
12513
13342
  if (!result.success)
@@ -12942,11 +13771,11 @@ function build_update_command(getScanner) {
12942
13771
  }
12943
13772
  }
12944
13773
  }).action((file, options) => {
12945
- if (!import_fs18.existsSync(file)) {
13774
+ if (!import_fs19.existsSync(file)) {
12946
13775
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
12947
13776
  process.exit(1);
12948
13777
  }
12949
- let content = import_fs18.readFileSync(file, "utf-8");
13778
+ let content = import_fs19.readFileSync(file, "utf-8");
12950
13779
  const changes = [];
12951
13780
  if (options.shader) {
12952
13781
  const guid = options.shader;
@@ -13095,7 +13924,7 @@ ${lm[1]} - ${kw}
13095
13924
  process.exitCode = 1;
13096
13925
  return;
13097
13926
  }
13098
- import_fs18.writeFileSync(file, content, "utf-8");
13927
+ import_fs19.writeFileSync(file, content, "utf-8");
13099
13928
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
13100
13929
  });
13101
13930
  cmd.command("meta [file]").description("Edit Unity .meta file importer settings").option("--set <key=value>", "Set an importer setting (e.g., isReadable=1)", (v, p) => [...p, v], []).option("--max-size <n>", "Set TextureImporter maxTextureSize").option("--compression <type>", "Set textureCompression (0=None, 1=LowQuality, 2=Normal, 3=HighQuality)").option("--filter-mode <mode>", "Set filterMode (0=Point, 1=Bilinear, 2=Trilinear)").option("--read-write", "Enable isReadable").option("--no-read-write", "Disable isReadable").option("--batch <glob>", "Apply to all matching files").option("--dry-run", "Preview changes without writing").option("-j, --json", "Output as JSON").action((file, options) => {
@@ -13156,9 +13985,9 @@ ${lm[1]} - ${kw}
13156
13985
  const suffix = import_path9.basename(pattern).replace(/^\*+/, "");
13157
13986
  const meta_files = [];
13158
13987
  const scan = (d) => {
13159
- for (const entry of import_fs18.readdirSync(d)) {
13988
+ for (const entry of import_fs19.readdirSync(d)) {
13160
13989
  const full = import_path9.join(d, entry);
13161
- if (import_fs18.statSync(full).isDirectory()) {
13990
+ if (import_fs19.statSync(full).isDirectory()) {
13162
13991
  if (pattern.includes("**"))
13163
13992
  scan(full);
13164
13993
  } else if (entry.endsWith(suffix)) {
@@ -13166,7 +13995,7 @@ ${lm[1]} - ${kw}
13166
13995
  }
13167
13996
  }
13168
13997
  };
13169
- if (import_fs18.existsSync(dir))
13998
+ if (import_fs19.existsSync(dir))
13170
13999
  scan(dir);
13171
14000
  if (meta_files.length === 0) {
13172
14001
  console.log(JSON.stringify({ success: false, error: `No files matched pattern: ${glob_pattern}` }, null, 2));
@@ -13174,7 +14003,7 @@ ${lm[1]} - ${kw}
13174
14003
  }
13175
14004
  const results = [];
13176
14005
  for (const mf of meta_files) {
13177
- let mc = import_fs18.readFileSync(mf, "utf-8");
14006
+ let mc = import_fs19.readFileSync(mf, "utf-8");
13178
14007
  const file_changes = [];
13179
14008
  for (const edit of edits) {
13180
14009
  const re = new RegExp(`^(\\s*${edit.key}:)[ \\t]*.*$`, "m");
@@ -13185,7 +14014,7 @@ ${lm[1]} - ${kw}
13185
14014
  }
13186
14015
  if (file_changes.length > 0) {
13187
14016
  if (!options.dryRun)
13188
- import_fs18.writeFileSync(mf, mc, "utf-8");
14017
+ import_fs19.writeFileSync(mf, mc, "utf-8");
13189
14018
  results.push({ file: mf, changes: file_changes });
13190
14019
  }
13191
14020
  }
@@ -13198,11 +14027,11 @@ ${lm[1]} - ${kw}
13198
14027
  }, null, 2));
13199
14028
  return;
13200
14029
  }
13201
- if (!import_fs18.existsSync(metaPath)) {
14030
+ if (!import_fs19.existsSync(metaPath)) {
13202
14031
  console.log(JSON.stringify({ success: false, error: `Meta file not found: ${metaPath}` }, null, 2));
13203
14032
  process.exit(1);
13204
14033
  }
13205
- let content = import_fs18.readFileSync(metaPath, "utf-8");
14034
+ let content = import_fs19.readFileSync(metaPath, "utf-8");
13206
14035
  const changes = [];
13207
14036
  for (const edit of edits) {
13208
14037
  const re = new RegExp(`^(\\s*${edit.key}:)[ \\t]*.*$`, "m");
@@ -13217,11 +14046,11 @@ ${lm[1]} - ${kw}
13217
14046
  console.log(JSON.stringify({ success: true, dry_run: true, file: metaPath, changes }, null, 2));
13218
14047
  return;
13219
14048
  }
13220
- import_fs18.writeFileSync(metaPath, content, "utf-8");
14049
+ import_fs19.writeFileSync(metaPath, content, "utf-8");
13221
14050
  console.log(JSON.stringify({ success: true, file: metaPath, changes }, null, 2));
13222
14051
  });
13223
14052
  cmd.command("animation <file>").description("Edit AnimationClip settings and events").option("--set <property=value>", "Set a clip property (e.g., wrap-mode=2 for Loop)", (v, p) => [...p, v], []).option("--add-event <time,function[,data]>", "Add an animation event (e.g., 0.5,OnFootstep,left)", (v, p) => [...p, v], []).option("--remove-event <index>", "Remove an animation event by index (0-based)").option("-j, --json", "Output as JSON").action((file, options) => {
13224
- if (!import_fs18.existsSync(file)) {
14053
+ if (!import_fs19.existsSync(file)) {
13225
14054
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
13226
14055
  process.exit(1);
13227
14056
  }
@@ -13229,7 +14058,7 @@ ${lm[1]} - ${kw}
13229
14058
  console.log(JSON.stringify({ success: false, error: `File is not an AnimationClip (.anim): ${file}` }, null, 2));
13230
14059
  process.exit(1);
13231
14060
  }
13232
- let content = import_fs18.readFileSync(file, "utf-8");
14061
+ let content = import_fs19.readFileSync(file, "utf-8");
13233
14062
  const changes = [];
13234
14063
  const property_map = {
13235
14064
  "wrap-mode": "m_WrapMode",
@@ -13390,16 +14219,16 @@ ${event_yaml}
13390
14219
  process.exitCode = 1;
13391
14220
  return;
13392
14221
  }
13393
- import_fs18.writeFileSync(file, content, "utf-8");
14222
+ import_fs19.writeFileSync(file, content, "utf-8");
13394
14223
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
13395
14224
  });
13396
14225
  const PARAM_TYPE_MAP = { float: 1, int: 3, bool: 4, trigger: 9 };
13397
14226
  cmd.command("animator <file>").description("Edit AnimatorController parameters").option("--add-parameter <name>", "Add a new parameter").option("--type <float|int|bool|trigger>", "Parameter type (required with --add-parameter)").option("--remove-parameter <name>", "Remove a parameter by name").option("--set-default <param=value>", "Set parameter default value (e.g., Speed=1.5)", (v, p) => [...p, v], []).option("-j, --json", "Output as JSON").action((file, options) => {
13398
- if (!import_fs18.existsSync(file)) {
14227
+ if (!import_fs19.existsSync(file)) {
13399
14228
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
13400
14229
  process.exit(1);
13401
14230
  }
13402
- let content = import_fs18.readFileSync(file, "utf-8");
14231
+ let content = import_fs19.readFileSync(file, "utf-8");
13403
14232
  const had_crlf = content.includes(`\r
13404
14233
  `);
13405
14234
  if (had_crlf)
@@ -13501,7 +14330,7 @@ $1`);
13501
14330
  if (had_crlf)
13502
14331
  content = content.replace(/\n/g, `\r
13503
14332
  `);
13504
- import_fs18.writeFileSync(file, content, "utf-8");
14333
+ import_fs19.writeFileSync(file, content, "utf-8");
13505
14334
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
13506
14335
  });
13507
14336
  cmd.command("sibling-index <file> <object_name> <index>").description("Set the sibling index of a GameObject, renumbering all siblings").option("-j, --json", "Output as JSON").action((file, object_name, index_str, _options) => {
@@ -13511,7 +14340,7 @@ $1`);
13511
14340
  process.exitCode = 1;
13512
14341
  return;
13513
14342
  }
13514
- if (!import_fs18.existsSync(file)) {
14343
+ if (!import_fs19.existsSync(file)) {
13515
14344
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
13516
14345
  process.exitCode = 1;
13517
14346
  return;
@@ -13706,7 +14535,7 @@ $1`);
13706
14535
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
13707
14536
  });
13708
14537
  cmd.command("animation-curves <file>").description("Add, remove, or modify animation curves in an .anim file").option("--add-curve <json>", 'Add a curve (JSON: {"type":"float","path":"Body","attribute":"m_Alpha","classID":23,"keyframes":[{"time":0,"value":1}]})').option("--remove-curve <spec>", "Remove a curve by path:attribute (e.g., Body/Mesh:m_Alpha)").option("--set-keyframes <json>", 'Replace keyframes (JSON: {"curve":"path:attribute","keyframes":[{"time":0,"value":10}]})').option("-j, --json", "Output as JSON").action((file, options) => {
13709
- if (!import_fs18.existsSync(file)) {
14538
+ if (!import_fs19.existsSync(file)) {
13710
14539
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
13711
14540
  process.exitCode = 1;
13712
14541
  return;
@@ -13716,7 +14545,7 @@ $1`);
13716
14545
  process.exitCode = 1;
13717
14546
  return;
13718
14547
  }
13719
- let content = import_fs18.readFileSync(file, "utf-8");
14548
+ let content = import_fs19.readFileSync(file, "utf-8");
13720
14549
  const had_crlf = content.includes(`\r
13721
14550
  `);
13722
14551
  if (had_crlf)
@@ -14014,16 +14843,16 @@ ${curve_yaml}`);
14014
14843
  if (had_crlf)
14015
14844
  content = content.replace(/\n/g, `\r
14016
14845
  `);
14017
- import_fs18.writeFileSync(file, content, "utf-8");
14846
+ import_fs19.writeFileSync(file, content, "utf-8");
14018
14847
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
14019
14848
  });
14020
14849
  cmd.command("animator-state <file>").description("Add/remove states and transitions in an AnimatorController").option("--add-state <name>", "Add a new AnimatorState").option("--motion <guid-or-path>", "Motion clip GUID or file path (companion to --add-state)").option("--layer <name>", "Target layer name (default: first layer)").option("--speed <n>", "State speed (companion to --add-state, default: 1)").option("--remove-state <name>", "Remove a state and all its transitions").option("--add-transition <src:dst>", 'Add a transition (use "any" for AnyState source)').option("--condition <param,mode,threshold>", "Transition condition (repeatable)", (v, p) => [...p, v], []).option("--has-exit-time", "Enable exit time on transition").option("--exit-time <n>", "Exit time value (default: 0.75)").option("--duration <n>", "Transition duration (default: 0.25)").option("--remove-transition <src:dst>", "Remove a transition by source:destination state names").option("--set-default-state <name>", "Set the default state in the state machine").option("-j, --json", "Output as JSON").action((file, options) => {
14021
- if (!import_fs18.existsSync(file)) {
14850
+ if (!import_fs19.existsSync(file)) {
14022
14851
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
14023
14852
  process.exitCode = 1;
14024
14853
  return;
14025
14854
  }
14026
- let content = import_fs18.readFileSync(file, "utf-8");
14855
+ let content = import_fs19.readFileSync(file, "utf-8");
14027
14856
  const had_crlf = content.includes(`\r
14028
14857
  `);
14029
14858
  if (had_crlf)
@@ -14362,7 +15191,7 @@ $1`);
14362
15191
  if (had_crlf)
14363
15192
  content = content.replace(/\n/g, `\r
14364
15193
  `);
14365
- import_fs18.writeFileSync(file, content, "utf-8");
15194
+ import_fs19.writeFileSync(file, content, "utf-8");
14366
15195
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
14367
15196
  });
14368
15197
  cmd.addCommand(prefab_cmd);
@@ -14405,14 +15234,27 @@ function build_delete_command() {
14405
15234
  if (!result.success)
14406
15235
  process.exitCode = 1;
14407
15236
  });
14408
- cmd.command("component <file> <file_id>").description("Remove a component from a Unity file by file ID").option("-j, --json", "Output as JSON").action((file, file_id, _options) => {
14409
- const result = removeComponent({
14410
- file_path: file,
14411
- file_id
14412
- });
14413
- console.log(JSON.stringify(result, null, 2));
14414
- if (!result.success)
14415
- process.exitCode = 1;
15237
+ cmd.command("component <file_or_project> <component>").description('Remove a component by file ID or type name (e.g., "Rigidbody", "StandaloneInputModule")').option("--on <game_object>", "Scope to a specific GameObject (name or fileID)").option("-p, --project <path>", "Unity project path (for script GUID lookup)").option("--all", "Remove from all scenes and prefabs in the project (first arg becomes project path)").option("-j, --json", "Output as JSON").action((file_or_project, component, options) => {
15238
+ if (options.all) {
15239
+ const result = removeComponentBatch({
15240
+ project_path: file_or_project,
15241
+ component_type: component,
15242
+ game_object: options.on
15243
+ });
15244
+ console.log(JSON.stringify(result, null, 2));
15245
+ if (!result.success)
15246
+ process.exitCode = 1;
15247
+ } else {
15248
+ const result = removeComponent({
15249
+ file_path: file_or_project,
15250
+ file_id: component,
15251
+ game_object: options.on,
15252
+ project_path: options.project
15253
+ });
15254
+ console.log(JSON.stringify(result, null, 2));
15255
+ if (!result.success)
15256
+ process.exitCode = 1;
15257
+ }
14416
15258
  });
14417
15259
  cmd.command("build <project_path> <scene_path>").description("Remove a scene from build settings").option("-j, --json", "Output as JSON").action((project_path, scene_path, _options) => {
14418
15260
  try {
@@ -14455,16 +15297,16 @@ function build_delete_command() {
14455
15297
  var import_path11 = require("path");
14456
15298
 
14457
15299
  // src/editor-client.ts
14458
- var import_fs19 = require("fs");
15300
+ var import_fs20 = require("fs");
14459
15301
  var import_path10 = require("path");
14460
15302
  function read_editor_config(project_path) {
14461
15303
  const config_path = import_path10.join(project_path, ".unity-agentic", "editor.json");
14462
- if (!import_fs19.existsSync(config_path)) {
15304
+ if (!import_fs20.existsSync(config_path)) {
14463
15305
  return { error: `Editor bridge not found at ${config_path}. Is the Unity Editor running with the bridge package installed?` };
14464
15306
  }
14465
15307
  let config;
14466
15308
  try {
14467
- const raw = import_fs19.readFileSync(config_path, "utf-8");
15309
+ const raw = import_fs20.readFileSync(config_path, "utf-8");
14468
15310
  config = JSON.parse(raw);
14469
15311
  } catch (err) {
14470
15312
  return { error: `Failed to parse editor.json: ${err instanceof Error ? err.message : String(err)}` };
@@ -14677,6 +15519,30 @@ async function stream_editor(options) {
14677
15519
  connect(`ws://127.0.0.1:${config.port}/unity-agentic`);
14678
15520
  });
14679
15521
  }
15522
+ async function ping_editor(port, timeout_ms = 2000) {
15523
+ return new Promise((resolve7) => {
15524
+ const timer = setTimeout(() => {
15525
+ resolve7({ reachable: false, error: `Timeout after ${timeout_ms}ms` });
15526
+ }, timeout_ms);
15527
+ try {
15528
+ const ws = new WebSocket(`ws://127.0.0.1:${port}/unity-agentic`);
15529
+ ws.onopen = () => {
15530
+ clearTimeout(timer);
15531
+ try {
15532
+ ws.close();
15533
+ } catch {}
15534
+ resolve7({ reachable: true });
15535
+ };
15536
+ ws.onerror = (err) => {
15537
+ clearTimeout(timer);
15538
+ resolve7({ reachable: false, error: String(err) });
15539
+ };
15540
+ } catch (err) {
15541
+ clearTimeout(timer);
15542
+ resolve7({ reachable: false, error: String(err) });
15543
+ }
15544
+ });
15545
+ }
14680
15546
  function resolve_config(options) {
14681
15547
  if (options.port) {
14682
15548
  return { port: options.port, pid: 0, version: "unknown" };
@@ -14749,11 +15615,14 @@ function build_editor_command() {
14749
15615
  process.exitCode = 1;
14750
15616
  return;
14751
15617
  }
15618
+ const ping = await ping_editor(config.port, 2000);
14752
15619
  console.log(JSON.stringify({
14753
15620
  port: config.port,
14754
15621
  pid: config.pid,
14755
15622
  version: config.version,
14756
- project_path
15623
+ project_path,
15624
+ bridge_reachable: ping.reachable,
15625
+ ...ping.reachable ? {} : { bridge_error: ping.error }
14757
15626
  }, null, 2));
14758
15627
  });
14759
15628
  cmd.command("play").description("Enter play mode").action(async function() {
@@ -15098,512 +15967,6 @@ function build_editor_command() {
15098
15967
  return cmd;
15099
15968
  }
15100
15969
 
15101
- // src/project-search.ts
15102
- init_scanner();
15103
- var import_fs20 = require("fs");
15104
- var path8 = __toESM(require("path"));
15105
- var BINARY_EXTENSIONS = new Set([
15106
- ".png",
15107
- ".jpg",
15108
- ".jpeg",
15109
- ".gif",
15110
- ".bmp",
15111
- ".tga",
15112
- ".psd",
15113
- ".tif",
15114
- ".tiff",
15115
- ".fbx",
15116
- ".obj",
15117
- ".dae",
15118
- ".blend",
15119
- ".3ds",
15120
- ".max",
15121
- ".dll",
15122
- ".so",
15123
- ".dylib",
15124
- ".exe",
15125
- ".a",
15126
- ".lib",
15127
- ".mp3",
15128
- ".wav",
15129
- ".ogg",
15130
- ".aif",
15131
- ".aiff",
15132
- ".mp4",
15133
- ".mov",
15134
- ".avi",
15135
- ".wmv",
15136
- ".zip",
15137
- ".gz",
15138
- ".tar",
15139
- ".rar",
15140
- ".7z",
15141
- ".ttf",
15142
- ".otf",
15143
- ".woff",
15144
- ".woff2",
15145
- ".bank",
15146
- ".bytes",
15147
- ".db"
15148
- ]);
15149
- var SKIP_DIRS = new Set(["Library", "Temp", "obj", "Logs", ".git", ".unity-agentic", "node_modules"]);
15150
- function walk_project_files(project_path, extensions, exclude_dirs) {
15151
- const nativeWalk = getNativeWalkProjectFiles();
15152
- if (nativeWalk) {
15153
- try {
15154
- return nativeWalk(project_path, extensions, exclude_dirs ?? null);
15155
- } catch {}
15156
- }
15157
- return walk_project_files_js(project_path, extensions, exclude_dirs);
15158
- }
15159
- function walk_project_files_js(project_path, extensions, exclude_dirs) {
15160
- const result = [];
15161
- const skipSet = new Set([...SKIP_DIRS, ...exclude_dirs || []]);
15162
- const extSet = new Set(extensions.map((e) => e.startsWith(".") ? e : `.${e}`));
15163
- function walk(dir) {
15164
- let entries;
15165
- try {
15166
- entries = import_fs20.readdirSync(dir);
15167
- } catch {
15168
- return;
15169
- }
15170
- for (const entry of entries) {
15171
- const full = path8.join(dir, entry);
15172
- let stat;
15173
- try {
15174
- stat = import_fs20.statSync(full);
15175
- } catch {
15176
- continue;
15177
- }
15178
- if (stat.isDirectory()) {
15179
- if (!skipSet.has(entry)) {
15180
- walk(full);
15181
- }
15182
- } else if (stat.isFile()) {
15183
- const ext = path8.extname(entry).toLowerCase();
15184
- if (extSet.has(ext)) {
15185
- result.push(full);
15186
- }
15187
- }
15188
- }
15189
- }
15190
- const assetsDir = path8.join(project_path, "Assets");
15191
- if (import_fs20.existsSync(assetsDir)) {
15192
- walk(assetsDir);
15193
- } else {
15194
- walk(project_path);
15195
- }
15196
- if (extSet.has(".asset")) {
15197
- const settingsDir = path8.join(project_path, "ProjectSettings");
15198
- if (import_fs20.existsSync(settingsDir)) {
15199
- walk(settingsDir);
15200
- }
15201
- }
15202
- return result;
15203
- }
15204
- function search_project(options) {
15205
- const {
15206
- project_path,
15207
- name,
15208
- exact = false,
15209
- component,
15210
- tag,
15211
- layer,
15212
- file_type = "all",
15213
- max_matches
15214
- } = options;
15215
- if (max_matches !== undefined && max_matches < 1) {
15216
- return {
15217
- success: false,
15218
- project_path,
15219
- total_files_scanned: 0,
15220
- total_matches: 0,
15221
- cursor: 0,
15222
- truncated: false,
15223
- matches: [],
15224
- error: "--max-matches must be a positive integer (>= 1)"
15225
- };
15226
- }
15227
- if (name !== undefined && name.trim() === "") {
15228
- return {
15229
- success: false,
15230
- project_path,
15231
- total_files_scanned: 0,
15232
- total_matches: 0,
15233
- cursor: 0,
15234
- truncated: false,
15235
- matches: [],
15236
- error: "Name pattern must not be empty"
15237
- };
15238
- }
15239
- if (!import_fs20.existsSync(project_path)) {
15240
- return {
15241
- success: false,
15242
- project_path,
15243
- total_files_scanned: 0,
15244
- total_matches: 0,
15245
- cursor: 0,
15246
- truncated: false,
15247
- matches: [],
15248
- error: `Project path not found: ${project_path}`
15249
- };
15250
- }
15251
- const ASSET_TYPE_EXTENSIONS = {
15252
- mat: [".mat"],
15253
- anim: [".anim"],
15254
- controller: [".controller"],
15255
- asset: [".asset"]
15256
- };
15257
- const isAssetType = file_type in ASSET_TYPE_EXTENSIONS;
15258
- if (isAssetType) {
15259
- const extensions2 = ASSET_TYPE_EXTENSIONS[file_type];
15260
- const files2 = walk_project_files(project_path, extensions2);
15261
- const matches2 = [];
15262
- for (const file of files2) {
15263
- const relPath = path8.relative(project_path, file);
15264
- const fileName = path8.basename(file, path8.extname(file));
15265
- if (name) {
15266
- const nameLower = name.toLowerCase();
15267
- const hasWildcard = name.includes("*") || name.includes("?");
15268
- if (hasWildcard) {
15269
- if (!glob_match(name, fileName))
15270
- continue;
15271
- } else if (exact) {
15272
- if (fileName !== name)
15273
- continue;
15274
- } else {
15275
- if (!fileName.toLowerCase().includes(nameLower))
15276
- continue;
15277
- }
15278
- }
15279
- let display_name = fileName;
15280
- try {
15281
- const content = import_fs20.readFileSync(file, "utf-8");
15282
- const nameMatch = /^\s*m_Name:\s*(.+)$/m.exec(content.slice(0, 2000));
15283
- if (nameMatch)
15284
- display_name = nameMatch[1].trim();
15285
- } catch {}
15286
- matches2.push({
15287
- file: relPath,
15288
- game_object: display_name,
15289
- file_id: "0"
15290
- });
15291
- if (max_matches !== undefined && matches2.length >= max_matches)
15292
- break;
15293
- }
15294
- return {
15295
- success: true,
15296
- project_path,
15297
- total_files_scanned: files2.length,
15298
- total_matches: matches2.length,
15299
- cursor: 0,
15300
- truncated: max_matches !== undefined && matches2.length >= max_matches,
15301
- matches: matches2
15302
- };
15303
- }
15304
- if (!isNativeModuleAvailable()) {
15305
- return {
15306
- success: false,
15307
- project_path,
15308
- total_files_scanned: 0,
15309
- total_matches: 0,
15310
- cursor: 0,
15311
- truncated: false,
15312
- matches: [],
15313
- error: "Native scanner module not available. Run bun install in the project root."
15314
- };
15315
- }
15316
- const extensions = [];
15317
- if (file_type === "scene" || file_type === "all")
15318
- extensions.push(".unity");
15319
- if (file_type === "prefab" || file_type === "all")
15320
- extensions.push(".prefab");
15321
- const files = walk_project_files(project_path, extensions);
15322
- const scanner = new UnityScanner;
15323
- const matches = [];
15324
- for (const file of files) {
15325
- try {
15326
- let gameObjects;
15327
- const needComponents = !!component;
15328
- const needMetadata = !!(tag || layer !== undefined);
15329
- if (name && !needMetadata && !needComponents) {
15330
- gameObjects = scanner.find_by_name(file, name, !exact);
15331
- } else if (needComponents) {
15332
- gameObjects = scanner.scan_scene_with_components(file);
15333
- if (name && !is_match_all(name)) {
15334
- const nameLower = name.toLowerCase();
15335
- const hasWildcard = name.includes("*") || name.includes("?");
15336
- gameObjects = gameObjects.filter((go) => {
15337
- if (!go.name)
15338
- return false;
15339
- if (hasWildcard) {
15340
- return glob_match(name, go.name);
15341
- }
15342
- if (exact) {
15343
- return go.name === name;
15344
- }
15345
- return go.name.toLowerCase().includes(nameLower);
15346
- });
15347
- }
15348
- } else if (needMetadata) {
15349
- gameObjects = scanner.scan_scene_metadata(file);
15350
- if (name && !is_match_all(name)) {
15351
- const nameLower = name.toLowerCase();
15352
- const hasWildcard = name.includes("*") || name.includes("?");
15353
- gameObjects = gameObjects.filter((go) => {
15354
- if (!go.name)
15355
- return false;
15356
- if (hasWildcard) {
15357
- return glob_match(name, go.name);
15358
- }
15359
- if (exact) {
15360
- return go.name === name;
15361
- }
15362
- return go.name.toLowerCase().includes(nameLower);
15363
- });
15364
- }
15365
- } else {
15366
- gameObjects = scanner.scan_scene_minimal(file);
15367
- }
15368
- for (const go of gameObjects) {
15369
- const isFindResult = "resultType" in go;
15370
- const isPrefab = isFindResult && go.resultType === "PrefabInstance";
15371
- if (isPrefab && (component || tag || layer !== undefined)) {
15372
- continue;
15373
- }
15374
- if (component) {
15375
- if ("components" in go && go.components) {
15376
- const hasComponent = go.components.some((c) => glob_match(component, c.type));
15377
- if (!hasComponent)
15378
- continue;
15379
- } else {
15380
- continue;
15381
- }
15382
- }
15383
- if (tag) {
15384
- if (!("tag" in go) || go.tag !== tag)
15385
- continue;
15386
- }
15387
- if (layer !== undefined) {
15388
- if (!("layer" in go) || go.layer !== layer)
15389
- continue;
15390
- }
15391
- const relPath = path8.relative(project_path, file);
15392
- const fileId = isFindResult ? go.fileId : go.file_id;
15393
- const match = {
15394
- file: relPath,
15395
- game_object: go.name,
15396
- file_id: fileId,
15397
- tag: "tag" in go ? go.tag : undefined,
15398
- layer: "layer" in go ? go.layer : undefined
15399
- };
15400
- if ("components" in go && go.components) {
15401
- match.components = go.components.map((c) => c.type);
15402
- }
15403
- matches.push(match);
15404
- if (max_matches !== undefined && matches.length >= max_matches)
15405
- break;
15406
- }
15407
- if (max_matches !== undefined && matches.length >= max_matches)
15408
- break;
15409
- } catch {
15410
- continue;
15411
- }
15412
- }
15413
- return {
15414
- success: true,
15415
- project_path,
15416
- total_files_scanned: files.length,
15417
- total_matches: matches.length,
15418
- cursor: 0,
15419
- truncated: max_matches !== undefined && matches.length >= max_matches,
15420
- matches
15421
- };
15422
- }
15423
- function grep_project(options) {
15424
- const nativeGrep = getNativeGrepProject();
15425
- if (nativeGrep) {
15426
- try {
15427
- const nativeResult = nativeGrep({
15428
- projectPath: options.project_path,
15429
- pattern: options.pattern,
15430
- fileType: options.file_type,
15431
- maxResults: options.max_results,
15432
- contextLines: options.context_lines
15433
- });
15434
- return {
15435
- success: nativeResult.success,
15436
- project_path: nativeResult.projectPath,
15437
- pattern: nativeResult.pattern,
15438
- total_files_scanned: nativeResult.totalFilesScanned,
15439
- total_matches: nativeResult.totalMatches,
15440
- truncated: nativeResult.truncated,
15441
- error: nativeResult.error,
15442
- matches: nativeResult.matches.map((m) => ({
15443
- file: m.file,
15444
- line_number: m.lineNumber,
15445
- line: m.line,
15446
- context_before: m.contextBefore,
15447
- context_after: m.contextAfter
15448
- }))
15449
- };
15450
- } catch {}
15451
- }
15452
- return grep_project_js(options);
15453
- }
15454
- function grep_project_js(options) {
15455
- const {
15456
- project_path,
15457
- pattern,
15458
- file_type = "all",
15459
- max_results = 100,
15460
- context_lines = 0
15461
- } = options;
15462
- if (!import_fs20.existsSync(project_path)) {
15463
- return {
15464
- success: false,
15465
- project_path,
15466
- pattern,
15467
- total_files_scanned: 0,
15468
- total_matches: 0,
15469
- truncated: false,
15470
- matches: [],
15471
- error: `Project path not found: ${project_path}`
15472
- };
15473
- }
15474
- let regex;
15475
- try {
15476
- regex = new RegExp(pattern, "i");
15477
- } catch (err) {
15478
- return {
15479
- success: false,
15480
- project_path,
15481
- pattern,
15482
- total_files_scanned: 0,
15483
- total_matches: 0,
15484
- truncated: false,
15485
- matches: [],
15486
- error: `Invalid regex pattern: ${err instanceof Error ? err.message : String(err)}`
15487
- };
15488
- }
15489
- const EXTENSION_MAP = {
15490
- cs: [".cs"],
15491
- yaml: [
15492
- ".yaml",
15493
- ".yml",
15494
- ".unity",
15495
- ".prefab",
15496
- ".asset",
15497
- ".mat",
15498
- ".anim",
15499
- ".controller",
15500
- ".overrideController",
15501
- ".mask",
15502
- ".mixer",
15503
- ".lighting",
15504
- ".preset",
15505
- ".signal",
15506
- ".playable",
15507
- ".renderTexture",
15508
- ".flare",
15509
- ".guiskin",
15510
- ".terrainlayer",
15511
- ".cubemap"
15512
- ],
15513
- unity: [".unity"],
15514
- prefab: [".prefab"],
15515
- asset: [".asset"],
15516
- mat: [".mat"],
15517
- anim: [".anim"],
15518
- controller: [".controller"],
15519
- all: [
15520
- ".cs",
15521
- ".unity",
15522
- ".prefab",
15523
- ".asset",
15524
- ".mat",
15525
- ".anim",
15526
- ".controller",
15527
- ".yaml",
15528
- ".yml",
15529
- ".txt",
15530
- ".json",
15531
- ".xml",
15532
- ".shader",
15533
- ".cginc",
15534
- ".hlsl",
15535
- ".compute",
15536
- ".asmdef",
15537
- ".asmref"
15538
- ]
15539
- };
15540
- const extensions = EXTENSION_MAP[file_type] || EXTENSION_MAP.all;
15541
- const files = walk_project_files(project_path, extensions);
15542
- const matches = [];
15543
- let totalFilesScanned = 0;
15544
- let truncated = false;
15545
- for (const file of files) {
15546
- const ext = path8.extname(file).toLowerCase();
15547
- if (BINARY_EXTENSIONS.has(ext))
15548
- continue;
15549
- totalFilesScanned++;
15550
- let content;
15551
- try {
15552
- content = import_fs20.readFileSync(file, "utf-8");
15553
- } catch {
15554
- continue;
15555
- }
15556
- const lines = content.split(`
15557
- `);
15558
- const relPath = path8.relative(project_path, file);
15559
- for (let i = 0;i < lines.length; i++) {
15560
- if (regex.test(lines[i])) {
15561
- let line = lines[i];
15562
- if (line.length > 200) {
15563
- line = line.substring(0, 200) + "...";
15564
- }
15565
- const match = {
15566
- file: relPath,
15567
- line_number: i + 1,
15568
- line
15569
- };
15570
- if (context_lines > 0) {
15571
- match.context_before = [];
15572
- match.context_after = [];
15573
- for (let j = Math.max(0, i - context_lines);j < i; j++) {
15574
- let ctxLine = lines[j];
15575
- if (ctxLine.length > 200)
15576
- ctxLine = ctxLine.substring(0, 200) + "...";
15577
- match.context_before.push(ctxLine);
15578
- }
15579
- for (let j = i + 1;j <= Math.min(lines.length - 1, i + context_lines); j++) {
15580
- let ctxLine = lines[j];
15581
- if (ctxLine.length > 200)
15582
- ctxLine = ctxLine.substring(0, 200) + "...";
15583
- match.context_after.push(ctxLine);
15584
- }
15585
- }
15586
- matches.push(match);
15587
- if (matches.length >= max_results) {
15588
- truncated = true;
15589
- break;
15590
- }
15591
- }
15592
- }
15593
- if (truncated)
15594
- break;
15595
- }
15596
- return {
15597
- success: true,
15598
- project_path,
15599
- pattern,
15600
- total_files_scanned: totalFilesScanned,
15601
- total_matches: matches.length,
15602
- truncated,
15603
- matches
15604
- };
15605
- }
15606
-
15607
15970
  // src/cli.ts
15608
15971
  var path9 = __toESM(require("path"));
15609
15972
  var { exec } = require("child_process");