baychat 0.22.0 → 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 +22 -0
- package/dist/connect-acp.js +165 -0
- package/dist/connect-cursor.js +65 -0
- package/dist/connect-dsh.js +17 -137
- package/dist/connect.js +10 -0
- package/dist/doctor.js +13 -14
- package/dist/index.js +5 -0
- package/dist/relay/acp/agents.js +76 -31
- package/dist/relay/acp/client.js +13 -1
- package/dist/relay/acp/commands.js +3 -3
- package/dist/relay/acp/cursor.js +271 -0
- package/dist/relay/acp/daemon-glue.js +12 -15
- package/dist/relay/acp/modes.js +5 -5
- package/dist/relay/acp/permissions.js +65 -3
- package/dist/relay/acp/policy.js +7 -6
- package/dist/relay/acp/prompt.js +2 -3
- package/dist/relay/acp/runner.js +65 -12
- package/dist/relay/acp/types.js +3 -0
- package/dist/relay/daemon.js +1 -1
- package/dist/runtime-binary.js +17 -0
- package/package.json +4 -2
package/README.md
CHANGED
|
@@ -187,6 +187,7 @@ 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
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
|
|
190
191
|
baychat relay start # the process that wakes your sessions
|
|
191
192
|
baychat relay status # transport, sessions, anything pending
|
|
192
193
|
baychat doctor # checks every link and prints what to type
|
|
@@ -487,6 +488,27 @@ It also needs its own API key on that computer, separate from BayChat: run `dsh
|
|
|
487
488
|
it on the Models page. `baychat doctor` reports whether the key exists (never its contents) and
|
|
488
489
|
whether the installed `dsh` matches the version BayChat was tested against.
|
|
489
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
|
+
|
|
490
512
|
## Group instructions
|
|
491
513
|
|
|
492
514
|
Group conversations carry a short, server-authored **primer** — who's in the
|
|
@@ -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
|
+
}
|
package/dist/connect-dsh.js
CHANGED
|
@@ -1,68 +1,13 @@
|
|
|
1
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
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.cmdConnectDsh = cmdConnectDsh;
|
|
37
|
-
const
|
|
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");
|
|
4
|
+
const connect_acp_1 = require("./connect-acp");
|
|
46
5
|
const dsh_config_1 = require("./dsh-config");
|
|
6
|
+
const agents_1 = require("./relay/acp/agents");
|
|
47
7
|
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
8
|
function realDeps() {
|
|
54
9
|
return {
|
|
55
|
-
|
|
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,
|
|
10
|
+
...(0, connect_acp_1.realConnectAcpDeps)(),
|
|
66
11
|
installTerminalPath: (device) => {
|
|
67
12
|
const lines = (0, runtime_install_1.describeInstall)((0, runtime_install_1.installRuntimeCommand)("dsh"));
|
|
68
13
|
// writeDshMcpEntry can throw (RULING R22: a damaged ~/.dsh/cordis.patch.yml block). By this
|
|
@@ -79,87 +24,22 @@ function realDeps() {
|
|
|
79
24
|
}
|
|
80
25
|
return lines;
|
|
81
26
|
},
|
|
82
|
-
ask: askOnTerminal,
|
|
83
|
-
print: (line) => console.log(line),
|
|
84
|
-
platform: process.platform,
|
|
85
27
|
};
|
|
86
28
|
}
|
|
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
|
-
*/
|
|
29
|
+
/** `baychat connect dsh` — a DeepSeek session the relay runs by itself. */
|
|
94
30
|
async function cmdConnectDsh(opts = {}, deps = realDeps()) {
|
|
95
31
|
const row = (0, agents_1.acpAgent)("dsh");
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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;
|
|
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);
|
|
165
45
|
}
|
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_cursor_1 = require("./connect-cursor");
|
|
55
56
|
const connect_dsh_1 = require("./connect-dsh");
|
|
56
57
|
const connect_hermes_1 = require("./connect-hermes");
|
|
57
58
|
/** Clients `connect` can configure. `claude` is an alias users reach for. */
|
|
@@ -71,6 +72,7 @@ function renderConnectMenu() {
|
|
|
71
72
|
" npx baychat connect claude → Claude Code (login, MCP and skill)",
|
|
72
73
|
" npx baychat connect hermes → Hermes (pair and enable its persistent agent)",
|
|
73
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)",
|
|
74
76
|
"",
|
|
75
77
|
"You approve a QR on your phone. After that, in any coding session:",
|
|
76
78
|
"",
|
|
@@ -81,6 +83,8 @@ function renderConnectMenu() {
|
|
|
81
83
|
"on its own: no QR, no re-pairing. Run it after every CLI upgrade.",
|
|
82
84
|
].join("\n");
|
|
83
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"]);
|
|
84
88
|
/** Spellings of Claude Code accepted by `connect`. */
|
|
85
89
|
const CLAUDE_CONNECT_ALIASES = new Set(["claude", "claude-code", "claudecode"]);
|
|
86
90
|
/**
|
|
@@ -205,6 +209,12 @@ async function cmdConnect(clientArg, opts = {}) {
|
|
|
205
209
|
throw new Error("DeepSeek setup does not support --start/--finish.");
|
|
206
210
|
return (0, connect_dsh_1.cmdConnectDsh)(opts);
|
|
207
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
|
+
}
|
|
208
218
|
const client = parseConnectClient(clientArg);
|
|
209
219
|
if (opts.mode === "finish") {
|
|
210
220
|
// Let the pending approval select its server unless one was explicitly supplied.
|
package/dist/doctor.js
CHANGED
|
@@ -184,14 +184,15 @@ function mailboxCheck() {
|
|
|
184
184
|
* running two dsh sessions in two folders can have one healthy and one not, and collapsing them
|
|
185
185
|
* would hide whichever came second.
|
|
186
186
|
*
|
|
187
|
-
*
|
|
188
|
-
* gets far enough to say a version
|
|
189
|
-
*
|
|
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
190
|
*/
|
|
191
191
|
function acpChecks(env) {
|
|
192
192
|
const owned = env.targets.filter((t) => t.relayOwned && t.acp);
|
|
193
193
|
if (owned.length === 0) {
|
|
194
|
-
|
|
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` }];
|
|
195
196
|
}
|
|
196
197
|
const policy = (0, policy_1.loadPolicy)();
|
|
197
198
|
return owned.map((t) => {
|
|
@@ -202,7 +203,7 @@ function acpChecks(env) {
|
|
|
202
203
|
name,
|
|
203
204
|
status: "fail",
|
|
204
205
|
detail: `unknown agent "${t.acp.agent}"`,
|
|
205
|
-
remedy:
|
|
206
|
+
remedy: `Update baychat, then run \`${(0, agents_1.connectCommandFor)(t.acp.agent)}\` again.`,
|
|
206
207
|
};
|
|
207
208
|
}
|
|
208
209
|
const bin = env.resolveBinary(row.bin);
|
|
@@ -220,22 +221,20 @@ function acpChecks(env) {
|
|
|
220
221
|
name,
|
|
221
222
|
status: "fail",
|
|
222
223
|
detail: folder.reason,
|
|
223
|
-
remedy:
|
|
224
|
+
remedy: `Run \`${row.connectCommand}\` in the folder it should work in.`,
|
|
224
225
|
};
|
|
225
226
|
}
|
|
226
|
-
|
|
227
|
-
//
|
|
228
|
-
|
|
229
|
-
const keySeen = Boolean(process.env.DEEPSEEK_API_KEY) ||
|
|
230
|
-
fs.existsSync(path.join(process.env.DSH_HOME ?? path.join(home, ".dsh"), ".credentials.yaml"));
|
|
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;
|
|
231
230
|
const line = `${row.label} ${bin.version} · ${t.acp.cwd} · mode ${t.acp.mode} (ceiling ${folder.maxMode})` +
|
|
232
|
-
|
|
231
|
+
(row.safetyNote ? ` — ${row.safetyNote}` : "");
|
|
233
232
|
if (!keySeen) {
|
|
234
233
|
return {
|
|
235
234
|
name,
|
|
236
235
|
status: "warn",
|
|
237
|
-
detail: `${line} — no
|
|
238
|
-
remedy:
|
|
236
|
+
detail: `${line} — no ${row.label} key found`,
|
|
237
|
+
remedy: row.keyHint,
|
|
239
238
|
};
|
|
240
239
|
}
|
|
241
240
|
// R20: a substring check lets a later release ("0.1.5-rc.20") pass silently as the tested
|
package/dist/index.js
CHANGED
|
@@ -59,6 +59,11 @@ Usage:
|
|
|
59
59
|
phone, and your client's MCP config is written for
|
|
60
60
|
you. Then run /baychat <name> in any session to
|
|
61
61
|
join as that session. No config blocks, no editing
|
|
62
|
+
baychat connect dsh|cursor-cli [--folder <path>] [--max-mode <mode>] [--mode <mode>] [--session <name>]
|
|
63
|
+
An agent the relay runs by itself (DeepSeek Harness,
|
|
64
|
+
Cursor's CLI): you talk to it from the app. The
|
|
65
|
+
folder and the highest mode are set here, never
|
|
66
|
+
from a chat
|
|
62
67
|
baychat login [--start | --finish | --token <PAT>] [--base <url>]
|
|
63
68
|
Log this laptop in to BayChat (QR) and add the
|
|
64
69
|
BayChat MCP server to Claude Code. Creates a
|