baychat 0.34.1 → 0.35.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.
Files changed (53) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +13 -2
  3. package/dist/api.js +30 -0
  4. package/dist/bays-command.js +6 -0
  5. package/dist/channel/feed.js +48 -4
  6. package/dist/channel/log-file.js +37 -0
  7. package/dist/channel/server.js +34 -4
  8. package/dist/claude-context-flush.js +31 -8
  9. package/dist/claude-context.js +19 -5
  10. package/dist/claude-onboarding.js +21 -8
  11. package/dist/cli-help.js +3 -0
  12. package/dist/client-paths.js +6 -1
  13. package/dist/client-scope.js +34 -0
  14. package/dist/commands.js +16 -8
  15. package/dist/connect-link.js +205 -0
  16. package/dist/connect-plan.js +2 -2
  17. package/dist/connect.js +8 -0
  18. package/dist/device-login.js +19 -0
  19. package/dist/launch/command.js +6 -3
  20. package/dist/main.js +18 -2
  21. package/dist/mcp-files.js +2 -0
  22. package/dist/mcp-register.js +2 -0
  23. package/dist/protocol-content.js +1 -1
  24. package/dist/refresh-clients.js +7 -0
  25. package/dist/relay/acp/approval.js +99 -30
  26. package/dist/relay/acp/daemon-glue.js +2 -1
  27. package/dist/relay/adapters.js +5 -1
  28. package/dist/relay/bay-relays.js +18 -5
  29. package/dist/relay/codex-allowances.js +113 -0
  30. package/dist/relay/codex-app-server.js +637 -157
  31. package/dist/relay/codex-approvals.js +390 -0
  32. package/dist/relay/codex-commands.js +61 -0
  33. package/dist/relay/codex-log.js +260 -0
  34. package/dist/relay/codex-runner.js +505 -0
  35. package/dist/relay/codex-version.js +50 -0
  36. package/dist/relay/daemon.js +190 -6
  37. package/dist/relay/fresh-start.js +2 -1
  38. package/dist/relay/launcher/capabilities.js +24 -9
  39. package/dist/relay/launcher/codex-launch.js +9 -2
  40. package/dist/relay/launcher/codex-relay-launch.js +80 -0
  41. package/dist/relay/launcher/handle.js +101 -30
  42. package/dist/relay/launcher/loop.js +5 -1
  43. package/dist/relay/launcher/stop-registered.js +124 -9
  44. package/dist/relay/registry.js +30 -0
  45. package/dist/relay/runtime-limits.js +167 -0
  46. package/dist/relay/spawn-env.js +40 -3
  47. package/dist/relay/terminal-pane.js +1 -0
  48. package/dist/sea-names.js +34 -0
  49. package/dist/session-command.js +3 -1
  50. package/dist/session-name.js +25 -2
  51. package/dist/skill-bootstrap.js +2 -0
  52. package/dist/tool-defs.js +30 -0
  53. package/package.json +1 -1
package/dist/commands.js CHANGED
@@ -37,6 +37,7 @@ const protocol_1 = require("./protocol");
37
37
  const connection_qr_1 = require("./connection-qr");
38
38
  const config_1 = require("./config");
39
39
  const bay_mcp_1 = require("./bay-mcp");
40
+ const client_scope_1 = require("./client-scope");
40
41
  const windows_cmd_1 = require("./windows-cmd");
41
42
  const context_1 = require("./context");
42
43
  const runtime_install_1 = require("./runtime-install");
@@ -922,14 +923,21 @@ function completeDeviceLogin(device, options) {
922
923
  const names = bays.map((b) => (b.isDefault ? `${b.bayName} (default)` : b.bayName)).join(", ");
923
924
  console.log(` This computer can join: ${names}`);
924
925
  }
