@sokeai/cli 1.0.39 → 1.0.53
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 +1 -24
- package/package.json +2 -2
- package/scripts/build-binaries.sh +1 -30
- package/scripts/e2e-test.sh +63 -3
- package/scripts/install.js +52 -110
- package/scripts/local-test.js +10 -85
- package/scripts/local-test.sh +52 -7
- package/scripts/push.sh +12 -4
- package/scripts/release.sh +45 -8
- package/scripts/test-auto-detect.js +71 -48
- package/skills/README.md +0 -1
- package/skills/oss-upload/skill.md +50 -0
- package/skills/oss-upload/upload.py +355 -0
- package/skills/soke-assign/README.md +310 -0
- package/skills/soke-assign/SKILL.md +383 -0
- package/skills/soke-course/README.md +168 -92
- package/skills/soke-course/SKILL.md +372 -460
- package/skills/soke-exam/SKILL.md +565 -0
- package/skills/soke-exam/references/exam-get-exam-user.md +212 -0
- package/skills/soke-learning-map/SKILL.md +544 -0
- package/skills/soke-learning-profile/SKILL.md +0 -1
- package/skills/soke-lesson/README.md +53 -0
- package/skills/soke-lesson/SKILL.md +510 -0
- package/skills/soke-material/README.md +49 -0
- package/skills/soke-material/SKILL.md +390 -0
- package/scripts/ci/check-legacy-constants.sh +0 -27
- package/scripts/ci/check-yaml-env-whitelist.sh +0 -33
- package/scripts/mcp-stdout-scan.sh +0 -46
- package/scripts/regress-auth.sh +0 -56
- package/scripts/regress-baseline.md +0 -84
- package/scripts/test-local-reconcile.js +0 -55
- package/skills/soke-business-training-report/SKILL.md +0 -58
- package/skills/soke-course/SUMMARY.md +0 -236
- package/skills/soke-course/templates/batch-create-from-excel.md +0 -75
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: soke-material
|
|
3
|
+
summary: 授客素材库管理(上传文件、创建素材、查询素材、管理分类),支持视频/音频/图片/文档等多种类型
|
|
4
|
+
version: 1.0.0
|
|
5
|
+
description: "授客素材库管理:素材库文件管理,包括文件上传到OSS、创建素材记录、查询素材列表和详情、管理素材分类。支持自动检测文件类型和设置默认时长。当用户需要上传文件、管理素材库、查询素材信息时使用。"
|
|
6
|
+
metadata:
|
|
7
|
+
requires:
|
|
8
|
+
bins: ["soke-cli"]
|
|
9
|
+
cliHelp: "soke-cli file --help"
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# 素材库管理 (material)
|
|
13
|
+
|
|
14
|
+
**CRITICAL — 开始前 MUST 先用 Read 工具读取 [`../soke-shared/SKILL.md`](../soke-shared/SKILL.md),其中包含认证、配置、权限处理**
|
|
15
|
+
|
|
16
|
+
## ⚠️ 使用前提
|
|
17
|
+
|
|
18
|
+
本 skill 基于 **soke-cli** 命令行工具,使用前必须完成以下步骤:
|
|
19
|
+
|
|
20
|
+
1. **安装 soke-cli**:确保已安装 `@sokeai/cli` NPM 包或 soke-cli 二进制文件
|
|
21
|
+
2. **用户登录授权**:运行 `soke-cli auth login` 完成 OAuth 设备码授权流程,获取用户访问令牌
|
|
22
|
+
|
|
23
|
+
**快速开始**:
|
|
24
|
+
```bash
|
|
25
|
+
|
|
26
|
+
# 1. 用户登录授权(必须)
|
|
27
|
+
soke-cli auth login
|
|
28
|
+
|
|
29
|
+
# 3. 验证登录状态
|
|
30
|
+
soke-cli config show
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
未完成登录授权将无法调用任何素材库管理相关的 API。详细的认证配置说明请参考 [soke-shared/SKILL.md](../soke-shared/SKILL.md)。
|
|
34
|
+
|
|
35
|
+
## 核心概念
|
|
36
|
+
|
|
37
|
+
- **Material(素材)**: 素材库中的文件资源,包含视频、音频、图片、文档等类型
|
|
38
|
+
- **OSS(对象存储)**: 阿里云对象存储服务,用于存储素材文件
|
|
39
|
+
- **Upload Signature(上传签名)**: OSS 上传所需的签名信息,包含 accessid、policy、signature 等
|
|
40
|
+
- **Category(素材分类)**: 素材分类,用于组织和管理素材
|
|
41
|
+
|
|
42
|
+
## 素材类型
|
|
43
|
+
|
|
44
|
+
| 类型 | 说明 | 支持的文件格式 |
|
|
45
|
+
|------|------|----------------|
|
|
46
|
+
| video | 视频文件 | mp4, avi, mov, wmv, flv, mkv, webm, m4v, mpg, mpeg, 3gp, f4v, rmvb, rm |
|
|
47
|
+
| audio | 音频文件 | mp3, wav, flac, aac, ogg, wma, m4a, ape, aiff |
|
|
48
|
+
| image | 图片文件 | jpg, jpeg, png, gif, bmp, svg, webp, ico, tiff |
|
|
49
|
+
| document | 文档文件 | pdf, ppt, pptx, doc, docx, xls, xlsx, txt 等 |
|
|
50
|
+
|
|
51
|
+
## 素材上传流程
|
|
52
|
+
|
|
53
|
+
完整的素材上传流程包括以下步骤:
|
|
54
|
+
|
|
55
|
+
1. **获取上传签名** - 调用 `/skills/uploadFile/signature` 获取 OSS 上传凭证
|
|
56
|
+
2. **上传文件到 OSS** - 使用签名信息将文件上传到阿里云 OSS
|
|
57
|
+
3. **创建素材记录** - 调用 `/skills/uploadFile/create` 在素材库中创建记录
|
|
58
|
+
|
|
59
|
+
**推荐使用**: `file +upload` 命令一键完成上述所有步骤
|
|
60
|
+
|
|
61
|
+
## 可用命令
|
|
62
|
+
|
|
63
|
+
### 素材上传与创建
|
|
64
|
+
|
|
65
|
+
#### `file +upload` - 上传文件到素材库(推荐)
|
|
66
|
+
|
|
67
|
+
一键完成文件上传到 OSS 并创建素材库记录,自动检测文件类型和设置默认时长。
|
|
68
|
+
|
|
69
|
+
**参数:**
|
|
70
|
+
- `--file` (必填): 本地文件路径
|
|
71
|
+
- `--type` (可选): 文件类型 (video/audio/image/document),默认自动检测
|
|
72
|
+
- `--length` (可选): 媒体时长(秒),默认视频600秒/音频300秒
|
|
73
|
+
- `--category-id` (可选): 分类ID
|
|
74
|
+
- `--object-name` (可选): OSS对象名称,默认使用文件名
|
|
75
|
+
|
|
76
|
+
**示例:**
|
|
77
|
+
```bash
|
|
78
|
+
# 自动检测类型和时长
|
|
79
|
+
soke-cli file +upload --file ./video.mp4
|
|
80
|
+
|
|
81
|
+
# 指定类型和时长
|
|
82
|
+
soke-cli file +upload --file ./audio.mp3 --type audio --length 236
|
|
83
|
+
|
|
84
|
+
# 指定分类
|
|
85
|
+
soke-cli file +upload --file ./document.pdf --category-id "CATEGORY-UUID"
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
**返回:**
|
|
89
|
+
- 素材 UUID
|
|
90
|
+
- 文件名
|
|
91
|
+
- 文件类型
|
|
92
|
+
- 文件大小
|
|
93
|
+
- 创建时间
|
|
94
|
+
|
|
95
|
+
#### `file +create` - 创建素材库记录
|
|
96
|
+
|
|
97
|
+
将已上传到 OSS 的文件信息保存到素材库(适用于已有 OSS 文件的场景)。
|
|
98
|
+
|
|
99
|
+
**参数:**
|
|
100
|
+
- `--filename` (必填): 文件名
|
|
101
|
+
- `--object` (必填): OSS 对象路径
|
|
102
|
+
- `--filesize` (必填): 文件大小(字节)
|
|
103
|
+
- `--ext` (必填): 文件扩展名
|
|
104
|
+
- `--type` (必填): 文件类型 (video/audio/image/document)
|
|
105
|
+
- `--length` (可选): 媒体时长(秒)
|
|
106
|
+
- `--category-id` (可选): 分类ID
|
|
107
|
+
|
|
108
|
+
**示例:**
|
|
109
|
+
```bash
|
|
110
|
+
soke-cli file +create \
|
|
111
|
+
--filename "video.mp4" \
|
|
112
|
+
--object "ding123/course/video/abc.mp4" \
|
|
113
|
+
--filesize 10485760 \
|
|
114
|
+
--ext "mp4" \
|
|
115
|
+
--type "video" \
|
|
116
|
+
--length 600
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 素材查询
|
|
120
|
+
|
|
121
|
+
#### `file +list-files` - 列出素材库文件
|
|
122
|
+
|
|
123
|
+
查询素材库文件列表,支持分页和类型筛选。
|
|
124
|
+
|
|
125
|
+
**参数:**
|
|
126
|
+
- `--page` (可选): 页码,默认 1
|
|
127
|
+
- `--limit` (可选): 每页数量,默认 10
|
|
128
|
+
- `--type` (可选): 文件类型筛选 (video/audio/image/document)
|
|
129
|
+
- `--category-id` (可选): 分类ID筛选
|
|
130
|
+
|
|
131
|
+
**示例:**
|
|
132
|
+
```bash
|
|
133
|
+
# 列出所有素材
|
|
134
|
+
soke-cli file +list-files
|
|
135
|
+
|
|
136
|
+
# 列出视频素材
|
|
137
|
+
soke-cli file +list-files --type video --page 1 --limit 20
|
|
138
|
+
|
|
139
|
+
# 按分类筛选
|
|
140
|
+
soke-cli file +list-files --category-id "CATEGORY-UUID"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
#### `file +get-info` - 获取素材详情
|
|
144
|
+
|
|
145
|
+
获取单个素材的详细信息。
|
|
146
|
+
|
|
147
|
+
**参数:**
|
|
148
|
+
- `--file-id` (必填): 素材 UUID
|
|
149
|
+
|
|
150
|
+
**示例:**
|
|
151
|
+
```bash
|
|
152
|
+
soke-cli file +get-info --file-id "6D78C63A-0633-47FB-8F55-72F6F07256A0"
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
#### `file +download` - 获取素材下载链接
|
|
156
|
+
|
|
157
|
+
获取素材文件的下载 URL。
|
|
158
|
+
|
|
159
|
+
**参数:**
|
|
160
|
+
- `--file-id` (必填): 素材 UUID
|
|
161
|
+
|
|
162
|
+
**示例:**
|
|
163
|
+
```bash
|
|
164
|
+
soke-cli file +download --file-id "6D78C63A-0633-47FB-8F55-72F6F07256A0"
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
### 分类管理
|
|
169
|
+
|
|
170
|
+
#### `file +list-categories` - 列出素材分类
|
|
171
|
+
|
|
172
|
+
查询素材分类列表。
|
|
173
|
+
|
|
174
|
+
**参数:**
|
|
175
|
+
- `--page` (可选): 页码,默认 1
|
|
176
|
+
- `--limit` (可选): 每页数量,默认 10
|
|
177
|
+
|
|
178
|
+
**示例:**
|
|
179
|
+
```bash
|
|
180
|
+
soke-cli file +list-categories
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## 使用场景
|
|
184
|
+
|
|
185
|
+
### 场景1:上传单个文件
|
|
186
|
+
|
|
187
|
+
**命令**:
|
|
188
|
+
```
|
|
189
|
+
soke-cli file +upload --file ./course-video.mp4 --format json
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
**响应示例**:
|
|
193
|
+
```json
|
|
194
|
+
{
|
|
195
|
+
"success": true,
|
|
196
|
+
"data": {
|
|
197
|
+
"uuid": "media-123456",
|
|
198
|
+
"filename": "course-video.mp4",
|
|
199
|
+
"filesize": 10485760,
|
|
200
|
+
"object": "uploads/2024/01/abc123.mp4",
|
|
201
|
+
"ext": "mp4",
|
|
202
|
+
"type": "video"
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**说明**:返回的素材 UUID 可用于创建课件
|
|
208
|
+
|
|
209
|
+
### 场景2:批量上传文件
|
|
210
|
+
|
|
211
|
+
**流程说明**:
|
|
212
|
+
1. 对每个文件执行 `file +upload` 命令
|
|
213
|
+
2. 从响应中提取 `data.uuid` 作为 `media_id`
|
|
214
|
+
3. 使用 `media_id` 创建课件
|
|
215
|
+
|
|
216
|
+
**命令序列**:
|
|
217
|
+
```
|
|
218
|
+
# 对每个文件重复以下步骤:
|
|
219
|
+
|
|
220
|
+
# 1. 上传文件
|
|
221
|
+
soke-cli file +upload --file "audio1.mp3" --type audio --format json
|
|
222
|
+
# 提取: media_id = data.uuid
|
|
223
|
+
|
|
224
|
+
# 2. 上传下一个文件
|
|
225
|
+
soke-cli file +upload --file "audio2.mp3" --type audio --format json
|
|
226
|
+
# 提取: media_id = data.uuid
|
|
227
|
+
|
|
228
|
+
# ... 重复上述步骤
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### 场景3:查询和管理素材
|
|
232
|
+
|
|
233
|
+
**查询所有视频素材**:
|
|
234
|
+
```
|
|
235
|
+
soke-cli file +list-files --type video --format json
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
**获取素材详情**:
|
|
239
|
+
```
|
|
240
|
+
soke-cli file +get-info --file-id "{file_id}" --format json
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**响应示例**:
|
|
244
|
+
```json
|
|
245
|
+
{
|
|
246
|
+
"success": true,
|
|
247
|
+
"data": {
|
|
248
|
+
"uuid": "media-123456",
|
|
249
|
+
"filename": "video.mp4",
|
|
250
|
+
"filesize": 10485760,
|
|
251
|
+
"object": "uploads/2024/01/abc123.mp4",
|
|
252
|
+
"ext": "mp4",
|
|
253
|
+
"type": "video",
|
|
254
|
+
"length": 600,
|
|
255
|
+
"category_id": "cat-789",
|
|
256
|
+
"created_at": "2024-01-01T12:00:00Z"
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## 最佳实践
|
|
262
|
+
|
|
263
|
+
### 1. 文件上传建议
|
|
264
|
+
|
|
265
|
+
**推荐使用 `file +upload`**:
|
|
266
|
+
- 一键完成 OSS 上传和素材记录创建
|
|
267
|
+
- 自动检测文件类型
|
|
268
|
+
- 自动设置默认时长
|
|
269
|
+
|
|
270
|
+
**手动流程(不推荐)**:
|
|
271
|
+
- 需要手动获取上传签名
|
|
272
|
+
- 需要手动上传到 OSS
|
|
273
|
+
- 需要手动创建素材记录
|
|
274
|
+
|
|
275
|
+
### 2. 文件类型检测
|
|
276
|
+
|
|
277
|
+
系统根据文件扩展名自动检测类型:
|
|
278
|
+
|
|
279
|
+
| 扩展名 | 检测为类型 |
|
|
280
|
+
|--------|-----------|
|
|
281
|
+
| mp4, avi, mov, wmv, flv, mkv, webm | video |
|
|
282
|
+
| mp3, wav, flac, aac, ogg, wma | audio |
|
|
283
|
+
| jpg, jpeg, png, gif, bmp, svg | image |
|
|
284
|
+
| pdf, doc, docx, ppt, pptx, xls, xlsx | document |
|
|
285
|
+
|
|
286
|
+
### 3. 默认时长设置
|
|
287
|
+
|
|
288
|
+
| 文件类型 | 默认时长 |
|
|
289
|
+
|---------|---------|
|
|
290
|
+
| video | 600秒(10分钟) |
|
|
291
|
+
| audio | 300秒(5分钟) |
|
|
292
|
+
| image | 0秒 |
|
|
293
|
+
| document | 0秒 |
|
|
294
|
+
|
|
295
|
+
**建议**:如果知道准确时长,使用 `--length` 参数指定
|
|
296
|
+
|
|
297
|
+
### 4. 素材信息提取
|
|
298
|
+
|
|
299
|
+
上传素材后,响应包含创建课件所需的所有字段:
|
|
300
|
+
|
|
301
|
+
**必需字段**:
|
|
302
|
+
- `uuid` → 用作 `media_id`
|
|
303
|
+
- `filename` → 文件名
|
|
304
|
+
- `filesize` → 文件大小(字节)
|
|
305
|
+
- `object` → OSS 对象路径
|
|
306
|
+
- `ext` → 文件扩展名
|
|
307
|
+
- `type` → 文件类型
|
|
308
|
+
|
|
309
|
+
**可选字段**:
|
|
310
|
+
- `length` → 媒体时长(视频/音频)
|
|
311
|
+
- `category_id` → 分类ID
|
|
312
|
+
|
|
313
|
+
### 5. 错误处理
|
|
314
|
+
|
|
315
|
+
**检查上传结果**:
|
|
316
|
+
- 验证 `success` 字段为 `true`
|
|
317
|
+
- 验证 `data.uuid` 不为空
|
|
318
|
+
- 如果失败,查看 `err_message` 字段
|
|
319
|
+
|
|
320
|
+
**常见问题**:
|
|
321
|
+
- 文件不存在 → 检查文件路径
|
|
322
|
+
- 文件类型不支持 → 使用支持的格式
|
|
323
|
+
- 文件大小超限 → 压缩文件(最大 1GB)
|
|
324
|
+
|
|
325
|
+
### 6. 批量操作建议
|
|
326
|
+
|
|
327
|
+
**批量上传**:
|
|
328
|
+
- 对每个文件单独调用 `file +upload`
|
|
329
|
+
- 记录每个文件的 `media_id`
|
|
330
|
+
- 可以并行上传以提高效率
|
|
331
|
+
|
|
332
|
+
**批量查询**:
|
|
333
|
+
- 使用 `--type` 参数按类型筛选
|
|
334
|
+
- 使用 `--category-id` 参数按分类筛选
|
|
335
|
+
- 使用分页参数处理大量数据
|
|
336
|
+
|
|
337
|
+
### 7. 跨平台兼容性
|
|
338
|
+
|
|
339
|
+
**文件路径**:
|
|
340
|
+
- 支持相对路径和绝对路径
|
|
341
|
+
- Windows 支持 `/` 和 `\` 两种分隔符
|
|
342
|
+
- 建议使用 `/` 保持跨平台兼容
|
|
343
|
+
|
|
344
|
+
**JSON 解析**:
|
|
345
|
+
- 使用 `--format json` 获取 JSON 输出
|
|
346
|
+
- 使用各平台的 JSON 解析工具提取字段
|
|
347
|
+
- 命令行: `jq`、PowerShell: `ConvertFrom-Json`、Python: `json.loads()`
|
|
348
|
+
|
|
349
|
+
## API 接口映射
|
|
350
|
+
|
|
351
|
+
| CLI 命令 | API 路径 | HTTP 方法 | 说明 |
|
|
352
|
+
|----------|----------|-----------|------|
|
|
353
|
+
| `file +upload` | `/skills/uploadFile/signature` + `/skills/uploadFile/create` | POST | 上传文件并创建素材 |
|
|
354
|
+
| `file +create` | `/skills/uploadFile/create` | POST | 创建素材记录 |
|
|
355
|
+
| `file +list-files` | `/skills/uploadFile/list` | GET | 列出素材列表 |
|
|
356
|
+
| `file +get-info` | `/skills/uploadFile/info` | GET | 获取素材详情 |
|
|
357
|
+
| `file +list-categories` | `/skills/uploadFile/category/list` | GET | 列出素材分类 |
|
|
358
|
+
|
|
359
|
+
## 注意事项
|
|
360
|
+
|
|
361
|
+
1. **文件大小限制**: 单个文件最大 1GB
|
|
362
|
+
2. **支持的存储类型**: aliyun(阿里云OSS)、ding(钉钉存储)
|
|
363
|
+
3. **自动类型检测**: `file +upload` 会根据文件扩展名自动检测类型
|
|
364
|
+
4. **默认时长**: 视频默认 600 秒,音频默认 300 秒,图片和文档为 0
|
|
365
|
+
5. **OSS 路径格式**: `{corpid}/{module}/{type}/{filename}`
|
|
366
|
+
|
|
367
|
+
## 权限要求
|
|
368
|
+
|
|
369
|
+
- **写操作 (上传、创建)): `file:file:write`
|
|
370
|
+
- **读操作** (查询、下载): `file:file:readonly`
|
|
371
|
+
|
|
372
|
+
## 错误处理
|
|
373
|
+
|
|
374
|
+
常见错误及解决方案:
|
|
375
|
+
|
|
376
|
+
| 错误码 | 错误信息 | 解决方案 |
|
|
377
|
+
|--------|----------|----------|
|
|
378
|
+
| 1001 | 参数错误 | 检查必填参数是否完整 |
|
|
379
|
+
| 1002 | 文件不存在 | 确认文件路径正确 |
|
|
380
|
+
| 1003 | 文件类型不支持 | 使用支持的文件格式 |
|
|
381
|
+
| 1004 | 文件大小超限 | 压缩文件或分片上传 |
|
|
382
|
+
| 2001 | 上传签名获取失败 | 检查认证信息和网络连接 |
|
|
383
|
+
| 2002 | OSS 上传失败 | 检查网络连接和 OSS 配置 |
|
|
384
|
+
| 3001 | 素材不存在 | 确认素材 UUID 正确 |
|
|
385
|
+
|
|
386
|
+
## 相关文档
|
|
387
|
+
|
|
388
|
+
- [课程管理 Skill](../soke-course/SKILL.md) - 使用素材创建课件
|
|
389
|
+
- [共享配置 Skill](../soke-shared/SKILL.md) - 认证和配置管理
|
|
390
|
+
- [API 接口文档](../../docs/API_PROXY_INTERFACES.md) - 完整的 API 接口说明
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# task 9.1 — 拦截误引入的 dws-cli upstream 旧常量。
|
|
3
|
-
#
|
|
4
|
-
# 故意提一个引入旧常量的 bad PR 时,CI 该步骤应当失败。
|
|
5
|
-
# 允许出现在 openspec/ 历史 doc 与 docs/dead-code-audit-*.md。
|
|
6
|
-
#
|
|
7
|
-
# 用法:bash scripts/ci/check-legacy-constants.sh
|
|
8
|
-
|
|
9
|
-
set -euo pipefail
|
|
10
|
-
|
|
11
|
-
PATTERN='OpenDevURL\|AuthorizeURL\|DefaultMCPBaseURL\|DefaultClientID\|DefaultClientSecret\|<YOUR_CLIENT_'
|
|
12
|
-
|
|
13
|
-
# 只扫源码目录;openspec/docs/scripts 历史档案允许出现。
|
|
14
|
-
hits=$(grep -rn -E "$PATTERN" cmd/ internal/ extension/ 2>/dev/null \
|
|
15
|
-
| grep -v vendorplugin/ \
|
|
16
|
-
| grep -v "_test.go" \
|
|
17
|
-
|| true)
|
|
18
|
-
|
|
19
|
-
if [ -n "$hits" ]; then
|
|
20
|
-
echo "[FAIL] 检测到 dws-cli 旧常量残留:"
|
|
21
|
-
echo "$hits"
|
|
22
|
-
echo
|
|
23
|
-
echo "请改用 vendor.Active().Endpoints() / OAuth() 读取(见 cmd/user_auth/vendor.go)。"
|
|
24
|
-
exit 1
|
|
25
|
-
fi
|
|
26
|
-
|
|
27
|
-
echo "[OK] 无 dws-cli 旧常量残留。"
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# task 9.2 — 校验 extension/vendorplugin/builtin/**/vendor.yaml 内 ${VAR}
|
|
3
|
-
# 占位符全部符合白名单(^SOKE_[A-Z0-9_]+$)。
|
|
4
|
-
#
|
|
5
|
-
# 故意写一个 ${HOME} 进 vendor.yaml 时,本步骤应当 FAIL。
|
|
6
|
-
#
|
|
7
|
-
# 用法:bash scripts/ci/check-yaml-env-whitelist.sh
|
|
8
|
-
|
|
9
|
-
set -euo pipefail
|
|
10
|
-
|
|
11
|
-
WHITELIST_RE='^SOKE_[A-Z0-9_]+$'
|
|
12
|
-
fail=0
|
|
13
|
-
|
|
14
|
-
for yaml in $(find extension/vendorplugin/builtin -name vendor.yaml 2>/dev/null); do
|
|
15
|
-
# 抽出所有 ${...} 占位符内层内容(去掉默认值部分)
|
|
16
|
-
vars=$(grep -oE '\$\{[^{}]+\}' "$yaml" \
|
|
17
|
-
| sed -E 's/^\$\{([^:}]+)(:-[^}]*)?\}$/\1/' \
|
|
18
|
-
| sort -u)
|
|
19
|
-
for v in $vars; do
|
|
20
|
-
if ! echo "$v" | grep -qE "$WHITELIST_RE"; then
|
|
21
|
-
echo "[FAIL] $yaml 引用了非白名单 env: $v"
|
|
22
|
-
fail=1
|
|
23
|
-
fi
|
|
24
|
-
done
|
|
25
|
-
done
|
|
26
|
-
|
|
27
|
-
if [ "$fail" -ne 0 ]; then
|
|
28
|
-
echo
|
|
29
|
-
echo "白名单规则:^SOKE_[A-Z0-9_]+\$(见 extension/vendorplugin/env_whitelist.go)"
|
|
30
|
-
exit 1
|
|
31
|
-
fi
|
|
32
|
-
|
|
33
|
-
echo "[OK] 所有 vendor.yaml 内 \${VAR} 占位符均符合 SOKE_* 白名单。"
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# mcp-stdout-scan.sh — openspec change make-soke-cli-mcp-first §10.3
|
|
3
|
-
#
|
|
4
|
-
# 静态扫描 internal/mcp/ / extension/vendor/builtin/ / internal/courseimport/ 三个
|
|
5
|
-
# 与 mcp stdio 帧纯净性相关的代码树,拦下任何会污染 stdout JSON-RPC 帧的调用:
|
|
6
|
-
# - fmt.Println / fmt.Printf / fmt.Print
|
|
7
|
-
# - fmt.Fprintln / Fprintf / Fprint to os.Stdout
|
|
8
|
-
# - os.Stdout.Write*
|
|
9
|
-
# - 顶层 print( / println( 内建函数
|
|
10
|
-
#
|
|
11
|
-
# 排除 _test.go (测试代码允许写 stdout);
|
|
12
|
-
# 通过 sed 把 `// ...` 注释剥掉再 grep,避免文档/注释里提到 fmt.Println 触发 false positive。
|
|
13
|
-
#
|
|
14
|
-
# 用法: bash scripts/mcp-stdout-scan.sh
|
|
15
|
-
# 退出码: 0 = 干净;1 = 有命中(并打到 stderr)。
|
|
16
|
-
|
|
17
|
-
set -e
|
|
18
|
-
|
|
19
|
-
ROOTS="internal/mcp extension/vendor/builtin internal/courseimport"
|
|
20
|
-
PATTERN='fmt\.(Println|Printf|Print\()|fmt\.(Fprintln|Fprintf|Fprint)\(os\.Stdout|os\.Stdout\.Write|^[[:space:]]*(print|println)\('
|
|
21
|
-
|
|
22
|
-
have_hits=0
|
|
23
|
-
for d in $ROOTS; do
|
|
24
|
-
if [ ! -d "$d" ]; then
|
|
25
|
-
echo "skip 不存在的目录 $d"
|
|
26
|
-
continue
|
|
27
|
-
fi
|
|
28
|
-
files=$(find "$d" -name '*.go' ! -name '*_test.go' 2>/dev/null)
|
|
29
|
-
for f in $files; do
|
|
30
|
-
# 把行号前缀附在文件内容上, 再用 sed 剥 // 注释 (单行注释一律剥), 再 grep
|
|
31
|
-
hits=$(awk '{ printf "%s:%d:%s\n", FILENAME, NR, $0 }' "$f" \
|
|
32
|
-
| sed 's_//.*$__' \
|
|
33
|
-
| grep -E "$PATTERN" \
|
|
34
|
-
|| true)
|
|
35
|
-
if [ -n "$hits" ]; then
|
|
36
|
-
echo "❌ $f 检测到 stdout-write 调用 (会破坏 mcp stdio JSON-RPC 帧):" >&2
|
|
37
|
-
echo "$hits" >&2
|
|
38
|
-
have_hits=1
|
|
39
|
-
fi
|
|
40
|
-
done
|
|
41
|
-
done
|
|
42
|
-
|
|
43
|
-
if [ "$have_hits" -eq 1 ]; then
|
|
44
|
-
exit 1
|
|
45
|
-
fi
|
|
46
|
-
echo "✓ stdout 纯净性扫描通过"
|
package/scripts/regress-auth.sh
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
# 回归 auth 命令体感 — task 8.1
|
|
3
|
-
#
|
|
4
|
-
# 串行跑:
|
|
5
|
-
# 1. auth status --json (预期未登录)
|
|
6
|
-
# 2. auth login (人工:浏览器跳转 + 真账号授权)
|
|
7
|
-
# 3. auth status --json (预期已登录)
|
|
8
|
-
# 4. auth logout
|
|
9
|
-
# 5. auth status --json (预期未登录)
|
|
10
|
-
#
|
|
11
|
-
# 用法:
|
|
12
|
-
# SOKE_CLIENT_ID=... SOKE_CLIENT_SECRET=... bash scripts/regress-auth.sh
|
|
13
|
-
# 首次运行前 build:make install 或 go build -o bin/soke-cli main.go
|
|
14
|
-
#
|
|
15
|
-
# 输出建议用 tee 保存,事后人工对比 baseline:
|
|
16
|
-
# bash scripts/regress-auth.sh 2>&1 | tee /tmp/regress-after.txt
|
|
17
|
-
# diff /tmp/regress-before.txt /tmp/regress-after.txt
|
|
18
|
-
|
|
19
|
-
set -u
|
|
20
|
-
|
|
21
|
-
BIN="${SOKE_CLI_BIN:-./bin/soke-cli}"
|
|
22
|
-
if [ ! -x "$BIN" ]; then
|
|
23
|
-
BIN="$(command -v soke-cli 2>/dev/null || echo "$BIN")"
|
|
24
|
-
fi
|
|
25
|
-
|
|
26
|
-
if [ ! -x "$BIN" ]; then
|
|
27
|
-
echo "[FATAL] 找不到 soke-cli 二进制(尝试 SOKE_CLI_BIN env 指定路径)" >&2
|
|
28
|
-
exit 2
|
|
29
|
-
fi
|
|
30
|
-
|
|
31
|
-
run_step() {
|
|
32
|
-
local label="$1"; shift
|
|
33
|
-
echo
|
|
34
|
-
echo "==== [$label] $* ===="
|
|
35
|
-
"$@"
|
|
36
|
-
local rc=$?
|
|
37
|
-
echo "==== [$label] exit=$rc ===="
|
|
38
|
-
return $rc
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
# baseline 时间戳 mask:让 diff 忽略 expires_at / iat / nbf / exp 等时间字段。
|
|
42
|
-
mask_timestamps() {
|
|
43
|
-
sed -E 's/("(expires_at|iat|nbf|exp|access_token|refresh_token)":\s*)"[^"]*"/\1"<masked>"/g'
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
run_step "1.before-status" "$BIN" auth status --json | mask_timestamps
|
|
47
|
-
echo
|
|
48
|
-
echo "请在浏览器完成授权后按回车继续…"
|
|
49
|
-
run_step "2.login" "$BIN" auth login
|
|
50
|
-
read -r _
|
|
51
|
-
run_step "3.after-login-status" "$BIN" auth status --json | mask_timestamps
|
|
52
|
-
run_step "4.logout" "$BIN" auth logout
|
|
53
|
-
run_step "5.final-status" "$BIN" auth status --json | mask_timestamps
|
|
54
|
-
|
|
55
|
-
echo
|
|
56
|
-
echo "[done] 完成。请人工对比 baseline diff(/tmp/regress-before.txt)。"
|
|
@@ -1,84 +0,0 @@
|
|
|
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。
|
|
@@ -1,55 +0,0 @@
|
|
|
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');
|