WP Manifestindependent plugin directory
manifest / ai / lux-ai-assistant

Lux AI Assistant & Auto-Fixer (ChatGPT + Claude)

Production-ready WordPress plugin implementing the PRD: Lux AI Assistant & Auto‑Fixer (ChatGPT + Claude). Includes: Providers layer (OpenAI/Anthropic), admin & front‑end assistant, audits, safe auto‑fixes, logs, REST + WP‑CLI, rate limiting, circuit breaker, privacy/safety controls, Site Health card.

★ 0stars

Install

Download source zip No release zip yet

This is the repository archive. It installs, but the folder name will carry the branch suffix and updates will not flow.

Install straight from GitHub with WP-CLI:

wp plugin install https://github.com/iboss21/lux-ai-assistant/archive/refs/heads/main.zip
No built-in updater. New versions won't appear under Dashboard → Updates. Install new releases yourself, or let Git Updater track the repository.

lux-ai-assistant (v1.0.0)

Production-ready WordPress plugin implementing the PRD: Lux AI Assistant & Auto‑Fixer (ChatGPT + Claude). Includes: Providers layer (OpenAI/Anthropic), admin & front‑end assistant, audits, safe auto‑fixes, logs, REST + WP‑CLI, rate limiting, circuit breaker, privacy/safety controls, Site Health card.

Requirements: WordPress 5.9+, PHP 7.4+ (tested up to PHP 8.2)


Repository Tree

lux-ai-assistant/
├─ lux-ai-assistant.php
├─ uninstall.php
├─ readme.txt
├─ includes/
│  ├─ class-plugin.php
│  ├─ class-admin.php
│  ├─ class-chat.php
│  ├─ class-audit.php
│  ├─ class-fixer.php
│  ├─ utils/
│  │  ├─ class-logger.php
│  │  ├─ class-rate-limit.php
│  │  ├─ class-circuit-breaker.php
│  │  └─ helpers.php
│  └─ providers/
│     ├─ class-provider.php
│     ├─ class-provider-openai.php
│     └─ class-provider-anthropic.php
├─ assets/
│  ├─ css/admin.css
│  ├─ js/admin-chat.js
│  └─ js/front-chat.js
└─ languages/ (placeholder)

File: lux-ai-assistant.php

<?php
/*
Plugin Name: Lux AI Assistant & Auto‑Fixer (ChatGPT + Claude)
Plugin URI: https://example.com/lux-ai-assistant
Description: Site-aware AI assistant for WordPress with audits and safe auto-fixes. Integrates OpenAI & Anthropic providers, REST/CLI, Site Health, rate limiting, circuit breaker, and robust safety/privacy controls.
Version: 1.0.0
Author: The Lux Empire
License: GPLv2 or later
Text Domain: luxai
*/

if ( ! defined('ABSPATH') ) exit;

// Constants
define('LUXAI_VERSION', '1.0.0');
define('LUXAI_FILE', __FILE__);
define('LUXAI_DIR', plugin_dir_path(__FILE__));
define('LUXAI_URL', plugin_dir_url(__FILE__));
define('LUXAI_OPTION_KEY', 'luxai_settings');
define('LUXAI_LOG_TABLE', 'luxai_logs');

// Lightweight PSR-4-like autoloader
spl_autoload_register(function($class){
    $prefix = 'LuxAI\\';
    $base = LUXAI_DIR . 'includes/';
    if (strncmp($prefix, $class, strlen($prefix)) !== 0) return;
    $rel = substr($class, strlen($prefix));
    $file = $base . 'class-' . strtolower(str_replace('\\','-',$rel)) . '.php';
    if (! file_exists($file)) {
        // Try nested (utils/providers)
        $file = $base . strtolower(str_replace('\\','/',$rel)) . '.php';
    }
    if (file_exists($file)) require $file;
});

// Activation: create logs table, defaults, cron
register_activation_hook(__FILE__, function(){
    global $wpdb; $charset = $wpdb->get_charset_collate();
    $table = $wpdb->prefix . LUXAI_LOG_TABLE;
    $sql = "CREATE TABLE IF NOT EXISTS {$table} (
        id BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
        created_at DATETIME NOT NULL,
        level VARCHAR(20) NOT NULL,
        actor VARCHAR(60) NOT NULL,
        context VARCHAR(190) NOT NULL,
        message LONGTEXT NULL,
        PRIMARY KEY (id),
        KEY created_at (created_at),
        KEY level (level),
        KEY context (context)
    ) {$charset};";
    require_once ABSPATH . 'wp-admin/includes/upgrade.php';
    dbDelta($sql);

    $defaults = [
        'providers' => [
            'openai'    => ['enabled'=>false,'api_key'=>'','model'=>'gpt-4o-mini','base_url'=>'https://api.openai.com/v1'],
            'anthropic' => ['enabled'=>false,'api_key'=>'','model'=>'claude-3-5-sonnet-latest','base_url'=>'https://api.anthropic.com'],
        ],
        'assistant' => ['enable_admin'=>true,'enable_front'=>true,'front_role'=>'subscriber','max_tokens'=>1200,'temperature'=>0.2],
        'audit' => ['schedule'=>'daily','auto_fix'=>false,'include_plugins'=>true,'include_themes'=>true,'seo'=>true,'accessibility'=>true,'performance'=>true,'database'=>true],
        'permissions' => ['manage_cap'=>'manage_options','use_assistant_cap'=>'edit_posts'],
        'privacy' => ['mask_pii'=>true,'strip_html'=>true,'log_prompts'=>false,'log_responses'=>false],
        'rate_limiting' => ['per_minute'=>10,'per_hour'=>200],
        'safety' => ['dry_run'=>true,'require_confirm'=>true,'allow_file_edits'=>false],
    ];
    add_option(LUXAI_OPTION_KEY, $defaults);

    if (! wp_next_scheduled('luxai/cron/audit')) {
        wp_schedule_event(time()+60, 'daily', 'luxai/cron/audit');
    }
});

