cwp pull
shipped 1.0.0cwp pull [env] [flags]
Acts on the environment the project makes obvious: the only one it defines, or the one called dev. Name another to act there. A project with several and no dev has to name one.
| Argument | What it is | Default |
|---|---|---|
[env] | environment to read from (default: default_environment in cwp.yml) |
| Flag | What it does | Default |
|---|---|---|
--only <artifact> | narrow to one artifact | |
--no-design-assets | leave the design system’s fonts and other binaries unfetched (F-048) | on |
--sensitive | does nothing since 2.0: credentials never enter the tree, and customCss travels on its own | off |
--force | override the refusals a crossing makes | off |
--no-backup | skip the remote backup taken before an adoption | on |
--yes | skip the confirmation prompt | off |
--with-agent | with —dry-run on a host with no shell: install and remove the PHP agent so the plan is real | off |
Plus the shared flags --json, -v, --verbose, -q, --quiet and --dry-run.
What it does
cwp pull reads an environment’s tree down: the six artifacts cwp manages,
written into files you can read, diff and commit.
| Artifact | Where it lands | In detail |
|---|---|---|
content | content/<type>/<slug>.yml, plus content/terms/, content/menus/ and content/authors.yml | content pull |
bricks | bricks/ — the design system as a tree of JSON | bricks pull |
settings | settings/<group>.yml | settings pull |
widgets | widgets/<area>.yml | widgets pull |
roles | roles.yml | roles pull |
inventory | inventory.yml | inventory snapshot |
The right-hand column is the older spelling of each read, kept as an alias for one minor cycle. Those pages hold an artifact’s own rules: what the design system leaves behind, how a settings group refuses a credential, why a role’s denied capabilities sit in their own list. This page is the verb; they are the payloads.
Those paths are also what the dirty-tree refusal watches. A crossing refuses on uncommitted work in these, and nowhere else.
One command reads all of it for a reason. A partial read makes the baseline
incoherent. If base means “the state both sides agreed on”, then after
reading one page there is no such state. There is only a patchwork whose items
carry baselines from different days, and what is the base for prod has no
answer. Reading the whole tree gives base one environment revision at one
point in time. --only <artifact> still narrows a run. It does not move the
baseline for the artifacts it skipped.
It writes to the site in one case, and says so. It imports nothing, sets no option and places no file in the docroot. The result is files in your working copy. The exception is adoption: a post the tree has never seen has no identity that survives a rename, so cwp mints one onto it. That is a write to the environment cwp is reading, and the plan declares it as one.
Declared means guarded. A pull that adopts refuses a protected environment
without --force, takes a restore point first, and shows the write in the plan
before it asks. A pull of an environment whose posts all carry identities
declares no such effect and is a read in the plain sense: no refusal, no
backup, nothing.
--no-backup skips the restore point. It is the escape from the backup, not
from the refusal.
It writes down the people who wrote the content. Each item records its
author’s login, and content/authors.yml records those people so a push can
resolve the login on another site. They keep their real names and addresses
through the scrub. That keeps the record usable, and it makes the record
personal data in your repository. The tree states the
boundary: the editorial staff travels, the customer database never does.
It is not the database. cwp db pull brings the database
down and cwp media pull brings the media files. Those two
replace what you have locally; this one does not touch it. The three nouns are
three payloads, and the tree is the one with a history and a baseline.
It refuses rather than clobbering
The verb is git’s, so the behaviour is git’s. Two refusals fire before cwp writes anything, and both name what they found:
Uncommitted work in a path this run writes. Not any dirty file: the README you are editing is none of the crossing’s business. Only the paths the selected artifacts land in:
✗ the tree has uncommitted changes in 3 path(s) this run would overwrite
→ commit or stash them first — cwp/content/page/pricing.md, settings.yml … —
or pass --force to overwrite them (the run records a snapshot you can recover from)
A project that is not a git repository passes this refusal. A missing git is not an answer from git, and cwp never claimed git as a requirement.
An item that changed on both sides. The drift read sees changed-both, and
this refusal acts on it. Choosing a side silently is the one thing a tool must
not do here: both changes are somebody’s work.
✗ 2 item(s) changed on both sides since cwp last transferred
→ look at them with `git diff`, then choose per item: page/pricing, post/hello —
commit the tree's version and push it, or pass --force to take the site's
The refusal sees content and does not yet see the other five artifacts. It needs a per-item hash on each side, and only content produces one. This page names that limit rather than leaving you to discover it: a refusal that quietly checks less than its name suggests is worse than one that does not exist.
A third refusal comes before both: cwp does not cross an environment that
was never adopted. cwp adopt gives the tree its relationship
with an environment.
The snapshot taken first
Before the first overwriting write, the runtime records a snapshot of the tree
with git stash create, so --force is recoverable rather than final. It comes
after the confirmation: a pull you decline leaves nothing behind.
The design system’s own files
A Bricks custom font is a media attachment like any other. It is also the face
every heading on the site uses, and cwp push can only send a
font file it can read on this machine. So the pull fetches those files whatever
the uploads strategy says: a handful of files, named by the builder,
transferred one by one.
✓ design-system assets — 2 of 2 file(s) fetched from dev — 2026/02/kula.woff
--no-design-assets switches it off. It applies to the design system, so cwp
refuses it under an --only that does not carry it. --sensitive stays
accepted for one minor cycle and does nothing. The builder’s settings travel by
a rule per key now, and a credential never enters the tree. The custom CSS
travels as its own file (see cwp bricks pull). Everything
about the fetch is best-effort. If the environment does not answer or the site
cannot say which files it needs, the pull warns and finishes. The push still
refuses to send a font it could not carry.
The media the content points at
After the artifacts, cwp reads the attachments the pulled items refer to and
brings those files into the tree. The capture has a budget on purpose, because
git stores every version of a binary whole and forever. A capture that hits
media.max_files or media.max_megabytes stops and says which files it left:
! 34 file(s) left on the site — file budget reached. Raise `media.max_files` or
`media.max_megabytes`, or leave them there: git stores every version of a binary
whole and forever
That is a warning rather than a step. The files that did not come down are the ones somebody has to decide about.
Example
cwp pull # the obvious environment, every artifact
cwp pull prod # name one explicitly
cwp pull --only content # narrow to one artifact
cwp pull --no-design-assets # skip the builder's binaries
cwp pull --dry-run --verbose # print the plan and run nothing
A dry run names the artifacts it would read and, for content, how many posts
that is. It also names the posts with no cwp identity yet. Giving one is the
single write a read makes:
✓ would read — content, bricks, settings, widgets, roles, inventory from "prod"
content: 7 post(s) from "prod"
The other five learn what they carry by carrying it, so they say nothing rather than a line each saying so.
Then read what arrived the way you read any other change:
git diff
What it does not do
It does not change what a site holds. The one write it makes is an identity marker on a post that had none. A visitor or an editor never sees it, and it alters no content, settings or configuration.
It does not import a database. A tree pull leaves your local database
exactly as it was. If you want the site’s data, run
cwp db pull. The mail guard, the captcha sandbox keys and the
scrub ride with that command.
The run says so on its last line. The manual is not open at the moment somebody types the command out of habit:
your local WordPress is untouched — the database and the uploads are `cwp db pull` and `cwp media pull`
It does not merge. There is no three-way merge and no conflict markers: it
refuses, and you resolve per item with git diff and the two directions cwp
already has.
It does not fetch themes or plugins. Those are code, .gitignore covers
them, and cwp fetch brings them down.