uloop-cli 0.68.1 → 0.68.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uloop-cli",
3
- "version": "0.68.1",
3
+ "version": "0.68.2",
4
4
  "//version": "x-release-please-version",
5
5
  "description": "CLI tool for Unity Editor communication via uLoopMCP",
6
6
  "main": "dist/cli.bundle.cjs",
@@ -13,6 +13,7 @@ import {
13
13
  spawnSync,
14
14
  SpawnSyncOptionsWithStringEncoding,
15
15
  } from 'child_process';
16
+ import { readFileSync, writeFileSync, unlinkSync } from 'fs';
16
17
  import { join } from 'path';
17
18
 
18
19
  const CLI_PATH = join(__dirname, '../..', 'dist/cli.bundle.cjs');
@@ -666,6 +667,56 @@ describe('CLI E2E Tests (requires running Unity)', () => {
666
667
  });
667
668
  });
668
669
 
670
+ describe('tool-settings', () => {
671
+ const settingsPath: string = join(UNITY_PROJECT_ROOT, '.uloop', 'settings.tools.json');
672
+ let originalSettings: string | null;
673
+
674
+ beforeAll(() => {
675
+ try {
676
+ originalSettings = readFileSync(settingsPath, 'utf-8');
677
+ } catch (error) {
678
+ if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
679
+ originalSettings = null;
680
+ } else {
681
+ throw error;
682
+ }
683
+ }
684
+ writeFileSync(settingsPath, JSON.stringify({ disabledTools: ['get-logs'] }));
685
+ });
686
+
687
+ afterAll(() => {
688
+ if (originalSettings !== null) {
689
+ writeFileSync(settingsPath, originalSettings);
690
+ } else {
691
+ unlinkSync(settingsPath);
692
+ }
693
+ });
694
+
695
+ it('should not display disabled tools in --help', () => {
696
+ const { stdout, exitCode } = runCli('--help');
697
+
698
+ expect(exitCode).toBe(0);
699
+ expect(stdout).toContain('compile');
700
+ expect(stdout).not.toContain('get-logs');
701
+ });
702
+
703
+ it('should not include disabled tools in --list-commands', () => {
704
+ const { stdout, exitCode } = runCli('--list-commands');
705
+
706
+ expect(exitCode).toBe(0);
707
+ const commands: string[] = stdout.trim().split('\n').filter(Boolean);
708
+ expect(commands).toContain('compile');
709
+ expect(commands).not.toContain('get-logs');
710
+ });
711
+
712
+ it('should output nothing for --list-options on disabled tool', () => {
713
+ const { stdout, exitCode } = runCli('--list-options get-logs');
714
+
715
+ expect(exitCode).toBe(0);
716
+ expect(stdout.trim()).toBe('');
717
+ });
718
+ });
719
+
669
720
  // Domain Reload tests must run last to avoid affecting other tests
