Update.
This commit is contained in:
parent
5cf55f423c
commit
930e0889fd
2
Makefile
2
Makefile
@ -2,7 +2,7 @@ all: build build_rpylib run build_mingw
|
|||||||
|
|
||||||
# Variables for compiler and flags
|
# Variables for compiler and flags
|
||||||
CC = gcc
|
CC = gcc
|
||||||
CFLAGS = -Ofast -Werror -Wall -lreadline -lncurses -lcurl -ljson-c -lsqlite3 -lm $(pkg-config --cflags --libs gnutls gmp) -lssl -lcrypto
|
CFLAGS = -Ofast -Werror -Wall -lreadline -lncurses -lcurl -ljson-c -lsqlite3 -lm -lpthread $(pkg-config --cflags --libs gnutls gmp) -lssl -lcrypto
|
||||||
|
|
||||||
# MinGW Variables
|
# MinGW Variables
|
||||||
MINGW_CC = x86_64-w64-mingw32-gcc # Change to x86_64-w64-mingw32-gcc for 64-bit
|
MINGW_CC = x86_64-w64-mingw32-gcc # Change to x86_64-w64-mingw32-gcc for 64-bit
|
||||||
|
|||||||
292
README.md
292
README.md
@ -1,36 +1,292 @@
|
|||||||
# Henry IRC Server
|
# R
|
||||||
|
|
||||||
A simple IRC server implemented in Python as a package named `henry`.
|
Author: retoor <retoor@molodetz.nl>
|
||||||
|
|
||||||
|
R is a command-line AI assistant written in C that provides an autonomous agent with full system access through function calling. It interfaces with OpenAI-compatible APIs and operates in an agentic loop, reasoning about tasks, executing tools, and iterating until goals are achieved.
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
- Basic IRC commands: NICK, USER, JOIN, PRIVMSG, QUIT
|
|
||||||
- Supports multiple clients and channels
|
### Autonomous Agent Architecture
|
||||||
|
- ReAct (Reasoning + Acting) pattern implementation
|
||||||
|
- Automatic tool execution loop with up to 300 iterations
|
||||||
|
- Intelligent response parsing to detect incomplete work
|
||||||
|
- Automatic continuation when tasks require multiple steps
|
||||||
|
- Built-in retry logic for API failures
|
||||||
|
|
||||||
|
### AI Provider Support
|
||||||
|
- OpenAI API (default)
|
||||||
|
- Anthropic Claude
|
||||||
|
- Ollama (self-hosted)
|
||||||
|
- Grok
|
||||||
|
- Any OpenAI-compatible API endpoint
|
||||||
|
|
||||||
|
### Tool System
|
||||||
|
The agent has access to 15+ tools exposed via JSON schema descriptions:
|
||||||
|
|
||||||
|
| Tool | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `linux_terminal_execute` | Execute shell commands with output capture |
|
||||||
|
| `linux_terminal_execute_interactive` | Execute interactive commands (vim, htop, etc.) |
|
||||||
|
| `read_file` | Read file contents |
|
||||||
|
| `write_file` | Write content to files with automatic parent directory creation |
|
||||||
|
| `directory_glob` | List directory contents with file metadata |
|
||||||
|
| `mkdir` | Create directories recursively |
|
||||||
|
| `chdir` | Change working directory |
|
||||||
|
| `getpwd` | Get current working directory |
|
||||||
|
| `http_fetch` | Fetch URL contents via HTTP GET |
|
||||||
|
| `web_search` | Search the web for information |
|
||||||
|
| `web_search_news` | Search for news articles |
|
||||||
|
| `db_set` | Store key-value pairs in local SQLite database |
|
||||||
|
| `db_get` | Retrieve values from database |
|
||||||
|
| `db_query` | Execute arbitrary SQL queries |
|
||||||
|
| `index_source_directory` | Index source files with contents |
|
||||||
|
| `python_execute` | Execute Python code and capture output |
|
||||||
|
|
||||||
|
### Database
|
||||||
|
- Local SQLite database at `~/.r.db`
|
||||||
|
- Persistent key-value storage
|
||||||
|
- Full SQL query support
|
||||||
|
- File version history tracking
|
||||||
|
|
||||||
|
### REPL Interface
|
||||||
|
- Readline integration with tab completion
|
||||||
|
- Persistent command history at `~/.r_history`
|
||||||
|
- Markdown syntax highlighting in terminal output
|
||||||
|
- Session management for separate conversation contexts
|
||||||
|
|
||||||
|
### REPL Commands
|
||||||
|
| Command | Description |
|
||||||
|
|---------|-------------|
|
||||||
|
| `!dump` | Show raw message JSON |
|
||||||
|
| `!clear` | Clear conversation history |
|
||||||
|
| `!session` | Display current session info |
|
||||||
|
| `!verbose` | Toggle verbose mode |
|
||||||
|
| `!tools` | List available tools |
|
||||||
|
| `!models` | Show available models |
|
||||||
|
| `!model <name>` | Switch AI model |
|
||||||
|
| `exit` | Exit the REPL |
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
Clone the repository or copy the `henry` package directory.
|
### Dependencies
|
||||||
|
```
|
||||||
|
libcurl json-c readline ncurses sqlite3 gnutls gmp openssl
|
||||||
|
```
|
||||||
|
|
||||||
|
On Debian/Ubuntu:
|
||||||
|
```bash
|
||||||
|
apt install libcurl4-openssl-dev libjson-c-dev libreadline-dev libncurses-dev libsqlite3-dev libgnutls28-dev libgmp-dev libssl-dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Build
|
||||||
|
```bash
|
||||||
|
make build
|
||||||
|
```
|
||||||
|
|
||||||
|
The binary is output to `bin/r` and copied to `./r`.
|
||||||
|
|
||||||
|
### Cross-Compilation
|
||||||
|
```bash
|
||||||
|
make build_mingw # Windows executable
|
||||||
|
```
|
||||||
|
|
||||||
|
### AppImage
|
||||||
|
```bash
|
||||||
|
make appimage # Creates portable r-x86_64.AppImage
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
| Variable | Description | Example |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| `R_KEY` | API key (takes precedence over OPENAI_API_KEY) | `sk-...` |
|
||||||
|
| `OPENAI_API_KEY` | OpenAI API key (fallback) | `sk-...` |
|
||||||
|
| `OPENROUTER_API_KEY` | OpenRouter API key | `sk-or-...` |
|
||||||
|
| `R_BASE_URL` | Custom API base URL (without /v1/...) | `https://openrouter.ai/api` |
|
||||||
|
| `R_MODEL` | AI model name | `gpt-4o`, `x-ai/grok-code-fast-1` |
|
||||||
|
| `R_USE_TOOLS` | Enable/disable function calling | `true`, `false`, `1`, `0` |
|
||||||
|
| `R_USE_STRICT` | Enable strict mode for tool schemas | `true`, `false`, `1`, `0` |
|
||||||
|
| `R_SYSTEM_MESSAGE` | Custom system prompt injected at startup | Any text |
|
||||||
|
| `R_SESSION` | Default session name for conversation persistence | `myproject`, `$(date)` |
|
||||||
|
|
||||||
|
### Context Files
|
||||||
|
- `.rcontext.txt` - Project-specific context (current directory)
|
||||||
|
- `~/.rcontext.txt` - Global user context
|
||||||
|
|
||||||
|
## Bash Integration
|
||||||
|
|
||||||
|
Integrate R as a fallback command handler in bash. When a command is not found, R will attempt to interpret it as a natural language query with automatic date-based sessions.
|
||||||
|
|
||||||
|
Add to `~/.bashrc`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
export R_BASE_URL="https://openrouter.ai/api"
|
||||||
|
export R_KEY="$OPENROUTER_API_KEY"
|
||||||
|
export R_MODEL="x-ai/grok-code-fast-1"
|
||||||
|
export R_SESSION=$(date +%Y-%m-%d)
|
||||||
|
|
||||||
|
command_not_found_handle() {
|
||||||
|
script_path="/path/to/r"
|
||||||
|
first_argument="$1"
|
||||||
|
if "$script_path" "$@"; then
|
||||||
|
echo -e ""
|
||||||
|
else
|
||||||
|
echo "bash: $first_argument: command not found"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
This enables:
|
||||||
|
- Automatic session per day via `R_SESSION=$(date +%Y-%m-%d)`
|
||||||
|
- Natural language commands directly in terminal
|
||||||
|
- Fallback to standard "command not found" on failure
|
||||||
|
|
||||||
|
## Why Terminal AI
|
||||||
|
|
||||||
|
Having an AI agent directly in your terminal eliminates context switching. Instead of copying error messages to a browser, describing your environment, or explaining file contents, the agent can see and act on everything directly.
|
||||||
|
|
||||||
|
### Practical Examples
|
||||||
|
|
||||||
|
**Debug errors instantly**
|
||||||
|
```bash
|
||||||
|
./r "The build failed, check the error and fix it"
|
||||||
|
# Agent reads error output, identifies the issue, edits the file, rebuilds
|
||||||
|
```
|
||||||
|
|
||||||
|
**Explore unfamiliar codebases**
|
||||||
|
```bash
|
||||||
|
./r "How does authentication work in this project?"
|
||||||
|
# Agent searches files, reads relevant code, explains the flow
|
||||||
|
```
|
||||||
|
|
||||||
|
**Automate repetitive tasks**
|
||||||
|
```bash
|
||||||
|
./r "Rename all .jpeg files to .jpg and create a backup folder first"
|
||||||
|
# Agent creates directory, moves files with proper naming
|
||||||
|
```
|
||||||
|
|
||||||
|
**System administration**
|
||||||
|
```bash
|
||||||
|
./r "Check disk usage and find the largest files in /var/log"
|
||||||
|
# Agent runs du, finds large files, presents a summary
|
||||||
|
```
|
||||||
|
|
||||||
|
**Database operations**
|
||||||
|
```bash
|
||||||
|
./r "Show me all users who signed up last week"
|
||||||
|
# Agent queries the database, formats results
|
||||||
|
```
|
||||||
|
|
||||||
|
**Web research while coding**
|
||||||
|
```bash
|
||||||
|
./r "What's the correct syntax for a PostgreSQL upsert?"
|
||||||
|
# Agent searches the web, returns the answer with examples
|
||||||
|
```
|
||||||
|
|
||||||
|
**Code generation**
|
||||||
|
```bash
|
||||||
|
./r "Write a Python script that monitors CPU usage and alerts if above 90%"
|
||||||
|
# Agent writes the script, saves it, makes it executable
|
||||||
|
```
|
||||||
|
|
||||||
|
**Git workflows**
|
||||||
|
```bash
|
||||||
|
./r "Show what changed since yesterday and summarize the commits"
|
||||||
|
# Agent runs git log, git diff, provides summary
|
||||||
|
```
|
||||||
|
|
||||||
|
**File operations**
|
||||||
|
```bash
|
||||||
|
./r "Find all TODO comments in this project and list them by file"
|
||||||
|
# Agent greps through codebase, organizes findings
|
||||||
|
```
|
||||||
|
|
||||||
|
**Data processing**
|
||||||
|
```bash
|
||||||
|
cat data.csv | ./r --stdin "Calculate the average of the third column"
|
||||||
|
# Agent processes piped data, returns calculation
|
||||||
|
```
|
||||||
|
|
||||||
|
**Multi-step tasks**
|
||||||
|
```bash
|
||||||
|
./r "Set up a new Python virtualenv, install requests and pytest, create a basic test file"
|
||||||
|
# Agent executes multiple commands in sequence, creates files
|
||||||
|
```
|
||||||
|
|
||||||
|
**Environment inspection**
|
||||||
|
```bash
|
||||||
|
./r "What ports are in use and what processes are using them?"
|
||||||
|
# Agent runs netstat/lsof, explains the output
|
||||||
|
```
|
||||||
|
|
||||||
|
The agent maintains conversation context within sessions, so follow-up queries understand previous actions. Combined with the bash integration, any unrecognized command becomes a natural language query to the AI.
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
Run the IRC server using the CLI entry point:
|
### Interactive Mode
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
python -m henry.cli
|
./r
|
||||||
```
|
```
|
||||||
|
|
||||||
The server listens on `127.0.0.1:6667` by default.
|
### Single Query
|
||||||
|
|
||||||
## Connecting
|
|
||||||
|
|
||||||
Use any IRC client to connect to `localhost` on port `6667`.
|
|
||||||
|
|
||||||
## Example
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Using irssi client
|
./r "What files are in the current directory?"
|
||||||
irssi -c 127.0.0.1 -p 6667
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### With Piped Input
|
||||||
|
```bash
|
||||||
|
cat error.log | ./r --stdin "Analyze these errors"
|
||||||
|
```
|
||||||
|
|
||||||
|
### With Context File
|
||||||
|
```bash
|
||||||
|
./r --context project_docs.txt "Explain this project"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Session Management
|
||||||
|
```bash
|
||||||
|
./r --session=myproject # Named session
|
||||||
|
./r -s myproject # Short form
|
||||||
|
```
|
||||||
|
|
||||||
|
### Options
|
||||||
|
| Flag | Description |
|
||||||
|
|------|-------------|
|
||||||
|
| `--verbose` | Enable verbose output |
|
||||||
|
| `--stdin` | Read prompt from stdin |
|
||||||
|
| `--context <file>` | Include file as context |
|
||||||
|
| `--py <file>` | Include Python file |
|
||||||
|
| `--session <name>` | Use named session |
|
||||||
|
| `--nh` | Disable syntax highlighting |
|
||||||
|
| `--free` | Use free tier API |
|
||||||
|
| `--api` | API mode |
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
### Header-Only Design
|
||||||
|
The codebase uses a header-only architecture where `.h` files contain both declarations and implementations. All code compiles through includes from `main.c`.
|
||||||
|
|
||||||
|
### Core Components
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `main.c` | Entry point, REPL loop, argument parsing |
|
||||||
|
| `agent.h` | Autonomous agent loop with ReAct pattern |
|
||||||
|
| `r.h` | Global configuration and environment handling |
|
||||||
|
| `auth.h` | API key resolution |
|
||||||
|
| `openai.h` | API communication |
|
||||||
|
| `chat.h` | JSON prompt construction |
|
||||||
|
| `messages.h` | Conversation history management |
|
||||||
|
| `tools.h` | Tool definitions and execution dispatcher |
|
||||||
|
| `http_curl.h` | libcurl wrapper for HTTP requests |
|
||||||
|
| `line.h` | Readline integration |
|
||||||
|
| `db_utils.h` | SQLite operations |
|
||||||
|
| `browse.h` | Web search functionality |
|
||||||
|
| `markdown.h` | Terminal markdown rendering |
|
||||||
|
| `utils.h` | Path utilities |
|
||||||
|
| `indexer.h` | Source directory indexing |
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT
|
MIT
|
||||||
|
|||||||
45
http_curl.h
45
http_curl.h
@ -29,10 +29,12 @@
|
|||||||
|
|
||||||
#include "auth.h"
|
#include "auth.h"
|
||||||
#include <curl/curl.h>
|
#include <curl/curl.h>
|
||||||
|
#include <pthread.h>
|
||||||
#include <stdint.h>
|
#include <stdint.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <stdlib.h>
|
#include <stdlib.h>
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
|
|
||||||
struct ResponseBuffer {
|
struct ResponseBuffer {
|
||||||
@ -65,11 +67,40 @@ static size_t WriteCallback(void *contents, size_t size, size_t nmemb,
|
|||||||
#define HTTP_MAX_RETRIES 3
|
#define HTTP_MAX_RETRIES 3
|
||||||
#define HTTP_RETRY_DELAY_MS 2000
|
#define HTTP_RETRY_DELAY_MS 2000
|
||||||
|
|
||||||
|
static struct timespec spinner_start_time = {0, 0};
|
||||||
|
static volatile int spinner_running = 0;
|
||||||
|
|
||||||
|
static double get_elapsed_seconds() {
|
||||||
|
struct timespec now;
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &now);
|
||||||
|
return (now.tv_sec - spinner_start_time.tv_sec) +
|
||||||
|
(now.tv_nsec - spinner_start_time.tv_nsec) / 1e9;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void *spinner_thread(void *arg) {
|
||||||
|
(void)arg;
|
||||||
|
const char *frames[] = {"⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"};
|
||||||
|
int frame = 0;
|
||||||
|
while (spinner_running) {
|
||||||
|
double elapsed = get_elapsed_seconds();
|
||||||
|
fprintf(stderr, "\r%s Querying AI... (%.1fs) ", frames[frame % 10], elapsed);
|
||||||
|
fflush(stderr);
|
||||||
|
frame++;
|
||||||
|
usleep(80000);
|
||||||
|
}
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
char *curl_post(const char *url, const char *data) {
|
char *curl_post(const char *url, const char *data) {
|
||||||
CURL *curl;
|
CURL *curl;
|
||||||
CURLcode res;
|
CURLcode res;
|
||||||
struct ResponseBuffer response = {NULL, 0};
|
struct ResponseBuffer response = {NULL, 0};
|
||||||
int retry_count = 0;
|
int retry_count = 0;
|
||||||
|
pthread_t spinner_tid;
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &spinner_start_time);
|
||||||
|
spinner_running = 1;
|
||||||
|
pthread_create(&spinner_tid, NULL, spinner_thread, NULL);
|
||||||
|
|
||||||
while (retry_count < HTTP_MAX_RETRIES) {
|
while (retry_count < HTTP_MAX_RETRIES) {
|
||||||
if (response.data) {
|
if (response.data) {
|
||||||
@ -78,6 +109,8 @@ char *curl_post(const char *url, const char *data) {
|
|||||||
response.data = malloc(1);
|
response.data = malloc(1);
|
||||||
response.size = 0;
|
response.size = 0;
|
||||||
if (!response.data) {
|
if (!response.data) {
|
||||||
|
spinner_running = 0;
|
||||||
|
pthread_join(spinner_tid, NULL);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
response.data[0] = '\0';
|
response.data[0] = '\0';
|
||||||
@ -85,6 +118,8 @@ char *curl_post(const char *url, const char *data) {
|
|||||||
curl = curl_easy_init();
|
curl = curl_easy_init();
|
||||||
if (!curl) {
|
if (!curl) {
|
||||||
free(response.data);
|
free(response.data);
|
||||||
|
spinner_running = 0;
|
||||||
|
pthread_join(spinner_tid, NULL);
|
||||||
return NULL;
|
return NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,16 +142,26 @@ char *curl_post(const char *url, const char *data) {
|
|||||||
curl_easy_cleanup(curl);
|
curl_easy_cleanup(curl);
|
||||||
|
|
||||||
if (res == CURLE_OK) {
|
if (res == CURLE_OK) {
|
||||||
|
spinner_running = 0;
|
||||||
|
pthread_join(spinner_tid, NULL);
|
||||||
|
fprintf(stderr, "\r \r");
|
||||||
|
fflush(stderr);
|
||||||
return response.data;
|
return response.data;
|
||||||
}
|
}
|
||||||
|
|
||||||
retry_count++;
|
retry_count++;
|
||||||
|
spinner_running = 0;
|
||||||
|
pthread_join(spinner_tid, NULL);
|
||||||
|
fprintf(stderr, "\r \r");
|
||||||
fprintf(stderr, "Network error: %s (attempt %d/%d)\n",
|
fprintf(stderr, "Network error: %s (attempt %d/%d)\n",
|
||||||
curl_easy_strerror(res), retry_count, HTTP_MAX_RETRIES);
|
curl_easy_strerror(res), retry_count, HTTP_MAX_RETRIES);
|
||||||
|
|
||||||
if (retry_count < HTTP_MAX_RETRIES) {
|
if (retry_count < HTTP_MAX_RETRIES) {
|
||||||
fprintf(stderr, "Retrying in %d seconds...\n", HTTP_RETRY_DELAY_MS / 1000);
|
fprintf(stderr, "Retrying in %d seconds...\n", HTTP_RETRY_DELAY_MS / 1000);
|
||||||
usleep(HTTP_RETRY_DELAY_MS * 1000);
|
usleep(HTTP_RETRY_DELAY_MS * 1000);
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &spinner_start_time);
|
||||||
|
spinner_running = 1;
|
||||||
|
pthread_create(&spinner_tid, NULL, spinner_thread, NULL);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
10
main.c
10
main.c
@ -260,10 +260,16 @@ static void init(void) {
|
|||||||
char datetime[64];
|
char datetime[64];
|
||||||
strftime(datetime, sizeof(datetime), "%Y-%m-%d %H:%M:%S %Z", tm_info);
|
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(
|
snprintf(
|
||||||
payload, sizeof(payload),
|
payload, sizeof(payload),
|
||||||
"# AUTONOMOUS AGENT INSTRUCTIONS\n"
|
"# AUTONOMOUS AGENT INSTRUCTIONS\n"
|
||||||
"Current date/time: %s\n\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, "
|
"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"
|
"select and execute tools when needed, observe results, and continue until the goal is achieved.\n\n"
|
||||||
"## Reasoning Pattern (ReAct)\n"
|
"## Reasoning Pattern (ReAct)\n"
|
||||||
@ -292,7 +298,7 @@ static void init(void) {
|
|||||||
"You MUST copy/paste relevant data from tool results into your response.\n"
|
"You MUST copy/paste relevant data from tool results into your response.\n"
|
||||||
"Bad: 'I searched and found information about X.'\n"
|
"Bad: 'I searched and found information about X.'\n"
|
||||||
"Good: 'Here is what I found: [actual content from search results]'\n",
|
"Good: 'Here is what I found: [actual content from search results]'\n",
|
||||||
datetime, schema);
|
datetime, cwd, schema);
|
||||||
|
|
||||||
free(schema);
|
free(schema);
|
||||||
fprintf(stderr, "Loading...");
|
fprintf(stderr, "Loading...");
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user