baychat 0.29.9 → 0.31.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 (45) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/README.md +43 -10
  3. package/dist/approval-wait.js +143 -0
  4. package/dist/approve-hook.js +68 -133
  5. package/dist/auto-update/describe.js +66 -0
  6. package/dist/auto-update/notice.js +40 -0
  7. package/dist/auto-update/policy.js +110 -0
  8. package/dist/auto-update/relay-glue.js +143 -0
  9. package/dist/auto-update/store.js +140 -0
  10. package/dist/auto-update/updater.js +324 -0
  11. package/dist/channel/permission.js +6 -0
  12. package/dist/cli-help.js +30 -3
  13. package/dist/denied/after-hook.js +46 -0
  14. package/dist/denied/confirm.js +131 -0
  15. package/dist/denied/grants.js +292 -0
  16. package/dist/denied/hook-log.js +75 -0
  17. package/dist/denied/live-session.js +154 -0
  18. package/dist/denied/notice.js +29 -0
  19. package/dist/denied/opt-in.js +68 -0
  20. package/dist/denied/permission-rule.js +186 -0
  21. package/dist/denied/settings-rules.js +221 -0
  22. package/dist/denied/transcript.js +173 -0
  23. package/dist/denied-hook.js +431 -0
  24. package/dist/doctor-command.js +7 -1
  25. package/dist/doctor.js +28 -0
  26. package/dist/help-topics.js +12 -4
  27. package/dist/hook-stdin.js +35 -0
  28. package/dist/index.js +13 -1
  29. package/dist/main.js +20 -2
  30. package/dist/permissions-command.js +104 -0
  31. package/dist/protocol-content.js +1 -1
  32. package/dist/refresh-clients.js +36 -0
  33. package/dist/relay/acp/runner.js +4 -0
  34. package/dist/relay/commands.js +19 -3
  35. package/dist/relay/daemon.js +37 -1
  36. package/dist/relay/launcher/approval-settings.js +56 -2
  37. package/dist/relay/launcher/loop.js +4 -1
  38. package/dist/relay/self-restart.js +149 -0
  39. package/dist/relay/terminal-pane.js +74 -2
  40. package/dist/runtimes.js +2 -2
  41. package/dist/self-upgrade.js +92 -27
  42. package/dist/start-command.js +15 -1
  43. package/dist/update-command.js +51 -31
  44. package/dist/waiting-cards.js +107 -0
  45. package/package.json +4 -1
package/dist/main.js CHANGED
@@ -3,10 +3,13 @@
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  const commands_1 = require("./commands");
5
5
  const approve_hook_1 = require("./approve-hook");
6
+ const denied_hook_1 = require("./denied-hook");
7
+ const permissions_command_1 = require("./permissions-command");
6
8
  const context_hooks_command_1 = require("./context-hooks-command");
7
9
  const session_name_1 = require("./session-name");
8
10
  const start_command_1 = require("./start-command");
9
11
  const update_command_1 = require("./update-command");
12
+ const notice_1 = require("./auto-update/notice");
10
13
  const session_command_1 = require("./session-command");
11
14
  const doctor_command_1 = require("./doctor-command");
12
15
  const bays_command_1 = require("./bays-command");
