@sokeai/cli 1.0.37 → 1.0.41
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 +24 -1
- package/package.json +1 -1
- package/scripts/build-binaries.sh +30 -1
- package/scripts/ci/check-legacy-constants.sh +27 -0
- package/scripts/ci/check-yaml-env-whitelist.sh +33 -0
- package/scripts/install.js +110 -52
- package/scripts/local-test.js +85 -10
- package/scripts/local-test.sh +7 -52
- package/scripts/mcp-stdout-scan.sh +46 -0
- package/scripts/push.sh +1 -1
- package/scripts/regress-auth.sh +56 -0
- package/scripts/regress-baseline.md +84 -0
- package/scripts/release.sh +35 -8
- package/scripts/test-auto-detect.js +48 -71
- package/scripts/test-local-reconcile.js +55 -0
- package/skills/soke-business-training-report/SKILL.md +58 -0
- package/skills/soke-course/SKILL.md +64 -0
- package/skills/soke-course/templates/batch-create-from-excel.md +75 -0
- package/skills/soke-learning-profile/SKILL.md +1 -0
- package/skills/soke-exam/SKILL.md +0 -318
- package/skills/soke-exam/references/exam-get-exam-user.md +0 -212
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# auth 回归 baseline 流程(task 8.2)
|
|
2
|
+
|
|
3
|
+
> 配套脚本:`scripts/regress-auth.sh`
|
|
4
|
+
> 用途:在本 change 引入前后两次跑同一脚本,diff 验证零回归。
|
|
5
|
+
|
|
6
|
+
## 1. baseline 采样(本 change 引入**前**)
|
|
7
|
+
|
|
8
|
+
在 master 或上一个 release tag 下:
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
git checkout <last-release-tag>
|
|
12
|
+
make install # 或:go build -o bin/soke-cli main.go
|
|
13
|
+
SOKE_CLIENT_ID=... SOKE_CLIENT_SECRET=... \
|
|
14
|
+
bash scripts/regress-auth.sh 2>&1 | tee /tmp/regress-before.txt
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`/tmp/regress-before.txt` 即 baseline,包含:
|
|
18
|
+
- 各步 stdout/stderr
|
|
19
|
+
- 各步 exit code(脚本内 `==== [label] exit=N ====` 行)
|
|
20
|
+
- timestamps 已 mask,仅保留结构性差异
|
|
21
|
+
|
|
22
|
+
## 2. 引入后采样
|
|
23
|
+
|
|
24
|
+
切到本 change 分支,重新 build 跑一次:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
git checkout worktree-generalize-vendor-plugin
|
|
28
|
+
make install
|
|
29
|
+
SOKE_CLIENT_ID=... SOKE_CLIENT_SECRET=... \
|
|
30
|
+
bash scripts/regress-auth.sh 2>&1 | tee /tmp/regress-after.txt
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## 3. diff
|
|
34
|
+
|
|
35
|
+
```bash
|
|
36
|
+
diff /tmp/regress-before.txt /tmp/regress-after.txt
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
期望:**无差异**(允许时间戳 mask 后的剩余 token 字段差异,因每次实际签发的
|
|
40
|
+
token bytes 不同 —— 但 mask 应已覆盖)。
|
|
41
|
+
|
|
42
|
+
若 diff 显示行为变化,本 change 视为引入回归,**禁止 merge**,
|
|
43
|
+
回到代码层检查 ClientID() / Endpoints() / CallbackPath 取值路径。
|
|
44
|
+
|
|
45
|
+
## 4. keychain 字段验证(任选其一,平台相关)
|
|
46
|
+
|
|
47
|
+
macOS:
|
|
48
|
+
```bash
|
|
49
|
+
security find-generic-password -s "soke-cli" -g 2>&1 | grep -E "acct|cdat"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Linux(libsecret):
|
|
53
|
+
```bash
|
|
54
|
+
secret-tool search service soke-cli
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
字段名(`access_token` / `refresh_token` 等)与 baseline 完全一致。
|
|
58
|
+
|
|
59
|
+
## 5. 三平台 matrix(task 8.3)
|
|
60
|
+
|
|
61
|
+
本 change 不接入 GH Actions yml(避免 PR 卡在未跑通的 CI 任务),由 ①
|
|
62
|
+
`make-soke-cli-mcp-first` 在 same release 时一并引入。matrix 草案:
|
|
63
|
+
|
|
64
|
+
```yaml
|
|
65
|
+
# .github/workflows/regress-auth.yml(草案,在 ① 同 release 一起合)
|
|
66
|
+
strategy:
|
|
67
|
+
matrix:
|
|
68
|
+
os: [ubuntu-latest, macos-latest, windows-latest]
|
|
69
|
+
jobs:
|
|
70
|
+
regress:
|
|
71
|
+
runs-on: ${{ matrix.os }}
|
|
72
|
+
steps:
|
|
73
|
+
- uses: actions/checkout@v4
|
|
74
|
+
- uses: actions/setup-go@v5
|
|
75
|
+
with: { go-version: '1.25' }
|
|
76
|
+
- run: |
|
|
77
|
+
go build -o bin/soke-cli main.go
|
|
78
|
+
# auth login 需要真账号,CI 内只跑 status / logout 路径
|
|
79
|
+
./bin/soke-cli auth status --json
|
|
80
|
+
./bin/soke-cli auth logout
|
|
81
|
+
./bin/soke-cli auth status --json
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
跑出 CI matrix 三个 job 全绿,本 change 才视为通过 task 8.3 AC。
|
package/scripts/release.sh
CHANGED
|
@@ -48,10 +48,10 @@ check_tools() {
|
|
|
48
48
|
check_git_status() {
|
|
49
49
|
echo -e "${YELLOW}检查 Git 状态...${NC}"
|
|
50
50
|
|
|
51
|
-
# 检查当前分支是否为
|
|
51
|
+
# 检查当前分支是否为 master
|
|
52
52
|
CURRENT_BRANCH=$(git branch --show-current)
|
|
53
|
-
if [ "$CURRENT_BRANCH" != "
|
|
54
|
-
echo -e "${RED}错误: 当前分支为 $CURRENT_BRANCH,发布必须在
|
|
53
|
+
if [ "$CURRENT_BRANCH" != "master" ]; then
|
|
54
|
+
echo -e "${RED}错误: 当前分支为 $CURRENT_BRANCH,发布必须在 master 分支上进行${NC}"
|
|
55
55
|
exit 1
|
|
56
56
|
fi
|
|
57
57
|
|
|
@@ -138,6 +138,8 @@ upload_github_release() {
|
|
|
138
138
|
|
|
139
139
|
\`\`\`bash
|
|
140
140
|
npm install -g @sokeai/cli
|
|
141
|
+
# 或
|
|
142
|
+
npm install -g @zevolve/cli
|
|
141
143
|
\`\`\`
|
|
142
144
|
|
|
143
145
|
## 更新日志
|
|
@@ -168,10 +170,32 @@ publish_npm() {
|
|
|
168
170
|
exit 1
|
|
169
171
|
fi
|
|
170
172
|
|
|
171
|
-
# 发布
|
|
173
|
+
# 1. 发布 @sokeai/cli
|
|
174
|
+
echo -e "${BLUE}正在发布 @sokeai/cli...${NC}"
|
|
172
175
|
npm publish --access public
|
|
173
|
-
|
|
174
|
-
|
|
176
|
+
echo -e "${GREEN}✓ @sokeai/cli 发布成功${NC}"
|
|
177
|
+
|
|
178
|
+
# 2. 发布 @zevolve/cli
|
|
179
|
+
echo -e "${BLUE}正在发布 @zevolve/cli...${NC}"
|
|
180
|
+
cp package.json package.json.bak
|
|
181
|
+
|
|
182
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
183
|
+
sed -i '' 's/"name": "@sokeai\/cli"/"name": "@zevolve\/cli"/' package.json
|
|
184
|
+
else
|
|
185
|
+
sed -i 's/"name": "@sokeai\/cli"/"name": "@zevolve\/cli"/' package.json
|
|
186
|
+
fi
|
|
187
|
+
|
|
188
|
+
# 尝试发布,如果失败则恢复原文件并退出
|
|
189
|
+
if ! npm publish --access public; then
|
|
190
|
+
echo -e "${RED}错误: @zevolve/cli 发布失败${NC}"
|
|
191
|
+
mv package.json.bak package.json
|
|
192
|
+
exit 1
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
mv package.json.bak package.json
|
|
196
|
+
echo -e "${GREEN}✓ @zevolve/cli 发布成功${NC}"
|
|
197
|
+
|
|
198
|
+
echo -e "${GREEN}✓ 所有 NPM 包发布成功${NC}"
|
|
175
199
|
echo ""
|
|
176
200
|
}
|
|
177
201
|
|
|
@@ -203,11 +227,14 @@ main() {
|
|
|
203
227
|
echo -e "${GREEN}========================================${NC}"
|
|
204
228
|
echo ""
|
|
205
229
|
echo -e "版本: ${GREEN}v${VERSION}${NC}"
|
|
206
|
-
echo -e "NPM:
|
|
207
|
-
echo -e "
|
|
230
|
+
echo -e "NPM (sokeai): ${BLUE}https://www.npmjs.com/package/@sokeai/cli${NC}"
|
|
231
|
+
echo -e "NPM (zevolve): ${BLUE}https://www.npmjs.com/package/@zevolve/cli${NC}"
|
|
232
|
+
echo -e "GitHub: ${BLUE}https://github.com/sokeai/soke-cli/releases/tag/v${VERSION}${NC}"
|
|
208
233
|
echo ""
|
|
209
234
|
echo "安装或升级到最新版本:"
|
|
210
235
|
echo -e "${YELLOW}npm install -g @sokeai/cli@latest${NC}"
|
|
236
|
+
echo -e "${YELLOW}或${NC}"
|
|
237
|
+
echo -e "${YELLOW}npm install -g @zevolve/cli@latest${NC}"
|
|
211
238
|
}
|
|
212
239
|
|
|
213
240
|
# 确认发布
|
|
@@ -5,66 +5,14 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const fs = require('fs');
|
|
8
|
+
const os = require('os');
|
|
8
9
|
const path = require('path');
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const entries = fs.readdirSync(packagedSkillsDir, { withFileTypes: true });
|
|
16
|
-
return entries
|
|
17
|
-
.filter(entry => entry.isDirectory() && entry.name.startsWith('soke-'))
|
|
18
|
-
.map(entry => entry.name)
|
|
19
|
-
.sort();
|
|
20
|
-
} catch (_) {
|
|
21
|
-
return [];
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
// 复制 parseSkillMetadata 函数
|
|
26
|
-
function parseSkillMetadata(skillDir) {
|
|
27
|
-
const skillMdPath = path.join(skillDir, 'SKILL.md');
|
|
28
|
-
if (!fs.existsSync(skillMdPath)) {
|
|
29
|
-
return null;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
try {
|
|
33
|
-
const content = fs.readFileSync(skillMdPath, 'utf8');
|
|
34
|
-
|
|
35
|
-
const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
|
36
|
-
if (!frontmatterMatch) return null;
|
|
37
|
-
|
|
38
|
-
const frontmatter = frontmatterMatch[1];
|
|
39
|
-
const metadata = {};
|
|
40
|
-
|
|
41
|
-
const nameMatch = frontmatter.match(/^name:\s*(.+)$/m);
|
|
42
|
-
if (nameMatch) metadata.name = nameMatch[1].trim();
|
|
43
|
-
|
|
44
|
-
const summaryMatch = frontmatter.match(/^summary:\s*(.+)$/m);
|
|
45
|
-
if (summaryMatch) metadata.summary = summaryMatch[1].trim();
|
|
46
|
-
|
|
47
|
-
const descMatch = frontmatter.match(/^description:\s*["'](.+)["']$/m);
|
|
48
|
-
if (descMatch) {
|
|
49
|
-
metadata.description = descMatch[1].trim();
|
|
50
|
-
} else {
|
|
51
|
-
const descMatch2 = frontmatter.match(/^description:\s*(.+)$/m);
|
|
52
|
-
if (descMatch2) metadata.description = descMatch2[1].trim();
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const versionMatch = frontmatter.match(/^version:\s*(.+)$/m);
|
|
56
|
-
if (versionMatch) metadata.version = versionMatch[1].trim();
|
|
57
|
-
|
|
58
|
-
const binsMatch = frontmatter.match(/bins:\s*\[(.+?)\]/);
|
|
59
|
-
if (binsMatch) {
|
|
60
|
-
metadata.bins = binsMatch[1].split(',').map(b => b.trim().replace(/['"]/g, ''));
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return metadata;
|
|
64
|
-
} catch (_) {
|
|
65
|
-
return null;
|
|
66
|
-
}
|
|
67
|
-
}
|
|
10
|
+
const {
|
|
11
|
+
detectSkillNames,
|
|
12
|
+
parseSkillMetadata,
|
|
13
|
+
pruneManagedSkillDirs,
|
|
14
|
+
pruneRegistrySkills
|
|
15
|
+
} = require('./install.js');
|
|
68
16
|
|
|
69
17
|
// 测试
|
|
70
18
|
const packageRoot = path.join(__dirname, '..');
|
|
@@ -102,19 +50,13 @@ for (const skillName of skillNames) {
|
|
|
102
50
|
// 验证结果
|
|
103
51
|
console.log('📊 验证结果:\n');
|
|
104
52
|
|
|
105
|
-
const
|
|
106
|
-
const
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
console.log(`❌ 缺少的 skills: ${missingSkills.join(', ')}`);
|
|
111
|
-
} else {
|
|
112
|
-
console.log('✅ 所有预期的 skills 都已检测到');
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (extraSkills.length > 0) {
|
|
116
|
-
console.log(`ℹ️ 额外的 skills: ${extraSkills.join(', ')}`);
|
|
53
|
+
const requiredSkills = ['soke-shared'];
|
|
54
|
+
const missingRequiredSkills = requiredSkills.filter(s => !skillNames.includes(s));
|
|
55
|
+
if (missingRequiredSkills.length > 0) {
|
|
56
|
+
console.log(`❌ 缺少基础 skills: ${missingRequiredSkills.join(', ')}`);
|
|
57
|
+
process.exit(1);
|
|
117
58
|
}
|
|
59
|
+
console.log('✅ 基础 skills 检测通过');
|
|
118
60
|
|
|
119
61
|
console.log('');
|
|
120
62
|
console.log('🎉 测试完成!');
|
|
@@ -123,3 +65,38 @@ console.log('💡 提示:');
|
|
|
123
65
|
console.log(' - 新增 skill 时,只需在 skills/ 目录下创建 soke-* 目录');
|
|
124
66
|
console.log(' - 确保每个 skill 都有 SKILL.md 文件,包含完整的 frontmatter');
|
|
125
67
|
console.log(' - install.js 会自动检测并注册所有 skills');
|
|
68
|
+
|
|
69
|
+
// 验证清理逻辑
|
|
70
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'soke-skill-prune-'));
|
|
71
|
+
const targetDir = path.join(tempRoot, 'skills');
|
|
72
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
73
|
+
fs.mkdirSync(path.join(targetDir, 'soke-stale'));
|
|
74
|
+
fs.mkdirSync(path.join(targetDir, 'soke-course'));
|
|
75
|
+
fs.mkdirSync(path.join(targetDir, 'custom-skill'));
|
|
76
|
+
|
|
77
|
+
const removedDirs = pruneManagedSkillDirs(targetDir, ['soke-course']);
|
|
78
|
+
if (!removedDirs.includes('soke-stale')) {
|
|
79
|
+
console.error('❌ pruneManagedSkillDirs 未删除过期 managed skill');
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
if (!fs.existsSync(path.join(targetDir, 'custom-skill'))) {
|
|
83
|
+
console.error('❌ pruneManagedSkillDirs 错误删除了非受管 skill');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const registry = {
|
|
88
|
+
skills: [
|
|
89
|
+
{ name: 'soke-stale' },
|
|
90
|
+
{ name: 'soke-course' },
|
|
91
|
+
{ name: 'custom-skill' }
|
|
92
|
+
]
|
|
93
|
+
};
|
|
94
|
+
const removedEntries = pruneRegistrySkills(registry, ['soke-course']);
|
|
95
|
+
if (removedEntries !== 1) {
|
|
96
|
+
console.error('❌ pruneRegistrySkills 未正确移除过期条目');
|
|
97
|
+
process.exit(1);
|
|
98
|
+
}
|
|
99
|
+
if (!registry.skills.find((entry) => entry.name === 'custom-skill')) {
|
|
100
|
+
console.error('❌ pruneRegistrySkills 错误移除了非受管条目');
|
|
101
|
+
process.exit(1);
|
|
102
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const os = require('os');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const {
|
|
7
|
+
pruneManagedSkillDirs,
|
|
8
|
+
pruneRegistrySkills,
|
|
9
|
+
updateWorkclawRegistry
|
|
10
|
+
} = require('./local-test.js');
|
|
11
|
+
|
|
12
|
+
const tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), 'soke-local-reconcile-'));
|
|
13
|
+
const targetDir = path.join(tempRoot, 'skills');
|
|
14
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
15
|
+
|
|
16
|
+
fs.mkdirSync(path.join(targetDir, 'soke-stale'));
|
|
17
|
+
fs.mkdirSync(path.join(targetDir, 'soke-exam'));
|
|
18
|
+
fs.mkdirSync(path.join(targetDir, 'custom-skill'));
|
|
19
|
+
|
|
20
|
+
const removedDirs = pruneManagedSkillDirs(targetDir, ['soke-exam']);
|
|
21
|
+
if (!removedDirs.includes('soke-stale')) {
|
|
22
|
+
console.error('❌ stale managed skill was not removed');
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
if (!fs.existsSync(path.join(targetDir, 'custom-skill'))) {
|
|
26
|
+
console.error('❌ unmanaged local skill should remain untouched');
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const registry = {
|
|
31
|
+
skills: [
|
|
32
|
+
{ name: 'soke-stale' },
|
|
33
|
+
{ name: 'soke-exam' },
|
|
34
|
+
{ name: 'custom-skill' }
|
|
35
|
+
]
|
|
36
|
+
};
|
|
37
|
+
const removedEntries = pruneRegistrySkills(registry, ['soke-exam']);
|
|
38
|
+
if (removedEntries !== 1) {
|
|
39
|
+
console.error('❌ registry stale entry removal failed');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
if (!registry.skills.find((entry) => entry.name === 'custom-skill')) {
|
|
43
|
+
console.error('❌ unmanaged registry entry should remain untouched');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const registryPath = path.join(tempRoot, 'registry.json');
|
|
48
|
+
updateWorkclawRegistry(registryPath, 'soke-contact', null, '/tmp/soke-contact');
|
|
49
|
+
const updatedRegistry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
50
|
+
if (!updatedRegistry.skills.find((entry) => entry.name === 'soke-contact')) {
|
|
51
|
+
console.error('❌ registry update should tolerate missing metadata');
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
console.log('✅ local reconciliation tests passed');
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: soke-business-training-report
|
|
3
|
+
description: 生成某场业务培训考试的整体分析报告,包括参考人数、通过率、平均分、最高分及未通过学员名单。
|
|
4
|
+
requiredAuthorizations: [{"platformId":"soke-ai"}]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# 培训考试整体分析报告 Skill
|
|
8
|
+
|
|
9
|
+
## 目标
|
|
10
|
+
根据用户提供的培训/考试名称,通过调用开放平台的考试相关 API,自动生成一份结构化的培训考试分析报告。
|
|
11
|
+
|
|
12
|
+
## 触发条件
|
|
13
|
+
当用户提出类似以下问题时,应使用此 Skill:
|
|
14
|
+
- "帮我分析一下本次业务培训的考试情况"
|
|
15
|
+
- "统计一下这次培训的通过率"
|
|
16
|
+
- "输出一份『新员工入职培训』的考试报告"
|
|
17
|
+
|
|
18
|
+
## 执行步骤
|
|
19
|
+
|
|
20
|
+
### 第一步:确认目标考试
|
|
21
|
+
1. 调用 `GET /exam/exam/list` 接口,获取考试列表。
|
|
22
|
+
- **必填参数**:`start_time` 和 `end_time`(Unix 时间戳,单位毫秒),时间差不超过 365 天。
|
|
23
|
+
- 若用户未提供时间范围,自动设置为近 3 个月。
|
|
24
|
+
2. 根据用户提供的培训名称关键词,在返回的 `data.list` 中模糊匹配 `title` 字段。
|
|
25
|
+
- 如果匹配到多个,列出候选供用户选择。
|
|
26
|
+
3. 记录目标考试的 `uuid`、`title`、`total_score`、`pass_score`。
|
|
27
|
+
|
|
28
|
+
### 第二步:获取考试学员成绩列表
|
|
29
|
+
1. 调用 `GET /exam/user/list` 接口,参数 `exam_id` 为考试 UUID。
|
|
30
|
+
2. 提取每个学员的 `pass_status`(passed/unpassed/not_attempt)、`last_score`。
|
|
31
|
+
|
|
32
|
+
### 第三步:计算整体统计指标
|
|
33
|
+
- 参考人数(排除 not_attempt)
|
|
34
|
+
- 通过人数(passed)
|
|
35
|
+
- 通过率、平均分、最高分
|
|
36
|
+
- 未通过学员列表
|
|
37
|
+
|
|
38
|
+
### 第四步:输出报告
|
|
39
|
+
按照以下格式输出:
|
|
40
|
+
|
|
41
|
+
📊 【培训名称】考试分析报告
|
|
42
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
43
|
+
👥 参考人数:XX 人
|
|
44
|
+
✅ 通过人数:XX 人
|
|
45
|
+
📈 通过率:XX%
|
|
46
|
+
📊 平均分:XX 分(满分 XX 分)
|
|
47
|
+
🏆 最高分:XX 分
|
|
48
|
+
❌ 未通过学员:XX 人
|
|
49
|
+
- 学员A(得分:XX)
|
|
50
|
+
...
|
|
51
|
+
|
|
52
|
+
## API 调用与鉴权
|
|
53
|
+
- 基础 URL:`https://oapi.soke.cn`
|
|
54
|
+
- 接口需要 `access_token`,在 Agent 环境中自动获取,无需手动处理。
|
|
55
|
+
|
|
56
|
+
## 注意事项
|
|
57
|
+
- `start_time` 和 `end_time` 必填,Skill 自动生成。
|
|
58
|
+
- 注意区分 `not_attempt`(未参考)、`reviewing`(阅卷中)、`unpassed`(未通过)和 `passed`(通过)。
|
|
@@ -3,6 +3,7 @@ name: soke-course
|
|
|
3
3
|
summary: 授客课程管理(课程列表/分类/课程详情/学习记录),通过 soke-cli 查询
|
|
4
4
|
version: 1.0.0
|
|
5
5
|
description: "授客课程管理:查询课程、课程分类、课程详情、学习记录。查询课程列表、课程分类、课程用户学习记录、课件列表、人脸识别记录。当用户需要查询课程信息、查看课程列表、查询学习记录、查看课程分类时使用。"
|
|
6
|
+
requiredAuthorizations: [{"platformId":"soke-ai"}]
|
|
6
7
|
metadata:
|
|
7
8
|
requires:
|
|
8
9
|
bins: ["soke-cli"]
|
|
@@ -51,6 +52,7 @@ Shortcut 是对常用操作的高级封装(`soke-cli course +<verb> [flags]`
|
|
|
51
52
|
| [`+get-course-user`](#get-course-user) | 获取单个用户的课程学习详情 |
|
|
52
53
|
| [`+list-lesson-learns`](#list-lesson-learns) | 列出课件学习记录 |
|
|
53
54
|
| [`+list-lesson-faces`](#list-lesson-faces) | 列出课件人脸识别记录 |
|
|
55
|
+
| [`+batch-create-from-excel`](#batch-create-from-excel) | 通过 Excel 批量创建课程并上传课件(原 createcourse 工具能力,已合入) |
|
|
54
56
|
|
|
55
57
|
## 命令详解
|
|
56
58
|
|
|
@@ -483,6 +485,68 @@ soke-cli course +list-lesson-learns --lesson-id <lesson_id>
|
|
|
483
485
|
soke-cli course +list-lesson-faces --lesson-id <lesson_id>
|
|
484
486
|
```
|
|
485
487
|
|
|
488
|
+
### +batch-create-from-excel
|
|
489
|
+
|
|
490
|
+
通过 Excel 文件批量创建课程并上传课件素材。能力来自原独立工具 createcourse,已物理合入本 CLI(`internal/courseimport`)。
|
|
491
|
+
|
|
492
|
+
**命令格式**:
|
|
493
|
+
```bash
|
|
494
|
+
soke-cli course +batch-create-from-excel \
|
|
495
|
+
--file <课程信息.xlsx> \
|
|
496
|
+
[--material-dir <素材目录>] \
|
|
497
|
+
[--separator <分隔符>] \
|
|
498
|
+
[--mode default|hg] \
|
|
499
|
+
[--picture-time 6] \
|
|
500
|
+
[--material-category-id <分类ID>] \
|
|
501
|
+
[--json-events]
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
**Flags**:
|
|
505
|
+
|
|
506
|
+
| 名称 | 必填 | 默认 | 说明 |
|
|
507
|
+
| --- | --- | --- | --- |
|
|
508
|
+
| `--file` | 是 | - | Excel 文件路径,必须 `.xlsx` |
|
|
509
|
+
| `--material-dir` | 否 | Excel 同目录 | 素材文件相对路径解析的根目录 |
|
|
510
|
+
| `--separator` | 否 | `-` | Excel `分类` / `标签` 列的分隔符,同时影响两列 |
|
|
511
|
+
| `--mode` | 否 | `default` | `default` 普通课程 / `hg` 海关学时模式 |
|
|
512
|
+
| `--picture-time` | 否 | `6` | 视频抽帧秒数;仅当系统已装 ffmpeg 时生效 |
|
|
513
|
+
| `--material-category-id` | 否 | 空 | 素材库默认分类 ID,空则交由后端兜底 |
|
|
514
|
+
| `--json-events` | 否 | false | stdout 输出 JSON-lines 进度事件,供 sokeclaw 等 IPC 调用方解析 |
|
|
515
|
+
|
|
516
|
+
**Excel 模板**: 详见 [`templates/batch-create-from-excel.md`](./templates/batch-create-from-excel.md)。
|
|
517
|
+
|
|
518
|
+
**进度事件协议**(`--json-events` 模式):
|
|
519
|
+
|
|
520
|
+
| `type` | 含义 |
|
|
521
|
+
| --- | --- |
|
|
522
|
+
| `env` | 启动时报告 ffmpeg 是否可用 |
|
|
523
|
+
| `course_start` | 开始处理某门课程 |
|
|
524
|
+
| `course_done` | 某门课程全流程成功 |
|
|
525
|
+
| `course_failed` | 某门课程失败(单门失败不中断整批) |
|
|
526
|
+
| `episode_uploaded` | 单节课件上传完成 |
|
|
527
|
+
| `cover_fallback` | 封面降级为默认封面 |
|
|
528
|
+
| `run_done` | 整批结束,带 `ok` / `failed` 计数 |
|
|
529
|
+
|
|
530
|
+
**示例**:
|
|
531
|
+
|
|
532
|
+
```bash
|
|
533
|
+
# 默认模式,Excel 与素材同目录
|
|
534
|
+
soke-cli course +batch-create-from-excel --file ./courses.xlsx
|
|
535
|
+
|
|
536
|
+
# HG 学时模式,显式指定素材目录,机器可读输出
|
|
537
|
+
soke-cli course +batch-create-from-excel \
|
|
538
|
+
--file ./hg-courses.xlsx \
|
|
539
|
+
--material-dir ./materials \
|
|
540
|
+
--mode hg \
|
|
541
|
+
--json-events
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
**注意**:
|
|
545
|
+
|
|
546
|
+
- ffmpeg 是可选依赖:已装则启用视频抽帧封面;未装则封面策略自动降级到默认封面,不影响主流程。如需启用抽帧,把 ffmpeg 装到 `PATH`,或设置环境变量 `SOKE_CLI_FFMPEG_PATH` 指向可执行文件。
|
|
547
|
+
- HG 模式下"所属单位标签"的 tag id 通过环境变量 `SOKE_CLI_HG_ORG_TAG_ID` 注入(原 createcourse 写死在 token 配置表里,合入 soke-cli 后改为外部注入)。
|
|
548
|
+
- 单门课程失败不会中断整批运行,失败统计会在 `run_done` 事件汇总。
|
|
549
|
+
|
|
486
550
|
## 注意事项
|
|
487
551
|
|
|
488
552
|
1. **时间格式**: 所有时间参数使用Unix时间戳(毫秒),不是秒
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# `+batch-create-from-excel` Excel 模板说明
|
|
2
|
+
|
|
3
|
+
`soke-cli course +batch-create-from-excel` 接受的 Excel 模板与原 `createcourse` 工具兼容。下面给出列定义、续行规则与制作技巧。
|
|
4
|
+
|
|
5
|
+
## 列定义
|
|
6
|
+
|
|
7
|
+
| 列名 | 必填 | 类型/格式 | 说明 |
|
|
8
|
+
| --- | --- | --- | --- |
|
|
9
|
+
| 序号 | 是 | 数字/文本 | 课程序号,行级唯一;续行(同一课程的下一节分集)填空 |
|
|
10
|
+
| 课程名称 | 是 | 文本 | 课程标题。续行可留空或填与上一行相同的标题 |
|
|
11
|
+
| 分类 | 否 | 分隔字符串 | 多级分类用 `--separator` 分隔,例如 `公共课-合规-反洗钱` |
|
|
12
|
+
| 标签 | 否 | 分隔字符串 | 多标签同样按 `--separator` 分隔 |
|
|
13
|
+
| 简介 | 否 | 文本 | 课程介绍 |
|
|
14
|
+
| 学分 | 否 | 数字 | `default` 模式有效 |
|
|
15
|
+
| 积分 | 否 | 数字 | `default` 模式有效;HG 模式忽略 |
|
|
16
|
+
| 封面图 | 否 | 文本 | URL / `default` / 本地图片路径 / 数字(秒,需 ffmpeg) / 留空(视频自动抽帧,需 ffmpeg) |
|
|
17
|
+
| 素材文件 | 否(分集行必填) | 路径 | 课件文件,相对路径会按 `--material-dir` 解析 |
|
|
18
|
+
|
|
19
|
+
### HG 模式额外列
|
|
20
|
+
|
|
21
|
+
启用 `--mode hg` 时,额外读取四列学时:
|
|
22
|
+
|
|
23
|
+
| 列名 | 单位 | 说明 |
|
|
24
|
+
| --- | --- | --- |
|
|
25
|
+
| 习思想 | 学时 | 习近平新时代中国特色社会主义思想 |
|
|
26
|
+
| 政治能力 | 学时 | - |
|
|
27
|
+
| 业务能力 | 学时 | - |
|
|
28
|
+
| 执法能力 | 学时 | - |
|
|
29
|
+
|
|
30
|
+
## 表头规则
|
|
31
|
+
|
|
32
|
+
- 前 2 行为表头,允许两行合并表头(工具按 trim 后的名称合并到位置映射)。
|
|
33
|
+
- 第 3 行起为数据。
|
|
34
|
+
- 总行数低于 4 会直接报错。
|
|
35
|
+
|
|
36
|
+
## 续行(同一课程多分集)
|
|
37
|
+
|
|
38
|
+
当一门课程含多节课件时:
|
|
39
|
+
|
|
40
|
+
1. 第一行填完整课程信息(序号 + 课程名称 + 其他元数据 + 第 1 节素材文件)。
|
|
41
|
+
2. 后续行的 `序号` 和 `课程名称` 留空(或 `课程名称` 与上一行相同),只填 `素材文件` (以及非 HG 模式的 `积分`)。
|
|
42
|
+
3. 工具会自动把后续行 append 到上一门课程的 `Episodes` 数组。
|
|
43
|
+
|
|
44
|
+
## 封面图来源策略
|
|
45
|
+
|
|
46
|
+
按以下优先级决定课程封面:
|
|
47
|
+
|
|
48
|
+
1. `封面图` 是 `http(s)://...` → 直接使用 URL。
|
|
49
|
+
2. `封面图` 是 `default` → 使用平台默认封面。
|
|
50
|
+
3. `封面图` 是本地图片路径(.jpg/.jpeg/.png/.gif/.bmp) → 上传 OSS 并使用返回的 URL。
|
|
51
|
+
4. `封面图` 是数字(秒数) + `素材文件` 是视频 + 系统有 ffmpeg → 从视频抽帧并上传。
|
|
52
|
+
5. `封面图` 留空 + `素材文件` 是视频 + 系统有 ffmpeg → 用 `--picture-time`(默认 6 秒)抽帧。
|
|
53
|
+
6. 以上都不满足 → 使用平台默认封面,并 emit `cover_fallback` 事件。
|
|
54
|
+
|
|
55
|
+
## 快速根据本地课件目录制作模板(Windows 示例)
|
|
56
|
+
|
|
57
|
+
> 来自原 createcourse 工具的工作流提示。
|
|
58
|
+
|
|
59
|
+
1. 打开 `cmd`,切到课件所在的最上层目录,例如 `cd d:\course`。
|
|
60
|
+
2. 把所有文件路径导出到文本文件:
|
|
61
|
+
```cmd
|
|
62
|
+
d:
|
|
63
|
+
cd course
|
|
64
|
+
dir /s /b /a:-d > filelist.txt
|
|
65
|
+
```
|
|
66
|
+
3. 打开空白 Excel,在「数据」选项卡选「从文本/CSV」导入 `filelist.txt`。
|
|
67
|
+
4. 对导入的数据列进行分列。
|
|
68
|
+
5. 根据实际存储情况合并单元格(同一课程的多节分集),补全课程学分、简介、封面图等信息。
|
|
69
|
+
|
|
70
|
+
## 模板示例
|
|
71
|
+
|
|
72
|
+
可参照仓库内随 createcourse 一起合入的两张样例图:
|
|
73
|
+
|
|
74
|
+
- `internal/courseimport/template_example.jpg`
|
|
75
|
+
- `internal/courseimport/template_example2.jpg`
|
|
@@ -3,6 +3,7 @@ name: soke-learning-profile
|
|
|
3
3
|
summary: 学员学习档案查询
|
|
4
4
|
version: 1.0.0
|
|
5
5
|
description: "学员学习档案查询:查询学员的完整学习记录,包括课程学习、考试成绩、证书获取、学分积分等。当用户需要查询学员学习档案、查看学员学习情况、统计学员学习数据、查询学员综合学习信息时使用。"
|
|
6
|
+
requiredAuthorizations: [{"platformId":"soke-ai"}]
|
|
6
7
|
metadata:
|
|
7
8
|
requires:
|
|
8
9
|
bins: ["soke-cli"]
|