package main import ( "context" "fmt" "os" "sort" "strings" "text/tabwriter" "github.com/spf13/cobra" "git.tukangketik.id/swanadiva/hostkeeper/internal/models" "git.tukangketik.id/swanadiva/hostkeeper/pkg/config" "git.tukangketik.id/swanadiva/hostkeeper/pkg/storage" ) var ( listGroup string listTag string listFormat string listSort string ) // listCmd represents the list command var listCmd = &cobra.Command{ Use: "list", Aliases: []string{"ls"}, Short: "List all saved SSH hosts", Long: `List all SSH hosts saved in HostKeeper. You can filter by group or tag, and format the output as table or json. Examples: # List all hosts hostkeeper list # List hosts in a specific group hostkeeper list --group production # List hosts with a specific tag hostkeeper list --tag web # Output in JSON format hostkeeper list --format json # Sort by name hostkeeper list --sort name`, RunE: runListHosts, } func init() { listCmd.Flags().StringVar(&listGroup, "group", "", "filter hosts by group") listCmd.Flags().StringVar(&listTag, "tag", "", "filter hosts by tag") listCmd.Flags().StringVar(&listFormat, "format", "table", "output format: table or json") listCmd.Flags().StringVar(&listSort, "sort", "name", "sort by: name, hostname, or group") rootCmd.AddCommand(listCmd) } func runListHosts(cmd *cobra.Command, args []string) error { cfg := appCfg if cfg == nil { var err error cfg, err = config.New() if err != nil { return fmt.Errorf("failed to initialize config: %w", err) } } // Initialize storage store, err := storage.NewJSONStorage(cfg.GetDataDir()) if err != nil { return fmt.Errorf("failed to initialize storage: %w", err) } // Get all hosts ctx := context.Background() hosts, err := store.ListHosts(ctx) if err != nil { return fmt.Errorf("failed to list hosts: %w", err) } // Apply filters if listGroup != "" { hosts = filterByGroup(hosts, listGroup) } if listTag != "" { hosts = filterByTag(hosts, listTag) } // Apply sorting sortHosts(hosts, listSort) // Output if len(hosts) == 0 { fmt.Println("No hosts found.") fmt.Println() fmt.Println("Add a host with: hostkeeper add [name] --host --user ") return nil } switch listFormat { case "json": return outputJSON(hosts) case "table": return outputTable(hosts) default: return fmt.Errorf("unsupported format: %s (use 'table' or 'json')", listFormat) } } func filterByGroup(hosts []*models.Host, group string) []*models.Host { var filtered []*models.Host for _, h := range hosts { if strings.EqualFold(h.Group, group) { filtered = append(filtered, h) } } return filtered } func filterByTag(hosts []*models.Host, tag string) []*models.Host { var filtered []*models.Host for _, h := range hosts { for _, t := range h.Tags { if strings.EqualFold(t, tag) { filtered = append(filtered, h) break } } } return filtered } func sortHosts(hosts []*models.Host, sortBy string) { switch sortBy { case "hostname": sort.Slice(hosts, func(i, j int) bool { return hosts[i].Hostname < hosts[j].Hostname }) case "group": sort.Slice(hosts, func(i, j int) bool { return hosts[i].Group < hosts[j].Group }) default: // "name" sort.Slice(hosts, func(i, j int) bool { return hosts[i].Name < hosts[j].Name }) } } func outputTable(hosts []*models.Host) error { w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0) fmt.Fprintln(w, "NAME\tHOSTNAME\tPORT\tUSER\tGROUP\tAUTH\tTAGS") fmt.Fprintln(w, "────\t────────\t────\t────\t─────\t────\t────") for _, h := range hosts { tags := strings.Join(h.Tags, ", ") fmt.Fprintf(w, "%s\t%s\t%d\t%s\t%s\t%s\t%s\n", h.Name, h.Hostname, h.Port, h.Username, h.Group, h.Auth.Type, tags, ) } return w.Flush() } func outputJSON(hosts []*models.Host) error { fmt.Print("[") for i, h := range hosts { if i > 0 { fmt.Print(",") } fmt.Printf(`{"id":"%s","name":"%s","hostname":"%s","port":%d,"username":"%s","group":"%s","auth_type":"%s"}`, h.ID, h.Name, h.Hostname, h.Port, h.Username, h.Group, h.Auth.Type) } fmt.Println("]") return nil }