@esign-ai/veriagent-cli 1.0.0
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 +115 -0
- package/index.js +475 -0
- package/package.json +21 -0
package/README.md
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# @esign-ai/veriagent-cli
|
|
2
|
+
|
|
3
|
+
VeriAgent CLI 是统一安装入口,用于在当前终端为 OpenClaw 或 Hermes 安装并初始化 VeriAgent 插件。
|
|
4
|
+
|
|
5
|
+
## 功能概览
|
|
6
|
+
|
|
7
|
+
| 能力 | 说明 |
|
|
8
|
+
|---|---|
|
|
9
|
+
| 交互安装 | 让用户选择安装到 OpenClaw 或 Hermes |
|
|
10
|
+
| 指定目标安装 | 通过命令参数直接指定 OpenClaw 或 Hermes |
|
|
11
|
+
| 依赖检查 | 检查目标 CLI 是否可用,Hermes 会校验最低版本 |
|
|
12
|
+
| 插件安装 | 调用目标宿主的插件安装命令 |
|
|
13
|
+
| 初始化 | 安装后自动进入 VeriAgent 初始化流程 |
|
|
14
|
+
|
|
15
|
+
## 环境要求
|
|
16
|
+
|
|
17
|
+
| 项 | 要求 |
|
|
18
|
+
|---|---|
|
|
19
|
+
| Node.js | 18 或以上 |
|
|
20
|
+
| OpenClaw | 安装 OpenClaw 时需要当前终端可执行 `openclaw` |
|
|
21
|
+
| Hermes | 安装 Hermes 时需要当前终端可执行 `hermes` |
|
|
22
|
+
| 浏览器 | VeriAgent 初始化流程需要打开授权页面 |
|
|
23
|
+
| 网络 | 可访问目标插件源和 VeriAgent 服务 |
|
|
24
|
+
|
|
25
|
+
## 使用方式
|
|
26
|
+
|
|
27
|
+
交互选择:
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
npx -y @esign-ai/veriagent-cli install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
指定安装到 OpenClaw:
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
npx -y @esign-ai/veriagent-cli install openclaw
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
指定安装到 Hermes:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
npx -y @esign-ai/veriagent-cli install hermes
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## OpenClaw 安装流程
|
|
46
|
+
|
|
47
|
+
```text
|
|
48
|
+
veriagent-cli install openclaw
|
|
49
|
+
-> 检查 openclaw 命令
|
|
50
|
+
-> 安装 @esign-ai/openclaw-veriagent
|
|
51
|
+
-> 重启 OpenClaw Gateway
|
|
52
|
+
-> 执行 openclaw veriagent init
|
|
53
|
+
-> 打开授权页面并完成 VeriAgent 初始化
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
等价执行:
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
openclaw plugins install clawhub:@esign-ai/openclaw-veriagent
|
|
60
|
+
openclaw gateway restart
|
|
61
|
+
openclaw veriagent init
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
如果插件已存在,CLI 会继续执行 Gateway 重启和 VeriAgent 初始化。
|
|
65
|
+
|
|
66
|
+
## Hermes 安装流程
|
|
67
|
+
|
|
68
|
+
```text
|
|
69
|
+
veriagent-cli install hermes
|
|
70
|
+
-> 检查 hermes 命令
|
|
71
|
+
-> 校验 Hermes 版本
|
|
72
|
+
-> 安装 hermes-veriagent 插件
|
|
73
|
+
-> 启用 hermes-veriagent 插件
|
|
74
|
+
-> 执行 hermes veriagent init
|
|
75
|
+
-> 打开授权页面并完成 VeriAgent 初始化
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
等价执行:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
hermes --version
|
|
82
|
+
hermes plugins install https://github.com/esign-ai/hermes-veriagent.git
|
|
83
|
+
hermes plugins enable hermes-veriagent
|
|
84
|
+
hermes veriagent init
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Hermes 版本需要满足 CLI 内置的最低版本要求。
|
|
88
|
+
|
|
89
|
+
## 初始化结果
|
|
90
|
+
|
|
91
|
+
安装完成后,可在目标宿主中查看 VeriAgent 状态。
|
|
92
|
+
|
|
93
|
+
OpenClaw:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
openclaw veriagent status
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Hermes:
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
hermes veriagent status
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
状态中应包含本地 `agentId` 和 `certificateFile`。
|
|
106
|
+
|
|
107
|
+
## 注意事项
|
|
108
|
+
|
|
109
|
+
| 事项 | 说明 |
|
|
110
|
+
|---|---|
|
|
111
|
+
| 当前终端执行 | CLI 会在当前终端顺序执行目标宿主命令 |
|
|
112
|
+
| 重复安装 | 已安装插件时会继续执行后续初始化步骤 |
|
|
113
|
+
| 浏览器授权 | 初始化过程中需要用户在浏览器完成授权 |
|
|
114
|
+
| Hermes 版本 | 版本不足时需要先升级 Hermes |
|
|
115
|
+
| OpenClaw Gateway | OpenClaw 插件安装后会重启 Gateway |
|
package/index.js
ADDED
|
@@ -0,0 +1,475 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const os = require('os')
|
|
5
|
+
const path = require('path')
|
|
6
|
+
const { spawn } = require('child_process')
|
|
7
|
+
const readline = require('readline')
|
|
8
|
+
|
|
9
|
+
const OPENCLAW_PACKAGE_NAME = '@esign-ai/openclaw-veriagent'
|
|
10
|
+
const HERMES_PLUGIN_SOURCE = 'https://github.com/esign-ai/hermes-veriagent.git'
|
|
11
|
+
const OPENCLAW_COMMAND = 'openclaw'
|
|
12
|
+
const HERMES_COMMAND = 'hermes'
|
|
13
|
+
const MIN_HERMES_VERSION = '0.14.0'
|
|
14
|
+
const OPENCLAW_GATEWAY_WAIT_MS = 1000
|
|
15
|
+
const HERMES_GATEWAY_WAIT_MS = 1000
|
|
16
|
+
const LOG_PREFIX = ''
|
|
17
|
+
const LOG_PREFIX_COLOR = [104, 194, 206]
|
|
18
|
+
|
|
19
|
+
main().catch(error => {
|
|
20
|
+
const message = String(error && error.message ? error.message : error)
|
|
21
|
+
console.error(`\n${formatPrefixedMessage(`安装未完成:${message}`)}`)
|
|
22
|
+
process.exitCode = 1
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
const cliArgs = parseCliArgs(process.argv.slice(2))
|
|
27
|
+
const arg = cliArgs.targetArg
|
|
28
|
+
const requestedVersion = cliArgs.requestedVersion
|
|
29
|
+
|
|
30
|
+
if (cliArgs.showHelp) {
|
|
31
|
+
printHelp()
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const target = await resolveTarget(arg, requestedVersion)
|
|
36
|
+
console.log(formatPrefixedMessage(`已选择:${target.label}`))
|
|
37
|
+
console.log(formatPrefixedMessage('将执行以下命令:'))
|
|
38
|
+
target.steps.filter(step => !step.silent).forEach((step, index) => {
|
|
39
|
+
console.log(` ${index + 1}. ${describeStep(step)}`)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
for (const step of target.steps) {
|
|
43
|
+
await runStep(step)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
console.log(`\n${formatPrefixedMessage(`${target.label} 安装流程已执行完成。`)}`)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function printHelp() {
|
|
50
|
+
console.log('@esign-ai/veriagent-cli')
|
|
51
|
+
console.log('')
|
|
52
|
+
console.log('用法:')
|
|
53
|
+
console.log(' npx -y @esign-ai/veriagent-cli install')
|
|
54
|
+
console.log(' npx -y @esign-ai/veriagent-cli install openclaw')
|
|
55
|
+
console.log(' npx -y @esign-ai/veriagent-cli install hermes')
|
|
56
|
+
console.log('')
|
|
57
|
+
console.log('当前支持的目标:')
|
|
58
|
+
getTargets().forEach(target => {
|
|
59
|
+
console.log(` - ${target.id}: ${target.description}`)
|
|
60
|
+
})
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function parseCliArgs(argv) {
|
|
64
|
+
const args = argv.map(item => String(item || '').trim()).filter(Boolean)
|
|
65
|
+
const firstArg = String(args[0] || '').toLowerCase()
|
|
66
|
+
|
|
67
|
+
if (!firstArg) {
|
|
68
|
+
return {
|
|
69
|
+
showHelp: false,
|
|
70
|
+
targetArg: '',
|
|
71
|
+
requestedVersion: '',
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (firstArg === '--help' || firstArg === '-h' || firstArg === 'help') {
|
|
76
|
+
return {
|
|
77
|
+
showHelp: true,
|
|
78
|
+
targetArg: '',
|
|
79
|
+
requestedVersion: '',
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (firstArg === 'install') {
|
|
84
|
+
return {
|
|
85
|
+
showHelp: false,
|
|
86
|
+
targetArg: String(args[1] || '').toLowerCase(),
|
|
87
|
+
requestedVersion: normalizeVersionArg(args[2]),
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
showHelp: false,
|
|
93
|
+
targetArg: firstArg,
|
|
94
|
+
requestedVersion: normalizeVersionArg(args[1]),
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
async function resolveTarget(arg, requestedVersion) {
|
|
99
|
+
const targets = getTargets(requestedVersion)
|
|
100
|
+
|
|
101
|
+
if (arg) {
|
|
102
|
+
const target = targets.find(item => item.id === arg || (item.aliases || []).includes(arg))
|
|
103
|
+
if (!target) {
|
|
104
|
+
const availableTargets = targets
|
|
105
|
+
.flatMap(item => [item.id].concat(item.aliases || []))
|
|
106
|
+
.join(', ')
|
|
107
|
+
throw new Error(`不支持的目标:${arg}。可用值:${availableTargets}`)
|
|
108
|
+
}
|
|
109
|
+
return target
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
113
|
+
return targets[0]
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return promptTargetSelection(targets)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function promptTargetSelection(targets) {
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
console.log('支持的 Agent 安装目标:')
|
|
122
|
+
targets.forEach((target, index) => {
|
|
123
|
+
console.log(` ${index + 1}. ${target.label} - ${target.description}`)
|
|
124
|
+
})
|
|
125
|
+
|
|
126
|
+
const rl = readline.createInterface({
|
|
127
|
+
input: process.stdin,
|
|
128
|
+
output: process.stdout,
|
|
129
|
+
})
|
|
130
|
+
|
|
131
|
+
rl.question('请选择要安装的目标,默认 1:', answer => {
|
|
132
|
+
rl.close()
|
|
133
|
+
const normalized = String(answer || '').trim()
|
|
134
|
+
if (!normalized) {
|
|
135
|
+
resolve(targets[0])
|
|
136
|
+
return
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const index = Number(normalized)
|
|
140
|
+
if (!Number.isInteger(index) || index < 1 || index > targets.length) {
|
|
141
|
+
reject(new Error(`无效选项:${normalized}`))
|
|
142
|
+
return
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
resolve(targets[index - 1])
|
|
146
|
+
})
|
|
147
|
+
})
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async function runStep(step) {
|
|
151
|
+
if (step.type === 'delay') {
|
|
152
|
+
if (!step.silent) {
|
|
153
|
+
console.log(`\n${formatPrefixedMessage(step.label)}`)
|
|
154
|
+
console.log(formatPrefixedMessage(step.message))
|
|
155
|
+
}
|
|
156
|
+
await sleep(step.durationMs)
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (step.type === 'hermes-version-check') {
|
|
161
|
+
console.log(`\n${formatPrefixedMessage(step.label)}`)
|
|
162
|
+
await checkHermesVersion(step.minimumVersion)
|
|
163
|
+
return
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log(`\n${formatPrefixedMessage(step.label)}`)
|
|
167
|
+
console.log(formatPrefixedMessage(formatCommand(step.command, step.args)))
|
|
168
|
+
|
|
169
|
+
const commandAvailable = await isCommandAvailable(step.command)
|
|
170
|
+
if (!commandAvailable) {
|
|
171
|
+
throw new Error(`找不到命令 \`${step.command}\`,请先确认本机已安装,并且在当前终端环境中可直接执行。`)
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
await new Promise((resolve, reject) => {
|
|
175
|
+
const spawnConfig = createSpawnConfig(step.command, step.args)
|
|
176
|
+
const shouldCaptureOutput = Array.isArray(step.continueOnOutput) && step.continueOnOutput.length > 0
|
|
177
|
+
const child = spawn(spawnConfig.command, spawnConfig.args, {
|
|
178
|
+
...spawnConfig.options,
|
|
179
|
+
stdio: shouldCaptureOutput ? ['inherit', 'pipe', 'pipe'] : 'inherit',
|
|
180
|
+
})
|
|
181
|
+
let output = ''
|
|
182
|
+
|
|
183
|
+
if (shouldCaptureOutput) {
|
|
184
|
+
child.stdout.on('data', chunk => {
|
|
185
|
+
output += String(chunk)
|
|
186
|
+
process.stdout.write(chunk)
|
|
187
|
+
})
|
|
188
|
+
child.stderr.on('data', chunk => {
|
|
189
|
+
output += String(chunk)
|
|
190
|
+
process.stderr.write(chunk)
|
|
191
|
+
})
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
child.on('error', error => {
|
|
195
|
+
if (error && error.code === 'ENOENT') {
|
|
196
|
+
reject(new Error(`找不到命令 \`${step.command}\`,请先确认本机已安装,并且在当前终端环境中可直接执行。`))
|
|
197
|
+
return
|
|
198
|
+
}
|
|
199
|
+
reject(error)
|
|
200
|
+
})
|
|
201
|
+
|
|
202
|
+
child.on('exit', code => {
|
|
203
|
+
if (code === 0) {
|
|
204
|
+
resolve()
|
|
205
|
+
return
|
|
206
|
+
}
|
|
207
|
+
if (code === 127) {
|
|
208
|
+
reject(new Error(`找不到命令 \`${step.command}\`,请先确认本机已安装,并且在登录 shell 中可直接执行。`))
|
|
209
|
+
return
|
|
210
|
+
}
|
|
211
|
+
if (shouldContinueAfterStepFailure(step, output)) {
|
|
212
|
+
console.log(formatPrefixedMessage(step.continueMessage || '检测到当前步骤已完成,继续执行后续流程。'))
|
|
213
|
+
resolve()
|
|
214
|
+
return
|
|
215
|
+
}
|
|
216
|
+
reject(new Error(`命令退出码异常:${code}`))
|
|
217
|
+
})
|
|
218
|
+
})
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function shouldContinueAfterStepFailure(step, output) {
|
|
222
|
+
const patterns = Array.isArray(step.continueOnOutput) ? step.continueOnOutput : []
|
|
223
|
+
const normalizedOutput = String(output || '')
|
|
224
|
+
return patterns.some(pattern => pattern.test(normalizedOutput))
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
function formatCommand(command, args) {
|
|
228
|
+
return [command].concat(args).join(' ')
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function describeStep(step) {
|
|
232
|
+
if (step.type === 'delay') {
|
|
233
|
+
return step.message
|
|
234
|
+
}
|
|
235
|
+
if (step.type === 'hermes-version-check') {
|
|
236
|
+
return `检查 Hermes 版本 >= ${step.minimumVersion}`
|
|
237
|
+
}
|
|
238
|
+
return formatCommand(step.command, step.args)
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function createSpawnConfig(command, args) {
|
|
242
|
+
return {
|
|
243
|
+
command,
|
|
244
|
+
args,
|
|
245
|
+
options: {
|
|
246
|
+
stdio: 'inherit',
|
|
247
|
+
shell: true,
|
|
248
|
+
},
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
function formatShellCommand(command, args) {
|
|
253
|
+
return [command].concat(args).map(quoteShellArg).join(' ')
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function quoteShellArg(value) {
|
|
257
|
+
const raw = String(value)
|
|
258
|
+
if (!raw) {
|
|
259
|
+
return "''"
|
|
260
|
+
}
|
|
261
|
+
return `'${raw.replace(/'/g, `'\\''`)}'`
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
function resolveShell() {
|
|
265
|
+
const shell = String(process.env.SHELL || '').trim()
|
|
266
|
+
return shell || '/bin/bash'
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
function formatPrefixedMessage(message) {
|
|
270
|
+
return `${formatLogPrefix()} ${message}`
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
function formatLogPrefix() {
|
|
274
|
+
if (!process.stdout.isTTY && !process.stderr.isTTY) {
|
|
275
|
+
return LOG_PREFIX
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
const [red, green, blue] = LOG_PREFIX_COLOR
|
|
279
|
+
return `\x1b[38;2;${red};${green};${blue}m${LOG_PREFIX}\x1b[0m`
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
function sleep(ms) {
|
|
283
|
+
return new Promise(resolve => setTimeout(resolve, ms))
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function getTargets(requestedVersion) {
|
|
287
|
+
return [
|
|
288
|
+
{
|
|
289
|
+
id: 'openclaw',
|
|
290
|
+
label: 'OpenClaw',
|
|
291
|
+
description: '安装 OpenClaw VeriAgent 插件并直接调用 VeriAgent CLI 安装流程',
|
|
292
|
+
steps: [
|
|
293
|
+
{
|
|
294
|
+
label: '安装 OpenClaw 插件',
|
|
295
|
+
command: OPENCLAW_COMMAND,
|
|
296
|
+
args: ['plugins', 'install', buildOpenClawPackageSpecifier(requestedVersion)],
|
|
297
|
+
continueOnOutput: [/plugin already exists/i],
|
|
298
|
+
continueMessage: 'OpenClaw 插件已存在,跳过安装并继续后续流程。',
|
|
299
|
+
},
|
|
300
|
+
{
|
|
301
|
+
label: '重启 OpenClaw Gateway',
|
|
302
|
+
command: OPENCLAW_COMMAND,
|
|
303
|
+
args: ['gateway', 'restart'],
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
label: '等待 OpenClaw Gateway 就绪',
|
|
307
|
+
type: 'delay',
|
|
308
|
+
silent: true,
|
|
309
|
+
durationMs: OPENCLAW_GATEWAY_WAIT_MS,
|
|
310
|
+
message: `等待 ${OPENCLAW_GATEWAY_WAIT_MS / 1000} 秒,确保 OpenClaw Gateway 完成重启`,
|
|
311
|
+
},
|
|
312
|
+
{
|
|
313
|
+
label: '触发 VeriAgent 安装流程',
|
|
314
|
+
command: OPENCLAW_COMMAND,
|
|
315
|
+
args: ['veriagent', 'init'],
|
|
316
|
+
},
|
|
317
|
+
],
|
|
318
|
+
},
|
|
319
|
+
{
|
|
320
|
+
id: 'hermes',
|
|
321
|
+
label: 'Hermes',
|
|
322
|
+
description: '安装 Hermes VeriAgent 插件并初始化',
|
|
323
|
+
steps: [
|
|
324
|
+
{
|
|
325
|
+
label: '检查 Hermes 版本',
|
|
326
|
+
type: 'hermes-version-check',
|
|
327
|
+
minimumVersion: MIN_HERMES_VERSION,
|
|
328
|
+
},
|
|
329
|
+
{
|
|
330
|
+
label: '安装 Hermes VeriAgent 插件',
|
|
331
|
+
command: HERMES_COMMAND,
|
|
332
|
+
args: ['plugins', 'install', buildHermesPackageSpecifier(requestedVersion)],
|
|
333
|
+
continueOnOutput: [/Plugin 'hermes-veriagent' already exists/i],
|
|
334
|
+
continueMessage: 'Hermes VeriAgent 插件已存在,跳过安装并继续后续流程。',
|
|
335
|
+
},
|
|
336
|
+
{
|
|
337
|
+
label: '启用 Hermes VeriAgent 插件',
|
|
338
|
+
command: HERMES_COMMAND,
|
|
339
|
+
args: ['plugins', 'enable', 'hermes-veriagent'],
|
|
340
|
+
},
|
|
341
|
+
{
|
|
342
|
+
label: '触发 VeriAgent 安装流程',
|
|
343
|
+
command: HERMES_COMMAND,
|
|
344
|
+
args: ['veriagent', 'init'],
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
},
|
|
348
|
+
]
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
function buildOpenClawPackageSpecifier(version) {
|
|
352
|
+
const specifier = version ? `${OPENCLAW_PACKAGE_NAME}@${version}` : OPENCLAW_PACKAGE_NAME
|
|
353
|
+
return `clawhub:${specifier}`
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function buildHermesPackageSpecifier(version) {
|
|
357
|
+
return HERMES_PLUGIN_SOURCE
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function normalizeVersionArg(value) {
|
|
361
|
+
return String(value || '').trim()
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function checkHermesVersion(minimumVersion) {
|
|
365
|
+
const commandAvailable = await isCommandAvailable(HERMES_COMMAND)
|
|
366
|
+
if (!commandAvailable) {
|
|
367
|
+
throw new Error('找不到命令 `hermes`,请先安装 Hermes,并确认当前终端可直接执行。')
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const output = await captureCommand(HERMES_COMMAND, ['--version'])
|
|
371
|
+
const currentVersion = extractSemver(output)
|
|
372
|
+
if (!currentVersion) {
|
|
373
|
+
throw new Error(`无法识别 Hermes 版本输出:${output || '(empty)'}`)
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
if (compareSemver(currentVersion, minimumVersion) < 0) {
|
|
377
|
+
throw new Error(
|
|
378
|
+
`当前 Hermes 版本为 ${currentVersion},VeriAgent 需要 Hermes >= ${minimumVersion}。` +
|
|
379
|
+
'请先执行 `hermes update`,再重新运行本命令。'
|
|
380
|
+
)
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
console.log(formatPrefixedMessage(`Hermes 版本检查通过:${currentVersion}`))
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
function captureCommand(command, args) {
|
|
387
|
+
return new Promise((resolve, reject) => {
|
|
388
|
+
const spawnConfig = createSpawnConfig(command, args)
|
|
389
|
+
const child = spawn(spawnConfig.command, spawnConfig.args, {
|
|
390
|
+
...spawnConfig.options,
|
|
391
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
392
|
+
})
|
|
393
|
+
let stdout = ''
|
|
394
|
+
let stderr = ''
|
|
395
|
+
|
|
396
|
+
child.stdout.on('data', chunk => {
|
|
397
|
+
stdout += String(chunk)
|
|
398
|
+
})
|
|
399
|
+
child.stderr.on('data', chunk => {
|
|
400
|
+
stderr += String(chunk)
|
|
401
|
+
})
|
|
402
|
+
child.on('error', reject)
|
|
403
|
+
child.on('exit', code => {
|
|
404
|
+
const output = `${stdout}\n${stderr}`.trim()
|
|
405
|
+
if (code === 0) {
|
|
406
|
+
resolve(output)
|
|
407
|
+
return
|
|
408
|
+
}
|
|
409
|
+
reject(new Error(`命令执行失败:${formatCommand(command, args)},退出码:${code},输出:${output || '(empty)'}`))
|
|
410
|
+
})
|
|
411
|
+
})
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
function extractSemver(output) {
|
|
415
|
+
const match = String(output || '').match(/v?(\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?)/)
|
|
416
|
+
return match ? match[1] : ''
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function compareSemver(left, right) {
|
|
420
|
+
const leftParts = parseSemverParts(left)
|
|
421
|
+
const rightParts = parseSemverParts(right)
|
|
422
|
+
for (let index = 0; index < 3; index += 1) {
|
|
423
|
+
if (leftParts[index] > rightParts[index]) {
|
|
424
|
+
return 1
|
|
425
|
+
}
|
|
426
|
+
if (leftParts[index] < rightParts[index]) {
|
|
427
|
+
return -1
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
return 0
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
function parseSemverParts(version) {
|
|
434
|
+
const parts = String(version || '')
|
|
435
|
+
.trim()
|
|
436
|
+
.replace(/^v/, '')
|
|
437
|
+
.split(/[.-]/)
|
|
438
|
+
.slice(0, 3)
|
|
439
|
+
.map(part => Number.parseInt(part, 10))
|
|
440
|
+
|
|
441
|
+
while (parts.length < 3) {
|
|
442
|
+
parts.push(0)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
return parts.map(part => Number.isFinite(part) ? part : 0)
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
async function isCommandAvailable(command) {
|
|
449
|
+
if (isFileCommand(command)) {
|
|
450
|
+
return fs.existsSync(command)
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
return new Promise(resolve => {
|
|
454
|
+
const probe = process.platform === 'win32'
|
|
455
|
+
? spawn(process.env.ComSpec || 'cmd.exe', ['/d', '/s', '/c', `where ${command}`], { stdio: 'ignore' })
|
|
456
|
+
: spawn(resolveShell(), ['-lc', `command -v ${quoteShellArg(command)} >/dev/null 2>&1`], { stdio: 'ignore' })
|
|
457
|
+
|
|
458
|
+
probe.on('error', () => resolve(false))
|
|
459
|
+
probe.on('exit', code => resolve(code === 0))
|
|
460
|
+
})
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function isFileCommand(command) {
|
|
464
|
+
const normalized = String(command || '').trim()
|
|
465
|
+
return path.isAbsolute(normalized) || normalized.includes('/') || normalized.includes('\\')
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
function resolveOpenClawStateDir() {
|
|
469
|
+
const explicitStateDir = String(process.env.OPENCLAW_STATE_DIR || '').trim()
|
|
470
|
+
if (explicitStateDir) {
|
|
471
|
+
return explicitStateDir
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
return path.join(os.homedir(), '.openclaw')
|
|
475
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@esign-ai/veriagent-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"bin": {
|
|
6
|
+
"veriagent-cli": "index.js"
|
|
7
|
+
},
|
|
8
|
+
"scripts": {
|
|
9
|
+
"build": "node scripts/build-package.js",
|
|
10
|
+
"package:cli": "npm run build && npm pack ./dist",
|
|
11
|
+
"publish:cli": "npm run build && npm publish ./dist"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"index.js",
|
|
15
|
+
"README.md"
|
|
16
|
+
],
|
|
17
|
+
"type": "commonjs",
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
}
|
|
21
|
+
}
|