package main import ( "context" "fmt" "os" "github.com/spf13/cobra" "github.com/enmanuel/agents/pkg/avatar" shellavatar "github.com/enmanuel/agents/shell/avatar" shellmatrix "github.com/enmanuel/agents/shell/matrix" ) func autoAvatarCmd() *cobra.Command { var ( provider string style string set string size int dryRun bool fromURL string fromFile string ) cmd := &cobra.Command{ Use: "auto-avatar ", Short: "Generate and set a random avatar from a free provider (or a custom URL/file)", Long: `Fetches a unique avatar image from a free provider (dicebear, robohash, multiavatar) using the agent ID as seed, uploads it to the Matrix media repo, and sets it as the bot's avatar. To use a custom avatar instead of the random generator, pass --from-url or --from-file. Examples: agentctl auto-avatar assistant-bot agentctl auto-avatar assistant-bot --provider robohash --set set1 agentctl auto-avatar assistant-bot --provider dicebear --style pixel-art agentctl auto-avatar assistant-bot --dry-run # only show the URL agentctl auto-avatar pokemon-expert --from-url https://example/pikachu.png agentctl auto-avatar pokemon-expert --from-file ./avatars/pokemon.png`, Args: cobra.ExactArgs(1), RunE: func(cmd *cobra.Command, args []string) error { agentID := args[0] if fromURL != "" && fromFile != "" { return fmt.Errorf("--from-url and --from-file are mutually exclusive") } // Custom source path: skip random generator entirely. if fromURL != "" || fromFile != "" { return runCustomAvatar(agentID, fromURL, fromFile, dryRun) } opts := avatar.DefaultOptions() if size > 0 { opts.Size = size } if style != "" { opts.DiceBearStyle = avatar.DiceBearStyle(style) } if set != "" { opts.RoboHashSet = avatar.RoboHashSet(set) } p := avatar.Provider(provider) imageURL := avatar.URL(p, agentID, opts) if dryRun { fmt.Printf("url %-20s %s\n", agentID, imageURL) return nil } // Fetch image from provider tmpPath, err := shellavatar.Fetch(context.Background(), p, agentID, opts) if err != nil { return fmt.Errorf("fetch avatar: %w", err) } defer os.Remove(tmpPath) fmt.Printf("fetch %-20s %s\n", agentID, imageURL) // Upload to Matrix and set as avatar cfg, err := loadMatrixCfg(agentID) if err != nil { return err } client, err := shellmatrix.New(cfg.Matrix) if err != nil { return fmt.Errorf("matrix client: %w", err) } uri, err := client.SetAvatar(context.Background(), tmpPath) if err != nil { return err } fmt.Printf("ok %-20s avatar → %s\n", agentID, uri) return nil }, } cmd.Flags().StringVar(&provider, "provider", "dicebear", "Avatar provider: dicebear, robohash, multiavatar") cmd.Flags().StringVar(&style, "style", "", "DiceBear style: bottts, pixel-art, adventurer, shapes, fun-emoji, identicon, thumbs") cmd.Flags().StringVar(&set, "set", "", "RoboHash set: set1 (robots), set2 (monsters), set3 (heads), set4 (cats), set5 (humans)") cmd.Flags().IntVar(&size, "size", 256, "Image size in pixels (square)") cmd.Flags().BoolVar(&dryRun, "dry-run", false, "Only print the image URL without fetching or uploading") cmd.Flags().StringVar(&fromURL, "from-url", "", "Use this URL as the avatar source (overrides provider/style)") cmd.Flags().StringVar(&fromFile, "from-file", "", "Use this local file as the avatar source (overrides provider/style)") return cmd } // runCustomAvatar uploads a user-supplied image (URL or local file) as the agent's avatar. func runCustomAvatar(agentID, fromURL, fromFile string, dryRun bool) error { var srcPath string var srcLabel string if fromURL != "" { srcLabel = fromURL if dryRun { fmt.Printf("url %-20s %s\n", agentID, fromURL) return nil } tmpPath, err := shellavatar.Download(context.Background(), fromURL) if err != nil { return fmt.Errorf("download avatar from %s: %w", fromURL, err) } defer os.Remove(tmpPath) srcPath = tmpPath } else { srcLabel = fromFile if _, err := os.Stat(fromFile); err != nil { return fmt.Errorf("avatar file %s: %w", fromFile, err) } if dryRun { fmt.Printf("file %-20s %s\n", agentID, fromFile) return nil } srcPath = fromFile } fmt.Printf("fetch %-20s %s\n", agentID, srcLabel) cfg, err := loadMatrixCfg(agentID) if err != nil { return err } client, err := shellmatrix.New(cfg.Matrix) if err != nil { return fmt.Errorf("matrix client: %w", err) } uri, err := client.SetAvatar(context.Background(), srcPath) if err != nil { return err } fmt.Printf("ok %-20s avatar → %s\n", agentID, uri) return nil }