From d2fdf1604271bc9b9625bf9656f22e2e585bbc7b Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 22 Feb 2026 16:59:50 +0100 Subject: [PATCH 1/6] Require WordPress 7.0+ --- ai-command.php | 11 +++++++++-- composer.json | 4 ++-- features/ai.feature | 10 ---------- features/credentials.feature | 19 +++++++++++++++++++ features/generate.feature | 15 +++++++++++++++ src/Credentials_Command.php | 4 ++++ 6 files changed, 49 insertions(+), 14 deletions(-) delete mode 100644 features/ai.feature diff --git a/ai-command.php b/ai-command.php index e3b9b56..fab0242 100644 --- a/ai-command.php +++ b/ai-command.php @@ -3,6 +3,7 @@ namespace WP_CLI\AI; use WP_CLI; +use WP_CLI\Utils; if ( ! class_exists( '\WP_CLI' ) ) { return; @@ -14,5 +15,11 @@ require_once $wpcli_ai_autoloader; } -WP_CLI::add_command( 'ai', AI_Command::class ); -WP_CLI::add_command( 'ai credentials', Credentials_Command::class ); +$wpcli_ai_before_invoke = static function () { + if ( Utils\wp_version_compare( '7.0-beta1', '<' ) ) { + WP_CLI::error( 'Requires WordPress 7.0 or greater.' ); + } +}; + +WP_CLI::add_command( 'ai', AI_Command::class, [ 'before_invoke' => $wpcli_ai_before_invoke ] ); +WP_CLI::add_command( 'ai credentials', Credentials_Command::class, [ 'before_invoke' => $wpcli_ai_before_invoke ] ); diff --git a/composer.json b/composer.json index 47691ba..58aea54 100644 --- a/composer.json +++ b/composer.json @@ -6,10 +6,10 @@ "license": "MIT", "authors": [], "require": { - "wordpress/wp-ai-client": "^0.3.0", - "wp-cli/wp-cli": "^2.12" + "wp-cli/wp-cli": "^2.13" }, "require-dev": { + "wordpress/wp-ai-client": "^0.3.0", "wp-cli/wp-cli-tests": "^5" }, "config": { diff --git a/features/ai.feature b/features/ai.feature deleted file mode 100644 index 035b867..0000000 --- a/features/ai.feature +++ /dev/null @@ -1,10 +0,0 @@ -Feature: Test that WP-CLI loads. - - Scenario: WP-CLI loads for your tests - Given a WP install - - When I run `wp eval 'echo "Hello world.";'` - Then STDOUT should contain: - """ - Hello world. - """ diff --git a/features/credentials.feature b/features/credentials.feature index 107d66f..0603aee 100644 --- a/features/credentials.feature +++ b/features/credentials.feature @@ -3,6 +3,18 @@ Feature: Manage AI provider credentials Background: Given a WP install + @less-than-wp-7.0 + Scenario: Command not available on WP < 7.0 + Given a WP install + + When I try `wp ai credentials list` + Then STDERR should contain: + """ + Requires WordPress 7.0 or greater. + """ + And the return code should be 1 + + @require-wp-7.0 Scenario: List credentials when none exist When I run `wp ai credentials list` Then STDOUT should contain: @@ -10,6 +22,7 @@ Feature: Manage AI provider credentials No credentials found. """ + @require-wp-7.0 Scenario: Set and list credentials When I run `wp ai credentials set openai --api-key=sk-test123456789` Then STDOUT should contain: @@ -23,6 +36,7 @@ Feature: Manage AI provider credentials [{"provider":"openai","api_key":"sk-*********6789"}] """ + @require-wp-7.0 Scenario: Get specific provider credentials When I run `wp ai credentials set anthropic --api-key=sk-ant-api-key-123` Then STDOUT should contain: @@ -40,6 +54,7 @@ Feature: Manage AI provider credentials "api_key":"sk-**********-123" """ + @require-wp-7.0 Scenario: Delete provider credentials When I run `wp ai credentials set google --api-key=test-google-key` Then STDOUT should contain: @@ -60,6 +75,7 @@ Feature: Manage AI provider credentials """ And the return code should be 1 + @require-wp-7.0 Scenario: Error when getting non-existent credentials When I try `wp ai credentials get nonexistent` Then STDERR should contain: @@ -68,6 +84,7 @@ Feature: Manage AI provider credentials """ And the return code should be 1 + @require-wp-7.0 Scenario: Error when setting credentials without api-key When I try `wp ai credentials set openai` Then STDERR should contain: @@ -76,6 +93,7 @@ Feature: Manage AI provider credentials """ And the return code should be 1 + @require-wp-7.0 Scenario: List multiple credentials in table format When I run `wp ai credentials set openai --api-key=sk-openai123` And I run `wp ai credentials set anthropic --api-key=sk-ant-api-456` @@ -85,6 +103,7 @@ Feature: Manage AI provider credentials | openai | sk-*****i123 | | anthropic | sk-*******-456 | + @require-wp-7.0 Scenario: Update existing credentials When I run `wp ai credentials set openai --api-key=old-key-123` Then STDOUT should contain: diff --git a/features/generate.feature b/features/generate.feature index 51db7fe..e9592d7 100644 --- a/features/generate.feature +++ b/features/generate.feature @@ -150,10 +150,23 @@ Feature: Generate AI content ); """ + @less-than-wp-7.0 + Scenario: Command not available on WP < 7.0 + Given a WP install + + When I try `wp ai generate text "Test prompt" --model=invalidformat` + Then STDERR should contain: + """ + Requires WordPress 7.0 or greater. + """ + And the return code should be 1 + + @require-wp-7.0 Scenario: Generate command validates model format When I try `wp ai generate text "Test prompt" --model=invalidformat` Then the return code should be 1 + @require-wp-7.0 Scenario: Generate command validates max-tokens When I try `wp ai generate text "Test prompt" --max-tokens=-5` Then the return code should be 1 @@ -162,6 +175,7 @@ Feature: Generate AI content Max tokens must be a positive integer """ + @require-wp-7.0 Scenario: Generate command validates top-p range When I try `wp ai generate text "Test prompt" --top-p=1.5` Then the return code should be 1 @@ -170,6 +184,7 @@ Feature: Generate AI content Top-p must be between 0.0 and 1.0 """ + @require-wp-7.0 Scenario: Generate command validates top-k positive When I try `wp ai generate text "Test prompt" --top-k=-10` Then the return code should be 1 diff --git a/src/Credentials_Command.php b/src/Credentials_Command.php index d2beea0..48e687f 100644 --- a/src/Credentials_Command.php +++ b/src/Credentials_Command.php @@ -220,6 +220,10 @@ private function get_all_credentials() { return array(); } + /** + * @var array $credentials + */ + return $credentials; } From 0b9982e7c73785c4be792c7a4b592c78c306f41d Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 22 Feb 2026 17:10:45 +0100 Subject: [PATCH 2/6] Remove dev dependency --- composer.json | 1 - 1 file changed, 1 deletion(-) diff --git a/composer.json b/composer.json index 58aea54..722acaf 100644 --- a/composer.json +++ b/composer.json @@ -9,7 +9,6 @@ "wp-cli/wp-cli": "^2.13" }, "require-dev": { - "wordpress/wp-ai-client": "^0.3.0", "wp-cli/wp-cli-tests": "^5" }, "config": { From 6dd1459c96685cd6c1f7792b48dc1282811a8a92 Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 22 Feb 2026 17:26:27 +0100 Subject: [PATCH 3/6] Use `wp_ai_client_prompt` --- src/AI_Command.php | 28 +--------------------------- 1 file changed, 1 insertion(+), 27 deletions(-) diff --git a/src/AI_Command.php b/src/AI_Command.php index c6716b7..6ae4e8f 100644 --- a/src/AI_Command.php +++ b/src/AI_Command.php @@ -117,8 +117,6 @@ class AI_Command extends WP_CLI_Command { * @return void */ public function generate( $args, $assoc_args ) { - $this->initialize_ai_client(); - list( $type, $prompt ) = $args; try { @@ -216,8 +214,6 @@ public function generate( $args, $assoc_args ) { * @return void */ public function check( $args, $assoc_args ) { - $this->initialize_ai_client(); - list( $prompt ) = $args; $type = $assoc_args['type'] ?? 'text'; @@ -281,11 +277,9 @@ public function check( $args, $assoc_args ) { * @return void */ public function status( $args, $assoc_args ) { - $this->initialize_ai_client(); - try { // Create a builder to check capabilities (using constant for consistency) - $builder = AI_Client::prompt(); + $builder = wp_ai_client_prompt(); // Check each capability $capabilities = array( @@ -424,24 +418,4 @@ private function generate_image( $builder, $assoc_args ) { WP_CLI::line( (string) $image_file->getDataUri() ); } } - - /** - * Ensures WordPress AI Client is available. - * - * @return void - */ - private function initialize_ai_client() { - \WordPress\AI_Client\AI_Client::init(); - - add_filter( - 'user_has_cap', - static function ( array $allcaps ) { - $allcaps[ \WordPress\AI_Client\Capabilities\Capabilities_Manager::PROMPT_AI_CAPABILITY ] = true; - - return $allcaps; - } - ); - - WP_CLI::do_hook( 'ai_client_init' ); - } } From 158b5a6a4df0a4753896d13608bfa5e23ed7110b Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 22 Feb 2026 18:22:11 +0100 Subject: [PATCH 4/6] More `wp_ai_client_prompt()` usage --- src/AI_Command.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/AI_Command.php b/src/AI_Command.php index 6ae4e8f..2f62d0f 100644 --- a/src/AI_Command.php +++ b/src/AI_Command.php @@ -120,7 +120,7 @@ public function generate( $args, $assoc_args ) { list( $type, $prompt ) = $args; try { - $builder = AI_Client::prompt( $prompt ); + $builder = wp_ai_client_prompt( $prompt ); if ( isset( $assoc_args['provider'] ) ) { $builder = $builder->using_provider( $assoc_args['provider'] ); @@ -218,7 +218,7 @@ public function check( $args, $assoc_args ) { $type = $assoc_args['type'] ?? 'text'; try { - $builder = AI_Client::prompt( $prompt ); + $builder = wp_ai_client_prompt( $prompt ); if ( 'text' === $type ) { $supported = $builder->is_supported_for_text_generation(); From 69386eb8b1b8e9e4f867c8107fbfe81c0e29c51d Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 22 Feb 2026 18:27:36 +0100 Subject: [PATCH 5/6] Fix test for `latest` --- features/generate.feature | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/features/generate.feature b/features/generate.feature index e9592d7..f9d2a42 100644 --- a/features/generate.feature +++ b/features/generate.feature @@ -28,6 +28,10 @@ Feature: Generate AI content use WordPress\AiClient\Results\Enums\FinishReasonEnum; use WordPress\AI_Client\API_Credentials\API_Credentials_Manager; + if ( ! interface_exists( 'WordPress\AiClient\Providers\Models\Contracts\ModelInterface' ) ) { + return; + } + class WP_CLI_Mock_Model implements ModelInterface, TextGenerationModelInterface { private $id; private $config; From f6c6ae99b853b285099d29c8a1f08d8e78516c4a Mon Sep 17 00:00:00 2001 From: Pascal Birchler Date: Sun, 22 Feb 2026 18:30:38 +0100 Subject: [PATCH 6/6] Temporary PHPStan fixes --- src/AI_Command.php | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/src/AI_Command.php b/src/AI_Command.php index 2f62d0f..b28df24 100644 --- a/src/AI_Command.php +++ b/src/AI_Command.php @@ -120,6 +120,7 @@ public function generate( $args, $assoc_args ) { list( $type, $prompt ) = $args; try { + // @phpstan-ignore function.notFound $builder = wp_ai_client_prompt( $prompt ); if ( isset( $assoc_args['provider'] ) ) { @@ -218,6 +219,7 @@ public function check( $args, $assoc_args ) { $type = $assoc_args['type'] ?? 'text'; try { + // @phpstan-ignore function.notFound $builder = wp_ai_client_prompt( $prompt ); if ( 'text' === $type ) { @@ -278,7 +280,7 @@ public function check( $args, $assoc_args ) { */ public function status( $args, $assoc_args ) { try { - // Create a builder to check capabilities (using constant for consistency) + // @phpstan-ignore function.notFound $builder = wp_ai_client_prompt(); // Check each capability @@ -306,15 +308,18 @@ public function status( $args, $assoc_args ) { * @param \WordPress\AI_Client\Builders\Prompt_Builder $builder The prompt builder. * @param array{format: string} $assoc_args Associative arguments. * @return void + * + * @phpstan-ignore class.notFound */ private function generate_text( $builder, $assoc_args ) { $format = $assoc_args['format'] ?? 'text'; - // Check if supported + // @phpstan-ignore class.notFound if ( ! $builder->is_supported_for_text_generation() ) { WP_CLI::error( 'Text generation is not supported. Make sure AI provider credentials are configured.' ); } + // @phpstan-ignore class.notFound $text = $builder->generate_text_result(); if ( 'json' === $format ) { @@ -334,8 +339,11 @@ private function generate_text( $builder, $assoc_args ) { "Summary:\nModel used: %s (%s)\nToken usage:\nInput tokens: %s\nOutput tokens: %s\nTotal: %s\n", $text->getModelMetadata()->getName(), $text->getProviderMetadata()->getName(), + // @phpstan-ignore class.notFound $token_usage[ TokenUsage::KEY_PROMPT_TOKENS ], + // @phpstan-ignore class.notFound $token_usage[ TokenUsage::KEY_COMPLETION_TOKENS ], + // @phpstan-ignore class.notFound $token_usage[ TokenUsage::KEY_TOTAL_TOKENS ], ), 'ai' @@ -345,12 +353,14 @@ private function generate_text( $builder, $assoc_args ) { /** * Generates an image from the prompt builder. * - * @param \WordPress\AI_Client\Builders\Prompt_Builder $builder The prompt builder. + * @param \WordPress\AI_Client\Builders\Prompt_Builder $builder The prompt builder. * @param array{'destination-file': string, stdout: bool} $assoc_args Associative arguments. * @return void + * + * @phpstan-ignore class.notFound */ private function generate_image( $builder, $assoc_args ) { - // Check if supported + // @phpstan-ignore class.notFound if ( ! $builder->is_supported_for_image_generation() ) { WP_CLI::error( 'Image generation is not supported. Make sure AI provider credentials are configured.' ); } @@ -368,6 +378,7 @@ private function generate_image( $builder, $assoc_args ) { } } + // @phpstan-ignore class.notFound $image_file = $builder->generate_image(); if ( isset( $assoc_args['destination-file'] ) ) {