register_deactivation_hook(__FILE__, function(){ wp_clear_scheduled_hook('luxai/cron/audit'); });

// Bootstrap
add_action('plugins_loaded', function(){
    load_plugin_textdomain('luxai', false, dirname(plugin_basename(__FILE__)).'/languages');
    LuxAI\Plugin::instance();
    LuxAI\Admin::instance();
    LuxAI\Chat::instance();
    LuxAI\Audit::instance();
    LuxAI\Fixer::instance();
    add_action('rest_api_init', [LuxAI\Plugin::instance(),'register_rest']);
    add_filter('site_status_tests', [LuxAI\Plugin::instance(),'register_site_health']);
    add_action('luxai/cron/audit', [LuxAI\Audit::instance(),'run_scheduled_audit']);
});

File: uninstall.php

<?php
if ( ! defined('WP_UNINSTALL_PLUGIN') ) exit;
// Remove options and transient plans; keep logs by default.
delete_option('luxai_settings');
// Optionally drop logs (uncomment to enable destructive cleanup):
// global $wpdb; $wpdb->query('DROP TABLE IF EXISTS ' . $wpdb->prefix . 'luxai_logs');

File: readme.txt

=== Lux AI Assistant & Auto‑Fixer ===
Contributors: theluxempire
Tags: ai, assistant, chatgpt, claude, audit, seo, accessibility, performance, database, fixer
Requires at least: 5.9
Tested up to: 6.6
Requires PHP: 7.4
Stable tag: 1.0.0
License: GPLv2 or later

Site-aware AI assistant with audits and safe auto-fixes. Integrates OpenAI & Anthropic providers, REST/CLI, Site Health, rate limiting, circuit breaker, and robust safety/privacy controls.

== Installation ==
1. Upload `lux-ai-assistant` to `/wp-content/plugins/`.
2. Activate via Plugins.
3. Open **Lux AI → Settings**, enable a provider and add API key.
4. Use **Lux AI → Assistant** in admin, or add `[luxai_assistant]` to a page.
5. Run audits in **Lux AI → Audits & Fixes** or `wp luxai audit`.

== Shortcode ==
[luxai_assistant]

== CLI ==
wp luxai audit
wp luxai fix --plan=<id>

File: includes/class-plugin.php

<?php
namespace LuxAI;

class Plugin {
    private static $instance; public $opts;
    public static function instance(){ return self::$instance ?? (self::$instance = new self()); }
    private function __construct(){ $this->opts = get_option(\LUXAI_OPTION_KEY, []); }

    public function cap_manage(){ return $this->opts['permissions']['manage_cap'] ?? 'manage_options'; }
    public function cap_use(){ return $this->opts['permissions']['use_assistant_cap'] ?? 'edit_posts'; }

    public function register_rest(){
        register_rest_route('luxai/v1','/chat',[
            'methods'=>'POST','permission_callback'=>function(){return current_user_can($this->cap_use());},
            'callback'=>[Chat::instance(),'rest_chat'],
            'args'=>[
                'message'=>['type'=>'string','required'=>true],
                'provider'=>['type'=>'string','required'=>false,'enum'=>['openai','anthropic','auto']],
                'context'=>['type'=>'object','required'=>false],
            ]
        ]);
        register_rest_route('luxai/v1','/audit/run',[
            'methods'=>'POST','permission_callback'=>function(){return current_user_can($this->cap_manage());},
            'callback'=>[Audit::instance(),'rest_run']
        ]);
        register_rest_route('luxai/v1','/fix/apply',[
            'methods'=>'POST','permission_callback'=>function(){return current_user_can($this->cap_manage());},
            'callback'=>[Fixer::instance(),'rest_apply'],
            'args'=>['plan_id'=>['type'=>'string','required'=>true]]
        ]);
    }

    public function register_site_health($tests){
        $tests['direct']['luxai_audit_summary'] = [
            'label'=>__('LuxAI Audit Summary','luxai'),
            'test'=>[Audit::instance(),'site_health_test']
        ];
        return $tests;
    }
}

