codewave-openclaw-installer 2.3.1 → 2.3.2

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 CHANGED
@@ -70,6 +70,29 @@ npx codewave-openclaw-installer
70
70
  4. `postflight`:重启 OpenClaw Gateway,并运行统一检查输出最终状态
71
71
  5. `channel-bootstrap`:检测到飞书或钉钉线索时,按官方入口自动安装并初始化
72
72
 
73
+ ## Skill 使用埋点
74
+
75
+ 自动上报埋点到追踪服务器:
76
+
77
+ ### 埋点时机
78
+
79
+ 1. **安装完成时**:上报 `skill_name: "用户安装"`
80
+ 2. **使用 skill 时**:上报具体的 skill 名称
81
+
82
+ ### 上报数据示例
83
+
84
+ ```json
85
+ {
86
+ "skill_name": "用户安装",
87
+ "user_id": "your-hostname",
88
+ "timestamp": "2026-04-13T14:53:15.000Z"
89
+ }
90
+ ```
91
+
92
+ - **设备标识**: 直接使用设备 hostname
93
+ - **静默失败**: 埋点失败不影响安装和 skill 使用
94
+ - **详细文档**: 见 [docs/skill-tracking.md](docs/skill-tracking.md)
95
+
73
96
  ## 目录结构
74
97
 
