package forge import ( "errors" "fmt" "os" "os/exec" "strings" "github.com/cruldra/superpowers-vscode/cli/internal/repo" "github.com/cruldra/superpowers-vscode/cli/internal/tea" ) // Env is where [Open] gets credentials from; tests swap in canned answers, real runs use [SystemEnv]. type Env struct { // GitHubToken returns the token the GitHub CLI holds for `host`. GitHubToken func(host string) (string, error) // TeaLogin returns the tea login whose URL points at `host`, or `nil` when there is none. TeaLogin func(host string) (*tea.TeaLogin, error) // Getenv reads an environment variable. Getenv func(key string) string } // SystemEnv reads credentials from this machine: `gh auth token`, the tea config file and the process environment. func SystemEnv() Env { return Env{ GitHubToken: ghAuthToken, TeaLogin: func(host string) (*tea.TeaLogin, error) { path, err := tea.DefaultConfigPath() if err != nil { return nil, err } return tea.LoginForHost(path, host) }, Getenv: os.Getenv, } } // ghAuthToken asks the GitHub CLI for the token it stores for `host`. func ghAuthToken(host string) (string, error) { out, err := exec.Command("gh", "auth", "token", "--hostname", host).Output() if err != nil { var exitErr *exec.ExitError if errors.As(err, &exitErr) && len(exitErr.Stderr) > 0 { return "", fmt.Errorf("gh auth token --hostname %s 失败: %w: %s", host, err, strings.TrimSpace(string(exitErr.Stderr))) } return "", fmt.Errorf("gh auth token --hostname %s 失败: %w", host, err) } return string(out), nil } // Open connects to the repo at `o` on the forge its host names, picking credentials the way that forge expects. // // GitHub takes the token from `gh auth token`. Gitea takes the token of the tea login for the same host, and its // URL as the API root; with no such login, or one without a token, it falls back to `GITEA_TOKEN` and the origin's web root. func Open(o repo.Origin, env Env) (Forge, error) { if KindForHost(o.Host) == KindGitHub { token, err := env.GitHubToken(o.Host) if err != nil { return nil, fmt.Errorf("取 GitHub token 失败,请先运行 `gh auth login`: %w", err) } token = strings.TrimSpace(token) if token == "" { return nil, fmt.Errorf("gh 没有 %s 的 token,请先运行 `gh auth login`", o.Host) } return NewGitHub(GitHubAPIBaseURL, token, o.Owner, o.Repo), nil } login, err := env.TeaLogin(o.Host) if err != nil { return nil, err } baseURL, token := o.WebURL, "" if login != nil { baseURL, token = login.URL, login.Token } if token == "" { token = env.Getenv("GITEA_TOKEN") } if token == "" { return nil, fmt.Errorf("没有 %s 的 Gitea token:tea config 里没有该 host 的 login 或其 token 为空,GITEA_TOKEN 也没设置;请运行 `tea login add` 或设置 GITEA_TOKEN", o.Host) } return NewGitea(baseURL, token, o.Owner, o.Repo), nil }