@sealant/mend 0.26.0 → 0.27.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +5 -5
- package/dist/{chunk-4MX7ZC34.js → chunk-BMMHU2SH.js} +9 -1
- package/dist/{dashboard-JIWYW3EM.js → dashboard-S56N7YOG.js} +1 -1
- package/dist/main.js +178 -53
- package/man/mend-accounts.1 +1 -1
- package/man/mend-adopt.1 +1 -1
- package/man/mend-attach.1 +1 -1
- package/man/mend-codex.1 +1 -1
- package/man/mend-completions.1 +1 -1
- package/man/mend-connect.1 +1 -1
- package/man/mend-continue.1 +1 -1
- package/man/mend-doctor.1 +1 -1
- package/man/mend-dotfiles-sync.1 +1 -1
- package/man/mend-dotfiles.1 +1 -1
- package/man/mend-env-cluster.1 +1 -1
- package/man/mend-env-load.1 +1 -1
- package/man/mend-env-show.1 +1 -1
- package/man/mend-help.1 +1 -1
- package/man/mend-keys-init.1 +1 -1
- package/man/mend-keys-mode.1 +1 -1
- package/man/mend-keys-share.1 +1 -1
- package/man/mend-keys-show.1 +1 -1
- package/man/mend-login.1 +1 -1
- package/man/mend-logout.1 +1 -1
- package/man/mend-man.1 +1 -1
- package/man/mend-pair.1 +1 -1
- package/man/mend-projects.1 +1 -1
- package/man/mend-refresh.1 +1 -1
- package/man/mend-rejoin.1 +1 -1
- package/man/mend-resume.1 +1 -1
- package/man/mend-run.1 +1 -1
- package/man/mend-server-logs.1 +1 -1
- package/man/mend-server-restart.1 +2 -2
- package/man/mend-server-setup.1 +1 -1
- package/man/mend-server-start.1 +1 -1
- package/man/mend-server-status.1 +1 -1
- package/man/mend-server-stop.1 +2 -2
- package/man/mend-server-upgrade.1 +1 -1
- package/man/mend-server.1 +1 -1
- package/man/mend-service-add.1 +1 -1
- package/man/mend-service-connect.1 +1 -1
- package/man/mend-service-init.1 +1 -1
- package/man/mend-service-list.1 +1 -1
- package/man/mend-service-logs.1 +1 -1
- package/man/mend-service-restart.1 +1 -1
- package/man/mend-service-run.1 +1 -1
- package/man/mend-service-stop.1 +1 -1
- package/man/mend-sessions.1 +1 -1
- package/man/mend-shell.1 +1 -1
- package/man/mend-skills-push.1 +1 -1
- package/man/mend-skills.1 +1 -1
- package/man/mend-ssh-setup.1 +1 -1
- package/man/mend-ssh.1 +1 -1
- package/man/mend-stop.1 +1 -1
- package/man/mend-ui.1 +1 -1
- package/man/mend-uninstall.1 +1 -1
- package/man/mend-version.1 +1 -1
- package/man/mend-worktrees.1 +1 -1
- package/man/mend.1 +2 -2
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -47,11 +47,11 @@ mend server upgrade --version 0.24.0 --assets-dir ./release-0.24.0 --offline
|
|
|
47
47
|
|
|
48
48
|
Each asset directory must contain `compose.v2.yaml` and `postgres-init.sh`. Mend copies them into an
|
|
49
49
|
immutable private generation; the source directory is not needed afterward. Preload official
|
|
50
|
-
`postgres:17-alpine` and canonical `ghcr.io/sealant-sh/mend:VERSION`. Image
|
|
51
|
-
report that exact version. `--offline` forbids GitHub requests and
|
|
52
|
-
Start/restart always reuse local release images and retained assets. The bundle
|
|
53
|
-
web port (`--port`, default `3105`) and the SSH gateway port (`--ssh-port`,
|
|
54
|
-
must differ.
|
|
50
|
+
`postgres:17-alpine`, `dxflrs/garage:v2.4.1` and canonical `ghcr.io/sealant-sh/mend:VERSION`. Image
|
|
51
|
+
labels and health must report that exact version. `--offline` forbids GitHub requests and
|
|
52
|
+
release-image pulls. Start/restart always reuse local release images and retained assets. The bundle
|
|
53
|
+
publishes only the web port (`--port`, default `3105`) and the SSH gateway port (`--ssh-port`,
|
|
54
|
+
default `2222`); the two must differ.
|
|
55
55
|
|
|
56
56
|
Stop/restart/upgrade interrupt connections. Workspace containers and data remain, but active work
|
|
57
57
|
may lose connectivity and need reconnection. No volumes are deleted. Status/logs never start an
|
|
@@ -26640,6 +26640,14 @@ var Project = class extends Schema_exports.Class("Project")({
|
|
|
26640
26640
|
* effectively instant. Explicit resource intent: N ready containers per project.
|
|
26641
26641
|
*/
|
|
26642
26642
|
hotSessions: Schema_exports.Number,
|
|
26643
|
+
/**
|
|
26644
|
+
* The command that builds this project's dependency tree, run by Mend in a workspace whose
|
|
26645
|
+
* tree does not match the executor's platform and by the install job that feeds the shared
|
|
26646
|
+
* cache (ADR-0002 decisions 2/9). Null = detected from the base tree's lockfile at launch.
|
|
26647
|
+
*/
|
|
26648
|
+
installCommand: Schema_exports.NullOr(Schema_exports.String).pipe(
|
|
26649
|
+
Schema_exports.withDecodingDefaultKey(Effect_exports.succeed(null))
|
|
26650
|
+
),
|
|
26643
26651
|
createdAt: Timestamp,
|
|
26644
26652
|
updatedAt: Timestamp
|
|
26645
26653
|
}) {
|
|
@@ -27180,7 +27188,7 @@ var ProjectClusterBindingsSnapshot = class extends Schema_exports.Class(
|
|
|
27180
27188
|
}) {
|
|
27181
27189
|
};
|
|
27182
27190
|
|
|
27183
|
-
// ../../node_modules/.pnpm/@sealant+api-contracts@0.
|
|
27191
|
+
// ../../node_modules/.pnpm/@sealant+api-contracts@0.31.2_effect@4.0.0-beta.93/node_modules/@sealant/api-contracts/dist/workspace-environment.js
|
|
27184
27192
|
var RESERVED_NAMES = new Map([
|
|
27185
27193
|
// Identity/process roots the daemon and shells own.
|
|
27186
27194
|
...[
|
package/dist/main.js
CHANGED
|
@@ -17,7 +17,7 @@ import {
|
|
|
17
17
|
repositoryCloneUrlIssue,
|
|
18
18
|
sessionDisplayName,
|
|
19
19
|
trackBracketedPaste
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-BMMHU2SH.js";
|
|
21
21
|
|
|
22
22
|
// src/main.ts
|
|
23
23
|
import { spawn as spawn3, spawnSync as spawnSync2 } from "node:child_process";
|
|
@@ -1112,7 +1112,7 @@ var COMMANDS = [
|
|
|
1112
1112
|
{
|
|
1113
1113
|
name: "server stop",
|
|
1114
1114
|
section: "this machine",
|
|
1115
|
-
summary: "stop Mend and
|
|
1115
|
+
summary: "stop Mend, Postgres and Garage without deleting data",
|
|
1116
1116
|
synopsis: [""],
|
|
1117
1117
|
description: [
|
|
1118
1118
|
"Stops only the installation's Compose services. Connections are interrupted. Workspace containers and volumes remain, but active work may lose connectivity and need reconnection. No volume deletion or Docker prune is performed."
|
|
@@ -1125,7 +1125,7 @@ var COMMANDS = [
|
|
|
1125
1125
|
summary: "restart Mend using the same generation and pin",
|
|
1126
1126
|
synopsis: ["[--offline]"],
|
|
1127
1127
|
description: [
|
|
1128
|
-
"Checks preloaded images before stopping Mend, then starts the saved generation and verifies exact-version health. Postgres
|
|
1128
|
+
"Checks preloaded images before stopping Mend, then starts the saved generation and verifies exact-version health. Postgres and Garage stay running. Connections are interrupted; workspace containers and data remain, but active work may need reconnection."
|
|
1129
1129
|
],
|
|
1130
1130
|
options: [{ flag: "--offline", text: "use local release images and assets" }],
|
|
1131
1131
|
see: ["server status", "server logs"]
|
|
@@ -1759,7 +1759,7 @@ var loginCommand = async (args, deps) => {
|
|
|
1759
1759
|
};
|
|
1760
1760
|
|
|
1761
1761
|
// src/server-setup.ts
|
|
1762
|
-
import { randomBytes } from "node:crypto";
|
|
1762
|
+
import { createHmac, randomBytes } from "node:crypto";
|
|
1763
1763
|
import { readFile } from "node:fs/promises";
|
|
1764
1764
|
import * as net2 from "node:net";
|
|
1765
1765
|
import * as os3 from "node:os";
|
|
@@ -1772,6 +1772,11 @@ var MEND_DOCKER_NAMESPACE = {
|
|
|
1772
1772
|
store: "mend-store",
|
|
1773
1773
|
control: "mend-control"
|
|
1774
1774
|
};
|
|
1775
|
+
var MEND_DOCKER_NAMESPACE_WITH_GARAGE = {
|
|
1776
|
+
...MEND_DOCKER_NAMESPACE,
|
|
1777
|
+
garage: "mend-garage"
|
|
1778
|
+
};
|
|
1779
|
+
var secondaryVolumesOf = (namespace) => namespace.garage === void 0 ? [namespace.control] : [namespace.control, namespace.garage];
|
|
1775
1780
|
var SERVER_VOLUME_OWNER_LABEL = "dev.sealant.mend.installation";
|
|
1776
1781
|
var ServerVolumeOwnershipError = class extends Error {
|
|
1777
1782
|
/** Stable error discriminator. */
|
|
@@ -1863,9 +1868,10 @@ var inProjectNamespace = (name, project2) => ["-", "_"].some(
|
|
|
1863
1868
|
(separator) => name.startsWith(`${project2}${separator}`) && name.length > project2.length + 1
|
|
1864
1869
|
);
|
|
1865
1870
|
var reservedResourceName = (name, kind, namespace) => {
|
|
1866
|
-
if ([namespace.project, namespace.store, namespace.control].includes(name))
|
|
1871
|
+
if ([namespace.project, namespace.store, namespace.control, namespace.garage ?? ""].includes(name))
|
|
1872
|
+
return true;
|
|
1867
1873
|
return ["-", "_"].some(
|
|
1868
|
-
(separator) => (kind === "container" ? ["mend", "postgres"] : ["default"]).some((service) => {
|
|
1874
|
+
(separator) => (kind === "container" ? ["mend", "postgres", "garage"] : ["default"]).some((service) => {
|
|
1869
1875
|
const reserved = `${namespace.project}${separator}${service}`;
|
|
1870
1876
|
return name === reserved || kind === "container" && name.startsWith(reserved) && /^[-_](?:[0-9]+|run[-_][a-f0-9]+)$/.test(name.slice(reserved.length));
|
|
1871
1877
|
})
|
|
@@ -1901,7 +1907,9 @@ var inspectForeignProject = async (runtime, context, kind, name, project2) => {
|
|
|
1901
1907
|
}
|
|
1902
1908
|
};
|
|
1903
1909
|
var refuseOldData = async (runtime, context, namespace, volumes) => {
|
|
1904
|
-
if (volumes.some(
|
|
1910
|
+
if (volumes.some(
|
|
1911
|
+
(name) => secondaryVolumesOf(namespace).includes(name) || name.startsWith(`${namespace.project}_`)
|
|
1912
|
+
))
|
|
1905
1913
|
return fail3("unowned-data", "existing volumes without anchor");
|
|
1906
1914
|
for (const kind of ["volume", "container", "network"]) {
|
|
1907
1915
|
const labelled = await listNames(runtime, context, kind, namespace.project);
|
|
@@ -1930,9 +1938,9 @@ var refuseOldData = async (runtime, context, namespace, volumes) => {
|
|
|
1930
1938
|
};
|
|
1931
1939
|
var parseOwnershipInput = (input) => {
|
|
1932
1940
|
const namespace = input.namespace ?? MEND_DOCKER_NAMESPACE;
|
|
1933
|
-
if (!input.dockerContext.trim() || input.identityBytes.byteLength === 0 || !/^[a-z0-9][a-z0-9_-]*$/.test(namespace.project) || ![namespace.store, namespace
|
|
1941
|
+
if (!input.dockerContext.trim() || input.identityBytes.byteLength === 0 || !/^[a-z0-9][a-z0-9_-]*$/.test(namespace.project) || ![namespace.store, ...secondaryVolumesOf(namespace)].every(
|
|
1934
1942
|
(name) => /^[a-zA-Z0-9][a-zA-Z0-9_.-]+$/.test(name)
|
|
1935
|
-
) || namespace.store
|
|
1943
|
+
) || (/* @__PURE__ */ new Set([namespace.store, ...secondaryVolumesOf(namespace)])).size !== 1 + secondaryVolumesOf(namespace).length)
|
|
1936
1944
|
return fail3("invalid-input", "ownership inputs");
|
|
1937
1945
|
const owner = createHash("sha256").update(input.identityBytes).digest("hex");
|
|
1938
1946
|
return { _tag: "ok", value: { owner, namespace } };
|
|
@@ -1955,13 +1963,16 @@ var ownership = async (runtime, input, mode) => {
|
|
|
1955
1963
|
}
|
|
1956
1964
|
const current = await listNames(runtime, input.dockerContext, "volume");
|
|
1957
1965
|
if (current._tag === "error") return current;
|
|
1958
|
-
|
|
1959
|
-
const
|
|
1960
|
-
if (
|
|
1961
|
-
|
|
1962
|
-
|
|
1963
|
-
|
|
1964
|
-
|
|
1966
|
+
for (const name of secondaryVolumesOf(namespace)) {
|
|
1967
|
+
const role = name === namespace.control ? "control" : "garage";
|
|
1968
|
+
if (current.value.includes(name)) {
|
|
1969
|
+
const owned = await inspectOwner(runtime, input.dockerContext, name, owner);
|
|
1970
|
+
if (owned._tag === "error") return owned;
|
|
1971
|
+
} else {
|
|
1972
|
+
if (mode === "verify") return fail3("missing", role);
|
|
1973
|
+
const created = await ensureVolume(runtime, input.dockerContext, name, owner);
|
|
1974
|
+
if (created._tag === "error") return created;
|
|
1975
|
+
}
|
|
1965
1976
|
}
|
|
1966
1977
|
return { _tag: "ok", value: { owner, namespace } };
|
|
1967
1978
|
};
|
|
@@ -2471,6 +2482,8 @@ var COMPOSE_ASSET = "compose.v2.yaml";
|
|
|
2471
2482
|
var POSTGRES_INIT_ASSET = "postgres-init.sh";
|
|
2472
2483
|
var RELEASE_BASE = "https://github.com/sealant-sh/Mend/releases/download";
|
|
2473
2484
|
var LATEST_RELEASE_URL = "https://api.github.com/repos/sealant-sh/Mend/releases/latest";
|
|
2485
|
+
var GARAGE_IMAGE = "dxflrs/garage:v2.4.1";
|
|
2486
|
+
var GARAGE_BUCKET = "mend";
|
|
2474
2487
|
var ServerSetupError = class extends Error {
|
|
2475
2488
|
_tag = "ServerSetupError";
|
|
2476
2489
|
};
|
|
@@ -2486,6 +2499,12 @@ var requiredString = (fields, key) => {
|
|
|
2486
2499
|
}
|
|
2487
2500
|
return value;
|
|
2488
2501
|
};
|
|
2502
|
+
var requiredBucket = (fields) => {
|
|
2503
|
+
const value = fields.get("bucket");
|
|
2504
|
+
if (value !== "garage")
|
|
2505
|
+
throw setupError("Server config is corrupt: bucket must be garage when present.");
|
|
2506
|
+
return value;
|
|
2507
|
+
};
|
|
2489
2508
|
var requiredInteger = (fields, key) => {
|
|
2490
2509
|
const value = fields.get(key);
|
|
2491
2510
|
if (typeof value !== "number" || !Number.isInteger(value)) {
|
|
@@ -2668,7 +2687,8 @@ var parseServerConfig = (raw) => {
|
|
|
2668
2687
|
),
|
|
2669
2688
|
appPort: requiredInteger(fields, "appPort"),
|
|
2670
2689
|
sshPort: requiredInteger(fields, "sshPort"),
|
|
2671
|
-
...fields.has("registryPort") ? { registryPort: requiredInteger(fields, "registryPort") } : {}
|
|
2690
|
+
...fields.has("registryPort") ? { registryPort: requiredInteger(fields, "registryPort") } : {},
|
|
2691
|
+
...fields.has("bucket") ? { bucket: requiredBucket(fields) } : {}
|
|
2672
2692
|
};
|
|
2673
2693
|
if (config.schemaVersion !== CONFIG_SCHEMA_VERSION || config.assetContract !== ASSET_CONTRACT && !LEGACY_ASSET_CONTRACTS.has(config.assetContract)) {
|
|
2674
2694
|
throw setupError(
|
|
@@ -2893,10 +2913,15 @@ var resolveLatestVersion = async (runtime) => {
|
|
|
2893
2913
|
if (version === "latest") throw setupError("GitHub returned an invalid latest release tag.");
|
|
2894
2914
|
return version;
|
|
2895
2915
|
};
|
|
2896
|
-
var
|
|
2916
|
+
var composeServiceNames = (body) => {
|
|
2897
2917
|
const [, afterServices = ""] = body.split(/^services:\s*$/m);
|
|
2898
2918
|
const [servicesBlock = ""] = afterServices.split(/^\S/m);
|
|
2899
|
-
|
|
2919
|
+
return [...servicesBlock.matchAll(/^ {2}([0-9A-Za-z_-]+):\s*$/gm)].map((match) => match[1]).filter((name) => name !== void 0).toSorted((left, right) => left.localeCompare(right));
|
|
2920
|
+
};
|
|
2921
|
+
var composeBucket = (body) => composeServiceNames(body).includes("garage") ? "garage" : void 0;
|
|
2922
|
+
var validateComposeAsset = (body) => {
|
|
2923
|
+
const serviceNames = composeServiceNames(body);
|
|
2924
|
+
const withGarage = serviceNames.includes("garage");
|
|
2900
2925
|
const volumeSections = body.split(/^volumes:[ \t]*$/m);
|
|
2901
2926
|
const volumeBlock = (volumeSections[1] ?? "").split(/^\S/m)[0] ?? "";
|
|
2902
2927
|
const volumeDeclarations = [
|
|
@@ -2904,7 +2929,8 @@ var validateComposeAsset = (body) => {
|
|
|
2904
2929
|
];
|
|
2905
2930
|
for (const [name, variable] of [
|
|
2906
2931
|
["mend-store", "MEND_STORE_VOLUME_NAME"],
|
|
2907
|
-
["mend-control", "MEND_CONTROL_VOLUME_NAME"]
|
|
2932
|
+
["mend-control", "MEND_CONTROL_VOLUME_NAME"],
|
|
2933
|
+
...withGarage ? [["mend-garage", "MEND_GARAGE_VOLUME_NAME"]] : []
|
|
2908
2934
|
]) {
|
|
2909
2935
|
const declarations = volumeDeclarations.filter((match) => match[1] === name);
|
|
2910
2936
|
const properties = (declarations[0]?.[2] ?? "").split("\n").map((line) => line.trim()).filter((line) => line !== "" && !line.startsWith("#"));
|
|
@@ -2930,9 +2956,22 @@ var validateComposeAsset = (body) => {
|
|
|
2930
2956
|
"mend-ssh:",
|
|
2931
2957
|
"mend-postgres:",
|
|
2932
2958
|
"/var/lib/mend/store",
|
|
2933
|
-
"/run/sealant/sockets"
|
|
2959
|
+
"/run/sealant/sockets",
|
|
2960
|
+
// The capture store's bucket (a release since the capture store); an older release's asset
|
|
2961
|
+
// has none and is still a valid v2 bundle.
|
|
2962
|
+
...withGarage ? [
|
|
2963
|
+
`image: ${GARAGE_IMAGE}`,
|
|
2964
|
+
" garage:",
|
|
2965
|
+
"mend-garage:",
|
|
2966
|
+
"MEND_GARAGE_RPC_SECRET",
|
|
2967
|
+
"MEND_GARAGE_ADMIN_TOKEN",
|
|
2968
|
+
"MEND_GARAGE_KEY_ID",
|
|
2969
|
+
"MEND_GARAGE_KEY_SECRET",
|
|
2970
|
+
"MEND_BLOB_STORE",
|
|
2971
|
+
"MEND_SESSION_ENDPOINT_URL"
|
|
2972
|
+
] : []
|
|
2934
2973
|
];
|
|
2935
|
-
if (requiredFragments.some((fragment) => !body.includes(fragment)) || serviceNames.join(",") !== "mend,postgres") {
|
|
2974
|
+
if (requiredFragments.some((fragment) => !body.includes(fragment)) || serviceNames.join(",") !== "mend,postgres" && serviceNames.join(",") !== "garage,mend,postgres") {
|
|
2936
2975
|
throw setupError(`Downloaded ${COMPOSE_ASSET} does not implement ${ASSET_CONTRACT}.`);
|
|
2937
2976
|
}
|
|
2938
2977
|
};
|
|
@@ -2962,6 +3001,27 @@ var renderIdentity = (secrets) => [
|
|
|
2962
3001
|
`WORKSPACE_SSH_GATEWAY_TOKEN=${secrets.workspaceSshGatewayToken}`,
|
|
2963
3002
|
""
|
|
2964
3003
|
].join("\n");
|
|
3004
|
+
var garageSecrets = (secrets) => {
|
|
3005
|
+
const derive = (label) => createHmac("sha256", Buffer.from(secrets.betterAuthSecret, "hex")).update(label).digest("hex");
|
|
3006
|
+
return {
|
|
3007
|
+
rpcSecret: derive("mend-garage-rpc-secret"),
|
|
3008
|
+
adminToken: derive("mend-garage-admin-token"),
|
|
3009
|
+
keyId: `GK${derive("mend-garage-key-id").slice(0, 24)}`,
|
|
3010
|
+
keySecret: derive("mend-garage-key-secret")
|
|
3011
|
+
};
|
|
3012
|
+
};
|
|
3013
|
+
var renderGarage = (secrets, config) => {
|
|
3014
|
+
if (config.bucket !== "garage") return [];
|
|
3015
|
+
const garage = garageSecrets(secrets);
|
|
3016
|
+
return [
|
|
3017
|
+
`MEND_GARAGE_RPC_SECRET=${garage.rpcSecret}`,
|
|
3018
|
+
`MEND_GARAGE_ADMIN_TOKEN=${garage.adminToken}`,
|
|
3019
|
+
`MEND_GARAGE_KEY_ID=${garage.keyId}`,
|
|
3020
|
+
`MEND_GARAGE_KEY_SECRET=${garage.keySecret}`,
|
|
3021
|
+
"MEND_GARAGE_VOLUME_NAME=mend-garage"
|
|
3022
|
+
];
|
|
3023
|
+
};
|
|
3024
|
+
var namespaceOf = (config) => config.bucket === "garage" ? MEND_DOCKER_NAMESPACE_WITH_GARAGE : MEND_DOCKER_NAMESPACE;
|
|
2965
3025
|
var renderSecrets = (secrets, config) => {
|
|
2966
3026
|
const sshHost = parseUrl(config.appUrl, "Server config appUrl").hostname.replace(/^\[|\]$/g, "");
|
|
2967
3027
|
const composeBind = net2.isIP(config.bind) === 6 ? `[${config.bind}]` : config.bind;
|
|
@@ -2978,6 +3038,7 @@ var renderSecrets = (secrets, config) => {
|
|
|
2978
3038
|
`SEALANT_SSH_HOST=${sshHost}`,
|
|
2979
3039
|
"MEND_STORE_VOLUME_NAME=mend-store",
|
|
2980
3040
|
"MEND_CONTROL_VOLUME_NAME=mend-control",
|
|
3041
|
+
...renderGarage(secrets, config),
|
|
2981
3042
|
`DOCKER_SOCKET_PATH=${config.dockerSocket}`,
|
|
2982
3043
|
""
|
|
2983
3044
|
].join("\n");
|
|
@@ -3147,6 +3208,17 @@ var checkLocalImages = async (runtime, config, policy = "local") => {
|
|
|
3147
3208
|
);
|
|
3148
3209
|
if (postgres.status !== 0)
|
|
3149
3210
|
throw commandFailure("Preload postgres:17-alpine before continuing", postgres);
|
|
3211
|
+
if (config.bucket === "garage") {
|
|
3212
|
+
const garage = await inspectImage(
|
|
3213
|
+
runtime,
|
|
3214
|
+
config.dockerContext,
|
|
3215
|
+
GARAGE_IMAGE,
|
|
3216
|
+
"{{.Id}}",
|
|
3217
|
+
policy
|
|
3218
|
+
);
|
|
3219
|
+
if (garage.status !== 0)
|
|
3220
|
+
throw commandFailure(`Preload ${GARAGE_IMAGE} before continuing`, garage);
|
|
3221
|
+
}
|
|
3150
3222
|
};
|
|
3151
3223
|
var checkComposeImages = async (runtime, installation) => {
|
|
3152
3224
|
const output = await runtime.run(
|
|
@@ -3160,14 +3232,50 @@ var checkComposeImages = async (runtime, installation) => {
|
|
|
3160
3232
|
const images = output.stdout.trim().split(/\s+/).toSorted();
|
|
3161
3233
|
const expected = [
|
|
3162
3234
|
`ghcr.io/sealant-sh/mend:${installation.config.serverVersion}`,
|
|
3163
|
-
"postgres:17-alpine"
|
|
3235
|
+
"postgres:17-alpine",
|
|
3236
|
+
...installation.config.bucket === "garage" ? [GARAGE_IMAGE] : []
|
|
3164
3237
|
].toSorted();
|
|
3165
3238
|
if (images.join("\n") !== expected.join("\n")) {
|
|
3166
3239
|
throw setupError(
|
|
3167
|
-
|
|
3240
|
+
`Compose must use only the canonical pinned Mend image, official postgres:17-alpine${installation.config.bucket === "garage" ? ` and ${GARAGE_IMAGE}` : ""}.`
|
|
3168
3241
|
);
|
|
3169
3242
|
}
|
|
3170
3243
|
};
|
|
3244
|
+
var initGarage = async (runtime, installation, secrets) => {
|
|
3245
|
+
if (installation.config.bucket !== "garage") return;
|
|
3246
|
+
const garage = garageSecrets(secrets);
|
|
3247
|
+
const exec = (args) => runtime.run(
|
|
3248
|
+
"docker",
|
|
3249
|
+
serverComposeArgs(
|
|
3250
|
+
{ directory: installation.directory, dockerContext: installation.config.dockerContext },
|
|
3251
|
+
["exec", "-T", "garage", "/garage", "-c", "/etc/garage.toml", ...args]
|
|
3252
|
+
),
|
|
3253
|
+
{ timeoutMs: serverProcessDeadlines.ordinary }
|
|
3254
|
+
);
|
|
3255
|
+
const status = await exec(["status"]);
|
|
3256
|
+
if (status.status !== 0 || status.error !== void 0)
|
|
3257
|
+
throw commandFailure("Garage did not answer its status", status);
|
|
3258
|
+
const node = status.stdout.match(/^([0-9a-f]{16})\s/m)?.[1];
|
|
3259
|
+
if (node === void 0) throw setupError("Garage reported no node in `garage status`.");
|
|
3260
|
+
await exec(["layout", "assign", "-z", "mend", "-c", "1GB", node]);
|
|
3261
|
+
await exec(["layout", "apply", "--version", "1"]);
|
|
3262
|
+
await exec(["bucket", "create", GARAGE_BUCKET]);
|
|
3263
|
+
await exec(["key", "import", "--yes", "-n", GARAGE_BUCKET, garage.keyId, garage.keySecret]);
|
|
3264
|
+
await exec([
|
|
3265
|
+
"bucket",
|
|
3266
|
+
"allow",
|
|
3267
|
+
"--read",
|
|
3268
|
+
"--write",
|
|
3269
|
+
"--owner",
|
|
3270
|
+
GARAGE_BUCKET,
|
|
3271
|
+
"--key",
|
|
3272
|
+
GARAGE_BUCKET
|
|
3273
|
+
]);
|
|
3274
|
+
const info = await exec(["bucket", "info", GARAGE_BUCKET]);
|
|
3275
|
+
if (info.status !== 0 || info.error !== void 0 || !info.stdout.includes(garage.keyId))
|
|
3276
|
+
throw commandFailure(`Garage bucket ${GARAGE_BUCKET} is not readable by Mend's key`, info);
|
|
3277
|
+
runtime.writeLine(`Capture store bucket ${GARAGE_BUCKET} is laid out in Garage`);
|
|
3278
|
+
};
|
|
3171
3279
|
var startingNotice = (runtime, version) => runtime.writeLine(
|
|
3172
3280
|
`Starting Mend ${version} containers; Docker waits up to ${serverProcessDeadlines.composeWaitSeconds}s for them to report healthy`
|
|
3173
3281
|
);
|
|
@@ -3214,7 +3322,7 @@ var setupServer = async (args, runtime, store) => {
|
|
|
3214
3322
|
const operatingSystem = await checkDocker(runtime, selectedContext.name);
|
|
3215
3323
|
runtime.writeLine(`Using Docker context "${selectedContext.name}" (${selectedContext.endpoint})`);
|
|
3216
3324
|
const serverVersion = await resolveServerVersion(runtime, options, existing?.config ?? null);
|
|
3217
|
-
const
|
|
3325
|
+
const configWithoutBucket = {
|
|
3218
3326
|
schemaVersion: CONFIG_SCHEMA_VERSION,
|
|
3219
3327
|
assetContract: ASSET_CONTRACT,
|
|
3220
3328
|
serverVersion,
|
|
@@ -3227,17 +3335,24 @@ var setupServer = async (args, runtime, store) => {
|
|
|
3227
3335
|
...validateExposure(existing?.config ?? null, options)
|
|
3228
3336
|
};
|
|
3229
3337
|
const assets = await resolveAssets(runtime, serverVersion, existing, store, options);
|
|
3338
|
+
const bucket = composeBucket(assets.compose);
|
|
3339
|
+
const config = {
|
|
3340
|
+
...configWithoutBucket,
|
|
3341
|
+
...bucket === void 0 ? {} : { bucket }
|
|
3342
|
+
};
|
|
3230
3343
|
const secrets = savedSecrets ?? createSecrets(runtime);
|
|
3231
3344
|
const generation = persistSetup(store, config, secrets, assets);
|
|
3232
3345
|
await checkComposeImages(runtime, { directory: generation.directory, config });
|
|
3233
3346
|
const ownership2 = await claimServerDockerVolumes(runtime, {
|
|
3234
3347
|
dockerContext: config.dockerContext,
|
|
3235
|
-
identityBytes: Buffer.from(generation.files.identity)
|
|
3348
|
+
identityBytes: Buffer.from(generation.files.identity),
|
|
3349
|
+
namespace: namespaceOf(config)
|
|
3236
3350
|
});
|
|
3237
3351
|
if (ownership2._tag === "error") throw setupError(ownership2.error.message);
|
|
3238
3352
|
await checkLocalImages(runtime, config, options.offline ? "local" : "pull-missing");
|
|
3239
3353
|
storeValue(store.activate(generation));
|
|
3240
3354
|
await startCompose(runtime, config, generation);
|
|
3355
|
+
await initGarage(runtime, { directory: generation.directory, config }, secrets);
|
|
3241
3356
|
await probeHealth(runtime, config.appUrl, config.serverVersion);
|
|
3242
3357
|
runtime.writeLine(`Mend ${config.serverVersion} is reachable at ${config.appUrl}`);
|
|
3243
3358
|
runtime.writeLine(
|
|
@@ -3302,7 +3417,7 @@ var composeCommand = async (runtime, installation, args) => {
|
|
|
3302
3417
|
var interruptionNotice = (runtime) => runtime.writeLine(
|
|
3303
3418
|
"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."
|
|
3304
3419
|
);
|
|
3305
|
-
var startInstallation = async (runtime, installation) => {
|
|
3420
|
+
var startInstallation = async (runtime, installation, secrets) => {
|
|
3306
3421
|
startingNotice(runtime, installation.config.serverVersion);
|
|
3307
3422
|
await composeCommand(runtime, installation, [
|
|
3308
3423
|
"up",
|
|
@@ -3312,6 +3427,7 @@ var startInstallation = async (runtime, installation) => {
|
|
|
3312
3427
|
"never",
|
|
3313
3428
|
"--no-build"
|
|
3314
3429
|
]);
|
|
3430
|
+
await initGarage(runtime, installation, secrets);
|
|
3315
3431
|
await probeHealth(runtime, installation.config.appUrl, installation.config.serverVersion);
|
|
3316
3432
|
runtime.writeLine(
|
|
3317
3433
|
`Mend ${installation.config.serverVersion} is reachable at ${installation.config.appUrl}`
|
|
@@ -3349,11 +3465,17 @@ var upgradeServer = async (args, runtime, store, existing) => {
|
|
|
3349
3465
|
const previous = storeValue(store.readActive());
|
|
3350
3466
|
if (previous === null) throw setupError("The active server generation is missing.");
|
|
3351
3467
|
const assets = await resolveAssets(runtime, version, existing, store, options);
|
|
3352
|
-
const {
|
|
3468
|
+
const {
|
|
3469
|
+
registryPort: _legacyRegistryPort,
|
|
3470
|
+
bucket: _previousBucket,
|
|
3471
|
+
...carried
|
|
3472
|
+
} = existing.config;
|
|
3473
|
+
const bucket = composeBucket(assets.compose);
|
|
3353
3474
|
const config = {
|
|
3354
3475
|
...carried,
|
|
3355
3476
|
assetContract: ASSET_CONTRACT,
|
|
3356
|
-
serverVersion: version
|
|
3477
|
+
serverVersion: version,
|
|
3478
|
+
...bucket === void 0 ? {} : { bucket }
|
|
3357
3479
|
};
|
|
3358
3480
|
const secrets = parseSecrets(previous.files.identity);
|
|
3359
3481
|
const files = {
|
|
@@ -3371,6 +3493,14 @@ var upgradeServer = async (args, runtime, store, existing) => {
|
|
|
3371
3493
|
const target = storeValue(store.prepare(files));
|
|
3372
3494
|
const installation = { directory: target.directory, config };
|
|
3373
3495
|
await checkComposeImages(runtime, installation);
|
|
3496
|
+
if (config.bucket === "garage" && existing.config.bucket !== "garage") {
|
|
3497
|
+
const ownership2 = await claimServerDockerVolumes(runtime, {
|
|
3498
|
+
dockerContext: config.dockerContext,
|
|
3499
|
+
identityBytes: Buffer.from(previous.files.identity),
|
|
3500
|
+
namespace: namespaceOf(config)
|
|
3501
|
+
});
|
|
3502
|
+
if (ownership2._tag === "error") throw setupError(ownership2.error.message);
|
|
3503
|
+
}
|
|
3374
3504
|
const running = await composeCommand(runtime, existing, [
|
|
3375
3505
|
"ps",
|
|
3376
3506
|
"--status",
|
|
@@ -3418,7 +3548,7 @@ var upgradeServer = async (args, runtime, store, existing) => {
|
|
|
3418
3548
|
recovery = "Could not reselect the previous generation. Inspect active before retrying any command.";
|
|
3419
3549
|
else if (appWasRunning) {
|
|
3420
3550
|
try {
|
|
3421
|
-
await startInstallation(runtime, existing);
|
|
3551
|
+
await startInstallation(runtime, existing, secrets);
|
|
3422
3552
|
recovery = "Previous pin and app recovered.";
|
|
3423
3553
|
} catch {
|
|
3424
3554
|
recovery = "Previous pin retained, but the old app could not recover. Run mend server start after fixing Docker.";
|
|
@@ -3430,7 +3560,7 @@ var upgradeServer = async (args, runtime, store, existing) => {
|
|
|
3430
3560
|
);
|
|
3431
3561
|
}
|
|
3432
3562
|
try {
|
|
3433
|
-
await startInstallation(runtime, installation);
|
|
3563
|
+
await startInstallation(runtime, installation, secrets);
|
|
3434
3564
|
} catch {
|
|
3435
3565
|
throw setupError(
|
|
3436
3566
|
`Mend ${version} startup, exact-version health or registry verification failed; migrations may have begun. The target pin remains active. Do NOT downgrade or restore the database automatically. Run mend server logs --tail 100 and mend server status; fix the target, then mend server start --offline. Previous generation: ${previous.directory}. Target generation: ${target.directory}. Database backup and recovery record: ${backup.directory}.`
|
|
@@ -3480,9 +3610,11 @@ var manageServer = async (command, args, runtime, store) => {
|
|
|
3480
3610
|
if (identity === null) throw setupError("The persisted server identity is missing.");
|
|
3481
3611
|
const ownership2 = await verifyServerDockerVolumes(runtime, {
|
|
3482
3612
|
dockerContext: installation.config.dockerContext,
|
|
3483
|
-
identityBytes: Buffer.from(identity)
|
|
3613
|
+
identityBytes: Buffer.from(identity),
|
|
3614
|
+
namespace: namespaceOf(installation.config)
|
|
3484
3615
|
});
|
|
3485
3616
|
if (ownership2._tag === "error") throw setupError(ownership2.error.message);
|
|
3617
|
+
const secrets = parseSecrets(identity);
|
|
3486
3618
|
if (command === "upgrade") return upgradeServer(args, runtime, store, installation);
|
|
3487
3619
|
if (command === "logs") {
|
|
3488
3620
|
let tail = 100;
|
|
@@ -3512,7 +3644,7 @@ var manageServer = async (command, args, runtime, store) => {
|
|
|
3512
3644
|
interruptionNotice(runtime);
|
|
3513
3645
|
await composeCommand(runtime, installation, ["stop", "--timeout", "30"]);
|
|
3514
3646
|
runtime.writeLine(
|
|
3515
|
-
|
|
3647
|
+
`Mend, Postgres${installation.config.bucket === "garage" ? " and Garage" : ""} stopped. Volumes, configuration and workspace containers are retained.`
|
|
3516
3648
|
);
|
|
3517
3649
|
return;
|
|
3518
3650
|
}
|
|
@@ -3521,7 +3653,7 @@ var manageServer = async (command, args, runtime, store) => {
|
|
|
3521
3653
|
interruptionNotice(runtime);
|
|
3522
3654
|
await composeCommand(runtime, installation, ["stop", "--timeout", "30", "mend"]);
|
|
3523
3655
|
}
|
|
3524
|
-
await startInstallation(runtime, installation);
|
|
3656
|
+
await startInstallation(runtime, installation, secrets);
|
|
3525
3657
|
};
|
|
3526
3658
|
var nodeServerRuntime = () => {
|
|
3527
3659
|
const environment = { ...process.env };
|
|
@@ -4503,7 +4635,7 @@ var planLines = (plan, configDir) => {
|
|
|
4503
4635
|
);
|
|
4504
4636
|
if (dockerContext !== "") {
|
|
4505
4637
|
lines.push(
|
|
4506
|
-
` containers mend, postgres \xB7 volumes ${[
|
|
4638
|
+
` containers mend, postgres, garage \xB7 volumes ${[MEND_DOCKER_NAMESPACE_WITH_GARAGE.store, ...secondaryVolumesOf(MEND_DOCKER_NAMESPACE_WITH_GARAGE), "mend-config", "mend-ssh", "mend-postgres"].join(", ")} \xB7 image ghcr.io/sealant-sh/mend:${version}`
|
|
4507
4639
|
);
|
|
4508
4640
|
}
|
|
4509
4641
|
lines.push(
|
|
@@ -4559,40 +4691,33 @@ var removeServer = async (runtime, failures, leftovers) => {
|
|
|
4559
4691
|
`docker compose down failed: ${(down.error ?? down.stderr.trim()) || "no output"}. Containers and files are retained; fix Docker and run mend uninstall again.`
|
|
4560
4692
|
);
|
|
4561
4693
|
}
|
|
4562
|
-
server.writeLine("removed containers mend, postgres and the Compose-owned volumes");
|
|
4694
|
+
server.writeLine("removed containers mend, postgres, garage and the Compose-owned volumes");
|
|
4695
|
+
const namespace = installation.config.bucket === "garage" ? MEND_DOCKER_NAMESPACE_WITH_GARAGE : MEND_DOCKER_NAMESPACE;
|
|
4696
|
+
const dataVolumes = [namespace.store, ...secondaryVolumesOf(namespace)];
|
|
4563
4697
|
const identity = store.readIdentity();
|
|
4564
4698
|
if (identity._tag === "error") throw identity.error;
|
|
4565
4699
|
const owned = identity.value === null ? null : await verifyServerDockerVolumes(server, {
|
|
4566
4700
|
dockerContext: context,
|
|
4567
|
-
identityBytes: Buffer.from(identity.value)
|
|
4701
|
+
identityBytes: Buffer.from(identity.value),
|
|
4702
|
+
namespace
|
|
4568
4703
|
});
|
|
4569
4704
|
if (owned !== null && owned._tag === "ok") {
|
|
4570
4705
|
const removed = await server.run(
|
|
4571
4706
|
"docker",
|
|
4572
|
-
dockerArgs(
|
|
4573
|
-
context,
|
|
4574
|
-
"volume",
|
|
4575
|
-
"rm",
|
|
4576
|
-
MEND_DOCKER_NAMESPACE.store,
|
|
4577
|
-
MEND_DOCKER_NAMESPACE.control
|
|
4578
|
-
)
|
|
4707
|
+
dockerArgs(context, "volume", "rm", ...dataVolumes)
|
|
4579
4708
|
);
|
|
4580
4709
|
if (removed.status === 0) {
|
|
4581
|
-
server.writeLine(
|
|
4582
|
-
`removed volumes ${MEND_DOCKER_NAMESPACE.store}, ${MEND_DOCKER_NAMESPACE.control}`
|
|
4583
|
-
);
|
|
4710
|
+
server.writeLine(`removed volumes ${dataVolumes.join(", ")}`);
|
|
4584
4711
|
} else {
|
|
4585
4712
|
failures.push(
|
|
4586
|
-
`could not remove volumes ${
|
|
4713
|
+
`could not remove volumes ${dataVolumes.join(", ")}: ${(removed.error ?? removed.stderr.trim()) || "no output"}`
|
|
4587
4714
|
);
|
|
4588
4715
|
}
|
|
4589
4716
|
} else if (owned !== null && owned.error.reason === "missing") {
|
|
4590
|
-
server.writeLine(
|
|
4591
|
-
`volumes ${MEND_DOCKER_NAMESPACE.store}, ${MEND_DOCKER_NAMESPACE.control} were already gone`
|
|
4592
|
-
);
|
|
4717
|
+
server.writeLine(`volumes ${dataVolumes.join(", ")} were already gone`);
|
|
4593
4718
|
} else {
|
|
4594
4719
|
leftovers.push(
|
|
4595
|
-
`volumes ${
|
|
4720
|
+
`volumes ${dataVolumes.join(", ")}: ownership could not be confirmed for this installation, so they stay (docker --context ${context} volume ls)`
|
|
4596
4721
|
);
|
|
4597
4722
|
}
|
|
4598
4723
|
const image = `ghcr.io/sealant-sh/mend:${installation.config.serverVersion}`;
|
|
@@ -7062,7 +7187,7 @@ var dashboard = async (config) => {
|
|
|
7062
7187
|
);
|
|
7063
7188
|
process.exit(rerun.status ?? 0);
|
|
7064
7189
|
}
|
|
7065
|
-
const { runDashboard } = await import("./dashboard-
|
|
7190
|
+
const { runDashboard } = await import("./dashboard-S56N7YOG.js");
|
|
7066
7191
|
const agentShare = await startShareIfBridge(config);
|
|
7067
7192
|
try {
|
|
7068
7193
|
await runDashboard({
|
package/man/mend-accounts.1
CHANGED
package/man/mend-adopt.1
CHANGED
package/man/mend-attach.1
CHANGED
package/man/mend-codex.1
CHANGED
package/man/mend-completions.1
CHANGED
package/man/mend-connect.1
CHANGED
package/man/mend-continue.1
CHANGED
package/man/mend-doctor.1
CHANGED
package/man/mend-dotfiles-sync.1
CHANGED
package/man/mend-dotfiles.1
CHANGED
package/man/mend-env-cluster.1
CHANGED
package/man/mend-env-load.1
CHANGED
package/man/mend-env-show.1
CHANGED
package/man/mend-help.1
CHANGED
package/man/mend-keys-init.1
CHANGED
package/man/mend-keys-mode.1
CHANGED
package/man/mend-keys-share.1
CHANGED
package/man/mend-keys-show.1
CHANGED
package/man/mend-login.1
CHANGED
package/man/mend-logout.1
CHANGED
package/man/mend-man.1
CHANGED
package/man/mend-pair.1
CHANGED
package/man/mend-projects.1
CHANGED
package/man/mend-refresh.1
CHANGED
package/man/mend-rejoin.1
CHANGED
package/man/mend-resume.1
CHANGED
package/man/mend-run.1
CHANGED
package/man/mend-server-logs.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH MEND-SERVER-RESTART 1 "" "mend 0.
|
|
1
|
+
.TH MEND-SERVER-RESTART 1 "" "mend 0.27.4" "mend manual"
|
|
2
2
|
.SH NAME
|
|
3
3
|
mend\-server\-restart \- restart Mend using the same generation and pin
|
|
4
4
|
.SH SYNOPSIS
|
|
@@ -6,7 +6,7 @@ mend\-server\-restart \- restart Mend using the same generation and pin
|
|
|
6
6
|
.br
|
|
7
7
|
.SH DESCRIPTION
|
|
8
8
|
.PP
|
|
9
|
-
Checks preloaded images before stopping Mend, then starts the saved generation and verifies exact\-version health. Postgres
|
|
9
|
+
Checks preloaded images before stopping Mend, then starts the saved generation and verifies exact\-version health. Postgres and Garage stay running. Connections are interrupted; workspace containers and data remain, but active work may need reconnection.
|
|
10
10
|
.SH OPTIONS
|
|
11
11
|
.TP
|
|
12
12
|
.B \-\-offline
|
package/man/mend-server-setup.1
CHANGED
package/man/mend-server-start.1
CHANGED
package/man/mend-server-status.1
CHANGED
package/man/mend-server-stop.1
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
.TH MEND-SERVER-STOP 1 "" "mend 0.
|
|
1
|
+
.TH MEND-SERVER-STOP 1 "" "mend 0.27.4" "mend manual"
|
|
2
2
|
.SH NAME
|
|
3
|
-
mend\-server\-stop \- stop Mend and
|
|
3
|
+
mend\-server\-stop \- stop Mend, Postgres and Garage without deleting data
|
|
4
4
|
.SH SYNOPSIS
|
|
5
5
|
.B mend server stop
|
|
6
6
|
.br
|
package/man/mend-server.1
CHANGED
package/man/mend-service-add.1
CHANGED
package/man/mend-service-init.1
CHANGED
package/man/mend-service-list.1
CHANGED
package/man/mend-service-logs.1
CHANGED
package/man/mend-service-run.1
CHANGED
package/man/mend-service-stop.1
CHANGED
package/man/mend-sessions.1
CHANGED
package/man/mend-shell.1
CHANGED
package/man/mend-skills-push.1
CHANGED
package/man/mend-skills.1
CHANGED
package/man/mend-ssh-setup.1
CHANGED
package/man/mend-ssh.1
CHANGED
package/man/mend-stop.1
CHANGED
package/man/mend-ui.1
CHANGED
package/man/mend-uninstall.1
CHANGED
package/man/mend-version.1
CHANGED
package/man/mend-worktrees.1
CHANGED
package/man/mend.1
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
.TH MEND 1 "" "mend 0.
|
|
1
|
+
.TH MEND 1 "" "mend 0.27.4" "mend manual"
|
|
2
2
|
.SH NAME
|
|
3
3
|
mend \- the agent workbench
|
|
4
4
|
.SH SYNOPSIS
|
|
@@ -137,7 +137,7 @@ show the saved pin, generation and container state
|
|
|
137
137
|
start the selected server generation
|
|
138
138
|
.TP
|
|
139
139
|
.B mend server stop
|
|
140
|
-
stop Mend and
|
|
140
|
+
stop Mend, Postgres and Garage without deleting data
|
|
141
141
|
.TP
|
|
142
142
|
.B mend server restart
|
|
143
143
|
restart Mend using the same generation and pin
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sealant/mend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.27.4",
|
|
4
4
|
"description": "The mend CLI: adopt a repository into Mend's store, launch your coding agent in a recorded session worktree, detach and reattach from any terminal",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude-code",
|
|
@@ -48,8 +48,8 @@
|
|
|
48
48
|
"typescript": "^5.7.2",
|
|
49
49
|
"vitest": "^3.0.5",
|
|
50
50
|
"@mend/domain": "0.0.0",
|
|
51
|
-
"@mend/
|
|
52
|
-
"@mend/
|
|
51
|
+
"@mend/workspace-ssh": "0.0.0",
|
|
52
|
+
"@mend/typescript": "0.0.0"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
55
55
|
"node": ">=22"
|