From 0dbe0442f07cfd84081388c1d45d83af8e3e23bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Pawe=C5=82=20Banaszewski?= Date: Thu, 18 Jun 2026 11:08:17 +0200 Subject: [PATCH] feat: add CLI commands to manage AI Gateway keys (#25689) Adds `coder ai-gateway keys` commands: * `create ` creates key with given name * `list` lists existing keys (alias `ls`) * `delete ` removes key matching by name or key id, name has priority (alias `rm`) --- codersdk/aigatewaykeys.go | 10 +- docs/reference/cli/ai-gateway.md | 16 ++ docs/reference/cli/ai-gateway_keys.md | 18 ++ docs/reference/cli/ai-gateway_keys_create.md | 10 + docs/reference/cli/ai-gateway_keys_delete.md | 24 ++ docs/reference/cli/ai-gateway_keys_list.md | 34 +++ docs/reference/cli/index.md | 1 + enterprise/cli/aigateway.go | 185 ++++++++++++++ enterprise/cli/aigateway_test.go | 228 ++++++++++++++++++ enterprise/cli/root.go | 1 + enterprise/cli/testdata/coder_--help.golden | 1 + .../testdata/coder_ai-gateway_--help.golden | 12 + .../coder_ai-gateway_keys_--help.golden | 14 ++ ...coder_ai-gateway_keys_create_--help.golden | 9 + ...coder_ai-gateway_keys_delete_--help.golden | 15 ++ .../coder_ai-gateway_keys_list_--help.golden | 18 ++ 16 files changed, 591 insertions(+), 5 deletions(-) create mode 100644 docs/reference/cli/ai-gateway.md create mode 100644 docs/reference/cli/ai-gateway_keys.md create mode 100644 docs/reference/cli/ai-gateway_keys_create.md create mode 100644 docs/reference/cli/ai-gateway_keys_delete.md create mode 100644 docs/reference/cli/ai-gateway_keys_list.md create mode 100644 enterprise/cli/aigateway.go create mode 100644 enterprise/cli/aigateway_test.go create mode 100644 enterprise/cli/testdata/coder_ai-gateway_--help.golden create mode 100644 enterprise/cli/testdata/coder_ai-gateway_keys_--help.golden create mode 100644 enterprise/cli/testdata/coder_ai-gateway_keys_create_--help.golden create mode 100644 enterprise/cli/testdata/coder_ai-gateway_keys_delete_--help.golden create mode 100644 enterprise/cli/testdata/coder_ai-gateway_keys_list_--help.golden diff --git a/codersdk/aigatewaykeys.go b/codersdk/aigatewaykeys.go index 7c4eb1c7a1..cce57dafad 100644 --- a/codersdk/aigatewaykeys.go +++ b/codersdk/aigatewaykeys.go @@ -14,11 +14,11 @@ import ( // AIGatewayKey is a shared secret used by a standalone AI Gateway // to authenticate into coderd. type AIGatewayKey struct { - ID uuid.UUID `json:"id" format:"uuid"` - Name string `json:"name"` - KeyPrefix string `json:"key_prefix"` - CreatedAt time.Time `json:"created_at" format:"date-time"` - LastUsedAt *time.Time `json:"last_used_at,omitempty" format:"date-time"` + ID uuid.UUID `json:"id" table:"id" format:"uuid"` + Name string `json:"name" table:"name,default_sort"` + KeyPrefix string `json:"key_prefix" table:"key prefix"` + CreatedAt time.Time `json:"created_at" table:"created at" format:"date-time"` + LastUsedAt *time.Time `json:"last_used_at,omitempty" table:"last used at" format:"date-time"` } // CreateAIGatewayKeyRequest requests a new AI Gateway key. diff --git a/docs/reference/cli/ai-gateway.md b/docs/reference/cli/ai-gateway.md new file mode 100644 index 0000000000..7666d36282 --- /dev/null +++ b/docs/reference/cli/ai-gateway.md @@ -0,0 +1,16 @@ + +# ai-gateway + +Manage AI Gateway + +## Usage + +```console +coder ai-gateway +``` + +## Subcommands + +| Name | Purpose | +|-------------------------------------------|------------------------| +| [keys](./ai-gateway_keys.md) | Manage AI Gateway keys | diff --git a/docs/reference/cli/ai-gateway_keys.md b/docs/reference/cli/ai-gateway_keys.md new file mode 100644 index 0000000000..c2e258ac6d --- /dev/null +++ b/docs/reference/cli/ai-gateway_keys.md @@ -0,0 +1,18 @@ + +# ai-gateway keys + +Manage AI Gateway keys + +## Usage + +```console +coder ai-gateway keys +``` + +## Subcommands + +| Name | Purpose | +|----------------------------------------------------|--------------------------| +| [create](./ai-gateway_keys_create.md) | Create an AI Gateway key | +| [delete](./ai-gateway_keys_delete.md) | Delete an AI Gateway key | +| [list](./ai-gateway_keys_list.md) | List AI Gateway keys | diff --git a/docs/reference/cli/ai-gateway_keys_create.md b/docs/reference/cli/ai-gateway_keys_create.md new file mode 100644 index 0000000000..7f6b967684 --- /dev/null +++ b/docs/reference/cli/ai-gateway_keys_create.md @@ -0,0 +1,10 @@ + +# ai-gateway keys create + +Create an AI Gateway key + +## Usage + +```console +coder ai-gateway keys create +``` diff --git a/docs/reference/cli/ai-gateway_keys_delete.md b/docs/reference/cli/ai-gateway_keys_delete.md new file mode 100644 index 0000000000..eee21bc0a2 --- /dev/null +++ b/docs/reference/cli/ai-gateway_keys_delete.md @@ -0,0 +1,24 @@ + +# ai-gateway keys delete + +Delete an AI Gateway key + +Aliases: + +* rm + +## Usage + +```console +coder ai-gateway keys delete [flags] +``` + +## Options + +### -y, --yes + +| | | +|------|-------------------| +| Type | bool | + +Bypass confirmation prompts. diff --git a/docs/reference/cli/ai-gateway_keys_list.md b/docs/reference/cli/ai-gateway_keys_list.md new file mode 100644 index 0000000000..39f1ffcebe --- /dev/null +++ b/docs/reference/cli/ai-gateway_keys_list.md @@ -0,0 +1,34 @@ + +# ai-gateway keys list + +List AI Gateway keys + +Aliases: + +* ls + +## Usage + +```console +coder ai-gateway keys list [flags] +``` + +## Options + +### -c, --column + +| | | +|---------|---------------------------------------------------------------| +| Type | [id\|name\|key prefix\|created at\|last used at] | +| Default | id,name,key prefix,last used at,created at | + +Columns to display in table output. + +### -o, --output + +| | | +|---------|--------------------------| +| Type | table\|json | +| Default | table | + +Output format. diff --git a/docs/reference/cli/index.md b/docs/reference/cli/index.md index a25269fd06..218cffa7db 100644 --- a/docs/reference/cli/index.md +++ b/docs/reference/cli/index.md @@ -66,6 +66,7 @@ Coder — A tool for provisioning self-hosted development environments with Terr | [support](./support.md) | Commands for troubleshooting issues with a Coder deployment. | | [server](./server.md) | Start a Coder server | | [provisioner](./provisioner.md) | View and manage provisioner daemons and jobs | +| [ai-gateway](./ai-gateway.md) | Manage AI Gateway | | [agent-firewall](./agent-firewall.md) | Network isolation tool for monitoring and restricting HTTP/HTTPS requests | | [features](./features.md) | List Enterprise features | | [licenses](./licenses.md) | Add, delete, and list licenses | diff --git a/enterprise/cli/aigateway.go b/enterprise/cli/aigateway.go new file mode 100644 index 0000000000..408391456b --- /dev/null +++ b/enterprise/cli/aigateway.go @@ -0,0 +1,185 @@ +package cli + +import ( + "context" + "fmt" + + "github.com/google/uuid" + "golang.org/x/xerrors" + + "github.com/coder/coder/v2/cli/cliui" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/serpent" +) + +func (r *RootCmd) aiGateway() *serpent.Command { + return &serpent.Command{ + Use: "ai-gateway", + Short: "Manage AI Gateway", + Handler: func(inv *serpent.Invocation) error { + return inv.Command.HelpHandler(inv) + }, + Children: []*serpent.Command{ + r.aiGatewayKeys(), + }, + } +} + +func (r *RootCmd) aiGatewayKeys() *serpent.Command { + return &serpent.Command{ + Use: "keys", + Short: "Manage AI Gateway keys", + Handler: func(inv *serpent.Invocation) error { + return inv.Command.HelpHandler(inv) + }, + Children: []*serpent.Command{ + r.aiGatewayKeysCreate(), + r.aiGatewayKeysDelete(), + r.aiGatewayKeysList(), + }, + } +} + +func (r *RootCmd) aiGatewayKeysCreate() *serpent.Command { + return &serpent.Command{ + Use: "create ", + Short: "Create an AI Gateway key", + Middleware: serpent.Chain( + serpent.RequireNArgs(1), + ), + Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + + res, err := client.CreateAIGatewayKey(inv.Context(), codersdk.CreateAIGatewayKeyRequest{ + Name: inv.Args[0], + }) + if err != nil { + return xerrors.Errorf("create AI Gateway key %q: %w", inv.Args[0], err) + } + + _, _ = fmt.Fprintf( + inv.Stdout, + "Successfully created AI Gateway key %s (ID: %s, Prefix: %s).\nSave this authentication token, it will not be shown again.\n\n%s\n", + cliui.Keyword(res.Name), + res.ID, + res.KeyPrefix, + cliui.Keyword(res.Key), + ) + return nil + }, + } +} + +func (r *RootCmd) aiGatewayKeysList() *serpent.Command { + formatter := cliui.NewOutputFormatter( + cliui.TableFormat([]codersdk.AIGatewayKey{}, []string{"id", "name", "key prefix", "last used at", "created at"}), + cliui.JSONFormat(), + ) + + cmd := &serpent.Command{ + Use: "list", + Aliases: []string{"ls"}, + Short: "List AI Gateway keys", + Middleware: serpent.Chain( + serpent.RequireNArgs(0), + ), + Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + + keys, err := client.ListAIGatewayKeys(inv.Context()) + if err != nil { + return xerrors.Errorf("list AI Gateway keys: %w", err) + } + + out, err := formatter.Format(inv.Context(), keys) + if err != nil { + return xerrors.Errorf("format AI Gateway keys: %w", err) + } + if out == "" { + cliui.Info(inv.Stderr, "No AI Gateway keys found.") + return nil + } + + _, err = fmt.Fprintln(inv.Stdout, out) + return err + }, + } + + formatter.AttachOptions(&cmd.Options) + return cmd +} + +func (r *RootCmd) aiGatewayKeysDelete() *serpent.Command { + cmd := &serpent.Command{ + Use: "delete ", + Aliases: []string{"rm"}, + Short: "Delete an AI Gateway key", + Middleware: serpent.Chain( + serpent.RequireNArgs(1), + ), + Options: serpent.OptionSet{ + cliui.SkipPromptOption(), + }, + Handler: func(inv *serpent.Invocation) error { + client, err := r.InitClient(inv) + if err != nil { + return err + } + + key, err := aiGatewayKeyByNameOrID(inv.Context(), client, inv.Args[0]) + if err != nil { + return err + } + + _, err = cliui.Prompt(inv, cliui.PromptOptions{ + Text: fmt.Sprintf("Are you sure you want to delete AI Gateway key %s (ID: %s, Prefix: %s)?", cliui.Keyword(key.Name), key.ID, key.KeyPrefix), + IsConfirm: true, + Default: cliui.ConfirmNo, + }) + if err != nil { + return err + } + + err = client.DeleteAIGatewayKey(inv.Context(), key.ID) + if err != nil { + return xerrors.Errorf("delete AI Gateway key %q: %w", key.Name, err) + } + + _, _ = fmt.Fprintf(inv.Stdout, "Successfully deleted AI Gateway key %s (ID: %s, Prefix: %s).\n", cliui.Keyword(key.Name), key.ID, key.KeyPrefix) + return nil + }, + } + + return cmd +} + +// aiGatewayKeyByNameOrID resolves an AI Gateway key from a name or ID. Names +// take priority over IDs. +func aiGatewayKeyByNameOrID(ctx context.Context, client *codersdk.Client, nameOrID string) (codersdk.AIGatewayKey, error) { + keys, err := client.ListAIGatewayKeys(ctx) + if err != nil { + return codersdk.AIGatewayKey{}, xerrors.Errorf("list AI Gateway keys: %w", err) + } + + for _, key := range keys { + if key.Name == nameOrID { + return key, nil + } + } + + if id, err := uuid.Parse(nameOrID); err == nil { + for _, key := range keys { + if key.ID == id { + return key, nil + } + } + } + + return codersdk.AIGatewayKey{}, xerrors.Errorf("AI Gateway key %q not found", nameOrID) +} diff --git a/enterprise/cli/aigateway_test.go b/enterprise/cli/aigateway_test.go new file mode 100644 index 0000000000..f79ddfa60c --- /dev/null +++ b/enterprise/cli/aigateway_test.go @@ -0,0 +1,228 @@ +package cli_test + +import ( + "bytes" + "context" + "encoding/json" + "fmt" + "regexp" + "slices" + "strings" + "testing" + + "github.com/google/uuid" + "github.com/stretchr/testify/require" + + "github.com/coder/coder/v2/cli/clitest" + "github.com/coder/coder/v2/coderd/aibridge/keys" + "github.com/coder/coder/v2/coderd/coderdtest" + "github.com/coder/coder/v2/codersdk" + "github.com/coder/coder/v2/enterprise/coderd/coderdenttest" + "github.com/coder/coder/v2/enterprise/coderd/license" + "github.com/coder/coder/v2/testutil" +) + +var ( + // aiGatewayCreateRe captures the ID and key prefix from create output. + aiGatewayCreateRe = regexp.MustCompile(`ID: ([0-9a-f-]+), Prefix: (\S+)\)`) + + // aiGatewayKeyRe captures the one-time secret key from create output, + // asserting it is KeyLength alphanumeric characters. + aiGatewayKeyRe = regexp.MustCompile(fmt.Sprintf(`(?s)it will not be shown again\.\n\n([0-9A-Za-z]{%d})\n`, keys.KeyLength)) +) + +// aiGatewayKey holds the values parsed from `keys create` output. +type aiGatewayKey struct { + name string + id uuid.UUID + prefix string +} + +// runAIGatewayKeys runs `coder ai-gateway keys ` as the given client +// and returns its stdout, stderr, and the run error. +func runAIGatewayKeys(ctx context.Context, t *testing.T, client *codersdk.Client, args ...string) (stdout, stderr string, err error) { + t.Helper() + inv, root := newCLI(t, append([]string{"ai-gateway", "keys"}, args...)...) + clitest.SetupConfig(t, client, root) //nolint:gocritic // tests run CLI operations as the owner + outBuf, errBuf := bytes.NewBuffer(nil), bytes.NewBuffer(nil) + inv.Stdout = outBuf + inv.Stderr = errBuf + err = inv.WithContext(ctx).Run() + return outBuf.String(), errBuf.String(), err +} + +// createAIGatewayKey creates a key and returns its parsed name, ID, and +// prefix, asserting the create output is well-formed. +func createAIGatewayKey(ctx context.Context, t *testing.T, client *codersdk.Client, name string) aiGatewayKey { + t.Helper() + stdout, _, err := runAIGatewayKeys(ctx, t, client, "create", name) + require.NoError(t, err) + require.Contains(t, stdout, "Successfully created AI Gateway key "+name) + + // The one-time secret key must be rendered as KeyLength alphanumerics. + require.Len(t, aiGatewayKeyRe.FindStringSubmatch(stdout), 2, "expected secret key in create output") + + matches := aiGatewayCreateRe.FindStringSubmatch(stdout) + require.Len(t, matches, 3, "expected ID and Prefix in create output") + id, err := uuid.Parse(matches[1]) + require.NoError(t, err) + return aiGatewayKey{name: name, id: id, prefix: matches[2]} +} + +// listAIGatewayKeys lists keys as JSON and returns the decoded result. +func listAIGatewayKeys(ctx context.Context, t *testing.T, client *codersdk.Client) []codersdk.AIGatewayKey { + t.Helper() + stdout, _, err := runAIGatewayKeys(ctx, t, client, "list", "--output=json") + require.NoError(t, err) + var listed []codersdk.AIGatewayKey + require.NoError(t, json.Unmarshal([]byte(stdout), &listed)) + return listed +} + +// deleteAIGatewayKey deletes a key by name or ID, asserts success, and +// returns the command stdout. +func deleteAIGatewayKey(ctx context.Context, t *testing.T, client *codersdk.Client, arg string) string { + t.Helper() + stdout, _, err := runAIGatewayKeys(ctx, t, client, "delete", "--yes", arg) + require.NoError(t, err) + return stdout +} + +func TestAIGatewayKeys(t *testing.T) { + t.Parallel() + + dv := coderdtest.DeploymentValues(t) + dv.AI.BridgeConfig.Enabled = true + ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + DeploymentValues: dv, + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureAIBridge: 1, + }, + }, + }) + t.Run("CRUD", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitLong) + + // List returns empty when no keys exist. + stdout, stderr, err := runAIGatewayKeys(ctx, t, ownerClient, "list") + require.NoError(t, err) + require.Empty(t, stdout) + require.Contains(t, stderr, "No AI Gateway keys found.") + + // Create two keys and capture their names, IDs, and prefixes. + created := make([]aiGatewayKey, 0, 2) + for _, name := range []string{"gateway-key-a", "gateway-key-b"} { + created = append(created, createAIGatewayKey(ctx, t, ownerClient, name)) + } + + // List returns both created keys as JSON. Sort by name so the + // assertion does not depend on the list ordering. + listed := listAIGatewayKeys(ctx, t, ownerClient) + require.Len(t, listed, 2) + slices.SortFunc(listed, func(a, b codersdk.AIGatewayKey) int { + return strings.Compare(a.Name, b.Name) + }) + for i, want := range created { + require.Equal(t, want.name, listed[i].Name) + require.Equal(t, want.id, listed[i].ID) + require.Equal(t, want.prefix, listed[i].KeyPrefix) + } + + // Default table output renders created keys, guarding against table + // column and struct tag drift. + tableOut, _, err := runAIGatewayKeys(ctx, t, ownerClient, "list") + require.NoError(t, err) + require.Contains(t, tableOut, "KEY PREFIX") + for _, key := range created { + require.Contains(t, tableOut, key.id.String()) + require.Contains(t, tableOut, key.name) + require.Contains(t, tableOut, key.prefix) + } + + // Delete the first key by name. + stdout = deleteAIGatewayKey(ctx, t, ownerClient, created[0].name) + require.Contains(t, stdout, "Successfully deleted AI Gateway key "+created[0].name) + + // List returns only the remaining key. + listed = listAIGatewayKeys(ctx, t, ownerClient) + require.Len(t, listed, 1) + require.Equal(t, created[1].name, listed[0].Name) + + // Delete the second key by ID. + stdout = deleteAIGatewayKey(ctx, t, ownerClient, created[1].id.String()) + require.Contains(t, stdout, "Successfully deleted AI Gateway key "+created[1].name) + + // List returns empty after all keys deleted. + require.Empty(t, listAIGatewayKeys(ctx, t, ownerClient)) + + // Delete a non-existent key returns not found. + _, _, err = runAIGatewayKeys(ctx, t, ownerClient, "delete", "--yes", created[0].name) + require.ErrorContains(t, err, created[0].name) + require.ErrorContains(t, err, "not found") + + // Name resolution takes priority over ID. A valid key name can be + // formatted like a UUID, so create a key whose name is another key's + // ID and confirm delete resolves by name first. + first := createAIGatewayKey(ctx, t, ownerClient, "some-key") + colliding := createAIGatewayKey(ctx, t, ownerClient, first.id.String()) + require.NotEqual(t, first.id, colliding.id) + + // Deleting with the ID-shaped argument deletes the colliding key + // (matched by name), not the key whose ID matches. + stdout = deleteAIGatewayKey(ctx, t, ownerClient, colliding.name) + require.Contains(t, stdout, "ID: "+colliding.id.String()) + + listed = listAIGatewayKeys(ctx, t, ownerClient) + require.Len(t, listed, 1) + require.Equal(t, first.id, listed[0].ID) + require.Equal(t, first.name, listed[0].Name) + }) + + t.Run("InvalidKeyName", func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitLong) + + _, _, err := runAIGatewayKeys(ctx, t, ownerClient, "create", strings.Repeat("a", 65)) + require.ErrorContains(t, err, "create AI Gateway key") + require.ErrorContains(t, err, "Invalid key name") + }) + + t.Run("MemberForbidden", func(t *testing.T) { + t.Parallel() + + memberClient, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) + + testCases := []struct { + name string + args []string + }{ + { + name: "list", + args: []string{"list"}, + }, + { + name: "create", + args: []string{"create", "member-key"}, + }, + { + name: "delete", + args: []string{"delete", "--yes", uuid.NewString()}, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + ctx := testutil.Context(t, testutil.WaitLong) + + _, _, err := runAIGatewayKeys(ctx, t, memberClient, tc.args...) + require.Error(t, err) + require.ErrorContains(t, err, "Forbidden") + }) + } + }) +} diff --git a/enterprise/cli/root.go b/enterprise/cli/root.go index 720624031a..f081335722 100644 --- a/enterprise/cli/root.go +++ b/enterprise/cli/root.go @@ -18,6 +18,7 @@ func (r *RootCmd) enterpriseOnly() []*serpent.Command { agplcli.ExperimentalCommand(append(r.AGPLExperimental(), r.enterpriseExperimental()...)), // New commands that don't exist in AGPL: + r.aiGateway(), r.agentFirewall(), r.boundaryAlias(), r.workspaceProxy(), diff --git a/enterprise/cli/testdata/coder_--help.golden b/enterprise/cli/testdata/coder_--help.golden index 4e392a8dd6..c9e3b53168 100644 --- a/enterprise/cli/testdata/coder_--help.golden +++ b/enterprise/cli/testdata/coder_--help.golden @@ -16,6 +16,7 @@ USAGE: SUBCOMMANDS: agent-firewall Network isolation tool for monitoring and restricting HTTP/HTTPS requests + ai-gateway Manage AI Gateway external-workspaces Create or manage external workspaces features List Enterprise features groups Manage groups diff --git a/enterprise/cli/testdata/coder_ai-gateway_--help.golden b/enterprise/cli/testdata/coder_ai-gateway_--help.golden new file mode 100644 index 0000000000..2c0d35058b --- /dev/null +++ b/enterprise/cli/testdata/coder_ai-gateway_--help.golden @@ -0,0 +1,12 @@ +coder v0.0.0-devel + +USAGE: + coder ai-gateway + + Manage AI Gateway + +SUBCOMMANDS: + keys Manage AI Gateway keys + +——— +Run `coder --help` for a list of global options. diff --git a/enterprise/cli/testdata/coder_ai-gateway_keys_--help.golden b/enterprise/cli/testdata/coder_ai-gateway_keys_--help.golden new file mode 100644 index 0000000000..8c465b07b2 --- /dev/null +++ b/enterprise/cli/testdata/coder_ai-gateway_keys_--help.golden @@ -0,0 +1,14 @@ +coder v0.0.0-devel + +USAGE: + coder ai-gateway keys + + Manage AI Gateway keys + +SUBCOMMANDS: + create Create an AI Gateway key + delete Delete an AI Gateway key + list List AI Gateway keys + +——— +Run `coder --help` for a list of global options. diff --git a/enterprise/cli/testdata/coder_ai-gateway_keys_create_--help.golden b/enterprise/cli/testdata/coder_ai-gateway_keys_create_--help.golden new file mode 100644 index 0000000000..bcaa7cd6d1 --- /dev/null +++ b/enterprise/cli/testdata/coder_ai-gateway_keys_create_--help.golden @@ -0,0 +1,9 @@ +coder v0.0.0-devel + +USAGE: + coder ai-gateway keys create + + Create an AI Gateway key + +——— +Run `coder --help` for a list of global options. diff --git a/enterprise/cli/testdata/coder_ai-gateway_keys_delete_--help.golden b/enterprise/cli/testdata/coder_ai-gateway_keys_delete_--help.golden new file mode 100644 index 0000000000..62b0db207a --- /dev/null +++ b/enterprise/cli/testdata/coder_ai-gateway_keys_delete_--help.golden @@ -0,0 +1,15 @@ +coder v0.0.0-devel + +USAGE: + coder ai-gateway keys delete [flags] + + Delete an AI Gateway key + + Aliases: rm + +OPTIONS: + -y, --yes bool + Bypass confirmation prompts. + +——— +Run `coder --help` for a list of global options. diff --git a/enterprise/cli/testdata/coder_ai-gateway_keys_list_--help.golden b/enterprise/cli/testdata/coder_ai-gateway_keys_list_--help.golden new file mode 100644 index 0000000000..043ac660f9 --- /dev/null +++ b/enterprise/cli/testdata/coder_ai-gateway_keys_list_--help.golden @@ -0,0 +1,18 @@ +coder v0.0.0-devel + +USAGE: + coder ai-gateway keys list [flags] + + List AI Gateway keys + + Aliases: ls + +OPTIONS: + -c, --column [id|name|key prefix|created at|last used at] (default: id,name,key prefix,last used at,created at) + Columns to display in table output. + + -o, --output table|json (default: table) + Output format. + +——— +Run `coder --help` for a list of global options.