Files
didactyl/src/tools.c
T

5263 lines
190 KiB
C

#define _POSIX_C_SOURCE 200809L
#include "tools.h"
#include <curl/curl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>
#include <limits.h>
#include <ctype.h>
#include <time.h>
#include <unistd.h>
#include <sys/wait.h>
#include "cjson/cJSON.h"
#include "main.h"
#include "nostr_handler.h"
#include "trigger_manager.h"
#include "llm.h"
#include "../../nostr_core_lib/nostr_core/nostr_core.h"
static char* json_error(const char* msg) {
cJSON* root = cJSON_CreateObject();
if (!root) return NULL;
cJSON_AddBoolToObject(root, "success", 0);
cJSON_AddStringToObject(root, "error", msg ? msg : "unknown error");
char* out = cJSON_PrintUnformatted(root);
cJSON_Delete(root);
return out;
}
static char* sanitize_json_string_controls(const char* in) {
if (!in) return NULL;
size_t len = strlen(in);
size_t cap = (len * 2U) + 1U;
char* out = (char*)malloc(cap);
if (!out) return NULL;
int in_string = 0;
int escaping = 0;
size_t j = 0;
for (size_t i = 0; i < len; i++) {
char c = in[i];
if (escaping) {
if (j + 1U >= cap) {
free(out);
return NULL;
}
out[j++] = c;
escaping = 0;
continue;
}
if (c == '\\') {
if (j + 1U >= cap) {
free(out);
return NULL;
}
out[j++] = c;
if (in_string) escaping = 1;
continue;
}
if (c == '"') {
if (j + 1U >= cap) {
free(out);
return NULL;
}
out[j++] = c;
in_string = !in_string;
continue;
}
if (in_string && (c == '\n' || c == '\r' || c == '\t')) {
if (j + 2U >= cap) {
free(out);
return NULL;
}
out[j++] = '\\';
out[j++] = (c == '\n') ? 'n' : (c == '\r') ? 'r' : 't';
continue;
}
if (j + 1U >= cap) {
free(out);
return NULL;
}
out[j++] = c;
}
out[j] = '\0';
return out;
}
static const char* find_key_start(const char* in, const char* key) {
if (!in || !key) return NULL;
char pattern[64];
int n = snprintf(pattern, sizeof(pattern), "\"%s\"", key);
if (n <= 0 || (size_t)n >= sizeof(pattern)) return NULL;
return strstr(in, pattern);
}
static const char* skip_ws(const char* p) {
while (p && *p && isspace((unsigned char)*p)) p++;
return p;
}
static int parse_loose_kind(const char* in, int* out_kind) {
if (!in || !out_kind) return -1;
const char* p = find_key_start(in, "kind");
if (!p) return -1;
p = strchr(p, ':');
if (!p) return -1;
p = skip_ws(p + 1);
if (!p || !*p) return -1;
char* end = NULL;
long v = strtol(p, &end, 10);
if (end == p) return -1;
*out_kind = (int)v;
return 0;
}
static char* parse_loose_json_string_value(const char* in, const char* key) {
if (!in || !key) return NULL;
const char* p = find_key_start(in, key);
if (!p) return NULL;
p = strchr(p, ':');
if (!p) return NULL;
p = skip_ws(p + 1);
if (!p || *p != '"') return NULL;
p++;
size_t max_len = strlen(p);
char* out = (char*)malloc(max_len + 1U);
if (!out) return NULL;
size_t j = 0;
int escaping = 0;
for (size_t i = 0; p[i] != '\0'; i++) {
char c = p[i];
if (escaping) {
switch (c) {
case 'n': out[j++] = '\n'; break;
case 'r': out[j++] = '\r'; break;
case 't': out[j++] = '\t'; break;
case '"': out[j++] = '"'; break;
case '\\': out[j++] = '\\'; break;
default: out[j++] = c; break;
}
escaping = 0;
continue;
}
if (c == '\\') {
escaping = 1;
continue;
}
if (c == '"') {
out[j] = '\0';
return out;
}
out[j++] = c;
}
out[j] = '\0';
return out;
}
static cJSON* parse_loose_nostr_post_args(const char* in) {
if (!in) return NULL;
int kind = 0;
if (parse_loose_kind(in, &kind) != 0) {
return NULL;
}
char* content = parse_loose_json_string_value(in, "content");
if (!content) {
return NULL;
}
cJSON* args = cJSON_CreateObject();
if (!args) {
free(content);
return NULL;
}
cJSON_AddNumberToObject(args, "kind", kind);
cJSON_AddStringToObject(args, "content", content);
free(content);
return args;
}
static cJSON* ensure_tags_array(cJSON** tags_inout) {
if (!tags_inout) return NULL;
if (*tags_inout) {
if (cJSON_IsArray(*tags_inout)) {
return *tags_inout;
}
cJSON_Delete(*tags_inout);
*tags_inout = NULL;
}
*tags_inout = cJSON_CreateArray();
return *tags_inout;
}
static int has_tag_key(cJSON* tags, const char* key) {
if (!tags || !cJSON_IsArray(tags) || !key) return 0;
int n = cJSON_GetArraySize(tags);
for (int i = 0; i < n; i++) {
cJSON* tag = cJSON_GetArrayItem(tags, i);
if (!tag || !cJSON_IsArray(tag)) continue;
cJSON* k = cJSON_GetArrayItem(tag, 0);
if (k && cJSON_IsString(k) && k->valuestring && strcmp(k->valuestring, key) == 0) {
return 1;
}
}
return 0;
}
static int add_string_tag(cJSON* tags, const char* key, const char* value) {
if (!tags || !cJSON_IsArray(tags) || !key || !value || value[0] == '\0') return -1;
cJSON* tag = cJSON_CreateArray();
if (!tag) return -1;
cJSON_AddItemToArray(tag, cJSON_CreateString(key));
cJSON_AddItemToArray(tag, cJSON_CreateString(value));
cJSON_AddItemToArray(tags, tag);
return 0;
}
static char* trim_copy(const char* start, size_t len) {
while (len > 0 && isspace((unsigned char)start[0])) {
start++;
len--;
}
while (len > 0 && isspace((unsigned char)start[len - 1])) {
len--;
}
char* out = (char*)malloc(len + 1U);
if (!out) return NULL;
memcpy(out, start, len);
out[len] = '\0';
return out;
}
static char* shell_quote_single(const char* in) {
if (!in) return NULL;
size_t len = strlen(in);
size_t extra = 2U;
for (size_t i = 0; i < len; i++) {
if (in[i] == '\'') {
extra += 4U;
} else {
extra += 1U;
}
}
char* out = (char*)malloc(extra + 1U);
if (!out) return NULL;
size_t j = 0;
out[j++] = '\'';
for (size_t i = 0; i < len; i++) {
if (in[i] == '\'') {
out[j++] = '\'';
out[j++] = '\\';
out[j++] = '\'';
out[j++] = '\'';
} else {
out[j++] = in[i];
}
}
out[j++] = '\'';
out[j] = '\0';
return out;
}
static char* first_markdown_h1(const char* content) {
if (!content) return NULL;
const char* p = content;
while (*p) {
const char* line = p;
const char* nl = strchr(line, '\n');
size_t len = nl ? (size_t)(nl - line) : strlen(line);
while (len > 0 && (line[len - 1] == '\r')) len--;
size_t i = 0;
while (i < len && isspace((unsigned char)line[i])) i++;
if (i < len && line[i] == '#') {
size_t j = i;
while (j < len && line[j] == '#') j++;
if (j < len && isspace((unsigned char)line[j])) {
while (j < len && isspace((unsigned char)line[j])) j++;
if (j < len) return trim_copy(line + j, len - j);
}
}
if (!nl) break;
p = nl + 1;
}
return NULL;
}
static int is_paragraph_line(const char* line, size_t len) {
size_t i = 0;
while (i < len && isspace((unsigned char)line[i])) i++;
if (i >= len) return 0;
char c = line[i];
if (c == '#' || c == '>' || c == '-' || c == '*' || c == '`' || c == '|') return 0;
if (isdigit((unsigned char)c)) {
size_t j = i;
while (j < len && isdigit((unsigned char)line[j])) j++;
if (j < len && line[j] == '.') return 0;
}
return 1;
}
static char* first_markdown_paragraph(const char* content) {
if (!content) return NULL;
const char* p = content;
while (*p) {
const char* line = p;
const char* nl = strchr(line, '\n');
size_t len = nl ? (size_t)(nl - line) : strlen(line);
while (len > 0 && line[len - 1] == '\r') len--;
if (is_paragraph_line(line, len)) {
size_t cap = 1024;
size_t used = 0;
char* out = (char*)malloc(cap);
if (!out) return NULL;
const char* q = line;
const char* qnl = nl;
size_t qlen = len;
while (1) {
if (!is_paragraph_line(q, qlen)) break;
size_t start = 0;
while (start < qlen && isspace((unsigned char)q[start])) start++;
size_t end = qlen;
while (end > start && isspace((unsigned char)q[end - 1])) end--;
size_t part_len = end - start;
if (used + part_len + 2 >= cap) {
cap = (cap * 2U) + part_len + 16U;
char* bigger = (char*)realloc(out, cap);
if (!bigger) {
free(out);
return NULL;
}
out = bigger;
}
if (part_len > 0) {
if (used > 0) out[used++] = ' ';
memcpy(out + used, q + start, part_len);
used += part_len;
}
if (!qnl) break;
q = qnl + 1;
qnl = strchr(q, '\n');
qlen = qnl ? (size_t)(qnl - q) : strlen(q);
while (qlen > 0 && q[qlen - 1] == '\r') qlen--;
}
out[used] = '\0';
return out;
}
if (!nl) break;
p = nl + 1;
}
return NULL;
}
static char* first_markdown_image_url(const char* content) {
if (!content) return NULL;
const char* p = content;
while ((p = strstr(p, "![")) != NULL) {
const char* close_bracket = strchr(p + 2, ']');
if (!close_bracket || close_bracket[1] != '(') {
p += 2;
continue;
}
const char* url_start = close_bracket + 2;
const char* url_end = strchr(url_start, ')');
if (!url_end || url_end <= url_start) {
p += 2;
continue;
}
return trim_copy(url_start, (size_t)(url_end - url_start));
}
return NULL;
}
static char* slugify_string(const char* in, const char* fallback) {
if (!in || in[0] == '\0') {
return fallback ? strdup(fallback) : NULL;
}
size_t len = strlen(in);
char* out = (char*)malloc(len + 1U);
if (!out) return NULL;
size_t j = 0;
int prev_dash = 0;
for (size_t i = 0; i < len; i++) {
unsigned char c = (unsigned char)in[i];
if (isalnum(c)) {
out[j++] = (char)tolower(c);
prev_dash = 0;
} else if (!prev_dash && j > 0) {
out[j++] = '-';
prev_dash = 1;
}
}
while (j > 0 && out[j - 1] == '-') j--;
out[j] = '\0';
if (j == 0) {
free(out);
return fallback ? strdup(fallback) : NULL;
}
return out;
}
static void ensure_nip23_metadata_tags(int kind, const char* content, cJSON** tags_inout) {
if (!content || (kind != 30023 && kind != 30024) || !tags_inout) {
return;
}
cJSON* tags = ensure_tags_array(tags_inout);
if (!tags) return;
char* title = has_tag_key(tags, "title") ? NULL : first_markdown_h1(content);
char* summary = has_tag_key(tags, "summary") ? NULL : first_markdown_paragraph(content);
char* image = has_tag_key(tags, "image") ? NULL : first_markdown_image_url(content);
if (title) {
(void)add_string_tag(tags, "title", title);
}
if (summary) {
(void)add_string_tag(tags, "summary", summary);
}
if (image) {
(void)add_string_tag(tags, "image", image);
}
if (!has_tag_key(tags, "published_at")) {
char published_at[32];
snprintf(published_at, sizeof(published_at), "%lld", (long long)time(NULL));
(void)add_string_tag(tags, "published_at", published_at);
}
if (!has_tag_key(tags, "d")) {
char* slug = NULL;
if (title) {
slug = slugify_string(title, "long-form-note");
} else {
char* derived_title = first_markdown_h1(content);
slug = slugify_string(derived_title, "long-form-note");
free(derived_title);
}
if (slug) {
(void)add_string_tag(tags, "d", slug);
free(slug);
}
}
free(title);
free(summary);
free(image);
}
static int is_safe_relative_path(const char* path) {
if (!path || path[0] == '\0') return 0;
if (path[0] == '/') return 0;
if (strstr(path, "..") != NULL) return 0;
if (strchr(path, '\\') != NULL) return 0;
return 1;
}
static int build_tool_path(tools_context_t* ctx, const char* rel_path, char* out, size_t out_size) {
if (!ctx || !ctx->cfg || !rel_path || !out || out_size == 0) return -1;
if (!is_safe_relative_path(rel_path)) return -1;
const char* cwd = ctx->cfg->tools.shell.working_directory[0] != '\0'
? ctx->cfg->tools.shell.working_directory
: ".";
int n = 0;
if (strcmp(cwd, ".") == 0) {
n = snprintf(out, out_size, "%s", rel_path);
} else {
n = snprintf(out, out_size, "%s/%s", cwd, rel_path);
}
if (n < 0 || (size_t)n >= out_size) return -1;
return 0;
}
int tools_init(tools_context_t* ctx, didactyl_config_t* cfg) {
if (!ctx || !cfg) return -1;
memset(ctx, 0, sizeof(*ctx));
ctx->cfg = cfg;
ctx->trigger_manager = NULL;
return 0;
}
void tools_cleanup(tools_context_t* ctx) {
if (!ctx) return;
memset(ctx, 0, sizeof(*ctx));
}
static int is_hex_string_len(const char* s, size_t expected_len) {
if (!s || strlen(s) != expected_len) return 0;
for (size_t i = 0; i < expected_len; i++) {
unsigned char c = (unsigned char)s[i];
if (!isxdigit(c)) return 0;
}
return 1;
}
static cJSON* parse_tool_args_json(const char* args_json) {
const char* raw_args_json = args_json ? args_json : "{}";
raw_args_json = skip_ws(raw_args_json);
if (!raw_args_json || raw_args_json[0] == '\0') {
raw_args_json = "{}";
}
cJSON* args = cJSON_Parse(raw_args_json);
char* repaired_args_json = NULL;
if (args && cJSON_IsString(args) && args->valuestring) {
cJSON* nested = cJSON_Parse(args->valuestring);
if (nested) {
cJSON_Delete(args);
args = nested;
}
}
if (!args) {
repaired_args_json = sanitize_json_string_controls(raw_args_json);
if (repaired_args_json) {
args = cJSON_Parse(repaired_args_json);
if (args && cJSON_IsString(args) && args->valuestring) {
cJSON* nested = cJSON_Parse(args->valuestring);
if (nested) {
cJSON_Delete(args);
args = nested;
}
}
}
}
free(repaired_args_json);
return args;
}
static cJSON* tasks_create_empty_root(void) {
cJSON* root = cJSON_CreateObject();
cJSON* tasks = cJSON_CreateArray();
if (!root || !tasks) {
cJSON_Delete(root);
cJSON_Delete(tasks);
return NULL;
}
cJSON_AddItemToObject(root, "tasks", tasks);
cJSON_AddNumberToObject(root, "next_id", 1);
return root;
}
static const char* normalize_task_status(const char* status) {
if (!status) return NULL;
if (strcmp(status, "pending") == 0) return "pending";
if (strcmp(status, "active") == 0) return "active";
if (strcmp(status, "done") == 0) return "done";
return NULL;
}
static cJSON* tasks_load_root(const char* path) {
if (!path) return NULL;
FILE* fp = fopen(path, "rb");
if (!fp) {
if (access(path, F_OK) == 0) {
return NULL;
}
return tasks_create_empty_root();
}
if (fseek(fp, 0, SEEK_END) != 0) {
fclose(fp);
return NULL;
}
long len = ftell(fp);
if (len < 0) {
fclose(fp);
return NULL;
}
if (fseek(fp, 0, SEEK_SET) != 0) {
fclose(fp);
return NULL;
}
char* buf = (char*)malloc((size_t)len + 1U);
if (!buf) {
fclose(fp);
return NULL;
}
size_t n = fread(buf, 1, (size_t)len, fp);
fclose(fp);
if (n != (size_t)len) {
free(buf);
return NULL;
}
buf[len] = '\0';
cJSON* root = cJSON_Parse(buf);
free(buf);
if (!root || !cJSON_IsObject(root)) {
cJSON_Delete(root);
return NULL;
}
cJSON* tasks = cJSON_GetObjectItemCaseSensitive(root, "tasks");
if (!tasks || !cJSON_IsArray(tasks)) {
cJSON_DeleteItemFromObjectCaseSensitive(root, "tasks");
cJSON_AddItemToObject(root, "tasks", cJSON_CreateArray());
}
cJSON* next_id = cJSON_GetObjectItemCaseSensitive(root, "next_id");
if (!next_id || !cJSON_IsNumber(next_id) || next_id->valuedouble < 1) {
cJSON_DeleteItemFromObjectCaseSensitive(root, "next_id");
cJSON_AddNumberToObject(root, "next_id", 1);
}
return root;
}
static int tasks_save_root(const char* path, cJSON* root) {
if (!path || !root) return -1;
char* raw = cJSON_PrintUnformatted(root);
if (!raw) return -1;
FILE* fp = fopen(path, "wb");
if (!fp) {
free(raw);
return -1;
}
size_t len = strlen(raw);
size_t n = fwrite(raw, 1, len, fp);
fclose(fp);
free(raw);
return (n == len) ? 0 : -1;
}
static cJSON* task_find_by_id(cJSON* tasks, int id, int* out_index) {
if (!tasks || !cJSON_IsArray(tasks) || id <= 0) return NULL;
int n = cJSON_GetArraySize(tasks);
for (int i = 0; i < n; i++) {
cJSON* task = cJSON_GetArrayItem(tasks, i);
cJSON* tid = task ? cJSON_GetObjectItemCaseSensitive(task, "id") : NULL;
if (tid && cJSON_IsNumber(tid) && (int)tid->valuedouble == id) {
if (out_index) *out_index = i;
return task;
}
}
return NULL;
}
typedef struct {
char* data;
size_t len;
size_t cap;
size_t max_bytes;
int truncated;
} http_fetch_buffer_t;
static size_t http_fetch_write_cb(void* contents, size_t size, size_t nmemb, void* userp) {
http_fetch_buffer_t* rb = (http_fetch_buffer_t*)userp;
size_t total = size * nmemb;
if (!rb || total == 0) return total;
if (rb->len >= rb->max_bytes) {
rb->truncated = 1;
return total;
}
size_t allowed = rb->max_bytes - rb->len;
size_t to_copy = total <= allowed ? total : allowed;
if (rb->len + to_copy + 1U > rb->cap) {
size_t new_cap = rb->cap == 0 ? 1024U : rb->cap;
while (new_cap < rb->len + to_copy + 1U) {
new_cap *= 2U;
}
char* bigger = (char*)realloc(rb->data, new_cap);
if (!bigger) return 0;
rb->data = bigger;
rb->cap = new_cap;
}
memcpy(rb->data + rb->len, contents, to_copy);
rb->len += to_copy;
rb->data[rb->len] = '\0';
if (to_copy < total) {
rb->truncated = 1;
}
return total;
}
static const char* detect_ca_bundle_path_for_tools(void) {
const char* env = getenv("SSL_CERT_FILE");
if (env && env[0] != '\0' && access(env, R_OK) == 0) {
return env;
}
static const char* candidates[] = {
"/etc/ssl/certs/ca-certificates.crt",
"/etc/ssl/cert.pem",
"/etc/pki/tls/certs/ca-bundle.crt",
"/etc/ssl/ca-bundle.pem"
};
for (size_t i = 0; i < sizeof(candidates) / sizeof(candidates[0]); i++) {
if (access(candidates[i], R_OK) == 0) {
return candidates[i];
}
}
return NULL;
}
static void free_string_array_heap(char** arr, int count) {
if (!arr) return;
for (int i = 0; i < count; i++) {
free(arr[i]);
}
free(arr);
}
static int tag_tuple_equal(cJSON* a, cJSON* b) {
if (!a || !b || !cJSON_IsArray(a) || !cJSON_IsArray(b)) return 0;
int an = cJSON_GetArraySize(a);
int bn = cJSON_GetArraySize(b);
if (an != bn) return 0;
for (int i = 0; i < an; i++) {
cJSON* ai = cJSON_GetArrayItem(a, i);
cJSON* bi = cJSON_GetArrayItem(b, i);
if (!ai || !bi || !cJSON_IsString(ai) || !cJSON_IsString(bi) ||
!ai->valuestring || !bi->valuestring || strcmp(ai->valuestring, bi->valuestring) != 0) {
return 0;
}
}
return 1;
}
static int tags_contains_tuple(cJSON* tags, cJSON* tuple) {
if (!tags || !tuple || !cJSON_IsArray(tags) || !cJSON_IsArray(tuple)) return 0;
int n = cJSON_GetArraySize(tags);
for (int i = 0; i < n; i++) {
cJSON* cur = cJSON_GetArrayItem(tags, i);
if (tag_tuple_equal(cur, tuple)) {
return 1;
}
}
return 0;
}
static int remove_matching_tag_tuples(cJSON* tags, cJSON* tuple) {
if (!tags || !tuple || !cJSON_IsArray(tags) || !cJSON_IsArray(tuple)) return 0;
int removed = 0;
for (int i = cJSON_GetArraySize(tags) - 1; i >= 0; i--) {
cJSON* cur = cJSON_GetArrayItem(tags, i);
if (tag_tuple_equal(cur, tuple)) {
cJSON_DeleteItemFromArray(tags, i);
removed++;
}
}
return removed;
}
static cJSON* find_tag_value_string(cJSON* tags, const char* key) {
if (!tags || !key || !cJSON_IsArray(tags)) return NULL;
int n = cJSON_GetArraySize(tags);
for (int i = 0; i < n; i++) {
cJSON* tag = cJSON_GetArrayItem(tags, i);
if (!tag || !cJSON_IsArray(tag) || cJSON_GetArraySize(tag) < 2) continue;
cJSON* k = cJSON_GetArrayItem(tag, 0);
cJSON* v = cJSON_GetArrayItem(tag, 1);
if (k && v && cJSON_IsString(k) && cJSON_IsString(v) &&
k->valuestring && v->valuestring && strcmp(k->valuestring, key) == 0) {
return v;
}
}
return NULL;
}
static int validate_skill_slug(const char* slug) {
if (!slug) return 0;
size_t len = strlen(slug);
if (len == 0 || len > 64U) return 0;
if (slug[0] == '-' || slug[len - 1] == '-') return 0;
int prev_dash = 0;
for (size_t i = 0; i < len; i++) {
unsigned char c = (unsigned char)slug[i];
if (c == '-') {
if (prev_dash) return 0;
prev_dash = 1;
continue;
}
if (!islower(c) && !isdigit(c)) return 0;
prev_dash = 0;
}
return 1;
}
static int ci_contains(const char* haystack, const char* needle) {
if (!haystack || !needle) return 0;
if (needle[0] == '\0') return 1;
size_t nlen = strlen(needle);
size_t hlen = strlen(haystack);
if (nlen > hlen) return 0;
for (size_t i = 0; i + nlen <= hlen; i++) {
size_t j = 0;
while (j < nlen) {
unsigned char a = (unsigned char)haystack[i + j];
unsigned char b = (unsigned char)needle[j];
if (tolower(a) != tolower(b)) break;
j++;
}
if (j == nlen) return 1;
}
return 0;
}
static int fetch_adoption_list_tags(tools_context_t* ctx, cJSON** out_tags, char** out_content) {
if (!ctx || !ctx->cfg || !out_tags || !out_content) return -1;
*out_tags = NULL;
*out_content = NULL;
cJSON* filter = cJSON_CreateObject();
cJSON* kinds = cJSON_CreateArray();
cJSON* authors = cJSON_CreateArray();
if (!filter || !kinds || !authors) {
cJSON_Delete(filter);
cJSON_Delete(kinds);
cJSON_Delete(authors);
return -1;
}
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(10123));
cJSON_AddItemToObject(filter, "kinds", kinds);
cJSON_AddItemToArray(authors, cJSON_CreateString(ctx->cfg->keys.public_key_hex));
cJSON_AddItemToObject(filter, "authors", authors);
cJSON_AddNumberToObject(filter, "limit", 1);
char* events_json = nostr_handler_query_json(filter, 8000);
cJSON_Delete(filter);
cJSON* tags = cJSON_CreateArray();
char* content = strdup("");
if (!tags || !content) {
free(events_json);
cJSON_Delete(tags);
free(content);
return -1;
}
if (events_json) {
cJSON* events = cJSON_Parse(events_json);
free(events_json);
if (events && cJSON_IsArray(events) && cJSON_GetArraySize(events) > 0) {
cJSON* ev0 = cJSON_GetArrayItem(events, 0);
if (ev0 && cJSON_IsObject(ev0)) {
cJSON* ev_content = cJSON_GetObjectItemCaseSensitive(ev0, "content");
if (ev_content && cJSON_IsString(ev_content) && ev_content->valuestring) {
free(content);
content = strdup(ev_content->valuestring);
if (!content) {
cJSON_Delete(events);
cJSON_Delete(tags);
return -1;
}
}
cJSON* ev_tags = cJSON_GetObjectItemCaseSensitive(ev0, "tags");
if (ev_tags && cJSON_IsArray(ev_tags)) {
cJSON* dup = cJSON_Duplicate(ev_tags, 1);
if (!dup) {
cJSON_Delete(events);
cJSON_Delete(tags);
free(content);
return -1;
}
cJSON_Delete(tags);
tags = dup;
}
}
}
cJSON_Delete(events);
}
*out_tags = tags;
*out_content = content;
return 0;
}
static int publish_adoption_list(const char* content, cJSON* tags, nostr_publish_result_t* out_result) {
if (!tags || !cJSON_IsArray(tags) || !out_result) return -1;
memset(out_result, 0, sizeof(*out_result));
return nostr_handler_publish_kind_event(10123, content ? content : "", tags, out_result);
}
static cJSON* extract_skill_summary(cJSON* event) {
if (!event || !cJSON_IsObject(event)) return NULL;
cJSON* summary = cJSON_CreateObject();
if (!summary) return NULL;
cJSON* kind = cJSON_GetObjectItemCaseSensitive(event, "kind");
cJSON* created_at = cJSON_GetObjectItemCaseSensitive(event, "created_at");
cJSON* id = cJSON_GetObjectItemCaseSensitive(event, "id");
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(event, "pubkey");
cJSON* content = cJSON_GetObjectItemCaseSensitive(event, "content");
cJSON* tags = cJSON_GetObjectItemCaseSensitive(event, "tags");
if (kind && cJSON_IsNumber(kind)) {
cJSON_AddNumberToObject(summary, "kind", kind->valuedouble);
}
if (created_at && cJSON_IsNumber(created_at)) {
cJSON_AddNumberToObject(summary, "created_at", created_at->valuedouble);
}
if (id && cJSON_IsString(id) && id->valuestring) {
cJSON_AddStringToObject(summary, "id", id->valuestring);
}
if (pubkey && cJSON_IsString(pubkey) && pubkey->valuestring) {
cJSON_AddStringToObject(summary, "pubkey", pubkey->valuestring);
}
if (tags && cJSON_IsArray(tags)) {
cJSON* d = find_tag_value_string(tags, "d");
cJSON* scope = find_tag_value_string(tags, "scope");
cJSON* description = find_tag_value_string(tags, "description");
if (d && cJSON_IsString(d) && d->valuestring) {
cJSON_AddStringToObject(summary, "slug", d->valuestring);
}
if (scope && cJSON_IsString(scope) && scope->valuestring) {
cJSON_AddStringToObject(summary, "scope", scope->valuestring);
}
if (description && cJSON_IsString(description) && description->valuestring) {
cJSON_AddStringToObject(summary, "description", description->valuestring);
}
}
if (content && cJSON_IsString(content) && content->valuestring) {
size_t len = strlen(content->valuestring);
size_t preview_len = len > 200U ? 200U : len;
char* preview = (char*)malloc(preview_len + 1U);
if (!preview) {
cJSON_Delete(summary);
return NULL;
}
memcpy(preview, content->valuestring, preview_len);
preview[preview_len] = '\0';
cJSON_AddStringToObject(summary, "content_preview", preview);
cJSON_AddNumberToObject(summary, "content_length", (double)len);
cJSON_AddBoolToObject(summary, "content_truncated", len > preview_len ? 1 : 0);
free(preview);
}
return summary;
}
char* tools_build_openai_schema_json(const tools_context_t* ctx) {
(void)ctx;
cJSON* tools = cJSON_CreateArray();
if (!tools) return NULL;
cJSON* t1 = cJSON_CreateObject();
cJSON* t1_fn = cJSON_CreateObject();
cJSON* t1_params = cJSON_CreateObject();
cJSON* t1_props = cJSON_CreateObject();
cJSON* t1_required = cJSON_CreateArray();
cJSON_AddStringToObject(t1, "type", "function");
cJSON_AddStringToObject(t1_fn, "name", "nostr_post");
cJSON_AddStringToObject(t1_fn, "description", "Publish a Nostr event to connected relays");
cJSON_AddStringToObject(t1_params, "type", "object");
cJSON_AddItemToObject(t1_params, "properties", t1_props);
cJSON_AddItemToObject(t1_params, "required", t1_required);
cJSON* p_kind = cJSON_CreateObject();
cJSON_AddStringToObject(p_kind, "type", "integer");
cJSON_AddItemToObject(t1_props, "kind", p_kind);
cJSON* p_content = cJSON_CreateObject();
cJSON_AddStringToObject(p_content, "type", "string");
cJSON_AddItemToObject(t1_props, "content", p_content);
cJSON* p_tags = cJSON_CreateObject();
cJSON_AddStringToObject(p_tags, "type", "array");
cJSON_AddStringToObject(p_tags, "description", "Optional Nostr tags array, e.g. [[\"d\",\"slug\"],[\"t\",\"nostr\"]]");
cJSON* p_tags_items = cJSON_CreateObject();
cJSON_AddStringToObject(p_tags_items, "type", "array");
cJSON* p_tag_item = cJSON_CreateObject();
cJSON_AddStringToObject(p_tag_item, "type", "string");
cJSON_AddItemToObject(p_tags_items, "items", p_tag_item);
cJSON_AddItemToObject(p_tags, "items", p_tags_items);
cJSON_AddItemToObject(t1_props, "tags", p_tags);
cJSON_AddItemToArray(t1_required, cJSON_CreateString("kind"));
cJSON_AddItemToArray(t1_required, cJSON_CreateString("content"));
cJSON_AddItemToObject(t1_fn, "parameters", t1_params);
cJSON_AddItemToObject(t1, "function", t1_fn);
cJSON_AddItemToArray(tools, t1);
cJSON* t2 = cJSON_CreateObject();
cJSON* t2_fn = cJSON_CreateObject();
cJSON* t2_params = cJSON_CreateObject();
cJSON* t2_props = cJSON_CreateObject();
cJSON* t2_required = cJSON_CreateArray();
cJSON_AddStringToObject(t2, "type", "function");
cJSON_AddStringToObject(t2_fn, "name", "nostr_query");
cJSON_AddStringToObject(t2_fn, "description", "Query events from relays using a Nostr filter");
cJSON_AddStringToObject(t2_params, "type", "object");
cJSON_AddItemToObject(t2_params, "properties", t2_props);
cJSON_AddItemToObject(t2_params, "required", t2_required);
cJSON* p_filter = cJSON_CreateObject();
cJSON_AddStringToObject(p_filter, "type", "object");
cJSON_AddItemToObject(t2_props, "filter", p_filter);
cJSON* p_timeout = cJSON_CreateObject();
cJSON_AddStringToObject(p_timeout, "type", "integer");
cJSON_AddItemToObject(t2_props, "timeout_ms", p_timeout);
cJSON_AddItemToArray(t2_required, cJSON_CreateString("filter"));
cJSON_AddItemToObject(t2_fn, "parameters", t2_params);
cJSON_AddItemToObject(t2, "function", t2_fn);
cJSON_AddItemToArray(tools, t2);
cJSON* t3 = cJSON_CreateObject();
cJSON* t3_fn = cJSON_CreateObject();
cJSON* t3_params = cJSON_CreateObject();
cJSON* t3_props = cJSON_CreateObject();
cJSON* t3_required = cJSON_CreateArray();
cJSON_AddStringToObject(t3, "type", "function");
cJSON_AddStringToObject(t3_fn, "name", "shell_exec");
cJSON_AddStringToObject(t3_fn, "description", "Execute a shell command and return stdout/stderr");
cJSON_AddStringToObject(t3_params, "type", "object");
cJSON_AddItemToObject(t3_params, "properties", t3_props);
cJSON_AddItemToObject(t3_params, "required", t3_required);
cJSON* p_cmd = cJSON_CreateObject();
cJSON_AddStringToObject(p_cmd, "type", "string");
cJSON_AddItemToObject(t3_props, "command", p_cmd);
cJSON_AddItemToArray(t3_required, cJSON_CreateString("command"));
cJSON_AddItemToObject(t3_fn, "parameters", t3_params);
cJSON_AddItemToObject(t3, "function", t3_fn);
cJSON_AddItemToArray(tools, t3);
cJSON* t4 = cJSON_CreateObject();
cJSON* t4_fn = cJSON_CreateObject();
cJSON* t4_params = cJSON_CreateObject();
cJSON* t4_props = cJSON_CreateObject();
cJSON* t4_required = cJSON_CreateArray();
cJSON_AddStringToObject(t4, "type", "function");
cJSON_AddStringToObject(t4_fn, "name", "file_read");
cJSON_AddStringToObject(t4_fn, "description", "Read a local file as text from the configured working directory");
cJSON_AddStringToObject(t4_params, "type", "object");
cJSON_AddItemToObject(t4_params, "properties", t4_props);
cJSON_AddItemToObject(t4_params, "required", t4_required);
cJSON* p_fr_path = cJSON_CreateObject();
cJSON_AddStringToObject(p_fr_path, "type", "string");
cJSON_AddItemToObject(t4_props, "path", p_fr_path);
cJSON* p_fr_max = cJSON_CreateObject();
cJSON_AddStringToObject(p_fr_max, "type", "integer");
cJSON_AddItemToObject(t4_props, "max_bytes", p_fr_max);
cJSON_AddItemToArray(t4_required, cJSON_CreateString("path"));
cJSON_AddItemToObject(t4_fn, "parameters", t4_params);
cJSON_AddItemToObject(t4, "function", t4_fn);
cJSON_AddItemToArray(tools, t4);
cJSON* t5 = cJSON_CreateObject();
cJSON* t5_fn = cJSON_CreateObject();
cJSON* t5_params = cJSON_CreateObject();
cJSON* t5_props = cJSON_CreateObject();
cJSON* t5_required = cJSON_CreateArray();
cJSON_AddStringToObject(t5, "type", "function");
cJSON_AddStringToObject(t5_fn, "name", "file_write");
cJSON_AddStringToObject(t5_fn, "description", "Write text content to a local file in the configured working directory");
cJSON_AddStringToObject(t5_params, "type", "object");
cJSON_AddItemToObject(t5_params, "properties", t5_props);
cJSON_AddItemToObject(t5_params, "required", t5_required);
cJSON* p_fw_path = cJSON_CreateObject();
cJSON_AddStringToObject(p_fw_path, "type", "string");
cJSON_AddItemToObject(t5_props, "path", p_fw_path);
cJSON* p_fw_content = cJSON_CreateObject();
cJSON_AddStringToObject(p_fw_content, "type", "string");
cJSON_AddItemToObject(t5_props, "content", p_fw_content);
cJSON* p_fw_append = cJSON_CreateObject();
cJSON_AddStringToObject(p_fw_append, "type", "boolean");
cJSON_AddItemToObject(t5_props, "append", p_fw_append);
cJSON_AddItemToArray(t5_required, cJSON_CreateString("path"));
cJSON_AddItemToArray(t5_required, cJSON_CreateString("content"));
cJSON_AddItemToObject(t5_fn, "parameters", t5_params);
cJSON_AddItemToObject(t5, "function", t5_fn);
cJSON_AddItemToArray(tools, t5);
cJSON* t6 = cJSON_CreateObject();
cJSON* t6_fn = cJSON_CreateObject();
cJSON* t6_params = cJSON_CreateObject();
cJSON* t6_props = cJSON_CreateObject();
cJSON* t6_required = cJSON_CreateArray();
cJSON_AddStringToObject(t6, "type", "function");
cJSON_AddStringToObject(t6_fn, "name", "nostr_post_readme");
cJSON_AddStringToObject(t6_fn, "description", "Publish README.md as kind 30023 with deterministic d tag readme.md");
cJSON_AddStringToObject(t6_params, "type", "object");
cJSON_AddItemToObject(t6_params, "properties", t6_props);
cJSON_AddItemToObject(t6_params, "required", t6_required);
cJSON_AddItemToObject(t6_fn, "parameters", t6_params);
cJSON_AddItemToObject(t6, "function", t6_fn);
cJSON_AddItemToArray(tools, t6);
cJSON* t7 = cJSON_CreateObject();
cJSON* t7_fn = cJSON_CreateObject();
cJSON* t7_params = cJSON_CreateObject();
cJSON* t7_props = cJSON_CreateObject();
cJSON* t7_required = cJSON_CreateArray();
cJSON_AddStringToObject(t7, "type", "function");
cJSON_AddStringToObject(t7_fn, "name", "nostr_delete");
cJSON_AddStringToObject(t7_fn, "description", "Request deletion of one or more previously published events (NIP-09 kind 5)");
cJSON_AddStringToObject(t7_params, "type", "object");
cJSON_AddItemToObject(t7_params, "properties", t7_props);
cJSON_AddItemToObject(t7_params, "required", t7_required);
cJSON* p_del_ids = cJSON_CreateObject();
cJSON_AddStringToObject(p_del_ids, "type", "array");
cJSON* p_del_ids_items = cJSON_CreateObject();
cJSON_AddStringToObject(p_del_ids_items, "type", "string");
cJSON_AddItemToObject(p_del_ids, "items", p_del_ids_items);
cJSON_AddItemToObject(t7_props, "event_ids", p_del_ids);
cJSON* p_del_kinds = cJSON_CreateObject();
cJSON_AddStringToObject(p_del_kinds, "type", "array");
cJSON* p_del_kinds_items = cJSON_CreateObject();
cJSON_AddStringToObject(p_del_kinds_items, "type", "integer");
cJSON_AddItemToObject(p_del_kinds, "items", p_del_kinds_items);
cJSON_AddItemToObject(t7_props, "kinds", p_del_kinds);
cJSON* p_del_reason = cJSON_CreateObject();
cJSON_AddStringToObject(p_del_reason, "type", "string");
cJSON_AddItemToObject(t7_props, "reason", p_del_reason);
cJSON_AddItemToArray(t7_required, cJSON_CreateString("event_ids"));
cJSON_AddItemToObject(t7_fn, "parameters", t7_params);
cJSON_AddItemToObject(t7, "function", t7_fn);
cJSON_AddItemToArray(tools, t7);
cJSON* t8 = cJSON_CreateObject();
cJSON* t8_fn = cJSON_CreateObject();
cJSON* t8_params = cJSON_CreateObject();
cJSON* t8_props = cJSON_CreateObject();
cJSON* t8_required = cJSON_CreateArray();
cJSON_AddStringToObject(t8, "type", "function");
cJSON_AddStringToObject(t8_fn, "name", "nostr_react");
cJSON_AddStringToObject(t8_fn, "description", "React to a Nostr event with like/dislike/emoji (NIP-25 kind 7)");
cJSON_AddStringToObject(t8_params, "type", "object");
cJSON_AddItemToObject(t8_params, "properties", t8_props);
cJSON_AddItemToObject(t8_params, "required", t8_required);
cJSON* p_react_event_id = cJSON_CreateObject();
cJSON_AddStringToObject(p_react_event_id, "type", "string");
cJSON_AddItemToObject(t8_props, "event_id", p_react_event_id);
cJSON* p_react_event_pubkey = cJSON_CreateObject();
cJSON_AddStringToObject(p_react_event_pubkey, "type", "string");
cJSON_AddItemToObject(t8_props, "event_pubkey", p_react_event_pubkey);
cJSON* p_react_event_kind = cJSON_CreateObject();
cJSON_AddStringToObject(p_react_event_kind, "type", "integer");
cJSON_AddItemToObject(t8_props, "event_kind", p_react_event_kind);
cJSON* p_react_reaction = cJSON_CreateObject();
cJSON_AddStringToObject(p_react_reaction, "type", "string");
cJSON_AddItemToObject(t8_props, "reaction", p_react_reaction);
cJSON_AddItemToArray(t8_required, cJSON_CreateString("event_id"));
cJSON_AddItemToArray(t8_required, cJSON_CreateString("event_pubkey"));
cJSON_AddItemToObject(t8_fn, "parameters", t8_params);
cJSON_AddItemToObject(t8, "function", t8_fn);
cJSON_AddItemToArray(tools, t8);
cJSON* t9 = cJSON_CreateObject();
cJSON* t9_fn = cJSON_CreateObject();
cJSON* t9_params = cJSON_CreateObject();
cJSON* t9_props = cJSON_CreateObject();
cJSON* t9_required = cJSON_CreateArray();
cJSON_AddStringToObject(t9, "type", "function");
cJSON_AddStringToObject(t9_fn, "name", "nostr_profile_get");
cJSON_AddStringToObject(t9_fn, "description", "Look up a Nostr profile (kind 0 metadata) by pubkey");
cJSON_AddStringToObject(t9_params, "type", "object");
cJSON_AddItemToObject(t9_params, "properties", t9_props);
cJSON_AddItemToObject(t9_params, "required", t9_required);
cJSON* p_profile_pubkey = cJSON_CreateObject();
cJSON_AddStringToObject(p_profile_pubkey, "type", "string");
cJSON_AddItemToObject(t9_props, "pubkey", p_profile_pubkey);
cJSON_AddItemToArray(t9_required, cJSON_CreateString("pubkey"));
cJSON_AddItemToObject(t9_fn, "parameters", t9_params);
cJSON_AddItemToObject(t9, "function", t9_fn);
cJSON_AddItemToArray(tools, t9);
cJSON* t10 = cJSON_CreateObject();
cJSON* t10_fn = cJSON_CreateObject();
cJSON* t10_params = cJSON_CreateObject();
cJSON* t10_props = cJSON_CreateObject();
cJSON_AddStringToObject(t10, "type", "function");
cJSON_AddStringToObject(t10_fn, "name", "nostr_relay_status");
cJSON_AddStringToObject(t10_fn, "description", "Get connection status and statistics for all relays");
cJSON_AddStringToObject(t10_params, "type", "object");
cJSON_AddItemToObject(t10_params, "properties", t10_props);
cJSON_AddItemToObject(t10_fn, "parameters", t10_params);
cJSON_AddItemToObject(t10, "function", t10_fn);
cJSON_AddItemToArray(tools, t10);
cJSON* t11 = cJSON_CreateObject();
cJSON* t11_fn = cJSON_CreateObject();
cJSON* t11_params = cJSON_CreateObject();
cJSON* t11_props = cJSON_CreateObject();
cJSON* t11_required = cJSON_CreateArray();
cJSON_AddStringToObject(t11, "type", "function");
cJSON_AddStringToObject(t11_fn, "name", "nostr_nip05_lookup");
cJSON_AddStringToObject(t11_fn, "description", "Look up or verify a NIP-05 identifier (user@domain)");
cJSON_AddStringToObject(t11_params, "type", "object");
cJSON_AddItemToObject(t11_params, "properties", t11_props);
cJSON_AddItemToObject(t11_params, "required", t11_required);
cJSON* p_nip05_identifier = cJSON_CreateObject();
cJSON_AddStringToObject(p_nip05_identifier, "type", "string");
cJSON_AddItemToObject(t11_props, "identifier", p_nip05_identifier);
cJSON* p_nip05_pubkey = cJSON_CreateObject();
cJSON_AddStringToObject(p_nip05_pubkey, "type", "string");
cJSON_AddItemToObject(t11_props, "pubkey", p_nip05_pubkey);
cJSON_AddItemToArray(t11_required, cJSON_CreateString("identifier"));
cJSON_AddItemToObject(t11_fn, "parameters", t11_params);
cJSON_AddItemToObject(t11, "function", t11_fn);
cJSON_AddItemToArray(tools, t11);
cJSON* t12 = cJSON_CreateObject();
cJSON* t12_fn = cJSON_CreateObject();
cJSON* t12_params = cJSON_CreateObject();
cJSON* t12_props = cJSON_CreateObject();
cJSON* t12_required = cJSON_CreateArray();
cJSON_AddStringToObject(t12, "type", "function");
cJSON_AddStringToObject(t12_fn, "name", "nostr_encode");
cJSON_AddStringToObject(t12_fn, "description", "Encode a Nostr entity into nostr: URI (npub, note, nprofile, nevent, naddr)");
cJSON_AddStringToObject(t12_params, "type", "object");
cJSON_AddItemToObject(t12_params, "properties", t12_props);
cJSON_AddItemToObject(t12_params, "required", t12_required);
cJSON* p_encode_type = cJSON_CreateObject();
cJSON_AddStringToObject(p_encode_type, "type", "string");
cJSON_AddItemToObject(t12_props, "type", p_encode_type);
cJSON* p_encode_hex = cJSON_CreateObject();
cJSON_AddStringToObject(p_encode_hex, "type", "string");
cJSON_AddItemToObject(t12_props, "hex", p_encode_hex);
cJSON* p_encode_relays = cJSON_CreateObject();
cJSON_AddStringToObject(p_encode_relays, "type", "array");
cJSON* p_encode_relays_item = cJSON_CreateObject();
cJSON_AddStringToObject(p_encode_relays_item, "type", "string");
cJSON_AddItemToObject(p_encode_relays, "items", p_encode_relays_item);
cJSON_AddItemToObject(t12_props, "relays", p_encode_relays);
cJSON* p_encode_kind = cJSON_CreateObject();
cJSON_AddStringToObject(p_encode_kind, "type", "integer");
cJSON_AddItemToObject(t12_props, "kind", p_encode_kind);
cJSON* p_encode_identifier = cJSON_CreateObject();
cJSON_AddStringToObject(p_encode_identifier, "type", "string");
cJSON_AddItemToObject(t12_props, "identifier", p_encode_identifier);
cJSON_AddItemToArray(t12_required, cJSON_CreateString("type"));
cJSON_AddItemToArray(t12_required, cJSON_CreateString("hex"));
cJSON_AddItemToObject(t12_fn, "parameters", t12_params);
cJSON_AddItemToObject(t12, "function", t12_fn);
cJSON_AddItemToArray(tools, t12);
cJSON* t13 = cJSON_CreateObject();
cJSON* t13_fn = cJSON_CreateObject();
cJSON* t13_params = cJSON_CreateObject();
cJSON* t13_props = cJSON_CreateObject();
cJSON* t13_required = cJSON_CreateArray();
cJSON_AddStringToObject(t13, "type", "function");
cJSON_AddStringToObject(t13_fn, "name", "nostr_decode");
cJSON_AddStringToObject(t13_fn, "description", "Decode a Nostr bech32/nostr: URI into components");
cJSON_AddStringToObject(t13_params, "type", "object");
cJSON_AddItemToObject(t13_params, "properties", t13_props);
cJSON_AddItemToObject(t13_params, "required", t13_required);
cJSON* p_decode_uri = cJSON_CreateObject();
cJSON_AddStringToObject(p_decode_uri, "type", "string");
cJSON_AddItemToObject(t13_props, "uri", p_decode_uri);
cJSON_AddItemToArray(t13_required, cJSON_CreateString("uri"));
cJSON_AddItemToObject(t13_fn, "parameters", t13_params);
cJSON_AddItemToObject(t13, "function", t13_fn);
cJSON_AddItemToArray(tools, t13);
cJSON* t14 = cJSON_CreateObject();
cJSON* t14_fn = cJSON_CreateObject();
cJSON* t14_params = cJSON_CreateObject();
cJSON* t14_props = cJSON_CreateObject();
cJSON* t14_required = cJSON_CreateArray();
cJSON_AddStringToObject(t14, "type", "function");
cJSON_AddStringToObject(t14_fn, "name", "nostr_dm_send");
cJSON_AddStringToObject(t14_fn, "description", "Send a NIP-04 encrypted DM");
cJSON_AddStringToObject(t14_params, "type", "object");
cJSON_AddItemToObject(t14_params, "properties", t14_props);
cJSON_AddItemToObject(t14_params, "required", t14_required);
cJSON* p_dm_recipient = cJSON_CreateObject();
cJSON_AddStringToObject(p_dm_recipient, "type", "string");
cJSON_AddItemToObject(t14_props, "recipient_pubkey", p_dm_recipient);
cJSON* p_dm_message = cJSON_CreateObject();
cJSON_AddStringToObject(p_dm_message, "type", "string");
cJSON_AddItemToObject(t14_props, "message", p_dm_message);
cJSON_AddItemToArray(t14_required, cJSON_CreateString("recipient_pubkey"));
cJSON_AddItemToArray(t14_required, cJSON_CreateString("message"));
cJSON_AddItemToObject(t14_fn, "parameters", t14_params);
cJSON_AddItemToObject(t14, "function", t14_fn);
cJSON_AddItemToArray(tools, t14);
cJSON* t15 = cJSON_CreateObject();
cJSON* t15_fn = cJSON_CreateObject();
cJSON* t15_params = cJSON_CreateObject();
cJSON* t15_props = cJSON_CreateObject();
cJSON* t15_required = cJSON_CreateArray();
cJSON_AddStringToObject(t15, "type", "function");
cJSON_AddStringToObject(t15_fn, "name", "nostr_relay_info");
cJSON_AddStringToObject(t15_fn, "description", "Fetch NIP-11 relay information document");
cJSON_AddStringToObject(t15_params, "type", "object");
cJSON_AddItemToObject(t15_params, "properties", t15_props);
cJSON_AddItemToObject(t15_params, "required", t15_required);
cJSON* p_relay_info_url = cJSON_CreateObject();
cJSON_AddStringToObject(p_relay_info_url, "type", "string");
cJSON_AddItemToObject(t15_props, "relay_url", p_relay_info_url);
cJSON_AddItemToArray(t15_required, cJSON_CreateString("relay_url"));
cJSON_AddItemToObject(t15_fn, "parameters", t15_params);
cJSON_AddItemToObject(t15, "function", t15_fn);
cJSON_AddItemToArray(tools, t15);
cJSON* t16 = cJSON_CreateObject();
cJSON* t16_fn = cJSON_CreateObject();
cJSON* t16_params = cJSON_CreateObject();
cJSON* t16_props = cJSON_CreateObject();
cJSON* t16_required = cJSON_CreateArray();
cJSON_AddStringToObject(t16, "type", "function");
cJSON_AddStringToObject(t16_fn, "name", "nostr_encrypt");
cJSON_AddStringToObject(t16_fn, "description", "Encrypt plaintext using NIP-44 for a recipient");
cJSON_AddStringToObject(t16_params, "type", "object");
cJSON_AddItemToObject(t16_params, "properties", t16_props);
cJSON_AddItemToObject(t16_params, "required", t16_required);
cJSON* p_encrypt_recipient = cJSON_CreateObject();
cJSON_AddStringToObject(p_encrypt_recipient, "type", "string");
cJSON_AddItemToObject(t16_props, "recipient_pubkey", p_encrypt_recipient);
cJSON* p_encrypt_plaintext = cJSON_CreateObject();
cJSON_AddStringToObject(p_encrypt_plaintext, "type", "string");
cJSON_AddItemToObject(t16_props, "plaintext", p_encrypt_plaintext);
cJSON_AddItemToArray(t16_required, cJSON_CreateString("recipient_pubkey"));
cJSON_AddItemToArray(t16_required, cJSON_CreateString("plaintext"));
cJSON_AddItemToObject(t16_fn, "parameters", t16_params);
cJSON_AddItemToObject(t16, "function", t16_fn);
cJSON_AddItemToArray(tools, t16);
cJSON* t17 = cJSON_CreateObject();
cJSON* t17_fn = cJSON_CreateObject();
cJSON* t17_params = cJSON_CreateObject();
cJSON* t17_props = cJSON_CreateObject();
cJSON* t17_required = cJSON_CreateArray();
cJSON_AddStringToObject(t17, "type", "function");
cJSON_AddStringToObject(t17_fn, "name", "nostr_decrypt");
cJSON_AddStringToObject(t17_fn, "description", "Decrypt NIP-44 ciphertext from a sender");
cJSON_AddStringToObject(t17_params, "type", "object");
cJSON_AddItemToObject(t17_params, "properties", t17_props);
cJSON_AddItemToObject(t17_params, "required", t17_required);
cJSON* p_decrypt_sender = cJSON_CreateObject();
cJSON_AddStringToObject(p_decrypt_sender, "type", "string");
cJSON_AddItemToObject(t17_props, "sender_pubkey", p_decrypt_sender);
cJSON* p_decrypt_ciphertext = cJSON_CreateObject();
cJSON_AddStringToObject(p_decrypt_ciphertext, "type", "string");
cJSON_AddItemToObject(t17_props, "ciphertext", p_decrypt_ciphertext);
cJSON_AddItemToArray(t17_required, cJSON_CreateString("sender_pubkey"));
cJSON_AddItemToArray(t17_required, cJSON_CreateString("ciphertext"));
cJSON_AddItemToObject(t17_fn, "parameters", t17_params);
cJSON_AddItemToObject(t17, "function", t17_fn);
cJSON_AddItemToArray(tools, t17);
cJSON* t18 = cJSON_CreateObject();
cJSON* t18_fn = cJSON_CreateObject();
cJSON* t18_params = cJSON_CreateObject();
cJSON* t18_props = cJSON_CreateObject();
cJSON* t18_required = cJSON_CreateArray();
cJSON_AddStringToObject(t18, "type", "function");
cJSON_AddStringToObject(t18_fn, "name", "nostr_dm_send_nip17");
cJSON_AddStringToObject(t18_fn, "description", "Send a private DM using NIP-17 gift wrap protocol");
cJSON_AddStringToObject(t18_params, "type", "object");
cJSON_AddItemToObject(t18_params, "properties", t18_props);
cJSON_AddItemToObject(t18_params, "required", t18_required);
cJSON* p_dm17_recipient = cJSON_CreateObject();
cJSON_AddStringToObject(p_dm17_recipient, "type", "string");
cJSON_AddItemToObject(t18_props, "recipient_pubkey", p_dm17_recipient);
cJSON* p_dm17_message = cJSON_CreateObject();
cJSON_AddStringToObject(p_dm17_message, "type", "string");
cJSON_AddItemToObject(t18_props, "message", p_dm17_message);
cJSON* p_dm17_subject = cJSON_CreateObject();
cJSON_AddStringToObject(p_dm17_subject, "type", "string");
cJSON_AddItemToObject(t18_props, "subject", p_dm17_subject);
cJSON_AddItemToArray(t18_required, cJSON_CreateString("recipient_pubkey"));
cJSON_AddItemToArray(t18_required, cJSON_CreateString("message"));
cJSON_AddItemToObject(t18_fn, "parameters", t18_params);
cJSON_AddItemToObject(t18, "function", t18_fn);
cJSON_AddItemToArray(tools, t18);
cJSON* t19 = cJSON_CreateObject();
cJSON* t19_fn = cJSON_CreateObject();
cJSON* t19_params = cJSON_CreateObject();
cJSON* t19_props = cJSON_CreateObject();
cJSON* t19_required = cJSON_CreateArray();
cJSON_AddStringToObject(t19, "type", "function");
cJSON_AddStringToObject(t19_fn, "name", "nostr_list_manage");
cJSON_AddStringToObject(t19_fn, "description", "Add/remove tag tuples in replaceable list events (NIP-51 style)");
cJSON_AddStringToObject(t19_params, "type", "object");
cJSON_AddItemToObject(t19_params, "properties", t19_props);
cJSON_AddItemToObject(t19_params, "required", t19_required);
cJSON* p_list_kind = cJSON_CreateObject();
cJSON_AddStringToObject(p_list_kind, "type", "integer");
cJSON_AddItemToObject(t19_props, "list_kind", p_list_kind);
cJSON* p_list_action = cJSON_CreateObject();
cJSON_AddStringToObject(p_list_action, "type", "string");
cJSON_AddItemToObject(t19_props, "action", p_list_action);
cJSON* p_list_items = cJSON_CreateObject();
cJSON_AddStringToObject(p_list_items, "type", "array");
cJSON* p_list_items_item = cJSON_CreateObject();
cJSON_AddStringToObject(p_list_items_item, "type", "array");
cJSON* p_list_items_item_item = cJSON_CreateObject();
cJSON_AddStringToObject(p_list_items_item_item, "type", "string");
cJSON_AddItemToObject(p_list_items_item, "items", p_list_items_item_item);
cJSON_AddItemToObject(p_list_items, "items", p_list_items_item);
cJSON_AddItemToObject(t19_props, "items", p_list_items);
cJSON_AddItemToArray(t19_required, cJSON_CreateString("list_kind"));
cJSON_AddItemToArray(t19_required, cJSON_CreateString("action"));
cJSON_AddItemToArray(t19_required, cJSON_CreateString("items"));
cJSON_AddItemToObject(t19_fn, "parameters", t19_params);
cJSON_AddItemToObject(t19, "function", t19_fn);
cJSON_AddItemToArray(tools, t19);
cJSON* t20 = cJSON_CreateObject();
cJSON* t20_fn = cJSON_CreateObject();
cJSON* t20_params = cJSON_CreateObject();
cJSON* t20_props = cJSON_CreateObject();
cJSON_AddStringToObject(t20, "type", "function");
cJSON_AddStringToObject(t20_fn, "name", "my_version");
cJSON_AddStringToObject(t20_fn, "description", "Return current Didactyl version and metadata from build macros");
cJSON_AddStringToObject(t20_params, "type", "object");
cJSON_AddItemToObject(t20_params, "properties", t20_props);
cJSON_AddItemToObject(t20_fn, "parameters", t20_params);
cJSON_AddItemToObject(t20, "function", t20_fn);
cJSON_AddItemToArray(tools, t20);
cJSON* t21 = cJSON_CreateObject();
cJSON* t21_fn = cJSON_CreateObject();
cJSON* t21_params = cJSON_CreateObject();
cJSON* t21_props = cJSON_CreateObject();
cJSON* t21_required = cJSON_CreateArray();
cJSON_AddStringToObject(t21, "type", "function");
cJSON_AddStringToObject(t21_fn, "name", "http_fetch");
cJSON_AddStringToObject(t21_fn, "description", "Fetch HTTP(S) resources with optional method, headers, timeout, and body");
cJSON_AddStringToObject(t21_params, "type", "object");
cJSON_AddItemToObject(t21_params, "properties", t21_props);
cJSON_AddItemToObject(t21_params, "required", t21_required);
cJSON* p_http_url = cJSON_CreateObject();
cJSON_AddStringToObject(p_http_url, "type", "string");
cJSON_AddItemToObject(t21_props, "url", p_http_url);
cJSON* p_http_method = cJSON_CreateObject();
cJSON_AddStringToObject(p_http_method, "type", "string");
cJSON_AddItemToObject(t21_props, "method", p_http_method);
cJSON* p_http_headers = cJSON_CreateObject();
cJSON_AddStringToObject(p_http_headers, "type", "array");
cJSON* p_http_headers_item = cJSON_CreateObject();
cJSON_AddStringToObject(p_http_headers_item, "type", "string");
cJSON_AddItemToObject(p_http_headers, "items", p_http_headers_item);
cJSON_AddItemToObject(t21_props, "headers", p_http_headers);
cJSON* p_http_body = cJSON_CreateObject();
cJSON_AddStringToObject(p_http_body, "type", "string");
cJSON_AddItemToObject(t21_props, "body", p_http_body);
cJSON* p_http_timeout = cJSON_CreateObject();
cJSON_AddStringToObject(p_http_timeout, "type", "integer");
cJSON_AddItemToObject(t21_props, "timeout_seconds", p_http_timeout);
cJSON* p_http_max_bytes = cJSON_CreateObject();
cJSON_AddStringToObject(p_http_max_bytes, "type", "integer");
cJSON_AddItemToObject(t21_props, "max_bytes", p_http_max_bytes);
cJSON_AddItemToArray(t21_required, cJSON_CreateString("url"));
cJSON_AddItemToObject(t21_fn, "parameters", t21_params);
cJSON_AddItemToObject(t21, "function", t21_fn);
cJSON_AddItemToArray(tools, t21);
cJSON* t22 = cJSON_CreateObject();
cJSON* t22_fn = cJSON_CreateObject();
cJSON* t22_params = cJSON_CreateObject();
cJSON* t22_props = cJSON_CreateObject();
cJSON* t22_required = cJSON_CreateArray();
cJSON_AddStringToObject(t22, "type", "function");
cJSON_AddStringToObject(t22_fn, "name", "skill_create");
cJSON_AddStringToObject(t22_fn, "description", "Create or update a skill definition as kind 31123/31124 and optionally auto-adopt it");
cJSON_AddStringToObject(t22_params, "type", "object");
cJSON_AddItemToObject(t22_params, "properties", t22_props);
cJSON_AddItemToObject(t22_params, "required", t22_required);
cJSON* p_skill_create_slug = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_create_slug, "type", "string");
cJSON_AddItemToObject(t22_props, "slug", p_skill_create_slug);
cJSON* p_skill_create_content = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_create_content, "type", "string");
cJSON_AddItemToObject(t22_props, "content", p_skill_create_content);
cJSON* p_skill_create_scope = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_create_scope, "type", "string");
cJSON_AddItemToObject(t22_props, "scope", p_skill_create_scope);
cJSON* p_skill_create_desc = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_create_desc, "type", "string");
cJSON_AddItemToObject(t22_props, "description", p_skill_create_desc);
cJSON* p_skill_create_auto = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_create_auto, "type", "boolean");
cJSON_AddItemToObject(t22_props, "auto_adopt", p_skill_create_auto);
cJSON_AddItemToArray(t22_required, cJSON_CreateString("slug"));
cJSON_AddItemToArray(t22_required, cJSON_CreateString("content"));
cJSON_AddItemToObject(t22_fn, "parameters", t22_params);
cJSON_AddItemToObject(t22, "function", t22_fn);
cJSON_AddItemToArray(tools, t22);
cJSON* t23 = cJSON_CreateObject();
cJSON* t23_fn = cJSON_CreateObject();
cJSON* t23_params = cJSON_CreateObject();
cJSON* t23_props = cJSON_CreateObject();
cJSON_AddStringToObject(t23, "type", "function");
cJSON_AddStringToObject(t23_fn, "name", "skill_list");
cJSON_AddStringToObject(t23_fn, "description", "List this agent's published skills, optionally filtered by scope");
cJSON_AddStringToObject(t23_params, "type", "object");
cJSON_AddItemToObject(t23_params, "properties", t23_props);
cJSON* p_skill_list_scope = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_list_scope, "type", "string");
cJSON_AddItemToObject(t23_props, "scope", p_skill_list_scope);
cJSON_AddItemToObject(t23_fn, "parameters", t23_params);
cJSON_AddItemToObject(t23, "function", t23_fn);
cJSON_AddItemToArray(tools, t23);
cJSON* t24 = cJSON_CreateObject();
cJSON* t24_fn = cJSON_CreateObject();
cJSON* t24_params = cJSON_CreateObject();
cJSON* t24_props = cJSON_CreateObject();
cJSON* t24_required = cJSON_CreateArray();
cJSON_AddStringToObject(t24, "type", "function");
cJSON_AddStringToObject(t24_fn, "name", "skill_adopt");
cJSON_AddStringToObject(t24_fn, "description", "Adopt a skill by adding its address to kind 10123 adoption list");
cJSON_AddStringToObject(t24_params, "type", "object");
cJSON_AddItemToObject(t24_params, "properties", t24_props);
cJSON_AddItemToObject(t24_params, "required", t24_required);
cJSON* p_skill_adopt_pubkey = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_adopt_pubkey, "type", "string");
cJSON_AddItemToObject(t24_props, "pubkey", p_skill_adopt_pubkey);
cJSON* p_skill_adopt_slug = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_adopt_slug, "type", "string");
cJSON_AddItemToObject(t24_props, "slug", p_skill_adopt_slug);
cJSON* p_skill_adopt_kind = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_adopt_kind, "type", "integer");
cJSON_AddItemToObject(t24_props, "kind", p_skill_adopt_kind);
cJSON_AddItemToArray(t24_required, cJSON_CreateString("pubkey"));
cJSON_AddItemToArray(t24_required, cJSON_CreateString("slug"));
cJSON_AddItemToObject(t24_fn, "parameters", t24_params);
cJSON_AddItemToObject(t24, "function", t24_fn);
cJSON_AddItemToArray(tools, t24);
cJSON* t25 = cJSON_CreateObject();
cJSON* t25_fn = cJSON_CreateObject();
cJSON* t25_params = cJSON_CreateObject();
cJSON* t25_props = cJSON_CreateObject();
cJSON* t25_required = cJSON_CreateArray();
cJSON_AddStringToObject(t25, "type", "function");
cJSON_AddStringToObject(t25_fn, "name", "skill_remove");
cJSON_AddStringToObject(t25_fn, "description", "Remove a skill address from kind 10123 adoption list");
cJSON_AddStringToObject(t25_params, "type", "object");
cJSON_AddItemToObject(t25_params, "properties", t25_props);
cJSON_AddItemToObject(t25_params, "required", t25_required);
cJSON* p_skill_remove_pubkey = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_remove_pubkey, "type", "string");
cJSON_AddItemToObject(t25_props, "pubkey", p_skill_remove_pubkey);
cJSON* p_skill_remove_slug = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_remove_slug, "type", "string");
cJSON_AddItemToObject(t25_props, "slug", p_skill_remove_slug);
cJSON* p_skill_remove_kind = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_remove_kind, "type", "integer");
cJSON_AddItemToObject(t25_props, "kind", p_skill_remove_kind);
cJSON_AddItemToArray(t25_required, cJSON_CreateString("slug"));
cJSON_AddItemToObject(t25_fn, "parameters", t25_params);
cJSON_AddItemToObject(t25, "function", t25_fn);
cJSON_AddItemToArray(tools, t25);
cJSON* t26 = cJSON_CreateObject();
cJSON* t26_fn = cJSON_CreateObject();
cJSON* t26_params = cJSON_CreateObject();
cJSON* t26_props = cJSON_CreateObject();
cJSON_AddStringToObject(t26, "type", "function");
cJSON_AddStringToObject(t26_fn, "name", "skill_search");
cJSON_AddStringToObject(t26_fn, "description", "Search public skills by query/author and optionally rank by adoption popularity");
cJSON_AddStringToObject(t26_params, "type", "object");
cJSON_AddItemToObject(t26_params, "properties", t26_props);
cJSON* p_skill_search_query = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_search_query, "type", "string");
cJSON_AddItemToObject(t26_props, "query", p_skill_search_query);
cJSON* p_skill_search_pubkey = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_search_pubkey, "type", "string");
cJSON_AddItemToObject(t26_props, "pubkey", p_skill_search_pubkey);
cJSON* p_skill_search_popular = cJSON_CreateObject();
cJSON_AddStringToObject(p_skill_search_popular, "type", "boolean");
cJSON_AddItemToObject(t26_props, "popular", p_skill_search_popular);
cJSON_AddItemToObject(t26_fn, "parameters", t26_params);
cJSON_AddItemToObject(t26, "function", t26_fn);
cJSON_AddItemToArray(tools, t26);
cJSON* t27 = cJSON_CreateObject();
cJSON* t27_fn = cJSON_CreateObject();
cJSON* t27_params = cJSON_CreateObject();
cJSON* t27_props = cJSON_CreateObject();
cJSON_AddStringToObject(t27, "type", "function");
cJSON_AddStringToObject(t27_fn, "name", "trigger_list");
cJSON_AddStringToObject(t27_fn, "description", "List active triggered skills and their runtime status");
cJSON_AddStringToObject(t27_params, "type", "object");
cJSON_AddItemToObject(t27_params, "properties", t27_props);
cJSON_AddItemToObject(t27_fn, "parameters", t27_params);
cJSON_AddItemToObject(t27, "function", t27_fn);
cJSON_AddItemToArray(tools, t27);
cJSON* t28 = cJSON_CreateObject();
cJSON* t28_fn = cJSON_CreateObject();
cJSON* t28_params = cJSON_CreateObject();
cJSON* t28_props = cJSON_CreateObject();
cJSON* t28_required = cJSON_CreateArray();
cJSON_AddStringToObject(t28, "type", "function");
cJSON_AddStringToObject(t28_fn, "name", "nostr_file_md_to_longform_post");
cJSON_AddStringToObject(t28_fn, "description", "Read a markdown file and publish it as kind 30023 longform post; defaults d-tag to lowercase filename");
cJSON_AddStringToObject(t28_params, "type", "object");
cJSON_AddItemToObject(t28_params, "properties", t28_props);
cJSON_AddItemToObject(t28_params, "required", t28_required);
cJSON* p_lf_file = cJSON_CreateObject();
cJSON_AddStringToObject(p_lf_file, "type", "string");
cJSON_AddItemToObject(t28_props, "file", p_lf_file);
cJSON* p_lf_title = cJSON_CreateObject();
cJSON_AddStringToObject(p_lf_title, "type", "string");
cJSON_AddItemToObject(t28_props, "title", p_lf_title);
cJSON* p_lf_image = cJSON_CreateObject();
cJSON_AddStringToObject(p_lf_image, "type", "string");
cJSON_AddItemToObject(t28_props, "image", p_lf_image);
cJSON* p_lf_summary = cJSON_CreateObject();
cJSON_AddStringToObject(p_lf_summary, "type", "string");
cJSON_AddItemToObject(t28_props, "summary", p_lf_summary);
cJSON_AddItemToArray(t28_required, cJSON_CreateString("file"));
cJSON_AddItemToObject(t28_fn, "parameters", t28_params);
cJSON_AddItemToObject(t28, "function", t28_fn);
cJSON_AddItemToArray(tools, t28);
cJSON* t29 = cJSON_CreateObject();
cJSON* t29_fn = cJSON_CreateObject();
cJSON* t29_params = cJSON_CreateObject();
cJSON* t29_props = cJSON_CreateObject();
cJSON_AddStringToObject(t29, "type", "function");
cJSON_AddStringToObject(t29_fn, "name", "tool_list");
cJSON_AddStringToObject(t29_fn, "description", "List available tools with name, description, and JSON parameter schema");
cJSON_AddStringToObject(t29_params, "type", "object");
cJSON_AddItemToObject(t29_params, "properties", t29_props);
cJSON_AddItemToObject(t29_fn, "parameters", t29_params);
cJSON_AddItemToObject(t29, "function", t29_fn);
cJSON_AddItemToArray(tools, t29);
cJSON* t30 = cJSON_CreateObject();
cJSON* t30_fn = cJSON_CreateObject();
cJSON* t30_params = cJSON_CreateObject();
cJSON* t30_props = cJSON_CreateObject();
cJSON_AddStringToObject(t30, "type", "function");
cJSON_AddStringToObject(t30_fn, "name", "model_get");
cJSON_AddStringToObject(t30_fn, "description", "Get current active LLM runtime configuration (excluding API key)");
cJSON_AddStringToObject(t30_params, "type", "object");
cJSON_AddItemToObject(t30_params, "properties", t30_props);
cJSON_AddItemToObject(t30_fn, "parameters", t30_params);
cJSON_AddItemToObject(t30, "function", t30_fn);
cJSON_AddItemToArray(tools, t30);
cJSON* t31 = cJSON_CreateObject();
cJSON* t31_fn = cJSON_CreateObject();
cJSON* t31_params = cJSON_CreateObject();
cJSON* t31_props = cJSON_CreateObject();
cJSON_AddStringToObject(t31, "type", "function");
cJSON_AddStringToObject(t31_fn, "name", "model_set");
cJSON_AddStringToObject(t31_fn, "description", "Update active LLM configuration and persist it to config.json");
cJSON_AddStringToObject(t31_params, "type", "object");
cJSON_AddItemToObject(t31_params, "properties", t31_props);
cJSON* p_model_set_provider = cJSON_CreateObject();
cJSON_AddStringToObject(p_model_set_provider, "type", "string");
cJSON_AddItemToObject(t31_props, "provider", p_model_set_provider);
cJSON* p_model_set_api_key = cJSON_CreateObject();
cJSON_AddStringToObject(p_model_set_api_key, "type", "string");
cJSON_AddItemToObject(t31_props, "api_key", p_model_set_api_key);
cJSON* p_model_set_model = cJSON_CreateObject();
cJSON_AddStringToObject(p_model_set_model, "type", "string");
cJSON_AddItemToObject(t31_props, "model", p_model_set_model);
cJSON* p_model_set_base_url = cJSON_CreateObject();
cJSON_AddStringToObject(p_model_set_base_url, "type", "string");
cJSON_AddItemToObject(t31_props, "base_url", p_model_set_base_url);
cJSON* p_model_set_max_tokens = cJSON_CreateObject();
cJSON_AddStringToObject(p_model_set_max_tokens, "type", "integer");
cJSON_AddItemToObject(t31_props, "max_tokens", p_model_set_max_tokens);
cJSON* p_model_set_temperature = cJSON_CreateObject();
cJSON_AddStringToObject(p_model_set_temperature, "type", "number");
cJSON_AddItemToObject(t31_props, "temperature", p_model_set_temperature);
cJSON_AddItemToObject(t31_fn, "parameters", t31_params);
cJSON_AddItemToObject(t31, "function", t31_fn);
cJSON_AddItemToArray(tools, t31);
cJSON* t32 = cJSON_CreateObject();
cJSON* t32_fn = cJSON_CreateObject();
cJSON* t32_params = cJSON_CreateObject();
cJSON* t32_props = cJSON_CreateObject();
cJSON_AddStringToObject(t32, "type", "function");
cJSON_AddStringToObject(t32_fn, "name", "model_list");
cJSON_AddStringToObject(t32_fn, "description", "List available model IDs using provider OpenAI-compatible /models endpoint");
cJSON_AddStringToObject(t32_params, "type", "object");
cJSON_AddItemToObject(t32_params, "properties", t32_props);
cJSON* p_model_list_base_url = cJSON_CreateObject();
cJSON_AddStringToObject(p_model_list_base_url, "type", "string");
cJSON_AddItemToObject(t32_props, "base_url", p_model_list_base_url);
cJSON_AddItemToObject(t32_fn, "parameters", t32_params);
cJSON_AddItemToObject(t32, "function", t32_fn);
cJSON_AddItemToArray(tools, t32);
cJSON* t33 = cJSON_CreateObject();
cJSON* t33_fn = cJSON_CreateObject();
cJSON* t33_params = cJSON_CreateObject();
cJSON* t33_props = cJSON_CreateObject();
cJSON_AddStringToObject(t33, "type", "function");
cJSON_AddStringToObject(t33_fn, "name", "nostr_pubkey");
cJSON_AddStringToObject(t33_fn, "description", "Return this agent's pubkey in hex format");
cJSON_AddStringToObject(t33_params, "type", "object");
cJSON_AddItemToObject(t33_params, "properties", t33_props);
cJSON_AddItemToObject(t33_fn, "parameters", t33_params);
cJSON_AddItemToObject(t33, "function", t33_fn);
cJSON_AddItemToArray(tools, t33);
cJSON* t34 = cJSON_CreateObject();
cJSON* t34_fn = cJSON_CreateObject();
cJSON* t34_params = cJSON_CreateObject();
cJSON* t34_props = cJSON_CreateObject();
cJSON_AddStringToObject(t34, "type", "function");
cJSON_AddStringToObject(t34_fn, "name", "nostr_npub");
cJSON_AddStringToObject(t34_fn, "description", "Return this agent's pubkey encoded as npub bech32");
cJSON_AddStringToObject(t34_params, "type", "object");
cJSON_AddItemToObject(t34_params, "properties", t34_props);
cJSON_AddItemToObject(t34_fn, "parameters", t34_params);
cJSON_AddItemToObject(t34, "function", t34_fn);
cJSON_AddItemToArray(tools, t34);
cJSON* t35 = cJSON_CreateObject();
cJSON* t35_fn = cJSON_CreateObject();
cJSON* t35_params = cJSON_CreateObject();
cJSON* t35_props = cJSON_CreateObject();
cJSON_AddStringToObject(t35, "type", "function");
cJSON_AddStringToObject(t35_fn, "name", "my_pubkey");
cJSON_AddStringToObject(t35_fn, "description", "Alias for nostr_pubkey: return this agent's pubkey in hex format");
cJSON_AddStringToObject(t35_params, "type", "object");
cJSON_AddItemToObject(t35_params, "properties", t35_props);
cJSON_AddItemToObject(t35_fn, "parameters", t35_params);
cJSON_AddItemToObject(t35, "function", t35_fn);
cJSON_AddItemToArray(tools, t35);
cJSON* t36 = cJSON_CreateObject();
cJSON* t36_fn = cJSON_CreateObject();
cJSON* t36_params = cJSON_CreateObject();
cJSON* t36_props = cJSON_CreateObject();
cJSON_AddStringToObject(t36, "type", "function");
cJSON_AddStringToObject(t36_fn, "name", "my_npub");
cJSON_AddStringToObject(t36_fn, "description", "Alias for nostr_npub: return this agent's pubkey encoded as npub bech32");
cJSON_AddStringToObject(t36_params, "type", "object");
cJSON_AddItemToObject(t36_params, "properties", t36_props);
cJSON_AddItemToObject(t36_fn, "parameters", t36_params);
cJSON_AddItemToObject(t36, "function", t36_fn);
cJSON_AddItemToArray(tools, t36);
cJSON* t37 = cJSON_CreateObject();
cJSON* t37_fn = cJSON_CreateObject();
cJSON* t37_params = cJSON_CreateObject();
cJSON* t37_props = cJSON_CreateObject();
cJSON* t37_required = cJSON_CreateArray();
cJSON_AddStringToObject(t37, "type", "function");
cJSON_AddStringToObject(t37_fn, "name", "task_manage");
cJSON_AddStringToObject(t37_fn, "description", "Manage agent short-term task memory stored in tasks.json (list/add/update/remove/clear/replace)");
cJSON_AddStringToObject(t37_params, "type", "object");
cJSON_AddItemToObject(t37_params, "properties", t37_props);
cJSON_AddItemToObject(t37_params, "required", t37_required);
cJSON* p_task_action = cJSON_CreateObject();
cJSON_AddStringToObject(p_task_action, "type", "string");
cJSON* p_task_action_enum = cJSON_CreateArray();
cJSON_AddItemToArray(p_task_action_enum, cJSON_CreateString("list"));
cJSON_AddItemToArray(p_task_action_enum, cJSON_CreateString("add"));
cJSON_AddItemToArray(p_task_action_enum, cJSON_CreateString("update"));
cJSON_AddItemToArray(p_task_action_enum, cJSON_CreateString("remove"));
cJSON_AddItemToArray(p_task_action_enum, cJSON_CreateString("clear"));
cJSON_AddItemToArray(p_task_action_enum, cJSON_CreateString("replace"));
cJSON_AddItemToObject(p_task_action, "enum", p_task_action_enum);
cJSON_AddItemToObject(t37_props, "action", p_task_action);
cJSON* p_task_text = cJSON_CreateObject();
cJSON_AddStringToObject(p_task_text, "type", "string");
cJSON_AddItemToObject(t37_props, "text", p_task_text);
cJSON* p_task_id = cJSON_CreateObject();
cJSON_AddStringToObject(p_task_id, "type", "integer");
cJSON_AddItemToObject(t37_props, "id", p_task_id);
cJSON* p_task_status = cJSON_CreateObject();
cJSON_AddStringToObject(p_task_status, "type", "string");
cJSON* p_task_status_enum = cJSON_CreateArray();
cJSON_AddItemToArray(p_task_status_enum, cJSON_CreateString("pending"));
cJSON_AddItemToArray(p_task_status_enum, cJSON_CreateString("active"));
cJSON_AddItemToArray(p_task_status_enum, cJSON_CreateString("done"));
cJSON_AddItemToObject(p_task_status, "enum", p_task_status_enum);
cJSON_AddItemToObject(t37_props, "status", p_task_status);
cJSON* p_task_tasks = cJSON_CreateObject();
cJSON_AddStringToObject(p_task_tasks, "type", "array");
cJSON* p_task_tasks_item = cJSON_CreateObject();
cJSON_AddStringToObject(p_task_tasks_item, "type", "string");
cJSON_AddItemToObject(p_task_tasks, "items", p_task_tasks_item);
cJSON_AddItemToObject(t37_props, "tasks", p_task_tasks);
cJSON_AddItemToArray(t37_required, cJSON_CreateString("action"));
cJSON_AddItemToObject(t37_fn, "parameters", t37_params);
cJSON_AddItemToObject(t37, "function", t37_fn);
cJSON_AddItemToArray(tools, t37);
char* out = cJSON_PrintUnformatted(tools);
cJSON_Delete(tools);
return out;
}
static char* execute_nostr_post(const char* args_json) {
const char* raw_args_json = args_json ? args_json : "{}";
raw_args_json = skip_ws(raw_args_json);
if (!raw_args_json || raw_args_json[0] == '\0') {
raw_args_json = "{}";
}
cJSON* args = cJSON_Parse(raw_args_json);
char* repaired_args_json = NULL;
if (args && cJSON_IsString(args) && args->valuestring) {
cJSON* nested = cJSON_Parse(args->valuestring);
if (nested) {
cJSON_Delete(args);
args = nested;
}
}
if (!args && raw_args_json) {
repaired_args_json = sanitize_json_string_controls(raw_args_json);
if (repaired_args_json) {
args = cJSON_Parse(repaired_args_json);
if (args && cJSON_IsString(args) && args->valuestring) {
cJSON* nested = cJSON_Parse(args->valuestring);
if (nested) {
cJSON_Delete(args);
args = nested;
}
}
}
}
if (!args && raw_args_json) {
args = parse_loose_nostr_post_args(raw_args_json);
}
if (!args) {
free(repaired_args_json);
return json_error("invalid arguments JSON");
}
cJSON* kind = cJSON_GetObjectItemCaseSensitive(args, "kind");
cJSON* content = cJSON_GetObjectItemCaseSensitive(args, "content");
cJSON* tags = cJSON_GetObjectItemCaseSensitive(args, "tags");
if (!kind || !cJSON_IsNumber(kind) || !content || !cJSON_IsString(content) || !content->valuestring) {
cJSON_Delete(args);
return json_error("nostr_post requires integer kind and string content");
}
if (tags && !cJSON_IsArray(tags)) {
cJSON_Delete(args);
return json_error("nostr_post tags must be an array when provided");
}
cJSON* tags_dup = NULL;
if (tags) {
tags_dup = cJSON_Duplicate(tags, 1);
if (!tags_dup) {
cJSON_Delete(args);
return json_error("nostr_post failed to duplicate tags");
}
}
ensure_nip23_metadata_tags((int)kind->valuedouble, content->valuestring, &tags_dup);
nostr_publish_result_t publish_result;
memset(&publish_result, 0, sizeof(publish_result));
int rc = nostr_handler_publish_kind_event((int)kind->valuedouble,
content->valuestring,
tags_dup,
&publish_result);
cJSON_Delete(tags_dup);
cJSON_Delete(args);
free(repaired_args_json);
if (rc != 0) {
nostr_handler_publish_result_free(&publish_result);
return json_error("nostr_post failed");
}
cJSON* out = cJSON_CreateObject();
if (!out) {
nostr_handler_publish_result_free(&publish_result);
return NULL;
}
cJSON_AddBoolToObject(out, "success", publish_result.success ? 1 : 0);
cJSON_AddStringToObject(out, "message", "nostr_post published");
cJSON_AddNumberToObject(out, "kind", publish_result.kind);
cJSON_AddStringToObject(out, "event_id", publish_result.event_id);
cJSON_AddNumberToObject(out, "relay_count", publish_result.relay_count);
cJSON_AddNumberToObject(out, "accepted_by_pool_count", publish_result.accepted_by_pool_count);
if (publish_result.note_uri[0] != '\0') {
cJSON_AddStringToObject(out, "note_uri", publish_result.note_uri);
}
if (publish_result.naddr_uri[0] != '\0') {
cJSON_AddStringToObject(out, "naddr_uri", publish_result.naddr_uri);
}
if (publish_result.d_tag[0] != '\0') {
cJSON_AddStringToObject(out, "d_tag", publish_result.d_tag);
}
cJSON* relays = cJSON_CreateArray();
if (!relays) {
nostr_handler_publish_result_free(&publish_result);
cJSON_Delete(out);
return NULL;
}
for (int i = 0; i < publish_result.relay_count; i++) {
cJSON_AddItemToArray(relays, cJSON_CreateString(publish_result.relays[i] ? publish_result.relays[i] : ""));
}
cJSON_AddItemToObject(out, "relays", relays);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
nostr_handler_publish_result_free(&publish_result);
return json;
}
static char* read_entire_file(const char* file_path, size_t* out_bytes) {
if (!file_path) return NULL;
FILE* fp = fopen(file_path, "rb");
if (!fp) return NULL;
if (fseek(fp, 0, SEEK_END) != 0) {
fclose(fp);
return NULL;
}
long size = ftell(fp);
if (size < 0) {
fclose(fp);
return NULL;
}
if (fseek(fp, 0, SEEK_SET) != 0) {
fclose(fp);
return NULL;
}
char* buf = (char*)malloc((size_t)size + 1U);
if (!buf) {
fclose(fp);
return NULL;
}
size_t n = fread(buf, 1, (size_t)size, fp);
fclose(fp);
buf[n] = '\0';
if (out_bytes) *out_bytes = n;
return buf;
}
static char* basename_lowercase_dup(const char* path) {
if (!path || path[0] == '\0') return NULL;
const char* base = strrchr(path, '/');
base = base ? (base + 1) : path;
if (base[0] == '\0') return NULL;
char* out = strdup(base);
if (!out) return NULL;
for (size_t i = 0; out[i] != '\0'; i++) {
out[i] = (char)tolower((unsigned char)out[i]);
}
return out;
}
static char* execute_nostr_post_readme(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
const char* raw_args_json = args_json ? args_json : "{}";
raw_args_json = skip_ws(raw_args_json);
if (!raw_args_json || raw_args_json[0] == '\0') {
raw_args_json = "{}";
}
cJSON* args = cJSON_Parse(raw_args_json);
char* repaired_args_json = NULL;
if (args && cJSON_IsString(args) && args->valuestring) {
cJSON* nested = cJSON_Parse(args->valuestring);
if (nested) {
cJSON_Delete(args);
args = nested;
}
}
if (!args) {
repaired_args_json = sanitize_json_string_controls(raw_args_json);
if (repaired_args_json) {
args = cJSON_Parse(repaired_args_json);
if (args && cJSON_IsString(args) && args->valuestring) {
cJSON* nested = cJSON_Parse(args->valuestring);
if (nested) {
cJSON_Delete(args);
args = nested;
}
}
}
}
if (!args) {
free(repaired_args_json);
return json_error("invalid arguments JSON");
}
cJSON_Delete(args);
free(repaired_args_json);
char readme_path[PATH_MAX];
if (build_tool_path(ctx, "README.md", readme_path, sizeof(readme_path)) != 0) {
return json_error("failed to resolve README path");
}
size_t bytes_read = 0;
char* content = read_entire_file(readme_path, &bytes_read);
if (!content) {
return json_error("failed to read README.md");
}
cJSON* tags = cJSON_CreateArray();
if (!tags) {
free(content);
return json_error("failed to create tags");
}
if (add_string_tag(tags, "d", "readme.md") != 0) {
cJSON_Delete(tags);
free(content);
return json_error("failed to set d tag");
}
if (add_string_tag(tags,
"image",
"https://laantungir.github.io/img_repo/d21c4060632ab3d9d37a6062eeecbdbfbd67f03cb20dbd64838b1ba0d9cd8922.jpg") != 0) {
cJSON_Delete(tags);
free(content);
return json_error("failed to set image tag");
}
ensure_nip23_metadata_tags(30023, content, &tags);
nostr_publish_result_t publish_result;
memset(&publish_result, 0, sizeof(publish_result));
int rc = nostr_handler_publish_kind_event(30023, content, tags, &publish_result);
cJSON_Delete(tags);
free(content);
if (rc != 0) {
nostr_handler_publish_result_free(&publish_result);
return json_error("nostr_post_readme failed");
}
cJSON* out = cJSON_CreateObject();
if (!out) {
nostr_handler_publish_result_free(&publish_result);
return NULL;
}
cJSON_AddBoolToObject(out, "success", publish_result.success ? 1 : 0);
cJSON_AddStringToObject(out, "message", "nostr_post_readme published");
cJSON_AddStringToObject(out, "path", readme_path);
cJSON_AddNumberToObject(out, "bytes_read", (double)bytes_read);
cJSON_AddNumberToObject(out, "kind", publish_result.kind);
cJSON_AddStringToObject(out, "event_id", publish_result.event_id);
cJSON_AddNumberToObject(out, "relay_count", publish_result.relay_count);
cJSON_AddNumberToObject(out, "accepted_by_pool_count", publish_result.accepted_by_pool_count);
if (publish_result.note_uri[0] != '\0') {
cJSON_AddStringToObject(out, "note_uri", publish_result.note_uri);
}
if (publish_result.naddr_uri[0] != '\0') {
cJSON_AddStringToObject(out, "naddr_uri", publish_result.naddr_uri);
}
if (publish_result.d_tag[0] != '\0') {
cJSON_AddStringToObject(out, "d_tag", publish_result.d_tag);
}
cJSON* relays = cJSON_CreateArray();
if (!relays) {
nostr_handler_publish_result_free(&publish_result);
cJSON_Delete(out);
return NULL;
}
for (int i = 0; i < publish_result.relay_count; i++) {
cJSON_AddItemToArray(relays, cJSON_CreateString(publish_result.relays[i] ? publish_result.relays[i] : ""));
}
cJSON_AddItemToObject(out, "relays", relays);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
nostr_handler_publish_result_free(&publish_result);
return json;
}
static char* execute_nostr_file_md_to_longform_post(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* file = cJSON_GetObjectItemCaseSensitive(args, "file");
cJSON* title = cJSON_GetObjectItemCaseSensitive(args, "title");
cJSON* image = cJSON_GetObjectItemCaseSensitive(args, "image");
cJSON* summary = cJSON_GetObjectItemCaseSensitive(args, "summary");
if (!file || !cJSON_IsString(file) || !file->valuestring || file->valuestring[0] == '\0') {
cJSON_Delete(args);
return json_error("nostr_file_md_to_longform_post requires string file");
}
if (title && !cJSON_IsString(title)) {
cJSON_Delete(args);
return json_error("nostr_file_md_to_longform_post title must be string when provided");
}
if (image && !cJSON_IsString(image)) {
cJSON_Delete(args);
return json_error("nostr_file_md_to_longform_post image must be string when provided");
}
if (summary && !cJSON_IsString(summary)) {
cJSON_Delete(args);
return json_error("nostr_file_md_to_longform_post summary must be string when provided");
}
char file_path[PATH_MAX];
if (build_tool_path(ctx, file->valuestring, file_path, sizeof(file_path)) != 0) {
cJSON_Delete(args);
return json_error("failed to resolve markdown file path");
}
size_t bytes_read = 0;
char* content = read_entire_file(file_path, &bytes_read);
if (!content) {
cJSON_Delete(args);
return json_error("failed to read markdown file");
}
char* d_tag = basename_lowercase_dup(file->valuestring);
if (!d_tag || d_tag[0] == '\0') {
free(d_tag);
free(content);
cJSON_Delete(args);
return json_error("failed to derive d tag from filename");
}
cJSON* tags = cJSON_CreateArray();
if (!tags) {
free(d_tag);
free(content);
cJSON_Delete(args);
return json_error("failed to create tags");
}
if (add_string_tag(tags, "d", d_tag) != 0) {
free(d_tag);
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
return json_error("failed to set d tag");
}
if (title && title->valuestring && title->valuestring[0] != '\0') {
if (add_string_tag(tags, "title", title->valuestring) != 0) {
free(d_tag);
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
return json_error("failed to set title tag");
}
}
if (image && image->valuestring && image->valuestring[0] != '\0') {
if (add_string_tag(tags, "image", image->valuestring) != 0) {
free(d_tag);
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
return json_error("failed to set image tag");
}
}
if (summary && summary->valuestring && summary->valuestring[0] != '\0') {
if (add_string_tag(tags, "summary", summary->valuestring) != 0) {
free(d_tag);
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
return json_error("failed to set summary tag");
}
}
ensure_nip23_metadata_tags(30023, content, &tags);
nostr_publish_result_t publish_result;
memset(&publish_result, 0, sizeof(publish_result));
int rc = nostr_handler_publish_kind_event(30023, content, tags, &publish_result);
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
if (rc != 0) {
free(d_tag);
nostr_handler_publish_result_free(&publish_result);
return json_error("nostr_file_md_to_longform_post failed");
}
cJSON* out = cJSON_CreateObject();
if (!out) {
free(d_tag);
nostr_handler_publish_result_free(&publish_result);
return NULL;
}
cJSON_AddBoolToObject(out, "success", publish_result.success ? 1 : 0);
cJSON_AddStringToObject(out, "message", "nostr_file_md_to_longform_post published");
cJSON_AddStringToObject(out, "path", file_path);
cJSON_AddStringToObject(out, "d_tag", d_tag);
cJSON_AddNumberToObject(out, "bytes_read", (double)bytes_read);
cJSON_AddNumberToObject(out, "kind", publish_result.kind);
cJSON_AddStringToObject(out, "event_id", publish_result.event_id);
cJSON_AddNumberToObject(out, "relay_count", publish_result.relay_count);
cJSON_AddNumberToObject(out, "accepted_by_pool_count", publish_result.accepted_by_pool_count);
if (publish_result.note_uri[0] != '\0') {
cJSON_AddStringToObject(out, "note_uri", publish_result.note_uri);
}
if (publish_result.naddr_uri[0] != '\0') {
cJSON_AddStringToObject(out, "naddr_uri", publish_result.naddr_uri);
}
cJSON* relays = cJSON_CreateArray();
if (!relays) {
free(d_tag);
nostr_handler_publish_result_free(&publish_result);
cJSON_Delete(out);
return NULL;
}
for (int i = 0; i < publish_result.relay_count; i++) {
cJSON_AddItemToArray(relays, cJSON_CreateString(publish_result.relays[i] ? publish_result.relays[i] : ""));
}
cJSON_AddItemToObject(out, "relays", relays);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
free(d_tag);
nostr_handler_publish_result_free(&publish_result);
return json;
}
static char* execute_nostr_delete(const char* args_json) {
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* event_ids = cJSON_GetObjectItemCaseSensitive(args, "event_ids");
cJSON* kinds = cJSON_GetObjectItemCaseSensitive(args, "kinds");
cJSON* reason = cJSON_GetObjectItemCaseSensitive(args, "reason");
if (!event_ids || !cJSON_IsArray(event_ids) || cJSON_GetArraySize(event_ids) <= 0) {
cJSON_Delete(args);
return json_error("nostr_delete requires non-empty event_ids array");
}
if (kinds && !cJSON_IsArray(kinds)) {
cJSON_Delete(args);
return json_error("nostr_delete kinds must be an array when provided");
}
if (reason && !cJSON_IsString(reason)) {
cJSON_Delete(args);
return json_error("nostr_delete reason must be a string when provided");
}
cJSON* tags = cJSON_CreateArray();
if (!tags) {
cJSON_Delete(args);
return json_error("failed to create tags");
}
int event_id_count = cJSON_GetArraySize(event_ids);
for (int i = 0; i < event_id_count; i++) {
cJSON* id = cJSON_GetArrayItem(event_ids, i);
if (!id || !cJSON_IsString(id) || !id->valuestring || !is_hex_string_len(id->valuestring, 64U)) {
cJSON_Delete(tags);
cJSON_Delete(args);
return json_error("nostr_delete event_ids must contain 64-char hex strings");
}
if (add_string_tag(tags, "e", id->valuestring) != 0) {
cJSON_Delete(tags);
cJSON_Delete(args);
return json_error("failed to add e tag");
}
}
if (kinds) {
int kind_count = cJSON_GetArraySize(kinds);
for (int i = 0; i < kind_count; i++) {
cJSON* k = cJSON_GetArrayItem(kinds, i);
if (!k || !cJSON_IsNumber(k)) {
cJSON_Delete(tags);
cJSON_Delete(args);
return json_error("nostr_delete kinds must contain integers");
}
char kind_buf[32];
snprintf(kind_buf, sizeof(kind_buf), "%d", (int)k->valuedouble);
if (add_string_tag(tags, "k", kind_buf) != 0) {
cJSON_Delete(tags);
cJSON_Delete(args);
return json_error("failed to add k tag");
}
}
}
nostr_publish_result_t publish_result;
memset(&publish_result, 0, sizeof(publish_result));
const char* reason_content = (reason && reason->valuestring) ? reason->valuestring : "";
int rc = nostr_handler_publish_kind_event(5, reason_content, tags, &publish_result);
cJSON_Delete(tags);
cJSON_Delete(args);
if (rc != 0) {
nostr_handler_publish_result_free(&publish_result);
return json_error("nostr_delete failed");
}
cJSON* out = cJSON_CreateObject();
if (!out) {
nostr_handler_publish_result_free(&publish_result);
return NULL;
}
cJSON_AddBoolToObject(out, "success", publish_result.success ? 1 : 0);
cJSON_AddStringToObject(out, "message", "nostr_delete published");
cJSON_AddNumberToObject(out, "kind", publish_result.kind);
cJSON_AddStringToObject(out, "event_id", publish_result.event_id);
cJSON_AddNumberToObject(out, "requested_event_count", event_id_count);
cJSON_AddNumberToObject(out, "relay_count", publish_result.relay_count);
cJSON_AddNumberToObject(out, "accepted_by_pool_count", publish_result.accepted_by_pool_count);
cJSON* relays = cJSON_CreateArray();
if (!relays) {
nostr_handler_publish_result_free(&publish_result);
cJSON_Delete(out);
return NULL;
}
for (int i = 0; i < publish_result.relay_count; i++) {
cJSON_AddItemToArray(relays, cJSON_CreateString(publish_result.relays[i] ? publish_result.relays[i] : ""));
}
cJSON_AddItemToObject(out, "relays", relays);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
nostr_handler_publish_result_free(&publish_result);
return json;
}
static char* execute_nostr_react(const char* args_json) {
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* event_id = cJSON_GetObjectItemCaseSensitive(args, "event_id");
cJSON* event_pubkey = cJSON_GetObjectItemCaseSensitive(args, "event_pubkey");
cJSON* event_kind = cJSON_GetObjectItemCaseSensitive(args, "event_kind");
cJSON* reaction = cJSON_GetObjectItemCaseSensitive(args, "reaction");
if (!event_id || !cJSON_IsString(event_id) || !event_id->valuestring || !is_hex_string_len(event_id->valuestring, 64U) ||
!event_pubkey || !cJSON_IsString(event_pubkey) || !event_pubkey->valuestring || !is_hex_string_len(event_pubkey->valuestring, 64U)) {
cJSON_Delete(args);
return json_error("nostr_react requires event_id and event_pubkey as 64-char hex strings");
}
if (event_kind && !cJSON_IsNumber(event_kind)) {
cJSON_Delete(args);
return json_error("nostr_react event_kind must be an integer when provided");
}
if (reaction && !cJSON_IsString(reaction)) {
cJSON_Delete(args);
return json_error("nostr_react reaction must be a string when provided");
}
cJSON* tags = cJSON_CreateArray();
if (!tags) {
cJSON_Delete(args);
return json_error("failed to create tags");
}
if (add_string_tag(tags, "e", event_id->valuestring) != 0 ||
add_string_tag(tags, "p", event_pubkey->valuestring) != 0) {
cJSON_Delete(tags);
cJSON_Delete(args);
return json_error("failed to add required reaction tags");
}
if (event_kind) {
char kind_buf[32];
snprintf(kind_buf, sizeof(kind_buf), "%d", (int)event_kind->valuedouble);
if (add_string_tag(tags, "k", kind_buf) != 0) {
cJSON_Delete(tags);
cJSON_Delete(args);
return json_error("failed to add k tag");
}
}
const char* reaction_content = (reaction && reaction->valuestring && reaction->valuestring[0] != '\0') ? reaction->valuestring : "+";
nostr_publish_result_t publish_result;
memset(&publish_result, 0, sizeof(publish_result));
int rc = nostr_handler_publish_kind_event(7, reaction_content, tags, &publish_result);
cJSON_Delete(tags);
cJSON_Delete(args);
if (rc != 0) {
nostr_handler_publish_result_free(&publish_result);
return json_error("nostr_react failed");
}
cJSON* out = cJSON_CreateObject();
if (!out) {
nostr_handler_publish_result_free(&publish_result);
return NULL;
}
cJSON_AddBoolToObject(out, "success", publish_result.success ? 1 : 0);
cJSON_AddStringToObject(out, "message", "nostr_react published");
cJSON_AddStringToObject(out, "reaction", reaction_content);
cJSON_AddNumberToObject(out, "kind", publish_result.kind);
cJSON_AddStringToObject(out, "event_id", publish_result.event_id);
cJSON_AddNumberToObject(out, "relay_count", publish_result.relay_count);
cJSON_AddNumberToObject(out, "accepted_by_pool_count", publish_result.accepted_by_pool_count);
cJSON* relays = cJSON_CreateArray();
if (!relays) {
nostr_handler_publish_result_free(&publish_result);
cJSON_Delete(out);
return NULL;
}
for (int i = 0; i < publish_result.relay_count; i++) {
cJSON_AddItemToArray(relays, cJSON_CreateString(publish_result.relays[i] ? publish_result.relays[i] : ""));
}
cJSON_AddItemToObject(out, "relays", relays);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
nostr_handler_publish_result_free(&publish_result);
return json;
}
static char* execute_nostr_profile_get(const char* args_json) {
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(args, "pubkey");
if (!pubkey || !cJSON_IsString(pubkey) || !pubkey->valuestring || !is_hex_string_len(pubkey->valuestring, 64U)) {
cJSON_Delete(args);
return json_error("nostr_profile_get requires pubkey as 64-char hex string");
}
cJSON* filter = cJSON_CreateObject();
cJSON* kinds = cJSON_CreateArray();
cJSON* authors = cJSON_CreateArray();
if (!filter || !kinds || !authors) {
cJSON_Delete(filter);
cJSON_Delete(kinds);
cJSON_Delete(authors);
cJSON_Delete(args);
return json_error("failed to create profile query filter");
}
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(0));
cJSON_AddItemToObject(filter, "kinds", kinds);
cJSON_AddItemToArray(authors, cJSON_CreateString(pubkey->valuestring));
cJSON_AddItemToObject(filter, "authors", authors);
cJSON_AddNumberToObject(filter, "limit", 1);
char* events_json = nostr_handler_query_json(filter, 8000);
cJSON_Delete(filter);
cJSON_Delete(args);
if (!events_json) {
return json_error("nostr_profile_get query failed");
}
cJSON* events = cJSON_Parse(events_json);
free(events_json);
if (!events || !cJSON_IsArray(events)) {
cJSON_Delete(events);
return json_error("nostr_profile_get returned invalid events JSON");
}
cJSON* out = cJSON_CreateObject();
if (!out) {
cJSON_Delete(events);
return NULL;
}
cJSON_AddBoolToObject(out, "success", 1);
int found = cJSON_GetArraySize(events) > 0;
cJSON_AddBoolToObject(out, "found", found ? 1 : 0);
if (found) {
cJSON* ev = cJSON_GetArrayItem(events, 0);
cJSON* ev_dup = ev ? cJSON_Duplicate(ev, 1) : NULL;
if (ev_dup) {
cJSON_AddItemToObject(out, "event", ev_dup);
}
cJSON* content = ev ? cJSON_GetObjectItemCaseSensitive(ev, "content") : NULL;
if (content && cJSON_IsString(content) && content->valuestring) {
cJSON* profile = cJSON_Parse(content->valuestring);
if (profile && cJSON_IsObject(profile)) {
cJSON_AddItemToObject(out, "profile", profile);
} else {
cJSON_Delete(profile);
cJSON_AddStringToObject(out, "profile_raw", content->valuestring);
}
}
}
cJSON_Delete(events);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_nostr_relay_status(const char* args_json) {
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON_Delete(args);
char* status_json = nostr_handler_relay_status_json();
if (!status_json) {
return json_error("nostr_relay_status failed");
}
cJSON* status = cJSON_Parse(status_json);
free(status_json);
if (!status) {
return json_error("nostr_relay_status returned invalid JSON");
}
cJSON* out = cJSON_CreateObject();
if (!out) {
cJSON_Delete(status);
return NULL;
}
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddItemToObject(out, "status", status);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_nostr_nip05_lookup(const char* args_json) {
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* identifier = cJSON_GetObjectItemCaseSensitive(args, "identifier");
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(args, "pubkey");
if (!identifier || !cJSON_IsString(identifier) || !identifier->valuestring || strchr(identifier->valuestring, '@') == NULL) {
cJSON_Delete(args);
return json_error("nostr_nip05_lookup requires identifier in user@domain format");
}
if (pubkey && (!cJSON_IsString(pubkey) || !pubkey->valuestring || !is_hex_string_len(pubkey->valuestring, 64U))) {
cJSON_Delete(args);
return json_error("nostr_nip05_lookup pubkey must be a 64-char hex string when provided");
}
char found_pubkey[65] = {0};
char** relays = NULL;
int relay_count = 0;
int rc = 0;
if (pubkey) {
rc = nostr_nip05_verify(identifier->valuestring,
pubkey->valuestring,
&relays,
&relay_count,
10);
} else {
rc = nostr_nip05_lookup(identifier->valuestring,
found_pubkey,
&relays,
&relay_count,
10);
}
cJSON* out = cJSON_CreateObject();
if (!out) {
free_string_array_heap(relays, relay_count);
cJSON_Delete(args);
return NULL;
}
cJSON_AddBoolToObject(out, "success", rc == NOSTR_SUCCESS ? 1 : 0);
cJSON_AddStringToObject(out, "identifier", identifier->valuestring);
if (pubkey) {
cJSON_AddBoolToObject(out, "verified", rc == NOSTR_SUCCESS ? 1 : 0);
cJSON_AddStringToObject(out, "pubkey", pubkey->valuestring);
} else if (rc == NOSTR_SUCCESS) {
cJSON_AddStringToObject(out, "pubkey", found_pubkey);
}
cJSON* relay_arr = cJSON_CreateArray();
if (!relay_arr) {
cJSON_Delete(out);
free_string_array_heap(relays, relay_count);
cJSON_Delete(args);
return NULL;
}
for (int i = 0; i < relay_count; i++) {
cJSON_AddItemToArray(relay_arr, cJSON_CreateString(relays[i] ? relays[i] : ""));
}
cJSON_AddItemToObject(out, "relays", relay_arr);
if (rc != NOSTR_SUCCESS) {
cJSON_AddNumberToObject(out, "error_code", rc);
}
free_string_array_heap(relays, relay_count);
cJSON_Delete(args);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_nostr_encode(const char* args_json) {
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* type = cJSON_GetObjectItemCaseSensitive(args, "type");
cJSON* hex = cJSON_GetObjectItemCaseSensitive(args, "hex");
if (!type || !cJSON_IsString(type) || !type->valuestring ||
!hex || !cJSON_IsString(hex) || !hex->valuestring || !is_hex_string_len(hex->valuestring, 64U)) {
cJSON_Delete(args);
return json_error("nostr_encode requires type string and hex 64-char string");
}
char type_name[32];
snprintf(type_name, sizeof(type_name), "%s", type->valuestring);
unsigned char key_bytes[32];
if (nostr_hex_to_bytes(hex->valuestring, key_bytes, sizeof(key_bytes)) != 0) {
cJSON_Delete(args);
return json_error("nostr_encode invalid hex");
}
char bech32[256] = {0};
int rc = NOSTR_ERROR_INVALID_INPUT;
if (strcmp(type_name, "npub") == 0) {
rc = nostr_key_to_bech32(key_bytes, "npub", bech32);
} else if (strcmp(type_name, "nsec") == 0) {
rc = nostr_key_to_bech32(key_bytes, "nsec", bech32);
} else if (strcmp(type_name, "note") == 0) {
rc = nostr_key_to_bech32(key_bytes, "note", bech32);
} else {
cJSON_Delete(args);
return json_error("nostr_encode currently supports npub, nsec, note");
}
cJSON_Delete(args);
if (rc != NOSTR_SUCCESS) {
return json_error("nostr_encode failed");
}
char uri[320] = {0};
snprintf(uri, sizeof(uri), "nostr:%s", bech32);
cJSON* out = cJSON_CreateObject();
if (!out) return NULL;
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "type", type_name);
cJSON_AddStringToObject(out, "uri", uri);
cJSON_AddBoolToObject(out, "limited_support", 1);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_nostr_decode(const char* args_json) {
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* uri_in = cJSON_GetObjectItemCaseSensitive(args, "uri");
if (!uri_in || !cJSON_IsString(uri_in) || !uri_in->valuestring || uri_in->valuestring[0] == '\0') {
cJSON_Delete(args);
return json_error("nostr_decode requires uri string");
}
const char* in = uri_in->valuestring;
if (strncmp(in, "nostr:", 6) == 0) {
in += 6;
}
unsigned char key[32];
char key_hex[65] = {0};
const char* type = NULL;
int rc = NOSTR_ERROR_INVALID_INPUT;
if (strncmp(in, "npub1", 5) == 0) {
type = "npub";
rc = nostr_decode_npub(in, key);
} else if (strncmp(in, "nsec1", 5) == 0) {
type = "nsec";
rc = nostr_decode_nsec(in, key);
} else {
cJSON_Delete(args);
return json_error("nostr_decode currently supports npub and nsec");
}
cJSON_Delete(args);
if (rc != NOSTR_SUCCESS) {
return json_error("nostr_decode failed");
}
nostr_bytes_to_hex(key, 32, key_hex);
cJSON* out = cJSON_CreateObject();
if (!out) return NULL;
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "type", type);
if (strcmp(type, "npub") == 0) {
cJSON_AddStringToObject(out, "pubkey", key_hex);
} else {
cJSON_AddStringToObject(out, "private_key", key_hex);
}
cJSON_AddBoolToObject(out, "limited_support", 1);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_nostr_dm_send(const char* args_json) {
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* recipient = cJSON_GetObjectItemCaseSensitive(args, "recipient_pubkey");
cJSON* message = cJSON_GetObjectItemCaseSensitive(args, "message");
if (!recipient || !cJSON_IsString(recipient) || !recipient->valuestring || !is_hex_string_len(recipient->valuestring, 64U) ||
!message || !cJSON_IsString(message) || !message->valuestring || message->valuestring[0] == '\0') {
cJSON_Delete(args);
return json_error("nostr_dm_send requires recipient_pubkey hex and non-empty message");
}
int rc = nostr_handler_send_dm(recipient->valuestring, message->valuestring);
cJSON* out = cJSON_CreateObject();
if (!out) {
cJSON_Delete(args);
return NULL;
}
cJSON_AddBoolToObject(out, "success", rc == 0 ? 1 : 0);
cJSON_AddStringToObject(out, "recipient_pubkey", recipient->valuestring);
cJSON_AddNumberToObject(out, "message_length", (double)strlen(message->valuestring));
cJSON_Delete(args);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_nostr_relay_info(const char* args_json) {
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* relay_url = cJSON_GetObjectItemCaseSensitive(args, "relay_url");
if (!relay_url || !cJSON_IsString(relay_url) || !relay_url->valuestring || relay_url->valuestring[0] == '\0') {
cJSON_Delete(args);
return json_error("nostr_relay_info requires relay_url");
}
char* info_json = nostr_handler_relay_info_json(relay_url->valuestring);
cJSON_Delete(args);
if (!info_json) {
return json_error("nostr_relay_info failed");
}
cJSON* info = cJSON_Parse(info_json);
free(info_json);
if (!info || !cJSON_IsObject(info)) {
cJSON_Delete(info);
return json_error("nostr_relay_info returned invalid JSON");
}
cJSON* out = cJSON_CreateObject();
if (!out) {
cJSON_Delete(info);
return NULL;
}
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddItemToObject(out, "info", info);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_nostr_encrypt(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* recipient = cJSON_GetObjectItemCaseSensitive(args, "recipient_pubkey");
cJSON* plaintext = cJSON_GetObjectItemCaseSensitive(args, "plaintext");
if (!recipient || !cJSON_IsString(recipient) || !recipient->valuestring || !is_hex_string_len(recipient->valuestring, 64U) ||
!plaintext || !cJSON_IsString(plaintext) || !plaintext->valuestring || plaintext->valuestring[0] == '\0') {
cJSON_Delete(args);
return json_error("nostr_encrypt requires recipient_pubkey hex and plaintext");
}
unsigned char recipient_pubkey[32];
if (nostr_hex_to_bytes(recipient->valuestring, recipient_pubkey, sizeof(recipient_pubkey)) != 0) {
cJSON_Delete(args);
return json_error("nostr_encrypt invalid recipient_pubkey");
}
size_t out_cap = (strlen(plaintext->valuestring) * 4U) + 1024U;
char* ciphertext = (char*)malloc(out_cap);
if (!ciphertext) {
cJSON_Delete(args);
return json_error("allocation failure");
}
int rc = nostr_nip44_encrypt(ctx->cfg->keys.private_key,
recipient_pubkey,
plaintext->valuestring,
ciphertext,
out_cap);
if (rc != NOSTR_SUCCESS) {
free(ciphertext);
cJSON_Delete(args);
return json_error("nostr_encrypt failed");
}
cJSON* out = cJSON_CreateObject();
if (!out) {
free(ciphertext);
cJSON_Delete(args);
return NULL;
}
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "recipient_pubkey", recipient->valuestring);
cJSON_AddStringToObject(out, "ciphertext", ciphertext);
free(ciphertext);
cJSON_Delete(args);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_nostr_decrypt(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* sender = cJSON_GetObjectItemCaseSensitive(args, "sender_pubkey");
cJSON* ciphertext = cJSON_GetObjectItemCaseSensitive(args, "ciphertext");
if (!sender || !cJSON_IsString(sender) || !sender->valuestring || !is_hex_string_len(sender->valuestring, 64U) ||
!ciphertext || !cJSON_IsString(ciphertext) || !ciphertext->valuestring || ciphertext->valuestring[0] == '\0') {
cJSON_Delete(args);
return json_error("nostr_decrypt requires sender_pubkey hex and ciphertext");
}
unsigned char sender_pubkey[32];
if (nostr_hex_to_bytes(sender->valuestring, sender_pubkey, sizeof(sender_pubkey)) != 0) {
cJSON_Delete(args);
return json_error("nostr_decrypt invalid sender_pubkey");
}
size_t out_cap = strlen(ciphertext->valuestring) + 1024U;
char* plaintext = (char*)malloc(out_cap);
if (!plaintext) {
cJSON_Delete(args);
return json_error("allocation failure");
}
int rc = nostr_nip44_decrypt(ctx->cfg->keys.private_key,
sender_pubkey,
ciphertext->valuestring,
plaintext,
out_cap);
if (rc != NOSTR_SUCCESS) {
free(plaintext);
cJSON_Delete(args);
return json_error("nostr_decrypt failed");
}
cJSON* out = cJSON_CreateObject();
if (!out) {
free(plaintext);
cJSON_Delete(args);
return NULL;
}
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "sender_pubkey", sender->valuestring);
cJSON_AddStringToObject(out, "plaintext", plaintext);
free(plaintext);
cJSON_Delete(args);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_nostr_dm_send_nip17(const char* args_json) {
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* recipient = cJSON_GetObjectItemCaseSensitive(args, "recipient_pubkey");
cJSON* message = cJSON_GetObjectItemCaseSensitive(args, "message");
cJSON* subject = cJSON_GetObjectItemCaseSensitive(args, "subject");
if (!recipient || !cJSON_IsString(recipient) || !recipient->valuestring || !is_hex_string_len(recipient->valuestring, 64U) ||
!message || !cJSON_IsString(message) || !message->valuestring || message->valuestring[0] == '\0') {
cJSON_Delete(args);
return json_error("nostr_dm_send_nip17 requires recipient_pubkey hex and non-empty message");
}
if (subject && !cJSON_IsString(subject)) {
cJSON_Delete(args);
return json_error("nostr_dm_send_nip17 subject must be a string when provided");
}
int rc = nostr_handler_send_dm_nip17(recipient->valuestring,
message->valuestring,
(subject && subject->valuestring) ? subject->valuestring : NULL);
cJSON* out = cJSON_CreateObject();
if (!out) {
cJSON_Delete(args);
return NULL;
}
cJSON_AddBoolToObject(out, "success", rc == 0 ? 1 : 0);
cJSON_AddStringToObject(out, "recipient_pubkey", recipient->valuestring);
cJSON_AddStringToObject(out, "protocol", "nip17");
cJSON_AddNumberToObject(out, "message_length", (double)strlen(message->valuestring));
cJSON_Delete(args);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_nostr_list_manage(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* list_kind = cJSON_GetObjectItemCaseSensitive(args, "list_kind");
cJSON* action = cJSON_GetObjectItemCaseSensitive(args, "action");
cJSON* items = cJSON_GetObjectItemCaseSensitive(args, "items");
if (!list_kind || !cJSON_IsNumber(list_kind) ||
!action || !cJSON_IsString(action) || !action->valuestring ||
!items || !cJSON_IsArray(items)) {
cJSON_Delete(args);
return json_error("nostr_list_manage requires list_kind, action, and items");
}
if (strcmp(action->valuestring, "add") != 0 && strcmp(action->valuestring, "remove") != 0) {
cJSON_Delete(args);
return json_error("nostr_list_manage action must be add or remove");
}
cJSON* filter = cJSON_CreateObject();
cJSON* kinds = cJSON_CreateArray();
cJSON* authors = cJSON_CreateArray();
if (!filter || !kinds || !authors) {
cJSON_Delete(filter);
cJSON_Delete(kinds);
cJSON_Delete(authors);
cJSON_Delete(args);
return json_error("failed to create list query filter");
}
cJSON_AddItemToArray(kinds, cJSON_CreateNumber((int)list_kind->valuedouble));
cJSON_AddItemToObject(filter, "kinds", kinds);
cJSON_AddItemToArray(authors, cJSON_CreateString(ctx->cfg->keys.public_key_hex));
cJSON_AddItemToObject(filter, "authors", authors);
cJSON_AddNumberToObject(filter, "limit", 1);
char* events_json = nostr_handler_query_json(filter, 8000);
cJSON_Delete(filter);
cJSON* events = events_json ? cJSON_Parse(events_json) : NULL;
free(events_json);
char* list_content = strdup("");
if (!list_content) {
cJSON_Delete(events);
cJSON_Delete(args);
return json_error("allocation failure");
}
cJSON* updated_tags = cJSON_CreateArray();
if (!updated_tags) {
free(list_content);
cJSON_Delete(events);
cJSON_Delete(args);
return json_error("failed to create list tags");
}
if (events && cJSON_IsArray(events) && cJSON_GetArraySize(events) > 0) {
cJSON* ev0 = cJSON_GetArrayItem(events, 0);
if (ev0 && cJSON_IsObject(ev0)) {
cJSON* content = cJSON_GetObjectItemCaseSensitive(ev0, "content");
if (content && cJSON_IsString(content) && content->valuestring) {
free(list_content);
list_content = strdup(content->valuestring);
if (!list_content) {
cJSON_Delete(updated_tags);
cJSON_Delete(events);
cJSON_Delete(args);
return json_error("allocation failure");
}
}
cJSON* tags = cJSON_GetObjectItemCaseSensitive(ev0, "tags");
if (tags && cJSON_IsArray(tags)) {
cJSON_Delete(updated_tags);
updated_tags = cJSON_Duplicate(tags, 1);
if (!updated_tags) {
free(list_content);
cJSON_Delete(events);
cJSON_Delete(args);
return json_error("failed to duplicate existing list tags");
}
}
}
}
int items_affected = 0;
int item_count = cJSON_GetArraySize(items);
for (int i = 0; i < item_count; i++) {
cJSON* tuple = cJSON_GetArrayItem(items, i);
if (!tuple || !cJSON_IsArray(tuple) || cJSON_GetArraySize(tuple) <= 0) {
continue;
}
int tuple_ok = 1;
int tuple_size = cJSON_GetArraySize(tuple);
for (int j = 0; j < tuple_size; j++) {
cJSON* part = cJSON_GetArrayItem(tuple, j);
if (!part || !cJSON_IsString(part) || !part->valuestring) {
tuple_ok = 0;
break;
}
}
if (!tuple_ok) {
continue;
}
if (strcmp(action->valuestring, "add") == 0) {
if (!tags_contains_tuple(updated_tags, tuple)) {
cJSON* dup = cJSON_Duplicate(tuple, 1);
if (!dup) {
free(list_content);
cJSON_Delete(updated_tags);
cJSON_Delete(events);
cJSON_Delete(args);
return json_error("failed to duplicate list item");
}
cJSON_AddItemToArray(updated_tags, dup);
items_affected++;
}
} else {
items_affected += remove_matching_tag_tuples(updated_tags, tuple);
}
}
nostr_publish_result_t publish_result;
memset(&publish_result, 0, sizeof(publish_result));
int rc = nostr_handler_publish_kind_event((int)list_kind->valuedouble,
list_content,
updated_tags,
&publish_result);
free(list_content);
cJSON_Delete(updated_tags);
cJSON_Delete(events);
if (rc != 0) {
cJSON_Delete(args);
nostr_handler_publish_result_free(&publish_result);
return json_error("nostr_list_manage failed");
}
cJSON* out = cJSON_CreateObject();
if (!out) {
cJSON_Delete(args);
nostr_handler_publish_result_free(&publish_result);
return NULL;
}
cJSON_AddBoolToObject(out, "success", publish_result.success ? 1 : 0);
cJSON_AddNumberToObject(out, "list_kind", publish_result.kind);
cJSON_AddStringToObject(out, "action", action->valuestring);
cJSON_Delete(args);
cJSON_AddNumberToObject(out, "items_affected", items_affected);
cJSON_AddStringToObject(out, "event_id", publish_result.event_id);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
nostr_handler_publish_result_free(&publish_result);
return json;
}
static char* execute_skill_create(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* slug = cJSON_GetObjectItemCaseSensitive(args, "slug");
cJSON* content = cJSON_GetObjectItemCaseSensitive(args, "content");
cJSON* scope = cJSON_GetObjectItemCaseSensitive(args, "scope");
cJSON* description = cJSON_GetObjectItemCaseSensitive(args, "description");
cJSON* auto_adopt = cJSON_GetObjectItemCaseSensitive(args, "auto_adopt");
cJSON* trigger = cJSON_GetObjectItemCaseSensitive(args, "trigger");
cJSON* filter = cJSON_GetObjectItemCaseSensitive(args, "filter");
cJSON* action = cJSON_GetObjectItemCaseSensitive(args, "action");
cJSON* enabled = cJSON_GetObjectItemCaseSensitive(args, "enabled");
if (!slug || !cJSON_IsString(slug) || !slug->valuestring ||
!content || !cJSON_IsString(content) || !content->valuestring) {
cJSON_Delete(args);
return json_error("skill_create requires slug and content strings");
}
if (!validate_skill_slug(slug->valuestring)) {
cJSON_Delete(args);
return json_error("skill_create slug must be lowercase letters/digits/hyphens (1-64 chars)");
}
if (scope && (!cJSON_IsString(scope) || !scope->valuestring)) {
cJSON_Delete(args);
return json_error("skill_create scope must be string when provided");
}
if (description && (!cJSON_IsString(description) || !description->valuestring)) {
cJSON_Delete(args);
return json_error("skill_create description must be string when provided");
}
if (trigger && (!cJSON_IsString(trigger) || !trigger->valuestring)) {
cJSON_Delete(args);
return json_error("skill_create trigger must be string when provided");
}
if (filter && (!cJSON_IsString(filter) || !filter->valuestring)) {
cJSON_Delete(args);
return json_error("skill_create filter must be string when provided");
}
if (action && (!cJSON_IsString(action) || !action->valuestring)) {
cJSON_Delete(args);
return json_error("skill_create action must be string when provided");
}
if (enabled && !cJSON_IsBool(enabled)) {
cJSON_Delete(args);
return json_error("skill_create enabled must be boolean when provided");
}
const char* scope_str = (scope && scope->valuestring && scope->valuestring[0] != '\0') ? scope->valuestring : "public";
int kind = 0;
if (strcmp(scope_str, "public") == 0) {
kind = 31123;
} else if (strcmp(scope_str, "private") == 0) {
kind = 31124;
} else {
cJSON_Delete(args);
return json_error("skill_create scope must be public or private");
}
int do_auto_adopt = (!auto_adopt || !cJSON_IsBool(auto_adopt) || cJSON_IsTrue(auto_adopt)) ? 1 : 0;
cJSON* tags = cJSON_CreateArray();
if (!tags) {
cJSON_Delete(args);
return json_error("failed to create skill tags");
}
if (add_string_tag(tags, "d", slug->valuestring) != 0 ||
add_string_tag(tags, "app", "didactyl") != 0 ||
add_string_tag(tags, "scope", scope_str) != 0) {
cJSON_Delete(tags);
cJSON_Delete(args);
return json_error("failed to add required skill tags");
}
if (description && description->valuestring && description->valuestring[0] != '\0') {
if (add_string_tag(tags, "description", description->valuestring) != 0) {
cJSON_Delete(tags);
cJSON_Delete(args);
return json_error("failed to add description tag");
}
}
const char* trigger_str = (trigger && trigger->valuestring && trigger->valuestring[0] != '\0')
? trigger->valuestring
: NULL;
const char* filter_str = (filter && filter->valuestring && filter->valuestring[0] != '\0')
? filter->valuestring
: NULL;
const char* action_str = (action && action->valuestring && action->valuestring[0] != '\0')
? action->valuestring
: "llm";
int enabled_int = (!enabled || cJSON_IsTrue(enabled)) ? 1 : 0;
if (trigger_str && strcmp(trigger_str, "nostr-subscription") != 0) {
cJSON_Delete(tags);
cJSON_Delete(args);
return json_error("skill_create trigger must be nostr-subscription when provided");
}
if ((trigger_str && !filter_str) || (!trigger_str && filter_str)) {
cJSON_Delete(tags);
cJSON_Delete(args);
return json_error("skill_create trigger and filter must both be provided together");
}
if (trigger_str) {
if (add_string_tag(tags, "trigger", trigger_str) != 0 ||
add_string_tag(tags, "filter", filter_str) != 0 ||
add_string_tag(tags, "action", action_str) != 0 ||
add_string_tag(tags, "enabled", enabled_int ? "true" : "false") != 0) {
cJSON_Delete(tags);
cJSON_Delete(args);
return json_error("failed to add trigger tags");
}
}
nostr_publish_result_t skill_publish;
memset(&skill_publish, 0, sizeof(skill_publish));
int rc = nostr_handler_publish_kind_event(kind, content->valuestring, tags, &skill_publish);
cJSON_Delete(tags);
if (rc != 0) {
cJSON_Delete(args);
nostr_handler_publish_result_free(&skill_publish);
return json_error("skill_create failed to publish skill event");
}
int adopted = 0;
int already_adopted = 0;
nostr_publish_result_t adoption_publish;
memset(&adoption_publish, 0, sizeof(adoption_publish));
if (do_auto_adopt && kind == 31123) {
cJSON* adoption_tags = NULL;
char* adoption_content = NULL;
if (fetch_adoption_list_tags(ctx, &adoption_tags, &adoption_content) == 0) {
char addr[256];
snprintf(addr, sizeof(addr), "31123:%s:%s", ctx->cfg->keys.public_key_hex, slug->valuestring);
cJSON* tuple = cJSON_CreateArray();
if (tuple) {
cJSON_AddItemToArray(tuple, cJSON_CreateString("a"));
cJSON_AddItemToArray(tuple, cJSON_CreateString(addr));
if (tags_contains_tuple(adoption_tags, tuple)) {
already_adopted = 1;
adopted = 1;
} else {
cJSON* dup = cJSON_Duplicate(tuple, 1);
if (dup) {
cJSON_AddItemToArray(adoption_tags, dup);
if (publish_adoption_list(adoption_content, adoption_tags, &adoption_publish) == 0) {
adopted = 1;
}
}
}
cJSON_Delete(tuple);
}
cJSON_Delete(adoption_tags);
free(adoption_content);
}
}
int trigger_registered = 0;
if (trigger_str && ctx->trigger_manager && enabled_int) {
trigger_action_type_t at = (strcmp(action_str, "template") == 0)
? TRIGGER_ACTION_TEMPLATE
: TRIGGER_ACTION_LLM;
if (trigger_manager_add(ctx->trigger_manager,
slug->valuestring,
content->valuestring,
filter_str,
at,
enabled_int) == 0) {
trigger_registered = 1;
}
}
cJSON* out = cJSON_CreateObject();
if (!out) {
cJSON_Delete(args);
nostr_handler_publish_result_free(&skill_publish);
nostr_handler_publish_result_free(&adoption_publish);
return NULL;
}
cJSON_AddBoolToObject(out, "success", skill_publish.success ? 1 : 0);
cJSON_AddStringToObject(out, "slug", slug->valuestring);
cJSON_AddNumberToObject(out, "kind", kind);
cJSON_AddStringToObject(out, "scope", scope_str);
cJSON_AddStringToObject(out, "skill_event_id", skill_publish.event_id);
cJSON_AddBoolToObject(out, "auto_adopt", do_auto_adopt ? 1 : 0);
cJSON_AddBoolToObject(out, "adopted", adopted ? 1 : 0);
cJSON_AddBoolToObject(out, "already_adopted", already_adopted ? 1 : 0);
cJSON_AddBoolToObject(out, "trigger_registered", trigger_registered ? 1 : 0);
if (adoption_publish.event_id[0] != '\0') {
cJSON_AddStringToObject(out, "adoption_event_id", adoption_publish.event_id);
}
if (skill_publish.naddr_uri[0] != '\0') {
cJSON_AddStringToObject(out, "naddr_uri", skill_publish.naddr_uri);
}
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
cJSON_Delete(args);
nostr_handler_publish_result_free(&skill_publish);
nostr_handler_publish_result_free(&adoption_publish);
return json;
}
static char* execute_skill_list(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* scope = cJSON_GetObjectItemCaseSensitive(args, "scope");
if (scope && (!cJSON_IsString(scope) || !scope->valuestring)) {
cJSON_Delete(args);
return json_error("skill_list scope must be string when provided");
}
const char* scope_str = (scope && scope->valuestring && scope->valuestring[0] != '\0') ? scope->valuestring : NULL;
int include_public = 1;
int include_private = 1;
if (scope_str) {
if (strcmp(scope_str, "public") == 0) {
include_private = 0;
} else if (strcmp(scope_str, "private") == 0) {
include_public = 0;
} else {
cJSON_Delete(args);
return json_error("skill_list scope must be public or private");
}
}
cJSON* filter = cJSON_CreateObject();
cJSON* kinds = cJSON_CreateArray();
cJSON* authors = cJSON_CreateArray();
if (!filter || !kinds || !authors) {
cJSON_Delete(filter);
cJSON_Delete(kinds);
cJSON_Delete(authors);
cJSON_Delete(args);
return json_error("failed to create skill_list filter");
}
if (include_public) cJSON_AddItemToArray(kinds, cJSON_CreateNumber(31123));
if (include_private) cJSON_AddItemToArray(kinds, cJSON_CreateNumber(31124));
cJSON_AddItemToObject(filter, "kinds", kinds);
cJSON_AddItemToArray(authors, cJSON_CreateString(ctx->cfg->keys.public_key_hex));
cJSON_AddItemToObject(filter, "authors", authors);
cJSON_AddNumberToObject(filter, "limit", 300);
char* events_json = nostr_handler_query_json(filter, 8000);
cJSON_Delete(filter);
cJSON_Delete(args);
if (!events_json) return json_error("skill_list query failed");
cJSON* events = cJSON_Parse(events_json);
free(events_json);
if (!events || !cJSON_IsArray(events)) {
cJSON_Delete(events);
return json_error("skill_list returned invalid JSON");
}
int fallback_used = 0;
if (cJSON_GetArraySize(events) == 0) {
cJSON_Delete(events);
events = NULL;
cJSON* fallback_filter = cJSON_CreateObject();
cJSON* fallback_kinds = cJSON_CreateArray();
if (!fallback_filter || !fallback_kinds) {
cJSON_Delete(fallback_filter);
cJSON_Delete(fallback_kinds);
return json_error("failed to create fallback skill_list filter");
}
if (include_public) cJSON_AddItemToArray(fallback_kinds, cJSON_CreateNumber(31123));
if (include_private) cJSON_AddItemToArray(fallback_kinds, cJSON_CreateNumber(31124));
cJSON_AddItemToObject(fallback_filter, "kinds", fallback_kinds);
cJSON_AddNumberToObject(fallback_filter, "limit", 600);
char* fallback_json = nostr_handler_query_json(fallback_filter, 8000);
cJSON_Delete(fallback_filter);
if (!fallback_json) {
return json_error("skill_list fallback query failed");
}
events = cJSON_Parse(fallback_json);
free(fallback_json);
if (!events || !cJSON_IsArray(events)) {
cJSON_Delete(events);
return json_error("skill_list fallback returned invalid JSON");
}
fallback_used = 1;
}
cJSON* out = cJSON_CreateObject();
cJSON* skills = cJSON_CreateArray();
if (!out || !skills) {
cJSON_Delete(out);
cJSON_Delete(skills);
cJSON_Delete(events);
return NULL;
}
int event_count = cJSON_GetArraySize(events);
for (int i = 0; i < event_count; i++) {
cJSON* ev = cJSON_GetArrayItem(events, i);
if (!ev || !cJSON_IsObject(ev)) continue;
if (fallback_used) {
cJSON* ev_pubkey = cJSON_GetObjectItemCaseSensitive(ev, "pubkey");
if (!ev_pubkey || !cJSON_IsString(ev_pubkey) || !ev_pubkey->valuestring ||
strcmp(ev_pubkey->valuestring, ctx->cfg->keys.public_key_hex) != 0) {
continue;
}
}
cJSON* summary = extract_skill_summary(ev);
if (summary) cJSON_AddItemToArray(skills, summary);
}
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddBoolToObject(out, "fallback_used", fallback_used ? 1 : 0);
cJSON_AddItemToObject(out, "skills", skills);
cJSON_AddNumberToObject(out, "count", cJSON_GetArraySize(skills));
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
cJSON_Delete(events);
return json;
}
static char* execute_skill_adopt(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(args, "pubkey");
cJSON* slug = cJSON_GetObjectItemCaseSensitive(args, "slug");
cJSON* kind = cJSON_GetObjectItemCaseSensitive(args, "kind");
if (!pubkey || !cJSON_IsString(pubkey) || !pubkey->valuestring || !is_hex_string_len(pubkey->valuestring, 64U) ||
!slug || !cJSON_IsString(slug) || !slug->valuestring || !validate_skill_slug(slug->valuestring)) {
cJSON_Delete(args);
return json_error("skill_adopt requires pubkey (hex) and valid slug");
}
int kind_val = (kind && cJSON_IsNumber(kind)) ? (int)kind->valuedouble : 31123;
if (kind_val != 31123 && kind_val != 31124) {
cJSON_Delete(args);
return json_error("skill_adopt kind must be 31123 or 31124");
}
char addr[256];
snprintf(addr, sizeof(addr), "%d:%s:%s", kind_val, pubkey->valuestring, slug->valuestring);
cJSON* tags = NULL;
char* content = NULL;
if (fetch_adoption_list_tags(ctx, &tags, &content) != 0) {
cJSON_Delete(args);
return json_error("skill_adopt failed to load adoption list");
}
cJSON* tuple = cJSON_CreateArray();
if (!tuple) {
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
return json_error("allocation failure");
}
cJSON_AddItemToArray(tuple, cJSON_CreateString("a"));
cJSON_AddItemToArray(tuple, cJSON_CreateString(addr));
int already_adopted = tags_contains_tuple(tags, tuple);
nostr_publish_result_t publish_result;
memset(&publish_result, 0, sizeof(publish_result));
if (!already_adopted) {
cJSON* dup = cJSON_Duplicate(tuple, 1);
if (!dup) {
cJSON_Delete(tuple);
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
return json_error("failed to duplicate adoption tuple");
}
cJSON_AddItemToArray(tags, dup);
if (publish_adoption_list(content, tags, &publish_result) != 0) {
cJSON_Delete(tuple);
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
return json_error("skill_adopt failed to publish adoption list");
}
}
cJSON* out = cJSON_CreateObject();
if (!out) {
cJSON_Delete(tuple);
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
nostr_handler_publish_result_free(&publish_result);
return NULL;
}
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "adopted_address", addr);
cJSON_AddBoolToObject(out, "already_adopted", already_adopted ? 1 : 0);
if (publish_result.event_id[0] != '\0') {
cJSON_AddStringToObject(out, "event_id", publish_result.event_id);
}
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
cJSON_Delete(tuple);
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
nostr_handler_publish_result_free(&publish_result);
return json;
}
static char* execute_skill_remove(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(args, "pubkey");
cJSON* slug = cJSON_GetObjectItemCaseSensitive(args, "slug");
cJSON* kind = cJSON_GetObjectItemCaseSensitive(args, "kind");
const char* pubkey_str = ctx->cfg->keys.public_key_hex;
if (pubkey) {
if (!cJSON_IsString(pubkey) || !pubkey->valuestring || !is_hex_string_len(pubkey->valuestring, 64U)) {
cJSON_Delete(args);
return json_error("skill_remove pubkey must be 64-char hex when provided");
}
pubkey_str = pubkey->valuestring;
}
if (!slug || !cJSON_IsString(slug) || !slug->valuestring || !validate_skill_slug(slug->valuestring)) {
cJSON_Delete(args);
return json_error("skill_remove requires valid slug");
}
int kind_val = (kind && cJSON_IsNumber(kind)) ? (int)kind->valuedouble : 31123;
if (kind_val != 31123 && kind_val != 31124) {
cJSON_Delete(args);
return json_error("skill_remove kind must be 31123 or 31124");
}
char addr[256];
snprintf(addr, sizeof(addr), "%d:%s:%s", kind_val, pubkey_str, slug->valuestring);
cJSON* tags = NULL;
char* content = NULL;
if (fetch_adoption_list_tags(ctx, &tags, &content) != 0) {
cJSON_Delete(args);
return json_error("skill_remove failed to load adoption list");
}
cJSON* tuple = cJSON_CreateArray();
if (!tuple) {
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
return json_error("allocation failure");
}
cJSON_AddItemToArray(tuple, cJSON_CreateString("a"));
cJSON_AddItemToArray(tuple, cJSON_CreateString(addr));
int removed = remove_matching_tag_tuples(tags, tuple);
nostr_publish_result_t publish_result;
memset(&publish_result, 0, sizeof(publish_result));
if (removed > 0) {
if (publish_adoption_list(content, tags, &publish_result) != 0) {
cJSON_Delete(tuple);
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
return json_error("skill_remove failed to publish adoption list");
}
}
if (ctx->trigger_manager) {
(void)trigger_manager_remove(ctx->trigger_manager, slug->valuestring);
}
cJSON* out = cJSON_CreateObject();
if (!out) {
cJSON_Delete(tuple);
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
nostr_handler_publish_result_free(&publish_result);
return NULL;
}
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "removed_address", addr);
cJSON_AddNumberToObject(out, "removed_count", removed);
cJSON_AddBoolToObject(out, "already_absent", removed == 0 ? 1 : 0);
if (publish_result.event_id[0] != '\0') {
cJSON_AddStringToObject(out, "event_id", publish_result.event_id);
}
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
cJSON_Delete(tuple);
cJSON_Delete(tags);
free(content);
cJSON_Delete(args);
nostr_handler_publish_result_free(&publish_result);
return json;
}
static char* execute_skill_search(const char* args_json) {
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* query = cJSON_GetObjectItemCaseSensitive(args, "query");
cJSON* pubkey = cJSON_GetObjectItemCaseSensitive(args, "pubkey");
cJSON* popular = cJSON_GetObjectItemCaseSensitive(args, "popular");
if (query && (!cJSON_IsString(query) || !query->valuestring)) {
cJSON_Delete(args);
return json_error("skill_search query must be string when provided");
}
if (pubkey && (!cJSON_IsString(pubkey) || !pubkey->valuestring || !is_hex_string_len(pubkey->valuestring, 64U))) {
cJSON_Delete(args);
return json_error("skill_search pubkey must be 64-char hex string when provided");
}
int do_popular = (popular && cJSON_IsBool(popular) && cJSON_IsTrue(popular)) ? 1 : 0;
const char* query_str = (query && query->valuestring) ? query->valuestring : NULL;
if (do_popular) {
cJSON* filter = cJSON_CreateObject();
cJSON* kinds = cJSON_CreateArray();
if (!filter || !kinds) {
cJSON_Delete(filter);
cJSON_Delete(kinds);
cJSON_Delete(args);
return json_error("failed to create skill_search popularity filter");
}
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(10123));
cJSON_AddItemToObject(filter, "kinds", kinds);
cJSON_AddNumberToObject(filter, "limit", 300);
char* events_json = nostr_handler_query_json(filter, 8000);
cJSON_Delete(filter);
cJSON_Delete(args);
if (!events_json) return json_error("skill_search popularity query failed");
cJSON* events = cJSON_Parse(events_json);
free(events_json);
if (!events || !cJSON_IsArray(events)) {
cJSON_Delete(events);
return json_error("skill_search popularity returned invalid JSON");
}
typedef struct {
char* addr;
int count;
} skill_count_t;
skill_count_t* counts = NULL;
int count_len = 0;
int event_n = cJSON_GetArraySize(events);
for (int i = 0; i < event_n; i++) {
cJSON* ev = cJSON_GetArrayItem(events, i);
cJSON* tags = ev ? cJSON_GetObjectItemCaseSensitive(ev, "tags") : NULL;
if (!tags || !cJSON_IsArray(tags)) continue;
int tn = cJSON_GetArraySize(tags);
for (int t = 0; t < tn; t++) {
cJSON* tuple = cJSON_GetArrayItem(tags, t);
if (!tuple || !cJSON_IsArray(tuple) || cJSON_GetArraySize(tuple) < 2) continue;
cJSON* k = cJSON_GetArrayItem(tuple, 0);
cJSON* v = cJSON_GetArrayItem(tuple, 1);
if (!k || !v || !cJSON_IsString(k) || !cJSON_IsString(v) || !k->valuestring || !v->valuestring) continue;
if (strcmp(k->valuestring, "a") != 0) continue;
if (strncmp(v->valuestring, "31123:", 6) != 0) continue;
if (pubkey && !strstr(v->valuestring, pubkey->valuestring)) continue;
if (query_str && !ci_contains(v->valuestring, query_str)) continue;
int found = -1;
for (int j = 0; j < count_len; j++) {
if (strcmp(counts[j].addr, v->valuestring) == 0) {
found = j;
break;
}
}
if (found >= 0) {
counts[found].count++;
} else {
skill_count_t* bigger = (skill_count_t*)realloc(counts, (size_t)(count_len + 1) * sizeof(skill_count_t));
if (!bigger) {
for (int j = 0; j < count_len; j++) free(counts[j].addr);
free(counts);
cJSON_Delete(events);
return json_error("allocation failure");
}
counts = bigger;
counts[count_len].addr = strdup(v->valuestring);
if (!counts[count_len].addr) {
for (int j = 0; j < count_len; j++) free(counts[j].addr);
free(counts);
cJSON_Delete(events);
return json_error("allocation failure");
}
counts[count_len].count = 1;
count_len++;
}
}
}
for (int i = 0; i < count_len; i++) {
for (int j = i + 1; j < count_len; j++) {
if (counts[j].count > counts[i].count) {
skill_count_t tmp = counts[i];
counts[i] = counts[j];
counts[j] = tmp;
}
}
}
cJSON* out = cJSON_CreateObject();
cJSON* items = cJSON_CreateArray();
if (!out || !items) {
cJSON_Delete(out);
cJSON_Delete(items);
for (int j = 0; j < count_len; j++) free(counts[j].addr);
free(counts);
cJSON_Delete(events);
return NULL;
}
for (int i = 0; i < count_len; i++) {
cJSON* it = cJSON_CreateObject();
if (!it) continue;
cJSON_AddStringToObject(it, "address", counts[i].addr);
cJSON_AddNumberToObject(it, "adoption_count", counts[i].count);
cJSON_AddItemToArray(items, it);
}
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddBoolToObject(out, "popular", 1);
cJSON_AddItemToObject(out, "results", items);
cJSON_AddNumberToObject(out, "count", count_len);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
for (int j = 0; j < count_len; j++) free(counts[j].addr);
free(counts);
cJSON_Delete(events);
return json;
}
cJSON* filter = cJSON_CreateObject();
cJSON* kinds = cJSON_CreateArray();
if (!filter || !kinds) {
cJSON_Delete(filter);
cJSON_Delete(kinds);
cJSON_Delete(args);
return json_error("failed to create skill_search filter");
}
cJSON_AddItemToArray(kinds, cJSON_CreateNumber(31123));
cJSON_AddItemToObject(filter, "kinds", kinds);
if (pubkey) {
cJSON* authors = cJSON_CreateArray();
if (!authors) {
cJSON_Delete(filter);
cJSON_Delete(args);
return json_error("failed to create skill_search authors");
}
cJSON_AddItemToArray(authors, cJSON_CreateString(pubkey->valuestring));
cJSON_AddItemToObject(filter, "authors", authors);
}
cJSON_AddNumberToObject(filter, "limit", 200);
char* events_json = nostr_handler_query_json(filter, 8000);
cJSON_Delete(filter);
cJSON_Delete(args);
if (!events_json) return json_error("skill_search query failed");
cJSON* events = cJSON_Parse(events_json);
free(events_json);
if (!events || !cJSON_IsArray(events)) {
cJSON_Delete(events);
return json_error("skill_search returned invalid JSON");
}
cJSON* out = cJSON_CreateObject();
cJSON* results = cJSON_CreateArray();
if (!out || !results) {
cJSON_Delete(out);
cJSON_Delete(results);
cJSON_Delete(events);
return NULL;
}
int n = cJSON_GetArraySize(events);
for (int i = 0; i < n; i++) {
cJSON* ev = cJSON_GetArrayItem(events, i);
cJSON* summary = extract_skill_summary(ev);
if (!summary) continue;
if (query_str && query_str[0] != '\0') {
cJSON* slug = cJSON_GetObjectItemCaseSensitive(summary, "slug");
cJSON* desc = cJSON_GetObjectItemCaseSensitive(summary, "description");
cJSON* preview = cJSON_GetObjectItemCaseSensitive(summary, "content_preview");
int match = 0;
if (slug && cJSON_IsString(slug) && slug->valuestring && ci_contains(slug->valuestring, query_str)) match = 1;
if (!match && desc && cJSON_IsString(desc) && desc->valuestring && ci_contains(desc->valuestring, query_str)) match = 1;
if (!match && preview && cJSON_IsString(preview) && preview->valuestring && ci_contains(preview->valuestring, query_str)) match = 1;
if (!match) {
cJSON_Delete(summary);
continue;
}
}
cJSON_AddItemToArray(results, summary);
}
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddBoolToObject(out, "popular", 0);
cJSON_AddItemToObject(out, "results", results);
cJSON_AddNumberToObject(out, "count", cJSON_GetArraySize(results));
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
cJSON_Delete(events);
return json;
}
static char* execute_nostr_query(const char* args_json) {
cJSON* args = cJSON_Parse(args_json ? args_json : "{}");
if (!args) return json_error("invalid arguments JSON");
cJSON* filter = cJSON_GetObjectItemCaseSensitive(args, "filter");
cJSON* timeout = cJSON_GetObjectItemCaseSensitive(args, "timeout_ms");
if (!filter || !cJSON_IsObject(filter)) {
cJSON_Delete(args);
return json_error("nostr_query requires object filter");
}
cJSON* filter_dup = cJSON_Duplicate(filter, 1);
if (!filter_dup) {
cJSON_Delete(args);
return json_error("failed to duplicate filter");
}
int timeout_ms = (timeout && cJSON_IsNumber(timeout)) ? (int)timeout->valuedouble : 8000;
char* events_json = nostr_handler_query_json(filter_dup, timeout_ms);
cJSON_Delete(filter_dup);
cJSON_Delete(args);
if (!events_json) return json_error("nostr_query failed");
cJSON* out = cJSON_CreateObject();
if (!out) {
free(events_json);
return NULL;
}
cJSON* events = cJSON_Parse(events_json);
free(events_json);
if (!events) {
cJSON_Delete(out);
return json_error("nostr_query returned invalid JSON");
}
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddItemToObject(out, "events", events);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_nostr_pubkey(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON_Delete(args);
cJSON* out = cJSON_CreateObject();
if (!out) return NULL;
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "pubkey", ctx->cfg->keys.public_key_hex);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_nostr_npub(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON_Delete(args);
char npub[256] = {0};
if (nostr_key_to_bech32(ctx->cfg->keys.public_key, "npub", npub) != NOSTR_SUCCESS) {
return json_error("failed to encode npub");
}
cJSON* out = cJSON_CreateObject();
if (!out) return NULL;
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "npub", npub);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_my_version(const char* args_json) {
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON_Delete(args);
cJSON* out = cJSON_CreateObject();
if (!out) return NULL;
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "name", DIDACTYL_NAME);
cJSON_AddStringToObject(out, "description", DIDACTYL_DESCRIPTION);
cJSON_AddStringToObject(out, "software", DIDACTYL_SOFTWARE);
cJSON_AddStringToObject(out, "version", DIDACTYL_VERSION);
cJSON_AddNumberToObject(out, "version_major", DIDACTYL_VERSION_MAJOR);
cJSON_AddNumberToObject(out, "version_minor", DIDACTYL_VERSION_MINOR);
cJSON_AddNumberToObject(out, "version_patch", DIDACTYL_VERSION_PATCH);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_http_fetch(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* url = cJSON_GetObjectItemCaseSensitive(args, "url");
cJSON* method = cJSON_GetObjectItemCaseSensitive(args, "method");
cJSON* headers = cJSON_GetObjectItemCaseSensitive(args, "headers");
cJSON* body = cJSON_GetObjectItemCaseSensitive(args, "body");
cJSON* timeout = cJSON_GetObjectItemCaseSensitive(args, "timeout_seconds");
cJSON* maxb = cJSON_GetObjectItemCaseSensitive(args, "max_bytes");
if (!url || !cJSON_IsString(url) || !url->valuestring || url->valuestring[0] == '\0') {
cJSON_Delete(args);
return json_error("http_fetch requires string url");
}
if (headers && !cJSON_IsArray(headers)) {
cJSON_Delete(args);
return json_error("http_fetch headers must be an array when provided");
}
if (body && !cJSON_IsString(body)) {
cJSON_Delete(args);
return json_error("http_fetch body must be a string when provided");
}
const char* method_str = (method && cJSON_IsString(method) && method->valuestring && method->valuestring[0] != '\0')
? method->valuestring
: "GET";
if (body && cJSON_IsString(body) && body->valuestring && strcasecmp(method_str, "GET") == 0) {
cJSON_Delete(args);
return json_error("http_fetch GET requests cannot include body");
}
int timeout_seconds = (timeout && cJSON_IsNumber(timeout)) ? (int)timeout->valuedouble : 20;
if (timeout_seconds <= 0) timeout_seconds = 20;
if (timeout_seconds > 120) timeout_seconds = 120;
int hard_max = ctx->cfg->tools.shell.max_output_bytes > 0 ? ctx->cfg->tools.shell.max_output_bytes : 65536;
int max_bytes = (maxb && cJSON_IsNumber(maxb)) ? (int)maxb->valuedouble : hard_max;
if (max_bytes <= 0 || max_bytes > hard_max) max_bytes = hard_max;
CURL* curl = curl_easy_init();
if (!curl) {
cJSON_Delete(args);
return json_error("http_fetch failed to initialize curl");
}
http_fetch_buffer_t rb;
memset(&rb, 0, sizeof(rb));
rb.max_bytes = (size_t)max_bytes;
struct curl_slist* req_headers = NULL;
req_headers = curl_slist_append(req_headers, "Accept: */*");
if (headers && cJSON_IsArray(headers)) {
int n = cJSON_GetArraySize(headers);
for (int i = 0; i < n; i++) {
cJSON* h = cJSON_GetArrayItem(headers, i);
if (h && cJSON_IsString(h) && h->valuestring && h->valuestring[0] != '\0') {
req_headers = curl_slist_append(req_headers, h->valuestring);
}
}
}
curl_easy_setopt(curl, CURLOPT_URL, url->valuestring);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, (long)timeout_seconds);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, http_fetch_write_cb);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &rb);
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, req_headers);
curl_easy_setopt(curl, CURLOPT_USERAGENT, "didactyl/http_fetch");
const char* ca_bundle = detect_ca_bundle_path_for_tools();
if (ca_bundle) {
curl_easy_setopt(curl, CURLOPT_CAINFO, ca_bundle);
}
if (strcasecmp(method_str, "GET") == 0) {
curl_easy_setopt(curl, CURLOPT_HTTPGET, 1L);
} else if (strcasecmp(method_str, "POST") == 0) {
curl_easy_setopt(curl, CURLOPT_POST, 1L);
if (body && cJSON_IsString(body) && body->valuestring) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body->valuestring);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)strlen(body->valuestring));
}
} else {
curl_easy_setopt(curl, CURLOPT_CUSTOMREQUEST, method_str);
if (body && cJSON_IsString(body) && body->valuestring) {
curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body->valuestring);
curl_easy_setopt(curl, CURLOPT_POSTFIELDSIZE, (long)strlen(body->valuestring));
}
}
CURLcode res = curl_easy_perform(curl);
long status_code = 0;
char* content_type = NULL;
char* content_type_copy = NULL;
curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &status_code);
curl_easy_getinfo(curl, CURLINFO_CONTENT_TYPE, &content_type);
if (content_type && content_type[0] != '\0') {
content_type_copy = strdup(content_type);
}
curl_slist_free_all(req_headers);
curl_easy_cleanup(curl);
cJSON* out = cJSON_CreateObject();
if (!out) {
free(rb.data);
return NULL;
}
int http_ok = (status_code >= 200 && status_code < 300) ? 1 : 0;
int success = (res == CURLE_OK && http_ok) ? 1 : 0;
cJSON_AddBoolToObject(out, "success", success);
cJSON_AddStringToObject(out, "url", url->valuestring);
cJSON_AddStringToObject(out, "method", method_str);
cJSON_AddNumberToObject(out, "status_code", status_code);
cJSON_AddBoolToObject(out, "http_ok", http_ok);
cJSON_AddBoolToObject(out, "truncated", rb.truncated ? 1 : 0);
cJSON_AddNumberToObject(out, "bytes_received", (double)rb.len);
if (content_type_copy && content_type_copy[0] != '\0') {
cJSON_AddStringToObject(out, "content_type", content_type_copy);
}
if (res != CURLE_OK) {
cJSON_AddStringToObject(out, "curl_error", curl_easy_strerror(res));
}
cJSON_AddStringToObject(out, "body", rb.data ? rb.data : "");
free(rb.data);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
cJSON_Delete(args);
free(content_type_copy);
return json;
}
static char* execute_shell_exec(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
if (!ctx->cfg->tools.shell.enabled) return json_error("shell tool disabled");
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* command = cJSON_GetObjectItemCaseSensitive(args, "command");
if (!command || !cJSON_IsString(command) || !command->valuestring || command->valuestring[0] == '\0') {
cJSON_Delete(args);
return json_error("shell_exec requires string command");
}
const char* cwd = ctx->cfg->tools.shell.working_directory[0] != '\0'
? ctx->cfg->tools.shell.working_directory
: ".";
int timeout_s = ctx->cfg->tools.shell.timeout_seconds > 0 ? ctx->cfg->tools.shell.timeout_seconds : 30;
char* quoted_cwd = shell_quote_single(cwd);
char* quoted_cmd = shell_quote_single(command->valuestring);
cJSON_Delete(args);
if (!quoted_cwd || !quoted_cmd) {
free(quoted_cwd);
free(quoted_cmd);
return json_error("allocation failure");
}
int needed = snprintf(NULL,
0,
"cd %s && timeout %ds sh -lc %s 2>&1",
quoted_cwd,
timeout_s,
quoted_cmd);
if (needed <= 0) {
free(quoted_cwd);
free(quoted_cmd);
return json_error("failed to build shell command");
}
char* cmd = (char*)malloc((size_t)needed + 1U);
if (!cmd) {
free(quoted_cwd);
free(quoted_cmd);
return json_error("allocation failure");
}
snprintf(cmd,
(size_t)needed + 1U,
"cd %s && timeout %ds sh -lc %s 2>&1",
quoted_cwd,
timeout_s,
quoted_cmd);
free(quoted_cwd);
free(quoted_cmd);
FILE* fp = popen(cmd, "r");
free(cmd);
if (!fp) return json_error("failed to execute command");
int max_bytes = ctx->cfg->tools.shell.max_output_bytes > 0 ? ctx->cfg->tools.shell.max_output_bytes : 65536;
char* output = (char*)calloc((size_t)max_bytes + 1U, 1U);
if (!output) {
pclose(fp);
return json_error("allocation failure");
}
size_t used = 0;
while (!feof(fp) && used < (size_t)max_bytes) {
size_t n = fread(output + used, 1, (size_t)max_bytes - used, fp);
used += n;
if (n == 0) break;
}
int raw_status = pclose(fp);
int exit_status = raw_status;
if (raw_status != -1) {
if (WIFEXITED(raw_status)) {
exit_status = WEXITSTATUS(raw_status);
} else if (WIFSIGNALED(raw_status)) {
exit_status = 128 + WTERMSIG(raw_status);
}
}
cJSON* out = cJSON_CreateObject();
if (!out) {
free(output);
return NULL;
}
cJSON_AddBoolToObject(out, "success", exit_status == 0 ? 1 : 0);
cJSON_AddNumberToObject(out, "exit_status", exit_status);
cJSON_AddStringToObject(out, "output", output);
free(output);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_file_read(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
cJSON* args = cJSON_Parse(args_json ? args_json : "{}");
if (!args) return json_error("invalid arguments JSON");
cJSON* path = cJSON_GetObjectItemCaseSensitive(args, "path");
cJSON* maxb = cJSON_GetObjectItemCaseSensitive(args, "max_bytes");
if (!path || !cJSON_IsString(path) || !path->valuestring) {
cJSON_Delete(args);
return json_error("file_read requires string path");
}
int hard_max = ctx->cfg->tools.shell.max_output_bytes > 0 ? ctx->cfg->tools.shell.max_output_bytes : 65536;
int max_bytes = (maxb && cJSON_IsNumber(maxb)) ? (int)maxb->valuedouble : hard_max;
if (max_bytes <= 0 || max_bytes > hard_max) max_bytes = hard_max;
char file_path[PATH_MAX];
if (build_tool_path(ctx, path->valuestring, file_path, sizeof(file_path)) != 0) {
cJSON_Delete(args);
return json_error("file_read path is not allowed");
}
FILE* fp = fopen(file_path, "rb");
cJSON_Delete(args);
if (!fp) return json_error("file_read failed to open file");
char* buf = (char*)calloc((size_t)max_bytes + 1U, 1U);
if (!buf) {
fclose(fp);
return json_error("allocation failure");
}
size_t n = fread(buf, 1, (size_t)max_bytes, fp);
int truncated = !feof(fp) ? 1 : 0;
fclose(fp);
buf[n] = '\0';
cJSON* out = cJSON_CreateObject();
if (!out) {
free(buf);
return NULL;
}
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "path", file_path);
cJSON_AddNumberToObject(out, "bytes_read", (double)n);
cJSON_AddBoolToObject(out, "truncated", truncated);
cJSON_AddStringToObject(out, "content", buf);
free(buf);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_file_write(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
cJSON* args = cJSON_Parse(args_json ? args_json : "{}");
if (!args) return json_error("invalid arguments JSON");
cJSON* path = cJSON_GetObjectItemCaseSensitive(args, "path");
cJSON* content = cJSON_GetObjectItemCaseSensitive(args, "content");
cJSON* append = cJSON_GetObjectItemCaseSensitive(args, "append");
if (!path || !cJSON_IsString(path) || !path->valuestring ||
!content || !cJSON_IsString(content) || !content->valuestring) {
cJSON_Delete(args);
return json_error("file_write requires string path and content");
}
char file_path[PATH_MAX];
if (build_tool_path(ctx, path->valuestring, file_path, sizeof(file_path)) != 0) {
cJSON_Delete(args);
return json_error("file_write path is not allowed");
}
const char* content_str = content->valuestring;
size_t len = strlen(content_str);
int do_append = (append && cJSON_IsBool(append) && cJSON_IsTrue(append)) ? 1 : 0;
FILE* fp = fopen(file_path, do_append ? "ab" : "wb");
cJSON_Delete(args);
if (!fp) return json_error("file_write failed to open file");
size_t n = fwrite(content_str, 1, len, fp);
fclose(fp);
if (n != len) return json_error("file_write failed to write all bytes");
cJSON* out = cJSON_CreateObject();
if (!out) return NULL;
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "path", file_path);
cJSON_AddNumberToObject(out, "bytes_written", (double)n);
cJSON_AddBoolToObject(out, "append", do_append);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_task_manage(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* action = cJSON_GetObjectItemCaseSensitive(args, "action");
if (!action || !cJSON_IsString(action) || !action->valuestring || action->valuestring[0] == '\0') {
cJSON_Delete(args);
return json_error("task_manage requires string action");
}
char tasks_path[PATH_MAX];
if (build_tool_path(ctx, "tasks.json", tasks_path, sizeof(tasks_path)) != 0) {
cJSON_Delete(args);
return json_error("failed to resolve tasks file path");
}
cJSON* root = tasks_load_root(tasks_path);
if (!root) {
cJSON_Delete(args);
return json_error("failed to load tasks file");
}
cJSON* tasks = cJSON_GetObjectItemCaseSensitive(root, "tasks");
if (!tasks || !cJSON_IsArray(tasks)) {
cJSON_DeleteItemFromObjectCaseSensitive(root, "tasks");
tasks = cJSON_CreateArray();
cJSON_AddItemToObject(root, "tasks", tasks);
}
cJSON* next_id_item = cJSON_GetObjectItemCaseSensitive(root, "next_id");
int next_id = (next_id_item && cJSON_IsNumber(next_id_item) && next_id_item->valuedouble >= 1)
? (int)next_id_item->valuedouble
: 1;
const char* action_s = action->valuestring;
int mutated = 0;
if (strcmp(action_s, "list") == 0) {
/* no-op */
} else if (strcmp(action_s, "add") == 0) {
cJSON* text = cJSON_GetObjectItemCaseSensitive(args, "text");
cJSON* status = cJSON_GetObjectItemCaseSensitive(args, "status");
if (!text || !cJSON_IsString(text) || !text->valuestring || text->valuestring[0] == '\0') {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("task_manage add requires non-empty string text");
}
const char* normalized_status = "pending";
if (status && !cJSON_IsNull(status)) {
if (!cJSON_IsString(status) || !status->valuestring) {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("task_manage add status must be string when provided");
}
normalized_status = normalize_task_status(status->valuestring);
if (!normalized_status) {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("task_manage add status must be pending, active, or done");
}
}
cJSON* task = cJSON_CreateObject();
if (!task) {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("allocation failure");
}
time_t now = time(NULL);
cJSON_AddNumberToObject(task, "id", next_id++);
cJSON_AddStringToObject(task, "text", text->valuestring);
cJSON_AddStringToObject(task, "status", normalized_status);
cJSON_AddNumberToObject(task, "created_at", (double)now);
cJSON_AddNumberToObject(task, "updated_at", (double)now);
cJSON_AddItemToArray(tasks, task);
mutated = 1;
} else if (strcmp(action_s, "update") == 0) {
cJSON* id = cJSON_GetObjectItemCaseSensitive(args, "id");
cJSON* text = cJSON_GetObjectItemCaseSensitive(args, "text");
cJSON* status = cJSON_GetObjectItemCaseSensitive(args, "status");
if (!id || !cJSON_IsNumber(id) || id->valuedouble < 1) {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("task_manage update requires integer id");
}
if ((!text || cJSON_IsNull(text)) && (!status || cJSON_IsNull(status))) {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("task_manage update requires text and/or status");
}
cJSON* task = task_find_by_id(tasks, (int)id->valuedouble, NULL);
if (!task) {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("task not found");
}
if (text && !cJSON_IsNull(text)) {
if (!cJSON_IsString(text) || !text->valuestring || text->valuestring[0] == '\0') {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("task_manage update text must be non-empty string when provided");
}
cJSON_DeleteItemFromObjectCaseSensitive(task, "text");
cJSON_AddStringToObject(task, "text", text->valuestring);
mutated = 1;
}
if (status && !cJSON_IsNull(status)) {
if (!cJSON_IsString(status) || !status->valuestring) {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("task_manage update status must be string when provided");
}
const char* normalized_status = normalize_task_status(status->valuestring);
if (!normalized_status) {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("task_manage update status must be pending, active, or done");
}
cJSON_DeleteItemFromObjectCaseSensitive(task, "status");
cJSON_AddStringToObject(task, "status", normalized_status);
mutated = 1;
}
if (mutated) {
time_t now = time(NULL);
cJSON_DeleteItemFromObjectCaseSensitive(task, "updated_at");
cJSON_AddNumberToObject(task, "updated_at", (double)now);
}
} else if (strcmp(action_s, "remove") == 0) {
cJSON* id = cJSON_GetObjectItemCaseSensitive(args, "id");
if (!id || !cJSON_IsNumber(id) || id->valuedouble < 1) {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("task_manage remove requires integer id");
}
int idx = -1;
cJSON* task = task_find_by_id(tasks, (int)id->valuedouble, &idx);
if (!task || idx < 0) {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("task not found");
}
cJSON_DeleteItemFromArray(tasks, idx);
mutated = 1;
} else if (strcmp(action_s, "clear") == 0) {
cJSON* status = cJSON_GetObjectItemCaseSensitive(args, "status");
if (!status || cJSON_IsNull(status)) {
while (cJSON_GetArraySize(tasks) > 0) {
cJSON_DeleteItemFromArray(tasks, 0);
}
mutated = 1;
} else {
if (!cJSON_IsString(status) || !status->valuestring) {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("task_manage clear status must be string when provided");
}
const char* normalized_status = normalize_task_status(status->valuestring);
if (!normalized_status) {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("task_manage clear status must be pending, active, or done");
}
int i = 0;
while (i < cJSON_GetArraySize(tasks)) {
cJSON* task = cJSON_GetArrayItem(tasks, i);
cJSON* task_status = task ? cJSON_GetObjectItemCaseSensitive(task, "status") : NULL;
const char* task_status_s = (task_status && cJSON_IsString(task_status) && task_status->valuestring)
? task_status->valuestring
: "pending";
if (strcmp(task_status_s, normalized_status) == 0) {
cJSON_DeleteItemFromArray(tasks, i);
mutated = 1;
} else {
i++;
}
}
}
} else if (strcmp(action_s, "replace") == 0) {
cJSON* tasks_in = cJSON_GetObjectItemCaseSensitive(args, "tasks");
if (!tasks_in || !cJSON_IsArray(tasks_in)) {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("task_manage replace requires array tasks");
}
while (cJSON_GetArraySize(tasks) > 0) {
cJSON_DeleteItemFromArray(tasks, 0);
}
int n = cJSON_GetArraySize(tasks_in);
time_t now = time(NULL);
for (int i = 0; i < n; i++) {
cJSON* text = cJSON_GetArrayItem(tasks_in, i);
if (!text || !cJSON_IsString(text) || !text->valuestring || text->valuestring[0] == '\0') {
continue;
}
cJSON* task = cJSON_CreateObject();
if (!task) {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("allocation failure");
}
cJSON_AddNumberToObject(task, "id", next_id++);
cJSON_AddStringToObject(task, "text", text->valuestring);
cJSON_AddStringToObject(task, "status", "pending");
cJSON_AddNumberToObject(task, "created_at", (double)now);
cJSON_AddNumberToObject(task, "updated_at", (double)now);
cJSON_AddItemToArray(tasks, task);
}
mutated = 1;
} else {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("task_manage action must be one of: list, add, update, remove, clear, replace");
}
if (mutated) {
cJSON_DeleteItemFromObjectCaseSensitive(root, "next_id");
cJSON_AddNumberToObject(root, "next_id", next_id);
if (tasks_save_root(tasks_path, root) != 0) {
cJSON_Delete(args);
cJSON_Delete(root);
return json_error("failed to save tasks file");
}
}
cJSON* out = cJSON_CreateObject();
if (!out) {
cJSON_Delete(args);
cJSON_Delete(root);
return NULL;
}
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "action", action_s);
cJSON_AddStringToObject(out, "path", tasks_path);
cJSON_AddBoolToObject(out, "mutated", mutated ? 1 : 0);
cJSON_AddNumberToObject(out, "count", cJSON_GetArraySize(tasks));
cJSON* tasks_dup = cJSON_Duplicate(tasks, 1);
if (!tasks_dup) {
cJSON_Delete(args);
cJSON_Delete(root);
cJSON_Delete(out);
return NULL;
}
cJSON_AddItemToObject(out, "tasks", tasks_dup);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
cJSON_Delete(args);
cJSON_Delete(root);
return json;
}
static char* execute_tool_list(tools_context_t* ctx, const char* args_json) {
(void)args_json;
if (!ctx) {
return json_error("tool context unavailable");
}
char* schema_json = tools_build_openai_schema_json(ctx);
if (!schema_json) {
return json_error("failed to build tool schemas");
}
cJSON* schema_arr = cJSON_Parse(schema_json);
free(schema_json);
if (!schema_arr || !cJSON_IsArray(schema_arr)) {
cJSON_Delete(schema_arr);
return json_error("failed to parse tool schemas");
}
cJSON* out = cJSON_CreateObject();
cJSON* tools = cJSON_CreateArray();
if (!out || !tools) {
cJSON_Delete(schema_arr);
cJSON_Delete(out);
cJSON_Delete(tools);
return json_error("out of memory");
}
cJSON* item = NULL;
cJSON_ArrayForEach(item, schema_arr) {
cJSON* fn = cJSON_GetObjectItemCaseSensitive(item, "function");
if (!fn || !cJSON_IsObject(fn)) {
continue;
}
cJSON* name = cJSON_GetObjectItemCaseSensitive(fn, "name");
if (!name || !cJSON_IsString(name) || !name->valuestring) {
continue;
}
cJSON* row = cJSON_CreateObject();
if (!row) {
continue;
}
cJSON_AddStringToObject(row, "name", name->valuestring);
cJSON* description = cJSON_GetObjectItemCaseSensitive(fn, "description");
if (description && cJSON_IsString(description) && description->valuestring) {
cJSON_AddStringToObject(row, "description", description->valuestring);
} else {
cJSON_AddStringToObject(row, "description", "");
}
cJSON* parameters = cJSON_GetObjectItemCaseSensitive(fn, "parameters");
if (parameters) {
cJSON_AddItemToObject(row, "parameters", cJSON_Duplicate(parameters, 1));
} else {
cJSON_AddItemToObject(row, "parameters", cJSON_CreateObject());
}
cJSON_AddItemToArray(tools, row);
}
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddNumberToObject(out, "count", (double)cJSON_GetArraySize(tools));
cJSON_AddItemToObject(out, "tools", tools);
char* result = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
cJSON_Delete(schema_arr);
return result;
}
static char* execute_trigger_list(tools_context_t* ctx, const char* args_json) {
(void)args_json;
if (!ctx || !ctx->trigger_manager) {
return json_error("trigger manager unavailable");
}
char* status = trigger_manager_status_json(ctx->trigger_manager);
if (!status) {
return json_error("failed to build trigger status");
}
return status;
}
static int json_object_set_string(cJSON* obj, const char* key, const char* value) {
if (!obj || !key || !value) return -1;
cJSON_DeleteItemFromObjectCaseSensitive(obj, key);
cJSON* v = cJSON_CreateString(value);
if (!v) return -1;
cJSON_AddItemToObject(obj, key, v);
return 0;
}
static int persist_llm_config(tools_context_t* ctx, const llm_config_t* cfg) {
if (!ctx || !ctx->cfg || !cfg) return -1;
if (ctx->cfg->config_path[0] == '\0') return -1;
size_t src_len = 0;
char* src = read_entire_file(ctx->cfg->config_path, &src_len);
if (!src) return -1;
cJSON* root = cJSON_ParseWithLength(src, src_len);
free(src);
if (!root || !cJSON_IsObject(root)) {
cJSON_Delete(root);
return -1;
}
cJSON* llm = cJSON_GetObjectItemCaseSensitive(root, "llm");
if (!llm || !cJSON_IsObject(llm)) {
cJSON_DeleteItemFromObjectCaseSensitive(root, "llm");
llm = cJSON_CreateObject();
if (!llm) {
cJSON_Delete(root);
return -1;
}
cJSON_AddItemToObject(root, "llm", llm);
}
if (json_object_set_string(llm, "provider", cfg->provider) != 0 ||
json_object_set_string(llm, "api_key", cfg->api_key) != 0 ||
json_object_set_string(llm, "model", cfg->model) != 0 ||
json_object_set_string(llm, "base_url", cfg->base_url) != 0) {
cJSON_Delete(root);
return -1;
}
cJSON_DeleteItemFromObjectCaseSensitive(llm, "max_tokens");
cJSON_AddNumberToObject(llm, "max_tokens", cfg->max_tokens);
cJSON_DeleteItemFromObjectCaseSensitive(llm, "temperature");
cJSON_AddNumberToObject(llm, "temperature", cfg->temperature);
char* out = cJSON_Print(root);
cJSON_Delete(root);
if (!out) return -1;
FILE* fp = fopen(ctx->cfg->config_path, "wb");
if (!fp) {
free(out);
return -1;
}
size_t out_len = strlen(out);
size_t n = fwrite(out, 1, out_len, fp);
fclose(fp);
free(out);
return n == out_len ? 0 : -1;
}
static int assign_string_field(cJSON* item, char* dst, size_t dst_size, int* changed) {
if (!item) return 0;
if (!cJSON_IsString(item) || !item->valuestring) return -1;
size_t n = strlen(item->valuestring);
if (n >= dst_size) return -1;
memcpy(dst, item->valuestring, n + 1U);
if (changed) *changed = 1;
return 0;
}
static char* execute_model_get(const char* args_json) {
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON_Delete(args);
llm_config_t cfg;
if (llm_get_config(&cfg) != 0) {
return json_error("llm runtime unavailable");
}
cJSON* out = cJSON_CreateObject();
if (!out) return NULL;
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "provider", cfg.provider);
cJSON_AddStringToObject(out, "model", cfg.model);
cJSON_AddStringToObject(out, "base_url", cfg.base_url);
cJSON_AddNumberToObject(out, "max_tokens", cfg.max_tokens);
cJSON_AddNumberToObject(out, "temperature", cfg.temperature);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static char* execute_model_set(tools_context_t* ctx, const char* args_json) {
if (!ctx || !ctx->cfg) return json_error("tool context unavailable");
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
llm_config_t cfg;
if (llm_get_config(&cfg) != 0) {
cJSON_Delete(args);
return json_error("llm runtime unavailable");
}
int changed = 0;
cJSON* provider = cJSON_GetObjectItemCaseSensitive(args, "provider");
cJSON* api_key = cJSON_GetObjectItemCaseSensitive(args, "api_key");
cJSON* model = cJSON_GetObjectItemCaseSensitive(args, "model");
cJSON* base_url = cJSON_GetObjectItemCaseSensitive(args, "base_url");
cJSON* max_tokens = cJSON_GetObjectItemCaseSensitive(args, "max_tokens");
cJSON* temperature = cJSON_GetObjectItemCaseSensitive(args, "temperature");
if (assign_string_field(provider, cfg.provider, sizeof(cfg.provider), &changed) != 0 ||
assign_string_field(api_key, cfg.api_key, sizeof(cfg.api_key), &changed) != 0 ||
assign_string_field(model, cfg.model, sizeof(cfg.model), &changed) != 0 ||
assign_string_field(base_url, cfg.base_url, sizeof(cfg.base_url), &changed) != 0) {
cJSON_Delete(args);
return json_error("model_set string field invalid or too long");
}
if (max_tokens) {
if (!cJSON_IsNumber(max_tokens)) {
cJSON_Delete(args);
return json_error("model_set max_tokens must be a number");
}
cfg.max_tokens = (int)max_tokens->valuedouble;
changed = 1;
}
if (temperature) {
if (!cJSON_IsNumber(temperature)) {
cJSON_Delete(args);
return json_error("model_set temperature must be a number");
}
cfg.temperature = temperature->valuedouble;
changed = 1;
}
cJSON_Delete(args);
if (!changed) {
return json_error("model_set requires at least one field to update");
}
if (llm_set_config(&cfg) != 0) {
return json_error("failed to update runtime llm config");
}
ctx->cfg->llm = cfg;
if (persist_llm_config(ctx, &cfg) != 0) {
return json_error("failed to persist llm config to config file");
}
cJSON* out = cJSON_CreateObject();
if (!out) return NULL;
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddStringToObject(out, "provider", cfg.provider);
cJSON_AddStringToObject(out, "model", cfg.model);
cJSON_AddStringToObject(out, "base_url", cfg.base_url);
cJSON_AddNumberToObject(out, "max_tokens", cfg.max_tokens);
cJSON_AddNumberToObject(out, "temperature", cfg.temperature);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
return json;
}
static void append_model_id(cJSON* ids, cJSON* item) {
if (!ids || !item) return;
if (cJSON_IsString(item) && item->valuestring) {
cJSON_AddItemToArray(ids, cJSON_CreateString(item->valuestring));
return;
}
if (cJSON_IsObject(item)) {
cJSON* id = cJSON_GetObjectItemCaseSensitive(item, "id");
if (id && cJSON_IsString(id) && id->valuestring) {
cJSON_AddItemToArray(ids, cJSON_CreateString(id->valuestring));
}
}
}
static char* execute_model_list(const char* args_json) {
cJSON* args = parse_tool_args_json(args_json);
if (!args) return json_error("invalid arguments JSON");
cJSON* base_url = cJSON_GetObjectItemCaseSensitive(args, "base_url");
if (base_url && (!cJSON_IsString(base_url) || !base_url->valuestring)) {
cJSON_Delete(args);
return json_error("model_list base_url must be a string");
}
const char* base_url_override = (base_url && base_url->valuestring && base_url->valuestring[0] != '\0')
? base_url->valuestring
: NULL;
char* raw = llm_list_models_json(base_url_override);
cJSON_Delete(args);
if (!raw) {
return json_error("model_list request failed");
}
cJSON* root = cJSON_Parse(raw);
free(raw);
if (!root) {
cJSON_Delete(root);
return json_error("model_list returned invalid JSON");
}
cJSON* ids = cJSON_CreateArray();
cJSON* out = cJSON_CreateObject();
if (!ids || !out) {
cJSON_Delete(ids);
cJSON_Delete(out);
cJSON_Delete(root);
return NULL;
}
if (cJSON_IsArray(root)) {
int n = cJSON_GetArraySize(root);
for (int i = 0; i < n; i++) {
append_model_id(ids, cJSON_GetArrayItem(root, i));
}
} else if (cJSON_IsObject(root)) {
cJSON* data = cJSON_GetObjectItemCaseSensitive(root, "data");
if (data && cJSON_IsArray(data)) {
int n = cJSON_GetArraySize(data);
for (int i = 0; i < n; i++) {
append_model_id(ids, cJSON_GetArrayItem(data, i));
}
}
}
cJSON_AddBoolToObject(out, "success", 1);
cJSON_AddNumberToObject(out, "count", cJSON_GetArraySize(ids));
cJSON_AddItemToObject(out, "models", ids);
char* json = cJSON_PrintUnformatted(out);
cJSON_Delete(out);
cJSON_Delete(root);
return json;
}
char* tools_execute(tools_context_t* ctx, const char* tool_name, const char* args_json) {
if (!tool_name) return json_error("missing tool name");
if (strcmp(tool_name, "nostr_post") == 0) {
return execute_nostr_post(args_json);
}
if (strcmp(tool_name, "nostr_delete") == 0) {
return execute_nostr_delete(args_json);
}
if (strcmp(tool_name, "nostr_react") == 0) {
return execute_nostr_react(args_json);
}
if (strcmp(tool_name, "nostr_profile_get") == 0) {
return execute_nostr_profile_get(args_json);
}
if (strcmp(tool_name, "nostr_relay_status") == 0) {
return execute_nostr_relay_status(args_json);
}
if (strcmp(tool_name, "nostr_nip05_lookup") == 0) {
return execute_nostr_nip05_lookup(args_json);
}
if (strcmp(tool_name, "nostr_encode") == 0) {
return execute_nostr_encode(args_json);
}
if (strcmp(tool_name, "nostr_decode") == 0) {
return execute_nostr_decode(args_json);
}
if (strcmp(tool_name, "nostr_dm_send") == 0) {
return execute_nostr_dm_send(args_json);
}
if (strcmp(tool_name, "nostr_relay_info") == 0) {
return execute_nostr_relay_info(args_json);
}
if (strcmp(tool_name, "nostr_encrypt") == 0) {
return execute_nostr_encrypt(ctx, args_json);
}
if (strcmp(tool_name, "nostr_decrypt") == 0) {
return execute_nostr_decrypt(ctx, args_json);
}
if (strcmp(tool_name, "nostr_dm_send_nip17") == 0) {
return execute_nostr_dm_send_nip17(args_json);
}
if (strcmp(tool_name, "nostr_list_manage") == 0) {
return execute_nostr_list_manage(ctx, args_json);
}
if (strcmp(tool_name, "nostr_query") == 0) {
return execute_nostr_query(args_json);
}
if (strcmp(tool_name, "my_version") == 0) {
return execute_my_version(args_json);
}
if (strcmp(tool_name, "nostr_pubkey") == 0) {
return execute_nostr_pubkey(ctx, args_json);
}
if (strcmp(tool_name, "nostr_npub") == 0) {
return execute_nostr_npub(ctx, args_json);
}
if (strcmp(tool_name, "my_pubkey") == 0) {
return execute_nostr_pubkey(ctx, args_json);
}
if (strcmp(tool_name, "my_npub") == 0) {
return execute_nostr_npub(ctx, args_json);
}
if (strcmp(tool_name, "http_fetch") == 0) {
return execute_http_fetch(ctx, args_json);
}
if (strcmp(tool_name, "shell_exec") == 0) {
return execute_shell_exec(ctx, args_json);
}
if (strcmp(tool_name, "file_read") == 0) {
return execute_file_read(ctx, args_json);
}
if (strcmp(tool_name, "file_write") == 0) {
return execute_file_write(ctx, args_json);
}
if (strcmp(tool_name, "skill_create") == 0) {
return execute_skill_create(ctx, args_json);
}
if (strcmp(tool_name, "skill_list") == 0) {
return execute_skill_list(ctx, args_json);
}
if (strcmp(tool_name, "skill_adopt") == 0) {
return execute_skill_adopt(ctx, args_json);
}
if (strcmp(tool_name, "skill_remove") == 0) {
return execute_skill_remove(ctx, args_json);
}
if (strcmp(tool_name, "skill_search") == 0) {
return execute_skill_search(args_json);
}
if (strcmp(tool_name, "trigger_list") == 0) {
return execute_trigger_list(ctx, args_json);
}
if (strcmp(tool_name, "tool_list") == 0) {
return execute_tool_list(ctx, args_json);
}
if (strcmp(tool_name, "model_get") == 0) {
return execute_model_get(args_json);
}
if (strcmp(tool_name, "model_set") == 0) {
return execute_model_set(ctx, args_json);
}
if (strcmp(tool_name, "model_list") == 0) {
return execute_model_list(args_json);
}
if (strcmp(tool_name, "nostr_post_readme") == 0) {
return execute_nostr_post_readme(ctx, args_json);
}
if (strcmp(tool_name, "nostr_file_md_to_longform_post") == 0) {
return execute_nostr_file_md_to_longform_post(ctx, args_json);
}
if (strcmp(tool_name, "task_manage") == 0) {
return execute_task_manage(ctx, args_json);
}
return json_error("unknown tool");
}