feat: adds --readonly and --pin flags, and displays Size column in list by default

This commit is contained in:
Lewis Wynne 2026-02-13 18:52:34 +00:00
parent e5b6dcd187
commit 5bcd3581dd
46 changed files with 711 additions and 177 deletions

View file

@ -2,6 +2,7 @@ package cmd
import (
"fmt"
"strings"
"github.com/spf13/cobra"
)
@ -9,13 +10,10 @@ import (
var metaCmd = &cobra.Command{
Use: "meta KEY[@STORE]",
Short: "View or modify metadata for a key",
Long: `View or modify metadata (TTL, encryption) for a key without changing its value.
Long: `View or modify metadata (TTL, encryption, read-only, pinned) for a key
without changing its value.
With no flags, displays the key's current metadata. Use flags to modify:
--ttl DURATION Set expiry (e.g. 30m, 2h)
--ttl never Remove expiry
--encrypt Encrypt the value at rest
--decrypt Decrypt the value (store as plaintext)`,
With no flags, displays the key's current metadata. Pass flags to modify.`,
Args: cobra.ExactArgs(1),
RunE: meta,
SilenceUsage: true,
@ -52,23 +50,46 @@ func meta(cmd *cobra.Command, args []string) error {
ttlStr, _ := cmd.Flags().GetString("ttl")
encryptFlag, _ := cmd.Flags().GetBool("encrypt")
decryptFlag, _ := cmd.Flags().GetBool("decrypt")
readonlyFlag, _ := cmd.Flags().GetBool("readonly")
writableFlag, _ := cmd.Flags().GetBool("writable")
pinFlag, _ := cmd.Flags().GetBool("pin")
unpinFlag, _ := cmd.Flags().GetBool("unpin")
force, _ := cmd.Flags().GetBool("force")
if encryptFlag && decryptFlag {
return fmt.Errorf("cannot meta '%s': --encrypt and --decrypt are mutually exclusive", args[0])
}
if readonlyFlag && writableFlag {
return fmt.Errorf("cannot meta '%s': --readonly and --writable are mutually exclusive", args[0])
}
if pinFlag && unpinFlag {
return fmt.Errorf("cannot meta '%s': --pin and --unpin are mutually exclusive", args[0])
}
// View mode: no flags set
if ttlStr == "" && !encryptFlag && !decryptFlag {
isModify := ttlStr != "" || encryptFlag || decryptFlag || readonlyFlag || writableFlag || pinFlag || unpinFlag
if !isModify {
expiresStr := "never"
if entry.ExpiresAt > 0 {
expiresStr = formatExpiry(entry.ExpiresAt)
}
fmt.Fprintf(cmd.OutOrStdout(), " key: %s\n", spec.Full())
fmt.Fprintf(cmd.OutOrStdout(), " secret: %v\n", entry.Secret)
fmt.Fprintf(cmd.OutOrStdout(), " writable: %v\n", !entry.ReadOnly)
fmt.Fprintf(cmd.OutOrStdout(), " pinned: %v\n", entry.Pinned)
fmt.Fprintf(cmd.OutOrStdout(), " expires: %s\n", expiresStr)
return nil
}
// Read-only enforcement: --readonly and --writable always work without --force,
// but other modifications on a read-only key require --force.
if entry.ReadOnly && !force && !readonlyFlag && !writableFlag {
onlyPinChange := !encryptFlag && !decryptFlag && ttlStr == "" && (pinFlag || unpinFlag)
if !onlyPinChange {
return fmt.Errorf("cannot meta '%s': key is read-only", args[0])
}
}
// Modification mode — may need identity for encrypt
if encryptFlag {
identity, err = ensureIdentity()
@ -81,12 +102,19 @@ func meta(cmd *cobra.Command, args []string) error {
return fmt.Errorf("cannot meta '%s': %v", args[0], err)
}
var changes []string
if ttlStr != "" {
expiresAt, err := parseTTLString(ttlStr)
if err != nil {
return fmt.Errorf("cannot meta '%s': %v", args[0], err)
}
entry.ExpiresAt = expiresAt
if expiresAt == 0 {
changes = append(changes, "cleared ttl")
} else {
changes = append(changes, "set ttl to "+ttlStr)
}
}
if encryptFlag {
@ -97,6 +125,7 @@ func meta(cmd *cobra.Command, args []string) error {
return fmt.Errorf("cannot meta '%s': secret is locked (identity file missing)", args[0])
}
entry.Secret = true
changes = append(changes, "encrypted")
}
if decryptFlag {
@ -107,18 +136,43 @@ func meta(cmd *cobra.Command, args []string) error {
return fmt.Errorf("cannot meta '%s': secret is locked (identity file missing)", args[0])
}
entry.Secret = false
changes = append(changes, "decrypted")
}
if readonlyFlag {
entry.ReadOnly = true
changes = append(changes, "made readonly")
}
if writableFlag {
entry.ReadOnly = false
changes = append(changes, "made writable")
}
if pinFlag {
entry.Pinned = true
changes = append(changes, "pinned")
}
if unpinFlag {
entry.Pinned = false
changes = append(changes, "unpinned")
}
if err := writeStoreFile(p, entries, recipients); err != nil {
return fmt.Errorf("cannot meta '%s': %v", args[0], err)
}
return autoSync("meta " + spec.Display())
summary := strings.Join(changes, ", ")
okf("%s %s", summary, spec.Display())
return autoSync(summary + " " + spec.Display())
}
func init() {
metaCmd.Flags().String("ttl", "", "set expiry (e.g. 30m, 2h) or 'never' to clear")
metaCmd.Flags().BoolP("encrypt", "e", false, "encrypt the value at rest")
metaCmd.Flags().BoolP("decrypt", "d", false, "decrypt the value (store as plaintext)")
metaCmd.Flags().Bool("readonly", false, "mark the key as read-only")
metaCmd.Flags().Bool("writable", false, "clear the read-only flag")
metaCmd.Flags().Bool("pin", false, "pin the key (sorts to top in list)")
metaCmd.Flags().Bool("unpin", false, "unpin the key")
metaCmd.Flags().Bool("force", false, "bypass read-only protection for metadata changes")
rootCmd.AddCommand(metaCmd)
}