ms-vite-plugin 1.1.2 → 1.1.4

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 (94) hide show
  1. package/dist/build.js +6 -0
  2. package/dist/cli.js +91 -1
  3. package/dist/mcp/device-config.d.ts +55 -0
  4. package/dist/mcp/device-config.js +183 -0
  5. package/dist/mcp/docs-service.d.ts +65 -0
  6. package/dist/mcp/docs-service.js +168 -0
  7. package/dist/mcp/project.d.ts +16 -0
  8. package/dist/mcp/project.js +74 -0
  9. package/dist/mcp/tools.d.ts +18 -0
  10. package/dist/mcp/tools.js +825 -0
  11. package/dist/mcp/types.d.ts +32 -0
  12. package/dist/mcp/types.js +11 -0
  13. package/dist/mcp-server.d.ts +2 -0
  14. package/dist/mcp-server.js +86 -0
  15. package/dist/project.d.ts +89 -0
  16. package/dist/project.js +306 -0
  17. package/dist/version.d.ts +12 -0
  18. package/dist/version.js +63 -0
  19. package/docs/api/action.md +922 -0
  20. package/docs/api/appleocr.md +229 -0
  21. package/docs/api/config.md +122 -0
  22. package/docs/api/cryptoUtils.md +232 -0
  23. package/docs/api/device.md +374 -0
  24. package/docs/api/file.md +516 -0
  25. package/docs/api/global.md +617 -0
  26. package/docs/api/hid.md +1032 -0
  27. package/docs/api/hotUpdate.md +166 -0
  28. package/docs/api/http.md +548 -0
  29. package/docs/api/image.md +907 -0
  30. package/docs/api/ime.md +290 -0
  31. package/docs/api/logger.md +324 -0
  32. package/docs/api/media.md +248 -0
  33. package/docs/api/mysql.md +441 -0
  34. package/docs/api/netCard.md +200 -0
  35. package/docs/api/node.md +353 -0
  36. package/docs/api/paddleocr.md +246 -0
  37. package/docs/api/pip.md +242 -0
  38. package/docs/api/system.md +572 -0
  39. package/docs/api/thread.md +269 -0
  40. package/docs/api/tomatoocr.md +425 -0
  41. package/docs/api/tts.md +334 -0
  42. package/docs/api/ui.md +947 -0
  43. package/docs/api/utils.md +265 -0
  44. package/docs/api/yolo.md +310 -0
  45. package/docs/apicn/action.md +919 -0
  46. package/docs/apicn/appleocr.md +233 -0
  47. package/docs/apicn/config.md +120 -0
  48. package/docs/apicn/device.md +385 -0
  49. package/docs/apicn/file.md +511 -0
  50. package/docs/apicn/global.md +613 -0
  51. package/docs/apicn/hid.md +1033 -0
  52. package/docs/apicn/hotUpdate.md +170 -0
  53. package/docs/apicn/http.md +672 -0
  54. package/docs/apicn/image.md +924 -0
  55. package/docs/apicn/ime.md +290 -0
  56. package/docs/apicn/logger.md +332 -0
  57. package/docs/apicn/media.md +252 -0
  58. package/docs/apicn/mysql.md +445 -0
  59. package/docs/apicn/netCard.md +200 -0
  60. package/docs/apicn/node.md +362 -0
  61. package/docs/apicn/paddleocr.md +255 -0
  62. package/docs/apicn/pip.md +242 -0
  63. package/docs/apicn/system.md +575 -0
  64. package/docs/apicn/thread.md +269 -0
  65. package/docs/apicn/tts.md +338 -0
  66. package/docs/apicn/ui.md +933 -0
  67. package/docs/apicn/utils.md +265 -0
  68. package/docs/apicn/yolo.md +314 -0
  69. package/docs/apipython/action.md +901 -0
  70. package/docs/apipython/appleocr.md +226 -0
  71. package/docs/apipython/config.md +126 -0
  72. package/docs/apipython/cryptoUtils.md +246 -0
  73. package/docs/apipython/device.md +365 -0
  74. package/docs/apipython/file.md +476 -0
  75. package/docs/apipython/g.md +154 -0
  76. package/docs/apipython/hid.md +1059 -0
  77. package/docs/apipython/hotUpdate.md +154 -0
  78. package/docs/apipython/image.md +938 -0
  79. package/docs/apipython/ime.md +306 -0
  80. package/docs/apipython/logger.md +330 -0
  81. package/docs/apipython/media.md +221 -0
  82. package/docs/apipython/mysql.md +432 -0
  83. package/docs/apipython/netCard.md +219 -0
  84. package/docs/apipython/node.md +331 -0
  85. package/docs/apipython/overview.md +66 -0
  86. package/docs/apipython/paddleocr.md +211 -0
  87. package/docs/apipython/pip.md +231 -0
  88. package/docs/apipython/system.md +458 -0
  89. package/docs/apipython/tomatoocr.md +444 -0
  90. package/docs/apipython/tts.md +331 -0
  91. package/docs/apipython/ui.md +949 -0
  92. package/docs/apipython/utils.md +284 -0
  93. package/docs/apipython/yolo.md +281 -0
  94. package/package.json +8 -4
