@wwkit/opm 1.0.1 → 1.0.3

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/src/config.json5 CHANGED
@@ -11,6 +11,7 @@
11
11
  // 命令行 -p/--proxy 传入时优先使用传入值。
12
12
  proxy: {
13
13
  local: "socks5://127.0.0.1:1080",
14
+ huawei: "http://{env:PROXY_USER}:{env:PROXY_PASSWORD}@proxyhm-hk.security.huawei.com:8080",
14
15
  active: "",
15
16
  },
16
17
 
@@ -53,7 +54,7 @@
53
54
  "ubuntu-aliyun": "deb http://mirrors.aliyun.com/ubuntu/ focal main restricted universe multiverse",
54
55
  "debian-official": "deb http://deb.debian.org/debian/ bookworm main",
55
56
  "debian-tsinghua": "deb https://mirrors.tuna.tsinghua.edu.cn/debian/ bookworm main",
56
- active: "ubuntu-tsinghua",
57
+ active: "ubuntu-huawei",
57
58
  },
58
59
  },
59
60
 
@@ -99,7 +100,8 @@
99
100
  registry: {
100
101
  official: "https://github.com/nvm-sh/nvm.git",
101
102
  gitee: "https://gitee.com/mirrors/nvm.git",
102
- active: "official",
103
+ huawei: "https://gitee.com/mirrors/nvm.git",
104
+ active: "huawei",
103
105
  },
104
106
  },
105
107
 
@@ -110,7 +112,8 @@
110
112
  official: "https://nodejs.org/dist",
111
113
  taobao: "https://npmmirror.com/mirrors/node",
112
114
  tsinghua: "https://mirrors.tuna.tsinghua.edu.cn/nodejs-release/",
113
- active: "taobao",
115
+ huawei: "https://mirrors.huaweicloud.com/nodejs/",
116
+ active: "huawei",
114
117
  },
115
118
  },
116
119
 
@@ -119,7 +122,8 @@
119
122
  registry: {
120
123
  official: "https://github.com/phpenv/phpenv.git",
121
124
  gitee: "https://gitee.com/mirrors/phpenv.git",
122
- active: "official",
125
+ huawei: "https://mirrors.huaweicloud.com/phpenv.git",
126
+ active: "huawei",
123
127
  },
124
128
  },
125
129
 
@@ -130,7 +134,8 @@
130
134
  official: "https://www.php.net/distributions",
131
135
  tsinghua: "https://mirrors.tuna.tsinghua.edu.cn/php",
132
136
  ustc: "https://mirrors.ustc.edu.cn/php",
133
- active: "official",
137
+ huawei: "https://mirrors.huaweicloud.com/php/",
138
+ active: "huawei",
134
139
  },
135
140
  },
136
141
 
@@ -141,7 +146,8 @@
141
146
  registry: {
142
147
  official: "https://github.com/astral-sh/python-build-standalone/releases/download",
143
148
  ghproxy: "https://ghproxy.com/https://github.com/astral-sh/python-build-standalone/releases/download",
144
- active: "official",
149
+ huawei: "https://mirrors.huaweicloud.com/python-build-standalone/",
150
+ active: "ghproxy",
145
151
  },
146
152
  },
147
153
 
@@ -152,7 +158,75 @@
152
158
  registry: {
153
159
  official: "https://googlechromelabs.github.io/chrome-for-testing",
154
160
  npmmirror: "https://npmmirror.com/mirrors/chrome-for-testing",
161
+ huawei: "https://mirrors.huaweicloud.com/google/chrome-for-testing/",
162
+ deepblue: "http://deepbluenet.com/mirrors/chrome-for-testing",
155
163
  active: "npmmirror",
156
164
  },
157
165
  },
