uloop-cli 0.56.0 → 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.
- package/dist/cli.bundle.cjs +680 -16
- package/dist/cli.bundle.cjs.map +4 -4
- package/package.json +7 -6
- package/src/__tests__/cli-e2e.test.ts +40 -0
- package/src/cli.ts +13 -1
- package/src/commands/launch.ts +125 -0
- package/src/default-tools.json +1 -1
- package/src/skills/skill-definitions/cli-only/uloop-launch/Skill/SKILL.md +51 -0
- package/src/version.ts +1 -1
package/dist/cli.bundle.cjs
CHANGED
|
@@ -5384,7 +5384,7 @@ var require_semver2 = __commonJS({
|
|
|
5384
5384
|
|
|
5385
5385
|
// src/cli.ts
|
|
5386
5386
|
var import_fs6 = require("fs");
|
|
5387
|
-
var
|
|
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((
|
|
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
|
-
|
|
5495
|
+
resolve5();
|
|
5496
5496
|
});
|
|
5497
5497
|
});
|
|
5498
5498
|
}
|
|
@@ -5508,7 +5508,7 @@ var DirectUnityClient = class {
|
|
|
5508
5508
|
};
|
|
5509
5509
|
const requestJson = JSON.stringify(request);
|
|
5510
5510
|
const framedMessage = createFrame(requestJson);
|
|
5511
|
-
return new Promise((
|
|
5511
|
+
return new Promise((resolve5, reject) => {
|
|
5512
5512
|
const socket = this.socket;
|
|
5513
5513
|
const timeoutId = setTimeout(() => {
|
|
5514
5514
|
reject(
|
|
@@ -5539,7 +5539,7 @@ var DirectUnityClient = class {
|
|
|
5539
5539
|
reject(new Error(`Unity error: ${response.error.message}`));
|
|
5540
5540
|
return;
|
|
5541
5541
|
}
|
|
5542
|
-
|
|
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.
|
|
5766
|
+
version: "0.57.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.
|
|
6218
|
+
var VERSION = "0.57.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((
|
|
6281
|
+
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
6282
6282
|
}
|
|
6283
6283
|
function isRetryableError(error) {
|
|
6284
6284
|
if (!(error instanceof Error)) {
|
|
@@ -7250,8 +7250,671 @@ 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
|
+
|
|
7253
7908
|
// src/cli.ts
|
|
7254
|
-
var BUILTIN_COMMANDS = [
|
|
7909
|
+
var BUILTIN_COMMANDS = [
|
|
7910
|
+
"list",
|
|
7911
|
+
"sync",
|
|
7912
|
+
"completion",
|
|
7913
|
+
"update",
|
|
7914
|
+
"fix",
|
|
7915
|
+
"skills",
|
|
7916
|
+
"launch"
|
|
7917
|
+
];
|
|
7255
7918
|
var program2 = new Command();
|
|
7256
7919
|
program2.name("uloop").description("Unity MCP CLI - Direct communication with Unity Editor").version(VERSION, "-v, --version", "Output the version number");
|
|
7257
7920
|
program2.option("--list-commands", "List all command names (for shell completion)");
|
|
@@ -7272,6 +7935,7 @@ program2.command("fix").description("Clean up stale lock files that may prevent
|
|
|
7272
7935
|
cleanupLockFiles();
|
|
7273
7936
|
});
|
|
7274
7937
|
registerSkillsCommand(program2);
|
|
7938
|
+
registerLaunchCommand(program2);
|
|
7275
7939
|
function registerToolCommand(tool) {
|
|
7276
7940
|
const cmd = program2.command(tool.name).description(tool.description);
|
|
7277
7941
|
const properties = tool.inputSchema.properties;
|
|
@@ -7471,7 +8135,7 @@ async function runWithErrorHandling(fn) {
|
|
|
7471
8135
|
}
|
|
7472
8136
|
function detectShell() {
|
|
7473
8137
|
const shell = process.env["SHELL"] || "";
|
|
7474
|
-
const shellName = (0,
|
|
8138
|
+
const shellName = (0, import_path7.basename)(shell).replace(/\.exe$/i, "");
|
|
7475
8139
|
if (shellName === "zsh") {
|
|
7476
8140
|
return "zsh";
|
|
7477
8141
|
}
|
|
@@ -7486,12 +8150,12 @@ function detectShell() {
|
|
|
7486
8150
|
function getShellConfigPath(shell) {
|
|
7487
8151
|
const home = (0, import_os2.homedir)();
|
|
7488
8152
|
if (shell === "zsh") {
|
|
7489
|
-
return (0,
|
|
8153
|
+
return (0, import_path7.join)(home, ".zshrc");
|
|
7490
8154
|
}
|
|
7491
8155
|
if (shell === "powershell") {
|
|
7492
|
-
return (0,
|
|
8156
|
+
return (0, import_path7.join)(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
|
|
7493
8157
|
}
|
|
7494
|
-
return (0,
|
|
8158
|
+
return (0, import_path7.join)(home, ".bashrc");
|
|
7495
8159
|
}
|
|
7496
8160
|
function getCompletionScript(shell) {
|
|
7497
8161
|
if (shell === "bash") {
|
|
@@ -7628,10 +8292,10 @@ function cleanupLockFiles() {
|
|
|
7628
8292
|
console.error("Could not find Unity project root.");
|
|
7629
8293
|
process.exit(1);
|
|
7630
8294
|
}
|
|
7631
|
-
const tempDir = (0,
|
|
8295
|
+
const tempDir = (0, import_path7.join)(projectRoot, "Temp");
|
|
7632
8296
|
let cleaned = 0;
|
|
7633
8297
|
for (const lockFile of LOCK_FILES) {
|
|
7634
|
-
const lockPath = (0,
|
|
8298
|
+
const lockPath = (0, import_path7.join)(tempDir, lockFile);
|
|
7635
8299
|
if ((0, import_fs6.existsSync)(lockPath)) {
|
|
7636
8300
|
(0, import_fs6.unlinkSync)(lockPath);
|
|
7637
8301
|
console.log(`Removed: ${lockFile}`);
|
|
@@ -7668,7 +8332,7 @@ function handleCompletion(install, shellOverride) {
|
|
|
7668
8332
|
return;
|
|
7669
8333
|
}
|
|
7670
8334
|
const configPath = getShellConfigPath(shell);
|
|
7671
|
-
const configDir = (0,
|
|
8335
|
+
const configDir = (0, import_path7.dirname)(configPath);
|
|
7672
8336
|
if (!(0, import_fs6.existsSync)(configDir)) {
|
|
7673
8337
|
(0, import_fs6.mkdirSync)(configDir, { recursive: true });
|
|
7674
8338
|
}
|