openbuilder 0.1.0 → 0.1.1

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/README.md CHANGED
@@ -293,6 +293,36 @@ To set up OpenBuilder as an automated meeting bot (e.g. for OpenClaw agents):
293
293
  - Caption mode (`--captions`) is the most reliable on headless servers
294
294
  - Audio mode (`--audio`) requires PulseAudio + ffmpeg + OpenAI key + Xvfb (experimental on servers)
295
295
 
296
+ ## Running on Your Own Hardware
297
+
298
+ OpenBuilder works on any machine running OpenClaw — Mac Mini, laptop, Raspberry Pi, VPS, etc.
299
+
300
+ ### macOS (Mac Mini, MacBook)
301
+ - `npx openbuilder` installs Chromium automatically
302
+ - Audio capture mode is not available (no PulseAudio) — captions mode works great
303
+ - Auth: `npx openbuilder auth` opens a browser window — sign in and press Enter
304
+ - For headless operation: use `--auto` auth with `.env` credentials
305
+
306
+ ### Linux (Ubuntu, Debian, etc.)
307
+ - May need system deps: `npx playwright-core install-deps chromium`
308
+ - For headless servers: ensure Xvfb is running (`Xvfb :99 -screen 0 1280x720x24 &`)
309
+ - Audio mode available if PulseAudio + ffmpeg are installed
310
+
311
+ ### What you need
312
+ 1. **Node.js 18+**
313
+ 2. **OpenClaw** running on the machine
314
+ 3. **A Google account** for the bot (or join as guest)
315
+ 4. **An AI API key** (Claude or OpenAI) for meeting reports
316
+
317
+ ### Quick setup (any platform)
318
+ ```bash
319
+ npx openbuilder # Install + Chromium
320
+ npx openbuilder auth # Sign into Google (one-time)
321
+ npx openbuilder config set anthropicApiKey sk-ant-... # For AI reports
322
+ ```
323
+
324
+ Then tell your OpenClaw agent: "Join this meeting: \<url\>"
325
+
296
326
  ## OpenClaw Integration
297
327
 
298
328
  OpenBuilder ships as an OpenClaw skill. After running `npx openbuilder`, it's available to your OpenClaw agent. The agent can:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openbuilder",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Open-source AI meeting assistant — join Google Meet, capture transcripts, generate AI-powered meeting reports with summaries, action items, and speaker analytics",
5
5
  "type": "module",
