feat(cli): add shell completions (#14341)

This commit is contained in:
Ethan
2024-08-20 14:47:46 +10:00
committed by GitHub
parent 6f9b3c1592
commit 0b2ba96065
80 changed files with 510 additions and 418 deletions
+4 -4
View File
@@ -65,8 +65,8 @@ func (f *OutputFormatter) AttachOptions(opts *serpent.OptionSet) {
Flag: "output",
FlagShorthand: "o",
Default: f.formats[0].ID(),
Value: serpent.StringOf(&f.formatID),
Description: "Output format. Available formats: " + strings.Join(formatNames, ", ") + ".",
Value: serpent.EnumOf(&f.formatID, formatNames...),
Description: "Output format.",
},
)
}
@@ -136,8 +136,8 @@ func (f *tableFormat) AttachOptions(opts *serpent.OptionSet) {
Flag: "column",
FlagShorthand: "c",
Default: strings.Join(f.defaultColumns, ","),
Value: serpent.StringArrayOf(&f.columns),
Description: "Columns to display in table output. Available columns: " + strings.Join(f.allColumns, ", ") + ".",
Value: serpent.EnumArrayOf(&f.columns, f.allColumns...),
Description: "Columns to display in table output.",
},
)
}
+8 -9
View File
@@ -106,11 +106,11 @@ func Test_OutputFormatter(t *testing.T) {
fs := cmd.Options.FlagSet()
selected, err := fs.GetString("output")
require.NoError(t, err)
require.Equal(t, "json", selected)
selected := cmd.Options.ByFlag("output")
require.NotNil(t, selected)
require.Equal(t, "json", selected.Value.String())
usage := fs.FlagUsages()
require.Contains(t, usage, "Available formats: json, foo")
require.Contains(t, usage, "Output format.")
require.Contains(t, usage, "foo flag 1234")
ctx := context.Background()
@@ -129,11 +129,10 @@ func Test_OutputFormatter(t *testing.T) {
require.Equal(t, "foo", out)
require.EqualValues(t, 1, atomic.LoadInt64(&called))
require.NoError(t, fs.Set("output", "bar"))
require.Error(t, fs.Set("output", "bar"))
out, err = f.Format(ctx, data)
require.Error(t, err)
require.ErrorContains(t, err, "bar")
require.Equal(t, "", out)
require.EqualValues(t, 1, atomic.LoadInt64(&called))
require.NoError(t, err)
require.Equal(t, "foo", out)
require.EqualValues(t, 2, atomic.LoadInt64(&called))
})
}
+89
View File
@@ -0,0 +1,89 @@
package cli
import (
"fmt"
"github.com/coder/coder/v2/cli/cliui"
"github.com/coder/serpent"
"github.com/coder/serpent/completion"
)
func (*RootCmd) completion() *serpent.Command {
var shellName string
var printOutput bool
shellOptions := completion.ShellOptions(&shellName)
return &serpent.Command{
Use: "completion",
Short: "Install or update shell completion scripts for the detected or chosen shell.",
Options: []serpent.Option{
{
Flag: "shell",
FlagShorthand: "s",
Description: "The shell to install completion for.",
Value: shellOptions,
},
{
Flag: "print",
Description: "Print the completion script instead of installing it.",
FlagShorthand: "p",
Value: serpent.BoolOf(&printOutput),
},
},
Handler: func(inv *serpent.Invocation) error {
if shellName != "" {
shell, err := completion.ShellByName(shellName, inv.Command.Parent.Name())
if err != nil {
return err
}
if printOutput {
return shell.WriteCompletion(inv.Stdout)
}
return installCompletion(inv, shell)
}
shell, err := completion.DetectUserShell(inv.Command.Parent.Name())
if err == nil {
return installCompletion(inv, shell)
}
// Silently continue to the shell selection if detecting failed.
choice, err := cliui.Select(inv, cliui.SelectOptions{
Message: "Select a shell to install completion for:",
Options: shellOptions.Choices,
})
if err != nil {
return err
}
shellChoice, err := completion.ShellByName(choice, inv.Command.Parent.Name())
if err != nil {
return err
}
if printOutput {
return shellChoice.WriteCompletion(inv.Stdout)
}
return installCompletion(inv, shellChoice)
},
}
}
func installCompletion(inv *serpent.Invocation, shell completion.Shell) error {
path, err := shell.InstallPath()
if err != nil {
cliui.Error(inv.Stderr, fmt.Sprintf("Failed to determine completion path %v", err))
return shell.WriteCompletion(inv.Stdout)
}
choice, err := cliui.Select(inv, cliui.SelectOptions{
Options: []string{
"Confirm",
"Print to terminal",
},
Message: fmt.Sprintf("Install completion for %s at %s?", shell.Name(), path),
HideSearch: true,
})
if err != nil {
return err
}
if choice == "Print to terminal" {
return shell.WriteCompletion(inv.Stdout)
}
return completion.InstallShellCompletion(shell)
}
+2 -45
View File
@@ -17,6 +17,7 @@ import (
"strings"
"github.com/cli/safeexec"
"github.com/natefinch/atomic"
"github.com/pkg/diff"
"github.com/pkg/diff/write"
"golang.org/x/exp/constraints"
@@ -524,7 +525,7 @@ func (r *RootCmd) configSSH() *serpent.Command {
}
if !bytes.Equal(configRaw, configModified) {
err = writeWithTempFileAndMove(sshConfigFile, bytes.NewReader(configModified))
err = atomic.WriteFile(sshConfigFile, bytes.NewReader(configModified))
if err != nil {
return xerrors.Errorf("write ssh config failed: %w", err)
}
@@ -758,50 +759,6 @@ func sshConfigSplitOnCoderSection(data []byte) (before, section []byte, after []
return data, nil, nil, nil
}
// writeWithTempFileAndMove writes to a temporary file in the same
// directory as path and renames the temp file to the file provided in
// path. This ensure we avoid trashing the file we are writing due to
// unforeseen circumstance like filesystem full, command killed, etc.
func writeWithTempFileAndMove(path string, r io.Reader) (err error) {
dir := filepath.Dir(path)
name := filepath.Base(path)
// Ensure that e.g. the ~/.ssh directory exists.
if err = os.MkdirAll(dir, 0o700); err != nil {
return xerrors.Errorf("create directory: %w", err)
}
// Create a tempfile in the same directory for ensuring write
// operation does not fail.
f, err := os.CreateTemp(dir, fmt.Sprintf(".%s.", name))
if err != nil {
return xerrors.Errorf("create temp file failed: %w", err)
}
defer func() {
if err != nil {
_ = os.Remove(f.Name()) // Cleanup in case a step failed.
}
}()
_, err = io.Copy(f, r)
if err != nil {
_ = f.Close()
return xerrors.Errorf("write temp file failed: %w", err)
}
err = f.Close()
if err != nil {
return xerrors.Errorf("close temp file failed: %w", err)
}
err = os.Rename(f.Name(), path)
if err != nil {
return xerrors.Errorf("rename temp file failed: %w", err)
}
return nil
}
// sshConfigExecEscape quotes the string if it contains spaces, as per
// `man 5 ssh_config`. However, OpenSSH uses exec in the users shell to
// run the command, and as such the formatting/escape requirements
+2
View File
@@ -81,6 +81,8 @@ var usageTemplate = func() *template.Template {
switch v := opt.Value.(type) {
case *serpent.Enum:
return strings.Join(v.Choices, "|")
case *serpent.EnumArray:
return fmt.Sprintf("[%s]", strings.Join(v.Choices, "|"))
default:
return v.Type()
}
+1 -1
View File
@@ -137,7 +137,7 @@ func (r *RootCmd) assignOrganizationRoles(orgContext *OrganizationContext) *serp
func (r *RootCmd) listOrganizationMembers(orgContext *OrganizationContext) *serpent.Command {
formatter := cliui.NewOutputFormatter(
cliui.TableFormat([]codersdk.OrganizationMemberWithUserData{}, []string{"username", "organization_roles"}),
cliui.TableFormat([]codersdk.OrganizationMemberWithUserData{}, []string{"username", "organization roles"}),
cliui.JSONFormat(),
)
+1 -1
View File
@@ -23,7 +23,7 @@ func TestListOrganizationMembers(t *testing.T) {
client, user := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleUserAdmin())
ctx := testutil.Context(t, testutil.WaitMedium)
inv, root := clitest.New(t, "organization", "members", "list", "-c", "user_id,username,roles")
inv, root := clitest.New(t, "organization", "members", "list", "-c", "user id,username,organization roles")
clitest.SetupConfig(t, client, root)
buf := new(bytes.Buffer)
+7 -7
View File
@@ -36,7 +36,7 @@ func (r *RootCmd) organizationRoles(orgContext *OrganizationContext) *serpent.Co
func (r *RootCmd) showOrganizationRoles(orgContext *OrganizationContext) *serpent.Command {
formatter := cliui.NewOutputFormatter(
cliui.ChangeFormatterData(
cliui.TableFormat([]roleTableRow{}, []string{"name", "display_name", "site_permissions", "organization_permissions", "user_permissions"}),
cliui.TableFormat([]roleTableRow{}, []string{"name", "display name", "site permissions", "organization permissions", "user permissions"}),
func(data any) (any, error) {
inputs, ok := data.([]codersdk.AssignableRoles)
if !ok {
@@ -103,7 +103,7 @@ func (r *RootCmd) showOrganizationRoles(orgContext *OrganizationContext) *serpen
func (r *RootCmd) editOrganizationRole(orgContext *OrganizationContext) *serpent.Command {
formatter := cliui.NewOutputFormatter(
cliui.ChangeFormatterData(
cliui.TableFormat([]roleTableRow{}, []string{"name", "display_name", "site_permissions", "organization_permissions", "user_permissions"}),
cliui.TableFormat([]roleTableRow{}, []string{"name", "display name", "site permissions", "organization permissions", "user permissions"}),
func(data any) (any, error) {
typed, _ := data.(codersdk.Role)
return []roleTableRow{roleToTableView(typed)}, nil
@@ -408,10 +408,10 @@ func roleToTableView(role codersdk.Role) roleTableRow {
type roleTableRow struct {
Name string `table:"name,default_sort"`
DisplayName string `table:"display_name"`
OrganizationID string `table:"organization_id"`
SitePermissions string ` table:"site_permissions"`
DisplayName string `table:"display name"`
OrganizationID string `table:"organization id"`
SitePermissions string ` table:"site permissions"`
// map[<org_id>] -> Permissions
OrganizationPermissions string `table:"organization_permissions"`
UserPermissions string `table:"user_permissions"`
OrganizationPermissions string `table:"organization permissions"`
UserPermissions string `table:"user permissions"`
}
+1
View File
@@ -82,6 +82,7 @@ const (
func (r *RootCmd) CoreSubcommands() []*serpent.Command {
// Please re-sort this list alphabetically if you change it!
return []*serpent.Command{
r.completion(),
r.dotfiles(),
r.externalAuth(),
r.login(),
+10 -10
View File
@@ -32,11 +32,11 @@ func (r *RootCmd) stat() *serpent.Command {
fs = afero.NewReadOnlyFs(afero.NewOsFs())
formatter = cliui.NewOutputFormatter(
cliui.TableFormat([]statsRow{}, []string{
"host_cpu",
"host_memory",
"home_disk",
"container_cpu",
"container_memory",
"host cpu",
"host memory",
"home disk",
"container cpu",
"container memory",
}),
cliui.JSONFormat(),
)
@@ -284,9 +284,9 @@ func (*RootCmd) statDisk(fs afero.Fs) *serpent.Command {
}
type statsRow struct {
HostCPU *clistat.Result `json:"host_cpu" table:"host_cpu,default_sort"`
HostMemory *clistat.Result `json:"host_memory" table:"host_memory"`
Disk *clistat.Result `json:"home_disk" table:"home_disk"`
ContainerCPU *clistat.Result `json:"container_cpu" table:"container_cpu"`
ContainerMemory *clistat.Result `json:"container_memory" table:"container_memory"`
HostCPU *clistat.Result `json:"host_cpu" table:"host cpu,default_sort"`
HostMemory *clistat.Result `json:"host_memory" table:"host memory"`
Disk *clistat.Result `json:"home_disk" table:"home disk"`
ContainerCPU *clistat.Result `json:"container_cpu" table:"container cpu"`
ContainerMemory *clistat.Result `json:"container_memory" table:"container memory"`
}
+3 -25
View File
@@ -3,7 +3,6 @@ package cli
import (
"fmt"
"net/http"
"strings"
"time"
"golang.org/x/xerrors"
@@ -239,35 +238,14 @@ func (r *RootCmd) templateEdit() *serpent.Command {
Value: serpent.DurationOf(&activityBump),
},
{
Flag: "autostart-requirement-weekdays",
// workspaces created from this template must be restarted on the given weekdays. To unset this value for the template (and disable the autostop requirement for the template), pass 'none'.
Flag: "autostart-requirement-weekdays",
Description: "Edit the template autostart requirement weekdays - workspaces created from this template can only autostart on the given weekdays. To unset this value for the template (and allow autostart on all days), pass 'all'.",
Value: serpent.Validate(serpent.StringArrayOf(&autostartRequirementDaysOfWeek), func(value *serpent.StringArray) error {
v := value.GetSlice()
if len(v) == 1 && v[0] == "all" {
return nil
}
_, err := codersdk.WeekdaysToBitmap(v)
if err != nil {
return xerrors.Errorf("invalid autostart requirement days of week %q: %w", strings.Join(v, ","), err)
}
return nil
}),
Value: serpent.EnumArrayOf(&autostartRequirementDaysOfWeek, append(codersdk.AllDaysOfWeek, "all")...),
},
{
Flag: "autostop-requirement-weekdays",
Description: "Edit the template autostop requirement weekdays - workspaces created from this template must be restarted on the given weekdays. To unset this value for the template (and disable the autostop requirement for the template), pass 'none'.",
Value: serpent.Validate(serpent.StringArrayOf(&autostopRequirementDaysOfWeek), func(value *serpent.StringArray) error {
v := value.GetSlice()
if len(v) == 1 && v[0] == "none" {
return nil
}
_, err := codersdk.WeekdaysToBitmap(v)
if err != nil {
return xerrors.Errorf("invalid autostop requirement days of week %q: %w", strings.Join(v, ","), err)
}
return nil
}),
Value: serpent.EnumArrayOf(&autostopRequirementDaysOfWeek, append(codersdk.AllDaysOfWeek, "none")...),
},
{
Flag: "autostop-requirement-weeks",
+7 -7
View File
@@ -40,11 +40,11 @@ func (r *RootCmd) templateVersions() *serpent.Command {
func (r *RootCmd) templateVersionsList() *serpent.Command {
defaultColumns := []string{
"Name",
"Created At",
"Created By",
"Status",
"Active",
"name",
"created at",
"created by",
"status",
"active",
}
formatter := cliui.NewOutputFormatter(
cliui.TableFormat([]templateVersionRow{}, defaultColumns),
@@ -70,10 +70,10 @@ func (r *RootCmd) templateVersionsList() *serpent.Command {
for _, opt := range i.Command.Options {
if opt.Flag == "column" {
if opt.ValueSource == serpent.ValueSourceDefault {
v, ok := opt.Value.(*serpent.StringArray)
v, ok := opt.Value.(*serpent.EnumArray)
if ok {
// Add the extra new default column.
*v = append(*v, "Archived")
_ = v.Append("Archived")
}
}
break
+2
View File
@@ -15,6 +15,8 @@ USAGE:
SUBCOMMANDS:
autoupdate Toggle auto-update policy for a workspace
completion Install or update shell completion scripts for the
detected or chosen shell.
config-ssh Add an SSH Host entry for your workspaces "ssh
coder.workspace"
create Create a workspace
+16
View File
@@ -0,0 +1,16 @@
coder v0.0.0-devel
USAGE:
coder completion [flags]
Install or update shell completion scripts for the detected or chosen shell.
OPTIONS:
-p, --print bool
Print the completion script instead of installing it.
-s, --shell bash|fish|zsh|powershell
The shell to install completion for.
———
Run `coder --help` for a list of global options.
+4 -7
View File
@@ -11,14 +11,11 @@ OPTIONS:
-a, --all bool
Specifies whether all workspaces will be listed or not.
-c, --column string-array (default: workspace,template,status,healthy,last built,current version,outdated,starts at,stops after)
Columns to display in table output. Available columns: favorite,
workspace, organization id, organization name, template, status,
healthy, last built, current version, outdated, starts at, starts
next, stops after, stops next, daily cost.
-c, --column [favorite|workspace|organization id|organization name|template|status|healthy|last built|current version|outdated|starts at|starts next|stops after|stops next|daily cost] (default: workspace,template,status,healthy,last built,current version,outdated,starts at,stops after)
Columns to display in table output.
-o, --output string (default: table)
Output format. Available formats: table, json.
-o, --output table|json (default: table)
Output format.
--search string (default: owner:me)
Search for a workspace with a query.
+4 -5
View File
@@ -15,12 +15,11 @@ OPTIONS:
-a, --all bool
Specifies whether all workspaces will be listed or not.
-c, --column string-array (default: workspace,starts at,starts next,stops after,stops next)
Columns to display in table output. Available columns: workspace,
starts at, starts next, stops after, stops next.
-c, --column [workspace|starts at|starts next|stops after|stops next] (default: workspace,starts at,starts next,stops after,stops next)
Columns to display in table output.
-o, --output string (default: table)
Output format. Available formats: table, json.
-o, --output table|json (default: table)
Output format.
--search string (default: owner:me)
Search for a workspace with a query.
+4 -5
View File
@@ -6,9 +6,8 @@ USAGE:
Run upload and download tests from your machine to a workspace
OPTIONS:
-c, --column string-array (default: Interval,Throughput)
Columns to display in table output. Available columns: Interval,
Throughput.
-c, --column [Interval|Throughput] (default: Interval,Throughput)
Columns to display in table output.
-d, --direct bool
Specifies whether to wait for a direct connection before testing
@@ -18,8 +17,8 @@ OPTIONS:
Specifies whether to run in reverse mode where the client receives and
the server sends.
-o, --output string (default: table)
Output format. Available formats: table, json.
-o, --output table|json (default: table)
Output format.
--pcap-file string
Specifies a file to write a network capture to.
+4 -5
View File
@@ -11,12 +11,11 @@ SUBCOMMANDS:
mem Show memory usage, in gigabytes.
OPTIONS:
-c, --column string-array (default: host_cpu,host_memory,home_disk,container_cpu,container_memory)
Columns to display in table output. Available columns: host cpu, host
memory, home disk, container cpu, container memory.
-c, --column [host cpu|host memory|home disk|container cpu|container memory] (default: host cpu,host memory,home disk,container cpu,container memory)
Columns to display in table output.
-o, --output string (default: table)
Output format. Available formats: table, json.
-o, --output table|json (default: table)
Output format.
———
Run `coder --help` for a list of global options.
+2 -2
View File
@@ -9,8 +9,8 @@ OPTIONS:
--host bool
Force host CPU measurement.
-o, --output string (default: text)
Output format. Available formats: text, json.
-o, --output text|json (default: text)
Output format.
———
Run `coder --help` for a list of global options.
+2 -2
View File
@@ -6,8 +6,8 @@ USAGE:
Show disk usage, in gigabytes.
OPTIONS:
-o, --output string (default: text)
Output format. Available formats: text, json.
-o, --output text|json (default: text)
Output format.
--path string (default: /)
Path for which to check disk usage.
+2 -2
View File
@@ -9,8 +9,8 @@ OPTIONS:
--host bool
Force host memory measurement.
-o, --output string (default: text)
Output format. Available formats: text, json.
-o, --output text|json (default: text)
Output format.
--prefix Ki|Mi|Gi|Ti (default: Gi)
SI Prefix for memory measurement.
+2 -2
View File
@@ -25,13 +25,13 @@ OPTIONS:
--allow-user-cancel-workspace-jobs bool (default: true)
Allow users to cancel in-progress workspace jobs.
--autostart-requirement-weekdays string-array
--autostart-requirement-weekdays [monday|tuesday|wednesday|thursday|friday|saturday|sunday|all]
Edit the template autostart requirement weekdays - workspaces created
from this template can only autostart on the given weekdays. To unset
this value for the template (and allow autostart on all days), pass
'all'.
--autostop-requirement-weekdays string-array
--autostop-requirement-weekdays [monday|tuesday|wednesday|thursday|friday|saturday|sunday|none]
Edit the template autostop requirement weekdays - workspaces created
from this template must be restarted on the given weekdays. To unset
this value for the template (and disable the autostop requirement for
+4 -6
View File
@@ -8,13 +8,11 @@ USAGE:
Aliases: ls
OPTIONS:
-c, --column string-array (default: name,organization name,last updated,used by)
Columns to display in table output. Available columns: name, created
at, last updated, organization id, organization name, provisioner,
active version id, used by, default ttl.
-c, --column [name|created at|last updated|organization id|organization name|provisioner|active version id|used by|default ttl] (default: name,organization name,last updated,used by)
Columns to display in table output.
-o, --output string (default: table)
Output format. Available formats: table, json.
-o, --output table|json (default: table)
Output format.
———
Run `coder --help` for a list of global options.
+4 -5
View File
@@ -9,15 +9,14 @@ OPTIONS:
-O, --org string, $CODER_ORGANIZATION
Select which organization (uuid or name) to use.
-c, --column string-array (default: Name,Created At,Created By,Status,Active)
Columns to display in table output. Available columns: name, created
at, created by, status, active, archived.
-c, --column [name|created at|created by|status|active|archived] (default: name,created at,created by,status,active)
Columns to display in table output.
--include-archived bool
Include archived versions in the result list.
-o, --output string (default: table)
Output format. Available formats: table, json.
-o, --output table|json (default: table)
Output format.
———
Run `coder --help` for a list of global options.
+4 -5
View File
@@ -12,12 +12,11 @@ OPTIONS:
Specifies whether all users' tokens will be listed or not (must have
Owner role to see all tokens).
-c, --column string-array (default: id,name,last used,expires at,created at)
Columns to display in table output. Available columns: id, name, last
used, expires at, created at, owner.
-c, --column [id|name|last used|expires at|created at|owner] (default: id,name,last used,expires at,created at)
Columns to display in table output.
-o, --output string (default: table)
Output format. Available formats: table, json.
-o, --output table|json (default: table)
Output format.
———
Run `coder --help` for a list of global options.
+1 -1
View File
@@ -11,7 +11,7 @@ USAGE:
$ coder users activate example_user
OPTIONS:
-c, --column string-array (default: username,email,created_at,status)
-c, --column [username|email|created at|status] (default: username,email,created at,status)
Specify a column to filter in the table.
———
+4 -5
View File
@@ -6,12 +6,11 @@ USAGE:
Aliases: ls
OPTIONS:
-c, --column string-array (default: username,email,created_at,status)
Columns to display in table output. Available columns: id, username,
email, created at, updated at, status.
-c, --column [id|username|email|created at|updated at|status] (default: username,email,created at,status)
Columns to display in table output.
-o, --output string (default: table)
Output format. Available formats: table, json.
-o, --output table|json (default: table)
Output format.
———
Run `coder --help` for a list of global options.
+2 -2
View File
@@ -8,8 +8,8 @@ USAGE:
$ coder users show me
OPTIONS:
-o, --output string (default: table)
Output format. Available formats: table, json.
-o, --output table|json (default: table)
Output format.
———
Run `coder --help` for a list of global options.
+1 -1
View File
@@ -9,7 +9,7 @@ USAGE:
$ coder users suspend example_user
OPTIONS:
-c, --column string-array (default: username,email,created_at,status)
-c, --column [username|email|created at|status] (default: username,email,created at,status)
Specify a column to filter in the table.
———
+2 -2
View File
@@ -6,8 +6,8 @@ USAGE:
Show coder version
OPTIONS:
-o, --output string (default: text)
Output format. Available formats: text, json.
-o, --output text|json (default: text)
Output format.
———
Run `coder --help` for a list of global options.
+1 -1
View File
@@ -15,7 +15,7 @@ import (
func (r *RootCmd) userList() *serpent.Command {
formatter := cliui.NewOutputFormatter(
cliui.TableFormat([]codersdk.User{}, []string{"username", "email", "created_at", "status"}),
cliui.TableFormat([]codersdk.User{}, []string{"username", "email", "created at", "status"}),
cliui.JSONFormat(),
)
client := new(codersdk.Client)
+3 -2
View File
@@ -36,6 +36,7 @@ func (r *RootCmd) createUserStatusCommand(sdkStatus codersdk.UserStatus) *serpen
client := new(codersdk.Client)
var columns []string
allColumns := []string{"username", "email", "created at", "status"}
cmd := &serpent.Command{
Use: fmt.Sprintf("%s <username|user_id>", verb),
Short: short,
@@ -99,8 +100,8 @@ func (r *RootCmd) createUserStatusCommand(sdkStatus codersdk.UserStatus) *serpen
Flag: "column",
FlagShorthand: "c",
Description: "Specify a column to filter in the table.",
Default: strings.Join([]string{"username", "email", "created_at", "status"}, ","),
Value: serpent.StringArrayOf(&columns),
Default: strings.Join(allColumns, ","),
Value: serpent.EnumArrayOf(&columns, allColumns...),
},
}
return cmd