uloop-cli 0.68.1 → 0.68.3
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/README.md +87 -18
- package/README_ja.md +87 -18
- package/dist/cli.bundle.cjs +153 -33
- package/dist/cli.bundle.cjs.map +3 -3
- package/package.json +1 -1
- package/src/__tests__/cli-e2e.test.ts +97 -4
- package/src/cli.ts +126 -30
- package/src/commands/focus-window.ts +54 -49
- package/src/default-tools.json +29 -1
- package/src/skills/deprecated-skills.ts +1 -1
- package/src/tool-cache.ts +18 -5
- package/src/version.ts +1 -1
- /package/src/skills/skill-definitions/cli-only/uloop-focus-window/{SKILL.md → Skill/SKILL.md} +0 -0
- /package/src/skills/skill-definitions/cli-only/uloop-get-project-info/{SKILL.md → Skill/SKILL.md} +0 -0
- /package/src/skills/skill-definitions/cli-only/uloop-get-version/{SKILL.md → Skill/SKILL.md} +0 -0
package/package.json
CHANGED
|
@@ -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');
|
|
@@ -459,16 +460,16 @@ describe('CLI E2E Tests (requires running Unity)', () => {
|
|
|
459
460
|
});
|
|
460
461
|
});
|
|
461
462
|
|
|
462
|
-
describe('get-
|
|
463
|
+
describe('get-unity-search-providers', () => {
|
|
463
464
|
it('should retrieve search providers', () => {
|
|
464
|
-
const result = runCliJson<{ Providers: unknown[] }>('get-
|
|
465
|
+
const result = runCliJson<{ Providers: unknown[] }>('get-unity-search-providers');
|
|
465
466
|
|
|
466
467
|
expect(Array.isArray(result.Providers)).toBe(true);
|
|
467
468
|
});
|
|
468
469
|
|
|
469
470
|
it('should support --include-descriptions false to exclude descriptions', () => {
|
|
470
471
|
const result = runCliJson<{ Providers: unknown[] }>(
|
|
471
|
-
'get-
|
|
472
|
+
'get-unity-search-providers --include-descriptions false',
|
|
472
473
|
);
|
|
473
474
|
|
|
474
475
|
expect(Array.isArray(result.Providers)).toBe(true);
|
|
@@ -476,7 +477,7 @@ describe('CLI E2E Tests (requires running Unity)', () => {
|
|
|
476
477
|
|
|
477
478
|
it('should support --sort-by-priority false to disable priority sorting', () => {
|
|
478
479
|
const result = runCliJson<{ Providers: unknown[] }>(
|
|
479
|
-
'get-
|
|
480
|
+
'get-unity-search-providers --sort-by-priority false',
|
|
480
481
|
);
|
|
481
482
|
|
|
482
483
|
expect(Array.isArray(result.Providers)).toBe(true);
|
|
@@ -500,6 +501,48 @@ describe('CLI E2E Tests (requires running Unity)', () => {
|
|
|
500
501
|
expect(stdout).toContain('--force-recompile');
|
|
501
502
|
});
|
|
502
503
|
|
|
504
|
+
it('should display grouped help with category headings', () => {
|
|
505
|
+
const { stdout, exitCode } = runCli('--help');
|
|
506
|
+
|
|
507
|
+
expect(exitCode).toBe(0);
|
|
508
|
+
expect(stdout).toContain('Built-in Tools:');
|
|
509
|
+
expect(stdout).toContain('CLI Commands:');
|
|
510
|
+
// CLI Commands should appear before Built-in Tools
|
|
511
|
+
const cliIndex: number = stdout.indexOf('CLI Commands:');
|
|
512
|
+
const builtInIndex: number = stdout.indexOf('Built-in Tools:');
|
|
513
|
+
expect(cliIndex).toBeLessThan(builtInIndex);
|
|
514
|
+
});
|
|
515
|
+
|
|
516
|
+
it('should display Third-party Tools section when cache contains third-party tools', () => {
|
|
517
|
+
const { stdout, exitCode } = runCli('--help');
|
|
518
|
+
|
|
519
|
+
expect(exitCode).toBe(0);
|
|
520
|
+
// hello-world is a third-party tool present in the local cache but not in default-tools.json
|
|
521
|
+
if (stdout.includes('hello-world')) {
|
|
522
|
+
expect(stdout).toContain('Third-party Tools:');
|
|
523
|
+
const builtInIndex: number = stdout.indexOf('Built-in Tools:');
|
|
524
|
+
const thirdPartyIndex: number = stdout.indexOf('Third-party Tools:');
|
|
525
|
+
expect(builtInIndex).toBeLessThan(thirdPartyIndex);
|
|
526
|
+
}
|
|
527
|
+
});
|
|
528
|
+
|
|
529
|
+
it('should resolve tool cache via --project-path', () => {
|
|
530
|
+
const withProjectPath = runCli(`--help --project-path "${UNITY_PROJECT_ROOT}"`);
|
|
531
|
+
const withoutProjectPath = runCli('--help');
|
|
532
|
+
|
|
533
|
+
expect(withProjectPath.exitCode).toBe(0);
|
|
534
|
+
expect(withoutProjectPath.exitCode).toBe(0);
|
|
535
|
+
|
|
536
|
+
// Both should show the same category headings
|
|
537
|
+
expect(withProjectPath.stdout).toContain('Built-in Tools:');
|
|
538
|
+
expect(withProjectPath.stdout).toContain('CLI Commands:');
|
|
539
|
+
|
|
540
|
+
// Third-party tools visible in normal help should also appear with --project-path
|
|
541
|
+
if (withoutProjectPath.stdout.includes('Third-party Tools:')) {
|
|
542
|
+
expect(withProjectPath.stdout).toContain('Third-party Tools:');
|
|
543
|
+
}
|
|
544
|
+
});
|
|
545
|
+
|
|
503
546
|
it('should display boolean options with value format in get-hierarchy help', () => {
|
|
504
547
|
const { stdout, exitCode } = runCli('get-hierarchy --help');
|
|
505
548
|
|
|
@@ -666,6 +709,56 @@ describe('CLI E2E Tests (requires running Unity)', () => {
|
|
|
666
709
|
});
|
|
667
710
|
});
|
|
668
711
|
|
|
712
|
+
describe('tool-settings', () => {
|
|
713
|
+
const settingsPath: string = join(UNITY_PROJECT_ROOT, '.uloop', 'settings.tools.json');
|
|
714
|
+
let originalSettings: string | null;
|
|
715
|
+
|
|
716
|
+
beforeAll(() => {
|
|
717
|
+
try {
|
|
718
|
+
originalSettings = readFileSync(settingsPath, 'utf-8');
|
|
719
|
+
} catch (error) {
|
|
720
|
+
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
|
721
|
+
originalSettings = null;
|
|
722
|
+
} else {
|
|
723
|
+
throw error;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
writeFileSync(settingsPath, JSON.stringify({ disabledTools: ['get-logs'] }));
|
|
727
|
+
});
|
|
728
|
+
|
|
729
|
+
afterAll(() => {
|
|
730
|
+
if (originalSettings !== null) {
|
|
731
|
+
writeFileSync(settingsPath, originalSettings);
|
|
732
|
+
} else {
|
|
733
|
+
unlinkSync(settingsPath);
|
|
734
|
+
}
|
|
735
|
+
});
|
|
736
|
+
|
|
737
|
+
it('should not display disabled tools in --help', () => {
|
|
738
|
+
const { stdout, exitCode } = runCli('--help');
|
|
739
|
+
|
|
740
|
+
expect(exitCode).toBe(0);
|
|
741
|
+
expect(stdout).toContain('compile');
|
|
742
|
+
expect(stdout).not.toContain('get-logs');
|
|
743
|
+
});
|
|
744
|
+
|
|
745
|
+
it('should not include disabled tools in --list-commands', () => {
|
|
746
|
+
const { stdout, exitCode } = runCli('--list-commands');
|
|
747
|
+
|
|
748
|
+
expect(exitCode).toBe(0);
|
|
749
|
+
const commands: string[] = stdout.trim().split('\n').filter(Boolean);
|
|
750
|
+
expect(commands).toContain('compile');
|
|
751
|
+
expect(commands).not.toContain('get-logs');
|
|
752
|
+
});
|
|
753
|
+
|
|
754
|
+
it('should output nothing for --list-options on disabled tool', () => {
|
|
755
|
+
const { stdout, exitCode } = runCli('--list-options get-logs');
|
|
756
|
+
|
|
757
|
+
expect(exitCode).toBe(0);
|
|
758
|
+
expect(stdout.trim()).toBe('');
|
|
759
|
+
});
|
|
760
|
+
});
|
|
761
|
+
|
|
669
762
|
// Domain Reload tests must run last to avoid affecting other tests
|
|
670
763
|
describe('compile --force-recompile (Domain Reload)', () => {
|
|
671
764
|
it('should support --force-recompile option', () => {
|
package/src/cli.ts
CHANGED
|
@@ -12,7 +12,7 @@ import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from '
|
|
|
12
12
|
import { join, basename, dirname } from 'path';
|
|
13
13
|
import { homedir } from 'os';
|
|
14
14
|
import { spawn } from 'child_process';
|
|
15
|
-
import { Command } from 'commander';
|
|
15
|
+
import { Command, Option } from 'commander';
|
|
16
16
|
import {
|
|
17
17
|
executeToolCommand,
|
|
18
18
|
listAvailableTools,
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
loadToolsCache,
|
|
25
25
|
hasCacheFile,
|
|
26
26
|
getDefaultTools,
|
|
27
|
+
getDefaultToolNames,
|
|
27
28
|
ToolDefinition,
|
|
28
29
|
ToolProperty,
|
|
29
30
|
getCachedServerVersion,
|
|
@@ -41,9 +42,20 @@ interface CliOptions extends GlobalOptions {
|
|
|
41
42
|
[key: string]: unknown;
|
|
42
43
|
}
|
|
43
44
|
|
|
45
|
+
const FOCUS_WINDOW_COMMAND = 'focus-window' as const;
|
|
44
46
|
const LAUNCH_COMMAND = 'launch' as const;
|
|
45
47
|
const UPDATE_COMMAND = 'update' as const;
|
|
46
48
|
|
|
49
|
+
const HELP_GROUP_BUILTIN_TOOLS = 'Built-in Tools:' as const;
|
|
50
|
+
const HELP_GROUP_THIRD_PARTY_TOOLS = 'Third-party Tools:' as const;
|
|
51
|
+
const HELP_GROUP_CLI_COMMANDS = 'CLI Commands:' as const;
|
|
52
|
+
|
|
53
|
+
const HELP_GROUP_ORDER = [
|
|
54
|
+
HELP_GROUP_CLI_COMMANDS,
|
|
55
|
+
HELP_GROUP_BUILTIN_TOOLS,
|
|
56
|
+
HELP_GROUP_THIRD_PARTY_TOOLS,
|
|
57
|
+
] as const;
|
|
58
|
+
|
|
47
59
|
// commander.js built-in flags that exit immediately without needing Unity
|
|
48
60
|
const NO_SYNC_FLAGS = ['-v', '--version', '-h', '--help'] as const;
|
|
49
61
|
|
|
@@ -55,7 +67,7 @@ const BUILTIN_COMMANDS = [
|
|
|
55
67
|
'fix',
|
|
56
68
|
'skills',
|
|
57
69
|
LAUNCH_COMMAND,
|
|
58
|
-
|
|
70
|
+
FOCUS_WINDOW_COMMAND,
|
|
59
71
|
] as const;
|
|
60
72
|
|
|
61
73
|
const program = new Command();
|
|
@@ -64,7 +76,45 @@ program
|
|
|
64
76
|
.name('uloop')
|
|
65
77
|
.description('Unity MCP CLI - Direct communication with Unity Editor')
|
|
66
78
|
.version(VERSION, '-v, --version', 'Output the version number')
|
|
67
|
-
.showHelpAfterError('(run with -h for available options)')
|
|
79
|
+
.showHelpAfterError('(run with -h for available options)')
|
|
80
|
+
.configureHelp({
|
|
81
|
+
sortSubcommands: true,
|
|
82
|
+
// commander.js default groupItems determines group display order by registration order,
|
|
83
|
+
// but CLI commands are registered at module level (before tools), so the default order
|
|
84
|
+
// would be wrong. We re-implement to enforce HELP_GROUP_ORDER.
|
|
85
|
+
groupItems<T extends Command | Option>(
|
|
86
|
+
unsortedItems: T[],
|
|
87
|
+
visibleItems: T[],
|
|
88
|
+
getGroup: (item: T) => string,
|
|
89
|
+
): Map<string, T[]> {
|
|
90
|
+
const groupMap = new Map<string, T[]>();
|
|
91
|
+
for (const item of unsortedItems) {
|
|
92
|
+
const group: string = getGroup(item);
|
|
93
|
+
if (!groupMap.has(group)) {
|
|
94
|
+
groupMap.set(group, []);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
for (const item of visibleItems) {
|
|
98
|
+
const group: string = getGroup(item);
|
|
99
|
+
if (!groupMap.has(group)) {
|
|
100
|
+
groupMap.set(group, []);
|
|
101
|
+
}
|
|
102
|
+
groupMap.get(group)!.push(item);
|
|
103
|
+
}
|
|
104
|
+
const ordered = new Map<string, T[]>();
|
|
105
|
+
for (const key of HELP_GROUP_ORDER) {
|
|
106
|
+
const items: T[] | undefined = groupMap.get(key);
|
|
107
|
+
if (items !== undefined) {
|
|
108
|
+
ordered.set(key, items);
|
|
109
|
+
groupMap.delete(key);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
for (const [key, value] of groupMap) {
|
|
113
|
+
ordered.set(key, value);
|
|
114
|
+
}
|
|
115
|
+
return ordered;
|
|
116
|
+
},
|
|
117
|
+
});
|
|
68
118
|
|
|
69
119
|
// --list-commands: Output command names for shell completion
|
|
70
120
|
program.option('--list-commands', 'List all command names (for shell completion)');
|
|
@@ -72,6 +122,12 @@ program.option('--list-commands', 'List all command names (for shell completion)
|
|
|
72
122
|
// --list-options <cmd>: Output options for a specific command (for shell completion)
|
|
73
123
|
program.option('--list-options <cmd>', 'List options for a command (for shell completion)');
|
|
74
124
|
|
|
125
|
+
// Set default help group for CLI commands registered at module level
|
|
126
|
+
program.commandsGroup(HELP_GROUP_CLI_COMMANDS);
|
|
127
|
+
// Eagerly initialize the implicit help command so it inherits the CLI Commands group.
|
|
128
|
+
// Without this, the lazy-created help command skips _initCommandGroup and falls into "Commands:" group.
|
|
129
|
+
program.helpCommand(true);
|
|
130
|
+
|
|
75
131
|
// Built-in commands (not from tools.json)
|
|
76
132
|
program
|
|
77
133
|
.command('list')
|
|
@@ -124,18 +180,19 @@ registerSkillsCommand(program);
|
|
|
124
180
|
// Register launch subcommand
|
|
125
181
|
registerLaunchCommand(program);
|
|
126
182
|
|
|
127
|
-
//
|
|
128
|
-
|
|
183
|
+
// focus-window is registered conditionally in main() based on tool settings,
|
|
184
|
+
// since it corresponds to an MCP tool that can be disabled via Tool Settings UI
|
|
129
185
|
|
|
130
186
|
/**
|
|
131
187
|
* Register a tool as a CLI command dynamically.
|
|
132
188
|
*/
|
|
133
|
-
function registerToolCommand(tool: ToolDefinition): void {
|
|
189
|
+
function registerToolCommand(tool: ToolDefinition, helpGroup: string): void {
|
|
134
190
|
// Skip if already registered as a built-in command
|
|
135
191
|
if (BUILTIN_COMMANDS.includes(tool.name as (typeof BUILTIN_COMMANDS)[number])) {
|
|
136
192
|
return;
|
|
137
193
|
}
|
|
138
|
-
const
|
|
194
|
+
const firstLine: string = tool.description.split('\n')[0];
|
|
195
|
+
const cmd = program.command(tool.name).description(firstLine).helpGroup(helpGroup);
|
|
139
196
|
|
|
140
197
|
// Add options from inputSchema.properties
|
|
141
198
|
const properties = tool.inputSchema.properties;
|
|
@@ -302,6 +359,10 @@ function convertValue(value: unknown, propInfo: ToolProperty): unknown {
|
|
|
302
359
|
return value;
|
|
303
360
|
}
|
|
304
361
|
|
|
362
|
+
function getToolHelpGroup(toolName: string, defaultToolNames: ReadonlySet<string>): string {
|
|
363
|
+
return defaultToolNames.has(toolName) ? HELP_GROUP_BUILTIN_TOOLS : HELP_GROUP_THIRD_PARTY_TOOLS;
|
|
364
|
+
}
|
|
365
|
+
|
|
305
366
|
function extractGlobalOptions(options: Record<string, unknown>): GlobalOptions {
|
|
306
367
|
return {
|
|
307
368
|
port: options['port'] as string | undefined,
|
|
@@ -717,12 +778,14 @@ function handleCompletionOptions(): boolean {
|
|
|
717
778
|
|
|
718
779
|
if (args.includes('--list-commands')) {
|
|
719
780
|
const tools = loadToolsCache();
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
781
|
+
const enabledTools: ToolDefinition[] = filterEnabledTools(tools.tools, projectPath);
|
|
782
|
+
const allCommands = [
|
|
783
|
+
...BUILTIN_COMMANDS.filter(
|
|
784
|
+
(cmd) => cmd !== FOCUS_WINDOW_COMMAND || isToolEnabled(cmd, projectPath),
|
|
785
|
+
),
|
|
786
|
+
...enabledTools.map((t) => t.name),
|
|
787
|
+
];
|
|
788
|
+
console.log(allCommands.sort().join('\n'));
|
|
726
789
|
return true;
|
|
727
790
|
}
|
|
728
791
|
|
|
@@ -746,13 +809,10 @@ function listOptionsForCommand(cmdName: string, projectPath?: string): void {
|
|
|
746
809
|
}
|
|
747
810
|
|
|
748
811
|
// 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
812
|
const tools = loadToolsCache();
|
|
752
|
-
const tool: ToolDefinition | undefined =
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
: tools.tools.find((t) => t.name === cmdName);
|
|
813
|
+
const tool: ToolDefinition | undefined = filterEnabledTools(tools.tools, projectPath).find(
|
|
814
|
+
(t) => t.name === cmdName,
|
|
815
|
+
);
|
|
756
816
|
if (!tool) {
|
|
757
817
|
return;
|
|
758
818
|
}
|
|
@@ -770,6 +830,9 @@ function listOptionsForCommand(cmdName: string, projectPath?: string): void {
|
|
|
770
830
|
* Check if a command exists in the current program.
|
|
771
831
|
*/
|
|
772
832
|
function commandExists(cmdName: string, projectPath?: string): boolean {
|
|
833
|
+
if (cmdName === FOCUS_WINDOW_COMMAND) {
|
|
834
|
+
return isToolEnabled(FOCUS_WINDOW_COMMAND, projectPath);
|
|
835
|
+
}
|
|
773
836
|
if (BUILTIN_COMMANDS.includes(cmdName as (typeof BUILTIN_COMMANDS)[number])) {
|
|
774
837
|
return true;
|
|
775
838
|
}
|
|
@@ -784,6 +847,26 @@ function shouldSkipAutoSync(cmdName: string | undefined, args: string[]): boolea
|
|
|
784
847
|
return args.some((arg) => (NO_SYNC_FLAGS as readonly string[]).includes(arg));
|
|
785
848
|
}
|
|
786
849
|
|
|
850
|
+
// Options that consume the next argument as a value
|
|
851
|
+
const OPTIONS_WITH_VALUE: ReadonlySet<string> = new Set(['--port', '-p', '--project-path']);
|
|
852
|
+
|
|
853
|
+
/**
|
|
854
|
+
* Find the first non-option argument that is not a value of a known option.
|
|
855
|
+
*/
|
|
856
|
+
function findCommandName(args: readonly string[]): string | undefined {
|
|
857
|
+
for (let i = 0; i < args.length; i++) {
|
|
858
|
+
const arg: string = args[i];
|
|
859
|
+
if (arg.startsWith('-')) {
|
|
860
|
+
if (OPTIONS_WITH_VALUE.has(arg)) {
|
|
861
|
+
i++; // skip the next arg (option value)
|
|
862
|
+
}
|
|
863
|
+
continue;
|
|
864
|
+
}
|
|
865
|
+
return arg;
|
|
866
|
+
}
|
|
867
|
+
return undefined;
|
|
868
|
+
}
|
|
869
|
+
|
|
787
870
|
function extractSyncGlobalOptions(args: string[]): GlobalOptions {
|
|
788
871
|
const options: GlobalOptions = {};
|
|
789
872
|
|
|
@@ -828,8 +911,8 @@ async function main(): Promise<void> {
|
|
|
828
911
|
}
|
|
829
912
|
|
|
830
913
|
const args = process.argv.slice(2);
|
|
831
|
-
const cmdName = args.find((arg) => !arg.startsWith('-'));
|
|
832
914
|
const syncGlobalOptions = extractSyncGlobalOptions(args);
|
|
915
|
+
const cmdName = findCommandName(args);
|
|
833
916
|
|
|
834
917
|
// No command name = no Unity operation; skip project detection
|
|
835
918
|
const NO_PROJECT_COMMANDS = [UPDATE_COMMAND, 'completion'] as const;
|
|
@@ -837,15 +920,24 @@ async function main(): Promise<void> {
|
|
|
837
920
|
cmdName === undefined || (NO_PROJECT_COMMANDS as readonly string[]).includes(cmdName);
|
|
838
921
|
|
|
839
922
|
if (skipProjectDetection) {
|
|
840
|
-
const
|
|
841
|
-
// Only filter disabled tools
|
|
842
|
-
//
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
923
|
+
const defaultToolNames: ReadonlySet<string> = getDefaultToolNames();
|
|
924
|
+
// Only filter disabled tools for top-level help (uloop --help); subcommand help
|
|
925
|
+
// (e.g. uloop completion --help) does not list dynamic tools, so scanning is unnecessary
|
|
926
|
+
const isTopLevelHelp: boolean =
|
|
927
|
+
cmdName === undefined && (args.includes('-h') || args.includes('--help'));
|
|
928
|
+
const shouldFilter: boolean = syncGlobalOptions.projectPath !== undefined || isTopLevelHelp;
|
|
929
|
+
// Use cache to include third-party tools in help output; falls back to defaults when no cache exists
|
|
930
|
+
const sourceTools: ToolDefinition[] = shouldFilter
|
|
931
|
+
? loadToolsCache(syncGlobalOptions.projectPath).tools
|
|
932
|
+
: getDefaultTools().tools;
|
|
933
|
+
const tools: ToolDefinition[] = shouldFilter
|
|
934
|
+
? filterEnabledTools(sourceTools, syncGlobalOptions.projectPath)
|
|
935
|
+
: sourceTools;
|
|
936
|
+
if (!shouldFilter || isToolEnabled(FOCUS_WINDOW_COMMAND, syncGlobalOptions.projectPath)) {
|
|
937
|
+
registerFocusWindowCommand(program, HELP_GROUP_BUILTIN_TOOLS);
|
|
938
|
+
}
|
|
847
939
|
for (const tool of tools) {
|
|
848
|
-
registerToolCommand(tool);
|
|
940
|
+
registerToolCommand(tool, getToolHelpGroup(tool.name, defaultToolNames));
|
|
849
941
|
}
|
|
850
942
|
program.parse();
|
|
851
943
|
return;
|
|
@@ -878,8 +970,12 @@ async function main(): Promise<void> {
|
|
|
878
970
|
// Register tool commands from cache (after potential auto-sync)
|
|
879
971
|
const toolsCache = loadToolsCache();
|
|
880
972
|
const projectPath: string | undefined = syncGlobalOptions.projectPath;
|
|
973
|
+
const defaultToolNames: ReadonlySet<string> = getDefaultToolNames();
|
|
974
|
+
if (isToolEnabled(FOCUS_WINDOW_COMMAND, projectPath)) {
|
|
975
|
+
registerFocusWindowCommand(program, HELP_GROUP_BUILTIN_TOOLS);
|
|
976
|
+
}
|
|
881
977
|
for (const tool of filterEnabledTools(toolsCache.tools, projectPath)) {
|
|
882
|
-
registerToolCommand(tool);
|
|
978
|
+
registerToolCommand(tool, getToolHelpGroup(tool.name, defaultToolNames));
|
|
883
979
|
}
|
|
884
980
|
|
|
885
981
|
if (cmdName && !commandExists(cmdName, projectPath)) {
|
|
@@ -894,7 +990,7 @@ async function main(): Promise<void> {
|
|
|
894
990
|
const newCache = loadToolsCache();
|
|
895
991
|
const tool = filterEnabledTools(newCache.tools, projectPath).find((t) => t.name === cmdName);
|
|
896
992
|
if (tool) {
|
|
897
|
-
registerToolCommand(tool);
|
|
993
|
+
registerToolCommand(tool, getToolHelpGroup(tool.name, defaultToolNames));
|
|
898
994
|
console.log(`\x1b[32m✓ Found '${cmdName}' after sync.\x1b[0m\n`);
|
|
899
995
|
} else {
|
|
900
996
|
console.error(`\x1b[31mError: Command '${cmdName}' not found even after sync.\x1b[0m`);
|
|
@@ -12,66 +12,71 @@ import { findRunningUnityProcess, focusUnityProcess } from 'launch-unity';
|
|
|
12
12
|
import { findUnityProjectRoot } from '../project-root.js';
|
|
13
13
|
import { validateProjectPath } from '../port-resolver.js';
|
|
14
14
|
|
|
15
|
-
export function registerFocusWindowCommand(program: Command): void {
|
|
16
|
-
program
|
|
15
|
+
export function registerFocusWindowCommand(program: Command, helpGroup?: string): void {
|
|
16
|
+
const cmd = program
|
|
17
17
|
.command('focus-window')
|
|
18
18
|
.description('Bring Unity Editor window to front using OS-level commands')
|
|
19
|
-
.option('--project-path <path>', 'Unity project path')
|
|
20
|
-
.action(async (options: { projectPath?: string }) => {
|
|
21
|
-
let projectRoot: string | null;
|
|
22
|
-
if (options.projectPath !== undefined) {
|
|
23
|
-
try {
|
|
24
|
-
projectRoot = validateProjectPath(options.projectPath);
|
|
25
|
-
} catch (error) {
|
|
26
|
-
console.error(
|
|
27
|
-
JSON.stringify({
|
|
28
|
-
Success: false,
|
|
29
|
-
Message: error instanceof Error ? error.message : String(error),
|
|
30
|
-
}),
|
|
31
|
-
);
|
|
32
|
-
process.exit(1);
|
|
33
|
-
return;
|
|
34
|
-
}
|
|
35
|
-
} else {
|
|
36
|
-
projectRoot = findUnityProjectRoot();
|
|
37
|
-
}
|
|
38
|
-
if (projectRoot === null) {
|
|
39
|
-
console.error(
|
|
40
|
-
JSON.stringify({
|
|
41
|
-
Success: false,
|
|
42
|
-
Message: 'Unity project not found',
|
|
43
|
-
}),
|
|
44
|
-
);
|
|
45
|
-
process.exit(1);
|
|
46
|
-
}
|
|
19
|
+
.option('--project-path <path>', 'Unity project path');
|
|
47
20
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
JSON.stringify({
|
|
52
|
-
Success: false,
|
|
53
|
-
Message: 'No running Unity process found for this project',
|
|
54
|
-
}),
|
|
55
|
-
);
|
|
56
|
-
process.exit(1);
|
|
57
|
-
}
|
|
21
|
+
if (helpGroup !== undefined) {
|
|
22
|
+
cmd.helpGroup(helpGroup);
|
|
23
|
+
}
|
|
58
24
|
|
|
25
|
+
cmd.action(async (options: { projectPath?: string }) => {
|
|
26
|
+
let projectRoot: string | null;
|
|
27
|
+
if (options.projectPath !== undefined) {
|
|
59
28
|
try {
|
|
60
|
-
|
|
61
|
-
console.log(
|
|
62
|
-
JSON.stringify({
|
|
63
|
-
Success: true,
|
|
64
|
-
Message: `Unity Editor window focused (PID: ${runningProcess.pid})`,
|
|
65
|
-
}),
|
|
66
|
-
);
|
|
29
|
+
projectRoot = validateProjectPath(options.projectPath);
|
|
67
30
|
} catch (error) {
|
|
68
31
|
console.error(
|
|
69
32
|
JSON.stringify({
|
|
70
33
|
Success: false,
|
|
71
|
-
Message:
|
|
34
|
+
Message: error instanceof Error ? error.message : String(error),
|
|
72
35
|
}),
|
|
73
36
|
);
|
|
74
37
|
process.exit(1);
|
|
38
|
+
return;
|
|
75
39
|
}
|
|
76
|
-
}
|
|
40
|
+
} else {
|
|
41
|
+
projectRoot = findUnityProjectRoot();
|
|
42
|
+
}
|
|
43
|
+
if (projectRoot === null) {
|
|
44
|
+
console.error(
|
|
45
|
+
JSON.stringify({
|
|
46
|
+
Success: false,
|
|
47
|
+
Message: 'Unity project not found',
|
|
48
|
+
}),
|
|
49
|
+
);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const runningProcess = await findRunningUnityProcess(projectRoot);
|
|
54
|
+
if (!runningProcess) {
|
|
55
|
+
console.error(
|
|
56
|
+
JSON.stringify({
|
|
57
|
+
Success: false,
|
|
58
|
+
Message: 'No running Unity process found for this project',
|
|
59
|
+
}),
|
|
60
|
+
);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
await focusUnityProcess(runningProcess.pid);
|
|
66
|
+
console.log(
|
|
67
|
+
JSON.stringify({
|
|
68
|
+
Success: true,
|
|
69
|
+
Message: `Unity Editor window focused (PID: ${runningProcess.pid})`,
|
|
70
|
+
}),
|
|
71
|
+
);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error(
|
|
74
|
+
JSON.stringify({
|
|
75
|
+
Success: false,
|
|
76
|
+
Message: `Failed to focus Unity window: ${error instanceof Error ? error.message : String(error)}`,
|
|
77
|
+
}),
|
|
78
|
+
);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
77
82
|
}
|
package/src/default-tools.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"version": "0.68.
|
|
2
|
+
"version": "0.68.3",
|
|
3
3
|
"tools": [
|
|
4
4
|
{
|
|
5
5
|
"name": "compile",
|
|
@@ -239,6 +239,34 @@
|
|
|
239
239
|
}
|
|
240
240
|
}
|
|
241
241
|
},
|
|
242
|
+
{
|
|
243
|
+
"name": "get-unity-search-providers",
|
|
244
|
+
"description": "Get detailed information about Unity Search providers including display names, descriptions, active status, and capabilities",
|
|
245
|
+
"inputSchema": {
|
|
246
|
+
"type": "object",
|
|
247
|
+
"properties": {
|
|
248
|
+
"ProviderId": {
|
|
249
|
+
"type": "string",
|
|
250
|
+
"description": "Specific provider ID to get details for (empty = all providers). Examples: 'asset', 'scene', 'menu', 'settings'"
|
|
251
|
+
},
|
|
252
|
+
"ActiveOnly": {
|
|
253
|
+
"type": "boolean",
|
|
254
|
+
"description": "Whether to include only active providers",
|
|
255
|
+
"default": false
|
|
256
|
+
},
|
|
257
|
+
"SortByPriority": {
|
|
258
|
+
"type": "boolean",
|
|
259
|
+
"description": "Sort providers by priority (lower number = higher priority)",
|
|
260
|
+
"default": true
|
|
261
|
+
},
|
|
262
|
+
"IncludeDescriptions": {
|
|
263
|
+
"type": "boolean",
|
|
264
|
+
"description": "Include detailed descriptions for each provider",
|
|
265
|
+
"default": true
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
},
|
|
242
270
|
{
|
|
243
271
|
"name": "get-menu-items",
|
|
244
272
|
"description": "Retrieve Unity MenuItems",
|
|
@@ -6,5 +6,5 @@
|
|
|
6
6
|
*/
|
|
7
7
|
export const DEPRECATED_SKILLS: string[] = [
|
|
8
8
|
'uloop-capture-window', // renamed to uloop-screenshot in v0.54.0
|
|
9
|
-
'uloop-get-provider-details', //
|
|
9
|
+
'uloop-get-provider-details', // renamed to uloop-get-unity-search-providers
|
|
10
10
|
];
|
package/src/tool-cache.ts
CHANGED
|
@@ -41,7 +41,10 @@ export interface ToolsCache {
|
|
|
41
41
|
const CACHE_DIR = '.uloop';
|
|
42
42
|
const CACHE_FILE = 'tools.json';
|
|
43
43
|
|
|
44
|
-
function getCacheDir(): string {
|
|
44
|
+
function getCacheDir(projectPath?: string): string {
|
|
45
|
+
if (projectPath !== undefined) {
|
|
46
|
+
return join(projectPath, CACHE_DIR);
|
|
47
|
+
}
|
|
45
48
|
const projectRoot = findUnityProjectRoot();
|
|
46
49
|
if (projectRoot === null) {
|
|
47
50
|
return join(process.cwd(), CACHE_DIR);
|
|
@@ -49,8 +52,8 @@ function getCacheDir(): string {
|
|
|
49
52
|
return join(projectRoot, CACHE_DIR);
|
|
50
53
|
}
|
|
51
54
|
|
|
52
|
-
function getCachePath(): string {
|
|
53
|
-
return join(getCacheDir(), CACHE_FILE);
|
|
55
|
+
function getCachePath(projectPath?: string): string {
|
|
56
|
+
return join(getCacheDir(projectPath), CACHE_FILE);
|
|
54
57
|
}
|
|
55
58
|
|
|
56
59
|
/**
|
|
@@ -62,9 +65,10 @@ export function getDefaultTools(): ToolsCache {
|
|
|
62
65
|
|
|
63
66
|
/**
|
|
64
67
|
* Load tools from cache file, falling back to default tools if cache doesn't exist.
|
|
68
|
+
* When projectPath is specified, reads cache from that project directory.
|
|
65
69
|
*/
|
|
66
|
-
export function loadToolsCache(): ToolsCache {
|
|
67
|
-
const cachePath = getCachePath();
|
|
70
|
+
export function loadToolsCache(projectPath?: string): ToolsCache {
|
|
71
|
+
const cachePath = getCachePath(projectPath);
|
|
68
72
|
|
|
69
73
|
if (existsSync(cachePath)) {
|
|
70
74
|
try {
|
|
@@ -107,6 +111,15 @@ export function getCacheFilePath(): string {
|
|
|
107
111
|
return getCachePath();
|
|
108
112
|
}
|
|
109
113
|
|
|
114
|
+
/**
|
|
115
|
+
* Get the set of default tool names bundled with npm package.
|
|
116
|
+
* Used to distinguish built-in tools from third-party tools.
|
|
117
|
+
*/
|
|
118
|
+
export function getDefaultToolNames(): ReadonlySet<string> {
|
|
119
|
+
const defaultTools: ToolsCache = getDefaultTools();
|
|
120
|
+
return new Set(defaultTools.tools.map((tool: ToolDefinition) => tool.name));
|
|
121
|
+
}
|
|
122
|
+
|
|
110
123
|
/**
|
|
111
124
|
* Get the Unity server version from cache file.
|
|
112
125
|
* Returns undefined if cache doesn't exist, is corrupted, or serverVersion is missing.
|
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.
|
|
7
|
+
export const VERSION = '0.68.3'; // x-release-please-version
|
/package/src/skills/skill-definitions/cli-only/uloop-focus-window/{SKILL.md → Skill/SKILL.md}
RENAMED
|
File without changes
|
/package/src/skills/skill-definitions/cli-only/uloop-get-project-info/{SKILL.md → Skill/SKILL.md}
RENAMED
|
File without changes
|
/package/src/skills/skill-definitions/cli-only/uloop-get-version/{SKILL.md → Skill/SKILL.md}
RENAMED
|
File without changes
|