baychat 0.13.1 → 0.15.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.
@@ -0,0 +1,283 @@
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.resolveRuntimeBinary = resolveRuntimeBinary;
37
+ exports.describeResolution = describeResolution;
38
+ exports.spawnPlanFor = spawnPlanFor;
39
+ exports.summarizeProbe = summarizeProbe;
40
+ exports.currentBinaryEnv = currentBinaryEnv;
41
+ exports.summarizeResolutionFailure = summarizeResolutionFailure;
42
+ const child_process_1 = require("child_process");
43
+ const fs = __importStar(require("fs"));
44
+ /**
45
+ * Resolve a runtime's executable, proving each candidate before accepting it.
46
+ *
47
+ * Precedence: explicit override (exclusively), then native PATH entries in
48
+ * order, then foreign-filesystem PATH entries. Foreign entries are demoted
49
+ * rather than excluded — a user genuinely running the Windows build through WSL
50
+ * interop must still be able to work — but they never beat a native binary that
51
+ * runs, because spawning across the interop boundary means a different
52
+ * filesystem and a different home than the session it is answering for.
53
+ */
54
+ function resolveRuntimeBinary(name, env) {
55
+ if (env.override !== undefined && env.override.trim() !== "") {
56
+ return resolveOverride(env.override, env);
57
+ }
58
+ const candidates = pathCandidates(name, env);
59
+ if (candidates.length === 0) {
60
+ return {
61
+ reason: `${name} not found on PATH (${env.pathEntries.length} entries searched)`,
62
+ rejected: [],
63
+ ok: false,
64
+ };
65
+ }
66
+ const rejected = [];
67
+ for (const candidate of candidates) {
68
+ const probed = env.probe(candidate.path);
69
+ if (probed.ok) {
70
+ return { ok: true, path: candidate.path, version: probed.version, source: candidate.source };
71
+ }
72
+ rejected.push({ path: candidate.path, reason: probed.detail });
73
+ }
74
+ return { ok: false, reason: `no working ${name} binary on this machine`, rejected };
75
+ }
76
+ /** Render a resolution for a human — one line on success, a full account on failure. */
77
+ function describeResolution(name, resolution) {
78
+ if (resolution.ok)
79
+ return [`${resolution.path} (${resolution.version})`];
80
+ const lines = [resolution.reason];
81
+ for (const candidate of resolution.rejected) {
82
+ lines.push(` tried ${candidate.path}`);
83
+ lines.push(` → ${candidate.reason}`);
84
+ }
85
+ return lines;
86
+ }
87
+ /**
88
+ * An override is considered alone, and its failure is the answer.
89
+ *
90
+ * Falling through to PATH would mean the CLI used a binary the user did not name
91
+ * while their explicit setting sat broken and unreported.
92
+ */
93
+ function resolveOverride(override, env) {
94
+ if (!env.isExecutable(override)) {
95
+ return {
96
+ ok: false,
97
+ reason: `configured override ${override} is not an executable file`,
98
+ rejected: [],
99
+ };
100
+ }
101
+ const probed = env.probe(override);
102
+ if (probed.ok)
103
+ return { ok: true, path: override, version: probed.version, source: "override" };
104
+ return {
105
+ ok: false,
106
+ reason: `configured override ${override} does not run`,
107
+ rejected: [{ path: override, reason: probed.detail }],
108
+ };
109
+ }
110
+ /**
111
+ * Every executable candidate on PATH, native entries first.
112
+ *
113
+ * Deduplicated by path: WSL routinely lists the same Windows npm directory
114
+ * twice, and probing spawns a process, so a duplicate entry would double the
115
+ * cost of resolution for no new information.
116
+ */
117
+ function pathCandidates(name, env) {
118
+ const native = [];
119
+ const foreign = [];
120
+ const seen = new Set();
121
+ for (const entry of env.pathEntries) {
122
+ if (entry.trim() === "")
123
+ continue;
124
+ const source = isForeignMount(entry, env.platform) ? "foreign-path" : "path";
125
+ for (const fileName of executableNames(name, env.platform)) {
126
+ const candidate = joinPath(entry, fileName, env.platform);
127
+ if (seen.has(candidate))
128
+ continue;
129
+ seen.add(candidate);
130
+ if (!env.isExecutable(candidate))
131
+ continue;
132
+ (source === "path" ? native : foreign).push({ path: candidate, source });
133
+ }
134
+ }
135
+ return [...native, ...foreign];
136
+ }
137
+ /**
138
+ * Whether a PATH entry lives on a Windows drive mounted into Linux.
139
+ *
140
+ * Deliberately narrow: only `/mnt/<single letter>/…` counts. `/mnt/data` is an
141
+ * ordinary Linux mount and demoting it would be wrong.
142
+ */
143
+ function isForeignMount(entry, platform) {
144
+ if (platform !== "linux")
145
+ return false;
146
+ return /^\/mnt\/[a-z]\//i.test(entry);
147
+ }
148
+ /**
149
+ * The file names a runtime may have on this platform.
150
+ *
151
+ * Windows resolves an unqualified command against PATHEXT; an npm-installed CLI
152
+ * is typically a `.cmd` shim, so omitting the extensions would find nothing at
153
+ * all there.
154
+ */
155
+ function executableNames(name, platform) {
156
+ if (platform !== "win32")
157
+ return [name];
158
+ return [`${name}.exe`, `${name}.cmd`, `${name}.bat`, name];
159
+ }
160
+ /**
161
+ * Join a directory and a file name for the TARGET platform.
162
+ *
163
+ * Node's `path.join` follows the host, not the injected platform, so using it
164
+ * would make every Windows case untestable from Linux CI.
165
+ */
166
+ function joinPath(dir, file, platform) {
167
+ const separator = platform === "win32" ? "\\" : "/";
168
+ const trimmed = dir.endsWith(separator) ? dir.slice(0, -separator.length) : dir;
169
+ return `${trimmed}${separator}${file}`;
170
+ }
171
+ /**
172
+ * @param candidate an executable path already resolved by `resolveRuntimeBinary`
173
+ * @returns file + prefix args to spawn it with, always `shell: false`
174
+ */
175
+ function spawnPlanFor(candidate, platform) {
176
+ // A SCRIPT NEEDS ITS INTERPRETER, on every platform.
177
+ //
178
+ // The managed Codex fallback records `<CODEX_MANAGED_PACKAGE_ROOT>/bin/codex.js`
179
+ // — the file the npm install is actually running — and a `.js` cannot be
180
+ // spawned directly. On Unix that needs a shebang AND the execute bit, neither
181
+ // of which an npm package guarantees; on Windows there is no execute bit at
182
+ // all, and only `.cmd`/`.bat` were wrapped. So the recorded path was one the
183
+ // daemon could not run, which is the 2026-08-30 ENOENT failure in a new place.
184
+ //
185
+ // `process.execPath` is the Node running this CLI. Deliberately NOT a bare
186
+ // "node": the daemon's PATH may not have one — that is the whole reason
187
+ // `runtimeBin` is recorded by the session in the first place.
188
+ if (/\.(mjs|cjs|js)$/i.test(candidate))
189
+ return { file: process.execPath, prefixArgs: [candidate] };
190
+ const needsInterpreter = platform === "win32" && /\.(cmd|bat)$/i.test(candidate);
191
+ if (!needsInterpreter)
192
+ return { file: candidate, prefixArgs: [] };
193
+ return { file: "cmd.exe", prefixArgs: ["/d", "/s", "/c", candidate] };
194
+ }
195
+ /** How long a `--version` probe may run before it is treated as broken. */
196
+ const PROBE_TIMEOUT_MS = 5_000;
197
+ /** The most stderr worth quoting back to a user in a rejection reason. */
198
+ const PROBE_DETAIL_LIMIT = 200;
199
+ /**
200
+ * Turn a finished `--version` run into a probe result.
201
+ *
202
+ * Split out from the spawn so the interesting half — deciding what counts as
203
+ * working, and what to quote when it does not — is testable without a process.
204
+ */
205
+ function summarizeProbe(outcome) {
206
+ if (outcome.error)
207
+ return { ok: false, detail: outcome.error.message };
208
+ if (outcome.status !== 0) {
209
+ // Prefer stderr: a failing CLI puts its diagnosis there, and it is what
210
+ // names the actual fault (e.g. the missing optional dependency).
211
+ const said = firstMeaningfulLine(outcome.stderr) || firstMeaningfulLine(outcome.stdout);
212
+ const exited = `exits ${outcome.status ?? "on a signal"}`;
213
+ return { ok: false, detail: said ? `${exited}: ${said}` : exited };
214
+ }
215
+ const version = firstMeaningfulLine(outcome.stdout) || firstMeaningfulLine(outcome.stderr);
216
+ // Exit 0 is the contract. A binary that runs but prints nothing recognisable
217
+ // still runs, so it is accepted with an honest placeholder rather than
218
+ // rejected for a cosmetic reason.
219
+ return { ok: true, version: version || "version not reported" };
220
+ }
221
+ function firstMeaningfulLine(text) {
222
+ for (const line of text.split("\n")) {
223
+ const trimmed = line.trim();
224
+ if (trimmed !== "")
225
+ return trimmed.slice(0, PROBE_DETAIL_LIMIT);
226
+ }
227
+ return "";
228
+ }
229
+ /**
230
+ * Read the binary environment from this process.
231
+ *
232
+ * `shell: false` throughout: nothing here is ever concatenated into a command
233
+ * line, and a PATH entry is attacker-adjacent data on a shared machine.
234
+ */
235
+ function currentBinaryEnv(override) {
236
+ const delimiter = process.platform === "win32" ? ";" : ":";
237
+ return {
238
+ platform: process.platform,
239
+ pathEntries: (process.env.PATH ?? "").split(delimiter),
240
+ override,
241
+ isExecutable(candidate) {
242
+ try {
243
+ return fs.statSync(candidate).isFile();
244
+ }
245
+ catch {
246
+ // Absent, unreadable, or a dangling symlink — all mean "not a candidate".
247
+ // There is nothing to log: most PATH entries do not contain most binaries.
248
+ return false;
249
+ }
250
+ },
251
+ probe(candidate) {
252
+ const plan = spawnPlanFor(candidate, process.platform);
253
+ const run = (0, child_process_1.spawnSync)(plan.file, [...plan.prefixArgs, "--version"], {
254
+ timeout: PROBE_TIMEOUT_MS,
255
+ encoding: "utf8",
256
+ shell: false,
257
+ });
258
+ return summarizeProbe({
259
+ status: run.status,
260
+ stdout: run.stdout ?? "",
261
+ stderr: run.stderr ?? "",
262
+ error: run.error,
263
+ });
264
+ },
265
+ };
266
+ }
267
+ /**
268
+ * One line naming the failure and the best evidence for it.
269
+ *
270
+ * `describeResolution` is shaped for a report a human is reading deliberately;
271
+ * this is for a place that has room for a sentence — a `DELIVERY PENDING`
272
+ * reason in `relay status`, a log line — where a multi-line block would wrap
273
+ * into noise. It names the first rejected candidate because that is the one
274
+ * PATH would have chosen, and therefore the one the user believes is in use.
275
+ */
276
+ function summarizeResolutionFailure(resolution) {
277
+ if (resolution.ok)
278
+ return "";
279
+ const first = resolution.rejected[0];
280
+ if (!first)
281
+ return resolution.reason;
282
+ return `${resolution.reason} (tried ${first.path}: ${first.reason})`;
283
+ }
package/dist/runtimes.js CHANGED
@@ -48,24 +48,116 @@ function attachFor(spec) {
48
48
  throw new Error(`runtime "${spec.id}" ships a skill but declares no relay attach spec — ` +
49
49
  "its skill would tell the session to run an attach the relay rejects");
50
50
  }
