feat: initial version of the project

This commit is contained in:
Romain Bertrand 2025-12-08 09:49:07 +01:00
commit 5b67d39aba
13 changed files with 1538 additions and 0 deletions

123
cmd/auth.go Normal file
View file

@ -0,0 +1,123 @@
package cmd
import (
"bufio"
"fmt"
"os"
"strings"
"syscall"
"github.com/spf13/cobra"
"codeberg.org/romaintb/fgj/internal/api"
"codeberg.org/romaintb/fgj/internal/config"
"golang.org/x/term"
)
var authCmd = &cobra.Command{
Use: "auth",
Short: "Authenticate fgj with a Forgejo instance",
Long: "Manage authentication state for Forgejo instances.",
}
var authLoginCmd = &cobra.Command{
Use: "login",
Short: "Authenticate with a Forgejo instance",
Long: "Authenticate with a Forgejo instance using a personal access token.",
RunE: runAuthLogin,
}
var authStatusCmd = &cobra.Command{
Use: "status",
Short: "View authentication status",
Long: "Display the authentication status for configured Forgejo instances.",
RunE: runAuthStatus,
}
func init() {
rootCmd.AddCommand(authCmd)
authCmd.AddCommand(authLoginCmd)
authCmd.AddCommand(authStatusCmd)
authLoginCmd.Flags().String("hostname", "", "Forgejo instance hostname (e.g., codeberg.org)")
authLoginCmd.Flags().StringP("token", "t", "", "Personal access token")
}
func runAuthLogin(cmd *cobra.Command, args []string) error {
hostname, _ := cmd.Flags().GetString("hostname")
token, _ := cmd.Flags().GetString("token")
reader := bufio.NewReader(os.Stdin)
if hostname == "" {
fmt.Print("Forgejo instance hostname (default: codeberg.org): ")
input, _ := reader.ReadString('\n')
hostname = strings.TrimSpace(input)
if hostname == "" {
hostname = "codeberg.org"
}
}
if token == "" {
fmt.Print("Personal access token: ")
tokenBytes, err := term.ReadPassword(int(syscall.Stdin))
if err != nil {
return fmt.Errorf("failed to read token: %w", err)
}
fmt.Println()
token = strings.TrimSpace(string(tokenBytes))
}
if token == "" {
return fmt.Errorf("token is required")
}
client, err := api.NewClient(hostname, token)
if err != nil {
return fmt.Errorf("failed to create client: %w", err)
}
user, _, err := client.GetMyUserInfo()
if err != nil {
return fmt.Errorf("authentication failed: %w", err)
}
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
cfg.SetHost(hostname, config.HostConfig{
Hostname: hostname,
Token: token,
User: user.UserName,
GitProtocol: "https",
})
if err := cfg.Save(); err != nil {
return fmt.Errorf("failed to save config: %w", err)
}
fmt.Printf("✓ Authenticated as %s on %s\n", user.UserName, hostname)
return nil
}
func runAuthStatus(cmd *cobra.Command, args []string) error {
cfg, err := config.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
if len(cfg.Hosts) == 0 {
fmt.Println("Not authenticated with any Forgejo instances")
fmt.Println("Run 'fgj auth login' to authenticate")
return nil
}
fmt.Println("Authenticated instances:")
for hostname, host := range cfg.Hosts {
fmt.Printf(" • %s (user: %s)\n", hostname, host.User)
}
return nil
}

301
cmd/issue.go Normal file
View file

