Skip to content

cwp ability

shipped 1.0.0
cwp ability <name> [json] [env] [flags]

Acts on the local site. Name an environment to act there instead.

ArgumentWhat it isDefault
<name>ability name, e.g. bricks/get-page-elements
[json]JSON input object. A leading { tells it from [env]
[env]environment to call on (default: default_environment in cwp.yml)
FlagWhat it doesDefault
--input-file <path>read the JSON input from a file instead
--allfollow hasMore and return every page (B-017)off
--from <repo-path>the committed file this payload comes from (P2)
--adhocaccept that this change is not reproducible, and record itoff
--forceoverride the protected-environment refusaloff
--no-backupskip the remote backup taken before the writeon
--no-snapshotskip the local design-system export taken before a destructive callon
--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

Calls one ability by name. It is the plain door onto the WordPress Abilities API: the name pattern accepts any namespace, and everything cwp does not wrap would otherwise need a throwaway wp eval-file script.

The result goes to stdout as pretty-printed JSON, so it pipes into jq. With --json it moves into the envelope’s data.result instead, because that stream may only ever carry one object.

The leading brace tells the input from the environment. Every ability takes a JSON object, so cwp ability bricks/get-mcp-version dev and cwp ability bricks/get-page-elements '{"slug":"x"}' dev both parse. cwp refuses the reverse order by name rather than silently reordering it. --input-file takes the same JSON from a file, for payloads too big to quote.

A namespace mistaken for another gets an answer rather than a guess:

✗ "bricks/get-posts" is not registered on this site
  → did you mean `snn/get-posts`? — run `cwp abilities` to see all 175

The guards come from the ability, not from a flag

A general-purpose door onto production is only safe if it knows which calls write. The abilities say so themselves, in their readonly, destructive and idempotent annotations.

A read runs unguarded, and runs under --dry-run for real. Anything else against a remote takes the full guard set: a protected environment refuses without --force, cwp asks for confirmation and takes a remote backup first. Under --dry-run cwp describes a write and does not run it.

cwp treats an ability that declares nothing as a write, and says so. The SNN namespace annotates none of its 24, so snn/get-posts gets the guards despite being a getter. Rounding that way is deliberate: guarding a read costs a confirmation, and letting an undeclared write through against production costs the thing this tool exists to prevent.

An upward write needs a committed source

The applier refuses any effect that changes a remote with nothing in the repository behind it. cwp push has its tracked paths, cwp push --only inventory has inventory.yml, and cwp push --only bricks has the bricks/ tree. An ad-hoc ability call is the one place in the tool with no such file. So a write against a remote takes one of two flags:

  • --from <repo-path> names the committed file the payload came from, and is the normal form.
  • --adhoc does not waive the rule, it records it. cwp writes the payload to cwp/adhoc/<timestamp>-<ability>.json before the call, and the run says out loud that the change is not reproducible. A protected environment refuses it outright: there, “I accept this is not reproducible” is not an answer anybody should give in passing.

Reads need neither, and a call against the local site needs neither.

--all, and why it is not the default

Every Bricks listing ability answers with 25 items and reports the rest in total and hasMore beside them. Reading items and moving on means silently working with a third of the site. That has cost real time on a real project (B-017).

The default is one page, because a caller piping into jq should get what it asked for. The warning is not optional:

! "bricks/list-global-classes" returned 25 of 32 items and there are more —
  pass --all to follow the pages (B-017)

--all follows hasMore and returns one merged listing with hasMore cleared. On an ability that takes no page argument it is a no-op with a warning, not an error.

The design system gets a restore point first

A destructive call in a design-system category (bricks/delete-global-class and its neighbours) takes a local export into cwp/bricks-snapshots/<timestamp>/ before it runs. Bricks keeps no revisions of those items: a delete is immediate and final, and an export is the only way back. --no-snapshot waives it and says what you are waiving. Against a remote the provider’s backup already covers it, so the snapshot is local only.

An unrecognised category snapshots too. Silence is not a licence. cwp reads an ability that declares neither readonly nor destructive the same way.

Example

cwp ability bricks/get-page-elements '{"slug":"about-us"}'
cwp ability bricks/list-global-classes --all
cwp ability bricks/import-transfer-package --from bricks/ prod
cwp ability bricks/set-page-settings '{"postId":42,"settings":{}}' prod --adhoc

What it does not do

It does not validate your input against the ability’s schema. Bricks does that, and cwp adds the list of accepted properties to the error. There is no wp ability command to print one.

It does not make an unreachable ability surface reachable. It does not soften an ability’s own annotation: there is no flag that says “trust me, this one is a read”.