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 needs a [tea.TeaLogin] for the same host and uses its URL as the API // root and its token; `GITEA_TOKEN` only fills in when that login has no token, so the variable never reaches a host the // user has not logged in to with tea. 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 } if login == nil { return nil, fmt.Errorf("tea config 里没有 %s 的 login,spx 不会把 Gitea token 发给 tea 没登录过的 host;请先运行 `tea login add` 登录这个 host", o.Host) } token := login.Token if token == "" { token = env.Getenv("GITEA_TOKEN") } if token == "" { return nil, fmt.Errorf("tea config 里 %s 的 login %q 没有 token,GITEA_TOKEN 也没设置;请重新运行 `tea login add` 或设置 GITEA_TOKEN", o.Host, login.Name) } return NewGitea(login.URL, token, o.Owner, o.Repo), nil }