@sealant/mend 0.17.0 → 0.19.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/dashboard-model.js +9 -1
- package/dist/dashboard.js +45 -1
- package/dist/help.js +824 -0
- package/dist/main.js +82 -116
- package/dist/version.js +48 -0
- package/man/mend-accounts.1 +11 -0
- package/man/mend-adopt.1 +27 -0
- package/man/mend-attach.1 +13 -0
- package/man/mend-codex.1 +60 -0
- package/man/mend-completions.1 +14 -0
- package/man/mend-connect.1 +25 -0
- package/man/mend-continue.1 +11 -0
- package/man/mend-doctor.1 +11 -0
- package/man/mend-dotfiles-sync.1 +15 -0
- package/man/mend-dotfiles.1 +11 -0
- package/man/mend-env-cluster.1 +26 -0
- package/man/mend-env-load.1 +21 -0
- package/man/mend-env-show.1 +15 -0
- package/man/mend-help.1 +11 -0
- package/man/mend-keys-init.1 +11 -0
- package/man/mend-keys-share.1 +11 -0
- package/man/mend-keys-show.1 +11 -0
- package/man/mend-login.1 +20 -0
- package/man/mend-logout.1 +11 -0
- package/man/mend-man.1 +11 -0
- package/man/mend-pair.1 +15 -0
- package/man/mend-projects.1 +11 -0
- package/man/mend-refresh.1 +11 -0
- package/man/mend-rejoin.1 +15 -0
- package/man/mend-resume.1 +15 -0
- package/man/mend-run.1 +18 -0
- package/man/mend-service-add.1 +21 -0
- package/man/mend-service-connect.1 +15 -0
- package/man/mend-service-init.1 +15 -0
- package/man/mend-service-list.1 +11 -0
- package/man/mend-service-logs.1 +15 -0
- package/man/mend-service-restart.1 +11 -0
- package/man/mend-service-run.1 +38 -0
- package/man/mend-service-stop.1 +11 -0
- package/man/mend-sessions.1 +27 -0
- package/man/mend-shell.1 +13 -0
- package/man/mend-skills-push.1 +21 -0
- package/man/mend-skills.1 +15 -0
- package/man/mend-ssh-setup.1 +15 -0
- package/man/mend-ssh.1 +11 -0
- package/man/mend-stop.1 +22 -0
- package/man/mend-ui.1 +13 -0
- package/man/mend-version.1 +11 -0
- package/man/mend-worktrees.1 +18 -0
- package/man/mend.1 +165 -0
- package/package.json +8 -3
package/dist/main.js
CHANGED
|
@@ -8,12 +8,14 @@ import * as path from "node:path";
|
|
|
8
8
|
import { doctorCommand } from "./doctor.js";
|
|
9
9
|
import { readSyncFiles, scanDotfileCandidates } from "./dotfiles.js";
|
|
10
10
|
import { formatLoadReport } from "./env.js";
|
|
11
|
+
import { findCommand, manFileName, renderCommand, renderGroup, renderIndex, renderManIndex, renderManPage, usageOf, } from "./help.js";
|
|
11
12
|
import { loginCommand } from "./login.js";
|
|
12
13
|
import { pairCommand, qrCommand } from "./pair.js";
|
|
13
14
|
import { isComposeFile, proposeFromCompose, proposeFromPackageJson, proposeFromWorkspacePackage, renderMendToml, workspaceGlobs, } from "./service-init.js";
|
|
14
15
|
import { agentIsLive, agentOutcome, cwdFacts, gitTopLevel, HARNESS_COMMANDS, isDetachChunk, LIVE_STATUSES, sessionDisplayName, matchProjectByCwd, normalizeProjectName, gitCurrentBranch, parseLaunchArgs, } from "./shared.js";
|
|
15
16
|
import { DEFAULT_SKILLS_DIR, scanSkillLibrary } from "./skills.js";
|
|
16
17
|
import { sshCommand } from "./ssh-setup.js";
|
|
18
|
+
import { cliVersion, fetchServerVersion, versionLines } from "./version.js";
|
|
17
19
|
// `$XDG_CONFIG_HOME/mend`, default `~/.config/mend`; a pre-XDG `~/.mend` stays authoritative
|
|
18
20
|
// when it is the only one present (mirrors @mend/store's resolver — the CLI stays dependency-light).
|
|
19
21
|
const mendCliHome = () => {
|
|
@@ -270,14 +272,14 @@ const launch = async (config, harness, args) => {
|
|
|
270
272
|
parsed.ask ||
|
|
271
273
|
parsed.fast;
|
|
272
274
|
if (harness === "run" && structured) {
|
|
273
|
-
return fail(
|
|
275
|
+
return fail(`mend run takes no prompt or harness flags · ${usageOf("run")}`);
|
|
274
276
|
}
|
|
275
277
|
if (harness === "run" && (parsed.detach || parsed.foreground)) {
|
|
276
278
|
return fail("mend run takes no lifecycle flags — it tails the record; Ctrl+C stops watching, not the command");
|
|
277
279
|
}
|
|
278
280
|
const argv = harness === "run" ? parsed.custom : (HARNESS_COMMANDS[harness] ?? []);
|
|
279
281
|
if (argv.length === 0) {
|
|
280
|
-
return fail(harness === "run" ? "
|
|
282
|
+
return fail(harness === "run" ? usageOf("run") : `unknown harness ${harness}`);
|
|
281
283
|
}
|
|
282
284
|
const project = await findProject(config, parsed.project, true);
|
|
283
285
|
// Say which project the cwd resolved to before anything is created — a
|
|
@@ -673,7 +675,7 @@ const stopCommand = async (config, args) => {
|
|
|
673
675
|
return;
|
|
674
676
|
}
|
|
675
677
|
if (!all && prefix === undefined) {
|
|
676
|
-
return fail("
|
|
678
|
+
return fail(usageOf("stop"));
|
|
677
679
|
}
|
|
678
680
|
const [sessions, projects] = await Promise.all([
|
|
679
681
|
api(config, "GET", "/sessions"),
|
|
@@ -902,7 +904,7 @@ const serviceAdd = async (config, args) => {
|
|
|
902
904
|
const positional = args.filter((a, i) => !a.startsWith("--") && (nameFlag === -1 || i !== nameFlag + 1));
|
|
903
905
|
const portRaw = positional.find((a) => /^\d+$/.test(a));
|
|
904
906
|
if (portRaw === undefined) {
|
|
905
|
-
return fail("
|
|
907
|
+
return fail(usageOf("service add"));
|
|
906
908
|
}
|
|
907
909
|
const port = Number(portRaw);
|
|
908
910
|
if (!Number.isInteger(port) || port < 1 || port > 65535) {
|
|
@@ -937,7 +939,7 @@ const serviceList = async (config) => {
|
|
|
937
939
|
const serviceStop = async (config, args) => {
|
|
938
940
|
const needle = args.find((a) => !a.startsWith("--"));
|
|
939
941
|
if (needle === undefined)
|
|
940
|
-
return fail("
|
|
942
|
+
return fail(usageOf("service stop"));
|
|
941
943
|
const services = await fetchServices(config);
|
|
942
944
|
const matches = services.filter((service) => service.label === needle || service.id.startsWith(needle));
|
|
943
945
|
if (matches.length === 0)
|
|
@@ -980,8 +982,7 @@ const autoConnect = async (config, service) => {
|
|
|
980
982
|
};
|
|
981
983
|
const serviceRun = async (config, args) => {
|
|
982
984
|
const dashdash = args.indexOf("--");
|
|
983
|
-
const usage = "
|
|
984
|
-
" mend service run [session] <name> [--no-connect] (a declared recipe)";
|
|
985
|
+
const usage = usageOf("service run");
|
|
985
986
|
// No explicit command = a DECLARED Service: resolve the name against the
|
|
986
987
|
// session worktree's mend.toml and start (or adopt) its recipe.
|
|
987
988
|
if (dashdash === -1) {
|
|
@@ -1056,7 +1057,7 @@ const serviceLogs = async (config, args) => {
|
|
|
1056
1057
|
const from = fromFlag === -1 ? "0" : args[fromFlag + 1];
|
|
1057
1058
|
const needle = args.find((argument, index) => !argument.startsWith("--") && index !== fromFlag + 1);
|
|
1058
1059
|
if (needle === undefined || from === undefined || !/^(0|[1-9]\d*)$/.test(from)) {
|
|
1059
|
-
return fail("
|
|
1060
|
+
return fail(usageOf("service logs"));
|
|
1060
1061
|
}
|
|
1061
1062
|
const everything = await fetchServiceViews(config, true);
|
|
1062
1063
|
const matches = everything.filter((view) => view.service.name === needle ||
|
|
@@ -1112,7 +1113,7 @@ const serviceLogs = async (config, args) => {
|
|
|
1112
1113
|
const serviceRestart = async (config, args) => {
|
|
1113
1114
|
const needle = args.find((a) => !a.startsWith("--"));
|
|
1114
1115
|
if (needle === undefined)
|
|
1115
|
-
return fail("
|
|
1116
|
+
return fail(usageOf("service restart"));
|
|
1116
1117
|
const service = await findLiveService(config, needle);
|
|
1117
1118
|
const restarted = await withSpinner(`restarting ${service.label ?? service.id.slice(0, 8)}…`, mutateService(config, "POST", `/services/${service.id}/restart`));
|
|
1118
1119
|
say(`${green("✓")} restarted · ${restarted.status}`);
|
|
@@ -1358,7 +1359,7 @@ const accountsCommand = async (config) => {
|
|
|
1358
1359
|
const connectCommand = async (config, args) => {
|
|
1359
1360
|
const [providerArg, ...flags] = args;
|
|
1360
1361
|
if (!isProvider(providerArg)) {
|
|
1361
|
-
return fail("
|
|
1362
|
+
return fail(usageOf("connect"));
|
|
1362
1363
|
}
|
|
1363
1364
|
const provider = providerArg;
|
|
1364
1365
|
if (flags.includes("--remove")) {
|
|
@@ -1591,7 +1592,7 @@ const keysCommand = async (config, args) => {
|
|
|
1591
1592
|
case undefined:
|
|
1592
1593
|
return keysShow(config);
|
|
1593
1594
|
default:
|
|
1594
|
-
return fail(`unknown keys command "${verb}"
|
|
1595
|
+
return fail(`unknown keys command "${verb}" · mend help keys lists them`);
|
|
1595
1596
|
}
|
|
1596
1597
|
};
|
|
1597
1598
|
const formatDotfileBytes = (bytes) => bytes < 1024 ? `${bytes} B` : `${(bytes / 1024).toFixed(1)} KB`;
|
|
@@ -1754,14 +1755,14 @@ const envCluster = async (config, args) => {
|
|
|
1754
1755
|
const explicitProject = takeFlagValue(args, "--project");
|
|
1755
1756
|
const positional = args.filter((arg, i) => !arg.startsWith("--") && args[i - 1] !== "--project");
|
|
1756
1757
|
const [verb, ...rest] = positional;
|
|
1757
|
-
const usage = "
|
|
1758
|
+
const usage = usageOf("env cluster");
|
|
1758
1759
|
const project = await findProject(config, explicitProject);
|
|
1759
1760
|
const route = `/projects/${project.id}/cluster-bindings`;
|
|
1760
1761
|
const appliesLine = () => say(dim(" applies from the next workspace launch; running workspaces keep what they started with"));
|
|
1761
1762
|
if (verb === "add") {
|
|
1762
1763
|
const [kind, objectName] = rest;
|
|
1763
1764
|
if ((kind !== "secret" && kind !== "configmap") || objectName === undefined) {
|
|
1764
|
-
return fail(`env cluster add takes a kind and an object name
|
|
1765
|
+
return fail(`env cluster add takes a kind and an object name\n${usage}`);
|
|
1765
1766
|
}
|
|
1766
1767
|
const result = await api(config, "POST", route, {
|
|
1767
1768
|
kind,
|
|
@@ -1775,7 +1776,7 @@ const envCluster = async (config, args) => {
|
|
|
1775
1776
|
const [ref] = rest;
|
|
1776
1777
|
const [kind, objectName] = ref?.split("/", 2) ?? [];
|
|
1777
1778
|
if ((kind !== "secret" && kind !== "configmap") || objectName === undefined) {
|
|
1778
|
-
return fail(`env cluster remove takes <kind>/<name>, e.g. secret/app-env
|
|
1779
|
+
return fail(`env cluster remove takes <kind>/<name>, e.g. secret/app-env\n${usage}`);
|
|
1779
1780
|
}
|
|
1780
1781
|
const snapshot = await api(config, "GET", route);
|
|
1781
1782
|
const binding = snapshot.bindings.find((b) => b.kind === kind && b.objectName === objectName);
|
|
@@ -1789,7 +1790,7 @@ const envCluster = async (config, args) => {
|
|
|
1789
1790
|
const clear = args.includes("--clear");
|
|
1790
1791
|
const [name] = rest;
|
|
1791
1792
|
if (!clear && name === undefined)
|
|
1792
|
-
return fail(`env cluster sa takes a name or --clear
|
|
1793
|
+
return fail(`env cluster sa takes a name or --clear\n${usage}`);
|
|
1793
1794
|
const result = await api(config, "PUT", `${route}/service-account`, { serviceAccount: clear ? null : name });
|
|
1794
1795
|
say(result.serviceAccount === null
|
|
1795
1796
|
? `${green("✓")} cleared the workspace service account ${dim(`· cluster r${result.revision}`)}`
|
|
@@ -1799,7 +1800,7 @@ const envCluster = async (config, args) => {
|
|
|
1799
1800
|
}
|
|
1800
1801
|
return appliesLine();
|
|
1801
1802
|
}
|
|
1802
|
-
return fail(`unknown env cluster command "${verb ?? ""}"
|
|
1803
|
+
return fail(`unknown env cluster command "${verb ?? ""}"\n${usage}`);
|
|
1803
1804
|
};
|
|
1804
1805
|
const envCommand = async (config, args) => {
|
|
1805
1806
|
const [verb, ...rest] = args;
|
|
@@ -1812,7 +1813,7 @@ const envCommand = async (config, args) => {
|
|
|
1812
1813
|
case undefined:
|
|
1813
1814
|
return envShow(config, rest);
|
|
1814
1815
|
default:
|
|
1815
|
-
return fail(`unknown env command "${verb}"
|
|
1816
|
+
return fail(`unknown env command "${verb}" · mend help env lists them`);
|
|
1816
1817
|
}
|
|
1817
1818
|
};
|
|
1818
1819
|
const dotfilesCommand = async (config, args) => {
|
|
@@ -1824,7 +1825,7 @@ const dotfilesCommand = async (config, args) => {
|
|
|
1824
1825
|
case undefined:
|
|
1825
1826
|
return dotfilesShow(config);
|
|
1826
1827
|
default:
|
|
1827
|
-
return fail(`unknown dotfiles command "${verb}"
|
|
1828
|
+
return fail(`unknown dotfiles command "${verb}" · mend help dotfiles lists them`);
|
|
1828
1829
|
}
|
|
1829
1830
|
};
|
|
1830
1831
|
/** With `--project` the project's library; bare, your own. */
|
|
@@ -1893,7 +1894,7 @@ const skillsCommand = async (config, args) => {
|
|
|
1893
1894
|
// Bare flags (`mend skills --project web`) read as the list.
|
|
1894
1895
|
if (verb.startsWith("--"))
|
|
1895
1896
|
return skillsList(config, args);
|
|
1896
|
-
return fail(`unknown skills command "${verb}"
|
|
1897
|
+
return fail(`unknown skills command "${verb}" · mend help skills lists them`);
|
|
1897
1898
|
}
|
|
1898
1899
|
};
|
|
1899
1900
|
// ─── completions: live sessions under TAB ───────────────────────────────────
|
|
@@ -1978,7 +1979,7 @@ const completionsCommand = (args) => {
|
|
|
1978
1979
|
process.stdout.write(BASH_COMPLETIONS);
|
|
1979
1980
|
return;
|
|
1980
1981
|
default:
|
|
1981
|
-
return fail(
|
|
1982
|
+
return fail(`${usageOf("completions")} · e.g. mend completions zsh > "$fpath[1]/_mend"`);
|
|
1982
1983
|
}
|
|
1983
1984
|
};
|
|
1984
1985
|
// ─── supervised run: platform workspace + PTY + record ──────────────────────
|
|
@@ -2327,7 +2328,9 @@ const sessionsCommand = async (config, args) => {
|
|
|
2327
2328
|
}
|
|
2328
2329
|
let rows;
|
|
2329
2330
|
if (all || projectName !== null) {
|
|
2330
|
-
|
|
2331
|
+
// --all means all: dead ends (settled, no conversation) included, which the server hides
|
|
2332
|
+
// otherwise for every client.
|
|
2333
|
+
const details = await Promise.all(scope.map((p) => api(config, "GET", `/projects/${p.id}${all ? "?deadEnds=include" : ""}`)));
|
|
2331
2334
|
rows = details.flatMap((detail) => detail.sessions.map((session) => ({
|
|
2332
2335
|
session,
|
|
2333
2336
|
projectName: detail.project.name,
|
|
@@ -2519,7 +2522,7 @@ const hasNodeFfi = () => {
|
|
|
2519
2522
|
*/
|
|
2520
2523
|
const dashboard = async (config) => {
|
|
2521
2524
|
if (process.stdout.isTTY !== true) {
|
|
2522
|
-
say(
|
|
2525
|
+
say(renderIndex());
|
|
2523
2526
|
return;
|
|
2524
2527
|
}
|
|
2525
2528
|
if (!hasNodeFfi()) {
|
|
@@ -2540,99 +2543,54 @@ const dashboard = async (config) => {
|
|
|
2540
2543
|
});
|
|
2541
2544
|
};
|
|
2542
2545
|
// ─── entry ──────────────────────────────────────────────────────────────────
|
|
2543
|
-
|
|
2544
|
-
|
|
2545
|
-
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
|
|
2577
|
-
|
|
2578
|
-
|
|
2579
|
-
|
|
2580
|
-
|
|
2581
|
-
named ones, e.g. DATABASE_URL) to secrets
|
|
2582
|
-
mend env show what the project store holds — names only, never values
|
|
2583
|
-
mend accounts your connected accounts on the platform (claude, codex, github)
|
|
2584
|
-
mend dotfiles your dotfiles on the server: repo + synced home files
|
|
2585
|
-
mend dotfiles sync [--all | paths…] capture config files from THIS machine into your store
|
|
2586
|
-
mend skills [--project [p]] your skill library on the server (or a project's)
|
|
2587
|
-
mend skills push [--project [p]] [--prune] [--dir <path>]
|
|
2588
|
-
upload ~/.agents/skills bundles into the library; sessions
|
|
2589
|
-
receive them at launch (--prune removes what's gone)
|
|
2590
|
-
mend keys share relay THIS machine's ssh-agent to the server (bridge mode:
|
|
2591
|
-
hardware keys sign here; Ctrl-C stops sharing)
|
|
2592
|
-
mend run -- <command...> same, with an arbitrary command
|
|
2593
|
-
mend worktrees [--project <p>] [--json]
|
|
2594
|
-
every worktree and the sessions inside it (mend sessions
|
|
2595
|
-
--json stays the v1 flat list; --json=v2 groups like this)
|
|
2596
|
-
mend attach [session-id-prefix] reattach this terminal to a running session
|
|
2597
|
-
(no id: pick one from the live sessions)
|
|
2598
|
-
mend stop [session-id-prefix] | --all [--project <p>]
|
|
2599
|
-
stop the agent — the workspace harvests and closes; the
|
|
2600
|
-
record and review remain
|
|
2601
|
-
mend shell [session-id-prefix] open a shell in a live session's workspace
|
|
2602
|
-
mend service run [session] --port <p> [--name <n>] [--udp] -- <command...>
|
|
2603
|
-
start + supervise a server in the session workspace
|
|
2604
|
-
mend service run [session] <name> start a declared Service (mend.toml recipe)
|
|
2605
|
-
mend service <name> shorthand for the above
|
|
2606
|
-
mend service init [--yes] scaffold mend.toml from package.json + compose ports
|
|
2607
|
-
mend service add [session] <port> [--name <n>] [--udp]
|
|
2608
|
-
adopt a listening workspace port — reachable on this machine
|
|
2609
|
-
mend service connect [name…] [--port <p>]
|
|
2610
|
-
bring live Services to THIS machine's loopback — each
|
|
2611
|
-
connection tunnels through the server, authenticated as you
|
|
2612
|
-
mend service list every live service and its observed state
|
|
2613
|
-
mend service logs <name-or-id> follow a supervised service's output (replay, then live)
|
|
2614
|
-
mend service restart <name-or-id> re-run its recorded command — same URL
|
|
2615
|
-
mend service stop <name-or-id> stop a service (closes its host port)
|
|
2616
|
-
mend continue [session-id] resume a session with its pending review follow-up
|
|
2617
|
-
mend resume [session-id] [--with h] rejoin a settled session (state restored; --with switches harness)
|
|
2618
|
-
mend rejoin [session-id] [--harness h] attach if live, otherwise resume; newest live wins
|
|
2619
|
-
mend refresh [project] fetch origin's branches into the store — new sessions
|
|
2620
|
-
base on current tips (default: the cwd's project)
|
|
2621
|
-
mend projects adopted projects and their live sessions
|
|
2622
|
-
mend sessions [--all] [--project p] [--json]
|
|
2623
|
-
sessions with review facts; JSON is stable for integrations
|
|
2624
|
-
mend status active sessions (alias of mend sessions)
|
|
2625
|
-
mend ssh workspace SSH status: gateway, registered keys, ssh config
|
|
2626
|
-
mend ssh setup [--key <path>] make this machine ready once: offer a key (ssh-agent
|
|
2627
|
-
preferred — nothing new created), write Host mend-ws
|
|
2628
|
-
mend completions zsh|bash print the TAB-completion hook (live session ids under TAB)
|
|
2629
|
-
|
|
2630
|
-
server: MEND_URL (default http://localhost:3105) · auth: MEND_TOKEN
|
|
2631
|
-
detach key: Ctrl+] (set MEND_DETACH_KEY=none when an outer multiplexer owns detaching)
|
|
2632
|
-
config file: ~/.config/mend/cli.json { "url": ..., "token": ..., "deviceId": ... }
|
|
2633
|
-
`;
|
|
2546
|
+
/** `mend help [command...]`: the index, a group, or one page. */
|
|
2547
|
+
const helpCommand = (words) => {
|
|
2548
|
+
if (words.length === 0) {
|
|
2549
|
+
say(renderIndex());
|
|
2550
|
+
return;
|
|
2551
|
+
}
|
|
2552
|
+
const doc = findCommand(words);
|
|
2553
|
+
if (doc !== null && (doc.name.split(" ").length > 1 || words.length === 1)) {
|
|
2554
|
+
say(renderCommand(doc));
|
|
2555
|
+
return;
|
|
2556
|
+
}
|
|
2557
|
+
const group = renderGroup(words[0] ?? "");
|
|
2558
|
+
if (doc !== null) {
|
|
2559
|
+
say(renderCommand(doc));
|
|
2560
|
+
return;
|
|
2561
|
+
}
|
|
2562
|
+
if (group !== null) {
|
|
2563
|
+
say(group);
|
|
2564
|
+
return;
|
|
2565
|
+
}
|
|
2566
|
+
return fail(`no command "${words.join(" ")}" · mend help lists them`);
|
|
2567
|
+
};
|
|
2568
|
+
/** `mend man [command...]`: the same page through man(1), or the text page without it. */
|
|
2569
|
+
const manCommand = (words) => {
|
|
2570
|
+
const doc = words.length === 0 ? null : findCommand(words);
|
|
2571
|
+
if (words.length > 0 && doc === null) {
|
|
2572
|
+
return fail(`no command "${words.join(" ")}" · mend help lists them`);
|
|
2573
|
+
}
|
|
2574
|
+
const version = cliVersion();
|
|
2575
|
+
const page = doc === null ? renderManIndex(version) : renderManPage(doc, version);
|
|
2576
|
+
const file = path.join(os.tmpdir(), `mend-man-${process.pid}-${manFileName(doc)}`);
|
|
2577
|
+
fs.writeFileSync(file, page);
|
|
2578
|
+
const result = spawnSync("man", ["-l", file], { stdio: "inherit" });
|
|
2579
|
+
fs.rmSync(file, { force: true });
|
|
2580
|
+
if (result.error !== undefined || result.status !== 0) {
|
|
2581
|
+
say(doc === null ? renderIndex() : renderCommand(doc));
|
|
2582
|
+
}
|
|
2583
|
+
};
|
|
2634
2584
|
const main = async () => {
|
|
2635
2585
|
const [command, ...rest] = process.argv.slice(2);
|
|
2586
|
+
// `mend <command> --help` (or -h) before the separator is that command's page,
|
|
2587
|
+
// never an argument: `mend run -- cmd --help` keeps its --help for cmd.
|
|
2588
|
+
const ownArgs = rest.slice(0, rest.indexOf("--") === -1 ? rest.length : rest.indexOf("--"));
|
|
2589
|
+
if (command !== undefined &&
|
|
2590
|
+
command !== "help" &&
|
|
2591
|
+
ownArgs.some((a) => a === "--help" || a === "-h")) {
|
|
2592
|
+
return helpCommand([command, ...ownArgs.filter((a) => !a.startsWith("-"))]);
|
|
2593
|
+
}
|
|
2636
2594
|
const config = loadConfig();
|
|
2637
2595
|
switch (command) {
|
|
2638
2596
|
case "adopt":
|
|
@@ -2666,7 +2624,7 @@ const main = async () => {
|
|
|
2666
2624
|
return connectCommand(config, rest);
|
|
2667
2625
|
case "pair":
|
|
2668
2626
|
return pairCommand(rest, boundApi(config));
|
|
2669
|
-
//
|
|
2627
|
+
// Hidden in the catalog: the installer renders its own QR through this.
|
|
2670
2628
|
case "qr":
|
|
2671
2629
|
return qrCommand(rest);
|
|
2672
2630
|
case "doctor":
|
|
@@ -2699,10 +2657,18 @@ const main = async () => {
|
|
|
2699
2657
|
return dashboard(config);
|
|
2700
2658
|
case "help":
|
|
2701
2659
|
case "--help":
|
|
2702
|
-
|
|
2660
|
+
case "-h":
|
|
2661
|
+
return helpCommand(rest);
|
|
2662
|
+
case "man":
|
|
2663
|
+
return manCommand(rest);
|
|
2664
|
+
case "version":
|
|
2665
|
+
case "--version":
|
|
2666
|
+
case "-v":
|
|
2667
|
+
for (const line of versionLines(cliVersion(), await fetchServerVersion(config.url)))
|
|
2668
|
+
say(line);
|
|
2703
2669
|
return;
|
|
2704
2670
|
default:
|
|
2705
|
-
return fail(`unknown command "${command}"
|
|
2671
|
+
return fail(`unknown command "${command}" · mend help lists them`);
|
|
2706
2672
|
}
|
|
2707
2673
|
};
|
|
2708
2674
|
await main();
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
/**
|
|
3
|
+
* `mend version`: this CLI's version, then the server's when it answers.
|
|
4
|
+
* The two drift apart in practice — an npm install on one machine, a cluster
|
|
5
|
+
* roll on another — and "which one am I on" is the first question any bug
|
|
6
|
+
* report needs answered. The server line is best-effort: a missing server is
|
|
7
|
+
* a fact to print, never an error.
|
|
8
|
+
*/
|
|
9
|
+
/** The published version, read from the package this file ships in (dist/ and src/ sit one level under it). */
|
|
10
|
+
export const cliVersion = () => {
|
|
11
|
+
const raw = fs.readFileSync(new URL("../package.json", import.meta.url), "utf8");
|
|
12
|
+
const parsed = JSON.parse(raw);
|
|
13
|
+
if (typeof parsed === "object" && parsed !== null && "version" in parsed) {
|
|
14
|
+
const version = parsed.version;
|
|
15
|
+
if (typeof version === "string")
|
|
16
|
+
return version;
|
|
17
|
+
}
|
|
18
|
+
return "unknown";
|
|
19
|
+
};
|
|
20
|
+
/** Ask the server's health route, briefly; a slow or absent server is `null`. */
|
|
21
|
+
export const fetchServerVersion = async (url, timeoutMs = 2_000) => {
|
|
22
|
+
try {
|
|
23
|
+
const response = await fetch(`${url}/api/health`, {
|
|
24
|
+
signal: AbortSignal.timeout(timeoutMs),
|
|
25
|
+
});
|
|
26
|
+
if (!response.ok)
|
|
27
|
+
return { url, version: null };
|
|
28
|
+
const body = await response.json();
|
|
29
|
+
const version = typeof body === "object" && body !== null && "version" in body ? body.version : null;
|
|
30
|
+
return { url, version: typeof version === "string" ? version : null };
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return { url, version: null };
|
|
34
|
+
}
|
|
35
|
+
};
|
|
36
|
+
/** The lines `mend version` prints — terse facts, no verdict beyond the mismatch note. */
|
|
37
|
+
export const versionLines = (cli, server) => {
|
|
38
|
+
const lines = [`mend ${cli}`];
|
|
39
|
+
if (server.version === null) {
|
|
40
|
+
lines.push(`server · unreachable · ${server.url}`);
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
lines.push(`server ${server.version} · ${server.url}`);
|
|
44
|
+
if (server.version !== cli)
|
|
45
|
+
lines.push("versions differ — the server's API wins");
|
|
46
|
+
}
|
|
47
|
+
return lines;
|
|
48
|
+
};
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
.TH MEND-ACCOUNTS 1 "" "mend 0.19.0" "mend manual"
|
|
2
|
+
.SH NAME
|
|
3
|
+
mend\-accounts \- your connected accounts on the platform
|
|
4
|
+
.SH SYNOPSIS
|
|
5
|
+
.B mend accounts
|
|
6
|
+
.br
|
|
7
|
+
.SH DESCRIPTION
|
|
8
|
+
.PP
|
|
9
|
+
claude, codex, and github: connected, invalid, or archived, and when each was last used.
|
|
10
|
+
.SH SEE ALSO
|
|
11
|
+
mend(1), mend\-connect(1)
|
package/man/mend-adopt.1
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
.TH MEND-ADOPT 1 "" "mend 0.19.0" "mend manual"
|
|
2
|
+
.SH NAME
|
|
3
|
+
mend\-adopt \- adopt a repository into the store
|
|
4
|
+
.SH SYNOPSIS
|
|
5
|
+
.B mend adopt [source] [\-\-name <name>] [\-\-auth ambient|mend\-key|bridge]
|
|
6
|
+
.br
|
|
7
|
+
.SH DESCRIPTION
|
|
8
|
+
.PP
|
|
9
|
+
Clones the repository into Mend's store. Every session then gets its own worktree of it. The default source is the current directory; any git URL works, GitHub, GitLab, self\-hosted, ssh://, or a local path.
|
|
10
|
+
.PP
|
|
11
|
+
\-\-auth says how the store fetches from the remote. ambient uses the server's own credentials. mend\-key uses this machine's deploy key (see mend keys). bridge relays this machine's ssh\-agent while mend keys share runs, so hardware keys stay here.
|
|
12
|
+
.SH OPTIONS
|
|
13
|
+
.TP
|
|
14
|
+
.B \-\-name <name>
|
|
15
|
+
the project's name in Mend. Default: the repository's
|
|
16
|
+
.TP
|
|
17
|
+
.B \-\-auth <mode>
|
|
18
|
+
ambient, mend\-key, or bridge. Default: ambient
|
|
19
|
+
.SH EXAMPLES
|
|
20
|
+
.PP
|
|
21
|
+
.B mend adopt
|
|
22
|
+
.br
|
|
23
|
+
the repository you are standing in
|
|
24
|
+
.PP
|
|
25
|
+
.B mend adopt git@github.com:acme/api.git \-\-auth mend\-key
|
|
26
|
+
.SH SEE ALSO
|
|
27
|
+
mend(1), mend\-keys\-init(1), mend\-keys\-share(1), mend\-refresh(1)
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
.TH MEND-ATTACH 1 "" "mend 0.19.0" "mend manual"
|
|
2
|
+
.SH NAME
|
|
3
|
+
mend\-attach \- reattach this terminal to a running session
|
|
4
|
+
.SH SYNOPSIS
|
|
5
|
+
.B mend attach [session\-id\-prefix]
|
|
6
|
+
.br
|
|
7
|
+
.SH DESCRIPTION
|
|
8
|
+
.PP
|
|
9
|
+
With no id, the one live session is taken; with several, a picker opens. A prefix of the id is enough.
|
|
10
|
+
.PP
|
|
11
|
+
A session that was picked up on the phone is taken back into this terminal: the phone's agent ends and the same conversation continues here.
|
|
12
|
+
.SH SEE ALSO
|
|
13
|
+
mend(1), mend\-stop(1), mend\-rejoin(1), mend\-shell(1)
|
package/man/mend-codex.1
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
.TH MEND-CODEX 1 "" "mend 0.19.0" "mend manual"
|
|
2
|
+
.SH NAME
|
|
3
|
+
mend\-codex \- launch codex, claude, or opencode in a recorded worktree
|
|
4
|
+
.SH SYNOPSIS
|
|
5
|
+
.B mend codex ["prompt"] [\-\-name <worktree>] [\-\-worktree <existing>] [\-\-model <id>] [\-\-effort <level>] [\-\-base <ref>] [\-\-ask] [\-\-fast] [\-\-detach|\-d] [\-\-foreground] [\-\-project <p>]
|
|
6
|
+
.br
|
|
7
|
+
also mend claude, mend opencode, with the same options
|
|
8
|
+
.SH DESCRIPTION
|
|
9
|
+
.PP
|
|
10
|
+
mend codex, mend claude, and mend opencode are the same command with a different harness. The session runs in a workspace on the platform, in its own git worktree, and everything it does is recorded. This terminal attaches to it.
|
|
11
|
+
.PP
|
|
12
|
+
The worktree's name is asked first. \-\-name skips the ask; an existing name joins that worktree as a new session. \-\-worktree joins only and fails if the name is unknown. A quoted prompt becomes the first message.
|
|
13
|
+
.PP
|
|
14
|
+
Detach with Ctrl+] and the session keeps running. Reattach from any terminal with mend attach, or from the phone.
|
|
15
|
+
.SH OPTIONS
|
|
16
|
+
.TP
|
|
17
|
+
.B \-\-name <worktree>
|
|
18
|
+
name the worktree; an existing name joins it
|
|
19
|
+
.TP
|
|
20
|
+
.B \-\-worktree <existing>
|
|
21
|
+
join an existing worktree only
|
|
22
|
+
.TP
|
|
23
|
+
.B \-\-model <id>
|
|
24
|
+
the harness's model id
|
|
25
|
+
.TP
|
|
26
|
+
.B \-\-effort <level>
|
|
27
|
+
low, medium, high, xhigh, or max
|
|
28
|
+
.TP
|
|
29
|
+
.B \-\-base <ref>
|
|
30
|
+
the branch or sha the worktree starts from
|
|
31
|
+
.TP
|
|
32
|
+
.B \-\-ask
|
|
33
|
+
keep the harness's permission prompts
|
|
34
|
+
.TP
|
|
35
|
+
.B \-\-fast
|
|
36
|
+
priority processing where the harness offers it (codex)
|
|
37
|
+
.TP
|
|
38
|
+
.B \-\-detach, \-d
|
|
39
|
+
launch without attaching
|
|
40
|
+
.TP
|
|
41
|
+
.B \-\-foreground
|
|
42
|
+
stop the session when this CLI exits
|
|
43
|
+
.TP
|
|
44
|
+
.B \-\-project <p>
|
|
45
|
+
the project by name, when the current directory is not inside it
|
|
46
|
+
.SH EXAMPLES
|
|
47
|
+
.PP
|
|
48
|
+
.B mend claude "add a health endpoint"
|
|
49
|
+
.br
|
|
50
|
+
asks for a worktree name, then runs
|
|
51
|
+
.PP
|
|
52
|
+
.B mend codex \-\-name auth\-rework \-d
|
|
53
|
+
.br
|
|
54
|
+
start in the background
|
|
55
|
+
.PP
|
|
56
|
+
.B mend codex \-\-worktree auth\-rework
|
|
57
|
+
.br
|
|
58
|
+
a second session in the same worktree
|
|
59
|
+
.SH SEE ALSO
|
|
60
|
+
mend(1), mend\-attach(1), mend\-stop(1), mend\-run(1), mend\-sessions(1)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
.TH MEND-COMPLETIONS 1 "" "mend 0.19.0" "mend manual"
|
|
2
|
+
.SH NAME
|
|
3
|
+
mend\-completions \- print the TAB\-completion hook
|
|
4
|
+
.SH SYNOPSIS
|
|
5
|
+
.B mend completions zsh|bash
|
|
6
|
+
.br
|
|
7
|
+
.SH DESCRIPTION
|
|
8
|
+
.PP
|
|
9
|
+
Commands complete, and live session ids complete under TAB by asking the server.
|
|
10
|
+
.SH EXAMPLES
|
|
11
|
+
.PP
|
|
12
|
+
.B mend completions zsh > "$fpath[1]/_mend"
|
|
13
|
+
.SH SEE ALSO
|
|
14
|
+
mend(1)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
.TH MEND-CONNECT 1 "" "mend 0.19.0" "mend manual"
|
|
2
|
+
.SH NAME
|
|
3
|
+
mend\-connect \- send this machine's provider credential to the platform
|
|
4
|
+
.SH SYNOPSIS
|
|
5
|
+
.B mend connect <claude|codex|github> [\-\-from\-stdin] [\-\-remove]
|
|
6
|
+
.br
|
|
7
|
+
.SH DESCRIPTION
|
|
8
|
+
.PP
|
|
9
|
+
Sessions run on the platform, so the platform needs your provider login. This reads the file the provider's own CLI wrote when you logged in (claude, codex) or asks gh for its token (github) and stores it under your own user. Nothing is shared with other users.
|
|
10
|
+
.SH OPTIONS
|
|
11
|
+
.TP
|
|
12
|
+
.B \-\-from\-stdin
|
|
13
|
+
paste a credential instead of reading the provider's file
|
|
14
|
+
.TP
|
|
15
|
+
.B \-\-remove
|
|
16
|
+
disconnect the provider
|
|
17
|
+
.SH EXAMPLES
|
|
18
|
+
.PP
|
|
19
|
+
.B mend connect claude
|
|
20
|
+
.br
|
|
21
|
+
after `claude login` on this machine
|
|
22
|
+
.PP
|
|
23
|
+
.B mend connect github \-\-remove
|
|
24
|
+
.SH SEE ALSO
|
|
25
|
+
mend(1), mend\-accounts(1)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
.TH MEND-CONTINUE 1 "" "mend 0.19.0" "mend manual"
|
|
2
|
+
.SH NAME
|
|
3
|
+
mend\-continue \- resume a session with its pending review follow\-up
|
|
4
|
+
.SH SYNOPSIS
|
|
5
|
+
.B mend continue [session\-id]
|
|
6
|
+
.br
|
|
7
|
+
.SH DESCRIPTION
|
|
8
|
+
.PP
|
|
9
|
+
The review comments you sent to the session become its first message. With no id, the newest session with a pending follow\-up is taken.
|
|
10
|
+
.SH SEE ALSO
|
|
11
|
+
mend(1), mend\-resume(1), mend\-sessions(1)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
.TH MEND-DOCTOR 1 "" "mend 0.19.0" "mend manual"
|
|
2
|
+
.SH NAME
|
|
3
|
+
mend\-doctor \- check this machine's setup
|
|
4
|
+
.SH SYNOPSIS
|
|
5
|
+
.B mend doctor
|
|
6
|
+
.br
|
|
7
|
+
.SH DESCRIPTION
|
|
8
|
+
.PP
|
|
9
|
+
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.
|
|
10
|
+
.SH SEE ALSO
|
|
11
|
+
mend(1), mend\-login(1), mend\-connect(1), mend\-keys\-init(1)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
.TH MEND-DOTFILES-SYNC 1 "" "mend 0.19.0" "mend manual"
|
|
2
|
+
.SH NAME
|
|
3
|
+
mend\-dotfiles\-sync \- capture config files from this machine into your store
|
|
4
|
+
.SH SYNOPSIS
|
|
5
|
+
.B mend dotfiles sync [\-\-all | paths...]
|
|
6
|
+
.br
|
|
7
|
+
.SH DESCRIPTION
|
|
8
|
+
.PP
|
|
9
|
+
Copies the named files from your home directory into your store on the server. \-\-all takes the known shell, git, and editor files. Setups that rely on ZDOTDIR do not transfer.
|
|
10
|
+
.SH OPTIONS
|
|
11
|
+
.TP
|
|
12
|
+
.B \-\-all
|
|
13
|
+
every known config file
|
|
14
|
+
.SH SEE ALSO
|
|
15
|
+
mend(1), mend\-dotfiles(1)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
.TH MEND-DOTFILES 1 "" "mend 0.19.0" "mend manual"
|
|
2
|
+
.SH NAME
|
|
3
|
+
mend\-dotfiles \- your dotfiles on the server: repo and synced files
|
|
4
|
+
.SH SYNOPSIS
|
|
5
|
+
.B mend dotfiles [show]
|
|
6
|
+
.br
|
|
7
|
+
.SH DESCRIPTION
|
|
8
|
+
.PP
|
|
9
|
+
Workspaces apply your dotfiles at launch, from a repository you point the server at or from files mend dotfiles sync captured. This shows what is set.
|
|
10
|
+
.SH SEE ALSO
|
|
11
|
+
mend(1), mend\-dotfiles\-sync(1)
|