@ -0,0 +1,301 @@
package cmd
import (
"fmt"
"os"
"strconv"
"strings"
"text/tabwriter"
"code.gitea.io/sdk/gitea"
"github.com/spf13/cobra"
"codeberg.org/romaintb/fgj/internal/api"
"codeberg.org/romaintb/fgj/internal/config"
)
var issueCmd = &cobra.Command{
Use: "issue",
Short: "Manage issues",
Long: "Create, view, list, and manage issues.",
}
var issueListCmd = &cobra.Command{
Use: "list [flags]",
Short: "List issues",
Long: "List issues in a repository.",
RunE: runIssueList,
}
var issueViewCmd = &cobra.Command{
Use: "view <number>",
Short: "View an issue",
Long: "Display detailed information about an issue.",
Args: cobra.ExactArgs(1),
RunE: runIssueView,
}
var issueCreateCmd = &cobra.Command{
Use: "create",
Short: "Create an issue",
Long: "Create a new issue.",
RunE: runIssueCreate,
}
var issueCommentCmd = &cobra.Command{
Use: "comment <number>",
Short: "Add a comment to an issue",
Long: "Add a comment to an existing issue.",
Args: cobra.ExactArgs(1),
RunE: runIssueComment,
}
var issueCloseCmd = &cobra.Command{
Use: "close <number>",
Short: "Close an issue",
Long: "Close an existing issue.",
Args: cobra.ExactArgs(1),
RunE: runIssueClose,
}
func init() {
rootCmd.AddCommand(issueCmd)
issueCmd.AddCommand(issueListCmd)
issueCmd.AddCommand(issueViewCmd)
issueCmd.AddCommand(issueCreateCmd)
issueCmd.AddCommand(issueCommentCmd)
issueCmd.AddCommand(issueCloseCmd)
issueListCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
issueListCmd.Flags().StringP("state", "s", "open", "Filter by state: open, closed, all")
issueViewCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
issueCreateCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
issueCreateCmd.Flags().StringP("title", "t", "", "Title for the issue")
issueCreateCmd.Flags().StringP("body", "b", "", "Body for the issue")
issueCommentCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
issueCommentCmd.Flags().StringP("body", "b", "", "Comment body")
issueCloseCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
}
func runIssueList(cmd *cobra.Command, args []string) error {
repo, _ := cmd.Flags().GetString("repo")
state, _ := cmd.Flags().GetString("state")
owner, name, err := parseRepo(repo)
if err != nil {
return err
}
cfg, err := config.Load()
if err != nil {
return err
}
client, err := api.NewClientFromConfig(cfg, "")
if err != nil {
return err
}
var stateType gitea.StateType
switch strings.ToLower(state) {
case "open":
stateType = gitea.StateOpen
case "closed":
stateType = gitea.StateClosed
case "all":
stateType = gitea.StateAll
default:
return fmt.Errorf("invalid state: %s", state)
}
issues, _, err := client.ListRepoIssues(owner, name, gitea.ListIssueOption{
State: stateType,
})
if err != nil {
return fmt.Errorf("failed to list issues: %w", err)
}
if len(issues) == 0 {
fmt.Printf("No %s issues in %s/%s\n", state, owner, name)
return nil
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "NUMBER\tTITLE\tSTATE\n")
for _, issue := range issues {
if issue.PullRequest == nil {
fmt.Fprintf(w, "#%d\t%s\t%s\n", issue.Index, issue.Title, issue.State)
}
}
w.Flush()
return nil
}
func runIssueView(cmd *cobra.Command, args []string) error {
repo, _ := cmd.Flags().GetString("repo")
issueNumber, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return fmt.Errorf("invalid issue number: %w", err)
}
owner, name, err := parseRepo(repo)
if err != nil {
return err
}
cfg, err := config.Load()
if err != nil {
return err
}
client, err := api.NewClientFromConfig(cfg, "")
if err != nil {
return err
}
issue, _, err := client.GetIssue(owner, name, issueNumber)
if err != nil {
return fmt.Errorf("failed to get issue: %w", err)
}
fmt.Printf("Issue #%d\n", issue.Index)
fmt.Printf("Title: %s\n", issue.Title)
fmt.Printf("State: %s\n", issue.State)
fmt.Printf("Author: %s\n", issue.Poster.UserName)
fmt.Printf("Created: %s\n", issue.Created.Format("2006-01-02 15:04:05"))
fmt.Printf("Updated: %s\n", issue.Updated.Format("2006-01-02 15:04:05"))
if issue.Body != "" {
fmt.Printf("\n%s\n", issue.Body)
}
comments, _, err := client.ListIssueComments(owner, name, issueNumber, gitea.ListIssueCommentOptions{})
if err == nil && len(comments) > 0 {
fmt.Printf("\nComments (%d):\n", len(comments))
for _, comment := range comments {
fmt.Printf("\n---\n%s (@%s) - %s\n%s\n",
comment.Poster.FullName,
comment.Poster.UserName,
comment.Created.Format("2006-01-02 15:04:05"),
comment.Body)
}
}
return nil
}
func runIssueCreate(cmd *cobra.Command, args []string) error {
repo, _ := cmd.Flags().GetString("repo")
title, _ := cmd.Flags().GetString("title")
body, _ := cmd.Flags().GetString("body")
owner, name, err := parseRepo(repo)
if err != nil {
return err
}
if title == "" {
return fmt.Errorf("title is required")
}
cfg, err := config.Load()
if err != nil {
return err
}
client, err := api.NewClientFromConfig(cfg, "")
if err != nil {
return err
}
issue, _, err := client.CreateIssue(owner, name, gitea.CreateIssueOption{
Title: title,
Body: body,
})
if err != nil {
return fmt.Errorf("failed to create issue: %w", err)
}
fmt.Printf("Issue created: #%d\n", issue.Index)
fmt.Printf("View at: %s\n", issue.HTMLURL)
return nil
}
func runIssueComment(cmd *cobra.Command, args []string) error {
repo, _ := cmd.Flags().GetString("repo")
body, _ := cmd.Flags().GetString("body")
issueNumber, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return fmt.Errorf("invalid issue number: %w", err)
}
owner, name, err := parseRepo(repo)
if err != nil {
return err
}
if body == "" {
return fmt.Errorf("comment body is required")
}
cfg, err := config.Load()
if err != nil {
return err
}
client, err := api.NewClientFromConfig(cfg, "")
if err != nil {
return err
}
comment, _, err := client.CreateIssueComment(owner, name, issueNumber, gitea.CreateIssueCommentOption{
Body: body,
})
if err != nil {
return fmt.Errorf("failed to create comment: %w", err)
}
fmt.Printf("Comment added to issue #%d\n", issueNumber)
fmt.Printf("View at: %s\n", comment.HTMLURL)
return nil
}
func runIssueClose(cmd *cobra.Command, args []string) error {
repo, _ := cmd.Flags().GetString("repo")
issueNumber, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return fmt.Errorf("invalid issue number: %w", err)
}
owner, name, err := parseRepo(repo)
if err != nil {
return err
}
cfg, err := config.Load()
if err != nil {
return err
}
client, err := api.NewClientFromConfig(cfg, "")
if err != nil {
return err
}
stateClosed := gitea.StateClosed
_, _, err = client.EditIssue(owner, name, issueNumber, gitea.EditIssueOption{
State: &stateClosed,
})
if err != nil {
return fmt.Errorf("failed to close issue: %w", err)
}
fmt.Printf("Issue #%d closed\n", issueNumber)
return nil
}

