seaportal

package module
v0.1.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: May 31, 2026 License: MIT Imports: 4 Imported by: 0

README

SeaPortal

Fast content extraction for AI agents. HTTP-first, no browser required.

Install

# npm (recommended)
npm install -g seaportal

# Go
go install github.com/pinchtab/seaportal/cmd/seaportal@latest

Usage

seaportal https://pinchtab.com

# Options
seaportal --json https://pinchtab.com       # JSON output
seaportal --snapshot https://pinchtab.com   # Accessibility tree
seaportal --fast https://pinchtab.com       # Bail early if browser needed
seaportal --no-dedupe https://pinchtab.com  # Disable deduplication

# Subcommands
seaportal sitemap https://pinchtab.com/sitemap.xml  # Flatten a sitemap
seaportal feed https://pinchtab.com/feed.xml        # Parse RSS / Atom / JSON Feed
seaportal mcp                                       # Run as an MCP server over stdio

# Version
seaportal --version

The full flag list and subcommands are in the CLI reference. SeaPortal also runs as an MCP server (seaportal mcp), and ships seabench, a benchmark/evaluation harness.

Accessibility Snapshot

The --snapshot flag outputs a semantic accessibility tree — useful for AI agents that need to understand page structure and interact with elements:

seaportal --snapshot https://pinchtab.com
{
  "role": "document",
  "children": [
    {
      "role": "navigation",
      "name": "Main",
      "tag": "nav",
      "ref": "e1",
      "selector": "#main-nav",
      "depth": 0,
      "children": [
        {"role": "link", "name": "Home", "tag": "a", "ref": "e2", "selector": "a.nav-link", "depth": 1, "href": "/", "interactive": true}
      ]
    }
  ]
}

Each node includes:

  • role — Accessibility role (heading, link, button, textbox, etc.)
  • name — Accessible name (from aria-label, title, alt, or text)
  • tag — HTML tag name (div, a, button, etc.)
  • ref — Element reference (e1, e2...) for targeting
  • selector — CSS selector for the element
  • depth — Nesting depth in the tree
  • interactive — Whether the element can be clicked/typed
  • level — Heading level (1-6) for headings
  • href — Link target for links
Snapshot Options
# Filter to interactive elements only
seaportal --snapshot --filter=interactive https://example.com

# Compact text output (instead of JSON)
seaportal --snapshot --format=compact https://example.com

# Limit output size (approximate token count)
seaportal --snapshot --max-tokens=2000 https://example.com

# Combine options
seaportal --snapshot --filter=interactive --format=compact https://example.com

Compact format outputs a readable text tree:

document
  e1 navigation "Main" <nav> [interactive]
    e2 link "Home" <a> [interactive] href=/
    e3 link "Docs" <a> [interactive] href=/docs
  e4 main <main>
    e5 heading "Welcome" <h1> level=1

As a Library

The public package is the module root, github.com/pinchtab/seaportal:

import "github.com/pinchtab/seaportal"

// Extract content
result := seaportal.FromURL("https://pinchtab.com")
fmt.Println(result.Content) // extracted Markdown

// With options
result := seaportal.FromURLWithOptions("https://pinchtab.com", seaportal.Options{
    Dedupe:   true,
    FastMode: true,
})

// Build accessibility snapshot
snapshot, err := seaportal.BuildSnapshot(htmlString)

// Snapshot with options (filter, max tokens)
opts := seaportal.SnapshotOptions{
    FilterInteractive: true,
    MaxTokens:         2000,
}
snapshot, err := seaportal.BuildSnapshotWithOptions(htmlString, opts)

// Compact text output
fmt.Println(snapshot.ToCompact())

See the API reference for the full surface.

Features

  • Fast on its niche — Pure HTTP; on reachable static/SSR pages p50 ~1s, p95 ~2s (across the open web the tail is much longer)
  • Stealthy — Chrome TLS fingerprint, realistic headers
  • Smart — Readability extraction + Markdown conversion
  • Semantic — Accessibility tree for AI agents
  • Honest — Classifies pages, signals when browser is needed
  • Clean — Deduplicates repeated content blocks

Detection

Automatically detects:

  • Bot protection (Cloudflare, AWS WAF, DataDome, PerimeterX)
  • Captcha pages
  • Access denied / login walls
  • SPA / JavaScript-only content

Page Classification

Class Description
static Pure HTML, high confidence
ssr Server-rendered, good extraction
hydrated SSR + JS enhancement, usually extractable
spa JavaScript-only content, needs browser
dynamic Heavy client-side rendering
blocked Bot protection, captcha, access denied

The quality float is an advisory soft signal, not a gate — clean server-rendered pages routinely score ~0 while extracting perfectly. Route on the page class and browser-recommendation signal (profile.decision / browserRecommended), not the raw quality value. See api.md and browser-discriminator.md.

