dsh-plugin-capabilities 0.3.6 → 0.3.8

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
@@ -764,9 +764,11 @@ function argvProfile(argv = process.argv) {
764
764
  if (flag !== -1 && flag + 1 < argv.length && !argv[flag + 1].startsWith("-")) return argv[flag + 1];
765
765
  return void 0;
766
766
  }
767
+ function dshHomeDir(dshHome = process.env.DSH_HOME) {
768
+ return dshHome ?? join2(homedir2(), ".dsh");
769
+ }
767
770
  function profileDir(profile, dshHome = process.env.DSH_HOME) {
768
- const home = dshHome ?? join2(homedir2(), ".dsh");
769
- return join2(home, "profiles", profile);
771
+ return join2(dshHomeDir(dshHome), "profiles", profile);
770
772
  }
771
773
 
772
774
  // src/routes.ts
@@ -1429,17 +1431,26 @@ import { join as join9 } from "node:path";
1429
1431
  import { parseDocument, Document } from "yaml";
1430
1432
  var MCP_PLUGIN = "@deepseek-ai/dsh-mcp-client";
1431
1433
  var SERVER_NAME_RE = /^[A-Za-z0-9_-]{1,32}$/;
1432
- function loadPatch(profileDirPath) {
1433
- const path = join9(profileDirPath, "cordis.patch.yml");
1434
+ function assertPatchParses(path, doc) {
1435
+ if (doc.errors.length === 0) return;
1436
+ const at = String(doc.errors[0].message).split("\n", 1)[0];
1437
+ const total = doc.errors.length > 1 ? `, +${doc.errors.length - 1} more` : "";
1438
+ throw new Error(
1439
+ `\u914D\u7F6E\u6587\u4EF6 ${path} \u5B58\u5728 YAML \u8BED\u6CD5\u9519\u8BEF\uFF08${at}${total}\uFF09\uFF0C\u672A\u5199\u5165\u4EFB\u4F55\u5185\u5BB9\uFF0C\u8BF7\u4FEE\u590D\u8BE5\u6587\u4EF6\u540E\u91CD\u8BD5 / YAML syntax error in the patch file (${at}${total}); nothing was written \u2014 fix it and retry`
1440
+ );
1441
+ }
1442
+ function loadPatch(dirPath) {
1443
+ const path = join9(dirPath, "cordis.patch.yml");
1434
1444
  const text = existsSync6(path) ? readFileSync6(path, "utf8") : "[]";
1435
1445
  const doc = parseDocument(text);
1446
+ assertPatchParses(path, doc);
1436
1447
  const contents = doc.contents;
1437
1448
  if (contents !== null && contents.flow === true && contents.items.length === 0) contents.flow = false;
1438
1449
  return doc;
1439
1450
  }
1440
- function savePatch(profileDirPath, doc) {
1441
- mkdirSync5(profileDirPath, { recursive: true });
1442
- writeFileSync4(join9(profileDirPath, "cordis.patch.yml"), String(doc), "utf8");
1451
+ function savePatch(dirPath, doc) {
1452
+ mkdirSync5(dirPath, { recursive: true });
1453
+ writeFileSync4(join9(dirPath, "cordis.patch.yml"), String(doc), "utf8");
1443
1454
  }
1444
1455
  function toNode(value) {
1445
1456
  return new Document(value).contents;
@@ -1523,10 +1534,36 @@ function takenIds(doc) {
1523
1534
  }
1524
1535
  return taken;
1525
1536
  }
1526
- function listMcp(profileDirPath) {
1527
- const doc = loadPatch(profileDirPath);
1537
+ function listMcp(dirPath) {
1538
+ const doc = loadPatch(dirPath);
1528
1539
  return mcpRowItems(doc).map(({ node }) => rowToMcp(doc, node));
1529
1540
  }
1541
+ function listMcpScoped(profileDirPath, dshHomePath) {
1542
+ let globalRows = [];
1543
+ let globalError;
1544
+ if (existsSync6(join9(dshHomePath, "cordis.patch.yml"))) {
1545
+ try {
1546
+ globalRows = listMcp(dshHomePath);
1547
+ } catch (error) {
1548
+ globalError = error instanceof Error ? error.message : String(error);
1549
+ }
1550
+ }
1551
+ const globalIds = new Set(globalRows.map((row) => row.id).filter((id) => id !== ""));
1552
+ return {
1553
+ servers: [
1554
+ ...globalRows.map((row) => ({ ...row, scope: "global" })),
1555
+ ...listMcp(profileDirPath).map((row) => ({
1556
+ ...row,
1557
+ scope: "profile",
1558
+ ...globalIds.has(row.id) ? { shadowed: true } : {}
1559
+ }))
1560
+ ],
1561
+ ...globalError !== void 0 ? { globalError } : {}
1562
+ };
1563
+ }
1564
+ function mcpScopeDir(scope, profileDirPath, dshHomePath) {
1565
+ return scope === "global" ? dshHomePath : profileDirPath;
1566
+ }
1530
1567
  function validateMcpInput(input) {
1531
1568
  if (!SERVER_NAME_RE.test(input.serverName)) return "serverName must be 1-32 chars of A-Z a-z 0-9 _ -";
1532
1569
  const id = input.id ?? "";
@@ -1538,9 +1575,9 @@ function validateMcpInput(input) {
1538
1575
  }
1539
1576
  return null;
1540
1577
  }
1541
- function upsertMcp(profileDirPath, input) {
1578
+ function upsertMcp(dirPath, input) {
1542
1579
  const inputId = input.id ?? "";
1543
- const doc = loadPatch(profileDirPath);
1580
+ const doc = loadPatch(dirPath);
1544
1581
  const list = managedInsert(doc);
1545
1582
  const existing = inputId !== "" ? mcpRowItems(doc).find(({ node: node2 }) => String(node2.get("id") ?? "") === inputId) : void 0;
1546
1583
  let id = inputId !== "" ? inputId : `mcp-${input.serverName}`;
@@ -1572,21 +1609,21 @@ function upsertMcp(profileDirPath, input) {
1572
1609
  } else {
1573
1610
  rowSeq(doc).items.splice(rowSeq(doc).items.indexOf(existing.node), 1, node);
1574
1611
  }
1575
- savePatch(profileDirPath, doc);
1612
+ savePatch(dirPath, doc);
1576
1613
  return id;
1577
1614
  }
1578
- function setMcpDisabled(profileDirPath, id, disabled) {
1579
- const doc = loadPatch(profileDirPath);
1615
+ function setMcpDisabled(dirPath, id, disabled) {
1616
+ const doc = loadPatch(dirPath);
1580
1617
  managedInsert(doc);
1581
1618
  const hit = mcpRowItems(doc).find(({ node }) => String(node.get("id") ?? "") === id);
1582
1619
  if (hit === void 0) return false;
1583
1620
  if (disabled) hit.node.set("disabled", true);
1584
1621
  else hit.node.delete("disabled");
1585
- savePatch(profileDirPath, doc);
1622
+ savePatch(dirPath, doc);
1586
1623
  return true;
1587
1624
  }
1588
- function removeMcp(profileDirPath, id) {
1589
- const doc = loadPatch(profileDirPath);
1625
+ function removeMcp(dirPath, id) {
1626
+ const doc = loadPatch(dirPath);
1590
1627
  managedInsert(doc);
1591
1628
  const hit = mcpRowItems(doc).find(({ node }) => String(node.get("id") ?? "") === id);
1592
1629
  if (hit === void 0 || hit.list === void 0) return false;
@@ -1596,7 +1633,7 @@ function removeMcp(profileDirPath, id) {
1596
1633
  if (owner !== void 0 && hit.list.items.length === 0 && owner.items.length === 1) {
1597
1634
  seq.items.splice(seq.items.indexOf(owner), 1);
1598
1635
  }
1599
- savePatch(profileDirPath, doc);
1636
+ savePatch(dirPath, doc);
1600
1637
  return true;
1601
1638
  }
1602
1639
 
@@ -1624,6 +1661,9 @@ function toSkillRow(skill, dshHome) {
1624
1661
  function toRootView(entry) {
1625
1662
  return { ...entry, live: entry.roots.every((root) => rootExists(root)) };
1626
1663
  }
1664
+ function readScope(value) {
1665
+ return value === "global" ? "global" : "profile";
1666
+ }
1627
1667
  function mountCapabilitiesRoutes(host, config) {
1628
1668
  const disposers = [
1629
1669
  host.webServer.register({
@@ -1940,7 +1980,7 @@ function mountCapabilitiesRoutes(host, config) {
1940
1980
  sendJson(response, 502, { error: "market index unavailable (offline?)" });
1941
1981
  return;
1942
1982
  }
1943
- const existing = new Set(listMcp(config.profileDirPath).map((row) => row.serverName));
1983
+ const existing = new Set(listMcpScoped(config.profileDirPath, config.dshHomePath).servers.map((row) => row.serverName));
1944
1984
  sendJson(response, 200, {
1945
1985
  source: index.source,
1946
1986
  servers: (index.servers ?? []).map((server) => ({ ...server, installed: existing.has(server.id) }))
@@ -1999,7 +2039,7 @@ function mountCapabilitiesRoutes(host, config) {
1999
2039
  sendJson(response, 404, { error: "server not found in the market index" });
2000
2040
  return;
2001
2041
  }
2002
- if (listMcp(config.profileDirPath).some((row) => row.serverName === server.id)) {
2042
+ if (listMcpScoped(config.profileDirPath, config.dshHomePath).servers.some((row) => row.serverName === server.id)) {
2003
2043
  sendJson(response, 409, { error: "already installed" });
2004
2044
  return;
2005
2045
  }
@@ -2030,7 +2070,12 @@ function mountCapabilitiesRoutes(host, config) {
2030
2070
  response.end();
2031
2071
  return;
2032
2072
  }
2033
- sendJson(response, 200, { servers: listMcp(config.profileDirPath), restartNeeded: true });
2073
+ const listing = listMcpScoped(config.profileDirPath, config.dshHomePath);
2074
+ sendJson(response, 200, {
2075
+ servers: listing.servers,
2076
+ ...listing.globalError !== void 0 ? { globalError: listing.globalError } : {},
2077
+ restartNeeded: true
2078
+ });
2034
2079
  }
2035
2080
  }),
2036
2081
  host.webServer.register({
@@ -2053,8 +2098,9 @@ function mountCapabilitiesRoutes(host, config) {
2053
2098
  sendJson(response, 400, { error: invalid });
2054
2099
  return;
2055
2100
  }
2056
- const id = upsertMcp(config.profileDirPath, input);
2057
- sendJson(response, 200, { ok: true, id, restartNeeded: true });
2101
+ const scope = readScope(input.scope);
2102
+ const id = upsertMcp(mcpScopeDir(scope, config.profileDirPath, config.dshHomePath), input);
2103
+ sendJson(response, 200, { ok: true, id, scope, restartNeeded: true });
2058
2104
  } catch (error) {
2059
2105
  sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
2060
2106
  }
@@ -2079,7 +2125,8 @@ function mountCapabilitiesRoutes(host, config) {
2079
2125
  sendJson(response, 400, { error: "id and disabled are required" });
2080
2126
  return;
2081
2127
  }
2082
- const ok = setMcpDisabled(config.profileDirPath, body.id, body.disabled);
2128
+ const dir = mcpScopeDir(readScope(body.scope), config.profileDirPath, config.dshHomePath);
2129
+ const ok = setMcpDisabled(dir, body.id, body.disabled);
2083
2130
  sendJson(response, ok ? 200 : 404, ok ? { ok: true, restartNeeded: true } : { error: "server row not found" });
2084
2131
  } catch (error) {
2085
2132
  sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
@@ -2105,7 +2152,8 @@ function mountCapabilitiesRoutes(host, config) {
2105
2152
  sendJson(response, 400, { error: "id is required" });
2106
2153
  return;
2107
2154
  }
2108
- const ok = removeMcp(config.profileDirPath, body.id);
2155
+ const dir = mcpScopeDir(readScope(body.scope), config.profileDirPath, config.dshHomePath);
2156
+ const ok = removeMcp(dir, body.id);
2109
2157
  sendJson(response, ok ? 200 : 404, ok ? { ok: true, restartNeeded: true } : { error: "server row not found" });
2110
2158
  } catch (error) {
2111
2159
  sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
@@ -2124,8 +2172,9 @@ function mountCapabilitiesRoutes(host, config) {
2124
2172
  try {
2125
2173
  sendJson(response, 200, {
2126
2174
  servers: scanAllMcp(),
2127
- // Profile serverNames, so the browser can grey out existing ones.
2128
- existing: listMcp(config.profileDirPath).map((row) => row.serverName)
2175
+ // ServerNames present in either patch layer, so the browser can
2176
+ // grey out existing ones.
2177
+ existing: listMcpScoped(config.profileDirPath, config.dshHomePath).servers.map((row) => row.serverName)
2129
2178
  });
2130
2179
  } catch (error) {
2131
2180
  sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
@@ -2153,9 +2202,8 @@ function mountCapabilitiesRoutes(host, config) {
2153
2202
  const results = [];
2154
2203
  for (const server of scanAllMcp()) {
2155
2204
  if (!wanted.has(`${server.agent}/${server.name}`)) continue;
2156
- const existing = listMcp(config.profileDirPath).some((row) => row.serverName === server.name);
2157
- if (existing) {
2158
- results.push({ name: server.name, ok: false, error: "already in profile" });
2205
+ if (listMcpScoped(config.profileDirPath, config.dshHomePath).servers.some((row) => row.serverName === server.name)) {
2206
+ results.push({ name: server.name, ok: false, error: "already configured" });
2159
2207
  continue;
2160
2208
  }
2161
2209
  const input = {
@@ -2252,6 +2300,7 @@ function apply(ctx, config) {
2252
2300
  ctx.effect(
2253
2301
  () => mountCapabilitiesRoutes(hostCtx, {
2254
2302
  profileDirPath: profileDir(profile),
2303
+ dshHomePath: dshHomeDir(),
2255
2304
  remountProvider
2256
2305
  }),
2257
2306
  "dsh-plugin-capabilities: http routes"