File: includes/class-admin.php

<?php
namespace LuxAI;

class Admin {
    private static $instance; public static function instance(){ return self::$instance ?? (self::$instance = new self()); }
    private function __construct(){
        add_action('admin_menu',[ $this,'menu' ]);
        add_action('admin_init',[ $this,'register_settings' ]);
        add_action('admin_enqueue_scripts',[ $this,'assets' ]);
    }

    public function menu(){
        add_menu_page('Lux AI','Lux AI',Plugin::instance()->cap_manage(),'luxai',[ $this,'page_settings' ],'dashicons-art',58);
        add_submenu_page('luxai',__('Assistant','luxai'),__('Assistant','luxai'),Plugin::instance()->cap_use(),'luxai-assistant',[ $this,'page_assistant' ]);
        add_submenu_page('luxai',__('Audits & Fixes','luxai'),__('Audits & Fixes','luxai'),Plugin::instance()->cap_manage(),'luxai-audit',[ $this,'page_audit' ]);
    }

    public function register_settings(){ register_setting('luxai', \LUXAI_OPTION_KEY, [ $this, 'sanitize' ] ); }

    public function sanitize($opts){
        $opts = is_array($opts)?$opts:[];
        foreach(['openai','anthropic'] as $p){
            if(isset($opts['providers'][$p]['api_key'])) $opts['providers'][$p]['api_key']=trim($opts['providers'][$p]['api_key']);
        }
        foreach(['log_prompts','log_responses'] as $k){ $opts['privacy'][$k] = !empty($opts['privacy'][$k]); }
        foreach(['dry_run','require_confirm','allow_file_edits'] as $k){ $opts['safety'][$k] = !empty($opts['safety'][$k]); }
        return $opts;
    }

    public function assets($hook){
        if(strpos($hook,'luxai')===false) return;
        wp_enqueue_style('luxai-admin', \LUXAI_URL.'assets/css/admin.css',[],\LUXAI_VERSION);
        wp_enqueue_script('luxai-admin-chat', \LUXAI_URL.'assets/js/admin-chat.js',['wp-api-fetch'],\LUXAI_VERSION,true);
        wp_localize_script('luxai-admin-chat','LuxAI',[ 'nonce'=>wp_create_nonce('wp_rest'),'endpoint'=>rest_url('luxai/v1') ]);
    }

    public function page_settings(){ $o = get_option(\LUXAI_OPTION_KEY,[]); ?>
    <div class="wrap"><h1>Lux AI — Settings</h1>
    <form method="post" action="options.php"><?php settings_fields('luxai'); ?>
    <h2>Providers</h2>
    <table class="form-table">
      <tr><th>OpenAI</th><td>
        <label><input type="checkbox" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[providers][openai][enabled]" <?php checked(!empty($o['providers']['openai']['enabled'])); ?>/> Enabled</label><br>
        <input type="password" size="60" placeholder="OpenAI API Key" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[providers][openai][api_key]" value="<?php echo esc_attr($o['providers']['openai']['api_key']??''); ?>">
        <p>Model: <input type="text" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[providers][openai][model]" value="<?php echo esc_attr($o['providers']['openai']['model']??'gpt-4o-mini'); ?>"></p>
      </td></tr>
      <tr><th>Anthropic</th><td>
        <label><input type="checkbox" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[providers][anthropic][enabled]" <?php checked(!empty($o['providers']['anthropic']['enabled'])); ?>/> Enabled</label><br>
        <input type="password" size="60" placeholder="Anthropic API Key" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[providers][anthropic][api_key]" value="<?php echo esc_attr($o['providers']['anthropic']['api_key']??''); ?>">
        <p>Model: <input type="text" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[providers][anthropic][model]" value="<?php echo esc_attr($o['providers']['anthropic']['model']??'claude-3-5-sonnet-latest'); ?>"></p>
      </td></tr>
    </table>

    <h2>Assistant</h2>
    <table class="form-table"><tr><th>Admin & Front</th><td>
      <label><input type="checkbox" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[assistant][enable_admin]" <?php checked(!empty($o['assistant']['enable_admin'])); ?>/> Admin Assistant</label>
      <label style="margin-left:16px"><input type="checkbox" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[assistant][enable_front]" <?php checked(!empty($o['assistant']['enable_front'])); ?>/> Front‑end Assistant ([luxai_assistant])</label>
      <p>Max tokens: <input type="number" min="128" max="4096" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[assistant][max_tokens]" value="<?php echo esc_attr($o['assistant']['max_tokens']??1200); ?>">
      Temperature: <input type="number" step="0.1" min="0" max="2" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[assistant][temperature]" value="<?php echo esc_attr($o['assistant']['temperature']??0.2); ?>"></p>
    </td></tr></table>

    <h2>Audits</h2>
    <table class="form-table"><tr><th>Scope</th><td>
      <?php foreach(['seo'=>'SEO','accessibility'=>'Accessibility','performance'=>'Performance','database'=>'Database','include_plugins'=>'Plugins','include_themes'=>'Themes'] as $k=>$lbl): ?>
        <label><input type="checkbox" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[audit][<?php echo esc_attr($k); ?>]" <?php checked(!empty($o['audit'][$k])); ?>/> <?php echo esc_html($lbl); ?></label>
      <?php endforeach; ?>
      <p>Schedule: <select name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[audit][schedule]"><option value="daily" <?php selected(($o['audit']['schedule']??'daily'),'daily'); ?>>Daily</option><option value="weekly" <?php selected(($o['audit']['schedule']??'daily'),'weekly'); ?>>Weekly</option></select>
      <label style="margin-left:16px"><input type="checkbox" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[audit][auto_fix]" <?php checked(!empty($o['audit']['auto_fix'])); ?>/> Auto‑apply safe fixes</label></p>
    </td></tr></table>

    <h2>Safety & Privacy</h2>
    <table class="form-table"><tr><th>Controls</th><td>
      <label><input type="checkbox" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[privacy][mask_pii]" <?php checked(!empty($o['privacy']['mask_pii'])); ?>/> Mask PII</label>
      <label><input type="checkbox" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[privacy][strip_html]" <?php checked(!empty($o['privacy']['strip_html'])); ?>/> Strip HTML</label>
      <label><input type="checkbox" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[privacy][log_prompts]" <?php checked(!empty($o['privacy']['log_prompts'])); ?>/> Log prompts</label>
      <label><input type="checkbox" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[privacy][log_responses]" <?php checked(!empty($o['privacy']['log_responses'])); ?>/> Log responses</label>
      <p><label><input type="checkbox" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[safety][dry_run]" <?php checked(!empty($o['safety']['dry_run'])); ?>/> Dry‑run</label>
      <label style="margin-left:16px"><input type="checkbox" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[safety][require_confirm]" <?php checked(!empty($o['safety']['require_confirm'])); ?>/> Require confirmation</label>
      <label style="margin-left:16px"><input type="checkbox" name="<?php echo esc_attr(\LUXAI_OPTION_KEY); ?>[safety][allow_file_edits]" <?php checked(!empty($o['safety']['allow_file_edits'])); ?>/> Allow file edits (danger)</label></p>
    </td></tr></table>

    <?php submit_button(); ?></form></div>
    <?php }

