unity-agentic-tools 0.4.2 → 0.4.3

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.4.2",
2302
+ version: "0.4.3",
2303
2303
  description: "Fast, token-efficient Unity YAML parser for AI agents",
2304
2304
  exports: {
2305
2305
  ".": {
@@ -2595,8 +2595,8 @@ function removeDirectoryRecursive(dir) {
2595
2595
  }
2596
2596
 
2597
2597
  // src/cmd-create.ts
2598
- var import_fs17 = require("fs");
2599
- var import_path7 = require("path");
2598
+ var import_fs18 = require("fs");
2599
+ var import_path10 = require("path");
2600
2600
  var import_crypto2 = require("crypto");
2601
2601
 
2602
2602
  // src/utils.ts
@@ -2809,6 +2809,19 @@ function build_guid_cache(project_path, raw) {
2809
2809
  }
2810
2810
  }
2811
2811
  return substringMatch;
2812
+ },
2813
+ find_all_by_name_exact(name, extension) {
2814
+ const nameLower = name.toLowerCase().replace(/\.[^.]+$/, "");
2815
+ const matches = [];
2816
+ for (const [guid, assetPath] of Object.entries(raw)) {
2817
+ if (extension && !assetPath.endsWith(extension))
2818
+ continue;
2819
+ const fileName = import_path5.basename(assetPath, import_path5.extname(assetPath)).toLowerCase();
2820
+ if (fileName === nameLower) {
2821
+ matches.push({ guid, path: assetPath });
2822
+ }
2823
+ }
2824
+ return matches;
2812
2825
  }
2813
2826
  };
2814
2827
  }
