@sealant/mend 0.25.0 → 0.26.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.
- package/dist/main.js +453 -39
- 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 +1 -1
- 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 +1 -1
- 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 +35 -0
- package/man/mend-version.1 +1 -1
- package/man/mend-worktrees.1 +1 -1
- package/man/mend.1 +5 -2
- 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
|
|
24
|
+
import * as fs9 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
|
|
28
|
+
import * as path10 from "node:path";
|
|
29
29
|
|
|
30
30
|
// src/agent-share.ts
|
|
31
31
|
import * as net from "node:net";
|
|
@@ -1213,6 +1213,30 @@ var COMMANDS = [
|
|
|
1213
1213
|
description: ["Other terminals and devices keep their own tokens."],
|
|
1214
1214
|
see: ["login"]
|
|
1215
1215
|
},
|
|
1216
|
+
{
|
|
1217
|
+
name: "uninstall",
|
|
1218
|
+
section: "this machine",
|
|
1219
|
+
summary: "remove the server, this machine's Mend files, or both",
|
|
1220
|
+
synopsis: ["[--all | --server | --home] [--yes]"],
|
|
1221
|
+
description: [
|
|
1222
|
+
"Asks which scope to remove when none is given, prints exactly what will go, and requires the word delete before the server is touched. The server scope removes the local Compose installation: its containers, every volume it owns (repositories, worktrees, the database), its release image, and the private configuration with its generations and backups. The home scope revokes this terminal's device token, then removes cli.json, the workspace SSH key, and the managed block in ~/.ssh/config.",
|
|
1223
|
+
"Nothing else under the configuration directory is touched; a host-run store or keys root is listed and left in place. Workspace containers carry no label Mend can filter on, so they are named with the command that removes them, never removed."
|
|
1224
|
+
],
|
|
1225
|
+
options: [
|
|
1226
|
+
{ flag: "--all", text: "the server and this machine's Mend files" },
|
|
1227
|
+
{ flag: "--server", text: "only the local server installation" },
|
|
1228
|
+
{ flag: "--home", text: "only this machine's sign-in, ssh key and config block" },
|
|
1229
|
+
{ flag: "--yes", text: "skip the confirmation; required without a terminal" }
|
|
1230
|
+
],
|
|
1231
|
+
examples: [
|
|
1232
|
+
{ command: "mend uninstall", text: "choose a scope, then confirm" },
|
|
1233
|
+
{
|
|
1234
|
+
command: "mend uninstall --home --yes",
|
|
1235
|
+
text: "forget this machine's sign-in without asking"
|
|
1236
|
+
}
|
|
1237
|
+
],
|
|
1238
|
+
see: ["server stop", "logout"]
|
|
1239
|
+
},
|
|
1216
1240
|
{
|
|
1217
1241
|
name: "completions",
|
|
1218
1242
|
section: "this machine",
|
|
@@ -4009,6 +4033,26 @@ var removeManagedBlock = (config, beginMarker, endMarker) => {
|
|
|
4009
4033
|
}
|
|
4010
4034
|
return success(`${config.slice(0, begin)}${after}`);
|
|
4011
4035
|
};
|
|
4036
|
+
var stripManagedWorkspaceSshBlocks = (existing) => {
|
|
4037
|
+
let config = existing;
|
|
4038
|
+
let removed = 0;
|
|
4039
|
+
const aliases = [
|
|
4040
|
+
...config.matchAll(/^# >>> mend workspace ssh (mend-ws-[a-z0-9-]+) \(managed\) >>>$/gm)
|
|
4041
|
+
].map((match) => match[1]).filter((alias) => alias !== void 0);
|
|
4042
|
+
for (const alias of new Set(aliases)) {
|
|
4043
|
+
const stripped = removeManagedBlock(config, blockBegin(alias), blockEnd(alias));
|
|
4044
|
+
if (!stripped.ok) return stripped;
|
|
4045
|
+
config = stripped.value;
|
|
4046
|
+
removed += 1;
|
|
4047
|
+
}
|
|
4048
|
+
if (config.includes(LEGACY_BLOCK_BEGIN)) {
|
|
4049
|
+
const stripped = removeManagedBlock(config, LEGACY_BLOCK_BEGIN, LEGACY_BLOCK_END);
|
|
4050
|
+
if (!stripped.ok) return stripped;
|
|
4051
|
+
config = stripped.value;
|
|
4052
|
+
removed += 1;
|
|
4053
|
+
}
|
|
4054
|
+
return success({ config, removed });
|
|
4055
|
+
};
|
|
4012
4056
|
var containsExactHost = (config, alias) => config.split("\n").some((line) => {
|
|
4013
4057
|
const match = /^\s*Host\s+(.+?)\s*$/i.exec(line);
|
|
4014
4058
|
return match?.[1]?.split(/\s+/).includes(alias) === true;
|
|
@@ -4353,22 +4397,315 @@ var sshCommand = async (args, api2, cliHome, serverUrl) => {
|
|
|
4353
4397
|
}
|
|
4354
4398
|
};
|
|
4355
4399
|
|
|
4400
|
+
// src/uninstall.ts
|
|
4401
|
+
import * as fs8 from "node:fs";
|
|
4402
|
+
import * as path9 from "node:path";
|
|
4403
|
+
var UNINSTALL_USAGE = "usage: mend uninstall [--all | --server | --home] [--yes]";
|
|
4404
|
+
var parseUninstallArgs = (args) => {
|
|
4405
|
+
let scope = null;
|
|
4406
|
+
let yes = false;
|
|
4407
|
+
for (const arg of args) {
|
|
4408
|
+
if (arg === "--yes" || arg === "-y") {
|
|
4409
|
+
yes = true;
|
|
4410
|
+
continue;
|
|
4411
|
+
}
|
|
4412
|
+
const next = arg === "--all" ? "all" : arg === "--server" ? "server" : arg === "--home" ? "home" : null;
|
|
4413
|
+
if (next === null) return { error: `${UNINSTALL_USAGE} \xB7 "${arg}" is not an option` };
|
|
4414
|
+
if (scope !== null && scope !== next) {
|
|
4415
|
+
return { error: `${UNINSTALL_USAGE} \xB7 pick one scope` };
|
|
4416
|
+
}
|
|
4417
|
+
scope = next;
|
|
4418
|
+
}
|
|
4419
|
+
return { scope, yes };
|
|
4420
|
+
};
|
|
4421
|
+
var SERVER_FILES = ["identity.env", "active", "generations", "backups"];
|
|
4422
|
+
var countEntries = (directory) => {
|
|
4423
|
+
try {
|
|
4424
|
+
return fs8.readdirSync(directory).length;
|
|
4425
|
+
} catch {
|
|
4426
|
+
return 0;
|
|
4427
|
+
}
|
|
4428
|
+
};
|
|
4429
|
+
var exists = (file) => {
|
|
4430
|
+
try {
|
|
4431
|
+
fs8.lstatSync(file);
|
|
4432
|
+
return true;
|
|
4433
|
+
} catch {
|
|
4434
|
+
return false;
|
|
4435
|
+
}
|
|
4436
|
+
};
|
|
4437
|
+
var managedBlockCount = (sshConfigFile) => {
|
|
4438
|
+
let config;
|
|
4439
|
+
try {
|
|
4440
|
+
config = fs8.readFileSync(sshConfigFile, "utf8");
|
|
4441
|
+
} catch {
|
|
4442
|
+
return 0;
|
|
4443
|
+
}
|
|
4444
|
+
const stripped = stripManagedWorkspaceSshBlocks(config);
|
|
4445
|
+
return stripped.ok ? stripped.value.removed : 0;
|
|
4446
|
+
};
|
|
4447
|
+
var describeUninstall = async (runtime, scope) => {
|
|
4448
|
+
let server = null;
|
|
4449
|
+
if (scope !== "home") {
|
|
4450
|
+
const configDir = runtime.server.configDir;
|
|
4451
|
+
const read = await withServerStore(
|
|
4452
|
+
configDir,
|
|
4453
|
+
async (store) => {
|
|
4454
|
+
const installation = readServerInstallation(store);
|
|
4455
|
+
if (installation._tag === "error") throw installation.error;
|
|
4456
|
+
return installation.value;
|
|
4457
|
+
},
|
|
4458
|
+
{ create: false }
|
|
4459
|
+
);
|
|
4460
|
+
if (read._tag === "error") {
|
|
4461
|
+
if (!read.error.message.startsWith("No Mend server is configured")) throw read.error;
|
|
4462
|
+
server = "none";
|
|
4463
|
+
} else if (read.value === null) {
|
|
4464
|
+
server = exists(path9.join(configDir, "identity.env")) ? {
|
|
4465
|
+
version: "(no active generation)",
|
|
4466
|
+
appUrl: "",
|
|
4467
|
+
dockerContext: "",
|
|
4468
|
+
generations: countEntries(path9.join(configDir, "generations")),
|
|
4469
|
+
backups: countEntries(path9.join(configDir, "backups"))
|
|
4470
|
+
} : "none";
|
|
4471
|
+
} else {
|
|
4472
|
+
server = {
|
|
4473
|
+
version: read.value.config.serverVersion,
|
|
4474
|
+
appUrl: read.value.config.appUrl,
|
|
4475
|
+
dockerContext: read.value.config.dockerContext,
|
|
4476
|
+
generations: countEntries(path9.join(configDir, "generations")),
|
|
4477
|
+
backups: countEntries(path9.join(configDir, "backups"))
|
|
4478
|
+
};
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
let home = null;
|
|
4482
|
+
if (scope !== "server") {
|
|
4483
|
+
const cliConfig = path9.join(runtime.cliHome, "cli.json");
|
|
4484
|
+
const sshDirectory = path9.join(runtime.cliHome, "ssh");
|
|
4485
|
+
home = {
|
|
4486
|
+
cliConfig: exists(cliConfig) ? cliConfig : null,
|
|
4487
|
+
sshDirectory: exists(sshDirectory) ? sshDirectory : null,
|
|
4488
|
+
managedSshBlocks: managedBlockCount(runtime.sshConfigFile),
|
|
4489
|
+
signedIn: runtime.signedIn
|
|
4490
|
+
};
|
|
4491
|
+
}
|
|
4492
|
+
return { scope, server, home };
|
|
4493
|
+
};
|
|
4494
|
+
var plural = (count, noun) => `${count} ${noun}${count === 1 ? "" : "s"}`;
|
|
4495
|
+
var planLines = (plan, configDir) => {
|
|
4496
|
+
const lines = [];
|
|
4497
|
+
if (plan.server === "none") {
|
|
4498
|
+
lines.push(`server none installed under ${configDir}`);
|
|
4499
|
+
} else if (plan.server !== null) {
|
|
4500
|
+
const { version, appUrl, dockerContext, generations, backups } = plan.server;
|
|
4501
|
+
lines.push(
|
|
4502
|
+
`server Mend ${version}${appUrl === "" ? "" : ` at ${appUrl}`}${dockerContext === "" ? "" : ` \xB7 docker context ${dockerContext}`}`
|
|
4503
|
+
);
|
|
4504
|
+
if (dockerContext !== "") {
|
|
4505
|
+
lines.push(
|
|
4506
|
+
` containers mend, postgres \xB7 volumes ${[MEND_DOCKER_NAMESPACE.store, MEND_DOCKER_NAMESPACE.control, "mend-config", "mend-ssh", "mend-postgres"].join(", ")} \xB7 image ghcr.io/sealant-sh/mend:${version}`
|
|
4507
|
+
);
|
|
4508
|
+
}
|
|
4509
|
+
lines.push(
|
|
4510
|
+
` ${configDir}: identity.env, active, ${plural(generations, "generation")}, ${plural(backups, "backup")}`
|
|
4511
|
+
);
|
|
4512
|
+
}
|
|
4513
|
+
if (plan.home !== null) {
|
|
4514
|
+
const parts = [];
|
|
4515
|
+
if (plan.home.cliConfig !== null) {
|
|
4516
|
+
parts.push(
|
|
4517
|
+
plan.home.signedIn === null ? plan.home.cliConfig : `${plan.home.cliConfig} (signed in to ${plan.home.signedIn.url})`
|
|
4518
|
+
);
|
|
4519
|
+
}
|
|
4520
|
+
if (plan.home.sshDirectory !== null) parts.push(plan.home.sshDirectory);
|
|
4521
|
+
if (plan.home.managedSshBlocks > 0) {
|
|
4522
|
+
parts.push(`${plural(plan.home.managedSshBlocks, "managed block")} in ~/.ssh/config`);
|
|
4523
|
+
}
|
|
4524
|
+
lines.push(parts.length === 0 ? "home nothing of Mend's here" : `home ${parts[0]}`);
|
|
4525
|
+
for (const part of parts.slice(1)) lines.push(` ${part}`);
|
|
4526
|
+
}
|
|
4527
|
+
return lines;
|
|
4528
|
+
};
|
|
4529
|
+
var planDeletesData = (plan) => plan.server !== null && plan.server !== "none";
|
|
4530
|
+
var dockerArgs = (context, ...rest) => [
|
|
4531
|
+
"--context",
|
|
4532
|
+
context,
|
|
4533
|
+
...rest
|
|
4534
|
+
];
|
|
4535
|
+
var removeServer = async (runtime, failures, leftovers) => {
|
|
4536
|
+
const { server } = runtime;
|
|
4537
|
+
const configDir = server.configDir;
|
|
4538
|
+
const result = await withServerStore(
|
|
4539
|
+
configDir,
|
|
4540
|
+
async (store) => {
|
|
4541
|
+
const read = readServerInstallation(store);
|
|
4542
|
+
if (read._tag === "error") throw read.error;
|
|
4543
|
+
const installation = read.value;
|
|
4544
|
+
if (installation !== null) {
|
|
4545
|
+
const context = installation.config.dockerContext;
|
|
4546
|
+
const down = await server.run(
|
|
4547
|
+
"docker",
|
|
4548
|
+
serverComposeArgs({ directory: installation.directory, dockerContext: context }, [
|
|
4549
|
+
"down",
|
|
4550
|
+
"--volumes",
|
|
4551
|
+
"--remove-orphans",
|
|
4552
|
+
"--timeout",
|
|
4553
|
+
"30"
|
|
4554
|
+
]),
|
|
4555
|
+
{ timeoutMs: serverProcessDeadlines.stop }
|
|
4556
|
+
);
|
|
4557
|
+
if (down.status !== 0) {
|
|
4558
|
+
throw new Error(
|
|
4559
|
+
`docker compose down failed: ${(down.error ?? down.stderr.trim()) || "no output"}. Containers and files are retained; fix Docker and run mend uninstall again.`
|
|
4560
|
+
);
|
|
4561
|
+
}
|
|
4562
|
+
server.writeLine("removed containers mend, postgres and the Compose-owned volumes");
|
|
4563
|
+
const identity = store.readIdentity();
|
|
4564
|
+
if (identity._tag === "error") throw identity.error;
|
|
4565
|
+
const owned = identity.value === null ? null : await verifyServerDockerVolumes(server, {
|
|
4566
|
+
dockerContext: context,
|
|
4567
|
+
identityBytes: Buffer.from(identity.value)
|
|
4568
|
+
});
|
|
4569
|
+
if (owned !== null && owned._tag === "ok") {
|
|
4570
|
+
const removed = await server.run(
|
|
4571
|
+
"docker",
|
|
4572
|
+
dockerArgs(
|
|
4573
|
+
context,
|
|
4574
|
+
"volume",
|
|
4575
|
+
"rm",
|
|
4576
|
+
MEND_DOCKER_NAMESPACE.store,
|
|
4577
|
+
MEND_DOCKER_NAMESPACE.control
|
|
4578
|
+
)
|
|
4579
|
+
);
|
|
4580
|
+
if (removed.status === 0) {
|
|
4581
|
+
server.writeLine(
|
|
4582
|
+
`removed volumes ${MEND_DOCKER_NAMESPACE.store}, ${MEND_DOCKER_NAMESPACE.control}`
|
|
4583
|
+
);
|
|
4584
|
+
} else {
|
|
4585
|
+
failures.push(
|
|
4586
|
+
`could not remove volumes ${MEND_DOCKER_NAMESPACE.store}, ${MEND_DOCKER_NAMESPACE.control}: ${(removed.error ?? removed.stderr.trim()) || "no output"}`
|
|
4587
|
+
);
|
|
4588
|
+
}
|
|
4589
|
+
} 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
|
+
);
|
|
4593
|
+
} else {
|
|
4594
|
+
leftovers.push(
|
|
4595
|
+
`volumes ${MEND_DOCKER_NAMESPACE.store}, ${MEND_DOCKER_NAMESPACE.control}: ownership could not be confirmed for this installation, so they stay (docker --context ${context} volume ls)`
|
|
4596
|
+
);
|
|
4597
|
+
}
|
|
4598
|
+
const image = `ghcr.io/sealant-sh/mend:${installation.config.serverVersion}`;
|
|
4599
|
+
const untagged = await server.run("docker", dockerArgs(context, "image", "rm", image));
|
|
4600
|
+
if (untagged.status === 0) server.writeLine(`removed image ${image}`);
|
|
4601
|
+
else leftovers.push(`image ${image}: not removed (${untagged.stderr.trim() || "absent"})`);
|
|
4602
|
+
const workspaces = await server.run(
|
|
4603
|
+
"docker",
|
|
4604
|
+
dockerArgs(
|
|
4605
|
+
context,
|
|
4606
|
+
"container",
|
|
4607
|
+
"ls",
|
|
4608
|
+
"--all",
|
|
4609
|
+
"--filter",
|
|
4610
|
+
"name=^sealant-",
|
|
4611
|
+
"--format",
|
|
4612
|
+
"{{.Names}}"
|
|
4613
|
+
)
|
|
4614
|
+
);
|
|
4615
|
+
const names = workspaces.stdout.split(/\s+/).filter((name) => name !== "");
|
|
4616
|
+
if (workspaces.status === 0 && names.length > 0) {
|
|
4617
|
+
leftovers.push(
|
|
4618
|
+
`${plural(names.length, "workspace container")} left on docker context ${context} (${names.slice(0, 3).join(", ")}${names.length > 3 ? ", \u2026" : ""}): docker --context ${context} rm -f ${names.join(" ")}`
|
|
4619
|
+
);
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
for (const name of SERVER_FILES) {
|
|
4623
|
+
fs8.rmSync(path9.join(configDir, name), { recursive: true, force: true });
|
|
4624
|
+
}
|
|
4625
|
+
server.writeLine(`removed ${configDir}/{identity.env, active, generations, backups}`);
|
|
4626
|
+
},
|
|
4627
|
+
{ create: false }
|
|
4628
|
+
);
|
|
4629
|
+
if (result._tag === "error") failures.push(result.error.message);
|
|
4630
|
+
};
|
|
4631
|
+
var removeHome = async (runtime, plan, failures, leftovers) => {
|
|
4632
|
+
const { server } = runtime;
|
|
4633
|
+
if (plan.cliConfig !== null) {
|
|
4634
|
+
fs8.rmSync(plan.cliConfig, { force: true });
|
|
4635
|
+
server.writeLine(`removed ${plan.cliConfig}`);
|
|
4636
|
+
}
|
|
4637
|
+
if (plan.sshDirectory !== null) {
|
|
4638
|
+
fs8.rmSync(plan.sshDirectory, { recursive: true, force: true });
|
|
4639
|
+
server.writeLine(`removed ${plan.sshDirectory}`);
|
|
4640
|
+
}
|
|
4641
|
+
if (plan.managedSshBlocks > 0) {
|
|
4642
|
+
try {
|
|
4643
|
+
const before = fs8.readFileSync(runtime.sshConfigFile, "utf8");
|
|
4644
|
+
const stripped = stripManagedWorkspaceSshBlocks(before);
|
|
4645
|
+
if (!stripped.ok) throw stripped.error;
|
|
4646
|
+
if (stripped.value.removed > 0) {
|
|
4647
|
+
fs8.writeFileSync(runtime.sshConfigFile, stripped.value.config, { mode: 384 });
|
|
4648
|
+
server.writeLine(
|
|
4649
|
+
`removed ${plural(stripped.value.removed, "managed block")} from ${runtime.sshConfigFile}`
|
|
4650
|
+
);
|
|
4651
|
+
}
|
|
4652
|
+
} catch (cause) {
|
|
4653
|
+
failures.push(
|
|
4654
|
+
`${runtime.sshConfigFile}: ${cause instanceof Error ? cause.message : String(cause)}`
|
|
4655
|
+
);
|
|
4656
|
+
}
|
|
4657
|
+
}
|
|
4658
|
+
let remaining = [];
|
|
4659
|
+
try {
|
|
4660
|
+
remaining = fs8.readdirSync(runtime.cliHome);
|
|
4661
|
+
} catch {
|
|
4662
|
+
return;
|
|
4663
|
+
}
|
|
4664
|
+
if (remaining.length === 0) {
|
|
4665
|
+
fs8.rmdirSync(runtime.cliHome);
|
|
4666
|
+
server.writeLine(`removed ${runtime.cliHome}`);
|
|
4667
|
+
} else {
|
|
4668
|
+
leftovers.push(
|
|
4669
|
+
`${runtime.cliHome} kept: ${remaining.join(", ")} ${remaining.length === 1 ? "is" : "are"} not this CLI's (a host-run server's store or keys, or another tool's files)`
|
|
4670
|
+
);
|
|
4671
|
+
}
|
|
4672
|
+
};
|
|
4673
|
+
var executeUninstall = async (runtime, plan) => {
|
|
4674
|
+
const failures = [];
|
|
4675
|
+
const leftovers = [];
|
|
4676
|
+
if (plan.home?.signedIn !== null && plan.home?.signedIn?.deviceId != null) {
|
|
4677
|
+
const failure2 = await runtime.revokeDevice();
|
|
4678
|
+
if (failure2 === null) {
|
|
4679
|
+
runtime.server.writeLine(`revoked this terminal's device on ${plan.home.signedIn.url}`);
|
|
4680
|
+
} else {
|
|
4681
|
+
leftovers.push(
|
|
4682
|
+
`device token on ${plan.home.signedIn.url}: ${failure2} (end it under Settings \u2192 Devices)`
|
|
4683
|
+
);
|
|
4684
|
+
}
|
|
4685
|
+
}
|
|
4686
|
+
if (plan.server !== null && plan.server !== "none") {
|
|
4687
|
+
await removeServer(runtime, failures, leftovers);
|
|
4688
|
+
}
|
|
4689
|
+
if (plan.home !== null) await removeHome(runtime, plan.home, failures, leftovers);
|
|
4690
|
+
return { failures, leftovers };
|
|
4691
|
+
};
|
|
4692
|
+
|
|
4356
4693
|
// src/main.ts
|
|
4357
4694
|
var mendCliHome = () => {
|
|
4358
4695
|
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
4359
|
-
const preferred =
|
|
4360
|
-
xdg === void 0 || xdg === "" ?
|
|
4696
|
+
const preferred = path10.join(
|
|
4697
|
+
xdg === void 0 || xdg === "" ? path10.join(os6.homedir(), ".config") : xdg,
|
|
4361
4698
|
"mend"
|
|
4362
4699
|
);
|
|
4363
|
-
const legacy =
|
|
4364
|
-
return !
|
|
4700
|
+
const legacy = path10.join(os6.homedir(), ".mend");
|
|
4701
|
+
return !fs9.existsSync(preferred) && fs9.existsSync(legacy) ? legacy : preferred;
|
|
4365
4702
|
};
|
|
4366
|
-
var CONFIG_PATH =
|
|
4703
|
+
var CONFIG_PATH = path10.join(mendCliHome(), "cli.json");
|
|
4367
4704
|
var loadConfig = () => {
|
|
4368
4705
|
let fileConfig = {};
|
|
4369
|
-
if (
|
|
4706
|
+
if (fs9.existsSync(CONFIG_PATH)) {
|
|
4370
4707
|
try {
|
|
4371
|
-
fileConfig = JSON.parse(
|
|
4708
|
+
fileConfig = JSON.parse(fs9.readFileSync(CONFIG_PATH, "utf8"));
|
|
4372
4709
|
} catch {
|
|
4373
4710
|
fail4(`could not parse ${CONFIG_PATH}`);
|
|
4374
4711
|
}
|
|
@@ -4505,7 +4842,7 @@ var adopt = async (config, args) => {
|
|
|
4505
4842
|
}
|
|
4506
4843
|
const sourceIssue = repositoryCloneUrlIssue(source);
|
|
4507
4844
|
if (sourceIssue !== null) return fail4(sourceIssue);
|
|
4508
|
-
const projectName = name ?? normalizeProjectName(
|
|
4845
|
+
const projectName = name ?? normalizeProjectName(path10.basename(source, ".git"));
|
|
4509
4846
|
const auth = authFlagIndex !== -1 && args[authFlagIndex + 1] !== void 0 ? String(args[authFlagIndex + 1]) : null;
|
|
4510
4847
|
if (auth !== null && auth !== "ambient" && auth !== "mend-key" && auth !== "bridge") {
|
|
4511
4848
|
return fail4(`--auth takes "ambient", "mend-key", or "bridge", not "${auth}"`);
|
|
@@ -4550,7 +4887,7 @@ var findProject = async (config, explicit, adoptCwd = false) => {
|
|
|
4550
4887
|
const sourceIssue = repositoryCloneUrlIssue(facts.originUrl);
|
|
4551
4888
|
if (sourceIssue !== null) return fail4(sourceIssue);
|
|
4552
4889
|
const adopted = await api(config, "POST", "/projects", {
|
|
4553
|
-
name: normalizeProjectName(
|
|
4890
|
+
name: normalizeProjectName(path10.basename(facts.repoRoot)),
|
|
4554
4891
|
source: facts.originUrl
|
|
4555
4892
|
});
|
|
4556
4893
|
say4(`${green4("\u2713")} adopted \xB7 ${adopted.name} \xB7 ${dim4(adopted.storePath)}`);
|
|
@@ -5341,13 +5678,13 @@ var serviceRestart = async (config, args) => {
|
|
|
5341
5678
|
};
|
|
5342
5679
|
var serviceInit = async (args) => {
|
|
5343
5680
|
const root = gitTopLevel(process.cwd()) ?? process.cwd();
|
|
5344
|
-
const target =
|
|
5345
|
-
if (
|
|
5681
|
+
const target = path10.join(root, "mend.toml");
|
|
5682
|
+
if (fs9.existsSync(target)) {
|
|
5346
5683
|
return fail4(`${target} already exists \u2014 edit it directly (init never merges)`);
|
|
5347
5684
|
}
|
|
5348
|
-
const rootFiles =
|
|
5685
|
+
const rootFiles = fs9.readdirSync(root);
|
|
5349
5686
|
const proposals = [];
|
|
5350
|
-
const readRoot = (name) =>
|
|
5687
|
+
const readRoot = (name) => fs9.readFileSync(path10.join(root, name), "utf8");
|
|
5351
5688
|
if (rootFiles.includes("package.json")) {
|
|
5352
5689
|
proposals.push(...proposeFromPackageJson(readRoot("package.json"), rootFiles));
|
|
5353
5690
|
}
|
|
@@ -5357,13 +5694,13 @@ var serviceInit = async (args) => {
|
|
|
5357
5694
|
);
|
|
5358
5695
|
for (const glob of globs) {
|
|
5359
5696
|
const parent = glob.replace(/\/?\*+$/, "");
|
|
5360
|
-
const dirs = glob.endsWith("*") ?
|
|
5697
|
+
const dirs = glob.endsWith("*") ? fs9.existsSync(path10.join(root, parent)) ? fs9.readdirSync(path10.join(root, parent)).map((dir) => path10.join(parent, dir)) : [] : [glob];
|
|
5361
5698
|
for (const dir of dirs) {
|
|
5362
|
-
const manifest =
|
|
5363
|
-
if (!
|
|
5699
|
+
const manifest = path10.join(root, dir, "package.json");
|
|
5700
|
+
if (!fs9.existsSync(manifest)) continue;
|
|
5364
5701
|
for (const proposal of proposeFromWorkspacePackage(
|
|
5365
|
-
|
|
5366
|
-
|
|
5702
|
+
path10.basename(dir),
|
|
5703
|
+
fs9.readFileSync(manifest, "utf8"),
|
|
5367
5704
|
rootFiles
|
|
5368
5705
|
)) {
|
|
5369
5706
|
if (!proposals.some((existing) => existing.name === proposal.name)) {
|
|
@@ -5407,7 +5744,7 @@ var serviceInit = async (args) => {
|
|
|
5407
5744
|
return;
|
|
5408
5745
|
}
|
|
5409
5746
|
}
|
|
5410
|
-
|
|
5747
|
+
fs9.writeFileSync(target, toml);
|
|
5411
5748
|
say4(`${green4("\u2713")} wrote ${target} \u2014 commit it, then: mend service run ${proposals[0]?.name}`);
|
|
5412
5749
|
};
|
|
5413
5750
|
var tunnelServices = async (config, services, portOverride) => {
|
|
@@ -5500,18 +5837,18 @@ var serviceCommand = async (config, args) => {
|
|
|
5500
5837
|
}
|
|
5501
5838
|
};
|
|
5502
5839
|
var isProvider = (value) => value === "claude" || value === "codex" || value === "github";
|
|
5503
|
-
var readIfExists = (file) =>
|
|
5840
|
+
var readIfExists = (file) => fs9.existsSync(file) ? fs9.readFileSync(file, "utf8") : null;
|
|
5504
5841
|
var localCredential = (provider) => {
|
|
5505
5842
|
const home = os6.homedir();
|
|
5506
5843
|
switch (provider) {
|
|
5507
5844
|
case "codex":
|
|
5508
5845
|
return readIfExists(
|
|
5509
|
-
|
|
5846
|
+
path10.join(process.env["CODEX_HOME"] ?? path10.join(home, ".codex"), "auth.json")
|
|
5510
5847
|
);
|
|
5511
5848
|
case "claude":
|
|
5512
5849
|
return readIfExists(
|
|
5513
|
-
|
|
5514
|
-
process.env["CLAUDE_CONFIG_DIR"] ??
|
|
5850
|
+
path10.join(
|
|
5851
|
+
process.env["CLAUDE_CONFIG_DIR"] ?? path10.join(home, ".claude"),
|
|
5515
5852
|
".credentials.json"
|
|
5516
5853
|
)
|
|
5517
5854
|
);
|
|
@@ -5565,7 +5902,7 @@ var connectCommand = async (config, args) => {
|
|
|
5565
5902
|
}
|
|
5566
5903
|
let secret;
|
|
5567
5904
|
if (flags.includes("--from-stdin")) {
|
|
5568
|
-
secret =
|
|
5905
|
+
secret = fs9.readFileSync(0, "utf8").trim();
|
|
5569
5906
|
if (secret === "") return fail4("nothing on stdin");
|
|
5570
5907
|
} else {
|
|
5571
5908
|
secret = localCredential(provider);
|
|
@@ -5586,14 +5923,14 @@ var takeFlagValue3 = (args, flag) => {
|
|
|
5586
5923
|
return at !== -1 && args[at + 1] !== void 0 ? String(args[at + 1]) : null;
|
|
5587
5924
|
};
|
|
5588
5925
|
var saveCliConfig = (next) => {
|
|
5589
|
-
|
|
5590
|
-
|
|
5926
|
+
fs9.mkdirSync(path10.dirname(CONFIG_PATH), { recursive: true, mode: 448 });
|
|
5927
|
+
fs9.writeFileSync(
|
|
5591
5928
|
CONFIG_PATH,
|
|
5592
5929
|
`${JSON.stringify({ url: next.url, token: next.token, deviceId: next.deviceId }, null, 2)}
|
|
5593
5930
|
`,
|
|
5594
5931
|
{ mode: 384 }
|
|
5595
5932
|
);
|
|
5596
|
-
|
|
5933
|
+
fs9.chmodSync(CONFIG_PATH, 384);
|
|
5597
5934
|
};
|
|
5598
5935
|
var login = async (config, args) => {
|
|
5599
5936
|
await loginCommand(args, {
|
|
@@ -5606,7 +5943,7 @@ var login = async (config, args) => {
|
|
|
5606
5943
|
});
|
|
5607
5944
|
};
|
|
5608
5945
|
var logout = async (config) => {
|
|
5609
|
-
if (!
|
|
5946
|
+
if (!fs9.existsSync(CONFIG_PATH) && config.token === null) {
|
|
5610
5947
|
say4(dim4("nothing saved \u2014 already signed out"));
|
|
5611
5948
|
return;
|
|
5612
5949
|
}
|
|
@@ -5621,6 +5958,80 @@ var logout = async (config) => {
|
|
|
5621
5958
|
saveCliConfig({ ...config, token: null, deviceId: null });
|
|
5622
5959
|
say4(`${green4("\u2713")} signed out \xB7 ${dim4(`token removed from ${CONFIG_PATH}`)}`);
|
|
5623
5960
|
};
|
|
5961
|
+
var UNINSTALL_CHOICES = [
|
|
5962
|
+
{ scope: "all", text: "everything \xB7 the server on this machine and this machine's Mend files" },
|
|
5963
|
+
{ scope: "server", text: "the server only \xB7 containers, volumes, configuration, backups" },
|
|
5964
|
+
{
|
|
5965
|
+
scope: "home",
|
|
5966
|
+
text: "this machine's files only \xB7 sign-in, workspace ssh key, ~/.ssh/config block"
|
|
5967
|
+
}
|
|
5968
|
+
];
|
|
5969
|
+
var askUninstallScope = async () => {
|
|
5970
|
+
if (process.stdin.isTTY !== true || process.stdout.isTTY !== true) {
|
|
5971
|
+
return fail4(`${UNINSTALL_USAGE} \xB7 no terminal to ask on`);
|
|
5972
|
+
}
|
|
5973
|
+
say4("what should go?");
|
|
5974
|
+
UNINSTALL_CHOICES.forEach((choice, index) => say4(` ${index + 1}. ${choice.text}`));
|
|
5975
|
+
const readline = await import("node:readline/promises");
|
|
5976
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
5977
|
+
const answer = (await rl.question(" 1, 2 or 3: ")).trim();
|
|
5978
|
+
rl.close();
|
|
5979
|
+
const chosen = UNINSTALL_CHOICES[Number(answer) - 1];
|
|
5980
|
+
if (chosen === void 0) return fail4(`"${answer}" is not one of the choices`);
|
|
5981
|
+
return chosen.scope;
|
|
5982
|
+
};
|
|
5983
|
+
var confirmUninstall = async (word) => {
|
|
5984
|
+
if (process.stdin.isTTY !== true) return fail4("non-interactive \u2014 pass --yes to remove");
|
|
5985
|
+
const readline = await import("node:readline/promises");
|
|
5986
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
5987
|
+
const answer = (await rl.question(word === "y" ? "remove? [y/N] " : `type ${word} to remove: `)).trim().toLowerCase();
|
|
5988
|
+
rl.close();
|
|
5989
|
+
return answer === word;
|
|
5990
|
+
};
|
|
5991
|
+
var uninstallCommand = async (config, args) => {
|
|
5992
|
+
const parsed = parseUninstallArgs(args);
|
|
5993
|
+
if ("error" in parsed) return fail4(parsed.error);
|
|
5994
|
+
const scope = parsed.scope ?? await askUninstallScope();
|
|
5995
|
+
const server = nodeServerRuntime();
|
|
5996
|
+
const runtime = {
|
|
5997
|
+
server,
|
|
5998
|
+
cliHome: mendCliHome(),
|
|
5999
|
+
sshConfigFile: path10.join(os6.homedir(), ".ssh", "config"),
|
|
6000
|
+
signedIn: config.token === null || !fs9.existsSync(CONFIG_PATH) ? null : { url: config.url, deviceId: config.deviceId },
|
|
6001
|
+
revokeDevice: async () => {
|
|
6002
|
+
if (config.deviceId === null) return "no device id saved";
|
|
6003
|
+
try {
|
|
6004
|
+
await request(config, "DELETE", `/me/devices/${config.deviceId}`);
|
|
6005
|
+
return null;
|
|
6006
|
+
} catch (error) {
|
|
6007
|
+
return error instanceof Error ? error.message : String(error);
|
|
6008
|
+
}
|
|
6009
|
+
}
|
|
6010
|
+
};
|
|
6011
|
+
const plan = await describeUninstall(runtime, scope);
|
|
6012
|
+
say4(dim4(`mend uninstall \xB7 ${scope === "all" ? "everything" : scope}`));
|
|
6013
|
+
for (const line of planLines(plan, server.configDir)) say4(` ${line}`);
|
|
6014
|
+
const nothing = (plan.server === null || plan.server === "none") && (plan.home === null || plan.home.cliConfig === null && plan.home.sshDirectory === null && plan.home.managedSshBlocks === 0);
|
|
6015
|
+
if (nothing) {
|
|
6016
|
+
say4(dim4("nothing to remove"));
|
|
6017
|
+
return;
|
|
6018
|
+
}
|
|
6019
|
+
if (planDeletesData(plan)) {
|
|
6020
|
+
say4("repositories, worktrees, the database and its backups are deleted with the server");
|
|
6021
|
+
}
|
|
6022
|
+
if (!parsed.yes && !await confirmUninstall(planDeletesData(plan) ? "delete" : "y")) {
|
|
6023
|
+
say4(dim4("nothing removed"));
|
|
6024
|
+
return;
|
|
6025
|
+
}
|
|
6026
|
+
const outcome = await executeUninstall(runtime, plan);
|
|
6027
|
+
for (const line of outcome.leftovers) say4(dim4(` kept \xB7 ${line}`));
|
|
6028
|
+
if (outcome.failures.length > 0) {
|
|
6029
|
+
return fail4(outcome.failures.join("\n"));
|
|
6030
|
+
}
|
|
6031
|
+
say4(
|
|
6032
|
+
`${green4("\u2713")} ${scope === "all" ? "Mend is gone from this machine" : scope === "server" ? "the server is gone from this machine" : "this machine no longer holds Mend files"}`
|
|
6033
|
+
);
|
|
6034
|
+
};
|
|
5624
6035
|
var printGitKey = (key) => {
|
|
5625
6036
|
if (key.publicKey === null) return;
|
|
5626
6037
|
say4(key.publicKey);
|
|
@@ -5795,10 +6206,10 @@ var envLoad = async (config, args) => {
|
|
|
5795
6206
|
const positional = args.filter(
|
|
5796
6207
|
(arg, i) => !arg.startsWith("--") && args[i - 1] !== "--project" && !consumed.has(i)
|
|
5797
6208
|
);
|
|
5798
|
-
const file =
|
|
6209
|
+
const file = path10.resolve(positional[0] ?? ".env");
|
|
5799
6210
|
let contents;
|
|
5800
6211
|
try {
|
|
5801
|
-
contents =
|
|
6212
|
+
contents = fs9.readFileSync(file, "utf8");
|
|
5802
6213
|
} catch {
|
|
5803
6214
|
return fail4(`cannot read ${file} \u2014 pass a path: mend env load path/to/.env`);
|
|
5804
6215
|
}
|
|
@@ -5814,10 +6225,10 @@ var envLoad = async (config, args) => {
|
|
|
5814
6225
|
{ contents, allSecret, secretNames }
|
|
5815
6226
|
);
|
|
5816
6227
|
if (report.loaded.length === 0 && report.rejected.length === 0 && report.malformedLines.length === 0) {
|
|
5817
|
-
say4(dim4(`${
|
|
6228
|
+
say4(dim4(`${path10.basename(file)} has no variables`));
|
|
5818
6229
|
return;
|
|
5819
6230
|
}
|
|
5820
|
-
say4(`${green4("\u2713")} loaded ${
|
|
6231
|
+
say4(`${green4("\u2713")} loaded ${path10.basename(file)} into ${project2.name}`);
|
|
5821
6232
|
for (const line of formatLoadReport(report, { dim: dim4, warn: amber })) say4(line);
|
|
5822
6233
|
const plaintextUrls = report.loaded.filter(
|
|
5823
6234
|
(entry) => entry.lane === "configuration" && /_(URL|URI|DSN)$/.test(entry.name)
|
|
@@ -6006,7 +6417,7 @@ var skillsList = async (config, args) => {
|
|
|
6006
6417
|
};
|
|
6007
6418
|
var skillsPush = async (config, args) => {
|
|
6008
6419
|
const dirFlag = takeFlagValue3(args, "--dir");
|
|
6009
|
-
const root = dirFlag === null ?
|
|
6420
|
+
const root = dirFlag === null ? path10.join(os6.homedir(), DEFAULT_SKILLS_DIR) : path10.resolve(dirFlag);
|
|
6010
6421
|
const scanned = scanSkillLibrary(root);
|
|
6011
6422
|
if ("error" in scanned) return fail4(scanned.error);
|
|
6012
6423
|
for (const note of scanned.notes) say4(dim4(` ${note.skill}: ${note.message}`));
|
|
@@ -6076,6 +6487,7 @@ _mend() {
|
|
|
6076
6487
|
'shell:open a shell in a live session workspace'
|
|
6077
6488
|
'service:reachable ports \u2014 add, list, stop'
|
|
6078
6489
|
'server:local server setup, lifecycle and upgrades'
|
|
6490
|
+
'uninstall:remove the server, local Mend files, or both'
|
|
6079
6491
|
'keys:the machine Mend deploy key \u2014 init, show, share'
|
|
6080
6492
|
'skills:skill libraries \u2014 list, push'
|
|
6081
6493
|
'accounts:your connected accounts on the platform'
|
|
@@ -6115,7 +6527,7 @@ _mend "$@"
|
|
|
6115
6527
|
var BASH_COMPLETIONS = `_mend() {
|
|
6116
6528
|
local cur=\${COMP_WORDS[COMP_CWORD]}
|
|
6117
6529
|
if [ "$COMP_CWORD" -eq 1 ]; then
|
|
6118
|
-
COMPREPLY=( $(compgen -W "adopt codex claude opencode run attach stop shell service server keys skills pair doctor continue resume rejoin refresh projects sessions status ui help" -- "$cur") )
|
|
6530
|
+
COMPREPLY=( $(compgen -W "adopt codex claude opencode run attach stop shell service server uninstall keys skills pair doctor continue resume rejoin refresh projects sessions status ui help" -- "$cur") )
|
|
6119
6531
|
return
|
|
6120
6532
|
fi
|
|
6121
6533
|
case \${COMP_WORDS[1]} in
|
|
@@ -6720,10 +7132,10 @@ var manCommand = (words) => {
|
|
|
6720
7132
|
}
|
|
6721
7133
|
const version = cliVersion();
|
|
6722
7134
|
const page = doc === null ? renderManIndex(version) : renderManPage(doc, version);
|
|
6723
|
-
const file =
|
|
6724
|
-
|
|
7135
|
+
const file = path10.join(os6.tmpdir(), `mend-man-${process.pid}-${manFileName(doc)}`);
|
|
7136
|
+
fs9.writeFileSync(file, page);
|
|
6725
7137
|
const result = spawnSync2("man", ["-l", file], { stdio: "inherit" });
|
|
6726
|
-
|
|
7138
|
+
fs9.rmSync(file, { force: true });
|
|
6727
7139
|
if (result.error !== void 0 || result.status !== 0) {
|
|
6728
7140
|
say4(doc === null ? renderIndex() : renderCommand(doc));
|
|
6729
7141
|
}
|
|
@@ -6760,6 +7172,8 @@ var main = async () => {
|
|
|
6760
7172
|
return login(config, rest);
|
|
6761
7173
|
case "logout":
|
|
6762
7174
|
return logout(config);
|
|
7175
|
+
case "uninstall":
|
|
7176
|
+
return uninstallCommand(config, rest);
|
|
6763
7177
|
case "keys":
|
|
6764
7178
|
return keysCommand(config, rest);
|
|
6765
7179
|
case "dotfiles":
|
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
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
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
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
.TH MEND-UNINSTALL 1 "" "mend 0.26.0" "mend manual"
|
|
2
|
+
.SH NAME
|
|
3
|
+
mend\-uninstall \- remove the server, this machine's Mend files, or both
|
|
4
|
+
.SH SYNOPSIS
|
|
5
|
+
.B mend uninstall [\-\-all | \-\-server | \-\-home] [\-\-yes]
|
|
6
|
+
.br
|
|
7
|
+
.SH DESCRIPTION
|
|
8
|
+
.PP
|
|
9
|
+
Asks which scope to remove when none is given, prints exactly what will go, and requires the word delete before the server is touched. The server scope removes the local Compose installation: its containers, every volume it owns (repositories, worktrees, the database), its release image, and the private configuration with its generations and backups. The home scope revokes this terminal's device token, then removes cli.json, the workspace SSH key, and the managed block in ~/.ssh/config.
|
|
10
|
+
.PP
|
|
11
|
+
Nothing else under the configuration directory is touched; a host\-run store or keys root is listed and left in place. Workspace containers carry no label Mend can filter on, so they are named with the command that removes them, never removed.
|
|
12
|
+
.SH OPTIONS
|
|
13
|
+
.TP
|
|
14
|
+
.B \-\-all
|
|
15
|
+
the server and this machine's Mend files
|
|
16
|
+
.TP
|
|
17
|
+
.B \-\-server
|
|
18
|
+
only the local server installation
|
|
19
|
+
.TP
|
|
20
|
+
.B \-\-home
|
|
21
|
+
only this machine's sign\-in, ssh key and config block
|
|
22
|
+
.TP
|
|
23
|
+
.B \-\-yes
|
|
24
|
+
skip the confirmation; required without a terminal
|
|
25
|
+
.SH EXAMPLES
|
|
26
|
+
.PP
|
|
27
|
+
.B mend uninstall
|
|
28
|
+
.br
|
|
29
|
+
choose a scope, then confirm
|
|
30
|
+
.PP
|
|
31
|
+
.B mend uninstall \-\-home \-\-yes
|
|
32
|
+
.br
|
|
33
|
+
forget this machine's sign\-in without asking
|
|
34
|
+
.SH SEE ALSO
|
|
35
|
+
mend(1), mend\-server\-stop(1), mend\-logout(1)
|
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.26.0" "mend manual"
|
|
2
2
|
.SH NAME
|
|
3
3
|
mend \- the agent workbench
|
|
4
4
|
.SH SYNOPSIS
|
|
@@ -160,6 +160,9 @@ your connected accounts on the platform
|
|
|
160
160
|
.B mend logout
|
|
161
161
|
revoke this terminal's device token and forget it
|
|
162
162
|
.TP
|
|
163
|
+
.B mend uninstall
|
|
164
|
+
remove the server, this machine's Mend files, or both
|
|
165
|
+
.TP
|
|
163
166
|
.B mend completions
|
|
164
167
|
print the TAB\-completion hook
|
|
165
168
|
.TP
|
|
@@ -189,4 +192,4 @@ url, token, and device id from mend login
|
|
|
189
192
|
.B ~/.config/mend/keys/
|
|
190
193
|
the deploy key from mend keys init
|
|
191
194
|
.SH SEE ALSO
|
|
192
|
-
mend\-login(1), mend\-connect(1), mend\-adopt(1), mend\-codex(1), mend\-pair(1), mend\-doctor(1), mend\-ui(1), mend\-attach(1), mend\-stop(1), mend\-shell(1), mend\-run(1), mend\-continue(1), mend\-resume(1), mend\-rejoin(1), mend\-sessions(1), mend\-worktrees(1), mend\-projects(1), mend\-refresh(1), mend\-service\-run(1), mend\-service\-add(1), mend\-service\-connect(1), mend\-service\-list(1), mend\-service\-logs(1), mend\-service\-restart(1), mend\-service\-stop(1), mend\-service\-init(1), mend\-env\-show(1), mend\-env\-load(1), mend\-env\-cluster(1), mend\-skills(1), mend\-skills\-push(1), mend\-dotfiles(1), mend\-dotfiles\-sync(1), mend\-keys\-init(1), mend\-keys\-show(1), mend\-keys\-mode(1), mend\-keys\-share(1), mend\-server(1), mend\-server\-setup(1), mend\-server\-status(1), mend\-server\-start(1), mend\-server\-stop(1), mend\-server\-restart(1), mend\-server\-logs(1), mend\-server\-upgrade(1), mend\-ssh(1), mend\-ssh\-setup(1), mend\-accounts(1), mend\-logout(1), mend\-completions(1), mend\-version(1), mend\-help(1), mend\-man(1)
|
|
195
|
+
mend\-login(1), mend\-connect(1), mend\-adopt(1), mend\-codex(1), mend\-pair(1), mend\-doctor(1), mend\-ui(1), mend\-attach(1), mend\-stop(1), mend\-shell(1), mend\-run(1), mend\-continue(1), mend\-resume(1), mend\-rejoin(1), mend\-sessions(1), mend\-worktrees(1), mend\-projects(1), mend\-refresh(1), mend\-service\-run(1), mend\-service\-add(1), mend\-service\-connect(1), mend\-service\-list(1), mend\-service\-logs(1), mend\-service\-restart(1), mend\-service\-stop(1), mend\-service\-init(1), mend\-env\-show(1), mend\-env\-load(1), mend\-env\-cluster(1), mend\-skills(1), mend\-skills\-push(1), mend\-dotfiles(1), mend\-dotfiles\-sync(1), mend\-keys\-init(1), mend\-keys\-show(1), mend\-keys\-mode(1), mend\-keys\-share(1), mend\-server(1), mend\-server\-setup(1), mend\-server\-status(1), mend\-server\-start(1), mend\-server\-stop(1), mend\-server\-restart(1), mend\-server\-logs(1), mend\-server\-upgrade(1), mend\-ssh(1), mend\-ssh\-setup(1), mend\-accounts(1), mend\-logout(1), mend\-uninstall(1), mend\-completions(1), mend\-version(1), mend\-help(1), mend\-man(1)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sealant/mend",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.26.0",
|
|
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/typescript": "0.0.0",
|
|
52
|
+
"@mend/workspace-ssh": "0.0.0"
|
|
53
53
|
},
|
|
54
54
|
"engines": {
|
|
55
55
|
"node": ">=22"
|