@midscene/shared 1.7.5-beta-20260420035759.0 → 1.7.5-beta-20260420052829.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/dist/es/cli/cli-runner.mjs +72 -29
- package/dist/es/mcp/base-tools.mjs +7 -15
- package/dist/lib/cli/cli-runner.js +72 -29
- package/dist/lib/mcp/base-tools.js +7 -15
- package/dist/types/mcp/types.d.ts +0 -1
- package/package.json +1 -1
- package/src/cli/cli-runner.ts +136 -57
- package/src/mcp/base-tools.ts +8 -14
- package/src/mcp/types.ts +0 -1
|
@@ -2,6 +2,7 @@ import { existsSync, writeFileSync } from "node:fs";
|
|
|
2
2
|
import { tmpdir } from "node:os";
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import dotenv from "dotenv";
|
|
5
|
+
import { z } from "zod";
|
|
5
6
|
import { getKeyAliases, isRecord } from "../key-alias-utils.mjs";
|
|
6
7
|
import { getDebug } from "../logger.mjs";
|
|
7
8
|
function _define_property(obj, key, value) {
|
|
@@ -27,9 +28,29 @@ function parseValue(raw) {
|
|
|
27
28
|
if (/^-?\d+(\.\d+)?$/.test(raw)) return Number(raw);
|
|
28
29
|
return raw;
|
|
29
30
|
}
|
|
31
|
+
function walkCliArgs(args, setArgValue) {
|
|
32
|
+
for(let i = 0; i < args.length; i++){
|
|
33
|
+
const arg = args[i];
|
|
34
|
+
if (!arg.startsWith('--')) continue;
|
|
35
|
+
const body = arg.slice(2);
|
|
36
|
+
const eqIdx = body.indexOf('=');
|
|
37
|
+
if (eqIdx >= 0) setArgValue(body.slice(0, eqIdx), parseValue(body.slice(eqIdx + 1)));
|
|
38
|
+
else if (args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
39
|
+
i++;
|
|
40
|
+
setArgValue(body, parseValue(args[i]));
|
|
41
|
+
} else setArgValue(body, true);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function parseRawCliArgs(args) {
|
|
45
|
+
const result = {};
|
|
46
|
+
walkCliArgs(args, (key, value)=>{
|
|
47
|
+
result[key] = value;
|
|
48
|
+
});
|
|
49
|
+
return result;
|
|
50
|
+
}
|
|
30
51
|
function parseCliArgs(args) {
|
|
31
52
|
const result = {};
|
|
32
|
-
|
|
53
|
+
walkCliArgs(args, (key, value)=>{
|
|
33
54
|
if (!key.includes('.')) {
|
|
34
55
|
result[key] = value;
|
|
35
56
|
return;
|
|
@@ -48,18 +69,7 @@ function parseCliArgs(args) {
|
|
|
48
69
|
}
|
|
49
70
|
const leafSegment = segments[segments.length - 1];
|
|
50
71
|
for (const alias of getKeyAliases(leafSegment))current[alias] = value;
|
|
51
|
-
};
|
|
52
|
-
for(let i = 0; i < args.length; i++){
|
|
53
|
-
const arg = args[i];
|
|
54
|
-
if (!arg.startsWith('--')) continue;
|
|
55
|
-
const body = arg.slice(2);
|
|
56
|
-
const eqIdx = body.indexOf('=');
|
|
57
|
-
if (eqIdx >= 0) setArgValue(body.slice(0, eqIdx), parseValue(body.slice(eqIdx + 1)));
|
|
58
|
-
else if (args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
59
|
-
i++;
|
|
60
|
-
setArgValue(body, parseValue(args[i]));
|
|
61
|
-
} else setArgValue(body, true);
|
|
62
|
-
}
|
|
72
|
+
});
|
|
63
73
|
return result;
|
|
64
74
|
}
|
|
65
75
|
function outputContentItem(item, isError) {
|
|
@@ -100,28 +110,59 @@ function getCliOptionDisplay(key, cliOption) {
|
|
|
100
110
|
aliases
|
|
101
111
|
};
|
|
102
112
|
}
|
|
103
|
-
function
|
|
113
|
+
function getAcceptedCliOptionNames(key, cliOption) {
|
|
114
|
+
return [
|
|
115
|
+
...new Set(cliOption ? [
|
|
116
|
+
cliOption.preferredName ?? key,
|
|
117
|
+
...cliOption.aliases ?? []
|
|
118
|
+
] : [
|
|
119
|
+
key,
|
|
120
|
+
...getKeyAliases(key)
|
|
121
|
+
])
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
function toOptionalCliSchemaField(field) {
|
|
125
|
+
if ('object' == typeof field && null !== field && 'function' == typeof field.optional) return field.optional();
|
|
126
|
+
const description = 'object' == typeof field && null !== field && "description" in field && 'string' == typeof field.description ? field.description : void 0;
|
|
127
|
+
return description ? z.any().describe(description) : z.any();
|
|
128
|
+
}
|
|
129
|
+
function buildCliArgSchema(def) {
|
|
130
|
+
return Object.fromEntries(Object.entries(def.schema).flatMap(([key, zodType])=>getAcceptedCliOptionNames(key, def.cli?.options?.[key]).map((cliKey)=>[
|
|
131
|
+
cliKey,
|
|
132
|
+
toOptionalCliSchemaField(zodType)
|
|
133
|
+
])));
|
|
134
|
+
}
|
|
135
|
+
function buildDisallowedCliSpellings(def) {
|
|
104
136
|
const disallowedSpellings = new Map();
|
|
105
137
|
for (const [key] of Object.entries(def.schema)){
|
|
106
138
|
const cliOption = def.cli?.options?.[key];
|
|
107
|
-
|
|
108
|
-
const acceptedNames = new Set(cliOption
|
|
109
|
-
const preferredLabel = formatCliOptionName(cliOption.preferredName ?? key);
|
|
139
|
+
const preferredLabel = formatCliOptionName(cliOption?.preferredName ?? key);
|
|
140
|
+
const acceptedNames = new Set(getAcceptedCliOptionNames(key, cliOption));
|
|
110
141
|
const knownSpellings = new Set([
|
|
111
142
|
key,
|
|
112
143
|
...getKeyAliases(key),
|
|
113
|
-
cliOption.preferredName
|
|
114
|
-
...cliOption
|
|
115
|
-
...cliOption.acceptedNames
|
|
144
|
+
...cliOption?.preferredName ? getKeyAliases(cliOption.preferredName) : [],
|
|
145
|
+
...cliOption?.aliases ?? []
|
|
116
146
|
]);
|
|
117
147
|
for (const spelling of knownSpellings)if (!acceptedNames.has(spelling)) disallowedSpellings.set(spelling, preferredLabel);
|
|
118
148
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
149
|
+
return disallowedSpellings;
|
|
150
|
+
}
|
|
151
|
+
function formatCliValidationError(scriptName, commandName, def, rawArgs) {
|
|
152
|
+
if (0 === Object.keys(def.schema).length) return;
|
|
153
|
+
const cliSchema = z.object(buildCliArgSchema(def)).strict();
|
|
154
|
+
const parsed = cliSchema.safeParse(rawArgs);
|
|
155
|
+
if (parsed.success) return;
|
|
156
|
+
const disallowedSpellings = buildDisallowedCliSpellings(def);
|
|
157
|
+
const unknownKeys = parsed.error.issues.flatMap((issue)=>'unrecognized_keys' === issue.code ? issue.keys : []);
|
|
158
|
+
if (unknownKeys.length > 0) return unknownKeys.map((key)=>{
|
|
122
159
|
const preferredLabel = disallowedSpellings.get(key);
|
|
123
|
-
if (preferredLabel)
|
|
124
|
-
|
|
160
|
+
if (preferredLabel) return `Unsupported option "--${key}" for ${scriptName} ${commandName}. Use "${preferredLabel}" instead.`;
|
|
161
|
+
return `Unknown option "--${key}" for ${scriptName} ${commandName}.`;
|
|
162
|
+
}).join('\n');
|
|
163
|
+
const [issue] = parsed.error.issues;
|
|
164
|
+
const optionName = 'string' == typeof issue?.path[0] ? `--${issue.path[0]}` : 'CLI arguments';
|
|
165
|
+
return `Invalid value for "${optionName}" in ${scriptName} ${commandName}: ${issue?.message ?? parsed.error.message}`;
|
|
125
166
|
}
|
|
126
167
|
function printCommandHelp(scriptName, cmd) {
|
|
127
168
|
const { def } = cmd;
|
|
@@ -196,14 +237,16 @@ async function runToolsCLI(tools, scriptName, options) {
|
|
|
196
237
|
printHelp(scriptName, commands, cliVersion);
|
|
197
238
|
throw new CLIError(`Unknown command: ${commandName}`);
|
|
198
239
|
}
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
debug('command: %s, args: %s', match.name, JSON.stringify(parsedArgs));
|
|
202
|
-
if (true === parsedArgs.help) {
|
|
240
|
+
const rawCliArgs = parseRawCliArgs(restArgs);
|
|
241
|
+
if (true === rawCliArgs.help) {
|
|
203
242
|
debug('showing command help for: %s', match.name);
|
|
204
243
|
printCommandHelp(scriptName, match);
|
|
205
244
|
return;
|
|
206
245
|
}
|
|
246
|
+
const cliValidationError = formatCliValidationError(scriptName, match.name, match.def, rawCliArgs);
|
|
247
|
+
if (cliValidationError) throw new CLIError(cliValidationError);
|
|
248
|
+
const parsedArgs = parseCliArgs(restArgs);
|
|
249
|
+
debug('command: %s, args: %s', match.name, JSON.stringify(parsedArgs));
|
|
207
250
|
const result = await match.def.handler(parsedArgs);
|
|
208
251
|
debug('command %s completed, isError: %s', match.name, result.isError ?? false);
|
|
209
252
|
outputResult(result);
|
|
@@ -37,26 +37,18 @@ class BaseMidsceneTools {
|
|
|
37
37
|
const options = Object.fromEntries(this.getInitArgKeys().map((key)=>{
|
|
38
38
|
const canonicalKey = `${this.initArgSpec.namespace}.${key}`;
|
|
39
39
|
const preferredName = this.initArgSpec.cli?.preferredNames?.[key] ?? (this.initArgSpec.cli?.preferBareKeys ? camelToKebab(key) : canonicalKey);
|
|
40
|
-
const acceptedNames =
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
] : void 0;
|
|
46
|
-
const aliases = new Set(acceptedNames ?? getKeyAliases(key));
|
|
47
|
-
if (this.initArgSpec.cli?.preferBareKeys) aliases.delete(preferredName);
|
|
48
|
-
else {
|
|
49
|
-
for (const alias of getKeyAliases(canonicalKey))aliases.add(alias);
|
|
50
|
-
aliases.delete(preferredName);
|
|
51
|
-
}
|
|
40
|
+
const acceptedNames = new Set([
|
|
41
|
+
preferredName,
|
|
42
|
+
...this.initArgSpec.cli?.preferBareKeys ? getKeyAliases(key) : getKeyAliases(canonicalKey)
|
|
43
|
+
]);
|
|
44
|
+
acceptedNames.delete(preferredName);
|
|
52
45
|
return [
|
|
53
46
|
canonicalKey,
|
|
54
47
|
{
|
|
55
48
|
preferredName,
|
|
56
49
|
aliases: [
|
|
57
|
-
...
|
|
58
|
-
]
|
|
59
|
-
acceptedNames
|
|
50
|
+
...acceptedNames
|
|
51
|
+
]
|
|
60
52
|
}
|
|
61
53
|
];
|
|
62
54
|
}));
|
|
@@ -44,6 +44,7 @@ const external_node_os_namespaceObject = require("node:os");
|
|
|
44
44
|
const external_node_path_namespaceObject = require("node:path");
|
|
45
45
|
const external_dotenv_namespaceObject = require("dotenv");
|
|
46
46
|
var external_dotenv_default = /*#__PURE__*/ __webpack_require__.n(external_dotenv_namespaceObject);
|
|
47
|
+
const external_zod_namespaceObject = require("zod");
|
|
47
48
|
const external_key_alias_utils_js_namespaceObject = require("../key-alias-utils.js");
|
|
48
49
|
const external_logger_js_namespaceObject = require("../logger.js");
|
|
49
50
|
function _define_property(obj, key, value) {
|
|
@@ -69,9 +70,29 @@ function parseValue(raw) {
|
|
|
69
70
|
if (/^-?\d+(\.\d+)?$/.test(raw)) return Number(raw);
|
|
70
71
|
return raw;
|
|
71
72
|
}
|
|
73
|
+
function walkCliArgs(args, setArgValue) {
|
|
74
|
+
for(let i = 0; i < args.length; i++){
|
|
75
|
+
const arg = args[i];
|
|
76
|
+
if (!arg.startsWith('--')) continue;
|
|
77
|
+
const body = arg.slice(2);
|
|
78
|
+
const eqIdx = body.indexOf('=');
|
|
79
|
+
if (eqIdx >= 0) setArgValue(body.slice(0, eqIdx), parseValue(body.slice(eqIdx + 1)));
|
|
80
|
+
else if (args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
81
|
+
i++;
|
|
82
|
+
setArgValue(body, parseValue(args[i]));
|
|
83
|
+
} else setArgValue(body, true);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function parseRawCliArgs(args) {
|
|
87
|
+
const result = {};
|
|
88
|
+
walkCliArgs(args, (key, value)=>{
|
|
89
|
+
result[key] = value;
|
|
90
|
+
});
|
|
91
|
+
return result;
|
|
92
|
+
}
|
|
72
93
|
function parseCliArgs(args) {
|
|
73
94
|
const result = {};
|
|
74
|
-
|
|
95
|
+
walkCliArgs(args, (key, value)=>{
|
|
75
96
|
if (!key.includes('.')) {
|
|
76
97
|
result[key] = value;
|
|
77
98
|
return;
|
|
@@ -90,18 +111,7 @@ function parseCliArgs(args) {
|
|
|
90
111
|
}
|
|
91
112
|
const leafSegment = segments[segments.length - 1];
|
|
92
113
|
for (const alias of (0, external_key_alias_utils_js_namespaceObject.getKeyAliases)(leafSegment))current[alias] = value;
|
|
93
|
-
};
|
|
94
|
-
for(let i = 0; i < args.length; i++){
|
|
95
|
-
const arg = args[i];
|
|
96
|
-
if (!arg.startsWith('--')) continue;
|
|
97
|
-
const body = arg.slice(2);
|
|
98
|
-
const eqIdx = body.indexOf('=');
|
|
99
|
-
if (eqIdx >= 0) setArgValue(body.slice(0, eqIdx), parseValue(body.slice(eqIdx + 1)));
|
|
100
|
-
else if (args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
101
|
-
i++;
|
|
102
|
-
setArgValue(body, parseValue(args[i]));
|
|
103
|
-
} else setArgValue(body, true);
|
|
104
|
-
}
|
|
114
|
+
});
|
|
105
115
|
return result;
|
|
106
116
|
}
|
|
107
117
|
function outputContentItem(item, isError) {
|
|
@@ -142,28 +152,59 @@ function getCliOptionDisplay(key, cliOption) {
|
|
|
142
152
|
aliases
|
|
143
153
|
};
|
|
144
154
|
}
|
|
145
|
-
function
|
|
155
|
+
function getAcceptedCliOptionNames(key, cliOption) {
|
|
156
|
+
return [
|
|
157
|
+
...new Set(cliOption ? [
|
|
158
|
+
cliOption.preferredName ?? key,
|
|
159
|
+
...cliOption.aliases ?? []
|
|
160
|
+
] : [
|
|
161
|
+
key,
|
|
162
|
+
...(0, external_key_alias_utils_js_namespaceObject.getKeyAliases)(key)
|
|
163
|
+
])
|
|
164
|
+
];
|
|
165
|
+
}
|
|
166
|
+
function toOptionalCliSchemaField(field) {
|
|
167
|
+
if ('object' == typeof field && null !== field && 'function' == typeof field.optional) return field.optional();
|
|
168
|
+
const description = 'object' == typeof field && null !== field && "description" in field && 'string' == typeof field.description ? field.description : void 0;
|
|
169
|
+
return description ? external_zod_namespaceObject.z.any().describe(description) : external_zod_namespaceObject.z.any();
|
|
170
|
+
}
|
|
171
|
+
function buildCliArgSchema(def) {
|
|
172
|
+
return Object.fromEntries(Object.entries(def.schema).flatMap(([key, zodType])=>getAcceptedCliOptionNames(key, def.cli?.options?.[key]).map((cliKey)=>[
|
|
173
|
+
cliKey,
|
|
174
|
+
toOptionalCliSchemaField(zodType)
|
|
175
|
+
])));
|
|
176
|
+
}
|
|
177
|
+
function buildDisallowedCliSpellings(def) {
|
|
146
178
|
const disallowedSpellings = new Map();
|
|
147
179
|
for (const [key] of Object.entries(def.schema)){
|
|
148
180
|
const cliOption = def.cli?.options?.[key];
|
|
149
|
-
|
|
150
|
-
const acceptedNames = new Set(cliOption
|
|
151
|
-
const preferredLabel = formatCliOptionName(cliOption.preferredName ?? key);
|
|
181
|
+
const preferredLabel = formatCliOptionName(cliOption?.preferredName ?? key);
|
|
182
|
+
const acceptedNames = new Set(getAcceptedCliOptionNames(key, cliOption));
|
|
152
183
|
const knownSpellings = new Set([
|
|
153
184
|
key,
|
|
154
185
|
...(0, external_key_alias_utils_js_namespaceObject.getKeyAliases)(key),
|
|
155
|
-
cliOption.preferredName
|
|
156
|
-
...cliOption
|
|
157
|
-
...cliOption.acceptedNames
|
|
186
|
+
...cliOption?.preferredName ? (0, external_key_alias_utils_js_namespaceObject.getKeyAliases)(cliOption.preferredName) : [],
|
|
187
|
+
...cliOption?.aliases ?? []
|
|
158
188
|
]);
|
|
159
189
|
for (const spelling of knownSpellings)if (!acceptedNames.has(spelling)) disallowedSpellings.set(spelling, preferredLabel);
|
|
160
190
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
191
|
+
return disallowedSpellings;
|
|
192
|
+
}
|
|
193
|
+
function formatCliValidationError(scriptName, commandName, def, rawArgs) {
|
|
194
|
+
if (0 === Object.keys(def.schema).length) return;
|
|
195
|
+
const cliSchema = external_zod_namespaceObject.z.object(buildCliArgSchema(def)).strict();
|
|
196
|
+
const parsed = cliSchema.safeParse(rawArgs);
|
|
197
|
+
if (parsed.success) return;
|
|
198
|
+
const disallowedSpellings = buildDisallowedCliSpellings(def);
|
|
199
|
+
const unknownKeys = parsed.error.issues.flatMap((issue)=>'unrecognized_keys' === issue.code ? issue.keys : []);
|
|
200
|
+
if (unknownKeys.length > 0) return unknownKeys.map((key)=>{
|
|
164
201
|
const preferredLabel = disallowedSpellings.get(key);
|
|
165
|
-
if (preferredLabel)
|
|
166
|
-
|
|
202
|
+
if (preferredLabel) return `Unsupported option "--${key}" for ${scriptName} ${commandName}. Use "${preferredLabel}" instead.`;
|
|
203
|
+
return `Unknown option "--${key}" for ${scriptName} ${commandName}.`;
|
|
204
|
+
}).join('\n');
|
|
205
|
+
const [issue] = parsed.error.issues;
|
|
206
|
+
const optionName = 'string' == typeof issue?.path[0] ? `--${issue.path[0]}` : 'CLI arguments';
|
|
207
|
+
return `Invalid value for "${optionName}" in ${scriptName} ${commandName}: ${issue?.message ?? parsed.error.message}`;
|
|
167
208
|
}
|
|
168
209
|
function printCommandHelp(scriptName, cmd) {
|
|
169
210
|
const { def } = cmd;
|
|
@@ -238,14 +279,16 @@ async function runToolsCLI(tools, scriptName, options) {
|
|
|
238
279
|
printHelp(scriptName, commands, cliVersion);
|
|
239
280
|
throw new CLIError(`Unknown command: ${commandName}`);
|
|
240
281
|
}
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
debug('command: %s, args: %s', match.name, JSON.stringify(parsedArgs));
|
|
244
|
-
if (true === parsedArgs.help) {
|
|
282
|
+
const rawCliArgs = parseRawCliArgs(restArgs);
|
|
283
|
+
if (true === rawCliArgs.help) {
|
|
245
284
|
debug('showing command help for: %s', match.name);
|
|
246
285
|
printCommandHelp(scriptName, match);
|
|
247
286
|
return;
|
|
248
287
|
}
|
|
288
|
+
const cliValidationError = formatCliValidationError(scriptName, match.name, match.def, rawCliArgs);
|
|
289
|
+
if (cliValidationError) throw new CLIError(cliValidationError);
|
|
290
|
+
const parsedArgs = parseCliArgs(restArgs);
|
|
291
|
+
debug('command: %s, args: %s', match.name, JSON.stringify(parsedArgs));
|
|
249
292
|
const result = await match.def.handler(parsedArgs);
|
|
250
293
|
debug('command %s completed, isError: %s', match.name, result.isError ?? false);
|
|
251
294
|
outputResult(result);
|
|
@@ -65,26 +65,18 @@ class BaseMidsceneTools {
|
|
|
65
65
|
const options = Object.fromEntries(this.getInitArgKeys().map((key)=>{
|
|
66
66
|
const canonicalKey = `${this.initArgSpec.namespace}.${key}`;
|
|
67
67
|
const preferredName = this.initArgSpec.cli?.preferredNames?.[key] ?? (this.initArgSpec.cli?.preferBareKeys ? (0, external_key_alias_utils_js_namespaceObject.camelToKebab)(key) : canonicalKey);
|
|
68
|
-
const acceptedNames =
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
] : void 0;
|
|
74
|
-
const aliases = new Set(acceptedNames ?? (0, external_key_alias_utils_js_namespaceObject.getKeyAliases)(key));
|
|
75
|
-
if (this.initArgSpec.cli?.preferBareKeys) aliases.delete(preferredName);
|
|
76
|
-
else {
|
|
77
|
-
for (const alias of (0, external_key_alias_utils_js_namespaceObject.getKeyAliases)(canonicalKey))aliases.add(alias);
|
|
78
|
-
aliases.delete(preferredName);
|
|
79
|
-
}
|
|
68
|
+
const acceptedNames = new Set([
|
|
69
|
+
preferredName,
|
|
70
|
+
...this.initArgSpec.cli?.preferBareKeys ? (0, external_key_alias_utils_js_namespaceObject.getKeyAliases)(key) : (0, external_key_alias_utils_js_namespaceObject.getKeyAliases)(canonicalKey)
|
|
71
|
+
]);
|
|
72
|
+
acceptedNames.delete(preferredName);
|
|
80
73
|
return [
|
|
81
74
|
canonicalKey,
|
|
82
75
|
{
|
|
83
76
|
preferredName,
|
|
84
77
|
aliases: [
|
|
85
|
-
...
|
|
86
|
-
]
|
|
87
|
-
acceptedNames
|
|
78
|
+
...acceptedNames
|
|
79
|
+
]
|
|
88
80
|
}
|
|
89
81
|
];
|
|
90
82
|
}));
|
package/package.json
CHANGED
package/src/cli/cli-runner.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { existsSync, writeFileSync } from 'node:fs';
|
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import dotenv from 'dotenv';
|
|
5
|
+
import { z } from 'zod';
|
|
5
6
|
import { getKeyAliases, isRecord } from '../key-alias-utils';
|
|
6
7
|
import { getDebug } from '../logger';
|
|
7
8
|
import type { BaseMidsceneTools } from '../mcp/base-tools';
|
|
@@ -61,10 +62,43 @@ export function parseValue(raw: string): unknown {
|
|
|
61
62
|
return raw;
|
|
62
63
|
}
|
|
63
64
|
|
|
65
|
+
function walkCliArgs(
|
|
66
|
+
args: string[],
|
|
67
|
+
setArgValue: (key: string, value: unknown) => void,
|
|
68
|
+
): void {
|
|
69
|
+
for (let i = 0; i < args.length; i++) {
|
|
70
|
+
const arg = args[i];
|
|
71
|
+
if (!arg.startsWith('--')) continue;
|
|
72
|
+
|
|
73
|
+
const body = arg.slice(2);
|
|
74
|
+
const eqIdx = body.indexOf('=');
|
|
75
|
+
|
|
76
|
+
if (eqIdx >= 0) {
|
|
77
|
+
// --key=value
|
|
78
|
+
setArgValue(body.slice(0, eqIdx), parseValue(body.slice(eqIdx + 1)));
|
|
79
|
+
} else if (args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
80
|
+
// --key value
|
|
81
|
+
i++;
|
|
82
|
+
setArgValue(body, parseValue(args[i]));
|
|
83
|
+
} else {
|
|
84
|
+
// --flag (boolean)
|
|
85
|
+
setArgValue(body, true);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function parseRawCliArgs(args: string[]): Record<string, unknown> {
|
|
91
|
+
const result: Record<string, unknown> = {};
|
|
92
|
+
walkCliArgs(args, (key, value) => {
|
|
93
|
+
result[key] = value;
|
|
94
|
+
});
|
|
95
|
+
return result;
|
|
96
|
+
}
|
|
97
|
+
|
|
64
98
|
export function parseCliArgs(args: string[]): Record<string, unknown> {
|
|
65
99
|
const result: Record<string, unknown> = {};
|
|
66
100
|
|
|
67
|
-
|
|
101
|
+
walkCliArgs(args, (key, value) => {
|
|
68
102
|
if (!key.includes('.')) {
|
|
69
103
|
result[key] = value;
|
|
70
104
|
return;
|
|
@@ -98,27 +132,7 @@ export function parseCliArgs(args: string[]): Record<string, unknown> {
|
|
|
98
132
|
for (const alias of getKeyAliases(leafSegment)) {
|
|
99
133
|
current[alias] = value;
|
|
100
134
|
}
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
for (let i = 0; i < args.length; i++) {
|
|
104
|
-
const arg = args[i];
|
|
105
|
-
if (!arg.startsWith('--')) continue;
|
|
106
|
-
|
|
107
|
-
const body = arg.slice(2);
|
|
108
|
-
const eqIdx = body.indexOf('=');
|
|
109
|
-
|
|
110
|
-
if (eqIdx >= 0) {
|
|
111
|
-
// --key=value
|
|
112
|
-
setArgValue(body.slice(0, eqIdx), parseValue(body.slice(eqIdx + 1)));
|
|
113
|
-
} else if (args[i + 1] && !args[i + 1].startsWith('--')) {
|
|
114
|
-
// --key value
|
|
115
|
-
i++;
|
|
116
|
-
setArgValue(body, parseValue(args[i]));
|
|
117
|
-
} else {
|
|
118
|
-
// --flag (boolean)
|
|
119
|
-
setArgValue(body, true);
|
|
120
|
-
}
|
|
121
|
-
}
|
|
135
|
+
});
|
|
122
136
|
|
|
123
137
|
return result;
|
|
124
138
|
}
|
|
@@ -175,28 +189,63 @@ function getCliOptionDisplay(
|
|
|
175
189
|
return { label, aliases };
|
|
176
190
|
}
|
|
177
191
|
|
|
178
|
-
function
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
192
|
+
function getAcceptedCliOptionNames(
|
|
193
|
+
key: string,
|
|
194
|
+
cliOption?: ToolCliOption,
|
|
195
|
+
): string[] {
|
|
196
|
+
return [
|
|
197
|
+
...new Set(
|
|
198
|
+
cliOption
|
|
199
|
+
? [cliOption.preferredName ?? key, ...(cliOption.aliases ?? [])]
|
|
200
|
+
: [key, ...getKeyAliases(key)],
|
|
201
|
+
),
|
|
202
|
+
];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function toOptionalCliSchemaField(field: unknown): z.ZodTypeAny {
|
|
206
|
+
if (
|
|
207
|
+
typeof field === 'object' &&
|
|
208
|
+
field !== null &&
|
|
209
|
+
typeof (field as z.ZodTypeAny).optional === 'function'
|
|
210
|
+
) {
|
|
211
|
+
return (field as z.ZodTypeAny).optional();
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const description =
|
|
215
|
+
typeof field === 'object' &&
|
|
216
|
+
field !== null &&
|
|
217
|
+
'description' in field &&
|
|
218
|
+
typeof (field as { description?: unknown }).description === 'string'
|
|
219
|
+
? (field as { description: string }).description
|
|
220
|
+
: undefined;
|
|
221
|
+
return description ? z.any().describe(description) : z.any();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function buildCliArgSchema(def: ToolDefinition): Record<string, z.ZodTypeAny> {
|
|
225
|
+
return Object.fromEntries(
|
|
226
|
+
Object.entries(def.schema).flatMap(([key, zodType]) =>
|
|
227
|
+
getAcceptedCliOptionNames(key, def.cli?.options?.[key]).map((cliKey) => [
|
|
228
|
+
cliKey,
|
|
229
|
+
toOptionalCliSchemaField(zodType),
|
|
230
|
+
]),
|
|
231
|
+
),
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function buildDisallowedCliSpellings(def: ToolDefinition): Map<string, string> {
|
|
184
236
|
const disallowedSpellings = new Map<string, string>();
|
|
185
237
|
|
|
186
238
|
for (const [key] of Object.entries(def.schema)) {
|
|
187
239
|
const cliOption = def.cli?.options?.[key];
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
const acceptedNames = new Set(cliOption.acceptedNames);
|
|
193
|
-
const preferredLabel = formatCliOptionName(cliOption.preferredName ?? key);
|
|
240
|
+
const preferredLabel = formatCliOptionName(cliOption?.preferredName ?? key);
|
|
241
|
+
const acceptedNames = new Set(getAcceptedCliOptionNames(key, cliOption));
|
|
194
242
|
const knownSpellings = new Set<string>([
|
|
195
243
|
key,
|
|
196
244
|
...getKeyAliases(key),
|
|
197
|
-
cliOption
|
|
198
|
-
|
|
199
|
-
|
|
245
|
+
...(cliOption?.preferredName
|
|
246
|
+
? getKeyAliases(cliOption.preferredName)
|
|
247
|
+
: []),
|
|
248
|
+
...(cliOption?.aliases ?? []),
|
|
200
249
|
]);
|
|
201
250
|
|
|
202
251
|
for (const spelling of knownSpellings) {
|
|
@@ -206,21 +255,46 @@ function validateRestrictedCliArgSpellings(
|
|
|
206
255
|
}
|
|
207
256
|
}
|
|
208
257
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
continue;
|
|
212
|
-
}
|
|
258
|
+
return disallowedSpellings;
|
|
259
|
+
}
|
|
213
260
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
261
|
+
function formatCliValidationError(
|
|
262
|
+
scriptName: string,
|
|
263
|
+
commandName: string,
|
|
264
|
+
def: ToolDefinition,
|
|
265
|
+
rawArgs: Record<string, unknown>,
|
|
266
|
+
): string | undefined {
|
|
267
|
+
if (Object.keys(def.schema).length === 0) {
|
|
268
|
+
return undefined;
|
|
269
|
+
}
|
|
219
270
|
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
271
|
+
const cliSchema = z.object(buildCliArgSchema(def)).strict();
|
|
272
|
+
const parsed = cliSchema.safeParse(rawArgs);
|
|
273
|
+
if (parsed.success) {
|
|
274
|
+
return undefined;
|
|
223
275
|
}
|
|
276
|
+
|
|
277
|
+
const disallowedSpellings = buildDisallowedCliSpellings(def);
|
|
278
|
+
const unknownKeys = parsed.error.issues.flatMap((issue) =>
|
|
279
|
+
issue.code === 'unrecognized_keys' ? issue.keys : [],
|
|
280
|
+
);
|
|
281
|
+
|
|
282
|
+
if (unknownKeys.length > 0) {
|
|
283
|
+
return unknownKeys
|
|
284
|
+
.map((key) => {
|
|
285
|
+
const preferredLabel = disallowedSpellings.get(key);
|
|
286
|
+
if (preferredLabel) {
|
|
287
|
+
return `Unsupported option "--${key}" for ${scriptName} ${commandName}. Use "${preferredLabel}" instead.`;
|
|
288
|
+
}
|
|
289
|
+
return `Unknown option "--${key}" for ${scriptName} ${commandName}.`;
|
|
290
|
+
})
|
|
291
|
+
.join('\n');
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
const [issue] = parsed.error.issues;
|
|
295
|
+
const optionName =
|
|
296
|
+
typeof issue?.path[0] === 'string' ? `--${issue.path[0]}` : 'CLI arguments';
|
|
297
|
+
return `Invalid value for "${optionName}" in ${scriptName} ${commandName}: ${issue?.message ?? parsed.error.message}`;
|
|
224
298
|
}
|
|
225
299
|
|
|
226
300
|
function printCommandHelp(scriptName: string, cmd: CLICommand): void {
|
|
@@ -343,21 +417,26 @@ export async function runToolsCLI(
|
|
|
343
417
|
throw new CLIError(`Unknown command: ${commandName}`);
|
|
344
418
|
}
|
|
345
419
|
|
|
346
|
-
|
|
347
|
-
|
|
420
|
+
const rawCliArgs = parseRawCliArgs(restArgs);
|
|
421
|
+
if (rawCliArgs.help === true) {
|
|
422
|
+
debug('showing command help for: %s', match.name);
|
|
423
|
+
printCommandHelp(scriptName, match);
|
|
424
|
+
return;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const cliValidationError = formatCliValidationError(
|
|
348
428
|
scriptName,
|
|
349
429
|
match.name,
|
|
350
430
|
match.def,
|
|
431
|
+
rawCliArgs,
|
|
351
432
|
);
|
|
433
|
+
if (cliValidationError) {
|
|
434
|
+
throw new CLIError(cliValidationError);
|
|
435
|
+
}
|
|
436
|
+
|
|
352
437
|
const parsedArgs = parseCliArgs(restArgs);
|
|
353
438
|
debug('command: %s, args: %s', match.name, JSON.stringify(parsedArgs));
|
|
354
439
|
|
|
355
|
-
if (parsedArgs.help === true) {
|
|
356
|
-
debug('showing command help for: %s', match.name);
|
|
357
|
-
printCommandHelp(scriptName, match);
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
|
|
361
440
|
const result = await match.def.handler(parsedArgs);
|
|
362
441
|
debug(
|
|
363
442
|
'command %s completed, isError: %s',
|
package/src/mcp/base-tools.ts
CHANGED
|
@@ -162,25 +162,19 @@ export abstract class BaseMidsceneTools<
|
|
|
162
162
|
? camelToKebab(key)
|
|
163
163
|
: canonicalKey);
|
|
164
164
|
|
|
165
|
-
const acceptedNames =
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
for (const alias of getKeyAliases(canonicalKey)) {
|
|
173
|
-
aliases.add(alias);
|
|
174
|
-
}
|
|
175
|
-
aliases.delete(preferredName);
|
|
176
|
-
}
|
|
165
|
+
const acceptedNames = new Set<string>([
|
|
166
|
+
preferredName,
|
|
167
|
+
...(this.initArgSpec!.cli?.preferBareKeys
|
|
168
|
+
? getKeyAliases(key)
|
|
169
|
+
: getKeyAliases(canonicalKey)),
|
|
170
|
+
]);
|
|
171
|
+
acceptedNames.delete(preferredName);
|
|
177
172
|
|
|
178
173
|
return [
|
|
179
174
|
canonicalKey,
|
|
180
175
|
{
|
|
181
176
|
preferredName,
|
|
182
|
-
aliases: [...
|
|
183
|
-
acceptedNames,
|
|
177
|
+
aliases: [...acceptedNames],
|
|
184
178
|
},
|
|
185
179
|
];
|
|
186
180
|
}),
|