/ internal / daemon / project_init.go
project_init.go
 1  package daemon
 2  
 3  import (
 4  	"net/http"
 5  	"os"
 6  	"path/filepath"
 7  	"strings"
 8  
 9  	"github.com/Kocoro-lab/ShanClaw/internal/agents"
10  )
11  
12  func (s *Server) handleProjectInit(w http.ResponseWriter, r *http.Request) {
13  	if s.deps == nil {
14  		writeError(w, http.StatusInternalServerError, "daemon deps not configured")
15  		return
16  	}
17  	var req struct {
18  		CWD          string `json:"cwd"`
19  		Instructions string `json:"instructions,omitempty"`
20  	}
21  	if !decodeBody(w, r, &req) {
22  		return
23  	}
24  
25  	if !filepath.IsAbs(req.CWD) {
26  		writeError(w, http.StatusBadRequest, "cwd must be an absolute path")
27  		return
28  	}
29  	info, err := os.Stat(req.CWD)
30  	if err != nil {
31  		writeError(w, http.StatusBadRequest, "cwd does not exist: "+req.CWD)
32  		return
33  	}
34  	if !info.IsDir() {
35  		writeError(w, http.StatusBadRequest, "cwd is not a directory: "+req.CWD)
36  		return
37  	}
38  
39  	shannonClean := filepath.Clean(s.deps.ShannonDir)
40  	if resolved, err := filepath.EvalSymlinks(s.deps.ShannonDir); err == nil {
41  		shannonClean = resolved
42  	}
43  	cwdClean := filepath.Clean(req.CWD)
44  	if resolved, err := filepath.EvalSymlinks(req.CWD); err == nil {
45  		cwdClean = resolved
46  	}
47  	if cwdClean == shannonClean || strings.HasPrefix(cwdClean, shannonClean+string(filepath.Separator)) {
48  		writeError(w, http.StatusBadRequest, "cannot initialize project inside the global shannon directory")
49  		return
50  	}
51  
52  	dotShannon := filepath.Join(req.CWD, ".shannon")
53  	var created, existed []string
54  
55  	if _, err := os.Stat(dotShannon); os.IsNotExist(err) {
56  		if err := os.MkdirAll(dotShannon, 0700); err != nil {
57  			writeError(w, http.StatusInternalServerError, err.Error())
58  			return
59  		}
60  		created = append(created, ".shannon/")
61  	} else {
62  		existed = append(existed, ".shannon/")
63  	}
64  
65  	if req.Instructions != "" {
66  		instPath := filepath.Join(dotShannon, "instructions.md")
67  		if _, err := os.Stat(instPath); os.IsNotExist(err) {
68  			if err := agents.AtomicWrite(instPath, []byte(req.Instructions)); err != nil {
69  				writeError(w, http.StatusInternalServerError, err.Error())
70  				return
71  			}
72  			created = append(created, ".shannon/instructions.md")
73  		} else {
74  			existed = append(existed, ".shannon/instructions.md")
75  		}
76  	}
77  
78  	if created == nil {
79  		created = []string{}
80  	}
81  	if existed == nil {
82  		existed = []string{}
83  	}
84  
85  	s.auditHTTPOp("POST", "/project/init", "initialized project at "+req.CWD)
86  	writeJSON(w, http.StatusOK, map[string]interface{}{
87  		"created": created,
88  		"existed": existed,
89  	})
90  }