@zhangheteng/skill-hub 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/NOTICE.md +7 -0
- package/README.md +32 -2
- package/apps/cli/dist/args.js +111 -0
- package/apps/cli/dist/commands/audit.js +38 -0
- package/apps/cli/dist/commands/backup.js +34 -0
- package/apps/cli/dist/commands/doctor.js +86 -0
- package/apps/cli/dist/commands/index-cmd.js +122 -0
- package/apps/cli/dist/commands/ingest.js +198 -0
- package/apps/cli/dist/commands/init.js +110 -0
- package/apps/cli/dist/commands/inspect.js +68 -0
- package/apps/cli/dist/commands/inventory.js +259 -0
- package/apps/cli/dist/commands/manage.js +149 -0
- package/apps/cli/dist/commands/manifest.js +127 -0
- package/apps/cli/dist/commands/restore.js +37 -0
- package/apps/cli/dist/commands/route.js +92 -0
- package/apps/cli/dist/commands/runtime.js +89 -0
- package/apps/cli/dist/commands/sync.js +195 -0
- package/apps/cli/dist/commands/takeover.js +45 -0
- package/apps/cli/dist/commands/usage.js +55 -0
- package/apps/cli/dist/commands/verify.js +48 -0
- package/apps/cli/dist/index.js +468 -0
- package/apps/cli/dist/output.js +7 -0
- package/apps/mcp-server/dist/http.js +265 -0
- package/apps/mcp-server/dist/index.js +42 -0
- package/apps/mcp-server/dist/server.js +541 -0
- package/apps/web/dist/index.js +355 -0
- package/apps/web/public/app.js +927 -0
- package/apps/web/public/index.html +109 -0
- package/apps/web/public/styles.css +1796 -0
- package/configs/agents/default.yaml +44 -0
- package/configs/models.yaml +55 -0
- package/configs/profiles/coding.yaml +29 -0
- package/configs/profiles/video.yaml +18 -0
- package/configs/taxonomy.yaml +24 -0
- package/package.json +42 -4
- package/packages/core/dist/agent-sources.js +38 -0
- package/packages/core/dist/audit.js +275 -0
- package/packages/core/dist/catalog.js +173 -0
- package/packages/core/dist/config.js +295 -0
- package/packages/core/dist/conflict.js +95 -0
- package/packages/core/dist/context-cost.js +19 -0
- package/packages/core/dist/frontmatter.js +118 -0
- package/packages/core/dist/hash.js +7 -0
- package/packages/core/dist/history.js +60 -0
- package/packages/core/dist/index.js +23 -0
- package/packages/core/dist/managed.js +1774 -0
- package/packages/core/dist/materialize.js +149 -0
- package/packages/core/dist/parse.js +74 -0
- package/packages/core/dist/scan.js +112 -0
- package/packages/core/dist/takeover.js +96 -0
- package/packages/core/dist/tree.js +61 -0
- package/packages/core/dist/types.js +4 -0
- package/packages/core/dist/usage.js +267 -0
- package/packages/router/dist/bm25.js +144 -0
- package/packages/router/dist/context-cost.js +82 -0
- package/packages/router/dist/external.js +107 -0
- package/packages/router/dist/factory.js +144 -0
- package/packages/router/dist/hub-service.js +589 -0
- package/packages/router/dist/index-store.js +153 -0
- package/packages/router/dist/index.js +22 -0
- package/packages/router/dist/llm/config.js +311 -0
- package/packages/router/dist/llm/http.js +102 -0
- package/packages/router/dist/llm/index.js +6 -0
- package/packages/router/dist/llm/ollama.js +68 -0
- package/packages/router/dist/llm/openai-compatible.js +66 -0
- package/packages/router/dist/llm/registry.js +103 -0
- package/packages/router/dist/llm/types.js +31 -0
- package/packages/router/dist/profile.js +124 -0
- package/packages/router/dist/runtime.js +270 -0
- package/packages/router/dist/scope.js +13 -0
- package/packages/router/dist/taxonomy.js +172 -0
- package/packages/router/dist/tokenize.js +38 -0
- package/packages/router/dist/types.js +1 -0
- package/packages/router/dist/typesafe.js +331 -0
- package/packages/router/dist/usage-overview.js +143 -0
- package/packages/router/dist/usage.js +76 -0
- package/packages/shared/dist/constants.js +14 -0
- package/packages/shared/dist/errors.js +90 -0
- package/packages/shared/dist/index.js +14 -0
- package/packages/shared/dist/logger.js +95 -0
- package/packages/shared/dist/paths.js +118 -0
- package/packages/shared/dist/types.js +12 -0
- package/packages/sync/dist/apply.js +227 -0
- package/packages/sync/dist/backup.js +193 -0
- package/packages/sync/dist/fs-utils.js +188 -0
- package/packages/sync/dist/index.js +18 -0
- package/packages/sync/dist/integrity.js +238 -0
- package/packages/sync/dist/plan.js +211 -0
- package/packages/sync/dist/restore.js +69 -0
- package/packages/sync/dist/scope.js +22 -0
- package/packages/sync/dist/types.js +1 -0
- package/packages/sync/dist/verify.js +189 -0
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { lstat } from "node:fs/promises";
|
|
2
|
+
import { catalogToSkillMetas, loadHubConfig, readCatalog, resolveHubConfigPaths, scanSkills, } from "#skill-hub/core";
|
|
3
|
+
import { verifySync } from "#skill-hub/sync";
|
|
4
|
+
import { SkillHubError, getDefaultHubHome, hubLayout, resolveAbsolutePath, } from "#skill-hub/shared";
|
|
5
|
+
import { printJson, printLines } from "../output.js";
|
|
6
|
+
export async function runVerify(options) {
|
|
7
|
+
const config = resolveHubConfigPaths(options.configPath ? await loadHubConfig(options.configPath) : await loadHubConfig());
|
|
8
|
+
const layout = hubLayout(getDefaultHubHome());
|
|
9
|
+
const catalogPath = options.catalogPath
|
|
10
|
+
? resolveAbsolutePath(options.catalogPath)
|
|
11
|
+
: layout.catalog;
|
|
12
|
+
let skills = [];
|
|
13
|
+
try {
|
|
14
|
+
skills = catalogToSkillMetas(await readCatalog(catalogPath));
|
|
15
|
+
}
|
|
16
|
+
catch (err) {
|
|
17
|
+
const exists = await lstat(catalogPath).catch((e) => {
|
|
18
|
+
if (e.code === "ENOENT")
|
|
19
|
+
return null;
|
|
20
|
+
throw e;
|
|
21
|
+
});
|
|
22
|
+
if (exists)
|
|
23
|
+
throw err;
|
|
24
|
+
const issues = [];
|
|
25
|
+
skills = await scanSkills(config.canonical_dir, { onIssue: (issue) => issues.push(issue) });
|
|
26
|
+
if (issues.length)
|
|
27
|
+
throw new SkillHubError({
|
|
28
|
+
code: "E_SYNC",
|
|
29
|
+
message: `canonical 扫描不完整: ${JSON.stringify(issues)}`,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
const report = await verifySync(config, skills);
|
|
33
|
+
if (options.json)
|
|
34
|
+
printJson(report);
|
|
35
|
+
else {
|
|
36
|
+
const s = report.summary;
|
|
37
|
+
printLines([
|
|
38
|
+
`verify ${report.ok ? "OK" : "ISSUES"} mode=${config.sync.mode} skills×agents=${report.items.length}`,
|
|
39
|
+
` ok=${s.ok} missing=${s.missing} not_symlink=${s.not_symlink} not_copy=${s.not_copy} broken=${s.broken_symlink} wrong=${s.wrong_target} mismatch=${s.content_mismatch}`,
|
|
40
|
+
...report.items
|
|
41
|
+
.filter((i) => i.kind !== "ok")
|
|
42
|
+
.slice(0, 40)
|
|
43
|
+
.map((i) => ` [${i.kind}] ${i.agentId}:${i.skillName} ${i.detail ?? ""}${i.suggestion ? ` → ${i.suggestion}` : ""}`),
|
|
44
|
+
report.items.filter((i) => i.kind !== "ok").length > 40 ? ` ... more issues` : "",
|
|
45
|
+
].filter(Boolean));
|
|
46
|
+
}
|
|
47
|
+
return report.ok ? 0 : 1;
|
|
48
|
+
}
|
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* skill-hub CLI
|
|
4
|
+
*/
|
|
5
|
+
import { SKILL_HUB_VERSION, SkillHubError, createLogger, toSkillHubError, } from "#skill-hub/shared";
|
|
6
|
+
import { agentSkillSources, loadHubConfig, resolveHubConfigPaths } from "#skill-hub/core";
|
|
7
|
+
import { flagBool, flagString, parseArgs } from "./args.js";
|
|
8
|
+
import { printJson, printLines } from "./output.js";
|
|
9
|
+
import { runDoctor } from "./commands/doctor.js";
|
|
10
|
+
import { runInventory } from "./commands/inventory.js";
|
|
11
|
+
import { runInit } from "./commands/init.js";
|
|
12
|
+
import { runIngest } from "./commands/ingest.js";
|
|
13
|
+
import { runIndex } from "./commands/index-cmd.js";
|
|
14
|
+
import { runRoute } from "./commands/route.js";
|
|
15
|
+
import { runSync } from "./commands/sync.js";
|
|
16
|
+
import { runBackup } from "./commands/backup.js";
|
|
17
|
+
import { runVerify } from "./commands/verify.js";
|
|
18
|
+
import { runRestore } from "./commands/restore.js";
|
|
19
|
+
import { runExplain, runHistory, runPath } from "./commands/inspect.js";
|
|
20
|
+
import { runTakeover } from "./commands/takeover.js";
|
|
21
|
+
import { runManage } from "./commands/manage.js";
|
|
22
|
+
import { runBrowse, runFiles, runRead } from "./commands/runtime.js";
|
|
23
|
+
import { runAudit } from "./commands/audit.js";
|
|
24
|
+
import { runManifest } from "./commands/manifest.js";
|
|
25
|
+
import { withUsageScope } from "#skill-hub/router";
|
|
26
|
+
import { runContextCost, runUsage } from "./commands/usage.js";
|
|
27
|
+
const log = createLogger({ name: "cli" });
|
|
28
|
+
async function main() {
|
|
29
|
+
const argv = process.argv.slice(2);
|
|
30
|
+
let parsed;
|
|
31
|
+
try {
|
|
32
|
+
parsed = parseArgs(argv);
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
const e = toSkillHubError(err, "参数错误");
|
|
36
|
+
fail(e, false);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const code = await withUsageScope("cli", () => dispatch(parsed));
|
|
41
|
+
process.exitCode = code;
|
|
42
|
+
}
|
|
43
|
+
catch (err) {
|
|
44
|
+
const e = toSkillHubError(err);
|
|
45
|
+
fail(e, parsed.json);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function dispatch(parsed) {
|
|
49
|
+
const { command, json, positionals, flags, config } = parsed;
|
|
50
|
+
switch (command) {
|
|
51
|
+
case "usage":
|
|
52
|
+
case "context-cost": {
|
|
53
|
+
const allowed = command === "usage"
|
|
54
|
+
? ["log", "days", "origin"]
|
|
55
|
+
: ["catalog", "max-chars", "context-window"];
|
|
56
|
+
if (positionals.length > 1)
|
|
57
|
+
throw new SkillHubError({ code: "E_CONFIG", message: `${command}最多接受一个Skill名称` });
|
|
58
|
+
for (const key of Object.keys(flags)) {
|
|
59
|
+
if (!allowed.includes(key) || flags[key] === true)
|
|
60
|
+
throw new SkillHubError({ code: "E_CONFIG", message: `${command}参数无效: --${key}` });
|
|
61
|
+
}
|
|
62
|
+
return command === "usage"
|
|
63
|
+
? runUsage({
|
|
64
|
+
json,
|
|
65
|
+
name: positionals[0],
|
|
66
|
+
file: flagString(flags, "log"),
|
|
67
|
+
days: flagString(flags, "days"),
|
|
68
|
+
origin: flagString(flags, "origin"),
|
|
69
|
+
})
|
|
70
|
+
: runContextCost({
|
|
71
|
+
json,
|
|
72
|
+
name: positionals[0],
|
|
73
|
+
catalogPath: flagString(flags, "catalog"),
|
|
74
|
+
configPath: config,
|
|
75
|
+
maxChars: flagString(flags, "max-chars"),
|
|
76
|
+
contextWindow: flagString(flags, "context-window"),
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
case "version":
|
|
80
|
+
if (json)
|
|
81
|
+
printJson({ version: SKILL_HUB_VERSION });
|
|
82
|
+
else
|
|
83
|
+
console.log(SKILL_HUB_VERSION);
|
|
84
|
+
return 0;
|
|
85
|
+
case "help":
|
|
86
|
+
printHelp();
|
|
87
|
+
return 0;
|
|
88
|
+
case "doctor":
|
|
89
|
+
return runDoctor({ json, configPath: config });
|
|
90
|
+
case "takeover":
|
|
91
|
+
return runTakeover({
|
|
92
|
+
json,
|
|
93
|
+
configPath: config,
|
|
94
|
+
out: flagString(flags, "out"),
|
|
95
|
+
includePlugins: flagBool(flags, "include-plugins"),
|
|
96
|
+
});
|
|
97
|
+
case "install":
|
|
98
|
+
case "adopt":
|
|
99
|
+
case "update":
|
|
100
|
+
case "check-updates":
|
|
101
|
+
case "list": {
|
|
102
|
+
if (command === "check-updates") {
|
|
103
|
+
for (const key of Object.keys(flags)) {
|
|
104
|
+
if (!["skill", "dry-run"].includes(key)) {
|
|
105
|
+
throw new SkillHubError({ code: "E_CONFIG", message: `check-updates 不接受 --${key}` });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
if (flags.skill === true) {
|
|
109
|
+
throw new SkillHubError({ code: "E_CONFIG", message: "--skill 需要 Skill 名称" });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
const named = flagString(flags, "skill")
|
|
113
|
+
?.split(",")
|
|
114
|
+
.map((s) => s.trim())
|
|
115
|
+
.filter(Boolean);
|
|
116
|
+
return runManage({
|
|
117
|
+
action: command,
|
|
118
|
+
json,
|
|
119
|
+
configPath: config,
|
|
120
|
+
source: command === "install" || command === "adopt" ? positionals[0] : undefined,
|
|
121
|
+
names: named ??
|
|
122
|
+
(command === "update" || command === "list" || command === "check-updates"
|
|
123
|
+
? positionals.length
|
|
124
|
+
? positionals.flatMap((s) => s.split(","))
|
|
125
|
+
: undefined
|
|
126
|
+
: undefined),
|
|
127
|
+
ref: flagString(flags, "ref"),
|
|
128
|
+
planToken: flags["plan-token"] === true ? "" : flagString(flags, "plan-token"),
|
|
129
|
+
overlayOf: flags["overlay-of"] === true ? "" : flagString(flags, "overlay-of"),
|
|
130
|
+
apply: flagBool(flags, "apply"),
|
|
131
|
+
yes: flagBool(flags, "yes"),
|
|
132
|
+
dryRun: flagBool(flags, "dry-run"),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
case "migrate-source": {
|
|
136
|
+
for (const key of Object.keys(flags)) {
|
|
137
|
+
if (!["source", "ref", "plan-token", "apply", "yes", "dry-run"].includes(key)) {
|
|
138
|
+
throw new SkillHubError({ code: "E_CONFIG", message: `migrate-source 不接受 --${key}` });
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (positionals.length !== 1 || !flagString(flags, "source") || flags.ref === true) {
|
|
142
|
+
throw new SkillHubError({
|
|
143
|
+
code: "E_CONFIG",
|
|
144
|
+
message: "migrate-source <name> 需要 --source <新来源>;--ref 需要值",
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
return runManage({
|
|
148
|
+
action: "migrate-source",
|
|
149
|
+
names: positionals,
|
|
150
|
+
source: flagString(flags, "source"),
|
|
151
|
+
ref: flagString(flags, "ref"),
|
|
152
|
+
planToken: flags["plan-token"] === true ? "" : flagString(flags, "plan-token"),
|
|
153
|
+
json,
|
|
154
|
+
configPath: config,
|
|
155
|
+
apply: flagBool(flags, "apply"),
|
|
156
|
+
yes: flagBool(flags, "yes"),
|
|
157
|
+
dryRun: flagBool(flags, "dry-run"),
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
case "rollback": {
|
|
161
|
+
const name = commandName(positionals, flags, "rollback");
|
|
162
|
+
return runManage({
|
|
163
|
+
action: "rollback",
|
|
164
|
+
json,
|
|
165
|
+
configPath: config,
|
|
166
|
+
names: name ? [name] : [],
|
|
167
|
+
backupPath: flagString(flags, "from"),
|
|
168
|
+
planToken: flags["plan-token"] === true ? "" : flagString(flags, "plan-token"),
|
|
169
|
+
apply: flagBool(flags, "apply"),
|
|
170
|
+
yes: flagBool(flags, "yes"),
|
|
171
|
+
dryRun: flagBool(flags, "dry-run"),
|
|
172
|
+
});
|
|
173
|
+
}
|
|
174
|
+
case "inventory": {
|
|
175
|
+
const agents = flagString(flags, "agents");
|
|
176
|
+
const sources = flagString(flags, "sources");
|
|
177
|
+
const scopes = parseScopes(flagString(flags, "scope"));
|
|
178
|
+
return runInventory({
|
|
179
|
+
json,
|
|
180
|
+
configPath: config,
|
|
181
|
+
agentFilter: agents ? agents.split(",").map((s) => s.trim()) : undefined,
|
|
182
|
+
sourceFilter: sources ? sources.split(",").map((s) => s.trim()) : undefined,
|
|
183
|
+
scopeFilter: scopes,
|
|
184
|
+
outPath: flagString(flags, "out"),
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
case "init":
|
|
188
|
+
return runInit({ json, force: flagBool(flags, "force") });
|
|
189
|
+
case "ingest": {
|
|
190
|
+
const sourcesRaw = flagString(flags, "sources") ??
|
|
191
|
+
(positionals[0] && !positionals[0].startsWith("-") ? positionals[0] : undefined);
|
|
192
|
+
const configured = resolveHubConfigPaths(config ? await loadHubConfig(config) : await loadHubConfig());
|
|
193
|
+
const sources = sourcesRaw
|
|
194
|
+
? sourcesRaw
|
|
195
|
+
.split(",")
|
|
196
|
+
.map((s) => s.trim())
|
|
197
|
+
.filter(Boolean)
|
|
198
|
+
: (configured.sources ?? []).filter((s) => s.enabled).map((s) => s.id);
|
|
199
|
+
// 默认物化到 canonical;--no-materialize 仅写 catalog 元数据
|
|
200
|
+
const noMaterialize = flagBool(flags, "no-materialize");
|
|
201
|
+
return runIngest({
|
|
202
|
+
json,
|
|
203
|
+
sources,
|
|
204
|
+
dryRun: flagBool(flags, "dry-run"),
|
|
205
|
+
prefer: flagString(flags, "prefer"),
|
|
206
|
+
conflictMode: flagString(flags, "conflict") === "skip" ? "skip" : "report",
|
|
207
|
+
catalogPath: flagString(flags, "catalog"),
|
|
208
|
+
materialize: !noMaterialize,
|
|
209
|
+
forceMaterialize: flagBool(flags, "force"),
|
|
210
|
+
sourceMappings: agentSkillSources(configured),
|
|
211
|
+
canonicalDir: configured.canonical_dir,
|
|
212
|
+
backupDir: configured.backup_dir,
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
case "index":
|
|
216
|
+
return runIndex({
|
|
217
|
+
json,
|
|
218
|
+
configPath: config,
|
|
219
|
+
rebuild: flagBool(flags, "rebuild"),
|
|
220
|
+
catalogPath: flagString(flags, "catalog"),
|
|
221
|
+
indexDir: flagString(flags, "index-dir"),
|
|
222
|
+
});
|
|
223
|
+
case "route": {
|
|
224
|
+
const q = positionals.join(" ").trim() || flagString(flags, "query") || "";
|
|
225
|
+
const topK = parseIntegerFlag(flags, "top-k", 5);
|
|
226
|
+
const scopes = parseScopes(flagString(flags, "scope"));
|
|
227
|
+
return runRoute({
|
|
228
|
+
json,
|
|
229
|
+
query: q,
|
|
230
|
+
topK,
|
|
231
|
+
profile: flagString(flags, "profile"),
|
|
232
|
+
includeBody: flagBool(flags, "include-body"),
|
|
233
|
+
catalogPath: flagString(flags, "catalog"),
|
|
234
|
+
configPath: config,
|
|
235
|
+
indexDir: flagString(flags, "index-dir"),
|
|
236
|
+
engine: flagString(flags, "engine"),
|
|
237
|
+
profilesDir: flagString(flags, "profiles-dir"),
|
|
238
|
+
scopes,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
case "audit":
|
|
242
|
+
return runAudit({
|
|
243
|
+
json,
|
|
244
|
+
name: positionals.join(" ").trim() || flagString(flags, "name"),
|
|
245
|
+
catalogPath: flagString(flags, "catalog"),
|
|
246
|
+
configPath: config,
|
|
247
|
+
failOn: flagString(flags, "fail-on"),
|
|
248
|
+
});
|
|
249
|
+
case "manifest":
|
|
250
|
+
return runManifest({
|
|
251
|
+
json,
|
|
252
|
+
outputPath: flagString(flags, "out"),
|
|
253
|
+
apply: flagBool(flags, "apply"),
|
|
254
|
+
yes: flagBool(flags, "yes"),
|
|
255
|
+
dryRun: flagBool(flags, "dry-run"),
|
|
256
|
+
catalogPath: flagString(flags, "catalog"),
|
|
257
|
+
configPath: config,
|
|
258
|
+
});
|
|
259
|
+
case "browse":
|
|
260
|
+
return runBrowse({
|
|
261
|
+
json,
|
|
262
|
+
query: positionals.join(" ").trim() || flagString(flags, "query"),
|
|
263
|
+
offset: parseOptionalIntegerFlag(flags, "offset"),
|
|
264
|
+
limit: parseOptionalIntegerFlag(flags, "limit"),
|
|
265
|
+
catalogPath: flagString(flags, "catalog"),
|
|
266
|
+
configPath: config,
|
|
267
|
+
});
|
|
268
|
+
case "files":
|
|
269
|
+
return runFiles({
|
|
270
|
+
json,
|
|
271
|
+
name: commandName(positionals, flags, "files"),
|
|
272
|
+
path: flagString(flags, "path"),
|
|
273
|
+
offset: parseOptionalIntegerFlag(flags, "offset"),
|
|
274
|
+
limit: parseOptionalIntegerFlag(flags, "limit"),
|
|
275
|
+
catalogPath: flagString(flags, "catalog"),
|
|
276
|
+
configPath: config,
|
|
277
|
+
});
|
|
278
|
+
case "read":
|
|
279
|
+
return runRead({
|
|
280
|
+
json,
|
|
281
|
+
name: commandName(positionals, flags, "read"),
|
|
282
|
+
file: flagString(flags, "file"),
|
|
283
|
+
offset: parseOptionalIntegerFlag(flags, "offset"),
|
|
284
|
+
maxChars: parseOptionalIntegerFlag(flags, "max-chars"),
|
|
285
|
+
catalogPath: flagString(flags, "catalog"),
|
|
286
|
+
configPath: config,
|
|
287
|
+
});
|
|
288
|
+
case "explain":
|
|
289
|
+
return runExplain({
|
|
290
|
+
json,
|
|
291
|
+
name: positionals.join(" ").trim() || flagString(flags, "name") || "",
|
|
292
|
+
catalogPath: flagString(flags, "catalog"),
|
|
293
|
+
configPath: config,
|
|
294
|
+
historyPath: flagString(flags, "history"),
|
|
295
|
+
});
|
|
296
|
+
case "path":
|
|
297
|
+
return runPath({
|
|
298
|
+
json,
|
|
299
|
+
name: positionals.join(" ").trim() || flagString(flags, "name") || "",
|
|
300
|
+
catalogPath: flagString(flags, "catalog"),
|
|
301
|
+
configPath: config,
|
|
302
|
+
});
|
|
303
|
+
case "history": {
|
|
304
|
+
return runHistory({
|
|
305
|
+
json,
|
|
306
|
+
name: positionals.join(" ").trim() || flagString(flags, "name"),
|
|
307
|
+
historyPath: flagString(flags, "history"),
|
|
308
|
+
limit: parseOptionalIntegerFlag(flags, "limit"),
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
case "backup":
|
|
312
|
+
return runBackup({
|
|
313
|
+
json,
|
|
314
|
+
configPath: config,
|
|
315
|
+
out: flagString(flags, "out"),
|
|
316
|
+
notes: flagString(flags, "notes"),
|
|
317
|
+
dryRun: flagBool(flags, "dry-run"),
|
|
318
|
+
includeCanonical: !flagBool(flags, "no-canonical"),
|
|
319
|
+
});
|
|
320
|
+
case "verify":
|
|
321
|
+
return runVerify({
|
|
322
|
+
json,
|
|
323
|
+
configPath: config,
|
|
324
|
+
catalogPath: flagString(flags, "catalog"),
|
|
325
|
+
});
|
|
326
|
+
case "restore": {
|
|
327
|
+
const agents = flagString(flags, "agents");
|
|
328
|
+
return runRestore({
|
|
329
|
+
json,
|
|
330
|
+
from: flagString(flags, "from") ?? positionals[0] ?? "",
|
|
331
|
+
yes: flagBool(flags, "yes"),
|
|
332
|
+
apply: flagBool(flags, "apply"),
|
|
333
|
+
dryRun: flagBool(flags, "dry-run") || !flagBool(flags, "apply"),
|
|
334
|
+
agents: agents ? agents.split(",").map((s) => s.trim()) : undefined,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
case "sync": {
|
|
338
|
+
const agentsFlag = flagString(flags, "agents");
|
|
339
|
+
return runSync({
|
|
340
|
+
json,
|
|
341
|
+
apply: flagBool(flags, "apply"),
|
|
342
|
+
dryRun: flagBool(flags, "dry-run") || !flagBool(flags, "apply"),
|
|
343
|
+
requireBackup: flagBool(flags, "require-backup"),
|
|
344
|
+
yes: flagBool(flags, "yes"),
|
|
345
|
+
allowWrite: flagBool(flags, "allow-write"),
|
|
346
|
+
backupDir: flagString(flags, "backup-dir"),
|
|
347
|
+
configPath: config,
|
|
348
|
+
catalogPath: flagString(flags, "catalog"),
|
|
349
|
+
agents: agentsFlag
|
|
350
|
+
? agentsFlag
|
|
351
|
+
.split(",")
|
|
352
|
+
.map((s) => s.trim())
|
|
353
|
+
.filter(Boolean)
|
|
354
|
+
: undefined,
|
|
355
|
+
createOnly: flagBool(flags, "create-only"),
|
|
356
|
+
replaceReal: flagBool(flags, "replace-real"),
|
|
357
|
+
});
|
|
358
|
+
}
|
|
359
|
+
default:
|
|
360
|
+
printLines([`未知命令: ${command}`, "运行 skill-hub help 查看用法"]);
|
|
361
|
+
return 1;
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
function printHelp() {
|
|
365
|
+
printLines([
|
|
366
|
+
`skill-hub ${SKILL_HUB_VERSION}`,
|
|
367
|
+
"",
|
|
368
|
+
"用法: skill-hub [--json] [--config <path>] <command> [options]",
|
|
369
|
+
"",
|
|
370
|
+
"命令:",
|
|
371
|
+
" doctor 环境自检(只读)",
|
|
372
|
+
" usage [name] [--log path] [--days 7|30] [--origin cli|mcp|web|library] 本地观测报告(只读)",
|
|
373
|
+
" context-cost [name] [--max-chars 8000] [--context-window N] [--catalog path] 静态上下文粗估(非账单)",
|
|
374
|
+
" takeover [--include-plugins] [--out report.json] 全目录接管预览(只读)",
|
|
375
|
+
" install <path|owner/repo> [--skill name,...] [--ref ref] [--overlay-of base] [--apply --yes]",
|
|
376
|
+
" adopt <path|owner/repo> [--skill name,...] [--ref ref] [--overlay-of base] [--apply --yes] 绑定相同内容的现有技能",
|
|
377
|
+
" update [name,...] [--plan-token token --apply --yes] 预览文件差异,写入需同一计划指纹",
|
|
378
|
+
" check-updates [name,...] 逐项只读检查来源更新与本地修改;不完整时退出3",
|
|
379
|
+
" migrate-source <name> --source <path|owner/repo> [--ref ref] [--plan-token token --apply --yes] 同内容来源迁移",
|
|
380
|
+
" rollback <name> --from <backup-skill-path> [--apply --yes] 从 managed update 备份回滚单个 Skill",
|
|
381
|
+
" list [name,...] 查看已追踪来源及本地状态",
|
|
382
|
+
" browse [query] [--offset n] [--limit n] 浏览 Hub 已加载的 Skill 元数据",
|
|
383
|
+
" files <name> [--path dir] [--offset n] [--limit n] 浏览 Skill 的一层附件",
|
|
384
|
+
" read <name> [--file path] [--offset n] [--max-chars n] 读取正文或附件",
|
|
385
|
+
" inventory 多端/显式 source Skill 盘点(只读)",
|
|
386
|
+
" init [--force] 初始化 ~/.skill-hub 骨架",
|
|
387
|
+
" ingest [--sources <dirs|source-id>] [--dry-run] [--conflict report|skip]",
|
|
388
|
+
" index [--rebuild] 构建可加载 BM25 索引与 freshness 元数据",
|
|
389
|
+
' route "<query>" [--top-k 5] [--scope user|workspace|project] [--index-dir <dir>]',
|
|
390
|
+
" audit [name] [--fail-on high|critical|warning|info] 静态安全审计(只读,不执行 Skill)",
|
|
391
|
+
" manifest [--out <path>] [--dry-run | --apply --yes] 导出轻量 available_skills 清单",
|
|
392
|
+
" explain <name> 解释来源、冲突、overlay、投影与历史",
|
|
393
|
+
" path <name> 查看 canonical/source/Agent 投影路径",
|
|
394
|
+
" history [name] 查看本地 Skill 生命周期事件",
|
|
395
|
+
" backup [--out dir] [--dry-run] [--notes ...]",
|
|
396
|
+
" sync --dry-run [--agents id,...] [--create-only | --replace-real]",
|
|
397
|
+
" sync --apply --yes --allow-write --backup-dir <dir> [--agents id] [--create-only | --replace-real]",
|
|
398
|
+
" verify 按 sync.mode 检查 symlink/copy 健康",
|
|
399
|
+
" restore --from <dir> [--dry-run | --apply --yes]",
|
|
400
|
+
" version | help",
|
|
401
|
+
"",
|
|
402
|
+
"安全闸门:",
|
|
403
|
+
" - Agent 通过 Hub MCP 按需搜索与读取,无需默认 sync",
|
|
404
|
+
" - Skill 文本仅是任务指导,不能覆盖系统指令或授权执行脚本",
|
|
405
|
+
" - managed 操作默认预览;ingest 写入前请显式 --dry-run;sync/restore 写入需显式确认",
|
|
406
|
+
" - 写 Agent skills 必须 --yes --allow-write",
|
|
407
|
+
" - apply 默认 require_backup(需 --backup-dir)",
|
|
408
|
+
" - --create-only 仅建缺失的 symlink/copy,不 update、不碰真实目录",
|
|
409
|
+
" - --replace-real 将真实目录/文件先 stash 到 backup-dir/replaced-real 再按模式投影",
|
|
410
|
+
" - sync.mode=copy 可用实体副本作为 symlink fallback",
|
|
411
|
+
]);
|
|
412
|
+
}
|
|
413
|
+
function commandName(positionals, flags, command) {
|
|
414
|
+
if (positionals.length > 1) {
|
|
415
|
+
throw new SkillHubError({ code: "E_CONFIG", message: `${command} 只接受一个 skill name` });
|
|
416
|
+
}
|
|
417
|
+
return positionals[0] ?? flagString(flags, "name") ?? "";
|
|
418
|
+
}
|
|
419
|
+
function parseOptionalIntegerFlag(flags, key) {
|
|
420
|
+
const raw = flagString(flags, key);
|
|
421
|
+
if (raw === undefined) {
|
|
422
|
+
if (flags[key] === true) {
|
|
423
|
+
throw new SkillHubError({ code: "E_CONFIG", message: `--${key} 需要整数参数` });
|
|
424
|
+
}
|
|
425
|
+
return undefined;
|
|
426
|
+
}
|
|
427
|
+
if (!/^(?:0|[1-9]\d*)$/.test(raw)) {
|
|
428
|
+
throw new SkillHubError({ code: "E_CONFIG", message: `--${key} 必须是非负整数` });
|
|
429
|
+
}
|
|
430
|
+
const value = Number(raw);
|
|
431
|
+
if (!Number.isSafeInteger(value)) {
|
|
432
|
+
throw new SkillHubError({ code: "E_CONFIG", message: `--${key} 必须是安全整数` });
|
|
433
|
+
}
|
|
434
|
+
return value;
|
|
435
|
+
}
|
|
436
|
+
function parseIntegerFlag(flags, key, fallback) {
|
|
437
|
+
return parseOptionalIntegerFlag(flags, key) ?? fallback;
|
|
438
|
+
}
|
|
439
|
+
function parseScopes(raw) {
|
|
440
|
+
if (!raw)
|
|
441
|
+
return undefined;
|
|
442
|
+
const values = raw
|
|
443
|
+
.split(",")
|
|
444
|
+
.map((s) => s.trim())
|
|
445
|
+
.filter(Boolean);
|
|
446
|
+
const allowed = new Set(["user", "workspace", "project"]);
|
|
447
|
+
for (const value of values) {
|
|
448
|
+
if (!allowed.has(value)) {
|
|
449
|
+
throw new SkillHubError({
|
|
450
|
+
code: "E_CONFIG",
|
|
451
|
+
message: `scope 无效: ${value}(应为 user|workspace|project)`,
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
return values;
|
|
456
|
+
}
|
|
457
|
+
function fail(err, json) {
|
|
458
|
+
const e = err instanceof SkillHubError ? err : toSkillHubError(err);
|
|
459
|
+
if (json) {
|
|
460
|
+
printJson(e.toJSON());
|
|
461
|
+
}
|
|
462
|
+
else {
|
|
463
|
+
console.error(e.toDisplayString());
|
|
464
|
+
}
|
|
465
|
+
log.debug("command failed", { code: e.code });
|
|
466
|
+
process.exitCode = e.exitCode;
|
|
467
|
+
}
|
|
468
|
+
void main();
|