@botbuddy/cli 1.8.2 → 1.8.4

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/bin/pw.mjs ADDED
@@ -0,0 +1,10 @@
1
+ #!/usr/bin/env node
2
+ // BOT-1491: `pw` is the first-class short binary for the lock-gated Playwright
3
+ // lane driver. `bb-pw` remains a working alias (bin/bb-pw.mjs) and
4
+ // `botbuddy pw …` the subcommand — all three call the same runPw entrypoint.
5
+ import { runPw } from "../src/pw/run.mjs";
6
+
7
+ runPw(process.argv.slice(2)).then((code) => process.exit(code)).catch((error) => {
8
+ console.error(`pw: ${error?.message ?? error}`);
9
+ process.exit(1);
10
+ });
package/package.json CHANGED
@@ -1,10 +1,11 @@
1
1
  {
2
2
  "name": "@botbuddy/cli",
3
- "version": "1.8.2",
3
+ "version": "1.8.4",
4
4
  "description": "BotBuddy — Swarm coordination CLI for multi-agent workflows",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "botbuddy": "./bin/botbuddy.mjs",
8
+ "pw": "./bin/pw.mjs",
8
9
  "bb-pw": "./bin/bb-pw.mjs"
9
10
  },
10
11
  "files": [
@@ -0,0 +1 @@
1
+ {"schema_version":1,"source_version":"1.8.3","source_identity":"8a450fe651f42fc96f9fd713da5fe707ff48a914c174496337495adf0c425588"}
package/src/commands.mjs CHANGED
@@ -105,7 +105,8 @@ ${bold("AGENT WAITS")}
105
105
 
106
106
  ${bold("BROWSER LANES")}
107
107
  pw <lane> <verb> [args…] Drive a lock-gated Playwright lane
108
- pw --help Show bb-pw-compatible lane usage
108
+ (also the short ${cyan("pw")} binary, or ${cyan("bb-pw")} alias)
109
+ pw --help Show lane usage
109
110
 
110
111
  ${bold("OTHER")}
111
112
  locks -m [--host name] Reserve typed local resources, including Playwright MCP lanes
@@ -8,6 +8,7 @@ import { readFileSync } from "node:fs";
8
8
  import { hostname } from "node:os";
9
9
  import { callToolJson } from "./api.mjs";
10
10
  import { acquireStackLock, lockPathForProject, projectIdFromConfig } from "./stack-file-lock.mjs";
11
+ import { machineUuid } from "./machine-id.mjs";
11
12
 
12
13
  export const SCHEMA_VERSION = 1;
13
14
  export const DEFAULT_PROJECTED_ENDPOINTS = 10;
@@ -1118,6 +1119,7 @@ async function runDockerCommandWithBotBuddyLock(argv, {
1118
1119
  runWorkflow = runDockerWorkflow,
1119
1120
  workflowOptions = {},
1120
1121
  machineHost = hostname(),
1122
+ machineId = machineUuid(),
1121
1123
  monotonicNow = Date.now,
1122
1124
  } = {}) {
1123
1125
  const parsed = parseDockerArgs(argv);
@@ -1131,6 +1133,10 @@ async function runDockerCommandWithBotBuddyLock(argv, {
1131
1133
  host: machineHost,
1132
1134
  slot: parsed.opts.lockSlot,
1133
1135
  mode: "lock",
1136
+ // BOT-1239: report THIS machine's unique id so the supabase_local stack lock states
1137
+ // which physical machine holds it (host is a hostname two machines can share). Only
1138
+ // sent when the probe found one — a NULL degrades to hostname grouping.
1139
+ ...(machineId ? { machine_uuid: machineId } : {}),
1134
1140
  ticket_id: parsed.opts.ticket,
1135
1141
  no_pr_reason: "local OrbStack hygiene",
1136
1142
  });
@@ -0,0 +1,70 @@
1
+ // BOT-1239 — the machine-UNIQUE identity of THIS physical machine.
2
+ //
3
+ // A hostname is not machine-unique (two machines can both be "ubuntu"), so a
4
+ // supabase_local lock keyed only on hostname cannot be attributed to the machine
5
+ // that holds it. This reads the same machine-unique token a BotBuddy Helper attests
6
+ // at enrollment (container_hosts.hardware_uuid) so a lock the CLI acquires reports the
7
+ // SAME identity — letting /docker merge a machine's Helper with its own stack lock and
8
+ // never with a foreign machine's under a shared hostname.
9
+ //
10
+ // * macOS : IOPlatformUUID from `ioreg -rd1 -c IOPlatformExpertDevice` (what the
11
+ // Helper attests, so the two identities match for one machine).
12
+ // * Linux : /etc/machine-id (falling back to /var/lib/dbus/machine-id).
13
+ // * else / on any error : null — the lock then degrades to hostname grouping, never
14
+ // a wrong merge. This must NEVER throw or block a lock acquisition.
15
+ //
16
+ // `BOTBUDDY_MACHINE_UUID` overrides the probe (explicit operator control / tests).
17
+ import { spawnSync } from "node:child_process";
18
+ import { readFileSync } from "node:fs";
19
+
20
+ const IOREG_UUID_RE = /"IOPlatformUUID"\s*=\s*"([^"]+)"/;
21
+
22
+ export function readMachineUuid({
23
+ platform = process.platform,
24
+ env = process.env,
25
+ exec = (cmd, args) => spawnSync(cmd, args, { encoding: "utf8", timeout: 4000 }),
26
+ readFile = (p) => readFileSync(p, "utf8"),
27
+ } = {}) {
28
+ try {
29
+ const override = (env.BOTBUDDY_MACHINE_UUID || "").trim();
30
+ if (override) return override;
31
+
32
+ if (platform === "darwin") {
33
+ const out = exec("ioreg", ["-rd1", "-c", "IOPlatformExpertDevice"]);
34
+ if (out && out.status === 0 && typeof out.stdout === "string") {
35
+ const m = out.stdout.match(IOREG_UUID_RE);
36
+ const uuid = m && m[1] ? m[1].trim() : "";
37
+ return uuid || null;
38
+ }
39
+ return null;
40
+ }
41
+
42
+ if (platform === "linux") {
43
+ for (const path of ["/etc/machine-id", "/var/lib/dbus/machine-id"]) {
44
+ try {
45
+ const id = String(readFile(path) || "").trim();
46
+ if (id) return id;
47
+ } catch { /* try the next source */ }
48
+ }
49
+ return null;
50
+ }
51
+
52
+ return null;
53
+ } catch {
54
+ // A machine id is a best-effort disambiguator; never let it fail an acquire.
55
+ return null;
56
+ }
57
+ }
58
+
59
+ let _cached;
60
+ let _cachedComputed = false;
61
+
62
+ // Cached, zero-argument accessor for the real process. Tests call readMachineUuid
63
+ // directly with injected dependencies instead of hitting this cache.
64
+ export function machineUuid() {
65
+ if (!_cachedComputed) {
66
+ _cached = readMachineUuid();
67
+ _cachedComputed = true;
68
+ }
69
+ return _cached;
70
+ }
package/src/pw/run.mjs CHANGED
@@ -11,7 +11,7 @@ import { loadConfig, getConfig } from "../config.mjs";
11
11
  // server-side, so the lane name bb-pw builds/matches/prints is the one the lock
