package main import ( "os" "path/filepath" "runtime" "slices" "strings" ) func expandPath(path string) (string, error) { if path == "~" || strings.HasPrefix(path, "~/") { home, err := os.UserHomeDir() if err != nil { return "", err } if path == "~" { return home, nil } return filepath.Join(home, strings.TrimPrefix(path, "~/")), nil } return path, nil } func dirLooksLikeFrontendSource(path string) bool { info, err := os.Stat(path) if err != nil || !info.IsDir() { return false } entries, err := os.ReadDir(path) if err != nil { return false } for _, entry := range entries { if entry.IsDir() { continue } name := entry.Name() if strings.HasSuffix(name, ".ts") || strings.HasSuffix(name, ".tsx") || strings.HasSuffix(name, ".js") || strings.HasSuffix(name, ".jsx") { return true } } return false } func dirExists(path string) bool { info, err := os.Stat(path) return err == nil && info.IsDir() } func samePath(a, b string) bool { if a == "" || b == "" { return false } absA, err := filepath.Abs(a) if err != nil { return false } absB, err := filepath.Abs(b) if err != nil { return false } infoA, errA := os.Stat(absA) infoB, errB := os.Stat(absB) if errA == nil && errB == nil && os.SameFile(infoA, infoB) { return true } realA, errA := filepath.EvalSymlinks(absA) realB, errB := filepath.EvalSymlinks(absB) if errA == nil && errB == nil { return realA == realB } return filepath.Clean(absA) == filepath.Clean(absB) } // consolidatedWorkspaceRoot locates the parent that owns the independent CMS, // orchestrator, and check-safety repositories. The checkout may live anywhere // (for example /srv/agent-work/blockninja), so discovery starts from runtime // locations before retaining the historical ~/src/blockninja fallback. func consolidatedWorkspaceRoot() string { var candidates []string if cwd, err := os.Getwd(); err == nil { candidates = append(candidates, cwd) } if executable, err := os.Executable(); err == nil { candidates = append(candidates, filepath.Dir(executable)) } if _, sourceFile, _, ok := runtime.Caller(0); ok { candidates = append(candidates, filepath.Dir(sourceFile)) } if home, err := os.UserHomeDir(); err == nil { candidates = append(candidates, filepath.Join(home, "src", "blockninja")) } seen := make(map[string]bool, len(candidates)) for _, candidate := range candidates { root := findConsolidatedWorkspaceRoot(candidate) if root == "" || seen[root] { continue } seen[root] = true return root } if home, err := os.UserHomeDir(); err == nil { return filepath.Join(home, "src", "blockninja") } return "" } func findConsolidatedWorkspaceRoot(start string) string { current, err := filepath.Abs(start) if err != nil { return "" } if info, statErr := os.Stat(current); statErr == nil && !info.IsDir() { current = filepath.Dir(current) } for { if fileExists(filepath.Join(current, "cms", "backend", "go.mod")) && fileExists(filepath.Join(current, "orchestrator", "backend", "go.mod")) && fileExists(filepath.Join(current, "check-safety", "go.mod")) { return current } parent := filepath.Dir(current) if parent == current { return "" } current = parent } } func normalizeDisplayLabel(label string) string { if label == "." { return "" } return label } func fileExists(path string) bool { info, err := os.Stat(path) return err == nil && !info.IsDir() } func prefixDisplayPath(prefix, path string) string { if prefix == "" || prefix == "." { return path } if path == "" { return prefix } return prefix + "/" + filepath.ToSlash(path) } func shortPluginLabel(path string) string { cleaned := filepath.Clean(path) base := filepath.Base(cleaned) if base == "." || base == string(filepath.Separator) || base == "" { return path } return base } func isPluginModuleRoot(root string) bool { modulePath, err := readModulePath(root) if err != nil { return fileExists(filepath.Join(root, "plugin.mod")) } return strings.Contains(modulePath, "/internal/plugins/") || fileExists(filepath.Join(root, "plugin.mod")) } func containsString(values []string, target string) bool { return slices.Contains(values, target) }