@@ -11,6 +11,7 @@ import (
1111 "os"
1212 "os/signal"
1313 "path/filepath"
14+ "sort"
1415 "strings"
1516 "time"
1617
@@ -21,6 +22,7 @@ import (
2122
2223 "github.com/docker/cagent/pkg/app"
2324 "github.com/docker/cagent/pkg/chat"
25+ "github.com/docker/cagent/pkg/config"
2426 "github.com/docker/cagent/pkg/content"
2527 "github.com/docker/cagent/pkg/evaluation"
2628 "github.com/docker/cagent/pkg/remote"
4042 useTUI bool
4143 remoteAddress string
4244 dryRun bool
45+ commandName string
4346)
4447
48+ const commandListSentinel = "__LIST__"
49+
4550// NewRunCmd creates a new run command
4651func NewRunCmd () * cobra.Command {
4752 cmd := & cobra.Command {
@@ -64,6 +69,11 @@ func NewRunCmd() *cobra.Command {
6469 cmd .PersistentFlags ().StringVar (& attachmentPath , "attach" , "" , "Attach an image file to the message" )
6570 cmd .PersistentFlags ().BoolVar (& useTUI , "tui" , true , "Run the agent with a Terminal User Interface (TUI)" )
6671 cmd .PersistentFlags ().StringVar (& remoteAddress , "remote" , "" , "Use remote runtime with specified address (only supported with TUI)" )
72+ cmd .PersistentFlags ().StringVarP (& commandName , "command" , "c" , "" , "Run a named command from the agent's commands section" )
73+ if f := cmd .PersistentFlags ().Lookup ("command" ); f != nil {
74+ // Allow `-c` without value to list available commands
75+ f .NoOptDefVal = commandListSentinel
76+ }
6777 addGatewayFlags (cmd )
6878
6979 return cmd
@@ -200,6 +210,52 @@ func doRunCommand(ctx context.Context, args []string, exec bool) error {
200210 slog .Debug ("Skipping local agent file loading for remote runtime" , "filename" , agentFilename )
201211 }
202212
213+ // Resolve --command/-c into a first message if provided
214+ var commandFirstMessage * string
215+ if trimmed := strings .TrimSpace (commandName ); trimmed != "" {
216+ // Handle listing commands when -c is provided without a value
217+ if trimmed == commandListSentinel {
218+ // If the next positional arg looks like a value (not a flag), treat it as the command value.
219+ if len (args ) == 2 && ! strings .HasPrefix (args [1 ], "-" ) {
220+ trimmed = args [1 ]
221+ // consume the positional so it won't be treated as a message later
222+ args = args [:1 ]
223+ } else {
224+ cmds , err := getCommandsForAgent (agentFilename , remoteAddress != "" , agents , agentName )
225+ if err != nil {
226+ return err
227+ }
228+ if len (cmds ) == 0 {
229+ return fmt .Errorf ("No commands defined for agent '%s'." , agentName )
230+ }
231+ printAvailableCommands (agentName , cmds )
232+ fmt .Println ()
233+ return nil
234+ }
235+ }
236+
237+ if len (args ) == 2 {
238+ return fmt .Errorf ("cannot use --command (-c) together with a message argument" )
239+ }
240+
241+ cmds , err := getCommandsForAgent (agentFilename , remoteAddress != "" , agents , agentName )
242+ if err != nil {
243+ return err
244+ }
245+ if len (cmds ) == 0 {
246+ return fmt .Errorf ("agent '%s' has no commands" , agentName )
247+ }
248+ if msg , ok := cmds [trimmed ]; ok {
249+ commandFirstMessage = & msg
250+ } else {
251+ var names []string
252+ for k := range cmds {
253+ names = append (names , k )
254+ }
255+ return fmt .Errorf ("'%s' is an unknown command.\n \n Available: %s" , trimmed , strings .Join (names , ", " ))
256+ }
257+ }
258+
203259 // Validate remote flag usage
204260 if remoteAddress != "" && (! useTUI || exec ) {
205261 return fmt .Errorf ("--remote flag can only be used with TUI mode" )
@@ -267,6 +323,10 @@ func doRunCommand(ctx context.Context, args []string, exec bool) error {
267323
268324 // For `cagent run --tui=false`
269325 if ! useTUI {
326+ // Inject first message for non-TUI if --command was used
327+ if commandFirstMessage != nil {
328+ args = []string {args [0 ], * commandFirstMessage }
329+ }
270330 return runWithoutTUI (ctx , agentFilename , rt , sess , args )
271331 }
272332
@@ -286,6 +346,11 @@ func doRunCommand(ctx context.Context, args []string, exec bool) error {
286346 }
287347 }
288348
349+ // Override firstMessage if --command was provided (cannot be combined with a message arg)
350+ if commandFirstMessage != nil {
351+ firstMessage = commandFirstMessage
352+ }
353+
289354 a := app .New ("cagent" , agentFilename , rt , agents , sess , firstMessage )
290355 m := tui .New (a )
291356
@@ -767,3 +832,50 @@ func fileToDataURL(filePath string) (string, error) {
767832
768833 return dataURL , nil
769834}
835+
836+ // getCommandsForAgent returns the commands map for the selected agent,
837+ // loading from the in-memory team for local runs or from the YAML file for remote runs.
838+ func getCommandsForAgent (agentFilename string , isRemote bool , agents * team.Team , agentName string ) (map [string ]string , error ) {
839+ if ! isRemote {
840+ if agents == nil {
841+ return nil , fmt .Errorf ("failed to load agent team" )
842+ }
843+ ag := agents .Agent (agentName )
844+ if ag == nil {
845+ return nil , fmt .Errorf ("agent not found: %s" , agentName )
846+ }
847+ return ag .Commands (), nil
848+ }
849+
850+ parentDir := filepath .Dir (agentFilename )
851+ fileName := filepath .Base (agentFilename )
852+ root , err := os .OpenRoot (parentDir )
853+ if err != nil {
854+ return nil , fmt .Errorf ("failed to open root: %w" , err )
855+ }
856+ defer func () {
857+ if err := root .Close (); err != nil {
858+ slog .Error ("Failed to close root" , "error" , err )
859+ }
860+ }()
861+
862+ cfg , err := config .LoadConfig (fileName , root )
863+ if err != nil {
864+ return nil , fmt .Errorf ("failed to load agent config: %w" , err )
865+ }
866+
867+ return map [string ]string (cfg .Agents [agentName ].Commands ), nil
868+ }
869+
870+ // printAvailableCommands pretty-prints the agent's commands sorted by name.
871+ func printAvailableCommands (agentName string , cmds map [string ]string ) {
872+ fmt .Printf ("Available commands for agent '%s':\n " , agentName )
873+ var names []string
874+ for k := range cmds {
875+ names = append (names , k )
876+ }
877+ sort .Strings (names )
878+ for _ , n := range names {
879+ fmt .Printf (" - %s: %s\n " , n , cmds [n ])
880+ }
881+ }
0 commit comments