@sokeai/cli 1.0.20 → 1.0.23
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 +67 -4
- package/package.json +1 -1
- package/scripts/e2e-test.sh +38 -0
- package/scripts/install.js +126 -36
- package/scripts/link-skills.sh +203 -0
- package/scripts/local-test.js +503 -0
- package/scripts/local-test.sh +178 -0
- package/scripts/test-auto-detect.js +125 -0
- package/skills/soke-learning-profile/SKILL.md +223 -0
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 测试 install.js 的自动检测功能
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const path = require('path');
|
|
9
|
+
|
|
10
|
+
// 复制 detectSkillNames 函数
|
|
11
|
+
function detectSkillNames(packagedSkillsDir) {
|
|
12
|
+
if (!fs.existsSync(packagedSkillsDir)) return [];
|
|
13
|
+
|
|
14
|
+
try {
|
|
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
|
+
}
|
|
68
|
+
|
|
69
|
+
// 测试
|
|
70
|
+
const packageRoot = path.join(__dirname, '..');
|
|
71
|
+
const skillsDir = path.join(packageRoot, 'skills');
|
|
72
|
+
|
|
73
|
+
console.log('🔍 测试自动检测功能\n');
|
|
74
|
+
console.log('Skills 目录:', skillsDir);
|
|
75
|
+
console.log('');
|
|
76
|
+
|
|
77
|
+
// 检测所有 skills
|
|
78
|
+
const skillNames = detectSkillNames(skillsDir);
|
|
79
|
+
|
|
80
|
+
console.log(`✅ 检测到 ${skillNames.length} 个 skills:\n`);
|
|
81
|
+
|
|
82
|
+
// 显示每个 skill 的详细信息
|
|
83
|
+
for (const skillName of skillNames) {
|
|
84
|
+
const skillDir = path.join(skillsDir, skillName);
|
|
85
|
+
const metadata = parseSkillMetadata(skillDir);
|
|
86
|
+
|
|
87
|
+
console.log(`📦 ${skillName}`);
|
|
88
|
+
|
|
89
|
+
if (metadata) {
|
|
90
|
+
console.log(` 名称: ${metadata.name || '未知'}`);
|
|
91
|
+
console.log(` 摘要: ${metadata.summary || '未知'}`);
|
|
92
|
+
console.log(` 版本: ${metadata.version || '未知'}`);
|
|
93
|
+
console.log(` 依赖: ${metadata.bins ? metadata.bins.join(', ') : '无'}`);
|
|
94
|
+
console.log(` 描述: ${metadata.description ? metadata.description.substring(0, 60) + '...' : '未知'}`);
|
|
95
|
+
} else {
|
|
96
|
+
console.log(' ⚠️ 无法解析 SKILL.md');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
console.log('');
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 验证结果
|
|
103
|
+
console.log('📊 验证结果:\n');
|
|
104
|
+
|
|
105
|
+
const expectedSkills = ['soke-course', 'soke-exam', 'soke-shared'];
|
|
106
|
+
const missingSkills = expectedSkills.filter(s => !skillNames.includes(s));
|
|
107
|
+
const extraSkills = skillNames.filter(s => !expectedSkills.includes(s));
|
|
108
|
+
|
|
109
|
+
if (missingSkills.length > 0) {
|
|
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(', ')}`);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
console.log('');
|
|
120
|
+
console.log('🎉 测试完成!');
|
|
121
|
+
console.log('');
|
|
122
|
+
console.log('💡 提示:');
|
|
123
|
+
console.log(' - 新增 skill 时,只需在 skills/ 目录下创建 soke-* 目录');
|
|
124
|
+
console.log(' - 确保每个 skill 都有 SKILL.md 文件,包含完整的 frontmatter');
|
|
125
|
+
console.log(' - install.js 会自动检测并注册所有 skills');
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: soke-learning-profile
|
|
3
|
+
summary: 学员学习档案查询
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: "学员学习档案查询:查询学员的完整学习记录,包括课程学习、考试成绩、证书获取、学分积分等。当用户需要查询学员学习档案、查看学员学习情况、统计学员学习数据、查询学员综合学习信息时使用。"
|
|
6
|
+
metadata:
|
|
7
|
+
requires:
|
|
8
|
+
bins: ["soke-cli"]
|
|
9
|
+
cliHelp: "soke-cli learning-profile --help"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
**CRITICAL — 开始前 MUST 先用 Read 工具读取 [`../soke-shared/SKILL.md`](../soke-shared/SKILL.md),其中包含认证、配置、权限处理**
|
|
13
|
+
|
|
14
|
+
## 核心概念
|
|
15
|
+
|
|
16
|
+
- **LearningProfile(学习档案)**: 学员的完整学习记录,包含课程、考试、证书、培训等多维度数据
|
|
17
|
+
- **DeptUser(部门用户)**: 企业内的学员,通过 `dept_user_id` 标识
|
|
18
|
+
- **Department(部门)**: 组织架构单元,通过 `dept_id` 标识
|
|
19
|
+
|
|
20
|
+
## 资源关系
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
LearningProfile (学习档案)
|
|
24
|
+
├── DeptUser (学员)
|
|
25
|
+
│ ├── dept_user_id (学员ID)
|
|
26
|
+
│ ├── dept_user_name (学员姓名)
|
|
27
|
+
│ └── dept_names (所属部门)
|
|
28
|
+
├── CourseData (课程数据)
|
|
29
|
+
│ ├── required_learning/finished (必修课程)
|
|
30
|
+
│ ├── optional_learning/finished (选修课程)
|
|
31
|
+
│ └── learn_time (学习时长)
|
|
32
|
+
├── ExamData (考试数据)
|
|
33
|
+
│ ├── not_attempt (未开始)
|
|
34
|
+
│ ├── passed (通过)
|
|
35
|
+
│ ├── notpassed (未通过)
|
|
36
|
+
│ └── reviewing (批阅中)
|
|
37
|
+
├── CertificateData (证书数据)
|
|
38
|
+
│ └── certificate_number (证书数量)
|
|
39
|
+
├── PointsAndCredits (积分学分)
|
|
40
|
+
│ ├── points (积分)
|
|
41
|
+
│ └── credits (学分)
|
|
42
|
+
└── TrainingData (培训数据)
|
|
43
|
+
├── training_finished (线下培训)
|
|
44
|
+
├── learning_map_finished (学习地图)
|
|
45
|
+
└── training_class_finished (培训班)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Shortcuts(推荐优先使用)
|
|
49
|
+
|
|
50
|
+
| Shortcut | 说明 |
|
|
51
|
+
|----------|------|
|
|
52
|
+
| [`+list`](#list) | 查询学员学习档案列表 |
|
|
53
|
+
|
|
54
|
+
## 辅助命令(contact 模块)
|
|
55
|
+
|
|
56
|
+
| Shortcut | 说明 |
|
|
57
|
+
|----------|------|
|
|
58
|
+
| `contact +search-user` | 根据姓名搜索学员 |
|
|
59
|
+
| `contact +search-dept` | 根据名称搜索部门 |
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 命令详解
|
|
64
|
+
|
|
65
|
+
### +list
|
|
66
|
+
|
|
67
|
+
查询学员学习档案列表,支持按部门、学员ID筛选。
|
|
68
|
+
|
|
69
|
+
**命令格式**:
|
|
70
|
+
```bash
|
|
71
|
+
soke-cli learning-profile +list \
|
|
72
|
+
[--dept-user-ids <user_id1,user_id2,...>] \
|
|
73
|
+
[--dept-ids <dept_id1,dept_id2,...>] \
|
|
74
|
+
[--is-new <0|1>] \
|
|
75
|
+
[--offset <offset>] \
|
|
76
|
+
[--page-size <size>]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
**参数说明**:
|
|
80
|
+
- `--dept-user-ids`: 学员ID列表,多个ID用逗号分隔(可选)
|
|
81
|
+
- `--dept-ids`: 部门ID列表,多个ID用逗号分隔(可选)
|
|
82
|
+
- `--is-new`: 是否新员工,0-否,1-是(可选)
|
|
83
|
+
- `--offset`: 偏移量,默认从0开始(可选)
|
|
84
|
+
- `--page-size`: 每页条数,最大100,默认10(可选)
|
|
85
|
+
|
|
86
|
+
**返回字段**(表格显示关键字段):
|
|
87
|
+
- `姓名`: 学员姓名
|
|
88
|
+
- `部门`: 所属部门
|
|
89
|
+
- `职位`: 职位
|
|
90
|
+
- `必修完成`: 必修课程完成情况(完成数/总数)
|
|
91
|
+
- `选修完成`: 选修课程完成情况(完成数/总数)
|
|
92
|
+
- `考试通过`: 考试通过数
|
|
93
|
+
- `学习时长`: 学习时长(格式化为"X小时Y分钟")
|
|
94
|
+
- `证书数`: 获得证书数量
|
|
95
|
+
- `学分`: 学分
|
|
96
|
+
- `积分`: 积分
|
|
97
|
+
|
|
98
|
+
**JSON 输出包含完整字段**(30+ 字段):
|
|
99
|
+
- 基础信息:`dept_user_id`, `dept_user_name`, `dept_names`, `position`, `job_number`, `avatar`, `is_leave`, `hired_date`
|
|
100
|
+
- 课程学习:`optional_learning`, `optional_finished`, `required_learning`, `required_finished`, `learn_time`
|
|
101
|
+
- 考试情况:`not_attempt`, `passed`, `notpassed`, `reviewing`
|
|
102
|
+
- 证书积分:`certificate_number`, `points`, `credits`
|
|
103
|
+
- 培训情况:`training_not_attempt`, `training_finished`, `class_length`
|
|
104
|
+
- 学习地图:`leaning_map_attempt`, `learning_map_finished`
|
|
105
|
+
- 培训班:`training_class_attempt`, `training_class_finished`
|
|
106
|
+
- 其他:`live_learn_time`, `external_training_time`, `knowledge_number`, `evaluation_score`
|
|
107
|
+
|
|
108
|
+
**示例**:
|
|
109
|
+
```bash
|
|
110
|
+
# 查询所有学员学习档案
|
|
111
|
+
soke-cli learning-profile +list --offset 0 --page-size 20
|
|
112
|
+
|
|
113
|
+
# 查询特定学员(单个)
|
|
114
|
+
soke-cli learning-profile +list --dept-user-ids user123
|
|
115
|
+
|
|
116
|
+
# 查询多个学员
|
|
117
|
+
soke-cli learning-profile +list --dept-user-ids user123,user456,user789
|
|
118
|
+
|
|
119
|
+
# 查询特定部门的学员
|
|
120
|
+
soke-cli learning-profile +list --dept-ids dept456
|
|
121
|
+
|
|
122
|
+
# 查询多个部门的学员
|
|
123
|
+
soke-cli learning-profile +list --dept-ids dept456,dept789
|
|
124
|
+
|
|
125
|
+
# 查询新员工的学习档案
|
|
126
|
+
soke-cli learning-profile +list --is-new 1 --page-size 50
|
|
127
|
+
|
|
128
|
+
# 分页查询(使用 offset)
|
|
129
|
+
soke-cli learning-profile +list --offset 0 --page-size 10 # 第1页
|
|
130
|
+
soke-cli learning-profile +list --offset 10 --page-size 10 # 第2页
|
|
131
|
+
soke-cli learning-profile +list --offset 20 --page-size 10 # 第3页
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
**权限要求**: `learningProfile:readonly`
|
|
135
|
+
|
|
136
|
+
**使用场景**:
|
|
137
|
+
- 当用户询问"查询学员学习档案"时使用
|
|
138
|
+
- 当用户需要查看学员学习情况、统计学习数据时使用
|
|
139
|
+
- 当用户需要导出学员学习报表时使用
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## 权限表
|
|
144
|
+
|
|
145
|
+
| 命令 | 所需权限 | 说明 |
|
|
146
|
+
|------|----------|------|
|
|
147
|
+
| `+list` | `learningProfile:readonly` | 查询学员学习档案 |
|
|
148
|
+
| `contact +search-user` | `contact:user:readonly` | 搜索学员 |
|
|
149
|
+
| `contact +search-dept` | `contact:department:readonly` | 搜索部门 |
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## 常见工作流
|
|
154
|
+
|
|
155
|
+
### 工作流1: 根据姓名查询学员学习档案
|
|
156
|
+
|
|
157
|
+
当用户询问"查询张三的学习档案"时:
|
|
158
|
+
|
|
159
|
+
**步骤1**: 搜索学员获取 dept_user_id
|
|
160
|
+
```bash
|
|
161
|
+
soke-cli contact +search-user --dept-user-name "张三"
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**步骤2**: 使用 dept_user_id 查询学习档案
|
|
165
|
+
```bash
|
|
166
|
+
soke-cli learning-profile +list --dept-user-ids <从步骤1获取的dept_user_id>
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**注意**: 学习档案接口不支持按姓名直接查询,必须先通过 contact 模块获取 dept_user_id。
|
|
170
|
+
|
|
171
|
+
### 工作流2: 查询部门所有学员的学习档案
|
|
172
|
+
|
|
173
|
+
当用户询问"查询技术部所有学员的学习情况"时:
|
|
174
|
+
|
|
175
|
+
**步骤1**: 搜索部门获取 dept_id
|
|
176
|
+
```bash
|
|
177
|
+
soke-cli contact +search-dept --dept-name "技术部"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**步骤2**: 使用 dept_id 查询该部门学员学习档案
|
|
181
|
+
```bash
|
|
182
|
+
soke-cli learning-profile +list --dept-ids <从步骤1获取的dept_id> --page-size 100
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### 工作流3: 批量导出学习档案
|
|
186
|
+
|
|
187
|
+
当用户需要导出学习档案数据时:
|
|
188
|
+
|
|
189
|
+
```bash
|
|
190
|
+
# 使用 JSON 格式输出并保存到文件
|
|
191
|
+
soke-cli learning-profile +list --page-size 100 --format json > learning_profiles.json
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## 注意事项
|
|
197
|
+
|
|
198
|
+
1. **时间格式**: `learn_time`, `class_length` 等时长字段单位为秒,表格输出时会自动格式化为"X小时Y分钟"
|
|
199
|
+
2. **分页**: 使用 `offset` 和 `page_size` 进行分页,默认每页10条,最大100条
|
|
200
|
+
3. **数组参数**: `dept_user_ids` 和 `dept_ids` 是数组类型,多个ID用逗号分隔
|
|
201
|
+
4. **姓名查询**: 学习档案接口不支持按姓名查询,需要先通过 `contact +search-user` 获取 dept_user_id
|
|
202
|
+
5. **权限**: 需要 `learningProfile:readonly` 权限,如遇权限错误参考 soke-shared
|
|
203
|
+
6. **数据完整性**: 表格输出仅显示关键字段,完整数据请使用 `--format json`
|
|
204
|
+
|
|
205
|
+
---
|
|
206
|
+
|
|
207
|
+
## 错误处理
|
|
208
|
+
|
|
209
|
+
### 权限不足
|
|
210
|
+
如果遇到权限错误,参考 [`../soke-shared/SKILL.md`](../soke-shared/SKILL.md)
|
|
211
|
+
|
|
212
|
+
### 参数错误
|
|
213
|
+
使用 `--help` 查看命令参数说明:
|
|
214
|
+
```bash
|
|
215
|
+
soke-cli learning-profile +list --help
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
### 数据为空
|
|
219
|
+
如果查询结果为空,检查:
|
|
220
|
+
1. 筛选条件是否正确(dept_ids, dept_user_ids, is_new)
|
|
221
|
+
2. 学员是否有学习记录
|
|
222
|
+
3. 是否有权限查看该部门/学员的数据
|
|
223
|
+
4. 注意:学习档案接口不支持按姓名查询,需要先获取 dept_user_id
|