670
721
  describe('compile --force-recompile (Domain Reload)', () => {
671
722
  it('should support --force-recompile option', () => {
package/src/cli.ts CHANGED
@@ -41,6 +41,7 @@ interface CliOptions extends GlobalOptions {
41
41
  [key: string]: unknown;
42
42
  }
43
43
 
44
+ const FOCUS_WINDOW_COMMAND = 'focus-window' as const;
44
45
  const LAUNCH_COMMAND = 'launch' as const;
45
46
  const UPDATE_COMMAND = 'update' as const;
46
47
 
@@ -55,7 +56,7 @@ const BUILTIN_COMMANDS = [
55
56
  'fix',
56
57
  'skills',
57
58
  LAUNCH_COMMAND,
58
- 'focus-window',
59
+ FOCUS_WINDOW_COMMAND,
59
60
  ] as const;
60
61
 
61
62
  const program = new Command();
@@ -64,7 +65,8 @@ program
64
65
  .name('uloop')
65
66
  .description('Unity MCP CLI - Direct communication with Unity Editor')
66
67
  .version(VERSION, '-v, --version', 'Output the version number')
67
- .showHelpAfterError('(run with -h for available options)');
68
+ .showHelpAfterError('(run with -h for available options)')
69
+ .configureHelp({ sortSubcommands: true });
68
70
 
69
71
  // --list-commands: Output command names for shell completion
70
72
  program.option('--list-commands', 'List all command names (for shell completion)');
@@ -124,8 +126,8 @@ registerSkillsCommand(program);
124
126
  // Register launch subcommand
125
127
  registerLaunchCommand(program);
126
128
 
127
- // Register focus-window subcommand
128
- registerFocusWindowCommand(program);
129
+ // focus-window is registered conditionally in main() based on tool settings,
130
+ // since it corresponds to an MCP tool that can be disabled via Tool Settings UI
129
131
 
130
132
  /**
131
133
  * Register a tool as a CLI command dynamically.
@@ -717,12 +719,14 @@ function handleCompletionOptions(): boolean {
717
719
 
718
720
  if (args.includes('--list-commands')) {
719
721
  const tools = loadToolsCache();
720
- // Only filter disabled tools when --project-path is explicitly provided;
721
- // without it, loadDisabledTools would trigger findUnityProjectRoot() scanning
722
- const enabledTools: ToolDefinition[] =
723
- projectPath !== undefined ? filterEnabledTools(tools.tools, projectPath) : tools.tools;
724
- const allCommands = [...BUILTIN_COMMANDS, ...enabledTools.map((t) => t.name)];
725
- console.log(allCommands.join('\n'));
722
+ const enabledTools: ToolDefinition[] = filterEnabledTools(tools.tools, projectPath);
723
+ const allCommands = [
724
+ ...BUILTIN_COMMANDS.filter(
725
+ (cmd) => cmd !== FOCUS_WINDOW_COMMAND || isToolEnabled(cmd, projectPath),
726
+ ),
727
+ ...enabledTools.map((t) => t.name),
728
+ ];
729
+ console.log(allCommands.sort().join('\n'));
726
730
  return true;
727
731
  }
728
732
 
@@ -746,13 +750,10 @@ function listOptionsForCommand(cmdName: string, projectPath?: string): void {
746
750
  }
747
751
 
748
752
  // Tool commands - only output tool-specific options
749
- // Only filter disabled tools when --project-path is explicitly provided;
750
- // without it, loadDisabledTools would trigger findUnityProjectRoot() scanning
751
753
  const tools = loadToolsCache();
752
- const tool: ToolDefinition | undefined =
753
- projectPath !== undefined
754
- ? filterEnabledTools(tools.tools, projectPath).find((t) => t.name === cmdName)
755
- : tools.tools.find((t) => t.name === cmdName);
754
+ const tool: ToolDefinition | undefined = filterEnabledTools(tools.tools, projectPath).find(
755
+ (t) => t.name === cmdName,
756
+ );
756
757
  if (!tool) {
757
758
  return;
758
759
  }
@@ -770,6 +771,9 @@ function listOptionsForCommand(cmdName: string, projectPath?: string): void {
770
771
  * Check if a command exists in the current program.
771
772
  */
772
773
  function commandExists(cmdName: string, projectPath?: string): boolean {
774
+ if (cmdName === FOCUS_WINDOW_COMMAND) {
775
+ return isToolEnabled(FOCUS_WINDOW_COMMAND, projectPath);
776
+ }
773
777
  if (BUILTIN_COMMANDS.includes(cmdName as (typeof BUILTIN_COMMANDS)[number])) {
774
778
  return true;
775
779
  }
@@ -838,12 +842,17 @@ async function main(): Promise<void> {
838
842
 
839
843
  if (skipProjectDetection) {
840
844
  const defaultTools = getDefaultTools();
841
- // Only filter disabled tools when --project-path is explicitly provided;
842
- // without it, filterEnabledTools would trigger findUnityProjectRoot() scanning
843
- const tools: ToolDefinition[] =
844
- syncGlobalOptions.projectPath !== undefined
845
- ? filterEnabledTools(defaultTools.tools, syncGlobalOptions.projectPath)
846
- : defaultTools.tools;
845
+ // Only filter disabled tools for top-level help (uloop --help); subcommand help
846
+ // (e.g. uloop completion --help) does not list dynamic tools, so scanning is unnecessary
847
+ const isTopLevelHelp: boolean =
848
+ cmdName === undefined && (args.includes('-h') || args.includes('--help'));
849
+ const shouldFilter: boolean = syncGlobalOptions.projectPath !== undefined || isTopLevelHelp;
850
+ const tools: ToolDefinition[] = shouldFilter
851
+ ? filterEnabledTools(defaultTools.tools, syncGlobalOptions.projectPath)
852
+ : defaultTools.tools;
853
+ if (!shouldFilter || isToolEnabled(FOCUS_WINDOW_COMMAND, syncGlobalOptions.projectPath)) {
854
+ registerFocusWindowCommand(program);
855
+ }
847
856
  for (const tool of tools) {
848
857
  registerToolCommand(tool);
849
858
  }
@@ -878,6 +887,9 @@ async function main(): Promise<void> {
878
887
  // Register tool commands from cache (after potential auto-sync)
879
888
  const toolsCache = loadToolsCache();
880
889
  const projectPath: string | undefined = syncGlobalOptions.projectPath;
890
+ if (isToolEnabled(FOCUS_WINDOW_COMMAND, projectPath)) {
891
+ registerFocusWindowCommand(program);
892
+ }
881
893
  for (const tool of filterEnabledTools(toolsCache.tools, projectPath)) {
882
894
  registerToolCommand(tool);
883
895
  }
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.68.1",
2
+ "version": "0.68.2",
3
3
  "tools": [
4
4
  {
5
5
  "name": "compile",
package/src/version.ts CHANGED
@@ -4,4 +4,4 @@
4
4
  * This file exists to avoid bundling the entire package.json into the CLI bundle.
5
5
  * This version is automatically updated by release-please.
6
6
  */
7
- export const VERSION = '0.68.1'; // x-release-please-version
7
+ export const VERSION = '0.68.2'; // x-release-please-version