feat(cli): add promote, cards, copy, demote, delete, edit commands

Complete CLI crystallization loop. Promote generates card_data
(template slots, checklist steps, link URLs). Cards view sorted by
use_count. Copy increments usage. Demote strips card layer. Delete
does soft then hard. Edit opens $EDITOR.
This commit is contained in:
2026-05-14 11:28:17 -04:00
parent a6fda5d1ee
commit c3cc9464b9
7 changed files with 431 additions and 2 deletions
+73
View File
@@ -0,0 +1,73 @@
package cmd
import (
"fmt"
"strings"
"github.com/lerko/nib/internal/db"
"github.com/lerko/nib/internal/display"
"github.com/spf13/cobra"
)
var (
cardsTag string
cardsType string
)
var cardsCmd = &cobra.Command{
Use: "cards",
Short: "list crystallized cards by usage",
RunE: runCards,
}
func init() {
cardsCmd.Flags().StringVar(&cardsTag, "tag", "", "filter by tag")
cardsCmd.Flags().StringVar(&cardsType, "type", "", "filter by card type")
rootCmd.AddCommand(cardsCmd)
}
func runCards(_ *cobra.Command, _ []string) error {
store, err := openStore()
if err != nil {
return err
}
defer store.Close()
p := db.DefaultListParams()
p.CardsOnly = true
p.Sort = "use_count"
p.Order = "desc"
if cardsTag != "" {
p.Tag = &cardsTag
}
if cardsType != "" {
if !db.ValidCardType(cardsType) {
return fmt.Errorf("invalid_type — %q is not a valid card type", cardsType)
}
ct := db.CardType(cardsType)
p.CardTypeFilter = &ct
}
entities, err := store.List(p)
if err != nil {
return err
}
for _, e := range entities {
glyph := display.DisplayGlyph(e.Glyph, e.CardType)
shortID := display.FormatID(e.ID)
var tagStr string
for _, tag := range e.Tags {
tagStr += " #" + tag
}
fmt.Printf("%s %-40s %-16s %3d× %s\n",
glyph, e.Body,
strings.TrimSpace(tagStr),
e.UseCount, shortID)
}
return nil
}