6
6
  "bin": {
@@ -9,7 +9,9 @@
9
9
  * Saves session to ~/.openbuilder/auth.json via Playwright's storageState.
10
10
  */
11
11
 
12
+ import { execSync } from "node:child_process";
12
13
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
14
+ import { homedir } from "node:os";
13
15
  import { createInterface } from "node:readline";
14
16
  import { config as dotenvConfig } from "dotenv";
15
17
  import { join, dirname } from "node:path";
@@ -163,15 +165,22 @@ async function main() {
163
165
  "--window-size=1280,720",
164
166
  ];
165
167
 
166
- // Find full Chrome for headed mode
168
+ // Find full Chrome for headed mode (cross-platform)
167
169
  let executablePath: string | undefined;
168
- const fullChromePath = join(
169
- process.env.HOME || "~",
170
- ".cache/ms-playwright/chromium-1208/chrome-linux64/chrome"
171
- );
172
- if (existsSync(fullChromePath)) {
173
- executablePath = fullChromePath;
174
- }
170
+ try {
171
+ const playwrightCache = join(homedir(), ".cache", "ms-playwright");
172
+ if (existsSync(playwrightCache)) {
173
+ const result = execSync(
174
+ process.platform === "darwin"
175
+ ? `find "${playwrightCache}" -path "*/Chromium.app/Contents/MacOS/Chromium" -type f 2>/dev/null | head -1`
176
+ : `find "${playwrightCache}" -path "*/chrome-linux*/chrome" -type f 2>/dev/null | head -1`,
177
+ { encoding: "utf-8" },
178
+ ).trim();
179
+ if (result && existsSync(result)) {
180
+ executablePath = result;
181
+ }
182
+ }
183
+ } catch { /* fallback to default */ }
175
184
 
176
185
  const browser = await pw.chromium.launch({
177
186
  headless,
@@ -529,6 +529,32 @@ async function waitForMeetingEnd(
529
529
  return "Navigated away from meeting";
530
530
  }
531
531
 
532
+ // Detect if redirected to Meet homepage (meeting ended and Google kicked us out)
533
+ const currentUrl = page.url();
534
+ if (
535
+ currentUrl === "https://meet.google.com/" ||
536
+ currentUrl === "https://meet.google.com" ||
537
+ currentUrl.match(/^https:\/\/meet\.google\.com\/?\?/) ||
538
+ currentUrl === "https://meet.google.com/landing"
539
+ ) {
540
+ return "Meeting ended (redirected to homepage)";
541
+ }
542
+
543
+ // Check if the Leave call button is gone (meeting UI disappeared)
544
+ try {
545
+ const leaveBtn = page.locator('[aria-label*="Leave call" i]').first();
546
+ const hasMeetingUI = await leaveBtn.isVisible({ timeout: 500 });
547
+ if (!hasMeetingUI) {
548
+ // Double-check: no meeting controls means we're not in a meeting
549
+ const hasAnyControls = await page.locator('[aria-label*="microphone" i], [aria-label*="camera" i]').first().isVisible({ timeout: 500 }).catch(() => false);
550
+ if (!hasAnyControls) {
551
+ return "Meeting ended (meeting UI gone)";
552
+ }
553
+ }
554
+ } catch {
555
+ // Couldn't check — continue
556
+ }
557
+
532
558
  // Check if all other participants have left
533
559
  const participantCount = await getParticipantCount(page);
534
560
 
@@ -1333,11 +1359,16 @@ export async function joinMeeting(opts: {
1333
1359
  let fullChromePath: string | undefined;
1334
1360
  if (useFullChrome) {
1335
1361
  try {
1336
- const result = execSync(
1337
- 'find ~/.cache/ms-playwright/chromium-*/chrome-linux64 -name "chrome" -type f | head -1',
1338
- { encoding: "utf-8" },
1339
- ).trim();
1340
- fullChromePath = result || undefined;
1362
+ const playwrightCache = join(homedir(), ".cache", "ms-playwright");
1363
+ if (existsSync(playwrightCache)) {
1364
+ const result = execSync(
1365
+ process.platform === "darwin"
1366
+ ? `find "${playwrightCache}" -path "*/Chromium.app/Contents/MacOS/Chromium" -type f 2>/dev/null | head -1`
1367
+ : `find "${playwrightCache}" -path "*/chrome-linux*/chrome" -type f 2>/dev/null | head -1`,
1368
+ { encoding: "utf-8" },
1369
+ ).trim();
1370
+ fullChromePath = result || undefined;
1371
+ }
1341
1372
  } catch { /* fallback to default */ }
1342
1373
  }
1343
1374
  if (useFullChrome && fullChromePath) {
@@ -1368,8 +1399,12 @@ export async function joinMeeting(opts: {
1368
1399
  const fullChromePath = useFullChrome
1369
1400
  ? (() => {
1370
1401
  try {
1402
+ const playwrightCache = join(homedir(), ".cache", "ms-playwright");
1403
+ if (!existsSync(playwrightCache)) return undefined;
1371
1404
  const result = execSync(
1372
- 'find ~/.cache/ms-playwright/chromium-*/chrome-linux64 -name "chrome" -type f | head -1',
1405
+ process.platform === "darwin"
1406
+ ? `find "${playwrightCache}" -path "*/Chromium.app/Contents/MacOS/Chromium" -type f 2>/dev/null | head -1`
1407
+ : `find "${playwrightCache}" -path "*/chrome-linux*/chrome" -type f 2>/dev/null | head -1`,
1373
1408
  { encoding: "utf-8" },
1374
1409
  ).trim();
1375
1410
  return result || undefined;