From 126b87a2a95081fbf96b47d75854f31372880f7f Mon Sep 17 00:00:00 2001 From: piekstra Date: Wed, 12 Aug 2026 14:46:46 -0400 Subject: [PATCH 1/2] feat: profiles list/use, attributed auth errors, init target naming, mail list (google-cli-common v0.3.0) Bumps google-cli-common v0.2.0 -> v0.3.0 and registers the new profiles command group. This delivers the credential/profile UX overhaul driven by a first-hand mis-diagnosis (a stale default-profile token read as 'gro is dead'): - gro profiles list [--check] - every stored profile, its account email, an active marker (with where the selection came from), and per-profile live token health. No more dumping the OS keychain to discover accounts. - gro profiles use - deliberate, visible switching of the active binding in config.yml. - Auth errors now name the failing credential ref, its selection source, and that other profiles may be unaffected (with the profiles-list hint). - gro init announces which profile/account it will (re)authenticate before any prompt or write; gro init --profile adds a NEW account without touching the active profile's token. - gro mail list - sibling symmetry with calendar/contacts/drive list (the 'unknown flag: --max' confusion was a missing command, not a flag divergence). - gro config show names the credential-ref source. README documents the new commands; root test pins the registered command. --- README.md | 17 +++++++++++++++++ go.mod | 4 ++-- go.sum | 8 ++++---- internal/cmd/root/root.go | 2 ++ internal/cmd/root/root_test.go | 1 + 5 files changed, 26 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 825eaff..e31fb53 100644 --- a/README.md +++ b/README.md @@ -277,6 +277,11 @@ gro -v All Gmail commands are under `gro mail`: ```bash +# List recent inbox messages +gro mail list +gro mail list --max 25 +gro mail list --ids # Output IDs only (for piping) + # Search messages gro mail search "is:unread" gro mail search "from:someone@example.com" --max 20 @@ -549,6 +554,18 @@ removed without removing anything. Usage: gro config clear [--all] [--dry-run] ``` +### gro mail list + +List the most recent inbox messages (equivalent to `gro mail search "in:inbox"`). + +``` +Usage: gro mail list [flags] + +Flags: + -m, --max int Maximum number of results (default 10) + --ids Output only message IDs (one per line, for piping) +``` + ### gro mail search Search for Gmail messages using Gmail's search syntax. diff --git a/go.mod b/go.mod index 6250355..804b112 100644 --- a/go.mod +++ b/go.mod @@ -5,7 +5,7 @@ go 1.26.4 require ( github.com/charmbracelet/lipgloss v1.1.0 github.com/muesli/termenv v0.16.0 - github.com/open-cli-collective/cli-common v0.4.1 + github.com/open-cli-collective/cli-common v0.5.0 github.com/spf13/cobra v1.10.2 golang.org/x/oauth2 v0.36.0 google.golang.org/api v0.289.0 @@ -66,7 +66,7 @@ require ( github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6 // indirect github.com/muesli/cancelreader v0.2.2 // indirect github.com/noamcohen97/touchid-go v0.3.0 // indirect - github.com/open-cli-collective/google-cli-common v0.2.0 + github.com/open-cli-collective/google-cli-common v0.3.0 github.com/opentracing/opentracing-go v1.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rivo/uniseg v0.4.7 // indirect diff --git a/go.sum b/go.sum index 594f62d..16a1cdb 100644 --- a/go.sum +++ b/go.sum @@ -128,10 +128,10 @@ github.com/muesli/termenv v0.16.0/go.mod h1:ZRfOIKPFDYQoDFF4Olj7/QJbW60Ol/kL1pU3 github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/noamcohen97/touchid-go v0.3.0 h1:fcXxVCizysD7KHRR6hrURt3nyNIs5JBGSbOIidD/3wo= github.com/noamcohen97/touchid-go v0.3.0/go.mod h1:X9MRNIBGEmPqwpDm1G3fQOAQX7fwBlhzUbnkDTxuta0= -github.com/open-cli-collective/cli-common v0.4.1 h1:6oNaUVmMtXWi4pJn1X+W6KZVDP/WAJwbyZUyxYpGdbQ= -github.com/open-cli-collective/cli-common v0.4.1/go.mod h1:3AzjCT0V8xgslHlGi1+rUkcV+Vf5wONGwISQkufLZLQ= -github.com/open-cli-collective/google-cli-common v0.2.0 h1:yJ9x1gijQ0rgPTvhuCg74a19XhSteiGWkCcqhcVknLc= -github.com/open-cli-collective/google-cli-common v0.2.0/go.mod h1:EgRDUOb7s6cRa8PncoUrablPL3OcEDjTU8TByE7tsqs= +github.com/open-cli-collective/cli-common v0.5.0 h1:HA+ctKhVMugvVPf3zoke3ewhQvs6ur4H4Z7q7RJewyU= +github.com/open-cli-collective/cli-common v0.5.0/go.mod h1:3AzjCT0V8xgslHlGi1+rUkcV+Vf5wONGwISQkufLZLQ= +github.com/open-cli-collective/google-cli-common v0.3.0 h1:iIxFT8NJXKIfF53Lk940pRofwFfXQg11z10GLOEyXZs= +github.com/open-cli-collective/google-cli-common v0.3.0/go.mod h1:lPrPo5pD9s0eyU8YtTXjsXE8DszelDqGKh35SPUW1Nc= github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs= github.com/opentracing/opentracing-go v1.2.0/go.mod h1:GxEUsuufX4nBwe+T+Wl9TAgYrxe9dPLANfrWvHYVTgc= github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ= diff --git a/internal/cmd/root/root.go b/internal/cmd/root/root.go index 0f12e79..ce45280 100644 --- a/internal/cmd/root/root.go +++ b/internal/cmd/root/root.go @@ -11,6 +11,7 @@ import ( config "github.com/open-cli-collective/google-cli-common/configcmd" "github.com/open-cli-collective/google-cli-common/initcmd" mail "github.com/open-cli-collective/google-cli-common/mailcmd" + "github.com/open-cli-collective/google-cli-common/profilescmd" "github.com/open-cli-collective/google-cli-common/refreshcmd" "github.com/open-cli-collective/google-cli-common/rootutil" "github.com/open-cli-collective/google-cli-common/setcred" @@ -86,6 +87,7 @@ func init() { // Register commands rootCmd.AddCommand(initcmd.NewCommand()) rootCmd.AddCommand(config.NewCommand()) + rootCmd.AddCommand(profilescmd.NewCommand()) rootCmd.AddCommand(setcred.NewCmd()) rootCmd.AddCommand(me.NewCommand()) rootCmd.AddCommand(mail.NewCommand()) diff --git a/internal/cmd/root/root_test.go b/internal/cmd/root/root_test.go index 31e7646..87008bc 100644 --- a/internal/cmd/root/root_test.go +++ b/internal/cmd/root/root_test.go @@ -47,6 +47,7 @@ func TestRootCommand(t *testing.T) { } testutil.SliceContains(t, names, "init") testutil.SliceContains(t, names, "config") + testutil.SliceContains(t, names, "profiles") testutil.SliceContains(t, names, "mail") testutil.SliceContains(t, names, "calendar") testutil.SliceContains(t, names, "contacts") From 03a6b6e3410a96b8190188451880c928b5819b67 Mon Sep 17 00:00:00 2001 From: piekstra Date: Wed, 12 Aug 2026 14:56:10 -0400 Subject: [PATCH 2/2] docs: document profiles commands and init --profile in the command reference --- README.md | 45 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 45 insertions(+) diff --git a/README.md b/README.md index e31fb53..b3859e5 100644 --- a/README.md +++ b/README.md @@ -149,6 +149,14 @@ After creating the OAuth credentials in step 2 (or 3), run: gro init ``` +Before touching anything, init names the profile (and account) it is about to +(re)authenticate. To add ANOTHER Google account later without overwriting the +first one, authenticate it as a new named profile: + +```bash +gro init --profile work +``` + The wizard will: 1. **Ingest credentials.json.** Pick one of three options when prompted: @@ -255,6 +263,16 @@ gro me --id gro me --extended +# List credential profiles (accounts) and their token state +gro profiles list +gro profiles list --check # live-verify each token + +# Switch the active profile +gro profiles use work + +# Add another account as a new named profile +gro init --profile work + # Check configuration status gro config show @@ -514,6 +532,7 @@ Flags: --credentials-file string Path to a downloaded OAuth client JSON (bypasses the wizard) --no-browser Don't try to open the consent URL in a browser --no-verify Skip connectivity verification after setup + --profile string Authenticate a NEW named profile (stored as google-readonly/) without touching the active profile ``` ### gro me @@ -528,6 +547,32 @@ Flags: --extended Add granted scopes, token expiry, and storage backend ``` +### gro profiles list + +List every credential profile stored in the OS keyring: the account email each +holds (as last verified), whether a token is present, and which profile is +active (marked `*`, with where the selection came from). + +``` +Usage: gro profiles list [flags] + +Flags: + -j, --json Emit JSON + --check Live-verify each profile's token against the API + (ok / expired or revoked / error, one API call per profile) +``` + +### gro profiles use + +Switch the active profile (writes `credential_ref` in `config.yml`). Accepts a +bare profile name or a full `google-readonly/` ref. Never touches +stored tokens; switching to a profile with no token warns and points at +`gro init`. + +``` +Usage: gro profiles use +``` + ### gro config show Display current configuration status including credentials and token.