Files
docker-agent/cmd/root/alias.go
T
2026-03-09 12:04:59 +01:00

275 lines
7.3 KiB
Go

package root
import (
"fmt"
"path/filepath"
"sort"
"strings"
"github.com/mattn/go-runewidth"
"github.com/spf13/cobra"
"github.com/docker/docker-agent/pkg/cli"
"github.com/docker/docker-agent/pkg/config"
"github.com/docker/docker-agent/pkg/paths"
"github.com/docker/docker-agent/pkg/telemetry"
"github.com/docker/docker-agent/pkg/userconfig"
)
func newAliasCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "alias",
Short: "Manage aliases",
Long: "Create and manage aliases for agent configurations or catalog references.",
Example: ` # Create an alias for a catalog agent
docker-agent alias add code agentcatalog/notion-expert
# Create an alias for a local agent file
docker-agent alias add myagent ~/myagent.yaml
# List all registered aliases
docker-agent alias list
# Remove an alias
docker-agent alias remove code`,
GroupID: "advanced",
}
cmd.AddCommand(newAliasAddCmd())
cmd.AddCommand(newAliasListCmd())
cmd.AddCommand(newAliasRemoveCmd())
return cmd
}
type aliasAddFlags struct {
yolo bool
model string
hideToolResults bool
}
func newAliasAddCmd() *cobra.Command {
var flags aliasAddFlags
cmd := &cobra.Command{
Use: "add <alias-name> <agent-path>",
Short: "Add a new alias",
Long: `Add a new alias for an agent configuration or catalog reference.
You can optionally specify runtime options that will be applied whenever
the alias is used:
--yolo Automatically approve all tool calls without prompting
--model Override the agent's model (format: [agent=]provider/model)
--hide-tool-results Hide tool call results in the TUI`,
Example: ` # Create a simple alias
docker-agent alias add code agentcatalog/notion-expert
# Create an alias that always runs in yolo mode
docker-agent alias add yolo-coder agentcatalog/coder --yolo
# Create an alias with a specific model
docker-agent alias add fast-coder agentcatalog/coder --model openai/gpt-4o-mini
# Create an alias with hidden tool results
docker-agent alias add quiet agentcatalog/coder --hide-tool-results
# Create an alias with multiple options
docker-agent alias add turbo agentcatalog/coder --yolo --model anthropic/claude-sonnet-4-0`,
Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
return runAliasAddCommand(cmd, args, &flags)
},
}
cmd.Flags().BoolVar(&flags.yolo, "yolo", false, "Automatically approve all tool calls without prompting")
cmd.Flags().StringVar(&flags.model, "model", "", "Override agent model (format: [agent=]provider/model)")
cmd.Flags().BoolVar(&flags.hideToolResults, "hide-tool-results", false, "Hide tool call results in the TUI")
return cmd
}
func newAliasListCmd() *cobra.Command {
return &cobra.Command{
Use: "list",
Aliases: []string{"ls"},
Short: "List all registered aliases",
Args: cobra.NoArgs,
RunE: runAliasListCommand,
}
}
func newAliasRemoveCmd() *cobra.Command {
return &cobra.Command{
Use: "remove <alias-name>",
Aliases: []string{"rm"},
Short: "Remove a registered alias",
Args: cobra.ExactArgs(1),
RunE: runAliasRemoveCommand,
}
}
func runAliasAddCommand(cmd *cobra.Command, args []string, flags *aliasAddFlags) error {
telemetry.TrackCommand("alias", append([]string{"add"}, args...))
out := cli.NewPrinter(cmd.OutOrStdout())
name := args[0]
agentPath := args[1]
// Load existing config
cfg, err := userconfig.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
// Expand tilde in path if present
absAgentPath, err := expandTilde(agentPath)
if err != nil {
return err
}
// Convert relative paths to absolute for local files (not OCI references or URLs)
if !config.IsOCIReference(absAgentPath) && !config.IsURLReference(absAgentPath) && !filepath.IsAbs(absAgentPath) {
absAgentPath, err = filepath.Abs(absAgentPath)
if err != nil {
return fmt.Errorf("failed to resolve absolute path: %w", err)
}
}
// Create alias with options
alias := &userconfig.Alias{
Path: absAgentPath,
Yolo: flags.yolo,
Model: flags.model,
HideToolResults: flags.hideToolResults,
}
// Store the alias
if err := cfg.SetAlias(name, alias); err != nil {
return err
}
// Save to file
if err := cfg.Save(); err != nil {
return fmt.Errorf("failed to save config: %w", err)
}
out.Printf("Alias '%s' created successfully\n", name)
out.Printf(" Alias: %s\n", name)
out.Printf(" Agent: %s\n", absAgentPath)
if flags.yolo {
out.Printf(" Yolo: enabled\n")
}
if flags.model != "" {
out.Printf(" Model: %s\n", flags.model)
}
if flags.hideToolResults {
out.Printf(" Hide tool results: enabled\n")
}
if name == "default" {
out.Printf("\nYou can now run: docker agent run %s (or even docker agent run)\n", name)
} else {
out.Printf("\nYou can now run: docker agent run %s\n", name)
}
return nil
}
func runAliasListCommand(cmd *cobra.Command, args []string) error {
telemetry.TrackCommand("alias", append([]string{"list"}, args...))
out := cli.NewPrinter(cmd.OutOrStdout())
cfg, err := userconfig.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
allAliases := cfg.Aliases
if len(allAliases) == 0 {
out.Println("No aliases registered.")
out.Println("\nCreate an alias with: docker agent alias add <name> <agent-path>")
return nil
}
out.Printf("Registered aliases (%d):\n\n", len(allAliases))
// Sort aliases by name for consistent output
names := make([]string, 0, len(allAliases))
for name := range allAliases {
names = append(names, name)
}
sort.Strings(names)
// Find max name width for alignment (using display width for proper Unicode handling)
maxLen := 0
for _, name := range names {
maxLen = max(maxLen, runewidth.StringWidth(name))
}
for _, name := range names {
alias := allAliases[name]
padding := strings.Repeat(" ", maxLen-runewidth.StringWidth(name))
// Build options string
var options []string
if alias.Yolo {
options = append(options, "yolo")
}
if alias.Model != "" {
options = append(options, "model="+alias.Model)
}
if alias.HideToolResults {
options = append(options, "hide-tool-results")
}
if len(options) > 0 {
out.Printf(" %s%s → %s [%s]\n", name, padding, alias.Path, strings.Join(options, ", "))
} else {
out.Printf(" %s%s → %s\n", name, padding, alias.Path)
}
}
out.Println("\nRun an alias with: docker agent run <alias>")
return nil
}
func runAliasRemoveCommand(cmd *cobra.Command, args []string) error {
telemetry.TrackCommand("alias", append([]string{"remove"}, args...))
out := cli.NewPrinter(cmd.OutOrStdout())
name := args[0]
cfg, err := userconfig.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}
if !cfg.DeleteAlias(name) {
return fmt.Errorf("alias '%s' not found", name)
}
if err := cfg.Save(); err != nil {
return fmt.Errorf("failed to save config: %w", err)
}
out.Printf("Alias '%s' removed successfully\n", name)
return nil
}
// expandTilde expands the tilde in a path to the user's home directory
func expandTilde(path string) (string, error) {
if !strings.HasPrefix(path, "~/") {
return path, nil
}
homeDir := paths.GetHomeDir()
if homeDir == "" {
return "", fmt.Errorf("failed to get user home directory")
}
return filepath.Join(homeDir, strings.TrimPrefix(path, "~/")), nil
}