Skip to content

Rolling main documentation

Built from commit 077b43bde250f7cb0ff0e0aba6f03959cbed4e25. Content may describe unreleased behavior.

Trusted Server CLI ​

The Trusted Server CLI binary is ts. It is a host-target operator tool for configuration, page audits, and EdgeZero-backed lifecycle commands.

Command index ​

The table summarizes native --help output on Linux and macOS. Platform-only commands are shown explicitly.

CommandAvailabilitySummaryUsage
tsLinux + macOSTrusted Server CLIts <COMMAND>
ts active-versionLinux + macOSPrint the currently active deployment version for a target adapterts active-version --adapter <ADAPTER> --service-id <SERVICE_ID>
ts auditLinux + macOSBrowser-backed page and ad-template auditsts audit [COMMAND]
ts audit ad-templatesLinux + macOSVerify configured ad-template slots against live page evidencets audit ad-templates <COMMAND>
ts audit ad-templates generateLinux + macOSScrape a live page's GPT slots and update the config's [creative_opportunities] slots in placets audit ad-templates generate [OPTIONS] <URL>
ts audit ad-templates verifyLinux + macOSVerify ad-template slots for one or more live URLsts audit ad-templates verify [OPTIONS] <URLS>...
ts audit generateLinux + macOSBootstrap a draft Trusted Server config + JS asset audit from a live pagets audit generate [OPTIONS] <URL>
ts audit pageLinux + macOSAudit a single page and print a read-only summaryts audit page [OPTIONS] <URL>
ts authLinux + macOSSign in / out / status against an EdgeZero adapterts auth <COMMAND>
ts auth loginLinux + macOSSign in (wrangler login / fastly profile create / spin cloud login)ts auth login --adapter <ADAPTER>
ts auth logoutLinux + macOSSign out (wrangler logout / fastly profile delete / spin cloud logout)ts auth logout --adapter <ADAPTER>
ts auth statusLinux + macOSShow the current session (wrangler whoami / fastly profile list / spin cloud info)ts auth status --adapter <ADAPTER>
ts buildLinux + macOSBuild the project for a target adapterts build --adapter <ADAPTER> [ADAPTER_ARGS]...
ts configLinux + macOSTrusted Server app-config commandsts config <COMMAND>
ts config ad-templatesLinux + macOSDiagnose server-side ad-template configuration and path matchingts config ad-templates <COMMAND>
ts config ad-templates checkLinux + macOSAssert that a page path or URL matches the expected slot setts config ad-templates check [OPTIONS] <--expected-slot <ID>|--expect-no-slots> <PATH_OR_URL>
ts config ad-templates explainLinux + macOSExplain why a page path or URL would or would not run the ad stackts config ad-templates explain [OPTIONS] <PATH_OR_URL>
ts config ad-templates lintLinux + macOSValidate ad-template config and summarize deploy-time implicationsts config ad-templates lint [OPTIONS]
ts config ad-templates matchLinux + macOSShow creative opportunity slots matching a page path or URLts config ad-templates match [OPTIONS] <PATH_OR_URL>
ts config diffLinux + macOSDiff trusted-server.toml against the live EdgeZero configts config diff [OPTIONS] --adapter <ADAPTER>
ts config gcLinux + macOSReclaim orphaned chunk entries leaked from prior oversized pushests config gc [OPTIONS] --adapter <ADAPTER>
ts config initLinux + macOSInitialize a Trusted Server config file from the example templatets config init [OPTIONS]
ts config pushLinux + macOSPush trusted-server.toml as a blob envelope through EdgeZerots config push [OPTIONS] --adapter <ADAPTER>
ts config validateLinux + macOSValidate edgezero.toml and the typed Trusted Server configts config validate [OPTIONS]
ts deployLinux + macOSDeploy the project through a target adapterts deploy [OPTIONS] --adapter <ADAPTER> [-- <ADAPTER_ARGS>...]
ts devLinux + macOSLocal developer tools (e.g. the macOS-only production-hostname proxy)ts dev
ts dev proxymacOS onlyRun the local production-hostname dev proxy (macOS only)ts dev proxy [OPTIONS] [COMMAND]
ts dev proxy camacOS onlyManage the per-machine dev CAts dev proxy ca <COMMAND>
ts dev proxy ca installmacOS onlyAdd the CA to the OS trust store (macOS login keychain)ts dev proxy ca install
ts dev proxy ca pathmacOS onlyPrint the per-machine CA certificate pathts dev proxy ca path
ts dev proxy ca regeneratemacOS onlyRegenerate the per-machine CA (invalidates prior trust)ts dev proxy ca regenerate
ts dev proxy ca uninstallmacOS onlyRemove the CA from the OS trust storets dev proxy ca uninstall
ts healthcheckLinux + macOSProbe a deployed version until it reports healthyts healthcheck [OPTIONS] --adapter <ADAPTER> --domain <DOMAIN> --service-id <SERVICE_ID> --version <VERSION>
ts prebidLinux + macOSTrusted Server Prebid commandsts prebid <COMMAND>
ts prebid bundleLinux + macOSGenerate a local external Prebid bundle and update config metadatats prebid bundle [OPTIONS]
ts provisionLinux + macOSProvision platform resources through a target adapterts provision [OPTIONS] --adapter <ADAPTER>
ts rollbackLinux + macOSRoll a service back to a previously active deployment versionts rollback [OPTIONS] --adapter <ADAPTER> --service-id <SERVICE_ID> --version <VERSION>
ts serveLinux + macOSServe the project locally through a target adapterts serve --adapter <ADAPTER>

