Echo/Docs

CLI command

geo

The GEO prompt panel — the questions buyers ask AI assistants in a product's niche — and the measurement runs that ask them of each declared assistant through Brama.

Invocation#

Shell
echo-cli geo prompts list --product <id> [--retired]
echo-cli geo prompts add --product <id> --prompt <text> --locale <code> [--intent <text>]
echo-cli geo prompts edit <id> [--prompt <text>] [--locale <code>] [--intent <text>]
echo-cli geo prompts retire <id>
echo-cli geo prompts restore <id>
echo-cli geo runs start --product <id>
echo-cli geo runs list --product <id>
echo-cli geo runs show <run-id>
echo-cli geo runs check <run-id>
echo-cli geo runs checks <run-id>
echo-cli geo runs ranks <run-id>
echo-cli geo runs tone <run-id>
echo-cli geo runs tones <run-id>
echo-cli geo runs schedule --product <id> --every-days <n> --by <name>
echo-cli geo runs schedules
echo-cli geo runs unschedule --product <id>
echo-cli geo runs due
echo-cli geo llms show --surface <slug> [--full]
echo-cli geo facts publish --product <id> --by <name> --file <facts.json>
echo-cli geo facts list --product <id>
echo-cli geo facts show --product <id>
echo-cli geo facts page --product <id>
echo-cli geo referrals count --days <days>
echo-cli geo conversions count --days <days> --signup-event <event>
echo-cli geo corrections list --product <id>
echo-cli geo corrections watch --product <id> --notify-user <user-id> --by <name>
echo-cli geo corrections unwatch --product <id>
echo-cli geo corrections watches [--product <id>]
echo-cli geo corrections check
echo-cli geo sources list --product <id>
echo-cli geo sources check --product <id>
echo-cli geo sources targets --product <id>
echo-cli geo sources threads --product <id> --account <reddit-account-id>
echo-cli geo competitors list --product <id>
echo-cli geo competitors add --product <id> --name <name>
echo-cli geo competitors remove <competitor-id>
echo-cli geo share show --product <id>
echo-cli geo share routes --product <id>
echo-cli geo share standings --product <id>
echo-cli geo models list
echo-cli geo models watch --by <name>
echo-cli geo experiments list --product <id>
echo-cli geo experiments add --product <id> --page <https-url> --change <text> --prompts <id,id> --changed-at <iso-time> --window-days <days>
echo-cli geo experiments report <experiment-id>
echo-cli geo crawlers list --surface <slug>
echo-cli geo crawlers allow --surface <slug> --crawler <token> --path </section>
echo-cli geo crawlers disallow --surface <slug> --crawler <token> --path </section>
echo-cli geo crawlers remove <rule-id>
echo-cli geo crawlers robots --surface <slug>
echo-cli geo crawlers check --surface <slug>
echo-cli geo extraction check --post <id> --prompt <id>
echo-cli geo extraction list --post <id>
echo-cli geo extraction apply <check-id>

Inputs and output#

