uloop-cli 0.56.0 → 0.58.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. [For AI] 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] Run 'uloop focus-window' to bring Unity to the front, then retry the tool. If the issue persists, 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.56.0",
5766
+ version: "0.58.0",
5767
5767
  tools: [
5768
5768
  {
5769
5769
  name: "compile",
@@ -6215,7 +6215,7 @@ function getCachedServerVersion() {
6215
6215
  }
6216
6216
 
6217
6217
  // src/version.ts
6218
- var VERSION = "0.56.0";
6218
+ var VERSION = "0.58.0";
6219
6219
 
6220
6220
  // src/spinner.ts
6221
6221
  var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
@@ -6278,7 +6278,7 @@ function suppressStdinEcho() {
6278
6278
  var RETRY_DELAY_MS = 500;
6279
6279
  var MAX_RETRIES = 3;
6280
6280
  function sleep(ms) {
6281
- return new Promise((resolve2) => setTimeout(resolve2, ms));
6281
+ return new Promise((resolve5) => setTimeout(resolve5, ms));
6282
6282
  }
6283
6283
  function isRetryableError(error) {
6284
6284
  if (!(error instanceof Error)) {
@@ -7250,8 +7250,715 @@ Uninstalling uloop skills (${location})...`);
7250
7250
  }
7251
7251
  }
7252
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
+
7908
+ // src/commands/focus-window.ts
7909
+ function registerFocusWindowCommand(program3) {
7910
+ program3.command("focus-window").description("Bring Unity Editor window to front using OS-level commands").action(async () => {
7911
+ const projectRoot = findUnityProjectRoot();
7912
+ if (projectRoot === null) {
7913
+ console.error(
7914
+ JSON.stringify({
7915
+ Success: false,
7916
+ Message: "Unity project not found"
7917
+ })
7918
+ );
7919
+ process.exit(1);
7920
+ }
7921
+ const runningProcess = await findRunningUnityProcess(projectRoot);
7922
+ if (!runningProcess) {
7923
+ console.error(
7924
+ JSON.stringify({
7925
+ Success: false,
7926
+ Message: "No running Unity process found for this project"
7927
+ })
7928
+ );
7929
+ process.exit(1);
7930
+ }
7931
+ try {
7932
+ await focusUnityProcess(runningProcess.pid);
7933
+ console.log(
7934
+ JSON.stringify({
7935
+ Success: true,
7936
+ Message: `Unity Editor window focused (PID: ${runningProcess.pid})`
7937
+ })
7938
+ );
7939
+ } catch (error) {
7940
+ console.error(
7941
+ JSON.stringify({
7942
+ Success: false,
7943
+ Message: `Failed to focus Unity window: ${error instanceof Error ? error.message : String(error)}`
7944
+ })
7945
+ );
7946
+ process.exit(1);
7947
+ }
7948
+ });
7949
+ }
7950
+
7253
7951
  // src/cli.ts
7254
- var BUILTIN_COMMANDS = ["list", "sync", "completion", "update", "fix", "skills"];
7952
+ var BUILTIN_COMMANDS = [
7953
+ "list",
7954
+ "sync",
7955
+ "completion",
7956
+ "update",
7957
+ "fix",
7958
+ "skills",
7959
+ "launch",
7960
+ "focus-window"
7961
+ ];
7255
7962
  var program2 = new Command();
7256
7963
  program2.name("uloop").description("Unity MCP CLI - Direct communication with Unity Editor").version(VERSION, "-v, --version", "Output the version number");
7257
7964
  program2.option("--list-commands", "List all command names (for shell completion)");
@@ -7272,7 +7979,12 @@ program2.command("fix").description("Clean up stale lock files that may prevent
7272
7979
  cleanupLockFiles();
7273
7980
  });
7274
7981
  registerSkillsCommand(program2);
7982
+ registerLaunchCommand(program2);
7983
+ registerFocusWindowCommand(program2);
7275
7984
  function registerToolCommand(tool) {
7985
+ if (BUILTIN_COMMANDS.includes(tool.name)) {
7986
+ return;
7987
+ }
7276
7988
  const cmd = program2.command(tool.name).description(tool.description);
7277
7989
  const properties = tool.inputSchema.properties;
7278
7990
  for (const [propName, propInfo] of Object.entries(properties)) {
@@ -7471,7 +8183,7 @@ async function runWithErrorHandling(fn) {
7471
8183
  }
7472
8184
  function detectShell() {
7473
8185
  const shell = process.env["SHELL"] || "";
7474
- const shellName = (0, import_path6.basename)(shell).replace(/\.exe$/i, "");
8186
+ const shellName = (0, import_path7.basename)(shell).replace(/\.exe$/i, "");
7475
8187
  if (shellName === "zsh") {
7476
8188
  return "zsh";
7477
8189
  }
@@ -7486,12 +8198,12 @@ function detectShell() {
7486
8198
  function getShellConfigPath(shell) {
7487
8199
  const home = (0, import_os2.homedir)();
7488
8200
  if (shell === "zsh") {
7489
- return (0, import_path6.join)(home, ".zshrc");
8201
+ return (0, import_path7.join)(home, ".zshrc");
7490
8202
  }
7491
8203
  if (shell === "powershell") {
7492
- return (0, import_path6.join)(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
8204
+ return (0, import_path7.join)(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
7493
8205
  }
7494
- return (0, import_path6.join)(home, ".bashrc");
8206
+ return (0, import_path7.join)(home, ".bashrc");
7495
8207
  }
7496
8208
  function getCompletionScript(shell) {
7497
8209
  if (shell === "bash") {
@@ -7628,10 +8340,10 @@ function cleanupLockFiles() {
7628
8340
  console.error("Could not find Unity project root.");
7629
8341
  process.exit(1);
7630
8342
  }
7631
- const tempDir = (0, import_path6.join)(projectRoot, "Temp");
8343
+ const tempDir = (0, import_path7.join)(projectRoot, "Temp");
7632
8344
  let cleaned = 0;
7633
8345
  for (const lockFile of LOCK_FILES) {
7634
- const lockPath = (0, import_path6.join)(tempDir, lockFile);
8346
+ const lockPath = (0, import_path7.join)(tempDir, lockFile);
7635
8347
  if ((0, import_fs6.existsSync)(lockPath)) {
7636
8348
  (0, import_fs6.unlinkSync)(lockPath);
7637
8349
  console.log(`Removed: ${lockFile}`);
@@ -7668,7 +8380,7 @@ function handleCompletion(install, shellOverride) {
7668
8380
  return;
7669
8381
  }
7670
8382
  const configPath = getShellConfigPath(shell);
7671
- const configDir = (0, import_path6.dirname)(configPath);
8383
+ const configDir = (0, import_path7.dirname)(configPath);
7672
8384
  if (!(0, import_fs6.existsSync)(configDir)) {
7673
8385
  (0, import_fs6.mkdirSync)(configDir, { recursive: true });
7674
8386
  }