Skip to content

cwp ability

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

Acts on the local site unless an environment is named.

ArgumentWhat it isDefault
<name>ability name, e.g. bricks/get-page-elements
[json]JSON input object — a leading { is what tells it from [env]
[env]environment to call on (omit for the local site)
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

Plus the shared flags --json, --verbose 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 was otherwise reachable only by writing 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.

Input is told from the environment by its leading brace. 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. The reverse order is refused by name rather than silently reordered. --input-file takes the same JSON from a file, for payloads too big to quote.

Mistaking one namespace for another is answered rather than guessed at:

✗ "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 — and 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, the call is confirmed, and a remote backup is taken first. Under --dry-run a write is described and not executed.

An ability that declares nothing is treated as a write, and says so. The SNN namespace annotates none of its 24, so snn/get-posts is guarded despite being a getter. That is the deliberate direction to round in: 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 plugins apply has plugins.yml, cwp bricks push has the bricks/ tree — and 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. The payload is written to .cwp/adhoc/<timestamp>-<ability>.json before the call, and the run says out loud that the change is not reproducible. It is refused outright on a protected environment — there, “I accept this is not reproducible” is not an answer anybody should be able to 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, which 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. Being told 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, because those items are not revisioned: 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 — the same reading given to an ability that declares neither readonly nor destructive.

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, since there is no wp ability command to print one.

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