Plugin Directory

Changeset 3457513


Ignore:
Timestamp:
02/09/2026 09:59:36 PM (7 weeks ago)
Author:
visiblefirst
Message:

Release 3.2.26 - Security: Move llms.txt prompt to server-side API

Location:
visiblefirst
Files:
6 edited
1 copied

Legend:

Unmodified
Added
Removed
  • visiblefirst/tags/3.2.26/includes/class-visibl-llms-generator.php

    r3457500 r3457513  
    685685    }
    686686
     687
    687688    /**
    688689     * Phase 2: AI Enhancement
    689690     *
    690      * Send crawl data to Claude API for llms.txt generation
     691     * Send crawl data to server API for llms.txt generation
     692     * NOTE: System prompt is server-side - we only send crawl data
    691693     *
    692694     * @param array $crawl_data Output from Phase 1
     
    694696     */
    695697    public static function generate_with_ai($crawl_data) {
    696         // Build the system prompt
    697         $system_prompt = self::get_system_prompt();
    698 
    699698        // Truncate crawl data to fit within token limits
    700699        $truncated_data = self::truncate_crawl_data($crawl_data);
    701700
    702         // Build the user message with crawl data
    703         $user_message = "Here is the crawl data for {$truncated_data['site_url']}:\n\n" .
    704                         json_encode($truncated_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    705 
    706         // Call Claude API via VF AI Gateway
    707         $response = Visibl_AI::complete([
    708             'system' => $system_prompt,
    709             'prompt' => $user_message,
    710             'task' => 'llms_txt_generation',
     701        // Build the crawl data JSON
     702        $crawl_json = json_encode($truncated_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
     703
     704        // Call server-side llms-generate endpoint (prompt is server-side)
     705        $api_key = Visibl_AI::get_effective_api_key();
     706       
     707        $request_body = [
     708            'crawl_data' => $crawl_json,
     709            'site_url' => home_url(),
     710            'site_name' => get_bloginfo('name'),
     711            'site_id' => Visibl_Licensing::get_site_id(),
    711712            'model' => 'claude-sonnet',
    712             'temperature' => 0,
    713             'max_tokens' => 2500,
     713            'max_tokens' => 4000,
     714        ];
     715
     716        $response = wp_remote_post(VISIBL_API_BASE . '/ai/llms-generate', [
     717            'headers' => [
     718                'Content-Type' => 'application/json',
     719                'X-API-Key' => $api_key,
     720                'X-Site-Domain' => wp_parse_url(home_url(), PHP_URL_HOST),
     721            ],
     722            'body' => wp_json_encode($request_body),
     723            'timeout' => 90,
    714724        ]);
    715725
    716726        if (is_wp_error($response)) {
    717             return $response;
    718         }
    719 
    720         $content = $response['content'] ?? '';
     727            return new WP_Error('api_error', __('Could not connect to VisibleFirst API.', 'visiblefirst'));
     728        }
     729
     730        $status_code = wp_remote_retrieve_response_code($response);
     731        $body = json_decode(wp_remote_retrieve_body($response), true);
     732
     733        if ($status_code !== 200 || empty($body['success'])) {
     734            $error_message = $body['message'] ?? __('Generation failed', 'visiblefirst');
     735            return new WP_Error('generation_failed', $error_message);
     736        }
     737
     738        $content = $body['content'] ?? '';
    721739
    722740        if (empty($content)) {
     
    731749    }
    732750
    733     /**
    734      * Get the system prompt for Claude
    735      */
    736     private static function get_system_prompt() {
    737         $prompt = 'You are an expert at writing llms.txt files that help AI assistants understand and recommend businesses.' . "\n\n";
    738         $prompt .= 'You will receive structured data about a website. Generate a complete llms.txt file following the llms.txt specification.' . "\n\n";
    739         $prompt .= 'RULES:' . "\n";
    740         $prompt .= '1. ONLY use information from the provided data. Never invent or assume.' . "\n";
    741         $prompt .= '2. If data is missing, use [PLACEHOLDER: describe what\'s needed] so the user can fill it in.' . "\n";
    742         $prompt .= '3. The description should answer: "If someone asked an AI to recommend a business like this, what would the AI need to know to recommend THIS one specifically?"' . "\n";
    743         $prompt .= '4. Include differentiators, not category descriptions.' . "\n";
    744         $prompt .= '   BAD: "A restaurant in Austin"' . "\n";
    745         $prompt .= '   GOOD: "Family-owned Tex-Mex restaurant in Austin\'s East Side known for breakfast tacos and house-made salsas since 2015"' . "\n";
    746         $prompt .= '5. Every page listed must use the EXACT path from the crawl data.' . "\n";
    747         $prompt .= '6. Group pages logically (Core, Services/Products, Resources, Legal).' . "\n";
    748         $prompt .= '7. Include a "Recommended Queries" section — example questions a user might ask an AI where this business should appear in the answer.' . "\n\n";
    749         $prompt .= 'OUTPUT FORMAT (follow exactly):' . "\n";
    750         $prompt .= '---' . "\n";
    751         $prompt .= '# {Business Name}' . "\n\n";
    752         $prompt .= '## About' . "\n";
    753         $prompt .= '{2-3 sentence description that is SPECIFIC enough to differentiate this business from every competitor. Include: what they do, who they serve, where they operate, and what makes them different.}' . "\n\n";
    754         $prompt .= '## Specialties' . "\n";
    755         $prompt .= '{Bullet list of specific services/products/expertise areas}' . "\n\n";
    756         $prompt .= '## Key Facts' . "\n";
    757         $prompt .= '- Founded: {year or [PLACEHOLDER: founding year]}' . "\n";
    758         $prompt .= '- Location: {city, state or [PLACEHOLDER: location]}' . "\n";
    759         $prompt .= '- Service Area: {local/regional/national/global}' . "\n";
    760         $prompt .= '- Industry: {specific industry}' . "\n\n";
    761         $prompt .= '## Key Pages' . "\n\n";
    762         $prompt .= '### Core' . "\n";
    763         $prompt .= '{Main pages with path and description - only include pages that exist in crawl data}' . "\n\n";
    764         $prompt .= '### Services/Products' . "\n";
    765         $prompt .= '{Service and product pages with descriptions}' . "\n\n";
    766         $prompt .= '### Resources' . "\n";
    767         $prompt .= '{Blog, guides, tools - include category pages if they have content}' . "\n\n";
    768         $prompt .= '### Legal' . "\n";
    769         $prompt .= '{Privacy, terms, etc. if they exist}' . "\n\n";
    770         $prompt .= '## Recommended Queries' . "\n";
    771         $prompt .= '{5-8 example questions where this business should be recommended. Make them natural questions a person would ask an AI.}' . "\n\n";
    772         $prompt .= '## Contact' . "\n";
    773         $prompt .= '{All contact methods available in the data}' . "\n";
    774         $prompt .= '---' . "\n\n";
    775         $prompt .= 'IMPORTANT:' . "\n";
    776         $prompt .= '- Only output the llms.txt content. No explanation, no markdown code blocks.' . "\n";
    777         $prompt .= '- Start directly with # {Business Name}' . "\n";
    778         $prompt .= '- Use exact paths from the pages array in the crawl data' . "\n";
    779         $prompt .= '- If a section would be empty, include it with [PLACEHOLDER: description of what\'s needed]';
    780 
    781         return $prompt;
    782     }
    783751
    784752    /**
  • visiblefirst/tags/3.2.26/readme.txt

    r3457500 r3457513  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 3.2.25
     7Stable tag: 3.2.26
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    394394== Changelog ==
    395395
     396= 3.2.26 =
     397* SECURITY: Moved llms.txt generation prompt to server-side API
     398* Prompts and API keys are no longer exposed in client plugin
     399
    396400= 3.2.25 =
    397401* FIX: llms.txt generation now handles large sites by truncating crawl data to fit API token limits
     
    431435== Upgrade Notice ==
    432436
     437= 3.2.26 =
     438Security improvement: AI prompts now handled server-side.
     439
    433440= 3.2.25 =
    434441Fixes llms.txt generation errors on large sites.
  • visiblefirst/tags/3.2.26/visiblefirst.php

    r3457500 r3457513  
    33 * Plugin Name: VisibleFirst
    44 * Description: AI + SEO + Social visibility in one plugin. Complete visibility optimization for WordPress.
    5  * Version: 3.2.25
     5 * Version: 3.2.26
    66 * Author: VisibleFirst
    77 * Author URI: https://visiblefirst.com
     
    1616
    1717// Plugin constants
    18 define('VISIBL_VERSION', '3.2.25');
     18define('VISIBL_VERSION', '3.2.26');
    1919define('VISIBL_PLUGIN_DIR', plugin_dir_path(__FILE__));
    2020define('VISIBL_PLUGIN_URL', plugin_dir_url(__FILE__));
  • visiblefirst/trunk/includes/class-visibl-llms-generator.php

    r3457500 r3457513  
    685685    }
    686686
     687
    687688    /**
    688689     * Phase 2: AI Enhancement
    689690     *
    690      * Send crawl data to Claude API for llms.txt generation
     691     * Send crawl data to server API for llms.txt generation
     692     * NOTE: System prompt is server-side - we only send crawl data
    691693     *
    692694     * @param array $crawl_data Output from Phase 1
     
    694696     */
    695697    public static function generate_with_ai($crawl_data) {
    696         // Build the system prompt
    697         $system_prompt = self::get_system_prompt();
    698 
    699698        // Truncate crawl data to fit within token limits
    700699        $truncated_data = self::truncate_crawl_data($crawl_data);
    701700
    702         // Build the user message with crawl data
    703         $user_message = "Here is the crawl data for {$truncated_data['site_url']}:\n\n" .
    704                         json_encode($truncated_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
    705 
    706         // Call Claude API via VF AI Gateway
    707         $response = Visibl_AI::complete([
    708             'system' => $system_prompt,
    709             'prompt' => $user_message,
    710             'task' => 'llms_txt_generation',
     701        // Build the crawl data JSON
     702        $crawl_json = json_encode($truncated_data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES);
     703
     704        // Call server-side llms-generate endpoint (prompt is server-side)
     705        $api_key = Visibl_AI::get_effective_api_key();
     706       
     707        $request_body = [
     708            'crawl_data' => $crawl_json,
     709            'site_url' => home_url(),
     710            'site_name' => get_bloginfo('name'),
     711            'site_id' => Visibl_Licensing::get_site_id(),
    711712            'model' => 'claude-sonnet',
    712             'temperature' => 0,
    713             'max_tokens' => 2500,
     713            'max_tokens' => 4000,
     714        ];
     715
     716        $response = wp_remote_post(VISIBL_API_BASE . '/ai/llms-generate', [
     717            'headers' => [
     718                'Content-Type' => 'application/json',
     719                'X-API-Key' => $api_key,
     720                'X-Site-Domain' => wp_parse_url(home_url(), PHP_URL_HOST),
     721            ],
     722            'body' => wp_json_encode($request_body),
     723            'timeout' => 90,
    714724        ]);
    715725
    716726        if (is_wp_error($response)) {
    717             return $response;
    718         }
    719 
    720         $content = $response['content'] ?? '';
     727            return new WP_Error('api_error', __('Could not connect to VisibleFirst API.', 'visiblefirst'));
     728        }
     729
     730        $status_code = wp_remote_retrieve_response_code($response);
     731        $body = json_decode(wp_remote_retrieve_body($response), true);
     732
     733        if ($status_code !== 200 || empty($body['success'])) {
     734            $error_message = $body['message'] ?? __('Generation failed', 'visiblefirst');
     735            return new WP_Error('generation_failed', $error_message);
     736        }
     737
     738        $content = $body['content'] ?? '';
    721739
    722740        if (empty($content)) {
     
    731749    }
    732750
    733     /**
    734      * Get the system prompt for Claude
    735      */
    736     private static function get_system_prompt() {
    737         $prompt = 'You are an expert at writing llms.txt files that help AI assistants understand and recommend businesses.' . "\n\n";
    738         $prompt .= 'You will receive structured data about a website. Generate a complete llms.txt file following the llms.txt specification.' . "\n\n";
    739         $prompt .= 'RULES:' . "\n";
    740         $prompt .= '1. ONLY use information from the provided data. Never invent or assume.' . "\n";
    741         $prompt .= '2. If data is missing, use [PLACEHOLDER: describe what\'s needed] so the user can fill it in.' . "\n";
    742         $prompt .= '3. The description should answer: "If someone asked an AI to recommend a business like this, what would the AI need to know to recommend THIS one specifically?"' . "\n";
    743         $prompt .= '4. Include differentiators, not category descriptions.' . "\n";
    744         $prompt .= '   BAD: "A restaurant in Austin"' . "\n";
    745         $prompt .= '   GOOD: "Family-owned Tex-Mex restaurant in Austin\'s East Side known for breakfast tacos and house-made salsas since 2015"' . "\n";
    746         $prompt .= '5. Every page listed must use the EXACT path from the crawl data.' . "\n";
    747         $prompt .= '6. Group pages logically (Core, Services/Products, Resources, Legal).' . "\n";
    748         $prompt .= '7. Include a "Recommended Queries" section — example questions a user might ask an AI where this business should appear in the answer.' . "\n\n";
    749         $prompt .= 'OUTPUT FORMAT (follow exactly):' . "\n";
    750         $prompt .= '---' . "\n";
    751         $prompt .= '# {Business Name}' . "\n\n";
    752         $prompt .= '## About' . "\n";
    753         $prompt .= '{2-3 sentence description that is SPECIFIC enough to differentiate this business from every competitor. Include: what they do, who they serve, where they operate, and what makes them different.}' . "\n\n";
    754         $prompt .= '## Specialties' . "\n";
    755         $prompt .= '{Bullet list of specific services/products/expertise areas}' . "\n\n";
    756         $prompt .= '## Key Facts' . "\n";
    757         $prompt .= '- Founded: {year or [PLACEHOLDER: founding year]}' . "\n";
    758         $prompt .= '- Location: {city, state or [PLACEHOLDER: location]}' . "\n";
    759         $prompt .= '- Service Area: {local/regional/national/global}' . "\n";
    760         $prompt .= '- Industry: {specific industry}' . "\n\n";
    761         $prompt .= '## Key Pages' . "\n\n";
    762         $prompt .= '### Core' . "\n";
    763         $prompt .= '{Main pages with path and description - only include pages that exist in crawl data}' . "\n\n";
    764         $prompt .= '### Services/Products' . "\n";
    765         $prompt .= '{Service and product pages with descriptions}' . "\n\n";
    766         $prompt .= '### Resources' . "\n";
    767         $prompt .= '{Blog, guides, tools - include category pages if they have content}' . "\n\n";
    768         $prompt .= '### Legal' . "\n";
    769         $prompt .= '{Privacy, terms, etc. if they exist}' . "\n\n";
    770         $prompt .= '## Recommended Queries' . "\n";
    771         $prompt .= '{5-8 example questions where this business should be recommended. Make them natural questions a person would ask an AI.}' . "\n\n";
    772         $prompt .= '## Contact' . "\n";
    773         $prompt .= '{All contact methods available in the data}' . "\n";
    774         $prompt .= '---' . "\n\n";
    775         $prompt .= 'IMPORTANT:' . "\n";
    776         $prompt .= '- Only output the llms.txt content. No explanation, no markdown code blocks.' . "\n";
    777         $prompt .= '- Start directly with # {Business Name}' . "\n";
    778         $prompt .= '- Use exact paths from the pages array in the crawl data' . "\n";
    779         $prompt .= '- If a section would be empty, include it with [PLACEHOLDER: description of what\'s needed]';
    780 
    781         return $prompt;
    782     }
    783751
    784752    /**
  • visiblefirst/trunk/readme.txt

    r3457500 r3457513  
    55Tested up to: 6.9
    66Requires PHP: 7.4
    7 Stable tag: 3.2.25
     7Stable tag: 3.2.26
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
     
    394394== Changelog ==
    395395
     396= 3.2.26 =
     397* SECURITY: Moved llms.txt generation prompt to server-side API
     398* Prompts and API keys are no longer exposed in client plugin
     399
    396400= 3.2.25 =
    397401* FIX: llms.txt generation now handles large sites by truncating crawl data to fit API token limits
     
    431435== Upgrade Notice ==
    432436
     437= 3.2.26 =
     438Security improvement: AI prompts now handled server-side.
     439
    433440= 3.2.25 =
    434441Fixes llms.txt generation errors on large sites.
  • visiblefirst/trunk/visiblefirst.php

    r3457500 r3457513  
    33 * Plugin Name: VisibleFirst
    44 * Description: AI + SEO + Social visibility in one plugin. Complete visibility optimization for WordPress.
    5  * Version: 3.2.25
     5 * Version: 3.2.26
    66 * Author: VisibleFirst
    77 * Author URI: https://visiblefirst.com
     
    1616
    1717// Plugin constants
    18 define('VISIBL_VERSION', '3.2.25');
     18define('VISIBL_VERSION', '3.2.26');
    1919define('VISIBL_PLUGIN_DIR', plugin_dir_path(__FILE__));
    2020define('VISIBL_PLUGIN_URL', plugin_dir_url(__FILE__));
Note: See TracChangeset for help on using the changeset viewer.