Просмотр исходного кода

dev: automated commit - 2025-07-15 23:20:30

Mariano Z. 7 месяцев назад
Родитель
Сommit
71cda52613
8 измененных файлов с 770 добавлено и 696 удалено
  1. 259 0
      commands.go
  2. 36 2
      dev
  3. 98 0
      git.go
  4. 22 0
      logging.go
  5. 7 694
      main.go
  6. 243 0
      scripts.go
  7. 28 0
      types.go
  8. 77 0
      utils.go

+ 259 - 0
commands.go

@@ -0,0 +1,259 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"path/filepath"
+	"strings"
+
+	"github.com/spf13/cobra"
+)
+
+var rootCmd = &cobra.Command{
+	Use:   "dev",
+	Short: "Development script runner",
+	Long:  "A CLI tool for managing and running development scripts",
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return cmd.Help()
+	},
+}
+
+var runCmd = &cobra.Command{
+	Use:   "run [script-filters...] [-- script-args...]",
+	Short: "Run scripts matching filters (or all if no filters)",
+	Long: `Run scripts matching filters. Use -- to pass arguments to scripts.
+
+Scripts marked with "# REQUIRES: sudo" will automatically run with elevated privileges.
+
+Examples:
+  dev run tools/dev.sh                    # Run specific script
+  dev run tools/dev.sh -- arg1 arg2       # Run with arguments
+  dev run --verbose install -- --force    # Run with flags
+`,
+	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
+		if err := ensureRunsDir(); err != nil {
+			return nil, cobra.ShellCompDirectiveNoFileComp
+		}
+
+		scripts, err := findScripts(nil)
+		if err != nil {
+			return nil, cobra.ShellCompDirectiveNoFileComp
+		}
+
+		var completions []string
+		for _, script := range scripts {
+			desc := script.Desc
+			if script.RequiresSudo {
+				desc = desc + " [SUDO]"
+			}
+			completions = append(completions, fmt.Sprintf("%s\t%s", script.RelPath, desc))
+		}
+		return completions, cobra.ShellCompDirectiveNoFileComp
+	},
+	RunE: func(cmd *cobra.Command, args []string) error {
+		// Parse arguments manually to handle -- separator
+		var filters []string
+		var scriptArgs []string
+
+		// Find "run" command position and -- separator in raw args
+		cmdPos := -1
+		separatorPos := -1
+		rawArgs := os.Args
+
+		for i, arg := range rawArgs {
+			if arg == "run" {
+				cmdPos = i
+				break
+			}
+		}
+
+		if cmdPos >= 0 {
+			for i := cmdPos + 1; i < len(rawArgs); i++ {
+				if rawArgs[i] == "--" {
+					separatorPos = i
+					break
+				}
+			}
+		}
+
+		if separatorPos >= 0 {
+			// Get everything between 'run' and '--' as filters
+			filters = rawArgs[cmdPos+1 : separatorPos]
+			// Remove any flags from filters
+			for i := 0; i < len(filters); i++ {
+				if strings.HasPrefix(filters[i], "-") {
+					filters = append(filters[:i], filters[i+1:]...)
+					i--
+				}
+			}
+			// Get everything after '--' as script args
+			scriptArgs = rawArgs[separatorPos+1:]
+		} else {
+			// No '--' separator found, consider all non-flag args as filters
+			for _, arg := range args {
+				if !strings.HasPrefix(arg, "-") {
+					filters = append(filters, arg)
+				}
+			}
+		}
+
+		if err := ensureRunsDir(); err != nil {
+			return err
+		}
+
+		scripts, err := findScripts(filters)
+		if err != nil {
+			return err
+		}
+
+		if len(scripts) == 0 {
+			if len(filters) > 0 {
+				warn("No scripts match filters: %s", strings.Join(filters, ", "))
+			} else {
+				warn("No scripts found. Use 'dev new <name>' to create one")
+			}
+			return nil
+		}
+
+		for _, script := range scripts {
+			if config.DryRun {
+				argsStr := ""
+				if len(scriptArgs) > 0 {
+					argsStr = fmt.Sprintf(" with args: %s", strings.Join(scriptArgs, " "))
+				}
+				sudoStr := ""
+				if script.RequiresSudo {
+					sudoStr = " (with sudo)"
+				}
+				log("Would run: %s%s%s", script.RelPath, argsStr, sudoStr)
+			} else {
+				if err := executeScript(script, scriptArgs, config.Verbose); err != nil {
+					errorLog("❌ %s failed", script.RelPath)
+					if !config.Verbose {
+						fmt.Printf("   Check log: %s\n", filepath.Join(config.LogsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log"))
+					}
+				} else {
+					if script.RequiresSudo {
+						sudoLog("✅ %s completed (elevated)", script.RelPath)
+					} else {
+						log("✅ %s completed", script.RelPath)
+					}
+				}
+			}
+		}
+		return nil
+	},
+}
+
+var listCmd = &cobra.Command{
+	Use:     "ls",
+	Aliases: []string{"list"},
+	Short:   "List all available scripts",
+	RunE: func(cmd *cobra.Command, args []string) error {
+		if err := ensureRunsDir(); err != nil {
+			return err
+		}
+
+		scripts, err := findScripts(nil)
+		if err != nil {
+			return err
+		}
+
+		if len(scripts) == 0 {
+			warn("No scripts found in %s", config.RunsDir)
+			fmt.Println("Use 'dev new <n>' to create a new script")
+			return nil
+		}
+
+		fmt.Printf("Available scripts in %s:\n\n", config.RunsDir)
+		for _, script := range scripts {
+			if script.RequiresSudo {
+				fmt.Printf("  %s%s%s - %s %s[SUDO]%s\n",
+					Blue, script.RelPath, NC, script.Desc, Purple, NC)
+			} else {
+				fmt.Printf("  %s%s%s - %s\n",
+					Blue, script.RelPath, NC, script.Desc)
+			}
+		}
+
+		sudoCount := 0
+		for _, script := range scripts {
+			if script.RequiresSudo {
+				sudoCount++
+			}
+		}
+
+		fmt.Printf("\nTotal: %d scripts", len(scripts))
+		if sudoCount > 0 {
+			fmt.Printf(" (%d require elevated privileges)", sudoCount)
+		}
+		fmt.Println()
+		return nil
+	},
+}
+
+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 {
+		return createNewScript(args[0])
+	},
+}
+
+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 depsCmd = &cobra.Command{
+	Use:   "deps",
+	Short: "Install dependencies",
+	RunE: func(cmd *cobra.Command, args []string) error {
+		return checkDependencies()
+	},
+}
+
+var completionCmd = &cobra.Command{
+	Use:   "completion [bash|zsh|fish]",
+	Short: "Generate completion script",
+	Long: `To load completions:
+
+Bash:
+  $ source <(dev completion bash)
+
+  # To load completions for each session, execute once:
+  # Linux:
+  $ dev completion bash > /etc/bash_completion.d/dev
+  # macOS:
+  $ dev completion bash > $(brew --prefix)/etc/bash_completion.d/dev
+
+Zsh:
+  # If shell completion is not already enabled in your environment,
+  # you will need to enable it.  You can execute the following once:
+  $ echo "autoload -U compinit; compinit" >> ~/.zshrc
+
+  # To load completions for each session, execute once:
+  $ dev completion zsh > "${fpath[1]}/_dev"
+
+  # You will need to start a new shell for this setup to take effect.
+`,
+	DisableFlagsInUseLine: true,
+	ValidArgs:             []string{"bash", "zsh", "fish"},
+	Args:                  cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
+	RunE: func(cmd *cobra.Command, args []string) error {
+		switch args[0] {
+		case "bash":
+			return rootCmd.GenBashCompletion(os.Stdout)
+		case "zsh":
+			return rootCmd.GenZshCompletion(os.Stdout)
+		case "fish":
+			return rootCmd.GenFishCompletion(os.Stdout, true)
+		}
+		return nil
+	},
+}

