@sealant/mend 0.24.2 → 0.25.0

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.
Files changed (57) hide show
  1. package/README.md +5 -6
  2. package/dist/main.js +224 -456
  3. package/man/mend-accounts.1 +1 -1
  4. package/man/mend-adopt.1 +1 -1
  5. package/man/mend-attach.1 +1 -1
  6. package/man/mend-codex.1 +1 -1
  7. package/man/mend-completions.1 +1 -1
  8. package/man/mend-connect.1 +1 -1
  9. package/man/mend-continue.1 +1 -1
  10. package/man/mend-doctor.1 +1 -1
  11. package/man/mend-dotfiles-sync.1 +1 -1
  12. package/man/mend-dotfiles.1 +1 -1
  13. package/man/mend-env-cluster.1 +1 -1
  14. package/man/mend-env-load.1 +1 -1
  15. package/man/mend-env-show.1 +1 -1
  16. package/man/mend-help.1 +1 -1
  17. package/man/mend-keys-init.1 +1 -1
  18. package/man/mend-keys-mode.1 +1 -1
  19. package/man/mend-keys-share.1 +1 -1
  20. package/man/mend-keys-show.1 +1 -1
  21. package/man/mend-login.1 +1 -1
  22. package/man/mend-logout.1 +1 -1
  23. package/man/mend-man.1 +1 -1
  24. package/man/mend-pair.1 +1 -1
  25. package/man/mend-projects.1 +1 -1
  26. package/man/mend-refresh.1 +1 -1
  27. package/man/mend-rejoin.1 +1 -1
  28. package/man/mend-resume.1 +1 -1
  29. package/man/mend-run.1 +1 -1
  30. package/man/mend-server-logs.1 +1 -1
  31. package/man/mend-server-restart.1 +3 -3
  32. package/man/mend-server-setup.1 +6 -9
  33. package/man/mend-server-start.1 +3 -3
  34. package/man/mend-server-status.1 +1 -1
  35. package/man/mend-server-stop.1 +1 -1
  36. package/man/mend-server-upgrade.1 +3 -3
  37. package/man/mend-server.1 +1 -1
  38. package/man/mend-service-add.1 +1 -1
  39. package/man/mend-service-connect.1 +1 -1
  40. package/man/mend-service-init.1 +1 -1
  41. package/man/mend-service-list.1 +1 -1
  42. package/man/mend-service-logs.1 +1 -1
  43. package/man/mend-service-restart.1 +1 -1
  44. package/man/mend-service-run.1 +1 -1
  45. package/man/mend-service-stop.1 +1 -1
  46. package/man/mend-sessions.1 +1 -1
  47. package/man/mend-shell.1 +1 -1
  48. package/man/mend-skills-push.1 +1 -1
  49. package/man/mend-skills.1 +1 -1
  50. package/man/mend-ssh-setup.1 +1 -1
  51. package/man/mend-ssh.1 +1 -1
  52. package/man/mend-stop.1 +1 -1
  53. package/man/mend-ui.1 +1 -1
  54. package/man/mend-version.1 +1 -1
  55. package/man/mend-worktrees.1 +1 -1
  56. package/man/mend.1 +1 -1
  57. package/package.json +3 -3
