@wwkit/opm 1.0.9 → 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 +21 -4
- package/docs/managers/nvm.md +2 -0
- package/docs/managers/phpenv.md +2 -0
- package/docs/managers/uv.md +2 -0
- package/docs/opm.html +6 -3
- 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/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 +2 -0
- package/src/harnesses/base.js +88 -20
- package/src/harnesses/harness/index.js +132 -0
- package/src/index.js +1 -0
- package/src/managers/node.js +17 -8
- package/src/managers/php.js +17 -8
- package/src/managers/python.js +18 -8
- package/src/tools/webwork/index.js +132 -463
|
@@ -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
|
+
}
|
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'
|
package/src/managers/node.js
CHANGED
|
@@ -68,6 +68,22 @@ export function installNvm(proxy) {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
+
/**
|
|
72
|
+
* 解析 nvm ls 输出为已安装版本列表
|
|
73
|
+
* @param {string} out
|
|
74
|
+
* @returns {{ name: string, version: string, current: boolean }[]}
|
|
75
|
+
*/
|
|
76
|
+
export function parseNvmInstalled(out) {
|
|
77
|
+
const items = []
|
|
78
|
+
for (const line of out.split('\n')) {
|
|
79
|
+
const m = line.match(/^(->)?\s*v(\d+\.\d+\.\d+)\b/)
|
|
80
|
+
if (m) {
|
|
81
|
+
items.push({ name: `v${m[2]}`, version: m[2], current: !!m[1] })
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return items
|
|
85
|
+
}
|
|
86
|
+
|
|
71
87
|
export class NodeManager extends RuntimeManager {
|
|
72
88
|
constructor() {
|
|
73
89
|
super({
|
|
@@ -127,14 +143,7 @@ export class NodeManager extends RuntimeManager {
|
|
|
127
143
|
* @private
|
|
128
144
|
*/
|
|
129
145
|
_parseInstalled(out) {
|
|
130
|
-
|
|
131
|
-
for (const line of out.split('\n')) {
|
|
132
|
-
const m = line.match(/^(->)?\s*v(\d+\.\d+\.\d+)\b/)
|
|
133
|
-
if (m) {
|
|
134
|
-
items.push({ name: `v${m[2]}`, version: m[2], current: !!m[1] })
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
return items
|
|
146
|
+
return parseNvmInstalled(out)
|
|
138
147
|
}
|
|
139
148
|
|
|
140
149
|
/**
|
package/src/managers/php.js
CHANGED
|
@@ -70,6 +70,22 @@ export function installPhpenv() {
|
|
|
70
70
|
}
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
+
/**
|
|
74
|
+
* 解析 phpenv versions 输出为已安装版本列表
|
|
75
|
+
* @param {string} out
|
|
76
|
+
* @returns {{ name: string, version: string, current: boolean }[]}
|
|
77
|
+
*/
|
|
78
|
+
export function parsePhpenvInstalled(out) {
|
|
79
|
+
const items = []
|
|
80
|
+
for (const line of out.split('\n')) {
|
|
81
|
+
const m = line.match(/^(\*)?\s+(\d+\.\d+\.\d+)\b/)
|
|
82
|
+
if (m) {
|
|
83
|
+
items.push({ name: m[2], version: m[2], current: !!m[1] })
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return items
|
|
87
|
+
}
|
|
88
|
+
|
|
73
89
|
export class PhpManager extends RuntimeManager {
|
|
74
90
|
constructor() {
|
|
75
91
|
super({
|
|
@@ -134,14 +150,7 @@ export class PhpManager extends RuntimeManager {
|
|
|
134
150
|
* @private
|
|
135
151
|
*/
|
|
136
152
|
_parseInstalled(out) {
|
|
137
|
-
|
|
138
|
-
for (const line of out.split('\n')) {
|
|
139
|
-
const m = line.match(/^(\*)?\s+(\d+\.\d+\.\d+)\b/)
|
|
140
|
-
if (m) {
|
|
141
|
-
items.push({ name: m[2], version: m[2], current: !!m[1] })
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return items
|
|
153
|
+
return parsePhpenvInstalled(out)
|
|
145
154
|
}
|
|
146
155
|
|
|
147
156
|
/**
|
package/src/managers/python.js
CHANGED
|
@@ -67,6 +67,23 @@ export function installUv(proxy) {
|
|
|
67
67
|
return { action: 'installed', hint: 'Restart shell or ensure ~/.local/bin is in PATH' }
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
/**
|
|
71
|
+
* 解析 uv python list --only-installed 输出为已安装版本列表
|
|
72
|
+
* 输出格式: cpython-3.12.4-linux-x86_64-gnu /path/to/python
|
|
73
|
+
* @param {string} out
|
|
74
|
+
* @returns {{ name: string, version: string, current: boolean }[]}
|
|
75
|
+
*/
|
|
76
|
+
export function parseUvInstalled(out) {
|
|
77
|
+
const items = []
|
|
78
|
+
for (const line of out.split('\n')) {
|
|
79
|
+
const m = line.match(/cpython-(\d+\.\d+\.\d+)/)
|
|
80
|
+
if (m) {
|
|
81
|
+
items.push({ name: m[1], version: m[1], current: false })
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return items
|
|
85
|
+
}
|
|
86
|
+
|
|
70
87
|
export class PythonManager extends RuntimeManager {
|
|
71
88
|
constructor() {
|
|
72
89
|
super({
|
|
@@ -129,14 +146,7 @@ export class PythonManager extends RuntimeManager {
|
|
|
129
146
|
* @private
|
|
130
147
|
*/
|
|
131
148
|
_parseInstalled(out) {
|
|
132
|
-
|
|
133
|
-
for (const line of out.split('\n')) {
|
|
134
|
-
const m = line.match(/cpython-(\d+\.\d+\.\d+)/)
|
|
135
|
-
if (m) {
|
|
136
|
-
items.push({ name: m[1], version: m[1], current: false })
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
return items
|
|
149
|
+
return parseUvInstalled(out)
|
|
140
150
|
}
|
|
141
151
|
|
|
142
152
|
/**
|