@yunkeai/yunke-cli 2.0.6 → 3.0.0-alpha.0
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/dist/bundle.js +1 -379
- package/dist/skills/find-skills/SKILL.md +74 -0
- package/dist/skills/manifest.json +43 -0
- package/dist/skills/problem-report/SKILL.md +101 -0
- package/dist/skills/skillhub-preference/SKILL.md +39 -0
- package/dist/skills/yunke-cli-skills/SKILL.md +167 -0
- package/dist/skills/yunke-crm/SKILL.md +138 -0
- package/dist/skills/yunke-crm-guide/SKILL.md +62 -0
- package/dist/skills/yunke-crm-periodic-report/SKILL.md +126 -0
- package/dist/skills/yunke-crm-periodic-report/references/cli-call-map.md +73 -0
- package/dist/skills/yunke-crm-periodic-report/references/product-spec-snippets.md +39 -0
- package/dist/skills/yunke-crm-team-analysis/SKILL.md +75 -0
- package/dist/skills/yunke-crm-team-analysis/references/report-template.md +293 -0
- package/dist/skills/yunke-wecom/SKILL.md +162 -0
- package/package.json +17 -14
- package/scripts/postinstall.cjs +259 -256
package/scripts/postinstall.cjs
CHANGED
|
@@ -1,256 +1,259 @@
|
|
|
1
|
-
'use strict'
|
|
2
|
-
|
|
3
|
-
const { spawnSync } = require('node:child_process')
|
|
4
|
-
const { mkdirSync, writeFileSync, existsSync, appendFileSync, readFileSync, readdirSync } = require('node:fs')
|
|
5
|
-
const { join } = require('node:path')
|
|
6
|
-
const { homedir } = require('node:os')
|
|
7
|
-
|
|
8
|
-
// ── 可通过环境变量覆盖(测试用) ──────────────────────────────────────────────
|
|
9
|
-
const BUNDLE_PATH =
|
|
10
|
-
process.env.YUNKE_POSTINSTALL_BUNDLE_OVERRIDE ??
|
|
11
|
-
join(__dirname, '..', 'dist', 'bundle.js')
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
const
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
function
|
|
46
|
-
const
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
mkdirSync(CONFIG_DIR, { recursive: true })
|
|
158
|
-
writeFileSync(
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
process.stdout.write('
|
|
252
|
-
}
|
|
253
|
-
process.stdout.write(
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
process.
|
|
1
|
+
'use strict'
|
|
2
|
+
|
|
3
|
+
const { spawnSync } = require('node:child_process')
|
|
4
|
+
const { mkdirSync, writeFileSync, existsSync, appendFileSync, readFileSync, readdirSync } = require('node:fs')
|
|
5
|
+
const { join } = require('node:path')
|
|
6
|
+
const { homedir } = require('node:os')
|
|
7
|
+
|
|
8
|
+
// ── 可通过环境变量覆盖(测试用) ──────────────────────────────────────────────
|
|
9
|
+
const BUNDLE_PATH =
|
|
10
|
+
process.env.YUNKE_POSTINSTALL_BUNDLE_OVERRIDE ??
|
|
11
|
+
join(__dirname, '..', 'dist', 'bundle.js')
|
|
12
|
+
|
|
13
|
+
// 包内 skill 根目录:build-npm.ts 把 resource/<slug>/ 拷到这里并写 manifest.json
|
|
14
|
+
const SKILLS_DIR =
|
|
15
|
+
process.env.YUNKE_POSTINSTALL_SKILLS_DIR ??
|
|
16
|
+
join(__dirname, '..', 'dist', 'skills')
|
|
17
|
+
const MANIFEST_PATH = join(SKILLS_DIR, 'manifest.json')
|
|
18
|
+
|
|
19
|
+
const CONFIG_DIR =
|
|
20
|
+
process.env.YUNKE_CONFIG_DIR ??
|
|
21
|
+
join(homedir(), '.yunke')
|
|
22
|
+
|
|
23
|
+
// 与 bundle 的 paths.ts 对齐:清理目标里的 home 路径(~/.workbuddy、~/.omnicli)统一走此变量,
|
|
24
|
+
// 测试用 YUNKE_HOME_OVERRIDE 隔离,避免误删真实机器上的目录
|
|
25
|
+
const HOME = process.env.YUNKE_HOME_OVERRIDE ?? homedir()
|
|
26
|
+
|
|
27
|
+
const LOG_FILE = join(CONFIG_DIR, 'logs', 'skillshub.log')
|
|
28
|
+
|
|
29
|
+
function logLine(level, msg) {
|
|
30
|
+
try {
|
|
31
|
+
mkdirSync(join(LOG_FILE, '..'), { recursive: true })
|
|
32
|
+
appendFileSync(LOG_FILE, `${new Date().toISOString()} [${level.toUpperCase()}] ${msg}\n`, 'utf-8')
|
|
33
|
+
} catch {
|
|
34
|
+
// 日志失败忽略,不影响安装
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function fatal(msg) {
|
|
39
|
+
process.stderr.write(`Error: ${msg}\n`)
|
|
40
|
+
logLine('error', msg)
|
|
41
|
+
process.exit(1)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 跑一条 yunke-cli 命令;命令失败也 exit 0(错误在 JSON 的 success 字段),两个信号都要看。 */
|
|
45
|
+
function runYunke(args) {
|
|
46
|
+
const result = spawnSync(process.execPath, [BUNDLE_PATH, ...args], { encoding: 'utf-8' })
|
|
47
|
+
const stdout = result.stdout || ''
|
|
48
|
+
const stderr = result.stderr || ''
|
|
49
|
+
let parsed = null
|
|
50
|
+
try { parsed = JSON.parse(stdout) } catch { /* 非 JSON 输出按失败处理 */ }
|
|
51
|
+
const ok = result.status === 0 && !(parsed && parsed.success === false)
|
|
52
|
+
return { ok, status: result.status, stdout, stderr }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// === [transient] 旧 omni-cli 残留清理。确认无用户残留后可整块删除本方法+调用 ===
|
|
56
|
+
// marker 带版本号:清理逻辑有实质增强(如新增 WorkBuddy prefix 清理)时 +1,
|
|
57
|
+
// 老版本 marker 的机器下次安装会自动重跑一次新逻辑
|
|
58
|
+
const CLEANUP_MARKER_VERSION = 'v2'
|
|
59
|
+
|
|
60
|
+
// 所有旧 slug(其中 6 个与新 SKILLS 重合:先删后装,确保内容整体刷新)
|
|
61
|
+
const LEGACY_SLUGS = [
|
|
62
|
+
'find-skills', 'skillhub-preference', 'omni-cli-skills',
|
|
63
|
+
'yunke-crm', 'yunke-crm-guide', 'yunke-crm-periodic-report', 'yunke-crm-team-analysis',
|
|
64
|
+
]
|
|
65
|
+
|
|
66
|
+
function cleanupLegacyOmniCli() {
|
|
67
|
+
const MARKER = join(CONFIG_DIR, '.cleaned-omni-cli') // CONFIG_DIR=~/.yunke
|
|
68
|
+
if (existsSync(MARKER)) {
|
|
69
|
+
try {
|
|
70
|
+
if (readFileSync(MARKER, 'utf-8') === CLEANUP_MARKER_VERSION) return
|
|
71
|
+
} catch { /* 读取失败当作未清理,重跑 */ }
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// 本地 forceRm(等价 bundle 的 forceRmSync:清 NODE_OPTIONS 绕 WorkBuddy genie-safe-delete shim)
|
|
75
|
+
const forceRm = (p) => spawnSync(
|
|
76
|
+
process.execPath,
|
|
77
|
+
['-e', `require('fs').rmSync(${JSON.stringify(p)},{recursive:true,force:true})`],
|
|
78
|
+
{ env: { ...process.env, NODE_OPTIONS: '' } }
|
|
79
|
+
)
|
|
80
|
+
|
|
81
|
+
// spawnSync 失败不抛异常(只体现在 status/stderr),rmSync 失败同理;必须回读
|
|
82
|
+
// existsSync 确认真的删掉了,否则会出现"日志写了 removed 但文件还在"的静默失败
|
|
83
|
+
const forceRmChecked = (p) => {
|
|
84
|
+
if (!existsSync(p)) return true
|
|
85
|
+
forceRm(p)
|
|
86
|
+
if (existsSync(p)) {
|
|
87
|
+
logLine('warn', `legacy omni-cli cleanup: failed to remove ${p}`)
|
|
88
|
+
return false
|
|
89
|
+
}
|
|
90
|
+
return true
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
logLine('info', 'legacy omni-cli cleanup: start')
|
|
94
|
+
let allRemoved = true
|
|
95
|
+
try {
|
|
96
|
+
// a. 卸默认 npm prefix 里的旧包(best-effort。注意:在 WorkBuddy staging 安装场景下
|
|
97
|
+
// npm_config_prefix 指向 staging 临时目录,这里等价 no-op;WB 私有 prefix 由 b' 直接删)
|
|
98
|
+
try {
|
|
99
|
+
spawnSync('npm', ['uninstall', '-g', '@yunkeai/omni-cli'], { stdio: 'inherit', shell: true })
|
|
100
|
+
logLine('info', 'legacy omni-cli cleanup: npm uninstall @yunkeai/omni-cli done')
|
|
101
|
+
} catch (e) {
|
|
102
|
+
logLine('warn', `legacy omni-cli cleanup: npm uninstall failed (best-effort): ${e}`)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// b'. WorkBuddy 私有 npm prefix 里的旧包。npm uninstall -g 只作用于默认 prefix 够不到这里,
|
|
106
|
+
// 直接删包目录 + bin shims。prefix 有两处:binaries/node/cli-connector-packages 和
|
|
107
|
+
// binaries/node/versions/*(每个 node 版本一个)。只删 omni-cli 及 npm 安装中断留下的
|
|
108
|
+
// .omni-cli-* 临时目录,绝不碰同 prefix 里正在使用的 @yunkeai/yunke-cli。
|
|
109
|
+
const WB_HOME = join(HOME, '.workbuddy')
|
|
110
|
+
if (existsSync(WB_HOME)) {
|
|
111
|
+
const wbPrefixes = [join(WB_HOME, 'binaries', 'node', 'cli-connector-packages')]
|
|
112
|
+
const wbVersions = join(WB_HOME, 'binaries', 'node', 'versions')
|
|
113
|
+
if (existsSync(wbVersions)) {
|
|
114
|
+
for (const ent of readdirSync(wbVersions, { withFileTypes: true })) {
|
|
115
|
+
if (ent.isDirectory()) wbPrefixes.push(join(wbVersions, ent.name))
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
for (const prefix of wbPrefixes) {
|
|
119
|
+
const scopeDir = join(prefix, 'node_modules', '@yunkeai')
|
|
120
|
+
if (existsSync(scopeDir)) {
|
|
121
|
+
for (const ent of readdirSync(scopeDir)) {
|
|
122
|
+
if (ent === 'omni-cli' || ent.startsWith('.omni-cli-')) {
|
|
123
|
+
if (!forceRmChecked(join(scopeDir, ent))) allRemoved = false
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
// 旧包 bin 名只有 omni-cli(package.json: {"bin":{"omni-cli":...}}),对应三个 shim 文件
|
|
128
|
+
for (const shim of ['omni-cli', 'omni-cli.cmd', 'omni-cli.ps1']) {
|
|
129
|
+
if (!forceRmChecked(join(prefix, shim))) allRemoved = false
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
// .workbuddy/skills 下的旧 skill 兜底直删:remove 命令在 WorkBuddy 宿主环境下曾被
|
|
133
|
+
// 静默劫持失败,这里不依赖命令通道,确保该目录删净(其余 agent 目录走 b 的 remove 命令)
|
|
134
|
+
for (const slug of LEGACY_SLUGS) {
|
|
135
|
+
if (!forceRmChecked(join(WB_HOME, 'skills', slug))) allRemoved = false
|
|
136
|
+
}
|
|
137
|
+
logLine('info', 'legacy omni-cli cleanup: WorkBuddy legacy package removed')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// b. 删各 agent 旧 skill(用 remove --force,复用 resolveAgentTargets 决定删哪些 agent 目录)
|
|
141
|
+
// 所有 skill 内容本次都变了,全删后由后续装载循环重装新版。
|
|
142
|
+
for (const slug of LEGACY_SLUGS) {
|
|
143
|
+
const r = runYunke(['skillshub.remove', '--slug', slug, '--force'])
|
|
144
|
+
if (r.ok) {
|
|
145
|
+
logLine('info', `legacy omni-cli cleanup: removed ${slug}`)
|
|
146
|
+
} else {
|
|
147
|
+
allRemoved = false
|
|
148
|
+
logLine('warn', `legacy omni-cli cleanup: remove ${slug} failed (exit ${r.status}); stdout=${r.stdout.slice(0, 400)}`)
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// c. 删旧状态目录 ~/.omnicli(旧 lockfile/凭证/旧 skill-cache/backups/logs 全清)
|
|
153
|
+
if (!forceRmChecked(join(HOME, '.omnicli'))) allRemoved = false
|
|
154
|
+
|
|
155
|
+
// d. 标记(全部删净才写;有残留不写,下次安装重试)
|
|
156
|
+
if (allRemoved) {
|
|
157
|
+
mkdirSync(CONFIG_DIR, { recursive: true })
|
|
158
|
+
writeFileSync(MARKER, CLEANUP_MARKER_VERSION)
|
|
159
|
+
logLine('info', `legacy omni-cli cleanup: done, marker ${CLEANUP_MARKER_VERSION} written`)
|
|
160
|
+
} else {
|
|
161
|
+
logLine('warn', 'legacy omni-cli cleanup: incomplete, marker NOT written (retry next install)')
|
|
162
|
+
}
|
|
163
|
+
} catch (e) {
|
|
164
|
+
// 整体失败不阻塞安装;不写 marker,下次重试
|
|
165
|
+
logLine('error', `legacy omni-cli cleanup: failed (will retry next install): ${e}`)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// ── 1. 包内 skill 清单是安装的前置条件(D2:缺失 = 硬失败,不回落 skillhub)────────
|
|
170
|
+
logLine('info', `postinstall start; bundle=${BUNDLE_PATH}; skills=${SKILLS_DIR}; node=${process.execPath}`)
|
|
171
|
+
|
|
172
|
+
if (!existsSync(MANIFEST_PATH)) {
|
|
173
|
+
fatal(`包内 skill 清单不存在:${MANIFEST_PATH}(安装包不完整,请重新下载)`)
|
|
174
|
+
}
|
|
175
|
+
if (!existsSync(BUNDLE_PATH)) {
|
|
176
|
+
fatal(`yunke-cli bundle 不存在:${BUNDLE_PATH}(安装包不完整,请重新下载)`)
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
let manifest
|
|
180
|
+
try {
|
|
181
|
+
manifest = JSON.parse(readFileSync(MANIFEST_PATH, 'utf-8'))
|
|
182
|
+
} catch (e) {
|
|
183
|
+
fatal(`包内 skill 清单无法解析:${MANIFEST_PATH} - ${e}`)
|
|
184
|
+
}
|
|
185
|
+
if (!Array.isArray(manifest.skills) || manifest.skills.some((s) => !s || typeof s.slug !== 'string')) {
|
|
186
|
+
fatal(`包内 skill 清单格式非法:${MANIFEST_PATH}`)
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ① 靠前:先清理旧 omni-cli 残留(绝不与下面 SKILLS 装载混序;先删后装)
|
|
190
|
+
cleanupLegacyOmniCli()
|
|
191
|
+
|
|
192
|
+
// ── 2. 按 remove 清单清理老机器残留(从清单移除不会删已装副本,必须显式删)──────────
|
|
193
|
+
// 失败仅告警:清理是尽力而为,不能因此让用户装不上新版。
|
|
194
|
+
for (const slug of Array.isArray(manifest.remove) ? manifest.remove : []) {
|
|
195
|
+
const r = runYunke(['skillshub.remove', '--slug', String(slug), '--force'])
|
|
196
|
+
if (r.ok) {
|
|
197
|
+
logLine('info', `remove list: ${slug} cleaned`)
|
|
198
|
+
} else {
|
|
199
|
+
logLine('warn', `remove list: ${slug} failed (exit ${r.status}); stdout=${r.stdout.slice(0, 400)}`)
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ── 3. 安装包内技能(多客户端分发:不传 --dir,让 yunke-cli 自行检测 AGENT_DIRS)─────
|
|
204
|
+
// 每个 skill 失败重试一次(缓解瞬时 IO/资源错误);任一最终失败 → exit 1。
|
|
205
|
+
const MAX_ATTEMPTS = 2
|
|
206
|
+
let allOk = true
|
|
207
|
+
|
|
208
|
+
for (const { slug } of manifest.skills) {
|
|
209
|
+
let ok = false
|
|
210
|
+
let last = null
|
|
211
|
+
|
|
212
|
+
for (let attempt = 1; attempt <= MAX_ATTEMPTS && !ok; attempt++) {
|
|
213
|
+
last = runYunke(['skillshub.install-bundled', '--slug', slug, '--source', SKILLS_DIR, '--force'])
|
|
214
|
+
ok = last.ok
|
|
215
|
+
if (ok) {
|
|
216
|
+
logLine('info', `${slug} installed from bundle (attempt ${attempt})`)
|
|
217
|
+
} else {
|
|
218
|
+
logLine('warn', `${slug} attempt ${attempt} failed (exit ${last.status}); stdout=${last.stdout.slice(0, 800)}; stderr=${last.stderr.slice(0, 800)}`)
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (ok) {
|
|
223
|
+
process.stdout.write(` ✅ ${slug} installed.\n`)
|
|
224
|
+
} else {
|
|
225
|
+
process.stderr.write(`Warn: failed to install ${slug}.\n`)
|
|
226
|
+
if (last && last.stderr) process.stderr.write(last.stderr)
|
|
227
|
+
logLine('error', `${slug} FAILED after ${MAX_ATTEMPTS} attempts; stdout=${last && last.stdout}; stderr=${last && last.stderr}`)
|
|
228
|
+
allOk = false
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// ── 4. 写 skillshub.json(api_base_url 来自 manifest;空串表示未配置,search 直接报错)──
|
|
233
|
+
mkdirSync(CONFIG_DIR, { recursive: true })
|
|
234
|
+
writeFileSync(
|
|
235
|
+
join(CONFIG_DIR, 'skillshub.json'),
|
|
236
|
+
JSON.stringify({
|
|
237
|
+
api_base_url: manifest.skillshub_api_base_url || '',
|
|
238
|
+
channel: 'stable',
|
|
239
|
+
target: 'generic',
|
|
240
|
+
}, null, 2),
|
|
241
|
+
'utf-8'
|
|
242
|
+
)
|
|
243
|
+
|
|
244
|
+
// ── 摘要 ─────────────────────────────────────────────────────────────────────
|
|
245
|
+
process.stdout.write('\n')
|
|
246
|
+
process.stdout.write('✅ @yunkeai/yunke-cli 安装成功!\n')
|
|
247
|
+
process.stdout.write('\n')
|
|
248
|
+
process.stdout.write('安装信息\n')
|
|
249
|
+
process.stdout.write(`可执行文件路径:${process.env.npm_config_prefix ?? homedir() + '/.local'}/bin/yunke\n`)
|
|
250
|
+
if (allOk) {
|
|
251
|
+
process.stdout.write('已自动安装包内配套技能(分发到所有已检测的 AI 客户端目录 + .agents/skills 兜底):\n')
|
|
252
|
+
for (const { slug } of manifest.skills) {
|
|
253
|
+
process.stdout.write(` ${slug}\n`)
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
process.stdout.write(`安装日志:${LOG_FILE}\n`)
|
|
257
|
+
|
|
258
|
+
logLine('info', `postinstall done; allOk=${allOk}`)
|
|
259
|
+
process.exit(allOk ? 0 : 1)
|