v0.0.11 - auto update readme.md
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user