@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
package/README.md
CHANGED
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
3. **业务模块快捷命令**
|
|
28
28
|
- 针对授客AI核心业务场景提供了丰富的快捷命令集:
|
|
29
29
|
- **通讯录 (contact)**: 部门与用户查询、管理、搜索等。
|
|
30
|
-
- **课程 (course)**:
|
|
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.
|
|
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/
|
|
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
|
-
|
|
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"
|
package/scripts/e2e-test.sh
CHANGED
|
@@ -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" "--
|
|
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
|
|
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
|
|
238
|
+
test_command "point" "+list-logs" "--start-time ${SEVEN_DAYS_AGO_MS} --end-time ${NOW_MS}" "获取积分日志"
|
|
179
239
|
echo ""
|
|
180
240
|
fi
|
|
181
241
|
|
package/scripts/install.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
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
|
-
|
|
557
|
-
|
|
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
|
-
|
|
560
|
-
|
|
561
|
-
|
|
507
|
+
try {
|
|
508
|
+
syncSkillsToSokeclawWorkspace();
|
|
509
|
+
} catch (_) {}
|
|
562
510
|
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
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
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
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
|
+
});
|
package/scripts/local-test.js
CHANGED
|
@@ -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 =
|
|
283
|
-
const description =
|
|
284
|
-
const version =
|
|
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 =
|
|
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
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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
|
}
|
package/scripts/local-test.sh
CHANGED
|
@@ -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
|
-
|
|
113
|
-
|
|
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 "${
|
|
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 "是否在当前分支提交代码?
|
|
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 "本次改动是否作为新版本发布?
|
|
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 "新版本号与当前版本号相同,是否继续?
|
|
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="
|
|
203
|
+
TARGET_BRANCH="soke-cli"
|
|
196
204
|
|
|
197
205
|
# 切换到主分支
|
|
198
206
|
print_info "切换到 $TARGET_BRANCH 分支..."
|