ms-vite-plugin 1.4.47 → 1.4.49

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 (43) hide show
  1. package/dist/build.js +1 -292
  2. package/dist/cli.js +6 -1234
  3. package/dist/mcp/device-config.d.ts +0 -47
  4. package/dist/mcp/device-config.js +1 -183
  5. package/dist/mcp/device-log.d.ts +0 -8
  6. package/dist/mcp/device-log.js +3 -27
  7. package/dist/mcp/doc-tools.d.ts +0 -7
  8. package/dist/mcp/doc-tools.js +2 -39
  9. package/dist/mcp/docs-service.d.ts +0 -13
  10. package/dist/mcp/docs-service.js +1 -63
  11. package/dist/mcp/httpapi-docs-service.d.ts +0 -31
  12. package/dist/mcp/httpapi-docs-service.js +1 -122
  13. package/dist/mcp/httpapi-tools.d.ts +0 -7
  14. package/dist/mcp/httpapi-tools.js +4 -222
  15. package/dist/mcp/image-tools.d.ts +0 -62
  16. package/dist/mcp/image-tools.js +8 -607
  17. package/dist/mcp/ocr-tools.d.ts +0 -25
  18. package/dist/mcp/ocr-tools.js +5 -413
  19. package/dist/mcp/project.d.ts +0 -14
  20. package/dist/mcp/project.js +1 -74
  21. package/dist/mcp/runtime-tools.d.ts +0 -7
  22. package/dist/mcp/runtime-tools.js +11 -308
  23. package/dist/mcp/tool-utils.d.ts +0 -40
  24. package/dist/mcp/tool-utils.js +1 -81
  25. package/dist/mcp/tools.d.ts +0 -12
  26. package/dist/mcp/tools.js +1 -31
  27. package/dist/mcp/types.d.ts +0 -12
  28. package/dist/mcp/types.js +1 -11
  29. package/dist/mcp-server.js +1 -85
  30. package/dist/packager.d.ts +0 -7
  31. package/dist/packager.js +1 -125
  32. package/dist/project.d.ts +0 -61
  33. package/dist/project.js +1 -619
  34. package/dist/stopGuardPlugin.d.ts +0 -6
  35. package/dist/stopGuardPlugin.js +1 -165
  36. package/dist/version.d.ts +0 -10
  37. package/dist/version.js +1 -63
  38. package/dist/ws-manager.d.ts +0 -95
  39. package/dist/ws-manager.js +1 -392
  40. package/docs/api/image.md +249 -25
  41. package/docs/apicn/image.md +245 -19
  42. package/docs/apipython/image.md +238 -18
  43. package/package.json +3 -2
package/dist/cli.js CHANGED
@@ -1,1235 +1,7 @@
1
1
  #!/usr/bin/env node
