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
@@ -1,11 +1,12 @@
1
1
  /**
2
2
  * Skills manager for installing/uninstalling/listing uloop skills.
3
+ * Supports both bundled skills and project-local skills.
3
4
  */
4
5
 
5
6
  // File paths are constructed from home directory and skill names, not from untrusted user input
6
7
  /* eslint-disable security/detect-non-literal-fs-filename */
7
8
 
8
- import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync } from 'fs';
9
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, rmSync, readdirSync } from 'fs';
9
10
  import { join } from 'path';
10
11
  import { homedir } from 'os';
11
12
  import { BUNDLED_SKILLS, BundledSkill } from './bundled-skills.js';
@@ -17,8 +18,18 @@ export interface SkillInfo {
17
18
  name: string;
18
19
  status: SkillStatus;
19
20
  path?: string;
21
+ source?: 'bundled' | 'project';
20
22
  }
21
23
 
24
+ export interface ProjectSkill {
25
+ name: string;
26
+ dirName: string;
27
+ content: string;
28
+ sourcePath: string;
29
+ }
30
+
31
+ const EXCLUDED_DIRS = new Set(['node_modules', '.git', 'Temp', 'obj', 'Build', 'Builds', 'Logs']);
32
+
22
33
  function getGlobalSkillsDir(target: TargetConfig): string {
23
34
  return join(homedir(), target.projectDir, 'skills');
24
35
  }
@@ -32,12 +43,20 @@ function getSkillPath(skillDirName: string, target: TargetConfig, global: boolea
32
43
  return join(baseDir, skillDirName, target.skillFileName);
33
44
  }
34
45
 
