unity-agentic-tools 0.3.0 → 0.3.2

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
@@ -2170,6 +2170,7 @@ __export(exports_scanner, {
2170
2170
  getNativeExtractCsharpTypes: () => getNativeExtractCsharpTypes,
2171
2171
  getNativeBuildTypeRegistry: () => getNativeBuildTypeRegistry,
2172
2172
  getNativeBuildPackageGuidCache: () => getNativeBuildPackageGuidCache,
2173
+ getNativeBuildLocalPackageGuidCache: () => getNativeBuildLocalPackageGuidCache,
2173
2174
  getNativeBuildGuidCache: () => getNativeBuildGuidCache,
2174
2175
  UnityScanner: () => UnityScanner
2175
2176
  });
@@ -2250,13 +2251,16 @@ function getNativeExtractDllTypes() {
2250
2251
  function getNativeBuildPackageGuidCache() {
2251
2252
  return nativeBuildPackageGuidCache;
2252
2253
  }
2254
+ function getNativeBuildLocalPackageGuidCache() {
2255
+ return nativeBuildLocalPackageGuidCache;
2256
+ }
2253
2257
  function getNativeExtractSerializedFields() {
2254
2258
  return nativeExtractSerializedFields;
2255
2259
  }
2256
2260
  function getNativeExtractDllFields() {
2257
2261
  return nativeExtractDllFields;
2258
2262
  }
2259
- var import_module, import_fs, import_path, __dirname = "/home/runner/work/unity-agentic-tools/unity-agentic-tools/unity-agentic-tools/src", RustScanner = null, nativeModuleError = null, nativeWalkProjectFiles = null, nativeGrepProject = null, nativeBuildGuidCache = null, nativeExtractCsharpTypes = null, nativeBuildTypeRegistry = null, nativeExtractDllTypes = null, nativeBuildPackageGuidCache = null, nativeExtractSerializedFields = null, nativeExtractDllFields = null;
2263
+ var import_module, import_fs, import_path, __dirname = "/home/runner/work/unity-agentic-tools/unity-agentic-tools/unity-agentic-tools/src", RustScanner = null, nativeModuleError = null, nativeWalkProjectFiles = null, nativeGrepProject = null, nativeBuildGuidCache = null, nativeExtractCsharpTypes = null, nativeBuildTypeRegistry = null, nativeExtractDllTypes = null, nativeBuildPackageGuidCache = null, nativeBuildLocalPackageGuidCache = null, nativeExtractSerializedFields = null, nativeExtractDllFields = null;
2260
2264
  var init_scanner = __esm(() => {
2261
2265
  import_module = require("module");
2262
2266
  import_fs = require("fs");
@@ -2280,6 +2284,7 @@ var init_scanner = __esm(() => {
2280
2284
  nativeBuildTypeRegistry = rustModule.buildTypeRegistry || null;
2281
2285
  nativeExtractDllTypes = rustModule.extractDllTypes || null;
2282
2286
  nativeBuildPackageGuidCache = rustModule.buildPackageGuidCache || null;
2287
+ nativeBuildLocalPackageGuidCache = rustModule.buildLocalPackageGuidCache || null;
2283
2288
  nativeExtractSerializedFields = rustModule.extractSerializedFields || null;
2284
2289
  nativeExtractDllFields = rustModule.extractDllFields || null;
2285
2290
  } catch (err) {
@@ -2294,7 +2299,7 @@ var require_package = __commonJS((exports2, module2) => {
2294
2299
  module2.exports = {
2295
2300
  name: "unity-agentic-tools",
2296
2301
  packageManager: "bun@latest",
2297
- version: "0.3.0",
2302
+ version: "0.3.2",
2298
2303
  description: "Fast, token-efficient Unity YAML parser for AI agents",
2299
2304
  exports: {
2300
2305
  ".": {
@@ -2386,6 +2391,7 @@ var CONFIG_DIR = ".unity-agentic";
2386
2391
  var CONFIG_FILE = "config.json";
2387
2392
  var GUID_CACHE_FILE = "guid-cache.json";
2388
2393
  var PACKAGE_CACHE_FILE = "package-cache.json";
2394
+ var LOCAL_PACKAGE_CACHE_FILE = "local-package-cache.json";
2389
2395
  var TYPE_REGISTRY_FILE = "type-registry.json";
2390
2396
  var DOC_INDEX_FILE = "doc-index.json";
2391
2397
  function setup(options = {}) {
@@ -2429,6 +2435,17 @@ function setup(options = {}) {
2429
2435
  packageGuidCount = Object.keys(packageCache).length;
2430
2436
  } catch {}
2431
2437
  }
2438
+ const nativeLocalPkgBuild = getNativeBuildLocalPackageGuidCache();
2439
+ if (nativeLocalPkgBuild) {
2440
+ try {
2441
+ const localPkgCache = nativeLocalPkgBuild(projectPath);
2442
+ const localPkgCount = Object.keys(localPkgCache).length;
2443
+ if (localPkgCount > 0) {
2444
+ const localPkgCachePath = import_path2.join(configPath, LOCAL_PACKAGE_CACHE_FILE);
2445
+ import_fs2.writeFileSync(localPkgCachePath, JSON.stringify(localPkgCache, null, 2));
2446
+ }
2447
+ } catch {}
2448
+ }
2432
2449
  let typeRegistryCreated = false;
2433
2450
  let typeCount;
2434
2451
  const nativeRegistryBuild = getNativeBuildTypeRegistry();
@@ -2578,7 +2595,7 @@ function removeDirectoryRecursive(dir) {
2578
2595
  }
2579
2596
 
2580
2597
  // src/cmd-create.ts
2581
- var import_fs15 = require("fs");
2598
+ var import_fs16 = require("fs");
2582
2599
  var import_path7 = require("path");
2583
2600
  var import_crypto2 = require("crypto");
2584
2601
 
@@ -2622,31 +2639,44 @@ function atomicWrite(filePath, content) {
2622
2639
  };
2623
2640
  }
2624
2641
  }
2625
- const tmpPath = `${filePath}.tmp`;
2642
+ const suffix = `${process.pid}-${Math.random().toString(36).slice(2, 8)}`;
2643
+ const tmpPath = `${filePath}.${suffix}.tmp`;
2644
+ const bakPath = `${filePath}.${suffix}.bak`;
2645
+ let bakCreated = false;
2626
2646
  try {
2627
2647
  import_fs4.writeFileSync(tmpPath, content, "utf-8");
2628
- if (import_fs4.existsSync(filePath)) {
2629
- import_fs4.renameSync(filePath, `${filePath}.bak`);
2630
- }
2631
- import_fs4.renameSync(tmpPath, filePath);
2632
2648
  try {
2633
- if (import_fs4.existsSync(`${filePath}.bak`)) {
2634
- import_fs4.unlinkSync(`${filePath}.bak`);
2649
+ import_fs4.renameSync(filePath, bakPath);
2650
+ bakCreated = true;
2651
+ } catch (err) {
2652
+ if (is_enoent(err)) {
2653
+ bakCreated = false;
2654
+ } else {
2655
+ throw err;
2635
2656
  }
2636
- } catch {}
2657
+ }
2658
+ import_fs4.renameSync(tmpPath, filePath);
2659
+ if (bakCreated) {
2660
+ try {
2661
+ import_fs4.unlinkSync(bakPath);
2662
+ } catch {}
2663
+ }
2637
2664
  return {
2638
2665
  success: true,
2639
2666
  file_path: filePath,
2640
2667
  bytes_written: Buffer.byteLength(content, "utf-8")
2641
2668
  };
2642
2669
  } catch (error) {
2643
- if (import_fs4.existsSync(`${filePath}.bak`)) {
2670
+ if (bakCreated) {
2644
2671
  try {
2645
- import_fs4.renameSync(`${filePath}.bak`, filePath);
2672
+ import_fs4.renameSync(bakPath, filePath);
2646
2673
  } catch (restoreError) {
2647
2674
  console.error("Failed to restore backup:", restoreError);
2648
2675
  }
2649
2676
  }
2677
+ try {
2678
+ import_fs4.unlinkSync(tmpPath);
2679
+ } catch {}
2650
2680
  return {
2651
2681
  success: false,
2652
2682
  file_path: filePath,
@@ -2654,6 +2684,12 @@ function atomicWrite(filePath, content) {
2654
2684
  };
2655
2685
  }
2656
2686
  }
2687
+ function is_enoent(err) {
2688
+ return err instanceof Error && "code" in err && err.code === "ENOENT";
2689
+ }
2690
+ function normalize_property_path(path) {
2691
+ return path.replace(/\.Array\.data\.(\d+)/g, ".Array.data[$1]");
2692
+ }
2657
2693
  function is_match_all(pattern) {
2658
2694
  return pattern === "*" || pattern === "." || pattern === "**" || pattern === ".*";
2659
2695
  }
@@ -3032,6 +3068,31 @@ function extractGuidFromMeta(metaPath) {
3032
3068
  return null;
3033
3069
  }
3034
3070
  }
3071
+ function resolve_source_prefab(doc, file_path, project_path) {
3072
+ const pi_blocks = doc.find_by_class_id(1001);
3073
+ if (pi_blocks.length === 0)
3074
+ return null;
3075
+ const pi_block = pi_blocks[0];
3076
+ const source_match = pi_block.raw.match(/m_SourcePrefab:[ \t]*\{[^}]*guid:[ \t]*([a-f0-9]{32})/);
3077
+ if (!source_match)
3078
+ return null;
3079
+ const source_guid = source_match[1];
3080
+ const resolved_project = project_path || find_unity_project_root(path.dirname(file_path));
3081
+ if (!resolved_project)
3082
+ return null;
3083
+ const cache = load_guid_cache_for_file(file_path, resolved_project);
3084
+ if (!cache)
3085
+ return null;
3086
+ const source_path = cache.resolve_absolute(source_guid);
3087
+ if (!source_path || !import_fs6.existsSync(source_path))
3088
+ return null;
3089
+ return {
3090
+ source_guid,
3091
+ source_path,
3092
+ prefab_instance_id: pi_block.file_id,
3093
+ prefab_instance_block: pi_block
3094
+ };
3095
+ }
3035
3096
  function resolveScriptGuid(script, projectPath) {
3036
3097
  if (/^[a-f0-9]{32}$/i.test(script)) {
3037
3098
  return { guid: script.toLowerCase(), path: null };
@@ -3081,27 +3142,122 @@ function resolveScriptGuid(script, projectPath) {
3081
3142
  return false;
3082
3143
  return true;
3083
3144
  });
3084
- if (matches.length === 1 && matches[0].guid) {
3085
- return { guid: matches[0].guid, path: matches[0].filePath };
3145
+ if (matches.length === 1) {
3146
+ if (matches[0].guid) {
3147
+ return { guid: matches[0].guid, path: matches[0].filePath };
3148
+ }
3149
+ if (matches[0].filePath && projectPath) {
3150
+ const fullFilePath = path.isAbsolute(matches[0].filePath) ? matches[0].filePath : path.join(projectPath, matches[0].filePath);
3151
+ const metaPath = fullFilePath + ".meta";
3152
+ if (import_fs6.existsSync(metaPath)) {
3153
+ const guid = extractGuidFromMeta(metaPath);
3154
+ if (guid)
3155
+ return { guid, path: matches[0].filePath };
3156
+ }
3157
+ if (matches[0].filePath.startsWith("Packages/")) {
3158
+ const pkgPath = path.join(projectPath, matches[0].filePath);
3159
+ const pkgMetaPath = pkgPath + ".meta";
3160
+ if (import_fs6.existsSync(pkgMetaPath)) {
3161
+ const guid = extractGuidFromMeta(pkgMetaPath);
3162
+ if (guid)
3163
+ return { guid, path: matches[0].filePath };
3164
+ }
3165
+ }
3166
+ if (matches[0].filePath?.endsWith(".dll")) {
3167
+ const dllBasename = path.basename(matches[0].filePath).toLowerCase();
3168
+ for (const cacheName of ["package-cache.json", "local-package-cache.json"]) {
3169
+ const cachePath = path.join(projectPath, ".unity-agentic", cacheName);
3170
+ if (!import_fs6.existsSync(cachePath))
3171
+ continue;
3172
+ try {
3173
+ const cache = JSON.parse(import_fs6.readFileSync(cachePath, "utf-8"));
3174
+ for (const [guid, assetPath] of Object.entries(cache)) {
3175
+ if (assetPath.toLowerCase().endsWith(".dll") && path.basename(assetPath).toLowerCase() === dllBasename) {
3176
+ return { guid, path: assetPath };
3177
+ }
3178
+ }
3179
+ } catch {}
3180
+ }
3181
+ }
3182
+ }
3086
3183
  }
3087
3184
  if (matches.length > 1) {
3088
3185
  const withGuid = matches.filter((m) => m.guid);
3089
3186
  if (withGuid.length === 1) {
3090
3187
  return { guid: withGuid[0].guid, path: withGuid[0].filePath };
3091
3188
  }
3189
+ const resolvedFromMeta = matches.filter((m) => !m.guid && m.filePath).map((m) => {
3190
+ const fullPath = path.isAbsolute(m.filePath) ? m.filePath : path.join(projectPath, m.filePath);
3191
+ let guid = import_fs6.existsSync(fullPath + ".meta") ? extractGuidFromMeta(fullPath + ".meta") : null;
3192
+ if (!guid && m.filePath.startsWith("Packages/")) {
3193
+ const pkgPath = path.join(projectPath, m.filePath);
3194
+ if (import_fs6.existsSync(pkgPath + ".meta")) {
3195
+ guid = extractGuidFromMeta(pkgPath + ".meta");
3196
+ }
3197
+ }
3198
+ if (!guid && m.filePath?.endsWith(".dll")) {
3199
+ const dllBase = path.basename(m.filePath).toLowerCase();
3200
+ for (const cn of ["package-cache.json", "local-package-cache.json"]) {
3201
+ const cp = path.join(projectPath, ".unity-agentic", cn);
3202
+ if (!import_fs6.existsSync(cp))
3203
+ continue;
3204
+ try {
3205
+ const c = JSON.parse(import_fs6.readFileSync(cp, "utf-8"));
3206
+ for (const [g, p] of Object.entries(c)) {
3207
+ if (p.toLowerCase().endsWith(".dll") && path.basename(p).toLowerCase() === dllBase) {
3208
+ guid = g;
3209
+ break;
3210
+ }
3211
+ }
3212
+ } catch {}
3213
+ if (guid)
3214
+ break;
3215
+ }
3216
+ }
3217
+ return guid ? { guid, path: m.filePath } : null;
3218
+ }).filter((r) => r !== null);
3219
+ const allResolved = [...withGuid.map((m) => ({ guid: m.guid, path: m.filePath })), ...resolvedFromMeta];
3220
+ if (allResolved.length === 1) {
3221
+ return allResolved[0];
3222
+ }
3223
+ if (allResolved.length > 1) {
3224
+ const pkgCachePaths = [
3225
+ path.join(projectPath, ".unity-agentic", "package-cache.json"),
3226
+ path.join(projectPath, ".unity-agentic", "local-package-cache.json")
3227
+ ];
3228
+ for (const cachePath of pkgCachePaths) {
3229
+ if (!import_fs6.existsSync(cachePath))
3230
+ continue;
3231
+ try {
3232
+ const cache = JSON.parse(import_fs6.readFileSync(cachePath, "utf-8"));
3233
+ const inCache = allResolved.filter((r) => (r.guid in cache));
3234
+ if (inCache.length === 1)
3235
+ return inCache[0];
3236
+ } catch {}
3237
+ }
3238
+ const paths = allResolved.map((m) => m.path).join(", ");
3239
+ throw new Error(`Ambiguous type "${script}": found ${allResolved.length} matches (${paths}). ` + `Use a qualified name (e.g., "Namespace.${targetName}") or provide the full path.`);
3240
+ }
3092
3241
  }
3093
3242
  } catch {}
3094
3243
  }
3095
- const packageCachePath = path.join(projectPath, ".unity-agentic", "package-cache.json");
3096
- if (import_fs6.existsSync(packageCachePath)) {
3244
+ const scriptNameLower = script.toLowerCase().replace(/\.cs$/, "");
3245
+ const dotIdx = scriptNameLower.lastIndexOf(".");
3246
+ const classNameOnly = dotIdx > 0 ? scriptNameLower.substring(dotIdx + 1) : null;
3247
+ const cachePaths = [
3248
+ path.join(projectPath, ".unity-agentic", "package-cache.json"),
3249
+ path.join(projectPath, ".unity-agentic", "local-package-cache.json")
3250
+ ];
3251
+ for (const cachePath of cachePaths) {
3252
+ if (!import_fs6.existsSync(cachePath))
3253
+ continue;
3097
3254
  try {
3098
- const packageCache = JSON.parse(import_fs6.readFileSync(packageCachePath, "utf-8"));
3099
- const scriptNameLower = script.toLowerCase().replace(/\.cs$/, "");
3100
- for (const [guid, assetPath] of Object.entries(packageCache)) {
3255
+ const cache = JSON.parse(import_fs6.readFileSync(cachePath, "utf-8"));
3256
+ for (const [guid, assetPath] of Object.entries(cache)) {
3101
3257
  if (!assetPath.endsWith(".cs"))
3102
3258
  continue;
3103
3259
  const fileName = path.basename(assetPath, ".cs").toLowerCase();
3104
- if (fileName === scriptNameLower) {
3260
+ if (fileName === scriptNameLower || classNameOnly && fileName === classNameOnly) {
3105
3261
  return { guid, path: assetPath };
3106
3262
  }
3107
3263
  }
@@ -3152,6 +3308,8 @@ function resolve_script_with_fields(script, project_path) {
3152
3308
  result.fields = chosen.fields;
3153
3309
  result.base_class = chosen.baseClass ?? undefined;
3154
3310
  result.kind = chosen.kind;
3311
+ result.namespace = chosen.namespace ?? undefined;
3312
+ result.class_name = chosen.name;
3155
3313
  }
3156
3314
  } else {
3157
3315
  result.extraction_error = "Native extractDllFields function not available";
@@ -3347,6 +3505,16 @@ function yaml_default_for_type(csharp_type, version) {
3347
3505
  function generate_field_yaml(fields, version, indent = " ") {
3348
3506
  const lines = [];
3349
3507
  for (const field of fields) {
3508
+ if (field.hasSerializeReference) {
3509
+ const t = field.typeName;
3510
+ if (t.endsWith("[]") || t.startsWith("List<") && t.endsWith(">")) {
3511
+ lines.push(`${indent}${field.name}: []`);
3512
+ } else {
3513
+ lines.push(`${indent}${field.name}:`);
3514
+ lines.push(`${indent} rid: 0`);
3515
+ }
3516
+ continue;
3517
+ }
3350
3518
  const default_value = yaml_default_for_type(field.typeName, version);
3351
3519
  if (default_value === null) {
3352
3520
  continue;
@@ -3390,6 +3558,17 @@ function parse_header(raw) {
3390
3558
  function escape_regex(str) {
3391
3559
  return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
3392
3560
  }
3561
+ function yaml_quote_if_needed(value) {
3562
+ if (value === "")
3563
+ return value;
3564
+ if (value.startsWith("{") && value.endsWith("}") || value.startsWith("[") && value.endsWith("]")) {
3565
+ return value;
3566
+ }
3567
+ if (value.includes(": ") || value.includes(" #") || /^[#\{\[*&!|>'"]/.test(value) || value !== value.trim()) {
3568
+ return `'${value.replace(/'/g, "''")}'`;
3569
+ }
3570
+ return value;
3571
+ }
3393
3572
 
3394
3573
  class UnityBlock {
3395
3574
  _raw;
@@ -3425,6 +3604,7 @@ class UnityBlock {
3425
3604
  return this._dirty;
3426
3605
  }
3427
3606
  get_property(path2) {
3607
+ path2 = normalize_property_path(path2);
3428
3608
  if (path2.includes("Array.data[")) {
3429
3609
  return this._get_array_element(path2);
3430
3610
  }
@@ -3451,13 +3631,17 @@ class UnityBlock {
3451
3631
  return match ? match[1].trim() : null;
3452
3632
  }
3453
3633
  set_property(path2, value, object_reference) {
3634
+ path2 = normalize_property_path(path2);
3454
3635
  const old_raw = this._raw;
3455
3636
  const effective_ref = object_reference && object_reference !== "{fileID: 0}" ? object_reference : undefined;
3456
3637
  if (!path2.includes(".") && !path2.includes("Array")) {
3457
3638
  const prop_pattern = new RegExp(`(^\\s*${escape_regex(path2)}:\\s*)(.*)$`, "m");
3458
3639
  if (prop_pattern.test(this._raw)) {
3459
- const replacement_value = effective_ref ?? value;
3460
- this._raw = this._raw.replace(prop_pattern, `$1${replacement_value}`);
3640
+ const replacement_value = effective_ref ?? yaml_quote_if_needed(value);
3641
+ this._raw = this._raw.replace(prop_pattern, (_match, prefix) => prefix + replacement_value);
3642
+ } else {
3643
+ const insert_value = effective_ref ?? yaml_quote_if_needed(value);
3644
+ this._insert_property(path2, insert_value);
3461
3645
  }
3462
3646
  return this._check_dirty(old_raw);
3463
3647
  }
@@ -3472,8 +3656,8 @@ class UnityBlock {
3472
3656
  const fields = inline_match[2];
3473
3657
  const field_pattern = new RegExp(`(${escape_regex(sub_field)}:\\s*)([^,}]+)`);
3474
3658
  if (field_pattern.test(fields)) {
3475
- const updated_fields = fields.replace(field_pattern, `$1${value}`);
3476
- this._raw = this._raw.replace(inline_pattern, `$1${updated_fields}$3`);
3659
+ const updated_fields = fields.replace(field_pattern, (_m, prefix) => prefix + value);
3660
+ this._raw = this._raw.replace(inline_pattern, (_m, g1, _g2, g3) => g1 + updated_fields + g3);
3477
3661
  inline_applied = true;
3478
3662
  }
3479
3663
  }
@@ -3482,6 +3666,8 @@ class UnityBlock {
3482
3666
  const block_result = this._resolve_block_style_path(parts, effective_value);
3483
3667
  if (block_result !== null) {
3484
3668
  this._raw = block_result;
3669
+ } else if (parts.length === 2) {
3670
+ this._insert_struct_property(parent_prop, sub_field, effective_value);
3485
3671
  }
3486
3672
  }
3487
3673
  return this._check_dirty(old_raw);
@@ -3499,7 +3685,7 @@ class UnityBlock {
3499
3685
  `).filter((l) => l.trim().startsWith("-"));
3500
3686
  if (index < lines.length) {
3501
3687
  const old_line = lines[index];
3502
- const new_line = old_line.replace(/-\s*.*/, `- ${ref_value}`);
3688
+ const new_line = old_line.replace(/-\s*.*/, () => `- ${ref_value}`);
3503
3689
  this._raw = this._raw.replace(old_line, new_line);
3504
3690
  }
3505
3691
  }
@@ -3508,6 +3694,44 @@ class UnityBlock {
3508
3694
  }
3509
3695
  return false;
3510
3696
  }
3697
+ _insert_property(path2, value) {
3698
+ const alt = path2.startsWith("m_") ? path2.slice(2) : "m_" + path2;
3699
+ const alt_pattern = new RegExp(`^\\s*${escape_regex(alt)}:`, "m");
3700
+ if (alt_pattern.test(this._raw)) {
3701
+ return;
3702
+ }
3703
+ const indented_line = ` ${path2}: ${value}`;
3704
+ const eci_pattern = /^([ \t]*m_EditorClassIdentifier:[ \t]*[^\n]*)/m;
3705
+ const eci_match = this._raw.match(eci_pattern);
3706
+ if (eci_match && eci_match.index !== undefined) {
3707
+ const insert_after = eci_match.index + eci_match[0].length;
3708
+ this._raw = this._raw.slice(0, insert_after) + `
3709
+ ` + indented_line + this._raw.slice(insert_after);
3710
+ return;
3711
+ }
3712
+ this._raw = this._raw.trimEnd() + `
3713
+ ` + indented_line + `
3714
+ `;
3715
+ }
3716
+ _insert_struct_property(parent, child, value) {
3717
+ const parent_pattern = new RegExp(`^[ ]*${escape_regex(parent)}:`, "m");
3718
+ if (parent_pattern.test(this._raw)) {
3719
+ return;
3720
+ }
3721
+ const struct_block = ` ${parent}:
3722
+ ${child}: ${value}`;
3723
+ const eci_pattern = /^([ \t]*m_EditorClassIdentifier:[ \t]*[^\n]*)/m;
3724
+ const eci_match = this._raw.match(eci_pattern);
3725
+ if (eci_match && eci_match.index !== undefined) {
3726
+ const insert_after = eci_match.index + eci_match[0].length;
3727
+ this._raw = this._raw.slice(0, insert_after) + `
3728
+ ` + struct_block + this._raw.slice(insert_after);
3729
+ return;
3730
+ }
3731
+ this._raw = this._raw.trimEnd() + `
3732
+ ` + struct_block + `
3733
+ `;
3734
+ }
3511
3735
  has_property(path2) {
3512
3736
  return this.get_property(path2) !== null;
3513
3737
  }
@@ -3563,7 +3787,7 @@ class UnityBlock {
3563
3787
  if (empty_match) {
3564
3788
  const base_indent = empty_match[2];
3565
3789
  const element_indent2 = base_indent + " ";
3566
- this._raw = this._raw.replace(empty_pattern, `$1
3790
+ this._raw = this._raw.replace(empty_pattern, (_m, g1) => `${g1}
3567
3791
  ${element_indent2}- ${value}`);
3568
3792
  return this._check_dirty(old_raw);
3569
3793
  }
@@ -3663,7 +3887,7 @@ ${element_indent2}- ${value}`);
3663
3887
  }
3664
3888
  extract_file_id_refs() {
3665
3889
  const refs = [];
3666
- const pattern = /\{fileID:\s*(\d+)/g;
3890
+ const pattern = /\{fileID:\s*(-?\d+)/g;
3667
3891
  const first_newline = this._raw.indexOf(`
3668
3892
  `);
3669
3893
  const body = first_newline === -1 ? "" : this._raw.slice(first_newline);
@@ -3969,7 +4193,7 @@ class UnityDocument {
3969
4193
  const game_objects = this.find_game_objects_by_name(name);
3970
4194
  const transform_ids = [];
3971
4195
  for (const go of game_objects) {
3972
- const comp_matches = go.raw.matchAll(/component:[ \t]*\{fileID:[ \t]*(\d+)\}/g);
4196
+ const comp_matches = go.raw.matchAll(/component:[ \t]*\{fileID:[ \t]*(-?\d+)\}/g);
3973
4197
  for (const cm of comp_matches) {
3974
4198
  const comp_block = this.find_by_file_id(cm[1]);
3975
4199
  if (comp_block && (comp_block.class_id === 4 || comp_block.class_id === 224)) {
@@ -3981,7 +4205,7 @@ class UnityDocument {
3981
4205
  return transform_ids;
3982
4206
  }
3983
4207
  require_unique_game_object(name_or_id) {
3984
- if (/^\d+$/.test(name_or_id)) {
4208
+ if (/^-?\d+$/.test(name_or_id)) {
3985
4209
  const block = this.find_by_file_id(name_or_id);
3986
4210
  if (!block) {
3987
4211
  return { error: `GameObject with fileID ${name_or_id} not found` };
@@ -4002,7 +4226,7 @@ class UnityDocument {
4002
4226
  return matches[0];
4003
4227
  }
4004
4228
  require_unique_transform(name_or_id) {
4005
- if (/^\d+$/.test(name_or_id)) {
4229
+ if (/^-?\d+$/.test(name_or_id)) {
4006
4230
  const block = this.find_by_file_id(name_or_id);
4007
4231
  if (!block) {
4008
4232
  return { error: `Block with fileID ${name_or_id} not found` };
@@ -4014,7 +4238,7 @@ class UnityDocument {
4014
4238
  return block;
4015
4239
  }
4016
4240
  if (block.class_id === 1) {
4017
- const comp_matches = block.raw.matchAll(/component:[ \t]*\{fileID:[ \t]*(\d+)\}/g);
4241
+ const comp_matches = block.raw.matchAll(/component:[ \t]*\{fileID:[ \t]*(-?\d+)\}/g);
4018
4242
  for (const cm of comp_matches) {
4019
4243
  const comp_block = this.find_by_file_id(cm[1]);
4020
4244
  if (comp_block && (comp_block.class_id === 4 || comp_block.class_id === 224)) {
@@ -4042,7 +4266,7 @@ class UnityDocument {
4042
4266
  find_prefab_root() {
4043
4267
  for (const block of this._blocks) {
4044
4268
  if (block.class_id === 4 && !block.is_stripped && /m_Father:\s*\{fileID:\s*0\}/.test(block.raw)) {
4045
- const go_match = block.raw.match(/m_GameObject:\s*\{fileID:\s*(\d+)\}/);
4269
+ const go_match = block.raw.match(/m_GameObject:\s*\{fileID:\s*(-?\d+)\}/);
4046
4270
  if (go_match) {
4047
4271
  const go_block = this.find_by_file_id(go_match[1]);
4048
4272
  if (go_block) {
@@ -4239,7 +4463,7 @@ class UnityDocument {
4239
4463
  return true;
4240
4464
  }
4241
4465
  if (raw.includes("m_Children:") && !raw.includes(`fileID: ${child_id}`)) {
4242
- raw = raw.replace(/(m_Children:\s*\n(?:\s*-\s*\{fileID:\s*\d+\}\s*\n)*)/, `$1 - {fileID: ${child_id}}
4466
+ raw = raw.replace(/(m_Children:\s*\n(?:\s*-\s*\{fileID:\s*-?\d+\}\s*\n)*)/, `$1 - {fileID: ${child_id}}
4243
4467
  `);
4244
4468
  parent.replace_raw(raw);
4245
4469
  return true;
@@ -4275,7 +4499,7 @@ class UnityDocument {
4275
4499
  if (!children_section)
4276
4500
  return;
4277
4501
  const child_ids = [];
4278
- const child_matches = children_section[0].matchAll(/\{fileID:\s*(\d+)\}/g);
4502
+ const child_matches = children_section[0].matchAll(/\{fileID:\s*(-?\d+)\}/g);
4279
4503
  for (const m of child_matches) {
4280
4504
  if (m[1] !== "0")
4281
4505
  child_ids.push(m[1]);
@@ -4284,13 +4508,13 @@ class UnityDocument {
4284
4508
  result.add(child_transform_id);
4285
4509
  const child_transform = this.find_by_file_id(child_transform_id);
4286
4510
  if (child_transform) {
4287
- const go_match = child_transform.raw.match(/m_GameObject:\s*\{fileID:\s*(\d+)\}/);
4511
+ const go_match = child_transform.raw.match(/m_GameObject:\s*\{fileID:\s*(-?\d+)\}/);
4288
4512
  if (go_match) {
4289
4513
  const go_id = go_match[1];
4290
4514
  result.add(go_id);
4291
4515
  const go_block = this.find_by_file_id(go_id);
4292
4516
  if (go_block) {
4293
- const comp_matches = go_block.raw.matchAll(/component:\s*\{fileID:\s*(\d+)\}/g);
4517
+ const comp_matches = go_block.raw.matchAll(/component:\s*\{fileID:\s*(-?\d+)\}/g);
4294
4518
  for (const cm of comp_matches) {
4295
4519
  result.add(cm[1]);
4296
4520
  }
@@ -4315,7 +4539,7 @@ class UnityDocument {
4315
4539
  return 0;
4316
4540
  const children_match = parent.raw.match(/m_Children:[\s\S]*?(?=\s*m_Father:)/);
4317
4541
  if (children_match) {
4318
- const entries = children_match[0].match(/\{fileID:\s*\d+\}/g);
4542
+ const entries = children_match[0].match(/\{fileID:\s*-?\d+\}/g);
4319
4543
  return entries ? entries.length : 0;
4320
4544
  }
4321
4545
  return 0;
@@ -4350,7 +4574,7 @@ ${new_children}`);
4350
4574
  const transform = this.find_by_file_id(transform_id);
4351
4575
  if (!transform)
4352
4576
  continue;
4353
- const go_match = transform.raw.match(/m_GameObject:\s*\{fileID:\s*(\d+)\}/);
4577
+ const go_match = transform.raw.match(/m_GameObject:\s*\{fileID:\s*(-?\d+)\}/);
4354
4578
  if (!go_match)
4355
4579
  continue;
4356
4580
  const go_id = go_match[1];
@@ -4358,7 +4582,7 @@ ${new_children}`);
4358
4582
  if (!go)
4359
4583
  continue;
4360
4584
  const comp_ids = [];
4361
- const comp_matches = go.raw.matchAll(/component:\s*\{fileID:\s*(\d+)\}/g);
4585
+ const comp_matches = go.raw.matchAll(/component:\s*\{fileID:\s*(-?\d+)\}/g);
4362
4586
  for (const m of comp_matches) {
4363
4587
  comp_ids.push(m[1]);
4364
4588
  }
@@ -4508,6 +4732,20 @@ function get_project_info(projectPath) {
4508
4732
  }
4509
4733
 
4510
4734
  // src/editor/create.ts
4735
+ function compute_dll_script_file_id(namespace, class_name) {
4736
+ const { createHash } = require("crypto");
4737
+ const full_name = namespace ? `${namespace}.${class_name}` : class_name;
4738
+ const name_bytes = Buffer.from(full_name, "utf-8");
4739
+ const input = Buffer.alloc(4 + name_bytes.length);
4740
+ input.writeInt32LE(114, 0);
4741
+ name_bytes.copy(input, 4);
4742
+ const hash = createHash("md4").update(input).digest();
4743
+ const a = hash.readInt32LE(0);
4744
+ const b = hash.readInt32LE(4);
4745
+ const c = hash.readInt32LE(8);
4746
+ const d = hash.readInt32LE(12);
4747
+ return a ^ b ^ c ^ d;
4748
+ }
4511
4749
  function get_layer_from_parent(doc, parentTransformId) {
4512
4750
  if (parentTransformId === "0")
4513
4751
  return 0;
@@ -4523,7 +4761,7 @@ function get_layer_from_parent(doc, parentTransformId) {
4523
4761
  const layerMatch = parentGo.raw.match(/m_Layer:\s*(\d+)/);
4524
4762
  return layerMatch ? parseInt(layerMatch[1], 10) : 0;
4525
4763
  }
4526
- function createGameObjectYAML(gameObjectId, transformId, name, parentTransformId = 0, rootOrder = 0, layer = 0) {
4764
+ function createGameObjectYAML(gameObjectId, transformId, name, parentTransformId = "0", rootOrder = 0, layer = 0) {
4527
4765
  return `--- !u!1 &${gameObjectId}
4528
4766
  GameObject:
4529
4767
  m_ObjectHideFlags: 0
@@ -4584,9 +4822,9 @@ function findStrippedRootTransform(doc, prefabInstanceId) {
4584
4822
  for (const block of doc.blocks) {
4585
4823
  if (block.class_id !== 4 || !block.is_stripped)
4586
4824
  continue;
4587
- const piMatch = block.raw.match(/m_PrefabInstance:\s*\{fileID:\s*(\d+)\}/);
4825
+ const piMatch = block.raw.match(/m_PrefabInstance:[ \t]*\{fileID:[ \t]*(-?\d+)\}/);
4588
4826
  if (piMatch && piMatch[1] === prefabInstanceId) {
4589
- const sourceMatch = block.raw.match(/m_CorrespondingSourceObject:\s*(\{[^}]+\})/);
4827
+ const sourceMatch = block.raw.match(/m_CorrespondingSourceObject:[ \t]*(\{[^}]+\})/);
4590
4828
  if (sourceMatch) {
4591
4829
  return {
4592
4830
  transformId: block.file_id,
@@ -4597,6 +4835,66 @@ function findStrippedRootTransform(doc, prefabInstanceId) {
4597
4835
  }
4598
4836
  return null;
4599
4837
  }
4838
+ function find_game_object_in_variant(doc, name, file_path, project_path) {
4839
+ const pi_blocks = doc.find_by_class_id(1001);
4840
+ for (const pi_block of pi_blocks) {
4841
+ const mod_pattern = /- target:[ \t]*(\{[^}]+\})\s*propertyPath:[ \t]*m_Name\s*value:[ \t]*([^\n]*)/g;
4842
+ let mod_match;
4843
+ while ((mod_match = mod_pattern.exec(pi_block.raw)) !== null) {
4844
+ const target_ref = mod_match[1];
4845
+ const mod_value = mod_match[2].trim();
4846
+ if (mod_value !== name)
4847
+ continue;
4848
+ for (const block of doc.blocks) {
4849
+ if (block.class_id !== 1 || !block.is_stripped)
4850
+ continue;
4851
+ const source_match = block.raw.match(/m_CorrespondingSourceObject:[ \t]*(\{[^}]+\})/);
4852
+ if (!source_match)
4853
+ continue;
4854
+ const normalized_source = source_match[1].replace(/\s+/g, " ");
4855
+ const normalized_target = target_ref.replace(/\s+/g, " ");
4856
+ if (normalized_source === normalized_target) {
4857
+ return {
4858
+ stripped_go_id: block.file_id,
4859
+ source_ref: target_ref,
4860
+ prefab_instance_id: pi_block.file_id
4861
+ };
4862
+ }
4863
+ }
4864
+ }
4865
+ }
4866
+ const source_info = resolve_source_prefab(doc, file_path, project_path);
4867
+ if (!source_info)
4868
+ return null;
4869
+ let source_doc;
4870
+ try {
4871
+ source_doc = UnityDocument.from_file(source_info.source_path);
4872
+ } catch {
4873
+ return null;
4874
+ }
4875
+ const source_gos = source_doc.find_game_objects_by_name(name);
4876
+ if (source_gos.length !== 1)
4877
+ return null;
4878
+ const source_go = source_gos[0];
4879
+ const source_go_file_id = source_go.file_id;
4880
+ const source_guid = source_info.source_guid;
4881
+ for (const block of doc.blocks) {
4882
+ if (block.class_id !== 1 || !block.is_stripped)
4883
+ continue;
4884
+ const source_match = block.raw.match(/m_CorrespondingSourceObject:[ \t]*\{[^}]*fileID:[ \t]*(-?\d+)[^}]*guid:[ \t]*([a-f0-9]{32})/);
4885
+ if (!source_match)
4886
+ continue;
4887
+ if (source_match[1] === source_go_file_id && source_match[2] === source_guid) {
4888
+ const source_ref = `{fileID: ${source_go_file_id}, guid: ${source_guid}, type: 3}`;
4889
+ return {
4890
+ stripped_go_id: block.file_id,
4891
+ source_ref,
4892
+ prefab_instance_id: source_info.prefab_instance_id
4893
+ };
4894
+ }
4895
+ }
4896
+ return null;
4897
+ }
4600
4898
  function appendToAddedGameObjects(doc, piId, targetSourceRef, newGoId) {
4601
4899
  const piBlock = doc.find_by_file_id(piId);
4602
4900
  if (!piBlock)
@@ -4606,13 +4904,34 @@ function appendToAddedGameObjects(doc, piId, targetSourceRef, newGoId) {
4606
4904
  insertIndex: -1
4607
4905
  addedObject: {fileID: ${newGoId}}`;
4608
4906
  let raw = piBlock.raw;
4609
- const emptyPattern = /m_AddedGameObjects:\s*\[\]/;
4907
+ const emptyPattern = /m_AddedGameObjects:[ \t]*\[\]/;
4610
4908
  if (emptyPattern.test(raw)) {
4611
4909
  raw = raw.replace(emptyPattern, `m_AddedGameObjects:${entry}`);
4612
4910
  piBlock.replace_raw(raw);
4613
4911
  return;
4614
4912
  }
4615
- const existingPattern = /(m_AddedGameObjects:\s*\n(?:\s+-[\s\S]*?(?=\n\s+m_|$))*)/;
4913
+ const existingPattern = /(m_AddedGameObjects:[ \t]*\n(?:[ \t]+-[\s\S]*?(?=\n[ \t]+m_|$))*)/;
4914
+ if (existingPattern.test(raw)) {
4915
+ raw = raw.replace(existingPattern, `$1${entry}`);
4916
+ piBlock.replace_raw(raw);
4917
+ }
4918
+ }
4919
+ function appendToAddedComponents(doc, piId, targetSourceRef, newComponentId) {
4920
+ const piBlock = doc.find_by_file_id(piId);
4921
+ if (!piBlock)
4922
+ return;
4923
+ const entry = `
4924
+ - targetCorrespondingSourceObject: ${targetSourceRef}
4925
+ insertIndex: -1
4926
+ addedObject: {fileID: ${newComponentId}}`;
4927
+ let raw = piBlock.raw;
4928
+ const emptyPattern = /m_AddedComponents:[ \t]*\[\]/;
4929
+ if (emptyPattern.test(raw)) {
4930
+ raw = raw.replace(emptyPattern, `m_AddedComponents:${entry}`);
4931
+ piBlock.replace_raw(raw);
4932
+ return;
4933
+ }
4934
+ const existingPattern = /(m_AddedComponents:[ \t]*\n(?:[ \t]+-[\s\S]*?(?=\n[ \t]+m_|$))*)/;
4616
4935
  if (existingPattern.test(raw)) {
4617
4936
  raw = raw.replace(existingPattern, `$1${entry}`);
4618
4937
  piBlock.replace_raw(raw);
@@ -4635,11 +4954,31 @@ var COMPONENT_DEFAULTS = {
4635
4954
  m_Materials:
4636
4955
  - {fileID: 0}`,
4637
4956
  33: ` m_Mesh: {fileID: 0}`,
4957
+ 50: ` m_BodyType: 0
4958
+ m_Mass: 1
4959
+ m_LinearDrag: 0
4960
+ m_AngularDrag: 0.05
4961
+ m_GravityScale: 1
4962
+ m_Interpolate: 0
4963
+ m_SleepingMode: 1
4964
+ m_CollisionDetection: 0`,
4638
4965
  54: ` m_Mass: 1
4639
4966
  m_Drag: 0
4640
4967
  m_AngularDrag: 0.05
4641
4968
  m_UseGravity: 1
4642
4969
  m_IsKinematic: 0`,
4970
+ 58: ` m_IsTrigger: 0
4971
+ m_Material: {fileID: 0}
4972
+ m_Offset: {x: 0, y: 0}
4973
+ m_Radius: 0.5`,
4974
+ 60: ` m_IsTrigger: 0
4975
+ m_Material: {fileID: 0}
4976
+ m_Offset: {x: 0, y: 0}
4977
+ m_Points: []`,
4978
+ 61: ` m_IsTrigger: 0
4979
+ m_Material: {fileID: 0}
4980
+ m_Offset: {x: 0, y: 0}
4981
+ m_Size: {x: 1, y: 1}`,
4643
4982
  64: ` m_IsTrigger: 0
4644
4983
  m_Convex: 0
4645
4984
  m_CookingOptions: 30
@@ -4648,6 +4987,19 @@ var COMPONENT_DEFAULTS = {
4648
4987
  m_Material: {fileID: 0}
4649
4988
  m_Center: {x: 0, y: 0, z: 0}
4650
4989
  m_Size: {x: 1, y: 1, z: 1}`,
4990
+ 66: ` m_IsTrigger: 0
4991
+ m_Material: {fileID: 0}
4992
+ m_Offset: {x: 0, y: 0}
4993
+ m_GeometryType: 0`,
4994
+ 68: ` m_IsTrigger: 0
4995
+ m_Material: {fileID: 0}
4996
+ m_Offset: {x: 0, y: 0}
4997
+ m_Points: []`,
4998
+ 70: ` m_IsTrigger: 0
4999
+ m_Material: {fileID: 0}
5000
+ m_Offset: {x: 0, y: 0}
5001
+ m_Size: {x: 1, y: 1}
5002
+ m_Direction: 0`,
4651
5003
  82: ` m_PlayOnAwake: 1
4652
5004
  m_Volume: 1
4653
5005
  m_Pitch: 1
@@ -4733,9 +5085,14 @@ function addComponentToGameObject(doc, gameObjectId, componentId) {
4733
5085
  `);
4734
5086
  goBlock.replace_raw(raw);
4735
5087
  }
4736
- function createMonoBehaviourYAML(componentId, gameObjectId, scriptGuid, fields, version) {
5088
+ function createMonoBehaviourYAML(componentId, gameObjectId, scriptGuid, fields, version, scriptFileId = 11500000) {
4737
5089
  const field_yaml = fields && fields.length > 0 ? generate_field_yaml(fields, version) : `
4738
5090
  `;
5091
+ const has_serialize_ref = fields?.some((f) => f.hasSerializeReference) ?? false;
5092
+ const references_section = has_serialize_ref ? ` references:
5093
+ version: 2
5094
+ RefIds: []
5095
+ ` : "";
4739
5096
  return `--- !u!114 &${componentId}
4740
5097
  MonoBehaviour:
4741
5098
  m_ObjectHideFlags: 0
@@ -4745,9 +5102,9 @@ MonoBehaviour:
4745
5102
  m_GameObject: {fileID: ${gameObjectId}}
4746
5103
  m_Enabled: 1
4747
5104
  m_EditorHideFlags: 0
4748
- m_Script: {fileID: 11500000, guid: ${scriptGuid}, type: 3}
5105
+ m_Script: {fileID: ${scriptFileId}, guid: ${scriptGuid}, type: 3}
4749
5106
  m_Name:
4750
- m_EditorClassIdentifier:${field_yaml}`;
5107
+ m_EditorClassIdentifier:${field_yaml}${references_section}`;
4751
5108
  }
4752
5109
  function createGameObject(options) {
4753
5110
  const { file_path, name, parent } = options;
@@ -4846,9 +5203,9 @@ function createGameObject(options) {
4846
5203
  const layer = get_layer_from_parent(doc, parentTransformIdStr);
4847
5204
  const gameObjectIdStr = doc.generate_file_id();
4848
5205
  const transformIdStr = doc.generate_file_id();
4849
- const gameObjectId = parseInt(gameObjectIdStr, 10);
4850
- const transformId = parseInt(transformIdStr, 10);
4851
- const parentTransformId = parseInt(parentTransformIdStr, 10);
5206
+ const gameObjectId = gameObjectIdStr;
5207
+ const transformId = transformIdStr;
5208
+ const parentTransformId = parentTransformIdStr;
4852
5209
  const newBlocks = createGameObjectYAML(gameObjectId, transformId, name.trim(), parentTransformId, rootOrder, layer);
4853
5210
  doc.append_raw(newBlocks);
4854
5211
  if (parentTransformIdStr !== "0" && !variantPiId) {
@@ -4870,7 +5227,7 @@ function createGameObject(options) {
4870
5227
  file_path,
4871
5228
  game_object_id: gameObjectId,
4872
5229
  transform_id: transformId,
4873
- prefab_instance_id: variantPiId ? parseInt(variantPiId, 10) : undefined
5230
+ prefab_instance_id: variantPiId || undefined
4874
5231
  };
4875
5232
  }
4876
5233
  function createScene(options) {
@@ -5316,9 +5673,9 @@ function createPrefabVariant(options) {
5316
5673
  const tempDoc = UnityDocument.from_string(`%YAML 1.1
5317
5674
  %TAG !u! tag:unity3d.com,2011:
5318
5675
  `);
5319
- const prefabInstanceId = parseInt(tempDoc.generate_file_id(), 10);
5320
- const strippedGoId = parseInt(tempDoc.generate_file_id(), 10);
5321
- const strippedTransformId = parseInt(tempDoc.generate_file_id(), 10);
5676
+ const prefabInstanceId = tempDoc.generate_file_id();
5677
+ const strippedGoId = tempDoc.generate_file_id();
5678
+ const strippedTransformId = tempDoc.generate_file_id();
5322
5679
  const finalName = variant_name || `${rootInfo.name} Variant`;
5323
5680
  const variantYaml = `%YAML 1.1
5324
5681
  %TAG !u! tag:unity3d.com,2011:
@@ -5434,7 +5791,7 @@ function createScriptableObject(options) {
5434
5791
  const baseName = path3.basename(output_path, ".asset");
5435
5792
  const field_yaml = resolved.fields && resolved.fields.length > 0 ? generate_field_yaml(resolved.fields, version) : `
5436
5793
  `;
5437
- const assetYaml = `%YAML 1.1
5794
+ let assetYaml = `%YAML 1.1
5438
5795
  %TAG !u! tag:unity3d.com,2011:
5439
5796
  --- !u!114 &11400000
5440
5797
  MonoBehaviour:
@@ -5448,6 +5805,16 @@ MonoBehaviour:
5448
5805
  m_Script: {fileID: 11500000, guid: ${resolved.guid}, type: 3}
5449
5806
  m_Name: ${baseName}
5450
5807
  m_EditorClassIdentifier:${field_yaml}`;
5808
+ if (options.initial_values && Object.keys(options.initial_values).length > 0) {
5809
+ const doc = UnityDocument.from_string(assetYaml);
5810
+ const block = doc.blocks[0];
5811
+ if (block) {
5812
+ for (const [key, val] of Object.entries(options.initial_values)) {
5813
+ block.set_property(key, val);
5814
+ }
5815
+ assetYaml = doc.serialize();
5816
+ }
5817
+ }
5451
5818
  try {
5452
5819
  import_fs8.writeFileSync(output_path, assetYaml, "utf-8");
5453
5820
  } catch (err) {
@@ -5528,6 +5895,80 @@ MonoImporter:
5528
5895
  guid
5529
5896
  };
5530
5897
  }
5898
+ var COMPONENT_BASE_CLASSES = new Set([
5899
+ "MonoBehaviour",
5900
+ "NetworkBehaviour",
5901
+ "StateMachineBehaviour"
5902
+ ]);
5903
+ function is_valid_component_base(base_class, project_path, depth = 0) {
5904
+ if (depth > 5)
5905
+ return false;
5906
+ if (COMPONENT_BASE_CLASSES.has(base_class))
5907
+ return true;
5908
+ if (!project_path)
5909
+ return false;
5910
+ const registryPath = path3.join(project_path, ".unity-agentic", "type-registry.json");
5911
+ if (import_fs8.existsSync(registryPath)) {
5912
+ try {
5913
+ const registry = JSON.parse(import_fs8.readFileSync(registryPath, "utf-8"));
5914
+ const match = registry.find((t) => t.name === base_class && t.kind === "class");
5915
+ if (match?.filePath) {
5916
+ const fullPath = path3.isAbsolute(match.filePath) ? match.filePath : path3.join(project_path, match.filePath);
5917
+ if (fullPath.endsWith(".cs") && import_fs8.existsSync(fullPath)) {
5918
+ const { getNativeExtractSerializedFields: getNativeExtractSerializedFields2 } = (init_scanner(), __toCommonJS(exports_scanner));
5919
+ const extract = getNativeExtractSerializedFields2();
5920
+ if (extract) {
5921
+ const typeInfos = extract(fullPath);
5922
+ const info = typeInfos?.find((t) => t.name === base_class);
5923
+ if (info?.baseClass) {
5924
+ return is_valid_component_base(info.baseClass, project_path, depth + 1);
5925
+ }
5926
+ }
5927
+ }
5928
+ if (!fullPath.endsWith(".cs") || !import_fs8.existsSync(fullPath)) {
5929
+ if (match.kind === "class") {
5930
+ return is_likely_component_base_name(base_class);
5931
+ }
5932
+ }
5933
+ }
5934
+ } catch {}
5935
+ }
5936
+ const cachePaths = [
5937
+ path3.join(project_path, ".unity-agentic", "package-cache.json"),
5938
+ path3.join(project_path, ".unity-agentic", "local-package-cache.json")
5939
+ ];
5940
+ for (const cachePath of cachePaths) {
5941
+ if (!import_fs8.existsSync(cachePath))
5942
+ continue;
5943
+ try {
5944
+ const cache = JSON.parse(import_fs8.readFileSync(cachePath, "utf-8"));
5945
+ const baseClassLower = base_class.toLowerCase();
5946
+ for (const [, assetPath] of Object.entries(cache)) {
5947
+ if (!assetPath.endsWith(".cs"))
5948
+ continue;
5949
+ if (path3.basename(assetPath, ".cs").toLowerCase() !== baseClassLower)
5950
+ continue;
5951
+ const fullPath = path3.join(project_path, assetPath);
5952
+ if (!import_fs8.existsSync(fullPath))
5953
+ continue;
5954
+ const { getNativeExtractSerializedFields: getNativeExtractSerializedFields2 } = (init_scanner(), __toCommonJS(exports_scanner));
5955
+ const extract = getNativeExtractSerializedFields2();
5956
+ if (!extract)
5957
+ continue;
5958
+ const typeInfos = extract(fullPath);
5959
+ const info = typeInfos?.find((t) => t.name === base_class);
5960
+ if (info?.baseClass) {
5961
+ return is_valid_component_base(info.baseClass, project_path, depth + 1);
5962
+ }
5963
+ }
5964
+ } catch {}
5965
+ }
5966
+ return false;
5967
+ }
5968
+ function is_likely_component_base_name(name) {
5969
+ const lower = name.toLowerCase();
5970
+ return lower.endsWith("behaviour") || lower.endsWith("behavior") || lower.includes("monobehaviour") || lower.includes("networkbehaviour");
5971
+ }
5531
5972
  function addComponent(options) {
5532
5973
  const { file_path, game_object_name, component_type } = options;
5533
5974
  const project_path = options.project_path || find_unity_project_root(path3.dirname(file_path)) || undefined;
@@ -5553,11 +5994,19 @@ function addComponent(options) {
5553
5994
  };
5554
5995
  }
5555
5996
  const goResult = doc.require_unique_game_object(game_object_name);
5997
+ let gameObjectIdStr;
5998
+ let variantInfo = null;
5556
5999
  if ("error" in goResult) {
5557
- return { success: false, file_path, error: goResult.error };
6000
+ const vResult = find_game_object_in_variant(doc, game_object_name, file_path, project_path);
6001
+ if (!vResult) {
6002
+ return { success: false, file_path, error: goResult.error };
6003
+ }
6004
+ gameObjectIdStr = vResult.stripped_go_id;
6005
+ variantInfo = { source_ref: vResult.source_ref, prefab_instance_id: vResult.prefab_instance_id };
6006
+ } else {
6007
+ gameObjectIdStr = goResult.file_id;
5558
6008
  }
5559
- const gameObjectIdStr = goResult.file_id;
5560
- const gameObjectId = parseInt(gameObjectIdStr, 10);
6009
+ const gameObjectId = gameObjectIdStr;
5561
6010
  const classId = get_class_id(component_type);
5562
6011
  let duplicateWarning;
5563
6012
  const goBlock = doc.find_by_file_id(gameObjectIdStr);
@@ -5572,7 +6021,7 @@ function addComponent(options) {
5572
6021
  }
5573
6022
  }
5574
6023
  const componentIdStr = doc.generate_file_id();
5575
- const componentId = parseInt(componentIdStr, 10);
6024
+ const componentId = componentIdStr;
5576
6025
  let componentYAML;
5577
6026
  let scriptGuid;
5578
6027
  let scriptPath;
@@ -5581,7 +6030,16 @@ function addComponent(options) {
5581
6030
  const componentName = UNITY_CLASS_IDS[classId] || component_type;
5582
6031
  componentYAML = createGenericComponentYAML(componentName, classId, componentId, gameObjectId);
5583
6032
  } else {
5584
- const resolved = resolve_script_with_fields(component_type, project_path);
6033
+ let resolved;
6034
+ try {
6035
+ resolved = resolve_script_with_fields(component_type, project_path);
6036
+ } catch (e) {
6037
+ return {
6038
+ success: false,
6039
+ file_path,
6040
+ error: e instanceof Error ? e.message : String(e)
6041
+ };
6042
+ }
5585
6043
  if (!resolved) {
5586
6044
  const hints = [];
5587
6045
  if (project_path) {
@@ -5608,11 +6066,11 @@ function addComponent(options) {
5608
6066
  error: `"${component_type}" is ${resolved.kind === "enum" ? "an enum" : "an interface"}, not a MonoBehaviour. Cannot add as a component.`
5609
6067
  };
5610
6068
  }
5611
- if (resolved.base_class && !["MonoBehaviour", "NetworkBehaviour", "StateMachineBehaviour"].includes(resolved.base_class)) {
6069
+ if (resolved.base_class && !COMPONENT_BASE_CLASSES.has(resolved.base_class) && !is_valid_component_base(resolved.base_class, project_path)) {
5612
6070
  return {
5613
6071
  success: false,
5614
6072
  file_path,
5615
- error: `"${component_type}" extends ${resolved.base_class}, not MonoBehaviour. Cannot add as a component.`
6073
+ error: `"${component_type}" extends ${resolved.base_class}, which does not inherit from MonoBehaviour. Cannot add as a component. If this is incorrect, ensure the type registry is up to date ("setup" command).`
5616
6074
  };
5617
6075
  }
5618
6076
  let version;
@@ -5621,12 +6079,20 @@ function addComponent(options) {
5621
6079
  version = read_project_version(project_path);
5622
6080
  } catch {}
5623
6081
  }
5624
- componentYAML = createMonoBehaviourYAML(componentId, gameObjectId, resolved.guid, resolved.fields, version);
6082
+ let scriptFileId = 11500000;
6083
+ if (resolved.path?.endsWith(".dll") && resolved.class_name) {
6084
+ scriptFileId = compute_dll_script_file_id(resolved.namespace ?? "", resolved.class_name);
6085
+ }
6086
+ componentYAML = createMonoBehaviourYAML(componentId, gameObjectId, resolved.guid, resolved.fields, version, scriptFileId);
5625
6087
  scriptGuid = resolved.guid;
5626
6088
  scriptPath = resolved.path || undefined;
5627
6089
  extractionError = resolved.extraction_error;
5628
6090
  }
5629
- addComponentToGameObject(doc, gameObjectIdStr, componentIdStr);
6091
+ if (variantInfo) {
6092
+ appendToAddedComponents(doc, variantInfo.prefab_instance_id, variantInfo.source_ref, componentIdStr);
6093
+ } else {
6094
+ addComponentToGameObject(doc, gameObjectIdStr, componentIdStr);
6095
+ }
5630
6096
  doc.append_raw(componentYAML);
5631
6097
  const saveResult = doc.save();
5632
6098
  if (!saveResult.success) {
@@ -5703,6 +6169,7 @@ function copyComponent(options) {
5703
6169
  }
5704
6170
  // src/editor/update.ts
5705
6171
  var import_fs10 = require("fs");
6172
+ var import_fs11 = require("fs");
5706
6173
 
5707
6174
  // src/settings.ts
5708
6175
  var import_fs9 = require("fs");
@@ -6309,6 +6776,14 @@ function edit_sorting_layer(options) {
6309
6776
  }
6310
6777
 
6311
6778
  // src/editor/update.ts
6779
+ function enrich_target_ref(targetRef, blockRaw) {
6780
+ if (/guid:/.test(targetRef))
6781
+ return targetRef;
6782
+ const sourceMatch = blockRaw.match(/m_SourcePrefab:[ \t]*\{[^}]*guid:[ \t]*([a-f0-9]{32})/);
6783
+ if (!sourceMatch)
6784
+ return null;
6785
+ return targetRef.replace(/\}$/, `, guid: ${sourceMatch[1]}, type: 3}`);
6786
+ }
6312
6787
  function eulerToQuaternion(euler) {
6313
6788
  const deg2rad = Math.PI / 180;
6314
6789
  const x = euler.x * deg2rad;
@@ -6331,6 +6806,9 @@ function validate_value_type(current_value, new_value) {
6331
6806
  const current = current_value.trim();
6332
6807
  const incoming = new_value.trim();
6333
6808
  if (/^\{fileID:/.test(current)) {
6809
+ if (/^\{fileID:[ \t]*0[ \t]*\}$/.test(current)) {
6810
+ return null;
6811
+ }
6334
6812
  if (!/^\{fileID:/.test(incoming)) {
6335
6813
  return `Expected a reference value ({fileID: ...}), got "${incoming}"`;
6336
6814
  }
@@ -6382,7 +6860,7 @@ function isAncestor(doc, childTransformId, candidateAncestorTransformId) {
6382
6860
  const block = doc.find_by_file_id(currentId);
6383
6861
  if (!block || block.class_id !== 4)
6384
6862
  break;
6385
- const fatherMatch = block.raw.match(/m_Father:\s*\{fileID:\s*(\d+)\}/);
6863
+ const fatherMatch = block.raw.match(/m_Father:\s*\{fileID:\s*(-?\d+)\}/);
6386
6864
  currentId = fatherMatch ? fatherMatch[1] : "0";
6387
6865
  }
6388
6866
  return false;
@@ -6392,7 +6870,7 @@ function resolveTransformByGameObjectId(doc, gameObjectFileId) {
6392
6870
  if (!found || found.class_id !== 1) {
6393
6871
  return { error: `GameObject with fileID ${gameObjectFileId} not found` };
6394
6872
  }
6395
- const componentMatch = found.raw.match(/m_Component:\s*\n\s*-\s*component:\s*\{fileID:\s*(\d+)\}/);
6873
+ const componentMatch = found.raw.match(/m_Component:\s*\n\s*-\s*component:\s*\{fileID:\s*(-?\d+)\}/);
6396
6874
  if (!componentMatch) {
6397
6875
  return { error: `GameObject fileID ${gameObjectFileId} has no Transform component` };
6398
6876
  }
@@ -6477,7 +6955,7 @@ function safeUnityYAMLEdit(filePath, objectName, propertyName, newValue, project
6477
6955
  newValue = "0";
6478
6956
  }
6479
6957
  let targetBlock = null;
6480
- if (/^\d+$/.test(objectName)) {
6958
+ if (/^-?\d+$/.test(objectName)) {
6481
6959
  targetBlock = doc.find_by_file_id(objectName);
6482
6960
  if (!targetBlock) {
6483
6961
  return {
@@ -6515,10 +6993,10 @@ function safeUnityYAMLEdit(filePath, objectName, propertyName, newValue, project
6515
6993
  const propertyPattern = new RegExp(`(^\\s*m_${normalizedProperty}:\\s*)([^\\n]*)`, "m");
6516
6994
  let updatedRaw = targetBlock.raw;
6517
6995
  if (propertyPattern.test(updatedRaw)) {
6518
- updatedRaw = updatedRaw.replace(propertyPattern, `$1${newValue}`);
6996
+ updatedRaw = updatedRaw.replace(propertyPattern, (_m, prefix) => prefix + newValue);
6519
6997
  } else {
6520
- updatedRaw = updatedRaw.replace(/(\n)(--- !u!|$)/, `
6521
- m_${normalizedProperty}: ${newValue}$1$2`);
6998
+ updatedRaw = updatedRaw.replace(/(\n)(--- !u!|$)/, (_m, g1, g2) => `
6999
+ m_${normalizedProperty}: ${newValue}${g1}${g2}`);
6522
7000
  }
6523
7001
  targetBlock.replace_raw(updatedRaw);
6524
7002
  const saveResult = doc.save();
@@ -6593,7 +7071,7 @@ function editComponentByFileId(options) {
6593
7071
  };
6594
7072
  }
6595
7073
  const classId = targetBlock.class_id;
6596
- const sameFileRefMatch = new_value.match(/^\{fileID:\s*(\d+)\}$/);
7074
+ const sameFileRefMatch = new_value.match(/^\{fileID:\s*(-?\d+)\}$/);
6597
7075
  if (sameFileRefMatch) {
6598
7076
  const refId = sameFileRefMatch[1];
6599
7077
  if (refId !== "0" && !doc.find_by_file_id(refId)) {
@@ -6604,7 +7082,9 @@ function editComponentByFileId(options) {
6604
7082
  };
6605
7083
  }
6606
7084
  }
6607
- const currentValue = targetBlock.get_property(exactProperty) ?? (exactProperty !== prefixedProperty ? targetBlock.get_property(prefixedProperty) : null);
7085
+ const exactValue = targetBlock.get_property(exactProperty);
7086
+ const resolvedProperty = exactValue !== null ? exactProperty : exactProperty !== prefixedProperty && targetBlock.get_property(prefixedProperty) !== null ? prefixedProperty : null;
7087
+ const currentValue = exactValue ?? (resolvedProperty === prefixedProperty ? targetBlock.get_property(prefixedProperty) : null);
6608
7088
  if (currentValue !== null) {
6609
7089
  const typeError = validate_value_type(currentValue, new_value);
6610
7090
  if (typeError) {
@@ -6615,9 +7095,14 @@ function editComponentByFileId(options) {
6615
7095
  };
6616
7096
  }
6617
7097
  }
6618
- let modified = targetBlock.set_property(exactProperty, new_value, "{fileID: 0}");
6619
- if (!modified && exactProperty !== prefixedProperty) {
6620
- modified = targetBlock.set_property(prefixedProperty, new_value, "{fileID: 0}");
7098
+ let modified;
7099
+ if (resolvedProperty) {
7100
+ modified = targetBlock.set_property(resolvedProperty, new_value, "{fileID: 0}");
7101
+ } else {
7102
+ modified = targetBlock.set_property(exactProperty, new_value, "{fileID: 0}");
7103
+ if (!modified && exactProperty !== prefixedProperty) {
7104
+ modified = targetBlock.set_property(prefixedProperty, new_value, "{fileID: 0}");
7105
+ }
6621
7106
  }
6622
7107
  if (!modified) {
6623
7108
  if (currentValue !== null) {
@@ -6654,8 +7139,10 @@ function editComponentByFileId(options) {
6654
7139
  };
6655
7140
  }
6656
7141
  function editPrefabOverride(options) {
6657
- const { file_path, prefab_instance, property_path, new_value, object_reference, target } = options;
6658
- const objRef = object_reference ?? "{fileID: 0}";
7142
+ const { file_path, prefab_instance, new_value, object_reference, target, managed_reference } = options;
7143
+ const property_path = /[\[\]]/.test(options.property_path) && !options.property_path.startsWith("'") ? `'${options.property_path}'` : options.property_path;
7144
+ const effectiveValue = managed_reference ?? new_value;
7145
+ const objRef = managed_reference ? "{fileID: 0}" : object_reference ?? "{fileID: 0}";
6659
7146
  if (!import_fs10.existsSync(file_path)) {
6660
7147
  return { success: false, file_path, error: `File not found: ${file_path}` };
6661
7148
  }
@@ -6669,11 +7156,16 @@ function editPrefabOverride(options) {
6669
7156
  if (!targetBlock) {
6670
7157
  return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
6671
7158
  }
6672
- const escapedPath = property_path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
6673
- const entryPattern = new RegExp(`(- target:\\s*\\{[^}]+\\}\\s*\\n\\s*propertyPath:\\s*)${escapedPath}(\\s*\\n\\s*value:\\s*)(.*)(\\s*\\n\\s*objectReference:\\s*)(.*)`, "m");
7159
+ const unquotedPath = property_path.replace(/^'|'$/g, "");
7160
+ const quotedPath = `'${unquotedPath}'`;
7161
+ const escapedUnquoted = unquotedPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7162
+ const escapedQuoted = quotedPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7163
+ const pathAlternation = escapedUnquoted === escapedQuoted ? escapedUnquoted : `(?:${escapedQuoted}|${escapedUnquoted})`;
7164
+ const entryPattern = new RegExp(`(- target:\\s*\\{[^}]+\\}\\s*\\n\\s*propertyPath:\\s*)${pathAlternation}(\\s*\\n\\s*value:\\s*)(.*)(\\s*\\n\\s*objectReference:\\s*)(.*)`, "m");
6674
7165
  const entryMatch = targetBlock.raw.match(entryPattern);
6675
7166
  if (entryMatch) {
6676
- const updatedText2 = targetBlock.raw.replace(entryPattern, `$1${property_path}$2${new_value}$4${objRef}`);
7167
+ const quotedValue = yaml_quote_if_needed(effectiveValue);
7168
+ const updatedText2 = targetBlock.raw.replace(entryPattern, (_m, g1, g2, _g3, g4) => g1 + property_path + g2 + quotedValue + g4 + objRef);
6677
7169
  targetBlock.replace_raw(updatedText2);
6678
7170
  const saveResult2 = doc.save();
6679
7171
  if (!saveResult2.success) {
@@ -6710,9 +7202,13 @@ function editPrefabOverride(options) {
6710
7202
  error: `Invalid target reference "${targetRef}". Expected format: {fileID: N, guid: ..., type: T}`
6711
7203
  };
6712
7204
  }
7205
+ const enriched = enrich_target_ref(targetRef, targetBlock.raw);
7206
+ if (enriched)
7207
+ targetRef = enriched;
7208
+ const quotedNewValue = yaml_quote_if_needed(effectiveValue);
6713
7209
  const newEntry = ` - target: ${targetRef}
6714
7210
  propertyPath: ${property_path}
6715
- value: ${new_value}
7211
+ value: ${quotedNewValue}
6716
7212
  objectReference: ${objRef}`;
6717
7213
  const removedPattern = /(\n\s*m_RemovedComponents:)/m;
6718
7214
  const removedMatch = targetBlock.raw.match(removedPattern);
@@ -6759,6 +7255,118 @@ ${newEntry}$1`);
6759
7255
  action: "added"
6760
7256
  };
6761
7257
  }
7258
+ function batchEditPrefabOverrides(file_path, prefab_instance, edits) {
7259
+ if (!import_fs10.existsSync(file_path)) {
7260
+ return { success: false, file_path, error: `File not found: ${file_path}` };
7261
+ }
7262
+ let doc;
7263
+ try {
7264
+ doc = UnityDocument.from_file(file_path);
7265
+ } catch (err) {
7266
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
7267
+ }
7268
+ const targetBlock = findPrefabInstanceBlock(doc, prefab_instance);
7269
+ if (!targetBlock) {
7270
+ return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
7271
+ }
7272
+ const actions = [];
7273
+ for (const edit of edits) {
7274
+ const { value, object_reference, target, managed_reference } = edit;
7275
+ const property_path = /[\[\]]/.test(edit.property_path) && !edit.property_path.startsWith("'") ? `'${edit.property_path}'` : edit.property_path;
7276
+ const effectiveValue = managed_reference ?? value;
7277
+ const objRef = managed_reference ? "{fileID: 0}" : object_reference ?? "{fileID: 0}";
7278
+ const unquotedPath = property_path.replace(/^'|'$/g, "");
7279
+ const quotedPathStr = `'${unquotedPath}'`;
7280
+ const batchEscapedUnquoted = unquotedPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7281
+ const batchEscapedQuoted = quotedPathStr.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7282
+ const batchPathAlt = batchEscapedUnquoted === batchEscapedQuoted ? batchEscapedUnquoted : `(?:${batchEscapedQuoted}|${batchEscapedUnquoted})`;
7283
+ const entryPattern = new RegExp(`(- target:\\s*\\{[^}]+\\}\\s*\\n\\s*propertyPath:\\s*)${batchPathAlt}(\\s*\\n\\s*value:\\s*)(.*)(\\s*\\n\\s*objectReference:\\s*)(.*)`, "m");
7284
+ const entryMatch = targetBlock.raw.match(entryPattern);
7285
+ if (entryMatch) {
7286
+ const quotedValue = yaml_quote_if_needed(effectiveValue);
7287
+ const updatedText = targetBlock.raw.replace(entryPattern, (_m, g1, g2, _g3, g4) => g1 + property_path + g2 + quotedValue + g4 + objRef);
7288
+ targetBlock.replace_raw(updatedText);
7289
+ actions.push({ property_path, action: "updated" });
7290
+ } else {
7291
+ let targetRef = target;
7292
+ if (!targetRef) {
7293
+ const rootProp = property_path.split(".")[0];
7294
+ const siblingPattern = new RegExp(`- target:\\s*(\\{[^}]+\\})\\s*\\n\\s*propertyPath:\\s*${rootProp.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}`, "m");
7295
+ const siblingMatch = targetBlock.raw.match(siblingPattern);
7296
+ if (siblingMatch) {
7297
+ targetRef = siblingMatch[1];
7298
+ }
7299
+ }
7300
+ if (!targetRef) {
7301
+ return {
7302
+ success: false,
7303
+ file_path,
7304
+ error: `Cannot infer target for new override "${property_path}". Provide "target" in the edit entry (e.g., "{fileID: 400000, guid: ..., type: 3}").`
7305
+ };
7306
+ }
7307
+ if (!/^\{fileID:\s*-?\d+/.test(targetRef)) {
7308
+ return {
7309
+ success: false,
7310
+ file_path,
7311
+ error: `Invalid target reference "${targetRef}" for "${property_path}". Expected format: {fileID: N, guid: ..., type: T}`
7312
+ };
7313
+ }
7314
+ const enriched = enrich_target_ref(targetRef, targetBlock.raw);
7315
+ if (enriched)
7316
+ targetRef = enriched;
7317
+ const quotedNewValue = yaml_quote_if_needed(effectiveValue);
7318
+ const newEntry = ` - target: ${targetRef}
7319
+ propertyPath: ${property_path}
7320
+ value: ${quotedNewValue}
7321
+ objectReference: ${objRef}`;
7322
+ const removedPattern = /(\n\s*m_RemovedComponents:)/m;
7323
+ const removedMatch = targetBlock.raw.match(removedPattern);
7324
+ let updatedText;
7325
+ if (removedMatch) {
7326
+ updatedText = targetBlock.raw.replace(removedPattern, `
7327
+ ${newEntry}$1`);
7328
+ } else {
7329
+ const lines = targetBlock.raw.split(`
7330
+ `);
7331
+ let lastObjRefIdx = -1;
7332
+ let inModifications = false;
7333
+ for (let i = 0;i < lines.length; i++) {
7334
+ if (/m_Modifications:/.test(lines[i])) {
7335
+ inModifications = true;
7336
+ continue;
7337
+ }
7338
+ if (inModifications && /^\s*objectReference:/.test(lines[i])) {
7339
+ lastObjRefIdx = i;
7340
+ }
7341
+ if (inModifications && /^\s*m_\w+:/.test(lines[i]) && !/objectReference/.test(lines[i]) && !/propertyPath/.test(lines[i])) {
7342
+ if (lastObjRefIdx !== -1)
7343
+ break;
7344
+ }
7345
+ }
7346
+ if (lastObjRefIdx !== -1) {
7347
+ lines.splice(lastObjRefIdx + 1, 0, newEntry);
7348
+ updatedText = lines.join(`
7349
+ `);
7350
+ } else {
7351
+ return { success: false, file_path, error: `Could not find insertion point in m_Modifications for "${property_path}"` };
7352
+ }
7353
+ }
7354
+ targetBlock.replace_raw(updatedText);
7355
+ actions.push({ property_path, action: "added" });
7356
+ }
7357
+ }
7358
+ const saveResult = doc.save();
7359
+ if (!saveResult.success) {
7360
+ return { success: false, file_path, error: saveResult.error };
7361
+ }
7362
+ return {
7363
+ success: true,
7364
+ file_path,
7365
+ prefab_instance_id: prefab_instance,
7366
+ applied: actions.length,
7367
+ actions
7368
+ };
7369
+ }
6762
7370
  function editTransform(options) {
6763
7371
  const { file_path, transform_id, position, rotation, scale } = options;
6764
7372
  const pathError = validate_file_path(file_path, "write");
@@ -6855,10 +7463,10 @@ function batchEditProperties(filePath, edits) {
6855
7463
  const propertyPattern = new RegExp(`(^\\s*m_${normalizedProperty}:\\s*)([^\\n]*)`, "m");
6856
7464
  let updatedRaw = targetBlock.raw;
6857
7465
  if (propertyPattern.test(updatedRaw)) {
6858
- updatedRaw = updatedRaw.replace(propertyPattern, `$1${edit.new_value}`);
7466
+ updatedRaw = updatedRaw.replace(propertyPattern, (_m, prefix) => prefix + edit.new_value);
6859
7467
  } else {
6860
- updatedRaw = updatedRaw.replace(/(\n)(--- !u!|$)/, `
6861
- m_${normalizedProperty}: ${edit.new_value}$1$2`);
7468
+ updatedRaw = updatedRaw.replace(/(\n)(--- !u!|$)/, (_m, g1, g2) => `
7469
+ m_${normalizedProperty}: ${edit.new_value}${g1}${g2}`);
6862
7470
  }
6863
7471
  targetBlock.replace_raw(updatedRaw);
6864
7472
  }
@@ -6888,7 +7496,7 @@ function reparentGameObject(options) {
6888
7496
  return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
6889
7497
  }
6890
7498
  let childTransformId;
6891
- if (by_id || /^\d+$/.test(object_name)) {
7499
+ if (by_id || /^-?\d+$/.test(object_name)) {
6892
7500
  if (isNaN(parseInt(object_name, 10))) {
6893
7501
  return { success: false, file_path, error: `Invalid fileID: "${object_name}" \u2014 expected a numeric value` };
6894
7502
  }
@@ -6908,11 +7516,11 @@ function reparentGameObject(options) {
6908
7516
  if (!childBlock) {
6909
7517
  return { success: false, file_path, error: `Transform ${childTransformId} not found` };
6910
7518
  }
6911
- const fatherMatch = childBlock.raw.match(/m_Father:\s*\{fileID:\s*(\d+)\}/);
7519
+ const fatherMatch = childBlock.raw.match(/m_Father:\s*\{fileID:\s*(-?\d+)\}/);
6912
7520
  const oldParentTransformId = fatherMatch ? fatherMatch[1] : "0";
6913
7521
  let newParentTransformId = "0";
6914
7522
  if (new_parent.toLowerCase() !== "root") {
6915
- if (by_id || /^\d+$/.test(new_parent)) {
7523
+ if (by_id || /^-?\d+$/.test(new_parent)) {
6916
7524
  if (isNaN(parseInt(new_parent, 10))) {
6917
7525
  return { success: false, file_path, error: `Invalid parent fileID: "${new_parent}" \u2014 expected a numeric value, or "root"` };
6918
7526
  }
@@ -6963,9 +7571,9 @@ function reparentGameObject(options) {
6963
7571
  return {
6964
7572
  success: true,
6965
7573
  file_path,
6966
- child_transform_id: parseInt(childTransformId, 10),
6967
- old_parent_transform_id: parseInt(oldParentTransformId, 10),
6968
- new_parent_transform_id: parseInt(newParentTransformId, 10)
7574
+ child_transform_id: childTransformId,
7575
+ old_parent_transform_id: oldParentTransformId,
7576
+ new_parent_transform_id: newParentTransformId
6969
7577
  };
6970
7578
  }
6971
7579
  function findPrefabInstanceBlock(doc, identifier) {
@@ -7320,39 +7928,267 @@ function removeRemovedGameObject(options) {
7320
7928
  } catch (err) {
7321
7929
  return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
7322
7930
  }
7323
- const targetBlock = findPrefabInstanceBlock(doc, prefab_instance);
7324
- if (!targetBlock) {
7325
- return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
7931
+ const targetBlock = findPrefabInstanceBlock(doc, prefab_instance);
7932
+ if (!targetBlock) {
7933
+ return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
7934
+ }
7935
+ const escapedRef = component_ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7936
+ const refPattern = new RegExp(`\\s*- ${escapedRef}\\n?`, "m");
7937
+ if (!refPattern.test(targetBlock.raw)) {
7938
+ return { success: false, file_path, error: `GameObject reference "${component_ref}" not found in m_RemovedGameObjects` };
7939
+ }
7940
+ const updatedRaw = targetBlock.raw.replace(refPattern, "");
7941
+ targetBlock.replace_raw(updatedRaw);
7942
+ if (!doc.validate()) {
7943
+ return { success: false, file_path, error: "Validation failed after removing GameObject" };
7944
+ }
7945
+ const restoreGoSaveResult = doc.save();
7946
+ if (!restoreGoSaveResult.success) {
7947
+ return { success: false, file_path, error: restoreGoSaveResult.error };
7948
+ }
7949
+ return {
7950
+ success: true,
7951
+ file_path,
7952
+ prefab_instance_id: targetBlock.file_id
7953
+ };
7954
+ }
7955
+ function generate_managed_reference_id(existing_raw, count = 1) {
7956
+ const { randomBytes } = require("crypto");
7957
+ const existingIds = new Set;
7958
+ const idPattern = /\b(\d{1,19})\b/g;
7959
+ for (const line of existing_raw.split(`
7960
+ `)) {
7961
+ if (line.includes("managedReferences[") || line.includes("rid:") || line.includes("objectReference:") && /\d{10,}/.test(line) || line.includes("value:") && /\d{16,}/.test(line)) {
7962
+ let m;
7963
+ while ((m = idPattern.exec(line)) !== null) {
7964
+ const val = BigInt(m[1]);
7965
+ if (val > 0n)
7966
+ existingIds.add(val);
7967
+ }
7968
+ }
7969
+ }
7970
+ const buf = randomBytes(8);
7971
+ const raw64 = buf.readBigUInt64BE();
7972
+ const mask46 = (1n << 46n) - 1n;
7973
+ let base = (raw64 & mask46 | 1n) << 18n;
7974
+ const INT64_MAX = 9223372036854775807n;
7975
+ if (base > INT64_MAX)
7976
+ base = base & INT64_MAX >> 18n << 18n;
7977
+ if (base <= 0n)
7978
+ base = 1n << 18n;
7979
+ const results = [];
7980
+ for (let i = 0;i < count; i++) {
7981
+ let id = base + BigInt(i);
7982
+ while (id <= 0n || id > INT64_MAX || existingIds.has(id)) {
7983
+ id += 1n;
7984
+ }
7985
+ existingIds.add(id);
7986
+ results.push(id.toString());
7987
+ }
7988
+ return results;
7989
+ }
7990
+ function addPrefabManagedReference(options) {
7991
+ const { file_path, prefab_instance, field_path, type_name, target, project_path } = options;
7992
+ const index = options.index ?? 0;
7993
+ if (!import_fs10.existsSync(file_path)) {
7994
+ return { success: false, file_path, error: `File not found: ${file_path}` };
7995
+ }
7996
+ const typeInfo = resolve_managed_type(type_name, project_path);
7997
+ let finalTypeInfo;
7998
+ if (typeInfo) {
7999
+ finalTypeInfo = typeInfo;
8000
+ } else {
8001
+ const spaceIdx = type_name.indexOf(" ");
8002
+ if (spaceIdx > 0) {
8003
+ const asm = type_name.substring(0, spaceIdx);
8004
+ const fullType = type_name.substring(spaceIdx + 1);
8005
+ const lastDot = fullType.lastIndexOf(".");
8006
+ finalTypeInfo = {
8007
+ assembly: asm,
8008
+ namespace: lastDot >= 0 ? fullType.substring(0, lastDot) : "",
8009
+ class_name: lastDot >= 0 ? fullType.substring(lastDot + 1) : fullType
8010
+ };
8011
+ } else {
8012
+ return {
8013
+ success: false,
8014
+ file_path,
8015
+ error: `Type "${type_name}" not found in type registry. Use "Assembly Namespace.ClassName" format or run "setup" to rebuild.`
8016
+ };
8017
+ }
8018
+ }
8019
+ let doc;
8020
+ try {
8021
+ doc = UnityDocument.from_file(file_path);
8022
+ } catch (err) {
8023
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
8024
+ }
8025
+ const piBlock = findPrefabInstanceBlock(doc, prefab_instance);
8026
+ if (!piBlock) {
8027
+ return { success: false, file_path, error: `PrefabInstance "${prefab_instance}" not found` };
8028
+ }
8029
+ const [rid] = generate_managed_reference_id(piBlock.raw);
8030
+ const typeValue = finalTypeInfo.namespace ? `${finalTypeInfo.assembly} ${finalTypeInfo.namespace}.${finalTypeInfo.class_name}` : `${finalTypeInfo.assembly} ${finalTypeInfo.class_name}`;
8031
+ const basePath = field_path.replace(/\.Array$/, "");
8032
+ const overrides = [
8033
+ {
8034
+ property_path: `${basePath}.Array.data[${index}].managedReferenceType`,
8035
+ value: typeValue,
8036
+ target
8037
+ },
8038
+ {
8039
+ property_path: `${basePath}.Array.data[${index}].managedReferenceValue`,
8040
+ value: rid,
8041
+ target
8042
+ },
8043
+ {
8044
+ property_path: `managedReferences[-2]`,
8045
+ value: "2",
8046
+ target
8047
+ }
8048
+ ];
8049
+ const result = batchEditPrefabOverrides(file_path, prefab_instance, overrides);
8050
+ if (!result.success) {
8051
+ return { success: false, file_path, error: result.error };
8052
+ }
8053
+ return {
8054
+ success: true,
8055
+ file_path,
8056
+ rid,
8057
+ type_info: finalTypeInfo,
8058
+ overrides_created: overrides.map((o) => o.property_path)
8059
+ };
8060
+ }
8061
+ function resolve_managed_type(type_name, project_path) {
8062
+ if (!project_path)
8063
+ return null;
8064
+ const { join: join10 } = require("path");
8065
+ const registryPath = join10(project_path, ".unity-agentic", "type-registry.json");
8066
+ if (!import_fs10.existsSync(registryPath))
8067
+ return null;
8068
+ try {
8069
+ const registry = JSON.parse(import_fs11.readFileSync(registryPath, "utf-8"));
8070
+ let targetName = type_name;
8071
+ let targetNs = null;
8072
+ const dotIndex = type_name.lastIndexOf(".");
8073
+ if (dotIndex > 0) {
8074
+ targetNs = type_name.substring(0, dotIndex);
8075
+ targetName = type_name.substring(dotIndex + 1);
8076
+ }
8077
+ const matches = registry.filter((t) => {
8078
+ if (t.name.toLowerCase() !== targetName.toLowerCase())
8079
+ return false;
8080
+ if (targetNs && t.namespace?.toLowerCase() !== targetNs.toLowerCase())
8081
+ return false;
8082
+ return true;
8083
+ });
8084
+ if (matches.length === 0)
8085
+ return null;
8086
+ const match = matches[0];
8087
+ let assembly = "Assembly-CSharp";
8088
+ if (match.filePath && (match.filePath.includes("Packages/") || match.filePath.includes("PackageCache/"))) {
8089
+ const pkgMatch = match.filePath.match(/(?:Packages|PackageCache)\/([^/]+)/);
8090
+ if (pkgMatch)
8091
+ assembly = pkgMatch[1].replace(/@.*$/, "");
8092
+ }
8093
+ return { class_name: match.name, namespace: match.namespace || "", assembly };
8094
+ } catch {
8095
+ return null;
8096
+ }
8097
+ }
8098
+ function editManagedReference(options) {
8099
+ const { file_path, file_id, field_path, type_name, project_path, append, initial_values } = options;
8100
+ if (!import_fs10.existsSync(file_path)) {
8101
+ return { success: false, file_path, error: `File not found: ${file_path}` };
8102
+ }
8103
+ let doc;
8104
+ try {
8105
+ doc = UnityDocument.from_file(file_path);
8106
+ } catch (err) {
8107
+ return { success: false, file_path, error: `Failed to read file: ${err instanceof Error ? err.message : String(err)}` };
8108
+ }
8109
+ const block = doc.find_by_file_id(file_id);
8110
+ if (!block) {
8111
+ return { success: false, file_path, error: `Component with fileID ${file_id} not found` };
8112
+ }
8113
+ if (block.class_id !== 114) {
8114
+ return { success: false, file_path, error: `fileID ${file_id} is not a MonoBehaviour (class ${block.class_id}). Managed references only apply to MonoBehaviours.` };
8115
+ }
8116
+ const typeInfo = resolve_managed_type(type_name, project_path);
8117
+ if (!typeInfo) {
8118
+ const spaceIdx = type_name.indexOf(" ");
8119
+ if (spaceIdx > 0) {
8120
+ const asm = type_name.substring(0, spaceIdx);
8121
+ const fullType = type_name.substring(spaceIdx + 1);
8122
+ const lastDot = fullType.lastIndexOf(".");
8123
+ const manual = {
8124
+ assembly: asm,
8125
+ namespace: lastDot >= 0 ? fullType.substring(0, lastDot) : "",
8126
+ class_name: lastDot >= 0 ? fullType.substring(lastDot + 1) : fullType
8127
+ };
8128
+ return applyManagedReference(doc, block, file_path, field_path, manual, append, initial_values);
8129
+ }
8130
+ return {
8131
+ success: false,
8132
+ file_path,
8133
+ error: `Type "${type_name}" not found in type registry. Use "Assembly Namespace.ClassName" format or run "setup" to rebuild.`
8134
+ };
7326
8135
  }
7327
- const escapedRef = component_ref.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
7328
- const refPattern = new RegExp(`\\s*- ${escapedRef}\\n?`, "m");
7329
- if (!refPattern.test(targetBlock.raw)) {
7330
- return { success: false, file_path, error: `GameObject reference "${component_ref}" not found in m_RemovedGameObjects` };
8136
+ return applyManagedReference(doc, block, file_path, field_path, typeInfo, append, initial_values);
8137
+ }
8138
+ function applyManagedReference(doc, block, file_path, field_path, typeInfo, append, initial_values) {
8139
+ let raw = block.raw;
8140
+ const ridMatches = [...raw.matchAll(/rid:[ \t]*(\d+)/g)];
8141
+ const existingRids = ridMatches.map((m) => parseInt(m[1], 10)).filter((n) => !isNaN(n));
8142
+ const nextRid = existingRids.length > 0 ? Math.max(...existingRids) + 1 : 1;
8143
+ let dataBlock = "{}";
8144
+ if (initial_values && Object.keys(initial_values).length > 0) {
8145
+ const lines = Object.entries(initial_values).map(([k, v]) => ` ${k}: ${v}`);
8146
+ dataBlock = `
8147
+ ` + lines.join(`
8148
+ `);
7331
8149
  }
7332
- const updatedRaw = targetBlock.raw.replace(refPattern, "");
7333
- targetBlock.replace_raw(updatedRaw);
7334
- if (!doc.validate()) {
7335
- return { success: false, file_path, error: "Validation failed after removing GameObject" };
8150
+ const refEntry = ` - rid: ${nextRid}
8151
+ type: {class: ${typeInfo.class_name}, ns: ${typeInfo.namespace}, asm: ${typeInfo.assembly}}
8152
+ data: ${dataBlock}`;
8153
+ const refIdsPattern = /(\s*RefIds:\s*\n)/;
8154
+ const referencesPattern = /(\s*references:\s*\n\s*version:\s*\d+\s*\n)/;
8155
+ if (refIdsPattern.test(raw)) {
8156
+ raw = raw.replace(refIdsPattern, `$1${refEntry}
8157
+ `);
8158
+ } else if (referencesPattern.test(raw)) {
8159
+ raw = raw.replace(referencesPattern, `$1 RefIds:
8160
+ ${refEntry}
8161
+ `);
8162
+ } else {
8163
+ raw = raw.trimEnd() + `
8164
+ references:
8165
+ version: 2
8166
+ RefIds:
8167
+ ${refEntry}
8168
+ `;
7336
8169
  }
7337
- const saveResult = doc.save();
7338
- if (!saveResult.success) {
7339
- return { success: false, file_path, error: saveResult.error };
8170
+ if (!append) {
8171
+ const fieldRidPattern = new RegExp(`(${field_path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}:\\s*\\n\\s*rid:[ \\t]*)\\d+`, "m");
8172
+ if (fieldRidPattern.test(raw)) {
8173
+ raw = raw.replace(fieldRidPattern, `$1${nextRid}`);
8174
+ }
7340
8175
  }
7341
- return {
7342
- success: true,
7343
- file_path,
7344
- prefab_instance_id: targetBlock.file_id
7345
- };
8176
+ block.replace_raw(raw);
8177
+ const mrSave = doc.save();
8178
+ if (!mrSave.success) {
8179
+ return { success: false, file_path, error: mrSave.error };
8180
+ }
8181
+ return { success: true, file_path, rid: nextRid, type_info: typeInfo };
7346
8182
  }
7347
8183
  // src/editor/delete.ts
7348
- var import_fs11 = require("fs");
8184
+ var import_fs12 = require("fs");
7349
8185
  function removeComponent(options) {
7350
8186
  const { file_path, file_id } = options;
7351
8187
  const pathError = validate_file_path(file_path, "write");
7352
8188
  if (pathError) {
7353
8189
  return { success: false, file_path, error: pathError };
7354
8190
  }
7355
- if (!import_fs11.existsSync(file_path)) {
8191
+ if (!import_fs12.existsSync(file_path)) {
7356
8192
  return { success: false, file_path, error: `File not found: ${file_path}` };
7357
8193
  }
7358
8194
  let doc;
@@ -7371,12 +8207,13 @@ function removeComponent(options) {
7371
8207
  if (found.class_id === 4) {
7372
8208
  return { success: false, file_path, error: "Cannot remove a Transform with remove-component. Use delete to remove the entire GameObject." };
7373
8209
  }
7374
- const goMatch = found.raw.match(/m_GameObject:\s*\{fileID:\s*(\d+)\}/);
8210
+ const goMatch = found.raw.match(/m_GameObject:\s*\{fileID:\s*(-?\d+)\}/);
7375
8211
  if (goMatch) {
7376
8212
  const parentGoId = goMatch[1];
7377
8213
  const goBlock = doc.find_by_file_id(parentGoId);
7378
8214
  if (goBlock) {
7379
- const compLinePattern = new RegExp(`\\s*- component: \\{fileID: ${file_id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\}\\n?`);
8215
+ const escaped = file_id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
8216
+ const compLinePattern = new RegExp(`^[ \\t]*- component: \\{fileID: ${escaped}\\}[ \\t]*\\n`, "m");
7380
8217
  const modifiedRaw = goBlock.raw.replace(compLinePattern, "");
7381
8218
  goBlock.replace_raw(modifiedRaw);
7382
8219
  }
@@ -7402,7 +8239,7 @@ function deleteGameObject(options) {
7402
8239
  if (pathError) {
7403
8240
  return { success: false, file_path, error: pathError };
7404
8241
  }
7405
- if (!import_fs11.existsSync(file_path)) {
8242
+ if (!import_fs12.existsSync(file_path)) {
7406
8243
  return { success: false, file_path, error: `File not found: ${file_path}` };
7407
8244
  }
7408
8245
  let doc;
@@ -7418,7 +8255,7 @@ function deleteGameObject(options) {
7418
8255
  const goBlock = goResult;
7419
8256
  const goId = goBlock.file_id;
7420
8257
  const componentIds = new Set;
7421
- const compMatches = goBlock.raw.matchAll(/component:\s*\{fileID:\s*(\d+)\}/g);
8258
+ const compMatches = goBlock.raw.matchAll(/component:\s*\{fileID:\s*(-?\d+)\}/g);
7422
8259
  for (const cm of compMatches) {
7423
8260
  componentIds.add(cm[1]);
7424
8261
  }
@@ -7428,7 +8265,7 @@ function deleteGameObject(options) {
7428
8265
  const block = doc.find_by_file_id(compId);
7429
8266
  if (block && block.class_id === 4) {
7430
8267
  transformId = compId;
7431
- const fatherMatch = block.raw.match(/m_Father:\s*\{fileID:\s*(\d+)\}/);
8268
+ const fatherMatch = block.raw.match(/m_Father:\s*\{fileID:\s*(-?\d+)\}/);
7432
8269
  if (fatherMatch) {
7433
8270
  fatherId = fatherMatch[1];
7434
8271
  }
@@ -7481,7 +8318,7 @@ function deletePrefabInstance(options) {
7481
8318
  if (pathError) {
7482
8319
  return { success: false, file_path, error: pathError };
7483
8320
  }
7484
- if (!import_fs11.existsSync(file_path)) {
8321
+ if (!import_fs12.existsSync(file_path)) {
7485
8322
  return { success: false, file_path, error: `File not found: ${file_path}` };
7486
8323
  }
7487
8324
  let doc;
@@ -7561,7 +8398,7 @@ function deletePrefabInstance(options) {
7561
8398
  };
7562
8399
  }
7563
8400
  // src/editor/duplicate.ts
7564
- var import_fs12 = require("fs");
8401
+ var import_fs13 = require("fs");
7565
8402
  var path5 = __toESM(require("path"));
7566
8403
  init_scanner();
7567
8404
  function findPrefabInstanceByName(doc, name) {
@@ -7577,7 +8414,7 @@ function findPrefabInstanceByName(doc, name) {
7577
8414
  }
7578
8415
  function duplicateGameObject(options) {
7579
8416
  const { file_path, object_name, new_name } = options;
7580
- if (!import_fs12.existsSync(file_path)) {
8417
+ if (!import_fs13.existsSync(file_path)) {
7581
8418
  return { success: false, file_path, error: `File not found: ${file_path}` };
7582
8419
  }
7583
8420
  let doc;
@@ -7710,7 +8547,7 @@ function duplicateGameObject(options) {
7710
8547
  }
7711
8548
  function unpackPrefab(options) {
7712
8549
  const { file_path, prefab_instance, project_path } = options;
7713
- if (!import_fs12.existsSync(file_path)) {
8550
+ if (!import_fs13.existsSync(file_path)) {
7714
8551
  return { success: false, file_path, error: `File not found: ${file_path}` };
7715
8552
  }
7716
8553
  let sceneDoc;
@@ -7756,7 +8593,7 @@ function unpackPrefab(options) {
7756
8593
  sourcePrefabPath = resolvedPath;
7757
8594
  }
7758
8595
  }
7759
- if (!sourcePrefabPath || !import_fs12.existsSync(sourcePrefabPath)) {
8596
+ if (!sourcePrefabPath || !import_fs13.existsSync(sourcePrefabPath)) {
7760
8597
  const inferredProject = project_path || find_unity_project_root(path5.dirname(file_path));
7761
8598
  if (inferredProject) {
7762
8599
  const nativeBuild = getNativeBuildGuidCache();
@@ -7770,7 +8607,7 @@ function unpackPrefab(options) {
7770
8607
  }
7771
8608
  }
7772
8609
  }
7773
- if (!sourcePrefabPath || !import_fs12.existsSync(sourcePrefabPath)) {
8610
+ if (!sourcePrefabPath || !import_fs13.existsSync(sourcePrefabPath)) {
7774
8611
  const searchedPaths = [];
7775
8612
  if (project_path)
7776
8613
  searchedPaths.push(`GUID cache: ${path5.join(project_path, ".unity-agentic", "guid-cache.json")}`);
@@ -8203,15 +9040,15 @@ function move_scene(projectPath, scenePath, newPosition) {
8203
9040
  }
8204
9041
 
8205
9042
  // src/packages.ts
8206
- var import_fs13 = require("fs");
9043
+ var import_fs14 = require("fs");
8207
9044
  var import_path6 = require("path");
8208
9045
  function load_manifest(project_path) {
8209
9046
  const manifest_path = import_path6.join(project_path, "Packages", "manifest.json");
8210
- if (!import_fs13.existsSync(manifest_path)) {
9047
+ if (!import_fs14.existsSync(manifest_path)) {
8211
9048
  return { error: `manifest.json not found at ${manifest_path}` };
8212
9049
  }
8213
9050
  try {
8214
- const raw = import_fs13.readFileSync(manifest_path, "utf-8");
9051
+ const raw = import_fs14.readFileSync(manifest_path, "utf-8");
8215
9052
  const parsed = JSON.parse(raw);
8216
9053
  if (!parsed.dependencies || typeof parsed.dependencies !== "object") {
8217
9054
  return { error: `Invalid manifest.json: missing "dependencies" object` };
@@ -8227,7 +9064,7 @@ function save_manifest(manifest_path, manifest) {
8227
9064
  sorted_deps[key] = manifest.dependencies[key];
8228
9065
  }
8229
9066
  const output = { ...manifest, dependencies: sorted_deps };
8230
- import_fs13.writeFileSync(manifest_path, JSON.stringify(output, null, 2) + `
9067
+ import_fs14.writeFileSync(manifest_path, JSON.stringify(output, null, 2) + `
8231
9068
  `, "utf-8");
8232
9069
  }
8233
9070
  function list_packages(project_path, search) {
@@ -8281,7 +9118,7 @@ function remove_package(project_path, name) {
8281
9118
  }
8282
9119
 
8283
9120
  // src/input-actions.ts
8284
- var import_fs14 = require("fs");
9121
+ var import_fs15 = require("fs");
8285
9122
  var import_crypto = require("crypto");
8286
9123
  function generate_action_id() {
8287
9124
  const bytes = import_crypto.randomBytes(16);
@@ -8295,18 +9132,18 @@ function generate_action_id() {
8295
9132
  ].join("-");
8296
9133
  }
8297
9134
  function load_input_actions(file) {
8298
- if (!import_fs14.existsSync(file)) {
9135
+ if (!import_fs15.existsSync(file)) {
8299
9136
  return { error: `File not found: ${file}` };
8300
9137
  }
8301
9138
  try {
8302
- const raw = import_fs14.readFileSync(file, "utf-8");
9139
+ const raw = import_fs15.readFileSync(file, "utf-8");
8303
9140
  return JSON.parse(raw);
8304
9141
  } catch (err) {
8305
9142
  return { error: `Failed to parse input actions: ${err instanceof Error ? err.message : String(err)}` };
8306
9143
  }
8307
9144
  }
8308
9145
  function save_input_actions(file, data) {
8309
- import_fs14.writeFileSync(file, JSON.stringify(data, null, 4) + `
9146
+ import_fs15.writeFileSync(file, JSON.stringify(data, null, 4) + `
8310
9147
  `, "utf-8");
8311
9148
  }
8312
9149
  function add_map(data, name) {
@@ -8460,11 +9297,22 @@ function build_create_command() {
8460
9297
  if (!result.success)
8461
9298
  process.exitCode = 1;
8462
9299
  });
8463
- cmd.command("scriptable-object <output_path> <script>").description("Create a new ScriptableObject .asset file").option("-p, --project <path>", "Unity project path (for script GUID lookup)").option("-j, --json", "Output as JSON").action((output_path, script, options) => {
9300
+ cmd.command("scriptable-object <output_path> <script>").description("Create a new ScriptableObject .asset file").option("-p, --project <path>", "Unity project path (for script GUID lookup)").option("--set <json>", `Initial field values as JSON object (e.g. '{"damage": "10", "targetScope": "1"}')`).option("-j, --json", "Output as JSON").action((output_path, script, options) => {
9301
+ let initial_values;
9302
+ if (options.set) {
9303
+ try {
9304
+ initial_values = JSON.parse(options.set);
9305
+ } catch {
9306
+ console.log(JSON.stringify({ success: false, error: `Invalid JSON for --set: ${options.set}` }, null, 2));
9307
+ process.exitCode = 1;
9308
+ return;
9309
+ }
9310
+ }
8464
9311
  const result = createScriptableObject({
8465
9312
  output_path,
8466
9313
  script,
8467
- project_path: options.project
9314
+ project_path: options.project,
9315
+ initial_values
8468
9316
  });
8469
9317
  console.log(JSON.stringify(result, null, 2));
8470
9318
  if (!result.success)
@@ -8521,7 +9369,7 @@ function build_create_command() {
8521
9369
  console.log(JSON.stringify({ success: false, error: "--shader <guid> is required" }, null, 2));
8522
9370
  process.exit(1);
8523
9371
  }
8524
- if (import_fs15.existsSync(output_path)) {
9372
+ if (import_fs16.existsSync(output_path)) {
8525
9373
  console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
8526
9374
  process.exit(1);
8527
9375
  }
@@ -8593,7 +9441,7 @@ Material:
8593
9441
  m_Colors: ${color_section}
8594
9442
  m_BuildTextureStacks: []
8595
9443
  `;
8596
- import_fs15.writeFileSync(output_path, mat_content, "utf-8");
9444
+ import_fs16.writeFileSync(output_path, mat_content, "utf-8");
8597
9445
  const guid = import_crypto2.randomBytes(16).toString("hex");
8598
9446
  const meta_content = `fileFormatVersion: 2
8599
9447
  guid: ${guid}
@@ -8604,7 +9452,7 @@ NativeFormatImporter:
8604
9452
  assetBundleName:
8605
9453
  assetBundleVariant:
8606
9454
  `;
8607
- import_fs15.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
9455
+ import_fs16.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
8608
9456
  console.log(JSON.stringify({
8609
9457
  success: true,
8610
9458
  file: output_path,
@@ -8634,7 +9482,7 @@ NativeFormatImporter:
8634
9482
  process.exitCode = 1;
8635
9483
  return;
8636
9484
  }
8637
- if (import_fs15.existsSync(output_path)) {
9485
+ if (import_fs16.existsSync(output_path)) {
8638
9486
  console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
8639
9487
  process.exitCode = 1;
8640
9488
  return;
@@ -8661,7 +9509,7 @@ ScriptedImporter:
8661
9509
  wrapperClassName:
8662
9510
  wrapperCodeNamespace:
8663
9511
  `;
8664
- import_fs15.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
9512
+ import_fs16.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
8665
9513
  console.log(JSON.stringify({
8666
9514
  success: true,
8667
9515
  file: output_path,
@@ -8677,7 +9525,7 @@ ScriptedImporter:
8677
9525
  return;
8678
9526
  }
8679
9527
  const name = name_arg || import_path7.basename(output_path).replace(/\.anim$/i, "");
8680
- if (import_fs15.existsSync(output_path)) {
9528
+ if (import_fs16.existsSync(output_path)) {
8681
9529
  console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
8682
9530
  process.exitCode = 1;
8683
9531
  return;
@@ -8744,7 +9592,7 @@ AnimationClip:
8744
9592
  m_HasMotionFloatCurves: 0
8745
9593
  m_Events: []
8746
9594
  `;
8747
- import_fs15.writeFileSync(output_path, anim_content, "utf-8");
9595
+ import_fs16.writeFileSync(output_path, anim_content, "utf-8");
8748
9596
  const guid = import_crypto2.randomBytes(16).toString("hex");
8749
9597
  const meta_content = `fileFormatVersion: 2
8750
9598
  guid: ${guid}
@@ -8755,7 +9603,7 @@ NativeFormatImporter:
8755
9603
  assetBundleName:
8756
9604
  assetBundleVariant:
8757
9605
  `;
8758
- import_fs15.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
9606
+ import_fs16.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
8759
9607
  console.log(JSON.stringify({
8760
9608
  success: true,
8761
9609
  file: output_path,
@@ -8773,7 +9621,7 @@ NativeFormatImporter:
8773
9621
  process.exitCode = 1;
8774
9622
  return;
8775
9623
  }
8776
- if (import_fs15.existsSync(output_path)) {
9624
+ if (import_fs16.existsSync(output_path)) {
8777
9625
  console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
8778
9626
  process.exitCode = 1;
8779
9627
  return;
@@ -8823,7 +9671,7 @@ AnimatorStateMachine:
8823
9671
  m_ParentStateMachinePosition: {x: 800, y: 20, z: 0}
8824
9672
  m_DefaultState: {fileID: 0}
8825
9673
  `;
8826
- import_fs15.writeFileSync(output_path, ctrl_content, "utf-8");
9674
+ import_fs16.writeFileSync(output_path, ctrl_content, "utf-8");
8827
9675
  const guid = import_crypto2.randomBytes(16).toString("hex");
8828
9676
  const meta_content = `fileFormatVersion: 2
8829
9677
  guid: ${guid}
@@ -8834,7 +9682,7 @@ NativeFormatImporter:
8834
9682
  assetBundleName:
8835
9683
  assetBundleVariant:
8836
9684
  `;
8837
- import_fs15.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
9685
+ import_fs16.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
8838
9686
  console.log(JSON.stringify({
8839
9687
  success: true,
8840
9688
  file: output_path,
@@ -8851,7 +9699,7 @@ NativeFormatImporter:
8851
9699
  process.exitCode = 1;
8852
9700
  return;
8853
9701
  }
8854
- if (import_fs15.existsSync(output_path)) {
9702
+ if (import_fs16.existsSync(output_path)) {
8855
9703
  console.log(JSON.stringify({ success: false, error: `File already exists: ${output_path}` }, null, 2));
8856
9704
  process.exitCode = 1;
8857
9705
  return;
@@ -8890,7 +9738,7 @@ Transform:
8890
9738
  m_Father: {fileID: 0}
8891
9739
  m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
8892
9740
  `;
8893
- import_fs15.writeFileSync(output_path, prefab_content, "utf-8");
9741
+ import_fs16.writeFileSync(output_path, prefab_content, "utf-8");
8894
9742
  const guid = import_crypto2.randomBytes(16).toString("hex");
8895
9743
  const meta_content = `fileFormatVersion: 2
8896
9744
  guid: ${guid}
@@ -8900,7 +9748,7 @@ PrefabImporter:
8900
9748
  assetBundleName:
8901
9749
  assetBundleVariant:
8902
9750
  `;
8903
- import_fs15.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
9751
+ import_fs16.writeFileSync(`${output_path}.meta`, meta_content, "utf-8");
8904
9752
  console.log(JSON.stringify({
8905
9753
  success: true,
8906
9754
  file: output_path,
@@ -8914,7 +9762,7 @@ PrefabImporter:
8914
9762
 
8915
9763
  // src/cmd-read.ts
8916
9764
  init_scanner();
8917
- var import_fs16 = require("fs");
9765
+ var import_fs17 = require("fs");
8918
9766
  var import_path8 = require("path");
8919
9767
  var import_os = require("os");
8920
9768
  function parse_material_yaml(content) {
@@ -8929,7 +9777,7 @@ function parse_material_yaml(content) {
8929
9777
  floats: [],
8930
9778
  colors: []
8931
9779
  };
8932
- const inline_ref_re = /\{[^}]*fileID:\s*(\d+)[^}]*guid:\s*([a-f0-9]{32})[^}]*\}/;
9780
+ const inline_ref_re = /\{[^}]*fileID:\s*(-?\d+)[^}]*guid:\s*([a-f0-9]{32})[^}]*\}/;
8933
9781
  const color_re = /\{[^}]*r:\s*([\d.e+-]+)[^}]*g:\s*([\d.e+-]+)[^}]*b:\s*([\d.e+-]+)[^}]*a:\s*([\d.e+-]+)[^}]*\}/;
8934
9782
  const scale_offset_re = /\{[^}]*x:\s*([\d.e+-]+)[^}]*y:\s*([\d.e+-]+)[^}]*\}/;
8935
9783
  let i = 0;
@@ -9161,7 +10009,7 @@ function categorize_asset(filePath) {
9161
10009
  function find_project_root_from_file(filePath) {
9162
10010
  let dir = import_path8.dirname(import_path8.resolve(filePath));
9163
10011
  for (let i = 0;i < 20; i++) {
9164
- if (import_fs16.existsSync(import_path8.join(dir, "Assets")) && import_fs16.existsSync(import_path8.join(dir, "ProjectSettings"))) {
10012
+ if (import_fs17.existsSync(import_path8.join(dir, "Assets")) && import_fs17.existsSync(import_path8.join(dir, "ProjectSettings"))) {
9165
10013
  return dir;
9166
10014
  }
9167
10015
  const parent = import_path8.dirname(dir);
@@ -9507,7 +10355,7 @@ function walk_files(dir, extensions) {
9507
10355
  const current = stack.pop();
9508
10356
  let entries;
9509
10357
  try {
9510
- entries = import_fs16.readdirSync(current);
10358
+ entries = import_fs17.readdirSync(current);
9511
10359
  } catch {
9512
10360
  continue;
9513
10361
  }
@@ -9516,7 +10364,7 @@ function walk_files(dir, extensions) {
9516
10364
  continue;
9517
10365
  const full = import_path8.join(current, entry);
9518
10366
  try {
9519
- const stat = import_fs16.statSync(full);
10367
+ const stat = import_fs17.statSync(full);
9520
10368
  if (stat.isDirectory()) {
9521
10369
  stack.push(full);
9522
10370
  } else if (extensions.has(import_path8.extname(full).toLowerCase())) {
@@ -9530,7 +10378,7 @@ function walk_files(dir, extensions) {
9530
10378
  return results;
9531
10379
  }
9532
10380
  function validate_unity_yaml(file) {
9533
- if (!import_fs16.existsSync(file)) {
10381
+ if (!import_fs17.existsSync(file)) {
9534
10382
  return `File not found: ${file}`;
9535
10383
  }
9536
10384
  try {
@@ -9615,13 +10463,59 @@ function build_read_command(getScanner) {
9615
10463
  result.warning = result.warning ? `${result.warning}; ${maxDepthWarning}` : maxDepthWarning;
9616
10464
  }
9617
10465
  if (result.total === 0 && !result.error) {
9618
- try {
9619
- const fileSize = import_fs16.statSync(file).size;
9620
- if (fileSize > 100) {
9621
- const corruptWarning = "File has valid Unity YAML header but contains no parseable GameObjects -- file may be corrupt or malformed";
9622
- result.warning = result.warning ? `${result.warning}; ${corruptWarning}` : corruptWarning;
9623
- }
9624
- } catch {}
10466
+ const prefabInstances = result.prefabInstances;
10467
+ if (prefabInstances && prefabInstances.length > 0) {
10468
+ try {
10469
+ const doc = UnityDocument.from_file(file);
10470
+ const projectPath = find_unity_project_root(import_path8.dirname(file));
10471
+ const resolved = resolve_source_prefab(doc, file, projectPath ?? undefined);
10472
+ if (resolved) {
10473
+ const sourceResult = getScanner().inspect_all_paginated({
10474
+ file: resolved.source_path,
10475
+ include_properties: options.properties === true,
10476
+ verbose: options.verbose === true,
10477
+ page_size: pageSize,
10478
+ cursor: 0,
10479
+ max_depth: maxDepth,
10480
+ filter_component: options.filterComponent
10481
+ });
10482
+ if (sourceResult.total > 0 && sourceResult.gameobjects) {
10483
+ const piBlock = resolved.prefab_instance_block;
10484
+ const nameOverrides = new Map;
10485
+ const namePattern = /- target:[ \t]*\{fileID:[ \t]*(-?\d+)[^}]*\}\s*\n\s*propertyPath:[ \t]*m_Name\s*\n\s*value:[ \t]*(.*)/g;
10486
+ let nameMatch;
10487
+ while ((nameMatch = namePattern.exec(piBlock.raw)) !== null) {
10488
+ nameOverrides.set(nameMatch[1], nameMatch[2].trim());
10489
+ }
10490
+ for (const go of sourceResult.gameobjects) {
10491
+ const goRecord = go;
10492
+ const override = nameOverrides.get(goRecord.fileId);
10493
+ if (override) {
10494
+ goRecord.name = override;
10495
+ }
10496
+ }
10497
+ const resultRecord = result;
10498
+ resultRecord.gameobjects = sourceResult.gameobjects;
10499
+ resultRecord.total = sourceResult.total;
10500
+ resultRecord.totalInScene = sourceResult.totalInScene;
10501
+ resultRecord.resolvedFromSource = true;
10502
+ resultRecord.sourcePrefab = resolved.source_path;
10503
+ resultRecord.sourceGuid = resolved.source_guid;
10504
+ const variantNote = `PrefabVariant resolved from source prefab: ${resolved.source_path}`;
10505
+ result.warning = result.warning ? `${result.warning}; ${variantNote}` : variantNote;
10506
+ }
10507
+ }
10508
+ } catch {}
10509
+ }
10510
+ if (result.total === 0) {
10511
+ try {
10512
+ const fileSize = import_fs17.statSync(file).size;
10513
+ if (fileSize > 100) {
10514
+ const corruptWarning = "File has valid Unity YAML header but contains no parseable GameObjects -- file may be corrupt or malformed";
10515
+ result.warning = result.warning ? `${result.warning}; ${corruptWarning}` : corruptWarning;
10516
+ }
10517
+ } catch {}
10518
+ }
9625
10519
  }
9626
10520
  if (options.summary) {
9627
10521
  const component_counts = {};
@@ -9654,7 +10548,7 @@ function build_read_command(getScanner) {
9654
10548
  process.exit(1);
9655
10549
  }
9656
10550
  let resolved_id = object_id;
9657
- if (!/^\d+$/.test(object_id)) {
10551
+ if (!/^-?\d+$/.test(object_id)) {
9658
10552
  const matches = getScanner().find_by_name(file, object_id, false);
9659
10553
  if (matches.length > 1) {
9660
10554
  const ids = matches.map((m) => m.fileId).join(", ");
@@ -9672,7 +10566,7 @@ function build_read_command(getScanner) {
9672
10566
  verbose: options.verbose
9673
10567
  });
9674
10568
  if (!result) {
9675
- const label = /^\d+$/.test(object_id) ? "fileID" : "name";
10569
+ const label = /^-?\d+$/.test(object_id) ? "fileID" : "name";
9676
10570
  console.log(JSON.stringify({ error: `GameObject with ${label} "${object_id}" not found` }, null, 2));
9677
10571
  process.exit(1);
9678
10572
  }
@@ -9718,12 +10612,6 @@ function build_read_command(getScanner) {
9718
10612
  };
9719
10613
  console.log(JSON.stringify(output, null, 2));
9720
10614
  });
9721
- cmd.command("scriptable-object").description('(Deprecated: renamed to "read asset")').argument("[file]").allowUnknownOption().action(() => {
9722
- console.log(JSON.stringify({
9723
- error: 'Command "read scriptable-object" has been renamed to "read asset". Use: unity-agentic-tools read asset <file>'
9724
- }, null, 2));
9725
- process.exit(1);
9726
- });
9727
10615
  cmd.command("material <file>").description("Read a Unity Material file (.mat) with structured property output").option("--project <path>", "Unity project root (for GUID resolution)").option("--summary", "Show shader name, property count, texture count only").option("-j, --json", "Output as JSON").action((file, options) => {
9728
10616
  if (!file.toLowerCase().endsWith(".mat")) {
9729
10617
  console.log(JSON.stringify({ error: `File must be a .mat file: ${file}` }, null, 2));
@@ -9734,7 +10622,7 @@ function build_read_command(getScanner) {
9734
10622
  console.log(JSON.stringify({ error: matValidationError }, null, 2));
9735
10623
  process.exit(1);
9736
10624
  }
9737
- const content = import_fs16.readFileSync(file, "utf-8");
10625
+ const content = import_fs17.readFileSync(file, "utf-8");
9738
10626
  if (!content.includes("Material:")) {
9739
10627
  console.log(JSON.stringify({ error: `File "${file}" does not contain a Material block. Use 'read asset' for generic Unity YAML files.` }, null, 2));
9740
10628
  process.exit(1);
@@ -9776,13 +10664,13 @@ function build_read_command(getScanner) {
9776
10664
  }, null, 2));
9777
10665
  });
9778
10666
  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) => {
9779
- if (!import_fs16.existsSync(file)) {
10667
+ if (!import_fs17.existsSync(file)) {
9780
10668
  console.log(JSON.stringify({ error: `File not found: ${file}` }, null, 2));
9781
10669
  process.exit(1);
9782
10670
  }
9783
10671
  let content;
9784
10672
  try {
9785
- content = import_fs16.readFileSync(file, "utf-8");
10673
+ content = import_fs17.readFileSync(file, "utf-8");
9786
10674
  } catch {
9787
10675
  console.log(JSON.stringify({ error: `Cannot read file: ${file}` }, null, 2));
9788
10676
  process.exit(1);
@@ -9823,7 +10711,7 @@ function build_read_command(getScanner) {
9823
10711
  const result = [];
9824
10712
  let fileContent;
9825
10713
  try {
9826
- fileContent = import_fs16.readFileSync(filePath, "utf-8");
10714
+ fileContent = import_fs17.readFileSync(filePath, "utf-8");
9827
10715
  } catch {
9828
10716
  return [];
9829
10717
  }
@@ -9840,7 +10728,7 @@ function build_read_command(getScanner) {
9840
10728
  const rAbsPath = cache.resolve_absolute(g);
9841
10729
  const rType = rPath ? categorize_asset(rPath) : "unknown";
9842
10730
  const dep = { guid: g, path: rPath, type: rType, depth };
9843
- if (rAbsPath && depth < max_depth && import_fs16.existsSync(rAbsPath)) {
10731
+ if (rAbsPath && depth < max_depth && import_fs17.existsSync(rAbsPath)) {
9844
10732
  const subs = traverse_file(rAbsPath, depth + 1);
9845
10733
  if (subs.length > 0)
9846
10734
  dep.sub_dependencies = subs;
@@ -9869,7 +10757,7 @@ function build_read_command(getScanner) {
9869
10757
  const rDep = { guid: dep.guid, path: dep.path, type: dep.type, depth: 0 };
9870
10758
  if (dep.path) {
9871
10759
  const absPath = cache.resolve_absolute(dep.guid);
9872
- if (absPath && import_fs16.existsSync(absPath)) {
10760
+ if (absPath && import_fs17.existsSync(absPath)) {
9873
10761
  const subs = traverse_file(absPath, 1);
9874
10762
  if (subs.length > 0)
9875
10763
  rDep.sub_dependencies = subs;
@@ -9932,7 +10820,7 @@ function build_read_command(getScanner) {
9932
10820
  try {
9933
10821
  const doc = UnityDocument.from_file(file);
9934
10822
  let block = null;
9935
- if (/^\d+$/.test(prefab_instance)) {
10823
+ if (/^-?\d+$/.test(prefab_instance)) {
9936
10824
  block = doc.find_by_file_id(prefab_instance);
9937
10825
  if (block && block.class_id !== 1001) {
9938
10826
  console.log(JSON.stringify({ error: `fileID ${prefab_instance} is not a PrefabInstance (class ${block.class_id})` }, null, 2));
@@ -9957,17 +10845,34 @@ function build_read_command(getScanner) {
9957
10845
  let i = 0;
9958
10846
  while (i < lines.length) {
9959
10847
  if (lines[i].trim().startsWith("- target:")) {
9960
- const target_match = lines[i].match(/\{fileID:\s*(\d+)/);
10848
+ const target_match = lines[i].match(/\{fileID:\s*(-?\d+)/);
9961
10849
  const property_match = i + 1 < lines.length ? lines[i + 1].match(/propertyPath:\s*(.+)/) : null;
9962
10850
  const value_match = i + 2 < lines.length ? lines[i + 2].match(/value:\s*(.*)/) : null;
9963
- const obj_ref_match = i + 3 < lines.length ? lines[i + 3].match(/objectReference:\s*\{fileID:\s*(\d+)/) : null;
10851
+ const obj_ref_match = i + 3 < lines.length ? lines[i + 3].match(/objectReference:[ \t]*(.+)/) : null;
9964
10852
  if (target_match && property_match) {
9965
- modifications.push({
10853
+ const mod = {
9966
10854
  target_file_id: target_match[1],
9967
10855
  property_path: property_match[1].trim(),
9968
10856
  value: value_match ? value_match[1].trim() : "",
9969
- object_reference: obj_ref_match ? obj_ref_match[1] : null
9970
- });
10857
+ object_reference: obj_ref_match ? obj_ref_match[1].trim() : null
10858
+ };
10859
+ const propPath = mod.property_path;
10860
+ const modValue = mod.value;
10861
+ if (propPath.endsWith(".managedReferenceType") && modValue) {
10862
+ const spaceIdx = modValue.indexOf(" ");
10863
+ if (spaceIdx > 0) {
10864
+ const assembly = modValue.substring(0, spaceIdx);
10865
+ const full_type = modValue.substring(spaceIdx + 1);
10866
+ const lastDot = full_type.lastIndexOf(".");
10867
+ mod.managed_reference_type = {
10868
+ assembly,
10869
+ full_type,
10870
+ namespace: lastDot >= 0 ? full_type.substring(0, lastDot) : "",
10871
+ class_name: lastDot >= 0 ? full_type.substring(lastDot + 1) : full_type
10872
+ };
10873
+ }
10874
+ }
10875
+ modifications.push(mod);
9971
10876
  }
9972
10877
  i += 4;
9973
10878
  } else {
@@ -10035,8 +10940,129 @@ function build_read_command(getScanner) {
10035
10940
  process.exit(1);
10036
10941
  }
10037
10942
  });
10943
+ 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) => {
10944
+ try {
10945
+ if (!import_fs17.existsSync(file)) {
10946
+ console.log(JSON.stringify({ error: `File not found: ${file}` }, null, 2));
10947
+ process.exit(1);
10948
+ }
10949
+ const doc = UnityDocument.from_file(file);
10950
+ let guid;
10951
+ const piBlocks = doc.find_by_class_id(1001);
10952
+ if (piBlocks.length > 0) {
10953
+ const sourceMatch = piBlocks[0].raw.match(/m_SourcePrefab:[ \t]*\{[^}]*guid:[ \t]*([a-f0-9]{32})/);
10954
+ guid = sourceMatch ? sourceMatch[1] : null;
10955
+ if (!guid) {
10956
+ console.log(JSON.stringify({ error: "Cannot extract source GUID from PrefabInstance m_SourcePrefab." }, null, 2));
10957
+ process.exit(1);
10958
+ }
10959
+ } else {
10960
+ const metaPath = file + ".meta";
10961
+ guid = extractGuidFromMeta(metaPath);
10962
+ if (!guid) {
10963
+ console.log(JSON.stringify({ error: `Cannot read GUID from ${metaPath}. Ensure the .meta file exists.` }, null, 2));
10964
+ process.exit(1);
10965
+ }
10966
+ }
10967
+ const goResult = doc.require_unique_game_object(gameobject_name);
10968
+ let targetFileId;
10969
+ if ("error" in goResult) {
10970
+ let found = false;
10971
+ for (const block of doc.blocks) {
10972
+ if (block.class_id !== 1001)
10973
+ continue;
10974
+ const nameMatch = block.raw.match(new RegExp(`- target:[ \\t]*\\{fileID:[ \\t]*(-?\\d+)[^}]*\\}\\s*\\n\\s*propertyPath:[ \\t]*m_Name\\s*\\n\\s*value:[ \\t]*${gameobject_name.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*$`, "m"));
10975
+ if (nameMatch) {
10976
+ targetFileId = nameMatch[1];
10977
+ found = true;
10978
+ break;
10979
+ }
10980
+ }
10981
+ if (!found) {
10982
+ console.log(JSON.stringify({ error: goResult.error }, null, 2));
10983
+ process.exit(1);
10984
+ }
10985
+ } else {
10986
+ targetFileId = goResult.file_id;
10987
+ }
10988
+ if (component_type) {
10989
+ const classId = get_class_id(component_type);
10990
+ const goBlock = doc.find_by_file_id(targetFileId);
10991
+ if (goBlock && !goBlock.is_stripped) {
10992
+ const compRefs = [...goBlock.raw.matchAll(/component:[ \t]*\{fileID:[ \t]*(-?\d+)\}/g)].map((m) => m[1]);
10993
+ let compFound = false;
10994
+ for (const refId of compRefs) {
10995
+ const compBlock = doc.find_by_file_id(refId);
10996
+ if (!compBlock)
10997
+ continue;
10998
+ if (classId !== null && compBlock.class_id === classId) {
10999
+ targetFileId = refId;
11000
+ compFound = true;
11001
+ break;
11002
+ }
11003
+ if (classId === null && compBlock.class_id === 114) {
11004
+ const scriptMatch = compBlock.raw.match(/m_Script:[ \t]*\{[^}]*guid:[ \t]*([a-f0-9]{32})/);
11005
+ if (scriptMatch) {
11006
+ const project = options.project || find_unity_project_root(import_path8.dirname(file));
11007
+ if (project) {
11008
+ const cache = load_guid_cache(project);
11009
+ if (cache) {
11010
+ const scriptPath = cache.resolve(scriptMatch[1]);
11011
+ if (scriptPath && import_path8.basename(scriptPath, ".cs").toLowerCase() === component_type.toLowerCase().replace(/\.cs$/, "")) {
11012
+ targetFileId = refId;
11013
+ compFound = true;
11014
+ break;
11015
+ }
11016
+ }
11017
+ }
11018
+ }
11019
+ }
11020
+ }
11021
+ if (!compFound) {
11022
+ console.log(JSON.stringify({ error: `Component "${component_type}" not found on GameObject "${gameobject_name}"` }, null, 2));
11023
+ process.exit(1);
11024
+ }
11025
+ } else {
11026
+ const project = options.project || find_unity_project_root(import_path8.dirname(file));
11027
+ const resolved = resolve_source_prefab(doc, file, project ?? undefined);
11028
+ if (resolved) {
11029
+ const sourceDoc = UnityDocument.from_file(resolved.source_path);
11030
+ const sourceGo = sourceDoc.require_unique_game_object(gameobject_name);
11031
+ if (!("error" in sourceGo)) {
11032
+ const compRefs = [...sourceGo.raw.matchAll(/component:[ \t]*\{fileID:[ \t]*(-?\d+)\}/g)].map((m) => m[1]);
11033
+ let compFound = false;
11034
+ for (const refId of compRefs) {
11035
+ const compBlock = sourceDoc.find_by_file_id(refId);
11036
+ if (!compBlock)
11037
+ continue;
11038
+ if (classId !== null && compBlock.class_id === classId) {
11039
+ targetFileId = refId;
11040
+ compFound = true;
11041
+ break;
11042
+ }
11043
+ }
11044
+ if (!compFound) {
11045
+ console.log(JSON.stringify({ error: `Component "${component_type}" not found on "${gameobject_name}" in source prefab` }, null, 2));
11046
+ process.exit(1);
11047
+ }
11048
+ }
11049
+ }
11050
+ }
11051
+ }
11052
+ const target = `{fileID: ${targetFileId}, guid: ${guid}, type: 3}`;
11053
+ console.log(JSON.stringify({
11054
+ target,
11055
+ file_id: targetFileId,
11056
+ guid,
11057
+ type: 3
11058
+ }, null, 2));
11059
+ } catch (err) {
11060
+ console.log(JSON.stringify({ error: err instanceof Error ? err.message : String(err) }, null, 2));
11061
+ process.exit(1);
11062
+ }
11063
+ });
10038
11064
  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) => {
10039
- if (!import_fs16.existsSync(file)) {
11065
+ if (!import_fs17.existsSync(file)) {
10040
11066
  console.log(JSON.stringify({ error: `File not found: ${file}` }, null, 2));
10041
11067
  process.exit(1);
10042
11068
  }
@@ -10121,18 +11147,20 @@ function build_read_command(getScanner) {
10121
11147
  types: displayed
10122
11148
  }, null, 2));
10123
11149
  });
10124
- 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) => {
11150
+ cmd.command("log [project-path]").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((projectPath, options) => {
11151
+ if (projectPath && !options.project)
11152
+ options.project = projectPath;
10125
11153
  const logPath = options.path || get_editor_log_path();
10126
- if (!logPath || !import_fs16.existsSync(logPath)) {
11154
+ if (!logPath || !import_fs17.existsSync(logPath)) {
10127
11155
  console.log(JSON.stringify({ error: `Editor.log not found${logPath ? `: ${logPath}` : ". Could not detect platform log path."}` }, null, 2));
10128
11156
  process.exit(1);
10129
11157
  }
10130
- const content = import_fs16.readFileSync(logPath, "utf-8");
11158
+ const content = import_fs17.readFileSync(logPath, "utf-8");
10131
11159
  let lines = content.split(/\r?\n/);
10132
11160
  if (options.project) {
10133
- const projectPath = import_path8.resolve(options.project);
10134
- const projectName = import_path8.basename(projectPath);
10135
- const escapedPath = projectPath.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
11161
+ const projectPath2 = import_path8.resolve(options.project);
11162
+ const projectName = import_path8.basename(projectPath2);
11163
+ const escapedPath = projectPath2.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10136
11164
  const escapedName = projectName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
10137
11165
  const session_markers = [
10138
11166
  new RegExp(`Loading project at '${escapedPath}'`),
@@ -10248,11 +11276,11 @@ function build_read_command(getScanner) {
10248
11276
  });
10249
11277
  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) => {
10250
11278
  const metaPath = file.endsWith(".meta") ? file : `${file}.meta`;
10251
- if (!import_fs16.existsSync(metaPath)) {
11279
+ if (!import_fs17.existsSync(metaPath)) {
10252
11280
  console.log(JSON.stringify({ error: `Meta file not found: ${metaPath}` }, null, 2));
10253
11281
  process.exit(1);
10254
11282
  }
10255
- const content = import_fs16.readFileSync(metaPath, "utf-8");
11283
+ const content = import_fs17.readFileSync(metaPath, "utf-8");
10256
11284
  const lines = content.split(/\r?\n/);
10257
11285
  let guid = "";
10258
11286
  let importer_type = "Unknown";
@@ -10340,7 +11368,7 @@ function build_read_command(getScanner) {
10340
11368
  console.log(JSON.stringify({ error: animValidationError }, null, 2));
10341
11369
  process.exit(1);
10342
11370
  }
10343
- const content = import_fs16.readFileSync(file, "utf-8");
11371
+ const content = import_fs17.readFileSync(file, "utf-8");
10344
11372
  const clip = parse_animation_yaml(content, options.curves === true);
10345
11373
  if (!clip) {
10346
11374
  console.log(JSON.stringify({ error: `No AnimationClip found in "${file}". Is this an .anim file?` }, null, 2));
@@ -10401,7 +11429,7 @@ function build_read_command(getScanner) {
10401
11429
  console.log(JSON.stringify({ error: ctrlValidationError }, null, 2));
10402
11430
  process.exit(1);
10403
11431
  }
10404
- const content = import_fs16.readFileSync(file, "utf-8");
11432
+ const content = import_fs17.readFileSync(file, "utf-8");
10405
11433
  const blocks = split_yaml_blocks(content);
10406
11434
  const controller_block = blocks.find((b) => b.class_id === 91);
10407
11435
  if (!controller_block) {
@@ -10631,7 +11659,7 @@ function build_read_command(getScanner) {
10631
11659
  process.exit(1);
10632
11660
  }
10633
11661
  const assetsDir = import_path8.join(import_path8.resolve(project_path), "Assets");
10634
- if (!import_fs16.existsSync(assetsDir)) {
11662
+ if (!import_fs17.existsSync(assetsDir)) {
10635
11663
  console.log(JSON.stringify({ error: `Assets directory not found in "${project_path}"` }, null, 2));
10636
11664
  process.exit(1);
10637
11665
  }
@@ -10660,7 +11688,7 @@ function build_read_command(getScanner) {
10660
11688
  const guid_pattern = `guid: ${guid}`;
10661
11689
  for (const f of files) {
10662
11690
  try {
10663
- const fc = import_fs16.readFileSync(f, "utf-8");
11691
+ const fc = import_fs17.readFileSync(f, "utf-8");
10664
11692
  if (fc.includes(guid_pattern)) {
10665
11693
  const rel = import_path8.relative(import_path8.resolve(project_path), f);
10666
11694
  const ftype = categorize_asset(f);
@@ -10690,7 +11718,7 @@ function build_read_command(getScanner) {
10690
11718
  cmd.command("unused <project_path>").description("Find potentially unused assets (zero inbound GUID references)").option("--type <type>", "Filter to specific asset types").option("--ignore <glob>", "Exclude paths matching this pattern").option("--max <n>", "Maximum results to return (default 200)", "200").option("-j, --json", "Output as JSON").action((project_path, options) => {
10691
11719
  const resolvedProject = import_path8.resolve(project_path);
10692
11720
  const assetsDir = import_path8.join(resolvedProject, "Assets");
10693
- if (!import_fs16.existsSync(assetsDir)) {
11721
+ if (!import_fs17.existsSync(assetsDir)) {
10694
11722
  console.log(JSON.stringify({ error: `Assets directory not found in "${project_path}"` }, null, 2));
10695
11723
  process.exit(1);
10696
11724
  }
@@ -10718,7 +11746,7 @@ function build_read_command(getScanner) {
10718
11746
  const guid_re = /guid:\s*([a-f0-9]{32})/g;
10719
11747
  for (const f of files) {
10720
11748
  try {
10721
- const fc = import_fs16.readFileSync(f, "utf-8");
11749
+ const fc = import_fs17.readFileSync(f, "utf-8");
10722
11750
  let gm;
10723
11751
  while ((gm = guid_re.exec(fc)) !== null) {
10724
11752
  referenced_guids.add(gm[1]);
@@ -10729,8 +11757,8 @@ function build_read_command(getScanner) {
10729
11757
  }
10730
11758
  const buildSettingsPath = import_path8.join(resolvedProject, "ProjectSettings", "EditorBuildSettings.asset");
10731
11759
  const build_scene_guids = new Set;
10732
- if (import_fs16.existsSync(buildSettingsPath)) {
10733
- const bsc = import_fs16.readFileSync(buildSettingsPath, "utf-8");
11760
+ if (import_fs17.existsSync(buildSettingsPath)) {
11761
+ const bsc = import_fs17.readFileSync(buildSettingsPath, "utf-8");
10734
11762
  let bm;
10735
11763
  while ((bm = guid_re.exec(bsc)) !== null) {
10736
11764
  build_scene_guids.add(bm[1]);
@@ -10848,19 +11876,11 @@ function build_read_command(getScanner) {
10848
11876
  }
10849
11877
  console.log(JSON.stringify({ file, ...ia }, null, 2));
10850
11878
  });
10851
- cmd.command("prefab").argument("[file]").allowUnknownOption().action(() => {
10852
- console.log(JSON.stringify({
10853
- success: false,
10854
- error: '"read prefab" does not exist. Use "read scene" -- it handles both .unity and .prefab files.',
10855
- correct_usage: "unity-agentic-tools read scene <file.prefab>"
10856
- }, null, 2));
10857
- process.exitCode = 1;
10858
- });
10859
11879
  return cmd;
10860
11880
  }
10861
11881
 
10862
11882
  // src/cmd-update.ts
10863
- var import_fs17 = require("fs");
11883
+ var import_fs18 = require("fs");
10864
11884
  var import_path9 = require("path");
10865
11885
 
10866
11886
  // src/animator-utils.ts
@@ -10992,7 +12012,7 @@ function parseVector(str) {
10992
12012
  }
10993
12013
  function resolve_transform_id(scanner, file, identifier) {
10994
12014
  let resolved_id = identifier;
10995
- if (!/^\d+$/.test(identifier)) {
12015
+ if (!/^-?\d+$/.test(identifier)) {
10996
12016
  const matches = scanner.find_by_name(file, identifier, false);
10997
12017
  if (matches.length > 1) {
10998
12018
  const ids = matches.map((m) => m.fileId).join(", ");
@@ -11006,10 +12026,10 @@ function resolve_transform_id(scanner, file, identifier) {
11006
12026
  if (result && !result.is_error) {
11007
12027
  const transform = result.components?.find((c) => c.class_id === 4 || c.class_id === 224);
11008
12028
  if (transform)
11009
- return { transform_id: parseInt(transform.file_id, 10) };
12029
+ return { transform_id: transform.file_id };
11010
12030
  }
11011
- if (/^\d+$/.test(identifier)) {
11012
- return { transform_id: parseInt(identifier, 10) };
12031
+ if (/^-?\d+$/.test(identifier)) {
12032
+ return { transform_id: identifier };
11013
12033
  }
11014
12034
  return { error: `Could not resolve "${identifier}" to a Transform component. Use a GameObject name or transform fileID.` };
11015
12035
  }
@@ -11027,7 +12047,7 @@ function build_update_command(getScanner) {
11027
12047
  if (!result.success)
11028
12048
  process.exitCode = 1;
11029
12049
  });
11030
- 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])").option("-j, --json", "Output as JSON").action((file, file_id, property, value, _options) => {
12050
+ cmd.command("component <file> <file_id> <property> <value>").description("Edit any component property by file ID. Supports dotted paths (m_LocalPosition.x) and array paths (m_Materials.Array.data[0]). Quote paths with brackets or use dot notation (data.0) to avoid shell glob expansion").option("-j, --json", "Output as JSON").action((file, file_id, property, value, _options) => {
11031
12051
  if (property === "m_RootOrder") {
11032
12052
  const num = Number(value);
11033
12053
  if (!Number.isInteger(num) || num < 0) {
@@ -11183,14 +12203,71 @@ function build_update_command(getScanner) {
11183
12203
  if (!result.success)
11184
12204
  process.exitCode = 1;
11185
12205
  });
11186
- prefab_cmd.command("override <file> <prefab_instance> <property_path> <value>").description("Edit or add a property override in a PrefabInstance m_Modifications list").option("--object-reference <ref>", "Object reference value (default: {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) => {
12206
+ 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) => {
12207
+ if (options.objectReference && !/^\{fileID:/.test(options.objectReference)) {
12208
+ 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.`);
12209
+ }
11187
12210
  const result = editPrefabOverride({
11188
12211
  file_path: file,
11189
12212
  prefab_instance,
11190
12213
  property_path,
11191
12214
  new_value: value,
11192
12215
  object_reference: options.objectReference,
11193
- target: options.target
12216
+ target: options.target,
12217
+ managed_reference: options.managedReference
12218
+ });
12219
+ console.log(JSON.stringify(result, null, 2));
12220
+ if (!result.success)
12221
+ process.exitCode = 1;
12222
+ });
12223
+ 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) => {
12224
+ let raw_edits;
12225
+ try {
12226
+ const parsed = JSON.parse(edits_json);
12227
+ if (!Array.isArray(parsed)) {
12228
+ console.log(JSON.stringify({ success: false, error: 'Edits must be a JSON array. Format: [{"property_path":"...","value":"..."}]' }, null, 2));
12229
+ process.exit(1);
12230
+ }
12231
+ raw_edits = parsed;
12232
+ } catch {
12233
+ console.log(JSON.stringify({ success: false, error: "Invalid JSON for edits" }, null, 2));
12234
+ process.exit(1);
12235
+ }
12236
+ const edits = raw_edits.map((e) => ({
12237
+ property_path: String(e.property_path ?? ""),
12238
+ value: String(e.new_value ?? e.value ?? ""),
12239
+ object_reference: e.object_reference != null ? String(e.object_reference) : undefined,
12240
+ target: e.target != null ? String(e.target) : undefined,
12241
+ managed_reference: e.managed_reference != null ? String(e.managed_reference) : undefined
12242
+ }));
12243
+ if (edits.length === 0) {
12244
+ console.log(JSON.stringify({ success: false, error: 'Empty edits array. Provide at least one edit: [{"property_path":"...","value":"..."}]' }, null, 2));
12245
+ process.exit(1);
12246
+ }
12247
+ for (const edit of edits) {
12248
+ if (!edit.property_path) {
12249
+ console.log(JSON.stringify({ success: false, error: 'Missing "property_path" in edit entry. JSON format: [{"property_path":"...","value":"..."}]' }, null, 2));
12250
+ process.exit(1);
12251
+ }
12252
+ if (!edit.value && !edit.object_reference && !edit.managed_reference) {
12253
+ console.log(JSON.stringify({ success: false, error: `Missing "value" for override "${edit.property_path}". Provide "value", "object_reference", or "managed_reference".` }, null, 2));
12254
+ process.exit(1);
12255
+ }
12256
+ }
12257
+ const result = batchEditPrefabOverrides(file, prefab_instance, edits);
12258
+ console.log(JSON.stringify(result, null, 2));
12259
+ if (!result.success)
12260
+ process.exitCode = 1;
12261
+ });
12262
+ 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) => {
12263
+ const result = addPrefabManagedReference({
12264
+ file_path: file,
12265
+ prefab_instance,
12266
+ field_path,
12267
+ type_name,
12268
+ target: options.target,
12269
+ index: parseInt(options.index, 10),
12270
+ project_path: options.project
11194
12271
  });
11195
12272
  console.log(JSON.stringify(result, null, 2));
11196
12273
  if (!result.success)
@@ -11222,7 +12299,7 @@ function build_update_command(getScanner) {
11222
12299
  process.exitCode = 1;
11223
12300
  }
11224
12301
  });
11225
- 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>.").option("--index <n>", "Index for insert/remove").option("-j, --json", "Output as JSON").action((file, file_id, array_property, action, args, options) => {
12302
+ 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) => {
11226
12303
  if (action !== "insert" && action !== "append" && action !== "remove") {
11227
12304
  console.log(JSON.stringify({ success: false, error: 'Action must be "insert", "append", or "remove"' }, null, 2));
11228
12305
  process.exit(1);
@@ -11357,7 +12434,7 @@ function build_update_command(getScanner) {
11357
12434
  if (!result.success)
11358
12435
  process.exitCode = 1;
11359
12436
  });
11360
- prefab_cmd.command("remove-override <file> <prefab_instance> <property_path>").description("Remove a property override from a PrefabInstance").option("--target <ref>", "Target reference to match (for disambiguation)").option("-j, --json", "Output as JSON").action((file, prefab_instance, property_path, options) => {
12437
+ 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) => {
11361
12438
  const result = removePrefabOverride({
11362
12439
  file_path: file,
11363
12440
  prefab_instance,
@@ -11418,11 +12495,11 @@ function build_update_command(getScanner) {
11418
12495
  }
11419
12496
  }
11420
12497
  }).action((file, options) => {
11421
- if (!import_fs17.existsSync(file)) {
12498
+ if (!import_fs18.existsSync(file)) {
11422
12499
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
11423
12500
  process.exit(1);
11424
12501
  }
11425
- let content = import_fs17.readFileSync(file, "utf-8");
12502
+ let content = import_fs18.readFileSync(file, "utf-8");
11426
12503
  const changes = [];
11427
12504
  if (options.shader) {
11428
12505
  const guid = options.shader;
@@ -11571,7 +12648,7 @@ ${lm[1]} - ${kw}
11571
12648
  process.exitCode = 1;
11572
12649
  return;
11573
12650
  }
11574
- import_fs17.writeFileSync(file, content, "utf-8");
12651
+ import_fs18.writeFileSync(file, content, "utf-8");
11575
12652
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
11576
12653
  });
11577
12654
  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) => {
@@ -11632,9 +12709,9 @@ ${lm[1]} - ${kw}
11632
12709
  const suffix = import_path9.basename(pattern).replace(/^\*+/, "");
11633
12710
  const meta_files = [];
11634
12711
  const scan = (d) => {
11635
- for (const entry of import_fs17.readdirSync(d)) {
12712
+ for (const entry of import_fs18.readdirSync(d)) {
11636
12713
  const full = import_path9.join(d, entry);
11637
- if (import_fs17.statSync(full).isDirectory()) {
12714
+ if (import_fs18.statSync(full).isDirectory()) {
11638
12715
  if (pattern.includes("**"))
11639
12716
  scan(full);
11640
12717
  } else if (entry.endsWith(suffix)) {
@@ -11642,7 +12719,7 @@ ${lm[1]} - ${kw}
11642
12719
  }
11643
12720
  }
11644
12721
  };
11645
- if (import_fs17.existsSync(dir))
12722
+ if (import_fs18.existsSync(dir))
11646
12723
  scan(dir);
11647
12724
  if (meta_files.length === 0) {
11648
12725
  console.log(JSON.stringify({ success: false, error: `No files matched pattern: ${glob_pattern}` }, null, 2));
@@ -11650,7 +12727,7 @@ ${lm[1]} - ${kw}
11650
12727
  }
11651
12728
  const results = [];
11652
12729
  for (const mf of meta_files) {
11653
- let mc = import_fs17.readFileSync(mf, "utf-8");
12730
+ let mc = import_fs18.readFileSync(mf, "utf-8");
11654
12731
  const file_changes = [];
11655
12732
  for (const edit of edits) {
11656
12733
  const re = new RegExp(`^(\\s*${edit.key}:)[ \\t]*.*$`, "m");
@@ -11661,7 +12738,7 @@ ${lm[1]} - ${kw}
11661
12738
  }
11662
12739
  if (file_changes.length > 0) {
11663
12740
  if (!options.dryRun)
11664
- import_fs17.writeFileSync(mf, mc, "utf-8");
12741
+ import_fs18.writeFileSync(mf, mc, "utf-8");
11665
12742
  results.push({ file: mf, changes: file_changes });
11666
12743
  }
11667
12744
  }
@@ -11674,11 +12751,11 @@ ${lm[1]} - ${kw}
11674
12751
  }, null, 2));
11675
12752
  return;
11676
12753
  }
11677
- if (!import_fs17.existsSync(metaPath)) {
12754
+ if (!import_fs18.existsSync(metaPath)) {
11678
12755
  console.log(JSON.stringify({ success: false, error: `Meta file not found: ${metaPath}` }, null, 2));
11679
12756
  process.exit(1);
11680
12757
  }
11681
- let content = import_fs17.readFileSync(metaPath, "utf-8");
12758
+ let content = import_fs18.readFileSync(metaPath, "utf-8");
11682
12759
  const changes = [];
11683
12760
  for (const edit of edits) {
11684
12761
  const re = new RegExp(`^(\\s*${edit.key}:)[ \\t]*.*$`, "m");
@@ -11693,11 +12770,11 @@ ${lm[1]} - ${kw}
11693
12770
  console.log(JSON.stringify({ success: true, dry_run: true, file: metaPath, changes }, null, 2));
11694
12771
  return;
11695
12772
  }
11696
- import_fs17.writeFileSync(metaPath, content, "utf-8");
12773
+ import_fs18.writeFileSync(metaPath, content, "utf-8");
11697
12774
  console.log(JSON.stringify({ success: true, file: metaPath, changes }, null, 2));
11698
12775
  });
11699
12776
  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) => {
11700
- if (!import_fs17.existsSync(file)) {
12777
+ if (!import_fs18.existsSync(file)) {
11701
12778
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
11702
12779
  process.exit(1);
11703
12780
  }
@@ -11705,7 +12782,7 @@ ${lm[1]} - ${kw}
11705
12782
  console.log(JSON.stringify({ success: false, error: `File is not an AnimationClip (.anim): ${file}` }, null, 2));
11706
12783
  process.exit(1);
11707
12784
  }
11708
- let content = import_fs17.readFileSync(file, "utf-8");
12785
+ let content = import_fs18.readFileSync(file, "utf-8");
11709
12786
  const changes = [];
11710
12787
  const property_map = {
11711
12788
  "wrap-mode": "m_WrapMode",
@@ -11866,16 +12943,16 @@ ${event_yaml}
11866
12943
  process.exitCode = 1;
11867
12944
  return;
11868
12945
  }
11869
- import_fs17.writeFileSync(file, content, "utf-8");
12946
+ import_fs18.writeFileSync(file, content, "utf-8");
11870
12947
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
11871
12948
  });
11872
12949
  const PARAM_TYPE_MAP = { float: 1, int: 3, bool: 4, trigger: 9 };
11873
12950
  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) => {
11874
- if (!import_fs17.existsSync(file)) {
12951
+ if (!import_fs18.existsSync(file)) {
11875
12952
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
11876
12953
  process.exit(1);
11877
12954
  }
11878
- let content = import_fs17.readFileSync(file, "utf-8");
12955
+ let content = import_fs18.readFileSync(file, "utf-8");
11879
12956
  const had_crlf = content.includes(`\r
11880
12957
  `);
11881
12958
  if (had_crlf)
@@ -11977,7 +13054,7 @@ $1`);
11977
13054
  if (had_crlf)
11978
13055
  content = content.replace(/\n/g, `\r
11979
13056
  `);
11980
- import_fs17.writeFileSync(file, content, "utf-8");
13057
+ import_fs18.writeFileSync(file, content, "utf-8");
11981
13058
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
11982
13059
  });
11983
13060
  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) => {
@@ -11987,7 +13064,7 @@ $1`);
11987
13064
  process.exitCode = 1;
11988
13065
  return;
11989
13066
  }
11990
- if (!import_fs17.existsSync(file)) {
13067
+ if (!import_fs18.existsSync(file)) {
11991
13068
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
11992
13069
  process.exitCode = 1;
11993
13070
  return;
@@ -12182,7 +13259,7 @@ $1`);
12182
13259
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
12183
13260
  });
12184
13261
  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) => {
12185
- if (!import_fs17.existsSync(file)) {
13262
+ if (!import_fs18.existsSync(file)) {
12186
13263
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
12187
13264
  process.exitCode = 1;
12188
13265
  return;
@@ -12192,7 +13269,7 @@ $1`);
12192
13269
  process.exitCode = 1;
12193
13270
  return;
12194
13271
  }
12195
- let content = import_fs17.readFileSync(file, "utf-8");
13272
+ let content = import_fs18.readFileSync(file, "utf-8");
12196
13273
  const had_crlf = content.includes(`\r
12197
13274
  `);
12198
13275
  if (had_crlf)
@@ -12490,16 +13567,16 @@ ${curve_yaml}`);
12490
13567
  if (had_crlf)
12491
13568
  content = content.replace(/\n/g, `\r
12492
13569
  `);
12493
- import_fs17.writeFileSync(file, content, "utf-8");
13570
+ import_fs18.writeFileSync(file, content, "utf-8");
12494
13571
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
12495
13572
  });
12496
13573
  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) => {
12497
- if (!import_fs17.existsSync(file)) {
13574
+ if (!import_fs18.existsSync(file)) {
12498
13575
  console.log(JSON.stringify({ success: false, error: `File not found: ${file}` }, null, 2));
12499
13576
  process.exitCode = 1;
12500
13577
  return;
12501
13578
  }
12502
- let content = import_fs17.readFileSync(file, "utf-8");
13579
+ let content = import_fs18.readFileSync(file, "utf-8");
12503
13580
  const had_crlf = content.includes(`\r
12504
13581
  `);
12505
13582
  if (had_crlf)
@@ -12838,10 +13915,34 @@ $1`);
12838
13915
  if (had_crlf)
12839
13916
  content = content.replace(/\n/g, `\r
12840
13917
  `);
12841
- import_fs17.writeFileSync(file, content, "utf-8");
13918
+ import_fs18.writeFileSync(file, content, "utf-8");
12842
13919
  console.log(JSON.stringify({ success: true, file, changes }, null, 2));
12843
13920
  });
12844
13921
  cmd.addCommand(prefab_cmd);
13922
+ 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) => {
13923
+ let initial_values;
13924
+ if (options.properties) {
13925
+ try {
13926
+ initial_values = JSON.parse(options.properties);
13927
+ } catch {
13928
+ console.log(JSON.stringify({ success: false, error: `Invalid JSON for --properties: ${options.properties}` }, null, 2));
13929
+ process.exitCode = 1;
13930
+ return;
13931
+ }
13932
+ }
13933
+ const result = editManagedReference({
13934
+ file_path: file,
13935
+ file_id: component_id,
13936
+ field_path,
13937
+ type_name,
13938
+ project_path: options.project,
13939
+ append: options.append === true,
13940
+ initial_values
13941
+ });
13942
+ console.log(JSON.stringify(result, null, 2));
13943
+ if (!result.success)
13944
+ process.exitCode = 1;
13945
+ });
12845
13946
  return cmd;
12846
13947
  }
12847
13948
 
@@ -12907,16 +14008,16 @@ function build_delete_command() {
12907
14008
  var import_path11 = require("path");
12908
14009
 
12909
14010
  // src/editor-client.ts
12910
- var import_fs18 = require("fs");
14011
+ var import_fs19 = require("fs");
12911
14012
  var import_path10 = require("path");
12912
14013
  function read_editor_config(project_path) {
12913
14014
  const config_path = import_path10.join(project_path, ".unity-agentic", "editor.json");
12914
- if (!import_fs18.existsSync(config_path)) {
14015
+ if (!import_fs19.existsSync(config_path)) {
12915
14016
  return { error: `Editor bridge not found at ${config_path}. Is the Unity Editor running with the bridge package installed?` };
12916
14017
  }
12917
14018
  let config;
12918
14019
  try {
12919
- const raw = import_fs18.readFileSync(config_path, "utf-8");
14020
+ const raw = import_fs19.readFileSync(config_path, "utf-8");
12920
14021
  config = JSON.parse(raw);
12921
14022
  } catch (err) {
12922
14023
  return { error: `Failed to parse editor.json: ${err instanceof Error ? err.message : String(err)}` };
@@ -12929,23 +14030,45 @@ function read_editor_config(project_path) {
12929
14030
  }
12930
14031
  return config;
12931
14032
  }
14033
+ var RETRYABLE_CODES = new Set([-32000, -32002, -32003]);
14034
+ var RETRY_DELAYS = [500, 1000, 2000];
12932
14035
  async function call_editor(options) {
12933
- const { method, params, timeout = 1e4 } = options;
14036
+ const maxRetries = options.retries ?? 2;
14037
+ let lastResponse;
14038
+ for (let attempt = 0;attempt <= maxRetries; attempt++) {
14039
+ lastResponse = await call_editor_once(options);
14040
+ if (!lastResponse.error || !RETRYABLE_CODES.has(lastResponse.error.code)) {
14041
+ return lastResponse;
14042
+ }
14043
+ if (attempt < maxRetries) {
14044
+ const delay = RETRY_DELAYS[Math.min(attempt, RETRY_DELAYS.length - 1)];
14045
+ await new Promise((r) => setTimeout(r, delay));
14046
+ }
14047
+ }
14048
+ return lastResponse;
14049
+ }
14050
+ function call_editor_once(options) {
14051
+ const { method, params, timeout = 1e4, no_wait } = options;
12934
14052
  const config = resolve_config(options);
12935
14053
  if ("error" in config) {
12936
- return {
14054
+ return Promise.resolve({
12937
14055
  jsonrpc: "2.0",
12938
14056
  id: "0",
12939
14057
  error: { code: -32000, message: config.error }
12940
- };
14058
+ });
12941
14059
  }
12942
14060
  const url = `ws://127.0.0.1:${config.port}/unity-agentic`;
12943
14061
  const request_id = generate_id();
14062
+ const wire_params = { ...params };
14063
+ if (timeout !== 1e4)
14064
+ wire_params._timeout = timeout;
14065
+ if (no_wait)
14066
+ wire_params.no_wait = true;
12944
14067
  const request = {
12945
14068
  jsonrpc: "2.0",
12946
14069
  id: request_id,
12947
14070
  method,
12948
- ...params ? { params } : {}
14071
+ ...Object.keys(wire_params).length > 0 ? { params: wire_params } : {}
12949
14072
  };
12950
14073
  return new Promise((resolve7) => {
12951
14074
  let resolved = false;
@@ -12967,6 +14090,20 @@ async function call_editor(options) {
12967
14090
  ws = new WebSocket(url);
12968
14091
  ws.onopen = () => {
12969
14092
  ws.send(JSON.stringify(request));
14093
+ if (no_wait && !resolved) {
14094
+ resolved = true;
14095
+ clearTimeout(timer);
14096
+ setTimeout(() => {
14097
+ try {
14098
+ ws.close();
14099
+ } catch {}
14100
+ }, 200);
14101
+ resolve7({
14102
+ jsonrpc: "2.0",
14103
+ id: request_id,
14104
+ result: { queued: true }
14105
+ });
14106
+ }
12970
14107
  };
12971
14108
  ws.onmessage = (event) => {
12972
14109
  try {
@@ -13022,42 +14159,75 @@ async function stream_editor(options) {
13022
14159
  if ("error" in config) {
13023
14160
  throw new Error(config.error);
13024
14161
  }
13025
- const url = `ws://127.0.0.1:${config.port}/unity-agentic`;
13026
- const request_id = generate_id();
13027
- const request = {
13028
- jsonrpc: "2.0",
13029
- id: request_id,
13030
- method,
13031
- ...params ? { params } : {}
13032
- };
14162
+ const MAX_RECONNECTS = 5;
14163
+ let reconnect_count = 0;
14164
+ let stopped = false;
13033
14165
  return new Promise((resolve7, reject) => {
13034
- let connected = false;
13035
- const timer = setTimeout(() => {
13036
- if (!connected) {
13037
- reject(new Error(`Timeout connecting to ${url}`));
13038
- }
13039
- }, timeout);
13040
- const ws = new WebSocket(url);
13041
- ws.onopen = () => {
13042
- connected = true;
13043
- clearTimeout(timer);
13044
- ws.send(JSON.stringify(request));
13045
- resolve7({ close: () => ws.close() });
13046
- };
13047
- ws.onmessage = (event) => {
13048
- try {
13049
- const data = JSON.parse(String(event.data));
13050
- if ("method" in data && !("id" in data)) {
13051
- on_event(data);
14166
+ let resolved = false;
14167
+ function connect(url) {
14168
+ const ws = new WebSocket(url);
14169
+ const request_id = generate_id();
14170
+ const request = {
14171
+ jsonrpc: "2.0",
14172
+ id: request_id,
14173
+ method,
14174
+ ...params ? { params } : {}
14175
+ };
14176
+ let connected = false;
14177
+ const timer = !resolved ? setTimeout(() => {
14178
+ if (!connected && !resolved) {
14179
+ reject(new Error(`Timeout connecting to ${url}`));
13052
14180
  }
13053
- } catch {}
13054
- };
13055
- ws.onerror = () => {
13056
- if (!connected) {
13057
- clearTimeout(timer);
13058
- reject(new Error(`WebSocket connection failed to ${url}`));
13059
- }
13060
- };
14181
+ }, timeout) : null;
14182
+ ws.onopen = () => {
14183
+ connected = true;
14184
+ if (timer)
14185
+ clearTimeout(timer);
14186
+ reconnect_count = 0;
14187
+ ws.send(JSON.stringify(request));
14188
+ if (!resolved) {
14189
+ resolved = true;
14190
+ resolve7({ close: () => {
14191
+ stopped = true;
14192
+ ws.close();
14193
+ } });
14194
+ }
14195
+ };
14196
+ ws.onmessage = (event) => {
14197
+ try {
14198
+ const data = JSON.parse(String(event.data));
14199
+ if ("method" in data && !("id" in data)) {
14200
+ on_event(data);
14201
+ }
14202
+ } catch {}
14203
+ };
14204
+ ws.onerror = () => {
14205
+ if (!connected && !resolved) {
14206
+ if (timer)
14207
+ clearTimeout(timer);
14208
+ reject(new Error(`WebSocket connection failed to ${url}`));
14209
+ }
14210
+ };
14211
+ ws.onclose = () => {
14212
+ if (stopped)
14213
+ return;
14214
+ if (!connected && !resolved)
14215
+ return;
14216
+ if (reconnect_count >= MAX_RECONNECTS)
14217
+ return;
14218
+ reconnect_count++;
14219
+ const delay = Math.min(500 * reconnect_count, 3000);
14220
+ setTimeout(() => {
14221
+ if (stopped)
14222
+ return;
14223
+ const fresh_config = resolve_config(options);
14224
+ if ("error" in fresh_config)
14225
+ return;
14226
+ connect(`ws://127.0.0.1:${fresh_config.port}/unity-agentic`);
14227
+ }, delay);
14228
+ };
14229
+ }
14230
+ connect(`ws://127.0.0.1:${config.port}/unity-agentic`);
13061
14231
  });
13062
14232
  }
13063
14233
  function resolve_config(options) {
@@ -13101,12 +14271,12 @@ function get_common_options(cmd) {
13101
14271
  const port = port_str ? parseInt(port_str, 10) : undefined;
13102
14272
  return { project_path, timeout, port };
13103
14273
  }
13104
- function build_call_options(cmd, method, params) {
14274
+ function build_call_options(cmd, method, params, extra) {
13105
14275
  const { project_path, timeout, port } = get_common_options(cmd);
13106
- return { project_path, method, params, timeout, port };
14276
+ return { project_path, method, params, timeout, port, ...extra?.no_wait ? { no_wait: true } : {} };
13107
14277
  }
13108
- async function handle_rpc(cmd, method, params) {
13109
- const options = build_call_options(cmd, method, params);
14278
+ async function handle_rpc(cmd, method, params, extra) {
14279
+ const options = build_call_options(cmd, method, params, extra);
13110
14280
  const response = await call_editor(options);
13111
14281
  output_response(response);
13112
14282
  }
@@ -13183,24 +14353,27 @@ function build_editor_command() {
13183
14353
  console.log(JSON.stringify(response.result, null, 2));
13184
14354
  }
13185
14355
  });
13186
- cmd.command("invoke <type> <member> [args...]").description("Call a static Unity Editor API method or read/set a static property").option("--set <value>", "Set a static property value").option("--args <json>", "JSON array of method arguments (overrides positional args)").action(async function(type, member, args, options) {
14356
+ cmd.command("invoke <type> <member> [args...]").description("Call a static Unity Editor API method or read/set a static property").option("--set <value>", "Set a static property value").option("--args <json>", "JSON array of method arguments (overrides positional args)").option("--no-wait", "Fire and forget -- return immediately without waiting for result").action(async function(type, member, args, options) {
13187
14357
  const params = { type, member };
13188
14358
  if (options.set !== undefined) {
13189
14359
  params.set = options.set;
13190
14360
  } else if (options.args) {
13191
14361
  params.args = options.args;
14362
+ } else if (args.length === 1 && args[0].startsWith("[")) {
14363
+ params.args = args[0];
13192
14364
  } else if (args.length > 0) {
13193
14365
  params.args = JSON.stringify(args);
13194
14366
  }
13195
- await handle_rpc(this, "editor.invoke", params);
14367
+ await handle_rpc(this, "editor.invoke", params, { no_wait: options.noWait });
13196
14368
  });
13197
- cmd.command("console-logs").description("Get recent console log entries").option("-c, --count <n>", "Number of log entries to retrieve", "50").option("--limit <n>", "Alias for --count").option("-t, --type <type>", "Filter by log type (Log, Warning, Error, Assert, Exception)").action(async function(options) {
14369
+ cmd.command("console-logs").description("Get recent console log entries").option("-c, --count <n>", "Number of log entries to retrieve", "50").option("--limit <n>", "Alias for --count").option("-t, --type <type>", "Filter by log type (Log, Warning, Error, Assert, Exception)").option("-s, --severity <type>", "Alias for --type").action(async function(options) {
13198
14370
  const params = {};
13199
14371
  const count = options.limit || options.count;
13200
14372
  if (count)
13201
14373
  params.count = parseInt(count, 10);
13202
- if (options.type)
13203
- params.type = options.type;
14374
+ const typeFilter = options.severity || options.type;
14375
+ if (typeFilter)
14376
+ params.type = typeFilter;
13204
14377
  await handle_rpc(this, "editor.console.getLogs", params);
13205
14378
  });
13206
14379
  cmd.command("console-clear").description("Clear the console log buffer").action(async function() {
@@ -13217,7 +14390,7 @@ function build_editor_command() {
13217
14390
  port,
13218
14391
  method: "editor.console.subscribe",
13219
14392
  on_event: (event) => {
13220
- if (event.method === "editor.console.logReceived" || event.method === "editor.event.logMessage") {
14393
+ if (event.method === "editor.console.logReceived") {
13221
14394
  const params = event.params ?? {};
13222
14395
  if (type_filter && typeof params.type === "string" && params.type.toLowerCase() !== type_filter) {
13223
14396
  return;
@@ -13475,23 +14648,12 @@ function build_editor_command() {
13475
14648
  process.exitCode = 1;
13476
14649
  }
13477
14650
  });
13478
- cmd.command("log").allowUnknownOption().argument("[args...]").action(() => {
13479
- console.log(JSON.stringify({
13480
- success: false,
13481
- error: '"editor log" does not exist. Use one of:',
13482
- alternatives: [
13483
- { command: "editor console-logs", description: "Get recent console entries from the running Editor (live bridge)" },
13484
- { command: "read log", description: "Read and filter the Unity Editor.log file on disk (no bridge needed)" }
13485
- ]
13486
- }, null, 2));
13487
- process.exitCode = 1;
13488
- });
13489
14651
  return cmd;
13490
14652
  }
13491
14653
 
13492
14654
  // src/project-search.ts
13493
14655
  init_scanner();
13494
- var import_fs19 = require("fs");
14656
+ var import_fs20 = require("fs");
13495
14657
  var path8 = __toESM(require("path"));
13496
14658
  var BINARY_EXTENSIONS = new Set([
13497
14659
  ".png",
@@ -13554,7 +14716,7 @@ function walk_project_files_js(project_path, extensions, exclude_dirs) {
13554
14716
  function walk(dir) {
13555
14717
  let entries;
13556
14718
  try {
13557
- entries = import_fs19.readdirSync(dir);
14719
+ entries = import_fs20.readdirSync(dir);
13558
14720
  } catch {
13559
14721
  return;
13560
14722
  }
@@ -13562,7 +14724,7 @@ function walk_project_files_js(project_path, extensions, exclude_dirs) {
13562
14724
  const full = path8.join(dir, entry);
13563
14725
  let stat;
13564
14726
  try {
13565
- stat = import_fs19.statSync(full);
14727
+ stat = import_fs20.statSync(full);
13566
14728
  } catch {
13567
14729
  continue;
13568
14730
  }
@@ -13579,14 +14741,14 @@ function walk_project_files_js(project_path, extensions, exclude_dirs) {
13579
14741
  }
13580
14742
  }
13581
14743
  const assetsDir = path8.join(project_path, "Assets");
13582
- if (import_fs19.existsSync(assetsDir)) {
14744
+ if (import_fs20.existsSync(assetsDir)) {
13583
14745
  walk(assetsDir);
13584
14746
  } else {
13585
14747
  walk(project_path);
13586
14748
  }
13587
14749
  if (extSet.has(".asset")) {
13588
14750
  const settingsDir = path8.join(project_path, "ProjectSettings");
13589
- if (import_fs19.existsSync(settingsDir)) {
14751
+ if (import_fs20.existsSync(settingsDir)) {
13590
14752
  walk(settingsDir);
13591
14753
  }
13592
14754
  }
@@ -13627,7 +14789,7 @@ function search_project(options) {
13627
14789
  error: "Name pattern must not be empty"
13628
14790
  };
13629
14791
  }
13630
- if (!import_fs19.existsSync(project_path)) {
14792
+ if (!import_fs20.existsSync(project_path)) {
13631
14793
  return {
13632
14794
  success: false,
13633
14795
  project_path,
@@ -13669,7 +14831,7 @@ function search_project(options) {
13669
14831
  }
13670
14832
  let display_name = fileName;
13671
14833
  try {
13672
- const content = import_fs19.readFileSync(file, "utf-8");
14834
+ const content = import_fs20.readFileSync(file, "utf-8");
13673
14835
  const nameMatch = /^\s*m_Name:\s*(.+)$/m.exec(content.slice(0, 2000));
13674
14836
  if (nameMatch)
13675
14837
  display_name = nameMatch[1].trim();
@@ -13850,7 +15012,7 @@ function grep_project_js(options) {
13850
15012
  max_results = 100,
13851
15013
  context_lines = 0
13852
15014
  } = options;
13853
- if (!import_fs19.existsSync(project_path)) {
15015
+ if (!import_fs20.existsSync(project_path)) {
13854
15016
  return {
13855
15017
  success: false,
13856
15018
  project_path,
@@ -13940,7 +15102,7 @@ function grep_project_js(options) {
13940
15102
  totalFilesScanned++;
13941
15103
  let content;
13942
15104
  try {
13943
- content = import_fs19.readFileSync(file, "utf-8");
15105
+ content = import_fs20.readFileSync(file, "utf-8");
13944
15106
  } catch {
13945
15107
  continue;
13946
15108
  }
@@ -14219,9 +15381,9 @@ program.command("status").description("Show current configuration and status").o
14219
15381
  let config = null;
14220
15382
  let guidCacheCount = 0;
14221
15383
  try {
14222
- const { existsSync: existsSync22, readFileSync: readFileSync15 } = require("fs");
15384
+ const { existsSync: existsSync22, readFileSync: readFileSync17 } = require("fs");
14223
15385
  if (existsSync22(configFile)) {
14224
- config = JSON.parse(readFileSync15(configFile, "utf-8"));
15386
+ config = JSON.parse(readFileSync17(configFile, "utf-8"));
14225
15387
  }
14226
15388
  const guidCacheObj = load_guid_cache(projectPath);
14227
15389
  guidCacheCount = guidCacheObj?.count ?? 0;