uloop-cli 0.55.2 → 0.57.0

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.
@@ -5384,7 +5384,7 @@ var require_semver2 = __commonJS({
5384
5384
 
5385
5385
  // src/cli.ts
5386
5386
  var import_fs6 = require("fs");
5387
- var import_path6 = require("path");
5387
+ var import_path7 = require("path");
5388
5388
  var import_os2 = require("os");
5389
5389
  var import_child_process = require("child_process");
5390
5390
 
@@ -5486,13 +5486,13 @@ var DirectUnityClient = class {
5486
5486
  requestId = 0;
5487
5487
  receiveBuffer = Buffer.alloc(0);
5488
5488
  async connect() {
5489
- return new Promise((resolve2, reject) => {
5489
+ return new Promise((resolve5, reject) => {
5490
5490
  this.socket = new net.Socket();
5491
5491
  this.socket.on("error", (error) => {
5492
5492
  reject(new Error(`Connection error: ${error.message}`));
5493
5493
  });
5494
5494
  this.socket.connect(this.port, this.host, () => {
5495
- resolve2();
5495
+ resolve5();
5496
5496
  });
5497
5497
  });
5498
5498
  }
@@ -5508,12 +5508,12 @@ var DirectUnityClient = class {
5508
5508
  };
5509
5509
  const requestJson = JSON.stringify(request);
5510
5510
  const framedMessage = createFrame(requestJson);
5511
- return new Promise((resolve2, reject) => {
5511
+ return new Promise((resolve5, reject) => {
5512
5512
  const socket = this.socket;
5513
5513
  const timeoutId = setTimeout(() => {
5514
5514
  reject(
5515
5515
  new Error(
5516
- `Request timed out after ${NETWORK_TIMEOUT_MS}ms. Unity may be frozen or busy. Please report this to the user and ask how to proceed. Do NOT kill Unity processes without user permission.`
5516
+ `Request timed out after ${NETWORK_TIMEOUT_MS}ms. Unity may be frozen or busy. [For AI] Please report this to the user and ask how to proceed. Do NOT kill Unity processes without user permission.`
5517
5517
  )
5518
5518
  );
5519
5519
  }, NETWORK_TIMEOUT_MS);
@@ -5539,7 +5539,7 @@ var DirectUnityClient = class {
5539
5539
  reject(new Error(`Unity error: ${response.error.message}`));
5540
5540
  return;
5541
5541
  }
5542
- resolve2(response.result);
5542
+ resolve5(response.result);
5543
5543
  };
5544
5544
  socket.on("data", onData);
5545
5545
  socket.write(framedMessage);
@@ -5763,7 +5763,7 @@ var import_path3 = require("path");
5763
5763
 
5764
5764
  // src/default-tools.json
5765
5765
  var default_tools_default = {
5766
- version: "0.55.2",
5766
+ version: "0.57.0",
5767
5767
  tools: [
5768
5768
  {
5769
5769
  name: "compile",
@@ -5906,6 +5906,11 @@ var default_tools_default = {
5906
5906
  IncludePaths: {
5907
5907
  type: "boolean",
5908
5908
  description: "Include path information"
5909
+ },
5910
+ UseSelection: {
5911
+ type: "boolean",
5912
+ description: "Use selected GameObject(s) as root(s). When true, RootPath is ignored.",
5913
+ default: false
5909
5914
  }
5910
5915
  }
5911
5916
  }
@@ -6195,9 +6200,22 @@ function hasCacheFile() {
6195
6200
  function getCacheFilePath() {
6196
6201
  return getCachePath();
6197
6202
  }
6203
+ function getCachedServerVersion() {
6204
+ const cachePath = getCachePath();
6205
+ if (!(0, import_fs3.existsSync)(cachePath)) {
6206
+ return void 0;
6207
+ }
6208
+ try {
6209
+ const content = (0, import_fs3.readFileSync)(cachePath, "utf-8");
6210
+ const cache = JSON.parse(content);
6211
+ return typeof cache.serverVersion === "string" ? cache.serverVersion : void 0;
6212
+ } catch {
6213
+ return void 0;
6214
+ }
6215
+ }
6198
6216
 
6199
6217
  // src/version.ts
6200
- var VERSION = "0.55.2";
6218
+ var VERSION = "0.57.0";
6201
6219
 
6202
6220
  // src/spinner.ts
6203
6221
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
@@ -6260,7 +6278,7 @@ function suppressStdinEcho() {
6260
6278
  var RETRY_DELAY_MS = 500;
6261
6279
  var MAX_RETRIES = 3;
6262
6280
  function sleep(ms) {
6263
- return new Promise((resolve2) => setTimeout(resolve2, ms));
6281
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
6264
6282
  }
6265
6283
  function isRetryableError(error) {
6266
6284
  if (!(error instanceof Error)) {
@@ -6441,6 +6459,7 @@ async function syncTools(globalOptions) {
6441
6459
  }
6442
6460
  const cache = {
6443
6461
  version: VERSION,
6462
+ serverVersion: result.Ver,
6444
6463
  updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
6445
6464
  tools: result.Tools.map((tool) => ({
6446
6465
  name: tool.name,
@@ -7231,8 +7250,671 @@ Uninstalling uloop skills (${location})...`);
7231
7250
  }
7232
7251
  }
7233
7252
 
7253
+ // src/commands/launch.ts
7254
+ var import_path6 = require("path");
7255
+
7256
+ // node_modules/launch-unity/dist/lib.js
7257
+ var import_node_child_process = require("node:child_process");
7258
+ var import_node_fs2 = require("node:fs");
7259
+ var import_promises3 = require("node:fs/promises");
7260
+ var import_node_path2 = require("node:path");
7261
+ var import_node_util = require("node:util");
7262
+
7263
+ // node_modules/launch-unity/dist/unityHub.js
7264
+ var import_promises2 = require("node:fs/promises");
7265
+ var import_node_fs = require("node:fs");
7266
+ var import_node_path = require("node:path");
7267
+ var resolveUnityHubProjectFiles = () => {
7268
+ if (process.platform === "darwin") {
7269
+ const home = process.env.HOME;
7270
+ if (!home) {
7271
+ return [];
7272
+ }
7273
+ const base = (0, import_node_path.join)(home, "Library", "Application Support", "UnityHub");
7274
+ return [(0, import_node_path.join)(base, "projects-v1.json"), (0, import_node_path.join)(base, "projects.json")];
7275
+ }
7276
+ if (process.platform === "win32") {
7277
+ const appData = process.env.APPDATA;
7278
+ if (!appData) {
7279
+ return [];
7280
+ }
7281
+ const base = (0, import_node_path.join)(appData, "UnityHub");
7282
+ return [(0, import_node_path.join)(base, "projects-v1.json"), (0, import_node_path.join)(base, "projects.json")];
7283
+ }
7284
+ return [];
7285
+ };
7286
+ var removeTrailingSeparators = (target) => {
7287
+ let trimmed = target;
7288
+ while (trimmed.length > 1 && (trimmed.endsWith("/") || trimmed.endsWith("\\"))) {
7289
+ trimmed = trimmed.slice(0, -1);
7290
+ }
7291
+ return trimmed;
7292
+ };
7293
+ var normalizePath = (target) => {
7294
+ const resolvedPath = (0, import_node_path.resolve)(target);
7295
+ return removeTrailingSeparators(resolvedPath);
7296
+ };
7297
+ var resolvePathWithActualCase = (target) => {
7298
+ try {
7299
+ return removeTrailingSeparators(import_node_fs.realpathSync.native(target));
7300
+ } catch {
7301
+ return normalizePath(target);
7302
+ }
7303
+ };
7304
+ var toComparablePath = (value) => {
7305
+ return value.replace(/\\/g, "/").toLocaleLowerCase();
7306
+ };
7307
+ var pathsEqual = (left, right) => {
7308
+ return toComparablePath(normalizePath(left)) === toComparablePath(normalizePath(right));
7309
+ };
7310
+ var safeParseProjectsJson = (content) => {
7311
+ try {
7312
+ return JSON.parse(content);
7313
+ } catch {
7314
+ return void 0;
7315
+ }
7316
+ };
7317
+ var logDebug = (message) => {
7318
+ if (process.env["LAUNCH_UNITY_DEBUG"] === "1") {
7319
+ console.log(`[unityHub] ${message}`);
7320
+ }
7321
+ };
7322
+ var ensureProjectEntryAndUpdate = async (projectPath, version, when, setFavorite = false) => {
7323
+ const canonicalProjectPath = resolvePathWithActualCase(projectPath);
7324
+ const projectTitle = (0, import_node_path.basename)(canonicalProjectPath);
7325
+ const containingFolderPath = (0, import_node_path.dirname)(canonicalProjectPath);
7326
+ const candidates = resolveUnityHubProjectFiles();
7327
+ if (candidates.length === 0) {
7328
+ logDebug("No Unity Hub project files found.");
7329
+ return;
7330
+ }
7331
+ for (const path of candidates) {
7332
+ logDebug(`Trying Unity Hub file: ${path}`);
7333
+ const content = await (0, import_promises2.readFile)(path, "utf8").catch(() => void 0);
7334
+ if (!content) {
7335
+ logDebug("Read failed or empty content, skipping.");
7336
+ continue;
7337
+ }
7338
+ const json = safeParseProjectsJson(content);
7339
+ if (!json) {
7340
+ logDebug("Parse failed, skipping.");
7341
+ continue;
7342
+ }
7343
+ const data = { ...json.data ?? {} };
7344
+ const existingKey = Object.keys(data).find((key) => {
7345
+ const entryPath = data[key]?.path;
7346
+ return entryPath ? pathsEqual(entryPath, projectPath) : false;
7347
+ });
7348
+ const targetKey = existingKey ?? canonicalProjectPath;
7349
+ const existingEntry = existingKey ? data[existingKey] : void 0;
7350
+ logDebug(existingKey ? `Found existing entry for project (key=${existingKey}). Updating lastModified.` : `No existing entry. Adding new entry (key=${targetKey}).`);
7351
+ const updatedEntry = {
7352
+ ...existingEntry,
7353
+ path: existingEntry?.path ?? canonicalProjectPath,
7354
+ containingFolderPath: existingEntry?.containingFolderPath ?? containingFolderPath,
7355
+ version: existingEntry?.version ?? version,
7356
+ title: existingEntry?.title ?? projectTitle,
7357
+ lastModified: when.getTime(),
7358
+ isFavorite: setFavorite ? true : existingEntry?.isFavorite ?? false
7359
+ };
7360
+ const updatedJson = {
7361
+ ...json,
7362
+ data: {
7363
+ ...data,
7364
+ [targetKey]: updatedEntry
7365
+ }
7366
+ };
7367
+ try {
7368
+ await (0, import_promises2.writeFile)(path, JSON.stringify(updatedJson, void 0, 2), "utf8");
7369
+ logDebug("Write succeeded.");
7370
+ } catch (error) {
7371
+ logDebug(`Write failed: ${error instanceof Error ? error.message : String(error)}`);
7372
+ }
7373
+ return;
7374
+ }
7375
+ };
7376
+ var updateLastModifiedIfExists = async (projectPath, when) => {
7377
+ const candidates = resolveUnityHubProjectFiles();
7378
+ if (candidates.length === 0) {
7379
+ return;
7380
+ }
7381
+ for (const path of candidates) {
7382
+ let content;
7383
+ let json;
7384
+ try {
7385
+ content = await (0, import_promises2.readFile)(path, "utf8");
7386
+ } catch {
7387
+ continue;
7388
+ }
7389
+ try {
7390
+ json = JSON.parse(content);
7391
+ } catch {
7392
+ continue;
7393
+ }
7394
+ if (!json.data) {
7395
+ return;
7396
+ }
7397
+ const projectKey = Object.keys(json.data).find((key) => {
7398
+ const entryPath = json.data?.[key]?.path;
7399
+ return entryPath ? pathsEqual(entryPath, projectPath) : false;
7400
+ });
7401
+ if (!projectKey) {
7402
+ return;
7403
+ }
7404
+ const original = json.data[projectKey];
7405
+ if (!original) {
7406
+ return;
7407
+ }
7408
+ json.data[projectKey] = {
7409
+ ...original,
7410
+ lastModified: when.getTime()
7411
+ };
7412
+ try {
7413
+ await (0, import_promises2.writeFile)(path, JSON.stringify(json, void 0, 2), "utf8");
7414
+ } catch {
7415
+ }
7416
+ return;
7417
+ }
7418
+ };
7419
+
7420
+ // node_modules/launch-unity/dist/lib.js
7421
+ var execFileAsync = (0, import_node_util.promisify)(import_node_child_process.execFile);
7422
+ var UNITY_EXECUTABLE_PATTERN_MAC = /Unity\.app\/Contents\/MacOS\/Unity/i;
7423
+ var UNITY_EXECUTABLE_PATTERN_WINDOWS = /Unity\.exe/i;
7424
+ var PROJECT_PATH_PATTERN = /-(?:projectPath|projectpath)(?:=|\s+)("[^"]+"|'[^']+'|[^\s"']+)/i;
7425
+ var PROCESS_LIST_COMMAND_MAC = "ps";
7426
+ var PROCESS_LIST_ARGS_MAC = ["-axo", "pid=,command=", "-ww"];
7427
+ var WINDOWS_POWERSHELL = "powershell";
7428
+ var UNITY_LOCKFILE_NAME = "UnityLockfile";
7429
+ var TEMP_DIRECTORY_NAME = "Temp";
7430
+ function getUnityVersion(projectPath) {
7431
+ const versionFile = (0, import_node_path2.join)(projectPath, "ProjectSettings", "ProjectVersion.txt");
7432
+ if (!(0, import_node_fs2.existsSync)(versionFile)) {
7433
+ throw new Error(`ProjectVersion.txt not found at ${versionFile}. This does not appear to be a Unity project.`);
7434
+ }
7435
+ const content = (0, import_node_fs2.readFileSync)(versionFile, "utf8");
7436
+ const version = content.match(/m_EditorVersion:\s*([^\s\n]+)/)?.[1];
7437
+ if (!version) {
7438
+ throw new Error(`Could not extract Unity version from ${versionFile}`);
7439
+ }
7440
+ return version;
7441
+ }
7442
+ function getUnityPathWindows(version) {
7443
+ const candidates = [];
7444
+ const programFiles = process.env["PROGRAMFILES"];
7445
+ const programFilesX86 = process.env["PROGRAMFILES(X86)"];
7446
+ const localAppData = process.env["LOCALAPPDATA"];
7447
+ const addCandidate = (base) => {
7448
+ if (!base) {
7449
+ return;
7450
+ }
7451
+ candidates.push((0, import_node_path2.join)(base, "Unity", "Hub", "Editor", version, "Editor", "Unity.exe"));
7452
+ };
7453
+ addCandidate(programFiles);
7454
+ addCandidate(programFilesX86);
7455
+ addCandidate(localAppData);
7456
+ candidates.push((0, import_node_path2.join)("C:\\", "Program Files", "Unity", "Hub", "Editor", version, "Editor", "Unity.exe"));
7457
+ for (const candidate of candidates) {
7458
+ if ((0, import_node_fs2.existsSync)(candidate)) {
7459
+ return candidate;
7460
+ }
7461
+ }
7462
+ return candidates[0] ?? (0, import_node_path2.join)("C:\\", "Program Files", "Unity", "Hub", "Editor", version, "Editor", "Unity.exe");
7463
+ }
7464
+ function getUnityPath(version) {
7465
+ if (process.platform === "darwin") {
7466
+ return `/Applications/Unity/Hub/Editor/${version}/Unity.app/Contents/MacOS/Unity`;
7467
+ }
7468
+ if (process.platform === "win32") {
7469
+ return getUnityPathWindows(version);
7470
+ }
7471
+ return `/Applications/Unity/Hub/Editor/${version}/Unity.app/Contents/MacOS/Unity`;
7472
+ }
7473
+ var removeTrailingSeparators2 = (target) => {
7474
+ let trimmed = target;
7475
+ while (trimmed.length > 1 && (trimmed.endsWith("/") || trimmed.endsWith("\\"))) {
7476
+ trimmed = trimmed.slice(0, -1);
7477
+ }
7478
+ return trimmed;
7479
+ };
7480
+ var normalizePath2 = (target) => {
7481
+ const resolvedPath = (0, import_node_path2.resolve)(target);
7482
+ const trimmed = removeTrailingSeparators2(resolvedPath);
7483
+ return trimmed;
7484
+ };
7485
+ var toComparablePath2 = (value) => {
7486
+ return value.replace(/\\/g, "/").toLocaleLowerCase();
7487
+ };
7488
+ var pathsEqual2 = (left, right) => {
7489
+ return toComparablePath2(normalizePath2(left)) === toComparablePath2(normalizePath2(right));
7490
+ };
7491
+ function extractProjectPath(command) {
7492
+ const match = command.match(PROJECT_PATH_PATTERN);
7493
+ if (!match) {
7494
+ return void 0;
7495
+ }
7496
+ const raw = match[1];
7497
+ if (!raw) {
7498
+ return void 0;
7499
+ }
7500
+ const trimmed = raw.trim();
7501
+ if (trimmed.startsWith('"') && trimmed.endsWith('"')) {
7502
+ return trimmed.slice(1, -1);
7503
+ }
7504
+ if (trimmed.startsWith("'") && trimmed.endsWith("'")) {
7505
+ return trimmed.slice(1, -1);
7506
+ }
7507
+ return trimmed;
7508
+ }
7509
+ var isUnityAuxiliaryProcess = (command) => {
7510
+ const normalizedCommand = command.toLowerCase();
7511
+ if (normalizedCommand.includes("-batchmode")) {
7512
+ return true;
7513
+ }
7514
+ return normalizedCommand.includes("assetimportworker");
7515
+ };
7516
+ async function listUnityProcessesMac() {
7517
+ let stdout = "";
7518
+ try {
7519
+ const result = await execFileAsync(PROCESS_LIST_COMMAND_MAC, PROCESS_LIST_ARGS_MAC);
7520
+ stdout = result.stdout;
7521
+ } catch (error) {
7522
+ const message = error instanceof Error ? error.message : String(error);
7523
+ console.error(`Failed to retrieve Unity process list: ${message}`);
7524
+ return [];
7525
+ }
7526
+ const lines = stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
7527
+ const processes = [];
7528
+ for (const line of lines) {
7529
+ const match = line.match(/^(\d+)\s+(.*)$/);
7530
+ if (!match) {
7531
+ continue;
7532
+ }
7533
+ const pidValue = Number.parseInt(match[1] ?? "", 10);
7534
+ if (!Number.isFinite(pidValue)) {
7535
+ continue;
7536
+ }
7537
+ const command = match[2] ?? "";
7538
+ if (!UNITY_EXECUTABLE_PATTERN_MAC.test(command)) {
7539
+ continue;
7540
+ }
7541
+ if (isUnityAuxiliaryProcess(command)) {
7542
+ continue;
7543
+ }
7544
+ const projectArgument = extractProjectPath(command);
7545
+ if (!projectArgument) {
7546
+ continue;
7547
+ }
7548
+ processes.push({
7549
+ pid: pidValue,
7550
+ projectPath: normalizePath2(projectArgument)
7551
+ });
7552
+ }
7553
+ return processes;
7554
+ }
7555
+ async function listUnityProcessesWindows() {
7556
+ const scriptLines = [
7557
+ "$ErrorActionPreference = 'Stop'",
7558
+ `$processes = Get-CimInstance Win32_Process -Filter "Name = 'Unity.exe'" | Where-Object { $_.CommandLine }`,
7559
+ "foreach ($process in $processes) {",
7560
+ ` $commandLine = $process.CommandLine -replace "\`r", ' ' -replace "\`n", ' '`,
7561
+ ' Write-Output ("{0}|{1}" -f $process.ProcessId, $commandLine)',
7562
+ "}"
7563
+ ];
7564
+ let stdout = "";
7565
+ try {
7566
+ const result = await execFileAsync(WINDOWS_POWERSHELL, ["-NoProfile", "-Command", scriptLines.join("\n")]);
7567
+ stdout = result.stdout ?? "";
7568
+ } catch (error) {
7569
+ const message = error instanceof Error ? error.message : String(error);
7570
+ console.error(`Failed to retrieve Unity process list on Windows: ${message}`);
7571
+ return [];
7572
+ }
7573
+ const lines = stdout.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
7574
+ const processes = [];
7575
+ for (const line of lines) {
7576
+ const delimiterIndex = line.indexOf("|");
7577
+ if (delimiterIndex < 0) {
7578
+ continue;
7579
+ }
7580
+ const pidText = line.slice(0, delimiterIndex).trim();
7581
+ const command = line.slice(delimiterIndex + 1).trim();
7582
+ const pidValue = Number.parseInt(pidText, 10);
7583
+ if (!Number.isFinite(pidValue)) {
7584
+ continue;
7585
+ }
7586
+ if (!UNITY_EXECUTABLE_PATTERN_WINDOWS.test(command)) {
7587
+ continue;
7588
+ }
7589
+ if (isUnityAuxiliaryProcess(command)) {
7590
+ continue;
7591
+ }
7592
+ const projectArgument = extractProjectPath(command);
7593
+ if (!projectArgument) {
7594
+ continue;
7595
+ }
7596
+ processes.push({
7597
+ pid: pidValue,
7598
+ projectPath: normalizePath2(projectArgument)
7599
+ });
7600
+ }
7601
+ return processes;
7602
+ }
7603
+ async function listUnityProcesses() {
7604
+ if (process.platform === "darwin") {
7605
+ return await listUnityProcessesMac();
7606
+ }
7607
+ if (process.platform === "win32") {
7608
+ return await listUnityProcessesWindows();
7609
+ }
7610
+ return [];
7611
+ }
7612
+ async function findRunningUnityProcess(projectPath) {
7613
+ const normalizedTarget = normalizePath2(projectPath);
7614
+ const processes = await listUnityProcesses();
7615
+ return processes.find((candidate) => pathsEqual2(candidate.projectPath, normalizedTarget));
7616
+ }
7617
+ async function focusUnityProcess(pid) {
7618
+ if (process.platform === "darwin") {
7619
+ await focusUnityProcessMac(pid);
7620
+ return;
7621
+ }
7622
+ if (process.platform === "win32") {
7623
+ await focusUnityProcessWindows(pid);
7624
+ }
7625
+ }
7626
+ async function focusUnityProcessMac(pid) {
7627
+ const script = `tell application "System Events" to set frontmost of (first process whose unix id is ${pid}) to true`;
7628
+ try {
7629
+ await execFileAsync("osascript", ["-e", script]);
7630
+ console.log("Brought existing Unity to the front.");
7631
+ } catch (error) {
7632
+ const message = error instanceof Error ? error.message : String(error);
7633
+ console.warn(`Failed to bring Unity to front: ${message}`);
7634
+ }
7635
+ }
7636
+ async function focusUnityProcessWindows(pid) {
7637
+ const addTypeLines = [
7638
+ 'Add-Type -TypeDefinition @"',
7639
+ "using System;",
7640
+ "using System.Runtime.InteropServices;",
7641
+ "public static class Win32Interop {",
7642
+ ' [DllImport("user32.dll")] public static extern bool SetForegroundWindow(IntPtr hWnd);',
7643
+ ' [DllImport("user32.dll")] public static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow);',
7644
+ "}",
7645
+ '"@'
7646
+ ];
7647
+ const scriptLines = [
7648
+ "$ErrorActionPreference = 'Stop'",
7649
+ ...addTypeLines,
7650
+ `try { $process = Get-Process -Id ${pid} -ErrorAction Stop } catch { return }`,
7651
+ "$handle = $process.MainWindowHandle",
7652
+ "if ($handle -eq 0) { return }",
7653
+ "[Win32Interop]::ShowWindowAsync($handle, 9) | Out-Null",
7654
+ "[Win32Interop]::SetForegroundWindow($handle) | Out-Null"
7655
+ ];
7656
+ try {
7657
+ await execFileAsync(WINDOWS_POWERSHELL, ["-NoProfile", "-Command", scriptLines.join("\n")]);
7658
+ console.log("Brought existing Unity to the front.");
7659
+ } catch (error) {
7660
+ const message = error instanceof Error ? error.message : String(error);
7661
+ console.warn(`Failed to bring Unity to front on Windows: ${message}`);
7662
+ }
7663
+ }
7664
+ async function handleStaleLockfile(projectPath) {
7665
+ const tempDirectoryPath = (0, import_node_path2.join)(projectPath, TEMP_DIRECTORY_NAME);
7666
+ const lockfilePath = (0, import_node_path2.join)(tempDirectoryPath, UNITY_LOCKFILE_NAME);
7667
+ if (!(0, import_node_fs2.existsSync)(lockfilePath)) {
7668
+ return;
7669
+ }
7670
+ console.log(`UnityLockfile found without active Unity process: ${lockfilePath}`);
7671
+ console.log("Assuming previous crash. Cleaning Temp directory and continuing launch.");
7672
+ try {
7673
+ await (0, import_promises3.rm)(tempDirectoryPath, { recursive: true, force: true });
7674
+ console.log("Deleted Temp directory.");
7675
+ } catch (error) {
7676
+ const message = error instanceof Error ? error.message : String(error);
7677
+ console.warn(`Failed to delete Temp directory: ${message}`);
7678
+ }
7679
+ try {
7680
+ await (0, import_promises3.rm)(lockfilePath, { force: true });
7681
+ console.log("Deleted UnityLockfile.");
7682
+ } catch (error) {
7683
+ const message = error instanceof Error ? error.message : String(error);
7684
+ console.warn(`Failed to delete UnityLockfile: ${message}`);
7685
+ }
7686
+ }
7687
+ var KILL_POLL_INTERVAL_MS = 100;
7688
+ var KILL_TIMEOUT_MS = 1e4;
7689
+ function isProcessAlive(pid) {
7690
+ try {
7691
+ process.kill(pid, 0);
7692
+ return true;
7693
+ } catch {
7694
+ return false;
7695
+ }
7696
+ }
7697
+ function killProcess(pid) {
7698
+ try {
7699
+ process.kill(pid, "SIGKILL");
7700
+ } catch {
7701
+ }
7702
+ }
7703
+ async function waitForProcessExit(pid) {
7704
+ const start = Date.now();
7705
+ while (Date.now() - start < KILL_TIMEOUT_MS) {
7706
+ if (!isProcessAlive(pid)) {
7707
+ return true;
7708
+ }
7709
+ await new Promise((resolve5) => setTimeout(resolve5, KILL_POLL_INTERVAL_MS));
7710
+ }
7711
+ return false;
7712
+ }
7713
+ async function killRunningUnity(projectPath) {
7714
+ const processInfo = await findRunningUnityProcess(projectPath);
7715
+ if (!processInfo) {
7716
+ console.log("No running Unity process found for this project.");
7717
+ return;
7718
+ }
7719
+ const pid = processInfo.pid;
7720
+ console.log(`Killing Unity (PID: ${pid})...`);
7721
+ killProcess(pid);
7722
+ const exited = await waitForProcessExit(pid);
7723
+ if (!exited) {
7724
+ throw new Error(`Failed to kill Unity (PID: ${pid}) within ${KILL_TIMEOUT_MS / 1e3}s.`);
7725
+ }
7726
+ console.log("Unity killed.");
7727
+ }
7728
+ function hasBuildTargetArg(unityArgs) {
7729
+ for (const arg of unityArgs) {
7730
+ if (arg === "-buildTarget") {
7731
+ return true;
7732
+ }
7733
+ if (arg.startsWith("-buildTarget=")) {
7734
+ return true;
7735
+ }
7736
+ }
7737
+ return false;
7738
+ }
7739
+ var EXCLUDED_DIR_NAMES = /* @__PURE__ */ new Set([
7740
+ "library",
7741
+ "temp",
7742
+ "logs",
7743
+ "obj",
7744
+ ".git",
7745
+ "node_modules",
7746
+ ".idea",
7747
+ ".vscode",
7748
+ ".vs"
7749
+ ]);
7750
+ function isUnityProjectRoot(candidateDir) {
7751
+ const versionFile = (0, import_node_path2.join)(candidateDir, "ProjectSettings", "ProjectVersion.txt");
7752
+ return (0, import_node_fs2.existsSync)(versionFile);
7753
+ }
7754
+ function listSubdirectoriesSorted(dir) {
7755
+ let entries = [];
7756
+ try {
7757
+ const dirents = (0, import_node_fs2.readdirSync)(dir, { withFileTypes: true });
7758
+ const subdirs = dirents.filter((d) => d.isDirectory()).map((d) => d.name).filter((name) => !EXCLUDED_DIR_NAMES.has(name.toLocaleLowerCase()));
7759
+ subdirs.sort((a, b) => a.localeCompare(b));
7760
+ entries = subdirs.map((name) => (0, import_node_path2.join)(dir, name));
7761
+ } catch {
7762
+ entries = [];
7763
+ }
7764
+ return entries;
7765
+ }
7766
+ function findUnityProjectBfs(rootDir, maxDepth) {
7767
+ const queue = [];
7768
+ let rootCanonical;
7769
+ try {
7770
+ rootCanonical = (0, import_node_fs2.realpathSync)(rootDir);
7771
+ } catch {
7772
+ rootCanonical = rootDir;
7773
+ }
7774
+ queue.push({ dir: rootCanonical, depth: 0 });
7775
+ const visited = /* @__PURE__ */ new Set([toComparablePath2(normalizePath2(rootCanonical))]);
7776
+ while (queue.length > 0) {
7777
+ const current = queue.shift();
7778
+ if (!current) {
7779
+ continue;
7780
+ }
7781
+ const { dir, depth } = current;
7782
+ if (isUnityProjectRoot(dir)) {
7783
+ return normalizePath2(dir);
7784
+ }
7785
+ const canDescend = maxDepth === -1 || depth < maxDepth;
7786
+ if (!canDescend) {
7787
+ continue;
7788
+ }
7789
+ const children = listSubdirectoriesSorted(dir);
7790
+ for (const child of children) {
7791
+ let childCanonical = child;
7792
+ try {
7793
+ const stat = (0, import_node_fs2.lstatSync)(child);
7794
+ if (stat.isSymbolicLink()) {
7795
+ try {
7796
+ childCanonical = (0, import_node_fs2.realpathSync)(child);
7797
+ } catch {
7798
+ continue;
7799
+ }
7800
+ }
7801
+ } catch {
7802
+ continue;
7803
+ }
7804
+ const key = toComparablePath2(normalizePath2(childCanonical));
7805
+ if (visited.has(key)) {
7806
+ continue;
7807
+ }
7808
+ visited.add(key);
7809
+ queue.push({ dir: childCanonical, depth: depth + 1 });
7810
+ }
7811
+ }
7812
+ return void 0;
7813
+ }
7814
+ function launch(opts) {
7815
+ const { projectPath, platform, unityArgs, unityVersion } = opts;
7816
+ const unityPath = getUnityPath(unityVersion);
7817
+ console.log(`Detected Unity version: ${unityVersion}`);
7818
+ if (!(0, import_node_fs2.existsSync)(unityPath)) {
7819
+ throw new Error(`Unity ${unityVersion} not found at ${unityPath}. Please install Unity through Unity Hub.`);
7820
+ }
7821
+ console.log("Opening Unity...");
7822
+ console.log(`Project Path: ${projectPath}`);
7823
+ const args = ["-projectPath", projectPath];
7824
+ const unityArgsContainBuildTarget = hasBuildTargetArg(unityArgs);
7825
+ if (platform && platform.length > 0 && !unityArgsContainBuildTarget) {
7826
+ args.push("-buildTarget", platform);
7827
+ }
7828
+ if (unityArgs.length > 0) {
7829
+ args.push(...unityArgs);
7830
+ }
7831
+ const child = (0, import_node_child_process.spawn)(unityPath, args, { stdio: "ignore", detached: true });
7832
+ child.unref();
7833
+ }
7834
+
7835
+ // src/commands/launch.ts
7836
+ function registerLaunchCommand(program3) {
7837
+ program3.command("launch").description("Launch Unity project with matching Editor version").argument("[project-path]", "Path to Unity project").option("-r, --restart", "Kill running Unity and restart").option("-p, --platform <platform>", "Build target (e.g., Android, iOS)").option("--max-depth <n>", "Search depth when project-path is omitted", "3").option("-a, --add-unity-hub", "Add to Unity Hub (does not launch)").option("-f, --favorite", "Add to Unity Hub as favorite (does not launch)").action(async (projectPath, options) => {
7838
+ await runLaunchCommand(projectPath, options);
7839
+ });
7840
+ }
7841
+ function parseMaxDepth(value) {
7842
+ if (value === void 0) {
7843
+ return 3;
7844
+ }
7845
+ const parsed = parseInt(value, 10);
7846
+ if (Number.isNaN(parsed)) {
7847
+ console.error(`Error: Invalid --max-depth value: "${value}". Must be an integer.`);
7848
+ process.exit(1);
7849
+ }
7850
+ return parsed;
7851
+ }
7852
+ async function runLaunchCommand(projectPath, options) {
7853
+ const maxDepth = parseMaxDepth(options.maxDepth);
7854
+ let resolvedProjectPath = projectPath ? (0, import_path6.resolve)(projectPath) : void 0;
7855
+ if (!resolvedProjectPath) {
7856
+ const searchRoot = process.cwd();
7857
+ const depthInfo = maxDepth === -1 ? "unlimited" : String(maxDepth);
7858
+ console.log(
7859
+ `No project-path provided. Searching under ${searchRoot} (max-depth: ${depthInfo})...`
7860
+ );
7861
+ const found = findUnityProjectBfs(searchRoot, maxDepth);
7862
+ if (!found) {
7863
+ console.error(`Error: Unity project not found under ${searchRoot}.`);
7864
+ process.exit(1);
7865
+ }
7866
+ console.log(`Selected project: ${found}`);
7867
+ resolvedProjectPath = found;
7868
+ }
7869
+ const unityVersion = getUnityVersion(resolvedProjectPath);
7870
+ const unityHubOnlyMode = options.addUnityHub === true || options.favorite === true;
7871
+ if (unityHubOnlyMode) {
7872
+ console.log(`Detected Unity version: ${unityVersion}`);
7873
+ console.log(`Project Path: ${resolvedProjectPath}`);
7874
+ const now2 = /* @__PURE__ */ new Date();
7875
+ await ensureProjectEntryAndUpdate(
7876
+ resolvedProjectPath,
7877
+ unityVersion,
7878
+ now2,
7879
+ options.favorite === true
7880
+ );
7881
+ console.log("Unity Hub entry updated.");
7882
+ return;
7883
+ }
7884
+ if (options.restart === true) {
7885
+ await killRunningUnity(resolvedProjectPath);
7886
+ } else {
7887
+ const runningProcess = await findRunningUnityProcess(resolvedProjectPath);
7888
+ if (runningProcess) {
7889
+ console.log(
7890
+ `Unity process already running for project: ${resolvedProjectPath} (PID: ${runningProcess.pid})`
7891
+ );
7892
+ await focusUnityProcess(runningProcess.pid);
7893
+ return;
7894
+ }
7895
+ }
7896
+ await handleStaleLockfile(resolvedProjectPath);
7897
+ const resolved = {
7898
+ projectPath: resolvedProjectPath,
7899
+ platform: options.platform,
7900
+ unityArgs: [],
7901
+ unityVersion
7902
+ };
7903
+ launch(resolved);
7904
+ const now = /* @__PURE__ */ new Date();
7905
+ await updateLastModifiedIfExists(resolvedProjectPath, now);
7906
+ }
7907
+
7234
7908
  // src/cli.ts
7235
- var BUILTIN_COMMANDS = ["list", "sync", "completion", "update", "fix", "skills"];
7909
+ var BUILTIN_COMMANDS = [
7910
+ "list",
7911
+ "sync",
7912
+ "completion",
7913
+ "update",
7914
+ "fix",
7915
+ "skills",
7916
+ "launch"
7917
+ ];
7236
7918
  var program2 = new Command();
7237
7919
  program2.name("uloop").description("Unity MCP CLI - Direct communication with Unity Editor").version(VERSION, "-v, --version", "Output the version number");
7238
7920
  program2.option("--list-commands", "List all command names (for shell completion)");
@@ -7253,6 +7935,7 @@ program2.command("fix").description("Clean up stale lock files that may prevent
7253
7935
  cleanupLockFiles();
7254
7936
  });
7255
7937
  registerSkillsCommand(program2);
7938
+ registerLaunchCommand(program2);
7256
7939
  function registerToolCommand(tool) {
7257
7940
  const cmd = program2.command(tool.name).description(tool.description);
7258
7941
  const properties = tool.inputSchema.properties;
@@ -7386,6 +8069,30 @@ function printConnectionError() {
7386
8069
  console.error("");
7387
8070
  console.error("[For AI] Please report the above to the user.");
7388
8071
  }
8072
+ function printVersionMismatchDiagnostic() {
8073
+ const serverVersion = getCachedServerVersion();
8074
+ if (serverVersion === void 0) {
8075
+ return;
8076
+ }
8077
+ if (serverVersion === VERSION) {
8078
+ return;
8079
+ }
8080
+ const isCliOlder = isVersionOlder(VERSION, serverVersion);
8081
+ const action = isCliOlder ? "Update" : "Downgrade";
8082
+ const updateCommand = `npm install -g uloop-cli@${serverVersion}`;
8083
+ console.error("");
8084
+ console.error("\x1B[33m\u2501\u2501\u2501 Version Mismatch Diagnostic \u2501\u2501\u2501\x1B[0m");
8085
+ console.error("");
8086
+ console.error(` CLI version: ${VERSION}`);
8087
+ console.error(` Unity package: ${serverVersion}`);
8088
+ console.error(" Version mismatch may cause communication errors.");
8089
+ console.error("");
8090
+ console.error(` To fix: ${action} CLI to match Unity package version`);
8091
+ console.error(` ${updateCommand}`);
8092
+ console.error("");
8093
+ console.error("[For AI] Please ask the user if they would like to run this command.");
8094
+ console.error("\x1B[33m\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\u2501\x1B[0m");
8095
+ }
7389
8096
  async function runWithErrorHandling(fn) {
7390
8097
  try {
7391
8098
  await fn();
@@ -7409,10 +8116,17 @@ async function runWithErrorHandling(fn) {
7409
8116
  if (message === "UNITY_NO_RESPONSE") {
7410
8117
  console.error("\x1B[33m\u23F3 Unity is busy (no response received).\x1B[0m");
7411
8118
  console.error("Unity may be compiling, reloading, or starting. Please wait and try again.");
8119
+ printVersionMismatchDiagnostic();
7412
8120
  process.exit(1);
7413
8121
  }
7414
8122
  if (isConnectionError(message)) {
7415
8123
  printConnectionError();
8124
+ printVersionMismatchDiagnostic();
8125
+ process.exit(1);
8126
+ }
8127
+ if (message.includes("Request timed out")) {
8128
+ console.error(`\x1B[31mError: ${message}\x1B[0m`);
8129
+ printVersionMismatchDiagnostic();
7416
8130
  process.exit(1);
7417
8131
  }
7418
8132
  console.error(`\x1B[31mError: ${message}\x1B[0m`);
@@ -7421,7 +8135,7 @@ async function runWithErrorHandling(fn) {
7421
8135
  }
7422
8136
  function detectShell() {
7423
8137
  const shell = process.env["SHELL"] || "";
7424
- const shellName = (0, import_path6.basename)(shell).replace(/\.exe$/i, "");
8138
+ const shellName = (0, import_path7.basename)(shell).replace(/\.exe$/i, "");
7425
8139
  if (shellName === "zsh") {
7426
8140
  return "zsh";
7427
8141
  }
@@ -7436,12 +8150,12 @@ function detectShell() {
7436
8150
  function getShellConfigPath(shell) {
7437
8151
  const home = (0, import_os2.homedir)();
7438
8152
  if (shell === "zsh") {
7439
- return (0, import_path6.join)(home, ".zshrc");
8153
+ return (0, import_path7.join)(home, ".zshrc");
7440
8154
  }
7441
8155
  if (shell === "powershell") {
7442
- return (0, import_path6.join)(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
8156
+ return (0, import_path7.join)(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
7443
8157
  }
7444
- return (0, import_path6.join)(home, ".bashrc");
8158
+ return (0, import_path7.join)(home, ".bashrc");
7445
8159
  }
7446
8160
  function getCompletionScript(shell) {
7447
8161
  if (shell === "bash") {
@@ -7578,10 +8292,10 @@ function cleanupLockFiles() {
7578
8292
  console.error("Could not find Unity project root.");
7579
8293
  process.exit(1);
7580
8294
  }
7581
- const tempDir = (0, import_path6.join)(projectRoot, "Temp");
8295
+ const tempDir = (0, import_path7.join)(projectRoot, "Temp");
7582
8296
  let cleaned = 0;
7583
8297
  for (const lockFile of LOCK_FILES) {
7584
- const lockPath = (0, import_path6.join)(tempDir, lockFile);
8298
+ const lockPath = (0, import_path7.join)(tempDir, lockFile);
7585
8299
  if ((0, import_fs6.existsSync)(lockPath)) {
7586
8300
  (0, import_fs6.unlinkSync)(lockPath);
7587
8301
  console.log(`Removed: ${lockFile}`);
@@ -7618,7 +8332,7 @@ function handleCompletion(install, shellOverride) {
7618
8332
  return;
7619
8333
  }
7620
8334
  const configPath = getShellConfigPath(shell);
7621
- const configDir = (0, import_path6.dirname)(configPath);
8335
+ const configDir = (0, import_path7.dirname)(configPath);
7622
8336
  if (!(0, import_fs6.existsSync)(configDir)) {
7623
8337
  (0, import_fs6.mkdirSync)(configDir, { recursive: true });
7624
8338
  }