codewave-openclaw-installer 2.3.0 → 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 +23 -0
- package/bin/install.mjs +232 -7
- package/bin/track-skill-usage.mjs +96 -0
- package/index.js +51 -3
- package/package.json +1 -1
- package/skills/ppt-master/scripts/project_manager.py +7 -0
- package/skills/ppt-master/scripts/track_skill.py +56 -0
- package/skills/risk-alert/SKILL.md +6 -0
- package/skills/risk-alert/scripts/risk-alert.js +11 -0
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
|
@@ -841,11 +841,11 @@ async function collectInteractiveConfigWrites(missing, options = {}) {
|
|
|
841
841
|
env: process.env,
|
|
842
842
|
toolkitConfig,
|
|
843
843
|
});
|
|
844
|
-
console.log(dim(' [
|
|
844
|
+
console.log(dim(' [智能客服] 可配置外部知识目录;留空则继续使用 skill 自带 docs/'));
|
|
845
845
|
if (configuredDocsDir) {
|
|
846
846
|
console.log(dim(` 当前知识目录: ${configuredDocsDir}`));
|
|
847
847
|
}
|
|
848
|
-
const wantsDocsDir = await askYesNo('
|
|
848
|
+
const wantsDocsDir = await askYesNo(' 是否现在配置智能客服知识目录?[Y/n] ', false);
|
|
849
849
|
if (wantsDocsDir) {
|
|
850
850
|
const docsDir = await ask(' SMART_CS_DOCS_DIR / docsDir absolute path: ');
|
|
851
851
|
if (docsDir) {
|
|
@@ -857,17 +857,17 @@ async function collectInteractiveConfigWrites(missing, options = {}) {
|
|
|
857
857
|
configuredAt: new Date().toISOString(),
|
|
858
858
|
},
|
|
859
859
|
}));
|
|
860
|
-
console.log(green(` ✅
|
|
860
|
+
console.log(green(` ✅ 已写入智能客服知识目录: ${docsDir}`));
|
|
861
861
|
} else {
|
|
862
862
|
console.log(dim(' 未填写知识目录,将继续使用默认 docs/'));
|
|
863
863
|
}
|
|
864
864
|
}
|
|
865
865
|
|
|
866
|
-
console.log(dim(' [
|
|
866
|
+
console.log(dim(' [智能客服] 可选配置向量检索;安装时只记录配置,不立即构建索引'));
|
|
867
867
|
if (configuredVector.enabled) {
|
|
868
868
|
console.log(dim(` 当前向量配置: enabled=true, model=${configuredVector.model || '(unset)'}`));
|
|
869
869
|
}
|
|
870
|
-
const wantsVector = await askYesNo('
|
|
870
|
+
const wantsVector = await askYesNo(' 是否现在配置智能客服向量模型?[y/N] ', true);
|
|
871
871
|
if (wantsVector) {
|
|
872
872
|
console.log(dim(' [Vector] 回车可跳过;若填写完整,首次使用时自动建索引,后续按文档变更增量重建'));
|
|
873
873
|
const baseUrl = await ask(' SMART_CS_VECTOR_BASE_URL: ');
|
|
@@ -894,7 +894,7 @@ async function collectInteractiveConfigWrites(missing, options = {}) {
|
|
|
894
894
|
configuredAt: new Date().toISOString(),
|
|
895
895
|
},
|
|
896
896
|
}));
|
|
897
|
-
console.log(green(` ✅
|
|
897
|
+
console.log(green(` ✅ 已写入智能客服向量配置: ${model}`));
|
|
898
898
|
} else {
|
|
899
899
|
console.log(dim(' 向量配置未填写完整,保持 grep / 默认检索路径'));
|
|
900
900
|
}
|
|
@@ -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
|
|
|
@@ -1469,7 +1672,7 @@ async function main() {
|
|
|
1469
1672
|
toolkitConfig,
|
|
1470
1673
|
});
|
|
1471
1674
|
const smartMode = smartVector.enabled ? 'vector(auto)' : 'grep';
|
|
1472
|
-
console.log('
|
|
1675
|
+
console.log(' 智能客服:');
|
|
1473
1676
|
console.log(` 知识目录 : ${smartDocsDir || '内置 docs/'}`);
|
|
1474
1677
|
console.log(` 检索模式 : ${smartMode}`);
|
|
1475
1678
|
console.log('');
|
|
@@ -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(
|
|
16
|
-
|
|
17
|
-
|
|
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
|
@@ -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)
|
|
@@ -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');
|