package handler import ( "encoding/json" "net/http" "strings" "git.yeij.top/AskaEth/Cyrene/pkg/plugins/manager" ) // PluginHandler exposes the Plugin Manager REST API via net/http. type PluginHandler struct { mgr *manager.PluginManager } func NewPluginHandler(mgr *manager.PluginManager) *PluginHandler { return &PluginHandler{mgr: mgr} } func (h *PluginHandler) RegisterRoutes(mux *http.ServeMux) { mux.HandleFunc("/api/v1/plugins", h.listPlugins) mux.HandleFunc("/api/v1/plugins/", h.pluginRoute) mux.HandleFunc("/api/v1/tools", h.listTools) mux.HandleFunc("/api/v1/tools/", h.toolRoute) mux.HandleFunc("/api/v1/health", h.health) } func (h *PluginHandler) health(w http.ResponseWriter, r *http.Request) { writeJSON(w, http.StatusOK, map[string]interface{}{"status": "ok", "service": "plugin-manager"}) } func (h *PluginHandler) listPlugins(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed")) return } plugins := h.mgr.List() writeJSON(w, http.StatusOK, map[string]interface{}{"plugins": plugins, "total": len(plugins)}) } func (h *PluginHandler) pluginRoute(w http.ResponseWriter, r *http.Request) { path := strings.TrimPrefix(r.URL.Path, "/api/v1/plugins/") parts := strings.SplitN(path, "/", 2) pluginID := parts[0] if pluginID == "" { h.listPlugins(w, r) return } if len(parts) == 1 { switch r.Method { case "GET": h.getPlugin(w, pluginID) case "DELETE": h.uninstallPlugin(w, r, pluginID) default: writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed")) } return } action := parts[1] switch action { case "enable": if r.Method != "POST" { writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed")) return } h.enablePlugin(w, r, pluginID) case "disable": if r.Method != "POST" { writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed")) return } h.disablePlugin(w, r, pluginID) case "reload": if r.Method != "POST" { writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed")) return } h.reloadPlugin(w, r, pluginID) case "tools": if r.Method != "GET" { writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed")) return } h.pluginTools(w, pluginID) default: writeJSON(w, http.StatusNotFound, errResp("not found")) } } func (h *PluginHandler) getPlugin(w http.ResponseWriter, id string) { info, ok := h.mgr.Get(id) if !ok { writeJSON(w, http.StatusNotFound, errResp("plugin not found")) return } writeJSON(w, http.StatusOK, info) } func (h *PluginHandler) enablePlugin(w http.ResponseWriter, r *http.Request, id string) { if err := h.mgr.Enable(r.Context(), id); err != nil { writeJSON(w, http.StatusInternalServerError, errResp(err.Error())) return } writeJSON(w, http.StatusOK, map[string]string{"status": "enabled"}) } func (h *PluginHandler) disablePlugin(w http.ResponseWriter, r *http.Request, id string) { if err := h.mgr.Disable(r.Context(), id); err != nil { writeJSON(w, http.StatusInternalServerError, errResp(err.Error())) return } writeJSON(w, http.StatusOK, map[string]string{"status": "disabled"}) } func (h *PluginHandler) reloadPlugin(w http.ResponseWriter, r *http.Request, id string) { if err := h.mgr.Reload(r.Context(), id); err != nil { writeJSON(w, http.StatusInternalServerError, errResp(err.Error())) return } writeJSON(w, http.StatusOK, map[string]string{"status": "reloaded"}) } func (h *PluginHandler) uninstallPlugin(w http.ResponseWriter, r *http.Request, id string) { if err := h.mgr.Uninstall(r.Context(), id); err != nil { writeJSON(w, http.StatusInternalServerError, errResp(err.Error())) return } writeJSON(w, http.StatusOK, map[string]string{"status": "uninstalled"}) } func (h *PluginHandler) pluginTools(w http.ResponseWriter, id string) { info, ok := h.mgr.Get(id) if !ok { writeJSON(w, http.StatusNotFound, errResp("plugin not found")) return } registry := h.mgr.Registry() tools := make([]interface{}, 0) for _, toolID := range info.Tools { if t, ok := registry.Get(toolID); ok { tools = append(tools, t.Definition()) } } writeJSON(w, http.StatusOK, map[string]interface{}{"tools": tools, "total": len(tools)}) } func (h *PluginHandler) listTools(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" { writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed")) return } defs := h.mgr.Registry().Definitions() writeJSON(w, http.StatusOK, map[string]interface{}{"tools": defs, "total": len(defs)}) } func (h *PluginHandler) toolRoute(w http.ResponseWriter, r *http.Request) { path := strings.TrimPrefix(r.URL.Path, "/api/v1/tools/") toolID := path if strings.HasSuffix(path, "/execute") { toolID = strings.TrimSuffix(path, "/execute") if r.Method != "POST" { writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed")) return } h.executeTool(w, r, toolID) return } if r.Method != "GET" { writeJSON(w, http.StatusMethodNotAllowed, errResp("method not allowed")) return } tool, ok := h.mgr.Registry().Get(toolID) if !ok { writeJSON(w, http.StatusNotFound, errResp("tool not found")) return } writeJSON(w, http.StatusOK, tool.Definition()) } func (h *PluginHandler) executeTool(w http.ResponseWriter, r *http.Request, toolID string) { var body struct { Arguments map[string]interface{} `json:"arguments"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { writeJSON(w, http.StatusBadRequest, errResp("invalid request body")) return } result, err := h.mgr.Registry().Execute(r.Context(), toolID, body.Arguments) if err != nil { writeJSON(w, http.StatusInternalServerError, errResp(err.Error())) return } writeJSON(w, http.StatusOK, result) } func errResp(msg string) map[string]string { return map[string]string{"error": msg} } func writeJSON(w http.ResponseWriter, status int, data interface{}) { w.Header().Set("Content-Type", "application/json") w.WriteHeader(status) json.NewEncoder(w).Encode(data) }