Initial commit: Azure DevOps TUI client
A terminal-based user interface for browsing and managing Azure DevOps work items. Features include: - Browse work items with filtering by area, iteration, state, and type - View work item details with markdown rendering - Open work items in browser - Create git branches from work items - Update work item state - Keyboard-driven navigation 🤖 Generated with [Claude Code](https://claude.ai/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Config holds the application configuration
|
||||
type Config struct {
|
||||
Organization string `mapstructure:"organization"`
|
||||
Project string `mapstructure:"project"`
|
||||
Team string `mapstructure:"team"`
|
||||
PAT string `mapstructure:"pat"`
|
||||
Theme string `mapstructure:"theme"`
|
||||
Defaults Defaults `mapstructure:"defaults"`
|
||||
}
|
||||
|
||||
// Defaults holds default filter settings
|
||||
type Defaults struct {
|
||||
Sprint string `mapstructure:"sprint"`
|
||||
State string `mapstructure:"state"`
|
||||
Assigned string `mapstructure:"assigned"`
|
||||
}
|
||||
|
||||
// Load loads the configuration from file and environment
|
||||
func Load() (*Config, error) {
|
||||
v := viper.New()
|
||||
|
||||
// Set config file name and paths
|
||||
v.SetConfigName("config")
|
||||
v.SetConfigType("yaml")
|
||||
|
||||
// Add config paths
|
||||
home, err := os.UserHomeDir()
|
||||
if err == nil {
|
||||
v.AddConfigPath(filepath.Join(home, ".config", "devops-tui"))
|
||||
}
|
||||
v.AddConfigPath(".")
|
||||
|
||||
// Set defaults
|
||||
v.SetDefault("theme", "default")
|
||||
v.SetDefault("defaults.sprint", "current")
|
||||
v.SetDefault("defaults.state", "all")
|
||||
v.SetDefault("defaults.assigned", "me")
|
||||
|
||||
// Read config file (ignore if not found)
|
||||
if err := v.ReadInConfig(); err != nil {
|
||||
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
|
||||
return nil, fmt.Errorf("error reading config file: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// Bind environment variables
|
||||
v.SetEnvPrefix("")
|
||||
v.AutomaticEnv()
|
||||
|
||||
// Map environment variables
|
||||
v.BindEnv("pat", "AZURE_DEVOPS_PAT")
|
||||
v.BindEnv("organization", "AZURE_DEVOPS_ORG")
|
||||
v.BindEnv("project", "AZURE_DEVOPS_PROJECT")
|
||||
v.BindEnv("team", "AZURE_DEVOPS_TEAM")
|
||||
|
||||
var cfg Config
|
||||
if err := v.Unmarshal(&cfg); err != nil {
|
||||
return nil, fmt.Errorf("error unmarshaling config: %w", err)
|
||||
}
|
||||
|
||||
// Validate required fields
|
||||
if cfg.Organization == "" {
|
||||
return nil, fmt.Errorf("organization is required (set in config or AZURE_DEVOPS_ORG)")
|
||||
}
|
||||
if cfg.Project == "" {
|
||||
return nil, fmt.Errorf("project is required (set in config or AZURE_DEVOPS_PROJECT)")
|
||||
}
|
||||
if cfg.Team == "" {
|
||||
return nil, fmt.Errorf("team is required (set in config or AZURE_DEVOPS_TEAM)")
|
||||
}
|
||||
if cfg.PAT == "" {
|
||||
return nil, fmt.Errorf("PAT is required (set in config or AZURE_DEVOPS_PAT)")
|
||||
}
|
||||
|
||||
return &cfg, nil
|
||||
}
|
||||
|
||||
// BaseURL returns the Azure DevOps API base URL
|
||||
func (c *Config) BaseURL() string {
|
||||
return fmt.Sprintf("https://dev.azure.com/%s/%s/_apis", c.Organization, c.Project)
|
||||
}
|
||||
|
||||
// TeamURL returns the Azure DevOps API URL for team-specific endpoints
|
||||
func (c *Config) TeamURL() string {
|
||||
return fmt.Sprintf("https://dev.azure.com/%s/%s/%s/_apis", c.Organization, c.Project, c.Team)
|
||||
}
|
||||
|
||||
// WebURL returns the Azure DevOps web URL for the project
|
||||
func (c *Config) WebURL() string {
|
||||
return fmt.Sprintf("https://dev.azure.com/%s/%s", c.Organization, c.Project)
|
||||
}
|
||||
|
||||
// CreateDefaultConfig creates a default config file
|
||||
func CreateDefaultConfig() error {
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configDir := filepath.Join(home, ".config", "devops-tui")
|
||||
if err := os.MkdirAll(configDir, 0755); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
configPath := filepath.Join(configDir, "config.yaml")
|
||||
|
||||
// Don't overwrite existing config
|
||||
if _, err := os.Stat(configPath); err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
content := `# Azure DevOps connection
|
||||
organization: "my-organization"
|
||||
project: "my-project"
|
||||
team: "my-team"
|
||||
|
||||
# Authentication
|
||||
# PAT can be set here or via environment variable AZURE_DEVOPS_PAT
|
||||
pat: ""
|
||||
|
||||
# UI settings
|
||||
theme: "default" # default, dark, light
|
||||
|
||||
# Default filters at startup
|
||||
defaults:
|
||||
sprint: "current" # "current", "all", or specific name
|
||||
state: "all" # "all", "new", "active", "resolved", "closed"
|
||||
assigned: "me" # "all", "me"
|
||||
`
|
||||
|
||||
return os.WriteFile(configPath, []byte(content), 0600)
|
||||
}
|
||||
Reference in New Issue
Block a user