A terminal client for Tangled, the git forge built on AT Protocol.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133package cli
import ( "errors" "fmt" "io" "os" "strings"
"github.com/alyraffauf/tg/internal/app" "github.com/spf13/cobra")
const ( // oauthCallbackHost is the loopback address the OAuth callback server // binds. The port is allocated dynamically per login (RFC 8252), not pinned // here, to avoid clashes with other local services. oauthCallbackHost = "127.0.0.1" oauthCallbackPath = "/callback")
func NewRoot(service *app.Service) *cobra.Command { rootCmd := &cobra.Command{ Use: "tg", Short: "A CLI for Tangled", SilenceErrors: true, SilenceUsage: true, } configureRoot(rootCmd)
auth := newAuthCommand(service) auth.AddCommand(newAuthLoginCommand(service), newAuthLogoutCommand(service), newAuthStatusCommand(service), newAuthTokenCommand(service), newAuthListCommand(service), newAuthSwitchCommand(service)) rootCmd.AddCommand(auth)
issue := newIssueCommand(service) issue.AddCommand(newIssueListCommand(service), newIssueViewCommand(service), newIssueCreateCommand(service), newIssueCommentCommand(service), newIssueCloseCommand(service), newIssueReopenCommand(service), newIssueEditCommand(service)) rootCmd.AddCommand(issue)
pull := newPRCommand(service) pull.AddCommand(newPRListCommand(service), newPRViewCommand(service), newPRCreateCommand(service), newPRCommentCommand(service), newPRDiffCommand(service), newPRCheckoutCommand(service), newPRCloseCommand(service), newPRReopenCommand(service), newPREditCommand(service), newPRMergeCommand(service)) rootCmd.AddCommand(pull)
repo := newRepoCommand(service) repo.AddCommand(newRepoViewCommand(service), newRepoCloneCommand(service), newRepoCreateCommand(service), newRepoListCommand(service), newRepoEditCommand(service), newRepoSetDefaultBranchCommand(service), newRepoDeleteCommand(service), newRepoForkCommand(service)) rootCmd.AddCommand(repo)
keys := newSSHKeyCommand(service) keys.AddCommand(newSSHKeyAddCommand(service), newSSHKeyListCommand(service), newSSHKeyDeleteCommand(service)) rootCmd.AddCommand(keys)
stringsCmd := newStringCommand(service) stringsCmd.AddCommand(newStringCreateCommand(service), newStringListCommand(service), newStringViewCommand(service), newStringDeleteCommand(service)) rootCmd.AddCommand(stringsCmd, newBrowseCommand(service), newCompletionCommand(service), newManCommand(service), newAPICommand(service)) return rootCmd}
func Execute() error { return ExecuteWith(os.Args[1:], os.Stdin, os.Stdout, os.Stderr)}
// ExecuteWith runs the CLI with explicit arguments and I/O streams.func ExecuteWith(arguments []string, input io.Reader, output, errorOutput io.Writer) error { flags, err := parseFlagSettings(arguments) if err != nil { renderError(errorOutput, err) return err } settings := loadConfig(flags, errorOutput) service := app.NewWithStreams(settings.Appview, output, errorOutput) service.SetAccount(settings.Account) root := NewRoot(service) root.SetArgs(arguments) root.SetIn(input) root.SetOut(output) root.SetErr(errorOutput) err = root.Execute() // A not-authenticated error from any service method is surfaced as the // familiar login hint, so individual commands don't each have to. if errors.Is(err, app.ErrNotAuthenticated) { err = fmt.Errorf("not logged in; run \"tg auth login\" first") } if err != nil { renderError(errorOutput, err) } return err}
func parseFlagSettings(arguments []string) (flagSettings, error) { var flags flagSettings for index := 0; index < len(arguments); index++ { if arguments[index] == "--" { break } argument := arguments[index] name, value, hasValue := strings.Cut(argument, "=") switch name { case "--config": flags.ConfigPath = flagValue(arguments, index, value, hasValue) flags.ConfigSet = true case "--appview": flags.Appview = flagValue(arguments, index, value, hasValue) flags.AppviewSet = true case "--account": flags.Account = flagValue(arguments, index, value, hasValue) flags.AccountSet = true } if !hasValue && (name == "--config" || name == "--appview" || name == "--account") { if index+1 >= len(arguments) || arguments[index+1] == "--" { return flagSettings{}, fmt.Errorf("flag %s requires a value", name) } index++ } } return flags, nil}
func flagValue(arguments []string, index int, value string, hasValue bool) string { if hasValue { return value } if index+1 < len(arguments) { return arguments[index+1] } return ""}
func configureRoot(rootCmd *cobra.Command) { rootCmd.PersistentFlags().String("config", "", "Path to config file (default: $XDG_CONFIG_HOME/tg/config.toml)") rootCmd.PersistentFlags().Bool("json", false, "Output in JSON format") rootCmd.PersistentFlags().String("appview", defaultAppview, "Appview host URL (overrides config file and TG_APPVIEW)") rootCmd.PersistentFlags().String("account", "", "Account handle or DID to use (overrides the active account and TG_ACCOUNT)")}