feat(cli): migrate to Cobra and add token get command#21
Conversation
- Migrate the CLI from the standard flag package to Cobra, introducing a root command, subcommands, and proper version handling - Refactor configuration and flag handling to use Cobra persistent flags and plain variables instead of pointer flags - Split configuration initialization to allow minimal setup for local-only commands like token get - Add a token command with a get subcommand to print stored access tokens, with optional JSON output - Add tests covering token get behavior, including JSON output and missing-token errors - Update dependencies to include Cobra and its related packages - Simplify string prefix handling in the TUI token storage display using newer standard library helpers Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Codecov Report❌ Patch coverage is
📢 Thoughts on this report? Let us know! |
Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Migrates the authgate-cli entrypoint to a Cobra-based command tree and adds a token get subcommand to read stored tokens without running the full auth flow.
Changes:
- Replace
flag-based CLI parsing with Cobra commands (authgate-cli,token get,version) and persistent flags registration. - Split config initialization into
initStoreConfig()(token store + client ID) andinitConfig()(full config + warnings/client setup). - Add
token get [--json]plus table-driven tests; simplify TUI storage-location parsing usingstrings.CutPrefix.
Reviewed changes
Copilot reviewed 7 out of 8 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
main.go |
Introduces Cobra root command execution and subcommands. |
config.go |
Reworks flag handling into Cobra persistent flags and splits store-only vs full config init. |
token_cmd.go |
Adds token / token get command and runTokenGet implementation. |
token_cmd_test.go |
Adds tests for runTokenGet plain, JSON, and missing-token cases. |
tui/token_view.go |
Refactors backend prefix parsing to strings.CutPrefix. |
go.mod |
Adds Cobra dependency. |
go.sum |
Adds checksums for Cobra’s dependency graph. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| cmd := &cobra.Command{ | ||
| Use: "get", | ||
| Short: "Print the stored access token", | ||
| RunE: func(cmd *cobra.Command, args []string) error { |
There was a problem hiding this comment.
token get currently accepts and ignores extra positional arguments. Consider setting Args: cobra.NoArgs (or similar) so authgate-cli token get foo fails fast with a helpful message instead of silently ignoring user input.
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| initStoreConfig() | ||
| code := runTokenGet(tokenStore, clientID, jsonOutput, os.Stdout, os.Stderr) | ||
| if code != 0 { | ||
| os.Exit(code) | ||
| } | ||
| return nil | ||
| }, |
There was a problem hiding this comment.
Like the root command, this subcommand calls os.Exit inside RunE, which prevents error propagation and complicates testing/embedding. Prefer returning an error (and optionally using cmd.SetOut/SetErr plus SilenceUsage) and letting main() decide the exit code.
| enc := json.NewEncoder(stdout) | ||
| enc.SetIndent("", " ") | ||
| _ = enc.Encode(out) | ||
| return 0 |
There was a problem hiding this comment.
runTokenGet ignores JSON encoding errors (_ = enc.Encode(out)). If stdout is closed or write fails, the command will still exit 0 and produce partial/invalid output. Handle the encode error and return a non-zero code (and write a useful message to stderr).
token_cmd.go
Outdated
| out := tokenGetOutput{ | ||
| AccessToken: tok.AccessToken, | ||
| RefreshToken: tok.RefreshToken, | ||
| TokenType: tok.TokenType, |
There was a problem hiding this comment.
--json output includes the refresh token and access token in cleartext. This is easy to leak via shell history, logs, or CI output and is inconsistent with the TUI’s masking approach. Consider omitting refresh_token from default JSON output (or gating it behind a separate explicit flag) and/or clearly warning in the help text that this prints secrets.
token_cmd_test.go
Outdated
| setup: func(s credstore.Store[credstore.Token]) { | ||
| _ = s.Save("test-id", credstore.Token{ | ||
| AccessToken: "my-access-token", | ||
| ExpiresAt: time.Now().Add(time.Hour), | ||
| ClientID: "test-id", | ||
| }) |
There was a problem hiding this comment.
The test setup ignores the return value from Store.Save. If the store fails to write (permissions/path issues), the test will continue and fail in a less clear way. Check the error and t.Fatalf to make the failure explicit.
token_cmd_test.go
Outdated
| setup: func(s credstore.Store[credstore.Token]) { | ||
| _ = s.Save("test-id", credstore.Token{ | ||
| AccessToken: "my-access-token", | ||
| ExpiresAt: time.Now().Add(time.Hour), | ||
| ClientID: "test-id", | ||
| }) |
There was a problem hiding this comment.
This test setup also ignores the return value from Store.Save. Checking the error and failing the test immediately will make write failures much easier to diagnose.
| if err := buildRootCmd().Execute(); err != nil { | ||
| os.Exit(1) | ||
| } | ||
| } | ||
|
|
||
| // Select UI manager based on environment detection | ||
| uiManager := tui.SelectManager() | ||
| func buildRootCmd() *cobra.Command { | ||
| rootCmd := &cobra.Command{ | ||
| Use: "authgate-cli", | ||
| Short: "OAuth 2.0 authentication CLI", | ||
| Version: getVersion(), | ||
| SilenceUsage: true, | ||
| SilenceErrors: true, | ||
| RunE: func(cmd *cobra.Command, args []string) error { |
There was a problem hiding this comment.
SilenceErrors: true combined with main() exiting on Execute() error without printing it will cause user-facing failures (e.g., unknown flag/invalid args) to exit with code 1 and no message. Either print the returned error in main() (to stderr) or stop silencing errors and let Cobra print them (keeping SilenceUsage: true is fine).
| RunE: func(cmd *cobra.Command, args []string) error { | ||
| initConfig() | ||
| uiManager := tui.SelectManager() | ||
| ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM) | ||
| exitCode := run(ctx, uiManager) | ||
| stop() | ||
| os.Exit(exitCode) | ||
| return nil | ||
| }, |
There was a problem hiding this comment.
Avoid calling os.Exit inside Cobra RunE. Exiting here bypasses Cobra's normal error/return flow and makes the command harder to test or reuse (and will skip any future defers added above). Prefer returning an error (or a sentinel error type carrying an exit code) and handle the process exit in main() after Execute() returns.
- Print Cobra errors to stderr in main() instead of silently exiting; introduce exitCodeError to propagate non-zero exit codes through Cobras error chain without printing a redundant message - Replace os.Exit calls inside RunE with exitCodeError returns so defers run correctly and commands remain testable - Add Args: cobra.NoArgs to token get to reject unexpected positional args - Handle JSON encode error in runTokenGet instead of ignoring it - Remove refresh_token from --json output to avoid leaking long-lived secrets into logs and shell history - Check Store.Save errors in tests with t.Fatalf for clearer diagnostics Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
Summary
flagpackage to Cobra, establishing a proper command tree (authgate-cli,token,token get,version)registerFlags()using Cobra persistent flagsinitStoreConfig()(token store + client ID only) andinitConfig()(full, callsinitStoreConfigas subroutine) — eliminates duplication and fixes a latent bug where both functions shared the sameconfigInitializedguardtoken get [--json]subcommand to print a stored access token without triggering the full auth flow or server-URL warningsrunTokenGetcovering plain output, JSON output, and missing-token error casestui/token_view.gostring prefix handling withstrings.CutPrefixTest plan
./bin/authgate-cli --versionprints version./bin/authgate-cli --helpshows all subcommands and global flags./bin/authgate-cli token get --helpshows--jsonflag./bin/authgate-cli token get --client-id=<uuid>prints access token (no HTTP warning)./bin/authgate-cli token get --client-id=<uuid> --jsonprints full JSON./bin/authgate-cli --client-id=<uuid>still triggers the normal auth flowmake testpassesmake lintpasses🤖 Generated with Claude Code