@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.
@@ -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
+ }
@@ -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
- ['install', 'Install uv (curl installer from astral.sh)'],
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 [action, ...rest] = argv
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
- output({ name: this.name, installed: true, ...(this.cfg.install() || {}) })
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-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,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.my3w.com",
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
  }
@@ -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
  }