From 6b4db98a7f394724fc36ad63784ee3b3394d0fe7 Mon Sep 17 00:00:00 2001 From: th0truth Date: Sat, 8 Aug 2026 14:14:05 +0300 Subject: [PATCH 1/3] fix(groq): fix memory leaks and resource issues --- src/api/groq.c | 57 ++++++++++++++++++++++++++++++++------------------ src/util.c | 4 +++- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/src/api/groq.c b/src/api/groq.c index db8cee7..5c20154 100644 --- a/src/api/groq.c +++ b/src/api/groq.c @@ -1,4 +1,3 @@ -#include "base.h" #include "api/http.h" #include "api/groq.h" #include "cJSON.h" @@ -38,7 +37,9 @@ build_groq_payload(const char *hardware_json, const char *user_prompt) cJSON_AddStringToObject(sys_msg, "content", "You are an expert Linux system administrator. Analyze the provided JSON hardware telemetry " "and answer the user's prompt. " - "Rules: 1) Be exceptionally concise. 2) DO NOT use markdown code blocks (like ```bash). " + "Rules:" + "1) Be exceptionally concise. " + "2) DO NOT use markdown code blocks (like ```bash). " "3) Format your answer as a plain conversational paragraph or simple bullet points without markdown formatting. " "4) Do not repeat the raw hardware stats back to the user unless absolutely necessary."); cJSON_AddItemToArray(messages, sys_msg); @@ -46,20 +47,32 @@ build_groq_payload(const char *hardware_json, const char *user_prompt) /* User Role: The data and the question */ size_t combined_len = strlen(hardware_json) + strlen(user_prompt) + 128; char *combined_content = malloc(combined_len); + if (combined_content == NULL) { + cJSON_Delete(root); + return NULL; + } - if (combined_content != NULL) { - snprintf(combined_content, combined_len, - "Hardware Telemetry:\n%s\n\nUser Question:\n%s", - hardware_json, user_prompt); - cJSON *usr_msg = cJSON_CreateObject(); - cJSON_AddStringToObject(usr_msg, "role", "user"); - cJSON_AddStringToObject(usr_msg, "content", combined_content); - cJSON_AddItemToArray(messages, usr_msg); - free(combined_content); + snprintf( + combined_content, combined_len, + "Hardware Telemetry:\n%s\n\nUser Question:\n%s", + hardware_json, user_prompt); + + cJSON *usr_msg = cJSON_CreateObject(); + if (usr_msg == NULL) { + cJSON_Delete(root); + return NULL; } + + cJSON_AddStringToObject(usr_msg, "role", "user"); + cJSON_AddStringToObject(usr_msg, "content", combined_content); + cJSON_AddItemToArray(messages, usr_msg); + + free(combined_content); + char *payload_str = cJSON_PrintUnformatted(root); cJSON_Delete(root); + return payload_str; } @@ -106,15 +119,20 @@ groq_analyze_hardware(const char *hardware_json, const char *user_prompt) if (cJSON_IsString(content) && content->valuestring != NULL) { /* Print matching the aesthetic of src/display.c */ printf("\n\033[1;36m╭─ AI Hardware Analysis (Groq) \033[0m\n"); - - /* Print line by line with the left border */ - char *text = content->valuestring; - char *line = strtok(text, "\n"); - while (line != NULL) { - printf("\033[1;36m|\033[0m \033[1;37m%s\033[0m\n", line); - line = strtok(NULL, "\n"); + + char *text_copy = strdup(content->valuestring); + if (text_copy != NULL) { + char *saveptr = NULL; + char *line = strtok_r(text_copy, "\n", &saveptr); + + while (line != NULL) { + printf("\033[1;36m|\033[0m \033[1;37m%s\033[0m\n", line); + line = strtok(NULL, "\n"); + } + + free(text_copy); } - + printf("\033[1;36m╰─\033[0m\n"); success = true; } @@ -136,7 +154,6 @@ groq_analyze_hardware(const char *hardware_json, const char *user_prompt) fprintf(stderr, " \033[1;31m[ERROR]\033[0m Network request to Groq API failed.\n"); } - /* Deep Cleanup (Zero Leaks) */ free(payload); curl_slist_free_all(headers); http_free_response(&response); diff --git a/src/util.c b/src/util.c index a2c1ed0..aef3eb6 100644 --- a/src/util.c +++ b/src/util.c @@ -243,7 +243,8 @@ parse_arguments(int argc, char **argv, Config *config) break; case 'A': config->use_ai = true; - if (optarg != NULL) { + if (config->ai_prompt != NULL) { + free(config->ai_prompt); config->ai_prompt = strdup(optarg); } break; @@ -273,6 +274,7 @@ parse_arguments(int argc, char **argv, Config *config) break; case 'o': if (optarg != NULL) { + free(config->output_file); config->output_file = strdup(optarg); } config->use_json = true; From cbdc681684d2ccb5bc61fc5362dd7fc1e99b0992 Mon Sep 17 00:00:00 2001 From: th0truth Date: Sat, 8 Aug 2026 14:57:29 +0300 Subject: [PATCH 2/3] fix(groq): add print_wrapped with correct segment-based wrapping --- src/api/groq.c | 80 ++++++++++++++++++++++++++++++++++++++++---------- src/util.c | 4 +-- 2 files changed, 66 insertions(+), 18 deletions(-) diff --git a/src/api/groq.c b/src/api/groq.c index 5c20154..e9ad9be 100644 --- a/src/api/groq.c +++ b/src/api/groq.c @@ -4,9 +4,64 @@ #include #include #include +#include +#include #define GROQ_API_URL "https://api.groq.com/openai/v1/chat/completions" +/** + * \brief Prints text with word-wrapping to fit terminal width. + * \param[in] text The text to print. + * \param[in] max_width The terminal width in columns. + */ +static void +print_wrapped(const char *text, int max_width) +{ + const char *color = "\033[1;37m"; + const char *reset = "\033[0m"; + + int wrap_at = max_width; + if (wrap_at < 20) { + wrap_at = 20; + } + + while (*text != '\0') { + /* Handle embedded newlines */ + if (*text == '\n') { + printf("\n"); + text++; + continue; + } + + /* Determine length of current segment up to next newline or end */ + const char *new_line = strchr(text, '\n'); + int segment_len = new_line ? (int)(new_line - text) : (int)strlen(text); + + if (segment_len <= wrap_at) { + printf("%s%.*s%s\n", color, segment_len, text, reset); + text += segment_len; + if (new_line) { + text++; + } + } else { + int brk = wrap_at; + while (brk > 0 && text[brk] != ' ') { + brk--; + } + if (brk == 0) { + brk = wrap_at; + } + + printf("%s%.*s%s\n", color, brk, text, reset); + text += brk; + + while (*text == ' ' || *text == '\t') { + text++; + } + } + } +} + static char * build_groq_payload(const char *hardware_json, const char *user_prompt) { @@ -117,23 +172,16 @@ groq_analyze_hardware(const char *hardware_json, const char *user_prompt) cJSON *content = cJSON_GetObjectItem(message, "content"); if (cJSON_IsString(content) && content->valuestring != NULL) { - /* Print matching the aesthetic of src/display.c */ - printf("\n\033[1;36m╭─ AI Hardware Analysis (Groq) \033[0m\n"); - - char *text_copy = strdup(content->valuestring); - if (text_copy != NULL) { - char *saveptr = NULL; - char *line = strtok_r(text_copy, "\n", &saveptr); - - while (line != NULL) { - printf("\033[1;36m|\033[0m \033[1;37m%s\033[0m\n", line); - line = strtok(NULL, "\n"); - } - - free(text_copy); + /* Get terminal width dynamically, defaulting to 80 */ + struct winsize ws; + int terminal_width = 80; + if (ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws) == 0 && ws.ws_col > 0) { + terminal_width = ws.ws_col; } - - printf("\033[1;36m╰─\033[0m\n"); + + printf("\n\033[1;36m── AI Hardware Analysis (Groq) ──\033[0m\n\n"); + print_wrapped(content->valuestring, terminal_width); + printf("\n\033[1;36m────\033[0m\n"); success = true; } } else { diff --git a/src/util.c b/src/util.c index aef3eb6..d35ae7d 100644 --- a/src/util.c +++ b/src/util.c @@ -243,8 +243,8 @@ parse_arguments(int argc, char **argv, Config *config) break; case 'A': config->use_ai = true; - if (config->ai_prompt != NULL) { - free(config->ai_prompt); + if (optarg != NULL) { + free(config->ai_prompt); config->ai_prompt = strdup(optarg); } break; From 50f832f682a18e4c7dc1097049ccd47e2102587a Mon Sep 17 00:00:00 2001 From: th0truth Date: Sat, 8 Aug 2026 14:58:45 +0300 Subject: [PATCH 3/3] refactor: remove function docstring --- src/api/groq.c | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/api/groq.c b/src/api/groq.c index e9ad9be..94d701c 100644 --- a/src/api/groq.c +++ b/src/api/groq.c @@ -9,11 +9,6 @@ #define GROQ_API_URL "https://api.groq.com/openai/v1/chat/completions" -/** - * \brief Prints text with word-wrapping to fit terminal width. - * \param[in] text The text to print. - * \param[in] max_width The terminal width in columns. - */ static void print_wrapped(const char *text, int max_width) {