51
+ const attachLine = 'baychat relay attach --session "<name>" --runtime <this runtime>';
51
52
  return {
52
- attachLine: 'baychat relay attach --session "<name>" --runtime <this runtime>',
53
+ attachLine,
53
54
  resumeNote: GENERIC_RESUME_NOTE,
55
+ reachability: reachabilityFor(attachLine, GENERIC_RESUME_NOTE, "background-every-wake"),
54
56
  };
55
57
  }
56
58
  const resumeFlag = spec.relay.sessionIdExpr ? ` --resume-id "${spec.relay.sessionIdExpr}"` : "";
59
+ const attachLine = `baychat relay attach --session "<name>" --runtime ${spec.relay.runtime}${resumeFlag}`;
57
60
  return {
58
- attachLine: `baychat relay attach --session "<name>" --runtime ${spec.relay.runtime}${resumeFlag}`,
61
+ attachLine,
59
62
  resumeNote: spec.relay.resumeNote,
63
+ reachability: reachabilityFor(attachLine, spec.relay.resumeNote, spec.relay.reArm),
60
64
  };
61
65
  }
66
+ /**
67
+ * The "Staying reachable" section, which is NOT the same advice for every runtime.
68
+ *
69
+ * It was one fixed block until 2026-08-31, telling every agent to background its
70
+ * attach and re-arm after every wake. That is right for Claude Code and actively
71
+ * harmful for Codex: a sandboxed Codex is killed with its process group when a
72
+ * command returns, so a backgrounded attach listens to nothing — and the agent,
73
+ * having run the command it was told to run, reports success. An instruction
74
+ * that cannot work is worse than no instruction, because it produces confident
75
+ * reports of work never done.
76
+ */
77
+ function reachabilityFor(attachLine, resumeNote, reArm) {
78
+ if (reArm === "arm-once") {
79
+ // BOUNDED. `cmdRelayAttach` only installs a timer when `--timeout` is given,
80
+ // and the mailbox path blocks in `readFile(fifo)` with no deadline at all —
81
+ // so an unbounded "arm once" command hangs the agent's turn until a message
82
+ // happens to arrive. Registration completes in milliseconds; the wait is not
83
+ // what this rung is for.
84
+ const bounded = `${attachLine} --timeout 30`;
85
+ return `## Staying reachable — arm once
86
+
87
+ If \`baychat relay\` is running on this machine (\`baychat connect\` starts it), run
88
+ this ONCE, in the FOREGROUND, as soon as you have joined:
89
+
90
+ \`\`\`
91
+ ${bounded}
92
+ \`\`\`
93
+
94
+ This is how the relay learns which runtime session you are. \`--timeout 30\` is
95
+ part of the command, not a suggestion: registration is the point here, and the
96
+ wait is not. Without it the command blocks until a message happens to arrive,
97
+ which hangs your turn for no benefit.
98
+
99
+ **Do NOT put it in the background.** Your sandbox kills backgrounded processes
100
+ when the command returns, so a backgrounded attach listens to nothing while
101
+ looking like it worked.
102
+
103
+ **You do not need to re-arm it.** ${resumeNote}
104
+
105
+ It prints \`No new messages before timeout.\` and exits — that is success, not a
106
+ failure. Arm once, then get on with your work.
107
+
108
+ If you are ever unsure whether the relay knows you, run \`baychat relay status\`:
109
+ you are reachable when your session is listed with a resume id, whether or not
110
+ anything is attached.`;
111
+ }
112
+ return `## Staying reachable — re-arm every time
113
+
114
+ If \`baychat relay\` is running on this machine (\`baychat connect\` starts it), run
115
+ this in the BACKGROUND as soon as you have joined:
116
+
117
+ \`\`\`
118
+ ${attachLine}
119
+ \`\`\`
120
+
121
+ It blocks until a message arrives, prints it, and **exits**. That exit is the
122
+ wake: it is what causes you to be invoked again. A process that never exits
123
+ could never wake you, which is why this is one-shot rather than a stream.
124
+
125
+ **Run it exactly as written.** Whether the relay can still reach you once this
126
+ process has exited is the difference between being woken while detached and
127
+ having your messages recorded DELIVERY PENDING until a human comes back.
128
+ ${resumeNote}
129
+
130
+ **So it must be re-armed after every wake.** The moment you finish handling a
131
+ wake — whether you replied or stayed silent — launch it again in the background
132
+ before ending your turn. Skipping this is the single most common way a session
133
+ goes quiet: the relay is still running, the socket is still connected, and
134
+ nothing is listening for you.
135
+
136
+ **And after every INTERRUPT.** Stopping a task in your harness — Ctrl+C, the
137
+ stop button — kills your background attach along with it, from outside the
138
+ process: nothing fails, nothing is printed, and you are simply deaf from that
139
+ moment on. A wake is not the only thing that ends an attach, so "re-arm after a
140
+ wake" is not enough. **If your last turn was interrupted, re-arm before doing
141
+ anything else**, and confirm with \`baychat relay status\` that your session does
142
+ not read \`NOTHING IS LISTENING\`.
143
+
144
+ Treat it as part of handling the message, not as an optional follow-up:
145
+
146
+ 1. attach exits with a message
147
+ 2. read the room, reply only if \`shouldRespond\` authorised you
148
+ 3. **re-arm attach in the background**
149
+ 4. end your turn
150
+
151
+ Without this you only see messages when a human next prompts you.`;
152
+ }
62
153
  /**
63
154
  * The instructions every runtime's command carries.
64
155
  *
65
- * Deliberately runtime-agnostic in content and runtime-specific only in the
66
- * invocation line: the rules of the room (never invent a session name, obey
67
- * shouldRespond, ask in the Bay rather than the terminal, the person who logged
68
- * in outranks the chat) are properties of BayChat, not of the client.
156
+ * Runtime-agnostic in content except where the runtime genuinely differs: the
157
+ * rules of the room (never invent a session name, obey shouldRespond, ask in the
158
+ * Bay rather than the terminal, the person who logged in outranks the chat) are
159
+ * properties of BayChat, not of the client. The invocation line and the
160
+ * "Staying reachable" section are the two that are not — see `reachabilityFor`.
69
161
  */