@@ -0,0 +1,32 @@
1
+ /**
2
+ * 设备连接配置结构
3
+ */
4
+ export type DeviceConfig = {
5
+ /** 设备 IP 地址 */
6
+ ip: string;
7
+ /** 设备端口 */
8
+ port: number;
9
+ /** 最近更新时间戳(毫秒) */
10
+ updatedAt: number;
11
+ };
12
+ /**
13
+ * 文档语言类型
14
+ */
15
+ export type ApiDocsLanguage = "js" | "js_zh" | "python";
16
+ /**
17
+ * 文档条目结构
18
+ */
19
+ export type ApiDocItem = {
20
+ /** 文档分类(即文件名,不含 .md) */
21
+ slug: string;
22
+ /** 文档标题(优先取首个 markdown 标题) */
23
+ title: string;
24
+ /** 文档全文 */
25
+ content: string;
26
+ /** 文档目录绝对路径 */
27
+ filePath: string;
28
+ };
29
+ /**
30
+ * 文档语言显示名映射
31
+ */
32
+ export declare const DOC_LANGUAGE_LABELS: Record<ApiDocsLanguage, string>;
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.DOC_LANGUAGE_LABELS = void 0;
4
+ /**
5
+ * 文档语言显示名映射
6
+ */
7
+ exports.DOC_LANGUAGE_LABELS = {
8
+ js: "JavaScript",
9
+ js_zh: "JavaScript (中文)",
10
+ python: "Python",
11
+ };
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,86 @@
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 fsExtra = __importStar(require("fs-extra"));
38
+ const stdio_js_1 = require("@modelcontextprotocol/sdk/server/stdio.js");
39
+ const tools_1 = require("./mcp/tools");
40
+ const device_config_1 = require("./mcp/device-config");
41
+ /**
42
+ * 启动 MCP 服务(stdio)
43
+ * @returns 启动成功后常驻运行
44
+ * @example
45
+ * await main()
46
+ */
47
+ async function main() {
48
+ const server = (0, tools_1.createMcpServer)();
49
+ const transport = new stdio_js_1.StdioServerTransport();
50
+ /**
51
+ * 进程退出时清理当前实例的临时配置文件,避免系统临时目录残留
52
+ * 注意:只清理“当前进程文件”,不会影响其他并发窗口实例
53
+ */
54
+ const cleanupConfigFile = async () => {
55
+ try {
56
+ await fsExtra.remove(device_config_1.DEVICE_CONFIG_FILE);
57
+ }
58
+ catch {
59
+ // 忽略清理错误,避免影响主流程退出
60
+ }
61
+ };
62
+ process.once("SIGINT", () => {
63
+ cleanupConfigFile()
64
+ .catch(() => undefined)
65
+ .finally(() => process.exit(0));
66
+ });
67
+ process.once("SIGTERM", () => {
68
+ cleanupConfigFile()
69
+ .catch(() => undefined)
70
+ .finally(() => process.exit(0));
71
+ });
72
+ process.once("exit", () => {
73
+ try {
74
+ fsExtra.removeSync(device_config_1.DEVICE_CONFIG_FILE);
75
+ }
76
+ catch {
77
+ // ignore
78
+ }
79
+ });
80
+ await server.connect(transport);
81
+ }
82
+ main().catch((error) => {
83
+ const message = error instanceof Error ? error.message : String(error);
84
+ console.error(`MCP 服务启动失败: ${message}`);
85
+ process.exit(1);
86
+ });
package/dist/project.d.ts CHANGED
@@ -8,6 +8,95 @@ export interface DeviceCliOptions {
8
8
  wsPort?: string;
9
9
  wsWaitMs?: string;
10
10
  }
