Ferrell Synthetic Intelligence commited on
Commit ·
e7d4595
1
Parent(s): dd6c84a
Persist community hub through local daemon
Browse files- app.js +29 -8
- community/README.md +1 -1
- community/store.json +14 -0
- community/store.mjs +32 -0
- daemon/server.mjs +10 -0
- daemon/test-community-store.mjs +17 -0
- index.html +2 -0
- package.json +1 -1
app.js
CHANGED
|
@@ -89,12 +89,7 @@ async function importModel(model) {
|
|
| 89 |
selectModel(model);
|
| 90 |
};
|
| 91 |
|
| 92 |
-
|
| 93 |
-
projects: [{ title: 'AIDE Sovereign Workbench', detail: 'Local-first IDE and model harness', boundary: 'private' }],
|
| 94 |
-
issues: [{ title: 'Connect LSP language servers', detail: 'Add the first daemon-managed language server adapter', status: 'open' }],
|
| 95 |
-
discussions: [{ title: 'Welcome to the local developer network', detail: 'No account, relay, or network required', status: 'local' }],
|
| 96 |
-
marketplace: [{ title: 'No marketplace listings yet', detail: 'Publish signed artifacts only after license and checksum review', status: 'empty' }]
|
| 97 |
-
};
|
| 98 |
input.click();
|
| 99 |
}
|
| 100 |
|
|
@@ -212,11 +207,36 @@ function renderCasefile() {
|
|
| 212 |
}
|
| 213 |
|
| 214 |
function renderCommunity(tab = 'projects') {
|
| 215 |
-
const entries =
|
| 216 |
$('#community-feed').innerHTML = `<div style="color:#72ff9e;margin-bottom:5px">LOCAL CACHE / SYNC OFF</div>${entries.map(entry => `<div style="border-left:2px solid #b277ff;padding-left:5px;margin:5px 0"><b>${esc(entry.title)}</b><br>${esc(entry.detail)}<br><span style="color:#718994">${esc(entry.boundary || entry.status || 'local')}</span></div>`).join('')}`;
|
| 217 |
document.querySelectorAll('[data-community-tab]').forEach(button => button.style.color = button.dataset.communityTab === tab ? '#72ff9e' : '#718994');
|
| 218 |
}
|
| 219 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 220 |
async function digestFile(file) {
|
| 221 |
const buffer = await file.arrayBuffer();
|
| 222 |
const digest = await crypto.subtle.digest('SHA-256', buffer);
|
|
@@ -277,7 +297,8 @@ async function boot() {
|
|
| 277 |
};
|
| 278 |
$('#evidence-input').onchange = importEvidence;
|
| 279 |
document.querySelectorAll('[data-community-tab]').forEach(button => button.onclick = () => renderCommunity(button.dataset.communityTab));
|
| 280 |
-
|
|
|
|
| 281 |
$('#input').onkeydown = event => { if (event.key === 'Enter') sendChat(); };
|
| 282 |
}
|
| 283 |
|
|
|
|
| 89 |
selectModel(model);
|
| 90 |
};
|
| 91 |
|
| 92 |
+
let communityStore = { projects: [], issues: [], discussions: [], marketplace: [] };
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
input.click();
|
| 94 |
}
|
| 95 |
|
|
|
|
| 207 |
}
|
| 208 |
|
| 209 |
function renderCommunity(tab = 'projects') {
|
| 210 |
+
const entries = communityStore[tab] || [];
|
| 211 |
$('#community-feed').innerHTML = `<div style="color:#72ff9e;margin-bottom:5px">LOCAL CACHE / SYNC OFF</div>${entries.map(entry => `<div style="border-left:2px solid #b277ff;padding-left:5px;margin:5px 0"><b>${esc(entry.title)}</b><br>${esc(entry.detail)}<br><span style="color:#718994">${esc(entry.boundary || entry.status || 'local')}</span></div>`).join('')}`;
|
| 212 |
document.querySelectorAll('[data-community-tab]').forEach(button => button.style.color = button.dataset.communityTab === tab ? '#72ff9e' : '#718994');
|
| 213 |
}
|
| 214 |
|
| 215 |
+
async function loadCommunity() {
|
| 216 |
+
try {
|
| 217 |
+
const response = await fetch('http://127.0.0.1:4777/api/community');
|
| 218 |
+
if (response.ok) communityStore = await response.json();
|
| 219 |
+
} catch { /* offline cache remains empty until the daemon is running */ }
|
| 220 |
+
renderCommunity();
|
| 221 |
+
}
|
| 222 |
+
|
| 223 |
+
async function addCommunityIssue() {
|
| 224 |
+
const title = $('#community-title').value.trim();
|
| 225 |
+
if (!title) return;
|
| 226 |
+
try {
|
| 227 |
+
const response = await fetch('http://127.0.0.1:4777/api/community/items', {
|
| 228 |
+
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
| 229 |
+
body: JSON.stringify({ type: 'issues', item: { title, detail: 'Created locally from AIDE.' } })
|
| 230 |
+
});
|
| 231 |
+
if (!response.ok) throw new Error('daemon rejected item');
|
| 232 |
+
$('#community-title').value = '';
|
| 233 |
+
await loadCommunity();
|
| 234 |
+
appendLog('COMMUNITY', `Issue created locally: ${title}`);
|
| 235 |
+
} catch (error) {
|
| 236 |
+
appendLog('COMMUNITY', `Local daemon unavailable: ${error.message}`, 'warning');
|
| 237 |
+
}
|
| 238 |
+
}
|
| 239 |
+
|
| 240 |
async function digestFile(file) {
|
| 241 |
const buffer = await file.arrayBuffer();
|
| 242 |
const digest = await crypto.subtle.digest('SHA-256', buffer);
|
|
|
|
| 297 |
};
|
| 298 |
$('#evidence-input').onchange = importEvidence;
|
| 299 |
document.querySelectorAll('[data-community-tab]').forEach(button => button.onclick = () => renderCommunity(button.dataset.communityTab));
|
| 300 |
+
$('#community-add').onclick = addCommunityIssue;
|
| 301 |
+
loadCommunity();
|
| 302 |
$('#input').onkeydown = event => { if (event.key === 'Enter') sendChat(); };
|
| 303 |
}
|
| 304 |
|
community/README.md
CHANGED
|
@@ -19,4 +19,4 @@ Use Git for code history, signed commits for authorship, end-to-end encryption f
|
|
| 19 |
|
| 20 |
See `protocol.md` for the encrypted sync, relay, marketplace, and moderation contracts.
|
| 21 |
|
| 22 |
-
The Community Hub stores projects, issues, discussions, and marketplace metadata locally first. Its current UI is a private offline cache; it does not claim that peer synchronization or payments are active.
|
|
|
|
| 19 |
|
| 20 |
See `protocol.md` for the encrypted sync, relay, marketplace, and moderation contracts.
|
| 21 |
|
| 22 |
+
The Community Hub stores projects, issues, discussions, and marketplace metadata locally first through the daemon-backed `community/store.json`. Its current UI is a private offline cache; it does not claim that peer synchronization or payments are active.
|
community/store.json
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"schema_version": "1.0",
|
| 3 |
+
"sync": "local-only",
|
| 4 |
+
"projects": [
|
| 5 |
+
{"title": "AIDE Sovereign Workbench", "detail": "Local-first IDE and model harness", "boundary": "private"}
|
| 6 |
+
],
|
| 7 |
+
"issues": [],
|
| 8 |
+
"discussions": [
|
| 9 |
+
{"title": "Welcome to the local developer network", "detail": "No account, relay, or network required", "status": "local"}
|
| 10 |
+
],
|
| 11 |
+
"marketplace": [
|
| 12 |
+
{"title": "No marketplace listings yet", "detail": "Publish signed artifacts only after license and checksum review", "status": "empty"}
|
| 13 |
+
]
|
| 14 |
+
}
|
community/store.mjs
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import { promises as fs } from 'node:fs';
|
| 2 |
+
import path from 'node:path';
|
| 3 |
+
|
| 4 |
+
const TYPES = new Set(['projects', 'issues', 'discussions', 'marketplace']);
|
| 5 |
+
|
| 6 |
+
export class CommunityStore {
|
| 7 |
+
constructor(file) {
|
| 8 |
+
this.file = file;
|
| 9 |
+
this.data = { schema_version: '1.0', sync: 'local-only', projects: [], issues: [], discussions: [], marketplace: [] };
|
| 10 |
+
}
|
| 11 |
+
|
| 12 |
+
async load() {
|
| 13 |
+
this.data = JSON.parse(await fs.readFile(this.file, 'utf8'));
|
| 14 |
+
return this.data;
|
| 15 |
+
}
|
| 16 |
+
|
| 17 |
+
list() { return structuredClone(this.data); }
|
| 18 |
+
|
| 19 |
+
async add(type, item) {
|
| 20 |
+
if (!TYPES.has(type)) throw new Error('community type is not allowed');
|
| 21 |
+
const title = String(item?.title || '').trim();
|
| 22 |
+
const detail = String(item?.detail || '').trim();
|
| 23 |
+
if (!title || title.length > 160 || detail.length > 2000) throw new Error('invalid community item');
|
| 24 |
+
const entry = { title, detail, status: item.status || 'local', created_at: new Date().toISOString() };
|
| 25 |
+
this.data[type].push(entry);
|
| 26 |
+
await fs.mkdir(path.dirname(this.file), { recursive: true });
|
| 27 |
+
const temporary = `${this.file}.tmp-${process.pid}`;
|
| 28 |
+
await fs.writeFile(temporary, `${JSON.stringify(this.data, null, 2)}\n`);
|
| 29 |
+
await fs.rename(temporary, this.file);
|
| 30 |
+
return entry;
|
| 31 |
+
}
|
| 32 |
+
}
|
daemon/server.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { execFile } from 'node:child_process';
|
|
| 3 |
import { promises as fs } from 'node:fs';
|
| 4 |
import path from 'node:path';
|
| 5 |
import { ModelManager } from './model-manager.mjs';
|
|
|
|
| 6 |
|
| 7 |
const HOST = '127.0.0.1';
|
| 8 |
const PORT = Number(process.env.AIDE_DAEMON_PORT || 4777);
|
|
@@ -11,6 +12,8 @@ const MODEL_DIR = path.resolve(process.env.AIDE_MODEL_DIR || path.join(WORKSPACE
|
|
| 11 |
const MANIFEST = path.join(WORKSPACE, 'models', 'manifest.json');
|
| 12 |
const modelManager = new ModelManager({ manifestPath: MANIFEST, modelDir: MODEL_DIR, binaryPath: process.env.AIDE_LLAMA_SERVER || '/root/runtime/llama-b10333/llama-server' });
|
| 13 |
await modelManager.load().catch(() => {});
|
|
|
|
|
|
|
| 14 |
|
| 15 |
function json(response, status, body) {
|
| 16 |
const payload = JSON.stringify(body);
|
|
@@ -69,6 +72,13 @@ const server = http.createServer(async (request, response) => {
|
|
| 69 |
if (request.method === 'GET' && request.url === '/api/models/status') {
|
| 70 |
return json(response, 200, { models: modelManager.status() });
|
| 71 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 72 |
if (request.method === 'POST' && request.url === '/api/models/start') {
|
| 73 |
return json(response, 200, await modelManager.start((await body(request)).id));
|
| 74 |
}
|
|
|
|
| 3 |
import { promises as fs } from 'node:fs';
|
| 4 |
import path from 'node:path';
|
| 5 |
import { ModelManager } from './model-manager.mjs';
|
| 6 |
+
import { CommunityStore } from '../community/store.mjs';
|
| 7 |
|
| 8 |
const HOST = '127.0.0.1';
|
| 9 |
const PORT = Number(process.env.AIDE_DAEMON_PORT || 4777);
|
|
|
|
| 12 |
const MANIFEST = path.join(WORKSPACE, 'models', 'manifest.json');
|
| 13 |
const modelManager = new ModelManager({ manifestPath: MANIFEST, modelDir: MODEL_DIR, binaryPath: process.env.AIDE_LLAMA_SERVER || '/root/runtime/llama-b10333/llama-server' });
|
| 14 |
await modelManager.load().catch(() => {});
|
| 15 |
+
const communityStore = new CommunityStore(path.join(WORKSPACE, 'community', 'store.json'));
|
| 16 |
+
await communityStore.load().catch(() => {});
|
| 17 |
|
| 18 |
function json(response, status, body) {
|
| 19 |
const payload = JSON.stringify(body);
|
|
|
|
| 72 |
if (request.method === 'GET' && request.url === '/api/models/status') {
|
| 73 |
return json(response, 200, { models: modelManager.status() });
|
| 74 |
}
|
| 75 |
+
if (request.method === 'GET' && request.url === '/api/community') {
|
| 76 |
+
return json(response, 200, communityStore.list());
|
| 77 |
+
}
|
| 78 |
+
if (request.method === 'POST' && request.url === '/api/community/items') {
|
| 79 |
+
const input = await body(request);
|
| 80 |
+
return json(response, 201, { item: await communityStore.add(input.type, input.item) });
|
| 81 |
+
}
|
| 82 |
if (request.method === 'POST' && request.url === '/api/models/start') {
|
| 83 |
return json(response, 200, await modelManager.start((await body(request)).id));
|
| 84 |
}
|
daemon/test-community-store.mjs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import assert from 'node:assert/strict';
|
| 2 |
+
import { mkdtemp, writeFile, readFile, mkdir } from 'node:fs/promises';
|
| 3 |
+
import { tmpdir } from 'node:os';
|
| 4 |
+
import path from 'node:path';
|
| 5 |
+
import { CommunityStore } from '../community/store.mjs';
|
| 6 |
+
|
| 7 |
+
const root = await mkdtemp(path.join(tmpdir(), 'aide-community-'));
|
| 8 |
+
const file = path.join(root, 'community', 'store.json');
|
| 9 |
+
await mkdir(path.dirname(file), { recursive: true });
|
| 10 |
+
await writeFile(file, JSON.stringify({ schema_version: '1.0', sync: 'local-only', projects: [], issues: [], discussions: [], marketplace: [] }));
|
| 11 |
+
const store = new CommunityStore(file);
|
| 12 |
+
await store.load();
|
| 13 |
+
await store.add('issues', { title: 'Local issue', detail: 'Keep this on device.' });
|
| 14 |
+
assert.equal(store.list().issues.length, 1);
|
| 15 |
+
assert.match(await readFile(file, 'utf8'), /Local issue/);
|
| 16 |
+
await assert.rejects(store.add('unknown', { title: 'bad' }), /not allowed/);
|
| 17 |
+
console.log('community store test passed');
|
index.html
CHANGED
|
@@ -62,6 +62,8 @@
|
|
| 62 |
<button data-community-tab="discussions">DISCUSSIONS</button>
|
| 63 |
<button data-community-tab="marketplace">MARKET</button>
|
| 64 |
</div>
|
|
|
|
|
|
|
| 65 |
<div id="community-feed" style="font:9px/1.6 ui-monospace,monospace;color:#9fb8ba;margin-top:7px"></div>
|
| 66 |
</aside>
|
| 67 |
|
|
|
|
| 62 |
<button data-community-tab="discussions">DISCUSSIONS</button>
|
| 63 |
<button data-community-tab="marketplace">MARKET</button>
|
| 64 |
</div>
|
| 65 |
+
<input id="community-title" placeholder="New local item" style="width:100%;margin-top:7px;background:#09151d;border:1px solid #203342;color:#e4f2ef;padding:6px;font:9px ui-monospace,monospace">
|
| 66 |
+
<button id="community-add" class="secondary" style="width:100%;margin-top:5px;padding:6px">ADD LOCAL ISSUE</button>
|
| 67 |
<div id="community-feed" style="font:9px/1.6 ui-monospace,monospace;color:#9fb8ba;margin-top:7px"></div>
|
| 68 |
</aside>
|
| 69 |
|
package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
| 5 |
"description": "Local-first sovereign development workbench",
|
| 6 |
"type": "module",
|
| 7 |
"scripts": {
|
| 8 |
-
"test": "node tests/smoke.mjs && node harness/test-orchestrator.mjs && node daemon/test-model-manager.mjs",
|
| 9 |
"check": "node --check app.js && node --check daemon/server.mjs && node --check harness/orchestrator.mjs && node --check harness/checks.mjs",
|
| 10 |
"veritas": "node harness/run-veritas.mjs"
|
| 11 |
},
|
|
|
|
| 5 |
"description": "Local-first sovereign development workbench",
|
| 6 |
"type": "module",
|
| 7 |
"scripts": {
|
| 8 |
+
"test": "node tests/smoke.mjs && node harness/test-orchestrator.mjs && node daemon/test-model-manager.mjs && node daemon/test-community-store.mjs",
|
| 9 |
"check": "node --check app.js && node --check daemon/server.mjs && node --check harness/orchestrator.mjs && node --check harness/checks.mjs",
|
| 10 |
"veritas": "node harness/run-veritas.mjs"
|
| 11 |
},
|