@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.
Files changed (80) hide show
  1. package/README.md +133 -6
  2. package/dist/chunk-4MX7ZC34.js +27894 -0
  3. package/dist/dashboard-YEU7SDYB.js +2951 -0
  4. package/dist/main.js +6917 -2580
  5. package/man/mend-accounts.1 +1 -1
  6. package/man/mend-adopt.1 +4 -4
  7. package/man/mend-attach.1 +1 -1
  8. package/man/mend-codex.1 +1 -1
  9. package/man/mend-completions.1 +1 -1
  10. package/man/mend-connect.1 +1 -1
  11. package/man/mend-continue.1 +1 -1
  12. package/man/mend-doctor.1 +1 -1
  13. package/man/mend-dotfiles-sync.1 +1 -1
  14. package/man/mend-dotfiles.1 +1 -1
  15. package/man/mend-env-cluster.1 +1 -1
  16. package/man/mend-env-load.1 +1 -1
  17. package/man/mend-env-show.1 +1 -1
  18. package/man/mend-help.1 +1 -1
  19. package/man/mend-keys-init.1 +1 -1
  20. package/man/mend-keys-mode.1 +1 -1
  21. package/man/mend-keys-share.1 +1 -1
  22. package/man/mend-keys-show.1 +1 -1
  23. package/man/mend-login.1 +1 -1
  24. package/man/mend-logout.1 +1 -1
  25. package/man/mend-man.1 +1 -1
  26. package/man/mend-pair.1 +1 -1
  27. package/man/mend-projects.1 +1 -1
  28. package/man/mend-refresh.1 +1 -1
  29. package/man/mend-rejoin.1 +1 -1
  30. package/man/mend-resume.1 +1 -1
  31. package/man/mend-run.1 +1 -1
  32. package/man/mend-server-logs.1 +15 -0
  33. package/man/mend-server-restart.1 +15 -0
  34. package/man/mend-server-setup.1 +64 -0
  35. package/man/mend-server-start.1 +15 -0
  36. package/man/mend-server-status.1 +11 -0
  37. package/man/mend-server-stop.1 +11 -0
  38. package/man/mend-server-upgrade.1 +30 -0
  39. package/man/mend-server.1 +11 -0
  40. package/man/mend-service-add.1 +1 -1
  41. package/man/mend-service-connect.1 +1 -1
  42. package/man/mend-service-init.1 +1 -1
  43. package/man/mend-service-list.1 +1 -1
  44. package/man/mend-service-logs.1 +1 -1
  45. package/man/mend-service-restart.1 +1 -1
  46. package/man/mend-service-run.1 +1 -1
  47. package/man/mend-service-stop.1 +1 -1
  48. package/man/mend-sessions.1 +1 -1
  49. package/man/mend-shell.1 +1 -1
  50. package/man/mend-skills-push.1 +1 -1
  51. package/man/mend-skills.1 +1 -1
  52. package/man/mend-ssh-setup.1 +6 -3
  53. package/man/mend-ssh.1 +1 -1
  54. package/man/mend-stop.1 +1 -1
  55. package/man/mend-ui.1 +1 -1
  56. package/man/mend-version.1 +1 -1
  57. package/man/mend-worktrees.1 +1 -1
  58. package/man/mend.1 +26 -2
  59. package/package.json +7 -3
  60. package/dist/agent-share.js +0 -150
  61. package/dist/clipboard.js +0 -73
  62. package/dist/dashboard-model.js +0 -308
  63. package/dist/dashboard.js +0 -1200
  64. package/dist/doctor.js +0 -233
  65. package/dist/dotfiles.js +0 -90
  66. package/dist/env.js +0 -17
  67. package/dist/help.js +0 -839
  68. package/dist/login.js +0 -240
  69. package/dist/pair.js +0 -83
  70. package/dist/review-model.js +0 -212
  71. package/dist/review-workflow.js +0 -8
  72. package/dist/review.js +0 -690
  73. package/dist/service-init.js +0 -227
  74. package/dist/shared.js +0 -338
  75. package/dist/skills.js +0 -134
  76. package/dist/ssh-setup.js +0 -181
  77. package/dist/terminal.js +0 -8
  78. package/dist/tui-theme.js +0 -41
  79. package/dist/version.js +0 -48
  80. package/dist/workbench-events.js +0 -72