35
- function isSkillInstalled(skill: BundledSkill, target: TargetConfig, global: boolean): boolean {
46
+ function isSkillInstalled(
47
+ skill: BundledSkill | ProjectSkill,
48
+ target: TargetConfig,
49
+ global: boolean,
50
+ ): boolean {
36
51
  const skillPath = getSkillPath(skill.dirName, target, global);
37
52
  return existsSync(skillPath);
38
53
  }
39
54
 
40
- function isSkillOutdated(skill: BundledSkill, target: TargetConfig, global: boolean): boolean {
55
+ function isSkillOutdated(
56
+ skill: BundledSkill | ProjectSkill,
57
+ target: TargetConfig,
58
+ global: boolean,
59
+ ): boolean {
41
60
  const skillPath = getSkillPath(skill.dirName, target, global);
42
61
  if (!existsSync(skillPath)) {
43
62
  return false;
@@ -47,7 +66,7 @@ function isSkillOutdated(skill: BundledSkill, target: TargetConfig, global: bool
47
66
  }
48
67
 
49
68
  export function getSkillStatus(
50
- skill: BundledSkill,
69
+ skill: BundledSkill | ProjectSkill,
51
70
  target: TargetConfig,
52
71
  global: boolean,
53
72
  ): SkillStatus {
@@ -60,17 +79,170 @@ export function getSkillStatus(
60
79
  return 'installed';
61
80
  }
62
81
 
82
+ export function parseFrontmatter(content: string): Record<string, string | boolean> {
83
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
84
+ if (!frontmatterMatch) {
85
+ return {};
86
+ }
87
+
88
+ const frontmatter: Record<string, string | boolean> = {};
89
+ const lines = frontmatterMatch[1].split('\n');
90
+
91
+ for (const line of lines) {
92
+ const colonIndex = line.indexOf(':');
93
+ if (colonIndex === -1) {
94
+ continue;
95
+ }
96
+
97
+ const key = line.slice(0, colonIndex).trim();
98
+ const value = line.slice(colonIndex + 1).trim();
99
+
100
+ if (value === 'true') {
101
+ frontmatter[key] = true;
102
+ } else if (value === 'false') {
103
+ frontmatter[key] = false;
104
+ } else {
105
+ frontmatter[key] = value;
106
+ }
107
+ }
108
+
109
+ return frontmatter;
110
+ }
111
+
112
+ function scanEditorFolderForSkills(editorPath: string, skills: ProjectSkill[]): void {
113
+ if (!existsSync(editorPath)) {
114
+ return;
115
+ }
116
+
117
+ const entries = readdirSync(editorPath, { withFileTypes: true });
118
+
119
+ for (const entry of entries) {
120
+ if (EXCLUDED_DIRS.has(entry.name)) {
121
+ continue;
122
+ }
123
+
124
+ const fullPath = join(editorPath, entry.name);
125
+
126
+ if (entry.isDirectory()) {
127
+ const skillMdPath = join(fullPath, 'SKILL.md');
128
+ if (existsSync(skillMdPath)) {
129
+ const content = readFileSync(skillMdPath, 'utf-8');
130
+ const frontmatter = parseFrontmatter(content);
131
+
132
+ if (frontmatter.internal === true) {
133
+ continue;
134
+ }
135
+
136
+ const name = typeof frontmatter.name === 'string' ? frontmatter.name : entry.name;
137
+
138
+ skills.push({
139
+ name,
140
+ dirName: name,
141
+ content,
142
+ sourcePath: skillMdPath,
143
+ });
144
+ }
145
+
146
+ scanEditorFolderForSkills(fullPath, skills);
147
+ }
148
+ }
149
+ }
150
+
151
+ function findEditorFolders(basePath: string, maxDepth: number = 2): string[] {
152
+ const editorFolders: string[] = [];
153
+
154
+ function scan(currentPath: string, depth: number): void {
155
+ if (depth > maxDepth || !existsSync(currentPath)) {
156
+ return;
157
+ }
158
+
159
+ const entries = readdirSync(currentPath, { withFileTypes: true });
160
+
161
+ for (const entry of entries) {
162
+ if (!entry.isDirectory() || EXCLUDED_DIRS.has(entry.name)) {
163
+ continue;
164
+ }
165
+
166
+ const fullPath = join(currentPath, entry.name);
167
+
168
+ if (entry.name === 'Editor') {
169
+ editorFolders.push(fullPath);
170
+ } else {
171
+ scan(fullPath, depth + 1);
172
+ }
173
+ }
174
+ }
175
+
176
+ scan(basePath, 0);
177
+ return editorFolders;
178
+ }
179
+
180
+ export function collectProjectSkills(): ProjectSkill[] {
181
+ const projectRoot = process.cwd();
182
+ const skills: ProjectSkill[] = [];
183
+ const seenNames = new Set<string>();
184
+
185
+ const searchPaths = [
186
+ join(projectRoot, 'Assets'),
187
+ join(projectRoot, 'Packages'),
188
+ join(projectRoot, 'Library', 'PackageCache'),
189
+ ];
190
+
191
+ for (const searchPath of searchPaths) {
192
+ if (!existsSync(searchPath)) {
193
+ continue;
194
+ }
195
+
196
+ const editorFolders = findEditorFolders(searchPath, 3);
197
+
198
+ for (const editorFolder of editorFolders) {
199
+ scanEditorFolderForSkills(editorFolder, skills);
200
+ }
201
+ }
202
+
203
+ const uniqueSkills: ProjectSkill[] = [];
204
+ for (const skill of skills) {
205
+ if (!seenNames.has(skill.name)) {
206
+ seenNames.add(skill.name);
207
+ uniqueSkills.push(skill);
208
+ }
209
+ }
210
+
211
+ return uniqueSkills;
212
+ }
213
+
63
214
  export function getAllSkillStatuses(target: TargetConfig, global: boolean): SkillInfo[] {
64
- return BUNDLED_SKILLS.map((skill) => ({
215
+ const bundledStatuses: SkillInfo[] = BUNDLED_SKILLS.map((skill) => ({
65
216
  name: skill.name,
66
217
  status: getSkillStatus(skill, target, global),
67
218
  path: isSkillInstalled(skill, target, global)
68
219
  ? getSkillPath(skill.dirName, target, global)
69
220
  : undefined,
221
+ source: 'bundled' as const,
70
222
  }));
223
+
224
+ const projectSkills = collectProjectSkills();
225
+ const bundledNames = new Set(BUNDLED_SKILLS.map((s) => s.name));
226
+
227
+ const projectStatuses: SkillInfo[] = projectSkills
228
+ .filter((skill) => !bundledNames.has(skill.name))
229
+ .map((skill) => ({
230
+ name: skill.name,
231
+ status: getSkillStatus(skill, target, global),
232
+ path: isSkillInstalled(skill, target, global)
233
+ ? getSkillPath(skill.dirName, target, global)
234
+ : undefined,
235
+ source: 'project' as const,
236
+ }));
237
+
238
+ return [...bundledStatuses, ...projectStatuses];
71
239
  }
72
240
 
73
- export function installSkill(skill: BundledSkill, target: TargetConfig, global: boolean): void {
241
+ export function installSkill(
242
+ skill: BundledSkill | ProjectSkill,
243
+ target: TargetConfig,
244
+ global: boolean,
245
+ ): void {
74
246
  const baseDir = global ? getGlobalSkillsDir(target) : getProjectSkillsDir(target);
75
247
  const skillDir = join(baseDir, skill.dirName);
76
248
  const skillPath = join(skillDir, target.skillFileName);
@@ -80,7 +252,7 @@ export function installSkill(skill: BundledSkill, target: TargetConfig, global:
80
252
  }
81
253
 
82
254
  export function uninstallSkill(
83
- skill: BundledSkill,
255
+ skill: BundledSkill | ProjectSkill,
84
256
  target: TargetConfig,
85
257
  global: boolean,
86
258
  ): boolean {
@@ -99,10 +271,18 @@ export interface InstallResult {
99
271
  installed: number;
100
272
  updated: number;
101
273
  skipped: number;
274
+ bundledCount: number;
275
+ projectCount: number;
102
276
  }
103
277
 
104
278
  export function installAllSkills(target: TargetConfig, global: boolean): InstallResult {
105
- const result: InstallResult = { installed: 0, updated: 0, skipped: 0 };
279
+ const result: InstallResult = {
280
+ installed: 0,
281
+ updated: 0,
282
+ skipped: 0,
283
+ bundledCount: 0,
284
+ projectCount: 0,
285
+ };
106
286
 
107
287
  for (const skill of BUNDLED_SKILLS) {
108
288
  const status = getSkillStatus(skill, target, global);
@@ -117,6 +297,31 @@ export function installAllSkills(target: TargetConfig, global: boolean): Install
117
297
  result.skipped++;
118
298
  }
119
299
  }
300
+ result.bundledCount = BUNDLED_SKILLS.length;
301
+
302
+ const projectSkills = collectProjectSkills();
303
+ const bundledNames = new Set(BUNDLED_SKILLS.map((s) => s.name));
304
+
305
+ for (const skill of projectSkills) {
306
+ if (bundledNames.has(skill.name)) {
307
+ continue;
308
+ }
309
+
310
+ const status = getSkillStatus(skill, target, global);
311
+
312
+ if (status === 'not_installed') {
313
+ installSkill(skill, target, global);
314
+ result.installed++;
315
+ result.projectCount++;
316
+ } else if (status === 'outdated') {
317
+ installSkill(skill, target, global);
318
+ result.updated++;
319
+ result.projectCount++;
320
+ } else {
321
+ result.skipped++;
322
+ result.projectCount++;
323
+ }
324
+ }
120
325
 
121
326
  return result;
122
327
  }
@@ -137,6 +342,21 @@ export function uninstallAllSkills(target: TargetConfig, global: boolean): Unins
137
342
  }
138
343
  }
139
344
 
345
+ const projectSkills = collectProjectSkills();
346
+ const bundledNames = new Set(BUNDLED_SKILLS.map((s) => s.name));
347
+
348
+ for (const skill of projectSkills) {
349
+ if (bundledNames.has(skill.name)) {
350
+ continue;
351
+ }
352
+
353
+ if (uninstallSkill(skill, target, global)) {
354
+ result.removed++;
355
+ } else {
356
+ result.notFound++;
357
+ }
358
+ }
359
+
140
360
  return result;
141
361
  }
142
362
 
@@ -145,5 +365,8 @@ export function getInstallDir(target: TargetConfig, global: boolean): string {
145
365
  }
146
366
 
147
367
  export function getTotalSkillCount(): number {
148
- return BUNDLED_SKILLS.length;
368
+ const projectSkills = collectProjectSkills();
369
+ const bundledNames = new Set(BUNDLED_SKILLS.map((s) => s.name));
370
+ const uniqueProjectCount = projectSkills.filter((s) => !bundledNames.has(s.name)).length;
371
+ return BUNDLED_SKILLS.length + uniqueProjectCount;
149
372
  }
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.48.3'; // x-release-please-version
7
+ export const VERSION = '0.49.0'; // x-release-please-version
package/CLAUDE.md DELETED
@@ -1,61 +0,0 @@
1
- # uloop CLI
2
-
3
- A CLI tool for communicating with Unity Editor. Completely independent from the MCP server (TypeScriptServer~).
4
-
5
- ## Architecture
6
-
7
- - **Zero dependency on TypeScriptServer~**
8
- - Communicates with Unity directly via TCP connection in `direct-unity-client.ts`
9
- - Interacts directly with Unity TCP server without going through MCP server
10
-
11
- ## Directory Structure
12
-
13
- ```text
14
- src/
15
- ├── cli.ts # Entry point (commander.js)
16
- ├── version.ts # Version management (auto-updated by release-please)
17
- ├── execute-tool.ts # Tool execution logic
18
- ├── direct-unity-client.ts # Direct Unity TCP communication
19
- ├── simple-framer.ts # TCP framing
20
- ├── port-resolver.ts # Port detection
21
- ├── tool-cache.ts # Tool cache (.uloop/tools.json)
22
- ├── arg-parser.ts # Argument parsing
23
- ├── default-tools.json # Default tool definitions
24
- ├── skills/ # Claude Code skills feature
25
- │ ├── skills-command.ts
26
- │ ├── skills-manager.ts
27
- │ ├── bundled-skills.ts
28
- │ └── skill-definitions/ # 13 skill definitions (.md)
29
- └── __tests__/
30
- └── cli-e2e.test.ts # E2E tests
31
- ```
32
-
33
- ## Build
34
-
35
- ```bash
36
- npm run build # Generates dist/cli.bundle.cjs
37
- npm run lint # Run ESLint
38
- ```
39
-
40
- ## E2E Tests
41
-
42
- E2E tests communicate with actual Unity Editor, so the following prerequisites are required:
43
-
44
- 1. Unity Editor must be running
45
- 2. uLoopMCP package must be installed
46
- 3. CLI must be built (`npm run build`)
47
-
48
- ```bash
49
- npm run test:cli # Run E2E tests (Unity must be running)
50
- ```
51
-
52
- ## npm Publishing
53
-
54
- This directory is published to npm as the `uloop-cli` package.
55
- Version is synchronized with `Packages/src/package.json` (managed by release-please).
56
-
57
- ## Notes
58
-
59
- - `version.ts` is a separate file from TypeScriptServer~ (not a copy)
60
- - Build artifact `dist/cli.bundle.cjs` is excluded via `.gitignore`
61
- - `node_modules/` is also excluded via `.gitignore`
@@ -1,81 +0,0 @@
1
- ---
2
- name: uloop-capture-unity-window
3
- description: Capture Unity EditorWindow and save as PNG image. Use when you need to: (1) Take a screenshot of Game View, Scene View, Console, Inspector, etc., (2) Capture visual state for debugging or verification, (3) Save editor output as an image file.
4
- ---
5
-
6
- # uloop capture-unity-window
7
-
8
- Capture any Unity EditorWindow by name and save as PNG image.
9
-
10
- ## Usage
11
-
12
- ```bash
13
- uloop capture-unity-window [--window-name <name>] [--resolution-scale <scale>]
14
- ```
15
-
16
- ## Parameters
17
-
18
- | Parameter | Type | Default | Description |
19
- |-----------|------|---------|-------------|
20
- | `--window-name` | string | `Game` | Window name to capture (e.g., "Game", "Scene", "Console", "Inspector", "Project", "Hierarchy", or any EditorWindow title) |
21
- | `--resolution-scale` | number | `1.0` | Resolution scale (0.1 to 1.0) |
22
-
23
- ## Window Name
24
-
25
- The window name is the text displayed in the window's title bar (tab). The user (human) will tell you which window to capture. Common window names include:
26
-
27
- - **Game**: Game View window
28
- - **Scene**: Scene View window
29
- - **Console**: Console window
30
- - **Inspector**: Inspector window
31
- - **Project**: Project browser window
32
- - **Hierarchy**: Hierarchy window
33
- - **Animation**: Animation window
34
- - **Animator**: Animator window
35
- - **Profiler**: Profiler window
36
- - **Audio Mixer**: Audio Mixer window
37
-
38
- You can also specify custom EditorWindow titles (e.g., "EditorWindow Capture Test").
39
-
40
- ## Examples
41
-
42
- ```bash
43
- # Capture Game View at full resolution
44
- uloop capture-unity-window
45
-
46
- # Capture Game View at half resolution
47
- uloop capture-unity-window --window-name Game --resolution-scale 0.5
48
-
49
- # Capture Scene View
50
- uloop capture-unity-window --window-name Scene
51
-
52
- # Capture Console window
53
- uloop capture-unity-window --window-name Console
54
-
55
- # Capture Inspector window
56
- uloop capture-unity-window --window-name Inspector
57
-
58
- # Capture Project browser
59
- uloop capture-unity-window --window-name Project
60
-
61
- # Capture custom EditorWindow by title
62
- uloop capture-unity-window --window-name "My Custom Window"
63
- ```
64
-
65
- ## Output
66
-
67
- Returns JSON with:
68
- - `CapturedCount`: Number of windows captured
69
- - `CapturedWindows`: Array of captured window info, each containing:
70
- - `ImagePath`: Absolute path to the saved PNG image
71
- - `FileSizeBytes`: Size of the saved file in bytes
72
- - `Width`: Captured image width in pixels
73
- - `Height`: Captured image height in pixels
74
-
75
- When multiple windows of the same type are open (e.g., multiple Inspector windows), all matching windows are captured with numbered filenames (e.g., `Inspector_1_*.png`, `Inspector_2_*.png`).
76
-
77
- ## Notes
78
-
79
- - Use `uloop focus-window` first if needed
80
- - Target window must be open in Unity Editor
81
- - Window name matching is case-insensitive
@@ -1,34 +0,0 @@
1
- ---
2
- name: uloop-clear-console
3
- description: Clear Unity console logs via uloop CLI. Use when you need to: (1) Clear the console before running tests, (2) Start a fresh debugging session, (3) Clean up log output for better readability.
4
- ---
5
-
6
- # uloop clear-console
7
-
8
- Clear Unity console logs.
9
-
10
- ## Usage
11
-
12
- ```bash
13
- uloop clear-console [--add-confirmation-message]
14
- ```
15
-
16
- ## Parameters
17
-
18
- | Parameter | Type | Default | Description |
19
- |-----------|------|---------|-------------|
20
- | `--add-confirmation-message` | boolean | `false` | Add confirmation message after clearing |
21
-
22
- ## Examples
23
-
24
- ```bash
25
- # Clear console
26
- uloop clear-console
27
-
28
- # Clear with confirmation
29
- uloop clear-console --add-confirmation-message
30
- ```
31
-
32
- ## Output
33
-
34
- Returns JSON confirming the console was cleared.
@@ -1,47 +0,0 @@
1
- ---
2
- name: uloop-compile
3
- description: Compile Unity project via uloop CLI. Use when you need to: (1) Verify C# code compiles successfully after editing scripts, (2) Check for compile errors or warnings, (3) Validate script changes before running tests.
4
- ---
5
-
6
- # uloop compile
7
-
8
- Execute Unity project compilation.
9
-
10
- ## Usage
11
-
12
- ```bash
13
- uloop compile [--force-recompile]
14
- ```
15
-
16
- ## Parameters
17
-
18
- | Parameter | Type | Description |
19
- |-----------|------|-------------|
20
- | `--force-recompile` | boolean | Force full recompilation (triggers Domain Reload) |
21
-
22
- ## Examples
23
-
24
- ```bash
25
- # Check compilation
26
- uloop compile
27
-
28
- # Force full recompilation
29
- uloop compile --force-recompile
30
- ```
31
-
32
- ## Output
33
-
34
- Returns JSON:
35
- - `Success`: boolean
36
- - `ErrorCount`: number
37
- - `WarningCount`: number
38
-
39
- ## Troubleshooting
40
-
41
- If CLI hangs or shows "Unity is busy" errors after compilation, stale lock files may be preventing connection. Run the following to clean them up:
42
-
43
- ```bash
44
- uloop fix
45
- ```
46
-
47
- This removes any leftover lock files (`compiling.lock`, `domainreload.lock`, `serverstarting.lock`) from the Unity project's Temp directory.
@@ -1,48 +0,0 @@
1
- ---
2
- name: uloop-control-play-mode
3
- description: Control Unity Editor play mode via uloop CLI. Use when you need to: (1) Start play mode for testing, (2) Stop play mode after testing, (3) Pause play mode for debugging.
4
- ---
5
-
6
- # uloop control-play-mode
7
-
8
- Control Unity Editor play mode (play/stop/pause).
9
-
10
- ## Usage
11
-
12
- ```bash
13
- uloop control-play-mode [options]
14
- ```
15
-
16
- ## Parameters
17
-
18
- | Parameter | Type | Default | Description |
19
- |-----------|------|---------|-------------|
20
- | `--action` | string | `Play` | Action to perform: `Play`, `Stop`, `Pause` |
21
-
22
- ## Examples
23
-
24
- ```bash
25
- # Start play mode
26
- uloop control-play-mode --action Play
27
-
28
- # Stop play mode
29
- uloop control-play-mode --action Stop
30
-
31
- # Pause play mode
32
- uloop control-play-mode --action Pause
33
- ```
34
-
35
- ## Output
36
-
37
- Returns JSON with the current play mode state:
38
- - `IsPlaying`: Whether Unity is currently in play mode
39
- - `IsPaused`: Whether play mode is paused
40
- - `Message`: Description of the action performed
41
-
42
- ## Notes
43
-
44
- - Play action starts the game in the Unity Editor (also resumes from pause)
45
- - Stop action exits play mode and returns to edit mode
46
- - Pause action pauses the game while remaining in play mode
47
- - Useful for automated testing workflows
48
-
@@ -1,79 +0,0 @@
1
- ---
2
- name: uloop-execute-dynamic-code
3
- description: Execute C# code dynamically in Unity Editor via uloop CLI. Use for editor automation: (1) Prefab/material wiring and AddComponent operations, (2) Reference wiring with SerializedObject, (3) Scene/hierarchy edits and batch operations. NOT for file I/O or script authoring.
4
- ---
5
-
6
- # uloop execute-dynamic-code
7
-
8
- Execute C# code dynamically in Unity Editor.
9
-
10
- ## Usage
11
-
12
- ```bash
13
- uloop execute-dynamic-code --code '<c# code>'
14
- ```
15
-
16
- ## Parameters
17
-
18
- | Parameter | Type | Description |
19
- |-----------|------|-------------|
20
- | `--code` | string | C# code to execute (direct statements, no class wrapper) |
21
- | `--compile-only` | boolean | Compile without execution |
22
- | `--auto-qualify-unity-types-once` | boolean | Auto-qualify Unity types |
23
-
24
- ## Code Format
25
-
26
- Write direct statements only (no classes/namespaces/methods). Return is optional.
27
-
28
- ```csharp
29
- // Using directives at top are hoisted
30
- using UnityEngine;
31
- var x = Mathf.PI;
32
- return x;
33
- ```
34
-
35
- ## String Literals (Shell-specific)
36
-
37
- | Shell | Method |
38
- |-------|--------|
39
- | bash/zsh/MINGW64/Git Bash | `'Debug.Log("Hello!");'` |
40
- | PowerShell | `'Debug.Log(""Hello!"");'` |
41
-
42
- ## Allowed Operations
43
-
44
- - Prefab/material wiring (PrefabUtility)
45
- - AddComponent + reference wiring (SerializedObject)
46
- - Scene/hierarchy edits
47
- - Inspector modifications
48
-
49
- ## Forbidden Operations
50
-
51
- - System.IO.* (File/Directory/Path)
52
- - AssetDatabase.CreateFolder / file writes
53
- - Create/edit .cs/.asmdef files
54
-
55
- ## Examples
56
-
57
- ### bash / zsh / MINGW64 / Git Bash
58
-
59
- ```bash
60
- uloop execute-dynamic-code --code 'return Selection.activeGameObject?.name;'
61
- uloop execute-dynamic-code --code 'new GameObject("MyObject");'
62
- uloop execute-dynamic-code --code 'UnityEngine.Debug.Log("Hello from CLI!");'
63
- ```
64
-
65
- ### PowerShell
66
-
67
- ```powershell
68
- uloop execute-dynamic-code --code 'return Selection.activeGameObject?.name;'
69
- uloop execute-dynamic-code --code 'new GameObject(""MyObject"");'
70
- uloop execute-dynamic-code --code 'UnityEngine.Debug.Log(""Hello from CLI!"");'
71
- ```
72
-
73
- ## Output
74
-
75
- Returns JSON with execution result or compile errors.
76
-
77
- ## Notes
78
-
79
- For file/directory operations, use terminal commands instead.