2026-01-28 19:34:39 +01:00
|
|
|
// retoor <retoor@molodetz.nl>
|
|
|
|
|
|
|
|
|
|
#include "agent.h"
|
|
|
|
|
#include "db.h"
|
|
|
|
|
#include "http_client.h"
|
|
|
|
|
#include "r_config.h"
|
|
|
|
|
#include "r_error.h"
|
|
|
|
|
#include "tool.h"
|
|
|
|
|
|
2026-01-29 02:27:20 +01:00
|
|
|
#include "line.h"
|
|
|
|
|
#include "markdown.h"
|
|
|
|
|
#include "utils.h"
|
2026-01-28 19:34:39 +01:00
|
|
|
|
|
|
|
|
#include <json-c/json.h>
|
|
|
|
|
#include <locale.h>
|
|
|
|
|
#include <signal.h>
|
|
|
|
|
#include <stdbool.h>
|
|
|
|
|
#include <stdio.h>
|
|
|
|
|
#include <stdlib.h>
|
|
|
|
|
#include <string.h>
|
|
|
|
|
#include <time.h>
|
|
|
|
|
#include <unistd.h>
|
|
|
|
|
|
|
|
|
|
static volatile sig_atomic_t sigint_count = 0;
|
|
|
|
|
static time_t first_sigint_time = 0;
|
|
|
|
|
static bool syntax_highlight_enabled = true;
|
|
|
|
|
static bool api_mode = false;
|
|
|
|
|
|
|
|
|
|
static db_handle global_db = NULL;
|
|
|
|
|
static messages_handle global_messages = NULL;
|
|
|
|
|
|
|
|
|
|
extern tool_registry_t *tools_get_registry(void);
|
|
|
|
|
extern void tools_registry_shutdown(void);
|
|
|
|
|
|
|
|
|
|
static bool include_file(const char *path);
|
|
|
|
|
static char *get_prompt_from_stdin(char *prompt);
|
|
|
|
|
static char *get_prompt_from_args(int argc, char **argv);
|
|
|
|
|
static bool try_prompt(int argc, char *argv[]);
|
|
|
|
|
static void repl(void);
|
|
|
|
|
static void init(void);
|
|
|
|
|
static void cleanup(void);
|
|
|
|
|
static void handle_sigint(int sig);
|
|
|
|
|
|
|
|
|
|
static char *get_env_string(void) {
|
2026-01-28 19:47:14 +01:00
|
|
|
FILE *fp = popen("env", "r");
|
|
|
|
|
if (!fp)
|
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
|
|
size_t buffer_size = 1024;
|
|
|
|
|
size_t total_size = 0;
|
|
|
|
|
char *output = malloc(buffer_size);
|
|
|
|
|
if (!output) {
|
|
|
|
|
pclose(fp);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
size_t bytes_read;
|
|
|
|
|
while ((bytes_read = fread(output + total_size, 1, buffer_size - total_size,
|
|
|
|
|
fp)) > 0) {
|
|
|
|
|
total_size += bytes_read;
|
|
|
|
|
if (total_size >= buffer_size) {
|
|
|
|
|
buffer_size *= 2;
|
|
|
|
|
char *temp = realloc(output, buffer_size);
|
|
|
|
|
if (!temp) {
|
|
|
|
|
free(output);
|
2026-01-28 19:34:39 +01:00
|
|
|
pclose(fp);
|
|
|
|
|
return NULL;
|
2026-01-28 19:47:14 +01:00
|
|
|
}
|
|
|
|
|
output = temp;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
2026-01-28 19:47:14 +01:00
|
|
|
}
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-28 19:47:14 +01:00
|
|
|
output[total_size] = '\0';
|
|
|
|
|
pclose(fp);
|
|
|
|
|
return output;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static char *get_prompt_from_stdin(char *prompt) {
|
2026-01-28 19:47:14 +01:00
|
|
|
int index = 0;
|
|
|
|
|
int c;
|
|
|
|
|
while ((c = getchar()) != EOF) {
|
|
|
|
|
prompt[index++] = (char)c;
|
|
|
|
|
}
|
|
|
|
|
prompt[index] = '\0';
|
|
|
|
|
return prompt;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static char *get_prompt_from_args(int argc, char **argv) {
|
2026-01-28 19:47:14 +01:00
|
|
|
r_config_handle cfg = r_config_get_instance();
|
|
|
|
|
|
|
|
|
|
char *prompt = malloc(10 * 1024 * 1024 + 1);
|
|
|
|
|
char *system_msg = malloc(1024 * 1024);
|
|
|
|
|
if (!prompt || !system_msg) {
|
|
|
|
|
free(prompt);
|
|
|
|
|
free(system_msg);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
system_msg[0] = '\0';
|
|
|
|
|
bool get_from_stdin = false;
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
|
|
|
if (strcmp(argv[i], "--stdin") == 0) {
|
|
|
|
|
fprintf(stderr, "Reading from stdin.\n");
|
|
|
|
|
get_from_stdin = true;
|
|
|
|
|
} else if (strcmp(argv[i], "--verbose") == 0) {
|
|
|
|
|
r_config_set_verbose(cfg, true);
|
|
|
|
|
} else if (strcmp(argv[i], "--py") == 0 && i + 1 < argc) {
|
|
|
|
|
char *py_file_path = expand_home_directory(argv[++i]);
|
|
|
|
|
fprintf(stderr, "Including \"%s\".\n", py_file_path);
|
|
|
|
|
include_file(py_file_path);
|
|
|
|
|
free(py_file_path);
|
|
|
|
|
} else if (strcmp(argv[i], "--context") == 0 && i + 1 < argc) {
|
|
|
|
|
char *context_file_path = argv[++i];
|
|
|
|
|
fprintf(stderr, "Including \"%s\".\n", context_file_path);
|
|
|
|
|
include_file(context_file_path);
|
|
|
|
|
} else if (strcmp(argv[i], "--api") == 0) {
|
|
|
|
|
api_mode = true;
|
|
|
|
|
} else if (strcmp(argv[i], "--nh") == 0) {
|
|
|
|
|
syntax_highlight_enabled = false;
|
|
|
|
|
fprintf(stderr, "Syntax highlighting disabled.\n");
|
|
|
|
|
} else if (strncmp(argv[i], "--session=", 10) == 0) {
|
|
|
|
|
continue;
|
|
|
|
|
} else if (strcmp(argv[i], "-s") == 0 ||
|
|
|
|
|
strcmp(argv[i], "--session") == 0) {
|
|
|
|
|
i++;
|
|
|
|
|
continue;
|
2026-01-28 19:34:39 +01:00
|
|
|
} else {
|
2026-01-28 19:47:14 +01:00
|
|
|
strcat(system_msg, argv[i]);
|
|
|
|
|
strcat(system_msg, (i < argc - 1) ? " " : ".");
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
2026-01-28 19:47:14 +01:00
|
|
|
}
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-28 19:47:14 +01:00
|
|
|
if (get_from_stdin) {
|
|
|
|
|
if (*system_msg && global_messages) {
|
|
|
|
|
messages_add(global_messages, "system", system_msg);
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
2026-01-28 19:47:14 +01:00
|
|
|
prompt = get_prompt_from_stdin(prompt);
|
|
|
|
|
free(system_msg);
|
|
|
|
|
} else {
|
|
|
|
|
free(prompt);
|
|
|
|
|
prompt = system_msg;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!*prompt) {
|
|
|
|
|
free(prompt);
|
|
|
|
|
return NULL;
|
|
|
|
|
}
|
|
|
|
|
return prompt;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool try_prompt(int argc, char *argv[]) {
|
2026-01-28 19:47:14 +01:00
|
|
|
char *prompt = get_prompt_from_args(argc, argv);
|
|
|
|
|
if (prompt) {
|
|
|
|
|
char *response = agent_chat(prompt, global_messages);
|
|
|
|
|
if (!response) {
|
|
|
|
|
printf("Could not get response from server\n");
|
|
|
|
|
free(prompt);
|
|
|
|
|
return false;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
2026-01-28 20:06:18 +01:00
|
|
|
// response is already printed inside agent_run
|
2026-01-28 19:47:14 +01:00
|
|
|
free(response);
|
|
|
|
|
free(prompt);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
return false;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static bool include_file(const char *path) {
|
2026-01-28 19:47:14 +01:00
|
|
|
char *file_content = read_file(path);
|
|
|
|
|
if (!file_content)
|
|
|
|
|
return false;
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-28 19:47:14 +01:00
|
|
|
if (global_messages) {
|
|
|
|
|
messages_add(global_messages, "system", file_content);
|
|
|
|
|
}
|
|
|
|
|
free(file_content);
|
|
|
|
|
return true;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void repl(void) {
|
2026-01-28 19:47:14 +01:00
|
|
|
r_config_handle cfg = r_config_get_instance();
|
|
|
|
|
tool_registry_t *tools = tools_get_registry();
|
|
|
|
|
|
|
|
|
|
line_init();
|
|
|
|
|
char *line = NULL;
|
|
|
|
|
|
|
|
|
|
while (true) {
|
|
|
|
|
line = line_read("> ");
|
|
|
|
|
if (!line || !*line)
|
|
|
|
|
continue;
|
|
|
|
|
|
|
|
|
|
if (!strncmp(line, "!dump", 5)) {
|
|
|
|
|
char *json = messages_to_string(global_messages);
|
|
|
|
|
if (json) {
|
|
|
|
|
printf("%s\n", json);
|
|
|
|
|
free(json);
|
|
|
|
|
}
|
|
|
|
|
continue;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-28 19:47:14 +01:00
|
|
|
if (!strncmp(line, "!clear", 6)) {
|
|
|
|
|
messages_clear(global_messages);
|
|
|
|
|
fprintf(stderr, "Session cleared.\n");
|
|
|
|
|
continue;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
2026-01-28 19:47:14 +01:00
|
|
|
if (!strncmp(line, "!session", 8)) {
|
|
|
|
|
printf("Session: %s\n", messages_get_session_id(global_messages));
|
|
|
|
|
continue;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
2026-01-28 19:47:14 +01:00
|
|
|
if (!strncmp(line, "!verbose", 8)) {
|
|
|
|
|
bool verbose = !r_config_is_verbose(cfg);
|
|
|
|
|
r_config_set_verbose(cfg, verbose);
|
|
|
|
|
fprintf(stderr, "%s\n",
|
|
|
|
|
verbose ? "Verbose mode enabled" : "Verbose mode disabled");
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (line && *line != '\n') {
|
|
|
|
|
line_add_history(line);
|
|
|
|
|
}
|
|
|
|
|
if (!strncmp(line, "!tools", 6)) {
|
|
|
|
|
struct json_object *descs = tool_registry_get_descriptions(tools);
|
|
|
|
|
printf("Available tools: %s\n", json_object_to_json_string(descs));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!strncmp(line, "!models", 7)) {
|
|
|
|
|
http_client_handle http = http_client_create(r_config_get_api_key(cfg));
|
|
|
|
|
if (http) {
|
|
|
|
|
http_client_set_show_spinner(http, false);
|
|
|
|
|
char *response = NULL;
|
|
|
|
|
if (http_get(http, r_config_get_models_url(cfg), &response) ==
|
|
|
|
|
R_SUCCESS &&
|
|
|
|
|
response) {
|
|
|
|
|
printf("Models: %s\n", response);
|
|
|
|
|
free(response);
|
|
|
|
|
}
|
|
|
|
|
http_client_destroy(http);
|
|
|
|
|
}
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!strncmp(line, "!model", 6)) {
|
|
|
|
|
if (line[6] == ' ') {
|
|
|
|
|
r_config_set_model(cfg, line + 7);
|
|
|
|
|
}
|
|
|
|
|
printf("Current model: %s\n", r_config_get_model(cfg));
|
|
|
|
|
continue;
|
|
|
|
|
}
|
|
|
|
|
if (!strncmp(line, "exit", 4)) {
|
|
|
|
|
exit(0);
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
|
2026-01-28 19:47:14 +01:00
|
|
|
while (line && *line != '\n') {
|
|
|
|
|
char *response = agent_chat(line, global_messages);
|
|
|
|
|
if (response) {
|
2026-01-29 07:42:06 +01:00
|
|
|
// response is already printed inside agent_run via
|
|
|
|
|
// parse_markdown_to_ansi
|
2026-01-28 19:47:14 +01:00
|
|
|
free(response);
|
|
|
|
|
} else {
|
|
|
|
|
fprintf(stderr, "Agent returned no response\n");
|
|
|
|
|
}
|
2026-01-28 20:06:18 +01:00
|
|
|
line = NULL;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
2026-01-28 19:47:14 +01:00
|
|
|
}
|
|
|
|
|
}
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-28 19:47:14 +01:00
|
|
|
static void init(void) {
|
|
|
|
|
setbuf(stdout, NULL);
|
|
|
|
|
line_init();
|
|
|
|
|
|
|
|
|
|
r_config_handle cfg = r_config_get_instance();
|
|
|
|
|
|
|
|
|
|
global_db = db_open(NULL);
|
|
|
|
|
global_messages = messages_create(r_config_get_session_id(cfg));
|
|
|
|
|
|
|
|
|
|
char *schema = db_get_schema(global_db);
|
|
|
|
|
char payload[1024 * 1024] = {0};
|
|
|
|
|
|
|
|
|
|
time_t now = time(NULL);
|
|
|
|
|
struct tm *tm_info = localtime(&now);
|
|
|
|
|
char datetime[64];
|
|
|
|
|
strftime(datetime, sizeof(datetime), "%Y-%m-%d %H:%M:%S %Z", tm_info);
|
|
|
|
|
|
|
|
|
|
char cwd[4096];
|
|
|
|
|
if (!getcwd(cwd, sizeof(cwd))) {
|
|
|
|
|
strcpy(cwd, "unknown");
|
|
|
|
|
}
|
|
|
|
|
snprintf(
|
|
|
|
|
payload, sizeof(payload),
|
|
|
|
|
"# AUTONOMOUS AGENT INSTRUCTIONS\n"
|
|
|
|
|
"Current date/time: %s\n"
|
|
|
|
|
"Working directory: %s\n\n"
|
|
|
|
|
"You are an autonomous AI agent. You operate in a loop: reason about the "
|
|
|
|
|
"task, "
|
|
|
|
|
"select and execute tools when needed, observe results, and continue "
|
|
|
|
|
"until the goal is achieved.\n\n"
|
|
|
|
|
"## Reasoning Pattern (ReAct)\n"
|
2026-01-29 06:01:05 +01:00
|
|
|
"For EVERY task, you MUST follow this sequence:\n"
|
2026-01-29 08:06:31 +01:00
|
|
|
"1. Plan: Break the task into logical sub-tasks. DECIDE which specialized "
|
|
|
|
|
"agents to spawn. CREATE a visible CHECKLIST of all deliverables (files, "
|
|
|
|
|
"features, pages).\n"
|
2026-01-29 07:42:06 +01:00
|
|
|
"2. Execute: Spawn agents or use tools. INTEGRATE their results "
|
2026-01-29 08:06:31 +01:00
|
|
|
"immediately. Update your checklist as you progress.\n"
|
|
|
|
|
"3. Verify: Check EVERY item on your checklist. Run code, check file "
|
|
|
|
|
"existence, verify links. If an item is missing, go back to Execute.\n"
|
|
|
|
|
"4. Conclude: Only after ALL checklist items are verified, provide your "
|
|
|
|
|
"final response.\n\n"
|
|
|
|
|
"## Project Scale Rules\n"
|
|
|
|
|
"- HUGE PROJECTS: If a 'huge' or 'multi-page' project is requested, "
|
|
|
|
|
"delivering a single file is FORBIDDEN. You MUST create a directory "
|
|
|
|
|
"structure (e.g., assets/, css/, js/) and multiple linked HTML files.\n"
|
|
|
|
|
"- CHECKLIST PROTOCOL: Your first response to a complex request MUST "
|
|
|
|
|
"include the '## Checklist' you intend to fulfill.\n"
|
|
|
|
|
"- NO Lying: Never claim a task is done or a feature exists unless you "
|
|
|
|
|
"have the tool output to prove it.\n\n"
|
2026-01-29 06:01:05 +01:00
|
|
|
"## Multi-Agent Orchestration (MANDATORY)\n"
|
|
|
|
|
"You are the Lead Orchestrator. You MUST delegate specialized work:\n"
|
2026-01-29 07:42:06 +01:00
|
|
|
"- researcher: For ALL information gathering. Never research yourself if "
|
|
|
|
|
"you can spawn a researcher.\n"
|
2026-01-29 06:01:05 +01:00
|
|
|
"- developer: For ALL coding, testing, and debugging.\n"
|
|
|
|
|
"- security: For ALL security-related audits.\n\n"
|
2026-01-29 07:42:06 +01:00
|
|
|
"IMPORTANT: When a sub-agent returns a result, you MUST read it, "
|
|
|
|
|
"synthesize it, and then perform any necessary follow-up actions (like "
|
|
|
|
|
"writing to a file or spawning another agent). NEVER assume a task is "
|
|
|
|
|
"done just because a sub-agent finished; YOU must complete the final "
|
|
|
|
|
"delivery.\n\n"
|
|
|
|
|
"MANDATORY FINAL ACTION: If the user asked to save results to a file, "
|
|
|
|
|
"YOU must call the write_file tool yourself with the synthesized data "
|
|
|
|
|
"from the sub-agent. Do not ask for permission.\n\n"
|
2026-01-28 19:47:14 +01:00
|
|
|
"## Tool Usage\n"
|
2026-01-29 07:42:06 +01:00
|
|
|
"- Use tools proactively. If you say you will do something, you MUST "
|
|
|
|
|
"call the tool in the SAME or NEXT turn.\n"
|
2026-01-29 06:01:05 +01:00
|
|
|
"- If a tool fails, analyze and retry with a different approach.\n\n"
|
2026-01-28 19:47:14 +01:00
|
|
|
"## CRITICAL OUTPUT RULES\n"
|
2026-01-29 07:42:06 +01:00
|
|
|
"- SHOW THE DATA: Always include the actual content from tool/agent "
|
|
|
|
|
"results in your response.\n"
|
|
|
|
|
"- NO PREMATURE COMPLETION: Do not say 'task complete' until you have "
|
|
|
|
|
"verified all files are written and all steps are finished.\n"
|
|
|
|
|
"- SEQUENTIAL EXECUTION: Perform one logical step at a time. If you need "
|
|
|
|
|
"to research AND write a file, spawn the researcher first, wait for the "
|
|
|
|
|
"result, THEN write the file.\n"
|
|
|
|
|
"## EXECUTION OF PYTHON CODE\n"
|
|
|
|
|
"Exclusively use native python without 3rd party packages unless you "
|
|
|
|
|
"have checked that they're installed on the system.\n"
|
2026-01-28 19:47:14 +01:00
|
|
|
"## Local Database\n"
|
|
|
|
|
"You have a local SQLite database accessible via db_query, db_get, and "
|
|
|
|
|
"db_set tools.\n"
|
|
|
|
|
"Use stemmed, lowercase keys to prevent duplicates.\n"
|
|
|
|
|
"Schema: %s\n\n"
|
|
|
|
|
"## Response Format\n"
|
|
|
|
|
"Your response IS the only thing the user sees. Tool outputs are hidden "
|
|
|
|
|
"from them.\n"
|
|
|
|
|
"You MUST copy/paste relevant data from tool results into your "
|
|
|
|
|
"response.\n"
|
|
|
|
|
"Bad: 'I searched and found information about X.'\n"
|
|
|
|
|
"Good: 'Here is what I found: [actual content from search results]'\n"
|
2026-01-28 19:55:47 +01:00
|
|
|
"## Tool calls\n"
|
|
|
|
|
"As long you're not done, your last response must be always a tool call "
|
|
|
|
|
"to keep going.\n"
|
|
|
|
|
"Complete the whole tasks before bothering or informing user.\n"
|
|
|
|
|
"Backup\n"
|
2026-01-28 19:47:14 +01:00
|
|
|
"You have the power to delete and modify files whatever your want to but "
|
|
|
|
|
"always make a .bak file with backup before editing unless you've "
|
|
|
|
|
"created the file yourself."
|
|
|
|
|
"## Terminal execution\n"
|
|
|
|
|
"You are in a bash environment, so can also execute bash commands.\n "
|
|
|
|
|
"Everything is allowed.\n"
|
2026-01-29 06:01:05 +01:00
|
|
|
"Be very creative with your bash tool use, use it excessively.\n"
|
2026-01-28 19:55:47 +01:00
|
|
|
"Prefer commands that do not require root access.\n"
|
|
|
|
|
"## COMMUNICATION\n"
|
|
|
|
|
"You are only allowed to talk once, so do that absolutely last with your "
|
2026-01-29 07:42:06 +01:00
|
|
|
"conclusion.\n",
|
|
|
|
|
datetime, cwd, schema ? schema : "{}");
|
2026-01-28 19:47:14 +01:00
|
|
|
free(schema);
|
|
|
|
|
fprintf(stderr, "Loading...");
|
|
|
|
|
|
|
|
|
|
if (global_messages) {
|
|
|
|
|
messages_add(global_messages, "system", payload);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const char *env_system_msg = r_config_get_system_message(cfg);
|
|
|
|
|
if (env_system_msg && *env_system_msg && global_messages) {
|
|
|
|
|
messages_add(global_messages, "system", env_system_msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (!include_file(".rcontext.txt")) {
|
|
|
|
|
include_file("~/.rcontext.txt");
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fprintf(stderr, "\r \r");
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void cleanup(void) {
|
2026-01-28 19:47:14 +01:00
|
|
|
if (global_messages) {
|
|
|
|
|
messages_destroy(global_messages);
|
|
|
|
|
global_messages = NULL;
|
|
|
|
|
}
|
|
|
|
|
if (global_db) {
|
|
|
|
|
db_close(global_db);
|
|
|
|
|
global_db = NULL;
|
|
|
|
|
}
|
|
|
|
|
tools_registry_shutdown();
|
|
|
|
|
r_config_destroy();
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void handle_sigint(int sig) {
|
2026-01-28 19:47:14 +01:00
|
|
|
(void)sig;
|
|
|
|
|
time_t current_time = time(NULL);
|
|
|
|
|
printf("\n");
|
|
|
|
|
if (sigint_count == 0) {
|
|
|
|
|
first_sigint_time = current_time;
|
|
|
|
|
sigint_count++;
|
|
|
|
|
} else {
|
|
|
|
|
if (difftime(current_time, first_sigint_time) <= 1) {
|
|
|
|
|
cleanup();
|
|
|
|
|
exit(0);
|
2026-01-28 19:34:39 +01:00
|
|
|
} else {
|
2026-01-28 19:47:14 +01:00
|
|
|
sigint_count = 1;
|
|
|
|
|
first_sigint_time = current_time;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
2026-01-28 19:47:14 +01:00
|
|
|
}
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
static void parse_session_arg(int argc, char *argv[]) {
|
2026-01-28 19:47:14 +01:00
|
|
|
r_config_handle cfg = r_config_get_instance();
|
|
|
|
|
|
|
|
|
|
for (int i = 1; i < argc; i++) {
|
|
|
|
|
if (strncmp(argv[i], "--session=", 10) == 0) {
|
|
|
|
|
const char *name = argv[i] + 10;
|
|
|
|
|
if (!r_config_set_session_id(cfg, name)) {
|
|
|
|
|
fprintf(stderr, "Error: Invalid session name '%s'\n", name);
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
if ((strcmp(argv[i], "-s") == 0 || strcmp(argv[i], "--session") == 0) &&
|
|
|
|
|
i + 1 < argc) {
|
|
|
|
|
const char *name = argv[++i];
|
|
|
|
|
if (!r_config_set_session_id(cfg, name)) {
|
|
|
|
|
fprintf(stderr, "Error: Invalid session name '%s'\n", name);
|
|
|
|
|
exit(1);
|
|
|
|
|
}
|
|
|
|
|
return;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
2026-01-28 19:47:14 +01:00
|
|
|
}
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
int main(int argc, char *argv[]) {
|
2026-01-28 19:47:14 +01:00
|
|
|
signal(SIGINT, handle_sigint);
|
|
|
|
|
atexit(cleanup);
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-28 19:47:14 +01:00
|
|
|
parse_session_arg(argc, argv);
|
|
|
|
|
init();
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-28 19:47:14 +01:00
|
|
|
char *env_string = get_env_string();
|
|
|
|
|
if (env_string && *env_string && global_messages) {
|
|
|
|
|
messages_add(global_messages, "system", env_string);
|
|
|
|
|
free(env_string);
|
|
|
|
|
}
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-28 19:47:14 +01:00
|
|
|
messages_load(global_messages);
|
2026-01-28 19:34:39 +01:00
|
|
|
|
2026-01-28 19:47:14 +01:00
|
|
|
if (try_prompt(argc, argv)) {
|
2026-01-28 19:34:39 +01:00
|
|
|
return 0;
|
2026-01-28 19:47:14 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
repl();
|
|
|
|
|
return 0;
|
2026-01-28 19:34:39 +01:00
|
|
|
}
|