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..722acaf 100644 --- a/composer.json +++ b/composer.json @@ -6,8 +6,7 @@ "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": { "wp-cli/wp-cli-tests": "^5" 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..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; @@ -150,10 +154,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 +179,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 +188,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/AI_Command.php b/src/AI_Command.php index c6716b7..b28df24 100644 --- a/src/AI_Command.php +++ b/src/AI_Command.php @@ -117,12 +117,11 @@ class AI_Command extends WP_CLI_Command { * @return void */ public function generate( $args, $assoc_args ) { - $this->initialize_ai_client(); - list( $type, $prompt ) = $args; try { - $builder = AI_Client::prompt( $prompt ); + // @phpstan-ignore function.notFound + $builder = wp_ai_client_prompt( $prompt ); if ( isset( $assoc_args['provider'] ) ) { $builder = $builder->using_provider( $assoc_args['provider'] ); @@ -216,13 +215,12 @@ 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'; try { - $builder = AI_Client::prompt( $prompt ); + // @phpstan-ignore function.notFound + $builder = wp_ai_client_prompt( $prompt ); if ( 'text' === $type ) { $supported = $builder->is_supported_for_text_generation(); @@ -281,11 +279,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(); + // @phpstan-ignore function.notFound + $builder = wp_ai_client_prompt(); // Check each capability $capabilities = array( @@ -312,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 ) { @@ -340,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' @@ -351,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.' ); } @@ -374,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'] ) ) { @@ -424,24 +429,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' ); - } } 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; }