Install from source ​

From the repository root, install the ts binary with the workspace Cargo alias:

bash
cargo install-cli

The alias runs cargo install --path crates/trusted-server-cli --bin ts --locked --force. Because it does not pass an explicit --target, Cargo builds the CLI for your current host platform. The binary is installed into Cargo's bin directory, usually ~/.cargo/bin; make sure that directory is on your PATH.

For example, add Cargo's bin directory to your current shell session:

bash
export PATH="$HOME/.cargo/bin:$PATH"

Verify the install:

bash
ts --help

Common workflow ​

bash
ts config init
# Edit trusted-server.toml
ts config validate
ts auth login --adapter fastly
ts provision --adapter fastly
ts config push --adapter fastly
ts serve --adapter fastly

Configuration commands ​

Create a starter Trusted Server config:

bash
ts config init

config init accepts --app-config <path> and the compatibility alias --config <path>.

Validate a local config before pushing it to platform storage:

bash
ts config validate

Push Trusted Server config through EdgeZero:

bash
ts config push --adapter fastly

config validate, config diff, and config push use EdgeZero's typed app-config loader. By default that loader applies TRUSTED_SERVER__... environment overlays before validation, comparison, and blob creation. The overlay only overrides leaves already present in the TOML; add newly introduced fields to existing configs before relying on their overrides. Pass --no-env for file-only operation. See Configuration for migration and rollback guidance.

config diff, config push --dry-run, and the confirmation preview shown by config push render resolved app-config values. Store-backed secret fields are key names at this stage, but deliberately inline values such as trusted_client_ip.shared_secret can appear verbatim. Use --no-diff for a push when terminal output or CI logs are not an approved place for inline configuration secrets; --no-diff does not change validation or publication.

config push publishes a single EdgeZero BlobEnvelope containing the validated Trusted Server settings JSON. This blob model is intentional because full Trusted Server configs can exceed Fastly limits when split into one config-store entry per setting.

Reclaim orphaned chunk entries leaked from prior oversized pushes:

bash
ts config gc --adapter fastly

Without --yes, config gc only previews: it reports what it would delete and deletes nothing. --dry-run states that intent explicitly and conflicts with --yes. To actually delete, pass --yes together with --older-than <window> (s/m/h/d suffixes, e.g. 7d; a bare number means seconds):

bash
ts config gc --adapter fastly --yes --older-than 7d

config gc sweeps every root in the selected physical store, so --older-than is a safety assertion about the whole store: nothing in it changed within the window and no writer is targeting it. Unlike the other config subcommands, gc never loads the typed app config; its --no-env flag instead ignores EDGEZERO__STORES__CONFIG__<ID>__NAME when resolving which physical store to sweep, and --store <id> overrides the manifest's config-store id outright. Both change which store gets swept, so on a destructive run check the store id gc reports before passing --yes.

Diagnose ad-template configuration ​

The static ts config ad-templates commands evaluate local configuration without launching a browser:

