dsh-plugin-capabilities 0.3.7 → 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
@@ -1437,8 +1439,8 @@ function assertPatchParses(path, doc) {
1437
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`
1438
1440
  );
1439
1441
  }
1440
- function loadPatch(profileDirPath) {
1441
- const path = join9(profileDirPath, "cordis.patch.yml");
1442
+ function loadPatch(dirPath) {
1443
+ const path = join9(dirPath, "cordis.patch.yml");
1442
1444
  const text = existsSync6(path) ? readFileSync6(path, "utf8") : "[]";
1443
1445
  const doc = parseDocument(text);
1444
1446
  assertPatchParses(path, doc);
@@ -1446,9 +1448,9 @@ function loadPatch(profileDirPath) {
1446
1448
  if (contents !== null && contents.flow === true && contents.items.length === 0) contents.flow = false;
1447
1449
  return doc;
1448
1450
  }
1449
- function savePatch(profileDirPath, doc) {
1450
- mkdirSync5(profileDirPath, { recursive: true });
1451
- 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");
1452
1454
  }
1453
1455
  function toNode(value) {
1454
1456
  return new Document(value).contents;
@@ -1532,10 +1534,36 @@ function takenIds(doc) {
1532
1534
  }
1533
1535
  return taken;
1534
1536
  }
1535
- function listMcp(profileDirPath) {
1536
- const doc = loadPatch(profileDirPath);
1537
+ function listMcp(dirPath) {
1538
+ const doc = loadPatch(dirPath);
1537
1539
  return mcpRowItems(doc).map(({ node }) => rowToMcp(doc, node));
1538
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
+ }
1539
1567
  function validateMcpInput(input) {
1540
1568
  if (!SERVER_NAME_RE.test(input.serverName)) return "serverName must be 1-32 chars of A-Z a-z 0-9 _ -";
1541
1569
  const id = input.id ?? "";
@@ -1547,9 +1575,9 @@ function validateMcpInput(input) {
1547
1575
  }
1548
1576
  return null;
1549
1577
  }
1550
- function upsertMcp(profileDirPath, input) {
1578
+ function upsertMcp(dirPath, input) {
1551
1579
  const inputId = input.id ?? "";
1552
- const doc = loadPatch(profileDirPath);
1580
+ const doc = loadPatch(dirPath);
1553
1581
  const list = managedInsert(doc);
1554
1582
  const existing = inputId !== "" ? mcpRowItems(doc).find(({ node: node2 }) => String(node2.get("id") ?? "") === inputId) : void 0;
1555
1583
  let id = inputId !== "" ? inputId : `mcp-${input.serverName}`;
@@ -1581,21 +1609,21 @@ function upsertMcp(profileDirPath, input) {
1581
1609
  } else {
1582
1610
  rowSeq(doc).items.splice(rowSeq(doc).items.indexOf(existing.node), 1, node);
1583
1611
  }
1584
- savePatch(profileDirPath, doc);
1612
+ savePatch(dirPath, doc);
1585
1613
  return id;
1586
1614
  }
1587
- function setMcpDisabled(profileDirPath, id, disabled) {
1588
- const doc = loadPatch(profileDirPath);
1615
+ function setMcpDisabled(dirPath, id, disabled) {
1616
+ const doc = loadPatch(dirPath);
1589
1617
  managedInsert(doc);
1590
1618
  const hit = mcpRowItems(doc).find(({ node }) => String(node.get("id") ?? "") === id);
1591
1619
  if (hit === void 0) return false;
1592
1620
  if (disabled) hit.node.set("disabled", true);
1593
1621
  else hit.node.delete("disabled");
1594
- savePatch(profileDirPath, doc);
1622
+ savePatch(dirPath, doc);
1595
1623
  return true;
1596
1624
  }
1597
- function removeMcp(profileDirPath, id) {
1598
- const doc = loadPatch(profileDirPath);
1625
+ function removeMcp(dirPath, id) {
1626
+ const doc = loadPatch(dirPath);
1599
1627
  managedInsert(doc);
1600
1628
  const hit = mcpRowItems(doc).find(({ node }) => String(node.get("id") ?? "") === id);
1601
1629
  if (hit === void 0 || hit.list === void 0) return false;
@@ -1605,7 +1633,7 @@ function removeMcp(profileDirPath, id) {
1605
1633
  if (owner !== void 0 && hit.list.items.length === 0 && owner.items.length === 1) {
1606
1634
  seq.items.splice(seq.items.indexOf(owner), 1);
1607
1635
  }
1608
- savePatch(profileDirPath, doc);
1636
+ savePatch(dirPath, doc);
1609
1637
  return true;
1610
1638
  }
1611
1639
 
@@ -1633,6 +1661,9 @@ function toSkillRow(skill, dshHome) {
1633
1661
  function toRootView(entry) {
1634
1662
  return { ...entry, live: entry.roots.every((root) => rootExists(root)) };
1635
1663
  }
1664
+ function readScope(value) {
1665
+ return value === "global" ? "global" : "profile";
1666
+ }
1636
1667
  function mountCapabilitiesRoutes(host, config) {
1637
1668
  const disposers = [
1638
1669
  host.webServer.register({
@@ -1949,7 +1980,7 @@ function mountCapabilitiesRoutes(host, config) {
1949
1980
  sendJson(response, 502, { error: "market index unavailable (offline?)" });
1950
1981
  return;
1951
1982
  }
1952
- 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));
1953
1984
  sendJson(response, 200, {
1954
1985
  source: index.source,
1955
1986
  servers: (index.servers ?? []).map((server) => ({ ...server, installed: existing.has(server.id) }))
@@ -2008,7 +2039,7 @@ function mountCapabilitiesRoutes(host, config) {
2008
2039
  sendJson(response, 404, { error: "server not found in the market index" });
2009
2040
  return;
2010
2041
  }
2011
- if (listMcp(config.profileDirPath).some((row) => row.serverName === server.id)) {
2042
+ if (listMcpScoped(config.profileDirPath, config.dshHomePath).servers.some((row) => row.serverName === server.id)) {
2012
2043
  sendJson(response, 409, { error: "already installed" });
2013
2044
  return;
2014
2045
  }
@@ -2039,7 +2070,12 @@ function mountCapabilitiesRoutes(host, config) {
2039
2070
  response.end();
2040
2071
  return;
2041
2072
  }
2042
- 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
+ });
2043
2079
  }
2044
2080
  }),
2045
2081
  host.webServer.register({
@@ -2062,8 +2098,9 @@ function mountCapabilitiesRoutes(host, config) {
2062
2098
  sendJson(response, 400, { error: invalid });
2063
2099
  return;
2064
2100
  }
2065
- const id = upsertMcp(config.profileDirPath, input);
2066
- 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 });
2067
2104
  } catch (error) {
2068
2105
  sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
2069
2106
  }
@@ -2088,7 +2125,8 @@ function mountCapabilitiesRoutes(host, config) {
2088
2125
  sendJson(response, 400, { error: "id and disabled are required" });
2089
2126
  return;
2090
2127
  }
2091
- 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);
2092
2130
  sendJson(response, ok ? 200 : 404, ok ? { ok: true, restartNeeded: true } : { error: "server row not found" });
2093
2131
  } catch (error) {
2094
2132
  sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
@@ -2114,7 +2152,8 @@ function mountCapabilitiesRoutes(host, config) {
2114
2152
  sendJson(response, 400, { error: "id is required" });
2115
2153
  return;
2116
2154
  }
2117
- 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);
2118
2157
  sendJson(response, ok ? 200 : 404, ok ? { ok: true, restartNeeded: true } : { error: "server row not found" });
2119
2158
  } catch (error) {
2120
2159
  sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
@@ -2133,8 +2172,9 @@ function mountCapabilitiesRoutes(host, config) {
2133
2172
  try {
2134
2173
  sendJson(response, 200, {
2135
2174
  servers: scanAllMcp(),
2136
- // Profile serverNames, so the browser can grey out existing ones.
2137
- 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)
2138
2178
  });
2139
2179
  } catch (error) {
2140
2180
  sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
@@ -2162,9 +2202,8 @@ function mountCapabilitiesRoutes(host, config) {
2162
2202
  const results = [];
2163
2203
  for (const server of scanAllMcp()) {
2164
2204
  if (!wanted.has(`${server.agent}/${server.name}`)) continue;
2165
- const existing = listMcp(config.profileDirPath).some((row) => row.serverName === server.name);
2166
- if (existing) {
2167
- 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" });
2168
2207
  continue;
2169
2208
  }
2170
2209
  const input = {
@@ -2261,6 +2300,7 @@ function apply(ctx, config) {
2261
2300
  ctx.effect(
2262
2301
  () => mountCapabilitiesRoutes(hostCtx, {
2263
2302
  profileDirPath: profileDir(profile),
2303
+ dshHomePath: dshHomeDir(),
2264
2304
  remountProvider
2265
2305
  }),
2266
2306
  "dsh-plugin-capabilities: http routes"