dsh-skills-anywhere 0.3.2
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/README.md +275 -0
- package/README.zh.md +275 -0
- package/cordis.patch.yml +39 -0
- package/lib/cli.d.ts +9 -0
- package/lib/cli.js +501 -0
- package/lib/index.d.ts +40 -0
- package/lib/index.js +25 -0
- package/lib/mcp.d.ts +48 -0
- package/lib/mcp.js +312 -0
- package/lib/provider-C8Jncd8r.js +1937 -0
- package/lib/provider-gYkY_3P9.d.ts +389 -0
- package/lib/search-DUE-pmE0.js +48 -0
- package/lib/tools.d.ts +58 -0
- package/lib/tools.js +239 -0
- package/package.json +122 -0
package/lib/cli.js
ADDED
|
@@ -0,0 +1,501 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { a as readSourcesFile, b as projectSourcesFile, d as findProjectRoot, g as AGENTS, i as readLock, l as writeSourcesFile, o as resolveSource, r as hasGit, s as sameRepository, t as SkillsAnywhereProvider, x as resolveConfig } from "./provider-C8Jncd8r.js";
|
|
3
|
+
import { isAbsolute, relative, sep } from "node:path";
|
|
4
|
+
import { parseArgs } from "node:util";
|
|
5
|
+
//#region src/cli.ts
|
|
6
|
+
/**
|
|
7
|
+
* `dsh-skills-anywhere` command line: inspect what the provider would give
|
|
8
|
+
* dsh, manage git sources, and diagnose skipped skills — without booting dsh.
|
|
9
|
+
*
|
|
10
|
+
* @module
|
|
11
|
+
*/
|
|
12
|
+
const HELP = `dsh-skills-anywhere — your skills, anywhere.
|
|
13
|
+
|
|
14
|
+
Usage: dsh-skills-anywhere <command> [options]
|
|
15
|
+
|
|
16
|
+
Commands
|
|
17
|
+
list Skills the provider would publish to dsh (default)
|
|
18
|
+
agents Supported agents and which directories exist here
|
|
19
|
+
sources Configured git sources and their synced commits
|
|
20
|
+
add <source> Add a git source (owner/repo, owner/repo/sub/dir,
|
|
21
|
+
https://github.com/o/r/tree/main/dir, git URL, or path)
|
|
22
|
+
remove <source> Remove a git source
|
|
23
|
+
sync Clone or refresh every source now
|
|
24
|
+
doctor Explain skipped, repaired, and duplicate skills
|
|
25
|
+
mcp Serve the same skills to any MCP client over stdio
|
|
26
|
+
|
|
27
|
+
Options
|
|
28
|
+
--cwd <dir> Project directory (default: current directory)
|
|
29
|
+
--project add/remove: use <project>/.dsh/skills-anywhere.json
|
|
30
|
+
--ref <ref> add: branch, tag, or commit
|
|
31
|
+
--path <dir> add: sub-directory inside the repository
|
|
32
|
+
--rank <n> add: precedence rank inside dsh (lower wins)
|
|
33
|
+
--force sync: refresh even when pinned to a commit
|
|
34
|
+
--all list: include dropped duplicates
|
|
35
|
+
--json Machine-readable output
|
|
36
|
+
-h, --help Show this help
|
|
37
|
+
`;
|
|
38
|
+
async function main(argv = process.argv.slice(2)) {
|
|
39
|
+
let parsed;
|
|
40
|
+
try {
|
|
41
|
+
parsed = parseArgs({
|
|
42
|
+
args: [...argv],
|
|
43
|
+
allowPositionals: true,
|
|
44
|
+
options: {
|
|
45
|
+
cwd: { type: "string" },
|
|
46
|
+
json: {
|
|
47
|
+
type: "boolean",
|
|
48
|
+
default: false
|
|
49
|
+
},
|
|
50
|
+
all: {
|
|
51
|
+
type: "boolean",
|
|
52
|
+
default: false
|
|
53
|
+
},
|
|
54
|
+
project: {
|
|
55
|
+
type: "boolean",
|
|
56
|
+
default: false
|
|
57
|
+
},
|
|
58
|
+
force: {
|
|
59
|
+
type: "boolean",
|
|
60
|
+
default: false
|
|
61
|
+
},
|
|
62
|
+
ref: { type: "string" },
|
|
63
|
+
path: { type: "string" },
|
|
64
|
+
rank: { type: "string" },
|
|
65
|
+
help: {
|
|
66
|
+
type: "boolean",
|
|
67
|
+
short: "h",
|
|
68
|
+
default: false
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
} catch (error) {
|
|
73
|
+
console.error(String(error instanceof Error ? error.message : error));
|
|
74
|
+
console.error(HELP);
|
|
75
|
+
return 2;
|
|
76
|
+
}
|
|
77
|
+
if (parsed.values.help) {
|
|
78
|
+
console.log(HELP);
|
|
79
|
+
return 0;
|
|
80
|
+
}
|
|
81
|
+
const [command = "list", ...positional] = parsed.positionals;
|
|
82
|
+
const cli = {
|
|
83
|
+
command,
|
|
84
|
+
positional,
|
|
85
|
+
cwd: parsed.values.cwd ?? process.cwd(),
|
|
86
|
+
json: parsed.values.json ?? false,
|
|
87
|
+
all: parsed.values.all ?? false,
|
|
88
|
+
project: parsed.values.project ?? false,
|
|
89
|
+
force: parsed.values.force ?? false,
|
|
90
|
+
...parsed.values.ref !== void 0 ? { ref: parsed.values.ref } : {},
|
|
91
|
+
...parsed.values.path !== void 0 ? { path: parsed.values.path } : {},
|
|
92
|
+
...parsed.values.rank !== void 0 ? { rank: Number(parsed.values.rank) } : {}
|
|
93
|
+
};
|
|
94
|
+
const config = resolveConfig({
|
|
95
|
+
watch: false,
|
|
96
|
+
sync: false
|
|
97
|
+
});
|
|
98
|
+
switch (command) {
|
|
99
|
+
case "list": return await list(cli, config);
|
|
100
|
+
case "agents": return agents(cli, config);
|
|
101
|
+
case "sources": return await sources(cli, config);
|
|
102
|
+
case "add": return await add(cli, config);
|
|
103
|
+
case "remove":
|
|
104
|
+
case "rm": return await remove(cli, config);
|
|
105
|
+
case "sync": return await sync(cli, config);
|
|
106
|
+
case "doctor": return await doctor(cli, config);
|
|
107
|
+
case "mcp": return await mcp(cli);
|
|
108
|
+
default:
|
|
109
|
+
console.error(`unknown command "${command}"\n`);
|
|
110
|
+
console.error(HELP);
|
|
111
|
+
return 2;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function logger() {
|
|
115
|
+
return {
|
|
116
|
+
info: (message) => console.error(message),
|
|
117
|
+
warn: (message) => console.error(message)
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
async function collect(cli, config) {
|
|
121
|
+
return (await collectWithSources(cli, config)).report;
|
|
122
|
+
}
|
|
123
|
+
/** One discovery pass plus the resolved git sources, so paths inside a source checkout can be shown repo-relative. */
|
|
124
|
+
async function collectWithSources(cli, config) {
|
|
125
|
+
const provider = new SkillsAnywhereProvider(config, logger());
|
|
126
|
+
try {
|
|
127
|
+
await provider.list({ cwd: cli.cwd });
|
|
128
|
+
return {
|
|
129
|
+
report: provider.report() ?? {
|
|
130
|
+
skills: [],
|
|
131
|
+
dropped: [],
|
|
132
|
+
invalid: [],
|
|
133
|
+
roots: [],
|
|
134
|
+
complete: true
|
|
135
|
+
},
|
|
136
|
+
sourceDirs: (await provider.sources(cli.cwd)).map((source) => source.dir)
|
|
137
|
+
};
|
|
138
|
+
} finally {
|
|
139
|
+
await provider.dispose();
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** `~`-shortened path, or the path inside its git source checkout (the FROM column already names the repo). */
|
|
143
|
+
function displayPath(path, config, sourceDirs) {
|
|
144
|
+
return sourceDirs.map((dir) => relative(dir, path)).filter((rel) => rel !== "" && !rel.startsWith("..") && !isAbsolute(rel)).toSorted((a, b) => a.length - b.length)[0] ?? shorten(path, config.home);
|
|
145
|
+
}
|
|
146
|
+
function originLabel(skill) {
|
|
147
|
+
const { origin } = skill;
|
|
148
|
+
switch (origin.kind) {
|
|
149
|
+
case "agent": return `${origin.agent ?? "agent"} (${origin.scope ?? "?"})`;
|
|
150
|
+
case "claude-plugins": return `claude plugin ${origin.plugin ?? "?"}${origin.marketplace !== void 0 ? ` @ ${origin.marketplace}` : ""}`;
|
|
151
|
+
case "source": return `source ${origin.repo ?? "?"}`;
|
|
152
|
+
default: return `custom (${origin.scope ?? "?"})`;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function table(rows) {
|
|
156
|
+
if (rows.length === 0) return "";
|
|
157
|
+
const widths = rows[0].map((_, column) => Math.max(...rows.map((row) => (row[column] ?? "").length)));
|
|
158
|
+
return rows.map((row) => row.map((cell, column) => cell.padEnd(widths[column] ?? 0)).join(" ").trimEnd()).join("\n");
|
|
159
|
+
}
|
|
160
|
+
async function list(cli, config) {
|
|
161
|
+
const { report, sourceDirs } = await collectWithSources(cli, config);
|
|
162
|
+
if (cli.json) {
|
|
163
|
+
console.log(JSON.stringify({
|
|
164
|
+
skills: report.skills.map((skill) => ({
|
|
165
|
+
name: skill.name,
|
|
166
|
+
description: skill.description,
|
|
167
|
+
source: skill.source,
|
|
168
|
+
rank: skill.rank,
|
|
169
|
+
path: skill.path,
|
|
170
|
+
origin: skill.origin,
|
|
171
|
+
warnings: skill.warnings
|
|
172
|
+
})),
|
|
173
|
+
...cli.all ? { dropped: report.dropped.map((entry) => ({
|
|
174
|
+
name: entry.skill.name,
|
|
175
|
+
path: entry.skill.path,
|
|
176
|
+
reason: entry.reason,
|
|
177
|
+
winner: entry.winner.path
|
|
178
|
+
})) } : {},
|
|
179
|
+
invalid: report.invalid.map((entry) => ({
|
|
180
|
+
path: entry.path,
|
|
181
|
+
reason: entry.reason
|
|
182
|
+
})),
|
|
183
|
+
complete: report.complete
|
|
184
|
+
}, null, 2));
|
|
185
|
+
return 0;
|
|
186
|
+
}
|
|
187
|
+
if (report.skills.length === 0) console.log("No skills found outside the dsh defaults. Try: dsh-skills-anywhere add anthropics/skills");
|
|
188
|
+
else console.log(table([[
|
|
189
|
+
"NAME",
|
|
190
|
+
"FROM",
|
|
191
|
+
"PATH"
|
|
192
|
+
], ...report.skills.map((skill) => [
|
|
193
|
+
skill.name,
|
|
194
|
+
originLabel(skill),
|
|
195
|
+
displayPath(skill.path, config, sourceDirs)
|
|
196
|
+
])]));
|
|
197
|
+
if (cli.all && report.dropped.length > 0) {
|
|
198
|
+
console.log(`\nDropped duplicates (${report.dropped.length}):`);
|
|
199
|
+
console.log(table(report.dropped.map((entry) => [
|
|
200
|
+
entry.skill.name,
|
|
201
|
+
entry.reason,
|
|
202
|
+
shorten(entry.skill.path, config.home),
|
|
203
|
+
`-> ${shorten(entry.winner.path, config.home)}`
|
|
204
|
+
])));
|
|
205
|
+
}
|
|
206
|
+
const renamed = report.skills.filter(isRenamed).length;
|
|
207
|
+
const repaired = report.skills.filter((skill) => skill.warnings.length > 0 && !isRenamed(skill)).length;
|
|
208
|
+
console.log(`\n${report.skills.length} skills` + (report.dropped.length > 0 ? `, ${report.dropped.length} duplicates hidden` : "") + (report.invalid.length > 0 ? `, ${report.invalid.length} skipped` : "") + (renamed > 0 ? `, ${renamed} renamed` : "") + (repaired > 0 ? `, ${repaired} repaired` : "") + (report.invalid.length + repaired + renamed > 0 ? " — run `dsh-skills-anywhere doctor` for details" : ""));
|
|
209
|
+
return 0;
|
|
210
|
+
}
|
|
211
|
+
function isRenamed(skill) {
|
|
212
|
+
return typeof skill.metadata.skillsAnywhere?.renamedFrom === "string";
|
|
213
|
+
}
|
|
214
|
+
async function agents(cli, config) {
|
|
215
|
+
const provider = new SkillsAnywhereProvider(config, logger());
|
|
216
|
+
try {
|
|
217
|
+
const roots = await provider.roots(cli.cwd);
|
|
218
|
+
const existing = new Set((await collect(cli, config)).roots.filter((root) => root.exists).map((root) => root.root.path));
|
|
219
|
+
const rows = AGENTS.map((agent) => {
|
|
220
|
+
const projectRoot = roots.find((root) => root.origin.kind === "agent" && root.origin.agent === agent.id && root.origin.scope === "project");
|
|
221
|
+
const userRoot = roots.find((root) => root.origin.kind === "agent" && root.origin.agent === agent.id && root.origin.scope === "user");
|
|
222
|
+
return {
|
|
223
|
+
id: agent.id,
|
|
224
|
+
label: agent.label,
|
|
225
|
+
project: agent.project ?? null,
|
|
226
|
+
user: agent.user !== void 0 ? `~/${agent.user}` : null,
|
|
227
|
+
present: projectRoot !== void 0 && existing.has(projectRoot.path) || userRoot !== void 0 && existing.has(userRoot.path)
|
|
228
|
+
};
|
|
229
|
+
});
|
|
230
|
+
if (cli.json) console.log(JSON.stringify(rows, null, 2));
|
|
231
|
+
else {
|
|
232
|
+
console.log(table([[
|
|
233
|
+
"AGENT",
|
|
234
|
+
"ID",
|
|
235
|
+
"PROJECT DIR",
|
|
236
|
+
"USER DIR",
|
|
237
|
+
"FOUND HERE"
|
|
238
|
+
], ...rows.map((row) => [
|
|
239
|
+
row.label,
|
|
240
|
+
row.id,
|
|
241
|
+
row.project ?? "-",
|
|
242
|
+
row.user ?? "-",
|
|
243
|
+
row.present ? "yes" : ""
|
|
244
|
+
])]));
|
|
245
|
+
console.log(`\n${rows.length} agents supported; ${rows.filter((row) => row.present).length} have a skills directory on this machine.`);
|
|
246
|
+
console.log("(.agents/skills and .dsh/skills are handled by the built-in dsh provider.)");
|
|
247
|
+
}
|
|
248
|
+
return 0;
|
|
249
|
+
} finally {
|
|
250
|
+
await provider.dispose();
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
async function sources(cli, config) {
|
|
254
|
+
const provider = new SkillsAnywhereProvider(config, logger());
|
|
255
|
+
try {
|
|
256
|
+
const resolved = await provider.sources(cli.cwd);
|
|
257
|
+
const lock = await readLock(config.lockFile);
|
|
258
|
+
if (cli.json) {
|
|
259
|
+
console.log(JSON.stringify(resolved.map((source) => ({
|
|
260
|
+
...source,
|
|
261
|
+
lock: lock[source.key] ?? null
|
|
262
|
+
})), null, 2));
|
|
263
|
+
return 0;
|
|
264
|
+
}
|
|
265
|
+
if (resolved.length === 0) {
|
|
266
|
+
console.log("No git sources configured. Add one with: dsh-skills-anywhere add owner/repo");
|
|
267
|
+
return 0;
|
|
268
|
+
}
|
|
269
|
+
console.log(table([[
|
|
270
|
+
"SOURCE",
|
|
271
|
+
"REF",
|
|
272
|
+
"SYNCED COMMIT",
|
|
273
|
+
"CACHE"
|
|
274
|
+
], ...resolved.map((source) => [
|
|
275
|
+
source.display,
|
|
276
|
+
source.ref ?? "(default)",
|
|
277
|
+
lock[source.key]?.sha.slice(0, 12) ?? "(never)",
|
|
278
|
+
shorten(source.dir, config.home)
|
|
279
|
+
])]));
|
|
280
|
+
console.log(`\nuser file: ${shorten(config.userSourcesFile, config.home)}`);
|
|
281
|
+
console.log(`project file: ${shorten(projectSourcesFile(await findProjectRoot(cli.cwd)), config.home)}`);
|
|
282
|
+
return 0;
|
|
283
|
+
} finally {
|
|
284
|
+
await provider.dispose();
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async function targetFile(cli, config) {
|
|
288
|
+
return cli.project ? projectSourcesFile(await findProjectRoot(cli.cwd)) : config.userSourcesFile;
|
|
289
|
+
}
|
|
290
|
+
async function add(cli, config) {
|
|
291
|
+
const input = cli.positional[0];
|
|
292
|
+
if (input === void 0) {
|
|
293
|
+
console.error("usage: dsh-skills-anywhere add <source> [--ref <ref>] [--path <dir>] [--rank <n>] [--project]");
|
|
294
|
+
return 2;
|
|
295
|
+
}
|
|
296
|
+
const spec = {
|
|
297
|
+
repo: input,
|
|
298
|
+
...cli.ref !== void 0 ? { ref: cli.ref } : {},
|
|
299
|
+
...cli.path !== void 0 ? { path: cli.path } : {},
|
|
300
|
+
...cli.rank !== void 0 && Number.isFinite(cli.rank) ? { rank: cli.rank } : {}
|
|
301
|
+
};
|
|
302
|
+
let resolved;
|
|
303
|
+
try {
|
|
304
|
+
resolved = resolveSource(spec, config.cacheDir);
|
|
305
|
+
} catch (error) {
|
|
306
|
+
console.error(String(error instanceof Error ? error.message : error));
|
|
307
|
+
return 2;
|
|
308
|
+
}
|
|
309
|
+
const file = await targetFile(cli, config);
|
|
310
|
+
const existing = await readSourcesFile(file);
|
|
311
|
+
if (existing.some((entry) => sameSourcePath(entry, resolved, config.cacheDir))) {
|
|
312
|
+
console.log(`${resolved.display} is already in ${shorten(file, config.home)}`);
|
|
313
|
+
return 0;
|
|
314
|
+
}
|
|
315
|
+
await writeSourcesFile(file, [...existing, spec]);
|
|
316
|
+
console.log(`added ${resolved.display} to ${shorten(file, config.home)}`);
|
|
317
|
+
if (!await hasGit()) {
|
|
318
|
+
console.error("git was not found on PATH; the source will sync once git is installed.");
|
|
319
|
+
return 0;
|
|
320
|
+
}
|
|
321
|
+
const provider = new SkillsAnywhereProvider(config, logger());
|
|
322
|
+
try {
|
|
323
|
+
const mine = (await provider.syncAll(cli.cwd)).find((result) => result.source.id === resolved.id);
|
|
324
|
+
if (mine !== void 0) printSync([mine], config);
|
|
325
|
+
const fromSource = (await collectWith(provider, cli)).skills.filter((skill) => skill.origin.kind === "source" && skill.origin.repo === resolved.display);
|
|
326
|
+
console.log(`${fromSource.length} skills available from ${resolved.display}`);
|
|
327
|
+
return mine?.status === "failed" ? 1 : 0;
|
|
328
|
+
} finally {
|
|
329
|
+
await provider.dispose();
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
async function collectWith(provider, cli) {
|
|
333
|
+
await provider.list({ cwd: cli.cwd });
|
|
334
|
+
return provider.report() ?? {
|
|
335
|
+
skills: [],
|
|
336
|
+
dropped: [],
|
|
337
|
+
invalid: [],
|
|
338
|
+
roots: [],
|
|
339
|
+
complete: true
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
/** Same repository and same sub-path, comparing resolved values so `o/r/sub` and `{ repo: 'o/r', path: 'sub' }` match. */
|
|
343
|
+
function sameSourcePath(entry, resolved, cacheDir) {
|
|
344
|
+
try {
|
|
345
|
+
const other = resolveSource(entry, cacheDir);
|
|
346
|
+
return other.id === resolved.id && (other.path ?? "") === (resolved.path ?? "");
|
|
347
|
+
} catch {
|
|
348
|
+
return false;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
async function remove(cli, config) {
|
|
352
|
+
const input = cli.positional[0];
|
|
353
|
+
if (input === void 0) {
|
|
354
|
+
console.error("usage: dsh-skills-anywhere remove <source> [--project]");
|
|
355
|
+
return 2;
|
|
356
|
+
}
|
|
357
|
+
const file = await targetFile(cli, config);
|
|
358
|
+
const existing = await readSourcesFile(file);
|
|
359
|
+
const remaining = existing.filter((entry) => !sameRepository(entry, input, config.cacheDir));
|
|
360
|
+
if (remaining.length === existing.length) {
|
|
361
|
+
console.error(`${input} is not listed in ${shorten(file, config.home)}`);
|
|
362
|
+
return 1;
|
|
363
|
+
}
|
|
364
|
+
await writeSourcesFile(file, remaining);
|
|
365
|
+
console.log(`removed ${input} from ${shorten(file, config.home)} (cached checkout kept under ${shorten(config.cacheDir, config.home)})`);
|
|
366
|
+
return 0;
|
|
367
|
+
}
|
|
368
|
+
async function sync(cli, config) {
|
|
369
|
+
const provider = new SkillsAnywhereProvider(config, logger());
|
|
370
|
+
try {
|
|
371
|
+
const results = await provider.syncAll(cli.cwd, { force: cli.force });
|
|
372
|
+
if (cli.json) console.log(JSON.stringify(results.map((result) => ({
|
|
373
|
+
source: result.source.display,
|
|
374
|
+
status: result.status,
|
|
375
|
+
sha: result.sha ?? null,
|
|
376
|
+
error: result.error ?? null
|
|
377
|
+
})), null, 2));
|
|
378
|
+
else if (results.length === 0) console.log("No git sources configured.");
|
|
379
|
+
else printSync(results, config);
|
|
380
|
+
return results.some((result) => result.status === "failed") ? 1 : 0;
|
|
381
|
+
} finally {
|
|
382
|
+
await provider.dispose();
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
function printSync(results, _config) {
|
|
386
|
+
console.log(table([[
|
|
387
|
+
"SOURCE",
|
|
388
|
+
"STATUS",
|
|
389
|
+
"COMMIT"
|
|
390
|
+
], ...results.map((result) => [
|
|
391
|
+
result.source.display,
|
|
392
|
+
result.status,
|
|
393
|
+
result.sha?.slice(0, 12) ?? result.error ?? ""
|
|
394
|
+
])]));
|
|
395
|
+
}
|
|
396
|
+
async function doctor(cli, config) {
|
|
397
|
+
const report = await collect(cli, config);
|
|
398
|
+
const git = await hasGit();
|
|
399
|
+
if (cli.json) {
|
|
400
|
+
console.log(JSON.stringify({
|
|
401
|
+
git,
|
|
402
|
+
roots: report.roots.map((root) => ({
|
|
403
|
+
label: root.root.label,
|
|
404
|
+
path: root.root.path,
|
|
405
|
+
exists: root.exists,
|
|
406
|
+
count: root.count
|
|
407
|
+
})),
|
|
408
|
+
renamed: report.skills.filter(isRenamed).map((skill) => ({
|
|
409
|
+
name: skill.name,
|
|
410
|
+
from: skill.metadata.skillsAnywhere.renamedFrom,
|
|
411
|
+
path: skill.path
|
|
412
|
+
})),
|
|
413
|
+
repaired: report.skills.filter((skill) => skill.warnings.length > 0 && !isRenamed(skill)).map((skill) => ({
|
|
414
|
+
name: skill.name,
|
|
415
|
+
path: skill.path,
|
|
416
|
+
warnings: skill.warnings
|
|
417
|
+
})),
|
|
418
|
+
invalid: report.invalid.map((entry) => ({
|
|
419
|
+
path: entry.path,
|
|
420
|
+
reason: entry.reason
|
|
421
|
+
})),
|
|
422
|
+
dropped: report.dropped.map((entry) => ({
|
|
423
|
+
name: entry.skill.name,
|
|
424
|
+
path: entry.skill.path,
|
|
425
|
+
reason: entry.reason,
|
|
426
|
+
winner: entry.winner.path
|
|
427
|
+
})),
|
|
428
|
+
complete: report.complete
|
|
429
|
+
}, null, 2));
|
|
430
|
+
return 0;
|
|
431
|
+
}
|
|
432
|
+
console.log(`git: ${git ? "available" : "NOT FOUND (git sources disabled)"}`);
|
|
433
|
+
console.log(`state: ${shorten(config.stateDir, config.home)}`);
|
|
434
|
+
const present = report.roots.filter((root) => root.exists);
|
|
435
|
+
console.log(`\nRoots present (${present.length} of ${report.roots.length}):`);
|
|
436
|
+
console.log(table(present.map((root) => [
|
|
437
|
+
root.root.label,
|
|
438
|
+
String(root.count).padStart(3),
|
|
439
|
+
shorten(root.root.path, config.home)
|
|
440
|
+
])));
|
|
441
|
+
const renamed = report.skills.filter(isRenamed);
|
|
442
|
+
if (renamed.length > 0) {
|
|
443
|
+
console.log(`\nRenamed to avoid collisions (${renamed.length}):`);
|
|
444
|
+
console.log(table(renamed.map((skill) => [
|
|
445
|
+
skill.metadata.skillsAnywhere.renamedFrom,
|
|
446
|
+
`-> ${skill.name}`,
|
|
447
|
+
shorten(skill.path, config.home)
|
|
448
|
+
])));
|
|
449
|
+
}
|
|
450
|
+
const repaired = report.skills.filter((skill) => skill.warnings.length > 0 && !isRenamed(skill));
|
|
451
|
+
if (repaired.length > 0) {
|
|
452
|
+
console.log(`\nRepaired frontmatter (${repaired.length}):`);
|
|
453
|
+
for (const skill of repaired) {
|
|
454
|
+
console.log(` ${skill.name} ${shorten(skill.path, config.home)}`);
|
|
455
|
+
for (const warning of skill.warnings) console.log(` - ${warning}`);
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
if (report.invalid.length > 0) {
|
|
459
|
+
console.log(`\nSkipped (${report.invalid.length}):`);
|
|
460
|
+
for (const entry of report.invalid) console.log(` ${shorten(entry.path, config.home)}\n - ${entry.reason}`);
|
|
461
|
+
}
|
|
462
|
+
if (report.dropped.length > 0) {
|
|
463
|
+
console.log(`\nHidden duplicates (${report.dropped.length}):`);
|
|
464
|
+
console.log(table(report.dropped.map((entry) => [
|
|
465
|
+
entry.skill.name,
|
|
466
|
+
entry.reason,
|
|
467
|
+
shorten(entry.skill.path, config.home),
|
|
468
|
+
`-> ${shorten(entry.winner.path, config.home)}`
|
|
469
|
+
])));
|
|
470
|
+
}
|
|
471
|
+
if (!report.complete) console.log("\nWARNING: at least one root could not be read completely; see messages above.");
|
|
472
|
+
return 0;
|
|
473
|
+
}
|
|
474
|
+
async function mcp(cli) {
|
|
475
|
+
const { runStdio } = await import("./mcp.js");
|
|
476
|
+
await runStdio({
|
|
477
|
+
cwd: cli.cwd,
|
|
478
|
+
config: {
|
|
479
|
+
sync: true,
|
|
480
|
+
watch: false
|
|
481
|
+
}
|
|
482
|
+
});
|
|
483
|
+
return 0;
|
|
484
|
+
}
|
|
485
|
+
function shorten(path, home) {
|
|
486
|
+
if (path === home) return "~";
|
|
487
|
+
const rel = relative(home, path);
|
|
488
|
+
if (rel === "" || rel.startsWith("..") || isAbsolute(rel)) return path;
|
|
489
|
+
return `~${sep}${rel}`;
|
|
490
|
+
}
|
|
491
|
+
if ((() => {
|
|
492
|
+
const entry = process.argv[1];
|
|
493
|
+
return entry !== void 0 && /(?:^|[\\/])(?:dsh-skills-anywhere|cli(?:\.js|\.ts)?)$/.test(entry);
|
|
494
|
+
})()) main().then((code) => {
|
|
495
|
+
process.exitCode = code;
|
|
496
|
+
}, (error) => {
|
|
497
|
+
console.error(error instanceof Error ? error.stack ?? error.message : String(error));
|
|
498
|
+
process.exitCode = 1;
|
|
499
|
+
});
|
|
500
|
+
//#endregion
|
|
501
|
+
export { main };
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { A as SourceSpec, C as RankConfig, D as LockEntry, E as resolveConfig, F as readSourcesFile, I as resolveSource, L as sameRepository, M as SyncStatus, N as hasGit, O as LockFile, P as readLock, R as syncSource, S as DEFAULT_RANKS, T as projectSourcesFile, _ as normalizeSkillName, a as DiscoveredSkill, b as CatalogConfig, c as InvalidSkill, d as SkillRoot, f as discover, g as isSkillName, h as ParsedSkill, i as applyCatalogBudget, j as SyncResult, k as ResolvedSource, l as RootReport, m as ParseResult, n as ProviderLogger, o as DiscoveryReport, p as findProjectRoot, r as SkillsAnywhereProvider, s as DroppedSkill, t as CatalogState, u as SkillOrigin, v as parseSkillMarkdown, w as ResolvedConfig, x as Config, y as splitFrontmatter, z as writeSourcesFile } from "./provider-gYkY_3P9.js";
|
|
2
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
3
|
+
//#region src/agents.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Where other coding agents keep their Agent Skills.
|
|
6
|
+
*
|
|
7
|
+
* The table follows the conventions catalogued by the `skills` CLI
|
|
8
|
+
* (https://github.com/vercel-labs/skills) and each agent's own docs. Two rows
|
|
9
|
+
* are deliberately absent because the shipped `@deepseek-ai/dsh-skill-filesystem`
|
|
10
|
+
* provider already scans them: `.agents/skills` (project and `~/.agents/skills`)
|
|
11
|
+
* and `.dsh/skills`. Re-scanning them here would only produce duplicates.
|
|
12
|
+
*
|
|
13
|
+
* `project` is relative to the project root (nearest ancestor with `.git`, else
|
|
14
|
+
* the cwd). `user` is relative to the home directory. Either may be absent.
|
|
15
|
+
*
|
|
16
|
+
* @module
|
|
17
|
+
*/
|
|
18
|
+
interface AgentSpec {
|
|
19
|
+
/** Stable identifier, usable in `excludeAgents`. */
|
|
20
|
+
readonly id: string;
|
|
21
|
+
/** Human-readable product name. */
|
|
22
|
+
readonly label: string;
|
|
23
|
+
/** Project-level skills directory, relative to the project root. */
|
|
24
|
+
readonly project?: string;
|
|
25
|
+
/** User-level skills directory, relative to the home directory. */
|
|
26
|
+
readonly user?: string;
|
|
27
|
+
}
|
|
28
|
+
export declare const AGENTS: readonly AgentSpec[];
|
|
29
|
+
/** Look up one agent by id. */
|
|
30
|
+
export declare function agentById(id: string): AgentSpec | undefined;
|
|
31
|
+
//#endregion
|
|
32
|
+
//#region src/index.d.ts
|
|
33
|
+
/** Cordis plugin name; stable across releases. */
|
|
34
|
+
export declare const name = "skills-anywhere";
|
|
35
|
+
/** The skill registry must exist before the provider can register. */
|
|
36
|
+
export declare const inject: string[];
|
|
37
|
+
/** Register the skills-anywhere provider on `ctx.skills`. */
|
|
38
|
+
export declare function apply(ctx: Context, config?: Config): void;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { type AgentSpec, type CatalogConfig, type CatalogState, Config, type Config as ConfigInput, DEFAULT_RANKS, type DiscoveredSkill, type DiscoveryReport, type DroppedSkill, type InvalidSkill, type LockEntry, type LockFile, type ParseResult, type ParsedSkill, type ProviderLogger, type RankConfig, type ResolvedConfig, type ResolvedSource, type RootReport, type SkillOrigin, type SkillRoot, SkillsAnywhereProvider, type SourceSpec, type SyncResult, type SyncStatus, applyCatalogBudget, discover, findProjectRoot, hasGit, isSkillName, normalizeSkillName, parseSkillMarkdown, projectSourcesFile, readLock, readSourcesFile, resolveConfig, resolveSource, sameRepository, splitFrontmatter, syncSource, writeSourcesFile };
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { _ as agentById, a as readSourcesFile, b as projectSourcesFile, c as syncSource, d as findProjectRoot, f as isSkillName, g as AGENTS, h as splitFrontmatter, i as readLock, l as writeSourcesFile, m as parseSkillMarkdown, n as applyCatalogBudget, o as resolveSource, p as normalizeSkillName, r as hasGit, s as sameRepository, t as SkillsAnywhereProvider, u as discover, v as Config, x as resolveConfig, y as DEFAULT_RANKS } from "./provider-C8Jncd8r.js";
|
|
2
|
+
//#region src/index.ts
|
|
3
|
+
/** Cordis plugin name; stable across releases. */
|
|
4
|
+
const name = "skills-anywhere";
|
|
5
|
+
/** The skill registry must exist before the provider can register. */
|
|
6
|
+
const inject = ["skills"];
|
|
7
|
+
/** Register the skills-anywhere provider on `ctx.skills`. */
|
|
8
|
+
function apply(ctx, config = {}) {
|
|
9
|
+
const resolved = resolveConfig(config);
|
|
10
|
+
let provider;
|
|
11
|
+
ctx.skills.registerProvider((control) => {
|
|
12
|
+
provider = new SkillsAnywhereProvider(resolved, {
|
|
13
|
+
info: (message) => ctx.logger.info(message),
|
|
14
|
+
warn: (message) => ctx.logger.warn(message),
|
|
15
|
+
debug: (message) => ctx.logger.debug(message)
|
|
16
|
+
}, control);
|
|
17
|
+
return provider;
|
|
18
|
+
});
|
|
19
|
+
ctx.effect(() => () => {
|
|
20
|
+
provider.dispose();
|
|
21
|
+
}, "skills-anywhere provider");
|
|
22
|
+
ctx.logger.info(`skills-anywhere: provider "${resolved.providerName}" registered (agents=${resolved.agents}, claudePlugins=${resolved.claudePlugins}, sources=${resolved.sources.length}${resolved.sourcesFiles ? "+files" : ""})`);
|
|
23
|
+
}
|
|
24
|
+
//#endregion
|
|
25
|
+
export { AGENTS, Config, DEFAULT_RANKS, SkillsAnywhereProvider, agentById, apply, applyCatalogBudget, discover, findProjectRoot, hasGit, inject, isSkillName, name, normalizeSkillName, parseSkillMarkdown, projectSourcesFile, readLock, readSourcesFile, resolveConfig, resolveSource, sameRepository, splitFrontmatter, syncSource, writeSourcesFile };
|
package/lib/mcp.d.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { a as DiscoveredSkill, n as ProviderLogger, o as DiscoveryReport, r as SkillsAnywhereProvider, w as ResolvedConfig, x as Config } from "./provider-gYkY_3P9.js";
|
|
2
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
+
//#region src/mcp.d.ts
|
|
4
|
+
export interface McpOptions {
|
|
5
|
+
/** Project directory that selects project-level skill roots and sources. */
|
|
6
|
+
readonly cwd?: string;
|
|
7
|
+
/** Provider configuration; defaults follow the dsh plugin defaults. */
|
|
8
|
+
readonly config?: Config | ResolvedConfig;
|
|
9
|
+
/** Where diagnostics go. Defaults to stderr, which stdio MCP clients ignore. */
|
|
10
|
+
readonly log?: ProviderLogger;
|
|
11
|
+
/** How long one discovery pass is reused before rescanning (ms). */
|
|
12
|
+
readonly cacheMs?: number;
|
|
13
|
+
/** Default `find_skills` result count. */
|
|
14
|
+
readonly findLimit?: number;
|
|
15
|
+
/** Hard cap for `find_skills` and `list_skills` result counts. */
|
|
16
|
+
readonly maxLimit?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface SkillsAnywhereMcp {
|
|
19
|
+
readonly server: McpServer;
|
|
20
|
+
readonly provider: SkillsAnywhereProvider;
|
|
21
|
+
/** Discover (or reuse the cached pass) and return the current pool. */
|
|
22
|
+
refresh(force?: boolean): Promise<DiscoveryReport>;
|
|
23
|
+
/** Close the transport and release watchers and timers. */
|
|
24
|
+
close(): Promise<void>;
|
|
25
|
+
}
|
|
26
|
+
export interface OpenedSkill {
|
|
27
|
+
readonly name: string;
|
|
28
|
+
readonly description: string;
|
|
29
|
+
readonly directory: string;
|
|
30
|
+
readonly path: string;
|
|
31
|
+
readonly source: string;
|
|
32
|
+
readonly content: string;
|
|
33
|
+
}
|
|
34
|
+
export declare function packageVersion(): string;
|
|
35
|
+
/** Skills the author allows a model to invoke, in catalog order. */
|
|
36
|
+
export declare function modelSkills(report: DiscoveryReport): DiscoveredSkill[];
|
|
37
|
+
/**
|
|
38
|
+
* Render a skill the way dsh hands it to its model, so clients that already
|
|
39
|
+
* understand `<skill_content>` blocks see a familiar shape.
|
|
40
|
+
*/
|
|
41
|
+
export declare function renderSkill(skill: Pick<OpenedSkill, 'name' | 'directory' | 'content'>): string;
|
|
42
|
+
/** Re-read a discovered skill from disk so edits since discovery are honoured. */
|
|
43
|
+
export declare function openSkill(skill: DiscoveredSkill, lenient: boolean): Promise<OpenedSkill | undefined>;
|
|
44
|
+
/** Build a server over a standalone provider. Nothing is connected yet. */
|
|
45
|
+
export declare function createSkillsAnywhereServer(options?: McpOptions): SkillsAnywhereMcp;
|
|
46
|
+
/** Serve over stdio until the client disconnects; resolves when closed. */
|
|
47
|
+
export declare function runStdio(options?: McpOptions): Promise<void>;
|
|
48
|
+
//#endregion
|