@hangox/mg-cli 1.0.0 → 1.0.2

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 +1580 -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
@@ -1,117 +0,0 @@
1
- /**
2
- * 日志模块
3
- */
4
-
5
- import { appendFileSync, existsSync, mkdirSync } from 'node:fs'
6
- import { dirname } from 'node:path'
7
- import { SERVER_LOG_FILE } from '../shared/constants.js'
8
- import { formatLogTime } from '../shared/utils.js'
9
-
10
- export enum LogLevel {
11
- DEBUG = 'DEBUG',
12
- INFO = 'INFO',
13
- WARN = 'WARN',
14
- ERROR = 'ERROR',
15
- }
16
-
17
- export interface LoggerOptions {
18
- /** 是否输出到控制台 */
19
- console?: boolean
20
- /** 是否输出到文件 */
21
- file?: boolean
22
- /** 日志文件路径 */
23
- filePath?: string
24
- /** 最小日志级别 */
25
- minLevel?: LogLevel
26
- }
27
-
28
- const levelPriority: Record<LogLevel, number> = {
29
- [LogLevel.DEBUG]: 0,
30
- [LogLevel.INFO]: 1,
31
- [LogLevel.WARN]: 2,
32
- [LogLevel.ERROR]: 3,
33
- }
34
-
35
- /**
36
- * 日志记录器
37
- */
38
- export class Logger {
39
- private options: Required<LoggerOptions>
40
-
41
- constructor(options: LoggerOptions = {}) {
42
- this.options = {
43
- console: options.console ?? true,
44
- file: options.file ?? false,
45
- filePath: options.filePath ?? SERVER_LOG_FILE,
46
- minLevel: options.minLevel ?? LogLevel.INFO,
47
- }
48
-
49
- // 确保日志目录存在
50
- if (this.options.file) {
51
- const dir = dirname(this.options.filePath)
52
- if (!existsSync(dir)) {
53
- mkdirSync(dir, { recursive: true })
54
- }
55
- }
56
- }
57
-
58
- /**
59
- * 记录日志
60
- */
61
- private log(level: LogLevel, message: string, ...args: unknown[]): void {
62
- // 检查日志级别
63
- if (levelPriority[level] < levelPriority[this.options.minLevel]) {
64
- return
65
- }
66
-
67
- const timestamp = formatLogTime()
68
- const formattedMessage = `[${timestamp}] [${level}] ${message}`
69
-
70
- // 输出到控制台
71
- if (this.options.console) {
72
- const consoleMethod = level === LogLevel.ERROR ? console.error : console.log
73
- if (args.length > 0) {
74
- consoleMethod(formattedMessage, ...args)
75
- } else {
76
- consoleMethod(formattedMessage)
77
- }
78
- }
79
-
80
- // 输出到文件
81
- if (this.options.file) {
82
- try {
83
- const fileMessage =
84
- args.length > 0
85
- ? `${formattedMessage} ${JSON.stringify(args)}\n`
86
- : `${formattedMessage}\n`
87
- appendFileSync(this.options.filePath, fileMessage)
88
- } catch (error) {
89
- // 文件写入失败时静默处理
90
- if (this.options.console) {
91
- console.error('日志文件写入失败:', error)
92
- }
93
- }
94
- }
95
- }
96
-
97
- debug(message: string, ...args: unknown[]): void {
98
- this.log(LogLevel.DEBUG, message, ...args)
99
- }
100
-
101
- info(message: string, ...args: unknown[]): void {
102
- this.log(LogLevel.INFO, message, ...args)
103
- }
104
-
105
- warn(message: string, ...args: unknown[]): void {
106
- this.log(LogLevel.WARN, message, ...args)
107
- }
108
-
109
- error(message: string, ...args: unknown[]): void {
110
- this.log(LogLevel.ERROR, message, ...args)
111
- }
112
- }
113
-
114
- /** 创建默认日志记录器 */
115
- export function createLogger(options?: LoggerOptions): Logger {
116
- return new Logger(options)
117
- }
@@ -1,192 +0,0 @@
1
- /**
2
- * 请求处理器
3
- * 处理 Consumer 请求,转发给 Provider
4
- */
5
-
6
- import type { ManagedWebSocket } from './connection-manager.js'
7
- import { ConnectionManager } from './connection-manager.js'
8
- import { Logger } from './logger.js'
9
- import { MessageType, REQUEST_TIMEOUT } from '../shared/constants.js'
10
- import { ErrorCode, MGError } from '../shared/errors.js'
11
- import type { RequestMessage, ResponseMessage } from '../shared/types.js'
12
- import { generateId } from '../shared/utils.js'
13
-
14
- /** 待处理的请求 */
15
- interface PendingRequest {
16
- /** 请求 ID */
17
- id: string
18
- /** Consumer WebSocket */
19
- consumer: ManagedWebSocket
20
- /** 超时定时器 */
21
- timer: NodeJS.Timeout
22
- /** 请求时间 */
23
- timestamp: number
24
- }
25
-
26
- /**
27
- * 请求处理器
28
- */
29
- export class RequestHandler {
30
- private logger: Logger
31
- private connectionManager: ConnectionManager
32
-
33
- /** 待处理的请求 */
34
- private pendingRequests = new Map<string, PendingRequest>()
35
-
36
- constructor(connectionManager: ConnectionManager, logger: Logger) {
37
- this.connectionManager = connectionManager
38
- this.logger = logger
39
- }
40
-
41
- /**
42
- * 处理 Consumer 请求
43
- */
44
- async handleRequest(consumer: ManagedWebSocket, message: RequestMessage): Promise<void> {
45
- const requestId = message.id || generateId()
46
- const { type, pageUrl, params } = message
47
-
48
- this.logger.info(`收到请求: ${type} (${requestId})`, { pageUrl })
49
-
50
- // 查找目标 Provider
51
- let provider: ManagedWebSocket | undefined
52
-
53
- if (pageUrl) {
54
- provider = this.connectionManager.findProviderByPageUrl(pageUrl)
55
- if (!provider) {
56
- this.sendError(consumer, requestId, ErrorCode.PAGE_NOT_FOUND, `未找到页面: ${pageUrl}`)
57
- return
58
- }
59
- } else {
60
- // 没有指定 pageUrl,使用第一个可用的 Provider
61
- provider = this.connectionManager.getFirstProvider()
62
- if (!provider) {
63
- this.sendError(consumer, requestId, ErrorCode.NO_PAGE_CONNECTED, '没有页面连接到 Server')
64
- return
65
- }
66
- }
67
-
68
- // 创建超时定时器
69
- const timer = setTimeout(() => {
70
- this.handleTimeout(requestId)
71
- }, REQUEST_TIMEOUT)
72
-
73
- // 保存待处理请求
74
- this.pendingRequests.set(requestId, {
75
- id: requestId,
76
- consumer,
77
- timer,
78
- timestamp: Date.now(),
79
- })
80
-
81
- // 转发请求给 Provider
82
- const forwardMessage: RequestMessage = {
83
- id: requestId,
84
- type,
85
- pageUrl: pageUrl || provider.connectionInfo.pageUrl,
86
- params,
87
- timestamp: Date.now(),
88
- }
89
-
90
- try {
91
- provider.send(JSON.stringify(forwardMessage))
92
- this.logger.info(`请求转发: ${type} -> ${provider.connectionInfo.pageUrl}`)
93
- } catch {
94
- this.cleanupRequest(requestId)
95
- this.sendError(consumer, requestId, ErrorCode.CONNECTION_FAILED, '转发请求失败')
96
- }
97
- }
98
-
99
- /**
100
- * 处理 Provider 响应
101
- */
102
- handleResponse(response: ResponseMessage): void {
103
- const { id } = response
104
- const pending = this.pendingRequests.get(id)
105
-
106
- if (!pending) {
107
- this.logger.warn(`收到未知请求的响应: ${id}`)
108
- return
109
- }
110
-
111
- this.cleanupRequest(id)
112
-
113
- // 转发响应给 Consumer
114
- try {
115
- pending.consumer.send(JSON.stringify(response))
116
- this.logger.info(
117
- `响应返回: ${id} (${response.success ? '成功' : '失败'})`
118
- )
119
- } catch (error) {
120
- this.logger.error(`响应转发失败: ${id}`, error)
121
- }
122
- }
123
-
124
- /**
125
- * 处理请求超时
126
- */
127
- private handleTimeout(requestId: string): void {
128
- const pending = this.pendingRequests.get(requestId)
129
- if (!pending) return
130
-
131
- this.logger.warn(`请求超时: ${requestId}`)
132
- this.cleanupRequest(requestId)
133
-
134
- this.sendError(pending.consumer, requestId, ErrorCode.REQUEST_TIMEOUT, '请求超时')
135
- }
136
-
137
- /**
138
- * 清理请求
139
- */
140
- private cleanupRequest(requestId: string): void {
141
- const pending = this.pendingRequests.get(requestId)
142
- if (pending) {
143
- clearTimeout(pending.timer)
144
- this.pendingRequests.delete(requestId)
145
- }
146
- }
147
-
148
- /**
149
- * 发送错误响应
150
- */
151
- private sendError(
152
- consumer: ManagedWebSocket,
153
- requestId: string,
154
- code: ErrorCode,
155
- message: string
156
- ): void {
157
- const error = new MGError(code, message)
158
- const response: ResponseMessage = {
159
- id: requestId,
160
- type: MessageType.ERROR,
161
- success: false,
162
- data: null,
163
- error: error.toJSON(),
164
- }
165
-
166
- try {
167
- consumer.send(JSON.stringify(response))
168
- } catch (err) {
169
- this.logger.error(`发送错误响应失败: ${requestId}`, err)
170
- }
171
- }
172
-
173
- /**
174
- * 清理特定连接的所有待处理请求
175
- */
176
- cleanupConnectionRequests(connectionId: string): void {
177
- for (const [requestId, pending] of this.pendingRequests) {
178
- if (pending.consumer.connectionId === connectionId) {
179
- this.cleanupRequest(requestId)
180
- }
181
- }
182
- }
183
-
184
- /**
185
- * 清理所有待处理请求
186
- */
187
- cleanupAll(): void {
188
- for (const [requestId] of this.pendingRequests) {
189
- this.cleanupRequest(requestId)
190
- }
191
- }
192
- }
@@ -1,297 +0,0 @@
1
- /**
2
- * WebSocket 服务器
3
- */
4
-
5
- import { WebSocketServer, WebSocket } from 'ws'
6
- import type { IncomingMessage } from 'node:http'
7
- import {
8
- ConnectionType,
9
- MessageType,
10
- DEFAULT_PORT,
11
- PORT_RANGE_START,
12
- PORT_RANGE_END,
13
- HEARTBEAT_INTERVAL,
14
- } from '../shared/constants.js'
15
- import { ErrorCode, MGError } from '../shared/errors.js'
16
- import type { BaseMessage, RegisterMessage, ResponseMessage } from '../shared/types.js'
17
- import { Logger, createLogger } from './logger.js'
18
- import { ConnectionManager, ManagedWebSocket } from './connection-manager.js'
19
- import { RequestHandler } from './request-handler.js'
20
-
21
- export interface ServerOptions {
22
- /** 监听端口 */
23
- port?: number
24
- /** 日志选项 */
25
- logger?: Logger
26
- }
27
-
28
- /**
29
- * MG WebSocket 服务器
30
- */
31
- export class MGServer {
32
- private wss: WebSocketServer | null = null
33
- private logger: Logger
34
- private connectionManager: ConnectionManager
35
- private requestHandler: RequestHandler
36
-
37
- private port: number
38
- private isRunning = false
39
-
40
- constructor(options: ServerOptions = {}) {
41
- this.port = options.port || DEFAULT_PORT
42
- this.logger = options.logger || createLogger()
43
- this.connectionManager = new ConnectionManager(this.logger)
44
- this.requestHandler = new RequestHandler(this.connectionManager, this.logger)
45
- }
46
-
47
- /**
48
- * 启动服务器
49
- */
50
- async start(): Promise<number> {
51
- if (this.isRunning) {
52
- throw new MGError(ErrorCode.SERVER_ALREADY_RUNNING, 'Server 已在运行中')
53
- }
54
-
55
- // 尝试绑定端口
56
- const port = await this.findAvailablePort()
57
-
58
- return new Promise((resolve, reject) => {
59
- this.wss = new WebSocketServer({ port })
60
-
61
- this.wss.on('listening', () => {
62
- this.port = port
63
- this.isRunning = true
64
- this.logger.info(`Server 启动成功,监听端口: ${port}`)
65
-
66
- // 启动心跳检查
67
- this.connectionManager.startHeartbeatCheck(HEARTBEAT_INTERVAL)
68
-
69
- resolve(port)
70
- })
71
-
72
- this.wss.on('error', (error: NodeJS.ErrnoException) => {
73
- this.logger.error('Server 错误:', error)
74
- reject(error)
75
- })
76
-
77
- this.wss.on('connection', (ws: WebSocket, request: IncomingMessage) => {
78
- this.handleConnection(ws, request)
79
- })
80
- })
81
- }
82
-
83
- /**
84
- * 查找可用端口
85
- */
86
- private async findAvailablePort(): Promise<number> {
87
- for (let port = PORT_RANGE_START; port <= PORT_RANGE_END; port++) {
88
- const available = await this.isPortAvailable(port)
89
- if (available) {
90
- return port
91
- }
92
- this.logger.debug(`端口 ${port} 被占用,尝试下一个`)
93
- }
94
-
95
- throw new MGError(
96
- ErrorCode.PORT_EXHAUSTED,
97
- `端口 ${PORT_RANGE_START}-${PORT_RANGE_END} 均被占用`
98
- )
99
- }
100
-
101
- /**
102
- * 检查端口是否可用
103
- */
104
- private isPortAvailable(port: number): Promise<boolean> {
105
- return new Promise((resolve) => {
106
- const testServer = new WebSocketServer({ port })
107
-
108
- testServer.on('listening', () => {
109
- testServer.close()
110
- resolve(true)
111
- })
112
-
113
- testServer.on('error', () => {
114
- resolve(false)
115
- })
116
- })
117
- }
118
-
119
- /**
120
- * 处理新连接
121
- */
122
- private handleConnection(ws: WebSocket, _request: IncomingMessage): void {
123
- this.logger.info('新连接建立')
124
-
125
- // 等待注册消息
126
- const registerTimeout = setTimeout(() => {
127
- this.logger.warn('连接注册超时,关闭连接')
128
- ws.close()
129
- }, 5000)
130
-
131
- ws.on('message', (data) => {
132
- try {
133
- const message = JSON.parse(data.toString()) as BaseMessage
134
-
135
- // 处理注册消息
136
- if (message.type === MessageType.REGISTER) {
137
- clearTimeout(registerTimeout)
138
- this.handleRegister(ws, message as RegisterMessage)
139
- return
140
- }
141
-
142
- // 其他消息需要已注册
143
- const managedWs = ws as ManagedWebSocket
144
- if (!managedWs.connectionId) {
145
- this.logger.warn('未注册的连接发送消息,忽略')
146
- return
147
- }
148
-
149
- this.handleMessage(managedWs, message)
150
- } catch (error) {
151
- this.logger.error('消息解析失败:', error)
152
- }
153
- })
154
-
155
- ws.on('close', () => {
156
- clearTimeout(registerTimeout)
157
- const managedWs = ws as ManagedWebSocket
158
- if (managedWs.connectionId) {
159
- this.requestHandler.cleanupConnectionRequests(managedWs.connectionId)
160
- this.connectionManager.removeConnection(managedWs)
161
- }
162
- })
163
-
164
- ws.on('error', (error) => {
165
- this.logger.error('WebSocket 错误:', error)
166
- })
167
- }
168
-
169
- /**
170
- * 处理注册消息
171
- */
172
- private handleRegister(ws: WebSocket, message: RegisterMessage): void {
173
- const { connectionType, pageUrl, pageId } = message.data
174
-
175
- const managedWs = this.connectionManager.addConnection(
176
- ws,
177
- connectionType,
178
- pageUrl,
179
- pageId
180
- )
181
-
182
- // 发送注册确认
183
- const ack: ResponseMessage = {
184
- id: message.id || '',
185
- type: MessageType.REGISTER_ACK,
186
- success: true,
187
- data: {
188
- connectionId: managedWs.connectionId,
189
- pageUrl,
190
- },
191
- }
192
-
193
- ws.send(JSON.stringify(ack))
194
- }
195
-
196
- /**
197
- * 处理消息
198
- */
199
- private handleMessage(ws: ManagedWebSocket, message: BaseMessage): void {
200
- this.connectionManager.updateLastActive(ws)
201
-
202
- switch (message.type) {
203
- case MessageType.PING:
204
- this.handlePing(ws, message)
205
- break
206
-
207
- case MessageType.RESPONSE:
208
- case MessageType.ERROR:
209
- // Provider 返回的响应
210
- this.requestHandler.handleResponse(message as ResponseMessage)
211
- break
212
-
213
- default:
214
- // Consumer 发送的请求
215
- if (ws.connectionInfo.type === ConnectionType.CONSUMER) {
216
- this.requestHandler.handleRequest(ws, message as any)
217
- }
218
- break
219
- }
220
- }
221
-
222
- /**
223
- * 处理心跳
224
- */
225
- private handlePing(ws: ManagedWebSocket, message: BaseMessage): void {
226
- const pong = {
227
- type: MessageType.PONG,
228
- timestamp: (message as any).timestamp || Date.now(),
229
- }
230
- ws.send(JSON.stringify(pong))
231
- }
232
-
233
- /**
234
- * 停止服务器
235
- */
236
- async stop(): Promise<void> {
237
- if (!this.isRunning || !this.wss) {
238
- return
239
- }
240
-
241
- this.logger.info('正在停止 Server...')
242
-
243
- // 清理所有请求
244
- this.requestHandler.cleanupAll()
245
-
246
- // 关闭所有连接
247
- this.connectionManager.closeAll()
248
-
249
- // 关闭服务器
250
- return new Promise((resolve) => {
251
- this.wss!.close(() => {
252
- this.isRunning = false
253
- this.wss = null
254
- this.logger.info('Server 已停止')
255
- resolve()
256
- })
257
- })
258
- }
259
-
260
- /**
261
- * 获取运行状态
262
- */
263
- getStatus(): {
264
- running: boolean
265
- port: number
266
- stats: { providers: number; consumers: number; total: number }
267
- connectedPages: string[]
268
- } {
269
- return {
270
- running: this.isRunning,
271
- port: this.port,
272
- stats: this.connectionManager.getStats(),
273
- connectedPages: this.connectionManager.getConnectedPageUrls(),
274
- }
275
- }
276
-
277
- /**
278
- * 获取端口
279
- */
280
- getPort(): number {
281
- return this.port
282
- }
283
-
284
- /**
285
- * 是否运行中
286
- */
287
- isServerRunning(): boolean {
288
- return this.isRunning
289
- }
290
- }
291
-
292
- /**
293
- * 创建服务器实例
294
- */
295
- export function createServer(options?: ServerOptions): MGServer {
296
- return new MGServer(options)
297
- }
@@ -1,90 +0,0 @@
1
- /**
2
- * MG Plugin 常量定义
3
- */
4
-
5
- import { homedir } from 'node:os'
6
- import { join } from 'node:path'
7
-
8
- // ==================== 端口配置 ====================
9
-
10
- /** 默认端口 */
11
- export const DEFAULT_PORT = 9527
12
-
13
- /** 端口范围:起始 */
14
- export const PORT_RANGE_START = 9527
15
-
16
- /** 端口范围:结束 */
17
- export const PORT_RANGE_END = 9536
18
-
19
- /** 最大尝试端口数 */
20
- export const MAX_PORT_ATTEMPTS = 10
21
-
22
- /** 端口扫描超时(毫秒) */
23
- export const PORT_SCAN_TIMEOUT = 500
24
-
25
- // ==================== 路径配置 ====================
26
-
27
- /** 配置目录 */
28
- export const CONFIG_DIR = join(homedir(), '.mg-plugin')
29
-
30
- /** Server 状态文件 */
31
- export const SERVER_INFO_FILE = join(CONFIG_DIR, 'server.json')
32
-
33
- /** 日志目录 */
34
- export const LOG_DIR = join(CONFIG_DIR, 'logs')
35
-
36
- /** Server 日志文件 */
37
- export const SERVER_LOG_FILE = join(LOG_DIR, 'server.log')
38
-
39
- // ==================== 超时配置 ====================
40
-
41
- /** 心跳间隔(毫秒)- 30 秒 */
42
- export const HEARTBEAT_INTERVAL = 30000
43
-
44
- /** 心跳超时(毫秒)- 90 秒(3 次心跳) */
45
- export const HEARTBEAT_TIMEOUT = 90000
46
-
47
- /** 请求超时(毫秒)- 30 秒 */
48
- export const REQUEST_TIMEOUT = 30000
49
-
50
- /** Server 启动等待超时(毫秒)- 5 秒 */
51
- export const SERVER_START_TIMEOUT = 5000
52
-
53
- /** CLI 重试间隔(毫秒) */
54
- export const RETRY_INTERVALS = [1000, 2000, 4000]
55
-
56
- /** 最大重试次数 */
57
- export const MAX_RETRY_COUNT = 3
58
-
59
- // ==================== 连接类型 ====================
60
-
61
- /** 连接类型 */
62
- export enum ConnectionType {
63
- /** 获取端 (CLI/MCP) */
64
- CONSUMER = 'consumer',
65
- /** 提供端 (Injector) */
66
- PROVIDER = 'provider',
67
- }
68
-
69
- // ==================== 消息类型 ====================
70
-
71
- /** WebSocket 消息类型 */
72
- export enum MessageType {
73
- // 系统消息
74
- PING = 'ping',
75
- PONG = 'pong',
76
- REGISTER = 'register',
77
- REGISTER_ACK = 'register_ack',
78
-
79
- // 业务消息
80
- GET_NODE_BY_ID = 'get_node_by_id',
81
- GET_ALL_NODES = 'get_all_nodes',
82
- GET_SELECTION = 'get_selection',
83
- EXPORT_IMAGE = 'export_image',
84
- EXECUTE_CODE = 'execute_code',
85
- GET_ALL_PAGES = 'get_all_pages',
86
-
87
- // 响应
88
- RESPONSE = 'response',
89
- ERROR = 'error',
90
- }