@sealant/mend 0.34.5 → 0.35.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-5DORQXQT.js → chunk-SMH65F6Z.js} +25 -1
- package/dist/{dashboard-KZ4XGZ6W.js → dashboard-MGR4YUFD.js} +1 -1
- package/dist/main.js +418 -316
- 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 +8 -4
- package/man/mend-continue.1 +1 -1
- package/man/mend-doctor.1 +1 -1
- 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 +5 -4
package/dist/main.js
CHANGED
|
@@ -34,15 +34,15 @@ import {
|
|
|
34
34
|
sessionDisplayName,
|
|
35
35
|
spoken,
|
|
36
36
|
trackBracketedPaste
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-SMH65F6Z.js";
|
|
38
38
|
|
|
39
39
|
// src/main.ts
|
|
40
|
-
import { spawn as spawn3, spawnSync as
|
|
41
|
-
import * as
|
|
40
|
+
import { spawn as spawn3, spawnSync as spawnSync6 } from "node:child_process";
|
|
41
|
+
import * as fs17 from "node:fs";
|
|
42
42
|
import { createRequire } from "node:module";
|
|
43
43
|
import * as net4 from "node:net";
|
|
44
|
-
import * as
|
|
45
|
-
import * as
|
|
44
|
+
import * as os11 from "node:os";
|
|
45
|
+
import * as path18 from "node:path";
|
|
46
46
|
|
|
47
47
|
// src/agent-share.ts
|
|
48
48
|
import * as net from "node:net";
|
|
@@ -223,6 +223,15 @@ import * as fs from "node:fs";
|
|
|
223
223
|
import * as os from "node:os";
|
|
224
224
|
import * as path from "node:path";
|
|
225
225
|
var claudeGrantDir = (home) => path.join(home, "claude-grant");
|
|
226
|
+
var forgetGrant = (configDir, platform = process.platform) => {
|
|
227
|
+
fs.rmSync(configDir, { recursive: true, force: true });
|
|
228
|
+
if (platform !== "darwin") return;
|
|
229
|
+
spawnSync(
|
|
230
|
+
"security",
|
|
231
|
+
["delete-generic-password", "-a", os.userInfo().username, "-s", keychainService(configDir)],
|
|
232
|
+
{ stdio: "ignore", timeout: 6e4 }
|
|
233
|
+
);
|
|
234
|
+
};
|
|
226
235
|
var personalClaudeDir = (environment = process.env) => {
|
|
227
236
|
const configured = environment["CLAUDE_CONFIG_DIR"];
|
|
228
237
|
return configured === void 0 || configured === "" ? path.join(os.homedir(), ".claude") : configured;
|
|
@@ -342,15 +351,94 @@ var readClipboardImage = async (platform = process.platform, env = process.env)
|
|
|
342
351
|
return null;
|
|
343
352
|
};
|
|
344
353
|
|
|
345
|
-
// src/
|
|
354
|
+
// src/codex-grant.ts
|
|
355
|
+
import { spawnSync as spawnSync2 } from "node:child_process";
|
|
346
356
|
import * as fs3 from "node:fs";
|
|
347
357
|
import * as os2 from "node:os";
|
|
348
358
|
import * as path3 from "node:path";
|
|
349
359
|
|
|
350
|
-
// src/
|
|
351
|
-
import { spawn } from "node:child_process";
|
|
360
|
+
// src/login-dir.ts
|
|
352
361
|
import * as fs2 from "node:fs";
|
|
353
362
|
import * as path2 from "node:path";
|
|
363
|
+
var throwawayLoginDir = (parent, prefix) => {
|
|
364
|
+
fs2.mkdirSync(parent, { recursive: true, mode: 448 });
|
|
365
|
+
const dir = fs2.mkdtempSync(path2.join(parent, prefix));
|
|
366
|
+
fs2.chmodSync(dir, 448);
|
|
367
|
+
return dir;
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
// src/codex-grant.ts
|
|
371
|
+
var codexCli = (environment = process.env) => ({
|
|
372
|
+
bin: environment["MEND_CODEX_BIN"] ?? "codex",
|
|
373
|
+
env: environment
|
|
374
|
+
});
|
|
375
|
+
var personalCodexHome = (environment = process.env) => {
|
|
376
|
+
const configured = environment["CODEX_HOME"];
|
|
377
|
+
return configured === void 0 || configured === "" ? path3.join(os2.homedir(), ".codex") : configured;
|
|
378
|
+
};
|
|
379
|
+
var runCodexDeviceLogin = (cli, codexHome) => {
|
|
380
|
+
const result = spawnSync2(cli.bin, ["login", "--device-auth"], {
|
|
381
|
+
stdio: "inherit",
|
|
382
|
+
env: { ...cli.env, CODEX_HOME: codexHome }
|
|
383
|
+
});
|
|
384
|
+
return result.error === void 0 && result.status === 0;
|
|
385
|
+
};
|
|
386
|
+
var refreshTokenOf = (authJson) => {
|
|
387
|
+
try {
|
|
388
|
+
const parsed = JSON.parse(authJson);
|
|
389
|
+
if (typeof parsed !== "object" || parsed === null) return null;
|
|
390
|
+
const tokens = parsed.tokens;
|
|
391
|
+
if (typeof tokens !== "object" || tokens === null) return null;
|
|
392
|
+
const refreshToken = tokens.refresh_token;
|
|
393
|
+
return typeof refreshToken === "string" && refreshToken !== "" ? refreshToken : null;
|
|
394
|
+
} catch {
|
|
395
|
+
return null;
|
|
396
|
+
}
|
|
397
|
+
};
|
|
398
|
+
var sameCodexLogin = (a, b) => {
|
|
399
|
+
const first = refreshTokenOf(a);
|
|
400
|
+
return first !== null && first === refreshTokenOf(b);
|
|
401
|
+
};
|
|
402
|
+
var codexGrant = (input) => {
|
|
403
|
+
const home = throwawayLoginDir(input.parent, "codex-login-");
|
|
404
|
+
try {
|
|
405
|
+
input.say(" Mend needs its own Codex login; it is sent to your server and not kept here");
|
|
406
|
+
input.say(" your own Codex login stays as it is");
|
|
407
|
+
if (!runCodexDeviceLogin(input.cli, home)) {
|
|
408
|
+
return { kind: "failed", reason: "codex: the login did not complete" };
|
|
409
|
+
}
|
|
410
|
+
const file = path3.join(home, "auth.json");
|
|
411
|
+
if (!fs3.existsSync(file)) {
|
|
412
|
+
return { kind: "failed", reason: `codex: logged in, but no auth.json appeared in ${home}` };
|
|
413
|
+
}
|
|
414
|
+
const secret = fs3.readFileSync(file, "utf8").trim();
|
|
415
|
+
if (refreshTokenOf(secret) === null) {
|
|
416
|
+
return {
|
|
417
|
+
kind: "failed",
|
|
418
|
+
reason: "codex: the login wrote no ChatGPT session (an API key login has nothing to refresh)"
|
|
419
|
+
};
|
|
420
|
+
}
|
|
421
|
+
if (input.personalAuthJson !== null && sameCodexLogin(secret, input.personalAuthJson)) {
|
|
422
|
+
return {
|
|
423
|
+
kind: "failed",
|
|
424
|
+
reason: "codex: that is the same login this machine's Codex holds, so both sides would race on refresh \u2014 connect it deliberately with --use-my-login"
|
|
425
|
+
};
|
|
426
|
+
}
|
|
427
|
+
return { kind: "grant", secret };
|
|
428
|
+
} finally {
|
|
429
|
+
fs3.rmSync(home, { recursive: true, force: true });
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
// src/doctor-bundle-collectors.ts
|
|
434
|
+
import * as fs5 from "node:fs";
|
|
435
|
+
import * as os3 from "node:os";
|
|
436
|
+
import * as path5 from "node:path";
|
|
437
|
+
|
|
438
|
+
// src/server-runtime.ts
|
|
439
|
+
import { spawn } from "node:child_process";
|
|
440
|
+
import * as fs4 from "node:fs";
|
|
441
|
+
import * as path4 from "node:path";
|
|
354
442
|
var serverProcessDeadlines = {
|
|
355
443
|
ordinary: 3e4,
|
|
356
444
|
pull: 10 * 6e4,
|
|
@@ -396,7 +484,7 @@ var runServerProcess = (command, args, parentEnvironment, options = {}) => {
|
|
|
396
484
|
return new Promise((resolve7) => {
|
|
397
485
|
let fd;
|
|
398
486
|
try {
|
|
399
|
-
if (options.stdoutFile !== void 0) fd =
|
|
487
|
+
if (options.stdoutFile !== void 0) fd = fs4.openSync(options.stdoutFile, "wx", 384);
|
|
400
488
|
const child = spawn(command, [...args], {
|
|
401
489
|
env,
|
|
402
490
|
detached: process.platform !== "win32",
|
|
@@ -447,12 +535,12 @@ var runServerProcess = (command, args, parentEnvironment, options = {}) => {
|
|
|
447
535
|
child.once("close", (status) => {
|
|
448
536
|
globalThis.clearTimeout(timer);
|
|
449
537
|
try {
|
|
450
|
-
if (fd !== void 0)
|
|
538
|
+
if (fd !== void 0) fs4.fsyncSync(fd);
|
|
451
539
|
} catch {
|
|
452
540
|
failure2 ??= "Could not durably write process output.";
|
|
453
541
|
} finally {
|
|
454
542
|
try {
|
|
455
|
-
if (fd !== void 0)
|
|
543
|
+
if (fd !== void 0) fs4.closeSync(fd);
|
|
456
544
|
} catch {
|
|
457
545
|
failure2 ??= "Could not close process output file.";
|
|
458
546
|
}
|
|
@@ -466,7 +554,7 @@ var runServerProcess = (command, args, parentEnvironment, options = {}) => {
|
|
|
466
554
|
});
|
|
467
555
|
} catch (cause) {
|
|
468
556
|
try {
|
|
469
|
-
if (fd !== void 0)
|
|
557
|
+
if (fd !== void 0) fs4.closeSync(fd);
|
|
470
558
|
} catch {
|
|
471
559
|
}
|
|
472
560
|
resolve7({
|
|
@@ -487,9 +575,9 @@ var serverComposeArgs = (installation, command) => [
|
|
|
487
575
|
"--project-directory",
|
|
488
576
|
installation.directory,
|
|
489
577
|
"--env-file",
|
|
490
|
-
|
|
578
|
+
path4.join(installation.directory, "server.env"),
|
|
491
579
|
"-f",
|
|
492
|
-
|
|
580
|
+
path4.join(installation.directory, "compose.yaml"),
|
|
493
581
|
...command
|
|
494
582
|
];
|
|
495
583
|
|
|
@@ -535,11 +623,11 @@ var mapLimit = async (items, limit, work) => {
|
|
|
535
623
|
return results;
|
|
536
624
|
};
|
|
537
625
|
var pathOf = (command, env = process.env) => {
|
|
538
|
-
for (const directory of (env["PATH"] ?? "").split(
|
|
626
|
+
for (const directory of (env["PATH"] ?? "").split(path5.delimiter)) {
|
|
539
627
|
if (directory === "") continue;
|
|
540
|
-
const candidate =
|
|
628
|
+
const candidate = path5.join(directory, command);
|
|
541
629
|
try {
|
|
542
|
-
|
|
630
|
+
fs5.accessSync(candidate, fs5.constants.X_OK);
|
|
543
631
|
return candidate;
|
|
544
632
|
} catch {
|
|
545
633
|
}
|
|
@@ -557,9 +645,9 @@ var cliCollector = (deps) => ({
|
|
|
557
645
|
node: process.versions.node,
|
|
558
646
|
platform: process.platform,
|
|
559
647
|
arch: process.arch,
|
|
560
|
-
osType:
|
|
561
|
-
osRelease:
|
|
562
|
-
hostname:
|
|
648
|
+
osType: os3.type(),
|
|
649
|
+
osRelease: os3.release(),
|
|
650
|
+
hostname: os3.hostname(),
|
|
563
651
|
term: deps.env["TERM"] ?? null,
|
|
564
652
|
shell: deps.env["SHELL"] ?? null,
|
|
565
653
|
lang: deps.env["LANG"] ?? null,
|
|
@@ -1029,8 +1117,8 @@ var bundleCollectors = (deps) => {
|
|
|
1029
1117
|
};
|
|
1030
1118
|
|
|
1031
1119
|
// src/doctor-bundle.ts
|
|
1032
|
-
import * as
|
|
1033
|
-
import * as
|
|
1120
|
+
import * as fs6 from "node:fs";
|
|
1121
|
+
import * as path6 from "node:path";
|
|
1034
1122
|
import { gzipSync } from "node:zlib";
|
|
1035
1123
|
|
|
1036
1124
|
// src/help.ts
|
|
@@ -1079,19 +1167,20 @@ var COMMANDS = [
|
|
|
1079
1167
|
summary: "send this machine's provider credential to the platform",
|
|
1080
1168
|
synopsis: ["<claude|codex|github> [--use-my-login] [--from-stdin] [--remove]"],
|
|
1081
1169
|
description: [
|
|
1082
|
-
"Sessions run on the platform, so the platform needs your provider login.
|
|
1083
|
-
"Claude
|
|
1170
|
+
"Sessions run on the platform, so the platform needs your provider login. It is stored under your own user and used only for your work. Nothing is shared with other users.",
|
|
1171
|
+
"Claude and Codex get a login of Mend's own. Both rotate their refresh token, so two copies of one login fight and the one that refreshes second is signed out, and Mend's server refreshes on a schedule. So this logs in through the provider's own flow and your own login stays as it is: Claude in the browser, Codex with a device code, both sent and not kept here. Run it again whenever Mend says a login needs reconnecting. GitHub sends gh's token."
|
|
1084
1172
|
],
|
|
1085
1173
|
options: [
|
|
1086
1174
|
{
|
|
1087
1175
|
flag: "--use-my-login",
|
|
1088
|
-
text: "claude: send the login this machine already uses; both sides then share
|
|
1176
|
+
text: "claude, codex: send the login this machine already uses; both sides then share it"
|
|
1089
1177
|
},
|
|
1090
1178
|
{ flag: "--from-stdin", text: "paste a credential instead of reading the provider's file" },
|
|
1091
1179
|
{ flag: "--remove", text: "disconnect the provider" }
|
|
1092
1180
|
],
|
|
1093
1181
|
examples: [
|
|
1094
1182
|
{ command: "mend connect claude", text: "a Claude login of Mend's own, through the browser" },
|
|
1183
|
+
{ command: "mend connect codex", text: "a Codex login of Mend's own, with a device code" },
|
|
1095
1184
|
{ command: "mend connect github --remove", text: "" }
|
|
1096
1185
|
],
|
|
1097
1186
|
see: ["accounts"]
|
|
@@ -2470,9 +2559,9 @@ var collectBundle = async (collectors) => {
|
|
|
2470
2559
|
};
|
|
2471
2560
|
var bundleFileName = (now) => `mend-bundle-${now.toISOString().replace(/\.\d{3}Z$/, "Z").replace(/:/g, "-")}.tgz`;
|
|
2472
2561
|
var writeBundle = (files, outPath, now) => {
|
|
2473
|
-
const directory =
|
|
2474
|
-
|
|
2475
|
-
const root =
|
|
2562
|
+
const directory = path6.dirname(outPath);
|
|
2563
|
+
fs6.mkdirSync(directory, { recursive: true, mode: 448 });
|
|
2564
|
+
const root = path6.basename(outPath).replace(/\.(?:tgz|tar\.gz)$/, "");
|
|
2476
2565
|
const entries = files.map((file) => ({
|
|
2477
2566
|
path: file.path,
|
|
2478
2567
|
content: Buffer.from(file.content, "utf8")
|
|
@@ -2481,8 +2570,8 @@ var writeBundle = (files, outPath, now) => {
|
|
|
2481
2570
|
entries.map((entry) => ({ path: `${root}/${entry.path}`, content: entry.content })),
|
|
2482
2571
|
now
|
|
2483
2572
|
);
|
|
2484
|
-
|
|
2485
|
-
|
|
2573
|
+
fs6.writeFileSync(outPath, bytes, { mode: 384 });
|
|
2574
|
+
fs6.chmodSync(outPath, 384);
|
|
2486
2575
|
return {
|
|
2487
2576
|
path: outPath,
|
|
2488
2577
|
bytes: bytes.length,
|
|
@@ -2543,7 +2632,7 @@ var doctorBundleCommand = async (args, deps) => {
|
|
|
2543
2632
|
return;
|
|
2544
2633
|
}
|
|
2545
2634
|
const now = deps.now();
|
|
2546
|
-
const outPath =
|
|
2635
|
+
const outPath = path6.resolve(parsed.args.out ?? path6.join(deps.defaultDir, bundleFileName(now)));
|
|
2547
2636
|
deps.warn("collecting \xB7 this can take a minute");
|
|
2548
2637
|
const files = await collectBundle(deps.collectors(parsed.args.tail));
|
|
2549
2638
|
const written = writeBundle(files, outPath, now);
|
|
@@ -2551,14 +2640,14 @@ var doctorBundleCommand = async (args, deps) => {
|
|
|
2551
2640
|
};
|
|
2552
2641
|
|
|
2553
2642
|
// src/doctor.ts
|
|
2554
|
-
import * as
|
|
2555
|
-
import * as
|
|
2643
|
+
import * as fs8 from "node:fs";
|
|
2644
|
+
import * as path8 from "node:path";
|
|
2556
2645
|
|
|
2557
2646
|
// src/docker-shutdown.ts
|
|
2558
|
-
import { spawnSync as
|
|
2559
|
-
import * as
|
|
2560
|
-
import * as
|
|
2561
|
-
import * as
|
|
2647
|
+
import { spawnSync as spawnSync3 } from "node:child_process";
|
|
2648
|
+
import * as fs7 from "node:fs";
|
|
2649
|
+
import * as os4 from "node:os";
|
|
2650
|
+
import * as path7 from "node:path";
|
|
2562
2651
|
var DOCKER_CAPTURE_STOP_GRACE_SECONDS = 3600;
|
|
2563
2652
|
var DOCKERD_DEFAULT_SHUTDOWN_TIMEOUT_SECONDS = 15;
|
|
2564
2653
|
var flagValue = (argv, name) => {
|
|
@@ -2583,12 +2672,12 @@ var daemonKindOf = (info) => {
|
|
|
2583
2672
|
var defaultConfigPath = (facts, daemon) => {
|
|
2584
2673
|
switch (daemon) {
|
|
2585
2674
|
case "desktop":
|
|
2586
|
-
return
|
|
2675
|
+
return path7.join(facts.home, ".docker", "daemon.json");
|
|
2587
2676
|
case "orbstack":
|
|
2588
|
-
return
|
|
2677
|
+
return path7.join(facts.home, ".orbstack", "config", "docker.json");
|
|
2589
2678
|
case "rootless":
|
|
2590
|
-
return
|
|
2591
|
-
facts.xdgConfigHome ??
|
|
2679
|
+
return path7.join(
|
|
2680
|
+
facts.xdgConfigHome ?? path7.join(facts.home, ".config"),
|
|
2592
2681
|
"docker",
|
|
2593
2682
|
"daemon.json"
|
|
2594
2683
|
);
|
|
@@ -2717,7 +2806,7 @@ var parseDockerInfo = (stdout) => {
|
|
|
2717
2806
|
};
|
|
2718
2807
|
var readHostFile = (file) => {
|
|
2719
2808
|
try {
|
|
2720
|
-
return { kind: "read", text:
|
|
2809
|
+
return { kind: "read", text: fs7.readFileSync(file, "utf8") };
|
|
2721
2810
|
} catch (cause) {
|
|
2722
2811
|
return cause instanceof Error && "code" in cause && cause.code === "ENOENT" ? { kind: "absent" } : { kind: "unreadable" };
|
|
2723
2812
|
}
|
|
@@ -2725,21 +2814,21 @@ var readHostFile = (file) => {
|
|
|
2725
2814
|
var hostProcView = {
|
|
2726
2815
|
list: (dir) => {
|
|
2727
2816
|
try {
|
|
2728
|
-
return
|
|
2817
|
+
return fs7.readdirSync(dir);
|
|
2729
2818
|
} catch {
|
|
2730
2819
|
return null;
|
|
2731
2820
|
}
|
|
2732
2821
|
},
|
|
2733
2822
|
read: (file) => {
|
|
2734
2823
|
try {
|
|
2735
|
-
return
|
|
2824
|
+
return fs7.readFileSync(file, "utf8");
|
|
2736
2825
|
} catch {
|
|
2737
2826
|
return null;
|
|
2738
2827
|
}
|
|
2739
2828
|
},
|
|
2740
2829
|
link: (file) => {
|
|
2741
2830
|
try {
|
|
2742
|
-
return
|
|
2831
|
+
return fs7.readlinkSync(file);
|
|
2743
2832
|
} catch {
|
|
2744
2833
|
return null;
|
|
2745
2834
|
}
|
|
@@ -2779,7 +2868,7 @@ var observeDockerd = (proc, host) => {
|
|
|
2779
2868
|
const cmdline = proc.read(`/proc/${entry}/cmdline`);
|
|
2780
2869
|
if (cmdline === null) continue;
|
|
2781
2870
|
const argv = cmdline.split("\0").filter((arg) => arg !== "");
|
|
2782
|
-
if (
|
|
2871
|
+
if (path7.basename(argv[0] ?? "") === "dockerd") daemons.push({ pid: entry, argv });
|
|
2783
2872
|
}
|
|
2784
2873
|
if (daemons.length === 0) return { kind: "none" };
|
|
2785
2874
|
const socket = unixSocketOf(host);
|
|
@@ -2839,7 +2928,7 @@ var observeDockerd = (proc, host) => {
|
|
|
2839
2928
|
var hostDockerEndpoint = () => {
|
|
2840
2929
|
const fromEnv = process.env["DOCKER_HOST"];
|
|
2841
2930
|
if (fromEnv !== void 0 && fromEnv.trim() !== "") return fromEnv.trim();
|
|
2842
|
-
const context =
|
|
2931
|
+
const context = spawnSync3(
|
|
2843
2932
|
"docker",
|
|
2844
2933
|
["context", "inspect", "--format", "{{.Endpoints.docker.Host}}"],
|
|
2845
2934
|
{
|
|
@@ -2865,11 +2954,11 @@ var hostDockerDaemonFacts = (infoStdout) => ({
|
|
|
2865
2954
|
info: infoStdout === null ? null : parseDockerInfo(infoStdout),
|
|
2866
2955
|
...dockerdFactsOf(observeDockerd(hostProcView, hostDockerEndpoint())),
|
|
2867
2956
|
readFile: readHostFile,
|
|
2868
|
-
home:
|
|
2957
|
+
home: os4.homedir(),
|
|
2869
2958
|
xdgConfigHome: process.env["XDG_CONFIG_HOME"] ?? null
|
|
2870
2959
|
});
|
|
2871
2960
|
var observeHostShutdownTimeout = () => {
|
|
2872
|
-
const info =
|
|
2961
|
+
const info = spawnSync3("docker", ["info", "--format", "{{json .}}"], {
|
|
2873
2962
|
encoding: "utf8",
|
|
2874
2963
|
timeout: 3e3,
|
|
2875
2964
|
stdio: ["ignore", "pipe", "ignore"]
|
|
@@ -3028,7 +3117,8 @@ var runChecks = async (config, probes) => {
|
|
|
3028
3117
|
} : {
|
|
3029
3118
|
label: provider,
|
|
3030
3119
|
state: "todo",
|
|
3031
|
-
|
|
3120
|
+
// A refused refresh ends the login (docs/adr/0008): say what happened, not the enum.
|
|
3121
|
+
detail: account.status === "invalid" ? "reconnect needed \xB7 the provider refused the login" : account.status,
|
|
3032
3122
|
fix: `mend connect ${provider}`
|
|
3033
3123
|
}
|
|
3034
3124
|
);
|
|
@@ -3098,10 +3188,10 @@ var runChecks = async (config, probes) => {
|
|
|
3098
3188
|
return checks;
|
|
3099
3189
|
};
|
|
3100
3190
|
var onPath = (command) => {
|
|
3101
|
-
for (const directory of (process.env["PATH"] ?? "").split(
|
|
3191
|
+
for (const directory of (process.env["PATH"] ?? "").split(path8.delimiter)) {
|
|
3102
3192
|
if (directory === "") continue;
|
|
3103
3193
|
try {
|
|
3104
|
-
|
|
3194
|
+
fs8.accessSync(path8.join(directory, command), fs8.constants.X_OK);
|
|
3105
3195
|
return true;
|
|
3106
3196
|
} catch {
|
|
3107
3197
|
}
|
|
@@ -3126,8 +3216,8 @@ var doctorCommand = async (config, localCredential2, claudeGrant2) => {
|
|
|
3126
3216
|
};
|
|
3127
3217
|
|
|
3128
3218
|
// src/dotfiles.ts
|
|
3129
|
-
import * as
|
|
3130
|
-
import * as
|
|
3219
|
+
import * as fs9 from "node:fs";
|
|
3220
|
+
import * as path9 from "node:path";
|
|
3131
3221
|
var DOTFILE_CANDIDATES = [
|
|
3132
3222
|
{
|
|
3133
3223
|
group: "shell",
|
|
@@ -3170,7 +3260,7 @@ var DOTFILE_CANDIDATES = [
|
|
|
3170
3260
|
var MAX_FILE_BYTES = 1024 * 1024;
|
|
3171
3261
|
var statFile = (home, relative4) => {
|
|
3172
3262
|
try {
|
|
3173
|
-
const stat =
|
|
3263
|
+
const stat = fs9.statSync(path9.join(home, relative4));
|
|
3174
3264
|
return stat.isFile() ? stat : null;
|
|
3175
3265
|
} catch {
|
|
3176
3266
|
return null;
|
|
@@ -3183,9 +3273,9 @@ var scanDotfileCandidates = (home) => DOTFILE_CANDIDATES.flatMap(
|
|
|
3183
3273
|
})
|
|
3184
3274
|
);
|
|
3185
3275
|
var homeRelativePath = (home, requested) => {
|
|
3186
|
-
const relative4 =
|
|
3187
|
-
const outside = relative4 === "" || relative4 === ".." || relative4.startsWith(`..${
|
|
3188
|
-
return outside ? null : relative4.split(
|
|
3276
|
+
const relative4 = path9.relative(home, path9.resolve(home, requested));
|
|
3277
|
+
const outside = relative4 === "" || relative4 === ".." || relative4.startsWith(`..${path9.sep}`) || path9.isAbsolute(relative4);
|
|
3278
|
+
return outside ? null : relative4.split(path9.sep).join("/");
|
|
3189
3279
|
};
|
|
3190
3280
|
var readSyncFiles = (home, paths_) => {
|
|
3191
3281
|
const files = [];
|
|
@@ -3205,7 +3295,7 @@ var readSyncFiles = (home, paths_) => {
|
|
|
3205
3295
|
}
|
|
3206
3296
|
files.push({
|
|
3207
3297
|
path: relative4,
|
|
3208
|
-
contentsBase64:
|
|
3298
|
+
contentsBase64: fs9.readFileSync(path9.join(home, relative4)).toString("base64"),
|
|
3209
3299
|
// eslint-disable-next-line no-bitwise -- permission bits are the point
|
|
3210
3300
|
mode: (stat.mode & 511).toString(8)
|
|
3211
3301
|
});
|
|
@@ -3303,11 +3393,18 @@ var parseGitAuthorArgs = (args) => {
|
|
|
3303
3393
|
};
|
|
3304
3394
|
var gitAuthorLine = (author) => `${author.name} <${author.email}> \xB7 ${author.source === "setting" ? "your setting" : "your account's name and email"}`;
|
|
3305
3395
|
|
|
3396
|
+
// src/http-client.ts
|
|
3397
|
+
import { Agent, setGlobalDispatcher } from "undici";
|
|
3398
|
+
var cliDispatcher = (connect2) => new Agent({ allowH2: false, ...connect2 === void 0 ? {} : { connect: connect2 } });
|
|
3399
|
+
var useHttp1 = () => {
|
|
3400
|
+
setGlobalDispatcher(cliDispatcher());
|
|
3401
|
+
};
|
|
3402
|
+
|
|
3306
3403
|
// src/landing.ts
|
|
3307
|
-
import { spawnSync as
|
|
3308
|
-
import * as
|
|
3309
|
-
import * as
|
|
3310
|
-
import * as
|
|
3404
|
+
import { spawnSync as spawnSync4 } from "node:child_process";
|
|
3405
|
+
import * as fs10 from "node:fs";
|
|
3406
|
+
import * as os5 from "node:os";
|
|
3407
|
+
import * as path10 from "node:path";
|
|
3311
3408
|
var paint = (code) => (text) => process.stdout.isTTY === true ? `[${code}m${text}[0m` : text;
|
|
3312
3409
|
var dim = paint("2");
|
|
3313
3410
|
var green = paint("32");
|
|
@@ -3522,7 +3619,7 @@ var landCommand = async (api2, args) => {
|
|
|
3522
3619
|
if (!landingSucceeded(report)) process.exitCode = 1;
|
|
3523
3620
|
};
|
|
3524
3621
|
var git = (cwd, args) => {
|
|
3525
|
-
const result =
|
|
3622
|
+
const result = spawnSync4("git", args, { cwd, encoding: "utf8" });
|
|
3526
3623
|
return {
|
|
3527
3624
|
status: result.status ?? 1,
|
|
3528
3625
|
stdout: result.stdout ?? "",
|
|
@@ -3545,9 +3642,9 @@ var gitRemotes = (cwd) => {
|
|
|
3545
3642
|
var remoteForOrigin = (remotes, originUrl, cwd) => {
|
|
3546
3643
|
if (originUrl === null) return null;
|
|
3547
3644
|
const origin = normalizeRemoteUrl(originUrl);
|
|
3548
|
-
const originPath = origin === null ?
|
|
3645
|
+
const originPath = origin === null ? path10.resolve(originUrl.replace(/^file:\/\//, "")) : null;
|
|
3549
3646
|
return remotes.find(
|
|
3550
|
-
(remote) => origin === null ?
|
|
3647
|
+
(remote) => origin === null ? path10.resolve(cwd, remote.url.replace(/^file:\/\//, "")) === originPath : normalizeRemoteUrl(remote.url) === origin
|
|
3551
3648
|
) ?? null;
|
|
3552
3649
|
};
|
|
3553
3650
|
var LOG_LINES = 10;
|
|
@@ -3570,10 +3667,10 @@ var fetchBundle = (cwd, bundle) => {
|
|
|
3570
3667
|
const before = git(cwd, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]);
|
|
3571
3668
|
const previous = before.status === 0 ? before.stdout.trim() : null;
|
|
3572
3669
|
if (previous !== bundle.tip) {
|
|
3573
|
-
const dir =
|
|
3670
|
+
const dir = fs10.mkdtempSync(path10.join(os5.tmpdir(), "mend-pull-"));
|
|
3574
3671
|
try {
|
|
3575
|
-
const file =
|
|
3576
|
-
|
|
3672
|
+
const file = path10.join(dir, "change.bundle");
|
|
3673
|
+
fs10.writeFileSync(file, bundle.bytes, { mode: 384 });
|
|
3577
3674
|
const fetched = git(cwd, [
|
|
3578
3675
|
"fetch",
|
|
3579
3676
|
"--no-tags",
|
|
@@ -3583,7 +3680,7 @@ var fetchBundle = (cwd, bundle) => {
|
|
|
3583
3680
|
]);
|
|
3584
3681
|
if (fetched.status !== 0) return refusedWith(gitWords(fetched));
|
|
3585
3682
|
} finally {
|
|
3586
|
-
|
|
3683
|
+
fs10.rmSync(dir, { recursive: true, force: true });
|
|
3587
3684
|
}
|
|
3588
3685
|
}
|
|
3589
3686
|
const after = git(cwd, ["rev-parse", "--verify", "--quiet", `${ref}^{commit}`]).stdout.trim();
|
|
@@ -3774,7 +3871,7 @@ var followStart = async (options) => {
|
|
|
3774
3871
|
|
|
3775
3872
|
// src/login.ts
|
|
3776
3873
|
import { spawn as spawn2 } from "node:child_process";
|
|
3777
|
-
import * as
|
|
3874
|
+
import * as os6 from "node:os";
|
|
3778
3875
|
|
|
3779
3876
|
// src/pair.ts
|
|
3780
3877
|
var paint2 = (code) => (text) => process.stdout.isTTY === true ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
@@ -3966,7 +4063,7 @@ var resolveServerUrl = async (args, deps) => {
|
|
|
3966
4063
|
};
|
|
3967
4064
|
var loginCommand = async (args, deps) => {
|
|
3968
4065
|
const base = await resolveServerUrl(args, deps);
|
|
3969
|
-
const started = await post(`${base}/api/cli/auth`, { name:
|
|
4066
|
+
const started = await post(`${base}/api/cli/auth`, { name: os6.hostname() });
|
|
3970
4067
|
if (started.status === 429) {
|
|
3971
4068
|
return fail3(
|
|
3972
4069
|
`the server asked for a pause \u2014 try again in ${retryAfterSecondsOf(started.json)}s`
|
|
@@ -4017,8 +4114,8 @@ var loginCommand = async (args, deps) => {
|
|
|
4017
4114
|
};
|
|
4018
4115
|
|
|
4019
4116
|
// src/organization.ts
|
|
4020
|
-
import * as
|
|
4021
|
-
import * as
|
|
4117
|
+
import * as fs11 from "node:fs";
|
|
4118
|
+
import * as path11 from "node:path";
|
|
4022
4119
|
var paint4 = (code) => (text) => process.stdout.isTTY === true ? `[${code}m${text}[0m` : text;
|
|
4023
4120
|
var dim4 = paint4("2");
|
|
4024
4121
|
var green4 = paint4("32");
|
|
@@ -4082,7 +4179,7 @@ var scanFolder = (dir) => {
|
|
|
4082
4179
|
const files = [];
|
|
4083
4180
|
const skipped = [];
|
|
4084
4181
|
const walk = (relative4) => {
|
|
4085
|
-
const entries =
|
|
4182
|
+
const entries = fs11.readdirSync(path11.join(dir, relative4), { withFileTypes: true }).toSorted((a, b) => a.name.localeCompare(b.name));
|
|
4086
4183
|
for (const entry of entries) {
|
|
4087
4184
|
const child = relative4 === "" ? entry.name : `${relative4}/${entry.name}`;
|
|
4088
4185
|
if (entry.isSymbolicLink()) {
|
|
@@ -4091,7 +4188,7 @@ var scanFolder = (dir) => {
|
|
|
4091
4188
|
if (SKIPPED_DIRECTORIES.has(entry.name)) skipped.push({ path: child, reason: "skipped" });
|
|
4092
4189
|
else walk(child);
|
|
4093
4190
|
} else if (entry.isFile()) {
|
|
4094
|
-
const size =
|
|
4191
|
+
const size = fs11.statSync(path11.join(dir, child)).size;
|
|
4095
4192
|
if (size > FOLDER_FILE_BYTES) skipped.push({ path: child, reason: "over 1 MiB" });
|
|
4096
4193
|
else files.push({ path: child, size });
|
|
4097
4194
|
}
|
|
@@ -4144,7 +4241,7 @@ var folderCommand = async (api2, args) => {
|
|
|
4144
4241
|
}
|
|
4145
4242
|
case "push": {
|
|
4146
4243
|
if (dir === void 0) return fail4("usage: mend folder push <name> <dir> [--replace]");
|
|
4147
|
-
if (!
|
|
4244
|
+
if (!fs11.existsSync(dir) || !fs11.statSync(dir).isDirectory()) {
|
|
4148
4245
|
return fail4(`${dir} is not a directory`);
|
|
4149
4246
|
}
|
|
4150
4247
|
const folder = await folderNamed(api2, name);
|
|
@@ -4159,7 +4256,7 @@ var folderCommand = async (api2, args) => {
|
|
|
4159
4256
|
files: batch.map((file) => ({
|
|
4160
4257
|
path: file.path,
|
|
4161
4258
|
// Read one batch at a time, so a large tree never sits in memory at once.
|
|
4162
|
-
contentsBase64:
|
|
4259
|
+
contentsBase64: fs11.readFileSync(path11.join(dir, file.path)).toString("base64")
|
|
4163
4260
|
})),
|
|
4164
4261
|
// --replace empties the folder with the first batch; the rest add to it.
|
|
4165
4262
|
merge: !(replace && index === 0)
|
|
@@ -4343,8 +4440,8 @@ var operatorCommand = async (api2, tryApi, baseUrl, args) => {
|
|
|
4343
4440
|
import { createHmac, randomBytes } from "node:crypto";
|
|
4344
4441
|
import { readFile } from "node:fs/promises";
|
|
4345
4442
|
import * as net2 from "node:net";
|
|
4346
|
-
import * as
|
|
4347
|
-
import * as
|
|
4443
|
+
import * as os8 from "node:os";
|
|
4444
|
+
import * as path13 from "node:path";
|
|
4348
4445
|
|
|
4349
4446
|
// src/server-docker-volumes.ts
|
|
4350
4447
|
import { createHash as createHash2 } from "node:crypto";
|
|
@@ -4562,9 +4659,9 @@ var verifyServerDockerVolumes = (runtime, input) => ownership(runtime, input, "v
|
|
|
4562
4659
|
|
|
4563
4660
|
// src/server-store.ts
|
|
4564
4661
|
import { randomUUID } from "node:crypto";
|
|
4565
|
-
import * as
|
|
4566
|
-
import * as
|
|
4567
|
-
import * as
|
|
4662
|
+
import * as fs12 from "node:fs";
|
|
4663
|
+
import * as os7 from "node:os";
|
|
4664
|
+
import * as path12 from "node:path";
|
|
4568
4665
|
var ServerStoreError = class extends Error {
|
|
4569
4666
|
_tag = "ServerStoreError";
|
|
4570
4667
|
};
|
|
@@ -4585,28 +4682,28 @@ var attempt = (operation) => {
|
|
|
4585
4682
|
var hasCode = (cause, code) => cause instanceof Error && "code" in cause && cause.code === code;
|
|
4586
4683
|
var readOptional = (file) => {
|
|
4587
4684
|
try {
|
|
4588
|
-
return
|
|
4685
|
+
return fs12.readFileSync(file, "utf8");
|
|
4589
4686
|
} catch (cause) {
|
|
4590
4687
|
if (hasCode(cause, "ENOENT")) return null;
|
|
4591
4688
|
throw cause;
|
|
4592
4689
|
}
|
|
4593
4690
|
};
|
|
4594
4691
|
var syncDirectory = (directory) => {
|
|
4595
|
-
const fd =
|
|
4692
|
+
const fd = fs12.openSync(directory, "r");
|
|
4596
4693
|
try {
|
|
4597
|
-
|
|
4694
|
+
fs12.fsyncSync(fd);
|
|
4598
4695
|
} finally {
|
|
4599
|
-
|
|
4696
|
+
fs12.closeSync(fd);
|
|
4600
4697
|
}
|
|
4601
4698
|
};
|
|
4602
4699
|
var writeDurable = (file, content, mode = 384) => {
|
|
4603
|
-
const fd =
|
|
4700
|
+
const fd = fs12.openSync(file, "wx", mode);
|
|
4604
4701
|
try {
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4702
|
+
fs12.writeFileSync(fd, content, "utf8");
|
|
4703
|
+
fs12.fchmodSync(fd, mode);
|
|
4704
|
+
fs12.fsyncSync(fd);
|
|
4608
4705
|
} finally {
|
|
4609
|
-
|
|
4706
|
+
fs12.closeSync(fd);
|
|
4610
4707
|
}
|
|
4611
4708
|
};
|
|
4612
4709
|
var fileKeys = ["identity", "config", "env", "compose", "postgresInit"];
|
|
@@ -4626,11 +4723,11 @@ var parseLockOwner = (raw) => {
|
|
|
4626
4723
|
};
|
|
4627
4724
|
var describeOwner = (lockDir) => {
|
|
4628
4725
|
try {
|
|
4629
|
-
const raw = JSON.parse(
|
|
4726
|
+
const raw = JSON.parse(fs12.readFileSync(path12.join(lockDir, "owner.json"), "utf8"));
|
|
4630
4727
|
const owner = parseLockOwner(raw);
|
|
4631
4728
|
if (owner === null) return "owner metadata is invalid";
|
|
4632
4729
|
const label = `PID ${owner.pid} on ${owner.hostname}`;
|
|
4633
|
-
if (owner.hostname !==
|
|
4730
|
+
if (owner.hostname !== os7.hostname()) return label;
|
|
4634
4731
|
try {
|
|
4635
4732
|
process.kill(owner.pid, 0);
|
|
4636
4733
|
return `${label} is still live`;
|
|
@@ -4646,28 +4743,28 @@ var lockGuidance = (lockDir) => new ServerStoreError(
|
|
|
4646
4743
|
);
|
|
4647
4744
|
var acquireLock = (configDir, create) => {
|
|
4648
4745
|
if (create) {
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
} else if (!
|
|
4746
|
+
fs12.mkdirSync(configDir, { recursive: true, mode: 448 });
|
|
4747
|
+
fs12.chmodSync(configDir, 448);
|
|
4748
|
+
} else if (!fs12.existsSync(configDir)) {
|
|
4652
4749
|
throw new ServerStoreError(
|
|
4653
4750
|
"No Mend server is configured. Run mend server setup explicitly to install one."
|
|
4654
4751
|
);
|
|
4655
4752
|
}
|
|
4656
|
-
const lockDir =
|
|
4753
|
+
const lockDir = path12.join(configDir, "server.lock");
|
|
4657
4754
|
try {
|
|
4658
|
-
|
|
4755
|
+
fs12.mkdirSync(lockDir, { mode: 448 });
|
|
4659
4756
|
} catch (cause) {
|
|
4660
4757
|
if (hasCode(cause, "EEXIST")) throw lockGuidance(lockDir);
|
|
4661
4758
|
throw cause;
|
|
4662
4759
|
}
|
|
4663
|
-
const owned =
|
|
4664
|
-
const ownerFile =
|
|
4665
|
-
const owner = `${JSON.stringify({ pid: process.pid, hostname:
|
|
4760
|
+
const owned = fs12.statSync(lockDir);
|
|
4761
|
+
const ownerFile = path12.join(lockDir, "owner.json");
|
|
4762
|
+
const owner = `${JSON.stringify({ pid: process.pid, hostname: os7.hostname(), token: randomUUID() })}
|
|
4666
4763
|
`;
|
|
4667
4764
|
let open = true;
|
|
4668
4765
|
const assertOwned = () => {
|
|
4669
|
-
const current =
|
|
4670
|
-
if (!open || current.dev !== owned.dev || current.ino !== owned.ino ||
|
|
4766
|
+
const current = fs12.statSync(lockDir);
|
|
4767
|
+
if (!open || current.dev !== owned.dev || current.ino !== owned.ino || fs12.readFileSync(ownerFile, "utf8") !== owner) {
|
|
4671
4768
|
throw new ServerStoreError(
|
|
4672
4769
|
"Server lock ownership was lost. Stop and check for another server command before retrying."
|
|
4673
4770
|
);
|
|
@@ -4683,8 +4780,8 @@ var acquireLock = (configDir, create) => {
|
|
|
4683
4780
|
release: () => {
|
|
4684
4781
|
const result = attempt(() => {
|
|
4685
4782
|
assertOwned();
|
|
4686
|
-
|
|
4687
|
-
|
|
4783
|
+
fs12.unlinkSync(ownerFile);
|
|
4784
|
+
fs12.rmdirSync(lockDir);
|
|
4688
4785
|
});
|
|
4689
4786
|
open = false;
|
|
4690
4787
|
return result;
|
|
@@ -4693,7 +4790,7 @@ var acquireLock = (configDir, create) => {
|
|
|
4693
4790
|
};
|
|
4694
4791
|
var readIdentity = (paths) => {
|
|
4695
4792
|
const identity = readOptional(paths.identity);
|
|
4696
|
-
if (identity === null && (
|
|
4793
|
+
if (identity === null && (fs12.existsSync(paths.active) || fs12.existsSync(paths.generations) && fs12.readdirSync(paths.generations).length > 0)) {
|
|
4697
4794
|
throw new ServerStoreError(
|
|
4698
4795
|
"Server identity.env is missing. Restore it from a retained generation or backup; refusing to generate replacement credentials."
|
|
4699
4796
|
);
|
|
@@ -4703,7 +4800,7 @@ var readIdentity = (paths) => {
|
|
|
4703
4800
|
var readActive = (paths) => {
|
|
4704
4801
|
let target;
|
|
4705
4802
|
try {
|
|
4706
|
-
target =
|
|
4803
|
+
target = fs12.readlinkSync(paths.active);
|
|
4707
4804
|
} catch (cause) {
|
|
4708
4805
|
if (hasCode(cause, "ENOENT")) return null;
|
|
4709
4806
|
throw cause;
|
|
@@ -4713,13 +4810,13 @@ var readActive = (paths) => {
|
|
|
4713
4810
|
"Server active pointer is invalid. Restore the symlink to a retained generation before retrying."
|
|
4714
4811
|
);
|
|
4715
4812
|
}
|
|
4716
|
-
const directory =
|
|
4813
|
+
const directory = path12.join(paths.configDir, target);
|
|
4717
4814
|
const files = {
|
|
4718
|
-
identity:
|
|
4719
|
-
config:
|
|
4720
|
-
env:
|
|
4721
|
-
compose:
|
|
4722
|
-
postgresInit:
|
|
4815
|
+
identity: fs12.readFileSync(path12.join(directory, fileNames.identity), "utf8"),
|
|
4816
|
+
config: fs12.readFileSync(path12.join(directory, fileNames.config), "utf8"),
|
|
4817
|
+
env: fs12.readFileSync(path12.join(directory, fileNames.env), "utf8"),
|
|
4818
|
+
compose: fs12.readFileSync(path12.join(directory, fileNames.compose), "utf8"),
|
|
4819
|
+
postgresInit: fs12.readFileSync(path12.join(directory, fileNames.postgresInit), "utf8")
|
|
4723
4820
|
};
|
|
4724
4821
|
if (readIdentity(paths) !== files.identity) {
|
|
4725
4822
|
throw new ServerStoreError(
|
|
@@ -4729,20 +4826,20 @@ var readActive = (paths) => {
|
|
|
4729
4826
|
return { directory, files };
|
|
4730
4827
|
};
|
|
4731
4828
|
var publishIdentity = (paths, identity) => {
|
|
4732
|
-
const temporary =
|
|
4829
|
+
const temporary = path12.join(paths.configDir, `.identity-${randomUUID()}`);
|
|
4733
4830
|
writeDurable(temporary, identity);
|
|
4734
|
-
|
|
4831
|
+
fs12.linkSync(temporary, paths.identity);
|
|
4735
4832
|
syncDirectory(paths.configDir);
|
|
4736
|
-
|
|
4833
|
+
fs12.unlinkSync(temporary);
|
|
4737
4834
|
};
|
|
4738
4835
|
var prepareGeneration = (paths, files) => {
|
|
4739
|
-
|
|
4836
|
+
fs12.mkdirSync(paths.generations, { recursive: true, mode: 448 });
|
|
4740
4837
|
const generationName = `gen-${randomUUID()}`;
|
|
4741
|
-
const directory =
|
|
4742
|
-
|
|
4838
|
+
const directory = path12.join(paths.generations, generationName);
|
|
4839
|
+
fs12.mkdirSync(directory, { mode: 448 });
|
|
4743
4840
|
for (const key of fileKeys) {
|
|
4744
4841
|
writeDurable(
|
|
4745
|
-
|
|
4842
|
+
path12.join(directory, fileNames[key]),
|
|
4746
4843
|
files[key],
|
|
4747
4844
|
key === "postgresInit" ? 493 : 384
|
|
4748
4845
|
);
|
|
@@ -4753,18 +4850,18 @@ var prepareGeneration = (paths, files) => {
|
|
|
4753
4850
|
return { directory, files };
|
|
4754
4851
|
};
|
|
4755
4852
|
var activateGeneration = (paths, generation) => {
|
|
4756
|
-
const relative4 =
|
|
4757
|
-
if (!/^generations\/gen-[0-9a-f-]{36}$/.test(relative4) || readIdentity(paths) !==
|
|
4853
|
+
const relative4 = path12.relative(paths.configDir, generation.directory);
|
|
4854
|
+
if (!/^generations\/gen-[0-9a-f-]{36}$/.test(relative4) || readIdentity(paths) !== fs12.readFileSync(path12.join(generation.directory, "identity.env"), "utf8")) {
|
|
4758
4855
|
throw new ServerStoreError("Cannot activate a generation outside this installation identity.");
|
|
4759
4856
|
}
|
|
4760
4857
|
for (const key of fileKeys) {
|
|
4761
|
-
if (
|
|
4858
|
+
if (fs12.readFileSync(path12.join(generation.directory, fileNames[key]), "utf8") !== generation.files[key]) {
|
|
4762
4859
|
throw new ServerStoreError("Cannot activate an incomplete or changed server generation.");
|
|
4763
4860
|
}
|
|
4764
4861
|
}
|
|
4765
|
-
const pointer =
|
|
4766
|
-
|
|
4767
|
-
|
|
4862
|
+
const pointer = path12.join(paths.configDir, `.active-${randomUUID()}`);
|
|
4863
|
+
fs12.symlinkSync(relative4, pointer);
|
|
4864
|
+
fs12.renameSync(pointer, paths.active);
|
|
4768
4865
|
syncDirectory(paths.configDir);
|
|
4769
4866
|
};
|
|
4770
4867
|
var prepareFiles = (paths, files) => {
|
|
@@ -4773,7 +4870,7 @@ var prepareFiles = (paths, files) => {
|
|
|
4773
4870
|
throw new ServerStoreError("Server identity already exists; refusing to replace credentials.");
|
|
4774
4871
|
}
|
|
4775
4872
|
const active = readActive(paths);
|
|
4776
|
-
if (active !== null && fileKeys.every((key) => files[key] === active.files[key]) && (
|
|
4873
|
+
if (active !== null && fileKeys.every((key) => files[key] === active.files[key]) && (fs12.statSync(path12.join(active.directory, fileNames.postgresInit)).mode & 4095) === 493)
|
|
4777
4874
|
return active;
|
|
4778
4875
|
if (identity === null) publishIdentity(paths, files.identity);
|
|
4779
4876
|
return prepareGeneration(paths, files);
|
|
@@ -4786,9 +4883,9 @@ var commitGeneration = (paths, files) => {
|
|
|
4786
4883
|
var createStore = (configDir, lock) => {
|
|
4787
4884
|
const paths = {
|
|
4788
4885
|
configDir,
|
|
4789
|
-
identity:
|
|
4790
|
-
generations:
|
|
4791
|
-
active:
|
|
4886
|
+
identity: path12.join(configDir, "identity.env"),
|
|
4887
|
+
generations: path12.join(configDir, "generations"),
|
|
4888
|
+
active: path12.join(configDir, "active")
|
|
4792
4889
|
};
|
|
4793
4890
|
const whileOwned = (operation) => attempt(() => {
|
|
4794
4891
|
lock.assertOwned();
|
|
@@ -4801,13 +4898,13 @@ var createStore = (configDir, lock) => {
|
|
|
4801
4898
|
prepare: (files) => whileOwned(() => prepareFiles(paths, files)),
|
|
4802
4899
|
activate: (generation) => whileOwned(() => activateGeneration(paths, generation)),
|
|
4803
4900
|
createBackup: (previous, target) => whileOwned(() => {
|
|
4804
|
-
const backups =
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
const directory =
|
|
4808
|
-
|
|
4901
|
+
const backups = path12.join(configDir, "backups");
|
|
4902
|
+
fs12.mkdirSync(backups, { recursive: true, mode: 448 });
|
|
4903
|
+
fs12.chmodSync(backups, 448);
|
|
4904
|
+
const directory = path12.join(backups, `upgrade-${randomUUID()}`);
|
|
4905
|
+
fs12.mkdirSync(directory, { mode: 448 });
|
|
4809
4906
|
writeDurable(
|
|
4810
|
-
|
|
4907
|
+
path12.join(directory, "recovery.json"),
|
|
4811
4908
|
`${JSON.stringify(
|
|
4812
4909
|
{
|
|
4813
4910
|
previousGeneration: previous.directory,
|
|
@@ -4823,21 +4920,21 @@ var createStore = (configDir, lock) => {
|
|
|
4823
4920
|
syncDirectory(directory);
|
|
4824
4921
|
syncDirectory(backups);
|
|
4825
4922
|
syncDirectory(configDir);
|
|
4826
|
-
const partialFile =
|
|
4923
|
+
const partialFile = path12.join(directory, "database.sql.partial");
|
|
4827
4924
|
return {
|
|
4828
4925
|
directory,
|
|
4829
4926
|
partialFile,
|
|
4830
4927
|
complete: () => whileOwned(() => {
|
|
4831
|
-
if (
|
|
4928
|
+
if (fs12.statSync(partialFile).size === 0)
|
|
4832
4929
|
throw new ServerStoreError("Database backup is empty; refusing target startup.");
|
|
4833
|
-
const fd =
|
|
4930
|
+
const fd = fs12.openSync(partialFile, "r");
|
|
4834
4931
|
try {
|
|
4835
|
-
|
|
4836
|
-
|
|
4932
|
+
fs12.fchmodSync(fd, 384);
|
|
4933
|
+
fs12.fsyncSync(fd);
|
|
4837
4934
|
} finally {
|
|
4838
|
-
|
|
4935
|
+
fs12.closeSync(fd);
|
|
4839
4936
|
}
|
|
4840
|
-
|
|
4937
|
+
fs12.renameSync(partialFile, path12.join(directory, "database.sql"));
|
|
4841
4938
|
syncDirectory(directory);
|
|
4842
4939
|
})
|
|
4843
4940
|
};
|
|
@@ -4846,7 +4943,7 @@ var createStore = (configDir, lock) => {
|
|
|
4846
4943
|
};
|
|
4847
4944
|
var refuseFlatLayout = (configDir) => {
|
|
4848
4945
|
if (["server.json", "server.env", "compose.yaml", "postgres-init.sh"].some(
|
|
4849
|
-
(name) =>
|
|
4946
|
+
(name) => fs12.existsSync(path12.join(configDir, name))
|
|
4850
4947
|
)) {
|
|
4851
4948
|
throw new ServerStoreError(
|
|
4852
4949
|
"Unreleased flat server configuration found. Preserve its credentials and volumes; migrate it to the generation layout before retrying. Setup will not replace this identity."
|
|
@@ -4854,7 +4951,7 @@ var refuseFlatLayout = (configDir) => {
|
|
|
4854
4951
|
}
|
|
4855
4952
|
};
|
|
4856
4953
|
var withServerStore = async (directory, operation, options = {}) => {
|
|
4857
|
-
const configDir =
|
|
4954
|
+
const configDir = path12.resolve(directory);
|
|
4858
4955
|
let lock;
|
|
4859
4956
|
let result;
|
|
4860
4957
|
try {
|
|
@@ -4871,9 +4968,9 @@ var withServerStore = async (directory, operation, options = {}) => {
|
|
|
4871
4968
|
};
|
|
4872
4969
|
|
|
4873
4970
|
// src/version.ts
|
|
4874
|
-
import * as
|
|
4971
|
+
import * as fs13 from "node:fs";
|
|
4875
4972
|
var cliVersion = () => {
|
|
4876
|
-
const raw =
|
|
4973
|
+
const raw = fs13.readFileSync(new URL("../package.json", import.meta.url), "utf8");
|
|
4877
4974
|
const parsed = JSON.parse(raw);
|
|
4878
4975
|
if (typeof parsed === "object" && parsed !== null && "version" in parsed) {
|
|
4879
4976
|
const version = parsed.version;
|
|
@@ -5576,7 +5673,7 @@ var resolveServerVersion = async (runtime, options, existing) => {
|
|
|
5576
5673
|
return version;
|
|
5577
5674
|
};
|
|
5578
5675
|
var parseDockerSocketPath = (socket) => {
|
|
5579
|
-
if (!
|
|
5676
|
+
if (!path13.isAbsolute(socket) || /[\r\n$'"#:\\]/.test(socket)) {
|
|
5580
5677
|
throw setupError(
|
|
5581
5678
|
"--docker-socket must be an absolute path without line breaks or Compose interpolation characters."
|
|
5582
5679
|
);
|
|
@@ -5602,8 +5699,8 @@ var resolveAssets = async (runtime, serverVersion, existing, store, options) =>
|
|
|
5602
5699
|
if (options.assetsDir !== void 0) {
|
|
5603
5700
|
try {
|
|
5604
5701
|
const [compose2, postgresInit2] = await Promise.all([
|
|
5605
|
-
readFile(
|
|
5606
|
-
readFile(
|
|
5702
|
+
readFile(path13.join(options.assetsDir, COMPOSE_ASSET), "utf8"),
|
|
5703
|
+
readFile(path13.join(options.assetsDir, POSTGRES_INIT_ASSET), "utf8")
|
|
5607
5704
|
]);
|
|
5608
5705
|
validateComposeAsset(compose2);
|
|
5609
5706
|
validatePostgresAsset(postgresInit2);
|
|
@@ -6129,10 +6226,10 @@ var manageServer = async (command, args, runtime, store) => {
|
|
|
6129
6226
|
};
|
|
6130
6227
|
var nodeServerRuntime = () => {
|
|
6131
6228
|
const environment = { ...process.env };
|
|
6132
|
-
const home =
|
|
6133
|
-
const configHome = environment["XDG_CONFIG_HOME"] ??
|
|
6229
|
+
const home = os8.homedir();
|
|
6230
|
+
const configHome = environment["XDG_CONFIG_HOME"] ?? path13.join(home, ".config");
|
|
6134
6231
|
return {
|
|
6135
|
-
configDir:
|
|
6232
|
+
configDir: path13.join(configHome, "mend"),
|
|
6136
6233
|
platform: process.platform,
|
|
6137
6234
|
cliVersion: cliVersion(),
|
|
6138
6235
|
run: (command, args, options) => runServerProcess(command, args, environment, options),
|
|
@@ -6585,8 +6682,8 @@ var createServiceTunnels = (options) => {
|
|
|
6585
6682
|
};
|
|
6586
6683
|
|
|
6587
6684
|
// src/skills.ts
|
|
6588
|
-
import * as
|
|
6589
|
-
import * as
|
|
6685
|
+
import * as fs14 from "node:fs";
|
|
6686
|
+
import * as path14 from "node:path";
|
|
6590
6687
|
var DEFAULT_SKILLS_DIR = ".agents/skills";
|
|
6591
6688
|
var MAX_SKILL_FILES = 64;
|
|
6592
6689
|
var MAX_SKILL_FILE_BYTES = 512 * 1024;
|
|
@@ -6609,12 +6706,12 @@ var readFrontmatterDescription = (contents) => {
|
|
|
6609
6706
|
var walkFiles = (root) => {
|
|
6610
6707
|
const found = [];
|
|
6611
6708
|
const visit = (dir) => {
|
|
6612
|
-
for (const entry of
|
|
6709
|
+
for (const entry of fs14.readdirSync(dir, { withFileTypes: true })) {
|
|
6613
6710
|
if (IGNORED_DIRS.has(entry.name)) continue;
|
|
6614
|
-
const absolute =
|
|
6711
|
+
const absolute = path14.join(dir, entry.name);
|
|
6615
6712
|
let stat;
|
|
6616
6713
|
try {
|
|
6617
|
-
stat =
|
|
6714
|
+
stat = fs14.statSync(absolute);
|
|
6618
6715
|
} catch {
|
|
6619
6716
|
continue;
|
|
6620
6717
|
}
|
|
@@ -6628,7 +6725,7 @@ var walkFiles = (root) => {
|
|
|
6628
6725
|
var scanSkillLibrary = (root) => {
|
|
6629
6726
|
let entries;
|
|
6630
6727
|
try {
|
|
6631
|
-
entries =
|
|
6728
|
+
entries = fs14.readdirSync(root, { withFileTypes: true });
|
|
6632
6729
|
} catch {
|
|
6633
6730
|
return { error: `${root} is not a readable directory` };
|
|
6634
6731
|
}
|
|
@@ -6636,23 +6733,23 @@ var scanSkillLibrary = (root) => {
|
|
|
6636
6733
|
const notes = [];
|
|
6637
6734
|
for (const entry of entries.toSorted((a, b) => a.name.localeCompare(b.name))) {
|
|
6638
6735
|
if (entry.name.startsWith(".")) continue;
|
|
6639
|
-
const skillDir =
|
|
6736
|
+
const skillDir = path14.join(root, entry.name);
|
|
6640
6737
|
let stat;
|
|
6641
6738
|
try {
|
|
6642
|
-
stat =
|
|
6739
|
+
stat = fs14.statSync(skillDir);
|
|
6643
6740
|
} catch {
|
|
6644
6741
|
continue;
|
|
6645
6742
|
}
|
|
6646
6743
|
if (!stat.isDirectory()) continue;
|
|
6647
|
-
if (!
|
|
6744
|
+
if (!fs14.existsSync(path14.join(skillDir, "SKILL.md"))) {
|
|
6648
6745
|
notes.push({ skill: entry.name, message: "no SKILL.md \u2014 skipped" });
|
|
6649
6746
|
continue;
|
|
6650
6747
|
}
|
|
6651
6748
|
const files = [];
|
|
6652
6749
|
let bytes = 0;
|
|
6653
6750
|
for (const absolute of walkFiles(skillDir)) {
|
|
6654
|
-
const relative4 =
|
|
6655
|
-
const buffer =
|
|
6751
|
+
const relative4 = path14.relative(skillDir, absolute).split(path14.sep).join("/");
|
|
6752
|
+
const buffer = fs14.readFileSync(absolute);
|
|
6656
6753
|
if (buffer.byteLength > MAX_SKILL_FILE_BYTES) {
|
|
6657
6754
|
notes.push({ skill: entry.name, message: `${relative4} is over 512KB \u2014 skipped` });
|
|
6658
6755
|
continue;
|
|
@@ -6687,16 +6784,16 @@ var scanSkillLibrary = (root) => {
|
|
|
6687
6784
|
};
|
|
6688
6785
|
|
|
6689
6786
|
// src/ssh-setup.ts
|
|
6690
|
-
import * as
|
|
6691
|
-
import * as
|
|
6787
|
+
import * as os10 from "node:os";
|
|
6788
|
+
import * as path16 from "node:path";
|
|
6692
6789
|
|
|
6693
6790
|
// ../../packages/workspace-ssh/src/index.ts
|
|
6694
|
-
import { spawnSync as
|
|
6791
|
+
import { spawnSync as spawnSync5 } from "node:child_process";
|
|
6695
6792
|
import { createHash as createHash3 } from "node:crypto";
|
|
6696
|
-
import * as
|
|
6793
|
+
import * as fs15 from "node:fs";
|
|
6697
6794
|
import { isIP as isIP2 } from "node:net";
|
|
6698
|
-
import * as
|
|
6699
|
-
import * as
|
|
6795
|
+
import * as os9 from "node:os";
|
|
6796
|
+
import * as path15 from "node:path";
|
|
6700
6797
|
var LEGACY_BLOCK_BEGIN = "# >>> mend workspace ssh (managed by `mend ssh setup`) >>>";
|
|
6701
6798
|
var LEGACY_BLOCK_END = "# <<< mend workspace ssh <<<";
|
|
6702
6799
|
var WorkspaceSshError = class extends Error {
|
|
@@ -6783,8 +6880,8 @@ var blockBegin = (alias) => `# >>> mend workspace ssh ${alias} (managed) >>>`;
|
|
|
6783
6880
|
var blockEnd = (alias) => `# <<< mend workspace ssh ${alias} <<<`;
|
|
6784
6881
|
var hasControls = (value) => /[\p{Cc}\p{Zl}\p{Zp}]/u.test(value);
|
|
6785
6882
|
var parseIdentityPath = (value) => {
|
|
6786
|
-
const resolved =
|
|
6787
|
-
value.startsWith("~/") ?
|
|
6883
|
+
const resolved = path15.resolve(
|
|
6884
|
+
value.startsWith("~/") ? path15.join(os9.homedir(), value.slice(2)) : value
|
|
6788
6885
|
);
|
|
6789
6886
|
if (value === "" || hasControls(value) || hasControls(resolved) || value.includes("${") || resolved.includes("${")) {
|
|
6790
6887
|
return failure(
|
|
@@ -6930,7 +7027,7 @@ var workspaceSshPublicKeyFingerprint = (publicKey) => {
|
|
|
6930
7027
|
var publicKeyIdentity = (publicKey) => publicKey.trim().split(/\s+/).slice(0, 2).join(" ");
|
|
6931
7028
|
var agentKeys = () => {
|
|
6932
7029
|
if (!process.env["SSH_AUTH_SOCK"]) return [];
|
|
6933
|
-
const listed =
|
|
7030
|
+
const listed = spawnSync5("ssh-add", ["-L"], {
|
|
6934
7031
|
encoding: "utf8",
|
|
6935
7032
|
timeout: 3e3,
|
|
6936
7033
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -6940,7 +7037,7 @@ var agentKeys = () => {
|
|
|
6940
7037
|
};
|
|
6941
7038
|
var readText = (file) => {
|
|
6942
7039
|
try {
|
|
6943
|
-
return success(
|
|
7040
|
+
return success(fs15.readFileSync(file, "utf8"));
|
|
6944
7041
|
} catch (cause) {
|
|
6945
7042
|
const error = cause instanceof Error && "code" in cause ? cause.code : void 0;
|
|
6946
7043
|
return error === "ENOENT" ? success("") : failure(new WorkspaceSshError("key", `Could not read ${file}.`, cause));
|
|
@@ -6967,7 +7064,7 @@ var readRequiredKey = (keyPath, source) => {
|
|
|
6967
7064
|
const privatePath = publicPath.slice(0, -".pub".length);
|
|
6968
7065
|
const read = readText(publicPath);
|
|
6969
7066
|
if (!read.ok) return read;
|
|
6970
|
-
const derived =
|
|
7067
|
+
const derived = spawnSync5("ssh-keygen", ["-y", "-P", "", "-f", privatePath], {
|
|
6971
7068
|
encoding: "utf8",
|
|
6972
7069
|
timeout: 3e3,
|
|
6973
7070
|
stdio: ["ignore", "pipe", "pipe"],
|
|
@@ -6998,19 +7095,19 @@ var readRequiredKey = (keyPath, source) => {
|
|
|
6998
7095
|
var readOptionalKey = (keyPath) => {
|
|
6999
7096
|
const parsed = parseIdentityPath(keyPath);
|
|
7000
7097
|
if (!parsed.ok) return parsed;
|
|
7001
|
-
if (!
|
|
7098
|
+
if (!fs15.existsSync(parsed.value) && !fs15.existsSync(publicKeyPath(parsed.value)))
|
|
7002
7099
|
return success(null);
|
|
7003
7100
|
return readRequiredKey(parsed.value, "existing");
|
|
7004
7101
|
};
|
|
7005
7102
|
var pinAgentKey = (configHome, publicKey) => {
|
|
7006
|
-
const identityFile =
|
|
7103
|
+
const identityFile = path15.resolve(
|
|
7007
7104
|
configHome,
|
|
7008
7105
|
"ssh",
|
|
7009
7106
|
`agent-${hashIdentity(publicKeyIdentity(publicKey))}.pub`
|
|
7010
7107
|
);
|
|
7011
7108
|
try {
|
|
7012
|
-
|
|
7013
|
-
|
|
7109
|
+
fs15.mkdirSync(path15.dirname(identityFile), { recursive: true, mode: 448 });
|
|
7110
|
+
fs15.writeFileSync(identityFile, `${publicKey}
|
|
7014
7111
|
`, { mode: 384 });
|
|
7015
7112
|
return success({ publicKey, identityFile, source: "agent" });
|
|
7016
7113
|
} catch (cause) {
|
|
@@ -7021,15 +7118,15 @@ var pinAgentKey = (configHome, publicKey) => {
|
|
|
7021
7118
|
};
|
|
7022
7119
|
var generateDedicatedKey = (privatePath) => {
|
|
7023
7120
|
try {
|
|
7024
|
-
|
|
7121
|
+
fs15.mkdirSync(path15.dirname(privatePath), { recursive: true, mode: 448 });
|
|
7025
7122
|
} catch (cause) {
|
|
7026
7123
|
return failure(
|
|
7027
|
-
new WorkspaceSshError("key", `Could not create ${
|
|
7124
|
+
new WorkspaceSshError("key", `Could not create ${path15.dirname(privatePath)}.`, cause)
|
|
7028
7125
|
);
|
|
7029
7126
|
}
|
|
7030
|
-
const generated =
|
|
7127
|
+
const generated = spawnSync5(
|
|
7031
7128
|
"ssh-keygen",
|
|
7032
|
-
["-t", "ed25519", "-f", privatePath, "-N", "", "-C", `mend-${
|
|
7129
|
+
["-t", "ed25519", "-f", privatePath, "-N", "", "-C", `mend-${os9.hostname()}`],
|
|
7033
7130
|
{ encoding: "utf8", timeout: 3e3, stdio: ["ignore", "pipe", "pipe"] }
|
|
7034
7131
|
);
|
|
7035
7132
|
if (generated.status !== 0) {
|
|
@@ -7053,7 +7150,7 @@ var generateDedicatedKey = (privatePath) => {
|
|
|
7053
7150
|
return fresh;
|
|
7054
7151
|
};
|
|
7055
7152
|
var dedicatedKey = (configHome, create) => {
|
|
7056
|
-
const privatePath =
|
|
7153
|
+
const privatePath = path15.join(configHome, "ssh", "id_ed25519");
|
|
7057
7154
|
const existing = readOptionalKey(privatePath);
|
|
7058
7155
|
if (existing.ok === false) return failure(existing.error);
|
|
7059
7156
|
if (existing.value !== null || !create) return existing;
|
|
@@ -7101,10 +7198,10 @@ var writeWorkspaceSshConfig = (configFile, target, identityFile) => {
|
|
|
7101
7198
|
const reconciled = reconcileWorkspaceSshConfig(before.value, target, identityFile);
|
|
7102
7199
|
if (reconciled.ok === false) return failure(reconciled.error);
|
|
7103
7200
|
try {
|
|
7104
|
-
|
|
7105
|
-
|
|
7106
|
-
|
|
7107
|
-
|
|
7201
|
+
fs15.mkdirSync(path15.dirname(configFile), { recursive: true, mode: 448 });
|
|
7202
|
+
fs15.chmodSync(path15.dirname(configFile), 448);
|
|
7203
|
+
fs15.writeFileSync(configFile, reconciled.value, { mode: 384 });
|
|
7204
|
+
fs15.chmodSync(configFile, 384);
|
|
7108
7205
|
return success(void 0);
|
|
7109
7206
|
} catch (cause) {
|
|
7110
7207
|
return failure(new WorkspaceSshError("config", `Could not write ${configFile}.`, cause));
|
|
@@ -7117,7 +7214,7 @@ var dim5 = ansi("2");
|
|
|
7117
7214
|
var green5 = ansi("32");
|
|
7118
7215
|
var warn = ansi("33");
|
|
7119
7216
|
var say5 = (line) => console.log(line);
|
|
7120
|
-
var sshConfigPath = () =>
|
|
7217
|
+
var sshConfigPath = () => path16.join(os10.homedir(), ".ssh", "config");
|
|
7121
7218
|
var flagValue2 = (args, flag) => {
|
|
7122
7219
|
const index = args.indexOf(flag);
|
|
7123
7220
|
return index === -1 || args[index + 1] === void 0 ? null : String(args[index + 1]);
|
|
@@ -7187,7 +7284,7 @@ var setup = async (api2, cliHome, serverUrl, args) => {
|
|
|
7187
7284
|
if (picked.value === null) return showFailure("No workspace SSH key is available.");
|
|
7188
7285
|
const registered = await api2("POST", "/workspace-ssh/keys", {
|
|
7189
7286
|
publicKey: picked.value.publicKey,
|
|
7190
|
-
name:
|
|
7287
|
+
name: os10.hostname()
|
|
7191
7288
|
});
|
|
7192
7289
|
const sourceLabel = {
|
|
7193
7290
|
explicit: "from --key",
|
|
@@ -7229,8 +7326,8 @@ var sshCommand = async (args, api2, cliHome, serverUrl) => {
|
|
|
7229
7326
|
};
|
|
7230
7327
|
|
|
7231
7328
|
// src/uninstall.ts
|
|
7232
|
-
import * as
|
|
7233
|
-
import * as
|
|
7329
|
+
import * as fs16 from "node:fs";
|
|
7330
|
+
import * as path17 from "node:path";
|
|
7234
7331
|
var UNINSTALL_USAGE = "usage: mend uninstall [--all | --server | --home] [--yes]";
|
|
7235
7332
|
var parseUninstallArgs = (args) => {
|
|
7236
7333
|
let scope = null;
|
|
@@ -7252,14 +7349,14 @@ var parseUninstallArgs = (args) => {
|
|
|
7252
7349
|
var SERVER_FILES = ["identity.env", "active", "generations", "backups"];
|
|
7253
7350
|
var countEntries = (directory) => {
|
|
7254
7351
|
try {
|
|
7255
|
-
return
|
|
7352
|
+
return fs16.readdirSync(directory).length;
|
|
7256
7353
|
} catch {
|
|
7257
7354
|
return 0;
|
|
7258
7355
|
}
|
|
7259
7356
|
};
|
|
7260
7357
|
var exists = (file) => {
|
|
7261
7358
|
try {
|
|
7262
|
-
|
|
7359
|
+
fs16.lstatSync(file);
|
|
7263
7360
|
return true;
|
|
7264
7361
|
} catch {
|
|
7265
7362
|
return false;
|
|
@@ -7268,7 +7365,7 @@ var exists = (file) => {
|
|
|
7268
7365
|
var managedBlockCount = (sshConfigFile) => {
|
|
7269
7366
|
let config;
|
|
7270
7367
|
try {
|
|
7271
|
-
config =
|
|
7368
|
+
config = fs16.readFileSync(sshConfigFile, "utf8");
|
|
7272
7369
|
} catch {
|
|
7273
7370
|
return 0;
|
|
7274
7371
|
}
|
|
@@ -7292,27 +7389,27 @@ var describeUninstall = async (runtime, scope) => {
|
|
|
7292
7389
|
if (!read.error.message.startsWith("No Mend server is configured")) throw read.error;
|
|
7293
7390
|
server = "none";
|
|
7294
7391
|
} else if (read.value === null) {
|
|
7295
|
-
server = exists(
|
|
7392
|
+
server = exists(path17.join(configDir, "identity.env")) ? {
|
|
7296
7393
|
version: "(no active generation)",
|
|
7297
7394
|
appUrl: "",
|
|
7298
7395
|
dockerContext: "",
|
|
7299
|
-
generations: countEntries(
|
|
7300
|
-
backups: countEntries(
|
|
7396
|
+
generations: countEntries(path17.join(configDir, "generations")),
|
|
7397
|
+
backups: countEntries(path17.join(configDir, "backups"))
|
|
7301
7398
|
} : "none";
|
|
7302
7399
|
} else {
|
|
7303
7400
|
server = {
|
|
7304
7401
|
version: read.value.config.serverVersion,
|
|
7305
7402
|
appUrl: read.value.config.appUrl,
|
|
7306
7403
|
dockerContext: read.value.config.dockerContext,
|
|
7307
|
-
generations: countEntries(
|
|
7308
|
-
backups: countEntries(
|
|
7404
|
+
generations: countEntries(path17.join(configDir, "generations")),
|
|
7405
|
+
backups: countEntries(path17.join(configDir, "backups"))
|
|
7309
7406
|
};
|
|
7310
7407
|
}
|
|
7311
7408
|
}
|
|
7312
7409
|
let home = null;
|
|
7313
7410
|
if (scope !== "server") {
|
|
7314
|
-
const cliConfig =
|
|
7315
|
-
const sshDirectory =
|
|
7411
|
+
const cliConfig = path17.join(runtime.cliHome, "cli.json");
|
|
7412
|
+
const sshDirectory = path17.join(runtime.cliHome, "ssh");
|
|
7316
7413
|
home = {
|
|
7317
7414
|
cliConfig: exists(cliConfig) ? cliConfig : null,
|
|
7318
7415
|
sshDirectory: exists(sshDirectory) ? sshDirectory : null,
|
|
@@ -7444,7 +7541,7 @@ var removeServer = async (runtime, failures, leftovers) => {
|
|
|
7444
7541
|
}
|
|
7445
7542
|
}
|
|
7446
7543
|
for (const name of SERVER_FILES) {
|
|
7447
|
-
|
|
7544
|
+
fs16.rmSync(path17.join(configDir, name), { recursive: true, force: true });
|
|
7448
7545
|
}
|
|
7449
7546
|
server.writeLine(`removed ${configDir}/{identity.env, active, generations, backups}`);
|
|
7450
7547
|
},
|
|
@@ -7455,20 +7552,20 @@ var removeServer = async (runtime, failures, leftovers) => {
|
|
|
7455
7552
|
var removeHome = async (runtime, plan, failures, leftovers) => {
|
|
7456
7553
|
const { server } = runtime;
|
|
7457
7554
|
if (plan.cliConfig !== null) {
|
|
7458
|
-
|
|
7555
|
+
fs16.rmSync(plan.cliConfig, { force: true });
|
|
7459
7556
|
server.writeLine(`removed ${plan.cliConfig}`);
|
|
7460
7557
|
}
|
|
7461
7558
|
if (plan.sshDirectory !== null) {
|
|
7462
|
-
|
|
7559
|
+
fs16.rmSync(plan.sshDirectory, { recursive: true, force: true });
|
|
7463
7560
|
server.writeLine(`removed ${plan.sshDirectory}`);
|
|
7464
7561
|
}
|
|
7465
7562
|
if (plan.managedSshBlocks > 0) {
|
|
7466
7563
|
try {
|
|
7467
|
-
const before =
|
|
7564
|
+
const before = fs16.readFileSync(runtime.sshConfigFile, "utf8");
|
|
7468
7565
|
const stripped = stripManagedWorkspaceSshBlocks(before);
|
|
7469
7566
|
if (!stripped.ok) throw stripped.error;
|
|
7470
7567
|
if (stripped.value.removed > 0) {
|
|
7471
|
-
|
|
7568
|
+
fs16.writeFileSync(runtime.sshConfigFile, stripped.value.config, { mode: 384 });
|
|
7472
7569
|
server.writeLine(
|
|
7473
7570
|
`removed ${plural(stripped.value.removed, "managed block")} from ${runtime.sshConfigFile}`
|
|
7474
7571
|
);
|
|
@@ -7481,12 +7578,12 @@ var removeHome = async (runtime, plan, failures, leftovers) => {
|
|
|
7481
7578
|
}
|
|
7482
7579
|
let remaining = [];
|
|
7483
7580
|
try {
|
|
7484
|
-
remaining =
|
|
7581
|
+
remaining = fs16.readdirSync(runtime.cliHome);
|
|
7485
7582
|
} catch {
|
|
7486
7583
|
return;
|
|
7487
7584
|
}
|
|
7488
7585
|
if (remaining.length === 0) {
|
|
7489
|
-
|
|
7586
|
+
fs16.rmdirSync(runtime.cliHome);
|
|
7490
7587
|
server.writeLine(`removed ${runtime.cliHome}`);
|
|
7491
7588
|
} else {
|
|
7492
7589
|
leftovers.push(
|
|
@@ -7550,19 +7647,19 @@ var upgradeUrl = async (input) => {
|
|
|
7550
7647
|
// src/main.ts
|
|
7551
7648
|
var mendCliHome = () => {
|
|
7552
7649
|
const xdg = process.env["XDG_CONFIG_HOME"];
|
|
7553
|
-
const preferred =
|
|
7554
|
-
xdg === void 0 || xdg === "" ?
|
|
7650
|
+
const preferred = path18.join(
|
|
7651
|
+
xdg === void 0 || xdg === "" ? path18.join(os11.homedir(), ".config") : xdg,
|
|
7555
7652
|
"mend"
|
|
7556
7653
|
);
|
|
7557
|
-
const legacy =
|
|
7558
|
-
return !
|
|
7654
|
+
const legacy = path18.join(os11.homedir(), ".mend");
|
|
7655
|
+
return !fs17.existsSync(preferred) && fs17.existsSync(legacy) ? legacy : preferred;
|
|
7559
7656
|
};
|
|
7560
|
-
var CONFIG_PATH =
|
|
7657
|
+
var CONFIG_PATH = path18.join(mendCliHome(), "cli.json");
|
|
7561
7658
|
var loadConfig = () => {
|
|
7562
7659
|
let fileConfig = {};
|
|
7563
|
-
if (
|
|
7660
|
+
if (fs17.existsSync(CONFIG_PATH)) {
|
|
7564
7661
|
try {
|
|
7565
|
-
fileConfig = JSON.parse(
|
|
7662
|
+
fileConfig = JSON.parse(fs17.readFileSync(CONFIG_PATH, "utf8"));
|
|
7566
7663
|
} catch {
|
|
7567
7664
|
fail6(`could not parse ${CONFIG_PATH}`);
|
|
7568
7665
|
}
|
|
@@ -7814,7 +7911,7 @@ var adopt = async (config, args) => {
|
|
|
7814
7911
|
}
|
|
7815
7912
|
const sourceIssue = repositoryCloneUrlIssue(source);
|
|
7816
7913
|
if (sourceIssue !== null) return fail6(sourceIssue);
|
|
7817
|
-
const projectName = name ?? normalizeProjectName(
|
|
7914
|
+
const projectName = name ?? normalizeProjectName(path18.basename(source, ".git"));
|
|
7818
7915
|
const auth = authFlagIndex !== -1 && args[authFlagIndex + 1] !== void 0 ? String(args[authFlagIndex + 1]) : null;
|
|
7819
7916
|
if (auth !== null && auth !== "ambient" && auth !== "mend-key" && auth !== "bridge") {
|
|
7820
7917
|
return fail6(`--auth takes "ambient", "mend-key", or "bridge", not "${auth}"`);
|
|
@@ -7863,7 +7960,7 @@ var findProject = async (config, explicit, adoptCwd = false) => {
|
|
|
7863
7960
|
const sourceIssue = repositoryCloneUrlIssue(facts.originUrl);
|
|
7864
7961
|
if (sourceIssue !== null) return fail6(sourceIssue);
|
|
7865
7962
|
const adopted = await api(config, "POST", "/projects", {
|
|
7866
|
-
name: normalizeProjectName(
|
|
7963
|
+
name: normalizeProjectName(path18.basename(facts.repoRoot)),
|
|
7867
7964
|
source: facts.originUrl
|
|
7868
7965
|
});
|
|
7869
7966
|
say6(`${green6("\u2713")} adopted \xB7 ${adopted.name} \xB7 ${dim6(adopted.storePath)}`);
|
|
@@ -8824,13 +8921,13 @@ var serviceRestart = async (config, args) => {
|
|
|
8824
8921
|
};
|
|
8825
8922
|
var serviceInit = async (args) => {
|
|
8826
8923
|
const root = gitTopLevel(process.cwd()) ?? process.cwd();
|
|
8827
|
-
const target =
|
|
8828
|
-
if (
|
|
8924
|
+
const target = path18.join(root, "mend.toml");
|
|
8925
|
+
if (fs17.existsSync(target)) {
|
|
8829
8926
|
return fail6(`${target} already exists \u2014 edit it directly (init never merges)`);
|
|
8830
8927
|
}
|
|
8831
|
-
const rootFiles =
|
|
8928
|
+
const rootFiles = fs17.readdirSync(root);
|
|
8832
8929
|
const proposals = [];
|
|
8833
|
-
const readRoot = (name) =>
|
|
8930
|
+
const readRoot = (name) => fs17.readFileSync(path18.join(root, name), "utf8");
|
|
8834
8931
|
if (rootFiles.includes("package.json")) {
|
|
8835
8932
|
proposals.push(...proposeFromPackageJson(readRoot("package.json"), rootFiles));
|
|
8836
8933
|
}
|
|
@@ -8840,13 +8937,13 @@ var serviceInit = async (args) => {
|
|
|
8840
8937
|
);
|
|
8841
8938
|
for (const glob of globs) {
|
|
8842
8939
|
const parent = glob.replace(/\/?\*+$/, "");
|
|
8843
|
-
const dirs = glob.endsWith("*") ?
|
|
8940
|
+
const dirs = glob.endsWith("*") ? fs17.existsSync(path18.join(root, parent)) ? fs17.readdirSync(path18.join(root, parent)).map((dir) => path18.join(parent, dir)) : [] : [glob];
|
|
8844
8941
|
for (const dir of dirs) {
|
|
8845
|
-
const manifest =
|
|
8846
|
-
if (!
|
|
8942
|
+
const manifest = path18.join(root, dir, "package.json");
|
|
8943
|
+
if (!fs17.existsSync(manifest)) continue;
|
|
8847
8944
|
for (const proposal of proposeFromWorkspacePackage(
|
|
8848
|
-
|
|
8849
|
-
|
|
8945
|
+
path18.basename(dir),
|
|
8946
|
+
fs17.readFileSync(manifest, "utf8"),
|
|
8850
8947
|
rootFiles
|
|
8851
8948
|
)) {
|
|
8852
8949
|
if (!proposals.some((existing) => existing.name === proposal.name)) {
|
|
@@ -8890,7 +8987,7 @@ var serviceInit = async (args) => {
|
|
|
8890
8987
|
return;
|
|
8891
8988
|
}
|
|
8892
8989
|
}
|
|
8893
|
-
|
|
8990
|
+
fs17.writeFileSync(target, toml);
|
|
8894
8991
|
say6(`${green6("\u2713")} wrote ${target} \u2014 commit it, then: mend service run ${proposals[0]?.name}`);
|
|
8895
8992
|
};
|
|
8896
8993
|
var tunnelServices = async (config, services, portOverride) => {
|
|
@@ -8994,23 +9091,23 @@ var serviceCommand = async (config, args) => {
|
|
|
8994
9091
|
}
|
|
8995
9092
|
};
|
|
8996
9093
|
var isProvider = (value) => value === "claude" || value === "codex" || value === "github";
|
|
8997
|
-
var readIfExists = (file) =>
|
|
9094
|
+
var readIfExists = (file) => fs17.existsSync(file) ? fs17.readFileSync(file, "utf8") : null;
|
|
8998
9095
|
var localCredential = (provider) => {
|
|
8999
|
-
const home =
|
|
9096
|
+
const home = os11.homedir();
|
|
9000
9097
|
switch (provider) {
|
|
9001
9098
|
case "codex":
|
|
9002
9099
|
return readIfExists(
|
|
9003
|
-
|
|
9100
|
+
path18.join(process.env["CODEX_HOME"] ?? path18.join(home, ".codex"), "auth.json")
|
|
9004
9101
|
);
|
|
9005
9102
|
case "claude":
|
|
9006
9103
|
return readIfExists(
|
|
9007
|
-
|
|
9008
|
-
process.env["CLAUDE_CONFIG_DIR"] ??
|
|
9104
|
+
path18.join(
|
|
9105
|
+
process.env["CLAUDE_CONFIG_DIR"] ?? path18.join(home, ".claude"),
|
|
9009
9106
|
".credentials.json"
|
|
9010
9107
|
)
|
|
9011
9108
|
);
|
|
9012
9109
|
case "github": {
|
|
9013
|
-
const result =
|
|
9110
|
+
const result = spawnSync6("gh", ["auth", "token"], { encoding: "utf8" });
|
|
9014
9111
|
const token = result.status === 0 ? result.stdout.trim() : "";
|
|
9015
9112
|
return token === "" ? null : token;
|
|
9016
9113
|
}
|
|
@@ -9022,7 +9119,7 @@ var accountLine = (account) => {
|
|
|
9022
9119
|
const identity = pick("login") ?? pick("email") ?? pick("accountEmail") ?? pick("accountId");
|
|
9023
9120
|
const suffix = pick("tokenSuffix");
|
|
9024
9121
|
const facts = [
|
|
9025
|
-
account.status === "active" ? "connected" : account.status,
|
|
9122
|
+
account.status === "active" ? "connected" : account.status === "invalid" ? "reconnect needed \xB7 the provider refused the login" : account.status,
|
|
9026
9123
|
identity,
|
|
9027
9124
|
suffix === null ? null : `\u2026${suffix}`,
|
|
9028
9125
|
`since ${account.connectedAt.slice(0, 10)}`
|
|
@@ -9055,7 +9152,7 @@ var claudeGrantSecret = () => {
|
|
|
9055
9152
|
return read.kind === "missing" ? null : read.secret;
|
|
9056
9153
|
};
|
|
9057
9154
|
var doctorBundle = (config, args) => doctorBundleCommand(args, {
|
|
9058
|
-
defaultDir:
|
|
9155
|
+
defaultDir: path18.join(mendCliHome(), "bundles"),
|
|
9059
9156
|
now: () => /* @__PURE__ */ new Date(),
|
|
9060
9157
|
say: say6,
|
|
9061
9158
|
warn: (line) => process.stderr.write(`${line}
|
|
@@ -9096,74 +9193,69 @@ var staleGrantReason = (secret) => {
|
|
|
9096
9193
|
};
|
|
9097
9194
|
var claudeGrant = async () => {
|
|
9098
9195
|
const cli = claudeCli();
|
|
9099
|
-
const
|
|
9196
|
+
const home = mendCliHome();
|
|
9100
9197
|
const personal = personalClaudeDir();
|
|
9101
9198
|
const personalBefore = grantStatus(cli, personal);
|
|
9102
|
-
|
|
9103
|
-
|
|
9104
|
-
|
|
9105
|
-
|
|
9106
|
-
|
|
9107
|
-
|
|
9108
|
-
|
|
9109
|
-
|
|
9110
|
-
say6(
|
|
9199
|
+
const dir = throwawayLoginDir(home, "claude-login-");
|
|
9200
|
+
try {
|
|
9201
|
+
if (grantStatus(cli, dir) === null) {
|
|
9202
|
+
fail6(
|
|
9203
|
+
"claude: could not run `claude auth status` \u2014 install Claude Code, set MEND_CLAUDE_BIN, or paste a credential: mend connect claude --from-stdin"
|
|
9204
|
+
);
|
|
9205
|
+
return null;
|
|
9206
|
+
}
|
|
9207
|
+
say6(" Mend needs its own Claude login; it is sent to your server and not kept here");
|
|
9111
9208
|
say6(dim6(" your own Claude login stays as it is"));
|
|
9112
9209
|
if (!runClaudeLogin(cli, dir)) {
|
|
9113
9210
|
fail6("claude: the login did not complete");
|
|
9114
9211
|
return null;
|
|
9115
9212
|
}
|
|
9116
|
-
status = grantStatus(cli, dir)
|
|
9117
|
-
if (!status.loggedIn) {
|
|
9213
|
+
const status = grantStatus(cli, dir);
|
|
9214
|
+
if (status === null || !status.loggedIn) {
|
|
9118
9215
|
fail6("claude: the login completed but Claude still reports no grant in Mend's directory");
|
|
9119
9216
|
return null;
|
|
9120
9217
|
}
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
|
|
9218
|
+
if (status.configDirectory !== null && path18.resolve(status.configDirectory) !== path18.resolve(dir)) {
|
|
9219
|
+
fail6(
|
|
9220
|
+
`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`
|
|
9221
|
+
);
|
|
9222
|
+
return null;
|
|
9223
|
+
}
|
|
9224
|
+
const read = readGrant(dir);
|
|
9225
|
+
if (read.kind === "missing") {
|
|
9226
|
+
const where = read.triedService === null ? read.triedPath : `${read.triedPath} or the Keychain item ${read.triedService}`;
|
|
9227
|
+
fail6(`claude: logged in, but no credential to read \u2014 looked in ${where}`);
|
|
9228
|
+
return null;
|
|
9229
|
+
}
|
|
9130
9230
|
const why = staleGrantReason(read.secret);
|
|
9131
9231
|
if (why !== null) {
|
|
9132
|
-
|
|
9133
|
-
|
|
9134
|
-
fail6("claude: the login did not complete");
|
|
9135
|
-
return null;
|
|
9136
|
-
}
|
|
9137
|
-
read = readGrant(dir);
|
|
9138
|
-
const still = read.kind === "missing" ? "missing" : staleGrantReason(read.secret);
|
|
9139
|
-
if (still !== null) {
|
|
9140
|
-
fail6(`claude: the grant is still ${still} after logging in`);
|
|
9141
|
-
return null;
|
|
9142
|
-
}
|
|
9232
|
+
fail6(`claude: the new login is already ${why}`);
|
|
9233
|
+
return null;
|
|
9143
9234
|
}
|
|
9235
|
+
const mine = localCredential("claude");
|
|
9236
|
+
if (mine !== null && sameGrant(read.secret, mine)) {
|
|
9237
|
+
fail6(
|
|
9238
|
+
"claude: that is the same grant this machine's Claude holds, so both sides would race on refresh \u2014 connect it deliberately with --use-my-login"
|
|
9239
|
+
);
|
|
9240
|
+
return null;
|
|
9241
|
+
}
|
|
9242
|
+
const personalAfter = grantStatus(cli, personal);
|
|
9243
|
+
if (personalBefore?.loggedIn === true && personalAfter?.loggedIn === false) {
|
|
9244
|
+
say6(
|
|
9245
|
+
` your own Claude login was signed out by this one \u2014 this account allows one grant at a time. Run \`claude auth login\` to get it back, and connect the shared grant with --use-my-login instead.`
|
|
9246
|
+
);
|
|
9247
|
+
} else if (personalAfter?.loggedIn === true) {
|
|
9248
|
+
say6(dim6(" your own Claude login still works \xB7 verified"));
|
|
9249
|
+
}
|
|
9250
|
+
const kept = claudeGrantDir(home);
|
|
9251
|
+
if (fs17.existsSync(kept)) {
|
|
9252
|
+
forgetGrant(kept);
|
|
9253
|
+
say6(dim6(` removed the copy an older mend kept in ${kept} \xB7 the server refreshes the login`));
|
|
9254
|
+
}
|
|
9255
|
+
return read.secret;
|
|
9256
|
+
} finally {
|
|
9257
|
+
forgetGrant(dir);
|
|
9144
9258
|
}
|
|
9145
|
-
if (read.kind === "missing") {
|
|
9146
|
-
const where = read.triedService === null ? read.triedPath : `${read.triedPath} or the Keychain item ${read.triedService}`;
|
|
9147
|
-
fail6(`claude: logged in, but no credential to read \u2014 looked in ${where}`);
|
|
9148
|
-
return null;
|
|
9149
|
-
}
|
|
9150
|
-
const mine = localCredential("claude");
|
|
9151
|
-
if (mine !== null && sameGrant(read.secret, mine)) {
|
|
9152
|
-
fail6(
|
|
9153
|
-
"claude: that is the same grant this machine's Claude holds, so both sides would race on refresh \u2014 remove Mend's directory and log in again, or accept the race with --use-my-login"
|
|
9154
|
-
);
|
|
9155
|
-
return null;
|
|
9156
|
-
}
|
|
9157
|
-
const personalAfter = grantStatus(cli, personal);
|
|
9158
|
-
if (personalBefore?.loggedIn === true && personalAfter?.loggedIn === false) {
|
|
9159
|
-
say6(
|
|
9160
|
-
` your own Claude login was signed out by this one \u2014 this account allows one grant at a time. Run \`claude auth login\` to get it back, and connect the shared grant with --use-my-login instead.`
|
|
9161
|
-
);
|
|
9162
|
-
} else if (personalAfter?.loggedIn === true) {
|
|
9163
|
-
say6(dim6(" your own Claude login still works \xB7 verified"));
|
|
9164
|
-
}
|
|
9165
|
-
say6(dim6(` grant ${read.kind === "file" ? read.path : `keychain ${read.service}`}`));
|
|
9166
|
-
return read.secret;
|
|
9167
9259
|
};
|
|
9168
9260
|
var connectCommand = async (config, args) => {
|
|
9169
9261
|
const [providerArg, ...flags] = args;
|
|
@@ -9182,8 +9274,17 @@ var connectCommand = async (config, args) => {
|
|
|
9182
9274
|
}
|
|
9183
9275
|
let secret;
|
|
9184
9276
|
if (flags.includes("--from-stdin")) {
|
|
9185
|
-
secret =
|
|
9277
|
+
secret = fs17.readFileSync(0, "utf8").trim();
|
|
9186
9278
|
if (secret === "") return fail6("nothing on stdin");
|
|
9279
|
+
} else if (provider === "codex" && !flags.includes("--use-my-login")) {
|
|
9280
|
+
const grant = codexGrant({
|
|
9281
|
+
cli: codexCli(),
|
|
9282
|
+
personalAuthJson: readIfExists(path18.join(personalCodexHome(), "auth.json")),
|
|
9283
|
+
say: say6,
|
|
9284
|
+
parent: mendCliHome()
|
|
9285
|
+
});
|
|
9286
|
+
if (grant.kind === "failed") return fail6(grant.reason);
|
|
9287
|
+
secret = grant.secret;
|
|
9187
9288
|
} else if (provider === "claude" && !flags.includes("--use-my-login")) {
|
|
9188
9289
|
secret = await claudeGrant();
|
|
9189
9290
|
if (secret === null) return;
|
|
@@ -9193,10 +9294,10 @@ var connectCommand = async (config, args) => {
|
|
|
9193
9294
|
const where = provider === "github" ? "`gh auth login` first, or pipe a token: gh auth token | mend connect github --from-stdin" : provider === "codex" ? "`codex login` first, or: mend connect codex --from-stdin < auth.json" : "`claude auth login` first, or: mend connect claude --from-stdin";
|
|
9194
9295
|
return fail6(`${provider}: no credential on this machine \u2014 ${where}`);
|
|
9195
9296
|
}
|
|
9196
|
-
if (provider === "claude") {
|
|
9297
|
+
if (provider === "claude" || provider === "codex") {
|
|
9197
9298
|
say6(
|
|
9198
9299
|
dim6(
|
|
9199
|
-
" --use-my-login: Mend and this machine will share one
|
|
9300
|
+
" --use-my-login: Mend and this machine will share one login, and whichever refreshes second is signed out"
|
|
9200
9301
|
)
|
|
9201
9302
|
);
|
|
9202
9303
|
}
|
|
@@ -9228,14 +9329,14 @@ var takeFlagValue4 = (args, flag) => {
|
|
|
9228
9329
|
return at !== -1 && args[at + 1] !== void 0 ? String(args[at + 1]) : null;
|
|
9229
9330
|
};
|
|
9230
9331
|
var saveCliConfig = (next) => {
|
|
9231
|
-
|
|
9232
|
-
|
|
9332
|
+
fs17.mkdirSync(path18.dirname(CONFIG_PATH), { recursive: true, mode: 448 });
|
|
9333
|
+
fs17.writeFileSync(
|
|
9233
9334
|
CONFIG_PATH,
|
|
9234
9335
|
`${JSON.stringify({ url: next.url, token: next.token, deviceId: next.deviceId }, null, 2)}
|
|
9235
9336
|
`,
|
|
9236
9337
|
{ mode: 384 }
|
|
9237
9338
|
);
|
|
9238
|
-
|
|
9339
|
+
fs17.chmodSync(CONFIG_PATH, 384);
|
|
9239
9340
|
};
|
|
9240
9341
|
var login = async (config, args) => {
|
|
9241
9342
|
await loginCommand(args, {
|
|
@@ -9248,7 +9349,7 @@ var login = async (config, args) => {
|
|
|
9248
9349
|
});
|
|
9249
9350
|
};
|
|
9250
9351
|
var logout = async (config) => {
|
|
9251
|
-
if (!
|
|
9352
|
+
if (!fs17.existsSync(CONFIG_PATH) && config.token === null) {
|
|
9252
9353
|
say6(dim6("nothing saved \u2014 already signed out"));
|
|
9253
9354
|
return;
|
|
9254
9355
|
}
|
|
@@ -9301,8 +9402,8 @@ var uninstallCommand = async (config, args) => {
|
|
|
9301
9402
|
const runtime = {
|
|
9302
9403
|
server,
|
|
9303
9404
|
cliHome: mendCliHome(),
|
|
9304
|
-
sshConfigFile:
|
|
9305
|
-
signedIn: config.token === null || !
|
|
9405
|
+
sshConfigFile: path18.join(os11.homedir(), ".ssh", "config"),
|
|
9406
|
+
signedIn: config.token === null || !fs17.existsSync(CONFIG_PATH) ? null : { url: config.url, deviceId: config.deviceId },
|
|
9306
9407
|
revokeDevice: async () => {
|
|
9307
9408
|
if (config.deviceId === null) return "no device id saved";
|
|
9308
9409
|
try {
|
|
@@ -9366,7 +9467,7 @@ var keysShare = async (config) => {
|
|
|
9366
9467
|
return fail6("SSH_AUTH_SOCK is not set \u2014 start (or plug in) your ssh-agent first");
|
|
9367
9468
|
}
|
|
9368
9469
|
await shareAgent({
|
|
9369
|
-
url: () => socketUrl(config, "keys-bridge", { host:
|
|
9470
|
+
url: () => socketUrl(config, "keys-bridge", { host: os11.hostname() }),
|
|
9370
9471
|
agentSock,
|
|
9371
9472
|
signal: new AbortController().signal,
|
|
9372
9473
|
onEvent: (event) => {
|
|
@@ -9496,7 +9597,7 @@ ${usageOf("dotfiles repo")}`);
|
|
|
9496
9597
|
var dotfilesSync = async (config, args) => {
|
|
9497
9598
|
const all = args.includes("--all");
|
|
9498
9599
|
const requestedPaths = args.filter((arg) => !arg.startsWith("--"));
|
|
9499
|
-
const home =
|
|
9600
|
+
const home = os11.homedir();
|
|
9500
9601
|
if (!all && requestedPaths.length === 0) {
|
|
9501
9602
|
const found = scanDotfileCandidates(home);
|
|
9502
9603
|
if (found.length === 0) {
|
|
@@ -9523,13 +9624,13 @@ var dotfilesSync = async (config, args) => {
|
|
|
9523
9624
|
if ("error" in read) return fail6(read.error);
|
|
9524
9625
|
const result = await api(config, "POST", "/dotfiles/snapshot", {
|
|
9525
9626
|
files: read.files,
|
|
9526
|
-
source:
|
|
9627
|
+
source: os11.hostname(),
|
|
9527
9628
|
merge: false
|
|
9528
9629
|
});
|
|
9529
9630
|
const snapshot = result.snapshot;
|
|
9530
9631
|
if (snapshot === null) return fail6("the server accepted the sync but reports no snapshot");
|
|
9531
9632
|
say6(
|
|
9532
|
-
`${green6("synced")} ${snapshot.files.length} file${snapshot.files.length === 1 ? "" : "s"} from ${
|
|
9633
|
+
`${green6("synced")} ${snapshot.files.length} file${snapshot.files.length === 1 ? "" : "s"} from ${os11.hostname()} \xB7 ${dim6(snapshot.sha.slice(0, 7))} ${dim6("\u2014 applies from the next session launch")}`
|
|
9533
9634
|
);
|
|
9534
9635
|
};
|
|
9535
9636
|
var envLoad = async (config, args) => {
|
|
@@ -9543,10 +9644,10 @@ var envLoad = async (config, args) => {
|
|
|
9543
9644
|
const positional = args.filter(
|
|
9544
9645
|
(arg, i) => !arg.startsWith("--") && args[i - 1] !== "--project" && !consumed.has(i)
|
|
9545
9646
|
);
|
|
9546
|
-
const file =
|
|
9647
|
+
const file = path18.resolve(positional[0] ?? ".env");
|
|
9547
9648
|
let contents;
|
|
9548
9649
|
try {
|
|
9549
|
-
contents =
|
|
9650
|
+
contents = fs17.readFileSync(file, "utf8");
|
|
9550
9651
|
} catch {
|
|
9551
9652
|
return fail6(`cannot read ${file} \u2014 pass a path: mend env load path/to/.env`);
|
|
9552
9653
|
}
|
|
@@ -9562,10 +9663,10 @@ var envLoad = async (config, args) => {
|
|
|
9562
9663
|
{ contents, allSecret, secretNames }
|
|
9563
9664
|
);
|
|
9564
9665
|
if (report.loaded.length === 0 && report.rejected.length === 0 && report.malformedLines.length === 0) {
|
|
9565
|
-
say6(dim6(`${
|
|
9666
|
+
say6(dim6(`${path18.basename(file)} has no variables`));
|
|
9566
9667
|
return;
|
|
9567
9668
|
}
|
|
9568
|
-
say6(`${green6("\u2713")} loaded ${
|
|
9669
|
+
say6(`${green6("\u2713")} loaded ${path18.basename(file)} into ${project2.name}`);
|
|
9569
9670
|
for (const line of formatLoadReport(report, { dim: dim6, warn: amber2 })) say6(line);
|
|
9570
9671
|
const plaintextUrls = report.loaded.filter(
|
|
9571
9672
|
(entry) => entry.lane === "configuration" && /_(URL|URI|DSN)$/.test(entry.name)
|
|
@@ -9756,7 +9857,7 @@ var skillsList = async (config, args) => {
|
|
|
9756
9857
|
};
|
|
9757
9858
|
var skillsPush = async (config, args) => {
|
|
9758
9859
|
const dirFlag = takeFlagValue4(args, "--dir");
|
|
9759
|
-
const root = dirFlag === null ?
|
|
9860
|
+
const root = dirFlag === null ? path18.join(os11.homedir(), DEFAULT_SKILLS_DIR) : path18.resolve(dirFlag);
|
|
9760
9861
|
const scanned = scanSkillLibrary(root);
|
|
9761
9862
|
if ("error" in scanned) return fail6(scanned.error);
|
|
9762
9863
|
for (const note of scanned.notes) say6(dim6(` ${note.skill}: ${note.message}`));
|
|
@@ -10431,14 +10532,14 @@ var dashboard = async (config, options = {}) => {
|
|
|
10431
10532
|
`the dashboard needs Node >= 26 (node:ffi) \u2014 this is ${process.version}; every other command still works`
|
|
10432
10533
|
);
|
|
10433
10534
|
}
|
|
10434
|
-
const rerun =
|
|
10535
|
+
const rerun = spawnSync6(
|
|
10435
10536
|
process.execPath,
|
|
10436
10537
|
["--experimental-ffi", "--disable-warning=ExperimentalWarning", ...process.argv.slice(1)],
|
|
10437
10538
|
{ stdio: "inherit" }
|
|
10438
10539
|
);
|
|
10439
10540
|
process.exit(rerun.status ?? 0);
|
|
10440
10541
|
}
|
|
10441
|
-
const { runDashboard } = await import("./dashboard-
|
|
10542
|
+
const { runDashboard } = await import("./dashboard-MGR4YUFD.js");
|
|
10442
10543
|
const agentShare = await startShareIfBridge(config);
|
|
10443
10544
|
const tunnels = options.noTunnel === true || serverIsLocal(config) ? null : createServiceTunnels({
|
|
10444
10545
|
listServices: () => fetchServices(config),
|
|
@@ -10472,7 +10573,7 @@ var startShareIfBridge = async (config) => {
|
|
|
10472
10573
|
}
|
|
10473
10574
|
if (access.mode !== "bridge") return null;
|
|
10474
10575
|
return startAgentShare({
|
|
10475
|
-
url: () => socketUrl(config, "keys-bridge", { host:
|
|
10576
|
+
url: () => socketUrl(config, "keys-bridge", { host: os11.hostname() }),
|
|
10476
10577
|
agentSock
|
|
10477
10578
|
});
|
|
10478
10579
|
};
|
|
@@ -10512,15 +10613,16 @@ var manCommand = (words) => {
|
|
|
10512
10613
|
}
|
|
10513
10614
|
const version = cliVersion();
|
|
10514
10615
|
const page = doc === null ? renderManIndex(version) : renderManPage(doc, version);
|
|
10515
|
-
const file =
|
|
10516
|
-
|
|
10517
|
-
const result =
|
|
10518
|
-
|
|
10616
|
+
const file = path18.join(os11.tmpdir(), `mend-man-${process.pid}-${manFileName(doc)}`);
|
|
10617
|
+
fs17.writeFileSync(file, page);
|
|
10618
|
+
const result = spawnSync6("man", ["-l", file], { stdio: "inherit" });
|
|
10619
|
+
fs17.rmSync(file, { force: true });
|
|
10519
10620
|
if (result.error !== void 0 || result.status !== 0) {
|
|
10520
10621
|
say6(doc === null ? renderIndex() : renderCommand(doc));
|
|
10521
10622
|
}
|
|
10522
10623
|
};
|
|
10523
10624
|
var main = async () => {
|
|
10625
|
+
useHttp1();
|
|
10524
10626
|
const [command, ...rest] = process.argv.slice(2);
|
|
10525
10627
|
const ownArgs = rest.slice(0, rest.indexOf("--") === -1 ? rest.length : rest.indexOf("--"));
|
|
10526
10628
|
if (command !== void 0 && command !== "help" && ownArgs.some((a) => a === "--help" || a === "-h")) {
|