+ 36 - 2
dev

@@ -28,20 +28,54 @@ BINARY="bin/dev-linux-$ARCH"
 
 # Build if binary doesn't exist or source is newer
 if [[ ! -f "$BINARY" ]] || [[ "main.go" -nt "$BINARY" ]]; then
+    # Check Go version
     if ! command -v go &>/dev/null; then
         error "Go not installed. Install with: sudo pacman -S go"
         exit 1
     fi
+
+    GO_VERSION=$(go version | cut -d' ' -f3)
+    if [[ "$GO_VERSION" < "go1.18" ]]; then
+        error "Go version 1.18 or higher required. Current version: $GO_VERSION"
+        exit 1
+    fi
+
+    # Check for required dependencies
+    if ! command -v git &>/dev/null; then
+        error "Git not installed. Install with: sudo pacman -S git"
+        exit 1
+    fi
+
+    # Create build cache directory
+    mkdir -p .build-cache
+
     log "Building dev tool..."
     mkdir -p bin
+
+    # Initialize Go modules if needed
+    if [[ ! -f "go.mod" ]]; then
+        log "Initializing Go modules..."
+        go mod init dev
+    fi
+
     go mod tidy
-    if go build -ldflags="-s -w" -o "$BINARY" main.go; then
+
+    # Build with cache
+    if go build -ldflags="-s -w" -o "$BINARY" -gcflags="all=-trimpath=$PWD" -asmflags="all=-trimpath=$PWD"; then
         log "✅ Built: $BINARY"
+        find bin -type f -name "dev-linux-*" -not -name "$(basename "$BINARY")" -delete
     else
         error "Build failed"
         exit 1
     fi
 fi
 
