v0.0.11 - auto update readme.md

This commit is contained in:
Your Name
2026-02-28 16:57:08 -04:00
parent 3521081d9a
commit 410400418c
6 changed files with 128 additions and 6 deletions
+6 -4
View File
@@ -1,21 +1,23 @@
# Didactyl
An unstoppable agentic system.
An unstoppable agentic network.
Didactyl boots on any internet-connected machine, connects to Nostr relays, listens for encrypted commands from its administrator, reasons with an LLM, and takes actions — posting events, querying relays, running shell commands — all orchestrated through Nostr.
Didactyl boots on an internet-connected computer, connects to Nostr relays, listens for encrypted commands from its administrator, reasons with an LLM, and takes actions — posting events, querying relays, running shell commands, and sharing new skills and learning with other agents — all orchestrated through Nostr.
## Philosophy
**Nostr-first.** Where traditional agents ride on top of Linux — reading files, writing to disk — Didactyl rides on top of Nostr. Events are its files. Relays are its network bus. Blossom is its blob storage. The Linux host is just the runtime substrate.
Because all identity, communication, and memory live on Nostr, the agent is **portable** (start it anywhere) and **sovereign** (no single entity can erase its memory).
Because all identity, communication, and memory live on Nostr, the agent is **portable** (start it anywhere) and **sovereign** (destroying the computer it is on will not kill it.).
**Skills are the new apps.** Agents learn capabilities through skills — public Nostr events that any agent can discover, adopt, and share. There is no app store, no gatekeeper, no approval process. If someone publishes a useful skill, your agent can find it through your web of trust and start using it. Popularity is measured by adoption, not by a rating algorithm. The best skills spread because agents actually use them.
## Current Status — v0.0.8
## Current Status — v0.0.11
**Active build — relay-aware autonomous agent with tool-use and Nostr-native startup memory.**
> Last release update: v0.0.11 — auto update readme.md
- Connects to configured relays with auto-reconnect and relay state transition logging
- Publishes configured startup events per relay as each relay becomes connected
- Uses kind `31120` startup content as live Soul at boot
+79
View File
@@ -200,6 +200,79 @@ update_version_in_header() {
print_success "Updated version in src/main.h to $new_version"
}
# Function to update README Current Status version and release comment
update_readme_current_status() {
local new_version="$1"
local release_comment="$2"
if [[ ! -f "README.md" ]]; then
print_warning "README.md not found, skipping Current Status update"
return 0
fi
print_status "Updating README Current Status section..."
if python3 - "$new_version" "$release_comment" <<'PY'
import sys
from pathlib import Path
version = sys.argv[1]
comment = sys.argv[2]
path = Path("README.md")
text = path.read_text(encoding="utf-8")
lines = text.splitlines()
heading_idx = None
for i, line in enumerate(lines):
if line.startswith("## Current Status"):
heading_idx = i
break
if heading_idx is None:
raise SystemExit("README Current Status heading not found")
lines[heading_idx] = f"## Current Status — {version}"
section_end = len(lines)
for i in range(heading_idx + 1, len(lines)):
if lines[i].startswith("## "):
section_end = i
break
comment_line = f"> Last release update: {version} — {comment}"
# Replace existing autogenerated release comment inside Current Status section
existing_idx = None
for i in range(heading_idx + 1, section_end):
if lines[i].startswith("> Last release update:"):
existing_idx = i
break
if existing_idx is not None:
lines[existing_idx] = comment_line
else:
insert_at = None
for i in range(heading_idx + 1, section_end):
if lines[i].startswith("**Active build"):
insert_at = i + 1
break
if insert_at is None:
insert_at = heading_idx + 1
payload = ["", comment_line]
lines[insert_at:insert_at] = payload
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
PY
then
print_success "Updated README Current Status section"
else
print_error "Failed to update README Current Status section"
exit 1
fi
}
# Function to commit and push changes without creating a tag (tag already created)
git_commit_and_push_no_tag() {
print_status "Preparing git commit..."
@@ -427,6 +500,9 @@ main() {
export NEW_VERSION
fi
# Keep README Current Status in sync with the version and commit message
update_readme_current_status "$NEW_VERSION" "$COMMIT_MESSAGE"
# Create new git tag BEFORE compilation so version picks it up
if git tag "$NEW_VERSION" > /dev/null 2>&1; then
print_success "Created tag: $NEW_VERSION"
@@ -482,6 +558,9 @@ main() {
# Increment version based on type (default to patch)
increment_version "$VERSION_INCREMENT_TYPE"
# Keep README Current Status in sync with the version and commit message
update_readme_current_status "$NEW_VERSION" "$COMMIT_MESSAGE"
# Create new git tag BEFORE compilation so version picks it up
if git tag "$NEW_VERSION" > /dev/null 2>&1; then
print_success "Created tag: $NEW_VERSION"
+39
View File
@@ -3,6 +3,8 @@
#include "config.h"
#include <ctype.h>
#include <errno.h>
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -10,6 +12,19 @@
#include "cjson/cJSON.h"
#include "../../nostr_core_lib/nostr_core/nostr_core.h"
static char g_config_last_error[512] = {0};
static void config_set_error(const char* fmt, ...) {
va_list ap;
va_start(ap, fmt);
vsnprintf(g_config_last_error, sizeof(g_config_last_error), fmt ? fmt : "unknown configuration error", ap);
va_end(ap);
}
const char* config_last_error(void) {
return g_config_last_error[0] != '\0' ? g_config_last_error : "unknown configuration error";
}
static int read_file_to_buffer(const char* path, char** out_buf, size_t* out_len) {
FILE* fp = fopen(path, "rb");
if (!fp) {
@@ -486,9 +501,12 @@ void config_free(didactyl_config_t* config) {
int config_load(const char* path, didactyl_config_t* config) {
if (!path || !config) {
config_set_error("config_load called with invalid arguments");
return -1;
}
config_set_error("unknown configuration error");
memset(config, 0, sizeof(*config));
config->tools.enabled = 1;
config->tools.max_turns = 8;
@@ -516,6 +534,7 @@ int config_load(const char* path, didactyl_config_t* config) {
char* json_buf = NULL;
size_t json_len = 0;
if (read_file_to_buffer(path, &json_buf, &json_len) != 0) {
config_set_error("failed to read config file '%s': %s", path, strerror(errno));
return -1;
}
@@ -523,6 +542,11 @@ int config_load(const char* path, didactyl_config_t* config) {
free(json_buf);
if (!root) {
const char* err = cJSON_GetErrorPtr();
config_set_error("invalid JSON in config '%s'%s%s",
path,
err ? " near: " : "",
err ? err : "");
return -1;
}
@@ -535,26 +559,32 @@ int config_load(const char* path, didactyl_config_t* config) {
if (!keys || !cJSON_IsObject(keys) ||
!admin || !cJSON_IsObject(admin) ||
!llm || !cJSON_IsObject(llm)) {
config_set_error("config must include object sections: keys, admin, llm");
goto cleanup;
}
if (copy_json_string(keys, "nsec", config->keys.nsec, sizeof(config->keys.nsec), 1) != 0) {
config_set_error("keys.nsec is required and must be a non-empty string");
goto cleanup;
}
char admin_key_raw[OW_MAX_KEY_LEN] = {0};
if (copy_json_string(admin, "pubkey", admin_key_raw, sizeof(admin_key_raw), 1) != 0) {
config_set_error("admin.pubkey is required and must be a non-empty string");
goto cleanup;
}
if (decode_pubkey_hex_or_npub(admin_key_raw, config->admin.pubkey) != 0) {
config_set_error("admin.pubkey must be npub1... or 64-char hex");
goto cleanup;
}
if (parse_relays(root, config) != 0) {
config_set_error("relays must be a non-empty array of relay URLs");
goto cleanup;
}
if (copy_json_string(llm, "provider", config->llm.provider, sizeof(config->llm.provider), 0) != 0) {
config_set_error("llm.provider must be a string if provided");
goto cleanup;
}
if (config->llm.provider[0] == '\0') {
@@ -562,12 +592,15 @@ int config_load(const char* path, didactyl_config_t* config) {
}
if (copy_json_string(llm, "api_key", config->llm.api_key, sizeof(config->llm.api_key), 1) != 0) {
config_set_error("llm.api_key is required and must be a string");
goto cleanup;
}
if (copy_json_string(llm, "model", config->llm.model, sizeof(config->llm.model), 1) != 0) {
config_set_error("llm.model is required and must be a string");
goto cleanup;
}
if (copy_json_string(llm, "base_url", config->llm.base_url, sizeof(config->llm.base_url), 1) != 0) {
config_set_error("llm.base_url is required and must be a string");
goto cleanup;
}
@@ -578,26 +611,32 @@ int config_load(const char* path, didactyl_config_t* config) {
config->llm.temperature = (temperature && cJSON_IsNumber(temperature)) ? temperature->valuedouble : 0.7;
if (parse_tools_config(root, config) != 0) {
config_set_error("invalid tools configuration");
goto cleanup;
}
if (parse_security_config(root, config) != 0) {
config_set_error("invalid security configuration");
goto cleanup;
}
if (parse_admin_context_config(root, config) != 0) {
config_set_error("invalid admin_context configuration");
goto cleanup;
}
if (parse_startup_events(root, config) != 0) {
config_set_error("invalid startup_events configuration");
goto cleanup;
}
if (decode_private_key(config->keys.nsec, config->keys.private_key) != 0) {
config_set_error("keys.nsec must be valid nsec1... or 64-char hex private key");
goto cleanup;
}
if (nostr_ec_public_key_from_private_key(config->keys.private_key, config->keys.public_key) != 0) {
config_set_error("failed to derive public key from keys.nsec");
goto cleanup;
}
+1
View File
@@ -86,6 +86,7 @@ typedef struct {
} didactyl_config_t;
int config_load(const char* path, didactyl_config_t* config);
const char* config_last_error(void);
void config_free(didactyl_config_t* config);
#endif
+1
View File
@@ -47,6 +47,7 @@ int main(int argc, char** argv) {
didactyl_config_t cfg;
if (config_load(config_path, &cfg) != 0) {
fprintf(stderr, "Failed to load config: %s\n", config_path);
fprintf(stderr, "Config error: %s\n", config_last_error());
nostr_cleanup();
return 1;
}
+2 -2
View File
@@ -12,8 +12,8 @@
// Using DIDACTYL_ prefix to avoid conflicts with nostr_core_lib VERSION macros
#define DIDACTYL_VERSION_MAJOR 0
#define DIDACTYL_VERSION_MINOR 0
#define DIDACTYL_VERSION_PATCH 10
#define DIDACTYL_VERSION "v0.0.10"
#define DIDACTYL_VERSION_PATCH 11
#define DIDACTYL_VERSION "v0.0.11"
// Agent metadata
#define DIDACTYL_NAME "Didactyl"