166
+
167
+ // uv 自身安装脚本镜像(install.sh URL)
168
+ // active 指向预设 key 或完整 URL;install 时 curl 该地址执行
169
+ uv: {
170
+ registry: {
171
+ official: "https://astral.sh/uv/install.sh",
172
+ ghproxy: "https://ghproxy.com/https://astral.sh/uv/install.sh",
173
+ active: "ghproxy",
174
+ },
175
+ },
176
+
177
+ // docker 自身安装脚本镜像(便捷脚本 URL)
178
+ // active 指向预设 key 或完整 URL;install 时 curl 该地址执行(Linux 用)
179
+ docker: {
180
+ registry: {
181
+ official: "https://get.docker.com",
182
+ huawei: "https://mirrors.huaweicloud.com/docker-ce/linux/",
183
+ active: "huawei",
184
+ },
185
+ },
186
+
187
+ // homebrew 自身安装脚本镜像(install.sh URL)
188
+ // active 指向预设 key 或完整 URL;install 时 curl 该地址执行
189
+ homebrew: {
190
+ registry: {
191
+ official: "https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh",
192
+ gitee: "https://gitee.com/mirrors/homebrew-install/raw/HEAD/install.sh",
193
+ active: "official",
194
+ },
195
+ },
196
+
197
+ // FTP 连接配置
198
+ // password 用 {env:XXX} 引用环境变量,不硬编码
199
+ // pasv: false — 阿里云虚拟主机必须用主动模式
200
+ ftp: {
201
+ host: "wh-nxb3pizm7zyfvpv4113.my3v.com",
202
+ port: 21,
203
+ user: "{env:FTP_USER}",
204
+ password: "{env:FTP_PASSWORD}",
205
+ remote_root: "/htdocs/webwork",
206
+ base_url: "http://deepbluenet.com/webwork",
207
+ timeout: 120,
208
+ max_retries: 2,
209
+ pasv: false,
210
+ },
211
+
212
+ // Git 全局配置预设(opm git init 一键初始化时使用)
213
+ // pullRebase: false — pull 时用 merge(有冲突直接合并),非 rebase
214
+ git: {
215
+ sslVerify: false,
216
+ user: {
217
+ name: "",
218
+ email: "",
219
+ },
220
+ pullRebase: false,
221
+ },
222
+
223
+ // opencode AI coding agent 环境变量
224
+ // {pkg:@wwkit/harness} 动态解析为已安装的 @wwkit/harness 包目录
225
+ // 任何设备安装 @wwkit/opm + @wwkit/harness 后自动生效,无需手动配置路径
226
+ opencode: {
227
+ env: {
228
+ OPENCODE_CONFIG_DIR: "{pkg:@wwkit/harness}",
229
+ OPENCODE_CONFIG_CONTENT: "",
230
+ },
231
+ },
158
232
  }
@@ -26,6 +26,8 @@ const ACTIONS = {
26
26
  config: { desc: 'Show config file content' },
27
27
  clear: { desc: 'Clear sessions / data' },
28
28
  dir: { desc: 'Show data / db / log / config paths' },
29
+ env: { desc: 'Show harness environment variables' },
30
+ open: { desc: 'Open TUI in a work directory (with env vars injected)' },
29
31
  help: { desc: 'Show this help' },
30
32
  }
31
33
 
@@ -77,6 +79,10 @@ export class HarnessGroup {
77
79
  return this._clear(rest)
78
80
  case 'dir':
79
81
  return this._dir()
82
+ case 'env':
83
+ return this._env()
84
+ case 'open':
85
+ return this._open(rest)
80
86
  default:
81
87
  console.error(`Unknown action: ${action}`)
82
88
  this.printHelp()
@@ -170,6 +176,30 @@ export class HarnessGroup {
170
176
  })
171
177
  }
172
178
 
