From d09300eadf7e5e354210778e0b4f3ac0f5a59c24 Mon Sep 17 00:00:00 2001 From: Kacper Sawicki Date: Thu, 29 Jan 2026 16:06:17 +0100 Subject: [PATCH] feat(cli): add 'coder login token' command to print session token (#21627) Adds a new subcommand to print the current session token for use in scripts and automation, similar to `gh auth token`. ## Usage ```bash CODER_SESSION_TOKEN=$(coder login token) ``` Fixes #21515 --- cli/login.go | 29 ++++++++++++++++++++ cli/login_test.go | 28 +++++++++++++++++++ cli/testdata/coder_login_--help.golden | 3 ++ cli/testdata/coder_login_token_--help.golden | 11 ++++++++ docs/admin/users/sessions-tokens.md | 15 ++++++++++ docs/ai-coder/ai-bridge/client-config.md | 10 +++++++ docs/manifest.json | 5 ++++ docs/reference/cli/login.md | 6 ++++ docs/reference/cli/login_token.md | 16 +++++++++++ 9 files changed, 123 insertions(+) create mode 100644 cli/testdata/coder_login_token_--help.golden create mode 100644 docs/reference/cli/login_token.md diff --git a/cli/login.go b/cli/login.go index 2871876b9f..297fbedf3e 100644 --- a/cli/login.go +++ b/cli/login.go @@ -462,9 +462,38 @@ func (r *RootCmd) login() *serpent.Command { Value: serpent.BoolOf(&useTokenForSession), }, } + cmd.Children = []*serpent.Command{ + r.loginToken(), + } return cmd } +func (r *RootCmd) loginToken() *serpent.Command { + return &serpent.Command{ + Use: "token", + Short: "Print the current session token", + Long: "Print the session token for use in scripts and automation.", + Middleware: serpent.RequireNArgs(0), + Handler: func(inv *serpent.Invocation) error { + tok, err := r.ensureTokenBackend().Read(r.clientURL) + if err != nil { + if xerrors.Is(err, os.ErrNotExist) { + return xerrors.New("no session token found - run 'coder login' first") + } + if xerrors.Is(err, sessionstore.ErrNotImplemented) { + return errKeyringNotSupported + } + return xerrors.Errorf("read session token: %w", err) + } + if tok == "" { + return xerrors.New("no session token found - run 'coder login' first") + } + _, err = fmt.Fprintln(inv.Stdout, tok) + return err + }, + } +} + // isWSL determines if coder-cli is running within Windows Subsystem for Linux func isWSL() (bool, error) { if runtime.GOOS == goosDarwin || runtime.GOOS == goosWindows { diff --git a/cli/login_test.go b/cli/login_test.go index 1616481da1..4125519e1f 100644 --- a/cli/login_test.go +++ b/cli/login_test.go @@ -537,3 +537,31 @@ func TestLogin(t *testing.T) { require.Equal(t, selected, first.OrganizationID.String()) }) } + +func TestLoginToken(t *testing.T) { + t.Parallel() + + t.Run("PrintsToken", func(t *testing.T) { + t.Parallel() + client := coderdtest.New(t, nil) + coderdtest.CreateFirstUser(t, client) + + inv, root := clitest.New(t, "login", "token", "--url", client.URL.String()) + clitest.SetupConfig(t, client, root) + pty := ptytest.New(t).Attach(inv) + ctx := testutil.Context(t, testutil.WaitShort) + err := inv.WithContext(ctx).Run() + require.NoError(t, err) + + pty.ExpectMatch(client.SessionToken()) + }) + + t.Run("NoTokenStored", func(t *testing.T) { + t.Parallel() + inv, _ := clitest.New(t, "login", "token") + ctx := testutil.Context(t, testutil.WaitShort) + err := inv.WithContext(ctx).Run() + require.Error(t, err) + require.Contains(t, err.Error(), "no session token found") + }) +} diff --git a/cli/testdata/coder_login_--help.golden b/cli/testdata/coder_login_--help.golden index 96129d8a55..62fc07378b 100644 --- a/cli/testdata/coder_login_--help.golden +++ b/cli/testdata/coder_login_--help.golden @@ -9,6 +9,9 @@ USAGE: macOS and Windows and a plain text file on Linux. Use the --use-keyring flag or CODER_USE_KEYRING environment variable to change the storage mechanism. +SUBCOMMANDS: + token Print the current session token + OPTIONS: --first-user-email string, $CODER_FIRST_USER_EMAIL Specifies an email address to use if creating the first user for the diff --git a/cli/testdata/coder_login_token_--help.golden b/cli/testdata/coder_login_token_--help.golden new file mode 100644 index 0000000000..5b8c8b8884 --- /dev/null +++ b/cli/testdata/coder_login_token_--help.golden @@ -0,0 +1,11 @@ +coder v0.0.0-devel + +USAGE: + coder login token + + Print the current session token + + Print the session token for use in scripts and automation. + +——— +Run `coder --help` for a list of global options. diff --git a/docs/admin/users/sessions-tokens.md b/docs/admin/users/sessions-tokens.md index 901f4ae038..82da4b845c 100644 --- a/docs/admin/users/sessions-tokens.md +++ b/docs/admin/users/sessions-tokens.md @@ -9,6 +9,21 @@ The [Coder CLI](../../install/cli.md) and token to authenticate. To generate a short-lived session token on behalf of your account, visit the following URL: `https://coder.example.com/cli-auth` +### Retrieve the current session token + +If you're already logged in with the CLI, you can retrieve your current session +token for use in scripts and automation: + +```sh +coder login token +``` + +This is useful for passing your session token to other tools: + +```sh +export CODER_SESSION_TOKEN=$(coder login token) +``` + ### Session Durations By default, sessions last 24 hours and are automatically refreshed. You can diff --git a/docs/ai-coder/ai-bridge/client-config.md b/docs/ai-coder/ai-bridge/client-config.md index 1747df8980..dbbb72cada 100644 --- a/docs/ai-coder/ai-bridge/client-config.md +++ b/docs/ai-coder/ai-bridge/client-config.md @@ -22,6 +22,16 @@ Instead of distributing provider-specific API keys (OpenAI/Anthropic keys) to us Again, the exact environment variable or setting naming may differ from tool to tool; consult your tool's documentation. +### Retrieving your session token + +If you're logged in with the Coder CLI, you can retrieve your current session +token using [`coder login token`](../../reference/cli/login_token.md): + +```sh +export ANTHROPIC_API_KEY=$(coder login token) +export ANTHROPIC_BASE_URL="https://coder.example.com/api/v2/aibridge/anthropic" +``` + ## Configuring In-Workspace Tools AI coding tools running inside a Coder workspace, such as IDE extensions, can be configured to use AI Bridge. diff --git a/docs/manifest.json b/docs/manifest.json index a18383a467..4c08695115 100644 --- a/docs/manifest.json +++ b/docs/manifest.json @@ -1487,6 +1487,11 @@ "description": "Authenticate with Coder deployment", "path": "reference/cli/login.md" }, + { + "title": "login token", + "description": "Print the current session token", + "path": "reference/cli/login_token.md" + }, { "title": "logout", "description": "Unauthenticate your local session", diff --git a/docs/reference/cli/login.md b/docs/reference/cli/login.md index 1371ebae1b..4a0eb2eb57 100644 --- a/docs/reference/cli/login.md +++ b/docs/reference/cli/login.md @@ -15,6 +15,12 @@ coder login [flags] [] By default, the session token is stored in the operating system keyring on macOS and Windows and a plain text file on Linux. Use the --use-keyring flag or CODER_USE_KEYRING environment variable to change the storage mechanism. ``` +## Subcommands + +| Name | Purpose | +|----------------------------------------|---------------------------------| +| [token](./login_token.md) | Print the current session token | + ## Options ### --first-user-email diff --git a/docs/reference/cli/login_token.md b/docs/reference/cli/login_token.md new file mode 100644 index 0000000000..70f7457e54 --- /dev/null +++ b/docs/reference/cli/login_token.md @@ -0,0 +1,16 @@ + +# login token + +Print the current session token + +## Usage + +```console +coder login token +``` + +## Description + +```console +Print the session token for use in scripts and automation. +```