demobite 1.7.5 → 1.7.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "demobite",
3
- "version": "1.7.5",
3
+ "version": "1.7.6",
4
4
  "description": "The DemoBites agentic recorder — you prompt, it films a real browser, and DemoBites turns the take into an editable demo bite.",
5
5
  "bin": {
6
6
  "demobite": "launcher/index.mjs"
package/skill/SKILL.md CHANGED
@@ -95,7 +95,7 @@ The camera browser uses a persistent profile at `.recorder/profile`. Signed in s
95
95
 
96
96
  When a page you need shows a login wall (login form, auth redirect, checkpoint page):
97
97
 
98
- 1. Open a HEADED Playwright window on that profile at the login page and tell the human: "Sign in in the window I opened. I will wait." Poll for a signed in signal (URL leaves the login path, or a session cookie appears), then close the window. The session now lives in the profile.
98
+ 1. Run `node scripts/signin.mjs <login url>` — THE ONE WAY to open that window (founder 2026-10-14). It launches the camera's own browser (the real Chrome, the recorder profile, the anti-automation flag), tells the human "Sign in in the window that opened. I will wait.", waits until the URL leaves the login page, and closes the window; the session now lives in the profile. NEVER launch a sign-in window of your own: a bare Playwright launch declares itself an automation browser (`navigator.webdriver`), and an app's bot check (Cloudflare Turnstile, reCAPTCHA…) refuses the sign-in — the take is lost there and the customer with it. If the script reports that the bot check refused even this window, say so plainly and stop; do not try other browsers.
99
99
  2. NEVER type, read, store, or ask for credentials. Not the password, not a 2FA code, nothing. The human signs in with their own hands.
100
100
  3. Do this only when a wall actually appears. Do not preemptively ask for logins.
101
101
 
@@ -0,0 +1,93 @@
1
+ #!/usr/bin/env node
2
+ // Phase 2: the target app's sign-in, by the human's own hands, in the
3
+ // recorder's own browser window (founder 2026-10-14: a sign-in window the
4
+ // agent improvised launched Chrome WITHOUT the camera's anti-automation flag,
5
+ // and the app's bot check — Cloudflare Turnstile on app.demobites.com —
6
+ // refused the sign-in: "Verification failed". Any customer whose app checks
7
+ // for bots at the login would lose the take there, and we would never see
8
+ // them again). This script is the ONE way to open that window: the same
9
+ // launch as record.mjs — the real Google Chrome binary, the persistent
10
+ // recorder profile, `--disable-blink-features=AutomationControlled` — so the
11
+ // page sees the genuine product, not a bare automation browser.
12
+ //
13
+ // node scripts/signin.mjs <login url> [--timeout <ms>] [--probe]
14
+ //
15
+ // It opens the window, tells the human to sign in, and waits until the URL
16
+ // leaves the login page (or a session cookie appears), then closes the
17
+ // window: the session now lives in `.recorder/profile` and the camera reuses
18
+ // it. Never types, reads, stores or asks for credentials. `--probe` only
19
+ // loads the page, reports whether the bot check passed, and exits — the
20
+ // recorder's own health check, no human needed.
21
+ import path from "node:path";
22
+ import { chromium } from "playwright";
23
+
24
+ const argv = process.argv.slice(2);
25
+ const url = argv.find((a) => !a.startsWith("--"));
26
+ if (!url) { console.error("Usage: node scripts/signin.mjs <login url> [--timeout <ms>] [--probe]"); process.exit(2); }
27
+ const probe = argv.includes("--probe");
28
+ const timeoutMs = Number(argv[argv.indexOf("--timeout") + 1]) || 10 * 60 * 1000;
29
+ const profileDir = process.env.RECORDER_PROFILE || path.resolve(process.cwd(), ".recorder", "profile");
30
+
31
+ // The camera's launch (record.mjs), minus the video.
32
+ const launchOpts = {
33
+ headless: false,
34
+ viewport: { width: 1440, height: 900 },
35
+ deviceScaleFactor: 1,
36
+ args: ["--force-color-profile=srgb", "--disable-blink-features=AutomationControlled"],
37
+ ignoreDefaultArgs: ["--enable-automation"],
38
+ };
39
+ let ctx;
40
+ try {
41
+ ctx = await chromium.launchPersistentContext(profileDir, { ...launchOpts, channel: "chrome" });
42
+ } catch (e) {
43
+ console.error(`Real Chrome failed to launch (${String(e.message).split("\n")[0]}); falling back to bundled Chromium.`);
44
+ ctx = await chromium.launchPersistentContext(profileDir, launchOpts);
45
+ }
46
+ const page = ctx.pages()[0] ?? (await ctx.newPage());
47
+ await page.goto(url, { waitUntil: "domcontentloaded" });
48
+ const startPath = new URL(page.url()).pathname;
49
+
50
+ const botCheck = async () => {
51
+ // Cloudflare Turnstile leaves a token in this input when it passes; the
52
+ // page shows "Verification failed" when it does not. Other walls: unknown.
53
+ try {
54
+ return await page.evaluate(() => {
55
+ const input = document.querySelector('input[name="cf-turnstile-response"]');
56
+ const text = document.body.innerText || "";
57
+ if (input && input.value) return "passed";
58
+ if (/verification failed/i.test(text)) return "failed";
59
+ return input ? "pending" : "none";
60
+ });
61
+ } catch { return "unknown"; }
62
+ };
63
+
64
+ if (probe) {
65
+ let state = "pending";
66
+ for (let i = 0; i < 20 && (state === "pending" || state === "unknown"); i++) { await page.waitForTimeout(1000); state = await botCheck(); }
67
+ const webdriver = await page.evaluate(() => navigator.webdriver).catch(() => "?");
68
+ console.log(`probe: bot check ${state} · navigator.webdriver=${webdriver} · ${page.url()}`);
69
+ await ctx.close();
70
+ process.exit(state === "failed" ? 1 : 0);
71
+ }
72
+
73
+ console.log("Sign in in the window that opened. I will wait — nothing is typed or read for you.");
74
+ const started = Date.now();
75
+ let lastCheck = "none";
76
+ while (Date.now() - started < timeoutMs) {
77
+ await page.waitForTimeout(1000);
78
+ let here;
79
+ try { here = new URL(page.url()); } catch { continue; }
80
+ if (here.pathname !== startPath && !/sign-?in|log-?in|auth|sso|oauth|callback/i.test(here.pathname)) {
81
+ console.log(`Signed in (now at ${here.pathname}). The session is saved in the recorder profile.`);
82
+ await ctx.close();
83
+ process.exit(0);
84
+ }
85
+ const state = await botCheck();
86
+ if (state === "failed" && lastCheck !== "failed") {
87
+ console.error("The page's bot check refused this window (\"Verification failed\"). Reload the page in this window once; if it fails again, this app cannot be signed into from an automated browser — tell the human plainly and stop.");
88
+ }
89
+ lastCheck = state;
90
+ }
91
+ console.error("No sign-in happened within the wait. Closing the window; the take did not start.");
92
+ await ctx.close();
93
+ process.exit(1);