@lyra-ai/toolkit 0.2.6 → 0.2.8
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/QUICKSTART.md +2 -9
- package/README.md +1 -4
- package/bin/toolkit.mjs +7 -18
- package/package.json +1 -1
- package/src/config-cmd.mjs +1 -1
- package/src/install.mjs +8 -7
- package/src/skillhub.mjs +7 -552
- package/src/status.mjs +1 -1
- package/src/zentao.mjs +57 -2
package/QUICKSTART.md
CHANGED
|
@@ -131,12 +131,6 @@ skillhub install code-review --agent claude-code --agent cursor
|
|
|
131
131
|
# 查看已安装
|
|
132
132
|
skillhub list --json
|
|
133
133
|
|
|
134
|
-
# 诊断问题
|
|
135
|
-
skillhub doctor
|
|
136
|
-
|
|
137
|
-
# 更新技能包
|
|
138
|
-
skillhub update --scope user
|
|
139
|
-
|
|
140
134
|
# 移除
|
|
141
135
|
skillhub remove code-review --scope user
|
|
142
136
|
```
|
|
@@ -150,14 +144,13 @@ skillhub publish ./my-skill
|
|
|
150
144
|
## skillhub 工作流
|
|
151
145
|
|
|
152
146
|
```
|
|
153
|
-
login(认证) → search(发现) → install(安装) → list
|
|
147
|
+
login(认证) → search(发现) → install(安装) → list(管理)
|
|
154
148
|
```
|
|
155
149
|
|
|
156
150
|
- **`login`** 存储 registry token
|
|
157
151
|
- **`search`** 在 registry 搜索技能包
|
|
158
152
|
- **`install`** 下载并安装到指定 scope
|
|
159
153
|
- **`list`** 列出本地已安装技能包
|
|
160
|
-
- **`doctor`** 诊断安装问题(缺失文件、冲突等)
|
|
161
154
|
|
|
162
155
|
## skillhub Agent 集成要点
|
|
163
156
|
|
|
@@ -171,7 +164,7 @@ login(认证) → search(发现) → install(安装) → list/doctor
|
|
|
171
164
|
skillhub login --token "$SKILLHUB_TOKEN"
|
|
172
165
|
skillhub search code-review --json --limit 5
|
|
173
166
|
skillhub install code-review --scope project --agent claude-code
|
|
174
|
-
skillhub
|
|
167
|
+
skillhub list --json
|
|
175
168
|
```
|
|
176
169
|
|
|
177
170
|
## skillhub 配置参考
|
package/README.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
安装后,你的 AI 编码数据自动进入组织分析平台,生成专属的多维度效能看板。
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
toolkit install --
|
|
16
|
+
toolkit install --registry http://your-platform:38000
|
|
17
17
|
```
|
|
18
18
|
|
|
19
19
|
- 会话结束后自动同步,无需手动操作
|
|
@@ -106,7 +106,6 @@ cd <本目录> && npm link
|
|
|
106
106
|
|
|
107
107
|
AI 技能包管理器:搜索、安装、发布、管理 AI coding agent 技能包。
|
|
108
108
|
|
|
109
|
-
- `skillhub version [--json]` → 输出版本号
|
|
110
109
|
- `skillhub help [topic]` → 帮助信息(`topic` 为命令名)
|
|
111
110
|
- `skillhub login --token <token> [--registry <url>]` → 登录 registry
|
|
112
111
|
- `skillhub logout` → 清除本地凭证
|
|
@@ -115,9 +114,7 @@ AI 技能包管理器:搜索、安装、发布、管理 AI coding agent 技能
|
|
|
115
114
|
- `skillhub install <slug> [--namespace <ns>] [--agent <profile>] [--scope user|project] [--max-file-size <size>] [--json]` → 安装技能包
|
|
116
115
|
- `skillhub list [--scope user|project] [--json]` → 列出已安装技能包
|
|
117
116
|
- `skillhub remove <slug> [--scope user|project] [--agent <profile>] [--json]` → 移除技能包
|
|
118
|
-
- `skillhub doctor [--fix] [--json]` → 诊断技能包安装状态
|
|
119
117
|
- `skillhub publish <path> [--json]` → 发布技能包到 registry
|
|
120
|
-
- `skillhub update [--scope user|project] [--agent <profile>] [--json]` → 更新已安装技能包
|
|
121
118
|
|
|
122
119
|
### Agent profiles(`--agent`)
|
|
123
120
|
|
package/bin/toolkit.mjs
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* toolkit — 研发工具集 CLI 入口
|
|
4
4
|
*
|
|
5
5
|
* 用法:
|
|
6
|
-
* toolkit install
|
|
6
|
+
* toolkit install --registry <url> 安装插件(自动检测 AI Coding Agent)
|
|
7
7
|
* toolkit status 查看状态
|
|
8
8
|
* toolkit flush 手动上传
|
|
9
9
|
* toolkit uninstall [--purge] 卸载
|
|
@@ -23,8 +23,8 @@ const cmd = args[0];
|
|
|
23
23
|
const HELP = `toolkit — 研发工具集
|
|
24
24
|
|
|
25
25
|
命令:
|
|
26
|
-
install --
|
|
27
|
-
|
|
26
|
+
install --registry <url> 安装插件(自动检测本机 AI Coding Agent)
|
|
27
|
+
当前支持: Claude Code (~/.claude)
|
|
28
28
|
status 查看安装状态 + 待同步会话
|
|
29
29
|
flush 手动触发一次同步
|
|
30
30
|
config 配置管理
|
|
@@ -44,29 +44,18 @@ async function main() {
|
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
|
-
// toolkit install --
|
|
47
|
+
// toolkit install --registry <url>(自动检测 AI Coding Agent)
|
|
48
48
|
if (cmd === 'install') {
|
|
49
|
-
const agentIdx = args.indexOf('--agent');
|
|
50
|
-
const agent = agentIdx >= 0 ? args[agentIdx + 1] : null;
|
|
51
|
-
if (!agent) {
|
|
52
|
-
console.error('ERROR: 缺少 --agent <name>');
|
|
53
|
-
console.error(' 用法: toolkit install --agent claudecode --registry <url>');
|
|
54
|
-
console.error(' 支持: claudecode');
|
|
55
|
-
process.exit(2);
|
|
56
|
-
}
|
|
57
|
-
if (agent !== 'claudecode') {
|
|
58
|
-
console.error(`ERROR: 暂不支持 agent "${agent}"。目前支持: claudecode`);
|
|
59
|
-
process.exit(2);
|
|
60
|
-
}
|
|
61
49
|
const regIdx = args.indexOf('--registry');
|
|
62
50
|
let registry = regIdx >= 0 ? args[regIdx + 1] : null;
|
|
63
51
|
if (!registry) {
|
|
64
52
|
console.error('ERROR: 缺少 --registry <url>');
|
|
65
|
-
console.error(' 用法: toolkit install --
|
|
53
|
+
console.error(' 用法: toolkit install --registry <url>');
|
|
54
|
+
console.error(' (自动检测本机 AI Coding Agent,当前支持: Claude Code)');
|
|
66
55
|
process.exit(2);
|
|
67
56
|
}
|
|
68
57
|
const { install } = await import(pathToFileURL(path.join(srcDir, 'install.mjs')).href);
|
|
69
|
-
await install(registry
|
|
58
|
+
await install(registry);
|
|
70
59
|
return;
|
|
71
60
|
}
|
|
72
61
|
|
package/package.json
CHANGED
package/src/config-cmd.mjs
CHANGED
|
@@ -27,7 +27,7 @@ export function configCmd(args) {
|
|
|
27
27
|
if (!sub || sub === 'list' || sub === 'help' || sub === '--help') {
|
|
28
28
|
const config = load();
|
|
29
29
|
if (!config.registry && !config.uid) {
|
|
30
|
-
console.error('配置文件不存在。先运行: toolkit install
|
|
30
|
+
console.error('配置文件不存在。先运行: toolkit install --registry <url>');
|
|
31
31
|
process.exit(1);
|
|
32
32
|
}
|
|
33
33
|
const pf = config['project-filter'];
|
package/src/install.mjs
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* install.mjs — `toolkit install --
|
|
3
|
+
* install.mjs — `toolkit install --registry <url>`
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* 自动检测本机 AI Coding Agent:目前识别 Claude Code(~/.claude),
|
|
6
|
+
* 命中即用其官方「Skills 目录 plugin」机制(@skills-dir)注册:
|
|
6
7
|
* ~/.claude/skills/toolkit/ ← 含 .claude-plugin/plugin.json + hooks/hooks.json
|
|
7
8
|
* + hook.mjs + flush.mjs + shared/
|
|
8
9
|
*
|
|
@@ -75,13 +76,13 @@ function cleanupLegacy() {
|
|
|
75
76
|
return cleaned;
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
export async function install(registry
|
|
79
|
-
|
|
80
|
-
//
|
|
81
|
-
// (后续 --agent qoder 时,检测 ~/.qoder)
|
|
79
|
+
export async function install(registry) {
|
|
80
|
+
// 1. 自动检测本机 AI Coding Agent:目前仅识别 Claude Code(~/.claude)。
|
|
81
|
+
// 后续可扩展其它 agent(如 ~/.qoder)。
|
|
82
82
|
const claudeDir = path.join(HOME, '.claude');
|
|
83
83
|
if (!fs.existsSync(claudeDir)) {
|
|
84
|
-
console.error('ERROR:
|
|
84
|
+
console.error('ERROR: 未检测到任何已支持的 AI Coding Agent。');
|
|
85
|
+
console.error(' 当前支持: Claude Code (需存在 ~/.claude 目录)');
|
|
85
86
|
process.exit(1);
|
|
86
87
|
}
|
|
87
88
|
|
package/src/skillhub.mjs
CHANGED
|
@@ -3339,21 +3339,11 @@ export function unzipSync(data, opts) {
|
|
|
3339
3339
|
|
|
3340
3340
|
|
|
3341
3341
|
// ==================================================================
|
|
3342
|
-
// === Tools: self-implemented replacements (
|
|
3342
|
+
// === Tools: self-implemented replacements (readline prompts) ===
|
|
3343
3343
|
// ==================================================================
|
|
3344
3344
|
|
|
3345
3345
|
import readline from 'node:readline';
|
|
3346
3346
|
|
|
3347
|
-
function semverGt(a, b) {
|
|
3348
|
-
const pa = String(a).replace(/^v/, '').split('.').map(Number);
|
|
3349
|
-
const pb = String(b).replace(/^v/, '').split('.').map(Number);
|
|
3350
|
-
for (let i = 0; i < 3; i++) {
|
|
3351
|
-
if ((pa[i] ?? 0) > (pb[i] ?? 0)) return true;
|
|
3352
|
-
if ((pa[i] ?? 0) < (pb[i] ?? 0)) return false;
|
|
3353
|
-
}
|
|
3354
|
-
return false;
|
|
3355
|
-
}
|
|
3356
|
-
|
|
3357
3347
|
async function promptSelect(message, choices) {
|
|
3358
3348
|
const rl = readline.createInterface({ input: process.stdin, output: process.stderr });
|
|
3359
3349
|
process.stderr.write(`${message}\n`);
|
|
@@ -3395,9 +3385,6 @@ async function promptMultiselect(message, choices) {
|
|
|
3395
3385
|
// === Version Info ===
|
|
3396
3386
|
// ==================================================================
|
|
3397
3387
|
|
|
3398
|
-
const PKG_NAME = "@astron-team/skillhub";
|
|
3399
|
-
const PKG_VERSION = "0.1.8";
|
|
3400
|
-
|
|
3401
3388
|
// ==================================================================
|
|
3402
3389
|
// === Source: shared/errors ===
|
|
3403
3390
|
// ==================================================================
|
|
@@ -3416,8 +3403,6 @@ class CliError extends Error {
|
|
|
3416
3403
|
// ==================================================================
|
|
3417
3404
|
|
|
3418
3405
|
const DEFAULT_REGISTRY = '';
|
|
3419
|
-
const CLI_VERSION = PKG_VERSION;
|
|
3420
|
-
const CLI_PACKAGE_NAME = PKG_NAME;
|
|
3421
3406
|
const EXIT = {
|
|
3422
3407
|
generic: 1,
|
|
3423
3408
|
auth: 2,
|
|
@@ -3614,79 +3599,6 @@ async function readBoundedResponseBody(response, maxBytes = MAX_PACKAGE_BYTES) {
|
|
|
3614
3599
|
return result.buffer;
|
|
3615
3600
|
}
|
|
3616
3601
|
|
|
3617
|
-
// ==================================================================
|
|
3618
|
-
// === Source: platform/updater ===
|
|
3619
|
-
// ==================================================================
|
|
3620
|
-
|
|
3621
|
-
import { spawn } from 'node:child_process';
|
|
3622
|
-
|
|
3623
|
-
async function runUpdateCommand(command) {
|
|
3624
|
-
try {
|
|
3625
|
-
const [program, ...args] = command;
|
|
3626
|
-
if (!program) {
|
|
3627
|
-
return { success: false, output: 'empty update command' };
|
|
3628
|
-
}
|
|
3629
|
-
|
|
3630
|
-
const spawnOptions = {
|
|
3631
|
-
stdio: ['ignore', 'pipe', 'pipe'],
|
|
3632
|
-
...(process.platform === 'win32' && { shell: true })
|
|
3633
|
-
};
|
|
3634
|
-
|
|
3635
|
-
return await new Promise((resolve) => {
|
|
3636
|
-
const proc = spawn(program, args, spawnOptions);
|
|
3637
|
-
const chunks = [];
|
|
3638
|
-
|
|
3639
|
-
proc.stdout?.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
|
3640
|
-
proc.stderr?.on('data', (chunk) => chunks.push(Buffer.from(chunk)));
|
|
3641
|
-
proc.on('error', (error) => resolve({ success: false, output: error.message }));
|
|
3642
|
-
proc.on('close', (code) => resolve({
|
|
3643
|
-
success: code === 0,
|
|
3644
|
-
output: Buffer.concat(chunks).toString('utf-8')
|
|
3645
|
-
}));
|
|
3646
|
-
});
|
|
3647
|
-
} catch (error) {
|
|
3648
|
-
return {
|
|
3649
|
-
success: false,
|
|
3650
|
-
output: error instanceof Error ? error.message : String(error)
|
|
3651
|
-
};
|
|
3652
|
-
}
|
|
3653
|
-
}
|
|
3654
|
-
|
|
3655
|
-
// ==================================================================
|
|
3656
|
-
// === Source: platform/package-manager ===
|
|
3657
|
-
// ==================================================================
|
|
3658
|
-
|
|
3659
|
-
function detectInstallMode(argv = process.argv, env = process.env) {
|
|
3660
|
-
const execPath = argv[1] ?? '';
|
|
3661
|
-
|
|
3662
|
-
if (execPath.includes('_npx/') || execPath.includes('_npx\\')) {
|
|
3663
|
-
return 'npx';
|
|
3664
|
-
}
|
|
3665
|
-
|
|
3666
|
-
const npmExecPath = env.npm_execpath ?? '';
|
|
3667
|
-
if (npmExecPath.includes('npx')) {
|
|
3668
|
-
return 'npx';
|
|
3669
|
-
}
|
|
3670
|
-
|
|
3671
|
-
const bunInstall = env.BUN_INSTALL ?? '';
|
|
3672
|
-
if (bunInstall && execPath.startsWith(bunInstall)) {
|
|
3673
|
-
return 'bun-global';
|
|
3674
|
-
}
|
|
3675
|
-
if (execPath.includes('/.bun/') || execPath.includes('\\.bun\\')) {
|
|
3676
|
-
return 'bun-global';
|
|
3677
|
-
}
|
|
3678
|
-
|
|
3679
|
-
const npmGlobalPrefix = env.npm_config_prefix ?? '';
|
|
3680
|
-
if (npmGlobalPrefix && execPath.startsWith(npmGlobalPrefix)) {
|
|
3681
|
-
return 'npm-global';
|
|
3682
|
-
}
|
|
3683
|
-
if (execPath.includes('/lib/node_modules/') || execPath.includes('/node_modules/.bin/')) {
|
|
3684
|
-
return 'npm-global';
|
|
3685
|
-
}
|
|
3686
|
-
|
|
3687
|
-
return 'unknown';
|
|
3688
|
-
}
|
|
3689
|
-
|
|
3690
3602
|
// ==================================================================
|
|
3691
3603
|
// === Source: platform/archive ===
|
|
3692
3604
|
// ==================================================================
|
|
@@ -4192,97 +4104,6 @@ class SkillHubClient {
|
|
|
4192
4104
|
}
|
|
4193
4105
|
}
|
|
4194
4106
|
|
|
4195
|
-
// ==================================================================
|
|
4196
|
-
// === Source: clients/npm-registry-client ===
|
|
4197
|
-
// ==================================================================
|
|
4198
|
-
|
|
4199
|
-
const DEFAULT_NPM_REGISTRY = 'https://registry.npmjs.org';
|
|
4200
|
-
|
|
4201
|
-
function readEnv(env, name) {
|
|
4202
|
-
const exactValue = env[name]?.trim();
|
|
4203
|
-
if (exactValue) {
|
|
4204
|
-
return exactValue;
|
|
4205
|
-
}
|
|
4206
|
-
|
|
4207
|
-
const lowerName = name.toLowerCase();
|
|
4208
|
-
for (const [key, value] of Object.entries(env)) {
|
|
4209
|
-
const normalizedValue = value?.trim();
|
|
4210
|
-
if (key.toLowerCase() === lowerName && normalizedValue) {
|
|
4211
|
-
return normalizedValue;
|
|
4212
|
-
}
|
|
4213
|
-
}
|
|
4214
|
-
return undefined;
|
|
4215
|
-
}
|
|
4216
|
-
|
|
4217
|
-
function resolveNpmRegistry(env) {
|
|
4218
|
-
return readEnv(env, 'SKILLHUB_NPM_REGISTRY')
|
|
4219
|
-
?? readEnv(env, 'npm_config_registry')
|
|
4220
|
-
?? readEnv(env, 'NPM_CONFIG_REGISTRY')
|
|
4221
|
-
?? DEFAULT_NPM_REGISTRY;
|
|
4222
|
-
}
|
|
4223
|
-
|
|
4224
|
-
function buildLatestUrl(registry, packageName) {
|
|
4225
|
-
try {
|
|
4226
|
-
const base = registry.endsWith('/') ? registry : `${registry}/`;
|
|
4227
|
-
return new URL(`${encodeURIComponent(packageName)}/latest`, base).toString();
|
|
4228
|
-
} catch {
|
|
4229
|
-
throw new CliError('invalid npm registry URL', EXIT.usage, {
|
|
4230
|
-
registry,
|
|
4231
|
-
next: 'check npm registry configuration and retry'
|
|
4232
|
-
});
|
|
4233
|
-
}
|
|
4234
|
-
}
|
|
4235
|
-
|
|
4236
|
-
class NpmRegistryClient {
|
|
4237
|
-
constructor(fetchImpl = fetch, timeoutMs = 10000, env = process.env) {
|
|
4238
|
-
this.fetchImpl = fetchImpl;
|
|
4239
|
-
this.timeoutMs = timeoutMs;
|
|
4240
|
-
this.env = env;
|
|
4241
|
-
}
|
|
4242
|
-
|
|
4243
|
-
async latestVersion(packageName = CLI_PACKAGE_NAME) {
|
|
4244
|
-
const registry = resolveNpmRegistry(this.env);
|
|
4245
|
-
const url = buildLatestUrl(registry, packageName);
|
|
4246
|
-
const controller = new AbortController();
|
|
4247
|
-
const timer = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
4248
|
-
try {
|
|
4249
|
-
let response;
|
|
4250
|
-
try {
|
|
4251
|
-
response = await this.fetchImpl(url, {
|
|
4252
|
-
signal: controller.signal
|
|
4253
|
-
});
|
|
4254
|
-
} catch (error) {
|
|
4255
|
-
const isTimeout = error instanceof Error && error.name === 'AbortError';
|
|
4256
|
-
throw new CliError('npm registry unreachable', EXIT.network, {
|
|
4257
|
-
registry,
|
|
4258
|
-
cause: error instanceof Error ? error.message : String(error),
|
|
4259
|
-
next: isTimeout
|
|
4260
|
-
? 'check npm registry connectivity or proxy settings and retry'
|
|
4261
|
-
: 'check npm registry/proxy configuration and retry'
|
|
4262
|
-
});
|
|
4263
|
-
}
|
|
4264
|
-
if (!response.ok) {
|
|
4265
|
-
throw new CliError(`npm registry returned ${response.status}`, EXIT.network, { registry });
|
|
4266
|
-
}
|
|
4267
|
-
let body;
|
|
4268
|
-
try {
|
|
4269
|
-
body = await response.json();
|
|
4270
|
-
} catch (error) {
|
|
4271
|
-
throw new CliError('npm registry response invalid', EXIT.network, {
|
|
4272
|
-
registry,
|
|
4273
|
-
cause: error instanceof Error ? error.message : String(error)
|
|
4274
|
-
});
|
|
4275
|
-
}
|
|
4276
|
-
if (typeof body !== 'object' || body === null || !('version' in body) || typeof body.version !== 'string') {
|
|
4277
|
-
throw new CliError('npm registry response missing version', EXIT.network, { registry });
|
|
4278
|
-
}
|
|
4279
|
-
return body.version;
|
|
4280
|
-
} finally {
|
|
4281
|
-
clearTimeout(timer);
|
|
4282
|
-
}
|
|
4283
|
-
}
|
|
4284
|
-
}
|
|
4285
|
-
|
|
4286
4107
|
// ==================================================================
|
|
4287
4108
|
// === Source: services/registry-service ===
|
|
4288
4109
|
// ==================================================================
|
|
@@ -4340,7 +4161,7 @@ async function deleteToolkitToken() {
|
|
|
4340
4161
|
function requireRegistry(registry) {
|
|
4341
4162
|
if (!registry) {
|
|
4342
4163
|
throw new CliError(
|
|
4343
|
-
'未配置 registry。运行: toolkit install
|
|
4164
|
+
'未配置 registry。运行: toolkit install --registry <url>',
|
|
4344
4165
|
EXIT.config
|
|
4345
4166
|
);
|
|
4346
4167
|
}
|
|
@@ -4440,6 +4261,8 @@ async function installSkill(options) {
|
|
|
4440
4261
|
try {
|
|
4441
4262
|
await rename2(tempDir, skillDir);
|
|
4442
4263
|
} catch (error) {
|
|
4264
|
+
console.error("安装失败!请检查是否有执行中的" + target.agent);
|
|
4265
|
+
console.error(error);
|
|
4443
4266
|
if (!options.force && await pathExists(skillDir)) {
|
|
4444
4267
|
throw new CliError(`skill already installed at ${skillDir}`, EXIT.filesystem, {
|
|
4445
4268
|
path: skillDir,
|
|
@@ -4506,8 +4329,7 @@ async function removeLocalSkill(options) {
|
|
|
4506
4329
|
for (const { item, target } of targetsToRemove) {
|
|
4507
4330
|
if (!target.rootDir || !isPathUnder(target.installDir, target.rootDir)) {
|
|
4508
4331
|
throw new CliError(`unsafe remove path: ${target.installDir} is not under ${target.rootDir ?? 'unknown root'}`, EXIT.filesystem, {
|
|
4509
|
-
path: target.installDir
|
|
4510
|
-
next: 'verify inventory integrity with `skillhub doctor`'
|
|
4332
|
+
path: target.installDir
|
|
4511
4333
|
});
|
|
4512
4334
|
}
|
|
4513
4335
|
|
|
@@ -4529,250 +4351,6 @@ async function removeLocalSkill(options) {
|
|
|
4529
4351
|
return { removed };
|
|
4530
4352
|
}
|
|
4531
4353
|
|
|
4532
|
-
// ==================================================================
|
|
4533
|
-
// === Source: services/update-service ===
|
|
4534
|
-
// ==================================================================
|
|
4535
|
-
|
|
4536
|
-
class UpdateService {
|
|
4537
|
-
constructor(deps) {
|
|
4538
|
-
this.deps = deps;
|
|
4539
|
-
}
|
|
4540
|
-
|
|
4541
|
-
async update(options) {
|
|
4542
|
-
const current = this.deps.currentVersion;
|
|
4543
|
-
const latest = await this.deps.latestVersion();
|
|
4544
|
-
|
|
4545
|
-
if (!semverGt(latest, current)) {
|
|
4546
|
-
return {
|
|
4547
|
-
updated: false,
|
|
4548
|
-
available: false,
|
|
4549
|
-
currentVersion: current,
|
|
4550
|
-
latestVersion: latest
|
|
4551
|
-
};
|
|
4552
|
-
}
|
|
4553
|
-
|
|
4554
|
-
if (options.checkOnly) {
|
|
4555
|
-
return {
|
|
4556
|
-
updated: false,
|
|
4557
|
-
available: true,
|
|
4558
|
-
currentVersion: current,
|
|
4559
|
-
latestVersion: latest
|
|
4560
|
-
};
|
|
4561
|
-
}
|
|
4562
|
-
|
|
4563
|
-
const mode = this.deps.detectInstallMode();
|
|
4564
|
-
|
|
4565
|
-
switch (mode) {
|
|
4566
|
-
case 'npx':
|
|
4567
|
-
return {
|
|
4568
|
-
updated: false,
|
|
4569
|
-
available: true,
|
|
4570
|
-
currentVersion: current,
|
|
4571
|
-
latestVersion: latest,
|
|
4572
|
-
next: `Run: npx ${CLI_PACKAGE_NAME}@latest <command> or install globally: npm install -g ${CLI_PACKAGE_NAME}`
|
|
4573
|
-
};
|
|
4574
|
-
|
|
4575
|
-
case 'npm-global': {
|
|
4576
|
-
const result = await this.deps.run(['npm', 'install', '-g', `${CLI_PACKAGE_NAME}@latest`]);
|
|
4577
|
-
return {
|
|
4578
|
-
updated: result.success,
|
|
4579
|
-
available: true,
|
|
4580
|
-
currentVersion: current,
|
|
4581
|
-
latestVersion: latest,
|
|
4582
|
-
error: result.success ? undefined : result.output
|
|
4583
|
-
};
|
|
4584
|
-
}
|
|
4585
|
-
|
|
4586
|
-
case 'bun-global': {
|
|
4587
|
-
const result = await this.deps.run(['bun', 'add', '-g', `${CLI_PACKAGE_NAME}@latest`]);
|
|
4588
|
-
return {
|
|
4589
|
-
updated: result.success,
|
|
4590
|
-
available: true,
|
|
4591
|
-
currentVersion: current,
|
|
4592
|
-
latestVersion: latest,
|
|
4593
|
-
error: result.success ? undefined : result.output
|
|
4594
|
-
};
|
|
4595
|
-
}
|
|
4596
|
-
|
|
4597
|
-
case 'unknown':
|
|
4598
|
-
default:
|
|
4599
|
-
return {
|
|
4600
|
-
updated: false,
|
|
4601
|
-
available: true,
|
|
4602
|
-
currentVersion: current,
|
|
4603
|
-
latestVersion: latest,
|
|
4604
|
-
next: `Update manually: npm install -g ${CLI_PACKAGE_NAME}@latest or bun add -g ${CLI_PACKAGE_NAME}@latest`
|
|
4605
|
-
};
|
|
4606
|
-
}
|
|
4607
|
-
}
|
|
4608
|
-
}
|
|
4609
|
-
|
|
4610
|
-
// ==================================================================
|
|
4611
|
-
// === Source: services/doctor-service ===
|
|
4612
|
-
// ==================================================================
|
|
4613
|
-
|
|
4614
|
-
import { lstat, readdir as readdir2, writeFile as writeFile6, readFile as readFile5 } from 'node:fs/promises';
|
|
4615
|
-
import { join as join4 } from 'node:path';
|
|
4616
|
-
|
|
4617
|
-
async function runDoctor(cwd, home) {
|
|
4618
|
-
const store = new InventoryStore(home);
|
|
4619
|
-
const skipped = [];
|
|
4620
|
-
const conflicts = [];
|
|
4621
|
-
|
|
4622
|
-
const entries = await scanMetadata(cwd, skipped);
|
|
4623
|
-
|
|
4624
|
-
const groups = new Map();
|
|
4625
|
-
for (const entry of entries) {
|
|
4626
|
-
const key = `${entry.metadata.registry}|${entry.metadata.namespace}|${entry.metadata.slug}`;
|
|
4627
|
-
if (!groups.has(key)) groups.set(key, []);
|
|
4628
|
-
groups.get(key).push(entry);
|
|
4629
|
-
}
|
|
4630
|
-
|
|
4631
|
-
const scannedItems = [];
|
|
4632
|
-
for (const [key, group] of groups) {
|
|
4633
|
-
const versions = new Set(group.map(e => e.metadata.version));
|
|
4634
|
-
if (versions.size > 1) {
|
|
4635
|
-
conflicts.push({ key, versions: [...versions] });
|
|
4636
|
-
continue;
|
|
4637
|
-
}
|
|
4638
|
-
const first = group[0];
|
|
4639
|
-
const targets = group.map(e => ({
|
|
4640
|
-
agent: e.metadata.agent,
|
|
4641
|
-
rootDir: join4(e.installDir, '..'),
|
|
4642
|
-
installDir: e.installDir,
|
|
4643
|
-
installedAt: e.metadata.installedAt
|
|
4644
|
-
}));
|
|
4645
|
-
scannedItems.push({
|
|
4646
|
-
registry: first.metadata.registry,
|
|
4647
|
-
namespace: first.metadata.namespace,
|
|
4648
|
-
slug: first.metadata.slug,
|
|
4649
|
-
version: first.metadata.version,
|
|
4650
|
-
targets
|
|
4651
|
-
});
|
|
4652
|
-
}
|
|
4653
|
-
|
|
4654
|
-
let oldInventory;
|
|
4655
|
-
try {
|
|
4656
|
-
oldInventory = await store.read();
|
|
4657
|
-
} catch {
|
|
4658
|
-
oldInventory = { items: [] };
|
|
4659
|
-
}
|
|
4660
|
-
|
|
4661
|
-
const scannedInstallDirs = new Set();
|
|
4662
|
-
for (const item of scannedItems) {
|
|
4663
|
-
for (const target of item.targets) {
|
|
4664
|
-
scannedInstallDirs.add(target.installDir);
|
|
4665
|
-
}
|
|
4666
|
-
}
|
|
4667
|
-
|
|
4668
|
-
const preservedItems = [];
|
|
4669
|
-
for (const oldItem of oldInventory.items) {
|
|
4670
|
-
const preservedTargets = oldItem.targets.filter(t => !scannedInstallDirs.has(t.installDir));
|
|
4671
|
-
if (preservedTargets.length > 0) {
|
|
4672
|
-
preservedItems.push({
|
|
4673
|
-
...oldItem,
|
|
4674
|
-
targets: preservedTargets
|
|
4675
|
-
});
|
|
4676
|
-
}
|
|
4677
|
-
}
|
|
4678
|
-
|
|
4679
|
-
const items = [...scannedItems, ...preservedItems];
|
|
4680
|
-
|
|
4681
|
-
let backupPath = null;
|
|
4682
|
-
try {
|
|
4683
|
-
const oldContent = await readFile5(store.path, 'utf-8');
|
|
4684
|
-
backupPath = `${store.path}.bak`;
|
|
4685
|
-
await writeFile6(backupPath, oldContent);
|
|
4686
|
-
} catch {
|
|
4687
|
-
}
|
|
4688
|
-
|
|
4689
|
-
const newInventory = { items };
|
|
4690
|
-
await store.writeAtomic(newInventory);
|
|
4691
|
-
|
|
4692
|
-
return {
|
|
4693
|
-
inventoryPath: store.path,
|
|
4694
|
-
backupPath,
|
|
4695
|
-
itemsScanned: scannedItems.length,
|
|
4696
|
-
targetsScanned: scannedItems.reduce((sum, item) => sum + item.targets.length, 0),
|
|
4697
|
-
itemsPreserved: preservedItems.length,
|
|
4698
|
-
targetsPreserved: preservedItems.reduce((sum, item) => sum + item.targets.length, 0),
|
|
4699
|
-
skipped,
|
|
4700
|
-
conflicts
|
|
4701
|
-
};
|
|
4702
|
-
}
|
|
4703
|
-
|
|
4704
|
-
async function scanMetadata(cwd, skipped) {
|
|
4705
|
-
const results = [];
|
|
4706
|
-
|
|
4707
|
-
let topEntries;
|
|
4708
|
-
try {
|
|
4709
|
-
topEntries = await readdir2(cwd);
|
|
4710
|
-
} catch {
|
|
4711
|
-
throw new CliError('cannot read project directory', EXIT.filesystem, { path: cwd });
|
|
4712
|
-
}
|
|
4713
|
-
|
|
4714
|
-
for (const dirName of topEntries) {
|
|
4715
|
-
if (!dirName.startsWith('.')) continue;
|
|
4716
|
-
const agentDir = join4(cwd, dirName);
|
|
4717
|
-
try {
|
|
4718
|
-
const st = await lstat(agentDir);
|
|
4719
|
-
if (st.isSymbolicLink() || !st.isDirectory()) {
|
|
4720
|
-
skipped.push({ path: agentDir, reason: 'not a regular directory' });
|
|
4721
|
-
continue;
|
|
4722
|
-
}
|
|
4723
|
-
} catch {
|
|
4724
|
-
skipped.push({ path: agentDir, reason: 'cannot stat' });
|
|
4725
|
-
continue;
|
|
4726
|
-
}
|
|
4727
|
-
const skillsDir = join4(agentDir, 'skills');
|
|
4728
|
-
let slugDirs;
|
|
4729
|
-
try {
|
|
4730
|
-
slugDirs = await readdir2(skillsDir);
|
|
4731
|
-
} catch {
|
|
4732
|
-
continue;
|
|
4733
|
-
}
|
|
4734
|
-
|
|
4735
|
-
for (const slug of slugDirs) {
|
|
4736
|
-
const slugPath = join4(skillsDir, slug);
|
|
4737
|
-
try {
|
|
4738
|
-
const st = await lstat(slugPath);
|
|
4739
|
-
if (st.isSymbolicLink() || !st.isDirectory()) {
|
|
4740
|
-
skipped.push({ path: slugPath, reason: 'not a regular directory' });
|
|
4741
|
-
continue;
|
|
4742
|
-
}
|
|
4743
|
-
} catch {
|
|
4744
|
-
skipped.push({ path: slugPath, reason: 'cannot stat' });
|
|
4745
|
-
continue;
|
|
4746
|
-
}
|
|
4747
|
-
const skillhubDir = join4(slugPath, '.skillhub');
|
|
4748
|
-
try {
|
|
4749
|
-
const skillhubSt = await lstat(skillhubDir);
|
|
4750
|
-
if (skillhubSt.isSymbolicLink() || !skillhubSt.isDirectory()) {
|
|
4751
|
-
skipped.push({ path: slugPath, reason: '.skillhub is not a regular directory' });
|
|
4752
|
-
continue;
|
|
4753
|
-
}
|
|
4754
|
-
} catch {
|
|
4755
|
-
skipped.push({ path: slugPath, reason: 'no .skillhub directory' });
|
|
4756
|
-
continue;
|
|
4757
|
-
}
|
|
4758
|
-
const metadataPath = join4(skillhubDir, 'metadata.json');
|
|
4759
|
-
try {
|
|
4760
|
-
const content = await readFile5(metadataPath, 'utf-8');
|
|
4761
|
-
const metadata = JSON.parse(content);
|
|
4762
|
-
if (!metadata.registry || !metadata.namespace || !metadata.slug || !metadata.version || !metadata.agent || !metadata.installedAt) {
|
|
4763
|
-
skipped.push({ path: slugPath, reason: 'incomplete metadata' });
|
|
4764
|
-
continue;
|
|
4765
|
-
}
|
|
4766
|
-
results.push({ metadata, installDir: slugPath });
|
|
4767
|
-
} catch {
|
|
4768
|
-
skipped.push({ path: slugPath, reason: 'no .skillhub/metadata.json' });
|
|
4769
|
-
}
|
|
4770
|
-
}
|
|
4771
|
-
}
|
|
4772
|
-
|
|
4773
|
-
return results;
|
|
4774
|
-
}
|
|
4775
|
-
|
|
4776
4354
|
// ==================================================================
|
|
4777
4355
|
// === Source: agents/profiles/make-profile ===
|
|
4778
4356
|
// ==================================================================
|
|
@@ -4819,6 +4397,7 @@ const traeProfile = makeProfile('trae', 'Trae', '.trae/skills', '.trae/skills');
|
|
|
4819
4397
|
const traeCnProfile = makeProfile('trae-cn', 'Trae CN', '.trae-cn/skills', '.trae-cn/skills');
|
|
4820
4398
|
const opencodeProfile = makeProfile('opencode', 'OpenCode', '.opencode/skills', '.opencode/skills');
|
|
4821
4399
|
const kiloProfile = makeProfile('kilo', 'Kilo', '.kilo/skills', '.kilo/skills');
|
|
4400
|
+
const qoderProfile = makeProfile('qoder', 'qoder', '.qoder/skills', '.qoder/skills');
|
|
4822
4401
|
const genericFallbackProfile = makeProfile('generic', 'Generic Fallback', '.agents/skills', '.agents/skills');
|
|
4823
4402
|
|
|
4824
4403
|
// ==================================================================
|
|
@@ -4829,7 +4408,7 @@ const allProfiles = [
|
|
|
4829
4408
|
claudeCodeProfile, codexProfile, cursorProfile, githubCopilotProfile,
|
|
4830
4409
|
geminiCliProfile, openhandsProfile, windsurfProfile, openclawProfile,
|
|
4831
4410
|
kiroCliProfile, rooProfile, traeProfile, traeCnProfile,
|
|
4832
|
-
opencodeProfile, kiloProfile
|
|
4411
|
+
opencodeProfile, kiloProfile, qoderProfile
|
|
4833
4412
|
];
|
|
4834
4413
|
|
|
4835
4414
|
const profileMap = new Map(allProfiles.map(p => [p.id, p]));
|
|
@@ -4991,11 +4570,6 @@ const commands = {
|
|
|
4991
4570
|
usage: 'skillhub help [command] [--json]',
|
|
4992
4571
|
examples: ['skillhub help', 'skillhub help install', 'skillhub help --json']
|
|
4993
4572
|
},
|
|
4994
|
-
version: {
|
|
4995
|
-
summary: 'Show installed CLI version',
|
|
4996
|
-
usage: 'skillhub version [--json]',
|
|
4997
|
-
examples: ['skillhub version', 'skillhub version --json']
|
|
4998
|
-
},
|
|
4999
4573
|
login: {
|
|
5000
4574
|
summary: 'Save registry and token',
|
|
5001
4575
|
usage: 'skillhub login [--token <token>] [--registry <url>] [--json]',
|
|
@@ -5035,20 +4609,10 @@ const commands = {
|
|
|
5035
4609
|
usage: 'skillhub remove <slug> [--agent <profile>] [--all] [--remote] [--hard] [--namespace <slug>] [--json]',
|
|
5036
4610
|
examples: ['skillhub remove pdf-parser', 'skillhub remove pdf-parser --remote --hard']
|
|
5037
4611
|
},
|
|
5038
|
-
doctor: {
|
|
5039
|
-
summary: 'Scan project and merge into local inventory (preserves entries outside scan scope)',
|
|
5040
|
-
usage: 'skillhub doctor [--json]',
|
|
5041
|
-
examples: ['skillhub doctor', 'skillhub doctor --json']
|
|
5042
|
-
},
|
|
5043
4612
|
publish: {
|
|
5044
4613
|
summary: 'Publish a local skill package',
|
|
5045
4614
|
usage: 'skillhub publish <path> [--namespace <slug>] [--visibility <public|namespace-only|private>] [--registry <url>] [--json]',
|
|
5046
4615
|
examples: ['skillhub publish ./my-skill', 'skillhub publish ./my-skill --namespace myspace']
|
|
5047
|
-
},
|
|
5048
|
-
update: {
|
|
5049
|
-
summary: 'Check or update CLI itself',
|
|
5050
|
-
usage: 'skillhub update [--check] [--json]',
|
|
5051
|
-
examples: ['skillhub update --check', 'skillhub update']
|
|
5052
4616
|
}
|
|
5053
4617
|
};
|
|
5054
4618
|
|
|
@@ -5081,15 +4645,6 @@ async function helpCommand(args) {
|
|
|
5081
4645
|
return formatCommandList();
|
|
5082
4646
|
}
|
|
5083
4647
|
|
|
5084
|
-
// ==================================================================
|
|
5085
|
-
// === Source: commands/version ===
|
|
5086
|
-
// ==================================================================
|
|
5087
|
-
|
|
5088
|
-
async function versionCommand(args) {
|
|
5089
|
-
const json = args.includes('--json');
|
|
5090
|
-
return printResult(json ? { ok: true, version: CLI_VERSION } : `SkillHub CLI ${CLI_VERSION}`, json);
|
|
5091
|
-
}
|
|
5092
|
-
|
|
5093
4648
|
// ==================================================================
|
|
5094
4649
|
// === Source: commands/login ===
|
|
5095
4650
|
// ==================================================================
|
|
@@ -5343,38 +4898,6 @@ async function removeCommand(skillNameArg, options) {
|
|
|
5343
4898
|
).join('\n');
|
|
5344
4899
|
}
|
|
5345
4900
|
|
|
5346
|
-
// ==================================================================
|
|
5347
|
-
// === Source: commands/doctor ===
|
|
5348
|
-
// ==================================================================
|
|
5349
|
-
|
|
5350
|
-
async function doctorCommand(options) {
|
|
5351
|
-
const result = await runDoctor(process.cwd());
|
|
5352
|
-
if (options.json) {
|
|
5353
|
-
return JSON.stringify({
|
|
5354
|
-
ok: true,
|
|
5355
|
-
inventoryPath: result.inventoryPath,
|
|
5356
|
-
backupPath: result.backupPath,
|
|
5357
|
-
itemsScanned: result.itemsScanned,
|
|
5358
|
-
targetsScanned: result.targetsScanned,
|
|
5359
|
-
itemsPreserved: result.itemsPreserved,
|
|
5360
|
-
targetsPreserved: result.targetsPreserved,
|
|
5361
|
-
skipped: result.skipped,
|
|
5362
|
-
conflicts: result.conflicts
|
|
5363
|
-
});
|
|
5364
|
-
}
|
|
5365
|
-
const lines = [
|
|
5366
|
-
`Inventory: ${result.inventoryPath}`,
|
|
5367
|
-
result.backupPath ? `Backup: ${result.backupPath}` : null,
|
|
5368
|
-
`Scanned: ${result.itemsScanned} items, ${result.targetsScanned} targets`,
|
|
5369
|
-
result.itemsPreserved > 0
|
|
5370
|
-
? `Preserved (outside scan): ${result.itemsPreserved} items, ${result.targetsPreserved} targets`
|
|
5371
|
-
: null,
|
|
5372
|
-
result.skipped.length > 0 ? `Skipped: ${result.skipped.length} directories` : null,
|
|
5373
|
-
result.conflicts.length > 0 ? `Conflicts: ${result.conflicts.length} groups` : null
|
|
5374
|
-
].filter(Boolean);
|
|
5375
|
-
return lines.join('\n');
|
|
5376
|
-
}
|
|
5377
|
-
|
|
5378
4901
|
// ==================================================================
|
|
5379
4902
|
// === Source: commands/publish ===
|
|
5380
4903
|
// ==================================================================
|
|
@@ -5483,58 +5006,6 @@ function toServerVisibility(visibility) {
|
|
|
5483
5006
|
return visibility.toUpperCase().replace(/-/g, '_');
|
|
5484
5007
|
}
|
|
5485
5008
|
|
|
5486
|
-
// ==================================================================
|
|
5487
|
-
// === Source: commands/update ===
|
|
5488
|
-
// ==================================================================
|
|
5489
|
-
|
|
5490
|
-
async function updateCommand(options) {
|
|
5491
|
-
const json = Boolean(options.json);
|
|
5492
|
-
const checkOnly = Boolean(options.check);
|
|
5493
|
-
|
|
5494
|
-
const service = new UpdateService({
|
|
5495
|
-
currentVersion: CLI_VERSION,
|
|
5496
|
-
latestVersion: () => new NpmRegistryClient().latestVersion(),
|
|
5497
|
-
detectInstallMode: () => detectInstallMode(),
|
|
5498
|
-
run: runUpdateCommand
|
|
5499
|
-
});
|
|
5500
|
-
|
|
5501
|
-
const result = await service.update({ checkOnly });
|
|
5502
|
-
|
|
5503
|
-
if (!result.available) {
|
|
5504
|
-
return printResult(
|
|
5505
|
-
json
|
|
5506
|
-
? { ok: true, upToDate: true, version: result.currentVersion }
|
|
5507
|
-
: `Already up to date (${result.currentVersion})`,
|
|
5508
|
-
json
|
|
5509
|
-
);
|
|
5510
|
-
}
|
|
5511
|
-
|
|
5512
|
-
if (result.updated) {
|
|
5513
|
-
return printResult(
|
|
5514
|
-
json
|
|
5515
|
-
? { ok: true, updated: true, from: result.currentVersion, to: result.latestVersion }
|
|
5516
|
-
: `Updated skillhub ${result.currentVersion} -> ${result.latestVersion}`,
|
|
5517
|
-
json
|
|
5518
|
-
);
|
|
5519
|
-
}
|
|
5520
|
-
|
|
5521
|
-
if (result.error) {
|
|
5522
|
-
throw new CliError(result.error, EXIT.generic, { from: result.currentVersion, to: result.latestVersion });
|
|
5523
|
-
}
|
|
5524
|
-
|
|
5525
|
-
const lines = [`Update available: ${result.currentVersion} -> ${result.latestVersion}`];
|
|
5526
|
-
if (result.next) {
|
|
5527
|
-
lines.push(result.next);
|
|
5528
|
-
}
|
|
5529
|
-
|
|
5530
|
-
return printResult(
|
|
5531
|
-
json
|
|
5532
|
-
? { ok: true, available: true, from: result.currentVersion, to: result.latestVersion, next: result.next }
|
|
5533
|
-
: lines.join('\n'),
|
|
5534
|
-
json
|
|
5535
|
-
);
|
|
5536
|
-
}
|
|
5537
|
-
|
|
5538
5009
|
// ==================================================================
|
|
5539
5010
|
// === Source: index (CLI entry point) ===
|
|
5540
5011
|
// ==================================================================
|
|
@@ -5682,13 +5153,6 @@ function readUnknownCommand(argv) {
|
|
|
5682
5153
|
cli.command('help [command]', 'Show help')
|
|
5683
5154
|
.option('--json', 'Output JSON');
|
|
5684
5155
|
|
|
5685
|
-
cli.command('version', 'Show CLI version')
|
|
5686
|
-
.option('--json', 'Output JSON');
|
|
5687
|
-
|
|
5688
|
-
cli.command('update', 'Update CLI to latest version')
|
|
5689
|
-
.option('--check', 'Check for updates without installing')
|
|
5690
|
-
.option('--json', 'Output JSON');
|
|
5691
|
-
|
|
5692
5156
|
cli.command('login', 'Save registry and token')
|
|
5693
5157
|
.option('--registry <url>', 'Registry URL')
|
|
5694
5158
|
.option('--token <token>', 'API token')
|
|
@@ -5737,9 +5201,6 @@ cli.command('remove <slug>', 'Remove local or remote skill')
|
|
|
5737
5201
|
.option('--token <token>', 'API token')
|
|
5738
5202
|
.option('--json', 'Output JSON');
|
|
5739
5203
|
|
|
5740
|
-
cli.command('doctor', 'Scan project and merge into local inventory')
|
|
5741
|
-
.option('--json', 'Output JSON');
|
|
5742
|
-
|
|
5743
5204
|
cli.command('publish <path>', 'Publish a local skill package')
|
|
5744
5205
|
.option('--namespace <slug>', 'Namespace')
|
|
5745
5206
|
.option('--visibility <v>', 'Visibility (public|namespace-only|private)')
|
|
@@ -5754,10 +5215,6 @@ async function dispatchCommand(commandName, args, options) {
|
|
|
5754
5215
|
switch (commandName) {
|
|
5755
5216
|
case 'help':
|
|
5756
5217
|
return helpCommand([...args, ...(options.json ? ['--json'] : [])]);
|
|
5757
|
-
case 'version':
|
|
5758
|
-
return versionCommand(options.json ? ['--json'] : []);
|
|
5759
|
-
case 'update':
|
|
5760
|
-
return updateCommand(options);
|
|
5761
5218
|
case 'login':
|
|
5762
5219
|
return loginCommand(options);
|
|
5763
5220
|
case 'logout':
|
|
@@ -5772,8 +5229,6 @@ async function dispatchCommand(commandName, args, options) {
|
|
|
5772
5229
|
return listCommand({ ...options, agent: toArray(options.agent) });
|
|
5773
5230
|
case 'remove':
|
|
5774
5231
|
return removeCommand(args[0], { ...options, agent: toArray(options.agent) });
|
|
5775
|
-
case 'doctor':
|
|
5776
|
-
return doctorCommand(options);
|
|
5777
5232
|
case 'publish':
|
|
5778
5233
|
return publishCommand(args[0], options);
|
|
5779
5234
|
default:
|
package/src/status.mjs
CHANGED
|
@@ -41,7 +41,7 @@ export function collectStatus() {
|
|
|
41
41
|
lines.push('════════════════════════════════════════');
|
|
42
42
|
|
|
43
43
|
if (!pluginExists && !configExists) {
|
|
44
|
-
lines.push(' ❌ 未安装。运行: toolkit install
|
|
44
|
+
lines.push(' ❌ 未安装。运行: toolkit install --registry <url>');
|
|
45
45
|
return lines.join('\n');
|
|
46
46
|
}
|
|
47
47
|
|
package/src/zentao.mjs
CHANGED
|
@@ -327,6 +327,48 @@ export function shapeBugListItem(b) {
|
|
|
327
327
|
};
|
|
328
328
|
}
|
|
329
329
|
|
|
330
|
+
// 去除 HTML 标签 + 折叠空白:action.comment/desc 可能含 <p>/<strong>/<br> 等
|
|
331
|
+
function stripHtml(s) {
|
|
332
|
+
if (!s) return '';
|
|
333
|
+
return String(s)
|
|
334
|
+
.replace(/<br\s*\/?\s*>/gi, '\n')
|
|
335
|
+
.replace(/<\/p>\s*<p[^>]*>/gi, '\n')
|
|
336
|
+
.replace(/<[^>]+>/g, '')
|
|
337
|
+
.replace(/ /g, ' ')
|
|
338
|
+
.replace(/</g, '<')
|
|
339
|
+
.replace(/>/g, '>')
|
|
340
|
+
.replace(/&/g, '&')
|
|
341
|
+
.replace(/[ \t]+\n/g, '\n')
|
|
342
|
+
.replace(/\n{3,}/g, '\n\n')
|
|
343
|
+
.trim();
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// 历史动作/评论轨迹:原始 actions[] 清洗为精简结构
|
|
347
|
+
// { actor, action, date, comment, extra, changes: [{ field, fieldName, old, new }] }
|
|
348
|
+
// comment/desc 去标签;history 取字段变更明细;空 comment / 无变更的动作仍保留(动作本身有意义)
|
|
349
|
+
export function shapeActions(actions) {
|
|
350
|
+
if (!Array.isArray(actions)) return [];
|
|
351
|
+
return actions
|
|
352
|
+
.filter((a) => a && typeof a === 'object')
|
|
353
|
+
.map((a) => ({
|
|
354
|
+
actor: a.actor ?? '',
|
|
355
|
+
action: a.action ?? '',
|
|
356
|
+
date: a.date ?? '',
|
|
357
|
+
comment: stripHtml(a.comment),
|
|
358
|
+
extra: a.extra ? stripHtml(String(a.extra)) : '',
|
|
359
|
+
changes: Array.isArray(a.history)
|
|
360
|
+
? a.history
|
|
361
|
+
.filter((h) => h && typeof h === 'object')
|
|
362
|
+
.map((h) => ({
|
|
363
|
+
field: h.field ?? '',
|
|
364
|
+
fieldName: h.fieldName ?? h.field ?? '',
|
|
365
|
+
old: h.old ?? '',
|
|
366
|
+
new: h.new ?? '',
|
|
367
|
+
}))
|
|
368
|
+
: [],
|
|
369
|
+
}));
|
|
370
|
+
}
|
|
371
|
+
|
|
330
372
|
export function shapeBugDetail(b) {
|
|
331
373
|
return {
|
|
332
374
|
...shapeBugListItem(b),
|
|
@@ -336,6 +378,7 @@ export function shapeBugDetail(b) {
|
|
|
336
378
|
resolvedDate: b.resolvedDate ?? '', resolvedBuild: b.resolvedBuild ?? '',
|
|
337
379
|
closedBy: userToAccount(b.closedBy), closedDate: b.closedDate ?? '',
|
|
338
380
|
lastEditedBy: userToAccount(b.lastEditedBy), lastEditedDate: b.lastEditedDate ?? '',
|
|
381
|
+
actions: shapeActions(b.actions),
|
|
339
382
|
};
|
|
340
383
|
}
|
|
341
384
|
|
|
@@ -377,14 +420,26 @@ export function bugListToMarkdown({ total, count, bugs }) {
|
|
|
377
420
|
|
|
378
421
|
export function bugDetailToMarkdown(b) {
|
|
379
422
|
const f = (k) => b[k] === '' || b[k] == null ? '-' : b[k];
|
|
380
|
-
|
|
423
|
+
const lines = [
|
|
381
424
|
`# Bug #${b.id}: ${b.title}`, '',
|
|
382
425
|
`- **Severity**: ${b.severity} | **Priority**: ${b.priority} | **Status**: ${b.status}`,
|
|
383
426
|
`- **Project**: ${b.projectId} | **Product**: ${b.productId}`,
|
|
384
427
|
`- **OpenedBy**: ${b.openedBy} ${b.openedDate} | **AssignedTo**: ${f('assignedTo')}`,
|
|
385
428
|
`- **Resolution**: ${f('resolution')} by ${f('resolvedBy')} ${f('resolvedDate')}`,
|
|
386
429
|
'', '## Steps', '', b.steps || '(empty)',
|
|
387
|
-
]
|
|
430
|
+
];
|
|
431
|
+
if (Array.isArray(b.actions) && b.actions.length) {
|
|
432
|
+
lines.push('', '## History', '');
|
|
433
|
+
for (const a of b.actions) {
|
|
434
|
+
const extra = a.extra ? ` → **${a.extra}**` : '';
|
|
435
|
+
lines.push(`- **${a.actor || '?'}** \`${a.action || '?'}\` _${a.date || '?'}_${extra}`);
|
|
436
|
+
if (a.comment) lines.push(` > ${a.comment.replace(/\n/g, '\n > ')}`);
|
|
437
|
+
for (const c of a.changes) {
|
|
438
|
+
lines.push(` - ${c.fieldName || c.field}: \`${c.old || '∅'}\` → \`${c.new || '∅'}\``);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
return lines.join('\n');
|
|
388
443
|
}
|
|
389
444
|
|
|
390
445
|
// HTTP 状态码 -> 统一错误 kind/message
|