Skip to content

Commit 23230a8

Browse files
committed
feat: add 'fix' command for error suggestions and enhance command history tracking
1 parent 19d9891 commit 23230a8

8 files changed

Lines changed: 559 additions & 56 deletions

File tree

README.md

Lines changed: 52 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,4 @@
1-
```bash
2-
nutshell/
3-
├── src/
4-
│ ├── core/
5-
│ │ ├── shell.c # Main shell loop
6-
│ │ ├── parser.c # Command parsing
7-
│ │ └── executor.c # Command execution
8-
│ ├── ai/
9-
│ │ ├── openai.c # OpenAI integration
10-
│ │ └── local_ml.c # On-device ML
11-
│ ├── pkg/
12-
│ │ ├── nutpkg.c # Package manager core
13-
│ │ └── registry.c # Package registry handling
14-
│ ├── utils/
15-
│ │ ├── security.c # Security features
16-
│ │ ├── autocomplete.c # Tab completion
17-
│ │ └── helpers.c # Utility functions
18-
│ └── plugins/ # Loadable plugins
19-
├── include/
20-
│ ├── nutshell/
21-
│ │ ├── core.h
22-
│ │ ├── ai.h
23-
│ │ ├── pkg.h
24-
│ │ └── utils.h
25-
├── lib/ # 3rd party libs
26-
├── scripts/ # Build/install scripts
27-
├── packages/ # Local package cache
28-
├── tests/ # Test suite
29-
├── Makefile
30-
└── README.md
31-
```
32-
33-
# Nutshell
1+
# Nutshell 🥜
342

353
Nutshell is an enhanced Unix shell that provides a simplified command language, package management, and AI-powered assistance.
364

@@ -93,7 +61,7 @@ Nutshell command | Unix equivalent | Description
9361

9462
Commands work just like in a standard Unix shell:
9563

96-
```
64+
```bash
9765
🥜 ~/projects ➜ peekaboo -la
9866
🥜 ~/projects ➜ hop nutshell
9967
🥜 ~/projects/nutshell ➜ command arg1 arg2
@@ -107,7 +75,7 @@ Nutshell includes AI features to help with shell commands:
10775

10876
1. Get an OpenAI API key from [OpenAI Platform](https://platform.openai.com/)
10977
2. Set your API key:
110-
```
78+
```bash
11179
🥜 ~ ➜ set-api-key YOUR_API_KEY
11280
```
11381

@@ -122,7 +90,7 @@ Nutshell includes AI features to help with shell commands:
12290

12391
Convert natural language to shell commands:
12492

125-
```
93+
```bash
12694
🥜 ~ ➜ ask find all PDF files modified in the last week
12795
```
12896

@@ -132,10 +100,21 @@ The shell will return the proper command and ask if you want to execute it.
132100

133101
Get explanations for complex commands:
134102

135-
```
103+
```bash
136104
🥜 ~ ➜ explain find . -name "*.txt" -mtime -7 -exec grep -l "important" {} \;
137105
```
138106

107+
#### Fix commands
108+
109+
Automatically fix common command errors:
110+
111+
```bash
112+
🥜 ~ ➜ torch apple.txt
113+
🥜 ~ ➜ fix # Will suggest to use touch apple.txt instead.
114+
```
115+
116+
The shell will suggest corrections for common mistakes and ask if you want to apply them.
117+
139118
### Debug Options
140119

141120
Enable AI debugging with environment variables:
@@ -309,6 +288,42 @@ make test-pkg # Test package installation
309288
make test-ai # Test AI integration
310289
```
311290

291+
## Contributing
292+
293+
Contributions are welcome! Please read the [contributing guidelines](CONTRIBUTING.md) before submitting a pull request.
294+
295+
```bash
296+
nutshell/
297+
├── src/
298+
│ ├── core/
299+
│ │ ├── shell.c # Main shell loop
300+
│ │ ├── parser.c # Command parsing
301+
│ │ └── executor.c # Command execution
302+
│ ├── ai/
303+
│ │ ├── openai.c # OpenAI integration
304+
│ │ └── local_ml.c # On-device ML
305+
│ ├── pkg/
306+
│ │ ├── nutpkg.c # Package manager core
307+
│ │ └── registry.c # Package registry handling
308+
│ ├── utils/
309+
│ │ ├── security.c # Security features
310+
│ │ ├── autocomplete.c # Tab completion
311+
│ │ └── helpers.c # Utility functions
312+
│ └── plugins/ # Loadable plugins
313+
├── include/
314+
│ ├── nutshell/
315+
│ │ ├── core.h
316+
│ │ ├── ai.h
317+
│ │ ├── pkg.h
318+
│ │ └── utils.h
319+
├── lib/ # 3rd party libs
320+
├── scripts/ # Build/install scripts
321+
├── packages/ # Local package cache
322+
├── tests/ # Test suite
323+
├── Makefile
324+
└── README.md
325+
```
326+
312327
## License
313328

314329
[MIT License](LICENSE)

include/nutshell/ai.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
#define NUTSHELL_AI_H
33

44
#include <stdbool.h>
5+
#include <nutshell/core.h> // Add this to get ParsedCommand definition
56

67
// Initialize AI integration
78
bool init_ai_integration();
@@ -15,6 +16,15 @@ char *explain_command_ai(const char *command);
1516
// Get command suggestions based on context
1617
char *suggest_commands(const char *context);
1718

19+
// Get fix suggestion for an error
20+
char *suggest_fix(const char *command, const char *error, int exit_status);
21+
22+
// Handle AI commands in the shell
23+
bool handle_ai_command(ParsedCommand *cmd);
24+
25+
// Initialize AI shell integration
26+
void init_ai_shell();
27+
1828
// Cleanup AI resources
1929
void cleanup_ai_integration();
2030

include/nutshell/core.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,17 @@ typedef struct ParsedCommand {
3232
bool background;
3333
} ParsedCommand;
3434

35+
// Command history tracking
36+
typedef struct CommandHistory {
37+
char *last_command;
38+
char *last_output;
39+
int exit_status;
40+
bool has_error;
41+
} CommandHistory;
42+
43+
// Global command history for error fixing
44+
extern CommandHistory cmd_history;
45+
3546
// Registry functions
3647
void init_registry();
3748
void register_command(const char *unix_cmd, const char *nut_cmd, bool is_builtin);

src/ai/openai.c

Lines changed: 189 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -330,10 +330,13 @@ static char *request_completion(const char *system_prompt, const char *user_prom
330330
// Function pointers for testing
331331
typedef char* (*NlToCommandFunc)(const char*);
332332
typedef char* (*ExplainCommandFunc)(const char*);
333+
typedef char* (*SuggestFixFunc)(const char*, const char*, int);
333334

334335
// Default to the real implementations
335336
static NlToCommandFunc nl_to_command_impl = NULL;
336337
static 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
339342
char* 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
366395
char* 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
381410
void 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

Comments
 (0)