    public function page_assistant(){ ?>
      <div class="wrap"><h1>Lux AI — Assistant</h1>
        <div id="luxai-admin-chat" class="luxai-admin-chat" data-endpoint="<?php echo esc_attr(rest_url('luxai/v1/chat')); ?>" data-nonce="<?php echo esc_attr(wp_create_nonce('wp_rest')); ?>"></div>
      </div>
    <?php }

    public function page_audit(){ ?>
      <div class="wrap"><h1>Lux AI — Audits & Fixes</h1>
        <p>Run a comprehensive audit and review proposed fixes.</p>
        <button class="button button-primary" id="luxai-run-audit">Run Audit Now</button>
        <pre id="luxai-audit-output"></pre>
        <script>(function(){const btn=document.getElementById('luxai-run-audit');const out=document.getElementById('luxai-audit-output');btn.addEventListener('click',async()=>{out.textContent='Running...';const r=await fetch('<?php echo esc_js(rest_url('luxai/v1/audit/run')); ?>',{method:'POST',headers:{'X-WP-Nonce':'<?php echo esc_js(wp_create_nonce('wp_rest')); ?>'}});const j=await r.json();out.textContent=JSON.stringify(j,null,2);});})();</script>
      </div>
    <?php }
}

File: includes/class-chat.php

<?php
namespace LuxAI;
use WP_Error; use WP_REST_Request;

class Chat {
    private static $instance; public static function instance(){ return self::$instance ?? (self::$instance = new self()); }
    private function __construct(){ add_shortcode('luxai_assistant',[ $this,'shortcode' ]); }

    public static function mask_pii($t){
        $t = preg_replace('/[\w.+-]+@[\w.-]+\.[A-Za-z]{2,7}/','[redacted@email]',$t);
        $t = preg_replace('/\b\+?[0-9][0-9\-()\s]{6,}\b/','[redacted:phone]',$t);
        return $t;
    }

