hasProvider(CursorProvider::class)) { return; } $registry->registerProvider(CursorProvider::class); } add_action('init', __NAMESPACE__ . '\\register_provider', 5); /** * Returns whether the Cursor provider is registered and configured. * * Uses the AI Client registry only — does not read Connectors option values. * * @since 0.1.7 */ function is_cursor_provider_configured(): bool { if (!class_exists(AiClient::class)) { return false; } try { $registry = AiClient::defaultRegistry(); return $registry->hasProvider('cursor') && $registry->isProviderConfigured('cursor'); } catch (\Throwable $e) { unset($e); return false; } } /** * Overrides auto-discovered Connectors metadata (description and plugin file). * * Leaves authentication to core Connectors discovery. Does not read or write API keys. * * @since 0.1.0 * * @param \WP_Connector_Registry $registry Connector registry. */ function override_connectors_card($registry): void { if (!is_object($registry) || !method_exists($registry, 'is_registered')) { return; } if (!$registry->is_registered('cursor')) { return; } $connector = $registry->unregister('cursor'); if (!is_array($connector)) { return; } $connector['description'] = __( 'Text generation via Cursor Cloud Agents. Enter the API key on this Connectors screen (or set CURSOR_API_KEY). Polling and model options are under Settings → AI Provider for Cursor.', 'ai-provider-for-cursor' ); $connector['plugin'] = array( 'file' => 'ai-provider-for-cursor/ai-provider-for-cursor.php', 'is_active' => static function (): bool { return class_exists(AiClient::class) && AiClient::defaultRegistry()->hasProvider('cursor'); }, ); $registry->register('cursor', $connector); } add_action('wp_connectors_init', __NAMESPACE__ . '\\override_connectors_card', 20); /** * Declares Cursor credentials to the WordPress AI plugin when the provider is configured. * * @since 0.1.1 * * @param bool $has_credentials Whether AI credentials are available. * @param array $connectors Registered AI connectors. * @return bool */ function filter_has_ai_credentials(bool $has_credentials, array $connectors): bool { if ($has_credentials) { return true; } if (!isset($connectors['cursor'])) { return false; } return is_cursor_provider_configured(); } add_filter('wpai_has_ai_credentials', __NAMESPACE__ . '\\filter_has_ai_credentials', 10, 2); /** * Reports Cursor connector configuration status for the AI plugin status widget. * * @since 0.1.1 * * @param bool $configured Whether the connector is configured. * @param array $connector_data Connector metadata. * @return bool */ function filter_is_cursor_connector_configured(bool $configured, array $connector_data): bool { unset($connector_data); if ($configured) { return true; } return is_cursor_provider_configured(); } add_filter('wpai_is_cursor_connector_configured', __NAMESPACE__ . '\\filter_is_cursor_connector_configured', 10, 2); /** * Migrates a legacy nested API key into the Connectors option without reading it. * * Uses add_option() so an existing Connectors value is never overwritten or read. * Then strips api_key from the plugin settings option. * * @since 0.1.7 */ function maybe_migrate_legacy_api_key(): void { $stored = get_option(SettingsStore::OPTION_KEY, array()); if (!is_array($stored) || empty($stored['api_key']) || !is_string($stored['api_key'])) { return; } $key = sanitize_text_field($stored['api_key']); if ($key !== '') { add_option('connectors_ai_cursor_api_key', $key, '', false); } unset($stored['api_key']); update_option(SettingsStore::OPTION_KEY, $stored, false); } add_action('plugins_loaded', __NAMESPACE__ . '\\maybe_migrate_legacy_api_key', 5); /** * Boots admin settings UI. * * @since 0.1.0 */ function boot_admin(): void { if (!is_admin()) { return; } SettingsPage::register(); } add_action('plugins_loaded', __NAMESPACE__ . '\\boot_admin'); /** * Adds a settings link on the Plugins screen. * * @since 0.1.0 * * @param array $links Existing links. * @return array */ function plugin_action_links(array $links): array { $links[] = sprintf( '%s', esc_url(SettingsPage::url()), esc_html__('Settings', 'ai-provider-for-cursor') ); return $links; } add_filter('plugin_action_links_' . plugin_basename(__FILE__), __NAMESPACE__ . '\\plugin_action_links');