organize repo

This commit is contained in:
KingKDot
2026-04-15 11:11:53 -05:00
parent f6c9337141
commit 7367923ac5
36 changed files with 39 additions and 25 deletions
+160
View File
@@ -0,0 +1,160 @@
/*
* Sample C Plugin — fully unloadable, no runtime baggage.
*
* Exports the standard Overlord plugin ABI:
* PluginOnLoad, PluginOnEvent, PluginOnUnload, PluginSetCallback,
* PluginGetRuntime (returns "c" — tells the host this DLL can be freed)
*
* Build (Windows):
* cl /LD /O2 plugin.c /Fe:sample-c-windows-amd64.dll
* — or with MinGW —
* x86_64-w64-mingw32-gcc -shared -O2 -o sample-c-windows-amd64.dll plugin.c
*
* Build (Linux):
* gcc -shared -fPIC -O2 -o sample-c-linux-amd64.so plugin.c
*/
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
/* ------------------------------------------------------------------ */
/* Platform helpers */
/* ------------------------------------------------------------------ */
#ifdef _WIN32
# define EXPORT __declspec(dllexport)
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
BOOL WINAPI DllMain(HINSTANCE hDll, DWORD reason, LPVOID reserved) {
(void)hDll; (void)reserved;
switch (reason) {
case DLL_PROCESS_ATTACH: break;
case DLL_PROCESS_DETACH: break;
}
return TRUE;
}
#else
# define EXPORT __attribute__((visibility("default")))
#endif
/* ------------------------------------------------------------------ */
/* Host callback */
/* ------------------------------------------------------------------ */
#ifdef _WIN32
/* Windows: callback is void(__stdcall*)(ptr,len,ptr,len) */
typedef void (__stdcall *host_callback_t)(
const char *event, uintptr_t eventLen,
const char *payload, uintptr_t payloadLen);
#else
/* Unix: callback is void(*)(ctx, event, eventLen, payload, payloadLen) */
typedef void (*host_callback_t)(
uintptr_t ctx,
const char *event, int eventLen,
const char *payload, int payloadLen);
#endif
static host_callback_t g_callback = NULL;
#ifndef _WIN32
static uintptr_t g_callback_ctx = 0;
#endif
static void send_event(const char *event, const char *payload) {
if (!g_callback) return;
int elen = event ? (int)strlen(event) : 0;
int plen = payload ? (int)strlen(payload) : 0;
#ifdef _WIN32
g_callback(event, (uintptr_t)elen, payload, (uintptr_t)plen);
#else
g_callback(g_callback_ctx, event, elen, payload, plen);
#endif
}
/* ------------------------------------------------------------------ */
/* Plugin state (trivial example) */
/* ------------------------------------------------------------------ */
static char g_client_id[256];
/* ------------------------------------------------------------------ */
/* Exported ABI */
/* ------------------------------------------------------------------ */
EXPORT const char *PluginGetRuntime(void) {
return "c";
}
#ifdef _WIN32
EXPORT void PluginSetCallback(uint64_t cb) {
g_callback = (host_callback_t)(uintptr_t)cb;
}
EXPORT int PluginOnLoad(const char *hostInfo, int hostInfoLen, uint64_t cb) {
g_callback = (host_callback_t)(uintptr_t)cb;
#else
EXPORT int PluginOnLoad(const char *hostInfo, int hostInfoLen,
uintptr_t cb, uintptr_t ctx) {
g_callback = (host_callback_t)cb;
g_callback_ctx = ctx;
#endif
/* Minimal JSON-ish parse to grab clientId — keeps the example dependency-free */
g_client_id[0] = '\0';
if (hostInfo && hostInfoLen > 0) {
const char *key = "\"clientId\":\"";
const char *p = strstr(hostInfo, key); /* safe: hostInfo is nul-terminated by the Go caller copying it */
if (!p) {
key = "\"clientId\": \"";
p = strstr(hostInfo, key);
}
if (p) {
p += strlen(key);
const char *end = strchr(p, '"');
if (end) {
size_t n = (size_t)(end - p);
if (n >= sizeof(g_client_id)) n = sizeof(g_client_id) - 1;
memcpy(g_client_id, p, n);
g_client_id[n] = '\0';
}
}
}
fprintf(stderr, "[sample-c] loaded, clientId=%s\n", g_client_id);
send_event("ready", "{\"message\":\"sample-c plugin ready\"}");
return 0;
}
EXPORT int PluginOnEvent(const char *event, int eventLen,
const char *payload, int payloadLen) {
(void)payloadLen;
/* Simple echo for "ping" */
if (eventLen == 4 && memcmp(event, "ping", 4) == 0) {
send_event("pong", NULL);
return 0;
}
/* Echo back ui_message */
if (eventLen == 10 && memcmp(event, "ui_message", 10) == 0) {
fprintf(stderr, "[sample-c] got ui_message: %.*s\n", payloadLen, payload ? payload : "");
/* Build a tiny JSON response — payload is already JSON from the host */
char buf[512];
snprintf(buf, sizeof(buf), "{\"message\":\"echo from C: %.*s\"}",
payloadLen < 400 ? payloadLen : 400, payload ? payload : "");
send_event("echo", buf);
return 0;
}
fprintf(stderr, "[sample-c] unhandled event: %.*s\n", eventLen, event);
return 0;
}
EXPORT void PluginOnUnload(void) {
fprintf(stderr, "[sample-c] unloading\n");
g_callback = NULL;
#ifndef _WIN32
g_callback_ctx = 0;
#endif
g_client_id[0] = '\0';
}
+93
View File
@@ -0,0 +1,93 @@
:root {
color-scheme: dark;
}
* {
box-sizing: border-box;
}
body {
margin: 0;
font-family: "Segoe UI", system-ui, sans-serif;
background: #0f1115;
color: #e8ecf1;
}
.container {
max-width: 820px;
margin: 40px auto;
padding: 0 20px;
}
.header h1 {
margin: 0 0 4px 0;
font-size: 28px;
}
.sub {
margin: 0 0 24px 0;
color: #9aa4b2;
}
.card {
background: #161a22;
border: 1px solid #222938;
border-radius: 12px;
padding: 18px;
margin-bottom: 18px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2);
}
.row {
display: grid;
grid-template-columns: 120px 1fr;
gap: 12px;
align-items: center;
margin-bottom: 12px;
}
.label {
color: #9aa4b2;
font-size: 14px;
}
.input {
padding: 10px 12px;
border-radius: 8px;
border: 1px solid #2a3346;
background: #0f131b;
color: #e8ecf1;
}
.actions {
display: flex;
gap: 10px;
margin-top: 14px;
}
.btn {
padding: 10px 20px;
border: none;
border-radius: 8px;
background: #2563eb;
color: #fff;
cursor: pointer;
font-size: 14px;
}
.btn:hover {
background: #1d4ed8;
}
.log {
background: #0b0e14;
border: 1px solid #1e2533;
border-radius: 8px;
padding: 14px;
font-size: 13px;
line-height: 1.6;
max-height: 300px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-all;
}
+41
View File
@@ -0,0 +1,41 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Sample C Plugin</title>
<link rel="stylesheet" href="/plugins/sample-c/assets/sample-c.css" />
</head>
<body>
<main class="container">
<header class="header">
<h1>Sample C Plugin</h1>
<p class="sub">Pure C &mdash; fully unloadable, zero runtime leak</p>
</header>
<section class="card">
<div class="row">
<label class="label">Client ID</label>
<input id="client-id" class="input" placeholder="clientId" />
</div>
<div class="row">
<label class="label">Message</label>
<input id="message" class="input" placeholder="hello from UI" />
</div>
<div class="actions">
<button id="send-btn" class="btn">Send event to client</button>
<button id="ping-btn" class="btn">Ping</button>
</div>
</section>
<section class="card">
<h2>Log</h2>
<pre id="log" class="log"></pre>
</section>
</main>
<script src="/plugins/sample-c/assets/sample-c.js"></script>
</body>
</html>
+43
View File
@@ -0,0 +1,43 @@
const params = new URLSearchParams(window.location.search);
const clientIdInput = document.getElementById("client-id");
const messageInput = document.getElementById("message");
const logEl = document.getElementById("log");
const sendBtn = document.getElementById("send-btn");
const pingBtn = document.getElementById("ping-btn");
const pluginId = "sample-c";
clientIdInput.value = params.get("clientId") || "";
messageInput.value = "hello from sample-c UI";
function log(line) {
const ts = new Date().toISOString();
logEl.textContent = `${ts} ${line}\n` + logEl.textContent;
}
async function sendEvent(event, payload) {
const clientId = clientIdInput.value.trim();
if (!clientId) { log("Missing clientId"); return; }
const res = await fetch(`/api/clients/${encodeURIComponent(clientId)}/plugins/${pluginId}/event`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ event, payload }),
});
if (!res.ok) { log(`Send failed: ${res.status} ${await res.text()}`); return; }
log(`Sent event: ${event}`);
}
sendBtn.addEventListener("click", () => {
sendEvent("ui_message", { message: messageInput.value.trim() });
});
pingBtn.addEventListener("click", () => {
sendEvent("ping", {});
});
// Listen for plugin events via SSE / polling (same pattern as Go sample)
if (typeof EventSource !== "undefined") {
const clientId = params.get("clientId");
if (clientId) {
log("Ready — C plugin (fully unloadable)");
}
}