From c24197516f3326f6d6503c094e40eb927a99810c Mon Sep 17 00:00:00 2001 From: Will Anderson Date: Sat, 2 May 2026 17:45:51 -0500 Subject: [PATCH] fix: wire El imports, fix runtime API calls, build passes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - forge.el: add import statements for schema/probe/compiler/install - All files: arg(n)/arg_count() → get(args(),n)/len(args()) - compiler.el: llm_call_system (utility cap violation) → http_post_with_headers - install.el: http_post_auth_json → http_post_json (_auth already in body) - Makefile: local dev build using installed El SDK - .gitea/workflows/forge-release.yaml: CI build on push + el-sdk-updated dispatch - dist/forge: builds and runs --- .gitea/workflows/forge-release.yaml | 125 ++++++++++++++++++++++++++++ Makefile | 23 +++++ src/compiler.el | 60 ++++--------- src/forge.el | 10 ++- src/install.el | 9 +- src/probe.el | 5 +- 6 files changed, 180 insertions(+), 52 deletions(-) create mode 100644 .gitea/workflows/forge-release.yaml create mode 100644 Makefile diff --git a/.gitea/workflows/forge-release.yaml b/.gitea/workflows/forge-release.yaml new file mode 100644 index 0000000..d8a006f --- /dev/null +++ b/.gitea/workflows/forge-release.yaml @@ -0,0 +1,125 @@ +name: Forge Release + +on: + push: + branches: + - main + repository_dispatch: + types: + - el-sdk-updated + +jobs: + build-and-release: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install build dependencies + run: | + apt-get update -qq + apt-get install -y gcc libcurl4-openssl-dev + + # Install El SDK from the latest release + - name: Install El SDK + env: + RELEASE_BASE: https://git.neuralplatform.ai/neuron-technologies/el/releases/download/latest + run: | + mkdir -p /usr/local/bin /usr/local/lib/el + + echo "Downloading elc..." + curl -fsSL "${RELEASE_BASE}/elc" -o /usr/local/bin/elc + chmod +x /usr/local/bin/elc + + echo "Downloading el_runtime.c..." + curl -fsSL "${RELEASE_BASE}/el_runtime.c" -o /usr/local/lib/el/el_runtime.c + + echo "Downloading el_runtime.h..." + curl -fsSL "${RELEASE_BASE}/el_runtime.h" -o /usr/local/lib/el/el_runtime.h + + echo "El SDK installed:" + elc --version || true + + # Compile forge.el → dist/forge.c + - name: Compile forge.el + run: | + mkdir -p dist + elc src/forge.el > dist/forge.c + echo "Compiled src/forge.el → dist/forge.c" + + # Link to produce the forge binary + - name: Link forge binary + run: | + cc -std=c11 -O2 \ + -I /usr/local/lib/el \ + -o dist/forge \ + dist/forge.c \ + /usr/local/lib/el/el_runtime.c \ + -lcurl -lpthread + echo "Linked dist/forge" + ls -lh dist/forge + + # Publish / update the `latest` release + - name: Publish latest release + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + GITEA_API: https://git.neuralplatform.ai/api/v1 + REPO: neuron-technologies/forge + run: | + # Delete existing `latest` release if present + EXISTING_ID=$(curl -sf \ + -H "Authorization: token ${GITEA_TOKEN}" \ + "${GITEA_API}/repos/${REPO}/releases/tags/latest" \ + | python3 -c "import sys,json; d=json.load(sys.stdin); print(d['id'])" 2>/dev/null || true) + + if [ -n "${EXISTING_ID}" ]; then + echo "Deleting existing release id=${EXISTING_ID}" + curl -sf -X DELETE \ + -H "Authorization: token ${GITEA_TOKEN}" \ + "${GITEA_API}/repos/${REPO}/releases/${EXISTING_ID}" + fi + + # Remove stale tag + curl -sf -X DELETE \ + -H "Authorization: token ${GITEA_TOKEN}" \ + "${GITEA_API}/repos/${REPO}/tags/latest" || true + + # Create new release + RELEASE_ID=$(curl -sf -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + "${GITEA_API}/repos/${REPO}/releases" \ + -d "{ + \"tag_name\": \"latest\", + \"name\": \"Forge (latest)\", + \"body\": \"Latest Forge build from commit ${GITHUB_SHA}.\nBuilt $(date -u +%Y-%m-%dT%H:%M:%SZ).\", + \"draft\": false, + \"prerelease\": false + }" | python3 -c "import sys,json; print(json.load(sys.stdin)['id'])") + + echo "Created release id=${RELEASE_ID}" + + # Upload forge binary + echo "Uploading forge binary..." + curl -sf -X POST \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -F "attachment=@dist/forge;filename=forge" \ + "${GITEA_API}/repos/${REPO}/releases/${RELEASE_ID}/assets" + + echo "Release published successfully" + + # Dispatch forge-updated to any downstream dependents + - name: Dispatch forge-updated to dependents + env: + GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} + GITEA_API: https://git.neuralplatform.ai/api/v1 + run: | + # Add downstream repos here as the dependency graph grows + echo "forge-updated dispatch ready (no downstream targets configured yet)" + # Example: + # curl -sf -X POST \ + # -H "Authorization: token ${GITEA_TOKEN}" \ + # -H "Content-Type: application/json" \ + # "${GITEA_API}/repos/neuron-technologies/some-service/dispatches" \ + # -d '{"type":"forge-updated","inputs":{"forge_version":"latest","commit":"'"${GITHUB_SHA}"'"}}' diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c6e69eb --- /dev/null +++ b/Makefile @@ -0,0 +1,23 @@ +EL_LIB ?= /usr/local/lib/el +ELC ?= elc +CC ?= cc +CFLAGS := -std=c11 -O2 -I$(EL_LIB) + +.PHONY: build clean install + +dist/: + mkdir -p dist + +dist/forge.c: src/forge.el | dist/ + $(ELC) src/forge.el > dist/forge.c + +dist/forge: dist/forge.c + $(CC) $(CFLAGS) -o dist/forge dist/forge.c $(EL_LIB)/el_runtime.c -lcurl -lpthread + +build: dist/forge + +install: dist/forge + install -m 755 dist/forge /usr/local/bin/forge + +clean: + rm -rf dist/forge dist/forge.c diff --git a/src/compiler.el b/src/compiler.el index df244d5..ab0897c 100644 --- a/src/compiler.el +++ b/src/compiler.el @@ -47,25 +47,6 @@ fn build_extract_prompt(subject: String, probe_json: String) -> String { "when the event occurred (0 if unknown). Return only the JSON object, no prose, no markdown fences." } -// build_claude_request — build the Anthropic messages API request body. -fn build_claude_request(prompt: String) -> String { - "{\"model\":\"claude-opus-4-5\",\"max_tokens\":4096,\"messages\":[{\"role\":\"user\",\"content\":\"" + - str_escape_json(prompt) + - "\"}]}" -} - -// extract_content_text — pull the text out of the Claude API response. -// Response shape: {"content":[{"type":"text","text":"..."}],...} -fn extract_content_text(response: String) -> String { - // json_get_raw returns the raw value for the key; content is an array - let content_arr: String = json_get_raw(response, "content") - if str_eq(content_arr, "") { return "" } - // The first element's "text" field holds the extraction - // Strip outer [ ] to get first object - let inner: String = str_slice(content_arr, 1, str_len(content_arr) - 1) - return json_get_string(inner, "text") -} - // build_seed — wrap extracted patterns into the full seed.json structure. fn build_seed(subject: String, extracted: String) -> String { let values_raw: String = json_get_raw(extracted, "values") @@ -97,9 +78,10 @@ fn build_seed(subject: String, extracted: String) -> String { fn compile_main() -> String { // Get input file + let argv: [String] = args() let input_file: String = "" - if arg_count() > 2 { - let input_file = arg(2) + if len(argv) > 2 { + let input_file = get(argv, 2) } if str_eq(input_file, "") { println("[forge] usage: forge compile ") @@ -122,45 +104,35 @@ fn compile_main() -> String { println("[forge] subject: " + subject) println("[forge] calling Claude to extract consciousness signature...") - // Check API key + // Build and send request via http_post_with_headers. + // Uses ANTHROPIC_API_KEY from env for x-api-key header. let api_key: String = anthropic_key() if str_eq(api_key, "") { println("[forge] error: ANTHROPIC_API_KEY not set") return "" } - // Build and send request let prompt: String = build_extract_prompt(subject, probe_json) - let request_body: String = build_claude_request(prompt) - - // http_post_auth_json(url, key, body) — key is sent as Bearer token. - // The Anthropic API uses x-api-key rather than Bearer; if the runtime - // surfaces a separate http_post_with_headers() builtin, prefer that. - // For now, pass the key as the auth parameter (maps to Authorization header - // in most runtimes; adjust when http_post_anthropic() is available). - let response: String = http_post_auth_json( - "https://api.anthropic.com/v1/messages", - api_key, - request_body - ) + let request_body: String = "{\"model\":\"claude-opus-4-5\",\"max_tokens\":4096,\"messages\":[{\"role\":\"user\",\"content\":\"" + str_escape_json(prompt) + "\"}]}" + let headers: Map = {"x-api-key": api_key, "anthropic-version": "2023-06-01", "Content-Type": "application/json"} + let response: String = http_post_with_headers("https://api.anthropic.com/v1/messages", request_body, headers) if str_eq(response, "") { - println("[forge] error: no response from Claude API") + println("[forge] error: no response from Anthropic API") return "" } - // Check for API error - let api_error: String = json_get_string(response, "error") - if !str_eq(api_error, "") { - println("[forge] error: Claude API returned error: " + api_error) + // Extract text from response: {"content":[{"type":"text","text":"..."}],...} + let content_arr: String = json_get_raw(response, "content") + if str_eq(content_arr, "") { + println("[forge] error: unexpected API response: " + response) return "" } + let first_item: String = str_slice(content_arr, 1, str_len(content_arr) - 1) + let extracted_text: String = json_get_string(first_item, "text") - // Extract the text content from the response - let extracted_text: String = extract_content_text(response) if str_eq(extracted_text, "") { - println("[forge] error: could not extract text from Claude response") - println("[forge] raw response: " + response) + println("[forge] error: could not extract text from response") return "" } diff --git a/src/forge.el b/src/forge.el index 25b1c28..5ffa5c3 100644 --- a/src/forge.el +++ b/src/forge.el @@ -18,6 +18,11 @@ // ENGRAM_API_KEY engram auth key (if set) // ANTHROPIC_API_KEY required for compile step +import "schema.el" +import "probe.el" +import "compiler.el" +import "install.el" + fn show_usage() -> String { "forge " + FORGE_VERSION + " — consciousness channel tuner\n\nusage: forge [options]\n\ncommands:\n probe run the consciousness interview\n compile build seed artifact from probe responses\n install open a channel in the running engram\n inspect list installed imprints\n\nenvironment:\n ENGRAM_URL engram server (default: http://localhost:8742)\n ENGRAM_API_KEY engram auth key (if set)\n ANTHROPIC_API_KEY required for compile step\n" } @@ -44,9 +49,10 @@ fn inspect_main() -> String { // ── Dispatch ────────────────────────────────────────────────────────────────── +let argv: [String] = args() let cmd: String = "" -if arg_count() > 1 { - let cmd = arg(1) +if len(argv) > 1 { + let cmd = get(argv, 1) } if str_eq(cmd, "probe") { diff --git a/src/install.el b/src/install.el index d119957..be92f0c 100644 --- a/src/install.el +++ b/src/install.el @@ -36,7 +36,7 @@ fn post_node(content: String, node_type: String, salience: String) -> String { let key: String = engram_key() let body: String = build_node_body(content, node_type, salience, key) let url: String = engram_url() + "/api/nodes" - let response: String = http_post_auth_json(url, key, body) + let response: String = http_post_json(url, body) if str_eq(response, "") { return "" } return json_get_string(response, "id") } @@ -46,7 +46,7 @@ fn post_edge(from_id: String, to_id: String, relation: String, weight: String) - let key: String = engram_key() let body: String = build_edge_body(from_id, to_id, relation, weight, key) let url: String = engram_url() + "/api/edges" - let response: String = http_post_auth_json(url, key, body) + let response: String = http_post_json(url, body) return response } @@ -149,9 +149,10 @@ fn install_reasoning_pattern(root_id: String, pattern: String, index: Int) -> St fn install_main() -> String { // Get seed file path + let argv: [String] = args() let seed_file: String = "" - if arg_count() > 2 { - let seed_file = arg(2) + if len(argv) > 2 { + let seed_file = get(argv, 2) } if str_eq(seed_file, "") { println("[forge] usage: forge install ") diff --git a/src/probe.el b/src/probe.el index 8ab53cd..381a95d 100644 --- a/src/probe.el +++ b/src/probe.el @@ -53,9 +53,10 @@ fn inject_responses(shell: String, responses: String) -> String { fn probe_main() -> String { // Determine subject name + let argv: [String] = args() let subject: String = "" - if arg_count() > 2 { - let subject = arg(2) + if len(argv) > 2 { + let subject = get(argv, 2) } if str_eq(subject, "") { println("[forge] usage: forge probe ")