@@ -2907,15 +2920,14 @@ var UNITY_CLASS_IDS = {
2907
2920
  114: "MonoBehaviour",
2908
2921
  115: "MonoScript",
2909
2922
  117: "Texture3D",
2910
- 119: "NewAnimationTrack",
2911
- 120: "Projector",
2912
- 121: "LineRenderer",
2913
- 122: "Flare",
2914
- 123: "Halo",
2915
- 124: "LensFlare",
2916
- 125: "FlareLayer",
2917
- 126: "HaloLayer",
2918
- 127: "NavMeshProjectSettings",
2923
+ 118: "NewAnimationTrack",
2924
+ 119: "Projector",
2925
+ 120: "LineRenderer",
2926
+ 121: "Flare",
2927
+ 122: "Halo",
2928
+ 123: "LensFlare",
2929
+ 124: "FlareLayer",
2930
+ 126: "NavMeshProjectSettings",
2919
2931
  128: "Font",
2920
2932
  129: "PlayerSettings",
2921
2933
  130: "NamedObject",
@@ -2942,6 +2954,7 @@ var UNITY_CLASS_IDS = {
2942
2954
  164: "AudioReverbFilter",
2943
2955
  165: "AudioHighPassFilter",
2944
2956
  166: "AudioChorusFilter",
2957
+ 1660057539: "SceneRoots",
2945
2958
  167: "AudioReverbZone",
2946
2959
  168: "AudioEchoFilter",
2947
2960
  169: "AudioLowPassFilter",
@@ -3012,29 +3025,41 @@ var UNITY_CLASS_IDS = {
3012
3025
  257: "TargetJoint2D",
3013
3026
  258: "LightProbes",
3014
3027
  259: "LightProbeProxyVolume",
3015
- 260: "SampleClip",
3016
- 261: "AudioMixerSnapshot",
3017
- 262: "AudioMixerGroup",
3028
+ 260: "SampleClipLegacy",
3029
+ 261: "AudioMixerSnapshotLegacy",
3030
+ 262: "AudioMixerGroupLegacy",
3018
3031
  265: "NScreenBridge",
3019
- 271: "AssetBundleManifest",
3032
+ 271: "SampleClip",
3020
3033
  272: "UnityAdsManager",
3021
- 273: "RuntimeInitializeOnLoadManager",
3022
- 280: "UnityConnectSettings",
3023
- 281: "AvatarMask",
3024
- 290: "PlayableDirector",
3025
- 292: "VideoPlayer",
3026
- 293: "VideoClip",
3027
- 294: "ParticleSystemForceField",
3028
- 298: "SpriteMask",
3034
+ 273: "AudioMixerGroup",
3035
+ 280: "UnityConnectSettingsLegacy",
3036
+ 281: "AvatarMaskLegacy",
3037
+ 290: "AssetBundleManifest",
3038
+ 292: "VideoPlayerLegacy",
3039
+ 293: "VideoClipLegacy",
3040
+ 294: "ParticleSystemForceFieldLegacy",
3041
+ 298: "SpriteMaskLegacy",
3029
3042
  300: "WorldAnchor",
3030
- 301: "OcclusionCullingData",
3031
- 310: "PrefabInstance",
3032
- 319: "TextureImporter",
3033
- 363: "Preset",
3043
+ 301: "OcclusionCullingDataLegacy",
3044
+ 310: "UnityConnectSettings",
3045
+ 1001: "PrefabInstance",
3046
+ 319: "AvatarMask",
3047
+ 320: "PlayableDirector",
3048
+ 328: "VideoPlayer",
3049
+ 329: "VideoClip",
3050
+ 330: "ParticleSystemForceField",
3051
+ 331: "SpriteMask",
3052
+ 363: "OcclusionCullingData",
3053
+ 1006: "TextureImporter",
3054
+ 181963792: "Preset",
3034
3055
  687078895: "SpriteAtlas",
3056
+ 156049354: "Grid",
3057
+ 1742807556: "GridLayout",
3035
3058
  1839735485: "Tilemap",
3036
- 1839735486: "TilemapCollider2D",
3037
- 1839735487: "TilemapRenderer"
3059
+ 19719996: "TilemapCollider2D",
3060
+ 483693784: "TilemapRenderer",
3061
+ 1839735486: "TilemapCollider2DLegacy",
3062
+ 1839735487: "TilemapRendererLegacy"
3038
3063
  };
3039
3064
  var UNITY_CLASS_NAMES = Object.fromEntries(Object.entries(UNITY_CLASS_IDS).map(([id, name]) => [name, parseInt(id, 10)]));
3040
3065
  function get_class_id_name(class_id) {
@@ -3100,8 +3125,12 @@ function resolve_source_prefab(doc, file_path, project_path) {
3100
3125
  prefab_instance_block: pi_block
3101
3126
  };
3102
3127
  }
3103
- function resolveScriptGuid(script, projectPath) {
3128
+ function resolveScriptGuid(script, projectPath, options) {
3129
+ const strictExactName = options?.strict_exact_name === true;
3104
3130
  if (/^[a-f0-9]{32}$/i.test(script)) {
3131
+ if (/^0{32}$/i.test(script)) {
3132
+ throw new Error(`Invalid script GUID "${script}": all-zero GUID is not allowed. Provide a real script GUID from a .meta file.`);
3133
+ }
3105
3134
  return { guid: script.toLowerCase(), path: null };
3106
3135
  }
3107
3136
  if (script.endsWith(".cs")) {
@@ -3126,9 +3155,20 @@ function resolveScriptGuid(script, projectPath) {
3126
3155
  if (projectPath) {
3127
3156
  const guidCache = load_guid_cache(projectPath);
3128
3157
  if (guidCache) {
3129
- const result = guidCache.find_by_name(script, ".cs");
3130
- if (result)
3131
- return result;
3158
+ if (strictExactName) {
3159
+ const exactMatches = guidCache.find_all_by_name_exact(script, ".cs");
3160
+ if (exactMatches.length === 1) {
3161
+ return exactMatches[0];
3162
+ }
3163
+ if (exactMatches.length > 1) {
3164
+ const paths = exactMatches.map((m) => m.path).join(", ");
3165
+ throw new Error(`Ambiguous type "${script}": found ${exactMatches.length} exact script name matches (${paths}). ` + 'Use a qualified name (e.g., "Namespace.TypeName") or provide the full script path.');
3166
+ }
3167
+ } else {
3168
+ const result = guidCache.find_by_name(script, ".cs");
3169
+ if (result)
3170
+ return result;
3171
+ }
3132
3172
  }
3133
3173
  const registryPath = path.join(projectPath, ".unity-agentic", "type-registry.json");
3134
3174
  if (import_fs6.existsSync(registryPath)) {
@@ -3279,15 +3319,30 @@ function resolveScriptGuid(script, projectPath) {
3279
3319
  continue;
3280
3320
  try {
3281
3321
  const cache = JSON.parse(import_fs6.readFileSync(cachePath, "utf-8"));
3322
+ const exactMatches = [];
3282
3323
  for (const [guid, assetPath] of Object.entries(cache)) {
3283
3324
  if (!assetPath.endsWith(".cs"))
3284
3325
  continue;
3285
3326
  const fileName = path.basename(assetPath, ".cs").toLowerCase();
3286
3327
  if (fileName === scriptNameLower || classNameOnly && fileName === classNameOnly) {
3287
- return { guid, path: assetPath };
3328
+ exactMatches.push({ guid, path: assetPath });
3288
3329
  }
3289
3330
  }
3290
- } catch {}
3331
+ if (exactMatches.length === 1) {
3332
+ return exactMatches[0];
3333
+ }
3334
+ if (exactMatches.length > 1) {
3335
+ const paths = exactMatches.map((m) => m.path).join(", ");
3336
+ throw new Error(`Ambiguous type "${script}": found ${exactMatches.length} exact script name matches (${paths}). ` + 'Use a qualified name (e.g., "Namespace.TypeName") or provide the full script path.');
3337
+ }
3338
+ } catch (err) {
3339
+ if (err instanceof Error && err.message.startsWith("Ambiguous type")) {
3340
+ throw err;
3341
+ }
3342
+ }
3343
+ }
3344
+ if (strictExactName) {
3345
+ return null;
3291
3346
  }
3292
3347
  try {
3293
3348
  const assetsDir = path.join(projectPath, "Assets");
@@ -3358,19 +3413,38 @@ function resolveAssetPathToPPtr(value, file_path, project_path) {
3358
3413
  return null;
3359
3414
  return `{fileID: ${mapping.fileID}, guid: ${guid}, type: ${mapping.type}}`;
3360
3415
  }
3361
- function resolve_script_with_fields(script, project_path) {
3362
- const resolved = resolveScriptGuid(script, project_path);
3416
+ function detect_abstract_class_in_source(file_path, class_name) {
3417
+ try {
3418
+ const source = import_fs6.readFileSync(file_path, "utf-8");
3419
+ const escaped_name = class_name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3420
+ const pattern = new RegExp(`(^|[^\\w])abstract\\s+class\\s+${escaped_name}(?=$|[^\\w])`, "m");
3421
+ return pattern.test(source);
3422
+ } catch {
3423
+ return false;
3424
+ }
3425
+ }
3426
+ function resolve_script_with_fields(script, project_path, options) {
3427
+ const resolved = resolveScriptGuid(script, project_path, options);
3363
3428
  if (!resolved)
3364
3429
  return null;
3365
3430
  const result = {
3366
3431
  guid: resolved.guid,
3367
3432
  path: resolved.path
3368
3433
  };
3434
+ if (resolved.path && resolved.path.endsWith(".cs")) {
3435
+ const full_path = path.isAbsolute(resolved.path) ? resolved.path : project_path ? path.join(project_path, resolved.path) : resolved.path;
3436
+ if (import_fs6.existsSync(full_path)) {
3437
+ const class_name = script.includes(".") ? script.split(".").pop() : script;
3438
+ if (class_name.length > 0) {
3439
+ result.is_abstract = detect_abstract_class_in_source(full_path, class_name);
3440
+ }
3441
+ }
3442
+ }
3369
3443
  if (!project_path)
3370
3444
  return result;
3371
3445
  if (resolved.path) {
3372
3446
  try {
3373
- const full_path = resolved.path.startsWith("/") ? resolved.path : path.join(project_path, resolved.path);
3447
+ const full_path = path.isAbsolute(resolved.path) ? resolved.path : path.join(project_path, resolved.path);
3374
3448
  if (!import_fs6.existsSync(full_path)) {
3375
3449
  result.extraction_error = `Script file not found at resolved path: ${full_path}`;
3376
3450
  } else if (resolved.path.endsWith(".cs")) {
@@ -3384,6 +3458,10 @@ function resolve_script_with_fields(script, project_path) {
3384
3458
  result.fields = chosen.fields;
3385
3459
  result.base_class = chosen.baseClass ?? undefined;
3386
3460
  result.kind = chosen.kind;
3461
+ result.class_name = chosen.name;
3462
+ if (chosen.name) {
3463
+ result.is_abstract = detect_abstract_class_in_source(full_path, chosen.name);
3464
+ }
3387
3465
  }
3388
3466
  } else {
3389
3467
  result.extraction_error = "Native extractSerializedFields function not available";
@@ -4483,7 +4561,21 @@ class UnityDocument {
4483
4561
  }
4484
4562
  }
4485
4563
  }
4486
- return transform_ids;
4564
+ const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
4565
+ const name_pattern = new RegExp(`propertyPath: m_Name\\s*\\n\\s*value:\\s*${escaped}\\s*$`, "m");
4566
+ for (const block of this._blocks) {
4567
+ if ((block.class_id === 1001 || block.class_id === 310) && name_pattern.test(block.raw)) {
4568
+ const pi_id = block.file_id;
4569
+ const pi_ref_pattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${pi_id}\\}`);
4570
+ const stripped_transform = this._blocks.find((b) => (b.class_id === 4 || b.class_id === 224) && b.is_stripped && pi_ref_pattern.test(b.raw));
4571
+ if (stripped_transform) {
4572
+ transform_ids.push(stripped_transform.file_id);
4573
+ } else {
4574
+ transform_ids.push(pi_id);
4575
+ }
4576
+ }
4577
+ }
4578
+ return Array.from(new Set(transform_ids));
4487
4579
  }
4488
4580
  require_unique_game_object(name_or_id) {
4489
4581
  if (/^-?\d+$/.test(name_or_id)) {
@@ -4512,10 +4604,7 @@ class UnityDocument {
4512
4604
  if (!block) {
4513
4605
  return { error: `Block with fileID ${name_or_id} not found` };
4514
4606
  }
4515
- if (block.class_id === 4) {
4516
- return block;
4517
- }
4518
- if (block.class_id === 224) {
4607
+ if (block.class_id === 4 || block.class_id === 224 || block.class_id === 1001 || block.class_id === 310) {
4519
4608
  return block;
4520
4609
  }
4521
4610
  if (block.class_id === 1) {
@@ -4528,15 +4617,40 @@ class UnityDocument {
4528
4617
  }
4529
4618
  return { error: `Transform for GameObject fileID ${name_or_id} not found` };
4530
4619
  }
4531
- return { error: `fileID ${name_or_id} is not a GameObject or Transform (class ${block.class_id})` };
4620
+ return { error: `fileID ${name_or_id} is not a GameObject, Transform, or PrefabInstance (class ${block.class_id})` };
4532
4621
  }
4533
4622
  const transform_ids = this.find_transforms_by_name(name_or_id);
4534
4623
  if (transform_ids.length === 0) {
4535
- return { error: `GameObject "${name_or_id}" not found` };
4624
+ return { error: `Object "${name_or_id}" not found` };
4536
4625
  }
4537
4626
  if (transform_ids.length > 1) {
4538
- const go_ids = this.find_game_objects_by_name(name_or_id).map((b) => b.file_id).join(", ");
4539
- return { error: `Multiple GameObjects named "${name_or_id}" found (fileIDs: ${go_ids}). Use numeric fileID to specify which one.` };
4627
+ const entity_ids = [];
4628
+ for (const tid of transform_ids) {
4629
+ const block = this.find_by_file_id(tid);
4630
+ if (block) {
4631
+ if (block.class_id === 4 || block.class_id === 224) {
4632
+ const go_match = block.raw.match(/m_GameObject:\s*\{fileID:\s*(-?\d+)\}/);
4633
+ if (go_match) {
4634
+ entity_ids.push(go_match[1]);
4635
+ } else {
4636
+ entity_ids.push(tid);
4637
+ }
4638
+ } else if (block.class_id === 1001 || block.class_id === 310) {
4639
+ entity_ids.push(tid);
4640
+ } else if (block.is_stripped) {
4641
+ const pi_match = block.raw.match(/m_PrefabInstance:\s*\{fileID:\s*(-?\d+)\}/);
4642
+ if (pi_match) {
4643
+ entity_ids.push(pi_match[1]);
4644
+ } else {
4645
+ entity_ids.push(tid);
4646
+ }
4647
+ } else {
4648
+ entity_ids.push(tid);
4649
+ }
4650
+ }
4651
+ }
4652
+ const ids_str = Array.from(new Set(entity_ids)).join(", ");
4653
+ return { error: `Multiple GameObjects/PrefabInstances named "${name_or_id}" found (fileIDs: ${ids_str}). Use numeric fileID to specify which one.` };
4540
4654
  }
4541
4655
  const transform = this.find_by_file_id(transform_ids[0]);
4542
4656
  if (!transform) {
@@ -4564,7 +4678,7 @@ class UnityDocument {
4564
4678
  if (t_block.class_id === 4 && t_block.is_stripped) {
4565
4679
  let name = "Variant";
4566
4680
  for (const pi_block of this._blocks) {
4567
- if (pi_block.class_id === 1001) {
4681
+ if (pi_block.class_id === 1001 || pi_block.class_id === 310) {
4568
4682
  const name_mod = pi_block.raw.match(/propertyPath: m_Name\s*\n\s*value:\s*(.+)/);
4569
4683
  if (name_mod) {
4570
4684
  name = name_mod[1].trim();
@@ -4717,7 +4831,7 @@ class UnityDocument {
4717
4831
  }
4718
4832
  add_child_to_parent(parent_id, child_id) {
4719
4833
  const parent = this.find_by_file_id(parent_id);
4720
- if (!parent || parent.class_id !== 4)
4834
+ if (!parent || parent.class_id !== 4 && parent.class_id !== 224)
4721
4835
  return false;
4722
4836
  let raw = parent.raw;
4723
4837
  const inline_empty = /m_Children:\s*\[\]/;
@@ -4753,7 +4867,7 @@ class UnityDocument {
4753
4867
  }
4754
4868
  remove_child_from_parent(parent_id, child_id) {
4755
4869
  const parent = this.find_by_file_id(parent_id);
4756
- if (!parent || parent.class_id !== 4)
4870
+ if (!parent || parent.class_id !== 4 && parent.class_id !== 224)
4757
4871
  return false;
4758
4872
  let raw = parent.raw;
4759
4873
  const child_line = new RegExp(`\\n[ \\t]*- \\{fileID: ${child_id}\\}`);
@@ -4774,7 +4888,49 @@ class UnityDocument {
4774
4888
  }
4775
4889
  _collect_hierarchy_recursive(transform_id, result) {
4776
4890
  const transform = this.find_by_file_id(transform_id);
4777
- if (!transform || transform.class_id !== 4)
4891
+ if (!transform)
4892
+ return;
4893
+ if (transform.class_id === 1001 || transform.class_id === 310) {
4894
+ const pi_id = transform.file_id;
4895
+ const pi_ref_pattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${pi_id}\\}`);
4896
+ for (const block of this._blocks) {
4897
+ if (block.is_stripped && pi_ref_pattern.test(block.raw)) {
4898
+ result.add(block.file_id);
4899
+ if (block.class_id === 4 || block.class_id === 224) {
4900
+ this._collect_hierarchy_recursive(block.file_id, result);
4901
+ }
4902
+ }
4903
+ }
4904
+ const added_go_matches = transform.raw.matchAll(/addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)\}/g);
4905
+ for (const match of added_go_matches) {
4906
+ const added_id = match[1];
4907
+ if (added_id !== "0" && !result.has(added_id)) {
4908
+ result.add(added_id);
4909
+ const added_block = this.find_by_file_id(added_id);
4910
+ if (added_block && added_block.class_id === 1) {
4911
+ const comp_matches = added_block.raw.matchAll(/component:[ \t]*\{fileID:[ \t]*(-?\d+)\}/g);
4912
+ for (const cm of comp_matches) {
4913
+ const comp_block = this.find_by_file_id(cm[1]);
4914
+ if (comp_block && (comp_block.class_id === 4 || comp_block.class_id === 224)) {
4915
+ if (!result.has(cm[1])) {
4916
+ result.add(cm[1]);
4917
+ this._collect_hierarchy_recursive(cm[1], result);
4918
+ }
4919
+ break;
4920
+ }
4921
+ }
4922
+ }
4923
+ }
4924
+ }
4925
+ const added_comp_matches = transform.raw.matchAll(/addedObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)\}/g);
4926
+ for (const match of added_comp_matches) {
4927
+ const added_id = match[1];
4928
+ if (added_id !== "0")
4929
+ result.add(added_id);
4930
+ }
4931
+ return;
4932
+ }
4933
+ if (transform.class_id !== 4 && transform.class_id !== 224)
4778
4934
  return;
4779
4935
  const children_section = transform.raw.match(/m_Children:[\s\S]*?(?=\s*m_Father:)/);
4780
4936
  if (!children_section)
@@ -4809,14 +4965,21 @@ class UnityDocument {
4809
4965
  if (parent_id === "0") {
4810
4966
  let count = 0;
4811
4967
  for (const block of this._blocks) {
4812
- if (block.class_id === 4 && /m_Father:\s*\{fileID:\s*0\}/.test(block.raw)) {
4968
+ if ((block.class_id === 4 || block.class_id === 224) && /m_Father:\s*\{fileID:\s*0\}/.test(block.raw)) {
4813
4969
  count++;
4970
+ } else if ((block.class_id === 1001 || block.class_id === 310) && /m_TransformParent:\s*\{fileID:\s*0\}/.test(block.raw)) {
4971
+ const pi_id = block.file_id;
4972
+ const pi_ref_pattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${pi_id}\\}`);
4973
+ const has_stripped = this._blocks.some((b) => (b.class_id === 4 || b.class_id === 224) && b.is_stripped && pi_ref_pattern.test(b.raw));
4974
+ if (!has_stripped) {
4975
+ count++;
4976
+ }
4814
4977
  }
4815
4978
  }
4816
4979
  return count;
4817
4980
  }
4818
4981
  const parent = this.find_by_file_id(parent_id);
4819
- if (!parent || parent.class_id !== 4)
4982
+ if (!parent || parent.class_id !== 4 && parent.class_id !== 224)
4820
4983
  return 0;
4821
4984
  const children_match = parent.raw.match(/m_Children:[\s\S]*?(?=\s*m_Father:)/);
4822
4985
  if (children_match) {
@@ -4849,19 +5012,97 @@ ${new_children}`);
4849
5012
  }
4850
5013
  return false;
4851
5014
  }
5015
+ add_root_to_scene_roots(root_id) {
5016
+ const pi_block = this.find_by_file_id(root_id);
5017
+ if (pi_block && (pi_block.class_id === 1001 || pi_block.class_id === 310)) {
5018
+ const pi_ref_pattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${root_id}\\}`);
5019
+ const stripped_root = this._blocks.find((b) => (b.class_id === 4 || b.class_id === 224) && b.is_stripped && pi_ref_pattern.test(b.raw) && /m_Father:\s*\{fileID:\s*0\}/.test(b.raw));
5020
+ if (stripped_root) {
5021
+ root_id = stripped_root.file_id;
5022
+ }
5023
+ }
5024
+ const sr_blocks = [...this.find_by_class_id(166), ...this.find_by_class_id(1660057539)];
5025
+ if (sr_blocks.length === 0)
5026
+ return false;
5027
+ const sr_block = sr_blocks[0];
5028
+ let raw = sr_block.raw;
5029
+ const roots_pattern = /(m_Roots:\s*\n(?:\s*-\s*\{fileID:\s*-?\d+\}\s*\n)*)/;
5030
+ if (roots_pattern.test(raw)) {
5031
+ raw = raw.replace(roots_pattern, `$1 - {fileID: ${root_id}}
5032
+ `);
5033
+ sr_block.replace_raw(raw);
5034
+ return true;
5035
+ }
5036
+ const empty_roots = /m_Roots:\s*\[\]/;
5037
+ if (empty_roots.test(raw)) {
5038
+ raw = raw.replace(empty_roots, `m_Roots:
5039
+ - {fileID: ${root_id}}`);
5040
+ sr_block.replace_raw(raw);
5041
+ return true;
5042
+ }
5043
+ return false;
5044
+ }
5045
+ remove_root_from_scene_roots(root_id) {
5046
+ const pi_block = this.find_by_file_id(root_id);
5047
+ if (pi_block && (pi_block.class_id === 1001 || pi_block.class_id === 310)) {
5048
+ const pi_ref_pattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${root_id}\\}`);
5049
+ const stripped_root = this._blocks.find((b) => (b.class_id === 4 || b.class_id === 224) && b.is_stripped && pi_ref_pattern.test(b.raw));
5050
+ if (stripped_root) {
5051
+ this._remove_id_from_scene_roots(stripped_root.file_id);
5052
+ }
5053
+ }
5054
+ return this._remove_id_from_scene_roots(root_id);
5055
+ }
5056
+ _remove_id_from_scene_roots(id) {
5057
+ const sr_blocks = [...this.find_by_class_id(166), ...this.find_by_class_id(1660057539)];
5058
+ if (sr_blocks.length === 0)
5059
+ return false;
5060
+ const sr_block = sr_blocks[0];
5061
+ let raw = sr_block.raw;
5062
+ const root_line = new RegExp(`\\n[ \\t]*- \\{fileID: ${id}\\}`);
5063
+ if (!root_line.test(raw))
5064
+ return false;
5065
+ raw = raw.replace(root_line, "");
5066
+ sr_block.replace_raw(raw);
5067
+ return true;
5068
+ }
4852
5069
  reorder_entities(ordered_transform_ids) {
4853
5070
  const entity_groups = [];
4854
5071
  for (const transform_id of ordered_transform_ids) {
4855
- const transform = this.find_by_file_id(transform_id);
4856
- if (!transform)
5072
+ const block = this.find_by_file_id(transform_id);
5073
+ if (!block)
5074
+ continue;
5075
+ let pi_block = null;
5076
+ if (block.class_id === 1001 || block.class_id === 310) {
5077
+ pi_block = block;
5078
+ } else if (block.is_stripped) {
5079
+ const pi_match = block.raw.match(/m_PrefabInstance:\s*\{fileID:\s*(-?\d+)\}/);
5080
+ if (pi_match) {
5081
+ const found = this.find_by_file_id(pi_match[1]);
5082
+ if (found && (found.class_id === 1001 || found.class_id === 310)) {
5083
+ pi_block = found;
5084
+ }
5085
+ }
5086
+ }
5087
+ if (pi_block) {
5088
+ const group = [pi_block.file_id];
5089
+ const hierarchy = this.collect_hierarchy(pi_block.file_id);
5090
+ for (const id of hierarchy)
5091
+ group.push(id);
5092
+ entity_groups.push(group);
4857
5093
  continue;
4858
- const go_match = transform.raw.match(/m_GameObject:\s*\{fileID:\s*(-?\d+)\}/);
4859
- if (!go_match)
5094
+ }
5095
+ const go_match = block.raw.match(/m_GameObject:\s*\{fileID:\s*(-?\d+)\}/);
5096
+ if (!go_match) {
5097
+ entity_groups.push([transform_id]);
4860
5098
  continue;
5099
+ }
4861
5100
  const go_id = go_match[1];
4862
5101
  const go = this.find_by_file_id(go_id);
4863
- if (!go)
5102
+ if (!go) {
5103
+ entity_groups.push([transform_id]);
4864
5104
  continue;
5105
+ }
4865
5106
  const comp_ids = [];
4866
5107
  const comp_matches = go.raw.matchAll(/component:\s*\{fileID:\s*(-?\d+)\}/g);
4867
5108
  for (const m of comp_matches) {
@@ -6589,6 +6830,9 @@ function createScriptableObject(options) {
6589
6830
  if (resolved.kind === "enum" || resolved.kind === "interface") {
6590
6831
  return { success: false, output_path, error: `"${script}" is ${resolved.kind === "enum" ? "an enum" : "an interface"}, not a ScriptableObject.` };
6591
6832
  }
6833
+ if (resolved.is_abstract) {
6834
+ return { success: false, output_path, error: `"${script}" is abstract and cannot be instantiated as a ScriptableObject asset.` };
6835
+ }
6592
6836
  if (resolved.base_class && resolved.base_class !== "ScriptableObject") {
6593
6837
  return { success: false, output_path, error: `"${script}" extends ${resolved.base_class}, not ScriptableObject. Cannot create as a ScriptableObject asset.` };
6594
6838
  }
@@ -6872,6 +7116,13 @@ function addComponent(options) {
6872
7116
  }
6873
7117
  const gameObjectId = gameObjectIdStr;
6874
7118
  const classId = get_class_id(component_type);
7119
+ if (classId === 114 && component_type.toLowerCase() === "monobehaviour") {
7120
+ return {
7121
+ success: false,
7122
+ file_path,
7123
+ error: '"MonoBehaviour" is a base class, not a concrete component script. Provide a script type/path/GUID (for example, "PlayerController", "Assets/Scripts/PlayerController.cs", or a 32-char GUID).'
7124
+ };
7125
+ }
6875
7126
  let duplicateWarning;
6876
7127
  const goBlock = doc.find_by_file_id(gameObjectIdStr);
6877
7128
  if (goBlock && classId !== null) {
@@ -6896,7 +7147,7 @@ function addComponent(options) {
6896
7147
  } else {
6897
7148
  let resolved;
6898
7149
  try {
6899
- resolved = resolve_script_with_fields(component_type, project_path);
7150
+ resolved = resolve_script_with_fields(component_type, project_path, { strict_exact_name: true });
6900
7151
  } catch (e) {
6901
7152
  return {
6902
7153
  success: false,
@@ -6934,6 +7185,13 @@ function addComponent(options) {
6934
7185
  error: `"${component_type}" is ${resolved.kind === "enum" ? "an enum" : "an interface"}, not a MonoBehaviour. Cannot add as a component.`
6935
7186
  };
6936
7187
  }
7188
+ if (resolved.is_abstract) {
7189
+ return {
7190
+ success: false,
7191
+ file_path,
7192
+ error: `"${component_type}" is abstract and cannot be added as a component.`
7193
+ };
7194
+ }
6937
7195
  if (resolved.base_class && !COMPONENT_BASE_CLASSES.has(resolved.base_class) && !is_valid_component_base(resolved.base_class, project_path)) {
6938
7196
  const warn = `"${component_type}" extends ${resolved.base_class}, which does not inherit from MonoBehaviour. Adding anyway, but ensure the script is a valid component. (Re-run "setup" if the inheritance graph is stale).`;
6939
7197
  if (!extractionError)
@@ -7766,6 +8024,56 @@ function validate_value_type(current_value, new_value) {
7766
8024
  }
7767
8025
  return null;
7768
8026
  }
8027
+ function findStrippedTransformForPrefabInstance(doc, prefabInstanceId) {
8028
+ const piRefPattern = new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${prefabInstanceId}\\}`);
8029
+ for (const block of doc.blocks) {
8030
+ if ((block.class_id === 4 || block.class_id === 224) && block.is_stripped && piRefPattern.test(block.raw)) {
8031
+ return block;
8032
+ }
8033
+ }
8034
+ return null;
8035
+ }
8036
+ function resolvePrefabRootOrderTarget(prefabInstanceBlock) {
8037
+ const modificationPattern = /- target:\s*(\{[^}]+\})\s*\n\s*propertyPath:\s*('?)([^\n']+)\2/gm;
8038
+ const transformPropertyPattern = /^m_(?:RootOrder|LocalPosition|LocalRotation|LocalScale|ConstrainProportionsScale)(?:\.|$)/;
8039
+ let transformTarget = null;
8040
+ for (const match of prefabInstanceBlock.raw.matchAll(modificationPattern)) {
8041
+ const targetRef = match[1];
8042
+ const propertyPath = match[3].trim();
8043
+ if (propertyPath === "m_RootOrder") {
8044
+ return targetRef;
8045
+ }
8046
+ if (!transformTarget && transformPropertyPattern.test(propertyPath)) {
8047
+ transformTarget = targetRef;
8048
+ }
8049
+ }
8050
+ return transformTarget;
8051
+ }
8052
+ function upsertPrefabRootOrderOverride(prefabInstanceBlock, newRootOrder) {
8053
+ const existingRootOrderPattern = /(- target:\s*\{[^}]+\}\s*\n\s*propertyPath:\s*'?m_RootOrder'?\s*\n\s*value:\s*)([^\n]*)(\s*\n\s*objectReference:\s*\{[^}]+\})/m;
8054
+ if (existingRootOrderPattern.test(prefabInstanceBlock.raw)) {
8055
+ prefabInstanceBlock.replace_raw(prefabInstanceBlock.raw.replace(existingRootOrderPattern, `$1${newRootOrder}$3`));
8056
+ return true;
8057
+ }
8058
+ const targetRef = resolvePrefabRootOrderTarget(prefabInstanceBlock) ?? "{fileID: 400000}";
8059
+ const rootOrderOverride = ` - target: ${targetRef}
8060
+ ` + ` propertyPath: m_RootOrder
8061
+ ` + ` value: ${newRootOrder}
8062
+ ` + " objectReference: {fileID: 0}";
8063
+ const removedPattern = /(\n\s*m_RemovedComponents:)/m;
8064
+ if (removedPattern.test(prefabInstanceBlock.raw)) {
8065
+ prefabInstanceBlock.replace_raw(prefabInstanceBlock.raw.replace(removedPattern, `
8066
+ ${rootOrderOverride}$1`));
8067
+ return true;
8068
+ }
8069
+ const modsPattern = /(m_Modifications:\s*\n)/;
8070
+ if (modsPattern.test(prefabInstanceBlock.raw)) {
8071
+ prefabInstanceBlock.replace_raw(prefabInstanceBlock.raw.replace(modsPattern, `$1${rootOrderOverride}
8072
+ `));
8073
+ return true;
8074
+ }
8075
+ return false;
8076
+ }
7769
8077
  function isAncestor(doc, childTransformId, candidateAncestorTransformId) {
7770
8078
  let currentId = candidateAncestorTransformId;
7771
8079
  const visited = new Set;
@@ -7776,17 +8084,34 @@ function isAncestor(doc, childTransformId, candidateAncestorTransformId) {
7776
8084
  return false;
7777
8085
  visited.add(currentId);
7778
8086
  const block = doc.find_by_file_id(currentId);
7779
- if (!block || block.class_id !== 4)
8087
+ if (!block)
8088
+ break;
8089
+ if (block.class_id === 1001 || block.class_id === 310) {
8090
+ const parentMatch = block.raw.match(/m_TransformParent:\s*\{fileID:\s*(-?\d+)\}/);
8091
+ currentId = parentMatch ? parentMatch[1] : "0";
8092
+ } else if (block.class_id === 4 || block.class_id === 224) {
8093
+ const fatherMatch = block.raw.match(/m_Father:\s*\{fileID:\s*(-?\d+)\}/);
8094
+ currentId = fatherMatch ? fatherMatch[1] : "0";
8095
+ } else {
7780
8096
  break;
7781
- const fatherMatch = block.raw.match(/m_Father:\s*\{fileID:\s*(-?\d+)\}/);
7782
- currentId = fatherMatch ? fatherMatch[1] : "0";
8097
+ }
7783
8098
  }
7784
8099
  return false;
7785
8100
  }
7786
8101
  function resolveTransformByGameObjectId(doc, gameObjectFileId) {
7787
8102
  const found = doc.find_by_file_id(gameObjectFileId);
7788
- if (!found || found.class_id !== 1) {
7789
- return { error: `GameObject with fileID ${gameObjectFileId} not found` };
8103
+ if (!found) {
8104
+ return { error: `Block with fileID ${gameObjectFileId} not found` };
8105
+ }
8106
+ if (found.class_id === 4 || found.class_id === 224) {
8107
+ return { id: gameObjectFileId };
8108
+ }
8109
+ if (found.class_id === 1001 || found.class_id === 310) {
8110
+ const strippedTransform = findStrippedTransformForPrefabInstance(doc, gameObjectFileId);
8111
+ return { id: strippedTransform ? strippedTransform.file_id : gameObjectFileId };
8112
+ }
8113
+ if (found.class_id !== 1) {
8114
+ return { error: `fileID ${gameObjectFileId} is not a GameObject, Transform, or PrefabInstance (class ${found.class_id})` };
7790
8115
  }
7791
8116
  const componentMatch = found.raw.match(/m_Component:\s*\n\s*-\s*component:\s*\{fileID:\s*(-?\d+)\}/);
7792
8117
  if (!componentMatch) {
@@ -7979,7 +8304,7 @@ function editComponentByFileId(options) {
7979
8304
  }
7980
8305
  const targetBlock = doc.find_by_file_id(file_id);
7981
8306
  if (!targetBlock) {
7982
- const hasPrefabInstance = doc.find_by_class_id(1001).length > 0;
8307
+ const hasPrefabInstance = doc.find_by_class_id(1001).length > 0 || doc.find_by_class_id(310).length > 0;
7983
8308
  if (hasPrefabInstance) {
7984
8309
  return {
7985
8310
  success: false,
@@ -8444,10 +8769,26 @@ function reparentGameObject(options) {
8444
8769
  }
8445
8770
  const childBlock = doc.find_by_file_id(childTransformId);
8446
8771
  if (!childBlock) {
8447
- return { success: false, file_path, error: `Transform ${childTransformId} not found` };
8772
+ return { success: false, file_path, error: `Transform or PrefabInstance ${childTransformId} not found` };
8773
+ }
8774
+ const childHierarchyTransformId = (() => {
8775
+ if (childBlock.class_id === 4 || childBlock.class_id === 224) {
8776
+ return childBlock.file_id;
8777
+ }
8778
+ if (childBlock.class_id === 1001 || childBlock.class_id === 310) {
8779
+ const stripped = findStrippedTransformForPrefabInstance(doc, childBlock.file_id);
8780
+ return stripped ? stripped.file_id : null;
8781
+ }
8782
+ return null;
8783
+ })();
8784
+ let oldParentTransformId = "0";
8785
+ if (childBlock.class_id === 1001 || childBlock.class_id === 310) {
8786
+ const parentMatch = childBlock.raw.match(/m_TransformParent:\s*\{fileID:\s*(-?\d+)\}/);
8787
+ oldParentTransformId = parentMatch ? parentMatch[1] : "0";
8788
+ } else {
8789
+ const fatherMatch = childBlock.raw.match(/m_Father:\s*\{fileID:\s*(-?\d+)\}/);
8790
+ oldParentTransformId = fatherMatch ? fatherMatch[1] : "0";
8448
8791
  }
8449
- const fatherMatch = childBlock.raw.match(/m_Father:\s*\{fileID:\s*(-?\d+)\}/);
8450
- const oldParentTransformId = fatherMatch ? fatherMatch[1] : "0";
8451
8792
  let newParentTransformId = "0";
8452
8793
  if (new_parent.toLowerCase() !== "root") {
8453
8794
  if (by_id || /^-?\d+$/.test(new_parent)) {
@@ -8474,11 +8815,55 @@ function reparentGameObject(options) {
8474
8815
  }
8475
8816
  }
8476
8817
  if (oldParentTransformId !== "0") {
8477
- doc.remove_child_from_parent(oldParentTransformId, childTransformId);
8818
+ if (childHierarchyTransformId) {
8819
+ doc.remove_child_from_parent(oldParentTransformId, childHierarchyTransformId);
8820
+ } else {
8821
+ doc.remove_child_from_parent(oldParentTransformId, childBlock.file_id);
8822
+ }
8823
+ } else {
8824
+ if (childHierarchyTransformId) {
8825
+ doc.remove_root_from_scene_roots(childHierarchyTransformId);
8826
+ } else {
8827
+ doc.remove_root_from_scene_roots(childBlock.file_id);
8828
+ }
8829
+ }
8830
+ if (childBlock.class_id === 1001 || childBlock.class_id === 310) {
8831
+ const tpPattern = new RegExp(`(m_TransformParent:\\s*)\\{fileID:\\s*(-?\\d+)\\}`);
8832
+ if (tpPattern.test(childBlock.raw)) {
8833
+ let updatedChildRaw = childBlock.raw.replace(tpPattern, `$1{fileID: ${newParentTransformId}}`);
8834
+ childBlock.replace_raw(updatedChildRaw);
8835
+ } else {
8836
+ const modPattern = /(m_Modification:\s*\n)/;
8837
+ const modIndentMatch = childBlock.raw.match(/^([ \t]*)m_Modification:/m);
8838
+ const indent = modIndentMatch ? modIndentMatch[1] : "";
8839
+ let updatedChildRaw = childBlock.raw.replace(modPattern, `$1${indent} m_TransformParent: {fileID: ${newParentTransformId}}
8840
+ `);
8841
+ childBlock.replace_raw(updatedChildRaw);
8842
+ }
8843
+ } else {
8844
+ const fatherPattern = new RegExp(`(m_Father:\\s*)\\{fileID:\\s*\\d+\\}`);
8845
+ if (fatherPattern.test(childBlock.raw)) {
8846
+ let updatedChildRaw = childBlock.raw.replace(fatherPattern, `$1{fileID: ${newParentTransformId}}`);
8847
+ childBlock.replace_raw(updatedChildRaw);
8848
+ } else {
8849
+ const anchorPattern = /^([ \t]*(?:m_GameObject|m_CorrespondingSourceObject|m_PrefabInstance):[ \t]*\{fileID:[ \t]*(-?\d+)[^}]*\}.*)/m;
8850
+ const headerPattern = /(^--- !u!(?:4|224) &-?\d+ stripped\n(?:Rect)?Transform:)/m;
8851
+ if (anchorPattern.test(childBlock.raw)) {
8852
+ let updatedChildRaw = childBlock.raw.replace(anchorPattern, `$1
8853
+ m_Father: {fileID: ${newParentTransformId}}`);
8854
+ childBlock.replace_raw(updatedChildRaw);
8855
+ } else if (headerPattern.test(childBlock.raw)) {
8856
+ let updatedChildRaw = childBlock.raw.replace(headerPattern, `$1
8857
+ m_Father: {fileID: ${newParentTransformId}}`);
8858
+ childBlock.replace_raw(updatedChildRaw);
8859
+ } else {
8860
+ let updatedChildRaw = childBlock.raw.trimEnd() + `
8861
+ m_Father: {fileID: ${newParentTransformId}}
8862
+ `;
8863
+ childBlock.replace_raw(updatedChildRaw);
8864
+ }
8865
+ }
8478
8866
  }
8479
- const fatherPattern = new RegExp(`(m_Father:\\s*)\\{fileID:\\s*\\d+\\}`);
8480
- let updatedChildRaw = childBlock.raw.replace(fatherPattern, `$1{fileID: ${newParentTransformId}}`);
8481
- childBlock.replace_raw(updatedChildRaw);
8482
8867
  {
8483
8868
  let newRootOrder;
8484
8869
  if (newParentTransformId === "0") {
@@ -8486,10 +8871,25 @@ function reparentGameObject(options) {
8486
8871
  } else {
8487
8872
  newRootOrder = doc.calculate_root_order(newParentTransformId);
8488
8873
  }
8489
- childBlock.set_property("m_RootOrder", String(newRootOrder));
8874
+ if (childBlock.class_id === 1001 || childBlock.class_id === 310) {
8875
+ const stripped = doc.blocks.find((b) => (b.class_id === 4 || b.class_id === 224) && b.is_stripped && new RegExp(`m_PrefabInstance:[ \\t]*\\{fileID:[ \\t]*${childTransformId}\\}`).test(b.raw));
8876
+ if (stripped) {
8877
+ stripped.set_property("m_RootOrder", String(newRootOrder));
8878
+ } else {
8879
+ upsertPrefabRootOrderOverride(childBlock, newRootOrder);
8880
+ }
8881
+ } else {
8882
+ childBlock.set_property("m_RootOrder", String(newRootOrder));
8883
+ }
8490
8884
  }
8491
8885
  if (newParentTransformId !== "0") {
8492
- doc.add_child_to_parent(newParentTransformId, childTransformId);
8886
+ if (childHierarchyTransformId) {
8887
+ doc.add_child_to_parent(newParentTransformId, childHierarchyTransformId);
8888
+ }
8889
+ } else {
8890
+ if (childHierarchyTransformId) {
8891
+ doc.add_root_to_scene_roots(childHierarchyTransformId);
8892
+ }
8493
8893
  }
8494
8894
  if (!doc.validate()) {
8495
8895
  return { success: false, file_path, error: "Validation failed after reparent" };
@@ -8508,9 +8908,9 @@ function reparentGameObject(options) {
8508
8908
  }
8509
8909
  function findPrefabInstanceBlock(doc, identifier) {
8510
8910
  const asId = doc.find_by_file_id(identifier);
8511
- if (asId && asId.class_id === 1001)
8911
+ if (asId && (asId.class_id === 1001 || asId.class_id === 310))
8512
8912
  return asId;
8513
- const piBlocks = doc.find_by_class_id(1001);
8913
+ const piBlocks = [...doc.find_by_class_id(1001), ...doc.find_by_class_id(310)];
8514
8914
  const escaped = identifier.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8515
8915
  const namePattern = new RegExp(`propertyPath:\\s*m_Name\\s+value:\\s*${escaped}\\s`);
8516
8916
  for (const block of piBlocks) {
@@ -9312,6 +9712,7 @@ ${fieldIndent} - rid: ${nextRid}`;
9312
9712
  }
9313
9713
  // src/editor/delete.ts
9314
9714
  var import_fs13 = require("fs");
9715
+ var import_path6 = require("path");
9315
9716
 
9316
9717
  // src/project-search.ts
9317
9718
  init_scanner();
@@ -10261,6 +10662,70 @@ function deletePrefabInstance(options) {
10261
10662
  deleted_count: allToRemove.size
10262
10663
  };
10263
10664
  }
10665
+ function deleteAssetFile(options) {
10666
+ const { file_path } = options;
10667
+ const pathError = validate_file_path(file_path, "write");
10668
+ if (pathError) {
10669
+ return { success: false, file_path, error: pathError };
10670
+ }
10671
+ if (!import_fs13.existsSync(file_path)) {
10672
+ return { success: false, file_path, error: `File not found: ${file_path}` };
10673
+ }
10674
+ const supportedExtensions = new Set([
10675
+ ".unity",
10676
+ ".prefab",
10677
+ ".mat",
10678
+ ".anim",
10679
+ ".controller",
10680
+ ".inputactions",
10681
+ ".asset"
10682
+ ]);
10683
+ const extension = import_path6.extname(file_path).toLowerCase();
10684
+ if (!supportedExtensions.has(extension)) {
10685
+ return {
10686
+ success: false,
10687
+ file_path,
10688
+ error: `Unsupported asset type "${extension}". Supported extensions: .unity, .prefab, .mat, .anim, .controller, .inputactions, .asset`
10689
+ };
10690
+ }
10691
+ const metaPath = `${file_path}.meta`;
10692
+ const hasMeta = import_fs13.existsSync(metaPath);
10693
+ try {
10694
+ import_fs13.unlinkSync(file_path);
10695
+ } catch (err) {
10696
+ return {
10697
+ success: false,
10698
+ file_path,
10699
+ error: `Failed to delete file: ${err instanceof Error ? err.message : String(err)}`
10700
+ };
10701
+ }
10702
+ if (hasMeta) {
10703
+ try {
10704
+ import_fs13.unlinkSync(metaPath);
10705
+ return {
10706
+ success: true,
10707
+ file_path,
10708
+ deleted_file: true,
10709
+ deleted_meta: true
10710
+ };
10711
+ } catch (err) {
10712
+ return {
10713
+ success: true,
10714
+ file_path,
10715
+ deleted_file: true,
10716
+ deleted_meta: false,
10717
+ warning: `Deleted asset file but failed to delete .meta file: ${err instanceof Error ? err.message : String(err)}`
10718
+ };
10719
+ }
10720
+ }
10721
+ return {
10722
+ success: true,
10723
+ file_path,
10724
+ deleted_file: true,
10725
+ deleted_meta: false,
10726
+ warning: `Asset file deleted, but no .meta file found at ${metaPath}`
10727
+ };
10728
+ }
10264
10729
  // src/editor/duplicate.ts
10265
10730
  var import_fs14 = require("fs");
10266
10731
  var path6 = __toESM(require("path"));
@@ -10905,9 +11370,9 @@ function move_scene(projectPath, scenePath, newPosition) {
10905
11370
 
10906
11371
  // src/packages.ts
10907
11372
  var import_fs15 = require("fs");
10908
- var import_path6 = require("path");
11373
+ var import_path7 = require("path");
10909
11374
  function load_manifest(project_path) {
10910
- const manifest_path = import_path6.join(project_path, "Packages", "manifest.json");
11375
+ const manifest_path = import_path7.join(project_path, "Packages", "manifest.json");
10911
11376
  if (!import_fs15.existsSync(manifest_path)) {
10912
11377
  return { error: `manifest.json not found at ${manifest_path}` };
10913
11378
  }
@@ -11101,27 +11566,384 @@ function remove_control_scheme(data, name) {
11101
11566
  return data;
11102
11567
  }
11103
11568
 
11104
- // src/cmd-create.ts
11105
- function build_create_command() {
11106
- const cmd = new Command("create").description("Create Unity objects (GameObjects, scenes, prefabs, components)");
11107
- cmd.command("gameobject <file> [name]").description("Create a new GameObject in a Unity file").option("-p, --parent <name|id>", "Parent GameObject name or Transform fileID").option("-n, --name <name>", "GameObject name (alternative to positional arg)").option("-j, --json", "Output as JSON").action((file, name_positional, options) => {
11108
- const name = name_positional || options.name;
11109
- if (!name) {
11110
- console.log(JSON.stringify({
11111
- success: false,
11112
- error: "Missing required name. Provide as positional argument or --name flag.",
11113
- correct_usage: [
11114
- "unity-agentic-tools create gameobject <file> <name>",
11115
- "unity-agentic-tools create gameobject <file> --name <name>"
11116
- ]
11117
- }, null, 2));
11118
- process.exitCode = 1;
11119
- return;
11569
+ // src/loaded-protection.ts
11570
+ var import_path9 = require("path");
11571
+
11572
+ // src/editor-client.ts
11573
+ var import_fs17 = require("fs");
11574
+ var import_path8 = require("path");
11575
+ function read_editor_config(project_path) {
11576
+ const config_path = import_path8.join(project_path, ".unity-agentic", "editor.json");
11577
+ if (!import_fs17.existsSync(config_path)) {
11578
+ return { error: `Editor bridge not found at ${config_path}. Is the Unity Editor running with the bridge package installed?` };
11579
+ }
11580
+ let config;
11581
+ try {
11582
+ const raw = import_fs17.readFileSync(config_path, "utf-8");
11583
+ config = JSON.parse(raw);
11584
+ } catch (err) {
11585
+ return { error: `Failed to parse editor.json: ${err instanceof Error ? err.message : String(err)}` };
11586
+ }
11587
+ if (typeof config.port !== "number" || typeof config.pid !== "number") {
11588
+ return { error: "Invalid editor.json: missing port or pid" };
11589
+ }
11590
+ if (!is_pid_alive(config.pid)) {
11591
+ return { error: `Unity Editor process (PID ${config.pid}) is not running. The editor may have been closed.` };
11592
+ }
11593
+ return config;
11594
+ }
11595
+ var RETRYABLE_CODES = new Set([-32000, -32002, -32003]);
11596
+ var RETRY_DELAYS = [500, 1000, 2000];
11597
+ async function call_editor(options) {
11598
+ const maxRetries = options.retries ?? 2;
11599
+ let lastResponse;
11600
+ for (let attempt = 0;attempt <= maxRetries; attempt++) {
11601
+ lastResponse = await call_editor_once(options);
11602
+ if (!lastResponse.error || !RETRYABLE_CODES.has(lastResponse.error.code)) {
11603
+ return lastResponse;
11120
11604
  }
11121
- const ext = file.toLowerCase().split(".").pop();
11122
- if (!ext || !["unity", "prefab", "asset"].includes(ext)) {
11123
- console.log(JSON.stringify({ success: false, error: `Invalid file type ".${ext}". create gameobject only works with .unity, .prefab, or .asset files` }, null, 2));
11124
- process.exitCode = 1;
11605
+ if (attempt < maxRetries) {
11606
+ const delay = RETRY_DELAYS[Math.min(attempt, RETRY_DELAYS.length - 1)];
11607
+ await new Promise((r) => setTimeout(r, delay));
11608
+ }
11609
+ }
11610
+ return lastResponse;
11611
+ }
11612
+ function call_editor_once(options) {
11613
+ const { method, params, timeout = 1e4, no_wait } = options;
11614
+ const config = resolve_config(options);
11615
+ if ("error" in config) {
11616
+ return Promise.resolve({
11617
+ jsonrpc: "2.0",
11618
+ id: "0",
11619
+ error: { code: -32000, message: config.error }
11620
+ });
11621
+ }
11622
+ const url = `ws://127.0.0.1:${config.port}/unity-agentic`;
11623
+ const request_id = generate_id();
11624
+ const wire_params = { ...params };
11625
+ if (timeout !== 1e4)
11626
+ wire_params._timeout = timeout;
11627
+ if (no_wait)
11628
+ wire_params.no_wait = true;
11629
+ const request = {
11630
+ jsonrpc: "2.0",
11631
+ id: request_id,
11632
+ method,
11633
+ ...Object.keys(wire_params).length > 0 ? { params: wire_params } : {}
11634
+ };
11635
+ return new Promise((resolve5) => {
11636
+ let resolved = false;
11637
+ let ws;
11638
+ const timer = setTimeout(() => {
11639
+ if (!resolved) {
11640
+ resolved = true;
11641
+ try {
11642
+ ws?.close();
11643
+ } catch {}
11644
+ resolve5({
11645
+ jsonrpc: "2.0",
11646
+ id: request_id,
11647
+ error: { code: -32001, message: `Timeout after ${timeout}ms waiting for response to ${method}` }
11648
+ });
11649
+ }
11650
+ }, timeout);
11651
+ try {
11652
+ ws = new WebSocket(url);
11653
+ ws.onopen = () => {
11654
+ ws.send(JSON.stringify(request));
11655
+ if (no_wait && !resolved) {
11656
+ resolved = true;
11657
+ clearTimeout(timer);
11658
+ setTimeout(() => {
11659
+ try {
11660
+ ws.close();
11661
+ } catch {}
11662
+ }, 200);
11663
+ resolve5({
11664
+ jsonrpc: "2.0",
11665
+ id: request_id,
11666
+ result: { queued: true }
11667
+ });
11668
+ }
11669
+ };
11670
+ ws.onmessage = (event) => {
11671
+ try {
11672
+ const data = JSON.parse(String(event.data));
11673
+ if (data.id === request_id) {
11674
+ if (!resolved) {
11675
+ resolved = true;
11676
+ clearTimeout(timer);
11677
+ ws.close();
11678
+ resolve5(data);
11679
+ }
11680
+ }
11681
+ } catch {}
11682
+ };
11683
+ ws.onerror = () => {
11684
+ if (!resolved) {
11685
+ resolved = true;
11686
+ clearTimeout(timer);
11687
+ resolve5({
11688
+ jsonrpc: "2.0",
11689
+ id: request_id,
11690
+ error: { code: -32002, message: `WebSocket connection failed to ${url}. Is the Unity Editor running?` }
11691
+ });
11692
+ }
11693
+ };
11694
+ ws.onclose = () => {
11695
+ if (!resolved) {
11696
+ resolved = true;
11697
+ clearTimeout(timer);
11698
+ resolve5({
11699
+ jsonrpc: "2.0",
11700
+ id: request_id,
11701
+ error: { code: -32003, message: "WebSocket connection closed before response received" }
11702
+ });
11703
+ }
11704
+ };
11705
+ } catch (err) {
11706
+ if (!resolved) {
11707
+ resolved = true;
11708
+ clearTimeout(timer);
11709
+ resolve5({
11710
+ jsonrpc: "2.0",
11711
+ id: request_id,
11712
+ error: { code: -32002, message: `Failed to connect: ${err instanceof Error ? err.message : String(err)}` }
11713
+ });
11714
+ }
11715
+ }
11716
+ });
11717
+ }
11718
+ async function stream_editor(options) {
11719
+ const { method, params, timeout = 30000, on_event } = options;
11720
+ const config = resolve_config(options);
11721
+ if ("error" in config) {
11722
+ throw new Error(config.error);
11723
+ }
11724
+ const MAX_RECONNECTS = 5;
11725
+ let reconnect_count = 0;
11726
+ let stopped = false;
11727
+ return new Promise((resolve5, reject) => {
11728
+ let resolved = false;
11729
+ function connect(url) {
11730
+ const ws = new WebSocket(url);
11731
+ const request_id = generate_id();
11732
+ const request = {
11733
+ jsonrpc: "2.0",
11734
+ id: request_id,
11735
+ method,
11736
+ ...params ? { params } : {}
11737
+ };
11738
+ let connected = false;
11739
+ const timer = !resolved ? setTimeout(() => {
11740
+ if (!connected && !resolved) {
11741
+ reject(new Error(`Timeout connecting to ${url}`));
11742
+ }
11743
+ }, timeout) : null;
11744
+ ws.onopen = () => {
11745
+ connected = true;
11746
+ if (timer)
11747
+ clearTimeout(timer);
11748
+ reconnect_count = 0;
11749
+ ws.send(JSON.stringify(request));
11750
+ if (!resolved) {
11751
+ resolved = true;
11752
+ resolve5({ close: () => {
11753
+ stopped = true;
11754
+ ws.close();
11755
+ } });
11756
+ }
11757
+ };
11758
+ ws.onmessage = (event) => {
11759
+ try {
11760
+ const data = JSON.parse(String(event.data));
11761
+ if ("method" in data && !("id" in data)) {
11762
+ on_event(data);
11763
+ }
11764
+ } catch {}
11765
+ };
11766
+ ws.onerror = () => {
11767
+ if (!connected && !resolved) {
11768
+ if (timer)
11769
+ clearTimeout(timer);
11770
+ reject(new Error(`WebSocket connection failed to ${url}`));
11771
+ }
11772
+ };
11773
+ ws.onclose = () => {
11774
+ if (stopped)
11775
+ return;
11776
+ if (!connected && !resolved)
11777
+ return;
11778
+ if (reconnect_count >= MAX_RECONNECTS)
11779
+ return;
11780
+ reconnect_count++;
11781
+ const delay = Math.min(500 * reconnect_count, 3000);
11782
+ setTimeout(() => {
11783
+ if (stopped)
11784
+ return;
11785
+ const fresh_config = resolve_config(options);
11786
+ if ("error" in fresh_config)
11787
+ return;
11788
+ connect(`ws://127.0.0.1:${fresh_config.port}/unity-agentic`);
11789
+ }, delay);
11790
+ };
11791
+ }
11792
+ connect(`ws://127.0.0.1:${config.port}/unity-agentic`);
11793
+ });
11794
+ }
11795
+ async function ping_editor(port, timeout_ms = 2000) {
11796
+ return new Promise((resolve5) => {
11797
+ const timer = setTimeout(() => {
11798
+ resolve5({ reachable: false, error: `Timeout after ${timeout_ms}ms` });
11799
+ }, timeout_ms);
11800
+ try {
11801
+ const ws = new WebSocket(`ws://127.0.0.1:${port}/unity-agentic`);
11802
+ ws.onopen = () => {
11803
+ clearTimeout(timer);
11804
+ try {
11805
+ ws.close();
11806
+ } catch {}
11807
+ resolve5({ reachable: true });
11808
+ };
11809
+ ws.onerror = (err) => {
11810
+ clearTimeout(timer);
11811
+ resolve5({ reachable: false, error: String(err) });
11812
+ };
11813
+ } catch (err) {
11814
+ clearTimeout(timer);
11815
+ resolve5({ reachable: false, error: String(err) });
11816
+ }
11817
+ });
11818
+ }
11819
+ function resolve_config(options) {
11820
+ if (options.port) {
11821
+ return { port: options.port, pid: 0, version: "unknown" };
11822
+ }
11823
+ return read_editor_config(options.project_path);
11824
+ }
11825
+ function is_pid_alive(pid) {
11826
+ try {
11827
+ process.kill(pid, 0);
11828
+ return true;
11829
+ } catch {
11830
+ return false;
11831
+ }
11832
+ }
11833
+ function generate_id() {
11834
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
11835
+ }
11836
+
11837
+ // src/loaded-protection.ts
11838
+ function normalize_slashes(value) {
11839
+ return value.replace(/\\/g, "/");
11840
+ }
11841
+ function to_project_asset_path(file_path, project_path) {
11842
+ const abs_file = import_path9.resolve(file_path);
11843
+ const abs_project = import_path9.resolve(project_path);
11844
+ const rel = import_path9.relative(abs_project, abs_file);
11845
+ if (!rel || rel.startsWith("..") || rel.includes(`..${import_path9.sep}`)) {
11846
+ if (/^Assets[\\/]/.test(file_path)) {
11847
+ return normalize_slashes(file_path);
11848
+ }
11849
+ return null;
11850
+ }
11851
+ return normalize_slashes(rel);
11852
+ }
11853
+ function normalize_loaded_paths(paths) {
11854
+ return new Set(paths.map((p) => normalize_slashes(p)));
11855
+ }
11856
+ async function get_loaded_state(project_path) {
11857
+ const config = read_editor_config(project_path);
11858
+ if ("error" in config)
11859
+ return null;
11860
+ const ping = await ping_editor(config.port, 1200);
11861
+ if (!ping.reachable)
11862
+ return null;
11863
+ const response = await call_editor({
11864
+ project_path,
11865
+ method: "editor.scene.loaded",
11866
+ timeout: 2500
11867
+ });
11868
+ if (response.error || !response.result || typeof response.result !== "object") {
11869
+ return null;
11870
+ }
11871
+ const result = response.result;
11872
+ const loaded = Array.isArray(result.loaded_scene_paths) ? result.loaded_scene_paths.filter((v) => typeof v === "string") : [];
11873
+ return {
11874
+ loaded_scene_paths: loaded,
11875
+ active_scene_path: typeof result.active_scene_path === "string" ? result.active_scene_path : undefined,
11876
+ prefab_stage_path: typeof result.prefab_stage_path === "string" ? result.prefab_stage_path : undefined
11877
+ };
11878
+ }
11879
+ async function is_editor_connected_for_project(project_path) {
11880
+ const resolved_project = resolve_project_path(project_path);
11881
+ const config = read_editor_config(resolved_project);
11882
+ if ("error" in config)
11883
+ return false;
11884
+ const ping = await ping_editor(config.port, 1200);
11885
+ return ping.reachable;
11886
+ }
11887
+ async function enforce_loaded_edit_protection(file_path, bypass, project_path) {
11888
+ if (bypass)
11889
+ return { allowed: true };
11890
+ const ext = file_path.toLowerCase().split(".").pop();
11891
+ if (!ext || ext !== "unity" && ext !== "prefab") {
11892
+ return { allowed: true };
11893
+ }
11894
+ const inferred_project = project_path ? resolve_project_path(project_path) : find_unity_project_root(import_path9.dirname(import_path9.resolve(file_path))) || process.cwd();
11895
+ const loaded_state = await get_loaded_state(inferred_project);
11896
+ if (!loaded_state) {
11897
+ return { allowed: true };
11898
+ }
11899
+ const target_asset_path = to_project_asset_path(file_path, inferred_project);
11900
+ if (!target_asset_path) {
11901
+ return { allowed: true };
11902
+ }
11903
+ const loaded_scenes = normalize_loaded_paths(loaded_state.loaded_scene_paths);
11904
+ const active_scene = loaded_state.active_scene_path ? normalize_slashes(loaded_state.active_scene_path) : "";
11905
+ const prefab_stage = loaded_state.prefab_stage_path ? normalize_slashes(loaded_state.prefab_stage_path) : "";
11906
+ if (target_asset_path.endsWith(".unity")) {
11907
+ if (loaded_scenes.has(target_asset_path)) {
11908
+ const reason = active_scene === target_asset_path ? "it is currently the active scene in the Unity Editor" : "it is currently loaded in the Unity Editor";
11909
+ return {
11910
+ allowed: false,
11911
+ error: `Refusing to edit ${target_asset_path}: ${reason}. Use --bypass-loaded-protection to force file-based edits while editor is connected.`
11912
+ };
11913
+ }
11914
+ }
11915
+ if (target_asset_path.endsWith(".prefab")) {
11916
+ if (prefab_stage === target_asset_path) {
11917
+ return {
11918
+ allowed: false,
11919
+ error: `Refusing to edit ${target_asset_path}: it is currently open in Prefab Mode. Use --bypass-loaded-protection to force file-based edits while editor is connected.`
11920
+ };
11921
+ }
11922
+ }
11923
+ return { allowed: true };
11924
+ }
11925
+
11926
+ // src/cmd-create.ts
11927
+ function build_create_command() {
11928
+ const cmd = new Command("create").description("Create Unity objects (GameObjects, scenes, prefabs, components)");
11929
+ cmd.command("gameobject <file> [name]").description("Create a new GameObject in a Unity file").option("-p, --parent <name|id>", "Parent GameObject name or Transform fileID").option("-n, --name <name>", "GameObject name (alternative to positional arg)").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (file, name_positional, options) => {
11930
+ const name = name_positional || options.name;
11931
+ if (!name) {
11932
+ console.log(JSON.stringify({
11933
+ success: false,
11934
+ error: "Missing required name. Provide as positional argument or --name flag.",
11935
+ correct_usage: [
11936
+ "unity-agentic-tools create gameobject <file> <name>",
11937
+ "unity-agentic-tools create gameobject <file> --name <name>"
11938
+ ]
11939
+ }, null, 2));
11940
+ process.exitCode = 1;
11941
+ return;
11942
+ }
11943
+ const ext = file.toLowerCase().split(".").pop();
11944
+ if (!ext || !["unity", "prefab", "asset"].includes(ext)) {
11945
+ console.log(JSON.stringify({ success: false, error: `Invalid file type ".${ext}". create gameobject only works with .unity, .prefab, or .asset files` }, null, 2));
11946
+ process.exitCode = 1;
11125
11947
  return;
11126
11948
  }
11127
11949
  let parent;
@@ -11129,6 +11951,12 @@ function build_create_command() {
11129
11951
  const asNumber = parseInt(options.parent, 10);
11130
11952
  parent = isNaN(asNumber) ? options.parent : asNumber;
11131
11953
  }
11954
+ const guard = await enforce_loaded_edit_protection(file, options.bypassLoadedProtection);
11955
+ if (!guard.allowed) {
11956
+ console.log(JSON.stringify({ success: false, file_path: file, error: guard.error }, null, 2));
11957
+ process.exitCode = 1;
11958
+ return;
11959
+ }
11132
11960
  const result = createGameObject({
11133
11961
  file_path: file,
11134
11962
  name,
@@ -11157,7 +11985,7 @@ function build_create_command() {
11157
11985
  if (!result.success)
11158
11986
  process.exitCode = 1;
11159
11987
  });
11160
- cmd.command("prefab-instance <scene_file> <prefab_path>").description("Instantiate a prefab into a scene file").option("-n, --name <name>", "Instance name (defaults to prefab filename)").option("-p, --parent <name|id>", "Parent GameObject name or Transform fileID").option("--position <x,y,z>", "Local position (default: 0,0,0)").option("-j, --json", "Output as JSON").action((scene_file, prefab_path_arg, options) => {
11988
+ cmd.command("prefab-instance <scene_file> <prefab_path>").description("Instantiate a prefab into a scene file").option("-n, --name <name>", "Instance name (defaults to prefab filename)").option("-p, --parent <name|id>", "Parent GameObject name or Transform fileID").option("--position <x,y,z>", "Local position (default: 0,0,0)").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (scene_file, prefab_path_arg, options) => {
11161
11989
  let position;
11162
11990
  if (options.position) {
11163
11991
  const parts = options.position.split(",").map(Number);
@@ -11176,6 +12004,12 @@ function build_create_command() {
11176
12004
  const asNumber = parseInt(options.parent, 10);
11177
12005
  parent = isNaN(asNumber) ? options.parent : asNumber;
11178
12006
  }
12007
+ const guard = await enforce_loaded_edit_protection(scene_file, options.bypassLoadedProtection);
12008
+ if (!guard.allowed) {
12009
+ console.log(JSON.stringify({ success: false, file_path: scene_file, error: guard.error }, null, 2));
12010
+ process.exitCode = 1;
12011
+ return;
12012
+ }
11179
12013
  const result = createPrefabInstance({
11180
12014
  scene_path: scene_file,
11181
12015
  prefab_path: prefab_path_arg,
@@ -11216,12 +12050,18 @@ function build_create_command() {
11216
12050
  if (!result.success)
11217
12051
  process.exitCode = 1;
11218
12052
  });
11219
- cmd.command("component <file> <object_name> <component>").description("Add a Unity component (e.g., MeshRenderer, Animator, Rigidbody) or custom script").option("-p, --project <path>", "Unity project path (for script GUID lookup)").option("-j, --json", "Output as JSON").action((file, object_name, component, options) => {
12053
+ cmd.command("component <file> <object_name> <component>").description("Add a Unity component (e.g., MeshRenderer, Animator, Rigidbody) or custom script").option("-p, --project <path>", "Unity project path (for script GUID lookup)").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (file, object_name, component, options) => {
11220
12054
  if (!component || component.trim() === "") {
11221
12055
  console.log(JSON.stringify({ success: false, file_path: file, error: "Component name must not be empty" }, null, 2));
11222
12056
  process.exitCode = 1;
11223
12057
  return;
11224
12058
  }
12059
+ const guard = await enforce_loaded_edit_protection(file, options.bypassLoadedProtection, options.project);
12060
+ if (!guard.allowed) {
12061
+ console.log(JSON.stringify({ success: false, file_path: file, error: guard.error }, null, 2));
12062
+ process.exitCode = 1;
12063
+ return;
12064
+ }
11225
12065
  const result = addComponent({
11226
12066
  file_path: file,
11227
12067
  game_object_name: object_name,
@@ -11232,7 +12072,13 @@ function build_create_command() {
11232
12072
  if (!result.success)
11233
12073
  process.exitCode = 1;
11234
12074
  });
11235
- cmd.command("component-copy <file> <source_file_id> <target_object_name>").description("Copy a component to a target GameObject").option("-j, --json", "Output as JSON").action((file, source_file_id, target_object_name, _options) => {
12075
+ cmd.command("component-copy <file> <source_file_id> <target_object_name>").description("Copy a component to a target GameObject").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (file, source_file_id, target_object_name, options) => {
12076
+ const guard = await enforce_loaded_edit_protection(file, options.bypassLoadedProtection);
12077
+ if (!guard.allowed) {
12078
+ console.log(JSON.stringify({ success: false, file_path: file, error: guard.error }, null, 2));
12079
+ process.exitCode = 1;
12080
+ return;
12081
+ }
11236
12082
  const result = copyComponent({
11237
12083
  file_path: file,
11238
12084
  source_file_id,
@@ -11260,7 +12106,7 @@ function build_create_command() {
11260
12106
  console.log(JSON.stringify({ success: false, error: "--shader <guid> is required" }, null, 2));
11261
12107
  process.exit(1);
11262
12108
  }
11263
- if (import_fs17.existsSync(output_path)) {
12109
+ if (import_fs18.existsSync(output_path)) {
11264
12110
  console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
11265
12111
  process.exit(1);
11266
12112
  }
@@ -11333,7 +12179,7 @@ Material:
11333
12179
  m_BuildTextureStacks: []
11334
12180
  `;
11335
12181
  ensure_parent_dir(output_path);
11336
- import_fs17.writeFileSync(output_path, mat_content, "utf-8");
12182
+ import_fs18.writeFileSync(output_path, mat_content, "utf-8");
11337
12183
  const guid = import_crypto2.randomBytes(16).toString("hex");
11338
12184
  const meta_content = `fileFormatVersion: 2
11339
12185
  guid: ${guid}
@@ -11344,7 +12190,7 @@ NativeFormatImporter:
11344
12190
  assetBundleName:
11345
12191
  assetBundleVariant:
11346
12192
  `;
11347
- import_fs17.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
12193
+ import_fs18.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
11348
12194
  console.log(JSON.stringify({
11349
12195
  success: true,
11350
12196
  file: output_path,
@@ -11375,7 +12221,7 @@ NativeFormatImporter:
11375
12221
  process.exitCode = 1;
11376
12222
  return;
11377
12223
  }
11378
- if (import_fs17.existsSync(output_path)) {
12224
+ if (import_fs18.existsSync(output_path)) {
11379
12225
  console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
11380
12226
  process.exitCode = 1;
11381
12227
  return;
@@ -11402,7 +12248,7 @@ ScriptedImporter:
11402
12248
  wrapperClassName:
11403
12249
  wrapperCodeNamespace:
11404
12250
  `;
11405
- import_fs17.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
12251
+ import_fs18.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
11406
12252
  console.log(JSON.stringify({
11407
12253
  success: true,
11408
12254
  file: output_path,
@@ -11417,8 +12263,8 @@ ScriptedImporter:
11417
12263
  process.exitCode = 1;
11418
12264
  return;
11419
12265
  }
11420
- const name = name_arg || import_path7.basename(output_path).replace(/\.anim$/i, "");
11421
- if (import_fs17.existsSync(output_path)) {
12266
+ const name = name_arg || import_path10.basename(output_path).replace(/\.anim$/i, "");
12267
+ if (import_fs18.existsSync(output_path)) {
11422
12268
  console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
11423
12269
  process.exitCode = 1;
11424
12270
  return;
@@ -11486,7 +12332,7 @@ AnimationClip:
11486
12332
  m_Events: []
11487
12333
  `;
11488
12334
  ensure_parent_dir(output_path);
11489
- import_fs17.writeFileSync(output_path, anim_content, "utf-8");
12335
+ import_fs18.writeFileSync(output_path, anim_content, "utf-8");
11490
12336
  const guid = import_crypto2.randomBytes(16).toString("hex");
11491
12337
  const meta_content = `fileFormatVersion: 2
11492
12338
  guid: ${guid}
@@ -11497,7 +12343,7 @@ NativeFormatImporter:
11497
12343
  assetBundleName:
11498
12344
  assetBundleVariant:
11499
12345
  `;
11500
- import_fs17.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
12346
+ import_fs18.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
11501
12347
  console.log(JSON.stringify({
11502
12348
  success: true,
11503
12349
  file: output_path,
@@ -11509,13 +12355,13 @@ NativeFormatImporter:
11509
12355
  }, null, 2));
11510
12356
  });
11511
12357
  cmd.command("animator <output_path> [name]").description("Create a blank .controller AnimatorController file (name defaults to filename without extension)").option("--layer <name>", 'Name of the initial layer (default: "Base Layer")', "Base Layer").option("-j, --json", "Output as JSON").action((output_path, name_arg, options) => {
11512
- const name = name_arg || import_path7.basename(output_path).replace(/\.controller$/i, "");
12358
+ const name = name_arg || import_path10.basename(output_path).replace(/\.controller$/i, "");
11513
12359
  if (!output_path.toLowerCase().endsWith(".controller")) {
11514
12360
  console.log(JSON.stringify({ success: false, error: "Output path must end with .controller" }, null, 2));
11515
12361
  process.exitCode = 1;
11516
12362
  return;
11517
12363
  }
11518
- if (import_fs17.existsSync(output_path)) {
12364
+ if (import_fs18.existsSync(output_path)) {
11519
12365
  console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
11520
12366
  process.exitCode = 1;
11521
12367
  return;
@@ -11566,7 +12412,7 @@ AnimatorStateMachine:
11566
12412
  m_DefaultState: {fileID: 0}
11567
12413
  `;
11568
12414
  ensure_parent_dir(output_path);
11569
- import_fs17.writeFileSync(output_path, ctrl_content, "utf-8");
12415
+ import_fs18.writeFileSync(output_path, ctrl_content, "utf-8");
11570
12416
  const guid = import_crypto2.randomBytes(16).toString("hex");
11571
12417
  const meta_content = `fileFormatVersion: 2
11572
12418
  guid: ${guid}
@@ -11577,7 +12423,7 @@ NativeFormatImporter:
11577
12423
  assetBundleName:
11578
12424
  assetBundleVariant:
11579
12425
  `;
11580
- import_fs17.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
12426
+ import_fs18.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
11581
12427
  console.log(JSON.stringify({
11582
12428
  success: true,
11583
12429
  file: output_path,
@@ -11588,13 +12434,13 @@ NativeFormatImporter:
11588
12434
  }, null, 2));
11589
12435
  });
11590
12436
  cmd.command("prefab <output_path> [name]").description("Create a blank .prefab file (name defaults to filename without extension)").option("-j, --json", "Output as JSON").action((output_path, name_arg) => {
11591
- const name = name_arg || import_path7.basename(output_path).replace(/\.prefab$/i, "");
12437
+ const name = name_arg || import_path10.basename(output_path).replace(/\.prefab$/i, "");
11592
12438
  if (!output_path.toLowerCase().endsWith(".prefab")) {
11593
12439
  console.log(JSON.stringify({ success: false, error: "Output path must end with .prefab" }, null, 2));
11594
12440
  process.exitCode = 1;
11595
12441
  return;
11596
12442
  }
11597
- if (import_fs17.existsSync(output_path)) {
12443
+ if (import_fs18.existsSync(output_path)) {
11598
12444
  console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
11599
12445
  process.exitCode = 1;
11600
12446
  return;
@@ -11634,7 +12480,7 @@ Transform:
11634
12480
  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
11635
12481
  `;
11636
12482
  ensure_parent_dir(output_path);
11637
- import_fs17.writeFileSync(output_path, prefab_content, "utf-8");
12483
+ import_fs18.writeFileSync(output_path, prefab_content, "utf-8");
11638
12484
  const guid = import_crypto2.randomBytes(16).toString("hex");
11639
12485
  const meta_content = `fileFormatVersion: 2
11640
12486
  guid: ${guid}
@@ -11644,7 +12490,7 @@ PrefabImporter:
11644
12490
  assetBundleName:
11645
12491
  assetBundleVariant:
11646
12492
  `;
11647
- import_fs17.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
12493
+ import_fs18.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
11648
12494
  console.log(JSON.stringify({
11649
12495
  success: true,
11650
12496
  file: output_path,
@@ -11658,8 +12504,8 @@ PrefabImporter:
11658
12504
 
11659
12505
  // src/cmd-read.ts
11660
12506
  init_scanner();
11661
- var import_fs18 = require("fs");
11662
- var import_path8 = require("path");
12507
+ var import_fs19 = require("fs");
12508
+ var import_path11 = require("path");
11663
12509
  var import_os = require("os");
11664
12510
  function parse_unity_ref(ref_text) {
11665
12511
  const file_id_match = ref_text.match(/fileID:[ \t]*(-?\d+)/i);
@@ -11969,7 +12815,7 @@ function parse_material_yaml(content) {
11969
12815
  return result;
11970
12816
  }
11971
12817
  function categorize_asset(filePath) {
11972
- const ext = import_path8.extname(filePath).toLowerCase();
12818
+ const ext = import_path11.extname(filePath).toLowerCase();
11973
12819
  const categories = {
11974
12820
  ".cs": "script",
11975
12821
  ".mat": "material",
@@ -12013,12 +12859,12 @@ function categorize_asset(filePath) {
12013
12859
  return categories[ext] || "other";
12014
12860
  }
12015
12861
  function find_project_root_from_file(filePath) {
12016
- let dir = import_path8.dirname(import_path8.resolve(filePath));
12862
+ let dir = import_path11.dirname(import_path11.resolve(filePath));
12017
12863
  for (let i = 0;i < 20; i++) {
12018
- if (import_fs18.existsSync(import_path8.join(dir, "Assets")) && import_fs18.existsSync(import_path8.join(dir, "ProjectSettings"))) {
12864
+ if (import_fs19.existsSync(import_path11.join(dir, "Assets")) && import_fs19.existsSync(import_path11.join(dir, "ProjectSettings"))) {
12019
12865
  return dir;
12020
12866
  }
12021
- const parent = import_path8.dirname(dir);
12867
+ const parent = import_path11.dirname(dir);
12022
12868
  if (parent === dir)
12023
12869
  break;
12024
12870
  dir = parent;
@@ -12028,13 +12874,13 @@ function find_project_root_from_file(filePath) {
12028
12874
  function get_editor_log_path() {
12029
12875
  const p = import_os.platform();
12030
12876
  if (p === "darwin")
12031
- return import_path8.join(import_os.homedir(), "Library", "Logs", "Unity", "Editor.log");
12877
+ return import_path11.join(import_os.homedir(), "Library", "Logs", "Unity", "Editor.log");
12032
12878
  if (p === "win32") {
12033
- const localAppData = process.env.LOCALAPPDATA || import_path8.join(import_os.homedir(), "AppData", "Local");
12034
- return import_path8.join(localAppData, "Unity", "Editor", "Editor.log");
12879
+ const localAppData = process.env.LOCALAPPDATA || import_path11.join(import_os.homedir(), "AppData", "Local");
12880
+ return import_path11.join(localAppData, "Unity", "Editor", "Editor.log");
12035
12881
  }
12036
12882
  if (p === "linux")
12037
- return import_path8.join(import_os.homedir(), ".config", "unity3d", "Editor.log");
12883
+ return import_path11.join(import_os.homedir(), ".config", "unity3d", "Editor.log");
12038
12884
  return null;
12039
12885
  }
12040
12886
  function parse_log_line_timestamp(line) {
@@ -12365,19 +13211,19 @@ function walk_files(dir, extensions) {
12365
13211
  const current = stack.pop();
12366
13212
  let entries;
12367
13213
  try {
12368
- entries = import_fs18.readdirSync(current);
13214
+ entries = import_fs19.readdirSync(current);
12369
13215
  } catch {
12370
13216
  continue;
12371
13217
  }
12372
13218
  for (const entry of entries) {
12373
13219
  if (entry.startsWith("."))
12374
13220
  continue;
12375
- const full = import_path8.join(current, entry);
13221
+ const full = import_path11.join(current, entry);
12376
13222
  try {
12377
- const stat = import_fs18.statSync(full);
13223
+ const stat = import_fs19.statSync(full);
12378
13224
  if (stat.isDirectory()) {
12379
13225
  stack.push(full);
12380
- } else if (extensions.has(import_path8.extname(full).toLowerCase())) {
13226
+ } else if (extensions.has(import_path11.extname(full).toLowerCase())) {
12381
13227
  results.push(full);
12382
13228
  }
12383
13229
  } catch {
@@ -12388,7 +13234,7 @@ function walk_files(dir, extensions) {
12388
13234
  return results;
12389
13235
  }
12390
13236
  function validate_unity_yaml(file) {
12391
- if (!import_fs18.existsSync(file)) {
13237
+ if (!import_fs19.existsSync(file)) {
12392
13238
  return `File not found: ${file}`;
12393
13239
  }
12394
13240
  try {
@@ -12484,7 +13330,7 @@ function build_read_command(getScanner) {
12484
13330
  if (strippedGoCount > 0) {
12485
13331
  variantDetected = true;
12486
13332
  }
12487
- const projectPath = find_unity_project_root(import_path8.dirname(file));
13333
+ const projectPath = find_unity_project_root(import_path11.dirname(file));
12488
13334
  const resolved = resolve_source_prefab(doc, file, projectPath ?? undefined);
12489
13335
  if (resolved) {
12490
13336
  const sourceResult = getScanner().inspect_all_paginated({
@@ -12527,7 +13373,7 @@ function build_read_command(getScanner) {
12527
13373
  }
12528
13374
  if (result.total === 0) {
12529
13375
  try {
12530
- const fileSize = import_fs18.statSync(file).size;
13376
+ const fileSize = import_fs19.statSync(file).size;
12531
13377
  if (fileSize > 100) {
12532
13378
  if (variantDetected && !variantResolvedFromSource) {
12533
13379
  const variantWarning = "PrefabVariant detected but source prefab hierarchy could not be resolved (missing project context or GUID cache). Use --project or run setup to resolve source hierarchy.";
@@ -12645,7 +13491,7 @@ function build_read_command(getScanner) {
12645
13491
  console.log(JSON.stringify({ error: matValidationError }, null, 2));
12646
13492
  process.exit(1);
12647
13493
  }
12648
- const content = import_fs18.readFileSync(file, "utf-8");
13494
+ const content = import_fs19.readFileSync(file, "utf-8");
12649
13495
  if (!content.includes("Material:")) {
12650
13496
  console.log(JSON.stringify({ error: `File "${file}" does not contain a Material block. Use 'read asset' for generic Unity YAML files.` }, null, 2));
12651
13497
  process.exit(1);
@@ -12687,13 +13533,13 @@ function build_read_command(getScanner) {
12687
13533
  }, null, 2));
12688
13534
  });
12689
13535
  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) => {
12690
- if (!import_fs18.existsSync(file)) {
13536
+ if (!import_fs19.existsSync(file)) {
12691
13537
  console.log(JSON.stringify({ error: `File not found: ${file}` }, null, 2));
12692
13538
  process.exit(1);
12693
13539
  }
12694
13540
  let content;
12695
13541
  try {
12696
- content = import_fs18.readFileSync(file, "utf-8");
13542
+ content = import_fs19.readFileSync(file, "utf-8");
12697
13543
  } catch {
12698
13544
  console.log(JSON.stringify({ error: `Cannot read file: ${file}` }, null, 2));
12699
13545
  process.exit(1);
@@ -12734,7 +13580,7 @@ function build_read_command(getScanner) {
12734
13580
  const result = [];
12735
13581
  let fileContent;
12736
13582
  try {
12737
- fileContent = import_fs18.readFileSync(filePath, "utf-8");
13583
+ fileContent = import_fs19.readFileSync(filePath, "utf-8");
12738
13584
  } catch {
12739
13585
  return [];
12740
13586
  }
@@ -12751,7 +13597,7 @@ function build_read_command(getScanner) {
12751
13597
  const rAbsPath = cache.resolve_absolute(g);
12752
13598
  const rType = rPath ? categorize_asset(rPath) : "unknown";
12753
13599
  const dep = { guid: g, path: rPath, type: rType, depth };
12754
- if (rAbsPath && depth < max_depth && import_fs18.existsSync(rAbsPath)) {
13600
+ if (rAbsPath && depth < max_depth && import_fs19.existsSync(rAbsPath)) {
12755
13601
  const subs = traverse_file(rAbsPath, depth + 1);
12756
13602
  if (subs.length > 0)
12757
13603
  dep.sub_dependencies = subs;
@@ -12780,7 +13626,7 @@ function build_read_command(getScanner) {
12780
13626
  const rDep = { guid: dep.guid, path: dep.path, type: dep.type, depth: 0 };
12781
13627
  if (dep.path) {
12782
13628
  const absPath = cache.resolve_absolute(dep.guid);
12783
- if (absPath && import_fs18.existsSync(absPath)) {
13629
+ if (absPath && import_fs19.existsSync(absPath)) {
12784
13630
  const subs = traverse_file(absPath, 1);
12785
13631
  if (subs.length > 0)
12786
13632
  rDep.sub_dependencies = subs;
@@ -12979,7 +13825,7 @@ function build_read_command(getScanner) {
12979
13825
  });
12980
13826
  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) => {
12981
13827
  try {
12982
- if (!import_fs18.existsSync(file)) {
13828
+ if (!import_fs19.existsSync(file)) {
12983
13829
  console.log(JSON.stringify({ error: `File not found: ${file}` }, null, 2));
12984
13830
  process.exit(1);
12985
13831
  }
@@ -13040,12 +13886,12 @@ function build_read_command(getScanner) {
13040
13886
  if (classId === null && compBlock.class_id === 114) {
13041
13887
  const scriptMatch = compBlock.raw.match(/m_Script:[ \t]*\{[^}]*guid:[ \t]*([a-f0-9]{32})/);
13042
13888
  if (scriptMatch) {
13043
- const project = options.project || find_unity_project_root(import_path8.dirname(file));
13889
+ const project = options.project || find_unity_project_root(import_path11.dirname(file));
13044
13890
  if (project) {
13045
13891
  const cache = load_guid_cache(project);
13046
13892
  if (cache) {
13047
13893
  const scriptPath = cache.resolve(scriptMatch[1]);
13048
- if (scriptPath && import_path8.basename(scriptPath, ".cs").toLowerCase() === component_type.toLowerCase().replace(/\.cs$/, "")) {
13894
+ if (scriptPath && import_path11.basename(scriptPath, ".cs").toLowerCase() === component_type.toLowerCase().replace(/\.cs$/, "")) {
13049
13895
  targetFileId = refId;
13050
13896
  compFound = true;
13051
13897
  break;
@@ -13060,7 +13906,7 @@ function build_read_command(getScanner) {
13060
13906
  process.exit(1);
13061
13907
  }
13062
13908
  } else {
13063
- const project = options.project || find_unity_project_root(import_path8.dirname(file));
13909
+ const project = options.project || find_unity_project_root(import_path11.dirname(file));
13064
13910
  const resolved = resolve_source_prefab(doc, file, project ?? undefined);
13065
13911
  if (resolved) {
13066
13912
  const sourceDoc = UnityDocument.from_file(resolved.source_path);
@@ -13099,7 +13945,7 @@ function build_read_command(getScanner) {
13099
13945
  }
13100
13946
  });
13101
13947
  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) => {
13102
- if (!import_fs18.existsSync(file)) {
13948
+ if (!import_fs19.existsSync(file)) {
13103
13949
  console.log(JSON.stringify({ error: `File not found: ${file}` }, null, 2));
13104
13950
  process.exit(1);
13105
13951
  }
@@ -13135,7 +13981,7 @@ function build_read_command(getScanner) {
13135
13981
  console.log(JSON.stringify({ error: "Native module not available" }, null, 2));
13136
13982
  process.exit(1);
13137
13983
  }
13138
- const projectPath = import_path8.resolve(options.project);
13984
+ const projectPath = import_path11.resolve(options.project);
13139
13985
  const validSources = ["assets", "packages", "dlls", "all"];
13140
13986
  if (!validSources.includes(options.source)) {
13141
13987
  console.log(JSON.stringify({ error: `Invalid --source "${options.source}". Valid values: ${validSources.join(", ")}` }, null, 2));
@@ -13186,15 +14032,15 @@ function build_read_command(getScanner) {
13186
14032
  });
13187
14033
  cmd.command("log").description("Read and filter the Unity Editor.log").option("--path <file>", "Path to Editor.log (auto-detected if omitted)").option("--project <path>", "Filter to log entries from a specific Unity project session").option("--tail <n>", "Show last N lines (default 50)", "50").option("--errors", "Show only error entries").option("--warnings", "Show only warning entries").option("--compile-errors", "Show only C# compilation errors").option("--import-errors", "Show only asset import errors").option("--since <timestamp>", "Filter entries after this timestamp (YYYY-MM-DD or HH:MM:SS)").option("--search <pattern>", "Regex filter on log content").option("-j, --json", "Output as JSON").action((options) => {
13188
14034
  const logPath = options.path || get_editor_log_path();
13189
- if (!logPath || !import_fs18.existsSync(logPath)) {
14035
+ if (!logPath || !import_fs19.existsSync(logPath)) {
13190
14036
  console.log(JSON.stringify({ error: `Editor.log not found${logPath ? `: ${logPath}` : ". Could not detect platform log path."}` }, null, 2));
13191
14037
  process.exit(1);
13192
14038
  }
13193
- const content = import_fs18.readFileSync(logPath, "utf-8");
14039
+ const content = import_fs19.readFileSync(logPath, "utf-8");
13194
14040
  let lines = content.split(/\r?\n/);
13195
14041
  if (options.project) {
13196
- const projectPath = import_path8.resolve(options.project);
13197
- const projectName = import_path8.basename(projectPath);
14042
+ const projectPath = import_path11.resolve(options.project);
14043
+ const projectName = import_path11.basename(projectPath);
13198
14044
  const escapedPath = projectPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13199
14045
  const escapedName = projectName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
13200
14046
  const session_markers = [
@@ -13315,11 +14161,11 @@ function build_read_command(getScanner) {
13315
14161
  });
13316
14162
  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) => {
13317
14163
  const metaPath = file.endsWith(".meta") ? file : `${file}.meta`;
13318
- if (!import_fs18.existsSync(metaPath)) {
14164
+ if (!import_fs19.existsSync(metaPath)) {
13319
14165
  console.log(JSON.stringify({ error: `Meta file not found: ${metaPath}` }, null, 2));
13320
14166
  process.exit(1);
13321
14167
  }
13322
- const content = import_fs18.readFileSync(metaPath, "utf-8");
14168
+ const content = import_fs19.readFileSync(metaPath, "utf-8");
13323
14169
  const lines = content.split(/\r?\n/);
13324
14170
  let guid = "";
13325
14171
  let importer_type = "Unknown";
@@ -13407,7 +14253,7 @@ function build_read_command(getScanner) {
13407
14253
  console.log(JSON.stringify({ error: animValidationError }, null, 2));
13408
14254
  process.exit(1);
13409
14255
  }
13410
- const content = import_fs18.readFileSync(file, "utf-8");
14256
+ const content = import_fs19.readFileSync(file, "utf-8");
13411
14257
  const clip = parse_animation_yaml(content, options.curves === true);
13412
14258
  if (!clip) {
13413
14259
  console.log(JSON.stringify({ error: `No AnimationClip found in "${file}". Is this an .anim file?` }, null, 2));
@@ -13468,7 +14314,7 @@ function build_read_command(getScanner) {
13468
14314
  console.log(JSON.stringify({ error: ctrlValidationError }, null, 2));
13469
14315
  process.exit(1);
13470
14316
  }
13471
- const content = import_fs18.readFileSync(file, "utf-8");
14317
+ const content = import_fs19.readFileSync(file, "utf-8");
13472
14318
  const blocks = split_yaml_blocks(content);
13473
14319
  const controller_block = blocks.find((b) => b.class_id === 91);
13474
14320
  if (!controller_block) {
@@ -13659,7 +14505,7 @@ function build_read_command(getScanner) {
13659
14505
  if (options.summary) {
13660
14506
  console.log(JSON.stringify({
13661
14507
  file,
13662
- name: yaml_field(controller_block.raw, "m_Name") || import_path8.basename(file),
14508
+ name: yaml_field(controller_block.raw, "m_Name") || import_path11.basename(file),
13663
14509
  parameter_count: params.length,
13664
14510
  layer_count: layers.length,
13665
14511
  state_count: states.length,
@@ -13672,7 +14518,7 @@ function build_read_command(getScanner) {
13672
14518
  default_state_names[s.file_id] = s.name;
13673
14519
  const default_out = {
13674
14520
  file,
13675
- name: yaml_field(controller_block.raw, "m_Name") || import_path8.basename(file),
14521
+ name: yaml_field(controller_block.raw, "m_Name") || import_path11.basename(file),
13676
14522
  parameters: params,
13677
14523
  layers: layers.map((l) => l.name),
13678
14524
  states: states.map((s) => ({
@@ -13698,8 +14544,8 @@ function build_read_command(getScanner) {
13698
14544
  process.exit(1);
13699
14545
  }
13700
14546
  const project_path = resolve_project_path(options.project);
13701
- const assetsDir = import_path8.join(import_path8.resolve(project_path), "Assets");
13702
- if (!import_fs18.existsSync(assetsDir)) {
14547
+ const assetsDir = import_path11.join(import_path11.resolve(project_path), "Assets");
14548
+ if (!import_fs19.existsSync(assetsDir)) {
13703
14549
  console.log(JSON.stringify({ error: `Assets directory not found in "${project_path}"` }, null, 2));
13704
14550
  process.exit(1);
13705
14551
  }
@@ -13728,9 +14574,9 @@ function build_read_command(getScanner) {
13728
14574
  const guid_pattern = `guid: ${guid}`;
13729
14575
  for (const f of files) {
13730
14576
  try {
13731
- const fc = import_fs18.readFileSync(f, "utf-8");
14577
+ const fc = import_fs19.readFileSync(f, "utf-8");
13732
14578
  if (fc.includes(guid_pattern)) {
13733
- const rel = import_path8.relative(import_path8.resolve(project_path), f);
14579
+ const rel = import_path11.relative(import_path11.resolve(project_path), f);
13734
14580
  const ftype = categorize_asset(f);
13735
14581
  referencing.push({ path: rel, type: ftype });
13736
14582
  }
@@ -13749,7 +14595,7 @@ function build_read_command(getScanner) {
13749
14595
  by_type[r.type].push(r.path);
13750
14596
  }
13751
14597
  console.log(JSON.stringify({
13752
- project_path: import_path8.resolve(project_path),
14598
+ project_path: import_path11.resolve(project_path),
13753
14599
  guid,
13754
14600
  total_references: filtered.length,
13755
14601
  by_type
@@ -13757,8 +14603,8 @@ function build_read_command(getScanner) {
13757
14603
  });
13758
14604
  cmd.command("unused").description("Find potentially unused assets (zero inbound GUID references)").option("-p, --project <path>", "Unity project path (defaults to cwd)").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((options) => {
13759
14605
  const resolvedProject = resolve_project_path(options.project);
13760
- const assetsDir = import_path8.join(resolvedProject, "Assets");
13761
- if (!import_fs18.existsSync(assetsDir)) {
14606
+ const assetsDir = import_path11.join(resolvedProject, "Assets");
14607
+ if (!import_fs19.existsSync(assetsDir)) {
13762
14608
  console.log(JSON.stringify({ error: `Assets directory not found in "${resolvedProject}"` }, null, 2));
13763
14609
  process.exit(1);
13764
14610
  }
@@ -13786,7 +14632,7 @@ function build_read_command(getScanner) {
13786
14632
  const guid_re = /guid:\s*([a-f0-9]{32})/g;
13787
14633
  for (const f of files) {
13788
14634
  try {
13789
- const fc = import_fs18.readFileSync(f, "utf-8");
14635
+ const fc = import_fs19.readFileSync(f, "utf-8");
13790
14636
  let gm;
13791
14637
  while ((gm = guid_re.exec(fc)) !== null) {
13792
14638
  referenced_guids.add(gm[1]);
@@ -13795,10 +14641,10 @@ function build_read_command(getScanner) {
13795
14641
  continue;
13796
14642
  }
13797
14643
  }
13798
- const buildSettingsPath = import_path8.join(resolvedProject, "ProjectSettings", "EditorBuildSettings.asset");
14644
+ const buildSettingsPath = import_path11.join(resolvedProject, "ProjectSettings", "EditorBuildSettings.asset");
13799
14645
  const build_scene_guids = new Set;
13800
- if (import_fs18.existsSync(buildSettingsPath)) {
13801
- const bsc = import_fs18.readFileSync(buildSettingsPath, "utf-8");
14646
+ if (import_fs19.existsSync(buildSettingsPath)) {
14647
+ const bsc = import_fs19.readFileSync(buildSettingsPath, "utf-8");
13802
14648
  let bm;
13803
14649
  while ((bm = guid_re.exec(bsc)) !== null) {
13804
14650
  build_scene_guids.add(bm[1]);
@@ -13819,7 +14665,7 @@ function build_read_command(getScanner) {
13819
14665
  continue;
13820
14666
  if (ignore_pattern && ignore_pattern.test(asset_path))
13821
14667
  continue;
13822
- if (import_path8.extname(asset_path) === "")
14668
+ if (import_path11.extname(asset_path) === "")
13823
14669
  continue;
13824
14670
  const asset_type = categorize_asset(asset_path);
13825
14671
  if (options.type && asset_type !== options.type)
@@ -13921,8 +14767,8 @@ function build_read_command(getScanner) {
13921
14767
  }
13922
14768
 
13923
14769
  // src/cmd-update.ts
13924
- var import_fs19 = require("fs");
13925
- var import_path9 = require("path");
14770
+ var import_fs20 = require("fs");
14771
+ var import_path12 = require("path");
13926
14772
 
13927
14773
  // src/animator-utils.ts
13928
14774
  function split_yaml_blocks2(content) {
@@ -14051,7 +14897,13 @@ function parseVector(str) {
14051
14897
  }
14052
14898
  return { x: parts[0], y: parts[1], z: parts[2] };
14053
14899
  }
14054
- function resolve_transform_id(scanner, file, identifier) {
14900
+ function resolve_transform_id(scanner, file, identifier, by_id = false) {
14901
+ if (by_id) {
14902
+ if (!/^-?\d+$/.test(identifier)) {
14903
+ return { error: `Invalid fileID: "${identifier}" \u2014 expected a numeric value when using --by-id` };
14904
+ }
14905
+ return { transform_id: identifier };
14906
+ }
14055
14907
  let resolved_id = identifier;
14056
14908
  if (!/^-?\d+$/.test(identifier)) {
14057
14909
  const matches = scanner.find_by_name(file, identifier, false);
@@ -14076,7 +14928,17 @@ function resolve_transform_id(scanner, file, identifier) {
14076
14928
  }
14077
14929
  function build_update_command(getScanner) {
14078
14930
  const cmd = new Command("update").description("Update Unity object properties, transforms, settings, and hierarchy");
14079
- cmd.command("gameobject <file> <object_name> <property> <value>").description("Edit GameObject property value safely").option("-j, --json", "Output as JSON").option("-p, --project <path>", "Unity project path (for tag validation)").action((file, object_name, property, value, options) => {
14931
+ async function check_loaded_edit_protection(file, bypass, project_path) {
14932
+ const guard = await enforce_loaded_edit_protection(file, bypass, project_path);
14933
+ if (guard.allowed)
14934
+ return true;
14935
+ console.log(JSON.stringify({ success: false, file_path: file, error: guard.error }, null, 2));
14936
+ process.exitCode = 1;
14937
+ return false;
14938
+ }
14939
+ cmd.command("gameobject <file> <object_name> <property> <value>").description("Edit GameObject property value safely").option("-j, --json", "Output as JSON").option("-p, --project <path>", "Unity project path (for tag validation)").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, object_name, property, value, options) => {
14940
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection, options.project))
14941
+ return;
14080
14942
  const result = editProperty({
14081
14943
  file_path: file,
14082
14944
  object_name,
@@ -14088,7 +14950,7 @@ function build_update_command(getScanner) {
14088
14950
  if (!result.success)
14089
14951
  process.exitCode = 1;
14090
14952
  });
14091
- 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) => {
14953
+ 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)").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, file_id, property, value, options) => {
14092
14954
  if (property === "m_RootOrder") {
14093
14955
  const num = Number(value);
14094
14956
  if (!Number.isInteger(num) || num < 0) {
@@ -14097,6 +14959,8 @@ function build_update_command(getScanner) {
14097
14959
  return;
14098
14960
  }
14099
14961
  }
14962
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection, options.project))
14963
+ return;
14100
14964
  const result = editComponentByFileId({
14101
14965
  file_path: file,
14102
14966
  file_id,
@@ -14108,7 +14972,7 @@ function build_update_command(getScanner) {
14108
14972
  if (!result.success)
14109
14973
  process.exitCode = 1;
14110
14974
  });
14111
- cmd.command("transform <file> <identifier>").description("Edit Transform by GameObject name or transform fileID").option("-p, --position <x,y,z>", "Set local position").option("-r, --rotation <x,y,z>", "Set local rotation (Euler angles in degrees)").option("-s, --scale <x,y,z>", "Set local scale").option("-j, --json", "Output as JSON").action((file, identifier, options) => {
14975
+ cmd.command("transform <file> <identifier>").description("Edit Transform by GameObject name or transform fileID. Use --by-id to force numeric fileID mode").option("-p, --position <x,y,z>", "Set local position").option("-r, --rotation <x,y,z>", "Set local rotation (Euler angles in degrees)").option("-s, --scale <x,y,z>", "Set local scale").option("--by-id", "Treat identifier as a numeric fileID instead of name lookup").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, identifier, options) => {
14112
14976
  if (!options.position && !options.rotation && !options.scale) {
14113
14977
  console.log(JSON.stringify({
14114
14978
  success: false,
@@ -14118,7 +14982,9 @@ function build_update_command(getScanner) {
14118
14982
  process.exitCode = 1;
14119
14983
  return;
14120
14984
  }
14121
- const resolved = resolve_transform_id(getScanner(), file, identifier);
14985
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
14986
+ return;
14987
+ const resolved = resolve_transform_id(getScanner(), file, identifier, options.byId);
14122
14988
  if ("error" in resolved) {
14123
14989
  console.log(JSON.stringify({
14124
14990
  success: false,
@@ -14227,7 +15093,9 @@ function build_update_command(getScanner) {
14227
15093
  if (!result.success)
14228
15094
  process.exitCode = 1;
14229
15095
  });
14230
- cmd.command("parent <file> <object_name> <new_parent>").description('Move a GameObject under a new parent. Use "root" to move to scene root. Use --by-id to specify fileIDs instead of names').option("-j, --json", "Output as JSON").option("--by-id", "Treat object_name and new_parent as numeric fileIDs instead of names").action((file, object_name, new_parent, options) => {
15096
+ cmd.command("parent <file> <object_name> <new_parent>").description('Move a GameObject under a new parent. Use "root" to move to scene root. Use --by-id to specify fileIDs instead of names').option("-j, --json", "Output as JSON").option("--by-id", "Treat object_name and new_parent as numeric fileIDs instead of names").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, object_name, new_parent, options) => {
15097
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
15098
+ return;
14231
15099
  const result = reparentGameObject({
14232
15100
  file_path: file,
14233
15101
  object_name,
@@ -14239,7 +15107,9 @@ function build_update_command(getScanner) {
14239
15107
  process.exitCode = 1;
14240
15108
  });
14241
15109
  const prefab_cmd = new Command("prefab").description("Prefab operations (unpack, override, remove-override, remove/restore component/gameobject)");
14242
- prefab_cmd.command("unpack <file> <prefab_instance>").description("Unpack a PrefabInstance into standalone GameObjects").option("-p, --project <path>", "Unity project path (for GUID cache lookup)").option("-j, --json", "Output as JSON").action((file, prefab_instance, options) => {
15110
+ prefab_cmd.command("unpack <file> <prefab_instance>").description("Unpack a PrefabInstance into standalone GameObjects").option("-p, --project <path>", "Unity project path (for GUID cache lookup)").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, options) => {
15111
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection, options.project))
15112
+ return;
14243
15113
  const result = unpackPrefab({
14244
15114
  file_path: file,
14245
15115
  prefab_instance,
@@ -14249,7 +15119,9 @@ function build_update_command(getScanner) {
14249
15119
  if (!result.success)
14250
15120
  process.exitCode = 1;
14251
15121
  });
14252
- prefab_cmd.command("override <file> <prefab_instance> <property_path> <value>").description("Edit or add a property override in a PrefabInstance m_Modifications list. Quote paths with brackets or use dot notation (data.0) to avoid shell glob expansion").option("--object-reference <ref>", "Object reference value (default: {fileID: 0})").option("--managed-reference <id>", "Managed reference ID -- placed in value: field, forces objectReference: {fileID: 0}").option("--target <target>", 'Target reference for new entries (e.g., "{fileID: 400000, guid: abc, type: 3}")').option("-j, --json", "Output as JSON").action((file, prefab_instance, property_path, value, options) => {
15122
+ prefab_cmd.command("override <file> <prefab_instance> <property_path> <value>").description("Edit or add a property override in a PrefabInstance m_Modifications list. Quote paths with brackets or use dot notation (data.0) to avoid shell glob expansion").option("--object-reference <ref>", "Object reference value (default: {fileID: 0})").option("--managed-reference <id>", "Managed reference ID -- placed in value: field, forces objectReference: {fileID: 0}").option("--target <target>", 'Target reference for new entries (e.g., "{fileID: 400000, guid: abc, type: 3}")').option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, property_path, value, options) => {
15123
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
15124
+ return;
14253
15125
  if (options.objectReference && !/^\{fileID:/.test(options.objectReference)) {
14254
15126
  console.error(`Warning: --object-reference "${options.objectReference}" does not look like a Unity object reference ({fileID: ...}). For managed reference IDs, use --managed-reference instead or pass the ID as the <value> argument.`);
14255
15127
  }
@@ -14266,7 +15138,9 @@ function build_update_command(getScanner) {
14266
15138
  if (!result.success)
14267
15139
  process.exitCode = 1;
14268
15140
  });
14269
- prefab_cmd.command("batch-overrides <file> <prefab_instance> <edits_json>").description('Batch edit multiple property overrides in a PrefabInstance. JSON format: [{"property_path":"...","value":"...","target":"...","object_reference":"..."}]').option("-j, --json", "Output as JSON").action((file, prefab_instance, edits_json, _options) => {
15141
+ prefab_cmd.command("batch-overrides <file> <prefab_instance> <edits_json>").description('Batch edit multiple property overrides in a PrefabInstance. JSON format: [{"property_path":"...","value":"...","target":"...","object_reference":"..."}]').option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, edits_json, options) => {
15142
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
15143
+ return;
14270
15144
  let raw_edits;
14271
15145
  try {
14272
15146
  const parsed = JSON.parse(edits_json);
@@ -14305,7 +15179,9 @@ function build_update_command(getScanner) {
14305
15179
  if (!result.success)
14306
15180
  process.exitCode = 1;
14307
15181
  });
14308
- prefab_cmd.command("managed-reference <file> <prefab_instance> <field_path> <type_name>").description("Add a [SerializeReference] managed reference to a prefab override. Auto-generates rid and creates type/data/version override entries.").requiredOption("--target <ref>", 'Target reference (e.g., "{fileID: 400000, guid: ..., type: 3}")').option("--index <n>", "Array index (default: 0)", "0").option("-p, --project <path>", "Unity project path (for type registry)").option("-j, --json", "Output as JSON").action((file, prefab_instance, field_path, type_name, options) => {
15182
+ prefab_cmd.command("managed-reference <file> <prefab_instance> <field_path> <type_name>").description("Add a [SerializeReference] managed reference to a prefab override. Auto-generates rid and creates type/data/version override entries.").requiredOption("--target <ref>", 'Target reference (e.g., "{fileID: 400000, guid: ..., type: 3}")').option("--index <n>", "Array index (default: 0)", "0").option("-p, --project <path>", "Unity project path (for type registry)").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, field_path, type_name, options) => {
15183
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection, options.project))
15184
+ return;
14309
15185
  const result = addPrefabManagedReference({
14310
15186
  file_path: file,
14311
15187
  prefab_instance,
@@ -14346,7 +15222,7 @@ function build_update_command(getScanner) {
14346
15222
  process.exitCode = 1;
14347
15223
  }
14348
15224
  });
14349
- cmd.command("array <file> <file_id> <array_property> <action> [args...]").description("Insert, append, or remove array elements in a component. Insert: <index> <value> or <value> --index <n>. Append: <value>. Remove: <index> or --index <n>. Quote paths with brackets or use dot notation (data.0) to avoid shell glob expansion").option("--index <n>", "Index for insert/remove").option("-j, --json", "Output as JSON").action((file, file_id, array_property, action, args, options) => {
15225
+ cmd.command("array <file> <file_id> <array_property> <action> [args...]").description("Insert, append, or remove array elements in a component. Insert: <index> <value> or <value> --index <n>. Append: <value>. Remove: <index> or --index <n>. Quote paths with brackets or use dot notation (data.0) to avoid shell glob expansion").option("--index <n>", "Index for insert/remove").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, file_id, array_property, action, args, options) => {
14350
15226
  if (action !== "insert" && action !== "append" && action !== "remove") {
14351
15227
  console.log(JSON.stringify({ success: false, error: 'Action must be "insert", "append", or "remove"' }, null, 2));
14352
15228
  process.exit(1);
@@ -14387,6 +15263,8 @@ function build_update_command(getScanner) {
14387
15263
  }
14388
15264
  }
14389
15265
  }
15266
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
15267
+ return;
14390
15268
  const result = editArray({
14391
15269
  file_path: file,
14392
15270
  file_id,
@@ -14399,7 +15277,9 @@ function build_update_command(getScanner) {
14399
15277
  if (!result.success)
14400
15278
  process.exitCode = 1;
14401
15279
  });
14402
- cmd.command("batch <file> <edits_json>").description('Batch edit multiple GameObject properties in a single file operation. JSON format: [{"object_name":"...","property":"...","value":"..."}]').option("-j, --json", "Output as JSON").action((file, edits_json, _options) => {
15280
+ cmd.command("batch <file> <edits_json>").description('Batch edit multiple GameObject properties in a single file operation. JSON format: [{"object_name":"...","property":"...","value":"..."}]').option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, edits_json, options) => {
15281
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
15282
+ return;
14403
15283
  let raw_edits;
14404
15284
  try {
14405
15285
  const parsed = JSON.parse(edits_json);
@@ -14440,7 +15320,9 @@ function build_update_command(getScanner) {
14440
15320
  if (!result.success)
14441
15321
  process.exitCode = 1;
14442
15322
  });
14443
- cmd.command("batch-components <file> <edits_json>").description('Batch edit multiple component properties by fileID in a single operation. JSON format: [{"file_id":"...","property":"...","value":"..."}]').option("-j, --json", "Output as JSON").action((file, edits_json, _options) => {
15323
+ cmd.command("batch-components <file> <edits_json>").description('Batch edit multiple component properties by fileID in a single operation. JSON format: [{"file_id":"...","property":"...","value":"..."}]').option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, edits_json, options) => {
15324
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
15325
+ return;
14444
15326
  let raw_edits;
14445
15327
  try {
14446
15328
  const parsed = JSON.parse(edits_json);
@@ -14481,7 +15363,9 @@ function build_update_command(getScanner) {
14481
15363
  if (!result.success)
14482
15364
  process.exitCode = 1;
14483
15365
  });
14484
- prefab_cmd.command("remove-override <file> <prefab_instance> <property_path>").description("Remove a property override from a PrefabInstance. Quote paths with brackets or use dot notation (data.0) to avoid shell glob expansion").option("--target <ref>", "Target reference to match (for disambiguation)").option("-j, --json", "Output as JSON").action((file, prefab_instance, property_path, options) => {
15366
+ prefab_cmd.command("remove-override <file> <prefab_instance> <property_path>").description("Remove a property override from a PrefabInstance. Quote paths with brackets or use dot notation (data.0) to avoid shell glob expansion").option("--target <ref>", "Target reference to match (for disambiguation)").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, property_path, options) => {
15367
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
15368
+ return;
14485
15369
  const result = removePrefabOverride({
14486
15370
  file_path: file,
14487
15371
  prefab_instance,
@@ -14492,7 +15376,9 @@ function build_update_command(getScanner) {
14492
15376
  if (!result.success)
14493
15377
  process.exitCode = 1;
14494
15378
  });
14495
- prefab_cmd.command("remove-component <file> <prefab_instance> <component_ref>").description("Add a component to the PrefabInstance m_RemovedComponents list").option("-j, --json", "Output as JSON").action((file, prefab_instance, component_ref, _options) => {
15379
+ prefab_cmd.command("remove-component <file> <prefab_instance> <component_ref>").description("Add a component to the PrefabInstance m_RemovedComponents list").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, component_ref, options) => {
15380
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
15381
+ return;
14496
15382
  const result = addRemovedComponent({
14497
15383
  file_path: file,
14498
15384
  prefab_instance,
@@ -14502,7 +15388,9 @@ function build_update_command(getScanner) {
14502
15388
  if (!result.success)
14503
15389
  process.exitCode = 1;
14504
15390
  });
14505
- prefab_cmd.command("restore-component <file> <prefab_instance> <component_ref>").description("Remove a component from the PrefabInstance m_RemovedComponents list (restore it)").option("-j, --json", "Output as JSON").action((file, prefab_instance, component_ref, _options) => {
15391
+ prefab_cmd.command("restore-component <file> <prefab_instance> <component_ref>").description("Remove a component from the PrefabInstance m_RemovedComponents list (restore it)").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, component_ref, options) => {
15392
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
15393
+ return;
14506
15394
  const result = removeRemovedComponent({
14507
15395
  file_path: file,
14508
15396
  prefab_instance,
@@ -14512,7 +15400,9 @@ function build_update_command(getScanner) {
14512
15400
  if (!result.success)
14513
15401
  process.exitCode = 1;
14514
15402
  });
14515
- prefab_cmd.command("remove-gameobject <file> <prefab_instance> <gameobject_ref>").description("Add a GameObject to the PrefabInstance m_RemovedGameObjects list").option("-j, --json", "Output as JSON").action((file, prefab_instance, gameobject_ref, _options) => {
15403
+ prefab_cmd.command("remove-gameobject <file> <prefab_instance> <gameobject_ref>").description("Add a GameObject to the PrefabInstance m_RemovedGameObjects list").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, gameobject_ref, options) => {
15404
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
15405
+ return;
14516
15406
  const result = addRemovedGameObject({
14517
15407
  file_path: file,
14518
15408
  prefab_instance,
@@ -14522,7 +15412,9 @@ function build_update_command(getScanner) {
14522
15412
  if (!result.success)
14523
15413
  process.exitCode = 1;
14524
15414
  });
14525
- prefab_cmd.command("restore-gameobject <file> <prefab_instance> <gameobject_ref>").description("Remove a GameObject from the PrefabInstance m_RemovedGameObjects list (restore it)").option("-j, --json", "Output as JSON").action((file, prefab_instance, gameobject_ref, _options) => {
15415
+ prefab_cmd.command("restore-gameobject <file> <prefab_instance> <gameobject_ref>").description("Remove a GameObject from the PrefabInstance m_RemovedGameObjects list (restore it)").option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, prefab_instance, gameobject_ref, options) => {
15416
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
15417
+ return;
14526
15418
  const result = removeRemovedGameObject({
14527
15419
  file_path: file,
14528
15420
  prefab_instance,
@@ -14542,11 +15434,11 @@ function build_update_command(getScanner) {
14542
15434
  }
14543
15435
  }
14544
15436
  }).action((file, options) => {
14545
- if (!import_fs19.existsSync(file)) {
15437
+ if (!import_fs20.existsSync(file)) {
14546
15438
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
14547
15439
  process.exit(1);
14548
15440
  }
14549
- let content = import_fs19.readFileSync(file, "utf-8");
15441
+ let content = import_fs20.readFileSync(file, "utf-8");
14550
15442
  const changes = [];
14551
15443
  if (options.shader) {
14552
15444
  const guid = options.shader;
@@ -14695,7 +15587,7 @@ ${lm[1]} - ${kw}
14695
15587
  process.exitCode = 1;
14696
15588
  return;
14697
15589
  }
14698
- import_fs19.writeFileSync(file, content, "utf-8");
15590
+ import_fs20.writeFileSync(file, content, "utf-8");
14699
15591
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
14700
15592
  });
14701
15593
  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) => {
@@ -14751,14 +15643,14 @@ ${lm[1]} - ${kw}
14751
15643
  const glob_pattern = options.batch;
14752
15644
  const pattern = glob_pattern.endsWith(".meta") ? glob_pattern : `${glob_pattern}.meta`;
14753
15645
  const first_glob = pattern.search(/[*?[\]{}]/);
14754
- const dir_part = first_glob >= 0 ? pattern.slice(0, pattern.lastIndexOf("/", first_glob)) : import_path9.dirname(pattern);
14755
- const dir = import_path9.resolve(dir_part);
14756
- const suffix = import_path9.basename(pattern).replace(/^\*+/, "");
15646
+ const dir_part = first_glob >= 0 ? pattern.slice(0, pattern.lastIndexOf("/", first_glob)) : import_path12.dirname(pattern);
15647
+ const dir = import_path12.resolve(dir_part);
15648
+ const suffix = import_path12.basename(pattern).replace(/^\*+/, "");
14757
15649
  const meta_files = [];
14758
15650
  const scan = (d) => {
14759
- for (const entry of import_fs19.readdirSync(d)) {
14760
- const full = import_path9.join(d, entry);
14761
- if (import_fs19.statSync(full).isDirectory()) {
15651
+ for (const entry of import_fs20.readdirSync(d)) {
15652
+ const full = import_path12.join(d, entry);
15653
+ if (import_fs20.statSync(full).isDirectory()) {
14762
15654
  if (pattern.includes("**"))
14763
15655
  scan(full);
14764
15656
  } else if (entry.endsWith(suffix)) {
@@ -14766,7 +15658,7 @@ ${lm[1]} - ${kw}
14766
15658
  }
14767
15659
  }
14768
15660
  };
14769
- if (import_fs19.existsSync(dir))
15661
+ if (import_fs20.existsSync(dir))
14770
15662
  scan(dir);
14771
15663
  if (meta_files.length === 0) {
14772
15664
  console.log(JSON.stringify({ success: false, error: `No files matched pattern: ${glob_pattern}` }, null, 2));
@@ -14774,7 +15666,7 @@ ${lm[1]} - ${kw}
14774
15666
  }
14775
15667
  const results = [];
14776
15668
  for (const mf of meta_files) {
14777
- let mc = import_fs19.readFileSync(mf, "utf-8");
15669
+ let mc = import_fs20.readFileSync(mf, "utf-8");
14778
15670
  const file_changes = [];
14779
15671
  for (const edit of edits) {
14780
15672
  const re = new RegExp(`^(\\s*${edit.key}:)[ \\t]*.*$`, "m");
@@ -14785,7 +15677,7 @@ ${lm[1]} - ${kw}
14785
15677
  }
14786
15678
  if (file_changes.length > 0) {
14787
15679
  if (!options.dryRun)
14788
- import_fs19.writeFileSync(mf, mc, "utf-8");
15680
+ import_fs20.writeFileSync(mf, mc, "utf-8");
14789
15681
  results.push({ file: mf, changes: file_changes });
14790
15682
  }
14791
15683
  }
@@ -14798,11 +15690,11 @@ ${lm[1]} - ${kw}
14798
15690
  }, null, 2));
14799
15691
  return;
14800
15692
  }
14801
- if (!import_fs19.existsSync(metaPath)) {
15693
+ if (!import_fs20.existsSync(metaPath)) {
14802
15694
  console.log(JSON.stringify({ success: false, error: `Meta file not found: ${metaPath}` }, null, 2));
14803
15695
  process.exit(1);
14804
15696
  }
14805
- let content = import_fs19.readFileSync(metaPath, "utf-8");
15697
+ let content = import_fs20.readFileSync(metaPath, "utf-8");
14806
15698
  const changes = [];
14807
15699
  for (const edit of edits) {
14808
15700
  const re = new RegExp(`^(\\s*${edit.key}:)[ \\t]*.*$`, "m");
@@ -14817,11 +15709,11 @@ ${lm[1]} - ${kw}
14817
15709
  console.log(JSON.stringify({ success: true, dry_run: true, file: metaPath, changes }, null, 2));
14818
15710
  return;
14819
15711
  }
14820
- import_fs19.writeFileSync(metaPath, content, "utf-8");
15712
+ import_fs20.writeFileSync(metaPath, content, "utf-8");
14821
15713
  console.log(JSON.stringify({ success: true, file: metaPath, changes }, null, 2));
14822
15714
  });
14823
15715
  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) => {
14824
- if (!import_fs19.existsSync(file)) {
15716
+ if (!import_fs20.existsSync(file)) {
14825
15717
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
14826
15718
  process.exit(1);
14827
15719
  }
@@ -14829,7 +15721,7 @@ ${lm[1]} - ${kw}
14829
15721
  console.log(JSON.stringify({ success: false, error: `File is not an AnimationClip (.anim): ${file}` }, null, 2));
14830
15722
  process.exit(1);
14831
15723
  }
14832
- let content = import_fs19.readFileSync(file, "utf-8");
15724
+ let content = import_fs20.readFileSync(file, "utf-8");
14833
15725
  const changes = [];
14834
15726
  const property_map = {
14835
15727
  "wrap-mode": "m_WrapMode",
@@ -14990,16 +15882,16 @@ ${event_yaml}
14990
15882
  process.exitCode = 1;
14991
15883
  return;
14992
15884
  }
14993
- import_fs19.writeFileSync(file, content, "utf-8");
15885
+ import_fs20.writeFileSync(file, content, "utf-8");
14994
15886
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
14995
15887
  });
14996
15888
  const PARAM_TYPE_MAP = { float: 1, int: 3, bool: 4, trigger: 9 };
14997
15889
  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) => {
14998
- if (!import_fs19.existsSync(file)) {
15890
+ if (!import_fs20.existsSync(file)) {
14999
15891
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
15000
15892
  process.exit(1);
15001
15893
  }
15002
- let content = import_fs19.readFileSync(file, "utf-8");
15894
+ let content = import_fs20.readFileSync(file, "utf-8");
15003
15895
  const had_crlf = content.includes(`\r
15004
15896
  `);
15005
15897
  if (had_crlf)
@@ -15101,17 +15993,19 @@ $1`);
15101
15993
  if (had_crlf)
15102
15994
  content = content.replace(/\n/g, `\r
15103
15995
  `);
15104
- import_fs19.writeFileSync(file, content, "utf-8");
15996
+ import_fs20.writeFileSync(file, content, "utf-8");
15105
15997
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
15106
15998
  });
15107
- 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) => {
15999
+ 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").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, object_name, index_str, options) => {
15108
16000
  const target_index = parseInt(index_str, 10);
15109
16001
  if (!Number.isFinite(target_index) || target_index < 0) {
15110
16002
  console.log(JSON.stringify({ success: false, error: "Index must be a non-negative integer" }, null, 2));
15111
16003
  process.exitCode = 1;
15112
16004
  return;
15113
16005
  }
15114
- if (!import_fs19.existsSync(file)) {
16006
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection))
16007
+ return;
16008
+ if (!import_fs20.existsSync(file)) {
15115
16009
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
15116
16010
  process.exitCode = 1;
15117
16011
  return;
@@ -15306,7 +16200,7 @@ $1`);
15306
16200
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
15307
16201
  });
15308
16202
  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) => {
15309
- if (!import_fs19.existsSync(file)) {
16203
+ if (!import_fs20.existsSync(file)) {
15310
16204
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
15311
16205
  process.exitCode = 1;
15312
16206
  return;
@@ -15316,7 +16210,7 @@ $1`);
15316
16210
  process.exitCode = 1;
15317
16211
  return;
15318
16212
  }
15319
- let content = import_fs19.readFileSync(file, "utf-8");
16213
+ let content = import_fs20.readFileSync(file, "utf-8");
15320
16214
  const had_crlf = content.includes(`\r
15321
16215
  `);
15322
16216
  if (had_crlf)
@@ -15614,16 +16508,16 @@ ${curve_yaml}`);
15614
16508
  if (had_crlf)
15615
16509
  content = content.replace(/\n/g, `\r
15616
16510
  `);
15617
- import_fs19.writeFileSync(file, content, "utf-8");
16511
+ import_fs20.writeFileSync(file, content, "utf-8");
15618
16512
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
15619
16513
  });
15620
16514
  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) => {
15621
- if (!import_fs19.existsSync(file)) {
16515
+ if (!import_fs20.existsSync(file)) {
15622
16516
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
15623
16517
  process.exitCode = 1;
15624
16518
  return;
15625
16519
  }
15626
- let content = import_fs19.readFileSync(file, "utf-8");
16520
+ let content = import_fs20.readFileSync(file, "utf-8");
15627
16521
  const had_crlf = content.includes(`\r
15628
16522
  `);
15629
16523
  if (had_crlf)
@@ -15962,11 +16856,11 @@ $1`);
15962
16856
  if (had_crlf)
15963
16857
  content = content.replace(/\n/g, `\r
15964
16858
  `);
15965
- import_fs19.writeFileSync(file, content, "utf-8");
16859
+ import_fs20.writeFileSync(file, content, "utf-8");
15966
16860
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
15967
16861
  });
15968
16862
  cmd.addCommand(prefab_cmd);
15969
- cmd.command("managed-reference <file> <component_id> <field_path> <type_name>").description('Add a managed reference (SerializeReference) to a component field. Type can be "Namespace.ClassName" (registry lookup) or "Assembly Namespace.ClassName" (manual).').option("-p, --project <path>", "Unity project path (for type registry)").option("--append", "Append to array (do not update field rid)").option("--properties <json>", `JSON object of initial field values for the data block (e.g. '{"damage": "10"}')`).option("-j, --json", "Output as JSON").action((file, component_id, field_path, type_name, options) => {
16863
+ cmd.command("managed-reference <file> <component_id> <field_path> <type_name>").description('Add a managed reference (SerializeReference) to a component field. Type can be "Namespace.ClassName" (registry lookup) or "Assembly Namespace.ClassName" (manual).').option("-p, --project <path>", "Unity project path (for type registry)").option("--append", "Append to array (do not update field rid)").option("--properties <json>", `JSON object of initial field values for the data block (e.g. '{"damage": "10"}')`).option("-j, --json", "Output as JSON").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").action(async (file, component_id, field_path, type_name, options) => {
15970
16864
  let initial_values;
15971
16865
  if (options.properties) {
15972
16866
  try {
@@ -15977,6 +16871,8 @@ $1`);
15977
16871
  return;
15978
16872
  }
15979
16873
  }
16874
+ if (!await check_loaded_edit_protection(file, options.bypassLoadedProtection, options.project))
16875
+ return;
15980
16876
  const result = editManagedReference({
15981
16877
  file_path: file,
15982
16878
  file_id: component_id,
@@ -15996,7 +16892,13 @@ $1`);
15996
16892
  // src/cmd-delete.ts
15997
16893
  function build_delete_command() {
15998
16894
  const cmd = new Command("delete").description("Delete Unity objects (GameObjects, components)");
15999
- cmd.command("gameobject <file> <object_name>").description("Delete a GameObject and its hierarchy from a Unity file").option("-j, --json", "Output as JSON").action((file, object_name, _options) => {
16895
+ cmd.command("gameobject <file> <object_name>").description("Delete a GameObject and its hierarchy from a Unity file").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (file, object_name, options) => {
16896
+ const guard = await enforce_loaded_edit_protection(file, options.bypassLoadedProtection);
16897
+ if (!guard.allowed) {
16898
+ console.log(JSON.stringify({ success: false, file_path: file, error: guard.error }, null, 2));
16899
+ process.exitCode = 1;
16900
+ return;
16901
+ }
16000
16902
  const result = deleteGameObject({
16001
16903
  file_path: file,
16002
16904
  object_name
@@ -16005,10 +16907,22 @@ function build_delete_command() {
16005
16907
  if (!result.success)
16006
16908
  process.exitCode = 1;
16007
16909
  });
16008
- 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) => {
16910
+ 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("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (file_or_project, component, options) => {
16009
16911
  if (options.all) {
16912
+ const resolvedProjectPath = resolve_project_path(file_or_project);
16913
+ if (!options.bypassLoadedProtection) {
16914
+ const connected = await is_editor_connected_for_project(resolvedProjectPath);
16915
+ if (connected) {
16916
+ console.log(JSON.stringify({
16917
+ success: false,
16918
+ error: "Refusing to run project-wide delete while editor is connected. Use --bypass-loaded-protection to force file-based edits."
16919
+ }, null, 2));
16920
+ process.exitCode = 1;
16921
+ return;
16922
+ }
16923
+ }
16010
16924
  const result = removeComponentBatch({
16011
- project_path: file_or_project,
16925
+ project_path: resolvedProjectPath,
16012
16926
  component_type: component,
16013
16927
  game_object: options.on
16014
16928
  });
@@ -16016,6 +16930,12 @@ function build_delete_command() {
16016
16930
  if (!result.success)
16017
16931
  process.exitCode = 1;
16018
16932
  } else {
16933
+ const guard = await enforce_loaded_edit_protection(file_or_project, options.bypassLoadedProtection, options.project);
16934
+ if (!guard.allowed) {
16935
+ console.log(JSON.stringify({ success: false, file_path: file_or_project, error: guard.error }, null, 2));
16936
+ process.exitCode = 1;
16937
+ return;
16938
+ }
16019
16939
  const result = removeComponent({
16020
16940
  file_path: file_or_project,
16021
16941
  file_id: component,
@@ -16039,7 +16959,13 @@ function build_delete_command() {
16039
16959
  process.exitCode = 1;
16040
16960
  }
16041
16961
  });
16042
- cmd.command("prefab <file> <prefab_instance>").description("Delete a PrefabInstance and all its stripped/added blocks from a Unity file").option("-j, --json", "Output as JSON").action((file, prefab_instance, _options) => {
16962
+ cmd.command("prefab <file> <prefab_instance>").description("Delete a PrefabInstance and all its stripped/added blocks from a Unity file").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (file, prefab_instance, options) => {
16963
+ const guard = await enforce_loaded_edit_protection(file, options.bypassLoadedProtection);
16964
+ if (!guard.allowed) {
16965
+ console.log(JSON.stringify({ success: false, file_path: file, error: guard.error }, null, 2));
16966
+ process.exitCode = 1;
16967
+ return;
16968
+ }
16043
16969
  const result = deletePrefabInstance({
16044
16970
  file_path: file,
16045
16971
  prefab_instance
@@ -16048,6 +16974,18 @@ function build_delete_command() {
16048
16974
  if (!result.success)
16049
16975
  process.exitCode = 1;
16050
16976
  });
16977
+ cmd.command("asset <file>").description("Delete an asset file and its .meta sidecar (missing .meta => warning)").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (file, options) => {
16978
+ const guard = await enforce_loaded_edit_protection(file, options.bypassLoadedProtection);
16979
+ if (!guard.allowed) {
16980
+ console.log(JSON.stringify({ success: false, file_path: file, error: guard.error }, null, 2));
16981
+ process.exitCode = 1;
16982
+ return;
16983
+ }
16984
+ const result = deleteAssetFile({ file_path: file });
16985
+ console.log(JSON.stringify(result, null, 2));
16986
+ if (!result.success)
16987
+ process.exitCode = 1;
16988
+ });
16051
16989
  cmd.command("package <name>").description("Remove a package from Packages/manifest.json").option("-p, --project <path>", "Unity project path (defaults to cwd)").option("-j, --json", "Output as JSON").action((name, options) => {
16052
16990
  try {
16053
16991
  const resolvedProjectPath = resolve_project_path(options.project);
@@ -16067,274 +17005,7 @@ function build_delete_command() {
16067
17005
  }
16068
17006
 
16069
17007
  // src/cmd-editor.ts
16070
- var import_path11 = require("path");
16071
-
16072
- // src/editor-client.ts
16073
- var import_fs20 = require("fs");
16074
- var import_path10 = require("path");
16075
- function read_editor_config(project_path) {
16076
- const config_path = import_path10.join(project_path, ".unity-agentic", "editor.json");
16077
- if (!import_fs20.existsSync(config_path)) {
16078
- return { error: `Editor bridge not found at ${config_path}. Is the Unity Editor running with the bridge package installed?` };
16079
- }
16080
- let config;
16081
- try {
16082
- const raw = import_fs20.readFileSync(config_path, "utf-8");
16083
- config = JSON.parse(raw);
16084
- } catch (err) {
16085
- return { error: `Failed to parse editor.json: ${err instanceof Error ? err.message : String(err)}` };
16086
- }
16087
- if (typeof config.port !== "number" || typeof config.pid !== "number") {
16088
- return { error: "Invalid editor.json: missing port or pid" };
16089
- }
16090
- if (!is_pid_alive(config.pid)) {
16091
- return { error: `Unity Editor process (PID ${config.pid}) is not running. The editor may have been closed.` };
16092
- }
16093
- return config;
16094
- }
16095
- var RETRYABLE_CODES = new Set([-32000, -32002, -32003]);
16096
- var RETRY_DELAYS = [500, 1000, 2000];
16097
- async function call_editor(options) {
16098
- const maxRetries = options.retries ?? 2;
16099
- let lastResponse;
16100
- for (let attempt = 0;attempt <= maxRetries; attempt++) {
16101
- lastResponse = await call_editor_once(options);
16102
- if (!lastResponse.error || !RETRYABLE_CODES.has(lastResponse.error.code)) {
16103
- return lastResponse;
16104
- }
16105
- if (attempt < maxRetries) {
16106
- const delay = RETRY_DELAYS[Math.min(attempt, RETRY_DELAYS.length - 1)];
16107
- await new Promise((r) => setTimeout(r, delay));
16108
- }
16109
- }
16110
- return lastResponse;
16111
- }
16112
- function call_editor_once(options) {
16113
- const { method, params, timeout = 1e4, no_wait } = options;
16114
- const config = resolve_config(options);
16115
- if ("error" in config) {
16116
- return Promise.resolve({
16117
- jsonrpc: "2.0",
16118
- id: "0",
16119
- error: { code: -32000, message: config.error }
16120
- });
16121
- }
16122
- const url = `ws://127.0.0.1:${config.port}/unity-agentic`;
16123
- const request_id = generate_id();
16124
- const wire_params = { ...params };
16125
- if (timeout !== 1e4)
16126
- wire_params._timeout = timeout;
16127
- if (no_wait)
16128
- wire_params.no_wait = true;
16129
- const request = {
16130
- jsonrpc: "2.0",
16131
- id: request_id,
16132
- method,
16133
- ...Object.keys(wire_params).length > 0 ? { params: wire_params } : {}
16134
- };
16135
- return new Promise((resolve7) => {
16136
- let resolved = false;
16137
- let ws;
16138
- const timer = setTimeout(() => {
16139
- if (!resolved) {
16140
- resolved = true;
16141
- try {
16142
- ws?.close();
16143
- } catch {}
16144
- resolve7({
16145
- jsonrpc: "2.0",
16146
- id: request_id,
16147
- error: { code: -32001, message: `Timeout after ${timeout}ms waiting for response to ${method}` }
16148
- });
16149
- }
16150
- }, timeout);
16151
- try {
16152
- ws = new WebSocket(url);
16153
- ws.onopen = () => {
16154
- ws.send(JSON.stringify(request));
16155
- if (no_wait && !resolved) {
16156
- resolved = true;
16157
- clearTimeout(timer);
16158
- setTimeout(() => {
16159
- try {
16160
- ws.close();
16161
- } catch {}
16162
- }, 200);
16163
- resolve7({
16164
- jsonrpc: "2.0",
16165
- id: request_id,
16166
- result: { queued: true }
16167
- });
16168
- }
16169
- };
16170
- ws.onmessage = (event) => {
16171
- try {
16172
- const data = JSON.parse(String(event.data));
16173
- if (data.id === request_id) {
16174
- if (!resolved) {
16175
- resolved = true;
16176
- clearTimeout(timer);
16177
- ws.close();
16178
- resolve7(data);
16179
- }
16180
- }
16181
- } catch {}
16182
- };
16183
- ws.onerror = () => {
16184
- if (!resolved) {
16185
- resolved = true;
16186
- clearTimeout(timer);
16187
- resolve7({
16188
- jsonrpc: "2.0",
16189
- id: request_id,
16190
- error: { code: -32002, message: `WebSocket connection failed to ${url}. Is the Unity Editor running?` }
16191
- });
16192
- }
16193
- };
16194
- ws.onclose = () => {
16195
- if (!resolved) {
16196
- resolved = true;
16197
- clearTimeout(timer);
16198
- resolve7({
16199
- jsonrpc: "2.0",
16200
- id: request_id,
16201
- error: { code: -32003, message: "WebSocket connection closed before response received" }
16202
- });
16203
- }
16204
- };
16205
- } catch (err) {
16206
- if (!resolved) {
16207
- resolved = true;
16208
- clearTimeout(timer);
16209
- resolve7({
16210
- jsonrpc: "2.0",
16211
- id: request_id,
16212
- error: { code: -32002, message: `Failed to connect: ${err instanceof Error ? err.message : String(err)}` }
16213
- });
16214
- }
16215
- }
16216
- });
16217
- }
16218
- async function stream_editor(options) {
16219
- const { method, params, timeout = 30000, on_event } = options;
16220
- const config = resolve_config(options);
16221
- if ("error" in config) {
16222
- throw new Error(config.error);
16223
- }
16224
- const MAX_RECONNECTS = 5;
16225
- let reconnect_count = 0;
16226
- let stopped = false;
16227
- return new Promise((resolve7, reject) => {
16228
- let resolved = false;
16229
- function connect(url) {
16230
- const ws = new WebSocket(url);
16231
- const request_id = generate_id();
16232
- const request = {
16233
- jsonrpc: "2.0",
16234
- id: request_id,
16235
- method,
16236
- ...params ? { params } : {}
16237
- };
16238
- let connected = false;
16239
- const timer = !resolved ? setTimeout(() => {
16240
- if (!connected && !resolved) {
16241
- reject(new Error(`Timeout connecting to ${url}`));
16242
- }
16243
- }, timeout) : null;
16244
- ws.onopen = () => {
16245
- connected = true;
16246
- if (timer)
16247
- clearTimeout(timer);
16248
- reconnect_count = 0;
16249
- ws.send(JSON.stringify(request));
16250
- if (!resolved) {
16251
- resolved = true;
16252
- resolve7({ close: () => {
16253
- stopped = true;
16254
- ws.close();
16255
- } });
16256
- }
16257
- };
16258
- ws.onmessage = (event) => {
16259
- try {
16260
- const data = JSON.parse(String(event.data));
16261
- if ("method" in data && !("id" in data)) {
16262
- on_event(data);
16263
- }
16264
- } catch {}
16265
- };
16266
- ws.onerror = () => {
16267
- if (!connected && !resolved) {
16268
- if (timer)
16269
- clearTimeout(timer);
16270
- reject(new Error(`WebSocket connection failed to ${url}`));
16271
- }
16272
- };
16273
- ws.onclose = () => {
16274
- if (stopped)
16275
- return;
16276
- if (!connected && !resolved)
16277
- return;
16278
- if (reconnect_count >= MAX_RECONNECTS)
16279
- return;
16280
- reconnect_count++;
16281
- const delay = Math.min(500 * reconnect_count, 3000);
16282
- setTimeout(() => {
16283
- if (stopped)
16284
- return;
16285
- const fresh_config = resolve_config(options);
16286
- if ("error" in fresh_config)
16287
- return;
16288
- connect(`ws://127.0.0.1:${fresh_config.port}/unity-agentic`);
16289
- }, delay);
16290
- };
16291
- }
16292
- connect(`ws://127.0.0.1:${config.port}/unity-agentic`);
16293
- });
16294
- }
16295
- async function ping_editor(port, timeout_ms = 2000) {
16296
- return new Promise((resolve7) => {
16297
- const timer = setTimeout(() => {
16298
- resolve7({ reachable: false, error: `Timeout after ${timeout_ms}ms` });
16299
- }, timeout_ms);
16300
- try {
16301
- const ws = new WebSocket(`ws://127.0.0.1:${port}/unity-agentic`);
16302
- ws.onopen = () => {
16303
- clearTimeout(timer);
16304
- try {
16305
- ws.close();
16306
- } catch {}
16307
- resolve7({ reachable: true });
16308
- };
16309
- ws.onerror = (err) => {
16310
- clearTimeout(timer);
16311
- resolve7({ reachable: false, error: String(err) });
16312
- };
16313
- } catch (err) {
16314
- clearTimeout(timer);
16315
- resolve7({ reachable: false, error: String(err) });
16316
- }
16317
- });
16318
- }
16319
- function resolve_config(options) {
16320
- if (options.port) {
16321
- return { port: options.port, pid: 0, version: "unknown" };
16322
- }
16323
- return read_editor_config(options.project_path);
16324
- }
16325
- function is_pid_alive(pid) {
16326
- try {
16327
- process.kill(pid, 0);
16328
- return true;
16329
- } catch {
16330
- return false;
16331
- }
16332
- }
16333
- function generate_id() {
16334
- return `${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
16335
- }
16336
-
16337
- // src/cmd-editor.ts
17008
+ var import_path13 = require("path");
16338
17009
  var BRIDGE_PACKAGE_NAME = "com.unity-agentic-tools.editor-bridge";
16339
17010
  var BRIDGE_PACKAGE_VERSION = "https://github.com/taconotsandwich/unity-agentic-tools.git?path=unity-package";
16340
17011
  function get_common_options(cmd) {
@@ -16352,7 +17023,7 @@ function get_common_options(cmd) {
16352
17023
  port_str = opts.port;
16353
17024
  current = current.parent;
16354
17025
  }
16355
- const project_path = import_path11.resolve(project || process.cwd());
17026
+ const project_path = import_path13.resolve(project || process.cwd());
16356
17027
  const timeout = parseInt(timeout_str || "10000", 10);
16357
17028
  const port = port_str ? parseInt(port_str, 10) : undefined;
16358
17029
  return { project_path, timeout, port };
@@ -16767,7 +17438,13 @@ program.addCommand(build_read_command(getScanner));
16767
17438
  program.addCommand(build_update_command(getScanner));
16768
17439
  program.addCommand(build_delete_command());
16769
17440
  program.addCommand(build_editor_command());
16770
- program.command("clone <file> <object_name>").description("Duplicate a GameObject and its hierarchy").option("-n, --name <new_name>", "Name for the duplicated object").option("-j, --json", "Output as JSON").action((file, object_name, options) => {
17441
+ program.command("clone <file> <object_name>").description("Duplicate a GameObject and its hierarchy").option("-n, --name <new_name>", "Name for the duplicated object").option("--bypass-loaded-protection", "Allow editing files currently loaded in Unity Editor").option("-j, --json", "Output as JSON").action(async (file, object_name, options) => {
17442
+ const guard = await enforce_loaded_edit_protection(file, options.bypassLoadedProtection);
17443
+ if (!guard.allowed) {
17444
+ console.log(JSON.stringify({ success: false, file_path: file, error: guard.error }, null, 2));
17445
+ process.exitCode = 1;
17446
+ return;
17447
+ }
16771
17448
  const result = duplicateGameObject({
16772
17449
  file_path: file,
16773
17450
  object_name,