The same files ship in ascent-types.zip.
types.d.ts
// Core types for the SEO module. These are the ONLY shapes the pure cores know about.
// Project-specific data reaches the cores exclusively as SeoEntity[] via the adapter,
// so no core ever references a project domain concept ("story", "author", ...).
/** One indexable page, produced by a project's adapter. */
export interface SeoEntity {
/** Free-form project type tag, e.g. 'story' | 'author' | 'page'. */
type: string
/** Absolute-from-root path, e.g. '/stories/the-blue-house'. */
url_path: string
title: string
/** Short excerpt used for AI context + thin-content checks. NEVER paywalled full text. */
body_sample: string
/** BCP-47 language tag, e.g. 'en', 'fr', 'mfe'. */
lang: string
/** ISO 8601 last-modified timestamp. */
updated_at: string
/** schema.org type to emit, e.g. 'Article' | 'Person' | 'WebPage'. */
schema_type: string
/** Translations of this entity, for hreflang alternates. */
alternates?: { lang: string; url_path: string }[]
/** Absolute image URL for OG/Twitter cards + Article schema. */
image_url?: string
}
/** A single health-audit finding. */
export interface Issue {
code: string
severity: 'error' | 'warn' | 'info'
detail: string
}
/** The doc-derived, admin-editable project profile that steers the engine. */
export interface SeoProfile {
siteName: string
siteUrl: string
description: string
geography?: string
languages?: string[]
audience?: string
competitors?: string[]
logoUrl?: string
sameAs?: string[]
twitterCard?: string
}
// ── v2 (autonomous research agent) ──────────────────────────────────────────────
/** One normalized search-result row from a SERP provider. */
export interface SerpResult {
position: number
domain: string
url: string
title: string
}
/** A registry source with a content hash for diff-driven re-investigation. */
export interface SourceRecord {
id: string
kind: string // 'project_docs' | 'own_site' | 'competitor' | 'serp' | 'search_console'
ref: string
content_hash?: string
}
/** Where a proposal/finding came from (attached to every proposal). */
export interface Provenance {
source: string
researcher: string
competitor?: string
rationale: string
}
/** One rank observation for a keyword. */
export interface RankRow {
keyword: string
our_position: number | null
checked_at: string
}adapter.d.ts
import type { SeoEntity } from '../core/types'
/**
* THE consumer seam. Each project implements this one function to enumerate its
* indexable pages as a uniform SeoEntity[]. Every core (engine, sitemap, JSON-LD,
* health) consumes the result — so the module stays 100% project-agnostic.
*
* Rules:
* - Return only publicly-indexable, published entities.
* - body_sample is an EXCERPT only — never expose paywalled/full body text.
* - Include `alternates` for translated entities so hreflang is emitted.
*/
export function listIndexableEntities(): Promise<SeoEntity[]>billingProvider.d.ts
// BillingProvider seam — an OPTIONAL subscription-billing abstraction for SELF-HOSTERS, mirroring the
// serpProvider / storage / notify seams. Swap adapters without touching the rest of the agent:
// - `mips` → hosted card payments via MIPS (billingProvider.mips.mjs). MIPS charges in the merchant's
// configured display currency at a locked FX rate — NOT Mauritius/MUR-only.
// - `fake` → in-memory, for tests + local demo (billingProvider.fake.mjs).
// - `<intl>`→ any other processor (owner-chosen). NOT assumed to be Stripe.
//
// NOTE: the hosted Ascent platform does NOT use this subscription seam — it meters paid usage with an
// account-level ops-credits WALLET (prepaid credits + top-up packs; BYOK runs on the caller's own keys).
// This seam is for self-hosters who want classic plan subscriptions. A plan name maps to entitlements via
// core/entitlements.mjs; the provider only handles money + subscription lifecycle, never feature gating.
export type SubscriptionStatus = 'active' | 'past_due' | 'canceled' | 'none'
export interface Subscription {
plan: string
status: SubscriptionStatus
/** ISO 8601, or null when there's no active period. */
renewsAt: string | null
}
export interface CheckoutSession {
/** Where to send the customer to pay (hosted payment page / redirect). */
redirectUrl: string
/** Opaque reference the provider echoes back on its callback/webhook. */
reference: string
}
export interface WebhookEvent {
event: string
account: string
plan: string
status: SubscriptionStatus
renewsAt?: string | null
}
export interface BillingProvider {
name: string
/** Start a subscription: returns where to send the customer to pay. */
createCheckout(account: string, plan: string): Promise<CheckoutSession>
/** Current subscription for an account (→ entitlements). */
getSubscription(account: string): Promise<Subscription>
/** Verify + parse a provider callback/webhook. MUST throw on an invalid signature. */
verifyWebhook(headers: Record<string, string>, rawBody: string): Promise<WebhookEvent>
/** Cancel at period end (or immediately, provider-dependent). */
cancel(account: string): Promise<void>
}fetchPage.d.ts
export interface FetchPage {
fetch(url: string): Promise<{ ok: boolean; url: string; text: string }>
}searchConsole.d.ts
/**
* Search Console seam (optional, off by default). Where the SERP provider reads the
* *public* ranking of a keyword, a SearchConsole provider reads the site owner's own
* measured query performance (clicks + impressions + average position) from Google
* Search Console — the buyer's real traffic, the true-outcomes signal for the dashboard.
*/
export interface KeywordPosition {
keyword: string
position: number
impressions: number
clicks?: number
}
export interface GscSummary {
clicks: number
impressions: number
ctr: number // percentage
position: number
}
export interface SearchConsole {
name: string
positions(): Promise<KeywordPosition[]>
summary?(): Promise<GscSummary>
}serpProvider.d.ts
/**
* SERP provider seam. A provider runs a search query and returns raw result
* rows ({url, title}) — normalization (positions, domains) is serpParse's job
* downstream. Providers are swappable and admin-configured; `google` is default.
*/
export interface SerpProviderResult {
url: string
title: string
}
export interface SerpSearchOpts {
location?: string
num?: number
}
export interface SerpProvider {
name: string
search(query: string, opts?: SerpSearchOpts): Promise<{ results: SerpProviderResult[] }>
}storage.d.ts
// StorageAdapter — the storage seam (E9).
//
// Every read/write the agent performs goes through ONE object. The pure cores never
// touch storage; the backend shell injects a StorageAdapter. This makes the whole
// component storage-agnostic: back it with Supabase (the reference), Postgres/MySQL/
// SQLite elsewhere, a document/NoSQL store, or the bundled in-memory/file store for a
// zero-database deployment.
//
// SECURITY — READ THIS. The Supabase reference enforces access with Row-Level Security +
// SECURITY DEFINER RPCs. Those do NOT exist outside Postgres. When you back the agent with
// any other store, the adapter (and the surface that calls it) is solely responsible for
// authorization: only an admin context may read/write agent state; the public serving reads
// (published guides, approved page meta) must expose ONLY published/approved rows and only
// their public columns. A storage adapter with no authz is an open database.
export interface AgentSettings {
autonomy?: Record<string, unknown>
notifications?: Record<string, unknown>
self_heal?: Record<string, unknown>
budget_cap_cents_month?: number
cost_rates?: Record<string, number>
serp_provider?: string
enabled_researchers?: Record<string, boolean>
model_weights?: Record<string, unknown>
notify_state?: Record<string, unknown>
[k: string]: unknown
}
export interface Proposal { id: string; type: string; payload: any; score?: number | null; status: 'pending' | 'approved' | 'rejected'; rationale?: string | null }
export interface RankRow { keyword: string; our_position: number | null; top_results: Array<{ position: number; domain: string }>; checked_at: string }
export interface Job { id: string; type: string; payload: any; status: 'queued' | 'running' | 'done' | 'failed' | 'capped'; attempts: number; updated_at: string }
export interface CircuitState { consecutiveFailures: number; open: boolean }
export interface FeedbackRow { proposal_type: string; outcome: string }
export interface StorageAdapter {
// ── settings ──
getSettings(): Promise<AgentSettings>
setSettings(patch: Partial<AgentSettings>): Promise<void>
// ── budget ledger ──
// The engine's built-in metering unit is real CENTS, capped by
// AgentSettings.budget_cap_cents_month and enforced by core/budgetGate.mjs.
// A host that meters in its own unit (credits, quota, seats) implements these two against ITS OWN
// ledger and converts — monthSpend() must return cents so budgetGate can compare against the cap.
// Returning 0 from monthSpend() with a cap of 0 disables all paid work, which is fail-safe but silent:
// if you are substituting your own metering, gate BEFORE calling the engine rather than starving it here.
monthSpend(): Promise<number>
recordSpend(lineType: string, cents: number): Promise<void>
// ── keywords ──
getTargetKeywords(): Promise<string[]>
upsertKeyword(k: { keyword: string; status?: string; is_target?: boolean }): Promise<void>
// ── rankings ──
saveRank(row: RankRow): Promise<void>
recentRankEntries(limit?: number): Promise<Array<{ our_position: number | null; top_results: any }>>
// ── proposals + feedback (the human/auto gate) ──
insertProposals(rows: Array<Omit<Proposal, 'id' | 'status'>>): Promise<void>
pendingProposals(): Promise<Proposal[]>
pendingProposalCount(): Promise<number>
approvedProposals(): Promise<Array<{ type: string; payload: any }>>
applyProposal(id: string, actor: string): Promise<void> // promote pending → live + audit
insertFeedback(rows: FeedbackRow[]): Promise<void>
allFeedback(): Promise<FeedbackRow[]>
// ── jobs ──
enqueue(type: string, payload: any): Promise<void>
claimJobs(limit: number): Promise<Job[]>
markDone(id: string): Promise<void>
markFailed(id: string, error: string): Promise<void>
markCapped(id: string): Promise<void>
// ── circuit breaker ──
getCircuit(target: string): Promise<CircuitState>
saveCircuit(target: string, state: CircuitState): Promise<void>
listCircuits(): Promise<Array<{ name: string; open: boolean; openedAtIso?: string }>>
// ── health / self-checks ──
saveHealthChecks(results: Array<{ name: string; ok: boolean; detail?: string }>): Promise<void>
recentHealthAllOk(): Promise<{ ok: boolean; failed: string[] }>
// ── notifications (E2) ──
notify(event: any, cfg: any): Promise<void>
getNotifyState(): Promise<Record<string, any>>
saveNotifyState(state: Record<string, any>): Promise<void>
// ── model (E6) ──
saveModel(model: any): Promise<void>
// NOTE: content (E3), self-heal revert (E4), competitor-matrix (E8) methods extend this
// same object in the reference Supabase adapter. A minimal store may omit them; the agent
// degrades gracefully (those features simply produce nothing).
}webSearch.d.ts
export interface WebSearchResult {
url: string
title: string
snippet?: string
}
export interface WebSearch {
name: string
search(query: string, opts?: { num?: number }): Promise<{ results: WebSearchResult[] }>
}Package exports
@ascent/seo-agent version 1.0.0. Not on a public registry; licensed customers receive it from Umbodi. The MCP server ships as seo-agent-mcp.
| Import path | Exports |
|---|---|
@ascent/seo-agent | createLicenseGate createSeoAgent memoryStore resolveConfig resolveModality resolveRuntime resolveViewport sitemapAdapter verifyLicense |
@ascent/seo-agent/license | createLicenseGate verifyLicense |
@ascent/seo-agent/config | PROFILES resolveConfig |
@ascent/seo-agent/runtime | resolveRuntime |
@ascent/seo-agent/cores | PLAN_NAMES articleSchema auditEntities breadcrumbSchema entitiesFromSitemap faqSchema joinOrigin mergeMeta normalizeLastmod organizationSchema parseSitemap pathFromUrl personSchema pickSitemapUrl planEntitlements renderJsonLd requireEntitlement slugify titleFromPath websiteSchema |
@ascent/seo-agent/service | createSeoService defaultHandlers resolveRequestDeps toNodeHandler |
@ascent/seo-agent/mcp | configSnippet defineTools handleMessage localBackend remoteBackend resolveBackend runServer |
@ascent/seo-agent/widgets | attrs buildHeadTags buildHealthGauge buildRankBadge buildResultCard esc formFactor injectHead isTouch makeAscentClient mount mountHealthGauge mountRankBadge mountResultCard |
@ascent/seo-agent/admin | actionButton buildAutonomyDial buildCompetitors buildKeywords buildProposals buildRankings buildStatusPanel handleActionClick mountAutonomyDial mountCompetitors mountKeywords mountPanel mountProposals mountRankings mountStatusPanel table |