essc — sourcing CLI
Old development note — likely to be deleted or substantially rewritten.
essc is an AOT-compiled .NET CLI for indexing elastic.co content into
Elasticsearch. It lives in src/tooling/essc/ and shares the search document
contract (Elastic.Documentation.Search.Contract) with the docs indexing
pipeline, keeping every content source schema-consistent. Commands are
namespaced by content source so new sources can be added without breaking the
existing interface.
Current sources:
| Source | Namespace | Description |
|---|---|---|
| Contentstack | contentstack |
elastic.co marketing, blog, product, event, and Search Labs pages |
| Labs | labs |
Security and observability labs properties |
| Legacy docs | guide (planned) |
/guide legacy documentation |
Search Labs (/search-labs/* — blog, tutorials, notebooks, integrations) is sourced from
Contentstack rather than crawled: it moved off the labs HTML crawler and is now indexed by
contentstack sync alongside the rest of the marketing site. Security Labs and Observability Labs
are still crawled by labs sync.
Pull the distroless container image:
docker pull ghcr.io/elastic/website-search-essc:latest
Available tags: latest, edge (latest main), and per-version tags.
Or run from source:
dotnet run --project src/tooling/essc -- --help
essc resolves credentials in this order of precedence:
- CLI flags (
--endpoint,--api-key) - Environment variables (see table below)
- Dotnet user-secrets store
docs-builder(local development) — the same store the rest of this repository uses, so aspire and essc shareParameters:ElasticsearchUrlandParameters:ElasticsearchApiKey
| Environment variable | Description |
|---|---|
ELASTICSEARCH_URL |
Full Elasticsearch URL including port |
ELASTICSEARCH_API_KEY |
Elasticsearch API key |
DESTINATION_ELASTIC_URL |
Destination cluster URL for cross-cluster commands (indices copy, indices sync-remote, indices unify-incremental-sync). Only needed when the destination differs from the source — it otherwise seeds from Parameters:ElasticsearchUrl / ELASTICSEARCH_URL |
DESTINATION_ELASTIC_APIKEY |
Destination cluster API key — same seeding rule as above |
CONTENTSTACK_API_KEY |
Contentstack Content Delivery API key |
CONTENTSTACK_DELIVERY_TOKEN |
Contentstack delivery token |
The dotnet configuration section keys (Parameters:ElasticsearchUrl, ContentStack:ApiKey,
etc.) are also accepted. For CI, use the flat environment variable names above.
For local development, populate the secrets store directly:
dotnet user-secrets set --id docs-builder Parameters:ElasticsearchUrl <url>
dotnet user-secrets set --id docs-builder Parameters:ElasticsearchApiKey <key>
dotnet user-secrets set --id docs-builder ContentStack:ApiKey <key>
dotnet user-secrets set --id docs-builder ContentStack:DeliveryToken <token>
For cross-cluster commands (indices copy, indices sync-remote, indices unify-incremental-sync),
the destination cluster seeds from the same Parameters:ElasticsearchUrl / Parameters:ElasticsearchApiKey
secret as the source — so configuring just one cluster in the secrets store is enough; pass
--to-url/--to-api-key (or --from-url/--from-api-key) on the command to point only one side
at a different cluster, or set DESTINATION_ELASTIC_URL/DESTINATION_ELASTIC_APIKEY to override
the destination for every run.
If you previously used essc from the website-search-data repository, copy the
Parameters:Elasticsearch* and ContentStack:* values from your old
elastic-website-ai-search store into the docs-builder store
(~/.microsoft/usersecrets/, on Windows %APPDATA%\Microsoft\UserSecrets\).
Fetches all published content from Contentstack and indexes it into Elasticsearch. Uses 5 parallel lanes. Cursors are saved after every page so interrupted runs resume automatically.
essc contentstack sync
| Flag | Default | Description |
|---|---|---|
--force |
false | Delete stored cursors and reindex from scratch |
--no-index |
false | Fetch and map only — skip Elasticsearch indexing |
--no-ai |
false | Skip generative AI (no post-sync enrich batch) |
--max-ai-docs |
100 (when omitted) | Positive cap on documents enriched after finalize; omit for default 100 |
--max-ai-time |
none | Wall-clock cap for post-sync AI (minimum 1m when set) |
--endpoint |
from secrets | Override Elasticsearch endpoint |
--api-key |
from secrets | Override Elasticsearch API key |
--page-per |
0 (unlimited) | Max pages per content type (useful for testing) |
--cache-folder |
OS app data | Override cursor state directory |
--max-ai-docs uses DataAnnotations: it must be at least 1 when passed; 0 is invalid. For a large or unbounded batch without re-syncing, use contentstack ai-enrich instead.
The sync runs in two phases:
- Fetch + index — pages through Contentstack in parallel, maps each item to a
SiteDocument, and bulk-indexes into both the lexical and semantic indices. - Finalize — flushes the remaining buffer, reindexes the lexical index into
the semantic index, publishes synonyms and query rules, then runs a bounded
generative AI enrichment pass on the semantic write index (unless
--no-ai).
Runs generative AI enrichment on existing site-* semantic indices without calling Contentstack. Same Elasticsearch overrides as contentstack sync.
essc contentstack ai-enrich
| Flag | Default | Description |
|---|---|---|
--max-ai-time |
unlimited | Stop enrichment after N minutes |
--max-ai-docs |
0 (unlimited) | Enrich at most N documents (0 = no cap) |
--endpoint |
from secrets | Override Elasticsearch endpoint |
--api-key |
from secrets | Override Elasticsearch API key |
Lists all Contentstack content types and their document counts.
essc contentstack types
Dumps raw Contentstack entries for a given content type, useful for inspecting the source data structure.
essc contentstack samples <content-type>
Discovers labs URLs from sitemaps, crawls HTML, and bulk-ingests into labs-* indices. See essc labs sync --help for crawl flags (--dry-run, --force, --no-ai, --max-ai-docs, --max-ai-time, etc.).
Runs generative AI enrichment on existing labs-* semantic indices without re-crawling. Flags match contentstack ai-enrich (--max-ai-time, --max-ai-docs, --endpoint, --api-key).
essc labs ai-enrich
Copies one or more indices verbatim between clusters using the Elasticsearch server-side remote
reindex API. Unlike indices sync-remote / indices unify-incremental-sync, this command is not
docs-search specific — it copies no synonym/query-rule resources and runs no lexical/semantic
inference phases. Useful for pulling standalone indices (e.g. AI-enrichment -ai-cache lookup
indices) from production down to a local cluster.
essc indices copy docs-assembler.semantic-prod-ai-cache labs-public.semantic-prod-ai-cache \
--from-url https://<prod-es> --from-api-key <key> \
--to-url http://localhost:9200
- Takes one or more source index/alias names as positional arguments.
--rename-from/--rename-to— regex rename applied to each source name to derive its destination name (back-references supported), e.g.--rename-from 'semantic-prod-' --rename-to 'semantic-local-'.--force— delete and recreate an existing destination index from the source's mapping and settings before reindexing. Without it, an existing destination index is reindexed into as-is.--strip-semantic-fields— strip_inference_fieldsmetadata when the source hassemantic_textmappings (seeindices sync-remote).- Shares
--from-url/--from-api-key/--to-url/--to-api-key/--slices/--rps/--aliaseswithindices sync-remoteandindices unify-incremental-sync.--sliceshas no effect onindices copy— every reindex it does is a remote reindex, and Elasticsearch rejectsslices > 1for reindex from a remote source.
When CI=true or stdout is not a TTY, essc switches from interactive progress
widgets to plain log lines:
[Lane 1] 100% blog — 1,240 fetched, 1,198 indexed
[Lane 2] 14% customer_tile — 42 fetched, 40 indexed (2 skipped)
… 14% secondary reindex — 1,240/8,500
✓ 100% secondary reindex — 8,500/8,500
essc ships as a distroless container only, published to
ghcr.io/elastic/website-search-essc using the .NET SDK's native container
support as part of the docs-builder release pipeline (./build.sh publishcontainers).
Base image: mcr.microsoft.com/dotnet/nightly/runtime-deps:10.0-noble-chiseled.
Tagged edge;latest;<version> on release, edge on every push to main.
AOT compilation is validated on every CI run by building the container image and
running essc --help against it.