package/dist/main.js CHANGED
@@ -21,11 +21,11 @@ import {
21
21
 
22
22
  // src/main.ts
23
23
  import { spawn as spawn3, spawnSync as spawnSync2 } from "node:child_process";
24
- import * as fs9 from "node:fs";
24
+ import * as fs8 from "node:fs";
25
25
  import { createRequire } from "node:module";
26
26
  import * as net3 from "node:net";
27
27
  import * as os6 from "node:os";
28
- import * as path10 from "node:path";
28
+ import * as path9 from "node:path";
29
29
 
30
30
  // src/agent-share.ts
31
31
  import * as net from "node:net";
@@ -40,7 +40,7 @@ var bridgeUrlOf = (serverUrl, token, host) => {
40
40
  var retryDelayMs = (attempt2) => Math.min(3e4, 1e3 * 2 ** Math.min(Math.max(attempt2 - 1, 0), 5));
41
41
  var SSH_AGENTC_REQUEST_IDENTITIES = 11;
42
42
  var SSH_AGENTC_SIGN_REQUEST = 13;
43
- var askLocalAgent = (agentSock, payload) => new Promise((resolve5, reject) => {
43
+ var askLocalAgent = (agentSock, payload) => new Promise((resolve4, reject) => {
44
44
  const connection = net.connect(agentSock, () => {
45
45
  connection.write(payload);
46
46
  });
@@ -54,12 +54,12 @@ var askLocalAgent = (agentSock, payload) => new Promise((resolve5, reject) => {
54
54
  if (pending.length >= 4 && pending.length >= 4 + pending.readUInt32BE(0)) {
55
55
  clearTimeout(timer);
56
56
  connection.end();
57
- resolve5(pending.subarray(0, 4 + pending.readUInt32BE(0)));
57
+ resolve4(pending.subarray(0, 4 + pending.readUInt32BE(0)));
58
58
  }
59
59
  });
60
- connection.on("error", (error2) => {
60
+ connection.on("error", (error) => {
61
61
  clearTimeout(timer);
62
- reject(error2);
62
+ reject(error);
63
63
  });
64
64
  });
65
65
  var sleep = (ms, signal) => delay(ms, void 0, { signal }).then(
@@ -74,12 +74,12 @@ var shareAgent = async (options) => {
74
74
  const ws = new WebSocket(url);
75
75
  const onAbort = () => ws.close();
76
76
  signal.addEventListener("abort", onAbort, { once: true });
77
- const closed = new Promise((resolve5) => {
78
- ws.addEventListener("close", () => resolve5(), { once: true });
77
+ const closed = new Promise((resolve4) => {
78
+ ws.addEventListener("close", () => resolve4(), { once: true });
79
79
  });
80
- const opened = await new Promise((resolve5) => {
81
- ws.addEventListener("open", () => resolve5(true), { once: true });
82
- ws.addEventListener("error", () => resolve5(false), { once: true });
80
+ const opened = await new Promise((resolve4) => {
81
+ ws.addEventListener("open", () => resolve4(true), { once: true });
82
+ ws.addEventListener("error", () => resolve4(false), { once: true });
83
83
  });
84
84
  if (opened) {
85
85
  attempt2 = 0;
@@ -112,8 +112,8 @@ var shareAgent = async (options) => {
112
112
  if (type === SSH_AGENTC_SIGN_REQUEST) {
113
113
  onEvent({ kind: "signed", context, seconds: (Date.now() - started) / 1e3 });
114
114
  }
115
- } catch (error2) {
116
- const message = error2 instanceof Error ? error2.message : String(error2);
115
+ } catch (error) {
116
+ const message = error instanceof Error ? error.message : String(error);
117
117
  if (ws.readyState === WebSocket.OPEN) {
118
118
  ws.send(JSON.stringify({ t: "err", id, message }));
119
119
  }
@@ -171,13 +171,13 @@ var parsePngfHex = (output) => {
171
171
  return Buffer.from(match[1], "hex");
172
172
  };
173
173
  var MAX_BYTES = 32 * 1024 * 1024;
174
- var run = (command, args) => new Promise((resolve5) => {
174
+ var run = (command, args) => new Promise((resolve4) => {
175
175
  execFile(
176
176
  command,
177
177
  [...args],
178
178
  { encoding: "buffer", maxBuffer: MAX_BYTES, timeout: 3e3 },
179
- (error2, stdout) => {
180
- resolve5(error2 === null ? stdout : null);
179
+ (error, stdout) => {
180
+ resolve4(error === null ? stdout : null);
181
181
  }
182
182
  );
183
183
  });
@@ -1037,7 +1037,7 @@ var COMMANDS = [
1037
1037
  section: "this machine",
1038
1038
  summary: "install or repair the local Mend server",
1039
1039
  synopsis: [
1040
- "[--context <name>] [--version <version|latest>] [--bind <ip>] [--url <origin>] [--origin <origin>...] [--port <n>] [--ssh-port <n>] [--registry-port <n>] [--docker-socket <path>] [--assets-dir <dir>] [--offline]"
1040
+ "[--context <name>] [--version <version|latest>] [--bind <ip>] [--url <origin>] [--origin <origin>...] [--port <n>] [--ssh-port <n>] [--docker-socket <path>] [--assets-dir <dir>] [--offline]"
1041
1041
  ],
1042
1042
  description: [
1043
1043
  "Checks a local Unix-socket Docker context and the Compose plugin, downloads the compose and Postgres initialization assets for one Mend release, preserves existing data and secrets, and starts the server. Re-running repairs the same pinned version. A changed --version is refused; use mend server upgrade. Updating this CLI never updates an existing server pin.",
@@ -1061,18 +1061,17 @@ var COMMANDS = [
1061
1061
  text: "additional exact browser origin; repeat for more than one"
1062
1062
  },
1063
1063
  { flag: "--port <n>", text: "external web port. Default: 3105" },
1064
- { flag: "--ssh-port <n>", text: "external workspace SSH port. Default: 2222" },
1065
1064
  {
1066
- flag: "--registry-port <n>",
1067
- text: "loopback registry port. Default: 5000; all three ports must differ"
1065
+ flag: "--ssh-port <n>",
1066
+ text: "external workspace SSH port. Default: 2222; must differ from --port"
1068
1067
  },
1069
1068
  {
1070
1069
  flag: "--assets-dir <dir>",
1071
- text: "copy compose.v1.yaml and postgres-init.sh from a release directory; fresh setup requires --version"
1070
+ text: "copy compose.v2.yaml and postgres-init.sh from a release directory; fresh setup requires --version"
1072
1071
  },
1073
1072
  {
1074
1073
  flag: "--offline",
1075
- text: "use retained or supplied assets and preloaded images only; no GitHub requests or release-image pulls; exact image label, health version and a loopback registry roundtrip required"
1074
+ text: "use retained or supplied assets and preloaded images only; no GitHub requests or release-image pulls; exact image label and health version required"
1076
1075
  },
1077
1076
  { flag: "--docker-socket <path>", text: "daemon-side socket mount override for diagnostics" }
1078
1077
  ],
@@ -1083,7 +1082,7 @@ var COMMANDS = [
1083
1082
  text: "explicit tailnet exposure"
1084
1083
  },
1085
1084
  {
1086
- command: "mend server setup --version 0.23.0 --assets-dir ./release-assets --offline --registry-port 5501",
1085
+ command: "mend server setup --version 0.25.0 --assets-dir ./release-assets --offline",
1087
1086
  text: "install from local release assets and preloaded images"
1088
1087
  }
1089
1088
  ],
@@ -1105,14 +1104,9 @@ var COMMANDS = [
1105
1104
  summary: "start the selected server generation",
1106
1105
  synopsis: ["[--offline]"],
1107
1106
  description: [
1108
- "Reuses the saved context, generation, credentials and exact pin. Uses preloaded release images, with no release-image pulls or asset downloads, even without --offline. Exact image label, health version and a loopback registry roundtrip are required."
1109
- ],
1110
- options: [
1111
- {
1112
- flag: "--offline",
1113
- text: "use local release images and assets; still probe the loopback registry"
1114
- }
1107
+ "Reuses the saved context, generation, credentials and exact pin. Uses preloaded release images, with no release-image pulls or asset downloads, even without --offline. Exact image label and health version are required."
1115
1108
  ],
1109
+ options: [{ flag: "--offline", text: "use local release images and assets" }],
1116
1110
  see: ["server status", "server upgrade"]
1117
1111
  },
1118
1112
  {
@@ -1131,14 +1125,9 @@ var COMMANDS = [
1131
1125
  summary: "restart Mend using the same generation and pin",
1132
1126
  synopsis: ["[--offline]"],
1133
1127
  description: [
1134
- "Checks preloaded images before stopping Mend, then starts the saved generation and verifies exact-version health and a loopback registry roundtrip. Postgres stays running. Connections are interrupted; workspace containers and data remain, but active work may need reconnection."
1135
- ],
1136
- options: [
1137
- {
1138
- flag: "--offline",
1139
- text: "use local release images and assets; still probe the loopback registry"
1140
- }
1128
+ "Checks preloaded images before stopping Mend, then starts the saved generation and verifies exact-version health. Postgres stays running. Connections are interrupted; workspace containers and data remain, but active work may need reconnection."
1141
1129
  ],
1130
+ options: [{ flag: "--offline", text: "use local release images and assets" }],
1142
1131
  see: ["server status", "server logs"]
1143
1132
  },
1144
1133
  {
@@ -1169,11 +1158,11 @@ var COMMANDS = [
1169
1158
  },
1170
1159
  {
1171
1160
  flag: "--assets-dir <dir>",
1172
- text: "copy target compose.v1.yaml and postgres-init.sh from local release assets"
1161
+ text: "copy target compose.v2.yaml and postgres-init.sh from local release assets"
1173
1162
  },
1174
1163
  {
1175
1164
  flag: "--offline",
1176
- text: "no GitHub requests or release-image pulls; target assets and exact-version images must be local; still probe the loopback registry"
1165
+ text: "no GitHub requests or release-image pulls; target assets and exact-version images must be local"
1177
1166
  }
1178
1167
  ],
1179
1168
  examples: [
@@ -1677,7 +1666,7 @@ var retryAfterSecondsOf = (json) => {
1677
1666
  }
1678
1667
  return 5;
1679
1668
  };
1680
- var sleep2 = (ms) => new Promise((resolve5) => setTimeout(resolve5, ms));
1669
+ var sleep2 = (ms) => new Promise((resolve4) => setTimeout(resolve4, ms));
1681
1670
  var resolveServerUrl = async (args, deps) => {
1682
1671
  const given = takeFlagValue2(args, "--url") ?? deps.configuredUrl;
1683
1672
  if (given !== null) {
@@ -1750,7 +1739,7 @@ import { randomBytes } from "node:crypto";
1750
1739
  import { readFile } from "node:fs/promises";
1751
1740
  import * as net2 from "node:net";
1752
1741
  import * as os3 from "node:os";
1753
- import * as path6 from "node:path";
1742
+ import * as path5 from "node:path";
1754
1743
 
1755
1744
  // src/server-docker-volumes.ts
1756
1745
  import { createHash } from "node:crypto";
@@ -1955,223 +1944,10 @@ var ownership = async (runtime, input, mode) => {
1955
1944
  var claimServerDockerVolumes = (runtime, input) => ownership(runtime, input, "claim");
1956
1945
  var verifyServerDockerVolumes = (runtime, input) => ownership(runtime, input, "verify");
1957
1946
 
1958
- // src/server-registry-probe.ts
1959
- import { Buffer as Buffer2 } from "node:buffer";
1960
- import * as fs3 from "node:fs/promises";
1961
- import * as path3 from "node:path";
1962
- var ServerRegistryProbeError = class extends Error {
1963
- /** Stable error discriminator. */
1964
- _tag = "ServerRegistryProbeError";
1965
- /** Failed roundtrip operation, independent of the host operating system. */
1966
- stage;
1967
- /** Bounded, credential-scrubbed failure detail. */
1968
- diagnostic;
1969
- /** Explain the failed Engine operation without recommending a wider registry binding. */
1970
- constructor(stage, diagnostic) {
1971
- super(
1972
- `Docker registry probe failed (${stage}): ${diagnostic}. The selected Docker Engine must import, push and pull through 127.0.0.1 at the configured registry port. Check bundle health, Docker context and Engine loopback registry support, then retry. Do not widen the registry binding.`
1973
- );
1974
- this.stage = stage;
1975
- this.diagnostic = diagnostic;
1976
- }
1977
- };
1978
- var PROBE_LABEL = "dev.sealant.mend.registry-probe";
1979
- var COMMAND_TIMEOUT_MS = 6e4;
1980
- var CLEANUP_TIMEOUT_MS = 15e3;
1981
- var error = (stage, diagnostic) => ({ _tag: "error", error: new ServerRegistryProbeError(stage, diagnostic) });
1982
- var safeDiagnostic = (text) => text.replace(/([a-z][a-z0-9+.-]*:\/\/)[^\s/@]+@/gi, "$1[redacted]@").replace(
1983
- /((?:password|passwd|token|secret|authorization|credential)[\w-]*\s*[=:]\s*)[^\s,;]+/gi,
1984
- "$1[redacted]"
1985
- ).replace(/Bearer\s+\S+/gi, "Bearer [redacted]").split("").map(
1986
- (character) => character.charCodeAt(0) < 32 || character.charCodeAt(0) === 127 ? " " : character
1987
- ).join("").slice(0, 2048);
1988
- var docker2 = async (runtime, context, stage, args) => {
1989
- try {
1990
- const output = await runtime.run("docker", ["--context", context, ...args], {
1991
- timeoutMs: stage === "cleanup" ? CLEANUP_TIMEOUT_MS : COMMAND_TIMEOUT_MS
1992
- });
1993
- if (output.status !== 0 || output.error !== void 0)
1994
- return error(
1995
- stage,
1996
- safeDiagnostic(
1997
- output.error ?? (output.stderr.trim() || `exit ${output.status ?? "unknown"}`)
1998
- )
1999
- );
2000
- return { _tag: "ok", value: output.stdout };
2001
- } catch {
2002
- return error(
2003
- stage,
2004
- "Process runner rejected; check Docker access and command timeout handling"
2005
- );
2006
- }
2007
- };
2008
- var tinyRootfs = (nonce) => {
2009
- const body = Buffer2.from(`Mend registry connectivity probe ${nonce}
2010
- `);
2011
- const header = Buffer2.alloc(512);
2012
- const field = (value, offset, length) => header.write(value, offset, length, "ascii");
2013
- field("mend-registry-probe", 0, 100);
2014
- field("0000600\0", 100, 8);
2015
- field("0000000\0", 108, 8);
2016
- field("0000000\0", 116, 8);
2017
- field(`${body.length.toString(8).padStart(11, "0")}\0`, 124, 12);
2018
- field("00000000000\0", 136, 12);
2019
- field(" ", 148, 8);
2020
- field("0", 156, 1);
2021
- field("ustar\0", 257, 6);
2022
- field("00", 263, 2);
2023
- const checksum = header.reduce((sum, byte) => sum + byte, 0);
2024
- field(`${checksum.toString(8).padStart(6, "0")}\0 `, 148, 8);
2025
- return Buffer2.concat([header, body, Buffer2.alloc(512 - body.length), Buffer2.alloc(1024)]);
2026
- };
2027
- var imageExists = async (runtime, input, reference, stage) => {
2028
- const listed = await docker2(runtime, input.dockerContext, stage, [
2029
- "image",
2030
- "ls",
2031
- "--all",
2032
- "--filter",
2033
- `reference=${reference}`,
2034
- "--format",
2035
- "{{json .ID}}"
2036
- ]);
2037
- if (listed._tag === "error") return listed;
2038
- try {
2039
- const lines = listed.value.trim().split("\n").filter(Boolean);
2040
- for (const line of lines) {
2041
- const id = JSON.parse(line);
2042
- if (typeof id !== "string" || !/^(?:sha256:)?[a-f0-9]{12,64}$/.test(id))
2043
- return error(stage, "Docker returned an inconclusive image listing");
2044
- }
2045
- return { _tag: "ok", value: lines.length > 0 };
2046
- } catch {
2047
- return error(stage, "Docker returned an invalid image listing");
2048
- }
2049
- };
2050
- var isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2051
- var ownedImage = async (runtime, input, reference, stage) => {
2052
- const inspected = await docker2(runtime, input.dockerContext, stage, [
2053
- "image",
2054
- "inspect",
2055
- reference,
2056
- "--format",
2057
- "{{json .}}"
2058
- ]);
2059
- if (inspected._tag === "error") return inspected;
2060
- try {
2061
- const image = JSON.parse(inspected.value);
2062
- if (!isRecord(image) || typeof image.Id !== "string" || !/^sha256:[a-f0-9]{64}$/.test(image.Id) || !isRecord(image.Config) || !isRecord(image.Config.Labels) || image.Config.Labels[PROBE_LABEL] !== input.nonce)
2063
- return error(stage, "Image ownership or ID could not be verified; the image was retained");
2064
- return { _tag: "ok", value: image.Id };
2065
- } catch {
2066
- return error(stage, "Docker returned an invalid image inspection; the image was retained");
2067
- }
2068
- };
2069
- var removeOwnedTag = async (runtime, input, reference, stage) => {
2070
- const exists = await imageExists(runtime, input, reference, stage);
2071
- if (exists._tag === "error") return exists;
2072
- if (!exists.value) return { _tag: "ok", value: void 0 };
2073
- const owned = await ownedImage(runtime, input, reference, stage);
2074
- if (owned._tag === "error") return owned;
2075
- const removed = await docker2(runtime, input.dockerContext, stage, ["image", "rm", reference]);
2076
- return removed._tag === "error" ? removed : { _tag: "ok", value: void 0 };
2077
- };
2078
- var roundtrip = async (runtime, input, reference, tarPath) => {
2079
- const imported = await docker2(runtime, input.dockerContext, "import", [
2080
- "image",
2081
- "import",
2082
- "--change",
2083
- `LABEL ${PROBE_LABEL}=${input.nonce}`,
2084
- tarPath,
2085
- reference
2086
- ]);
2087
- if (imported._tag === "error") return imported;
2088
- const original = await ownedImage(runtime, input, reference, "inspect");
2089
- if (original._tag === "error") return original;
2090
- const pushed = await docker2(runtime, input.dockerContext, "push", ["image", "push", reference]);
2091
- if (pushed._tag === "error") return pushed;
2092
- const removed = await removeOwnedTag(runtime, input, reference, "remove-local");
2093
- if (removed._tag === "error") return removed;
2094
- const absent = await imageExists(runtime, input, reference, "remove-local");
2095
- if (absent._tag === "error") return absent;
2096
- if (absent.value)
2097
- return error("remove-local", "Probe tag remained local; refusing to claim a pull roundtrip");
2098
- const pulled = await docker2(runtime, input.dockerContext, "pull", ["image", "pull", reference]);
2099
- if (pulled._tag === "error") return pulled;
2100
- const observed = await ownedImage(runtime, input, reference, "inspect");
2101
- if (observed._tag === "error") return observed;
2102
- return observed.value === original.value ? observed : error("inspect", "Pulled image ID differs from the imported image ID");
2103
- };
2104
- var parseProbeReference = (input) => {
2105
- if (!input.dockerContext.trim() || !Number.isInteger(input.registryPort) || input.registryPort < 1 || input.registryPort > 65535 || !/^[a-f0-9]{32,64}$/.test(input.nonce) || !path3.isAbsolute(input.temporaryDirectory))
2106
- return error(
2107
- "input",
2108
- "Expected an explicit context, TCP port, fresh 16 to 32 byte hex nonce and absolute temporary directory"
2109
- );
2110
- return {
2111
- _tag: "ok",
2112
- value: `127.0.0.1:${input.registryPort}/mend-registry-probe/${input.nonce}:probe`
2113
- };
2114
- };
2115
- var probeServerRegistry = async (runtime, input) => {
2116
- const cleanupWarnings = [];
2117
- const parsed = parseProbeReference(input);
2118
- if (parsed._tag === "error") return { ...parsed, cleanupWarnings };
2119
- const reference = parsed.value;
2120
- const exists = await imageExists(runtime, input, reference, "collision-check");
2121
- if (exists._tag === "error") return { ...exists, cleanupWarnings };
2122
- if (exists.value)
2123
- return {
2124
- ...error(
2125
- "collision-check",
2126
- "Probe reference already exists; supply a fresh nonce. Existing image retained"
2127
- ),
2128
- cleanupWarnings
2129
- };
2130
- let directory;
2131
- try {
2132
- directory = await fs3.mkdtemp(path3.join(input.temporaryDirectory, "mend-registry-probe-"));
2133
- } catch {
2134
- return {
2135
- ...error("temporary-files", "Could not create private probe directory"),
2136
- cleanupWarnings
2137
- };
2138
- }
2139
- let result;
2140
- let importAttempted = false;
2141
- try {
2142
- const tarPath = path3.join(directory, "rootfs.tar");
2143
- await fs3.writeFile(tarPath, tinyRootfs(input.nonce), { mode: 384, flag: "wx" });
2144
- importAttempted = true;
2145
- result = await roundtrip(runtime, input, reference, tarPath);
2146
- } catch {
2147
- result = error("temporary-files", "Could not write the private probe archive");
2148
- } finally {
2149
- if (importAttempted) {
2150
- const cleanup = await removeOwnedTag(runtime, input, reference, "cleanup");
2151
- if (cleanup._tag === "error") cleanupWarnings.push(cleanup.error);
2152
- }
2153
- try {
2154
- await fs3.rm(directory, { recursive: true, force: true });
2155
- } catch {
2156
- cleanupWarnings.push(
2157
- new ServerRegistryProbeError(
2158
- "cleanup",
2159
- `Could not remove private probe directory ${directory}`
2160
- )
2161
- );
2162
- }
2163
- }
2164
- return result._tag === "error" ? { ...result, cleanupWarnings } : {
2165
- _tag: "ok",
2166
- value: { reference, imageId: result.value },
2167
- cleanupWarnings
2168
- };
2169
- };
2170
-
2171
1947
  // src/server-runtime.ts
2172
1948
  import { spawn as spawn2 } from "node:child_process";
2173
- import * as fs4 from "node:fs";
2174
- import * as path4 from "node:path";
1949
+ import * as fs3 from "node:fs";
1950
+ import * as path3 from "node:path";
2175
1951
  var serverProcessDeadlines = {
2176
1952
  ordinary: 3e4,
2177
1953
  pull: 10 * 6e4,
@@ -2214,10 +1990,10 @@ var runServerProcess = (command, args, parentEnvironment, options = {}) => {
2214
1990
  ]) {
2215
1991
  if (parentEnvironment[key] !== void 0) env[key] = parentEnvironment[key];
2216
1992
  }
2217
- return new Promise((resolve5) => {
1993
+ return new Promise((resolve4) => {
2218
1994
  let fd;
2219
1995
  try {
2220
- if (options.stdoutFile !== void 0) fd = fs4.openSync(options.stdoutFile, "wx", 384);
1996
+ if (options.stdoutFile !== void 0) fd = fs3.openSync(options.stdoutFile, "wx", 384);
2221
1997
  const child = spawn2(command, [...args], {
2222
1998
  env,
2223
1999
  detached: process.platform !== "win32",
@@ -2262,23 +2038,23 @@ var runServerProcess = (command, args, parentEnvironment, options = {}) => {
2262
2038
  failure2 ??= `Process timed out after ${timeoutMs}ms`;
2263
2039
  terminate();
2264
2040
  }, timeoutMs);
2265
- child.once("error", (error2) => {
2266
- failure2 ??= error2.message;
2041
+ child.once("error", (error) => {
2042
+ failure2 ??= error.message;
2267
2043
  });
2268
2044
  child.once("close", (status) => {
2269
2045
  globalThis.clearTimeout(timer);
2270
2046
  try {
2271
- if (fd !== void 0) fs4.fsyncSync(fd);
2047
+ if (fd !== void 0) fs3.fsyncSync(fd);
2272
2048
  } catch {
2273
2049
  failure2 ??= "Could not durably write process output.";
2274
2050
  } finally {
2275
2051
  try {
2276
- if (fd !== void 0) fs4.closeSync(fd);
2052
+ if (fd !== void 0) fs3.closeSync(fd);
2277
2053
  } catch {
2278
2054
  failure2 ??= "Could not close process output file.";
2279
2055
  }
2280
2056
  }
2281
- resolve5({
2057
+ resolve4({
2282
2058
  status: failure2 === void 0 ? status : null,
2283
2059
  stdout,
2284
2060
  stderr,
@@ -2287,10 +2063,10 @@ var runServerProcess = (command, args, parentEnvironment, options = {}) => {
2287
2063
  });
2288
2064
  } catch (cause) {
2289
2065
  try {
2290
- if (fd !== void 0) fs4.closeSync(fd);
2066
+ if (fd !== void 0) fs3.closeSync(fd);
2291
2067
  } catch {
2292
2068
  }
2293
- resolve5({
2069
+ resolve4({
2294
2070
  status: null,
2295
2071
  stdout: "",
2296
2072
  stderr: "",
@@ -2308,17 +2084,17 @@ var serverComposeArgs = (installation, command) => [
2308
2084
  "--project-directory",
2309
2085
  installation.directory,
2310
2086
  "--env-file",
2311
- path4.join(installation.directory, "server.env"),
2087
+ path3.join(installation.directory, "server.env"),
2312
2088
  "-f",
2313
- path4.join(installation.directory, "compose.yaml"),
2089
+ path3.join(installation.directory, "compose.yaml"),
2314
2090
  ...command
2315
2091
  ];
2316
2092
 
2317
2093
  // src/server-store.ts
2318
2094
  import { randomUUID } from "node:crypto";
2319
- import * as fs5 from "node:fs";
2095
+ import * as fs4 from "node:fs";
2320
2096
  import * as os2 from "node:os";
2321
- import * as path5 from "node:path";
2097
+ import * as path4 from "node:path";
2322
2098
  var ServerStoreError = class extends Error {
2323
2099
  _tag = "ServerStoreError";
2324
2100
  };
@@ -2339,28 +2115,28 @@ var attempt = (operation) => {
2339
2115
  var hasCode = (cause, code) => cause instanceof Error && "code" in cause && cause.code === code;
2340
2116
  var readOptional = (file) => {
2341
2117
  try {
2342
- return fs5.readFileSync(file, "utf8");
2118
+ return fs4.readFileSync(file, "utf8");
2343
2119
  } catch (cause) {
2344
2120
  if (hasCode(cause, "ENOENT")) return null;
2345
2121
  throw cause;
2346
2122
  }
2347
2123
  };
2348
2124
  var syncDirectory = (directory) => {
2349
- const fd = fs5.openSync(directory, "r");
2125
+ const fd = fs4.openSync(directory, "r");
2350
2126
  try {
2351
- fs5.fsyncSync(fd);
2127
+ fs4.fsyncSync(fd);
2352
2128
  } finally {
2353
- fs5.closeSync(fd);
2129
+ fs4.closeSync(fd);
2354
2130
  }
2355
2131
  };
2356
2132
  var writeDurable = (file, content, mode = 384) => {
2357
- const fd = fs5.openSync(file, "wx", mode);
2133
+ const fd = fs4.openSync(file, "wx", mode);
2358
2134
  try {
2359
- fs5.writeFileSync(fd, content, "utf8");
2360
- fs5.fchmodSync(fd, mode);
2361
- fs5.fsyncSync(fd);
2135
+ fs4.writeFileSync(fd, content, "utf8");
2136
+ fs4.fchmodSync(fd, mode);
2137
+ fs4.fsyncSync(fd);
2362
2138
  } finally {
2363
- fs5.closeSync(fd);
2139
+ fs4.closeSync(fd);
2364
2140
  }
2365
2141
  };
2366
2142
  var fileKeys = ["identity", "config", "env", "compose", "postgresInit"];
@@ -2380,7 +2156,7 @@ var parseLockOwner = (raw) => {
2380
2156
  };
2381
2157
  var describeOwner = (lockDir) => {
2382
2158
  try {
2383
- const raw = JSON.parse(fs5.readFileSync(path5.join(lockDir, "owner.json"), "utf8"));
2159
+ const raw = JSON.parse(fs4.readFileSync(path4.join(lockDir, "owner.json"), "utf8"));
2384
2160
  const owner = parseLockOwner(raw);
2385
2161
  if (owner === null) return "owner metadata is invalid";
2386
2162
  const label = `PID ${owner.pid} on ${owner.hostname}`;
@@ -2400,28 +2176,28 @@ var lockGuidance = (lockDir) => new ServerStoreError(
2400
2176
  );
2401
2177
  var acquireLock = (configDir, create) => {
2402
2178
  if (create) {
2403
- fs5.mkdirSync(configDir, { recursive: true, mode: 448 });
2404
- fs5.chmodSync(configDir, 448);
2405
- } else if (!fs5.existsSync(configDir)) {
2179
+ fs4.mkdirSync(configDir, { recursive: true, mode: 448 });
2180
+ fs4.chmodSync(configDir, 448);
2181
+ } else if (!fs4.existsSync(configDir)) {
2406
2182
  throw new ServerStoreError(
2407
2183
  "No Mend server is configured. Run mend server setup explicitly to install one."
2408
2184
  );
2409
2185
  }
2410
- const lockDir = path5.join(configDir, "server.lock");
2186
+ const lockDir = path4.join(configDir, "server.lock");
2411
2187
  try {
2412
- fs5.mkdirSync(lockDir, { mode: 448 });
2188
+ fs4.mkdirSync(lockDir, { mode: 448 });
2413
2189
  } catch (cause) {
2414
2190
  if (hasCode(cause, "EEXIST")) throw lockGuidance(lockDir);
2415
2191
  throw cause;
2416
2192
  }
2417
- const owned = fs5.statSync(lockDir);
2418
- const ownerFile = path5.join(lockDir, "owner.json");
2193
+ const owned = fs4.statSync(lockDir);
2194
+ const ownerFile = path4.join(lockDir, "owner.json");
2419
2195
  const owner = `${JSON.stringify({ pid: process.pid, hostname: os2.hostname(), token: randomUUID() })}
2420
2196
  `;
2421
2197
  let open = true;
2422
2198
  const assertOwned = () => {
2423
- const current = fs5.statSync(lockDir);
2424
- if (!open || current.dev !== owned.dev || current.ino !== owned.ino || fs5.readFileSync(ownerFile, "utf8") !== owner) {
2199
+ const current = fs4.statSync(lockDir);
2200
+ if (!open || current.dev !== owned.dev || current.ino !== owned.ino || fs4.readFileSync(ownerFile, "utf8") !== owner) {
2425
2201
  throw new ServerStoreError(
2426
2202
  "Server lock ownership was lost. Stop and check for another server command before retrying."
2427
2203
  );
@@ -2437,8 +2213,8 @@ var acquireLock = (configDir, create) => {
2437
2213
  release: () => {
2438
2214
  const result = attempt(() => {
2439
2215
  assertOwned();
2440
- fs5.unlinkSync(ownerFile);
2441
- fs5.rmdirSync(lockDir);
2216
+ fs4.unlinkSync(ownerFile);
2217
+ fs4.rmdirSync(lockDir);
2442
2218
  });
2443
2219
  open = false;
2444
2220
  return result;
@@ -2447,7 +2223,7 @@ var acquireLock = (configDir, create) => {
2447
2223
  };
2448
2224
  var readIdentity = (paths) => {
2449
2225
  const identity = readOptional(paths.identity);
2450
- if (identity === null && (fs5.existsSync(paths.active) || fs5.existsSync(paths.generations) && fs5.readdirSync(paths.generations).length > 0)) {
2226
+ if (identity === null && (fs4.existsSync(paths.active) || fs4.existsSync(paths.generations) && fs4.readdirSync(paths.generations).length > 0)) {
2451
2227
  throw new ServerStoreError(
2452
2228
  "Server identity.env is missing. Restore it from a retained generation or backup; refusing to generate replacement credentials."
2453
2229
  );
@@ -2457,7 +2233,7 @@ var readIdentity = (paths) => {
2457
2233
  var readActive = (paths) => {
2458
2234
  let target;
2459
2235
  try {
2460
- target = fs5.readlinkSync(paths.active);
2236
+ target = fs4.readlinkSync(paths.active);
2461
2237
  } catch (cause) {
2462
2238
  if (hasCode(cause, "ENOENT")) return null;
2463
2239
  throw cause;
@@ -2467,13 +2243,13 @@ var readActive = (paths) => {
2467
2243
  "Server active pointer is invalid. Restore the symlink to a retained generation before retrying."
2468
2244
  );
2469
2245
  }
2470
- const directory = path5.join(paths.configDir, target);
2246
+ const directory = path4.join(paths.configDir, target);
2471
2247
  const files = {
2472
- identity: fs5.readFileSync(path5.join(directory, fileNames.identity), "utf8"),
2473
- config: fs5.readFileSync(path5.join(directory, fileNames.config), "utf8"),
2474
- env: fs5.readFileSync(path5.join(directory, fileNames.env), "utf8"),
2475
- compose: fs5.readFileSync(path5.join(directory, fileNames.compose), "utf8"),
2476
- postgresInit: fs5.readFileSync(path5.join(directory, fileNames.postgresInit), "utf8")
2248
+ identity: fs4.readFileSync(path4.join(directory, fileNames.identity), "utf8"),
2249
+ config: fs4.readFileSync(path4.join(directory, fileNames.config), "utf8"),
2250
+ env: fs4.readFileSync(path4.join(directory, fileNames.env), "utf8"),
2251
+ compose: fs4.readFileSync(path4.join(directory, fileNames.compose), "utf8"),
2252
+ postgresInit: fs4.readFileSync(path4.join(directory, fileNames.postgresInit), "utf8")
2477
2253
  };
2478
2254
  if (readIdentity(paths) !== files.identity) {
2479
2255
  throw new ServerStoreError(
@@ -2483,20 +2259,20 @@ var readActive = (paths) => {
2483
2259
  return { directory, files };
2484
2260
  };
2485
2261
  var publishIdentity = (paths, identity) => {
2486
- const temporary = path5.join(paths.configDir, `.identity-${randomUUID()}`);
2262
+ const temporary = path4.join(paths.configDir, `.identity-${randomUUID()}`);
2487
2263
  writeDurable(temporary, identity);
2488
- fs5.linkSync(temporary, paths.identity);
2264
+ fs4.linkSync(temporary, paths.identity);
2489
2265
  syncDirectory(paths.configDir);
2490
- fs5.unlinkSync(temporary);
2266
+ fs4.unlinkSync(temporary);
2491
2267
  };
2492
2268
  var prepareGeneration = (paths, files) => {
2493
- fs5.mkdirSync(paths.generations, { recursive: true, mode: 448 });
2269
+ fs4.mkdirSync(paths.generations, { recursive: true, mode: 448 });
2494
2270
  const generationName = `gen-${randomUUID()}`;
2495
- const directory = path5.join(paths.generations, generationName);
2496
- fs5.mkdirSync(directory, { mode: 448 });
2271
+ const directory = path4.join(paths.generations, generationName);
2272
+ fs4.mkdirSync(directory, { mode: 448 });
2497
2273
  for (const key of fileKeys) {
2498
2274
  writeDurable(
2499
- path5.join(directory, fileNames[key]),
2275
+ path4.join(directory, fileNames[key]),
2500
2276
  files[key],
2501
2277
  key === "postgresInit" ? 493 : 384
2502
2278
  );
@@ -2507,18 +2283,18 @@ var prepareGeneration = (paths, files) => {
2507
2283
  return { directory, files };
2508
2284
  };
2509
2285
  var activateGeneration = (paths, generation) => {
2510
- const relative3 = path5.relative(paths.configDir, generation.directory);
2511
- if (!/^generations\/gen-[0-9a-f-]{36}$/.test(relative3) || readIdentity(paths) !== fs5.readFileSync(path5.join(generation.directory, "identity.env"), "utf8")) {
2286
+ const relative3 = path4.relative(paths.configDir, generation.directory);
2287
+ if (!/^generations\/gen-[0-9a-f-]{36}$/.test(relative3) || readIdentity(paths) !== fs4.readFileSync(path4.join(generation.directory, "identity.env"), "utf8")) {
2512
2288
  throw new ServerStoreError("Cannot activate a generation outside this installation identity.");
2513
2289
  }
2514
2290
  for (const key of fileKeys) {
2515
- if (fs5.readFileSync(path5.join(generation.directory, fileNames[key]), "utf8") !== generation.files[key]) {
2291
+ if (fs4.readFileSync(path4.join(generation.directory, fileNames[key]), "utf8") !== generation.files[key]) {
2516
2292
  throw new ServerStoreError("Cannot activate an incomplete or changed server generation.");
2517
2293
  }
2518
2294
  }
2519
- const pointer = path5.join(paths.configDir, `.active-${randomUUID()}`);
2520
- fs5.symlinkSync(relative3, pointer);
2521
- fs5.renameSync(pointer, paths.active);
2295
+ const pointer = path4.join(paths.configDir, `.active-${randomUUID()}`);
2296
+ fs4.symlinkSync(relative3, pointer);
2297
+ fs4.renameSync(pointer, paths.active);
2522
2298
  syncDirectory(paths.configDir);
2523
2299
  };
2524
2300
  var prepareFiles = (paths, files) => {
@@ -2527,7 +2303,7 @@ var prepareFiles = (paths, files) => {
2527
2303
  throw new ServerStoreError("Server identity already exists; refusing to replace credentials.");
2528
2304
  }
2529
2305
  const active = readActive(paths);
2530
- if (active !== null && fileKeys.every((key) => files[key] === active.files[key]) && (fs5.statSync(path5.join(active.directory, fileNames.postgresInit)).mode & 4095) === 493)
2306
+ if (active !== null && fileKeys.every((key) => files[key] === active.files[key]) && (fs4.statSync(path4.join(active.directory, fileNames.postgresInit)).mode & 4095) === 493)
2531
2307
  return active;
2532
2308
  if (identity === null) publishIdentity(paths, files.identity);
2533
2309
  return prepareGeneration(paths, files);
@@ -2540,9 +2316,9 @@ var commitGeneration = (paths, files) => {
2540
2316
  var createStore = (configDir, lock) => {
2541
2317
  const paths = {
2542
2318
  configDir,
2543
- identity: path5.join(configDir, "identity.env"),
2544
- generations: path5.join(configDir, "generations"),
2545
- active: path5.join(configDir, "active")
2319
+ identity: path4.join(configDir, "identity.env"),
2320
+ generations: path4.join(configDir, "generations"),
2321
+ active: path4.join(configDir, "active")
2546
2322
  };
2547
2323
  const whileOwned = (operation) => attempt(() => {
2548
2324
  lock.assertOwned();
@@ -2555,13 +2331,13 @@ var createStore = (configDir, lock) => {
2555
2331
  prepare: (files) => whileOwned(() => prepareFiles(paths, files)),
2556
2332
  activate: (generation) => whileOwned(() => activateGeneration(paths, generation)),
2557
2333
  createBackup: (previous, target) => whileOwned(() => {
2558
- const backups = path5.join(configDir, "backups");
2559
- fs5.mkdirSync(backups, { recursive: true, mode: 448 });
2560
- fs5.chmodSync(backups, 448);
2561
- const directory = path5.join(backups, `upgrade-${randomUUID()}`);
2562
- fs5.mkdirSync(directory, { mode: 448 });
2334
+ const backups = path4.join(configDir, "backups");
2335
+ fs4.mkdirSync(backups, { recursive: true, mode: 448 });
2336
+ fs4.chmodSync(backups, 448);
2337
+ const directory = path4.join(backups, `upgrade-${randomUUID()}`);
2338
+ fs4.mkdirSync(directory, { mode: 448 });
2563
2339
  writeDurable(
2564
- path5.join(directory, "recovery.json"),
2340
+ path4.join(directory, "recovery.json"),
2565
2341
  `${JSON.stringify(
2566
2342
  {
2567
2343
  previousGeneration: previous.directory,
@@ -2577,21 +2353,21 @@ var createStore = (configDir, lock) => {
2577
2353
  syncDirectory(directory);
2578
2354
  syncDirectory(backups);
2579
2355
  syncDirectory(configDir);
2580
- const partialFile = path5.join(directory, "database.sql.partial");
2356
+ const partialFile = path4.join(directory, "database.sql.partial");
2581
2357
  return {
2582
2358
  directory,
2583
2359
  partialFile,
2584
2360
  complete: () => whileOwned(() => {
2585
- if (fs5.statSync(partialFile).size === 0)
2361
+ if (fs4.statSync(partialFile).size === 0)
2586
2362
  throw new ServerStoreError("Database backup is empty; refusing target startup.");
2587
- const fd = fs5.openSync(partialFile, "r");
2363
+ const fd = fs4.openSync(partialFile, "r");
2588
2364
  try {
2589
- fs5.fchmodSync(fd, 384);
2590
- fs5.fsyncSync(fd);
2365
+ fs4.fchmodSync(fd, 384);
2366
+ fs4.fsyncSync(fd);
2591
2367
  } finally {
2592
- fs5.closeSync(fd);
2368
+ fs4.closeSync(fd);
2593
2369
  }
2594
- fs5.renameSync(partialFile, path5.join(directory, "database.sql"));
2370
+ fs4.renameSync(partialFile, path4.join(directory, "database.sql"));
2595
2371
  syncDirectory(directory);
2596
2372
  })
2597
2373
  };
@@ -2600,7 +2376,7 @@ var createStore = (configDir, lock) => {
2600
2376
  };
2601
2377
  var refuseFlatLayout = (configDir) => {
2602
2378
  if (["server.json", "server.env", "compose.yaml", "postgres-init.sh"].some(
2603
- (name) => fs5.existsSync(path5.join(configDir, name))
2379
+ (name) => fs4.existsSync(path4.join(configDir, name))
2604
2380
  )) {
2605
2381
  throw new ServerStoreError(
2606
2382
  "Unreleased flat server configuration found. Preserve its credentials and volumes; migrate it to the generation layout before retrying. Setup will not replace this identity."
@@ -2608,7 +2384,7 @@ var refuseFlatLayout = (configDir) => {
2608
2384
  }
2609
2385
  };
2610
2386
  var withServerStore = async (directory, operation, options = {}) => {
2611
- const configDir = path5.resolve(directory);
2387
+ const configDir = path4.resolve(directory);
2612
2388
  let lock;
2613
2389
  let result;
2614
2390
  try {
@@ -2625,9 +2401,9 @@ var withServerStore = async (directory, operation, options = {}) => {
2625
2401
  };
2626
2402
 
2627
2403
  // src/version.ts
2628
- import * as fs6 from "node:fs";
2404
+ import * as fs5 from "node:fs";
2629
2405
  var cliVersion = () => {
2630
- const raw = fs6.readFileSync(new URL("../package.json", import.meta.url), "utf8");
2406
+ const raw = fs5.readFileSync(new URL("../package.json", import.meta.url), "utf8");
2631
2407
  const parsed = JSON.parse(raw);
2632
2408
  if (typeof parsed === "object" && parsed !== null && "version" in parsed) {
2633
2409
  const version = parsed.version;
@@ -2661,12 +2437,13 @@ var versionLines = (cli, server) => {
2661
2437
 
2662
2438
  // src/server-setup.ts
2663
2439
  var CONFIG_SCHEMA_VERSION = 1;
2664
- var ASSET_CONTRACT = "mend-docker-v1";
2440
+ var ASSET_CONTRACT = "mend-docker-v2";
2441
+ var LEGACY_ASSET_CONTRACTS = /* @__PURE__ */ new Set(["mend-docker-v1"]);
2665
2442
  var MINIMUM_DOCKER_API = "1.45";
2666
2443
  var DEFAULT_APP_PORT = 3105;
2667
2444
  var DEFAULT_SSH_PORT = 2222;
2668
2445
  var DEFAULT_BIND = "127.0.0.1";
2669
- var COMPOSE_ASSET = "compose.v1.yaml";
2446
+ var COMPOSE_ASSET = "compose.v2.yaml";
2670
2447
  var POSTGRES_INIT_ASSET = "postgres-init.sh";
2671
2448
  var RELEASE_BASE = "https://github.com/sealant-sh/Mend/releases/download";
2672
2449
  var LATEST_RELEASE_URL = "https://api.github.com/repos/sealant-sh/Mend/releases/latest";
@@ -2726,7 +2503,6 @@ var SETUP_FLAGS = /* @__PURE__ */ new Set([
2726
2503
  "--port",
2727
2504
  "--ssh-port",
2728
2505
  "--docker-socket",
2729
- "--registry-port",
2730
2506
  "--assets-dir",
2731
2507
  "--offline"
2732
2508
  ]);
@@ -2755,7 +2531,6 @@ var parseSetupOptions = (args) => {
2755
2531
  const version = flagValue2("--version");
2756
2532
  const appPort = flagValue2("--port");
2757
2533
  const sshPort = flagValue2("--ssh-port");
2758
- const registryPort = flagValue2("--registry-port");
2759
2534
  const origins = values.get("--origin");
2760
2535
  return {
2761
2536
  context: flagValue2("--context"),
@@ -2766,7 +2541,6 @@ var parseSetupOptions = (args) => {
2766
2541
  appPort: appPort === void 0 ? void 0 : parsePort(appPort, "--port"),
2767
2542
  sshPort: sshPort === void 0 ? void 0 : parsePort(sshPort, "--ssh-port"),
2768
2543
  dockerSocket: flagValue2("--docker-socket"),
2769
- registryPort: registryPort === void 0 ? void 0 : parsePort(registryPort, "--registry-port"),
2770
2544
  assetsDir: flagValue2("--assets-dir"),
2771
2545
  offline: values.has("--offline")
2772
2546
  };
@@ -2813,9 +2587,8 @@ var checkExposurePair = (bind, appUrl, requireExplicitUrl) => {
2813
2587
  var validateExposure = (existing, options) => {
2814
2588
  const appPort = options.appPort ?? existing?.appPort ?? DEFAULT_APP_PORT;
2815
2589
  const sshPort = options.sshPort ?? existing?.sshPort ?? DEFAULT_SSH_PORT;
2816
- const registryPort = options.registryPort ?? existing?.registryPort ?? 5e3;
2817
- if ((/* @__PURE__ */ new Set([appPort, sshPort, registryPort])).size !== 3) {
2818
- throw setupError("--port, --ssh-port and --registry-port must use different ports.");
2590
+ if (appPort === sshPort) {
2591
+ throw setupError("--port and --ssh-port must use different ports.");
2819
2592
  }
2820
2593
  const bind = options.bind ?? existing?.bind ?? DEFAULT_BIND;
2821
2594
  if (net2.isIP(bind) === 0) {
@@ -2830,7 +2603,7 @@ var validateExposure = (existing, options) => {
2830
2603
  const allowedOrigins = [
2831
2604
  ...new Set((requestedOrigins ?? inheritedOrigins).filter((origin) => origin !== appUrl))
2832
2605
  ];
2833
- return { bind, appUrl, allowedOrigins, appPort, sshPort, registryPort };
2606
+ return { bind, appUrl, allowedOrigins, appPort, sshPort };
2834
2607
  };
2835
2608
  var parseServerConfig = (raw) => {
2836
2609
  let decoded;
@@ -2871,16 +2644,17 @@ var parseServerConfig = (raw) => {
2871
2644
  ),
2872
2645
  appPort: requiredInteger(fields, "appPort"),
2873
2646
  sshPort: requiredInteger(fields, "sshPort"),
2874
- registryPort: fields.has("registryPort") ? requiredInteger(fields, "registryPort") : 5e3
2647
+ ...fields.has("registryPort") ? { registryPort: requiredInteger(fields, "registryPort") } : {}
2875
2648
  };
2876
- if (config.schemaVersion !== CONFIG_SCHEMA_VERSION || config.assetContract !== ASSET_CONTRACT) {
2649
+ if (config.schemaVersion !== CONFIG_SCHEMA_VERSION || config.assetContract !== ASSET_CONTRACT && !LEGACY_ASSET_CONTRACTS.has(config.assetContract)) {
2877
2650
  throw setupError(
2878
2651
  `Server config uses unsupported contract ${config.schemaVersion}/${config.assetContract}. Upgrade the CLI before setup.`
2879
2652
  );
2880
2653
  }
2881
2654
  parsePort(String(config.appPort), "Server config appPort");
2882
2655
  parsePort(String(config.sshPort), "Server config sshPort");
2883
- parsePort(String(config.registryPort), "Server config registryPort");
2656
+ if (config.registryPort !== void 0)
2657
+ parsePort(String(config.registryPort), "Server config registryPort");
2884
2658
  validateExposure(null, {
2885
2659
  context: void 0,
2886
2660
  version: void 0,
@@ -2890,7 +2664,6 @@ var parseServerConfig = (raw) => {
2890
2664
  appPort: config.appPort,
2891
2665
  sshPort: config.sshPort,
2892
2666
  dockerSocket: void 0,
2893
- registryPort: config.registryPort,
2894
2667
  assetsDir: void 0,
2895
2668
  offline: false
2896
2669
  });
@@ -2919,7 +2692,7 @@ var parseSecrets = (raw) => {
2919
2692
  postgresAdminPassword: envValue(fields, "MEND_POSTGRES_ADMIN_PASSWORD"),
2920
2693
  mendDatabasePassword: envValue(fields, "MEND_DB_PASSWORD"),
2921
2694
  sealantDatabasePassword: envValue(fields, "SEALANT_DB_PASSWORD"),
2922
- queuePassword: envValue(fields, "MEND_RABBITMQ_PASSWORD"),
2695
+ ...fields.has("MEND_RABBITMQ_PASSWORD") ? { queuePassword: envValue(fields, "MEND_RABBITMQ_PASSWORD") } : {},
2923
2696
  betterAuthSecret: envValue(fields, "BETTER_AUTH_SECRET"),
2924
2697
  sealantCredentialsKey: envValue(fields, "SEALANT_CREDENTIALS_KEY"),
2925
2698
  sealantServiceKey: envValue(fields, "SEALANT_SERVICE_KEY"),
@@ -2932,7 +2705,7 @@ var parseSecrets = (raw) => {
2932
2705
  ["MEND_POSTGRES_ADMIN_PASSWORD", secrets.postgresAdminPassword],
2933
2706
  ["MEND_DB_PASSWORD", secrets.mendDatabasePassword],
2934
2707
  ["SEALANT_DB_PASSWORD", secrets.sealantDatabasePassword],
2935
- ["MEND_RABBITMQ_PASSWORD", secrets.queuePassword],
2708
+ ...secrets.queuePassword === void 0 ? [] : [["MEND_RABBITMQ_PASSWORD", secrets.queuePassword]],
2936
2709
  ["WORKSPACE_SSH_GATEWAY_TOKEN", secrets.workspaceSshGatewayToken]
2937
2710
  ];
2938
2711
  for (const [name, value] of hexSecrets) {
@@ -2957,7 +2730,6 @@ var createSecrets = (runtime) => {
2957
2730
  postgresAdminPassword: hex(0),
2958
2731
  mendDatabasePassword: hex(32),
2959
2732
  sealantDatabasePassword: hex(64),
2960
- queuePassword: hex(96),
2961
2733
  betterAuthSecret: hex(128),
2962
2734
  sealantCredentialsKey: bytes.subarray(160, 192).toString("base64"),
2963
2735
  sealantServiceKey: `slt_svc_${hex(192)}`,
@@ -3132,8 +2904,6 @@ var validateComposeAsset = (body) => {
3132
2904
  "mend-control:",
3133
2905
  "mend-config:",
3134
2906
  "mend-ssh:",
3135
- "mend-rabbitmq:",
3136
- "mend-registry:",
3137
2907
  "mend-postgres:",
3138
2908
  "/var/lib/mend/store",
3139
2909
  "/run/sealant/sockets"
@@ -3161,7 +2931,7 @@ var renderIdentity = (secrets) => [
3161
2931
  `MEND_POSTGRES_ADMIN_PASSWORD=${secrets.postgresAdminPassword}`,
3162
2932
  `MEND_DB_PASSWORD=${secrets.mendDatabasePassword}`,
3163
2933
  `SEALANT_DB_PASSWORD=${secrets.sealantDatabasePassword}`,
3164
- `MEND_RABBITMQ_PASSWORD=${secrets.queuePassword}`,
2934
+ ...secrets.queuePassword === void 0 ? [] : [`MEND_RABBITMQ_PASSWORD=${secrets.queuePassword}`],
3165
2935
  `BETTER_AUTH_SECRET=${secrets.betterAuthSecret}`,
3166
2936
  `SEALANT_CREDENTIALS_KEY=${secrets.sealantCredentialsKey}`,
3167
2937
  `SEALANT_SERVICE_KEY=${secrets.sealantServiceKey}`,
@@ -3180,7 +2950,7 @@ var renderSecrets = (secrets, config) => {
3180
2950
  `MEND_BIND_HOST=${composeBind}`,
3181
2951
  `MEND_PORT=${config.appPort}`,
3182
2952
  `MEND_SSH_PORT=${config.sshPort}`,
3183
- `MEND_REGISTRY_PORT=${config.registryPort}`,
2953
+ ...config.registryPort === void 0 ? [] : [`MEND_REGISTRY_PORT=${config.registryPort}`],
3184
2954
  `SEALANT_SSH_HOST=${sshHost}`,
3185
2955
  "MEND_STORE_VOLUME_NAME=mend-store",
3186
2956
  "MEND_CONTROL_VOLUME_NAME=mend-control",
@@ -3262,7 +3032,7 @@ var resolveServerVersion = async (runtime, options, existing) => {
3262
3032
  return version;
3263
3033
  };
3264
3034
  var parseDockerSocketPath = (socket) => {
3265
- if (!path6.isAbsolute(socket) || /[\r\n$'"#:\\]/.test(socket)) {
3035
+ if (!path5.isAbsolute(socket) || /[\r\n$'"#:\\]/.test(socket)) {
3266
3036
  throw setupError(
3267
3037
  "--docker-socket must be an absolute path without line breaks or Compose interpolation characters."
3268
3038
  );
@@ -3288,8 +3058,8 @@ var resolveAssets = async (runtime, serverVersion, existing, store, options) =>
3288
3058
  if (options.assetsDir !== void 0) {
3289
3059
  try {
3290
3060
  const [compose2, postgresInit2] = await Promise.all([
3291
- readFile(path6.join(options.assetsDir, COMPOSE_ASSET), "utf8"),
3292
- readFile(path6.join(options.assetsDir, POSTGRES_INIT_ASSET), "utf8")
3061
+ readFile(path5.join(options.assetsDir, COMPOSE_ASSET), "utf8"),
3062
+ readFile(path5.join(options.assetsDir, POSTGRES_INIT_ASSET), "utf8")
3293
3063
  ]);
3294
3064
  validateComposeAsset(compose2);
3295
3065
  validatePostgresAsset(postgresInit2);
@@ -3297,7 +3067,7 @@ var resolveAssets = async (runtime, serverVersion, existing, store, options) =>
3297
3067
  } catch (cause) {
3298
3068
  if (cause instanceof ServerSetupError) throw cause;
3299
3069
  throw setupError(
3300
- "Could not read release assets from --assets-dir. Supply compose.v1.yaml and postgres-init.sh."
3070
+ "Could not read release assets from --assets-dir. Supply compose.v2.yaml and postgres-init.sh."
3301
3071
  );
3302
3072
  }
3303
3073
  }
@@ -3408,6 +3178,11 @@ var setupServer = async (args, runtime, store) => {
3408
3178
  `Setup retains Mend ${existing.config.serverVersion}. Use mend server upgrade --version ${options.version} to change the server pin.`
3409
3179
  );
3410
3180
  }
3181
+ if (existing !== null && existing.config.assetContract !== ASSET_CONTRACT) {
3182
+ throw setupError(
3183
+ `Mend ${existing.config.serverVersion} was installed under the ${existing.config.assetContract} bundle contract. Use mend server upgrade --version latest to move it to ${ASSET_CONTRACT}; setup cannot repair it in place.`
3184
+ );
3185
+ }
3411
3186
  const selectedContext = await selectDockerContext(
3412
3187
  runtime,
3413
3188
  options.context ?? existing?.config.dockerContext
@@ -3440,7 +3215,6 @@ var setupServer = async (args, runtime, store) => {
3440
3215
  storeValue(store.activate(generation));
3441
3216
  await startCompose(runtime, config, generation);
3442
3217
  await probeHealth(runtime, config.appUrl, config.serverVersion);
3443
- await verifyRegistry(runtime, config);
3444
3218
  runtime.writeLine(`Mend ${config.serverVersion} is reachable at ${config.appUrl}`);
3445
3219
  runtime.writeLine(
3446
3220
  `Open ${config.appUrl}, create the first account, then run: mend login --url ${config.appUrl}`
@@ -3504,16 +3278,6 @@ var composeCommand = async (runtime, installation, args) => {
3504
3278
  var interruptionNotice = (runtime) => runtime.writeLine(
3505
3279
  "Connections will be interrupted. Workspace containers and data are retained, but active work can lose connectivity and may need reconnection. Mend does not stop workspace containers."
3506
3280
  );
3507
- var verifyRegistry = async (runtime, config) => {
3508
- const registry = await probeServerRegistry(runtime, {
3509
- dockerContext: config.dockerContext,
3510
- registryPort: config.registryPort,
3511
- nonce: runtime.randomBytes(24).toString("hex"),
3512
- temporaryDirectory: path6.resolve(runtime.configDir)
3513
- });
3514
- for (const warning of registry.cleanupWarnings) runtime.writeLine(`Warning: ${warning.message}`);
3515
- if (registry._tag === "error") throw setupError(registry.error.message);
3516
- };
3517
3281
  var startInstallation = async (runtime, installation) => {
3518
3282
  startingNotice(runtime, installation.config.serverVersion);
3519
3283
  await composeCommand(runtime, installation, [
@@ -3525,7 +3289,6 @@ var startInstallation = async (runtime, installation) => {
3525
3289
  "--no-build"
3526
3290
  ]);
3527
3291
  await probeHealth(runtime, installation.config.appUrl, installation.config.serverVersion);
3528
- await verifyRegistry(runtime, installation.config);
3529
3292
  runtime.writeLine(
3530
3293
  `Mend ${installation.config.serverVersion} is reachable at ${installation.config.appUrl}`
3531
3294
  );
@@ -3562,7 +3325,12 @@ var upgradeServer = async (args, runtime, store, existing) => {
3562
3325
  const previous = storeValue(store.readActive());
3563
3326
  if (previous === null) throw setupError("The active server generation is missing.");
3564
3327
  const assets = await resolveAssets(runtime, version, existing, store, options);
3565
- const config = { ...existing.config, serverVersion: version };
3328
+ const { registryPort: _legacyRegistryPort, ...carried } = existing.config;
3329
+ const config = {
3330
+ ...carried,
3331
+ assetContract: ASSET_CONTRACT,
3332
+ serverVersion: version
3333
+ };
3566
3334
  const secrets = parseSecrets(previous.files.identity);
3567
3335
  const files = {
3568
3336
  identity: previous.files.identity,
@@ -3734,9 +3502,9 @@ var manageServer = async (command, args, runtime, store) => {
3734
3502
  var nodeServerRuntime = () => {
3735
3503
  const environment = { ...process.env };
3736
3504
  const home = os3.homedir();
3737
- const configHome = environment["XDG_CONFIG_HOME"] ?? path6.join(home, ".config");
3505
+ const configHome = environment["XDG_CONFIG_HOME"] ?? path5.join(home, ".config");
3738
3506
  return {
3739
- configDir: path6.join(configHome, "mend"),
3507
+ configDir: path5.join(configHome, "mend"),
3740
3508
  platform: process.platform,
3741
3509
  cliVersion: cliVersion(),
3742
3510
  run: (command, args, options) => runServerProcess(command, args, environment, options),
@@ -3753,7 +3521,7 @@ var nodeServerRuntime = () => {
3753
3521
  }
3754
3522
  },
3755
3523
  randomBytes,
3756
- sleep: (milliseconds) => new Promise((resolve5) => setTimeout(resolve5, milliseconds)),
3524
+ sleep: (milliseconds) => new Promise((resolve4) => setTimeout(resolve4, milliseconds)),
3757
3525
  writeLine: (line) => process.stdout.write(`${line}
3758
3526
  `)
3759
3527
  };
@@ -3962,8 +3730,8 @@ var renderMendToml = (proposals) => {
3962
3730
  };
3963
3731
 
3964
3732
  // src/skills.ts
3965
- import * as fs7 from "node:fs";
3966
- import * as path7 from "node:path";
3733
+ import * as fs6 from "node:fs";
3734
+ import * as path6 from "node:path";
3967
3735
  var DEFAULT_SKILLS_DIR = ".agents/skills";
3968
3736
  var MAX_SKILL_FILES = 64;
3969
3737
  var MAX_SKILL_FILE_BYTES = 512 * 1024;
@@ -3986,12 +3754,12 @@ var readFrontmatterDescription = (contents) => {
3986
3754
  var walkFiles = (root) => {
3987
3755
  const found = [];
3988
3756
  const visit = (dir) => {
3989
- for (const entry of fs7.readdirSync(dir, { withFileTypes: true })) {
3757
+ for (const entry of fs6.readdirSync(dir, { withFileTypes: true })) {
3990
3758
  if (IGNORED_DIRS.has(entry.name)) continue;
3991
- const absolute = path7.join(dir, entry.name);
3759
+ const absolute = path6.join(dir, entry.name);
3992
3760
  let stat;
3993
3761
  try {
3994
- stat = fs7.statSync(absolute);
3762
+ stat = fs6.statSync(absolute);
3995
3763
  } catch {
3996
3764
  continue;
3997
3765
  }
@@ -4005,7 +3773,7 @@ var walkFiles = (root) => {
4005
3773
  var scanSkillLibrary = (root) => {
4006
3774
  let entries;
4007
3775
  try {
4008
- entries = fs7.readdirSync(root, { withFileTypes: true });
3776
+ entries = fs6.readdirSync(root, { withFileTypes: true });
4009
3777
  } catch {
4010
3778
  return { error: `${root} is not a readable directory` };
4011
3779
  }
@@ -4013,23 +3781,23 @@ var scanSkillLibrary = (root) => {
4013
3781
  const notes = [];
4014
3782
  for (const entry of entries.toSorted((a, b) => a.name.localeCompare(b.name))) {
4015
3783
  if (entry.name.startsWith(".")) continue;
4016
- const skillDir = path7.join(root, entry.name);
3784
+ const skillDir = path6.join(root, entry.name);
4017
3785
  let stat;
4018
3786
  try {
4019
- stat = fs7.statSync(skillDir);
3787
+ stat = fs6.statSync(skillDir);
4020
3788
  } catch {
4021
3789
  continue;
4022
3790
  }
4023
3791
  if (!stat.isDirectory()) continue;
4024
- if (!fs7.existsSync(path7.join(skillDir, "SKILL.md"))) {
3792
+ if (!fs6.existsSync(path6.join(skillDir, "SKILL.md"))) {
4025
3793
  notes.push({ skill: entry.name, message: "no SKILL.md \u2014 skipped" });
4026
3794
  continue;
4027
3795
  }
4028
3796
  const files = [];
4029
3797
  let bytes = 0;
4030
3798
  for (const absolute of walkFiles(skillDir)) {
4031
- const relative3 = path7.relative(skillDir, absolute).split(path7.sep).join("/");
4032
- const buffer = fs7.readFileSync(absolute);
3799
+ const relative3 = path6.relative(skillDir, absolute).split(path6.sep).join("/");
3800
+ const buffer = fs6.readFileSync(absolute);
4033
3801
  if (buffer.byteLength > MAX_SKILL_FILE_BYTES) {
4034
3802
  notes.push({ skill: entry.name, message: `${relative3} is over 512KB \u2014 skipped` });
4035
3803
  continue;
@@ -4065,15 +3833,15 @@ var scanSkillLibrary = (root) => {
4065
3833
 
4066
3834
  // src/ssh-setup.ts
4067
3835
  import * as os5 from "node:os";
4068
- import * as path9 from "node:path";
3836
+ import * as path8 from "node:path";
4069
3837
 
4070
3838
  // ../../packages/workspace-ssh/src/index.ts
4071
3839
  import { spawnSync } from "node:child_process";
4072
3840
  import { createHash as createHash2 } from "node:crypto";
4073
- import * as fs8 from "node:fs";
3841
+ import * as fs7 from "node:fs";
4074
3842
  import { isIP as isIP2 } from "node:net";
4075
3843
  import * as os4 from "node:os";
4076
- import * as path8 from "node:path";
3844
+ import * as path7 from "node:path";
4077
3845
  var LEGACY_BLOCK_BEGIN = "# >>> mend workspace ssh (managed by `mend ssh setup`) >>>";
4078
3846
  var LEGACY_BLOCK_END = "# <<< mend workspace ssh <<<";
4079
3847
  var WorkspaceSshError = class extends Error {
@@ -4089,7 +3857,7 @@ var WorkspaceSshError = class extends Error {
4089
3857
  }
4090
3858
  };
4091
3859
  var success = (value) => ({ ok: true, value });
4092
- var failure = (error2) => ({ ok: false, error: error2 });
3860
+ var failure = (error) => ({ ok: false, error });
4093
3861
  var stripIpv6Brackets = (hostname6) => hostname6.startsWith("[") && hostname6.endsWith("]") ? hostname6.slice(1, -1) : hostname6;
4094
3862
  var hashIdentity = (identity) => createHash2("sha256").update(identity).digest("hex").slice(0, 24);
4095
3863
  var aliasLabel = (hostname6) => {
@@ -4160,8 +3928,8 @@ var blockBegin = (alias) => `# >>> mend workspace ssh ${alias} (managed) >>>`;
4160
3928
  var blockEnd = (alias) => `# <<< mend workspace ssh ${alias} <<<`;
4161
3929
  var hasControls = (value) => /[\p{Cc}\p{Zl}\p{Zp}]/u.test(value);
4162
3930
  var parseIdentityPath = (value) => {
4163
- const resolved = path8.resolve(
4164
- value.startsWith("~/") ? path8.join(os4.homedir(), value.slice(2)) : value
3931
+ const resolved = path7.resolve(
3932
+ value.startsWith("~/") ? path7.join(os4.homedir(), value.slice(2)) : value
4165
3933
  );
4166
3934
  if (value === "" || hasControls(value) || hasControls(resolved) || value.includes("${") || resolved.includes("${")) {
4167
3935
  return failure(
@@ -4297,10 +4065,10 @@ var agentKeys = () => {
4297
4065
  };
4298
4066
  var readText = (file) => {
4299
4067
  try {
4300
- return success(fs8.readFileSync(file, "utf8"));
4068
+ return success(fs7.readFileSync(file, "utf8"));
4301
4069
  } catch (cause) {
4302
- const error2 = cause instanceof Error && "code" in cause ? cause.code : void 0;
4303
- return error2 === "ENOENT" ? success("") : failure(new WorkspaceSshError("key", `Could not read ${file}.`, cause));
4070
+ const error = cause instanceof Error && "code" in cause ? cause.code : void 0;
4071
+ return error === "ENOENT" ? success("") : failure(new WorkspaceSshError("key", `Could not read ${file}.`, cause));
4304
4072
  }
4305
4073
  };
4306
4074
  var configuredWorkspaceSshIdentityFile = (config, target) => {
@@ -4355,19 +4123,19 @@ var readRequiredKey = (keyPath, source) => {
4355
4123
  var readOptionalKey = (keyPath) => {
4356
4124
  const parsed = parseIdentityPath(keyPath);
4357
4125
  if (!parsed.ok) return parsed;
4358
- if (!fs8.existsSync(parsed.value) && !fs8.existsSync(publicKeyPath(parsed.value)))
4126
+ if (!fs7.existsSync(parsed.value) && !fs7.existsSync(publicKeyPath(parsed.value)))
4359
4127
  return success(null);
4360
4128
  return readRequiredKey(parsed.value, "existing");
4361
4129
  };
4362
4130
  var pinAgentKey = (configHome, publicKey) => {
4363
- const identityFile = path8.resolve(
4131
+ const identityFile = path7.resolve(
4364
4132
  configHome,
4365
4133
  "ssh",
4366
4134
  `agent-${hashIdentity(publicKeyIdentity(publicKey))}.pub`
4367
4135
  );
4368
4136
  try {
4369
- fs8.mkdirSync(path8.dirname(identityFile), { recursive: true, mode: 448 });
4370
- fs8.writeFileSync(identityFile, `${publicKey}
4137
+ fs7.mkdirSync(path7.dirname(identityFile), { recursive: true, mode: 448 });
4138
+ fs7.writeFileSync(identityFile, `${publicKey}
4371
4139
  `, { mode: 384 });
4372
4140
  return success({ publicKey, identityFile, source: "agent" });
4373
4141
  } catch (cause) {
@@ -4378,10 +4146,10 @@ var pinAgentKey = (configHome, publicKey) => {
4378
4146
  };
4379
4147
  var generateDedicatedKey = (privatePath) => {
4380
4148
  try {
4381
- fs8.mkdirSync(path8.dirname(privatePath), { recursive: true, mode: 448 });
4149
+ fs7.mkdirSync(path7.dirname(privatePath), { recursive: true, mode: 448 });
4382
4150
  } catch (cause) {
4383
4151
  return failure(
4384
- new WorkspaceSshError("key", `Could not create ${path8.dirname(privatePath)}.`, cause)
4152
+ new WorkspaceSshError("key", `Could not create ${path7.dirname(privatePath)}.`, cause)
4385
4153
  );
4386
4154
  }
4387
4155
  const generated = spawnSync(
@@ -4410,7 +4178,7 @@ var generateDedicatedKey = (privatePath) => {
4410
4178
  return fresh;
4411
4179
  };
4412
4180
  var dedicatedKey = (configHome, create) => {
4413
- const privatePath = path8.join(configHome, "ssh", "id_ed25519");
4181
+ const privatePath = path7.join(configHome, "ssh", "id_ed25519");
4414
4182
  const existing = readOptionalKey(privatePath);
4415
4183
  if (existing.ok === false) return failure(existing.error);
4416
4184
  if (existing.value !== null || !create) return existing;
@@ -4458,10 +4226,10 @@ var writeWorkspaceSshConfig = (configFile, target, identityFile) => {
4458
4226
  const reconciled = reconcileWorkspaceSshConfig(before.value, target, identityFile);
4459
4227
  if (reconciled.ok === false) return failure(reconciled.error);
4460
4228
  try {
4461
- fs8.mkdirSync(path8.dirname(configFile), { recursive: true, mode: 448 });
4462
- fs8.chmodSync(path8.dirname(configFile), 448);
4463
- fs8.writeFileSync(configFile, reconciled.value, { mode: 384 });
4464
- fs8.chmodSync(configFile, 384);
4229
+ fs7.mkdirSync(path7.dirname(configFile), { recursive: true, mode: 448 });
4230
+ fs7.chmodSync(path7.dirname(configFile), 448);
4231
+ fs7.writeFileSync(configFile, reconciled.value, { mode: 384 });
4232
+ fs7.chmodSync(configFile, 384);
4465
4233
  return success(void 0);
4466
4234
  } catch (cause) {
4467
4235
  return failure(new WorkspaceSshError("config", `Could not write ${configFile}.`, cause));
@@ -4474,7 +4242,7 @@ var dim3 = ansi("2");
4474
4242
  var green3 = ansi("32");
4475
4243
  var warn = ansi("33");
4476
4244
  var say3 = (line) => console.log(line);
4477
- var sshConfigPath = () => path9.join(os5.homedir(), ".ssh", "config");
4245
+ var sshConfigPath = () => path8.join(os5.homedir(), ".ssh", "config");
4478
4246
  var flagValue = (args, flag) => {
4479
4247
  const index = args.indexOf(flag);
4480
4248
  return index === -1 || args[index + 1] === void 0 ? null : String(args[index + 1]);
@@ -4588,19 +4356,19 @@ var sshCommand = async (args, api2, cliHome, serverUrl) => {
4588
4356
  // src/main.ts
4589
4357
  var mendCliHome = () => {
4590
4358
  const xdg = process.env["XDG_CONFIG_HOME"];
4591
- const preferred = path10.join(
4592
- xdg === void 0 || xdg === "" ? path10.join(os6.homedir(), ".config") : xdg,
4359
+ const preferred = path9.join(
4360
+ xdg === void 0 || xdg === "" ? path9.join(os6.homedir(), ".config") : xdg,
4593
4361
  "mend"
4594
4362
  );
4595
- const legacy = path10.join(os6.homedir(), ".mend");
4596
- return !fs9.existsSync(preferred) && fs9.existsSync(legacy) ? legacy : preferred;
4363
+ const legacy = path9.join(os6.homedir(), ".mend");
4364
+ return !fs8.existsSync(preferred) && fs8.existsSync(legacy) ? legacy : preferred;
4597
4365
  };
4598
- var CONFIG_PATH = path10.join(mendCliHome(), "cli.json");
4366
+ var CONFIG_PATH = path9.join(mendCliHome(), "cli.json");
4599
4367
  var loadConfig = () => {
4600
4368
  let fileConfig = {};
4601
- if (fs9.existsSync(CONFIG_PATH)) {
4369
+ if (fs8.existsSync(CONFIG_PATH)) {
4602
4370
  try {
4603
- fileConfig = JSON.parse(fs9.readFileSync(CONFIG_PATH, "utf8"));
4371
+ fileConfig = JSON.parse(fs8.readFileSync(CONFIG_PATH, "utf8"));
4604
4372
  } catch {
4605
4373
  fail4(`could not parse ${CONFIG_PATH}`);
4606
4374
  }
@@ -4700,8 +4468,8 @@ var request = async (config, method, route, body) => {
4700
4468
  var api = async (config, method, route, body) => {
4701
4469
  try {
4702
4470
  return await request(config, method, route, body);
4703
- } catch (error2) {
4704
- return fail4(error2 instanceof Error ? error2.message : String(error2));
4471
+ } catch (error) {
4472
+ return fail4(error instanceof Error ? error.message : String(error));
4705
4473
  }
4706
4474
  };
4707
4475
  var boundApi = (config) => (method, route, body) => api(config, method, route, body);
@@ -4737,7 +4505,7 @@ var adopt = async (config, args) => {
4737
4505
  }
4738
4506
  const sourceIssue = repositoryCloneUrlIssue(source);
4739
4507
  if (sourceIssue !== null) return fail4(sourceIssue);
4740
- const projectName = name ?? normalizeProjectName(path10.basename(source, ".git"));
4508
+ const projectName = name ?? normalizeProjectName(path9.basename(source, ".git"));
4741
4509
  const auth = authFlagIndex !== -1 && args[authFlagIndex + 1] !== void 0 ? String(args[authFlagIndex + 1]) : null;
4742
4510
  if (auth !== null && auth !== "ambient" && auth !== "mend-key" && auth !== "bridge") {
4743
4511
  return fail4(`--auth takes "ambient", "mend-key", or "bridge", not "${auth}"`);
@@ -4782,7 +4550,7 @@ var findProject = async (config, explicit, adoptCwd = false) => {
4782
4550
  const sourceIssue = repositoryCloneUrlIssue(facts.originUrl);
4783
4551
  if (sourceIssue !== null) return fail4(sourceIssue);
4784
4552
  const adopted = await api(config, "POST", "/projects", {
4785
- name: normalizeProjectName(path10.basename(facts.repoRoot)),
4553
+ name: normalizeProjectName(path9.basename(facts.repoRoot)),
4786
4554
  source: facts.originUrl
4787
4555
  });
4788
4556
  say4(`${green4("\u2713")} adopted \xB7 ${adopted.name} \xB7 ${dim4(adopted.storePath)}`);
@@ -4927,8 +4695,8 @@ var attachTty = async (config, sessionId, harness, from, processId, options) =>
4927
4695
  const ws = new WebSocket(url);
4928
4696
  ws.binaryType = "arraybuffer";
4929
4697
  try {
4930
- await new Promise((resolve5, reject) => {
4931
- ws.addEventListener("open", () => resolve5(), { once: true });
4698
+ await new Promise((resolve4, reject) => {
4699
+ ws.addEventListener("open", () => resolve4(), { once: true });
4932
4700
  ws.addEventListener("error", () => reject(new Error("could not connect")), { once: true });
4933
4701
  });
4934
4702
  } catch {
@@ -4936,8 +4704,8 @@ var attachTty = async (config, sessionId, harness, from, processId, options) =>
4936
4704
  }
4937
4705
  const stopHerdrHint = hintHerdrAttachment(harness);
4938
4706
  let finishAttachment;
4939
- const finished = new Promise((resolve5) => {
4940
- finishAttachment = resolve5;
4707
+ const finished = new Promise((resolve4) => {
4708
+ finishAttachment = resolve4;
4941
4709
  });
4942
4710
  const onClose = () => finishAttachment?.();
4943
4711
  if (ws.readyState === WebSocket.CLOSED) finishAttachment?.();
@@ -5555,7 +5323,7 @@ var serviceLogs = async (config, args) => {
5555
5323
  cursor = page.nextFrom;
5556
5324
  const ended = page.status === "exited" || page.status === "failed";
5557
5325
  if (ended && !advanced) break;
5558
- if (!advanced) await new Promise((resolve5) => setTimeout(resolve5, 250));
5326
+ if (!advanced) await new Promise((resolve4) => setTimeout(resolve4, 250));
5559
5327
  }
5560
5328
  say4("");
5561
5329
  say4(dim4(`stream ended \xB7 next sequence ${cursor}`));
@@ -5573,13 +5341,13 @@ var serviceRestart = async (config, args) => {
5573
5341
  };
5574
5342
  var serviceInit = async (args) => {
5575
5343
  const root = gitTopLevel(process.cwd()) ?? process.cwd();
5576
- const target = path10.join(root, "mend.toml");
5577
- if (fs9.existsSync(target)) {
5344
+ const target = path9.join(root, "mend.toml");
5345
+ if (fs8.existsSync(target)) {
5578
5346
  return fail4(`${target} already exists \u2014 edit it directly (init never merges)`);
5579
5347
  }
5580
- const rootFiles = fs9.readdirSync(root);
5348
+ const rootFiles = fs8.readdirSync(root);
5581
5349
  const proposals = [];
5582
- const readRoot = (name) => fs9.readFileSync(path10.join(root, name), "utf8");
5350
+ const readRoot = (name) => fs8.readFileSync(path9.join(root, name), "utf8");
5583
5351
  if (rootFiles.includes("package.json")) {
5584
5352
  proposals.push(...proposeFromPackageJson(readRoot("package.json"), rootFiles));
5585
5353
  }
@@ -5589,13 +5357,13 @@ var serviceInit = async (args) => {
5589
5357
  );
5590
5358
  for (const glob of globs) {
5591
5359
  const parent = glob.replace(/\/?\*+$/, "");
5592
- const dirs = glob.endsWith("*") ? fs9.existsSync(path10.join(root, parent)) ? fs9.readdirSync(path10.join(root, parent)).map((dir) => path10.join(parent, dir)) : [] : [glob];
5360
+ const dirs = glob.endsWith("*") ? fs8.existsSync(path9.join(root, parent)) ? fs8.readdirSync(path9.join(root, parent)).map((dir) => path9.join(parent, dir)) : [] : [glob];
5593
5361
  for (const dir of dirs) {
5594
- const manifest = path10.join(root, dir, "package.json");
5595
- if (!fs9.existsSync(manifest)) continue;
5362
+ const manifest = path9.join(root, dir, "package.json");
5363
+ if (!fs8.existsSync(manifest)) continue;
5596
5364
  for (const proposal of proposeFromWorkspacePackage(
5597
- path10.basename(dir),
5598
- fs9.readFileSync(manifest, "utf8"),
5365
+ path9.basename(dir),
5366
+ fs8.readFileSync(manifest, "utf8"),
5599
5367
  rootFiles
5600
5368
  )) {
5601
5369
  if (!proposals.some((existing) => existing.name === proposal.name)) {
@@ -5639,7 +5407,7 @@ var serviceInit = async (args) => {
5639
5407
  return;
5640
5408
  }
5641
5409
  }
5642
- fs9.writeFileSync(target, toml);
5410
+ fs8.writeFileSync(target, toml);
5643
5411
  say4(`${green4("\u2713")} wrote ${target} \u2014 commit it, then: mend service run ${proposals[0]?.name}`);
5644
5412
  };
5645
5413
  var tunnelServices = async (config, services, portOverride) => {
@@ -5670,16 +5438,16 @@ var tunnelServices = async (config, services, portOverride) => {
5670
5438
  socket.on("close", () => ws.close());
5671
5439
  socket.on("error", () => ws.close());
5672
5440
  });
5673
- await new Promise((resolve5, reject) => {
5674
- server.once("error", (error2) => {
5441
+ await new Promise((resolve4, reject) => {
5442
+ server.once("error", (error) => {
5675
5443
  reject(
5676
5444
  new Error(
5677
- error2.code === "EADDRINUSE" ? `127.0.0.1:${port} is already in use here \u2014 pick one with: mend service connect ${service.label} --port <n>` : error2.message
5445
+ error.code === "EADDRINUSE" ? `127.0.0.1:${port} is already in use here \u2014 pick one with: mend service connect ${service.label} --port <n>` : error.message
5678
5446
  )
5679
5447
  );
5680
5448
  });
5681
- server.listen(port, "127.0.0.1", () => resolve5());
5682
- }).catch((error2) => fail4(error2.message));
5449
+ server.listen(port, "127.0.0.1", () => resolve4());
5450
+ }).catch((error) => fail4(error.message));
5683
5451
  say4(
5684
5452
  `${green4("\u25CF")} ${service.label ?? service.id.slice(0, 8)} \u2192 127.0.0.1:${port} ${dim4(`(tunnel to ${config.url})`)}`
5685
5453
  );
@@ -5732,18 +5500,18 @@ var serviceCommand = async (config, args) => {
5732
5500
  }
5733
5501
  };
5734
5502
  var isProvider = (value) => value === "claude" || value === "codex" || value === "github";
5735
- var readIfExists = (file) => fs9.existsSync(file) ? fs9.readFileSync(file, "utf8") : null;
5503
+ var readIfExists = (file) => fs8.existsSync(file) ? fs8.readFileSync(file, "utf8") : null;
5736
5504
  var localCredential = (provider) => {
5737
5505
  const home = os6.homedir();
5738
5506
  switch (provider) {
5739
5507
  case "codex":
5740
5508
  return readIfExists(
5741
- path10.join(process.env["CODEX_HOME"] ?? path10.join(home, ".codex"), "auth.json")
5509
+ path9.join(process.env["CODEX_HOME"] ?? path9.join(home, ".codex"), "auth.json")
5742
5510
  );
5743
5511
  case "claude":
5744
5512
  return readIfExists(
5745
- path10.join(
5746
- process.env["CLAUDE_CONFIG_DIR"] ?? path10.join(home, ".claude"),
5513
+ path9.join(
5514
+ process.env["CLAUDE_CONFIG_DIR"] ?? path9.join(home, ".claude"),
5747
5515
  ".credentials.json"
5748
5516
  )
5749
5517
  );
@@ -5797,7 +5565,7 @@ var connectCommand = async (config, args) => {
5797
5565
  }
5798
5566
  let secret;
5799
5567
  if (flags.includes("--from-stdin")) {
5800
- secret = fs9.readFileSync(0, "utf8").trim();
5568
+ secret = fs8.readFileSync(0, "utf8").trim();
5801
5569
  if (secret === "") return fail4("nothing on stdin");
5802
5570
  } else {
5803
5571
  secret = localCredential(provider);
@@ -5818,14 +5586,14 @@ var takeFlagValue3 = (args, flag) => {
5818
5586
  return at !== -1 && args[at + 1] !== void 0 ? String(args[at + 1]) : null;
5819
5587
  };
5820
5588
  var saveCliConfig = (next) => {
5821
- fs9.mkdirSync(path10.dirname(CONFIG_PATH), { recursive: true, mode: 448 });
5822
- fs9.writeFileSync(
5589
+ fs8.mkdirSync(path9.dirname(CONFIG_PATH), { recursive: true, mode: 448 });
5590
+ fs8.writeFileSync(
5823
5591
  CONFIG_PATH,
5824
5592
  `${JSON.stringify({ url: next.url, token: next.token, deviceId: next.deviceId }, null, 2)}
5825
5593
  `,
5826
5594
  { mode: 384 }
5827
5595
  );
5828
- fs9.chmodSync(CONFIG_PATH, 384);
5596
+ fs8.chmodSync(CONFIG_PATH, 384);
5829
5597
  };
5830
5598
  var login = async (config, args) => {
5831
5599
  await loginCommand(args, {
@@ -5838,7 +5606,7 @@ var login = async (config, args) => {
5838
5606
  });
5839
5607
  };
5840
5608
  var logout = async (config) => {
5841
- if (!fs9.existsSync(CONFIG_PATH) && config.token === null) {
5609
+ if (!fs8.existsSync(CONFIG_PATH) && config.token === null) {
5842
5610
  say4(dim4("nothing saved \u2014 already signed out"));
5843
5611
  return;
5844
5612
  }
@@ -6027,10 +5795,10 @@ var envLoad = async (config, args) => {
6027
5795
  const positional = args.filter(
6028
5796
  (arg, i) => !arg.startsWith("--") && args[i - 1] !== "--project" && !consumed.has(i)
6029
5797
  );
6030
- const file = path10.resolve(positional[0] ?? ".env");
5798
+ const file = path9.resolve(positional[0] ?? ".env");
6031
5799
  let contents;
6032
5800
  try {
6033
- contents = fs9.readFileSync(file, "utf8");
5801
+ contents = fs8.readFileSync(file, "utf8");
6034
5802
  } catch {
6035
5803
  return fail4(`cannot read ${file} \u2014 pass a path: mend env load path/to/.env`);
6036
5804
  }
@@ -6046,10 +5814,10 @@ var envLoad = async (config, args) => {
6046
5814
  { contents, allSecret, secretNames }
6047
5815
  );
6048
5816
  if (report.loaded.length === 0 && report.rejected.length === 0 && report.malformedLines.length === 0) {
6049
- say4(dim4(`${path10.basename(file)} has no variables`));
5817
+ say4(dim4(`${path9.basename(file)} has no variables`));
6050
5818
  return;
6051
5819
  }
6052
- say4(`${green4("\u2713")} loaded ${path10.basename(file)} into ${project2.name}`);
5820
+ say4(`${green4("\u2713")} loaded ${path9.basename(file)} into ${project2.name}`);
6053
5821
  for (const line of formatLoadReport(report, { dim: dim4, warn: amber })) say4(line);
6054
5822
  const plaintextUrls = report.loaded.filter(
6055
5823
  (entry) => entry.lane === "configuration" && /_(URL|URI|DSN)$/.test(entry.name)
@@ -6238,7 +6006,7 @@ var skillsList = async (config, args) => {
6238
6006
  };
6239
6007
  var skillsPush = async (config, args) => {
6240
6008
  const dirFlag = takeFlagValue3(args, "--dir");
6241
- const root = dirFlag === null ? path10.join(os6.homedir(), DEFAULT_SKILLS_DIR) : path10.resolve(dirFlag);
6009
+ const root = dirFlag === null ? path9.join(os6.homedir(), DEFAULT_SKILLS_DIR) : path9.resolve(dirFlag);
6242
6010
  const scanned = scanSkillLibrary(root);
6243
6011
  if ("error" in scanned) return fail4(scanned.error);
6244
6012
  for (const note of scanned.notes) say4(dim4(` ${note.skill}: ${note.message}`));
@@ -6328,7 +6096,7 @@ _mend() {
6328
6096
  compadd setup status start stop restart logs upgrade
6329
6097
  else
6330
6098
  case $words[3] in
6331
- setup) compadd -- --version --assets-dir --offline --context --port --ssh-port --registry-port --bind --url --origin --docker-socket ;;
6099
+ setup) compadd -- --version --assets-dir --offline --context --port --ssh-port --bind --url --origin --docker-socket ;;
6332
6100
  upgrade) compadd -- --version --assets-dir --offline ;;
6333
6101
  start|restart) compadd -- --offline ;;
6334
6102
  logs) compadd -- --tail ;;
@@ -6357,7 +6125,7 @@ var BASH_COMPLETIONS = `_mend() {
6357
6125
  options="setup status start stop restart logs upgrade"
6358
6126
  else
6359
6127
  case \${COMP_WORDS[2]} in
6360
- setup) options="--version --assets-dir --offline --context --port --ssh-port --registry-port --bind --url --origin --docker-socket" ;;
6128
+ setup) options="--version --assets-dir --offline --context --port --ssh-port --bind --url --origin --docker-socket" ;;
6361
6129
  upgrade) options="--version --assets-dir --offline" ;;
6362
6130
  start|restart) options="--offline" ;;
6363
6131
  logs) options="--tail" ;;
@@ -6539,10 +6307,10 @@ var resumeForRejoin = async (config, sessionId, label) => {
6539
6307
  request(config, "POST", `/sessions/${sessionId}/resume`, { harness: null })
6540
6308
  );
6541
6309
  return true;
6542
- } catch (error2) {
6310
+ } catch (error) {
6543
6311
  const refreshed = await api(config, "GET", `/sessions/${sessionId}`);
6544
6312
  if (agentIsLive(refreshed.session, refreshed.currentAgent)) return false;
6545
- return fail4(error2 instanceof Error ? error2.message : String(error2));
6313
+ return fail4(error instanceof Error ? error.message : String(error));
6546
6314
  }
6547
6315
  };
6548
6316
  var rejoinCommand = async (config, args) => {
@@ -6952,10 +6720,10 @@ var manCommand = (words) => {
6952
6720
  }
6953
6721
  const version = cliVersion();
6954
6722
  const page = doc === null ? renderManIndex(version) : renderManPage(doc, version);
6955
- const file = path10.join(os6.tmpdir(), `mend-man-${process.pid}-${manFileName(doc)}`);
6956
- fs9.writeFileSync(file, page);
6723
+ const file = path9.join(os6.tmpdir(), `mend-man-${process.pid}-${manFileName(doc)}`);
6724
+ fs8.writeFileSync(file, page);
6957
6725
  const result = spawnSync2("man", ["-l", file], { stdio: "inherit" });
6958
- fs9.rmSync(file, { force: true });
6726
+ fs8.rmSync(file, { force: true });
6959
6727
  if (result.error !== void 0 || result.status !== 0) {
6960
6728
  say4(doc === null ? renderIndex() : renderCommand(doc));
6961
6729
  }