@ascent/seo-agent is the packaged form of this engine: one facade over the pure cores + injectable backend shells, safe to run in Node, edge, Deno, or the browser.
Install
npm i @ascent/seo-agentThe facade
SP0 status: createSeoAgent(config, deps) takes a required second deps argument — the injected seams (content, storage, gate, LLM calls, job handlers, etc). There is no automatic config→deps wiring yet: content:/storage: fields on the config are inert pass-through today (planned, not yet implemented); licenseKey: is not read at all — the gate is injected via deps.gate instead (see Licensing below). The snippet below is illustrative only — see examples/sdk/run.mjs for the real, runnable two-arg form with every seam faked (node examples/sdk/run.mjs).
import { createSeoAgent } from '@ascent/seo-agent'
const seo = createSeoAgent(
{ site: { domain: 'acme.com' } },
{
// deps: the seams — required today, not yet auto-wired from config
runtime: { kind: 'node', trusted: true, caps: {} },
gate: { plan: 'growth', licensed: true, allows: () => ({ allowed: true }) },
listIndexableEntities: async () => [/* your entities */],
auth: () => ({ ok: true }),
fetchDocs: async () => 'docs',
callClaude: async () => '{}',
db: { /* getExistingKeywords, saveProfile, insertProposals, saveHealth */ },
claimJobs: async () => [], handlers: {}, markDone: async () => {},
markFailed: async () => {}, markCapped: async () => {},
},
)
await seo.analyze() // deterministic health audit — always runs, no gate
await seo.generate() // AI content proposals — gated on deps.gate.allows('contentEnabled')
await seo.run('drain') // work the job queue — not gated todaycreateSeoAgent returns { config, analyze, generate, run }. It's a thin wrapper: it wires the seams you inject into the existing backend shells and cores. No engine logic lives in the SDK layer.
The four profiles
init scaffolds one of four profiles depending on how much of the stack you want. Only headless and api exist today; frontend and admin depend on widget/admin code that hasn't shipped yet.
| Profile | Imports | Runs where | Status |
|---|---|---|---|
| headless | facade + adapters + config | trusted server; browser proxies | shipped |
| api | none (HTTP client only) | consumer calls the deployed HTTP surface | shipped |
| frontend | + /widgets | server APIs + browser widgets | planned — SP4 |
| admin | + /widgets + /admin | + admin cockpit | planned — SP5 |
Scaffold with flags (scriptable for CI) or answer the prompts interactively:
npx @ascent/seo-agent init --profile=headless --runtime=node \
--content=sitemap --storage=supabase --serp=serperinit is idempotent — re-run safely, --force to overwrite — and finishes by printing a profile-tailored activation checklist.
Trusted vs untrusted runtime
createSeoAgent(config, deps) routes on deps.runtime.trusted — same code, safe in both:
- Trusted (Node / edge / Deno server): the full facade runs locally and may hold secret
keys (Anthropic, SERP providers) directly.
- Untrusted (browser): secrets never ship. Deterministic calls (
analyze) still run
locally; anything needing a secret proxies to your deployed service endpoint instead, and degrades to {configured:false} rather than crashing.
Licensing
Today, only generate() is capability-gated, and the gate itself is injected, not derived from config: pass it as deps.gate (built via createLicenseGate(...) from sdk/license.mjs). content:/storage: are inert pass-through; licenseKey: is not read at all. With no license, or a gate that disallows contentEnabled:
- the free deterministic cores still run —
analyze(), health checks — no license needed. generate()returns{ licensed: false, reason }instead of throwing. Never a crash, never
silent.
run('drain')is not gated today; gating for it (and for rankings/competitor research,
which don't exist as facade methods yet) is planned, not shipped.
See docs/SECURITY.md (in the licensed distribution) for the human-approval gate and budget cap that apply once you're licensed, and docs/INSTALL.md for the full scaffold → deploy path.