@wwkit/opm 1.0.8 → 1.0.10
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 +30 -20
- package/docs/managers/apt.md +2 -3
- package/docs/managers/brew.md +2 -6
- package/docs/managers/bun.md +2 -4
- package/docs/managers/composer.md +2 -6
- package/docs/managers/dnf.md +2 -3
- package/docs/managers/npm.md +2 -6
- package/docs/managers/nvm.md +2 -0
- package/docs/managers/phpenv.md +2 -0
- package/docs/managers/pip.md +2 -6
- package/docs/managers/uv.md +2 -0
- package/docs/opm.html +13 -23
- package/package.json +3 -3
- package/src/cli/groups/ensure.js +25 -4
- package/src/cli/groups/nvm.js +5 -1
- package/src/cli/groups/overlay.js +169 -0
- package/src/cli/groups/package.js +42 -18
- package/src/cli/groups/phpenv.js +5 -1
- package/src/cli/groups/uv.js +5 -1
- package/src/cli/groups/version-manager.js +27 -0
- package/src/cli/index.js +4 -0
- package/src/config.json5 +1 -0
- package/src/harnesses/base.js +88 -20
- package/src/harnesses/harness/index.js +132 -0
- package/src/harnesses/opencode/index.js +1 -1
- package/src/index.js +2 -0
- package/src/managers/apt.js +3 -12
- package/src/managers/base.js +17 -2
- package/src/managers/brew.js +3 -5
- package/src/managers/bun.js +0 -18
- package/src/managers/composer.js +65 -9
- package/src/managers/dnf.js +3 -12
- package/src/managers/node.js +17 -8
- package/src/managers/npm.js +51 -45
- package/src/managers/php.js +17 -8
- package/src/managers/pip.js +24 -18
- package/src/managers/python.js +18 -8
- package/src/managers/winget.js +3 -13
- package/src/tools/webwork/index.js +132 -463
|
@@ -50,14 +50,13 @@ const MANAGER_DESC = {
|
|
|
50
50
|
}
|
|
51
51
|
|
|
52
52
|
const ACTIONS = {
|
|
53
|
-
version: { desc: 'Show manager version' },
|
|
53
|
+
version: { desc: 'Show manager or package version' },
|
|
54
54
|
installed: { desc: 'Check if the manager itself is installed' },
|
|
55
55
|
registry: { desc: 'Query or set registry/mirror address' },
|
|
56
56
|
search: { desc: 'Check if a package is available in the source' },
|
|
57
|
-
view: { desc: 'Check
|
|
57
|
+
view: { desc: 'Check package in global and project scopes' },
|
|
58
58
|
info: { desc: 'Show package details' },
|
|
59
59
|
versions: { desc: 'List all available versions of a package' },
|
|
60
|
-
list: { desc: 'List all installed packages' },
|
|
61
60
|
outdated: { desc: 'List outdated packages' },
|
|
62
61
|
install: { desc: 'Install a package' },
|
|
63
62
|
uninstall: { desc: 'Uninstall a package' },
|
|
@@ -105,8 +104,18 @@ export class PackageGroup {
|
|
|
105
104
|
|
|
106
105
|
switch (action) {
|
|
107
106
|
case 'version': {
|
|
108
|
-
const
|
|
109
|
-
|
|
107
|
+
const name = positional[0]
|
|
108
|
+
if (name) {
|
|
109
|
+
const result = await manager.scopedInstalled(name, { proxy: opts.proxy })
|
|
110
|
+
output({
|
|
111
|
+
name: result.name,
|
|
112
|
+
global: { installed: result.global.installed, ...(result.global.version ? { version: result.global.version } : {}) },
|
|
113
|
+
project: { installed: result.project.installed, ...(result.project.version ? { version: result.project.version } : {}) },
|
|
114
|
+
})
|
|
115
|
+
} else {
|
|
116
|
+
const result = await manager.getVersion()
|
|
117
|
+
output((result.version || '').replace(/^v/, ''))
|
|
118
|
+
}
|
|
110
119
|
break
|
|
111
120
|
}
|
|
112
121
|
|
|
@@ -151,10 +160,10 @@ export class PackageGroup {
|
|
|
151
160
|
case 'view': {
|
|
152
161
|
const name = positional[0]
|
|
153
162
|
if (!name) {
|
|
154
|
-
console.error(`Usage: opm ${this.name} view <name
|
|
163
|
+
console.error(`Usage: opm ${this.name} view <name>`)
|
|
155
164
|
process.exit(1)
|
|
156
165
|
}
|
|
157
|
-
const result = await manager.
|
|
166
|
+
const result = await manager.scopedInstalled(name, { proxy: opts.proxy })
|
|
158
167
|
output(result)
|
|
159
168
|
break
|
|
160
169
|
}
|
|
@@ -181,12 +190,6 @@ export class PackageGroup {
|
|
|
181
190
|
break
|
|
182
191
|
}
|
|
183
192
|
|
|
184
|
-
case 'list': {
|
|
185
|
-
const result = await manager.listInstalled({ global: !!opts.global })
|
|
186
|
-
output(result)
|
|
187
|
-
break
|
|
188
|
-
}
|
|
189
|
-
|
|
190
193
|
case 'outdated': {
|
|
191
194
|
const result = await manager.listOutdated({ global: !!opts.global, proxy: opts.proxy })
|
|
192
195
|
output(result)
|
|
@@ -254,16 +257,15 @@ export class PackageGroup {
|
|
|
254
257
|
Usage: opm ${this.name} <action> [args] [options]
|
|
255
258
|
|
|
256
259
|
Actions:
|
|
257
|
-
version
|
|
260
|
+
version [name] Show manager version, or package version in global+project scopes
|
|
258
261
|
installed Check if ${this.name} itself is installed
|
|
259
262
|
registry Show active registry (from opm config)
|
|
260
263
|
registry set <url|preset> Set active registry (writes opm config)
|
|
261
264
|
registry presets List available mirror presets
|
|
262
265
|
search <name> [version] Check package availability in source
|
|
263
|
-
view <name>
|
|
266
|
+
view <name> Show package in global and project scopes
|
|
264
267
|
info <name> Show package details
|
|
265
268
|
versions <name> List all available versions
|
|
266
|
-
list [-g] List all installed packages
|
|
267
269
|
outdated [-g] List outdated packages
|
|
268
270
|
install <name> [version] Install a package
|
|
269
271
|
uninstall <name> Uninstall a package
|
|
@@ -285,7 +287,6 @@ Examples:
|
|
|
285
287
|
opm ${this.name} view <name>
|
|
286
288
|
opm ${this.name} info <name>
|
|
287
289
|
opm ${this.name} versions <name>
|
|
288
|
-
opm ${this.name} list
|
|
289
290
|
opm ${this.name} outdated
|
|
290
291
|
opm ${this.name} install <name>
|
|
291
292
|
opm ${this.name} uninstall <name>
|
|
@@ -363,7 +364,7 @@ export class RuntimeGroup extends PackageGroup {
|
|
|
363
364
|
return
|
|
364
365
|
}
|
|
365
366
|
|
|
366
|
-
const runtimeActions = new Set(['use', 'current', 'default', 'installed', 'dir', 'install', 'upgrade', 'search', 'versions'])
|
|
367
|
+
const runtimeActions = new Set(['use', 'current', 'default', 'installed', 'dir', 'install', 'upgrade', 'search', 'versions', 'version', 'view', 'list'])
|
|
367
368
|
if (runtimeActions.has(action)) {
|
|
368
369
|
const manager = MANAGERS[this.name]()
|
|
369
370
|
const parsed = parseFlags(rest)
|
|
@@ -465,6 +466,29 @@ export class RuntimeGroup extends PackageGroup {
|
|
|
465
466
|
break
|
|
466
467
|
}
|
|
467
468
|
|
|
469
|
+
case 'version': {
|
|
470
|
+
const result = await manager.getVersion()
|
|
471
|
+
output((result.version || '').replace(/^v/, ''))
|
|
472
|
+
break
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
case 'view': {
|
|
476
|
+
const version = positional[0]
|
|
477
|
+
if (!version) {
|
|
478
|
+
console.error(`Usage: opm ${this.name} view <version>`)
|
|
479
|
+
process.exit(1)
|
|
480
|
+
}
|
|
481
|
+
const result = await manager.isInstalled(version, { global: false })
|
|
482
|
+
output(result)
|
|
483
|
+
break
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
case 'list': {
|
|
487
|
+
const result = await manager.listInstalled({ global: false })
|
|
488
|
+
output(result)
|
|
489
|
+
break
|
|
490
|
+
}
|
|
491
|
+
|
|
468
492
|
default:
|
|
469
493
|
return super._dispatch(manager, action, parsed, opts)
|
|
470
494
|
}
|
package/src/cli/groups/phpenv.js
CHANGED
|
@@ -14,7 +14,7 @@ import os from 'node:os'
|
|
|
14
14
|
import { ToolManagerGroup } from './version-manager.js'
|
|
15
15
|
import { Shell } from '@wwkit/shared'
|
|
16
16
|
import { getActiveRegistry } from '../../config.js'
|
|
17
|
-
import { installPhpenv } from '../../managers/php.js'
|
|
17
|
+
import { installPhpenv, parsePhpenvInstalled } from '../../managers/php.js'
|
|
18
18
|
|
|
19
19
|
const shell = new Shell()
|
|
20
20
|
|
|
@@ -47,6 +47,8 @@ export class PhpenvGroup extends ToolManagerGroup {
|
|
|
47
47
|
const m = out.trim().match(/phpenv\s+([\d.]+)/)
|
|
48
48
|
return m ? m[1] : out.trim()
|
|
49
49
|
},
|
|
50
|
+
listCmd: () => `${getPhpenvBin()} versions`,
|
|
51
|
+
parseList: parsePhpenvInstalled,
|
|
50
52
|
install: () => installPhpenv(),
|
|
51
53
|
upgrade: () => {
|
|
52
54
|
execSync(`cd "${getPhpenvRoot()}" && git remote set-url origin "${getActiveRegistry('phpenv')}" && git pull --tags`, {
|
|
@@ -74,6 +76,7 @@ export class PhpenvGroup extends ToolManagerGroup {
|
|
|
74
76
|
['upgrade', 'Update phpenv and php-build plugin (git pull)'],
|
|
75
77
|
['uninstall', 'Remove phpenv (~/.phpenv)'],
|
|
76
78
|
['dir', 'Show phpenv install directory'],
|
|
79
|
+
['list', 'List installed PHP versions (phpenv versions)'],
|
|
77
80
|
['help', 'Show this help'],
|
|
78
81
|
],
|
|
79
82
|
examples: [
|
|
@@ -83,6 +86,7 @@ export class PhpenvGroup extends ToolManagerGroup {
|
|
|
83
86
|
'opm phpenv install',
|
|
84
87
|
'opm phpenv upgrade',
|
|
85
88
|
'opm phpenv dir',
|
|
89
|
+
'opm phpenv list',
|
|
86
90
|
],
|
|
87
91
|
})
|
|
88
92
|
}
|
package/src/cli/groups/uv.js
CHANGED
|
@@ -11,7 +11,7 @@ import fs from 'node:fs'
|
|
|
11
11
|
import path from 'node:path'
|
|
12
12
|
import os from 'node:os'
|
|
13
13
|
import { ToolManagerGroup } from './version-manager.js'
|
|
14
|
-
import { installUv, getUvBin } from '../../managers/python.js'
|
|
14
|
+
import { installUv, getUvBin, parseUvInstalled } from '../../managers/python.js'
|
|
15
15
|
|
|
16
16
|
export class UvGroup extends ToolManagerGroup {
|
|
17
17
|
constructor() {
|
|
@@ -29,6 +29,8 @@ export class UvGroup extends ToolManagerGroup {
|
|
|
29
29
|
getPath: () => getUvBin(),
|
|
30
30
|
versionCmd: () => `${getUvBin()} --version`,
|
|
31
31
|
parseVersion: (out) => out.trim(),
|
|
32
|
+
listCmd: () => `${getUvBin()} python list --only-installed`,
|
|
33
|
+
parseList: parseUvInstalled,
|
|
32
34
|
install: (proxy) => installUv(proxy),
|
|
33
35
|
upgrade: () => {
|
|
34
36
|
execSync(`${getUvBin()} self update`, { stdio: 'inherit' })
|
|
@@ -56,6 +58,7 @@ export class UvGroup extends ToolManagerGroup {
|
|
|
56
58
|
['upgrade', 'Update uv (uv self update)'],
|
|
57
59
|
['uninstall', 'Remove uv (~/.local/bin/uv + ~/.local/share/uv)'],
|
|
58
60
|
['dir', 'Show uv install directory'],
|
|
61
|
+
['list', 'List installed Python versions (uv python list --only-installed)'],
|
|
59
62
|
['help', 'Show this help'],
|
|
60
63
|
],
|
|
61
64
|
examples: [
|
|
@@ -67,6 +70,7 @@ export class UvGroup extends ToolManagerGroup {
|
|
|
67
70
|
'opm uv install',
|
|
68
71
|
'opm uv upgrade',
|
|
69
72
|
'opm uv dir',
|
|
73
|
+
'opm uv list',
|
|
70
74
|
'opm uv uninstall',
|
|
71
75
|
],
|
|
72
76
|
})
|
|
@@ -25,6 +25,8 @@ const shell = new Shell()
|
|
|
25
25
|
* @property {() => string} getPath - view 展示的安装路径
|
|
26
26
|
* @property {() => string} versionCmd - 返回查询版本的 shell 命令
|
|
27
27
|
* @property {(out: string) => string} [parseVersion] - 解析版本输出
|
|
28
|
+
* @property {() => string} [listCmd] - 返回列出已安装版本的 shell 命令(缺省则 list 输出空列表)
|
|
29
|
+
* @property {(out: string) => { name: string, version: string, current: boolean }[]} [parseList] - 解析 list 输出
|
|
28
30
|
* @property {(proxy?: string) => Object} install - 安装并返回结果字段(失败时 throw)
|
|
29
31
|
* @property {() => Object} upgrade - 升级并返回结果字段(失败时 throw)
|
|
30
32
|
* @property {() => Object} uninstall - 卸载并返回结果字段(失败时 throw)
|
|
@@ -81,6 +83,8 @@ export class ToolManagerGroup {
|
|
|
81
83
|
return this._uninstall(parsed)
|
|
82
84
|
case 'dir':
|
|
83
85
|
return this._dir()
|
|
86
|
+
case 'list':
|
|
87
|
+
return this._list()
|
|
84
88
|
default:
|
|
85
89
|
console.error(`Unknown action: ${action}`)
|
|
86
90
|
this.printHelp()
|
|
@@ -193,6 +197,29 @@ const out = execSync(this.cfg.versionCmd(), { encoding: 'utf8', shell: shell.she
|
|
|
193
197
|
output(result)
|
|
194
198
|
}
|
|
195
199
|
|
|
200
|
+
/**
|
|
201
|
+
* 列出工具已安装的版本
|
|
202
|
+
* @returns {void}
|
|
203
|
+
* @private
|
|
204
|
+
*/
|
|
205
|
+
_list() {
|
|
206
|
+
if (!this.cfg.isInstalled()) {
|
|
207
|
+
output({ name: this.name, installed: false, hint: `Run \`opm ${this.name} install\` first` })
|
|
208
|
+
return
|
|
209
|
+
}
|
|
210
|
+
if (!this.cfg.listCmd) {
|
|
211
|
+
output({ name: this.name, count: 0, versions: [] })
|
|
212
|
+
return
|
|
213
|
+
}
|
|
214
|
+
try {
|
|
215
|
+
const out = execSync(this.cfg.listCmd(), { encoding: 'utf8', shell: shell.shellForExec(this.cfg.needsBash) })
|
|
216
|
+
const versions = this.cfg.parseList ? this.cfg.parseList(out) : []
|
|
217
|
+
output({ name: this.name, count: versions.length, versions })
|
|
218
|
+
} catch {
|
|
219
|
+
output({ name: this.name, count: 0, versions: [] })
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
196
223
|
printHelp() {
|
|
197
224
|
const actionLines = this.cfg.actions.map(([sig, desc]) => ` ${sig.padEnd(34)} ${desc}`).join('\n')
|
|
198
225
|
const exampleLines = this.cfg.examples.map((e) => ` ${e}`).join('\n')
|
package/src/cli/index.js
CHANGED
|
@@ -20,12 +20,14 @@ import { DockerGroup } from './groups/docker.js'
|
|
|
20
20
|
import { EnsureGroup } from './groups/ensure.js'
|
|
21
21
|
import { PingGroup } from './groups/ping.js'
|
|
22
22
|
import { ProcGroup } from './groups/proc.js'
|
|
23
|
+
import { OverlayGroup } from './groups/overlay.js'
|
|
23
24
|
import { FtpGroup } from '../tools/ftp/index.js'
|
|
24
25
|
import { GitGroup } from '../tools/git/index.js'
|
|
25
26
|
import { ShareGroup } from '../tools/share/index.js'
|
|
26
27
|
import { WebworkGroup } from '../tools/webwork/index.js'
|
|
27
28
|
import { HarnessGroup } from '../harnesses/base.js'
|
|
28
29
|
import { opencodeHarness } from '../harnesses/opencode/index.js'
|
|
30
|
+
import { harnessCfg } from '../harnesses/harness/index.js'
|
|
29
31
|
import { CftGroup } from '../binaries/cft/index.js'
|
|
30
32
|
import { generateDocHtml } from './doc.js'
|
|
31
33
|
|
|
@@ -58,10 +60,12 @@ const GROUPS = {
|
|
|
58
60
|
uv: new UvGroup(),
|
|
59
61
|
config: new ConfigGroup(),
|
|
60
62
|
opencode: new HarnessGroup(opencodeHarness),
|
|
63
|
+
harness: new HarnessGroup(harnessCfg),
|
|
61
64
|
cft: new CftGroup(),
|
|
62
65
|
ensure: new EnsureGroup(),
|
|
63
66
|
ping: new PingGroup(),
|
|
64
67
|
proc: new ProcGroup(),
|
|
68
|
+
overlay: new OverlayGroup(),
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
class CLI {
|
package/src/config.json5
CHANGED
package/src/harnesses/base.js
CHANGED
|
@@ -2,7 +2,12 @@
|
|
|
2
2
|
* AI harness 统一命令组基类
|
|
3
3
|
*
|
|
4
4
|
* 各 harness(opencode / claude / …)共享同一套命令:
|
|
5
|
-
* version / installed / install / uninstall / upgrade / config / clear / dir
|
|
5
|
+
* version / installed / install / uninstall / upgrade / config / clear / dir / env
|
|
6
|
+
* 内容型 harness(harness/@wwkit/harness 插件)额外提供:
|
|
7
|
+
* list(列出 skills/agents/commands) / status(安装 + env 健康检查)
|
|
8
|
+
*
|
|
9
|
+
* 支持的动作按 cfg 能力动态裁剪(_supportedActions):只有 cfg 提供了对应的
|
|
10
|
+
* 实现(list/status/clear/getConfigFile/getEnvVars)时才出现在帮助与路由中。
|
|
6
11
|
*
|
|
7
12
|
* install/uninstall/upgrade 委托 NpmManager(global),自动使用 opm 配置的
|
|
8
13
|
* npm registry 镜像与 proxy,而非裸 npm。
|
|
@@ -25,7 +30,9 @@ const ACTIONS = {
|
|
|
25
30
|
upgrade: { desc: 'Upgrade to latest via npm -g (uses opm npm registry + proxy)' },
|
|
26
31
|
config: { desc: 'Show config file content' },
|
|
27
32
|
clear: { desc: 'Clear sessions / data' },
|
|
28
|
-
dir: { desc: 'Show data / db / log / config paths' },
|
|
33
|
+
dir: { desc: 'Show install / data / db / log / config paths' },
|
|
34
|
+
list: { desc: 'List installed content (skills/agents/commands)' },
|
|
35
|
+
status: { desc: 'Show install status + environment health' },
|
|
29
36
|
env: { desc: 'Show harness environment variables' },
|
|
30
37
|
help: { desc: 'Show this help' },
|
|
31
38
|
}
|
|
@@ -35,12 +42,15 @@ const ACTIONS = {
|
|
|
35
42
|
* @property {string} name - harness 名(命令名)
|
|
36
43
|
* @property {string} desc - 描述
|
|
37
44
|
* @property {string} npmPackage - npm 全局包名
|
|
38
|
-
* @property {() => string|null} getConfigFile - 配置文件路径
|
|
39
|
-
* @property {() => string} getDataDir - 数据目录
|
|
40
|
-
* @property {() => string} getDbPath - DB 路径
|
|
41
|
-
* @property {() => string} getLogPath - 日志路径
|
|
42
|
-
* @property {(argv: string[]) => Promise<void>} clear - 清理实现
|
|
45
|
+
* @property {() => string|null} [getConfigFile] - 配置文件路径
|
|
46
|
+
* @property {() => string} [getDataDir] - 数据目录
|
|
47
|
+
* @property {() => string} [getDbPath] - DB 路径
|
|
48
|
+
* @property {() => string} [getLogPath] - 日志路径
|
|
49
|
+
* @property {(argv: string[]) => Promise<void>} [clear] - 清理实现
|
|
43
50
|
* @property {string[][]} [clearOptions] - clear 子选项帮助 [flag, desc]
|
|
51
|
+
* @property {() => Promise<Object>} [list] - 列出包内容(skills/agents/commands)
|
|
52
|
+
* @property {() => Promise<Object>} [status] - 安装/环境健康检查
|
|
53
|
+
* @property {() => Promise<Object>} [dirInfo] - 自定义 dir 输出(覆盖默认 npm install 路径)
|
|
44
54
|
*/
|
|
45
55
|
|
|
46
56
|
export class HarnessGroup {
|
|
@@ -62,9 +72,19 @@ export class HarnessGroup {
|
|
|
62
72
|
}
|
|
63
73
|
|
|
64
74
|
if (!action || !ACTIONS[action]) {
|
|
75
|
+
if (!this.cfg.open) {
|
|
76
|
+
this.printHelp()
|
|
77
|
+
return
|
|
78
|
+
}
|
|
65
79
|
return this._open(argv)
|
|
66
80
|
}
|
|
67
81
|
|
|
82
|
+
if (!this._supportedActions().includes(action)) {
|
|
83
|
+
console.error(`Action "${action}" is not supported for ${this.cfg.name}`)
|
|
84
|
+
this.printHelp()
|
|
85
|
+
process.exit(1)
|
|
86
|
+
}
|
|
87
|
+
|
|
68
88
|
switch (action) {
|
|
69
89
|
case 'version':
|
|
70
90
|
return this._version()
|
|
@@ -82,6 +102,10 @@ export class HarnessGroup {
|
|
|
82
102
|
return this._clear(rest)
|
|
83
103
|
case 'dir':
|
|
84
104
|
return this._dir()
|
|
105
|
+
case 'list':
|
|
106
|
+
return this._list()
|
|
107
|
+
case 'status':
|
|
108
|
+
return this._status()
|
|
85
109
|
case 'env':
|
|
86
110
|
return this._env()
|
|
87
111
|
default:
|
|
@@ -91,6 +115,23 @@ export class HarnessGroup {
|
|
|
91
115
|
}
|
|
92
116
|
}
|
|
93
117
|
|
|
118
|
+
/**
|
|
119
|
+
* 本 harness 支持的动作列表(按 cfg 能力动态裁剪)
|
|
120
|
+
* @returns {string[]}
|
|
121
|
+
* @private
|
|
122
|
+
*/
|
|
123
|
+
_supportedActions() {
|
|
124
|
+
return Object.keys(ACTIONS).filter((sig) => {
|
|
125
|
+
if (sig === 'help') return true
|
|
126
|
+
if (sig === 'list') return Boolean(this.cfg.list)
|
|
127
|
+
if (sig === 'status') return Boolean(this.cfg.status)
|
|
128
|
+
if (sig === 'clear') return Boolean(this.cfg.clear)
|
|
129
|
+
if (sig === 'config') return Boolean(this.cfg.getConfigFile)
|
|
130
|
+
if (sig === 'env') return Boolean(this.cfg.getEnvVars)
|
|
131
|
+
return true
|
|
132
|
+
})
|
|
133
|
+
}
|
|
134
|
+
|
|
94
135
|
/**
|
|
95
136
|
* 解析 proxy(-p/--proxy 优先,否则回退配置 active proxy)
|
|
96
137
|
* @param {string[]} argv
|
|
@@ -152,6 +193,22 @@ export class HarnessGroup {
|
|
|
152
193
|
output({ file, content })
|
|
153
194
|
}
|
|
154
195
|
|
|
196
|
+
async _list() {
|
|
197
|
+
if (!this.cfg.list) {
|
|
198
|
+
output({ name: this.cfg.name, error: 'list not supported for this harness' })
|
|
199
|
+
return
|
|
200
|
+
}
|
|
201
|
+
output(await this.cfg.list())
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async _status() {
|
|
205
|
+
if (!this.cfg.status) {
|
|
206
|
+
output({ name: this.cfg.name, error: 'status not supported for this harness' })
|
|
207
|
+
return
|
|
208
|
+
}
|
|
209
|
+
output(await this.cfg.status())
|
|
210
|
+
}
|
|
211
|
+
|
|
155
212
|
async _clear(rest) {
|
|
156
213
|
const parsed = parseFlags(rest)
|
|
157
214
|
if (parsed.flags.help === 'true') {
|
|
@@ -162,6 +219,10 @@ export class HarnessGroup {
|
|
|
162
219
|
}
|
|
163
220
|
|
|
164
221
|
async _dir() {
|
|
222
|
+
if (this.cfg.dirInfo) {
|
|
223
|
+
output(await this.cfg.dirInfo())
|
|
224
|
+
return
|
|
225
|
+
}
|
|
165
226
|
const npm = new NpmManager()
|
|
166
227
|
const npmResult = await npm.isInstalled(this.cfg.npmPackage, { global: true })
|
|
167
228
|
const configFile = this.cfg.getConfigFile()
|
|
@@ -218,18 +279,34 @@ export class HarnessGroup {
|
|
|
218
279
|
}
|
|
219
280
|
|
|
220
281
|
printHelp() {
|
|
221
|
-
const actionLines =
|
|
222
|
-
.map((
|
|
282
|
+
const actionLines = this._supportedActions()
|
|
283
|
+
.map((sig) => ` ${sig.padEnd(12)} ${ACTIONS[sig].desc}`)
|
|
223
284
|
.join('\n')
|
|
224
285
|
|
|
225
286
|
let clearSection = ''
|
|
226
|
-
if (this.cfg.clearOptions && this.cfg.clearOptions.length > 0) {
|
|
287
|
+
if (this._supportedActions().includes('clear') && this.cfg.clearOptions && this.cfg.clearOptions.length > 0) {
|
|
227
288
|
const optLines = this.cfg.clearOptions
|
|
228
289
|
.map(([flag, desc]) => ` ${flag.padEnd(20)} ${desc}`)
|
|
229
290
|
.join('\n')
|
|
230
291
|
clearSection = `\nClear options:\n${optLines}\n`
|
|
231
292
|
}
|
|
232
293
|
|
|
294
|
+
const supported = this._supportedActions()
|
|
295
|
+
const examples = [
|
|
296
|
+
' opm ' + this.cfg.name + ' version',
|
|
297
|
+
' opm ' + this.cfg.name + ' install',
|
|
298
|
+
' opm ' + this.cfg.name + ' upgrade',
|
|
299
|
+
...(supported.includes('list') ? [` opm ${this.cfg.name} list`] : []),
|
|
300
|
+
...(supported.includes('status') ? [` opm ${this.cfg.name} status`] : []),
|
|
301
|
+
...(supported.includes('env') ? [` opm ${this.cfg.name} env`] : []),
|
|
302
|
+
...(supported.includes('dir') ? [` opm ${this.cfg.name} dir`] : []),
|
|
303
|
+
]
|
|
304
|
+
if (this.cfg.open) {
|
|
305
|
+
examples.push(` opm ${this.cfg.name} Launch TUI in current directory`)
|
|
306
|
+
examples.push(` opm ${this.cfg.name} -d /path/to/project`)
|
|
307
|
+
examples.push(` opm ${this.cfg.name} -c config --agent work Pass through args to opencode`)
|
|
308
|
+
}
|
|
309
|
+
|
|
233
310
|
console.log(`
|
|
234
311
|
Usage: opm ${this.cfg.name} <action> [options]
|
|
235
312
|
|
|
@@ -242,16 +319,7 @@ Options:
|
|
|
242
319
|
-h, --help Show this help
|
|
243
320
|
|
|
244
321
|
Examples:
|
|
245
|
-
|
|
246
|
-
opm ${this.cfg.name} install
|
|
247
|
-
opm ${this.cfg.name} upgrade
|
|
248
|
-
opm ${this.cfg.name} config
|
|
249
|
-
opm ${this.cfg.name} clear
|
|
250
|
-
opm ${this.cfg.name} dir
|
|
251
|
-
opm ${this.cfg.name} env
|
|
252
|
-
opm ${this.cfg.name} Launch TUI in current directory
|
|
253
|
-
opm ${this.cfg.name} -d /path/to/project
|
|
254
|
-
opm ${this.cfg.name} -c config --agent work Pass through args to opencode
|
|
322
|
+
${examples.join('\n')}
|
|
255
323
|
`)
|
|
256
324
|
}
|
|
257
325
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @wwkit/harness harness 配置
|
|
3
|
+
*
|
|
4
|
+
* @wwkit/harness 是 opencode 插件包(skills/agents/commands/scripts),
|
|
5
|
+
* 通过 npm 全局安装;postinstall 设置 OPENCODE_CONFIG_DIR 指向包根目录,
|
|
6
|
+
* 使 opencode 自动加载其 skills/agents/commands。
|
|
7
|
+
*
|
|
8
|
+
* 与 opencode(AI agent CLI)不同:无独立配置文件、无 DB/日志/会话,
|
|
9
|
+
* 因此不支持 config/clear;而提供 list(列出包内容)与 status(健康检查)。
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from 'node:fs'
|
|
13
|
+
import path from 'node:path'
|
|
14
|
+
import { NpmManager } from '../../managers/npm.js'
|
|
15
|
+
|
|
16
|
+
const CONTENT_DIRS = ['skills', 'agents', 'commands']
|
|
17
|
+
const ENV_VARS = ['OPENCODE_CONFIG_DIR', 'OPENCODE_EXPERIMENTAL_LSP_TOOL']
|
|
18
|
+
const NAME = 'harness'
|
|
19
|
+
const NPM_PACKAGE = '@wwkit/harness'
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 已安装的 @wwkit/harness 全局包信息
|
|
23
|
+
* @returns {Promise<{ installed: boolean, version?: string, path?: string }>}
|
|
24
|
+
* @private
|
|
25
|
+
*/
|
|
26
|
+
async function getInstalled() {
|
|
27
|
+
const npm = new NpmManager()
|
|
28
|
+
return npm.isInstalled(NPM_PACKAGE, { global: true })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 读取 env 变量当前值
|
|
33
|
+
* @returns {Record<string, string>}
|
|
34
|
+
*/
|
|
35
|
+
export function getEnvVars() {
|
|
36
|
+
const result = {}
|
|
37
|
+
for (const key of ENV_VARS) {
|
|
38
|
+
result[key] = process.env[key] || ''
|
|
39
|
+
}
|
|
40
|
+
return result
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* 列出包根目录下 skills/agents/commands 内容
|
|
45
|
+
* @param {string} root - 已安装包根目录
|
|
46
|
+
* @returns {Record<string, string[]>}
|
|
47
|
+
*/
|
|
48
|
+
export function readContentDirs(root) {
|
|
49
|
+
const result = {}
|
|
50
|
+
for (const dir of CONTENT_DIRS) {
|
|
51
|
+
const full = path.join(root, dir)
|
|
52
|
+
result[dir] = fs.existsSync(full) ? fs.readdirSync(full).sort() : []
|
|
53
|
+
}
|
|
54
|
+
return result
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* list:列出安装包提供的 skills/agents/commands
|
|
59
|
+
* @returns {Promise<Object>}
|
|
60
|
+
*/
|
|
61
|
+
async function list() {
|
|
62
|
+
const installed = await getInstalled()
|
|
63
|
+
if (!installed.installed || !installed.path) {
|
|
64
|
+
return { name: NAME, installed: false, hint: `Run \`opm ${NAME} install\` to install ${NPM_PACKAGE}` }
|
|
65
|
+
}
|
|
66
|
+
const content = readContentDirs(installed.path)
|
|
67
|
+
const counts = {}
|
|
68
|
+
for (const dir of CONTENT_DIRS) counts[dir] = content[dir].length
|
|
69
|
+
return { name: NAME, installed: true, version: installed.version, install: installed.path, counts, ...content }
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* status:健康检查(安装 + env + 内容目录完整性)
|
|
74
|
+
* @returns {Promise<Object>}
|
|
75
|
+
*/
|
|
76
|
+
async function status() {
|
|
77
|
+
const installed = await getInstalled()
|
|
78
|
+
const env = getEnvVars()
|
|
79
|
+
if (!installed.installed || !installed.path) {
|
|
80
|
+
return {
|
|
81
|
+
name: NAME,
|
|
82
|
+
installed: false,
|
|
83
|
+
ok: false,
|
|
84
|
+
env,
|
|
85
|
+
hint: `Run \`opm ${NAME} install\` to install ${NPM_PACKAGE}`,
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
const content = readContentDirs(installed.path)
|
|
89
|
+
const contentOk = {}
|
|
90
|
+
for (const dir of CONTENT_DIRS) contentOk[dir] = content[dir].length > 0
|
|
91
|
+
const envDirOk = env.OPENCODE_CONFIG_DIR === installed.path
|
|
92
|
+
const envLspOk = env.OPENCODE_EXPERIMENTAL_LSP_TOOL === 'true'
|
|
93
|
+
const ok = envDirOk && envLspOk && Object.values(contentOk).every(Boolean)
|
|
94
|
+
return {
|
|
95
|
+
name: NAME,
|
|
96
|
+
installed: true,
|
|
97
|
+
version: installed.version,
|
|
98
|
+
ok,
|
|
99
|
+
envDirOk,
|
|
100
|
+
envLspOk,
|
|
101
|
+
contentDirs: contentOk,
|
|
102
|
+
env,
|
|
103
|
+
...(!ok ? { hint: 'Environment or content is not fully ready, see fields above' } : {}),
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* dir:安装路径 + skills/agents/commands 目录
|
|
109
|
+
* @returns {Promise<Object>}
|
|
110
|
+
*/
|
|
111
|
+
async function dirInfo() {
|
|
112
|
+
const installed = await getInstalled()
|
|
113
|
+
if (!installed.installed || !installed.path) {
|
|
114
|
+
return { name: NAME, install: '', hint: `Run \`opm ${NAME} install\` to install ${NPM_PACKAGE}` }
|
|
115
|
+
}
|
|
116
|
+
const paths = { install: installed.path }
|
|
117
|
+
for (const dir of CONTENT_DIRS) {
|
|
118
|
+
paths[dir] = path.join(installed.path, dir)
|
|
119
|
+
}
|
|
120
|
+
return { name: NAME, ...paths }
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export const harnessCfg = {
|
|
124
|
+
name: NAME,
|
|
125
|
+
desc: '@wwkit/harness opencode plugin (skills/agents/commands)',
|
|
126
|
+
npmPackage: NPM_PACKAGE,
|
|
127
|
+
envVars: ENV_VARS,
|
|
128
|
+
getEnvVars,
|
|
129
|
+
list,
|
|
130
|
+
status,
|
|
131
|
+
dirInfo,
|
|
132
|
+
}
|
|
@@ -21,7 +21,7 @@ const shell = new Shell()
|
|
|
21
21
|
/**
|
|
22
22
|
* opencode 专属环境变量名
|
|
23
23
|
*/
|
|
24
|
-
const ENV_VARS = ['OPENCODE_CONFIG_DIR', 'OPENCODE_CONFIG_CONTENT']
|
|
24
|
+
const ENV_VARS = ['OPENCODE_CONFIG_DIR', 'OPENCODE_CONFIG_CONTENT', 'OPENCODE_EXPERIMENTAL_BACKGROUND_SUBAGENTS']
|
|
25
25
|
|
|
26
26
|
/**
|
|
27
27
|
* 查找 opencode 配置文件路径(优先 .jsonc,其次 .json)
|
package/src/index.js
CHANGED
|
@@ -11,6 +11,7 @@ export { PackageGroup, RuntimeGroup } from './cli/groups/package.js'
|
|
|
11
11
|
export { ConfigGroup } from './cli/groups/config.js'
|
|
12
12
|
export { HarnessGroup } from './harnesses/base.js'
|
|
13
13
|
export { opencodeHarness } from './harnesses/opencode/index.js'
|
|
14
|
+
export { harnessCfg } from './harnesses/harness/index.js'
|
|
14
15
|
export { BinaryGroup } from './binaries/base.js'
|
|
15
16
|
export { CftGroup } from './binaries/cft/index.js'
|
|
16
17
|
export { NvmGroup } from './cli/groups/nvm.js'
|
|
@@ -21,6 +22,7 @@ export { WslGroup } from './cli/groups/wsl.js'
|
|
|
21
22
|
export { DockerGroup } from './cli/groups/docker.js'
|
|
22
23
|
export { PingGroup } from './cli/groups/ping.js'
|
|
23
24
|
export { ProcGroup } from './cli/groups/proc.js'
|
|
25
|
+
export { OverlayGroup } from './cli/groups/overlay.js'
|
|
24
26
|
export { FtpGroup } from './tools/ftp/index.js'
|
|
25
27
|
export { GitGroup } from './tools/git/index.js'
|
|
26
28
|
export { ShareGroup } from './tools/share/index.js'
|
package/src/managers/apt.js
CHANGED
|
@@ -249,6 +249,9 @@ export class AptManager extends PackageManager {
|
|
|
249
249
|
}
|
|
250
250
|
|
|
251
251
|
async isInstalled(name, opts = {}) {
|
|
252
|
+
if (opts.global === false) {
|
|
253
|
+
return { name, installed: false, hint: 'no project scope (system-level manager)' }
|
|
254
|
+
}
|
|
252
255
|
const args = ['query', '-W', '-f=${Status} ${Version}', name]
|
|
253
256
|
const out = this._exec(args, { allowNonZero: true })
|
|
254
257
|
const m = out.trim().match(/^install ok installed\s+(\S+)$/)
|
|
@@ -323,18 +326,6 @@ export class AptManager extends PackageManager {
|
|
|
323
326
|
return { name, versions, registry }
|
|
324
327
|
}
|
|
325
328
|
|
|
326
|
-
async listInstalled(opts = {}) {
|
|
327
|
-
const out = this._exec(['list', '--installed'], { allowNonZero: true })
|
|
328
|
-
const items = []
|
|
329
|
-
for (const line of this._parseLines(out)) {
|
|
330
|
-
const m = line.match(/^(\S+?)\/(?:\S+,)?now\s+(\S+)\s+(\S+)\s+\[/)
|
|
331
|
-
if (m) {
|
|
332
|
-
items.push({ name: m[1], version: m[2] })
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
|
-
return items
|
|
336
|
-
}
|
|
337
|
-
|
|
338
329
|
async listOutdated(opts = {}) {
|
|
339
330
|
const out = this._exec(['list', '--upgradable', ...this._proxyArgs(opts.proxy)], { allowNonZero: true })
|
|
340
331
|
const items = []
|