@michengai/dsh-skills-manager 1.2.4 → 1.2.6
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/CHANGELOG.md +8 -0
- package/CHANGELOG.zh-CN.md +8 -0
- package/README.md +3 -3
- package/README.zh-CN.md +3 -3
- package/lib/client.js +4 -4
- package/lib/core.js +80 -6
- package/package.json +1 -1
package/lib/core.js
CHANGED
|
@@ -900,6 +900,49 @@ function parseBoolValue(raw) {
|
|
|
900
900
|
if (v === "false" || v === "no" || v === "off" || v === "0") return false;
|
|
901
901
|
return void 0;
|
|
902
902
|
}
|
|
903
|
+
function linkedWriteError(path) {
|
|
904
|
+
return {
|
|
905
|
+
ok: false,
|
|
906
|
+
error: `\u76EE\u5F55\u94FE\u63A5\u4E0D\u80FD\u5220\u9664\u6216\u8986\u76D6: ${path}`,
|
|
907
|
+
code: "error.skill.linked",
|
|
908
|
+
params: { path }
|
|
909
|
+
};
|
|
910
|
+
}
|
|
911
|
+
async function readlinkOrNull(path) {
|
|
912
|
+
try {
|
|
913
|
+
return await fs.readlink(path);
|
|
914
|
+
} catch {
|
|
915
|
+
return null;
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
async function isDirectoryLink(path) {
|
|
919
|
+
const st = await lstatOrNull(path);
|
|
920
|
+
if (!st) return false;
|
|
921
|
+
if (st.isSymbolicLink()) {
|
|
922
|
+
try {
|
|
923
|
+
return (await fs.stat(path)).isDirectory();
|
|
924
|
+
} catch {
|
|
925
|
+
return false;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
if (process.platform !== "win32" || !st.isDirectory()) return false;
|
|
929
|
+
return await readlinkOrNull(path) !== null;
|
|
930
|
+
}
|
|
931
|
+
async function linkedDirectoryBundle(directory, name) {
|
|
932
|
+
const bundlePath = entryPath(directory, name);
|
|
933
|
+
if (bundlePath === null || !await isDirectoryLink(bundlePath)) return null;
|
|
934
|
+
const docPath = join(bundlePath, "SKILL.md");
|
|
935
|
+
const docStat = await lstatOrNull(docPath);
|
|
936
|
+
if (!docStat?.isFile() || docStat.isSymbolicLink()) return null;
|
|
937
|
+
return {
|
|
938
|
+
kind: "bundle",
|
|
939
|
+
docPath,
|
|
940
|
+
entryPath: bundlePath,
|
|
941
|
+
realDocPath: await fs.realpath(docPath),
|
|
942
|
+
realEntryPath: await fs.realpath(bundlePath),
|
|
943
|
+
linked: true
|
|
944
|
+
};
|
|
945
|
+
}
|
|
903
946
|
async function resolveEntry(root, name) {
|
|
904
947
|
const definition = rootDefinition(root);
|
|
905
948
|
if (definition && (!definition.mutable || definition.key === "agents")) {
|
|
@@ -917,6 +960,8 @@ async function resolveEntry(root, name) {
|
|
|
917
960
|
if (!rootStat || !rootStat.isDirectory() || rootStat.isSymbolicLink())
|
|
918
961
|
return null;
|
|
919
962
|
const rootReal = await resolvedPath(rootPath);
|
|
963
|
+
const linked = await linkedDirectoryBundle(directory, name);
|
|
964
|
+
if (linked) return linked;
|
|
920
965
|
const bundleStat = await lstatOrNull(bundlePath);
|
|
921
966
|
if (bundleStat && bundleStat.isDirectory() && !bundleStat.isSymbolicLink()) {
|
|
922
967
|
const bundleDoc = join(bundlePath, "SKILL.md");
|
|
@@ -1032,7 +1077,18 @@ async function scanEntries(root, options = {}) {
|
|
|
1032
1077
|
const rootReal = await resolvedPath(directory);
|
|
1033
1078
|
for (const it of items) {
|
|
1034
1079
|
try {
|
|
1035
|
-
if (it.isSymbolicLink())
|
|
1080
|
+
if (it.isSymbolicLink() || process.platform === "win32" && it.isDirectory()) {
|
|
1081
|
+
const linked = await linkedDirectoryBundle(directory, it.name);
|
|
1082
|
+
if (linked) {
|
|
1083
|
+
const doc = options.metadataOnly ? { map: {}, hasFrontmatter: false } : parseSkillDoc(await fs.readFile(linked.realDocPath, "utf8"));
|
|
1084
|
+
byName.set(it.name, {
|
|
1085
|
+
...entryOf(it.name, "bundle", linked.docPath, doc),
|
|
1086
|
+
...linked
|
|
1087
|
+
});
|
|
1088
|
+
continue;
|
|
1089
|
+
}
|
|
1090
|
+
if (it.isSymbolicLink()) continue;
|
|
1091
|
+
}
|
|
1036
1092
|
if (it.isDirectory() && entryPath(directory, it.name) !== null) {
|
|
1037
1093
|
const docPath = join(directory, it.name, "SKILL.md");
|
|
1038
1094
|
const st = await lstatOrNull(docPath);
|
|
@@ -1769,6 +1825,9 @@ async function deleteSkill(root, name, log, options = {}) {
|
|
|
1769
1825
|
if ((await lstatOrNull(target.path))?.isSymbolicLink())
|
|
1770
1826
|
return { ok: false, error: `\u6280\u80FD\u76EE\u5F55\u4E0D\u5B89\u5168\uFF0C\u62D2\u7EDD\u5199\u5165: ${target.path}`, code: "error.root.unsafe", params: { path: target.path } };
|
|
1771
1827
|
}
|
|
1828
|
+
} else {
|
|
1829
|
+
const bundle = entryPath(definition.path, name);
|
|
1830
|
+
if (bundle && await isDirectoryLink(bundle)) return linkedWriteError(bundle);
|
|
1772
1831
|
}
|
|
1773
1832
|
const id = options.repositoryTrash?.id || options.catalogTrash?.id || `${Date.now()}-${randomUUID()}`;
|
|
1774
1833
|
if (!/^\d+-[a-f0-9-]{36}$/.test(id)) throw codedError("\u56DE\u6536\u7AD9\u6761\u76EE\u7F16\u53F7\u975E\u6CD5", "error.trash.invalid", { id });
|
|
@@ -2136,6 +2195,10 @@ async function copyToTemporary(source, target, isDir) {
|
|
|
2136
2195
|
}
|
|
2137
2196
|
}
|
|
2138
2197
|
async function replaceWithCopy(source, dest, isDir, existing = []) {
|
|
2198
|
+
for (const path of [dest, ...existing]) {
|
|
2199
|
+
if (await isDirectoryLink(path))
|
|
2200
|
+
throw codedError(`\u76EE\u5F55\u94FE\u63A5\u4E0D\u80FD\u5220\u9664\u6216\u8986\u76D6: ${path}`, "error.skill.linked", { path });
|
|
2201
|
+
}
|
|
2139
2202
|
const stage = await copyToTemporary(source, dest, isDir);
|
|
2140
2203
|
const backups = [];
|
|
2141
2204
|
try {
|
|
@@ -2279,6 +2342,16 @@ async function importSkill(source, log, options = {}) {
|
|
|
2279
2342
|
});
|
|
2280
2343
|
continue;
|
|
2281
2344
|
}
|
|
2345
|
+
const linkPath = join(targetRoot, c.kebab);
|
|
2346
|
+
if (await isDirectoryLink(linkPath)) {
|
|
2347
|
+
failed.push({
|
|
2348
|
+
source: c.source,
|
|
2349
|
+
error: `\u76EE\u5F55\u94FE\u63A5\u4E0D\u80FD\u5220\u9664\u6216\u8986\u76D6: ${linkPath}`,
|
|
2350
|
+
code: "error.skill.linked",
|
|
2351
|
+
params: { path: linkPath }
|
|
2352
|
+
});
|
|
2353
|
+
continue;
|
|
2354
|
+
}
|
|
2282
2355
|
const dest = c.isDir ? join(targetRoot, c.kebab) : join(targetRoot, `${c.kebab}.md`);
|
|
2283
2356
|
const paths = [
|
|
2284
2357
|
join(targetRoot, c.kebab),
|
|
@@ -2646,7 +2719,7 @@ async function skillDetail(key, name, options = {}) {
|
|
|
2646
2719
|
frontmatter: summary.hasFrontmatter ? Object.fromEntries(Object.entries(doc.map)) : null,
|
|
2647
2720
|
diagnostics: summary.diagnostics,
|
|
2648
2721
|
loadable: summary.loadable,
|
|
2649
|
-
sourceReadOnly: !root.mutable
|
|
2722
|
+
sourceReadOnly: !root.mutable || entry.linked === true
|
|
2650
2723
|
};
|
|
2651
2724
|
}
|
|
2652
2725
|
async function listProviderCandidates(options = {}) {
|
|
@@ -2673,7 +2746,7 @@ async function listProviderCandidates(options = {}) {
|
|
|
2673
2746
|
const project = root.scope === "project";
|
|
2674
2747
|
const policyOnly = root.mutable && root.key !== "agents";
|
|
2675
2748
|
const overlayRank = group.length > 1 ? Math.min(...group.map((item) => Math.min(item.root.rank, item.entry.providerRank ?? item.root.rank))) - 1 : void 0;
|
|
2676
|
-
const needsOverlay = !policyOnly || group.length > 1 || policy.override !== void 0 || !entry.invocationPolicyValid || policyResult.writable === false;
|
|
2749
|
+
const needsOverlay = !policyOnly || group.length > 1 || policy.override !== void 0 || !entry.invocationPolicyValid || policyResult.writable === false || !policy.sourceEnabled;
|
|
2677
2750
|
if (!needsOverlay) continue;
|
|
2678
2751
|
candidates.push({
|
|
2679
2752
|
name: canonicalSkillName({ root, entry, policy }),
|
|
@@ -2700,8 +2773,8 @@ async function listProviderCandidates(options = {}) {
|
|
|
2700
2773
|
metadata: {
|
|
2701
2774
|
dshSkillsManager: {
|
|
2702
2775
|
root: root.key,
|
|
2703
|
-
readOnly: !root.mutable,
|
|
2704
|
-
sourceReadOnly: !root.mutable,
|
|
2776
|
+
readOnly: !root.mutable || entry.linked === true,
|
|
2777
|
+
sourceReadOnly: !root.mutable || entry.linked === true,
|
|
2705
2778
|
policyOnly
|
|
2706
2779
|
}
|
|
2707
2780
|
}
|
|
@@ -2904,7 +2977,8 @@ async function state(options = {}) {
|
|
|
2904
2977
|
effectiveUserInvocable: policy.userInvocable,
|
|
2905
2978
|
diagnostics: e.diagnostics,
|
|
2906
2979
|
path: e.docPath,
|
|
2907
|
-
categoryId: policyResult.state.categoryMembers[root.key]?.[e.name] || null
|
|
2980
|
+
categoryId: policyResult.state.categoryMembers[root.key]?.[e.name] || null,
|
|
2981
|
+
linked: e.linked === true
|
|
2908
2982
|
});
|
|
2909
2983
|
all.push({
|
|
2910
2984
|
root,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@michengai/dsh-skills-manager",
|
|
3
|
-
"version": "1.2.
|
|
3
|
+
"version": "1.2.6",
|
|
4
4
|
"description": "DSH Skills Manager — 在 DeepSeek Harness 中统一加载并安全管理本机 Agent Skills · Load and safely manage local Agent Skills in DSH",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|