277
cmd/pr.go Normal file
View file

@ -0,0 +1,277 @@
package cmd
import (
"fmt"
"os"
"strconv"
"strings"
"text/tabwriter"
"code.gitea.io/sdk/gitea"
"github.com/spf13/cobra"
"codeberg.org/romaintb/fgj/internal/api"
"codeberg.org/romaintb/fgj/internal/config"
)
var prCmd = &cobra.Command{
Use: "pr",
Aliases: []string{"pull-request"},
Short: "Manage pull requests",
Long: "Create, view, list, and manage pull requests.",
}
var prListCmd = &cobra.Command{
Use: "list [flags]",
Short: "List pull requests",
Long: "List pull requests in a repository.",
RunE: runPRList,
}
var prViewCmd = &cobra.Command{
Use: "view <number>",
Short: "View a pull request",
Long: "Display detailed information about a pull request.",
Args: cobra.ExactArgs(1),
RunE: runPRView,
}
var prCreateCmd = &cobra.Command{
Use: "create",
Short: "Create a pull request",
Long: "Create a new pull request.",
RunE: runPRCreate,
}
var prMergeCmd = &cobra.Command{
Use: "merge <number>",
Short: "Merge a pull request",
Long: "Merge a pull request.",
Args: cobra.ExactArgs(1),
RunE: runPRMerge,
}
func init() {
rootCmd.AddCommand(prCmd)
prCmd.AddCommand(prListCmd)
prCmd.AddCommand(prViewCmd)
prCmd.AddCommand(prCreateCmd)
prCmd.AddCommand(prMergeCmd)
prListCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
prListCmd.Flags().StringP("state", "s", "open", "Filter by state: open, closed, all")
prViewCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
prCreateCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
prCreateCmd.Flags().StringP("title", "t", "", "Title for the pull request")
prCreateCmd.Flags().StringP("body", "b", "", "Body for the pull request")
prCreateCmd.Flags().StringP("head", "H", "", "Head branch")
prCreateCmd.Flags().StringP("base", "B", "", "Base branch (default: main)")
prMergeCmd.Flags().StringP("repo", "R", "", "Repository in owner/name format")
prMergeCmd.Flags().String("merge-method", "merge", "Merge method: merge, rebase, squash")
}
func runPRList(cmd *cobra.Command, args []string) error {
repo, _ := cmd.Flags().GetString("repo")
state, _ := cmd.Flags().GetString("state")
owner, name, err := parseRepo(repo)
if err != nil {
return err
}
cfg, err := config.Load()
if err != nil {
return err
}
client, err := api.NewClientFromConfig(cfg, "")
if err != nil {
return err
}
var stateType gitea.StateType
switch strings.ToLower(state) {
case "open":
stateType = gitea.StateOpen
case "closed":
stateType = gitea.StateClosed
case "all":
stateType = gitea.StateAll
default:
return fmt.Errorf("invalid state: %s", state)
}
prs, _, err := client.ListRepoPullRequests(owner, name, gitea.ListPullRequestsOptions{
State: stateType,
})
if err != nil {
return fmt.Errorf("failed to list pull requests: %w", err)
}
if len(prs) == 0 {
fmt.Printf("No %s pull requests in %s/%s\n", state, owner, name)
return nil
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "NUMBER\tTITLE\tBRANCH\tSTATE\n")
for _, pr := range prs {
fmt.Fprintf(w, "#%d\t%s\t%s\t%s\n", pr.Index, pr.Title, pr.Head.Ref, pr.State)
}
w.Flush()
return nil
}
func runPRView(cmd *cobra.Command, args []string) error {
repo, _ := cmd.Flags().GetString("repo")
prNumber, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return fmt.Errorf("invalid pull request number: %w", err)
}
owner, name, err := parseRepo(repo)
if err != nil {
return err
}
cfg, err := config.Load()
if err != nil {
return err
}
client, err := api.NewClientFromConfig(cfg, "")
if err != nil {
return err
}
pr, _, err := client.GetPullRequest(owner, name, prNumber)
if err != nil {
return fmt.Errorf("failed to get pull request: %w", err)
}
fmt.Printf("Pull Request #%d\n", pr.Index)
fmt.Printf("Title: %s\n", pr.Title)
fmt.Printf("State: %s\n", pr.State)
fmt.Printf("Author: %s\n", pr.Poster.UserName)
fmt.Printf("Branch: %s -> %s\n", pr.Head.Ref, pr.Base.Ref)
fmt.Printf("Created: %s\n", pr.Created.Format("2006-01-02 15:04:05"))
fmt.Printf("Updated: %s\n", pr.Updated.Format("2006-01-02 15:04:05"))
if pr.Body != "" {
fmt.Printf("\n%s\n", pr.Body)
}
return nil
}
func runPRCreate(cmd *cobra.Command, args []string) error {
repo, _ := cmd.Flags().GetString("repo")
title, _ := cmd.Flags().GetString("title")
body, _ := cmd.Flags().GetString("body")
head, _ := cmd.Flags().GetString("head")
base, _ := cmd.Flags().GetString("base")
if base == "" {
base = "main"
}
owner, name, err := parseRepo(repo)
if err != nil {
return err
}
if title == "" {
return fmt.Errorf("title is required")
}
if head == "" {
return fmt.Errorf("head branch is required")
}
cfg, err := config.Load()
if err != nil {
return err
}
client, err := api.NewClientFromConfig(cfg, "")
if err != nil {
return err
}
pr, _, err := client.CreatePullRequest(owner, name, gitea.CreatePullRequestOption{
Title: title,
Body: body,
Head: head,
Base: base,
})
if err != nil {
return fmt.Errorf("failed to create pull request: %w", err)
}
fmt.Printf("Pull request created: #%d\n", pr.Index)
fmt.Printf("View at: %s\n", pr.HTMLURL)
return nil
}
func runPRMerge(cmd *cobra.Command, args []string) error {
repo, _ := cmd.Flags().GetString("repo")
mergeMethod, _ := cmd.Flags().GetString("merge-method")
prNumber, err := strconv.ParseInt(args[0], 10, 64)
if err != nil {
return fmt.Errorf("invalid pull request number: %w", err)
}
owner, name, err := parseRepo(repo)
if err != nil {
return err
}
cfg, err := config.Load()
if err != nil {
return err
}
client, err := api.NewClientFromConfig(cfg, "")
if err != nil {
return err
}
var method gitea.MergeStyle
switch strings.ToLower(mergeMethod) {
case "merge":
method = gitea.MergeStyleMerge
case "rebase":
method = gitea.MergeStyleRebase
case "squash":
method = gitea.MergeStyleSquash
default:
return fmt.Errorf("invalid merge method: %s", mergeMethod)
}
_, _, err = client.MergePullRequest(owner, name, prNumber, gitea.MergePullRequestOption{
Style: method,
})
if err != nil {
return fmt.Errorf("failed to merge pull request: %w", err)
}
fmt.Printf("Pull request #%d merged successfully\n", prNumber)
return nil
}
func parseRepo(repo string) (string, string, error) {
if repo == "" {
return "", "", fmt.Errorf("repository flag is required (use -R owner/name)")
}
parts := strings.Split(repo, "/")
if len(parts) != 2 {
return "", "", fmt.Errorf("invalid repository format: %s (expected: owner/name)", repo)
}
return parts[0], parts[1], nil
}