Reliability / what to expect

SeaPortal is a fast first-pass triage that fails over, not a universal fetcher. It wins on static and server-rendered pages and tells you when to reach for a browser instead of pretending every URL extracts.

Numbers below are a frozen snapshot of the committed live sweeps — full breakdown, dates, and git SHAs in the reliability reference:

Reachable, in-niche (static/SSR) Across the open web (Tranco top-1000)
Latency (ok fetches) p50 ~1s, p95 ~2s p50 ~1.6s, p90 >10s, p95 ~15s
Success ~94% ok 40% ok — ~53% netting out the ~242 dead CDN/DNS infra hosts

What that means in practice:

  • In its niche it's fast and reliable.
  • Across the raw open web, ~1 in 3 hosts time out and ~1 in 4 error — many are CDN/DNS infrastructure domains (akamaiedge.net, cloudfront.net, …) that never serve HTML.
  • Treat extraction as triage: set --timeout and route on the browser-recommendation signal (profile.decision / browserRecommended), failing over to a real browser rather than assuming the happy path.

Regenerate any time with ./dev bench sweep (see the seabench reference).

What It Doesn't Do

  • JavaScript execution
  • Full browser rendering
  • Cookie/session management

For JS-heavy pages, use a browser and pass HTML to seaportal.FromHTML().

Core vs. advanced surfaces

SeaPortal is, first, one thing: a fast, no-browser fetch-and-extract primitive that returns clean Markdown + an accessibility snapshot and tells you when a page needs a browser. That is the core, and everything in the value prop above describes it.

Layered on top are secondary, opt-in helpers — useful, but not the identity and off by default:

Surface What it is Where
Chunking (--chunk) Split Markdown into heading/sentence/window chunks for RAG api.md
BM25 ranking (--query) Score heading-bounded sections by relevance api.md
Split output (--split-*) Shard a large extraction across files api.md
TEI-Lite XML (--xml) Wrap a result as TEI-Lite for corpus tooling api.md
Sitemaps & feeds Flatten sitemap.xml, parse RSS/Atom/JSON Feed api.md
seabench Benchmark / capability harness — dev tooling, not shipped product seabench.md

If you only want the core, ignore all of the above: seaportal <url> and seaportal.FromURL(...) never touch them.

License

MIT

Documentation

Overview

Package seaportal provides fast content extraction for AI agents. HTTP-first, no browser required.

This is the public API. All implementation lives in internal/.

Index

Constants

View Source
const (
	// LinkRetentionAll keeps inline `[text](url)` as-is (default).
	LinkRetentionAll = engine.LinkRetentionAll
	// LinkRetentionNone strips both link text and URL.
	LinkRetentionNone = engine.LinkRetentionNone
	// LinkRetentionText keeps the link text, drops the URL.
	LinkRetentionText = engine.LinkRetentionText
	// LinkRetentionFooter delegates to ConvertLinksToCitations:
	// numbered `⟨N⟩` markers and a `## References` section.
	LinkRetentionFooter = engine.LinkRetentionFooter
)
View Source
const (
	// ChunkOff disables chunking (default).
	ChunkOff = engine.ChunkOff
	// ChunkHeading splits at H2/H3 boundaries.
	ChunkHeading = engine.ChunkHeading
	// ChunkSentence groups sentences to a token target.
	ChunkSentence = engine.ChunkSentence
	// ChunkWindow slides a char window with overlap.
	ChunkWindow = engine.ChunkWindow
)
View Source
const (
	DecisionStaticHighConfidence = engine.DecisionStaticHighConfidence
	DecisionStaticOK             = engine.DecisionStaticOK
	DecisionStaticCaution        = engine.DecisionStaticCaution
	DecisionBrowserNeeded        = engine.DecisionBrowserNeeded
	DecisionBlocked              = engine.DecisionBlocked
	DecisionUnreachable          = engine.DecisionUnreachable
	DecisionNotFound             = engine.DecisionNotFound
	DecisionUnsupported          = engine.DecisionUnsupported
)

Browser-routing decisions. See docs/reference/browser-discriminator.md.

Variables

View Source
var FlattenSitemap = engine.FlattenSitemap

FlattenSitemap fetches a sitemap URL and recursively flattens `<sitemapindex>` references into a single slice of SitemapEntry.

View Source
var ParseFeed = engine.ParseFeed

ParseFeed fetches a feed URL and parses it as RSS 2.0, Atom 1.0, or JSON Feed 1.x, returning a unified slice of FeedItem.

Functions

func CleanupMarkdown

func CleanupMarkdown(md string) string

CleanupMarkdown normalises whitespace and formatting in markdown.

