unity-mcp-cli 0.56.0 → 0.57.1
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 +108 -40
- package/dist/commands/run-system-tool.d.ts +2 -0
- package/dist/commands/run-system-tool.js +132 -0
- package/dist/commands/run-system-tool.js.map +1 -0
- package/dist/commands/run-tool.js +36 -84
- package/dist/commands/run-tool.js.map +1 -1
- package/dist/commands/setup-mcp.d.ts +2 -0
- package/dist/commands/setup-mcp.js +119 -0
- package/dist/commands/setup-mcp.js.map +1 -0
- package/dist/commands/setup-skills.d.ts +2 -0
- package/dist/commands/setup-skills.js +113 -0
- package/dist/commands/setup-skills.js.map +1 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/agents.d.ts +22 -0
- package/dist/utils/agents.js +546 -0
- package/dist/utils/agents.js.map +1 -0
- package/dist/utils/connection.d.ts +34 -0
- package/dist/utils/connection.js +77 -0
- package/dist/utils/connection.js.map +1 -0
- package/dist/utils/input.d.ts +13 -0
- package/dist/utils/input.js +72 -0
- package/dist/utils/input.js.map +1 -0
- package/dist/utils/json-parse.d.ts +36 -0
- package/dist/utils/json-parse.js +103 -0
- package/dist/utils/json-parse.js.map +1 -0
- package/docs/README.es.md +206 -98
- package/docs/README.ja.md +205 -97
- package/docs/README.zh-CN.md +204 -96
- package/package.json +1 -1
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import { verbose } from './ui.js';
|
|
4
|
+
import { generatePortFromDirectory } from './port.js';
|
|
5
|
+
import { readConfig, resolveConnectionFromConfig } from './config.js';
|
|
6
|
+
import * as ui from './ui.js';
|
|
7
|
+
/**
|
|
8
|
+
* Returns true if the given directory looks like a Unity project (has an Assets subfolder).
|
|
9
|
+
*/
|
|
10
|
+
export function isUnityProject(dir) {
|
|
11
|
+
return fs.existsSync(path.join(dir, 'Assets'));
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Resolve the project path from positional arg, --path option, or cwd.
|
|
15
|
+
*/
|
|
16
|
+
export function resolveProjectPath(positionalPath, options) {
|
|
17
|
+
const resolved = path.resolve(positionalPath ?? options.path ?? process.cwd());
|
|
18
|
+
if ((positionalPath !== undefined || options.path !== undefined) && !fs.existsSync(resolved)) {
|
|
19
|
+
ui.error(`Project path does not exist: ${resolved}`);
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
return resolved;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve the project path and validate it is a Unity project.
|
|
26
|
+
* Skips validation when --url is provided (explicit server override).
|
|
27
|
+
*/
|
|
28
|
+
export function resolveAndValidateProjectPath(positionalPath, options) {
|
|
29
|
+
const resolved = resolveProjectPath(positionalPath, options);
|
|
30
|
+
// Skip Unity project validation when --url is explicitly provided
|
|
31
|
+
if (options.url) {
|
|
32
|
+
return resolved;
|
|
33
|
+
}
|
|
34
|
+
if (!isUnityProject(resolved)) {
|
|
35
|
+
ui.error(`Not a Unity project (missing Assets folder): ${resolved}`);
|
|
36
|
+
ui.info('Provide a Unity project path as an argument, or use --url to connect to a server directly.');
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
return resolved;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Resolve the server URL and auth token.
|
|
43
|
+
*
|
|
44
|
+
* URL priority:
|
|
45
|
+
* 1. --url flag (explicit override)
|
|
46
|
+
* 2. Config file connectionMode → Custom: host, Cloud: hardcoded cloud URL
|
|
47
|
+
* 3. Deterministic port from project path
|
|
48
|
+
*
|
|
49
|
+
* Token priority:
|
|
50
|
+
* 1. --token flag (explicit override)
|
|
51
|
+
* 2. Config file token
|
|
52
|
+
*/
|
|
53
|
+
export function resolveConnection(projectPath, options) {
|
|
54
|
+
const config = readConfig(projectPath);
|
|
55
|
+
const fromConfig = config ? resolveConnectionFromConfig(config) : { url: undefined, token: undefined };
|
|
56
|
+
verbose(`Config loaded: connectionMode=${config?.connectionMode ?? 'N/A'}, configUrl=${fromConfig.url ?? 'N/A'}, hasToken=${!!fromConfig.token}`);
|
|
57
|
+
let url;
|
|
58
|
+
if (options.url) {
|
|
59
|
+
url = options.url.replace(/\/$/, '');
|
|
60
|
+
verbose(`Using explicit --url: ${url}`);
|
|
61
|
+
}
|
|
62
|
+
else if (fromConfig.url) {
|
|
63
|
+
url = fromConfig.url.replace(/\/$/, '');
|
|
64
|
+
verbose(`Using URL from config (${config?.connectionMode} mode): ${url}`);
|
|
65
|
+
}
|
|
66
|
+
else {
|
|
67
|
+
const port = generatePortFromDirectory(projectPath);
|
|
68
|
+
url = `http://localhost:${port}`;
|
|
69
|
+
verbose(`Using deterministic port URL: ${url}`);
|
|
70
|
+
}
|
|
71
|
+
const token = options.token ?? fromConfig.token;
|
|
72
|
+
if (options.token) {
|
|
73
|
+
verbose('Using explicit --token');
|
|
74
|
+
}
|
|
75
|
+
return { url, token };
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=connection.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"connection.js","sourceRoot":"","sources":["../../src/utils/connection.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface InputOptions {
|
|
2
|
+
input?: string;
|
|
3
|
+
inputFile?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Parse tool input from --input, --input-file, or stdin.
|
|
7
|
+
*
|
|
8
|
+
* - `--input-file -` or `--input-file /dev/stdin` reads from stdin (cross-platform).
|
|
9
|
+
* - `--input-file <path>` reads from a file.
|
|
10
|
+
* - `--input <json>` parses inline JSON.
|
|
11
|
+
* - Falls back to `{}` if nothing provided.
|
|
12
|
+
*/
|
|
13
|
+
export declare function parseInput(options: InputOptions): string;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import * as fs from 'fs';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
import * as ui from './ui.js';
|
|
4
|
+
import { verbose } from './ui.js';
|
|
5
|
+
import { parseJsonStrict, JsonParseError } from './json-parse.js';
|
|
6
|
+
/**
|
|
7
|
+
* Parse tool input from --input, --input-file, or stdin.
|
|
8
|
+
*
|
|
9
|
+
* - `--input-file -` or `--input-file /dev/stdin` reads from stdin (cross-platform).
|
|
10
|
+
* - `--input-file <path>` reads from a file.
|
|
11
|
+
* - `--input <json>` parses inline JSON.
|
|
12
|
+
* - Falls back to `{}` if nothing provided.
|
|
13
|
+
*/
|
|
14
|
+
export function parseInput(options) {
|
|
15
|
+
if (options.inputFile) {
|
|
16
|
+
const isStdin = options.inputFile === '-' || options.inputFile === '/dev/stdin';
|
|
17
|
+
let content;
|
|
18
|
+
if (isStdin) {
|
|
19
|
+
verbose('Reading input from stdin...');
|
|
20
|
+
try {
|
|
21
|
+
content = fs.readFileSync(0, 'utf-8');
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
ui.error('Failed to read from stdin.');
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
if (!content.trim()) {
|
|
28
|
+
ui.error('No input received from stdin.');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
const filePath = path.resolve(options.inputFile);
|
|
34
|
+
if (!fs.existsSync(filePath)) {
|
|
35
|
+
ui.error(`Input file does not exist: ${filePath}`);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
content = fs.readFileSync(filePath, 'utf-8');
|
|
39
|
+
}
|
|
40
|
+
try {
|
|
41
|
+
parseJsonStrict(content);
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
const source = isStdin ? 'stdin' : options.inputFile;
|
|
45
|
+
if (err instanceof JsonParseError) {
|
|
46
|
+
ui.error(`--input-file content from '${source}' must be valid JSON\n${err.message}`);
|
|
47
|
+
}
|
|
48
|
+
else {
|
|
49
|
+
ui.error(`--input-file content from '${source}' must be valid JSON`);
|
|
50
|
+
}
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
return content;
|
|
54
|
+
}
|
|
55
|
+
if (options.input) {
|
|
56
|
+
try {
|
|
57
|
+
const result = parseJsonStrict(options.input);
|
|
58
|
+
return result.raw;
|
|
59
|
+
}
|
|
60
|
+
catch (err) {
|
|
61
|
+
if (err instanceof JsonParseError) {
|
|
62
|
+
ui.error(`--input must be valid JSON\n${err.message}`);
|
|
63
|
+
}
|
|
64
|
+
else {
|
|
65
|
+
ui.error('--input must be valid JSON');
|
|
66
|
+
}
|
|
67
|
+
process.exit(1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return '{}';
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=input.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"input.js","sourceRoot":"","sources":["../../src/utils/input.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,IAAI,CAAC;AACzB,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAC9B,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAOlE;;;;;;;GAOG;AACH,MAAM,UAAU,UAAU,CAAC,OAAqB;IAC9C,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACtB,MAAM,OAAO,GAAG,OAAO,CAAC,SAAS,KAAK,GAAG,IAAI,OAAO,CAAC,SAAS,KAAK,YAAY,CAAC;QAChF,IAAI,OAAe,CAAC;QAEpB,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,6BAA6B,CAAC,CAAC;YACvC,IAAI,CAAC;gBACH,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;YACxC,CAAC;YAAC,MAAM,CAAC;gBACP,EAAE,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;gBACvC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;YACD,IAAI,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC;gBACpB,EAAE,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;gBAC1C,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;aAAM,CAAC;YACN,MAAM,QAAQ,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YACjD,IAAI,CAAC,EAAE,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;gBAC7B,EAAE,CAAC,KAAK,CAAC,8BAA8B,QAAQ,EAAE,CAAC,CAAC;gBACnD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;YACD,OAAO,GAAG,EAAE,CAAC,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;QAC/C,CAAC;QAED,IAAI,CAAC;YACH,eAAe,CAAC,OAAO,CAAC,CAAC;QAC3B,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC;YACrD,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;gBAClC,EAAE,CAAC,KAAK,CAAC,8BAA8B,MAAM,yBAAyB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACvF,CAAC;iBAAM,CAAC;gBACN,EAAE,CAAC,KAAK,CAAC,8BAA8B,MAAM,sBAAsB,CAAC,CAAC;YACvE,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAED,IAAI,OAAO,CAAC,KAAK,EAAE,CAAC;QAClB,IAAI,CAAC;YACH,MAAM,MAAM,GAAG,eAAe,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC;YAC9C,OAAO,MAAM,CAAC,GAAG,CAAC;QACpB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,IAAI,GAAG,YAAY,cAAc,EAAE,CAAC;gBAClC,EAAE,CAAC,KAAK,CAAC,+BAA+B,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;YACzD,CAAC;iBAAM,CAAC;gBACN,EAAE,CAAC,KAAK,CAAC,4BAA4B,CAAC,CAAC;YACzC,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Robust JSON parsing with auto-stringify fallback and detailed error reporting.
|
|
3
|
+
*
|
|
4
|
+
* 1. Try to parse the raw string as JSON.
|
|
5
|
+
* 2. If that fails, wrap it in quotes (stringify) and try again.
|
|
6
|
+
* 3. If still invalid, report the exact position where parsing fails.
|
|
7
|
+
*/
|
|
8
|
+
export interface JsonParseResult {
|
|
9
|
+
readonly value: unknown;
|
|
10
|
+
readonly raw: string;
|
|
11
|
+
readonly wasStringified: boolean;
|
|
12
|
+
}
|
|
13
|
+
export declare class JsonParseError extends Error {
|
|
14
|
+
readonly position: number;
|
|
15
|
+
readonly snippet: string;
|
|
16
|
+
constructor(message: string, position: number, snippet: string);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Parse a string as strict JSON. Throws {@link JsonParseError} with
|
|
20
|
+
* position and snippet on failure.
|
|
21
|
+
*
|
|
22
|
+
* @param input The raw string to parse as JSON.
|
|
23
|
+
* @returns A result containing the parsed value.
|
|
24
|
+
* @throws {@link JsonParseError} with position and snippet on failure.
|
|
25
|
+
*/
|
|
26
|
+
export declare function parseJsonStrict(input: string): JsonParseResult;
|
|
27
|
+
/**
|
|
28
|
+
* Parse a string as JSON with auto-stringify fallback.
|
|
29
|
+
* If the input is not valid JSON, it wraps the raw string via
|
|
30
|
+
* `JSON.stringify` so it becomes a valid JSON string literal.
|
|
31
|
+
* Check {@link JsonParseResult.wasStringified} to detect this case.
|
|
32
|
+
*
|
|
33
|
+
* @param input The raw string to parse as JSON.
|
|
34
|
+
* @returns A result containing the parsed value (never throws).
|
|
35
|
+
*/
|
|
36
|
+
export declare function parseJsonRobust(input: string): JsonParseResult;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
export class JsonParseError extends Error {
|
|
2
|
+
constructor(message, position, snippet) {
|
|
3
|
+
super(message);
|
|
4
|
+
this.name = 'JsonParseError';
|
|
5
|
+
this.position = position;
|
|
6
|
+
this.snippet = snippet;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Extract the error position from a JSON.parse SyntaxError message.
|
|
11
|
+
* Common formats:
|
|
12
|
+
* - "... at position 42" (V8 / Node)
|
|
13
|
+
* - "... at line 3 column 5" (some engines)
|
|
14
|
+
* - "Unexpected token X in JSON at position 42"
|
|
15
|
+
*/
|
|
16
|
+
function extractErrorPosition(error, input) {
|
|
17
|
+
const msg = error.message;
|
|
18
|
+
const posMatch = msg.match(/position\s+(\d+)/i);
|
|
19
|
+
if (posMatch) {
|
|
20
|
+
return parseInt(posMatch[1], 10);
|
|
21
|
+
}
|
|
22
|
+
const lineColMatch = msg.match(/line\s+(\d+)\s+column\s+(\d+)/i);
|
|
23
|
+
if (lineColMatch) {
|
|
24
|
+
const targetLine = parseInt(lineColMatch[1], 10);
|
|
25
|
+
const targetCol = parseInt(lineColMatch[2], 10);
|
|
26
|
+
const lines = input.split('\n');
|
|
27
|
+
let offset = 0;
|
|
28
|
+
for (let i = 0; i < targetLine - 1 && i < lines.length; i++) {
|
|
29
|
+
offset += lines[i].length + 1; // +1 for newline
|
|
30
|
+
}
|
|
31
|
+
return offset + targetCol - 1;
|
|
32
|
+
}
|
|
33
|
+
return -1;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* Build a human-readable snippet around the error position.
|
|
37
|
+
*/
|
|
38
|
+
function buildSnippet(input, position) {
|
|
39
|
+
if (position < 0 || position > input.length) {
|
|
40
|
+
return input.length <= 80 ? input : input.slice(0, 80) + '...';
|
|
41
|
+
}
|
|
42
|
+
const contextBefore = 20;
|
|
43
|
+
const contextAfter = 20;
|
|
44
|
+
const start = Math.max(0, position - contextBefore);
|
|
45
|
+
const end = Math.min(input.length, position + contextAfter);
|
|
46
|
+
const before = (start > 0 ? '...' : '') + input.slice(start, position);
|
|
47
|
+
const errorChar = position < input.length ? input[position] : '<end>';
|
|
48
|
+
const after = input.slice(position + 1, end) + (end < input.length ? '...' : '');
|
|
49
|
+
const pointer = ' '.repeat(before.length) + '^';
|
|
50
|
+
return `${before}${errorChar}${after}\n${pointer}`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Build a detailed error message for a JSON parse failure.
|
|
54
|
+
*/
|
|
55
|
+
function buildDetailedError(input, error) {
|
|
56
|
+
const position = extractErrorPosition(error, input);
|
|
57
|
+
const snippet = buildSnippet(input, position);
|
|
58
|
+
const posInfo = position >= 0 ? ` at position ${position}` : '';
|
|
59
|
+
const message = `Invalid JSON${posInfo}: ${error.message}\n\n` +
|
|
60
|
+
` ${snippet.split('\n').join('\n ')}\n`;
|
|
61
|
+
return new JsonParseError(message, position, snippet);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Parse a string as strict JSON. Throws {@link JsonParseError} with
|
|
65
|
+
* position and snippet on failure.
|
|
66
|
+
*
|
|
67
|
+
* @param input The raw string to parse as JSON.
|
|
68
|
+
* @returns A result containing the parsed value.
|
|
69
|
+
* @throws {@link JsonParseError} with position and snippet on failure.
|
|
70
|
+
*/
|
|
71
|
+
export function parseJsonStrict(input) {
|
|
72
|
+
try {
|
|
73
|
+
const value = JSON.parse(input);
|
|
74
|
+
return { value, raw: input, wasStringified: false };
|
|
75
|
+
}
|
|
76
|
+
catch (error) {
|
|
77
|
+
if (error instanceof SyntaxError) {
|
|
78
|
+
throw buildDetailedError(input, error);
|
|
79
|
+
}
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Parse a string as JSON with auto-stringify fallback.
|
|
85
|
+
* If the input is not valid JSON, it wraps the raw string via
|
|
86
|
+
* `JSON.stringify` so it becomes a valid JSON string literal.
|
|
87
|
+
* Check {@link JsonParseResult.wasStringified} to detect this case.
|
|
88
|
+
*
|
|
89
|
+
* @param input The raw string to parse as JSON.
|
|
90
|
+
* @returns A result containing the parsed value (never throws).
|
|
91
|
+
*/
|
|
92
|
+
export function parseJsonRobust(input) {
|
|
93
|
+
try {
|
|
94
|
+
const value = JSON.parse(input);
|
|
95
|
+
return { value, raw: input, wasStringified: false };
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
const stringified = JSON.stringify(input);
|
|
99
|
+
const value = JSON.parse(stringified);
|
|
100
|
+
return { value, raw: stringified, wasStringified: true };
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=json-parse.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"json-parse.js","sourceRoot":"","sources":["../../src/utils/json-parse.ts"],"names":[],"mappings":"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"}
|