From 877ac905748b1f9fb107f912bbfdf843dc831097 Mon Sep 17 00:00:00 2001 From: Daniel Legt Date: Thu, 30 Apr 2026 11:31:43 +0300 Subject: [PATCH] feat(cli): add comprehensive command suite + Adds new commands for managing boxes, environment variables, and running the server. * The command structure is greatly expanded to improve user experience and coverage for core service functionalities. --- cmd/cmd_box.go | 368 ++++++++++++++++++++++++++++++++++++++++++++++ cmd/cmd_env.go | 276 ++++++++++++++++++++++++++++++++++ cmd/cmd_format.go | 80 ++++++++++ cmd/cmd_run.go | 21 +++ cmd/main.go | 17 +-- 5 files changed, 748 insertions(+), 14 deletions(-) create mode 100644 cmd/cmd_box.go create mode 100644 cmd/cmd_env.go create mode 100644 cmd/cmd_format.go create mode 100644 cmd/cmd_run.go diff --git a/cmd/cmd_box.go b/cmd/cmd_box.go new file mode 100644 index 0000000..1c5c091 --- /dev/null +++ b/cmd/cmd_box.go @@ -0,0 +1,368 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "strings" + "time" + + "github.com/spf13/cobra" + "github.com/spf13/pflag" + "golang.org/x/crypto/bcrypt" + + "warpbox/lib/boxstore" + "warpbox/lib/helpers" + "warpbox/lib/models" +) + +func newBoxCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "box", + Short: "Manage boxes", + Long: "Manage WarpBox upload boxes: list, view, inspect, delete, modify.", + } + + cmd.AddCommand(newBoxListCommand()) + cmd.AddCommand(newBoxViewCommand()) + cmd.AddCommand(newBoxInspectCommand()) + cmd.AddCommand(newBoxDeleteCommand()) + cmd.AddCommand(newBoxChangeCommand()) + cmd.AddCommand(newBoxGetCommand()) + + return cmd +} + +func newBoxListCommand() *cobra.Command { + var format string + var uploadRoot string + cmd := &cobra.Command{ + Use: "ls", + Aliases: []string{"list", "view"}, + Short: "List all boxes", + RunE: func(cmd *cobra.Command, args []string) error { + if uploadRoot != "" { + boxstore.SetUploadRoot(uploadRoot) + } + summaries, err := boxstore.ListBoxSummaries() + if err != nil { + return fmt.Errorf("failed to list boxes: %w", err) + } + if len(summaries) == 0 { + fmt.Println("No boxes found.") + return nil + } + switch format { + case "json": + return formatBoxSummariesJSON(summaries) + case "table", "": + return formatBoxSummariesTable(summaries) + default: + return fmt.Errorf("unknown format: %s (use 'table' or 'json')", format) + } + }, + } + cmd.Flags().StringVarP(&format, "format", "o", "table", "Output format: table, json") + cmd.Flags().StringVar(&uploadRoot, "upload-root", "", "Override upload root directory") + return cmd +} + +func newBoxViewCommand() *cobra.Command { + var uploadRoot string + cmd := &cobra.Command{ + Use: "view", + Short: "View box summary", + Long: "View a box summary showing files, size, expiry, etc.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if uploadRoot != "" { + boxstore.SetUploadRoot(uploadRoot) + } + boxID := args[0] + summary, err := boxstore.BoxSummary(boxID) + if err != nil { + return fmt.Errorf("failed to view box %s: %w", boxID, err) + } + printBoxSummary(&summary) + return nil + }, + } + cmd.Flags().StringVar(&uploadRoot, "upload-root", "", "Override upload root directory") + return cmd +} + +func newBoxInspectCommand() *cobra.Command { + var uploadRoot string + var full bool + cmd := &cobra.Command{ + Use: "inspect", + Short: "Inspect box manifest (raw JSON)", + Long: "Print the full box manifest as JSON. Use --full for hidden fields.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if uploadRoot != "" { + boxstore.SetUploadRoot(uploadRoot) + } + boxID := args[0] + manifest, err := boxstore.ReadManifest(boxID) + if err != nil { + return fmt.Errorf("failed to read manifest for box %s: %w", boxID, err) + } + if !full { + sanitized := manifest + sanitized.PasswordHash = "[REDACTED]" + sanitized.PasswordSalt = "[REDACTED]" + sanitized.AuthToken = "[REDACTED]" + manifest = sanitized + } + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + return enc.Encode(manifest) + }, + } + cmd.Flags().StringVar(&uploadRoot, "upload-root", "", "Override upload root directory") + cmd.Flags().BoolVar(&full, "full", false, "Show sensitive fields (password hash, auth token)") + return cmd +} + +func newBoxDeleteCommand() *cobra.Command { + var uploadRoot string + var force bool + cmd := &cobra.Command{ + Use: "rm", + Aliases: []string{"del", "delete"}, + Short: "Delete a box", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if uploadRoot != "" { + boxstore.SetUploadRoot(uploadRoot) + } + boxID := args[0] + if !force { + fmt.Printf("This will permanently delete box %s and all its files.\n", boxID) + fmt.Print("Confirm (y/N): ") + var confirm string + if _, err := fmt.Scanln(&confirm); err != nil { + confirm = "n" + } + if strings.ToLower(strings.TrimSpace(confirm)) != "y" { + fmt.Println("Aborted.") + return nil + } + } + if err := boxstore.DeleteBox(boxID); err != nil { + return fmt.Errorf("failed to delete box %s: %w", boxID, err) + } + fmt.Printf("Box %s deleted.\n", boxID) + return nil + }, + } + cmd.Flags().StringVar(&uploadRoot, "upload-root", "", "Override upload root directory") + cmd.Flags().BoolVarP(&force, "force", "f", false, "Skip confirmation prompt") + return cmd +} + +func newBoxChangeCommand() *cobra.Command { + var uploadRoot string + var retention int64 + var retentionList bool + var password string + var zip bool + var oneTime bool + var renew bool + var renewSeconds int64 + + cmd := &cobra.Command{ + Use: "change", + Aliases: []string{"update", "modify"}, + Short: "Change box properties", + Long: "Change box properties: retention, password, zip, one-time download, renew expiry.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if uploadRoot != "" { + boxstore.SetUploadRoot(uploadRoot) + } + boxID := args[0] + + if retentionList { + printRetentionOptions() + return nil + } + + changes, err := gatherBoxChanges(cmd.Flags(), retention, password, zip, oneTime, renew, renewSeconds) + if err != nil { + return err + } + + if len(changes) == 0 { + fmt.Println("No changes specified. Use --retention, --password, --zip, --one-time, --renew, or --retention-list.") + return nil + } + + manifest, err := boxstore.ReadManifest(boxID) + if err != nil { + return fmt.Errorf("failed to read manifest for box %s: %w", boxID, err) + } + + for _, apply := range changes { + if err := apply(&manifest); err != nil { + return err + } + } + + if err := boxstore.WriteManifest(boxID, manifest); err != nil { + return fmt.Errorf("failed to save manifest for box %s: %w", boxID, err) + } + + fmt.Printf("Box %s updated.\n", boxID) + return nil + }, + } + + cmd.Flags().StringVar(&uploadRoot, "upload-root", "", "Override upload root directory") + cmd.Flags().Int64Var(&retention, "retention", 0, "Set retention seconds (use --retention-list for valid values)") + cmd.Flags().BoolVar(&retentionList, "retention-list", false, "List available retention options") + cmd.Flags().StringVar(&password, "password", "", "Set a new password (empty string to remove)") + cmd.Flags().BoolVar(&zip, "zip", true, "Allow ZIP downloads (default true, --zip=false to disable)") + cmd.Flags().BoolVar(&oneTime, "one-time", false, "Enable one-time download mode") + cmd.Flags().BoolVar(&renew, "renew", false, "Renew box expiry (use --renew-seconds for duration)") + cmd.Flags().Int64Var(&renewSeconds, "renew-seconds", 0, "Seconds to extend expiry by (used with --renew)") + + return cmd +} + +type changeFunc func(*models.BoxManifest) error + +func gatherBoxChanges(flags *pflag.FlagSet, retention int64, password string, zip bool, oneTime bool, renew bool, renewSeconds int64) ([]changeFunc, error) { + var changes []changeFunc + + if flags.Changed("retention") { + if retention < 0 { + return nil, fmt.Errorf("retention cannot be negative") + } + changes = append(changes, func(m *models.BoxManifest) error { + if m.OneTimeDownload { + m.OneTimeDownload = false + } + m.RetentionSecs = retention + for _, opt := range boxstore.RetentionOptions() { + if opt.Seconds == retention { + m.RetentionKey = opt.Key + m.RetentionLabel = opt.Label + return nil + } + } + m.RetentionKey = "custom" + return nil + }) + } + + if flags.Changed("password") { + changes = append(changes, func(m *models.BoxManifest) error { + if password == "" { + m.PasswordHash = "" + m.PasswordHashAlg = "" + m.AuthToken = "" + return nil + } + token, err := helpers.RandomHexID(16) + if err != nil { + return fmt.Errorf("could not generate auth token") + } + hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost) + if err != nil { + return fmt.Errorf("could not hash password: %w", err) + } + m.PasswordHash = string(hash) + m.PasswordHashAlg = "bcrypt" + m.AuthToken = token + return nil + }) + } + + if flags.Changed("zip") { + changes = append(changes, func(m *models.BoxManifest) error { + if m.OneTimeDownload { + return nil + } + m.DisableZip = !zip + return nil + }) + } + + if flags.Changed("one-time") { + changes = append(changes, func(m *models.BoxManifest) error { + if oneTime { + m.OneTimeDownload = true + m.DisableZip = false + if boxstore.OneTimeDownloadExpiry() > 0 { + m.RetentionSecs = boxstore.OneTimeDownloadExpiry() + } + } else { + m.OneTimeDownload = false + } + return nil + }) + } + + if flags.Changed("renew") { + changes = append(changes, func(m *models.BoxManifest) error { + secs := renewSeconds + if secs <= 0 { + secs = m.RetentionSecs + } + return renewBoxExpiry(m, secs) + }) + } + + return changes, nil +} + +func renewBoxExpiry(m *models.BoxManifest, seconds int64) error { + if seconds <= 0 || m.OneTimeDownload { + return nil + } + if m.ExpiresAt.IsZero() { + m.ExpiresAt = time.Now().UTC().Add(time.Duration(seconds) * time.Second) + return nil + } + m.ExpiresAt = m.ExpiresAt.Add(time.Duration(seconds) * time.Second) + return nil +} + +func newBoxGetCommand() *cobra.Command { + var uploadRoot string + cmd := &cobra.Command{ + Use: "get", + Short: "Get box URL and info", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if uploadRoot != "" { + boxstore.SetUploadRoot(uploadRoot) + } + boxID := args[0] + manifest, err := boxstore.ReadManifest(boxID) + if err != nil { + return fmt.Errorf("failed to read manifest for box %s: %w", boxID, err) + } + + fmt.Printf("Box ID:\t%s\n", boxID) + fmt.Printf("URL:\t/box/%s\n", boxID) + if !manifest.CreatedAt.IsZero() { + fmt.Printf("Created:\t%s\n", manifest.CreatedAt.Format(time.RFC3339)) + } + if !manifest.ExpiresAt.IsZero() { + fmt.Printf("Expires:\t%s\n", manifest.ExpiresAt.Format(time.RFC3339)) + } + if boxstore.IsPasswordProtected(manifest) { + fmt.Println("Password:\tprotected") + } + if manifest.OneTimeDownload { + fmt.Println("Mode:\tone-time download") + } + return nil + }, + } + cmd.Flags().StringVar(&uploadRoot, "upload-root", "", "Override upload root directory") + return cmd +} diff --git a/cmd/cmd_env.go b/cmd/cmd_env.go new file mode 100644 index 0000000..345149d --- /dev/null +++ b/cmd/cmd_env.go @@ -0,0 +1,276 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "strings" + "text/tabwriter" + + "warpbox/lib/config" + + "github.com/spf13/cobra" +) + +func newEnvCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "env", + Short: "Explore environment variable options", + Long: "List and inspect WarpBox environment variables sourced from the codebase.", + } + + cmd.AddCommand(newEnvListCommand()) + cmd.AddCommand(newEnvDescribeCommand()) + + return cmd +} + +func newEnvListCommand() *cobra.Command { + var format string + var includeHidden bool + cmd := &cobra.Command{ + Use: "ls", + Aliases: []string{"list"}, + Short: "List all environment variables", + RunE: func(cmd *cobra.Command, args []string) error { + return formatEnvList(format, includeHidden) + }, + } + cmd.Flags().StringVarP(&format, "format", "o", "table", "Output format: table, json, env") + cmd.Flags().BoolVar(&includeHidden, "hidden", false, "Include non-editable and hard-limit settings") + return cmd +} + +func newEnvDescribeCommand() *cobra.Command { + cmd := &cobra.Command{ + Use: "describe", + Aliases: []string{"show", "info", "get"}, + Short: "Describe an environment variable", + Long: "Show detailed info about a specific env var or setting key.", + Args: cobra.ExactArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + return describeEnvVar(args[0]) + }, + } + return cmd +} + +type envRow struct { + EnvName string + Key string + Label string + Type config.SettingType + Default string + Editable bool + HardLimit bool + Minimum int64 +} + +type describeRow struct { + EnvName string + Key string + Label string + Type config.SettingType + Default string + Value string + Source string + Editable bool + HardLimit bool + Minimum int64 +} + +func formatEnvList(format string, includeHidden bool) error { + allRows := buildAllEnvRows(includeHidden) + + switch format { + case "json": + type envOut struct { + EnvName string `json:"env_name"` + Key string `json:"key"` + Label string `json:"label"` + Type string `json:"type"` + Default string `json:"default"` + Editable bool `json:"editable"` + HardLimit bool `json:"hard_limit"` + Minimum int64 `json:"minimum,omitempty"` + } + out := make([]envOut, len(allRows)) + for i, r := range allRows { + out[i] = envOut{ + EnvName: r.EnvName, Key: r.Key, Label: r.Label, + Type: string(r.Type), Default: r.Default, Editable: r.Editable, + HardLimit: r.HardLimit, Minimum: r.Minimum, + } + } + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + return enc.Encode(out) + + case "env": + for _, r := range allRows { + fmt.Printf("%s=\"%s\"\n", r.EnvName, r.Default) + } + return nil + + case "table", "": + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintln(w, "ENV NAME\tKey\tLabel\tType\tDefault\tEditable") + for _, r := range allRows { + fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%v\n", + r.EnvName, r.Key, r.Label, r.Type, r.Default, r.Editable) + } + return w.Flush() + + default: + return fmt.Errorf("unknown format: %s (use 'table', 'json', or 'env')", format) + } +} + +func buildAllEnvRows(includeHidden bool) []envRow { + cfg, loadErr := config.Load() + + var rows []envRow + + for _, def := range config.Definitions { + if !includeHidden && (!def.Editable || def.HardLimit) { + continue + } + row := envRow{ + EnvName: def.EnvName, + Key: def.Key, + Label: def.Label, + Type: def.Type, + Editable: def.Editable, + HardLimit: def.HardLimit, + Minimum: def.Minimum, + } + if loadErr == nil { + row.Default = getEnvDefault(cfg, def) + } + rows = append(rows, row) + } + + extra := buildExtraEnvRows(includeHidden) + if loadErr == nil { + for i := range extra { + extra[i].Default = extra[i].Default + } + } + rows = append(rows, extra...) + + return rows +} + +func getEnvDefault(cfg *config.Config, def config.SettingDefinition) string { + for _, row := range cfg.SettingRows() { + if row.Definition.Key == def.Key && row.Source == config.SourceDefault { + return row.Value + } + } + return "" +} + +func buildExtraEnvRows(includeHidden bool) []envRow { + extra := []envRow{ + {EnvName: "WARPBOX_ADMIN_ENABLED", Key: "admin_enabled", Label: "Admin interface mode", Type: config.SettingTypeText, Editable: false, Default: "auto"}, + {EnvName: "WARPBOX_ADMIN_USERNAME", Key: "admin_username", Label: "Admin username", Type: config.SettingTypeText, Editable: false, Default: "admin"}, + {EnvName: "WARPBOX_ADMIN_PASSWORD", Key: "admin_password", Label: "Admin password", Type: config.SettingTypeText, Editable: false, Default: "(none)"}, + {EnvName: "WARPBOX_ADMIN_EMAIL", Key: "admin_email", Label: "Admin email", Type: config.SettingTypeText, Editable: false, Default: "(none)"}, + {EnvName: "WARPBOX_ADMIN_COOKIE_SECURE", Key: "admin_cookie_secure", Label: "Admin cookie secure flag", Type: config.SettingTypeBool, Editable: false, Default: "false"}, + {EnvName: "WARPBOX_ALLOW_ADMIN_SETTINGS_OVERRIDE", Key: "allow_admin_override", Label: "Allow admin UI to override settings", Type: config.SettingTypeBool, Editable: false, HardLimit: true, Default: "true"}, + } + + sizePairs := []struct { + bytesEnv string + mbEnv string + label string + }{ + {"WARPBOX_GLOBAL_MAX_FILE_SIZE_BYTES", "WARPBOX_GLOBAL_MAX_FILE_SIZE_MB", "Global max file size"}, + {"WARPBOX_GLOBAL_MAX_BOX_SIZE_BYTES", "WARPBOX_GLOBAL_MAX_BOX_SIZE_MB", "Global max box size"}, + {"WARPBOX_DEFAULT_USER_MAX_FILE_SIZE_BYTES", "WARPBOX_DEFAULT_USER_MAX_FILE_SIZE_MB", "Default user max file size"}, + {"WARPBOX_DEFAULT_USER_MAX_BOX_SIZE_BYTES", "WARPBOX_DEFAULT_USER_MAX_BOX_SIZE_MB", "Default user max box size"}, + } + + for _, pair := range sizePairs { + extra = append(extra, envRow{EnvName: pair.bytesEnv, Key: pair.bytesEnv, Label: pair.label + " (bytes)", Type: config.SettingTypeInt64, Editable: false, HardLimit: true, Minimum: 0, Default: "(use bytes or MB variant)"}) + extra = append(extra, envRow{EnvName: pair.mbEnv, Key: pair.mbEnv, Label: pair.label + " (MB)", Type: config.SettingTypeInt64, Editable: false, HardLimit: true, Minimum: 0, Default: "(use bytes or MB variant)"}) + } + + return extra +} + +func describeEnvVar(query string) error { + cfg, loadErr := config.Load() + + for _, def := range config.Definitions { + if matchEnv(query, def.EnvName, def.Key) { + row := describeRow{ + EnvName: def.EnvName, + Key: def.Key, + Label: def.Label, + Type: def.Type, + Editable: def.Editable, + HardLimit: def.HardLimit, + Minimum: def.Minimum, + } + if loadErr == nil { + for _, r := range cfg.SettingRows() { + if r.Definition.Key == def.Key { + row.Value = r.Value + row.Source = string(r.Source) + break + } + } + } + printDescribeRow(row) + return nil + } + } + + extras := buildExtraEnvRows(true) + for _, er := range extras { + if matchEnv(query, er.EnvName, er.Key) { + row := describeRow{ + EnvName: er.EnvName, + Key: er.Key, + Label: er.Label, + Type: er.Type, + Editable: er.Editable, + HardLimit: er.HardLimit, + Minimum: er.Minimum, + Default: er.Default, + } + printDescribeRow(row) + return nil + } + } + + return fmt.Errorf("no environment variable found matching: %s\n\nUse 'warpbox env ls' to list all available options.", query) +} + +func matchEnv(query, envName, key string) bool { + return strings.EqualFold(query, envName) || strings.EqualFold(query, key) +} + +func printDescribeRow(r describeRow) { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintf(w, "Environment Variable:\t%s\n", r.EnvName) + fmt.Fprintf(w, "Setting Key:\t%s\n", r.Key) + fmt.Fprintf(w, "Label:\t%s\n", r.Label) + fmt.Fprintf(w, "Type:\t%s\n", r.Type) + fmt.Fprintf(w, "Editable (runtime):\t%v\n", r.Editable) + fmt.Fprintf(w, "Hard Limit:\t%v\n", r.HardLimit) + if r.Minimum > 0 { + fmt.Fprintf(w, "Minimum:\t%d\n", r.Minimum) + } + if r.Default != "" { + fmt.Fprintf(w, "Default:\t%s\n", r.Default) + } + if r.Value != "" { + fmt.Fprintf(w, "Current Value:\t%s\n", r.Value) + } + if r.Source != "" { + fmt.Fprintf(w, "Source:\t%s\n", r.Source) + } + w.Flush() +} diff --git a/cmd/cmd_format.go b/cmd/cmd_format.go new file mode 100644 index 0000000..8158f73 --- /dev/null +++ b/cmd/cmd_format.go @@ -0,0 +1,80 @@ +package main + +import ( + "encoding/json" + "fmt" + "os" + "text/tabwriter" + "time" + + "warpbox/lib/boxstore" + "warpbox/lib/models" +) + +func formatBoxSummariesTable(summaries []models.BoxSummary) error { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintln(w, "ID\tFiles\tSize\tCreated\tExpires\tPassword\tOne-Time\tExpired") + for _, s := range summaries { + expires := "-" + if !s.ExpiresAt.IsZero() { + expires = s.ExpiresAt.Format("2006-01-02 15:04:05") + } + created := s.CreatedAt.Format("2006-01-02 15:04:05") + fmt.Fprintf(w, "%s\t%d\t%s\t%s\t%s\t%v\t%v\t%v\n", + s.ID, s.FileCount, s.TotalSizeLabel, created, expires, + s.PasswordProtected, s.OneTimeDownload, s.Expired) + } + return w.Flush() +} + +func formatBoxSummariesJSON(summaries []models.BoxSummary) error { + type summaryOut struct { + ID string `json:"id"` + FileCount int `json:"file_count"` + TotalSize int64 `json:"total_size"` + TotalSizeLabel string `json:"total_size_label"` + CreatedAt time.Time `json:"created_at"` + ExpiresAt time.Time `json:"expires_at"` + Expired bool `json:"expired"` + OneTimeDownload bool `json:"one_time_download"` + PasswordProtected bool `json:"password_protected"` + } + out := make([]summaryOut, len(summaries)) + for i, s := range summaries { + out[i] = summaryOut{ + ID: s.ID, FileCount: s.FileCount, TotalSize: s.TotalSize, + TotalSizeLabel: s.TotalSizeLabel, CreatedAt: s.CreatedAt, + ExpiresAt: s.ExpiresAt, Expired: s.Expired, + OneTimeDownload: s.OneTimeDownload, PasswordProtected: s.PasswordProtected, + } + } + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + return enc.Encode(out) +} + +func printBoxSummary(s *models.BoxSummary) { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintf(w, "ID:\t%s\n", s.ID) + fmt.Fprintf(w, "Files:\t%d\n", s.FileCount) + fmt.Fprintf(w, "Total Size:\t%s\n", s.TotalSizeLabel) + if !s.CreatedAt.IsZero() { + fmt.Fprintf(w, "Created:\t%s\n", s.CreatedAt.Format(time.RFC3339)) + } + if !s.ExpiresAt.IsZero() { + fmt.Fprintf(w, "Expires:\t%s\n", s.ExpiresAt.Format(time.RFC3339)) + } + fmt.Fprintf(w, "Expired:\t%v\n", s.Expired) + fmt.Fprintf(w, "Password Protected:\t%v\n", s.PasswordProtected) + fmt.Fprintf(w, "One-Time Download:\t%v\n", s.OneTimeDownload) + w.Flush() +} + +func printRetentionOptions() { + w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) + fmt.Fprintln(w, "Key\tLabel\tSeconds") + for _, opt := range boxstore.RetentionOptions() { + fmt.Fprintf(w, "%s\t%s\t%d\n", opt.Key, opt.Label, opt.Seconds) + } + w.Flush() +} diff --git a/cmd/cmd_run.go b/cmd/cmd_run.go new file mode 100644 index 0000000..bfb95c1 --- /dev/null +++ b/cmd/cmd_run.go @@ -0,0 +1,21 @@ +package main + +import ( + "warpbox/lib/server" + + "github.com/spf13/cobra" +) + +func newRunCommand() *cobra.Command { + var addr string + cmd := &cobra.Command{ + Use: "run", + Short: "Run the HTTP server", + Long: "Run the WarpBox HTTP server.", + RunE: func(cmd *cobra.Command, args []string) error { + return server.Run(addr) + }, + } + cmd.Flags().StringVar(&addr, "addr", ":8080", "HTTP server address") + return cmd +} diff --git a/cmd/main.go b/cmd/main.go index f65201f..c3c7e0b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -5,8 +5,6 @@ import ( "os" "github.com/spf13/cobra" - - "warpbox/lib/server" ) func main() { @@ -23,17 +21,8 @@ func newRootCommand() *cobra.Command { Long: "WarpBox provides commands for running and managing the WarpBox service.", } - var addr string - runCmd := &cobra.Command{ - Use: "run", - Short: "Run the HTTP server", - Long: "Run the WarpBox HTTP server. The root endpoint responds with ok.", - RunE: func(cmd *cobra.Command, args []string) error { - return server.Run(addr) - }, - } - runCmd.Flags().StringVar(&addr, "addr", ":8080", "HTTP server address") - - rootCmd.AddCommand(runCmd) + rootCmd.AddCommand(newRunCommand()) + rootCmd.AddCommand(newBoxCommand()) + rootCmd.AddCommand(newEnvCommand()) return rootCmd }