@@ -31,6 +31,9 @@ pub enum AgentSubcommand {
3131 /// Create a new agent interactively.
3232 Create ( CreateArgs ) ,
3333
34+ /// Edit an existing agent in your default editor.
35+ Edit ( EditArgs ) ,
36+
3437 /// Remove a user-defined agent.
3538 Remove ( RemoveArgs ) ,
3639}
@@ -113,6 +116,17 @@ pub struct CreateArgs {
113116 pub model : String ,
114117}
115118
119+ /// Arguments for edit command.
120+ #[ derive( Debug , Parser ) ]
121+ pub struct EditArgs {
122+ /// Name of the agent to edit.
123+ pub name : String ,
124+
125+ /// Editor to use (defaults to $EDITOR or $VISUAL).
126+ #[ arg( short, long) ]
127+ pub editor : Option < String > ,
128+ }
129+
116130/// Arguments for remove command.
117131#[ derive( Debug , Parser ) ]
118132pub struct RemoveArgs {
@@ -292,6 +306,7 @@ impl AgentCli {
292306 AgentSubcommand :: List ( args) => run_list ( args) . await ,
293307 AgentSubcommand :: Show ( args) => run_show ( args) . await ,
294308 AgentSubcommand :: Create ( args) => run_create ( args) . await ,
309+ AgentSubcommand :: Edit ( args) => run_edit ( args) . await ,
295310 AgentSubcommand :: Remove ( args) => run_remove ( args) . await ,
296311 }
297312 }
@@ -1291,6 +1306,121 @@ mode: {mode}
12911306 Ok ( ( ) )
12921307}
12931308
1309+ /// Edit agent command.
1310+ ///
1311+ /// Opens the agent file in the user's default editor, then validates the file
1312+ /// after editing. If validation fails, offers to re-open the editor to fix issues.
1313+ async fn run_edit ( args : EditArgs ) -> Result < ( ) > {
1314+ let agents = load_all_agents ( ) ?;
1315+
1316+ let agent = agents
1317+ . iter ( )
1318+ . find ( |a| a. name == args. name )
1319+ . ok_or_else ( || anyhow:: anyhow!( "Agent '{}' not found" , args. name) ) ?;
1320+
1321+ if agent. native {
1322+ bail ! (
1323+ "Cannot edit built-in agent '{}'.\n \n \
1324+ Built-in agents are part of the Cortex core and cannot be modified.\n \
1325+ To customize this agent, create a copy:\n \
1326+ cortex agent create my-{}",
1327+ args. name,
1328+ args. name
1329+ ) ;
1330+ }
1331+
1332+ let path = agent
1333+ . path
1334+ . as_ref ( )
1335+ . ok_or_else ( || anyhow:: anyhow!( "Agent '{}' has no file path" , args. name) ) ?;
1336+
1337+ // Determine the editor to use
1338+ let editor = args
1339+ . editor
1340+ . or_else ( || std:: env:: var ( "VISUAL" ) . ok ( ) )
1341+ . or_else ( || std:: env:: var ( "EDITOR" ) . ok ( ) )
1342+ . unwrap_or_else ( || {
1343+ if cfg ! ( windows) {
1344+ "notepad" . to_string ( )
1345+ } else {
1346+ "vi" . to_string ( )
1347+ }
1348+ } ) ;
1349+
1350+ // Make a backup of the original file
1351+ let backup_content = std:: fs:: read_to_string ( path)
1352+ . with_context ( || format ! ( "Failed to read agent file: {}" , path. display( ) ) ) ?;
1353+
1354+ loop {
1355+ // Open the editor
1356+ println ! ( "Opening {} in {}..." , path. display( ) , editor) ;
1357+ let status = std:: process:: Command :: new ( & editor)
1358+ . arg ( path)
1359+ . status ( )
1360+ . with_context ( || format ! ( "Failed to launch editor: {}" , editor) ) ?;
1361+
1362+ if !status. success ( ) {
1363+ bail ! ( "Editor exited with error" ) ;
1364+ }
1365+
1366+ // Read and validate the edited file
1367+ let content = std:: fs:: read_to_string ( path)
1368+ . with_context ( || format ! ( "Failed to read edited file: {}" , path. display( ) ) ) ?;
1369+
1370+ // Try to parse the frontmatter to validate
1371+ match parse_frontmatter ( & content) {
1372+ Ok ( ( frontmatter, _body) ) => {
1373+ // Validate required fields
1374+ if frontmatter. name . trim ( ) . is_empty ( ) {
1375+ eprintln ! ( "\n Error: Agent name cannot be empty." ) ;
1376+ } else if !frontmatter
1377+ . name
1378+ . chars ( )
1379+ . all ( |c| c. is_ascii_alphanumeric ( ) || c == '-' || c == '_' )
1380+ {
1381+ eprintln ! (
1382+ "\n Error: Agent name must contain only alphanumeric characters, hyphens, and underscores."
1383+ ) ;
1384+ } else {
1385+ // Validation passed
1386+ println ! ( "\n Agent '{}' updated successfully!" , frontmatter. name) ;
1387+ return Ok ( ( ) ) ;
1388+ }
1389+ }
1390+ Err ( e) => {
1391+ eprintln ! ( "\n Error: Invalid agent configuration: {}" , e) ;
1392+ }
1393+ }
1394+
1395+ // Validation failed - offer to re-edit or rollback
1396+ print ! (
1397+ "Would you like to (e)dit again, (r)ollback to original, or (k)eep invalid file? [e/r/k]: "
1398+ ) ;
1399+ io:: stdout ( ) . flush ( ) ?;
1400+
1401+ let mut input = String :: new ( ) ;
1402+ io:: stdin ( ) . lock ( ) . read_line ( & mut input) ?;
1403+
1404+ match input. trim ( ) . to_lowercase ( ) . as_str ( ) {
1405+ "r" | "rollback" => {
1406+ // Restore the backup
1407+ std:: fs:: write ( path, & backup_content)
1408+ . with_context ( || format ! ( "Failed to restore backup: {}" , path. display( ) ) ) ?;
1409+ println ! ( "Rolled back to original version." ) ;
1410+ return Ok ( ( ) ) ;
1411+ }
1412+ "k" | "keep" => {
1413+ eprintln ! ( "Warning: Keeping invalid configuration. The agent may fail to load." ) ;
1414+ return Ok ( ( ) ) ;
1415+ }
1416+ _ => {
1417+ // Default: re-edit
1418+ continue ;
1419+ }
1420+ }
1421+ }
1422+ }
1423+
12941424/// Remove agent command.
12951425async fn run_remove ( args : RemoveArgs ) -> Result < ( ) > {
12961426 let agents = load_all_agents ( ) ?;
0 commit comments