CommandPurpose
lintSummarize configuration and report invalid slot page patterns.
match <path-or-url> [--details]List matching slots; --details includes divs, paths, formats/providers.
check <path-or-url> --expected-slot IDAssert the exact matching slot set; repeat --expected-slot.
check <path-or-url> --expect-no-slotsAssert that no slots match.
explain <path-or-url>Print every runtime ad-stack gate and its final yes/no verdict.

check --allow-extra-slots permits matches beyond the repeated --expected-slot values. It conflicts with --expect-no-slots.

explain models a GET navigation with consent allowed by default. Use --method <METHOD>, --non-navigation, --prefetch, --bot, or --consent-denied to model another request. Provider configuration is printed as a separate advisory; it does not change the runtime gate verdict.

Every ts config ad-templates ... and ts audit ad-templates ... command accepts the same config-location flags:

FlagBehavior
--app-config <PATH>Read this app config instead of deriving <app.name>.toml from the manifest.
--manifest <PATH>Read this manifest; defaults to edgezero.toml.
--no-envDisable TRUSTED_SERVER__... overlays for read-only commands.

The mutating audit generator always edits file-backed values and never writes environment-only overlays into TOML, including during --dry-run.

For CI-oriented assertions, exit code 0 means the assertion passed, 1 means the command ran and found drift (config ad-templates check or audit verification with --strict), and 2 means argument parsing, configuration, browser launch, or another tool operation failed.

Lifecycle commands ​

Lifecycle commands delegate to the selected EdgeZero adapter:

bash
ts auth login --adapter fastly
ts build --adapter fastly
ts provision --adapter fastly
ts deploy --adapter fastly
ts serve --adapter fastly

ts deploy accepts --staging (Fastly only) to build and upload a staged draft version cloned from the active one instead of activating a production deploy. Adapter passthrough arguments must now follow a -- separator; unknown flags before -- (including the renamed-away --stage) are rejected at parse time rather than forwarded. This is a change: passthrough args previously worked without the separator, so existing runbooks and CI jobs that pass adapter flags directly need the -- added:

bash
ts deploy --adapter fastly --service-id <service-id> --staging
ts deploy --adapter fastly -- --comment "release"

A staged deploy only redirects the staged version's config selector at the <logical-store-id>_staging key — it does not copy the production config blob there. Push the staged config before probing the staged version:

bash
ts config push --adapter fastly --staging
ts config diff --adapter fastly --staging

The staged version resolves its app-config key through the version-linked edgezero_runtime_env store. After ts config push --staging, the staged binary reads <logical-store-id>_staging while the active production version continues to read the production key.

--staging on config push / config diff writes and compares the <logical-store-id>_staging key in the same store. It is mutually exclusive with --key: the staging key is derived from the store's logical id, so an explicit key would be written where nothing reads it.

Inspect and verify deployments with the deploy lifecycle commands. All three are Fastly-only — the axum, cloudflare, and spin adapters reject them:

bash
# Capture the production rollback target BEFORE deploying: after a deploy this
# prints the NEW version, and Fastly keeps no record of which version was live
# before it, so the target is then unrecoverable.
ts active-version --adapter fastly --service-id <service-id>

# Probe a deployed version until it reports healthy. `<version>` is the version
# the deploy activated; pass `--service-id` to `ts deploy` and it emits that as
# a machine-readable `version=<N>` line.
ts healthcheck --adapter fastly --service-id <service-id> \
  --version <version> --domain edge.example

# Re-activate the version captured before the deploy
ts rollback --adapter fastly --service-id <service-id> \
  --version <bad-version> --rollback-to <previous-version>

Capture the rollback target before mutating production with ts active-version, or use an orchestration layer that captures the same previous-version value before deployment. ts deploy cannot reconstruct that value after the active version changes.

healthcheck probes / by default (--path overrides) and makes 3 total attempts — not 3 retries after a first try — with a 5 second delay between attempts and a 10 second per-attempt timeout (--retry, --retry-delay, --timeout). With --staging it resolves the staged version's IP from the service id and probes that instead of the production endpoint.

rollback cannot infer the production rollback target: Fastly exposes no metadata to tell a previously live version from a staged one, so pass the version to re-activate via --rollback-to. With --staging, it deactivates the staged --version instead and needs no --rollback-to.

