uloop-cli 0.44.2 → 0.45.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.bundle.cjs +412 -151
- package/dist/cli.bundle.cjs.map +4 -4
- package/package.json +1 -1
- package/src/arg-parser.ts +3 -0
- package/src/cli.ts +70 -23
- package/src/default-tools.json +1 -1
- package/src/direct-unity-client.ts +3 -0
- package/src/execute-tool.ts +212 -50
- package/src/port-resolver.ts +3 -0
- package/src/project-root.ts +3 -0
- package/src/skills/skill-definitions/uloop-compile/SKILL.md +10 -0
- package/src/skills/skills-command.ts +106 -36
- package/src/skills/skills-manager.ts +44 -30
- package/src/skills/target-config.ts +34 -0
- package/src/spinner.ts +49 -0
- package/src/tool-cache.ts +3 -0
- package/src/version.ts +1 -1
package/dist/cli.bundle.cjs
CHANGED
|
@@ -3453,8 +3453,8 @@ var require_commander = __commonJS({
|
|
|
3453
3453
|
});
|
|
3454
3454
|
|
|
3455
3455
|
// src/cli.ts
|
|
3456
|
-
var
|
|
3457
|
-
var
|
|
3456
|
+
var import_fs5 = require("fs");
|
|
3457
|
+
var import_path6 = require("path");
|
|
3458
3458
|
var import_os2 = require("os");
|
|
3459
3459
|
var import_child_process = require("child_process");
|
|
3460
3460
|
|
|
@@ -3475,6 +3475,11 @@ var {
|
|
|
3475
3475
|
Help
|
|
3476
3476
|
} = import_index.default;
|
|
3477
3477
|
|
|
3478
|
+
// src/execute-tool.ts
|
|
3479
|
+
var readline = __toESM(require("readline"), 1);
|
|
3480
|
+
var import_fs3 = require("fs");
|
|
3481
|
+
var import_path4 = require("path");
|
|
3482
|
+
|
|
3478
3483
|
// src/direct-unity-client.ts
|
|
3479
3484
|
var net = __toESM(require("net"), 1);
|
|
3480
3485
|
|
|
@@ -3685,7 +3690,7 @@ var import_path3 = require("path");
|
|
|
3685
3690
|
|
|
3686
3691
|
// src/default-tools.json
|
|
3687
3692
|
var default_tools_default = {
|
|
3688
|
-
version: "0.
|
|
3693
|
+
version: "0.45.2",
|
|
3689
3694
|
tools: [
|
|
3690
3695
|
{
|
|
3691
3696
|
name: "compile",
|
|
@@ -4082,9 +4087,96 @@ function getCacheFilePath() {
|
|
|
4082
4087
|
}
|
|
4083
4088
|
|
|
4084
4089
|
// src/version.ts
|
|
4085
|
-
var VERSION = "0.
|
|
4090
|
+
var VERSION = "0.45.2";
|
|
4091
|
+
|
|
4092
|
+
// src/spinner.ts
|
|
4093
|
+
var SPINNER_FRAMES = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
4094
|
+
var FRAME_INTERVAL_MS = 80;
|
|
4095
|
+
function createSpinner(initialMessage) {
|
|
4096
|
+
if (!process.stderr.isTTY) {
|
|
4097
|
+
return {
|
|
4098
|
+
update: () => {
|
|
4099
|
+
},
|
|
4100
|
+
stop: () => {
|
|
4101
|
+
}
|
|
4102
|
+
};
|
|
4103
|
+
}
|
|
4104
|
+
let frameIndex = 0;
|
|
4105
|
+
let currentMessage = initialMessage;
|
|
4106
|
+
const render = () => {
|
|
4107
|
+
const frame = SPINNER_FRAMES[frameIndex];
|
|
4108
|
+
process.stderr.write(`\r\x1B[K${frame} ${currentMessage}`);
|
|
4109
|
+
frameIndex = (frameIndex + 1) % SPINNER_FRAMES.length;
|
|
4110
|
+
};
|
|
4111
|
+
render();
|
|
4112
|
+
const intervalId = setInterval(render, FRAME_INTERVAL_MS);
|
|
4113
|
+
return {
|
|
4114
|
+
update(message) {
|
|
4115
|
+
currentMessage = message;
|
|
4116
|
+
},
|
|
4117
|
+
stop() {
|
|
4118
|
+
clearInterval(intervalId);
|
|
4119
|
+
process.stderr.write("\r\x1B[K");
|
|
4120
|
+
}
|
|
4121
|
+
};
|
|
4122
|
+
}
|
|
4086
4123
|
|
|
4087
4124
|
// src/execute-tool.ts
|
|
4125
|
+
function suppressStdinEcho() {
|
|
4126
|
+
if (!process.stdin.isTTY) {
|
|
4127
|
+
return () => {
|
|
4128
|
+
};
|
|
4129
|
+
}
|
|
4130
|
+
const rl = readline.createInterface({
|
|
4131
|
+
input: process.stdin,
|
|
4132
|
+
output: process.stdout,
|
|
4133
|
+
terminal: false
|
|
4134
|
+
});
|
|
4135
|
+
process.stdin.setRawMode(true);
|
|
4136
|
+
process.stdin.resume();
|
|
4137
|
+
const onData = (data) => {
|
|
4138
|
+
if (data[0] === 3) {
|
|
4139
|
+
process.exit(130);
|
|
4140
|
+
}
|
|
4141
|
+
};
|
|
4142
|
+
process.stdin.on("data", onData);
|
|
4143
|
+
return () => {
|
|
4144
|
+
process.stdin.off("data", onData);
|
|
4145
|
+
process.stdin.setRawMode(false);
|
|
4146
|
+
process.stdin.pause();
|
|
4147
|
+
rl.close();
|
|
4148
|
+
};
|
|
4149
|
+
}
|
|
4150
|
+
var RETRY_DELAY_MS = 500;
|
|
4151
|
+
var MAX_RETRIES = 3;
|
|
4152
|
+
function sleep(ms) {
|
|
4153
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
4154
|
+
}
|
|
4155
|
+
function isRetryableError(error) {
|
|
4156
|
+
if (!(error instanceof Error)) {
|
|
4157
|
+
return false;
|
|
4158
|
+
}
|
|
4159
|
+
const message = error.message;
|
|
4160
|
+
return message.includes("ECONNREFUSED") || message === "UNITY_NO_RESPONSE";
|
|
4161
|
+
}
|
|
4162
|
+
function checkUnityBusyState() {
|
|
4163
|
+
const projectRoot = findUnityProjectRoot();
|
|
4164
|
+
if (projectRoot === null) {
|
|
4165
|
+
return;
|
|
4166
|
+
}
|
|
4167
|
+
const compilingLock = (0, import_path4.join)(projectRoot, "Temp", "compiling.lock");
|
|
4168
|
+
if ((0, import_fs3.existsSync)(compilingLock)) {
|
|
4169
|
+
throw new Error("UNITY_COMPILING");
|
|
4170
|
+
}
|
|
4171
|
+
const domainReloadLock = (0, import_path4.join)(projectRoot, "Temp", "domainreload.lock");
|
|
4172
|
+
if ((0, import_fs3.existsSync)(domainReloadLock)) {
|
|
4173
|
+
throw new Error("UNITY_DOMAIN_RELOAD");
|
|
4174
|
+
}
|
|
4175
|
+
const serverStartingLock = (0, import_path4.join)(projectRoot, "Temp", "serverstarting.lock");
|
|
4176
|
+
if ((0, import_fs3.existsSync)(serverStartingLock)) {
|
|
4177
|
+
throw new Error("UNITY_SERVER_STARTING");
|
|
4178
|
+
}
|
|
4179
|
+
}
|
|
4088
4180
|
async function executeToolCommand(toolName, params, globalOptions) {
|
|
4089
4181
|
let portNumber;
|
|
4090
4182
|
if (globalOptions.port) {
|
|
@@ -4095,14 +4187,38 @@ async function executeToolCommand(toolName, params, globalOptions) {
|
|
|
4095
4187
|
portNumber = parsed;
|
|
4096
4188
|
}
|
|
4097
4189
|
const port = await resolveUnityPort(portNumber);
|
|
4098
|
-
const
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4190
|
+
const restoreStdin = suppressStdinEcho();
|
|
4191
|
+
const spinner = createSpinner("Connecting to Unity...");
|
|
4192
|
+
let lastError;
|
|
4193
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
4194
|
+
checkUnityBusyState();
|
|
4195
|
+
const client = new DirectUnityClient(port);
|
|
4196
|
+
try {
|
|
4197
|
+
await client.connect();
|
|
4198
|
+
spinner.update(`Executing ${toolName}...`);
|
|
4199
|
+
const result = await client.sendRequest(toolName, params);
|
|
4200
|
+
if (result === void 0 || result === null) {
|
|
4201
|
+
throw new Error("UNITY_NO_RESPONSE");
|
|
4202
|
+
}
|
|
4203
|
+
spinner.stop();
|
|
4204
|
+
restoreStdin();
|
|
4205
|
+
console.log(JSON.stringify(result, null, 2));
|
|
4206
|
+
return;
|
|
4207
|
+
} catch (error) {
|
|
4208
|
+
lastError = error;
|
|
4209
|
+
client.disconnect();
|
|
4210
|
+
if (!isRetryableError(error) || attempt >= MAX_RETRIES) {
|
|
4211
|
+
break;
|
|
4212
|
+
}
|
|
4213
|
+
spinner.update("Retrying connection...");
|
|
4214
|
+
await sleep(RETRY_DELAY_MS);
|
|
4215
|
+
} finally {
|
|
4216
|
+
client.disconnect();
|
|
4217
|
+
}
|
|
4105
4218
|
}
|
|
4219
|
+
spinner.stop();
|
|
4220
|
+
restoreStdin();
|
|
4221
|
+
throw lastError;
|
|
4106
4222
|
}
|
|
4107
4223
|
async function listAvailableTools(globalOptions) {
|
|
4108
4224
|
let portNumber;
|
|
@@ -4114,19 +4230,40 @@ async function listAvailableTools(globalOptions) {
|
|
|
4114
4230
|
portNumber = parsed;
|
|
4115
4231
|
}
|
|
4116
4232
|
const port = await resolveUnityPort(portNumber);
|
|
4117
|
-
const
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4233
|
+
const restoreStdin = suppressStdinEcho();
|
|
4234
|
+
const spinner = createSpinner("Connecting to Unity...");
|
|
4235
|
+
let lastError;
|
|
4236
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
4237
|
+
checkUnityBusyState();
|
|
4238
|
+
const client = new DirectUnityClient(port);
|
|
4239
|
+
try {
|
|
4240
|
+
await client.connect();
|
|
4241
|
+
spinner.update("Fetching tool list...");
|
|
4242
|
+
const result = await client.sendRequest("get-tool-details", { IncludeDevelopmentOnly: false });
|
|
4243
|
+
if (!result.Tools || !Array.isArray(result.Tools)) {
|
|
4244
|
+
throw new Error("Unexpected response from Unity: missing Tools array");
|
|
4245
|
+
}
|
|
4246
|
+
spinner.stop();
|
|
4247
|
+
restoreStdin();
|
|
4248
|
+
for (const tool of result.Tools) {
|
|
4249
|
+
console.log(` - ${tool.name}`);
|
|
4250
|
+
}
|
|
4251
|
+
return;
|
|
4252
|
+
} catch (error) {
|
|
4253
|
+
lastError = error;
|
|
4254
|
+
client.disconnect();
|
|
4255
|
+
if (!isRetryableError(error) || attempt >= MAX_RETRIES) {
|
|
4256
|
+
break;
|
|
4257
|
+
}
|
|
4258
|
+
spinner.update("Retrying connection...");
|
|
4259
|
+
await sleep(RETRY_DELAY_MS);
|
|
4260
|
+
} finally {
|
|
4261
|
+
client.disconnect();
|
|
4126
4262
|
}
|
|
4127
|
-
} finally {
|
|
4128
|
-
client.disconnect();
|
|
4129
4263
|
}
|
|
4264
|
+
spinner.stop();
|
|
4265
|
+
restoreStdin();
|
|
4266
|
+
throw lastError;
|
|
4130
4267
|
}
|
|
4131
4268
|
function convertProperties(unityProps) {
|
|
4132
4269
|
const result = {};
|
|
@@ -4150,35 +4287,57 @@ async function syncTools(globalOptions) {
|
|
|
4150
4287
|
portNumber = parsed;
|
|
4151
4288
|
}
|
|
4152
4289
|
const port = await resolveUnityPort(portNumber);
|
|
4153
|
-
const
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
4175
|
-
|
|
4176
|
-
|
|
4177
|
-
|
|
4290
|
+
const restoreStdin = suppressStdinEcho();
|
|
4291
|
+
const spinner = createSpinner("Connecting to Unity...");
|
|
4292
|
+
let lastError;
|
|
4293
|
+
for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) {
|
|
4294
|
+
checkUnityBusyState();
|
|
4295
|
+
const client = new DirectUnityClient(port);
|
|
4296
|
+
try {
|
|
4297
|
+
await client.connect();
|
|
4298
|
+
spinner.update("Syncing tools...");
|
|
4299
|
+
const result = await client.sendRequest("get-tool-details", { IncludeDevelopmentOnly: false });
|
|
4300
|
+
spinner.stop();
|
|
4301
|
+
if (!result.Tools || !Array.isArray(result.Tools)) {
|
|
4302
|
+
restoreStdin();
|
|
4303
|
+
throw new Error("Unexpected response from Unity: missing Tools array");
|
|
4304
|
+
}
|
|
4305
|
+
const cache = {
|
|
4306
|
+
version: VERSION,
|
|
4307
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4308
|
+
tools: result.Tools.map((tool) => ({
|
|
4309
|
+
name: tool.name,
|
|
4310
|
+
description: tool.description,
|
|
4311
|
+
inputSchema: {
|
|
4312
|
+
type: "object",
|
|
4313
|
+
properties: convertProperties(tool.parameterSchema.Properties),
|
|
4314
|
+
required: tool.parameterSchema.Required
|
|
4315
|
+
}
|
|
4316
|
+
}))
|
|
4317
|
+
};
|
|
4318
|
+
saveToolsCache(cache);
|
|
4319
|
+
console.log(`Synced ${cache.tools.length} tools to ${getCacheFilePath()}`);
|
|
4320
|
+
console.log("\nTools:");
|
|
4321
|
+
for (const tool of cache.tools) {
|
|
4322
|
+
console.log(` - ${tool.name}`);
|
|
4323
|
+
}
|
|
4324
|
+
restoreStdin();
|
|
4325
|
+
return;
|
|
4326
|
+
} catch (error) {
|
|
4327
|
+
lastError = error;
|
|
4328
|
+
client.disconnect();
|
|
4329
|
+
if (!isRetryableError(error) || attempt >= MAX_RETRIES) {
|
|
4330
|
+
break;
|
|
4331
|
+
}
|
|
4332
|
+
spinner.update("Retrying connection...");
|
|
4333
|
+
await sleep(RETRY_DELAY_MS);
|
|
4334
|
+
} finally {
|
|
4335
|
+
client.disconnect();
|
|
4178
4336
|
}
|
|
4179
|
-
} finally {
|
|
4180
|
-
client.disconnect();
|
|
4181
4337
|
}
|
|
4338
|
+
spinner.stop();
|
|
4339
|
+
restoreStdin();
|
|
4340
|
+
throw lastError;
|
|
4182
4341
|
}
|
|
4183
4342
|
|
|
4184
4343
|
// src/arg-parser.ts
|
|
@@ -4188,12 +4347,12 @@ function pascalToKebabCase(pascal) {
|
|
|
4188
4347
|
}
|
|
4189
4348
|
|
|
4190
4349
|
// src/skills/skills-manager.ts
|
|
4191
|
-
var
|
|
4192
|
-
var
|
|
4350
|
+
var import_fs4 = require("fs");
|
|
4351
|
+
var import_path5 = require("path");
|
|
4193
4352
|
var import_os = require("os");
|
|
4194
4353
|
|
|
4195
4354
|
// src/skills/skill-definitions/uloop-compile/SKILL.md
|
|
4196
|
-
var SKILL_default =
|
|
4355
|
+
var SKILL_default = '---\nname: uloop-compile\ndescription: Compile Unity project via uloop CLI. Use when you need to: (1) Verify C# code compiles successfully after editing scripts, (2) Check for compile errors or warnings, (3) Validate script changes before running tests.\n---\n\n# uloop compile\n\nExecute Unity project compilation.\n\n## Usage\n\n```bash\nuloop compile [--force-recompile]\n```\n\n## Parameters\n\n| Parameter | Type | Description |\n|-----------|------|-------------|\n| `--force-recompile` | boolean | Force full recompilation (triggers Domain Reload) |\n\n## Examples\n\n```bash\n# Check compilation\nuloop compile\n\n# Force full recompilation\nuloop compile --force-recompile\n```\n\n## Output\n\nReturns JSON:\n- `Success`: boolean\n- `ErrorCount`: number\n- `WarningCount`: number\n\n## Troubleshooting\n\nIf CLI hangs or shows "Unity is busy" errors after compilation, stale lock files may be preventing connection. Run the following to clean them up:\n\n```bash\nuloop fix\n```\n\nThis removes any leftover lock files (`compiling.lock`, `domainreload.lock`, `serverstarting.lock`) from the Unity project\'s Temp directory.\n';
|
|
4197
4356
|
|
|
4198
4357
|
// src/skills/skill-definitions/uloop-get-logs/SKILL.md
|
|
4199
4358
|
var SKILL_default2 = '---\nname: uloop-get-logs\ndescription: Retrieve Unity Console logs via uloop CLI. Use when you need to: (1) Check for errors or warnings after operations, (2) Debug runtime issues in Unity Editor, (3) Investigate unexpected behavior or exceptions.\n---\n\n# uloop get-logs\n\nRetrieve logs from Unity Console.\n\n## Usage\n\n```bash\nuloop get-logs [options]\n```\n\n## Parameters\n\n| Parameter | Type | Default | Description |\n|-----------|------|---------|-------------|\n| `--log-type` | string | `All` | Log type filter: `Error`, `Warning`, `Log`, `All` |\n| `--max-count` | integer | `100` | Maximum number of logs to retrieve |\n| `--search-text` | string | - | Text to search within logs |\n| `--include-stack-trace` | boolean | `true` | Include stack trace in output |\n| `--use-regex` | boolean | `false` | Use regex for search |\n| `--search-in-stack-trace` | boolean | `false` | Search within stack trace |\n\n## Examples\n\n```bash\n# Get all logs\nuloop get-logs\n\n# Get only errors\nuloop get-logs --log-type Error\n\n# Search for specific text\nuloop get-logs --search-text "NullReference"\n\n# Regex search\nuloop get-logs --search-text "Missing.*Component" --use-regex\n```\n\n## Output\n\nReturns JSON array of log entries with message, type, and optional stack trace.\n';
|
|
@@ -4269,69 +4428,69 @@ var BUNDLED_SKILLS = [
|
|
|
4269
4428
|
];
|
|
4270
4429
|
|
|
4271
4430
|
// src/skills/skills-manager.ts
|
|
4272
|
-
function getGlobalSkillsDir() {
|
|
4273
|
-
return (0,
|
|
4431
|
+
function getGlobalSkillsDir(target) {
|
|
4432
|
+
return (0, import_path5.join)((0, import_os.homedir)(), target.projectDir, "skills");
|
|
4274
4433
|
}
|
|
4275
|
-
function getProjectSkillsDir() {
|
|
4276
|
-
return (0,
|
|
4434
|
+
function getProjectSkillsDir(target) {
|
|
4435
|
+
return (0, import_path5.join)(process.cwd(), target.projectDir, "skills");
|
|
4277
4436
|
}
|
|
4278
|
-
function getSkillPath(skillDirName, global) {
|
|
4279
|
-
const baseDir = global ? getGlobalSkillsDir() : getProjectSkillsDir();
|
|
4280
|
-
return (0,
|
|
4437
|
+
function getSkillPath(skillDirName, target, global) {
|
|
4438
|
+
const baseDir = global ? getGlobalSkillsDir(target) : getProjectSkillsDir(target);
|
|
4439
|
+
return (0, import_path5.join)(baseDir, skillDirName, target.skillFileName);
|
|
4281
4440
|
}
|
|
4282
|
-
function isSkillInstalled(skill, global) {
|
|
4283
|
-
const skillPath = getSkillPath(skill.dirName, global);
|
|
4284
|
-
return (0,
|
|
4441
|
+
function isSkillInstalled(skill, target, global) {
|
|
4442
|
+
const skillPath = getSkillPath(skill.dirName, target, global);
|
|
4443
|
+
return (0, import_fs4.existsSync)(skillPath);
|
|
4285
4444
|
}
|
|
4286
|
-
function isSkillOutdated(skill, global) {
|
|
4287
|
-
const skillPath = getSkillPath(skill.dirName, global);
|
|
4288
|
-
if (!(0,
|
|
4445
|
+
function isSkillOutdated(skill, target, global) {
|
|
4446
|
+
const skillPath = getSkillPath(skill.dirName, target, global);
|
|
4447
|
+
if (!(0, import_fs4.existsSync)(skillPath)) {
|
|
4289
4448
|
return false;
|
|
4290
4449
|
}
|
|
4291
|
-
const installedContent = (0,
|
|
4450
|
+
const installedContent = (0, import_fs4.readFileSync)(skillPath, "utf-8");
|
|
4292
4451
|
return installedContent !== skill.content;
|
|
4293
4452
|
}
|
|
4294
|
-
function getSkillStatus(skill, global) {
|
|
4295
|
-
if (!isSkillInstalled(skill, global)) {
|
|
4453
|
+
function getSkillStatus(skill, target, global) {
|
|
4454
|
+
if (!isSkillInstalled(skill, target, global)) {
|
|
4296
4455
|
return "not_installed";
|
|
4297
4456
|
}
|
|
4298
|
-
if (isSkillOutdated(skill, global)) {
|
|
4457
|
+
if (isSkillOutdated(skill, target, global)) {
|
|
4299
4458
|
return "outdated";
|
|
4300
4459
|
}
|
|
4301
4460
|
return "installed";
|
|
4302
4461
|
}
|
|
4303
|
-
function getAllSkillStatuses(global) {
|
|
4462
|
+
function getAllSkillStatuses(target, global) {
|
|
4304
4463
|
return BUNDLED_SKILLS.map((skill) => ({
|
|
4305
4464
|
name: skill.name,
|
|
4306
|
-
status: getSkillStatus(skill, global),
|
|
4307
|
-
path: isSkillInstalled(skill, global) ? getSkillPath(skill.dirName, global) : void 0
|
|
4465
|
+
status: getSkillStatus(skill, target, global),
|
|
4466
|
+
path: isSkillInstalled(skill, target, global) ? getSkillPath(skill.dirName, target, global) : void 0
|
|
4308
4467
|
}));
|
|
4309
4468
|
}
|
|
4310
|
-
function installSkill(skill, global) {
|
|
4311
|
-
const baseDir = global ? getGlobalSkillsDir() : getProjectSkillsDir();
|
|
4312
|
-
const skillDir = (0,
|
|
4313
|
-
const skillPath = (0,
|
|
4314
|
-
(0,
|
|
4315
|
-
(0,
|
|
4469
|
+
function installSkill(skill, target, global) {
|
|
4470
|
+
const baseDir = global ? getGlobalSkillsDir(target) : getProjectSkillsDir(target);
|
|
4471
|
+
const skillDir = (0, import_path5.join)(baseDir, skill.dirName);
|
|
4472
|
+
const skillPath = (0, import_path5.join)(skillDir, target.skillFileName);
|
|
4473
|
+
(0, import_fs4.mkdirSync)(skillDir, { recursive: true });
|
|
4474
|
+
(0, import_fs4.writeFileSync)(skillPath, skill.content, "utf-8");
|
|
4316
4475
|
}
|
|
4317
|
-
function uninstallSkill(skill, global) {
|
|
4318
|
-
const baseDir = global ? getGlobalSkillsDir() : getProjectSkillsDir();
|
|
4319
|
-
const skillDir = (0,
|
|
4320
|
-
if (!(0,
|
|
4476
|
+
function uninstallSkill(skill, target, global) {
|
|
4477
|
+
const baseDir = global ? getGlobalSkillsDir(target) : getProjectSkillsDir(target);
|
|
4478
|
+
const skillDir = (0, import_path5.join)(baseDir, skill.dirName);
|
|
4479
|
+
if (!(0, import_fs4.existsSync)(skillDir)) {
|
|
4321
4480
|
return false;
|
|
4322
4481
|
}
|
|
4323
|
-
(0,
|
|
4482
|
+
(0, import_fs4.rmSync)(skillDir, { recursive: true, force: true });
|
|
4324
4483
|
return true;
|
|
4325
4484
|
}
|
|
4326
|
-
function installAllSkills(global) {
|
|
4485
|
+
function installAllSkills(target, global) {
|
|
4327
4486
|
const result = { installed: 0, updated: 0, skipped: 0 };
|
|
4328
4487
|
for (const skill of BUNDLED_SKILLS) {
|
|
4329
|
-
const status = getSkillStatus(skill, global);
|
|
4488
|
+
const status = getSkillStatus(skill, target, global);
|
|
4330
4489
|
if (status === "not_installed") {
|
|
4331
|
-
installSkill(skill, global);
|
|
4490
|
+
installSkill(skill, target, global);
|
|
4332
4491
|
result.installed++;
|
|
4333
4492
|
} else if (status === "outdated") {
|
|
4334
|
-
installSkill(skill, global);
|
|
4493
|
+
installSkill(skill, target, global);
|
|
4335
4494
|
result.updated++;
|
|
4336
4495
|
} else {
|
|
4337
4496
|
result.skipped++;
|
|
@@ -4339,10 +4498,10 @@ function installAllSkills(global) {
|
|
|
4339
4498
|
}
|
|
4340
4499
|
return result;
|
|
4341
4500
|
}
|
|
4342
|
-
function uninstallAllSkills(global) {
|
|
4501
|
+
function uninstallAllSkills(target, global) {
|
|
4343
4502
|
const result = { removed: 0, notFound: 0 };
|
|
4344
4503
|
for (const skill of BUNDLED_SKILLS) {
|
|
4345
|
-
if (uninstallSkill(skill, global)) {
|
|
4504
|
+
if (uninstallSkill(skill, target, global)) {
|
|
4346
4505
|
result.removed++;
|
|
4347
4506
|
} else {
|
|
4348
4507
|
result.notFound++;
|
|
@@ -4350,41 +4509,103 @@ function uninstallAllSkills(global) {
|
|
|
4350
4509
|
}
|
|
4351
4510
|
return result;
|
|
4352
4511
|
}
|
|
4353
|
-
function getInstallDir(global) {
|
|
4354
|
-
return global ? getGlobalSkillsDir() : getProjectSkillsDir();
|
|
4512
|
+
function getInstallDir(target, global) {
|
|
4513
|
+
return global ? getGlobalSkillsDir(target) : getProjectSkillsDir(target);
|
|
4355
4514
|
}
|
|
4356
4515
|
function getTotalSkillCount() {
|
|
4357
4516
|
return BUNDLED_SKILLS.length;
|
|
4358
4517
|
}
|
|
4359
4518
|
|
|
4519
|
+
// src/skills/target-config.ts
|
|
4520
|
+
var TARGET_CONFIGS = {
|
|
4521
|
+
claude: {
|
|
4522
|
+
id: "claude",
|
|
4523
|
+
displayName: "Claude Code",
|
|
4524
|
+
projectDir: ".claude",
|
|
4525
|
+
skillFileName: "SKILL.md"
|
|
4526
|
+
},
|
|
4527
|
+
codex: {
|
|
4528
|
+
id: "codex",
|
|
4529
|
+
displayName: "Codex CLI",
|
|
4530
|
+
projectDir: ".codex",
|
|
4531
|
+
skillFileName: "SKILL.md"
|
|
4532
|
+
}
|
|
4533
|
+
};
|
|
4534
|
+
var ALL_TARGET_IDS = ["claude", "codex"];
|
|
4535
|
+
function getTargetConfig(id) {
|
|
4536
|
+
return TARGET_CONFIGS[id];
|
|
4537
|
+
}
|
|
4538
|
+
|
|
4360
4539
|
// src/skills/skills-command.ts
|
|
4361
4540
|
function registerSkillsCommand(program3) {
|
|
4362
|
-
const skillsCmd = program3.command("skills").description("Manage uloop skills for
|
|
4363
|
-
skillsCmd.command("list").description("List all uloop skills and their installation status").option("-g, --global", "Check global installation
|
|
4364
|
-
|
|
4541
|
+
const skillsCmd = program3.command("skills").description("Manage uloop skills for AI coding tools");
|
|
4542
|
+
skillsCmd.command("list").description("List all uloop skills and their installation status").option("-g, --global", "Check global installation").option("--claude", "Check Claude Code installation").option("--codex", "Check Codex CLI installation").action((options) => {
|
|
4543
|
+
const targets = resolveTargets(options);
|
|
4544
|
+
const global = options.global ?? false;
|
|
4545
|
+
listSkills(targets, global);
|
|
4365
4546
|
});
|
|
4366
|
-
skillsCmd.command("install").description("Install all uloop skills").option("-g, --global", "Install to global location
|
|
4367
|
-
|
|
4547
|
+
skillsCmd.command("install").description("Install all uloop skills").option("-g, --global", "Install to global location").option("--claude", "Install to Claude Code").option("--codex", "Install to Codex CLI").action((options) => {
|
|
4548
|
+
const targets = resolveTargets(options);
|
|
4549
|
+
if (targets.length === 0) {
|
|
4550
|
+
showTargetGuidance("install");
|
|
4551
|
+
return;
|
|
4552
|
+
}
|
|
4553
|
+
installSkills(targets, options.global ?? false);
|
|
4368
4554
|
});
|
|
4369
|
-
skillsCmd.command("uninstall").description("Uninstall all uloop skills").option("-g, --global", "Uninstall from global location
|
|
4370
|
-
|
|
4555
|
+
skillsCmd.command("uninstall").description("Uninstall all uloop skills").option("-g, --global", "Uninstall from global location").option("--claude", "Uninstall from Claude Code").option("--codex", "Uninstall from Codex CLI").action((options) => {
|
|
4556
|
+
const targets = resolveTargets(options);
|
|
4557
|
+
if (targets.length === 0) {
|
|
4558
|
+
showTargetGuidance("uninstall");
|
|
4559
|
+
return;
|
|
4560
|
+
}
|
|
4561
|
+
uninstallSkills(targets, options.global ?? false);
|
|
4371
4562
|
});
|
|
4372
4563
|
}
|
|
4373
|
-
function
|
|
4564
|
+
function resolveTargets(options) {
|
|
4565
|
+
const targets = [];
|
|
4566
|
+
if (options.claude) {
|
|
4567
|
+
targets.push(getTargetConfig("claude"));
|
|
4568
|
+
}
|
|
4569
|
+
if (options.codex) {
|
|
4570
|
+
targets.push(getTargetConfig("codex"));
|
|
4571
|
+
}
|
|
4572
|
+
return targets;
|
|
4573
|
+
}
|
|
4574
|
+
function showTargetGuidance(command) {
|
|
4575
|
+
console.log(`
|
|
4576
|
+
Please specify at least one target for '${command}':`);
|
|
4577
|
+
console.log("");
|
|
4578
|
+
console.log("Available targets:");
|
|
4579
|
+
console.log(" --claude Claude Code (.claude/skills/)");
|
|
4580
|
+
console.log(" --codex Codex CLI (.codex/skills/)");
|
|
4581
|
+
console.log("");
|
|
4582
|
+
console.log("Options:");
|
|
4583
|
+
console.log(" -g, --global Use global location (~/.claude/ or ~/.codex/)");
|
|
4584
|
+
console.log("");
|
|
4585
|
+
console.log("Examples:");
|
|
4586
|
+
console.log(` uloop skills ${command} --claude`);
|
|
4587
|
+
console.log(` uloop skills ${command} --codex --global`);
|
|
4588
|
+
console.log(` uloop skills ${command} --claude --codex`);
|
|
4589
|
+
}
|
|
4590
|
+
function listSkills(targets, global) {
|
|
4374
4591
|
const location = global ? "Global" : "Project";
|
|
4375
|
-
const
|
|
4592
|
+
const targetConfigs = targets.length > 0 ? targets : ALL_TARGET_IDS.map(getTargetConfig);
|
|
4376
4593
|
console.log(`
|
|
4377
|
-
uloop Skills Status
|
|
4378
|
-
console.log(`Location: ${dir}`);
|
|
4379
|
-
console.log("=".repeat(50));
|
|
4594
|
+
uloop Skills Status:`);
|
|
4380
4595
|
console.log("");
|
|
4381
|
-
const
|
|
4382
|
-
|
|
4383
|
-
|
|
4384
|
-
|
|
4385
|
-
console.log(
|
|
4596
|
+
for (const target of targetConfigs) {
|
|
4597
|
+
const dir = getInstallDir(target, global);
|
|
4598
|
+
console.log(`${target.displayName} (${location}):`);
|
|
4599
|
+
console.log(`Location: ${dir}`);
|
|
4600
|
+
console.log("=".repeat(50));
|
|
4601
|
+
const statuses = getAllSkillStatuses(target, global);
|
|
4602
|
+
for (const skill of statuses) {
|
|
4603
|
+
const icon = getStatusIcon(skill.status);
|
|
4604
|
+
const statusText = getStatusText(skill.status);
|
|
4605
|
+
console.log(` ${icon} ${skill.name} (${statusText})`);
|
|
4606
|
+
}
|
|
4607
|
+
console.log("");
|
|
4386
4608
|
}
|
|
4387
|
-
console.log("");
|
|
4388
4609
|
console.log(`Total: ${getTotalSkillCount()} bundled skills`);
|
|
4389
4610
|
}
|
|
4390
4611
|
function getStatusIcon(status) {
|
|
@@ -4411,34 +4632,40 @@ function getStatusText(status) {
|
|
|
4411
4632
|
return "unknown";
|
|
4412
4633
|
}
|
|
4413
4634
|
}
|
|
4414
|
-
function installSkills(global) {
|
|
4635
|
+
function installSkills(targets, global) {
|
|
4415
4636
|
const location = global ? "global" : "project";
|
|
4416
|
-
const dir = getInstallDir(global);
|
|
4417
4637
|
console.log(`
|
|
4418
4638
|
Installing uloop skills (${location})...`);
|
|
4419
4639
|
console.log("");
|
|
4420
|
-
const
|
|
4421
|
-
|
|
4422
|
-
|
|
4423
|
-
|
|
4424
|
-
|
|
4425
|
-
|
|
4640
|
+
for (const target of targets) {
|
|
4641
|
+
const dir = getInstallDir(target, global);
|
|
4642
|
+
const result = installAllSkills(target, global);
|
|
4643
|
+
console.log(`${target.displayName}:`);
|
|
4644
|
+
console.log(` \x1B[32m\u2713\x1B[0m Installed: ${result.installed}`);
|
|
4645
|
+
console.log(` \x1B[33m\u2191\x1B[0m Updated: ${result.updated}`);
|
|
4646
|
+
console.log(` \x1B[90m-\x1B[0m Skipped (up-to-date): ${result.skipped}`);
|
|
4647
|
+
console.log(` Location: ${dir}`);
|
|
4648
|
+
console.log("");
|
|
4649
|
+
}
|
|
4426
4650
|
}
|
|
4427
|
-
function uninstallSkills(global) {
|
|
4651
|
+
function uninstallSkills(targets, global) {
|
|
4428
4652
|
const location = global ? "global" : "project";
|
|
4429
|
-
const dir = getInstallDir(global);
|
|
4430
4653
|
console.log(`
|
|
4431
4654
|
Uninstalling uloop skills (${location})...`);
|
|
4432
4655
|
console.log("");
|
|
4433
|
-
const
|
|
4434
|
-
|
|
4435
|
-
|
|
4436
|
-
|
|
4437
|
-
|
|
4656
|
+
for (const target of targets) {
|
|
4657
|
+
const dir = getInstallDir(target, global);
|
|
4658
|
+
const result = uninstallAllSkills(target, global);
|
|
4659
|
+
console.log(`${target.displayName}:`);
|
|
4660
|
+
console.log(` \x1B[31m\u2717\x1B[0m Removed: ${result.removed}`);
|
|
4661
|
+
console.log(` \x1B[90m-\x1B[0m Not found: ${result.notFound}`);
|
|
4662
|
+
console.log(` Location: ${dir}`);
|
|
4663
|
+
console.log("");
|
|
4664
|
+
}
|
|
4438
4665
|
}
|
|
4439
4666
|
|
|
4440
4667
|
// src/cli.ts
|
|
4441
|
-
var BUILTIN_COMMANDS = ["list", "sync", "completion", "update", "skills"];
|
|
4668
|
+
var BUILTIN_COMMANDS = ["list", "sync", "completion", "update", "fix", "skills"];
|
|
4442
4669
|
var program2 = new Command();
|
|
4443
4670
|
program2.name("uloop").description("Unity MCP CLI - Direct communication with Unity Editor").version(VERSION, "-v, --version", "Output the version number");
|
|
4444
4671
|
program2.option("--list-commands", "List all command names (for shell completion)");
|
|
@@ -4455,6 +4682,9 @@ program2.command("completion").description("Setup shell completion").option("--i
|
|
|
4455
4682
|
program2.command("update").description("Update uloop CLI to the latest version").action(() => {
|
|
4456
4683
|
updateCli();
|
|
4457
4684
|
});
|
|
4685
|
+
program2.command("fix").description("Clean up stale lock files that may prevent CLI from connecting").action(() => {
|
|
4686
|
+
cleanupLockFiles();
|
|
4687
|
+
});
|
|
4458
4688
|
registerSkillsCommand(program2);
|
|
4459
4689
|
var toolsCache = loadToolsCache();
|
|
4460
4690
|
for (const tool of toolsCache.tools) {
|
|
@@ -4538,27 +4768,34 @@ function extractGlobalOptions(options) {
|
|
|
4538
4768
|
port: options["port"]
|
|
4539
4769
|
};
|
|
4540
4770
|
}
|
|
4541
|
-
function isDomainReloadLockFilePresent() {
|
|
4542
|
-
const projectRoot = findUnityProjectRoot();
|
|
4543
|
-
if (projectRoot === null) {
|
|
4544
|
-
return false;
|
|
4545
|
-
}
|
|
4546
|
-
const lockPath = (0, import_path5.join)(projectRoot, "Temp", "domainreload.lock");
|
|
4547
|
-
return (0, import_fs4.existsSync)(lockPath);
|
|
4548
|
-
}
|
|
4549
4771
|
async function runWithErrorHandling(fn) {
|
|
4550
4772
|
try {
|
|
4551
4773
|
await fn();
|
|
4552
4774
|
} catch (error) {
|
|
4553
4775
|
const message = error instanceof Error ? error.message : String(error);
|
|
4776
|
+
if (message === "UNITY_COMPILING") {
|
|
4777
|
+
console.error("\x1B[33m\u23F3 Unity is compiling scripts.\x1B[0m");
|
|
4778
|
+
console.error("Please wait for compilation to finish and try again.");
|
|
4779
|
+
process.exit(1);
|
|
4780
|
+
}
|
|
4781
|
+
if (message === "UNITY_DOMAIN_RELOAD") {
|
|
4782
|
+
console.error("\x1B[33m\u23F3 Unity is reloading (Domain Reload in progress).\x1B[0m");
|
|
4783
|
+
console.error("Please wait a moment and try again.");
|
|
4784
|
+
process.exit(1);
|
|
4785
|
+
}
|
|
4786
|
+
if (message === "UNITY_SERVER_STARTING") {
|
|
4787
|
+
console.error("\x1B[33m\u23F3 Unity server is starting.\x1B[0m");
|
|
4788
|
+
console.error("Please wait a moment and try again.");
|
|
4789
|
+
process.exit(1);
|
|
4790
|
+
}
|
|
4791
|
+
if (message === "UNITY_NO_RESPONSE") {
|
|
4792
|
+
console.error("\x1B[33m\u23F3 Unity is busy (no response received).\x1B[0m");
|
|
4793
|
+
console.error("Unity may be compiling, reloading, or starting. Please wait and try again.");
|
|
4794
|
+
process.exit(1);
|
|
4795
|
+
}
|
|
4554
4796
|
if (message.includes("ECONNREFUSED")) {
|
|
4555
|
-
|
|
4556
|
-
|
|
4557
|
-
console.error("Please wait a moment and try again.");
|
|
4558
|
-
} else {
|
|
4559
|
-
console.error("\x1B[31mError: Cannot connect to Unity.\x1B[0m");
|
|
4560
|
-
console.error("Make sure Unity is running with uLoopMCP installed.");
|
|
4561
|
-
}
|
|
4797
|
+
console.error("\x1B[31mError: Cannot connect to Unity.\x1B[0m");
|
|
4798
|
+
console.error("Make sure Unity is running with uLoopMCP installed.");
|
|
4562
4799
|
process.exit(1);
|
|
4563
4800
|
}
|
|
4564
4801
|
console.error(`\x1B[31mError: ${message}\x1B[0m`);
|
|
@@ -4567,7 +4804,7 @@ async function runWithErrorHandling(fn) {
|
|
|
4567
4804
|
}
|
|
4568
4805
|
function detectShell() {
|
|
4569
4806
|
const shell = process.env["SHELL"] || "";
|
|
4570
|
-
const shellName = (0,
|
|
4807
|
+
const shellName = (0, import_path6.basename)(shell).replace(/\.exe$/i, "");
|
|
4571
4808
|
if (shellName === "zsh") {
|
|
4572
4809
|
return "zsh";
|
|
4573
4810
|
}
|
|
@@ -4582,12 +4819,12 @@ function detectShell() {
|
|
|
4582
4819
|
function getShellConfigPath(shell) {
|
|
4583
4820
|
const home = (0, import_os2.homedir)();
|
|
4584
4821
|
if (shell === "zsh") {
|
|
4585
|
-
return (0,
|
|
4822
|
+
return (0, import_path6.join)(home, ".zshrc");
|
|
4586
4823
|
}
|
|
4587
4824
|
if (shell === "powershell") {
|
|
4588
|
-
return (0,
|
|
4825
|
+
return (0, import_path6.join)(home, "Documents", "WindowsPowerShell", "Microsoft.PowerShell_profile.ps1");
|
|
4589
4826
|
}
|
|
4590
|
-
return (0,
|
|
4827
|
+
return (0, import_path6.join)(home, ".bashrc");
|
|
4591
4828
|
}
|
|
4592
4829
|
function getCompletionScript(shell) {
|
|
4593
4830
|
if (shell === "bash") {
|
|
@@ -4663,6 +4900,30 @@ function updateCli() {
|
|
|
4663
4900
|
process.exit(1);
|
|
4664
4901
|
});
|
|
4665
4902
|
}
|
|
4903
|
+
var LOCK_FILES = ["compiling.lock", "domainreload.lock", "serverstarting.lock"];
|
|
4904
|
+
function cleanupLockFiles() {
|
|
4905
|
+
const projectRoot = findUnityProjectRoot();
|
|
4906
|
+
if (projectRoot === null) {
|
|
4907
|
+
console.error("Could not find Unity project root.");
|
|
4908
|
+
process.exit(1);
|
|
4909
|
+
}
|
|
4910
|
+
const tempDir = (0, import_path6.join)(projectRoot, "Temp");
|
|
4911
|
+
let cleaned = 0;
|
|
4912
|
+
for (const lockFile of LOCK_FILES) {
|
|
4913
|
+
const lockPath = (0, import_path6.join)(tempDir, lockFile);
|
|
4914
|
+
if ((0, import_fs5.existsSync)(lockPath)) {
|
|
4915
|
+
(0, import_fs5.unlinkSync)(lockPath);
|
|
4916
|
+
console.log(`Removed: ${lockFile}`);
|
|
4917
|
+
cleaned++;
|
|
4918
|
+
}
|
|
4919
|
+
}
|
|
4920
|
+
if (cleaned === 0) {
|
|
4921
|
+
console.log("No lock files found.");
|
|
4922
|
+
} else {
|
|
4923
|
+
console.log(`
|
|
4924
|
+
\u2705 Cleaned up ${cleaned} lock file(s).`);
|
|
4925
|
+
}
|
|
4926
|
+
}
|
|
4666
4927
|
function handleCompletion(install, shellOverride) {
|
|
4667
4928
|
let shell;
|
|
4668
4929
|
if (shellOverride) {
|
|
@@ -4686,13 +4947,13 @@ function handleCompletion(install, shellOverride) {
|
|
|
4686
4947
|
return;
|
|
4687
4948
|
}
|
|
4688
4949
|
const configPath = getShellConfigPath(shell);
|
|
4689
|
-
const configDir = (0,
|
|
4690
|
-
if (!(0,
|
|
4691
|
-
(0,
|
|
4950
|
+
const configDir = (0, import_path6.dirname)(configPath);
|
|
4951
|
+
if (!(0, import_fs5.existsSync)(configDir)) {
|
|
4952
|
+
(0, import_fs5.mkdirSync)(configDir, { recursive: true });
|
|
4692
4953
|
}
|
|
4693
4954
|
let content = "";
|
|
4694
|
-
if ((0,
|
|
4695
|
-
content = (0,
|
|
4955
|
+
if ((0, import_fs5.existsSync)(configPath)) {
|
|
4956
|
+
content = (0, import_fs5.readFileSync)(configPath, "utf-8");
|
|
4696
4957
|
content = content.replace(
|
|
4697
4958
|
/\n?# >>> uloop completion >>>[\s\S]*?# <<< uloop completion <<<\n?/g,
|
|
4698
4959
|
""
|
|
@@ -4706,7 +4967,7 @@ ${startMarker}
|
|
|
4706
4967
|
${script}
|
|
4707
4968
|
${endMarker}
|
|
4708
4969
|
`;
|
|
4709
|
-
(0,
|
|
4970
|
+
(0, import_fs5.writeFileSync)(configPath, content + lineToAdd, "utf-8");
|
|
4710
4971
|
} else {
|
|
4711
4972
|
const evalLine = `eval "$(uloop completion --shell ${shell})"`;
|
|
4712
4973
|
const lineToAdd = `
|
|
@@ -4714,7 +4975,7 @@ ${startMarker}
|
|
|
4714
4975
|
${evalLine}
|
|
4715
4976
|
${endMarker}
|
|
4716
4977
|
`;
|
|
4717
|
-
(0,
|
|
4978
|
+
(0, import_fs5.writeFileSync)(configPath, content + lineToAdd, "utf-8");
|
|
4718
4979
|
}
|
|
4719
4980
|
console.log(`Completion installed to ${configPath}`);
|
|
4720
4981
|
if (shell === "powershell") {
|