75
98
  ```
package/bin/install.mjs CHANGED
@@ -1400,6 +1400,209 @@ async function runPostflight(context) {
1400
1400
  }
1401
1401
  }
1402
1402
 
1403
+ /**
1404
+ * 为非 CLI skill 注入埋点代码
1405
+ */
1406
+ async function injectSkillTracking() {
1407
+ const extensionDir = join(homedir(), '.openclaw', 'extensions', 'codewave-openclaw-installer');
1408
+
1409
+ if (!existsSync(extensionDir)) {
1410
+ console.log(dim(` ↺ 扩展目录不存在,跳过 skill 埋点注入`));
1411
+ return;
1412
+ }
1413
+
1414
+ // 1. risk-alert (Node.js)
1415
+ const riskAlertScript = join(extensionDir, 'skills', 'risk-alert', 'scripts', 'risk-alert.js');
1416
+ if (existsSync(riskAlertScript)) {
1417
+ let content = readFileSync(riskAlertScript, 'utf8');
1418
+
1419
+ // 检查是否已经注入过
1420
+ if (!content.includes('上报埋点')) {
1421
+ const trackCode = `
1422
+ // 上报埋点(静默失败)
1423
+ try {
1424
+ const trackScript = path.join(os.homedir(), '.openclaw/extensions/codewave-openclaw-installer/bin/track-skill-usage.mjs');
1425
+ if (fs.existsSync(trackScript)) {
1426
+ execSync(\`node "\${trackScript}" "risk-alert"\`, { stdio: 'ignore', timeout: 3000 });
1427
+ }
1428
+ } catch (e) {
1429
+ // 静默失败
1430
+ }
1431
+ `;
1432
+
1433
+ // 在 require 语句后插入
1434
+ content = content.replace(
1435
+ /(const path = require\('path'\);)/,
1436
+ `$1\nconst os = require('os');\n${trackCode}`
1437
+ );
1438
+
1439
+ writeFileSync(riskAlertScript, content);
1440
+ console.log(green(` ✓ risk-alert 埋点注入成功`));
1441
+ }
1442
+ }
1443
+
1444
+ // 2. ppt-master (Python)
1445
+ const pptMasterDir = join(extensionDir, 'skills', 'ppt-master', 'scripts');
1446
+ if (existsSync(pptMasterDir)) {
1447
+ // 创建 track_skill.py
1448
+ const trackSkillPy = join(pptMasterDir, 'track_skill.py');
1449
+ const trackSkillCode = `#!/usr/bin/env python3
1450
+ """
1451
+ PPT Master skill 埋点工具
1452
+ 在 Python 脚本中调用以上报使用埋点
1453
+ """
1454
+
1455
+ import os
1456
+ import json
1457
+ import socket
1458
+ from datetime import datetime
1459
+ from urllib import request
1460
+ from urllib.error import URLError
1461
+
1462
+ TRACK_API_URL = "http://117.187.202.190:8000/api/track"
1463
+ TRACK_TIMEOUT = 3 # 3秒超时
1464
+
1465
+
1466
+ def track_skill_usage(skill_name="ppt-master", silent=True):
1467
+ """
1468
+ 上报 skill 使用埋点
1469
+
1470
+ Args:
1471
+ skill_name: skill 名称
1472
+ silent: 静默模式,失败不抛出异常
1473
+
1474
+ Returns:
1475
+ bool: 是否成功
1476
+ """
1477
+ try:
1478
+ data = {
1479
+ "skill_name": skill_name,
1480
+ "user_id": socket.gethostname(),
1481
+ "timestamp": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z")
1482
+ }
1483
+
1484
+ req = request.Request(
1485
+ TRACK_API_URL,
1486
+ data=json.dumps(data).encode('utf-8'),
1487
+ headers={'Content-Type': 'application/json'}
1488
+ )
1489
+
1490
+ with request.urlopen(req, timeout=TRACK_TIMEOUT) as response:
1491
+ return response.status == 200
1492
+
1493
+ except (URLError, Exception) as e:
1494
+ if not silent:
1495
+ print(f"[Track] Error: {e}")
1496
+ return False
1497
+
1498
+
1499
+ if __name__ == "__main__":
1500
+ import sys
1501
+ skill_name = sys.argv[1] if len(sys.argv) > 1 else "ppt-master"
1502
+ success = track_skill_usage(skill_name, silent=False)
1503
+ print(f"{'✓' if success else '✗'} Tracked: {skill_name}")
1504
+ sys.exit(0 if success else 1)
1505
+ `;
1506
+
1507
+ writeFileSync(trackSkillPy, trackSkillCode);
1508
+ chmodSync(trackSkillPy, 0o755);
1509
+
1510
+ // 修改 project_manager.py
1511
+ const projectManagerPy = join(pptMasterDir, 'project_manager.py');
1512
+ if (existsSync(projectManagerPy)) {
1513
+ let content = readFileSync(projectManagerPy, 'utf8');
1514
+
1515
+ if (!content.includes('上报埋点')) {
1516
+ content = content.replace(
1517
+ /(from urllib\.parse import urlparse)/,
1518
+ `$1\n\n# 上报埋点(静默失败)\ntry:\n from track_skill import track_skill_usage\n track_skill_usage("ppt-master")\nexcept Exception:\n pass`
1519
+ );
1520
+
1521
+ writeFileSync(projectManagerPy, content);
1522
+ }
1523
+ }
1524
+
1525
+ console.log(green(` ✓ ppt-master 埋点注入成功`));
1526
+ }
1527
+ }
1528
+
1529
+ /**
1530
+ * 创建 CLI wrapper 用于自动上报埋点
1531
+ */
1532
+ async function createCliWrappers() {
1533
+ const wrappers = [
1534
+ {
1535
+ name: 'razel-cli',
1536
+ binPath: join(homedir(), '.local', 'bin', 'razel-cli'),
1537
+ mapping: {
1538
+ popo: 'popo-doc',
1539
+ 'meeting-prd': 'meeting-to-prd',
1540
+ 'm2p': 'meeting-to-prd',
1541
+ },
1542
+ },
1543
+ {
1544
+ name: 'razel-py-cli',
1545
+ binPath: join(homedir(), '.local', 'bin', 'razel-py-cli'),
1546
+ mapping: {
1547
+ 'meeting-prd': 'meeting-to-prd',
1548
+ speech: 'speed-to-text',
1549
+ 's2t': 'speed-to-text',
1550
+ pmo: 'pmo-weekly-report',
1551
+ },
1552
+ },
1553
+ ];
1554
+
1555
+ for (const { name, binPath, mapping } of wrappers) {
1556
+ if (!existsSync(binPath)) {
1557
+ console.log(dim(` ↺ ${name} 不存在,跳过 wrapper 创建`));
1558
+ continue;
1559
+ }
1560
+
1561
+ const realPath = `${binPath}.real`;
1562
+
1563
+ // 备份原始命令(如果还没备份)
1564
+ if (!existsSync(realPath)) {
1565
+ cpSync(binPath, realPath);
1566
+ }
1567
+
1568
+ // 生成 mapping 的 case 语句
1569
+ const caseStatements = Object.entries(mapping)
1570
+ .map(([key, value]) => ` ${key})\n TRACK_NAME="${value}"\n ;;`)
1571
+ .join('\n');
1572
+
1573
+ // 生成 wrapper 脚本
1574
+ const wrapperScript = `#!/bin/bash
1575
+
1576
+ # ${name} wrapper - 自动上报埋点
1577
+ # 原始命令已备份到 ${name}.real
1578
+
1579
+ # 提取 skill 名称(第一个参数)
1580
+ SKILL_NAME="\${1:-unknown}"
1581
+
1582
+ # 映射 CLI 子命令到 skill 名称
1583
+ case "$SKILL_NAME" in
1584
+ ${caseStatements}
1585
+ *)
1586
+ TRACK_NAME="${name}-\${SKILL_NAME}"
1587
+ ;;
1588
+ esac
1589
+
1590
+ # 异步上报埋点(后台进程,不阻塞)
1591
+ node ~/.openclaw/extensions/codewave-openclaw-installer/bin/track-skill-usage.mjs "$TRACK_NAME" >/dev/null 2>&1 &
1592
+
1593
+ # 等待一小段时间确保埋点请求发出(不影响性能)
1594
+ sleep 0.1
1595
+
1596
+ # 调用真实命令,传递所有参数和返回码
1597
+ exec "$(dirname "$0")/${name}.real" "$@"
1598
+ `;
1599
+
1600
+ writeFileSync(binPath, wrapperScript);
1601
+ chmodSync(binPath, 0o755);
1602
+ console.log(green(` ✓ ${name} wrapper 创建成功`));
1603
+ }
1604
+ }
1605
+
1403
1606
  async function main() {
1404
1607
  const context = createInstallContext(process.platform);
1405
1608
 
@@ -1477,6 +1680,28 @@ async function main() {
1477
1680
  console.log(cyan(' razel-py-cli --help'));
1478
1681
  console.log(cyan(' razel-cli --help'));
1479
1682
  console.log('');
1683
+
1684
+ // 创建 CLI wrapper 用于埋点追踪
1685
+ try {
1686
+ await createCliWrappers();
1687
+ } catch (err) {
1688
+ console.warn(yellow(` ⚠ 创建 CLI wrapper 失败: ${err.message}`));
1689
+ }
1690
+
1691
+ // 为非 CLI skill 注入埋点代码
1692
+ try {
1693
+ await injectSkillTracking();
1694
+ } catch (err) {
1695
+ console.warn(yellow(` ⚠ Skill 埋点注入失败: ${err.message}`));
1696
+ }
1697
+
1698
+ // 上报安装埋点
1699
+ try {
1700
+ const { trackSkillUsage } = await import(join(PKG_ROOT, 'bin', 'track-skill-usage.mjs'));
1701
+ await trackSkillUsage('用户安装', { silent: true });
1702
+ } catch {
1703
+ // 静默失败,不影响安装流程
1704
+ }
1480
1705
  }
1481
1706
 
1482
1707
  main().catch((err) => {
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Skill 使用埋点追踪器
5
+ * 当用户使用 skill 时自动上报到埋点服务器
6
+ */
7
+
8
+ import { hostname } from 'node:os';
9
+
10
+ const TRACK_API_URL = 'http://117.187.202.190:8000/api/track';
11
+ const TRACK_TIMEOUT = 3000; // 3秒超时
12
+
13
+ /**
14
+ * 获取设备唯一标识(直接使用 hostname)
15
+ */
16
+ function getDeviceId() {
17
+ return hostname();
18
+ }
19
+
20
+ /**
21
+ * 上报 skill 使用埋点
22
+ * @param {string} skillName - skill 名称
23
+ * @param {object} options - 可选参数
24
+ * @returns {Promise<boolean>}
25
+ */
26
+ export async function trackSkillUsage(skillName, options = {}) {
27
+ const {
28
+ userId = getDeviceId(),
29
+ apiUrl = TRACK_API_URL,
30
+ timeout = TRACK_TIMEOUT,
31
+ silent = true, // 静默模式,失败不影响主流程
32
+ } = options;
33
+
34
+ try {
35
+ const controller = new AbortController();
36
+ const timeoutId = setTimeout(() => controller.abort(), timeout);
37
+
38
+ const response = await fetch(apiUrl, {
39
+ method: 'POST',
40
+ headers: {
41
+ 'Content-Type': 'application/json',
42
+ },
43
+ body: JSON.stringify({
44
+ skill_name: skillName,
45
+ user_id: userId,
46
+ timestamp: new Date().toISOString(),
47
+ }),
48
+ signal: controller.signal,
49
+ });
50
+
51
+ clearTimeout(timeoutId);
52
+
53
+ if (!response.ok && !silent) {
54
+ console.warn(`[Track] Failed to track skill usage: ${response.status}`);
55
+ return false;
56
+ }
57
+
58
+ return true;
59
+ } catch (error) {
60
+ if (!silent) {
61
+ console.warn(`[Track] Error tracking skill usage: ${error.message}`);
62
+ }
63
+ return false;
64
+ }
65
+ }
66
+
67
+ /**
68
+ * 从 SKILL.md 的 frontmatter 提取 skill name
69
+ */
70
+ export function extractSkillName(skillMdContent) {
71
+ const match = skillMdContent.match(/^---\s*\nname:\s*(.+?)\s*\n/m);
72
+ return match ? match[1].trim() : null;
73
+ }
74
+
75
+ // CLI 模式
76
+ if (import.meta.url === `file://${process.argv[1]}`) {
77
+ const skillName = process.argv[2];
78
+ if (!skillName) {
79
+ console.error('Usage: track-skill-usage.mjs <skill-name>');
80
+ process.exit(1);
81
+ }
82
+
83
+ trackSkillUsage(skillName, { silent: false })
84
+ .then((success) => {
85
+ if (success) {
86
+ console.log(`✓ Tracked: ${skillName}`);
87
+ } else {
88
+ console.error(`✗ Failed to track: ${skillName}`);
89
+ process.exit(1);
90
+ }
91
+ })
92
+ .catch((error) => {
93
+ console.error(`✗ Error: ${error.message}`);
94
+ process.exit(1);
95
+ });
96
+ }
package/index.js CHANGED
@@ -6,15 +6,63 @@
6
6
  */
7
7
 
8
8
  import { emptyPluginConfigSchema } from 'openclaw/plugin-sdk';
9
+ import { trackSkillUsage } from './bin/track-skill-usage.mjs';
10
+
11
+ // 插件中的 skill 列表(精确匹配,只追踪这些)
12
+ const PLUGIN_SKILLS = new Set([
13
+ 'acceptance-doc-entry',
14
+ 'meeting-to-prd',
15
+ 'pmo-weekly-report',
16
+ 'popo-doc',
17
+ 'popo-robot-api',
18
+ 'ppt-master',
19
+ 'risk-alert',
20
+ 'smart-customer-service',
21
+ 'speed-to-text',
22
+ 'tencent-meeting',
23
+ 'yidun-skill-sec',
24
+ ]);
9
25
 
10
26
  const plugin = {
11
27
  id: 'codewave-openclaw-installer',
12
28
  name: 'CodeWave Toolkit',
13
29
  description: '网易智企 CodeWave 低代码平台 OpenClaw 工具集',
14
30
  configSchema: emptyPluginConfigSchema(),
15
- register(_api) {
16
- // skills 插件,无需注册 channel/tool
17
- // Skills 由 openclaw.plugin.json 中的 "skills" 字段声明,OpenClaw 自动加载
31
+ register(api) {
32
+ if (!api.on) return;
33
+
34
+ // 监听 before_tool_call 事件
35
+ // 注意:OpenClaw 的 before_tool_call 传的是 tool 名称(exec、read 等),
36
+ // 不是 skill 名称。这里同时监听,以便在 OpenClaw 未来支持 skill 级事件时兼容。
37
+ api.on('before_tool_call', (event) => {
38
+ const toolName = event.toolName;
39
+ if (!toolName) return;
40
+
41
+ // 精确匹配(使用 Set.has 代替 includes,避免子串误匹配)
42
+ if (PLUGIN_SKILLS.has(toolName)) {
43
+ trackSkillUsage(toolName).catch((err) => {
44
+ console.error(`[CodeWave Track] Failed to track: ${err.message}`);
45
+ });
46
+ }
47
+ });
48
+
49
+ // 监听 skill:read 事件(OpenClaw 在读取 SKILL.md 时触发)
50
+ // 当 AI 读取某个 skill 的 SKILL.md 文件时,可以认为该 skill 被使用了
51
+ api.on('before_tool_call', (event) => {
52
+ const { toolName, params } = event;
53
+ if (toolName !== 'read' || !params?.path) return;
54
+
55
+ // 检查是否在读取本插件的 skill 的 SKILL.md
56
+ const pathStr = String(params.path);
57
+ for (const skill of PLUGIN_SKILLS) {
58
+ if (pathStr.includes(`/${skill}/SKILL.md`)) {
59
+ trackSkillUsage(skill).catch((err) => {
60
+ console.error(`[CodeWave Track] Failed to track: ${err.message}`);
61
+ });
62
+ break;
63
+ }
64
+ }
65
+ });
18
66
  },
19
67
  };
20
68
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codewave-openclaw-installer",
3
- "version": "2.3.1",
3
+ "version": "2.3.2",
4
4
  "description": "网易智企 CodeWave OpenClaw 扩展:Skills + CLI 依赖一键安装",
5
5
  "type": "module",
6
6
  "main": "index.js",
@@ -18,6 +18,13 @@ from datetime import datetime
18
18
  from pathlib import Path
19
19
  from urllib.parse import urlparse
20
20
 
21
+ # 上报埋点(静默失败)
22
+ try:
23
+ from track_skill import track_skill_usage
24
+ track_skill_usage("ppt-master")
25
+ except Exception:
26
+ pass
27
+
21
28
  try:
22
29
  from project_utils import (
23
30
  CANVAS_FORMATS,
@@ -0,0 +1,56 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ PPT Master skill 埋点工具
4
+ 在 Python 脚本中调用以上报使用埋点
5
+ """
6
+
7
+ import os
8
+ import json
9
+ import socket
10
+ from datetime import datetime
11
+ from urllib import request
12
+ from urllib.error import URLError
13
+
14
+ TRACK_API_URL = "http://117.187.202.190:8000/api/track"
15
+ TRACK_TIMEOUT = 3 # 3秒超时
16
+
17
+
18
+ def track_skill_usage(skill_name="ppt-master", silent=True):
19
+ """
20
+ 上报 skill 使用埋点
21
+
22
+ Args:
23
+ skill_name: skill 名称
24
+ silent: 静默模式,失败不抛出异常
25
+
26
+ Returns:
27
+ bool: 是否成功
28
+ """
29
+ try:
30
+ data = {
31
+ "skill_name": skill_name,
32
+ "user_id": socket.gethostname(),
33
+ "timestamp": datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.000Z")
34
+ }
35
+
36
+ req = request.Request(
37
+ TRACK_API_URL,
38
+ data=json.dumps(data).encode('utf-8'),
39
+ headers={'Content-Type': 'application/json'}
40
+ )
41
+
42
+ with request.urlopen(req, timeout=TRACK_TIMEOUT) as response:
43
+ return response.status == 200
44
+
45
+ except (URLError, Exception) as e:
46
+ if not silent:
47
+ print(f"[Track] Error: {e}")
48
+ return False
49
+
50
+
51
+ if __name__ == "__main__":
52
+ import sys
53
+ skill_name = sys.argv[1] if len(sys.argv) > 1 else "ppt-master"
54
+ success = track_skill_usage(skill_name, silent=False)
55
+ print(f"{'✓' if success else '✗'} Tracked: {skill_name}")
56
+ sys.exit(0 if success else 1)
@@ -1,3 +1,9 @@
1
+ ---
2
+ name: risk-alert
3
+ description: "智能风险预警,自动监控飞书群聊消息,识别风险并推送预警卡片"
4
+ version: 1.0.0
5
+ ---
6
+
1
7
  # Risk Alert - 智能风险预警 Skill
2
8
 
3
9
  ## 功能概述
@@ -11,6 +11,17 @@
11
11
  const { execSync, spawn } = require('child_process');
12
12
  const fs = require('fs');
13
13
  const path = require('path');
14
+ const os = require('os');
15
+
16
+ // 上报埋点(静默失败)
17
+ try {
18
+ const trackScript = path.join(os.homedir(), '.openclaw/extensions/codewave-openclaw-installer/bin/track-skill-usage.mjs');
19
+ if (fs.existsSync(trackScript)) {
20
+ execSync(`node "${trackScript}" "risk-alert"`, { stdio: 'ignore', timeout: 3000 });
21
+ }
22
+ } catch (e) {
23
+ // 静默失败
24
+ }
14
25
 
15
26
  const SCRIPTS_DIR = path.join(__dirname);
16
27
  const RAW_MESSAGES_MD = path.join(SCRIPTS_DIR, 'raw-messages.md');