package main import ( "context" "fmt" "os" "path/filepath" "strings" "github.com/fatih/color" "github.com/spf13/cobra" "github.com/steveyegge/beads/internal/storage/sqlite" ) var initCmd = &cobra.Command{ Use: "init", Short: "Initialize bd in the current directory", Long: `Initialize bd in the current directory by creating a .beads/ directory and database file. Optionally specify a custom issue prefix.`, Run: func(cmd *cobra.Command, args []string) { prefix, _ := cmd.Flags().GetString("prefix") quiet, _ := cmd.Flags().GetBool("quiet") // Check BEADS_DB environment variable if --db flag not set // (PersistentPreRun doesn't run for init command) if dbPath == "" { if envDB := os.Getenv("BEADS_DB"); envDB != "" { dbPath = envDB } } if prefix == "" { // Auto-detect from directory name cwd, err := os.Getwd() if err != nil { fmt.Fprintf(os.Stderr, "Error: failed to get current directory: %v\n", err) os.Exit(1) } prefix = filepath.Base(cwd) } // Normalize prefix: strip trailing hyphens // The hyphen is added automatically during ID generation prefix = strings.TrimRight(prefix, "-") // Create database // Use global dbPath if set via --db flag or BEADS_DB env var, otherwise default to .beads/{prefix}.db initDBPath := dbPath if initDBPath == "" { initDBPath = filepath.Join(".beads", prefix+".db") } // Determine if we should create .beads/ directory in CWD // Only create it if the database will be stored there cwd, err := os.Getwd() if err != nil { fmt.Fprintf(os.Stderr, "Error: failed to get current directory: %v\n", err) os.Exit(1) } localBeadsDir := filepath.Join(cwd, ".beads") initDBDir := filepath.Dir(initDBPath) // Convert both to absolute paths for comparison localBeadsDirAbs, err := filepath.Abs(localBeadsDir) if err != nil { localBeadsDirAbs = filepath.Clean(localBeadsDir) } initDBDirAbs, err := filepath.Abs(initDBDir) if err != nil { initDBDirAbs = filepath.Clean(initDBDir) } useLocalBeads := filepath.Clean(initDBDirAbs) == filepath.Clean(localBeadsDirAbs) if useLocalBeads { // Create .beads directory if err := os.MkdirAll(localBeadsDir, 0750); err != nil { fmt.Fprintf(os.Stderr, "Error: failed to create .beads directory: %v\n", err) os.Exit(1) } // Create .gitignore in .beads directory gitignorePath := filepath.Join(localBeadsDir, ".gitignore") gitignoreContent := `# SQLite databases *.db *.db-journal *.db-wal *.db-shm # Daemon runtime files daemon.log daemon.pid bd.sock # Legacy database files db.sqlite bd.db # Keep JSONL exports (source of truth for git) !*.jsonl ` if err := os.WriteFile(gitignorePath, []byte(gitignoreContent), 0644); err != nil { fmt.Fprintf(os.Stderr, "Warning: failed to create .gitignore: %v\n", err) // Non-fatal - continue anyway } } // Ensure parent directory exists for the database if err := os.MkdirAll(initDBDir, 0750); err != nil { fmt.Fprintf(os.Stderr, "Error: failed to create database directory %s: %v\n", initDBDir, err) os.Exit(1) } store, err := sqlite.New(initDBPath) if err != nil { fmt.Fprintf(os.Stderr, "Error: failed to create database: %v\n", err) os.Exit(1) } // Set the issue prefix in config ctx := context.Background() if err := store.SetConfig(ctx, "issue_prefix", prefix); err != nil { fmt.Fprintf(os.Stderr, "Error: failed to set issue prefix: %v\n", err) _ = store.Close() os.Exit(1) } // Store the bd version in metadata (for version mismatch detection) if err := store.SetMetadata(ctx, "bd_version", Version); err != nil { fmt.Fprintf(os.Stderr, "Warning: failed to store version metadata: %v\n", err) // Non-fatal - continue anyway } // Check if git has existing issues to import (fresh clone scenario) issueCount, jsonlPath := checkGitForIssues() if issueCount > 0 { if !quiet { fmt.Fprintf(os.Stderr, "\nāœ“ Database initialized. Found %d issues in git, importing...\n", issueCount) } if err := importFromGit(ctx, initDBPath, store, jsonlPath); err != nil { if !quiet { fmt.Fprintf(os.Stderr, "Warning: auto-import failed: %v\n", err) fmt.Fprintf(os.Stderr, "Try manually: git show HEAD:%s | bd import -i /dev/stdin\n", jsonlPath) } // Non-fatal - continue with empty database } else if !quiet { fmt.Fprintf(os.Stderr, "āœ“ Successfully imported %d issues from git.\n\n", issueCount) } } if err := store.Close(); err != nil { fmt.Fprintf(os.Stderr, "Warning: failed to close database: %v\n", err) } // Skip output if quiet mode if quiet { return } green := color.New(color.FgGreen).SprintFunc() cyan := color.New(color.FgCyan).SprintFunc() fmt.Printf("\n%s bd initialized successfully!\n\n", green("āœ“")) fmt.Printf(" Database: %s\n", cyan(initDBPath)) fmt.Printf(" Issue prefix: %s\n", cyan(prefix)) fmt.Printf(" Issues will be named: %s\n\n", cyan(prefix+"-1, "+prefix+"-2, ...")) fmt.Printf("Run %s to get started.\n\n", cyan("bd quickstart")) }, } func init() { initCmd.Flags().StringP("prefix", "p", "", "Issue prefix (default: current directory name)") initCmd.Flags().BoolP("quiet", "q", false, "Suppress output (quiet mode)") rootCmd.AddCommand(initCmd) }