uloop-cli 0.48.3 → 0.49.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 (31) hide show
  1. package/dist/cli.bundle.cjs +211 -22
  2. package/dist/cli.bundle.cjs.map +3 -3
  3. package/jest.config.cjs +2 -1
  4. package/md-transformer.cjs +11 -0
  5. package/package.json +1 -1
  6. package/scripts/generate-bundled-skills.ts +87 -31
  7. package/src/__tests__/cli-e2e.test.ts +76 -10
  8. package/src/__tests__/skills-manager.test.ts +158 -0
  9. package/src/cli.ts +44 -2
  10. package/src/default-tools.json +1 -1
  11. package/src/skills/bundled-skills.ts +19 -16
  12. package/src/skills/skill-definitions/cli-only/uloop-get-version/SKILL.md +37 -0
  13. package/src/skills/skills-manager.ts +232 -9
  14. package/src/version.ts +1 -1
  15. package/CLAUDE.md +0 -61
  16. package/src/skills/skill-definitions/uloop-capture-unity-window/SKILL.md +0 -81
  17. package/src/skills/skill-definitions/uloop-clear-console/SKILL.md +0 -34
  18. package/src/skills/skill-definitions/uloop-compile/SKILL.md +0 -47
  19. package/src/skills/skill-definitions/uloop-control-play-mode/SKILL.md +0 -48
  20. package/src/skills/skill-definitions/uloop-execute-dynamic-code/SKILL.md +0 -79
  21. package/src/skills/skill-definitions/uloop-execute-menu-item/SKILL.md +0 -43
  22. package/src/skills/skill-definitions/uloop-find-game-objects/SKILL.md +0 -46
  23. package/src/skills/skill-definitions/uloop-focus-window/SKILL.md +0 -34
  24. package/src/skills/skill-definitions/uloop-get-hierarchy/SKILL.md +0 -44
  25. package/src/skills/skill-definitions/uloop-get-logs/SKILL.md +0 -45
  26. package/src/skills/skill-definitions/uloop-get-menu-items/SKILL.md +0 -44
  27. package/src/skills/skill-definitions/uloop-get-provider-details/SKILL.md +0 -45
  28. package/src/skills/skill-definitions/uloop-get-version/SKILL.md +0 -32
  29. package/src/skills/skill-definitions/uloop-run-tests/SKILL.md +0 -43
  30. package/src/skills/skill-definitions/uloop-unity-search/SKILL.md +0 -44
  31. /package/src/skills/skill-definitions/{uloop-get-project-info → cli-only/uloop-get-project-info}/SKILL.md +0 -0
