@sealant/mend 0.36.0-next.658 → 0.36.0-next.663
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-GKRTZRV7.js → chunk-VOP6USOZ.js} +19 -0
- package/dist/{dashboard-IBHSVQ5S.js → dashboard-CEJN6IOO.js} +1 -1
- package/dist/main.js +707 -116
- package/man/mend-accounts.1 +1 -1
- package/man/mend-adopt.1 +1 -1
- package/man/mend-attach.1 +1 -1
- package/man/mend-codex.1 +1 -1
- package/man/mend-completions.1 +1 -1
- package/man/mend-connect-pi.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-repo.1 +1 -1
- package/man/mend-dotfiles-sync.1 +1 -1
- package/man/mend-dotfiles.1 +1 -1
- package/man/mend-env-cluster.1 +1 -1
- package/man/mend-env-load.1 +1 -1
- package/man/mend-env-show.1 +1 -1
- package/man/mend-folder-create.1 +1 -1
- package/man/mend-folder-list.1 +1 -1
- package/man/mend-folder-push.1 +1 -1
- package/man/mend-folder-rm.1 +1 -1
- package/man/mend-git-author.1 +1 -1
- package/man/mend-help.1 +1 -1
- package/man/mend-invite.1 +1 -1
- package/man/mend-keys-init.1 +1 -1
- package/man/mend-keys-mode.1 +1 -1
- package/man/mend-keys-share.1 +1 -1
- package/man/mend-keys-show.1 +1 -1
- package/man/mend-land.1 +1 -1
- package/man/mend-login.1 +1 -1
- package/man/mend-logout.1 +1 -1
- package/man/mend-logs.1 +26 -0
- package/man/mend-man.1 +1 -1
- package/man/mend-members.1 +1 -1
- package/man/mend-memory-import.1 +1 -1
- package/man/mend-memory-rm.1 +1 -1
- package/man/mend-memory-show.1 +1 -1
- package/man/mend-memory.1 +1 -1
- package/man/mend-models.1 +1 -1
- package/man/mend-operator-exposure.1 +1 -1
- package/man/mend-operator-gate.1 +1 -1
- package/man/mend-operator-grant-owner.1 +1 -1
- package/man/mend-operator-org-create.1 +1 -1
- package/man/mend-operator-org-invite-owner.1 +1 -1
- package/man/mend-operator-org-list.1 +1 -1
- package/man/mend-operator-org-rename.1 +1 -1
- package/man/mend-operator-reset-link.1 +1 -1
- package/man/mend-pair.1 +1 -1
- package/man/mend-projects.1 +10 -2
- package/man/mend-pull.1 +1 -1
- package/man/mend-refresh.1 +1 -1
- package/man/mend-rejoin.1 +1 -1
- package/man/mend-resume.1 +1 -1
- package/man/mend-run.1 +35 -4
- package/man/mend-secrets-add.1 +1 -1
- package/man/mend-secrets-rm.1 +1 -1
- package/man/mend-secrets.1 +1 -1
- package/man/mend-server-logs.1 +1 -1
- package/man/mend-server-restart.1 +1 -1
- package/man/mend-server-setup.1 +1 -1
- package/man/mend-server-start.1 +1 -1
- package/man/mend-server-status.1 +1 -1
- package/man/mend-server-stop.1 +1 -1
- package/man/mend-server-upgrade.1 +1 -1
- package/man/mend-server.1 +1 -1
- package/man/mend-service-add.1 +1 -1
- package/man/mend-service-connect.1 +1 -1
- package/man/mend-service-init.1 +1 -1
- package/man/mend-service-list.1 +1 -1
- package/man/mend-service-logs.1 +3 -1
- package/man/mend-service-restart.1 +1 -1
- package/man/mend-service-run.1 +8 -3
- package/man/mend-service-stop.1 +1 -1
- package/man/mend-session-share.1 +1 -1
- package/man/mend-sessions.1 +1 -1
- package/man/mend-shell.1 +1 -1
- package/man/mend-skills-push.1 +1 -1
- package/man/mend-skills.1 +1 -1
- package/man/mend-snake.1 +1 -1
- package/man/mend-ssh-setup.1 +1 -1
- package/man/mend-ssh.1 +1 -1
- package/man/mend-stop.1 +1 -1
- package/man/mend-ui.1 +1 -1
- package/man/mend-uninstall.1 +1 -1
- package/man/mend-version.1 +1 -1
- package/man/mend-wait.1 +28 -0
- package/man/mend-workspace-replace.1 +1 -1
- package/man/mend-worktrees-rm.1 +1 -1
- package/man/mend-worktrees.1 +1 -1
- package/man/mend.1 +8 -2
- package/package.json +2 -2
package/dist/main.js
CHANGED
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
WORKTREE_REMOVAL_FORCE_HINT,
|
|
18
18
|
agentIsLive,
|
|
19
19
|
agentMemoryNameOf,
|
|
20
|
-
agentOutcome,
|
|
21
20
|
agentStartingFacts,
|
|
22
21
|
agentStartingLine,
|
|
23
22
|
askYesNo,
|
|
@@ -48,6 +47,7 @@ import {
|
|
|
48
47
|
normalizeRemoteUrl,
|
|
49
48
|
parseLaunchArgs,
|
|
50
49
|
pasteBytes,
|
|
50
|
+
redactCredentials,
|
|
51
51
|
replaceConfirmLines,
|
|
52
52
|
repositoryCloneUrlIssue,
|
|
53
53
|
sameGrant,
|
|
@@ -68,7 +68,7 @@ import {
|
|
|
68
68
|
workspaceRetirementLine,
|
|
69
69
|
worktreeJoinLine,
|
|
70
70
|
worktreeListsPeople
|
|
71
|
-
} from "./chunk-
|
|
71
|
+
} from "./chunk-VOP6USOZ.js";
|
|
72
72
|
|
|
73
73
|
// src/main.ts
|
|
74
74
|
import { spawn as spawn3, spawnSync as spawnSync6 } from "node:child_process";
|
|
@@ -1962,13 +1962,71 @@ var COMMANDS = [
|
|
|
1962
1962
|
name: "run",
|
|
1963
1963
|
section: "sessions",
|
|
1964
1964
|
summary: "run any command as a session",
|
|
1965
|
-
synopsis: [
|
|
1965
|
+
synopsis: [
|
|
1966
|
+
"[--project <p>] [--name <n> | --worktree <n>] [--base <ref>] [--detach] [--json] -- <command...>"
|
|
1967
|
+
],
|
|
1966
1968
|
description: [
|
|
1967
|
-
"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."
|
|
1969
|
+
"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.",
|
|
1970
|
+
"The command's output is printed as the record has it, and mend run exits with the command's exit code. It runs in a terminal, so stdout and stderr arrive together, on stdout. What Mend says itself goes to stderr, so out=$(mend run -- git log -1) holds the command's output and nothing else. Ctrl+C stops watching and puts the terminal back; the command keeps running, and mend logs and mend wait pick it up again. A signal exits 128 + its number: 130 for SIGINT (Ctrl+C), 129 for SIGHUP, 143 for SIGTERM. A second signal exits at once, and an exit waits at most 5 seconds for a reader that takes nothing, then says the output may be incomplete.",
|
|
1971
|
+
"Output this terminal could not be given in full (a read the server refused, a reader that went away) fails mend run with exit 1 even when the command succeeded, and the command's own code is said on stderr: a script never takes cut output for the whole of it.",
|
|
1972
|
+
"The platform takes at most 64 words, none empty and none starting or ending with whitespace (a script that starts with a newline, for example). Such a command is refused before anything is created; trim the word and run it again.",
|
|
1973
|
+
"With --detach, mend run returns once the command runs. With --json, stdout carries one JSON object in place of the output: the session id, the process id, the worktree, the branch, and the process's status and exit code as last observed, which is how it ended without --detach (and with it, when the command ended first).",
|
|
1974
|
+
"Workspaces set PAGER=cat, so git log and friends print instead of waiting for a pager the image does not have. A project variable of the same name, or the pager in your own git config, wins."
|
|
1968
1975
|
],
|
|
1969
|
-
options: [
|
|
1970
|
-
|
|
1971
|
-
|
|
1976
|
+
options: [
|
|
1977
|
+
project(),
|
|
1978
|
+
{ flag: "--name <n>", text: "name the worktree; an existing name joins it" },
|
|
1979
|
+
{ flag: "--worktree <n>", text: "join an existing worktree only" },
|
|
1980
|
+
{ flag: "--base <ref>", text: "create the worktree from another git base" },
|
|
1981
|
+
{ flag: "--detach, -d", text: "return once the command runs" },
|
|
1982
|
+
{ flag: "--json", text: "print the session and process ids, and the end, as JSON" }
|
|
1983
|
+
],
|
|
1984
|
+
examples: [
|
|
1985
|
+
{ command: "mend run -- pnpm test", text: "the test output here, exit code included" },
|
|
1986
|
+
{
|
|
1987
|
+
command: "id=$(mend run --detach --json -- make build | jq -r .sessionId)",
|
|
1988
|
+
text: "start it, then mend wait $id"
|
|
1989
|
+
}
|
|
1990
|
+
],
|
|
1991
|
+
see: ["logs", "wait", "codex"]
|
|
1992
|
+
},
|
|
1993
|
+
{
|
|
1994
|
+
name: "logs",
|
|
1995
|
+
section: "sessions",
|
|
1996
|
+
summary: "print a session's recorded terminal output",
|
|
1997
|
+
synopsis: ["[session] [--follow] [--from <sequence>] [--process <id>]"],
|
|
1998
|
+
description: [
|
|
1999
|
+
"Prints what the session's command (or agent) wrote to its terminal, as the record holds it, on stdout. Settled sessions count: the record outlives the process and the workspace. --follow keeps printing until the process ends. The record is printed as it is, nothing taken out: anyone who can read the project can read it, so what a command printed, a password included, reaches them too. A signal stops it and puts the terminal back, exiting 128 + its number: 130 for SIGINT (Ctrl+C), 129 for SIGHUP, 143 for SIGTERM.",
|
|
2000
|
+
"<session> is the session id, a prefix of it, or the worktree's name. With none, the one live session is taken. --process reads another process of the session, a shell or a Service attempt, by a prefix of its id."
|
|
2001
|
+
],
|
|
2002
|
+
options: [
|
|
2003
|
+
{ flag: "--follow, -f", text: "keep printing until the process ends" },
|
|
2004
|
+
{ flag: "--from <sequence>", text: "start at a record sequence. Default: 0" },
|
|
2005
|
+
{ flag: "--process <id>", text: "another process of the session, by a prefix of its id" }
|
|
2006
|
+
],
|
|
2007
|
+
examples: [{ command: "mend logs 3f2a --follow", text: "" }],
|
|
2008
|
+
see: ["run", "wait", "service logs"]
|
|
2009
|
+
},
|
|
2010
|
+
{
|
|
2011
|
+
name: "wait",
|
|
2012
|
+
section: "sessions",
|
|
2013
|
+
summary: "wait for a session's command to end",
|
|
2014
|
+
synopsis: ["[session] [--timeout <seconds>] [--process <id>] [--json]"],
|
|
2015
|
+
description: [
|
|
2016
|
+
"Returns once the session's command (or agent) has ended, with its exit code: the code the platform reported, or 1 when it reported none. A command that already ended answers at once. While a launch or a resume is starting, the previous process's end does not count. --process waits for one process by its id, the processId mend run --json prints.",
|
|
2017
|
+
"When --timeout passes first, mend wait exits 124, as timeout(1) does, and the command keeps running. Stopped by a signal, it exits 128 + its number (130 for Ctrl+C), and the command keeps running. The timeout covers everything: finding the session, every read and every retry. --json then prints the last state read, and nothing more is asked of the server.",
|
|
2018
|
+
"<session> is the session id, a prefix of it, or the worktree's name. With none, the one live session is taken."
|
|
2019
|
+
],
|
|
2020
|
+
options: [
|
|
2021
|
+
{ flag: "--timeout <seconds>", text: "give up after this long; exit 124" },
|
|
2022
|
+
{
|
|
2023
|
+
flag: "--process <id>",
|
|
2024
|
+
text: "wait for this process of the session, by a prefix of its id"
|
|
2025
|
+
},
|
|
2026
|
+
{ flag: "--json", text: "print the session, the process, the status and the exit code" }
|
|
2027
|
+
],
|
|
2028
|
+
examples: [{ command: "mend wait 3f2a --timeout 600", text: "" }],
|
|
2029
|
+
see: ["run", "logs"]
|
|
1972
2030
|
},
|
|
1973
2031
|
{
|
|
1974
2032
|
name: "continue",
|
|
@@ -2074,8 +2132,13 @@ var COMMANDS = [
|
|
|
2074
2132
|
name: "projects",
|
|
2075
2133
|
section: "sessions",
|
|
2076
2134
|
summary: "adopted projects and their live sessions",
|
|
2077
|
-
synopsis: [],
|
|
2078
|
-
description: [
|
|
2135
|
+
synopsis: ["[--json]"],
|
|
2136
|
+
description: [
|
|
2137
|
+
"One line per project, with the sessions running in it.",
|
|
2138
|
+
'--json prints them for scripts, stable like mend sessions --json: {"version": 1, "projects": [...]}, an empty list when there are none. Each project has id, name, originUrl (a string, or null), defaultBranch, storePath, liveSessions (a number) and current (true when the current directory is inside it).',
|
|
2139
|
+
"A repository URL is printed without its credentials: https://oauth2:TOKEN@github.com/acme/repo.git reads as https://github.com/acme/repo.git, here and in every line, message and JSON the CLI prints itself; an ssh URL keeps its user and loses only the password. A command's recorded output (mend run, mend logs, mend service logs, mend attach) and a file's contents (mend memory show) are printed as they are, and a session's record is readable by anyone who can read the project."
|
|
2140
|
+
],
|
|
2141
|
+
options: [{ flag: "--json", text: "the projects as JSON" }],
|
|
2079
2142
|
see: ["adopt", "sessions"]
|
|
2080
2143
|
},
|
|
2081
2144
|
{
|
|
@@ -2156,14 +2219,19 @@ var COMMANDS = [
|
|
|
2156
2219
|
section: "services",
|
|
2157
2220
|
summary: "start and supervise a server in the session's workspace",
|
|
2158
2221
|
synopsis: [
|
|
2159
|
-
"[session] --port <port> [--name <n>] [--udp] [--http|--https] [--no-connect] -- <command...>",
|
|
2160
|
-
"[session] <name> [--no-connect]"
|
|
2222
|
+
"[session] --port <port> [--name <n>] [--udp] [--http|--https] [--wait] [--no-connect] -- <command...>",
|
|
2223
|
+
"[session] <name> [--wait] [--no-connect]"
|
|
2161
2224
|
],
|
|
2162
2225
|
description: [
|
|
2163
2226
|
"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.",
|
|
2227
|
+
"Mend waits up to a minute for the port to answer before it returns. --wait makes the exit status say how that ended: 0 once the port answered, 1 when it did not, and 124 when the server gave no answer within 90 seconds. The Service keeps running in every case. A waited start returns and opens no tunnel; mend service connect reaches the port. UDP has no probe, and a recipe that declares only a port is adopted with one probe, so --wait takes neither.",
|
|
2164
2228
|
"The port is tunnelled to this machine's loopback as soon as it listens, unless --no-connect."
|
|
2165
2229
|
],
|
|
2166
2230
|
options: [
|
|
2231
|
+
{
|
|
2232
|
+
flag: "--wait",
|
|
2233
|
+
text: "exit 1 when the port did not answer, 124 with no answer in 90 s; no tunnel"
|
|
2234
|
+
},
|
|
2167
2235
|
{ flag: "--port <port>", text: "the port the command listens on inside the workspace" },
|
|
2168
2236
|
{ flag: "--name <n>", text: "the service's name. Default: the command" },
|
|
2169
2237
|
{ flag: "--udp", text: "a UDP port" },
|
|
@@ -2216,7 +2284,10 @@ var COMMANDS = [
|
|
|
2216
2284
|
section: "services",
|
|
2217
2285
|
summary: "follow a supervised service's output",
|
|
2218
2286
|
synopsis: ["<name-or-id> [--from <sequence>]"],
|
|
2219
|
-
description: [
|
|
2287
|
+
description: [
|
|
2288
|
+
"Replays the recorded output, then follows it live. Ctrl-C stops following.",
|
|
2289
|
+
"The record is printed as it is, nothing taken out, and anyone who can read the project can read it: what the Service printed, a password included, reaches them too."
|
|
2290
|
+
],
|
|
2220
2291
|
options: [{ flag: "--from <sequence>", text: "start the replay at a record sequence" }],
|
|
2221
2292
|
see: ["service run"]
|
|
2222
2293
|
},
|
|
@@ -3998,8 +4069,10 @@ var doctorCommand = async (config, localCredential2, claudeGrant2) => {
|
|
|
3998
4069
|
onPath,
|
|
3999
4070
|
dockerShutdown: observeHostShutdownTimeout
|
|
4000
4071
|
});
|
|
4001
|
-
for (const check of checks)
|
|
4072
|
+
for (const check of checks) {
|
|
4073
|
+
process.stdout.write(`${redactCredentials(formatCheck(check, paintMark))}
|
|
4002
4074
|
`);
|
|
4075
|
+
}
|
|
4003
4076
|
if (checks.some((check) => check.state === "failed")) process.exitCode = 1;
|
|
4004
4077
|
};
|
|
4005
4078
|
|
|
@@ -4197,10 +4270,10 @@ var paint = (code) => (text) => process.stdout.isTTY === true ? `[${code}m${text
|
|
|
4197
4270
|
var dim = paint("2");
|
|
4198
4271
|
var green = paint("32");
|
|
4199
4272
|
var amber = paint("33");
|
|
4200
|
-
var say = (line) => process.stdout.write(`${line}
|
|
4273
|
+
var say = (line) => process.stdout.write(`${redactCredentials(line)}
|
|
4201
4274
|
`);
|
|
4202
4275
|
var fail = (message) => {
|
|
4203
|
-
process.stderr.write(`mend: ${message}
|
|
4276
|
+
process.stderr.write(`mend: ${redactCredentials(message)}
|
|
4204
4277
|
`);
|
|
4205
4278
|
process.exit(1);
|
|
4206
4279
|
};
|
|
@@ -4667,10 +4740,10 @@ import * as os7 from "node:os";
|
|
|
4667
4740
|
var paint2 = (code) => (text) => process.stdout.isTTY === true ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
4668
4741
|
var dim2 = paint2("2");
|
|
4669
4742
|
var green2 = paint2("32");
|
|
4670
|
-
var say2 = (line) => process.stdout.write(`${line}
|
|
4743
|
+
var say2 = (line) => process.stdout.write(`${redactCredentials(line)}
|
|
4671
4744
|
`);
|
|
4672
4745
|
var fail2 = (message) => {
|
|
4673
|
-
process.stderr.write(`mend: ${message}
|
|
4746
|
+
process.stderr.write(`mend: ${redactCredentials(message)}
|
|
4674
4747
|
`);
|
|
4675
4748
|
process.exit(1);
|
|
4676
4749
|
};
|
|
@@ -4761,10 +4834,10 @@ var paint3 = (code) => (text) => process.stdout.isTTY === true ? `\x1B[${code}m$
|
|
|
4761
4834
|
var dim3 = paint3("2");
|
|
4762
4835
|
var green3 = paint3("32");
|
|
4763
4836
|
var cobalt = paint3("34");
|
|
4764
|
-
var say3 = (line) => process.stdout.write(`${line}
|
|
4837
|
+
var say3 = (line) => process.stdout.write(`${redactCredentials(line)}
|
|
4765
4838
|
`);
|
|
4766
4839
|
var fail3 = (message) => {
|
|
4767
|
-
process.stderr.write(`mend: ${message}
|
|
4840
|
+
process.stderr.write(`mend: ${redactCredentials(message)}
|
|
4768
4841
|
`);
|
|
4769
4842
|
process.exit(1);
|
|
4770
4843
|
};
|
|
@@ -4942,10 +5015,10 @@ import * as path12 from "node:path";
|
|
|
4942
5015
|
var paint4 = (code) => (text) => process.stdout.isTTY === true ? `[${code}m${text}[0m` : text;
|
|
4943
5016
|
var dim4 = paint4("2");
|
|
4944
5017
|
var green4 = paint4("32");
|
|
4945
|
-
var say4 = (line) => process.stdout.write(`${line}
|
|
5018
|
+
var say4 = (line) => process.stdout.write(`${redactCredentials(line)}
|
|
4946
5019
|
`);
|
|
4947
5020
|
var fail4 = (message) => {
|
|
4948
|
-
process.stderr.write(`mend: ${message}
|
|
5021
|
+
process.stderr.write(`mend: ${redactCredentials(message)}
|
|
4949
5022
|
`);
|
|
4950
5023
|
process.exit(1);
|
|
4951
5024
|
};
|
|
@@ -5569,6 +5642,208 @@ var piProfileLines = (scan) => {
|
|
|
5569
5642
|
];
|
|
5570
5643
|
};
|
|
5571
5644
|
|
|
5645
|
+
// src/run-scripts.ts
|
|
5646
|
+
var RUN_ARGV_MAX = 64;
|
|
5647
|
+
var whitespaceName = (character) => character === "\n" || character === "\r" ? "a newline" : character === " " ? "a tab" : character === " " ? "a space" : "whitespace";
|
|
5648
|
+
var runArgvIssue = (argv) => {
|
|
5649
|
+
if (argv.length > RUN_ARGV_MAX) {
|
|
5650
|
+
return `the command has ${argv.length} words and the platform takes at most ${RUN_ARGV_MAX} \xB7 put them in a script and run that`;
|
|
5651
|
+
}
|
|
5652
|
+
for (const [index, word] of argv.entries()) {
|
|
5653
|
+
const which = index === 0 ? "the program" : `argument ${index}`;
|
|
5654
|
+
if (word === "") {
|
|
5655
|
+
return `${which} is empty \xB7 the platform refuses empty arguments`;
|
|
5656
|
+
}
|
|
5657
|
+
if (word.trim() !== word) {
|
|
5658
|
+
const leading = word.trimStart() !== word;
|
|
5659
|
+
const edge = leading ? word.charAt(0) : word.charAt(word.length - 1);
|
|
5660
|
+
return `${which} ${leading ? "starts" : "ends"} with ${whitespaceName(edge)} \xB7 the platform refuses arguments with leading or trailing whitespace \xB7 trim it and run again`;
|
|
5661
|
+
}
|
|
5662
|
+
}
|
|
5663
|
+
return null;
|
|
5664
|
+
};
|
|
5665
|
+
var pickProcess = (detail, prefix) => {
|
|
5666
|
+
const processes = detail.processes ?? [];
|
|
5667
|
+
if (prefix !== null) {
|
|
5668
|
+
const matches = processes.filter((candidate) => candidate.id?.startsWith(prefix) === true);
|
|
5669
|
+
const [only] = matches;
|
|
5670
|
+
if (matches.length > 1) {
|
|
5671
|
+
return { error: `"${prefix}" matches ${matches.length} processes; type more of the id` };
|
|
5672
|
+
}
|
|
5673
|
+
if (only === void 0) return { error: `no process of this session matches "${prefix}"` };
|
|
5674
|
+
return { process: only };
|
|
5675
|
+
}
|
|
5676
|
+
const chosen = detail.currentAgent ?? processes.at(-1);
|
|
5677
|
+
if (chosen === void 0) return { error: "this session has run nothing yet" };
|
|
5678
|
+
return { process: chosen };
|
|
5679
|
+
};
|
|
5680
|
+
var SETTLED2 = /* @__PURE__ */ new Set(["completed", "failed", "stopped"]);
|
|
5681
|
+
var processRowOf = (detail, processId) => {
|
|
5682
|
+
const rows = [
|
|
5683
|
+
...detail.currentAgent === null ? [] : [detail.currentAgent],
|
|
5684
|
+
...detail.processes ?? []
|
|
5685
|
+
].filter((row) => row.id === processId);
|
|
5686
|
+
return rows.find((row) => row.exitedAt !== null) ?? rows[0];
|
|
5687
|
+
};
|
|
5688
|
+
var commandEndOf = (detail, processId = null) => {
|
|
5689
|
+
const settled = SETTLED2.has(detail.session.status);
|
|
5690
|
+
if (processId !== null) {
|
|
5691
|
+
const target = processRowOf(detail, processId);
|
|
5692
|
+
if (target !== void 0 && target.exitedAt !== null) {
|
|
5693
|
+
return { processId, status: target.status, exitCode: target.exitCode };
|
|
5694
|
+
}
|
|
5695
|
+
if (settled) {
|
|
5696
|
+
return { processId, status: detail.session.status, exitCode: target?.exitCode ?? null };
|
|
5697
|
+
}
|
|
5698
|
+
return null;
|
|
5699
|
+
}
|
|
5700
|
+
if (detail.session.status === "starting") return null;
|
|
5701
|
+
const agent = detail.currentAgent;
|
|
5702
|
+
if (agent !== null && agent.exitedAt !== null) {
|
|
5703
|
+
return { processId: agent.id ?? null, status: agent.status, exitCode: agent.exitCode };
|
|
5704
|
+
}
|
|
5705
|
+
if (settled) {
|
|
5706
|
+
return {
|
|
5707
|
+
processId: agent?.id ?? null,
|
|
5708
|
+
status: detail.session.status,
|
|
5709
|
+
exitCode: agent?.exitCode ?? null
|
|
5710
|
+
};
|
|
5711
|
+
}
|
|
5712
|
+
return null;
|
|
5713
|
+
};
|
|
5714
|
+
var exitStatusOf = (end) => end.exitCode !== null && Number.isInteger(end.exitCode) && end.exitCode >= 0 && end.exitCode <= 255 ? end.exitCode : 1;
|
|
5715
|
+
var endLine = (end) => `${end.status} \xB7 ${end.exitCode === null ? "no exit code reported" : `code ${end.exitCode}`}`;
|
|
5716
|
+
var isRefusal = (error) => error instanceof MendRequestError && error.kind === "http" && (error.status === null || !GATEWAY_STATUSES.has(error.status));
|
|
5717
|
+
var beforeDeadline = async (work, clock2, deadline) => {
|
|
5718
|
+
if (deadline === null) return { done: true, value: await work };
|
|
5719
|
+
const left = deadline - clock2.now();
|
|
5720
|
+
if (left <= 0) {
|
|
5721
|
+
work.catch(() => void 0);
|
|
5722
|
+
return { done: false };
|
|
5723
|
+
}
|
|
5724
|
+
const timer = new AbortController();
|
|
5725
|
+
try {
|
|
5726
|
+
return await Promise.race([
|
|
5727
|
+
work.then((value) => ({ done: true, value })),
|
|
5728
|
+
clock2.sleep(left, timer.signal).then(() => ({ done: false }))
|
|
5729
|
+
]);
|
|
5730
|
+
} finally {
|
|
5731
|
+
timer.abort();
|
|
5732
|
+
}
|
|
5733
|
+
};
|
|
5734
|
+
var readThrough = async (read, clock2, deadline) => {
|
|
5735
|
+
const unreachableAfterMs = clock2.unreachableAfterMs ?? 6e4;
|
|
5736
|
+
const since = clock2.now();
|
|
5737
|
+
for (; ; ) {
|
|
5738
|
+
try {
|
|
5739
|
+
return await beforeDeadline(read(), clock2, deadline);
|
|
5740
|
+
} catch (error) {
|
|
5741
|
+
if (isRefusal(error) || clock2.now() - since >= unreachableAfterMs) throw error;
|
|
5742
|
+
const pause2 = deadline === null ? clock2.pollMs ?? 1e3 : Math.min(clock2.pollMs ?? 1e3, deadline - clock2.now());
|
|
5743
|
+
if (pause2 <= 0) return { done: false };
|
|
5744
|
+
await clock2.sleep(pause2);
|
|
5745
|
+
}
|
|
5746
|
+
}
|
|
5747
|
+
};
|
|
5748
|
+
var unbounded = async (read, clock2) => {
|
|
5749
|
+
const got = await readThrough(read, clock2, null);
|
|
5750
|
+
if (!got.done) throw new Error("a read with no deadline timed out");
|
|
5751
|
+
return got.value;
|
|
5752
|
+
};
|
|
5753
|
+
var followLogs = async (options) => {
|
|
5754
|
+
const pollMs = options.pollMs ?? 250;
|
|
5755
|
+
const quietReadsAfterEnd = options.quietReadsAfterEnd ?? 2;
|
|
5756
|
+
let cursor = options.from;
|
|
5757
|
+
let quiet = 0;
|
|
5758
|
+
for (; ; ) {
|
|
5759
|
+
const from = cursor;
|
|
5760
|
+
const page = await unbounded(() => options.read(from), options);
|
|
5761
|
+
for (const chunk of page.chunks) await options.write(Buffer.from(chunk.dataBase64, "base64"));
|
|
5762
|
+
const advanced = page.nextFrom !== cursor;
|
|
5763
|
+
cursor = page.nextFrom;
|
|
5764
|
+
if (advanced) {
|
|
5765
|
+
quiet = 0;
|
|
5766
|
+
continue;
|
|
5767
|
+
}
|
|
5768
|
+
if (!options.follow) return { next: cursor, status: page.status };
|
|
5769
|
+
if (page.status === "exited" || page.status === "failed") {
|
|
5770
|
+
quiet += 1;
|
|
5771
|
+
if (quiet >= quietReadsAfterEnd) return { next: cursor, status: page.status };
|
|
5772
|
+
}
|
|
5773
|
+
await options.sleep(pollMs);
|
|
5774
|
+
}
|
|
5775
|
+
};
|
|
5776
|
+
var waitForCommand = async (options) => {
|
|
5777
|
+
const pollMs = options.pollMs ?? 1e3;
|
|
5778
|
+
let last = null;
|
|
5779
|
+
for (; ; ) {
|
|
5780
|
+
const got = await readThrough(options.read, options, options.deadline);
|
|
5781
|
+
if (!got.done) return { kind: "timeout", last };
|
|
5782
|
+
last = got.value;
|
|
5783
|
+
const end = commandEndOf(got.value, options.processId);
|
|
5784
|
+
if (end !== null) return { kind: "ended", end };
|
|
5785
|
+
const left = options.deadline === null ? pollMs : Math.min(pollMs, options.deadline - options.now());
|
|
5786
|
+
if (left <= 0) return { kind: "timeout", last };
|
|
5787
|
+
await options.sleep(left);
|
|
5788
|
+
}
|
|
5789
|
+
};
|
|
5790
|
+
var splitArgs = (args, valued, switches) => {
|
|
5791
|
+
const values = /* @__PURE__ */ new Map();
|
|
5792
|
+
const on = /* @__PURE__ */ new Set();
|
|
5793
|
+
let session = null;
|
|
5794
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
5795
|
+
const arg = args[index] ?? "";
|
|
5796
|
+
if (valued.includes(arg)) {
|
|
5797
|
+
const value = args[index + 1];
|
|
5798
|
+
if (value === void 0) return { error: `${arg} needs a value` };
|
|
5799
|
+
values.set(arg, value);
|
|
5800
|
+
index += 1;
|
|
5801
|
+
} else if (switches.includes(arg)) {
|
|
5802
|
+
on.add(arg);
|
|
5803
|
+
} else if (arg.startsWith("-")) {
|
|
5804
|
+
return { error: `unknown flag ${arg}` };
|
|
5805
|
+
} else if (session !== null) {
|
|
5806
|
+
return { error: `one session only; "${arg}" is extra` };
|
|
5807
|
+
} else {
|
|
5808
|
+
session = arg;
|
|
5809
|
+
}
|
|
5810
|
+
}
|
|
5811
|
+
return { values, on, session };
|
|
5812
|
+
};
|
|
5813
|
+
var parseLogsArgs = (args) => {
|
|
5814
|
+
const split = splitArgs(args, ["--from", "--process"], ["--follow", "-f"]);
|
|
5815
|
+
if ("error" in split) return split;
|
|
5816
|
+
const from = split.values.get("--from") ?? "0";
|
|
5817
|
+
if (!/^(0|[1-9]\d*)$/u.test(from)) return { error: "--from takes a record sequence, e.g. 0" };
|
|
5818
|
+
return {
|
|
5819
|
+
args: {
|
|
5820
|
+
session: split.session,
|
|
5821
|
+
follow: split.on.has("--follow") || split.on.has("-f"),
|
|
5822
|
+
from,
|
|
5823
|
+
process: split.values.get("--process") ?? null
|
|
5824
|
+
}
|
|
5825
|
+
};
|
|
5826
|
+
};
|
|
5827
|
+
var parseWaitArgs = (args) => {
|
|
5828
|
+
const split = splitArgs(args, ["--timeout", "--process"], ["--json"]);
|
|
5829
|
+
if ("error" in split) return split;
|
|
5830
|
+
const timeout = split.values.get("--timeout");
|
|
5831
|
+
const seconds2 = timeout === void 0 ? null : Number(timeout);
|
|
5832
|
+
if (seconds2 !== null && !(Number.isFinite(seconds2) && seconds2 > 0)) {
|
|
5833
|
+
return { error: "--timeout takes a number of seconds above 0" };
|
|
5834
|
+
}
|
|
5835
|
+
return {
|
|
5836
|
+
args: {
|
|
5837
|
+
session: split.session,
|
|
5838
|
+
process: split.values.get("--process") ?? null,
|
|
5839
|
+
timeoutMs: seconds2 === null ? null : Math.ceil(seconds2 * 1e3),
|
|
5840
|
+
json: split.on.has("--json")
|
|
5841
|
+
}
|
|
5842
|
+
};
|
|
5843
|
+
};
|
|
5844
|
+
var WAIT_TIMED_OUT = 124;
|
|
5845
|
+
var isSessionId = (word) => /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(word);
|
|
5846
|
+
|
|
5572
5847
|
// src/secret-files.ts
|
|
5573
5848
|
import * as path14 from "node:path";
|
|
5574
5849
|
var secretFilePathOf = (input, home) => {
|
|
@@ -8337,7 +8612,7 @@ var nodeServerRuntime = () => {
|
|
|
8337
8612
|
},
|
|
8338
8613
|
randomBytes,
|
|
8339
8614
|
sleep: (milliseconds) => new Promise((resolve8) => setTimeout(resolve8, milliseconds)),
|
|
8340
|
-
writeLine: (line) => process.stdout.write(`${line}
|
|
8615
|
+
writeLine: (line) => process.stdout.write(`${redactCredentials(line)}
|
|
8341
8616
|
`),
|
|
8342
8617
|
dockerDaemonFacts: hostDockerDaemonFacts,
|
|
8343
8618
|
readLogin: (configDir) => savedLogin(configDir, environment)
|
|
@@ -9317,7 +9592,7 @@ var ansi = (code) => (text) => process.stdout.isTTY === true ? `\x1B[${code}m${t
|
|
|
9317
9592
|
var dim5 = ansi("2");
|
|
9318
9593
|
var green5 = ansi("32");
|
|
9319
9594
|
var warn = ansi("33");
|
|
9320
|
-
var say5 = (line) => console.log(line);
|
|
9595
|
+
var say5 = (line) => console.log(redactCredentials(line));
|
|
9321
9596
|
var sshConfigPath = () => path19.join(os12.homedir(), ".ssh", "config");
|
|
9322
9597
|
var flagValue2 = (args, flag) => {
|
|
9323
9598
|
const index = args.indexOf(flag);
|
|
@@ -9757,10 +10032,10 @@ var upgradeUrl = async (input) => {
|
|
|
9757
10032
|
var paint5 = (code) => (text) => process.stdout.isTTY === true ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
9758
10033
|
var dim6 = paint5("2");
|
|
9759
10034
|
var green6 = paint5("32");
|
|
9760
|
-
var say6 = (line) => process.stdout.write(`${line}
|
|
10035
|
+
var say6 = (line) => process.stdout.write(`${redactCredentials(line)}
|
|
9761
10036
|
`);
|
|
9762
10037
|
var fail6 = (message) => {
|
|
9763
|
-
process.stderr.write(`mend: ${message}
|
|
10038
|
+
process.stderr.write(`mend: ${redactCredentials(message)}
|
|
9764
10039
|
`);
|
|
9765
10040
|
process.exit(1);
|
|
9766
10041
|
};
|
|
@@ -9922,7 +10197,7 @@ var loadConfig = () => {
|
|
|
9922
10197
|
};
|
|
9923
10198
|
};
|
|
9924
10199
|
var fail7 = (message) => {
|
|
9925
|
-
process.stderr.write(`mend: ${message}
|
|
10200
|
+
process.stderr.write(`mend: ${redactCredentials(message)}
|
|
9926
10201
|
`);
|
|
9927
10202
|
process.exit(1);
|
|
9928
10203
|
};
|
|
@@ -9933,12 +10208,16 @@ var parseMendUrl = (value) => {
|
|
|
9933
10208
|
return fail7(`"${value}" is not a valid Mend URL`);
|
|
9934
10209
|
}
|
|
9935
10210
|
};
|
|
9936
|
-
var
|
|
10211
|
+
var chrome = process.stdout;
|
|
10212
|
+
var chromeToStderr = () => {
|
|
10213
|
+
chrome = process.stderr;
|
|
10214
|
+
};
|
|
10215
|
+
var paint6 = (code) => (text) => chrome.isTTY ? `\x1B[${code}m${text}\x1B[0m` : text;
|
|
9937
10216
|
var dim7 = paint6("2");
|
|
9938
10217
|
var green7 = paint6("32");
|
|
9939
10218
|
var amber2 = paint6("33");
|
|
9940
10219
|
var cobalt2 = paint6("34");
|
|
9941
|
-
var say7 = (line) =>
|
|
10220
|
+
var say7 = (line) => chrome.write(`${redactCredentials(line)}
|
|
9942
10221
|
`);
|
|
9943
10222
|
var detachKeyEnabled = process.env["MEND_DETACH_KEY"] !== "none";
|
|
9944
10223
|
var detachHint = () => detachKeyEnabled ? ` \xB7 detach: ${dim7("Ctrl+]")}` : "";
|
|
@@ -10077,15 +10356,15 @@ var socketUrl = (config, target, params, extra, signal) => upgradeUrl({
|
|
|
10077
10356
|
});
|
|
10078
10357
|
var boundApi = (config) => (method, route, body) => api(config, method, route, body);
|
|
10079
10358
|
var withSpinner = async (label, work) => {
|
|
10080
|
-
if (
|
|
10359
|
+
if (chrome.isTTY !== true) return work;
|
|
10081
10360
|
const frames = ["\u280B", "\u2819", "\u2839", "\u2838", "\u283C", "\u2834", "\u2826", "\u2827", "\u2807", "\u280F"];
|
|
10082
10361
|
const started = Date.now();
|
|
10083
10362
|
let frame = 0;
|
|
10084
10363
|
const timer = setInterval(() => {
|
|
10085
10364
|
const seconds2 = Math.round((Date.now() - started) / 1e3);
|
|
10086
10365
|
const text = typeof label === "string" ? label : label();
|
|
10087
|
-
|
|
10088
|
-
`\r\x1B[2K ${frames[frame % frames.length]} ${text} ${dim7(`${seconds2}s`)} `
|
|
10366
|
+
chrome.write(
|
|
10367
|
+
`\r\x1B[2K ${frames[frame % frames.length]} ${redactCredentials(text)} ${dim7(`${seconds2}s`)} `
|
|
10089
10368
|
);
|
|
10090
10369
|
frame += 1;
|
|
10091
10370
|
}, 120);
|
|
@@ -10093,7 +10372,7 @@ var withSpinner = async (label, work) => {
|
|
|
10093
10372
|
return await work;
|
|
10094
10373
|
} finally {
|
|
10095
10374
|
clearInterval(timer);
|
|
10096
|
-
|
|
10375
|
+
chrome.write("\r\x1B[2K");
|
|
10097
10376
|
}
|
|
10098
10377
|
};
|
|
10099
10378
|
var followStarting = async (config, sessionId, label, start) => {
|
|
@@ -10114,7 +10393,7 @@ var followStarting = async (config, sessionId, label, start) => {
|
|
|
10114
10393
|
read: () => request(config, "GET", `/sessions/${sessionId}`),
|
|
10115
10394
|
onLine: (next) => {
|
|
10116
10395
|
line = next === "starting" ? label : next;
|
|
10117
|
-
if (
|
|
10396
|
+
if (chrome.isTTY !== true) say7(dim7(` ${next}`));
|
|
10118
10397
|
},
|
|
10119
10398
|
sleep: sleep3,
|
|
10120
10399
|
now: Date.now
|
|
@@ -10301,8 +10580,12 @@ var workspaceLinesOf = async (config, sessions) => {
|
|
|
10301
10580
|
);
|
|
10302
10581
|
};
|
|
10303
10582
|
var launch = async (config, harness, args) => {
|
|
10583
|
+
if (harness === "run") chromeToStderr();
|
|
10304
10584
|
const parsed = parseLaunchArgs(args);
|
|
10305
10585
|
if (parsed.error !== null) return fail7(parsed.error);
|
|
10586
|
+
if (harness !== "run" && parsed.json) {
|
|
10587
|
+
return fail7(`mend ${harness} takes no --json \xB7 mend run --json prints what it started`);
|
|
10588
|
+
}
|
|
10306
10589
|
const structured = parsed.prompt !== null || parsed.model !== null || parsed.effort !== null || parsed.ask || parsed.fast;
|
|
10307
10590
|
if (harness === "run" && structured) {
|
|
10308
10591
|
return fail7(`mend run takes no prompt or harness flags \xB7 ${usageOf("run")}`);
|
|
@@ -10312,15 +10595,17 @@ var launch = async (config, harness, args) => {
|
|
|
10312
10595
|
"mend run takes no landing flags \u2014 a command has no turns to land after; land it with mend land"
|
|
10313
10596
|
);
|
|
10314
10597
|
}
|
|
10315
|
-
if (harness === "run" &&
|
|
10598
|
+
if (harness === "run" && parsed.foreground) {
|
|
10316
10599
|
return fail7(
|
|
10317
|
-
"mend run takes no
|
|
10600
|
+
"mend run takes no --foreground \u2014 Ctrl+C stops watching, not the command; mend stop ends it"
|
|
10318
10601
|
);
|
|
10319
10602
|
}
|
|
10320
10603
|
const argv = harness === "run" ? parsed.custom : HARNESS_COMMANDS[harness] ?? [];
|
|
10321
10604
|
if (argv.length === 0) {
|
|
10322
10605
|
return fail7(harness === "run" ? usageOf("run") : `unknown harness ${harness}`);
|
|
10323
10606
|
}
|
|
10607
|
+
const argvIssue = harness === "run" ? runArgvIssue(argv) : null;
|
|
10608
|
+
if (argvIssue !== null) return fail7(`${argvIssue} \xB7 nothing was created`);
|
|
10324
10609
|
const project2 = await findProject(config, parsed.project, true);
|
|
10325
10610
|
say7(
|
|
10326
10611
|
`${green7("\u2713")} project ${project2.name} ${dim7(`\xB7 ${project2.defaultBranch}${parsed.project === null ? " \xB7 from cwd" : ""}`)}`
|
|
@@ -10392,7 +10677,7 @@ var launch = async (config, harness, args) => {
|
|
|
10392
10677
|
);
|
|
10393
10678
|
say7(`${cobalt2(" watch")} \xB7 ${config.url}/sessions/${session.id}`);
|
|
10394
10679
|
if (harness === "run") {
|
|
10395
|
-
return supervisedRun(config, session, argv);
|
|
10680
|
+
return supervisedRun(config, session, argv, { detach: parsed.detach, json: parsed.json });
|
|
10396
10681
|
}
|
|
10397
10682
|
const launchBody = {
|
|
10398
10683
|
...parsed.prompt === null ? {} : { prompt: parsed.prompt },
|
|
@@ -10622,7 +10907,7 @@ var attachTty = async (config, sessionId, harness, from, processId, options) =>
|
|
|
10622
10907
|
watching = true;
|
|
10623
10908
|
const members = await request(config, "GET", "/organization/members").catch(() => []);
|
|
10624
10909
|
process.stdout.write(
|
|
10625
|
-
`\r\x1B[2K${dim7(watchNotice(ownerNameOf(detail?.session.ownerUserId, members)))}\r
|
|
10910
|
+
`\r\x1B[2K${dim7(redactCredentials(watchNotice(ownerNameOf(detail?.session.ownerUserId, members))))}\r
|
|
10626
10911
|
${dim7(`read-only \xB7 ${detachKeyEnabled ? "Ctrl+] or " : ""}Ctrl+C detaches`)}\r
|
|
10627
10912
|
\r
|
|
10628
10913
|
`
|
|
@@ -10668,10 +10953,7 @@ ${dim7(`read-only \xB7 ${detachKeyEnabled ? "Ctrl+] or " : ""}Ctrl+C detaches`)}
|
|
|
10668
10953
|
ws?.removeEventListener("message", onTtyFrame);
|
|
10669
10954
|
if (rawModeEnabled) process.stdin.setRawMode(false);
|
|
10670
10955
|
if (interactive && ws !== null && process.stdout.isTTY === true) {
|
|
10671
|
-
process.stdout.write(
|
|
10672
|
-
"\x1B[<u\x1B[=0;1u\x1B[?2004l\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?1049l\x1B[?25h"
|
|
10673
|
-
// show the cursor
|
|
10674
|
-
);
|
|
10956
|
+
process.stdout.write(TERMINAL_MODES_RESET);
|
|
10675
10957
|
}
|
|
10676
10958
|
process.stdin.pause();
|
|
10677
10959
|
if (ws !== null && (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING))
|
|
@@ -10679,6 +10961,7 @@ ${dim7(`read-only \xB7 ${detachKeyEnabled ? "Ctrl+] or " : ""}Ctrl+C detaches`)}
|
|
|
10679
10961
|
stopHerdrHint();
|
|
10680
10962
|
}
|
|
10681
10963
|
};
|
|
10964
|
+
var TERMINAL_MODES_RESET = "\x1B[<u\x1B[=0;1u\x1B[?2004l\x1B[?1000l\x1B[?1002l\x1B[?1003l\x1B[?1006l\x1B[?1049l\x1B[?25h";
|
|
10682
10965
|
var stopSessionQuickly = async (config, sessionId) => {
|
|
10683
10966
|
const headers = { "content-type": "application/json" };
|
|
10684
10967
|
if (config.token !== null) headers["authorization"] = `Bearer ${config.token}`;
|
|
@@ -11121,19 +11404,48 @@ var autoConnect = async (config, service) => {
|
|
|
11121
11404
|
say7(dim7(" remote server \u2014 tunneling the port here \xB7 Ctrl-C stops the tunnel, not the Service"));
|
|
11122
11405
|
await tunnelServices(config, [service], null);
|
|
11123
11406
|
};
|
|
11407
|
+
var SERVICE_WAIT_MS = (() => {
|
|
11408
|
+
const configured = Number(process.env["MEND_SERVICE_WAIT_MS"]);
|
|
11409
|
+
return Number.isFinite(configured) && configured > 0 ? configured : 9e4;
|
|
11410
|
+
})();
|
|
11411
|
+
var withinServiceWait = async (work, deadline) => {
|
|
11412
|
+
const bounded = await beforeDeadline(work, clock, deadline);
|
|
11413
|
+
if (bounded.done) return bounded.value;
|
|
11414
|
+
process.stderr.write(
|
|
11415
|
+
`mend: no answer within ${Math.round(SERVICE_WAIT_MS / 1e3)} s \xB7 the Service may still be starting \xB7 mend service list
|
|
11416
|
+
`
|
|
11417
|
+
);
|
|
11418
|
+
return exitFlushed(WAIT_TIMED_OUT);
|
|
11419
|
+
};
|
|
11420
|
+
var failUnlessAnswered = (config, service, wait) => {
|
|
11421
|
+
if (!wait) return;
|
|
11422
|
+
const name = service.label ?? service.id.slice(0, 8);
|
|
11423
|
+
if (service.status !== "reachable") {
|
|
11424
|
+
fail7(
|
|
11425
|
+
`nothing answered on :${service.workspacePort} \xB7 ${service.status} \xB7 the Service keeps running \xB7 mend service logs ${name}`
|
|
11426
|
+
);
|
|
11427
|
+
}
|
|
11428
|
+
if (willAutoConnect(config, service, false)) {
|
|
11429
|
+
say7(dim7(` connect: mend service connect ${name}`));
|
|
11430
|
+
}
|
|
11431
|
+
};
|
|
11124
11432
|
var serviceRun = async (config, args) => {
|
|
11125
11433
|
const dashdash = args.indexOf("--");
|
|
11126
11434
|
const usage2 = usageOf("service run");
|
|
11435
|
+
const wait = (dashdash === -1 ? args : args.slice(0, dashdash)).includes("--wait");
|
|
11436
|
+
const deadline = wait ? clock.now() + SERVICE_WAIT_MS : null;
|
|
11127
11437
|
if (dashdash === -1) {
|
|
11128
11438
|
const positionals = args.filter((a) => !a.startsWith("--"));
|
|
11129
11439
|
const name2 = positionals.at(-1);
|
|
11130
11440
|
if (name2 === void 0) return fail7(usage2);
|
|
11131
11441
|
const prefix2 = positionals.length > 1 ? positionals[0] : void 0;
|
|
11132
|
-
const session2 = await
|
|
11133
|
-
|
|
11134
|
-
|
|
11135
|
-
|
|
11136
|
-
|
|
11442
|
+
const session2 = await withinServiceWait(
|
|
11443
|
+
resolveLiveSession(config, prefix2, "service run"),
|
|
11444
|
+
deadline
|
|
11445
|
+
);
|
|
11446
|
+
const recipes = await withinServiceWait(
|
|
11447
|
+
api(config, "GET", `/sessions/${session2.id}/recipes`),
|
|
11448
|
+
deadline
|
|
11137
11449
|
);
|
|
11138
11450
|
const recipe = recipes.find((entry) => entry.name === name2);
|
|
11139
11451
|
if (recipe === void 0) {
|
|
@@ -11142,16 +11454,28 @@ var serviceRun = async (config, args) => {
|
|
|
11142
11454
|
recipes.length === 0 ? `no mend.toml recipes in this worktree \u2014 declare [service.${name2}] first` : `no recipe named "${name2}" \u2014 declared: ${known}`
|
|
11143
11455
|
);
|
|
11144
11456
|
}
|
|
11457
|
+
if (wait && recipe.protocol === "udp") {
|
|
11458
|
+
return fail7(`--wait needs a TCP port: ${recipe.name} is UDP, which has no probe`);
|
|
11459
|
+
}
|
|
11460
|
+
if (wait && recipe.command === null) {
|
|
11461
|
+
return fail7(
|
|
11462
|
+
`--wait needs a command Mend starts: ${recipe.name} declares only a port, which Mend adopts with one probe \xB7 run it without --wait, or give the recipe a command`
|
|
11463
|
+
);
|
|
11464
|
+
}
|
|
11145
11465
|
const service2 = await withSpinner(
|
|
11146
11466
|
recipe.command === null ? `adopting ${recipe.name} on :${recipe.port}\u2026` : recipe.protocol === "udp" ? `starting ${recipe.name} (udp :${recipe.port})\u2026` : `starting ${recipe.name} \u2014 waiting for :${recipe.port} to answer\u2026`,
|
|
11147
|
-
|
|
11148
|
-
|
|
11149
|
-
|
|
11467
|
+
withinServiceWait(
|
|
11468
|
+
mutateService(config, "POST", `/sessions/${session2.id}/services/recipe`, {
|
|
11469
|
+
name: recipe.name
|
|
11470
|
+
}),
|
|
11471
|
+
deadline
|
|
11472
|
+
)
|
|
11150
11473
|
);
|
|
11151
|
-
const tunneling2 = willAutoConnect(config, service2, args.includes("--no-connect"));
|
|
11474
|
+
const tunneling2 = willAutoConnect(config, service2, args.includes("--no-connect") || wait);
|
|
11152
11475
|
say7(`${green7("\u2713")} Service ${service2.label ?? ""} \xB7 ${service2.status}`);
|
|
11153
11476
|
printServiceEndpoint(config, service2, tunneling2);
|
|
11154
11477
|
say7(dim7(` logs: mend service logs ${service2.label ?? service2.id.slice(0, 8)}`));
|
|
11478
|
+
failUnlessAnswered(config, service2, wait);
|
|
11155
11479
|
if (tunneling2) await autoConnect(config, service2);
|
|
11156
11480
|
return;
|
|
11157
11481
|
}
|
|
@@ -11169,24 +11493,34 @@ var serviceRun = async (config, args) => {
|
|
|
11169
11493
|
if (http && https) return fail7(usage2);
|
|
11170
11494
|
const browserScheme = https ? "https" : http ? "http" : null;
|
|
11171
11495
|
if (protocol === "udp" && browserScheme !== null) return fail7(usage2);
|
|
11496
|
+
if (protocol === "udp" && wait) {
|
|
11497
|
+
return fail7("--wait needs a TCP port: UDP has no probe, so nothing could be waited for");
|
|
11498
|
+
}
|
|
11172
11499
|
const prefix = head.find(
|
|
11173
|
-
(a, i) => !a.startsWith("--") && i !== portFlag + 1 && i !== nameFlag + 1
|
|
11500
|
+
(a, i) => !a.startsWith("--") && (portFlag === -1 || i !== portFlag + 1) && (nameFlag === -1 || i !== nameFlag + 1)
|
|
11501
|
+
);
|
|
11502
|
+
const session = await withinServiceWait(
|
|
11503
|
+
resolveLiveSession(config, prefix, "service run"),
|
|
11504
|
+
deadline
|
|
11174
11505
|
);
|
|
11175
|
-
const session = await resolveLiveSession(config, prefix, "service run");
|
|
11176
11506
|
const service = await withSpinner(
|
|
11177
11507
|
protocol === "udp" ? `starting ${name ?? argv[0]} (udp :${port})\u2026` : `starting ${name ?? argv[0]} \u2014 waiting for :${port} to answer\u2026`,
|
|
11178
|
-
|
|
11179
|
-
|
|
11180
|
-
|
|
11181
|
-
|
|
11182
|
-
|
|
11183
|
-
|
|
11184
|
-
|
|
11508
|
+
withinServiceWait(
|
|
11509
|
+
mutateService(config, "POST", `/sessions/${session.id}/services/run`, {
|
|
11510
|
+
argv,
|
|
11511
|
+
port,
|
|
11512
|
+
name,
|
|
11513
|
+
protocol,
|
|
11514
|
+
browserScheme
|
|
11515
|
+
}),
|
|
11516
|
+
deadline
|
|
11517
|
+
)
|
|
11185
11518
|
);
|
|
11186
|
-
const tunneling = willAutoConnect(config, service, head.includes("--no-connect"));
|
|
11519
|
+
const tunneling = willAutoConnect(config, service, head.includes("--no-connect") || wait);
|
|
11187
11520
|
say7(`${green7("\u2713")} Service ${service.label ?? ""} \xB7 ${service.status}`);
|
|
11188
11521
|
printServiceEndpoint(config, service, tunneling);
|
|
11189
11522
|
say7(dim7(` logs: mend service logs ${service.label ?? service.id.slice(0, 8)}`));
|
|
11523
|
+
failUnlessAnswered(config, service, wait);
|
|
11190
11524
|
if (tunneling) await autoConnect(config, service);
|
|
11191
11525
|
};
|
|
11192
11526
|
var serviceLogs = async (config, args) => {
|
|
@@ -11308,7 +11642,7 @@ var serviceInit = async (args) => {
|
|
|
11308
11642
|
const toml = renderMendToml(proposals);
|
|
11309
11643
|
say7(dim7(`proposed ${target}:`));
|
|
11310
11644
|
say7("");
|
|
11311
|
-
process.stdout.write(toml);
|
|
11645
|
+
process.stdout.write(redactCredentials(toml));
|
|
11312
11646
|
say7("");
|
|
11313
11647
|
if (!args.includes("--yes")) {
|
|
11314
11648
|
if (process.stdin.isTTY !== true) {
|
|
@@ -11355,7 +11689,7 @@ var attachTunnels = (config, optOut) => {
|
|
|
11355
11689
|
return;
|
|
11356
11690
|
}
|
|
11357
11691
|
const rows = process.stdout.rows ?? 24;
|
|
11358
|
-
process.stdout.write(`\x1B7\x1B[${rows};1H\x1B[2K${line}\x1B8`);
|
|
11692
|
+
process.stdout.write(`\x1B7\x1B[${rows};1H\x1B[2K${redactCredentials(line)}\x1B8`);
|
|
11359
11693
|
};
|
|
11360
11694
|
const tunnels = createServiceTunnels({
|
|
11361
11695
|
listServices: () => fetchServices(config),
|
|
@@ -11634,7 +11968,7 @@ var accountsCommand = async (config) => {
|
|
|
11634
11968
|
} catch (error) {
|
|
11635
11969
|
return fail7(error instanceof Error ? error.message : String(error));
|
|
11636
11970
|
}
|
|
11637
|
-
for (const line of lines) process.stdout.write(`${line}
|
|
11971
|
+
for (const line of lines) process.stdout.write(`${redactCredentials(line)}
|
|
11638
11972
|
`);
|
|
11639
11973
|
};
|
|
11640
11974
|
var claudeGrantSecret = () => {
|
|
@@ -11645,7 +11979,7 @@ var doctorBundle = (config, args) => doctorBundleCommand(args, {
|
|
|
11645
11979
|
defaultDir: path21.join(mendCliHome(), "bundles"),
|
|
11646
11980
|
now: () => /* @__PURE__ */ new Date(),
|
|
11647
11981
|
say: say7,
|
|
11648
|
-
warn: (line) => process.stderr.write(`${line}
|
|
11982
|
+
warn: (line) => process.stderr.write(`${redactCredentials(line)}
|
|
11649
11983
|
`),
|
|
11650
11984
|
collectors: (tail) => bundleCollectors({
|
|
11651
11985
|
cliVersion: cliVersion(),
|
|
@@ -11833,7 +12167,7 @@ var connectCommand = async (config, args) => {
|
|
|
11833
12167
|
secret: narrowed.secret
|
|
11834
12168
|
})
|
|
11835
12169
|
);
|
|
11836
|
-
process.stdout.write(`${accountLine(account)}
|
|
12170
|
+
process.stdout.write(`${redactCredentials(accountLine(account))}
|
|
11837
12171
|
`);
|
|
11838
12172
|
const facts = claudeGrantFacts(narrowed.secret);
|
|
11839
12173
|
if (facts !== null) {
|
|
@@ -12443,6 +12777,7 @@ _mend() {
|
|
|
12443
12777
|
'adopt:adopt a repository into the store'
|
|
12444
12778
|
'codex:new session + codex' 'claude:new session + claude' 'opencode:new session + opencode' 'pi:new session + pi'
|
|
12445
12779
|
'run:new session + arbitrary command'
|
|
12780
|
+
'logs:recorded terminal output of a session' 'wait:wait for the command of a session to end'
|
|
12446
12781
|
'attach:reattach to a running session' 'stop:stop the agent \u2014 record and review remain'
|
|
12447
12782
|
'shell:open a shell in a live session workspace'
|
|
12448
12783
|
'service:reachable ports \u2014 add, list, stop'
|
|
@@ -12479,7 +12814,7 @@ _mend() {
|
|
|
12479
12814
|
esac
|
|
12480
12815
|
fi
|
|
12481
12816
|
;;
|
|
12482
|
-
shell|attach|stop|continue|resume|rejoin|land|pull)
|
|
12817
|
+
shell|attach|stop|continue|resume|rejoin|land|pull|logs|wait)
|
|
12483
12818
|
local -a sessions
|
|
12484
12819
|
sessions=(\${(f)"$(command mend __complete session 2>/dev/null | tr '\\t' ':')"})
|
|
12485
12820
|
(( \${#sessions} )) && _describe 'session' sessions
|
|
@@ -12491,7 +12826,7 @@ _mend "$@"
|
|
|
12491
12826
|
var BASH_COMPLETIONS = `_mend() {
|
|
12492
12827
|
local cur=\${COMP_WORDS[COMP_CWORD]}
|
|
12493
12828
|
if [ "$COMP_CWORD" -eq 1 ]; then
|
|
12494
|
-
COMPREPLY=( $(compgen -W "adopt codex claude opencode pi run attach stop shell service server uninstall keys git-author skills memory connect pair doctor continue resume rejoin land pull refresh projects sessions status ui help" -- "$cur") )
|
|
12829
|
+
COMPREPLY=( $(compgen -W "adopt codex claude opencode pi run logs wait attach stop shell service server uninstall keys git-author skills memory connect pair doctor continue resume rejoin land pull refresh projects sessions status ui help" -- "$cur") )
|
|
12495
12830
|
return
|
|
12496
12831
|
fi
|
|
12497
12832
|
case \${COMP_WORDS[1]} in
|
|
@@ -12509,7 +12844,7 @@ var BASH_COMPLETIONS = `_mend() {
|
|
|
12509
12844
|
fi
|
|
12510
12845
|
COMPREPLY=( $(compgen -W "$options" -- "$cur") )
|
|
12511
12846
|
;;
|
|
12512
|
-
shell|attach|stop|continue|resume|rejoin|land|pull)
|
|
12847
|
+
shell|attach|stop|continue|resume|rejoin|land|pull|logs|wait)
|
|
12513
12848
|
COMPREPLY=( $(compgen -W "$(command mend __complete session 2>/dev/null | cut -f1)" -- "$cur") )
|
|
12514
12849
|
;;
|
|
12515
12850
|
esac
|
|
@@ -12528,57 +12863,292 @@ var completionsCommand = (args) => {
|
|
|
12528
12863
|
return fail7(`${usageOf("completions")} \xB7 e.g. mend completions zsh > "$fpath[1]/_mend"`);
|
|
12529
12864
|
}
|
|
12530
12865
|
};
|
|
12531
|
-
var
|
|
12532
|
-
const
|
|
12866
|
+
var pause = (ms, signal) => {
|
|
12867
|
+
const { promise, resolve: resolve8 } = Promise.withResolvers();
|
|
12868
|
+
const timer = setTimeout(resolve8, ms);
|
|
12869
|
+
signal?.addEventListener(
|
|
12870
|
+
"abort",
|
|
12871
|
+
() => {
|
|
12872
|
+
clearTimeout(timer);
|
|
12873
|
+
resolve8();
|
|
12874
|
+
},
|
|
12875
|
+
{ once: true }
|
|
12876
|
+
);
|
|
12877
|
+
return promise;
|
|
12878
|
+
};
|
|
12879
|
+
var clock = { sleep: pause, now: Date.now };
|
|
12880
|
+
var printJson = (value) => {
|
|
12881
|
+
process.stdout.write(`${redactCredentials(JSON.stringify(value, null, 2))}
|
|
12882
|
+
`);
|
|
12883
|
+
};
|
|
12884
|
+
var outputOnTerminal = false;
|
|
12885
|
+
var stdoutErrorsHandled = false;
|
|
12886
|
+
var outputStopped = false;
|
|
12887
|
+
var restoreTerminal = () => {
|
|
12888
|
+
if (!outputOnTerminal || process.stdout.isTTY !== true) return;
|
|
12889
|
+
outputOnTerminal = false;
|
|
12890
|
+
process.stdout.write(`${TERMINAL_MODES_RESET}\x1B[0m`);
|
|
12891
|
+
};
|
|
12892
|
+
process.once("exit", restoreTerminal);
|
|
12893
|
+
var writeOutput = (bytes) => {
|
|
12894
|
+
if (outputStopped) return Promise.reject(new Error("stopped by a signal"));
|
|
12895
|
+
if (process.stdout.isTTY === true) outputOnTerminal = true;
|
|
12896
|
+
if (!stdoutErrorsHandled) {
|
|
12897
|
+
stdoutErrorsHandled = true;
|
|
12898
|
+
process.stdout.on("error", () => void 0);
|
|
12899
|
+
}
|
|
12900
|
+
return new Promise((resolve8, reject) => {
|
|
12901
|
+
process.stdout.write(bytes, (error) => {
|
|
12902
|
+
if (error === null || error === void 0) resolve8();
|
|
12903
|
+
else reject(error);
|
|
12904
|
+
});
|
|
12905
|
+
});
|
|
12906
|
+
};
|
|
12907
|
+
var flushed = (stream) => new Promise((resolve8) => {
|
|
12908
|
+
if (stream.destroyed || !stream.writable) resolve8();
|
|
12909
|
+
else stream.write("", () => resolve8());
|
|
12910
|
+
});
|
|
12911
|
+
var EXIT_FLUSH_GRACE_MS = 5e3;
|
|
12912
|
+
var exitFlushed = async (code) => {
|
|
12913
|
+
restoreTerminal();
|
|
12914
|
+
const flush = await beforeDeadline(
|
|
12915
|
+
Promise.all([flushed(process.stdout), flushed(process.stderr)]),
|
|
12916
|
+
clock,
|
|
12917
|
+
clock.now() + EXIT_FLUSH_GRACE_MS
|
|
12918
|
+
);
|
|
12919
|
+
if (!flush.done) {
|
|
12920
|
+
process.stderr.write(
|
|
12921
|
+
`mend: output may be incomplete \xB7 the reader took none of it for ${EXIT_FLUSH_GRACE_MS / 1e3} s
|
|
12922
|
+
`
|
|
12923
|
+
);
|
|
12924
|
+
process.exit(code === 0 ? 1 : code);
|
|
12925
|
+
}
|
|
12926
|
+
process.exit(code);
|
|
12927
|
+
};
|
|
12928
|
+
var failFlushed = (message) => {
|
|
12929
|
+
process.stderr.write(`mend: ${redactCredentials(message)}
|
|
12930
|
+
`);
|
|
12931
|
+
return exitFlushed(1);
|
|
12932
|
+
};
|
|
12933
|
+
var stopOutputOnSignal = (line) => {
|
|
12934
|
+
const signals = [
|
|
12935
|
+
["SIGHUP", 129],
|
|
12936
|
+
["SIGINT", 130],
|
|
12937
|
+
["SIGTERM", 143]
|
|
12938
|
+
];
|
|
12939
|
+
const handlers = signals.map(([signal, code]) => {
|
|
12940
|
+
const handler = () => {
|
|
12941
|
+
if (outputStopped) {
|
|
12942
|
+
restoreTerminal();
|
|
12943
|
+
process.exit(code);
|
|
12944
|
+
}
|
|
12945
|
+
outputStopped = true;
|
|
12946
|
+
say7("");
|
|
12947
|
+
say7(`${amber2("\xB7")} ${line}`);
|
|
12948
|
+
void exitFlushed(code);
|
|
12949
|
+
};
|
|
12950
|
+
process.on(signal, handler);
|
|
12951
|
+
return () => process.off(signal, handler);
|
|
12952
|
+
});
|
|
12953
|
+
return () => {
|
|
12954
|
+
for (const undo of handlers) undo();
|
|
12955
|
+
};
|
|
12956
|
+
};
|
|
12957
|
+
var readLogPage = (config, processId, from) => request(
|
|
12958
|
+
config,
|
|
12959
|
+
"GET",
|
|
12960
|
+
`/processes/${processId}/logs?from=${encodeURIComponent(from)}&limit=256`
|
|
12961
|
+
);
|
|
12962
|
+
var writeProcessLogs = (config, processId, from, follow) => followLogs({
|
|
12963
|
+
from,
|
|
12964
|
+
follow,
|
|
12965
|
+
read: (cursor) => readLogPage(config, processId, cursor),
|
|
12966
|
+
write: writeOutput,
|
|
12967
|
+
...clock
|
|
12968
|
+
});
|
|
12969
|
+
var waitForSessionCommand = (config, sessionId, processId, deadline) => waitForCommand({
|
|
12970
|
+
read: () => request(config, "GET", `/sessions/${sessionId}`),
|
|
12971
|
+
processId,
|
|
12972
|
+
deadline,
|
|
12973
|
+
...clock
|
|
12974
|
+
});
|
|
12975
|
+
var runJsonOf = (config, session, processId, state) => ({
|
|
12976
|
+
version: 1,
|
|
12977
|
+
sessionId: session.id,
|
|
12978
|
+
processId,
|
|
12979
|
+
worktree: session.worktree,
|
|
12980
|
+
branch: session.branch,
|
|
12981
|
+
url: `${config.url}/sessions/${session.id}`,
|
|
12982
|
+
status: state.status,
|
|
12983
|
+
exitCode: state.exitCode
|
|
12984
|
+
});
|
|
12985
|
+
var observedState = (detail, processId, fallback) => {
|
|
12986
|
+
if (detail === null) return { status: fallback, exitCode: null };
|
|
12987
|
+
const row = (processId === null ? void 0 : processRowOf(detail, processId)) ?? detail.currentAgent;
|
|
12988
|
+
return row === null ? { status: detail.session.status, exitCode: null } : { status: row.status, exitCode: row.exitCode };
|
|
12989
|
+
};
|
|
12990
|
+
var exitWithCommandEnd = (config, session, end, json2, undelivered = null) => {
|
|
12991
|
+
const commandCode = exitStatusOf(end);
|
|
12992
|
+
const code = undelivered !== null && commandCode === 0 ? 1 : commandCode;
|
|
12993
|
+
say7(`${commandCode === 0 ? green7("\u2713") : amber2("\xB7")} ${endLine(end)} \xB7 recorded`);
|
|
12994
|
+
if (undelivered !== null) {
|
|
12995
|
+
say7(
|
|
12996
|
+
`${amber2("\xB7")} output not delivered \xB7 ${undelivered} \xB7 read it with mend logs ${session.id.slice(0, 8)}`
|
|
12997
|
+
);
|
|
12998
|
+
}
|
|
12999
|
+
say7(`${cobalt2(" review")} \xB7 ${config.url}/sessions/${session.id}`);
|
|
13000
|
+
if (json2) printJson(runJsonOf(config, session, end.processId, end));
|
|
13001
|
+
return exitFlushed(code);
|
|
13002
|
+
};
|
|
13003
|
+
var supervisedRun = async (config, session, argv, options) => {
|
|
13004
|
+
const outcome = await followStarting(
|
|
12533
13005
|
config,
|
|
12534
13006
|
session.id,
|
|
12535
13007
|
"provisioning workspace \u2014 a first launch builds the harness image (can take minutes)\u2026",
|
|
12536
13008
|
request(config, "POST", `/sessions/${session.id}/launch`, { argv })
|
|
12537
13009
|
);
|
|
13010
|
+
const ranAndEnded = outcome.kind === "settled" ? await request(config, "GET", `/sessions/${session.id}`).catch(() => null) : null;
|
|
13011
|
+
if (outcome.kind !== "live" && ranAndEnded?.currentAgent?.id === void 0) {
|
|
13012
|
+
startedOrExit(config, session.id, outcome);
|
|
13013
|
+
return;
|
|
13014
|
+
}
|
|
13015
|
+
const detail = ranAndEnded ?? await api(config, "GET", `/sessions/${session.id}`);
|
|
13016
|
+
const processId = detail.currentAgent?.id ?? null;
|
|
13017
|
+
const id8 = session.id.slice(0, 8);
|
|
13018
|
+
if (options.detach) {
|
|
13019
|
+
const ended = commandEndOf(detail, processId);
|
|
13020
|
+
if (ended === null) {
|
|
13021
|
+
say7(`${green7("\u2713 recording")} \xB7 running detached \xB7 session ${dim7(id8)}`);
|
|
13022
|
+
say7(`${cobalt2(" output")} \xB7 mend logs ${id8} --follow`);
|
|
13023
|
+
say7(`${cobalt2(" wait")} \xB7 mend wait ${id8}`);
|
|
13024
|
+
} else {
|
|
13025
|
+
say7(`${amber2("\xB7")} ended before detaching \xB7 ${endLine(ended)} \xB7 session ${dim7(id8)}`);
|
|
13026
|
+
say7(`${cobalt2(" output")} \xB7 mend logs ${id8}`);
|
|
13027
|
+
}
|
|
13028
|
+
if (options.json) {
|
|
13029
|
+
printJson(
|
|
13030
|
+
runJsonOf(
|
|
13031
|
+
config,
|
|
13032
|
+
session,
|
|
13033
|
+
processId,
|
|
13034
|
+
ended ?? observedState(detail, processId, detail.session.status)
|
|
13035
|
+
)
|
|
13036
|
+
);
|
|
13037
|
+
}
|
|
13038
|
+
return;
|
|
13039
|
+
}
|
|
12538
13040
|
say7(
|
|
12539
|
-
`${green7("\u2713 recording")} \xB7
|
|
13041
|
+
`${green7("\u2713 recording")} \xB7 ${argv[0] ?? "command"} \xB7 workspace mounts the worktree \xB7 Ctrl+C stops watching, not the command`
|
|
12540
13042
|
);
|
|
12541
|
-
say7(dim7(` ${argv.join(" ")} \u2014 live record:`));
|
|
12542
13043
|
say7("");
|
|
12543
|
-
|
|
12544
|
-
if (
|
|
12545
|
-
|
|
12546
|
-
|
|
12547
|
-
|
|
12548
|
-
|
|
12549
|
-
|
|
12550
|
-
|
|
12551
|
-
|
|
12552
|
-
const { done, value } = await reader.read();
|
|
12553
|
-
if (done) break;
|
|
12554
|
-
buffer += decoder.decode(value, { stream: true });
|
|
12555
|
-
const parts = buffer.split("\n\n");
|
|
12556
|
-
buffer = parts.pop() ?? "";
|
|
12557
|
-
for (const part of parts) {
|
|
12558
|
-
const data = part.split("\n").filter((line) => line.startsWith("data: ")).map((line) => line.slice(6)).join("");
|
|
12559
|
-
if (data === "") continue;
|
|
12560
|
-
let event;
|
|
12561
|
-
try {
|
|
12562
|
-
event = JSON.parse(data);
|
|
12563
|
-
} catch {
|
|
12564
|
-
continue;
|
|
12565
|
-
}
|
|
12566
|
-
if (event.sessionId !== session.id) continue;
|
|
12567
|
-
if (event.type === "session-progress" && event.line !== void 0) {
|
|
12568
|
-
say7(dim7(` ${event.line}`));
|
|
12569
|
-
}
|
|
12570
|
-
if (event.type === "session") {
|
|
12571
|
-
const detail = await api(config, "GET", `/sessions/${session.id}`);
|
|
12572
|
-
const ended = agentOutcome(detail.currentAgent) ?? (["completed", "failed", "stopped"].includes(detail.session.status) ? detail.session.status : null);
|
|
12573
|
-
if (ended !== null) settled = ended;
|
|
12574
|
-
}
|
|
13044
|
+
let undelivered = null;
|
|
13045
|
+
if (!options.json) {
|
|
13046
|
+
stopOutputOnSignal(`stopped watching \xB7 the command keeps running \xB7 mend logs ${id8} --follow`);
|
|
13047
|
+
}
|
|
13048
|
+
if (processId !== null && !options.json) {
|
|
13049
|
+
try {
|
|
13050
|
+
await writeProcessLogs(config, processId, "0", true);
|
|
13051
|
+
} catch (error) {
|
|
13052
|
+
undelivered = error instanceof Error ? error.message : String(error);
|
|
12575
13053
|
}
|
|
12576
13054
|
}
|
|
12577
|
-
await
|
|
13055
|
+
const waited = await waitForSessionCommand(config, session.id, processId, null).catch(
|
|
13056
|
+
(error) => failFlushed(error instanceof Error ? error.message : String(error))
|
|
13057
|
+
);
|
|
13058
|
+
if (waited.kind === "timeout") return failFlushed("the wait ended without a timeout");
|
|
12578
13059
|
say7("");
|
|
12579
|
-
|
|
12580
|
-
|
|
12581
|
-
|
|
13060
|
+
await exitWithCommandEnd(config, session, waited.end, options.json, undelivered);
|
|
13061
|
+
};
|
|
13062
|
+
var resolveAnySession = async (config, word) => {
|
|
13063
|
+
if (isSessionId(word)) {
|
|
13064
|
+
return (await api(config, "GET", `/sessions/${word}`)).session;
|
|
13065
|
+
}
|
|
13066
|
+
const projects = await api(config, "GET", "/projects");
|
|
13067
|
+
const details = await Promise.all(
|
|
13068
|
+
projects.map(
|
|
13069
|
+
(project2) => api(config, "GET", `/projects/${project2.id}?deadEnds=include`)
|
|
13070
|
+
)
|
|
13071
|
+
);
|
|
13072
|
+
const picked = pickSession(
|
|
13073
|
+
details.flatMap((detail) => detail.sessions),
|
|
13074
|
+
word
|
|
13075
|
+
);
|
|
13076
|
+
if ("error" in picked) return fail7(picked.error);
|
|
13077
|
+
return picked.session;
|
|
13078
|
+
};
|
|
13079
|
+
var logsCommand = async (config, args) => {
|
|
13080
|
+
chromeToStderr();
|
|
13081
|
+
const parsed = parseLogsArgs(args);
|
|
13082
|
+
if ("error" in parsed) return fail7(`${parsed.error} \xB7 ${usageOf("logs")}`);
|
|
13083
|
+
const { session: word, follow, from, process: processPrefix } = parsed.args;
|
|
13084
|
+
const session = word === null ? await resolveLiveSession(config, void 0, "logs") : await resolveAnySession(config, word);
|
|
13085
|
+
const detail = await api(config, "GET", `/sessions/${session.id}`);
|
|
13086
|
+
const picked = pickProcess(detail, processPrefix);
|
|
13087
|
+
if ("error" in picked) return fail7(`session ${session.id.slice(0, 8)} \xB7 ${picked.error}`);
|
|
13088
|
+
const target = picked.process;
|
|
13089
|
+
if (target.id === void 0 || target.sealantSessionId === null) {
|
|
13090
|
+
return fail7(
|
|
13091
|
+
`session ${session.id.slice(0, 8)} \xB7 this process has no recorded terminal (an adopted port, or a server older than process ids)`
|
|
13092
|
+
);
|
|
13093
|
+
}
|
|
13094
|
+
stopOutputOnSignal(
|
|
13095
|
+
follow ? `stopped watching \xB7 the process keeps running \xB7 mend logs ${session.id.slice(0, 8)} --follow` : "stopped reading the record"
|
|
13096
|
+
);
|
|
13097
|
+
try {
|
|
13098
|
+
const last = await writeProcessLogs(config, target.id, from, follow);
|
|
13099
|
+
restoreTerminal();
|
|
13100
|
+
if (follow) say7(dim7(`
|
|
13101
|
+
record ended \xB7 ${last.status} \xB7 next sequence ${last.next}`));
|
|
13102
|
+
} catch (error) {
|
|
13103
|
+
return failFlushed(
|
|
13104
|
+
`output not delivered \xB7 ${error instanceof Error ? error.message : String(error)}`
|
|
13105
|
+
);
|
|
13106
|
+
}
|
|
13107
|
+
await exitFlushed(0);
|
|
13108
|
+
};
|
|
13109
|
+
var waitCommand = async (config, args) => {
|
|
13110
|
+
chromeToStderr();
|
|
13111
|
+
const parsed = parseWaitArgs(args);
|
|
13112
|
+
if ("error" in parsed) return fail7(`${parsed.error} \xB7 ${usageOf("wait")}`);
|
|
13113
|
+
const { session: word, timeoutMs, json: json2, process: processPrefix } = parsed.args;
|
|
13114
|
+
const deadline = timeoutMs === null ? null : clock.now() + timeoutMs;
|
|
13115
|
+
const after = `${Math.round((timeoutMs ?? 0) / 1e3)} s`;
|
|
13116
|
+
const timedOut = async (session2, processId2, last) => {
|
|
13117
|
+
say7(
|
|
13118
|
+
session2 === null ? `${amber2("\xB7")} no session found within ${after}` : `${amber2("\xB7")} session ${session2.id.slice(0, 8)} \xB7 still running after ${after} \xB7 the command keeps running`
|
|
13119
|
+
);
|
|
13120
|
+
if (json2 && session2 !== null) {
|
|
13121
|
+
const observed = processId2 ?? last?.currentAgent?.id ?? null;
|
|
13122
|
+
printJson(
|
|
13123
|
+
runJsonOf(config, session2, observed, observedState(last, observed, session2.status))
|
|
13124
|
+
);
|
|
13125
|
+
}
|
|
13126
|
+
return exitFlushed(WAIT_TIMED_OUT);
|
|
13127
|
+
};
|
|
13128
|
+
const resolved = await beforeDeadline(
|
|
13129
|
+
word === null ? resolveLiveSession(config, void 0, "wait") : resolveAnySession(config, word),
|
|
13130
|
+
clock,
|
|
13131
|
+
deadline
|
|
13132
|
+
);
|
|
13133
|
+
if (!resolved.done) return timedOut(null, null, null);
|
|
13134
|
+
const session = resolved.value;
|
|
13135
|
+
let processId = null;
|
|
13136
|
+
if (processPrefix !== null) {
|
|
13137
|
+
const read = await beforeDeadline(
|
|
13138
|
+
api(config, "GET", `/sessions/${session.id}`),
|
|
13139
|
+
clock,
|
|
13140
|
+
deadline
|
|
13141
|
+
);
|
|
13142
|
+
if (!read.done) return timedOut(session, null, null);
|
|
13143
|
+
const picked = pickProcess(read.value, processPrefix);
|
|
13144
|
+
if ("error" in picked) return fail7(`session ${session.id.slice(0, 8)} \xB7 ${picked.error}`);
|
|
13145
|
+
processId = picked.process.id ?? null;
|
|
13146
|
+
}
|
|
13147
|
+
const waited = await waitForSessionCommand(config, session.id, processId, deadline).catch(
|
|
13148
|
+
(error) => failFlushed(error instanceof Error ? error.message : String(error))
|
|
13149
|
+
);
|
|
13150
|
+
if (waited.kind === "timeout") return timedOut(session, processId, waited.last);
|
|
13151
|
+
await exitWithCommandEnd(config, session, waited.end, json2);
|
|
12582
13152
|
};
|
|
12583
13153
|
var continueSession = async (config, args) => {
|
|
12584
13154
|
const explicitSession = args.find((a) => !a.startsWith("--")) ?? null;
|
|
@@ -12769,22 +13339,39 @@ var rejoinCommand = async (config, args) => {
|
|
|
12769
13339
|
await finishAttach(config, session.id, outcome);
|
|
12770
13340
|
exitAfterSessionEnd(config, session.id);
|
|
12771
13341
|
};
|
|
12772
|
-
var projectsCommand = async (config) => {
|
|
13342
|
+
var projectsCommand = async (config, args) => {
|
|
13343
|
+
const unknown = args.find((arg) => arg !== "--json");
|
|
13344
|
+
if (unknown !== void 0) return fail7(`unknown argument ${unknown} \xB7 ${usageOf("projects")}`);
|
|
12773
13345
|
const [projects, active] = await Promise.all([
|
|
12774
13346
|
api(config, "GET", "/projects"),
|
|
12775
13347
|
api(config, "GET", "/sessions")
|
|
12776
13348
|
]);
|
|
12777
|
-
if (projects.length === 0) {
|
|
12778
|
-
say7(dim7("no adopted projects \u2014 mend adopt brings one in"));
|
|
12779
|
-
return;
|
|
12780
|
-
}
|
|
12781
13349
|
const liveByProject = /* @__PURE__ */ new Map();
|
|
12782
13350
|
for (const session of active) {
|
|
12783
13351
|
liveByProject.set(session.projectId, (liveByProject.get(session.projectId) ?? 0) + 1);
|
|
12784
13352
|
}
|
|
13353
|
+
const here = matchProjectByCwd(projects, cwdFacts(process.cwd()));
|
|
13354
|
+
if (args.includes("--json")) {
|
|
13355
|
+
printJson({
|
|
13356
|
+
version: 1,
|
|
13357
|
+
projects: projects.map((project2) => ({
|
|
13358
|
+
id: project2.id,
|
|
13359
|
+
name: project2.name,
|
|
13360
|
+
originUrl: project2.originUrl,
|
|
13361
|
+
defaultBranch: project2.defaultBranch,
|
|
13362
|
+
storePath: project2.storePath,
|
|
13363
|
+
liveSessions: liveByProject.get(project2.id) ?? 0,
|
|
13364
|
+
current: project2.id === here?.id
|
|
13365
|
+
}))
|
|
13366
|
+
});
|
|
13367
|
+
return;
|
|
13368
|
+
}
|
|
13369
|
+
if (projects.length === 0) {
|
|
13370
|
+
say7(dim7("no adopted projects \u2014 mend adopt brings one in"));
|
|
13371
|
+
return;
|
|
13372
|
+
}
|
|
12785
13373
|
const nameWidth = Math.max(...projects.map((p) => p.name.length));
|
|
12786
13374
|
const branchWidth = Math.max(...projects.map((p) => p.defaultBranch.length));
|
|
12787
|
-
const here = matchProjectByCwd(projects, cwdFacts(process.cwd()));
|
|
12788
13375
|
for (const project2 of projects) {
|
|
12789
13376
|
const live = liveByProject.get(project2.id) ?? 0;
|
|
12790
13377
|
const liveLabel = live > 0 ? green7(`${live} live`) : dim7("\u2014");
|
|
@@ -13087,7 +13674,7 @@ var dashboard = async (config, options = {}) => {
|
|
|
13087
13674
|
);
|
|
13088
13675
|
process.exit(rerun.status ?? 0);
|
|
13089
13676
|
}
|
|
13090
|
-
const { runDashboard } = await import("./dashboard-
|
|
13677
|
+
const { runDashboard } = await import("./dashboard-CEJN6IOO.js");
|
|
13091
13678
|
const agentShare = await startShareIfBridge(config);
|
|
13092
13679
|
const tunnels = options.noTunnel === true || serverIsLocal(config) ? null : createServiceTunnels({
|
|
13093
13680
|
listServices: () => fetchServices(config),
|
|
@@ -13191,6 +13778,10 @@ var main = async () => {
|
|
|
13191
13778
|
case "pi":
|
|
13192
13779
|
case "run":
|
|
13193
13780
|
return withAgentShare(config, () => launch(config, command, rest));
|
|
13781
|
+
case "logs":
|
|
13782
|
+
return logsCommand(config, rest);
|
|
13783
|
+
case "wait":
|
|
13784
|
+
return waitCommand(config, rest);
|
|
13194
13785
|
case "attach":
|
|
13195
13786
|
return withAgentShare(config, () => attach(config, rest));
|
|
13196
13787
|
case "stop":
|
|
@@ -13266,7 +13857,7 @@ var main = async () => {
|
|
|
13266
13857
|
case "rejoin":
|
|
13267
13858
|
return withAgentShare(config, () => rejoinCommand(config, rest));
|
|
13268
13859
|
case "projects":
|
|
13269
|
-
return projectsCommand(config);
|
|
13860
|
+
return projectsCommand(config, rest);
|
|
13270
13861
|
case "refresh":
|
|
13271
13862
|
return refreshCommand(config, rest);
|
|
13272
13863
|
case "land":
|