feat(mail): add channel subscribe/unsubscribe/subscribers CLI commands

Adds three new subcommands to `gt mail channel`:
- subscribe <name>: Subscribe current identity to a channel
- unsubscribe <name>: Unsubscribe current identity from a channel
- subscribers <name>: List all subscribers to a channel

These commands expose the existing beads.SubscribeToChannel and
beads.UnsubscribeFromChannel functions through the CLI.

Closes gt-77334r

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
tom
2026-01-17 03:42:02 -08:00
committed by Steve Yegge
parent f19a0ab5d6
commit f14dadc956

View File

@@ -80,6 +80,32 @@ var channelDeleteCmd = &cobra.Command{
RunE: runChannelDelete,
}
var channelSubscribeCmd = &cobra.Command{
Use: "subscribe <name>",
Short: "Subscribe to a channel",
Long: `Subscribe the current identity (BD_ACTOR) to a channel.
Subscribers receive messages broadcast to the channel.`,
Args: cobra.ExactArgs(1),
RunE: runChannelSubscribe,
}
var channelUnsubscribeCmd = &cobra.Command{
Use: "unsubscribe <name>",
Short: "Unsubscribe from a channel",
Long: `Unsubscribe the current identity (BD_ACTOR) from a channel.`,
Args: cobra.ExactArgs(1),
RunE: runChannelUnsubscribe,
}
var channelSubscribersCmd = &cobra.Command{
Use: "subscribers <name>",
Short: "List channel subscribers",
Long: `List all subscribers to a channel.`,
Args: cobra.ExactArgs(1),
RunE: runChannelSubscribers,
}
func init() {
// List flags
channelListCmd.Flags().BoolVar(&channelJSON, "json", false, "Output as JSON")
@@ -91,6 +117,9 @@ func init() {
channelCreateCmd.Flags().IntVar(&channelRetainCount, "retain-count", 0, "Number of messages to retain (0 = unlimited)")
channelCreateCmd.Flags().IntVar(&channelRetainHours, "retain-hours", 0, "Hours to retain messages (0 = forever)")
// Subscribers flags
channelSubscribersCmd.Flags().BoolVar(&channelJSON, "json", false, "Output as JSON")
// Main channel command flags
mailChannelCmd.Flags().BoolVar(&channelJSON, "json", false, "Output as JSON")
@@ -99,6 +128,9 @@ func init() {
mailChannelCmd.AddCommand(channelShowCmd)
mailChannelCmd.AddCommand(channelCreateCmd)
mailChannelCmd.AddCommand(channelDeleteCmd)
mailChannelCmd.AddCommand(channelSubscribeCmd)
mailChannelCmd.AddCommand(channelUnsubscribeCmd)
mailChannelCmd.AddCommand(channelSubscribersCmd)
mailCmd.AddCommand(mailChannelCmd)
}
@@ -305,6 +337,88 @@ func runChannelDelete(cmd *cobra.Command, args []string) error {
return nil
}
func runChannelSubscribe(cmd *cobra.Command, args []string) error {
name := args[0]
subscriber := os.Getenv("BD_ACTOR")
if subscriber == "" {
return fmt.Errorf("BD_ACTOR not set - cannot determine subscriber identity")
}
townRoot, err := workspace.FindFromCwdOrError()
if err != nil {
return fmt.Errorf("not in a Gas Town workspace: %w", err)
}
b := beads.New(townRoot)
if err := b.SubscribeToChannel(name, subscriber); err != nil {
return fmt.Errorf("subscribing to channel: %w", err)
}
fmt.Printf("Subscribed %s to channel %q\n", subscriber, name)
return nil
}
func runChannelUnsubscribe(cmd *cobra.Command, args []string) error {
name := args[0]
subscriber := os.Getenv("BD_ACTOR")
if subscriber == "" {
return fmt.Errorf("BD_ACTOR not set - cannot determine subscriber identity")
}
townRoot, err := workspace.FindFromCwdOrError()
if err != nil {
return fmt.Errorf("not in a Gas Town workspace: %w", err)
}
b := beads.New(townRoot)
if err := b.UnsubscribeFromChannel(name, subscriber); err != nil {
return fmt.Errorf("unsubscribing from channel: %w", err)
}
fmt.Printf("Unsubscribed %s from channel %q\n", subscriber, name)
return nil
}
func runChannelSubscribers(cmd *cobra.Command, args []string) error {
name := args[0]
townRoot, err := workspace.FindFromCwdOrError()
if err != nil {
return fmt.Errorf("not in a Gas Town workspace: %w", err)
}
b := beads.New(townRoot)
_, fields, err := b.GetChannelBead(name)
if err != nil {
return fmt.Errorf("getting channel: %w", err)
}
if fields == nil {
return fmt.Errorf("channel not found: %s", name)
}
if channelJSON {
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
return enc.Encode(fields.Subscribers)
}
if len(fields.Subscribers) == 0 {
fmt.Printf("Channel %q has no subscribers\n", name)
return nil
}
fmt.Printf("Subscribers to channel %q:\n", name)
for _, sub := range fields.Subscribers {
fmt.Printf(" %s\n", sub)
}
return nil
}
// channelMessage represents a message in a channel.
type channelMessage struct {
ID string `json:"id"`