Replace per-command abilities with single wp-cli/execute ability#7
Replace per-command abilities with single wp-cli/execute ability#7superdav42 merged 3 commits intomainfrom
Conversation
Point wp-cli at shared WordPress 7.0-RC2 multisite dev install at ../wordpress (wordpress.local:8080). Documents reset workflow and WP-CLI usage in AGENTS.md.
Agents now pass WP-CLI commands as plain text strings instead of parsing hundreds of individual tool definitions. This reduces token overhead from ~500+ ability schemas to one. - Rewrite executor to accept raw command strings with a tokenizer - Use array-based proc_open (PHP 7.4+) for shell-free execution - Delete schema builder (no per-command schemas needed) - Remove Network: true constraint — works on single-site and multisite - Check blocklist and permissions at execution time per command - Bump version to 2.0.0
📝 WalkthroughWalkthroughThe plugin shifts from discovering and registering many WP-CLI command abilities to offering a single Changes
Sequence DiagramsequenceDiagram
participant Client
participant Abilities as WordPress<br/>Abilities API
participant Executor as Command<br/>Executor
participant Blocklist as Blocklist<br/>Cache
participant Perms as Permissions<br/>Classifier
participant WPCLI as WP-CLI via<br/>proc_open
Client->>Abilities: Call wp-cli/execute(command)
Abilities->>Executor: execute(command)
Executor->>Executor: Tokenize command (quotes/escapes)
Executor->>Executor: Derive command_path from tokens
Executor->>Blocklist: is_blocked(command_path)
alt blocked
Blocklist-->>Executor: WP_Error (403)
Executor-->>Abilities: Return error
else allowed
Executor->>Perms: classify(command_path)
Perms-->>Executor: level
Executor->>Perms: check_level(level)
alt permission denied
Perms-->>Executor: WP_Error (403)
Executor-->>Abilities: Return error
else permission granted
Executor->>Executor: Inject flags (--path,--url,--no-color,--user)
Executor->>WPCLI: proc_open(array of args)
WPCLI-->>Executor: stdout/stderr/exit
Executor->>Executor: Parse output / update current site URL if needed
Executor-->>Abilities: Return result
end
end
Abilities-->>Client: JSON response
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
inc/class-command-executor.php (1)
170-227: Unclosed quotes are silently accepted.The tokenizer doesn't validate that quotes are properly closed. An input like
post list --title="testwill include the trailing content in$currentwithout error. This could lead to unexpected behavior.Consider adding validation for unclosed quotes and returning a
WP_Errorwhen detected.♻️ Optional: Add unclosed quote detection
if ($current !== '') { $tokens[] = $current; } + if ($in_single || $in_double) { + return []; // Return empty to trigger "Could not parse" error + } + return $tokens;🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@inc/class-command-executor.php` around lines 170 - 227, The tokenize method currently accepts inputs with unclosed single or double quotes (function tokenize), causing silent inclusion of trailing text; add detection after the loop to check the state flags $in_single and $in_double and, if either is true, return a WP_Error (or throw/propagate an error consistent with the plugin's error handling) instead of returning tokens; update any callers of tokenize to handle WP_Error (or the chosen error type) so malformed commands like --title="test are rejected with a clear error.inc/class-wp-cli-abilities.php (1)
35-36: Minor: Use Yoda condition.Per coding guidelines, PHP code should use Yoda conditions.
♻️ Suggested fix
- if (self::$instance === null) { + if (null === self::$instance) {As per coding guidelines: "Use Yoda conditions in PHP code"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@inc/class-wp-cli-abilities.php` around lines 35 - 36, The conditional currently checks "self::$instance === null" which violates the project's Yoda condition rule; change the comparison to "null === self::$instance" wherever this singleton instantiation appears (the block that sets "self::$instance = new self()"), i.e., replace "if (self::$instance === null)" with "if (null === self::$instance)" to conform to Yoda style for the self::$instance check.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@inc/class-command-executor.php`:
- Around line 238-251: extract_command_path() currently collects all non-flag
tokens so positional args like IDs become part of the path and make classify()
pick the wrong leaf; modify extract_command_path() to stop collecting tokens
when a token looks like a positional argument (e.g., purely numeric or starts
with a digit or contains characters that indicate an argument rather than a
verb/subcommand) so the returned path contains only command and subcommand
words; ensure this change fixes classify()’s leaf-based lookup (the functions to
update are extract_command_path and any code that relies on its output in
classify(), which compares the leaf against READ_ACTIONS and
DESTRUCTIVE_ACTIONS).
---
Nitpick comments:
In `@inc/class-command-executor.php`:
- Around line 170-227: The tokenize method currently accepts inputs with
unclosed single or double quotes (function tokenize), causing silent inclusion
of trailing text; add detection after the loop to check the state flags
$in_single and $in_double and, if either is true, return a WP_Error (or
throw/propagate an error consistent with the plugin's error handling) instead of
returning tokens; update any callers of tokenize to handle WP_Error (or the
chosen error type) so malformed commands like --title="test are rejected with a
clear error.
In `@inc/class-wp-cli-abilities.php`:
- Around line 35-36: The conditional currently checks "self::$instance === null"
which violates the project's Yoda condition rule; change the comparison to "null
=== self::$instance" wherever this singleton instantiation appears (the block
that sets "self::$instance = new self()"), i.e., replace "if (self::$instance
=== null)" with "if (null === self::$instance)" to conform to Yoda style for the
self::$instance check.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: f8df3cb0-94f5-485b-8123-6c08535da298
📒 Files selected for processing (8)
AGENTS.mdcli-abilities-bridge.phpcomposer.jsoninc/class-command-executor.phpinc/class-schema-builder.phpinc/class-wp-cli-abilities.phpreadme.txtwp-cli.yml
💤 Files with no reviewable changes (1)
- inc/class-schema-builder.php
| private static function extract_command_path(array $tokens): string { | ||
|
|
||
| if ($current_user_id > 0) { | ||
| $cmd_parts[] = '--user=' . escapeshellarg((string) $current_user_id); | ||
| } | ||
| $path_parts = []; | ||
|
|
||
| // Ensure non-interactive. | ||
| $cmd_parts[] = '--no-color'; | ||
| foreach ($tokens as $token) { | ||
| if (str_starts_with($token, '-')) { | ||
| break; | ||
| } | ||
|
|
||
| $command = implode(' ', $cmd_parts); | ||
| $path_parts[] = $token; | ||
| } | ||
|
|
||
| $result = self::run($command, $command_path); | ||
| return implode(' ', $path_parts); | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check how classify() handles the leaf extraction
ast-grep --pattern $'public static function classify(string $command_path): string {
$$$
}'Repository: Ultimate-Multisite/cli-abilities-bridge
Length of output: 65
🏁 Script executed:
fd -type f -name "*command-permissions*"Repository: Ultimate-Multisite/cli-abilities-bridge
Length of output: 253
🏁 Script executed:
rg "function classify" -A 20 --type phpRepository: Ultimate-Multisite/cli-abilities-bridge
Length of output: 1356
🏁 Script executed:
rg "extract_command_path" -B 5 -A 15 --type phpRepository: Ultimate-Multisite/cli-abilities-bridge
Length of output: 2501
🏁 Script executed:
rg "READ_ACTIONS|DESTRUCTIVE_ACTIONS" -B 2 -A 10 inc/class-command-permissions.phpRepository: Ultimate-Multisite/cli-abilities-bridge
Length of output: 897
Positional arguments included in command path may cause overly restrictive permission classification.
The extract_command_path() method includes all non-flag tokens as part of the command path. For commands like post get 123 --format=json, this results in post get 123 being extracted instead of just post get.
The classify() method then uses the last word (leaf) of this path to determine permission level. With positional arguments included, the leaf becomes 123 instead of get. Since 123 doesn't match any action in READ_ACTIONS or DESTRUCTIVE_ACTIONS, the command defaults to LEVEL_WRITE, requiring manage_network capability for what should be a LEVEL_READ operation.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@inc/class-command-executor.php` around lines 238 - 251,
extract_command_path() currently collects all non-flag tokens so positional args
like IDs become part of the path and make classify() pick the wrong leaf; modify
extract_command_path() to stop collecting tokens when a token looks like a
positional argument (e.g., purely numeric or starts with a digit or contains
characters that indicate an argument rather than a verb/subcommand) so the
returned path contains only command and subcommand words; ensure this change
fixes classify()’s leaf-based lookup (the functions to update are
extract_command_path and any code that relies on its output in classify(), which
compares the leaf against READ_ACTIONS and DESTRUCTIVE_ACTIONS).
Summary
wp-cli/executeability that accepts raw command strings — agents pass commands the same way they would use bashproc_open(PHP 7.4+) for shell-free execution, eliminating injection risk entirelyNetwork: trueconstraint so the plugin works on both single-site and multisiteWhy
AI agents understand bash. Parsing 500+ individual tool definitions wastes tokens and adds no value — the agent already knows WP-CLI syntax. One ability with
{ command: "post list --format=json" }is both cheaper and more natural.What changed
inc/class-wp-cli-abilities.phpinc/class-command-executor.phpinc/class-schema-builder.phpcli-abilities-bridge.phpNetwork: true, bumped to v2.0.0.readme.txtAGENTS.mdcomposer.jsonBlocklist, permissions, system commands, and system executor are unchanged.
Summary by CodeRabbit
New Features
wp-cli/executeability replaces individual command discovery.--url.Documentation
wp abilities syncworkflow documentation.