925
- refreshConnectedClients(device.baseUrl, device.token);
926
- if (options.registerClaude !== false)
927
- registerWithClaude(device.baseUrl, device.token);
928
- (0, bay_mcp_1.applyBayEntries)(device.baseUrl, options.previousBays ?? [], bays, {
929
- claude: options.registerClaude !== false,
930
- io: exports.realConfigIo,
931
- env: (0, client_paths_1.currentPathEnv)(),
932
- });
926
+ // A login on its own setup or server never rewrites the computer's global client configs.
927
+ const isolated = (0, client_scope_1.isolatedLoginReason)(device.baseUrl);
928
+ if (isolated) {
929
+ console.log((0, client_scope_1.isolatedLoginNotice)(isolated));
930
+ }
931
+ else {
932
+ refreshConnectedClients(device.baseUrl, device.token);
933
+ if (options.registerClaude !== false)
934
+ registerWithClaude(device.baseUrl, device.token);
935
+ (0, bay_mcp_1.applyBayEntries)(device.baseUrl, options.previousBays ?? [], bays, {
936
+ claude: options.registerClaude !== false,
937
+ io: exports.realConfigIo,
938
+ env: (0, client_paths_1.currentPathEnv)(),
939
+ });
940
+ }
933
941
  if (options.hint !== false) {
934
942
  console.log("\n Run /baychat <name> in any session.");
935
943
  if (bays.length > 1)
@@ -0,0 +1,205 @@
1
+ "use strict";
2
+ // `baychat connect <link>` — "Connect my agent" in one paste (docs/features/CONNECT_MY_AGENT.md).
3
+ //
4
+ // The person, signed in to BayChat, makes a one-time link and pastes ONE sentence holding it into
5
+ // their coding agent. The agent runs `npx -y baychat@latest connect <link>`, and this file does the
6
+ // rest without a question: spend the link for this computer's login (the same login the QR gives),
7
+ // run the ordinary `connect claude` / `connect codex` setup on top of it, join the Bay under the
8
+ // name the person chose, and say so in the Bay.
9
+ //
10
+ // THE LINK IS A SECRET until it is spent, so this never prints it: anything that names it shows the
11
+ // first four characters only (`maskConnectToken`). The token lives in the link's #fragment, which
12
+ // no server sees; it travels once, in a POST body, to the redeem endpoint.
13
+ Object.defineProperty(exports, "__esModule", { value: true });
14
+ exports.realConnectLinkDeps = exports.CONNECT_LINK_CLIENTS = void 0;
15
+ exports.looksLikeConnectLink = looksLikeConnectLink;
16
+ exports.maskConnectToken = maskConnectToken;
17
+ exports.parseConnectLink = parseConnectLink;
18
+ exports.describeRedeemError = describeRedeemError;
19
+ exports.helloLine = helloLine;
20
+ exports.cmdConnectLink = cmdConnectLink;
21
+ exports.joinAndGreetOverMcp = joinAndGreetOverMcp;
22
+ const index_js_1 = require("@modelcontextprotocol/sdk/client/index.js");
23
+ const streamableHttp_js_1 = require("@modelcontextprotocol/sdk/client/streamableHttp.js");
24
+ const api_1 = require("./api");
25
+ const config_1 = require("./config");
26
+ const device_login_1 = require("./device-login");
27
+ const machine_name_1 = require("./machine-name");
28
+ /** The runtimes a link can connect, by the name in its path (`/connect/<client>`). */
29
+ exports.CONNECT_LINK_CLIENTS = { "claude-code": "claude", codex: "codex" };
30
+ /** Whether a `connect` argument is a link rather than a runtime name. */
31
+ function looksLikeConnectLink(arg) {
32
+ return /^https?:\/\//i.test(arg.trim()) && /\/connect\//.test(arg);
33
+ }
34
+ /**
35
+ * Where a link may come from and send this computer. A link is pasted by a person into an agent, so
36
+ * a forged one must not be able to point the login, the MCP config and every later tool call at
37
+ * somebody else's server: production links are baychat.io's, and only a link made on THIS computer
38
+ * (loopback) may name another API (`api=`, for local testing).
39
+ */
40
+ const LINK_HOSTS = new Set(["baychat.io", "www.baychat.io"]);
41
+ const isLoopback = (host) => host === "localhost" || host === "127.0.0.1" || host === "[::1]";
42
+ /** "abcd…" — the most of a link token any output may show. */
43
+ function maskConnectToken(token) {
44
+ return `${token.slice(0, 4)}…`;
45
+ }
46
+ /**
47
+ * Read a connect link: `https://baychat.io/connect/<client>#t=<token>[&api=<url>]`. The error never
48
+ * repeats the link, since a mistyped link may still hold a valid token.
49
+ */
50
+ function parseConnectLink(raw) {
51
+ let url;
52
+ try {
53
+ url = new URL(raw.trim());
54
+ }
55
+ catch {
56
+ throw new Error("That is not a BayChat connect link. Copy the whole sentence from BayChat again.");
57
+ }
58
+ if (!LINK_HOSTS.has(url.hostname) && !isLoopback(url.hostname)) {
59
+ throw new Error("That link is not from BayChat (baychat.io). Make a connect link in BayChat and paste that one.");
60
+ }
61
+ const client = /^\/connect\/([a-z-]+)\/?$/.exec(url.pathname)?.[1];
62
+ const fragment = new URLSearchParams(url.hash.replace(/^#/, ""));
63
+ const token = fragment.get("t") ?? "";
64
+ if (!client || !token) {
65
+ throw new Error("That is not a BayChat connect link. Copy the whole sentence from BayChat again.");
66
+ }
67
+ if (!(client in exports.CONNECT_LINK_CLIENTS)) {
68
+ throw new Error(`This link is for "${client}", which this BayChat CLI cannot connect yet. Update it: npx -y baychat@latest connect <link>.`);
69
+ }
70
+ const api = fragment.get("api");
71
+ let apiBase = config_1.DEFAULT_API_URL;
72
+ if (api) {
73
+ let parsed;
74
+ try {
75
+ parsed = new URL(api);
76
+ }
77
+ catch {
78
+ throw new Error("The link's api= is not a URL.");
79
+ }
80
+ if (!["http:", "https:"].includes(parsed.protocol) || !isLoopback(parsed.hostname) || !isLoopback(url.hostname)) {
81
+ throw new Error("Only a link made for local testing (localhost) may name its own API.");
82
+ }
83
+ apiBase = parsed.origin;
84
+ }
85
+ return { client: client, token, apiBase };
86
+ }
87
+ /** What a refused redeem means, in words an agent can pass on to its person. */
88
+ function describeRedeemError(error) {
89
+ if (error instanceof api_1.ApiError) {
90
+ switch (error.code) {
91
+ case "CONNECT_LINK_INVALID":
92
+ return "This connect link has expired or was already used. Ask the person to make a new one in BayChat (Agents → Add agent); links last 30 minutes and work once.";
93
+ case "SESSION_LIMIT_REACHED":
94
+ case "SESSION_CAP_REACHED":
95
+ return `BayChat refused: ${error.message} The link is still good for 30 minutes after it was made: end a session or upgrade, then run the same command again.`;
96
+ case "AGENT_DISABLED":
97
+ return "An agent with this name was switched off in BayChat. Ask the person to switch it on (Settings → Agents) or make a link with another name.";
98
+ case "CONNECT_NAME_TAKEN":
99
+ return "Too many agents in this Bay already use that name. Ask the person to make a link with another name.";
100
+ }
101
+ if (error.status === 429)
102
+ return "Too many tries — wait ten minutes and run the same command again.";
103
+ return `BayChat refused the link (${error.status}): ${error.message}`;
104
+ }
105
+ return error instanceof Error ? error.message : String(error);
106
+ }
107
+ function helloLine(name, runtime, computer) {
108
+ const app = runtime === "claude" ? "Claude Code" : "Codex";
109
+ return `Connected ✅ I'm ${name}, ${app} on ${computer}. Restart ${app} (or start a session from the BayChat app) and I'll answer here.`;
110
+ }
111
+ /** `baychat connect <link>`. 0 = connected, 1 = refused (the reason is printed), 2 = setup failed. */
112
+ async function cmdConnectLink(raw, deps, opts = {}) {
113
+ let link;
114
+ try {
115
+ link = parseConnectLink(raw);
116
+ }
117
+ catch (e) {
118
+ deps.log(e instanceof Error ? e.message : String(e));
119
+ return 1;
120
+ }
121
+ const runtime = exports.CONNECT_LINK_CLIENTS[link.client];
122
+ const computer = (0, machine_name_1.machineName)();
123
+ deps.log(`Connecting this computer with link ${maskConnectToken(link.token)}`);
124
+ let redeemed;
125
+ try {
126
+ redeemed = await deps.redeem(link.apiBase, link.token, computer, opts.name?.trim() || undefined);
127
+ }
128
+ catch (e) {
129
+ deps.log(describeRedeemError(e));
130
+ return 1;
131
+ }
132
+ // The API this link was checked against, never one the answer names: a server cannot move this
133
+ // computer's login somewhere else.
134
+ const base = link.apiBase;
135
+ const { device } = deps.save(redeemed, base);
136
+ deps.log(`✓ This computer is signed in to BayChat as ${device.user.name}${redeemed.bay ? ` (Bay "${redeemed.bay.name}")` : ""}.`);
137
+ const asked = opts.name?.trim();
138
+ if (asked && !redeemed.selfNamed && redeemed.requestedName !== asked) {
139
+ // Either the person named it on the link (their name wins), or BayChat did not accept this one.
140
+ deps.log(` This agent is "${redeemed.agentName}" (BayChat's choice, not "${asked}").`);
141
+ }
142
+ else if (redeemed.agentName !== redeemed.requestedName) {
143
+ deps.log(` "${redeemed.requestedName}" is somebody else's agent in this Bay, so this one is "${redeemed.agentName}".`);
144
+ }
145
+ const code = await deps.setUp(runtime, base);
146
+ if (code !== 0) {
147
+ deps.log("The login is saved, but the setup did not finish. Run: npx -y baychat@latest doctor");
148
+ return 2;
149
+ }
150
+ // The group comes only from the server's record of the link, never from anything typed here.
151
+ const g = redeemed.group ?? null;
152
+ const group = g ? ("id" in g ? g.id : g.newTitle) : undefined;
153
+ const where = g ? ("id" in g ? (g.title ?? "the group") : g.newTitle) : null;
154
+ try {
155
+ await deps.joinAndGreet(base, device.token, redeemed.agentName, helloLine(redeemed.agentName, runtime, computer), group);
156
+ }
157
+ catch (e) {
158
+ deps.log(`Joined nothing yet: ${describeRedeemError(e)}`);
159
+ deps.log("The computer is connected. After a restart the agent can join with /baychat <name>.");
160
+ return 2;
161
+ }
162
+ deps.log(`✓ "${redeemed.agentName}" is in your Bay${where ? ` (group "${where}")` : ""} and said hello.`);
163
+ deps.log(` Tell the person: restart ${runtime === "claude" ? "Claude Code" : "Codex"} (or start a session from the BayChat app) so it can answer there.`);
164
+ return 0;
165
+ }
166
+ /** The real join + hello: the remote MCP's own `join_session` and `send_message`, as the device. */
167
+ async function joinAndGreetOverMcp(base, deviceToken, name, hello, group) {
168
+ const client = new index_js_1.Client({ name: "baychat-connect", version: "1" });
169
+ await client.connect(new streamableHttp_js_1.StreamableHTTPClientTransport(new URL("/api/mcp", base), {
170
+ requestInit: { headers: { Authorization: `Bearer ${deviceToken}` }, signal: AbortSignal.timeout(20_000) },
171
+ }));
172
+ try {
173
+ const joined = await client.callTool({ name: "join_session", arguments: { session: name, ...(group ? { group } : {}) } });
174
+ const text = joined.content.map((c) => c.text ?? "").join("\n");
175
+ if (joined.isError)
176
+ throw new Error(text || "BayChat refused the join.");
177
+ const structured = (joined.structuredContent ?? {});
178
+ // A group join names the group's conversation in its text; a private join also returns it
179
+ // as `directConversationId`. The group wins when there is one.
180
+ const conversationId = /conversation (\S+?)\./.exec(text)?.[1] ||
181
+ (typeof structured.directConversationId === "string" && structured.directConversationId) ||
182
+ undefined;
183
+ if (!conversationId)
184
+ throw new Error("BayChat joined the session but named no chat to greet in.");
185
+ const sent = await client.callTool({
186
+ name: "send_message",
187
+ arguments: { session: name, conversationId, content: hello },
188
+ });
189
+ if (sent.isError)
190
+ throw new Error("Joined, but the hello was refused.");
191
+ return conversationId;
192
+ }
193
+ finally {
194
+ // Closing a finished MCP session can only fail on a dropped connection; the work is done.
195
+ await client.close().catch((e) => console.error(`(closing the BayChat connection: ${e instanceof Error ? e.message : String(e)})`));
196
+ }
197
+ }
198
+ const realConnectLinkDeps = (setUp) => ({
199
+ redeem: api_1.redeemConnectLink,
200
+ save: device_login_1.saveApprovedDeviceLogin,
201
+ setUp,
202
+ joinAndGreet: joinAndGreetOverMcp,
203
+ log: (line) => console.log(line),
204
+ });
205
+ exports.realConnectLinkDeps = realConnectLinkDeps;
@@ -57,11 +57,11 @@ function parseConnectArgs(args) {
57
57
  throw new Error("Choose --start or --finish once.");
58
58
  result.mode = arg === "--start" ? "start" : "finish";
59
59
  }
60
- else if (arg === "--folder" || arg === "--max-mode" || arg === "--mode" || arg === "--session") {
60
+ else if (arg === "--folder" || arg === "--max-mode" || arg === "--mode" || arg === "--session" || arg === "--name") {
61
61
  const value = args[++index];
62
62
  if (!value?.trim() || value.startsWith("--"))
63
63
  throw new Error(`Supply ${arg} with a value.`);
64
- const key = arg === "--folder" ? "folder" : arg === "--max-mode" ? "maxMode" : arg === "--mode" ? "agentMode" : "session";
64
+ const key = arg === "--folder" ? "folder" : arg === "--max-mode" ? "maxMode" : arg === "--mode" ? "agentMode" : arg === "--name" ? "name" : "session";
65
65
  if (result[key])
66
66
  throw new Error(`Supply ${arg} once.`);
67
67
  result[key] = value;
package/dist/connect.js CHANGED
@@ -55,6 +55,7 @@ Object.defineProperty(exports, "cmdConnectClaude", { enumerable: true, get: func
55
55
  const connect_cursor_1 = require("./connect-cursor");
56
56
  const connect_dsh_1 = require("./connect-dsh");
57
57
  const connect_hermes_1 = require("./connect-hermes");
58
+ const connect_link_1 = require("./connect-link");
58
59
  /** Clients `connect` can configure. `claude` is an alias users reach for. */
59
60
  const CLIENT_ALIASES = {
60
61
  codex: "codex",
@@ -216,6 +217,13 @@ async function cmdConnect(clientArg, opts = {}) {
216
217
  console.log(renderConnectMenu());
217
218
  return 0;
218
219
  }
220
+ // A one-paste connect link (docs/features/CONNECT_MY_AGENT.md): it carries the login, the runtime
221
+ // and the name, so it takes no other option and asks nothing.
222
+ if ((0, connect_link_1.looksLikeConnectLink)(clientArg)) {
223
+ if (opts.mode)
224
+ throw new Error("A connect link does not take --start/--finish.");
225
+ return (0, connect_link_1.cmdConnectLink)(clientArg, (0, connect_link_1.realConnectLinkDeps)((runtime, base) => runtime === "claude" ? (0, connect_claude_1.cmdConnectClaude)({ base }) : cmdConnect("codex", { base })), opts.name ? { name: opts.name } : {});
226
+ }
219
227
  // Claude Code first: it is not an MCP-config client, so it must not reach
220
228
  // `parseConnectClient`, which would reject it.
221
229
  if (isClaudeConnectTarget(clientArg))
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.parseBayLogins = parseBayLogins;
37
37
  exports.startDeviceLogin = startDeviceLogin;
38
38
  exports.finishDeviceLogin = finishDeviceLogin;
39
+ exports.saveApprovedDeviceLogin = saveApprovedDeviceLogin;
39
40
  const fs = __importStar(require("node:fs"));
40
41
  const path = __importStar(require("node:path"));
41
42
  const node_crypto_1 = require("node:crypto");
@@ -156,6 +157,24 @@ async function finishDeviceLogin(baseUrl) {
156
157
  return { status: "approved", device: single, ...(bays ? { bays } : {}) };
157
158
  });
158
159
  }
160
+ /**
161
+ * Save a login that arrived already approved — a spent connect link (connect-link.ts) — exactly as a
162
+ * QR pickup is saved: the same shape check, the same per-Bay set, the same private files.
163
+ */
164
+ function saveApprovedDeviceLogin(approved, baseUrl) {
165
+ const parsed = deviceSchema.safeParse({
166
+ token: approved.token,
167
+ user: approved.user,
168
+ expiresAt: approved.expiresAt,
169
+ bays: parseBayLogins(approved.bays, approved.token),
170
+ baseUrl: normalizeBase(baseUrl),
171
+ });
172
+ if (!parsed.success)
173
+ throw new Error("BayChat returned an invalid login. Make a new connect link and try again.");
174
+ const { bays, ...single } = parsed.data;
175
+ (0, config_1.saveDeviceCredentials)(single, bays);
176
+ return { device: single, ...(bays ? { bays } : {}) };
177
+ }
159
178
  function pendingPath() {
160
179
  return path.join((0, config_1.configDir)(), "login-pending.json");
161
180
  }
@@ -48,9 +48,12 @@ exports.LAUNCH_USAGE = `Usage: baychat launch add <folder> [--label <label>] [--
48
48
  baychat launch remove <id> Stop offering one of them`;
49
49
  /** Printed by every `add`: the one thing a person must know before saying yes. */
50
50
  exports.APPROVALS_NOTICE = "Claude Code sessions started here from BayChat ask for approvals in BayChat.";
51
- /** Printed when Codex is offered: it has no BayChat approvals, so what it does is decided up front. */
52
- exports.CODEX_UNATTENDED_NOTICE = "Codex started here from BayChat does NOT ask for approvals: it runs --ask-for-approval never inside its workspace sandbox, with no network. " +
53
- "It can change files in this folder, and anyone who may wake it in the chat can ask it to. MCP servers in its own config run as usual.";
51
+ /** Printed when Codex is offered: what it may do without asking, and what it asks on the phone. */
52
+ exports.CODEX_UNATTENDED_NOTICE = "Codex started here from BayChat is run by the relay (Codex 0.160 or newer): it can change files in this folder without asking, with no network, " +
53
+ "and asks for approval in BayChat — on the session owner's phone — before anything more (a command outside its sandbox, network access, files elsewhere). " +
54
+ "A message from someone without command access to the session makes that turn read only. " +
55
+ "With BAYCHAT_CODEX_TRANSPORT=exec on the relay, Codex cannot answer in BayChat, so starting it from the phone is refused. " +
56
+ "MCP servers in its own config run as usual.";
54
57
  const realDeps = () => {
55
58
  const file = (0, claude_trust_1.claudeConfigPath)();
56
59
  return {
package/dist/main.js CHANGED
@@ -23,8 +23,10 @@ const server_1 = require("./channel/server");
23
23
  const launch_1 = require("./channel/launch");
24
24
  const mcp_config_1 = require("./mcp-config");
25
25
  const commands_2 = require("./relay/commands");
26
+ const codex_log_1 = require("./relay/codex-log");
26
27
  const bays_1 = require("./bays");
27
28
  const config_1 = require("./config");
29
+ const api_1 = require("./api");
28
30
  const help_topics_1 = require("./help-topics");
29
31
  const cli_help_1 = require("./cli-help");
30
32
  const args_1 = require("./args");
@@ -100,7 +102,9 @@ async function main() {
100
102
  const runtime = (0, args_1.flag)(args, "--runtime") ?? (0, owner_pid_1.detectRuntime)(profiles_1.RUNTIME_PROFILES);
101
103
  if (!runtime)
102
104
  throw new Error("Cannot detect this runtime. Pass --runtime, or choose a BayChat session name.");
103
- console.log(await (0, session_name_1.automaticSessionName)(runtime));
105
+ // Checked against this person's sessions, so a session that joined before keeps its name.
106
+ const device = (0, config_1.loadDeviceCredentials)();
107
+ console.log(await (0, session_name_1.automaticSessionName)(runtime, undefined, device ? () => (0, api_1.mySessionNames)(device.baseUrl, device.token) : undefined));
104
108
  return 0;
105
109
  }
106
110
  case "onboard":
@@ -293,8 +297,20 @@ async function main() {
293
297
  ownerPid: numberFlag(rest, "--owner-pid"),
294
298
  });
295
299
  }
300
+ case "log": {
301
+ // A relay-run Codex session's own log (relay/codex-log.ts): what it ran, changed and said.
302
+ (0, args_1.rejectUnknownFlags)(rest, ["--follow"], "baychat relay log <session> [-f]");
303
+ const session = rest.find((a) => !a.startsWith("-"));
304
+ if (!session)
305
+ throw new Error("Usage: baychat relay log <session> [-f]");
306
+ const follow = rest.includes("-f") || rest.includes("--follow");
307
+ const stop = new AbortController();
308
+ if (follow)
309
+ process.once("SIGINT", () => stop.abort());
310
+ return await (0, codex_log_1.showCodexLog)(session, { follow, signal: stop.signal });
311
+ }
296
312
  default:
297
- throw new Error("Usage: baychat relay <start|status|stop|attach> [options]");
313
+ throw new Error("Usage: baychat relay <start|status|stop|attach|log> [options]");
298
314
  }
299
315
  }
300
316
  case "hermes": {
package/dist/mcp-files.js CHANGED
@@ -403,6 +403,7 @@ exports.SEND_FILES_PARAM = {
403
403
  exports.FILE_TOOL_DEFS = [
404
404
  {
405
405
  name: "upload_file",
406
+ access: "write",
406
407
  title: "Upload a file to BayChat",
407
408
  description: "Upload ONE file from this machine to BayChat and get back an attachment id to send with " +
408
409
  "send_message (attachmentIds). Prefer send_message's files parameter when you just want to " +
@@ -419,6 +420,7 @@ exports.FILE_TOOL_DEFS = [
419
420
  },
420
421
  {
421
422
  name: "download_attachment",
423
+ access: "read",
422
424
  title: "Download an attachment",
423
425
  description: "Download an attachment from a BayChat message onto this machine and get back the local " +
424
426
  "path, so you can open it with your own file tools. Pass the signed attachmentUrl exactly " +
@@ -17,6 +17,7 @@
17
17
  // takes no arguments must omit the key entirely.
18
18
  Object.defineProperty(exports, "__esModule", { value: true });
19
19
  exports.registerToolDefs = registerToolDefs;
20
+ const tool_defs_1 = require("./tool-defs");
20
21
  /**
21
22
  * Register every def against its handler, failing loudly if one is missing.
22
23
  *
@@ -33,6 +34,7 @@ function registerToolDefs(server, defs, handlers) {
33
34
  description: def.description,
34
35
  ...(Object.keys(def.inputSchema).length ? { inputSchema: def.inputSchema } : {}),
35
36
  ...(def._meta ? { _meta: def._meta } : {}),
37
+ annotations: (0, tool_defs_1.toolAnnotations)(def.access),
36
38
  }, async (args) => handler(args));
37
39
  }
38
40
  }