CLI command
seo programmatic
Programmatic pages: a template of a blog surface rendered for each row of a dataset, near-duplicate and thin pages held with the reason, the rest written as draft posts.
Invocation#
echo-cli seo programmatic template --file <template.json> --by <who> | templates | check --template <template-id> --rows <rows.json> --max-similarity <-1..1> [--min-unique-words <n>] | generate --template <template-id> --rows <rows.json> --max-similarity <-1..1> [--min-unique-words <n>] --by <who> | batches --template <template-id>Inputs and output#
template writes the template: id, surface_slug, name, slug_template, title_template, description_template, body_template, keywords, reading_minutes, slots (the {{slot}} placeholders it uses), slot_patterns (each typed slot with its pattern; {} when none is typed), created_by and created_at; templates lists them newest first. check writes template_id, max_similarity, min_unique_words (null when none was given) and pages: each row's row_index, slug, title, closest_row and similarity (its most similar sibling by body), unique_words (the words its slots add to the template's own body text), and hold_reason, null when the page would become a draft; it exits 1 when any page would be held. generate writes the same with id, created_by and created_at, and post_id, the draft each page not held became. batches writes a template's batches newest first with their pages.
State effect#
Every verb calls Echo Web's authenticated /api/seo/programmatic with ECHO_INTERNAL_BEARER at ECHO_WEB_BASE_URL; the Programmatic pages section of the blog admin and of Echo Desktop's GEO screen call the same operations, and echo-mcp runs them as echo_programmatic_template, echo_programmatic_templates, echo_programmatic_check, echo_programmatic_generate and echo_programmatic_batches. --file holds the template's fields as JSON: surface, name, slug_template, title_template, description_template, body_template, keywords, reading_minutes and, optionally, slot_patterns, an object mapping a slot to the regular expression each of its values must match whole ("\\d+" for a count, "\\d{4}-\\d{2}-\\d{2}" for a date) — the slot's type. --rows holds the dataset as a JSON list of objects giving every slot, each value text or a number (written as its digits); a typed slot's value must match its pattern; a slot's value in the slug is written lower case with words joined by dashes, and each rendered page must pass the blog's own post rules. Every page's body is embedded with the deployment's STADO_ECHO_EMBEDDING_MODEL and compared with every other page of the batch by cosine similarity; a page more similar to its closest sibling than --max-similarity is held, as is one whose slug another row renders or the surface already uses. With --min-unique-words, a page whose body holds fewer than that many words beyond the template body's own words (the body with its placeholders taken out, counted by whitespace) is held as thin content: its slots add too little for the page to read differently from its siblings. Templates, batches and pages are kept in seo_programmatic_templates, seo_programmatic_batches and seo_programmatic_pages; drafts are ordinary blog posts, reviewed and published like any other.
Refusals#
- A wrong invocation is refused before anything is sent: "seo programmatic takes <every verb's usage, separated by semicolons>; got <word>", "seo programmatic <verb> does not take <flag>; it takes …", "<flag> needs a value", "seo programmatic <verb> needs <flag>" and "<flag> <path> is not a readable JSON file: <cause>".
- Echo Web answers a bad request with "<field> is required: …" for each template field, "keywords is required: the keywords every page of the family carries", "reading_minutes must be a positive whole number: how long one page takes to read; got <value>", "slug_template must use a slot, or every page would have the same slug", "slot_patterns, when sent, must be an object mapping a slot to the regular expression its values must match", "slot_patterns names <slot>, which the template does not use; its slots are <slots>", "slot_patterns.<slot> must be a regular expression its values match; got <value>", "slot_patterns.<slot> is not a regular expression: <cause>", "max_similarity is required: the greatest cosine similarity, from -1 to 1, a page may have to another page of the batch; got <value>", "min_unique_words must be a positive whole number: the fewest words a page's slots must add to the template's own text; got <value>", "rows is required: the dataset, a list of objects giving every slot of the template", "rows[<n>] must be an object of slot values", "rows[<n>] gives no <slots>; the template's slots are <slots>, each text or a number", "rows[<n>].<slot> is <value>, which does not match <pattern>, the pattern the template declares for it", "rows[<n>] renders a page the blog refuses: <issues>" or "created_by is required: who generates the batch"; not found with "no blog surface is named <slug>" or "no programmatic template has id <id>; echo-cli seo programmatic templates lists them"; a conflict with "<surface> already has a template named <name>"; and fails naming the cause with "Stado model-router embeddings failed (<status>): <detail>", "the batch of <name> could not be stored: <cause>", "row <n> of batch <id> could not become a draft at <slug>: <cause>" or "the pages of batch <id> could not be stored: <cause>". A page held as thin reads "its slots add <n> words to the template's <m>; at least <floor> are required". The command prints Echo Web's sentence and exits 1.