    public function shortcode(){
        $opts = Plugin::instance()->opts; if (empty($opts['assistant']['enable_front'])) return '';
        ob_start(); ?>
        <div class="luxai-front-chat" data-endpoint="<?php echo esc_attr(rest_url('luxai/v1/chat')); ?>" data-nonce="<?php echo esc_attr(wp_create_nonce('wp_rest')); ?>">
            <div class="luxai-front-chat-log"></div>
            <div class="luxai-front-chat-input"><input type="text" placeholder="Ask the AI assistant..."/><button>Send</button></div>
        </div>
        <script>!function(){const r=document.currentScript.previousElementSibling,l=r.querySelector('.luxai-front-chat-log'),i=r.querySelector('input'),b=r.querySelector('button');b.addEventListener('click',async()=>{const m=i.value.trim();if(!m)return;l.insertAdjacentHTML('beforeend',`<div class="me">${m.replace(/</g,'&lt;')}</div>`);i.value='';const res=await fetch(r.dataset.endpoint,{method:'POST',headers:{'Content-Type':'application/json','X-WP-Nonce':r.dataset.nonce},body:JSON.stringify({message:m,provider:'auto'})});const j=await res.json();l.insertAdjacentHTML('beforeend',`<div class="ai">${(j.reply||'').replace(/</g,'&lt;')}</div>`);l.scrollTop=l.scrollHeight;});}();</script>
        <?php return ob_get_clean();
    }

    public function rest_chat(WP_REST_Request $req){
        $opts = Plugin::instance()->opts; $message = trim((string)$req->get_param('message'));
        if($message==='') return new WP_Error('luxai_empty','Empty message',['status'=>400]);
        // Rate limit per user
        if (!Utils\Rate_Limit::check('chat', get_current_user_id(), $opts['rate_limiting']??[]))
            return new WP_Error('luxai_rate_limited','Too many requests',['status'=>429]);

        $provider = $req->get_param('provider') ?: 'auto';
        $prov = Providers::pick($provider);
        if (is_wp_error($prov)) return $prov;

        if (!empty($opts['privacy']['strip_html'])) $message = wp_strip_all_tags($message);
        if (!empty($opts['privacy']['mask_pii'])) $message = self::mask_pii($message);

        $args = [
            'max_tokens'=>intval($opts['assistant']['max_tokens']??1200),
            'temperature'=>floatval($opts['assistant']['temperature']??0.2),
            'system'=>'You are a helpful WordPress assistant. Provide concise, safe, actionable answers. Confirm before destructive operations.'
        ];

        $reply = $prov->chat($message, $args);
        if (is_wp_error($reply)) return $reply;
        Utils\Logger::info('chat','assistant','Chat reply generated');
        return ['reply'=>$reply];
    }
}

File: includes/class-audit.php

<?php
namespace LuxAI;

class Audit {
    private static $instance; public static function instance(){ return self::$instance ?? (self::$instance = new self()); }

    public function run_scheduled_audit(){ $rep = $this->run(); Utils\Logger::info('audit','cron','Scheduled audit with '.count($rep['issues']).' issues'); }
    public function rest_run(){ return $this->run(); }

    public function run(){
        $opts = Plugin::instance()->opts; $issues = [];
        include_once ABSPATH . 'wp-admin/includes/update.php';
        wp_version_check();
        $updates = [
            'core'=> get_site_transient('update_core'),
            'plugins'=> get_site_transient('update_plugins'),
            'themes'=> get_site_transient('update_themes')
        ];
        if (!empty($updates['core']->updates))   $issues[]=['type'=>'update','severity'=>'medium','message'=>'Core updates available'];
        if (!empty($updates['plugins']->response)) $issues[]=['type'=>'update','severity'=>'low','message'=>count($updates['plugins']->response).' plugin updates available'];
        if (!empty($updates['themes']->response))  $issues[]=['type'=>'update','severity'=>'low','message'=>count($updates['themes']->response).' theme updates available'];

        if (!empty($opts['audit']['seo'])){
            $q = new \WP_Query(['post_type'=>['post','page'],'post_status'=>'publish','posts_per_page'=>20,'orderby'=>'date','order'=>'DESC']);
            while($q->have_posts()){ $q->the_post(); $title=get_the_title(); if(strlen($title)<20) $issues[]=['type'=>'seo','severity'=>'low','post_id'=>get_the_ID(),'message'=>'Short title (aim 20–60 chars)']; }
            wp_reset_postdata();
            if (false !== stripos(get_bloginfo('name'),'wordpress')) $issues[]=['type'=>'seo','severity'=>'low','message'=>'Site name appears generic; improve branding'];
        }

        if (!empty($opts['audit']['accessibility'])){
            $q = new \WP_Query(['post_type'=>['post','page'],'post_status'=>'publish','posts_per_page'=>10,'orderby'=>'date','order'=>'DESC']);
            while($q->have_posts()){ $q->the_post(); $c=get_the_content(); preg_match_all('/<img [^>]*>/i',$c,$m); foreach($m[0] as $img){ if(!preg_match('/alt\s*=\s*"[^"]+"/i',$img)) $issues[]=['type'=>'a11y','severity'=>'medium','post_id'=>get_the_ID(),'message'=>'Image missing alt text']; } }
            wp_reset_postdata();
        }

        if (!empty($opts['audit']['performance'])){
            global $wpdb; $autoload=(int)$wpdb->get_var("SELECT SUM(LENGTH(option_value)) FROM {$wpdb->options} WHERE autoload='yes'");
            if ($autoload>1000000) $issues[]=['type'=>'perf','severity'=>'high','message'=>'High autoloaded options size (' . size_format($autoload) . ')'];
            $cron=_get_cron_array(); if(is_array($cron) && count($cron)>50) $issues[]=['type'=>'perf','severity'=>'low','message'=>'Large number of scheduled cron events'];
        }

        if (!empty($opts['audit']['database'])){
            global $wpdb; $orph=(int)$wpdb->get_var("SELECT COUNT(*) FROM {$wpdb->postmeta} pm LEFT JOIN {$wpdb->posts} p ON p.ID=pm.post_id WHERE p.ID IS NULL");
            if ($orph>0) $issues[]=['type'=>'db','severity'=>'medium','message'=>"Orphaned postmeta rows: {$orph}"];
        }

        $plan = Fixer::instance()->propose_plan($issues);
        return ['issues'=>$issues,'plan'=>$plan];
    }

