baychat 0.21.5 → 0.23.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 +75 -1
- package/dist/approve-hook.js +5 -2
- package/dist/commands.js +6 -4
- package/dist/connect-acp.js +165 -0
- package/dist/connect-cursor.js +65 -0
- package/dist/connect-dsh.js +45 -0
- package/dist/connect-plan.js +16 -1
- package/dist/connect.js +17 -0
- package/dist/doctor-command.js +7 -0
- package/dist/doctor.js +93 -1
- package/dist/dsh-config.js +147 -0
- package/dist/index.js +27 -0
- package/dist/relay/acp/agents.js +231 -0
- package/dist/relay/acp/approval.js +67 -0
- package/dist/relay/acp/client.js +265 -0
- package/dist/relay/acp/commands.js +69 -0
- package/dist/relay/acp/cursor.js +271 -0
- package/dist/relay/acp/daemon-glue.js +198 -0
- package/dist/relay/acp/dump-config.js +31 -0
- package/dist/relay/acp/modes.js +36 -0
- package/dist/relay/acp/permissions.js +104 -0
- package/dist/relay/acp/policy.js +138 -0
- package/dist/relay/acp/presence.js +87 -0
- package/dist/relay/acp/prompt.js +68 -0
- package/dist/relay/acp/runner.js +364 -0
- package/dist/relay/acp/sdk.js +19 -0
- package/dist/relay/acp/turn-queue.js +163 -0
- package/dist/relay/acp/types.js +5 -0
- package/dist/relay/commands.js +28 -0
- package/dist/relay/daemon.js +195 -0
- package/dist/relay/mailbox.js +1 -0
- package/dist/relay/profiles.js +22 -0
- package/dist/relay/registry.js +46 -1
- package/dist/relay/session-commands.js +89 -0
- package/dist/relay/session-reply.js +25 -0
- package/dist/relay/terminal-pane.js +197 -0
- package/dist/relay/types.js +0 -9
- package/dist/runtime-binary.js +17 -0
- package/dist/runtimes.js +13 -0
- package/dist/session-command.js +16 -3
- package/dist/skill-bootstrap.js +16 -8
- package/dist/start-command.js +235 -0
- package/dist/update-command.js +201 -0
- package/package.json +6 -2
package/README.md
CHANGED
|
@@ -186,6 +186,8 @@ baychat login # once per computer: the QR
|
|
|
186
186
|
baychat connect # list the apps it can configure
|
|
187
187
|
baychat connect claude # refresh Claude Code's skill (no QR, no re-pairing)
|
|
188
188
|
baychat connect codex # configure Codex — restart Codex afterwards
|
|
189
|
+
baychat connect dsh # a DeepSeek Harness session the relay runs for you — see below
|
|
190
|
+
baychat connect cursor-cli # a Cursor CLI session the relay runs for you — see below
|
|
189
191
|
baychat relay start # the process that wakes your sessions
|
|
190
192
|
baychat relay status # transport, sessions, anything pending
|
|
191
193
|
baychat doctor # checks every link and prints what to type
|
|
@@ -350,7 +352,9 @@ everything we need.
|
|
|
350
352
|
right conversation. This needs the agent to be able to say which session it is, and to have a
|
|
351
353
|
way to resume that session without a UI (`--resume <id>` and friends).
|
|
352
354
|
|
|
353
|
-
Adapters ship for **Claude Code**, **Codex**, **Cursor** and **
|
|
355
|
+
Adapters ship for **Claude Code**, **Codex**, **Cursor**, **Hermes** and **DeepSeek Harness**
|
|
356
|
+
(`baychat connect dsh`, run by the relay over ACP rather than woken in a terminal — see above and
|
|
357
|
+
[`ACP_RELAY.md`](../../docs/features/ACP_RELAY.md)). Many others — Gemini CLI,
|
|
354
358
|
Copilot CLI, Goose, OpenCode/Crush, Qwen Code, Kimi Code CLI, CodeBuddy, iFlow, Trae, Aider — look
|
|
355
359
|
compatible on paper, with per-agent detail, exact flags, known bugs and a **date on every row** in
|
|
356
360
|
[the compatibility table](https://baychat.io/runtimes.md).
|
|
@@ -435,6 +439,76 @@ relay also falls back to it automatically when app-server cannot be used at
|
|
|
435
439
|
all — but not when the failure is a real answer about your session, because
|
|
436
440
|
retrying that would only repeat it with the reason replaced by an exit code.
|
|
437
441
|
|
|
442
|
+
## `baychat connect dsh` — DeepSeek Harness, run by the relay
|
|
443
|
+
|
|
444
|
+
```
|
|
445
|
+
baychat connect dsh [--folder <path>] [--max-mode <mode>] [--mode <mode>] [--session <name>] [--terminal]
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
Unlike every other runtime in this file, DeepSeek Harness (`dsh`) is not woken in your terminal —
|
|
449
|
+
the relay spawns it directly and drives it over **ACP** (Agent Client Protocol), one process per
|
|
450
|
+
turn. Full detail — the four modes, their honest limits, the rule file, known limits — is in
|
|
451
|
+
[`docs/features/ACP_RELAY.md`](../../docs/features/ACP_RELAY.md).
|
|
452
|
+
|
|
453
|
+
> ⚠️ **DeepSeek Harness is experimental and has not been security-audited.** DeepSeek recommends
|
|
454
|
+
> running it in a disposable VM or container, not on your everyday machine. `connect dsh` and
|
|
455
|
+
> `baychat doctor` both print this reminder every time.
|
|
456
|
+
|
|
457
|
+
| Flag | Default | Meaning |
|
|
458
|
+
|---|---|---|
|
|
459
|
+
| `--folder <path>` | asked interactively, defaulting to the current directory | the folder DeepSeek may work in — resolved and stored in `~/.baychat/relay-policy.json` |
|
|
460
|
+
| `--max-mode <mode>` | asked interactively, defaulting to `ask` | the ceiling: `/mode-*` from the chat can never go higher than this, ever |
|
|
461
|
+
| `--mode <mode>` | `chat` | the mode the session starts in |
|
|
462
|
+
| `--session <name>` | `DeepSeek` | the BayChat session name it registers and joins as |
|
|
463
|
+
| `--terminal` | off | ALSO set up `dsh` for use by hand in a terminal: the BayChat skill and a BayChat MCP entry in `~/.dsh/cordis.patch.yml`. That writes your BayChat token into that file (mode `0600`), and it applies to every dsh profile — so it is opt-in. The relay-run session needs none of it. |
|
|
464
|
+
|
|
465
|
+
Modes, weakest to strongest: `chat` (default — BayChat tools only), `read` (+ file read/search),
|
|
466
|
+
`ask` (+ shell; a write asks on your phone first), `full` (+ writes without asking, in its
|
|
467
|
+
folder). Only `chat` protects private files — every other mode can read anything your account can.
|
|
468
|
+
Windows offers `chat` and `read` only.
|
|
469
|
+
|
|
470
|
+
Who gets that power: **you, or a Bay owner/admin you gave command access**. Their messages run in
|
|
471
|
+
the session's mode and they may use `/mode-*`; anyone else's message makes that turn `chat`. The
|
|
472
|
+
ceiling still caps everything, and in `ask` the approval card still comes to **your** phone only.
|
|
473
|
+
Revoke the grant (or demote the admin) to take it away.
|
|
474
|
+
|
|
475
|
+
Re-running `connect dsh` changes the ceiling, and re-registers the session at the starting mode
|
|
476
|
+
(`--mode`, default `chat`) — it does **not** keep a mode you raised from the chat. A running relay
|
|
477
|
+
refuses to raise a live session's mode (it keeps or lowers it only), and `connect` then exits with
|
|
478
|
+
an error; to raise it now, use `/mode-…` from the chat. The starting mode `connect` wrote itself
|
|
479
|
+
still applies once the relay reloads its sessions (for example after a restart) — that is you, on
|
|
480
|
+
this computer, and it is still capped by the folder's ceiling. Re-running with a different `--folder`
|
|
481
|
+
**adds** that folder to the allowed folders; the old ones stay allowed until you remove them from
|
|
482
|
+
`~/.baychat/relay-policy.json`.
|
|
483
|
+
|
|
484
|
+
A `dsh` you run by hand in a terminal is never woken headlessly, and `baychat start` does not
|
|
485
|
+
launch `dsh` — the relay-run session is the one whose modes are enforced.
|
|
486
|
+
|
|
487
|
+
It also needs its own API key on that computer, separate from BayChat: run `dsh web` there and add
|
|
488
|
+
it on the Models page. `baychat doctor` reports whether the key exists (never its contents) and
|
|
489
|
+
whether the installed `dsh` matches the version BayChat was tested against.
|
|
490
|
+
|
|
491
|
+
## `baychat connect cursor-cli` — Cursor's CLI, run by the relay
|
|
492
|
+
|
|
493
|
+
```
|
|
494
|
+
baychat connect cursor-cli [--folder <path>] [--max-mode <mode>] [--mode <mode>] [--session <name>]
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
The same relay path as `connect dsh`, for Cursor's CLI (`agent acp`). Log it in first with
|
|
498
|
+
`agent login`; `connect cursor-cli` refuses until `agent status` says you are. Flags are as for
|
|
499
|
+
`dsh` (default session name `Cursor`; no `--terminal`). This is **not** `baychat connect cursor`,
|
|
500
|
+
which still configures the Cursor editor's MCP file.
|
|
501
|
+
|
|
502
|
+
Modes: `chat` (default — BayChat tools only), `read` (+ reads; no shell, no writes), and on Linux
|
|
503
|
+
only `ask` (edits files inside its folder without asking, edits outside it are refused, every
|
|
504
|
+
command asks on your phone first). There is no `full` for Cursor. Every mode above `chat` can read
|
|
505
|
+
anything your account can. BayChat keeps each session's Cursor config in its own folder under
|
|
506
|
+
`~/.baychat/acp/`, never in your project. A `.cursor/cli.json` or `.cursor/hooks.json` in the
|
|
507
|
+
session's folder or any folder above it stops the session (Cursor would let it override BayChat's
|
|
508
|
+
limits): remove or rename it. Worked on Cursor's free Hobby plan when checked on
|
|
509
|
+
19 Sep 2026; Pro is recommended for regular use. Detail: the Cursor section of
|
|
510
|
+
[`docs/features/ACP_RELAY.md`](../../docs/features/ACP_RELAY.md).
|
|
511
|
+
|
|
438
512
|
## Group instructions
|
|
439
513
|
|
|
440
514
|
Group conversations carry a short, server-authored **primer** — who's in the
|
package/dist/approve-hook.js
CHANGED
|
@@ -207,9 +207,12 @@ function describeToolRequest(toolName, toolInput) {
|
|
|
207
207
|
/** The question the card asks. Blunt on purpose: this is a shell on a server, read on a phone,
|
|
208
208
|
* and the safe answer has to be the obvious one. */
|
|
209
209
|
function composeQuestion(params) {
|
|
210
|
+
const who = params.agentLabel ?? "Claude Code";
|
|
211
|
+
const context = params.agentLabel
|
|
212
|
+
? "It is running in BayChat's relay; nobody is at a terminal."
|
|
213
|
+
: "This would normally be a prompt in the terminal.";
|
|
210
214
|
const lines = [
|
|
211
|
-
|
|
212
|
-
`This would normally be a prompt in the terminal.`,
|
|
215
|
+
`${who} on ${params.host} is asking permission to use ${params.toolName}. ` + context,
|
|
213
216
|
"",
|
|
214
217
|
describeToolRequest(params.toolName, params.toolInput),
|
|
215
218
|
"",
|
package/dist/commands.js
CHANGED
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.CATCHUP_UNTRUSTED_REMINDER = void 0;
|
|
6
|
+
exports.realConfigIo = exports.CATCHUP_UNTRUSTED_REMINDER = void 0;
|
|
7
7
|
exports.requireCredentials = requireCredentials;
|
|
8
8
|
exports.cmdPair = cmdPair;
|
|
9
9
|
exports.cmdWhoami = cmdWhoami;
|
|
@@ -653,7 +653,7 @@ function claudeMcpAddSpawn(platform, baseUrl, token) {
|
|
|
653
653
|
*/
|
|
654
654
|
function refreshConnectedClients(baseUrl, token) {
|
|
655
655
|
try {
|
|
656
|
-
const updated = (0, credential_refresh_1.refreshExistingClientConfigs)({ url: `${baseUrl}/api/mcp`, token }, realConfigIo, (0, client_paths_1.currentPathEnv)());
|
|
656
|
+
const updated = (0, credential_refresh_1.refreshExistingClientConfigs)({ url: `${baseUrl}/api/mcp`, token }, exports.realConfigIo, (0, client_paths_1.currentPathEnv)());
|
|
657
657
|
for (const path of updated)
|
|
658
658
|
console.log(`✓ Updated ${path} with the new credential`);
|
|
659
659
|
}
|
|
@@ -663,8 +663,10 @@ function refreshConnectedClients(baseUrl, token) {
|
|
|
663
663
|
console.log(` Could not refresh client configs: ${err instanceof Error ? err.message : String(err)}`);
|
|
664
664
|
}
|
|
665
665
|
}
|
|
666
|
-
/** Real filesystem IO for the refresh. Mirrors `connect.ts`'s writer.
|
|
667
|
-
|
|
666
|
+
/** Real filesystem IO for the refresh. Mirrors `connect.ts`'s writer.
|
|
667
|
+
* Exported so `baychat update` refreshes configs through the SAME writer a login does —
|
|
668
|
+
* two spellings of "write a client config" would eventually disagree about permissions. */
|
|
669
|
+
exports.realConfigIo = {
|
|
668
670
|
readFile: (p) => {
|
|
669
671
|
try {
|
|
670
672
|
return node_fs_1.default.readFileSync(p, "utf8");
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.realConnectAcpDeps = realConnectAcpDeps;
|
|
37
|
+
exports.connectAcpAgent = connectAcpAgent;
|
|
38
|
+
const readline = __importStar(require("readline"));
|
|
39
|
+
const commands_1 = require("./commands");
|
|
40
|
+
const config_1 = require("./config");
|
|
41
|
+
const agents_1 = require("./relay/acp/agents");
|
|
42
|
+
const modes_1 = require("./relay/acp/modes");
|
|
43
|
+
const policy_1 = require("./relay/acp/policy");
|
|
44
|
+
const presence_1 = require("./relay/acp/presence");
|
|
45
|
+
const commands_2 = require("./relay/commands");
|
|
46
|
+
const registry_1 = require("./relay/registry");
|
|
47
|
+
const runtime_binary_1 = require("./runtime-binary");
|
|
48
|
+
function askOnTerminal(question) {
|
|
49
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
50
|
+
return new Promise((resolve) => rl.question(question, (answer) => (rl.close(), resolve(answer.trim()))));
|
|
51
|
+
}
|
|
52
|
+
/** The real world, for every ACP agent's `connect`. */
|
|
53
|
+
function realConnectAcpDeps() {
|
|
54
|
+
return {
|
|
55
|
+
device: config_1.loadDeviceCredentials,
|
|
56
|
+
login: (base) => (0, commands_1.cmdLogin)({ base, hint: false }),
|
|
57
|
+
resolveBinary: (name) => (0, runtime_binary_1.resolveRuntimeBinary)(name, (0, runtime_binary_1.currentBinaryEnv)()),
|
|
58
|
+
join: async (session) => {
|
|
59
|
+
const device = (0, config_1.loadDeviceCredentials)();
|
|
60
|
+
if (!device)
|
|
61
|
+
throw new Error("not logged in");
|
|
62
|
+
await (0, presence_1.joinSession)((0, presence_1.deviceMcpCaller)(device), session);
|
|
63
|
+
},
|
|
64
|
+
registerWithRelay: commands_2.registerAcpSession,
|
|
65
|
+
ensureRelay: commands_2.ensureRelayInstalled,
|
|
66
|
+
ask: askOnTerminal,
|
|
67
|
+
print: (line) => console.log(line),
|
|
68
|
+
platform: process.platform,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
/**
|
|
72
|
+
* `baychat connect <agent>` for a relay-run (ACP) agent.
|
|
73
|
+
*
|
|
74
|
+
* ORDER MATTERS: everything that can refuse (no login, no binary, the agent's own preflight, a
|
|
75
|
+
* bad folder, a bad mode, a session name already taken by something the relay does not run)
|
|
76
|
+
* refuses BEFORE anything is written, so a failed connect leaves no half-made rule file.
|
|
77
|
+
*/
|
|
78
|
+
async function connectAcpAgent(row, spec, opts, deps) {
|
|
79
|
+
const say = deps.print;
|
|
80
|
+
let device = deps.device();
|
|
81
|
+
if (!device) {
|
|
82
|
+
say(spec.greeting);
|
|
83
|
+
if (!(await deps.login(opts.base)))
|
|
84
|
+
return 2;
|
|
85
|
+
device = deps.device();
|
|
86
|
+
}
|
|
87
|
+
if (!device) {
|
|
88
|
+
say(`No device credential was saved. Retry \`${row.connectCommand}\`.`);
|
|
89
|
+
return 1;
|
|
90
|
+
}
|
|
91
|
+
const binary = deps.resolveBinary(row.bin);
|
|
92
|
+
if (!binary.ok) {
|
|
93
|
+
say(`${row.label} is not installed here. ${row.installHint}`);
|
|
94
|
+
return 1;
|
|
95
|
+
}
|
|
96
|
+
if (!(0, agents_1.binaryIdentified)(row, binary.version)) {
|
|
97
|
+
say((0, agents_1.unidentifiedBinary)(row, binary.path, binary.version));
|
|
98
|
+
return 1;
|
|
99
|
+
}
|
|
100
|
+
const refusal = await spec.preflight?.(binary.path);
|
|
101
|
+
if (refusal) {
|
|
102
|
+
say(refusal);
|
|
103
|
+
return 1;
|
|
104
|
+
}
|
|
105
|
+
const folderInput = opts.folder ?? ((await deps.ask(`Folder ${row.label} may work in [${process.cwd()}]: `)) || process.cwd());
|
|
106
|
+
const folder = (0, policy_1.validateNewRoot)(folderInput);
|
|
107
|
+
if (!folder.ok) {
|
|
108
|
+
say(folder.reason);
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
const offered = row.modesFor(deps.platform);
|
|
112
|
+
const maxInput = opts.maxMode ?? ((await deps.ask(`Highest mode the chat may ever switch it to (${offered.join(" / ")}) [${offered.includes("ask") ? "ask" : offered.at(-1)}]: `)) || (offered.includes("ask") ? "ask" : offered.at(-1)));
|
|
113
|
+
if (!(0, modes_1.isAcpMode)(maxInput) || !offered.includes(maxInput)) {
|
|
114
|
+
say(`"${maxInput}" is not a mode offered on this computer. Choose one of: ${offered.join(", ")}.`);
|
|
115
|
+
return 1;
|
|
116
|
+
}
|
|
117
|
+
const mode = opts.agentMode ?? "chat";
|
|
118
|
+
if (!(0, modes_1.isAcpMode)(mode) || (0, modes_1.modeRank)(mode) > (0, modes_1.modeRank)(maxInput)) {
|
|
119
|
+
say(`The starting mode must be one of ${offered.join(", ")} and no higher than ${maxInput}.`);
|
|
120
|
+
return 1;
|
|
121
|
+
}
|
|
122
|
+
const session = opts.session?.trim() || spec.defaultSession;
|
|
123
|
+
const registry = new registry_1.SessionRegistry();
|
|
124
|
+
registry.load();
|
|
125
|
+
const existing = registry.get(session);
|
|
126
|
+
if (existing && !existing.acp) {
|
|
127
|
+
say(`A session named "${session}" already exists on this computer and is not run by the relay. Choose another name with --session <name>.`);
|
|
128
|
+
return 1;
|
|
129
|
+
}
|
|
130
|
+
const otherAgent = (0, agents_1.otherAgentRefusal)(row, session, existing?.acp?.agent);
|
|
131
|
+
if (otherAgent) {
|
|
132
|
+
say(otherAgent);
|
|
133
|
+
return 1;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
row.launch(mode, (0, agents_1.acpPatchDir)(), session); // a session name the agent's config cannot hold throws here
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
say(err instanceof Error ? err.message : String(err));
|
|
140
|
+
return 1;
|
|
141
|
+
}
|
|
142
|
+
// ── Nothing above this line wrote anything. ─────────────────────────────────────────────
|
|
143
|
+
(0, policy_1.savePolicy)((0, policy_1.upsertRoot)((0, policy_1.loadPolicy)(), row.id, folder.path, maxInput));
|
|
144
|
+
(0, agents_1.writeModePatch)(row, mode, session, (0, agents_1.acpPatchDir)());
|
|
145
|
+
await deps.join(session);
|
|
146
|
+
const acp = { agent: row.id, cwd: folder.path, mode };
|
|
147
|
+
registry.upsert({ name: session, runtime: row.id, runtimeBin: binary.path, relayOwned: true, acp });
|
|
148
|
+
const live = await deps.registerWithRelay({ type: "acp-register", session, runtime: row.id, runtimeBin: binary.path, acp });
|
|
149
|
+
say("");
|
|
150
|
+
say(` ${row.label}: ${binary.path} (${binary.version})`);
|
|
151
|
+
if (!(0, agents_1.matchesTestedVersion)(binary.version, row.testedVersion))
|
|
152
|
+
say(` Note: BayChat was tested with ${row.testedVersion}. ${row.label} is a preview and changes often — if something misbehaves, run \`baychat doctor\`.`);
|
|
153
|
+
for (const line of spec.notes(maxInput))
|
|
154
|
+
say(` ${line}`);
|
|
155
|
+
say(` Folder: ${folder.path}`);
|
|
156
|
+
say(` Mode: ${(0, agents_1.describeAgentMode)(row, mode)}`);
|
|
157
|
+
say(` Ceiling: ${maxInput} — the chat can never switch it higher. Change it by running this command again.`);
|
|
158
|
+
for (const line of spec.extras(device))
|
|
159
|
+
say(` ${line}`);
|
|
160
|
+
say(` ${await deps.ensureRelay()}${live ? "" : " (it will pick this session up when it starts)"}`);
|
|
161
|
+
say("");
|
|
162
|
+
say(` ${spec.firstMessage(session)}`);
|
|
163
|
+
say(` From the chat (you, or anyone you gave command access): ${offered.map((m) => `/mode-${m}`).join(" ")} · /stop /new /status.`);
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cursorLoginState = cursorLoginState;
|
|
4
|
+
exports.cmdConnectCursor = cmdConnectCursor;
|
|
5
|
+
const child_process_1 = require("child_process");
|
|
6
|
+
const connect_acp_1 = require("./connect-acp");
|
|
7
|
+
const agents_1 = require("./relay/acp/agents");
|
|
8
|
+
const modes_1 = require("./relay/acp/modes");
|
|
9
|
+
const runtime_binary_1 = require("./runtime-binary");
|
|
10
|
+
const STATUS_TIMEOUT_MS = 15_000;
|
|
11
|
+
/** Colour codes, which a terminal-aware CLI may print even into a pipe. */
|
|
12
|
+
const ANSI = /\u001b\[[0-9;]*[A-Za-z]/g;
|
|
13
|
+
/**
|
|
14
|
+
* What `agent status` says about the Cursor login [run, CLI 2026.09.18-9a7762b: "Logged in" /
|
|
15
|
+
* "Not logged in"]. Anything else is `unknown` — Cursor changed its wording, or the `agent`
|
|
16
|
+
* found on PATH is some other program — and `connect` refuses rather than guess.
|
|
17
|
+
*/
|
|
18
|
+
function cursorLoginState(statusOutput) {
|
|
19
|
+
const text = statusOutput.replace(ANSI, "");
|
|
20
|
+
if (/not logged in/i.test(text))
|
|
21
|
+
return "logged-out"; // first: it contains "logged in"
|
|
22
|
+
if (/logged in/i.test(text))
|
|
23
|
+
return "logged-in";
|
|
24
|
+
return "unknown";
|
|
25
|
+
}
|
|
26
|
+
function runAgentStatus(binaryPath) {
|
|
27
|
+
// No shell: the path came from PATH resolution. A Windows `.cmd` shim needs its interpreter named.
|
|
28
|
+
const plan = (0, runtime_binary_1.spawnPlanFor)(binaryPath, process.platform);
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
(0, child_process_1.execFile)(plan.file, [...plan.prefixArgs, "status"], { timeout: STATUS_TIMEOUT_MS, windowsHide: true, shell: false }, (err, stdout, stderr) => {
|
|
31
|
+
const said = `${stdout ?? ""}${stderr ?? ""}`;
|
|
32
|
+
// A failed run is still an answer: "Not logged in" may come with a non-zero exit.
|
|
33
|
+
resolve(said || (err ? err.message : ""));
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
function realDeps() {
|
|
38
|
+
return { ...(0, connect_acp_1.realConnectAcpDeps)(), loginStatus: runAgentStatus };
|
|
39
|
+
}
|
|
40
|
+
/** `baychat connect cursor-cli` — a Cursor CLI session the relay runs by itself. */
|
|
41
|
+
async function cmdConnectCursor(opts = {}, deps = realDeps()) {
|
|
42
|
+
const row = (0, agents_1.acpAgent)("cursor");
|
|
43
|
+
return (0, connect_acp_1.connectAcpAgent)(row, {
|
|
44
|
+
greeting: "Connect Cursor — approve the QR in BayChat.",
|
|
45
|
+
defaultSession: "Cursor",
|
|
46
|
+
async preflight(binaryPath) {
|
|
47
|
+
const state = cursorLoginState(await deps.loginStatus(binaryPath));
|
|
48
|
+
if (state === "logged-in")
|
|
49
|
+
return undefined;
|
|
50
|
+
if (state === "logged-out")
|
|
51
|
+
return `Cursor is not logged in on this computer. Run \`agent login\`, then \`${row.connectCommand}\` again.`;
|
|
52
|
+
return `Could not tell whether Cursor is logged in: \`${binaryPath} status\` said neither "Logged in" nor "Not logged in". If that is not Cursor's CLI, install it with \`curl https://cursor.com/install -fsS | bash\`; if it is, run \`agent login\`, then \`${row.connectCommand}\` again.`;
|
|
53
|
+
},
|
|
54
|
+
notes: (ceiling) => [
|
|
55
|
+
"Plan: Works on Cursor's free Hobby plan as of 19 Sep 2026; Pro recommended for regular use.",
|
|
56
|
+
"Modes the chat can switch it to:",
|
|
57
|
+
...row
|
|
58
|
+
.modesFor(deps.platform)
|
|
59
|
+
.filter((m) => (0, modes_1.modeRank)(m) <= (0, modes_1.modeRank)(ceiling))
|
|
60
|
+
.map((m) => ` ${(0, agents_1.describeAgentMode)(row, m)}`),
|
|
61
|
+
],
|
|
62
|
+
extras: () => [],
|
|
63
|
+
firstMessage: (session) => `Message "${session}" in BayChat. It uses this computer's Cursor login (\`agent login\`).`,
|
|
64
|
+
}, opts, deps);
|
|
65
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.cmdConnectDsh = cmdConnectDsh;
|
|
4
|
+
const connect_acp_1 = require("./connect-acp");
|
|
5
|
+
const dsh_config_1 = require("./dsh-config");
|
|
6
|
+
const agents_1 = require("./relay/acp/agents");
|
|
7
|
+
const runtime_install_1 = require("./runtime-install");
|
|
8
|
+
function realDeps() {
|
|
9
|
+
return {
|
|
10
|
+
...(0, connect_acp_1.realConnectAcpDeps)(),
|
|
11
|
+
installTerminalPath: (device) => {
|
|
12
|
+
const lines = (0, runtime_install_1.describeInstall)((0, runtime_install_1.installRuntimeCommand)("dsh"));
|
|
13
|
+
// writeDshMcpEntry can throw (RULING R22: a damaged ~/.dsh/cordis.patch.yml block). By this
|
|
14
|
+
// point the relay-run session has already been created — policy saved, mode patches
|
|
15
|
+
// written, joined, registered — so a throw here must not escape and abort cmdConnectDsh:
|
|
16
|
+
// it would report the whole connect as failed even though the chat-driven agent works
|
|
17
|
+
// fine. Report the terminal-path failure as a line instead.
|
|
18
|
+
try {
|
|
19
|
+
const mcp = (0, dsh_config_1.writeDshMcpEntry)({ url: `${device.baseUrl.replace(/\/$/, "")}/api/mcp`, token: device.token });
|
|
20
|
+
lines.push(`Terminal use: BayChat tools added to ${mcp.file}${mcp.backup ? ` (backup: ${mcp.backup})` : ""} — in dsh, type /baychat <name>`);
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
lines.push(`Terminal use: FAILED — ${err instanceof Error ? err.message : String(err)}`);
|
|
24
|
+
}
|
|
25
|
+
return lines;
|
|
26
|
+
},
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
/** `baychat connect dsh` — a DeepSeek session the relay runs by itself. */
|
|
30
|
+
async function cmdConnectDsh(opts = {}, deps = realDeps()) {
|
|
31
|
+
const row = (0, agents_1.acpAgent)("dsh");
|
|
32
|
+
return (0, connect_acp_1.connectAcpAgent)(row, {
|
|
33
|
+
greeting: "Connect DeepSeek — approve the QR in BayChat.",
|
|
34
|
+
defaultSession: "DeepSeek",
|
|
35
|
+
notes: () => [
|
|
36
|
+
`Safety: ${row.label} is experimental and has not been security-audited. DeepSeek recommends running it in a disposable VM or container, not on your everyday computer.`,
|
|
37
|
+
],
|
|
38
|
+
// Opt-in (Ruling R26): the terminal path writes the BayChat token into a file every dsh
|
|
39
|
+
// profile reads, and the relay-run path needs none of it.
|
|
40
|
+
extras: (device) => opts.terminal
|
|
41
|
+
? deps.installTerminalPath(device)
|
|
42
|
+
: ["Terminal use: not set up (add --terminal to also use BayChat from dsh by hand — it writes your BayChat token into ~/.dsh/cordis.patch.yml, which applies to every dsh profile)."],
|
|
43
|
+
firstMessage: (session) => `Message "${session}" in BayChat. It needs a DeepSeek API key on this computer: run \`dsh web\` and add it on the Models page.`,
|
|
44
|
+
}, opts, deps);
|
|
45
|
+
}
|
package/dist/connect-plan.js
CHANGED
|
@@ -57,8 +57,23 @@ function parseConnectArgs(args) {
|
|
|
57
57
|
throw new Error("Choose --start or --finish once.");
|
|
58
58
|
result.mode = arg === "--start" ? "start" : "finish";
|
|
59
59
|
}
|
|
60
|
+
else if (arg === "--folder" || arg === "--max-mode" || arg === "--mode" || arg === "--session") {
|
|
61
|
+
const value = args[++index];
|
|
62
|
+
if (!value?.trim() || value.startsWith("--"))
|
|
63
|
+
throw new Error(`Supply ${arg} with a value.`);
|
|
64
|
+
const key = arg === "--folder" ? "folder" : arg === "--max-mode" ? "maxMode" : arg === "--mode" ? "agentMode" : "session";
|
|
65
|
+
if (result[key])
|
|
66
|
+
throw new Error(`Supply ${arg} once.`);
|
|
67
|
+
result[key] = value;
|
|
68
|
+
}
|
|
69
|
+
else if (arg === "--terminal") {
|
|
70
|
+
if (result.terminal)
|
|
71
|
+
throw new Error("Supply --terminal once.");
|
|
72
|
+
result.terminal = true;
|
|
73
|
+
}
|
|
60
74
|
else if (arg.startsWith("-") || result.client || !arg.trim()) {
|
|
61
|
-
throw new Error("Usage: baychat connect <runtime> [--start | --finish] [--base <url>]"
|
|
75
|
+
throw new Error("Usage: baychat connect <runtime> [--start | --finish] [--base <url>] " +
|
|
76
|
+
"[--folder <path>] [--max-mode <mode>] [--mode <mode>] [--session <name>] [--terminal]");
|
|
62
77
|
}
|
|
63
78
|
else
|
|
64
79
|
result.client = arg;
|
package/dist/connect.js
CHANGED
|
@@ -52,6 +52,8 @@ const runtime_binary_1 = require("./runtime-binary");
|
|
|
52
52
|
const connect_claude_1 = require("./connect-claude");
|
|
53
53
|
var connect_claude_2 = require("./connect-claude");
|
|
54
54
|
Object.defineProperty(exports, "cmdConnectClaude", { enumerable: true, get: function () { return connect_claude_2.cmdConnectClaude; } });
|
|
55
|
+
const connect_cursor_1 = require("./connect-cursor");
|
|
56
|
+
const connect_dsh_1 = require("./connect-dsh");
|
|
55
57
|
const connect_hermes_1 = require("./connect-hermes");
|
|
56
58
|
/** Clients `connect` can configure. `claude` is an alias users reach for. */
|
|
57
59
|
const CLIENT_ALIASES = {
|
|
@@ -69,6 +71,8 @@ function renderConnectMenu() {
|
|
|
69
71
|
...rows,
|
|
70
72
|
" npx baychat connect claude → Claude Code (login, MCP and skill)",
|
|
71
73
|
" npx baychat connect hermes → Hermes (pair and enable its persistent agent)",
|
|
74
|
+
" npx baychat connect dsh → DeepSeek Harness (the relay runs it; talk to it from the app)",
|
|
75
|
+
" npx baychat connect cursor-cli → Cursor's CLI (the relay runs it; talk to it from the app)",
|
|
72
76
|
"",
|
|
73
77
|
"You approve a QR on your phone. After that, in any coding session:",
|
|
74
78
|
"",
|
|
@@ -79,6 +83,8 @@ function renderConnectMenu() {
|
|
|
79
83
|
"on its own: no QR, no re-pairing. Run it after every CLI upgrade.",
|
|
80
84
|
].join("\n");
|
|
81
85
|
}
|
|
86
|
+
/** Spellings of the relay-run Cursor CLI (`agent acp`) accepted by `connect`. */
|
|
87
|
+
const CURSOR_CLI_ALIASES = new Set(["cursor-cli", "cursor-agent"]);
|
|
82
88
|
/** Spellings of Claude Code accepted by `connect`. */
|
|
83
89
|
const CLAUDE_CONNECT_ALIASES = new Set(["claude", "claude-code", "claudecode"]);
|
|
84
90
|
/**
|
|
@@ -198,6 +204,17 @@ async function cmdConnect(clientArg, opts = {}) {
|
|
|
198
204
|
throw new Error("Hermes pairing does not support --start/--finish.");
|
|
199
205
|
return (0, connect_hermes_1.cmdConnectHermes)(opts);
|
|
200
206
|
}
|
|
207
|
+
if (clientArg.trim().toLowerCase() === "dsh" || clientArg.trim().toLowerCase() === "deepseek") {
|
|
208
|
+
if (opts.mode)
|
|
209
|
+
throw new Error("DeepSeek setup does not support --start/--finish.");
|
|
210
|
+
return (0, connect_dsh_1.cmdConnectDsh)(opts);
|
|
211
|
+
}
|
|
212
|
+
// Before `parseConnectClient`: plain `cursor` is the Cursor EDITOR's MCP file and stays that.
|
|
213
|
+
if (CURSOR_CLI_ALIASES.has(clientArg.trim().toLowerCase())) {
|
|
214
|
+
if (opts.mode)
|
|
215
|
+
throw new Error("Cursor CLI setup does not support --start/--finish.");
|
|
216
|
+
return (0, connect_cursor_1.cmdConnectCursor)(opts);
|
|
217
|
+
}
|
|
201
218
|
const client = parseConnectClient(clientArg);
|
|
202
219
|
if (opts.mode === "finish") {
|
|
203
220
|
// Let the pending approval select its server unless one was explicitly supplied.
|
package/dist/doctor-command.js
CHANGED
|
@@ -48,6 +48,7 @@ const os = __importStar(require("os"));
|
|
|
48
48
|
const config_1 = require("./config");
|
|
49
49
|
const doctor_1 = require("./doctor");
|
|
50
50
|
const commands_1 = require("./relay/commands");
|
|
51
|
+
const registry_1 = require("./relay/registry");
|
|
51
52
|
const credential_refresh_1 = require("./credential-refresh");
|
|
52
53
|
const client_paths_1 = require("./client-paths");
|
|
53
54
|
const runtime_binary_1 = require("./runtime-binary");
|
|
@@ -69,12 +70,18 @@ function codexPath() {
|
|
|
69
70
|
return resolved.ok ? resolved.path : undefined;
|
|
70
71
|
}
|
|
71
72
|
async function currentDoctorEnv() {
|
|
73
|
+
// Read directly, not through `relay.sessions`: a relay-owned dsh session is worth
|
|
74
|
+
// diagnosing precisely when the relay is NOT running, which is often why `doctor` was
|
|
75
|
+
// reached for in the first place.
|
|
76
|
+
const registry = new registry_1.SessionRegistry();
|
|
77
|
+
registry.load();
|
|
72
78
|
return {
|
|
73
79
|
home: os.homedir(),
|
|
74
80
|
platform: process.platform,
|
|
75
81
|
now: Date.now(),
|
|
76
82
|
device: (0, config_1.loadDeviceCredentials)(),
|
|
77
83
|
relay: await (0, commands_1.tryRelayStatus)(),
|
|
84
|
+
targets: registry.all(),
|
|
78
85
|
readText(filePath) {
|
|
79
86
|
try {
|
|
80
87
|
return fs.readFileSync(filePath, "utf8");
|
package/dist/doctor.js
CHANGED
|
@@ -57,13 +57,17 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
57
57
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
58
58
|
exports.buildReport = buildReport;
|
|
59
59
|
exports.mailboxCheck = mailboxCheck;
|
|
60
|
+
exports.acpChecks = acpChecks;
|
|
60
61
|
exports.exitCodeFor = exitCodeFor;
|
|
61
62
|
exports.renderReport = renderReport;
|
|
62
63
|
const fs = __importStar(require("fs"));
|
|
64
|
+
const os = __importStar(require("os"));
|
|
63
65
|
const path = __importStar(require("path"));
|
|
64
66
|
const mailbox_1 = require("./relay/mailbox");
|
|
65
67
|
const parent_watch_1 = require("./relay/parent-watch");
|
|
66
68
|
const client_paths_1 = require("./client-paths");
|
|
69
|
+
const agents_1 = require("./relay/acp/agents");
|
|
70
|
+
const policy_1 = require("./relay/acp/policy");
|
|
67
71
|
const runtime_binary_1 = require("./runtime-binary");
|
|
68
72
|
const runtimes_1 = require("./runtimes");
|
|
69
73
|
/**
|
|
@@ -106,6 +110,7 @@ function buildReport(env) {
|
|
|
106
110
|
mailbox: mailboxCheck(),
|
|
107
111
|
staleCredentials: staleCredentialCheck(env),
|
|
108
112
|
runtimes: REPORTED.map((runtime) => runtimeReport(runtime, env)),
|
|
113
|
+
agents: acpChecks({ resolveBinary: env.resolveBinary, targets: env.targets, homedir: env.home }),
|
|
109
114
|
pending: pendingCheck(env),
|
|
110
115
|
};
|
|
111
116
|
}
|
|
@@ -168,6 +173,83 @@ function mailboxCheck() {
|
|
|
168
173
|
// where the truth is established, honestly, as pending-or-delivered.
|
|
169
174
|
return { name: "mailbox", status: "pass", detail: `registered a mailbox FIFO: ${live.join(", ")}` };
|
|
170
175
|
}
|
|
176
|
+
/**
|
|
177
|
+
* Relay-run (ACP) agents: is each one startable, inside its rules, and able to reach a model?
|
|
178
|
+
*
|
|
179
|
+
* Takes `targets` rather than the full `DoctorEnv` for the same testability reason every other
|
|
180
|
+
* check here does — see `DoctorEnv.targets` for why these come from the persisted registry and
|
|
181
|
+
* not from `relay.sessions`.
|
|
182
|
+
*
|
|
183
|
+
* ONE finding per relay-owned session (not one report for the feature), because a machine
|
|
184
|
+
* running two dsh sessions in two folders can have one healthy and one not, and collapsing them
|
|
185
|
+
* would hide whichever came second.
|
|
186
|
+
*
|
|
187
|
+
* An agent whose maker advises a precaution (dsh: experimental, not security-audited — R11/R17)
|
|
188
|
+
* has it repeated on every line that gets far enough to say a version — one short clause, not a
|
|
189
|
+
* paragraph, because the reader has already seen it once from `connect`.
|
|
190
|
+
*/
|
|
191
|
+
function acpChecks(env) {
|
|
192
|
+
const owned = env.targets.filter((t) => t.relayOwned && t.acp);
|
|
193
|
+
if (owned.length === 0) {
|
|
194
|
+
const commands = agents_1.ACP_AGENTS.map((row) => `\`${row.connectCommand}\``).join(" or ");
|
|
195
|
+
return [{ name: "relay-run agents", status: "skip", detail: `none — ${commands} sets one up` }];
|
|
196
|
+
}
|
|
197
|
+
const policy = (0, policy_1.loadPolicy)();
|
|
198
|
+
return owned.map((t) => {
|
|
199
|
+
const name = `agent ${t.name}`;
|
|
200
|
+
const row = (0, agents_1.acpAgent)(t.acp.agent);
|
|
201
|
+
if (!row) {
|
|
202
|
+
return {
|
|
203
|
+
name,
|
|
204
|
+
status: "fail",
|
|
205
|
+
detail: `unknown agent "${t.acp.agent}"`,
|
|
206
|
+
remedy: `Update baychat, then run \`${(0, agents_1.connectCommandFor)(t.acp.agent)}\` again.`,
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
const bin = env.resolveBinary(row.bin);
|
|
210
|
+
if (!bin.ok) {
|
|
211
|
+
return {
|
|
212
|
+
name,
|
|
213
|
+
status: "fail",
|
|
214
|
+
detail: `${row.label} is not installed where the relay can find it`,
|
|
215
|
+
remedy: row.installHint,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
const folder = (0, policy_1.checkFolder)(policy, row.id, t.acp.cwd);
|
|
219
|
+
if (!folder.ok) {
|
|
220
|
+
return {
|
|
221
|
+
name,
|
|
222
|
+
status: "fail",
|
|
223
|
+
detail: folder.reason,
|
|
224
|
+
remedy: `Run \`${row.connectCommand}\` in the folder it should work in.`,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
// EXISTENCE only, and only for an agent whose credential BayChat can see at all (the row
|
|
228
|
+
// says): the key is the agent's; BayChat never opens the file that holds it, and never logs it.
|
|
229
|
+
const keySeen = row.credentialSeen?.(env.homedir ?? os.homedir()) ?? true;
|
|
230
|
+
const line = `${row.label} ${bin.version} · ${t.acp.cwd} · mode ${t.acp.mode} (ceiling ${folder.maxMode})` +
|
|
231
|
+
(row.safetyNote ? ` — ${row.safetyNote}` : "");
|
|
232
|
+
if (!keySeen) {
|
|
233
|
+
return {
|
|
234
|
+
name,
|
|
235
|
+
status: "warn",
|
|
236
|
+
detail: `${line} — no ${row.label} key found`,
|
|
237
|
+
remedy: row.keyHint,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
// R20: a substring check lets a later release ("0.1.5-rc.20") pass silently as the tested
|
|
241
|
+
// one ("0.1.5-rc.2"). `matchesTestedVersion` bounds the match to a whole version token.
|
|
242
|
+
if (!(0, agents_1.matchesTestedVersion)(bin.version, row.testedVersion)) {
|
|
243
|
+
return {
|
|
244
|
+
name,
|
|
245
|
+
status: "warn",
|
|
246
|
+
detail: `${line} — BayChat was tested with ${row.testedVersion}`,
|
|
247
|
+
remedy: "If it misbehaves, install the tested version or update baychat.",
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
return { name, status: "pass", detail: line };
|
|
251
|
+
});
|
|
252
|
+
}
|
|
171
253
|
/**
|
|
172
254
|
* The process exit code for a report.
|
|
173
255
|
*
|
|
@@ -198,6 +280,8 @@ function renderReport(report) {
|
|
|
198
280
|
for (const check of rest)
|
|
199
281
|
lines.push(...renderGroupCheck("", check));
|
|
200
282
|
}
|
|
283
|
+
for (const check of report.agents)
|
|
284
|
+
lines.push(...renderSummaryCheck(check));
|
|
201
285
|
lines.push(...renderSummaryCheck(report.pending));
|
|
202
286
|
return lines;
|
|
203
287
|
}
|
|
@@ -513,7 +597,15 @@ function withRemedy(line, check) {
|
|
|
513
597
|
return [line, `${" ".repeat(LABEL_WIDTH + 2 + CHECK_WIDTH)}→ ${check.remedy}`];
|
|
514
598
|
}
|
|
515
599
|
function allChecks(report) {
|
|
516
|
-
return [
|
|
600
|
+
return [
|
|
601
|
+
report.credentials,
|
|
602
|
+
report.staleCredentials,
|
|
603
|
+
report.relay,
|
|
604
|
+
report.mailbox,
|
|
605
|
+
...report.runtimes.flatMap((r) => r.checks),
|
|
606
|
+
...report.agents,
|
|
607
|
+
report.pending,
|
|
608
|
+
];
|
|
517
609
|
}
|
|
518
610
|
/** Shorten a path under the user's home, which is where nearly all of these are. */
|
|
519
611
|
function display(filePath, env) {
|