@hangox/mg-cli 1.0.0
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/.eslintrc.cjs +26 -0
- package/CLAUDE.md +43 -0
- package/bin/mg-cli.js +4 -0
- package/package.json +51 -0
- package/src/cli/client.ts +266 -0
- package/src/cli/commands/execute-code.ts +59 -0
- package/src/cli/commands/export-image.ts +193 -0
- package/src/cli/commands/get-all-nodes.ts +81 -0
- package/src/cli/commands/get-all-pages.ts +118 -0
- package/src/cli/commands/get-node-by-id.ts +83 -0
- package/src/cli/commands/get-node-by-link.ts +105 -0
- package/src/cli/commands/server.ts +130 -0
- package/src/cli/index.ts +33 -0
- package/src/index.ts +9 -0
- package/src/server/connection-manager.ts +211 -0
- package/src/server/daemon-runner.ts +22 -0
- package/src/server/daemon.ts +211 -0
- package/src/server/index.ts +8 -0
- package/src/server/logger.ts +117 -0
- package/src/server/request-handler.ts +192 -0
- package/src/server/websocket-server.ts +297 -0
- package/src/shared/constants.ts +90 -0
- package/src/shared/errors.ts +131 -0
- package/src/shared/index.ts +8 -0
- package/src/shared/types.ts +227 -0
- package/src/shared/utils.ts +352 -0
- package/tests/unit/shared/constants.test.ts +66 -0
- package/tests/unit/shared/errors.test.ts +82 -0
- package/tests/unit/shared/utils.test.ts +208 -0
- package/tsconfig.json +22 -0
- package/tsup.config.ts +33 -0
- package/vitest.config.ts +22 -0
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Server 管理命令
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from 'commander'
|
|
6
|
+
import {
|
|
7
|
+
startServerForeground,
|
|
8
|
+
startServerDaemon,
|
|
9
|
+
stopServer,
|
|
10
|
+
restartServer,
|
|
11
|
+
getServerStatus,
|
|
12
|
+
} from '../../server/daemon.js'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 创建 server 命令组
|
|
16
|
+
*/
|
|
17
|
+
export function createServerCommand(): Command {
|
|
18
|
+
const serverCmd = new Command('server')
|
|
19
|
+
.description('Server 管理命令')
|
|
20
|
+
|
|
21
|
+
// server start
|
|
22
|
+
serverCmd
|
|
23
|
+
.command('start')
|
|
24
|
+
.description('启动 MG Server')
|
|
25
|
+
.option('--port <number>', '指定启动端口', (value) => parseInt(value, 10))
|
|
26
|
+
.option('--foreground', '前台模式运行(不作为守护进程)', false)
|
|
27
|
+
.action(async (options) => {
|
|
28
|
+
try {
|
|
29
|
+
if (options.foreground) {
|
|
30
|
+
await startServerForeground(options.port)
|
|
31
|
+
} else {
|
|
32
|
+
const info = await startServerDaemon(options.port)
|
|
33
|
+
console.log('MG Server 启动成功')
|
|
34
|
+
console.log(`监听端口: ${info.port}`)
|
|
35
|
+
console.log(`进程 PID: ${info.pid}`)
|
|
36
|
+
console.log(`运行模式: 守护进程`)
|
|
37
|
+
}
|
|
38
|
+
} catch (error: any) {
|
|
39
|
+
console.error(`错误: ${error.message}`)
|
|
40
|
+
process.exit(1)
|
|
41
|
+
}
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
// server stop
|
|
45
|
+
serverCmd
|
|
46
|
+
.command('stop')
|
|
47
|
+
.description('停止 MG Server')
|
|
48
|
+
.action(() => {
|
|
49
|
+
try {
|
|
50
|
+
const { stopped, info } = stopServer()
|
|
51
|
+
|
|
52
|
+
if (stopped && info) {
|
|
53
|
+
console.log('MG Server 已停止')
|
|
54
|
+
console.log(`PID: ${info.pid}`)
|
|
55
|
+
|
|
56
|
+
// 计算运行时长
|
|
57
|
+
const uptimeMs = Date.now() - new Date(info.startedAt).getTime()
|
|
58
|
+
const seconds = Math.floor(uptimeMs / 1000)
|
|
59
|
+
const minutes = Math.floor(seconds / 60)
|
|
60
|
+
const hours = Math.floor(minutes / 60)
|
|
61
|
+
|
|
62
|
+
let uptime = ''
|
|
63
|
+
if (hours > 0) {
|
|
64
|
+
uptime = `${hours} 小时 ${minutes % 60} 分钟`
|
|
65
|
+
} else if (minutes > 0) {
|
|
66
|
+
uptime = `${minutes} 分钟 ${seconds % 60} 秒`
|
|
67
|
+
} else {
|
|
68
|
+
uptime = `${seconds} 秒`
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
console.log(`运行时长: ${uptime}`)
|
|
72
|
+
} else {
|
|
73
|
+
console.log('MG Server 未运行')
|
|
74
|
+
}
|
|
75
|
+
} catch (error: any) {
|
|
76
|
+
console.error(`错误: ${error.message}`)
|
|
77
|
+
process.exit(1)
|
|
78
|
+
}
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// server restart
|
|
82
|
+
serverCmd
|
|
83
|
+
.command('restart')
|
|
84
|
+
.description('重启 MG Server')
|
|
85
|
+
.option('--port <number>', '重启后使用的端口', (value) => parseInt(value, 10))
|
|
86
|
+
.action(async (options) => {
|
|
87
|
+
try {
|
|
88
|
+
const status = getServerStatus()
|
|
89
|
+
|
|
90
|
+
if (status.running) {
|
|
91
|
+
console.log(`正在停止 MG Server (PID: ${status.pid})...`)
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const info = await restartServer(options.port)
|
|
95
|
+
|
|
96
|
+
console.log('MG Server 已重启')
|
|
97
|
+
console.log(`监听端口: ${info.port}`)
|
|
98
|
+
console.log(`新进程 PID: ${info.pid}`)
|
|
99
|
+
} catch (error: any) {
|
|
100
|
+
console.error(`错误: ${error.message}`)
|
|
101
|
+
process.exit(1)
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
// server status
|
|
106
|
+
serverCmd
|
|
107
|
+
.command('status')
|
|
108
|
+
.description('查看 Server 运行状态')
|
|
109
|
+
.action(() => {
|
|
110
|
+
try {
|
|
111
|
+
const status = getServerStatus()
|
|
112
|
+
|
|
113
|
+
if (status.running) {
|
|
114
|
+
console.log('MG Server 状态: 运行中 ✓')
|
|
115
|
+
console.log(`监听端口: ${status.port}`)
|
|
116
|
+
console.log(`进程 PID: ${status.pid}`)
|
|
117
|
+
console.log(`启动时间: ${status.startedAt}`)
|
|
118
|
+
console.log(`运行时长: ${status.uptime}`)
|
|
119
|
+
} else {
|
|
120
|
+
console.log('MG Server 状态: 未运行 ✗')
|
|
121
|
+
console.log("提示: 使用 'mg-cli server start' 启动 Server")
|
|
122
|
+
}
|
|
123
|
+
} catch (error: any) {
|
|
124
|
+
console.error(`错误: ${error.message}`)
|
|
125
|
+
process.exit(1)
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
return serverCmd
|
|
130
|
+
}
|
package/src/cli/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* MG CLI 入口
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from 'commander'
|
|
6
|
+
import { createServerCommand } from './commands/server.js'
|
|
7
|
+
import { createGetNodeByIdCommand } from './commands/get-node-by-id.js'
|
|
8
|
+
import { createGetNodeByLinkCommand } from './commands/get-node-by-link.js'
|
|
9
|
+
import { createGetAllNodesCommand } from './commands/get-all-nodes.js'
|
|
10
|
+
import { createExportImageCommand } from './commands/export-image.js'
|
|
11
|
+
import { createExecuteCodeCommand } from './commands/execute-code.js'
|
|
12
|
+
import { createGetAllPagesCommand } from './commands/get-all-pages.js'
|
|
13
|
+
|
|
14
|
+
const program = new Command()
|
|
15
|
+
|
|
16
|
+
program
|
|
17
|
+
.name('mg-cli')
|
|
18
|
+
.description('MasterGo CLI 工具 - 用于 Claude Code 与 MasterGo 通信')
|
|
19
|
+
.version('1.0.0')
|
|
20
|
+
|
|
21
|
+
// 添加 server 命令组
|
|
22
|
+
program.addCommand(createServerCommand())
|
|
23
|
+
|
|
24
|
+
// 添加业务命令
|
|
25
|
+
program.addCommand(createGetNodeByIdCommand())
|
|
26
|
+
program.addCommand(createGetNodeByLinkCommand())
|
|
27
|
+
program.addCommand(createGetAllNodesCommand())
|
|
28
|
+
program.addCommand(createExportImageCommand())
|
|
29
|
+
program.addCommand(createExecuteCodeCommand())
|
|
30
|
+
program.addCommand(createGetAllPagesCommand())
|
|
31
|
+
|
|
32
|
+
// 解析命令行参数
|
|
33
|
+
program.parse()
|