Ferrell Synthetic Intelligence commited on
Commit
e7d4595
·
1 Parent(s): dd6c84a

Persist community hub through local daemon

Browse files
app.js CHANGED
@@ -89,12 +89,7 @@ async function importModel(model) {
89
  selectModel(model);
90
  };
91
 
92
- const communitySeed = {
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 = communitySeed[tab] || [];
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
- renderCommunity();
 
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
  },