@sealant/mend 0.33.0 → 0.34.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +74 -13
- package/dist/{chunk-GJFMD7XF.js → chunk-AAAXHRWL.js} +457 -87
- package/dist/{dashboard-NWHR7I5W.js → dashboard-DWXXYFPE.js} +41 -13
- package/dist/main.js +916 -322
- 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
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
|
+
GATEWAY_STATUSES,
|
|
3
4
|
HARNESS_COMMANDS,
|
|
4
5
|
LIVE_STATUSES,
|
|
6
|
+
MendRequestError,
|
|
5
7
|
agentIsLive,
|
|
6
8
|
agentOutcome,
|
|
9
|
+
captureLineOf,
|
|
7
10
|
claudeGrantFacts,
|
|
8
11
|
createSseParser,
|
|
9
12
|
cwdFacts,
|
|
@@ -17,6 +20,7 @@ import {
|
|
|
17
20
|
landingFactsFromWire,
|
|
18
21
|
matchProjectByCwd,
|
|
19
22
|
narrowCredential,
|
|
23
|
+
noAnswerError,
|
|
20
24
|
normalizeGitAuthor,
|
|
21
25
|
normalizeProjectName,
|
|
22
26
|
normalizeRemoteUrl,
|
|
@@ -26,16 +30,17 @@ import {
|
|
|
26
30
|
sameGrant,
|
|
27
31
|
servicesHoldOf,
|
|
28
32
|
sessionDisplayName,
|
|
33
|
+
spoken,
|
|
29
34
|
trackBracketedPaste
|
|
30
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-AAAXHRWL.js";
|
|
31
36
|
|
|
32
37
|
// src/main.ts
|
|
33
|
-
import { spawn as spawn3, spawnSync as
|
|
34
|
-
import * as
|
|
38
|
+
import { spawn as spawn3, spawnSync as spawnSync5 } from "node:child_process";
|
|
39
|
+
import * as fs15 from "node:fs";
|
|
35
40
|
import { createRequire } from "node:module";
|
|
36
41
|
import * as net4 from "node:net";
|
|
37
|
-
import * as
|
|
38
|
-
import * as
|
|
42
|
+
import * as os10 from "node:os";
|
|
43
|
+
import * as path16 from "node:path";
|
|
39
44
|
|
|
40
45
|
// src/agent-share.ts
|
|
41
46
|
import * as net from "node:net";
|
|
@@ -1139,7 +1144,7 @@ var COMMANDS = [
|
|
|
1139
1144
|
summary: "check this machine's setup, or bundle it for a bug report",
|
|
1140
1145
|
synopsis: ["", "--bundle [--out <path>] [--tail <n>]"],
|
|
1141
1146
|
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.",
|
|
1147
|
+
"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
1148
|
"--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
1149
|
"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
1150
|
],
|
|
@@ -2505,8 +2510,333 @@ var doctorBundleCommand = async (args, deps) => {
|
|
|
2505
2510
|
};
|
|
2506
2511
|
|
|
2507
2512
|
// src/doctor.ts
|
|
2513
|
+
import * as fs6 from "node:fs";
|
|
2514
|
+
import * as path6 from "node:path";
|
|
2515
|
+
|
|
2516
|
+
// src/docker-shutdown.ts
|
|
2517
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
2508
2518
|
import * as fs5 from "node:fs";
|
|
2519
|
+
import * as os3 from "node:os";
|
|
2509
2520
|
import * as path5 from "node:path";
|
|
2521
|
+
var DOCKER_CAPTURE_STOP_GRACE_SECONDS = 3600;
|
|
2522
|
+
var DOCKERD_DEFAULT_SHUTDOWN_TIMEOUT_SECONDS = 15;
|
|
2523
|
+
var flagValue = (argv, name) => {
|
|
2524
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
2525
|
+
const arg = argv[index] ?? "";
|
|
2526
|
+
if (arg.startsWith(`${name}=`)) return arg.slice(name.length + 1);
|
|
2527
|
+
if (arg === name) return argv[index + 1] ?? "";
|
|
2528
|
+
}
|
|
2529
|
+
return null;
|
|
2530
|
+
};
|
|
2531
|
+
var seconds = (value) => {
|
|
2532
|
+
if (typeof value === "number" && Number.isInteger(value) && value >= 0) return value;
|
|
2533
|
+
if (typeof value === "string" && /^\d+$/.test(value.trim())) return Number(value.trim());
|
|
2534
|
+
return null;
|
|
2535
|
+
};
|
|
2536
|
+
var daemonKindOf = (info) => {
|
|
2537
|
+
if (/docker desktop/i.test(info.operatingSystem)) return "desktop";
|
|
2538
|
+
if (/orbstack/i.test(info.operatingSystem)) return "orbstack";
|
|
2539
|
+
if (info.securityOptions.some((option) => option.includes("name=rootless"))) return "rootless";
|
|
2540
|
+
return "dockerd";
|
|
2541
|
+
};
|
|
2542
|
+
var defaultConfigPath = (facts, daemon) => {
|
|
2543
|
+
switch (daemon) {
|
|
2544
|
+
case "desktop":
|
|
2545
|
+
return path5.join(facts.home, ".docker", "daemon.json");
|
|
2546
|
+
case "orbstack":
|
|
2547
|
+
return path5.join(facts.home, ".orbstack", "config", "docker.json");
|
|
2548
|
+
case "rootless":
|
|
2549
|
+
return path5.join(
|
|
2550
|
+
facts.xdgConfigHome ?? path5.join(facts.home, ".config"),
|
|
2551
|
+
"docker",
|
|
2552
|
+
"daemon.json"
|
|
2553
|
+
);
|
|
2554
|
+
case "dockerd":
|
|
2555
|
+
return "/etc/docker/daemon.json";
|
|
2556
|
+
}
|
|
2557
|
+
};
|
|
2558
|
+
var readShutdownTimeout = (facts) => {
|
|
2559
|
+
if (facts.info === null) return { kind: "unknown", why: "docker info did not answer" };
|
|
2560
|
+
const daemon = daemonKindOf(facts.info);
|
|
2561
|
+
const managed = daemon === "desktop" || daemon === "orbstack";
|
|
2562
|
+
const argv = managed ? null : facts.dockerdArgv;
|
|
2563
|
+
const unresolved = facts.dockerdUnresolved ?? null;
|
|
2564
|
+
if (argv === null && !managed && unresolved !== null) return { kind: "unknown", why: unresolved };
|
|
2565
|
+
const configPath = (argv === null ? null : flagValue(argv, "--config-file")) ?? defaultConfigPath(facts, daemon);
|
|
2566
|
+
if (argv !== null) {
|
|
2567
|
+
const flag = flagValue(argv, "--shutdown-timeout");
|
|
2568
|
+
if (flag !== null) {
|
|
2569
|
+
const value = seconds(flag);
|
|
2570
|
+
return value === null ? { kind: "unknown", why: `dockerd --shutdown-timeout ${flag} unreadable` } : {
|
|
2571
|
+
kind: "observed",
|
|
2572
|
+
seconds: value,
|
|
2573
|
+
source: "dockerd --shutdown-timeout",
|
|
2574
|
+
daemon,
|
|
2575
|
+
configPath,
|
|
2576
|
+
fromFlag: true
|
|
2577
|
+
};
|
|
2578
|
+
}
|
|
2579
|
+
}
|
|
2580
|
+
const file = facts.readFile(configPath);
|
|
2581
|
+
if (file.kind === "unreadable") return { kind: "unknown", why: `${configPath} unreadable` };
|
|
2582
|
+
if (file.kind === "read") {
|
|
2583
|
+
let parsed;
|
|
2584
|
+
try {
|
|
2585
|
+
parsed = JSON.parse(file.text);
|
|
2586
|
+
} catch {
|
|
2587
|
+
return { kind: "unknown", why: `${configPath} is not JSON` };
|
|
2588
|
+
}
|
|
2589
|
+
const configured = typeof parsed === "object" && parsed !== null && "shutdown-timeout" in parsed ? parsed["shutdown-timeout"] : void 0;
|
|
2590
|
+
if (configured !== void 0) {
|
|
2591
|
+
const value = seconds(configured);
|
|
2592
|
+
return value === null ? { kind: "unknown", why: `shutdown-timeout in ${configPath} unreadable` } : {
|
|
2593
|
+
kind: "observed",
|
|
2594
|
+
seconds: value,
|
|
2595
|
+
source: configPath,
|
|
2596
|
+
daemon,
|
|
2597
|
+
configPath,
|
|
2598
|
+
fromFlag: false
|
|
2599
|
+
};
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
if (argv === null && !managed) {
|
|
2603
|
+
return {
|
|
2604
|
+
kind: "unknown",
|
|
2605
|
+
why: `no dockerd process observed here \xB7 not set in ${configPath}`
|
|
2606
|
+
};
|
|
2607
|
+
}
|
|
2608
|
+
return {
|
|
2609
|
+
kind: "observed",
|
|
2610
|
+
seconds: DOCKERD_DEFAULT_SHUTDOWN_TIMEOUT_SECONDS,
|
|
2611
|
+
source: `dockerd default \xB7 not set in ${configPath}`,
|
|
2612
|
+
daemon,
|
|
2613
|
+
configPath,
|
|
2614
|
+
fromFlag: false
|
|
2615
|
+
};
|
|
2616
|
+
};
|
|
2617
|
+
var restartWords = (daemon) => {
|
|
2618
|
+
switch (daemon) {
|
|
2619
|
+
case "desktop":
|
|
2620
|
+
return "restart Docker Desktop";
|
|
2621
|
+
case "orbstack":
|
|
2622
|
+
return "restart OrbStack";
|
|
2623
|
+
case "rootless":
|
|
2624
|
+
return "systemctl --user restart docker";
|
|
2625
|
+
case "dockerd":
|
|
2626
|
+
return "restart dockerd";
|
|
2627
|
+
}
|
|
2628
|
+
};
|
|
2629
|
+
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)}`;
|
|
2630
|
+
var dockerShutdownCheck = (reading, grace = DOCKER_CAPTURE_STOP_GRACE_SECONDS) => {
|
|
2631
|
+
if (reading.kind === "unknown") {
|
|
2632
|
+
return {
|
|
2633
|
+
label: "docker",
|
|
2634
|
+
state: "todo",
|
|
2635
|
+
detail: `shutdown-timeout not observed \xB7 ${reading.why}`,
|
|
2636
|
+
fix: null
|
|
2637
|
+
};
|
|
2638
|
+
}
|
|
2639
|
+
if (reading.seconds >= grace) {
|
|
2640
|
+
return {
|
|
2641
|
+
label: "docker",
|
|
2642
|
+
state: "ok",
|
|
2643
|
+
detail: `shutdown-timeout ${reading.seconds} s \xB7 ${reading.source} \xB7 covers the ${grace} s capture grace`,
|
|
2644
|
+
fix: null
|
|
2645
|
+
};
|
|
2646
|
+
}
|
|
2647
|
+
return {
|
|
2648
|
+
label: "docker",
|
|
2649
|
+
state: "todo",
|
|
2650
|
+
detail: `shutdown-timeout ${reading.seconds} s \xB7 ${reading.source} \xB7 below the ${grace} s capture grace`,
|
|
2651
|
+
fix: raiseShutdownTimeoutWords(reading, grace)
|
|
2652
|
+
};
|
|
2653
|
+
};
|
|
2654
|
+
var dockerShutdownSetupLine = (reading, grace = DOCKER_CAPTURE_STOP_GRACE_SECONDS) => {
|
|
2655
|
+
if (reading.kind === "unknown") {
|
|
2656
|
+
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.`;
|
|
2657
|
+
}
|
|
2658
|
+
if (reading.seconds >= grace) return null;
|
|
2659
|
+
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)}.`;
|
|
2660
|
+
};
|
|
2661
|
+
var parseDockerInfo = (stdout) => {
|
|
2662
|
+
let parsed;
|
|
2663
|
+
try {
|
|
2664
|
+
parsed = JSON.parse(stdout);
|
|
2665
|
+
} catch {
|
|
2666
|
+
return null;
|
|
2667
|
+
}
|
|
2668
|
+
if (typeof parsed !== "object" || parsed === null || !("OperatingSystem" in parsed)) return null;
|
|
2669
|
+
const operatingSystem = parsed.OperatingSystem;
|
|
2670
|
+
if (typeof operatingSystem !== "string") return null;
|
|
2671
|
+
const options = "SecurityOptions" in parsed ? parsed.SecurityOptions : [];
|
|
2672
|
+
return {
|
|
2673
|
+
operatingSystem,
|
|
2674
|
+
securityOptions: Array.isArray(options) ? options.filter((option) => typeof option === "string") : []
|
|
2675
|
+
};
|
|
2676
|
+
};
|
|
2677
|
+
var readHostFile = (file) => {
|
|
2678
|
+
try {
|
|
2679
|
+
return { kind: "read", text: fs5.readFileSync(file, "utf8") };
|
|
2680
|
+
} catch (cause) {
|
|
2681
|
+
return cause instanceof Error && "code" in cause && cause.code === "ENOENT" ? { kind: "absent" } : { kind: "unreadable" };
|
|
2682
|
+
}
|
|
2683
|
+
};
|
|
2684
|
+
var hostProcView = {
|
|
2685
|
+
list: (dir) => {
|
|
2686
|
+
try {
|
|
2687
|
+
return fs5.readdirSync(dir);
|
|
2688
|
+
} catch {
|
|
2689
|
+
return null;
|
|
2690
|
+
}
|
|
2691
|
+
},
|
|
2692
|
+
read: (file) => {
|
|
2693
|
+
try {
|
|
2694
|
+
return fs5.readFileSync(file, "utf8");
|
|
2695
|
+
} catch {
|
|
2696
|
+
return null;
|
|
2697
|
+
}
|
|
2698
|
+
},
|
|
2699
|
+
link: (file) => {
|
|
2700
|
+
try {
|
|
2701
|
+
return fs5.readlinkSync(file);
|
|
2702
|
+
} catch {
|
|
2703
|
+
return null;
|
|
2704
|
+
}
|
|
2705
|
+
}
|
|
2706
|
+
};
|
|
2707
|
+
var DOCKER_DEFAULT_HOST = "unix:///var/run/docker.sock";
|
|
2708
|
+
var socketPath = (file) => file.replace(/^\/var\/run\//, "/run/");
|
|
2709
|
+
var unixSocketOf = (host) => host.startsWith("unix://") && host.length > "unix://".length ? socketPath(host.slice("unix://".length)) : null;
|
|
2710
|
+
var hostFlags = (argv) => {
|
|
2711
|
+
const hosts = [];
|
|
2712
|
+
for (let index = 1; index < argv.length; index += 1) {
|
|
2713
|
+
const arg = argv[index] ?? "";
|
|
2714
|
+
for (const name of ["-H", "--host"]) {
|
|
2715
|
+
if (arg === name) hosts.push(argv[index + 1] ?? "");
|
|
2716
|
+
else if (arg.startsWith(`${name}=`)) hosts.push(arg.slice(name.length + 1));
|
|
2717
|
+
}
|
|
2718
|
+
}
|
|
2719
|
+
return hosts;
|
|
2720
|
+
};
|
|
2721
|
+
var listeningInodes = (table, socket) => {
|
|
2722
|
+
const inodes = /* @__PURE__ */ new Set();
|
|
2723
|
+
for (const line of table.split("\n").slice(1)) {
|
|
2724
|
+
const fields = line.trim().split(/\s+/);
|
|
2725
|
+
const flags = fields[3];
|
|
2726
|
+
const inode = fields[6];
|
|
2727
|
+
const bound = fields.slice(7).join(" ");
|
|
2728
|
+
if (flags === void 0 || inode === void 0 || bound === "") continue;
|
|
2729
|
+
if ((Number.parseInt(flags, 16) & 65536) === 0) continue;
|
|
2730
|
+
if (socketPath(bound) === socket) inodes.add(inode);
|
|
2731
|
+
}
|
|
2732
|
+
return inodes;
|
|
2733
|
+
};
|
|
2734
|
+
var observeDockerd = (proc, host) => {
|
|
2735
|
+
const daemons = [];
|
|
2736
|
+
for (const entry of proc.list("/proc") ?? []) {
|
|
2737
|
+
if (!/^\d+$/.test(entry)) continue;
|
|
2738
|
+
const cmdline = proc.read(`/proc/${entry}/cmdline`);
|
|
2739
|
+
if (cmdline === null) continue;
|
|
2740
|
+
const argv = cmdline.split("\0").filter((arg) => arg !== "");
|
|
2741
|
+
if (path5.basename(argv[0] ?? "") === "dockerd") daemons.push({ pid: entry, argv });
|
|
2742
|
+
}
|
|
2743
|
+
if (daemons.length === 0) return { kind: "none" };
|
|
2744
|
+
const socket = unixSocketOf(host);
|
|
2745
|
+
if (socket === null) {
|
|
2746
|
+
return {
|
|
2747
|
+
kind: "unresolved",
|
|
2748
|
+
why: `docker talks to ${host} \xB7 not a socket on this machine`
|
|
2749
|
+
};
|
|
2750
|
+
}
|
|
2751
|
+
const selfTable = proc.read("/proc/self/net/unix");
|
|
2752
|
+
const seenHere = selfTable === null ? /* @__PURE__ */ new Set() : listeningInodes(selfTable, socket);
|
|
2753
|
+
const candidates = [];
|
|
2754
|
+
for (const daemon of daemons) {
|
|
2755
|
+
const hosts = hostFlags(daemon.argv);
|
|
2756
|
+
const named = hosts.some((value) => unixSocketOf(value) === socket);
|
|
2757
|
+
const activated = hosts.some((value) => value.startsWith("fd://"));
|
|
2758
|
+
if (hosts.length > 0 && !named && !activated) continue;
|
|
2759
|
+
const table = proc.read(`/proc/${daemon.pid}/net/unix`);
|
|
2760
|
+
const listening = table === null ? null : listeningInodes(table, socket);
|
|
2761
|
+
if (listening !== null) {
|
|
2762
|
+
if (listening.size === 0) continue;
|
|
2763
|
+
if (seenHere.size > 0 && ![...listening].some((inode) => seenHere.has(inode))) continue;
|
|
2764
|
+
}
|
|
2765
|
+
const fds = proc.list(`/proc/${daemon.pid}/fd`);
|
|
2766
|
+
const held = fds === null || listening === null ? null : fds.some((fd) => {
|
|
2767
|
+
const target = proc.link(`/proc/${daemon.pid}/fd/${fd}`);
|
|
2768
|
+
const inode = target?.match(/^socket:\[(\d+)\]$/)?.[1];
|
|
2769
|
+
return inode !== void 0 && listening.has(inode);
|
|
2770
|
+
});
|
|
2771
|
+
if (held === false) continue;
|
|
2772
|
+
const nsPids = proc.read(`/proc/${daemon.pid}/status`)?.split("\n").find((line) => line.startsWith("NSpid:"))?.slice("NSpid:".length).trim().split(/\s+/);
|
|
2773
|
+
candidates.push({
|
|
2774
|
+
...daemon,
|
|
2775
|
+
owns: held === true,
|
|
2776
|
+
named,
|
|
2777
|
+
nested: nsPids !== void 0 && nsPids.length > 1
|
|
2778
|
+
});
|
|
2779
|
+
}
|
|
2780
|
+
const narrowed = [
|
|
2781
|
+
(candidate) => candidate.owns,
|
|
2782
|
+
(candidate) => candidate.named,
|
|
2783
|
+
(candidate) => !candidate.nested
|
|
2784
|
+
].reduce((left, prefer) => {
|
|
2785
|
+
const preferred = left.filter(prefer);
|
|
2786
|
+
return preferred.length > 0 ? preferred : left;
|
|
2787
|
+
}, candidates);
|
|
2788
|
+
const [only, ...others] = narrowed;
|
|
2789
|
+
if (only !== void 0 && others.length === 0) {
|
|
2790
|
+
return { kind: "observed", pid: only.pid, argv: only.argv };
|
|
2791
|
+
}
|
|
2792
|
+
const seen = `${daemons.length} dockerd process${daemons.length === 1 ? "" : "es"}`;
|
|
2793
|
+
return {
|
|
2794
|
+
kind: "unresolved",
|
|
2795
|
+
why: only === void 0 ? `${seen} \xB7 none observed serving ${socket}` : `${seen} \xB7 could not tell which serves ${socket}`
|
|
2796
|
+
};
|
|
2797
|
+
};
|
|
2798
|
+
var hostDockerEndpoint = () => {
|
|
2799
|
+
const fromEnv = process.env["DOCKER_HOST"];
|
|
2800
|
+
if (fromEnv !== void 0 && fromEnv.trim() !== "") return fromEnv.trim();
|
|
2801
|
+
const context = spawnSync2(
|
|
2802
|
+
"docker",
|
|
2803
|
+
["context", "inspect", "--format", "{{.Endpoints.docker.Host}}"],
|
|
2804
|
+
{
|
|
2805
|
+
encoding: "utf8",
|
|
2806
|
+
timeout: 3e3,
|
|
2807
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2808
|
+
}
|
|
2809
|
+
);
|
|
2810
|
+
const endpoint = context.status === 0 ? context.stdout.trim() : "";
|
|
2811
|
+
return endpoint === "" ? DOCKER_DEFAULT_HOST : endpoint;
|
|
2812
|
+
};
|
|
2813
|
+
var dockerdFactsOf = (observation) => {
|
|
2814
|
+
switch (observation.kind) {
|
|
2815
|
+
case "observed":
|
|
2816
|
+
return { dockerdArgv: observation.argv, dockerdUnresolved: null };
|
|
2817
|
+
case "none":
|
|
2818
|
+
return { dockerdArgv: null, dockerdUnresolved: null };
|
|
2819
|
+
case "unresolved":
|
|
2820
|
+
return { dockerdArgv: null, dockerdUnresolved: observation.why };
|
|
2821
|
+
}
|
|
2822
|
+
};
|
|
2823
|
+
var hostDockerDaemonFacts = (infoStdout) => ({
|
|
2824
|
+
info: infoStdout === null ? null : parseDockerInfo(infoStdout),
|
|
2825
|
+
...dockerdFactsOf(observeDockerd(hostProcView, hostDockerEndpoint())),
|
|
2826
|
+
readFile: readHostFile,
|
|
2827
|
+
home: os3.homedir(),
|
|
2828
|
+
xdgConfigHome: process.env["XDG_CONFIG_HOME"] ?? null
|
|
2829
|
+
});
|
|
2830
|
+
var observeHostShutdownTimeout = () => {
|
|
2831
|
+
const info = spawnSync2("docker", ["info", "--format", "{{json .}}"], {
|
|
2832
|
+
encoding: "utf8",
|
|
2833
|
+
timeout: 3e3,
|
|
2834
|
+
stdio: ["ignore", "pipe", "ignore"]
|
|
2835
|
+
});
|
|
2836
|
+
return readShutdownTimeout(hostDockerDaemonFacts(info.status === 0 ? info.stdout : null));
|
|
2837
|
+
};
|
|
2838
|
+
|
|
2839
|
+
// src/doctor.ts
|
|
2510
2840
|
var TIMEOUT_MS = 3e3;
|
|
2511
2841
|
var MARKS = { ok: "\u2713", todo: "\u25CB", failed: "\u2717" };
|
|
2512
2842
|
var LABEL_WIDTH = 11;
|
|
@@ -2721,13 +3051,16 @@ var runChecks = async (config, probes) => {
|
|
|
2721
3051
|
const machine = signedIn ? await getJson(config, "/machine") : null;
|
|
2722
3052
|
const exposure = machine === null || machine.value === null ? void 0 : machine.value.exposure;
|
|
2723
3053
|
checks.push(exposure === void 0 ? notChecked("exposure") : exposureCheck(exposure));
|
|
3054
|
+
if (probes.dockerShutdown !== void 0 && probes.onPath("docker")) {
|
|
3055
|
+
checks.push(dockerShutdownCheck(probes.dockerShutdown()));
|
|
3056
|
+
}
|
|
2724
3057
|
return checks;
|
|
2725
3058
|
};
|
|
2726
3059
|
var onPath = (command) => {
|
|
2727
|
-
for (const directory of (process.env["PATH"] ?? "").split(
|
|
3060
|
+
for (const directory of (process.env["PATH"] ?? "").split(path6.delimiter)) {
|
|
2728
3061
|
if (directory === "") continue;
|
|
2729
3062
|
try {
|
|
2730
|
-
|
|
3063
|
+
fs6.accessSync(path6.join(directory, command), fs6.constants.X_OK);
|
|
2731
3064
|
return true;
|
|
2732
3065
|
} catch {
|
|
2733
3066
|
}
|
|
@@ -2740,15 +3073,20 @@ var paintMark = (state, mark) => {
|
|
|
2740
3073
|
return `\x1B[${code}m${mark}\x1B[0m`;
|
|
2741
3074
|
};
|
|
2742
3075
|
var doctorCommand = async (config, localCredential2, claudeGrant2) => {
|
|
2743
|
-
const checks = await runChecks(config, {
|
|
3076
|
+
const checks = await runChecks(config, {
|
|
3077
|
+
localCredential: localCredential2,
|
|
3078
|
+
claudeGrant: claudeGrant2,
|
|
3079
|
+
onPath,
|
|
3080
|
+
dockerShutdown: observeHostShutdownTimeout
|
|
3081
|
+
});
|
|
2744
3082
|
for (const check of checks) process.stdout.write(`${formatCheck(check, paintMark)}
|
|
2745
3083
|
`);
|
|
2746
3084
|
if (checks.some((check) => check.state === "failed")) process.exitCode = 1;
|
|
2747
3085
|
};
|
|
2748
3086
|
|
|
2749
3087
|
// src/dotfiles.ts
|
|
2750
|
-
import * as
|
|
2751
|
-
import * as
|
|
3088
|
+
import * as fs7 from "node:fs";
|
|
3089
|
+
import * as path7 from "node:path";
|
|
2752
3090
|
var DOTFILE_CANDIDATES = [
|
|
2753
3091
|
{
|
|
2754
3092
|
group: "shell",
|
|
@@ -2791,7 +3129,7 @@ var DOTFILE_CANDIDATES = [
|
|
|
2791
3129
|
var MAX_FILE_BYTES = 1024 * 1024;
|
|
2792
3130
|
var statFile = (home, relative4) => {
|
|
2793
3131
|
try {
|
|
2794
|
-
const stat =
|
|
3132
|
+
const stat = fs7.statSync(path7.join(home, relative4));
|
|
2795
3133
|
return stat.isFile() ? stat : null;
|
|
2796
3134
|
} catch {
|
|
2797
3135
|
return null;
|
|
@@ -2804,9 +3142,9 @@ var scanDotfileCandidates = (home) => DOTFILE_CANDIDATES.flatMap(
|
|
|
2804
3142
|
})
|
|
2805
3143
|
);
|
|
2806
3144
|
var homeRelativePath = (home, requested) => {
|
|
2807
|
-
const relative4 =
|
|
2808
|
-
const outside = relative4 === "" || relative4 === ".." || relative4.startsWith(`..${
|
|
2809
|
-
return outside ? null : relative4.split(
|
|
3145
|
+
const relative4 = path7.relative(home, path7.resolve(home, requested));
|
|
3146
|
+
const outside = relative4 === "" || relative4 === ".." || relative4.startsWith(`..${path7.sep}`) || path7.isAbsolute(relative4);
|
|
3147
|
+
return outside ? null : relative4.split(path7.sep).join("/");
|
|
2810
3148
|
};
|
|
2811
3149
|
var readSyncFiles = (home, paths_) => {
|
|
2812
3150
|
const files = [];
|
|
@@ -2826,7 +3164,7 @@ var readSyncFiles = (home, paths_) => {
|
|
|
2826
3164
|
}
|
|
2827
3165
|
files.push({
|
|
2828
3166
|
path: relative4,
|
|
2829
|
-
contentsBase64:
|
|
3167
|
+
contentsBase64: fs7.readFileSync(path7.join(home, relative4)).toString("base64"),
|
|
2830
3168
|
// eslint-disable-next-line no-bitwise -- permission bits are the point
|
|
2831
3169
|
mode: (stat.mode & 511).toString(8)
|
|
2832
3170
|
});
|
|
@@ -2925,10 +3263,10 @@ var parseGitAuthorArgs = (args) => {
|
|
|
2925
3263
|
var gitAuthorLine = (author) => `${author.name} <${author.email}> \xB7 ${author.source === "setting" ? "your setting" : "your account's name and email"}`;
|
|
2926
3264
|
|
|
2927
3265
|
// src/landing.ts
|
|
2928
|
-
import { spawnSync as
|
|
2929
|
-
import * as
|
|
2930
|
-
import * as
|
|
2931
|
-
import * as
|
|
3266
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
3267
|
+
import * as fs8 from "node:fs";
|
|
3268
|
+
import * as os4 from "node:os";
|
|
3269
|
+
import * as path8 from "node:path";
|
|
2932
3270
|
var paint = (code) => (text) => process.stdout.isTTY === true ? `[${code}m${text}[0m` : text;
|
|
2933
3271
|
var dim = paint("2");
|
|
2934
3272
|
var green = paint("32");
|
|
@@ -3143,7 +3481,7 @@ var landCommand = async (api2, args) => {
|
|
|
3143
3481
|
if (!landingSucceeded(report)) process.exitCode = 1;
|
|
3144
3482
|
};
|
|
3145
3483
|
var git = (cwd, args) => {
|
|
3146
|
-
const result =
|
|
3484
|
+
const result = spawnSync3("git", args, { cwd, encoding: "utf8" });
|
|
3147
3485
|
return {
|
|
3148
3486
|
status: result.status ?? 1,
|
|
3149
3487
|
stdout: result.stdout ?? "",
|
|
@@ -3166,9 +3504,9 @@ var gitRemotes = (cwd) => {
|
|
|
3166
3504
|
var remoteForOrigin = (remotes, originUrl, cwd) => {
|
|
3167
3505
|
if (originUrl === null) return null;
|
|
3168
3506
|
const origin = normalizeRemoteUrl(originUrl);
|
|
3169
|
-
const originPath = origin === null ?
|
|
3507
|
+
const originPath = origin === null ? path8.resolve(originUrl.replace(/^file:\/\//, "")) : null;
|
|
3170
3508
|
return remotes.find(
|
|
3171
|
-
(remote) => origin === null ?
|
|
3509
|
+
(remote) => origin === null ? path8.resolve(cwd, remote.url.replace(/^file:\/\//, "")) === originPath : normalizeRemoteUrl(remote.url) === origin
|
|
3172
3510
|
) ?? null;
|
|
3173
3511
|
};
|
|
3174
3512
|
var LOG_LINES = 10;
|
|
@@ -3191,10 +3529,10 @@ var fetchBundle = (cwd, bundle) => {
|
|
|
3191
3529
|
const before = git(cwd, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]);
|
|
3192
3530
|
const previous = before.status === 0 ? before.stdout.trim() : null;
|
|
3193
3531
|
if (previous !== bundle.tip) {
|
|
3194
|
-
const dir =
|
|
3532
|
+
const dir = fs8.mkdtempSync(path8.join(os4.tmpdir(), "mend-pull-"));
|
|
3195
3533
|
try {
|
|
3196
|
-
const file =
|
|
3197
|
-
|
|
3534
|
+
const file = path8.join(dir, "change.bundle");
|
|
3535
|
+
fs8.writeFileSync(file, bundle.bytes, { mode: 384 });
|
|
3198
3536
|
const fetched = git(cwd, [
|
|
3199
3537
|
"fetch",
|
|
3200
3538
|
"--no-tags",
|
|
@@ -3204,7 +3542,7 @@ var fetchBundle = (cwd, bundle) => {
|
|
|
3204
3542
|
]);
|
|
3205
3543
|
if (fetched.status !== 0) return refusedWith(gitWords(fetched));
|
|
3206
3544
|
} finally {
|
|
3207
|
-
|
|
3545
|
+
fs8.rmSync(dir, { recursive: true, force: true });
|
|
3208
3546
|
}
|
|
3209
3547
|
}
|
|
3210
3548
|
const after = git(cwd, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]).stdout.trim();
|
|
@@ -3321,9 +3659,81 @@ var pullCommand = async (api2, download2, args, cwd = process.cwd()) => {
|
|
|
3321
3659
|
for (const line of fetchedLines(fetched)) say(line);
|
|
3322
3660
|
};
|
|
3323
3661
|
|
|
3662
|
+
// src/launch-follow.ts
|
|
3663
|
+
var SETTLED = /* @__PURE__ */ new Set(["completed", "failed", "stopped", "stopping"]);
|
|
3664
|
+
var agentRuns = (agent) => agent !== null && agent.kind !== "agent-protocol" && agent.exitedAt === null && agent.status === "running" && agent.sealantSessionId !== null;
|
|
3665
|
+
var startingLineOf = (session) => {
|
|
3666
|
+
const summary = session.summary?.trim() ?? "";
|
|
3667
|
+
if (summary === "") return session.status;
|
|
3668
|
+
if (/^[a-z][a-z -]*·/u.test(summary)) return summary;
|
|
3669
|
+
return `${session.status} \xB7 ${summary}`;
|
|
3670
|
+
};
|
|
3671
|
+
var refusalOf = (error) => error instanceof MendRequestError && error.kind === "http" && (error.status === null || !GATEWAY_STATUSES.has(error.status)) ? error.message : null;
|
|
3672
|
+
var followStart = async (options) => {
|
|
3673
|
+
const pollMs = options.pollMs ?? 2e3;
|
|
3674
|
+
const unreachableAfterMs = options.unreachableAfterMs ?? 6e4;
|
|
3675
|
+
const launch2 = {
|
|
3676
|
+
state: options.start === null ? { kind: "no-answer" } : { kind: "pending" }
|
|
3677
|
+
};
|
|
3678
|
+
const start = options.start;
|
|
3679
|
+
const launched = (async () => {
|
|
3680
|
+
if (start === null) return;
|
|
3681
|
+
try {
|
|
3682
|
+
launch2.state = { kind: "answered", session: await start };
|
|
3683
|
+
} catch (error) {
|
|
3684
|
+
const refusal = refusalOf(error);
|
|
3685
|
+
launch2.state = refusal === null ? { kind: "no-answer" } : { kind: "refused", message: refusal };
|
|
3686
|
+
}
|
|
3687
|
+
})();
|
|
3688
|
+
let lastLine = null;
|
|
3689
|
+
let failingSince = null;
|
|
3690
|
+
let seenStarting = false;
|
|
3691
|
+
for (; ; ) {
|
|
3692
|
+
if (launch2.state.kind === "pending") await Promise.race([launched, options.sleep(pollMs)]);
|
|
3693
|
+
const state = launch2.state;
|
|
3694
|
+
if (state.kind === "refused") return { kind: "refused", message: state.message };
|
|
3695
|
+
if (state.kind === "answered" && state.session.status === "running") {
|
|
3696
|
+
return { kind: "live", session: state.session };
|
|
3697
|
+
}
|
|
3698
|
+
let detail;
|
|
3699
|
+
try {
|
|
3700
|
+
detail = await options.read();
|
|
3701
|
+
} catch (error) {
|
|
3702
|
+
const refusal = refusalOf(error);
|
|
3703
|
+
if (refusal !== null) return { kind: "refused", message: refusal };
|
|
3704
|
+
failingSince ??= options.now();
|
|
3705
|
+
if (state.kind !== "pending" && options.now() - failingSince >= unreachableAfterMs) {
|
|
3706
|
+
return {
|
|
3707
|
+
kind: "unreachable",
|
|
3708
|
+
message: error instanceof Error ? error.message : String(error)
|
|
3709
|
+
};
|
|
3710
|
+
}
|
|
3711
|
+
if (state.kind !== "pending") await options.sleep(pollMs);
|
|
3712
|
+
continue;
|
|
3713
|
+
}
|
|
3714
|
+
failingSince = null;
|
|
3715
|
+
const { session, currentAgent } = detail;
|
|
3716
|
+
if (agentRuns(currentAgent)) return { kind: "live", session };
|
|
3717
|
+
if (SETTLED.has(session.status)) {
|
|
3718
|
+
if (seenStarting || state.kind !== "pending") return { kind: "settled", session };
|
|
3719
|
+
} else {
|
|
3720
|
+
seenStarting = true;
|
|
3721
|
+
}
|
|
3722
|
+
if (currentAgent === null && state.kind === "answered" && state.session.status === "running" && session.status === "running") {
|
|
3723
|
+
return { kind: "live", session };
|
|
3724
|
+
}
|
|
3725
|
+
const line = startingLineOf(session);
|
|
3726
|
+
if (line !== lastLine) {
|
|
3727
|
+
lastLine = line;
|
|
3728
|
+
options.onLine(line);
|
|
3729
|
+
}
|
|
3730
|
+
if (state.kind !== "pending") await options.sleep(pollMs);
|
|
3731
|
+
}
|
|
3732
|
+
};
|
|
3733
|
+
|
|
3324
3734
|
// src/login.ts
|
|
3325
3735
|
import { spawn as spawn2 } from "node:child_process";
|
|
3326
|
-
import * as
|
|
3736
|
+
import * as os5 from "node:os";
|
|
3327
3737
|
|
|
3328
3738
|
// src/pair.ts
|
|
3329
3739
|
var paint2 = (code) => (text) => process.stdout.isTTY === true ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
@@ -3492,8 +3902,8 @@ var post = async (url, body) => {
|
|
|
3492
3902
|
};
|
|
3493
3903
|
var retryAfterSecondsOf = (json2) => {
|
|
3494
3904
|
if (typeof json2 === "object" && json2 !== null && "retryAfterSeconds" in json2) {
|
|
3495
|
-
const
|
|
3496
|
-
if (Number.isFinite(
|
|
3905
|
+
const seconds2 = Number(json2.retryAfterSeconds);
|
|
3906
|
+
if (Number.isFinite(seconds2) && seconds2 > 0) return seconds2;
|
|
3497
3907
|
}
|
|
3498
3908
|
return 5;
|
|
3499
3909
|
};
|
|
@@ -3515,7 +3925,7 @@ var resolveServerUrl = async (args, deps) => {
|
|
|
3515
3925
|
};
|
|
3516
3926
|
var loginCommand = async (args, deps) => {
|
|
3517
3927
|
const base = await resolveServerUrl(args, deps);
|
|
3518
|
-
const started = await post(`${base}/api/cli/auth`, { name:
|
|
3928
|
+
const started = await post(`${base}/api/cli/auth`, { name: os5.hostname() });
|
|
3519
3929
|
if (started.status === 429) {
|
|
3520
3930
|
return fail3(
|
|
3521
3931
|
`the server asked for a pause \u2014 try again in ${retryAfterSecondsOf(started.json)}s`
|
|
@@ -3566,8 +3976,8 @@ var loginCommand = async (args, deps) => {
|
|
|
3566
3976
|
};
|
|
3567
3977
|
|
|
3568
3978
|
// src/organization.ts
|
|
3569
|
-
import * as
|
|
3570
|
-
import * as
|
|
3979
|
+
import * as fs9 from "node:fs";
|
|
3980
|
+
import * as path9 from "node:path";
|
|
3571
3981
|
var paint4 = (code) => (text) => process.stdout.isTTY === true ? `[${code}m${text}[0m` : text;
|
|
3572
3982
|
var dim4 = paint4("2");
|
|
3573
3983
|
var green4 = paint4("32");
|
|
@@ -3631,7 +4041,7 @@ var scanFolder = (dir) => {
|
|
|
3631
4041
|
const files = [];
|
|
3632
4042
|
const skipped = [];
|
|
3633
4043
|
const walk = (relative4) => {
|
|
3634
|
-
const entries =
|
|
4044
|
+
const entries = fs9.readdirSync(path9.join(dir, relative4), { withFileTypes: true }).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
3635
4045
|
for (const entry of entries) {
|
|
3636
4046
|
const child = relative4 === "" ? entry.name : `${relative4}/${entry.name}`;
|
|
3637
4047
|
if (entry.isSymbolicLink()) {
|
|
@@ -3640,7 +4050,7 @@ var scanFolder = (dir) => {
|
|
|
3640
4050
|
if (SKIPPED_DIRECTORIES.has(entry.name)) skipped.push({ path: child, reason: "skipped" });
|
|
3641
4051
|
else walk(child);
|
|
3642
4052
|
} else if (entry.isFile()) {
|
|
3643
|
-
const size =
|
|
4053
|
+
const size = fs9.statSync(path9.join(dir, child)).size;
|
|
3644
4054
|
if (size > FOLDER_FILE_BYTES) skipped.push({ path: child, reason: "over 1 MiB" });
|
|
3645
4055
|
else files.push({ path: child, size });
|
|
3646
4056
|
}
|
|
@@ -3693,7 +4103,7 @@ var folderCommand = async (api2, args) => {
|
|
|
3693
4103
|
}
|
|
3694
4104
|
case "push": {
|
|
3695
4105
|
if (dir === void 0) return fail4("usage: mend folder push <name> <dir> [--replace]");
|
|
3696
|
-
if (!
|
|
4106
|
+
if (!fs9.existsSync(dir) || !fs9.statSync(dir).isDirectory()) {
|
|
3697
4107
|
return fail4(`${dir} is not a directory`);
|
|
3698
4108
|
}
|
|
3699
4109
|
const folder = await folderNamed(api2, name);
|
|
@@ -3708,7 +4118,7 @@ var folderCommand = async (api2, args) => {
|
|
|
3708
4118
|
files: batch.map((file) => ({
|
|
3709
4119
|
path: file.path,
|
|
3710
4120
|
// Read one batch at a time, so a large tree never sits in memory at once.
|
|
3711
|
-
contentsBase64:
|
|
4121
|
+
contentsBase64: fs9.readFileSync(path9.join(dir, file.path)).toString("base64")
|
|
3712
4122
|
})),
|
|
3713
4123
|
// --replace empties the folder with the first batch; the rest add to it.
|
|
3714
4124
|
merge: !(replace && index === 0)
|
|
@@ -3892,8 +4302,8 @@ var operatorCommand = async (api2, tryApi, baseUrl, args) => {
|
|
|
3892
4302
|
import { createHmac, randomBytes } from "node:crypto";
|
|
3893
4303
|
import { readFile } from "node:fs/promises";
|
|
3894
4304
|
import * as net2 from "node:net";
|
|
3895
|
-
import * as
|
|
3896
|
-
import * as
|
|
4305
|
+
import * as os7 from "node:os";
|
|
4306
|
+
import * as path11 from "node:path";
|
|
3897
4307
|
|
|
3898
4308
|
// src/server-docker-volumes.ts
|
|
3899
4309
|
import { createHash as createHash2 } from "node:crypto";
|
|
@@ -4111,9 +4521,9 @@ var verifyServerDockerVolumes = (runtime, input) => ownership(runtime, input, "v
|
|
|
4111
4521
|
|
|
4112
4522
|
// src/server-store.ts
|
|
4113
4523
|
import { randomUUID } from "node:crypto";
|
|
4114
|
-
import * as
|
|
4115
|
-
import * as
|
|
4116
|
-
import * as
|
|
4524
|
+
import * as fs10 from "node:fs";
|
|
4525
|
+
import * as os6 from "node:os";
|
|
4526
|
+
import * as path10 from "node:path";
|
|
4117
4527
|
var ServerStoreError = class extends Error {
|
|
4118
4528
|
_tag = "ServerStoreError";
|
|
4119
4529
|
};
|
|
@@ -4134,28 +4544,28 @@ var attempt = (operation) => {
|
|
|
4134
4544
|
var hasCode = (cause, code) => cause instanceof Error && "code" in cause && cause.code === code;
|
|
4135
4545
|
var readOptional = (file) => {
|
|
4136
4546
|
try {
|
|
4137
|
-
return
|
|
4547
|
+
return fs10.readFileSync(file, "utf8");
|
|
4138
4548
|
} catch (cause) {
|
|
4139
4549
|
if (hasCode(cause, "ENOENT")) return null;
|
|
4140
4550
|
throw cause;
|
|
4141
4551
|
}
|
|
4142
4552
|
};
|
|
4143
4553
|
var syncDirectory = (directory) => {
|
|
4144
|
-
const fd =
|
|
4554
|
+
const fd = fs10.openSync(directory, "r");
|
|
4145
4555
|
try {
|
|
4146
|
-
|
|
4556
|
+
fs10.fsyncSync(fd);
|
|
4147
4557
|
} finally {
|
|
4148
|
-
|
|
4558
|
+
fs10.closeSync(fd);
|
|
4149
4559
|
}
|
|
4150
4560
|
};
|
|
4151
4561
|
var writeDurable = (file, content, mode = 384) => {
|
|
4152
|
-
const fd =
|
|
4562
|
+
const fd = fs10.openSync(file, "wx", mode);
|
|
4153
4563
|
try {
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4564
|
+
fs10.writeFileSync(fd, content, "utf8");
|
|
4565
|
+
fs10.fchmodSync(fd, mode);
|
|
4566
|
+
fs10.fsyncSync(fd);
|
|
4157
4567
|
} finally {
|
|
4158
|
-
|
|
4568
|
+
fs10.closeSync(fd);
|
|
4159
4569
|
}
|
|
4160
4570
|
};
|
|
4161
4571
|
var fileKeys = ["identity", "config", "env", "compose", "postgresInit"];
|
|
@@ -4175,11 +4585,11 @@ var parseLockOwner = (raw) => {
|
|
|
4175
4585
|
};
|
|
4176
4586
|
var describeOwner = (lockDir) => {
|
|
4177
4587
|
try {
|
|
4178
|
-
const raw = JSON.parse(
|
|
4588
|
+
const raw = JSON.parse(fs10.readFileSync(path10.join(lockDir, "owner.json"), "utf8"));
|
|
4179
4589
|
const owner = parseLockOwner(raw);
|
|
4180
4590
|
if (owner === null) return "owner metadata is invalid";
|
|
4181
4591
|
const label = `PID ${owner.pid} on ${owner.hostname}`;
|
|
4182
|
-
if (owner.hostname !==
|
|
4592
|
+
if (owner.hostname !== os6.hostname()) return label;
|
|
4183
4593
|
try {
|
|
4184
4594
|
process.kill(owner.pid, 0);
|
|
4185
4595
|
return `${label} is still live`;
|
|
@@ -4195,28 +4605,28 @@ var lockGuidance = (lockDir) => new ServerStoreError(
|
|
|
4195
4605
|
);
|
|
4196
4606
|
var acquireLock = (configDir, create) => {
|
|
4197
4607
|
if (create) {
|
|
4198
|
-
|
|
4199
|
-
|
|
4200
|
-
} else if (!
|
|
4608
|
+
fs10.mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
4609
|
+
fs10.chmodSync(configDir, 448);
|
|
4610
|
+
} else if (!fs10.existsSync(configDir)) {
|
|
4201
4611
|
throw new ServerStoreError(
|
|
4202
4612
|
"No Mend server is configured. Run mend server setup explicitly to install one."
|
|
4203
4613
|
);
|
|
4204
4614
|
}
|
|
4205
|
-
const lockDir =
|
|
4615
|
+
const lockDir = path10.join(configDir, "server.lock");
|
|
4206
4616
|
try {
|
|
4207
|
-
|
|
4617
|
+
fs10.mkdirSync(lockDir, { mode: 448 });
|
|
4208
4618
|
} catch (cause) {
|
|
4209
4619
|
if (hasCode(cause, "EEXIST")) throw lockGuidance(lockDir);
|
|
4210
4620
|
throw cause;
|
|
4211
4621
|
}
|
|
4212
|
-
const owned =
|
|
4213
|
-
const ownerFile =
|
|
4214
|
-
const owner = `${JSON.stringify({ pid: process.pid, hostname:
|
|
4622
|
+
const owned = fs10.statSync(lockDir);
|
|
4623
|
+
const ownerFile = path10.join(lockDir, "owner.json");
|
|
4624
|
+
const owner = `${JSON.stringify({ pid: process.pid, hostname: os6.hostname(), token: randomUUID() })}
|
|
4215
4625
|
`;
|
|
4216
4626
|
let open = true;
|
|
4217
4627
|
const assertOwned = () => {
|
|
4218
|
-
const current =
|
|
4219
|
-
if (!open || current.dev !== owned.dev || current.ino !== owned.ino ||
|
|
4628
|
+
const current = fs10.statSync(lockDir);
|
|
4629
|
+
if (!open || current.dev !== owned.dev || current.ino !== owned.ino || fs10.readFileSync(ownerFile, "utf8") !== owner) {
|
|
4220
4630
|
throw new ServerStoreError(
|
|
4221
4631
|
"Server lock ownership was lost. Stop and check for another server command before retrying."
|
|
4222
4632
|
);
|
|
@@ -4232,8 +4642,8 @@ var acquireLock = (configDir, create) => {
|
|
|
4232
4642
|
release: () => {
|
|
4233
4643
|
const result = attempt(() => {
|
|
4234
4644
|
assertOwned();
|
|
4235
|
-
|
|
4236
|
-
|
|
4645
|
+
fs10.unlinkSync(ownerFile);
|
|
4646
|
+
fs10.rmdirSync(lockDir);
|
|
4237
4647
|
});
|
|
4238
4648
|
open = false;
|
|
4239
4649
|
return result;
|
|
@@ -4242,7 +4652,7 @@ var acquireLock = (configDir, create) => {
|
|
|
4242
4652
|
};
|
|
4243
4653
|
var readIdentity = (paths) => {
|
|
4244
4654
|
const identity = readOptional(paths.identity);
|
|
4245
|
-
if (identity === null && (
|
|
4655
|
+
if (identity === null && (fs10.existsSync(paths.active) || fs10.existsSync(paths.generations) && fs10.readdirSync(paths.generations).length > 0)) {
|
|
4246
4656
|
throw new ServerStoreError(
|
|
4247
4657
|
"Server identity.env is missing. Restore it from a retained generation or backup; refusing to generate replacement credentials."
|
|
4248
4658
|
);
|
|
@@ -4252,7 +4662,7 @@ var readIdentity = (paths) => {
|
|
|
4252
4662
|
var readActive = (paths) => {
|
|
4253
4663
|
let target;
|
|
4254
4664
|
try {
|
|
4255
|
-
target =
|
|
4665
|
+
target = fs10.readlinkSync(paths.active);
|
|
4256
4666
|
} catch (cause) {
|
|
4257
4667
|
if (hasCode(cause, "ENOENT")) return null;
|
|
4258
4668
|
throw cause;
|
|
@@ -4262,13 +4672,13 @@ var readActive = (paths) => {
|
|
|
4262
4672
|
"Server active pointer is invalid. Restore the symlink to a retained generation before retrying."
|
|
4263
4673
|
);
|
|
4264
4674
|
}
|
|
4265
|
-
const directory =
|
|
4675
|
+
const directory = path10.join(paths.configDir, target);
|
|
4266
4676
|
const files = {
|
|
4267
|
-
identity:
|
|
4268
|
-
config:
|
|
4269
|
-
env:
|
|
4270
|
-
compose:
|
|
4271
|
-
postgresInit:
|
|
4677
|
+
identity: fs10.readFileSync(path10.join(directory, fileNames.identity), "utf8"),
|
|
4678
|
+
config: fs10.readFileSync(path10.join(directory, fileNames.config), "utf8"),
|
|
4679
|
+
env: fs10.readFileSync(path10.join(directory, fileNames.env), "utf8"),
|
|
4680
|
+
compose: fs10.readFileSync(path10.join(directory, fileNames.compose), "utf8"),
|
|
4681
|
+
postgresInit: fs10.readFileSync(path10.join(directory, fileNames.postgresInit), "utf8")
|
|
4272
4682
|
};
|
|
4273
4683
|
if (readIdentity(paths) !== files.identity) {
|
|
4274
4684
|
throw new ServerStoreError(
|
|
@@ -4278,20 +4688,20 @@ var readActive = (paths) => {
|
|
|
4278
4688
|
return { directory, files };
|
|
4279
4689
|
};
|
|
4280
4690
|
var publishIdentity = (paths, identity) => {
|
|
4281
|
-
const temporary =
|
|
4691
|
+
const temporary = path10.join(paths.configDir, `.identity-${randomUUID()}`);
|
|
4282
4692
|
writeDurable(temporary, identity);
|
|
4283
|
-
|
|
4693
|
+
fs10.linkSync(temporary, paths.identity);
|
|
4284
4694
|
syncDirectory(paths.configDir);
|
|
4285
|
-
|
|
4695
|
+
fs10.unlinkSync(temporary);
|
|
4286
4696
|
};
|
|
4287
4697
|
var prepareGeneration = (paths, files) => {
|
|
4288
|
-
|
|
4698
|
+
fs10.mkdirSync(paths.generations, { recursive: true, mode: 448 });
|
|
4289
4699
|
const generationName = `gen-${randomUUID()}`;
|
|
4290
|
-
const directory =
|
|
4291
|
-
|
|
4700
|
+
const directory = path10.join(paths.generations, generationName);
|
|
4701
|
+
fs10.mkdirSync(directory, { mode: 448 });
|
|
4292
4702
|
for (const key of fileKeys) {
|
|
4293
4703
|
writeDurable(
|
|
4294
|
-
|
|
4704
|
+
path10.join(directory, fileNames[key]),
|
|
4295
4705
|
files[key],
|
|
4296
4706
|
key === "postgresInit" ? 493 : 384
|
|
4297
4707
|
);
|
|
@@ -4302,18 +4712,18 @@ var prepareGeneration = (paths, files) => {
|
|
|
4302
4712
|
return { directory, files };
|
|
4303
4713
|
};
|
|
4304
4714
|
var activateGeneration = (paths, generation) => {
|
|
4305
|
-
const relative4 =
|
|
4306
|
-
if (!/^generations\/gen-[0-9a-f-]{36}$/.test(relative4) || readIdentity(paths) !==
|
|
4715
|
+
const relative4 = path10.relative(paths.configDir, generation.directory);
|
|
4716
|
+
if (!/^generations\/gen-[0-9a-f-]{36}$/.test(relative4) || readIdentity(paths) !== fs10.readFileSync(path10.join(generation.directory, "identity.env"), "utf8")) {
|
|
4307
4717
|
throw new ServerStoreError("Cannot activate a generation outside this installation identity.");
|
|
4308
4718
|
}
|
|
4309
4719
|
for (const key of fileKeys) {
|
|
4310
|
-
if (
|
|
4720
|
+
if (fs10.readFileSync(path10.join(generation.directory, fileNames[key]), "utf8") !== generation.files[key]) {
|
|
4311
4721
|
throw new ServerStoreError("Cannot activate an incomplete or changed server generation.");
|
|
4312
4722
|
}
|
|
4313
4723
|
}
|
|
4314
|
-
const pointer =
|
|
4315
|
-
|
|
4316
|
-
|
|
4724
|
+
const pointer = path10.join(paths.configDir, `.active-${randomUUID()}`);
|
|
4725
|
+
fs10.symlinkSync(relative4, pointer);
|
|
4726
|
+
fs10.renameSync(pointer, paths.active);
|
|
4317
4727
|
syncDirectory(paths.configDir);
|
|
4318
4728
|
};
|
|
4319
4729
|
var prepareFiles = (paths, files) => {
|
|
@@ -4322,7 +4732,7 @@ var prepareFiles = (paths, files) => {
|
|
|
4322
4732
|
throw new ServerStoreError("Server identity already exists; refusing to replace credentials.");
|
|
4323
4733
|
}
|
|
4324
4734
|
const active = readActive(paths);
|
|
4325
|
-
if (active !== null && fileKeys.every((key) => files[key] === active.files[key]) && (
|
|
4735
|
+
if (active !== null && fileKeys.every((key) => files[key] === active.files[key]) && (fs10.statSync(path10.join(active.directory, fileNames.postgresInit)).mode & 4095) === 493)
|
|
4326
4736
|
return active;
|
|
4327
4737
|
if (identity === null) publishIdentity(paths, files.identity);
|
|
4328
4738
|
return prepareGeneration(paths, files);
|
|
@@ -4335,9 +4745,9 @@ var commitGeneration = (paths, files) => {
|
|
|
4335
4745
|
var createStore = (configDir, lock) => {
|
|
4336
4746
|
const paths = {
|
|
4337
4747
|
configDir,
|
|
4338
|
-
identity:
|
|
4339
|
-
generations:
|
|
4340
|
-
active:
|
|
4748
|
+
identity: path10.join(configDir, "identity.env"),
|
|
4749
|
+
generations: path10.join(configDir, "generations"),
|
|
4750
|
+
active: path10.join(configDir, "active")
|
|
4341
4751
|
};
|
|
4342
4752
|
const whileOwned = (operation) => attempt(() => {
|
|
4343
4753
|
lock.assertOwned();
|
|
@@ -4350,13 +4760,13 @@ var createStore = (configDir, lock) => {
|
|
|
4350
4760
|
prepare: (files) => whileOwned(() => prepareFiles(paths, files)),
|
|
4351
4761
|
activate: (generation) => whileOwned(() => activateGeneration(paths, generation)),
|
|
4352
4762
|
createBackup: (previous, target) => whileOwned(() => {
|
|
4353
|
-
const backups =
|
|
4354
|
-
|
|
4355
|
-
|
|
4356
|
-
const directory =
|
|
4357
|
-
|
|
4763
|
+
const backups = path10.join(configDir, "backups");
|
|
4764
|
+
fs10.mkdirSync(backups, { recursive: true, mode: 448 });
|
|
4765
|
+
fs10.chmodSync(backups, 448);
|
|
4766
|
+
const directory = path10.join(backups, `upgrade-${randomUUID()}`);
|
|
4767
|
+
fs10.mkdirSync(directory, { mode: 448 });
|
|
4358
4768
|
writeDurable(
|
|
4359
|
-
|
|
4769
|
+
path10.join(directory, "recovery.json"),
|
|
4360
4770
|
`${JSON.stringify(
|
|
4361
4771
|
{
|
|
4362
4772
|
previousGeneration: previous.directory,
|
|
@@ -4372,21 +4782,21 @@ var createStore = (configDir, lock) => {
|
|
|
4372
4782
|
syncDirectory(directory);
|
|
4373
4783
|
syncDirectory(backups);
|
|
4374
4784
|
syncDirectory(configDir);
|
|
4375
|
-
const partialFile =
|
|
4785
|
+
const partialFile = path10.join(directory, "database.sql.partial");
|
|
4376
4786
|
return {
|
|
4377
4787
|
directory,
|
|
4378
4788
|
partialFile,
|
|
4379
4789
|
complete: () => whileOwned(() => {
|
|
4380
|
-
if (
|
|
4790
|
+
if (fs10.statSync(partialFile).size === 0)
|
|
4381
4791
|
throw new ServerStoreError("Database backup is empty; refusing target startup.");
|
|
4382
|
-
const fd =
|
|
4792
|
+
const fd = fs10.openSync(partialFile, "r");
|
|
4383
4793
|
try {
|
|
4384
|
-
|
|
4385
|
-
|
|
4794
|
+
fs10.fchmodSync(fd, 384);
|
|
4795
|
+
fs10.fsyncSync(fd);
|
|
4386
4796
|
} finally {
|
|
4387
|
-
|
|
4797
|
+
fs10.closeSync(fd);
|
|
4388
4798
|
}
|
|
4389
|
-
|
|
4799
|
+
fs10.renameSync(partialFile, path10.join(directory, "database.sql"));
|
|
4390
4800
|
syncDirectory(directory);
|
|
4391
4801
|
})
|
|
4392
4802
|
};
|
|
@@ -4395,7 +4805,7 @@ var createStore = (configDir, lock) => {
|
|
|
4395
4805
|
};
|
|
4396
4806
|
var refuseFlatLayout = (configDir) => {
|
|
4397
4807
|
if (["server.json", "server.env", "compose.yaml", "postgres-init.sh"].some(
|
|
4398
|
-
(name) =>
|
|
4808
|
+
(name) => fs10.existsSync(path10.join(configDir, name))
|
|
4399
4809
|
)) {
|
|
4400
4810
|
throw new ServerStoreError(
|
|
4401
4811
|
"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 +4813,7 @@ var refuseFlatLayout = (configDir) => {
|
|
|
4403
4813
|
}
|
|
4404
4814
|
};
|
|
4405
4815
|
var withServerStore = async (directory, operation, options = {}) => {
|
|
4406
|
-
const configDir =
|
|
4816
|
+
const configDir = path10.resolve(directory);
|
|
4407
4817
|
let lock;
|
|
4408
4818
|
let result;
|
|
4409
4819
|
try {
|
|
@@ -4420,9 +4830,9 @@ var withServerStore = async (directory, operation, options = {}) => {
|
|
|
4420
4830
|
};
|
|
4421
4831
|
|
|
4422
4832
|
// src/version.ts
|
|
4423
|
-
import * as
|
|
4833
|
+
import * as fs11 from "node:fs";
|
|
4424
4834
|
var cliVersion = () => {
|
|
4425
|
-
const raw =
|
|
4835
|
+
const raw = fs11.readFileSync(new URL("../package.json", import.meta.url), "utf8");
|
|
4426
4836
|
const parsed = JSON.parse(raw);
|
|
4427
4837
|
if (typeof parsed === "object" && parsed !== null && "version" in parsed) {
|
|
4428
4838
|
const version = parsed.version;
|
|
@@ -4554,21 +4964,21 @@ var parseSetupOptions = (args) => {
|
|
|
4554
4964
|
previous.push(value);
|
|
4555
4965
|
values.set(flag, previous);
|
|
4556
4966
|
}
|
|
4557
|
-
const
|
|
4558
|
-
const version =
|
|
4559
|
-
const appPort =
|
|
4560
|
-
const sshPort =
|
|
4967
|
+
const flagValue3 = (flag) => values.get(flag)?.[0];
|
|
4968
|
+
const version = flagValue3("--version");
|
|
4969
|
+
const appPort = flagValue3("--port");
|
|
4970
|
+
const sshPort = flagValue3("--ssh-port");
|
|
4561
4971
|
const origins = values.get("--origin");
|
|
4562
4972
|
return {
|
|
4563
|
-
context:
|
|
4973
|
+
context: flagValue3("--context"),
|
|
4564
4974
|
version: version === void 0 ? void 0 : parseVersion(version),
|
|
4565
|
-
bind:
|
|
4566
|
-
url:
|
|
4975
|
+
bind: flagValue3("--bind"),
|
|
4976
|
+
url: flagValue3("--url"),
|
|
4567
4977
|
origins: origins === void 0 ? void 0 : origins,
|
|
4568
4978
|
appPort: appPort === void 0 ? void 0 : parsePort(appPort, "--port"),
|
|
4569
4979
|
sshPort: sshPort === void 0 ? void 0 : parsePort(sshPort, "--ssh-port"),
|
|
4570
|
-
dockerSocket:
|
|
4571
|
-
assetsDir:
|
|
4980
|
+
dockerSocket: flagValue3("--docker-socket"),
|
|
4981
|
+
assetsDir: flagValue3("--assets-dir"),
|
|
4572
4982
|
offline: values.has("--offline")
|
|
4573
4983
|
};
|
|
4574
4984
|
};
|
|
@@ -5125,7 +5535,7 @@ var resolveServerVersion = async (runtime, options, existing) => {
|
|
|
5125
5535
|
return version;
|
|
5126
5536
|
};
|
|
5127
5537
|
var parseDockerSocketPath = (socket) => {
|
|
5128
|
-
if (!
|
|
5538
|
+
if (!path11.isAbsolute(socket) || /[\r\n$'"#:\\]/.test(socket)) {
|
|
5129
5539
|
throw setupError(
|
|
5130
5540
|
"--docker-socket must be an absolute path without line breaks or Compose interpolation characters."
|
|
5131
5541
|
);
|
|
@@ -5151,8 +5561,8 @@ var resolveAssets = async (runtime, serverVersion, existing, store, options) =>
|
|
|
5151
5561
|
if (options.assetsDir !== void 0) {
|
|
5152
5562
|
try {
|
|
5153
5563
|
const [compose2, postgresInit2] = await Promise.all([
|
|
5154
|
-
readFile(
|
|
5155
|
-
readFile(
|
|
5564
|
+
readFile(path11.join(options.assetsDir, COMPOSE_ASSET), "utf8"),
|
|
5565
|
+
readFile(path11.join(options.assetsDir, POSTGRES_INIT_ASSET), "utf8")
|
|
5156
5566
|
]);
|
|
5157
5567
|
validateComposeAsset(compose2);
|
|
5158
5568
|
validatePostgresAsset(postgresInit2);
|
|
@@ -5344,6 +5754,19 @@ var setupServer = async (args, runtime, store) => {
|
|
|
5344
5754
|
};
|
|
5345
5755
|
const assets = await resolveAssets(runtime, serverVersion, existing, store, options);
|
|
5346
5756
|
const bucket = composeBucket(assets.compose);
|
|
5757
|
+
if (bucket !== void 0 && runtime.dockerDaemonFacts !== void 0) {
|
|
5758
|
+
const info = await runtime.run("docker", [
|
|
5759
|
+
"--context",
|
|
5760
|
+
selectedContext.name,
|
|
5761
|
+
"info",
|
|
5762
|
+
"--format",
|
|
5763
|
+
"{{json .}}"
|
|
5764
|
+
]);
|
|
5765
|
+
const line = dockerShutdownSetupLine(
|
|
5766
|
+
readShutdownTimeout(runtime.dockerDaemonFacts(info.status === 0 ? info.stdout : null))
|
|
5767
|
+
);
|
|
5768
|
+
if (line !== null) runtime.writeLine(line);
|
|
5769
|
+
}
|
|
5347
5770
|
const config = {
|
|
5348
5771
|
...configWithoutBucket,
|
|
5349
5772
|
...bucket === void 0 ? {} : { bucket }
|
|
@@ -5665,10 +6088,10 @@ var manageServer = async (command, args, runtime, store) => {
|
|
|
5665
6088
|
};
|
|
5666
6089
|
var nodeServerRuntime = () => {
|
|
5667
6090
|
const environment = { ...process.env };
|
|
5668
|
-
const home =
|
|
5669
|
-
const configHome = environment["XDG_CONFIG_HOME"] ??
|
|
6091
|
+
const home = os7.homedir();
|
|
6092
|
+
const configHome = environment["XDG_CONFIG_HOME"] ?? path11.join(home, ".config");
|
|
5670
6093
|
return {
|
|
5671
|
-
configDir:
|
|
6094
|
+
configDir: path11.join(configHome, "mend"),
|
|
5672
6095
|
platform: process.platform,
|
|
5673
6096
|
cliVersion: cliVersion(),
|
|
5674
6097
|
run: (command, args, options) => runServerProcess(command, args, environment, options),
|
|
@@ -5687,7 +6110,8 @@ var nodeServerRuntime = () => {
|
|
|
5687
6110
|
randomBytes,
|
|
5688
6111
|
sleep: (milliseconds) => new Promise((resolve7) => setTimeout(resolve7, milliseconds)),
|
|
5689
6112
|
writeLine: (line) => process.stdout.write(`${line}
|
|
5690
|
-
`)
|
|
6113
|
+
`),
|
|
6114
|
+
dockerDaemonFacts: hostDockerDaemonFacts
|
|
5691
6115
|
};
|
|
5692
6116
|
};
|
|
5693
6117
|
var serverCommand = async (args, runtime = nodeServerRuntime()) => {
|
|
@@ -6120,8 +6544,8 @@ var createServiceTunnels = (options) => {
|
|
|
6120
6544
|
};
|
|
6121
6545
|
|
|
6122
6546
|
// src/skills.ts
|
|
6123
|
-
import * as
|
|
6124
|
-
import * as
|
|
6547
|
+
import * as fs12 from "node:fs";
|
|
6548
|
+
import * as path12 from "node:path";
|
|
6125
6549
|
var DEFAULT_SKILLS_DIR = ".agents/skills";
|
|
6126
6550
|
var MAX_SKILL_FILES = 64;
|
|
6127
6551
|
var MAX_SKILL_FILE_BYTES = 512 * 1024;
|
|
@@ -6144,12 +6568,12 @@ var readFrontmatterDescription = (contents) => {
|
|
|
6144
6568
|
var walkFiles = (root) => {
|
|
6145
6569
|
const found = [];
|
|
6146
6570
|
const visit = (dir) => {
|
|
6147
|
-
for (const entry of
|
|
6571
|
+
for (const entry of fs12.readdirSync(dir, { withFileTypes: true })) {
|
|
6148
6572
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
6149
|
-
const absolute =
|
|
6573
|
+
const absolute = path12.join(dir, entry.name);
|
|
6150
6574
|
let stat;
|
|
6151
6575
|
try {
|
|
6152
|
-
stat =
|
|
6576
|
+
stat = fs12.statSync(absolute);
|
|
6153
6577
|
} catch {
|
|
6154
6578
|
continue;
|
|
6155
6579
|
}
|
|
@@ -6163,7 +6587,7 @@ var walkFiles = (root) => {
|
|
|
6163
6587
|
var scanSkillLibrary = (root) => {
|
|
6164
6588
|
let entries;
|
|
6165
6589
|
try {
|
|
6166
|
-
entries =
|
|
6590
|
+
entries = fs12.readdirSync(root, { withFileTypes: true });
|
|
6167
6591
|
} catch {
|
|
6168
6592
|
return { error: `${root} is not a readable directory` };
|
|
6169
6593
|
}
|
|
@@ -6171,23 +6595,23 @@ var scanSkillLibrary = (root) => {
|
|
|
6171
6595
|
const notes = [];
|
|
6172
6596
|
for (const entry of entries.toSorted((a, b) => a.name.localeCompare(b.name))) {
|
|
6173
6597
|
if (entry.name.startsWith(".")) continue;
|
|
6174
|
-
const skillDir =
|
|
6598
|
+
const skillDir = path12.join(root, entry.name);
|
|
6175
6599
|
let stat;
|
|
6176
6600
|
try {
|
|
6177
|
-
stat =
|
|
6601
|
+
stat = fs12.statSync(skillDir);
|
|
6178
6602
|
} catch {
|
|
6179
6603
|
continue;
|
|
6180
6604
|
}
|
|
6181
6605
|
if (!stat.isDirectory()) continue;
|
|
6182
|
-
if (!
|
|
6606
|
+
if (!fs12.existsSync(path12.join(skillDir, "SKILL.md"))) {
|
|
6183
6607
|
notes.push({ skill: entry.name, message: "no SKILL.md \u2014 skipped" });
|
|
6184
6608
|
continue;
|
|
6185
6609
|
}
|
|
6186
6610
|
const files = [];
|
|
6187
6611
|
let bytes = 0;
|
|
6188
6612
|
for (const absolute of walkFiles(skillDir)) {
|
|
6189
|
-
const relative4 =
|
|
6190
|
-
const buffer =
|
|
6613
|
+
const relative4 = path12.relative(skillDir, absolute).split(path12.sep).join("/");
|
|
6614
|
+
const buffer = fs12.readFileSync(absolute);
|
|
6191
6615
|
if (buffer.byteLength > MAX_SKILL_FILE_BYTES) {
|
|
6192
6616
|
notes.push({ skill: entry.name, message: `${relative4} is over 512KB \u2014 skipped` });
|
|
6193
6617
|
continue;
|
|
@@ -6222,16 +6646,16 @@ var scanSkillLibrary = (root) => {
|
|
|
6222
6646
|
};
|
|
6223
6647
|
|
|
6224
6648
|
// src/ssh-setup.ts
|
|
6225
|
-
import * as
|
|
6226
|
-
import * as
|
|
6649
|
+
import * as os9 from "node:os";
|
|
6650
|
+
import * as path14 from "node:path";
|
|
6227
6651
|
|
|
6228
6652
|
// ../../packages/workspace-ssh/src/index.ts
|
|
6229
|
-
import { spawnSync as
|
|
6653
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
6230
6654
|
import { createHash as createHash3 } from "node:crypto";
|
|
6231
|
-
import * as
|
|
6655
|
+
import * as fs13 from "node:fs";
|
|
6232
6656
|
import { isIP as isIP2 } from "node:net";
|
|
6233
|
-
import * as
|
|
6234
|
-
import * as
|
|
6657
|
+
import * as os8 from "node:os";
|
|
6658
|
+
import * as path13 from "node:path";
|
|
6235
6659
|
var LEGACY_BLOCK_BEGIN = "# >>> mend workspace ssh (managed by `mend ssh setup`) >>>";
|
|
6236
6660
|
var LEGACY_BLOCK_END = "# <<< mend workspace ssh <<<";
|
|
6237
6661
|
var WorkspaceSshError = class extends Error {
|
|
@@ -6318,8 +6742,8 @@ var blockBegin = (alias) => `# >>> mend workspace ssh ${alias} (managed) >>>`;
|
|
|
6318
6742
|
var blockEnd = (alias) => `# <<< mend workspace ssh ${alias} <<<`;
|
|
6319
6743
|
var hasControls = (value) => /[\p{Cc}\p{Zl}\p{Zp}]/u.test(value);
|
|
6320
6744
|
var parseIdentityPath = (value) => {
|
|
6321
|
-
const resolved =
|
|
6322
|
-
value.startsWith("~/") ?
|
|
6745
|
+
const resolved = path13.resolve(
|
|
6746
|
+
value.startsWith("~/") ? path13.join(os8.homedir(), value.slice(2)) : value
|
|
6323
6747
|
);
|
|
6324
6748
|
if (value === "" || hasControls(value) || hasControls(resolved) || value.includes("${") || resolved.includes("${")) {
|
|
6325
6749
|
return failure(
|
|
@@ -6465,7 +6889,7 @@ var workspaceSshPublicKeyFingerprint = (publicKey) => {
|
|
|
6465
6889
|
var publicKeyIdentity = (publicKey) => publicKey.trim().split(/\s+/).slice(0, 2).join(" ");
|
|
6466
6890
|
var agentKeys = () => {
|
|
6467
6891
|
if (!process.env["SSH_AUTH_SOCK"]) return [];
|
|
6468
|
-
const listed =
|
|
6892
|
+
const listed = spawnSync4("ssh-add", ["-L"], {
|
|
6469
6893
|
encoding: "utf8",
|
|
6470
6894
|
timeout: 3e3,
|
|
6471
6895
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -6475,7 +6899,7 @@ var agentKeys = () => {
|
|
|
6475
6899
|
};
|
|
6476
6900
|
var readText = (file) => {
|
|
6477
6901
|
try {
|
|
6478
|
-
return success(
|
|
6902
|
+
return success(fs13.readFileSync(file, "utf8"));
|
|
6479
6903
|
} catch (cause) {
|
|
6480
6904
|
const error = cause instanceof Error && "code" in cause ? cause.code : void 0;
|
|
6481
6905
|
return error === "ENOENT" ? success("") : failure(new WorkspaceSshError("key", `Could not read ${file}.`, cause));
|
|
@@ -6502,7 +6926,7 @@ var readRequiredKey = (keyPath, source) => {
|
|
|
6502
6926
|
const privatePath = publicPath.slice(0, -".pub".length);
|
|
6503
6927
|
const read = readText(publicPath);
|
|
6504
6928
|
if (!read.ok) return read;
|
|
6505
|
-
const derived =
|
|
6929
|
+
const derived = spawnSync4("ssh-keygen", ["-y", "-P", "", "-f", privatePath], {
|
|
6506
6930
|
encoding: "utf8",
|
|
6507
6931
|
timeout: 3e3,
|
|
6508
6932
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -6533,19 +6957,19 @@ var readRequiredKey = (keyPath, source) => {
|
|
|
6533
6957
|
var readOptionalKey = (keyPath) => {
|
|
6534
6958
|
const parsed = parseIdentityPath(keyPath);
|
|
6535
6959
|
if (!parsed.ok) return parsed;
|
|
6536
|
-
if (!
|
|
6960
|
+
if (!fs13.existsSync(parsed.value) && !fs13.existsSync(publicKeyPath(parsed.value)))
|
|
6537
6961
|
return success(null);
|
|
6538
6962
|
return readRequiredKey(parsed.value, "existing");
|
|
6539
6963
|
};
|
|
6540
6964
|
var pinAgentKey = (configHome, publicKey) => {
|
|
6541
|
-
const identityFile =
|
|
6965
|
+
const identityFile = path13.resolve(
|
|
6542
6966
|
configHome,
|
|
6543
6967
|
"ssh",
|
|
6544
6968
|
`agent-${hashIdentity(publicKeyIdentity(publicKey))}.pub`
|
|
6545
6969
|
);
|
|
6546
6970
|
try {
|
|
6547
|
-
|
|
6548
|
-
|
|
6971
|
+
fs13.mkdirSync(path13.dirname(identityFile), { recursive: true, mode: 448 });
|
|
6972
|
+
fs13.writeFileSync(identityFile, `${publicKey}
|
|
6549
6973
|
`, { mode: 384 });
|
|
6550
6974
|
return success({ publicKey, identityFile, source: "agent" });
|
|
6551
6975
|
} catch (cause) {
|
|
@@ -6556,15 +6980,15 @@ var pinAgentKey = (configHome, publicKey) => {
|
|
|
6556
6980
|
};
|
|
6557
6981
|
var generateDedicatedKey = (privatePath) => {
|
|
6558
6982
|
try {
|
|
6559
|
-
|
|
6983
|
+
fs13.mkdirSync(path13.dirname(privatePath), { recursive: true, mode: 448 });
|
|
6560
6984
|
} catch (cause) {
|
|
6561
6985
|
return failure(
|
|
6562
|
-
new WorkspaceSshError("key", `Could not create ${
|
|
6986
|
+
new WorkspaceSshError("key", `Could not create ${path13.dirname(privatePath)}.`, cause)
|
|
6563
6987
|
);
|
|
6564
6988
|
}
|
|
6565
|
-
const generated =
|
|
6989
|
+
const generated = spawnSync4(
|
|
6566
6990
|
"ssh-keygen",
|
|
6567
|
-
["-t", "ed25519", "-f", privatePath, "-N", "", "-C", `mend-${
|
|
6991
|
+
["-t", "ed25519", "-f", privatePath, "-N", "", "-C", `mend-${os8.hostname()}`],
|
|
6568
6992
|
{ encoding: "utf8", timeout: 3e3, stdio: ["ignore", "pipe", "pipe"] }
|
|
6569
6993
|
);
|
|
6570
6994
|
if (generated.status !== 0) {
|
|
@@ -6588,7 +7012,7 @@ var generateDedicatedKey = (privatePath) => {
|
|
|
6588
7012
|
return fresh;
|
|
6589
7013
|
};
|
|
6590
7014
|
var dedicatedKey = (configHome, create) => {
|
|
6591
|
-
const privatePath =
|
|
7015
|
+
const privatePath = path13.join(configHome, "ssh", "id_ed25519");
|
|
6592
7016
|
const existing = readOptionalKey(privatePath);
|
|
6593
7017
|
if (existing.ok === false) return failure(existing.error);
|
|
6594
7018
|
if (existing.value !== null || !create) return existing;
|
|
@@ -6636,10 +7060,10 @@ var writeWorkspaceSshConfig = (configFile, target, identityFile) => {
|
|
|
6636
7060
|
const reconciled = reconcileWorkspaceSshConfig(before.value, target, identityFile);
|
|
6637
7061
|
if (reconciled.ok === false) return failure(reconciled.error);
|
|
6638
7062
|
try {
|
|
6639
|
-
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
7063
|
+
fs13.mkdirSync(path13.dirname(configFile), { recursive: true, mode: 448 });
|
|
7064
|
+
fs13.chmodSync(path13.dirname(configFile), 448);
|
|
7065
|
+
fs13.writeFileSync(configFile, reconciled.value, { mode: 384 });
|
|
7066
|
+
fs13.chmodSync(configFile, 384);
|
|
6643
7067
|
return success(void 0);
|
|
6644
7068
|
} catch (cause) {
|
|
6645
7069
|
return failure(new WorkspaceSshError("config", `Could not write ${configFile}.`, cause));
|
|
@@ -6652,8 +7076,8 @@ var dim5 = ansi("2");
|
|
|
6652
7076
|
var green5 = ansi("32");
|
|
6653
7077
|
var warn = ansi("33");
|
|
6654
7078
|
var say5 = (line) => console.log(line);
|
|
6655
|
-
var sshConfigPath = () =>
|
|
6656
|
-
var
|
|
7079
|
+
var sshConfigPath = () => path14.join(os9.homedir(), ".ssh", "config");
|
|
7080
|
+
var flagValue2 = (args, flag) => {
|
|
6657
7081
|
const index = args.indexOf(flag);
|
|
6658
7082
|
return index === -1 || args[index + 1] === void 0 ? null : String(args[index + 1]);
|
|
6659
7083
|
};
|
|
@@ -6670,7 +7094,7 @@ var showStatus = async (api2, cliHome, serverUrl, args) => {
|
|
|
6670
7094
|
const parsedTarget = parseWorkspaceSshTarget({
|
|
6671
7095
|
serverUrl,
|
|
6672
7096
|
publishedPort: view.gateway.port,
|
|
6673
|
-
hostnameOverride:
|
|
7097
|
+
hostnameOverride: flagValue2(args, "--host")
|
|
6674
7098
|
});
|
|
6675
7099
|
if (parsedTarget.ok === false) return showFailure(parsedTarget.error.message);
|
|
6676
7100
|
const config = readWorkspaceSshConfig(sshConfigPath());
|
|
@@ -6707,14 +7131,14 @@ var setup = async (api2, cliHome, serverUrl, args) => {
|
|
|
6707
7131
|
const parsedTarget = parseWorkspaceSshTarget({
|
|
6708
7132
|
serverUrl,
|
|
6709
7133
|
publishedPort: view.gateway.port,
|
|
6710
|
-
hostnameOverride:
|
|
7134
|
+
hostnameOverride: flagValue2(args, "--host")
|
|
6711
7135
|
});
|
|
6712
7136
|
if (parsedTarget.ok === false) return showFailure(parsedTarget.error.message);
|
|
6713
7137
|
const config = readWorkspaceSshConfig(sshConfigPath());
|
|
6714
7138
|
if (config.ok === false) return showFailure(config.error.message);
|
|
6715
7139
|
const picked = pickWorkspaceSshKey({
|
|
6716
7140
|
configHome: cliHome,
|
|
6717
|
-
explicitKeyPath:
|
|
7141
|
+
explicitKeyPath: flagValue2(args, "--key"),
|
|
6718
7142
|
configuredIdentityFile: configuredWorkspaceSshIdentityFile(config.value, parsedTarget.value),
|
|
6719
7143
|
create: true
|
|
6720
7144
|
});
|
|
@@ -6722,7 +7146,7 @@ var setup = async (api2, cliHome, serverUrl, args) => {
|
|
|
6722
7146
|
if (picked.value === null) return showFailure("No workspace SSH key is available.");
|
|
6723
7147
|
const registered = await api2("POST", "/workspace-ssh/keys", {
|
|
6724
7148
|
publicKey: picked.value.publicKey,
|
|
6725
|
-
name:
|
|
7149
|
+
name: os9.hostname()
|
|
6726
7150
|
});
|
|
6727
7151
|
const sourceLabel = {
|
|
6728
7152
|
explicit: "from --key",
|
|
@@ -6764,8 +7188,8 @@ var sshCommand = async (args, api2, cliHome, serverUrl) => {
|
|
|
6764
7188
|
};
|
|
6765
7189
|
|
|
6766
7190
|
// src/uninstall.ts
|
|
6767
|
-
import * as
|
|
6768
|
-
import * as
|
|
7191
|
+
import * as fs14 from "node:fs";
|
|
7192
|
+
import * as path15 from "node:path";
|
|
6769
7193
|
var UNINSTALL_USAGE = "usage: mend uninstall [--all | --server | --home] [--yes]";
|
|
6770
7194
|
var parseUninstallArgs = (args) => {
|
|
6771
7195
|
let scope = null;
|
|
@@ -6787,14 +7211,14 @@ var parseUninstallArgs = (args) => {
|
|
|
6787
7211
|
var SERVER_FILES = ["identity.env", "active", "generations", "backups"];
|
|
6788
7212
|
var countEntries = (directory) => {
|
|
6789
7213
|
try {
|
|
6790
|
-
return
|
|
7214
|
+
return fs14.readdirSync(directory).length;
|
|
6791
7215
|
} catch {
|
|
6792
7216
|
return 0;
|
|
6793
7217
|
}
|
|
6794
7218
|
};
|
|
6795
7219
|
var exists = (file) => {
|
|
6796
7220
|
try {
|
|
6797
|
-
|
|
7221
|
+
fs14.lstatSync(file);
|
|
6798
7222
|
return true;
|
|
6799
7223
|
} catch {
|
|
6800
7224
|
return false;
|
|
@@ -6803,7 +7227,7 @@ var exists = (file) => {
|
|
|
6803
7227
|
var managedBlockCount = (sshConfigFile) => {
|
|
6804
7228
|
let config;
|
|
6805
7229
|
try {
|
|
6806
|
-
config =
|
|
7230
|
+
config = fs14.readFileSync(sshConfigFile, "utf8");
|
|
6807
7231
|
} catch {
|
|
6808
7232
|
return 0;
|
|
6809
7233
|
}
|
|
@@ -6827,27 +7251,27 @@ var describeUninstall = async (runtime, scope) => {
|
|
|
6827
7251
|
if (!read.error.message.startsWith("No Mend server is configured")) throw read.error;
|
|
6828
7252
|
server = "none";
|
|
6829
7253
|
} else if (read.value === null) {
|
|
6830
|
-
server = exists(
|
|
7254
|
+
server = exists(path15.join(configDir, "identity.env")) ? {
|
|
6831
7255
|
version: "(no active generation)",
|
|
6832
7256
|
appUrl: "",
|
|
6833
7257
|
dockerContext: "",
|
|
6834
|
-
generations: countEntries(
|
|
6835
|
-
backups: countEntries(
|
|
7258
|
+
generations: countEntries(path15.join(configDir, "generations")),
|
|
7259
|
+
backups: countEntries(path15.join(configDir, "backups"))
|
|
6836
7260
|
} : "none";
|
|
6837
7261
|
} else {
|
|
6838
7262
|
server = {
|
|
6839
7263
|
version: read.value.config.serverVersion,
|
|
6840
7264
|
appUrl: read.value.config.appUrl,
|
|
6841
7265
|
dockerContext: read.value.config.dockerContext,
|
|
6842
|
-
generations: countEntries(
|
|
6843
|
-
backups: countEntries(
|
|
7266
|
+
generations: countEntries(path15.join(configDir, "generations")),
|
|
7267
|
+
backups: countEntries(path15.join(configDir, "backups"))
|
|
6844
7268
|
};
|
|
6845
7269
|
}
|
|
6846
7270
|
}
|
|
6847
7271
|
let home = null;
|
|
6848
7272
|
if (scope !== "server") {
|
|
6849
|
-
const cliConfig =
|
|
6850
|
-
const sshDirectory =
|
|
7273
|
+
const cliConfig = path15.join(runtime.cliHome, "cli.json");
|
|
7274
|
+
const sshDirectory = path15.join(runtime.cliHome, "ssh");
|
|
6851
7275
|
home = {
|
|
6852
7276
|
cliConfig: exists(cliConfig) ? cliConfig : null,
|
|
6853
7277
|
sshDirectory: exists(sshDirectory) ? sshDirectory : null,
|
|
@@ -6979,7 +7403,7 @@ var removeServer = async (runtime, failures, leftovers) => {
|
|
|
6979
7403
|
}
|
|
6980
7404
|
}
|
|
6981
7405
|
for (const name of SERVER_FILES) {
|
|
6982
|
-
|
|
7406
|
+
fs14.rmSync(path15.join(configDir, name), { recursive: true, force: true });
|
|
6983
7407
|
}
|
|
6984
7408
|
server.writeLine(`removed ${configDir}/{identity.env, active, generations, backups}`);
|
|
6985
7409
|
},
|
|
@@ -6990,20 +7414,20 @@ var removeServer = async (runtime, failures, leftovers) => {
|
|
|
6990
7414
|
var removeHome = async (runtime, plan, failures, leftovers) => {
|
|
6991
7415
|
const { server } = runtime;
|
|
6992
7416
|
if (plan.cliConfig !== null) {
|
|
6993
|
-
|
|
7417
|
+
fs14.rmSync(plan.cliConfig, { force: true });
|
|
6994
7418
|
server.writeLine(`removed ${plan.cliConfig}`);
|
|
6995
7419
|
}
|
|
6996
7420
|
if (plan.sshDirectory !== null) {
|
|
6997
|
-
|
|
7421
|
+
fs14.rmSync(plan.sshDirectory, { recursive: true, force: true });
|
|
6998
7422
|
server.writeLine(`removed ${plan.sshDirectory}`);
|
|
6999
7423
|
}
|
|
7000
7424
|
if (plan.managedSshBlocks > 0) {
|
|
7001
7425
|
try {
|
|
7002
|
-
const before =
|
|
7426
|
+
const before = fs14.readFileSync(runtime.sshConfigFile, "utf8");
|
|
7003
7427
|
const stripped = stripManagedWorkspaceSshBlocks(before);
|
|
7004
7428
|
if (!stripped.ok) throw stripped.error;
|
|
7005
7429
|
if (stripped.value.removed > 0) {
|
|
7006
|
-
|
|
7430
|
+
fs14.writeFileSync(runtime.sshConfigFile, stripped.value.config, { mode: 384 });
|
|
7007
7431
|
server.writeLine(
|
|
7008
7432
|
`removed ${plural(stripped.value.removed, "managed block")} from ${runtime.sshConfigFile}`
|
|
7009
7433
|
);
|
|
@@ -7016,12 +7440,12 @@ var removeHome = async (runtime, plan, failures, leftovers) => {
|
|
|
7016
7440
|
}
|
|
7017
7441
|
let remaining = [];
|
|
7018
7442
|
try {
|
|
7019
|
-
remaining =
|
|
7443
|
+
remaining = fs14.readdirSync(runtime.cliHome);
|
|
7020
7444
|
} catch {
|
|
7021
7445
|
return;
|
|
7022
7446
|
}
|
|
7023
7447
|
if (remaining.length === 0) {
|
|
7024
|
-
|
|
7448
|
+
fs14.rmdirSync(runtime.cliHome);
|
|
7025
7449
|
server.writeLine(`removed ${runtime.cliHome}`);
|
|
7026
7450
|
} else {
|
|
7027
7451
|
leftovers.push(
|
|
@@ -7085,19 +7509,19 @@ var upgradeUrl = async (input) => {
|
|
|
7085
7509
|
// src/main.ts
|
|
7086
7510
|
var mendCliHome = () => {
|
|
7087
7511
|
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
7088
|
-
const preferred =
|
|
7089
|
-
xdg === void 0 || xdg === "" ?
|
|
7512
|
+
const preferred = path16.join(
|
|
7513
|
+
xdg === void 0 || xdg === "" ? path16.join(os10.homedir(), ".config") : xdg,
|
|
7090
7514
|
"mend"
|
|
7091
7515
|
);
|
|
7092
|
-
const legacy =
|
|
7093
|
-
return !
|
|
7516
|
+
const legacy = path16.join(os10.homedir(), ".mend");
|
|
7517
|
+
return !fs15.existsSync(preferred) && fs15.existsSync(legacy) ? legacy : preferred;
|
|
7094
7518
|
};
|
|
7095
|
-
var CONFIG_PATH =
|
|
7519
|
+
var CONFIG_PATH = path16.join(mendCliHome(), "cli.json");
|
|
7096
7520
|
var loadConfig = () => {
|
|
7097
7521
|
let fileConfig = {};
|
|
7098
|
-
if (
|
|
7522
|
+
if (fs15.existsSync(CONFIG_PATH)) {
|
|
7099
7523
|
try {
|
|
7100
|
-
fileConfig = JSON.parse(
|
|
7524
|
+
fileConfig = JSON.parse(fs15.readFileSync(CONFIG_PATH, "utf8"));
|
|
7101
7525
|
} catch {
|
|
7102
7526
|
fail6(`could not parse ${CONFIG_PATH}`);
|
|
7103
7527
|
}
|
|
@@ -7135,9 +7559,10 @@ var herdrAgentOf = (harness) => {
|
|
|
7135
7559
|
if (harness === "codex" || harness === "claude" || harness === "opencode") return harness;
|
|
7136
7560
|
return null;
|
|
7137
7561
|
};
|
|
7562
|
+
var noHerdrHint = () => void 0;
|
|
7138
7563
|
var hintHerdrAttachment = (harness) => {
|
|
7139
7564
|
const agent = herdrAgentOf(harness);
|
|
7140
|
-
if (process.env["HERDR_ENV"] !== "1" || agent === null) return
|
|
7565
|
+
if (process.env["HERDR_ENV"] !== "1" || agent === null) return noHerdrHint;
|
|
7141
7566
|
const carrier = spawn3(
|
|
7142
7567
|
process.execPath,
|
|
7143
7568
|
["-e", "process.stdin.resume();process.stdin.once('end',()=>process.exit(0))"],
|
|
@@ -7163,6 +7588,8 @@ var request = async (config, method, route, body) => {
|
|
|
7163
7588
|
const headers = { "content-type": "application/json" };
|
|
7164
7589
|
if (config.token !== null) headers["authorization"] = `Bearer ${config.token}`;
|
|
7165
7590
|
let response;
|
|
7591
|
+
const started = Date.now();
|
|
7592
|
+
const call = `${method} ${route}`;
|
|
7166
7593
|
try {
|
|
7167
7594
|
response = await fetch(`${config.url}/api${route}`, {
|
|
7168
7595
|
method,
|
|
@@ -7170,23 +7597,30 @@ var request = async (config, method, route, body) => {
|
|
|
7170
7597
|
// Spread, not `body: null` — fresh oxlint rejects a body key on GETs.
|
|
7171
7598
|
...body === void 0 ? {} : { body: JSON.stringify(body) }
|
|
7172
7599
|
});
|
|
7173
|
-
} catch {
|
|
7174
|
-
throw
|
|
7600
|
+
} catch (error) {
|
|
7601
|
+
throw noAnswerError(error, config.url, call, Date.now() - started);
|
|
7175
7602
|
}
|
|
7176
7603
|
if (response.status === 401) {
|
|
7177
|
-
throw new
|
|
7178
|
-
|
|
7604
|
+
throw new MendRequestError(
|
|
7605
|
+
"http",
|
|
7606
|
+
config.token === null ? `not signed in to ${config.url} \u2014 run: mend login` : `unauthorized at ${config.url} \u2014 the saved token was rejected; run: mend login`,
|
|
7607
|
+
401
|
|
7179
7608
|
);
|
|
7180
7609
|
}
|
|
7181
|
-
|
|
7610
|
+
let text;
|
|
7611
|
+
try {
|
|
7612
|
+
text = await response.text();
|
|
7613
|
+
} catch (error) {
|
|
7614
|
+
throw noAnswerError(error, config.url, call, Date.now() - started);
|
|
7615
|
+
}
|
|
7182
7616
|
if (!response.ok) {
|
|
7183
|
-
let message = `${
|
|
7617
|
+
let message = `${call} \u2192 ${response.status}`;
|
|
7184
7618
|
try {
|
|
7185
7619
|
const parsed = JSON.parse(text);
|
|
7186
7620
|
message = parsed.message ?? message;
|
|
7187
7621
|
} catch {
|
|
7188
7622
|
}
|
|
7189
|
-
throw new
|
|
7623
|
+
throw new MendRequestError("http", message, response.status);
|
|
7190
7624
|
}
|
|
7191
7625
|
try {
|
|
7192
7626
|
return JSON.parse(text);
|
|
@@ -7205,10 +7639,11 @@ var download = (config) => async (route) => {
|
|
|
7205
7639
|
const headers = {};
|
|
7206
7640
|
if (config.token !== null) headers["authorization"] = `Bearer ${config.token}`;
|
|
7207
7641
|
let response;
|
|
7642
|
+
const started = Date.now();
|
|
7208
7643
|
try {
|
|
7209
7644
|
response = await fetch(`${config.url}/api${route}`, { headers });
|
|
7210
|
-
} catch {
|
|
7211
|
-
return fail6(
|
|
7645
|
+
} catch (error) {
|
|
7646
|
+
return fail6(noAnswerError(error, config.url, `GET ${route}`, Date.now() - started).message);
|
|
7212
7647
|
}
|
|
7213
7648
|
if (response.status === 401) {
|
|
7214
7649
|
return fail6(
|
|
@@ -7219,13 +7654,14 @@ var download = (config) => async (route) => {
|
|
|
7219
7654
|
return { status: response.status, header: (name) => response.headers.get(name), bytes };
|
|
7220
7655
|
};
|
|
7221
7656
|
var warnedLegacyBearer = false;
|
|
7222
|
-
var mintTicket = (config) => async (target, params) => {
|
|
7657
|
+
var mintTicket = (config, signal) => async (target, params) => {
|
|
7223
7658
|
const headers = { "content-type": "application/json" };
|
|
7224
7659
|
if (config.token !== null) headers["authorization"] = `Bearer ${config.token}`;
|
|
7225
7660
|
const response = await fetch(`${config.url}/api/upgrade-tickets`, {
|
|
7226
7661
|
method: "POST",
|
|
7227
7662
|
headers,
|
|
7228
|
-
body: JSON.stringify({ target, ...params })
|
|
7663
|
+
body: JSON.stringify({ target, ...params }),
|
|
7664
|
+
...signal === void 0 ? {} : { signal }
|
|
7229
7665
|
});
|
|
7230
7666
|
if (response.status === 404) {
|
|
7231
7667
|
if (await mintRefusedInTransit(config.url)) throw new Error(MINT_REFUSED_IN_TRANSIT);
|
|
@@ -7243,12 +7679,12 @@ var mintTicket = (config) => async (target, params) => {
|
|
|
7243
7679
|
throw new Error("upgrade ticket missing from the answer");
|
|
7244
7680
|
return { kind: "ticket", ticket: minted.ticket };
|
|
7245
7681
|
};
|
|
7246
|
-
var socketUrl = (config, target, params, extra) => upgradeUrl({
|
|
7682
|
+
var socketUrl = (config, target, params, extra, signal) => upgradeUrl({
|
|
7247
7683
|
serverUrl: parseMendUrl(config.url).toString(),
|
|
7248
7684
|
target,
|
|
7249
7685
|
params,
|
|
7250
7686
|
...extra === void 0 ? {} : { extra },
|
|
7251
|
-
mint: mintTicket(config),
|
|
7687
|
+
mint: mintTicket(config, signal),
|
|
7252
7688
|
legacyToken: config.token
|
|
7253
7689
|
});
|
|
7254
7690
|
var boundApi = (config) => (method, route, body) => api(config, method, route, body);
|
|
@@ -7258,8 +7694,11 @@ var withSpinner = async (label, work) => {
|
|
|
7258
7694
|
const started = Date.now();
|
|
7259
7695
|
let frame = 0;
|
|
7260
7696
|
const timer = setInterval(() => {
|
|
7261
|
-
const
|
|
7262
|
-
|
|
7697
|
+
const seconds2 = Math.round((Date.now() - started) / 1e3);
|
|
7698
|
+
const text = typeof label === "string" ? label : label();
|
|
7699
|
+
process.stdout.write(
|
|
7700
|
+
`\r\x1B[2K ${frames[frame % frames.length]} ${text} ${dim6(`${seconds2}s`)} `
|
|
7701
|
+
);
|
|
7263
7702
|
frame += 1;
|
|
7264
7703
|
}, 120);
|
|
7265
7704
|
try {
|
|
@@ -7269,6 +7708,52 @@ var withSpinner = async (label, work) => {
|
|
|
7269
7708
|
process.stdout.write("\r\x1B[2K");
|
|
7270
7709
|
}
|
|
7271
7710
|
};
|
|
7711
|
+
var followStarting = async (config, sessionId, label, start) => {
|
|
7712
|
+
let line = label;
|
|
7713
|
+
const timers = /* @__PURE__ */ new Set();
|
|
7714
|
+
const sleep3 = (ms) => new Promise((resolve7) => {
|
|
7715
|
+
const timer = setTimeout(() => {
|
|
7716
|
+
timers.delete(timer);
|
|
7717
|
+
resolve7();
|
|
7718
|
+
}, ms);
|
|
7719
|
+
timers.add(timer);
|
|
7720
|
+
});
|
|
7721
|
+
try {
|
|
7722
|
+
return await withSpinner(
|
|
7723
|
+
() => line,
|
|
7724
|
+
followStart({
|
|
7725
|
+
start,
|
|
7726
|
+
read: () => request(config, "GET", `/sessions/${sessionId}`),
|
|
7727
|
+
onLine: (next) => {
|
|
7728
|
+
line = next === "starting" ? label : next;
|
|
7729
|
+
if (process.stdout.isTTY !== true) say6(dim6(` ${next}`));
|
|
7730
|
+
},
|
|
7731
|
+
sleep: sleep3,
|
|
7732
|
+
now: Date.now
|
|
7733
|
+
})
|
|
7734
|
+
);
|
|
7735
|
+
} finally {
|
|
7736
|
+
for (const timer of timers) clearTimeout(timer);
|
|
7737
|
+
}
|
|
7738
|
+
};
|
|
7739
|
+
var startedOrExit = (config, sessionId, outcome) => {
|
|
7740
|
+
const id8 = sessionId.slice(0, 8);
|
|
7741
|
+
switch (outcome.kind) {
|
|
7742
|
+
case "live":
|
|
7743
|
+
return outcome.session;
|
|
7744
|
+
case "refused":
|
|
7745
|
+
return fail6(outcome.message);
|
|
7746
|
+
case "settled":
|
|
7747
|
+
return fail6(
|
|
7748
|
+
`session ${id8} \xB7 ${startingLineOf(outcome.session)} before its agent ran \xB7 ${config.url}/sessions/${sessionId}`
|
|
7749
|
+
);
|
|
7750
|
+
case "unreachable":
|
|
7751
|
+
return fail6(
|
|
7752
|
+
`${outcome.message} \xB7 the session may still be starting \xB7 follow it: mend attach ${id8}`
|
|
7753
|
+
);
|
|
7754
|
+
}
|
|
7755
|
+
};
|
|
7756
|
+
var startAndFollow = async (config, sessionId, label, start) => startedOrExit(config, sessionId, await followStarting(config, sessionId, label, start));
|
|
7272
7757
|
var adopt = async (config, args) => {
|
|
7273
7758
|
const nameFlag = args.indexOf("--name");
|
|
7274
7759
|
const name = nameFlag !== -1 && args[nameFlag + 1] !== void 0 ? String(args[nameFlag + 1]) : null;
|
|
@@ -7288,7 +7773,7 @@ var adopt = async (config, args) => {
|
|
|
7288
7773
|
}
|
|
7289
7774
|
const sourceIssue = repositoryCloneUrlIssue(source);
|
|
7290
7775
|
if (sourceIssue !== null) return fail6(sourceIssue);
|
|
7291
|
-
const projectName = name ?? normalizeProjectName(
|
|
7776
|
+
const projectName = name ?? normalizeProjectName(path16.basename(source, ".git"));
|
|
7292
7777
|
const auth = authFlagIndex !== -1 && args[authFlagIndex + 1] !== void 0 ? String(args[authFlagIndex + 1]) : null;
|
|
7293
7778
|
if (auth !== null && auth !== "ambient" && auth !== "mend-key" && auth !== "bridge") {
|
|
7294
7779
|
return fail6(`--auth takes "ambient", "mend-key", or "bridge", not "${auth}"`);
|
|
@@ -7337,7 +7822,7 @@ var findProject = async (config, explicit, adoptCwd = false) => {
|
|
|
7337
7822
|
const sourceIssue = repositoryCloneUrlIssue(facts.originUrl);
|
|
7338
7823
|
if (sourceIssue !== null) return fail6(sourceIssue);
|
|
7339
7824
|
const adopted = await api(config, "POST", "/projects", {
|
|
7340
|
-
name: normalizeProjectName(
|
|
7825
|
+
name: normalizeProjectName(path16.basename(facts.repoRoot)),
|
|
7341
7826
|
source: facts.originUrl
|
|
7342
7827
|
});
|
|
7343
7828
|
say6(`${green6("\u2713")} adopted \xB7 ${adopted.name} \xB7 ${dim6(adopted.storePath)}`);
|
|
@@ -7470,9 +7955,11 @@ var launch = async (config, harness, args) => {
|
|
|
7470
7955
|
...parsed.ask ? { permissionMode: "ask" } : {},
|
|
7471
7956
|
...parsed.fast ? { speed: "fast" } : {}
|
|
7472
7957
|
} : { argv };
|
|
7473
|
-
await
|
|
7958
|
+
await startAndFollow(
|
|
7959
|
+
config,
|
|
7960
|
+
session.id,
|
|
7474
7961
|
"provisioning workspace \u2014 a first launch builds the harness image (can take minutes)\u2026",
|
|
7475
|
-
|
|
7962
|
+
request(config, "POST", `/sessions/${session.id}/launch`, launchBody)
|
|
7476
7963
|
);
|
|
7477
7964
|
if (lifecycle === "detach") {
|
|
7478
7965
|
say6(`${green6("\u2713 recording")} \xB7 running detached`);
|
|
@@ -7494,58 +7981,67 @@ var autoLandLine = (override, project2) => {
|
|
|
7494
7981
|
}
|
|
7495
7982
|
return `${green6("\u2713")} automatic landing \xB7 ${override ? "on" : "off"} for this session ${dim6("\xB7 from this terminal, land with mend land")}`;
|
|
7496
7983
|
};
|
|
7497
|
-
var
|
|
7498
|
-
|
|
7499
|
-
|
|
7500
|
-
|
|
7501
|
-
|
|
7502
|
-
|
|
7503
|
-
|
|
7504
|
-
|
|
7984
|
+
var ATTACH_CONNECT_TIMEOUT_MS = (() => {
|
|
7985
|
+
const configured = Number(process.env["MEND_ATTACH_TIMEOUT_MS"]);
|
|
7986
|
+
return Number.isFinite(configured) && configured > 0 ? configured : 3e4;
|
|
7987
|
+
})();
|
|
7988
|
+
var connectingLine = (sessionId, elapsedMs) => `\r\x1B[2K connecting to ${sessionId.slice(0, 8)} \xB7 ${Math.round(elapsedMs / 1e3)}s \xB7 ${detachKeyEnabled ? "Ctrl+] or " : ""}Ctrl+C cancels`;
|
|
7989
|
+
var takeTerminal = () => {
|
|
7990
|
+
if (process.stdin.isTTY !== true) {
|
|
7991
|
+
console.error(
|
|
7992
|
+
"mend: stdin is not a terminal, so it cannot be put in raw mode \u2014 keys reach the session a line at a time and echo here"
|
|
7505
7993
|
);
|
|
7506
|
-
|
|
7507
|
-
return "unavailable";
|
|
7994
|
+
return { raw: false };
|
|
7508
7995
|
}
|
|
7509
|
-
const ws = new WebSocket(url);
|
|
7510
|
-
ws.binaryType = "arraybuffer";
|
|
7511
7996
|
try {
|
|
7512
|
-
|
|
7513
|
-
|
|
7514
|
-
|
|
7515
|
-
|
|
7516
|
-
|
|
7517
|
-
return
|
|
7997
|
+
process.stdin.setRawMode(true);
|
|
7998
|
+
} catch (error) {
|
|
7999
|
+
console.error(
|
|
8000
|
+
`mend: could not put this terminal in raw mode (${error instanceof Error ? error.message : String(error)}) \u2014 keys reach the session a line at a time and echo here`
|
|
8001
|
+
);
|
|
8002
|
+
return { raw: false };
|
|
7518
8003
|
}
|
|
7519
|
-
|
|
7520
|
-
|
|
7521
|
-
|
|
7522
|
-
finishAttachment = resolve7;
|
|
7523
|
-
});
|
|
7524
|
-
const onClose = () => finishAttachment?.();
|
|
7525
|
-
if (ws.readyState === WebSocket.CLOSED) finishAttachment?.();
|
|
7526
|
-
else ws.addEventListener("close", onClose, { once: true });
|
|
7527
|
-
const sendResize = () => {
|
|
7528
|
-
if (ws.readyState !== WebSocket.OPEN) return;
|
|
7529
|
-
ws.send(
|
|
7530
|
-
JSON.stringify({
|
|
7531
|
-
t: "resize",
|
|
7532
|
-
cols: process.stdout.columns ?? 80,
|
|
7533
|
-
rows: process.stdout.rows ?? 24
|
|
7534
|
-
})
|
|
8004
|
+
if (!process.stdin.isRaw) {
|
|
8005
|
+
console.error(
|
|
8006
|
+
"mend: this terminal did not accept raw mode \u2014 keys reach the session a line at a time and echo here"
|
|
7535
8007
|
);
|
|
8008
|
+
return { raw: false };
|
|
8009
|
+
}
|
|
8010
|
+
return { raw: true };
|
|
8011
|
+
};
|
|
8012
|
+
var terminalSize = () => ({
|
|
8013
|
+
cols: process.stdout.columns || 80,
|
|
8014
|
+
rows: process.stdout.rows || 24
|
|
8015
|
+
});
|
|
8016
|
+
var attachTty = async (config, sessionId, harness, from, processId, options) => {
|
|
8017
|
+
const interactive = options?.readOnly !== true;
|
|
8018
|
+
const rawModeEnabled = interactive ? takeTerminal().raw : false;
|
|
8019
|
+
const connecting = new AbortController();
|
|
8020
|
+
let abortReason = null;
|
|
8021
|
+
const abort = (reason) => {
|
|
8022
|
+
abortReason ??= reason;
|
|
8023
|
+
connecting.abort();
|
|
7536
8024
|
};
|
|
7537
|
-
|
|
7538
|
-
const rawTty = process.stdin.isTTY === true;
|
|
7539
|
-
let rawModeEnabled = false;
|
|
8025
|
+
let ws = null;
|
|
7540
8026
|
let detached = false;
|
|
7541
8027
|
let sawEnd = false;
|
|
7542
8028
|
let interrupted = false;
|
|
8029
|
+
let finishAttachment;
|
|
8030
|
+
const finished = new Promise((resolve7) => {
|
|
8031
|
+
finishAttachment = resolve7;
|
|
8032
|
+
});
|
|
7543
8033
|
const onSignal = () => {
|
|
7544
8034
|
interrupted = true;
|
|
7545
|
-
|
|
8035
|
+
abort("interrupted");
|
|
8036
|
+
ws?.close();
|
|
7546
8037
|
finishAttachment?.();
|
|
7547
8038
|
};
|
|
7548
8039
|
const signals = ["SIGHUP", "SIGINT", "SIGTERM"];
|
|
8040
|
+
const sendResize = (size = terminalSize()) => {
|
|
8041
|
+
if (ws === null || ws.readyState !== WebSocket.OPEN) return;
|
|
8042
|
+
ws.send(JSON.stringify({ t: "resize", cols: size.cols, rows: size.rows }));
|
|
8043
|
+
};
|
|
8044
|
+
const onWinch = () => sendResize();
|
|
7549
8045
|
let bracketedPaste = false;
|
|
7550
8046
|
const onTtyFrame = (event) => {
|
|
7551
8047
|
if (typeof event.data !== "string") {
|
|
@@ -7560,15 +8056,24 @@ var attachTty = async (config, sessionId, harness, from, processId, options) =>
|
|
|
7560
8056
|
return;
|
|
7561
8057
|
}
|
|
7562
8058
|
sawEnd = true;
|
|
7563
|
-
ws
|
|
8059
|
+
ws?.close();
|
|
7564
8060
|
finishAttachment?.();
|
|
7565
8061
|
} catch {
|
|
7566
8062
|
}
|
|
7567
8063
|
};
|
|
8064
|
+
const forward = (bytes) => {
|
|
8065
|
+
if (ws !== null && ws.readyState === WebSocket.OPEN) ws.send(new Uint8Array(bytes).buffer);
|
|
8066
|
+
};
|
|
7568
8067
|
const onKeys = (data) => {
|
|
8068
|
+
if (ws === null) {
|
|
8069
|
+
if (detachKeyEnabled && isDetachChunk(data)) abort("detached");
|
|
8070
|
+
else if (data.includes(3)) abort("interrupted");
|
|
8071
|
+
return;
|
|
8072
|
+
}
|
|
7569
8073
|
if (detachKeyEnabled && isDetachChunk(data)) {
|
|
7570
8074
|
detached = true;
|
|
7571
8075
|
ws.close();
|
|
8076
|
+
finishAttachment?.();
|
|
7572
8077
|
return;
|
|
7573
8078
|
}
|
|
7574
8079
|
if (ws.readyState !== WebSocket.OPEN) return;
|
|
@@ -7578,9 +8083,6 @@ var attachTty = async (config, sessionId, harness, from, processId, options) =>
|
|
|
7578
8083
|
}
|
|
7579
8084
|
ws.send(new Uint8Array(data).buffer);
|
|
7580
8085
|
};
|
|
7581
|
-
const forward = (bytes) => {
|
|
7582
|
-
if (ws.readyState === WebSocket.OPEN) ws.send(new Uint8Array(bytes).buffer);
|
|
7583
|
-
};
|
|
7584
8086
|
const pasteClipboardImage = async (keystroke) => {
|
|
7585
8087
|
const image = await readClipboardImage();
|
|
7586
8088
|
if (image === null) {
|
|
@@ -7599,36 +8101,88 @@ var attachTty = async (config, sessionId, harness, from, processId, options) =>
|
|
|
7599
8101
|
process.stdout.write("\x07");
|
|
7600
8102
|
}
|
|
7601
8103
|
};
|
|
8104
|
+
const connect2 = async () => {
|
|
8105
|
+
let url;
|
|
8106
|
+
try {
|
|
8107
|
+
url = await socketUrl(
|
|
8108
|
+
config,
|
|
8109
|
+
"tty",
|
|
8110
|
+
processId === void 0 ? { session: sessionId } : { process: processId },
|
|
8111
|
+
{ from: from.toString() },
|
|
8112
|
+
connecting.signal
|
|
8113
|
+
);
|
|
8114
|
+
} catch {
|
|
8115
|
+
return connecting.signal.aborted ? "aborted" : "unavailable";
|
|
8116
|
+
}
|
|
8117
|
+
if (connecting.signal.aborted) return "aborted";
|
|
8118
|
+
const socket = new WebSocket(url);
|
|
8119
|
+
socket.binaryType = "arraybuffer";
|
|
8120
|
+
const opened = await new Promise((resolve7) => {
|
|
8121
|
+
const onAbort = () => resolve7(false);
|
|
8122
|
+
connecting.signal.addEventListener("abort", onAbort, { once: true });
|
|
8123
|
+
const settle = (value) => {
|
|
8124
|
+
connecting.signal.removeEventListener("abort", onAbort);
|
|
8125
|
+
resolve7(value);
|
|
8126
|
+
};
|
|
8127
|
+
socket.addEventListener("open", () => settle(true), { once: true });
|
|
8128
|
+
socket.addEventListener("error", () => settle(false), { once: true });
|
|
8129
|
+
});
|
|
8130
|
+
if (opened && !connecting.signal.aborted) return socket;
|
|
8131
|
+
socket.close();
|
|
8132
|
+
return connecting.signal.aborted ? "aborted" : "unavailable";
|
|
8133
|
+
};
|
|
8134
|
+
const started = Date.now();
|
|
8135
|
+
let connectingShown = false;
|
|
8136
|
+
const connectingTimer = setInterval(() => {
|
|
8137
|
+
if (process.stdout.isTTY !== true) return;
|
|
8138
|
+
connectingShown = true;
|
|
8139
|
+
process.stdout.write(connectingLine(sessionId, Date.now() - started));
|
|
8140
|
+
}, 1e3);
|
|
8141
|
+
const deadline = setTimeout(() => abort("no-answer"), ATTACH_CONNECT_TIMEOUT_MS);
|
|
8142
|
+
const stopConnecting = () => {
|
|
8143
|
+
for (const timer of [connectingTimer, deadline]) clearTimeout(timer);
|
|
8144
|
+
if (connectingShown) process.stdout.write("\r\x1B[2K");
|
|
8145
|
+
connectingShown = false;
|
|
8146
|
+
};
|
|
8147
|
+
let stopHerdrHint = noHerdrHint;
|
|
7602
8148
|
try {
|
|
7603
|
-
sendResize();
|
|
7604
|
-
process.stdout.on("resize", onWinch);
|
|
7605
8149
|
if (options?.handleSignals === true) for (const signal of signals) process.on(signal, onSignal);
|
|
7606
|
-
if (
|
|
7607
|
-
if (rawTty) {
|
|
7608
|
-
process.stdin.setRawMode(true);
|
|
7609
|
-
rawModeEnabled = true;
|
|
7610
|
-
}
|
|
7611
|
-
process.stdin.resume();
|
|
8150
|
+
if (interactive) {
|
|
7612
8151
|
process.stdin.on("data", onKeys);
|
|
8152
|
+
process.stdin.resume();
|
|
7613
8153
|
}
|
|
8154
|
+
const connected = await connect2();
|
|
8155
|
+
stopConnecting();
|
|
8156
|
+
if (connected === "unavailable") return "unavailable";
|
|
8157
|
+
if (connected === "aborted") return abortReason ?? "interrupted";
|
|
8158
|
+
ws = connected;
|
|
8159
|
+
stopHerdrHint = hintHerdrAttachment(harness);
|
|
8160
|
+
const onClose = () => finishAttachment?.();
|
|
8161
|
+
if (ws.readyState === WebSocket.CLOSED) finishAttachment?.();
|
|
8162
|
+
else ws.addEventListener("close", onClose, { once: true });
|
|
7614
8163
|
ws.addEventListener("message", onTtyFrame);
|
|
8164
|
+
const size = terminalSize();
|
|
8165
|
+
if (size.rows > 1) sendResize({ cols: size.cols, rows: size.rows - 1 });
|
|
8166
|
+
setTimeout(() => sendResize(), 80);
|
|
8167
|
+
process.stdout.on("resize", onWinch);
|
|
7615
8168
|
await finished;
|
|
7616
8169
|
return detached ? "detached" : interrupted ? "interrupted" : sawEnd ? "ended" : "dropped";
|
|
7617
8170
|
} finally {
|
|
8171
|
+
stopConnecting();
|
|
7618
8172
|
for (const signal of signals) process.off(signal, onSignal);
|
|
7619
8173
|
process.stdin.off("data", onKeys);
|
|
7620
8174
|
process.stdout.off("resize", onWinch);
|
|
7621
|
-
ws
|
|
7622
|
-
ws.removeEventListener("close", onClose);
|
|
8175
|
+
ws?.removeEventListener("message", onTtyFrame);
|
|
7623
8176
|
if (rawModeEnabled) process.stdin.setRawMode(false);
|
|
7624
|
-
if (
|
|
8177
|
+
if (interactive && ws !== null && process.stdout.isTTY === true) {
|
|
7625
8178
|
process.stdout.write(
|
|
7626
8179
|
"\x1B[<u\x1B[=0;1u\x1B[?2004l\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?1049l\x1B[?25h"
|
|
7627
8180
|
// show the cursor
|
|
7628
8181
|
);
|
|
7629
8182
|
}
|
|
7630
8183
|
process.stdin.pause();
|
|
7631
|
-
if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING)
|
|
8184
|
+
if (ws !== null && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING))
|
|
8185
|
+
ws.close();
|
|
7632
8186
|
stopHerdrHint();
|
|
7633
8187
|
}
|
|
7634
8188
|
};
|
|
@@ -7656,11 +8210,18 @@ var stopAndExit = async (config, sessionId) => {
|
|
|
7656
8210
|
say6(`${amber2("could not stop")} \u2014 the session may still run \xB7 mend stop ${id8}`);
|
|
7657
8211
|
process.exit(1);
|
|
7658
8212
|
};
|
|
8213
|
+
var noAnswerLine = (config, id8) => `${amber2("no answer")} \u2014 ${config.url} did not open the terminal within ${spoken(ATTACH_CONNECT_TIMEOUT_MS)}; the session keeps running \xB7 try again: mend attach ${id8} \xB7 check the server: mend doctor`;
|
|
7659
8214
|
var finishAttach = async (config, sessionId, outcome, mode = "background") => {
|
|
7660
8215
|
const id8 = sessionId.slice(0, 8);
|
|
7661
8216
|
if (outcome === "unavailable") {
|
|
7662
8217
|
return fail6(`tty attach unavailable: could not connect to ${config.url}`);
|
|
7663
8218
|
}
|
|
8219
|
+
if (outcome === "no-answer") {
|
|
8220
|
+
say6("");
|
|
8221
|
+
say6(noAnswerLine(config, id8));
|
|
8222
|
+
if (mode === "foreground") return stopAndExit(config, sessionId);
|
|
8223
|
+
process.exit(1);
|
|
8224
|
+
}
|
|
7664
8225
|
if (outcome === "detached") {
|
|
7665
8226
|
say6("");
|
|
7666
8227
|
say6(`${amber2("detached")} \u2014 the session keeps running; reattach: mend attach ${id8}`);
|
|
@@ -7722,6 +8283,9 @@ var attachPicked = async (config, session, tunnels) => {
|
|
|
7722
8283
|
say6(
|
|
7723
8284
|
`${green6("\u2713")} attaching to ${sessionDisplayName(session)} \xB7 ${session.harness} ${dim6(session.id.slice(0, 8))}${detachHint()}`
|
|
7724
8285
|
);
|
|
8286
|
+
if (session.status === "starting") {
|
|
8287
|
+
await startAndFollow(config, session.id, "starting \u2014 attaching once the agent runs\u2026", null);
|
|
8288
|
+
}
|
|
7725
8289
|
await tunnels?.start(session.id);
|
|
7726
8290
|
say6("");
|
|
7727
8291
|
const outcome = await attachWithTunnels(
|
|
@@ -7734,9 +8298,11 @@ var attachPicked = async (config, session, tunnels) => {
|
|
|
7734
8298
|
const detail = await api(config, "GET", `/sessions/${session.id}`);
|
|
7735
8299
|
if (detail.currentAgent?.kind === "agent-protocol" && agentIsLive(detail.session, detail.currentAgent)) {
|
|
7736
8300
|
say6(`${amber2("taking over")} from the protocol session`);
|
|
7737
|
-
await
|
|
8301
|
+
await startAndFollow(
|
|
8302
|
+
config,
|
|
8303
|
+
session.id,
|
|
7738
8304
|
"reopening as a terminal \u2014 same conversation\u2026",
|
|
7739
|
-
|
|
8305
|
+
request(config, "POST", `/sessions/${session.id}/handoff`, { to: "pty" })
|
|
7740
8306
|
);
|
|
7741
8307
|
say6("");
|
|
7742
8308
|
await attachOrExit(config, session.id, session.harness, "background", tunnels);
|
|
@@ -7805,6 +8371,10 @@ var stopSessions = async (config, targets, summarise) => {
|
|
|
7805
8371
|
if (hold !== null) {
|
|
7806
8372
|
say6(`${amber2(" " + hold)} \xB7 mend stop --services ${session.id.slice(0, 8)}`);
|
|
7807
8373
|
}
|
|
8374
|
+
const capture = after === null ? null : captureLineOf(after.session);
|
|
8375
|
+
if (capture !== null) {
|
|
8376
|
+
say6(`${amber2(" " + capture)} \xB7 the workspace stops once nothing is pending`);
|
|
8377
|
+
}
|
|
7808
8378
|
say6(`${cobalt2(" review")} \xB7 ${config.url}/sessions/${session.id}`);
|
|
7809
8379
|
}
|
|
7810
8380
|
if (summarise) {
|
|
@@ -7888,6 +8458,10 @@ var shellCommand = async (config, args) => {
|
|
|
7888
8458
|
return fail6(`tty attach unavailable: could not connect to ${config.url}`);
|
|
7889
8459
|
}
|
|
7890
8460
|
say6("");
|
|
8461
|
+
if (outcome === "no-answer") {
|
|
8462
|
+
say6(noAnswerLine(config, session.id.slice(0, 8)));
|
|
8463
|
+
process.exit(1);
|
|
8464
|
+
}
|
|
7891
8465
|
if (outcome === "detached" || outcome === "interrupted" || outcome === "dropped") {
|
|
7892
8466
|
say6(`${amber2("detached")} \u2014 the shell keeps running and holds the workspace open`);
|
|
7893
8467
|
process.exit(0);
|
|
@@ -8192,13 +8766,13 @@ var serviceRestart = async (config, args) => {
|
|
|
8192
8766
|
};
|
|
8193
8767
|
var serviceInit = async (args) => {
|
|
8194
8768
|
const root = gitTopLevel(process.cwd()) ?? process.cwd();
|
|
8195
|
-
const target =
|
|
8196
|
-
if (
|
|
8769
|
+
const target = path16.join(root, "mend.toml");
|
|
8770
|
+
if (fs15.existsSync(target)) {
|
|
8197
8771
|
return fail6(`${target} already exists \u2014 edit it directly (init never merges)`);
|
|
8198
8772
|
}
|
|
8199
|
-
const rootFiles =
|
|
8773
|
+
const rootFiles = fs15.readdirSync(root);
|
|
8200
8774
|
const proposals = [];
|
|
8201
|
-
const readRoot = (name) =>
|
|
8775
|
+
const readRoot = (name) => fs15.readFileSync(path16.join(root, name), "utf8");
|
|
8202
8776
|
if (rootFiles.includes("package.json")) {
|
|
8203
8777
|
proposals.push(...proposeFromPackageJson(readRoot("package.json"), rootFiles));
|
|
8204
8778
|
}
|
|
@@ -8208,13 +8782,13 @@ var serviceInit = async (args) => {
|
|
|
8208
8782
|
);
|
|
8209
8783
|
for (const glob of globs) {
|
|
8210
8784
|
const parent = glob.replace(/\/?\*+$/, "");
|
|
8211
|
-
const dirs = glob.endsWith("*") ?
|
|
8785
|
+
const dirs = glob.endsWith("*") ? fs15.existsSync(path16.join(root, parent)) ? fs15.readdirSync(path16.join(root, parent)).map((dir) => path16.join(parent, dir)) : [] : [glob];
|
|
8212
8786
|
for (const dir of dirs) {
|
|
8213
|
-
const manifest =
|
|
8214
|
-
if (!
|
|
8787
|
+
const manifest = path16.join(root, dir, "package.json");
|
|
8788
|
+
if (!fs15.existsSync(manifest)) continue;
|
|
8215
8789
|
for (const proposal of proposeFromWorkspacePackage(
|
|
8216
|
-
|
|
8217
|
-
|
|
8790
|
+
path16.basename(dir),
|
|
8791
|
+
fs15.readFileSync(manifest, "utf8"),
|
|
8218
8792
|
rootFiles
|
|
8219
8793
|
)) {
|
|
8220
8794
|
if (!proposals.some((existing) => existing.name === proposal.name)) {
|
|
@@ -8258,7 +8832,7 @@ var serviceInit = async (args) => {
|
|
|
8258
8832
|
return;
|
|
8259
8833
|
}
|
|
8260
8834
|
}
|
|
8261
|
-
|
|
8835
|
+
fs15.writeFileSync(target, toml);
|
|
8262
8836
|
say6(`${green6("\u2713")} wrote ${target} \u2014 commit it, then: mend service run ${proposals[0]?.name}`);
|
|
8263
8837
|
};
|
|
8264
8838
|
var tunnelServices = async (config, services, portOverride) => {
|
|
@@ -8362,23 +8936,23 @@ var serviceCommand = async (config, args) => {
|
|
|
8362
8936
|
}
|
|
8363
8937
|
};
|
|
8364
8938
|
var isProvider = (value) => value === "claude" || value === "codex" || value === "github";
|
|
8365
|
-
var readIfExists = (file) =>
|
|
8939
|
+
var readIfExists = (file) => fs15.existsSync(file) ? fs15.readFileSync(file, "utf8") : null;
|
|
8366
8940
|
var localCredential = (provider) => {
|
|
8367
|
-
const home =
|
|
8941
|
+
const home = os10.homedir();
|
|
8368
8942
|
switch (provider) {
|
|
8369
8943
|
case "codex":
|
|
8370
8944
|
return readIfExists(
|
|
8371
|
-
|
|
8945
|
+
path16.join(process.env["CODEX_HOME"] ?? path16.join(home, ".codex"), "auth.json")
|
|
8372
8946
|
);
|
|
8373
8947
|
case "claude":
|
|
8374
8948
|
return readIfExists(
|
|
8375
|
-
|
|
8376
|
-
process.env["CLAUDE_CONFIG_DIR"] ??
|
|
8949
|
+
path16.join(
|
|
8950
|
+
process.env["CLAUDE_CONFIG_DIR"] ?? path16.join(home, ".claude"),
|
|
8377
8951
|
".credentials.json"
|
|
8378
8952
|
)
|
|
8379
8953
|
);
|
|
8380
8954
|
case "github": {
|
|
8381
|
-
const result =
|
|
8955
|
+
const result = spawnSync5("gh", ["auth", "token"], { encoding: "utf8" });
|
|
8382
8956
|
const token = result.status === 0 ? result.stdout.trim() : "";
|
|
8383
8957
|
return token === "" ? null : token;
|
|
8384
8958
|
}
|
|
@@ -8423,7 +8997,7 @@ var claudeGrantSecret = () => {
|
|
|
8423
8997
|
return read.kind === "missing" ? null : read.secret;
|
|
8424
8998
|
};
|
|
8425
8999
|
var doctorBundle = (config, args) => doctorBundleCommand(args, {
|
|
8426
|
-
defaultDir:
|
|
9000
|
+
defaultDir: path16.join(mendCliHome(), "bundles"),
|
|
8427
9001
|
now: () => /* @__PURE__ */ new Date(),
|
|
8428
9002
|
say: say6,
|
|
8429
9003
|
warn: (line) => process.stderr.write(`${line}
|
|
@@ -8487,7 +9061,7 @@ var claudeGrant = async () => {
|
|
|
8487
9061
|
return null;
|
|
8488
9062
|
}
|
|
8489
9063
|
}
|
|
8490
|
-
if (status.configDirectory !== null &&
|
|
9064
|
+
if (status.configDirectory !== null && path16.resolve(status.configDirectory) !== path16.resolve(dir)) {
|
|
8491
9065
|
fail6(
|
|
8492
9066
|
`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
9067
|
);
|
|
@@ -8550,7 +9124,7 @@ var connectCommand = async (config, args) => {
|
|
|
8550
9124
|
}
|
|
8551
9125
|
let secret;
|
|
8552
9126
|
if (flags.includes("--from-stdin")) {
|
|
8553
|
-
secret =
|
|
9127
|
+
secret = fs15.readFileSync(0, "utf8").trim();
|
|
8554
9128
|
if (secret === "") return fail6("nothing on stdin");
|
|
8555
9129
|
} else if (provider === "claude" && !flags.includes("--use-my-login")) {
|
|
8556
9130
|
secret = await claudeGrant();
|
|
@@ -8596,14 +9170,14 @@ var takeFlagValue4 = (args, flag) => {
|
|
|
8596
9170
|
return at !== -1 && args[at + 1] !== void 0 ? String(args[at + 1]) : null;
|
|
8597
9171
|
};
|
|
8598
9172
|
var saveCliConfig = (next) => {
|
|
8599
|
-
|
|
8600
|
-
|
|
9173
|
+
fs15.mkdirSync(path16.dirname(CONFIG_PATH), { recursive: true, mode: 448 });
|
|
9174
|
+
fs15.writeFileSync(
|
|
8601
9175
|
CONFIG_PATH,
|
|
8602
9176
|
`${JSON.stringify({ url: next.url, token: next.token, deviceId: next.deviceId }, null, 2)}
|
|
8603
9177
|
`,
|
|
8604
9178
|
{ mode: 384 }
|
|
8605
9179
|
);
|
|
8606
|
-
|
|
9180
|
+
fs15.chmodSync(CONFIG_PATH, 384);
|
|
8607
9181
|
};
|
|
8608
9182
|
var login = async (config, args) => {
|
|
8609
9183
|
await loginCommand(args, {
|
|
@@ -8616,7 +9190,7 @@ var login = async (config, args) => {
|
|
|
8616
9190
|
});
|
|
8617
9191
|
};
|
|
8618
9192
|
var logout = async (config) => {
|
|
8619
|
-
if (!
|
|
9193
|
+
if (!fs15.existsSync(CONFIG_PATH) && config.token === null) {
|
|
8620
9194
|
say6(dim6("nothing saved \u2014 already signed out"));
|
|
8621
9195
|
return;
|
|
8622
9196
|
}
|
|
@@ -8669,8 +9243,8 @@ var uninstallCommand = async (config, args) => {
|
|
|
8669
9243
|
const runtime = {
|
|
8670
9244
|
server,
|
|
8671
9245
|
cliHome: mendCliHome(),
|
|
8672
|
-
sshConfigFile:
|
|
8673
|
-
signedIn: config.token === null || !
|
|
9246
|
+
sshConfigFile: path16.join(os10.homedir(), ".ssh", "config"),
|
|
9247
|
+
signedIn: config.token === null || !fs15.existsSync(CONFIG_PATH) ? null : { url: config.url, deviceId: config.deviceId },
|
|
8674
9248
|
revokeDevice: async () => {
|
|
8675
9249
|
if (config.deviceId === null) return "no device id saved";
|
|
8676
9250
|
try {
|
|
@@ -8734,7 +9308,7 @@ var keysShare = async (config) => {
|
|
|
8734
9308
|
return fail6("SSH_AUTH_SOCK is not set \u2014 start (or plug in) your ssh-agent first");
|
|
8735
9309
|
}
|
|
8736
9310
|
await shareAgent({
|
|
8737
|
-
url: () => socketUrl(config, "keys-bridge", { host:
|
|
9311
|
+
url: () => socketUrl(config, "keys-bridge", { host: os10.hostname() }),
|
|
8738
9312
|
agentSock,
|
|
8739
9313
|
signal: new AbortController().signal,
|
|
8740
9314
|
onEvent: (event) => {
|
|
@@ -8864,7 +9438,7 @@ ${usageOf("dotfiles repo")}`);
|
|
|
8864
9438
|
var dotfilesSync = async (config, args) => {
|
|
8865
9439
|
const all = args.includes("--all");
|
|
8866
9440
|
const requestedPaths = args.filter((arg) => !arg.startsWith("--"));
|
|
8867
|
-
const home =
|
|
9441
|
+
const home = os10.homedir();
|
|
8868
9442
|
if (!all && requestedPaths.length === 0) {
|
|
8869
9443
|
const found = scanDotfileCandidates(home);
|
|
8870
9444
|
if (found.length === 0) {
|
|
@@ -8891,13 +9465,13 @@ var dotfilesSync = async (config, args) => {
|
|
|
8891
9465
|
if ("error" in read) return fail6(read.error);
|
|
8892
9466
|
const result = await api(config, "POST", "/dotfiles/snapshot", {
|
|
8893
9467
|
files: read.files,
|
|
8894
|
-
source:
|
|
9468
|
+
source: os10.hostname(),
|
|
8895
9469
|
merge: false
|
|
8896
9470
|
});
|
|
8897
9471
|
const snapshot = result.snapshot;
|
|
8898
9472
|
if (snapshot === null) return fail6("the server accepted the sync but reports no snapshot");
|
|
8899
9473
|
say6(
|
|
8900
|
-
`${green6("synced")} ${snapshot.files.length} file${snapshot.files.length === 1 ? "" : "s"} from ${
|
|
9474
|
+
`${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
9475
|
);
|
|
8902
9476
|
};
|
|
8903
9477
|
var envLoad = async (config, args) => {
|
|
@@ -8911,10 +9485,10 @@ var envLoad = async (config, args) => {
|
|
|
8911
9485
|
const positional = args.filter(
|
|
8912
9486
|
(arg, i) => !arg.startsWith("--") && args[i - 1] !== "--project" && !consumed.has(i)
|
|
8913
9487
|
);
|
|
8914
|
-
const file =
|
|
9488
|
+
const file = path16.resolve(positional[0] ?? ".env");
|
|
8915
9489
|
let contents;
|
|
8916
9490
|
try {
|
|
8917
|
-
contents =
|
|
9491
|
+
contents = fs15.readFileSync(file, "utf8");
|
|
8918
9492
|
} catch {
|
|
8919
9493
|
return fail6(`cannot read ${file} \u2014 pass a path: mend env load path/to/.env`);
|
|
8920
9494
|
}
|
|
@@ -8930,10 +9504,10 @@ var envLoad = async (config, args) => {
|
|
|
8930
9504
|
{ contents, allSecret, secretNames }
|
|
8931
9505
|
);
|
|
8932
9506
|
if (report.loaded.length === 0 && report.rejected.length === 0 && report.malformedLines.length === 0) {
|
|
8933
|
-
say6(dim6(`${
|
|
9507
|
+
say6(dim6(`${path16.basename(file)} has no variables`));
|
|
8934
9508
|
return;
|
|
8935
9509
|
}
|
|
8936
|
-
say6(`${green6("\u2713")} loaded ${
|
|
9510
|
+
say6(`${green6("\u2713")} loaded ${path16.basename(file)} into ${project2.name}`);
|
|
8937
9511
|
for (const line of formatLoadReport(report, { dim: dim6, warn: amber2 })) say6(line);
|
|
8938
9512
|
const plaintextUrls = report.loaded.filter(
|
|
8939
9513
|
(entry) => entry.lane === "configuration" && /_(URL|URI|DSN)$/.test(entry.name)
|
|
@@ -9124,7 +9698,7 @@ var skillsList = async (config, args) => {
|
|
|
9124
9698
|
};
|
|
9125
9699
|
var skillsPush = async (config, args) => {
|
|
9126
9700
|
const dirFlag = takeFlagValue4(args, "--dir");
|
|
9127
|
-
const root = dirFlag === null ?
|
|
9701
|
+
const root = dirFlag === null ? path16.join(os10.homedir(), DEFAULT_SKILLS_DIR) : path16.resolve(dirFlag);
|
|
9128
9702
|
const scanned = scanSkillLibrary(root);
|
|
9129
9703
|
if ("error" in scanned) return fail6(scanned.error);
|
|
9130
9704
|
for (const note of scanned.notes) say6(dim6(` ${note.skill}: ${note.message}`));
|
|
@@ -9275,9 +9849,11 @@ var completionsCommand = (args) => {
|
|
|
9275
9849
|
}
|
|
9276
9850
|
};
|
|
9277
9851
|
var supervisedRun = async (config, session, argv) => {
|
|
9278
|
-
const launched = await
|
|
9852
|
+
const launched = await startAndFollow(
|
|
9853
|
+
config,
|
|
9854
|
+
session.id,
|
|
9279
9855
|
"provisioning workspace \u2014 a first launch builds the harness image (can take minutes)\u2026",
|
|
9280
|
-
|
|
9856
|
+
request(config, "POST", `/sessions/${session.id}/launch`, { argv })
|
|
9281
9857
|
);
|
|
9282
9858
|
say6(
|
|
9283
9859
|
`${green6("\u2713 recording")} \xB7 run ${dim6(launched.id.slice(0, 8))} \xB7 workspace mounts the worktree`
|
|
@@ -9413,9 +9989,13 @@ var resumeCommand = async (config, args) => {
|
|
|
9413
9989
|
say6(`${cobalt2(" watch")} \xB7 ${config.url}/sessions/${match.id}`);
|
|
9414
9990
|
const priorAgent = detail.annotations.find((annotation) => annotation.sessionId === match.id)?.currentAgent ?? null;
|
|
9415
9991
|
const protocolPrior = priorAgent?.kind === "agent-protocol" && withHarness === null;
|
|
9416
|
-
await
|
|
9992
|
+
await startAndFollow(
|
|
9993
|
+
config,
|
|
9994
|
+
match.id,
|
|
9417
9995
|
protocolPrior ? "reopening as a terminal \u2014 same conversation\u2026" : "resuming \u2014 a fresh workspace restores the saved session state\u2026",
|
|
9418
|
-
protocolPrior ?
|
|
9996
|
+
protocolPrior ? request(config, "POST", `/sessions/${match.id}/handoff`, { to: "pty" }) : request(config, "POST", `/sessions/${match.id}/resume`, {
|
|
9997
|
+
harness: withHarness
|
|
9998
|
+
})
|
|
9419
9999
|
);
|
|
9420
10000
|
say6(`${green6("\u2713 recording")} \xB7 same worktree, conversation restored${detachHint()}`);
|
|
9421
10001
|
say6("");
|
|
@@ -9423,17 +10003,18 @@ var resumeCommand = async (config, args) => {
|
|
|
9423
10003
|
exitAfterSessionEnd(config, match.id);
|
|
9424
10004
|
};
|
|
9425
10005
|
var resumeForRejoin = async (config, sessionId, label) => {
|
|
9426
|
-
|
|
9427
|
-
|
|
9428
|
-
|
|
9429
|
-
|
|
9430
|
-
)
|
|
9431
|
-
|
|
9432
|
-
|
|
10006
|
+
const outcome = await followStarting(
|
|
10007
|
+
config,
|
|
10008
|
+
sessionId,
|
|
10009
|
+
label,
|
|
10010
|
+
request(config, "POST", `/sessions/${sessionId}/resume`, { harness: null })
|
|
10011
|
+
);
|
|
10012
|
+
if (outcome.kind === "refused") {
|
|
9433
10013
|
const refreshed = await api(config, "GET", `/sessions/${sessionId}`);
|
|
9434
10014
|
if (agentIsLive(refreshed.session, refreshed.currentAgent)) return false;
|
|
9435
|
-
return fail6(error instanceof Error ? error.message : String(error));
|
|
9436
10015
|
}
|
|
10016
|
+
startedOrExit(config, sessionId, outcome);
|
|
10017
|
+
return true;
|
|
9437
10018
|
};
|
|
9438
10019
|
var rejoinCommand = async (config, args) => {
|
|
9439
10020
|
const harnessFlag = args.indexOf("--harness");
|
|
@@ -9464,9 +10045,11 @@ var rejoinCommand = async (config, args) => {
|
|
|
9464
10045
|
let restored = false;
|
|
9465
10046
|
const currentAgent = detail.annotations.find((annotation) => annotation.sessionId === session.id)?.currentAgent ?? null;
|
|
9466
10047
|
if (currentAgent?.kind === "agent-protocol") {
|
|
9467
|
-
await
|
|
10048
|
+
await startAndFollow(
|
|
10049
|
+
config,
|
|
10050
|
+
session.id,
|
|
9468
10051
|
alreadyLive ? "taking over from the protocol session \u2014 same conversation\u2026" : "reopening as a terminal \u2014 same conversation\u2026",
|
|
9469
|
-
|
|
10052
|
+
request(config, "POST", `/sessions/${session.id}/handoff`, { to: "pty" })
|
|
9470
10053
|
);
|
|
9471
10054
|
restored = true;
|
|
9472
10055
|
} else if (!alreadyLive) {
|
|
@@ -9549,7 +10132,10 @@ var refreshCommand = async (config, args) => {
|
|
|
9549
10132
|
}
|
|
9550
10133
|
if (branches.length > 12) say6(dim6(` \u2026 ${branches.length - 12} more`));
|
|
9551
10134
|
};
|
|
9552
|
-
var rowHold = (row) =>
|
|
10135
|
+
var rowHold = (row) => (
|
|
10136
|
+
// A workspace a stop is still saving (docs/adr/0002) reads what is left, first.
|
|
10137
|
+
captureLineOf(row.session) ?? (row.annotation === void 0 ? null : servicesHoldOf(row.session, row.annotation.currentAgent, row.annotation.liveServices ?? 0))
|
|
10138
|
+
);
|
|
9553
10139
|
var printSessionRow = (row) => {
|
|
9554
10140
|
const { session, annotation } = row;
|
|
9555
10141
|
const live = ACTIVE_STATUSES.has(session.status);
|
|
@@ -9647,6 +10233,14 @@ var sessionsCommand = async (config, args) => {
|
|
|
9647
10233
|
openComments: annotation.openComments,
|
|
9648
10234
|
totalComments: annotation.totalComments,
|
|
9649
10235
|
pendingFollowUp: annotation.pendingFollowUp
|
|
10236
|
+
},
|
|
10237
|
+
capture: session.capturePending === void 0 ? null : {
|
|
10238
|
+
pending: session.capturePending ?? null,
|
|
10239
|
+
pendingBytes: session.capturePendingBytes ?? null,
|
|
10240
|
+
refused: session.captureRefused ?? null,
|
|
10241
|
+
drain: session.captureDrain ?? null,
|
|
10242
|
+
notSaved: (session.captureNotSavedAt ?? null) !== null,
|
|
10243
|
+
line: captureLineOf(session)
|
|
9650
10244
|
}
|
|
9651
10245
|
}))
|
|
9652
10246
|
};
|
|
@@ -9779,14 +10373,14 @@ var dashboard = async (config, options = {}) => {
|
|
|
9779
10373
|
`the dashboard needs Node >= 26 (node:ffi) \u2014 this is ${process.version}; every other command still works`
|
|
9780
10374
|
);
|
|
9781
10375
|
}
|
|
9782
|
-
const rerun =
|
|
10376
|
+
const rerun = spawnSync5(
|
|
9783
10377
|
process.execPath,
|
|
9784
10378
|
["--experimental-ffi", "--disable-warning=ExperimentalWarning", ...process.argv.slice(1)],
|
|
9785
10379
|
{ stdio: "inherit" }
|
|
9786
10380
|
);
|
|
9787
10381
|
process.exit(rerun.status ?? 0);
|
|
9788
10382
|
}
|
|
9789
|
-
const { runDashboard } = await import("./dashboard-
|
|
10383
|
+
const { runDashboard } = await import("./dashboard-DWXXYFPE.js");
|
|
9790
10384
|
const agentShare = await startShareIfBridge(config);
|
|
9791
10385
|
const tunnels = options.noTunnel === true || serverIsLocal(config) ? null : createServiceTunnels({
|
|
9792
10386
|
listServices: () => fetchServices(config),
|
|
@@ -9820,7 +10414,7 @@ var startShareIfBridge = async (config) => {
|
|
|
9820
10414
|
}
|
|
9821
10415
|
if (access.mode !== "bridge") return null;
|
|
9822
10416
|
return startAgentShare({
|
|
9823
|
-
url: () => socketUrl(config, "keys-bridge", { host:
|
|
10417
|
+
url: () => socketUrl(config, "keys-bridge", { host: os10.hostname() }),
|
|
9824
10418
|
agentSock
|
|
9825
10419
|
});
|
|
9826
10420
|
};
|
|
@@ -9860,10 +10454,10 @@ var manCommand = (words) => {
|
|
|
9860
10454
|
}
|
|
9861
10455
|
const version = cliVersion();
|
|
9862
10456
|
const page = doc === null ? renderManIndex(version) : renderManPage(doc, version);
|
|
9863
|
-
const file =
|
|
9864
|
-
|
|
9865
|
-
const result =
|
|
9866
|
-
|
|
10457
|
+
const file = path16.join(os10.tmpdir(), `mend-man-${process.pid}-${manFileName(doc)}`);
|
|
10458
|
+
fs15.writeFileSync(file, page);
|
|
10459
|
+
const result = spawnSync5("man", ["-l", file], { stdio: "inherit" });
|
|
10460
|
+
fs15.rmSync(file, { force: true });
|
|
9867
10461
|
if (result.error !== void 0 || result.status !== 0) {
|
|
9868
10462
|
say6(doc === null ? renderIndex() : renderCommand(doc));
|
|
9869
10463
|
}
|