func ContentChanged

func ContentChanged(oldContent, newContent string) bool

ContentChanged checks if content has changed based on fingerprints.

func DetectBlocked

func DetectBlocked(html string) bool

DetectBlocked checks if a page is blocked by bot protection.

func DetectSPA

func DetectSPA(html string) (signals []string, isSPA bool)

DetectSPA checks HTML for single-page application signals.

func ExtractFromHTML

func ExtractFromHTML(html string, targetURL string) (string, error)

ExtractFromHTML extracts markdown from raw HTML (simple interface).

func FetchBytes

func FetchBytes(ctx context.Context, rawURL string, opts FetchBytesOptions) ([]byte, http.Header, int, error)

FetchBytes returns response bytes, headers, and status for rawURL.

func PreprocessHTML

func PreprocessHTML(html string) string

PreprocessHTML cleans HTML before extraction.

func QuickNeedsBrowser

func QuickNeedsBrowser(html string) (needsBrowser bool, reason string)

QuickNeedsBrowser checks if HTML likely needs a browser to render.

func ResultToTEIXML

func ResultToTEIXML(r Result) ([]byte, error)

ResultToTEIXML wraps a Result into a TEI-Lite XML document.

func SemanticFingerprint

func SemanticFingerprint(content string) string

SemanticFingerprint generates a content fingerprint for change detection.

Types

type BrowserDecision

type BrowserDecision = engine.BrowserDecision

BrowserDecision is the routing category exposed on Profile.Decision for callers (e.g. PinchTab) deciding whether to fall through to a real browser.

type CardItem

type CardItem = engine.CardItem

CardItem represents a card/item on an index page.

type Chunk

type Chunk = engine.Chunk

Chunk is one piece of a chunked Markdown body.

func ChunkMarkdown

func ChunkMarkdown(md string, cfg ChunkConfig) []Chunk

ChunkMarkdown returns Markdown chunks under cfg, or nil when off / too short.

type ChunkConfig

type ChunkConfig = engine.ChunkConfig

ChunkConfig controls Markdown chunking.

func ParseChunkConfig

func ParseChunkConfig(s string) (ChunkConfig, error)

ParseChunkConfig parses the CLI form "heading" / "sentence[:N]" / "window[:N[:O]]".

type ChunkStrategy

type ChunkStrategy = engine.ChunkStrategy

ChunkStrategy selects a chunking algorithm.

type DedupeOptions

type DedupeOptions = engine.DedupeOptions

DedupeOptions configures deduplication behaviour.

type DedupeResult

type DedupeResult = engine.DedupeResult

DedupeResult holds content deduplication metrics.

func Dedupe

func Dedupe(content string) DedupeResult

Dedupe removes duplicate content blocks.

func DedupeWithOptions

func DedupeWithOptions(content string, opts DedupeOptions) DedupeResult

DedupeWithOptions removes duplicate content blocks with custom options.

type ExtractionOutcome

type ExtractionOutcome = engine.ExtractionOutcome

ExtractionOutcome indicates whether content is usable or needs a browser.

type FeedItem

type FeedItem = engine.FeedItem

FeedItem is a normalised feed entry across RSS 2.0, Atom 1.0, and JSON Feed 1.x sources.

type FetchBytesOptions

type FetchBytesOptions = engine.FetchBytesOptions

FetchBytesOptions controls a raw network fetch with optional security checks.

type FlattenSitemapOptions

type FlattenSitemapOptions = engine.FlattenSitemapOptions

FlattenSitemapOptions controls FlattenSitemap behaviour.

type IndexPageResult

type IndexPageResult = engine.IndexPageResult

IndexPageResult holds index/listing page extraction results.

type LinkRetention

type LinkRetention = engine.LinkRetention

LinkRetention controls how inline Markdown links are kept in extracted output.

func ParseLinkRetention

func ParseLinkRetention(s string) (LinkRetention, error)

ParseLinkRetention parses a mode name ("none"|"text"|"all"|"footer").

type Options

type Options = engine.Options

Options controls extraction behaviour.

type PageClass

type PageClass = engine.PageClass

PageClass is the type of page (static, SSR, hydrated, dynamic, SPA, blocked).

type PageProfile

type PageProfile = engine.PageProfile

PageProfile describes the classification of a page.

func ClassifyPage

func ClassifyPage(result Result) PageProfile

ClassifyPage determines the page type from extraction results.

type ParseFeedOptions

type ParseFeedOptions = engine.ParseFeedOptions

ParseFeedOptions controls ParseFeed behaviour.

type RankedSection

type RankedSection = engine.RankedSection

RankedSection is a BM25-scored, heading-bounded slice of Markdown.

func RankSections

func RankSections(content, query string, k1, b float64, topN int) []RankedSection