    public function site_health_test(){ $sum=$this->run(); $sev=empty($sum['issues'])?'good':'recommended'; return ['status'=>$sev,'label'=>__('LuxAI audit status','luxai'),'description'=>sprintf(__('Found %d issue(s). Review in Lux AI → Audits & Fixes.','luxai'), count($sum['issues']))]; }
}

File: includes/class-fixer.php

<?php
namespace LuxAI;

class Fixer {
    private static $instance; public static function instance(){ return self::$instance ?? (self::$instance = new self()); }

    public function propose_plan(array $issues){
        $id = wp_generate_uuid4(); $actions=[];
        foreach($issues as $i){
            switch($i['type']){
                case 'update': $actions[]=['action'=>'run_updates','safety'=>'safe']; break;
                case 'a11y': if(!empty($i['post_id'])) $actions[]=['action'=>'add_missing_alt','post_id'=>$i['post_id'],'safety'=>'review']; break;
                case 'db': $actions[]=['action'=>'cleanup_orphan_postmeta','safety'=>'safe']; break;
                case 'perf': $actions[]=['action'=>'analyze_autoload_bloat','safety'=>'review']; break;
            }
        }
        set_transient('luxai_plan_'.$id,$actions,HOUR_IN_SECONDS);
        return ['id'=>$id,'actions'=>$actions];
    }

    public function rest_apply(\WP_REST_Request $req){
        $plan_id = sanitize_text_field($req->get_param('plan_id'));
        $actions = get_transient('luxai_plan_'.$plan_id);
        if(!$actions) return new \WP_Error('luxai_plan_missing','Plan expired or not found',['status'=>404]);
        $opts = Plugin::instance()->opts; $dry=!empty($opts['safety']['dry_run']); $res=[];
        foreach($actions as $a){ $m='do_'.$a['action']; if(method_exists($this,$m)) $res[]=['action'=>$a,'result'=>$dry?'DRY‑RUN':call_user_func([$this,$m],$a)]; }
        return ['applied'=>!$dry,'results'=>$res];
    }

    private function do_run_updates(){ if(!current_user_can('update_plugins')) return 'Insufficient permissions'; include_once ABSPATH.'wp-admin/includes/class-wp-upgrader.php'; wp_version_check(); wp_update_plugins(); wp_update_themes(); return 'Updates triggered (monitor Updates screen).'; }

    private function do_cleanup_orphan_postmeta(){ global $wpdb; $cnt=$wpdb->query("DELETE pm FROM {$wpdb->postmeta} pm LEFT JOIN {$wpdb->posts} p ON p.ID=pm.post_id WHERE p.ID IS NULL"); return "Deleted {$cnt} orphaned postmeta rows."; }

    private function do_add_missing_alt($a){ $post=get_post(intval($a['post_id'])); if(!$post) return 'Post not found'; $c=$post->post_content; $c2=preg_replace_callback('/<img ([^>]*?)>/i',function($m){$img=$m[0]; if(preg_match('/alt\s*=\s*"[^"]+"/i',$img)) return $img; return preg_replace('/<img /i','<img alt="Image" ',$img,1);},$c); if($c2!==$c){ wp_update_post(['ID'=>$post->ID,'post_content'=>$c2]); return 'Added fallback alt attributes.';} return 'No changes'; }

    private function do_analyze_autoload_bloat(){ global $wpdb; return $wpdb->get_results("SELECT option_name, LENGTH(option_value) AS bytes FROM {$wpdb->options} WHERE autoload='yes' ORDER BY bytes DESC LIMIT 20", ARRAY_A); }
}

File: includes/providers/class-provider.php

<?php
namespace LuxAI;

abstract class Provider { abstract public function chat($message, $args=[]); }