11
+ /**
12
+ * 设备日志条目
13
+ */
14
+ export interface DeviceLogEntry {
15
+ /** 日志级别,例如 debug/info/warn/error */
16
+ level: string;
17
+ /** 日志文本 */
18
+ message: string;
19
+ /** 日志时间戳(ISO 字符串) */
20
+ timestamp: string;
21
+ }
22
+ /**
23
+ * 运行时状态条目(来自 SSE runtime_status 事件)
24
+ */
25
+ export interface DeviceRuntimeStatusEntry {
26
+ /** 原始 runtime_status 数据 */
27
+ raw: Record<string, unknown>;
28
+ /** 事件时间戳(本地接收时间) */
29
+ timestamp: string;
30
+ }
31
+ /**
32
+ * 设备日志判定项
33
+ */
34
+ export interface DeviceLogFinding {
35
+ /** 严重级别 */
36
+ severity: "error" | "warn";
37
+ /** 命中的判定规则 */
38
+ rule: string;
39
+ /** 证据日志 */
40
+ log: DeviceLogEntry;
41
+ }
42
+ /**
43
+ * SSE 日志监听结果
44
+ */
45
+ export interface DeviceLogWatchResult {
46
+ /** 收集到的日志条目 */
47
+ logs: DeviceLogEntry[];
48
+ /** 收集到的 runtime_status 条目 */
49
+ runtimeStatus: DeviceRuntimeStatusEntry[];
50
+ /** 自动判定结果 */
51
+ findings: DeviceLogFinding[];
52
+ /** 结果摘要 */
53
+ summary: string;
54
+ }
55
+ /**
56
+ * 在设备上获取截图 JPG 二进制数据(HTTP)
57
+ * @param options 命令选项(transport 仅支持 http)
58
+ * @returns 返回 JPG 二进制 Buffer
59
+ * @example
60
+ * const image = await getScreenshotOnDevice({ ip: "192.168.1.10", port: "9800" })
61
+ */
62
+ export declare function getScreenshotOnDevice(options: DeviceCliOptions): Promise<Buffer>;
63
+ /**
64
+ * 在设备上获取截图 Base64(HTTP)
65
+ * @param options 命令选项(transport 仅支持 http)
66
+ * @returns 返回截图 base64 字符串
67
+ * @example
68
+ * const b64 = await getScreenshotBase64OnDevice({ ip: "192.168.1.10", port: "9800" })
69
+ */
70
+ export declare function getScreenshotBase64OnDevice(options: DeviceCliOptions): Promise<string>;
71
+ /**
72
+ * 在设备上获取节点 XML(HTTP)
73
+ * @param options 命令选项(transport 仅支持 http)
74
+ * @param maxDepth 节点最大深度,默认 50
75
+ * @param timeout 获取节点超时秒数,默认 120
76
+ * @returns 返回 XML 字符串
77
+ * @example
78
+ * const xml = await getSourceOnDevice({ ip: "192.168.1.10", port: "9800" }, 50, 120)
79
+ */
80
+ export declare function getSourceOnDevice(options: DeviceCliOptions, maxDepth?: number, timeout?: number): Promise<string>;
81
+ /**
82
+ * 基于关键字与日志级别做自动异常判定
83
+ * @param logs 待判定日志列表
84
+ * @returns 返回判定命中项
85
+ * @example
86
+ * const findings = analyzeDeviceLogs([{ level: "error", message: "crash", timestamp: "..." }])
87
+ */
88
+ export declare function analyzeDeviceLogs(logs: DeviceLogEntry[]): DeviceLogFinding[];
89
+ /**
90
+ * 监听设备 SSE 日志并返回自动判定结果
91
+ * @param ip 设备 IP 地址
92
+ * @param port 设备端口
93
+ * @param durationMs 监听时长(毫秒),默认 15000
94
+ * @param maxLogs 最多收集日志条数,默认 200
95
+ * @returns 返回日志、运行状态和判定结果
96
+ * @example
97
+ * const result = await watchDeviceLogsBySse("192.168.1.10", 9800, 10000, 100)
98
+ */
99
+ export declare function watchDeviceLogsBySse(ip: string, port: number, durationMs?: number, maxLogs?: number): Promise<DeviceLogWatchResult>;
11
100
  /**
12
101
  * 在设备上运行项目(先同步后运行)
13
102
  * @param options 命令选项
package/dist/project.js CHANGED
@@ -36,6 +36,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.getScreenshotOnDevice = getScreenshotOnDevice;
40
+ exports.getScreenshotBase64OnDevice = getScreenshotBase64OnDevice;
41
+ exports.getSourceOnDevice = getSourceOnDevice;
42
+ exports.analyzeDeviceLogs = analyzeDeviceLogs;
43
+ exports.watchDeviceLogsBySse = watchDeviceLogsBySse;
39
44
  exports.runOnDevice = runOnDevice;
40
45
  exports.runUIOnDevice = runUIOnDevice;
41
46
  exports.stopOnDevice = stopOnDevice;
@@ -364,6 +369,307 @@ async function requestDeviceAction(action, ip, port) {
364
369
  throw new Error(`${action} 失败,状态码: ${response.status}`);
365
370
  }
366
371
  }
372
+ /**
373
+ * 在设备上获取截图 JPG 二进制数据(HTTP)
374
+ * @param options 命令选项(transport 仅支持 http)
375
+ * @returns 返回 JPG 二进制 Buffer
376
+ * @example
377
+ * const image = await getScreenshotOnDevice({ ip: "192.168.1.10", port: "9800" })
378
+ */
379
+ async function getScreenshotOnDevice(options) {
380
+ const transport = parseTransport(options);
381
+ if (transport === "ws") {
382
+ await ensureWsReady(options);
383
+ const result = await ws_manager_1.WSManager.request("screenshot");
384
+ if (!result.success || typeof result.data !== "string") {
385
+ throw new Error(`截图失败: ${String(result.message ?? "unknown")}`);
386
+ }
387
+ return Buffer.from(result.data, "base64");
388
+ }
389
+ const { ip, port } = parseHttpOptions(options);
390
+ const url = `http://${ip}:${port}/api/screenshot`;
391
+ const response = await fetch(url, { method: "GET" });
392
+ if (!response.ok) {
393
+ throw new Error(`截图失败,状态码: ${response.status}`);
394
+ }
395
+ const arrayBuffer = await response.arrayBuffer();
396
+ return Buffer.from(arrayBuffer);
397
+ }
398
+ /**
399
+ * 在设备上获取截图 Base64(HTTP)
400
+ * @param options 命令选项(transport 仅支持 http)
401
+ * @returns 返回截图 base64 字符串
402
+ * @example
403
+ * const b64 = await getScreenshotBase64OnDevice({ ip: "192.168.1.10", port: "9800" })
404
+ */
405
+ async function getScreenshotBase64OnDevice(options) {
406
+ const transport = parseTransport(options);
407
+ if (transport === "ws") {
408
+ await ensureWsReady(options);
409
+ const result = await ws_manager_1.WSManager.request("screenshot");
410
+ if (!result.success || typeof result.data !== "string") {
411
+ throw new Error(`截图(base64)失败: ${String(result.message ?? "unknown")}`);
412
+ }
413
+ return result.data;
414
+ }
415
+ const { ip, port } = parseHttpOptions(options);
416
+ const url = `http://${ip}:${port}/api/screenshotBase64`;
417
+ const response = await fetch(url, { method: "GET" });
418
+ if (!response.ok) {
419
+ throw new Error(`截图(base64)失败,状态码: ${response.status}`);
420
+ }
421
+ const payload = (await response.json());
422
+ if (!payload.success || typeof payload.data !== "string") {
423
+ throw new Error(`截图(base64)失败: ${String(payload.message ?? "unknown")}`);
424
+ }
425
+ return payload.data;
426
+ }
427
+ /**
428
+ * 在设备上获取节点 XML(HTTP)
429
+ * @param options 命令选项(transport 仅支持 http)
430
+ * @param maxDepth 节点最大深度,默认 50
431
+ * @param timeout 获取节点超时秒数,默认 120
432
+ * @returns 返回 XML 字符串
433
+ * @example
434
+ * const xml = await getSourceOnDevice({ ip: "192.168.1.10", port: "9800" }, 50, 120)
435
+ */
436
+ async function getSourceOnDevice(options, maxDepth = 50, timeout = 120) {
437
+ const transport = parseTransport(options);
438
+ if (!Number.isInteger(maxDepth) || maxDepth < 1) {
439
+ throw new Error(`无效 maxDepth: ${maxDepth}`);
440
+ }
441
+ if (!Number.isInteger(timeout) || timeout < 1) {
442
+ throw new Error(`无效 timeout: ${timeout}`);
443
+ }
444
+ if (transport === "ws") {
445
+ await ensureWsReady(options);
446
+ const result = await ws_manager_1.WSManager.request("source", {
447
+ max_depth: maxDepth,
448
+ timeout,
449
+ maxDepth,
450
+ });
451
+ if (!result.success || typeof result.data !== "string") {
452
+ throw new Error(`获取节点失败: ${String(result.message ?? "unknown")}`);
453
+ }
454
+ return result.data;
455
+ }
456
+ const { ip, port } = parseHttpOptions(options);
457
+ const url = new URL(`http://${ip}:${port}/api/source`);
458
+ url.searchParams.set("max_depth", String(maxDepth));
459
+ url.searchParams.set("timeout", String(timeout));
460
+ const response = await fetch(url.toString(), { method: "GET" });
461
+ if (!response.ok) {
462
+ throw new Error(`获取节点失败,状态码: ${response.status}`);
463
+ }
464
+ return response.text();
465
+ }
466
+ /**
467
+ * 将 SSE 文本块解析为事件字段结构
468
+ * @param rawBlock 单个 SSE 事件文本块(以空行分隔)
469
+ * @returns 返回事件名与数据字符串
470
+ * @example
471
+ * parseSseEventBlock("event: log\ndata: {\"level\":\"info\"}")
472
+ */
473
+ function parseSseEventBlock(rawBlock) {
474
+ const lines = rawBlock.split(/\r?\n/);
475
+ let eventName = "message";
476
+ const dataLines = [];
477
+ for (const line of lines) {
478
+ if (!line || line.startsWith(":")) {
479
+ continue;
480
+ }
481
+ if (line.startsWith("event:")) {
482
+ eventName = line.slice("event:".length).trim() || "message";
483
+ continue;
484
+ }
485
+ if (line.startsWith("data:")) {
486
+ dataLines.push(line.slice("data:".length).trimStart());
487
+ }
488
+ }
489
+ return {
490
+ event: eventName,
491
+ data: dataLines.join("\n"),
492
+ };
493
+ }
494
+ /**
495
+ * 将原始日志对象标准化为 DeviceLogEntry
496
+ * @param payload 日志原始对象
497
+ * @returns 返回标准化日志对象
498
+ * @example
499
+ * normalizeLogEntry({ level: "info", message: "ok", timestamp: "2026-01-01T00:00:00Z" })
500
+ */
501
+ function normalizeLogEntry(payload) {
502
+ const levelText = String(payload.level ?? "info").toLowerCase();
503
+ const messageText = String(payload.message ?? "");
504
+ const timestampText = typeof payload.timestamp === "string" && payload.timestamp
505
+ ? payload.timestamp
506
+ : new Date().toISOString();
507
+ return {
508
+ level: levelText,
509
+ message: messageText,
510
+ timestamp: timestampText,
511
+ };
512
+ }
513
+ /**
514
+ * 基于关键字与日志级别做自动异常判定
515
+ * @param logs 待判定日志列表
516
+ * @returns 返回判定命中项
517
+ * @example
518
+ * const findings = analyzeDeviceLogs([{ level: "error", message: "crash", timestamp: "..." }])
519
+ */
520
+ function analyzeDeviceLogs(logs) {
521
+ const findings = [];
522
+ const criticalPatterns = [
523
+ { rule: "崩溃关键词", regex: /\b(crash|fatal|abort|sigabrt|segfault)\b/i },
524
+ {
525
+ rule: "异常关键词",
526
+ regex: /\b(exception|uncaught|assert|error:|failed|failure)\b/i,
527
+ },
528
+ {
529
+ rule: "内存风险关键词",
530
+ regex: /\b(out of memory|oom|memory leak|allocation failed)\b/i,
531
+ },
532
+ {
533
+ rule: "设备离线关键词",
534
+ regex: /\b(timeout|disconnected|connection reset|network error)\b/i,
535
+ },
536
+ ];
537
+ for (const log of logs) {
538
+ const level = log.level.toLowerCase();
539
+ const message = log.message;
540
+ if (level === "error") {
541
+ findings.push({
542
+ severity: "error",
543
+ rule: "error 级别日志",
544
+ log,
545
+ });
546
+ }
547
+ else if (level === "warn") {
548
+ findings.push({
549
+ severity: "warn",
550
+ rule: "warn 级别日志",
551
+ log,
552
+ });
553
+ }
554
+ for (const pattern of criticalPatterns) {
555
+ if (pattern.regex.test(message)) {
556
+ findings.push({
557
+ severity: level === "error" ? "error" : "warn",
558
+ rule: pattern.rule,
559
+ log,
560
+ });
561
+ }
562
+ }
563
+ }
564
+ return findings;
565
+ }
566
+ /**
567
+ * 监听设备 SSE 日志并返回自动判定结果
568
+ * @param ip 设备 IP 地址
569
+ * @param port 设备端口
570
+ * @param durationMs 监听时长(毫秒),默认 15000
571
+ * @param maxLogs 最多收集日志条数,默认 200
572
+ * @returns 返回日志、运行状态和判定结果
573
+ * @example
574
+ * const result = await watchDeviceLogsBySse("192.168.1.10", 9800, 10000, 100)
575
+ */
576
+ async function watchDeviceLogsBySse(ip, port, durationMs = 15000, maxLogs = 200) {
577
+ if (!ip.trim()) {
578
+ throw new Error("监听日志失败: 设备 IP 不能为空");
579
+ }
580
+ if (!Number.isInteger(port) || port < 1 || port > 65535) {
581
+ throw new Error(`监听日志失败: 无效端口 ${port}`);
582
+ }
583
+ if (!Number.isInteger(durationMs) || durationMs < 1000) {
584
+ throw new Error(`监听日志失败: 无效 durationMs ${durationMs}`);
585
+ }
586
+ if (!Number.isInteger(maxLogs) || maxLogs < 1) {
587
+ throw new Error(`监听日志失败: 无效 maxLogs ${maxLogs}`);
588
+ }
589
+ const url = `http://${ip}:${port}/logger/sse`;
590
+ const logs = [];
591
+ const runtimeStatus = [];
592
+ const controller = new AbortController();
593
+ const timeoutHandle = setTimeout(() => {
594
+ controller.abort();
595
+ }, durationMs);
596
+ try {
597
+ const response = await fetch(url, {
598
+ method: "GET",
599
+ signal: controller.signal,
600
+ headers: {
601
+ Accept: "text/event-stream",
602
+ },
603
+ });
604
+ if (!response.ok || !response.body) {
605
+ throw new Error(`监听日志失败: ${response.status} ${response.statusText || "unknown"}`);
606
+ }
607
+ const reader = response.body.getReader();
608
+ const decoder = new TextDecoder("utf-8");
609
+ let buffer = "";
610
+ while (true) {
611
+ const { value, done } = await reader.read();
612
+ if (done) {
613
+ break;
614
+ }
615
+ buffer += decoder.decode(value, { stream: true });
616
+ // SSE 事件以双换行分隔,这里逐块解析,避免半包解析错误
617
+ let splitIndex = buffer.search(/\r?\n\r?\n/);
618
+ while (splitIndex >= 0) {
619
+ const rawBlock = buffer.slice(0, splitIndex);
620
+ buffer = buffer.slice(splitIndex + (buffer[splitIndex] === "\r" ? 4 : 2));
621
+ const event = parseSseEventBlock(rawBlock);
622
+ if (event.data) {
623
+ try {
624
+ const parsed = JSON.parse(event.data);
625
+ if (event.event === "log") {
626
+ if (logs.length < maxLogs) {
627
+ logs.push(normalizeLogEntry(parsed));
628
+ }
629
+ }
630
+ else if (event.event === "runtime_status") {
631
+ runtimeStatus.push({
632
+ raw: parsed,
633
+ timestamp: new Date().toISOString(),
634
+ });
635
+ }
636
+ }
637
+ catch {
638
+ // 兼容非 JSON 数据:不抛错,继续监听后续事件
639
+ }
640
+ }
641
+ if (logs.length >= maxLogs) {
642
+ controller.abort();
643
+ break;
644
+ }
645
+ splitIndex = buffer.search(/\r?\n\r?\n/);
646
+ }
647
+ }
648
+ }
649
+ catch (error) {
650
+ // 监听超时是预期行为,忽略 AbortError 并正常输出已采集日志
651
+ if (!(error instanceof Error &&
652
+ (error.name === "AbortError" ||
653
+ error.message === "The operation was aborted."))) {
654
+ throw error;
655
+ }
656
+ }
657
+ finally {
658
+ clearTimeout(timeoutHandle);
659
+ }
660
+ const findings = analyzeDeviceLogs(logs);
661
+ const errorCount = findings.filter((item) => item.severity === "error").length;
662
+ const warnCount = findings.filter((item) => item.severity === "warn").length;
663
+ const summary = findings.length === 0
664
+ ? `日志监听完成:共 ${logs.length} 条日志,未发现明显异常。`
665
+ : `日志监听完成:共 ${logs.length} 条日志,发现 ${errorCount} 条 error 风险、${warnCount} 条 warn 风险。`;
666
+ return {
667
+ logs,
668
+ runtimeStatus,
669
+ findings,
670
+ summary,
671
+ };
672
+ }
367
673
  /**
368
674
  * 通过 WS 发送设备动作请求
369
675
  * @param action 动作名称,支持 run、runUI、stop
@@ -0,0 +1,12 @@
1
+ /**
2
+ * 读取当前包版本号(自动定位 package.json)
3
+ * @param fallbackVersion 读取失败时回退版本号,默认 dev
4
+ * @returns 返回版本号字符串
5
+ * @example
6
+ * const version = readPackageVersion()
7
+ */
8
+ export declare function readPackageVersion(fallbackVersion?: string): string;
9
+ /**
10
+ * 当前包版本号(模块加载时读取)
11
+ */
12
+ export declare const PACKAGE_VERSION: string;
@@ -0,0 +1,63 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.PACKAGE_VERSION = void 0;
37
+ exports.readPackageVersion = readPackageVersion;
38
+ const fsExtra = __importStar(require("fs-extra"));
39
+ const path = __importStar(require("path"));
40
+ /**
41
+ * 读取当前包版本号(自动定位 package.json)
42
+ * @param fallbackVersion 读取失败时回退版本号,默认 dev
43
+ * @returns 返回版本号字符串
44
+ * @example
45
+ * const version = readPackageVersion()
46
+ */
47
+ function readPackageVersion(fallbackVersion = "dev") {
48
+ try {
49
+ const packageJsonPath = path.resolve(__dirname, "..", "package.json");
50
+ const packageJson = fsExtra.readJSONSync(packageJsonPath);
51
+ if (typeof packageJson.version === "string" && packageJson.version.trim()) {
52
+ return packageJson.version.trim();
53
+ }
54
+ }
55
+ catch {
56
+ // ignore and fallback
57
+ }
58
+ return fallbackVersion;
59
+ }
60
+ /**
61
+ * 当前包版本号(模块加载时读取)
62
+ */
63
+ exports.PACKAGE_VERSION = readPackageVersion();