@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.
Files changed (34) hide show
  1. package/README.md +1 -24
  2. package/package.json +2 -2
  3. package/scripts/build-binaries.sh +1 -30
  4. package/scripts/e2e-test.sh +63 -3
  5. package/scripts/install.js +52 -110
  6. package/scripts/local-test.js +10 -85
  7. package/scripts/local-test.sh +52 -7
  8. package/scripts/push.sh +12 -4
  9. package/scripts/release.sh +45 -8
  10. package/scripts/test-auto-detect.js +71 -48
  11. package/skills/README.md +0 -1
  12. package/skills/oss-upload/skill.md +50 -0
  13. package/skills/oss-upload/upload.py +355 -0
  14. package/skills/soke-assign/README.md +310 -0
  15. package/skills/soke-assign/SKILL.md +383 -0
  16. package/skills/soke-course/README.md +168 -92
  17. package/skills/soke-course/SKILL.md +372 -460
  18. package/skills/soke-exam/SKILL.md +565 -0
  19. package/skills/soke-exam/references/exam-get-exam-user.md +212 -0
  20. package/skills/soke-learning-map/SKILL.md +544 -0
  21. package/skills/soke-learning-profile/SKILL.md +0 -1
  22. package/skills/soke-lesson/README.md +53 -0
  23. package/skills/soke-lesson/SKILL.md +510 -0
  24. package/skills/soke-material/README.md +49 -0
  25. package/skills/soke-material/SKILL.md +390 -0
  26. package/scripts/ci/check-legacy-constants.sh +0 -27
  27. package/scripts/ci/check-yaml-env-whitelist.sh +0 -33
  28. package/scripts/mcp-stdout-scan.sh +0 -46
  29. package/scripts/regress-auth.sh +0 -56
  30. package/scripts/regress-baseline.md +0 -84
  31. package/scripts/test-local-reconcile.js +0 -55
  32. package/skills/soke-business-training-report/SKILL.md +0 -58
  33. package/skills/soke-course/SUMMARY.md +0 -236
  34. package/skills/soke-course/templates/batch-create-from-excel.md +0 -75
package/README.md CHANGED
@@ -27,7 +27,7 @@
27
27
  3. **业务模块快捷命令**
28
28
  - 针对授客AI核心业务场景提供了丰富的快捷命令集:
29
29
  - **通讯录 (contact)**: 部门与用户查询、管理、搜索等。
30
- - **课程 (course)**: 课程列表、分类、学习记录、人脸识别记录等;含 `+batch-create-from-excel` 从 Excel 批量创建课程并上传课件(ffmpeg 可选,系统已装则启用视频抽帧封面,未装则降级到默认封面)。
30
+ - **课程 (course)**: 课程列表、分类、学习记录、人脸识别记录等。
31
31
  - **考试 (exam)**: 考试列表、分类、考试成绩与记录查询。
32
32
  - **学习档案 (learning-profile)**: 学员学习档案查询、学习情况统计。
33
33
  - **学习地图 (learning-map)**: 学习地图、阶段、任务查询与分配。
@@ -39,7 +39,6 @@
39
39
  4. **AI Agent Skills**
40
40
  - 提供 AI Agent 技能(Skills),使 AI 助手能够自动发现和调用 CLI 功能。
41
41
  - 支持自然语言交互,无需记忆复杂的命令参数。
42
- - 支持维护者删除受管 Skill,并通过本地同步与升级安装自动清理废弃 Skill。
43
42
 
44
43
  ## 安装方法
45
44
 
@@ -165,28 +164,6 @@ soke-cli api GET /users/me
165
164
  soke-cli api POST /some/endpoint --data '{"key": "value"}'