package/dist/doctor.js DELETED
@@ -1,233 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- /**
4
- * `mend doctor`: one read-only pass over everything a first run depends on, printed
5
- * as mono status lines (DESIGN.md §4 — a mark plus a word, never a badge). Every
6
- * line states what was observed; a line that needs an action ends with the single
7
- * command that takes it. Nothing here writes, launches, or repairs anything.
8
- *
9
- * No request waits longer than 3s: a doctor that hangs is worse than a doctor that
10
- * reports "not checked".
11
- */
12
- const TIMEOUT_MS = 3_000;
13
- const MARKS = { ok: "✓", todo: "○", failed: "✗" };
14
- const LABEL_WIDTH = 11;
15
- /** One status line. The mark is painted by the caller so the formatter stays testable. */
16
- export const formatCheck = (check, paint = (_state, mark) => mark) => `${paint(check.state, MARKS[check.state])} ${check.label.padEnd(LABEL_WIDTH)} ${check.detail}${check.fix === null ? "" : ` → ${check.fix}`}`;
17
- /** A read that never throws and never blocks: the outcome is a value the checklist can print. */
18
- const getJson = async (config, route) => {
19
- try {
20
- const response = await fetch(`${config.url}/api${route}`, {
21
- headers: config.token === null ? {} : { authorization: `Bearer ${config.token}` },
22
- signal: AbortSignal.timeout(TIMEOUT_MS),
23
- });
24
- if (!response.ok)
25
- return { value: null, status: response.status };
26
- return { value: (await response.json()), status: response.status };
27
- }
28
- catch {
29
- return { value: null, status: null };
30
- }
31
- };
32
- /** Where each provider's own CLI writes the credential Mend forwards (mirrors `mend connect`). */
33
- const LOGIN_COMMANDS = {
34
- claude: "claude setup-token",
35
- codex: "codex login",
36
- github: "gh auth login",
37
- };
38
- const HARNESS_CLIS = [
39
- { command: "claude", provider: "claude" },
40
- { command: "codex", provider: "codex" },
41
- { command: "gh", provider: "github" },
42
- ];
43
- const notChecked = (label) => ({
44
- label,
45
- state: "todo",
46
- detail: "not checked",
47
- fix: null,
48
- });
49
- const identityOf = (account) => {
50
- const meta = account.metadata;
51
- for (const key of ["login", "email", "accountEmail", "accountId"]) {
52
- const value = meta[key];
53
- if (typeof value === "string" && value !== "")
54
- return value;
55
- }
56
- return null;
57
- };
58
- /** Every fact the checklist prints, in the order a first run needs them. */
59
- export const runChecks = async (config, probes) => {
60
- const checks = [];
61
- const health = await getJson(config, "/health");
62
- checks.push(health.value === null
63
- ? {
64
- label: "server",
65
- state: "failed",
66
- detail: `cannot reach ${config.url}`,
67
- fix: "start the Mend server",
68
- }
69
- : {
70
- label: "server",
71
- state: "ok",
72
- detail: `${config.url} · mend ${health.value.version}`,
73
- fix: null,
74
- });
75
- // Projects double as the cheapest authenticated read there is: it proves the token
76
- // without asking the platform anything.
77
- const projects = health.value !== null && config.token !== null
78
- ? await getJson(config, "/projects")
79
- : null;
80
- if (health.value === null)
81
- checks.push(notChecked("signed in"));
82
- else if (config.token === null) {
83
- checks.push({
84
- label: "signed in",
85
- state: "failed",
86
- detail: "no token saved",
87
- fix: "mend login",
88
- });
89
- }
90
- else if (projects !== null && projects.value !== null) {
91
- checks.push({ label: "signed in", state: "ok", detail: "token accepted", fix: null });
92
- }
93
- else {
94
- const status = projects === null ? null : projects.status;
95
- checks.push(status === 401 || status === 403
96
- ? { label: "signed in", state: "failed", detail: "token rejected", fix: "mend login" }
97
- : {
98
- label: "signed in",
99
- state: "failed",
100
- detail: `GET /projects → ${status ?? "no answer"}`,
101
- fix: null,
102
- });
103
- }
104
- const signedIn = projects !== null && projects.value !== null;
105
- const connection = signedIn ? await getJson(config, "/sealant/connection") : null;
106
- if (connection === null)
107
- checks.push(notChecked("sealant"));
108
- else if (connection.value === null) {
109
- // 404 is a server older than the endpoint: nothing was observed, so nothing is claimed.
110
- checks.push(connection.status === 404
111
- ? notChecked("sealant")
112
- : {
113
- label: "sealant",
114
- state: "failed",
115
- detail: `GET /sealant/connection → ${connection.status ?? "no answer"}`,
116
- fix: null,
117
- });
118
- }
119
- else if (connection.value.status === "connected") {
120
- checks.push({
121
- label: "sealant",
122
- state: "ok",
123
- detail: `connected · ${connection.value.baseUrl}`,
124
- fix: null,
125
- });
126
- }
127
- else {
128
- checks.push({
129
- label: "sealant",
130
- state: "failed",
131
- detail: `${connection.value.status} · ${connection.value.detail ?? connection.value.baseUrl}`,
132
- fix: null,
133
- });
134
- }
135
- const identity = signedIn ? await getJson(config, "/me/sealant") : null;
136
- const platform = identity === null ? null : identity.value;
137
- for (const { provider } of HARNESS_CLIS) {
138
- if (platform === null) {
139
- checks.push(notChecked(provider));
140
- continue;
141
- }
142
- const account = platform.accounts.find((row) => row.provider === provider && row.name === "default") ??
143
- platform.accounts.find((row) => row.provider === provider);
144
- if (account === undefined) {
145
- checks.push({
146
- label: provider,
147
- state: "todo",
148
- detail: "not connected",
149
- fix: `mend connect ${provider}`,
150
- });
151
- continue;
152
- }
153
- const who = identityOf(account);
154
- checks.push(account.status === "active"
155
- ? {
156
- label: provider,
157
- state: "ok",
158
- detail: who === null ? "connected" : `connected · ${who}`,
159
- fix: null,
160
- }
161
- : {
162
- label: provider,
163
- state: "todo",
164
- detail: account.status,
165
- fix: `mend connect ${provider}`,
166
- });
167
- }
168
- const adopted = projects === null ? null : projects.value;
169
- if (adopted === null)
170
- checks.push(notChecked("projects"));
171
- else if (adopted.length === 0) {
172
- checks.push({ label: "projects", state: "todo", detail: "none adopted", fix: "mend adopt" });
173
- }
174
- else {
175
- checks.push({ label: "projects", state: "ok", detail: `${adopted.length} adopted`, fix: null });
176
- }
177
- for (const { command, provider } of HARNESS_CLIS) {
178
- const label = `${command} cli`;
179
- if (!probes.onPath(command)) {
180
- checks.push({ label, state: "todo", detail: "not on PATH", fix: null });
181
- continue;
182
- }
183
- const credential = probes.localCredential(provider);
184
- checks.push(credential === null
185
- ? {
186
- label,
187
- state: "todo",
188
- detail: "on PATH · no credential here",
189
- fix: LOGIN_COMMANDS[provider],
190
- }
191
- : { label, state: "ok", detail: "on PATH · credential present", fix: null });
192
- }
193
- const machine = signedIn ? await getJson(config, "/machine") : null;
194
- const tailnet = machine === null || machine.value === null ? null : machine.value.tailnet;
195
- if (tailnet === null)
196
- checks.push(notChecked("tailnet"));
197
- else if (tailnet.status === "reachable" && tailnet.address !== null) {
198
- checks.push({ label: "tailnet", state: "ok", detail: tailnet.address, fix: null });
199
- }
200
- else {
201
- checks.push({ label: "tailnet", state: "todo", detail: "not detected", fix: null });
202
- }
203
- return checks;
204
- };
205
- /** An executable of that name on PATH — the question `command -v` asks, without a subprocess. */
206
- export const onPath = (command) => {
207
- for (const directory of (process.env["PATH"] ?? "").split(path.delimiter)) {
208
- if (directory === "")
209
- continue;
210
- try {
211
- fs.accessSync(path.join(directory, command), fs.constants.X_OK);
212
- return true;
213
- }
214
- catch {
215
- // keep looking
216
- }
217
- }
218
- return false;
219
- };
220
- /** Green observed · amber not started · red a blocker (DESIGN.md §4); plain text on a pipe. */
221
- const paintMark = (state, mark) => {
222
- if (process.stdout.isTTY !== true)
223
- return mark;
224
- const code = state === "ok" ? "32" : state === "todo" ? "33" : "31";
225
- return `[${code}m${mark}`;
226
- };
227
- export const doctorCommand = async (config, localCredential) => {
228
- const checks = await runChecks(config, { localCredential, onPath });
229
- for (const check of checks)
230
- process.stdout.write(`${formatCheck(check, paintMark)}\n`);
231
- if (checks.some((check) => check.state === "failed"))
232
- process.exitCode = 1;
233
- };
package/dist/dotfiles.js DELETED
@@ -1,90 +0,0 @@
1
- import * as fs from "node:fs";
2
- import * as path from "node:path";
3
- /**
4
- * Local dotfiles scanning for `mend dotfiles sync`. This runs on the machine that HAS the
5
- * files — the whole point: the Mend server may be a VPS whose home directory is a service
6
- * account's, so contents are captured here and streamed to the server's per-user dotfiles store.
7
- *
8
- * The candidate list mirrors the product's curated set (plain configuration only — never keys,
9
- * credential stores, or histories). The CLI stays dependency-light by design, so the list is
10
- * duplicated here rather than imported from a workspace package.
11
- */
12
- export const DOTFILE_CANDIDATES = [
13
- {
14
- group: "shell",
15
- paths: [
16
- ".zshrc",
17
- ".zshenv",
18
- ".zprofile",
19
- ".bashrc",
20
- ".bash_profile",
21
- ".profile",
22
- ".aliases",
23
- ".config/fish/config.fish",
24
- ".config/starship.toml",
25
- ],
26
- },
27
- {
28
- group: "git",
29
- paths: [".gitconfig", ".gitignore_global", ".config/git/config", ".config/git/ignore"],
30
- },
31
- {
32
- group: "editors",
33
- paths: [".vimrc", ".ideavimrc", ".editorconfig", ".config/helix/config.toml"],
34
- },
35
- {
36
- group: "terminal",
37
- paths: [".tmux.conf", ".config/tmux/tmux.conf", ".inputrc", ".dir_colors"],
38
- },
39
- {
40
- group: "tools",
41
- paths: [
42
- ".ripgreprc",
43
- ".config/bat/config",
44
- ".config/lazygit/config.yml",
45
- ".config/mise/config.toml",
46
- ".config/atuin/config.toml",
47
- ".config/zellij/config.kdl",
48
- ],
49
- },
50
- ];
51
- /** The per-file cap the server enforces; checked here so a mistake fails before the upload. */
52
- export const MAX_FILE_BYTES = 1024 * 1024;
53
- const statFile = (home, relative) => {
54
- try {
55
- const stat = fs.statSync(path.join(home, relative));
56
- return stat.isFile() ? stat : null;
57
- }
58
- catch {
59
- return null;
60
- }
61
- };
62
- /** Probe the curated candidates under `home`; only existing regular files appear. */
63
- export const scanDotfileCandidates = (home) => DOTFILE_CANDIDATES.flatMap(({ group, paths }) => paths.flatMap((relative) => {
64
- const stat = statFile(home, relative);
65
- return stat === null ? [] : [{ path: relative, group, bytes: stat.size }];
66
- }));
67
- /**
68
- * Read the selected paths for upload. Explicitly requested paths must exist (a typo should fail,
69
- * not silently sync nothing); oversized files fail with the server's own rule. Modes ride along
70
- * so an executable script stays executable.
71
- */
72
- export const readSyncFiles = (home, paths_) => {
73
- const files = [];
74
- for (const relative of paths_) {
75
- const stat = statFile(home, relative);
76
- if (stat === null) {
77
- return { error: `${relative} is not a file under ${home}` };
78
- }
79
- if (stat.size > MAX_FILE_BYTES) {
80
- return { error: `${relative} is over 1MB — dotfiles are text; trim the selection` };
81
- }
82
- files.push({
83
- path: relative,
84
- contentsBase64: fs.readFileSync(path.join(home, relative)).toString("base64"),
85
- // eslint-disable-next-line no-bitwise -- permission bits are the point
86
- mode: (stat.mode & 0o777).toString(8),
87
- });
88
- }
89
- return { files };
90
- };
package/dist/env.js DELETED
@@ -1,17 +0,0 @@
1
- /**
2
- * `mend env`: load a dotenv file into the project's env store, and show what is stored. Parsing
3
- * AND routing happen SERVER-SIDE (one parser for the CLI and the browser); the CLI ships the file
4
- * text once and prints the per-name report. Values are never printed.
5
- */
6
- /** Render the report as terse status lines — names, lanes, actions, reasons; never a value. */
7
- export const formatLoadReport = (report, paint) => {
8
- const width = Math.max(0, ...report.loaded.map((entry) => entry.name.length), ...report.rejected.map((entry) => entry.name.length));
9
- const lines = [];
10
- for (const entry of report.loaded) {
11
- lines.push(` ${entry.name.padEnd(width)} ${entry.lane === "secret" ? "secret" : "configuration"} ${paint.dim(`· ${entry.action}${entry.lane === "configuration" ? " · plaintext" : ""}`)}`);
12
- }
13
- for (const entry of report.rejected) {
14
- lines.push(` ${entry.name.padEnd(width)} ${paint.warn("rejected")} ${paint.dim(`· ${entry.reason}`)}`);
15
- }
16
- return lines;
17
- };