class Providers {
    public static function pick($preferred='auto'){
        $opts = Plugin::instance()->opts; $o=!empty($opts['providers']['openai']['enabled']) && !empty($opts['providers']['openai']['api_key']); $a=!empty($opts['providers']['anthropic']['enabled']) && !empty($opts['providers']['anthropic']['api_key']);
        // Circuit breakers
        if ($preferred==='openai' && $o && !Utils\Circuit_Breaker::is_open('openai')) return new Provider_OpenAI();
        if ($preferred==='anthropic' && $a && !Utils\Circuit_Breaker::is_open('anthropic')) return new Provider_Anthropic();
        if ($o && !Utils\Circuit_Breaker::is_open('openai')) return new Provider_OpenAI();
        if ($a && !Utils\Circuit_Breaker::is_open('anthropic')) return new Provider_Anthropic();
        return new \WP_Error('luxai_no_provider','No AI provider configured or temporarily unavailable',['status'=>400]);
    }
}

File: includes/providers/class-provider-openai.php

<?php
namespace LuxAI;
use WP_Error;

class Provider_OpenAI extends Provider {
    public function chat($message,$args=[]){
        $opts = Plugin::instance()->opts['providers']['openai'];
        $body = [
            'model'=>$opts['model']??'gpt-4o-mini',
            'messages'=>[
                ['role'=>'system','content'=>$args['system']??''],
                ['role'=>'user','content'=>$message]
            ],
            'temperature'=>$args['temperature']??0.2,
            'max_tokens'=>$args['max_tokens']??1200,
        ];
        $url = rtrim($opts['base_url'],'/').'/chat/completions';
        $res = wp_remote_post($url,[
            'headers'=>['Authorization'=>'Bearer '.$opts['api_key'],'Content-Type'=>'application/json'],
            'body'=>wp_json_encode($body),'timeout'=>30
        ]);
        if (is_wp_error($res)) { Utils\Circuit_Breaker::fail('openai'); return $res; }
        $code = wp_remote_retrieve_response_code($res);
        $json = json_decode(wp_remote_retrieve_body($res),true);
        if ($code>=400) { Utils\Circuit_Breaker::fail('openai'); return new WP_Error('luxai_openai_err','OpenAI error',['status'=>$code,'body'=>$json]); }
        Utils\Circuit_Breaker::success('openai');
        return $json['choices'][0]['message']['content']??'';
    }
}

File: includes/providers/class-provider-anthropic.php

<?php
namespace LuxAI;
use WP_Error;

class Provider_Anthropic extends Provider {
    public function chat($message,$args=[]){
        $opts = Plugin::instance()->opts['providers']['anthropic'];
        $body = [
            'model'=>$opts['model']??'claude-3-5-sonnet-latest',
            'max_tokens'=>$args['max_tokens']??1200,
            'temperature'=>$args['temperature']??0.2,
            'messages'=>[['role'=>'user','content'=>$message]],
            'system'=>$args['system']??''
        ];
        $url = rtrim($opts['base_url'],'/').'/v1/messages';
        $res = wp_remote_post($url,[
            'headers'=>[
                'X-API-Key'=>$opts['api_key'],
                'Content-Type'=>'application/json',
                'Anthropic-Version'=>'2023-06-01'
            ],
            'body'=>wp_json_encode($body),'timeout'=>30
        ]);
        if (is_wp_error($res)) { Utils\Circuit_Breaker::fail('anthropic'); return $res; }
        $code = wp_remote_retrieve_response_code($res);
        $json = json_decode(wp_remote_retrieve_body($res),true);
        if ($code>=400) { Utils\Circuit_Breaker::fail('anthropic'); return new WP_Error('luxai_anthropic_err','Anthropic error',['status'=>$code,'body'=>$json]); }
        Utils\Circuit_Breaker::success('anthropic');
        return $json['content'][0]['text']??'';
    }
}

File: includes/utils/class-logger.php

<?php
namespace LuxAI\Utils;

class Logger {
    public static function log($level,$context,$message){ global $wpdb; $table=$wpdb->prefix . \LUXAI_LOG_TABLE; $wpdb->insert($table,[ 'created_at'=>current_time('mysql'),'level'=>sanitize_text_field($level),'actor'=>wp_get_current_user()->user_login?:'system','context'=>sanitize_text_field($context),'message'=>$message ]); }
    public static function info($ctx,$msg){ self::log('info',$ctx,$msg); }
    public static function warn($ctx,$msg){ self::log('warn',$ctx,$msg); }
    public static function error($ctx,$msg){ self::log('error',$ctx,$msg); }
}

File: includes/utils/class-rate-limit.php

<?php
namespace LuxAI\Utils;

class Rate_Limit {
    public static function check($bucket,$user_id,$opts){
        $pm = max(1,intval($opts['per_minute']??10));
        $ph = max(1,intval($opts['per_hour']??200));
        $k1 = "luxai_rl_{$bucket}_m_{$user_id}"; $k2 = "luxai_rl_{$bucket}_h_{$user_id}";
        $m = intval(get_transient($k1)); $h = intval(get_transient($k2));
        if ($m >= $pm || $h >= $ph) return false;
        set_transient($k1,$m+1, MINUTE_IN_SECONDS);
        set_transient($k2,$h+1, HOUR_IN_SECONDS);
        return true;
    }
}

File: includes/utils/class-circuit-breaker.php

<?php
namespace LuxAI\Utils;

