The refresh loop
Once the registry exists you don’t bootstrap again — you change an input and reconcile. In steady state that’s a three-command rhythm:
gtmesh extract --cluster slack # newer data → a newer export in the bag
gtmesh plan # see the diff (printed to stdout)
gtmesh apply # enact it (idempotent)That’s the whole loop. The rest of this page explains why each step behaves the way it does — and why you can run the cheap part of it on a schedule without worrying.
The three commands
extract — refresh the data
gtmesh extract pulls newer keyword/search data and writes it to the bag (data/raw/). Each
export is immutable and timestamped — extract never overwrites an old pull, it adds a newer
one beside it. So the bag is a growing pile of dated snapshots, not a single mutable file.
gtmesh extract --cluster slack --dry-run # report the planned pulls, spend no credits
gtmesh extract --cluster slack # the real pull → data/raw/keywords/ahrefs/
gtmesh extract # all configured seed clustersplan — preview the diff
gtmesh plan reads the frozen bag and computes what the registry should become. It picks the
newest snapshot for each keyword (this is the “recency” behaviour — your latest pull wins) and
shows you the diff against the committed registry.
plan is read-only and free. It prints to stdout and writes a derived, git-ignored
.gtmesh/plan.json — it never re-fetches data and never changes the registry. Run it whenever you
want a look; re-run it as many times as you like.
gtmesh plan # prints the plan to stdout
gtmesh plan > plan.md # keep the rendered plan in a file
gtmesh plan --json | jq . # machine outputapply — enact it
gtmesh apply makes the registry (and the page files) match what plan showed. It’s
idempotent: it only acts where there’s a real difference, so running it twice in a row does
nothing the second time. Terraform-style, it previews what it will do and prompts to confirm.
gtmesh apply # shows the intended actions, prompts to confirm, then enacts
gtmesh apply --yes # skip the prompt (scripts/CI; required in a non-interactive shell)You never need plan between an extract and an apply — apply computes the same diff
itself. plan is the optional preview; apply is the one that writes.
Why the bag is frozen and timestamped
Because extract appends dated snapshots and plan reads the frozen bag, the loop is
deterministic and auditable:
- A given bag always produces the same plan — re-running
plancan’t surprise you with different numbers, because the data it reads doesn’t move. - The keyword history is preserved. You can see how volume or difficulty drifted over time, and old pulls are never silently lost.
plannever spends API credits. The only command that talks to a provider isextract.
Re-extract cadence
How often you re-extract is your call — keyword metrics drift slowly, so most operators pull on a
schedule (weekly or monthly) rather than constantly. The key safety property: the catalogue loop
is safe to run on a schedule. extract → plan → apply keeps the map of everything that could
exist in sync, and because apply only acts on real differences, an automated nightly or weekly
run is harmless when nothing changed.
What it is not is an automatic content factory. Re-extracting and applying keeps the catalogue current; it never writes or rewrites a page body on its own. Which catalogued pages you actually build stays a deliberate, human-gated choice — see lifecycle & reconcile.
Editing an input shows recompute
When you edit a reference/ table, a seed list, or a config rule, the catalogued-but-unbuilt rows
your edit touches show up in the plan as recompute (with reason: inputs-changed). That’s
the signal your edit took effect — apply will recompute those rows.
This is deliberate. Without it, an edit to an unbuilt row used to read as noop, which made it look
like your change did nothing. recompute tells you the difference was registered and is waiting to
be applied.
For exactly what every kind of edit triggers on every kind of row — and who acts on it — see lifecycle & reconcile.