openbuilder 0.1.0 → 0.1.2

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,14 +293,78 @@ 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
+ ## Platform Compatibility
297
+
298
+ OpenBuilder works anywhere OpenClaw runs, as long as Playwright Chromium is available.
299
+
300
+ | Platform | Status | Notes |
301
+ |----------|--------|-------|
302
+ | **macOS** (Mac Mini, MacBook — Intel & Apple Silicon) | ✅ Tested | Chromium installs automatically. Captions mode works great. No PulseAudio needed. |
303
+ | **Linux x64** (Ubuntu, Debian, VPS, EC2) | ✅ Tested | May need system deps: `npx playwright-core install-deps chromium`. Headless servers need Xvfb. |
304
+ | **Windows** (via WSL2) | ✅ Should work | Same as Linux x64. Not yet tested — feedback welcome. |
305
+ | **Docker / Podman** | ✅ Should work | Run `npx playwright-core install-deps chromium` in container. Not yet tested. |
306
+ | **Raspberry Pi** (ARM64, Pi 4/5) | ⚠️ Not tested | Playwright Chromium ARM builds can be unreliable. May need manual Chromium install. |
307
+ | **Raspberry Pi 3** (ARMv7) | ❌ Not supported | Playwright does not ship ARMv7 Chromium. |
308
+
309
+ ### macOS (Mac Mini, MacBook)
310
+
311
+ - `npx openbuilder` installs Chromium automatically
312
+ - Audio capture mode is not available (no PulseAudio) — captions mode is the default and works great
313
+ - Auth: `npx openbuilder auth` opens a browser window — sign in and press Enter
314
+ - For headless/unattended operation: use `--auto` auth with `.env` credentials
315
+
316
+ ### Linux (Ubuntu, Debian, VPS, EC2)
317
+
318
+ - Install system dependencies: `npx playwright-core install-deps chromium`
319
+ - For headless servers (no display): start Xvfb first — `Xvfb :99 -screen 0 1280x720x24 &` and `export DISPLAY=:99`
320
+ - Audio capture mode available if PulseAudio + ffmpeg are installed (optional — captions mode works without them)
321
+
322
+ ### VPS Providers
323
+
324
+ Works on any VPS that runs OpenClaw:
325
+
326
+ - **AWS** (EC2, Lightsail) — tested ✅
327
+ - **Oracle Cloud** (Always Free tier) — should work
328
+ - **Hetzner**, **Fly.io**, **GCP**, **Railway**, **Render** — should work (Linux x64)
329
+ - **DigitalOcean** — should work
330
+
331
+ Minimum: 1 vCPU, 1GB RAM (Chromium is the main resource consumer).
332
+
333
+ ### Requirements
334
+
335
+ 1. **Node.js 18+**
336
+ 2. **OpenClaw** running on the machine
337
+ 3. **A Google account** for the bot (or join as guest with `--anon`)
338
+ 4. **An AI API key** — Claude (Anthropic) or OpenAI — for meeting reports (optional but recommended)
339
+
340
+ ### Quick Setup (any platform)
341
+
342
+ ```bash
343
+ npx openbuilder # Install skill + Chromium
344
+ npx openbuilder auth # Sign into Google (one-time)
345
+ npx openbuilder config set anthropicApiKey sk-ant-... # For AI reports
346
+ ```
347
+
348
+ Then tell your OpenClaw agent: "Join this meeting: https://meet.google.com/..."
349
+
350
+ ### Known Limitations
351
+
352
+ - **Google Meet only** — Zoom and Teams support planned for a future release
353
+ - **Captions depend on Google Meet's CC feature** — if Meet changes their DOM structure, caption scraping may break
354
+ - **Audio capture mode is experimental** on headless servers — PulseAudio routing can be unreliable. Use `--captions` for reliability
355
+ - **Bot appears as a participant** — other meeting participants will see "Super Liang" (or your bot's Google account name) in the People panel
356
+ - **Google may block automated logins** — if Google flags the bot account, re-run `npx openbuilder auth` interactively or from a different IP
357
+ - **One meeting at a time** per bot instance
358
+
296
359
  ## OpenClaw Integration
297
360
 
298
361
  OpenBuilder ships as an OpenClaw skill. After running `npx openbuilder`, it's available to your OpenClaw agent. The agent can:
299
362
 
300
363
  - Join meetings on your behalf
301
364
  - Capture and summarize transcripts
302
- - Generate full meeting reports
303
- - Send screenshots to your chat
365
+ - Generate full meeting reports with action items and decisions
366
+ - Send screenshots to your chat on demand
367
+ - Tell you what's being discussed in real time
304
368
 
305
369
  See [SKILL.md](./SKILL.md) for the full agent integration guide.
306
370
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openbuilder",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
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;