@michengai/dsh-skills-manager 1.1.19 → 1.1.21
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 +13 -0
- package/CHANGELOG.zh-CN.md +13 -0
- package/README.md +3 -3
- package/README.zh-CN.md +2 -2
- package/lib/catalog-skill.js +142 -0
- package/lib/clawhub-catalog.js +134 -0
- package/lib/client.js +97 -83
- package/lib/core.js +140 -37
- package/lib/index.js +51 -15
- package/lib/repositories.js +370 -57
- package/lib/repository-updates.js +8 -2
- package/lib/skills-sh-catalog.js +92 -0
- package/package.json +9 -9
package/lib/core.js
CHANGED
|
@@ -118,7 +118,7 @@ function userRoots() {
|
|
|
118
118
|
key: "agents",
|
|
119
119
|
path: join(resolveAgentsHome(), "skills"),
|
|
120
120
|
label: "\u516C\u5171 Agent",
|
|
121
|
-
mutable:
|
|
121
|
+
mutable: true,
|
|
122
122
|
toggleable: true,
|
|
123
123
|
native: true,
|
|
124
124
|
rank: 450
|
|
@@ -611,11 +611,11 @@ function rootDefinition(root) {
|
|
|
611
611
|
function rootByKey(key) {
|
|
612
612
|
return userRoots().find((item) => item.key === key) || null;
|
|
613
613
|
}
|
|
614
|
-
function writableRootDefinition(root
|
|
614
|
+
function writableRootDefinition(root) {
|
|
615
615
|
const definition = rootDefinition(root);
|
|
616
|
-
if (
|
|
616
|
+
if (definition?.key === "agents") {
|
|
617
617
|
const canonical = rootByKey("agents");
|
|
618
|
-
return canonical && resolve(definition.path) === resolve(canonical.path) ?
|
|
618
|
+
return canonical && resolve(definition.path) === resolve(canonical.path) ? canonical : null;
|
|
619
619
|
}
|
|
620
620
|
if (!definition || definition.mutable !== true) return null;
|
|
621
621
|
if (definition.key === "dsh")
|
|
@@ -624,16 +624,18 @@ function writableRootDefinition(root, allowRepositoryRoot = false) {
|
|
|
624
624
|
return null;
|
|
625
625
|
return definition;
|
|
626
626
|
}
|
|
627
|
-
async function
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
return { ok: false, error: `\u6280\u80FD\u76EE\u5F55\u4E0D\u5B89\u5168\uFF0C\u62D2\u7EDD\u5199\u5165: ${path}`, code: "error.root.unsafe", params: { path } };
|
|
634
|
-
if (dirname(path) === path) break;
|
|
635
|
-
}
|
|
627
|
+
async function checkedUserRootAncestors(definition) {
|
|
628
|
+
for (let path = resolve(definition.path); ; path = dirname(path)) {
|
|
629
|
+
const st = await lstatOrNull(path);
|
|
630
|
+
if (st && (!st.isDirectory() || st.isSymbolicLink()))
|
|
631
|
+
return { ok: false, error: `\u6280\u80FD\u76EE\u5F55\u4E0D\u5B89\u5168\uFF0C\u62D2\u7EDD\u5199\u5165: ${path}`, code: "error.root.unsafe", params: { path } };
|
|
632
|
+
if (dirname(path) === path) break;
|
|
636
633
|
}
|
|
634
|
+
return definition;
|
|
635
|
+
}
|
|
636
|
+
async function checkedWritableRootDefinition(root) {
|
|
637
|
+
const definition = writableRootDefinition(root);
|
|
638
|
+
if (definition?.key === "agents") return checkedUserRootAncestors(definition);
|
|
637
639
|
if (!definition || definition.scope !== "project") return definition;
|
|
638
640
|
if (await overlapsUserSkillRoot(definition.path)) {
|
|
639
641
|
return {
|
|
@@ -656,12 +658,17 @@ async function checkedWritableRootDefinition(root, allowRepositoryRoot = false)
|
|
|
656
658
|
}
|
|
657
659
|
return definition;
|
|
658
660
|
}
|
|
661
|
+
async function checkedRepositoryRootDefinition(root) {
|
|
662
|
+
const definition = await checkedWritableRootDefinition(root);
|
|
663
|
+
if (!definition || definition.ok === false || definition.scope === "project" || definition.key === "agents") return definition;
|
|
664
|
+
return checkedUserRootAncestors(definition);
|
|
665
|
+
}
|
|
659
666
|
async function repositoryDestinations() {
|
|
660
667
|
const result = [];
|
|
661
668
|
for (const root of userRoots().filter((item) => item.key === "dsh" || item.key === "agents")) {
|
|
662
669
|
let error;
|
|
663
670
|
try {
|
|
664
|
-
const checked = await
|
|
671
|
+
const checked = await checkedRepositoryRootDefinition(root);
|
|
665
672
|
if (!checked || checked.ok === false) error = checked || { code: "error.root.unsafe", error: "\u6280\u80FD\u5B89\u88C5\u4F4D\u7F6E\u4E0D\u53EF\u7528" };
|
|
666
673
|
} catch (caught) {
|
|
667
674
|
const exception = caught;
|
|
@@ -768,21 +775,22 @@ function parseSkillDoc(text) {
|
|
|
768
775
|
const m = /^([A-Za-z0-9_-]+)\s*:\s*(.*)$/.exec(lines[i]);
|
|
769
776
|
if (m) {
|
|
770
777
|
fields.push({ key: m[1], raw: m[2] });
|
|
771
|
-
const block = /^([>|])[-+]?\s*$/.exec(m[2]);
|
|
778
|
+
const block = /^([>|])(?:([1-9])[-+]?|[-+]([1-9])?)?\s*$/.exec(m[2]);
|
|
772
779
|
if (block) {
|
|
773
780
|
const content = [];
|
|
774
781
|
while (i + 1 < end && (/^\s/.test(lines[i + 1]) || lines[i + 1] === "")) {
|
|
775
782
|
i++;
|
|
776
783
|
content.push(lines[i]);
|
|
777
784
|
}
|
|
778
|
-
const
|
|
785
|
+
const explicitIndentation = block[2] || block[3];
|
|
786
|
+
const indentation = explicitIndentation ? Number(explicitIndentation) : content.filter((line) => line.trim() !== "").reduce(
|
|
779
787
|
(min, line) => Math.min(min, (/^\s*/.exec(line) || [""])[0].length),
|
|
780
788
|
Infinity
|
|
781
789
|
);
|
|
782
790
|
const normalized = content.map(
|
|
783
791
|
(line) => Number.isFinite(indentation) ? line.slice(Math.min(indentation, line.length)) : line
|
|
784
792
|
);
|
|
785
|
-
map[m[1]] = block[1] === ">" ? normalized.join(" ").replace(/\s+/g, " ").trim() : normalized.join("\n").trim();
|
|
793
|
+
map[m[1]] = block[1] === ">" ? normalized.join(" ").replace(/\s+/g, " ").trim() : explicitIndentation ? normalized.join("\n").replace(/^\n+|\n+$/g, "") : normalized.join("\n").trim();
|
|
786
794
|
} else {
|
|
787
795
|
map[m[1]] = decodeYamlScalar(m[2]);
|
|
788
796
|
}
|
|
@@ -894,7 +902,7 @@ function parseBoolValue(raw) {
|
|
|
894
902
|
}
|
|
895
903
|
async function resolveEntry(root, name) {
|
|
896
904
|
const definition = rootDefinition(root);
|
|
897
|
-
if (definition && !definition.mutable) {
|
|
905
|
+
if (definition && (!definition.mutable || definition.key === "agents")) {
|
|
898
906
|
if (!validDiscoveryName(name)) return null;
|
|
899
907
|
return (await discoverReadonlyEntries(definition.path)).entries.find(
|
|
900
908
|
(entry) => entry.name === name
|
|
@@ -960,7 +968,7 @@ function entryOf(name, kind, docPath, doc) {
|
|
|
960
968
|
});
|
|
961
969
|
if (doc.hasFrontmatter && !declaredName)
|
|
962
970
|
diagnostics.push({ level: "error", code: "diagnostic.name.missing" });
|
|
963
|
-
else if (declaredName && !KEBAB_RE.test(declaredName))
|
|
971
|
+
else if (declaredName && !KEBAB_RE.test(toKebab(declaredName)))
|
|
964
972
|
diagnostics.push({
|
|
965
973
|
level: "error",
|
|
966
974
|
code: "diagnostic.name.invalid",
|
|
@@ -984,13 +992,13 @@ function entryOf(name, kind, docPath, doc) {
|
|
|
984
992
|
invocationPolicyValid,
|
|
985
993
|
hasFrontmatter: doc.hasFrontmatter,
|
|
986
994
|
// 调用策略值异常可以由 manager 本地策略覆盖;结构本身合法即可加载。
|
|
987
|
-
loadable: doc.hasFrontmatter && KEBAB_RE.test(declaredName)
|
|
995
|
+
loadable: doc.hasFrontmatter && description !== "" && KEBAB_RE.test(KEBAB_RE.test(declaredName) ? declaredName : toKebab(declaredName)),
|
|
988
996
|
diagnostics
|
|
989
997
|
};
|
|
990
998
|
}
|
|
991
999
|
async function scanEntries(root, options = {}) {
|
|
992
1000
|
const definition = rootDefinition(root);
|
|
993
|
-
if (definition && !definition.mutable) {
|
|
1001
|
+
if (definition && (!definition.mutable || definition.key === "agents")) {
|
|
994
1002
|
const discovered = await discoverReadonlyEntries(definition.path);
|
|
995
1003
|
if (options.metadataOnly) return discovered;
|
|
996
1004
|
const entries2 = [];
|
|
@@ -1113,7 +1121,7 @@ function defaultManagerState() {
|
|
|
1113
1121
|
disabledSkills[root.key] = [];
|
|
1114
1122
|
enabledSkills[root.key] = [];
|
|
1115
1123
|
}
|
|
1116
|
-
return { version: 1, sources, disabledSkills, enabledSkills, categories: /* @__PURE__ */ Object.create(null), categoryMembers: /* @__PURE__ */ Object.create(null) };
|
|
1124
|
+
return { version: 1, sources, disabledSkills, enabledSkills, categories: /* @__PURE__ */ Object.create(null), categoryMembers: /* @__PURE__ */ Object.create(null), categoryOrder: /* @__PURE__ */ Object.create(null) };
|
|
1117
1125
|
}
|
|
1118
1126
|
function validStateSkillName(name) {
|
|
1119
1127
|
return validDiscoveryName(name);
|
|
@@ -1214,6 +1222,7 @@ function normalizeManagerState(value) {
|
|
|
1214
1222
|
}
|
|
1215
1223
|
normalized.categories = normalizeCategories(value.categories);
|
|
1216
1224
|
normalized.categoryMembers = normalizeCategoryMembers(value.categoryMembers, normalized.categories);
|
|
1225
|
+
normalized.categoryOrder = normalizeCategoryOrder(value.categoryOrder, normalized.categories);
|
|
1217
1226
|
return normalized;
|
|
1218
1227
|
}
|
|
1219
1228
|
const CATEGORY_NAME_MAX = 24;
|
|
@@ -1252,6 +1261,20 @@ function normalizeCategoryMembers(value, categories) {
|
|
|
1252
1261
|
}
|
|
1253
1262
|
return members;
|
|
1254
1263
|
}
|
|
1264
|
+
function normalizeCategoryOrder(value, categories) {
|
|
1265
|
+
const order = /* @__PURE__ */ Object.create(null);
|
|
1266
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return order;
|
|
1267
|
+
for (const [key, groups] of Object.entries(value)) {
|
|
1268
|
+
if (!categoryRootKey(key) || !groups || typeof groups !== "object" || Array.isArray(groups)) continue;
|
|
1269
|
+
const validIds = /* @__PURE__ */ new Set(["", ...(categories[key] || []).map((item) => item.id)]);
|
|
1270
|
+
order[key] = /* @__PURE__ */ Object.create(null);
|
|
1271
|
+
for (const [id, names] of Object.entries(groups)) {
|
|
1272
|
+
if (!validIds.has(id) || !Array.isArray(names)) continue;
|
|
1273
|
+
order[key][id] = [...new Set(names.filter(validStateSkillName))];
|
|
1274
|
+
}
|
|
1275
|
+
}
|
|
1276
|
+
return order;
|
|
1277
|
+
}
|
|
1255
1278
|
async function readManagerState() {
|
|
1256
1279
|
try {
|
|
1257
1280
|
const raw = await fs.readFile(managerStatePath(), "utf8");
|
|
@@ -1480,6 +1503,7 @@ async function writableCategoryState(key, options = {}) {
|
|
|
1480
1503
|
if (current.writable === false) return invalidManagerStateWrite();
|
|
1481
1504
|
if (!current.state.categories[root.key]) current.state.categories[root.key] = [];
|
|
1482
1505
|
if (!current.state.categoryMembers[root.key]) current.state.categoryMembers[root.key] = /* @__PURE__ */ Object.create(null);
|
|
1506
|
+
if (!current.state.categoryOrder[root.key]) current.state.categoryOrder[root.key] = /* @__PURE__ */ Object.create(null);
|
|
1483
1507
|
return { rootKey: root.key, root, state: current.state };
|
|
1484
1508
|
}
|
|
1485
1509
|
async function createCategory(key, name, log, options = {}) {
|
|
@@ -1492,6 +1516,7 @@ async function createCategory(key, name, log, options = {}) {
|
|
|
1492
1516
|
return categoryError("error.category.duplicate", `\u5DF2\u6709\u540C\u540D\u5206\u7C7B: ${label}`, { name: label });
|
|
1493
1517
|
const category = { id: randomBytes(8).toString("hex"), name: label };
|
|
1494
1518
|
ready.state.categories[ready.rootKey].push(category);
|
|
1519
|
+
ready.state.categoryOrder[ready.rootKey][category.id] = [];
|
|
1495
1520
|
await writeManagerState(ready.state);
|
|
1496
1521
|
if (log) log("category-create", `\u5728 ${ready.rootKey} \u6DFB\u52A0\u5206\u7C7B ${label}`);
|
|
1497
1522
|
return { root: ready.rootKey, category };
|
|
@@ -1518,6 +1543,7 @@ async function deleteCategory(key, id, log, options = {}) {
|
|
|
1518
1543
|
const list = ready.state.categories[ready.rootKey];
|
|
1519
1544
|
if (!list.some((item) => item.id === id)) return categoryError("error.category.notFound", "\u5206\u7C7B\u4E0D\u5B58\u5728", { id });
|
|
1520
1545
|
ready.state.categories[ready.rootKey] = list.filter((item) => item.id !== id);
|
|
1546
|
+
delete ready.state.categoryOrder[ready.rootKey][id];
|
|
1521
1547
|
for (const [skillName, categoryId] of Object.entries(ready.state.categoryMembers[ready.rootKey])) {
|
|
1522
1548
|
if (categoryId === id) delete ready.state.categoryMembers[ready.rootKey][skillName];
|
|
1523
1549
|
}
|
|
@@ -1536,12 +1562,67 @@ async function assignSkillCategory(key, skillName, categoryId, log, options = {}
|
|
|
1536
1562
|
return categoryError("error.skill.notFound", `\u6280\u80FD\u4E0D\u5B58\u5728: ${skillName}`, { name: skillName });
|
|
1537
1563
|
if (!ready.state.categories[ready.rootKey].some((item) => item.id === categoryId))
|
|
1538
1564
|
return categoryError("error.category.notFound", "\u5206\u7C7B\u4E0D\u5B58\u5728", { id: categoryId });
|
|
1565
|
+
const previousId = ready.state.categoryMembers[ready.rootKey][skillName] || "";
|
|
1566
|
+
if (previousId !== categoryId) {
|
|
1567
|
+
ready.state.categoryOrder[ready.rootKey][previousId] = (ready.state.categoryOrder[ready.rootKey][previousId] || []).filter((name) => name !== skillName);
|
|
1568
|
+
const targetOrder = ready.state.categoryOrder[ready.rootKey][categoryId] || [];
|
|
1569
|
+
if (!targetOrder.includes(skillName)) targetOrder.push(skillName);
|
|
1570
|
+
ready.state.categoryOrder[ready.rootKey][categoryId] = targetOrder;
|
|
1571
|
+
}
|
|
1539
1572
|
ready.state.categoryMembers[ready.rootKey][skillName] = categoryId;
|
|
1540
|
-
} else
|
|
1573
|
+
} else {
|
|
1574
|
+
const previousId = ready.state.categoryMembers[ready.rootKey][skillName] || "";
|
|
1575
|
+
if (previousId) {
|
|
1576
|
+
ready.state.categoryOrder[ready.rootKey][previousId] = (ready.state.categoryOrder[ready.rootKey][previousId] || []).filter((name) => name !== skillName);
|
|
1577
|
+
const looseOrder = ready.state.categoryOrder[ready.rootKey][""] || [];
|
|
1578
|
+
if (!looseOrder.includes(skillName)) looseOrder.push(skillName);
|
|
1579
|
+
ready.state.categoryOrder[ready.rootKey][""] = looseOrder;
|
|
1580
|
+
}
|
|
1581
|
+
delete ready.state.categoryMembers[ready.rootKey][skillName];
|
|
1582
|
+
}
|
|
1541
1583
|
await writeManagerState(ready.state);
|
|
1542
1584
|
if (log) log("category-assign", `${ready.rootKey}/${skillName} \u5F52\u5165 ${categoryId || "\u672A\u5206\u7C7B"}`);
|
|
1543
1585
|
return { root: ready.rootKey, name: skillName, categoryId: categoryId || null };
|
|
1544
1586
|
}
|
|
1587
|
+
async function moveSkillInCategory(key, skillName, categoryId, targetName, position = "before", log, options = {}) {
|
|
1588
|
+
const ready = await writableCategoryState(key, options);
|
|
1589
|
+
if ("ok" in ready) return ready;
|
|
1590
|
+
if (!validStateSkillName(skillName))
|
|
1591
|
+
return categoryError("error.skill.notFound", `\u6280\u80FD\u4E0D\u5B58\u5728: ${skillName}`, { name: skillName });
|
|
1592
|
+
if (categoryId && !ready.state.categories[ready.rootKey].some((item) => item.id === categoryId))
|
|
1593
|
+
return categoryError("error.category.notFound", "\u5206\u7C7B\u4E0D\u5B58\u5728", { id: categoryId });
|
|
1594
|
+
if (position !== "before" && position !== "after")
|
|
1595
|
+
return categoryError("error.category.position", "\u6280\u80FD\u6392\u5E8F\u4F4D\u7F6E\u65E0\u6548");
|
|
1596
|
+
const entries = (await scanEntries(ready.root)).entries.map((entry) => entry.name);
|
|
1597
|
+
if (!entries.includes(skillName))
|
|
1598
|
+
return categoryError("error.skill.notFound", `\u6280\u80FD\u4E0D\u5B58\u5728: ${skillName}`, { name: skillName });
|
|
1599
|
+
if (targetName && (!validStateSkillName(targetName) || !entries.includes(targetName)))
|
|
1600
|
+
return categoryError("error.skill.notFound", `\u6392\u5E8F\u76EE\u6807\u4E0D\u5B58\u5728: ${targetName}`, { name: targetName });
|
|
1601
|
+
const members = ready.state.categoryMembers[ready.rootKey];
|
|
1602
|
+
const orders = ready.state.categoryOrder[ready.rootKey];
|
|
1603
|
+
const previousId = members[skillName] || "";
|
|
1604
|
+
const orderedNames = (id) => {
|
|
1605
|
+
const available = entries.filter((name) => (members[name] || "") === id).sort((a, b) => a.localeCompare(b));
|
|
1606
|
+
const availableSet = new Set(available);
|
|
1607
|
+
const saved = (orders[id] || []).filter((name) => availableSet.has(name));
|
|
1608
|
+
return [...saved, ...available.filter((name) => !saved.includes(name))];
|
|
1609
|
+
};
|
|
1610
|
+
if (targetName && (members[targetName] || "") !== categoryId)
|
|
1611
|
+
return categoryError("error.category.target", "\u6392\u5E8F\u76EE\u6807\u4E0D\u5C5E\u4E8E\u76EE\u6807\u5206\u7C7B", { name: targetName, category: categoryId });
|
|
1612
|
+
const sourceOrder = orderedNames(previousId).filter((name) => name !== skillName);
|
|
1613
|
+
const targetOrder = previousId === categoryId ? sourceOrder : orderedNames(categoryId).filter((name) => name !== skillName);
|
|
1614
|
+
let index = targetName ? targetOrder.indexOf(targetName) : -1;
|
|
1615
|
+
if (index < 0) index = targetOrder.length;
|
|
1616
|
+
else if (position === "after") index += 1;
|
|
1617
|
+
targetOrder.splice(index, 0, skillName);
|
|
1618
|
+
if (previousId !== categoryId) orders[previousId] = sourceOrder;
|
|
1619
|
+
orders[categoryId] = targetOrder;
|
|
1620
|
+
if (categoryId) members[skillName] = categoryId;
|
|
1621
|
+
else delete members[skillName];
|
|
1622
|
+
await writeManagerState(ready.state);
|
|
1623
|
+
if (log) log("category-order", `${ready.rootKey}/${skillName} \u79FB\u81F3 ${categoryId || "\u672A\u5206\u7C7B"} ${position} ${targetName || "\u672B\u5C3E"}`);
|
|
1624
|
+
return { root: ready.rootKey, name: skillName, categoryId: categoryId || null, order: targetOrder };
|
|
1625
|
+
}
|
|
1545
1626
|
async function setCategoryEnabled(key, categoryId, enabled, log, options = {}) {
|
|
1546
1627
|
const ready = await writableCategoryState(key, options);
|
|
1547
1628
|
if ("ok" in ready) return ready;
|
|
@@ -1647,7 +1728,7 @@ function trashRootMetadata(definition) {
|
|
|
1647
1728
|
async function restoreRootDefinition(metadata, options = {}) {
|
|
1648
1729
|
if (!metadata.root) return rootByKey("dsh");
|
|
1649
1730
|
if (metadata.root.scope === "user" && rootByKey(metadata.root.key))
|
|
1650
|
-
return checkedWritableRootDefinition(rootByKey(metadata.root.key)
|
|
1731
|
+
return checkedWritableRootDefinition(rootByKey(metadata.root.key));
|
|
1651
1732
|
if (metadata.root.scope !== "project" || metadata.root.kind !== "project-dsh" || typeof metadata.root.key !== "string" || typeof metadata.root.projectRoot !== "string" || !isAbsolute(metadata.root.projectRoot))
|
|
1652
1733
|
return {
|
|
1653
1734
|
ok: false,
|
|
@@ -1670,7 +1751,7 @@ async function restoreRootDefinition(metadata, options = {}) {
|
|
|
1670
1751
|
return checkedWritableRootDefinition(root);
|
|
1671
1752
|
}
|
|
1672
1753
|
async function deleteSkill(root, name, log, options = {}) {
|
|
1673
|
-
const definition = await checkedWritableRootDefinition(root
|
|
1754
|
+
const definition = await checkedWritableRootDefinition(root);
|
|
1674
1755
|
if (definition && definition.ok === false) return definition;
|
|
1675
1756
|
if (!definition) return readonlyError("delete");
|
|
1676
1757
|
const resolved = await resolveEntry(definition, name);
|
|
@@ -1683,7 +1764,13 @@ async function deleteSkill(root, name, log, options = {}) {
|
|
|
1683
1764
|
};
|
|
1684
1765
|
const targets = (await safeExistingEntryPaths(definition.path, name)).filter((target) => !options.repositoryEntryOnly || target.fileName === name);
|
|
1685
1766
|
if (!targets.length) return { ok: false, error: `\u6280\u80FD\u4E0D\u5B58\u5728: ${name}`, code: "error.skill.notFound", params: { name } };
|
|
1686
|
-
|
|
1767
|
+
if (definition.key === "agents") {
|
|
1768
|
+
for (const target of targets) {
|
|
1769
|
+
if ((await lstatOrNull(target.path))?.isSymbolicLink())
|
|
1770
|
+
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
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
const id = options.repositoryTrash?.id || options.catalogTrash?.id || `${Date.now()}-${randomUUID()}`;
|
|
1687
1774
|
if (!/^\d+-[a-f0-9-]{36}$/.test(id)) throw codedError("\u56DE\u6536\u7AD9\u6761\u76EE\u7F16\u53F7\u975E\u6CD5", "error.trash.invalid", { id });
|
|
1688
1775
|
const trashRoot = trashRootPath();
|
|
1689
1776
|
const stage = join(trashRoot, `.stage-${randomUUID()}`);
|
|
@@ -1712,7 +1799,8 @@ async function deleteSkill(root, name, log, options = {}) {
|
|
|
1712
1799
|
deletedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1713
1800
|
entries: moved.map((item) => item.fileName),
|
|
1714
1801
|
root: trashRootMetadata(definition),
|
|
1715
|
-
...options.repositoryTrash ? { repository: options.repositoryTrash.repository } : {}
|
|
1802
|
+
...options.repositoryTrash ? { repository: options.repositoryTrash.repository } : {},
|
|
1803
|
+
...options.catalogTrash ? { catalog: options.catalogTrash.ids } : {}
|
|
1716
1804
|
};
|
|
1717
1805
|
await fs.writeFile(
|
|
1718
1806
|
join(stage, "metadata.json"),
|
|
@@ -1840,8 +1928,15 @@ async function forgetCategoryMember(metadata) {
|
|
|
1840
1928
|
const current = await readManagerState();
|
|
1841
1929
|
if (current.writable === false) return;
|
|
1842
1930
|
const members = current.state.categoryMembers[key];
|
|
1843
|
-
|
|
1844
|
-
|
|
1931
|
+
const orders = current.state.categoryOrder[key];
|
|
1932
|
+
const hasMember = !!(members && members[metadata.name]);
|
|
1933
|
+
const hasOrder = !!(orders && Object.values(orders).some((names) => names.includes(metadata.name)));
|
|
1934
|
+
if (!hasMember && !hasOrder) return;
|
|
1935
|
+
if (members) delete members[metadata.name];
|
|
1936
|
+
if (orders) {
|
|
1937
|
+
for (const id of Object.keys(orders))
|
|
1938
|
+
orders[id] = orders[id].filter((name) => name !== metadata.name);
|
|
1939
|
+
}
|
|
1845
1940
|
await writeManagerState(current.state);
|
|
1846
1941
|
}
|
|
1847
1942
|
async function permanentlyDeleteTrash(id, log) {
|
|
@@ -2092,7 +2187,7 @@ async function replaceWithCopy(source, dest, isDir, existing = []) {
|
|
|
2092
2187
|
}
|
|
2093
2188
|
async function importSkill(source, log, options = {}) {
|
|
2094
2189
|
const requestedRoot = Object.prototype.hasOwnProperty.call(options, "root") ? options.root : rootByKey("dsh");
|
|
2095
|
-
const definition = await checkedWritableRootDefinition(requestedRoot
|
|
2190
|
+
const definition = await checkedWritableRootDefinition(requestedRoot);
|
|
2096
2191
|
if (definition && definition.ok === false) return definition;
|
|
2097
2192
|
if (!definition) return readonlyError("create");
|
|
2098
2193
|
const targetRoot = definition.path;
|
|
@@ -2214,6 +2309,7 @@ async function importSkill(source, log, options = {}) {
|
|
|
2214
2309
|
if (pending.length === 0 && conflicts.length === 0) return failureResult();
|
|
2215
2310
|
return { kind: analysis.kind, pending, conflicts, failed };
|
|
2216
2311
|
}
|
|
2312
|
+
if (!dryRun && options.beforeImport) await options.beforeImport(pending, conflicts, failed);
|
|
2217
2313
|
if (pending.length > 0 || conflict === "overwrite" && conflicts.length > 0) {
|
|
2218
2314
|
await fs.mkdir(targetRoot, { recursive: true });
|
|
2219
2315
|
}
|
|
@@ -2449,7 +2545,7 @@ function yamlString(value) {
|
|
|
2449
2545
|
}
|
|
2450
2546
|
async function createSkill(input, log, options = {}) {
|
|
2451
2547
|
const requestedRoot = Object.prototype.hasOwnProperty.call(options, "root") ? options.root : rootByKey("dsh");
|
|
2452
|
-
const definition = await checkedWritableRootDefinition(requestedRoot
|
|
2548
|
+
const definition = await checkedWritableRootDefinition(requestedRoot);
|
|
2453
2549
|
if (definition && definition.ok === false) return definition;
|
|
2454
2550
|
if (!definition) return readonlyError("create");
|
|
2455
2551
|
const root = definition.path;
|
|
@@ -2575,12 +2671,12 @@ async function listProviderCandidates(options = {}) {
|
|
|
2575
2671
|
const hasExplicitDisable = group.some((item) => item.policy.override === false);
|
|
2576
2672
|
if (!hasNativeCopy && !hasExplicitDisable && !policy.sourceEnabled) continue;
|
|
2577
2673
|
const project = root.scope === "project";
|
|
2578
|
-
const policyOnly = root.mutable;
|
|
2674
|
+
const policyOnly = root.mutable && root.key !== "agents";
|
|
2579
2675
|
const overlayRank = group.length > 1 ? Math.min(...group.map((item) => Math.min(item.root.rank, item.entry.providerRank ?? item.root.rank))) - 1 : void 0;
|
|
2580
2676
|
const needsOverlay = !policyOnly || group.length > 1 || policy.override !== void 0 || !entry.invocationPolicyValid || policyResult.writable === false;
|
|
2581
2677
|
if (!needsOverlay) continue;
|
|
2582
2678
|
candidates.push({
|
|
2583
|
-
name: entry
|
|
2679
|
+
name: canonicalSkillName({ root, entry, policy }),
|
|
2584
2680
|
description: entry.description,
|
|
2585
2681
|
invocation: {
|
|
2586
2682
|
modelInvocable: policy.modelInvocable,
|
|
@@ -2636,8 +2732,9 @@ async function getProviderSkill(candidate, options = {}) {
|
|
|
2636
2732
|
await fs.readFile(entry.realDocPath || entry.docPath, "utf8")
|
|
2637
2733
|
);
|
|
2638
2734
|
const summary = entryOf(locator.entryName, entry.kind, entry.docPath, doc);
|
|
2639
|
-
if (!summary.loadable
|
|
2640
|
-
|
|
2735
|
+
if (!summary.loadable) return void 0;
|
|
2736
|
+
const invocationName = KEBAB_RE.test(summary.declaredName) ? summary.declaredName : toKebab(summary.declaredName);
|
|
2737
|
+
if (invocationName !== candidate.name && summary.declaredName !== candidate.name) return void 0;
|
|
2641
2738
|
return {
|
|
2642
2739
|
name: candidate.name,
|
|
2643
2740
|
description: candidate.description,
|
|
@@ -2654,7 +2751,10 @@ async function getProviderSkill(candidate, options = {}) {
|
|
|
2654
2751
|
}
|
|
2655
2752
|
}
|
|
2656
2753
|
function canonicalSkillName(item) {
|
|
2657
|
-
|
|
2754
|
+
const declared = item.entry.declaredName || "";
|
|
2755
|
+
if (KEBAB_RE.test(declared)) return declared;
|
|
2756
|
+
const kebab = toKebab(declared);
|
|
2757
|
+
return kebab || declared || item.entry.name;
|
|
2658
2758
|
}
|
|
2659
2759
|
function groupLoadableSkillsByName(items) {
|
|
2660
2760
|
const groups = /* @__PURE__ */ new Map();
|
|
@@ -2834,7 +2934,8 @@ async function state(options = {}) {
|
|
|
2834
2934
|
truncated: truncated === true,
|
|
2835
2935
|
enabled: policyResult.writable !== false && (root.kind === "project-dsh" || policyResult.state.sources[root.key] !== false),
|
|
2836
2936
|
skills,
|
|
2837
|
-
categories: policyResult.state.categories[root.key] || []
|
|
2937
|
+
categories: policyResult.state.categories[root.key] || [],
|
|
2938
|
+
categoryOrder: policyResult.state.categoryOrder[root.key] || {}
|
|
2838
2939
|
});
|
|
2839
2940
|
}
|
|
2840
2941
|
const userItems = all.filter((item) => item.root.scope !== "project");
|
|
@@ -2876,6 +2977,7 @@ export {
|
|
|
2876
2977
|
KEBAB_RE,
|
|
2877
2978
|
assignSkillCategory,
|
|
2878
2979
|
browseDirectories,
|
|
2980
|
+
checkedRepositoryRootDefinition,
|
|
2879
2981
|
createCategory,
|
|
2880
2982
|
createSkill,
|
|
2881
2983
|
createWorkspaceIndex,
|
|
@@ -2892,6 +2994,7 @@ export {
|
|
|
2892
2994
|
logPath,
|
|
2893
2995
|
managerHomePath,
|
|
2894
2996
|
managerStatePath,
|
|
2997
|
+
moveSkillInCategory,
|
|
2895
2998
|
parseBoolValue,
|
|
2896
2999
|
parseSkillDoc,
|
|
2897
3000
|
permanentlyDeleteTrash,
|
package/lib/index.js
CHANGED
|
@@ -7,9 +7,8 @@ import {
|
|
|
7
7
|
renameCategory,
|
|
8
8
|
deleteCategory,
|
|
9
9
|
assignSkillCategory,
|
|
10
|
+
moveSkillInCategory,
|
|
10
11
|
setCategoryEnabled,
|
|
11
|
-
deleteSkill,
|
|
12
|
-
permanentlyDeleteTrash,
|
|
13
12
|
importSkill,
|
|
14
13
|
importUploadedSkill,
|
|
15
14
|
browseDirectories,
|
|
@@ -27,6 +26,9 @@ import {
|
|
|
27
26
|
import { registerPluginUpdater } from "./plugin-updater.js";
|
|
28
27
|
import { createRepositoryManager } from "./repositories.js";
|
|
29
28
|
import { installationSourceKey } from "./repository-updates.js";
|
|
29
|
+
import { listClawHub, searchClawHub } from "./clawhub-catalog.js";
|
|
30
|
+
import { searchSkillsSh } from "./skills-sh-catalog.js";
|
|
31
|
+
import { loadCatalogPackage, readCatalogSkill } from "./catalog-skill.js";
|
|
30
32
|
const name = "skills-manager";
|
|
31
33
|
const inject = ["webServer", "skills", "tools", "sessions"];
|
|
32
34
|
const CLIENT_MARKER_HEADER = "x-dsh-skills-manager";
|
|
@@ -362,7 +364,6 @@ function apply(ctx) {
|
|
|
362
364
|
return { projectCwds: target.root.workspaceCwds ?? [] };
|
|
363
365
|
};
|
|
364
366
|
const scopedOptions = (scope) => scope ?? projectOptions();
|
|
365
|
-
const userWriteOptions = (body, target) => ({ allowRepositoryRoot: !target && body.root === "agents" });
|
|
366
367
|
const requestRoot = async (key, cwds) => cwds ? (await projectRoots(cwds)).find((root) => root.key === key) : rootByKey[key] || (await projectRoots(projectOptions().projectCwds)).find((root) => root.key === key);
|
|
367
368
|
const withScope = async (body, action) => {
|
|
368
369
|
const scope = await workspaceScope(body);
|
|
@@ -396,7 +397,25 @@ function apply(ctx) {
|
|
|
396
397
|
return run(res, async () => ({ items: await listWorkspaces(registeredWorkspacePaths(ctx) ?? []) }), void 0, req.method === "HEAD");
|
|
397
398
|
}
|
|
398
399
|
if ((req.method === "GET" || req.method === "HEAD") && path === "/api/dsh-skills-manager/repositories") {
|
|
399
|
-
return run(res, () => repositories.list(), void 0, req.method === "HEAD");
|
|
400
|
+
return run(res, () => enqueueMutation(() => repositories.list()), void 0, req.method === "HEAD");
|
|
401
|
+
}
|
|
402
|
+
if (req.method === "GET" && (path === "/api/dsh-skills-manager/clawhub/list" || path === "/api/dsh-skills-manager/clawhub/search")) {
|
|
403
|
+
const searching = path.endsWith("/search");
|
|
404
|
+
const allowed = searching ? ["q", "limit"] : ["limit", "cursor", "sort"];
|
|
405
|
+
if ([...u.searchParams.keys()].some((key) => !allowed.includes(key) || u.searchParams.getAll(key).length !== 1) || searching && !u.searchParams.has("q")) {
|
|
406
|
+
json(res, 400, { ok: false, code: "error.catalog.query", error: "\u76EE\u5F55\u67E5\u8BE2\u53C2\u6570\u65E0\u6548" });
|
|
407
|
+
return;
|
|
408
|
+
}
|
|
409
|
+
const parameter = (key) => u.searchParams.get(key) ?? void 0;
|
|
410
|
+
return run(res, () => searching ? searchClawHub(parameter("q"), { limit: parameter("limit") }) : listClawHub({ limit: parameter("limit"), sort: parameter("sort"), cursor: parameter("cursor") }));
|
|
411
|
+
}
|
|
412
|
+
if (req.method === "GET" && path === "/api/dsh-skills-manager/skills-sh/search") {
|
|
413
|
+
const queries = u.searchParams.getAll("q");
|
|
414
|
+
if (queries.length !== 1) {
|
|
415
|
+
json(res, 400, { ok: false, code: "error.catalog.query", error: "\u641C\u7D22\u8BCD\u4E0D\u80FD\u4E3A\u7A7A\u4E14\u4E0D\u80FD\u8D85\u8FC7 100 \u4E2A\u5B57\u7B26" });
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
return run(res, () => searchSkillsSh(queries[0]));
|
|
400
419
|
}
|
|
401
420
|
if (req.method === "HEAD") {
|
|
402
421
|
if (path === "/api/dsh-skills-manager/state") return run(res, stateWithSources, void 0, true);
|
|
@@ -421,6 +440,18 @@ function apply(ctx) {
|
|
|
421
440
|
return run(res, () => browseDirectories(body.path));
|
|
422
441
|
}
|
|
423
442
|
if (path === "/api/dsh-skills-manager/repositories/refresh") return run(res, () => repositories.refresh(body));
|
|
443
|
+
if (path === "/api/dsh-skills-manager/catalog/detail") return run(res, () => readCatalogSkill(body));
|
|
444
|
+
if (path === "/api/dsh-skills-manager/catalog/install") {
|
|
445
|
+
return run(res, async () => {
|
|
446
|
+
if (body.root !== void 0 && body.root !== "dsh" && body.root !== "agents") throw Object.assign(new Error("\u6280\u80FD\u5B89\u88C5\u4F4D\u7F6E\u65E0\u6548"), { statusCode: 400, code: "error.repo.invalid" });
|
|
447
|
+
if (body.workspace !== void 0 && body.root !== void 0 && body.root !== "dsh") throw Object.assign(new Error("\u5DE5\u4F5C\u533A\u53EA\u80FD\u5B89\u88C5\u5230\u9879\u76EE DSH skills"), { statusCode: 400, code: "error.repo.invalid" });
|
|
448
|
+
const target = await workspaceTarget(body);
|
|
449
|
+
if (target && "ok" in target && target.ok === false) return target;
|
|
450
|
+
const pack = await loadCatalogPackage(body);
|
|
451
|
+
const catalogId = body.catalog === "skillsh" ? `skillsh:${body.source}/${body.skillId}` : `clawhub:${body.owner}/${body.slug}`;
|
|
452
|
+
return enqueueMutation(() => repositories.installCatalog(catalogId, pack, { root: body.root, ...body.workspace === void 0 ? {} : { workspace: body.workspace } }));
|
|
453
|
+
}, afterWrite);
|
|
454
|
+
}
|
|
424
455
|
return enqueueMutation(() => {
|
|
425
456
|
switch (path) {
|
|
426
457
|
case "/api/dsh-skills-manager/repositories/add":
|
|
@@ -453,23 +484,30 @@ function apply(ctx) {
|
|
|
453
484
|
case "/api/dsh-skills-manager/source-disable":
|
|
454
485
|
return run(res, () => withScope(body, (scope) => setSourceEnabled(String(body.root || ""), false, log, scopedOptions(scope))), afterWrite);
|
|
455
486
|
case "/api/dsh-skills-manager/category-create":
|
|
456
|
-
return run(res, () => withScope(body, (scope) => createCategory(String(body.root || ""), String(body.name || ""), log, scopedOptions(scope)))
|
|
487
|
+
return run(res, () => withScope(body, (scope) => createCategory(String(body.root || ""), String(body.name || ""), log, scopedOptions(scope))));
|
|
457
488
|
case "/api/dsh-skills-manager/category-rename":
|
|
458
|
-
return run(res, () => withScope(body, (scope) => renameCategory(String(body.root || ""), String(body.id || ""), String(body.name || ""), log, scopedOptions(scope)))
|
|
489
|
+
return run(res, () => withScope(body, (scope) => renameCategory(String(body.root || ""), String(body.id || ""), String(body.name || ""), log, scopedOptions(scope))));
|
|
459
490
|
case "/api/dsh-skills-manager/category-delete":
|
|
460
|
-
return run(res, () => withScope(body, (scope) => deleteCategory(String(body.root || ""), String(body.id || ""), log, scopedOptions(scope)))
|
|
491
|
+
return run(res, () => withScope(body, (scope) => deleteCategory(String(body.root || ""), String(body.id || ""), log, scopedOptions(scope))));
|
|
461
492
|
case "/api/dsh-skills-manager/category-assign":
|
|
462
|
-
return run(res, () => withScope(body, (scope) => assignSkillCategory(String(body.root || ""), String(body.name || ""), String(body.category || ""), log, scopedOptions(scope)))
|
|
493
|
+
return run(res, () => withScope(body, (scope) => assignSkillCategory(String(body.root || ""), String(body.name || ""), String(body.category || ""), log, scopedOptions(scope))));
|
|
494
|
+
case "/api/dsh-skills-manager/category-move":
|
|
495
|
+
return run(res, () => withScope(body, (scope) => moveSkillInCategory(String(body.root || ""), String(body.name || ""), String(body.category || ""), body.targetName ? String(body.targetName) : void 0, body.position === void 0 ? "before" : String(body.position), log, scopedOptions(scope))));
|
|
463
496
|
case "/api/dsh-skills-manager/category-enable":
|
|
464
497
|
return run(res, () => withScope(body, (scope) => setCategoryEnabled(String(body.root || ""), String(body.category || ""), true, log, scopedOptions(scope))), afterWrite);
|
|
465
498
|
case "/api/dsh-skills-manager/category-disable":
|
|
466
499
|
return run(res, () => withScope(body, (scope) => setCategoryEnabled(String(body.root || ""), String(body.category || ""), false, log, scopedOptions(scope))), afterWrite);
|
|
467
500
|
case "/api/dsh-skills-manager/delete":
|
|
468
|
-
return run(res, () => withScope(body, async (scope) =>
|
|
501
|
+
return run(res, () => withScope(body, async (scope) => {
|
|
502
|
+
const target = await workspaceTarget(body);
|
|
503
|
+
if (target && "ok" in target && target.ok === false) return target;
|
|
504
|
+
const root = target ? target.root : await requestRoot(String(body.root || "dsh"), scope?.projectCwds);
|
|
505
|
+
return repositories.trashSkill(root, String(body.name || ""));
|
|
506
|
+
}), afterWrite);
|
|
469
507
|
case "/api/dsh-skills-manager/trash-restore":
|
|
470
508
|
return run(res, () => withScope(body, (scope) => repositories.restoreTrash(String(body.id || ""), { ...scope ?? {}, registeredWorkspaces: registeredWorkspacePaths(ctx) })), afterWrite);
|
|
471
509
|
case "/api/dsh-skills-manager/trash-delete":
|
|
472
|
-
return run(res, () => permanentlyDeleteTrash(String(body.id || "")
|
|
510
|
+
return run(res, () => repositories.permanentlyDeleteTrash(String(body.id || "")));
|
|
473
511
|
case "/api/dsh-skills-manager/detail":
|
|
474
512
|
return run(res, () => withScope(body, (scope) => skillDetail(String(body.root || "dsh"), String(body.name || ""), scope ? { ...scope, workspaceBound: true } : projectOptions())));
|
|
475
513
|
case "/api/dsh-skills-manager/create":
|
|
@@ -479,7 +517,7 @@ function apply(ctx) {
|
|
|
479
517
|
return createSkill(
|
|
480
518
|
{ name: body.name, description: body.description, body: body.body },
|
|
481
519
|
log,
|
|
482
|
-
{ root: target ? target.root : await requestRoot(String(body.root || "dsh"))
|
|
520
|
+
{ root: target ? target.root : await requestRoot(String(body.root || "dsh")) }
|
|
483
521
|
);
|
|
484
522
|
}, afterWrite);
|
|
485
523
|
case "/api/dsh-skills-manager/import":
|
|
@@ -489,8 +527,7 @@ function apply(ctx) {
|
|
|
489
527
|
return importSkill(String(body.source || ""), log, {
|
|
490
528
|
conflict: body.conflict === "overwrite" ? "overwrite" : "skip",
|
|
491
529
|
dryRun: body.dryRun === true,
|
|
492
|
-
...target ? { root: target.root } : body.root === "agents" ? { root: await requestRoot("agents") } : {}
|
|
493
|
-
...userWriteOptions(body, target)
|
|
530
|
+
...target ? { root: target.root } : body.root === "agents" ? { root: await requestRoot("agents") } : {}
|
|
494
531
|
});
|
|
495
532
|
}, body.dryRun === true ? void 0 : afterWrite);
|
|
496
533
|
case "/api/dsh-skills-manager/upload":
|
|
@@ -499,8 +536,7 @@ function apply(ctx) {
|
|
|
499
536
|
if (target && "ok" in target && target.ok === false) return target;
|
|
500
537
|
return importUploadedSkill({ name: body.name, entries: body.entries, zip: body.zip }, log, {
|
|
501
538
|
conflict: body.conflict === "overwrite" ? "overwrite" : "skip",
|
|
502
|
-
...target ? { root: target.root } : body.root === "agents" ? { root: await requestRoot("agents") } : {}
|
|
503
|
-
...userWriteOptions(body, target)
|
|
539
|
+
...target ? { root: target.root } : body.root === "agents" ? { root: await requestRoot("agents") } : {}
|
|
504
540
|
});
|
|
505
541
|
}, afterWrite);
|
|
506
542
|
default:
|