179
+ _env() {
180
+ if (!this.cfg.getEnvVars) {
181
+ output({ name: this.cfg.name, env: {}, hint: 'No env vars defined for this harness' })
182
+ return
183
+ }
184
+ const env = this.cfg.getEnvVars()
185
+ output({ name: this.cfg.name, env })
186
+ }
187
+
188
+ _open(rest) {
189
+ if (!this.cfg.open) {
190
+ console.error(`open is not supported for ${this.cfg.name}`)
191
+ process.exit(1)
192
+ }
193
+ const parsed = parseFlags(rest)
194
+ const workDir = parsed.flags.d || parsed.flags.dir || process.cwd()
195
+ const resolved = path.resolve(workDir)
196
+ if (!fs.existsSync(resolved)) {
197
+ console.error(`Directory not found: ${resolved}`)
198
+ process.exit(1)
199
+ }
200
+ this.cfg.open(resolved)
201
+ }
202
+
173
203
  printHelp() {
174
204
  const actionLines = Object.entries(ACTIONS)
175
205
  .map(([sig, { desc }]) => ` ${sig.padEnd(12)} ${desc}`)
@@ -191,6 +221,7 @@ ${actionLines}
191
221
  ${clearSection}
192
222
  Options:
193
223
  -p, --proxy <url> Proxy for install/upgrade (overrides config proxy.active)
224
+ -d, --dir <dir> Working directory for open (default: cwd)
194
225
  -h, --help Show this help
195
226
 
196
227
  Examples:
@@ -200,6 +231,9 @@ Examples:
200
231
  opm ${this.cfg.name} config
201
232
  opm ${this.cfg.name} clear
202
233
  opm ${this.cfg.name} dir
234
+ opm ${this.cfg.name} env
235
+ opm ${this.cfg.name} open Open in current directory
236
+ opm ${this.cfg.name} open -d /path/to/project
203
237
  `)
204
238
  }
205
239
  }
@@ -3,14 +3,26 @@
3
3
  *
4
4
  * opencode 是 AI coding agent,通过 npm 全局包 opencode-ai 安装(binary: opencode)。
5
5
  * 数据目录跨平台(db.js),会话清理复用 clear.js。
6
+ * env 命令管理 OPENCODE_CONFIG_DIR / OPENCODE_CONFIG_CONTENT 环境变量。
7
+ * open 命令在指定工作目录启动 opencode TUI(自动注入 env 变量)。
6
8
  */
7
9
 
8
10
  import fs from 'node:fs'
9
11
  import path from 'node:path'
10
12
  import os from 'node:os'
13
+ import { spawn } from 'node:child_process'
14
+ import { Shell } from '@wwkit/shared'
15
+ import { getConfig } from '../../config.js'
11
16
  import { getDataDir, getDbPath, getLogPath } from './db.js'
12
17
  import { runClear } from './clear.js'
13
18
 
19
+ const shell = new Shell()
20
+
21
+ /**
22
+ * opencode 专属环境变量名
23
+ */
24
+ const ENV_VARS = ['OPENCODE_CONFIG_DIR', 'OPENCODE_CONFIG_CONTENT']
25
+
14
26
  /**
15
27
  * 查找 opencode 配置文件路径(优先 .jsonc,其次 .json)
16
28
  * @returns {string|null}
@@ -25,6 +37,47 @@ function findConfigFile() {
25
37
  return null
26
38
  }
27
39
 
40
+ /**
41
+ * 获取 env 变量当前值
42
+ * 优先级:系统环境变量 > opm config opencode.env
43
+ * @returns {Record<string, string>}
44
+ */
45
+ function getEnvVars() {
46
+ const cfgEnv = (getConfig().opencode?.env) || {}
47
+ const result = {}
48
+ for (const key of ENV_VARS) {
49
+ result[key] = process.env[key] || cfgEnv[key] || ''
50
+ }
51
+ return result
52
+ }
53
+
54
+ /**
55
+ * 在指定工作目录启动 opencode TUI
56
+ * 自动注入有值的环境变量(系统 env > opm config)
57
+ * @param {string} workDir - 工作目录
58
+ */
59
+ function openTui(workDir) {
60
+ const env = { ...process.env }
61
+ const cfgEnv = getEnvVars()
62
+ for (const key of ENV_VARS) {
63
+ if (cfgEnv[key]) {
64
+ env[key] = cfgEnv[key]
65
+ }
66
+ }
67
+
68
+ const cmd = shell.isWindows ? 'opencode.exe' : 'opencode'
69
+ const child = spawn(cmd, [], {
70
+ cwd: workDir,
71
+ stdio: 'inherit',
72
+ env,
73
+ shell: shell.shellForExec(false),
74
+ })
75
+ child.on('error', (err) => {
76
+ console.error(`Failed to start opencode: ${err.message}`)
77
+ process.exit(1)
78
+ })
79
+ }
80
+
28
81
  export const opencodeHarness = {
29
82
  name: 'opencode',
30
83
  desc: 'opencode AI coding agent',
@@ -43,4 +96,7 @@ export const opencodeHarness = {
43
96
  ['--backup', 'Backup the DB before deleting (default: off)'],
44
97
  ['--db <path>', 'Override the opencode database path'],
45
98
  ],
99
+ envVars: ENV_VARS,
100
+ getEnvVars,
101
+ open: openTui,
46
102
  }
package/src/index.js CHANGED
@@ -16,6 +16,13 @@ export { CftGroup } from './binaries/cft/index.js'
16
16
  export { NvmGroup } from './cli/groups/nvm.js'
17
17
  export { PhpenvGroup } from './cli/groups/phpenv.js'
18
18
  export { UvGroup } from './cli/groups/uv.js'
19
+ export { HomebrewGroup } from './cli/groups/homebrew.js'
20
+ export { WslGroup } from './cli/groups/wsl.js'
21
+ export { DockerGroup } from './cli/groups/docker.js'
22
+ export { PingGroup } from './cli/groups/ping.js'
23
+ export { ProcGroup } from './cli/groups/proc.js'
24
+ export { FtpGroup } from './tools/ftp/index.js'
25
+ export { GitGroup } from './tools/git/index.js'
19
26
  export { ToolManagerGroup } from './cli/groups/version-manager.js'
20
27
  export { parseFlags } from './cli/helpers/args.js'
21
28
 
@@ -31,10 +38,22 @@ export { BunManager } from './managers/bun.js'
31
38
  export { NodeManager, isNvmInstalled, getNvmSh } from './managers/node.js'
32
39
  export { PhpManager, isPhpenvInstalled, getPhpenvBin } from './managers/php.js'
33
40
  export { PythonManager, isUvInstalled, getUvBin } from './managers/python.js'
41
+ export {
42
+ isWslInstalled, getWslVersion, parseWslVersionOutput, parseDistroList,
43
+ parseAvailableList, listDistros, listAvailable, installDistro, removeDistro,
44
+ startDistro, stopDistro, stopAll, setDefaultDistro, getDefaultDistro,
45
+ getWslStatus, openWsl, installWsl, updateWsl, uninstallWsl, getWslDir,
46
+ } from './managers/wsl.js'
47
+ export {
48
+ isDockerInstalled, getDockerVersion, installDocker, upgradeDocker,
49
+ uninstallDocker, getDockerDir, getDockerStatus, startDocker, stopDocker,
50
+ restartDocker, openDocker,
51
+ } from './managers/docker.js'
34
52
 
35
53
  export {
36
54
  NPM_PRESETS, PIP_PRESETS, DNF_PRESETS, APT_PRESETS, BREW_PRESETS, COMPOSER_PRESETS,
37
55
  BUN_PRESETS, NVM_PRESETS, NODE_PRESETS, PHPENV_PRESETS, PHP_PRESETS, PYTHON_PRESETS,
56
+ UV_PRESETS, DOCKER_PRESETS, HOMEBREW_PRESETS,
38
57
  getPresets, resolveRegistryInput,
39
58
  } from './presets.js'
40
59
  export { PROXY_PRESETS } from './proxy-presets.js'
@@ -12,9 +12,6 @@ import fs from 'node:fs'
12
12
  import { PackageManager } from './base.js'
13
13
  import { getActiveRegistry, getPresets, setActiveRegistry } from '../config.js'
14
14
 
15
- const BREW_INSTALL_URL = 'https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh'
16
- const BREW_UNINSTALL_URL = 'https://raw.githubusercontent.com/Homebrew/uninstall/HEAD/uninstall.sh'
17
-
18
15
  /**
19
16
  * 定位 brew 可执行文件(兼容 Intel / Apple Silicon 路径),找不到时退回 PATH
20
17
  * @returns {string}
@@ -40,14 +37,17 @@ export function isHomebrewInstalled() {
40
37
  }
41
38
 
42
39
  /**
43
- * 安装 Homebrew(curl 官方安装脚本)
40
+ * 安装 Homebrew(curl 官方安装脚本;脚本 URL 来自 homebrew.registry.active)
41
+ * @param {string} [proxy] - 代理地址
44
42
  * @returns {{ action: string, hint: string }}
45
43
  */
46
- export function installHomebrew() {
44
+ export function installHomebrew(proxy) {
47
45
  if (process.platform === 'win32') {
48
46
  throw new Error('Homebrew is not supported on Windows (use WSL)')
49
47
  }
50
- execSync(`curl -fsSL "${BREW_INSTALL_URL}" | bash`, { stdio: 'inherit', shell: true })
48
+ const installUrl = getActiveRegistry('homebrew') || 'https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh'
49
+ const proxyArg = proxy ? ` -x "${proxy}"` : ''
50
+ execSync(`curl -fsSL${proxyArg} "${installUrl}" | bash`, { stdio: 'inherit', shell: true })
51
51
  return { action: 'installed', hint: 'Restart shell or ensure /opt/homebrew/bin (or /usr/local/bin) is in PATH' }
52
52
  }
53
53
 
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Docker 管理器
3
+ *
4
+ * 跨平台安装策略:
5
+ * - Linux: 官方便捷脚本 get.docker.com
6
+ * - macOS: brew install --cask docker (Docker Desktop)
7
+ * - Windows: WSL2 内跑便捷脚本(WSL 当作 Linux)
8
+ *
9
+ * daemon 管理:
10
+ * - Linux: systemctl start/stop/restart docker
11
+ * - macOS: open/quit Docker Desktop app
12
+ * - Windows: wsl -e systemctl start/stop/restart docker
13
+ */
14
+
15
+ import { execSync, spawnSync } from 'node:child_process'
16
+ import fs from 'node:fs'
17
+ import { getBrewBin } from './brew.js'
18
+ import { openWsl } from './wsl.js'
19
+ import { getActiveRegistry } from '../config.js'
20
+
21
+ /**
22
+ * 检查 docker 命令是否可用
23
+ * @returns {boolean}
24
+ */
25
+ export function isDockerInstalled() {
26
+ try {
27
+ execSync('docker --version', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })
28
+ return true
29
+ } catch {
30
+ return false
31
+ }
32
+ }
33
+
34
+ /**
35
+ * 获取 docker 版本
36
+ * @returns {string}
37
+ */
38
+ export function getDockerVersion() {
39
+ const out = execSync('docker --version', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })
40
+ const m = out.match(/Docker version\s+(\S+)/)
41
+ return m ? m[1] : out.trim()
42
+ }
43
+
44
+ /**
45
+ * 在 Linux 环境中执行 Docker 便捷脚本(脚本 URL 来自 docker.registry.active)
46
+ * @param {string} [proxy] - 代理地址
47
+ * @private
48
+ */
49
+ function runConvenienceScript(proxy) {
50
+ const installUrl = getActiveRegistry('docker') || 'https://get.docker.com'
51
+ const proxyArg = proxy ? ` -x "${proxy}"` : ''
52
+ execSync(`curl -fsSL${proxyArg} ${installUrl} | sh`, { stdio: 'inherit', shell: true })
53
+ }
54
+
55
+ /**
56
+ * 安装 Docker
57
+ * @param {string} [proxy] - 代理地址
58
+ * @returns {{ action: string, method: string, hint?: string }}
59
+ */
60
+ export function installDocker(proxy) {
61
+ const platform = process.platform
62
+
63
+ if (platform === 'linux') {
64
+ runConvenienceScript()
65
+ return { action: 'installed', method: 'convenience-script' }
66
+ }
67
+
68
+ if (platform === 'darwin') {
69
+ execSync(`${getBrewBin()} install --cask docker`, { stdio: 'inherit', shell: true })
70
+ return { action: 'installed', method: 'brew-cask', hint: 'Start Docker Desktop from Applications' }
71
+ }
72
+
73
+ if (platform === 'win32') {
74
+ execSync('wsl.exe -e bash -c "curl -fsSL https://get.docker.com | sh"', { stdio: 'inherit', shell: true })
75
+ return { action: 'installed', method: 'wsl-convenience-script', hint: 'Docker installed inside WSL2' }
76
+ }
77
+
78
+ throw new Error(`Unsupported platform: ${platform}`)
79
+ }
80
+
81
+ /**
82
+ * 升级 Docker
83
+ * @returns {{ action: string, method: string }}
84
+ */
85
+ export function upgradeDocker() {
86
+ const platform = process.platform
87
+
88
+ if (platform === 'linux') {
89
+ try {
90
+ execSync('apt-get update && apt-get upgrade -y docker-ce', { stdio: 'inherit', shell: true })
91
+ return { action: 'upgraded', method: 'apt' }
92
+ } catch {
93
+ try {
94
+ execSync('dnf upgrade -y docker-ce', { stdio: 'inherit', shell: true })
95
+ return { action: 'upgraded', method: 'dnf' }
96
+ } catch {
97
+ runConvenienceScript(proxy)
98
+ return { action: 'upgraded', method: 'convenience-script' }
99
+ }
100
+ }
101
+ }
102
+
103
+ if (platform === 'darwin') {
104
+ execSync(`${getBrewBin()} upgrade --cask docker`, { stdio: 'inherit', shell: true })
105
+ return { action: 'upgraded', method: 'brew-cask' }
106
+ }
107
+
108
+ if (platform === 'win32') {
109
+ execSync('wsl.exe -e bash -c "sudo apt-get update && sudo apt-get upgrade -y docker-ce"', { stdio: 'inherit', shell: true })
110
+ return { action: 'upgraded', method: 'wsl-apt' }
111
+ }
112
+
113
+ throw new Error(`Unsupported platform: ${platform}`)
114
+ }
115
+
116
+ /**
117
+ * 卸载 Docker
118
+ * @returns {{ action: string, hint: string }}
119
+ */
120
+ export function uninstallDocker() {
121
+ const platform = process.platform
122
+
123
+ if (platform === 'linux') {
124
+ try {
125
+ execSync('apt-get purge -y docker-ce docker-ce-cli containerd.io', { stdio: 'inherit', shell: true })
126
+ } catch {
127
+ execSync('dnf remove -y docker-ce docker-ce-cli containerd.io', { stdio: 'inherit', shell: true })
128
+ }
129
+ return { action: 'uninstalled', hint: 'Images/containers left in /var/lib/docker, remove manually if needed' }
130
+ }
131
+
132
+ if (platform === 'darwin') {
133
+ execSync(`${getBrewBin()} uninstall --cask docker`, { stdio: 'inherit', shell: true })
134
+ return { action: 'uninstalled', hint: 'Docker Desktop app removed' }
135
+ }
136
+
137
+ if (platform === 'win32') {
138
+ return {
139
+ action: 'uninstall',
140
+ hint: 'Run in WSL: sudo apt-get purge -y docker-ce docker-ce-cli containerd.io',
141
+ }
142
+ }
143
+
144
+ throw new Error(`Unsupported platform: ${platform}`)
145
+ }
146
+
147
+ /**
148
+ * 获取 Docker 数据目录
149
+ * @returns {string}
150
+ */
151
+ export function getDockerDir() {
152
+ const platform = process.platform
153
+
154
+ if (platform === 'linux') {
155
+ return '/var/lib/docker'
156
+ }
157
+
158
+ if (platform === 'darwin') {
159
+ const home = process.env.HOME || ''
160
+ return `${home}/.docker`
161
+ }
162
+
163
+ if (platform === 'win32') {
164
+ const localAppData = process.env.LOCALAPPDATA || ''
165
+ return localAppData ? `${localAppData}\\Docker` : ''
166
+ }
167
+
168
+ return ''
169
+ }
170
+
171
+ /**
172
+ * 获取 Docker 状态(docker info 摘要)
173
+ * @returns {{ installed: boolean, running?: boolean, info?: string }}
174
+ */
175
+ export function getDockerStatus() {
176
+ if (!isDockerInstalled()) return { installed: false }
177
+ try {
178
+ const out = execSync('docker info --format "{{.ServerVersion}} {{.OperatingSystem}} {{.Containers}} {{.Images}}"', {
179
+ encoding: 'utf8',
180
+ stdio: ['pipe', 'pipe', 'pipe'],
181
+ })
182
+ const [serverVersion, operatingSystem, containers, images] = out.trim().split(' ')
183
+ return {
184
+ installed: true,
185
+ running: true,
186
+ serverVersion,
187
+ operatingSystem,
188
+ containers: parseInt(containers, 10) || 0,
189
+ images: parseInt(images, 10) || 0,
190
+ }
191
+ } catch {
192
+ return { installed: true, running: false, hint: 'Docker daemon not running' }
193
+ }
194
+ }
195
+
196
+ /**
197
+ * 启动 Docker daemon
198
+ * @returns {{ action: string, method: string }}
199
+ */
200
+ export function startDocker() {
201
+ const platform = process.platform
202
+
203
+ if (platform === 'linux') {
204
+ execSync('sudo systemctl start docker', { stdio: 'inherit', shell: true })
205
+ return { action: 'started', method: 'systemctl' }
206
+ }
207
+
208
+ if (platform === 'darwin') {
209
+ execSync('open -a Docker', { stdio: 'inherit', shell: true })
210
+ return { action: 'started', method: 'docker-desktop' }
211
+ }
212
+
213
+ if (platform === 'win32') {
214
+ execSync('wsl.exe -e sudo systemctl start docker', { stdio: 'inherit', shell: true })
215
+ return { action: 'started', method: 'wsl-systemctl' }
216
+ }
217
+
218
+ throw new Error(`Unsupported platform: ${platform}`)
219
+ }
220
+
221
+ /**
222
+ * 停止 Docker daemon
223
+ * @returns {{ action: string, method: string }}
224
+ */
225
+ export function stopDocker() {
226
+ const platform = process.platform
227
+
228
+ if (platform === 'linux') {
229
+ execSync('sudo systemctl stop docker', { stdio: 'inherit', shell: true })
230
+ return { action: 'stopped', method: 'systemctl' }
231
+ }
232
+
233
+ if (platform === 'darwin') {
234
+ execSync('osascript -e \'quit app "Docker"\'', { stdio: 'inherit', shell: true })
235
+ return { action: 'stopped', method: 'docker-desktop' }
236
+ }
237
+
238
+ if (platform === 'win32') {
239
+ execSync('wsl.exe -e sudo systemctl stop docker', { stdio: 'inherit', shell: true })
240
+ return { action: 'stopped', method: 'wsl-systemctl' }
241
+ }
242
+
243
+ throw new Error(`Unsupported platform: ${platform}`)
244
+ }
245
+
246
+ /**
247
+ * 重启 Docker daemon
248
+ * @returns {{ action: string, method: string }}
249
+ */
250
+ export function restartDocker() {
251
+ const platform = process.platform
252
+
253
+ if (platform === 'linux') {
254
+ execSync('sudo systemctl restart docker', { stdio: 'inherit', shell: true })
255
+ return { action: 'restarted', method: 'systemctl' }
256
+ }
257
+
258
+ if (platform === 'darwin') {
259
+ execSync('osascript -e \'quit app "Docker"\' && sleep 1 && open -a Docker', { stdio: 'inherit', shell: true })
260
+ return { action: 'restarted', method: 'docker-desktop' }
261
+ }
262
+
263
+ if (platform === 'win32') {
264
+ execSync('wsl.exe -e sudo systemctl restart docker', { stdio: 'inherit', shell: true })
265
+ return { action: 'restarted', method: 'wsl-systemctl' }
266
+ }
267
+
268
+ throw new Error(`Unsupported platform: ${platform}`)
269
+ }
270
+
271
+ /**
272
+ * 打开终端并确保 docker 可用
273
+ * - Linux: 新开终端
274
+ * - macOS: 打开 Terminal.app
275
+ * - Windows: 打开 PowerShell → WSL2(docker 装在 WSL 内)
276
+ * @returns {{ action: string, method: string }}
277
+ */
278
+ export function openDocker() {
279
+ const platform = process.platform
280
+
281
+ if (platform === 'linux') {
282
+ const shell = process.env.SHELL || 'bash'
283
+ spawnSync(shell, [], { stdio: 'inherit' })
284
+ return { action: 'opened', method: 'shell' }
285
+ }
286
+
287
+ if (platform === 'darwin') {
288
+ execSync('open -a Terminal', { stdio: 'inherit', shell: true })
289
+ return { action: 'opened', method: 'terminal-app' }
290
+ }
291
+
292
+ if (platform === 'win32') {
293
+ openWsl('')
294
+ return { action: 'opened', method: 'wsl-powershell' }
295
+ }
296
+
297
+ throw new Error(`Unsupported platform: ${platform}`)
298
+ }
@@ -44,12 +44,18 @@ export function isNvmInstalled() {
44
44
 
45
45
  /**
46
46
  * 安装 nvm(git clone + 写 shell profile)
47
+ * @param {string} [proxy] - 代理地址
47
48
  * @returns {{ action: string, profile: string, hint: string }}
48
49
  */
49
- export function installNvm() {
50
+ export function installNvm(proxy) {
50
51
  const mirror = getActiveRegistry('nvm')
51
52
  const nvmDir = getNvmDir()
52
- execSync(`git clone "${mirror}" "${nvmDir}"`, { stdio: 'inherit', shell: shell.resolveBash() })
53
+ const env = { ...process.env }
54
+ if (proxy) {
55
+ env.http_proxy = proxy
56
+ env.https_proxy = proxy
57
+ }
58
+ execSync(`git clone "${mirror}" "${nvmDir}"`, { stdio: 'inherit', shell: shell.resolveBash(), env })
53
59
  const lines = [
54
60
  `export NVM_DIR="${nvmDir}"`,
55
61
  `[ -s "\\$NVM_DIR/nvm.sh" ] && \\. "\\$NVM_DIR/nvm.sh"`,