← 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();
})();