212
cmd/repo.go Normal file
View file

@ -0,0 +1,212 @@
package cmd
import (
"fmt"
"os"
"text/tabwriter"
"code.gitea.io/sdk/gitea"
"github.com/spf13/cobra"
"codeberg.org/romaintb/fgj/internal/api"
"codeberg.org/romaintb/fgj/internal/config"
)
var repoCmd = &cobra.Command{
Use: "repo",
Short: "Manage repositories",
Long: "View and manage repositories.",
}
var repoViewCmd = &cobra.Command{
Use: "view [owner/name]",
Short: "View repository details",
Long: "Display detailed information about a repository.",
Args: cobra.MaximumNArgs(1),
RunE: runRepoView,
}
var repoListCmd = &cobra.Command{
Use: "list",
Short: "List your repositories",
Long: "List repositories owned by the authenticated user.",
RunE: runRepoList,
}
var repoCloneCmd = &cobra.Command{
Use: "clone <owner/name>",
Short: "Clone a repository",
Long: "Clone a repository locally.",
Args: cobra.ExactArgs(1),
RunE: runRepoClone,
}
var repoForkCmd = &cobra.Command{
Use: "fork <owner/name>",
Short: "Fork a repository",
Long: "Create a fork of a repository.",
Args: cobra.ExactArgs(1),
RunE: runRepoFork,
}
func init() {
rootCmd.AddCommand(repoCmd)
repoCmd.AddCommand(repoViewCmd)
repoCmd.AddCommand(repoListCmd)
repoCmd.AddCommand(repoCloneCmd)
repoCmd.AddCommand(repoForkCmd)
repoCloneCmd.Flags().StringP("protocol", "p", "https", "Clone protocol: https or ssh")
}
func runRepoView(cmd *cobra.Command, args []string) error {
var repo string
if len(args) > 0 {
repo = args[0]
}
owner, name, err := parseRepo(repo)
if err != nil {
return err
}
cfg, err := config.Load()
if err != nil {
return err
}
client, err := api.NewClientFromConfig(cfg, "")
if err != nil {
return err
}
repository, _, err := client.GetRepo(owner, name)
if err != nil {
return fmt.Errorf("failed to get repository: %w", err)
}
fmt.Printf("Repository: %s/%s\n", repository.Owner.UserName, repository.Name)
fmt.Printf("Description: %s\n", repository.Description)
fmt.Printf("URL: %s\n", repository.HTMLURL)
fmt.Printf("Clone URL (HTTPS): %s\n", repository.CloneURL)
fmt.Printf("Clone URL (SSH): %s\n", repository.SSHURL)
fmt.Printf("Default Branch: %s\n", repository.DefaultBranch)
fmt.Printf("Stars: %d\n", repository.Stars)
fmt.Printf("Forks: %d\n", repository.Forks)
fmt.Printf("Open Issues: %d\n", repository.OpenIssues)
fmt.Printf("Private: %v\n", repository.Private)
fmt.Printf("Created: %s\n", repository.Created.Format("2006-01-02 15:04:05"))
fmt.Printf("Updated: %s\n", repository.Updated.Format("2006-01-02 15:04:05"))
return nil
}
func runRepoList(cmd *cobra.Command, args []string) error {
cfg, err := config.Load()
if err != nil {
return err
}
client, err := api.NewClientFromConfig(cfg, "")
if err != nil {
return err
}
user, _, err := client.GetMyUserInfo()
if err != nil {
return fmt.Errorf("failed to get user info: %w", err)
}
repos, _, err := client.ListUserRepos(user.UserName, gitea.ListReposOptions{})
if err != nil {
return fmt.Errorf("failed to list repositories: %w", err)
}
if len(repos) == 0 {
fmt.Println("No repositories found")
return nil
}
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
fmt.Fprintf(w, "NAME\tVISIBILITY\tDESCRIPTION\n")
for _, repo := range repos {
visibility := "public"
if repo.Private {
visibility = "private"
}
desc := repo.Description
if len(desc) > 50 {
desc = desc[:47] + "..."
}
fmt.Fprintf(w, "%s/%s\t%s\t%s\n", repo.Owner.UserName, repo.Name, visibility, desc)
}
w.Flush()
return nil
}
func runRepoClone(cmd *cobra.Command, args []string) error {
repo := args[0]
protocol, _ := cmd.Flags().GetString("protocol")
owner, name, err := parseRepo(repo)
if err != nil {
return err
}
cfg, err := config.Load()
if err != nil {
return err
}
client, err := api.NewClientFromConfig(cfg, "")
if err != nil {
return err
}
repository, _, err := client.GetRepo(owner, name)
if err != nil {
return fmt.Errorf("failed to get repository: %w", err)
}
var cloneURL string
if protocol == "ssh" {
cloneURL = repository.SSHURL
} else {
cloneURL = repository.CloneURL
}
fmt.Printf("Cloning %s/%s...\n", owner, name)
fmt.Printf("git clone %s\n", cloneURL)
return nil
}
func runRepoFork(cmd *cobra.Command, args []string) error {
repo := args[0]
owner, name, err := parseRepo(repo)
if err != nil {
return err
}
cfg, err := config.Load()
if err != nil {
return err
}
client, err := api.NewClientFromConfig(cfg, "")
if err != nil {
return err
}
fork, _, err := client.CreateFork(owner, name, gitea.CreateForkOption{})
if err != nil {
return fmt.Errorf("failed to fork repository: %w", err)
}
fmt.Printf("Repository forked successfully\n")
fmt.Printf("View at: %s\n", fork.HTMLURL)
fmt.Printf("Clone URL: %s\n", fork.CloneURL)
return nil
}

