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
|
@@ -0,0 +1,235 @@
|
|
|
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.START_RUNTIMES = void 0;
|
|
37
|
+
exports.startRefusal = startRefusal;
|
|
38
|
+
exports.cmdStart = cmdStart;
|
|
39
|
+
const child_process_1 = require("child_process");
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const readline = __importStar(require("readline"));
|
|
43
|
+
const config_1 = require("./config");
|
|
44
|
+
const terminal_pane_1 = require("./relay/terminal-pane");
|
|
45
|
+
const profiles_1 = require("./relay/profiles");
|
|
46
|
+
const runtime_binary_1 = require("./runtime-binary");
|
|
47
|
+
const update_command_1 = require("./update-command");
|
|
48
|
+
/**
|
|
49
|
+
* `baychat start [name]` — the whole product in one command.
|
|
50
|
+
*
|
|
51
|
+
* ── THE BAR THIS EXISTS TO MEET ───────────────────────────────────────────────────────
|
|
52
|
+
*
|
|
53
|
+
* Karmen's, 2026-09-14: *"everything we do we have to make very easy, basically just
|
|
54
|
+
* download baychat, and then start session, nothing else."*
|
|
55
|
+
*
|
|
56
|
+
* So: this launches the person's agent, inside a terminal multiplexer when one is
|
|
57
|
+
* available, and says nothing about multiplexers at all. They type one word. `/clear` and
|
|
58
|
+
* `/new` from the app then work, because `relay attach` finds `$TMUX`/`$TMUX_PANE` waiting
|
|
59
|
+
* for it — and if no multiplexer is installed, the agent still starts and everything except
|
|
60
|
+
* those two commands still works. **The fallback is silent on purpose.** A warning about a
|
|
61
|
+
* program they have never heard of, at the moment they are trying to start work, is exactly
|
|
62
|
+
* the "something else" this command exists to remove.
|
|
63
|
+
*
|
|
64
|
+
* ── WHY NOT A PSEUDO-TERMINAL OF OUR OWN ──────────────────────────────────────────────
|
|
65
|
+
*
|
|
66
|
+
* `node-pty` would remove the multiplexer requirement completely and work on Windows too.
|
|
67
|
+
* It is 64 MB unpacked and compiles through node-gyp on install, so `npx baychat` would
|
|
68
|
+
* start demanding a C++ toolchain — which fails for everyone, not only the people who
|
|
69
|
+
* wanted `/clear`. Checked 2026-09-14 and rejected on that basis, not on taste.
|
|
70
|
+
*
|
|
71
|
+
* ── WHY IT DOES NOT JUST JOIN, TOO ────────────────────────────────────────────────────
|
|
72
|
+
*
|
|
73
|
+
* Joining happens INSIDE the agent (the installed skill runs `baychat join`), because that
|
|
74
|
+
* is the only place the session's own runtime id, binary and pane can be read. This command
|
|
75
|
+
* gets the agent into a terminal we can reach; the agent does the rest, exactly as it does
|
|
76
|
+
* today.
|
|
77
|
+
*/
|
|
78
|
+
const PREFERENCE_FILE = "start-runtime.json";
|
|
79
|
+
/**
|
|
80
|
+
* Runtimes `start` never probes for or launches. dsh (Ruling R27): it is experimental and
|
|
81
|
+
* meant to run only in a VM, and BayChat runs it through the relay over ACP
|
|
82
|
+
* (`baychat connect dsh`), which enforces its modes — a terminal launch would not.
|
|
83
|
+
*/
|
|
84
|
+
const NOT_STARTED = {
|
|
85
|
+
dsh: "DeepSeek Harness is not started from here: the relay runs it over ACP. Set it up with `baychat connect dsh`.",
|
|
86
|
+
};
|
|
87
|
+
/** Runtimes worth probing for, best-supported first. */
|
|
88
|
+
exports.START_RUNTIMES = ["claude", "codex", ...profiles_1.RUNTIME_PROFILES.map((p) => p.id)].filter((id) => !Object.hasOwn(NOT_STARTED, id));
|
|
89
|
+
/** Why `start` will not launch `runtime`, or undefined when it will. */
|
|
90
|
+
function startRefusal(runtime) {
|
|
91
|
+
return Object.hasOwn(NOT_STARTED, runtime) ? NOT_STARTED[runtime] : undefined;
|
|
92
|
+
}
|
|
93
|
+
async function cmdStart(args) {
|
|
94
|
+
if (!(0, config_1.loadDeviceCredentials)()) {
|
|
95
|
+
console.error("This machine is not signed in yet. Run `npx baychat login` first.");
|
|
96
|
+
return 1;
|
|
97
|
+
}
|
|
98
|
+
// THE UPDATE NOBODY HAS TO REMEMBER.
|
|
99
|
+
//
|
|
100
|
+
// Shipping a CLI change reaches nobody until the package is refetched, every connected
|
|
101
|
+
// client's config is rewritten, and the relay is restarted — a live daemon holds old code
|
|
102
|
+
// in memory indefinitely. Three steps, in different places, and the failure is silence.
|
|
103
|
+
// So the one command people actually run does them, once a day.
|
|
104
|
+
//
|
|
105
|
+
// ⚠️ NEVER BLOCKS STARTING WORK. Any failure — offline, npm down, a permissions problem —
|
|
106
|
+
// is swallowed: the agent starts anyway. An update is an improvement to a session, never
|
|
107
|
+
// a precondition for one, and a person who cannot start their agent because a background
|
|
108
|
+
// refresh failed would rightly never trust this command again.
|
|
109
|
+
if (!args.includes("--no-update") && (0, update_command_1.autoUpdateDue)()) {
|
|
110
|
+
try {
|
|
111
|
+
await (0, update_command_1.cmdUpdate)({ quiet: true });
|
|
112
|
+
}
|
|
113
|
+
catch {
|
|
114
|
+
/* Starting the agent matters more than being current. */
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
const runtimeFlag = valueAfter(args, "--runtime");
|
|
118
|
+
const positional = args.filter((a) => !a.startsWith("--") && a !== runtimeFlag);
|
|
119
|
+
const name = positional[0]?.trim();
|
|
120
|
+
const runtime = runtimeFlag ?? (await chooseRuntime());
|
|
121
|
+
if (!runtime)
|
|
122
|
+
return 1;
|
|
123
|
+
const refusal = startRefusal(runtime);
|
|
124
|
+
if (refusal) {
|
|
125
|
+
console.error(refusal);
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
const binary = (0, runtime_binary_1.resolveRuntimeBinary)(runtime, (0, runtime_binary_1.currentBinaryEnv)());
|
|
129
|
+
if (!binary.ok) {
|
|
130
|
+
console.error(`Could not start ${runtime}: ${binary.reason}`);
|
|
131
|
+
return 1;
|
|
132
|
+
}
|
|
133
|
+
// ALREADY INSIDE ONE? Then just run the agent. Nesting multiplexers sends every prefix
|
|
134
|
+
// key to the wrong one, and the person already has everything this command provides.
|
|
135
|
+
const inside = (0, terminal_pane_1.detectPane)();
|
|
136
|
+
const multiplexer = inside ? undefined : firstAvailable(["tmux", "screen"]);
|
|
137
|
+
if (!multiplexer) {
|
|
138
|
+
const result = (0, child_process_1.spawnSync)(binary.path, [], { stdio: "inherit" });
|
|
139
|
+
return result.status ?? 0;
|
|
140
|
+
}
|
|
141
|
+
const sessionName = name || "baychat";
|
|
142
|
+
const argv = multiplexer === "tmux"
|
|
143
|
+
? // `-A` attaches to an existing session of that name instead of failing, so running
|
|
144
|
+
// this twice returns you to your agent rather than erroring at you.
|
|
145
|
+
["new", "-A", "-s", sessionName, binary.path]
|
|
146
|
+
: ["-S", sessionName, "-R", binary.path];
|
|
147
|
+
const result = (0, child_process_1.spawnSync)(multiplexer, argv, { stdio: "inherit" });
|
|
148
|
+
if (result.error) {
|
|
149
|
+
// Falling back rather than failing: the multiplexer is an enhancement, and refusing to
|
|
150
|
+
// start somebody's agent because of one would be the tail wagging the dog.
|
|
151
|
+
const direct = (0, child_process_1.spawnSync)(binary.path, [], { stdio: "inherit" });
|
|
152
|
+
return direct.status ?? 0;
|
|
153
|
+
}
|
|
154
|
+
return result.status ?? 0;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* Which agent to start.
|
|
158
|
+
*
|
|
159
|
+
* Asks only when it genuinely cannot tell — one installed runtime needs no question — and
|
|
160
|
+
* remembers the answer, because being asked the same thing every morning is "something
|
|
161
|
+
* else" by another name.
|
|
162
|
+
*/
|
|
163
|
+
async function chooseRuntime() {
|
|
164
|
+
const remembered = readPreference();
|
|
165
|
+
if (remembered)
|
|
166
|
+
return remembered;
|
|
167
|
+
const env = (0, runtime_binary_1.currentBinaryEnv)();
|
|
168
|
+
const installed = [...new Set(exports.START_RUNTIMES)].filter((id) => (0, runtime_binary_1.resolveRuntimeBinary)(id, env).ok);
|
|
169
|
+
if (installed.length === 0) {
|
|
170
|
+
console.error("No coding agent found on this machine. Install one (Claude Code or Codex) and run this again.");
|
|
171
|
+
return undefined;
|
|
172
|
+
}
|
|
173
|
+
if (installed.length === 1) {
|
|
174
|
+
writePreference(installed[0]);
|
|
175
|
+
return installed[0];
|
|
176
|
+
}
|
|
177
|
+
console.log("Which agent should BayChat start?");
|
|
178
|
+
installed.forEach((id, index) => console.log(` ${index + 1}. ${id}`));
|
|
179
|
+
const answer = await ask(`Choose 1-${installed.length}: `);
|
|
180
|
+
const chosen = installed[Number(answer.trim()) - 1];
|
|
181
|
+
if (!chosen) {
|
|
182
|
+
console.error("Not one of the options.");
|
|
183
|
+
return undefined;
|
|
184
|
+
}
|
|
185
|
+
writePreference(chosen);
|
|
186
|
+
return chosen;
|
|
187
|
+
}
|
|
188
|
+
function ask(question) {
|
|
189
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
190
|
+
return new Promise((resolve) => rl.question(question, (answer) => {
|
|
191
|
+
rl.close();
|
|
192
|
+
resolve(answer);
|
|
193
|
+
}));
|
|
194
|
+
}
|
|
195
|
+
function preferencePath() {
|
|
196
|
+
return path.join((0, config_1.configDir)(), PREFERENCE_FILE);
|
|
197
|
+
}
|
|
198
|
+
function readPreference() {
|
|
199
|
+
try {
|
|
200
|
+
const raw = JSON.parse(fs.readFileSync(preferencePath(), "utf8"));
|
|
201
|
+
return typeof raw.runtime === "string" ? raw.runtime : undefined;
|
|
202
|
+
}
|
|
203
|
+
catch {
|
|
204
|
+
// Absent or unreadable is simply "not chosen yet" — never an error worth showing.
|
|
205
|
+
return undefined;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
function writePreference(runtime) {
|
|
209
|
+
try {
|
|
210
|
+
fs.mkdirSync((0, config_1.configDir)(), { recursive: true });
|
|
211
|
+
fs.writeFileSync(preferencePath(), JSON.stringify({ runtime }, null, 2));
|
|
212
|
+
}
|
|
213
|
+
catch {
|
|
214
|
+
// Not remembering costs one question next time. Never worth failing a start for.
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* The first of these that RUNS, not the first on the PATH.
|
|
219
|
+
*
|
|
220
|
+
* The distinction has already cost this codebase a week: the relay's runtime probe picked a
|
|
221
|
+
* binary that existed and could not start, and resolution fell through to a version too old
|
|
222
|
+
* for the feature it was being asked for.
|
|
223
|
+
*/
|
|
224
|
+
function firstAvailable(candidates) {
|
|
225
|
+
for (const candidate of candidates) {
|
|
226
|
+
const probe = (0, child_process_1.spawnSync)(candidate, ["--version"], { stdio: "ignore" });
|
|
227
|
+
if (!probe.error && probe.status === 0)
|
|
228
|
+
return candidate;
|
|
229
|
+
}
|
|
230
|
+
return undefined;
|
|
231
|
+
}
|
|
232
|
+
function valueAfter(args, flagName) {
|
|
233
|
+
const index = args.indexOf(flagName);
|
|
234
|
+
return index === -1 ? undefined : args[index + 1];
|
|
235
|
+
}
|
|
@@ -0,0 +1,201 @@
|
|
|
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.cmdUpdate = cmdUpdate;
|
|
37
|
+
exports.autoUpdateDue = autoUpdateDue;
|
|
38
|
+
const child_process_1 = require("child_process");
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
const path = __importStar(require("path"));
|
|
41
|
+
const config_1 = require("./config");
|
|
42
|
+
const credential_refresh_1 = require("./credential-refresh");
|
|
43
|
+
const commands_1 = require("./commands");
|
|
44
|
+
const client_paths_1 = require("./client-paths");
|
|
45
|
+
const commands_2 = require("./relay/commands");
|
|
46
|
+
/**
|
|
47
|
+
* `baychat update` — bring this whole machine onto the current version, in one word.
|
|
48
|
+
*
|
|
49
|
+
* ── THE PROBLEM IT EXISTS TO END ──────────────────────────────────────────────────────
|
|
50
|
+
*
|
|
51
|
+
* Karmen, 2026-09-14: *"it's not every time when we make an update the person has to
|
|
52
|
+
* update three different things from two different terminals… I just want it to be like
|
|
53
|
+
* `baychat update` and everything reloads."*
|
|
54
|
+
*
|
|
55
|
+
* She is describing something this project has been bitten by repeatedly. Shipping a CLI
|
|
56
|
+
* change reaches NOBODY until, on every machine: the package is refetched, each connected
|
|
57
|
+
* client's MCP config is rewritten, and **the relay daemon is restarted** — a live daemon
|
|
58
|
+
* holds the old code in memory and will happily keep running it for weeks. Three steps, in
|
|
59
|
+
* different places, none of them discoverable, and the failure is silence rather than an
|
|
60
|
+
* error.
|
|
61
|
+
*
|
|
62
|
+
* ── IT REFRESHES WHAT IS ALREADY THERE, AND CONNECTS NOTHING NEW ──────────────────────
|
|
63
|
+
*
|
|
64
|
+
* Every step is idempotent and every step is a RE-application of a decision the person
|
|
65
|
+
* already made. `refreshExistingClientConfigs` only rewrites config files that already
|
|
66
|
+
* name BayChat; Claude Code is only re-registered if its binary is present; Hermes only if
|
|
67
|
+
* `~/.hermes` exists. Running this on a machine that has never been connected does
|
|
68
|
+
* nothing, loudly — which is the right answer, because "update" must never quietly become
|
|
69
|
+
* "set up", and a command that installed things would not be safe to run automatically.
|
|
70
|
+
*
|
|
71
|
+
* And it must be safe to run automatically, because that is the second half of her ask:
|
|
72
|
+
* `baychat start` calls this on a day-boundary so nobody has to remember it at all.
|
|
73
|
+
*/
|
|
74
|
+
/** How often `start` re-checks, when it is driving. One a day is invisible; one an hour is a tax. */
|
|
75
|
+
const AUTO_CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
76
|
+
const STAMP_FILE = "last-update-check.json";
|
|
77
|
+
async function cmdUpdate(opts = {}) {
|
|
78
|
+
const say = (line) => {
|
|
79
|
+
if (!opts.quiet)
|
|
80
|
+
console.log(line);
|
|
81
|
+
};
|
|
82
|
+
const device = (0, config_1.loadDeviceCredentials)();
|
|
83
|
+
if (!device) {
|
|
84
|
+
console.error("This machine is not signed in, so there is nothing to update. Run `npx baychat login` first.");
|
|
85
|
+
return 1;
|
|
86
|
+
}
|
|
87
|
+
const done = [];
|
|
88
|
+
// 1. THE PACKAGE. Only a global install can be upgraded in place; an npx run already
|
|
89
|
+
// fetched whatever npx had cached, and the honest advice there is `@latest`.
|
|
90
|
+
const packageStep = updatePackage();
|
|
91
|
+
if (packageStep)
|
|
92
|
+
done.push(packageStep);
|
|
93
|
+
// 2. EVERY CONNECTED CLIENT's copy of the credential and endpoint.
|
|
94
|
+
try {
|
|
95
|
+
const updated = (0, credential_refresh_1.refreshExistingClientConfigs)({ url: `${device.baseUrl}/api/mcp`, token: device.token }, commands_1.realConfigIo, (0, client_paths_1.currentPathEnv)());
|
|
96
|
+
for (const file of updated)
|
|
97
|
+
done.push(`refreshed ${file}`);
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
// Never fatal: the next steps are independent, and a machine that failed one of four
|
|
101
|
+
// is better off doing the other three than stopping at the first.
|
|
102
|
+
say(` Could not refresh client configs: ${errText(err)}`);
|
|
103
|
+
}
|
|
104
|
+
// 3. CLAUDE CODE, which is not an MCP-config client and registers through its own CLI.
|
|
105
|
+
if (hasBinary("claude") && (0, commands_1.registerWithClaude)(device.baseUrl, device.token)) {
|
|
106
|
+
done.push("re-registered BayChat with Claude Code");
|
|
107
|
+
}
|
|
108
|
+
// 4. THE RELAY — the step people forget, and the one that silently keeps old code
|
|
109
|
+
// running. Stopped and started rather than signalled: the daemon loads its code at
|
|
110
|
+
// process start, so nothing short of a restart picks up a new version.
|
|
111
|
+
try {
|
|
112
|
+
await (0, commands_2.cmdRelayStop)();
|
|
113
|
+
}
|
|
114
|
+
catch {
|
|
115
|
+
// Not running is the normal case on a fresh boot, and is not a failure to report.
|
|
116
|
+
}
|
|
117
|
+
const relay = await (0, commands_2.ensureRelayInstalled)();
|
|
118
|
+
done.push(relay.startsWith("Relay:") ? relay.toLowerCase() : relay);
|
|
119
|
+
writeStamp();
|
|
120
|
+
if (done.length === 0) {
|
|
121
|
+
say("Everything was already up to date.");
|
|
122
|
+
return 0;
|
|
123
|
+
}
|
|
124
|
+
if (opts.quiet) {
|
|
125
|
+
// Driven by `start`: one line, because the person asked to begin work, not to read a
|
|
126
|
+
// report. The full account is available by running the command by hand.
|
|
127
|
+
console.log(`BayChat updated (${done.length} step${done.length === 1 ? "" : "s"}).`);
|
|
128
|
+
return 0;
|
|
129
|
+
}
|
|
130
|
+
console.log("Updated this machine:");
|
|
131
|
+
for (const line of done)
|
|
132
|
+
console.log(` ✓ ${line}`);
|
|
133
|
+
return 0;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Has it been long enough for `start` to check again?
|
|
137
|
+
*
|
|
138
|
+
* A stamp rather than a version comparison: the point is to bound how often the network is
|
|
139
|
+
* touched on a path whose whole job is to start work quickly.
|
|
140
|
+
*/
|
|
141
|
+
function autoUpdateDue(now = Date.now()) {
|
|
142
|
+
try {
|
|
143
|
+
const raw = JSON.parse(fs.readFileSync(stampPath(), "utf8"));
|
|
144
|
+
if (typeof raw.at !== "number")
|
|
145
|
+
return true;
|
|
146
|
+
return now - raw.at >= AUTO_CHECK_INTERVAL_MS;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// Never checked, or unreadable — checking is the safe answer.
|
|
150
|
+
return true;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
function stampPath() {
|
|
154
|
+
return path.join((0, config_1.configDir)(), STAMP_FILE);
|
|
155
|
+
}
|
|
156
|
+
function writeStamp() {
|
|
157
|
+
try {
|
|
158
|
+
fs.mkdirSync((0, config_1.configDir)(), { recursive: true });
|
|
159
|
+
fs.writeFileSync(stampPath(), JSON.stringify({ at: Date.now() }));
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
// Costs one extra check tomorrow. Never worth failing an update for.
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Upgrade the package itself, where that is a thing this process can do.
|
|
167
|
+
*
|
|
168
|
+
* ⚠️ AN `npx` RUN CANNOT UPGRADE ITSELF, and pretending otherwise is worse than saying so:
|
|
169
|
+
* npx resolves from its own cache, so `npx baychat` can keep running a version from weeks
|
|
170
|
+
* ago while reporting success at every step. The honest output there is the one flag that
|
|
171
|
+
* fixes it.
|
|
172
|
+
*/
|
|
173
|
+
function updatePackage() {
|
|
174
|
+
const globalInstall = isGlobalInstall();
|
|
175
|
+
if (!globalInstall) {
|
|
176
|
+
console.log("Tip: `npx baychat` can run a cached older copy. Use `npx baychat@latest` (or install it once with `npm i -g baychat`) so updates land.");
|
|
177
|
+
return undefined;
|
|
178
|
+
}
|
|
179
|
+
const result = (0, child_process_1.spawnSync)("npm", ["install", "-g", "baychat@latest"], {
|
|
180
|
+
encoding: "utf8",
|
|
181
|
+
timeout: 120_000,
|
|
182
|
+
});
|
|
183
|
+
if (result.status !== 0) {
|
|
184
|
+
console.log(` Could not upgrade the package: ${result.stderr?.trim() || "npm failed"}`);
|
|
185
|
+
return undefined;
|
|
186
|
+
}
|
|
187
|
+
return "upgraded the baychat package";
|
|
188
|
+
}
|
|
189
|
+
/** Is this process running from a global npm install, rather than an npx cache? */
|
|
190
|
+
function isGlobalInstall() {
|
|
191
|
+
const self = process.argv[1] ?? "";
|
|
192
|
+
// npx unpacks into `_npx` inside the npm cache; a global install never does.
|
|
193
|
+
return self.includes("node_modules") && !self.includes("_npx");
|
|
194
|
+
}
|
|
195
|
+
function hasBinary(name) {
|
|
196
|
+
const probe = (0, child_process_1.spawnSync)(name, ["--version"], { stdio: "ignore", timeout: 15_000 });
|
|
197
|
+
return !probe.error && probe.status === 0;
|
|
198
|
+
}
|
|
199
|
+
function errText(err) {
|
|
200
|
+
return err instanceof Error ? err.message : String(err);
|
|
201
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "baychat",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "BayChat connector CLI — pair an agent session (Claude Code, Codex) with BayChat and chat in groups",
|
|
5
5
|
"bin": {
|
|
6
6
|
"baychat": "dist/index.js"
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
"sync-protocol": "node scripts/sync-protocol.mjs",
|
|
14
14
|
"build": "node scripts/sync-protocol.mjs && node scripts/sync-hermes-plugin.mjs && tsc && node scripts/make-executable.mjs",
|
|
15
15
|
"test": "vitest run",
|
|
16
|
+
"test:contract": "vitest run src/relay/acp/contract.test.ts",
|
|
16
17
|
"prepublishOnly": "npm run build && npm test"
|
|
17
18
|
},
|
|
18
19
|
"devDependencies": {
|
|
@@ -38,6 +39,7 @@
|
|
|
38
39
|
"LICENSE"
|
|
39
40
|
],
|
|
40
41
|
"dependencies": {
|
|
42
|
+
"@agentclientprotocol/sdk": "1.4.0",
|
|
41
43
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
42
44
|
"qrcode": "^1.5.4",
|
|
43
45
|
"yaml": "^2.9.0",
|