@@ -335,6 +335,10 @@ const CAT_ADVANCED: &str = "Advanced";
335335
336336#[ derive( Subcommand ) ]
337337enum Commands {
338+ /// Initialize AGENTS.md in the current directory.
339+ /// Creates a project-specific configuration file for AI agents.
340+ Init ( InitCommand ) ,
341+
338342 // ═══════════════════════════════════════════════════════════════════════════
339343 // Execution Modes
340344 // ═══════════════════════════════════════════════════════════════════════════
@@ -575,6 +579,25 @@ struct CompletionCommand {
575579 /// Falls back to bash if detection fails.
576580 #[ arg( value_enum) ]
577581 shell : Option < Shell > ,
582+
583+ /// Install completions to your shell configuration file.
584+ /// This will append an eval command to your shell's rc file
585+ /// (e.g., ~/.bashrc, ~/.zshrc) to enable tab completion.
586+ #[ arg( long = "install" ) ]
587+ install : bool ,
588+ }
589+
590+ /// Init command - initialize AGENTS.md.
591+ #[ derive( Args ) ]
592+ struct InitCommand {
593+ /// Force overwrite if AGENTS.md already exists.
594+ #[ arg( short = 'f' , long = "force" ) ]
595+ force : bool ,
596+
597+ /// Accept defaults without prompting (non-interactive mode).
598+ /// Useful for scripting and automation.
599+ #[ arg( short = 'y' , long = "yes" ) ]
600+ yes : bool ,
578601}
579602
580603/// Resume command.
@@ -903,6 +926,7 @@ async fn main() -> Result<()> {
903926 } ;
904927 run_tui ( initial_prompt, & cli. interactive ) . await
905928 }
929+ Some ( Commands :: Init ( init_cli) ) => run_init ( init_cli) . await ,
906930 Some ( Commands :: Run ( run_cli) ) => run_cli. run ( ) . await ,
907931 Some ( Commands :: Exec ( exec_cli) ) => exec_cli. run ( ) . await ,
908932 Some ( Commands :: Login ( login_cli) ) => {
@@ -966,8 +990,12 @@ async fn main() -> Result<()> {
966990 }
967991 Some ( Commands :: Completion ( completion_cli) ) => {
968992 let shell = completion_cli. shell . unwrap_or_else ( detect_shell_from_env) ;
969- generate_completions ( shell) ;
970- Ok ( ( ) )
993+ if completion_cli. install {
994+ install_completions ( shell)
995+ } else {
996+ generate_completions ( shell) ;
997+ Ok ( ( ) )
998+ }
971999 }
9721000 Some ( Commands :: Sandbox ( sandbox_args) ) => match sandbox_args. cmd {
9731001 SandboxCommand :: Macos ( cmd) => {
@@ -1204,6 +1232,133 @@ _cortex_quote_path() {
12041232 let _ = writer. write_all ( output. as_bytes ( ) ) ;
12051233}
12061234
1235+ /// Install shell completions to the user's shell configuration file.
1236+ fn install_completions ( shell : Shell ) -> Result < ( ) > {
1237+ use std:: fs:: OpenOptions ;
1238+ use std:: io:: Write ;
1239+
1240+ let home =
1241+ dirs:: home_dir ( ) . ok_or_else ( || anyhow:: anyhow!( "Could not determine home directory" ) ) ?;
1242+
1243+ let ( rc_file, eval_cmd) = match shell {
1244+ Shell :: Bash => ( home. join ( ".bashrc" ) , r#"eval "$(cortex completion bash)""# ) ,
1245+ Shell :: Zsh => ( home. join ( ".zshrc" ) , r#"eval "$(cortex completion zsh)""# ) ,
1246+ Shell :: Fish => {
1247+ // Fish uses a different approach - write completion file directly
1248+ let fish_dir = home. join ( ".config/fish/completions" ) ;
1249+ std:: fs:: create_dir_all ( & fish_dir) ?;
1250+ let fish_file = fish_dir. join ( "cortex.fish" ) ;
1251+
1252+ let mut cmd = Cli :: command ( ) ;
1253+ let mut output = Vec :: new ( ) ;
1254+ generate ( shell, & mut cmd, "cortex" , & mut output) ;
1255+ std:: fs:: write ( & fish_file, output) ?;
1256+
1257+ println ! ( "Completions installed to: {}" , fish_file. display( ) ) ;
1258+ println ! (
1259+ "Restart your shell or run 'source {}' to activate." ,
1260+ fish_file. display( )
1261+ ) ;
1262+ return Ok ( ( ) ) ;
1263+ }
1264+ Shell :: PowerShell => {
1265+ // PowerShell uses profile
1266+ let profile_dir = if cfg ! ( windows) {
1267+ home. join ( "Documents/PowerShell" )
1268+ } else {
1269+ home. join ( ".config/powershell" )
1270+ } ;
1271+ std:: fs:: create_dir_all ( & profile_dir) ?;
1272+
1273+ (
1274+ profile_dir. join ( "Microsoft.PowerShell_profile.ps1" ) ,
1275+ "cortex completion powershell | Out-String | Invoke-Expression" ,
1276+ )
1277+ }
1278+ Shell :: Elvish => {
1279+ // Elvish uses lib directory
1280+ let elvish_dir = home. join ( ".elvish/lib" ) ;
1281+ std:: fs:: create_dir_all ( & elvish_dir) ?;
1282+ let elvish_file = elvish_dir. join ( "cortex.elv" ) ;
1283+
1284+ let mut cmd = Cli :: command ( ) ;
1285+ let mut output = Vec :: new ( ) ;
1286+ generate ( shell, & mut cmd, "cortex" , & mut output) ;
1287+ std:: fs:: write ( & elvish_file, output) ?;
1288+
1289+ println ! ( "Completions installed to: {}" , elvish_file. display( ) ) ;
1290+ println ! ( "Add 'use cortex' to your rc.elv to activate." ) ;
1291+ return Ok ( ( ) ) ;
1292+ }
1293+ _ => {
1294+ bail ! (
1295+ "Shell {:?} is not supported for automatic installation. Use 'cortex completion {:?}' to generate the script manually." ,
1296+ shell,
1297+ shell
1298+ ) ;
1299+ }
1300+ } ;
1301+
1302+ // Check if already installed
1303+ if rc_file. exists ( ) {
1304+ let content = std:: fs:: read_to_string ( & rc_file) ?;
1305+ if content. contains ( "cortex completion" ) {
1306+ println ! ( "Completions already installed in {}" , rc_file. display( ) ) ;
1307+ println ! ( "If completions aren't working, restart your shell." ) ;
1308+ return Ok ( ( ) ) ;
1309+ }
1310+ }
1311+
1312+ // Append the eval command to the rc file
1313+ let mut file = OpenOptions :: new ( )
1314+ . create ( true )
1315+ . append ( true )
1316+ . open ( & rc_file) ?;
1317+
1318+ writeln ! ( file) ?;
1319+ writeln ! ( file, "# Cortex CLI completions" ) ?;
1320+ writeln ! ( file, "{}" , eval_cmd) ?;
1321+
1322+ println ! ( "Completions installed to: {}" , rc_file. display( ) ) ;
1323+ println ! (
1324+ "Restart your shell or run 'source {}' to activate." ,
1325+ rc_file. display( )
1326+ ) ;
1327+
1328+ Ok ( ( ) )
1329+ }
1330+
1331+ /// Run the init command to create AGENTS.md.
1332+ async fn run_init ( init_cli : InitCommand ) -> Result < ( ) > {
1333+ use cortex_commands:: builtin:: InitCommand as InitCmd ;
1334+ use std:: io:: IsTerminal ;
1335+
1336+ let cwd = std:: env:: current_dir ( ) ?;
1337+
1338+ // Check if we're in a TTY and --yes wasn't provided
1339+ let is_tty = std:: io:: stdin ( ) . is_terminal ( ) && std:: io:: stdout ( ) . is_terminal ( ) ;
1340+
1341+ // If not TTY and --yes not provided, fail gracefully instead of crashing
1342+ if !is_tty && !init_cli. yes {
1343+ bail ! (
1344+ "Non-interactive mode detected but --yes flag not provided.\n \
1345+ Use 'cortex init --yes' to run non-interactively with default settings."
1346+ ) ;
1347+ }
1348+
1349+ let cmd = InitCmd :: new ( ) . force ( init_cli. force ) ;
1350+
1351+ match cmd. execute ( & cwd) {
1352+ Ok ( result) => {
1353+ println ! ( "{}" , result. message( ) ) ;
1354+ Ok ( ( ) )
1355+ }
1356+ Err ( e) => {
1357+ bail ! ( "Failed to initialize: {}" , e) ;
1358+ }
1359+ }
1360+ }
1361+
12071362async fn run_resume ( resume_cli : ResumeCommand ) -> Result < ( ) > {
12081363 use cortex_protocol:: ConversationId ;
12091364
0 commit comments