/** Pure HTML extraction for site onboarding crawls. No network. */
import { type SocialLink } from "./social.ts";
export type ExtractedImage = {
src: string;
alt: string;
width?: number;
height?: number;
inChrome?: boolean;
fromJsonLdLogo?: boolean;
fromOg?: boolean;
aboveFold?: boolean;
pageUrl?: string;
nearbyHeading?: string;
};
export type ExtractedCta = {
href: string;
label: string;
kind: "tel" | "mailto" | "internal" | "external" | "other";
};
export type ExtractedTestimonial = {
quote: string;
attribution?: string;
};
export type ExtractedFaq = {
question: string;
answer: string;
};
export type ExtractedPage = {
aliasOf?: string;
url: string;
title: string;
description: string;
canonical?: string;
lang?: string;
h1: string[];
headings: string[];
outline: string[];
paragraphs: string[];
lists: string[];
testimonials: ExtractedTestimonial[];
faqs: ExtractedFaq[];
links: {
href: string;
label: string;
relative?: boolean;
}[];
ctas: ExtractedCta[];
images: ExtractedImage[];
phones: string[];
emails: string[];
social: SocialLink[];
jsonLd: unknown[];
audit: PageAuditHints;
};
export type PageAuditHints = {
h1Count: number;
missingAlts: number;
unlabeledInputs: number;
hasCanonical: boolean;
hasLang: boolean;
hasJsonLd: boolean;
titleLength: number;
descriptionLength: number;
};
export declare function shouldSkipPath(pathname: string): boolean;
/** Drop runaway query-string variants while keeping unique content URLs. */
export declare function shouldSkipQueryUrl(href: string): boolean;
export declare function stripNoise(html: string): string;
export declare function absolutize(href: string, base: string): string | null;
/** Resolve `` against the document URL; fall back to the page URL. */
export declare function documentBase(html: string, pageUrl: string): string;
/** Path-relative href (about, ../x) as opposed to root-relative, absolute, or hash. */
export declare function isPathRelativeHref(href: string): boolean;
export declare function sameOrigin(a: string, b: string): boolean;
/** Treat www and apex as the same site for crawl bounds. */
export declare function sameSite(a: string, b: string): boolean;
export declare function isLikelyTrackingImage(img: ExtractedImage): boolean;
export declare function extractPhones(text: string): string[];
export declare function extractEmails(text: string): string[];
export declare function parseJsonLd(html: string): unknown[];
export declare function businessFromJsonLd(nodes: unknown[]): {
name?: string;
phone?: string;
email?: string;
address?: string;
city?: string;
hours?: string[];
url?: string;
logoUrl?: string;
};
export declare function jsonLdServices(nodes: unknown[]): {
name: string;
blurb: string;
}[];
/** Builders often put their prose in divs. Keep leaf prose without duplicating
* parent containers, headings, controls or existing semantic paragraphs. */
export declare function extractNarrativeParagraphs(html: string): string[];
export declare function extractPage(html: string, pageUrl: string): ExtractedPage;
export declare function parseSitemapLocs(xml: string): string[];
export declare function inferPrimaryCta(ctas: ExtractedCta[]): ExtractedCta | undefined;
/** Slugify a page URL into a safe copy filename stem. */
export declare function pageCopySlug(pageUrl: string, index: number): string;