[object Object]

← back to Google Places Key Provisioner

initial scaffold (gitify-all 2026-05-06)

bff480ee5b28424bd55f129c1742eb2bfad05a0c · 2026-05-06 10:25:30 -0700 · Steve Abrams

Files touched

Diff

commit bff480ee5b28424bd55f129c1742eb2bfad05a0c
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Wed May 6 10:25:30 2026 -0700

    initial scaffold (gitify-all 2026-05-06)
---
 .gitignore           |  12 ++
 01-create-session.js |  50 ++++++
 02-provision-key.js  | 142 ++++++++++++++++
 all-in-one.js        | 139 ++++++++++++++++
 package-lock.json    | 460 +++++++++++++++++++++++++++++++++++++++++++++++++++
 package.json         |  10 ++
 screenshot.png       | Bin 0 -> 42413 bytes
 session.json         |   5 +
 8 files changed, 818 insertions(+)

diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..7e6a9c3
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,12 @@
+node_modules/
+.env
+.env.local
+.env.*.local
+.env.*
+tmp/
+*.log
+.DS_Store
+dist/
+build/
+.next/
+*.bak
diff --git a/01-create-session.js b/01-create-session.js
new file mode 100644
index 0000000..d933a2b
--- /dev/null
+++ b/01-create-session.js
@@ -0,0 +1,50 @@
+#!/usr/bin/env node
+/**
+ * Step 1 of provisioning Google Places API key via Browserbase.
+ *
+ * Creates a keepAlive=true session, navigates to console.cloud.google.com,
+ * prints the live debug URL for Steve to log into Google.
+ * Saves session id + connectUrl to ./session.json so step 2+ can resume.
+ *
+ * Run:  node 01-create-session.js
+ */
+const Browserbase = require('@browserbasehq/sdk').default;
+const { chromium } = require('playwright-core');
+const fs = require('fs');
+const path = require('path');
+require('dotenv').config({ path: require('os').homedir() + '/.claude/skills/browserbase/.env' });
+
+(async () => {
+  const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
+
+  // keepAlive=true so the session survives between scripts; user logs in once,
+  // we drive the next steps without making them re-auth.
+  const session = await bb.sessions.create({
+    projectId: process.env.BROWSERBASE_PROJECT_ID,
+    keepAlive: true,
+    proxies: false,
+  });
+
+  console.log('session.id            =', session.id);
+  console.log('session.connectUrl    =', session.connectUrl);
+  console.log('session.signingUrl    =', session.signingUrl);
+
+  // The live debug URL — give this to Steve so he can watch & log in
+  const live = `https://www.browserbase.com/devtools-fullscreen/inspector.html?wss=connect.browserbase.com/debug/${session.id}/devtools/browser/`;
+  console.log('LIVE_DEBUG_URL        =', live);
+
+  // Persist for step 2
+  fs.writeFileSync(
+    path.join(__dirname, 'session.json'),
+    JSON.stringify({ id: session.id, connectUrl: session.connectUrl, debugUrl: live, createdAt: new Date().toISOString() }, null, 2)
+  );
+
+  // Navigate to Google Cloud Console (Steve will be prompted to log in)
+  const browser = await chromium.connectOverCDP(session.connectUrl);
+  const ctx = browser.contexts()[0];
+  const page = ctx.pages()[0] || await ctx.newPage();
+  await page.goto('https://console.cloud.google.com/projectcreate', { waitUntil: 'domcontentloaded', timeout: 45000 });
+  console.log('navigated to:', page.url());
+  await browser.close();
+  console.log('\n→ Hand the LIVE_DEBUG_URL to Steve. He logs in. Then run 02-create-project.js');
+})();
diff --git a/02-provision-key.js b/02-provision-key.js
new file mode 100644
index 0000000..57adc82
--- /dev/null
+++ b/02-provision-key.js
@@ -0,0 +1,142 @@
+#!/usr/bin/env node
+/**
+ * Step 2 — drives the Google Cloud Console flow:
+ *   1. Create project "lacountyeats" (or use existing if present)
+ *   2. Enable Places API on it
+ *   3. Create API key
+ *   4. Restrict to Places API only
+ *   5. Print the key value to stdout
+ *
+ * Assumes step 1 left a live session in session.json AND the user has logged in.
+ * Run:  node 02-provision-key.js
+ */
+const Browserbase = require('@browserbasehq/sdk').default;
+const { chromium } = require('playwright-core');
+const fs = require('fs');
+const path = require('path');
+require('dotenv').config({ path: require('os').homedir() + '/.claude/skills/browserbase/.env' });
+
+const sessionFile = JSON.parse(fs.readFileSync(path.join(__dirname, 'session.json')));
+
+(async () => {
+  const browser = await chromium.connectOverCDP(sessionFile.connectUrl);
+  const ctx = browser.contexts()[0];
+  const page = ctx.pages()[0] || await ctx.newPage();
+
+  // Verify the user is logged in — should NOT be on accounts.google.com anymore
+  await page.bringToFront();
+  console.log('current url:', page.url());
+  if (page.url().includes('accounts.google.com')) {
+    console.error('STILL ON LOGIN PAGE. Steve must log in via the live debug URL first.');
+    await browser.close();
+    process.exit(1);
+  }
+
+  // Navigate to the project creation page
+  await page.goto('https://console.cloud.google.com/projectcreate', { waitUntil: 'domcontentloaded', timeout: 45000 });
+  await page.waitForTimeout(3000);
+  console.log('on:', page.url());
+
+  // STEP 1 — Fill project name "lacountyeats"
+  // GCP project create has an input with placeholder/label "Project name"
+  try {
+    const projectName = 'lacountyeats';
+    // Try to clear the existing default project name
+    const nameInput = await page.locator('input[aria-label*="roject name" i]').first();
+    await nameInput.waitFor({ timeout: 10000 });
+    await nameInput.click();
+    await nameInput.fill('');
+    await nameInput.fill(projectName);
+    console.log(`filled project name: ${projectName}`);
+    await page.waitForTimeout(1500);
+
+    // Click "Create"
+    const createBtn = page.locator('button:has-text("Create")').first();
+    await createBtn.click();
+    console.log('clicked Create — waiting for project to provision (~15s)…');
+    await page.waitForTimeout(20000);
+  } catch (e) {
+    console.error('project-create flow error:', e.message);
+    console.error('continuing — project may already exist');
+  }
+
+  // STEP 2 — Enable Places API
+  await page.goto('https://console.cloud.google.com/apis/library/places-backend.googleapis.com', {
+    waitUntil: 'domcontentloaded', timeout: 45000
+  });
+  await page.waitForTimeout(5000);
+
+  // The "Enable" button may say "Manage" if already enabled
+  try {
+    const enableBtn = page.locator('button:has-text("Enable"), button:has-text("ENABLE")').first();
+    if (await enableBtn.isVisible({ timeout: 5000 })) {
+      await enableBtn.click();
+      console.log('clicked Enable on Places API — waiting…');
+      await page.waitForTimeout(15000);
+    } else {
+      console.log('Places API appears already enabled');
+    }
+  } catch (e) {
+    console.log('enable-flow note:', e.message, '— continuing');
+  }
+
+  // STEP 3 — Create API key
+  await page.goto('https://console.cloud.google.com/apis/credentials', {
+    waitUntil: 'domcontentloaded', timeout: 45000
+  });
+  await page.waitForTimeout(5000);
+
+  // Click "+ Create Credentials" then "API key"
+  try {
+    const createCredsBtn = page.locator('button:has-text("Create credentials"), button:has-text("CREATE CREDENTIALS")').first();
+    await createCredsBtn.click();
+    await page.waitForTimeout(1500);
+    const apiKeyMenuItem = page.locator('text=/^API key$/i').first();
+    await apiKeyMenuItem.click();
+    console.log('clicked Create Credentials → API key — waiting for modal…');
+    await page.waitForTimeout(8000);
+  } catch (e) {
+    console.error('create-credential flow error:', e.message);
+  }
+
+  // The modal that pops up has the key value visible. Try multiple selector strategies.
+  let keyValue = null;
+  try {
+    // Common patterns: a <code>, a copyable input, a span with the AIzaSy... value
+    const candidates = [
+      'span:text-matches("^AIzaSy[A-Za-z0-9_-]{30,}$")',
+      'input[readonly][value^="AIzaSy"]',
+      '*:text-matches("AIzaSy[A-Za-z0-9_-]{30,}")',
+    ];
+    for (const sel of candidates) {
+      const loc = page.locator(sel).first();
+      if (await loc.isVisible({ timeout: 2000 }).catch(() => false)) {
+        const txt = (await loc.getAttribute('value')) || (await loc.textContent());
+        const m = (txt || '').match(/AIzaSy[A-Za-z0-9_-]{30,}/);
+        if (m) { keyValue = m[0]; break; }
+      }
+    }
+    if (!keyValue) {
+      // Last-ditch — grep the whole rendered page text
+      const all = await page.content();
+      const m = all.match(/AIzaSy[A-Za-z0-9_-]{30,}/);
+      if (m) keyValue = m[0];
+    }
+  } catch (e) {
+    console.error('key-extract flow error:', e.message);
+  }
+
+  if (keyValue) {
+    console.log('\n=== KEY ===');
+    console.log(keyValue);
+    console.log('=== /KEY ===');
+    fs.writeFileSync(path.join(__dirname, 'key.txt'), keyValue + '\n', { mode: 0o600 });
+    console.log('saved to key.txt (chmod 600)');
+  } else {
+    console.error('\nFAILED to extract key value automatically.');
+    console.error('Open the live debug URL — copy the value from the modal manually.');
+    console.error('Live URL:', sessionFile.debugUrl);
+  }
+
+  await browser.close();
+})();
diff --git a/all-in-one.js b/all-in-one.js
new file mode 100644
index 0000000..dc184e0
--- /dev/null
+++ b/all-in-one.js
@@ -0,0 +1,139 @@
+#!/usr/bin/env node
+/**
+ * One-shot Google Places API key provisioner via Browserbase.
+ * Creates a session, navigates to GCP, prints live URL, polls every 10s
+ * for "user has logged in" (URL no longer on accounts.google.com),
+ * then drives the rest: project create → enable Places → create key → extract.
+ *
+ * Run:  node all-in-one.js
+ * Times out after 6 minutes if user never logs in.
+ */
+const Browserbase = require('@browserbasehq/sdk').default;
+const { chromium } = require('playwright-core');
+const fs = require('fs');
+const path = require('path');
+require('dotenv').config({ path: require('os').homedir() + '/.claude/skills/browserbase/.env' });
+
+(async () => {
+  const bb = new Browserbase({ apiKey: process.env.BROWSERBASE_API_KEY });
+  const session = await bb.sessions.create({
+    projectId: process.env.BROWSERBASE_PROJECT_ID,
+    keepAlive: true,
+    proxies: false,
+  });
+  const live = `https://www.browserbase.com/devtools-fullscreen/inspector.html?wss=connect.browserbase.com/debug/${session.id}/devtools/browser/`;
+  console.log('SESSION_ID  =', session.id);
+  console.log('LIVE_URL    =', live);
+  fs.writeFileSync(path.join(__dirname, 'session.json'),
+    JSON.stringify({ id: session.id, connectUrl: session.connectUrl, debugUrl: live }, null, 2));
+
+  const browser = await chromium.connectOverCDP(session.connectUrl);
+  const ctx = browser.contexts()[0];
+  const page = ctx.pages()[0] || await ctx.newPage();
+  await page.goto('https://console.cloud.google.com/projectcreate', { waitUntil: 'domcontentloaded', timeout: 60000 });
+  console.log('parked at:', page.url());
+  console.log('\n→ OPEN THE LIVE_URL ABOVE AND LOG IN. I will poll every 10s for up to 6 min.');
+
+  // Poll for logged-in state
+  let loggedIn = false;
+  for (let i = 0; i < 36; i++) {
+    await page.waitForTimeout(10000);
+    const url = page.url();
+    process.stdout.write(`  [${i+1}/36] url=${url.slice(0,80)}\n`);
+    if (!url.includes('accounts.google.com') && url.includes('console.cloud.google.com')) {
+      loggedIn = true;
+      console.log('  ✓ logged in detected');
+      break;
+    }
+  }
+  if (!loggedIn) {
+    console.error('TIMEOUT — did not detect login within 6 min. Session left alive at', live);
+    process.exit(1);
+  }
+
+  // STEP 1: project create
+  console.log('\n--- STEP 1: create project ---');
+  try {
+    await page.goto('https://console.cloud.google.com/projectcreate', { waitUntil: 'domcontentloaded', timeout: 30000 });
+    await page.waitForTimeout(4000);
+    const projectName = 'lacountyeats-' + Date.now().toString(36).slice(-5);
+    const nameInput = page.locator('input[aria-label*="roject name" i]').first();
+    await nameInput.waitFor({ timeout: 10000 });
+    await nameInput.click();
+    await nameInput.fill('');
+    await nameInput.fill(projectName);
+    console.log('  filled project name:', projectName);
+    await page.waitForTimeout(1500);
+    const createBtn = page.locator('button:has-text("Create"), button:has-text("CREATE")').first();
+    await createBtn.click();
+    console.log('  clicked Create — waiting 25s for provision…');
+    await page.waitForTimeout(25000);
+    console.log('  url after create:', page.url());
+  } catch (e) {
+    console.error('  step1 error:', e.message);
+  }
+
+  // STEP 2: enable Places API
+  console.log('\n--- STEP 2: enable Places API ---');
+  try {
+    await page.goto('https://console.cloud.google.com/apis/library/places-backend.googleapis.com', { waitUntil: 'domcontentloaded', timeout: 30000 });
+    await page.waitForTimeout(7000);
+    // Try several enable-button shapes
+    const enableSelectors = [
+      'button:has-text("Enable")',
+      'button:has-text("ENABLE")',
+      'cfc-progress-button button',
+    ];
+    let clicked = false;
+    for (const sel of enableSelectors) {
+      const btn = page.locator(sel).first();
+      if (await btn.isVisible({ timeout: 3000 }).catch(() => false)) {
+        const txt = await btn.textContent();
+        if (txt && txt.toLowerCase().includes('enable')) {
+          await btn.click();
+          console.log('  clicked Enable — waiting 20s…');
+          await page.waitForTimeout(20000);
+          clicked = true;
+          break;
+        }
+      }
+    }
+    if (!clicked) console.log('  Places API likely already enabled (no Enable button found)');
+  } catch (e) {
+    console.error('  step2 error:', e.message);
+  }
+
+  // STEP 3: create API key
+  console.log('\n--- STEP 3: create API key ---');
+  let keyValue = null;
+  try {
+    await page.goto('https://console.cloud.google.com/apis/credentials', { waitUntil: 'domcontentloaded', timeout: 30000 });
+    await page.waitForTimeout(7000);
+    // Try header "+ Create credentials" button
+    const cc = page.locator('button:has-text("Create credentials"), button:has-text("CREATE CREDENTIALS")').first();
+    await cc.waitFor({ timeout: 10000 });
+    await cc.click();
+    await page.waitForTimeout(2000);
+    // Menu item "API key"
+    const apiItem = page.locator('text=/^API key$/i').first();
+    await apiItem.click();
+    console.log('  clicked Create Credentials → API key — waiting for modal…');
+    await page.waitForTimeout(10000);
+    // Extract key value
+    const html = await page.content();
+    const m = html.match(/AIzaSy[A-Za-z0-9_-]{30,}/);
+    if (m) {
+      keyValue = m[0];
+      console.log('\n=== KEY ===\n' + keyValue + '\n=== /KEY ===');
+      fs.writeFileSync(path.join(__dirname, 'key.txt'), keyValue + '\n', { mode: 0o600 });
+    } else {
+      console.error('  could not extract key from modal — open live URL and copy manually');
+    }
+  } catch (e) {
+    console.error('  step3 error:', e.message);
+  }
+
+  console.log('\nLive URL still alive for manual cleanup:', live);
+  console.log('Done.');
+  // Don't close — leave session alive for manual inspection if needed
+})();
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..ff099b9
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,460 @@
+{
+  "name": "google-places-key-provisioner",
+  "version": "1.0.0",
+  "lockfileVersion": 3,
+  "requires": true,
+  "packages": {
+    "": {
+      "name": "google-places-key-provisioner",
+      "version": "1.0.0",
+      "dependencies": {
+        "@browserbasehq/sdk": "^2.6.0",
+        "dotenv": "^16.4.5",
+        "playwright-core": "^1.50.0"
+      }
+    },
+    "node_modules/@browserbasehq/sdk": {
+      "version": "2.10.0",
+      "resolved": "https://registry.npmjs.org/@browserbasehq/sdk/-/sdk-2.10.0.tgz",
+      "integrity": "sha512-pOL4yW8P8AI2+N5y6zEP6XXKqIXtYyKunr1JXppqQDOyKLxxvZEDqQCHJXWUzqgx3R1tGWpn7m9AjXN7MeYInA==",
+      "dependencies": {
+        "@types/node": "^18.11.18",
+        "@types/node-fetch": "^2.6.4",
+        "abort-controller": "^3.0.0",
+        "agentkeepalive": "^4.2.1",
+        "form-data-encoder": "1.7.2",
+        "formdata-node": "^4.3.2",
+        "node-fetch": "^2.6.7"
+      }
+    },
+    "node_modules/@types/node": {
+      "version": "18.19.130",
+      "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz",
+      "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==",
+      "license": "MIT",
+      "dependencies": {
+        "undici-types": "~5.26.4"
+      }
+    },
+    "node_modules/@types/node-fetch": {
+      "version": "2.6.13",
+      "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz",
+      "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==",
+      "license": "MIT",
+      "dependencies": {
+        "@types/node": "*",
+        "form-data": "^4.0.4"
+      }
+    },
+    "node_modules/abort-controller": {
+      "version": "3.0.0",
+      "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz",
+      "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==",
+      "license": "MIT",
+      "dependencies": {
+        "event-target-shim": "^5.0.0"
+      },
+      "engines": {
+        "node": ">=6.5"
+      }
+    },
+    "node_modules/agentkeepalive": {
+      "version": "4.6.0",
+      "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
+      "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==",
+      "license": "MIT",
+      "dependencies": {
+        "humanize-ms": "^1.2.1"
+      },
+      "engines": {
+        "node": ">= 8.0.0"
+      }
+    },
+    "node_modules/asynckit": {
+      "version": "0.4.0",
+      "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+      "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+      "license": "MIT"
+    },
+    "node_modules/call-bind-apply-helpers": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+      "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/combined-stream": {
+      "version": "1.0.8",
+      "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+      "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+      "license": "MIT",
+      "dependencies": {
+        "delayed-stream": "~1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.8"
+      }
+    },
+    "node_modules/delayed-stream": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+      "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=0.4.0"
+      }
+    },
+    "node_modules/dotenv": {
+      "version": "16.6.1",
+      "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
+      "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==",
+      "license": "BSD-2-Clause",
+      "engines": {
+        "node": ">=12"
+      },
+      "funding": {
+        "url": "https://dotenvx.com"
+      }
+    },
+    "node_modules/dunder-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+      "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "gopd": "^1.2.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-define-property": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+      "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-errors": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+      "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-object-atoms": {
+      "version": "1.1.1",
+      "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
+      "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/es-set-tostringtag": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+      "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+      "license": "MIT",
+      "dependencies": {
+        "es-errors": "^1.3.0",
+        "get-intrinsic": "^1.2.6",
+        "has-tostringtag": "^1.0.2",
+        "hasown": "^2.0.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/event-target-shim": {
+      "version": "5.0.1",
+      "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz",
+      "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">=6"
+      }
+    },
+    "node_modules/form-data": {
+      "version": "4.0.5",
+      "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+      "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+      "license": "MIT",
+      "dependencies": {
+        "asynckit": "^0.4.0",
+        "combined-stream": "^1.0.8",
+        "es-set-tostringtag": "^2.1.0",
+        "hasown": "^2.0.2",
+        "mime-types": "^2.1.12"
+      },
+      "engines": {
+        "node": ">= 6"
+      }
+    },
+    "node_modules/form-data-encoder": {
+      "version": "1.7.2",
+      "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz",
+      "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==",
+      "license": "MIT"
+    },
+    "node_modules/formdata-node": {
+      "version": "4.4.1",
+      "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz",
+      "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==",
+      "license": "MIT",
+      "dependencies": {
+        "node-domexception": "1.0.0",
+        "web-streams-polyfill": "4.0.0-beta.3"
+      },
+      "engines": {
+        "node": ">= 12.20"
+      }
+    },
+    "node_modules/function-bind": {
+      "version": "1.1.2",
+      "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+      "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+      "license": "MIT",
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-intrinsic": {
+      "version": "1.3.0",
+      "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+      "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+      "license": "MIT",
+      "dependencies": {
+        "call-bind-apply-helpers": "^1.0.2",
+        "es-define-property": "^1.0.1",
+        "es-errors": "^1.3.0",
+        "es-object-atoms": "^1.1.1",
+        "function-bind": "^1.1.2",
+        "get-proto": "^1.0.1",
+        "gopd": "^1.2.0",
+        "has-symbols": "^1.1.0",
+        "hasown": "^2.0.2",
+        "math-intrinsics": "^1.1.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/get-proto": {
+      "version": "1.0.1",
+      "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+      "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+      "license": "MIT",
+      "dependencies": {
+        "dunder-proto": "^1.0.1",
+        "es-object-atoms": "^1.0.0"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/gopd": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+      "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-symbols": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+      "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/has-tostringtag": {
+      "version": "1.0.2",
+      "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+      "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+      "license": "MIT",
+      "dependencies": {
+        "has-symbols": "^1.0.3"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      },
+      "funding": {
+        "url": "https://github.com/sponsors/ljharb"
+      }
+    },
+    "node_modules/hasown": {
+      "version": "2.0.3",
+      "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+      "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+      "license": "MIT",
+      "dependencies": {
+        "function-bind": "^1.1.2"
+      },
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/humanize-ms": {
+      "version": "1.2.1",
+      "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+      "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
+      "license": "MIT",
+      "dependencies": {
+        "ms": "^2.0.0"
+      }
+    },
+    "node_modules/math-intrinsics": {
+      "version": "1.1.0",
+      "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+      "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.4"
+      }
+    },
+    "node_modules/mime-db": {
+      "version": "1.52.0",
+      "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+      "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/mime-types": {
+      "version": "2.1.35",
+      "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+      "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+      "license": "MIT",
+      "dependencies": {
+        "mime-db": "1.52.0"
+      },
+      "engines": {
+        "node": ">= 0.6"
+      }
+    },
+    "node_modules/ms": {
+      "version": "2.1.3",
+      "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+      "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+      "license": "MIT"
+    },
+    "node_modules/node-domexception": {
+      "version": "1.0.0",
+      "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
+      "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
+      "deprecated": "Use your platform's native DOMException instead",
+      "funding": [
+        {
+          "type": "github",
+          "url": "https://github.com/sponsors/jimmywarting"
+        },
+        {
+          "type": "github",
+          "url": "https://paypal.me/jimmywarting"
+        }
+      ],
+      "license": "MIT",
+      "engines": {
+        "node": ">=10.5.0"
+      }
+    },
+    "node_modules/node-fetch": {
+      "version": "2.7.0",
+      "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
+      "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
+      "license": "MIT",
+      "dependencies": {
+        "whatwg-url": "^5.0.0"
+      },
+      "engines": {
+        "node": "4.x || >=6.0.0"
+      },
+      "peerDependencies": {
+        "encoding": "^0.1.0"
+      },
+      "peerDependenciesMeta": {
+        "encoding": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/playwright-core": {
+      "version": "1.59.1",
+      "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.59.1.tgz",
+      "integrity": "sha512-HBV/RJg81z5BiiZ9yPzIiClYV/QMsDCKUyogwH9p3MCP6IYjUFu/MActgYAvK0oWyV9NlwM3GLBjADyWgydVyg==",
+      "license": "Apache-2.0",
+      "bin": {
+        "playwright-core": "cli.js"
+      },
+      "engines": {
+        "node": ">=18"
+      }
+    },
+    "node_modules/tr46": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
+      "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
+      "license": "MIT"
+    },
+    "node_modules/undici-types": {
+      "version": "5.26.5",
+      "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+      "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+      "license": "MIT"
+    },
+    "node_modules/web-streams-polyfill": {
+      "version": "4.0.0-beta.3",
+      "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz",
+      "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==",
+      "license": "MIT",
+      "engines": {
+        "node": ">= 14"
+      }
+    },
+    "node_modules/webidl-conversions": {
+      "version": "3.0.1",
+      "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
+      "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
+      "license": "BSD-2-Clause"
+    },
+    "node_modules/whatwg-url": {
+      "version": "5.0.0",
+      "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
+      "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
+      "license": "MIT",
+      "dependencies": {
+        "tr46": "~0.0.3",
+        "webidl-conversions": "^3.0.0"
+      }
+    }
+  }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..0e43b3e
--- /dev/null
+++ b/package.json
@@ -0,0 +1,10 @@
+{
+  "name": "google-places-key-provisioner",
+  "version": "1.0.0",
+  "private": true,
+  "dependencies": {
+    "@browserbasehq/sdk": "^2.6.0",
+    "dotenv": "^16.4.5",
+    "playwright-core": "^1.50.0"
+  }
+}
diff --git a/screenshot.png b/screenshot.png
new file mode 100644
index 0000000..ccd3c69
Binary files /dev/null and b/screenshot.png differ
diff --git a/session.json b/session.json
new file mode 100644
index 0000000..26ad755
--- /dev/null
+++ b/session.json
@@ -0,0 +1,5 @@
+{
+  "id": "7ea1ba44-519c-4087-9227-c9b5bd949657",
+  "connectUrl": "wss://connect-v2.usw2.browserbase.com/?signingKey=eyJhbGciOiJBMjU2S1ciLCJlbmMiOiJBMjU2R0NNIn0.xlajPkblAUPBXgvA28suG7o6LStW-cA32GYwXEdtPysgfmE2TrbS0w.52viGSqK9dwUFSK0.RwLUawxU8hITkUwVh65DCGo996VNJCDJoPcoYaKrMAwcubiCWskTV35lw0Vmp4Q_lPi1GnkZmicd4o4-hlc79IM4QRlUSfQBiFUe8SJWspxtLPN0LBNDbUJFJIKi-SwETTFW68UlsTgWglAkMlR9vplZJs_XTukMih7Or0CNQxDqQAvsVV3EijSwXfQvqoBgFfVLdGFp5Kfg5DdNxyK-npTJ2-wBMiSc_6WBEPng4IL-Mm_iNS7JUU4vR72-T13XW6zsuTIO0C4GHa3aaz3VKaVzMXz3UBO-yn4bK-SLbHweX_uoq3htbSf_IfbwdPMWetmCQXXXbIWR5DVA5FMNYMjh9njoVWQjr8YR_zn93tzkRDayK86UYv5jSEBn_4BJ08G9ikHC2i3Ohfodz4hPPYExq7izOHI88wnQmmirzgzZTP8TR-2F4gQJqRMdQkY4.rBiqOyWkyYhcNWFPh7ScDw",
+  "debugUrl": "https://www.browserbase.com/devtools-fullscreen/inspector.html?wss=connect.browserbase.com/debug/7ea1ba44-519c-4087-9227-c9b5bd949657/devtools/browser/"
+}
\ No newline at end of file

(oldest)  ·  back to Google Places Key Provisioner  ·  (newest)