← back to Google Places Key Provisioner

02-provision-key.js

143 lines

#!/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();
})();