package/jest.config.cjs CHANGED
@@ -2,10 +2,11 @@
2
2
  module.exports = {
3
3
  preset: 'ts-jest',
4
4
  testEnvironment: 'node',
5
- moduleFileExtensions: ['ts', 'js', 'json'],
5
+ moduleFileExtensions: ['ts', 'js', 'json', 'md'],
6
6
  testMatch: ['**/__tests__/**/*.test.ts'],
7
7
  transform: {
8
8
  '^.+\\.ts$': 'ts-jest',
9
+ '^.+\\.md$': '<rootDir>/md-transformer.cjs',
9
10
  },
10
11
  moduleNameMapper: {
11
12
  '^(\\.{1,2}/.*)\\.js$': '$1',
@@ -0,0 +1,11 @@
1
+ /**
2
+ * Jest transformer for .md files
3
+ * Converts markdown files to string exports (similar to esbuild's --loader:.md=text)
4
+ */
5
+ module.exports = {
6
+ process(sourceText) {
7
+ return {
8
+ code: `module.exports = ${JSON.stringify(sourceText)};`,
9
+ };
10
+ },
11
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uloop-cli",
3
- "version": "0.48.3",
3
+ "version": "0.49.0",
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",
@@ -1,25 +1,31 @@
1
1
  /**
2
- * Auto-generates bundled-skills.ts from skill-definitions directory.
3
- * Scans all SKILL.md files and generates import statements and BUNDLED_SKILLS array.
2
+ * Auto-generates bundled-skills.ts from multiple source directories.
3
+ * Scans SKILL.md files from:
4
+ * 1. Editor/Api/McpTools/SKILL.md (C# tool folders)
5
+ * 2. skill-definitions/cli-only/SKILL.md (CLI-only skills)
6
+ *
4
7
  * Skills with `internal: true` in frontmatter are excluded from generation.
5
8
  *
6
9
  * Usage: npx tsx scripts/generate-bundled-skills.ts
7
10
  */
8
11
 
9
12
  import { readdirSync, readFileSync, writeFileSync, existsSync } from 'fs';
10
- import { join, dirname } from 'path';
13
+ import { join, dirname, relative } from 'path';
11
14
  import { fileURLToPath } from 'url';
12
15
 
13
16
  const __filename = fileURLToPath(import.meta.url);
14
17
  const __dirname = dirname(__filename);
15
18
 
16
- const SKILL_DEFINITIONS_DIR = join(__dirname, '../src/skills/skill-definitions');
19
+ const MCPTOOLS_DIR = join(__dirname, '../../Editor/Api/McpTools');
20
+ const CLI_ONLY_DIR = join(__dirname, '../src/skills/skill-definitions/cli-only');
17
21
  const OUTPUT_FILE = join(__dirname, '../src/skills/bundled-skills.ts');
22
+ const OUTPUT_DIR = dirname(OUTPUT_FILE);
18
23
 
19
24
  interface SkillMetadata {
20
25
  dirName: string;
21
26
  name: string;
22
27
  isInternal: boolean;
28
+ importPath: string;
23
29
  }
24
30
 
25
31
  function parseFrontmatter(content: string): Record<string, string | boolean> {
@@ -52,26 +58,79 @@ function parseFrontmatter(content: string): Record<string, string | boolean> {
52
58
  return frontmatter;
53
59
  }
54
60
 
55
- function getSkillMetadata(dirName: string): SkillMetadata | null {
56
- const skillPath = join(SKILL_DEFINITIONS_DIR, dirName, 'SKILL.md');
57
-
58
- if (!existsSync(skillPath)) {
61
+ function getSkillMetadataFromPath(
62
+ skillMdPath: string,
63
+ folderName: string
64
+ ): SkillMetadata | null {
65
+ if (!existsSync(skillMdPath)) {
59
66
  return null;
60
67
  }
61
68
 
62
- const content = readFileSync(skillPath, 'utf-8');
69
+ const content = readFileSync(skillMdPath, 'utf-8');
63
70
  const frontmatter = parseFrontmatter(content);
64
71
 
65
- const name = typeof frontmatter.name === 'string' ? frontmatter.name : dirName;
72
+ const name = typeof frontmatter.name === 'string' ? frontmatter.name : folderName;
66
73
  const isInternal = frontmatter.internal === true;
74
+ const dirName = name;
75
+
76
+ const relativePath = relative(OUTPUT_DIR, skillMdPath).replace(/\\/g, '/');
77
+ const importPath = relativePath.startsWith('.') ? relativePath : `./${relativePath}`;
78
+
79
+ return { dirName, name, isInternal, importPath };
80
+ }
81
+
82
+ function collectSkillsFromMcpTools(): SkillMetadata[] {
83
+ if (!existsSync(MCPTOOLS_DIR)) {
84
+ console.warn(`Warning: McpTools directory not found: ${MCPTOOLS_DIR}`);
85
+ return [];
86
+ }
87
+
88
+ const dirs = readdirSync(MCPTOOLS_DIR, { withFileTypes: true })
89
+ .filter((dirent) => dirent.isDirectory())
90
+ .map((dirent) => dirent.name)
91
+ .sort();
92
+
93
+ const skills: SkillMetadata[] = [];
94
+
95
+ for (const dirName of dirs) {
96
+ const skillPath = join(MCPTOOLS_DIR, dirName, 'SKILL.md');
97
+ const metadata = getSkillMetadataFromPath(skillPath, dirName);
98
+ if (metadata !== null) {
99
+ skills.push(metadata);
100
+ }
101
+ }
102
+
103
+ return skills;
104
+ }
105
+
106
+ function collectSkillsFromCliOnly(): SkillMetadata[] {
107
+ if (!existsSync(CLI_ONLY_DIR)) {
108
+ console.warn(`Warning: CLI-only directory not found: ${CLI_ONLY_DIR}`);
109
+ return [];
110
+ }
67
111
 
68
- return { dirName, name, isInternal };
112
+ const dirs = readdirSync(CLI_ONLY_DIR, { withFileTypes: true })
113
+ .filter((dirent) => dirent.isDirectory())
114
+ .map((dirent) => dirent.name)
115
+ .sort();
116
+
117
+ const skills: SkillMetadata[] = [];
118
+
119
+ for (const dirName of dirs) {
120
+ const skillPath = join(CLI_ONLY_DIR, dirName, 'SKILL.md');
121
+ const metadata = getSkillMetadataFromPath(skillPath, dirName);
122
+ if (metadata !== null) {
123
+ skills.push(metadata);
124
+ }
125
+ }
126
+
127
+ return skills;
69
128
  }
70
129
 
71
130
  function toVariableName(dirName: string): string {
72
131
  return dirName
73
132
  .replace(/^uloop-/, '')
74
- .replace(/-([a-z])/g, (_, char) => char.toUpperCase())
133
+ .replace(/-([a-z])/g, (_, char: string) => char.toUpperCase())
75
134
  .concat('Skill');
76
135
  }
77
136
 
@@ -79,7 +138,7 @@ function generateBundledSkillsFile(skills: SkillMetadata[]): string {
79
138
  const imports = skills
80
139
  .map((skill) => {
81
140
  const varName = toVariableName(skill.dirName);
82
- return `import ${varName} from './skill-definitions/${skill.dirName}/SKILL.md';`;
141
+ return `import ${varName} from '${skill.importPath}';`;
83
142
  })
84
143
  .join('\n');
85
144
 
@@ -98,8 +157,11 @@ function generateBundledSkillsFile(skills: SkillMetadata[]): string {
98
157
  * AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
99
158
  * Generated by: scripts/generate-bundled-skills.ts
100
159
  *
101
- * This file is automatically generated from skill-definitions directory.
102
- * To add a new skill, create a new directory in skill-definitions with a SKILL.md file.
160
+ * This file is automatically generated from:
161
+ * - Editor/Api/McpTools/<ToolFolder>/SKILL.md
162
+ * - skill-definitions/cli-only/<SkillFolder>/SKILL.md
163
+ *
164
+ * To add a new skill, create a SKILL.md file in the appropriate location.
103
165
  * To exclude a skill from bundling, add \`internal: true\` to its frontmatter.
104
166
  */
105
167
 
@@ -122,33 +184,28 @@ export function getBundledSkillByName(name: string): BundledSkill | undefined {
122
184
  }
123
185
 
124
186
  function main(): void {
125
- const dirs = readdirSync(SKILL_DEFINITIONS_DIR, { withFileTypes: true })
126
- .filter((dirent) => dirent.isDirectory())
127
- .map((dirent) => dirent.name)
128
- .sort();
187
+ const mcpToolsSkills = collectSkillsFromMcpTools();
188
+ const cliOnlySkills = collectSkillsFromCliOnly();
129
189
 
130
190
  const allSkills: SkillMetadata[] = [];
131
191
  const internalSkills: string[] = [];
132
192
 
133
- for (const dirName of dirs) {
134
- const metadata = getSkillMetadata(dirName);
135
- if (metadata === null) {
136
- console.warn(`Warning: No SKILL.md found in ${dirName}, skipping`);
137
- continue;
138
- }
139
-
140
- if (metadata.isInternal) {
141
- internalSkills.push(metadata.name);
193
+ for (const skill of [...mcpToolsSkills, ...cliOnlySkills]) {
194
+ if (skill.isInternal) {
195
+ internalSkills.push(skill.name);
142
196
  continue;
143
197
  }
144
-
145
- allSkills.push(metadata);
198
+ allSkills.push(skill);
146
199
  }
147
200
 
201
+ allSkills.sort((a, b) => a.name.localeCompare(b.name));
202
+
148
203
  const output = generateBundledSkillsFile(allSkills);
149
204
  writeFileSync(OUTPUT_FILE, output, 'utf-8');
150
205
 
151
206
  console.log(`Generated ${OUTPUT_FILE}`);
207
+ console.log(` - From McpTools: ${mcpToolsSkills.length} skills found`);
208
+ console.log(` - From cli-only: ${cliOnlySkills.length} skills found`);
152
209
  console.log(` - Included: ${allSkills.length} skills`);
153
210
  if (internalSkills.length > 0) {
154
211
  console.log(` - Excluded (internal): ${internalSkills.join(', ')}`);
@@ -156,4 +213,3 @@ function main(): void {
156
213
  }
157
214
 
158
215
  main();
159
-
@@ -92,22 +92,19 @@ describe('CLI E2E Tests (requires running Unity)', () => {
92
92
  expect(result.Success).toBe(true);
93
93
  expect(result.ErrorCount).toBe(0);
94
94
  });
95
-
96
- it('should support --force-recompile option', () => {
97
- const { exitCode } = runCli('compile --force-recompile');
98
-
99
- // Domain Reload causes connection to be lost, so we just verify the command runs
100
- // The exit code may be non-zero due to connection being dropped during reload
101
- expect(typeof exitCode).toBe('number');
102
- });
103
95
  });
104
96
 
105
97
  describe('get-logs', () => {
106
98
  const TEST_LOG_MENU_PATH = 'uLoopMCP/Debug/LogGetter Tests/Output Test Logs';
99
+ const MENU_ITEM_WAIT_MS = 1000;
107
100
 
108
101
  function setupTestLogs(): void {
109
- runCli('clear-console');
110
- runCli(`execute-menu-item --menu-item-path "${TEST_LOG_MENU_PATH}"`);
102
+ runCliWithRetry('clear-console');
103
+ const result = runCliWithRetry(`execute-menu-item --menu-item-path "${TEST_LOG_MENU_PATH}"`);
104
+ if (result.exitCode !== 0) {
105
+ throw new Error(`execute-menu-item failed: ${result.stderr || result.stdout}`);
106
+ }
107
+ sleepSync(MENU_ITEM_WAIT_MS);
111
108
  }
112
109
 
113
110
  it('should retrieve test logs after executing Output Test Logs menu item', () => {
@@ -339,6 +336,64 @@ describe('CLI E2E Tests (requires running Unity)', () => {
339
336
  });
340
337
  });
341
338
 
339
+ describe('skills', () => {
340
+ it('should list skills for claude target', () => {
341
+ const { stdout, exitCode } = runCli('skills list --claude');
342
+
343
+ expect(exitCode).toBe(0);
344
+ expect(stdout).toContain('uloop-compile');
345
+ expect(stdout).toContain('uloop-get-logs');
346
+ expect(stdout).toContain('uloop-run-tests');
347
+ });
348
+
349
+ it('should show bundled and project skills count', () => {
350
+ const { stdout, exitCode } = runCli('skills list --claude');
351
+
352
+ expect(exitCode).toBe(0);
353
+ // Should show total skills count
354
+ expect(stdout).toMatch(/total:\s*\d+/i);
355
+ });
356
+
357
+ it('should install skills for claude target', () => {
358
+ // First uninstall to ensure clean state
359
+ runCli('skills uninstall --claude');
360
+
361
+ const { stdout, exitCode } = runCli('skills install --claude');
362
+
363
+ expect(exitCode).toBe(0);
364
+ expect(stdout).toMatch(/installed|updated|skipped/i);
365
+ });
366
+
367
+ it('should uninstall skills for claude target', () => {
368
+ // First install to ensure there are skills to uninstall
369
+ runCli('skills install --claude');
370
+
371
+ const { stdout, exitCode } = runCli('skills uninstall --claude');
372
+
373
+ expect(exitCode).toBe(0);
374
+ expect(stdout).toMatch(/removed|not found/i);
375
+ });
376
+
377
+ it('should include project skills in list when available', () => {
378
+ const { stdout, exitCode } = runCli('skills list --claude');
379
+
380
+ expect(exitCode).toBe(0);
381
+ // HelloWorld sample should be detected as a project skill
382
+ expect(stdout).toContain('uloop-hello-world');
383
+ });
384
+
385
+ it('should install project skills along with bundled skills', () => {
386
+ // First uninstall
387
+ runCli('skills uninstall --claude');
388
+
389
+ const { stdout, exitCode } = runCli('skills install --claude');
390
+
391
+ expect(exitCode).toBe(0);
392
+ // Should mention project skills were installed
393
+ expect(stdout).toMatch(/project|installed/i);
394
+ });
395
+ });
396
+
342
397
  describe('error handling', () => {
343
398
  it('should handle unknown commands gracefully', () => {
344
399
  const { exitCode } = runCli('unknown-command');
@@ -346,4 +401,15 @@ describe('CLI E2E Tests (requires running Unity)', () => {
346
401
  expect(exitCode).not.toBe(0);
347
402
  });
348
403
  });
404
+
405
+ // Domain Reload tests must run last to avoid affecting other tests
406
+ describe('compile --force-recompile (Domain Reload)', () => {
407
+ it('should support --force-recompile option', () => {
408
+ const { exitCode } = runCli('compile --force-recompile');
409
+
410
+ // Domain Reload causes connection to be lost, so we just verify the command runs
411
+ // The exit code may be non-zero due to connection being dropped during reload
412
+ expect(typeof exitCode).toBe('number');
413
+ });
414
+ });
349
415
  });
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Unit tests for skills-manager.ts
3
+ *
4
+ * Tests pure functions that don't require Unity connection.
5
+ */
6
+
7
+ import { parseFrontmatter } from '../skills/skills-manager.js';
8
+
9
+ describe('parseFrontmatter', () => {
10
+ it('should parse basic frontmatter with string values', () => {
11
+ const content = `---
12
+ name: uloop-test-skill
13
+ description: A test skill for testing
14
+ ---
15
+
16
+ # Test Skill
17
+
18
+ Some content here.
19
+ `;
20
+
21
+ const result = parseFrontmatter(content);
22
+
23
+ expect(result.name).toBe('uloop-test-skill');
24
+ expect(result.description).toBe('A test skill for testing');
25
+ });
26
+
27
+ it('should parse boolean true value', () => {
28
+ const content = `---
29
+ name: internal-skill
30
+ internal: true
31
+ ---
32
+
33
+ # Internal Skill
34
+ `;
35
+
36
+ const result = parseFrontmatter(content);
37
+
38
+ expect(result.name).toBe('internal-skill');
39
+ expect(result.internal).toBe(true);
40
+ });
41
+
42
+ it('should parse boolean false value', () => {
43
+ const content = `---
44
+ name: public-skill
45
+ internal: false
46
+ ---
47
+
48
+ # Public Skill
49
+ `;
50
+
51
+ const result = parseFrontmatter(content);
52
+
53
+ expect(result.name).toBe('public-skill');
54
+ expect(result.internal).toBe(false);
55
+ });
56
+
57
+ it('should return empty object for content without frontmatter', () => {
58
+ const content = `# No Frontmatter
59
+
60
+ Just some markdown content.
61
+ `;
62
+
63
+ const result = parseFrontmatter(content);
64
+
65
+ expect(result).toEqual({});
66
+ });
67
+
68
+ it('should return empty object for empty content', () => {
69
+ const result = parseFrontmatter('');
70
+
71
+ expect(result).toEqual({});
72
+ });
73
+
74
+ it('should handle frontmatter with colons in value', () => {
75
+ const content = `---
76
+ name: uloop-test
77
+ description: Use when: (1) first case, (2) second case
78
+ ---
79
+
80
+ # Test
81
+ `;
82
+
83
+ const result = parseFrontmatter(content);
84
+
85
+ expect(result.name).toBe('uloop-test');
86
+ expect(result.description).toBe('Use when: (1) first case, (2) second case');
87
+ });
88
+
89
+ it('should handle frontmatter with empty values', () => {
90
+ const content = `---
91
+ name: uloop-test
92
+ description:
93
+ ---
94
+
95
+ # Test
96
+ `;
97
+
98
+ const result = parseFrontmatter(content);
99
+
100
+ expect(result.name).toBe('uloop-test');
101
+ expect(result.description).toBe('');
102
+ });
103
+
104
+ it('should skip lines without colons', () => {
105
+ const content = `---
106
+ name: uloop-test
107
+ this line has no colon
108
+ description: valid description
109
+ ---
110
+
111
+ # Test
112
+ `;
113
+
114
+ const result = parseFrontmatter(content);
115
+
116
+ expect(result.name).toBe('uloop-test');
117
+ expect(result.description).toBe('valid description');
118
+ expect(Object.keys(result)).toHaveLength(2);
119
+ });
120
+
121
+ it('should trim whitespace from keys and values', () => {
122
+ const content = `---
123
+ name : uloop-test
124
+ description : spaced description
125
+ ---
126
+
127
+ # Test
128
+ `;
129
+
130
+ const result = parseFrontmatter(content);
131
+
132
+ expect(result.name).toBe('uloop-test');
133
+ expect(result.description).toBe('spaced description');
134
+ });
135
+
136
+ it('should handle incomplete frontmatter (missing closing)', () => {
137
+ const content = `---
138
+ name: uloop-test
139
+ description: incomplete
140
+
141
+ # No closing delimiter
142
+ `;
143
+
144
+ const result = parseFrontmatter(content);
145
+
146
+ expect(result).toEqual({});
147
+ });
148
+
149
+ it('should handle frontmatter-only content (no body)', () => {
150
+ const content = `---
151
+ name: minimal-skill
152
+ ---`;
153
+
154
+ const result = parseFrontmatter(content);
155
+
156
+ expect(result.name).toBe('minimal-skill');
157
+ });
158
+ });
package/src/cli.ts CHANGED
@@ -595,7 +595,49 @@ function listOptionsForCommand(cmdName: string): void {
595
595
  console.log(options.join('\n'));
596
596
  }
597
597
 
598
- // Handle completion options first (before commander parsing)
599
- if (!handleCompletionOptions()) {
598
+ /**
599
+ * Check if a command exists in the current program.
600
+ */
601
+ function commandExists(cmdName: string): boolean {
602
+ if (BUILTIN_COMMANDS.includes(cmdName as (typeof BUILTIN_COMMANDS)[number])) {
603
+ return true;
604
+ }
605
+ const tools = loadToolsCache();
606
+ return tools.tools.some((t) => t.name === cmdName);
607
+ }
608
+
609
+ /**
610
+ * Main entry point with auto-sync for unknown commands.
611
+ */
612
+ async function main(): Promise<void> {
613
+ if (handleCompletionOptions()) {
614
+ return;
615
+ }
616
+
617
+ const args = process.argv.slice(2);
618
+ const cmdName = args.find((arg) => !arg.startsWith('-'));
619
+
620
+ if (cmdName && !commandExists(cmdName)) {
621
+ console.log(`\x1b[33mUnknown command '${cmdName}'. Syncing tools from Unity...\x1b[0m`);
622
+ try {
623
+ await syncTools({});
624
+ const newCache = loadToolsCache();
625
+ const tool = newCache.tools.find((t) => t.name === cmdName);
626
+ if (tool) {
627
+ registerToolCommand(tool);
628
+ console.log(`\x1b[32m✓ Found '${cmdName}' after sync.\x1b[0m\n`);
629
+ } else {
630
+ console.error(`\x1b[31mError: Command '${cmdName}' not found even after sync.\x1b[0m`);
631
+ process.exit(1);
632
+ }
633
+ } catch (error) {
634
+ const message = error instanceof Error ? error.message : String(error);
635
+ console.error(`\x1b[31mError: Failed to sync tools: ${message}\x1b[0m`);
636
+ process.exit(1);
637
+ }
638
+ }
639
+
600
640
  program.parse();
601
641
  }
642
+
643
+ void main();
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.48.3",
2
+ "version": "0.49.0",
3
3
  "tools": [
4
4
  {
5
5
  "name": "compile",
@@ -2,25 +2,28 @@
2
2
  * AUTO-GENERATED FILE - DO NOT EDIT MANUALLY
3
3
  * Generated by: scripts/generate-bundled-skills.ts
4
4
  *
5
- * This file is automatically generated from skill-definitions directory.
6
- * To add a new skill, create a new directory in skill-definitions with a SKILL.md file.
5
+ * This file is automatically generated from:
6
+ * - Editor/Api/McpTools/<ToolFolder>/SKILL.md
7
+ * - skill-definitions/cli-only/<SkillFolder>/SKILL.md
8
+ *
9
+ * To add a new skill, create a SKILL.md file in the appropriate location.
7
10
  * To exclude a skill from bundling, add `internal: true` to its frontmatter.
8
11
  */
9
12
 
10
- import captureUnityWindowSkill from './skill-definitions/uloop-capture-unity-window/SKILL.md';
11
- import clearConsoleSkill from './skill-definitions/uloop-clear-console/SKILL.md';
12
- import compileSkill from './skill-definitions/uloop-compile/SKILL.md';
13
- import controlPlayModeSkill from './skill-definitions/uloop-control-play-mode/SKILL.md';
14
- import executeDynamicCodeSkill from './skill-definitions/uloop-execute-dynamic-code/SKILL.md';
15
- import executeMenuItemSkill from './skill-definitions/uloop-execute-menu-item/SKILL.md';
16
- import findGameObjectsSkill from './skill-definitions/uloop-find-game-objects/SKILL.md';
17
- import focusWindowSkill from './skill-definitions/uloop-focus-window/SKILL.md';
18
- import getHierarchySkill from './skill-definitions/uloop-get-hierarchy/SKILL.md';
19
- import getLogsSkill from './skill-definitions/uloop-get-logs/SKILL.md';
20
- import getMenuItemsSkill from './skill-definitions/uloop-get-menu-items/SKILL.md';
21
- import getProviderDetailsSkill from './skill-definitions/uloop-get-provider-details/SKILL.md';
22
- import runTestsSkill from './skill-definitions/uloop-run-tests/SKILL.md';
23
- import unitySearchSkill from './skill-definitions/uloop-unity-search/SKILL.md';
13
+ import captureUnityWindowSkill from '../../../Editor/Api/McpTools/CaptureUnityWindow/SKILL.md';
14
+ import clearConsoleSkill from '../../../Editor/Api/McpTools/ClearConsole/SKILL.md';
15
+ import compileSkill from '../../../Editor/Api/McpTools/Compile/SKILL.md';
16
+ import controlPlayModeSkill from '../../../Editor/Api/McpTools/ControlPlayMode/SKILL.md';
17
+ import executeDynamicCodeSkill from '../../../Editor/Api/McpTools/ExecuteDynamicCode/SKILL.md';
18
+ import executeMenuItemSkill from '../../../Editor/Api/McpTools/ExecuteMenuItem/SKILL.md';
19
+ import findGameObjectsSkill from '../../../Editor/Api/McpTools/FindGameObjects/SKILL.md';
20
+ import focusWindowSkill from '../../../Editor/Api/McpTools/FocusUnityWindow/SKILL.md';
21
+ import getHierarchySkill from '../../../Editor/Api/McpTools/GetHierarchy/SKILL.md';
22
+ import getLogsSkill from '../../../Editor/Api/McpTools/GetLogs/SKILL.md';
23
+ import getMenuItemsSkill from '../../../Editor/Api/McpTools/GetMenuItems/SKILL.md';
24
+ import getProviderDetailsSkill from '../../../Editor/Api/McpTools/UnitySearchProviderDetails/SKILL.md';
25
+ import runTestsSkill from '../../../Editor/Api/McpTools/RunTests/SKILL.md';
26
+ import unitySearchSkill from '../../../Editor/Api/McpTools/UnitySearch/SKILL.md';
24
27
 
25
28
  export interface BundledSkill {
26
29
  name: string;
@@ -0,0 +1,37 @@
1
+ ---
2
+ name: uloop-get-version
3
+ description: Get Unity and project information via uloop CLI. Use when you need to verify Unity version, check project settings (ProductName, CompanyName, Version), or troubleshoot environment issues.
4
+ internal: true
5
+ ---
6
+
7
+ # uloop get-version
8
+
9
+ Get Unity version and project information.
10
+
11
+ ## Usage
12
+
13
+ ```bash
14
+ uloop get-version
15
+ ```
16
+
17
+ ## Parameters
18
+
19
+ None.
20
+
21
+ ## Output
22
+
23
+ Returns JSON with:
24
+ - `UnityVersion`: Unity Editor version
25
+ - `Platform`: Current platform
26
+ - `DataPath`: Assets folder path
27
+ - `PersistentDataPath`: Persistent data path
28
+ - `TemporaryCachePath`: Temporary cache path
29
+ - `IsEditor`: Whether running in editor
30
+ - `ProductName`: Application product name
31
+ - `CompanyName`: Company name
32
+ - `Version`: Application version
33
+ - `Ver`: uLoopMCP package version
34
+
35
+ ## Notes
36
+
37
+ This is a sample custom tool demonstrating how to create MCP tools.