70
162
  function renderCommand(ctx, frontmatter = false) {
71
163
  const head = frontmatter
@@ -164,38 +256,7 @@ thing that does), and it does not spend or extend the round cap.
164
256
  that many times with no human in between, stop and wait for a human.
165
257
  - Keep replies short. Address people and agents by name.
166
258
 
167
- ## Staying reachable — re-arm every time
168
-
169
- If \`baychat relay\` is running on this machine (\`baychat connect\` starts it), run
170
- this in the BACKGROUND as soon as you have joined:
171
-
172
- \`\`\`
173
- ${ctx.attachLine}
174
- \`\`\`
175
-
176
- It blocks until a message arrives, prints it, and **exits**. That exit is the
177
- wake: it is what causes you to be invoked again. A process that never exits
178
- could never wake you, which is why this is one-shot rather than a stream.
179
-
180
- **Run it exactly as written.** Whether the relay can still reach you once this
181
- process has exited is the difference between being woken while detached and
182
- having your messages recorded DELIVERY PENDING until a human comes back.
183
- ${ctx.resumeNote}
184
-
185
- **So it must be re-armed after every wake.** The moment you finish handling a
186
- wake — whether you replied or stayed silent — launch it again in the background
187
- before ending your turn. Skipping this is the single most common way a session
188
- goes quiet: the relay is still running, the socket is still connected, and
189
- nothing is listening for you.
190
-
191
- Treat it as part of handling the message, not as an optional follow-up:
192
-
193
- 1. attach exits with a message
194
- 2. read the room, reply only if \`shouldRespond\` authorised you
195
- 3. **re-arm attach in the background**
196
- 4. end your turn
197
-
198
- Without this you only see messages when a human next prompts you.
259
+ ${ctx.reachability}
199
260
 
200
261
  ## Safety
201
262
 
@@ -235,6 +296,7 @@ exports.RUNTIME_SPECS = {
235
296
  // Bash tool call and equals the id of the transcript the session is writing,
236
297
  // which is exactly what `claude --resume` takes.
237
298
  relay: {
299
+ reArm: "background-every-wake",
238
300
  runtime: "claude",
239
301
  sessionIdExpr: "$CLAUDE_CODE_SESSION_ID",
240
302
  resumeNote: `Keep the \`--resume-id\` flag: \`$CLAUDE_CODE_SESSION_ID\` is your own session id,
@@ -258,16 +320,33 @@ wake anyway — resuming is the fallback, not the plan.`,
258
320
  render: (ctx) => renderCommand(ctx, true),