-# Run the binary
+if [[ ${1:-} == "clean" ]]; then
+    log "Cleaning up build artifacts..."
+    rm -rf .build-cache
+    rm -rf bin/*.old
+    exit 0
+fi
+
+# Run the binary with all arguments
 exec "$BINARY" "$@"

+ 98 - 0
git.go

@@ -0,0 +1,98 @@
+package main
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+	"os/exec"
+	"strings"
+	"time"
+)
+
+func isGitRepo() bool {
+	cmd := exec.Command("git", "rev-parse", "--git-dir")
+	return cmd.Run() == nil
+}
+
+func hasUncommittedChanges() bool {
+	cmd := exec.Command("git", "diff-index", "--quiet", "HEAD", "--")
+	return cmd.Run() != nil
+}
+
+func getCurrentBranch() (string, error) {
+	cmd := exec.Command("git", "branch", "--show-current")
+	output, err := cmd.Output()
+	if err != nil {
+		return "", err
+	}
+	return strings.TrimSpace(string(output)), nil
+}
+
+func handlePush() error {
+	log("Preparing to push repository...")
+
+	if !isGitRepo() {
+		errorLog("Not in a git repository")
+		return fmt.Errorf("not in git repo")
+	}
+
+	if hasUncommittedChanges() {
+		warn("You have uncommitted changes")
+		fmt.Println()
+
+		cmd := exec.Command("git", "status", "--short")
+		cmd.Stdout = os.Stdout
+		cmd.Run()
+		fmt.Println()
+
+		defaultMsg := fmt.Sprintf("dev: automated commit - %s", time.Now().Format("2006-01-02 15:04:05"))
+
+		fmt.Print("Commit all changes? (Y/n): ")
+		reader := bufio.NewReader(os.Stdin)
+		answer, _ := reader.ReadString('\n')
+		answer = strings.TrimSpace(strings.ToLower(answer))
+
+		if answer != "n" && answer != "no" {
+			fmt.Println()
+			fmt.Printf("Default: %s\n", defaultMsg)
+			fmt.Print("Custom commit message (or press Enter for default): ")
+
+			commitMsg, _ := reader.ReadString('\n')
+			commitMsg = strings.TrimSpace(commitMsg)
+
+			if commitMsg == "" {
+				commitMsg = defaultMsg
+			}
+
+			if err := exec.Command("git", "add", ".").Run(); err != nil {
+				return fmt.Errorf("failed to add changes: %v", err)
+			}
+
+			if err := exec.Command("git", "commit", "-m", commitMsg).Run(); err != nil {
+				return fmt.Errorf("failed to commit changes: %v", err)
+			}
+
+			log("✓ Changes committed: %s", commitMsg)
+		}
+	}
+
+	if err := runSecurityScan(); err != nil {
+		return err
+	}
+
+	branch, err := getCurrentBranch()
+	if err != nil {
+		return fmt.Errorf("failed to get current branch: %v", err)
+	}
+
+	log("Pushing branch '%s' to origin...", branch)
+	cmd := exec.Command("git", "push", "origin", branch)
+
+	if err := cmd.Run(); err != nil {
+		errorLog("❌ Push failed")
+		return fmt.Errorf("push failed")
+	}
+
+	log("✅ Successfully pushed to origin/%s", branch)
+	return nil
+}

+ 22 - 0
logging.go

@@ -0,0 +1,22 @@
+package main
+
+import (
+	"fmt"
+	"os"
+)
+
+func log(msg string, args ...any) {
+	fmt.Printf(Green+"[RUN]"+NC+" "+msg+"\n", args...)
+}
+
+func warn(msg string, args ...any) {
+	fmt.Printf(Yellow+"[WARN]"+NC+" "+msg+"\n", args...)
+}
+
+func errorLog(msg string, args ...any) {
+	fmt.Fprintf(os.Stderr, Red+"[ERROR]"+NC+" "+msg+"\n", args...)
+}
+
+func sudoLog(msg string, args ...any) {
+	fmt.Printf(Purple+"[SUDO]"+NC+" "+msg+"\n", args...)
+}

+ 7 - 694
main.go

@@ -1,706 +1,19 @@
 package main
 
 import (
-	"bufio"
-	"fmt"
-	"io"
 	"os"
-	"os/exec"
-	"path/filepath"
-	"sort"
-	"strings"
-	"time"
-
-	"github.com/spf13/cobra"
-)
-
-const (
-	Red    = "\033[0;31m"
-	Green  = "\033[0;32m"
-	Blue   = "\033[0;34m"
-	Yellow = "\033[0;33m"
-	Purple = "\033[0;35m" // For elevated scripts
-	NC     = "\033[0m"
 )
 
-type Config struct {
-	RunsDir     string
-	LogsDir     string
-	DryRun      bool
-	Verbose     bool
-	Interactive bool
-}
-
-type Script struct {
-	Path         string // Full filesystem path
-	Name         string // Just filename (e.g. "install.sh")
-	RelPath      string // Relative path from runs/ (e.g. "tools/install.sh")
-	Desc         string // Description from script comment
-	RequiresSudo bool   // Whether script needs elevated privileges
-}
-
-var config Config
-
-func log(msg string, args ...any) {
-	fmt.Printf(Green+"[RUN]"+NC+" "+msg+"\n", args...)
-}
-
-func warn(msg string, args ...any) {
-	fmt.Printf(Yellow+"[WARN]"+NC+" "+msg+"\n", args...)
-}
-
-func errorLog(msg string, args ...any) {
-	fmt.Fprintf(os.Stderr, Red+"[ERROR]"+NC+" "+msg+"\n", args...)
-}
-
-func sudoLog(msg string, args ...any) {
-	fmt.Printf(Purple+"[SUDO]"+NC+" "+msg+"\n", args...)
-}
-
-func commandExists(cmd string) bool {
-	_, err := exec.LookPath(cmd)
-	return err == nil
-}
-
-func checkDependencies() error {
-	requiredTools := []string{"git", "find", "grep"}
-	optionalTools := []string{"gitleaks"}
-	var missing []string
-
-	log("Checking dependencies...")
-
-	for _, tool := range requiredTools {
-		if !commandExists(tool) {
-			missing = append(missing, tool)
-		}
-	}
-
-	for _, tool := range optionalTools {
-		if !commandExists(tool) {
-			warn("Optional tool missing: %s (recommended for security scanning)", tool)
-		} else {
-			log("✓ Found: %s", tool)
-		}
-	}
-
-	if len(missing) > 0 {
-		errorLog("Missing required tools: %s", strings.Join(missing, ", "))
-		errorLog("Please install missing dependencies")
-		return fmt.Errorf("missing dependencies")
-	}
-
-	log("✓ All required dependencies found")
-	return nil
-}
-
-func runSecurityScan() error {
-	log("Running security scan...")
-
-	if !commandExists("gitleaks") {
-		warn("GitLeaks not installed - skipping security scan")
-		warn("Install with: paru -S gitleaks")
-		fmt.Println()
-
-		fmt.Print("Continue without security scan? (y/N): ")
-		reader := bufio.NewReader(os.Stdin)
-		answer, _ := reader.ReadString('\n')
-		answer = strings.TrimSpace(strings.ToLower(answer))
-
-		if answer != "y" && answer != "yes" {
-			errorLog("Push cancelled for security")
-			return fmt.Errorf("security scan cancelled")
-		}
-		return nil
-	}
-
-	log("Using GitLeaks for secret detection...")
-	cmd := exec.Command("gitleaks", "detect", "--verbose", "--exit-code", "1")
-
-	if err := cmd.Run(); err != nil {
-		errorLog("❌ Secrets detected! Review before pushing.")
-		return fmt.Errorf("secrets detected")
-	}
-
-	log("✅ No secrets detected")
-	return nil
-}
-
-func isGitRepo() bool {
-	cmd := exec.Command("git", "rev-parse", "--git-dir")
-	return cmd.Run() == nil
-}
-
-func hasUncommittedChanges() bool {
-	cmd := exec.Command("git", "diff-index", "--quiet", "HEAD", "--")
-	return cmd.Run() != nil
-}
-
-func getCurrentBranch() (string, error) {
-	cmd := exec.Command("git", "branch", "--show-current")
-	output, err := cmd.Output()
-	if err != nil {
-		return "", err
-	}
-	return strings.TrimSpace(string(output)), nil
-}
-
-func handlePush() error {
-	log("Preparing to push repository...")
-
-	if !isGitRepo() {
-		errorLog("Not in a git repository")
-		return fmt.Errorf("not in git repo")
-	}
-
-	if hasUncommittedChanges() {
-		warn("You have uncommitted changes")
-		fmt.Println()
-
-		cmd := exec.Command("git", "status", "--short")
-		cmd.Stdout = os.Stdout
-		cmd.Run()
-		fmt.Println()
-
-		defaultMsg := fmt.Sprintf("dev: automated commit - %s", time.Now().Format("2006-01-02 15:04:05"))
-
-		fmt.Print("Commit all changes? (Y/n): ")
-		reader := bufio.NewReader(os.Stdin)
-		answer, _ := reader.ReadString('\n')
-		answer = strings.TrimSpace(strings.ToLower(answer))
-
-		if answer != "n" && answer != "no" {
-			fmt.Println()
-			fmt.Printf("Default: %s\n", defaultMsg)
-			fmt.Print("Custom commit message (or press Enter for default): ")
-
-			commitMsg, _ := reader.ReadString('\n')
-			commitMsg = strings.TrimSpace(commitMsg)
-
-			if commitMsg == "" {
-				commitMsg = defaultMsg
-			}
-
-			if err := exec.Command("git", "add", ".").Run(); err != nil {
-				return fmt.Errorf("failed to add changes: %v", err)
-			}
-
-			if err := exec.Command("git", "commit", "-m", commitMsg).Run(); err != nil {
-				return fmt.Errorf("failed to commit changes: %v", err)
-			}
-
-			log("✓ Changes committed: %s", commitMsg)
-		}
-	}
-
-	if err := runSecurityScan(); err != nil {
-		return err
-	}
-
-	branch, err := getCurrentBranch()
-	if err != nil {
-		return fmt.Errorf("failed to get current branch: %v", err)
-	}
-
-	log("Pushing branch '%s' to origin...", branch)
-	cmd := exec.Command("git", "push", "origin", branch)
-
-	if err := cmd.Run(); err != nil {
-		errorLog("❌ Push failed")
-		return fmt.Errorf("push failed")
-	}
-
-	log("✅ Successfully pushed to origin/%s", branch)
-	return nil
-}
-
-func getScriptMetadata(scriptPath string) (string, bool) {
-	file, err := os.Open(scriptPath)
-	if err != nil {
-		return "No description", false
-	}
-	defer file.Close()
-
-	var desc string
-	var requiresSudo bool
-
-	scanner := bufio.NewScanner(file)
-	for scanner.Scan() {
-		line := strings.TrimSpace(scanner.Text())
-
-		if strings.HasPrefix(line, "# NAME:") {
-			desc = strings.TrimSpace(strings.TrimPrefix(line, "# NAME:"))
-		}
-
-		if strings.HasPrefix(line, "# REQUIRES: sudo") ||
-			strings.HasPrefix(line, "# REQUIRES:sudo") ||
-			strings.HasPrefix(line, "# ELEVATED: true") ||
-			strings.HasPrefix(line, "# ELEVATED:true") ||
-			strings.HasPrefix(line, "# SUDO: true") ||
-			strings.HasPrefix(line, "# SUDO:true") {
-			requiresSudo = true
-		}
-	}
-
-	if desc == "" {
-		desc = "No description"
-	}
-
-	return desc, requiresSudo
-}
-
-func findScripts(filters []string) ([]Script, error) {
-	var scripts []Script
-
-	err := filepath.Walk(config.RunsDir, func(path string, info os.FileInfo, err error) error {
-		if err != nil {
-			return err
-		}
-
-		if info.Mode().IsRegular() && (info.Mode()&0o111) != 0 {
-			// Get relative path from runs directory
-			relPath, err := filepath.Rel(config.RunsDir, path)
-			if err != nil {
-				return err
-			}
-
-			// Skip hidden files and directories
-			if strings.HasPrefix(filepath.Base(path), ".") {
-				return nil
-			}
-
-			scriptName := filepath.Base(path)
-
-			if len(filters) == 0 || matchesFilters(relPath, scriptName, filters) {
-				desc, requiresSudo := getScriptMetadata(path)
-
-				scripts = append(scripts, Script{
-					Path:         path,
-					Name:         scriptName,
-					RelPath:      relPath,
-					Desc:         desc,
-					RequiresSudo: requiresSudo,
-				})
-			}
-		}
-		return nil
-	})
-
-	sort.Slice(scripts, func(i, j int) bool {
-		return scripts[i].RelPath < scripts[j].RelPath
-	})
-
-	return scripts, err
-}
-
-func matchesFilters(relPath, scriptName string, filters []string) bool {
-	for _, filter := range filters {
-		// Normalize paths for comparison (remove .sh extension from filter if present)
-		normalizedFilter := strings.TrimSuffix(filter, ".sh")
-		normalizedRelPath := strings.TrimSuffix(relPath, ".sh")
-		normalizedScriptName := strings.TrimSuffix(scriptName, ".sh")
-
-		// Check exact matches
-		if normalizedRelPath == normalizedFilter || normalizedScriptName == normalizedFilter {
-			return true
-		}
-
-		// Check if filter matches the relative path or script name (case insensitive)
-		filterLower := strings.ToLower(normalizedFilter)
-		relPathLower := strings.ToLower(normalizedRelPath)
-		scriptNameLower := strings.ToLower(normalizedScriptName)
-
-		if strings.Contains(relPathLower, filterLower) || strings.Contains(scriptNameLower, filterLower) {
-			return true
-		}
-
-		// Check directory match (e.g., "tools" matches "tools/install.sh")
-		if strings.HasPrefix(relPathLower, filterLower+"/") {
-			return true
-		}
-	}
-	return false
-}
-
-func executeScript(script Script, args []string, verbose bool) error {
-	logFile := filepath.Join(config.LogsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log")
-
-	var cmd *exec.Cmd
-	if script.RequiresSudo {
-		sudoLog("Script requires elevated privileges: %s", script.RelPath)
-		if !commandExists("sudo") {
-			errorLog("sudo command not found - cannot run elevated script")
-			return fmt.Errorf("sudo not available")
-		}
-		fullArgs := append([]string{script.Path}, args...)
-		cmd = exec.Command("sudo", fullArgs...)
-		sudoLog("Running with sudo: %s", strings.Join(append([]string{script.Path}, args...), " "))
-	} else {
-		cmd = exec.Command(script.Path, args...)
-	}
-
-	if config.Interactive {
-		cmd.Stdin = os.Stdin
-	}
-
-	logFileHandle, err := os.Create(logFile)
-	if err != nil {
-		return err
-	}
-	defer logFileHandle.Close()
-
-	showOutput := verbose || config.Interactive
-	if showOutput {
-		cmd.Stdout = io.MultiWriter(os.Stdout, logFileHandle)
-		cmd.Stderr = io.MultiWriter(os.Stderr, logFileHandle)
-	} else {
-		cmd.Stdout = logFileHandle
-		cmd.Stderr = logFileHandle
-	}
-
-	return cmd.Run()
-}
-
-func createNewScript(scriptName string) error {
-	scriptPath := filepath.Join(config.RunsDir, scriptName)
-
-	dirPath := filepath.Dir(scriptPath)
-
-	if err := os.MkdirAll(dirPath, 0o755); err != nil {
-		return err
-	}
-
-	if !strings.HasSuffix(scriptName, ".sh") {
-		scriptName += ".sh"
-	}
-
-	if _, err := os.Stat(scriptPath); err == nil {
-		return fmt.Errorf("script %s already exists", scriptName)
-	}
-
-	template := fmt.Sprintf(`#!/usr/bin/env bash
-# NAME: %s script
-# REQUIRES: sudo
-
-set -euo pipefail
-
-echo "Running %s script..."
-
-echo "✅ %s completed successfully"
-`, strings.TrimSuffix(scriptName, ".sh"), scriptName, strings.TrimSuffix(scriptName, ".sh"))
-
-	err := os.WriteFile(scriptPath, []byte(template), 0o755)
-	if err != nil {
-		return err
-	}
-
-	log("✅ Created script: %s", scriptPath)
-	return nil
-}
-
 func initConfig() {
-	wd, _ := os.Getwd()
-	config.RunsDir = filepath.Join(wd, "runs")
-	config.LogsDir = filepath.Join(wd, "logs")
-}
-
-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 {
-			return err
-		}
-
-		exampleScript := filepath.Join(config.RunsDir, "example.sh")
-		content := `#!/usr/bin/env bash
-# NAME: Example script
-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 {
-			return err
-		}
-
-		sudoExampleScript := filepath.Join(config.RunsDir, "system-info.sh")
-		sudoContent := `#!/usr/bin/env bash
-# NAME: System information collector
-# REQUIRES: sudo
-
-set -euo pipefail
-
-echo "Collecting system information (requires elevated privileges)..."
-
-echo "=== Disk Usage ==="
-df -h
-
-echo "=== Memory Info ==="
-free -h
-
-echo "=== System Logs (last 10 lines) ==="
-tail -n 10 /var/log/syslog 2>/dev/null || tail -n 10 /var/log/messages 2>/dev/null || echo "No accessible system logs"
-
-echo "✅ System info collection completed"
-`
-		if err := os.WriteFile(sudoExampleScript, []byte(sudoContent), 0o755); err != nil {
-			return err
-		}
-
-		log("✅ Created runs/ directory with example scripts")
-		return nil
+	config = Config{
+		DryRun:      false,
+		Verbose:     false,
+		Interactive: false,
+		LogsDir:     "./logs",
+		RunsDir:     "./runs",
 	}
-	return nil
-}
-
-var rootCmd = &cobra.Command{
-	Use:   "dev",
-	Short: "Development script runner",
-	Long:  "A CLI tool for managing and running development scripts",
-	RunE: func(cmd *cobra.Command, args []string) error {
-		// Default behavior: list scripts if no subcommand
-		return listCmd.RunE(cmd, args)
-	},
-}
-
-var runCmd = &cobra.Command{
-	Use:   "run [filters...] [-- script-args...]",
-	Short: "Run scripts matching filters (or all if no filters)",
-	Long: `Run scripts matching filters. Use -- to pass arguments to scripts.
-
-Scripts marked with "# REQUIRES: sudo" will automatically run with elevated privileges.
-
-Examples:
-  dev run tools/dev.sh                    # Run specific script
-  dev run tools/dev.sh -- arg1 arg2       # Run with arguments
-  dev run --verbose install -- --force    # Run with flags
-  dev run system-info                     # Runs with sudo if marked as required`,
-	ValidArgsFunction: func(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
-		scripts, err := findScripts(nil)
-		if err != nil {
-			return nil, cobra.ShellCompDirectiveNoFileComp
-		}
-
-		var completions []string
-		for _, script := range scripts {
-			if strings.HasPrefix(script.Name, toComplete) || strings.HasPrefix(script.RelPath, toComplete) {
-				desc := script.Desc
-				if script.RequiresSudo {
-					desc = desc + " [SUDO]"
-				}
-				completions = append(completions, fmt.Sprintf("%s\t%s", script.RelPath, desc))
-			}
-		}
-		return completions, cobra.ShellCompDirectiveNoFileComp
-	},
-	RunE: func(cmd *cobra.Command, args []string) error {
-		// Parse arguments manually to handle -- separator
-		var filters []string
-		var scriptArgs []string
-
-		// Find "run" command position and -- separator in raw args
-		rawArgs := os.Args[1:] // Skip program name
-		runIndex := -1
-		dashDashIndex := -1
-
-		for i, arg := range rawArgs {
-			if arg == "run" {
-				runIndex = i
-			}
-			if arg == "--" && runIndex >= 0 {
-				dashDashIndex = i
-				break
-			}
-		}
-
-		if dashDashIndex >= 0 {
-			for i := runIndex + 1; i < dashDashIndex; i++ {
-				arg := rawArgs[i]
-				if !strings.HasPrefix(arg, "-") {
-					filters = append(filters, arg)
-				}
-			}
-			scriptArgs = rawArgs[dashDashIndex+1:]
-		} else {
-			filters = args
-			scriptArgs = []string{}
-		}
-
-		if err := ensureRunsDir(); err != nil {
-			return err
-		}
-
-		if err := os.MkdirAll(config.LogsDir, 0o755); err != nil {
-			return err
-		}
-
-		scripts, err := findScripts(filters)
-		if err != nil {
-			return err
-		}
-
-		if len(scripts) == 0 {
-			if len(filters) > 0 {
-				warn("No scripts match filters: %s", strings.Join(filters, ", "))
-			} else {
-				warn("No scripts found. Use 'dev new <name>' to create one")
-			}
-			return nil
-		}
-
-		for _, script := range scripts {
-			if config.DryRun {
-				argsStr := ""
-				if len(scriptArgs) > 0 {
-					argsStr = fmt.Sprintf(" with args: %s", strings.Join(scriptArgs, " "))
-				}
-				sudoStr := ""
-				if script.RequiresSudo {
-					sudoStr = " [SUDO]"
-				}
-				fmt.Printf("[DRY] Would run: %s - %s%s%s\n", script.RelPath, script.Desc, sudoStr, argsStr)
-				continue
-			}
-
-			argsDisplay := ""
-			if len(scriptArgs) > 0 {
-				argsDisplay = fmt.Sprintf(" with args: %s", strings.Join(scriptArgs, " "))
-			}
-
-			sudoDisplay := ""
-			if script.RequiresSudo {
-				sudoDisplay = " [ELEVATED]"
-			}
-
-			fmt.Printf("Running: %s%s%s\n", script.RelPath, sudoDisplay, argsDisplay)
-
-			if err := executeScript(script, scriptArgs, config.Verbose); err != nil {
-				errorLog("❌ %s failed", script.RelPath)
-				if !config.Verbose {
-					fmt.Printf("   Check log: %s\n", filepath.Join(config.LogsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log"))
-				}
-			} else {
-				if script.RequiresSudo {
-					sudoLog("✅ %s completed (elevated)", script.RelPath)
-				} else {
-					log("✅ %s completed", script.RelPath)
-				}
-			}
-		}
-		return nil
-	},
-}
-
-var listCmd = &cobra.Command{
-	Use:     "ls",
-	Aliases: []string{"list"},
-	Short:   "List all available scripts",
-	RunE: func(cmd *cobra.Command, args []string) error {
-		if err := ensureRunsDir(); err != nil {
-			return err
-		}
-
-		scripts, err := findScripts(nil)
-		if err != nil {
-			return err
-		}
-
-		if len(scripts) == 0 {
-			warn("No scripts found in %s", config.RunsDir)
-			fmt.Println("Use 'dev new <name>' to create a new script")
-			return nil
-		}
-
-		fmt.Printf("Available scripts in %s:\n\n", config.RunsDir)
-		for _, script := range scripts {
-			if script.RequiresSudo {
-				fmt.Printf("  %s%s%s - %s %s[SUDO]%s\n",
-					Blue, script.RelPath, NC, script.Desc, Purple, NC)
-			} else {
-				fmt.Printf("  %s%s%s - %s\n",
-					Blue, script.RelPath, NC, script.Desc)
-			}
-		}
-
-		sudoCount := 0
-		for _, script := range scripts {
-			if script.RequiresSudo {
-				sudoCount++
-			}
-		}
-
-		fmt.Printf("\nTotal: %d scripts", len(scripts))
-		if sudoCount > 0 {
-			fmt.Printf(" (%d require elevated privileges)", sudoCount)
-		}
-		fmt.Println()
-		return nil
-	},
-}
-
-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 {
-		return createNewScript(args[0])
-	},
-}
-
-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 depsCmd = &cobra.Command{
-	Use:   "deps",
-	Short: "Install dependencies",
-	RunE: func(cmd *cobra.Command, args []string) error {
-		return checkDependencies()
-	},
-}
-
-var completionCmd = &cobra.Command{
-	Use:   "completion [bash|zsh|fish|powershell]",
-	Short: "Generate completion script",
-	Long: `To load completions:
-
-Bash:
-  $ source <(dev completion bash)
-
-  # To load completions for each session, execute once:
-  # Linux:
-  $ dev completion bash > /etc/bash_completion.d/dev
-  # macOS:
-  $ dev completion bash > $(brew --prefix)/etc/bash_completion.d/dev
-
-Zsh:
-  # If shell completion is not already enabled in your environment,
-  # you will need to enable it.  You can execute the following once:
-  $ echo "autoload -U compinit; compinit" >> ~/.zshrc
-
-  # To load completions for each session, execute once:
-  $ dev completion zsh > "${fpath[1]}/_dev"
 
-  # You will need to start a new shell for this setup to take effect.
-`,
-	DisableFlagsInUseLine: true,
-	ValidArgs:             []string{"bash", "zsh", "fish", "powershell"},
-	Args:                  cobra.MatchAll(cobra.ExactArgs(1), cobra.OnlyValidArgs),
-	RunE: func(cmd *cobra.Command, args []string) error {
-		switch args[0] {
-		case "bash":
-			return rootCmd.GenBashCompletion(os.Stdout)
-		case "zsh":
-			return rootCmd.GenZshCompletion(os.Stdout)
-		case "fish":
-			return rootCmd.GenFishCompletion(os.Stdout, true)
-		case "powershell":
-			return rootCmd.GenPowerShellCompletionWithDesc(os.Stdout)
-		}
-		return nil
-	},
+	os.MkdirAll("logs", 0o755)
 }
 
 func main() {

+ 243 - 0
scripts.go

@@ -0,0 +1,243 @@
+package main
+
+import (
+	"bufio"
+	"fmt"
+	"io"
+	"os"
+	"os/exec"
+	"path/filepath"
+	"sort"
+	"strings"
+)
+
+func getScriptMetadata(scriptPath string) (string, bool) {
+	file, err := os.Open(scriptPath)
+	if err != nil {
+		return "No description", false
+	}
+	defer file.Close()
+
+	var desc string
+	var requiresSudo bool
+
+	scanner := bufio.NewScanner(file)
+	for scanner.Scan() {
+		line := strings.TrimSpace(scanner.Text())
+
+		if strings.HasPrefix(line, "# NAME:") {
+			desc = strings.TrimSpace(strings.TrimPrefix(line, "# NAME:"))
+		}
+
+		if strings.HasPrefix(line, "# REQUIRES: sudo") ||
+			strings.HasPrefix(line, "# REQUIRES:sudo") ||
+			strings.HasPrefix(line, "# ELEVATED: true") ||
+			strings.HasPrefix(line, "# ELEVATED:true") ||
+			strings.HasPrefix(line, "# SUDO: true") ||
+			strings.HasPrefix(line, "# SUDO:true") {
+			requiresSudo = true
+		}
+	}
+
+	if desc == "" {
+		desc = "No description"
+	}
+
+	return desc, requiresSudo
+}
+
+func findScripts(filters []string) ([]Script, error) {
+	var scripts []Script
+
+	err := filepath.Walk(config.RunsDir, func(path string, info os.FileInfo, err error) error {
+		if err != nil {
+			return err
+		}
+
+		if info.Mode().IsRegular() && (info.Mode()&0o111) != 0 {
+			// Get relative path from runs directory
+			relPath, err := filepath.Rel(config.RunsDir, path)
+			if err != nil {
+				return err
+			}
+
+			// Skip hidden files and directories
+			if strings.HasPrefix(filepath.Base(path), ".") {
+				return nil
+			}
+
+			scriptName := filepath.Base(path)
+
+			if len(filters) == 0 || matchesFilters(relPath, scriptName, filters) {
+				desc, requiresSudo := getScriptMetadata(path)
+
+				scripts = append(scripts, Script{
+					Path:         path,
+					Name:         scriptName,
+					RelPath:      relPath,
+					Desc:         desc,
+					RequiresSudo: requiresSudo,
+				})
+			}
+		}
+		return nil
+	})
+
+	sort.Slice(scripts, func(i, j int) bool {
+		return scripts[i].RelPath < scripts[j].RelPath
+	})
+
+	return scripts, err
+}
+
+func matchesFilters(relPath, scriptName string, filters []string) bool {
+	for _, filter := range filters {
+		// Normalize paths for comparison (remove .sh extension from filter if present)
+		normalizedFilter := strings.TrimSuffix(filter, ".sh")
+		normalizedRelPath := strings.TrimSuffix(relPath, ".sh")
+		normalizedScriptName := strings.TrimSuffix(scriptName, ".sh")
+
+		// Check exact matches
+		if normalizedRelPath == normalizedFilter || normalizedScriptName == normalizedFilter {
+			return true
+		}
+
+		// Check if filter matches the relative path or script name (case insensitive)
+		filterLower := strings.ToLower(normalizedFilter)
+		relPathLower := strings.ToLower(normalizedRelPath)
+		scriptNameLower := strings.ToLower(normalizedScriptName)
+
+		if strings.Contains(relPathLower, filterLower) || strings.Contains(scriptNameLower, filterLower) {
+			return true
+		}
+
+		// Check directory match (e.g., "tools" matches "tools/install.sh")
+		if strings.HasPrefix(relPathLower, filterLower+"/") {
+			return true
+		}
+	}
+	return false
+}
+
+func executeScript(script Script, args []string, verbose bool) error {
+	logFile := filepath.Join(config.LogsDir, strings.TrimSuffix(script.Name, filepath.Ext(script.Name))+".log")
+
+	var cmd *exec.Cmd
+	if script.RequiresSudo {
+		sudoLog("Script requires elevated privileges: %s", script.RelPath)
+		if !commandExists("sudo") {
+			errorLog("sudo command not found - cannot run elevated script")
+			return fmt.Errorf("sudo not available")
+		}
+		fullArgs := append([]string{script.Path}, args...)
+		cmd = exec.Command("sudo", fullArgs...)
+		sudoLog("Running with sudo: %s", strings.Join(append([]string{script.Path}, args...), " "))
+	} else {
+		cmd = exec.Command(script.Path, args...)
+	}
+
+	if config.Interactive {
+		cmd.Stdin = os.Stdin
+	}
+
+	logFileHandle, err := os.Create(logFile)
+	if err != nil {
+		return err
+	}
+	defer logFileHandle.Close()
+
+	showOutput := verbose || config.Interactive
+	if showOutput {
+		cmd.Stdout = io.MultiWriter(os.Stdout, logFileHandle)
+		cmd.Stderr = io.MultiWriter(os.Stderr, logFileHandle)
+	} else {
+		cmd.Stdout = logFileHandle
+		cmd.Stderr = logFileHandle
+	}
+
+	return cmd.Run()
+}
+
+func createNewScript(scriptName string) error {
+	scriptPath := filepath.Join(config.RunsDir, scriptName)
+
+	dirPath := filepath.Dir(scriptPath)
+
+	if err := os.MkdirAll(dirPath, 0o755); err != nil {
+		return err
+	}
+
+	if !strings.HasSuffix(scriptName, ".sh") {
+		scriptName += ".sh"
+	}
+
+	if _, err := os.Stat(scriptPath); err == nil {
+		return fmt.Errorf("script %s already exists", scriptName)
+	}
+
+	template := fmt.Sprintf(`#!/usr/bin/env bash
+# NAME: %s script
+# REQUIRES: sudo
+
+set -euo pipefail
+
+echo "Running %s script..."
+
+echo "✅ %s completed successfully"
+`, strings.TrimSuffix(scriptName, ".sh"), scriptName, strings.TrimSuffix(scriptName, ".sh"))
+
+	err := os.WriteFile(scriptPath, []byte(template), 0o755)
+	if err != nil {
+		return err
+	}
+
+	log("✅ Created script: %s", scriptPath)
+	return nil
+}
+
+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 {
+			return err
+		}
+
+		exampleScript := filepath.Join(config.RunsDir, "example.sh")
+		content := `#!/usr/bin/env bash
+# NAME: Example script
+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 {
+			return err
+		}
+
+		sudoExampleScript := filepath.Join(config.RunsDir, "system-info.sh")
+		sudoContent := `#!/usr/bin/env bash
+# NAME: System information collector
+# REQUIRES: sudo
+
+set -euo pipefail
+
+echo "Collecting system information (requires elevated privileges)..."
+
+echo "=== Disk Usage ==="
+df -h
+
+echo "=== Memory Info ==="
+free -h
+
+echo "=== System Logs (last 10 lines) ==="
+tail -n 10 /var/log/syslog 2>/dev/null || tail -n 10 /var/log/messages 2>/dev/null || echo "No accessible system logs"
+
+echo "✅ System info collection completed"
+`
+		if err := os.WriteFile(sudoExampleScript, []byte(sudoContent), 0o755); err != nil {
+			return err
+		}
+
+		log("✅ Created runs/ directory with example scripts")
+		return nil
+	}
+	return nil
+}

+ 28 - 0
types.go

@@ -0,0 +1,28 @@
+package main
+
+const (
+	Red    = "\033[0;31m"
+	Green  = "\033[0;32m"
+	Blue   = "\033[0;34m"
+	Yellow = "\033[0;33m"
+	Purple = "\033[0;35m" // For elevated scripts
+	NC     = "\033[0m"
+)
+
+type Config struct {
+	LogsDir     string
+	RunsDir     string
+	DryRun      bool
+	Verbose     bool
+	Interactive bool
+}
+
+type Script struct {
+	Path         string // Full filesystem path
+	Name         string // Just filename (e.g. "install.sh")
+	RelPath      string // Relative path from runs/ (e.g. "tools/install.sh")
+	Desc         string // Description from script comment
+	RequiresSudo bool   // Whether script needs elevated privileges
+}
+
+var config Config

+ 77 - 0
utils.go

@@ -0,0 +1,77 @@
+package main
+
+import (
+	"bufio"
+	"fmt"
+	"os"
+	"os/exec"
+	"strings"
+)
+
+func commandExists(cmd string) bool {
+	_, err := exec.LookPath(cmd)
+	return err == nil
+}
+
+func checkDependencies() error {
+	requiredTools := []string{"git", "find", "grep"}
+	optionalTools := []string{"gitleaks"}
+	var missing []string
+
+	log("Checking dependencies...")
+
+	for _, tool := range requiredTools {
+		if !commandExists(tool) {
+			missing = append(missing, tool)
+		}
+	}
+
+	for _, tool := range optionalTools {
+		if !commandExists(tool) {
+			warn("Optional tool missing: %s (recommended for security scanning)", tool)
+		} else {
+			log("✓ Found: %s", tool)
+		}
+	}
+
+	if len(missing) > 0 {
+		errorLog("Missing required tools: %s", strings.Join(missing, ", "))
+		errorLog("Please install missing dependencies")
+		return fmt.Errorf("missing dependencies")
+	}
+
+	log("✓ All required dependencies found")
+	return nil
+}
+
+func runSecurityScan() error {
+	log("Running security scan...")
+
+	if !commandExists("gitleaks") {
+		warn("GitLeaks not installed - skipping security scan")
+		warn("Install with: paru -S gitleaks")
+		fmt.Println()
+
+		fmt.Print("Continue without security scan? (y/N): ")
+		reader := bufio.NewReader(os.Stdin)
+		answer, _ := reader.ReadString('\n')
+		answer = strings.TrimSpace(strings.ToLower(answer))
+
+		if answer != "y" && answer != "yes" {
+			errorLog("Push cancelled for security")
+			return fmt.Errorf("security scan cancelled")
+		}
+		return nil
+	}
+
+	log("Using GitLeaks for secret detection...")
+	cmd := exec.Command("gitleaks", "detect", "--verbose", "--exit-code", "1")
+
+	if err := cmd.Run(); err != nil {
+		errorLog("❌ Secrets detected! Review before pushing.")
+		return fmt.Errorf("secrets detected")
+	}
+
+	log("✅ No secrets detected")
+	return nil
+}