@sealant/mend 0.22.0 → 0.24.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/README.md +133 -6
- package/dist/chunk-4MX7ZC34.js +27894 -0
- package/dist/dashboard-YEU7SDYB.js +2951 -0
- package/dist/main.js +6917 -2580
- package/man/mend-accounts.1 +1 -1
- package/man/mend-adopt.1 +4 -4
- package/man/mend-attach.1 +1 -1
- package/man/mend-codex.1 +1 -1
- package/man/mend-completions.1 +1 -1
- package/man/mend-connect.1 +1 -1
- package/man/mend-continue.1 +1 -1
- package/man/mend-doctor.1 +1 -1
- package/man/mend-dotfiles-sync.1 +1 -1
- package/man/mend-dotfiles.1 +1 -1
- package/man/mend-env-cluster.1 +1 -1
- package/man/mend-env-load.1 +1 -1
- package/man/mend-env-show.1 +1 -1
- package/man/mend-help.1 +1 -1
- package/man/mend-keys-init.1 +1 -1
- package/man/mend-keys-mode.1 +1 -1
- package/man/mend-keys-share.1 +1 -1
- package/man/mend-keys-show.1 +1 -1
- package/man/mend-login.1 +1 -1
- package/man/mend-logout.1 +1 -1
- package/man/mend-man.1 +1 -1
- package/man/mend-pair.1 +1 -1
- package/man/mend-projects.1 +1 -1
- package/man/mend-refresh.1 +1 -1
- package/man/mend-rejoin.1 +1 -1
- package/man/mend-resume.1 +1 -1
- package/man/mend-run.1 +1 -1
- package/man/mend-server-logs.1 +15 -0
- package/man/mend-server-restart.1 +15 -0
- package/man/mend-server-setup.1 +64 -0
- package/man/mend-server-start.1 +15 -0
- package/man/mend-server-status.1 +11 -0
- package/man/mend-server-stop.1 +11 -0
- package/man/mend-server-upgrade.1 +30 -0
- package/man/mend-server.1 +11 -0
- package/man/mend-service-add.1 +1 -1
- package/man/mend-service-connect.1 +1 -1
- package/man/mend-service-init.1 +1 -1
- package/man/mend-service-list.1 +1 -1
- package/man/mend-service-logs.1 +1 -1
- package/man/mend-service-restart.1 +1 -1
- package/man/mend-service-run.1 +1 -1
- package/man/mend-service-stop.1 +1 -1
- package/man/mend-sessions.1 +1 -1
- package/man/mend-shell.1 +1 -1
- package/man/mend-skills-push.1 +1 -1
- package/man/mend-skills.1 +1 -1
- package/man/mend-ssh-setup.1 +6 -3
- package/man/mend-ssh.1 +1 -1
- package/man/mend-stop.1 +1 -1
- package/man/mend-ui.1 +1 -1
- package/man/mend-version.1 +1 -1
- package/man/mend-worktrees.1 +1 -1
- package/man/mend.1 +26 -2
- package/package.json +7 -3
- package/dist/agent-share.js +0 -150
- package/dist/clipboard.js +0 -73
- package/dist/dashboard-model.js +0 -308
- package/dist/dashboard.js +0 -1200
- package/dist/doctor.js +0 -233
- package/dist/dotfiles.js +0 -90
- package/dist/env.js +0 -17
- package/dist/help.js +0 -839
- package/dist/login.js +0 -240
- package/dist/pair.js +0 -83
- package/dist/review-model.js +0 -212
- package/dist/review-workflow.js +0 -8
- package/dist/review.js +0 -690
- package/dist/service-init.js +0 -227
- package/dist/shared.js +0 -338
- package/dist/skills.js +0 -134
- package/dist/ssh-setup.js +0 -181
- package/dist/terminal.js +0 -8
- package/dist/tui-theme.js +0 -41
- package/dist/version.js +0 -48
- package/dist/workbench-events.js +0 -72
package/dist/help.js
DELETED
|
@@ -1,839 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The command catalog: one record per command, and every help surface renders
|
|
3
|
-
* from it. `mend help` is the index, `mend help <command>` (or `<command>
|
|
4
|
-
* --help`) is one page, `usage:` lines in errors quote the same synopsis, and
|
|
5
|
-
* `man mend` / `mend man <command>` are the same pages as roff. Copy lives
|
|
6
|
-
* here once, so a flag added to a command shows up everywhere or nowhere.
|
|
7
|
-
*/
|
|
8
|
-
export const SECTIONS = [
|
|
9
|
-
"start",
|
|
10
|
-
"sessions",
|
|
11
|
-
"services",
|
|
12
|
-
"project setup",
|
|
13
|
-
"this machine",
|
|
14
|
-
];
|
|
15
|
-
const project = (what = "the project") => ({
|
|
16
|
-
flag: "--project <p>",
|
|
17
|
-
text: `${what} by name, when the current directory is not inside it`,
|
|
18
|
-
});
|
|
19
|
-
const sessionArg = "With no id, the one live session is taken; with several, a picker opens. A prefix of the id is enough.";
|
|
20
|
-
export const COMMANDS = [
|
|
21
|
-
// ── start ──────────────────────────────────────────────────────────────
|
|
22
|
-
{
|
|
23
|
-
name: "login",
|
|
24
|
-
section: "start",
|
|
25
|
-
summary: "sign this terminal in through the browser",
|
|
26
|
-
synopsis: ["[--url <server>]"],
|
|
27
|
-
description: [
|
|
28
|
-
"Opens <server>/authorize in your browser. You press Authorize there. The CLI saves a device token to ~/.config/mend/cli.json with mode 0600. No password is typed in the terminal, and the token can be revoked from the server at any time.",
|
|
29
|
-
],
|
|
30
|
-
options: [
|
|
31
|
-
{
|
|
32
|
-
flag: "--url <server>",
|
|
33
|
-
text: "the Mend server. Default: MEND_URL, then http://localhost:3105",
|
|
34
|
-
},
|
|
35
|
-
],
|
|
36
|
-
examples: [
|
|
37
|
-
{ command: "mend login --url http://10.0.0.216:3105", text: "sign in to a LAN server" },
|
|
38
|
-
],
|
|
39
|
-
see: ["logout", "pair", "doctor"],
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
name: "connect",
|
|
43
|
-
section: "start",
|
|
44
|
-
summary: "send this machine's provider credential to the platform",
|
|
45
|
-
synopsis: ["<claude|codex|github> [--from-stdin] [--remove]"],
|
|
46
|
-
description: [
|
|
47
|
-
"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.",
|
|
48
|
-
],
|
|
49
|
-
options: [
|
|
50
|
-
{ flag: "--from-stdin", text: "paste a credential instead of reading the provider's file" },
|
|
51
|
-
{ flag: "--remove", text: "disconnect the provider" },
|
|
52
|
-
],
|
|
53
|
-
examples: [
|
|
54
|
-
{ command: "mend connect claude", text: "after `claude login` on this machine" },
|
|
55
|
-
{ command: "mend connect github --remove", text: "" },
|
|
56
|
-
],
|
|
57
|
-
see: ["accounts"],
|
|
58
|
-
},
|
|
59
|
-
{
|
|
60
|
-
name: "adopt",
|
|
61
|
-
section: "start",
|
|
62
|
-
summary: "adopt a repository into the store",
|
|
63
|
-
synopsis: ["[source] [--name <name>] [--auth ambient|mend-key|bridge]"],
|
|
64
|
-
description: [
|
|
65
|
-
"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.",
|
|
66
|
-
"--auth says how the store fetches from the remote. Default: your mode from mend keys mode. mend-key signs with your Mend key on the server (see mend keys). bridge relays this machine's ssh-agent while a mend command runs here, so hardware keys stay on your desk. ambient uses the server's own credentials.",
|
|
67
|
-
],
|
|
68
|
-
options: [
|
|
69
|
-
{ flag: "--name <name>", text: "the project's name in Mend. Default: the repository's" },
|
|
70
|
-
{ flag: "--auth <mode>", text: "mend-key, bridge, or ambient. Default: mend keys mode" },
|
|
71
|
-
],
|
|
72
|
-
examples: [
|
|
73
|
-
{ command: "mend adopt", text: "the repository you are standing in" },
|
|
74
|
-
{ command: "mend adopt git@github.com:acme/api.git --auth mend-key", text: "" },
|
|
75
|
-
],
|
|
76
|
-
see: ["keys init", "keys share", "refresh"],
|
|
77
|
-
},
|
|
78
|
-
{
|
|
79
|
-
name: "codex",
|
|
80
|
-
aliases: ["claude", "opencode"],
|
|
81
|
-
section: "start",
|
|
82
|
-
summary: "launch codex, claude, or opencode in a recorded worktree",
|
|
83
|
-
synopsis: [
|
|
84
|
-
'["prompt"] [--name <worktree>] [--worktree <existing>] [--model <id>] [--effort <level>] [--base <ref>] [--ask] [--fast] [--detach|-d] [--foreground] [--project <p>]',
|
|
85
|
-
],
|
|
86
|
-
description: [
|
|
87
|
-
"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.",
|
|
88
|
-
"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.",
|
|
89
|
-
"Detach with Ctrl+] and the session keeps running. Reattach from any terminal with mend attach, or from the phone.",
|
|
90
|
-
"Ctrl+V with an image on this machine's clipboard sends the image to the session and pastes its path; codex and claude read it. Needs wl-paste on Wayland, xclip on X11, nothing extra on macOS.",
|
|
91
|
-
],
|
|
92
|
-
options: [
|
|
93
|
-
{ flag: "--name <worktree>", text: "name the worktree; an existing name joins it" },
|
|
94
|
-
{ flag: "--worktree <existing>", text: "join an existing worktree only" },
|
|
95
|
-
{ flag: "--model <id>", text: "the harness's model id" },
|
|
96
|
-
{ flag: "--effort <level>", text: "low, medium, high, xhigh, or max" },
|
|
97
|
-
{ flag: "--base <ref>", text: "the branch or sha the worktree starts from" },
|
|
98
|
-
{ flag: "--ask", text: "keep the harness's permission prompts" },
|
|
99
|
-
{ flag: "--fast", text: "priority processing where the harness offers it (codex)" },
|
|
100
|
-
{ flag: "--detach, -d", text: "launch without attaching" },
|
|
101
|
-
{ flag: "--foreground", text: "stop the session when this CLI exits" },
|
|
102
|
-
project(),
|
|
103
|
-
],
|
|
104
|
-
examples: [
|
|
105
|
-
{
|
|
106
|
-
command: 'mend claude "add a health endpoint"',
|
|
107
|
-
text: "asks for a worktree name, then runs",
|
|
108
|
-
},
|
|
109
|
-
{ command: "mend codex --name auth-rework -d", text: "start in the background" },
|
|
110
|
-
{
|
|
111
|
-
command: "mend codex --worktree auth-rework",
|
|
112
|
-
text: "a second session in the same worktree",
|
|
113
|
-
},
|
|
114
|
-
],
|
|
115
|
-
see: ["attach", "stop", "run", "sessions"],
|
|
116
|
-
},
|
|
117
|
-
{
|
|
118
|
-
name: "pair",
|
|
119
|
-
section: "start",
|
|
120
|
-
summary: "pair a phone or a second machine",
|
|
121
|
-
synopsis: ["[--url <base url>]"],
|
|
122
|
-
description: [
|
|
123
|
-
"Prints a QR code, the code itself, and the URL this server is reached at. The pairing is for one device, once, and expires after 10 minutes.",
|
|
124
|
-
],
|
|
125
|
-
options: [
|
|
126
|
-
{ flag: "--url <base url>", text: "the address the other device should use for this server" },
|
|
127
|
-
],
|
|
128
|
-
see: ["login"],
|
|
129
|
-
},
|
|
130
|
-
{
|
|
131
|
-
name: "doctor",
|
|
132
|
-
section: "start",
|
|
133
|
-
summary: "check this machine's setup",
|
|
134
|
-
synopsis: [],
|
|
135
|
-
description: [
|
|
136
|
-
"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.",
|
|
137
|
-
],
|
|
138
|
-
see: ["login", "connect", "keys init"],
|
|
139
|
-
},
|
|
140
|
-
// ── sessions ───────────────────────────────────────────────────────────
|
|
141
|
-
{
|
|
142
|
-
name: "ui",
|
|
143
|
-
section: "sessions",
|
|
144
|
-
summary: "the dashboard: every project and session, live",
|
|
145
|
-
synopsis: [],
|
|
146
|
-
description: [
|
|
147
|
-
"A full-screen view of every project and session, updating live. Pick a session to attach, stop, or take over. Bare mend with no command opens the same thing.",
|
|
148
|
-
"The dashboard needs Node 26 or newer for its terminal. Every other command works on Node 22.",
|
|
149
|
-
],
|
|
150
|
-
see: ["sessions", "attach"],
|
|
151
|
-
},
|
|
152
|
-
{
|
|
153
|
-
name: "attach",
|
|
154
|
-
section: "sessions",
|
|
155
|
-
summary: "reattach this terminal to a running session",
|
|
156
|
-
synopsis: ["[session-id-prefix]"],
|
|
157
|
-
description: [
|
|
158
|
-
sessionArg,
|
|
159
|
-
"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.",
|
|
160
|
-
"Ctrl+V with an image on this machine's clipboard sends the image to the session and pastes its path; codex and claude read it. Needs wl-paste on Wayland, xclip on X11, nothing extra on macOS.",
|
|
161
|
-
],
|
|
162
|
-
see: ["stop", "rejoin", "shell"],
|
|
163
|
-
},
|
|
164
|
-
{
|
|
165
|
-
name: "stop",
|
|
166
|
-
section: "sessions",
|
|
167
|
-
summary: "stop the agent; the record and the review remain",
|
|
168
|
-
synopsis: ["[session-id-prefix]", "--all [--project <p>]"],
|
|
169
|
-
description: [
|
|
170
|
-
"Ends the agent process. The workspace harvests the harness state and closes. The worktree, the record, and the review stay, and mend resume brings the conversation back.",
|
|
171
|
-
sessionArg,
|
|
172
|
-
],
|
|
173
|
-
options: [{ flag: "--all", text: "every live session" }, project("limit --all to one project")],
|
|
174
|
-
see: ["resume", "attach"],
|
|
175
|
-
},
|
|
176
|
-
{
|
|
177
|
-
name: "shell",
|
|
178
|
-
section: "sessions",
|
|
179
|
-
summary: "open a shell in a live session's workspace",
|
|
180
|
-
synopsis: ["[session-id-prefix]"],
|
|
181
|
-
description: [
|
|
182
|
-
"A second terminal into the same workspace the agent works in, beside it. Useful for running the tests yourself or looking at a file.",
|
|
183
|
-
sessionArg,
|
|
184
|
-
],
|
|
185
|
-
see: ["attach", "service run"],
|
|
186
|
-
},
|
|
187
|
-
{
|
|
188
|
-
name: "run",
|
|
189
|
-
section: "sessions",
|
|
190
|
-
summary: "run any command as a session",
|
|
191
|
-
synopsis: ["[--project <p>] -- <command...>"],
|
|
192
|
-
description: [
|
|
193
|
-
"The same worktree, record, and review as mend codex, with a command of your own in place of a harness. Everything after -- is the command.",
|
|
194
|
-
],
|
|
195
|
-
options: [project()],
|
|
196
|
-
examples: [{ command: "mend run -- aider --model gpt-5", text: "" }],
|
|
197
|
-
see: ["codex"],
|
|
198
|
-
},
|
|
199
|
-
{
|
|
200
|
-
name: "continue",
|
|
201
|
-
section: "sessions",
|
|
202
|
-
summary: "resume a session with its pending review follow-up",
|
|
203
|
-
synopsis: ["[session-id]"],
|
|
204
|
-
description: [
|
|
205
|
-
"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.",
|
|
206
|
-
],
|
|
207
|
-
see: ["resume", "sessions"],
|
|
208
|
-
},
|
|
209
|
-
{
|
|
210
|
-
name: "resume",
|
|
211
|
-
section: "sessions",
|
|
212
|
-
summary: "rejoin a settled session with its state restored",
|
|
213
|
-
synopsis: ["[session-id] [--with <harness>]"],
|
|
214
|
-
description: [
|
|
215
|
-
"Starts a new agent process from the saved harness state, so the conversation continues where it stopped. --with switches the harness; the conversation carries over between claude and codex.",
|
|
216
|
-
],
|
|
217
|
-
options: [
|
|
218
|
-
{ flag: "--with <harness>", text: "claude or codex; default: the one the session used" },
|
|
219
|
-
],
|
|
220
|
-
see: ["stop", "rejoin"],
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
name: "rejoin",
|
|
224
|
-
section: "sessions",
|
|
225
|
-
summary: "attach if live, otherwise resume",
|
|
226
|
-
synopsis: ["[session-id] [--harness <h>]"],
|
|
227
|
-
description: [
|
|
228
|
-
"With no id, the newest live session wins; failing that, the newest settled one.",
|
|
229
|
-
],
|
|
230
|
-
options: [{ flag: "--harness <h>", text: "the harness to resume with, when resuming" }],
|
|
231
|
-
see: ["attach", "resume"],
|
|
232
|
-
},
|
|
233
|
-
{
|
|
234
|
-
name: "sessions",
|
|
235
|
-
aliases: ["status"],
|
|
236
|
-
section: "sessions",
|
|
237
|
-
summary: "sessions with their review facts",
|
|
238
|
-
synopsis: ["[--all] [--project <p>] [--json | --json=v2]"],
|
|
239
|
-
description: [
|
|
240
|
-
"One line per session: harness, worktree, status, and what the review found. Live sessions by default.",
|
|
241
|
-
"The JSON is stable for integrations. --json is the v1 flat list; --json=v2 groups sessions by worktree, the same shape mend worktrees prints.",
|
|
242
|
-
],
|
|
243
|
-
options: [
|
|
244
|
-
{ flag: "--all", text: "settled sessions too" },
|
|
245
|
-
project(),
|
|
246
|
-
{ flag: "--json", text: "the v1 flat list" },
|
|
247
|
-
{ flag: "--json=v2", text: "grouped by worktree" },
|
|
248
|
-
],
|
|
249
|
-
see: ["worktrees", "projects"],
|
|
250
|
-
},
|
|
251
|
-
{
|
|
252
|
-
name: "worktrees",
|
|
253
|
-
section: "sessions",
|
|
254
|
-
summary: "every worktree and the sessions inside it",
|
|
255
|
-
synopsis: ["[--project <p>] [--json]"],
|
|
256
|
-
description: ["A worktree can hold several sessions over time; this shows them together."],
|
|
257
|
-
options: [project(), { flag: "--json", text: "the v2 grouped shape" }],
|
|
258
|
-
see: ["sessions"],
|
|
259
|
-
},
|
|
260
|
-
{
|
|
261
|
-
name: "projects",
|
|
262
|
-
section: "sessions",
|
|
263
|
-
summary: "adopted projects and their live sessions",
|
|
264
|
-
synopsis: [],
|
|
265
|
-
description: ["One line per project, with the sessions running in it."],
|
|
266
|
-
see: ["adopt", "sessions"],
|
|
267
|
-
},
|
|
268
|
-
{
|
|
269
|
-
name: "refresh",
|
|
270
|
-
section: "sessions",
|
|
271
|
-
summary: "fetch origin's branches into the store",
|
|
272
|
-
synopsis: ["[project]"],
|
|
273
|
-
description: [
|
|
274
|
-
"New sessions base on the tips the store holds; this brings them up to date with origin. Default: the project the current directory belongs to.",
|
|
275
|
-
],
|
|
276
|
-
see: ["adopt"],
|
|
277
|
-
},
|
|
278
|
-
// ── services ───────────────────────────────────────────────────────────
|
|
279
|
-
{
|
|
280
|
-
name: "service run",
|
|
281
|
-
section: "services",
|
|
282
|
-
summary: "start and supervise a server in the session's workspace",
|
|
283
|
-
synopsis: [
|
|
284
|
-
"[session] --port <port> [--name <n>] [--udp] [--http|--https] [--no-connect] -- <command...>",
|
|
285
|
-
"[session] <name> [--no-connect]",
|
|
286
|
-
],
|
|
287
|
-
description: [
|
|
288
|
-
"With --, the command after it is started in the workspace and supervised: its output is recorded, and mend service restart re-runs it. Without --, the name is a Service declared in the worktree's mend.toml. mend service <name> is the shorthand for that.",
|
|
289
|
-
"The port is tunnelled to this machine's loopback as soon as it listens, unless --no-connect.",
|
|
290
|
-
],
|
|
291
|
-
options: [
|
|
292
|
-
{ flag: "--port <port>", text: "the port the command listens on inside the workspace" },
|
|
293
|
-
{ flag: "--name <n>", text: "the service's name. Default: the command" },
|
|
294
|
-
{ flag: "--udp", text: "a UDP port" },
|
|
295
|
-
{ flag: "--http, --https", text: "how a browser should open it" },
|
|
296
|
-
{ flag: "--no-connect", text: "do not tunnel the port to this machine" },
|
|
297
|
-
],
|
|
298
|
-
examples: [
|
|
299
|
-
{ command: "mend service run --port 3000 -- pnpm dev", text: "" },
|
|
300
|
-
{ command: "mend service web", text: "the Service named web in mend.toml" },
|
|
301
|
-
],
|
|
302
|
-
see: ["service init", "service connect", "service logs"],
|
|
303
|
-
},
|
|
304
|
-
{
|
|
305
|
-
name: "service add",
|
|
306
|
-
section: "services",
|
|
307
|
-
summary: "adopt a port something in the workspace already listens on",
|
|
308
|
-
synopsis: ["[session] <port> [--name <n>] [--udp] [--http|--https]"],
|
|
309
|
-
description: [
|
|
310
|
-
"For a server the agent started itself. The port becomes a Service, reachable on this machine like one mend service run started.",
|
|
311
|
-
],
|
|
312
|
-
options: [
|
|
313
|
-
{ flag: "--name <n>", text: "the service's name. Default: the port" },
|
|
314
|
-
{ flag: "--udp", text: "a UDP port" },
|
|
315
|
-
{ flag: "--http, --https", text: "how a browser should open it" },
|
|
316
|
-
],
|
|
317
|
-
see: ["service run", "service list"],
|
|
318
|
-
},
|
|
319
|
-
{
|
|
320
|
-
name: "service connect",
|
|
321
|
-
section: "services",
|
|
322
|
-
summary: "bring live Services to this machine's loopback",
|
|
323
|
-
synopsis: ["[name...] [--port <p>]"],
|
|
324
|
-
description: [
|
|
325
|
-
"Each connection tunnels through the server, authenticated as you. With no names, every live Service. Ctrl-C closes them.",
|
|
326
|
-
],
|
|
327
|
-
options: [{ flag: "--port <p>", text: "the local port, when connecting one Service" }],
|
|
328
|
-
see: ["service list"],
|
|
329
|
-
},
|
|
330
|
-
{
|
|
331
|
-
name: "service list",
|
|
332
|
-
section: "services",
|
|
333
|
-
summary: "every live service and its observed state",
|
|
334
|
-
synopsis: [],
|
|
335
|
-
description: ["Name, session, port, whether it listens, and where it is reachable from here."],
|
|
336
|
-
see: ["service run", "service connect"],
|
|
337
|
-
},
|
|
338
|
-
{
|
|
339
|
-
name: "service logs",
|
|
340
|
-
section: "services",
|
|
341
|
-
summary: "follow a supervised service's output",
|
|
342
|
-
synopsis: ["<name-or-id> [--from <sequence>]"],
|
|
343
|
-
description: ["Replays the recorded output, then follows it live. Ctrl-C stops following."],
|
|
344
|
-
options: [{ flag: "--from <sequence>", text: "start the replay at a record sequence" }],
|
|
345
|
-
see: ["service run"],
|
|
346
|
-
},
|
|
347
|
-
{
|
|
348
|
-
name: "service restart",
|
|
349
|
-
section: "services",
|
|
350
|
-
summary: "re-run a service's recorded command, same URL",
|
|
351
|
-
synopsis: ["<name-or-id>"],
|
|
352
|
-
description: ["The port and the local address stay the same, so open tabs keep working."],
|
|
353
|
-
see: ["service stop"],
|
|
354
|
-
},
|
|
355
|
-
{
|
|
356
|
-
name: "service stop",
|
|
357
|
-
section: "services",
|
|
358
|
-
summary: "stop a service and close its host port",
|
|
359
|
-
synopsis: ["<name-or-id>"],
|
|
360
|
-
description: ["Ends the process and the tunnel. The record of its output remains."],
|
|
361
|
-
see: ["service restart"],
|
|
362
|
-
},
|
|
363
|
-
{
|
|
364
|
-
name: "service init",
|
|
365
|
-
section: "services",
|
|
366
|
-
summary: "scaffold mend.toml from package.json and compose ports",
|
|
367
|
-
synopsis: ["[--yes]"],
|
|
368
|
-
description: [
|
|
369
|
-
"Writes a mend.toml in the current repository with one Service per script and exposed port it can find. Edit it afterwards; sessions read it from their worktree.",
|
|
370
|
-
],
|
|
371
|
-
options: [{ flag: "--yes", text: "write without asking" }],
|
|
372
|
-
see: ["service run"],
|
|
373
|
-
},
|
|
374
|
-
// ── project setup ──────────────────────────────────────────────────────
|
|
375
|
-
{
|
|
376
|
-
name: "env show",
|
|
377
|
-
section: "project setup",
|
|
378
|
-
summary: "what the project store holds: names only, never values",
|
|
379
|
-
synopsis: ["[--project <p>]"],
|
|
380
|
-
description: ["Configuration names and secret names for the project. Values never print."],
|
|
381
|
-
options: [project()],
|
|
382
|
-
see: ["env load"],
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
name: "env load",
|
|
386
|
-
section: "project setup",
|
|
387
|
-
summary: "load a .env into the project",
|
|
388
|
-
synopsis: ["[file] [--project <p>] [--secret [A,B]]"],
|
|
389
|
-
description: [
|
|
390
|
-
"Ordinary names become configuration. Secret-shaped names (KEY, TOKEN, PASSWORD, and the like) become secrets, which Mend stores encrypted and never shows again. Sessions receive both at launch. Default file: .env in the current directory.",
|
|
391
|
-
],
|
|
392
|
-
options: [
|
|
393
|
-
project(),
|
|
394
|
-
{
|
|
395
|
-
flag: "--secret [A,B]",
|
|
396
|
-
text: "send every name to secrets, or only the named ones, e.g. DATABASE_URL",
|
|
397
|
-
},
|
|
398
|
-
],
|
|
399
|
-
examples: [
|
|
400
|
-
{ command: "mend env load .env.production --secret DATABASE_URL,STRIPE_KEY", text: "" },
|
|
401
|
-
],
|
|
402
|
-
see: ["env show", "env cluster"],
|
|
403
|
-
},
|
|
404
|
-
{
|
|
405
|
-
name: "env cluster",
|
|
406
|
-
section: "project setup",
|
|
407
|
-
summary: "bind Kubernetes secrets and configmaps to the workspaces",
|
|
408
|
-
synopsis: [
|
|
409
|
-
"add secret|configmap <name>",
|
|
410
|
-
"remove <kind>/<name>",
|
|
411
|
-
"sa <name> | sa --clear",
|
|
412
|
-
"[--project <p>]",
|
|
413
|
-
],
|
|
414
|
-
description: [
|
|
415
|
-
"For a Mend server running in Kubernetes. The platform mounts the named objects into the project's workspaces at launch; Mend never reads their contents. sa sets the service account the workspaces run as. Changes apply from the next launch; running workspaces keep what they started with.",
|
|
416
|
-
],
|
|
417
|
-
options: [project()],
|
|
418
|
-
examples: [
|
|
419
|
-
{ command: "mend env cluster add secret app-env", text: "" },
|
|
420
|
-
{ command: "mend env cluster remove secret/app-env", text: "" },
|
|
421
|
-
],
|
|
422
|
-
see: ["env load"],
|
|
423
|
-
},
|
|
424
|
-
{
|
|
425
|
-
name: "skills",
|
|
426
|
-
section: "project setup",
|
|
427
|
-
summary: "your skill library on the server, or a project's",
|
|
428
|
-
synopsis: ["[list] [--project [p]]"],
|
|
429
|
-
description: [
|
|
430
|
-
"Skills are instruction bundles the harness loads at launch. Yours apply to every session; a project's apply to sessions in that project.",
|
|
431
|
-
],
|
|
432
|
-
options: [{ flag: "--project [p]", text: "the project's library instead of yours" }],
|
|
433
|
-
see: ["skills push"],
|
|
434
|
-
},
|
|
435
|
-
{
|
|
436
|
-
name: "skills push",
|
|
437
|
-
section: "project setup",
|
|
438
|
-
summary: "upload ~/.agents/skills into the library",
|
|
439
|
-
synopsis: ["[--project [p]] [--prune] [--dir <path>]"],
|
|
440
|
-
description: [
|
|
441
|
-
"Scans the directory codex and claude read skills from and uploads every bundle. Sessions receive the library at launch. Unchanged bundles are skipped.",
|
|
442
|
-
],
|
|
443
|
-
options: [
|
|
444
|
-
{ flag: "--project [p]", text: "push into the project's library instead of yours" },
|
|
445
|
-
{ flag: "--prune", text: "remove server-side skills the directory no longer has" },
|
|
446
|
-
{ flag: "--dir <path>", text: "scan another directory. Default: ~/.agents/skills" },
|
|
447
|
-
],
|
|
448
|
-
see: ["skills"],
|
|
449
|
-
},
|
|
450
|
-
{
|
|
451
|
-
name: "dotfiles",
|
|
452
|
-
section: "project setup",
|
|
453
|
-
summary: "your dotfiles on the server: repo and synced files",
|
|
454
|
-
synopsis: ["[show]"],
|
|
455
|
-
description: [
|
|
456
|
-
"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.",
|
|
457
|
-
],
|
|
458
|
-
see: ["dotfiles sync"],
|
|
459
|
-
},
|
|
460
|
-
{
|
|
461
|
-
name: "dotfiles sync",
|
|
462
|
-
section: "project setup",
|
|
463
|
-
summary: "capture config files from this machine into your store",
|
|
464
|
-
synopsis: ["[--all | paths...]"],
|
|
465
|
-
description: [
|
|
466
|
-
"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.",
|
|
467
|
-
],
|
|
468
|
-
options: [{ flag: "--all", text: "every known config file" }],
|
|
469
|
-
see: ["dotfiles"],
|
|
470
|
-
},
|
|
471
|
-
{
|
|
472
|
-
name: "keys init",
|
|
473
|
-
section: "project setup",
|
|
474
|
-
summary: "create your Mend key (ed25519) on the server",
|
|
475
|
-
synopsis: [],
|
|
476
|
-
description: [
|
|
477
|
-
"One key per user, held on the server, never copied anywhere. Add the public key to your git account's SSH keys and every repository you can reach works, from detached sessions and the phone too. For one repository only, add it as that repository's deploy key instead.",
|
|
478
|
-
],
|
|
479
|
-
see: ["keys show", "keys mode", "adopt"],
|
|
480
|
-
},
|
|
481
|
-
{
|
|
482
|
-
name: "keys show",
|
|
483
|
-
section: "project setup",
|
|
484
|
-
summary: "print your Mend public key",
|
|
485
|
-
synopsis: [],
|
|
486
|
-
description: [
|
|
487
|
-
"The key to add to your git account's SSH keys, or as a deploy key on one repository.",
|
|
488
|
-
],
|
|
489
|
-
see: ["keys init"],
|
|
490
|
-
},
|
|
491
|
-
{
|
|
492
|
-
name: "keys mode",
|
|
493
|
-
section: "project setup",
|
|
494
|
-
summary: "how your remotes are reached: mend-key or bridge",
|
|
495
|
-
synopsis: ["[mend-key|bridge]"],
|
|
496
|
-
description: [
|
|
497
|
-
"mend-key (the default) signs with your Mend key on the server and works whenever the server is up. bridge signs with this machine's ssh-agent, so a hardware key never leaves your desk, but only while a mend command is running here. New projects adopt with this mode; a project's setup page can override it. Without a value, prints the current mode.",
|
|
498
|
-
],
|
|
499
|
-
see: ["keys init", "keys share"],
|
|
500
|
-
},
|
|
501
|
-
{
|
|
502
|
-
name: "keys share",
|
|
503
|
-
section: "project setup",
|
|
504
|
-
summary: "relay this machine's ssh-agent to the server",
|
|
505
|
-
synopsis: [],
|
|
506
|
-
description: [
|
|
507
|
-
"Bridge mode for projects adopted with --auth bridge: the server's git operations sign with the keys in this machine's ssh-agent, so hardware keys never leave here. Runs until Ctrl-C.",
|
|
508
|
-
"When your mode is bridge (mend keys mode), every attaching mend command and the dashboard do this on their own for as long as they run, so this command is for a machine that is not running one. The server takes one signer at a time; a newer share replaces the older one.",
|
|
509
|
-
],
|
|
510
|
-
see: ["keys mode", "adopt"],
|
|
511
|
-
},
|
|
512
|
-
// ── this machine ───────────────────────────────────────────────────────
|
|
513
|
-
{
|
|
514
|
-
name: "ssh",
|
|
515
|
-
section: "this machine",
|
|
516
|
-
summary: "workspace SSH status: gateway, registered keys, ssh config",
|
|
517
|
-
synopsis: ["[status]"],
|
|
518
|
-
description: ["Whether this machine can ssh into workspaces, and what is missing if not."],
|
|
519
|
-
see: ["ssh setup"],
|
|
520
|
-
},
|
|
521
|
-
{
|
|
522
|
-
name: "ssh setup",
|
|
523
|
-
section: "this machine",
|
|
524
|
-
summary: "make this machine ready to ssh into workspaces, once",
|
|
525
|
-
synopsis: ["[--key <path>]"],
|
|
526
|
-
description: [
|
|
527
|
-
"Registers a public key with the gateway (a key already in your ssh-agent is preferred; nothing new is created unless there is none) and writes a Host mend-ws block to ~/.ssh/config.",
|
|
528
|
-
],
|
|
529
|
-
options: [{ flag: "--key <path>", text: "the public key to register" }],
|
|
530
|
-
see: ["ssh", "shell"],
|
|
531
|
-
},
|
|
532
|
-
{
|
|
533
|
-
name: "accounts",
|
|
534
|
-
section: "this machine",
|
|
535
|
-
summary: "your connected accounts on the platform",
|
|
536
|
-
synopsis: [],
|
|
537
|
-
description: [
|
|
538
|
-
"claude, codex, and github: connected, invalid, or archived, and when each was last used.",
|
|
539
|
-
],
|
|
540
|
-
see: ["connect"],
|
|
541
|
-
},
|
|
542
|
-
{
|
|
543
|
-
name: "logout",
|
|
544
|
-
section: "this machine",
|
|
545
|
-
summary: "revoke this terminal's device token and forget it",
|
|
546
|
-
synopsis: [],
|
|
547
|
-
description: ["Other terminals and devices keep their own tokens."],
|
|
548
|
-
see: ["login"],
|
|
549
|
-
},
|
|
550
|
-
{
|
|
551
|
-
name: "completions",
|
|
552
|
-
section: "this machine",
|
|
553
|
-
summary: "print the TAB-completion hook",
|
|
554
|
-
synopsis: ["zsh|bash"],
|
|
555
|
-
description: [
|
|
556
|
-
"Commands complete, and live session ids complete under TAB by asking the server.",
|
|
557
|
-
],
|
|
558
|
-
examples: [{ command: 'mend completions zsh > "$fpath[1]/_mend"', text: "" }],
|
|
559
|
-
},
|
|
560
|
-
{
|
|
561
|
-
name: "version",
|
|
562
|
-
section: "this machine",
|
|
563
|
-
summary: "this CLI's version, and the server's when it answers",
|
|
564
|
-
synopsis: [],
|
|
565
|
-
description: [
|
|
566
|
-
"Two lines: the version of this CLI, then the server's from its health route. A server that does not answer within two seconds prints as unreachable. --version and -v do the same.",
|
|
567
|
-
],
|
|
568
|
-
see: ["doctor"],
|
|
569
|
-
},
|
|
570
|
-
{
|
|
571
|
-
name: "help",
|
|
572
|
-
section: "this machine",
|
|
573
|
-
summary: "this index, or one command's page",
|
|
574
|
-
synopsis: ["[command...]"],
|
|
575
|
-
description: [
|
|
576
|
-
"mend help alone lists every command by section. mend help <command> prints that command's page: usage, description, options, examples. mend <command> --help is the same page.",
|
|
577
|
-
],
|
|
578
|
-
see: ["man"],
|
|
579
|
-
},
|
|
580
|
-
{
|
|
581
|
-
name: "man",
|
|
582
|
-
section: "this machine",
|
|
583
|
-
summary: "one command's page, or the whole manual, in man",
|
|
584
|
-
synopsis: ["[command...]"],
|
|
585
|
-
description: [
|
|
586
|
-
"Renders the same pages as roff and opens them in man. Without man on the PATH, the text page prints instead. The npm package also installs mend(1) and mend-<command>(1), so man mend works after a global install.",
|
|
587
|
-
],
|
|
588
|
-
see: ["help"],
|
|
589
|
-
},
|
|
590
|
-
{
|
|
591
|
-
name: "qr",
|
|
592
|
-
section: "this machine",
|
|
593
|
-
summary: "render a QR code for the installer",
|
|
594
|
-
synopsis: ["<text>"],
|
|
595
|
-
description: ["The installer renders its pairing QR through this."],
|
|
596
|
-
hidden: true,
|
|
597
|
-
},
|
|
598
|
-
];
|
|
599
|
-
// ── lookup ────────────────────────────────────────────────────────────────
|
|
600
|
-
const firstWord = (doc) => doc.name.split(" ")[0] ?? doc.name;
|
|
601
|
-
/**
|
|
602
|
-
* The longest catalog entry the leading words name. `help service run` finds
|
|
603
|
-
* "service run"; `help service` finds the group (every "service …" page);
|
|
604
|
-
* `help claude` finds the codex page through its alias.
|
|
605
|
-
*/
|
|
606
|
-
export const findCommand = (words) => {
|
|
607
|
-
const [first, ...rest] = words;
|
|
608
|
-
if (first === undefined)
|
|
609
|
-
return null;
|
|
610
|
-
const candidates = COMMANDS.filter((doc) => firstWord(doc) === first || doc.aliases?.includes(first));
|
|
611
|
-
let best = null;
|
|
612
|
-
for (const doc of candidates) {
|
|
613
|
-
const tail = doc.name.split(" ").slice(1);
|
|
614
|
-
if (tail.every((word, index) => rest[index] === word)) {
|
|
615
|
-
if (best === null || doc.name.length > best.name.length)
|
|
616
|
-
best = doc;
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
return best;
|
|
620
|
-
};
|
|
621
|
-
/** Every page whose name starts with the word: the "service …" family. */
|
|
622
|
-
export const commandGroup = (first) => COMMANDS.filter((doc) => !doc.hidden && firstWord(doc) === first && doc.name.includes(" "));
|
|
623
|
-
/** `usage: mend <name> <synopsis>` for an error line; one line per shape. */
|
|
624
|
-
export const usageOf = (name) => {
|
|
625
|
-
const doc = findCommand(name.split(" "));
|
|
626
|
-
if (doc === null)
|
|
627
|
-
return `usage: mend ${name}`;
|
|
628
|
-
const shapes = doc.synopsis.length === 0 ? [""] : doc.synopsis;
|
|
629
|
-
return shapes
|
|
630
|
-
.map((shape, index) => `${index === 0 ? "usage: " : " "}mend ${doc.name}${shape === "" ? "" : ` ${shape}`}`)
|
|
631
|
-
.join("\n");
|
|
632
|
-
};
|
|
633
|
-
// ── text rendering ────────────────────────────────────────────────────────
|
|
634
|
-
/**
|
|
635
|
-
* Wrap at `width`, keeping `indent` spaces on every line; `hang` adds that
|
|
636
|
-
* many more on continuation lines, so a usage line reads as one shape.
|
|
637
|
-
*/
|
|
638
|
-
export const wrap = (text, width, indent = 0, hang = 0) => {
|
|
639
|
-
const lines = [];
|
|
640
|
-
let line = "";
|
|
641
|
-
const room = () => Math.max(16, width - indent - (lines.length === 0 ? 0 : hang));
|
|
642
|
-
for (const word of text.split(/\s+/).filter((w) => w !== "")) {
|
|
643
|
-
if (line === "")
|
|
644
|
-
line = word;
|
|
645
|
-
else if (line.length + 1 + word.length <= room())
|
|
646
|
-
line += ` ${word}`;
|
|
647
|
-
else {
|
|
648
|
-
lines.push(" ".repeat(indent + (lines.length === 0 ? 0 : hang)) + line);
|
|
649
|
-
line = word;
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
if (line !== "")
|
|
653
|
-
lines.push(" ".repeat(indent + (lines.length === 0 ? 0 : hang)) + line);
|
|
654
|
-
return lines;
|
|
655
|
-
};
|
|
656
|
-
/** Two columns: a label, then text wrapped beside it (or under it when the label is long). */
|
|
657
|
-
const twoColumns = (rows, width, indent = 2, gap = 2) => {
|
|
658
|
-
const longest = Math.max(0, ...rows.map(([label]) => label.length));
|
|
659
|
-
const column = Math.min(longest, 28) + gap;
|
|
660
|
-
const out = [];
|
|
661
|
-
for (const [label, text] of rows) {
|
|
662
|
-
const body = wrap(text, width, indent + column);
|
|
663
|
-
if (label.length + gap > column || body.length === 0) {
|
|
664
|
-
out.push(" ".repeat(indent) + label);
|
|
665
|
-
out.push(...body);
|
|
666
|
-
continue;
|
|
667
|
-
}
|
|
668
|
-
const [first, ...more] = body;
|
|
669
|
-
out.push(" ".repeat(indent) + label.padEnd(column) + (first ?? "").trimStart());
|
|
670
|
-
out.push(...more);
|
|
671
|
-
}
|
|
672
|
-
return out;
|
|
673
|
-
};
|
|
674
|
-
const ENVIRONMENT = [
|
|
675
|
-
["MEND_URL", "the server. Default http://localhost:3105"],
|
|
676
|
-
["MEND_TOKEN", "a token in place of the saved login"],
|
|
677
|
-
[
|
|
678
|
-
"MEND_DETACH_KEY",
|
|
679
|
-
"the detach chord; default Ctrl+]. none when an outer multiplexer owns detaching",
|
|
680
|
-
],
|
|
681
|
-
];
|
|
682
|
-
const FILES = [
|
|
683
|
-
["~/.config/mend/cli.json", "url, token, and device id from mend login"],
|
|
684
|
-
["~/.config/mend/keys/", "the deploy key from mend keys init"],
|
|
685
|
-
];
|
|
686
|
-
export const terminalWidth = () => Math.min(process.stdout.columns ?? 80, 100);
|
|
687
|
-
/** `mend help`: every command by section, one line each. */
|
|
688
|
-
export const renderIndex = (width = terminalWidth()) => {
|
|
689
|
-
const out = ["mend · the agent workbench", ""];
|
|
690
|
-
const visible = COMMANDS.filter((doc) => !doc.hidden);
|
|
691
|
-
const longest = Math.max(...visible.map((doc) => doc.name.length));
|
|
692
|
-
const column = longest + 2;
|
|
693
|
-
for (const section of SECTIONS) {
|
|
694
|
-
out.push(section);
|
|
695
|
-
for (const doc of visible.filter((d) => d.section === section)) {
|
|
696
|
-
const body = wrap(doc.summary, width, 2 + column);
|
|
697
|
-
out.push(` ${doc.name.padEnd(column)}${(body[0] ?? "").trimStart()}`);
|
|
698
|
-
out.push(...body.slice(1));
|
|
699
|
-
}
|
|
700
|
-
out.push("");
|
|
701
|
-
}
|
|
702
|
-
out.push(...wrap("mend help <command> for usage, options, and examples; mend <command> --help is the same page. man mend after a global install, or mend man <command>.", width));
|
|
703
|
-
out.push("");
|
|
704
|
-
out.push("environment");
|
|
705
|
-
out.push(...twoColumns(ENVIRONMENT, width));
|
|
706
|
-
out.push("");
|
|
707
|
-
out.push("files");
|
|
708
|
-
out.push(...twoColumns(FILES, width));
|
|
709
|
-
return out.join("\n");
|
|
710
|
-
};
|
|
711
|
-
const invocations = (doc) => {
|
|
712
|
-
const shapes = doc.synopsis.length === 0 ? [""] : doc.synopsis;
|
|
713
|
-
return shapes.map((shape) => `mend ${doc.name}${shape === "" ? "" : ` ${shape}`}`);
|
|
714
|
-
};
|
|
715
|
-
/** "also mend claude, mend opencode" for a page that several first words reach. */
|
|
716
|
-
const aliasNote = (doc) => doc.aliases === undefined || doc.aliases.length === 0
|
|
717
|
-
? null
|
|
718
|
-
: `also ${doc.aliases.map((alias) => `mend ${alias}`).join(", ")}, with the same options`;
|
|
719
|
-
/** `mend help <command>`: one page. */
|
|
720
|
-
export const renderCommand = (doc, width = terminalWidth()) => {
|
|
721
|
-
const out = [`mend ${doc.name} · ${doc.summary}`, "", "usage"];
|
|
722
|
-
for (const line of invocations(doc))
|
|
723
|
-
out.push(...wrap(line, width, 2, 4));
|
|
724
|
-
const alias = aliasNote(doc);
|
|
725
|
-
if (alias !== null)
|
|
726
|
-
out.push(` ${alias}`);
|
|
727
|
-
out.push("", "description");
|
|
728
|
-
for (const paragraph of doc.description) {
|
|
729
|
-
out.push(...wrap(paragraph, width, 2));
|
|
730
|
-
out.push("");
|
|
731
|
-
}
|
|
732
|
-
if (doc.options !== undefined && doc.options.length > 0) {
|
|
733
|
-
out.push("options");
|
|
734
|
-
out.push(...twoColumns(doc.options.map((o) => [o.flag, o.text]), width));
|
|
735
|
-
out.push("");
|
|
736
|
-
}
|
|
737
|
-
if (doc.examples !== undefined && doc.examples.length > 0) {
|
|
738
|
-
out.push("examples");
|
|
739
|
-
for (const example of doc.examples) {
|
|
740
|
-
out.push(` ${example.command}`);
|
|
741
|
-
if (example.text !== "")
|
|
742
|
-
out.push(...wrap(example.text, width, 6));
|
|
743
|
-
}
|
|
744
|
-
out.push("");
|
|
745
|
-
}
|
|
746
|
-
const group = commandGroup(firstWord(doc)).filter((other) => other !== doc);
|
|
747
|
-
if (!doc.name.includes(" ") && group.length > 0) {
|
|
748
|
-
out.push("subcommands");
|
|
749
|
-
out.push(...twoColumns(group.map((g) => [g.name, g.summary]), width));
|
|
750
|
-
out.push("");
|
|
751
|
-
}
|
|
752
|
-
if (doc.see !== undefined && doc.see.length > 0) {
|
|
753
|
-
out.push("see also");
|
|
754
|
-
out.push(...wrap(doc.see.map((name) => `mend ${name}`).join(" · "), width, 2));
|
|
755
|
-
out.push("");
|
|
756
|
-
}
|
|
757
|
-
return out.join("\n").trimEnd();
|
|
758
|
-
};
|
|
759
|
-
/** `mend help service`: the family's pages in one. */
|
|
760
|
-
export const renderGroup = (first, width = terminalWidth()) => {
|
|
761
|
-
const group = commandGroup(first);
|
|
762
|
-
if (group.length === 0)
|
|
763
|
-
return null;
|
|
764
|
-
const out = [`mend ${first} · ${group.length} commands`, ""];
|
|
765
|
-
out.push(...twoColumns(group.map((g) => [g.name, g.summary]), width));
|
|
766
|
-
out.push("", `mend help ${first} <subcommand> for one page`);
|
|
767
|
-
return out.join("\n");
|
|
768
|
-
};
|
|
769
|
-
// ── roff rendering (man pages) ────────────────────────────────────────────
|
|
770
|
-
/** Escape for roff: backslashes, leading control characters, and dashes that must stay ASCII. */
|
|
771
|
-
export const roff = (text) => {
|
|
772
|
-
const escaped = text.replace(/\\/g, "\\e").replace(/-/g, "\\-");
|
|
773
|
-
return /^[.']/.test(escaped) ? `\\&${escaped}` : escaped;
|
|
774
|
-
};
|
|
775
|
-
const manHeader = (title, version, summary) => [
|
|
776
|
-
`.TH ${title.toUpperCase()} 1 "" "mend ${roff(version)}" "mend manual"`,
|
|
777
|
-
".SH NAME",
|
|
778
|
-
`${roff(title)} \\- ${roff(summary)}`,
|
|
779
|
-
];
|
|
780
|
-
/** mend-<command>(1). */
|
|
781
|
-
export const renderManPage = (doc, version) => {
|
|
782
|
-
const title = `mend-${doc.name.replace(/ /g, "-")}`;
|
|
783
|
-
const out = [...manHeader(title, version, doc.summary), ".SH SYNOPSIS"];
|
|
784
|
-
for (const line of invocations(doc))
|
|
785
|
-
out.push(`.B ${roff(line)}`, ".br");
|
|
786
|
-
const alias = aliasNote(doc);
|
|
787
|
-
if (alias !== null)
|
|
788
|
-
out.push(roff(alias));
|
|
789
|
-
out.push(".SH DESCRIPTION");
|
|
790
|
-
for (const paragraph of doc.description)
|
|
791
|
-
out.push(".PP", roff(paragraph));
|
|
792
|
-
if (doc.options !== undefined && doc.options.length > 0) {
|
|
793
|
-
out.push(".SH OPTIONS");
|
|
794
|
-
for (const option of doc.options)
|
|
795
|
-
out.push(".TP", `.B ${roff(option.flag)}`, roff(option.text));
|
|
796
|
-
}
|
|
797
|
-
if (doc.examples !== undefined && doc.examples.length > 0) {
|
|
798
|
-
out.push(".SH EXAMPLES");
|
|
799
|
-
for (const example of doc.examples) {
|
|
800
|
-
out.push(".PP", `.B ${roff(example.command)}`);
|
|
801
|
-
if (example.text !== "")
|
|
802
|
-
out.push(".br", roff(example.text));
|
|
803
|
-
}
|
|
804
|
-
}
|
|
805
|
-
const see = ["mend(1)", ...(doc.see ?? []).map((name) => `mend-${name.replace(/ /g, "-")}(1)`)];
|
|
806
|
-
out.push(".SH SEE ALSO", roff(see.join(", ")));
|
|
807
|
-
return `${out.join("\n")}\n`;
|
|
808
|
-
};
|
|
809
|
-
/** mend(1): the index plus every page as a section. */
|
|
810
|
-
export const renderManIndex = (version) => {
|
|
811
|
-
const out = [
|
|
812
|
-
...manHeader("mend", version, "the agent workbench"),
|
|
813
|
-
".SH SYNOPSIS",
|
|
814
|
-
".B mend",
|
|
815
|
-
"[\\fIcommand\\fR] [\\fIoptions\\fR]",
|
|
816
|
-
".SH DESCRIPTION",
|
|
817
|
-
".PP",
|
|
818
|
-
roff("Mend adopts a repository into a central store, runs your coding agent (codex, claude, opencode, or any command) in a recorded git worktree on the platform, and lets you attach, review, and steer from any terminal or device. Each command has its own page: mend-<command>(1)."),
|
|
819
|
-
];
|
|
820
|
-
for (const section of SECTIONS) {
|
|
821
|
-
out.push(`.SH ${section.toUpperCase()}`);
|
|
822
|
-
for (const doc of COMMANDS.filter((d) => !d.hidden && d.section === section)) {
|
|
823
|
-
out.push(".TP", `.B mend ${roff(doc.name)}`, roff(doc.summary));
|
|
824
|
-
}
|
|
825
|
-
}
|
|
826
|
-
out.push(".SH ENVIRONMENT");
|
|
827
|
-
for (const [name, text] of ENVIRONMENT)
|
|
828
|
-
out.push(".TP", `.B ${roff(name)}`, roff(text));
|
|
829
|
-
out.push(".SH FILES");
|
|
830
|
-
for (const [name, text] of FILES)
|
|
831
|
-
out.push(".TP", `.B ${roff(name)}`, roff(text));
|
|
832
|
-
out.push(".SH SEE ALSO");
|
|
833
|
-
out.push(roff(COMMANDS.filter((d) => !d.hidden)
|
|
834
|
-
.map((d) => `mend-${d.name.replace(/ /g, "-")}(1)`)
|
|
835
|
-
.join(", ")));
|
|
836
|
-
return `${out.join("\n")}\n`;
|
|
837
|
-
};
|
|
838
|
-
/** The file name each page installs as. */
|
|
839
|
-
export const manFileName = (doc) => doc === null ? "mend.1" : `mend-${doc.name.replace(/ /g, "-")}.1`;
|