prompts list writes an indented JSON array of the product's prompts (id, product_id, prompt, locale, intent, active, created_at, updated_at), active ones only unless --retired is given, ordered by locale then creation; add, edit, retire and restore write the one prompt as it now stands. runs start writes { run, answers }: the run (id, product_id, routes, brand_terms, started_at, finished_at) and one answer per active prompt and route (prompt_id, route, answer and matched_terms, or refusal). runs list writes the product's runs newest first; runs show writes one run with its answers. llms show writes, as plain text rather than JSON, the llms.txt the surface serves at GET /api/public/llms/<surface> — its name, its category, a Facts section holding the fact sheet in force of the product the surface is linked to (when it was published, the address of the same sheet as JSON at /api/public/facts/<product>, and each fact as "- <name>: <value> ([source](<source>))"; a surface linked to no product, or whose product has published no sheet, has no Facts section), and a link with the description of every published post — or with --full the llms-full.txt, which adds each post's whole Markdown body; both are built from the published posts and the stored sheet on every read, so a publish, an archive or a newly published sheet shows on the next one. corrections list writes one entry per fact on the sheet that a checked answer stated wrongly (fact, open, routes, quotes, runs, cited_pages, last_seen_at), open ones first: cited_pages are the pages those inaccurate answers cite, the sources likely feeding the claim; an entry is open while the newest checked run still holds an inaccurate answer about that fact and closes on its own once a later checked run does not; nothing opens or closes one by hand. corrections watch names the user whose pending actions receive an alert when one of the product's corrections opens (replacing an owner named before) and writes the watch (product_id, notify_user, declared_by, declared_at, checked_at, error); unwatch writes the watch it removed; watches writes { watches } and, with --product, { watches, states }, each fact's kept state (fact, open, alert_id, opened_at, checked_at); check writes the checks it made (product_id, opened — the states it raised an alert for —, still_open, closed). The daily /api/cron/geo-corrections, at 05:37 UTC after the scheduled runs, and check read every watched product's corrections and raise one pending action of type geo_correction, naming the fact, the routes, the quotes, the runs and the cited pages, for each correction open now that was not open at the check before — never seen, or closed since — so a correction still open is not alerted again and one that reopens is; each watch keeps when it was checked and the error that stopped a check. sources list writes one entry per domain the answers of the product's runs cite (domain, citations, pages, routes, prompts), most cited first, read from every http or https address written in the stored answers; an answer that cites nothing adds nothing. sources check reads every cited page through Weles, in the window ECHO_RESEARCH_VIEWPORT declares (<width>x<height>), and stores one reading per page — mentions_brand true or false for the product's name, or read_error with Weles's reason when the page could not be read — and writes those readings; sources targets writes the newest reading of every checked page, pages without the brand first, then unread pages, then pages that mention it.

State effect#

