@bluevs/vhcli 0.5.9 → 0.5.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.
@@ -1,126 +0,0 @@
1
- #!/bin/bash
2
- # install-skill.sh — vhcli + skill 双向依赖检测与安装
3
- #
4
- # 两种入口场景:
5
- # A) 已安装 skill,检测 vhcli CLI 是否就绪
6
- # B) 已安装 vhcli CLI,检测 skill 是否同步到 workspace
7
- #
8
- # 用法: bash skills/vhcli/scripts/install-skill.sh
9
-
10
- set -euo pipefail
11
-
12
- WORKSPACE="${OPENCLAW_WORKSPACE:-$HOME/.openclaw/workspace}"
13
- SKILL_DIR="$WORKSPACE/skills/vhcli"
14
- NPM_ROOT="$(npm root -g 2>/dev/null || echo '')"
15
- PKG_DIR="${NPM_ROOT:+$NPM_ROOT/@bluevs/vhcli}"
16
- PKG_SKILL_DIR="${PKG_DIR:+$PKG_DIR/skill}"
17
- VERSION_FILE="$SKILL_DIR/.installed_version"
18
-
19
- # ============================================================
20
- # 方向 A:skill 已存在 → 确保 vhcli CLI 可用
21
- # ============================================================
22
- check_cli() {
23
- if command -v vhcli &>/dev/null; then
24
- echo "✅ vhcli CLI 已安装 ($(vhcli --help 2>&1 | head -1 || echo 'ok'))"
25
- return 0
26
- else
27
- echo "⚠️ vhcli CLI 未安装"
28
- echo " 请执行: npm install -g @bluevs/vhcli"
29
- echo ""
30
- echo " 自动安装? (执行中...)"
31
- npm install -g @bluevs/vhcli 2>&1 | tail -3
32
- if command -v vhcli &>/dev/null; then
33
- echo "✅ vhcli CLI 安装成功"
34
- # 安装后刷新路径
35
- NPM_ROOT="$(npm root -g 2>/dev/null || echo '')"
36
- PKG_DIR="${NPM_ROOT:+$NPM_ROOT/@bluevs/vhcli}"
37
- PKG_SKILL_DIR="${PKG_DIR:+$PKG_DIR/skill}"
38
- return 0
39
- else
40
- echo "❌ 安装失败,请手动执行: npm install -g @bluevs/vhcli"
41
- return 1
42
- fi
43
- fi
44
- }
45
-
46
- # ============================================================
47
- # 方向 B:vhcli CLI 已存在 → 确保 skill 同步到 workspace
48
- # ============================================================
49
- sync_skill() {
50
- # 检查 npm 包是否含 skill 目录
51
- if [ -z "$PKG_SKILL_DIR" ] || [ ! -d "$PKG_SKILL_DIR" ]; then
52
- echo "ℹ️ 当前 vhcli 版本未包含 skill/ 目录,跳过自动同步"
53
- # 检查本地 skill 是否已存在(手动维护场景)
54
- if [ -f "$SKILL_DIR/SKILL.md" ]; then
55
- echo "✅ 本地 skill 已存在(手动维护模式)"
56
- else
57
- echo "⚠️ 本地 skill 不存在,且 npm 包无 skill 目录"
58
- echo " 请手动将 skill 文件放入: $SKILL_DIR/"
59
- fi
60
- return 0
61
- fi
62
-
63
- # 获取 npm 包版本
64
- PKG_VERSION=$(node -p "require('$PKG_DIR/package.json').version" 2>/dev/null || echo "unknown")
65
-
66
- # 检查本地版本
67
- if [ -f "$VERSION_FILE" ]; then
68
- LOCAL_VERSION=$(cat "$VERSION_FILE")
69
- if [ "$LOCAL_VERSION" = "$PKG_VERSION" ]; then
70
- echo "✅ vhcli skill 已是最新版本 ($PKG_VERSION),跳过同步"
71
- return 0
72
- fi
73
- echo "🔄 升级 vhcli skill: $LOCAL_VERSION → $PKG_VERSION"
74
- else
75
- if [ -f "$SKILL_DIR/SKILL.md" ]; then
76
- echo "🔄 同步 npm skill v$PKG_VERSION → 本地 (首次版本标记)"
77
- else
78
- echo "📦 首次安装 vhcli skill v$PKG_VERSION (from npm)"
79
- fi
80
- fi
81
-
82
- # 同步
83
- mkdir -p "$SKILL_DIR"
84
-
85
- # SKILL.md + docs/ → 直接覆盖(取最新文档)
86
- cp -f "$PKG_SKILL_DIR/SKILL.md" "$SKILL_DIR/SKILL.md"
87
- if [ -d "$PKG_SKILL_DIR/docs" ]; then
88
- mkdir -p "$SKILL_DIR/docs"
89
- cp -f "$PKG_SKILL_DIR/docs/"*.md "$SKILL_DIR/docs/" 2>/dev/null || true
90
- fi
91
-
92
- # scripts/ → 只添加新文件,不覆盖本地定制
93
- if [ -d "$PKG_SKILL_DIR/scripts" ]; then
94
- mkdir -p "$SKILL_DIR/scripts"
95
- for f in "$PKG_SKILL_DIR/scripts/"*; do
96
- [ -f "$f" ] || continue
97
- fname=$(basename "$f")
98
- if [ ! -f "$SKILL_DIR/scripts/$fname" ]; then
99
- cp "$f" "$SKILL_DIR/scripts/$fname"
100
- echo " + scripts/$fname (new)"
101
- else
102
- echo " ~ scripts/$fname (kept local version)"
103
- fi
104
- done
105
- fi
106
-
107
- # 记录版本
108
- echo "$PKG_VERSION" > "$VERSION_FILE"
109
- echo "✅ vhcli skill v$PKG_VERSION 同步完成"
110
- }
111
-
112
- # ============================================================
113
- # 主流程:双向检测
114
- # ============================================================
115
- echo "=== VH Skill 环境检测 ==="
116
- echo ""
117
-
118
- # A: 确保 CLI 可用
119
- check_cli || exit 1
120
- echo ""
121
-
122
- # B: 确保 skill 同步
123
- sync_skill
124
-
125
- echo ""
126
- echo "=== 检测完成 ==="
@@ -1,228 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * vh-auth-card.cjs — AI营销平台授权飞书交互卡片
4
- *
5
- * 用法:
6
- * node vh-auth-card.cjs [target]
7
- *
8
- * target: 飞书 open_id (ou_xxx) 或 chat_id (oc_xxx)
9
- *
10
- * 流程:
11
- * 1. spawn vhcli auth login → 解析 device_code + 授权 URL
12
- * 2. 发送蓝色飞书卡片(带按钮 + 验证码)
13
- * 3. 等待 auth login 子进程退出
14
- * - exit 0 → 授权成功 → patch 绿卡
15
- * - exit 非0 → 授权失败 → patch 红卡
16
- * - 超时 120s → patch 橙卡
17
- */
18
- const { spawn, execSync } = require('node:child_process');
19
- const { readFileSync } = require('node:fs');
20
-
21
- // --- 飞书 SDK ---
22
- const larkSdkPath = (() => {
23
- const paths = [
24
- '/root/.openclaw/npm/node_modules/@larksuiteoapi/node-sdk',
25
- '/app/dist/extensions/feishu/node_modules/@larksuiteoapi/node-sdk',
26
- ];
27
- for (const p of paths) {
28
- try { require.resolve(p); return p; } catch {}
29
- }
30
- throw new Error('Cannot find @larksuiteoapi/node-sdk');
31
- })();
32
- const lark = require(larkSdkPath);
33
-
34
- // --- 配置 ---
35
- function loadCreds() {
36
- const cfg = JSON.parse(readFileSync(process.env.HOME + '/.openclaw/openclaw.json', 'utf8'));
37
- const f = cfg.channels.feishu;
38
- return { appId: f.appId, appSecret: f.appSecret };
39
- }
40
-
41
- // --- 卡片构建 ---
42
- function buildCard({ title, headerTpl, status, code, url }) {
43
- const elements = [{ tag: 'markdown', content: status }];
44
- if (code) elements.push({ tag: 'markdown', content: `**验证码:** \`${code}\`` });
45
- if (url) elements.push({
46
- tag: 'button',
47
- text: { tag: 'plain_text', content: '🔗 打开授权页面' },
48
- type: 'primary',
49
- behaviors: [{ type: 'open_url', default_url: url, pc_url: url, ios_url: url, android_url: url }],
50
- });
51
- return {
52
- schema: '2.0',
53
- config: { update_multi: true },
54
- header: { title: { tag: 'plain_text', content: title }, template: headerTpl },
55
- body: { elements },
56
- };
57
- }
58
-
59
- // --- Target 解析 ---
60
- function resolveTarget() {
61
- // 优先级:argv > VH_AUTH_TARGET > OPENCLAW_CHAT_ID
62
- const raw = process.argv[2] || process.env.VH_AUTH_TARGET || process.env.OPENCLAW_CHAT_ID;
63
- if (!raw) return null;
64
-
65
- // 处理各种格式
66
- if (raw.startsWith('oc_')) {
67
- return { id: raw, type: 'chat_id' };
68
- }
69
- if (raw.startsWith('user:')) {
70
- // 私聊 chat_id 格式 "user:ou_xxx" → 取 open_id
71
- return { id: raw.slice(5), type: 'open_id' };
72
- }
73
- if (raw.startsWith('ou_')) {
74
- return { id: raw, type: 'open_id' };
75
- }
76
- // 其他格式尝试作为 chat_id
77
- return { id: raw, type: 'chat_id' };
78
- }
79
-
80
- // --- 主流程 ---
81
- async function main() {
82
- const resolved = resolveTarget();
83
- if (!resolved) {
84
- console.error('Usage: node vh-auth-card.cjs [target]');
85
- console.error(' target: open_id(ou_xxx) / chat_id(oc_xxx) / user:ou_xxx');
86
- console.error(' Also reads from env: VH_AUTH_TARGET or OPENCLAW_CHAT_ID');
87
- process.exit(1);
88
- }
89
-
90
- const target = resolved.id;
91
- const toType = resolved.type;
92
- const client = new lark.Client({ ...loadCreds(), disableTokenCache: false });
93
- const TIMEOUT_MS = 120000;
94
-
95
- // Step 1: spawn vhcli auth login, 解析输出
96
- console.log('Starting vhcli auth login...');
97
-
98
- const { url, code, exitPromise, child } = await new Promise((resolve, reject) => {
99
- const child = spawn('vhcli', ['auth', 'login'], { stdio: ['pipe', 'pipe', 'pipe'] });
100
- let output = '';
101
- let resolved = false;
102
-
103
- const onData = (chunk) => {
104
- output += chunk.toString();
105
- // 尝试解析 URL 和 code
106
- const urlMatch = output.match(/https?:\/\/[^\s]+/);
107
- const codeMatch = output.match(/Your code:\s*(\S+)/i) || output.match(/code[::]\s*(\S+)/i);
108
- if (urlMatch && codeMatch && !resolved) {
109
- resolved = true;
110
- const exitPromise = new Promise(res => child.on('close', res));
111
- resolve({ url: urlMatch[0], code: codeMatch[1], exitPromise, child });
112
- }
113
- };
114
-
115
- child.stdout.on('data', onData);
116
- child.stderr.on('data', onData);
117
-
118
- child.on('close', (exitCode) => {
119
- if (!resolved) {
120
- reject(new Error(`vhcli auth login exited (code ${exitCode}) before outputting auth info:\n${output}`));
121
- }
122
- });
123
-
124
- // 5s 内没解析出来就失败
125
- setTimeout(() => {
126
- if (!resolved) {
127
- child.kill();
128
- reject(new Error('Timeout parsing auth login output:\n' + output));
129
- }
130
- }, 10000);
131
- });
132
-
133
- console.log(`URL: ${url}`);
134
- console.log(`Code: ${code}`);
135
-
136
- // Step 2: 先发文字通知
137
- await client.im.message.create({
138
- params: { receive_id_type: toType },
139
- data: {
140
- receive_id: target,
141
- msg_type: 'text',
142
- content: JSON.stringify({ text: '请先完成AI营销平台授权' }),
143
- },
144
- });
145
- console.log('Text sent');
146
-
147
- // Step 3: 发蓝色卡片
148
- const resp = await client.im.message.create({
149
- params: { receive_id_type: toType },
150
- data: {
151
- receive_id: target,
152
- msg_type: 'interactive',
153
- content: JSON.stringify(buildCard({
154
- title: '🔐 AI营销平台授权',
155
- headerTpl: 'blue',
156
- status: '⏳ 请点击下方按钮完成授权',
157
- code,
158
- url,
159
- })),
160
- },
161
- });
162
-
163
- const messageId = resp.data.message_id;
164
- console.log('Card sent: ' + messageId);
165
-
166
- // Step 3: 等待 auth login 子进程退出 or 超时
167
- const timeoutPromise = new Promise(res => setTimeout(() => res('TIMEOUT'), TIMEOUT_MS));
168
- const result = await Promise.race([
169
- exitPromise.then(code => code === 0 ? 'SUCCESS' : 'FAILED'),
170
- timeoutPromise,
171
- ]);
172
-
173
- // Step 4: patch 卡片
174
- if (result === 'SUCCESS') {
175
- await client.im.message.patch({
176
- path: { message_id: messageId },
177
- data: {
178
- content: JSON.stringify(buildCard({
179
- title: '✅ AI营销平台授权成功',
180
- headerTpl: 'green',
181
- status: '可以开始使用了',
182
- })),
183
- },
184
- });
185
- console.log('AUTH_SUCCESS');
186
- // 通过 openclaw agent 注入消息唤醒 session(fire-and-forget)
187
- const notify = spawn('openclaw', [
188
- 'agent',
189
- '--message', '[system] VH平台授权已完成,请继续执行用户之前的请求',
190
- '--channel', 'feishu',
191
- '--deliver',
192
- '--timeout', '60',
193
- ], { stdio: 'ignore', detached: true });
194
- notify.unref();
195
- } else if (result === 'TIMEOUT') {
196
- child.kill();
197
- await client.im.message.patch({
198
- path: { message_id: messageId },
199
- data: {
200
- content: JSON.stringify(buildCard({
201
- title: '⏰ 授权超时',
202
- headerTpl: 'orange',
203
- status: '授权等待超时(120s),请重新发起',
204
- })),
205
- },
206
- });
207
- console.log('AUTH_TIMEOUT');
208
- } else {
209
- await client.im.message.patch({
210
- path: { message_id: messageId },
211
- data: {
212
- content: JSON.stringify(buildCard({
213
- title: '❌ 授权失败',
214
- headerTpl: 'red',
215
- status: '授权过程出错,请重试',
216
- })),
217
- },
218
- });
219
- console.log('AUTH_FAILED');
220
- }
221
-
222
- process.exit(result === 'SUCCESS' ? 0 : 1);
223
- }
224
-
225
- main().catch(e => {
226
- console.error(e.message || e);
227
- process.exit(1);
228
- });