57
cmd/root.go Normal file
View file

@ -0,0 +1,57 @@
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
var cfgFile string
var rootCmd = &cobra.Command{
Use: "fgj",
Short: "Forgejo CLI tool - work seamlessly with Forgejo from the command line",
Long: `fgj is a command line tool for Forgejo instances (including Codeberg).
It brings pull requests, issues, and other Forgejo concepts to the terminal.`,
Version: "0.1.0",
}
func Execute() error {
return rootCmd.Execute()
}
func init() {
cobra.OnInitialize(initConfig)
rootCmd.PersistentFlags().StringVar(&cfgFile, "config", "", "config file (default is $HOME/.config/fgj/config.yaml)")
rootCmd.PersistentFlags().String("hostname", "", "Forgejo instance hostname")
viper.BindPFlag("hostname", rootCmd.PersistentFlags().Lookup("hostname"))
}
func initConfig() {
if cfgFile != "" {
viper.SetConfigFile(cfgFile)
} else {
home, err := os.UserHomeDir()
if err != nil {
fmt.Fprintln(os.Stderr, err)
os.Exit(1)
}
configDir := home + "/.config/fgj"
os.MkdirAll(configDir, 0755)
viper.AddConfigPath(configDir)
viper.SetConfigType("yaml")
viper.SetConfigName("config")
}
viper.AutomaticEnv()
viper.SetEnvPrefix("FGJ")
if err := viper.ReadInConfig(); err == nil {
// Config file found and successfully parsed
}
}