dsh-plugin-capabilities 0.3.3 → 0.3.4

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/lib/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // src/index.ts
2
2
  import { existsSync as existsSync7 } from "node:fs";
3
- import { dirname as dirname4, join as join9 } from "node:path";
3
+ import { dirname as dirname4, join as join10 } from "node:path";
4
4
  import { fileURLToPath as fileURLToPath2 } from "node:url";
5
5
 
6
6
  // src/agents.ts
@@ -174,7 +174,7 @@ function skipVoid(ctx, banNewLines, banComments) {
174
174
  skipComment(ctx);
175
175
  }
176
176
  }
177
- function skipUntil(ctx, sep, end) {
177
+ function skipUntil(ctx, sep2, end) {
178
178
  let ptr = ctx.p;
179
179
  if (!end) {
180
180
  ptr = indexOfNewline(ctx.s, ptr);
@@ -185,7 +185,7 @@ function skipUntil(ctx, sep, end) {
185
185
  let c = ctx.s.charCodeAt(ctx.p);
186
186
  if (c === 35) {
187
187
  skipComment(ctx);
188
- } else if (c === end || c === sep) {
188
+ } else if (c === end || c === sep2) {
189
189
  return;
190
190
  }
191
191
  }
@@ -771,6 +771,7 @@ function profileDir(profile, dshHome = process.env.DSH_HOME) {
771
771
 
772
772
  // src/routes.ts
773
773
  import { mkdirSync as mkdirSync6 } from "node:fs";
774
+ import { join as join9, sep } from "node:path";
774
775
 
775
776
  // src/http.ts
776
777
  async function readJsonBody(request) {
@@ -1371,6 +1372,20 @@ function setSkillPolicy(file, enabled) {
1371
1372
  if (!enabled) kept.push("disable-model-invocation: true", "user-invocable: false");
1372
1373
  writeFileSync3(file, `---${newline}${kept.join(newline)}${newline}---${body}`, "utf8");
1373
1374
  }
1375
+ function updateSkillFile(file, input) {
1376
+ const text = readFileSync5(file, "utf8");
1377
+ const match = /^---\r?\n([\s\S]*?)\r?\n---/.exec(text);
1378
+ if (match === null) throw new Error("skill file has no frontmatter block");
1379
+ const newline = text.includes("\r\n---") ? "\r\n" : "\n";
1380
+ const lines = match[1].split(/\r?\n/);
1381
+ const kept = lines.filter((line) => !/^(name|description|whenToUse|disable-model-invocation|user-invocable):/.test(line));
1382
+ const head = [`name: ${input.name}`, `description: ${quote(input.description)}`];
1383
+ if (input.whenToUse !== void 0 && input.whenToUse !== "") head.push(`whenToUse: ${quote(input.whenToUse)}`);
1384
+ if (!input.modelInvocable) kept.push("disable-model-invocation: true");
1385
+ if (!input.userInvocable) kept.push("user-invocable: false");
1386
+ const body = input.content.replace(/\r\n/g, "\n").trim();
1387
+ writeFileSync3(file, `---${newline}${[...head, ...kept].join(newline)}${newline}---${newline}${newline}${body}${newline}`, "utf8");
1388
+ }
1374
1389
 
1375
1390
  // src/mcp.ts
1376
1391
  import { existsSync as existsSync6, mkdirSync as mkdirSync5, readFileSync as readFileSync6, writeFileSync as writeFileSync4 } from "node:fs";
@@ -1550,10 +1565,25 @@ function removeMcp(profileDirPath, id) {
1550
1565
  }
1551
1566
 
1552
1567
  // src/routes.ts
1553
- var EDITABLE_SOURCE = "user-dsh";
1554
- function toSkillRow(skill) {
1568
+ function customSkillWritable(dir, dshHome) {
1569
+ const state = pluginStateDir(dshHome);
1570
+ if (dir === state || dir.startsWith(state + sep)) return true;
1571
+ return loadState(dshHome).skillRoots.some((entry) => entry.roots.some((root) => dir === root || dir.startsWith(root + sep)));
1572
+ }
1573
+ function skillWritable(skill, dir, dshHome) {
1574
+ if (dir === void 0) return false;
1575
+ if (skill.source === "user-dsh") return true;
1576
+ return skill.source === "custom" && customSkillWritable(dir, dshHome);
1577
+ }
1578
+ function toSkillRow(skill, dshHome) {
1555
1579
  const dir = skill.resourceBase?.kind === "directory" ? skill.resourceBase.path : void 0;
1556
- return { ...skill, editable: skill.source === EDITABLE_SOURCE, ...dir !== void 0 ? { dir } : {}, policyEditable: dir !== void 0 };
1580
+ return {
1581
+ ...skill,
1582
+ editable: skillWritable(skill, dir, dshHome),
1583
+ removable: skill.source === "user-dsh",
1584
+ ...dir !== void 0 ? { dir } : {},
1585
+ policyEditable: dir !== void 0
1586
+ };
1557
1587
  }
1558
1588
  function toRootView(entry) {
1559
1589
  return { ...entry, live: entry.roots.every((root) => rootExists(root)) };
@@ -1571,7 +1601,7 @@ function mountCapabilitiesRoutes(host, config) {
1571
1601
  }
1572
1602
  try {
1573
1603
  const skills = await host.skills.list();
1574
- sendJson(response, 200, { skills: skills.map(toSkillRow) });
1604
+ sendJson(response, 200, { skills: skills.map((skill) => toSkillRow(skill, process.env.DSH_HOME)) });
1575
1605
  } catch (error) {
1576
1606
  sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
1577
1607
  }
@@ -1628,7 +1658,17 @@ function mountCapabilitiesRoutes(host, config) {
1628
1658
  sendJson(response, 400, { error: invalid });
1629
1659
  return;
1630
1660
  }
1631
- writeSkill(input);
1661
+ const existing = (await host.skills.list()).find((skill) => skill.name === input.name);
1662
+ if (existing !== void 0) {
1663
+ const dir = existing.resourceBase?.kind === "directory" ? existing.resourceBase.path : void 0;
1664
+ if (!skillWritable(existing, dir, process.env.DSH_HOME)) {
1665
+ sendJson(response, 403, { error: `skills from source '${existing.source}' are read-only` });
1666
+ return;
1667
+ }
1668
+ updateSkillFile(join9(dir, "SKILL.md"), input);
1669
+ } else {
1670
+ writeSkill(input);
1671
+ }
1632
1672
  sendJson(response, 200, { ok: true, name: input.name });
1633
1673
  } catch (error) {
1634
1674
  sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
@@ -2131,7 +2171,7 @@ function mountCapabilitiesRoutes(host, config) {
2131
2171
  // src/index.ts
2132
2172
  var name = "dsh-plugin-capabilities";
2133
2173
  function packagedSkillsDir() {
2134
- return join9(dirname4(fileURLToPath2(import.meta.url)), "..", "skills");
2174
+ return join10(dirname4(fileURLToPath2(import.meta.url)), "..", "skills");
2135
2175
  }
2136
2176
  var inject = ["webServer", "skills"];
2137
2177
  function apply(ctx, config) {