Audit a public page ​

ts audit loads a public page in a fresh headless Chrome/Chromium session, collects rendered JavaScript asset evidence, detects known Trusted Server integrations, and writes local draft artifacts.

Chrome or Chromium must be installed locally. The command checks common PATH names and standard macOS/Linux install locations.

bash
ts audit generate https://publisher.example

By default, the command writes:

FilePurpose
js-assets.tomlJavaScript asset inventory, detected integrations, counts, and warnings.
trusted-server.tomlDraft Trusted Server config based on the starter template and final URL.

The generated config is a draft. Review it, replace placeholders/secrets, adjust publisher-specific settings, then run:

bash
ts config validate

The draft also fills [integrations.js_asset_proxy] with disabled third-party script candidates from the audit. These entries are inventory only: they do not register routes or rewrite HTML until you set integrations.js_asset_proxy.enabled = true and change individual assets[].proxy values to "enabled" or "blocked". Some candidates may be runtime-injected scripts; JS Asset Proxy only rewrites matching script src URLs present in HTML processed by Trusted Server.

If a config already exists, avoid overwriting it:

bash
ts audit generate https://publisher.example --no-config

Use custom output paths when reviewing artifacts first:

bash
ts audit generate https://publisher.example \
  --js-assets audit/js-assets.toml \
  --config audit/trusted-server.toml

Use --force only when replacing existing output files is intentional:

bash
ts audit generate https://publisher.example --force

The legacy ts audit <url> form remains a compatibility alias for artifact generation. New automation should use ts audit generate <url>.

Generate ad-template slots from a live site ​

ts audit ad-templates generate <url> discovers the publisher's ad slots and rewrites the [creative_opportunities] slot array in trusted-server.toml in place, preserving every other section and comment.

bash
ts audit ad-templates generate https://publisher.example/

It samples the site rather than a single page. Ad slots repeat per site section, so the crawl is sized by the publisher's taxonomy — a dozen sections — not its catalogue:

  1. Load the requested page and read its links and, from robots.txt, its sitemap.
  2. Group both into candidate sections, keeping one landing page and one article per section.
  3. Load those pages, recording each slot's div, sizes, and GAM ad-unit path.
  4. Reconcile every slot across the pages it appeared on.
  5. Infer a {section} ad-unit template if the evidence proves one.
  6. Verify the result loads, then write it.

What it writes ​

Given a site whose ad units track the section, the run produces:

toml
[creative_opportunities]
gam_network_id = "99999"
section_root = "homepage"
section_segment = 0

[[creative_opportunities.slot]]
id = "ad-header-0"
div_id = "ad-header-0"
gam_unit_path = "/{network_id}/example/{section}"
page_patterns = ["/", "/deals", "/deals/*", "/news", "/news/*"]
formats = [{ width = 728, height = 90 }]

