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