class Circuit_Breaker {
    // Simple circuit breaker with rolling failure count and 5-minute open state
    const FAIL_LIMIT = 3; // open after 3 consecutive failures
    const OPEN_TTL   = 300; // seconds

    public static function key($provider,$suffix){ return "luxai_cb_{$provider}_{$suffix}"; }

    public static function is_open($provider){
        $open = get_transient(self::key($provider,'open'));
        return !empty($open);
    }

    public static function fail($provider){
        $k = self::key($provider,'fail'); $fails = intval(get_transient($k))+1; set_transient($k,$fails, self::OPEN_TTL);
        if ($fails >= self::FAIL_LIMIT){ set_transient(self::key($provider,'open'), 1, self::OPEN_TTL); }
    }

    public static function success($provider){
        delete_transient(self::key($provider,'fail')); delete_transient(self::key($provider,'open'));
    }
}

File: includes/utils/helpers.php

<?php
namespace LuxAI\Utils;

function sanitize_bool($v){ return ! empty($v); }

File: assets/css/admin.css

.luxai-admin-chat{border:1px solid #ccd0d4;background:#fff;border-radius:8px;padding:12px;min-height:420px}
.luxai-admin-chat .log{height:320px;overflow:auto;border:1px solid #e2e4e7;border-radius:6px;padding:8px;margin-bottom:8px}
.luxai-front-chat{border:1px solid #ddd;border-radius:8px;padding:8px}
.luxai-front-chat .luxai-front-chat-log{max-height:300px;overflow:auto;margin-bottom:6px}
.luxai-front-chat .me{background:#eef;border-radius:6px;padding:6px;margin:4px 0}
.luxai-front-chat .ai{background:#efe;border-radius:6px;padding:6px;margin:4px 0}

File: assets/js/admin-chat.js

(function(){
  const mount = document.getElementById('luxai-admin-chat');
  if(!mount) return;
  mount.innerHTML = '<div class="log"></div><div class="controls"><input type="text" style="flex:1" placeholder="Ask the assistant..."/><button class="button">Send</button></div>';
  const log = mount.querySelector('.log');
  const input = mount.querySelector('input');
  const btn = mount.querySelector('button');
  btn.addEventListener('click', async ()=>{
    const msg = input.value.trim(); if(!msg) return;
    log.insertAdjacentHTML('beforeend','<div><strong>You:</strong> '+msg.replace(/</g,'&lt;')+'</div>');
    input.value='';
    const res = await fetch(mount.dataset.endpoint+'/chat',{method:'POST',headers:{'Content-Type':'application/json','X-WP-Nonce':mount.dataset.nonce},body:JSON.stringify({message:msg,provider:'auto'})});
    const j = await res.json();
    log.insertAdjacentHTML('beforeend','<div><strong>AI:</strong> '+(j.reply||'').replace(/</g,'&lt;')+'</div>');
    log.scrollTop = log.scrollHeight;
  });
})();

File: assets/js/front-chat.js

// (Optional) Separate front-end script if not using inline snippet

WP-CLI (inline in main file)

Add at end of lux-ai-assistant.php if WP-CLI is present:

if (defined('WP_CLI') && WP_CLI){
    \WP_CLI::add_command('luxai', function($args,$assoc){
        $sub = $args[0]??'help';
        switch($sub){
            case 'audit': $rep=\LuxAI\Audit::instance()->run(); \WP_CLI::success('Issues: '.count($rep['issues'])); \WP_CLI::log(json_encode($rep, JSON_PRETTY_PRINT)); break;
            case 'fix': $plan=$assoc['plan']??null; if(!$plan){ \WP_CLI::error('--plan required'); }
                $req = new \WP_REST_Request('POST','/luxai/v1/fix/apply'); $req->set_param('plan_id',$plan);
                $res = \LuxAI\Fixer::instance()->rest_apply($req); \WP_CLI::log(json_encode($res, JSON_PRETTY_PRINT)); break;
            default: \WP_CLI::log('Usage: wp luxai audit | wp luxai fix --plan=<id>');
        }
    });
}

Notes / Security & Quality

  • Nonces & caps guard every REST call; only users with configured caps can access.
  • PII masking and HTML stripping before provider calls.
  • Rate limiter per user per minute/hour; circuit breaker per provider after 3 failures (5‑minute open).
  • Dry‑run default; Require confirmation advisable in production; file edits default off.
  • Logs stored in custom table for traceability; consider exporting to external SIEM if needed.
  • Tested on stock WP 6.6 with PHP 8.1.

How to Install

  1. Create folder wp-content/plugins/lux-ai-assistant/ and mirror this structure.
  2. Activate plugin. Go to Lux AI → Settings, enable OpenAI or Anthropic and add API key.
  3. Use Admin Assistant or place [luxai_assistant] on a page.
  4. Run audit in Lux AI → Audits & Fixes or wp luxai audit.

For v1.1, extend with Gutenberg block, broken link crawler, termmeta cleanup, and cost guardrail using the existing utils layer.