unity-agentic-tools 0.6.1 → 0.7.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.
Files changed (47) hide show
  1. package/README.md +18 -2
  2. package/dist/bridge-install.d.ts +6 -1
  3. package/dist/bridge-install.d.ts.map +1 -1
  4. package/dist/bridge-install.js +75 -4
  5. package/dist/bridge-install.js.map +1 -1
  6. package/dist/cli.js +432 -161
  7. package/dist/cli.js.map +1 -1
  8. package/dist/editor-client.d.ts +8 -1
  9. package/dist/editor-client.d.ts.map +1 -1
  10. package/dist/editor-client.js +256 -54
  11. package/dist/editor-client.js.map +1 -1
  12. package/dist/index.d.ts +0 -7
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +39 -972
  15. package/dist/index.js.map +1 -1
  16. package/dist/types.d.ts +16 -239
  17. package/dist/types.d.ts.map +1 -1
  18. package/dist/types.js +1 -0
  19. package/dist/types.js.map +1 -1
  20. package/package.json +8 -13
  21. package/dist/cli-output.d.ts +0 -7
  22. package/dist/cli-output.d.ts.map +0 -1
  23. package/dist/cli-output.js +0 -24
  24. package/dist/cli-output.js.map +0 -1
  25. package/dist/doc-indexer-cli.js +0 -3218
  26. package/dist/guid-cache.d.ts +0 -45
  27. package/dist/guid-cache.d.ts.map +0 -1
  28. package/dist/guid-cache.js +0 -111
  29. package/dist/guid-cache.js.map +0 -1
  30. package/dist/project-search.d.ts +0 -16
  31. package/dist/project-search.d.ts.map +0 -1
  32. package/dist/project-search.js +0 -538
  33. package/dist/project-search.js.map +0 -1
  34. package/dist/scanner.d.ts +0 -150
  35. package/dist/scanner.d.ts.map +0 -1
  36. package/dist/scanner.js +0 -223
  37. package/dist/scanner.js.map +0 -1
  38. package/dist/setup.d.ts +0 -25
  39. package/dist/setup.d.ts.map +0 -1
  40. package/dist/setup.js +0 -185
  41. package/dist/setup.js.map +0 -1
  42. package/native/index.d.ts +0 -335
  43. package/native/index.js +0 -330
  44. package/native/unity-file-tools.darwin-arm64.node +0 -0
  45. package/native/unity-file-tools.darwin-x64.node +0 -0
  46. package/native/unity-file-tools.linux-x64-gnu.node +0 -0
  47. package/native/unity-file-tools.win32-x64-msvc.node +0 -0
