@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.
- package/dist/cli.js +1580 -0
- package/dist/cli.js.map +1 -0
- package/dist/daemon-runner.js +794 -0
- package/dist/daemon-runner.js.map +1 -0
- package/dist/index-DNrszrq9.d.ts +568 -0
- package/dist/index.d.ts +129 -0
- package/dist/index.js +950 -0
- package/dist/index.js.map +1 -0
- package/dist/server.d.ts +2 -0
- package/dist/server.js +689 -0
- package/dist/server.js.map +1 -0
- package/package.json +5 -1
- package/.eslintrc.cjs +0 -26
- package/CLAUDE.md +0 -43
- package/src/cli/client.ts +0 -266
- package/src/cli/commands/execute-code.ts +0 -59
- package/src/cli/commands/export-image.ts +0 -193
- package/src/cli/commands/get-all-nodes.ts +0 -81
- package/src/cli/commands/get-all-pages.ts +0 -118
- package/src/cli/commands/get-node-by-id.ts +0 -83
- package/src/cli/commands/get-node-by-link.ts +0 -105
- package/src/cli/commands/server.ts +0 -130
- package/src/cli/index.ts +0 -33
- package/src/index.ts +0 -9
- package/src/server/connection-manager.ts +0 -211
- package/src/server/daemon-runner.ts +0 -22
- package/src/server/daemon.ts +0 -211
- package/src/server/index.ts +0 -8
- package/src/server/logger.ts +0 -117
- package/src/server/request-handler.ts +0 -192
- package/src/server/websocket-server.ts +0 -297
- package/src/shared/constants.ts +0 -90
- package/src/shared/errors.ts +0 -131
- package/src/shared/index.ts +0 -8
- package/src/shared/types.ts +0 -227
- package/src/shared/utils.ts +0 -352
- package/tests/unit/shared/constants.test.ts +0 -66
- package/tests/unit/shared/errors.test.ts +0 -82
- package/tests/unit/shared/utils.test.ts +0 -208
- package/tsconfig.json +0 -22
- package/tsup.config.ts +0 -33
- package/vitest.config.ts +0 -22
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* export_image 命令
|
|
3
|
-
* 导出 MasterGo 节点为图片文件
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Command } from 'commander'
|
|
7
|
-
import { writeFileSync } from 'node:fs'
|
|
8
|
-
import { resolve, dirname, extname } from 'node:path'
|
|
9
|
-
import { mkdirSync } from 'node:fs'
|
|
10
|
-
import { tmpdir } from 'node:os'
|
|
11
|
-
import { MessageType } from '../../shared/constants.js'
|
|
12
|
-
import { MGClient, parseMgpLink } from '../client.js'
|
|
13
|
-
import { MGError } from '../../shared/errors.js'
|
|
14
|
-
import type { ExportImageParams } from '../../shared/types.js'
|
|
15
|
-
|
|
16
|
-
type ImageFormat = 'PNG' | 'JPG' | 'SVG' | 'PDF' | 'WEBP'
|
|
17
|
-
|
|
18
|
-
interface ExportImageOptions {
|
|
19
|
-
output?: string
|
|
20
|
-
link?: string
|
|
21
|
-
format?: string
|
|
22
|
-
scale?: string
|
|
23
|
-
width?: string
|
|
24
|
-
height?: string
|
|
25
|
-
useAbsoluteBounds?: boolean
|
|
26
|
-
useRenderBounds?: boolean
|
|
27
|
-
noAutoStart?: boolean
|
|
28
|
-
noRetry?: boolean
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
/** 导出响应 */
|
|
32
|
-
interface ExportResponse {
|
|
33
|
-
/** Base64 编码的图片数据 */
|
|
34
|
-
data: string
|
|
35
|
-
/** MIME 类型 */
|
|
36
|
-
mimeType: string
|
|
37
|
-
/** 文件名建议 */
|
|
38
|
-
filename?: string
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* 创建 export_image 命令
|
|
43
|
-
*/
|
|
44
|
-
export function createExportImageCommand(): Command {
|
|
45
|
-
return new Command('export_image')
|
|
46
|
-
.description('导出 MasterGo 节点为图片文件。强烈建议指定 --output,否则保存到临时目录可能被系统清理')
|
|
47
|
-
.option('--output <path>', '输出文件路径。强烈建议指定,否则保存到系统临时目录可能被清理')
|
|
48
|
-
.option('--link <mgp-link>', 'mgp:// 协议链接。不指定则导出当前选中节点')
|
|
49
|
-
.option('--format <type>', '导出格式:PNG(无损透明)、JPG(有损)、SVG(矢量)、PDF、WEBP', 'PNG')
|
|
50
|
-
.option('--scale <number>', '缩放倍率(如 1、2、3)。与 width/height 互斥')
|
|
51
|
-
.option('--width <number>', '固定宽度(像素)。与 scale/height 互斥')
|
|
52
|
-
.option('--height <number>', '固定高度(像素)。与 scale/width 互斥')
|
|
53
|
-
.option('--useAbsoluteBounds', '使用完整尺寸。true: 包含被裁剪部分,false: 只导出可见区域', false)
|
|
54
|
-
.option('--no-use-render-bounds', '不包含特效和外描边。默认包含阴影、外描边等')
|
|
55
|
-
.option('--no-auto-start', '禁用自动启动 Server')
|
|
56
|
-
.option('--no-retry', '禁用自动重试')
|
|
57
|
-
.action(async (options: ExportImageOptions) => {
|
|
58
|
-
await handleExportImage(options)
|
|
59
|
-
})
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 处理 export_image 命令
|
|
64
|
-
*/
|
|
65
|
-
async function handleExportImage(options: ExportImageOptions): Promise<void> {
|
|
66
|
-
// 验证格式
|
|
67
|
-
const format = (options.format?.toUpperCase() || 'PNG') as ImageFormat
|
|
68
|
-
const validFormats: ImageFormat[] = ['PNG', 'JPG', 'SVG', 'PDF', 'WEBP']
|
|
69
|
-
if (!validFormats.includes(format)) {
|
|
70
|
-
console.error(`错误: 不支持的格式 "${options.format}"`)
|
|
71
|
-
console.error(`支持的格式: ${validFormats.join(', ')}`)
|
|
72
|
-
process.exit(1)
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// 验证参数互斥
|
|
76
|
-
const sizeParams = [options.scale, options.width, options.height].filter(Boolean)
|
|
77
|
-
if (sizeParams.length > 1) {
|
|
78
|
-
console.error('错误: scale、width、height 三者互斥,只能指定其中一个')
|
|
79
|
-
process.exit(1)
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
// 解析 link 参数
|
|
83
|
-
let pageUrl: string | undefined
|
|
84
|
-
let nodeId: string | undefined
|
|
85
|
-
|
|
86
|
-
if (options.link) {
|
|
87
|
-
const linkInfo = parseMgpLink(options.link)
|
|
88
|
-
if (!linkInfo) {
|
|
89
|
-
console.error(`错误: 无效的 mgp:// 链接格式: ${options.link}`)
|
|
90
|
-
process.exit(1)
|
|
91
|
-
}
|
|
92
|
-
pageUrl = linkInfo.pageUrl
|
|
93
|
-
nodeId = linkInfo.nodeId
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
const client = new MGClient({
|
|
97
|
-
noAutoStart: options.noAutoStart,
|
|
98
|
-
noRetry: options.noRetry,
|
|
99
|
-
})
|
|
100
|
-
|
|
101
|
-
try {
|
|
102
|
-
// 连接 Server
|
|
103
|
-
await client.connect()
|
|
104
|
-
|
|
105
|
-
// 构建请求参数
|
|
106
|
-
const params: ExportImageParams = {
|
|
107
|
-
format,
|
|
108
|
-
useAbsoluteBounds: options.useAbsoluteBounds || false,
|
|
109
|
-
useRenderBounds: options.useRenderBounds !== false,
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
if (nodeId) {
|
|
113
|
-
params.nodeId = nodeId
|
|
114
|
-
}
|
|
115
|
-
if (options.scale) {
|
|
116
|
-
params.scale = parseFloat(options.scale)
|
|
117
|
-
}
|
|
118
|
-
if (options.width) {
|
|
119
|
-
params.width = parseInt(options.width, 10)
|
|
120
|
-
}
|
|
121
|
-
if (options.height) {
|
|
122
|
-
params.height = parseInt(options.height, 10)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// 发送请求(传入 pageUrl 以指定目标页面)
|
|
126
|
-
const response = await client.requestWithRetry<ExportResponse>(
|
|
127
|
-
MessageType.EXPORT_IMAGE,
|
|
128
|
-
params as Record<string, unknown>,
|
|
129
|
-
pageUrl
|
|
130
|
-
)
|
|
131
|
-
|
|
132
|
-
// 确定输出路径
|
|
133
|
-
const ext = getExtension(format)
|
|
134
|
-
let outputPath: string
|
|
135
|
-
if (options.output) {
|
|
136
|
-
outputPath = resolve(options.output)
|
|
137
|
-
// 如果没有扩展名,添加扩展名
|
|
138
|
-
if (!extname(outputPath)) {
|
|
139
|
-
outputPath = `${outputPath}${ext}`
|
|
140
|
-
}
|
|
141
|
-
} else {
|
|
142
|
-
// 使用临时目录
|
|
143
|
-
const filename = response.filename || `export_${Date.now()}${ext}`
|
|
144
|
-
outputPath = resolve(tmpdir(), filename)
|
|
145
|
-
console.log('警告: 未指定 --output,文件将保存到临时目录,可能会被系统清理')
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
// 确保目录存在
|
|
149
|
-
const outputDir = dirname(outputPath)
|
|
150
|
-
mkdirSync(outputDir, { recursive: true })
|
|
151
|
-
|
|
152
|
-
// 解码 Base64 并保存
|
|
153
|
-
const buffer = Buffer.from(response.data, 'base64')
|
|
154
|
-
writeFileSync(outputPath, buffer)
|
|
155
|
-
|
|
156
|
-
// 输出结果
|
|
157
|
-
const sizeKB = (buffer.length / 1024).toFixed(2)
|
|
158
|
-
console.log(`文件路径: ${outputPath}`)
|
|
159
|
-
if (options.link) {
|
|
160
|
-
console.log(`Link: ${options.link}`)
|
|
161
|
-
}
|
|
162
|
-
if (nodeId) {
|
|
163
|
-
console.log(`节点 ID: ${nodeId}`)
|
|
164
|
-
} else {
|
|
165
|
-
console.log('节点 ID: (选中的节点)')
|
|
166
|
-
}
|
|
167
|
-
console.log(`导出格式: ${format}`)
|
|
168
|
-
console.log(`文件大小: ${buffer.length.toLocaleString()} 字节 (约 ${sizeKB} KB)`)
|
|
169
|
-
} catch (error) {
|
|
170
|
-
if (error instanceof MGError) {
|
|
171
|
-
console.error(`错误 [${error.code}]: ${error.message}`)
|
|
172
|
-
} else {
|
|
173
|
-
console.error(`错误: ${error instanceof Error ? error.message : error}`)
|
|
174
|
-
}
|
|
175
|
-
process.exit(1)
|
|
176
|
-
} finally {
|
|
177
|
-
client.close()
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
/**
|
|
182
|
-
* 获取格式对应的文件扩展名
|
|
183
|
-
*/
|
|
184
|
-
function getExtension(format: ImageFormat): string {
|
|
185
|
-
const extensions: Record<ImageFormat, string> = {
|
|
186
|
-
PNG: '.png',
|
|
187
|
-
JPG: '.jpg',
|
|
188
|
-
SVG: '.svg',
|
|
189
|
-
PDF: '.pdf',
|
|
190
|
-
WEBP: '.webp',
|
|
191
|
-
}
|
|
192
|
-
return extensions[format]
|
|
193
|
-
}
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* get_all_nodes 命令
|
|
3
|
-
* 获取当前页面的所有节点树
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Command } from 'commander'
|
|
7
|
-
import { writeFileSync } from 'node:fs'
|
|
8
|
-
import { resolve, dirname } from 'node:path'
|
|
9
|
-
import { mkdirSync } from 'node:fs'
|
|
10
|
-
import { MessageType } from '../../shared/constants.js'
|
|
11
|
-
import { MGClient } from '../client.js'
|
|
12
|
-
import type { GetAllNodesParams, NodeInfo } from '../../shared/types.js'
|
|
13
|
-
|
|
14
|
-
interface GetAllNodesOptions {
|
|
15
|
-
output: string
|
|
16
|
-
maxDepth?: string
|
|
17
|
-
includeInvisible?: boolean
|
|
18
|
-
noAutoStart?: boolean
|
|
19
|
-
noRetry?: boolean
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* 创建 get_all_nodes 命令
|
|
24
|
-
*/
|
|
25
|
-
export function createGetAllNodesCommand(): Command {
|
|
26
|
-
return new Command('get_all_nodes')
|
|
27
|
-
.description('获取当前页面的所有节点树。警告:深度每增加 1,数据量可能呈指数级增长。建议从 maxDepth=1 开始')
|
|
28
|
-
.requiredOption('--output <path>', '输出 JSON 文件路径。支持绝对路径或相对路径')
|
|
29
|
-
.option('--maxDepth <number>', '最大深度,默认 1。深度 2 可能产生 100KB-500KB,深度 3 可能超过 1MB', '1')
|
|
30
|
-
.option('--includeInvisible', '包含不可见节点(visible: false),默认不包含', false)
|
|
31
|
-
.option('--no-auto-start', '禁用自动启动 Server')
|
|
32
|
-
.option('--no-retry', '禁用自动重试')
|
|
33
|
-
.action(async (options: GetAllNodesOptions) => {
|
|
34
|
-
await handleGetAllNodes(options)
|
|
35
|
-
})
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* 处理 get_all_nodes 命令
|
|
40
|
-
*/
|
|
41
|
-
async function handleGetAllNodes(options: GetAllNodesOptions): Promise<void> {
|
|
42
|
-
const client = new MGClient({
|
|
43
|
-
noAutoStart: options.noAutoStart,
|
|
44
|
-
noRetry: options.noRetry,
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
try {
|
|
48
|
-
// 连接 Server
|
|
49
|
-
await client.connect()
|
|
50
|
-
|
|
51
|
-
// 发送请求
|
|
52
|
-
const params: GetAllNodesParams = {
|
|
53
|
-
maxDepth: parseInt(options.maxDepth || '1', 10),
|
|
54
|
-
includeInvisible: options.includeInvisible || false,
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const data = await client.requestWithRetry<NodeInfo[]>(MessageType.GET_ALL_NODES, params)
|
|
58
|
-
|
|
59
|
-
// 保存到文件
|
|
60
|
-
const outputPath = resolve(options.output)
|
|
61
|
-
const outputDir = dirname(outputPath)
|
|
62
|
-
mkdirSync(outputDir, { recursive: true })
|
|
63
|
-
|
|
64
|
-
const jsonContent = JSON.stringify(data, null, 2)
|
|
65
|
-
writeFileSync(outputPath, jsonContent, 'utf-8')
|
|
66
|
-
|
|
67
|
-
// 输出结果
|
|
68
|
-
const size = jsonContent.length
|
|
69
|
-
const sizeKB = (size / 1024).toFixed(2)
|
|
70
|
-
const nodeCount = Array.isArray(data) ? data.length : 1
|
|
71
|
-
console.log(`文件路径: ${outputPath}`)
|
|
72
|
-
console.log(`节点数量: ${nodeCount}`)
|
|
73
|
-
console.log(`数据大小: ${size.toLocaleString()} 字符 (约 ${sizeKB} KB)`)
|
|
74
|
-
console.log(`节点深度: ${params.maxDepth}`)
|
|
75
|
-
} catch (error) {
|
|
76
|
-
console.error(`错误: ${error instanceof Error ? error.message : error}`)
|
|
77
|
-
process.exit(1)
|
|
78
|
-
} finally {
|
|
79
|
-
client.close()
|
|
80
|
-
}
|
|
81
|
-
}
|
|
@@ -1,118 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* get_all_pages 命令
|
|
3
|
-
* 获取 MasterGo 文档的所有页面信息
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Command } from 'commander'
|
|
7
|
-
import { writeFileSync } from 'node:fs'
|
|
8
|
-
import { resolve, dirname } from 'node:path'
|
|
9
|
-
import { mkdirSync } from 'node:fs'
|
|
10
|
-
import { tmpdir } from 'node:os'
|
|
11
|
-
import { MessageType } from '../../shared/constants.js'
|
|
12
|
-
import { MGClient } from '../client.js'
|
|
13
|
-
import { extractFileId } from '../../shared/utils.js'
|
|
14
|
-
import type { AllPagesInfo } from '../../shared/types.js'
|
|
15
|
-
|
|
16
|
-
interface GetAllPagesOptions {
|
|
17
|
-
link?: string
|
|
18
|
-
fileId?: string
|
|
19
|
-
output?: string
|
|
20
|
-
noAutoStart?: boolean
|
|
21
|
-
noRetry?: boolean
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 创建 get_all_pages 命令
|
|
26
|
-
*/
|
|
27
|
-
export function createGetAllPagesCommand(): Command {
|
|
28
|
-
return new Command('get_all_pages')
|
|
29
|
-
.description('获取 MasterGo 文档的所有页面信息。不指定 --output 时保存到系统临时目录')
|
|
30
|
-
.option('--link <url>', '页面链接。支持完整 URL 或 mgp:// 协议')
|
|
31
|
-
.option('--fileId <id>', '文件 ID(纯数字)。从 URL 中 /file/ 后面的数字')
|
|
32
|
-
.option('--output <path>', '输出 JSON 文件路径。不指定则保存到系统临时目录')
|
|
33
|
-
.option('--no-auto-start', '禁用自动启动 Server')
|
|
34
|
-
.option('--no-retry', '禁用自动重试')
|
|
35
|
-
.action(async (options: GetAllPagesOptions) => {
|
|
36
|
-
await handleGetAllPages(options)
|
|
37
|
-
})
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* 处理 get_all_pages 命令
|
|
42
|
-
*/
|
|
43
|
-
async function handleGetAllPages(options: GetAllPagesOptions): Promise<void> {
|
|
44
|
-
// 提取 fileId
|
|
45
|
-
let fileId: string | null = null
|
|
46
|
-
|
|
47
|
-
if (options.fileId) {
|
|
48
|
-
fileId = options.fileId
|
|
49
|
-
} else if (options.link) {
|
|
50
|
-
fileId = extractFileId(options.link)
|
|
51
|
-
if (!fileId) {
|
|
52
|
-
console.error('错误: 无法从链接中提取 fileId')
|
|
53
|
-
console.error('支持的格式:')
|
|
54
|
-
console.error(' - 完整 URL: https://mastergo.netease.com/file/174875497054651')
|
|
55
|
-
console.error(' - mgp 协议: mgp://mastergo.netease.com/file/174875497054651')
|
|
56
|
-
console.error(' - 纯 fileId: 174875497054651')
|
|
57
|
-
process.exit(1)
|
|
58
|
-
}
|
|
59
|
-
} else {
|
|
60
|
-
// 没有提供参数,获取当前连接页面的所有页面
|
|
61
|
-
console.log('未提供 --link 或 --fileId,将获取当前连接页面的所有页面信息')
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
const client = new MGClient({
|
|
65
|
-
noAutoStart: options.noAutoStart,
|
|
66
|
-
noRetry: options.noRetry,
|
|
67
|
-
})
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
// 连接 Server
|
|
71
|
-
await client.connect()
|
|
72
|
-
|
|
73
|
-
// 构建 pageUrl(如果有 fileId)
|
|
74
|
-
let pageUrl: string | undefined
|
|
75
|
-
if (fileId) {
|
|
76
|
-
// 构建标准化的 pageUrl
|
|
77
|
-
pageUrl = `mastergo.netease.com/file/${fileId}`
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
// 发送请求
|
|
81
|
-
const data = await client.requestWithRetry<AllPagesInfo>(
|
|
82
|
-
MessageType.GET_ALL_PAGES,
|
|
83
|
-
{},
|
|
84
|
-
pageUrl
|
|
85
|
-
)
|
|
86
|
-
|
|
87
|
-
// 确定输出路径
|
|
88
|
-
let outputPath: string
|
|
89
|
-
if (options.output) {
|
|
90
|
-
outputPath = resolve(options.output)
|
|
91
|
-
} else {
|
|
92
|
-
// 使用系统临时目录
|
|
93
|
-
const filename = `pages_${fileId || 'current'}_${Date.now()}.json`
|
|
94
|
-
outputPath = resolve(tmpdir(), filename)
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
// 确保目录存在
|
|
98
|
-
const outputDir = dirname(outputPath)
|
|
99
|
-
mkdirSync(outputDir, { recursive: true })
|
|
100
|
-
|
|
101
|
-
// 保存到文件
|
|
102
|
-
const jsonContent = JSON.stringify(data, null, 2)
|
|
103
|
-
writeFileSync(outputPath, jsonContent, 'utf-8')
|
|
104
|
-
|
|
105
|
-
// 输出结果
|
|
106
|
-
const size = jsonContent.length
|
|
107
|
-
const sizeKB = (size / 1024).toFixed(2)
|
|
108
|
-
console.log(`文件路径: ${outputPath}`)
|
|
109
|
-
console.log(`文档名称: ${data.documentName}`)
|
|
110
|
-
console.log(`页面数量: ${data.totalCount}`)
|
|
111
|
-
console.log(`数据大小: ${size.toLocaleString()} 字符 (约 ${sizeKB} KB)`)
|
|
112
|
-
} catch (error) {
|
|
113
|
-
console.error(`错误: ${error instanceof Error ? error.message : error}`)
|
|
114
|
-
process.exit(1)
|
|
115
|
-
} finally {
|
|
116
|
-
client.close()
|
|
117
|
-
}
|
|
118
|
-
}
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* get_node_by_id 命令
|
|
3
|
-
* 根据节点 ID 获取节点详细信息
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Command } from 'commander'
|
|
7
|
-
import { writeFileSync } from 'node:fs'
|
|
8
|
-
import { resolve, dirname } from 'node:path'
|
|
9
|
-
import { mkdirSync } from 'node:fs'
|
|
10
|
-
import { MessageType } from '../../shared/constants.js'
|
|
11
|
-
import { MGClient } from '../client.js'
|
|
12
|
-
import type { GetNodeParams, NodeInfo } from '../../shared/types.js'
|
|
13
|
-
|
|
14
|
-
interface GetNodeByIdOptions {
|
|
15
|
-
nodeId: string
|
|
16
|
-
output: string
|
|
17
|
-
maxDepth?: string
|
|
18
|
-
includeInvisible?: boolean
|
|
19
|
-
noAutoStart?: boolean
|
|
20
|
-
noRetry?: boolean
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* 创建 get_node_by_id 命令
|
|
25
|
-
*/
|
|
26
|
-
export function createGetNodeByIdCommand(): Command {
|
|
27
|
-
return new Command('get_node_by_id')
|
|
28
|
-
.description('根据节点 ID 获取节点详细信息。数据保存到指定 JSON 文件,返回文件路径和大小信息')
|
|
29
|
-
.requiredOption('--nodeId <id>', '节点 ID,格式如 123:456。可从 MasterGo 浮窗链接中获取')
|
|
30
|
-
.requiredOption('--output <path>', '输出 JSON 文件路径。支持绝对路径或相对路径')
|
|
31
|
-
.option('--maxDepth <number>', '遍历深度,默认 1。增加深度会显著增加数据量', '1')
|
|
32
|
-
.option('--includeInvisible', '包含不可见节点(visible: false),默认不包含', false)
|
|
33
|
-
.option('--no-auto-start', '禁用自动启动 Server')
|
|
34
|
-
.option('--no-retry', '禁用自动重试')
|
|
35
|
-
.action(async (options: GetNodeByIdOptions) => {
|
|
36
|
-
await handleGetNodeById(options)
|
|
37
|
-
})
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* 处理 get_node_by_id 命令
|
|
42
|
-
*/
|
|
43
|
-
async function handleGetNodeById(options: GetNodeByIdOptions): Promise<void> {
|
|
44
|
-
const client = new MGClient({
|
|
45
|
-
noAutoStart: options.noAutoStart,
|
|
46
|
-
noRetry: options.noRetry,
|
|
47
|
-
})
|
|
48
|
-
|
|
49
|
-
try {
|
|
50
|
-
// 连接 Server
|
|
51
|
-
await client.connect()
|
|
52
|
-
|
|
53
|
-
// 发送请求
|
|
54
|
-
const params: GetNodeParams = {
|
|
55
|
-
nodeId: options.nodeId,
|
|
56
|
-
maxDepth: parseInt(options.maxDepth || '1', 10),
|
|
57
|
-
includeInvisible: options.includeInvisible || false,
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const data = await client.requestWithRetry<NodeInfo>(MessageType.GET_NODE_BY_ID, params)
|
|
61
|
-
|
|
62
|
-
// 保存到文件
|
|
63
|
-
const outputPath = resolve(options.output)
|
|
64
|
-
const outputDir = dirname(outputPath)
|
|
65
|
-
mkdirSync(outputDir, { recursive: true })
|
|
66
|
-
|
|
67
|
-
const jsonContent = JSON.stringify(data, null, 2)
|
|
68
|
-
writeFileSync(outputPath, jsonContent, 'utf-8')
|
|
69
|
-
|
|
70
|
-
// 输出结果
|
|
71
|
-
const size = jsonContent.length
|
|
72
|
-
const sizeKB = (size / 1024).toFixed(2)
|
|
73
|
-
console.log(`文件路径: ${outputPath}`)
|
|
74
|
-
console.log(`节点 ID: ${options.nodeId}`)
|
|
75
|
-
console.log(`数据大小: ${size.toLocaleString()} 字符 (约 ${sizeKB} KB)`)
|
|
76
|
-
console.log(`节点深度: ${params.maxDepth}`)
|
|
77
|
-
} catch (error) {
|
|
78
|
-
console.error(`错误: ${error instanceof Error ? error.message : error}`)
|
|
79
|
-
process.exit(1)
|
|
80
|
-
} finally {
|
|
81
|
-
client.close()
|
|
82
|
-
}
|
|
83
|
-
}
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* get_node_by_link 命令
|
|
3
|
-
* 解析 mgp:// 协议链接并获取节点信息
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { Command } from 'commander'
|
|
7
|
-
import { writeFileSync } from 'node:fs'
|
|
8
|
-
import { resolve, dirname } from 'node:path'
|
|
9
|
-
import { mkdirSync } from 'node:fs'
|
|
10
|
-
import { MessageType } from '../../shared/constants.js'
|
|
11
|
-
import { MGClient, parseMgpLink } from '../client.js'
|
|
12
|
-
import { ErrorCode, MGError } from '../../shared/errors.js'
|
|
13
|
-
import type { GetNodeParams, NodeInfo } from '../../shared/types.js'
|
|
14
|
-
|
|
15
|
-
interface GetNodeByLinkOptions {
|
|
16
|
-
link: string
|
|
17
|
-
output: string
|
|
18
|
-
maxDepth?: string
|
|
19
|
-
includeInvisible?: boolean
|
|
20
|
-
noAutoStart?: boolean
|
|
21
|
-
noRetry?: boolean
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* 创建 get_node_by_link 命令
|
|
26
|
-
*/
|
|
27
|
-
export function createGetNodeByLinkCommand(): Command {
|
|
28
|
-
return new Command('get_node_by_link')
|
|
29
|
-
.description('解析 mgp:// 协议链接并获取节点信息')
|
|
30
|
-
.requiredOption('--link <url>', 'mgp:// 协议链接')
|
|
31
|
-
.requiredOption('--output <path>', '输出 JSON 文件路径')
|
|
32
|
-
.option('--maxDepth <number>', '遍历深度', '1')
|
|
33
|
-
.option('--includeInvisible', '包含不可见节点', false)
|
|
34
|
-
.option('--no-auto-start', '禁用自动启动 Server')
|
|
35
|
-
.option('--no-retry', '禁用自动重试')
|
|
36
|
-
.action(async (options: GetNodeByLinkOptions) => {
|
|
37
|
-
await handleGetNodeByLink(options)
|
|
38
|
-
})
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* 处理 get_node_by_link 命令
|
|
43
|
-
*/
|
|
44
|
-
async function handleGetNodeByLink(options: GetNodeByLinkOptions): Promise<void> {
|
|
45
|
-
// 解析 mgp:// 链接
|
|
46
|
-
const parsed = parseMgpLink(options.link)
|
|
47
|
-
if (!parsed) {
|
|
48
|
-
console.error(`错误 [${ErrorCode.INVALID_LINK}]: 无效的 mgp:// 链接格式`)
|
|
49
|
-
console.error(`提供的链接: ${options.link}`)
|
|
50
|
-
console.error(`期望格式: mgp://[mastergo_page_url]/nodeId`)
|
|
51
|
-
process.exit(1)
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const { pageUrl, nodeId } = parsed
|
|
55
|
-
|
|
56
|
-
const client = new MGClient({
|
|
57
|
-
noAutoStart: options.noAutoStart,
|
|
58
|
-
noRetry: options.noRetry,
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
try {
|
|
62
|
-
// 连接 Server
|
|
63
|
-
await client.connect()
|
|
64
|
-
|
|
65
|
-
// 发送请求
|
|
66
|
-
const params: GetNodeParams = {
|
|
67
|
-
nodeId,
|
|
68
|
-
maxDepth: parseInt(options.maxDepth || '1', 10),
|
|
69
|
-
includeInvisible: options.includeInvisible || false,
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const data = await client.requestWithRetry<NodeInfo>(
|
|
73
|
-
MessageType.GET_NODE_BY_ID,
|
|
74
|
-
params,
|
|
75
|
-
pageUrl
|
|
76
|
-
)
|
|
77
|
-
|
|
78
|
-
// 保存到文件
|
|
79
|
-
const outputPath = resolve(options.output)
|
|
80
|
-
const outputDir = dirname(outputPath)
|
|
81
|
-
mkdirSync(outputDir, { recursive: true })
|
|
82
|
-
|
|
83
|
-
const jsonContent = JSON.stringify(data, null, 2)
|
|
84
|
-
writeFileSync(outputPath, jsonContent, 'utf-8')
|
|
85
|
-
|
|
86
|
-
// 输出结果
|
|
87
|
-
const size = jsonContent.length
|
|
88
|
-
const sizeKB = (size / 1024).toFixed(2)
|
|
89
|
-
console.log(`文件路径: ${outputPath}`)
|
|
90
|
-
console.log(`Link: ${options.link}`)
|
|
91
|
-
console.log(`页面 URL: ${pageUrl}`)
|
|
92
|
-
console.log(`节点 ID: ${nodeId}`)
|
|
93
|
-
console.log(`数据大小: ${size.toLocaleString()} 字符 (约 ${sizeKB} KB)`)
|
|
94
|
-
console.log(`节点深度: ${params.maxDepth}`)
|
|
95
|
-
} catch (error) {
|
|
96
|
-
if (error instanceof MGError) {
|
|
97
|
-
console.error(`错误 [${error.code}]: ${error.message}`)
|
|
98
|
-
} else {
|
|
99
|
-
console.error(`错误: ${error instanceof Error ? error.message : error}`)
|
|
100
|
-
}
|
|
101
|
-
process.exit(1)
|
|
102
|
-
} finally {
|
|
103
|
-
client.close()
|
|
104
|
-
}
|
|
105
|
-
}
|