package bootstrap import ( "embed" "log/slog" "os" "path/filepath" ) //go:embed templates/*.md var templateFS embed.FS // templateFiles lists the templates to seed, in order. // BOOTSTRAP.md is handled separately (only seeded for brand-new workspaces). var templateFiles = []string{ AgentsFile, SoulFile, ToolsFile, IdentityFile, UserFile, } // ReadTemplate returns the content of an embedded template file. func ReadTemplate(name string) (string, error) { content, err := templateFS.ReadFile(filepath.Join("templates", name)) if err != nil { return "", err } return string(content), nil } // EnsureWorkspaceFiles seeds template files into a workspace directory. // Only writes files that don't already exist (will not overwrite). // BOOTSTRAP.md is only seeded if the workspace is brand new (no AGENTS.md exists). // Returns the list of files that were created. func EnsureWorkspaceFiles(workspaceDir string) ([]string, error) { if err := os.MkdirAll(workspaceDir, 0755); err != nil { return nil, err } var created []string // Check if this is a brand-new workspace (no AGENTS.md yet) _, agentsErr := os.Stat(filepath.Join(workspaceDir, AgentsFile)) isBrandNew := os.IsNotExist(agentsErr) // Seed standard template files for _, name := range templateFiles { ok, err := seedTemplate(workspaceDir, name) if err != nil { slog.Warn("bootstrap: failed to seed template", "file", name, "error", err) continue } if ok { created = append(created, name) } } // Seed BOOTSTRAP.md only for brand-new workspaces if isBrandNew { ok, err := seedTemplate(workspaceDir, BootstrapFile) if err != nil { slog.Warn("bootstrap: failed to seed BOOTSTRAP.md", "error", err) } else if ok { created = append(created, BootstrapFile) } } return created, nil } // seedTemplate writes a template file to the workspace if it doesn't exist. // Returns true if the file was created, false if it already exists. func seedTemplate(workspaceDir, name string) (bool, error) { dstPath := filepath.Join(workspaceDir, name) // Only create if file doesn't exist (O_EXCL) f, err := os.OpenFile(dstPath, os.O_WRONLY|os.O_CREATE|os.O_EXCL, 0644) if err != nil { if os.IsExist(err) { return false, nil // already exists, skip } return false, err } defer f.Close() // Read embedded template content, err := templateFS.ReadFile(filepath.Join("templates", name)) if err != nil { os.Remove(dstPath) // clean up empty file return false, err } if _, err := f.Write(content); err != nil { return false, err } return true, nil }