diff --git a/cmd/add.go b/cmd/add.go index 92afb16..b700a81 100644 --- a/cmd/add.go +++ b/cmd/add.go @@ -9,10 +9,11 @@ import ( ) var addCmd = &cobra.Command{ - Use: "add ", - Short: "Add a file to lnk management", - Long: "Moves a file to the lnk repository and creates a symlink in its place.", - Args: cobra.ExactArgs(1), + Use: "add ", + Short: "✨ Add a file to lnk management", + Long: "Moves a file to the lnk repository and creates a symlink in its place.", + Args: cobra.ExactArgs(1), + SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { filePath := args[0] @@ -22,7 +23,9 @@ var addCmd = &cobra.Command{ } basename := filepath.Base(filePath) - fmt.Printf("Added %s to lnk\n", basename) + fmt.Printf("✨ \033[1mAdded %s to lnk\033[0m\n", basename) + fmt.Printf(" šŸ”— \033[90m%s\033[0m → \033[36m~/.config/lnk/%s\033[0m\n", filePath, basename) + fmt.Printf(" šŸ“ Use \033[1mlnk push\033[0m to sync to remote\n") return nil }, } diff --git a/cmd/init.go b/cmd/init.go index 2a204f5..d007617 100644 --- a/cmd/init.go +++ b/cmd/init.go @@ -8,9 +8,10 @@ import ( ) var initCmd = &cobra.Command{ - Use: "init", - Short: "Initialize a new lnk repository", - Long: "Creates the lnk directory and initializes a Git repository for managing dotfiles.", + Use: "init", + Short: "šŸŽÆ Initialize a new lnk repository", + Long: "Creates the lnk directory and initializes a Git repository for managing dotfiles.", + SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { remote, _ := cmd.Flags().GetString("remote") @@ -20,9 +21,18 @@ var initCmd = &cobra.Command{ } if remote != "" { - fmt.Printf("Initialized lnk repository by cloning: %s\n", remote) + fmt.Printf("šŸŽÆ \033[1mInitialized lnk repository\033[0m\n") + fmt.Printf(" šŸ“¦ Cloned from: \033[36m%s\033[0m\n", remote) + fmt.Printf(" šŸ“ Location: \033[90m~/.config/lnk\033[0m\n") + fmt.Printf("\nšŸ’” \033[33mNext steps:\033[0m\n") + fmt.Printf(" • Run \033[1mlnk pull\033[0m to restore symlinks\n") + fmt.Printf(" • Use \033[1mlnk add \033[0m to manage new files\n") } else { - fmt.Println("Initialized lnk repository") + fmt.Printf("šŸŽÆ \033[1mInitialized empty lnk repository\033[0m\n") + fmt.Printf(" šŸ“ Location: \033[90m~/.config/lnk\033[0m\n") + fmt.Printf("\nšŸ’” \033[33mNext steps:\033[0m\n") + fmt.Printf(" • Run \033[1mlnk add \033[0m to start managing dotfiles\n") + fmt.Printf(" • Add a remote with: \033[1mgit remote add origin \033[0m\n") } return nil diff --git a/cmd/pull.go b/cmd/pull.go index a8ad1c8..403c5fc 100644 --- a/cmd/pull.go +++ b/cmd/pull.go @@ -8,9 +8,10 @@ import ( ) var pullCmd = &cobra.Command{ - Use: "pull", - Short: "Pull changes from remote and restore symlinks", - Long: "Fetches changes from remote repository and automatically restores symlinks for all managed files.", + Use: "pull", + Short: "ā¬‡ļø Pull changes from remote and restore symlinks", + Long: "Fetches changes from remote repository and automatically restores symlinks for all managed files.", + SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { lnk := core.NewLnk() restored, err := lnk.Pull() @@ -19,12 +20,20 @@ var pullCmd = &cobra.Command{ } if len(restored) > 0 { - fmt.Printf("Successfully pulled changes and restored %d symlink(s):\n", len(restored)) - for _, file := range restored { - fmt.Printf(" - %s\n", file) + fmt.Printf("ā¬‡ļø \033[1;32mSuccessfully pulled changes\033[0m\n") + fmt.Printf(" šŸ”— Restored \033[1m%d symlink", len(restored)) + if len(restored) > 1 { + fmt.Printf("s") } + fmt.Printf("\033[0m:\n") + for _, file := range restored { + fmt.Printf(" ✨ \033[36m%s\033[0m\n", file) + } + fmt.Printf("\n šŸŽ‰ Your dotfiles are synced and ready!\n") } else { - fmt.Println("Successfully pulled changes (no symlinks needed restoration)") + fmt.Printf("ā¬‡ļø \033[1;32mSuccessfully pulled changes\033[0m\n") + fmt.Printf(" āœ… All symlinks already in place\n") + fmt.Printf(" šŸŽ‰ Everything is up to date!\n") } return nil diff --git a/cmd/push.go b/cmd/push.go index 7d0ea14..440f913 100644 --- a/cmd/push.go +++ b/cmd/push.go @@ -8,10 +8,11 @@ import ( ) var pushCmd = &cobra.Command{ - Use: "push [message]", - Short: "Push local changes to remote repository", - Long: "Stages all changes, creates a sync commit with the provided message, and pushes to remote.", - Args: cobra.MaximumNArgs(1), + Use: "push [message]", + Short: "šŸš€ Push local changes to remote repository", + Long: "Stages all changes, creates a sync commit with the provided message, and pushes to remote.", + Args: cobra.MaximumNArgs(1), + SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { message := "lnk: sync configuration files" if len(args) > 0 { @@ -23,7 +24,10 @@ var pushCmd = &cobra.Command{ return fmt.Errorf("failed to push changes: %w", err) } - fmt.Println("Successfully pushed changes to remote") + fmt.Printf("šŸš€ \033[1;32mSuccessfully pushed changes\033[0m\n") + fmt.Printf(" šŸ’¾ Commit: \033[90m%s\033[0m\n", message) + fmt.Printf(" šŸ“” Synced to remote\n") + fmt.Printf(" ✨ Your dotfiles are up to date!\n") return nil }, } diff --git a/cmd/rm.go b/cmd/rm.go index 7f2acd3..7ef0669 100644 --- a/cmd/rm.go +++ b/cmd/rm.go @@ -9,10 +9,11 @@ import ( ) var rmCmd = &cobra.Command{ - Use: "rm ", - Short: "Remove a file from lnk management", - Long: "Removes a symlink and restores the original file from the lnk repository.", - Args: cobra.ExactArgs(1), + Use: "rm ", + Short: "šŸ—‘ļø Remove a file from lnk management", + Long: "Removes a symlink and restores the original file from the lnk repository.", + Args: cobra.ExactArgs(1), + SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { filePath := args[0] @@ -22,7 +23,9 @@ var rmCmd = &cobra.Command{ } basename := filepath.Base(filePath) - fmt.Printf("Removed %s from lnk\n", basename) + fmt.Printf("šŸ—‘ļø \033[1mRemoved %s from lnk\033[0m\n", basename) + fmt.Printf(" ā†©ļø \033[90m~/.config/lnk/%s\033[0m → \033[36m%s\033[0m\n", basename, filePath) + fmt.Printf(" šŸ“„ Original file restored\n") return nil }, } diff --git a/cmd/root.go b/cmd/root.go index 2374e83..7bc8bd0 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -14,8 +14,21 @@ var ( var rootCmd = &cobra.Command{ Use: "lnk", - Short: "Dotfiles, linked. No fluff.", - Long: "Lnk is a minimalist CLI tool for managing dotfiles using symlinks and Git.", + Short: "šŸ”— Dotfiles, linked. No fluff.", + Long: `šŸ”— Lnk - Git-native dotfiles management that doesn't suck. + +Move your dotfiles to ~/.config/lnk, symlink them back, and use Git like normal. +That's it. + +✨ Examples: + lnk init # Fresh start + lnk init -r # Clone existing dotfiles + lnk add ~/.vimrc ~/.bashrc # Start managing files + lnk push "setup complete" # Sync to remote + lnk pull # Get latest changes + +šŸŽÆ Simple, fast, and Git-native.`, + SilenceUsage: true, } // SetVersion sets the version information for the CLI diff --git a/cmd/status.go b/cmd/status.go index 0ce335f..7199442 100644 --- a/cmd/status.go +++ b/cmd/status.go @@ -8,9 +8,10 @@ import ( ) var statusCmd = &cobra.Command{ - Use: "status", - Short: "Show repository sync status", - Long: "Display how many commits ahead/behind the local repository is relative to the remote.", + Use: "status", + Short: "šŸ“Š Show repository sync status", + Long: "Display how many commits ahead/behind the local repository is relative to the remote.", + SilenceUsage: true, RunE: func(cmd *cobra.Command, args []string) error { lnk := core.NewLnk() status, err := lnk.Status() @@ -19,13 +20,32 @@ var statusCmd = &cobra.Command{ } if status.Ahead == 0 && status.Behind == 0 { - fmt.Println("Repository is up to date with remote") + fmt.Printf("āœ… \033[1;32mRepository is up to date\033[0m\n") + fmt.Printf(" šŸ“” Synced with \033[36m%s\033[0m\n", status.Remote) } else { + fmt.Printf("šŸ“Š \033[1mRepository Status\033[0m\n") + fmt.Printf(" šŸ“” Remote: \033[36m%s\033[0m\n", status.Remote) + fmt.Printf("\n") + if status.Ahead > 0 { - fmt.Printf("Your branch is ahead of '%s' by %d commit(s)\n", status.Remote, status.Ahead) + commitText := "commit" + if status.Ahead > 1 { + commitText = "commits" + } + fmt.Printf(" ā¬†ļø \033[1;33m%d %s ahead\033[0m - ready to push\n", status.Ahead, commitText) } if status.Behind > 0 { - fmt.Printf("Your branch is behind '%s' by %d commit(s)\n", status.Remote, status.Behind) + commitText := "commit" + if status.Behind > 1 { + commitText = "commits" + } + fmt.Printf(" ā¬‡ļø \033[1;31m%d %s behind\033[0m - run \033[1mlnk pull\033[0m\n", status.Behind, commitText) + } + + if status.Ahead > 0 && status.Behind == 0 { + fmt.Printf("\nšŸ’” Run \033[1mlnk push\033[0m to sync your changes") + } else if status.Behind > 0 { + fmt.Printf("\nšŸ’” Run \033[1mlnk pull\033[0m to get latest changes") } } diff --git a/internal/core/lnk.go b/internal/core/lnk.go index f7bf1a1..7e0700f 100644 --- a/internal/core/lnk.go +++ b/internal/core/lnk.go @@ -68,7 +68,7 @@ func (l *Lnk) InitWithRemote(remoteURL string) error { return nil } else { // It's not a lnk repository, error to prevent data loss - return fmt.Errorf("directory %s appears to contain an existing Git repository that is not managed by lnk. Please backup or move the existing repository before initializing lnk", l.repoPath) + return fmt.Errorf("āŒ Directory \033[31m%s\033[0m contains an existing Git repository\n šŸ’” Please backup or move the existing repository before initializing lnk", l.repoPath) } } @@ -282,7 +282,7 @@ type StatusInfo struct { func (l *Lnk) Status() (*StatusInfo, error) { // Check if repository is initialized if !l.git.IsGitRepository() { - return nil, fmt.Errorf("lnk repository not initialized - run 'lnk init' first") + return nil, fmt.Errorf("āŒ Lnk repository not initialized\n šŸ’” Run \033[1mlnk init\033[0m first") } gitStatus, err := l.git.GetStatus() @@ -301,7 +301,7 @@ func (l *Lnk) Status() (*StatusInfo, error) { func (l *Lnk) Push(message string) error { // Check if repository is initialized if !l.git.IsGitRepository() { - return fmt.Errorf("lnk repository not initialized - run 'lnk init' first") + return fmt.Errorf("āŒ Lnk repository not initialized\n šŸ’” Run \033[1mlnk init\033[0m first") } // Check if there are any changes @@ -335,7 +335,7 @@ func (l *Lnk) Push(message string) error { func (l *Lnk) Pull() ([]string, error) { // Check if repository is initialized if !l.git.IsGitRepository() { - return nil, fmt.Errorf("lnk repository not initialized - run 'lnk init' first") + return nil, fmt.Errorf("āŒ Lnk repository not initialized\n šŸ’” Run \033[1mlnk init\033[0m first") } // Pull changes from remote (this will be a no-op in tests since we don't have real remotes) diff --git a/internal/fs/filesystem.go b/internal/fs/filesystem.go index 345c50b..996e492 100644 --- a/internal/fs/filesystem.go +++ b/internal/fs/filesystem.go @@ -21,14 +21,14 @@ func (fs *FileSystem) ValidateFileForAdd(filePath string) error { info, err := os.Stat(filePath) if err != nil { if os.IsNotExist(err) { - return fmt.Errorf("file does not exist: %s", filePath) + return fmt.Errorf("āŒ File does not exist: \033[31m%s\033[0m", filePath) } - return fmt.Errorf("failed to stat file: %w", err) + return fmt.Errorf("āŒ Failed to check file: %w", err) } // Allow both regular files and directories if !info.Mode().IsRegular() && !info.IsDir() { - return fmt.Errorf("only regular files and directories are supported: %s", filePath) + return fmt.Errorf("āŒ Only regular files and directories are supported: \033[31m%s\033[0m", filePath) } return nil @@ -40,14 +40,14 @@ func (fs *FileSystem) ValidateSymlinkForRemove(filePath, repoPath string) error info, err := os.Lstat(filePath) // Use Lstat to not follow symlinks if err != nil { if os.IsNotExist(err) { - return fmt.Errorf("file does not exist: %s", filePath) + return fmt.Errorf("āŒ File does not exist: \033[31m%s\033[0m", filePath) } - return fmt.Errorf("failed to stat file: %w", err) + return fmt.Errorf("āŒ Failed to check file: %w", err) } // Check if it's a symlink if info.Mode()&os.ModeSymlink == 0 { - return fmt.Errorf("file is not managed by lnk: %s", filePath) + return fmt.Errorf("āŒ File is not managed by lnk: \033[31m%s\033[0m\n šŸ’” Use \033[1mlnk add\033[0m to manage this file first", filePath) } // Check if symlink points to the repository @@ -67,7 +67,7 @@ func (fs *FileSystem) ValidateSymlinkForRemove(filePath, repoPath string) error // Check if target is inside the repository if !strings.HasPrefix(target, repoPath+string(filepath.Separator)) && target != repoPath { - return fmt.Errorf("file is not managed by lnk: %s", filePath) + return fmt.Errorf("āŒ File is not managed by lnk: \033[31m%s\033[0m", filePath) } return nil diff --git a/test/integration_test.go b/test/integration_test.go index 7a9a86b..b59bdff 100644 --- a/test/integration_test.go +++ b/test/integration_test.go @@ -162,7 +162,7 @@ func (suite *LnkIntegrationTestSuite) TestAddNonexistentFile() { err = suite.lnk.Add("/nonexistent/file") suite.Error(err) - suite.Contains(err.Error(), "file does not exist") + suite.Contains(err.Error(), "File does not exist") } func (suite *LnkIntegrationTestSuite) TestAddDirectory() { @@ -406,7 +406,7 @@ func (suite *LnkIntegrationTestSuite) TestRemoveNonSymlink() { err = suite.lnk.Remove(testFile) suite.Error(err) - suite.Contains(err.Error(), "file is not managed by lnk") + suite.Contains(err.Error(), "File is not managed by lnk") } func (suite *LnkIntegrationTestSuite) TestXDGConfigHomeFallback() { @@ -550,7 +550,7 @@ func (suite *LnkIntegrationTestSuite) TestInitWithNonLnkRepo() { // Now try to init lnk - should error to protect existing repo err = suite.lnk.Init() suite.Error(err) - suite.Contains(err.Error(), "appears to contain an existing Git repository") + suite.Contains(err.Error(), "contains an existing Git repository") // Verify the original file is still there suite.FileExists(testFile)