diff --git a/index.html b/index.html index e7e11e9..009c5a2 100644 --- a/index.html +++ b/index.html @@ -7,7 +7,7 @@ name="description" content="amerc is an agent mercenary layer for embedding, bringing, and hosting agents across vertical software boundaries." /> - + diff --git a/package.json b/package.json index 3fb2a44..61e10b2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "amerc-site", - "version": "0.30.0-landing", + "version": "0.31.0-activity", "private": true, "type": "module", "scripts": { diff --git a/server/amerc-api.mjs b/server/amerc-api.mjs index 468559e..ba41969 100644 --- a/server/amerc-api.mjs +++ b/server/amerc-api.mjs @@ -349,6 +349,21 @@ const server = http.createServer(async (req, res) => { return send(res, 200, { ok: true, classes, instances: insts.length, online, sessions, owners, tags }); } + // live activity feed (public, derived from existing tables) + if (path === '/agents/activity' && method === 'GET') { + const items = []; + for (const c of db.prepare("SELECT name, owner, created_at FROM agent_classes WHERE visibility='public' ORDER BY created_at DESC LIMIT 8").all()) + items.push({ kind: 'class', text: `${c.owner} published “${c.name}”`, at: c.created_at }); + for (const i of db.prepare("SELECT i.created_at, i.owner, c.name cn FROM agent_instances i JOIN agent_classes c ON c.id=i.class_id WHERE i.visibility='public' ORDER BY i.created_at DESC LIMIT 8").all()) + items.push({ kind: 'instance', text: `${i.owner} ran an instance of “${i.cn}”`, at: i.created_at }); + for (const s of db.prepare("SELECT s.started_at, c.name cn FROM agent_sessions s LEFT JOIN agent_classes c ON c.id=s.class_id ORDER BY s.started_at DESC LIMIT 8").all()) + items.push({ kind: 'session', text: `a session started on “${s.cn || 'an agent'}”`, at: s.started_at }); + for (const sc of db.prepare('SELECT owner, host, created_at FROM showcases ORDER BY created_at DESC LIMIT 5').all()) + items.push({ kind: 'showcase', text: `${sc.owner} opened a showcase ${sc.host}`, at: sc.created_at }); + items.sort((a, b) => b.at - a.at); + return send(res, 200, { ok: true, activity: items.slice(0, 12) }); + } + // browse classes (public + own private) if (path === '/agents/classes' && method === 'GET') { const tag = url.searchParams.get('tag'); const q = (url.searchParams.get('q') || '').toLowerCase(); const mine = url.searchParams.get('mine'); diff --git a/src/Scene2D.jsx b/src/Scene2D.jsx index 9a48934..a70b063 100644 --- a/src/Scene2D.jsx +++ b/src/Scene2D.jsx @@ -6,7 +6,7 @@ import Mansion from './Mansion.jsx'; import TavernGame from './TavernGame.jsx'; import { api } from './api.js'; -const RELEASE = '0.30.0-landing'; +const RELEASE = '0.31.0-activity'; const NAV = [ { id: 'home', label: 'Tavern' }, @@ -102,6 +102,23 @@ const HF_USES = [ { icon: '🖥️', title: 'Web terminal', body: 'Open a relayed PTY to the agent’s terminal — watch and drive it right from the browser.' }, { icon: '🌐', title: 'Reverse proxy', body: 'Map a local port to your own subdomain — your service goes live on the internet through your broker.' }, ]; +function relTime(at) { const s = Math.floor((Date.now() - at) / 1000); if (s < 60) return `${s}s ago`; const m = Math.floor(s / 60); if (m < 60) return `${m}m ago`; const h = Math.floor(m / 60); if (h < 24) return `${h}h ago`; return `${Math.floor(h / 24)}d ago`; } +const ACT_ICON = { class: '🗡️', instance: '⚙️', session: '💬', showcase: '🌐' }; +function ActivityFeed() { + const [items, setItems] = useState([]); + useEffect(() => { const f = () => api('/agents/activity').then((d) => setItems(d.activity || [])).catch(() => {}); f(); const t = setInterval(f, 15000); return () => clearInterval(t); }, []); + if (!items.length) return null; + return ( +
{u.body}