package cli import ( "fmt" "strings" "github.com/spf13/cobra" "github.com/m3tam3re/agent-lib/internal/vendor" ) func newVendorCmd() *cobra.Command { cmd := &cobra.Command{ Use: "vendor", Short: "Curate external sources into this work repository", } cmd.AddCommand(newVendorAddCmd()) cmd.AddCommand(newVendorSelectCmd()) cmd.AddCommand(newVendorListCmd()) cmd.AddCommand(newVendorInspectCmd()) cmd.AddCommand(newVendorUpdateCmd()) cmd.AddCommand(newVendorDiffCmd()) cmd.AddCommand(newVendorRemoveCmd()) return cmd } func newVendorAddCmd() *cobra.Command { var ref string var include []string var renames []string cmd := &cobra.Command{ Use: "add ", Short: "Vendor a new external git source", Long: "Clones the source (in-process, via go-git), discovers skills, commands,\n" + "agents and MCP fragments, materializes the selection under external//\n" + "and pins URL, ref, revision, selection and renames in the lockfile.\n" + "Default selection is everything; --include switches to an explicit list.\n" + "Collisions abort with a hard error; resolve them with --rename upstream=deployed.", Args: cobra.ExactArgs(2), RunE: func(cmd *cobra.Command, args []string) error { renameMap, err := vendor.ParseRenames(renames) if err != nil { return err } opts := vendor.AddOptions{ Name: args[0], URL: args[1], Ref: ref, Include: splitList(include), Renames: renameMap, } if err := vendor.Add(".", opts, cmd.OutOrStdout()); err != nil { return fmt.Errorf("vendor add: %w", err) } return nil }, } cmd.Flags().StringVar(&ref, "ref", "", "branch or tag to track (default: the source's default branch)") cmd.Flags().StringSliceVar(&include, "include", nil, "comma-separated item ids to vendor (switches selection to include mode)") cmd.Flags().StringSliceVar(&renames, "rename", nil, "deployed-name override upstream-id=deployed-name (repeatable)") return cmd } func splitList(in []string) []string { var out []string for _, s := range in { for _, part := range strings.Split(s, ",") { if t := strings.TrimSpace(part); t != "" { out = append(out, t) } } } return out } func newVendorSelectCmd() *cobra.Command { var add, remove []string var mode string cmd := &cobra.Command{ Use: "select ", Short: "Change a source's selection without advancing the pinned revision", Long: "Manages include/exclude selection after the initial vendor add.\n" + "Include mode: --add appends ids (validated against the pinned upstream),\n" + "--remove drops ids and deletes their folders. All mode: --remove moves ids\n" + "onto the exclude list; --add is an error (everything is selected already).\n" + "--mode all resets to everything: include/exclude lists are cleared and the\n" + "full pinned upstream is re-materialized. Collisions re-run on every change;\n" + "renames from the lockfile are honored. The revision never moves — that is\n" + "vendor update's job.", Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { if mode != "" && mode != "all" { return fmt.Errorf("--mode accepts only \"all\" (got %q)", mode) } opts := vendor.SelectOptions{ Add: splitList(add), Remove: splitList(remove), ModeAll: mode == "all", } if _, err := vendor.Select(".", args[0], opts, cmd.OutOrStdout()); err != nil { return fmt.Errorf("vendor select: %w", err) } return nil }, } cmd.Flags().StringSliceVar(&add, "add", nil, "comma-separated upstream ids to add to the selection") cmd.Flags().StringSliceVar(&remove, "remove", nil, "comma-separated upstream ids to remove from the selection") cmd.Flags().StringVar(&mode, "mode", "", "switch selection mode (only \"all\": clear include/exclude, take everything)") return cmd }