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 +30 -0
- package/package.json +1 -1
- package/scripts/builder-auth.ts +17 -8
- package/scripts/builder-join.ts +41 -6
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.
|
|
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": {
|
package/scripts/builder-auth.ts
CHANGED
|
@@ -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
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
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,
|
package/scripts/builder-join.ts
CHANGED
|
@@ -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
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
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
|
-
|
|
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;
|