@hangox/mg-cli 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/dist/cli.js +1574 -0
  2. package/dist/cli.js.map +1 -0
  3. package/dist/daemon-runner.js +794 -0
  4. package/dist/daemon-runner.js.map +1 -0
  5. package/dist/index-DNrszrq9.d.ts +568 -0
  6. package/dist/index.d.ts +129 -0
  7. package/dist/index.js +950 -0
  8. package/dist/index.js.map +1 -0
  9. package/dist/server.d.ts +2 -0
  10. package/dist/server.js +689 -0
  11. package/dist/server.js.map +1 -0
  12. package/package.json +5 -1
  13. package/.eslintrc.cjs +0 -26
  14. package/CLAUDE.md +0 -43
  15. package/src/cli/client.ts +0 -266
  16. package/src/cli/commands/execute-code.ts +0 -59
  17. package/src/cli/commands/export-image.ts +0 -193
  18. package/src/cli/commands/get-all-nodes.ts +0 -81
  19. package/src/cli/commands/get-all-pages.ts +0 -118
  20. package/src/cli/commands/get-node-by-id.ts +0 -83
  21. package/src/cli/commands/get-node-by-link.ts +0 -105
  22. package/src/cli/commands/server.ts +0 -130
  23. package/src/cli/index.ts +0 -33
  24. package/src/index.ts +0 -9
  25. package/src/server/connection-manager.ts +0 -211
  26. package/src/server/daemon-runner.ts +0 -22
  27. package/src/server/daemon.ts +0 -211
  28. package/src/server/index.ts +0 -8
  29. package/src/server/logger.ts +0 -117
  30. package/src/server/request-handler.ts +0 -192
  31. package/src/server/websocket-server.ts +0 -297
  32. package/src/shared/constants.ts +0 -90
  33. package/src/shared/errors.ts +0 -131
  34. package/src/shared/index.ts +0 -8
  35. package/src/shared/types.ts +0 -227
  36. package/src/shared/utils.ts +0 -352
  37. package/tests/unit/shared/constants.test.ts +0 -66
  38. package/tests/unit/shared/errors.test.ts +0 -82
  39. package/tests/unit/shared/utils.test.ts +0 -208
  40. package/tsconfig.json +0 -22
  41. package/tsup.config.ts +0 -33
  42. package/vitest.config.ts +0 -22
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server/websocket-server.ts","../src/shared/constants.ts","../src/shared/errors.ts","../src/server/logger.ts","../src/shared/utils.ts","../src/server/connection-manager.ts","../src/server/request-handler.ts"],"sourcesContent":["/**\n * WebSocket 服务器\n */\n\nimport { WebSocketServer, WebSocket } from 'ws'\nimport type { IncomingMessage } from 'node:http'\nimport {\n ConnectionType,\n MessageType,\n DEFAULT_PORT,\n PORT_RANGE_START,\n PORT_RANGE_END,\n HEARTBEAT_INTERVAL,\n} from '../shared/constants.js'\nimport { ErrorCode, MGError } from '../shared/errors.js'\nimport type { BaseMessage, RegisterMessage, ResponseMessage } from '../shared/types.js'\nimport { Logger, createLogger } from './logger.js'\nimport { ConnectionManager, ManagedWebSocket } from './connection-manager.js'\nimport { RequestHandler } from './request-handler.js'\n\nexport interface ServerOptions {\n /** 监听端口 */\n port?: number\n /** 日志选项 */\n logger?: Logger\n}\n\n/**\n * MG WebSocket 服务器\n */\nexport class MGServer {\n private wss: WebSocketServer | null = null\n private logger: Logger\n private connectionManager: ConnectionManager\n private requestHandler: RequestHandler\n\n private port: number\n private isRunning = false\n\n constructor(options: ServerOptions = {}) {\n this.port = options.port || DEFAULT_PORT\n this.logger = options.logger || createLogger()\n this.connectionManager = new ConnectionManager(this.logger)\n this.requestHandler = new RequestHandler(this.connectionManager, this.logger)\n }\n\n /**\n * 启动服务器\n */\n async start(): Promise<number> {\n if (this.isRunning) {\n throw new MGError(ErrorCode.SERVER_ALREADY_RUNNING, 'Server 已在运行中')\n }\n\n // 尝试绑定端口\n const port = await this.findAvailablePort()\n\n return new Promise((resolve, reject) => {\n this.wss = new WebSocketServer({ port })\n\n this.wss.on('listening', () => {\n this.port = port\n this.isRunning = true\n this.logger.info(`Server 启动成功,监听端口: ${port}`)\n\n // 启动心跳检查\n this.connectionManager.startHeartbeatCheck(HEARTBEAT_INTERVAL)\n\n resolve(port)\n })\n\n this.wss.on('error', (error: NodeJS.ErrnoException) => {\n this.logger.error('Server 错误:', error)\n reject(error)\n })\n\n this.wss.on('connection', (ws: WebSocket, request: IncomingMessage) => {\n this.handleConnection(ws, request)\n })\n })\n }\n\n /**\n * 查找可用端口\n */\n private async findAvailablePort(): Promise<number> {\n for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {\n const available = await this.isPortAvailable(port)\n if (available) {\n return port\n }\n this.logger.debug(`端口 ${port} 被占用,尝试下一个`)\n }\n\n throw new MGError(\n ErrorCode.PORT_EXHAUSTED,\n `端口 ${PORT_RANGE_START}-${PORT_RANGE_END} 均被占用`\n )\n }\n\n /**\n * 检查端口是否可用\n */\n private isPortAvailable(port: number): Promise<boolean> {\n return new Promise((resolve) => {\n const testServer = new WebSocketServer({ port })\n\n testServer.on('listening', () => {\n testServer.close()\n resolve(true)\n })\n\n testServer.on('error', () => {\n resolve(false)\n })\n })\n }\n\n /**\n * 处理新连接\n */\n private handleConnection(ws: WebSocket, _request: IncomingMessage): void {\n this.logger.info('新连接建立')\n\n // 等待注册消息\n const registerTimeout = setTimeout(() => {\n this.logger.warn('连接注册超时,关闭连接')\n ws.close()\n }, 5000)\n\n ws.on('message', (data) => {\n try {\n const message = JSON.parse(data.toString()) as BaseMessage\n\n // 处理注册消息\n if (message.type === MessageType.REGISTER) {\n clearTimeout(registerTimeout)\n this.handleRegister(ws, message as RegisterMessage)\n return\n }\n\n // 其他消息需要已注册\n const managedWs = ws as ManagedWebSocket\n if (!managedWs.connectionId) {\n this.logger.warn('未注册的连接发送消息,忽略')\n return\n }\n\n this.handleMessage(managedWs, message)\n } catch (error) {\n this.logger.error('消息解析失败:', error)\n }\n })\n\n ws.on('close', () => {\n clearTimeout(registerTimeout)\n const managedWs = ws as ManagedWebSocket\n if (managedWs.connectionId) {\n this.requestHandler.cleanupConnectionRequests(managedWs.connectionId)\n this.connectionManager.removeConnection(managedWs)\n }\n })\n\n ws.on('error', (error) => {\n this.logger.error('WebSocket 错误:', error)\n })\n }\n\n /**\n * 处理注册消息\n */\n private handleRegister(ws: WebSocket, message: RegisterMessage): void {\n const { connectionType, pageUrl, pageId } = message.data\n\n const managedWs = this.connectionManager.addConnection(\n ws,\n connectionType,\n pageUrl,\n pageId\n )\n\n // 发送注册确认\n const ack: ResponseMessage = {\n id: message.id || '',\n type: MessageType.REGISTER_ACK,\n success: true,\n data: {\n connectionId: managedWs.connectionId,\n pageUrl,\n },\n }\n\n ws.send(JSON.stringify(ack))\n }\n\n /**\n * 处理消息\n */\n private handleMessage(ws: ManagedWebSocket, message: BaseMessage): void {\n this.connectionManager.updateLastActive(ws)\n\n switch (message.type) {\n case MessageType.PING:\n this.handlePing(ws, message)\n break\n\n case MessageType.RESPONSE:\n case MessageType.ERROR:\n // Provider 返回的响应\n this.requestHandler.handleResponse(message as ResponseMessage)\n break\n\n default:\n // Consumer 发送的请求\n if (ws.connectionInfo.type === ConnectionType.CONSUMER) {\n this.requestHandler.handleRequest(ws, message as any)\n }\n break\n }\n }\n\n /**\n * 处理心跳\n */\n private handlePing(ws: ManagedWebSocket, message: BaseMessage): void {\n const pong = {\n type: MessageType.PONG,\n timestamp: (message as any).timestamp || Date.now(),\n }\n ws.send(JSON.stringify(pong))\n }\n\n /**\n * 停止服务器\n */\n async stop(): Promise<void> {\n if (!this.isRunning || !this.wss) {\n return\n }\n\n this.logger.info('正在停止 Server...')\n\n // 清理所有请求\n this.requestHandler.cleanupAll()\n\n // 关闭所有连接\n this.connectionManager.closeAll()\n\n // 关闭服务器\n return new Promise((resolve) => {\n this.wss!.close(() => {\n this.isRunning = false\n this.wss = null\n this.logger.info('Server 已停止')\n resolve()\n })\n })\n }\n\n /**\n * 获取运行状态\n */\n getStatus(): {\n running: boolean\n port: number\n stats: { providers: number; consumers: number; total: number }\n connectedPages: string[]\n } {\n return {\n running: this.isRunning,\n port: this.port,\n stats: this.connectionManager.getStats(),\n connectedPages: this.connectionManager.getConnectedPageUrls(),\n }\n }\n\n /**\n * 获取端口\n */\n getPort(): number {\n return this.port\n }\n\n /**\n * 是否运行中\n */\n isServerRunning(): boolean {\n return this.isRunning\n }\n}\n\n/**\n * 创建服务器实例\n */\nexport function createServer(options?: ServerOptions): MGServer {\n return new MGServer(options)\n}\n","/**\n * MG Plugin 常量定义\n */\n\nimport { homedir } from 'node:os'\nimport { join } from 'node:path'\n\n// ==================== 端口配置 ====================\n\n/** 默认端口 */\nexport const DEFAULT_PORT = 9527\n\n/** 端口范围:起始 */\nexport const PORT_RANGE_START = 9527\n\n/** 端口范围:结束 */\nexport const PORT_RANGE_END = 9536\n\n/** 最大尝试端口数 */\nexport const MAX_PORT_ATTEMPTS = 10\n\n/** 端口扫描超时(毫秒) */\nexport const PORT_SCAN_TIMEOUT = 500\n\n// ==================== 路径配置 ====================\n\n/** 配置目录 */\nexport const CONFIG_DIR = join(homedir(), '.mg-plugin')\n\n/** Server 状态文件 */\nexport const SERVER_INFO_FILE = join(CONFIG_DIR, 'server.json')\n\n/** 日志目录 */\nexport const LOG_DIR = join(CONFIG_DIR, 'logs')\n\n/** Server 日志文件 */\nexport const SERVER_LOG_FILE = join(LOG_DIR, 'server.log')\n\n// ==================== 超时配置 ====================\n\n/** 心跳间隔(毫秒)- 30 秒 */\nexport const HEARTBEAT_INTERVAL = 30000\n\n/** 心跳超时(毫秒)- 90 秒(3 次心跳) */\nexport const HEARTBEAT_TIMEOUT = 90000\n\n/** 请求超时(毫秒)- 30 秒 */\nexport const REQUEST_TIMEOUT = 30000\n\n/** Server 启动等待超时(毫秒)- 5 秒 */\nexport const SERVER_START_TIMEOUT = 5000\n\n/** CLI 重试间隔(毫秒) */\nexport const RETRY_INTERVALS = [1000, 2000, 4000]\n\n/** 最大重试次数 */\nexport const MAX_RETRY_COUNT = 3\n\n// ==================== 连接类型 ====================\n\n/** 连接类型 */\nexport enum ConnectionType {\n /** 获取端 (CLI/MCP) */\n CONSUMER = 'consumer',\n /** 提供端 (Injector) */\n PROVIDER = 'provider',\n}\n\n// ==================== 消息类型 ====================\n\n/** WebSocket 消息类型 */\nexport enum MessageType {\n // 系统消息\n PING = 'ping',\n PONG = 'pong',\n REGISTER = 'register',\n REGISTER_ACK = 'register_ack',\n\n // 业务消息\n GET_NODE_BY_ID = 'get_node_by_id',\n GET_ALL_NODES = 'get_all_nodes',\n GET_SELECTION = 'get_selection',\n EXPORT_IMAGE = 'export_image',\n EXECUTE_CODE = 'execute_code',\n GET_ALL_PAGES = 'get_all_pages',\n\n // 响应\n RESPONSE = 'response',\n ERROR = 'error',\n}\n","/**\n * MG Plugin 错误码定义\n */\n\n/** 错误码枚举 */\nexport enum ErrorCode {\n /** 无法连接到 MG Server */\n CONNECTION_FAILED = 'E001',\n /** 连接超时 */\n CONNECTION_TIMEOUT = 'E002',\n /** 没有 MasterGo 页面连接到 Server */\n NO_PAGE_CONNECTED = 'E003',\n /** 未找到匹配的页面 */\n PAGE_NOT_FOUND = 'E004',\n /** 节点不存在 */\n NODE_NOT_FOUND = 'E005',\n /** 没有选中任何节点 */\n NO_SELECTION = 'E006',\n /** mg 对象不可用 */\n MG_UNAVAILABLE = 'E007',\n /** 导出图片失败 */\n EXPORT_FAILED = 'E008',\n /** 文件写入失败 */\n FILE_WRITE_FAILED = 'E009',\n /** 无效的 mgp:// 链接格式 */\n INVALID_LINK = 'E010',\n /** 参数校验失败 */\n INVALID_PARAMS = 'E011',\n /** 请求超时 */\n REQUEST_TIMEOUT = 'E012',\n /** 所有备选端口均被占用 */\n PORT_EXHAUSTED = 'E013',\n /** 无法发现 Server (端口扫描失败) */\n SERVER_DISCOVERY_FAILED = 'E014',\n /** 自动启动 Server 失败 */\n SERVER_START_FAILED = 'E015',\n /** Server 已在运行中 */\n SERVER_ALREADY_RUNNING = 'E016',\n /** 连接断开 */\n CONNECTION_LOST = 'E017',\n /** 未知错误 */\n UNKNOWN_ERROR = 'E099',\n}\n\n/** 错误码对应的错误名称 */\nexport const ErrorNames: Record<ErrorCode, string> = {\n [ErrorCode.CONNECTION_FAILED]: 'CONNECTION_FAILED',\n [ErrorCode.CONNECTION_TIMEOUT]: 'CONNECTION_TIMEOUT',\n [ErrorCode.NO_PAGE_CONNECTED]: 'NO_PAGE_CONNECTED',\n [ErrorCode.PAGE_NOT_FOUND]: 'PAGE_NOT_FOUND',\n [ErrorCode.NODE_NOT_FOUND]: 'NODE_NOT_FOUND',\n [ErrorCode.NO_SELECTION]: 'NO_SELECTION',\n [ErrorCode.MG_UNAVAILABLE]: 'MG_UNAVAILABLE',\n [ErrorCode.EXPORT_FAILED]: 'EXPORT_FAILED',\n [ErrorCode.FILE_WRITE_FAILED]: 'FILE_WRITE_FAILED',\n [ErrorCode.INVALID_LINK]: 'INVALID_LINK',\n [ErrorCode.INVALID_PARAMS]: 'INVALID_PARAMS',\n [ErrorCode.REQUEST_TIMEOUT]: 'REQUEST_TIMEOUT',\n [ErrorCode.PORT_EXHAUSTED]: 'PORT_EXHAUSTED',\n [ErrorCode.SERVER_DISCOVERY_FAILED]: 'SERVER_DISCOVERY_FAILED',\n [ErrorCode.SERVER_START_FAILED]: 'SERVER_START_FAILED',\n [ErrorCode.SERVER_ALREADY_RUNNING]: 'SERVER_ALREADY_RUNNING',\n [ErrorCode.CONNECTION_LOST]: 'CONNECTION_LOST',\n [ErrorCode.UNKNOWN_ERROR]: 'UNKNOWN_ERROR',\n}\n\n/** 错误码对应的默认消息 */\nexport const ErrorMessages: Record<ErrorCode, string> = {\n [ErrorCode.CONNECTION_FAILED]: '无法连接到 MG Server',\n [ErrorCode.CONNECTION_TIMEOUT]: '连接超时',\n [ErrorCode.NO_PAGE_CONNECTED]: '没有 MasterGo 页面连接到 Server',\n [ErrorCode.PAGE_NOT_FOUND]: '未找到匹配的页面',\n [ErrorCode.NODE_NOT_FOUND]: '节点不存在',\n [ErrorCode.NO_SELECTION]: '没有选中任何节点',\n [ErrorCode.MG_UNAVAILABLE]: 'mg 对象不可用',\n [ErrorCode.EXPORT_FAILED]: '导出图片失败',\n [ErrorCode.FILE_WRITE_FAILED]: '文件写入失败',\n [ErrorCode.INVALID_LINK]: '无效的 mgp:// 链接格式',\n [ErrorCode.INVALID_PARAMS]: '参数校验失败',\n [ErrorCode.REQUEST_TIMEOUT]: '请求超时',\n [ErrorCode.PORT_EXHAUSTED]: '所有备选端口均被占用',\n [ErrorCode.SERVER_DISCOVERY_FAILED]: '无法发现 Server (端口扫描失败)',\n [ErrorCode.SERVER_START_FAILED]: '自动启动 Server 失败',\n [ErrorCode.SERVER_ALREADY_RUNNING]: 'Server 已在运行中',\n [ErrorCode.CONNECTION_LOST]: '连接断开',\n [ErrorCode.UNKNOWN_ERROR]: '未知错误',\n}\n\n/** MG Plugin 错误类 */\nexport class MGError extends Error {\n /** 错误码 */\n code: ErrorCode\n /** 错误名称 */\n errorName: string\n /** 额外详情 */\n details?: Record<string, unknown>\n\n constructor(code: ErrorCode, message?: string, details?: Record<string, unknown>) {\n super(message || ErrorMessages[code])\n this.name = 'MGError'\n this.code = code\n this.errorName = ErrorNames[code]\n this.details = details\n }\n\n /** 转换为 JSON 格式 */\n toJSON() {\n return {\n code: this.code,\n name: this.errorName,\n message: this.message,\n details: this.details,\n }\n }\n\n /** 格式化输出 */\n toString() {\n return `错误 [${this.code}]: ${this.message}`\n }\n}\n\n/**\n * 创建错误\n */\nexport function createError(\n code: ErrorCode,\n message?: string,\n details?: Record<string, unknown>\n): MGError {\n return new MGError(code, message, details)\n}\n","/**\n * 日志模块\n */\n\nimport { appendFileSync, existsSync, mkdirSync } from 'node:fs'\nimport { dirname } from 'node:path'\nimport { SERVER_LOG_FILE } from '../shared/constants.js'\nimport { formatLogTime } from '../shared/utils.js'\n\nexport enum LogLevel {\n DEBUG = 'DEBUG',\n INFO = 'INFO',\n WARN = 'WARN',\n ERROR = 'ERROR',\n}\n\nexport interface LoggerOptions {\n /** 是否输出到控制台 */\n console?: boolean\n /** 是否输出到文件 */\n file?: boolean\n /** 日志文件路径 */\n filePath?: string\n /** 最小日志级别 */\n minLevel?: LogLevel\n}\n\nconst levelPriority: Record<LogLevel, number> = {\n [LogLevel.DEBUG]: 0,\n [LogLevel.INFO]: 1,\n [LogLevel.WARN]: 2,\n [LogLevel.ERROR]: 3,\n}\n\n/**\n * 日志记录器\n */\nexport class Logger {\n private options: Required<LoggerOptions>\n\n constructor(options: LoggerOptions = {}) {\n this.options = {\n console: options.console ?? true,\n file: options.file ?? false,\n filePath: options.filePath ?? SERVER_LOG_FILE,\n minLevel: options.minLevel ?? LogLevel.INFO,\n }\n\n // 确保日志目录存在\n if (this.options.file) {\n const dir = dirname(this.options.filePath)\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true })\n }\n }\n }\n\n /**\n * 记录日志\n */\n private log(level: LogLevel, message: string, ...args: unknown[]): void {\n // 检查日志级别\n if (levelPriority[level] < levelPriority[this.options.minLevel]) {\n return\n }\n\n const timestamp = formatLogTime()\n const formattedMessage = `[${timestamp}] [${level}] ${message}`\n\n // 输出到控制台\n if (this.options.console) {\n const consoleMethod = level === LogLevel.ERROR ? console.error : console.log\n if (args.length > 0) {\n consoleMethod(formattedMessage, ...args)\n } else {\n consoleMethod(formattedMessage)\n }\n }\n\n // 输出到文件\n if (this.options.file) {\n try {\n const fileMessage =\n args.length > 0\n ? `${formattedMessage} ${JSON.stringify(args)}\\n`\n : `${formattedMessage}\\n`\n appendFileSync(this.options.filePath, fileMessage)\n } catch (error) {\n // 文件写入失败时静默处理\n if (this.options.console) {\n console.error('日志文件写入失败:', error)\n }\n }\n }\n }\n\n debug(message: string, ...args: unknown[]): void {\n this.log(LogLevel.DEBUG, message, ...args)\n }\n\n info(message: string, ...args: unknown[]): void {\n this.log(LogLevel.INFO, message, ...args)\n }\n\n warn(message: string, ...args: unknown[]): void {\n this.log(LogLevel.WARN, message, ...args)\n }\n\n error(message: string, ...args: unknown[]): void {\n this.log(LogLevel.ERROR, message, ...args)\n }\n}\n\n/** 创建默认日志记录器 */\nexport function createLogger(options?: LoggerOptions): Logger {\n return new Logger(options)\n}\n","/**\n * MG Plugin 工具函数\n */\n\nimport { existsSync, mkdirSync, readFileSync, writeFileSync, unlinkSync } from 'node:fs'\nimport { dirname, resolve, isAbsolute } from 'node:path'\nimport { CONFIG_DIR, SERVER_INFO_FILE, LOG_DIR } from './constants.js'\nimport type { ServerInfo } from './types.js'\n\n// ==================== 文件操作 ====================\n\n/**\n * 确保目录存在\n */\nexport function ensureDir(dir: string): void {\n if (!existsSync(dir)) {\n mkdirSync(dir, { recursive: true })\n }\n}\n\n/**\n * 确保配置目录存在\n */\nexport function ensureConfigDir(): void {\n ensureDir(CONFIG_DIR)\n ensureDir(LOG_DIR)\n}\n\n/**\n * 读取 Server 信息文件\n */\nexport function readServerInfo(): ServerInfo | null {\n try {\n if (!existsSync(SERVER_INFO_FILE)) {\n return null\n }\n const content = readFileSync(SERVER_INFO_FILE, 'utf-8')\n return JSON.parse(content) as ServerInfo\n } catch {\n return null\n }\n}\n\n/**\n * 写入 Server 信息文件\n */\nexport function writeServerInfo(info: ServerInfo): void {\n ensureConfigDir()\n writeFileSync(SERVER_INFO_FILE, JSON.stringify(info, null, 2), 'utf-8')\n}\n\n/**\n * 删除 Server 信息文件\n */\nexport function deleteServerInfo(): void {\n try {\n if (existsSync(SERVER_INFO_FILE)) {\n unlinkSync(SERVER_INFO_FILE)\n }\n } catch {\n // 忽略删除错误\n }\n}\n\n/**\n * 解析输出路径(支持相对路径和绝对路径)\n */\nexport function resolveOutputPath(outputPath: string): string {\n if (isAbsolute(outputPath)) {\n return outputPath\n }\n return resolve(process.cwd(), outputPath)\n}\n\n/**\n * 确保输出路径的目录存在\n */\nexport function ensureOutputDir(outputPath: string): void {\n const dir = dirname(outputPath)\n ensureDir(dir)\n}\n\n// ==================== 进程管理 ====================\n\n/**\n * 检查进程是否存在\n */\nexport function isProcessRunning(pid: number): boolean {\n try {\n // 发送信号 0 不会终止进程,但如果进程不存在会抛出错误\n process.kill(pid, 0)\n return true\n } catch {\n return false\n }\n}\n\n/**\n * 终止进程\n */\nexport function killProcess(pid: number): boolean {\n try {\n process.kill(pid, 'SIGTERM')\n return true\n } catch {\n return false\n }\n}\n\n// ==================== URL 处理 ====================\n\n/**\n * 标准化页面 URL\n *\n * 输入: https://mastergo.netease.com/file/174135798361888?fileOpenFrom=home&page_id=0%3A8347\n * 输出: mastergo.netease.com/file/174135798361888\n */\nexport function normalizePageUrl(url: string): string {\n try {\n const urlObj = new URL(url)\n // 返回 host + pathname,去掉 https:// 前缀和查询参数\n return urlObj.host + urlObj.pathname\n } catch {\n // 如果已经是标准化的格式,直接返回\n return url\n }\n}\n\n/**\n * 检查是否是设计页面 URL\n */\nexport function isDesignPageUrl(url: string): boolean {\n // 匹配 /file/数字 格式\n return /\\/file\\/\\d+/.test(url)\n}\n\n/**\n * 解析 mgp:// 链接\n *\n * 支持的格式 (queryParams 格式,nodeId 需要 URL 编码):\n * - mgp://mastergo.netease.com/file/174135798361888?nodeId=123%3A456 (单个节点)\n * - mgp://mastergo.netease.com/file/174135798361888?nodeId=0%3A8633&nodePath=314%3A13190%2F0%3A8633 (带父节点路径)\n *\n * 输出: { pageUrl: 'mastergo.netease.com/file/174135798361888', nodeId: '0:8633', nodePath: ['314:13190', '0:8633'] }\n */\nexport function parseMgpLink(link: string): { pageUrl: string; nodeId: string; nodePath?: string[] } | null {\n // 检查是否是 mgp:// 协议\n if (!link.startsWith('mgp://')) {\n return null\n }\n\n try {\n // 移除 mgp:// 前缀,构造为可解析的 URL\n const urlPart = link.slice(6) // 移除 'mgp://'\n const questionMarkIndex = urlPart.indexOf('?')\n\n if (questionMarkIndex === -1) {\n // 没有查询参数,无效格式\n return null\n }\n\n const pageUrl = urlPart.slice(0, questionMarkIndex)\n const queryString = urlPart.slice(questionMarkIndex + 1)\n\n // 解析查询参数\n const params = new URLSearchParams(queryString)\n const encodedNodeId = params.get('nodeId')\n\n if (!encodedNodeId) {\n return null\n }\n\n // 解码 nodeId\n const nodeId = decodeURIComponent(encodedNodeId)\n\n // 验证 nodeId 格式:支持单个节点 (数字:数字) 或带路径的格式 (数字:数字/数字:数字/...)\n if (!/^(\\d+:\\d+)(\\/\\d+:\\d+)*$/.test(nodeId)) {\n return null\n }\n\n // 解析可选的 nodePath\n const encodedNodePath = params.get('nodePath')\n let nodePath: string[] | undefined\n\n if (encodedNodePath) {\n const decodedNodePath = decodeURIComponent(encodedNodePath)\n nodePath = decodedNodePath.split('/').filter(Boolean)\n // 验证每个路径段的格式\n if (!nodePath.every(segment => /^\\d+:\\d+$/.test(segment))) {\n return null\n }\n }\n\n return {\n pageUrl,\n nodeId,\n nodePath,\n }\n } catch {\n return null\n }\n}\n\n/**\n * 生成 mgp:// 链接\n *\n * @param pageUrl 页面 URL(会被标准化)\n * @param nodeId 节点 ID(会被 URL 编码)\n * @param nodePath 可选的父节点路径(会被 URL 编码)\n */\nexport function generateMgpLink(pageUrl: string, nodeId: string, nodePath?: string[]): string {\n const normalizedUrl = normalizePageUrl(pageUrl)\n const encodedNodeId = encodeURIComponent(nodeId)\n\n let link = `mgp://${normalizedUrl}?nodeId=${encodedNodeId}`\n\n if (nodePath && nodePath.length > 0) {\n const encodedNodePath = encodeURIComponent(nodePath.join('/'))\n link += `&nodePath=${encodedNodePath}`\n }\n\n return link\n}\n\n// ==================== 输出格式化 ====================\n\n/**\n * 格式化文件大小\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes < 1024) {\n return `${bytes} 字节`\n }\n const kb = bytes / 1024\n if (kb < 1024) {\n return `${kb.toFixed(2)} KB`\n }\n const mb = kb / 1024\n return `${mb.toFixed(2)} MB`\n}\n\n/**\n * 格式化时间间隔\n */\nexport function formatDuration(ms: number): string {\n const seconds = Math.floor(ms / 1000)\n const minutes = Math.floor(seconds / 60)\n const hours = Math.floor(minutes / 60)\n const days = Math.floor(hours / 24)\n\n if (days > 0) {\n return `${days} 天 ${hours % 24} 小时`\n }\n if (hours > 0) {\n return `${hours} 小时 ${minutes % 60} 分钟`\n }\n if (minutes > 0) {\n return `${minutes} 分钟 ${seconds % 60} 秒`\n }\n return `${seconds} 秒`\n}\n\n/**\n * 生成唯一 ID\n */\nexport function generateId(): string {\n return `${Date.now()}_${Math.random().toString(36).substring(2, 9)}`\n}\n\n/**\n * 获取当前时间的 ISO 格式字符串\n */\nexport function getCurrentISOTime(): string {\n return new Date().toISOString()\n}\n\n/**\n * 格式化日期时间用于日志\n */\nexport function formatLogTime(date: Date = new Date()): string {\n const year = date.getFullYear()\n const month = String(date.getMonth() + 1).padStart(2, '0')\n const day = String(date.getDate()).padStart(2, '0')\n const hours = String(date.getHours()).padStart(2, '0')\n const minutes = String(date.getMinutes()).padStart(2, '0')\n const seconds = String(date.getSeconds()).padStart(2, '0')\n return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`\n}\n\n// ==================== FileId 提取 ====================\n\n/**\n * 从完整 URL 提取 fileId\n * \n * 支持格式:\n * - https://mastergo.netease.com/file/174875497054651?page_id=321%3A11979\n * - mastergo.netease.com/file/174875497054651\n * \n * @returns fileId 或 null\n */\nexport function extractFileIdFromUrl(url: string): string | null {\n // 匹配 /file/数字 格式\n const match = url.match(/\\/file\\/(\\d+)/)\n return match ? match[1] : null\n}\n\n/**\n * 从 mgp:// 链接提取 fileId\n * \n * 支持格式:\n * - mgp://mastergo.netease.com/file/174875497054651?nodeId=xxx\n * \n * @returns fileId 或 null\n */\nexport function extractFileIdFromMgpLink(link: string): string | null {\n if (!link.startsWith('mgp://')) {\n return null\n }\n return extractFileIdFromUrl(link)\n}\n\n/**\n * 统一处理输入,提取 fileId\n * \n * 支持三种输入格式:\n * 1. 完整 URL: https://mastergo.netease.com/file/174875497054651?page_id=xxx\n * 2. mgp 协议: mgp://mastergo.netease.com/file/174875497054651?nodeId=xxx\n * 3. 纯 fileId: 174875497054651\n * \n * @returns fileId 或 null\n */\nexport function extractFileId(input: string): string | null {\n const trimmed = input.trim()\n \n // 1. 纯数字 fileId\n if (/^\\d+$/.test(trimmed)) {\n return trimmed\n }\n \n // 2. mgp:// 协议\n if (trimmed.startsWith('mgp://')) {\n return extractFileIdFromMgpLink(trimmed)\n }\n \n // 3. 完整 URL (http:// 或 https://)\n if (trimmed.startsWith('http://') || trimmed.startsWith('https://')) {\n return extractFileIdFromUrl(trimmed)\n }\n \n // 4. 尝试作为不带协议的 URL 解析\n return extractFileIdFromUrl(trimmed)\n}\n","/**\n * 连接管理器\n * 管理 Provider 和 Consumer 的 WebSocket 连接\n */\n\nimport type { WebSocket } from 'ws'\nimport { ConnectionType, HEARTBEAT_TIMEOUT } from '../shared/constants.js'\nimport type { ConnectionInfo } from '../shared/types.js'\nimport { generateId } from '../shared/utils.js'\nimport { Logger } from './logger.js'\n\n/** 带连接信息的 WebSocket */\nexport interface ManagedWebSocket extends WebSocket {\n /** 连接 ID */\n connectionId: string\n /** 连接信息 */\n connectionInfo: ConnectionInfo\n /** 是否存活 */\n isAlive: boolean\n}\n\n/**\n * 连接管理器\n */\nexport class ConnectionManager {\n private logger: Logger\n\n /** Provider 连接(按页面 URL 索引) */\n private providers = new Map<string, ManagedWebSocket>()\n\n /** Consumer 连接 */\n private consumers = new Map<string, ManagedWebSocket>()\n\n /** 所有连接(按 ID 索引) */\n private allConnections = new Map<string, ManagedWebSocket>()\n\n /** 心跳检查定时器 */\n private heartbeatTimer: NodeJS.Timeout | null = null\n\n constructor(logger: Logger) {\n this.logger = logger\n }\n\n /**\n * 启动心跳检查\n */\n startHeartbeatCheck(interval: number = 30000): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n }\n\n this.heartbeatTimer = setInterval(() => {\n this.checkHeartbeats()\n }, interval)\n }\n\n /**\n * 停止心跳检查\n */\n stopHeartbeatCheck(): void {\n if (this.heartbeatTimer) {\n clearInterval(this.heartbeatTimer)\n this.heartbeatTimer = null\n }\n }\n\n /**\n * 检查所有连接的心跳\n */\n private checkHeartbeats(): void {\n const now = Date.now()\n\n for (const [id, ws] of this.allConnections) {\n const lastActive = ws.connectionInfo.lastActiveAt.getTime()\n const elapsed = now - lastActive\n\n if (elapsed > HEARTBEAT_TIMEOUT) {\n this.logger.warn(`连接 ${id} 心跳超时,关闭连接`)\n this.removeConnection(ws)\n ws.terminate()\n }\n }\n }\n\n /**\n * 添加连接\n */\n addConnection(\n ws: WebSocket,\n type: ConnectionType,\n pageUrl?: string,\n pageId?: string\n ): ManagedWebSocket {\n const connectionId = generateId()\n const now = new Date()\n\n const connectionInfo: ConnectionInfo = {\n id: connectionId,\n type,\n pageUrl,\n pageId,\n connectedAt: now,\n lastActiveAt: now,\n }\n\n const managedWs = ws as ManagedWebSocket\n managedWs.connectionId = connectionId\n managedWs.connectionInfo = connectionInfo\n managedWs.isAlive = true\n\n this.allConnections.set(connectionId, managedWs)\n\n if (type === ConnectionType.PROVIDER && pageUrl) {\n // 如果已有同页面的连接,先移除旧的\n const existing = this.providers.get(pageUrl)\n if (existing) {\n this.logger.info(`页面 ${pageUrl} 已有连接,替换为新连接`)\n this.removeConnection(existing)\n existing.close()\n }\n this.providers.set(pageUrl, managedWs)\n this.logger.info(`Provider 连接: ${pageUrl}`)\n } else if (type === ConnectionType.CONSUMER) {\n this.consumers.set(connectionId, managedWs)\n this.logger.info(`Consumer 连接: ${connectionId}`)\n }\n\n return managedWs\n }\n\n /**\n * 移除连接\n */\n removeConnection(ws: ManagedWebSocket): void {\n const { connectionId, connectionInfo } = ws\n\n this.allConnections.delete(connectionId)\n\n if (connectionInfo.type === ConnectionType.PROVIDER && connectionInfo.pageUrl) {\n this.providers.delete(connectionInfo.pageUrl)\n this.logger.info(`Provider 断开: ${connectionInfo.pageUrl}`)\n } else if (connectionInfo.type === ConnectionType.CONSUMER) {\n this.consumers.delete(connectionId)\n this.logger.info(`Consumer 断开: ${connectionId}`)\n }\n }\n\n /**\n * 更新连接活跃时间\n */\n updateLastActive(ws: ManagedWebSocket): void {\n ws.connectionInfo.lastActiveAt = new Date()\n ws.isAlive = true\n }\n\n /**\n * 根据页面 URL 查找 Provider\n */\n findProviderByPageUrl(pageUrl: string): ManagedWebSocket | undefined {\n return this.providers.get(pageUrl)\n }\n\n /**\n * 获取第一个可用的 Provider\n */\n getFirstProvider(): ManagedWebSocket | undefined {\n const iterator = this.providers.values()\n const first = iterator.next()\n return first.value\n }\n\n /**\n * 获取所有 Provider 信息\n */\n getAllProviders(): ConnectionInfo[] {\n return Array.from(this.providers.values()).map((ws) => ws.connectionInfo)\n }\n\n /**\n * 获取连接统计\n */\n getStats(): { providers: number; consumers: number; total: number } {\n return {\n providers: this.providers.size,\n consumers: this.consumers.size,\n total: this.allConnections.size,\n }\n }\n\n /**\n * 获取所有已连接的页面 URL\n */\n getConnectedPageUrls(): string[] {\n return Array.from(this.providers.keys())\n }\n\n /**\n * 关闭所有连接\n */\n closeAll(): void {\n this.stopHeartbeatCheck()\n\n for (const ws of this.allConnections.values()) {\n ws.close()\n }\n\n this.providers.clear()\n this.consumers.clear()\n this.allConnections.clear()\n }\n}\n","/**\n * 请求处理器\n * 处理 Consumer 请求,转发给 Provider\n */\n\nimport type { ManagedWebSocket } from './connection-manager.js'\nimport { ConnectionManager } from './connection-manager.js'\nimport { Logger } from './logger.js'\nimport { MessageType, REQUEST_TIMEOUT } from '../shared/constants.js'\nimport { ErrorCode, MGError } from '../shared/errors.js'\nimport type { RequestMessage, ResponseMessage } from '../shared/types.js'\nimport { generateId } from '../shared/utils.js'\n\n/** 待处理的请求 */\ninterface PendingRequest {\n /** 请求 ID */\n id: string\n /** Consumer WebSocket */\n consumer: ManagedWebSocket\n /** 超时定时器 */\n timer: NodeJS.Timeout\n /** 请求时间 */\n timestamp: number\n}\n\n/**\n * 请求处理器\n */\nexport class RequestHandler {\n private logger: Logger\n private connectionManager: ConnectionManager\n\n /** 待处理的请求 */\n private pendingRequests = new Map<string, PendingRequest>()\n\n constructor(connectionManager: ConnectionManager, logger: Logger) {\n this.connectionManager = connectionManager\n this.logger = logger\n }\n\n /**\n * 处理 Consumer 请求\n */\n async handleRequest(consumer: ManagedWebSocket, message: RequestMessage): Promise<void> {\n const requestId = message.id || generateId()\n const { type, pageUrl, params } = message\n\n this.logger.info(`收到请求: ${type} (${requestId})`, { pageUrl })\n\n // 查找目标 Provider\n let provider: ManagedWebSocket | undefined\n\n if (pageUrl) {\n provider = this.connectionManager.findProviderByPageUrl(pageUrl)\n if (!provider) {\n this.sendError(consumer, requestId, ErrorCode.PAGE_NOT_FOUND, `未找到页面: ${pageUrl}`)\n return\n }\n } else {\n // 没有指定 pageUrl,使用第一个可用的 Provider\n provider = this.connectionManager.getFirstProvider()\n if (!provider) {\n this.sendError(consumer, requestId, ErrorCode.NO_PAGE_CONNECTED, '没有页面连接到 Server')\n return\n }\n }\n\n // 创建超时定时器\n const timer = setTimeout(() => {\n this.handleTimeout(requestId)\n }, REQUEST_TIMEOUT)\n\n // 保存待处理请求\n this.pendingRequests.set(requestId, {\n id: requestId,\n consumer,\n timer,\n timestamp: Date.now(),\n })\n\n // 转发请求给 Provider\n const forwardMessage: RequestMessage = {\n id: requestId,\n type,\n pageUrl: pageUrl || provider.connectionInfo.pageUrl,\n params,\n timestamp: Date.now(),\n }\n\n try {\n provider.send(JSON.stringify(forwardMessage))\n this.logger.info(`请求转发: ${type} -> ${provider.connectionInfo.pageUrl}`)\n } catch {\n this.cleanupRequest(requestId)\n this.sendError(consumer, requestId, ErrorCode.CONNECTION_FAILED, '转发请求失败')\n }\n }\n\n /**\n * 处理 Provider 响应\n */\n handleResponse(response: ResponseMessage): void {\n const { id } = response\n const pending = this.pendingRequests.get(id)\n\n if (!pending) {\n this.logger.warn(`收到未知请求的响应: ${id}`)\n return\n }\n\n this.cleanupRequest(id)\n\n // 转发响应给 Consumer\n try {\n pending.consumer.send(JSON.stringify(response))\n this.logger.info(\n `响应返回: ${id} (${response.success ? '成功' : '失败'})`\n )\n } catch (error) {\n this.logger.error(`响应转发失败: ${id}`, error)\n }\n }\n\n /**\n * 处理请求超时\n */\n private handleTimeout(requestId: string): void {\n const pending = this.pendingRequests.get(requestId)\n if (!pending) return\n\n this.logger.warn(`请求超时: ${requestId}`)\n this.cleanupRequest(requestId)\n\n this.sendError(pending.consumer, requestId, ErrorCode.REQUEST_TIMEOUT, '请求超时')\n }\n\n /**\n * 清理请求\n */\n private cleanupRequest(requestId: string): void {\n const pending = this.pendingRequests.get(requestId)\n if (pending) {\n clearTimeout(pending.timer)\n this.pendingRequests.delete(requestId)\n }\n }\n\n /**\n * 发送错误响应\n */\n private sendError(\n consumer: ManagedWebSocket,\n requestId: string,\n code: ErrorCode,\n message: string\n ): void {\n const error = new MGError(code, message)\n const response: ResponseMessage = {\n id: requestId,\n type: MessageType.ERROR,\n success: false,\n data: null,\n error: error.toJSON(),\n }\n\n try {\n consumer.send(JSON.stringify(response))\n } catch (err) {\n this.logger.error(`发送错误响应失败: ${requestId}`, err)\n }\n }\n\n /**\n * 清理特定连接的所有待处理请求\n */\n cleanupConnectionRequests(connectionId: string): void {\n for (const [requestId, pending] of this.pendingRequests) {\n if (pending.consumer.connectionId === connectionId) {\n this.cleanupRequest(requestId)\n }\n }\n }\n\n /**\n * 清理所有待处理请求\n */\n cleanupAll(): void {\n for (const [requestId] of this.pendingRequests) {\n this.cleanupRequest(requestId)\n }\n }\n}\n"],"mappings":"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","names":["existsSync","mkdirSync","dirname","LogLevel","dirname","existsSync","mkdirSync","resolve"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hangox/mg-cli",
3
- "version": "1.0.0",
3
+ "version": "1.0.1",
4
4
  "description": "MasterGo Plugin CLI 工具和 WebSocket 服务器",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -8,6 +8,10 @@
8
8
  "bin": {
9
9
  "mg-cli": "./bin/mg-cli.js"
10
10
  },
11
+ "files": [
12
+ "dist",
13
+ "bin"
14
+ ],
11
15
  "scripts": {
12
16
  "dev": "tsup --watch",
13
17
  "build": "tsup",
package/.eslintrc.cjs DELETED
@@ -1,26 +0,0 @@
1
- module.exports = {
2
- root: true,
3
- env: {
4
- node: true,
5
- es2022: true,
6
- },
7
- parser: '@typescript-eslint/parser',
8
- parserOptions: {
9
- ecmaVersion: 2022,
10
- sourceType: 'module',
11
- },
12
- plugins: ['@typescript-eslint'],
13
- extends: [
14
- 'eslint:recommended',
15
- 'plugin:@typescript-eslint/recommended',
16
- ],
17
- rules: {
18
- // 允许使用 console(CLI 工具需要输出)
19
- 'no-console': 'off',
20
- // TypeScript 相关
21
- '@typescript-eslint/explicit-function-return-type': 'off',
22
- '@typescript-eslint/no-explicit-any': 'warn',
23
- '@typescript-eslint/no-unused-vars': ['error', { argsIgnorePattern: '^_' }],
24
- },
25
- ignorePatterns: ['dist', 'node_modules', '*.config.*'],
26
- }
package/CLAUDE.md DELETED
@@ -1,43 +0,0 @@
1
- # MG CLI
2
-
3
- MasterGo Plugin 的命令行工具和 WebSocket 服务器。
4
-
5
- ## 文档索引
6
-
7
- 详细设计和开发指南请参阅 docs 目录:
8
-
9
- | 文档 | 说明 |
10
- |------|------|
11
- | [00-概述.md](../docs/roles/00-概述.md) | 项目总览、系统架构、测试方案 |
12
- | [01-MGPlugin-Server.md](../docs/roles/01-MGPlugin-Server.md) | Server 设计、守护进程、端口管理 |
13
- | [02-MG-Cli.md](../docs/roles/02-MG-Cli.md) | **CLI 完整文档** - 命令详解、开发指南、测试方案 |
14
- | [06-通用协议.md](../docs/roles/06-通用协议.md) | 消息协议、错误码、心跳机制 |
15
-
16
- ## 快速开始
17
-
18
- ```bash
19
- # 安装依赖
20
- pnpm install
21
-
22
- # 开发模式
23
- pnpm dev
24
-
25
- # 构建
26
- pnpm build
27
-
28
- # 链接到全局
29
- pnpm link --global
30
-
31
- # 测试命令
32
- mg-cli --help
33
- mg-cli server start
34
- mg-cli server status
35
- ```
36
-
37
- ## 一键检查
38
-
39
- ```bash
40
- pnpm check # typecheck + lint + test + build
41
- ```
42
-
43
- **编写代码后务必运行 `pnpm check` 确保所有测试通过**
package/src/cli/client.ts DELETED
@@ -1,266 +0,0 @@
1
- /**
2
- * CLI 客户端
3
- * 负责连接 Server 发送请求
4
- */
5
-
6
- import WebSocket from 'ws'
7
- import { readServerInfo, parseMgpLink } from '../shared/utils.js'
8
- import {
9
- PORT_RANGE_START,
10
- PORT_RANGE_END,
11
- PORT_SCAN_TIMEOUT,
12
- REQUEST_TIMEOUT,
13
- SERVER_START_TIMEOUT,
14
- RETRY_INTERVALS,
15
- MAX_RETRY_COUNT,
16
- ConnectionType,
17
- MessageType,
18
- } from '../shared/constants.js'
19
- import { ErrorCode, MGError, ErrorMessages } from '../shared/errors.js'
20
- import { startServerDaemon } from '../server/daemon.js'
21
- import { generateId } from '../shared/utils.js'
22
- import type { RequestMessage, ResponseMessage } from '../shared/types.js'
23
-
24
- /** 客户端选项 */
25
- export interface ClientOptions {
26
- /** 禁用自动启动 Server */
27
- noAutoStart?: boolean
28
- /** 禁用重试 */
29
- noRetry?: boolean
30
- }
31
-
32
- /**
33
- * MG CLI 客户端
34
- */
35
- export class MGClient {
36
- private ws: WebSocket | null = null
37
- private options: ClientOptions
38
-
39
- constructor(options: ClientOptions = {}) {
40
- this.options = options
41
- }
42
-
43
- /**
44
- * 连接到 Server
45
- */
46
- async connect(): Promise<void> {
47
- // 1. 尝试从文件读取端口
48
- const serverInfo = readServerInfo()
49
- if (serverInfo) {
50
- try {
51
- await this.tryConnect(serverInfo.port)
52
- return
53
- } catch {
54
- // 文件信息失效,继续扫描
55
- }
56
- }
57
-
58
- // 2. 端口扫描
59
- for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
60
- try {
61
- await this.tryConnect(port)
62
- return
63
- } catch {
64
- // 继续尝试下一个端口
65
- }
66
- }
67
-
68
- // 3. 自动启动 Server
69
- if (!this.options.noAutoStart) {
70
- console.log('Server 未运行,正在自动启动...')
71
- try {
72
- const info = await startServerDaemon()
73
- console.log(`Server 已启动,端口: ${info.port}`)
74
-
75
- // 等待 Server 就绪
76
- await this.waitForServer(info.port)
77
- return
78
- } catch (error) {
79
- throw new MGError(
80
- ErrorCode.SERVER_START_FAILED,
81
- `自动启动 Server 失败: ${error instanceof Error ? error.message : error}`
82
- )
83
- }
84
- }
85
-
86
- throw new MGError(ErrorCode.CONNECTION_FAILED, ErrorMessages[ErrorCode.CONNECTION_FAILED])
87
- }
88
-
89
- /**
90
- * 尝试连接指定端口
91
- */
92
- private tryConnect(port: number): Promise<void> {
93
- return new Promise((resolve, reject) => {
94
- const ws = new WebSocket(`ws://localhost:${port}`)
95
- const timer = setTimeout(() => {
96
- ws.close()
97
- reject(new Error('连接超时'))
98
- }, PORT_SCAN_TIMEOUT)
99
-
100
- ws.on('open', () => {
101
- clearTimeout(timer)
102
- this.ws = ws
103
- // 注册为 Consumer
104
- this.register()
105
- resolve()
106
- })
107
-
108
- ws.on('error', (error) => {
109
- clearTimeout(timer)
110
- reject(error)
111
- })
112
- })
113
- }
114
-
115
- /**
116
- * 等待 Server 就绪
117
- */
118
- private async waitForServer(port: number): Promise<void> {
119
- const startTime = Date.now()
120
- const interval = 500
121
-
122
- while (Date.now() - startTime < SERVER_START_TIMEOUT) {
123
- try {
124
- await this.tryConnect(port)
125
- return
126
- } catch {
127
- await new Promise((r) => setTimeout(r, interval))
128
- }
129
- }
130
-
131
- throw new Error('等待 Server 启动超时')
132
- }
133
-
134
- /**
135
- * 注册为 Consumer
136
- */
137
- private register(): void {
138
- if (!this.ws) return
139
-
140
- const message = {
141
- type: MessageType.REGISTER,
142
- data: {
143
- connectionType: ConnectionType.CONSUMER,
144
- },
145
- timestamp: Date.now(),
146
- }
147
-
148
- this.ws.send(JSON.stringify(message))
149
- }
150
-
151
- /**
152
- * 发送请求并等待响应
153
- */
154
- async request<T = unknown>(
155
- type: MessageType,
156
- params?: Record<string, unknown>,
157
- pageUrl?: string
158
- ): Promise<T> {
159
- if (!this.ws) {
160
- throw new MGError(ErrorCode.CONNECTION_FAILED, '未连接到 Server')
161
- }
162
-
163
- const requestId = generateId()
164
- const message: RequestMessage = {
165
- id: requestId,
166
- type,
167
- params,
168
- pageUrl,
169
- timestamp: Date.now(),
170
- }
171
-
172
- return new Promise((resolve, reject) => {
173
- const timer = setTimeout(() => {
174
- reject(new MGError(ErrorCode.REQUEST_TIMEOUT, ErrorMessages[ErrorCode.REQUEST_TIMEOUT]))
175
- }, REQUEST_TIMEOUT)
176
-
177
- const messageHandler = (data: WebSocket.Data) => {
178
- try {
179
- const response = JSON.parse(data.toString()) as ResponseMessage
180
- if (response.id === requestId) {
181
- clearTimeout(timer)
182
- this.ws?.off('message', messageHandler)
183
-
184
- if (response.success) {
185
- resolve(response.data as T)
186
- } else {
187
- const error = response.error
188
- reject(
189
- new MGError(
190
- error?.code || ErrorCode.UNKNOWN_ERROR,
191
- error?.message || '未知错误'
192
- )
193
- )
194
- }
195
- }
196
- } catch {
197
- // 忽略解析错误
198
- }
199
- }
200
-
201
- this.ws!.on('message', messageHandler)
202
- this.ws!.send(JSON.stringify(message))
203
- })
204
- }
205
-
206
- /**
207
- * 带重试的请求
208
- */
209
- async requestWithRetry<T = unknown>(
210
- type: MessageType,
211
- params?: Record<string, unknown>,
212
- pageUrl?: string
213
- ): Promise<T> {
214
- if (this.options.noRetry) {
215
- return this.request<T>(type, params, pageUrl)
216
- }
217
-
218
- let lastError: Error | null = null
219
-
220
- for (let attempt = 0; attempt <= MAX_RETRY_COUNT; attempt++) {
221
- try {
222
- return await this.request<T>(type, params, pageUrl)
223
- } catch (error) {
224
- lastError = error instanceof Error ? error : new Error(String(error))
225
-
226
- // 检查是否是可重试的错误
227
- if (error instanceof MGError) {
228
- const retryable = [ErrorCode.CONNECTION_LOST, ErrorCode.REQUEST_TIMEOUT]
229
- if (!retryable.includes(error.code)) {
230
- throw error
231
- }
232
- }
233
-
234
- // 最后一次尝试不再等待
235
- if (attempt < MAX_RETRY_COUNT) {
236
- const delay = RETRY_INTERVALS[attempt] || RETRY_INTERVALS[RETRY_INTERVALS.length - 1]
237
- await new Promise((r) => setTimeout(r, delay))
238
-
239
- // 重新连接
240
- try {
241
- await this.connect()
242
- } catch {
243
- // 重连失败,继续重试
244
- }
245
- }
246
- }
247
- }
248
-
249
- throw lastError || new MGError(ErrorCode.UNKNOWN_ERROR, '请求失败')
250
- }
251
-
252
- /**
253
- * 关闭连接
254
- */
255
- close(): void {
256
- if (this.ws) {
257
- this.ws.close()
258
- this.ws = null
259
- }
260
- }
261
- }
262
-
263
- /**
264
- * 解析 mgp:// 链接
265
- */
266
- export { parseMgpLink }
@@ -1,59 +0,0 @@
1
- /**
2
- * execute_code 命令
3
- * 在 MasterGo 页面执行自定义 JavaScript 代码
4
- */
5
-
6
- import { Command } from 'commander'
7
- import { MessageType } from '../../shared/constants.js'
8
- import { MGClient } from '../client.js'
9
-
10
- interface ExecuteCodeOptions {
11
- noAutoStart?: boolean
12
- noRetry?: boolean
13
- }
14
-
15
- /**
16
- * 创建 execute_code 命令
17
- */
18
- export function createExecuteCodeCommand(): Command {
19
- return new Command('execute_code')
20
- .description('在 MasterGo 页面执行自定义 JavaScript 代码。通过 mg 变量访问 MasterGo API,结果会被 JSON 序列化返回')
21
- .argument('<code>', '要执行的代码。可使用 mg 变量,如 mg.currentPage.name、mg.currentPage.selection')
22
- .option('--no-auto-start', '禁用自动启动 Server')
23
- .option('--no-retry', '禁用自动重试')
24
- .action(async (code: string, options: ExecuteCodeOptions) => {
25
- await handleExecuteCode(code, options)
26
- })
27
- }
28
-
29
- /**
30
- * 处理 execute_code 命令
31
- */
32
- async function handleExecuteCode(code: string, options: ExecuteCodeOptions): Promise<void> {
33
- const client = new MGClient({
34
- noAutoStart: options.noAutoStart,
35
- noRetry: options.noRetry,
36
- })
37
-
38
- try {
39
- // 连接 Server
40
- await client.connect()
41
-
42
- // 发送请求
43
- const result = await client.requestWithRetry<unknown>(MessageType.EXECUTE_CODE, { code })
44
-
45
- // 输出结果
46
- if (result === null || result === undefined) {
47
- console.log('执行完成(无返回值)')
48
- } else if (typeof result === 'object') {
49
- console.log(JSON.stringify(result, null, 2))
50
- } else {
51
- console.log(result)
52
- }
53
- } catch (error) {
54
- console.error(`错误: ${error instanceof Error ? error.message : error}`)
55
- process.exit(1)
56
- } finally {
57
- client.close()
58
- }
59
- }