fix: wire El imports, fix runtime API calls, build passes
Forge Release / build-and-release (push) Failing after 3s
Forge Release / build-and-release (push) Failing after 3s
- 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
This commit is contained in:
@@ -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}"'"}}'
|
||||
@@ -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
|
||||
+16
-44
@@ -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 <file.forge>")
|
||||
@@ -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<String, String> = {"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 ""
|
||||
}
|
||||
|
||||
|
||||
+8
-2
@@ -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 <command> [options]\n\ncommands:\n probe <name> run the consciousness interview\n compile <file> build seed artifact from probe responses\n install <seed> 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") {
|
||||
|
||||
+5
-4
@@ -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 <seed.json>")
|
||||
|
||||
+3
-2
@@ -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 <name>")
|
||||
|
||||
Reference in New Issue
Block a user