Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 82 additions & 21 deletions cmd/root/alias.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,10 @@ import (
"github.com/mattn/go-runewidth"
"github.com/spf13/cobra"

"github.com/docker/cagent/pkg/aliases"
"github.com/docker/cagent/pkg/cli"
"github.com/docker/cagent/pkg/paths"
"github.com/docker/cagent/pkg/telemetry"
"github.com/docker/cagent/pkg/userconfig"
)

func newAliasCmd() *cobra.Command {
Expand Down Expand Up @@ -41,13 +41,45 @@ func newAliasCmd() *cobra.Command {
return cmd
}

type aliasAddFlags struct {
yolo bool
model string
}

func newAliasAddCmd() *cobra.Command {
return &cobra.Command{
var flags aliasAddFlags

cmd := &cobra.Command{
Use: "add <alias-name> <agent-path>",
Short: "Add a new alias",
Args: cobra.ExactArgs(2),
RunE: runAliasAddCommand,
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)`,
Example: ` # Create a simple alias
cagent alias add code agentcatalog/notion-expert

# Create an alias that always runs in yolo mode
cagent alias add yolo-coder agentcatalog/coder --yolo

# Create an alias with a specific model
cagent alias add fast-coder agentcatalog/coder --model openai/gpt-4o-mini

# Create an alias with both options
cagent 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)")

return cmd
}

func newAliasListCmd() *cobra.Command {
Expand All @@ -70,17 +102,17 @@ func newAliasRemoveCmd() *cobra.Command {
}
}

func runAliasAddCommand(cmd *cobra.Command, args []string) error {
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 aliases
s, err := aliases.Load()
// Load existing config
cfg, err := userconfig.Load()
if err != nil {
return fmt.Errorf("failed to load aliases: %w", err)
return fmt.Errorf("failed to load config: %w", err)
}

// Expand tilde in path if it's a local file path
Expand All @@ -89,17 +121,32 @@ func runAliasAddCommand(cmd *cobra.Command, args []string) error {
return err
}

// Create alias with options
alias := &userconfig.Alias{
Path: absAgentPath,
Yolo: flags.yolo,
Model: flags.model,
}

// Store the alias
s.Set(name, absAgentPath)
if err := cfg.SetAlias(name, alias); err != nil {
return err
}

// Save to file
if err := s.Save(); err != nil {
return fmt.Errorf("failed to save aliases: %w", err)
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 name == "default" {
out.Printf("\nYou can now run: cagent run %s (or even cagent run)\n", name)
Expand All @@ -115,12 +162,12 @@ func runAliasListCommand(cmd *cobra.Command, args []string) error {

out := cli.NewPrinter(cmd.OutOrStdout())

s, err := aliases.Load()
cfg, err := userconfig.Load()
if err != nil {
return fmt.Errorf("failed to load aliases: %w", err)
return fmt.Errorf("failed to load config: %w", err)
}

allAliases := s.List()
allAliases := cfg.Aliases
if len(allAliases) == 0 {
out.Println("No aliases registered.")
out.Println("\nCreate an alias with: cagent alias add <name> <agent-path>")
Expand All @@ -143,9 +190,23 @@ func runAliasListCommand(cmd *cobra.Command, args []string) error {
}

for _, name := range names {
path := allAliases[name]
alias := allAliases[name]
padding := strings.Repeat(" ", maxLen-runewidth.StringWidth(name))
out.Printf(" %s%s → %s\n", name, padding, path)

// Build options string
var options []string
if alias.Yolo {
options = append(options, "yolo")
}
if alias.Model != "" {
options = append(options, "model="+alias.Model)
}

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: cagent run <alias>")
Expand All @@ -159,17 +220,17 @@ func runAliasRemoveCommand(cmd *cobra.Command, args []string) error {
out := cli.NewPrinter(cmd.OutOrStdout())
name := args[0]

s, err := aliases.Load()
cfg, err := userconfig.Load()
if err != nil {
return fmt.Errorf("failed to load aliases: %w", err)
return fmt.Errorf("failed to load config: %w", err)
}

if !s.Delete(name) {
if !cfg.DeleteAlias(name) {
return fmt.Errorf("alias '%s' not found", name)
}

if err := s.Save(); err != nil {
return fmt.Errorf("failed to save aliases: %w", err)
if err := cfg.Save(); err != nil {
return fmt.Errorf("failed to save config: %w", err)
}

out.Printf("Alias '%s' removed successfully\n", name)
Expand Down
8 changes: 4 additions & 4 deletions cmd/root/completion.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import (

"github.com/spf13/cobra"

"github.com/docker/cagent/pkg/aliases"
"github.com/docker/cagent/pkg/config"
"github.com/docker/cagent/pkg/userconfig"
)

func completeRunExec(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) {
Expand All @@ -31,11 +31,11 @@ func completeAlias(toComplete string) ([]string, cobra.ShellCompDirective) {
var candidates []string

// Add matching aliases
s, err := aliases.Load()
cfg, err := userconfig.Load()
if err == nil {
for k, v := range s.List() {
for k, v := range cfg.Aliases {
if strings.HasPrefix(k, toComplete) {
candidates = append(candidates, k+"\t"+v)
candidates = append(candidates, k+"\t"+v.Path)
}
}
}
Expand Down
78 changes: 78 additions & 0 deletions cmd/root/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package root

import (
"fmt"

"github.com/goccy/go-yaml"
"github.com/spf13/cobra"

"github.com/docker/cagent/pkg/cli"
"github.com/docker/cagent/pkg/telemetry"
"github.com/docker/cagent/pkg/userconfig"
)

func newConfigCmd() *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: "Manage user configuration",
Long: "View and manage user-level cagent configuration stored in ~/.config/cagent/config.yaml",
Example: ` # Show the current configuration
cagent config show

# Show the path to the config file
cagent config path`,
GroupID: "advanced",
RunE: runConfigShowCommand,
}

cmd.AddCommand(newConfigShowCmd())
cmd.AddCommand(newConfigPathCmd())

return cmd
}

func newConfigShowCmd() *cobra.Command {
return &cobra.Command{
Use: "show",
Short: "Show the current configuration",
Long: "Display the current user configuration in YAML format",
Args: cobra.NoArgs,
RunE: runConfigShowCommand,
}
}

func newConfigPathCmd() *cobra.Command {
return &cobra.Command{
Use: "path",
Short: "Show the path to the config file",
Args: cobra.NoArgs,
RunE: runConfigPathCommand,
}
}

func runConfigShowCommand(cmd *cobra.Command, _ []string) error {
telemetry.TrackCommand("config", []string{"show"})

out := cli.NewPrinter(cmd.OutOrStdout())

config, err := userconfig.Load()
if err != nil {
return fmt.Errorf("failed to load config: %w", err)
}

data, err := yaml.MarshalWithOptions(config, yaml.IndentSequence(true), yaml.UseSingleQuote(false))
if err != nil {
return fmt.Errorf("failed to format config: %w", err)
}

out.Print(string(data))
return nil
}

func runConfigPathCommand(cmd *cobra.Command, _ []string) error {
telemetry.TrackCommand("config", []string{"path"})

out := cli.NewPrinter(cmd.OutOrStdout())
out.Println(userconfig.Path())
return nil
}
Loading