xapi-cli/cmd/login.go
Soldier 94635e7ace feat: MVP release - OAuth 1.0a CLI for X API
Complete CLI tool with 4 core commands:
- xapi login: Configure OAuth credentials via editor
- xapi status: Test authentication
- xapi search: Search tweets with preview/execute modes
- xapi create: Post tweets with preview/execute modes

Features:
- OAuth 1.0a authentication with HMAC-SHA1 signing
- OAuth 2.0 Client ID/Secret support (for future features)
- TOML-based configuration
- Editor integration for config management
- Helpful error messages for permission issues
- Quota-aware design (no caching to avoid complexity)

Built for developers on Free/Basic X API tiers.
2025-11-13 21:46:18 +00:00

148 lines
3.8 KiB
Go

/*
Copyright © 2025 maxtheweb
*/
package cmd
import (
"fmt"
"os"
"os/exec"
"path/filepath"
"github.com/spf13/cobra"
)
// loginCmd represents the login command
var loginCmd = &cobra.Command{
Use: "login",
Short: "Set up your X API credentials easily",
Long: `Opens your editor to configure your X API credentials.
The configuration will be saved to ~/.config/xapi/config.toml
Get your credentials from: https://developer.x.com/en/portal/dashboard
You'll need:
- OAuth 1.0a credentials (Consumer Key/Secret, Access Token/Secret)
- OAuth 2.0 credentials (Client ID/Secret - optional, for future features)
Usage:
xapi login # Edit credentials in your editor`,
Run: func(cmd *cobra.Command, args []string) {
runLogin()
},
}
func init() {
rootCmd.AddCommand(loginCmd)
}
// findEditor searches for an available text editor
func findEditor() string {
// First, check if EDITOR is set
if editor := os.Getenv("EDITOR"); editor != "" {
// Verify it exists
if _, err := exec.LookPath(editor); err == nil {
return editor
}
}
// Check for VISUAL (some systems use this)
if visual := os.Getenv("VISUAL"); visual != "" {
if _, err := exec.LookPath(visual); err == nil {
return visual
}
}
// Try common editors in order of preference
editors := []string{
"nvim", // Neovim (power users)
"vim", // Vim (widely available)
"vi", // Vi (almost always there)
"nano", // Nano (beginner-friendly)
"emacs", // Emacs (for the enlightened)
"micro", // Micro (modern terminal editor)
"code", // VS Code (if they have it in terminal)
"subl", // Sublime Text
"gedit", // GNOME editor
"kate", // KDE editor
}
for _, editor := range editors {
if _, err := exec.LookPath(editor); err == nil {
return editor
}
}
return "" // No editor found
}
func runLogin() {
// Get config directory
homeDir, err := os.UserHomeDir()
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting home directory: %v\n", err)
os.Exit(1)
}
configDir := filepath.Join(homeDir, ".config", "xapi")
configFile := filepath.Join(configDir, "config.toml")
// Create config directory if it doesn't exist
if err := os.MkdirAll(configDir, 0755); err != nil {
fmt.Fprintf(os.Stderr, "Error creating config directory: %v\n", err)
os.Exit(1)
}
// Check if config file exists, if not create template
if _, err := os.Stat(configFile); os.IsNotExist(err) {
template := `# X API Configuration
# Get your credentials from: https://developer.x.com/en/portal/dashboard
#
# SECURITY NOTE: Never share this file or commit it to version control!
# OAuth 1.0a Credentials (required for posting and searching)
[oauth]
consumer_key = ""
consumer_secret = ""
access_token = ""
access_token_secret = ""
# OAuth 2.0 Credentials (optional, for future features)
[oauth2]
client_id = ""
client_secret = ""
`
if err := os.WriteFile(configFile, []byte(template), 0600); err != nil {
fmt.Fprintf(os.Stderr, "Error creating config file: %v\n", err)
os.Exit(1)
}
}
// Get editor using smart detection
editor := findEditor()
if editor == "" {
fmt.Fprintf(os.Stderr, "Error: No text editor found!\n\n")
fmt.Println("Please install one of: nvim, vim, vi, nano, emacs")
fmt.Println("Or set your preferred editor: export EDITOR=nvim")
os.Exit(1)
}
// Open editor
fmt.Printf("Opening %s with %s...\n", configFile, editor)
cmd := exec.Command(editor, configFile)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err := cmd.Run(); err != nil {
fmt.Fprintf(os.Stderr, "Error opening editor: %v\n", err)
os.Exit(1)
}
fmt.Println("\nCredentials saved!")
fmt.Println("\nNext step:")
fmt.Println(" xapi status # Test your authentication")
fmt.Println("\nThen you can use:")
fmt.Println(" xapi search # Search tweets")
fmt.Println(" xapi create # Create posts")
}