RankSections scores Markdown sections (H2/H3-bounded) by BM25 against the query and returns them in descending score order. topN > 0 truncates; defaults k1=1.5, b=0.75 are applied when 0 is passed.

type Result

type Result = engine.Result

Result holds the extraction output for a URL.

func FromHTML

func FromHTML(html string, targetURL string) Result

FromHTML extracts content from raw HTML.

func FromHTMLWithOptions

func FromHTMLWithOptions(html string, targetURL string, opts Options) Result

FromHTMLWithOptions extracts content from raw HTML with custom options.

func FromResponse

func FromResponse(resp *http.Response, targetURL string, start time.Time) Result

FromResponse extracts content from an HTTP response.

func FromURL

func FromURL(targetURL string) Result

FromURL extracts content from a URL with default options.

func FromURLWithDedupe

func FromURLWithDedupe(targetURL string) Result

FromURLWithDedupe extracts content with deduplication enabled.

func FromURLWithOptions

func FromURLWithOptions(targetURL string, opts Options) Result

FromURLWithOptions extracts content from a URL with custom options.

type SecurityPolicy

type SecurityPolicy = engine.SecurityPolicy

SecurityPolicy is the opt-in SSRF / private-IP / redirect / decompression guard threaded through the fetch path. Set it on Options.Security. A nil policy keeps the historical unguarded behaviour.

func DefaultSecurityPolicy

func DefaultSecurityPolicy() *SecurityPolicy

DefaultSecurityPolicy returns the recommended secure-by-default policy: block private/internal IPs, http/https only, a 10-redirect cap with per-hop revalidation, and 50 MiB raw / 200 MiB decompressed body caps.

type SitemapEntry

type SitemapEntry = engine.SitemapEntry

SitemapEntry is a single URL entry flattened from a sitemap.

type SnapshotNode

type SnapshotNode = engine.SnapshotNode

SnapshotNode is a node in the accessibility snapshot tree.

func BuildSnapshot

func BuildSnapshot(htmlStr string) (*SnapshotNode, error)

BuildSnapshot creates an accessibility tree from HTML.

func BuildSnapshotWithOptions

func BuildSnapshotWithOptions(htmlStr string, opts SnapshotOptions) (*SnapshotNode, error)

BuildSnapshotWithOptions creates an accessibility tree with custom options.

type SnapshotOptions

type SnapshotOptions = engine.SnapshotOptions

SnapshotOptions controls accessibility snapshot generation.

type SplitConfig

type SplitConfig = engine.SplitConfig

SplitConfig controls SplitResultToFiles.

type SplitFile

type SplitFile = engine.SplitFile

SplitFile is one entry in the SplitResultToFiles manifest.

func SplitResultToFiles

func SplitResultToFiles(r Result, cfg SplitConfig) ([]SplitFile, error)

SplitResultToFiles writes the Result's content split across multiple files under cfg.Dir and returns the manifest.

type Validation

type Validation = engine.Validation

Validation holds extraction quality validation results.

func ValidateExtraction

func ValidateExtraction(r *Result) Validation

ValidateExtraction assesses extraction quality.

Directories

Path Synopsis
cmd
seabench command
Command seabench is the SeaPortal benchmark / evaluation harness.
Command seabench is the SeaPortal benchmark / evaluation harness.
seaportal command
internal
engine
Package portal provides content extraction with SPA detection
Package portal provides content extraction with SPA detection
engine/leakcheck
Package leakcheck provides a test helper that fails a Go test when it finishes with more live goroutines than it started with.
Package leakcheck provides a test helper that fails a Go test when it finishes with more live goroutines than it started with.
engine/mock
Package mock provides a record/replay HTTP RoundTripper layer for tests that exercise the engine's HTTP-fetch paths without paying real-network latency or flakiness costs.
Package mock provides a record/replay HTTP RoundTripper layer for tests that exercise the engine's HTTP-fetch paths without paying real-network latency or flakiness costs.
mcp
Package mcp implements a minimal Model Context Protocol (MCP) server over JSON-RPC 2.0 line-delimited stdio.
Package mcp implements a minimal Model Context Protocol (MCP) server over JSON-RPC 2.0 line-delimited stdio.
quality
Package quality provides extraction quality analysis for markdown content.
Package quality provides extraction quality analysis for markdown content.
testserver/fixture
Package fixture provides a declarative, in-process HTTP test server for per-test scenarios that exercise HTTP-level behaviour (status codes, redirects, slow responses, charset declarations, header echoes, etc.).
Package fixture provides a declarative, in-process HTTP test server for per-test scenarios that exercise HTTP-level behaviour (status codes, redirects, slow responses, charset declarations, header echoes, etc.).

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL