date('c'), 'source'=>'opus5-plugin-store']; $PLUGIN_DIR = '/opt/weval-plugins'; $REGISTRY = '/var/www/html/api/wevia-plugin-registry.json'; if (!file_exists($PLUGIN_DIR)) { @mkdir($PLUGIN_DIR, 0755, true); } $action = $_GET['action'] ?? 'list'; function scan_plugins($dir) { $plugins = []; if (!is_dir($dir)) return $plugins; foreach (scandir($dir) as $name) { if ($name === '.' || $name === '..') continue; $path = "$dir/$name"; if (!is_dir($path)) continue; $manifest_file = "$path/manifest.json"; $readme_file = "$path/README.md"; $plugin = [ 'id' => $name, 'path' => $path, 'has_manifest' => file_exists($manifest_file), 'has_readme' => file_exists($readme_file), ]; if ($plugin['has_manifest']) { $manifest = @json_decode(file_get_contents($manifest_file), true) ?: []; $plugin['name'] = $manifest['name'] ?? $name; $plugin['version'] = $manifest['version'] ?? '0.1.0'; $plugin['description'] = $manifest['description'] ?? ''; $plugin['author'] = $manifest['author'] ?? 'unknown'; $plugin['intents'] = $manifest['intents'] ?? []; $plugin['entrypoint'] = $manifest['entrypoint'] ?? 'run.sh'; $plugin['enabled'] = !empty($manifest['enabled']); } else { // Auto-detect basic info $plugin['name'] = $name; $plugin['version'] = '0.0.1'; $plugin['description'] = '[no manifest]'; $plugin['enabled'] = false; // Try to detect entrypoint foreach (['run.sh', 'index.php', 'main.py', 'plugin.sh'] as $ep) { if (file_exists("$path/$ep")) { $plugin['entrypoint'] = $ep; break; } } } $plugins[] = $plugin; } return $plugins; } switch ($action) { case 'discover': case 'list': $plugins = scan_plugins($PLUGIN_DIR); $R['count'] = count($plugins); $R['plugins'] = $plugins; // Write registry @file_put_contents($REGISTRY, json_encode([ 'ts' => date('c'), 'count' => count($plugins), 'plugins' => $plugins ], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); $R['registry_written'] = $REGISTRY; break; case 'enable': $pid = preg_replace('/[^a-z0-9_-]/i', '', $_GET['id'] ?? ''); if (!$pid) { http_response_code(400); echo json_encode(['err'=>'no_id']); exit; } $mf = "$PLUGIN_DIR/$pid/manifest.json"; if (!file_exists($mf)) { http_response_code(404); echo json_encode(['err'=>'manifest_not_found']); exit; } $m = json_decode(file_get_contents($mf), true); $m['enabled'] = true; @file_put_contents($mf, json_encode($m, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); $R['enabled'] = $pid; break; case 'disable': $pid = preg_replace('/[^a-z0-9_-]/i', '', $_GET['id'] ?? ''); if (!$pid) { http_response_code(400); echo json_encode(['err'=>'no_id']); exit; } $mf = "$PLUGIN_DIR/$pid/manifest.json"; if (!file_exists($mf)) { http_response_code(404); echo json_encode(['err'=>'manifest_not_found']); exit; } $m = json_decode(file_get_contents($mf), true); $m['enabled'] = false; @file_put_contents($mf, json_encode($m, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE)); $R['disabled'] = $pid; break; case 'run': $pid = preg_replace('/[^a-z0-9_-]/i', '', $_GET['id'] ?? ''); if (!$pid) { http_response_code(400); echo json_encode(['err'=>'no_id']); exit; } $path = "$PLUGIN_DIR/$pid"; $mf = "$path/manifest.json"; if (!file_exists($mf)) { http_response_code(404); echo json_encode(['err'=>'manifest_not_found']); exit; } $m = json_decode(file_get_contents($mf), true); if (empty($m['enabled'])) { http_response_code(403); echo json_encode(['err'=>'plugin_disabled']); exit; } $ep = $m['entrypoint'] ?? 'run.sh'; $full_ep = "$path/$ep"; if (!file_exists($full_ep)) { http_response_code(404); echo json_encode(['err'=>'entrypoint_not_found', 'ep'=>$full_ep]); exit; } // Safe exec with timeout 10s $out = @shell_exec("timeout 10 bash " . escapeshellarg($full_ep) . " 2>&1"); $R['plugin'] = $pid; $R['exec_output'] = substr((string)$out, 0, 3000); $R['truncated'] = strlen((string)$out) > 3000; break; default: $R['err'] = 'unknown_action'; $R['available'] = ['discover', 'list', 'enable', 'disable', 'run']; } $R['doctrine'] = '73 — plugin store autodiscovery (manifest.json scan + registry + enable/disable/run)'; echo json_encode($R, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE);