Bladeren bron

dev: automated commit - 2025-05-31 17:07:41

Mariano Z. 8 maanden geleden
bovenliggende
commit
b35d327830
4 gewijzigde bestanden met toevoegingen van 203 en 339 verwijderingen
  1. 2 0
      dev
  2. 7 0
      go.mod
  3. 10 0
      go.sum
  4. 184 339
      main.go

+ 2 - 0
dev

@@ -34,6 +34,8 @@ if [[ ! -f "$BINARY" ]] || [[ "main.go" -nt "$BINARY" ]]; then
     log "Building dev tool..."
     mkdir -p bin
 
+    go mod tidy
+
     if go build -ldflags="-s -w" -o "$BINARY" main.go; then
         log "✅ Built: $BINARY"
     else

+ 7 - 0
go.mod

@@ -1,3 +1,10 @@
 module marianozunino/dev
 
 go 1.24.1
+
+require github.com/spf13/cobra v1.9.1
+
+require (
+	github.com/inconshreveable/mousetrap v1.1.0 // indirect
+	github.com/spf13/pflag v1.0.6 // indirect
+)

+ 10 - 0
go.sum

@@ -0,0 +1,10 @@
+github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
+github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
+github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
+github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
+github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo=
+github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0=
+github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
+github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
+gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
+gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

+ 184 - 339
main.go

@@ -10,43 +10,33 @@ import (
 	"sort"
 	"strings"
 	"time"
+
+	"github.com/spf13/cobra"
 )
 
-// Colors for terminal output
 const (
 	Red    = "\033[0;31m"
 	Green  = "\033[0;32m"
 	Blue   = "\033[0;34m"
 	Yellow = "\033[0;33m"
-	NC     = "\033[0m" // No Color
+	NC     = "\033[0m"
 )
 
-// Global configuration
 type Config struct {
-	ScriptDir string
-	EnvFile   string
-	RunsDir   string
-	LogsDir   string
-	DryRun    bool
-	Verbose   bool
-	Filters   []string
-	Command   string
+	RunsDir string
+	LogsDir string
+	DryRun  bool
+	Verbose bool
 }
 
-// Script represents an executable script
 type Script struct {
 	Path        string
 	Name        string
 	Description string
 }
 
-// ExecutionResult tracks script execution outcomes
-type ExecutionResult struct {
-	Executed []string
-	Failed   []string
-}
+var config Config
 
-// Logging functions
 func log(msg string, args ...interface{}) {
 	fmt.Printf(Green+"[RUN]"+NC+" "+msg+"\n", args...)
 }
@@ -59,13 +49,11 @@ func errorLog(msg string, args ...interface{}) {
 	fmt.Fprintf(os.Stderr, Red+"[ERROR]"+NC+" "+msg+"\n", args...)
 }
 
-// Check if command exists in PATH
 func commandExists(cmd string) bool {
 	_, err := exec.LookPath(cmd)
 	return err == nil
 }
 
-// Check for required and optional dependencies
 func checkDependencies() error {
 	requiredTools := []string{"git", "find", "grep"}
 	optionalTools := []string{"gitleaks"}
@@ -73,14 +61,12 @@ func checkDependencies() error {
 
 	log("Checking dependencies...")
 
-	// Check required tools
 	for _, tool := range requiredTools {
 		if !commandExists(tool) {
 			missing = append(missing, tool)
 		}
 	}
 
-	// Check optional tools
 	for _, tool := range optionalTools {
 		if !commandExists(tool) {
 			warn("Optional tool missing: %s (recommended for security scanning)", tool)
@@ -89,7 +75,6 @@ func checkDependencies() error {
 		}
 	}
 
-	// Report missing required tools
 	if len(missing) > 0 {
 		errorLog("Missing required tools: %s", strings.Join(missing, ", "))
 		errorLog("Please install missing dependencies")
@@ -100,7 +85,6 @@ func checkDependencies() error {
 	return nil
 }
 
-// Run security scan using gitleaks
 func runSecurityScan() error {
 	log("Running security scan...")
 
@@ -133,19 +117,16 @@ func runSecurityScan() error {
 	return nil
 }
 
-// Check if we're in a git repository
 func isGitRepo() bool {
 	cmd := exec.Command("git", "rev-parse", "--git-dir")
 	return cmd.Run() == nil
 }
 
-// Check for uncommitted changes
 func hasUncommittedChanges() bool {
 	cmd := exec.Command("git", "diff-index", "--quiet", "HEAD", "--")
 	return cmd.Run() != nil
 }
 
-// Get current git branch
 func getCurrentBranch() (string, error) {
 	cmd := exec.Command("git", "branch", "--show-current")
 	output, err := cmd.Output()
@@ -155,28 +136,23 @@ func getCurrentBranch() (string, error) {
 	return strings.TrimSpace(string(output)), nil
 }
 
-// Handle git push workflow
 func handlePush() error {
 	log("Preparing to push repository...")
 
-	// Check if we're in a git repository
 	if !isGitRepo() {
 		errorLog("Not in a git repository")
 		return fmt.Errorf("not in git repo")
 	}
 
-	// Check for uncommitted changes
 	if hasUncommittedChanges() {
 		warn("You have uncommitted changes")
 		fmt.Println()
 
-		// Show git status
 		cmd := exec.Command("git", "status", "--short")
 		cmd.Stdout = os.Stdout
 		cmd.Run()
 		fmt.Println()
 
-		// Generate default commit message
 		defaultMsg := fmt.Sprintf("dev: automated commit - %s", time.Now().Format("2006-01-02 15:04:05"))
 
 		fmt.Print("Commit all changes? (Y/n): ")
@@ -196,7 +172,6 @@ func handlePush() error {
 				commitMsg = defaultMsg
 			}
 
-			// Add and commit changes
 			if err := exec.Command("git", "add", ".").Run(); err != nil {
 				return fmt.Errorf("failed to add changes: %v", err)
 			}
@@ -209,18 +184,15 @@ func handlePush() error {
 		}
 	}
 
-	// Run security scan
 	if err := runSecurityScan(); err != nil {
 		return err
 	}
 
-	// Get current branch
 	branch, err := getCurrentBranch()
 	if err != nil {
 		return fmt.Errorf("failed to get current branch: %v", err)
 	}
 
-	// Push to origin
 	log("Pushing branch '%s' to origin...", branch)
 	cmd := exec.Command("git", "push", "origin", branch)
 
@@ -233,41 +205,6 @@ func handlePush() error {
 	return nil
 }
 
-// Show help message
-func showHelp(programName string) {
-	fmt.Printf(`%s - Development script runner
-
-Usage: %s <command> [options] [arguments]
-
-COMMANDS:
-  run [filters...]  Run scripts matching filters (or all if no filters)
-  push,u,ush        Commit, scan for secrets, and push to git origin
-  ls, list          List all available scripts
-  new <name>        Create a new script template
-  deps, check       Check for required dependencies
-  help              Show this help message
-
-RUN OPTIONS:
-  --dry             Show what would run without executing
-  --verbose         Show detailed output during execution
-
-EXAMPLES:
-  %s                # Show this help
-  %s run            # Run all scripts (with confirmation)
-  %s run docker     # Run scripts containing 'docker'
-  %s run --dry api  # Show what API scripts would run
-  %s ls             # List all scripts
-  %s new backup     # Create a new script called 'backup.sh'
-  %s push           # Secure git push with secret scanning
-
-SECURITY:
-  The push command automatically scans for secrets using GitLeaks
-  before pushing to prevent accidental credential exposure.
-
-`, programName, programName, programName, programName, programName, programName, programName, programName, programName)
-}
-
-// Get script description from comment
 func getDescription(scriptPath string) string {
 	file, err := os.Open(scriptPath)
 	if err != nil {
@@ -288,7 +225,6 @@ func getDescription(scriptPath string) string {
 	return "No description"
 }
 
-// Check if script name matches any of the filters
 func matchesFilters(scriptName string, filters []string) bool {
 	if len(filters) == 0 {
 		return true
@@ -304,16 +240,14 @@ func matchesFilters(scriptName string, filters []string) bool {
 	return false
 }
 
-// Find executable scripts in runs directory
-func findScripts(runsDir string, filters []string) ([]Script, error) {
+func findScripts(filters []string) ([]Script, error) {
 	var scripts []Script
 
-	err := filepath.Walk(runsDir, func(path string, info os.FileInfo, err error) error {
+	err := filepath.Walk(config.RunsDir, func(path string, info os.FileInfo, err error) error {
 		if err != nil {
 			return err
 		}
 
-		// Check if file is executable
 		if info.Mode().IsRegular() && (info.Mode()&0o111) != 0 {
 			scriptName := filepath.Base(path)
 			if matchesFilters(scriptName, filters) {
@@ -330,7 +264,6 @@ func findScripts(runsDir string, filters []string) ([]Script, error) {
 		return nil, err
 	}
 
-	// Sort scripts by name
 	sort.Slice(scripts, func(i, j int) bool {
 		return scripts[i].Name < scripts[j].Name
 	})
@@ -338,96 +271,21 @@ func findScripts(runsDir string, filters []string) ([]Script, error) {
 	return scripts, nil
 }
 
-// List all available scripts
-func listScripts(runsDir string) error {
-	scripts, err := findScripts(runsDir, nil)
-	if err != nil {
-		return err
-	}
-
-	if len(scripts) == 0 {
-		warn("No executable scripts found in %s", runsDir)
-		return nil
-	}
-
-	fmt.Printf(Blue + "Available scripts:" + NC + "\n")
-	for _, script := range scripts {
-		fmt.Printf("  %s%s%s - %s\n", Green, script.Name, NC, script.Description)
-	}
-	fmt.Printf("\nTotal: %d scripts\n", len(scripts))
-	return nil
-}
-
-// Create a new script template
-func createNewScript(runsDir, scriptName string) error {
-	if scriptName == "" {
-		return fmt.Errorf("script name required")
-	}
-
-	// Ensure runs directory exists
-	if err := os.MkdirAll(runsDir, 0o755); err != nil {
-		return err
-	}
-
-	// Add .sh extension if not provided
-	if !strings.HasSuffix(scriptName, ".sh") {
-		scriptName += ".sh"
-	}
-
-	scriptPath := filepath.Join(runsDir, scriptName)
-
-	// Check if script already exists
-	if _, err := os.Stat(scriptPath); err == nil {
-		return fmt.Errorf("script %s already exists", scriptName)
-	}
-
-	// Create script template
-	template := fmt.Sprintf(`#!/usr/bin/env bash
-# NAME: %s script
-
-set -euo pipefail
-
-# Add your script logic here
-echo "Running %s script..."
-
-# Example:
-# - Add your commands below
-# - Use proper error handling
-# - Add logging as needed
-
-echo "✅ %s completed successfully"
-`, strings.TrimSuffix(scriptName, ".sh"), scriptName, strings.TrimSuffix(scriptName, ".sh"))
-
-	if err := os.WriteFile(scriptPath, []byte(template), 0o755); err != nil {
-		return err
-	}
-
-	log("✅ Created new script: %s", scriptPath)
-	log("Edit the script to add your commands")
-	return nil
-}
-
-// Execute a single script
-func executeScript(script Script, logsDir string, verbose bool) error {
-	logFile := filepath.Join(logsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log")
-
+func executeScript(script Script, verbose bool) error {
+	logFile := filepath.Join(config.LogsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log")
 	cmd := exec.Command(script.Path)
 
 	if verbose {
-		// Create log file
 		logFileHandle, err := os.Create(logFile)
 		if err != nil {
 			return err
 		}
 		defer logFileHandle.Close()
 
-		// Use MultiWriter to write to both stdout and log file
 		cmd.Stdout = io.MultiWriter(os.Stdout, logFileHandle)
 		cmd.Stderr = io.MultiWriter(os.Stderr, logFileHandle)
-
 		return cmd.Run()
 	} else {
-		// Only write to log file
 		logFileHandle, err := os.Create(logFile)
 		if err != nil {
 			return err
@@ -436,77 +294,10 @@ func executeScript(script Script, logsDir string, verbose bool) error {
 
 		cmd.Stdout = logFileHandle
 		cmd.Stderr = logFileHandle
-
 		return cmd.Run()
 	}
 }
 
-// Parse command line arguments
-func parseArgs(args []string) (*Config, error) {
-	config := &Config{}
-
-	// Get current working directory (equivalent to bash SCRIPT_DIR)
-	scriptDir, err := os.Getwd()
-	if err != nil {
-		return nil, err
-	}
-	config.ScriptDir = scriptDir
-	config.EnvFile = filepath.Join(config.ScriptDir, ".env")
-	config.RunsDir = filepath.Join(config.ScriptDir, "runs")
-	config.LogsDir = filepath.Join(config.ScriptDir, "logs")
-
-	if len(args) == 0 {
-		return config, fmt.Errorf("help")
-	}
-
-	i := 0
-	// First argument should be the command
-	if i < len(args) {
-		arg := args[i]
-		switch arg {
-		case "--help", "help":
-			return config, fmt.Errorf("help")
-		case "run":
-			config.Command = "run"
-			i++
-		case "push", "u", "ush":
-			config.Command = "push"
-			i++
-		case "ls", "list":
-			config.Command = "list"
-			i++
-		case "new":
-			config.Command = "new"
-			i++
-		case "deps", "check":
-			config.Command = "deps"
-			i++
-		default:
-			return nil, fmt.Errorf("unknown command: %s", arg)
-		}
-	}
-
-	// Parse remaining arguments based on command
-	for i < len(args) {
-		arg := args[i]
-		switch arg {
-		case "--dry":
-			config.DryRun = true
-		case "--verbose":
-			config.Verbose = true
-		default:
-			if strings.HasPrefix(arg, "-") {
-				return nil, fmt.Errorf("unknown option: %s", arg)
-			}
-			config.Filters = append(config.Filters, arg)
-		}
-		i++
-	}
-
-	return config, nil
-}
-
-// Confirm execution of all scripts
 func confirmExecution(scripts []Script) bool {
 	fmt.Printf(Red + "⚠️  About to run ALL scripts:" + NC + "\n")
 	for _, script := range scripts {
@@ -522,73 +313,19 @@ func confirmExecution(scripts []Script) bool {
 	return answer == "y" || answer == "yes"
 }
 
-func main() {
-	const programName = "dev"
-
-	// Parse arguments
-	config, err := parseArgs(os.Args[1:])
-	if err != nil {
-		if err.Error() == "help" {
-			showHelp(programName)
-			os.Exit(0)
-		}
-		errorLog("Error: %v", err)
-		fmt.Println()
-		showHelp(programName)
-		os.Exit(1)
-	}
-
-	// Handle special commands
-	switch config.Command {
-	case "push":
-		if err := handlePush(); err != nil {
-			os.Exit(1)
-		}
-		os.Exit(0)
-	case "deps":
-		if err := checkDependencies(); err != nil {
-			os.Exit(1)
-		}
-		os.Exit(0)
-	case "list":
-		if err := listScripts(config.RunsDir); err != nil {
-			errorLog("Error listing scripts: %v", err)
-			os.Exit(1)
-		}
-		os.Exit(0)
-	case "new":
-		if len(config.Filters) == 0 {
-			errorLog("Script name required for 'new' command")
-			fmt.Printf("Usage: %s new <script-name>\n", programName)
-			os.Exit(1)
-		}
-		scriptName := config.Filters[0]
-		if err := createNewScript(config.RunsDir, scriptName); err != nil {
-			errorLog("Error creating script: %v", err)
-			os.Exit(1)
-		}
-		os.Exit(0)
-	case "run":
-		// Continue with script execution logic below
-	case "":
-		// No command provided, show help
-		showHelp(programName)
-		os.Exit(0)
-	default:
-		errorLog("Unknown command: %s", config.Command)
-		showHelp(programName)
-		os.Exit(1)
-	}
+func initConfig() {
+	wd, _ := os.Getwd()
+	config.RunsDir = filepath.Join(wd, "runs")
+	config.LogsDir = filepath.Join(wd, "logs")
+}
 
-	// Initialize runs directory if it doesn't exist (only for run command)
+func ensureRunsDir() error {
 	if _, err := os.Stat(config.RunsDir); os.IsNotExist(err) {
 		log("Creating runs directory at: %s", config.RunsDir)
 		if err := os.MkdirAll(config.RunsDir, 0o755); err != nil {
-			errorLog("Failed to create runs directory: %v", err)
-			os.Exit(1)
+			return err
 		}
 
-		// Create example script
 		exampleScript := filepath.Join(config.RunsDir, "example.sh")
 		content := `#!/usr/bin/env bash
 # NAME: Example script
@@ -596,82 +333,190 @@ echo "Hello from runs/example.sh!"
 echo "Edit this script or add your own to runs/"
 `
 		if err := os.WriteFile(exampleScript, []byte(content), 0o755); err != nil {
-			errorLog("Failed to create example script: %v", err)
-			os.Exit(1)
+			return err
 		}
 
 		log("✅ Created runs/ directory with example script")
-		log("Use '%s ls' to list scripts or '%s new <name>' to create a new one", programName, programName)
-		os.Exit(0)
+		log("Use 'dev ls' to list scripts or 'dev new <name>' to create a new one")
+		return nil
 	}
+	return nil
+}
 
-	// Create logs directory
-	if err := os.MkdirAll(config.LogsDir, 0o755); err != nil {
-		errorLog("Failed to create logs directory: %v", err)
-		os.Exit(1)
-	}
+var rootCmd = &cobra.Command{
+	Use:   "dev",
+	Short: "Development script runner",
+	Long:  "A simple tool to manage and run development scripts with security scanning",
+}
 
-	// Find scripts to run
-	scripts, err := findScripts(config.RunsDir, config.Filters)
-	if err != nil {
-		errorLog("Error finding scripts: %v", err)
-		os.Exit(1)
-	}
+var runCmd = &cobra.Command{
+	Use:   "run [filters...]",
+	Short: "Run scripts matching filters (or all if no filters)",
+	RunE: func(cmd *cobra.Command, args []string) error {
+		if err := ensureRunsDir(); err != nil {
+			return err
+		}
 
-	if len(scripts) == 0 {
-		if len(config.Filters) > 0 {
-			warn("No scripts match the given filters: %s", strings.Join(config.Filters, ", "))
-			fmt.Printf("Use '%s ls' to see all available scripts\n", programName)
-		} else {
+		if err := os.MkdirAll(config.LogsDir, 0o755); err != nil {
+			return err
+		}
+
+		scripts, err := findScripts(args)
+		if err != nil {
+			return err
+		}
+
+		if len(scripts) == 0 {
+			if len(args) > 0 {
+				warn("No scripts match the given filters: %s", strings.Join(args, ", "))
+				fmt.Println("Use 'dev ls' to see all available scripts")
+			} else {
+				warn("No executable scripts found in %s", config.RunsDir)
+				fmt.Println("Use 'dev new <name>' to create a new script")
+			}
+			return nil
+		}
+
+		if len(args) == 0 && !config.DryRun {
+			if !confirmExecution(scripts) {
+				fmt.Println("Cancelled")
+				return nil
+			}
+		}
+
+		var executed, failed []string
+
+		for _, script := range scripts {
+			if config.DryRun {
+				fmt.Printf(Blue+"[DRY]"+NC+" Would run: %s - %s\n", script.Name, script.Description)
+				continue
+			}
+
+			fmt.Printf("\n"+Blue+"▶"+NC+" Running: %s\n", script.Name)
+			if script.Description != "No description" {
+				fmt.Printf("  %s\n", script.Description)
+			}
+
+			if err := executeScript(script, config.Verbose); err != nil {
+				errorLog("❌ %s failed (see %s)", script.Name,
+					filepath.Join(config.LogsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log"))
+				failed = append(failed, script.Name)
+			} else {
+				log("✅ %s completed", script.Name)
+				executed = append(executed, script.Name)
+			}
+		}
+
+		if !config.DryRun {
+			fmt.Printf("\n" + Blue + "═══ SUMMARY ═══" + NC + "\n")
+			fmt.Printf("✅ Completed: %d\n", len(executed))
+			if len(failed) > 0 {
+				fmt.Printf("❌ Failed: %d\n", len(failed))
+				fmt.Printf("\n" + Red + "Failed scripts:" + NC + "\n")
+				for _, f := range failed {
+					fmt.Printf("  • %s\n", f)
+				}
+				return fmt.Errorf("some scripts failed")
+			}
+		}
+		return nil
+	},
+}
+
+var pushCmd = &cobra.Command{
+	Use:     "push",
+	Aliases: []string{"u", "ush"},
+	Short:   "Commit, scan for secrets, and push to git origin",
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return handlePush()
+	},
+}
+
+var listCmd = &cobra.Command{
+	Use:     "ls",
+	Aliases: []string{"list"},
+	Short:   "List all available scripts",
+	RunE: func(cmd *cobra.Command, args []string) error {
+		scripts, err := findScripts(nil)
+		if err != nil {
+			return err
+		}
+
+		if len(scripts) == 0 {
 			warn("No executable scripts found in %s", config.RunsDir)
-			fmt.Printf("Use '%s new <name>' to create a new script\n", programName)
+			return nil
 		}
-		os.Exit(0)
-	}
 
-	// Confirm execution if running all scripts without dry run
-	if len(config.Filters) == 0 && !config.DryRun {
-		if !confirmExecution(scripts) {
-			fmt.Println("Cancelled")
-			os.Exit(0)
+		fmt.Printf(Blue + "Available scripts:" + NC + "\n")
+		for _, script := range scripts {
+			fmt.Printf("  %s%s%s - %s\n", Green, script.Name, NC, script.Description)
 		}
-	}
+		fmt.Printf("\nTotal: %d scripts\n", len(scripts))
+		return nil
+	},
+}
 
-	// Execute scripts
-	result := ExecutionResult{}
+var newCmd = &cobra.Command{
+	Use:   "new <name>",
+	Short: "Create a new script template",
+	Args:  cobra.ExactArgs(1),
+	RunE: func(cmd *cobra.Command, args []string) error {
+		scriptName := args[0]
 
-	for _, script := range scripts {
-		if config.DryRun {
-			fmt.Printf(Blue+"[DRY]"+NC+" Would run: %s - %s\n", script.Name, script.Description)
-			continue
+		if err := os.MkdirAll(config.RunsDir, 0o755); err != nil {
+			return err
 		}
 
-		fmt.Printf("\n"+Blue+"▶"+NC+" Running: %s\n", script.Name)
-		if script.Description != "No description" {
-			fmt.Printf("  %s\n", script.Description)
+		if !strings.HasSuffix(scriptName, ".sh") {
+			scriptName += ".sh"
 		}
 
-		if err := executeScript(script, config.LogsDir, config.Verbose); err != nil {
-			errorLog("❌ %s failed (see %s)", script.Name,
-				filepath.Join(config.LogsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log"))
-			result.Failed = append(result.Failed, script.Name)
-		} else {
-			log("✅ %s completed", script.Name)
-			result.Executed = append(result.Executed, script.Name)
+		scriptPath := filepath.Join(config.RunsDir, scriptName)
+
+		if _, err := os.Stat(scriptPath); err == nil {
+			return fmt.Errorf("script %s already exists", scriptName)
 		}
-	}
 
-	// Print summary
-	if !config.DryRun {
-		fmt.Printf("\n" + Blue + "═══ SUMMARY ═══" + NC + "\n")
-		fmt.Printf("✅ Completed: %d\n", len(result.Executed))
-		if len(result.Failed) > 0 {
-			fmt.Printf("❌ Failed: %d\n", len(result.Failed))
-			fmt.Printf("\n" + Red + "Failed scripts:" + NC + "\n")
-			for _, failed := range result.Failed {
-				fmt.Printf("  • %s\n", failed)
-			}
-			os.Exit(1)
+		template := fmt.Sprintf(`#!/usr/bin/env bash
+# NAME: %s script
+
+set -euo pipefail
+
+echo "Running %s script..."
+
+# Add your commands here
+
+echo "✅ %s completed successfully"
+`, strings.TrimSuffix(scriptName, ".sh"), scriptName, strings.TrimSuffix(scriptName, ".sh"))
+
+		if err := os.WriteFile(scriptPath, []byte(template), 0o755); err != nil {
+			return err
 		}
+
+		log("✅ Created new script: %s", scriptPath)
+		log("Edit the script to add your commands")
+		return nil
+	},
+}
+
+var depsCmd = &cobra.Command{
+	Use:     "deps",
+	Aliases: []string{"check"},
+	Short:   "Check for required dependencies",
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return checkDependencies()
+	},
+}
+
+func main() {
+	initConfig()
+
+	runCmd.Flags().BoolVar(&config.DryRun, "dry", false, "Show what would run without executing")
+	runCmd.Flags().BoolVarP(&config.Verbose, "verbose", "v", false, "Show detailed output during execution")
+
+	rootCmd.AddCommand(runCmd, pushCmd, listCmd, newCmd, depsCmd)
+
+	if err := rootCmd.Execute(); err != nil {
+		os.Exit(1)
 	}
 }