uloop-cli 0.68.2 → 0.69.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.
- package/README.md +87 -18
- package/README_ja.md +87 -18
- package/dist/cli.bundle.cjs +333 -137
- package/dist/cli.bundle.cjs.map +4 -4
- package/knip.json +4 -0
- package/package.json +4 -3
- package/src/__tests__/cli-e2e.test.ts +46 -4
- package/src/__tests__/execute-tool.test.ts +10 -0
- package/src/__tests__/port-resolver.test.ts +68 -0
- package/src/__tests__/project-validator.test.ts +107 -0
- package/src/arg-parser.ts +0 -118
- package/src/cli.ts +121 -14
- package/src/commands/focus-window.ts +54 -49
- package/src/compile-helpers.ts +5 -5
- package/src/default-tools.json +29 -1
- package/src/direct-unity-client.ts +2 -2
- package/src/execute-tool.ts +26 -0
- package/src/port-resolver.ts +39 -18
- package/src/project-root.ts +1 -1
- package/src/project-validator.ts +70 -0
- package/src/simple-framer.ts +2 -2
- package/src/skills/deprecated-skills.ts +1 -1
- package/src/skills/skills-manager.ts +9 -13
- package/src/skills/target-config.ts +1 -1
- package/src/spinner.ts +1 -1
- package/src/tool-cache.ts +18 -5
- package/src/version.ts +1 -1
package/knip.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "uloop-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.69.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",
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
"lint:fix": "eslint src --fix",
|
|
15
15
|
"format": "prettier --write src/**/*.ts",
|
|
16
16
|
"format:check": "prettier --check src/**/*.ts",
|
|
17
|
+
"knip": "knip",
|
|
17
18
|
"test:cli": "jest src/__tests__ --testTimeout=60000 --runInBand"
|
|
18
19
|
},
|
|
19
20
|
"keywords": [
|
|
@@ -48,7 +49,7 @@
|
|
|
48
49
|
"devDependencies": {
|
|
49
50
|
"@eslint/js": "10.0.1",
|
|
50
51
|
"@types/jest": "30.0.0",
|
|
51
|
-
"@types/node": "25.3.
|
|
52
|
+
"@types/node": "25.3.3",
|
|
52
53
|
"@types/semver": "7.7.1",
|
|
53
54
|
"esbuild": "0.27.3",
|
|
54
55
|
"eslint": "10.0.2",
|
|
@@ -56,9 +57,9 @@
|
|
|
56
57
|
"eslint-plugin-prettier": "5.5.5",
|
|
57
58
|
"eslint-plugin-security": "4.0.0",
|
|
58
59
|
"jest": "30.2.0",
|
|
60
|
+
"knip": "5.85.0",
|
|
59
61
|
"prettier": "3.8.1",
|
|
60
62
|
"ts-jest": "29.4.6",
|
|
61
|
-
"tsx": "4.21.0",
|
|
62
63
|
"typescript": "5.9.3",
|
|
63
64
|
"typescript-eslint": "8.56.1"
|
|
64
65
|
},
|
|
@@ -460,16 +460,16 @@ describe('CLI E2E Tests (requires running Unity)', () => {
|
|
|
460
460
|
});
|
|
461
461
|
});
|
|
462
462
|
|
|
463
|
-
describe('get-
|
|
463
|
+
describe('get-unity-search-providers', () => {
|
|
464
464
|
it('should retrieve search providers', () => {
|
|
465
|
-
const result = runCliJson<{ Providers: unknown[] }>('get-
|
|
465
|
+
const result = runCliJson<{ Providers: unknown[] }>('get-unity-search-providers');
|
|
466
466
|
|
|
467
467
|
expect(Array.isArray(result.Providers)).toBe(true);
|
|
468
468
|
});
|
|
469
469
|
|
|
470
470
|
it('should support --include-descriptions false to exclude descriptions', () => {
|
|
471
471
|
const result = runCliJson<{ Providers: unknown[] }>(
|
|
472
|
-
'get-
|
|
472
|
+
'get-unity-search-providers --include-descriptions false',
|
|
473
473
|
);
|
|
474
474
|
|
|
475
475
|
expect(Array.isArray(result.Providers)).toBe(true);
|
|
@@ -477,7 +477,7 @@ describe('CLI E2E Tests (requires running Unity)', () => {
|
|
|
477
477
|
|
|
478
478
|
it('should support --sort-by-priority false to disable priority sorting', () => {
|
|
479
479
|
const result = runCliJson<{ Providers: unknown[] }>(
|
|
480
|
-
'get-
|
|
480
|
+
'get-unity-search-providers --sort-by-priority false',
|
|
481
481
|
);
|
|
482
482
|
|
|
483
483
|
expect(Array.isArray(result.Providers)).toBe(true);
|
|
@@ -501,6 +501,48 @@ describe('CLI E2E Tests (requires running Unity)', () => {
|
|
|
501
501
|
expect(stdout).toContain('--force-recompile');
|
|
502
502
|
});
|
|
503
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
|
+
|
|
504
546
|
it('should display boolean options with value format in get-hierarchy help', () => {
|
|
505
547
|
const { stdout, exitCode } = runCli('get-hierarchy --help');
|
|
506
548
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { isTransportDisconnectError } from '../execute-tool.js';
|
|
2
|
+
import { UnityNotRunningError } from '../port-resolver.js';
|
|
3
|
+
import { ProjectMismatchError } from '../project-validator.js';
|
|
2
4
|
|
|
3
5
|
describe('isTransportDisconnectError', () => {
|
|
4
6
|
it('returns true for UNITY_NO_RESPONSE', () => {
|
|
@@ -28,4 +30,12 @@ describe('isTransportDisconnectError', () => {
|
|
|
28
30
|
expect(isTransportDisconnectError(null)).toBe(false);
|
|
29
31
|
expect(isTransportDisconnectError(undefined)).toBe(false);
|
|
30
32
|
});
|
|
33
|
+
|
|
34
|
+
it('returns false for UnityNotRunningError', () => {
|
|
35
|
+
expect(isTransportDisconnectError(new UnityNotRunningError('/project'))).toBe(false);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('returns false for ProjectMismatchError', () => {
|
|
39
|
+
expect(isTransportDisconnectError(new ProjectMismatchError('/a', '/b'))).toBe(false);
|
|
40
|
+
});
|
|
31
41
|
});
|
|
@@ -1,8 +1,11 @@
|
|
|
1
|
+
import { mkdtempSync, mkdirSync, writeFileSync, rmSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
1
3
|
import { tmpdir } from 'os';
|
|
2
4
|
import {
|
|
3
5
|
resolvePortFromUnitySettings,
|
|
4
6
|
validateProjectPath,
|
|
5
7
|
resolveUnityPort,
|
|
8
|
+
UnityNotRunningError,
|
|
6
9
|
} from '../port-resolver.js';
|
|
7
10
|
|
|
8
11
|
describe('resolvePortFromUnitySettings', () => {
|
|
@@ -75,3 +78,68 @@ describe('resolveUnityPort', () => {
|
|
|
75
78
|
expect(port).toBe(8711);
|
|
76
79
|
});
|
|
77
80
|
});
|
|
81
|
+
|
|
82
|
+
describe('resolveUnityPort with project settings', () => {
|
|
83
|
+
let tempProjectRoot: string;
|
|
84
|
+
|
|
85
|
+
beforeEach(() => {
|
|
86
|
+
tempProjectRoot = mkdtempSync(join(tmpdir(), 'unity-port-test-'));
|
|
87
|
+
mkdirSync(join(tempProjectRoot, 'Assets'));
|
|
88
|
+
mkdirSync(join(tempProjectRoot, 'ProjectSettings'));
|
|
89
|
+
mkdirSync(join(tempProjectRoot, 'UserSettings'));
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
afterEach(() => {
|
|
93
|
+
rmSync(tempProjectRoot, { recursive: true });
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
it('throws UnityNotRunningError when isServerRunning is false', async () => {
|
|
97
|
+
writeFileSync(
|
|
98
|
+
join(tempProjectRoot, 'UserSettings/UnityMcpSettings.json'),
|
|
99
|
+
JSON.stringify({ isServerRunning: false, customPort: 8700 }),
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
await expect(resolveUnityPort(undefined, tempProjectRoot)).rejects.toThrow(
|
|
103
|
+
UnityNotRunningError,
|
|
104
|
+
);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
it('returns port when isServerRunning is true', async () => {
|
|
108
|
+
writeFileSync(
|
|
109
|
+
join(tempProjectRoot, 'UserSettings/UnityMcpSettings.json'),
|
|
110
|
+
JSON.stringify({ isServerRunning: true, customPort: 8711 }),
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const port = await resolveUnityPort(undefined, tempProjectRoot);
|
|
114
|
+
expect(port).toBe(8711);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('returns port when isServerRunning is undefined (old settings format)', async () => {
|
|
118
|
+
writeFileSync(
|
|
119
|
+
join(tempProjectRoot, 'UserSettings/UnityMcpSettings.json'),
|
|
120
|
+
JSON.stringify({ customPort: 8711 }),
|
|
121
|
+
);
|
|
122
|
+
|
|
123
|
+
const port = await resolveUnityPort(undefined, tempProjectRoot);
|
|
124
|
+
expect(port).toBe(8711);
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it('throws when settings file has no valid port', async () => {
|
|
128
|
+
writeFileSync(
|
|
129
|
+
join(tempProjectRoot, 'UserSettings/UnityMcpSettings.json'),
|
|
130
|
+
JSON.stringify({ isServerRunning: true }),
|
|
131
|
+
);
|
|
132
|
+
|
|
133
|
+
await expect(resolveUnityPort(undefined, tempProjectRoot)).rejects.toThrow(
|
|
134
|
+
'Could not read Unity server port from settings',
|
|
135
|
+
);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('throws when settings file contains invalid JSON', async () => {
|
|
139
|
+
writeFileSync(join(tempProjectRoot, 'UserSettings/UnityMcpSettings.json'), 'not valid json{{{');
|
|
140
|
+
|
|
141
|
+
await expect(resolveUnityPort(undefined, tempProjectRoot)).rejects.toThrow(
|
|
142
|
+
'Could not read Unity server port from settings',
|
|
143
|
+
);
|
|
144
|
+
});
|
|
145
|
+
});
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { mkdtempSync, mkdirSync, rmSync } from 'fs';
|
|
2
|
+
import { join } from 'path';
|
|
3
|
+
import { tmpdir } from 'os';
|
|
4
|
+
import { ProjectMismatchError, validateConnectedProject } from '../project-validator.js';
|
|
5
|
+
import { isTransportDisconnectError } from '../execute-tool.js';
|
|
6
|
+
import type { DirectUnityClient } from '../direct-unity-client.js';
|
|
7
|
+
|
|
8
|
+
function createMockClient(response?: unknown, error?: Error): DirectUnityClient {
|
|
9
|
+
return {
|
|
10
|
+
isConnected: () => true,
|
|
11
|
+
sendRequest: jest.fn().mockImplementation(() => {
|
|
12
|
+
if (error) {
|
|
13
|
+
return Promise.reject(error);
|
|
14
|
+
}
|
|
15
|
+
return Promise.resolve(response);
|
|
16
|
+
}),
|
|
17
|
+
connect: jest.fn(),
|
|
18
|
+
disconnect: jest.fn(),
|
|
19
|
+
} as unknown as DirectUnityClient;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
describe('validateConnectedProject', () => {
|
|
23
|
+
let tempDirA: string;
|
|
24
|
+
let tempDirB: string;
|
|
25
|
+
|
|
26
|
+
beforeEach(() => {
|
|
27
|
+
tempDirA = mkdtempSync(join(tmpdir(), 'project-a-'));
|
|
28
|
+
tempDirB = mkdtempSync(join(tmpdir(), 'project-b-'));
|
|
29
|
+
mkdirSync(join(tempDirA, 'Assets'));
|
|
30
|
+
mkdirSync(join(tempDirB, 'Assets'));
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
afterEach(() => {
|
|
34
|
+
jest.restoreAllMocks();
|
|
35
|
+
rmSync(tempDirA, { recursive: true });
|
|
36
|
+
rmSync(tempDirB, { recursive: true });
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('throws ProjectMismatchError when connected project differs from expected', async () => {
|
|
40
|
+
const client = createMockClient({ DataPath: join(tempDirB, 'Assets') });
|
|
41
|
+
|
|
42
|
+
await expect(validateConnectedProject(client, tempDirA)).rejects.toThrow(ProjectMismatchError);
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
it('does not throw when connected project matches expected', async () => {
|
|
46
|
+
const client = createMockClient({ DataPath: join(tempDirA, 'Assets') });
|
|
47
|
+
|
|
48
|
+
await expect(validateConnectedProject(client, tempDirA)).resolves.toBeUndefined();
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('normalizes paths with trailing separators', async () => {
|
|
52
|
+
const client = createMockClient({ DataPath: join(tempDirA, 'Assets') });
|
|
53
|
+
|
|
54
|
+
await expect(validateConnectedProject(client, tempDirA + '/')).resolves.toBeUndefined();
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('logs warning and continues when get-version returns Method not found', async () => {
|
|
58
|
+
const client = createMockClient(undefined, new Error('Unity error: Method not found (-32601)'));
|
|
59
|
+
const stderrSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
60
|
+
|
|
61
|
+
await expect(validateConnectedProject(client, tempDirA)).resolves.toBeUndefined();
|
|
62
|
+
|
|
63
|
+
expect(stderrSpy).toHaveBeenCalledWith(
|
|
64
|
+
expect.stringContaining('Could not verify project identity'),
|
|
65
|
+
);
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('re-throws non-Method-not-found errors', async () => {
|
|
69
|
+
const client = createMockClient(undefined, new Error('Unity error: some other error'));
|
|
70
|
+
|
|
71
|
+
await expect(validateConnectedProject(client, tempDirA)).rejects.toThrow(
|
|
72
|
+
'Unity error: some other error',
|
|
73
|
+
);
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('logs warning and continues when DataPath is missing from response', async () => {
|
|
77
|
+
const client = createMockClient({});
|
|
78
|
+
const stderrSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
79
|
+
|
|
80
|
+
await expect(validateConnectedProject(client, tempDirA)).resolves.toBeUndefined();
|
|
81
|
+
|
|
82
|
+
expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('invalid get-version response'));
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it('logs warning and continues when DataPath is empty string', async () => {
|
|
86
|
+
const client = createMockClient({ DataPath: '' });
|
|
87
|
+
const stderrSpy = jest.spyOn(console, 'error').mockImplementation();
|
|
88
|
+
|
|
89
|
+
await expect(validateConnectedProject(client, tempDirA)).resolves.toBeUndefined();
|
|
90
|
+
|
|
91
|
+
expect(stderrSpy).toHaveBeenCalledWith(expect.stringContaining('invalid get-version response'));
|
|
92
|
+
});
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
describe('ProjectMismatchError', () => {
|
|
96
|
+
it('is not a transport disconnect error', () => {
|
|
97
|
+
const error = new ProjectMismatchError('/project/a', '/project/b');
|
|
98
|
+
expect(isTransportDisconnectError(error)).toBe(false);
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('stores expected and connected project roots', () => {
|
|
102
|
+
const error = new ProjectMismatchError('/expected/path', '/connected/path');
|
|
103
|
+
expect(error.expectedProjectRoot).toBe('/expected/path');
|
|
104
|
+
expect(error.connectedProjectRoot).toBe('/connected/path');
|
|
105
|
+
expect(error.message).toBe('PROJECT_MISMATCH');
|
|
106
|
+
});
|
|
107
|
+
});
|
package/src/arg-parser.ts
CHANGED
|
@@ -3,31 +3,6 @@
|
|
|
3
3
|
* Converts CLI options to Unity tool parameters.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
// Object keys come from tool schema definitions which are internal trusted data
|
|
7
|
-
/* eslint-disable security/detect-object-injection */
|
|
8
|
-
|
|
9
|
-
export interface ToolParameter {
|
|
10
|
-
Type: string;
|
|
11
|
-
Description: string;
|
|
12
|
-
DefaultValue?: unknown;
|
|
13
|
-
Enum?: string[];
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export interface ToolSchema {
|
|
17
|
-
properties: Record<string, ToolParameter>;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Converts kebab-case CLI option name to PascalCase parameter name.
|
|
22
|
-
* e.g., "force-recompile" -> "ForceRecompile"
|
|
23
|
-
*/
|
|
24
|
-
export function kebabToPascalCase(kebab: string): string {
|
|
25
|
-
return kebab
|
|
26
|
-
.split('-')
|
|
27
|
-
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
|
28
|
-
.join('');
|
|
29
|
-
}
|
|
30
|
-
|
|
31
6
|
/**
|
|
32
7
|
* Converts PascalCase parameter name to kebab-case CLI option name.
|
|
33
8
|
* e.g., "ForceRecompile" -> "force-recompile"
|
|
@@ -36,96 +11,3 @@ export function pascalToKebabCase(pascal: string): string {
|
|
|
36
11
|
const kebab = pascal.replace(/([A-Z])/g, '-$1').toLowerCase();
|
|
37
12
|
return kebab.startsWith('-') ? kebab.slice(1) : kebab;
|
|
38
13
|
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Parses CLI arguments into tool parameters based on the tool schema.
|
|
42
|
-
*/
|
|
43
|
-
export function parseToolArgs(
|
|
44
|
-
args: string[],
|
|
45
|
-
schema: ToolSchema,
|
|
46
|
-
cliOptions: Record<string, unknown>,
|
|
47
|
-
): Record<string, unknown> {
|
|
48
|
-
const params: Record<string, unknown> = {};
|
|
49
|
-
|
|
50
|
-
for (const [paramName, paramInfo] of Object.entries(schema.properties)) {
|
|
51
|
-
const kebabName = pascalToKebabCase(paramName);
|
|
52
|
-
const cliValue = cliOptions[kebabName];
|
|
53
|
-
|
|
54
|
-
if (cliValue === undefined) {
|
|
55
|
-
if (paramInfo.DefaultValue !== undefined) {
|
|
56
|
-
params[paramName] = paramInfo.DefaultValue;
|
|
57
|
-
}
|
|
58
|
-
continue;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
params[paramName] = convertValue(cliValue, paramInfo.Type);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
return params;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
function convertValue(value: unknown, type: string): unknown {
|
|
68
|
-
if (value === undefined || value === null) {
|
|
69
|
-
return value;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const lowerType = type.toLowerCase();
|
|
73
|
-
|
|
74
|
-
switch (lowerType) {
|
|
75
|
-
case 'boolean':
|
|
76
|
-
if (typeof value === 'boolean') {
|
|
77
|
-
return value;
|
|
78
|
-
}
|
|
79
|
-
if (typeof value === 'string') {
|
|
80
|
-
return value.toLowerCase() === 'true';
|
|
81
|
-
}
|
|
82
|
-
return Boolean(value);
|
|
83
|
-
|
|
84
|
-
case 'number':
|
|
85
|
-
case 'integer':
|
|
86
|
-
if (typeof value === 'number') {
|
|
87
|
-
return value;
|
|
88
|
-
}
|
|
89
|
-
if (typeof value === 'string') {
|
|
90
|
-
const parsed = parseFloat(value);
|
|
91
|
-
return isNaN(parsed) ? 0 : parsed;
|
|
92
|
-
}
|
|
93
|
-
return Number(value);
|
|
94
|
-
|
|
95
|
-
case 'string':
|
|
96
|
-
if (typeof value === 'string') {
|
|
97
|
-
return value;
|
|
98
|
-
}
|
|
99
|
-
if (typeof value === 'number' || typeof value === 'boolean') {
|
|
100
|
-
return String(value);
|
|
101
|
-
}
|
|
102
|
-
return JSON.stringify(value);
|
|
103
|
-
|
|
104
|
-
case 'array':
|
|
105
|
-
if (Array.isArray(value)) {
|
|
106
|
-
return value;
|
|
107
|
-
}
|
|
108
|
-
if (typeof value === 'string') {
|
|
109
|
-
return value.split(',').map((s) => s.trim());
|
|
110
|
-
}
|
|
111
|
-
return [value];
|
|
112
|
-
|
|
113
|
-
default:
|
|
114
|
-
return value;
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/**
|
|
119
|
-
* Generates commander.js option string from parameter info.
|
|
120
|
-
* e.g., "--force-recompile" for boolean, "--max-count <value>" for others
|
|
121
|
-
*/
|
|
122
|
-
export function generateOptionString(paramName: string, paramInfo: ToolParameter): string {
|
|
123
|
-
const kebabName = pascalToKebabCase(paramName);
|
|
124
|
-
const lowerType = paramInfo.Type.toLowerCase();
|
|
125
|
-
|
|
126
|
-
if (lowerType === 'boolean') {
|
|
127
|
-
return `--${kebabName}`;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
return `--${kebabName} <value>`;
|
|
131
|
-
}
|