diff options
| author | Peter Stone <thepeterstone@gmail.com> | 2026-03-03 21:15:50 +0000 |
|---|---|---|
| committer | Peter Stone <thepeterstone@gmail.com> | 2026-03-03 21:15:50 +0000 |
| commit | 74cc740398cf2d90804ab19db728c844c2e056b7 (patch) | |
| tree | e8532d1da9273e1613beb7b762b16134da0de286 /internal/api/templates.go | |
| parent | f527972f4d8311a09e639ede6c4da4ca669cfd5e (diff) | |
Add elaborate, logs-stream, templates, and subtask-list endpoints
- POST /api/tasks/elaborate: calls claude to draft a task config from
a natural-language prompt
- GET /api/executions/{id}/logs/stream: SSE tail of stdout.log
- CRUD /api/templates: create/list/get/update/delete reusable task configs
- GET /api/tasks/{id}/subtasks: list child tasks
- Server.NewServer accepts claudeBinPath for elaborate; injectable
elaborateCmdPath and logStore for test isolation
- Valid-transition guard added to POST /api/tasks/{id}/run
- CLI passes claude binary path through to the server
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/api/templates.go')
| -rw-r--r-- | internal/api/templates.go | 144 |
1 files changed, 144 insertions, 0 deletions
diff --git a/internal/api/templates.go b/internal/api/templates.go new file mode 100644 index 0000000..0139895 --- /dev/null +++ b/internal/api/templates.go @@ -0,0 +1,144 @@ +package api + +import ( + "encoding/json" + "errors" + "net/http" + "time" + + "github.com/google/uuid" + "github.com/thepeterstone/claudomator/internal/storage" + "github.com/thepeterstone/claudomator/internal/task" +) + +func (s *Server) handleListTemplates(w http.ResponseWriter, r *http.Request) { + templates, err := s.store.ListTemplates() + if err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + if templates == nil { + templates = []*storage.Template{} + } + writeJSON(w, http.StatusOK, templates) +} + +func (s *Server) handleCreateTemplate(w http.ResponseWriter, r *http.Request) { + var input struct { + Name string `json:"name"` + Description string `json:"description"` + Claude task.ClaudeConfig `json:"claude"` + Timeout string `json:"timeout"` + Priority string `json:"priority"` + Tags []string `json:"tags"` + } + if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON: " + err.Error()}) + return + } + if input.Name == "" { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "name is required"}) + return + } + + now := time.Now().UTC() + tmpl := &storage.Template{ + ID: uuid.New().String(), + Name: input.Name, + Description: input.Description, + Claude: input.Claude, + Timeout: input.Timeout, + Priority: input.Priority, + Tags: input.Tags, + CreatedAt: now, + UpdatedAt: now, + } + if tmpl.Priority == "" { + tmpl.Priority = "normal" + } + if tmpl.Tags == nil { + tmpl.Tags = []string{} + } + + if err := s.store.CreateTemplate(tmpl); err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + writeJSON(w, http.StatusCreated, tmpl) +} + +func (s *Server) handleGetTemplate(w http.ResponseWriter, r *http.Request) { + id := r.PathValue("id") + tmpl, err := s.store.GetTemplate(id) + if err != nil { + if errors.Is(err, storage.ErrTemplateNotFound) { + writeJSON(w, http.StatusNotFound, map[string]string{"error": "template not found"}) + return + } + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + writeJSON(w, http.StatusOK, tmpl) +} + +// handleUpdateTemplate fully replaces all fields of the template identified by {id}. +// All fields from the request body overwrite existing values; name is required. +func (s *Server) handleUpdateTemplate(w http.ResponseWriter, r *http.Request) { + id := r.PathValue("id") + existing, err := s.store.GetTemplate(id) + if err != nil { + if errors.Is(err, storage.ErrTemplateNotFound) { + writeJSON(w, http.StatusNotFound, map[string]string{"error": "template not found"}) + return + } + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + + var input struct { + Name string `json:"name"` + Description string `json:"description"` + Claude task.ClaudeConfig `json:"claude"` + Timeout string `json:"timeout"` + Priority string `json:"priority"` + Tags []string `json:"tags"` + } + if err := json.NewDecoder(r.Body).Decode(&input); err != nil { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "invalid JSON: " + err.Error()}) + return + } + if input.Name == "" { + writeJSON(w, http.StatusBadRequest, map[string]string{"error": "name is required"}) + return + } + + existing.Name = input.Name + existing.Description = input.Description + existing.Claude = input.Claude + existing.Timeout = input.Timeout + existing.Priority = input.Priority + if input.Tags != nil { + existing.Tags = input.Tags + } + existing.UpdatedAt = time.Now().UTC() + + if err := s.store.UpdateTemplate(existing); err != nil { + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + writeJSON(w, http.StatusOK, existing) +} + +func (s *Server) handleDeleteTemplate(w http.ResponseWriter, r *http.Request) { + id := r.PathValue("id") + err := s.store.DeleteTemplate(id) + if err != nil { + if errors.Is(err, storage.ErrTemplateNotFound) { + writeJSON(w, http.StatusNotFound, map[string]string{"error": "template not found"}) + return + } + writeJSON(w, http.StatusInternalServerError, map[string]string{"error": err.Error()}) + return + } + w.WriteHeader(http.StatusNoContent) +} |