2
- "use strict";
3
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
- if (k2 === undefined) k2 = k;
5
- var desc = Object.getOwnPropertyDescriptor(m, k);
6
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
- desc = { enumerable: true, get: function() { return m[k]; } };
8
- }
9
- Object.defineProperty(o, k2, desc);
10
- }) : (function(o, m, k, k2) {
11
- if (k2 === undefined) k2 = k;
12
- o[k2] = m[k];
13
- }));
14
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
- Object.defineProperty(o, "default", { enumerable: true, value: v });
16
- }) : function(o, v) {
17
- o["default"] = v;
18
- });
19
- var __importStar = (this && this.__importStar) || (function () {
20
- var ownKeys = function(o) {
21
- ownKeys = Object.getOwnPropertyNames || function (o) {
22
- var ar = [];
23
- for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
- return ar;
25
- };
26
- return ownKeys(o);
27
- };
28
- return function (mod) {
29
- if (mod && mod.__esModule) return mod;
30
- var result = {};
31
- if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
- __setModuleDefault(result, mod);
33
- return result;
34
- };
35
- })();
36
- Object.defineProperty(exports, "__esModule", { value: true });
37
- const commander_1 = require("commander");
38
- const os = __importStar(require("os"));
39
- const path = __importStar(require("path"));
40
- const build_1 = require("./build");
41
- const fsExtra = __importStar(require("fs-extra"));
42
- const project_1 = require("./project");
43
- const packager_1 = require("./packager");
44
- const ws_manager_1 = require("./ws-manager");
45
- const version_1 = require("./version");
46
- const docs_service_1 = require("./mcp/docs-service");
47
- const httpapi_docs_service_1 = require("./mcp/httpapi-docs-service");
48
- const tool_utils_1 = require("./mcp/tool-utils");
49
- const types_1 = require("./mcp/types");
50
- const ocr_tools_1 = require("./mcp/ocr-tools");
51
- const image_tools_1 = require("./mcp/image-tools");
52
- /**
53
- * WS 状态持久化文件路径(用于跨进程查询 ws-status)
54
- */
55
- const WS_PID_FILE = path.join(os.tmpdir(), "ms-cli-ws-server.json");
56
- /**
57
- * 检查工作目录是否为有效的 KuaiJS 项目
58
- * @param workspacePath 工作目录路径
59
- * @returns 是否为有效项目
60
- * @example
61
- * await isValidKuaiJSProject(process.cwd())
62
- */
63
- async function isValidKuaiJSProject(workspacePath) {
64
- try {
65
- const packageJsonPath = path.join(workspacePath, "package.json");
66
- const scriptsPath = path.join(workspacePath, "scripts");
67
- const [packageExists, scriptsExists] = await Promise.all([
68
- fsExtra.pathExists(packageJsonPath),
69
- fsExtra.pathExists(scriptsPath),
70
- ]);
71
- return packageExists && scriptsExists;
72
- }
73
- catch {
74
- return false;
75
- }
76
- }
77
- /**
78
- * 统一校验项目目录合法性
79
- * @param workspacePath 待校验的项目目录
80
- * @returns 校验通过返回 Promise<void>
81
- * @example
82
- * await ensureValidKuaiJSProject("/Users/demo/project")
83
- */
84
- async function ensureValidKuaiJSProject(workspacePath) {
85
- if (await isValidKuaiJSProject(workspacePath)) {
86
- return;
87
- }
88
- console.error("❌ 错误: 当前目录不是有效的 快点JS 项目");
89
- console.error("请确保目录包含以下文件:");
90
- console.error(" - package.json");
91
- console.error(" - scripts/ 目录");
92
- process.exit(1);
93
- }
94
- /**
95
- * 解析整数参数
96
- * @param value 用户传入的参数文本
97
- * @param defaultValue 默认值
98
- * @param name 参数名称
99
- * @param min 最小值
100
- * @param max 最大值
101
- * @returns 返回校验后的整数
102
- * @example
103
- * parseCliInteger("20", 10, "limit", 1, 100)
104
- */
105
- function parseCliInteger(value, defaultValue, name, min, max) {
106
- const text = (value ?? String(defaultValue)).trim();
107
- const numberValue = Number.parseInt(text, 10);
108
- if (!Number.isInteger(numberValue) ||
109
- numberValue < min ||
110
- (max !== undefined && numberValue > max)) {
111
- throw new Error(`无效 ${name}: ${text},允许范围 ${min}${max === undefined ? "+" : `..${max}`}`);
112
- }
113
- return numberValue;
114
- }
115
- /**
116
- * 解析浮点数参数
117
- * @param value 用户传入的参数文本
118
- * @param defaultValue 默认值
119
- * @param name 参数名称
120
- * @param min 最小值
121
- * @param max 最大值
122
- * @returns 返回校验后的数字
123
- * @example
124
- * parseCliNumber("0.6", 0.6, "confidence", 0, 1)
125
- */
126
- function parseCliNumber(value, defaultValue, name, min, max) {
127
- const text = (value ?? String(defaultValue)).trim();
128
- const numberValue = Number.parseFloat(text);
129
- if (!Number.isFinite(numberValue) || numberValue < min || numberValue > max) {
130
- throw new Error(`无效 ${name}: ${text},允许范围 ${min}..${max}`);
131
- }
132
- return numberValue;
133
- }
134
- /**
135
- * 解析枚举参数
136
- * @param value 用户传入的参数文本
137
- * @param defaultValue 默认值
138
- * @param choices 允许的取值
139
- * @param name 参数名称
140
- * @returns 返回校验后的枚举值
141
- * @example
142
- * parseCliChoice("json", "text", ["text", "json"], "format")
143
- */
144
- function parseCliChoice(value, defaultValue, choices, name) {
145
- const text = (value ?? defaultValue).trim();
146
- if (!choices.includes(text)) {
147
- throw new Error(`无效 ${name}: ${text},可选值: ${choices.join("|")}`);
148
- }
149
- return text;
150
- }
151
- /**
152
- * 解析 HTTP API 请求方法
153
- * @param value 用户传入的方法
154
- * @returns 返回 HTTP API 方法
155
- * @example
156
- * parseHttpApiMethodOption("GET")
157
- */
158
- function parseHttpApiMethodOption(value) {
159
- const method = value?.trim().toUpperCase();
160
- return parseCliChoice(method, "GET", ["GET", "POST"], "method");
161
- }
162
- /**
163
- * 解析设备 HTTP 地址
164
- * @param options 设备命令选项
165
- * @returns 返回设备 HTTP 目标
166
- * @example
167
- * parseDeviceHttpTarget({ ip: "192.168.1.10", port: "9800" })
168
- */
169
- function parseDeviceHttpTarget(options) {
170
- const ip = options.ip?.trim();
171
- if (!ip) {
172
- throw new Error("请通过 --ip 指定设备 IP 地址");
173
- }
174
- const port = parseCliInteger(options.port, 9800, "port", 1, 65535);
175
- return {
176
- ip,
177
- port: String(port),
178
- label: `${ip}:${port}`,
179
- };
180
- }
181
- /**
182
- * 输出文本到终端或文件
183
- * @param text 待输出文本
184
- * @param output 用户指定输出路径
185
- * @param savedLabel 保存成功提示
186
- * @returns 输出完成后返回
187
- * @example
188
- * await writeOrPrintText("ok", "./out.txt", "结果已保存")
189
- */
190
- async function writeOrPrintText(text, output, savedLabel) {
191
- if (output?.trim()) {
192
- const outputPath = path.resolve(output.trim());
193
- await fsExtra.ensureDir(path.dirname(outputPath));
194
- await fsExtra.writeFile(outputPath, text, "utf8");
195
- console.log(`✅ ${savedLabel}: ${outputPath}`);
196
- return;
197
- }
198
- console.log(text);
199
- }
200
- /**
201
- * 格式化 CLI 文档路径清单
202
- * @returns 返回本地文档路径文本
203
- * @example
204
- * const text = formatCliDocsPathsText()
205
- */
206
- function formatCliDocsPathsText() {
207
- const languages = ["js", "js_zh", "python"];
208
- return [
209
- "KuaiJS 文档路径:",
210
- `docsRoot: ${(0, docs_service_1.getDocsRootDir)()}`,
211
- ...languages.map((language) => `${language}: ${(0, docs_service_1.getDocsDirByLanguage)(language)} (${types_1.DOC_LANGUAGE_LABELS[language]})`),
212
- `httpApi: ${(0, httpapi_docs_service_1.getHttpApiDocPath)()}`,
213
- "说明: 直接读取上述 markdown 路径。",
214
- ].join("\n");
215
- }
216
- /**
217
- * 收集可重复字符串参数
218
- * @param value 当前参数值
219
- * @param previous 之前收集的值
220
- * @returns 返回追加后的数组
221
- * @example
222
- * collectStringOption("登录", [])
223
- */
224
- function collectStringOption(value, previous) {
225
- previous.push(value);
226
- return previous;
227
- }
228
- /**
229
- * 解析字符串数组参数
230
- * @param value 用户传入文本
231
- * @param name 参数名
232
- * @returns 返回字符串数组或 undefined
233
- * @example
234
- * parseStringArrayOption("zh-Hans,en-US", "languages")
235
- */
236
- function parseStringArrayOption(value, name) {
237
- const text = value?.trim();
238
- if (!text) {
239
- return undefined;
240
- }
241
- if (text.startsWith("[")) {
242
- const parsed = JSON.parse(text);
243
- if (!Array.isArray(parsed) ||
244
- !parsed.every((item) => typeof item === "string" && item.trim())) {
245
- throw new Error(`${name} 必须是字符串数组。`);
246
- }
247
- return parsed.map((item) => item.trim());
248
- }
249
- const items = text
250
- .split(",")
251
- .map((item) => item.trim())
252
- .filter((item) => item.length > 0);
253
- if (items.length === 0) {
254
- throw new Error(`${name} 不能为空。`);
255
- }
256
- return items;
257
- }
258
- /**
259
- * 解析 Apple OCR 语言数组
260
- * @param value 用户传入语言文本
261
- * @returns 返回语言数组或 undefined
262
- * @example
263
- * parseAppleOcrLanguages("zh-Hans,en-US")
264
- */
265
- function parseAppleOcrLanguages(value) {
266
- const languages = parseStringArrayOption(value, "languages");
267
- if (!languages) {
268
- return undefined;
269
- }
270
- const allowed = new Set(ocr_tools_1.APPLE_OCR_LANGUAGES);
271
- const invalid = languages.filter((language) => !allowed.has(language));
272
- if (invalid.length > 0) {
273
- throw new Error(`无效 languages: ${invalid.join(", ")},可选值: ${ocr_tools_1.APPLE_OCR_LANGUAGES.join(", ")}`);
274
- }
275
- return languages;
276
- }
277
- /**
278
- * 解析 JSON 对象参数
279
- * @param value 用户传入 JSON 文本
280
- * @param name 参数名
281
- * @returns 返回对象或 undefined
282
- * @example
283
- * parseJsonRecordOption('{"x":1}', "query")
284
- */
285
- function parseJsonRecordOption(value, name) {
286
- const text = value?.trim();
287
- if (!text) {
288
- return undefined;
289
- }
290
- const parsed = JSON.parse(text);
291
- if (!parsed ||
292
- typeof parsed !== "object" ||
293
- Array.isArray(parsed) ||
294
- !Object.values(parsed).every((item) => typeof item === "string" ||
295
- typeof item === "number" ||
296
- typeof item === "boolean" ||
297
- item === null)) {
298
- throw new Error(`${name} 必须是 JSON 对象,且值只能是 string/number/boolean/null。`);
299
- }
300
- return parsed;
301
- }
302
- /**
303
- * 解析 JSON 请求体参数
304
- * @param value 用户传入请求体文本
305
- * @returns 返回 JSON 值或 undefined
306
- * @example
307
- * parseJsonBodyOption('{"x":1}')
308
- */
309
- function parseJsonBodyOption(value) {
310
- const text = value?.trim();
311
- if (text === undefined || text.length === 0) {
312
- return undefined;
313
- }
314
- try {
315
- return JSON.parse(text);
316
- }
317
- catch {
318
- return value;
319
- }
320
- }
321
- /**
322
- * 标准化 HTTP API 路径
323
- * @param apiPath 用户传入路径
324
- * @returns 返回相对路径
325
- * @example
326
- * normalizeCliHttpApiPath("/api/status")
327
- */
328
- function normalizeCliHttpApiPath(apiPath) {
329
- const normalizedPath = apiPath.trim();
330
- if (/^https?:\/\//i.test(normalizedPath)) {
331
- throw new Error("path 只能传 HTTP API 相对路径,不能传完整 URL。");
332
- }
333
- if (!normalizedPath.startsWith("/")) {
334
- throw new Error("path 必须以 / 开头,例如 /api/status。");
335
- }
336
- if (normalizedPath.includes("?")) {
337
- throw new Error("path 不能包含 query string,请使用 --query 传递查询参数。");
338
- }
339
- return normalizedPath;
340
- }
341
- /**
342
- * 追加 HTTP query 参数
343
- * @param url 目标 URL
344
- * @param query query 参数
345
- * @returns 无返回值
346
- * @example
347
- * appendCliQueryParams(url, { x: 1 })
348
- */
349
- function appendCliQueryParams(url, query) {
350
- if (!query) {
351
- return;
352
- }
353
- for (const [key, value] of Object.entries(query)) {
354
- if (value === null) {
355
- continue;
356
- }
357
- url.searchParams.set(key, String(value));
358
- }
359
- }
360
- /**
361
- * 创建 HTTP API 请求体
362
- * @param body 用户传入请求体
363
- * @returns 返回 fetch 请求体和头
364
- * @example
365
- * createCliHttpRequestBody({ x: 1 })
366
- */
367
- function createCliHttpRequestBody(body) {
368
- if (body === undefined) {
369
- return {};
370
- }
371
- return {
372
- headers: {
373
- "Content-Type": "application/json",
374
- },
375
- requestBody: JSON.stringify(body),
376
- };
377
- }
378
- /**
379
- * 读取 HTTP API 响应文本
380
- * @param response fetch 响应
381
- * @param responseFormat 响应格式
382
- * @returns 返回格式化文本
383
- * @example
384
- * await readCliHttpApiResponseText(response, "json")
385
- */
386
- async function readCliHttpApiResponseText(response, responseFormat) {
387
- const responseText = await response.text();
388
- if (responseFormat === "text") {
389
- return responseText;
390
- }
391
- return (0, tool_utils_1.formatRuntimeJsonText)(JSON.parse(responseText));
392
- }
393
- /**
394
- * 构建命令处理函数
395
- * @param options 命令选项
396
- * @returns 请求完成后退出
397
- * @example
398
- * ms build -d
399
- */
400
- async function buildCommand(options) {
401
- try {
402
- const workspacePath = process.cwd();
403
- const isDev = options.dev || false;
404
- console.log(`🔍 检查项目目录: ${workspacePath}`);
405
- await ensureValidKuaiJSProject(workspacePath);
406
- console.log(`🚀 开始构建项目 (${isDev ? "开发模式" : "生产模式"})...`);
407
- await (0, build_1.buildAll)(isDev, workspacePath);
408
- console.log("✅ 构建完成!");
409
- // console.log(
410
- // `📦 输出文件: ${path.join(workspacePath, "dist", "ms.msbundle")}`
411
- // );
412
- }
413
- catch (error) {
414
- console.error("❌ 构建失败:", error instanceof Error ? error.message : error);
415
- process.exit(1);
416
- }
417
- }
418
- /**
419
- * package 命令处理函数(生产构建 + 加密)
420
- * @returns 请求完成后退出
421
- * @example
422
- * ms package
423
- */
424
- async function packageCommand() {
425
- try {
426
- const workspacePath = process.cwd();
427
- console.log(`🔍 检查项目目录: ${workspacePath}`);
428
- await ensureValidKuaiJSProject(workspacePath);
429
- console.log("📦 开始打包生产构建并加密 bundle...");
430
- const encryptedPath = await (0, packager_1.packageProject)(workspacePath);
431
- console.log(`✅ 打包完成: ${encryptedPath}`);
432
- }
433
- catch (error) {
434
- console.error("❌ 打包失败:", error instanceof Error ? error.message : error);
435
- process.exit(1);
436
- }
437
- }
438
- /**
439
- * run 命令处理函数
440
- * @param options 命令选项
441
- * @returns 请求完成后退出
442
- * @example
443
- * ms run --ip 192.168.1.100 --port 9800
444
- */
445
- async function runCommand(options) {
446
- try {
447
- const workspacePath = process.cwd();
448
- await ensureValidKuaiJSProject(workspacePath);
449
- await (0, project_1.runOnDevice)(options);
450
- }
451
- catch (error) {
452
- console.error("❌ 运行失败:", error instanceof Error ? error.message : error);
453
- process.exit(1);
454
- }
455
- }
456
- /**
457
- * runUI 命令处理函数
458
- * @param options 命令选项
459
- * @returns 请求完成后退出
460
- * @example
461
- * ms run-ui --ip 192.168.1.100 --port 9800
462
- */
463
- async function runUICommand(options) {
464
- try {
465
- const workspacePath = process.cwd();
466
- await ensureValidKuaiJSProject(workspacePath);
467
- await (0, project_1.runUIOnDevice)(options);
468
- }
469
- catch (error) {
470
- console.error("❌ UI 预览失败:", error instanceof Error ? error.message : error);
471
- process.exit(1);
472
- }
473
- }
474
- /**
475
- * stop 命令处理函数
476
- * @param options 命令选项
477
- * @returns 请求完成后退出
478
- * @example
479
- * ms stop --ip 192.168.1.100 --port 9800
480
- */
481
- async function stopCommand(options) {
482
- try {
483
- await (0, project_1.stopOnDevice)(options);
484
- }
485
- catch (error) {
486
- console.error("❌ 停止失败:", error instanceof Error ? error.message : error);
487
- process.exit(1);
488
- }
489
- }
490
- /**
491
- * screenshot 命令处理函数
492
- * @param options 命令选项
493
- * @returns 请求完成后退出
494
- * @example
495
- * ms screenshot --ip 192.168.1.100 --port 9800 --output ./screen.jpg
496
- */
497
- async function screenshotCommand(options) {
498
- try {
499
- const format = options.format ?? "file";
500
- if (format === "base64") {
501
- const base64 = await (0, project_1.getScreenshotBase64OnDevice)(options);
502
- console.log(base64);
503
- return;
504
- }
505
- const image = await (0, project_1.getScreenshotOnDevice)(options);
506
- const outputPath = options.output?.trim()
507
- ? path.resolve(options.output.trim())
508
- : path.resolve(process.cwd(), "screenshot.jpg");
509
- await fsExtra.ensureDir(path.dirname(outputPath));
510
- await fsExtra.writeFile(outputPath, image);
511
- console.log(`✅ 截图成功: ${outputPath} (${image.length} bytes)`);
512
- }
513
- catch (error) {
514
- console.error("❌ 截图失败:", error instanceof Error ? error.message : error);
515
- process.exit(1);
516
- }
517
- }
518
- /**
519
- * source 命令处理函数
520
- * @param options 命令选项
521
- * @returns 请求完成后退出
522
- * @example
523
- * ms source --ip 192.168.1.100 --port 9800 --max-depth 50 --timeout 120 --mode 1
524
- */
525
- async function sourceCommand(options) {
526
- try {
527
- const maxDepthText = (options.maxDepth ?? "50").trim();
528
- const timeoutText = (options.timeout ?? "120").trim();
529
- const modeText = (options.mode ?? "1").trim();
530
- const maxDepth = Number.parseInt(maxDepthText, 10);
531
- const timeout = Number.parseInt(timeoutText, 10);
532
- const mode = Number.parseInt(modeText, 10);
533
- if (!Number.isInteger(maxDepth) || maxDepth < 1) {
534
- throw new Error(`无效 max-depth: ${maxDepthText}`);
535
- }
536
- if (!Number.isInteger(timeout) || timeout < 1) {
537
- throw new Error(`无效 timeout: ${timeoutText}`);
538
- }
539
- if (!Number.isInteger(mode) || (mode !== 1 && mode !== 2 && mode !== 3)) {
540
- throw new Error(`无效 mode: ${modeText}`);
541
- }
542
- const source = await (0, project_1.getSourceOnDevice)(options, maxDepth, timeout, mode);
543
- if (options.output?.trim()) {
544
- const outputPath = path.resolve(options.output.trim());
545
- await fsExtra.ensureDir(path.dirname(outputPath));
546
- await fsExtra.writeFile(outputPath, source, "utf8");
547
- console.log(`✅ 节点已保存: ${outputPath}`);
548
- return;
549
- }
550
- console.log(source);
551
- }
552
- catch (error) {
553
- console.error("❌ 获取节点失败:", error instanceof Error ? error.message : error);
554
- process.exit(1);
555
- }
556
- }
557
- /**
558
- * logs 命令处理函数
559
- * @param options 命令选项
560
- * @returns 请求完成后退出
561
- * @example
562
- * ms logs --ip 192.168.1.100 --port 9800 --limit 200
563
- */
564
- async function logsCommand(options) {
565
- try {
566
- const limit = parseCliInteger(options.limit, 200, "limit", 1, 5000);
567
- const format = parseCliChoice(options.format, "text", ["text", "json"], "format");
568
- const result = await (0, project_1.getCurrentLogLinesOnDevice)(options, limit);
569
- const text = format === "json"
570
- ? (0, tool_utils_1.formatRuntimeJsonText)(result)
571
- : result.lines.length > 0
572
- ? result.lines.join("\n")
573
- : "无日志";
574
- await writeOrPrintText(text, options.output, "日志已保存");
575
- if (format === "text" && result.hasMore && !options.output?.trim()) {
576
- console.error(`还有更早日志,可增大 --limit,当前最多 ${limit} 行。`);
577
- }
578
- }
579
- catch (error) {
580
- console.error("❌ 获取日志失败:", error instanceof Error ? error.message : error);
581
- process.exit(1);
582
- }
583
- }
584
- /**
585
- * docs paths 命令处理函数
586
- * @param options 命令选项
587
- * @returns 请求完成后退出
588
- * @example
589
- * ms docs paths
590
- */
591
- async function docsPathsCommand(options) {
592
- try {
593
- await writeOrPrintText(formatCliDocsPathsText(), options.output, "文档路径已保存");
594
- }
595
- catch (error) {
596
- console.error("❌ 输出文档路径失败:", error instanceof Error ? error.message : error);
597
- process.exit(1);
598
- }
599
- }
600
- /**
601
- * http-call 命令处理函数
602
- * @param options 命令选项
603
- * @returns 请求完成后退出
604
- * @example
605
- * ms http-call --ip 192.168.1.100 --method GET --path /api/status
606
- */
607
- async function httpCallCommand(options) {
608
- try {
609
- const target = parseDeviceHttpTarget(options);
610
- const method = parseHttpApiMethodOption(options.method);
611
- const apiPath = normalizeCliHttpApiPath(options.path ?? "");
612
- const entry = await (0, httpapi_docs_service_1.findHttpApiDocEntry)(apiPath, method);
613
- if (!entry) {
614
- throw new Error(`HTTP API 文档中不存在该接口: ${method} ${apiPath}。请先读取 docs paths 返回的 HTTP API 文档路径。`);
615
- }
616
- const query = parseJsonRecordOption(options.query, "query");
617
- const body = parseJsonBodyOption(options.body);
618
- if (method === "GET" && body !== undefined) {
619
- throw new Error("GET 接口不能传 body,请使用 --query。");
620
- }
621
- const responseFormat = parseCliChoice(options.responseFormat, "json", ["json", "text"], "response-format");
622
- const timeoutMs = parseCliInteger(options.timeoutMs, 30000, "timeout-ms", 1000, 600000);
623
- const url = new URL(`http://${target.ip}:${target.port}${apiPath}`);
624
- appendCliQueryParams(url, query);
625
- const controller = new AbortController();
626
- const timeout = setTimeout(() => controller.abort(), timeoutMs);
627
- try {
628
- const { headers, requestBody } = createCliHttpRequestBody(body);
629
- const response = await fetch(url.toString(), {
630
- method,
631
- signal: controller.signal,
632
- headers,
633
- body: requestBody,
634
- });
635
- const responseText = await readCliHttpApiResponseText(response, responseFormat);
636
- const text = [
637
- `HTTP API 调用${response.ok ? "成功" : "失败"}: ${method} ${apiPath}`,
638
- `设备: ${target.label}`,
639
- `文档: ${entry.title} (lines ${entry.startLine}-${entry.endLine})`,
640
- `状态码: ${response.status}`,
641
- "",
642
- responseText,
643
- ].join("\n");
644
- await writeOrPrintText(text, options.output, "HTTP API 响应已保存");
645
- if (!response.ok) {
646
- process.exit(1);
647
- }
648
- }
649
- finally {
650
- clearTimeout(timeout);
651
- }
652
- }
653
- catch (error) {
654
- const message = error instanceof Error && error.name === "AbortError"
655
- ? "请求超时"
656
- : error instanceof Error
657
- ? error.message
658
- : String(error);
659
- console.error("❌ HTTP API 调用失败:", message);
660
- process.exit(1);
661
- }
662
- }
663
- /**
664
- * ocr 命令处理函数
665
- * @param options 命令选项
666
- * @returns 请求完成后退出
667
- * @example
668
- * ms ocr --ip 192.168.1.100 --mode recognize
669
- */
670
- async function ocrCommand(options) {
671
- try {
672
- const target = parseDeviceHttpTarget(options);
673
- const engine = parseCliChoice(options.engine, "appleocr", ["appleocr", "paddleocr"], "engine");
674
- const mode = parseCliChoice(options.mode, "recognize", ["recognize", "numbers", "findText"], "mode");
675
- const input = options.input?.trim() || "screen";
676
- const x = parseCliInteger(options.x, 0, "x", 0);
677
- const y = parseCliInteger(options.y, 0, "y", 0);
678
- const ex = parseCliInteger(options.ex, 0, "ex", 0);
679
- const ey = parseCliInteger(options.ey, 0, "ey", 0);
680
- const textItems = [
681
- ...(parseStringArrayOption(options.texts, "texts") ?? []),
682
- ...(options.text ?? []),
683
- ];
684
- const texts = textItems.length > 0 ? textItems : undefined;
685
- const languages = parseAppleOcrLanguages(options.languages);
686
- const confidenceThreshold = parseCliNumber(options.confidenceThreshold, 0.6, "confidence-threshold", 0, 1);
687
- const paddleModel = options.paddleModel === undefined
688
- ? undefined
689
- : parseCliChoice(options.paddleModel, ocr_tools_1.PADDLE_OCR_MODELS[0], ocr_tools_1.PADDLE_OCR_MODELS, "paddle-model");
690
- const maxSideLen = options.maxSideLen === undefined
691
- ? undefined
692
- : parseCliInteger(options.maxSideLen, 0, "max-side-len", 0);
693
- const useGpu = options.useGpu === true ? true : undefined;
694
- const timeoutMs = parseCliInteger(options.timeoutMs, 60000, "timeout-ms", 1000, 600000);
695
- const format = parseCliChoice(options.format, "summary", ["summary", "json"], "format");
696
- const result = await (0, ocr_tools_1.runOcrRecognitionOnDevice)(target, {
697
- engine,
698
- mode,
699
- input,
700
- x,
701
- y,
702
- ex,
703
- ey,
704
- texts,
705
- languages,
706
- confidenceThreshold,
707
- exactMatch: options.exactMatch === true,
708
- paddleModel,
709
- maxSideLen,
710
- useGpu,
711
- outputPath: format === "summary" ? options.output : undefined,
712
- timeoutMs,
713
- });
714
- const text = format === "json" ? (0, tool_utils_1.formatRuntimeJsonText)(result.body) : result.text;
715
- if (format === "json") {
716
- await writeOrPrintText(text, options.output, "OCR 结果已保存");
717
- }
718
- else {
719
- console.log([
720
- text,
721
- "",
722
- `device: ${target.label}`,
723
- `engine: ${engine}`,
724
- `mode: ${mode}`,
725
- `input: ${input}`,
726
- `region: x=${x}, y=${y}, ex=${ex}, ey=${ey}`,
727
- ...(engine === "paddleocr" && paddleModel
728
- ? [`paddleModel: ${paddleModel}`]
729
- : []),
730
- ...(engine === "paddleocr" && maxSideLen !== undefined
731
- ? [`maxSideLen: ${maxSideLen}`]
732
- : []),
733
- `status: ${result.status}`,
734
- ].join("\n"));
735
- }
736
- if (result.body.success === false) {
737
- process.exit(1);
738
- }
739
- }
740
- catch (error) {
741
- console.error("❌ OCR 执行失败:", error instanceof Error ? error.message : error);
742
- process.exit(1);
743
- }
744
- }
745
- /**
746
- * image-crop 命令处理函数
747
- * @param options 命令选项
748
- * @returns 请求完成后退出
749
- * @example
750
- * ms image-crop --image ./screen.jpg --x 0 --y 0 --width 100 --height 100
751
- */
752
- async function imageCropCommand(options) {
753
- try {
754
- const imagePath = options.image?.trim();
755
- if (!imagePath) {
756
- throw new Error("请通过 --image 指定输入图片路径。");
757
- }
758
- const x = parseCliInteger(options.x, 0, "x", 0);
759
- const y = parseCliInteger(options.y, 0, "y", 0);
760
- const width = parseCliInteger(options.width, 1, "width", 1);
761
- const height = parseCliInteger(options.height, 1, "height", 1);
762
- const text = await (0, image_tools_1.cropLocalImage)(imagePath, x, y, width, height, options.output);
763
- console.log(text);
764
- }
765
- catch (error) {
766
- console.error("❌ 图片裁切失败:", error instanceof Error ? error.message : error);
767
- process.exit(1);
768
- }
769
- }
770
- /**
771
- * screen-crop 命令处理函数
772
- * @param options 命令选项
773
- * @returns 请求完成后退出
774
- * @example
775
- * ms screen-crop -i 192.168.1.100 --x 0 --y 0 --width 100 --height 100
776
- */
777
- async function screenCropCommand(options) {
778
- try {
779
- const target = parseDeviceHttpTarget(options);
780
- const x = parseCliInteger(options.x, 0, "x", 0);
781
- const y = parseCliInteger(options.y, 0, "y", 0);
782
- const width = parseCliInteger(options.width, 1, "width", 1);
783
- const height = parseCliInteger(options.height, 1, "height", 1);
784
- const text = await (0, image_tools_1.cropDeviceScreen)(target, x, y, width, height, options.output);
785
- console.log(text);
786
- }
787
- catch (error) {
788
- console.error("❌ 屏幕裁图失败:", error instanceof Error ? error.message : error);
789
- process.exit(1);
790
- }
791
- }
792
- /**
793
- * image-pick-color 命令处理函数
794
- * @param options 命令选项
795
- * @returns 请求完成后退出
796
- * @example
797
- * ms image-pick-color --image ./screen.jpg --x 10 --y 10
798
- */
799
- async function imagePickColorCommand(options) {
800
- try {
801
- const imagePath = options.image?.trim();
802
- if (!imagePath) {
803
- throw new Error("请通过 --image 指定输入图片路径。");
804
- }
805
- const x = parseCliInteger(options.x, 0, "x", 0);
806
- const y = parseCliInteger(options.y, 0, "y", 0);
807
- const radius = parseCliInteger(options.radius, 0, "radius", 0, 20);
808
- const text = await (0, image_tools_1.pickLocalImageColor)(imagePath, x, y, radius);
809
- console.log(text);
810
- }
811
- catch (error) {
812
- console.error("❌ 图片取色失败:", error instanceof Error ? error.message : error);
813
- process.exit(1);
814
- }
815
- }
816
- /**
817
- * screen-pick-color 命令处理函数
818
- * @param options 命令选项
819
- * @returns 请求完成后退出
820
- * @example
821
- * ms screen-pick-color -i 192.168.1.100 --x 10 --y 10
822
- */
823
- async function screenPickColorCommand(options) {
824
- try {
825
- const target = parseDeviceHttpTarget(options);
826
- const x = parseCliInteger(options.x, 0, "x", 0);
827
- const y = parseCliInteger(options.y, 0, "y", 0);
828
- const radius = parseCliInteger(options.radius, 0, "radius", 0, 20);
829
- const text = await (0, image_tools_1.pickDeviceScreenColor)(target, x, y, radius);
830
- console.log(text);
831
- }
832
- catch (error) {
833
- console.error("❌ 屏幕取色失败:", error instanceof Error ? error.message : error);
834
- process.exit(1);
835
- }
836
- }
837
- /**
838
- * image-transparent 命令处理函数
839
- * @param options 命令选项
840
- * @returns 请求完成后退出
841
- * @example
842
- * ms image-transparent --image ./button.png --transparent-color "#ffffff"
843
- */
844
- async function imageTransparentCommand(options) {
845
- try {
846
- const imagePath = options.image?.trim();
847
- if (!imagePath) {
848
- throw new Error("请通过 --image 指定输入图片路径。");
849
- }
850
- const sampleCorner = parseCliChoice(options.sampleCorner, "topLeft", ["topLeft", "topRight", "bottomLeft", "bottomRight"], "sample-corner");
851
- const tolerance = parseCliInteger(options.tolerance, 24, "tolerance", 0, 441);
852
- const text = await (0, image_tools_1.makeLocalImageTransparent)(imagePath, options.output, options.transparentColor, sampleCorner, tolerance);
853
- console.log(text);
854
- }
855
- catch (error) {
856
- console.error("❌ 透明图制作失败:", error instanceof Error ? error.message : error);
857
- process.exit(1);
858
- }
859
- }
860
- /**
861
- * ws-start 命令处理函数
862
- * @param options 命令选项
863
- * @returns 常驻直到收到退出信号
864
- * @example
865
- * ms ws-start --ws-port 31111
866
- */
867
- async function wsStartCommand(options) {
868
- try {
869
- const wsPortText = (options.wsPort ?? "31111").trim();
870
- const wsPort = Number.parseInt(wsPortText, 10);
871
- if (!Number.isInteger(wsPort) || wsPort < 1 || wsPort > 65535) {
872
- throw new Error(`无效 ws 端口: ${wsPortText}`);
873
- }
874
- const ws = ws_manager_1.WSManager.get();
875
- await ws.start(wsPort);
876
- /**
877
- * 写入 WS 运行状态到临时文件,供 ws-status 命令跨进程读取
878
- */
879
- const writeStatus = async () => {
880
- const payload = {
881
- pid: process.pid,
882
- port: wsPort,
883
- address: ws.getAddress(),
884
- startedAt: Date.now(),
885
- connected: ws.isClientConnected(),
886
- clientIp: ws.getConnectedClientIp(),
887
- };
888
- await fsExtra.writeJSON(WS_PID_FILE, payload);
889
- };
890
- await writeStatus();
891
- const statusTimer = setInterval(() => {
892
- writeStatus().catch(() => {
893
- // ignore periodic write errors
894
- });
895
- }, 1000);
896
- await fsExtra.writeJSON(WS_PID_FILE, {
897
- pid: process.pid,
898
- port: wsPort,
899
- address: ws.getAddress(),
900
- startedAt: Date.now(),
901
- connected: ws.isClientConnected(),
902
- clientIp: ws.getConnectedClientIp(),
903
- });
904
- await new Promise((resolve) => {
905
- const shutdown = async () => {
906
- process.off("SIGINT", shutdown);
907
- process.off("SIGTERM", shutdown);
908
- clearInterval(statusTimer);
909
- await ws.stop();
910
- await fsExtra.remove(WS_PID_FILE);
911
- resolve();
912
- };
913
- process.on("SIGINT", shutdown);
914
- process.on("SIGTERM", shutdown);
915
- });
916
- }
917
- catch (error) {
918
- console.error("❌ WS 服务启动失败:", error instanceof Error ? error.message : error);
919
- process.exit(1);
920
- }
921
- }
922
- /**
923
- * ws-stop 命令处理函数
924
- * @returns 请求完成后退出
925
- * @example
926
- * ms ws-stop
927
- */
928
- async function wsStopCommand() {
929
- try {
930
- if (!(await fsExtra.pathExists(WS_PID_FILE))) {
931
- console.log("WS 服务未运行");
932
- return;
933
- }
934
- const info = await fsExtra.readJSON(WS_PID_FILE);
935
- const pid = Number(info.pid);
936
- if (!Number.isInteger(pid) || pid <= 0) {
937
- await fsExtra.remove(WS_PID_FILE);
938
- throw new Error("WS PID 文件损坏");
939
- }
940
- try {
941
- process.kill(pid, "SIGTERM");
942
- console.log(`✅ 已发送停止信号到 WS 进程: ${pid}`);
943
- }
944
- catch {
945
- console.log("WS 进程不存在,清理 PID 文件");
946
- }
947
- await fsExtra.remove(WS_PID_FILE);
948
- }
949
- catch (error) {
950
- console.error("❌ WS 服务停止失败:", error instanceof Error ? error.message : error);
951
- process.exit(1);
952
- }
953
- }
954
- /**
955
- * ws-status 命令处理函数
956
- * @returns 请求完成后退出
957
- * @example
958
- * ms ws-status
959
- */
960
- async function wsStatusCommand() {
961
- if (!(await fsExtra.pathExists(WS_PID_FILE))) {
962
- console.log(JSON.stringify({
963
- status: "stopped",
964
- running: false,
965
- pid: null,
966
- address: null,
967
- connected: false,
968
- clientIp: null,
969
- }));
970
- return;
971
- }
972
- const info = await fsExtra.readJSON(WS_PID_FILE);
973
- const pid = Number(info.pid);
974
- const address = String(info.address || "unknown");
975
- const connected = Boolean(info.connected);
976
- const clientIp = info.clientIp === null || info.clientIp === undefined
977
- ? null
978
- : String(info.clientIp);
979
- let running = false;
980
- try {
981
- process.kill(pid, 0);
982
- running = true;
983
- }
984
- catch {
985
- running = false;
986
- }
987
- if (!running) {
988
- await fsExtra.remove(WS_PID_FILE);
989
- console.log(JSON.stringify({
990
- status: "stopped",
991
- running: false,
992
- pid: null,
993
- address: null,
994
- connected: false,
995
- clientIp: null,
996
- }));
997
- return;
998
- }
999
- console.log(JSON.stringify({
1000
- status: "running",
1001
- running: true,
1002
- pid,
1003
- address,
1004
- connected,
1005
- clientIp: connected ? clientIp || "unknown" : null,
1006
- }));
1007
- }
1008
- /**
1009
- * 生成顶层 help 的详细命令参数说明
1010
- * @returns 返回格式化后的帮助文本
1011
- * @example
1012
- * renderDetailedCommandHelp()
1013
- */
1014
- function renderDetailedCommandHelp() {
1015
- const lines = [];
1016
- lines.push("");
1017
- lines.push("详细命令参数:");
1018
- const commands = commander_1.program.commands.filter((cmd) => cmd.name() !== "help");
1019
- for (const cmd of commands) {
1020
- lines.push(` ${cmd.name()}:`);
1021
- if (cmd.options.length === 0) {
1022
- lines.push(" (无额外参数)");
1023
- continue;
1024
- }
1025
- for (const opt of cmd.options) {
1026
- const defaultText = opt.defaultValue !== undefined && opt.defaultValue !== null
1027
- ? ` (默认: ${String(opt.defaultValue)})`
1028
- : "";
1029
- const desc = opt.description
1030
- ? ` ${opt.description}${defaultText}`
1031
- : defaultText;
1032
- lines.push(` ${opt.flags}${desc}`);
1033
- }
1034
- }
1035
- lines.push("");
1036
- return lines.join("\n");
1037
- }
1038
- // 配置 CLI 程序
1039
- commander_1.program.name("ms-cli").description("快点JS 构建工具").version(version_1.PACKAGE_VERSION);
1040
- commander_1.program.addHelpText("after", renderDetailedCommandHelp);
1041
- // 构建命令
1042
- commander_1.program
1043
- .command("build")
1044
- .description("构建 快点JS 项目")
1045
- .option("-d, --dev", "开发模式构建 (包含 source map)")
1046
- .action(buildCommand);
1047
- // 打包命令
1048
- commander_1.program
1049
- .command("package")
1050
- .description("生产构建并加密 ms.msbundle")
1051
- .action(packageCommand);
1052
- // 运行命令
1053
- commander_1.program
1054
- .command("run")
1055
- .description("在设备上运行项目")
1056
- .option("-i, --ip <ip>", "设备 IP 地址(transport=http 时必填)")
1057
- .option("--port <port>", "设备端口", "9800")
1058
- .option("-t, --transport <transport>", "传输方式: http|ws", "http")
1059
- .option("--ws-port <wsPort>", "WS 服务端口", "31111")
1060
- .option("--ws-wait-ms <wsWaitMs>", "WS 等待设备连接时间(毫秒)", "30000")
1061
- .action(runCommand);
1062
- // UI 预览命令
1063
- commander_1.program
1064
- .command("run-ui")
1065
- .description("在设备上预览 UI")
1066
- .option("-i, --ip <ip>", "设备 IP 地址(transport=http 时必填)")
1067
- .option("--port <port>", "设备端口", "9800")
1068
- .option("-t, --transport <transport>", "传输方式: http|ws", "http")
1069
- .option("--ws-port <wsPort>", "WS 服务端口", "31111")
1070
- .option("--ws-wait-ms <wsWaitMs>", "WS 等待设备连接时间(毫秒)", "30000")
1071
- .action(runUICommand);
1072
- // 停止命令
1073
- commander_1.program
1074
- .command("stop")
1075
- .description("停止设备上的项目")
1076
- .option("-i, --ip <ip>", "设备 IP 地址(transport=http 时必填)")
1077
- .option("--port <port>", "设备端口", "9800")
1078
- .option("-t, --transport <transport>", "传输方式: http|ws", "http")
1079
- .option("--ws-port <wsPort>", "WS 服务端口", "31111")
1080
- .option("--ws-wait-ms <wsWaitMs>", "WS 等待设备连接时间(毫秒)", "30000")
1081
- .action(stopCommand);
1082
- // 截图命令
1083
- commander_1.program
1084
- .command("screenshot")
1085
- .description("获取设备截图(JPG)")
1086
- .option("-i, --ip <ip>", "设备 IP 地址(transport=http 时必填)")
1087
- .option("--port <port>", "设备端口", "9800")
1088
- .option("-t, --transport <transport>", "传输方式: http|ws", "http")
1089
- .option("--ws-port <wsPort>", "WS 服务端口", "31111")
1090
- .option("--ws-wait-ms <wsWaitMs>", "WS 等待设备连接时间(毫秒)", "30000")
1091
- .option("--format <format>", "返回格式: file|base64", "file")
1092
- .option("--output <output>", "截图输出文件路径(format=file 时生效)")
1093
- .action(screenshotCommand);
1094
- // 节点命令
1095
- commander_1.program
1096
- .command("source")
1097
- .description("获取设备当前页面节点 XML")
1098
- .option("-i, --ip <ip>", "设备 IP 地址(transport=http 时必填)")
1099
- .option("--port <port>", "设备端口", "9800")
1100
- .option("-t, --transport <transport>", "传输方式: http|ws", "http")
1101
- .option("--ws-port <wsPort>", "WS 服务端口", "31111")
1102
- .option("--ws-wait-ms <wsWaitMs>", "WS 等待设备连接时间(毫秒)", "30000")
1103
- .option("--max-depth <maxDepth>", "节点最大深度", "50")
1104
- .option("--timeout <timeout>", "节点抓取超时秒数", "120")
1105
- .option("--mode <mode>", "节点抓取模式: 1=模式1, 2=模式2, 3=模式3", "1")
1106
- .option("--output <output>", "节点 XML 输出文件路径(可选)")
1107
- .action(sourceCommand);
1108
- // 日志命令
1109
- commander_1.program
1110
- .command("logs")
1111
- .description("获取设备当前日志最新行")
1112
- .option("-i, --ip <ip>", "设备 IP 地址")
1113
- .option("--port <port>", "设备端口", "9800")
1114
- .option("--limit <limit>", "读取最新日志行数,范围 1..5000", "200")
1115
- .option("--format <format>", "返回格式: text|json", "text")
1116
- .option("--output <output>", "日志输出文件路径(可选)")
1117
- .action(logsCommand);
1118
- // OCR 命令
1119
- commander_1.program
1120
- .command("ocr")
1121
- .description("在设备上执行 OCR 识别")
1122
- .option("-i, --ip <ip>", "设备 IP 地址")
1123
- .option("--port <port>", "设备端口", "9800")
1124
- .option("--engine <engine>", "OCR 引擎: appleocr|paddleocr", "appleocr")
1125
- .option("--mode <mode>", "OCR 模式: recognize|numbers|findText", "recognize")
1126
- .option("--input <input>", '输入源,默认 "screen"', "screen")
1127
- .option("--x <x>", "区域左上角 x", "0")
1128
- .option("--y <y>", "区域左上角 y", "0")
1129
- .option("--ex <ex>", "区域右下角 x;全屏可传 0", "0")
1130
- .option("--ey <ey>", "区域右下角 y;全屏可传 0", "0")
1131
- .option("--text <text>", "findText 文本,可重复", collectStringOption, [])
1132
- .option("--texts <texts>", "findText 文本数组,支持 JSON 数组或逗号分隔")
1133
- .option("--languages <languages>", "Apple OCR 语言,支持 JSON 数组或逗号分隔")
1134
- .option("--exact-match", "findText 要求完整匹配文本")
1135
- .option("--confidence-threshold <confidenceThreshold>", "PaddleOCR 置信度阈值", "0.6")
1136
- .option("--paddle-model <paddleModel>", "仅 paddleocr:切换模型 ppocr-v6-tiny|ppocr-v6-small|ppocr-v5")
1137
- .option("--max-side-len <maxSideLen>", "仅 paddleocr:检测最大边长,0 回落默认 768")
1138
- .option("--use-gpu", "仅 paddleocr:切换模型时启用 GPU")
1139
- .option("--timeout-ms <timeoutMs>", "请求超时时间(毫秒)", "60000")
1140
- .option("--format <format>", "返回格式: summary|json", "summary")
1141
- .option("--output <output>", "OCR 输出文件路径(可选)")
1142
- .action(ocrCommand);
1143
- // 本地图片裁切命令
1144
- commander_1.program
1145
- .command("image-crop")
1146
- .description("裁切本地图片并输出 PNG")
1147
- .requiredOption("--image <image>", "输入图片路径")
1148
- .option("--x <x>", "裁切起点 x", "0")
1149
- .option("--y <y>", "裁切起点 y", "0")
1150
- .requiredOption("--width <width>", "裁切宽度")
1151
- .requiredOption("--height <height>", "裁切高度")
1152
- .option("--output <output>", "输出 PNG 路径(可选)")
1153
- .action(imageCropCommand);
1154
- // 设备截图裁切命令
1155
- commander_1.program
1156
- .command("screen-crop")
1157
- .description("从设备截图中裁切 PNG")
1158
- .option("-i, --ip <ip>", "设备 IP 地址")
1159
- .option("--port <port>", "设备端口", "9800")
1160
- .option("--x <x>", "裁切起点 x", "0")
1161
- .option("--y <y>", "裁切起点 y", "0")
1162
- .requiredOption("--width <width>", "裁切宽度")
1163
- .requiredOption("--height <height>", "裁切高度")
1164
- .option("--output <output>", "输出 PNG 路径(可选)")
1165
- .action(screenCropCommand);
1166
- // 本地图片取色命令
1167
- commander_1.program
1168
- .command("image-pick-color")
1169
- .description("读取本地图片指定坐标颜色")
1170
- .requiredOption("--image <image>", "输入图片路径")
1171
- .option("--x <x>", "目标坐标 x", "0")
1172
- .option("--y <y>", "目标坐标 y", "0")
1173
- .option("--radius <radius>", "采样半径,范围 0..20", "0")
1174
- .action(imagePickColorCommand);
1175
- // 设备截图取色命令
1176
- commander_1.program
1177
- .command("screen-pick-color")
1178
- .description("读取设备截图指定坐标颜色")
1179
- .option("-i, --ip <ip>", "设备 IP 地址")
1180
- .option("--port <port>", "设备端口", "9800")
1181
- .option("--x <x>", "目标坐标 x", "0")
1182
- .option("--y <y>", "目标坐标 y", "0")
1183
- .option("--radius <radius>", "采样半径,范围 0..20", "0")
1184
- .action(screenPickColorCommand);
1185
- // 本地图片透明化命令
1186
- commander_1.program
1187
- .command("image-transparent")
1188
- .description("将图片中接近指定颜色或角落背景色的像素设为透明")
1189
- .requiredOption("--image <image>", "输入图片路径")
1190
- .option("--output <output>", "输出 PNG 路径(可选)")
1191
- .option("--transparent-color <transparentColor>", "透明化颜色,格式 #RRGGBB")
1192
- .option("--sample-corner <sampleCorner>", "未传 transparent-color 时采样角落: topLeft|topRight|bottomLeft|bottomRight", "topLeft")
1193
- .option("--tolerance <tolerance>", "RGB 欧氏距离容差,范围 0..441", "24")
1194
- .action(imageTransparentCommand);
1195
- // API 文档命令
1196
- const docsCommand = commander_1.program
1197
- .command("docs")
1198
- .description("显示 KuaiJS 本地文档路径");
1199
- docsCommand
1200
- .command("paths")
1201
- .description("显示 KuaiJS 本地文档路径")
1202
- .option("--output <output>", "输出文件路径(可选)")
1203
- .action(docsPathsCommand);
1204
- // HTTP API 调用命令
1205
- commander_1.program
1206
- .command("http-call")
1207
- .description("按 HTTP API 文档声明调用设备接口")
1208
- .option("-i, --ip <ip>", "设备 IP 地址")
1209
- .option("--port <port>", "设备端口", "9800")
1210
- .requiredOption("--method <method>", "HTTP 方法: GET|POST")
1211
- .requiredOption("--path <path>", "HTTP API 相对路径,例如 /api/status")
1212
- .option("--query <query>", "JSON 对象 query 参数")
1213
- .option("--body <body>", "JSON 请求体;普通字符串会作为 JSON 字符串提交")
1214
- .option("--response-format <responseFormat>", "响应格式: json|text", "json")
1215
- .option("--timeout-ms <timeoutMs>", "请求超时时间(毫秒)", "30000")
1216
- .option("--output <output>", "响应输出文件路径(可选)")
1217
- .action(httpCallCommand);
1218
- // WS 服务启动命令
1219
- commander_1.program
1220
- .command("ws-start")
1221
- .description("启动 CLI 内置 WS 服务")
1222
- .option("--ws-port <wsPort>", "WS 服务端口", "31111")
1223
- .action(wsStartCommand);
1224
- // WS 服务停止命令
1225
- commander_1.program
1226
- .command("ws-stop")
1227
- .description("停止 CLI 内置 WS 服务")
1228
- .action(wsStopCommand);
1229
- // WS 服务状态命令
1230
- commander_1.program
1231
- .command("ws-status")
1232
- .description("查看 CLI 内置 WS 服务状态")
1233
- .action(wsStatusCommand);
1234
- // 解析命令行参数
1235
- commander_1.program.parse();
2
+ "use strict";var R=exports&&exports.__createBinding||(Object.create?(function(e,t,r,o){o===void 0&&(o=r);var n=Object.getOwnPropertyDescriptor(t,r);(!n||("get"in n?!t.__esModule:n.writable||n.configurable))&&(n={enumerable:!0,get:function(){return t[r]}}),Object.defineProperty(e,o,n)}):(function(e,t,r,o){o===void 0&&(o=r),e[o]=t[r]})),J=exports&&exports.__setModuleDefault||(Object.create?(function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}):function(e,t){e.default=t}),N=exports&&exports.__importStar||(function(){var e=function(t){return e=Object.getOwnPropertyNames||function(r){var o=[];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(o[o.length]=n);return o},e(t)};return function(t){if(t&&t.__esModule)return t;var r={};if(t!=null)for(var o=e(t),n=0;n<o.length;n++)o[n]!=="default"&&R(r,t,o[n]);return J(r,t),r}})();Object.defineProperty(exports,"__esModule",{value:!0});const c=require("commander"),M=N(require("os")),m=N(require("path")),G=require("./build"),p=N(require("fs-extra")),h=require("./project"),W=require("./packager"),H=require("./ws-manager"),k=require("./version"),A=require("./mcp/docs-service"),L=require("./mcp/httpapi-docs-service"),j=require("./mcp/tool-utils"),B=require("./mcp/types"),x=require("./mcp/ocr-tools"),P=require("./mcp/image-tools"),d=m.join(M.tmpdir(),"ms-cli-ws-server.json");async function U(e){try{const t=m.join(e,"package.json"),r=m.join(e,"scripts"),[o,n]=await Promise.all([p.pathExists(t),p.pathExists(r)]);return o&&n}catch{return!1}}async function C(e){await U(e)||(console.error("\u274C \u9519\u8BEF: \u5F53\u524D\u76EE\u5F55\u4E0D\u662F\u6709\u6548\u7684 \u5FEB\u70B9JS \u9879\u76EE"),console.error("\u8BF7\u786E\u4FDD\u76EE\u5F55\u5305\u542B\u4EE5\u4E0B\u6587\u4EF6:"),console.error(" - package.json"),console.error(" - scripts/ \u76EE\u5F55"),process.exit(1))}function a(e,t,r,o,n){const i=(e??String(t)).trim(),s=Number.parseInt(i,10);if(!Number.isInteger(s)||s<o||n!==void 0&&s>n)throw new Error(`\u65E0\u6548 ${r}: ${i}\uFF0C\u5141\u8BB8\u8303\u56F4 ${o}${n===void 0?"+":`..${n}`}`);return s}function F(e,t,r,o,n){const i=(e??String(t)).trim(),s=Number.parseFloat(i);if(!Number.isFinite(s)||s<o||s>n)throw new Error(`\u65E0\u6548 ${r}: ${i}\uFF0C\u5141\u8BB8\u8303\u56F4 ${o}..${n}`);return s}function g(e,t,r,o){const n=(e??t).trim();if(!r.includes(n))throw new Error(`\u65E0\u6548 ${o}: ${n}\uFF0C\u53EF\u9009\u503C: ${r.join("|")}`);return n}function V(e){const t=e?.trim().toUpperCase();return g(t,"GET",["GET","POST"],"method")}function E(e){const t=e.ip?.trim();if(!t)throw new Error("\u8BF7\u901A\u8FC7 --ip \u6307\u5B9A\u8BBE\u5907 IP \u5730\u5740");const r=a(e.port,9800,"port",1,65535);return{ip:t,port:String(r),label:`${t}:${r}`}}async function $(e,t,r){if(t?.trim()){const o=m.resolve(t.trim());await p.ensureDir(m.dirname(o)),await p.writeFile(o,e,"utf8"),console.log(`\u2705 ${r}: ${o}`);return}console.log(e)}function z(){const e=["js","js_zh","python"];return["KuaiJS \u6587\u6863\u8DEF\u5F84:",`docsRoot: ${(0,A.getDocsRootDir)()}`,...e.map(t=>`${t}: ${(0,A.getDocsDirByLanguage)(t)} (${B.DOC_LANGUAGE_LABELS[t]})`),`httpApi: ${(0,L.getHttpApiDocPath)()}`,"\u8BF4\u660E: \u76F4\u63A5\u8BFB\u53D6\u4E0A\u8FF0 markdown \u8DEF\u5F84\u3002"].join(`
3
+ `)}function K(e,t){return t.push(e),t}function q(e,t){const r=e?.trim();if(!r)return;if(r.startsWith("[")){const n=JSON.parse(r);if(!Array.isArray(n)||!n.every(i=>typeof i=="string"&&i.trim()))throw new Error(`${t} \u5FC5\u987B\u662F\u5B57\u7B26\u4E32\u6570\u7EC4\u3002`);return n.map(i=>i.trim())}const o=r.split(",").map(n=>n.trim()).filter(n=>n.length>0);if(o.length===0)throw new Error(`${t} \u4E0D\u80FD\u4E3A\u7A7A\u3002`);return o}function X(e){const t=q(e,"languages");if(!t)return;const r=new Set(x.APPLE_OCR_LANGUAGES),o=t.filter(n=>!r.has(n));if(o.length>0)throw new Error(`\u65E0\u6548 languages: ${o.join(", ")}\uFF0C\u53EF\u9009\u503C: ${x.APPLE_OCR_LANGUAGES.join(", ")}`);return t}function Q(e,t){const r=e?.trim();if(!r)return;const o=JSON.parse(r);if(!o||typeof o!="object"||Array.isArray(o)||!Object.values(o).every(n=>typeof n=="string"||typeof n=="number"||typeof n=="boolean"||n===null))throw new Error(`${t} \u5FC5\u987B\u662F JSON \u5BF9\u8C61\uFF0C\u4E14\u503C\u53EA\u80FD\u662F string/number/boolean/null\u3002`);return o}function Y(e){const t=e?.trim();if(!(t===void 0||t.length===0))try{return JSON.parse(t)}catch{return e}}function Z(e){const t=e.trim();if(/^https?:\/\//i.test(t))throw new Error("path \u53EA\u80FD\u4F20 HTTP API \u76F8\u5BF9\u8DEF\u5F84\uFF0C\u4E0D\u80FD\u4F20\u5B8C\u6574 URL\u3002");if(!t.startsWith("/"))throw new Error("path \u5FC5\u987B\u4EE5 / \u5F00\u5934\uFF0C\u4F8B\u5982 /api/status\u3002");if(t.includes("?"))throw new Error("path \u4E0D\u80FD\u5305\u542B query string\uFF0C\u8BF7\u4F7F\u7528 --query \u4F20\u9012\u67E5\u8BE2\u53C2\u6570\u3002");return t}function tt(e,t){if(t)for(const[r,o]of Object.entries(t))o!==null&&e.searchParams.set(r,String(o))}function et(e){return e===void 0?{}:{headers:{"Content-Type":"application/json"},requestBody:JSON.stringify(e)}}async function ot(e,t){const r=await e.text();return t==="text"?r:(0,j.formatRuntimeJsonText)(JSON.parse(r))}async function rt(e){try{const t=process.cwd(),r=e.dev||!1;console.log(`\u{1F50D} \u68C0\u67E5\u9879\u76EE\u76EE\u5F55: ${t}`),await C(t),console.log(`\u{1F680} \u5F00\u59CB\u6784\u5EFA\u9879\u76EE (${r?"\u5F00\u53D1\u6A21\u5F0F":"\u751F\u4EA7\u6A21\u5F0F"})...`),await(0,G.buildAll)(r,t),console.log("\u2705 \u6784\u5EFA\u5B8C\u6210!")}catch(t){console.error("\u274C \u6784\u5EFA\u5931\u8D25:",t instanceof Error?t.message:t),process.exit(1)}}async function nt(){try{const e=process.cwd();console.log(`\u{1F50D} \u68C0\u67E5\u9879\u76EE\u76EE\u5F55: ${e}`),await C(e),console.log("\u{1F4E6} \u5F00\u59CB\u6253\u5305\u751F\u4EA7\u6784\u5EFA\u5E76\u52A0\u5BC6 bundle...");const t=await(0,W.packageProject)(e);console.log(`\u2705 \u6253\u5305\u5B8C\u6210: ${t}`)}catch(e){console.error("\u274C \u6253\u5305\u5931\u8D25:",e instanceof Error?e.message:e),process.exit(1)}}async function it(e){try{const t=process.cwd();await C(t),await(0,h.runOnDevice)(e)}catch(t){console.error("\u274C \u8FD0\u884C\u5931\u8D25:",t instanceof Error?t.message:t),process.exit(1)}}async function st(e){try{const t=process.cwd();await C(t),await(0,h.runUIOnDevice)(e)}catch(t){console.error("\u274C UI \u9884\u89C8\u5931\u8D25:",t instanceof Error?t.message:t),process.exit(1)}}async function at(e){try{await(0,h.stopOnDevice)(e)}catch(t){console.error("\u274C \u505C\u6B62\u5931\u8D25:",t instanceof Error?t.message:t),process.exit(1)}}async function ct(e){try{if((e.format??"file")==="base64"){const n=await(0,h.getScreenshotBase64OnDevice)(e);console.log(n);return}const r=await(0,h.getScreenshotOnDevice)(e),o=e.output?.trim()?m.resolve(e.output.trim()):m.resolve(process.cwd(),"screenshot.jpg");await p.ensureDir(m.dirname(o)),await p.writeFile(o,r),console.log(`\u2705 \u622A\u56FE\u6210\u529F: ${o} (${r.length} bytes)`)}catch(t){console.error("\u274C \u622A\u56FE\u5931\u8D25:",t instanceof Error?t.message:t),process.exit(1)}}async function pt(e){try{const t=(e.maxDepth??"50").trim(),r=(e.timeout??"120").trim(),o=(e.mode??"1").trim(),n=Number.parseInt(t,10),i=Number.parseInt(r,10),s=Number.parseInt(o,10);if(!Number.isInteger(n)||n<1)throw new Error(`\u65E0\u6548 max-depth: ${t}`);if(!Number.isInteger(i)||i<1)throw new Error(`\u65E0\u6548 timeout: ${r}`);if(!Number.isInteger(s)||s!==1&&s!==2&&s!==3)throw new Error(`\u65E0\u6548 mode: ${o}`);const u=await(0,h.getSourceOnDevice)(e,n,i,s);if(e.output?.trim()){const l=m.resolve(e.output.trim());await p.ensureDir(m.dirname(l)),await p.writeFile(l,u,"utf8"),console.log(`\u2705 \u8282\u70B9\u5DF2\u4FDD\u5B58: ${l}`);return}console.log(u)}catch(t){console.error("\u274C \u83B7\u53D6\u8282\u70B9\u5931\u8D25:",t instanceof Error?t.message:t),process.exit(1)}}async function ut(e){try{const t=a(e.limit,200,"limit",1,5e3),r=g(e.format,"text",["text","json"],"format"),o=await(0,h.getCurrentLogLinesOnDevice)(e,t),n=r==="json"?(0,j.formatRuntimeJsonText)(o):o.lines.length>0?o.lines.join(`
4
+ `):"\u65E0\u65E5\u5FD7";await $(n,e.output,"\u65E5\u5FD7\u5DF2\u4FDD\u5B58"),r==="text"&&o.hasMore&&!e.output?.trim()&&console.error(`\u8FD8\u6709\u66F4\u65E9\u65E5\u5FD7\uFF0C\u53EF\u589E\u5927 --limit\uFF0C\u5F53\u524D\u6700\u591A ${t} \u884C\u3002`)}catch(t){console.error("\u274C \u83B7\u53D6\u65E5\u5FD7\u5931\u8D25:",t instanceof Error?t.message:t),process.exit(1)}}async function mt(e){try{await $(z(),e.output,"\u6587\u6863\u8DEF\u5F84\u5DF2\u4FDD\u5B58")}catch(t){console.error("\u274C \u8F93\u51FA\u6587\u6863\u8DEF\u5F84\u5931\u8D25:",t instanceof Error?t.message:t),process.exit(1)}}async function dt(e){try{const t=E(e),r=V(e.method),o=Z(e.path??""),n=await(0,L.findHttpApiDocEntry)(o,r);if(!n)throw new Error(`HTTP API \u6587\u6863\u4E2D\u4E0D\u5B58\u5728\u8BE5\u63A5\u53E3: ${r} ${o}\u3002\u8BF7\u5148\u8BFB\u53D6 docs paths \u8FD4\u56DE\u7684 HTTP API \u6587\u6863\u8DEF\u5F84\u3002`);const i=Q(e.query,"query"),s=Y(e.body);if(r==="GET"&&s!==void 0)throw new Error("GET \u63A5\u53E3\u4E0D\u80FD\u4F20 body\uFF0C\u8BF7\u4F7F\u7528 --query\u3002");const u=g(e.responseFormat,"json",["json","text"],"response-format"),l=a(e.timeoutMs,3e4,"timeout-ms",1e3,6e5),w=new URL(`http://${t.ip}:${t.port}${o}`);tt(w,i);const S=new AbortController,I=setTimeout(()=>S.abort(),l);try{const{headers:b,requestBody:y}=et(s),f=await fetch(w.toString(),{method:r,signal:S.signal,headers:b,body:y}),T=await ot(f,u),_=[`HTTP API \u8C03\u7528${f.ok?"\u6210\u529F":"\u5931\u8D25"}: ${r} ${o}`,`\u8BBE\u5907: ${t.label}`,`\u6587\u6863: ${n.title} (lines ${n.startLine}-${n.endLine})`,`\u72B6\u6001\u7801: ${f.status}`,"",T].join(`
5
+ `);await $(_,e.output,"HTTP API \u54CD\u5E94\u5DF2\u4FDD\u5B58"),f.ok||process.exit(1)}finally{clearTimeout(I)}}catch(t){const r=t instanceof Error&&t.name==="AbortError"?"\u8BF7\u6C42\u8D85\u65F6":t instanceof Error?t.message:String(t);console.error("\u274C HTTP API \u8C03\u7528\u5931\u8D25:",r),process.exit(1)}}async function lt(e){try{const t=E(e),r=g(e.engine,"appleocr",["appleocr","paddleocr"],"engine"),o=g(e.mode,"recognize",["recognize","numbers","findText"],"mode"),n=e.input?.trim()||"screen",i=a(e.x,0,"x",0),s=a(e.y,0,"y",0),u=a(e.ex,0,"ex",0),l=a(e.ey,0,"ey",0),w=[...q(e.texts,"texts")??[],...e.text??[]],S=w.length>0?w:void 0,I=X(e.languages),b=F(e.confidenceThreshold,.6,"confidence-threshold",0,1),y=e.paddleModel===void 0?void 0:g(e.paddleModel,x.PADDLE_OCR_MODELS[0],x.PADDLE_OCR_MODELS,"paddle-model"),f=e.maxSideLen===void 0?void 0:a(e.maxSideLen,0,"max-side-len",0),T=e.useGpu===!0?!0:void 0,_=a(e.timeoutMs,6e4,"timeout-ms",1e3,6e5),v=g(e.format,"summary",["summary","json"],"format"),O=await(0,x.runOcrRecognitionOnDevice)(t,{engine:r,mode:o,input:n,x:i,y:s,ex:u,ey:l,texts:S,languages:I,confidenceThreshold:b,exactMatch:e.exactMatch===!0,paddleModel:y,maxSideLen:f,useGpu:T,outputPath:v==="summary"?e.output:void 0,timeoutMs:_}),D=v==="json"?(0,j.formatRuntimeJsonText)(O.body):O.text;v==="json"?await $(D,e.output,"OCR \u7ED3\u679C\u5DF2\u4FDD\u5B58"):console.log([D,"",`device: ${t.label}`,`engine: ${r}`,`mode: ${o}`,`input: ${n}`,`region: x=${i}, y=${s}, ex=${u}, ey=${l}`,...r==="paddleocr"&&y?[`paddleModel: ${y}`]:[],...r==="paddleocr"&&f!==void 0?[`maxSideLen: ${f}`]:[],`status: ${O.status}`].join(`
6
+ `)),O.body.success===!1&&process.exit(1)}catch(t){console.error("\u274C OCR \u6267\u884C\u5931\u8D25:",t instanceof Error?t.message:t),process.exit(1)}}async function ft(e){try{const t=e.image?.trim();if(!t)throw new Error("\u8BF7\u901A\u8FC7 --image \u6307\u5B9A\u8F93\u5165\u56FE\u7247\u8DEF\u5F84\u3002");const r=a(e.x,0,"x",0),o=a(e.y,0,"y",0),n=a(e.width,1,"width",1),i=a(e.height,1,"height",1),s=await(0,P.cropLocalImage)(t,r,o,n,i,e.output);console.log(s)}catch(t){console.error("\u274C \u56FE\u7247\u88C1\u5207\u5931\u8D25:",t instanceof Error?t.message:t),process.exit(1)}}async function gt(e){try{const t=E(e),r=a(e.x,0,"x",0),o=a(e.y,0,"y",0),n=a(e.width,1,"width",1),i=a(e.height,1,"height",1),s=await(0,P.cropDeviceScreen)(t,r,o,n,i,e.output);console.log(s)}catch(t){console.error("\u274C \u5C4F\u5E55\u88C1\u56FE\u5931\u8D25:",t instanceof Error?t.message:t),process.exit(1)}}async function ht(e){try{const t=e.image?.trim();if(!t)throw new Error("\u8BF7\u901A\u8FC7 --image \u6307\u5B9A\u8F93\u5165\u56FE\u7247\u8DEF\u5F84\u3002");const r=a(e.x,0,"x",0),o=a(e.y,0,"y",0),n=a(e.radius,0,"radius",0,20),i=await(0,P.pickLocalImageColor)(t,r,o,n);console.log(i)}catch(t){console.error("\u274C \u56FE\u7247\u53D6\u8272\u5931\u8D25:",t instanceof Error?t.message:t),process.exit(1)}}async function wt(e){try{const t=E(e),r=a(e.x,0,"x",0),o=a(e.y,0,"y",0),n=a(e.radius,0,"radius",0,20),i=await(0,P.pickDeviceScreenColor)(t,r,o,n);console.log(i)}catch(t){console.error("\u274C \u5C4F\u5E55\u53D6\u8272\u5931\u8D25:",t instanceof Error?t.message:t),process.exit(1)}}async function yt(e){try{const t=e.image?.trim();if(!t)throw new Error("\u8BF7\u901A\u8FC7 --image \u6307\u5B9A\u8F93\u5165\u56FE\u7247\u8DEF\u5F84\u3002");const r=g(e.sampleCorner,"topLeft",["topLeft","topRight","bottomLeft","bottomRight"],"sample-corner"),o=a(e.tolerance,24,"tolerance",0,441),n=await(0,P.makeLocalImageTransparent)(t,e.output,e.transparentColor,r,o);console.log(n)}catch(t){console.error("\u274C \u900F\u660E\u56FE\u5236\u4F5C\u5931\u8D25:",t instanceof Error?t.message:t),process.exit(1)}}async function xt(e){try{const t=(e.wsPort??"31111").trim(),r=Number.parseInt(t,10);if(!Number.isInteger(r)||r<1||r>65535)throw new Error(`\u65E0\u6548 ws \u7AEF\u53E3: ${t}`);const o=H.WSManager.get();await o.start(r);const n=async()=>{const s={pid:process.pid,port:r,address:o.getAddress(),startedAt:Date.now(),connected:o.isClientConnected(),clientIp:o.getConnectedClientIp()};await p.writeJSON(d,s)};await n();const i=setInterval(()=>{n().catch(()=>{})},1e3);await p.writeJSON(d,{pid:process.pid,port:r,address:o.getAddress(),startedAt:Date.now(),connected:o.isClientConnected(),clientIp:o.getConnectedClientIp()}),await new Promise(s=>{const u=async()=>{process.off("SIGINT",u),process.off("SIGTERM",u),clearInterval(i),await o.stop(),await p.remove(d),s()};process.on("SIGINT",u),process.on("SIGTERM",u)})}catch(t){console.error("\u274C WS \u670D\u52A1\u542F\u52A8\u5931\u8D25:",t instanceof Error?t.message:t),process.exit(1)}}async function Pt(){try{if(!await p.pathExists(d)){console.log("WS \u670D\u52A1\u672A\u8FD0\u884C");return}const e=await p.readJSON(d),t=Number(e.pid);if(!Number.isInteger(t)||t<=0)throw await p.remove(d),new Error("WS PID \u6587\u4EF6\u635F\u574F");try{process.kill(t,"SIGTERM"),console.log(`\u2705 \u5DF2\u53D1\u9001\u505C\u6B62\u4FE1\u53F7\u5230 WS \u8FDB\u7A0B: ${t}`)}catch{console.log("WS \u8FDB\u7A0B\u4E0D\u5B58\u5728\uFF0C\u6E05\u7406 PID \u6587\u4EF6")}await p.remove(d)}catch(e){console.error("\u274C WS \u670D\u52A1\u505C\u6B62\u5931\u8D25:",e instanceof Error?e.message:e),process.exit(1)}}async function St(){if(!await p.pathExists(d)){console.log(JSON.stringify({status:"stopped",running:!1,pid:null,address:null,connected:!1,clientIp:null}));return}const e=await p.readJSON(d),t=Number(e.pid),r=String(e.address||"unknown"),o=!!e.connected,n=e.clientIp===null||e.clientIp===void 0?null:String(e.clientIp);let i=!1;try{process.kill(t,0),i=!0}catch{i=!1}if(!i){await p.remove(d),console.log(JSON.stringify({status:"stopped",running:!1,pid:null,address:null,connected:!1,clientIp:null}));return}console.log(JSON.stringify({status:"running",running:!0,pid:t,address:r,connected:o,clientIp:o?n||"unknown":null}))}function Ot(){const e=[];e.push(""),e.push("\u8BE6\u7EC6\u547D\u4EE4\u53C2\u6570:");const t=c.program.commands.filter(r=>r.name()!=="help");for(const r of t){if(e.push(` ${r.name()}:`),r.options.length===0){e.push(" (\u65E0\u989D\u5916\u53C2\u6570)");continue}for(const o of r.options){const n=o.defaultValue!==void 0&&o.defaultValue!==null?` (\u9ED8\u8BA4: ${String(o.defaultValue)})`:"",i=o.description?` ${o.description}${n}`:n;e.push(` ${o.flags}${i}`)}}return e.push(""),e.join(`
7
+ `)}c.program.name("ms-cli").description("\u5FEB\u70B9JS \u6784\u5EFA\u5DE5\u5177").version(k.PACKAGE_VERSION),c.program.addHelpText("after",Ot),c.program.command("build").description("\u6784\u5EFA \u5FEB\u70B9JS \u9879\u76EE").option("-d, --dev","\u5F00\u53D1\u6A21\u5F0F\u6784\u5EFA (\u5305\u542B source map)").action(rt),c.program.command("package").description("\u751F\u4EA7\u6784\u5EFA\u5E76\u52A0\u5BC6 ms.msbundle").action(nt),c.program.command("run").description("\u5728\u8BBE\u5907\u4E0A\u8FD0\u884C\u9879\u76EE").option("-i, --ip <ip>","\u8BBE\u5907 IP \u5730\u5740\uFF08transport=http \u65F6\u5FC5\u586B\uFF09").option("--port <port>","\u8BBE\u5907\u7AEF\u53E3","9800").option("-t, --transport <transport>","\u4F20\u8F93\u65B9\u5F0F: http|ws","http").option("--ws-port <wsPort>","WS \u670D\u52A1\u7AEF\u53E3","31111").option("--ws-wait-ms <wsWaitMs>","WS \u7B49\u5F85\u8BBE\u5907\u8FDE\u63A5\u65F6\u95F4(\u6BEB\u79D2)","30000").action(it),c.program.command("run-ui").description("\u5728\u8BBE\u5907\u4E0A\u9884\u89C8 UI").option("-i, --ip <ip>","\u8BBE\u5907 IP \u5730\u5740\uFF08transport=http \u65F6\u5FC5\u586B\uFF09").option("--port <port>","\u8BBE\u5907\u7AEF\u53E3","9800").option("-t, --transport <transport>","\u4F20\u8F93\u65B9\u5F0F: http|ws","http").option("--ws-port <wsPort>","WS \u670D\u52A1\u7AEF\u53E3","31111").option("--ws-wait-ms <wsWaitMs>","WS \u7B49\u5F85\u8BBE\u5907\u8FDE\u63A5\u65F6\u95F4(\u6BEB\u79D2)","30000").action(st),c.program.command("stop").description("\u505C\u6B62\u8BBE\u5907\u4E0A\u7684\u9879\u76EE").option("-i, --ip <ip>","\u8BBE\u5907 IP \u5730\u5740\uFF08transport=http \u65F6\u5FC5\u586B\uFF09").option("--port <port>","\u8BBE\u5907\u7AEF\u53E3","9800").option("-t, --transport <transport>","\u4F20\u8F93\u65B9\u5F0F: http|ws","http").option("--ws-port <wsPort>","WS \u670D\u52A1\u7AEF\u53E3","31111").option("--ws-wait-ms <wsWaitMs>","WS \u7B49\u5F85\u8BBE\u5907\u8FDE\u63A5\u65F6\u95F4(\u6BEB\u79D2)","30000").action(at),c.program.command("screenshot").description("\u83B7\u53D6\u8BBE\u5907\u622A\u56FE\uFF08JPG\uFF09").option("-i, --ip <ip>","\u8BBE\u5907 IP \u5730\u5740\uFF08transport=http \u65F6\u5FC5\u586B\uFF09").option("--port <port>","\u8BBE\u5907\u7AEF\u53E3","9800").option("-t, --transport <transport>","\u4F20\u8F93\u65B9\u5F0F: http|ws","http").option("--ws-port <wsPort>","WS \u670D\u52A1\u7AEF\u53E3","31111").option("--ws-wait-ms <wsWaitMs>","WS \u7B49\u5F85\u8BBE\u5907\u8FDE\u63A5\u65F6\u95F4(\u6BEB\u79D2)","30000").option("--format <format>","\u8FD4\u56DE\u683C\u5F0F: file|base64","file").option("--output <output>","\u622A\u56FE\u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84\uFF08format=file \u65F6\u751F\u6548\uFF09").action(ct),c.program.command("source").description("\u83B7\u53D6\u8BBE\u5907\u5F53\u524D\u9875\u9762\u8282\u70B9 XML").option("-i, --ip <ip>","\u8BBE\u5907 IP \u5730\u5740\uFF08transport=http \u65F6\u5FC5\u586B\uFF09").option("--port <port>","\u8BBE\u5907\u7AEF\u53E3","9800").option("-t, --transport <transport>","\u4F20\u8F93\u65B9\u5F0F: http|ws","http").option("--ws-port <wsPort>","WS \u670D\u52A1\u7AEF\u53E3","31111").option("--ws-wait-ms <wsWaitMs>","WS \u7B49\u5F85\u8BBE\u5907\u8FDE\u63A5\u65F6\u95F4(\u6BEB\u79D2)","30000").option("--max-depth <maxDepth>","\u8282\u70B9\u6700\u5927\u6DF1\u5EA6","50").option("--timeout <timeout>","\u8282\u70B9\u6293\u53D6\u8D85\u65F6\u79D2\u6570","120").option("--mode <mode>","\u8282\u70B9\u6293\u53D6\u6A21\u5F0F: 1=\u6A21\u5F0F1, 2=\u6A21\u5F0F2, 3=\u6A21\u5F0F3","1").option("--output <output>","\u8282\u70B9 XML \u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84\uFF08\u53EF\u9009\uFF09").action(pt),c.program.command("logs").description("\u83B7\u53D6\u8BBE\u5907\u5F53\u524D\u65E5\u5FD7\u6700\u65B0\u884C").option("-i, --ip <ip>","\u8BBE\u5907 IP \u5730\u5740").option("--port <port>","\u8BBE\u5907\u7AEF\u53E3","9800").option("--limit <limit>","\u8BFB\u53D6\u6700\u65B0\u65E5\u5FD7\u884C\u6570\uFF0C\u8303\u56F4 1..5000","200").option("--format <format>","\u8FD4\u56DE\u683C\u5F0F: text|json","text").option("--output <output>","\u65E5\u5FD7\u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84\uFF08\u53EF\u9009\uFF09").action(ut),c.program.command("ocr").description("\u5728\u8BBE\u5907\u4E0A\u6267\u884C OCR \u8BC6\u522B").option("-i, --ip <ip>","\u8BBE\u5907 IP \u5730\u5740").option("--port <port>","\u8BBE\u5907\u7AEF\u53E3","9800").option("--engine <engine>","OCR \u5F15\u64CE: appleocr|paddleocr","appleocr").option("--mode <mode>","OCR \u6A21\u5F0F: recognize|numbers|findText","recognize").option("--input <input>",'\u8F93\u5165\u6E90\uFF0C\u9ED8\u8BA4 "screen"',"screen").option("--x <x>","\u533A\u57DF\u5DE6\u4E0A\u89D2 x","0").option("--y <y>","\u533A\u57DF\u5DE6\u4E0A\u89D2 y","0").option("--ex <ex>","\u533A\u57DF\u53F3\u4E0B\u89D2 x\uFF1B\u5168\u5C4F\u53EF\u4F20 0","0").option("--ey <ey>","\u533A\u57DF\u53F3\u4E0B\u89D2 y\uFF1B\u5168\u5C4F\u53EF\u4F20 0","0").option("--text <text>","findText \u6587\u672C\uFF0C\u53EF\u91CD\u590D",K,[]).option("--texts <texts>","findText \u6587\u672C\u6570\u7EC4\uFF0C\u652F\u6301 JSON \u6570\u7EC4\u6216\u9017\u53F7\u5206\u9694").option("--languages <languages>","Apple OCR \u8BED\u8A00\uFF0C\u652F\u6301 JSON \u6570\u7EC4\u6216\u9017\u53F7\u5206\u9694").option("--exact-match","findText \u8981\u6C42\u5B8C\u6574\u5339\u914D\u6587\u672C").option("--confidence-threshold <confidenceThreshold>","PaddleOCR \u7F6E\u4FE1\u5EA6\u9608\u503C","0.6").option("--paddle-model <paddleModel>","\u4EC5 paddleocr\uFF1A\u5207\u6362\u6A21\u578B ppocr-v6-tiny|ppocr-v6-small|ppocr-v5").option("--max-side-len <maxSideLen>","\u4EC5 paddleocr\uFF1A\u68C0\u6D4B\u6700\u5927\u8FB9\u957F\uFF0C0 \u56DE\u843D\u9ED8\u8BA4 768").option("--use-gpu","\u4EC5 paddleocr\uFF1A\u5207\u6362\u6A21\u578B\u65F6\u542F\u7528 GPU").option("--timeout-ms <timeoutMs>","\u8BF7\u6C42\u8D85\u65F6\u65F6\u95F4(\u6BEB\u79D2)","60000").option("--format <format>","\u8FD4\u56DE\u683C\u5F0F: summary|json","summary").option("--output <output>","OCR \u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84\uFF08\u53EF\u9009\uFF09").action(lt),c.program.command("image-crop").description("\u88C1\u5207\u672C\u5730\u56FE\u7247\u5E76\u8F93\u51FA PNG").requiredOption("--image <image>","\u8F93\u5165\u56FE\u7247\u8DEF\u5F84").option("--x <x>","\u88C1\u5207\u8D77\u70B9 x","0").option("--y <y>","\u88C1\u5207\u8D77\u70B9 y","0").requiredOption("--width <width>","\u88C1\u5207\u5BBD\u5EA6").requiredOption("--height <height>","\u88C1\u5207\u9AD8\u5EA6").option("--output <output>","\u8F93\u51FA PNG \u8DEF\u5F84\uFF08\u53EF\u9009\uFF09").action(ft),c.program.command("screen-crop").description("\u4ECE\u8BBE\u5907\u622A\u56FE\u4E2D\u88C1\u5207 PNG").option("-i, --ip <ip>","\u8BBE\u5907 IP \u5730\u5740").option("--port <port>","\u8BBE\u5907\u7AEF\u53E3","9800").option("--x <x>","\u88C1\u5207\u8D77\u70B9 x","0").option("--y <y>","\u88C1\u5207\u8D77\u70B9 y","0").requiredOption("--width <width>","\u88C1\u5207\u5BBD\u5EA6").requiredOption("--height <height>","\u88C1\u5207\u9AD8\u5EA6").option("--output <output>","\u8F93\u51FA PNG \u8DEF\u5F84\uFF08\u53EF\u9009\uFF09").action(gt),c.program.command("image-pick-color").description("\u8BFB\u53D6\u672C\u5730\u56FE\u7247\u6307\u5B9A\u5750\u6807\u989C\u8272").requiredOption("--image <image>","\u8F93\u5165\u56FE\u7247\u8DEF\u5F84").option("--x <x>","\u76EE\u6807\u5750\u6807 x","0").option("--y <y>","\u76EE\u6807\u5750\u6807 y","0").option("--radius <radius>","\u91C7\u6837\u534A\u5F84\uFF0C\u8303\u56F4 0..20","0").action(ht),c.program.command("screen-pick-color").description("\u8BFB\u53D6\u8BBE\u5907\u622A\u56FE\u6307\u5B9A\u5750\u6807\u989C\u8272").option("-i, --ip <ip>","\u8BBE\u5907 IP \u5730\u5740").option("--port <port>","\u8BBE\u5907\u7AEF\u53E3","9800").option("--x <x>","\u76EE\u6807\u5750\u6807 x","0").option("--y <y>","\u76EE\u6807\u5750\u6807 y","0").option("--radius <radius>","\u91C7\u6837\u534A\u5F84\uFF0C\u8303\u56F4 0..20","0").action(wt),c.program.command("image-transparent").description("\u5C06\u56FE\u7247\u4E2D\u63A5\u8FD1\u6307\u5B9A\u989C\u8272\u6216\u89D2\u843D\u80CC\u666F\u8272\u7684\u50CF\u7D20\u8BBE\u4E3A\u900F\u660E").requiredOption("--image <image>","\u8F93\u5165\u56FE\u7247\u8DEF\u5F84").option("--output <output>","\u8F93\u51FA PNG \u8DEF\u5F84\uFF08\u53EF\u9009\uFF09").option("--transparent-color <transparentColor>","\u900F\u660E\u5316\u989C\u8272\uFF0C\u683C\u5F0F #RRGGBB").option("--sample-corner <sampleCorner>","\u672A\u4F20 transparent-color \u65F6\u91C7\u6837\u89D2\u843D: topLeft|topRight|bottomLeft|bottomRight","topLeft").option("--tolerance <tolerance>","RGB \u6B27\u6C0F\u8DDD\u79BB\u5BB9\u5DEE\uFF0C\u8303\u56F4 0..441","24").action(yt);const Ct=c.program.command("docs").description("\u663E\u793A KuaiJS \u672C\u5730\u6587\u6863\u8DEF\u5F84");Ct.command("paths").description("\u663E\u793A KuaiJS \u672C\u5730\u6587\u6863\u8DEF\u5F84").option("--output <output>","\u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84\uFF08\u53EF\u9009\uFF09").action(mt),c.program.command("http-call").description("\u6309 HTTP API \u6587\u6863\u58F0\u660E\u8C03\u7528\u8BBE\u5907\u63A5\u53E3").option("-i, --ip <ip>","\u8BBE\u5907 IP \u5730\u5740").option("--port <port>","\u8BBE\u5907\u7AEF\u53E3","9800").requiredOption("--method <method>","HTTP \u65B9\u6CD5: GET|POST").requiredOption("--path <path>","HTTP API \u76F8\u5BF9\u8DEF\u5F84\uFF0C\u4F8B\u5982 /api/status").option("--query <query>","JSON \u5BF9\u8C61 query \u53C2\u6570").option("--body <body>","JSON \u8BF7\u6C42\u4F53\uFF1B\u666E\u901A\u5B57\u7B26\u4E32\u4F1A\u4F5C\u4E3A JSON \u5B57\u7B26\u4E32\u63D0\u4EA4").option("--response-format <responseFormat>","\u54CD\u5E94\u683C\u5F0F: json|text","json").option("--timeout-ms <timeoutMs>","\u8BF7\u6C42\u8D85\u65F6\u65F6\u95F4(\u6BEB\u79D2)","30000").option("--output <output>","\u54CD\u5E94\u8F93\u51FA\u6587\u4EF6\u8DEF\u5F84\uFF08\u53EF\u9009\uFF09").action(dt),c.program.command("ws-start").description("\u542F\u52A8 CLI \u5185\u7F6E WS \u670D\u52A1").option("--ws-port <wsPort>","WS \u670D\u52A1\u7AEF\u53E3","31111").action(xt),c.program.command("ws-stop").description("\u505C\u6B62 CLI \u5185\u7F6E WS \u670D\u52A1").action(Pt),c.program.command("ws-status").description("\u67E5\u770B CLI \u5185\u7F6E WS \u670D\u52A1\u72B6\u6001").action(St),c.program.parse();