fix: pre-select all in init prompt, preserve agent paths with slashes, fix rollback off-by-one

This commit is contained in:
2026-05-23 11:25:31 +01:00
parent 0dfccbc48f
commit 9633f9ad4c
3 changed files with 54 additions and 7 deletions
+37 -4
View File
@@ -49,7 +49,11 @@ func NewInit(reg *adapter.Registry) *cobra.Command {
var picked []string
switch {
case len(from) > 0:
picked = from
for _, f := range from {
if f != "" {
picked = append(picked, f)
}
}
case noPrompt:
picked = candidates
default:
@@ -58,6 +62,14 @@ func NewInit(reg *adapter.Registry) *cobra.Command {
return err
}
}
if len(picked) == 0 {
return fmt.Errorf("no harnesses selected; rerun with --no-prompt or --from <name,name>")
}
for _, name := range picked {
if _, ok := reg.Get(name); !ok {
return fmt.Errorf("unknown adapter %q (try `harness-sync adapter list`)", name)
}
}
return runImport(cmd, reg, r, home, picked)
},
}
@@ -134,7 +146,10 @@ func runImport(cmd *cobra.Command, reg *adapter.Registry, root, home string, pic
if err := repo.Commit(fmt.Sprintf("import from %s", strings.Join(picked, ", "))); err != nil {
return err
}
_, _ = fmt.Fprintf(cmd.OutOrStdout(), "imported from %v into %s\n", picked, root)
_, _ = fmt.Fprintf(cmd.OutOrStdout(),
"imported from %v into %s\n skills: %d\n agents: %d\n mcp servers: %d\n instructions: %d source(s)\n",
picked, root, len(bundle.Skills), len(bundle.Agents), len(bundle.MCP.Servers), len(instructions),
)
return nil
}
@@ -183,7 +198,11 @@ func writeCanonical(root string, b *canonical.Bundle, picked []string) error {
}
for _, s := range b.Skills {
path := filepath.Join(root, "skills", s.Name, "SKILL.md")
rel := s.Path
if rel == "" {
rel = filepath.Join(safeFilename(s.Name), "SKILL.md")
}
path := filepath.Join(root, "skills", rel)
if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil {
return err
}
@@ -192,7 +211,14 @@ func writeCanonical(root string, b *canonical.Bundle, picked []string) error {
}
}
for _, a := range b.Agents {
path := filepath.Join(root, "agents", a.Name+".md")
rel := a.Path
if rel == "" {
rel = safeFilename(a.Name) + ".md"
}
path := filepath.Join(root, "agents", rel)
if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil {
return err
}
if err := os.WriteFile(path, []byte(a.Body), 0o600); err != nil {
return err
}
@@ -205,3 +231,10 @@ func writeCanonical(root string, b *canonical.Bundle, picked []string) error {
}
return nil
}
// safeFilename strips path separators and other characters that would break
// when used as a basename.
func safeFilename(s string) string {
r := strings.NewReplacer("/", "-", "\\", "-", ":", "-", "\x00", "")
return r.Replace(s)
}
+6 -1
View File
@@ -68,7 +68,12 @@ func (r *Repo) IsRepo() bool {
return err == nil
}
// Revert undoes the last N commits with `git revert --no-edit HEAD~N..HEAD`.
// n must be >= 1.
func (r *Repo) Revert(n int) error {
_, err := r.run("revert", "--no-edit", fmt.Sprintf("HEAD~%d..HEAD", n-1))
if n < 1 {
return fmt.Errorf("revert: n must be >= 1, got %d", n)
}
_, err := r.run("revert", "--no-edit", fmt.Sprintf("HEAD~%d..HEAD", n))
return err
}
+11 -2
View File
@@ -44,19 +44,28 @@ func MultiSelect(title string, choices []string, opts ...Option) ([]string, erro
}
return o.nonInteractive, nil
}
var picked []string
// Pre-select every choice. Users deselect what they don't want; hitting
// enter without toggling thus accepts all (the common case) instead of
// silently producing an empty result.
picked := make([]string, len(choices))
copy(picked, choices)
opts2 := make([]huh.Option[string], 0, len(choices))
for _, c := range choices {
opts2 = append(opts2, huh.NewOption(c, c))
opts2 = append(opts2, huh.NewOption(c, c).Selected(true))
}
form := huh.NewForm(huh.NewGroup(
huh.NewMultiSelect[string]().
Title(title).
Description("space to toggle · enter to confirm · all pre-selected").
Options(opts2...).
Value(&picked),
))
if err := form.Run(); err != nil {
return nil, err
}
if len(picked) == 0 {
return nil, fmt.Errorf("no items selected")
}
return picked, nil
}