2026-08-22 21:52:30 +02:00
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 ())
2026-08-23 15:13:50 +02:00
cmd . AddCommand ( newVendorSelectCmd ())
2026-08-22 21:54:32 +02:00
cmd . AddCommand ( newVendorListCmd ())
cmd . AddCommand ( newVendorInspectCmd ())
2026-08-22 22:01:06 +02:00
cmd . AddCommand ( newVendorUpdateCmd ())
cmd . AddCommand ( newVendorDiffCmd ())
cmd . AddCommand ( newVendorRemoveCmd ())
2026-08-22 21:52:30 +02:00
return cmd
}
func newVendorAddCmd () * cobra . Command {
var ref string
2026-10-02 12:35:39 +02:00
var include , exclude , onlyTypes , excludeTypes [] string
2026-08-22 21:56:21 +02:00
var renames [] string
2026-08-22 21:52:30 +02:00
cmd := & cobra . Command {
Use : "add <name> <url>" ,
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/<name>/\n" +
"and pins URL, ref, revision, selection and renames in the lockfile.\n" +
2026-10-02 12:35:39 +02:00
"Default selection is everything; --include switches to an explicit list,\n" +
"--exclude keeps everything except the listed ids. --types (allow-list) and\n" +
"--exclude-types (deny-list) filter whole content types and survive updates:\n" +
"new upstream items of an excluded type never enter the work repository.\n" +
2026-08-22 21:56:21 +02:00
"Collisions abort with a hard error; resolve them with --rename upstream=deployed." ,
2026-08-22 21:52:30 +02:00
Args : cobra . ExactArgs ( 2 ),
RunE : func ( cmd * cobra . Command , args [] string ) error {
2026-08-22 21:56:21 +02:00
renameMap , err := vendor . ParseRenames ( renames )
if err != nil {
return err
}
2026-08-22 21:52:30 +02:00
opts := vendor . AddOptions {
2026-10-02 12:35:39 +02:00
Name : args [ 0 ],
URL : args [ 1 ],
Ref : ref ,
Include : splitList ( include ),
Exclude : splitList ( exclude ),
OnlyTypes : splitList ( onlyTypes ),
ExcludeTypes : splitList ( excludeTypes ),
Renames : renameMap ,
2026-08-22 21:52:30 +02:00
}
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)" )
2026-10-02 12:35:39 +02:00
cmd . Flags (). StringSliceVar ( & exclude , "exclude" , nil , "comma-separated item ids to skip (keeps all mode, drops these ids)" )
cmd . Flags (). StringSliceVar ( & onlyTypes , "types" , nil , "comma-separated content types to take: skills, commands, agents, mcp (allow-list)" )
cmd . Flags (). StringSliceVar ( & excludeTypes , "exclude-types" , nil , "comma-separated content types to skip: skills, commands, agents, mcp (deny-list)" )
2026-08-22 21:56:21 +02:00
cmd . Flags (). StringSliceVar ( & renames , "rename" , nil , "deployed-name override upstream-id=deployed-name (repeatable)" )
2026-08-22 21:52:30 +02:00
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
}
2026-08-23 15:13:50 +02:00
func newVendorSelectCmd () * cobra . Command {
var add , remove [] string
2026-10-02 12:35:39 +02:00
var onlyTypes , excludeTypes [] string
2026-08-23 15:13:50 +02:00
var mode string
cmd := & cobra . Command {
Use : "select <name>" ,
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" +
2026-10-02 12:35:39 +02:00
"--types/--exclude-types replace the content-type filter (skills, commands,\n" +
"agents, mcp); absent flags keep it as-is. --mode all resets to everything:\n" +
"include/exclude lists and the type filter are cleared and the full pinned\n" +
"upstream is re-materialized. Collisions re-run on every change; renames\n" +
"from the lockfile are honored. The revision never moves — that is\n" +
2026-08-23 15:13:50 +02:00
"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 {
2026-10-02 12:35:39 +02:00
Add : splitList ( add ),
Remove : splitList ( remove ),
ModeAll : mode == "all" ,
OnlyTypes : splitList ( onlyTypes ),
ExcludeTypes : splitList ( excludeTypes ),
2026-08-23 15:13:50 +02:00
}
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" )
2026-10-02 12:35:39 +02:00
cmd . Flags (). StringSliceVar ( & onlyTypes , "types" , nil , "content types to take: skills, commands, agents, mcp (allow-list; replaces the type filter)" )
cmd . Flags (). StringSliceVar ( & excludeTypes , "exclude-types" , nil , "content types to skip: skills, commands, agents, mcp (deny-list; replaces the type filter)" )
cmd . Flags (). StringVar ( & mode , "mode" , "" , "switch selection mode (only \"all\": clear include/exclude and the type filter, take everything)" )
2026-08-23 15:13:50 +02:00
return cmd
}