baychat 0.21.4 → 0.22.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 +74 -13
- package/dist/approve-hook.js +5 -2
- package/dist/claude-onboarding.js +6 -12
- package/dist/commands.js +11 -6
- package/dist/connect-claude.js +19 -4
- package/dist/connect-dsh.js +165 -0
- package/dist/connect-plan.js +47 -1
- package/dist/connect.js +40 -9
- package/dist/doctor-command.js +7 -0
- package/dist/doctor.js +94 -1
- package/dist/dsh-config.js +147 -0
- package/dist/index.js +37 -2
- package/dist/relay/acp/agents.js +186 -0
- package/dist/relay/acp/approval.js +67 -0
- package/dist/relay/acp/client.js +253 -0
- package/dist/relay/acp/commands.js +69 -0
- package/dist/relay/acp/daemon-glue.js +201 -0
- package/dist/relay/acp/dump-config.js +31 -0
- package/dist/relay/acp/modes.js +36 -0
- package/dist/relay/acp/permissions.js +42 -0
- package/dist/relay/acp/policy.js +137 -0
- package/dist/relay/acp/presence.js +87 -0
- package/dist/relay/acp/prompt.js +69 -0
- package/dist/relay/acp/runner.js +311 -0
- package/dist/relay/acp/sdk.js +19 -0
- package/dist/relay/acp/turn-queue.js +163 -0
- package/dist/relay/acp/types.js +2 -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-install.js +9 -2
- package/dist/runtimes.js +16 -0
- package/dist/session-command.js +16 -3
- package/dist/session-setup.js +38 -0
- package/dist/skill-bootstrap.js +47 -0
- package/dist/start-command.js +235 -0
- package/dist/update-command.js +201 -0
- package/package.json +3 -1
package/README.md
CHANGED
|
@@ -14,17 +14,19 @@ network requests time out after 15 seconds instead of waiting indefinitely.
|
|
|
14
14
|
|
|
15
15
|
## Coding sessions: one short command
|
|
16
16
|
|
|
17
|
-
|
|
18
|
-
Then type in the agent conversation:
|
|
17
|
+
Use the BayChat skill in either agent conversation. It runs missing setup itself:
|
|
19
18
|
|
|
20
19
|
```text
|
|
21
20
|
$baychat --group "Coding" # Codex: automatic name
|
|
22
21
|
$baychat Atlas "Coding" # Codex: chosen name
|
|
23
22
|
/baychat --group "Coding" # Claude Code: automatic name
|
|
24
23
|
/baychat Scout "Coding" # Claude Code: chosen name
|
|
24
|
+
$baychat --update # Codex updates BayChat from the session
|
|
25
|
+
/baychat --update # Claude does the same
|
|
25
26
|
```
|
|
26
27
|
|
|
27
|
-
Use the exact title
|
|
28
|
+
Use the exact group title; a missing group is created when requested this way.
|
|
29
|
+
An existing group requires membership and admin rights. The command creates or
|
|
28
30
|
reuses the agent and session together, joins the group, and retains a private
|
|
29
31
|
chat with you. Automatic names, such as `Codex-7c3a912b84d2`, come from a verified
|
|
30
32
|
native session identity; retries reuse the name. If that identity cannot be
|
|
@@ -42,9 +44,11 @@ Coding sessions show idle after five minutes without use and expire after
|
|
|
42
44
|
24 hours. `end_session` closes one immediately. History survives and the same
|
|
43
45
|
name can rejoin. Persistent agents such as Hermes keep their own lifecycle.
|
|
44
46
|
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
CLI 0.21.5 installs a small skill entry point that reads the current workflow from
|
|
48
|
+
the package. Subsequent npm updates need no separate skill refresh. Missing MCP
|
|
49
|
+
setup runs inside the session; device approval or a client reload may still need
|
|
50
|
+
you. For a first installation without the skill, `baychat connect codex` or
|
|
51
|
+
`baychat connect claude` installs it once. Updating npm does not deploy remote MCP.
|
|
48
52
|
|
|
49
53
|
## What the package and MCP each do
|
|
50
54
|
|
|
@@ -182,6 +186,7 @@ baychat login # once per computer: the QR
|
|
|
182
186
|
baychat connect # list the apps it can configure
|
|
183
187
|
baychat connect claude # refresh Claude Code's skill (no QR, no re-pairing)
|
|
184
188
|
baychat connect codex # configure Codex — restart Codex afterwards
|
|
189
|
+
baychat connect dsh # a DeepSeek Harness session the relay runs for you — see below
|
|
185
190
|
baychat relay start # the process that wakes your sessions
|
|
186
191
|
baychat relay status # transport, sessions, anything pending
|
|
187
192
|
baychat doctor # checks every link and prints what to type
|
|
@@ -211,15 +216,20 @@ Use `--private` when only the private chat is wanted.
|
|
|
211
216
|
|
|
212
217
|
Publishing a new version upgrades nobody by itself.
|
|
213
218
|
|
|
214
|
-
```
|
|
215
|
-
|
|
216
|
-
baychat
|
|
217
|
-
# 3. reload the skill and reconnect MCP in the client
|
|
219
|
+
```text
|
|
220
|
+
$baychat --update # in Codex
|
|
221
|
+
/baychat --update # in Claude
|
|
218
222
|
```
|
|
219
223
|
|
|
224
|
+
The session runs `npm install --global baychat@latest` and confirms the version.
|
|
225
|
+
The installed entry point loads future workflow changes from that package;
|
|
226
|
+
`connect` is no longer a repeated skill-refresh step. Install the new entry point
|
|
227
|
+
once when upgrading from 0.21.4 or older, using `baychat connect <runtime>` inside
|
|
228
|
+
the session. No conversation membership or messages are created by updating.
|
|
229
|
+
|
|
220
230
|
For Codex or a chosen legacy relay, restart the relay after updating its code.
|
|
221
|
-
Claude's native remote path needs no relay restart. A
|
|
222
|
-
|
|
231
|
+
Claude's native remote path needs no relay restart. A host reload may still be
|
|
232
|
+
needed after changing MCP configuration, but not merely for new packaged skill instructions.
|
|
223
233
|
|
|
224
234
|
### Being reached when you are not typing
|
|
225
235
|
|
|
@@ -341,7 +351,9 @@ everything we need.
|
|
|
341
351
|
right conversation. This needs the agent to be able to say which session it is, and to have a
|
|
342
352
|
way to resume that session without a UI (`--resume <id>` and friends).
|
|
343
353
|
|
|
344
|
-
Adapters ship for **Claude Code**, **Codex**, **Cursor** and **
|
|
354
|
+
Adapters ship for **Claude Code**, **Codex**, **Cursor**, **Hermes** and **DeepSeek Harness**
|
|
355
|
+
(`baychat connect dsh`, run by the relay over ACP rather than woken in a terminal — see above and
|
|
356
|
+
[`ACP_RELAY.md`](../../docs/features/ACP_RELAY.md)). Many others — Gemini CLI,
|
|
345
357
|
Copilot CLI, Goose, OpenCode/Crush, Qwen Code, Kimi Code CLI, CodeBuddy, iFlow, Trae, Aider — look
|
|
346
358
|
compatible on paper, with per-agent detail, exact flags, known bugs and a **date on every row** in
|
|
347
359
|
[the compatibility table](https://baychat.io/runtimes.md).
|
|
@@ -426,6 +438,55 @@ relay also falls back to it automatically when app-server cannot be used at
|
|
|
426
438
|
all — but not when the failure is a real answer about your session, because
|
|
427
439
|
retrying that would only repeat it with the reason replaced by an exit code.
|
|
428
440
|
|
|
441
|
+
## `baychat connect dsh` — DeepSeek Harness, run by the relay
|
|
442
|
+
|
|
443
|
+
```
|
|
444
|
+
baychat connect dsh [--folder <path>] [--max-mode <mode>] [--mode <mode>] [--session <name>] [--terminal]
|
|
445
|
+
```
|
|
446
|
+
|
|
447
|
+
Unlike every other runtime in this file, DeepSeek Harness (`dsh`) is not woken in your terminal —
|
|
448
|
+
the relay spawns it directly and drives it over **ACP** (Agent Client Protocol), one process per
|
|
449
|
+
turn. Full detail — the four modes, their honest limits, the rule file, known limits — is in
|
|
450
|
+
[`docs/features/ACP_RELAY.md`](../../docs/features/ACP_RELAY.md).
|
|
451
|
+
|
|
452
|
+
> ⚠️ **DeepSeek Harness is experimental and has not been security-audited.** DeepSeek recommends
|
|
453
|
+
> running it in a disposable VM or container, not on your everyday machine. `connect dsh` and
|
|
454
|
+
> `baychat doctor` both print this reminder every time.
|
|
455
|
+
|
|
456
|
+
| Flag | Default | Meaning |
|
|
457
|
+
|---|---|---|
|
|
458
|
+
| `--folder <path>` | asked interactively, defaulting to the current directory | the folder DeepSeek may work in — resolved and stored in `~/.baychat/relay-policy.json` |
|
|
459
|
+
| `--max-mode <mode>` | asked interactively, defaulting to `ask` | the ceiling: `/mode-*` from the chat can never go higher than this, ever |
|
|
460
|
+
| `--mode <mode>` | `chat` | the mode the session starts in |
|
|
461
|
+
| `--session <name>` | `DeepSeek` | the BayChat session name it registers and joins as |
|
|
462
|
+
| `--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. |
|
|
463
|
+
|
|
464
|
+
Modes, weakest to strongest: `chat` (default — BayChat tools only), `read` (+ file read/search),
|
|
465
|
+
`ask` (+ shell; a write asks on your phone first), `full` (+ writes without asking, in its
|
|
466
|
+
folder). Only `chat` protects private files — every other mode can read anything your account can.
|
|
467
|
+
Windows offers `chat` and `read` only.
|
|
468
|
+
|
|
469
|
+
Who gets that power: **you, or a Bay owner/admin you gave command access**. Their messages run in
|
|
470
|
+
the session's mode and they may use `/mode-*`; anyone else's message makes that turn `chat`. The
|
|
471
|
+
ceiling still caps everything, and in `ask` the approval card still comes to **your** phone only.
|
|
472
|
+
Revoke the grant (or demote the admin) to take it away.
|
|
473
|
+
|
|
474
|
+
Re-running `connect dsh` changes the ceiling, and re-registers the session at the starting mode
|
|
475
|
+
(`--mode`, default `chat`) — it does **not** keep a mode you raised from the chat. A running relay
|
|
476
|
+
refuses to raise a live session's mode (it keeps or lowers it only), and `connect` then exits with
|
|
477
|
+
an error; to raise it now, use `/mode-…` from the chat. The starting mode `connect` wrote itself
|
|
478
|
+
still applies once the relay reloads its sessions (for example after a restart) — that is you, on
|
|
479
|
+
this computer, and it is still capped by the folder's ceiling. Re-running with a different `--folder`
|
|
480
|
+
**adds** that folder to the allowed folders; the old ones stay allowed until you remove them from
|
|
481
|
+
`~/.baychat/relay-policy.json`.
|
|
482
|
+
|
|
483
|
+
A `dsh` you run by hand in a terminal is never woken headlessly, and `baychat start` does not
|
|
484
|
+
launch `dsh` — the relay-run session is the one whose modes are enforced.
|
|
485
|
+
|
|
486
|
+
It also needs its own API key on that computer, separate from BayChat: run `dsh web` there and add
|
|
487
|
+
it on the Models page. `baychat doctor` reports whether the key exists (never its contents) and
|
|
488
|
+
whether the installed `dsh` matches the version BayChat was tested against.
|
|
489
|
+
|
|
429
490
|
## Group instructions
|
|
430
491
|
|
|
431
492
|
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
|
"",
|
|
@@ -6,10 +6,8 @@ exports.CLAUDE_JOIN_STEPS = `1. Use **remote BayChat MCP + native WebSocket Moni
|
|
|
6
6
|
if needed, for \`join_session\`, \`listen_messages\`, \`get_delivery_status\`,
|
|
7
7
|
\`send_message\` and \`get_messages\` (also \`create_group\` for a named group).
|
|
8
8
|
Check that native Monitor supports \`ws\` before creating membership.
|
|
9
|
-
If a required tool is absent,
|
|
10
|
-
|
|
11
|
-
select baychat and Reconnect, then retry the same request. If still missing,
|
|
12
|
-
resume this same conversation after restarting Claude."
|
|
9
|
+
If a required BayChat tool is absent, follow Automatic setup above once,
|
|
10
|
+
then continue these steps with the original name and room when tools are loaded.
|
|
13
11
|
Missing tools are not an authentication failure. Do not infer the cause from session start times.
|
|
14
12
|
Do not launch a local stdio MCP server, hand-write JSON-RPC, inspect credentials
|
|
15
13
|
or installed source, start a relay, or try alternate transports to get around it.
|
|
@@ -34,20 +32,16 @@ exports.CLAUDE_JOIN_STEPS = `1. Use **remote BayChat MCP + native WebSocket Moni
|
|
|
34
32
|
confirms incoming delivery. \`awaiting_connection\` means a ticket was created
|
|
35
33
|
but Monitor has not connected; show that exact state, not "ready".
|
|
36
34
|
Print the confirmed name and room. Use that name as \`session\` on every
|
|
37
|
-
later BayChat tool call.
|
|
38
|
-
|
|
35
|
+
later BayChat tool call. Read one \`get_messages\` batch with \`limit: 10\`;
|
|
36
|
+
say hello once in the confirmed conversation unless already greeted.
|
|
39
37
|
|
|
40
38
|
### If connection needs attention
|
|
41
39
|
|
|
42
40
|
- An actual MCP authentication refusal is evidence that this connection needs
|
|
43
41
|
renewal. A local credential file's date alone is not: MCP and relay may use
|
|
44
42
|
different logins. Do not diagnose both as down from one file.
|
|
45
|
-
-
|
|
46
|
-
|
|
47
|
-
and show the returned approval link in your reply. It exits immediately.
|
|
48
|
-
Wait for the user's approval; then run \`baychat login --finish\` once.
|
|
49
|
-
If still pending, show the link and wait. Reconnect BayChat in the client to
|
|
50
|
-
load the refreshed login, then retry the same name and room.
|
|
43
|
+
- For login or missing tools, use Automatic setup above; the session runs the
|
|
44
|
+
terminal commands itself. Keep credentials inside the CLI and MCP client.
|
|
51
45
|
- Native WebSocket Monitor requires a supported Claude Code host (documented
|
|
52
46
|
from 2.1.195). If the tool is missing, denied or lacks \`ws\`, explain that
|
|
53
47
|
incoming delivery is not connected and show the specific host limitation.
|
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");
|
|
@@ -811,14 +813,17 @@ async function cmdLogin(opts = {}) {
|
|
|
811
813
|
if (opts.mode === "start") {
|
|
812
814
|
const request = await (0, device_login_1.startDeviceLogin)(base);
|
|
813
815
|
console.log(request.url);
|
|
814
|
-
|
|
816
|
+
if (opts.hint !== false)
|
|
817
|
+
console.log(`Approve this device in BayChat, then run baychat login --finish on this same computer. Link expires ${request.expiresAt}.`);
|
|
815
818
|
return true;
|
|
816
819
|
}
|
|
817
820
|
if (opts.mode === "finish") {
|
|
818
821
|
const result = await (0, device_login_1.finishDeviceLogin)(opts.base || process.env.BAYCHAT_API_URL);
|
|
819
822
|
if (result.status === "pending") {
|
|
820
823
|
console.log(result.url);
|
|
821
|
-
console.log(
|
|
824
|
+
console.log(opts.hint === false
|
|
825
|
+
? "Waiting for your approval. No background login is running."
|
|
826
|
+
: "Waiting for your approval. After approving, run baychat login --finish. No background login is running.");
|
|
822
827
|
return false;
|
|
823
828
|
}
|
|
824
829
|
completeDeviceLogin(result.device, {
|
package/dist/connect-claude.js
CHANGED
|
@@ -8,10 +8,24 @@ const runtime_install_1 = require("./runtime-install");
|
|
|
8
8
|
async function cmdConnectClaude(opts = {}) {
|
|
9
9
|
try {
|
|
10
10
|
const existing = (0, config_1.loadDeviceCredentials)();
|
|
11
|
-
const base = (opts.base ||
|
|
11
|
+
const base = (opts.base ||
|
|
12
|
+
process.env.BAYCHAT_API_URL ||
|
|
13
|
+
existing?.baseUrl ||
|
|
14
|
+
config_1.DEFAULT_API_URL).replace(/\/$/, "");
|
|
12
15
|
const expires = existing ? Date.parse(existing.expiresAt) : NaN;
|
|
13
|
-
if (
|
|
14
|
-
|
|
16
|
+
if (opts.mode === "finish" ||
|
|
17
|
+
!existing ||
|
|
18
|
+
existing.baseUrl.replace(/\/$/, "") !== base ||
|
|
19
|
+
!Number.isFinite(expires) ||
|
|
20
|
+
expires <= Date.now()) {
|
|
21
|
+
if (!(await (0, commands_1.cmdLogin)({
|
|
22
|
+
base: opts.mode === "finish" ? opts.base : base,
|
|
23
|
+
registerClaude: false,
|
|
24
|
+
hint: false,
|
|
25
|
+
...(opts.mode ? { mode: opts.mode } : {}),
|
|
26
|
+
})))
|
|
27
|
+
return 2;
|
|
28
|
+
if (opts.mode === "start")
|
|
15
29
|
return 2;
|
|
16
30
|
}
|
|
17
31
|
const device = (0, config_1.loadDeviceCredentials)();
|
|
@@ -27,7 +41,8 @@ async function cmdConnectClaude(opts = {}) {
|
|
|
27
41
|
return 0;
|
|
28
42
|
}
|
|
29
43
|
catch (err) {
|
|
30
|
-
console.error("Could not connect Claude Code: " +
|
|
44
|
+
console.error("Could not connect Claude Code: " +
|
|
45
|
+
(err instanceof Error ? err.message : String(err)));
|
|
31
46
|
return 1;
|
|
32
47
|
}
|
|
33
48
|
}
|
|
@@ -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.cmdConnectDsh = cmdConnectDsh;
|
|
37
|
+
const readline = __importStar(require("readline"));
|
|
38
|
+
const commands_1 = require("./commands");
|
|
39
|
+
const config_1 = require("./config");
|
|
40
|
+
const agents_1 = require("./relay/acp/agents");
|
|
41
|
+
const modes_1 = require("./relay/acp/modes");
|
|
42
|
+
const policy_1 = require("./relay/acp/policy");
|
|
43
|
+
const presence_1 = require("./relay/acp/presence");
|
|
44
|
+
const commands_2 = require("./relay/commands");
|
|
45
|
+
const registry_1 = require("./relay/registry");
|
|
46
|
+
const dsh_config_1 = require("./dsh-config");
|
|
47
|
+
const runtime_install_1 = require("./runtime-install");
|
|
48
|
+
const runtime_binary_1 = require("./runtime-binary");
|
|
49
|
+
function askOnTerminal(question) {
|
|
50
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
51
|
+
return new Promise((resolve) => rl.question(question, (answer) => (rl.close(), resolve(answer.trim()))));
|
|
52
|
+
}
|
|
53
|
+
function realDeps() {
|
|
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
|
+
installTerminalPath: (device) => {
|
|
67
|
+
const lines = (0, runtime_install_1.describeInstall)((0, runtime_install_1.installRuntimeCommand)("dsh"));
|
|
68
|
+
// writeDshMcpEntry can throw (RULING R22: a damaged ~/.dsh/cordis.patch.yml block). By this
|
|
69
|
+
// point the relay-run session has already been created — policy saved, mode patches
|
|
70
|
+
// written, joined, registered — so a throw here must not escape and abort cmdConnectDsh:
|
|
71
|
+
// it would report the whole connect as failed even though the chat-driven agent works
|
|
72
|
+
// fine. Report the terminal-path failure as a line instead.
|
|
73
|
+
try {
|
|
74
|
+
const mcp = (0, dsh_config_1.writeDshMcpEntry)({ url: `${device.baseUrl.replace(/\/$/, "")}/api/mcp`, token: device.token });
|
|
75
|
+
lines.push(`Terminal use: BayChat tools added to ${mcp.file}${mcp.backup ? ` (backup: ${mcp.backup})` : ""} — in dsh, type /baychat <name>`);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
lines.push(`Terminal use: FAILED — ${err instanceof Error ? err.message : String(err)}`);
|
|
79
|
+
}
|
|
80
|
+
return lines;
|
|
81
|
+
},
|
|
82
|
+
ask: askOnTerminal,
|
|
83
|
+
print: (line) => console.log(line),
|
|
84
|
+
platform: process.platform,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* `baychat connect dsh` — a DeepSeek session the relay runs by itself.
|
|
89
|
+
*
|
|
90
|
+
* ORDER MATTERS: everything that can refuse (no login, no binary, a bad folder, a bad mode,
|
|
91
|
+
* a session name already taken by something the relay does not run) refuses BEFORE anything
|
|
92
|
+
* is written, so a failed connect leaves no half-made rule file.
|
|
93
|
+
*/
|
|
94
|
+
async function cmdConnectDsh(opts = {}, deps = realDeps()) {
|
|
95
|
+
const row = (0, agents_1.acpAgent)("dsh");
|
|
96
|
+
const say = deps.print;
|
|
97
|
+
let device = deps.device();
|
|
98
|
+
if (!device) {
|
|
99
|
+
say("Connect DeepSeek — approve the QR in BayChat.");
|
|
100
|
+
if (!(await deps.login(opts.base)))
|
|
101
|
+
return 2;
|
|
102
|
+
device = deps.device();
|
|
103
|
+
}
|
|
104
|
+
if (!device) {
|
|
105
|
+
say("No device credential was saved. Retry `baychat connect dsh`.");
|
|
106
|
+
return 1;
|
|
107
|
+
}
|
|
108
|
+
const binary = deps.resolveBinary(row.bin);
|
|
109
|
+
if (!binary.ok) {
|
|
110
|
+
say(`${row.label} is not installed here. ${row.installHint}`);
|
|
111
|
+
return 1;
|
|
112
|
+
}
|
|
113
|
+
const folderInput = opts.folder ?? ((await deps.ask(`Folder ${row.label} may work in [${process.cwd()}]: `)) || process.cwd());
|
|
114
|
+
const folder = (0, policy_1.validateNewRoot)(folderInput);
|
|
115
|
+
if (!folder.ok) {
|
|
116
|
+
say(folder.reason);
|
|
117
|
+
return 1;
|
|
118
|
+
}
|
|
119
|
+
const offered = row.modesFor(deps.platform);
|
|
120
|
+
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)));
|
|
121
|
+
if (!(0, modes_1.isAcpMode)(maxInput) || !offered.includes(maxInput)) {
|
|
122
|
+
say(`"${maxInput}" is not a mode offered on this computer. Choose one of: ${offered.join(", ")}.`);
|
|
123
|
+
return 1;
|
|
124
|
+
}
|
|
125
|
+
const mode = opts.agentMode ?? "chat";
|
|
126
|
+
if (!(0, modes_1.isAcpMode)(mode) || (0, modes_1.modeRank)(mode) > (0, modes_1.modeRank)(maxInput)) {
|
|
127
|
+
say(`The starting mode must be one of ${offered.join(", ")} and no higher than ${maxInput}.`);
|
|
128
|
+
return 1;
|
|
129
|
+
}
|
|
130
|
+
const session = opts.session?.trim() || "DeepSeek";
|
|
131
|
+
const registry = new registry_1.SessionRegistry();
|
|
132
|
+
registry.load();
|
|
133
|
+
const existing = registry.get(session);
|
|
134
|
+
if (existing && !existing.acp) {
|
|
135
|
+
say(`A session named "${session}" already exists on this computer and is not run by the relay. Choose another name with --session <name>.`);
|
|
136
|
+
return 1;
|
|
137
|
+
}
|
|
138
|
+
// ── Nothing above this line wrote anything. ─────────────────────────────────────────────
|
|
139
|
+
(0, policy_1.savePolicy)((0, policy_1.upsertRoot)((0, policy_1.loadPolicy)(), row.id, folder.path, maxInput));
|
|
140
|
+
(0, agents_1.writeModePatches)(row, (0, agents_1.acpPatchDir)());
|
|
141
|
+
await deps.join(session);
|
|
142
|
+
const acp = { agent: row.id, cwd: folder.path, mode };
|
|
143
|
+
registry.upsert({ name: session, runtime: row.id, runtimeBin: binary.path, relayOwned: true, acp });
|
|
144
|
+
const live = await deps.registerWithRelay({ type: "acp-register", session, runtime: row.id, runtimeBin: binary.path, acp });
|
|
145
|
+
say("");
|
|
146
|
+
say(` ${row.label}: ${binary.path} (${binary.version})`);
|
|
147
|
+
if (!(0, agents_1.matchesTestedVersion)(binary.version, row.testedVersion))
|
|
148
|
+
say(` Note: BayChat was tested with ${row.testedVersion}. ${row.label} is a preview and changes often — if something misbehaves, run \`baychat doctor\`.`);
|
|
149
|
+
say(` 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.`);
|
|
150
|
+
say(` Folder: ${folder.path}`);
|
|
151
|
+
say(` Mode: ${(0, modes_1.describeMode)(mode)}`);
|
|
152
|
+
say(` Ceiling: ${maxInput} — the chat can never switch it higher. Change it by running this command again.`);
|
|
153
|
+
// Opt-in (Ruling R26): the terminal path writes the BayChat token into a file every dsh
|
|
154
|
+
// profile reads, and the relay-run path needs none of it.
|
|
155
|
+
if (opts.terminal)
|
|
156
|
+
for (const line of deps.installTerminalPath(device))
|
|
157
|
+
say(` ${line}`);
|
|
158
|
+
else
|
|
159
|
+
say(" 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).");
|
|
160
|
+
say(` ${await deps.ensureRelay()}${live ? "" : " (it will pick this session up when it starts)"}`);
|
|
161
|
+
say("");
|
|
162
|
+
say(` Message "${session}" in BayChat. It needs a DeepSeek API key on this computer: run \`dsh web\` and add it on the Models page.`);
|
|
163
|
+
say(" From the chat (you, or anyone you gave command access): /mode-chat /mode-read /mode-ask /mode-full · /stop /new /status.");
|
|
164
|
+
return 0;
|
|
165
|
+
}
|
package/dist/connect-plan.js
CHANGED
|
@@ -35,11 +35,53 @@
|
|
|
35
35
|
// carries them out - so the sequencing is testable without a network, a terminal,
|
|
36
36
|
// or a phone.
|
|
37
37
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
exports.parseConnectArgs = parseConnectArgs;
|
|
38
39
|
exports.deviceStateFrom = deviceStateFrom;
|
|
39
40
|
exports.planConnect = planConnect;
|
|
40
41
|
exports.planNeedsPhoneApproval = planNeedsPhoneApproval;
|
|
41
42
|
exports.expiryNudge = expiryNudge;
|
|
42
43
|
exports.describeConnection = describeConnection;
|
|
44
|
+
/** Parse setup arguments without treating a flag value as a runtime name. */
|
|
45
|
+
function parseConnectArgs(args) {
|
|
46
|
+
const result = {};
|
|
47
|
+
for (let index = 0; index < args.length; index++) {
|
|
48
|
+
const arg = args[index];
|
|
49
|
+
if (arg === "--base") {
|
|
50
|
+
const value = args[++index];
|
|
51
|
+
if (result.base || !value?.trim() || value.startsWith("--"))
|
|
52
|
+
throw new Error("Supply --base once with a URL.");
|
|
53
|
+
result.base = value;
|
|
54
|
+
}
|
|
55
|
+
else if (arg === "--start" || arg === "--finish") {
|
|
56
|
+
if (result.mode)
|
|
57
|
+
throw new Error("Choose --start or --finish once.");
|
|
58
|
+
result.mode = arg === "--start" ? "start" : "finish";
|
|
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
|
+
}
|
|
74
|
+
else if (arg.startsWith("-") || result.client || !arg.trim()) {
|
|
75
|
+
throw new Error("Usage: baychat connect <runtime> [--start | --finish] [--base <url>] " +
|
|
76
|
+
"[--folder <path>] [--max-mode <mode>] [--mode <mode>] [--session <name>] [--terminal]");
|
|
77
|
+
}
|
|
78
|
+
else
|
|
79
|
+
result.client = arg;
|
|
80
|
+
}
|
|
81
|
+
if (result.mode && !result.client)
|
|
82
|
+
throw new Error("Choose a runtime to configure.");
|
|
83
|
+
return result;
|
|
84
|
+
}
|
|
43
85
|
const THREE_DAYS_MS = 3 * 86_400_000;
|
|
44
86
|
/** Read the on-disk credential into a state, without judging what to do about it. */
|
|
45
87
|
function deviceStateFrom(device, now = Date.now()) {
|
|
@@ -67,7 +109,11 @@ function planConnect(input) {
|
|
|
67
109
|
steps.push({ kind: "device-login", reason: device.kind });
|
|
68
110
|
}
|
|
69
111
|
else {
|
|
70
|
-
steps.push({
|
|
112
|
+
steps.push({
|
|
113
|
+
kind: "device-ok",
|
|
114
|
+
userName: device.userName,
|
|
115
|
+
msLeft: device.msLeft,
|
|
116
|
+
});
|
|
71
117
|
}
|
|
72
118
|
// No code, no room, no agent. The config carries the device credential, and the
|
|
73
119
|
// session tools it unlocks let `/baychat <name>` do the rest, per session.
|
package/dist/connect.js
CHANGED
|
@@ -52,6 +52,7 @@ 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_dsh_1 = require("./connect-dsh");
|
|
55
56
|
const connect_hermes_1 = require("./connect-hermes");
|
|
56
57
|
/** Clients `connect` can configure. `claude` is an alias users reach for. */
|
|
57
58
|
const CLIENT_ALIASES = {
|
|
@@ -69,6 +70,7 @@ function renderConnectMenu() {
|
|
|
69
70
|
...rows,
|
|
70
71
|
" npx baychat connect claude → Claude Code (login, MCP and skill)",
|
|
71
72
|
" npx baychat connect hermes → Hermes (pair and enable its persistent agent)",
|
|
73
|
+
" npx baychat connect dsh → DeepSeek Harness (the relay runs it; talk to it from the app)",
|
|
72
74
|
"",
|
|
73
75
|
"You approve a QR on your phone. After that, in any coding session:",
|
|
74
76
|
"",
|
|
@@ -193,17 +195,40 @@ async function cmdConnect(clientArg, opts = {}) {
|
|
|
193
195
|
// `parseConnectClient`, which would reject it.
|
|
194
196
|
if (isClaudeConnectTarget(clientArg))
|
|
195
197
|
return (0, connect_claude_1.cmdConnectClaude)(opts);
|
|
196
|
-
if (clientArg.trim().toLowerCase() === "hermes")
|
|
198
|
+
if (clientArg.trim().toLowerCase() === "hermes") {
|
|
199
|
+
if (opts.mode)
|
|
200
|
+
throw new Error("Hermes pairing does not support --start/--finish.");
|
|
197
201
|
return (0, connect_hermes_1.cmdConnectHermes)(opts);
|
|
202
|
+
}
|
|
203
|
+
if (clientArg.trim().toLowerCase() === "dsh" || clientArg.trim().toLowerCase() === "deepseek") {
|
|
204
|
+
if (opts.mode)
|
|
205
|
+
throw new Error("DeepSeek setup does not support --start/--finish.");
|
|
206
|
+
return (0, connect_dsh_1.cmdConnectDsh)(opts);
|
|
207
|
+
}
|
|
198
208
|
const client = parseConnectClient(clientArg);
|
|
199
|
-
|
|
209
|
+
if (opts.mode === "finish") {
|
|
210
|
+
// Let the pending approval select its server unless one was explicitly supplied.
|
|
211
|
+
if (!(await (0, commands_1.cmdLogin)({
|
|
212
|
+
base: opts.base,
|
|
213
|
+
mode: "finish",
|
|
214
|
+
registerClaude: client === "desktop",
|
|
215
|
+
hint: false,
|
|
216
|
+
})))
|
|
217
|
+
return 2;
|
|
218
|
+
}
|
|
219
|
+
const device = (0, config_1.loadDeviceCredentials)();
|
|
220
|
+
const base = (opts.base ||
|
|
221
|
+
process.env.BAYCHAT_API_URL ||
|
|
222
|
+
device?.baseUrl ||
|
|
223
|
+
config_1.DEFAULT_API_URL).replace(/\/$/, "");
|
|
200
224
|
const steps = (0, connect_plan_1.planConnect)({
|
|
201
225
|
client,
|
|
202
|
-
|
|
226
|
+
// Credentials belong to their server; a different endpoint needs its own login.
|
|
227
|
+
device: (0, connect_plan_1.deviceStateFrom)(device?.baseUrl.replace(/\/$/, "") === base ? device : null),
|
|
203
228
|
needsRestart: (0, client_paths_1.needsRestart)(client),
|
|
204
229
|
});
|
|
205
230
|
for (const step of steps) {
|
|
206
|
-
const outcome = await runStep(step, { base, client });
|
|
231
|
+
const outcome = await runStep(step, { base, client, mode: opts.mode });
|
|
207
232
|
if (outcome.kind === "abort")
|
|
208
233
|
return outcome.code;
|
|
209
234
|
}
|
|
@@ -233,24 +258,30 @@ async function cmdConnect(clientArg, opts = {}) {
|
|
|
233
258
|
// The relay is what makes a joined session hear messages without being
|
|
234
259
|
// prompted. Best-effort by design: it reports what it did (or didn't) and
|
|
235
260
|
// never fails the connect it is tacked onto.
|
|
236
|
-
|
|
261
|
+
if (!opts.mode)
|
|
262
|
+
console.log(` ${await (0, commands_2.ensureRelayInstalled)()}`);
|
|
237
263
|
return 0;
|
|
238
264
|
}
|
|
239
265
|
async function runStep(step, ctx) {
|
|
240
266
|
switch (step.kind) {
|
|
241
267
|
case "device-login": {
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
268
|
+
if (!ctx.mode)
|
|
269
|
+
console.log(step.reason === "expired"
|
|
270
|
+
? "Your laptop login has expired. Reconnecting this laptop…\n"
|
|
271
|
+
: "Connecting this laptop — approve the QR on your phone.\n");
|
|
245
272
|
// registerClaude is left ON only for the desktop/claude clients; for any
|
|
246
273
|
// other client, editing Claude's config unasked is a surprise.
|
|
247
274
|
const ok = await (0, commands_1.cmdLogin)({
|
|
248
275
|
base: ctx.base,
|
|
249
276
|
registerClaude: ctx.client === "desktop",
|
|
250
277
|
hint: false,
|
|
278
|
+
...(ctx.mode ? { mode: ctx.mode } : {}),
|
|
251
279
|
});
|
|
280
|
+
if (ctx.mode === "start")
|
|
281
|
+
return { kind: "abort", code: 2 };
|
|
252
282
|
if (!ok) {
|
|
253
|
-
|
|
283
|
+
if (!ctx.mode)
|
|
284
|
+
console.log("\nLaptop not connected — run `npx baychat connect` again when ready.");
|
|
254
285
|
return { kind: "abort", code: 2 };
|
|
255
286
|
}
|
|
256
287
|
console.log("✓ Laptop connected.\n");
|
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");
|