@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.
Files changed (3) hide show
  1. package/README.md +115 -0
  2. package/index.js +475 -0
  3. 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
+ }