166
165
  ```
167
166
 
168
- ### 删除受管 Skill(维护者)
169
-
170
- 如果你需要从当前仓库中下线一个受管 Skill,可以执行:
171
-
172
- ```bash
173
- soke-cli delete-skill soke-business-training-report
174
- ```
175
-
176
- 删除后再执行:
177
-
178
- ```bash
179
- bash ./scripts/local-test.sh
180
- ```
181
-
182
- 本地 Agent 中由 `soke-cli` 管理的过期 `soke-*` Skills 会被自动清理。客户后续执行:
183
-
184
- ```bash
185
- npm install -g @sokeai/cli@latest
186
- ```
187
-
188
- 也会按当前发布包中的 Skill 集合自动清理废弃的受管 Skill。
189
-
190
167
  ## 开发指南
191
168
 
192
169
  ### 从接口封装到发布的完整流程
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sokeai/cli",
3
- "version": "1.0.39",
3
+ "version": "1.0.53",
4
4
  "description": "授客AI官方CLI工具 - 支持AI Agent Skills",
5
5
  "bin": {
6
6
  "soke-cli": "scripts/run.js"
@@ -16,7 +16,7 @@
16
16
  ],
17
17
  "repository": {
18
18
  "type": "git",
19
- "url": "https://github.com/liuchenlong1111/soke-cli.git"
19
+ "url": "https://github.com/sokeai/soke-cli.git"
20
20
  },
21
21
  "keywords": [
22
22
  "soke",
@@ -15,37 +15,8 @@ NC='\033[0m' # No Color
15
15
  VERSION=$(node -p "require('./package.json').version")
16
16
  echo -e "${GREEN}开始编译 soke-cli v${VERSION}${NC}"
17
17
 
18
- # task 11.2 — OAuth 凭证通过 env 注入(替代原 sed 改 Go 源的方式)。
19
- #
20
- # 重要:vendor.yaml 的 ${SOKE_CLIENT_ID} / ${SOKE_CLIENT_SECRET} 由
21
- # sokeai/provider.go 在 init() 阶段 Interpolate,**取的是产物运行时的 env**,
22
- # 而不是 build 期。因此:
23
- #
24
- # * 本地开发:用户在 shell 里 export SOKE_CLIENT_ID / SOKE_CLIENT_SECRET,
25
- # 然后 go run / 已 build 的 ./bin/soke-cli 启动时 init() 取到。
26
- # * Release 二进制:client_id / client_secret 不在 build 阶段嵌入产物。
27
- # 发版 CI 应当在制作 NPM 包时附带一个 wrapper(scripts/run.js)在启动
28
- # soke-cli 前 setenv 注入(具体方式由 release-lead 在发版 CI 里定),
29
- # 或要求最终用户自己 export(仅适用于内部分发场景)。
30
- #
31
- # 本脚本只做 sanity check + 把当前 shell env 透传到 go build(用于自动化
32
- # 测试场景中,build 后立即跑 ./bin/soke-cli auth login 走流程)。
33
- if [ -z "${SOKE_CLIENT_ID:-}" ] || [ -z "${SOKE_CLIENT_SECRET:-}" ]; then
34
- if [ "${SOKE_CLI_RELEASE:-}" = "1" ]; then
35
- echo -e "${RED}[FATAL] release 模式必须设置 SOKE_CLIENT_ID / SOKE_CLIENT_SECRET${NC}"
36
- echo -e "${RED} 二进制本身不嵌入凭证,run.js wrapper 也需要相应的注入逻辑${NC}"
37
- exit 1
38
- fi
39
- echo -e "${YELLOW}[警告] SOKE_CLIENT_ID / SOKE_CLIENT_SECRET 未设置;${NC}"
40
- echo -e "${YELLOW} 本地 build 后直接 ./bin/soke-cli auth login 会 fail-fast。${NC}"
41
- fi
42
- export SOKE_CLIENT_ID SOKE_CLIENT_SECRET
43
-
44
18
  # 编译参数
45
- # -X 注入路径必须匹配 internal/version.Version 所在包,
46
- # 此前误写为 main.Version,导致 --version 始终落回 checker.go 里
47
- # 的 hardcoded 默认值。
48
- LDFLAGS="-s -w -X codeup.aliyun.com/5edbc121d1d1abe63b55f1c7/soke/soke-cli/internal/version.Version=${VERSION}"
19
+ LDFLAGS="-s -w -X main.Version=${VERSION}"
49
20
 
50
21
  # 创建输出目录
51
22
  BIN_DIR="bin"
@@ -19,6 +19,10 @@ PASSED_TESTS=0
19
19
  FAILED_TESTS=0
20
20
  FAILED_COMMANDS=()
21
21
 
22
+ # 动态时间范围:最近7天(毫秒时间戳)
23
+ NOW_MS=$(date +%s)000
24
+ SEVEN_DAYS_AGO_MS=$(( ($(date +%s) - 6 * 86400) ))000
25
+
22
26
  # 获取版本号
23
27
  VERSION=$(node -p "require('./package.json').version")
24
28
 
@@ -152,8 +156,64 @@ fi
152
156
  # ==================== Exam 模块 ====================
153
157
  if should_test_module "exam"; then
154
158
  echo -e "${YELLOW}[Exam 模块]${NC}"
155
- test_command "exam" "+list-exams" "--start-time 1672502400000 --end-time 1704038400000" "获取考试列表"
159
+ test_command "exam" "+list-exams" "--page 1 --page-size 10" "获取考试列表"
156
160
  test_command "exam" "+list-categories" "" "获取考试分类"
161
+ test_command "exam" "+list-pools" "--status 1" "获取题库列表"
162
+ test_command "exam" "+list-pool-categories" "" "获取题库分类"
163
+ test_command "exam" "+list-papers" "--status 1" "获取试卷列表"
164
+ echo ""
165
+ fi
166
+
167
+ # ==================== Exam Write 模块 (写操作) ====================
168
+ if should_test_module "exam-write"; then
169
+ echo -e "${YELLOW}[Exam Write 模块 - 创建不定项选择题]${NC}"
170
+
171
+ # 1. 创建题库
172
+ POOL_TITLE="E2E不定项选择题库_$(date +%s)"
173
+ echo -n "测试 [exam +create-pool]: 创建题库 ($POOL_TITLE) ... "
174
+ CREATE_RESULT=$(./soke-cli exam +create-pool \
175
+ --title "$POOL_TITLE" \
176
+ --path '["C74A6931-3DEC-4F50-80A2-ECB3FC59127F"]' \
177
+ --format json 2>/dev/null)
178
+ POOL_UUID=$(echo "$CREATE_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('uuid',''))" 2>/dev/null)
179
+
180
+ if [ -n "$POOL_UUID" ]; then
181
+ echo -e "${GREEN}✓ 通过${NC} (uuid: $POOL_UUID)"
182
+ PASSED_TESTS=$((PASSED_TESTS + 1))
183
+ else
184
+ echo -e "${RED}✗ 失败${NC}"
185
+ FAILED_TESTS=$((FAILED_TESTS + 1))
186
+ FAILED_COMMANDS+=("exam +create-pool --title $POOL_TITLE")
187
+ fi
188
+ TOTAL_TESTS=$((TOTAL_TESTS + 1))
189
+
190
+ # 2. 创建不定项选择题
191
+ if [ -n "$POOL_UUID" ]; then
192
+ echo -n "测试 [exam +create-question]: 创建不定项选择题 ... "
193
+ QUESTION_RESULT=$(./soke-cli exam +create-question \
194
+ --title "以下哪些是Go语言的基本数据类型?" \
195
+ --type uncertain_choice \
196
+ --score 5 \
197
+ --difficulty medium \
198
+ --pool-id "$POOL_UUID" \
199
+ --choices '[{"value":"int","key":"A"},{"value":"float64","key":"B"},{"value":"string","key":"C"},{"value":"char","key":"D"}]' \
200
+ --answer '["0","1","2"]' \
201
+ --analysis "Go的基本数据类型包括int、float64、string等,没有char类型(使用rune和byte)" \
202
+ --format json 2>/dev/null)
203
+ QUESTION_CODE=$(echo "$QUESTION_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('code',''))" 2>/dev/null)
204
+ QUESTION_UUID=$(echo "$QUESTION_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('data',{}).get('uuid',''))" 2>/dev/null)
205
+
206
+ if [ "$QUESTION_CODE" = "200" ] && [ -n "$QUESTION_UUID" ]; then
207
+ echo -e "${GREEN}✓ 通过${NC} (uuid: $QUESTION_UUID)"
208
+ PASSED_TESTS=$((PASSED_TESTS + 1))
209
+ else
210
+ ERR_MSG=$(echo "$QUESTION_RESULT" | python3 -c "import sys,json; print(json.load(sys.stdin).get('message','')[:80])" 2>/dev/null)
211
+ echo -e "${RED}✗ 失败${NC} ($ERR_MSG)"
212
+ FAILED_TESTS=$((FAILED_TESTS + 1))
213
+ FAILED_COMMANDS+=("exam +create-question --type uncertain_choice")
214
+ fi
215
+ TOTAL_TESTS=$((TOTAL_TESTS + 1))
216
+ fi
157
217
  echo ""
158
218
  fi
159
219
 
@@ -168,14 +228,14 @@ fi
168
228
  # ==================== Credit 模块 ====================
169
229
  if should_test_module "credit"; then
170
230
  echo -e "${YELLOW}[Credit 模块]${NC}"
171
- test_command "credit" "+list-logs" "--start-time 1672502400000 --end-time 1704038400000" "获取学分日志"
231
+ test_command "credit" "+list-logs" "--start-time ${SEVEN_DAYS_AGO_MS} --end-time ${NOW_MS}" "获取学分日志"
172
232
  echo ""
173
233
  fi
174
234
 
175
235
  # ==================== Point 模块 ====================
176
236
  if should_test_module "point"; then
177
237
  echo -e "${YELLOW}[Point 模块]${NC}"
178
- test_command "point" "+list-logs" "--start-time 1672502400000 --end-time 1704038400000" "获取积分日志"
238
+ test_command "point" "+list-logs" "--start-time ${SEVEN_DAYS_AGO_MS} --end-time ${NOW_MS}" "获取积分日志"
179
239
  echo ""
180
240
  fi
181
241
 
@@ -107,46 +107,6 @@ function detectSkillNames(packagedSkillsDir) {
107
107
  }
108
108
  }
109
109
 
110
- function removeManagedSkillDir(targetDir, skillName) {
111
- const dest = path.join(targetDir, skillName);
112
- if (!fs.existsSync(dest)) return false;
113
- try {
114
- fs.rmSync(dest, { recursive: true, force: true });
115
- return true;
116
- } catch (_) {
117
- return false;
118
- }
119
- }
120
-
121
- function pruneManagedSkillDirs(targetDir, expectedSkillNames) {
122
- if (!fs.existsSync(targetDir)) return [];
123
- try {
124
- const entries = fs.readdirSync(targetDir, { withFileTypes: true });
125
- const removed = [];
126
- for (const entry of entries) {
127
- if (!entry.isDirectory() || !entry.name.startsWith('soke-')) continue;
128
- if (expectedSkillNames.includes(entry.name)) continue;
129
- if (removeManagedSkillDir(targetDir, entry.name)) {
130
- removed.push(entry.name);
131
- }
132
- }
133
- return removed;
134
- } catch (_) {
135
- return [];
136
- }
137
- }
138
-
139
- function pruneRegistrySkills(registry, expectedSkillNames) {
140
- if (!registry || !Array.isArray(registry.skills)) return 0;
141
- const before = registry.skills.length;
142
- registry.skills = registry.skills.filter((entry) => {
143
- if (!entry || typeof entry.name !== 'string') return true;
144
- if (!entry.name.startsWith('soke-')) return true;
145
- return expectedSkillNames.includes(entry.name);
146
- });
147
- return before - registry.skills.length;
148
- }
149
-
150
110
  function syncSkillsToSokeclawWorkspace() {
151
111
  const packageRoot = path.join(__dirname, '..');
152
112
  const packagedSkillsDir = path.join(packageRoot, 'skills');
@@ -158,7 +118,6 @@ function syncSkillsToSokeclawWorkspace() {
158
118
  for (const targetDir of targetDirs) {
159
119
  try {
160
120
  fs.mkdirSync(targetDir, { recursive: true });
161
- pruneManagedSkillDirs(targetDir, skillNames);
162
121
  for (const skillName of skillNames) {
163
122
  const src = path.join(packagedSkillsDir, skillName);
164
123
  const dest = path.join(targetDir, skillName);
@@ -304,9 +263,6 @@ function syncSkillsToWorkclawRegistry() {
304
263
  if (fs.existsSync(src)) copyDirRecursive(src, dest);
305
264
  }
306
265
 
307
- pruneManagedSkillDirs(workclawSkillInstallDir, skillNames);
308
- pruneRegistrySkills(registry, skillNames);
309
-
310
266
  // 自动注册所有 skills
311
267
  for (const skillName of skillNames) {
312
268
  const skillDir = path.join(packagedSkillsDir, skillName);
@@ -462,19 +418,29 @@ const archMap = {
462
418
  const mappedPlatform = platformMap[platform];
463
419
  const mappedArch = archMap[arch];
464
420
 
421
+ if (!mappedPlatform || !mappedArch) {
422
+ console.error(`不支持的平台: ${platform}-${arch}`);
423
+ console.error('支持的平台: darwin-x64, darwin-arm64, linux-x64, windows-x64');
424
+ process.exit(1);
425
+ }
426
+
465
427
  const binaryName = platform === 'win32' ? 'soke-cli.exe' : 'soke-cli';
466
- const binaryFileName = mappedPlatform && mappedArch
467
- ? `soke-cli-${mappedPlatform}-${mappedArch}${platform === 'win32' ? '.exe' : ''}`
468
- : '';
428
+ const binaryFileName = `soke-cli-${mappedPlatform}-${mappedArch}${platform === 'win32' ? '.exe' : ''}`;
469
429
 
470
430
  // GitHub Releases 下载地址
471
- const downloadURL = binaryFileName
472
- ? `https://github.com/liuchenlong1111/soke-cli/releases/download/v${version}/${binaryFileName}`
473
- : '';
431
+ const downloadURL = `https://github.com/liuchenlong1111/soke-cli/releases/download/v${version}/${binaryFileName}`;
474
432
 