Every verb calls Echo Web's authenticated /api/geo operation with ECHO_INTERNAL_BEARER at ECHO_WEB_BASE_URL; the dashboard at /geo and Echo Desktop's GEO prompts screen call the same operation. prompts add stores one prompt; edit replaces exactly the named fields; retire sets active false and keeps the row, so measurements taken against it still name it; restore sets it active again. runs start asks every active prompt of each route the deployment declares in ECHO_GEO_ASSISTANT_ROUTES (canonical provider/model routes, comma separated) through Brama, one after another, and stores each answer whole with the brand terms it names (the product's name, matched without regard to case); a route Brama refused or that declined is stored with that refusal instead of an answer, so it is never counted as an answer that left the brand out. The run is marked finished when every answer is stored. runs check asks the judge route ECHO_GEO_JUDGE_ROUTE declares, through Brama, to compare every answer the run kept with the product's fact sheet in force and stores one verdict per answer and sheet — accurate, inaccurate or not_stated — with, for inaccurate, the fact on the sheet it contradicts and the answer's sentence copied exactly; a verdict that names no fact on the sheet or quotes a sentence the answer does not hold is refused rather than stored, and checking again against the same sheet replaces the earlier verdicts; it writes { fact_sheet_id, checks }. facts publish reads a JSON list of { name, value, source } from --file and adds a new fact sheet under the --by name; no sheet is ever edited, so every earlier sheet stays readable through facts list, and the newest is the one in force, which facts show reads from the public GET /api/public/facts/<product-id> exactly as people and AI models get it (facts and published_at). facts page reads the same sheet and writes { url, facts, published_at }: url is Echo Web's public page /facts/<product-id>, open without a session, where people read every fact of the sheet in force with its value and linked source; the fact sheet section on /geo links it and Echo Desktop's opens it. sources list is read the same way by the Cited sources section of /geo and of Echo Desktop's GEO screen, and by echo-mcp as echo_geo_cited_sources. referrals count reads, without writing, every web event of the last --days days whose referrer host — or a parent domain of it — ECHO_AI_REFERRER_HOSTS_JSON declares as an AI assistant (an object of host to assistant name), and writes { since, counts } with events, sessions and visitors per assistant and site, most events first; a host declared later also counts the visits recorded before it.

Refusals#

  • A wrong invocation is refused as a usage error before anything is sent: an unknown or missing noun answers "geo takes <every noun listed above, separated by commas>; got <word>", and an unknown or missing verb answers "geo <noun> takes <the usage of every verb of that noun, as listed above, separated by semicolons>; got <word>" (for example "geo llms takes show --surface <slug> [--full]; got drop"); "geo <noun> <verb> does not take <flag>; it takes …", "<flag> needs a value", "geo prompts list needs --product <id>, as echo-cli products prints it" (and the same for every verb that takes --product), "geo llms show needs --surface <slug>, as echo-cli blogs prints it" (and the same for geo crawlers list, robots and check), "geo facts publish needs --product" (and --by, --file), "geo referrals count needs --days <days>: the number of days to count", "geo prompts add needs --product" (and --prompt, --locale), "geo prompts edit changes nothing; name at least one of --prompt, --locale, --intent", "geo <noun> <verb> needs a <noun> id, as geo <noun> list prints it" and "geo <noun> <verb> takes only the <noun> id; got …" for every verb that takes an id. A --file that cannot be read or is not JSON fails with "<file> is not a readable JSON list of { name, value, source }: <cause>". runs check asks the judge to answer by one call of record_verdict, whose arguments are the verdict, the contradicted fact (one of the sheet's fact names, or null) and the quote, and fails, storing nothing for the answer it was judging, with "the route <route> declined to call record_verdict: …", "the route <route> did not answer by one call of record_verdict: …", "the judge gave GEO answer <id> the verdict <value>, not one of accurate, inaccurate, not_stated", "the judge called GEO answer <id> inaccurate citing <fact>, which is no fact on the sheet" or "the judge called GEO answer <id> inaccurate quoting <text>, which is not in the answer"; a run whose routes all refused answers "GEO run <id> kept no answer to check; every route refused"; a deployment without ECHO_GEO_JUDGE_ROUTE answers that the setting is not configured. sources check on a deployment without ECHO_RESEARCH_VIEWPORT, or with one that is not <width>x<height>, fails naming the setting.
  • Without ECHO_WEB_BASE_URL any verb, and without ECHO_INTERNAL_BEARER every verb but llms show, facts show and crawlers robots (public reads that send no bearer), prints "<name> is not configured" and fails. llms show, crawlers robots and every crawlers verb of a surface that is not active answer not found with "no active blog surface is named <slug>".
  • Echo Web's refusal is printed as "Echo Web refused <method> /api/… with HTTP <status>: <its answer>" and fails. A bad request answers "product_id is required: the product whose panel to list", "<field> is required and may not be blank", "intent, when sent, must be non-blank text; send null to clear it", "GEO prompt <id> is already retired", "GEO prompt <id> is active; only a retired prompt can be restored", "product <id> has no active GEO prompts; add some with echo-cli geo prompts add", "facts is required: a non-empty list of { name, value, source }", "fact <n> has no <field>; every fact names what it is, its value and the source it was read from", "fact <n> (<name>) cites <source>; a source must be an HTTPS address a reader can open" or "the fact <name> is stated twice; state each fact once"; not found answers "no product has id <id>; echo-cli products lists the ones that exist", "no GEO prompt has id <id>; echo-cli geo prompts list --product <id> lists them", "no GEO run has id <id>; echo-cli geo runs list --product <id> lists them" or "product <id> has published no fact sheet; publish one with echo-cli geo facts publish"; a conflict answers "product <id> already has this prompt for locale <locale> as GEO prompt <id>"; a deployment without ECHO_GEO_ASSISTANT_ROUTES answers that the setting is not configured; a wrong bearer answers Unauthorized.
  • competitors add stores a name GEO reads look for beside the product's own; competitors remove deletes only the name, never an answer. runs ranks writes, for every answer the run kept, the product and its competitors in the order the answer first names them (order) and the ones it leaves out (absent); share show writes, for every run of the product and every route, how many answers name the product and each competitor (named), how many answers there were and how many were refused; share standings writes the same places across every run of the product, per prompt and route: prompt_id, route, runs (the runs that asked it), answers, refused, and names — for the product and each competitor, named (the answers naming it), first (the answers naming it first) and mean_place (its mean place over the answers naming it, 1 for first; null when none did) — best mean place first; the dashboard's /geo and Echo Desktop's GEO insights show them as Standings across runs, and echo-mcp reads them as echo_geo_standings. All read the stored answers with the names listed now, so a competitor added later is found in answers kept before. Usage errors: "geo competitors takes list --product <id>; add --product <id> --name <name>; remove <competitor-id>; got <word>", "geo share takes show --product <id>; routes --product <id>; standings --product <id>; got <word>", "geo competitors add needs --product <id>, as echo-cli products prints it", "geo competitors add needs --name <name>", "geo competitors remove needs a competitor id, as geo competitors list prints it". Echo Web answers a bad request with "name is required and may not be blank", "<name> is the product itself, not a competitor" or "product_id is required: the product whose standings across runs to read", a conflict with "product <id> already lists <name> as competitor <id>", and not found with "no competitor has id <id>; echo-cli geo competitors list --product <id> lists them".
  • models watch reads Brama's catalogue now (GET /v1/models?kind=text at STADO_MODEL_ROUTER_URL with ECHO_STADO_MODEL_ROUTER_TOKEN, so Brama says which ids it can serve Echo) through POST /api/geo/routes/models and writes { served, baseline, newly_seen }: every servable text model not seen before is kept in geo_model_sightings (route, provider, proposed, first_seen_by, first_seen_at). The deployment's first watch is its baseline and proposes nothing; a model a later watch sees first is proposed unless ECHO_GEO_ASSISTANT_ROUTES already declares it. Echo Web watches daily at /api/cron/geo-model-watch (04:13 UTC). models list writes the proposals, newest first, each with declared (whether ECHO_GEO_ASSISTANT_ROUTES names it now); once the operator declares one, every later run asks it and share routes shows its answers beside its predecessor's. The Proposed assistant models section of /geo and of Echo Desktop's GEO screen show the same, and echo-mcp reads it as echo_geo_model_proposals. watch without --by is a usage error; Brama refusing the read fails with "Brama answered <address> with HTTP <status>: <body>".
  • experiments add records a change to one page — its HTTPS address, what changed, when (--changed-at, an ISO 8601 time) and the panel prompts it is meant to move (--prompts, comma-separated prompt ids) — with the window in days the operator compares on each side of the change; nothing about it is assumed. experiments list writes the product's experiments, newest change first. experiments report writes { experiment, before, after }: for each side, the window (from, to), the runs started inside it and, per route, the answers to the experiment's prompts, how many name the product and how many were refused, read from the stored answers on every call, so a run taken later inside the after window counts on the next report. Usage errors: "geo experiments takes list --product <id>; add …; report <experiment-id>; got <word>", "geo experiments add needs <flag>" for each of --product, --page, --change, --prompts, --changed-at and --window-days, "geo experiments report needs an experiment id, as geo experiments list prints it". Echo Web answers a bad request with "page must be the HTTPS address of the changed page, not <page>", "changed_at must be an ISO 8601 time, not <value>", "window_days must be a positive whole number: the days compared on each side of the change" or "prompt_ids is required: the ids of the panel prompts the change is meant to move", and not found with "product <id> has no GEO prompt <ids>; echo-cli geo prompts list --product <id> lists them" or "no GEO experiment has id <id>; echo-cli geo experiments list --product <id> lists them".
  • crawlers allow and disallow store one rule for a blog surface — the crawler's user-agent token as its operator publishes it, and a section path starting with / — and crawlers remove deletes one; nothing about which crawlers exist is assumed. crawlers list writes the surface's rules by crawler then path. crawlers robots writes, as plain text, the robots.txt the public GET /api/public/robots/<surface> serves, built from the rules on every request in the RFC 9309 format: one User-agent group per crawler a rule names, each rule an Allow or Disallow line; a crawler no rule names gets no group. crawlers check reads the robots.txt the surface's origin serves now and writes { served_at, status, matches, missing, extra }: the lines Echo builds that the live file lacks and the live lines Echo does not build; an origin that does not serve one answers with its status and every expected line missing. Usage errors: "geo crawlers takes list --surface <slug>; allow …; disallow …; remove <rule-id>; robots --surface <slug>; check --surface <slug>; got <word>", "geo crawlers allow needs <flag>" (and disallow) for each of --surface, --crawler and --path, "geo crawlers remove needs a rule id, as geo crawlers list prints it". Echo Web answers a bad request with "crawler is required: the user-agent token the crawler announces, as its operator publishes it" or "path is required: the section, starting with /", a conflict with "<slug> already has rule <id> for <crawler> on <path>; remove it first", and not found with "no crawler rule has id <id>; echo-cli geo crawlers list --surface <slug> lists them".
  • runs tone asks the judge route ECHO_GEO_JUDGE_ROUTE declares, through Brama, how every answer the run kept speaks of the product — positive, neutral, negative, or absent when it does not speak of it — and stores one tone per answer with the answer's sentence that carries it copied exactly, replacing an earlier tone; it writes the tones. The judge answers by one call of record_tone, whose arguments are the tone and the quote. runs tones writes the stored tones, and runs checks the stored accuracy verdicts (answer_id, fact_sheet_id, verdict, contradicted_fact, quote, judged_by, judged_at) oldest judgement first, without asking anything. runs tone fails, storing nothing for the answer it was judging, with "the route <route> declined to call record_tone: …", "the route <route> did not answer by one call of record_tone: …", "the judge gave GEO answer <id> the tone <value>, not one of positive, neutral, negative, absent" or "the judge called GEO answer <id> <tone> quoting <text>, which is not in the answer"; a run whose routes all refused answers "GEO run <id> kept no answer to judge; every route refused"; a deployment without ECHO_GEO_JUDGE_ROUTE answers that the setting is not configured.
  • share routes writes every assistant route the product's runs have asked (route, first_run_at, last_run_at, runs, answers, named, refused), newest first by when it was first asked: named counts the answers naming the product, read from the stored answers. A route newly declared in ECHO_GEO_ASSISTANT_ROUTES appears after the first run that asks it, beside the routes it replaces, so a new model's knowledge of the product reads against its predecessor's.
  • conversions count reads, without writing, every web event of the last --days days and writes { since, conversions }: per AI assistant and site, arrived, the visitors whose first outside touch was that assistant; first_touch_signups and last_touch_signups, the visitors who sent --signup-event and whose first, or last, outside touch before it was that assistant; and first_touch_revenue with first_touch_payers, what the visitors it first touched paid at or after that touch per currency (never converted) and how many of them paid, from every payment stored in the window — the web and mobile events with a positive revenue property and a currency, as the Stripe and RevenueCat webhooks record them. A visitor is one person, every identifier the identity edges link counted together, so a payment recorded under the identifier they signed in with counts; a touch is an event whose referrer is another host than the page's own, so moving between a site's pages is not one; the assistants are the hosts ECHO_AI_REFERRER_HOSTS_JSON declares, matched as referrals count matches them. Usage errors: "geo conversions count needs --days <days>: the number of days to count" and "geo conversions count needs --signup-event <event>: the event a visitor sends when they sign up". Echo Web answers a bad request with "days is required: the number of days to count", "signup_event is required: the event a visitor sends when they sign up" or "days must be a positive whole number: …", and a deployment without ECHO_AI_REFERRER_HOSTS_JSON answers that the setting is not configured.
  • sources threads reads, through Weles, every Reddit thread the product's runs cite (an address whose path is /r/<subreddit>/comments/<article>), drafts one reply through Brama with the route ECHO_GEO_REPLY_ROUTE declares from the thread, the fact sheet in force and the rules of every community echo-cli social communities declares on reddit that the thread's address names, and queues it as a pending suggestion in the reddit-comments queue for the --account given, naming the prompt whose answer cited the thread; it writes one entry per thread (post_url, prompt_id, suggestion_id, already_queued). A thread already in the queue for that account is left as it is. Nothing is posted: a suggestion posts only after a reviewer approves it on /reddit-comments, which shows beside each such suggestion the prompt (and its locale) whose assistant answers cited the thread, read with GET /api/reddit-comments as cited_prompt, and posting it is still counted against the community's rate. Usage errors: "geo sources threads needs --product <id>, as echo-cli products prints it" and "geo sources threads needs --account <id>, the Reddit account echo-cli social-accounts prints". Echo Web answers not found with "no social account has id <id>; echo-cli social-accounts lists them" or "product <id> has published no fact sheet; publish one with echo-cli geo facts publish", a bad request with "no answer of product <id> cites a Reddit thread; echo-cli geo sources list shows what they cite", and fails with "the reply route <route> declined to draft a reply to <thread>" or "the reply route <route> drafted an empty reply to <thread>"; a deployment without ECHO_GEO_REPLY_ROUTE or ECHO_RESEARCH_VIEWPORT answers that the setting is not configured.
  • extraction check asks the judge route ECHO_GEO_JUDGE_ROUTE declares, through Brama, to answer the --prompt from the --post alone and stores the verdict with the post revision it read: answerable with the answer drawn from the post, or not answerable with a short Markdown block — a definition, a table or a plain claim using only facts the post states — proposed for it; the judge answers by one call of record_extraction whose arguments are those three fields; it writes the check. extraction list writes the post's checks newest first. extraction apply appends a check's proposed block to its post, so the post's revision history records the change, marks the check applied and, for a published post, revalidates its landing; it writes { check, revision, revalidated }. Usage errors: "geo extraction check needs --post <id>, as echo-cli blogs prints it", "geo extraction check needs --prompt <id>, as geo prompts list prints it", "geo extraction list needs --post <id>, as echo-cli blogs prints it", "geo extraction apply needs a check id, as geo extraction list prints it". Echo Web answers not found with "no blog post has id <id>; echo-cli blogs lists them", "no GEO prompt has id <id>; …" or "no extraction check has id <id>; echo-cli geo extraction list --post <id> lists them"; a bad request with "extraction check <id> found the post answers the prompt; there is nothing to apply", "extraction check <id> was applied at <time>", "post <id> is at revision <n>, not the revision <m> the check read; check it again" or "post <id> changed while the block was applied; check it again"; and fails with "the route <route> declined to call record_extraction: …", "the route <route> did not answer by one call of record_extraction: …", "the judge did not say whether post <id> answers the prompt", "the judge called post <id> answerable without the answer", "the judge called post <id> unanswerable without a proposed block" or "post <id> took the block as revision <n>, but its landing was not revalidated: <cause>".
  • runs schedule asks a product's panel on a schedule: every day /api/cron/geo-runs starts the run of each product whose schedule was never tried or was last tried at least --every-days days ago, exactly as runs start does, and keeps on the schedule when it tried and the run it started or the error that stopped it (a panel with no active prompt, ECHO_GEO_ASSISTANT_ROUTES missing, Brama refusing), so the next day tries again; scheduling a product again replaces its interval. runs schedules writes every schedule (product_id, every_days, declared_by, declared_at, attempted_at, run_id, error); runs unschedule removes one and keeps its runs; runs due starts every due schedule now as the daily job does. The Scheduled runs part of the GEO screen's measurements and of Echo Desktop's GEO screen call the same /api/geo/runs/schedules operations, and echo-mcp lists the schedules as echo_geo_run_schedules. Usage errors: "geo runs schedule needs <flag>" for each of --product, --every-days and --by, "--every-days must be a whole number of days; got <text>" and "geo runs schedules takes nothing; got …" (and due). Echo Web answers a bad request with "every_days must be a positive whole number: how many days pass between runs; got <value>" or "declared_by is required: …", and not found with "no product has id <id>; echo-cli products lists the ones that exist" or "product <id> has no GEO run schedule; echo-cli geo runs schedules lists them".