@icyfenix-dmla/cli 2026.4.17-546

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/bin/dmla.js ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * DMLA CLI 入口
4
+ */
5
+ import '../src/index.js'
package/package.json ADDED
@@ -0,0 +1,52 @@
1
+ {
2
+ "name": "@icyfenix-dmla/cli",
3
+ "version": "2026.4.17-546",
4
+ "description": "DMLA 沙箱服务命令行工具",
5
+ "type": "module",
6
+ "main": "src/index.js",
7
+ "bin": {
8
+ "dmla": "./bin/dmla.js"
9
+ },
10
+ "scripts": {
11
+ "start": "node src/index.js",
12
+ "test": "node --experimental-vm-modules $(npm root)/jest/bin/jest.js"
13
+ },
14
+ "dependencies": {
15
+ "commander": "^12.1.0",
16
+ "chalk": "^5.3.0",
17
+ "enquirer": "^2.4.1",
18
+ "dockerode": "^4.0.2",
19
+ "express": "^4.21.2",
20
+ "cors": "^2.8.5"
21
+ },
22
+ "devDependencies": {
23
+ "jest": "^29.7.0"
24
+ },
25
+ "jest": {
26
+ "testEnvironment": "node",
27
+ "transform": {},
28
+ "moduleFileExtensions": [
29
+ "js",
30
+ "mjs"
31
+ ],
32
+ "testMatch": [
33
+ "**/tests/**/*.test.js"
34
+ ]
35
+ },
36
+ "engines": {
37
+ "node": ">=18.0.0"
38
+ },
39
+ "keywords": [
40
+ "dmla",
41
+ "sandbox",
42
+ "python",
43
+ "machine-learning",
44
+ "cli"
45
+ ],
46
+ "author": "icyfenix",
47
+ "license": "MIT",
48
+ "repository": {
49
+ "type": "git",
50
+ "url": "https://github.com/icyfenix/dmla"
51
+ }
52
+ }
@@ -0,0 +1,334 @@
1
+ /**
2
+ * 管理命令(安装、更新、诊断)
3
+ */
4
+ import chalk from 'chalk'
5
+ import Docker from 'dockerode'
6
+ import { spawn, execSync } from 'child_process'
7
+ import http from 'http'
8
+ import path from 'path'
9
+ import { fileURLToPath } from 'url'
10
+ import fs from 'fs'
11
+
12
+ const __filename = fileURLToPath(import.meta.url)
13
+ const __dirname = path.dirname(__filename)
14
+
15
+ const docker = new Docker()
16
+
17
+ // 配置
18
+ const CONFIG = {
19
+ imageCpu: 'dmla-sandbox:cpu',
20
+ imageGpu: 'dmla-sandbox:gpu',
21
+ dockerhubRegistry: 'icyfenix',
22
+ tcrRegistry: 'ccr.ccs.tencentyun.com/icyfenix',
23
+ imageName: 'dmla-sandbox',
24
+ defaultPort: 3001
25
+ }
26
+
27
+ /**
28
+ * 获取镜像仓库地址
29
+ */
30
+ function getRegistryUrl(registry) {
31
+ if (registry === 'tcr') {
32
+ return `${CONFIG.tcrRegistry}/${CONFIG.imageName}`
33
+ }
34
+ return `${CONFIG.dockerhubRegistry}/${CONFIG.imageName}`
35
+ }
36
+
37
+ /**
38
+ * 安装镜像
39
+ */
40
+ export async function installImages(types, registry = 'dockerhub') {
41
+ const registryUrl = getRegistryUrl(registry)
42
+
43
+ console.log(chalk.gray(` 从 ${registry === 'tcr' ? '腾讯云 TCR' : 'Docker Hub'} 拉取镜像`))
44
+
45
+ for (const type of types) {
46
+ console.log()
47
+ console.log(chalk.bold(`📥 拉取 ${type.toUpperCase()} 版本镜像...`))
48
+
49
+ const remoteImage = `${registryUrl}:${type}`
50
+ const localImage = type === 'gpu' ? CONFIG.imageGpu : CONFIG.imageCpu
51
+
52
+ try {
53
+ // 拉取镜像
54
+ await pullImageWithProgress(remoteImage)
55
+
56
+ // Tag 为本地名称
57
+ console.log(chalk.gray(` 重命名为 ${localImage}...`))
58
+ const image = docker.getImage(remoteImage)
59
+ await image.tag({ repo: CONFIG.imageName, tag: type })
60
+
61
+ console.log(chalk.green(`✅ ${type.toUpperCase()} 镜像安装完成`))
62
+ } catch (error) {
63
+ console.log(chalk.red(`❌ ${type.toUpperCase()} 镜像安装失败: ${error.message}`))
64
+ }
65
+ }
66
+
67
+ console.log()
68
+ console.log(chalk.green('🎉 镜像安装完成'))
69
+ console.log(chalk.yellow('💡 提示: 运行 dmla start 启动服务'))
70
+ }
71
+
72
+ /**
73
+ * 带进度显示的镜像拉取
74
+ */
75
+ async function pullImageWithProgress(imageName) {
76
+ return new Promise((resolve, reject) => {
77
+ docker.pull(imageName, (err, stream) => {
78
+ if (err) {
79
+ reject(err)
80
+ return
81
+ }
82
+
83
+ // 解析进度
84
+ docker.modem.followProgress(stream, (err, output) => {
85
+ if (err) {
86
+ reject(err)
87
+ } else {
88
+ resolve(output)
89
+ }
90
+ }, (event) => {
91
+ // 显示进度
92
+ if (event.status) {
93
+ let progress = event.status
94
+ if (event.progress) {
95
+ progress += ` ${event.progress}`
96
+ }
97
+ if (event.id) {
98
+ console.log(chalk.gray(` [${event.id}] ${progress}`))
99
+ } else {
100
+ console.log(chalk.gray(` ${progress}`))
101
+ }
102
+ }
103
+ })
104
+ })
105
+ })
106
+ }
107
+
108
+ /**
109
+ * 更新所有组件
110
+ */
111
+ export async function updateAll(registry = 'dockerhub') {
112
+ console.log()
113
+
114
+ // 更新 npm 包
115
+ console.log(chalk.bold('📦 更新 npm 包'))
116
+ try {
117
+ console.log(chalk.gray(' 执行 npm update -g @icyfenix-dmla/cli...'))
118
+ execSync('npm update -g @icyfenix-dmla/cli', { stdio: 'inherit' })
119
+ console.log(chalk.green('✅ npm 包已更新'))
120
+ } catch (error) {
121
+ console.log(chalk.yellow('⚠️ npm 包更新失败或已是最新版本'))
122
+ }
123
+
124
+ console.log()
125
+
126
+ // 检查并更新镜像
127
+ console.log(chalk.bold('🖼️ 检查 Docker 镜像更新'))
128
+ const registryUrl = getRegistryUrl(registry)
129
+
130
+ for (const type of ['cpu', 'gpu']) {
131
+ const remoteImage = `${registryUrl}:${type}`
132
+ const localImage = type === 'gpu' ? CONFIG.imageGpu : CONFIG.imageCpu
133
+
134
+ console.log(chalk.gray(` 检查 ${type.toUpperCase()} 版本...`))
135
+
136
+ try {
137
+ // 检查本地镜像是否存在
138
+ let localImageInfo = null
139
+ try {
140
+ localImageInfo = await docker.getImage(localImage).inspect()
141
+ } catch {
142
+ // 本地镜像不存在,需要拉取
143
+ }
144
+
145
+ // 拉取最新镜像
146
+ console.log(chalk.gray(` 拉取最新 ${type.toUpperCase()} 镜像...`))
147
+ await pullImageWithProgress(remoteImage)
148
+
149
+ // 获取拉取的镜像信息
150
+ const remoteImageInfo = await docker.getImage(remoteImage).inspect()
151
+
152
+ // 比较镜像 ID
153
+ if (localImageInfo && localImageInfo.Id === remoteImageInfo.Id) {
154
+ console.log(chalk.green(`✅ ${type.toUpperCase()} 镜像已是最新版本`))
155
+ } else {
156
+ // Tag 为本地名称
157
+ console.log(chalk.gray(` 重命名为 ${localImage}...`))
158
+ const image = docker.getImage(remoteImage)
159
+ await image.tag({ repo: CONFIG.imageName, tag: type })
160
+
161
+ console.log(chalk.green(`✅ ${type.toUpperCase()} 镜像已更新`))
162
+ }
163
+ } catch (error) {
164
+ console.log(chalk.yellow(`⚠️ ${type.toUpperCase()} 镜像更新失败: ${error.message}`))
165
+ }
166
+ }
167
+
168
+ console.log()
169
+ console.log(chalk.green('🎉 更新完成'))
170
+ }
171
+
172
+ /**
173
+ * 环境诊断
174
+ */
175
+ export async function runDoctor() {
176
+ console.log()
177
+ const issues = []
178
+
179
+ // ───────────────────────────────────────────────────────────
180
+ // Docker 检查
181
+ // ───────────────────────────────────────────────────────────
182
+ console.log(chalk.bold('🐳 Docker 环境'))
183
+
184
+ try {
185
+ const dockerInfo = await docker.info()
186
+ console.log(chalk.green(' ✅ Docker 已安装'))
187
+ console.log(chalk.gray(` 版本: ${dockerInfo.ServerVersion || '未知'}`))
188
+
189
+ // 检查版本是否满足要求
190
+ const minVersion = '20.10'
191
+ if (dockerInfo.ServerVersion && dockerInfo.ServerVersion < minVersion) {
192
+ issues.push(`Docker 版本过低,建议升级到 ${minVersion} 或更高`)
193
+ }
194
+ } catch (error) {
195
+ console.log(chalk.red(' ❌ Docker 未安装或未运行'))
196
+ issues.push('请安装 Docker 并确保服务正在运行')
197
+ }
198
+
199
+ console.log()
200
+
201
+ // ───────────────────────────────────────────────────────────
202
+ // 镜像检查
203
+ // ───────────────────────────────────────────────────────────
204
+ console.log(chalk.bold('🖼️ Docker 镜像'))
205
+
206
+ const cpuImage = CONFIG.imageCpu
207
+ const gpuImage = CONFIG.imageGpu
208
+
209
+ let cpuExists = false
210
+ let gpuExists = false
211
+
212
+ try {
213
+ const cpuInfo = await docker.getImage(cpuImage).inspect()
214
+ cpuExists = true
215
+ console.log(chalk.green(` ✅ CPU 镜像已安装`))
216
+ console.log(chalk.gray(` 大小: ${Math.round(cpuInfo.Size / 1024 / 1024)} MB`))
217
+ } catch {
218
+ console.log(chalk.red(` ❌ CPU 镜像未安装`))
219
+ issues.push('运行 dmla install --cpu 安装 CPU 镜像')
220
+ }
221
+
222
+ try {
223
+ const gpuInfo = await docker.getImage(gpuImage).inspect()
224
+ gpuExists = true
225
+ console.log(chalk.green(` ✅ GPU 镜像已安装`))
226
+ console.log(chalk.gray(` 大小: ${Math.round(gpuInfo.Size / 1024 / 1024)} MB`))
227
+ } catch {
228
+ console.log(chalk.yellow(` ⚠️ GPU 镜像未安装`))
229
+ console.log(chalk.gray(' (可选,仅在需要 GPU 时安装)'))
230
+ }
231
+
232
+ console.log()
233
+
234
+ // ───────────────────────────────────────────────────────────
235
+ // GPU 检查
236
+ // ───────────────────────────────────────────────────────────
237
+ console.log(chalk.bold('🎮 GPU 驱动'))
238
+
239
+ try {
240
+ const output = execSync('nvidia-smi -L', { timeout: 5000, encoding: 'utf8' })
241
+ if (output.includes('GPU')) {
242
+ console.log(chalk.green(' ✅ NVIDIA GPU 可用'))
243
+ const lines = output.split('\n').filter(l => l.trim())
244
+ lines.forEach(line => console.log(chalk.gray(` ${line.trim()}`)))
245
+
246
+ // 检查 GPU 镜像
247
+ if (!gpuExists) {
248
+ console.log(chalk.yellow(' 💡 检测到 GPU,建议安装 GPU 镜像'))
249
+ issues.push('运行 dmla install --gpu 安装 GPU 镜像')
250
+ }
251
+ } else {
252
+ console.log(chalk.gray(' GPU 不可用'))
253
+ }
254
+ } catch {
255
+ console.log(chalk.gray(' GPU 不可用'))
256
+ console.log(chalk.gray(' (如果需要 GPU,请安装 NVIDIA 驱动)'))
257
+ }
258
+
259
+ console.log()
260
+
261
+ // ───────────────────────────────────────────────────────────
262
+ // 端口检查
263
+ // ───────────────────────────────────────────────────────────
264
+ console.log(chalk.bold('🔌 端口可用性'))
265
+
266
+ const port = CONFIG.defaultPort
267
+ const portAvailable = await checkPortAvailable(port)
268
+
269
+ if (portAvailable) {
270
+ console.log(chalk.green(` ✅ 端口 ${port} 可用`))
271
+ } else {
272
+ console.log(chalk.red(` ❌ 端口 ${port} 已被占用`))
273
+ issues.push(`端口 ${port} 已被占用,使用 --port 指定其他端口`)
274
+ }
275
+
276
+ console.log()
277
+
278
+ // ───────────────────────────────────────────────────────────
279
+ // 网络连通性
280
+ // ───────────────────────────────────────────────────────────
281
+ console.log(chalk.bold('🌐 网络连通性'))
282
+
283
+ // 测试 Docker Hub
284
+ console.log(chalk.gray(' 测试 Docker Hub 连接...'))
285
+ try {
286
+ execSync('docker pull icyfenix/dmla-sandbox:cpu --quiet', { timeout: 10000 })
287
+ console.log(chalk.green(' ✅ Docker Hub 连接正常'))
288
+ } catch {
289
+ console.log(chalk.yellow(' ⚠️ Docker Hub 连接超时或受限'))
290
+ }
291
+
292
+ // 测试 TCR
293
+ console.log(chalk.gray(' 测试腾讯云 TCR 连接...'))
294
+ try {
295
+ execSync('docker pull ccr.ccs.tencentyun.com/icyfenix/dmla-sandbox:cpu --quiet', { timeout: 10000 })
296
+ console.log(chalk.green(' ✅ TCR 连接正常'))
297
+ } catch {
298
+ console.log(chalk.yellow(' ⚠️ TCR 连接超时或受限'))
299
+ }
300
+
301
+ console.log()
302
+
303
+ // ───────────────────────────────────────────────────────────
304
+ // 问题汇总
305
+ // ───────────────────────────────────────────────────────────
306
+ if (issues.length > 0) {
307
+ console.log(chalk.bold.red('❌ 发现以下问题:'))
308
+ console.log()
309
+ issues.forEach((issue, i) => {
310
+ console.log(chalk.red(` ${i + 1}. ${issue}`))
311
+ })
312
+ console.log()
313
+ console.log(chalk.yellow('💡 请根据上述提示解决问题后再次运行 dmla doctor'))
314
+ } else {
315
+ console.log(chalk.bold.green('✅ 所有检查通过,环境正常'))
316
+ console.log()
317
+ console.log(chalk.gray('💡 运行 dmla start 启动服务'))
318
+ }
319
+ }
320
+
321
+ /**
322
+ * 检查端口是否可用
323
+ */
324
+ async function checkPortAvailable(port) {
325
+ return new Promise((resolve) => {
326
+ const server = http.createServer()
327
+ server.once('error', () => resolve(false))
328
+ server.once('listening', () => {
329
+ server.close()
330
+ resolve(true)
331
+ })
332
+ server.listen(port)
333
+ })
334
+ }
@@ -0,0 +1,293 @@
1
+ /**
2
+ * 服务管理命令
3
+ */
4
+ import chalk from 'chalk'
5
+ import Docker from 'dockerode'
6
+ import { spawn } from 'child_process'
7
+ import http from 'http'
8
+ import path from 'path'
9
+ import { fileURLToPath } from 'url'
10
+ import fs from 'fs'
11
+
12
+ const __filename = fileURLToPath(import.meta.url)
13
+ const __dirname = path.dirname(__filename)
14
+
15
+ const docker = new Docker()
16
+
17
+ // 配置
18
+ const CONFIG = {
19
+ imageCpu: 'dmla-sandbox:cpu',
20
+ imageGpu: 'dmla-sandbox:gpu',
21
+ defaultPort: 3001
22
+ }
23
+
24
+ /**
25
+ * 检查端口是否可用
26
+ */
27
+ async function checkPortAvailable(port) {
28
+ return new Promise((resolve) => {
29
+ const server = http.createServer()
30
+ server.once('error', () => resolve(false))
31
+ server.once('listening', () => {
32
+ server.close()
33
+ resolve(true)
34
+ })
35
+ server.listen(port)
36
+ })
37
+ }
38
+
39
+ /**
40
+ * 检查镜像是否存在
41
+ */
42
+ async function checkImageExists(type) {
43
+ const image = type === 'gpu' ? CONFIG.imageGpu : CONFIG.imageCpu
44
+ try {
45
+ await docker.getImage(image).inspect()
46
+ return true
47
+ } catch {
48
+ return false
49
+ }
50
+ }
51
+
52
+ /**
53
+ * 检查 GPU 是否可用
54
+ */
55
+ async function checkGPUAvailable() {
56
+ try {
57
+ // 尝试运行 nvidia-smi 命令
58
+ const result = await new Promise((resolve, reject) => {
59
+ const proc = spawn('nvidia-smi', ['-L'], { timeout: 5000 })
60
+ let output = ''
61
+ proc.stdout.on('data', (data) => output += data.toString())
62
+ proc.stderr.on('data', (data) => output += data.toString())
63
+ proc.on('close', (code) => {
64
+ if (code === 0) resolve(output)
65
+ else reject(new Error('nvidia-smi failed'))
66
+ })
67
+ proc.on('error', reject)
68
+ })
69
+ return result.includes('GPU')
70
+ } catch {
71
+ return false
72
+ }
73
+ }
74
+
75
+ /**
76
+ * 检查服务是否运行
77
+ */
78
+ async function checkServiceRunning(port) {
79
+ return new Promise((resolve) => {
80
+ const req = http.request({
81
+ hostname: 'localhost',
82
+ port: port,
83
+ path: '/api/health',
84
+ method: 'GET',
85
+ timeout: 2000
86
+ }, (res) => {
87
+ resolve(res.statusCode === 200)
88
+ })
89
+ req.on('error', () => resolve(false))
90
+ req.on('timeout', () => {
91
+ req.destroy()
92
+ resolve(false)
93
+ })
94
+ req.end()
95
+ })
96
+ }
97
+
98
+ /**
99
+ * 查找运行中的服务容器
100
+ */
101
+ async function findServiceContainer() {
102
+ try {
103
+ const containers = await docker.listContainers({ all: true })
104
+ // 查找 dmla 服务容器
105
+ for (const container of containers) {
106
+ if (container.Names.some(name => name.includes('dmla-server'))) {
107
+ return container
108
+ }
109
+ }
110
+ return null
111
+ } catch {
112
+ return null
113
+ }
114
+ }
115
+
116
+ /**
117
+ * 启动服务
118
+ */
119
+ export async function startServer(port, useGpu = false) {
120
+ // 检查端口
121
+ const portAvailable = await checkPortAvailable(port)
122
+ if (!portAvailable) {
123
+ console.log(chalk.red(`❌ 端口 ${port} 已被占用`))
124
+ console.log(chalk.yellow('💡 提示: 使用 --port 选项指定其他端口'))
125
+ return
126
+ }
127
+
128
+ // 检查镜像
129
+ const imageType = useGpu ? 'gpu' : 'cpu'
130
+ const imageExists = await checkImageExists(imageType)
131
+ if (!imageExists) {
132
+ console.log(chalk.red(`❌ 镜像 ${useGpu ? CONFIG.imageGpu : CONFIG.imageCpu} 不存在`))
133
+ console.log(chalk.yellow('💡 提示: 运行 dmla install 安装镜像'))
134
+ return
135
+ }
136
+
137
+ // 检查服务是否已运行
138
+ const alreadyRunning = await checkServiceRunning(port)
139
+ if (alreadyRunning) {
140
+ console.log(chalk.green(`✅ 服务已在端口 ${port} 运行`))
141
+ return
142
+ }
143
+
144
+ // 启动服务
145
+ console.log(chalk.gray(' 正在启动...'))
146
+
147
+ try {
148
+ // 使用 spawn 启动 server 进程
149
+ const serverPath = path.resolve(__dirname, '../../../local-server/src/index.js')
150
+
151
+ // 如果 server 文件不存在,说明是独立安装模式,需要启动内置服务
152
+ const standaloneServerPath = path.resolve(__dirname, '../server/index.js')
153
+
154
+ const actualServerPath = fs.existsSync(serverPath) ? serverPath :
155
+ fs.existsSync(standaloneServerPath) ? standaloneServerPath : null
156
+
157
+ if (!actualServerPath) {
158
+ console.log(chalk.red('❌ 找不到服务入口文件'))
159
+ console.log(chalk.yellow('💡 提示: 确保正确安装了 @icyfenix-dmla/cli'))
160
+ return
161
+ }
162
+
163
+ const env = {
164
+ ...process.env,
165
+ PORT: port.toString(),
166
+ USE_GPU: useGpu ? 'true' : 'false'
167
+ }
168
+
169
+ const serverProcess = spawn('node', [actualServerPath], {
170
+ env,
171
+ stdio: 'inherit',
172
+ detached: true
173
+ })
174
+
175
+ serverProcess.unref()
176
+
177
+ // 等待服务启动
178
+ console.log(chalk.gray(' 等待服务就绪...'))
179
+ let attempts = 0
180
+ const maxAttempts = 30
181
+
182
+ while (attempts < maxAttempts) {
183
+ const running = await checkServiceRunning(port)
184
+ if (running) {
185
+ console.log(chalk.green(`✅ 服务已启动: http://localhost:${port}`))
186
+ console.log(chalk.gray(` 健康检查: http://localhost:${port}/api/health`))
187
+ return
188
+ }
189
+ await new Promise(resolve => setTimeout(resolve, 500))
190
+ attempts++
191
+ }
192
+
193
+ console.log(chalk.yellow('⚠️ 服务启动超时,请检查日志'))
194
+ } catch (error) {
195
+ console.log(chalk.red(`❌ 启动失败: ${error.message}`))
196
+ }
197
+ }
198
+
199
+ /**
200
+ * 停止服务
201
+ */
202
+ export async function stopServer() {
203
+ // 查找运行中的容器
204
+ const container = await findServiceContainer()
205
+
206
+ if (container) {
207
+ try {
208
+ const containerObj = docker.getContainer(container.Id)
209
+ await containerObj.stop()
210
+ await containerObj.remove()
211
+ console.log(chalk.green('✅ 服务已停止'))
212
+ } catch (error) {
213
+ console.log(chalk.red(`❌ 停止失败: ${error.message}`))
214
+ }
215
+ } else {
216
+ // 尝试通过端口查找进程
217
+ console.log(chalk.yellow('⚠️ 未找到运行中的服务容器'))
218
+ console.log(chalk.gray(' 提示: 服务可能以非容器模式运行'))
219
+ }
220
+ }
221
+
222
+ /**
223
+ * 获取状态
224
+ */
225
+ export async function getStatus() {
226
+ console.log()
227
+
228
+ // 检查 npm 包版本
229
+ console.log(chalk.bold('📦 npm 包版本'))
230
+ try {
231
+ const pkgPath = path.resolve(__dirname, '../package.json')
232
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
233
+ console.log(chalk.gray(` @icyfenix-dmla/cli: ${pkg.version}`))
234
+ } catch {
235
+ console.log(chalk.gray(' 版本信息不可用'))
236
+ }
237
+
238
+ console.log()
239
+
240
+ // 检查镜像
241
+ console.log(chalk.bold('🖼️ Docker 镜像'))
242
+ const cpuExists = await checkImageExists('cpu')
243
+ const gpuExists = await checkImageExists('gpu')
244
+ console.log(chalk.gray(` CPU: ${cpuExists ? chalk.green('已安装') : chalk.red('未安装')}`))
245
+ console.log(chalk.gray(` GPU: ${gpuExists ? chalk.green('已安装') : chalk.red('未安装')}`))
246
+
247
+ console.log()
248
+
249
+ // 检查 GPU
250
+ console.log(chalk.bold('🎮 GPU 状态'))
251
+ const gpuAvailable = await checkGPUAvailable()
252
+ if (gpuAvailable) {
253
+ console.log(chalk.green(' GPU 可用'))
254
+ try {
255
+ const proc = spawn('nvidia-smi', ['-L'])
256
+ proc.stdout.on('data', (data) => {
257
+ const lines = data.toString().split('\n').filter(l => l.trim())
258
+ lines.forEach(line => console.log(chalk.gray(` ${line}`)))
259
+ })
260
+ } catch {}
261
+ } else {
262
+ console.log(chalk.gray(' GPU 不可用'))
263
+ }
264
+
265
+ console.log()
266
+
267
+ // 检查服务
268
+ console.log(chalk.bold('🚀 服务状态'))
269
+ const running = await checkServiceRunning(CONFIG.defaultPort)
270
+ if (running) {
271
+ console.log(chalk.green(` 服务运行中 (端口 ${CONFIG.defaultPort})`))
272
+ try {
273
+ // 获取详细状态
274
+ const healthUrl = `http://localhost:${CONFIG.defaultPort}/api/sandbox/health`
275
+ http.get(healthUrl, (res) => {
276
+ let data = ''
277
+ res.on('data', (chunk) => data += chunk)
278
+ res.on('end', () => {
279
+ try {
280
+ const health = JSON.parse(data)
281
+ if (health.images) {
282
+ console.log(chalk.gray(` CPU 镜像: ${health.images.cpu ? '就绪' : '未就绪'}`))
283
+ console.log(chalk.gray(` GPU 镜像: ${health.images.gpu ? '就绪' : '未就绪'}`))
284
+ }
285
+ } catch {}
286
+ })
287
+ })
288
+ } catch {}
289
+ } else {
290
+ console.log(chalk.gray(' 服务未运行'))
291
+ console.log(chalk.yellow(' 提示: 运行 dmla start 启动服务'))
292
+ }
293
+ }
package/src/index.js ADDED
@@ -0,0 +1,104 @@
1
+ /**
2
+ * DMLA CLI 入口
3
+ * 沙箱服务命令行管理工具
4
+ */
5
+ import { program } from 'commander'
6
+ import chalk from 'chalk'
7
+ import { startServer, stopServer, getStatus } from './commands/server.js'
8
+ import { installImages, updateAll, runDoctor } from './commands/manage.js'
9
+
10
+ const VERSION = '0.0.0' // 将在发布时由 workflow 更新
11
+
12
+ program
13
+ .name('dmla')
14
+ .description('DMLA 沙箱服务命令行管理工具')
15
+ .version(VERSION)
16
+
17
+ // ─────────────────────────────────────────────────────────────
18
+ // start 命令
19
+ // ─────────────────────────────────────────────────────────────
20
+ program
21
+ .command('start')
22
+ .description('启动沙箱服务')
23
+ .option('-p, --port <number>', '服务端口', '3001')
24
+ .option('--gpu', '使用 GPU 镜像')
25
+ .action(async (options) => {
26
+ const port = parseInt(options.port, 10)
27
+ const useGpu = options.gpu
28
+ console.log(chalk.blue('🚀 启动 DMLA 沙箱服务...'))
29
+ console.log(chalk.gray(` 端口: ${port}`))
30
+ console.log(chalk.gray(` 镜像: ${useGpu ? 'GPU' : 'CPU'}`))
31
+ await startServer(port, useGpu)
32
+ })
33
+
34
+ // ─────────────────────────────────────────────────────────────
35
+ // stop 命令
36
+ // ─────────────────────────────────────────────────────────────
37
+ program
38
+ .command('stop')
39
+ .description('停止运行中的沙箱服务')
40
+ .action(async () => {
41
+ console.log(chalk.blue('🛑 停止 DMLA 沙箱服务...'))
42
+ await stopServer()
43
+ })
44
+
45
+ // ─────────────────────────────────────────────────────────────
46
+ // status 命令
47
+ // ─────────────────────────────────────────────────────────────
48
+ program
49
+ .command('status')
50
+ .description('查看服务状态')
51
+ .action(async () => {
52
+ console.log(chalk.blue('📊 DMLA 沙箱服务状态'))
53
+ await getStatus()
54
+ })
55
+
56
+ // ─────────────────────────────────────────────────────────────
57
+ // install 命令
58
+ // ─────────────────────────────────────────────────────────────
59
+ program
60
+ .command('install')
61
+ .description('安装 Docker 镜像')
62
+ .option('--cpu', '仅安装 CPU 版本')
63
+ .option('--gpu', '仅安装 GPU 版本')
64
+ .option('--all', '安装所有镜像(默认)')
65
+ .option('-r, --registry <type>', '镜像仓库 (dockerhub/tcr)', 'dockerhub')
66
+ .action(async (options) => {
67
+ const registry = options.registry
68
+ let types = []
69
+
70
+ if (options.cpu) types.push('cpu')
71
+ if (options.gpu) types.push('gpu')
72
+ if (types.length === 0 || options.all) types = ['cpu', 'gpu']
73
+
74
+ console.log(chalk.blue('📦 安装 DMLA Docker 镜像...'))
75
+ console.log(chalk.gray(` 仓库: ${registry}`))
76
+ console.log(chalk.gray(` 类型: ${types.join(', ')}`))
77
+
78
+ await installImages(types, registry)
79
+ })
80
+
81
+ // ─────────────────────────────────────────────────────────────
82
+ // update 命令
83
+ // ─────────────────────────────────────────────────────────────
84
+ program
85
+ .command('update')
86
+ .description('更新 npm 包和 Docker 镜像')
87
+ .option('-r, --registry <type>', '镜像仓库 (dockerhub/tcr)', 'dockerhub')
88
+ .action(async (options) => {
89
+ console.log(chalk.blue('🔄 更新 DMLA...'))
90
+ await updateAll(options.registry)
91
+ })
92
+
93
+ // ─────────────────────────────────────────────────────────────
94
+ // doctor 命令
95
+ // ─────────────────────────────────────────────────────────────
96
+ program
97
+ .command('doctor')
98
+ .description('诊断安装环境')
99
+ .action(async () => {
100
+ console.log(chalk.blue('🔍 DMLA 环境诊断'))
101
+ await runDoctor()
102
+ })
103
+
104
+ program.parse()
@@ -0,0 +1,34 @@
1
+ /**
2
+ * CLI 命令单元测试
3
+ */
4
+ import { describe, it, expect, beforeAll } from '@jest/globals'
5
+ import { fileURLToPath } from 'url'
6
+ import path from 'path'
7
+ import fs from 'fs'
8
+
9
+ // ESM 中获取 __dirname
10
+ const __filename = fileURLToPath(import.meta.url)
11
+ const __dirname = path.dirname(__filename)
12
+
13
+ // 基础测试:确保模块可导入
14
+ describe('CLI Module', () => {
15
+ beforeAll(() => {
16
+ // 设置测试环境
17
+ })
18
+
19
+ it('should have correct package.json', async () => {
20
+ const pkgPath = path.resolve(__dirname, '../package.json')
21
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
22
+
23
+ expect(pkg.name).toBe('@icyfenix-dmla/cli')
24
+ expect(pkg.bin).toBeDefined()
25
+ expect(pkg.bin.dmla).toBe('./bin/dmla.js')
26
+ })
27
+
28
+ it('should have commander dependency', async () => {
29
+ const pkgPath = path.resolve(__dirname, '../package.json')
30
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf8'))
31
+
32
+ expect(pkg.dependencies.commander).toBeDefined()
33
+ })
34
+ })