@sealant/mend 0.33.0 → 0.34.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -13
- package/dist/{chunk-GJFMD7XF.js → chunk-JJ66V76Y.js} +385 -87
- package/dist/{dashboard-NWHR7I5W.js → dashboard-OADX3PHI.js} +10 -1
- package/dist/main.js +599 -236
- 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 +2 -2
- package/man/mend-dotfiles-repo.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-folder-create.1 +1 -1
- package/man/mend-folder-list.1 +1 -1
- package/man/mend-folder-push.1 +1 -1
- package/man/mend-folder-rm.1 +1 -1
- package/man/mend-git-author.1 +1 -1
- package/man/mend-help.1 +1 -1
- package/man/mend-invite.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-land.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-members.1 +1 -1
- package/man/mend-operator-exposure.1 +1 -1
- package/man/mend-operator-gate.1 +1 -1
- package/man/mend-operator-grant-owner.1 +1 -1
- package/man/mend-operator-org-create.1 +1 -1
- package/man/mend-operator-org-invite-owner.1 +1 -1
- package/man/mend-operator-org-list.1 +1 -1
- package/man/mend-operator-org-rename.1 +1 -1
- package/man/mend-operator-reset-link.1 +1 -1
- package/man/mend-pair.1 +1 -1
- package/man/mend-projects.1 +1 -1
- package/man/mend-pull.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-session-share.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-snake.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 +1 -1
- package/package.json +3 -3
package/dist/main.js
CHANGED
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
LIVE_STATUSES,
|
|
5
5
|
agentIsLive,
|
|
6
6
|
agentOutcome,
|
|
7
|
+
captureLineOf,
|
|
7
8
|
claudeGrantFacts,
|
|
8
9
|
createSseParser,
|
|
9
10
|
cwdFacts,
|
|
@@ -27,15 +28,15 @@ import {
|
|
|
27
28
|
servicesHoldOf,
|
|
28
29
|
sessionDisplayName,
|
|
29
30
|
trackBracketedPaste
|
|
30
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-JJ66V76Y.js";
|
|
31
32
|
|
|
32
33
|
// src/main.ts
|
|
33
|
-
import { spawn as spawn3, spawnSync as
|
|
34
|
-
import * as
|
|
34
|
+
import { spawn as spawn3, spawnSync as spawnSync5 } from "node:child_process";
|
|
35
|
+
import * as fs15 from "node:fs";
|
|
35
36
|
import { createRequire } from "node:module";
|
|
36
37
|
import * as net4 from "node:net";
|
|
37
|
-
import * as
|
|
38
|
-
import * as
|
|
38
|
+
import * as os10 from "node:os";
|
|
39
|
+
import * as path16 from "node:path";
|
|
39
40
|
|
|
40
41
|
// src/agent-share.ts
|
|
41
42
|
import * as net from "node:net";
|
|
@@ -1139,7 +1140,7 @@ var COMMANDS = [
|
|
|
1139
1140
|
summary: "check this machine's setup, or bundle it for a bug report",
|
|
1140
1141
|
synopsis: ["", "--bundle [--out <path>] [--tail <n>]"],
|
|
1141
1142
|
description: [
|
|
1142
|
-
"Read-only. One line per fact: the server, the sign-in, the platform connection, the provider accounts, the git key. Each unfinished line ends with the command that fixes it.",
|
|
1143
|
+
"Read-only. One line per fact: the server, the sign-in, the platform connection, the provider accounts, the git key, and where docker is on PATH its daemon's shutdown-timeout against the capture grace. Each unfinished line ends with the command that fixes it.",
|
|
1143
1144
|
"--bundle collects what a maintainer asks for one output at a time into one tar.gz: this CLI and its environment, the doctor lines, the server's health, the local server's configuration (its compose file and the names of its .env keys, never their values), docker version, info, contexts, the Mend and workspace containers with their inspect facts, the local server's container logs, the running workspace containers' logs, every session with its processes, exit codes and argv, the recorded terminal output of each, the connected accounts, and the versions and paths of claude, codex, gh, git and docker on this machine.",
|
|
1144
1145
|
"Each part is collected on its own: one that fails leaves a <name>.error.txt in the bundle instead of stopping it. One redactor runs over every file before it is written: bearer and header values, Slack, OpenAI, GitHub and AWS token shapes, JWTs, passwords in URLs, and any password, secret or token value. The archive is written with mode 0600. It still contains logs and configuration; read it before you share it."
|
|
1145
1146
|
],
|
|
@@ -2505,8 +2506,333 @@ var doctorBundleCommand = async (args, deps) => {
|
|
|
2505
2506
|
};
|
|
2506
2507
|
|
|
2507
2508
|
// src/doctor.ts
|
|
2509
|
+
import * as fs6 from "node:fs";
|
|
2510
|
+
import * as path6 from "node:path";
|
|
2511
|
+
|
|
2512
|
+
// src/docker-shutdown.ts
|
|
2513
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2508
2514
|
import * as fs5 from "node:fs";
|
|
2515
|
+
import * as os3 from "node:os";
|
|
2509
2516
|
import * as path5 from "node:path";
|
|
2517
|
+
var DOCKER_CAPTURE_STOP_GRACE_SECONDS = 3600;
|
|
2518
|
+
var DOCKERD_DEFAULT_SHUTDOWN_TIMEOUT_SECONDS = 15;
|
|
2519
|
+
var flagValue = (argv, name) => {
|
|
2520
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
2521
|
+
const arg = argv[index] ?? "";
|
|
2522
|
+
if (arg.startsWith(`${name}=`)) return arg.slice(name.length + 1);
|
|
2523
|
+
if (arg === name) return argv[index + 1] ?? "";
|
|
2524
|
+
}
|
|
2525
|
+
return null;
|
|
2526
|
+
};
|
|
2527
|
+
var seconds = (value) => {
|
|
2528
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0) return value;
|
|
2529
|
+
if (typeof value === "string" && /^\d+$/.test(value.trim())) return Number(value.trim());
|
|
2530
|
+
return null;
|
|
2531
|
+
};
|
|
2532
|
+
var daemonKindOf = (info) => {
|
|
2533
|
+
if (/docker desktop/i.test(info.operatingSystem)) return "desktop";
|
|
2534
|
+
if (/orbstack/i.test(info.operatingSystem)) return "orbstack";
|
|
2535
|
+
if (info.securityOptions.some((option) => option.includes("name=rootless"))) return "rootless";
|
|
2536
|
+
return "dockerd";
|
|
2537
|
+
};
|
|
2538
|
+
var defaultConfigPath = (facts, daemon) => {
|
|
2539
|
+
switch (daemon) {
|
|
2540
|
+
case "desktop":
|
|
2541
|
+
return path5.join(facts.home, ".docker", "daemon.json");
|
|
2542
|
+
case "orbstack":
|
|
2543
|
+
return path5.join(facts.home, ".orbstack", "config", "docker.json");
|
|
2544
|
+
case "rootless":
|
|
2545
|
+
return path5.join(
|
|
2546
|
+
facts.xdgConfigHome ?? path5.join(facts.home, ".config"),
|
|
2547
|
+
"docker",
|
|
2548
|
+
"daemon.json"
|
|
2549
|
+
);
|
|
2550
|
+
case "dockerd":
|
|
2551
|
+
return "/etc/docker/daemon.json";
|
|
2552
|
+
}
|
|
2553
|
+
};
|
|
2554
|
+
var readShutdownTimeout = (facts) => {
|
|
2555
|
+
if (facts.info === null) return { kind: "unknown", why: "docker info did not answer" };
|
|
2556
|
+
const daemon = daemonKindOf(facts.info);
|
|
2557
|
+
const managed = daemon === "desktop" || daemon === "orbstack";
|
|
2558
|
+
const argv = managed ? null : facts.dockerdArgv;
|
|
2559
|
+
const unresolved = facts.dockerdUnresolved ?? null;
|
|
2560
|
+
if (argv === null && !managed && unresolved !== null) return { kind: "unknown", why: unresolved };
|
|
2561
|
+
const configPath = (argv === null ? null : flagValue(argv, "--config-file")) ?? defaultConfigPath(facts, daemon);
|
|
2562
|
+
if (argv !== null) {
|
|
2563
|
+
const flag = flagValue(argv, "--shutdown-timeout");
|
|
2564
|
+
if (flag !== null) {
|
|
2565
|
+
const value = seconds(flag);
|
|
2566
|
+
return value === null ? { kind: "unknown", why: `dockerd --shutdown-timeout ${flag} unreadable` } : {
|
|
2567
|
+
kind: "observed",
|
|
2568
|
+
seconds: value,
|
|
2569
|
+
source: "dockerd --shutdown-timeout",
|
|
2570
|
+
daemon,
|
|
2571
|
+
configPath,
|
|
2572
|
+
fromFlag: true
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
const file = facts.readFile(configPath);
|
|
2577
|
+
if (file.kind === "unreadable") return { kind: "unknown", why: `${configPath} unreadable` };
|
|
2578
|
+
if (file.kind === "read") {
|
|
2579
|
+
let parsed;
|
|
2580
|
+
try {
|
|
2581
|
+
parsed = JSON.parse(file.text);
|
|
2582
|
+
} catch {
|
|
2583
|
+
return { kind: "unknown", why: `${configPath} is not JSON` };
|
|
2584
|
+
}
|
|
2585
|
+
const configured = typeof parsed === "object" && parsed !== null && "shutdown-timeout" in parsed ? parsed["shutdown-timeout"] : void 0;
|
|
2586
|
+
if (configured !== void 0) {
|
|
2587
|
+
const value = seconds(configured);
|
|
2588
|
+
return value === null ? { kind: "unknown", why: `shutdown-timeout in ${configPath} unreadable` } : {
|
|
2589
|
+
kind: "observed",
|
|
2590
|
+
seconds: value,
|
|
2591
|
+
source: configPath,
|
|
2592
|
+
daemon,
|
|
2593
|
+
configPath,
|
|
2594
|
+
fromFlag: false
|
|
2595
|
+
};
|
|
2596
|
+
}
|
|
2597
|
+
}
|
|
2598
|
+
if (argv === null && !managed) {
|
|
2599
|
+
return {
|
|
2600
|
+
kind: "unknown",
|
|
2601
|
+
why: `no dockerd process observed here \xB7 not set in ${configPath}`
|
|
2602
|
+
};
|
|
2603
|
+
}
|
|
2604
|
+
return {
|
|
2605
|
+
kind: "observed",
|
|
2606
|
+
seconds: DOCKERD_DEFAULT_SHUTDOWN_TIMEOUT_SECONDS,
|
|
2607
|
+
source: `dockerd default \xB7 not set in ${configPath}`,
|
|
2608
|
+
daemon,
|
|
2609
|
+
configPath,
|
|
2610
|
+
fromFlag: false
|
|
2611
|
+
};
|
|
2612
|
+
};
|
|
2613
|
+
var restartWords = (daemon) => {
|
|
2614
|
+
switch (daemon) {
|
|
2615
|
+
case "desktop":
|
|
2616
|
+
return "restart Docker Desktop";
|
|
2617
|
+
case "orbstack":
|
|
2618
|
+
return "restart OrbStack";
|
|
2619
|
+
case "rootless":
|
|
2620
|
+
return "systemctl --user restart docker";
|
|
2621
|
+
case "dockerd":
|
|
2622
|
+
return "restart dockerd";
|
|
2623
|
+
}
|
|
2624
|
+
};
|
|
2625
|
+
var raiseShutdownTimeoutWords = (reading, grace) => reading.fromFlag ? `raise dockerd --shutdown-timeout to ${grace}, then ${restartWords(reading.daemon)}` : `set "shutdown-timeout": ${grace} in ${reading.configPath}, then ${restartWords(reading.daemon)}`;
|
|
2626
|
+
var dockerShutdownCheck = (reading, grace = DOCKER_CAPTURE_STOP_GRACE_SECONDS) => {
|
|
2627
|
+
if (reading.kind === "unknown") {
|
|
2628
|
+
return {
|
|
2629
|
+
label: "docker",
|
|
2630
|
+
state: "todo",
|
|
2631
|
+
detail: `shutdown-timeout not observed \xB7 ${reading.why}`,
|
|
2632
|
+
fix: null
|
|
2633
|
+
};
|
|
2634
|
+
}
|
|
2635
|
+
if (reading.seconds >= grace) {
|
|
2636
|
+
return {
|
|
2637
|
+
label: "docker",
|
|
2638
|
+
state: "ok",
|
|
2639
|
+
detail: `shutdown-timeout ${reading.seconds} s \xB7 ${reading.source} \xB7 covers the ${grace} s capture grace`,
|
|
2640
|
+
fix: null
|
|
2641
|
+
};
|
|
2642
|
+
}
|
|
2643
|
+
return {
|
|
2644
|
+
label: "docker",
|
|
2645
|
+
state: "todo",
|
|
2646
|
+
detail: `shutdown-timeout ${reading.seconds} s \xB7 ${reading.source} \xB7 below the ${grace} s capture grace`,
|
|
2647
|
+
fix: raiseShutdownTimeoutWords(reading, grace)
|
|
2648
|
+
};
|
|
2649
|
+
};
|
|
2650
|
+
var dockerShutdownSetupLine = (reading, grace = DOCKER_CAPTURE_STOP_GRACE_SECONDS) => {
|
|
2651
|
+
if (reading.kind === "unknown") {
|
|
2652
|
+
return `Docker shutdown-timeout not observed (${reading.why}); a daemon shutdown below ${grace} s kills capture workspaces before they save. See docs/SELF-HOSTING.md.`;
|
|
2653
|
+
}
|
|
2654
|
+
if (reading.seconds >= grace) return null;
|
|
2655
|
+
return `Docker shutdown-timeout is ${reading.seconds} s (${reading.source}), below the ${grace} s capture grace: a host restart or daemon stop kills capture workspaces after ${reading.seconds} s, before they save. To raise it: ${raiseShutdownTimeoutWords(reading, grace)}.`;
|
|
2656
|
+
};
|
|
2657
|
+
var parseDockerInfo = (stdout) => {
|
|
2658
|
+
let parsed;
|
|
2659
|
+
try {
|
|
2660
|
+
parsed = JSON.parse(stdout);
|
|
2661
|
+
} catch {
|
|
2662
|
+
return null;
|
|
2663
|
+
}
|
|
2664
|
+
if (typeof parsed !== "object" || parsed === null || !("OperatingSystem" in parsed)) return null;
|
|
2665
|
+
const operatingSystem = parsed.OperatingSystem;
|
|
2666
|
+
if (typeof operatingSystem !== "string") return null;
|
|
2667
|
+
const options = "SecurityOptions" in parsed ? parsed.SecurityOptions : [];
|
|
2668
|
+
return {
|
|
2669
|
+
operatingSystem,
|
|
2670
|
+
securityOptions: Array.isArray(options) ? options.filter((option) => typeof option === "string") : []
|
|
2671
|
+
};
|
|
2672
|
+
};
|
|
2673
|
+
var readHostFile = (file) => {
|
|
2674
|
+
try {
|
|
2675
|
+
return { kind: "read", text: fs5.readFileSync(file, "utf8") };
|
|
2676
|
+
} catch (cause) {
|
|
2677
|
+
return cause instanceof Error && "code" in cause && cause.code === "ENOENT" ? { kind: "absent" } : { kind: "unreadable" };
|
|
2678
|
+
}
|
|
2679
|
+
};
|
|
2680
|
+
var hostProcView = {
|
|
2681
|
+
list: (dir) => {
|
|
2682
|
+
try {
|
|
2683
|
+
return fs5.readdirSync(dir);
|
|
2684
|
+
} catch {
|
|
2685
|
+
return null;
|
|
2686
|
+
}
|
|
2687
|
+
},
|
|
2688
|
+
read: (file) => {
|
|
2689
|
+
try {
|
|
2690
|
+
return fs5.readFileSync(file, "utf8");
|
|
2691
|
+
} catch {
|
|
2692
|
+
return null;
|
|
2693
|
+
}
|
|
2694
|
+
},
|
|
2695
|
+
link: (file) => {
|
|
2696
|
+
try {
|
|
2697
|
+
return fs5.readlinkSync(file);
|
|
2698
|
+
} catch {
|
|
2699
|
+
return null;
|
|
2700
|
+
}
|
|
2701
|
+
}
|
|
2702
|
+
};
|
|
2703
|
+
var DOCKER_DEFAULT_HOST = "unix:///var/run/docker.sock";
|
|
2704
|
+
var socketPath = (file) => file.replace(/^\/var\/run\//, "/run/");
|
|
2705
|
+
var unixSocketOf = (host) => host.startsWith("unix://") && host.length > "unix://".length ? socketPath(host.slice("unix://".length)) : null;
|
|
2706
|
+
var hostFlags = (argv) => {
|
|
2707
|
+
const hosts = [];
|
|
2708
|
+
for (let index = 1; index < argv.length; index += 1) {
|
|
2709
|
+
const arg = argv[index] ?? "";
|
|
2710
|
+
for (const name of ["-H", "--host"]) {
|
|
2711
|
+
if (arg === name) hosts.push(argv[index + 1] ?? "");
|
|
2712
|
+
else if (arg.startsWith(`${name}=`)) hosts.push(arg.slice(name.length + 1));
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
return hosts;
|
|
2716
|
+
};
|
|
2717
|
+
var listeningInodes = (table, socket) => {
|
|
2718
|
+
const inodes = /* @__PURE__ */ new Set();
|
|
2719
|
+
for (const line of table.split("\n").slice(1)) {
|
|
2720
|
+
const fields = line.trim().split(/\s+/);
|
|
2721
|
+
const flags = fields[3];
|
|
2722
|
+
const inode = fields[6];
|
|
2723
|
+
const bound = fields.slice(7).join(" ");
|
|
2724
|
+
if (flags === void 0 || inode === void 0 || bound === "") continue;
|
|
2725
|
+
if ((Number.parseInt(flags, 16) & 65536) === 0) continue;
|
|
2726
|
+
if (socketPath(bound) === socket) inodes.add(inode);
|
|
2727
|
+
}
|
|
2728
|
+
return inodes;
|
|
2729
|
+
};
|
|
2730
|
+
var observeDockerd = (proc, host) => {
|
|
2731
|
+
const daemons = [];
|
|
2732
|
+
for (const entry of proc.list("/proc") ?? []) {
|
|
2733
|
+
if (!/^\d+$/.test(entry)) continue;
|
|
2734
|
+
const cmdline = proc.read(`/proc/${entry}/cmdline`);
|
|
2735
|
+
if (cmdline === null) continue;
|
|
2736
|
+
const argv = cmdline.split("\0").filter((arg) => arg !== "");
|
|
2737
|
+
if (path5.basename(argv[0] ?? "") === "dockerd") daemons.push({ pid: entry, argv });
|
|
2738
|
+
}
|
|
2739
|
+
if (daemons.length === 0) return { kind: "none" };
|
|
2740
|
+
const socket = unixSocketOf(host);
|
|
2741
|
+
if (socket === null) {
|
|
2742
|
+
return {
|
|
2743
|
+
kind: "unresolved",
|
|
2744
|
+
why: `docker talks to ${host} \xB7 not a socket on this machine`
|
|
2745
|
+
};
|
|
2746
|
+
}
|
|
2747
|
+
const selfTable = proc.read("/proc/self/net/unix");
|
|
2748
|
+
const seenHere = selfTable === null ? /* @__PURE__ */ new Set() : listeningInodes(selfTable, socket);
|
|
2749
|
+
const candidates = [];
|
|
2750
|
+
for (const daemon of daemons) {
|
|
2751
|
+
const hosts = hostFlags(daemon.argv);
|
|
2752
|
+
const named = hosts.some((value) => unixSocketOf(value) === socket);
|
|
2753
|
+
const activated = hosts.some((value) => value.startsWith("fd://"));
|
|
2754
|
+
if (hosts.length > 0 && !named && !activated) continue;
|
|
2755
|
+
const table = proc.read(`/proc/${daemon.pid}/net/unix`);
|
|
2756
|
+
const listening = table === null ? null : listeningInodes(table, socket);
|
|
2757
|
+
if (listening !== null) {
|
|
2758
|
+
if (listening.size === 0) continue;
|
|
2759
|
+
if (seenHere.size > 0 && ![...listening].some((inode) => seenHere.has(inode))) continue;
|
|
2760
|
+
}
|
|
2761
|
+
const fds = proc.list(`/proc/${daemon.pid}/fd`);
|
|
2762
|
+
const held = fds === null || listening === null ? null : fds.some((fd) => {
|
|
2763
|
+
const target = proc.link(`/proc/${daemon.pid}/fd/${fd}`);
|
|
2764
|
+
const inode = target?.match(/^socket:\[(\d+)\]$/)?.[1];
|
|
2765
|
+
return inode !== void 0 && listening.has(inode);
|
|
2766
|
+
});
|
|
2767
|
+
if (held === false) continue;
|
|
2768
|
+
const nsPids = proc.read(`/proc/${daemon.pid}/status`)?.split("\n").find((line) => line.startsWith("NSpid:"))?.slice("NSpid:".length).trim().split(/\s+/);
|
|
2769
|
+
candidates.push({
|
|
2770
|
+
...daemon,
|
|
2771
|
+
owns: held === true,
|
|
2772
|
+
named,
|
|
2773
|
+
nested: nsPids !== void 0 && nsPids.length > 1
|
|
2774
|
+
});
|
|
2775
|
+
}
|
|
2776
|
+
const narrowed = [
|
|
2777
|
+
(candidate) => candidate.owns,
|
|
2778
|
+
(candidate) => candidate.named,
|
|
2779
|
+
(candidate) => !candidate.nested
|
|
2780
|
+
].reduce((left, prefer) => {
|
|
2781
|
+
const preferred = left.filter(prefer);
|
|
2782
|
+
return preferred.length > 0 ? preferred : left;
|
|
2783
|
+
}, candidates);
|
|
2784
|
+
const [only, ...others] = narrowed;
|
|
2785
|
+
if (only !== void 0 && others.length === 0) {
|
|
2786
|
+
return { kind: "observed", pid: only.pid, argv: only.argv };
|
|
2787
|
+
}
|
|
2788
|
+
const seen = `${daemons.length} dockerd process${daemons.length === 1 ? "" : "es"}`;
|
|
2789
|
+
return {
|
|
2790
|
+
kind: "unresolved",
|
|
2791
|
+
why: only === void 0 ? `${seen} \xB7 none observed serving ${socket}` : `${seen} \xB7 could not tell which serves ${socket}`
|
|
2792
|
+
};
|
|
2793
|
+
};
|
|
2794
|
+
var hostDockerEndpoint = () => {
|
|
2795
|
+
const fromEnv = process.env["DOCKER_HOST"];
|
|
2796
|
+
if (fromEnv !== void 0 && fromEnv.trim() !== "") return fromEnv.trim();
|
|
2797
|
+
const context = spawnSync2(
|
|
2798
|
+
"docker",
|
|
2799
|
+
["context", "inspect", "--format", "{{.Endpoints.docker.Host}}"],
|
|
2800
|
+
{
|
|
2801
|
+
encoding: "utf8",
|
|
2802
|
+
timeout: 3e3,
|
|
2803
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2804
|
+
}
|
|
2805
|
+
);
|
|
2806
|
+
const endpoint = context.status === 0 ? context.stdout.trim() : "";
|
|
2807
|
+
return endpoint === "" ? DOCKER_DEFAULT_HOST : endpoint;
|
|
2808
|
+
};
|
|
2809
|
+
var dockerdFactsOf = (observation) => {
|
|
2810
|
+
switch (observation.kind) {
|
|
2811
|
+
case "observed":
|
|
2812
|
+
return { dockerdArgv: observation.argv, dockerdUnresolved: null };
|
|
2813
|
+
case "none":
|
|
2814
|
+
return { dockerdArgv: null, dockerdUnresolved: null };
|
|
2815
|
+
case "unresolved":
|
|
2816
|
+
return { dockerdArgv: null, dockerdUnresolved: observation.why };
|
|
2817
|
+
}
|
|
2818
|
+
};
|
|
2819
|
+
var hostDockerDaemonFacts = (infoStdout) => ({
|
|
2820
|
+
info: infoStdout === null ? null : parseDockerInfo(infoStdout),
|
|
2821
|
+
...dockerdFactsOf(observeDockerd(hostProcView, hostDockerEndpoint())),
|
|
2822
|
+
readFile: readHostFile,
|
|
2823
|
+
home: os3.homedir(),
|
|
2824
|
+
xdgConfigHome: process.env["XDG_CONFIG_HOME"] ?? null
|
|
2825
|
+
});
|
|
2826
|
+
var observeHostShutdownTimeout = () => {
|
|
2827
|
+
const info = spawnSync2("docker", ["info", "--format", "{{json .}}"], {
|
|
2828
|
+
encoding: "utf8",
|
|
2829
|
+
timeout: 3e3,
|
|
2830
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2831
|
+
});
|
|
2832
|
+
return readShutdownTimeout(hostDockerDaemonFacts(info.status === 0 ? info.stdout : null));
|
|
2833
|
+
};
|
|
2834
|
+
|
|
2835
|
+
// src/doctor.ts
|
|
2510
2836
|
var TIMEOUT_MS = 3e3;
|
|
2511
2837
|
var MARKS = { ok: "\u2713", todo: "\u25CB", failed: "\u2717" };
|
|
2512
2838
|
var LABEL_WIDTH = 11;
|
|
@@ -2721,13 +3047,16 @@ var runChecks = async (config, probes) => {
|
|
|
2721
3047
|
const machine = signedIn ? await getJson(config, "/machine") : null;
|
|
2722
3048
|
const exposure = machine === null || machine.value === null ? void 0 : machine.value.exposure;
|
|
2723
3049
|
checks.push(exposure === void 0 ? notChecked("exposure") : exposureCheck(exposure));
|
|
3050
|
+
if (probes.dockerShutdown !== void 0 && probes.onPath("docker")) {
|
|
3051
|
+
checks.push(dockerShutdownCheck(probes.dockerShutdown()));
|
|
3052
|
+
}
|
|
2724
3053
|
return checks;
|
|
2725
3054
|
};
|
|
2726
3055
|
var onPath = (command) => {
|
|
2727
|
-
for (const directory of (process.env["PATH"] ?? "").split(
|
|
3056
|
+
for (const directory of (process.env["PATH"] ?? "").split(path6.delimiter)) {
|
|
2728
3057
|
if (directory === "") continue;
|
|
2729
3058
|
try {
|
|
2730
|
-
|
|
3059
|
+
fs6.accessSync(path6.join(directory, command), fs6.constants.X_OK);
|
|
2731
3060
|
return true;
|
|
2732
3061
|
} catch {
|
|
2733
3062
|
}
|
|
@@ -2740,15 +3069,20 @@ var paintMark = (state, mark) => {
|
|
|
2740
3069
|
return `\x1B[${code}m${mark}\x1B[0m`;
|
|
2741
3070
|
};
|
|
2742
3071
|
var doctorCommand = async (config, localCredential2, claudeGrant2) => {
|
|
2743
|
-
const checks = await runChecks(config, {
|
|
3072
|
+
const checks = await runChecks(config, {
|
|
3073
|
+
localCredential: localCredential2,
|
|
3074
|
+
claudeGrant: claudeGrant2,
|
|
3075
|
+
onPath,
|
|
3076
|
+
dockerShutdown: observeHostShutdownTimeout
|
|
3077
|
+
});
|
|
2744
3078
|
for (const check of checks) process.stdout.write(`${formatCheck(check, paintMark)}
|
|
2745
3079
|
`);
|
|
2746
3080
|
if (checks.some((check) => check.state === "failed")) process.exitCode = 1;
|
|
2747
3081
|
};
|
|
2748
3082
|
|
|
2749
3083
|
// src/dotfiles.ts
|
|
2750
|
-
import * as
|
|
2751
|
-
import * as
|
|
3084
|
+
import * as fs7 from "node:fs";
|
|
3085
|
+
import * as path7 from "node:path";
|
|
2752
3086
|
var DOTFILE_CANDIDATES = [
|
|
2753
3087
|
{
|
|
2754
3088
|
group: "shell",
|
|
@@ -2791,7 +3125,7 @@ var DOTFILE_CANDIDATES = [
|
|
|
2791
3125
|
var MAX_FILE_BYTES = 1024 * 1024;
|
|
2792
3126
|
var statFile = (home, relative4) => {
|
|
2793
3127
|
try {
|
|
2794
|
-
const stat =
|
|
3128
|
+
const stat = fs7.statSync(path7.join(home, relative4));
|
|
2795
3129
|
return stat.isFile() ? stat : null;
|
|
2796
3130
|
} catch {
|
|
2797
3131
|
return null;
|
|
@@ -2804,9 +3138,9 @@ var scanDotfileCandidates = (home) => DOTFILE_CANDIDATES.flatMap(
|
|
|
2804
3138
|
})
|
|
2805
3139
|
);
|
|
2806
3140
|
var homeRelativePath = (home, requested) => {
|
|
2807
|
-
const relative4 =
|
|
2808
|
-
const outside = relative4 === "" || relative4 === ".." || relative4.startsWith(`..${
|
|
2809
|
-
return outside ? null : relative4.split(
|
|
3141
|
+
const relative4 = path7.relative(home, path7.resolve(home, requested));
|
|
3142
|
+
const outside = relative4 === "" || relative4 === ".." || relative4.startsWith(`..${path7.sep}`) || path7.isAbsolute(relative4);
|
|
3143
|
+
return outside ? null : relative4.split(path7.sep).join("/");
|
|
2810
3144
|
};
|
|
2811
3145
|
var readSyncFiles = (home, paths_) => {
|
|
2812
3146
|
const files = [];
|
|
@@ -2826,7 +3160,7 @@ var readSyncFiles = (home, paths_) => {
|
|
|
2826
3160
|
}
|
|
2827
3161
|
files.push({
|
|
2828
3162
|
path: relative4,
|
|
2829
|
-
contentsBase64:
|
|
3163
|
+
contentsBase64: fs7.readFileSync(path7.join(home, relative4)).toString("base64"),
|
|
2830
3164
|
// eslint-disable-next-line no-bitwise -- permission bits are the point
|
|
2831
3165
|
mode: (stat.mode & 511).toString(8)
|
|
2832
3166
|
});
|
|
@@ -2925,10 +3259,10 @@ var parseGitAuthorArgs = (args) => {
|
|
|
2925
3259
|
var gitAuthorLine = (author) => `${author.name} <${author.email}> \xB7 ${author.source === "setting" ? "your setting" : "your account's name and email"}`;
|
|
2926
3260
|
|
|
2927
3261
|
// src/landing.ts
|
|
2928
|
-
import { spawnSync as
|
|
2929
|
-
import * as
|
|
2930
|
-
import * as
|
|
2931
|
-
import * as
|
|
3262
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
3263
|
+
import * as fs8 from "node:fs";
|
|
3264
|
+
import * as os4 from "node:os";
|
|
3265
|
+
import * as path8 from "node:path";
|
|
2932
3266
|
var paint = (code) => (text) => process.stdout.isTTY === true ? `[${code}m${text}[0m` : text;
|
|
2933
3267
|
var dim = paint("2");
|
|
2934
3268
|
var green = paint("32");
|
|
@@ -3143,7 +3477,7 @@ var landCommand = async (api2, args) => {
|
|
|
3143
3477
|
if (!landingSucceeded(report)) process.exitCode = 1;
|
|
3144
3478
|
};
|
|
3145
3479
|
var git = (cwd, args) => {
|
|
3146
|
-
const result =
|
|
3480
|
+
const result = spawnSync3("git", args, { cwd, encoding: "utf8" });
|
|
3147
3481
|
return {
|
|
3148
3482
|
status: result.status ?? 1,
|
|
3149
3483
|
stdout: result.stdout ?? "",
|
|
@@ -3166,9 +3500,9 @@ var gitRemotes = (cwd) => {
|
|
|
3166
3500
|
var remoteForOrigin = (remotes, originUrl, cwd) => {
|
|
3167
3501
|
if (originUrl === null) return null;
|
|
3168
3502
|
const origin = normalizeRemoteUrl(originUrl);
|
|
3169
|
-
const originPath = origin === null ?
|
|
3503
|
+
const originPath = origin === null ? path8.resolve(originUrl.replace(/^file:\/\//, "")) : null;
|
|
3170
3504
|
return remotes.find(
|
|
3171
|
-
(remote) => origin === null ?
|
|
3505
|
+
(remote) => origin === null ? path8.resolve(cwd, remote.url.replace(/^file:\/\//, "")) === originPath : normalizeRemoteUrl(remote.url) === origin
|
|
3172
3506
|
) ?? null;
|
|
3173
3507
|
};
|
|
3174
3508
|
var LOG_LINES = 10;
|
|
@@ -3191,10 +3525,10 @@ var fetchBundle = (cwd, bundle) => {
|
|
|
3191
3525
|
const before = git(cwd, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]);
|
|
3192
3526
|
const previous = before.status === 0 ? before.stdout.trim() : null;
|
|
3193
3527
|
if (previous !== bundle.tip) {
|
|
3194
|
-
const dir =
|
|
3528
|
+
const dir = fs8.mkdtempSync(path8.join(os4.tmpdir(), "mend-pull-"));
|
|
3195
3529
|
try {
|
|
3196
|
-
const file =
|
|
3197
|
-
|
|
3530
|
+
const file = path8.join(dir, "change.bundle");
|
|
3531
|
+
fs8.writeFileSync(file, bundle.bytes, { mode: 384 });
|
|
3198
3532
|
const fetched = git(cwd, [
|
|
3199
3533
|
"fetch",
|
|
3200
3534
|
"--no-tags",
|
|
@@ -3204,7 +3538,7 @@ var fetchBundle = (cwd, bundle) => {
|
|
|
3204
3538
|
]);
|
|
3205
3539
|
if (fetched.status !== 0) return refusedWith(gitWords(fetched));
|
|
3206
3540
|
} finally {
|
|
3207
|
-
|
|
3541
|
+
fs8.rmSync(dir, { recursive: true, force: true });
|
|
3208
3542
|
}
|
|
3209
3543
|
}
|
|
3210
3544
|
const after = git(cwd, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]).stdout.trim();
|
|
@@ -3323,7 +3657,7 @@ var pullCommand = async (api2, download2, args, cwd = process.cwd()) => {
|
|
|
3323
3657
|
|
|
3324
3658
|
// src/login.ts
|
|
3325
3659
|
import { spawn as spawn2 } from "node:child_process";
|
|
3326
|
-
import * as
|
|
3660
|
+
import * as os5 from "node:os";
|
|
3327
3661
|
|
|
3328
3662
|
// src/pair.ts
|
|
3329
3663
|
var paint2 = (code) => (text) => process.stdout.isTTY === true ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
@@ -3492,8 +3826,8 @@ var post = async (url, body) => {
|
|
|
3492
3826
|
};
|
|
3493
3827
|
var retryAfterSecondsOf = (json2) => {
|
|
3494
3828
|
if (typeof json2 === "object" && json2 !== null && "retryAfterSeconds" in json2) {
|
|
3495
|
-
const
|
|
3496
|
-
if (Number.isFinite(
|
|
3829
|
+
const seconds2 = Number(json2.retryAfterSeconds);
|
|
3830
|
+
if (Number.isFinite(seconds2) && seconds2 > 0) return seconds2;
|
|
3497
3831
|
}
|
|
3498
3832
|
return 5;
|
|
3499
3833
|
};
|
|
@@ -3515,7 +3849,7 @@ var resolveServerUrl = async (args, deps) => {
|
|
|
3515
3849
|
};
|
|
3516
3850
|
var loginCommand = async (args, deps) => {
|
|
3517
3851
|
const base = await resolveServerUrl(args, deps);
|
|
3518
|
-
const started = await post(`${base}/api/cli/auth`, { name:
|
|
3852
|
+
const started = await post(`${base}/api/cli/auth`, { name: os5.hostname() });
|
|
3519
3853
|
if (started.status === 429) {
|
|
3520
3854
|
return fail3(
|
|
3521
3855
|
`the server asked for a pause \u2014 try again in ${retryAfterSecondsOf(started.json)}s`
|
|
@@ -3566,8 +3900,8 @@ var loginCommand = async (args, deps) => {
|
|
|
3566
3900
|
};
|
|
3567
3901
|
|
|
3568
3902
|
// src/organization.ts
|
|
3569
|
-
import * as
|
|
3570
|
-
import * as
|
|
3903
|
+
import * as fs9 from "node:fs";
|
|
3904
|
+
import * as path9 from "node:path";
|
|
3571
3905
|
var paint4 = (code) => (text) => process.stdout.isTTY === true ? `[${code}m${text}[0m` : text;
|
|
3572
3906
|
var dim4 = paint4("2");
|
|
3573
3907
|
var green4 = paint4("32");
|
|
@@ -3631,7 +3965,7 @@ var scanFolder = (dir) => {
|
|
|
3631
3965
|
const files = [];
|
|
3632
3966
|
const skipped = [];
|
|
3633
3967
|
const walk = (relative4) => {
|
|
3634
|
-
const entries =
|
|
3968
|
+
const entries = fs9.readdirSync(path9.join(dir, relative4), { withFileTypes: true }).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
3635
3969
|
for (const entry of entries) {
|
|
3636
3970
|
const child = relative4 === "" ? entry.name : `${relative4}/${entry.name}`;
|
|
3637
3971
|
if (entry.isSymbolicLink()) {
|
|
@@ -3640,7 +3974,7 @@ var scanFolder = (dir) => {
|
|
|
3640
3974
|
if (SKIPPED_DIRECTORIES.has(entry.name)) skipped.push({ path: child, reason: "skipped" });
|
|
3641
3975
|
else walk(child);
|
|
3642
3976
|
} else if (entry.isFile()) {
|
|
3643
|
-
const size =
|
|
3977
|
+
const size = fs9.statSync(path9.join(dir, child)).size;
|
|
3644
3978
|
if (size > FOLDER_FILE_BYTES) skipped.push({ path: child, reason: "over 1 MiB" });
|
|
3645
3979
|
else files.push({ path: child, size });
|
|
3646
3980
|
}
|
|
@@ -3693,7 +4027,7 @@ var folderCommand = async (api2, args) => {
|
|
|
3693
4027
|
}
|
|
3694
4028
|
case "push": {
|
|
3695
4029
|
if (dir === void 0) return fail4("usage: mend folder push <name> <dir> [--replace]");
|
|
3696
|
-
if (!
|
|
4030
|
+
if (!fs9.existsSync(dir) || !fs9.statSync(dir).isDirectory()) {
|
|
3697
4031
|
return fail4(`${dir} is not a directory`);
|
|
3698
4032
|
}
|
|
3699
4033
|
const folder = await folderNamed(api2, name);
|
|
@@ -3708,7 +4042,7 @@ var folderCommand = async (api2, args) => {
|
|
|
3708
4042
|
files: batch.map((file) => ({
|
|
3709
4043
|
path: file.path,
|
|
3710
4044
|
// Read one batch at a time, so a large tree never sits in memory at once.
|
|
3711
|
-
contentsBase64:
|
|
4045
|
+
contentsBase64: fs9.readFileSync(path9.join(dir, file.path)).toString("base64")
|
|
3712
4046
|
})),
|
|
3713
4047
|
// --replace empties the folder with the first batch; the rest add to it.
|
|
3714
4048
|
merge: !(replace && index === 0)
|
|
@@ -3892,8 +4226,8 @@ var operatorCommand = async (api2, tryApi, baseUrl, args) => {
|
|
|
3892
4226
|
import { createHmac, randomBytes } from "node:crypto";
|
|
3893
4227
|
import { readFile } from "node:fs/promises";
|
|
3894
4228
|
import * as net2 from "node:net";
|
|
3895
|
-
import * as
|
|
3896
|
-
import * as
|
|
4229
|
+
import * as os7 from "node:os";
|
|
4230
|
+
import * as path11 from "node:path";
|
|
3897
4231
|
|
|
3898
4232
|
// src/server-docker-volumes.ts
|
|
3899
4233
|
import { createHash as createHash2 } from "node:crypto";
|
|
@@ -4111,9 +4445,9 @@ var verifyServerDockerVolumes = (runtime, input) => ownership(runtime, input, "v
|
|
|
4111
4445
|
|
|
4112
4446
|
// src/server-store.ts
|
|
4113
4447
|
import { randomUUID } from "node:crypto";
|
|
4114
|
-
import * as
|
|
4115
|
-
import * as
|
|
4116
|
-
import * as
|
|
4448
|
+
import * as fs10 from "node:fs";
|
|
4449
|
+
import * as os6 from "node:os";
|
|
4450
|
+
import * as path10 from "node:path";
|
|
4117
4451
|
var ServerStoreError = class extends Error {
|
|
4118
4452
|
_tag = "ServerStoreError";
|
|
4119
4453
|
};
|
|
@@ -4134,28 +4468,28 @@ var attempt = (operation) => {
|
|
|
4134
4468
|
var hasCode = (cause, code) => cause instanceof Error && "code" in cause && cause.code === code;
|
|
4135
4469
|
var readOptional = (file) => {
|
|
4136
4470
|
try {
|
|
4137
|
-
return
|
|
4471
|
+
return fs10.readFileSync(file, "utf8");
|
|
4138
4472
|
} catch (cause) {
|
|
4139
4473
|
if (hasCode(cause, "ENOENT")) return null;
|
|
4140
4474
|
throw cause;
|
|
4141
4475
|
}
|
|
4142
4476
|
};
|
|
4143
4477
|
var syncDirectory = (directory) => {
|
|
4144
|
-
const fd =
|
|
4478
|
+
const fd = fs10.openSync(directory, "r");
|
|
4145
4479
|
try {
|
|
4146
|
-
|
|
4480
|
+
fs10.fsyncSync(fd);
|
|
4147
4481
|
} finally {
|
|
4148
|
-
|
|
4482
|
+
fs10.closeSync(fd);
|
|
4149
4483
|
}
|
|
4150
4484
|
};
|
|
4151
4485
|
var writeDurable = (file, content, mode = 384) => {
|
|
4152
|
-
const fd =
|
|
4486
|
+
const fd = fs10.openSync(file, "wx", mode);
|
|
4153
4487
|
try {
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4488
|
+
fs10.writeFileSync(fd, content, "utf8");
|
|
4489
|
+
fs10.fchmodSync(fd, mode);
|
|
4490
|
+
fs10.fsyncSync(fd);
|
|
4157
4491
|
} finally {
|
|
4158
|
-
|
|
4492
|
+
fs10.closeSync(fd);
|
|
4159
4493
|
}
|
|
4160
4494
|
};
|
|
4161
4495
|
var fileKeys = ["identity", "config", "env", "compose", "postgresInit"];
|
|
@@ -4175,11 +4509,11 @@ var parseLockOwner = (raw) => {
|
|
|
4175
4509
|
};
|
|
4176
4510
|
var describeOwner = (lockDir) => {
|
|
4177
4511
|
try {
|
|
4178
|
-
const raw = JSON.parse(
|
|
4512
|
+
const raw = JSON.parse(fs10.readFileSync(path10.join(lockDir, "owner.json"), "utf8"));
|
|
4179
4513
|
const owner = parseLockOwner(raw);
|
|
4180
4514
|
if (owner === null) return "owner metadata is invalid";
|
|
4181
4515
|
const label = `PID ${owner.pid} on ${owner.hostname}`;
|
|
4182
|
-
if (owner.hostname !==
|
|
4516
|
+
if (owner.hostname !== os6.hostname()) return label;
|
|
4183
4517
|
try {
|
|
4184
4518
|
process.kill(owner.pid, 0);
|
|
4185
4519
|
return `${label} is still live`;
|
|
@@ -4195,28 +4529,28 @@ var lockGuidance = (lockDir) => new ServerStoreError(
|
|
|
4195
4529
|
);
|
|
4196
4530
|
var acquireLock = (configDir, create) => {
|
|
4197
4531
|
if (create) {
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
} else if (!
|
|
4532
|
+
fs10.mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
4533
|
+
fs10.chmodSync(configDir, 448);
|
|
4534
|
+
} else if (!fs10.existsSync(configDir)) {
|
|
4201
4535
|
throw new ServerStoreError(
|
|
4202
4536
|
"No Mend server is configured. Run mend server setup explicitly to install one."
|
|
4203
4537
|
);
|
|
4204
4538
|
}
|
|
4205
|
-
const lockDir =
|
|
4539
|
+
const lockDir = path10.join(configDir, "server.lock");
|
|
4206
4540
|
try {
|
|
4207
|
-
|
|
4541
|
+
fs10.mkdirSync(lockDir, { mode: 448 });
|
|
4208
4542
|
} catch (cause) {
|
|
4209
4543
|
if (hasCode(cause, "EEXIST")) throw lockGuidance(lockDir);
|
|
4210
4544
|
throw cause;
|
|
4211
4545
|
}
|
|
4212
|
-
const owned =
|
|
4213
|
-
const ownerFile =
|
|
4214
|
-
const owner = `${JSON.stringify({ pid: process.pid, hostname:
|
|
4546
|
+
const owned = fs10.statSync(lockDir);
|
|
4547
|
+
const ownerFile = path10.join(lockDir, "owner.json");
|
|
4548
|
+
const owner = `${JSON.stringify({ pid: process.pid, hostname: os6.hostname(), token: randomUUID() })}
|
|
4215
4549
|
`;
|
|
4216
4550
|
let open = true;
|
|
4217
4551
|
const assertOwned = () => {
|
|
4218
|
-
const current =
|
|
4219
|
-
if (!open || current.dev !== owned.dev || current.ino !== owned.ino ||
|
|
4552
|
+
const current = fs10.statSync(lockDir);
|
|
4553
|
+
if (!open || current.dev !== owned.dev || current.ino !== owned.ino || fs10.readFileSync(ownerFile, "utf8") !== owner) {
|
|
4220
4554
|
throw new ServerStoreError(
|
|
4221
4555
|
"Server lock ownership was lost. Stop and check for another server command before retrying."
|
|
4222
4556
|
);
|
|
@@ -4232,8 +4566,8 @@ var acquireLock = (configDir, create) => {
|
|
|
4232
4566
|
release: () => {
|
|
4233
4567
|
const result = attempt(() => {
|
|
4234
4568
|
assertOwned();
|
|
4235
|
-
|
|
4236
|
-
|
|
4569
|
+
fs10.unlinkSync(ownerFile);
|
|
4570
|
+
fs10.rmdirSync(lockDir);
|
|
4237
4571
|
});
|
|
4238
4572
|
open = false;
|
|
4239
4573
|
return result;
|
|
@@ -4242,7 +4576,7 @@ var acquireLock = (configDir, create) => {
|
|
|
4242
4576
|
};
|
|
4243
4577
|
var readIdentity = (paths) => {
|
|
4244
4578
|
const identity = readOptional(paths.identity);
|
|
4245
|
-
if (identity === null && (
|
|
4579
|
+
if (identity === null && (fs10.existsSync(paths.active) || fs10.existsSync(paths.generations) && fs10.readdirSync(paths.generations).length > 0)) {
|
|
4246
4580
|
throw new ServerStoreError(
|
|
4247
4581
|
"Server identity.env is missing. Restore it from a retained generation or backup; refusing to generate replacement credentials."
|
|
4248
4582
|
);
|
|
@@ -4252,7 +4586,7 @@ var readIdentity = (paths) => {
|
|
|
4252
4586
|
var readActive = (paths) => {
|
|
4253
4587
|
let target;
|
|
4254
4588
|
try {
|
|
4255
|
-
target =
|
|
4589
|
+
target = fs10.readlinkSync(paths.active);
|
|
4256
4590
|
} catch (cause) {
|
|
4257
4591
|
if (hasCode(cause, "ENOENT")) return null;
|
|
4258
4592
|
throw cause;
|
|
@@ -4262,13 +4596,13 @@ var readActive = (paths) => {
|
|
|
4262
4596
|
"Server active pointer is invalid. Restore the symlink to a retained generation before retrying."
|
|
4263
4597
|
);
|
|
4264
4598
|
}
|
|
4265
|
-
const directory =
|
|
4599
|
+
const directory = path10.join(paths.configDir, target);
|
|
4266
4600
|
const files = {
|
|
4267
|
-
identity:
|
|
4268
|
-
config:
|
|
4269
|
-
env:
|
|
4270
|
-
compose:
|
|
4271
|
-
postgresInit:
|
|
4601
|
+
identity: fs10.readFileSync(path10.join(directory, fileNames.identity), "utf8"),
|
|
4602
|
+
config: fs10.readFileSync(path10.join(directory, fileNames.config), "utf8"),
|
|
4603
|
+
env: fs10.readFileSync(path10.join(directory, fileNames.env), "utf8"),
|
|
4604
|
+
compose: fs10.readFileSync(path10.join(directory, fileNames.compose), "utf8"),
|
|
4605
|
+
postgresInit: fs10.readFileSync(path10.join(directory, fileNames.postgresInit), "utf8")
|
|
4272
4606
|
};
|
|
4273
4607
|
if (readIdentity(paths) !== files.identity) {
|
|
4274
4608
|
throw new ServerStoreError(
|
|
@@ -4278,20 +4612,20 @@ var readActive = (paths) => {
|
|
|
4278
4612
|
return { directory, files };
|
|
4279
4613
|
};
|
|
4280
4614
|
var publishIdentity = (paths, identity) => {
|
|
4281
|
-
const temporary =
|
|
4615
|
+
const temporary = path10.join(paths.configDir, `.identity-${randomUUID()}`);
|
|
4282
4616
|
writeDurable(temporary, identity);
|
|
4283
|
-
|
|
4617
|
+
fs10.linkSync(temporary, paths.identity);
|
|
4284
4618
|
syncDirectory(paths.configDir);
|
|
4285
|
-
|
|
4619
|
+
fs10.unlinkSync(temporary);
|
|
4286
4620
|
};
|
|
4287
4621
|
var prepareGeneration = (paths, files) => {
|
|
4288
|
-
|
|
4622
|
+
fs10.mkdirSync(paths.generations, { recursive: true, mode: 448 });
|
|
4289
4623
|
const generationName = `gen-${randomUUID()}`;
|
|
4290
|
-
const directory =
|
|
4291
|
-
|
|
4624
|
+
const directory = path10.join(paths.generations, generationName);
|
|
4625
|
+
fs10.mkdirSync(directory, { mode: 448 });
|
|
4292
4626
|
for (const key of fileKeys) {
|
|
4293
4627
|
writeDurable(
|
|
4294
|
-
|
|
4628
|
+
path10.join(directory, fileNames[key]),
|
|
4295
4629
|
files[key],
|
|
4296
4630
|
key === "postgresInit" ? 493 : 384
|
|
4297
4631
|
);
|
|
@@ -4302,18 +4636,18 @@ var prepareGeneration = (paths, files) => {
|
|
|
4302
4636
|
return { directory, files };
|
|
4303
4637
|
};
|
|
4304
4638
|
var activateGeneration = (paths, generation) => {
|
|
4305
|
-
const relative4 =
|
|
4306
|
-
if (!/^generations\/gen-[0-9a-f-]{36}$/.test(relative4) || readIdentity(paths) !==
|
|
4639
|
+
const relative4 = path10.relative(paths.configDir, generation.directory);
|
|
4640
|
+
if (!/^generations\/gen-[0-9a-f-]{36}$/.test(relative4) || readIdentity(paths) !== fs10.readFileSync(path10.join(generation.directory, "identity.env"), "utf8")) {
|
|
4307
4641
|
throw new ServerStoreError("Cannot activate a generation outside this installation identity.");
|
|
4308
4642
|
}
|
|
4309
4643
|
for (const key of fileKeys) {
|
|
4310
|
-
if (
|
|
4644
|
+
if (fs10.readFileSync(path10.join(generation.directory, fileNames[key]), "utf8") !== generation.files[key]) {
|
|
4311
4645
|
throw new ServerStoreError("Cannot activate an incomplete or changed server generation.");
|
|
4312
4646
|
}
|
|
4313
4647
|
}
|
|
4314
|
-
const pointer =
|
|
4315
|
-
|
|
4316
|
-
|
|
4648
|
+
const pointer = path10.join(paths.configDir, `.active-${randomUUID()}`);
|
|
4649
|
+
fs10.symlinkSync(relative4, pointer);
|
|
4650
|
+
fs10.renameSync(pointer, paths.active);
|
|
4317
4651
|
syncDirectory(paths.configDir);
|
|
4318
4652
|
};
|
|
4319
4653
|
var prepareFiles = (paths, files) => {
|
|
@@ -4322,7 +4656,7 @@ var prepareFiles = (paths, files) => {
|
|
|
4322
4656
|
throw new ServerStoreError("Server identity already exists; refusing to replace credentials.");
|
|
4323
4657
|
}
|
|
4324
4658
|
const active = readActive(paths);
|
|
4325
|
-
if (active !== null && fileKeys.every((key) => files[key] === active.files[key]) && (
|
|
4659
|
+
if (active !== null && fileKeys.every((key) => files[key] === active.files[key]) && (fs10.statSync(path10.join(active.directory, fileNames.postgresInit)).mode & 4095) === 493)
|
|
4326
4660
|
return active;
|
|
4327
4661
|
if (identity === null) publishIdentity(paths, files.identity);
|
|
4328
4662
|
return prepareGeneration(paths, files);
|
|
@@ -4335,9 +4669,9 @@ var commitGeneration = (paths, files) => {
|
|
|
4335
4669
|
var createStore = (configDir, lock) => {
|
|
4336
4670
|
const paths = {
|
|
4337
4671
|
configDir,
|
|
4338
|
-
identity:
|
|
4339
|
-
generations:
|
|
4340
|
-
active:
|
|
4672
|
+
identity: path10.join(configDir, "identity.env"),
|
|
4673
|
+
generations: path10.join(configDir, "generations"),
|
|
4674
|
+
active: path10.join(configDir, "active")
|
|
4341
4675
|
};
|
|
4342
4676
|
const whileOwned = (operation) => attempt(() => {
|
|
4343
4677
|
lock.assertOwned();
|
|
@@ -4350,13 +4684,13 @@ var createStore = (configDir, lock) => {
|
|
|
4350
4684
|
prepare: (files) => whileOwned(() => prepareFiles(paths, files)),
|
|
4351
4685
|
activate: (generation) => whileOwned(() => activateGeneration(paths, generation)),
|
|
4352
4686
|
createBackup: (previous, target) => whileOwned(() => {
|
|
4353
|
-
const backups =
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
const directory =
|
|
4357
|
-
|
|
4687
|
+
const backups = path10.join(configDir, "backups");
|
|
4688
|
+
fs10.mkdirSync(backups, { recursive: true, mode: 448 });
|
|
4689
|
+
fs10.chmodSync(backups, 448);
|
|
4690
|
+
const directory = path10.join(backups, `upgrade-${randomUUID()}`);
|
|
4691
|
+
fs10.mkdirSync(directory, { mode: 448 });
|
|
4358
4692
|
writeDurable(
|
|
4359
|
-
|
|
4693
|
+
path10.join(directory, "recovery.json"),
|
|
4360
4694
|
`${JSON.stringify(
|
|
4361
4695
|
{
|
|
4362
4696
|
previousGeneration: previous.directory,
|
|
@@ -4372,21 +4706,21 @@ var createStore = (configDir, lock) => {
|
|
|
4372
4706
|
syncDirectory(directory);
|
|
4373
4707
|
syncDirectory(backups);
|
|
4374
4708
|
syncDirectory(configDir);
|
|
4375
|
-
const partialFile =
|
|
4709
|
+
const partialFile = path10.join(directory, "database.sql.partial");
|
|
4376
4710
|
return {
|
|
4377
4711
|
directory,
|
|
4378
4712
|
partialFile,
|
|
4379
4713
|
complete: () => whileOwned(() => {
|
|
4380
|
-
if (
|
|
4714
|
+
if (fs10.statSync(partialFile).size === 0)
|
|
4381
4715
|
throw new ServerStoreError("Database backup is empty; refusing target startup.");
|
|
4382
|
-
const fd =
|
|
4716
|
+
const fd = fs10.openSync(partialFile, "r");
|
|
4383
4717
|
try {
|
|
4384
|
-
|
|
4385
|
-
|
|
4718
|
+
fs10.fchmodSync(fd, 384);
|
|
4719
|
+
fs10.fsyncSync(fd);
|
|
4386
4720
|
} finally {
|
|
4387
|
-
|
|
4721
|
+
fs10.closeSync(fd);
|
|
4388
4722
|
}
|
|
4389
|
-
|
|
4723
|
+
fs10.renameSync(partialFile, path10.join(directory, "database.sql"));
|
|
4390
4724
|
syncDirectory(directory);
|
|
4391
4725
|
})
|
|
4392
4726
|
};
|
|
@@ -4395,7 +4729,7 @@ var createStore = (configDir, lock) => {
|
|
|
4395
4729
|
};
|
|
4396
4730
|
var refuseFlatLayout = (configDir) => {
|
|
4397
4731
|
if (["server.json", "server.env", "compose.yaml", "postgres-init.sh"].some(
|
|
4398
|
-
(name) =>
|
|
4732
|
+
(name) => fs10.existsSync(path10.join(configDir, name))
|
|
4399
4733
|
)) {
|
|
4400
4734
|
throw new ServerStoreError(
|
|
4401
4735
|
"Unreleased flat server configuration found. Preserve its credentials and volumes; migrate it to the generation layout before retrying. Setup will not replace this identity."
|
|
@@ -4403,7 +4737,7 @@ var refuseFlatLayout = (configDir) => {
|
|
|
4403
4737
|
}
|
|
4404
4738
|
};
|
|
4405
4739
|
var withServerStore = async (directory, operation, options = {}) => {
|
|
4406
|
-
const configDir =
|
|
4740
|
+
const configDir = path10.resolve(directory);
|
|
4407
4741
|
let lock;
|
|
4408
4742
|
let result;
|
|
4409
4743
|
try {
|
|
@@ -4420,9 +4754,9 @@ var withServerStore = async (directory, operation, options = {}) => {
|
|
|
4420
4754
|
};
|
|
4421
4755
|
|
|
4422
4756
|
// src/version.ts
|
|
4423
|
-
import * as
|
|
4757
|
+
import * as fs11 from "node:fs";
|
|
4424
4758
|
var cliVersion = () => {
|
|
4425
|
-
const raw =
|
|
4759
|
+
const raw = fs11.readFileSync(new URL("../package.json", import.meta.url), "utf8");
|
|
4426
4760
|
const parsed = JSON.parse(raw);
|
|
4427
4761
|
if (typeof parsed === "object" && parsed !== null && "version" in parsed) {
|
|
4428
4762
|
const version = parsed.version;
|
|
@@ -4554,21 +4888,21 @@ var parseSetupOptions = (args) => {
|
|
|
4554
4888
|
previous.push(value);
|
|
4555
4889
|
values.set(flag, previous);
|
|
4556
4890
|
}
|
|
4557
|
-
const
|
|
4558
|
-
const version =
|
|
4559
|
-
const appPort =
|
|
4560
|
-
const sshPort =
|
|
4891
|
+
const flagValue3 = (flag) => values.get(flag)?.[0];
|
|
4892
|
+
const version = flagValue3("--version");
|
|
4893
|
+
const appPort = flagValue3("--port");
|
|
4894
|
+
const sshPort = flagValue3("--ssh-port");
|
|
4561
4895
|
const origins = values.get("--origin");
|
|
4562
4896
|
return {
|
|
4563
|
-
context:
|
|
4897
|
+
context: flagValue3("--context"),
|
|
4564
4898
|
version: version === void 0 ? void 0 : parseVersion(version),
|
|
4565
|
-
bind:
|
|
4566
|
-
url:
|
|
4899
|
+
bind: flagValue3("--bind"),
|
|
4900
|
+
url: flagValue3("--url"),
|
|
4567
4901
|
origins: origins === void 0 ? void 0 : origins,
|
|
4568
4902
|
appPort: appPort === void 0 ? void 0 : parsePort(appPort, "--port"),
|
|
4569
4903
|
sshPort: sshPort === void 0 ? void 0 : parsePort(sshPort, "--ssh-port"),
|
|
4570
|
-
dockerSocket:
|
|
4571
|
-
assetsDir:
|
|
4904
|
+
dockerSocket: flagValue3("--docker-socket"),
|
|
4905
|
+
assetsDir: flagValue3("--assets-dir"),
|
|
4572
4906
|
offline: values.has("--offline")
|
|
4573
4907
|
};
|
|
4574
4908
|
};
|
|
@@ -5125,7 +5459,7 @@ var resolveServerVersion = async (runtime, options, existing) => {
|
|
|
5125
5459
|
return version;
|
|
5126
5460
|
};
|
|
5127
5461
|
var parseDockerSocketPath = (socket) => {
|
|
5128
|
-
if (!
|
|
5462
|
+
if (!path11.isAbsolute(socket) || /[\r\n$'"#:\\]/.test(socket)) {
|
|
5129
5463
|
throw setupError(
|
|
5130
5464
|
"--docker-socket must be an absolute path without line breaks or Compose interpolation characters."
|
|
5131
5465
|
);
|
|
@@ -5151,8 +5485,8 @@ var resolveAssets = async (runtime, serverVersion, existing, store, options) =>
|
|
|
5151
5485
|
if (options.assetsDir !== void 0) {
|
|
5152
5486
|
try {
|
|
5153
5487
|
const [compose2, postgresInit2] = await Promise.all([
|
|
5154
|
-
readFile(
|
|
5155
|
-
readFile(
|
|
5488
|
+
readFile(path11.join(options.assetsDir, COMPOSE_ASSET), "utf8"),
|
|
5489
|
+
readFile(path11.join(options.assetsDir, POSTGRES_INIT_ASSET), "utf8")
|
|
5156
5490
|
]);
|
|
5157
5491
|
validateComposeAsset(compose2);
|
|
5158
5492
|
validatePostgresAsset(postgresInit2);
|
|
@@ -5344,6 +5678,19 @@ var setupServer = async (args, runtime, store) => {
|
|
|
5344
5678
|
};
|
|
5345
5679
|
const assets = await resolveAssets(runtime, serverVersion, existing, store, options);
|
|
5346
5680
|
const bucket = composeBucket(assets.compose);
|
|
5681
|
+
if (bucket !== void 0 && runtime.dockerDaemonFacts !== void 0) {
|
|
5682
|
+
const info = await runtime.run("docker", [
|
|
5683
|
+
"--context",
|
|
5684
|
+
selectedContext.name,
|
|
5685
|
+
"info",
|
|
5686
|
+
"--format",
|
|
5687
|
+
"{{json .}}"
|
|
5688
|
+
]);
|
|
5689
|
+
const line = dockerShutdownSetupLine(
|
|
5690
|
+
readShutdownTimeout(runtime.dockerDaemonFacts(info.status === 0 ? info.stdout : null))
|
|
5691
|
+
);
|
|
5692
|
+
if (line !== null) runtime.writeLine(line);
|
|
5693
|
+
}
|
|
5347
5694
|
const config = {
|
|
5348
5695
|
...configWithoutBucket,
|
|
5349
5696
|
...bucket === void 0 ? {} : { bucket }
|
|
@@ -5665,10 +6012,10 @@ var manageServer = async (command, args, runtime, store) => {
|
|
|
5665
6012
|
};
|
|
5666
6013
|
var nodeServerRuntime = () => {
|
|
5667
6014
|
const environment = { ...process.env };
|
|
5668
|
-
const home =
|
|
5669
|
-
const configHome = environment["XDG_CONFIG_HOME"] ??
|
|
6015
|
+
const home = os7.homedir();
|
|
6016
|
+
const configHome = environment["XDG_CONFIG_HOME"] ?? path11.join(home, ".config");
|
|
5670
6017
|
return {
|
|
5671
|
-
configDir:
|
|
6018
|
+
configDir: path11.join(configHome, "mend"),
|
|
5672
6019
|
platform: process.platform,
|
|
5673
6020
|
cliVersion: cliVersion(),
|
|
5674
6021
|
run: (command, args, options) => runServerProcess(command, args, environment, options),
|
|
@@ -5687,7 +6034,8 @@ var nodeServerRuntime = () => {
|
|
|
5687
6034
|
randomBytes,
|
|
5688
6035
|
sleep: (milliseconds) => new Promise((resolve7) => setTimeout(resolve7, milliseconds)),
|
|
5689
6036
|
writeLine: (line) => process.stdout.write(`${line}
|
|
5690
|
-
`)
|
|
6037
|
+
`),
|
|
6038
|
+
dockerDaemonFacts: hostDockerDaemonFacts
|
|
5691
6039
|
};
|
|
5692
6040
|
};
|
|
5693
6041
|
var serverCommand = async (args, runtime = nodeServerRuntime()) => {
|
|
@@ -6120,8 +6468,8 @@ var createServiceTunnels = (options) => {
|
|
|
6120
6468
|
};
|
|
6121
6469
|
|
|
6122
6470
|
// src/skills.ts
|
|
6123
|
-
import * as
|
|
6124
|
-
import * as
|
|
6471
|
+
import * as fs12 from "node:fs";
|
|
6472
|
+
import * as path12 from "node:path";
|
|
6125
6473
|
var DEFAULT_SKILLS_DIR = ".agents/skills";
|
|
6126
6474
|
var MAX_SKILL_FILES = 64;
|
|
6127
6475
|
var MAX_SKILL_FILE_BYTES = 512 * 1024;
|
|
@@ -6144,12 +6492,12 @@ var readFrontmatterDescription = (contents) => {
|
|
|
6144
6492
|
var walkFiles = (root) => {
|
|
6145
6493
|
const found = [];
|
|
6146
6494
|
const visit = (dir) => {
|
|
6147
|
-
for (const entry of
|
|
6495
|
+
for (const entry of fs12.readdirSync(dir, { withFileTypes: true })) {
|
|
6148
6496
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
6149
|
-
const absolute =
|
|
6497
|
+
const absolute = path12.join(dir, entry.name);
|
|
6150
6498
|
let stat;
|
|
6151
6499
|
try {
|
|
6152
|
-
stat =
|
|
6500
|
+
stat = fs12.statSync(absolute);
|
|
6153
6501
|
} catch {
|
|
6154
6502
|
continue;
|
|
6155
6503
|
}
|
|
@@ -6163,7 +6511,7 @@ var walkFiles = (root) => {
|
|
|
6163
6511
|
var scanSkillLibrary = (root) => {
|
|
6164
6512
|
let entries;
|
|
6165
6513
|
try {
|
|
6166
|
-
entries =
|
|
6514
|
+
entries = fs12.readdirSync(root, { withFileTypes: true });
|
|
6167
6515
|
} catch {
|
|
6168
6516
|
return { error: `${root} is not a readable directory` };
|
|
6169
6517
|
}
|
|
@@ -6171,23 +6519,23 @@ var scanSkillLibrary = (root) => {
|
|
|
6171
6519
|
const notes = [];
|
|
6172
6520
|
for (const entry of entries.toSorted((a, b) => a.name.localeCompare(b.name))) {
|
|
6173
6521
|
if (entry.name.startsWith(".")) continue;
|
|
6174
|
-
const skillDir =
|
|
6522
|
+
const skillDir = path12.join(root, entry.name);
|
|
6175
6523
|
let stat;
|
|
6176
6524
|
try {
|
|
6177
|
-
stat =
|
|
6525
|
+
stat = fs12.statSync(skillDir);
|
|
6178
6526
|
} catch {
|
|
6179
6527
|
continue;
|
|
6180
6528
|
}
|
|
6181
6529
|
if (!stat.isDirectory()) continue;
|
|
6182
|
-
if (!
|
|
6530
|
+
if (!fs12.existsSync(path12.join(skillDir, "SKILL.md"))) {
|
|
6183
6531
|
notes.push({ skill: entry.name, message: "no SKILL.md \u2014 skipped" });
|
|
6184
6532
|
continue;
|
|
6185
6533
|
}
|
|
6186
6534
|
const files = [];
|
|
6187
6535
|
let bytes = 0;
|
|
6188
6536
|
for (const absolute of walkFiles(skillDir)) {
|
|
6189
|
-
const relative4 =
|
|
6190
|
-
const buffer =
|
|
6537
|
+
const relative4 = path12.relative(skillDir, absolute).split(path12.sep).join("/");
|
|
6538
|
+
const buffer = fs12.readFileSync(absolute);
|
|
6191
6539
|
if (buffer.byteLength > MAX_SKILL_FILE_BYTES) {
|
|
6192
6540
|
notes.push({ skill: entry.name, message: `${relative4} is over 512KB \u2014 skipped` });
|
|
6193
6541
|
continue;
|
|
@@ -6222,16 +6570,16 @@ var scanSkillLibrary = (root) => {
|
|
|
6222
6570
|
};
|
|
6223
6571
|
|
|
6224
6572
|
// src/ssh-setup.ts
|
|
6225
|
-
import * as
|
|
6226
|
-
import * as
|
|
6573
|
+
import * as os9 from "node:os";
|
|
6574
|
+
import * as path14 from "node:path";
|
|
6227
6575
|
|
|
6228
6576
|
// ../../packages/workspace-ssh/src/index.ts
|
|
6229
|
-
import { spawnSync as
|
|
6577
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
6230
6578
|
import { createHash as createHash3 } from "node:crypto";
|
|
6231
|
-
import * as
|
|
6579
|
+
import * as fs13 from "node:fs";
|
|
6232
6580
|
import { isIP as isIP2 } from "node:net";
|
|
6233
|
-
import * as
|
|
6234
|
-
import * as
|
|
6581
|
+
import * as os8 from "node:os";
|
|
6582
|
+
import * as path13 from "node:path";
|
|
6235
6583
|
var LEGACY_BLOCK_BEGIN = "# >>> mend workspace ssh (managed by `mend ssh setup`) >>>";
|
|
6236
6584
|
var LEGACY_BLOCK_END = "# <<< mend workspace ssh <<<";
|
|
6237
6585
|
var WorkspaceSshError = class extends Error {
|
|
@@ -6318,8 +6666,8 @@ var blockBegin = (alias) => `# >>> mend workspace ssh ${alias} (managed) >>>`;
|
|
|
6318
6666
|
var blockEnd = (alias) => `# <<< mend workspace ssh ${alias} <<<`;
|
|
6319
6667
|
var hasControls = (value) => /[\p{Cc}\p{Zl}\p{Zp}]/u.test(value);
|
|
6320
6668
|
var parseIdentityPath = (value) => {
|
|
6321
|
-
const resolved =
|
|
6322
|
-
value.startsWith("~/") ?
|
|
6669
|
+
const resolved = path13.resolve(
|
|
6670
|
+
value.startsWith("~/") ? path13.join(os8.homedir(), value.slice(2)) : value
|
|
6323
6671
|
);
|
|
6324
6672
|
if (value === "" || hasControls(value) || hasControls(resolved) || value.includes("${") || resolved.includes("${")) {
|
|
6325
6673
|
return failure(
|
|
@@ -6465,7 +6813,7 @@ var workspaceSshPublicKeyFingerprint = (publicKey) => {
|
|
|
6465
6813
|
var publicKeyIdentity = (publicKey) => publicKey.trim().split(/\s+/).slice(0, 2).join(" ");
|
|
6466
6814
|
var agentKeys = () => {
|
|
6467
6815
|
if (!process.env["SSH_AUTH_SOCK"]) return [];
|
|
6468
|
-
const listed =
|
|
6816
|
+
const listed = spawnSync4("ssh-add", ["-L"], {
|
|
6469
6817
|
encoding: "utf8",
|
|
6470
6818
|
timeout: 3e3,
|
|
6471
6819
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -6475,7 +6823,7 @@ var agentKeys = () => {
|
|
|
6475
6823
|
};
|
|
6476
6824
|
var readText = (file) => {
|
|
6477
6825
|
try {
|
|
6478
|
-
return success(
|
|
6826
|
+
return success(fs13.readFileSync(file, "utf8"));
|
|
6479
6827
|
} catch (cause) {
|
|
6480
6828
|
const error = cause instanceof Error && "code" in cause ? cause.code : void 0;
|
|
6481
6829
|
return error === "ENOENT" ? success("") : failure(new WorkspaceSshError("key", `Could not read ${file}.`, cause));
|
|
@@ -6502,7 +6850,7 @@ var readRequiredKey = (keyPath, source) => {
|
|
|
6502
6850
|
const privatePath = publicPath.slice(0, -".pub".length);
|
|
6503
6851
|
const read = readText(publicPath);
|
|
6504
6852
|
if (!read.ok) return read;
|
|
6505
|
-
const derived =
|
|
6853
|
+
const derived = spawnSync4("ssh-keygen", ["-y", "-P", "", "-f", privatePath], {
|
|
6506
6854
|
encoding: "utf8",
|
|
6507
6855
|
timeout: 3e3,
|
|
6508
6856
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -6533,19 +6881,19 @@ var readRequiredKey = (keyPath, source) => {
|
|
|
6533
6881
|
var readOptionalKey = (keyPath) => {
|
|
6534
6882
|
const parsed = parseIdentityPath(keyPath);
|
|
6535
6883
|
if (!parsed.ok) return parsed;
|
|
6536
|
-
if (!
|
|
6884
|
+
if (!fs13.existsSync(parsed.value) && !fs13.existsSync(publicKeyPath(parsed.value)))
|
|
6537
6885
|
return success(null);
|
|
6538
6886
|
return readRequiredKey(parsed.value, "existing");
|
|
6539
6887
|
};
|
|
6540
6888
|
var pinAgentKey = (configHome, publicKey) => {
|
|
6541
|
-
const identityFile =
|
|
6889
|
+
const identityFile = path13.resolve(
|
|
6542
6890
|
configHome,
|
|
6543
6891
|
"ssh",
|
|
6544
6892
|
`agent-${hashIdentity(publicKeyIdentity(publicKey))}.pub`
|
|
6545
6893
|
);
|
|
6546
6894
|
try {
|
|
6547
|
-
|
|
6548
|
-
|
|
6895
|
+
fs13.mkdirSync(path13.dirname(identityFile), { recursive: true, mode: 448 });
|
|
6896
|
+
fs13.writeFileSync(identityFile, `${publicKey}
|
|
6549
6897
|
`, { mode: 384 });
|
|
6550
6898
|
return success({ publicKey, identityFile, source: "agent" });
|
|
6551
6899
|
} catch (cause) {
|
|
@@ -6556,15 +6904,15 @@ var pinAgentKey = (configHome, publicKey) => {
|
|
|
6556
6904
|
};
|
|
6557
6905
|
var generateDedicatedKey = (privatePath) => {
|
|
6558
6906
|
try {
|
|
6559
|
-
|
|
6907
|
+
fs13.mkdirSync(path13.dirname(privatePath), { recursive: true, mode: 448 });
|
|
6560
6908
|
} catch (cause) {
|
|
6561
6909
|
return failure(
|
|
6562
|
-
new WorkspaceSshError("key", `Could not create ${
|
|
6910
|
+
new WorkspaceSshError("key", `Could not create ${path13.dirname(privatePath)}.`, cause)
|
|
6563
6911
|
);
|
|
6564
6912
|
}
|
|
6565
|
-
const generated =
|
|
6913
|
+
const generated = spawnSync4(
|
|
6566
6914
|
"ssh-keygen",
|
|
6567
|
-
["-t", "ed25519", "-f", privatePath, "-N", "", "-C", `mend-${
|
|
6915
|
+
["-t", "ed25519", "-f", privatePath, "-N", "", "-C", `mend-${os8.hostname()}`],
|
|
6568
6916
|
{ encoding: "utf8", timeout: 3e3, stdio: ["ignore", "pipe", "pipe"] }
|
|
6569
6917
|
);
|
|
6570
6918
|
if (generated.status !== 0) {
|
|
@@ -6588,7 +6936,7 @@ var generateDedicatedKey = (privatePath) => {
|
|
|
6588
6936
|
return fresh;
|
|
6589
6937
|
};
|
|
6590
6938
|
var dedicatedKey = (configHome, create) => {
|
|
6591
|
-
const privatePath =
|
|
6939
|
+
const privatePath = path13.join(configHome, "ssh", "id_ed25519");
|
|
6592
6940
|
const existing = readOptionalKey(privatePath);
|
|
6593
6941
|
if (existing.ok === false) return failure(existing.error);
|
|
6594
6942
|
if (existing.value !== null || !create) return existing;
|
|
@@ -6636,10 +6984,10 @@ var writeWorkspaceSshConfig = (configFile, target, identityFile) => {
|
|
|
6636
6984
|
const reconciled = reconcileWorkspaceSshConfig(before.value, target, identityFile);
|
|
6637
6985
|
if (reconciled.ok === false) return failure(reconciled.error);
|
|
6638
6986
|
try {
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6987
|
+
fs13.mkdirSync(path13.dirname(configFile), { recursive: true, mode: 448 });
|
|
6988
|
+
fs13.chmodSync(path13.dirname(configFile), 448);
|
|
6989
|
+
fs13.writeFileSync(configFile, reconciled.value, { mode: 384 });
|
|
6990
|
+
fs13.chmodSync(configFile, 384);
|
|
6643
6991
|
return success(void 0);
|
|
6644
6992
|
} catch (cause) {
|
|
6645
6993
|
return failure(new WorkspaceSshError("config", `Could not write ${configFile}.`, cause));
|
|
@@ -6652,8 +7000,8 @@ var dim5 = ansi("2");
|
|
|
6652
7000
|
var green5 = ansi("32");
|
|
6653
7001
|
var warn = ansi("33");
|
|
6654
7002
|
var say5 = (line) => console.log(line);
|
|
6655
|
-
var sshConfigPath = () =>
|
|
6656
|
-
var
|
|
7003
|
+
var sshConfigPath = () => path14.join(os9.homedir(), ".ssh", "config");
|
|
7004
|
+
var flagValue2 = (args, flag) => {
|
|
6657
7005
|
const index = args.indexOf(flag);
|
|
6658
7006
|
return index === -1 || args[index + 1] === void 0 ? null : String(args[index + 1]);
|
|
6659
7007
|
};
|
|
@@ -6670,7 +7018,7 @@ var showStatus = async (api2, cliHome, serverUrl, args) => {
|
|
|
6670
7018
|
const parsedTarget = parseWorkspaceSshTarget({
|
|
6671
7019
|
serverUrl,
|
|
6672
7020
|
publishedPort: view.gateway.port,
|
|
6673
|
-
hostnameOverride:
|
|
7021
|
+
hostnameOverride: flagValue2(args, "--host")
|
|
6674
7022
|
});
|
|
6675
7023
|
if (parsedTarget.ok === false) return showFailure(parsedTarget.error.message);
|
|
6676
7024
|
const config = readWorkspaceSshConfig(sshConfigPath());
|
|
@@ -6707,14 +7055,14 @@ var setup = async (api2, cliHome, serverUrl, args) => {
|
|
|
6707
7055
|
const parsedTarget = parseWorkspaceSshTarget({
|
|
6708
7056
|
serverUrl,
|
|
6709
7057
|
publishedPort: view.gateway.port,
|
|
6710
|
-
hostnameOverride:
|
|
7058
|
+
hostnameOverride: flagValue2(args, "--host")
|
|
6711
7059
|
});
|
|
6712
7060
|
if (parsedTarget.ok === false) return showFailure(parsedTarget.error.message);
|
|
6713
7061
|
const config = readWorkspaceSshConfig(sshConfigPath());
|
|
6714
7062
|
if (config.ok === false) return showFailure(config.error.message);
|
|
6715
7063
|
const picked = pickWorkspaceSshKey({
|
|
6716
7064
|
configHome: cliHome,
|
|
6717
|
-
explicitKeyPath:
|
|
7065
|
+
explicitKeyPath: flagValue2(args, "--key"),
|
|
6718
7066
|
configuredIdentityFile: configuredWorkspaceSshIdentityFile(config.value, parsedTarget.value),
|
|
6719
7067
|
create: true
|
|
6720
7068
|
});
|
|
@@ -6722,7 +7070,7 @@ var setup = async (api2, cliHome, serverUrl, args) => {
|
|
|
6722
7070
|
if (picked.value === null) return showFailure("No workspace SSH key is available.");
|
|
6723
7071
|
const registered = await api2("POST", "/workspace-ssh/keys", {
|
|
6724
7072
|
publicKey: picked.value.publicKey,
|
|
6725
|
-
name:
|
|
7073
|
+
name: os9.hostname()
|
|
6726
7074
|
});
|
|
6727
7075
|
const sourceLabel = {
|
|
6728
7076
|
explicit: "from --key",
|
|
@@ -6764,8 +7112,8 @@ var sshCommand = async (args, api2, cliHome, serverUrl) => {
|
|
|
6764
7112
|
};
|
|
6765
7113
|
|
|
6766
7114
|
// src/uninstall.ts
|
|
6767
|
-
import * as
|
|
6768
|
-
import * as
|
|
7115
|
+
import * as fs14 from "node:fs";
|
|
7116
|
+
import * as path15 from "node:path";
|
|
6769
7117
|
var UNINSTALL_USAGE = "usage: mend uninstall [--all | --server | --home] [--yes]";
|
|
6770
7118
|
var parseUninstallArgs = (args) => {
|
|
6771
7119
|
let scope = null;
|
|
@@ -6787,14 +7135,14 @@ var parseUninstallArgs = (args) => {
|
|
|
6787
7135
|
var SERVER_FILES = ["identity.env", "active", "generations", "backups"];
|
|
6788
7136
|
var countEntries = (directory) => {
|
|
6789
7137
|
try {
|
|
6790
|
-
return
|
|
7138
|
+
return fs14.readdirSync(directory).length;
|
|
6791
7139
|
} catch {
|
|
6792
7140
|
return 0;
|
|
6793
7141
|
}
|
|
6794
7142
|
};
|
|
6795
7143
|
var exists = (file) => {
|
|
6796
7144
|
try {
|
|
6797
|
-
|
|
7145
|
+
fs14.lstatSync(file);
|
|
6798
7146
|
return true;
|
|
6799
7147
|
} catch {
|
|
6800
7148
|
return false;
|
|
@@ -6803,7 +7151,7 @@ var exists = (file) => {
|
|
|
6803
7151
|
var managedBlockCount = (sshConfigFile) => {
|
|
6804
7152
|
let config;
|
|
6805
7153
|
try {
|
|
6806
|
-
config =
|
|
7154
|
+
config = fs14.readFileSync(sshConfigFile, "utf8");
|
|
6807
7155
|
} catch {
|
|
6808
7156
|
return 0;
|
|
6809
7157
|
}
|
|
@@ -6827,27 +7175,27 @@ var describeUninstall = async (runtime, scope) => {
|
|
|
6827
7175
|
if (!read.error.message.startsWith("No Mend server is configured")) throw read.error;
|
|
6828
7176
|
server = "none";
|
|
6829
7177
|
} else if (read.value === null) {
|
|
6830
|
-
server = exists(
|
|
7178
|
+
server = exists(path15.join(configDir, "identity.env")) ? {
|
|
6831
7179
|
version: "(no active generation)",
|
|
6832
7180
|
appUrl: "",
|
|
6833
7181
|
dockerContext: "",
|
|
6834
|
-
generations: countEntries(
|
|
6835
|
-
backups: countEntries(
|
|
7182
|
+
generations: countEntries(path15.join(configDir, "generations")),
|
|
7183
|
+
backups: countEntries(path15.join(configDir, "backups"))
|
|
6836
7184
|
} : "none";
|
|
6837
7185
|
} else {
|
|
6838
7186
|
server = {
|
|
6839
7187
|
version: read.value.config.serverVersion,
|
|
6840
7188
|
appUrl: read.value.config.appUrl,
|
|
6841
7189
|
dockerContext: read.value.config.dockerContext,
|
|
6842
|
-
generations: countEntries(
|
|
6843
|
-
backups: countEntries(
|
|
7190
|
+
generations: countEntries(path15.join(configDir, "generations")),
|
|
7191
|
+
backups: countEntries(path15.join(configDir, "backups"))
|
|
6844
7192
|
};
|
|
6845
7193
|
}
|
|
6846
7194
|
}
|
|
6847
7195
|
let home = null;
|
|
6848
7196
|
if (scope !== "server") {
|
|
6849
|
-
const cliConfig =
|
|
6850
|
-
const sshDirectory =
|
|
7197
|
+
const cliConfig = path15.join(runtime.cliHome, "cli.json");
|
|
7198
|
+
const sshDirectory = path15.join(runtime.cliHome, "ssh");
|
|
6851
7199
|
home = {
|
|
6852
7200
|
cliConfig: exists(cliConfig) ? cliConfig : null,
|
|
6853
7201
|
sshDirectory: exists(sshDirectory) ? sshDirectory : null,
|
|
@@ -6979,7 +7327,7 @@ var removeServer = async (runtime, failures, leftovers) => {
|
|
|
6979
7327
|
}
|
|
6980
7328
|
}
|
|
6981
7329
|
for (const name of SERVER_FILES) {
|
|
6982
|
-
|
|
7330
|
+
fs14.rmSync(path15.join(configDir, name), { recursive: true, force: true });
|
|
6983
7331
|
}
|
|
6984
7332
|
server.writeLine(`removed ${configDir}/{identity.env, active, generations, backups}`);
|
|
6985
7333
|
},
|
|
@@ -6990,20 +7338,20 @@ var removeServer = async (runtime, failures, leftovers) => {
|
|
|
6990
7338
|
var removeHome = async (runtime, plan, failures, leftovers) => {
|
|
6991
7339
|
const { server } = runtime;
|
|
6992
7340
|
if (plan.cliConfig !== null) {
|
|
6993
|
-
|
|
7341
|
+
fs14.rmSync(plan.cliConfig, { force: true });
|
|
6994
7342
|
server.writeLine(`removed ${plan.cliConfig}`);
|
|
6995
7343
|
}
|
|
6996
7344
|
if (plan.sshDirectory !== null) {
|
|
6997
|
-
|
|
7345
|
+
fs14.rmSync(plan.sshDirectory, { recursive: true, force: true });
|
|
6998
7346
|
server.writeLine(`removed ${plan.sshDirectory}`);
|
|
6999
7347
|
}
|
|
7000
7348
|
if (plan.managedSshBlocks > 0) {
|
|
7001
7349
|
try {
|
|
7002
|
-
const before =
|
|
7350
|
+
const before = fs14.readFileSync(runtime.sshConfigFile, "utf8");
|
|
7003
7351
|
const stripped = stripManagedWorkspaceSshBlocks(before);
|
|
7004
7352
|
if (!stripped.ok) throw stripped.error;
|
|
7005
7353
|
if (stripped.value.removed > 0) {
|
|
7006
|
-
|
|
7354
|
+
fs14.writeFileSync(runtime.sshConfigFile, stripped.value.config, { mode: 384 });
|
|
7007
7355
|
server.writeLine(
|
|
7008
7356
|
`removed ${plural(stripped.value.removed, "managed block")} from ${runtime.sshConfigFile}`
|
|
7009
7357
|
);
|
|
@@ -7016,12 +7364,12 @@ var removeHome = async (runtime, plan, failures, leftovers) => {
|
|
|
7016
7364
|
}
|
|
7017
7365
|
let remaining = [];
|
|
7018
7366
|
try {
|
|
7019
|
-
remaining =
|
|
7367
|
+
remaining = fs14.readdirSync(runtime.cliHome);
|
|
7020
7368
|
} catch {
|
|
7021
7369
|
return;
|
|
7022
7370
|
}
|
|
7023
7371
|
if (remaining.length === 0) {
|
|
7024
|
-
|
|
7372
|
+
fs14.rmdirSync(runtime.cliHome);
|
|
7025
7373
|
server.writeLine(`removed ${runtime.cliHome}`);
|
|
7026
7374
|
} else {
|
|
7027
7375
|
leftovers.push(
|
|
@@ -7085,19 +7433,19 @@ var upgradeUrl = async (input) => {
|
|
|
7085
7433
|
// src/main.ts
|
|
7086
7434
|
var mendCliHome = () => {
|
|
7087
7435
|
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
7088
|
-
const preferred =
|
|
7089
|
-
xdg === void 0 || xdg === "" ?
|
|
7436
|
+
const preferred = path16.join(
|
|
7437
|
+
xdg === void 0 || xdg === "" ? path16.join(os10.homedir(), ".config") : xdg,
|
|
7090
7438
|
"mend"
|
|
7091
7439
|
);
|
|
7092
|
-
const legacy =
|
|
7093
|
-
return !
|
|
7440
|
+
const legacy = path16.join(os10.homedir(), ".mend");
|
|
7441
|
+
return !fs15.existsSync(preferred) && fs15.existsSync(legacy) ? legacy : preferred;
|
|
7094
7442
|
};
|
|
7095
|
-
var CONFIG_PATH =
|
|
7443
|
+
var CONFIG_PATH = path16.join(mendCliHome(), "cli.json");
|
|
7096
7444
|
var loadConfig = () => {
|
|
7097
7445
|
let fileConfig = {};
|
|
7098
|
-
if (
|
|
7446
|
+
if (fs15.existsSync(CONFIG_PATH)) {
|
|
7099
7447
|
try {
|
|
7100
|
-
fileConfig = JSON.parse(
|
|
7448
|
+
fileConfig = JSON.parse(fs15.readFileSync(CONFIG_PATH, "utf8"));
|
|
7101
7449
|
} catch {
|
|
7102
7450
|
fail6(`could not parse ${CONFIG_PATH}`);
|
|
7103
7451
|
}
|
|
@@ -7258,8 +7606,8 @@ var withSpinner = async (label, work) => {
|
|
|
7258
7606
|
const started = Date.now();
|
|
7259
7607
|
let frame = 0;
|
|
7260
7608
|
const timer = setInterval(() => {
|
|
7261
|
-
const
|
|
7262
|
-
process.stdout.write(`\r ${frames[frame % frames.length]} ${label} ${dim6(`${
|
|
7609
|
+
const seconds2 = Math.round((Date.now() - started) / 1e3);
|
|
7610
|
+
process.stdout.write(`\r ${frames[frame % frames.length]} ${label} ${dim6(`${seconds2}s`)} `);
|
|
7263
7611
|
frame += 1;
|
|
7264
7612
|
}, 120);
|
|
7265
7613
|
try {
|
|
@@ -7288,7 +7636,7 @@ var adopt = async (config, args) => {
|
|
|
7288
7636
|
}
|
|
7289
7637
|
const sourceIssue = repositoryCloneUrlIssue(source);
|
|
7290
7638
|
if (sourceIssue !== null) return fail6(sourceIssue);
|
|
7291
|
-
const projectName = name ?? normalizeProjectName(
|
|
7639
|
+
const projectName = name ?? normalizeProjectName(path16.basename(source, ".git"));
|
|
7292
7640
|
const auth = authFlagIndex !== -1 && args[authFlagIndex + 1] !== void 0 ? String(args[authFlagIndex + 1]) : null;
|
|
7293
7641
|
if (auth !== null && auth !== "ambient" && auth !== "mend-key" && auth !== "bridge") {
|
|
7294
7642
|
return fail6(`--auth takes "ambient", "mend-key", or "bridge", not "${auth}"`);
|
|
@@ -7337,7 +7685,7 @@ var findProject = async (config, explicit, adoptCwd = false) => {
|
|
|
7337
7685
|
const sourceIssue = repositoryCloneUrlIssue(facts.originUrl);
|
|
7338
7686
|
if (sourceIssue !== null) return fail6(sourceIssue);
|
|
7339
7687
|
const adopted = await api(config, "POST", "/projects", {
|
|
7340
|
-
name: normalizeProjectName(
|
|
7688
|
+
name: normalizeProjectName(path16.basename(facts.repoRoot)),
|
|
7341
7689
|
source: facts.originUrl
|
|
7342
7690
|
});
|
|
7343
7691
|
say6(`${green6("\u2713")} adopted \xB7 ${adopted.name} \xB7 ${dim6(adopted.storePath)}`);
|
|
@@ -7805,6 +8153,10 @@ var stopSessions = async (config, targets, summarise) => {
|
|
|
7805
8153
|
if (hold !== null) {
|
|
7806
8154
|
say6(`${amber2(" " + hold)} \xB7 mend stop --services ${session.id.slice(0, 8)}`);
|
|
7807
8155
|
}
|
|
8156
|
+
const capture = after === null ? null : captureLineOf(after.session);
|
|
8157
|
+
if (capture !== null) {
|
|
8158
|
+
say6(`${amber2(" " + capture)} \xB7 the workspace stops once nothing is pending`);
|
|
8159
|
+
}
|
|
7808
8160
|
say6(`${cobalt2(" review")} \xB7 ${config.url}/sessions/${session.id}`);
|
|
7809
8161
|
}
|
|
7810
8162
|
if (summarise) {
|
|
@@ -8192,13 +8544,13 @@ var serviceRestart = async (config, args) => {
|
|
|
8192
8544
|
};
|
|
8193
8545
|
var serviceInit = async (args) => {
|
|
8194
8546
|
const root = gitTopLevel(process.cwd()) ?? process.cwd();
|
|
8195
|
-
const target =
|
|
8196
|
-
if (
|
|
8547
|
+
const target = path16.join(root, "mend.toml");
|
|
8548
|
+
if (fs15.existsSync(target)) {
|
|
8197
8549
|
return fail6(`${target} already exists \u2014 edit it directly (init never merges)`);
|
|
8198
8550
|
}
|
|
8199
|
-
const rootFiles =
|
|
8551
|
+
const rootFiles = fs15.readdirSync(root);
|
|
8200
8552
|
const proposals = [];
|
|
8201
|
-
const readRoot = (name) =>
|
|
8553
|
+
const readRoot = (name) => fs15.readFileSync(path16.join(root, name), "utf8");
|
|
8202
8554
|
if (rootFiles.includes("package.json")) {
|
|
8203
8555
|
proposals.push(...proposeFromPackageJson(readRoot("package.json"), rootFiles));
|
|
8204
8556
|
}
|
|
@@ -8208,13 +8560,13 @@ var serviceInit = async (args) => {
|
|
|
8208
8560
|
);
|
|
8209
8561
|
for (const glob of globs) {
|
|
8210
8562
|
const parent = glob.replace(/\/?\*+$/, "");
|
|
8211
|
-
const dirs = glob.endsWith("*") ?
|
|
8563
|
+
const dirs = glob.endsWith("*") ? fs15.existsSync(path16.join(root, parent)) ? fs15.readdirSync(path16.join(root, parent)).map((dir) => path16.join(parent, dir)) : [] : [glob];
|
|
8212
8564
|
for (const dir of dirs) {
|
|
8213
|
-
const manifest =
|
|
8214
|
-
if (!
|
|
8565
|
+
const manifest = path16.join(root, dir, "package.json");
|
|
8566
|
+
if (!fs15.existsSync(manifest)) continue;
|
|
8215
8567
|
for (const proposal of proposeFromWorkspacePackage(
|
|
8216
|
-
|
|
8217
|
-
|
|
8568
|
+
path16.basename(dir),
|
|
8569
|
+
fs15.readFileSync(manifest, "utf8"),
|
|
8218
8570
|
rootFiles
|
|
8219
8571
|
)) {
|
|
8220
8572
|
if (!proposals.some((existing) => existing.name === proposal.name)) {
|
|
@@ -8258,7 +8610,7 @@ var serviceInit = async (args) => {
|
|
|
8258
8610
|
return;
|
|
8259
8611
|
}
|
|
8260
8612
|
}
|
|
8261
|
-
|
|
8613
|
+
fs15.writeFileSync(target, toml);
|
|
8262
8614
|
say6(`${green6("\u2713")} wrote ${target} \u2014 commit it, then: mend service run ${proposals[0]?.name}`);
|
|
8263
8615
|
};
|
|
8264
8616
|
var tunnelServices = async (config, services, portOverride) => {
|
|
@@ -8362,23 +8714,23 @@ var serviceCommand = async (config, args) => {
|
|
|
8362
8714
|
}
|
|
8363
8715
|
};
|
|
8364
8716
|
var isProvider = (value) => value === "claude" || value === "codex" || value === "github";
|
|
8365
|
-
var readIfExists = (file) =>
|
|
8717
|
+
var readIfExists = (file) => fs15.existsSync(file) ? fs15.readFileSync(file, "utf8") : null;
|
|
8366
8718
|
var localCredential = (provider) => {
|
|
8367
|
-
const home =
|
|
8719
|
+
const home = os10.homedir();
|
|
8368
8720
|
switch (provider) {
|
|
8369
8721
|
case "codex":
|
|
8370
8722
|
return readIfExists(
|
|
8371
|
-
|
|
8723
|
+
path16.join(process.env["CODEX_HOME"] ?? path16.join(home, ".codex"), "auth.json")
|
|
8372
8724
|
);
|
|
8373
8725
|
case "claude":
|
|
8374
8726
|
return readIfExists(
|
|
8375
|
-
|
|
8376
|
-
process.env["CLAUDE_CONFIG_DIR"] ??
|
|
8727
|
+
path16.join(
|
|
8728
|
+
process.env["CLAUDE_CONFIG_DIR"] ?? path16.join(home, ".claude"),
|
|
8377
8729
|
".credentials.json"
|
|
8378
8730
|
)
|
|
8379
8731
|
);
|
|
8380
8732
|
case "github": {
|
|
8381
|
-
const result =
|
|
8733
|
+
const result = spawnSync5("gh", ["auth", "token"], { encoding: "utf8" });
|
|
8382
8734
|
const token = result.status === 0 ? result.stdout.trim() : "";
|
|
8383
8735
|
return token === "" ? null : token;
|
|
8384
8736
|
}
|
|
@@ -8423,7 +8775,7 @@ var claudeGrantSecret = () => {
|
|
|
8423
8775
|
return read.kind === "missing" ? null : read.secret;
|
|
8424
8776
|
};
|
|
8425
8777
|
var doctorBundle = (config, args) => doctorBundleCommand(args, {
|
|
8426
|
-
defaultDir:
|
|
8778
|
+
defaultDir: path16.join(mendCliHome(), "bundles"),
|
|
8427
8779
|
now: () => /* @__PURE__ */ new Date(),
|
|
8428
8780
|
say: say6,
|
|
8429
8781
|
warn: (line) => process.stderr.write(`${line}
|
|
@@ -8487,7 +8839,7 @@ var claudeGrant = async () => {
|
|
|
8487
8839
|
return null;
|
|
8488
8840
|
}
|
|
8489
8841
|
}
|
|
8490
|
-
if (status.configDirectory !== null &&
|
|
8842
|
+
if (status.configDirectory !== null && path16.resolve(status.configDirectory) !== path16.resolve(dir)) {
|
|
8491
8843
|
fail6(
|
|
8492
8844
|
`claude: this Claude read ${status.configDirectory} instead of ${dir}, so a separate grant is not possible \u2014 connect the shared one deliberately with --use-my-login`
|
|
8493
8845
|
);
|
|
@@ -8550,7 +8902,7 @@ var connectCommand = async (config, args) => {
|
|
|
8550
8902
|
}
|
|
8551
8903
|
let secret;
|
|
8552
8904
|
if (flags.includes("--from-stdin")) {
|
|
8553
|
-
secret =
|
|
8905
|
+
secret = fs15.readFileSync(0, "utf8").trim();
|
|
8554
8906
|
if (secret === "") return fail6("nothing on stdin");
|
|
8555
8907
|
} else if (provider === "claude" && !flags.includes("--use-my-login")) {
|
|
8556
8908
|
secret = await claudeGrant();
|
|
@@ -8596,14 +8948,14 @@ var takeFlagValue4 = (args, flag) => {
|
|
|
8596
8948
|
return at !== -1 && args[at + 1] !== void 0 ? String(args[at + 1]) : null;
|
|
8597
8949
|
};
|
|
8598
8950
|
var saveCliConfig = (next) => {
|
|
8599
|
-
|
|
8600
|
-
|
|
8951
|
+
fs15.mkdirSync(path16.dirname(CONFIG_PATH), { recursive: true, mode: 448 });
|
|
8952
|
+
fs15.writeFileSync(
|
|
8601
8953
|
CONFIG_PATH,
|
|
8602
8954
|
`${JSON.stringify({ url: next.url, token: next.token, deviceId: next.deviceId }, null, 2)}
|
|
8603
8955
|
`,
|
|
8604
8956
|
{ mode: 384 }
|
|
8605
8957
|
);
|
|
8606
|
-
|
|
8958
|
+
fs15.chmodSync(CONFIG_PATH, 384);
|
|
8607
8959
|
};
|
|
8608
8960
|
var login = async (config, args) => {
|
|
8609
8961
|
await loginCommand(args, {
|
|
@@ -8616,7 +8968,7 @@ var login = async (config, args) => {
|
|
|
8616
8968
|
});
|
|
8617
8969
|
};
|
|
8618
8970
|
var logout = async (config) => {
|
|
8619
|
-
if (!
|
|
8971
|
+
if (!fs15.existsSync(CONFIG_PATH) && config.token === null) {
|
|
8620
8972
|
say6(dim6("nothing saved \u2014 already signed out"));
|
|
8621
8973
|
return;
|
|
8622
8974
|
}
|
|
@@ -8669,8 +9021,8 @@ var uninstallCommand = async (config, args) => {
|
|
|
8669
9021
|
const runtime = {
|
|
8670
9022
|
server,
|
|
8671
9023
|
cliHome: mendCliHome(),
|
|
8672
|
-
sshConfigFile:
|
|
8673
|
-
signedIn: config.token === null || !
|
|
9024
|
+
sshConfigFile: path16.join(os10.homedir(), ".ssh", "config"),
|
|
9025
|
+
signedIn: config.token === null || !fs15.existsSync(CONFIG_PATH) ? null : { url: config.url, deviceId: config.deviceId },
|
|
8674
9026
|
revokeDevice: async () => {
|
|
8675
9027
|
if (config.deviceId === null) return "no device id saved";
|
|
8676
9028
|
try {
|
|
@@ -8734,7 +9086,7 @@ var keysShare = async (config) => {
|
|
|
8734
9086
|
return fail6("SSH_AUTH_SOCK is not set \u2014 start (or plug in) your ssh-agent first");
|
|
8735
9087
|
}
|
|
8736
9088
|
await shareAgent({
|
|
8737
|
-
url: () => socketUrl(config, "keys-bridge", { host:
|
|
9089
|
+
url: () => socketUrl(config, "keys-bridge", { host: os10.hostname() }),
|
|
8738
9090
|
agentSock,
|
|
8739
9091
|
signal: new AbortController().signal,
|
|
8740
9092
|
onEvent: (event) => {
|
|
@@ -8864,7 +9216,7 @@ ${usageOf("dotfiles repo")}`);
|
|
|
8864
9216
|
var dotfilesSync = async (config, args) => {
|
|
8865
9217
|
const all = args.includes("--all");
|
|
8866
9218
|
const requestedPaths = args.filter((arg) => !arg.startsWith("--"));
|
|
8867
|
-
const home =
|
|
9219
|
+
const home = os10.homedir();
|
|
8868
9220
|
if (!all && requestedPaths.length === 0) {
|
|
8869
9221
|
const found = scanDotfileCandidates(home);
|
|
8870
9222
|
if (found.length === 0) {
|
|
@@ -8891,13 +9243,13 @@ var dotfilesSync = async (config, args) => {
|
|
|
8891
9243
|
if ("error" in read) return fail6(read.error);
|
|
8892
9244
|
const result = await api(config, "POST", "/dotfiles/snapshot", {
|
|
8893
9245
|
files: read.files,
|
|
8894
|
-
source:
|
|
9246
|
+
source: os10.hostname(),
|
|
8895
9247
|
merge: false
|
|
8896
9248
|
});
|
|
8897
9249
|
const snapshot = result.snapshot;
|
|
8898
9250
|
if (snapshot === null) return fail6("the server accepted the sync but reports no snapshot");
|
|
8899
9251
|
say6(
|
|
8900
|
-
`${green6("synced")} ${snapshot.files.length} file${snapshot.files.length === 1 ? "" : "s"} from ${
|
|
9252
|
+
`${green6("synced")} ${snapshot.files.length} file${snapshot.files.length === 1 ? "" : "s"} from ${os10.hostname()} \xB7 ${dim6(snapshot.sha.slice(0, 7))} ${dim6("\u2014 applies from the next session launch")}`
|
|
8901
9253
|
);
|
|
8902
9254
|
};
|
|
8903
9255
|
var envLoad = async (config, args) => {
|
|
@@ -8911,10 +9263,10 @@ var envLoad = async (config, args) => {
|
|
|
8911
9263
|
const positional = args.filter(
|
|
8912
9264
|
(arg, i) => !arg.startsWith("--") && args[i - 1] !== "--project" && !consumed.has(i)
|
|
8913
9265
|
);
|
|
8914
|
-
const file =
|
|
9266
|
+
const file = path16.resolve(positional[0] ?? ".env");
|
|
8915
9267
|
let contents;
|
|
8916
9268
|
try {
|
|
8917
|
-
contents =
|
|
9269
|
+
contents = fs15.readFileSync(file, "utf8");
|
|
8918
9270
|
} catch {
|
|
8919
9271
|
return fail6(`cannot read ${file} \u2014 pass a path: mend env load path/to/.env`);
|
|
8920
9272
|
}
|
|
@@ -8930,10 +9282,10 @@ var envLoad = async (config, args) => {
|
|
|
8930
9282
|
{ contents, allSecret, secretNames }
|
|
8931
9283
|
);
|
|
8932
9284
|
if (report.loaded.length === 0 && report.rejected.length === 0 && report.malformedLines.length === 0) {
|
|
8933
|
-
say6(dim6(`${
|
|
9285
|
+
say6(dim6(`${path16.basename(file)} has no variables`));
|
|
8934
9286
|
return;
|
|
8935
9287
|
}
|
|
8936
|
-
say6(`${green6("\u2713")} loaded ${
|
|
9288
|
+
say6(`${green6("\u2713")} loaded ${path16.basename(file)} into ${project2.name}`);
|
|
8937
9289
|
for (const line of formatLoadReport(report, { dim: dim6, warn: amber2 })) say6(line);
|
|
8938
9290
|
const plaintextUrls = report.loaded.filter(
|
|
8939
9291
|
(entry) => entry.lane === "configuration" && /_(URL|URI|DSN)$/.test(entry.name)
|
|
@@ -9124,7 +9476,7 @@ var skillsList = async (config, args) => {
|
|
|
9124
9476
|
};
|
|
9125
9477
|
var skillsPush = async (config, args) => {
|
|
9126
9478
|
const dirFlag = takeFlagValue4(args, "--dir");
|
|
9127
|
-
const root = dirFlag === null ?
|
|
9479
|
+
const root = dirFlag === null ? path16.join(os10.homedir(), DEFAULT_SKILLS_DIR) : path16.resolve(dirFlag);
|
|
9128
9480
|
const scanned = scanSkillLibrary(root);
|
|
9129
9481
|
if ("error" in scanned) return fail6(scanned.error);
|
|
9130
9482
|
for (const note of scanned.notes) say6(dim6(` ${note.skill}: ${note.message}`));
|
|
@@ -9549,7 +9901,10 @@ var refreshCommand = async (config, args) => {
|
|
|
9549
9901
|
}
|
|
9550
9902
|
if (branches.length > 12) say6(dim6(` \u2026 ${branches.length - 12} more`));
|
|
9551
9903
|
};
|
|
9552
|
-
var rowHold = (row) =>
|
|
9904
|
+
var rowHold = (row) => (
|
|
9905
|
+
// A workspace a stop is still saving (docs/adr/0002) reads what is left, first.
|
|
9906
|
+
captureLineOf(row.session) ?? (row.annotation === void 0 ? null : servicesHoldOf(row.session, row.annotation.currentAgent, row.annotation.liveServices ?? 0))
|
|
9907
|
+
);
|
|
9553
9908
|
var printSessionRow = (row) => {
|
|
9554
9909
|
const { session, annotation } = row;
|
|
9555
9910
|
const live = ACTIVE_STATUSES.has(session.status);
|
|
@@ -9647,6 +10002,14 @@ var sessionsCommand = async (config, args) => {
|
|
|
9647
10002
|
openComments: annotation.openComments,
|
|
9648
10003
|
totalComments: annotation.totalComments,
|
|
9649
10004
|
pendingFollowUp: annotation.pendingFollowUp
|
|
10005
|
+
},
|
|
10006
|
+
capture: session.capturePending === void 0 ? null : {
|
|
10007
|
+
pending: session.capturePending ?? null,
|
|
10008
|
+
pendingBytes: session.capturePendingBytes ?? null,
|
|
10009
|
+
refused: session.captureRefused ?? null,
|
|
10010
|
+
drain: session.captureDrain ?? null,
|
|
10011
|
+
notSaved: (session.captureNotSavedAt ?? null) !== null,
|
|
10012
|
+
line: captureLineOf(session)
|
|
9650
10013
|
}
|
|
9651
10014
|
}))
|
|
9652
10015
|
};
|
|
@@ -9779,14 +10142,14 @@ var dashboard = async (config, options = {}) => {
|
|
|
9779
10142
|
`the dashboard needs Node >= 26 (node:ffi) \u2014 this is ${process.version}; every other command still works`
|
|
9780
10143
|
);
|
|
9781
10144
|
}
|
|
9782
|
-
const rerun =
|
|
10145
|
+
const rerun = spawnSync5(
|
|
9783
10146
|
process.execPath,
|
|
9784
10147
|
["--experimental-ffi", "--disable-warning=ExperimentalWarning", ...process.argv.slice(1)],
|
|
9785
10148
|
{ stdio: "inherit" }
|
|
9786
10149
|
);
|
|
9787
10150
|
process.exit(rerun.status ?? 0);
|
|
9788
10151
|
}
|
|
9789
|
-
const { runDashboard } = await import("./dashboard-
|
|
10152
|
+
const { runDashboard } = await import("./dashboard-OADX3PHI.js");
|
|
9790
10153
|
const agentShare = await startShareIfBridge(config);
|
|
9791
10154
|
const tunnels = options.noTunnel === true || serverIsLocal(config) ? null : createServiceTunnels({
|
|
9792
10155
|
listServices: () => fetchServices(config),
|
|
@@ -9820,7 +10183,7 @@ var startShareIfBridge = async (config) => {
|
|
|
9820
10183
|
}
|
|
9821
10184
|
if (access.mode !== "bridge") return null;
|
|
9822
10185
|
return startAgentShare({
|
|
9823
|
-
url: () => socketUrl(config, "keys-bridge", { host:
|
|
10186
|
+
url: () => socketUrl(config, "keys-bridge", { host: os10.hostname() }),
|
|
9824
10187
|
agentSock
|
|
9825
10188
|
});
|
|
9826
10189
|
};
|
|
@@ -9860,10 +10223,10 @@ var manCommand = (words) => {
|
|
|
9860
10223
|
}
|
|
9861
10224
|
const version = cliVersion();
|
|
9862
10225
|
const page = doc === null ? renderManIndex(version) : renderManPage(doc, version);
|
|
9863
|
-
const file =
|
|
9864
|
-
|
|
9865
|
-
const result =
|
|
9866
|
-
|
|
10226
|
+
const file = path16.join(os10.tmpdir(), `mend-man-${process.pid}-${manFileName(doc)}`);
|
|
10227
|
+
fs15.writeFileSync(file, page);
|
|
10228
|
+
const result = spawnSync5("man", ["-l", file], { stdio: "inherit" });
|
|
10229
|
+
fs15.rmSync(file, { force: true });
|
|
9867
10230
|
if (result.error !== void 0 || result.status !== 0) {
|
|
9868
10231
|
say6(doc === null ? renderIndex() : renderCommand(doc));
|
|
9869
10232
|
}
|