12
12
  // kernel actually stored ("jonos-mbp:8", not "Jonos-MBP.localdomain:8").
13
13
  const hostFor = (env) => canonicalizeHostString(env.PLAYWRIGHT_MCP_HOST || env.HOSTNAME || os.hostname());
14
- function help(out) { out.write("Usage: botbuddy pw [--profile <name>] [--session-id <id>] <lane> <verb> [args…]\n\nAlias: bb-pw <lane> <verb> [args…]\n\n--session-id <id> accept a lane held by this arming-session agent id (from\n register_agent); defaults to $BOTBUDDY_SESSION_ID, then the\n id saved by `botbuddy register`.\n"); }
14
+ function help(out) { out.write("Usage: pw [--profile <name>] [--session-id <id>] <lane> <verb> [args…]\n\nAliases: bb-pw <lane> <verb> [args…] · botbuddy pw <lane> <verb> [args…]\n (all three drive the same lock-gated Playwright lane)\n\n--session-id <id> accept a lane held by this arming-session agent id (from\n register_agent); defaults to $BOTBUDDY_SESSION_ID, then the\n id saved by `botbuddy register`.\n"); }
15
15
  function redact(value, secretValues = []) { return secretValues.reduce((text, secret) => secret ? text.split(secret).join("[redacted]") : text, String(value ?? "")); }
16
16
  // BOT-1488: the register_agent identity for this machine, persisted by
17
17
  // `botbuddy register` into ~/.botbuddy/config.json. This is the SESSION agent