Keyword research for Kickside — Google Ads search demand and keyword ideas through a WebAssembly client, exposed as agent tools, a pullable demand source, and a deterministic demand score.

MIT 35 downloads
Updated 13 days ago Repository
kicksidekeyword-research

Run

wippy run butschster/keyword-research

butschster/keyword-research

Search demand as a first-class capability inside Kickside: keyword metrics and keyword ideas from the Google Ads Keyword Planner, reached natively by the host rather than through an external service or a scraped page.

What it is for

Anything that decides what to write about, what to bid on, or what to build next eventually needs the same fact: how many people actually search for this, and what else they search for around it. Without a number, that question is settled by whoever argues best.

It answers it three ways, for three kinds of caller:

| You have | You want | Use | |---|---|---| | an agent weighing topics or products | numbers it can compare mid-conversation | the agent tools | | a scoring pipeline, a content machine, a dashboard | the same numbers on a schedule, no model in between | the demand source | | your own Lua | normalised rows and an authenticated client | the libraries |

What it exposes

Agent toolsKeywordMetrics for demand on keywords you name, KeywordIdeas for expanding a seed or a page into what people actually type. Both return markdown tables, because an agent comparing numbers reads a table, not JSON. They ship together as the Google Keyword Research trait.

A pullable demand source (butschster.keyword_research:demand) — the deterministic path. A Data Sync polls a configured keyword set on a schedule and the numbers reach a scoring factor without a model in between. Demand is a full snapshot, keyed per keyword per geo per language per day, so history accumulates and drift is visible instead of a frozen figure quietly going stale.

Librarieskeywords (normalised rows), client (token exchange with a cached access token), config (settings), signer (the assertion, and the WASM result(value, err) boundary).

Why a WebAssembly component

Google service accounts authenticate with an RS256-signed assertion, and the Wippy Lua runtime cannot produce one — crypto verifies JWTs but has no private-key signature. The component covers exactly that one operation and nothing else: no host capability beyond the defaults, and the private key travels a single call path. Networking stays in Lua's own http_client.

Configuration

All settings live in the Settings UI under butschster.keyword_research, encrypted where they are secret. Nothing is read from the environment: service_account_json (the whole key file, so a mismatched client_email/private_key pair is impossible), developer_token, customer_id, login_customer_id (only behind a manager account), subject (the user the service account impersonates), and api_version — Google retires a version about a year after release, so it is configuration rather than a constant.

Units, and data that is absent

Bids arrive in micros and are converted once, at the boundary. A keyword Google has no data for keeps a nil demand figure rather than a zero: collapsing the two understates demand silently, and both tools say which they mean instead of printing a confident 0.

Mounting

One dependency (kickside/contract) and one binding — butschster.keyword_research.security:user_security_scope. The signing component travels with the module and its integrity hash is checked at load.