package cmd import ( "flag" "fmt" "strings" "git.noodles.cam/claude-skills/marketplace/cli/internal/gitea" "git.noodles.cam/claude-skills/marketplace/cli/internal/wire" ) // labelColumn is how much of the label list a row shows before it is cut. const labelColumn = 38 func init() { register(&Command{ Name: "remote", Group: GroupSync, Short: "list what exists in the tracker, one line each", Long: `Discovery only: this prints and WRITES NOTHING. The local store is a store, not a search-results folder, and a listing that landed in it would leave files nobody asked for beside the issues somebody did. Pick the numbers here, then pull them. #42 open type/task, tech/sql Wire sqlc into the repo layer └─ local: wire-sqlc-appclick The second line appears when the number is already in the local ledger, so it is obvious what a pull would refresh and what it would add. --limit here caps the LISTING: N lines out, closed ones among them. That is not what the same flag means to ` + "`kettle pull`" + `, and the difference is not an oversight — pull bounds what it WRITES, this command writes nothing, and enumeration is the whole job. Projects are not filterable: the projects API is not exposed by Gitea. Use milestones or labels, or the web UI.`, Examples: []Example{ {"kettle remote", "the open issues, 30 of them"}, {"kettle remote --state all --label type/bug --limit 50", "every bug, open and closed"}, {"kettle remote --milestone v0.2", "what is in a milestone"}, {"kettle remote -q sqlc", "keyword search over title and body"}, }, Setup: func(fs *flag.FlagSet) func([]string) error { state := fs.String("state", "open", "open, closed or all") var labels stringList fs.Var(&labels, "label", "filter by label; repeat for AND") // Both spellings, the way the Python this replaces took them. var query string fs.StringVar(&query, "q", "", "search text in title and body") fs.StringVar(&query, "query", "", "the long spelling of -q") milestone := fs.String("milestone", "", "milestone id or title") limit := fs.Int("limit", 30, "how many lines to print") out := storeFlag(fs) return func(args []string) error { if len(args) > 0 { return Fail("remote takes no arguments — filter with --label, --milestone or -q") } if !contains([]string{"open", "closed", "all"}, *state) { return Fail("--state %q must be open, closed or all", *state) } if *limit < 1 { return Fail("--limit must be 1 or more, got %d", *limit) } root, client, err := syncStart(*out) if err != nil { return err } listing, err := client.ListIssues(gitea.IssueFilter{ State: *state, Labels: labels, Query: query, Milestone: *milestone, Limit: *limit, }) if err != nil { return err } // The ledger, not the files: a pushed issue has no file left and // is still something a pull would land on a known slug. ledger := gitea.LoadRemoteMap(root) repo := client.Repo() for i := range listing.Issues { p := &listing.Issues[i] labels := "-" if names := p.LabelNames(); len(names) > 0 { labels = strings.Join(names, ", ") } // One line per issue is the whole point; a repository that // namespaces heavily would wrap the column otherwise. if len(labels) > labelColumn { labels = labels[:labelColumn] } fmt.Printf("#%-5d %-7s %-38s %s\n", p.Number, p.State, labels, p.Title) if local := ledger.Slug(wire.Key{Repo: repo, Number: p.Number}); local != "" { fmt.Printf("%13s└─ local: %s\n", "", local) } } scope := "" if listing.Milestone != "" { scope = " in milestone " + listing.Milestone } hint := "" if *milestone != "" { hint = "--milestone " + *milestone } fmt.Printf("%d issue(s)%s — pull them with: kettle pull %s\n", len(listing.Issues), scope, hint) return nil } }, }) }