@@ -330,10 +330,13 @@ static char *request_completion(const char *system_prompt, const char *user_prom
330330// Function pointers for testing
331331typedef char * (* NlToCommandFunc )(const char * );
332332typedef char * (* ExplainCommandFunc )(const char * );
333+ typedef char * (* SuggestFixFunc )(const char * , const char * , int );
333334
334335// Default to the real implementations
335336static NlToCommandFunc nl_to_command_impl = NULL ;
336337static ExplainCommandFunc explain_command_impl = NULL ;
338+ // Remove static to make it accessible to tests
339+ SuggestFixFunc suggest_fix_impl = NULL ;
337340
338341// The actual implementation functions
339342char * real_nl_to_command (const char * natural_language_query ) {
@@ -362,6 +365,32 @@ char* real_explain_command_ai(const char* command) {
362365 return request_completion (system_prompt , command );
363366}
364367
368+ // Define this function early so it can be referenced by set_ai_mock_functions
369+ char * real_suggest_fix (const char * command , const char * error , int exit_status ) {
370+ // System prompt for fix suggestions
371+ const char * system_prompt =
372+ "You are a shell command assistant helping fix errors. The user has run a command "
373+ "that produced an error. Please suggest a fix or alternative command.\n"
374+ "Rules:\n"
375+ "1. Analyze why the command failed and provide a brief explanation\n"
376+ "2. Suggest a corrected command that will likely work\n"
377+ "3. If there are multiple possible fixes, list the most likely one first\n"
378+ "4. Use Nutshell commands where appropriate (peekaboo for ls, hop for cd, etc.)\n"
379+ "5. Format your response with sections: 'Explanation:', 'Suggested Fix:', followed by 'Corrected command:' on a new line\n"
380+ "6. Be helpful and educational in your response" ;
381+
382+ // Create user prompt with context
383+ char user_prompt [4096 ];
384+ snprintf (user_prompt , sizeof (user_prompt ),
385+ "Command: %s\nOutput/Error: %s\nExit status: %d\n"
386+ "Please suggest how to fix this command. If the command has a typo or common error, "
387+ "show the corrected version. Format your response with 'Explanation:' followed by "
388+ "'Corrected command:' sections." ,
389+ command , error && strlen (error ) > 0 ? error : "No error output available" , exit_status );
390+
391+ return request_completion (system_prompt , user_prompt );
392+ }
393+
365394// Public API functions that use the function pointers
366395char * nl_to_command (const char * natural_language_query ) {
367396 if (nl_to_command_impl == NULL ) {
@@ -377,10 +406,19 @@ char* explain_command_ai(const char* command) {
377406 return explain_command_impl (command );
378407}
379408
380- // Function to set mock implementations for testing
409+ // Function to set mock implementations for testing - modified to match header
381410void set_ai_mock_functions (NlToCommandFunc nl_func , ExplainCommandFunc explain_func ) {
382411 nl_to_command_impl = nl_func ? nl_func : real_nl_to_command ;
383412 explain_command_impl = explain_func ? explain_func : real_explain_command_ai ;
413+ // Remove the third parameter handling
414+ }
415+
416+ // Public wrapper function
417+ char * suggest_fix (const char * command , const char * error , int exit_status ) {
418+ if (suggest_fix_impl == NULL ) {
419+ suggest_fix_impl = real_suggest_fix ;
420+ }
421+ return suggest_fix_impl (command , error , exit_status );
384422}
385423
386424// Suggest commands based on context
@@ -517,3 +555,153 @@ void reset_api_key_for_testing() {
517555 }
518556 AI_DEBUG ("API key reset for testing" );
519557}
558+
559+ // Built-in command to get fix suggestions for the last command
560+ int fix_command (int argc , char * * argv ) {
561+ // Suppress unused parameter warnings
562+ (void )argc ;
563+ (void )argv ;
564+
565+ // Import command history
566+ extern CommandHistory cmd_history ;
567+
568+ // Check if we have a last command
569+ if (!cmd_history .last_command || strlen (cmd_history .last_command ) == 0 ) {
570+ printf ("No previous command to fix.\n" );
571+ return 1 ;
572+ }
573+
574+ // Check if we have API key
575+ if (!has_api_key ()) {
576+ printf ("No OpenAI API key found. Please set one with: set-api-key YOUR_API_KEY\n" );
577+ return 1 ;
578+ }
579+
580+ printf ("Analyzing: %s\n" , cmd_history .last_command );
581+ if (cmd_history .has_error ) {
582+ printf ("Exit status: %d\n" , cmd_history .exit_status );
583+ }
584+
585+ // If there's no error output but we have a non-zero exit status, use a generic message
586+ const char * error_text = cmd_history .last_output && strlen (cmd_history .last_output ) > 0 ?
587+ cmd_history .last_output : "Command failed with no output" ;
588+
589+ // Get fix suggestion
590+ char * result = suggest_fix (cmd_history .last_command , error_text , cmd_history .exit_status );
591+
592+ if (result ) {
593+ printf ("\n%s\n\n" , result );
594+
595+ // Skip interactive prompt in testing mode
596+ if (getenv ("NUTSHELL_TESTING" )) {
597+ free (result );
598+ return 0 ;
599+ }
600+
601+ // Create a copy of the result for parsing
602+ char * result_copy = strdup (result );
603+ if (!result_copy ) {
604+ free (result );
605+ return 1 ;
606+ }
607+
608+ // Extract command from the suggestion
609+ char * suggested_cmd = NULL ;
610+ char * lines [100 ] = {0 }; // Increased max lines to 100
611+ int line_count = 0 ;
612+
613+ // Split the result into lines
614+ char * line = strtok (result_copy , "\n" );
615+ while (line && line_count < 100 ) {
616+ lines [line_count ++ ] = line ;
617+ line = strtok (NULL , "\n" );
618+ }
619+
620+ // Try to find a code block with a command
621+ bool in_code_block = false;
622+ char * code_block_content = NULL ;
623+
624+ AI_DEBUG ("Searching for command in AI response with %d lines" , line_count );
625+
626+ for (int i = 0 ; i < line_count ; i ++ ) {
627+ AI_DEBUG ("Line %d: %s" , i , lines [i ]);
628+
629+ // Check for code block markers (with or without language specifier)
630+ if (strncmp (lines [i ], "```" , 3 ) == 0 ) {
631+ if (!in_code_block ) {
632+ // Start of code block - skip this line
633+ in_code_block = true;
634+ AI_DEBUG ("Code block starts at line %d" , i );
635+ } else {
636+ // End of code block
637+ in_code_block = false;
638+ AI_DEBUG ("Code block ends at line %d" , i );
639+ }
640+ continue ;
641+ }
642+
643+ // If we're in a code block, check for actual command content
644+ if (in_code_block ) {
645+ // Skip empty lines or comments
646+ if (strlen (lines [i ]) == 0 || lines [i ][0 ] == '#' ) {
647+ continue ;
648+ }
649+
650+ // Found code within a code block
651+ code_block_content = lines [i ];
652+ AI_DEBUG ("Found command in code block: %s" , code_block_content );
653+ break ;
654+ }
655+
656+ // Look for lines after "Corrected command:" pattern
657+ if (strstr (lines [i ], "Corrected command:" ) != NULL && i + 1 < line_count ) {
658+ // The next line is likely our command
659+ // Skip to the next line that's not empty and doesn't start with ```
660+ for (int j = i + 1 ; j < line_count ; j ++ ) {
661+ if (strlen (lines [j ]) == 0 || strncmp (lines [j ], "```" , 3 ) == 0 ) {
662+ continue ;
663+ }
664+ suggested_cmd = lines [j ];
665+ AI_DEBUG ("Found command after 'Corrected command:' pattern: %s" , suggested_cmd );
666+ break ;
667+ }
668+ if (suggested_cmd ) {
669+ break ;
670+ }
671+ }
672+ }
673+
674+ // If we found a likely command, ask if user wants to execute it
675+ if (code_block_content ) {
676+ suggested_cmd = code_block_content ;
677+ }
678+
679+ if (suggested_cmd ) {
680+ printf ("Would you like to execute: %s ? (y/n): " , suggested_cmd );
681+ char response [10 ] = {0 };
682+ if (fgets (response , sizeof (response ), stdin )) {
683+ if (response [0 ] == 'y' || response [0 ] == 'Y' ) {
684+ // Create a temporary script and execute it
685+ char temp_script [1024 ];
686+ snprintf (temp_script , sizeof (temp_script ), "%s/.nutshell/temp_fix.sh" , getenv ("HOME" ));
687+ FILE * fp = fopen (temp_script , "w" );
688+ if (fp ) {
689+ fprintf (fp , "#!/bin/bash\n%s\n" , suggested_cmd );
690+ fclose (fp );
691+ chmod (temp_script , 0700 );
692+ system (temp_script );
693+ // Clean up
694+ unlink (temp_script );
695+ }
696+ }
697+ }
698+ }
699+
700+ free (result_copy );
701+ free (result );
702+ } else {
703+ printf ("Failed to get a fix suggestion.\n" );
704+ }
705+
706+ return 0 ;
707+ }
0 commit comments