dsh-plugin-capabilities 0.1.0 → 0.1.2

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
@@ -796,6 +796,52 @@ function sendJson(response, status, body) {
796
796
  response.end(payload);
797
797
  }
798
798
 
799
+ // src/restart.ts
800
+ import { spawn } from "node:child_process";
801
+ import { openSync } from "node:fs";
802
+ import { tmpdir } from "node:os";
803
+ import { dirname, resolve } from "node:path";
804
+ function dshLaunch(argv = process.argv, execArgv = process.execArgv) {
805
+ const entry = argv[1];
806
+ if (entry !== void 0 && /[\\/](?:bin\.(?:js|ts)|dsh)$/.test(entry)) {
807
+ const abs = resolve(entry);
808
+ return { file: process.execPath, args: [...execArgv, abs, ...argv.slice(2)], cwd: dirname(abs), viaShell: false };
809
+ }
810
+ return { file: "dsh", args: [...argv.slice(2)], cwd: void 0, viaShell: process.platform === "win32" };
811
+ }
812
+ function scheduleRestart(launch) {
813
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-").slice(0, 19);
814
+ const logOut = `${tmpdir()}${tmpdir().endsWith("/") ? "" : "\\"}dsh-plugin-capabilities-restart-${stamp}.out.log`;
815
+ const logErr = logOut.replace(".out.log", ".err.log");
816
+ const child = spawn(launch.file, launch.args, {
817
+ cwd: launch.cwd,
818
+ stdio: ["ignore", openSync(logOut, "a"), openSync(logErr, "a")],
819
+ env: process.env,
820
+ shell: launch.viaShell,
821
+ windowsHide: true
822
+ });
823
+ child.unref();
824
+ setTimeout(() => process.kill(process.pid, "SIGTERM"), 500);
825
+ return { pid: process.pid, replacementPid: child.pid, logOut, logErr };
826
+ }
827
+ function trustedRestartRequest(request, socketAddress) {
828
+ const address = socketAddress ?? (request.socket.remoteAddress ?? "");
829
+ if (address !== "127.0.0.1" && address !== "::1" && address !== "::ffff:127.0.0.1") return false;
830
+ if (request.headers.forwarded !== void 0 || request.headers["x-forwarded-for"] !== void 0 || request.headers["x-real-ip"] !== void 0) return false;
831
+ const origin = request.headers.origin;
832
+ const host = request.headers.host;
833
+ if (origin === void 0 || host === void 0) return false;
834
+ try {
835
+ const parsed = new URL(origin);
836
+ return (parsed.protocol === "http:" || parsed.protocol === "https:") && parsed.host === host;
837
+ } catch {
838
+ return false;
839
+ }
840
+ }
841
+ function restartOwnedByShell(env = process.env) {
842
+ return env.DSH_DESKTOP === "1";
843
+ }
844
+
799
845
  // src/skills.ts
800
846
  import { existsSync as existsSync2, mkdirSync, rmSync, statSync, writeFileSync } from "node:fs";
801
847
  import { homedir as homedir3 } from "node:os";
@@ -899,7 +945,8 @@ function listMcp(profileDirPath) {
899
945
  }
900
946
  function validateMcpInput(input) {
901
947
  if (!SERVER_NAME_RE.test(input.serverName)) return "serverName must be 1-32 chars of A-Z a-z 0-9 _ -";
902
- if (input.id.includes("/") || input.id.includes("..")) return "invalid id";
948
+ const id = input.id ?? "";
949
+ if (id.includes("/") || id.includes("..")) return "invalid id";
903
950
  if (input.transport === "stdio") {
904
951
  if (input.command === void 0 || input.command.trim() === "") return "stdio transport requires a command";
905
952
  } else if (input.url === void 0 || !/^https?:\/\//.test(input.url)) {
@@ -908,10 +955,11 @@ function validateMcpInput(input) {
908
955
  return null;
909
956
  }
910
957
  function upsertMcp(profileDirPath, input) {
958
+ const inputId = input.id ?? "";
911
959
  const doc = loadPatch(profileDirPath);
912
960
  const seq = rowSeq(doc);
913
- const existing = input.id !== "" ? mcpRows(doc).find((item) => item.get("id") === input.id) : void 0;
914
- let id = input.id !== "" ? input.id : `mcp-${input.serverName}`;
961
+ const existing = inputId !== "" ? mcpRows(doc).find((item) => item.get("id") === inputId) : void 0;
962
+ let id = inputId !== "" ? inputId : `mcp-${input.serverName}`;
915
963
  if (existing === void 0) {
916
964
  const taken = new Set(
917
965
  (seq.items ?? []).map((item) => String(item.get("id") ?? "")).filter((id2) => id2 !== "")
@@ -1215,6 +1263,27 @@ function mountCapabilitiesRoutes(host, config) {
1215
1263
  sendJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
1216
1264
  }
1217
1265
  }
1266
+ }),
1267
+ host.webServer.register({
1268
+ kind: "exact",
1269
+ path: "/dsh-plugin-capabilities/restart",
1270
+ handler: (request, response) => {
1271
+ if (request.method !== "POST") {
1272
+ response.writeHead(405, { allow: "POST" });
1273
+ response.end();
1274
+ return;
1275
+ }
1276
+ if (!trustedRestartRequest(request)) {
1277
+ sendJson(response, 403, { error: "untrusted origin" });
1278
+ return;
1279
+ }
1280
+ if (restartOwnedByShell()) {
1281
+ sendJson(response, 409, { error: "restart is owned by the desktop shell" });
1282
+ return;
1283
+ }
1284
+ const { pid, replacementPid, logOut } = scheduleRestart(dshLaunch());
1285
+ sendJson(response, 200, { ok: true, pid, replacementPid, logOut });
1286
+ }
1218
1287
  })
1219
1288
  ];
1220
1289
  return () => {