@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 +10 -0
- package/package.json +2 -1
- package/src/botbuddy-release-repair.json +1 -0
- package/src/commands.mjs +2 -1
- package/src/docker-hygiene.mjs +6 -0
- package/src/machine-id.mjs +70 -0
- package/src/pw/run.mjs +1 -1
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.
|
|
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
|
-
|
|
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
|
package/src/docker-hygiene.mjs
CHANGED
|
@@ -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:
|
|
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
|