Files

166 lines
4.2 KiB
Go

package main
import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/spf13/cobra"
"github.com/memohai/memoh/internal/bots"
"github.com/memohai/memoh/internal/tui"
)
func newBotsCommand(ctx *cliContext) *cobra.Command {
cmd := &cobra.Command{
Use: "bots",
Short: "Manage bots and their workspace containers",
}
cmd.AddCommand(newBotsCreateCommand(ctx))
cmd.AddCommand(newBotsDeleteCommand(ctx))
cmd.AddCommand(newBotsContainerCommand())
return cmd
}
func newBotsCreateCommand(ctx *cliContext) *cobra.Command {
var displayName string
var avatarURL string
var timezone string
var inactive bool
cmd := &cobra.Command{
Use: "create",
Short: "Create a bot",
Args: cobra.NoArgs,
RunE: func(_ *cobra.Command, _ []string) error {
client, err := authenticatedClient(ctx)
if err != nil {
return err
}
requestCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
req := buildCreateBotRequest(displayName, avatarURL, timezone, inactive)
bot, err := client.CreateBot(requestCtx, req)
if err != nil {
return err
}
fmt.Printf("Created bot %s (%s)\n", bot.DisplayName, bot.ID)
return nil
},
}
cmd.Flags().StringVar(&displayName, "name", "", "Bot display name")
cmd.Flags().StringVar(&avatarURL, "avatar-url", "", "Bot avatar URL")
cmd.Flags().StringVar(&timezone, "timezone", "", "Bot timezone")
cmd.Flags().BoolVar(&inactive, "inactive", false, "Create the bot in inactive state")
_ = cmd.MarkFlagRequired("name")
return cmd
}
func newBotsDeleteCommand(ctx *cliContext) *cobra.Command {
var yes bool
cmd := &cobra.Command{
Use: "delete <bot-id>",
Short: "Delete a bot",
Args: cobra.ExactArgs(1),
RunE: func(_ *cobra.Command, args []string) error {
botID := strings.TrimSpace(args[0])
if botID == "" {
return errors.New("bot id is required")
}
if !yes {
return fmt.Errorf("refusing to delete bot %s without --yes", botID)
}
client, err := authenticatedClient(ctx)
if err != nil {
return err
}
requestCtx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := client.DeleteBot(requestCtx, botID); err != nil {
return err
}
fmt.Printf("Deleted bot %s\n", botID)
return nil
},
}
cmd.Flags().BoolVarP(&yes, "yes", "y", false, "Confirm bot deletion")
return cmd
}
func newBotsContainerCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "ctr [ctr args]",
Aliases: []string{"container"},
Short: "Manage the nested containerd inside the server container",
DisableFlagParsing: true,
SilenceUsage: true,
Long: `Run ctr inside the Docker Compose server service so you can inspect and
manage the nested containerd that Memoh uses for workspace containers.
By default this command injects the containerd namespace from config.toml.
Pass --no-namespace or provide your own ctr -n/--namespace flag to override it.`,
Example: ` memoh bots ctr images ls
memoh bots ctr containers ls
memoh bots ctr --namespace default tasks ls
memoh bots ctr --server-service server -- snapshots ls`,
RunE: func(cmd *cobra.Command, args []string) error {
if len(args) == 0 || isHelpArg(args[0]) {
return cmd.Help()
}
opts, ctrArgs, err := parseContainerdOptions(args)
if err != nil {
return err
}
if len(ctrArgs) == 0 {
return errors.New("missing ctr arguments")
}
return runServerContainerd(cmd.Context(), opts, ctrArgs)
},
}
return cmd
}
func authenticatedClient(ctx *cliContext) (*tui.Client, error) {
token := strings.TrimSpace(ctx.state.Token)
if token == "" {
return nil, errors.New("missing access token, please run `memoh login` first")
}
return tui.NewClient(ctx.state.ServerURL, token), nil
}
func buildCreateBotRequest(displayName, avatarURL, timezone string, inactive bool) bots.CreateBotRequest {
req := bots.CreateBotRequest{
DisplayName: strings.TrimSpace(displayName),
AvatarURL: strings.TrimSpace(avatarURL),
}
if strings.TrimSpace(timezone) != "" {
tz := strings.TrimSpace(timezone)
req.Timezone = &tz
}
if inactive {
active := false
req.IsActive = &active
}
return req
}