Changelog
Release notes for analog-sdk.
0.19.0 — 2026-08-19
Section titled “0.19.0 — 2026-08-19”-
analog export --collection INDEXexports the Collection a result prints asCollection N— its canonical records over exactly its connected sections — and--fields/--where/--sort/--limitshape it as one table.analog get’s query hint addresses the Collection when one exists,analog describelists Collections with that selector, and the pooled-export refusal names it. -
assessnow reads the syndication feeds a page declares in its head (<link rel="alternate">RSS, Atom, or JSON Feed) and names them in the guidance — read from the HTML, never fetched.FitAssessment.declared_feedscarries them structurally.
Changed
Section titled “Changed”-
Response schema v27 adds the required
document_section_plan;result.sectionsfollows that authored order, reuses each recordSection, and keeps unresolved prose regions in place with empty Markdown. -
Markdown output now uses ATX headings and stable hyphen list markers, preserves semantic blocks and irregular tables more faithfully, and safely serializes authored code fences, links, images, and media destinations.
-
Markdown output adopts Marcato 0.3: single-cell layout tables unwrap to prose with their line breaks intact instead of rendering as one-cell tables full of literal
<br>text. -
preview()now summarizes a page’s navigation sections in one aggregate line — section count, total links, and their placements (header / in-body / sidebar / footer) — instead of rendering their records inline. Up to two labeled in-body navigation sections (a table of contents, a curated link list) still appear compactly at their position in the reading order, and on a page that is entirely navigation the summary line leads. The full link records remain instructured_contentand onesections_by_kind("navigation")call away; nothing is dropped from the result. -
Navigation is now part of
structured_content: header, footer, sidebar, and in-page link groups arrive as sections of kind"navigation"at their position in the reading order, one record per link (text,url,group,depth), withSection.placementsaying where each sits. The separate top-levelnavigationfield is gone;sections_by_kind("navigation")collects the whole map, andfindcovers links like any other record. -
preview()now says when a page’s substance is prose rather than records. On a page whose mixedsectionsview carries at least as much rendered text in Markdown Sections as in record sections, the preview adds one framing line — how many markdown regions, how much text, and that they ride beside the record sections insectionsin reading order — followed by the full-page markdown pointer. A not-extracted outline entry whose region’s text renders in the mixed view now says so directly instead of reporting only why it produced no records, so an article-shaped page with a few incidental record sections can no longer read as fully extracted. Nothing changes in the result itself; records,sections, andmarkdowncarry exactly what they did before. -
Results no longer expose the
bot_protectionclassification. When no structured Sections are extracted, the preview now states that observed outcome without guessing why and keeps the page document available. -
Virtualized-feed reconstruction now rebuilds items the page only ever showed as pre-hydration placeholders. When a feed item’s sole captured state renders its content as escaped source markup — the page shipped the item’s own markup but never hydrated it while scrolling — the capture now carries that content as real elements, links included, inside the item’s preserved wrapper. Items whose text merely mentions markup are never rewritten. The windowed-feed disclosure counts how many items were rebuilt from their shipped source.
-
The built-in browser now refuses redirects, client-side navigation, subresources, and WebSockets that target non-public network addresses before connecting.
allow_private=Trueremains the explicit opt-in for trusted development and intranet pages. -
The built-in browser no longer prints internal
TargetClosedErrortracebacks to stderr when a page finishes loading late resources during teardown; stderr carries only what the command has to say. -
Comparison sections now have working markdown on both surfaces: a saved result’s per-section markdown renders the authored feature table (plans as columns) instead of coming back empty on grid-built pages, and the whole-page markdown carries values that pages draw as icons with a declared name — a checkmark cell’s “Not available” appears as text instead of a blank that reads as authoritative absence.
-
describe’s table now shows whether a field behaves numerically in queries, and in what unit —numeric(seconds)beside the field’s traits — so the facts that govern how--wherereads a comparison are visible on the surface you actually read before writing one, not only in-f json. -
distinct --countacross sections with different shapes now carries the same caveat the value list enforces: the count still answers, with a stderr warning that the pool can conflate unlike values and how to scope per shape — and the value list’s refusal no longer teaches--countas if it were caveat-free. -
A page that the server answers with an error status (a 404, a 410, a 5xx) is now a fetch error naming that status, through the built-in browser exactly as through the HTTP fetcher — never rendered and presented as content.
FetchStatusError(aFetchError) carries the status and URL; blocked-request statuses keep their separateFetchBlockedErrorframing. -
findnow covers everything the markdown export shows: a section whose records dropped some content still reports a match from the section’s text (saying that’s where it was found), and content between extracted regions is reported from the page markdown as a last resort — a string you can see inexport -f mdis never answered with “No matches” again. -
get(andanalog()in auto mode) now leads its preview with the platform’s own data feed when the page looks like a Shopify store or Greenhouse board — naming the collection’s feed on a collection page and routing toassessfor the check. Read from the page, no extra request; previously onlyassessmentioned the feed. -
A page whose only pagination evidence is a head
<link rel="next">is no longer reported as a confirmed paginated collection, even when the link’s URL carries a page number — the declaration can’t corroborate itself, and infinite-scroll feeds emit exactly this shape. The link is still reported and followable; the confirmed claim and itspages=Nsweep guidance now require a second, independent signal (rendered pager links, a stated count, or in-place controls). The unconfirmed disclosure also no longer asserts “these records cover this page only”, which an infinite-scroll capture spanning many pages would contradict. -
The hidden-fields footer in
analog get/analog openoutput now names a complete runnable command —analog open <handle> --completefor saved results, “re-run with--complete” otherwise. Previously it said only “pass--complete”, and the flag exists ongetandopenbut not ondescribeorexport, so the bare hint invited pairing it with commands that reject it. -
load_allcan no longer make a result worse than not asking for it: when pressing a load-more control destroys content instead of adding to it (a virtualized timeline resetting itself, say) or leads away from the page, the capture keeps everything gathered before the press and the result says what happened — instead of saving the collapsed page and advising you it was small. -
Page content whose text merely looks like a load-more control — a forum topic titled “Load more button”, say — no longer produces a load-more suggestion:
load_allis offered only when the page has a real control to press. -
Offline logout warnings now print a runnable command for revoking the still-active key from another signed-in machine.
-
A “next” link that leads to a different document (a documentation site’s next chapter) no longer confirms a paginated collection, so
--pagesis not offered on such pages and a sweep will not merge the next chapter into this page’s records. Pagers whose next link steps to a later page position keep confirming. -
Preview guidance no longer points to a
page_sectionfield when the result does not provide one. -
A pagination sweep no longer stops with “a page re-served only records already merged” when a swept page simply produced no records — the sweep now continues to the later pages and names each record-less page in its disclosure (their text stays in the merged markdown). Separately, a page whose only real pagination evidence is a declared next document is no longer reported as a confirmed collection just because unrelated page content mentions “load more” — pagination is only read from links that actually carry a page number, as always documented.
-
Previews no longer tie two page-level disclosures to whether sections extracted. The single-subject withholding note now appears even when unrelated sections carry records (previously any extracted section silenced it), and zero-record results now include the page-outline block (previously the outline rendered only beside extracted sections — exactly where an empty result most needs to distinguish “not on the page” from “on the page, not extracted”).
-
The preview’s prose framing (“Most of this page’s text is prose, not records”) no longer lets navigation sections weigh against the prose: a link tree is the page’s map, rolled up in its own tier, not the records the prose is compared with. Long documentation pages whose header, sidebar, and footer link trees tipped the balance now frame honestly and point at the markdown.
-
Captures of virtualized feeds no longer accumulate repeated copies of the same content: when a page re-renders as it scrolls (a live thread, a windowed timeline), the capture now keeps the first serving of each piece of content instead of concatenating every intermediate state — so record counts over such pages reflect the page, not the number of times it re-drew itself.
-
Virtualized-feed captures no longer keep two copies of one item when the page serves it in different render states during the scroll — a pre-hydration placeholder whose content is the item’s source markup as escaped text beside the fully rendered item. When a feed declares item identity on its rows (
data-id), the reconstruction now keeps exactly one copy per item, preferring the fully rendered state wherever one was observed; an item the page only ever showed as a placeholder is still captured rather than dropped. Counts over such feeds stop double-counting, and escaped markup stops leaking into text. -
A reveal click that lands on a different page no longer hijacks the capture: when a “show everything” control turns out to route elsewhere — a careers page’s “See all articles” leading to the blog, say — the result now describes the page you asked for, not the destination.
-
assesson a Shopify collection page now checks the collection’s own product feed (/collections/<handle>/products.json) instead of the store-wide one, so the count it reports is the collection’s; if the collection feed doesn’t answer, the store feed is checked and the guidance names both. Probes and the fit now read the URL that served the page after redirects. -
A
--wherecomparison that can’t mean what it looks like now says so: a value that doesn’t read as a number (“13:00”), a field none of whose values do, or a value written in a different scale than the field’s unit (matching every row or none) each get one stderr line naming the field’s scale — instead of returning zero rows or the full table as if the filter had worked. Exit codes are unchanged.
0.18.0 — 2026-08-05
Section titled “0.18.0 — 2026-08-05”-
analog doctorchecks whether the environment can run Analog end-to-end — credential, outbound network, backend, and the built-in browser — and says what to do about whichever failed. It distinguishes an environment that blocks network access (common in sandboxed agent harnesses) from a site refusal or an Analog outage, and fetch failures that look transport-shaped now point to it. -
analog updateupgrades Analog through whichever channel installed it — uv tool, Homebrew, and pipx installs run the upgrade directly; a pip-managed environment gets the exact command printed instead of run.analog update --printemits the command alone for scripts. -
Added
analog login --no-browserfor interactive remote shells and containers: finish email verification in a browser elsewhere, then paste a one-time handoff at the hidden terminal prompt. The credential remains stored locally and is never displayed. -
analog describe -f jsonemits the field-stats as a JSON array of the selected sections — section index, label, kind, record count, and each field’s stats (type, source, multivalued, traits, hidden, numeric, unit, coverage, cardinality, sample) — completing the machine-readable form of the query workflow. -
analog history --json,analog find --json, andanalog distinct --jsonemit machine-stable JSON: history as an array of entries (handle, url, saved_at, name, sections, records — newest first), find as exactly the objects the Python surface’s.find()returns, and distinct as{value, count}pairs most frequent first (with--count, the bare number). Empty results are[], never prose. -
Page outlines now mark regions preserved in full-page markdown but not emitted as structured records as
markdown_only, distinguishing unstructured text from missing content. -
Results now present record and Markdown sections together in page order, so prose remains directly inspectable and searchable alongside extracted records.
-
Results disclose when a single-subject reading of a page was weighed and not returned: the new
subject_withheldfield carriesconflicting_subjects(the page presents more than one candidate subject),declaration_unverified(the page declares a subject the visible page doesn’t corroborate), orbelow_evidence_bar(too little verifiable evidence for a record we’d trust), and stays unset on pages where nothing was withheld. Empty-result previews state the reason in plain terms.
Changed
Section titled “Changed”-
Section.xpathnow returns an ordered list of exact locations when a section draws from several page regions, andSection.markdownrenders those regions together. Single-region sections still return one XPath; unresolved locations remainNone. -
Every upgrade nudge — the status floor and schema warnings, the sign-in failure hint, the wire-schema-skew error — now says
analog update, which resolves the right upgrade for the install it runs in. Previously the nudges named one channel’s command, which could be wrong advice in another channel’s install.
-
Checking whether the built-in browser is installed no longer prints stray teardown errors to stderr — the check reads playwright’s own metadata instead of starting its driver, so
analog doctor,analog login, andis_browser_installed()stay silent. An interrupted browser download now correctly reads as not installed. -
Corrected
analog exporthelp to describe--kindas selecting kinds present in a saved result; it no longer promises acontentkind that many results do not provide. -
analog feedback quality --helpnow states the 2,000-character note limit. -
analog help --helpnow answers like every other command instead of erroring, the exit-code table appears in every command’s--help(it previously showed on only some), andanalog whoamistates its--verifydefault. -
When
analog logoutcan’t reach the backend to revoke the key, the warning now states the consequence plainly — the key stays active server-side until revoked — and prints the exactanalog keys revoke …command, with this key’s own prefix, to finish the revocation from any signed-in machine. The local credential is still cleared either way. -
Markdown now preserves the extent of page-authored notes, warnings, and other advisory blocks, so following prose remains outside them.
-
Full-page Markdown now renders animated counters as one current value instead of concatenating every transition frame.
-
The page outline now names a labelled region that carried items but produced no records — a sidebar of opening hours, say — instead of folding it into the one-line structure summary, where a whole region could be missing from both the records and the output. Page chrome stays summarised as before.
-
A page whose only pagination evidence is a declared next document — the way a documentation page or blog post names its neighbour — is no longer reported as page 1 of a paginated collection, and no longer suggests a multi-page sweep that would merge a separate document into the result. The next link is still disclosed, and pages that confirm a collection keep the full disclosure and the sweep. Results carry the distinction as
collection_confirmed. -
analog get,analog history, andanalog opennow show the URL that served a page when a redirect changes its path, query, host, or port. -
Section-scoped Markdown exports now retain table column headings.
0.17.2 — 2026-07-27
Section titled “0.17.2 — 2026-07-27”- Added
analog quickstart, a network-free guide to connecting an account and making a first extraction from the shell or Python.
-
Markdown keeps authored bold and italic emphasis in normalized document views.
-
Browser calls made directly on an active
asyncioevent loop now teach the workingasyncio.to_threadintegration instead of suggesting an async SDK that does not exist. -
Browser captures complete collections from scrollable page regions instead of stopping at the initially visible rows.
-
Heterogeneous results now point a refused
to_dataframe()call to the working per-sectionstructured_content[i].to_dataframe()recovery instead of switching Python callers to raw records. -
analog find --fieldnow searches saved results with different section shapes without failing on sections that do not contain the selected field. -
Fit guidance recommends structured extraction without promising complete records before the result is available.
-
Multi-page fetching now advances through a page’s own numbered next-page link when no separately labeled next link is available.
-
Multi-page link sweeps now put each retained record’s source URL in
source_page_url.page_sweep.source_page_fieldidentifies the exact field when an authored name requires a collision-safe suffix. -
Pagination disclosures and fit guidance now use Python-native
pages=wording in SDK results and shell-native--pageswording in CLI output, with a valid page count instead of mixed syntax or a placeholder. -
Saved auto-mode results now retain their fit recommendation and preview guidance when reopened.
-
CLI private-address refusals now name the actionable
--allow-privateflag, while Python exceptions retainallow_private=True. -
Extraction quality improved across editorial, commerce, and reference pages.
-
Duplicate saved-result names now report what happened, offer recovery choices, and expose the conflicting name on
ResultNameInUseError.name. -
Python discovery on fit assessments, saved-result metadata, and Collections now leads with their documented data and operations instead of inherited compatibility methods.
-
Python’s built-in
help()on returned model classes now leads with their documented fields and operations instead of hundreds of inherited Pydantic internals. -
Runtime guidance now composes real saved-result references and uses prose instead of placeholder commands when a value must be chosen by the caller.
-
Saved-result failures now derive from
AnalogErrorwhile retaining their existingKeyErrororValueErrorbehavior. -
Saved-result schema errors now give Python-native recovery guidance in library calls while retaining command-specific guidance in the CLI.
-
Section.find(..., field=...)now raisesKeyErrorfor unknown field names instead of returning the same empty result as a valid search with no matches. -
Python’s
analog()now rejects unsupported mode values before fetching, extracting, or saving.
0.17.1 — 2026-07-26
Section titled “0.17.1 — 2026-07-26”-
Browse disclosures persist with the saved result.
result.browse_actions— the interactions the fetcher took or offered to reach the content (a reveal-all click, a “Load more” control present but not pressed) — now saves with the result and appears in the JSON envelope, soanalog openre-prints the sameBrowse:lines the original fetch showed and a cached result still discloses what the capture did and didn’t cover. It isNoneon artifacts saved before recording existed; those open exactly as before. -
analog statusleads with the fact you act on. The backend line now readsok (schema N, build xxxxxxxx): the wire-schema number — the compatibility contract — comes first, and the deploy identifier is shortened to eight characters under an honestbuildlabel (previously forty characters labeledversion, which promised something interpretable and delivered noise). Pastingstatusoutput into a report still pins the exact deploy. -
Package completion shows the public API.
dir(analog)now follows the curatedanalog.__all__surface, so IDE completion no longer mixes public classes and functions with incidental submodule names or theannotationsfuture-import marker. Existing imports are unchanged; this only cleans up discovery. -
historysays when. Each row now shows how long ago the result was saved (just now,5m,2h,3d,6w— the unitsrm --older-thantakes), and rows align whether or not a result has a friendly name. Two saves of the same URL used to be indistinguishable except by handle. -
Icon-drawn labels and legend terms render what the page declares. The declared-text contract icon-only controls and table cells already had now covers non-interactive elements too: a support level drawn as an icon, a legend term whose only content is an icon — anywhere the page declares the meaning as clipped text or a
titleattribute, the markdown renders the page’s own words instead of nothing. Visible text always wins, and nothing is ever invented. One refinement from the same contract: when a page renders an icon link and a text link to the same destination side by side, the markdown keeps one link instead of printing the name twice. -
A typo can’t pass as an empty result.
--where,--sort, andfind --fieldnow exit1when the named field exists nowhere in the result (previously a stderr warning with exit0, so a typo’d filter looked exactly like a true zero-match to anything reading stdout). Filtering or sorting on a field that only some sections carry is unchanged: known name, match-nothing / sort-last semantics. And a valid filter that matches nothing now still emits the CSV header row — “ran, zero matches” is visible output, not an empty string. -
Python
findrejects unknown fields too.result.find(pattern, field="...")now raisesKeyErrorwith the available names when no section carries that field, instead of returning[]as though the pattern had no matches. A field present in only some sections remains a valid restriction. -
The taught query runs. The
Query it:lineanalog getprints now composes a command that succeeds on its own result: on a multi-section page it projects fields every section shares, or scopes itself to one section when nothing is shared. Previously it could suggest a pooled export that export itself refused. -
Pooling remedies match the result. When
result.recordsor a pooled serializer refuses sections with different fields, its error always points at the single-section accessor and only suggestssections_by_kind(...)when the result actually carries kinds. The suggested calls name those real kinds; a kindless result no longer teaches a call that can only return an empty list. -
The divergent-record note names your handle. The review recipe it prints (
analog open … --complete) now carries the saved result’s actual handle instead of a literal<handle>placeholder — the suggested command runs as printed. -
Withheld areas are named. When extraction withholds a page region it couldn’t do justice to, the result’s preview now names each withheld region — its heading label when the page gave one, its location otherwise, with item count — wherever it sits in the page skeleton. Previously a region buried inside other non-extracted structure was disclosed only as an anonymous count, so an agent couldn’t tell whether its answer lived inside. Results saved without a page skeleton keep the count-only note.
-
assessrecognizes a saved result when it’s handed one. Passing a handle, name, orlatest(natural, since every other read command takes them — butassessmeasures live pages, so it takes a URL) now explains just that and names the saved result’s own surfaces (analog open/analog describe) plus its source URL for a live re-assessment. Other non-URL input gets a usage error teaching the expected shape. Previously both surfaced a fetcher scheme error with the fetch-failed exit code. -
Python separates invalid URLs from fetch refusals.
analog()andassess()now raiseValueErrorbefore fetching when their URL is not HTTP(S), including when the caller supplieshtml=; they never read afile://target. A well-formed private or loopback URL remains a policy refusal in theFetchErrorfamily, and its preciseUrlNotAllowedErroris now importable fromanalog.analog getreports a non-URL as a usage error instead of a failed fetch. -
Saved-result references are consistent in Python. The library’s
open, field mutation, metadata, update, rename, and delete functions now accept the same three forms as the CLI: a handle, friendly name, orlatest. A miss raisesResultNotFoundErrorexplaining those forms and pointing atanalog.history(); deleting a well-formed missing handle remains an idempotent no-op. Previouslyrenameminted names that most library functions rejected, anddeleteon one silently did nothing. -
One printed spelling per section id. Previews now say
section[N]everywheredescribeandfindalready did;get/openpreviously printed the longerstructured_content[N]for the same section. Input is unchanged: both spellings, bare indices, and labels remain accepted by every--section, and every--sectionflag’s help now teaches the printed spelling. -
describekeeps fields tellable-apart under a giant shared prefix. When every field in a section starts with the same long run (a table caption folded into each name can top 100 characters), the field column used to truncate all rows to one identical string. The shared prefix now prints once above the table and rows show the distinguishing tail (…_month,…_apr); a full field name is the printed prefix plus its tail. Sections with ordinary names are unchanged. -
Refusals teach only remedies you can run. When a pooled export is refused because sections have different shapes, the error now names
--kindonly when the result’s sections actually carry kinds (and lists them), and its--fieldsexample uses real columns the sections share — never placeholder names. Nothing changes about what is refused, only that every suggested way out works. -
analog export -f markdownnow works as an alias for-f md— the spelling the rest of the product uses when it talks about this output. -
One mistake, one exit code. Naming a field the result doesn’t have now exits
1everywhere (the same class as an unknown handle: the command was well-formed, the data lacks the name). Previouslyexport --fieldsreported it as exit2whiledistinctsaid1. Exit2remains reserved for malformed invocations. Both errors now also point atanalog describe <your ref>with the ref filled in — the suggested command runs as printed. A pooled export refused because sections have different shapes is the same class: it now exits1too, without the usage banner it used to print. -
dir()on extraction results and sections now shows their documented data, reading, and export surfaces. -
Runtime preview and describe truncation notes now include a runnable export command for the saved result.
-
diffanswers “did the content change?”, not just “did the shape change?”. Each matched section now reports record turnover — records present in only one of the two saves, compared by whole-record content — with up to three examples named by an identifying field when the section has one (a feed’stitle, a grid’sname). Two saves whose counts matched while the records rotated used to read as “nothing changed”. Records carry no stable key, so a record that changed any value counts once on each side; the schema and field-stat views are unchanged. -
The original
analog hintcrystal can now be replayed asanalog hint 001, andanalog hint --listshows available animation IDs. -
analog hint 002adds a traveling-defect animation to the local terminal-art gallery. -
analog hintnow draws the landing page’s emerald crystal in the terminal.
0.17.0 — 2026-07-25
Section titled “0.17.0 — 2026-07-25”Changed
Section titled “Changed”-
Extraction improved on platform status pages and grouped multi-day schedule pages.
-
Markdown now reflects the rendered page only. Content that was not displayed at fetch time — dismissed cookie dialogs, closed drawers and menus, hidden template and variant blocks — no longer appears in any markdown surface (
result.markdown, per-section markdown,-f mdexports). Markdown of template-heavy pages gets substantially smaller, with the visible content untouched; the document title is still included, andassess’s measured token estimate shrinks with it. -
Field schemas follow the page. A section’s
fieldsnow lists its fields in the same order as its records — the page’s own order for comparison and schedule sections, most-relevant-first elsewhere — instead of alphabetically, sodescribeand schema listings read in record order. Saved results keep the order they were saved with.
-
Icon-only controls render what the page declares. A status icon, an icon-drawn button, or an icon-only link whose meaning the page declares in its accessibility tree (
aria-label, SVG<title>) now renders that declared text in markdown — a status page’s component list readsAPM … Operationalinstead of a bare name, and an icon-only link keeps its destination with its declared text instead of disappearing. Visible text always wins over a label,aria-hiddencontent stays silent, and nothing is ever invented — the same contract icon-only table cells already had. -
Markdown discloses values the page draws only as icons. A comparison column whose values exist only as unnamed icons (a checkmark matrix, say) renders blank in any text form — indistinguishable from “no value”. Every markdown the CLI emits (
export -f md, per-section export,get/open --markdown) now carries the same withheld note the records preview shows: the affected columns, and that a clipped screenshot of the region will show them. A per-section markdown export also repeats the preview’s plain withheld-columns line, since its comparison table is built from the records.assess’s comparison guidance now points at the withheld note instead of a blanket “read the page as markdown”. Results without withheld columns are unchanged. -
Links in records resolve against the page that answered. When a fetch is redirected, the document’s relative links — record URL fields, and the pagination disclosure’s next link — now resolve against the URL that actually served the page, not the address you requested. In a
pages=Nsweep, each followed page resolves against its own serving URL, and the sweep’s page list names the pages as served. Previously a page served from another host produced record URLs anchored to the requested host. -
Comparison sections now print as the page authored them. A comparison section — a pricing or edition matrix with few records and many fields — used to preview as one unreadable line per sampled record, and its markdown lost the comparison entirely.
preview()andanalog export -f md --sectionnow render these sections in the page’s authored orientation: features as rows, one column per record (headed by each record’stable_columnvalue), with the page’s own row-group headings preserved.records,fields, and every structured export (json/csv/yaml) are unchanged; ordinary sections keep the record orientation. -
Section ids round-trip.
--section(ondescribe,export,distinct, anddiff) now accepts the id forms results print —structured_content[N]andsection[N]— alongside labels, bare indices, andall. A heading label shared by several sections now selects all of them instead of silently the first. -
Markdown tables now carry the declared text of icon-only cells. A comparison matrix that draws boolean cells as icons (checkmarks) declares their meaning accessibly (
aria-label, SVG<title>); the markdown view now renders those declared words instead of empty cells, so a feature column no longer reads as if no plan offers it. Cells with no declared text anywhere remain empty, andaria-hiddencontent stays silent.
-
Results now name the URL that actually served the page. When a fetch is redirected,
result.resolved_urlcarries the final URL (it isNonewhen you supplied the HTML yourself), and it persists with the saved result, so an agent can cite the source it actually read. When the serving host differs from the requested one — the case that changes a citation — the fetch log says so andanalog historynames the serving URL on that result’s row. The requested URL remains the result’s identity everywhere else. -
Section.locate_in(html)— find a section in a different rendering of its page. A result’s xpaths address the document the extraction read; a live DOM after scripts ran, or a re-fetch, can shift positional paths.locate_infinds the section there by its own record values and returns matches with xpaths valid in the document you passed. One match is a confident location; several mean the page holds several full copies of the content (a duplicate widget, a mobile/desktop double render) — pick with what only you can see, such as which copy is visible. An empty list means the section couldn’t be pinned — never a silent guess at a wrong element. The preview’s visual-withheld hint now points at it for screenshot clips. -
Results now carry the page as Analog read it.
document_viewships the exact document the response’s xpaths address, as a compact verified patch against the HTML you submitted;attach_htmlreconstructs it automatically. Section, outline, and navigation xpaths now resolve reliably on pages where a plain re-parse used to come up empty or silently land on the wrong element — so per-section markdown stops coming back empty on those pages, and markdown no longer includes markup that plays no part in what a reader sees. When the view is absent (an older service) or fails verification, everything degrades to parsing the submitted HTML as-is. Wire schema 24. -
page_sweep.divergent_sections— a multi-page sweep now discloses when a later page or batch contributed a section whose fields matched no section captured earlier: the count arrives inpage_sweep, the affected section carries amerge_notesaying why it stands alone, and the preview adds a sentence warning that no single field spans every merged record. A sweep that fragments is never presented as one uniformly queryable collection.
0.16.1 — 2026-07-23
Section titled “0.16.1 — 2026-07-23”- Pages that fold detail behind in-page disclosure links now report the
available content under
browse_actions;expand_all=Truereveals it before capture. Ordinary navigation links remain untouched.
0.16.0 — 2026-07-23
Section titled “0.16.0 — 2026-07-23”-
Feedback submissions now return a durable receipt.
Client.submit_feedback(...)exposes the opaquereport_id, and bothanalog feedbackcommands print it asReceipt:so a submission can be cited during later triage. The ID is not a report-reading endpoint. -
Built-in browser recipes are now replayable.
Browser.reciperecords every serializable setting that shaped a fetch, andBrowser.from_recipe(...)reconstructs it. New browser-backed saved results retain that recipe infetch_settings.browser_recipe; older saves remain readable and state that the exact recipe is unknown. -
Compatible page areas can now arrive as a
Collection.result.collectionspreserves the physicalSectionindices and identity field while providing one canonical local records view. Repeated editorial placements remain available through the member Sections; Collection records select one whole authored placement and never fuse values across placements. A complete Collection powersresult.records, CSV, YAML, and DataFrame export without duplicating repeated placements. Wire schema 23. -
page_sweep.cross_section_repeats— a multi-page sweep now discloses when the same records appear in more than one section of the merged result (two layouts of one collection that stayed separate sections), matched by a shared per-record URL. The preview adds a sentence when it’s nonzero, so a “duplicates dropped” count is never read as “all duplicates handled.”
Changed
Section titled “Changed”- The serializer family is now methods, matching
to_dataframe():result.to_json(),result.to_yaml(),result.to_csv()replace theresult.json/result.yaml/result.csvproperties. A serialization renders output (to_json()includes the full markdown rendering), so it reads as the call it is — and.jsonno longer collides with Pydantic’s inheritedjson()method, which broke generic Pydantic tooling’s expectations. No compatibility aliases; update call sites by addingto_and parentheses..markdownstays a property: it is the page’s content, cached after first render, not an export.
-
The built-in browser now preserves visible page content rendered inside open web-component boundaries instead of returning an empty host element.
-
Finite virtualized lists no longer lose rows that appear only between the first and final rendered windows during scrolling.
-
load_moreguidance now refers only to content controls. Generic “More” controls in site navigation, headers, footers, menus, and dialogs are no longer offered or clicked. -
Complete static pages no longer report incomplete feed coverage. A
windowed_feeddisclosure now means the browser restored content that the page removed during scrolling. -
Pagination guidance now requires credible evidence that the current collection continues. Unrelated
page=1links and carousel slide counts no longer produce whole-page pagination claims. -
The built-in browser now captures complete collections when a page initially shows only a preview and reveals the rest through an in-page control.
-
Extraction quality improved on complex multi-section pages, with more complete record coverage and more accurate Section boundaries.
-
Comparison results now keep their row and column identities together. Authored blank values are returned as
null, visual-only values are named inwithheld_columns, and comparisons that cannot be preserved as records point to markdown instead. Fit guidance recommends structured comparison only when both axes are named and no comparison fields are withheld. Wire schema 22. -
Transient gateway failures no longer spend daily quota more than once for one extraction. Retries share the logical extraction’s quota accounting without retaining the URL, page content, or result server-side.
-
Pages with a continuously-updating DOM (live clocks, ad slots, client-side hydration) now capture much faster in the built-in browser. Such a page never sits perfectly still, so the pre-extraction scroll pass used to run to its internal cap before giving up; it now recognizes a settled page promptly and captures the same content in a fraction of the time.
-
The first help screen (
analog --help) now shows the recommended one-call Python usage —from analog import analog; analog("https://example.com")— instead of the lower-level hosted client.
0.15.0 — 2026-07-18
Section titled “0.15.0 — 2026-07-18”-
pages=Nnow captures script-driven pagination. In-page batches are merged under the same result contract as link-followed pages.page_sweep.mechanismis"in_page",batches_capturedreports how many batches arrived, andpagination.in_page_controlsdiscloses the available controls. If a control navigates away, captured batches are kept and the stop reason iscontrol_navigated. See the fetching guide. -
pagination.stated_totalreports the page’s own collection count. It is the site’s claim, not Analog’s record count, so both remain visible when they differ. -
Sticky field orders.
analog reorder-fields <ref> FIELD...andanalog.reorder_fields()put named fields first on a saved result and remember the order for later fetches of the same URL.--onceorsticky=Falseapplies it only to the current result; replayed rules appear inapplied_orders. See work with results. -
Comparison tables arrive as one joined section. Compared plans or models are records keyed by
table_column; grouped table fields expose their heading throughFieldInfo.table_group. Wire schema 17. -
assessreportsnavigation_probe. The fit check can show top-level destinations from the already-fetched page before an extraction is spent. See the fit-check guide. -
section.withheld_visualidentifies glyph-only columns. The disclosure names the columns and directs the reader to a clipped screenshot rather than guessing what a glyph means. Wire schema 16. -
Results now carry the page’s navigation and outline.
result.navigationcontains labeled link trees;result.outlinedistinguishes extracted, non-extracted, and structural regions and replacespage_sections. Both appear inpreview()and are searchable withfind(). -
Sections expose
Section.label_url. When a section heading has one unambiguous destination, the absolute URL points to the fuller page; otherwise it remains empty. -
Greenhouse fit checks disclose the board’s public jobs feed. The feed’s own current total is reported as
result.fit.item_count, the provider-neutral replacement forproduct_count.
Changed
Section titled “Changed”-
Uniform multi-page sweeps use less service compute while preserving the same results. Wire schema 19.
-
Outline outcomes are more specific. Empty repeated regions use
no_fields_detectedorno_emittable_fields; overlapping regions useoverlapping_area_extractedand may point to the shipped section throughsuperseded_by. The catch-allnot_selectedis retired. Wire schemas 18, 20, and 21. -
The feedback vocabulary gains
garbled_markdownfor text that returned malformed or out of order. -
Extraction quality improved on script-driven listings, comparison tables, navigation-heavy pages, and table-heavy pages.
-
--load-allreaches a wider range of clearly labeled load-more controls. -
Bot-protection challenges no longer show pagination or fit advice derived from the challenge page.
-
assesspreserves the page’s most prominent navigation destinations when a menu contains many links. -
Automatic browser interactions no longer replace the requested page with a navigated destination. If the captured address still changes, the result discloses both addresses.
-
analog findsearches navigation and outline entries as well as records.--fieldcontinues to restrict the search to records. -
export --fieldsnow rejects unknown fields with the available field names and guidance for heterogeneous sections.
0.14.0 — 2026-07-15
Section titled “0.14.0 — 2026-07-15”analog docs,analog contact. Pointer commands:docsprints the documentation URL (pure output —analog get $(analog docs) --mode localreads the docs in the terminal);contactprints the team email and points atanalog feedbackfor reports that go straight into triage.
Changed
Section titled “Changed”- Analog fetches only public web addresses by default.
getandassessnow decline local and private-network hosts and non-web schemes likefile:, so a page can’t steer a fetch atlocalhost, an internal address, or the local disk. Pass--allow-private(allow_private=True) to reach a dev server or intranet host on purpose. Paginated sweeps follow only links on the page’s own host.
- Signed-out
analog getsays what it’s doing. Without a sign-in,getreturns the page’s markdown (mode=local) — it now says so on stderr and names the fix: runanalog loginfor structured records. Previously the downgrade was silent, and a first run beforelogincould read as “Analog returns prose.” - Numeric columns survive renames and narrowed exports. Renaming a
numeric field (including swaps) keeps its normalized numbers under
the new name, and
export --envelopere-aligns per-record numbers and structural-outlier positions after--where/--sort/--limit/--fields. - Detail toggles inside a card’s own header now expand. With
--expand-all(expand_all=True), per-item disclosure toggles that sit inside a section’s header are recognized and opened, so their detail is captured — previously only toggles outside a header were. - A deactivated account ends with a clear terminal error — the commands name the state and stop, instead of a generic sign-in failure.
- Hardened the fetch and sign-in paths for launch. The site-feed probe streams and size-caps what it downloads, so a hostile server can’t balloon memory; and sign-in, credential handling, and result parsing got a robustness pass.
0.13.0 — 2026-07-11
Section titled “0.13.0 — 2026-07-11”analog help— the subcommand form of--help.analog helpprints the top-level help;analog help getprints exactly whatanalog get --helpprints; nested paths work (analog help feedback quality).-hnow works everywhere as the short form.
Changed
Section titled “Changed”- The feedback label vocabulary is now version 3 — every label
names what you observed in the result (
no_records_found,misfielded_values,page_chrome_in_records,wrong_content_extracted, …). At open launch the vocabulary freezes to additive-only. - Wire schema is now 13.
Section.items_detectedcounts the repeating items the page shows in the section’s area. Results saved by earlier versions reopen unchanged.
0.12.1 — 2026-07-10
Section titled “0.12.1 — 2026-07-10”Paginated collections can be swept —
analog(url, pages=N) (CLI: --pages N) follows the site’s own next
links, politely, capped at 50, and discloses its coverage on
result.page_sweep. Per-item detail toggles can be expanded
(expand_all=True / --expand-all), and long feeds no longer lose
records to scrolling. Also: saved results remember how they were
fetched; virtualized tables disclose the page’s own declared size;
result.rate_limit gains the daily quota; the package ships
py.typed; analog assess mentions a site’s sitemap when robots.txt
lists one; back-to-back extractions got dramatically faster (one warm
browser per process); legacy-encoded pages decode correctly on the
plain-GET path; and a long list of contract, disclosure, and
robustness fixes landed alongside. Extraction quality improved on
discussion threads (records gained a depth field), menu-styled
pages, and multi-paragraph content.
0.12.0 — 2026-07-06
Section titled “0.12.0 — 2026-07-06”analog feedback — quality reports with standardized labels, and
feature requests. Extraction URLs are never logged; a quality report
includes the URL you explicitly submit and never page content.
result.to_dataframe(); filtered history deletion (analog rm --older-than 30d); store disk-usage reporting; partial-coverage
disclosures for paginated pages; the credential moved from the OS
keychain to a local owner-only file (run analog login once after
upgrading); a lighter install; and the fit check reworked around
measured facts and task guidance. Extraction quality improved on
store and listing pages, and field-naming contracts firmed up —
price is always the pay-now price, with price_excl_vat and
review_count beside it where pages show them.
0.11.0 — 2026-07-04
Section titled “0.11.0 — 2026-07-04”result.records; analog find; section-addressable markdown;
field renaming as a library operation; guessed-name and
page-truncation disclosures; withheld-column warnings; documented
exit codes on every command. Extraction quality improved on complex
HTML tables.