gtm-skills

Browser Extension Builder

Scaffolds a working, local Chrome extension: it detects any LinkedIn profile, shows a floating panel with a “Reveal contact info” button, calls Explorium to return a verified email/phone/job title/company, and lets the user push that contact to HubSpot (with an editable field-mapping review first) or draft a personalized email in Gmail.

Unlike the workflow skills in this repo, which run a prospecting or enrichment task inside the current Claude Code session, this skill produces a standalone browser extension loaded via Chrome’s “Load unpacked” — a real artifact the user keeps using, not a one-off list. Reach for enrich-contact or list-builder when the user wants a contact or list right now; reach for this skill (or lead-gen-tool-builder) when they want a tool.

No personal keys are stored in this skill. The bundled extension has no hardcoded credentials — both the Explorium key and the HubSpot token are pasted into the extension’s own Options page at install time and stored in chrome.storage.local, local to the user’s browser profile.

When to use

Trigger this for any request to build a Chrome/browser extension for prospecting or contact discovery, a “Lusha alternative”, an “Apollo.io extension clone”, a LinkedIn email/phone finder, or a browser tool that reveals contact info and pushes it to a CRM. Example phrasings: “build me a Lusha-style Chrome extension”, “clone the Apollo.io browser extension but use our own data”, “make a LinkedIn contact finder extension”, “I want a browser extension that reveals emails on LinkedIn and pushes to HubSpot”.

What you get

The bundled template (assets/lead-finder-extension/) is a ready-to-load Manifest V3 extension:

Workflow

1. Copy the template into the user’s workspace

cp -R "<this-skill-dir>/assets/lead-finder-extension" ./lead-finder-extension

Don’t copy node_modules or build artifacts — there are none; this is a vanilla, dependency-free MV3 extension (no build step at all).

2. Get API keys

Only Explorium is required to reveal contacts; HubSpot is optional and only needed for the push button. Summarize from assets/lead-finder-extension/ README.md:

3. Load it and configure keys

# Chrome -> chrome://extensions -> enable Developer mode -> Load unpacked
# -> select the lead-finder-extension folder

Click the toolbar icon → Set API keys → paste the Explorium key and (if using CRM push) the HubSpot token → Save.

4. Verify

Open a real linkedin.com/in/... profile — the panel should appear top-right within a couple of seconds. Click Reveal contact info and confirm real data comes back. If HubSpot is configured, push a contact and confirm the success toast, then check the contact exists in HubSpot.

After any code change: reload the extension’s card in chrome://extensions, then hard-refresh the LinkedIn tab — Chrome does not re-inject content scripts into tabs that were already open.

Architecture

Customizing / restyling to match a brand

This is the most common follow-up request: “make it look like Lusha”, “use Apollo.io’s design system”, “give it a HubSpot-native look”. Do this:

  1. Research the real brand before guessing. Web search and generic knowledge about a company’s colors are frequently wrong (a blue-brand guess for a company that’s actually lime-green is a realistic failure mode). The most reliable signal is the company’s own favicon or Chrome extension icon — fetch it directly and read the pixel colors:
    curl -sL <https://company.com/favicon.ico> -o /tmp/favicon.ico
    python3 -c "
    from PIL import Image
    img = Image.open('/tmp/favicon.ico').convert('RGBA')
    print('corner (bg):', img.getpixel((2, 2)))
    print('center (mark):', img.getpixel((img.width//2, img.height//2)))
    "
    

    Cross-check against the target’s Chrome Web Store listing and any “brand/visual identity” blog posts before finalizing a palette.

  2. Update styles/tokens.css with the real hex values — rename the token if its semantics changed (e.g. --leadfinder-primary staying “primary” is fine; don’t leave a token named -lime holding a blue hex).
  3. Regenerate the icon to match the brand’s mark. For a simple geometric shape, hand-write the SVG. For anything more organic, generate it with an image model and composite it onto an exact-hex background with Pillow so the brand color is pixel-accurate rather than whatever the model approximated:
    from PIL import Image, ImageDraw
    bg = Image.new("RGBA", (128, 128), (0, 0, 0, 0))
    ImageDraw.Draw(bg).rounded_rectangle([0, 0, 127, 127], radius=18, fill=(R, G, B, 255))
    mark = Image.open("generated_mark.png").convert("RGBA").resize((92, 92))
    bg.alpha_composite(mark, (18, 18))
    

    Then rasterize all four sizes with rsvg-convert (if you kept an SVG source) or export directly from the composite.

  4. Rename display strings (manifest.json name, popup/options headers, the panel title in content.js) — leave the leadfinder- CSS/JS class prefix alone unless the user asks for a full fork/rename, since renaming it everywhere is mechanical but easy to half-do and break a selector.
  5. Show the result before calling it done. Render the popup, options page, and result panel as standalone HTML files against styles/ tokens.css + content.css in a browser preview, and look at the screenshot yourself — don’t assume a hex swap looks right.
  6. Never reproduce a trademarked logo pixel-for-pixel if the resulting extension will be shared or published anywhere beyond the requester’s own local machine — recreate the style (color, shape language, motion weight), not a copy of the asset itself. This matters more once the output leaves a single person’s local chrome://extensions page.

Limitations