Skip to content

cwp pull

shipped 1.0.0
cwp 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.

tree ← site

ArgumentWhat it isDefault
[env]environment to read from (default: default_environment in cwp.yml)
FlagWhat it doesDefault
--only <artifact>narrow to one artifact
--no-design-assetsleave the design system’s fonts and other binaries unfetched (F-048)on
--sensitivedoes nothing since 2.0: credentials never enter the tree, and customCss travels on its ownoff
--forceoverride the refusals a crossing makesoff
--no-backupskip the remote backup taken before an adoptionon
--yesskip the confirmation promptoff
--with-agentwith —dry-run on a host with no shell: install and remove the PHP agent so the plan is realoff

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.

ArtifactWhere it landsIn detail
contentcontent/<type>/<slug>.yml, plus content/terms/, content/menus/ and content/authors.ymlcontent pull
bricksbricks/ — the design system as a tree of JSONbricks pull
settingssettings/<group>.ymlsettings pull
widgetswidgets/<area>.ymlwidgets pull
rolesroles.ymlroles pull
inventoryinventory.ymlinventory 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.