From bfd04aab3c8037a5b52a25018216ef42e21b87de Mon Sep 17 00:00:00 2001 From: Rian Stockbower Date: Tue, 19 May 2026 05:48:07 -0400 Subject: [PATCH] feat: rename whoami command to me for CLI consistency MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit slck used `whoami` for the identity command while every sibling CLI (cfl, jtk, gro, sfdc, nrcli) uses `me`. Rename to `me` and drop `whoami` entirely — no alias, no backwards compatibility. A root-command test asserts `me` is registered and `whoami` is gone with no alias fallback. Closes #159 --- CHANGELOG.md | 3 ++ README.md | 2 +- integration-tests.md | 6 +-- internal/cmd/{whoami/whoami.go => me/me.go} | 20 +++++----- .../{whoami/whoami_test.go => me/me_test.go} | 38 +++++++++---------- internal/cmd/root/root.go | 4 +- internal/cmd/root/root_test.go | 23 +++++++++++ internal/noleak/noleak_test.go | 10 ++--- 8 files changed, 66 insertions(+), 40 deletions(-) rename internal/cmd/{whoami/whoami.go => me/me.go} (87%) rename internal/cmd/{whoami/whoami_test.go => me/me_test.go} (83%) diff --git a/CHANGELOG.md b/CHANGELOG.md index fc967a8..7f50dae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,9 @@ - `messages thread` and `messages history` now surface file attachments in text output as `[file] (, ) — slck files download ` hints, giving readers a valid download command inline (#141) - `messages thread` and `messages history` now render Slack rich-text blocks (previously dropped), so messages pasted as tables, lists, or formatted content are visible in the default text output (#141) +### Changed +- Renamed the `whoami` command to `me` for consistency with sibling CLIs; `whoami` has been removed with no backwards-compatible alias (#159) + ### Fixed - `messages thread` text output no longer truncates at 80 characters (#134) - `messages thread` / `messages history` no longer silently drop file attachments or rich-text block content (#141) diff --git a/README.md b/README.md index 17a0b99..ef9290a 100644 --- a/README.md +++ b/README.md @@ -746,7 +746,7 @@ slck canvas delete F12345ABC ```bash # Show authenticated identity (bot, user, workspace) -slck whoami +slck me ``` ### Config diff --git a/integration-tests.md b/integration-tests.md index 757b41f..adeddba 100644 --- a/integration-tests.md +++ b/integration-tests.md @@ -573,12 +573,12 @@ These are read-only tests with no side effects. No additional scopes required (uses `auth.test` which works with any valid token). -### 9.1 Whoami +### 9.1 Me | Step | Command | Expected | |------|---------|----------| -| 1 | `slck whoami` | Shows Bot name/ID, Workspace name | -| 2 | `slck whoami -o json` | JSON with bot, workspace fields | +| 1 | `slck me` | Shows Bot name/ID, Workspace name | +| 2 | `slck me -o json` | JSON with bot, workspace fields | --- diff --git a/internal/cmd/whoami/whoami.go b/internal/cmd/me/me.go similarity index 87% rename from internal/cmd/whoami/whoami.go rename to internal/cmd/me/me.go index 3c47967..f88cbb0 100644 --- a/internal/cmd/whoami/whoami.go +++ b/internal/cmd/me/me.go @@ -1,4 +1,4 @@ -package whoami +package me import ( "github.com/spf13/cobra" @@ -7,10 +7,10 @@ import ( "github.com/open-cli-collective/slack-chat-api/internal/output" ) -type whoamiOptions struct{} +type meOptions struct{} -// WhoamiResult represents the authenticated identity info -type WhoamiResult struct { +// MeResult represents the authenticated identity info +type MeResult struct { Bot *BotInfo `json:"bot,omitempty"` User *UserInfo `json:"user,omitempty"` Workspace *WorkspaceInfo `json:"workspace"` @@ -34,25 +34,25 @@ type WorkspaceInfo struct { ID string `json:"id"` } -// NewCmd creates the whoami command +// NewCmd creates the me command func NewCmd() *cobra.Command { - opts := &whoamiOptions{} + opts := &meOptions{} return &cobra.Command{ - Use: "whoami", + Use: "me", Short: "Show the authenticated identity", Long: `Show the identity associated with the configured API tokens. This is a quick way to verify which bot or user account will be used for operations, and which workspace the tokens are associated with.`, RunE: func(cmd *cobra.Command, args []string) error { - return runWhoami(opts, nil, nil) + return runMe(opts, nil, nil) }, } } -func runWhoami(opts *whoamiOptions, botClient *client.Client, userClient *client.Client) error { - result := &WhoamiResult{} +func runMe(opts *meOptions, botClient *client.Client, userClient *client.Client) error { + result := &MeResult{} var workspace *WorkspaceInfo // Test bot token diff --git a/internal/cmd/whoami/whoami_test.go b/internal/cmd/me/me_test.go similarity index 83% rename from internal/cmd/whoami/whoami_test.go rename to internal/cmd/me/me_test.go index ffe700b..5969cb9 100644 --- a/internal/cmd/whoami/whoami_test.go +++ b/internal/cmd/me/me_test.go @@ -1,4 +1,4 @@ -package whoami +package me import ( "encoding/json" @@ -13,7 +13,7 @@ import ( "github.com/open-cli-collective/slack-chat-api/internal/testutil" ) -func TestRunWhoami_BotTokenOnly(t *testing.T) { +func TestRunMe_BotTokenOnly(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/auth.test", r.URL.Path) w.Header().Set("Content-Type", "application/json") @@ -29,14 +29,14 @@ func TestRunWhoami_BotTokenOnly(t *testing.T) { defer server.Close() botClient := client.NewWithConfig(server.URL, "xoxb-test", nil) - opts := &whoamiOptions{} + opts := &meOptions{} // Pass bot client, nil for user client - err := runWhoami(opts, botClient, nil) + err := runMe(opts, botClient, nil) require.NoError(t, err) } -func TestRunWhoami_UserTokenOnly(t *testing.T) { +func TestRunMe_UserTokenOnly(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { assert.Equal(t, "/auth.test", r.URL.Path) w.Header().Set("Content-Type", "application/json") @@ -51,14 +51,14 @@ func TestRunWhoami_UserTokenOnly(t *testing.T) { defer server.Close() userClient := client.NewWithConfig(server.URL, "xoxp-test", nil) - opts := &whoamiOptions{} + opts := &meOptions{} // Pass nil for bot client, user client provided - err := runWhoami(opts, nil, userClient) + err := runMe(opts, nil, userClient) require.NoError(t, err) } -func TestRunWhoami_BothTokens(t *testing.T) { +func TestRunMe_BothTokens(t *testing.T) { botServer := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]interface{}{ @@ -86,24 +86,24 @@ func TestRunWhoami_BothTokens(t *testing.T) { botClient := client.NewWithConfig(botServer.URL, "xoxb-test", nil) userClient := client.NewWithConfig(userServer.URL, "xoxp-test", nil) - opts := &whoamiOptions{} + opts := &meOptions{} - err := runWhoami(opts, botClient, userClient) + err := runMe(opts, botClient, userClient) require.NoError(t, err) } -func TestRunWhoami_NoTokens(t *testing.T) { +func TestRunMe_NoTokens(t *testing.T) { // Hermetic empty keyring (file backend, temp HOME) — no real keychain. testutil.Setup(t) - opts := &whoamiOptions{} + opts := &meOptions{} // Pass nil clients to trigger token lookup - err := runWhoami(opts, nil, nil) + err := runMe(opts, nil, nil) require.NoError(t, err) } -func TestRunWhoami_AuthFailed(t *testing.T) { +func TestRunMe_AuthFailed(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") _ = json.NewEncoder(w).Encode(map[string]interface{}{ @@ -114,14 +114,14 @@ func TestRunWhoami_AuthFailed(t *testing.T) { defer server.Close() botClient := client.NewWithConfig(server.URL, "bad-token", nil) - opts := &whoamiOptions{} + opts := &meOptions{} // Auth fails, but function should handle gracefully - err := runWhoami(opts, botClient, nil) + err := runMe(opts, botClient, nil) require.NoError(t, err) } -func TestRunWhoami_BotWithoutBotID(t *testing.T) { +func TestRunMe_BotWithoutBotID(t *testing.T) { // Some tokens may not have a bot_id server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", "application/json") @@ -136,8 +136,8 @@ func TestRunWhoami_BotWithoutBotID(t *testing.T) { defer server.Close() botClient := client.NewWithConfig(server.URL, "xoxb-test", nil) - opts := &whoamiOptions{} + opts := &meOptions{} - err := runWhoami(opts, botClient, nil) + err := runMe(opts, botClient, nil) require.NoError(t, err) } diff --git a/internal/cmd/root/root.go b/internal/cmd/root/root.go index bfbe777..cb15e55 100644 --- a/internal/cmd/root/root.go +++ b/internal/cmd/root/root.go @@ -13,11 +13,11 @@ import ( "github.com/open-cli-collective/slack-chat-api/internal/cmd/emoji" "github.com/open-cli-collective/slack-chat-api/internal/cmd/files" "github.com/open-cli-collective/slack-chat-api/internal/cmd/initcmd" + "github.com/open-cli-collective/slack-chat-api/internal/cmd/me" "github.com/open-cli-collective/slack-chat-api/internal/cmd/messages" "github.com/open-cli-collective/slack-chat-api/internal/cmd/search" "github.com/open-cli-collective/slack-chat-api/internal/cmd/setcred" "github.com/open-cli-collective/slack-chat-api/internal/cmd/users" - "github.com/open-cli-collective/slack-chat-api/internal/cmd/whoami" "github.com/open-cli-collective/slack-chat-api/internal/cmd/workspace" "github.com/open-cli-collective/slack-chat-api/internal/output" "github.com/open-cli-collective/slack-chat-api/internal/version" @@ -96,7 +96,7 @@ func init() { rootCmd.AddCommand(messages.NewCmd()) rootCmd.AddCommand(search.NewCmd()) rootCmd.AddCommand(workspace.NewCmd()) - rootCmd.AddCommand(whoami.NewCmd()) + rootCmd.AddCommand(me.NewCmd()) rootCmd.AddCommand(config.NewCmd()) rootCmd.AddCommand(emoji.NewCmd()) rootCmd.AddCommand(files.NewCmd()) diff --git a/internal/cmd/root/root_test.go b/internal/cmd/root/root_test.go index 991ed74..d2db9ed 100644 --- a/internal/cmd/root/root_test.go +++ b/internal/cmd/root/root_test.go @@ -69,3 +69,26 @@ func TestNoFlagsDefaultsBotMode(t *testing.T) { t.Errorf("default should resolve the bot token; got %q", msg) } } + +// TestRootRegistersMeNotWhoami locks the issue #159 contract: the identity +// command is registered as `me` (matching sibling CLIs) and `whoami` is gone +// with no alias fallback (no backwards compatibility). +func TestRootRegistersMeNotWhoami(t *testing.T) { + var meCount int + for _, c := range rootCmd.Commands() { + if c.Name() == "whoami" { + t.Errorf("whoami command must not be registered (issue #159)") + } + if c.Name() == "me" { + meCount++ + } + for _, a := range c.Aliases { + if a == "whoami" { + t.Errorf("command %q must not alias whoami (no backwards compatibility)", c.Name()) + } + } + } + if meCount != 1 { + t.Errorf("expected exactly one `me` command, found %d", meCount) + } +} diff --git a/internal/noleak/noleak_test.go b/internal/noleak/noleak_test.go index 1a5317d..ba571f1 100644 --- a/internal/noleak/noleak_test.go +++ b/internal/noleak/noleak_test.go @@ -21,9 +21,9 @@ import ( "github.com/open-cli-collective/slack-chat-api/internal/cmd/channels" cfgcmd "github.com/open-cli-collective/slack-chat-api/internal/cmd/config" initcmd "github.com/open-cli-collective/slack-chat-api/internal/cmd/initcmd" + "github.com/open-cli-collective/slack-chat-api/internal/cmd/me" "github.com/open-cli-collective/slack-chat-api/internal/cmd/messages" "github.com/open-cli-collective/slack-chat-api/internal/cmd/setcred" - "github.com/open-cli-collective/slack-chat-api/internal/cmd/whoami" "github.com/open-cli-collective/slack-chat-api/internal/keychain" "github.com/open-cli-collective/slack-chat-api/internal/output" "github.com/open-cli-collective/slack-chat-api/internal/testutil" @@ -133,14 +133,14 @@ func TestNoLeak_InitFromEnv(t *testing.T) { assertNoLeak(t, "init --bot-token-from-env", out) } -func TestNoLeak_Whoami(t *testing.T) { +func TestNoLeak_Me(t *testing.T) { testutil.Setup(t) seed(t) - c := whoami.NewCmd() + c := me.NewCmd() c.SetArgs([]string{}) - // whoami will fail to reach Slack (hermetic env); we only care that no + // me will fail to reach Slack (hermetic env); we only care that no // output channel echoes the seeded token. - assertNoLeak(t, "whoami", captureAll(t, "", func() { _ = c.Execute() })) + assertNoLeak(t, "me", captureAll(t, "", func() { _ = c.Execute() })) } func TestNoLeak_ConfigTest(t *testing.T) {