@wwkit/opm 1.0.2 → 1.0.4
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/README.md +194 -11
- package/bin/index.js +23 -0
- package/docs/managers/git.md +103 -0
- package/docs/managers/proc.md +77 -0
- package/docs/managers/uv.md +9 -3
- package/docs/opm.html +154 -11
- package/docs/packages/opencode.md +46 -15
- package/docs/test/linux-environment-test-report.md +147 -0
- package/package.json +3 -2
- package/src/cli/groups/docker.js +10 -5
- package/src/cli/groups/homebrew.js +9 -5
- package/src/cli/groups/nvm.js +6 -2
- package/src/cli/groups/ping.js +461 -0
- package/src/cli/groups/proc.js +232 -0
- package/src/cli/groups/uv.js +9 -8
- package/src/cli/groups/version-manager.js +34 -8
- package/src/cli/index.js +10 -0
- package/src/config.json5 +84 -7
- package/src/harnesses/base.js +34 -0
- package/src/harnesses/opencode/index.js +56 -0
- package/src/index.js +6 -0
- package/src/managers/brew.js +6 -6
- package/src/managers/docker.js +10 -7
- package/src/managers/node.js +8 -2
- package/src/managers/proc.js +424 -0
- package/src/managers/python.js +7 -4
- package/src/presets.js +3 -0
- package/src/tools/ftp/index.js +151 -16
- package/src/tools/git/config.js +374 -0
- package/src/tools/git/index.js +378 -0
- package/src/tools/share/.htaccess +20 -0
- package/src/tools/share/client.js +172 -0
- package/src/tools/share/index.js +735 -0
- package/src/tools/share/server.js +430 -0
- package/src/tools/share/server.php +452 -0
- package/src/tools/webwork/index.js +558 -0
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* proc 命令组 — 进程与系统信息管理
|
|
3
|
+
*
|
|
4
|
+
* 用法:
|
|
5
|
+
* opm proc info 系统信息(OS、CPU、内存、磁盘、运行时间)
|
|
6
|
+
* opm proc list 列出前 N 个进程(默认按内存排序,默认 10)
|
|
7
|
+
* opm proc search 按进程名或端口号搜索
|
|
8
|
+
* opm proc port 列出监听端口及其进程
|
|
9
|
+
* opm proc kill 终止进程(按 ID / 端口 / 名称)
|
|
10
|
+
* opm proc tree 显示进程树
|
|
11
|
+
* opm proc detail 显示指定 PID 的详细信息
|
|
12
|
+
*
|
|
13
|
+
* 基于 systeminformation(随包安装)+ netstat 回退获取端口映射。
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { parseFlags } from '../helpers/args.js'
|
|
17
|
+
import { output } from '../../formatter.js'
|
|
18
|
+
import {
|
|
19
|
+
getSystemInfo,
|
|
20
|
+
listProcesses,
|
|
21
|
+
searchProcesses,
|
|
22
|
+
listPorts,
|
|
23
|
+
killByPid,
|
|
24
|
+
killByPort,
|
|
25
|
+
killByName,
|
|
26
|
+
getProcessTree,
|
|
27
|
+
getProcessDetail,
|
|
28
|
+
} from '../../managers/proc.js'
|
|
29
|
+
|
|
30
|
+
const ACTIONS = {
|
|
31
|
+
info: { desc: 'Show system information (OS, CPU, memory, disk, uptime)' },
|
|
32
|
+
list: { desc: 'List top N processes by memory or CPU (default: 10, by mem)' },
|
|
33
|
+
search: { desc: 'Search processes by name or port' },
|
|
34
|
+
port: { desc: 'List listening ports and their processes' },
|
|
35
|
+
kill: { desc: 'Kill process by ID, port, or name' },
|
|
36
|
+
tree: { desc: 'Show process tree (by PID or all top-level)' },
|
|
37
|
+
detail: { desc: 'Show detailed info for a specific PID' },
|
|
38
|
+
help: { desc: 'Show this help' },
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export class ProcGroup {
|
|
42
|
+
constructor() {
|
|
43
|
+
this.name = 'proc'
|
|
44
|
+
this.desc = 'Process & system info management (list/search/kill/port/tree)'
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async run(argv) {
|
|
48
|
+
const [action, ...rest] = argv
|
|
49
|
+
|
|
50
|
+
if (!action || action === '-h' || action === '--help' || action === 'help') {
|
|
51
|
+
this.printHelp()
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const parsed = parseFlags(rest)
|
|
56
|
+
|
|
57
|
+
switch (action) {
|
|
58
|
+
case 'info':
|
|
59
|
+
return this._info()
|
|
60
|
+
case 'list':
|
|
61
|
+
return this._list(parsed)
|
|
62
|
+
case 'search':
|
|
63
|
+
return this._search(parsed)
|
|
64
|
+
case 'port':
|
|
65
|
+
return this._port(parsed)
|
|
66
|
+
case 'kill':
|
|
67
|
+
return this._kill(parsed)
|
|
68
|
+
case 'tree':
|
|
69
|
+
return this._tree(parsed)
|
|
70
|
+
case 'detail':
|
|
71
|
+
return this._detail(parsed)
|
|
72
|
+
default:
|
|
73
|
+
console.error(`Unknown action: ${action}`)
|
|
74
|
+
this.printHelp()
|
|
75
|
+
process.exit(1)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async _info() {
|
|
80
|
+
const info = await getSystemInfo()
|
|
81
|
+
output(info)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async _list(parsed) {
|
|
85
|
+
const count = parseInt(parsed.flags.n || parsed.flags.count || '10', 10)
|
|
86
|
+
const n = (isNaN(count) || count < 1) ? 10 : count
|
|
87
|
+
const by = parsed.flags.b || parsed.flags.by || 'mem'
|
|
88
|
+
const sortBy = by === 'cpu' ? 'cpu' : 'mem'
|
|
89
|
+
const result = await listProcesses(n, sortBy)
|
|
90
|
+
output(result)
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async _search(parsed) {
|
|
94
|
+
const name = parsed.flags.n || parsed.flags.name || ''
|
|
95
|
+
const port = parsed.flags.p || parsed.flags.port || ''
|
|
96
|
+
const exact = parsed.flags.exact === 'true'
|
|
97
|
+
|
|
98
|
+
if (!name && !port) {
|
|
99
|
+
console.error('Usage: opm proc search -n <name> [-p <port>] [--exact]')
|
|
100
|
+
console.error('At least one of -n/--name or -p/--port is required')
|
|
101
|
+
process.exit(1)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const result = await searchProcesses({ name: name || undefined, port: port || undefined, exact })
|
|
105
|
+
output(result)
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async _port(parsed) {
|
|
109
|
+
const port = parsed.flags.p || parsed.flags.port || ''
|
|
110
|
+
const result = await listPorts(port || undefined)
|
|
111
|
+
output(result)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
async _kill(parsed) {
|
|
115
|
+
const id = parsed.flags.i || parsed.flags.id || ''
|
|
116
|
+
const port = parsed.flags.p || parsed.flags.port || ''
|
|
117
|
+
const name = parsed.flags.n || parsed.flags.name || ''
|
|
118
|
+
const force = parsed.flags.f === 'true' || parsed.flags.force === 'true'
|
|
119
|
+
const yes = parsed.flags.y === 'true' || parsed.flags.yes === 'true'
|
|
120
|
+
const signal = force ? 'SIGKILL' : 'SIGTERM'
|
|
121
|
+
|
|
122
|
+
if (!id && !port && !name) {
|
|
123
|
+
console.error('Usage: opm proc kill -i <pid> | -p <port> | -n <name> [-f] [-y]')
|
|
124
|
+
console.error('At least one of -i/--id, -p/--port, or -n/--name is required')
|
|
125
|
+
process.exit(1)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!yes) {
|
|
129
|
+
const targets = []
|
|
130
|
+
if (id) targets.push(`PID ${id}`)
|
|
131
|
+
if (port) targets.push(`port ${port}`)
|
|
132
|
+
if (name) targets.push(`name "${name}"`)
|
|
133
|
+
const msg = `Kill process by ${targets.join(', ')} with ${signal}? [y/N] `
|
|
134
|
+
const answer = await this._confirm(msg)
|
|
135
|
+
if (!answer) {
|
|
136
|
+
output({ action: 'kill', signal, cancelled: true })
|
|
137
|
+
return
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
if (id) {
|
|
142
|
+
const pid = parseInt(id, 10)
|
|
143
|
+
const result = await killByPid(pid, signal)
|
|
144
|
+
output({ action: 'kill', signal, killed: [result] })
|
|
145
|
+
return
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (port) {
|
|
149
|
+
const portNum = parseInt(port, 10)
|
|
150
|
+
const result = await killByPort(portNum, signal)
|
|
151
|
+
output({ action: 'kill', signal, ...result })
|
|
152
|
+
return
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (name) {
|
|
156
|
+
const result = await killByName(name, signal)
|
|
157
|
+
output({ action: 'kill', signal, ...result })
|
|
158
|
+
return
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async _tree(parsed) {
|
|
163
|
+
const pidArg = parsed.positional[0] || parsed.flags.pid || ''
|
|
164
|
+
const rootPid = pidArg ? parseInt(pidArg, 10) : null
|
|
165
|
+
const result = await getProcessTree(rootPid)
|
|
166
|
+
output(result)
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
async _detail(parsed) {
|
|
170
|
+
const pidArg = parsed.positional[0] || parsed.flags.pid || ''
|
|
171
|
+
if (!pidArg) {
|
|
172
|
+
console.error('Usage: opm proc detail <pid>')
|
|
173
|
+
process.exit(1)
|
|
174
|
+
}
|
|
175
|
+
const pid = parseInt(pidArg, 10)
|
|
176
|
+
const result = await getProcessDetail(pid)
|
|
177
|
+
output(result)
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
_confirm(msg) {
|
|
181
|
+
return new Promise((resolve) => {
|
|
182
|
+
process.stderr.write(msg)
|
|
183
|
+
process.stdin.setEncoding('utf8')
|
|
184
|
+
process.stdin.resume()
|
|
185
|
+
process.stdin.once('data', (data) => {
|
|
186
|
+
process.stdin.pause()
|
|
187
|
+
const answer = data.trim().toLowerCase()
|
|
188
|
+
resolve(answer === 'y' || answer === 'yes')
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
printHelp() {
|
|
194
|
+
const actionLines = Object.entries(ACTIONS)
|
|
195
|
+
.map(([sig, { desc }]) => ` ${sig.padEnd(12)} ${desc}`)
|
|
196
|
+
.join('\n')
|
|
197
|
+
|
|
198
|
+
console.log(`
|
|
199
|
+
Usage: opm proc <action> [options]
|
|
200
|
+
|
|
201
|
+
Actions:
|
|
202
|
+
${actionLines}
|
|
203
|
+
|
|
204
|
+
Options:
|
|
205
|
+
-n, --count <n> Number of processes (list, default: 10)
|
|
206
|
+
-n, --name <name> Process name pattern (search/kill, fuzzy by default)
|
|
207
|
+
-b, --by <mem|cpu> Sort by memory or CPU (list, default: mem)
|
|
208
|
+
-p, --port <port> Port number (search/kill/port)
|
|
209
|
+
-i, --id <pid> Process ID (kill)
|
|
210
|
+
--exact Exact match (search, default: fuzzy)
|
|
211
|
+
-f, --force Use SIGKILL instead of SIGTERM (kill)
|
|
212
|
+
-y, --yes Skip confirmation prompt (kill)
|
|
213
|
+
-h, --help Show this help
|
|
214
|
+
|
|
215
|
+
Examples:
|
|
216
|
+
opm proc info
|
|
217
|
+
opm proc list
|
|
218
|
+
opm proc list -n 20 -b cpu
|
|
219
|
+
opm proc search -n node
|
|
220
|
+
opm proc search -n node -p 3000
|
|
221
|
+
opm proc search -n node --exact
|
|
222
|
+
opm proc port
|
|
223
|
+
opm proc port -p 8080
|
|
224
|
+
opm proc kill -i 1234
|
|
225
|
+
opm proc kill -p 8080 -f -y
|
|
226
|
+
opm proc kill -n node -y
|
|
227
|
+
opm proc tree
|
|
228
|
+
opm proc tree 1234
|
|
229
|
+
opm proc detail 1234
|
|
230
|
+
`)
|
|
231
|
+
}
|
|
232
|
+
}
|
package/src/cli/groups/uv.js
CHANGED
|
@@ -2,9 +2,8 @@
|
|
|
2
2
|
* uv 命令组 — 管理 uv 自身
|
|
3
3
|
*
|
|
4
4
|
* uv 是 Astral 出品的快速 Python 包/版本管理器(Rust 实现)。
|
|
5
|
-
* 安装方式:curl 安装脚本(非 git clone
|
|
5
|
+
* 安装方式:curl 安装脚本(非 git clone);脚本 URL 由 uv.registry.active 提供;
|
|
6
6
|
* upgrade = uv self update;uninstall = rm 二进制 + 数据目录。
|
|
7
|
-
* uv 无镜像源,故无 registry action。
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
9
|
import { execSync, spawnSync } from 'node:child_process'
|
|
@@ -14,15 +13,11 @@ import os from 'node:os'
|
|
|
14
13
|
import { ToolManagerGroup } from './version-manager.js'
|
|
15
14
|
import { installUv, getUvBin } from '../../managers/python.js'
|
|
16
15
|
|
|
17
|
-
const UV_INSTALL_URL = 'https://astral.sh/uv/install.sh'
|
|
18
|
-
const UV_INSTALL_PS_URL = 'https://astral.sh/uv/install.ps1'
|
|
19
|
-
|
|
20
16
|
export class UvGroup extends ToolManagerGroup {
|
|
21
17
|
constructor() {
|
|
22
18
|
super({
|
|
23
19
|
name: 'uv',
|
|
24
20
|
desc: 'uv version manager (manage uv itself)',
|
|
25
|
-
hasRegistry: false,
|
|
26
21
|
isInstalled: () => {
|
|
27
22
|
try {
|
|
28
23
|
execSync(`${getUvBin()} --version`, { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] })
|
|
@@ -34,7 +29,7 @@ export class UvGroup extends ToolManagerGroup {
|
|
|
34
29
|
getPath: () => getUvBin(),
|
|
35
30
|
versionCmd: () => `${getUvBin()} --version`,
|
|
36
31
|
parseVersion: (out) => out.trim(),
|
|
37
|
-
install: () => installUv(),
|
|
32
|
+
install: (proxy) => installUv(proxy),
|
|
38
33
|
upgrade: () => {
|
|
39
34
|
execSync(`${getUvBin()} self update`, { stdio: 'inherit' })
|
|
40
35
|
return { action: 'upgraded' }
|
|
@@ -54,7 +49,10 @@ export class UvGroup extends ToolManagerGroup {
|
|
|
54
49
|
actions: [
|
|
55
50
|
['version', 'Show uv version'],
|
|
56
51
|
['installed', 'Check if uv is installed'],
|
|
57
|
-
['
|
|
52
|
+
['registry', 'Show active uv install script URL (from opm config)'],
|
|
53
|
+
['registry set <url|preset>', 'Set active uv install script URL (writes opm config)'],
|
|
54
|
+
['registry presets', 'List available mirror presets'],
|
|
55
|
+
['install', 'Install uv (curl installer)'],
|
|
58
56
|
['upgrade', 'Update uv (uv self update)'],
|
|
59
57
|
['uninstall', 'Remove uv (~/.local/bin/uv + ~/.local/share/uv)'],
|
|
60
58
|
['dir', 'Show uv install directory'],
|
|
@@ -63,6 +61,9 @@ export class UvGroup extends ToolManagerGroup {
|
|
|
63
61
|
examples: [
|
|
64
62
|
'opm uv version',
|
|
65
63
|
'opm uv installed',
|
|
64
|
+
'opm uv registry',
|
|
65
|
+
'opm uv registry set official',
|
|
66
|
+
'opm uv registry presets',
|
|
66
67
|
'opm uv install',
|
|
67
68
|
'opm uv upgrade',
|
|
68
69
|
'opm uv dir',
|
|
@@ -7,9 +7,10 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { execSync } from 'node:child_process'
|
|
10
|
+
import readline from 'node:readline'
|
|
10
11
|
import { parseFlags } from '../helpers/args.js'
|
|
11
12
|
import { Shell } from '@wwkit/shared'
|
|
12
|
-
import { getActiveRegistry, setActiveRegistry, getPresets } from '../../config.js'
|
|
13
|
+
import { getActiveRegistry, setActiveRegistry, getPresets, getActiveProxy } from '../../config.js'
|
|
13
14
|
import { output } from '../../formatter.js'
|
|
14
15
|
|
|
15
16
|
const shell = new Shell()
|
|
@@ -24,7 +25,7 @@ const shell = new Shell()
|
|
|
24
25
|
* @property {() => string} getPath - view 展示的安装路径
|
|
25
26
|
* @property {() => string} versionCmd - 返回查询版本的 shell 命令
|
|
26
27
|
* @property {(out: string) => string} [parseVersion] - 解析版本输出
|
|
27
|
-
* @property {() => Object} install - 安装并返回结果字段(失败时 throw)
|
|
28
|
+
* @property {(proxy?: string) => Object} install - 安装并返回结果字段(失败时 throw)
|
|
28
29
|
* @property {() => Object} upgrade - 升级并返回结果字段(失败时 throw)
|
|
29
30
|
* @property {() => Object} uninstall - 卸载并返回结果字段(失败时 throw)
|
|
30
31
|
* @property {string} usage - 帮助 usage 行
|
|
@@ -44,7 +45,8 @@ export class ToolManagerGroup {
|
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
async run(argv) {
|
|
47
|
-
const
|
|
48
|
+
const parsed = parseFlags(argv)
|
|
49
|
+
const [action, ...rest] = parsed.positional
|
|
48
50
|
|
|
49
51
|
if (!action || action === '-h' || action === '--help' || action === 'help') {
|
|
50
52
|
this.printHelp()
|
|
@@ -57,6 +59,13 @@ export class ToolManagerGroup {
|
|
|
57
59
|
process.exit(1)
|
|
58
60
|
}
|
|
59
61
|
|
|
62
|
+
let proxy = ''
|
|
63
|
+
if ('p' in parsed.flags || 'proxy' in parsed.flags) {
|
|
64
|
+
let raw = parsed.flags.proxy || parsed.flags.p || ''
|
|
65
|
+
if (raw === 'true') raw = ''
|
|
66
|
+
proxy = raw || getActiveProxy()
|
|
67
|
+
}
|
|
68
|
+
|
|
60
69
|
switch (action) {
|
|
61
70
|
case 'version':
|
|
62
71
|
return this._version()
|
|
@@ -65,11 +74,11 @@ export class ToolManagerGroup {
|
|
|
65
74
|
case 'registry':
|
|
66
75
|
return this._registry(parseFlags(rest))
|
|
67
76
|
case 'install':
|
|
68
|
-
return this._install()
|
|
77
|
+
return this._install(proxy)
|
|
69
78
|
case 'upgrade':
|
|
70
79
|
return this._upgrade()
|
|
71
80
|
case 'uninstall':
|
|
72
|
-
return this._uninstall()
|
|
81
|
+
return this._uninstall(parsed)
|
|
73
82
|
case 'dir':
|
|
74
83
|
return this._dir()
|
|
75
84
|
default:
|
|
@@ -110,13 +119,15 @@ export class ToolManagerGroup {
|
|
|
110
119
|
}
|
|
111
120
|
}
|
|
112
121
|
|
|
113
|
-
_install() {
|
|
122
|
+
_install(proxy) {
|
|
114
123
|
if (this.cfg.isInstalled()) {
|
|
115
124
|
output({ name: this.name, installed: true, hint: `${this.name} already installed, run \`opm ${this.name} upgrade\` to update` })
|
|
116
125
|
return
|
|
117
126
|
}
|
|
118
127
|
try {
|
|
119
|
-
|
|
128
|
+
const installFn = this.cfg.install
|
|
129
|
+
const result = installFn.length >= 1 ? installFn(proxy) : installFn()
|
|
130
|
+
output({ name: this.name, installed: true, ...(result || {}) })
|
|
120
131
|
} catch (err) {
|
|
121
132
|
console.error(err.message || `Failed to install ${this.name}`)
|
|
122
133
|
process.exit(1)
|
|
@@ -136,11 +147,26 @@ export class ToolManagerGroup {
|
|
|
136
147
|
}
|
|
137
148
|
}
|
|
138
149
|
|
|
139
|
-
_uninstall() {
|
|
150
|
+
async _uninstall(parsed) {
|
|
140
151
|
if (!this.cfg.isInstalled()) {
|
|
141
152
|
output({ name: this.name, installed: false, hint: `${this.name} not installed` })
|
|
142
153
|
return
|
|
143
154
|
}
|
|
155
|
+
const force = parsed.flags.y || parsed.flags.yes || parsed.flags.force
|
|
156
|
+
if (force !== 'true' && force !== true) {
|
|
157
|
+
console.error(`WARNING: This will remove ${this.name} and all its data.`)
|
|
158
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stderr })
|
|
159
|
+
const answer = await new Promise((resolve) => {
|
|
160
|
+
rl.question(`Are you sure? [y/N] `, (ans) => {
|
|
161
|
+
rl.close()
|
|
162
|
+
resolve(ans.trim().toLowerCase())
|
|
163
|
+
})
|
|
164
|
+
})
|
|
165
|
+
if (answer !== 'y' && answer !== 'yes') {
|
|
166
|
+
output({ name: this.name, installed: true, hint: 'Uninstall cancelled' })
|
|
167
|
+
return
|
|
168
|
+
}
|
|
169
|
+
}
|
|
144
170
|
try {
|
|
145
171
|
output({ name: this.name, ...(this.cfg.uninstall() || {}) })
|
|
146
172
|
} catch (err) {
|
package/src/cli/index.js
CHANGED
|
@@ -18,7 +18,12 @@ import { HomebrewGroup } from './groups/homebrew.js'
|
|
|
18
18
|
import { WslGroup } from './groups/wsl.js'
|
|
19
19
|
import { DockerGroup } from './groups/docker.js'
|
|
20
20
|
import { EnsureGroup } from './groups/ensure.js'
|
|
21
|
+
import { PingGroup } from './groups/ping.js'
|
|
22
|
+
import { ProcGroup } from './groups/proc.js'
|
|
21
23
|
import { FtpGroup } from '../tools/ftp/index.js'
|
|
24
|
+
import { GitGroup } from '../tools/git/index.js'
|
|
25
|
+
import { ShareGroup } from '../tools/share/index.js'
|
|
26
|
+
import { WebworkGroup } from '../tools/webwork/index.js'
|
|
22
27
|
import { HarnessGroup } from '../harnesses/base.js'
|
|
23
28
|
import { opencodeHarness } from '../harnesses/opencode/index.js'
|
|
24
29
|
import { CftGroup } from '../binaries/cft/index.js'
|
|
@@ -39,6 +44,9 @@ const GROUPS = {
|
|
|
39
44
|
wsl: new WslGroup(),
|
|
40
45
|
docker: new DockerGroup(),
|
|
41
46
|
ftp: new FtpGroup(),
|
|
47
|
+
git: new GitGroup(),
|
|
48
|
+
share: new ShareGroup(),
|
|
49
|
+
webwork: new WebworkGroup(),
|
|
42
50
|
composer: new PackageGroup('composer'),
|
|
43
51
|
bun: new PackageGroup('bun'),
|
|
44
52
|
winget: new PackageGroup('winget'),
|
|
@@ -52,6 +60,8 @@ const GROUPS = {
|
|
|
52
60
|
opencode: new HarnessGroup(opencodeHarness),
|
|
53
61
|
cft: new CftGroup(),
|
|
54
62
|
ensure: new EnsureGroup(),
|
|
63
|
+
ping: new PingGroup(),
|
|
64
|
+
proc: new ProcGroup(),
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
class CLI {
|
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-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
149
|
+
huawei: "https://mirrors.huaweicloud.com/python-build-standalone/",
|
|
150
|
+
active: "ghproxy",
|
|
145
151
|
},
|
|
146
152
|
},
|
|
147
153
|
|
|
@@ -152,15 +158,47 @@
|
|
|
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
|
},
|
|
158
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
|
+
|
|
159
197
|
// FTP 连接配置
|
|
160
198
|
// password 用 {env:XXX} 引用环境变量,不硬编码
|
|
161
199
|
// pasv: false — 阿里云虚拟主机必须用主动模式
|
|
162
200
|
ftp: {
|
|
163
|
-
host: "wh-nxb3pizm7zyfvpv4113.
|
|
201
|
+
host: "wh-nxb3pizm7zyfvpv4113.my3v.com",
|
|
164
202
|
port: 21,
|
|
165
203
|
user: "{env:FTP_USER}",
|
|
166
204
|
password: "{env:FTP_PASSWORD}",
|
|
@@ -170,4 +208,43 @@
|
|
|
170
208
|
max_retries: 2,
|
|
171
209
|
pasv: false,
|
|
172
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
|
+
},
|
|
232
|
+
|
|
233
|
+
// share 文本分享服务
|
|
234
|
+
// server.active 指向预设 key(official/local)或完整 URL
|
|
235
|
+
// serve 为本地服务参数(opm share serve 使用)
|
|
236
|
+
// password 为分享默认密码(opm share upload/get/open 使用,-p/--password 可覆盖)
|
|
237
|
+
share: {
|
|
238
|
+
password: "0000",
|
|
239
|
+
server: {
|
|
240
|
+
official: "http://deepbluenet.com",
|
|
241
|
+
local: "http://127.0.0.1:8787",
|
|
242
|
+
active: "local",
|
|
243
|
+
},
|
|
244
|
+
serve: {
|
|
245
|
+
host: "0.0.0.0",
|
|
246
|
+
port: 8787,
|
|
247
|
+
dataDir: "~/.config/opm/share/data",
|
|
248
|
+
},
|
|
249
|
+
},
|
|
173
250
|
}
|
package/src/harnesses/base.js
CHANGED
|
@@ -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
|
}
|