475
433
  const binDir = path.join(__dirname, '..', 'bin');
476
434
  const binaryPath = path.join(binDir, binaryName);
477
435
 
436
+ console.log(`正在为 ${mappedPlatform}-${mappedArch} 下载 soke-cli v${version}...`);
437
+ console.log(`下载地址: ${downloadURL}`);
438
+
439
+ // 创建 bin 目录
440
+ if (!fs.existsSync(binDir)) {
441
+ fs.mkdirSync(binDir, { recursive: true });
442
+ }
443
+
478
444
  // 下载文件
479
445
  function downloadFile(url, dest) {
480
446
  return new Promise((resolve, reject) => {
@@ -525,67 +491,43 @@ function downloadFile(url, dest) {
525
491
  }
526
492
 
527
493
  // 执行下载
528
- if (require.main === module) {
529
- if (!mappedPlatform || !mappedArch) {
530
- console.error(`不支持的平台: ${platform}-${arch}`);
531
- console.error('支持的平台: darwin-x64, darwin-arm64, linux-x64, windows-x64');
532
- process.exit(1);
533
- }
534
-
535
- console.log(`正在为 ${mappedPlatform}-${mappedArch} 下载 soke-cli v${version}...`);
536
- console.log(`下载地址: ${downloadURL}`);
537
-
538
- if (!fs.existsSync(binDir)) {
539
- fs.mkdirSync(binDir, { recursive: true });
540
- }
541
-
542
- downloadFile(downloadURL, binaryPath)
543
- .then(() => {
544
- // 设置可执行权限(非 Windows 平台)
545
- if (platform !== 'win32') {
546
- try {
547
- fs.chmodSync(binaryPath, 0o755);
548
- console.log('已设置可执行权限');
549
- } catch (err) {
550
- console.error('设置可执行权限失败:', err.message);
551
- process.exit(1);
552
- }
553
- }
554
-
494
+ downloadFile(downloadURL, binaryPath)
495
+ .then(() => {
496
+ // 设置可执行权限(非 Windows 平台)
497
+ if (platform !== 'win32') {
555
498
  try {
556
- syncSkillsToSokeclawWorkspace();
557
- } catch (_) {}
499
+ fs.chmodSync(binaryPath, 0o755);
500
+ console.log('已设置可执行权限');
501
+ } catch (err) {
502
+ console.error('设置可执行权限失败:', err.message);
503
+ process.exit(1);
504
+ }
505
+ }
558
506
 
559
- try {
560
- syncSkillsToWorkclawRegistry();
561
- } catch (_) {}
507
+ try {
508
+ syncSkillsToSokeclawWorkspace();
509
+ } catch (_) {}
562
510
 
563
- return maybeAssistGuiPath();
564
- })
565
- .then(() => {
566
- console.log('soke-cli 安装成功!');
567
- console.log(`二进制文件位置: ${binaryPath}`);
568
- console.log('\n使用方法:');
569
- console.log(' soke-cli --help');
570
- console.log(' soke-cli config init');
571
- console.log(' soke-cli auth login');
572
- })
573
- .catch((err) => {
574
- console.error('\n安装失败:', err.message);
575
- console.error('\n可能的原因:');
576
- console.error('1. 网络连接问题');
577
- console.error('2. GitHub Releases 中不存在该版本的二进制文件');
578
- console.error('3. 不支持当前平台');
579
- console.error('\n请访问 https://github.com/sokeai/soke-cli/releases 手动下载');
580
- process.exit(1);
581
- });
582
- }
511
+ try {
512
+ syncSkillsToWorkclawRegistry();
513
+ } catch (_) {}
583
514
 
584
- module.exports = {
585
- detectSkillNames,
586
- parseSkillMetadata,
587
- pruneManagedSkillDirs,
588
- pruneRegistrySkills,
589
- syncSkillsToSokeclawWorkspace,
590
- syncSkillsToWorkclawRegistry
591
- };
515
+ return maybeAssistGuiPath();
516
+ })
517
+ .then(() => {
518
+ console.log('soke-cli 安装成功!');
519
+ console.log(`二进制文件位置: ${binaryPath}`);
520
+ console.log('\n使用方法:');
521
+ console.log(' soke-cli --help');
522
+ console.log(' soke-cli config init');
523
+ console.log(' soke-cli auth login');
524
+ })
525
+ .catch((err) => {
526
+ console.error('\n安装失败:', err.message);
527
+ console.error('\n可能的原因:');
528
+ console.error('1. 网络连接问题');
529
+ console.error('2. GitHub Releases 中不存在该版本的二进制文件');
530
+ console.error('3. 不支持当前平台');
531
+ console.error('\n请访问 https://github.com/sokeai/soke-cli/releases 手动下载');
532
+ process.exit(1);
533
+ });
@@ -104,46 +104,6 @@ function detectSkillNames(packagedSkillsDir) {
104
104
  }
105
105
  }
106
106
 
107
- function removeManagedSkillDir(targetDir, skillName) {
108
- const skillDir = path.join(targetDir, skillName);
109
- if (!fs.existsSync(skillDir)) return false;
110
- try {
111
- fs.rmSync(skillDir, { recursive: true, force: true });
112
- return true;
113
- } catch (_) {
114
- return false;
115
- }
116
- }
117
-
118
- function pruneManagedSkillDirs(targetDir, expectedSkillNames) {
119
- if (!fs.existsSync(targetDir)) return [];
120
- try {
121
- const entries = fs.readdirSync(targetDir, { withFileTypes: true });
122
- const removed = [];
123
- for (const entry of entries) {
124
- if (!entry.isDirectory() || !entry.name.startsWith('soke-')) continue;
125
- if (expectedSkillNames.includes(entry.name)) continue;
126
- if (removeManagedSkillDir(targetDir, entry.name)) {
127
- removed.push(entry.name);
128
- }
129
- }
130
- return removed;
131
- } catch (_) {
132
- return [];
133
- }
134
- }
135
-
136
- function pruneRegistrySkills(registry, expectedSkillNames) {
137
- if (!registry || !Array.isArray(registry.skills)) return 0;
138
- const before = registry.skills.length;
139
- registry.skills = registry.skills.filter((entry) => {
140
- if (!entry || typeof entry.name !== 'string') return true;
141
- if (!entry.name.startsWith('soke-')) return true;
142
- return expectedSkillNames.includes(entry.name);
143
- });
144
- return before - registry.skills.length;
145
- }
146
-
147
107
  /**
148
108
  * 从 SKILL.md 解析元数据
149
109
  */
@@ -266,7 +226,6 @@ function detectLocalAgentDirs() {
266
226
  * 更新 workclaw registry.json
267
227
  */
268
228
  function updateWorkclawRegistry(registryPath, skillName, metadata, skillInstallPath) {
269
- const safeMetadata = metadata && typeof metadata === 'object' ? metadata : {};
270
229
  let registry;
271
230
 
272
231
  try {
@@ -279,11 +238,11 @@ function updateWorkclawRegistry(registryPath, skillName, metadata, skillInstallP
279
238
  if (!registry.migrations) registry.migrations = {};
280
239
  if (!Array.isArray(registry.skills)) registry.skills = [];
281
240
 
282
- const displayName = safeMetadata.summary || safeMetadata.name || skillName;
283
- const description = safeMetadata.description || `${displayName} - 授客AI CLI工具`;
284
- const version = safeMetadata.version || '1.0.0';
241
+ const displayName = metadata.summary || metadata.name || skillName;
242
+ const description = metadata.description || `${displayName} - 授客AI CLI工具`;
243
+ const version = metadata.version || '1.0.0';
285
244
  const emoji = inferSkillEmoji(skillName);
286
- const requires = safeMetadata.bins ? { bins: safeMetadata.bins } : {};
245
+ const requires = metadata.bins ? { bins: metadata.bins } : {};
287
246
 
288
247
  const skillEntry = {
289
248
  id: `skill:${skillName}`,
@@ -364,26 +323,6 @@ function distributeSkill(skillName, packagedSkillsDir, targetDirs) {
364
323
  return successCount > 0;
365
324
  }
366
325
 
367
- function reconcileTarget(target, expectedSkillNames) {
368
- const removedDirs = pruneManagedSkillDirs(target.path, expectedSkillNames);
369
- if (removedDirs.length > 0) {
370
- logInfo(` 清理过期 skills: ${removedDirs.join(', ')}`);
371
- }
372
-
373
- if (target.type === 'workclaw' && target.registryPath && fs.existsSync(target.registryPath)) {
374
- try {
375
- const registry = JSON.parse(fs.readFileSync(target.registryPath, 'utf8'));
376
- const removedEntries = pruneRegistrySkills(registry, expectedSkillNames);
377
- if (removedEntries > 0) {
378
- fs.writeFileSync(target.registryPath, JSON.stringify(registry, null, 2));
379
- logInfo(` 清理 registry 过期条目: ${removedEntries}`);
380
- }
381
- } catch (err) {
382
- logError(` ✗ 清理 registry 失败: ${err.message}`);
383
- }
384
- }
385
- }
386
-
387
326
  /**
388
327
  * 清理所有本地环境中的 skills
389
328
  */
@@ -516,10 +455,6 @@ function main() {
516
455
  }
517
456
  }
518
457
 
519
- for (const target of targetDirs) {
520
- reconcileTarget(target, allSkills);
521
- }
522
-
523
458
  // 总结
524
459
  logSection('分发完成');
525
460
 
@@ -558,21 +493,11 @@ function main() {
558
493
  console.log('');
559
494
  }
560
495
 
561
- module.exports = {
562
- detectSkillNames,
563
- parseSkillMetadata,
564
- pruneManagedSkillDirs,
565
- pruneRegistrySkills,
566
- updateWorkclawRegistry
567
- };
568
-
569
496
  // 运行
570
- if (require.main === module) {
571
- try {
572
- main();
573
- } catch (err) {
574
- logError(`发生错误: ${err.message}`);
575
- console.error(err);
576
- process.exit(1);
577
- }
497
+ try {
498
+ main();
499
+ } catch (err) {
500
+ logError(`发生错误: ${err.message}`);
501
+ console.error(err);
502
+ process.exit(1);
578
503
  }
@@ -109,15 +109,59 @@ echo ""
109
109
 
110
110
  # 步骤3.5: 分发 Skills 到本地 Agent
111
111
  echo -e "${YELLOW}[3.5/4] 分发 Skills 到本地 Agent...${NC}"
112
- echo -e " 执行 scripts/local-test.js 同步并清理本地 Agent Skills..."
113
- if node ./scripts/local-test.js; then
112
+ if [ "$SKIP_GLOBAL" = false ]; then
113
+ echo -e " 执行 scripts/install.js 同步 Skills..."
114
+ # 注入特殊环境变量或者直接调用 node 执行
115
+ # 我们只想要触发 syncSkillsToSokeclawWorkspace 和 syncSkillsToWorkclawRegistry
116
+ # 但原 install.js 会尝试下载二进制文件,所以我们新建一个临时脚本或修改调用方式
117
+
118
+ # 为了安全起见,这里直接使用 node 运行一小段脚本引入 install.js 中的逻辑
119
+ cat > ./scripts/sync-skills-local.js << 'EOF'
120
+ const fs = require('fs');
121
+ const path = require('path');
122
+ const installCode = fs.readFileSync(path.join(__dirname, 'install.js'), 'utf8');
123
+
124
+ // 提取需要的函数
125
+ const syncFn1Match = installCode.match(/function syncSkillsToSokeclawWorkspace\(\) \{[\s\S]*?\n\}/);
126
+ const syncFn2Match = installCode.match(/function syncSkillsToWorkclawRegistry\(\) \{[\s\S]*?\n\}/);
127
+
128
+ // 我们更推荐直接利用已经写好的 js 模块(如果它是通过 module.exports 导出的)
129
+ // 但因为 install.js 是直接执行的脚本,我们通过简单的 node 脚本手动拷贝
130
+ const os = require('os');
131
+ function copyDirRecursive(srcDir, destDir) {
132
+ if (!fs.existsSync(srcDir)) return;
133
+ if (!fs.existsSync(destDir)) fs.mkdirSync(destDir, { recursive: true });
134
+ const entries = fs.readdirSync(srcDir, { withFileTypes: true });
135
+ for (const entry of entries) {
136
+ const srcPath = path.join(srcDir, entry.name);
137
+ const destPath = path.join(destDir, entry.name);
138
+ if (entry.isDirectory()) { copyDirRecursive(srcPath, destPath); continue; }
139
+ fs.copyFileSync(srcPath, destPath);
140
+ }
141
+ }
142
+
143
+ const homeDir = os.homedir();
144
+ const defaultSokeclawDir = path.join(homeDir, '.sokeclaw', 'openai-agents', 'workspaces', 'main', 'skills');
145
+ const packageRoot = path.join(__dirname, '..');
146
+ const packagedSkillsDir = path.join(packageRoot, 'skills');
147
+
148
+ if (fs.existsSync(packagedSkillsDir)) {
149
+ const skillNames = fs.readdirSync(packagedSkillsDir).filter(n => n.startsWith('soke-'));
150
+ if (fs.existsSync(defaultSokeclawDir)) {
151
+ for (const skillName of skillNames) {
152
+ const src = path.join(packagedSkillsDir, skillName);
153
+ const dest = path.join(defaultSokeclawDir, skillName);
154
+ copyDirRecursive(src, dest);
155
+ console.log(` ✓ 同步 ${skillName} 到 ${dest}`);
156
+ }
157
+ }
158
+ }
159
+ EOF
160
+ node ./scripts/sync-skills-local.js
161
+ rm ./scripts/sync-skills-local.js
114
162
  echo -e "${GREEN} ✓ Skills 同步完成${NC}"
115
- if [ "$SKIP_GLOBAL" = true ]; then
116
- echo -e "${YELLOW} 提示: 已跳过全局 CLI 覆盖,但本地 Agent Skills 已按当前仓库状态完成同步${NC}"
117
- fi
118
163
  else
119
- echo -e "${RED} Skills 同步失败${NC}"
120
- exit 1
164
+ echo -e "${YELLOW} 跳过全局安装,同时跳过 Skills 同步${NC}"
121
165
  fi
122
166
  echo ""
123
167
 
@@ -178,3 +222,4 @@ echo ""
178
222
  echo -e "${GREEN}========================================${NC}"
179
223
  echo -e "${GREEN} 本地测试完成! ✓${NC}"
180
224
  echo -e "${GREEN}========================================${NC}"
225
+
package/scripts/push.sh CHANGED
@@ -102,7 +102,10 @@ CURRENT_BRANCH=$(git branch --show-current)
102
102
  echo ""
103
103
  print_info "当前所在分支: ${GREEN}${CURRENT_BRANCH}${NC}"
104
104
  echo ""
105
- read -p "是否在当前分支提交代码?(y/n): " confirm
105
+ read -p "是否在当前分支提交代码?[Y/N]: " confirm
106
+
107
+ # 默认是 Y
108
+ confirm=${confirm:-Y}
106
109
 
107
110
  if [[ "$confirm" != "y" && "$confirm" != "Y" ]]; then
108
111
  print_warning "用户取消操作,脚本退出"
@@ -111,7 +114,10 @@ fi
111
114
 
112
115
  # 询问是否作为新版本发布
113
116
  echo ""
114
- read -p "本次改动是否作为新版本发布?(y/n): " is_release
117
+ read -p "本次改动是否作为新版本发布?[Y/N]: " is_release
118
+
119
+ # 默认是 N
120
+ is_release=${is_release:-N}
115
121
 
116
122
  IS_RELEASE=false
117
123
  NEW_VERSION=""
@@ -135,8 +141,10 @@ if [[ "$is_release" == "y" || "$is_release" == "Y" ]]; then
135
141
 
136
142
  if validate_version "$NEW_VERSION"; then
137
143
  if [[ "$NEW_VERSION" == "$CURRENT_VERSION" ]]; then
138
- print_warning "新版本号与当前版本号相同,是否继续?(y/n)"
144
+ print_warning "新版本号与当前版本号相同,是否继续?[Y/N]"
139
145
  read -p "> " continue_same
146
+ # 默认是 N
147
+ continue_same=${continue_same:-N}
140
148
  if [[ "$continue_same" == "y" || "$continue_same" == "Y" ]]; then
141
149
  break
142
150
  fi
@@ -192,7 +200,7 @@ git commit -m "$commit_info"
192
200
  FEATURE_BRANCH=$CURRENT_BRANCH
193
201
 
194
202
  # 目标主分支名称
195
- TARGET_BRANCH="master"
203
+ TARGET_BRANCH="soke-cli"
196
204
 
197
205
  # 切换到主分支
198
206
  print_info "切换到 $TARGET_BRANCH 分支..."