package/dist/cli.js CHANGED
@@ -2129,37 +2129,33 @@ var require_commander = __commonJS((exports2) => {
2129
2129
  var require_package = __commonJS((exports2, module2) => {
2130
2130
  module2.exports = {
2131
2131
  name: "unity-agentic-tools",
2132
- packageManager: "bun@latest",
2133
- version: "0.6.1",
2132
+ packageManager: "bun@1.3.14",
2133
+ version: "0.7.0",
2134
2134
  description: "Compact Unity Editor command runner for AI agents",
2135
2135
  exports: {
2136
2136
  ".": {
2137
2137
  import: "./dist/index.js",
2138
2138
  types: "./dist/index.d.ts"
2139
2139
  },
2140
- "./cli": "./dist/cli.js",
2141
- "./scanner": "./dist/scanner.js"
2140
+ "./cli": "./dist/cli.js"
2142
2141
  },
2143
2142
  files: [
2144
2143
  "dist",
2145
- "native",
2146
2144
  "README.md",
2147
2145
  "LICENSE"
2148
2146
  ],
2149
2147
  scripts: {
2150
- build: "bun run clean && bun run build:types && bun run build:lib && bun run build:cli && cp ../doc-indexer/dist/cli.js dist/doc-indexer-cli.js",
2148
+ build: "bun run clean && bun run build:types && bun run build:lib && bun run build:cli",
2151
2149
  "build:types": "tsc --project tsconfig.json",
2152
- "build:lib": "bun build src/index.ts --outdir=dist --target=bun --format=cjs --external unity-file-tools",
2153
- "build:cli": "bun build src/cli.ts --outdir=dist --target=bun --format=cjs --external unity-file-tools",
2154
- "build:rust": "cd ../rust-core && bun run build",
2155
- "build:all": "bun run build:rust || echo 'Rust build skipped' && bun run build",
2150
+ "build:lib": "bun build src/index.ts --outdir=dist --target=bun --format=cjs",
2151
+ "build:cli": "bun build src/cli.ts --outdir=dist --target=bun --format=cjs && chmod +x dist/cli.js",
2156
2152
  dev: "bun build src/index.ts --watch",
2157
2153
  clean: "rm -rf dist",
2158
2154
  rebuild: "bun run clean && bun run build",
2159
2155
  test: "bun test",
2160
2156
  "test:watch": "bun test --watch",
2161
- "test:coverage": "bun test --coverage",
2162
- "test:unity-headless": "bun test/run-headless-validation.ts"
2157
+ "test:unity-headless": "bun test/run-headless-validation.ts",
2158
+ "test:stress": "bun test/run-bridge-stress.ts"
2163
2159
  },
2164
2160
  dependencies: {
2165
2161
  commander: "^14.0.2"
@@ -2168,7 +2164,6 @@ var require_package = __commonJS((exports2, module2) => {
2168
2164
  "@types/node": "^25.0.0",
2169
2165
  "@vitest/ui": "^4.0.16",
2170
2166
  typescript: "^5.3.0",
2171
- "unity-file-tools": "workspace:*",
2172
2167
  vitest: "^4.0.16"
2173
2168
  },
2174
2169
  type: "commonjs",
@@ -2212,6 +2207,10 @@ var {
2212
2207
  Help
2213
2208
  } = import__.default;
2214
2209
 
2210
+ // src/bridge-install.ts
2211
+ var import_fs2 = require("fs");
2212
+ var import_path2 = require("path");
2213
+
2215
2214
  // src/packages.ts
2216
2215
  var import_fs = require("fs");
2217
2216
  var import_path = require("path");
@@ -2275,15 +2274,21 @@ function remove_package(project_path, name) {
2275
2274
  }
2276
2275
 
2277
2276
  // src/bridge-install.ts
2277
+ var __dirname = "/home/runner/work/unity-agentic-tools/unity-agentic-tools/unity-agentic-tools/src";
2278
2278
  var BRIDGE_PACKAGE_NAME = "com.unity-agentic-tools.editor-bridge";
2279
- var BRIDGE_PACKAGE_VERSION = "https://github.com/taconotsandwich/unity-agentic-tools.git?path=unity-package";
2280
- function install_bridge_package(project_path) {
2279
+ var REMOTE_BRIDGE_PACKAGE_VERSION = "https://github.com/taconotsandwich/unity-agentic-tools.git?path=unity-package";
2280
+ function install_bridge_package(project_path, options = {}) {
2281
+ const target = resolve_bridge_package_target(options);
2282
+ if ("error" in target) {
2283
+ return target;
2284
+ }
2281
2285
  const manifest_result = load_manifest(project_path);
2282
2286
  if ("error" in manifest_result) {
2283
2287
  return manifest_result;
2284
2288
  }
2285
2289
  const existing_version = manifest_result.manifest.dependencies[BRIDGE_PACKAGE_NAME];
2286
- if (typeof existing_version === "string" && existing_version.startsWith("file:")) {
2290
+ const should_preserve_existing_file = !options.remote && !options.local_package_path && !options.require_local;
2291
+ if (should_preserve_existing_file && typeof existing_version === "string" && existing_version.startsWith("file:")) {
2287
2292
  return {
2288
2293
  success: true,
2289
2294
  action: "preserved",
@@ -2291,12 +2296,75 @@ function install_bridge_package(project_path) {
2291
2296
  version: existing_version
2292
2297
  };
2293
2298
  }
2294
- return add_package(project_path, BRIDGE_PACKAGE_NAME, BRIDGE_PACKAGE_VERSION);
2299
+ return add_package(project_path, BRIDGE_PACKAGE_NAME, target.version);
2300
+ }
2301
+ function resolve_bridge_package_target(options) {
2302
+ if (options.remote && (options.local_package_path || options.require_local)) {
2303
+ return { error: "Use either --local or --remote, not both." };
2304
+ }
2305
+ if (options.remote) {
2306
+ return { version: REMOTE_BRIDGE_PACKAGE_VERSION };
2307
+ }
2308
+ if (options.local_package_path) {
2309
+ const local_path = import_path2.resolve(options.local_package_path);
2310
+ return local_package_target(local_path);
2311
+ }
2312
+ if (options.require_local) {
2313
+ const detected_path = detect_local_bridge_package_path();
2314
+ if (detected_path) {
2315
+ return local_package_target(detected_path);
2316
+ }
2317
+ return {
2318
+ error: "Local bridge package could not be auto-detected. Pass --local <path-to-unity-package> or run from a source checkout."
2319
+ };
2320
+ }
2321
+ return { version: REMOTE_BRIDGE_PACKAGE_VERSION };
2322
+ }
2323
+ function detect_local_bridge_package_path() {
2324
+ const candidate = import_path2.resolve(__dirname, "..", "..", "unity-package");
2325
+ return is_bridge_package(candidate) ? candidate : undefined;
2326
+ }
2327
+ function local_package_target(package_path) {
2328
+ const normalized_path = normalize_local_bridge_package_path(package_path);
2329
+ if (!normalized_path) {
2330
+ return {
2331
+ error: `Local bridge package not found at ${package_path}. Expected package.json with name "${BRIDGE_PACKAGE_NAME}".`
2332
+ };
2333
+ }
2334
+ return { version: to_file_dependency(normalized_path) };
2335
+ }
2336
+ function normalize_local_bridge_package_path(package_path) {
2337
+ if (is_bridge_package(package_path)) {
2338
+ return package_path;
2339
+ }
2340
+ const nested_package_path = import_path2.join(package_path, "unity-package");
2341
+ if (is_bridge_package(nested_package_path)) {
2342
+ return nested_package_path;
2343
+ }
2344
+ return;
2345
+ }
2346
+ function is_bridge_package(package_path) {
2347
+ const package_json_path = import_path2.join(package_path, "package.json");
2348
+ if (!import_fs2.existsSync(package_json_path)) {
2349
+ return false;
2350
+ }
2351
+ try {
2352
+ const parsed = JSON.parse(import_fs2.readFileSync(package_json_path, "utf-8"));
2353
+ return is_record(parsed) && parsed.name === BRIDGE_PACKAGE_NAME;
2354
+ } catch {
2355
+ return false;
2356
+ }
2357
+ }
2358
+ function is_record(value) {
2359
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2360
+ }
2361
+ function to_file_dependency(package_path) {
2362
+ return `file:${package_path.replace(/\\/g, "/")}`;
2295
2363
  }
2296
2364
 
2297
2365
  // src/cleanup.ts
2298
- var import_fs2 = require("fs");
2299
- var import_path2 = require("path");
2366
+ var import_fs3 = require("fs");
2367
+ var import_path3 = require("path");
2300
2368
  var CONFIG_DIR = ".unity-agentic";
2301
2369
  var GUID_CACHE_FILE = "guid-cache.json";
2302
2370
  var PACKAGE_CACHE_FILE = "package-cache.json";
@@ -2306,10 +2374,10 @@ var DOC_INDEX_FILE = "doc-index.json";
2306
2374
  var EDITOR_LOCK_FILE = "editor.json";
2307
2375
  var LAST_EDITOR_CONFIG_FILE = "editor.last.json";
2308
2376
  function cleanup(options = {}) {
2309
- const projectPath = import_path2.resolve(options.project || process.cwd());
2310
- const configPath = import_path2.join(projectPath, CONFIG_DIR);
2377
+ const projectPath = import_path3.resolve(options.project || process.cwd());
2378
+ const configPath = import_path3.join(projectPath, CONFIG_DIR);
2311
2379
  const modes = resolve_cleanup_modes(options);
2312
- if (!import_fs2.existsSync(configPath)) {
2380
+ if (!import_fs3.existsSync(configPath)) {
2313
2381
  return {
2314
2382
  success: true,
2315
2383
  project_path: projectPath,
@@ -2377,16 +2445,16 @@ function resolve_cleanup_modes(options) {
2377
2445
  return modes;
2378
2446
  }
2379
2447
  function remove_stale_lock_files(configPath, filesRemoved) {
2380
- const editorLockPath = import_path2.join(configPath, EDITOR_LOCK_FILE);
2381
- if (import_fs2.existsSync(editorLockPath) && is_stale_editor_lock(editorLockPath)) {
2448
+ const editorLockPath = import_path3.join(configPath, EDITOR_LOCK_FILE);
2449
+ if (import_fs3.existsSync(editorLockPath) && is_stale_editor_lock(editorLockPath)) {
2382
2450
  remove_file(configPath, EDITOR_LOCK_FILE, filesRemoved);
2383
2451
  }
2384
2452
  remove_file(configPath, LAST_EDITOR_CONFIG_FILE, filesRemoved);
2385
2453
  }
2386
2454
  function is_stale_editor_lock(filePath) {
2387
2455
  try {
2388
- const parsed = JSON.parse(import_fs2.readFileSync(filePath, "utf-8"));
2389
- if (!is_record(parsed)) {
2456
+ const parsed = JSON.parse(import_fs3.readFileSync(filePath, "utf-8"));
2457
+ if (!is_record2(parsed)) {
2390
2458
  return true;
2391
2459
  }
2392
2460
  const pid = parsed.pid;
@@ -2403,10 +2471,10 @@ function is_process_alive(pid) {
2403
2471
  process.kill(pid, 0);
2404
2472
  return true;
2405
2473
  } catch (err) {
2406
- return is_record(err) && err.code === "EPERM";
2474
+ return is_record2(err) && err.code === "EPERM";
2407
2475
  }
2408
2476
  }
2409
- function is_record(value) {
2477
+ function is_record2(value) {
2410
2478
  return typeof value === "object" && value !== null && !Array.isArray(value);
2411
2479
  }
2412
2480
  function remove_files(configPath, files, filesRemoved) {
@@ -2415,38 +2483,38 @@ function remove_files(configPath, files, filesRemoved) {
2415
2483
  }
2416
2484
  }
2417
2485
  function remove_file(configPath, file, filesRemoved) {
2418
- const filePath = import_path2.join(configPath, file);
2419
- if (!import_fs2.existsSync(filePath)) {
2486
+ const filePath = import_path3.join(configPath, file);
2487
+ if (!import_fs3.existsSync(filePath)) {
2420
2488
  return;
2421
2489
  }
2422
2490
  try {
2423
- import_fs2.unlinkSync(filePath);
2491
+ import_fs3.unlinkSync(filePath);
2424
2492
  filesRemoved.push(file);
2425
2493
  } catch {}
2426
2494
  }
2427
2495
  function removeDirectoryRecursive(dir) {
2428
- if (!import_fs2.existsSync(dir)) {
2496
+ if (!import_fs3.existsSync(dir)) {
2429
2497
  return;
2430
2498
  }
2431
- const entries = import_fs2.readdirSync(dir, { withFileTypes: true });
2499
+ const entries = import_fs3.readdirSync(dir, { withFileTypes: true });
2432
2500
  for (const entry of entries) {
2433
- const fullPath = import_path2.join(dir, entry.name);
2501
+ const fullPath = import_path3.join(dir, entry.name);
2434
2502
  if (entry.isDirectory()) {
2435
2503
  removeDirectoryRecursive(fullPath);
2436
2504
  } else {
2437
- import_fs2.unlinkSync(fullPath);
2505
+ import_fs3.unlinkSync(fullPath);
2438
2506
  }
2439
2507
  }
2440
- import_fs2.rmdirSync(dir);
2508
+ import_fs3.rmdirSync(dir);
2441
2509
  }
2442
2510
 
2443
2511
  // src/editor-discovery.ts
2444
- var import_fs4 = require("fs");
2445
- var import_path4 = require("path");
2512
+ var import_fs5 = require("fs");
2513
+ var import_path5 = require("path");
2446
2514
 
2447
2515
  // src/gitignore.ts
2448
- var import_fs3 = require("fs");
2449
- var import_path3 = require("path");
2516
+ var import_fs4 = require("fs");
2517
+ var import_path4 = require("path");
2450
2518
  var AGENTIC_DIR = ".unity-agentic";
2451
2519
  var GITIGNORE_ENTRY = `${AGENTIC_DIR}/`;
2452
2520
  var ACCEPTED_AGENTIC_PATTERNS = new Set([
@@ -2456,20 +2524,20 @@ var ACCEPTED_AGENTIC_PATTERNS = new Set([
2456
2524
  `/${GITIGNORE_ENTRY}`
2457
2525
  ]);
2458
2526
  function ensure_gitignore_ignores_agentic_dir(project_path) {
2459
- const gitignore_path = import_path3.join(project_path, ".gitignore");
2460
- if (!import_fs3.existsSync(gitignore_path)) {
2461
- import_fs3.writeFileSync(gitignore_path, `${GITIGNORE_ENTRY}
2527
+ const gitignore_path = import_path4.join(project_path, ".gitignore");
2528
+ if (!import_fs4.existsSync(gitignore_path)) {
2529
+ import_fs4.writeFileSync(gitignore_path, `${GITIGNORE_ENTRY}
2462
2530
  `);
2463
2531
  return;
2464
2532
  }
2465
- const content = import_fs3.readFileSync(gitignore_path, "utf-8");
2533
+ const content = import_fs4.readFileSync(gitignore_path, "utf-8");
2466
2534
  if (gitignore_ignores_agentic_dir(content)) {
2467
2535
  return;
2468
2536
  }
2469
2537
  const separator = content.length === 0 || content.endsWith(`
2470
2538
  `) ? "" : `
2471
2539
  `;
2472
- import_fs3.writeFileSync(gitignore_path, `${content}${separator}${GITIGNORE_ENTRY}
2540
+ import_fs4.writeFileSync(gitignore_path, `${content}${separator}${GITIGNORE_ENTRY}
2473
2541
  `);
2474
2542
  }
2475
2543
  function gitignore_ignores_agentic_dir(content) {
@@ -2486,7 +2554,7 @@ function gitignore_ignores_agentic_dir(content) {
2486
2554
  }
2487
2555
 
2488
2556
  // src/util.ts
2489
- function is_record2(value) {
2557
+ function is_record3(value) {
2490
2558
  return typeof value === "object" && value !== null;
2491
2559
  }
2492
2560
 
@@ -2507,7 +2575,7 @@ async function request_editor_at_port(options) {
2507
2575
  method,
2508
2576
  ...Object.keys(wire_params).length > 0 ? { params: wire_params } : {}
2509
2577
  };
2510
- return new Promise((resolve2) => {
2578
+ return new Promise((resolve3) => {
2511
2579
  let resolved = false;
2512
2580
  let ws;
2513
2581
  const timer = setTimeout(() => {
@@ -2516,7 +2584,7 @@ async function request_editor_at_port(options) {
2516
2584
  try {
2517
2585
  ws?.close();
2518
2586
  } catch {}
2519
- resolve2({
2587
+ resolve3({
2520
2588
  jsonrpc: "2.0",
2521
2589
  id: request_id,
2522
2590
  error: { code: -32001, message: `Timeout after ${timeout}ms waiting for response to ${method}` }
@@ -2535,7 +2603,7 @@ async function request_editor_at_port(options) {
2535
2603
  ws.close();
2536
2604
  } catch {}
2537
2605
  }, 200);
2538
- resolve2({
2606
+ resolve3({
2539
2607
  jsonrpc: "2.0",
2540
2608
  id: request_id,
2541
2609
  result: { queued: true }
@@ -2550,7 +2618,7 @@ async function request_editor_at_port(options) {
2550
2618
  resolved = true;
2551
2619
  clearTimeout(timer);
2552
2620
  ws.close();
2553
- resolve2(data);
2621
+ resolve3(data);
2554
2622
  }
2555
2623
  }
2556
2624
  } catch {}
@@ -2559,7 +2627,7 @@ async function request_editor_at_port(options) {
2559
2627
  if (!resolved) {
2560
2628
  resolved = true;
2561
2629
  clearTimeout(timer);
2562
- resolve2({
2630
+ resolve3({
2563
2631
  jsonrpc: "2.0",
2564
2632
  id: request_id,
2565
2633
  error: { code: -32002, message: `WebSocket connection failed to ${url}. Is the Unity Editor running?` }
@@ -2570,7 +2638,7 @@ async function request_editor_at_port(options) {
2570
2638
  if (!resolved) {
2571
2639
  resolved = true;
2572
2640
  clearTimeout(timer);
2573
- resolve2({
2641
+ resolve3({
2574
2642
  jsonrpc: "2.0",
2575
2643
  id: request_id,
2576
2644
  error: { code: -32003, message: "WebSocket connection closed before response received" }
@@ -2581,7 +2649,7 @@ async function request_editor_at_port(options) {
2581
2649
  if (!resolved) {
2582
2650
  resolved = true;
2583
2651
  clearTimeout(timer);
2584
- resolve2({
2652
+ resolve3({
2585
2653
  jsonrpc: "2.0",
2586
2654
  id: request_id,
2587
2655
  error: { code: -32002, message: `Failed to connect: ${err instanceof Error ? err.message : String(err)}` }
@@ -2592,9 +2660,9 @@ async function request_editor_at_port(options) {
2592
2660
  }
2593
2661
  async function ping_editor(port, timeout_ms = 2000) {
2594
2662
  const url = `ws://127.0.0.1:${port}/unity-agentic`;
2595
- return new Promise((resolve2) => {
2663
+ return new Promise((resolve3) => {
2596
2664
  const timer = setTimeout(() => {
2597
- resolve2({ reachable: false, error: `Timeout after ${timeout_ms}ms` });
2665
+ resolve3({ reachable: false, error: `Timeout after ${timeout_ms}ms` });
2598
2666
  }, timeout_ms);
2599
2667
  try {
2600
2668
  const ws = new WebSocket(url);
@@ -2603,15 +2671,15 @@ async function ping_editor(port, timeout_ms = 2000) {
2603
2671
  try {
2604
2672
  ws.close();
2605
2673
  } catch {}
2606
- resolve2({ reachable: true });
2674
+ resolve3({ reachable: true });
2607
2675
  };
2608
2676
  ws.onerror = (err) => {
2609
2677
  clearTimeout(timer);
2610
- resolve2({ reachable: false, error: describe_websocket_error(err, `WebSocket connection failed to ${url}`) });
2678
+ resolve3({ reachable: false, error: describe_websocket_error(err, `WebSocket connection failed to ${url}`) });
2611
2679
  };
2612
2680
  } catch (err) {
2613
2681
  clearTimeout(timer);
2614
- resolve2({ reachable: false, error: describe_websocket_error(err, `WebSocket connection failed to ${url}`) });
2682
+ resolve3({ reachable: false, error: describe_websocket_error(err, `WebSocket connection failed to ${url}`) });
2615
2683
  }
2616
2684
  });
2617
2685
  }
@@ -2622,7 +2690,7 @@ function describe_websocket_error(err, fallback) {
2622
2690
  if (err instanceof Error && err.message.trim().length > 0) {
2623
2691
  return err.message;
2624
2692
  }
2625
- if (is_record2(err)) {
2693
+ if (is_record3(err)) {
2626
2694
  if (typeof err.message === "string" && err.message.trim().length > 0) {
2627
2695
  return err.message;
2628
2696
  }
@@ -2646,13 +2714,13 @@ var BRIDGE_PORT_RANGE_END = 53791;
2646
2714
  var LAST_KNOWN_CONFIG_FILE = "editor.last.json";
2647
2715
  var LAST_KNOWN_EDITOR_CONFIGS = new Map;
2648
2716
  function read_editor_config(project_path) {
2649
- const config_path = import_path4.join(project_path, ".unity-agentic", "editor.json");
2650
- if (!import_fs4.existsSync(config_path)) {
2717
+ const config_path = import_path5.join(project_path, ".unity-agentic", "editor.json");
2718
+ if (!import_fs5.existsSync(config_path)) {
2651
2719
  return { error: `Editor bridge not found at ${config_path}. Is the Unity Editor running with the bridge package installed?` };
2652
2720
  }
2653
2721
  let config;
2654
2722
  try {
2655
- const raw = import_fs4.readFileSync(config_path, "utf-8");
2723
+ const raw = import_fs5.readFileSync(config_path, "utf-8");
2656
2724
  config = JSON.parse(raw);
2657
2725
  } catch (err) {
2658
2726
  return { error: `Failed to parse editor.json: ${err instanceof Error ? err.message : String(err)}` };
@@ -2751,7 +2819,7 @@ async function discover_bridge_infos(timeout_ms, preferred_ports = []) {
2751
2819
  return results.filter((result) => result !== null);
2752
2820
  }
2753
2821
  function parse_bridge_info(result, port) {
2754
- if (!is_record2(result)) {
2822
+ if (!is_record3(result)) {
2755
2823
  return null;
2756
2824
  }
2757
2825
  const project_path = typeof result.project_path === "string" ? result.project_path : null;
@@ -2780,20 +2848,20 @@ function remember_editor_config(project_path, config) {
2780
2848
  }
2781
2849
  function forget_cached_editor_config(project_path, normalized_project_path) {
2782
2850
  LAST_KNOWN_EDITOR_CONFIGS.delete(normalized_project_path);
2783
- const config_path = import_path4.join(project_path, ".unity-agentic", LAST_KNOWN_CONFIG_FILE);
2851
+ const config_path = import_path5.join(project_path, ".unity-agentic", LAST_KNOWN_CONFIG_FILE);
2784
2852
  try {
2785
- if (import_fs4.existsSync(config_path)) {
2786
- import_fs4.unlinkSync(config_path);
2853
+ if (import_fs5.existsSync(config_path)) {
2854
+ import_fs5.unlinkSync(config_path);
2787
2855
  }
2788
2856
  } catch {}
2789
2857
  }
2790
2858
  function read_cached_editor_config(project_path, normalized_project_path) {
2791
- const config_path = import_path4.join(project_path, ".unity-agentic", LAST_KNOWN_CONFIG_FILE);
2792
- if (!import_fs4.existsSync(config_path)) {
2859
+ const config_path = import_path5.join(project_path, ".unity-agentic", LAST_KNOWN_CONFIG_FILE);
2860
+ if (!import_fs5.existsSync(config_path)) {
2793
2861
  return;
2794
2862
  }
2795
2863
  try {
2796
- const raw = import_fs4.readFileSync(config_path, "utf-8");
2864
+ const raw = import_fs5.readFileSync(config_path, "utf-8");
2797
2865
  const parsed = JSON.parse(raw);
2798
2866
  if (typeof parsed.port !== "number" || typeof parsed.pid !== "number" || typeof parsed.version !== "string") {
2799
2867
  return;
@@ -2810,12 +2878,12 @@ function read_cached_editor_config(project_path, normalized_project_path) {
2810
2878
  }
2811
2879
  }
2812
2880
  function write_cached_editor_config(project_path, config) {
2813
- const config_dir = import_path4.join(project_path, ".unity-agentic");
2814
- const config_path = import_path4.join(config_dir, LAST_KNOWN_CONFIG_FILE);
2881
+ const config_dir = import_path5.join(project_path, ".unity-agentic");
2882
+ const config_path = import_path5.join(config_dir, LAST_KNOWN_CONFIG_FILE);
2815
2883
  try {
2816
- import_fs4.mkdirSync(config_dir, { recursive: true });
2884
+ import_fs5.mkdirSync(config_dir, { recursive: true });
2817
2885
  ensure_gitignore_ignores_agentic_dir(project_path);
2818
- import_fs4.writeFileSync(config_path, JSON.stringify({
2886
+ import_fs5.writeFileSync(config_path, JSON.stringify({
2819
2887
  port: config.port,
2820
2888
  pid: config.pid,
2821
2889
  version: config.version,
@@ -2824,10 +2892,10 @@ function write_cached_editor_config(project_path, config) {
2824
2892
  } catch {}
2825
2893
  }
2826
2894
  function normalize_project_path(project_path) {
2827
- const resolved_path = import_path4.resolve(project_path);
2895
+ const resolved_path = import_path5.resolve(project_path);
2828
2896
  let normalized = resolved_path;
2829
2897
  try {
2830
- normalized = typeof import_fs4.realpathSync.native === "function" ? import_fs4.realpathSync.native(resolved_path) : import_fs4.realpathSync(resolved_path);
2898
+ normalized = typeof import_fs5.realpathSync.native === "function" ? import_fs5.realpathSync.native(resolved_path) : import_fs5.realpathSync(resolved_path);
2831
2899
  } catch {
2832
2900
  normalized = resolved_path;
2833
2901
  }
@@ -2838,7 +2906,7 @@ function is_pid_alive(pid) {
2838
2906
  process.kill(pid, 0);
2839
2907
  return true;
2840
2908
  } catch (err) {
2841
- if (is_record2(err) && err.code === "EPERM") {
2909
+ if (is_record3(err) && err.code === "EPERM") {
2842
2910
  return true;
2843
2911
  }
2844
2912
  return false;
@@ -2865,14 +2933,29 @@ var TRANSITION_TOLERANT_COMMAND_SEMANTICS = {
2865
2933
  retry_delays_ms: [250, 500, 1000, 1500, 2000],
2866
2934
  discovery_timeout_ms: 250
2867
2935
  };
2868
- var TRANSITION_TOLERANT_READ_METHODS = new Set([
2869
- "editor.playMode.getState",
2870
- "editor.console.getLogs",
2871
- "editor.hierarchy.snapshot",
2872
- "editor.hierarchy.query",
2873
- "editor.ui.snapshot",
2874
- "editor.ui.query",
2875
- "editor.input.map"
2936
+ var READ_RUN_TARGETS = new Set([
2937
+ "scene.hierarchy",
2938
+ "scene.query",
2939
+ "query.assets",
2940
+ "query.asset",
2941
+ "query.scene",
2942
+ "query.object",
2943
+ "play.state",
2944
+ "ui.snapshot",
2945
+ "ui.query",
2946
+ "input.map",
2947
+ "tests.results",
2948
+ "UnityAgenticTools.Util.Hierarchy.Snapshot",
2949
+ "UnityAgenticTools.Util.Hierarchy.Query",
2950
+ "UnityAgenticTools.Query.Assets.Find",
2951
+ "UnityAgenticTools.Query.Assets.Info",
2952
+ "UnityAgenticTools.Query.Scene.Hierarchy",
2953
+ "UnityAgenticTools.Query.Scene.Object",
2954
+ "UnityAgenticTools.Util.PlayMode.GetState",
2955
+ "UnityAgenticTools.Util.UI.Snapshot",
2956
+ "UnityAgenticTools.Util.UI.Query",
2957
+ "UnityAgenticTools.Util.Input.Map",
2958
+ "UnityAgenticTools.Util.TestRunner.GetResults"
2876
2959
  ]);
2877
2960
  var PLAY_MODE_RUN_TARGETS = new Set([
2878
2961
  "play.enter",
@@ -2890,36 +2973,131 @@ var PLAY_MODE_RUN_TARGETS = new Set([
2890
2973
  var READ_RETRYABLE_CODES = new Set([-32000, -32002, -32003, CLIENT_DISCOVERY_UNAVAILABLE_CODE]);
2891
2974
  var COMMAND_RETRYABLE_CODES = new Set([-32002, CLIENT_DISCOVERY_UNAVAILABLE_CODE]);
2892
2975
  var TRANSITION_TOLERANT_COMMAND_RETRYABLE_CODES = new Set([-32000, -32002, -32003, CLIENT_DISCOVERY_UNAVAILABLE_CODE]);
2976
+ var RELOAD_TOLERANCE_MS = 30000;
2977
+ var RELOAD_POLL_INTERVAL_MS = 500;
2893
2978
  async function call_editor(options) {
2894
2979
  const semantics = get_action_semantics(options.method, "unary", options.params);
2895
- const maxRetries = options.retries ?? semantics.default_retries;
2896
- let lastResponse;
2897
- for (let attempt = 0;attempt <= maxRetries; attempt++) {
2898
- lastResponse = await call_editor_once(options, semantics);
2899
- if (!should_retry_response(lastResponse, semantics)) {
2900
- return lastResponse;
2980
+ const explicit_retries = options.retries !== undefined;
2981
+ const max_retries = options.retries ?? semantics.default_retries;
2982
+ const delays = semantics.retry_delays_ms;
2983
+ const started = Date.now();
2984
+ let last_response;
2985
+ for (let attempt = 0;; attempt++) {
2986
+ last_response = await call_editor_once(options, semantics);
2987
+ if (!should_retry_response(last_response, semantics)) {
2988
+ return last_response;
2989
+ }
2990
+ if (attempt < max_retries) {
2991
+ await sleep(delays[Math.min(attempt, delays.length - 1)]);
2992
+ continue;
2901
2993
  }
2902
- if (attempt < maxRetries) {
2903
- const delay = semantics.retry_delays_ms[Math.min(attempt, semantics.retry_delays_ms.length - 1)];
2904
- await new Promise((r) => setTimeout(r, delay));
2994
+ if (explicit_retries || !within_reload_tolerance(options, started)) {
2995
+ return last_response;
2905
2996
  }
2997
+ await sleep(RELOAD_POLL_INTERVAL_MS);
2906
2998
  }
2907
- return lastResponse;
2999
+ }
3000
+ function within_reload_tolerance(options, started) {
3001
+ if (options.port !== undefined) {
3002
+ return false;
3003
+ }
3004
+ if (Date.now() - started >= RELOAD_TOLERANCE_MS) {
3005
+ return false;
3006
+ }
3007
+ return !("error" in read_editor_config(options.project_path));
3008
+ }
3009
+ function sleep(ms) {
3010
+ return new Promise((r) => setTimeout(r, ms));
3011
+ }
3012
+ var READINESS_FIELDS = [
3013
+ "is_playing",
3014
+ "is_paused",
3015
+ "is_compiling",
3016
+ "is_updating",
3017
+ "is_playmode_transitioning",
3018
+ "is_reloading",
3019
+ "is_stable"
3020
+ ];
3021
+ async function read_editor_readiness(port, timeout_ms) {
3022
+ const response = await request_editor_at_port({
3023
+ port,
3024
+ method: "editor.bridge.getInfo",
3025
+ timeout: timeout_ms
3026
+ });
3027
+ if (response.error) {
3028
+ return { error: response.error.message };
3029
+ }
3030
+ if (!is_record3(response.result)) {
3031
+ return { error: "Bridge returned no readiness fields" };
3032
+ }
3033
+ const info = response.result;
3034
+ const readiness = {};
3035
+ for (const field of READINESS_FIELDS) {
3036
+ const value = info[field];
3037
+ if (typeof value !== "boolean") {
3038
+ return { error: `Bridge did not report ${field}; the installed package may be older than this CLI` };
3039
+ }
3040
+ readiness[field] = value;
3041
+ }
3042
+ return readiness;
2908
3043
  }
2909
3044
  async function stream_editor(options) {
2910
- const { method, params, timeout = 30000, on_event } = options;
3045
+ const { method, params, timeout = 30000, on_event, on_error } = options;
2911
3046
  const semantics = get_action_semantics(method, "stream", params);
2912
3047
  const config = await resolve_config(options, semantics.discovery_timeout_ms);
2913
3048
  if ("error" in config) {
2914
3049
  throw new Error(config.error);
2915
3050
  }
2916
- const MAX_RECONNECTS = 5;
2917
- let reconnect_count = 0;
2918
3051
  let stopped = false;
2919
- return new Promise((resolve3, reject) => {
3052
+ let reconnect_count = 0;
3053
+ let reconnect_deadline;
3054
+ let active_socket;
3055
+ return new Promise((resolve4, reject) => {
2920
3056
  let resolved = false;
3057
+ function stop() {
3058
+ stopped = true;
3059
+ try {
3060
+ active_socket?.close();
3061
+ } catch {}
3062
+ }
3063
+ function fail(error) {
3064
+ if (stopped)
3065
+ return;
3066
+ stop();
3067
+ if (!resolved) {
3068
+ resolved = true;
3069
+ reject(error);
3070
+ return;
3071
+ }
3072
+ on_error?.(error);
3073
+ }
3074
+ function schedule_reconnect() {
3075
+ if (stopped)
3076
+ return;
3077
+ reconnect_deadline ??= Date.now() + RELOAD_TOLERANCE_MS;
3078
+ if (Date.now() >= reconnect_deadline) {
3079
+ fail(new Error(`Stream lost: could not reconnect within ${RELOAD_TOLERANCE_MS}ms`));
3080
+ return;
3081
+ }
3082
+ reconnect_count += 1;
3083
+ const delay = Math.min(500 * reconnect_count, 3000);
3084
+ setTimeout(() => {
3085
+ if (stopped)
3086
+ return;
3087
+ resolve_config(options, semantics.discovery_timeout_ms).then((fresh_config) => {
3088
+ if (stopped)
3089
+ return;
3090
+ if ("error" in fresh_config) {
3091
+ schedule_reconnect();
3092
+ return;
3093
+ }
3094
+ connect(`ws://127.0.0.1:${fresh_config.port}/unity-agentic`);
3095
+ });
3096
+ }, delay);
3097
+ }
2921
3098
  function connect(url) {
2922
3099
  const ws = new WebSocket(url);
3100
+ active_socket = ws;
2923
3101
  const request_id = generate_editor_request_id();
2924
3102
  const request = {
2925
3103
  jsonrpc: "2.0",
@@ -2928,57 +3106,77 @@ async function stream_editor(options) {
2928
3106
  ...params ? { params } : {}
2929
3107
  };
2930
3108
  let connected = false;
2931
- const timer = !resolved ? setTimeout(() => {
2932
- if (!connected && !resolved) {
3109
+ let attempt_finished = false;
3110
+ function finish_attempt() {
3111
+ if (attempt_finished) {
3112
+ return false;
3113
+ }
3114
+ attempt_finished = true;
3115
+ clearTimeout(timer);
3116
+ return true;
3117
+ }
3118
+ const timer = setTimeout(() => {
3119
+ if (connected || !finish_attempt() || stopped)
3120
+ return;
3121
+ if (!resolved) {
3122
+ stopped = true;
2933
3123
  reject(new Error(`Timeout connecting to ${url}`));
3124
+ return;
2934
3125
  }
2935
- }, timeout) : null;
3126
+ try {
3127
+ ws.close();
3128
+ } catch {}
3129
+ schedule_reconnect();
3130
+ }, timeout);
2936
3131
  ws.onopen = () => {
2937
3132
  connected = true;
2938
- if (timer)
2939
- clearTimeout(timer);
3133
+ clearTimeout(timer);
2940
3134
  reconnect_count = 0;
3135
+ reconnect_deadline = undefined;
2941
3136
  ws.send(JSON.stringify(request));
2942
3137
  if (!resolved) {
2943
3138
  resolved = true;
2944
- resolve3({ close: () => {
2945
- stopped = true;
2946
- ws.close();
2947
- } });
3139
+ resolve4({ close: stop });
2948
3140
  }
2949
3141
  };
2950
3142
  ws.onmessage = (event) => {
3143
+ let data;
2951
3144
  try {
2952
- const data = JSON.parse(String(event.data));
2953
- if ("method" in data && !("id" in data)) {
2954
- on_event(data);
2955
- }
2956
- } catch {}
3145
+ data = JSON.parse(String(event.data));
3146
+ } catch {
3147
+ return;
3148
+ }
3149
+ if (typeof data !== "object" || data === null) {
3150
+ return;
3151
+ }
3152
+ if ("method" in data && !("id" in data)) {
3153
+ on_event(data);
3154
+ return;
3155
+ }
3156
+ const frame = data;
3157
+ if (frame.id === request_id && frame.error) {
3158
+ fail(new Error(`Subscription rejected: ${frame.error.message ?? "unknown error"}`));
3159
+ }
2957
3160
  };
2958
3161
  ws.onerror = () => {
2959
- if (!connected && !resolved) {
2960
- if (timer)
2961
- clearTimeout(timer);
3162
+ if (connected || !finish_attempt() || stopped)
3163
+ return;
3164
+ if (!resolved) {
3165
+ stopped = true;
2962
3166
  reject(new Error(`WebSocket connection failed to ${url}`));
3167
+ return;
2963
3168
  }
3169
+ schedule_reconnect();
2964
3170
  };
2965
3171
  ws.onclose = () => {
2966
- if (stopped)
2967
- return;
2968
- if (!connected && !resolved)
3172
+ if (!finish_attempt() || stopped)
2969
3173
  return;
2970
- if (reconnect_count >= MAX_RECONNECTS)
3174
+ if (!resolved) {
3175
+ stopped = true;
3176
+ reject(new Error(`WebSocket connection failed to ${url}`));
2971
3177
  return;
2972
- reconnect_count++;
2973
- const delay = Math.min(500 * reconnect_count, 3000);
2974
- setTimeout(async () => {
2975
- if (stopped)
2976
- return;
2977
- const fresh_config = await resolve_config(options, semantics.discovery_timeout_ms);
2978
- if ("error" in fresh_config)
2979
- return;
2980
- connect(`ws://127.0.0.1:${fresh_config.port}/unity-agentic`);
2981
- }, delay);
3178
+ }
3179
+ schedule_reconnect();
2982
3180
  };
2983
3181
  }
2984
3182
  connect(`ws://127.0.0.1:${config.port}/unity-agentic`);
@@ -3015,7 +3213,7 @@ function get_action_semantics(method, kind = "unary", params) {
3015
3213
  kind: "stream"
3016
3214
  };
3017
3215
  }
3018
- if (TRANSITION_TOLERANT_READ_METHODS.has(method)) {
3216
+ if (is_read_invoke(method, params)) {
3019
3217
  return TRANSITION_TOLERANT_READ_SEMANTICS;
3020
3218
  }
3021
3219
  if (is_play_mode_transition_invoke(method, params)) {
@@ -3023,6 +3221,16 @@ function get_action_semantics(method, kind = "unary", params) {
3023
3221
  }
3024
3222
  return DEFAULT_ACTION_SEMANTICS;
3025
3223
  }
3224
+ function is_read_invoke(method, params) {
3225
+ if (method !== "editor.invoke" || !params) {
3226
+ return false;
3227
+ }
3228
+ if (params.type !== "UnityAgenticTools.Commands.Registry" || params.member !== "Run") {
3229
+ return false;
3230
+ }
3231
+ const target = parse_registry_run_target(params.args);
3232
+ return typeof target === "string" && READ_RUN_TARGETS.has(target);
3233
+ }
3026
3234
  function is_play_mode_transition_invoke(method, params) {
3027
3235
  if (method !== "editor.invoke" || !params) {
3028
3236
  return false;
@@ -3087,19 +3295,45 @@ function resolve_bridge_options(options) {
3087
3295
  function build_registry_args(values) {
3088
3296
  return JSON.stringify(values);
3089
3297
  }
3090
- function output_rpc_response(response) {
3298
+ function resolve_install_options(options) {
3299
+ if (options.remote && options.local !== undefined) {
3300
+ return { error: "Use either --local or --remote, not both." };
3301
+ }
3302
+ if (options.remote) {
3303
+ return { remote: true };
3304
+ }
3305
+ if (typeof options.local === "string") {
3306
+ return {
3307
+ local_package_path: path.resolve(options.local),
3308
+ require_local: true
3309
+ };
3310
+ }
3311
+ if (options.local === true) {
3312
+ return { require_local: true };
3313
+ }
3314
+ return {};
3315
+ }
3316
+ function print_json(value, pretty) {
3317
+ console.log(JSON.stringify(value, null, pretty ? 2 : 0));
3318
+ }
3319
+ function output_rpc_response(response, pretty) {
3091
3320
  if (response.error) {
3092
- console.log(JSON.stringify({ success: false, error: response.error.message, code: response.error.code }, null, 2));
3321
+ print_json({
3322
+ success: false,
3323
+ error: response.error.message,
3324
+ code: response.error.code,
3325
+ ...response.error.data !== undefined ? { data: response.error.data } : {}
3326
+ }, pretty);
3093
3327
  process.exitCode = 1;
3094
3328
  return;
3095
3329
  }
3096
- console.log(JSON.stringify(response.result, null, 2));
3330
+ print_json(response.result, pretty);
3097
3331
  if (payload_reports_failure(response.result)) {
3098
3332
  process.exitCode = 1;
3099
3333
  }
3100
3334
  }
3101
3335
  function payload_reports_failure(payload) {
3102
- if (!is_record3(payload)) {
3336
+ if (!is_record4(payload)) {
3103
3337
  return false;
3104
3338
  }
3105
3339
  if (payload.success === false) {
@@ -3107,9 +3341,30 @@ function payload_reports_failure(payload) {
3107
3341
  }
3108
3342
  return payload_reports_failure(payload.result);
3109
3343
  }
3110
- function is_record3(value) {
3344
+ function is_record4(value) {
3111
3345
  return typeof value === "object" && value !== null && !Array.isArray(value);
3112
3346
  }
3347
+ function strip_command_listing_details(payload) {
3348
+ if (!is_record4(payload)) {
3349
+ return payload;
3350
+ }
3351
+ if (Array.isArray(payload.commands)) {
3352
+ return {
3353
+ ...payload,
3354
+ commands: payload.commands.map((entry) => {
3355
+ if (!is_record4(entry)) {
3356
+ return entry;
3357
+ }
3358
+ const { args: _args, source: _source, ...brief } = entry;
3359
+ return brief;
3360
+ })
3361
+ };
3362
+ }
3363
+ if ("result" in payload) {
3364
+ return { ...payload, result: strip_command_listing_details(payload.result) };
3365
+ }
3366
+ return payload;
3367
+ }
3113
3368
  function event_matches_topic(event, topic, type_filter) {
3114
3369
  if (topic === "console") {
3115
3370
  if (event.method !== "editor.console.logReceived") {
@@ -3133,13 +3388,12 @@ function event_matches_topic(event, topic, type_filter) {
3133
3388
  return false;
3134
3389
  }
3135
3390
  function print_stream_event(event, pretty = false) {
3136
- const payload = {
3391
+ print_json({
3137
3392
  method: event.method,
3138
3393
  ...event.params ? event.params : {}
3139
- };
3140
- console.log(JSON.stringify(payload, null, pretty ? 2 : 0));
3394
+ }, pretty);
3141
3395
  }
3142
- program.command("list [query]").description("List runnable Unity commands and project script commands").option("-p, --project <path>", "Path to Unity project (defaults to cwd)").option("--timeout <ms>", "WebSocket timeout in ms", "10000").option("--port <n>", "Connect to a specific bridge port").option("--raw", "Include raw public static methods/properties for matching types").action(async (query, options) => {
3396
+ program.command("list [query]").description("List runnable Unity commands and project script commands").option("-p, --project <path>", "Path to Unity project (defaults to cwd)").option("--timeout <ms>", "WebSocket timeout in ms", "10000").option("--port <n>", "Connect to a specific bridge port").option("--raw", "Include raw public static methods/properties for matching types").option("--brief", "List names and descriptions only, omit args").option("--pretty", "Pretty-print JSON output").action(async (query, options) => {
3143
3397
  const bridge = resolve_bridge_options(options);
3144
3398
  const response = await call_editor({
3145
3399
  ...bridge,
@@ -3150,9 +3404,12 @@ program.command("list [query]").description("List runnable Unity commands and pr
3150
3404
  args: build_registry_args([query || "", options.raw === true ? "true" : "false"])
3151
3405
  }
3152
3406
  });
3153
- output_rpc_response(response);
3407
+ if (options.brief === true && !response.error) {
3408
+ response.result = strip_command_listing_details(response.result);
3409
+ }
3410
+ output_rpc_response(response, options.pretty === true);
3154
3411
  });
3155
- program.command("run <target> [args...]").description("Run a named Unity command or raw static method/property").option("-p, --project <path>", "Path to Unity project (defaults to cwd)").option("--timeout <ms>", "WebSocket timeout in ms", "60000").option("--port <n>", "Connect to a specific bridge port").option("--args <json>", "JSON array of command arguments (overrides positional args)").option("--set <value>", "Set a static property value").option("--no-wait", "Fire and forget -- return immediately without waiting for result").action(async (target, args, options) => {
3412
+ program.command("run <target> [args...]").description("Run a named Unity command or raw static method/property").option("-p, --project <path>", "Path to Unity project (defaults to cwd)").option("--timeout <ms>", "WebSocket timeout in ms", "60000").option("--port <n>", "Connect to a specific bridge port").option("--args <json>", "JSON array of command arguments (overrides positional args)").option("--set <value>", "Set a static property value").option("--no-wait", "Fire and forget -- return immediately without waiting for result").option("--pretty", "Pretty-print JSON output").action(async (target, args, options) => {
3156
3413
  const bridge = resolve_bridge_options(options);
3157
3414
  const command_args_json = options.args || JSON.stringify(args);
3158
3415
  const registry_args = options.set !== undefined ? [target, command_args_json, options.set] : [target, command_args_json];
@@ -3166,16 +3423,16 @@ program.command("run <target> [args...]").description("Run a named Unity command
3166
3423
  args: build_registry_args(registry_args)
3167
3424
  }
3168
3425
  });
3169
- output_rpc_response(response);
3426
+ output_rpc_response(response, options.pretty === true);
3170
3427
  });
3171
3428
  program.command("stream [topic]").description("Stream Unity bridge events over WebSocket (default: console)").option("-p, --project <path>", "Path to Unity project (defaults to cwd)").option("--timeout <ms>", "WebSocket timeout in ms", "30000").option("--port <n>", "Connect to a specific bridge port").option("-t, --type <type>", "Console log type filter").option("--duration <ms>", "Stop after duration in ms (0 = indefinite)", "0").option("--pretty", "Pretty-print JSON events").action(async (topic_raw, options) => {
3172
3429
  const topic = (topic_raw || "console").toLowerCase();
3173
3430
  const valid_topics = ["console", "events", "playmode", "tests"];
3174
3431
  if (!valid_topics.includes(topic)) {
3175
- console.log(JSON.stringify({
3432
+ print_json({
3176
3433
  success: false,
3177
3434
  error: `Invalid stream topic "${topic}". Use: ${valid_topics.join(", ")}`
3178
- }, null, 2));
3435
+ }, options.pretty === true);
3179
3436
  process.exitCode = 1;
3180
3437
  return;
3181
3438
  }
@@ -3189,6 +3446,10 @@ program.command("stream [topic]").description("Stream Unity bridge events over W
3189
3446
  if (event_matches_topic(event, topic, options.type)) {
3190
3447
  print_stream_event(event, options.pretty === true);
3191
3448
  }
3449
+ },
3450
+ on_error: (err) => {
3451
+ print_json({ success: false, error: err.message }, options.pretty === true);
3452
+ process.exit(1);
3192
3453
  }
3193
3454
  });
3194
3455
  if (duration > 0) {
@@ -3203,43 +3464,53 @@ program.command("stream [topic]").description("Stream Unity bridge events over W
3203
3464
  });
3204
3465
  }
3205
3466
  } catch (err) {
3206
- console.log(JSON.stringify({
3467
+ print_json({
3207
3468
  success: false,
3208
3469
  error: err instanceof Error ? err.message : String(err)
3209
- }, null, 2));
3470
+ }, options.pretty === true);
3210
3471
  process.exitCode = 1;
3211
3472
  }
3212
3473
  });
3213
- program.command("install").description("Install the Unity command bridge package into a Unity project").option("-p, --project <path>", "Path to Unity project (defaults to cwd)").action((options) => {
3474
+ program.command("install").description("Install the Unity command bridge package into a Unity project").option("-p, --project <path>", "Path to Unity project (defaults to cwd)").option("--local [path]", "Install from a local unity-package path; auto-detects source checkout when omitted").option("--remote", "Force the published GitHub bridge package URL").option("--pretty", "Pretty-print JSON output").action((options) => {
3214
3475
  const bridge = resolve_bridge_options(options);
3215
- const result = install_bridge_package(bridge.project_path);
3476
+ const pretty = options.pretty === true;
3477
+ const install_options = resolve_install_options(options);
3478
+ if ("error" in install_options) {
3479
+ print_json({ success: false, error: install_options.error }, pretty);
3480
+ process.exitCode = 1;
3481
+ return;
3482
+ }
3483
+ const result = install_bridge_package(bridge.project_path, install_options);
3216
3484
  if ("error" in result) {
3217
- console.log(JSON.stringify({ success: false, error: result.error }, null, 2));
3485
+ print_json({ success: false, error: result.error }, pretty);
3218
3486
  process.exitCode = 1;
3219
3487
  return;
3220
3488
  }
3221
- console.log(JSON.stringify(result, null, 2));
3489
+ print_json(result, pretty);
3222
3490
  });
3223
- program.command("uninstall").description("Remove the Unity command bridge package from a Unity project").option("-p, --project <path>", "Path to Unity project (defaults to cwd)").action((options) => {
3491
+ program.command("uninstall").description("Remove the Unity command bridge package from a Unity project").option("-p, --project <path>", "Path to Unity project (defaults to cwd)").option("--pretty", "Pretty-print JSON output").action((options) => {
3224
3492
  const bridge = resolve_bridge_options(options);
3493
+ const pretty = options.pretty === true;
3225
3494
  const result = remove_package(bridge.project_path, BRIDGE_PACKAGE_NAME2);
3226
3495
  if ("error" in result) {
3227
- console.log(JSON.stringify({ success: false, error: result.error }, null, 2));
3496
+ print_json({ success: false, error: result.error }, pretty);
3228
3497
  process.exitCode = 1;
3229
3498
  return;
3230
3499
  }
3231
- console.log(JSON.stringify(result, null, 2));
3500
+ print_json(result, pretty);
3232
3501
  });
3233
- program.command("cleanup").description("Clean Unity Agentic Tools project state").option("-p, --project <path>", "Path to Unity project (defaults to cwd)").option("--stale", "Remove stale bridge lock state (default)").option("--cache", "Remove rebuildable project caches").option("--all", "Remove the entire .unity-agentic directory").action((options) => {
3502
+ program.command("cleanup").description("Clean Unity Agentic Tools project state").option("-p, --project <path>", "Path to Unity project (defaults to cwd)").option("--stale", "Remove stale bridge lock state (default)").option("--cache", "Remove rebuildable project caches").option("--all", "Remove the entire .unity-agentic directory").option("--pretty", "Pretty-print JSON output").action((options) => {
3234
3503
  const result = cleanup(options);
3504
+ print_json(result, options.pretty === true);
3235
3505
  if (!result.success) {
3236
- console.log(JSON.stringify(result, null, 2));
3237
3506
  process.exitCode = 1;
3238
- return;
3239
3507
  }
3240
- console.log(JSON.stringify(result, null, 2));
3241
3508
  });
3242
- program.command("status").description("Show Unity command bridge status").option("-p, --project <path>", "Path to Unity project (defaults to current directory)").option("--timeout <ms>", "WebSocket timeout in ms", "2000").option("--port <n>", "Connect to a specific bridge port").action(async (options) => {
3509
+ async function describe_readiness(port, timeout_ms) {
3510
+ const readiness = await read_editor_readiness(port, timeout_ms);
3511
+ return "error" in readiness ? { readiness_error: readiness.error } : { readiness };
3512
+ }
3513
+ program.command("status").description("Show Unity command bridge status").option("-p, --project <path>", "Path to Unity project (defaults to current directory)").option("--timeout <ms>", "WebSocket timeout in ms", "2000").option("--port <n>", "Connect to a specific bridge port").option("--pretty", "Pretty-print JSON output").action(async (options) => {
3243
3514
  const bridge = resolve_bridge_options(options);
3244
3515
  const projectPath = bridge.project_path;
3245
3516
  const status = {
@@ -3253,7 +3524,7 @@ program.command("status").description("Show Unity command bridge status").option
3253
3524
  port: bridge.port,
3254
3525
  source: "manual",
3255
3526
  reachable: ping.reachable,
3256
- ...ping.reachable ? {} : { error: ping.error }
3527
+ ...ping.reachable ? await describe_readiness(bridge.port, bridge.timeout) : { error: ping.error }
3257
3528
  };
3258
3529
  } else {
3259
3530
  const editor_config = await discover_editor_config(projectPath, bridge.timeout);
@@ -3271,11 +3542,11 @@ program.command("status").description("Show Unity command bridge status").option
3271
3542
  source: editor_config.source ?? "lockfile",
3272
3543
  project_path: editor_config.project_path,
3273
3544
  reachable: ping.reachable,
3274
- ...ping.reachable ? {} : { error: ping.error }
3545
+ ...ping.reachable ? await describe_readiness(editor_config.port, bridge.timeout) : { error: ping.error }
3275
3546
  };
3276
3547
  }
3277
3548
  }
3278
- console.log(JSON.stringify(status, null, 2));
3549
+ print_json(status, options.pretty === true);
3279
3550
  });
3280
3551
  program.parse();
3281
3552
  })