Each section contributes two patterns. * crosses / in this glob dialect, so /news/* matches /news/a/b but not the bare /news landing page; emitting only the star form would drop the landing page from the slot.

Sizes are unioned across pages, so a format that renders only on articles survives alongside the homepage's.

When it keeps literal paths, and when it refuses ​

A wrong ad-unit template makes the publisher bid against inventory that does not exist, so the command prefers a narrow literal path over a plausible guess.

SituationResult
Only one page was crawledLiteral path. One observation cannot distinguish a literal from a template.
The ad unit never varied by sectionLiteral path.
A section's slug is not derivable from its URL (/site-news requesting .../sitenews)The slot is omitted; the note lists the ad-unit paths it used and says none generalized.
No crawled page lacked a section segment, so section_root is unwitnessedNo template is written and the reason names the crawl gap. A slot that merely never appears on the root (a sidebar, an in-article unit) still templates, borrowing the section_root another slot witnessed; a note says so.
Two path segments could both be the sectionNo template; the ambiguity is reported.
The ad unit varies by device, geo, or anything the URL cannot supplyThe refused slot is omitted and the reason is written as a note.
Crawled pages report different GAM network idsThe run fails; the pages are not one property.
More than a quarter of crawled pages return no slotsThe run fails. That is the signature of bot protection serving challenge pages, and writing from it would silently narrow the slot set.
Several live elements normalize onto one div-id prefixThe whole group is omitted, on every page of the crawl. A prefix resolves to at most one element and the exact ids change per render; the prefix is named in a note.
A per-render token sits before the placement part of a div idThe slot is omitted from a single observation and the family prefix is named in a note; no stable prefix identifies one element.
A crawled page redirects off the audited originThe page is skipped on that profile, its path is named in a note, and it stops counting toward profile coverage. There is no override for generation; another site's evidence is never folded into the config.

Every run checks that the config it produced still loads before replacing the file, and --dry-run runs the same check — a clean preview is evidence the config loads, not just that it parses. Dry-run stdout is a zero-context unified diff containing only the managed creative-opportunity fields; notes and refusal reasons go to stderr, so unrelated config and secrets are not printed. Crawl progress also goes to stderr, one line per phase and page — for example Auditing desktop [2/17]: /news. Progress renders the path only, never the origin, userinfo, query, or fragment, and there is no flag to suppress it. A --dry-run that changes nothing says so on stderr too, leaving stdout an empty diff.

Bounding and steering the crawl ​

bash
# Cover more of a large site.
ts audit ad-templates generate https://publisher.example/ --max-sections 20 --max-pages 41

# Audit exactly one page, as earlier releases did.
ts audit ad-templates generate https://publisher.example/ --max-pages 1

# Trigger lazy-loaded inventory on every crawled page.
ts audit ad-templates generate https://publisher.example/ --scroll

# Set the patterns yourself; this disables pattern inference entirely, and the
# run fails outright if any slot's template had to borrow section_root.
ts audit ad-templates generate https://publisher.example/ \
  --page-pattern '/' --page-pattern '/news' --page-pattern '/news/*'

# Preview without writing.
ts audit ad-templates generate https://publisher.example/ --dry-run

Re-running merges into the existing slots: a slot seen again keeps its hand-tuned fields and gains this run's patterns and newly observed formats, and a hand-written gam_unit_path template is preserved. A configured div_id is matched exactly when the crawl observed that exact id; it is treated as a runtime prefix only when it was never observed as a literal element, so a configured ad-sidebar-1 no longer absorbs a discovered ad-sidebar-10 — the sibling is appended as its own slot, and when the parent carried a floor price, targeting, or provider settings, a stderr note names the split because the new slot does not inherit them. A prefix that does claim several discovered divs is named in a stderr note, because the runtime resolves a prefix to at most one element. --replace discards existing slots instead, which also discards any template you wrote by hand.

--scroll performs the same deterministic stepped scroll on every page and device profile after the initial settle, then waits for the page to settle again before collecting evidence. It is opt-in because it increases crawl time, ad requests, and publisher-page side effects.

During a normal merge, configured slots missing from the current crawl are preserved and named in a stderr note. Absence is not proof that a slot is stale: the crawl may have missed a page type, device target, or lazy-loaded placement. Review coverage and re-run with --scroll when appropriate. Only use --replace when intentionally pruning every slot the run did not rediscover.

A slot that never appeared without a section segment can borrow a section_root witnessed by another slot only while its patterns are derived from the paths where it was observed. If --page-pattern would override those patterns, generation fails and names the affected div ids; remove the explicit patterns so the safe per-slot patterns can be derived.

A merge refuses to change the section policy that preserved {section} slots were written against. If the config has a non-empty section_root, an inferred root or segment mismatch fails and asks for --replace as an explicit migration. An explicitly configured section_segment is preserved even when section_root is unset. When the root is unset and the segment is either unset or agrees with inference, the first merge adopts the inferred root and makes the otherwise unloadable {section} config valid.

Locale-prefixed sites are inferred at their observed section depth. Only real ISO 639-1 language codes are read as a locale prefix, so a two-letter section root such as /tv or /us keeps sections at the first segment. For example, /en/news/story can produce section_segment = 1; generated patterns retain the locale prefix (/en/news and /en/news/*). The crawler never invents an unwitnessed locale or section.

Behind bot protection, pass a valid clearance cookie. The crawl reuses one browser session, so clearance earned on the first page carries to the rest, and --page-delay-ms spaces the requests — an unpaced crawl is both discourteous to the origin and likelier to be challenged partway through:

bash
ts audit ad-templates generate https://publisher.example/ \
  --cookie '<NAME>=<VALUE>' --page-delay-ms 1500

Some origins refuse a headless browser outright regardless of the cookie. --headful runs a visible one, which is also the quickest way to see whether a challenge is being shown:

bash
ts audit ad-templates generate https://publisher.example/ --headful

Publishers gate slot definition behind their consent platform, and the audit runs in a throwaway browser profile with no consent cookie. Left alone, such a site defines no slots at all and looks identical to a site with no ad stack.

The crawl therefore answers the two IAB interfaces every compliant platform exposes — TCF v2 and US Privacy — as a consenting, out-of-scope reader, before any page script runs. This changes only what the audit browser sees; it does not affect the publisher's own readers. Pass --no-assume-consent to observe the un-consented page instead.

When a page still yields no slots, the run reports GPT's observable state — whether the library reached apiReady, how many queued commands never drained, how many scripts ran. An empty slot registry has several very different causes, and that line distinguishes them.

Auditing a production hostname served locally ​

ts dev proxy serves a production hostname from a local Trusted Server. Auditing through it keeps the page's origin, cookie scope, and any origin checks in the ad stack matching production rather than localhost:

bash
ts dev proxy --map www.publisher.example=127.0.0.1:7676 --upstream-plaintext --rewrite-host

ts audit ad-templates generate https://www.publisher.example/ \
  --browser-proxy 127.0.0.1:18080 --danger-accept-invalid-certs

--danger-accept-invalid-certs covers the proxy's MITM certificate when the throwaway browser profile does not trust its CA; installing that CA (ts dev proxy ca) is preferable. Against a real origin the flag is dangerous — the audit sends any --cookie session upstream and treats the response as evidence, so an invalid certificate could mean an impersonator is both harvesting the session and fabricating the result.

Note that a local Trusted Server injects its own configured slots into the page, so a run through the proxy can rediscover config it already has. Slot ids that are absent from the current config are the publisher's own.

Slots that change div id on every render ​

Some ad stacks build div ids from a per-render token, so one placement arrives under a new id on every page. Those ids match nothing at runtime, so the run declines to write them and reports the group instead:

text
note: skipped 3 slot(s) that look like one placement under a per-render div id
      on `/123456789/publisher/overlay` (ex_slot_a1_overlay_1, …);
      they share the prefix `ex_slot`. Add it once by hand with a div_id prefix
      that is stable across renders

This particular group is detected by evidence, not by recognising token shapes: candidates share an ad-unit path and formats, and what separates a fragmented placement from two legitimate siblings on one unit is co-occurrence — real siblings appear together on a page, fragments never do. (A single id whose per-render token sits before the placement part is refused on shape alone, from one observation, as the table above notes.) The suggested prefix is a starting point only, not written as a div_id, because it reaches only as far as the observed tokens happen to agree.

Checking for a device split ​

Publishers often serve a different ad unit per device (/network/desktop/news against /network/mobile/news). A desktop-only crawl cannot see that — it infers a template correct for desktop and silently wrong for every mobile impression.

bash
ts audit ad-templates generate https://publisher.example/ --profiles desktop,mobile

Each page is loaded once per profile. Where the profiles disagree, the slot is omitted and the diagnostic explains the conflicting paths. The generator does not fall back to a fabricated default ad unit.

Deploy ordering for templated config ​

A config containing section_root or section_segment is not rollback-safe. These keys are rejected outright by a Trusted Server binary that predates ad-unit templating, and the rejection fails the entire configuration load — not just the ad-template section — so every route serves an error. This is a full-site outage, not a degraded ad stack.

When a run reports that it wrote a {section} template:

  1. Deploy the template-aware binary first.
  2. Then ts config push.
  3. Do not roll that binary back while the config is live.

A run that did not template writes neither key, and leaves the config exactly as rollback-safe as it was.

Audit safety defaults ​

Every ts audit browser session validates TLS certificates. This matters because --cookie sends a real session to the origin and the page's own response becomes the audit's evidence, so a certificate-invalid host could both harvest the session and fabricate what the audit reports. Override only for a host you control with a known self-signed certificate:

bash
ts audit page https://staging.publisher.example --danger-accept-invalid-certs

ts audit ad-templates verify matches configured slots against the post-redirect path, so it refuses a redirect that leaves the requested origin rather than accepting another site's evidence as verification. Allow it for a known redirect between your own properties (for example apex to www):

bash
ts audit ad-templates verify https://publisher.example/ --allow-cross-origin-redirect

Verification accepts multiple URLs and reuses one browser/profile. Add --strict to return exit 1 when a confirmable slot is missing or partially confirmed, and --json for the stable machine-readable report. Video- and native-only slots are reported as unconfirmable; that records a checker limitation and does not fail strict mode. A live out-of-page slot with no sizes against banner-configured formats is reported partial and does fail strict mode. --scroll enables the optional second evidence phase and labels evidence first seen after the deterministic scroll.

Browser-backed ad-template generation and verification share --chrome, --headful, --browser-proxy, --no-assume-consent, --scroll, --settle-quiet-ms, --settle-max-ms, and --danger-accept-invalid-certs; --scroll runs the same deterministic scroll pass in both, and in verification it additionally labels the second evidence phase. Verification also accepts --browser-profile desktop|mobile; generation uses --profiles desktop,mobile to compare both profiles. --cookie NAME=VALUE is repeatable and creates host-only, root-path cookies; HTTPS targets also mark them Secure. Verification refuses cookies when URLs span multiple origins. The quiet settle window must not exceed the maximum.

Generation shares one --settle-max-ms budget across the initial settle, optional post-scroll settle, and GPT registry wait. The clock starts after navigation, immediately before the initial settle; scrolling also consumes the remaining budget. GPT polling receives the remaining budget or a minimum of --settle-quiet-ms plus two 250ms poll intervals, whichever is greater. One interval lets an already-stable registry establish a repeated reading; the other leaves time to confirm the completed dwell before the deadline. Later slot batches or slow browser reads can still prevent a full dwell, in which case generation returns the latest non-empty registry with a partial-evidence warning. The minimum allowance adds at most the quiet window plus 500ms to the shared settle allowance.

GPT polling precedes metadata extraction, so those reads cannot consume its budget. DOM and network evidence reflect the page after the GPT wait. If the budget is spent before post-scroll settling, generation reports that the wait was skipped and post-scroll evidence may be missing. Navigation and browser operations have their own timeouts, and scrolling or an in-flight operation can finish after the settle budget, so this is not a total page deadline. Two consecutive empty GPT polls end the wait early regardless of the budget; slots registered later may be missed. Increasing --settle-max-ms can help when a non-empty registry does not stabilize, but on continuously busy pages initial settling can still consume the entire shared budget, leaving GPT only its minimum polling allowance.

Verification applies --settle-max-ms separately to its initial and optional post-scroll settle phases. It performs no GPT wait: its evidence collector is injected ahead of publisher scripts and records each slot as it is defined.

ts audit is not an EdgeZero adapter command. It has no --adapter option and it does not provision resources, push config, build, deploy, or contact platform APIs.

Generate an external Prebid bundle ​

ts prebid bundle builds the local external Prebid browser bundle configured in trusted-server.toml.

toml
[integrations.prebid.bundle.modules]
bidder = ["rubiconBidAdapter", "kargoBidAdapter"]
user_id = ["sharedIdSystem"]
analytics = ["atsAnalyticsAdapter"]

Module values are exact upstream Prebid filename stems without .js. The bidder list is required and cannot be empty. Omit user_id to use the curated default preset, or set it to [] to select none. Omitted and empty analytics lists both select no analytics adapters.

Run the command after installing JS dependencies:

bash
cd crates/trusted-server-js/lib && npm ci
cd ../../..
ts prebid bundle

By default, generated artifacts are written to dist/prebid/. The versioned manifest records effective module selections, bidder and analytics runtime codes, the content-addressed filename, SHA-256, and SRI. The command copies the hash and SRI into integrations.prebid only after the generator and manifest both pass validation.

Upload the generated JavaScript file yourself, set external_bundle_url to its HTTPS asset URL, and include that host plus any redirect targets in proxy.allowed_domains before running ts config validate or ts config push.

Use custom paths when needed:

bash
ts prebid bundle --config publisher-a.toml --out build/prebid

ts prebid bundle is local-only. It has no --adapter option and does not upload, provision, deploy, or push config.

Released under the Apache License 2.0.