namespace, '/health', [ 'methods' => 'GET', 'callback' => [$this, 'health'], 'permission_callback' => '__return_true', ]); // Content generation register_rest_route($this->namespace, '/content/generate', [ 'methods' => 'POST', 'callback' => [$this, 'generate_content'], 'permission_callback' => [$this, 'check_auth'], 'args' => [ 'prompt' => ['required' => true, 'type' => 'string'], 'post_type' => ['default' => 'post', 'type' => 'string'], 'status' => ['default' => 'draft', 'type' => 'string'], 'title' => ['type' => 'string'], ], ]); // Content schedule register_rest_route($this->namespace, '/content/schedule', [ 'methods' => 'POST', 'callback' => [$this, 'schedule_content'], 'permission_callback' => [$this, 'check_auth'], 'args' => [ 'prompt' => ['required' => true, 'type' => 'string'], 'scheduled_date' => ['required' => true, 'type' => 'string'], 'post_type' => ['default' => 'post', 'type' => 'string'], 'title' => ['type' => 'string'], ], ]); // Posts CRUD register_rest_route($this->namespace, '/posts', [ 'methods' => 'GET', 'callback' => [$this, 'list_posts'], 'permission_callback' => [$this, 'check_auth'], ]); register_rest_route($this->namespace, '/posts/(?P\d+)', [ 'methods' => 'GET', 'callback' => [$this, 'get_post'], 'permission_callback' => [$this, 'check_auth'], ]); register_rest_route($this->namespace, '/posts/(?P\d+)', [ 'methods' => ['PUT', 'PATCH'], 'callback' => [$this, 'update_post'], 'permission_callback' => [$this, 'check_auth'], ]); register_rest_route($this->namespace, '/posts/(?P\d+)', [ 'methods' => 'DELETE', 'callback' => [$this, 'delete_post'], 'permission_callback' => [$this, 'check_auth'], ]); // Users register_rest_route($this->namespace, '/users', [ 'methods' => 'GET', 'callback' => [$this, 'list_users'], 'permission_callback' => [$this, 'check_auth'], ]); register_rest_route($this->namespace, '/users/(?P\d+)', [ 'methods' => 'GET', 'callback' => [$this, 'get_user'], 'permission_callback' => [$this, 'check_auth'], ]); // Settings register_rest_route($this->namespace, '/settings', [ 'methods' => 'GET', 'callback' => [$this, 'get_settings'], 'permission_callback' => [$this, 'check_auth'], ]); register_rest_route($this->namespace, '/settings', [ 'methods' => 'PUT', 'callback' => [$this, 'update_settings'], 'permission_callback' => [$this, 'check_auth'], ]); // Schedules register_rest_route($this->namespace, '/schedules', [ 'methods' => 'GET', 'callback' => [$this, 'list_schedules'], 'permission_callback' => [$this, 'check_auth'], ]); // Logs register_rest_route($this->namespace, '/logs', [ 'methods' => 'GET', 'callback' => [$this, 'get_logs'], 'permission_callback' => [$this, 'check_auth'], 'args' => [ 'level' => ['type' => 'string'], 'limit' => ['default' => 50, 'type' => 'integer'], 'offset' => ['default' => 0, 'type' => 'integer'], ], ]); } public function check_auth($request) { $key = $request->get_header('X-API-Key'); if (!$key) { // Also check query param for webhook compatibility $key = $request->get_param('api_key'); } $stored = get_option('hermes_api_key'); return $stored && hash_equals($stored, $key); } public function health() { return [ 'status' => 'ok', 'version' => HERMES_AI_BRIDGE_VERSION, 'wordpress' => get_bloginfo('version'), 'php' => PHP_VERSION, 'site' => get_bloginfo('url'), ]; } public function generate_content($request) { $prompt = $request->get_param('prompt'); $post_type = $request->get_param('post_type'); $status = $request->get_param('status'); $title = $request->get_param('title'); $ai = new AiProvider(); $content = $ai->generate($prompt, 'post'); if (is_wp_error($content)) { return new \WP_REST_Response(['error' => $content->get_error_message()], 500); } $post_id = wp_insert_post([ 'post_title' => $title ?: wp_trim_words($content, 10, ''), 'post_content' => $content, 'post_type' => $post_type, 'post_status' => $status, ]); if (is_wp_error($post_id)) { return new \WP_REST_Response(['error' => $post_id->get_error_message()], 500); } self::log('info', 'Content generated', ['post_id' => $post_id, 'prompt' => substr($prompt, 0, 100)]); return [ 'success' => true, 'post_id' => $post_id, 'edit_url' => get_edit_post_link($post_id, 'raw'), ]; } public function schedule_content($request) { $prompt = $request->get_param('prompt'); $scheduled_date = $request->get_param('scheduled_date'); $post_type = $request->get_param('post_type'); $title = $request->get_param('title'); $task_id = wp_insert_post([ 'post_title' => '[AI Task] ' . ($title ?: 'Scheduled: ' . date('Y-m-d H:i')), 'post_content' => wp_json_encode([ 'prompt' => $prompt, 'post_type' => $post_type, 'scheduled_date' => $scheduled_date, ]), 'post_type' => 'hermes_schedule', 'post_status' => 'publish', 'meta_input' => [ '_hermes_scheduled_date' => $scheduled_date, '_hermes_task_type' => 'content_generation', '_hermes_prompt' => $prompt, ], ]); if (is_wp_error($task_id)) { return new \WP_REST_Response(['error' => $task_id->get_error_message()], 500); } self::log('info', 'Content scheduled', ['task_id' => $task_id, 'date' => $scheduled_date]); return [ 'success' => true, 'task_id' => $task_id, 'scheduled_date' => $scheduled_date, ]; } public function list_posts($request) { $posts = get_posts([ 'post_type' => 'post', 'posts_per_page' => $request->get_param('per_page') ?: 20, 'post_status' => 'any', ]); return array_map(function($p) { return [ 'id' => $p->ID, 'title' => $p->post_title, 'status' => $p->post_status, 'date' => $p->post_date, 'modified' => $p->post_modified, 'permalink' => get_permalink($p->ID), ]; }, $posts); } public function get_post($request) { $post = get_post($request->get_param('id')); if (!$post) return new \WP_REST_Response(['error' => 'Post not found'], 404); return [ 'id' => $post->ID, 'title' => $post->post_title, 'content' => $post->post_content, 'excerpt' => $post->post_excerpt, 'status' => $post->post_status, 'date' => $post->post_date, 'modified' => $post->post_modified, 'permalink' => get_permalink($post->ID), ]; } public function update_post($request) { $post_id = $request->get_param('id'); $data = []; if ($request->has_param('title')) $data['post_title'] = $request->get_param('title'); if ($request->has_param('content')) $data['post_content'] = $request->get_param('content'); if ($request->has_param('status')) $data['post_status'] = $request->get_param('status'); if ($request->has_param('excerpt')) $data['post_excerpt'] = $request->get_param('excerpt'); $data['ID'] = $post_id; $result = wp_update_post($data, true); if (is_wp_error($result)) { return new \WP_REST_Response(['error' => $result->get_error_message()], 500); } return ['success' => true, 'post_id' => $result]; } public function delete_post($request) { $result = wp_delete_post($request->get_param('id'), true); if (!$result) return new \WP_REST_Response(['error' => 'Delete failed'], 500); return ['success' => true, 'deleted' => $result->ID]; } public function list_users($request) { $users = get_users(['fields' => ['ID', 'user_login', 'user_email', 'display_name', 'roles']]); return $users; } public function get_user($request) { $user = get_userdata($request->get_param('id')); if (!$user) return new \WP_REST_Response(['error' => 'User not found'], 404); return [ 'id' => $user->ID, 'login' => $user->user_login, 'email' => $user->user_email, 'display_name' => $user->display_name, 'roles' => array_values($user->roles), ]; } public function get_settings() { return [ 'site_name' => get_bloginfo('name'), 'site_url' => get_bloginfo('url'), 'admin_email' => get_bloginfo('admin_email'), 'language' => get_bloginfo('language'), 'timezone' => get_option('timezone_string'), 'date_format' => get_option('date_format'), 'time_format' => get_option('time_format'), 'posts_per_page' => get_option('posts_per_page'), 'ai_provider' => get_option('hermes_ai_provider'), 'ai_model' => get_option('hermes_ai_model'), ]; } public function update_settings($request) { $allowed = ['hermes_ai_provider', 'hermes_ai_model', 'hermes_ai_api_key', 'hermes_ai_base_url', 'hermes_ai_max_tokens', 'hermes_cron_interval']; foreach ($request->get_params() as $key => $value) { if (in_array($key, $allowed)) { update_option($key, sanitize_text_field($value)); } } return ['success' => true]; } public function list_schedules() { $tasks = get_posts([ 'post_type' => 'hermes_schedule', 'posts_per_page' => 50, 'post_status' => 'any', ]); return array_map(function($t) { return [ 'id' => $t->ID, 'type' => get_post_meta($t->ID, '_hermes_task_type', true), 'scheduled_date' => get_post_meta($t->ID, '_hermes_scheduled_date', true), 'status' => $t->post_status, 'created' => $t->post_date, ]; }, $tasks); } public function get_logs($request) { global $wpdb; $where = '1=1'; if ($request->get_param('level')) { $where .= $wpdb->prepare(' AND level = %s', $request->get_param('level')); } $limit = intval($request->get_param('limit')); $offset = intval($request->get_param('offset')); $results = $wpdb->get_results( "SELECT id, level, message, context, created_at FROM {$wpdb->prefix}hermes_log WHERE $where ORDER BY id DESC LIMIT $offset, $limit" ); return array_map(function($r) { $r->context = $r->context ? json_decode($r->context) : null; return $r; }, $results); } public static function log($level, $message, $context = []) { global $wpdb; $wpdb->insert( $wpdb->prefix . 'hermes_log', [ 'level' => $level, 'message' => $message, 'context' => wp_json_encode($context), 'created_at' => current_time('mysql'), ] ); } }