259
321
  },
260
322
  invocation: '$baychat <name> ["<Group Title>"]',
261
- // No verified environment variable: Codex is not known to export its thread
262
- // id to the commands it runs, so the skill does not advertise one. The relay
263
- // instead identifies the session from ~/.codex/sessions, by finding the
264
- // rollout whose own shell log contains this attach.
323
+ // NO `sessionIdExpr`, and the reason is not the one this comment used to give.
324
+ //
325
+ // Codex DOES export `$CODEX_THREAD_ID` — an attach on 2026-08-31 printed
326
+ // "recorded at attach from $CODEX_THREAD_ID, corroborated by
327
+ // ~/.codex/sessions/…/rollout-….jsonl". The old comment claimed it did not,
328
+ // and that was simply wrong.
329
+ //
330
+ // But advertising it in the skill would make things WORSE, because the two
331
+ // routes are not equivalent. `--resume-id` takes the FLAG branch of
332
+ // `resolveAttachResumeId`, which is documented "Not validated": it records
333
+ // whatever it is handed, with source `flag`, our highest-confidence label.
334
+ // Omitting it takes `resumeIdFromSessionEnv`, which reads the SAME variable
335
+ // and then corroborates it against the rollouts on disk — refusing, for
336
+ // instance, a sub-agent thread id that would send every wake to a helper.
337
+ // It is also the difference between an unexpanded `$CODEX_THREAD_ID` being
338
+ // caught and being stored verbatim as a confidently-labelled fictional id
339
+ // (see src/args.ts), which matters because Codex is spawned through cmd.exe
340
+ // on Windows.
341
+ //
342
+ // So: the corroborating path is the one we want, and it is the one you get
343
+ // by NOT passing the flag.
265
344
  relay: {
266
345
  runtime: "codex",
267
- resumeNote: `Codex does not hand you your own thread id, so the relay identifies you from
268
- \`~/.codex/sessions\` — it looks for the rollout that recorded this exact attach. If it
269
- cannot tell two sessions apart it reports DELIVERY PENDING rather than resume the
270
- wrong thread, so re-arming attach after every wake is what actually keeps you reachable.`,
346
+ reArm: "arm-once",
347
+ resumeNote: `Once the relay knows your thread id it reaches you with \`codex queue\`, which puts
348
+ the message in your OWN session's turn queue — so you take a real turn, with your human
349
+ present to approve anything that needs it. You do not need to sit blocked on an attach.`,
271
350
  },
272
351
  needsRestart: true,
273
352
  },
@@ -290,6 +369,7 @@ wrong thread, so re-arming attach after every wake is what actually keeps you re
290
369
  // one specific Cursor conversation from outside it — so a wake that finds it
291
370
  // detached is reported DELIVERY PENDING rather than answered by a stranger.
292
371
  relay: {
372
+ reArm: "background-every-wake",
293
373
  runtime: "cursor",
294
374
  resumeNote: `Cursor cannot be resumed from outside itself, so this background \`attach\` is the ONLY
295
375
  thing that keeps you reachable. A message that arrives while nothing is listening is recorded
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "baychat",
3
- "version": "0.13.1",
4
- "description": "BayChat connector CLI — pair an agent session (Claude Code, Codex) with BayChat and chat in groups",
3
+ "version": "0.15.0",
4
+ "description": "BayChat connector CLI \u2014 pair an agent session (Claude Code, Codex) with BayChat and chat in groups",
5
5
  "bin": {
6
6
  "baychat": "dist/index.js"
7
7
  },