Skip to content

Commit 8f8550d

Browse files
authored
Merge pull request #1387 from dgageot/user-config
Add support for User config
2 parents 4ab6436 + fe03b3d commit 8f8550d

File tree

17 files changed

+1233
-255
lines changed

17 files changed

+1233
-255
lines changed

cmd/root/alias.go

Lines changed: 82 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,10 @@ import (
99
"github.com/mattn/go-runewidth"
1010
"github.com/spf13/cobra"
1111

12-
"github.com/docker/cagent/pkg/aliases"
1312
"github.com/docker/cagent/pkg/cli"
1413
"github.com/docker/cagent/pkg/paths"
1514
"github.com/docker/cagent/pkg/telemetry"
15+
"github.com/docker/cagent/pkg/userconfig"
1616
)
1717

1818
func newAliasCmd() *cobra.Command {
@@ -41,13 +41,45 @@ func newAliasCmd() *cobra.Command {
4141
return cmd
4242
}
4343

44+
type aliasAddFlags struct {
45+
yolo bool
46+
model string
47+
}
48+
4449
func newAliasAddCmd() *cobra.Command {
45-
return &cobra.Command{
50+
var flags aliasAddFlags
51+
52+
cmd := &cobra.Command{
4653
Use: "add <alias-name> <agent-path>",
4754
Short: "Add a new alias",
48-
Args: cobra.ExactArgs(2),
49-
RunE: runAliasAddCommand,
55+
Long: `Add a new alias for an agent configuration or catalog reference.
56+
57+
You can optionally specify runtime options that will be applied whenever
58+
the alias is used:
59+
60+
--yolo Automatically approve all tool calls without prompting
61+
--model Override the agent's model (format: [agent=]provider/model)`,
62+
Example: ` # Create a simple alias
63+
cagent alias add code agentcatalog/notion-expert
64+
65+
# Create an alias that always runs in yolo mode
66+
cagent alias add yolo-coder agentcatalog/coder --yolo
67+
68+
# Create an alias with a specific model
69+
cagent alias add fast-coder agentcatalog/coder --model openai/gpt-4o-mini
70+
71+
# Create an alias with both options
72+
cagent alias add turbo agentcatalog/coder --yolo --model anthropic/claude-sonnet-4-0`,
73+
Args: cobra.ExactArgs(2),
74+
RunE: func(cmd *cobra.Command, args []string) error {
75+
return runAliasAddCommand(cmd, args, &flags)
76+
},
5077
}
78+
79+
cmd.Flags().BoolVar(&flags.yolo, "yolo", false, "Automatically approve all tool calls without prompting")
80+
cmd.Flags().StringVar(&flags.model, "model", "", "Override agent model (format: [agent=]provider/model)")
81+
82+
return cmd
5183
}
5284

5385
func newAliasListCmd() *cobra.Command {
@@ -70,17 +102,17 @@ func newAliasRemoveCmd() *cobra.Command {
70102
}
71103
}
72104

73-
func runAliasAddCommand(cmd *cobra.Command, args []string) error {
105+
func runAliasAddCommand(cmd *cobra.Command, args []string, flags *aliasAddFlags) error {
74106
telemetry.TrackCommand("alias", append([]string{"add"}, args...))
75107

76108
out := cli.NewPrinter(cmd.OutOrStdout())
77109
name := args[0]
78110
agentPath := args[1]
79111

80-
// Load existing aliases
81-
s, err := aliases.Load()
112+
// Load existing config
113+
cfg, err := userconfig.Load()
82114
if err != nil {
83-
return fmt.Errorf("failed to load aliases: %w", err)
115+
return fmt.Errorf("failed to load config: %w", err)
84116
}
85117

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

124+
// Create alias with options
125+
alias := &userconfig.Alias{
126+
Path: absAgentPath,
127+
Yolo: flags.yolo,
128+
Model: flags.model,
129+
}
130+
92131
// Store the alias
93-
s.Set(name, absAgentPath)
132+
if err := cfg.SetAlias(name, alias); err != nil {
133+
return err
134+
}
94135

95136
// Save to file
96-
if err := s.Save(); err != nil {
97-
return fmt.Errorf("failed to save aliases: %w", err)
137+
if err := cfg.Save(); err != nil {
138+
return fmt.Errorf("failed to save config: %w", err)
98139
}
99140

100141
out.Printf("Alias '%s' created successfully\n", name)
101142
out.Printf(" Alias: %s\n", name)
102143
out.Printf(" Agent: %s\n", absAgentPath)
144+
if flags.yolo {
145+
out.Printf(" Yolo: enabled\n")
146+
}
147+
if flags.model != "" {
148+
out.Printf(" Model: %s\n", flags.model)
149+
}
103150

104151
if name == "default" {
105152
out.Printf("\nYou can now run: cagent run %s (or even cagent run)\n", name)
@@ -115,12 +162,12 @@ func runAliasListCommand(cmd *cobra.Command, args []string) error {
115162

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

118-
s, err := aliases.Load()
165+
cfg, err := userconfig.Load()
119166
if err != nil {
120-
return fmt.Errorf("failed to load aliases: %w", err)
167+
return fmt.Errorf("failed to load config: %w", err)
121168
}
122169

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

145192
for _, name := range names {
146-
path := allAliases[name]
193+
alias := allAliases[name]
147194
padding := strings.Repeat(" ", maxLen-runewidth.StringWidth(name))
148-
out.Printf(" %s%s → %s\n", name, padding, path)
195+
196+
// Build options string
197+
var options []string
198+
if alias.Yolo {
199+
options = append(options, "yolo")
200+
}
201+
if alias.Model != "" {
202+
options = append(options, "model="+alias.Model)
203+
}
204+
205+
if len(options) > 0 {
206+
out.Printf(" %s%s → %s [%s]\n", name, padding, alias.Path, strings.Join(options, ", "))
207+
} else {
208+
out.Printf(" %s%s → %s\n", name, padding, alias.Path)
209+
}
149210
}
150211

151212
out.Println("\nRun an alias with: cagent run <alias>")
@@ -159,17 +220,17 @@ func runAliasRemoveCommand(cmd *cobra.Command, args []string) error {
159220
out := cli.NewPrinter(cmd.OutOrStdout())
160221
name := args[0]
161222

162-
s, err := aliases.Load()
223+
cfg, err := userconfig.Load()
163224
if err != nil {
164-
return fmt.Errorf("failed to load aliases: %w", err)
225+
return fmt.Errorf("failed to load config: %w", err)
165226
}
166227

167-
if !s.Delete(name) {
228+
if !cfg.DeleteAlias(name) {
168229
return fmt.Errorf("alias '%s' not found", name)
169230
}
170231

171-
if err := s.Save(); err != nil {
172-
return fmt.Errorf("failed to save aliases: %w", err)
232+
if err := cfg.Save(); err != nil {
233+
return fmt.Errorf("failed to save config: %w", err)
173234
}
174235

175236
out.Printf("Alias '%s' removed successfully\n", name)

cmd/root/completion.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import (
88

99
"github.com/spf13/cobra"
1010

11-
"github.com/docker/cagent/pkg/aliases"
1211
"github.com/docker/cagent/pkg/config"
12+
"github.com/docker/cagent/pkg/userconfig"
1313
)
1414

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

3333
// Add matching aliases
34-
s, err := aliases.Load()
34+
cfg, err := userconfig.Load()
3535
if err == nil {
36-
for k, v := range s.List() {
36+
for k, v := range cfg.Aliases {
3737
if strings.HasPrefix(k, toComplete) {
38-
candidates = append(candidates, k+"\t"+v)
38+
candidates = append(candidates, k+"\t"+v.Path)
3939
}
4040
}
4141
}

cmd/root/config.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package root
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/goccy/go-yaml"
7+
"github.com/spf13/cobra"
8+
9+
"github.com/docker/cagent/pkg/cli"
10+
"github.com/docker/cagent/pkg/telemetry"
11+
"github.com/docker/cagent/pkg/userconfig"
12+
)
13+
14+
func newConfigCmd() *cobra.Command {
15+
cmd := &cobra.Command{
16+
Use: "config",
17+
Short: "Manage user configuration",
18+
Long: "View and manage user-level cagent configuration stored in ~/.config/cagent/config.yaml",
19+
Example: ` # Show the current configuration
20+
cagent config show
21+
22+
# Show the path to the config file
23+
cagent config path`,
24+
GroupID: "advanced",
25+
RunE: runConfigShowCommand,
26+
}
27+
28+
cmd.AddCommand(newConfigShowCmd())
29+
cmd.AddCommand(newConfigPathCmd())
30+
31+
return cmd
32+
}
33+
34+
func newConfigShowCmd() *cobra.Command {
35+
return &cobra.Command{
36+
Use: "show",
37+
Short: "Show the current configuration",
38+
Long: "Display the current user configuration in YAML format",
39+
Args: cobra.NoArgs,
40+
RunE: runConfigShowCommand,
41+
}
42+
}
43+
44+
func newConfigPathCmd() *cobra.Command {
45+
return &cobra.Command{
46+
Use: "path",
47+
Short: "Show the path to the config file",
48+
Args: cobra.NoArgs,
49+
RunE: runConfigPathCommand,
50+
}
51+
}
52+
53+
func runConfigShowCommand(cmd *cobra.Command, _ []string) error {
54+
telemetry.TrackCommand("config", []string{"show"})
55+
56+
out := cli.NewPrinter(cmd.OutOrStdout())
57+
58+
config, err := userconfig.Load()
59+
if err != nil {
60+
return fmt.Errorf("failed to load config: %w", err)
61+
}
62+
63+
data, err := yaml.MarshalWithOptions(config, yaml.IndentSequence(true), yaml.UseSingleQuote(false))
64+
if err != nil {
65+
return fmt.Errorf("failed to format config: %w", err)
66+
}
67+
68+
out.Print(string(data))
69+
return nil
70+
}
71+
72+
func runConfigPathCommand(cmd *cobra.Command, _ []string) error {
73+
telemetry.TrackCommand("config", []string{"path"})
74+
75+
out := cli.NewPrinter(cmd.OutOrStdout())
76+
out.Println(userconfig.Path())
77+
return nil
78+
}

0 commit comments

Comments
 (0)