@@ -54,8 +57,13 @@ async function main() {
54
57
  // else", and a usage dump printed at somebody who typed the product's own name is the
55
58
  // "something else". `--help` still prints the help, and `start` refuses with one sentence
56
59
  // when this machine is not signed in, so nothing is launched from an unusable state.
57
- if (command === undefined)
60
+ if (command === undefined) {
61
+ (0, notice_1.printUpdateNotice)(command);
58
62
  return (0, start_command_1.cmdStart)([]);
63
+ }
64
+ // "BayChat updated itself to …": the relay's one line for the terminal, shown once, on a command
65
+ // a person typed (never into a hook's or an MCP server's output).
66
+ (0, notice_1.printUpdateNotice)(command);
59
67
  // Asking for help never acts: `--help`/`-h` on any command prints its usage and runs nothing
60
68
  // (cli-help.ts says which commands' arguments are not ours to read — `claude`, `send`, …).
61
69
  const help = (0, cli_help_1.helpRequest)(command, args);
@@ -74,7 +82,10 @@ async function main() {
74
82
  return (0, start_command_1.cmdStart)(args);
75
83
  case "update":
76
84
  // `--quiet` is how an old version hands a `start`-driven update to the new one.
77
- return (0, update_command_1.cmdUpdate)({ quiet: args.includes("--quiet") });
85
+ (0, args_1.rejectUnknownFlags)(args, ["--quiet", "--auto"], "baychat update [--auto on|off]");
86
+ if (args.includes("--auto") && !(0, args_1.flag)(args, "--auto"))
87
+ throw new Error("Usage: baychat update --auto on|off");
88
+ return (0, update_command_1.cmdUpdate)({ quiet: args.includes("--quiet"), auto: (0, args_1.flag)(args, "--auto") });
78
89
  case "skill": {
79
90
  const runtime = args[1];
80
91
  if (args.length !== 2 ||
@@ -301,6 +312,13 @@ async function main() {
301
312
  // exit-1 hook with no JSON on stdout is a NON-BLOCKING error: the tool call proceeds. The
302
313
  // guard in the catch below covers that anyway.
303
314
  return await (0, approve_hook_1.cmdApproveHook)(args);
315
+ case "denied-hook":
316
+ // Never throws and always returns 0; prints `retry` or nothing (see denied-hook.ts). For
317
+ // this event Claude Code ignores the exit code, and printing nothing leaves the denial
318
+ // standing — so a crash here is already the safe outcome.
319
+ return await (0, denied_hook_1.cmdDeniedHook)(args);
320
+ case "permissions":
321
+ return await (0, permissions_command_1.cmdPermissions)(args);
304
322
  case "context-hooks":
305
323
  return await (0, context_hooks_command_1.cmdContextHooks)(args);
306
324
  case "doctor":
@@ -0,0 +1,104 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.cmdPermissions = cmdPermissions;
4
+ // `baychat permissions` — the denied-command card's switch, and the rules it left on this computer.
5
+ //
6
+ // baychat permissions [list] what BayChat keeps (and any once-rule still waiting)
7
+ // baychat permissions remove <id> take one back out of its settings file
8
+ // baychat permissions on | off the card itself, for sessions BayChat starts from now on
9
+ // baychat permissions sweep (internal) take back once-rules whose time is up
10
+ // baychat permissions confirm --grant <id>
11
+ // (internal) confirm an unconfirmed grant against the
12
+ // session's transcript, or take it back (denied/confirm.ts)
13
+ //
14
+ // Only rules BayChat wrote are listed or removed. A rule the person wrote themselves never
15
+ // appears here, because a rule that was already in the file was never recorded (denied/grants.ts).
16
+ const confirm_1 = require("./denied/confirm");
17
+ const grants_1 = require("./denied/grants");
18
+ const hook_log_1 = require("./denied/hook-log");
19
+ const opt_in_1 = require("./denied/opt-in");
20
+ /** The longest a scheduled sweep may sleep: a once-rule's TTL is minutes, never hours. */
21
+ const MAX_SWEEP_DELAY_MS = 60 * 60_000;
22
+ const USAGE = "Usage: baychat permissions [list | remove <id> | on | off]";
23
+ async function cmdPermissions(args, deps = {}) {
24
+ const out = deps.out ?? ((line) => console.log(line));
25
+ const err = deps.err ?? ((line) => console.error(line));
26
+ const [sub = "list", ...rest] = args;
27
+ switch (sub) {
28
+ case "list": {
29
+ out((0, opt_in_1.deniedCardEnabled)()
30
+ ? "The denied-command card is ON for sessions BayChat starts (`baychat permissions off` turns it off)."
31
+ : "The denied-command card is OFF (`baychat permissions on` turns it on).");
32
+ const grants = (0, grants_1.listGrants)();
33
+ if (grants.length === 0) {
34
+ out("BayChat keeps no permission rules on this computer.");
35
+ return 0;
36
+ }
37
+ out("");
38
+ for (const grant of grants)
39
+ out(describeGrant(grant));
40
+ return 0;
41
+ }
42
+ case "remove": {
43
+ const id = rest[0];
44
+ if (!id) {
45
+ err(USAGE);
46
+ return 1;
47
+ }
48
+ if (!(await (0, grants_1.revokeGrant)(id))) {
49
+ err(`No rule with id ${id}. \`baychat permissions list\` shows them.`);
50
+ return 1;
51
+ }
52
+ out(`Removed ${id}. Claude Code reloads its settings, so running sessions stop allowing it too.`);
53
+ return 0;
54
+ }
55
+ case "on": {
56
+ // A deliberate act by a person: the card lets a phone add rules on this computer, so a
57
+ // session's own shell (no terminal) cannot switch it on for itself.
58
+ if (!(deps.isInteractive ?? (() => Boolean(process.stdin.isTTY && process.stdout.isTTY)))()) {
59
+ err("`baychat permissions on` must be run by you, in a terminal.");
60
+ return 1;
61
+ }
62
+ (0, opt_in_1.enableDeniedCard)();
63
+ out("The denied-command card is ON. Sessions BayChat starts from now on will put a card in BayChat when");
64
+ out("Claude Code's auto mode blocks a call. Read docs/features/DENIED_COMMAND_CARD.md for what it can and can't allow.");
65
+ return 0;
66
+ }
67
+ case "off":
68
+ (0, opt_in_1.disableDeniedCard)();
69
+ out("The denied-command card is OFF for sessions BayChat starts from now on. Rules it kept stay until removed.");
70
+ return 0;
71
+ case "sweep": {
72
+ const now = deps.now ?? Date.now;
73
+ const at = Number(rest[rest.indexOf("--at") + 1]);
74
+ if (rest.includes("--at") && Number.isFinite(at)) {
75
+ const delay = Math.min(Math.max(at - now(), 0), MAX_SWEEP_DELAY_MS);
76
+ await (deps.sleep ?? ((ms) => new Promise((r) => setTimeout(r, ms))))(delay + 1000);
77
+ }
78
+ await (0, grants_1.sweepExpiredGrants)(now());
79
+ await (0, confirm_1.takeBackStaleUnconfirmed)({ log: hook_log_1.logBoth, ...deps.confirm });
80
+ return 0;
81
+ }
82
+ case "confirm": {
83
+ // Started detached by denied-hook (never by a person); not in USAGE on purpose.
84
+ const id = rest[rest.indexOf("--grant") + 1];
85
+ if (!rest.includes("--grant") || !id) {
86
+ err("Usage: baychat permissions confirm --grant <id>");
87
+ return 1;
88
+ }
89
+ await (0, confirm_1.confirmGrant)(id, { log: hook_log_1.logBoth, ...deps.confirm });
90
+ return 0;
91
+ }
92
+ default:
93
+ err(USAGE);
94
+ return 1;
95
+ }
96
+ }
97
+ function describeGrant(grant) {
98
+ const when = grant.kind === "once" ? `once, until ${grant.expiresAt ?? "?"}` : `always, since ${grant.addedAt}`;
99
+ // A once-rule can carry a secret from the command (it is never offered as "always" then), so it
100
+ // is listed as the card showed it, masked; an always-rule never holds one and is listed as written.
101
+ const rules = grant.kind === "once" ? grant.shown : grant.rules.length ? grant.rules.join(", ") : "(the rule was already there)";
102
+ const scope = grant.scope === "user" ? "user settings, every project" : "this project";
103
+ return `${grant.id} ${rules}\n ${when} · ${scope} · ${grant.settingsFile}${grant.session ? ` · ${grant.session}` : ""}`;
104
+ }