From 026d0c8d6b452def99e013e7d37bebcca49c5556 Mon Sep 17 00:00:00 2001 From: MayaTheShy Date: Sun, 22 Mar 2026 04:10:11 -0400 Subject: [PATCH] Add Cross-Project Integration API for RemoteTurtle system --- web/server/server.js | 75 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/web/server/server.js b/web/server/server.js index 262d159..06533f2 100644 --- a/web/server/server.js +++ b/web/server/server.js @@ -966,6 +966,78 @@ wss.on('close', () => { clearInterval(WS_PING_INTERVAL); }); +// ========== Cross-Project Integration API ========== +// These endpoints allow the RemoteTurtle system to query inventory state + +const TURTLE_SERVER_URL = process.env.TURTLE_SERVER_URL || ''; // e.g. http://turtle-server:3001 + +// Find where a specific item is stored (for turtles to pick up items) +app.get('/api/integration/locate-item', (req, res) => { + const { name, minCount } = req.query; + if (!name) return res.status(400).json({ error: 'Item name required (?name=minecraft:diamond)' }); + + const items = inventoryState.itemList || []; + const match = items.find(i => i.name === name); + if (!match || match.count < (parseInt(minCount) || 1)) { + return res.json({ found: false, available: match ? match.count : 0 }); + } + res.json({ found: true, name: match.name, count: match.count, displayName: match.displayName }); +}); + +// Search items by partial name (for turtle autocomplete/fuzzy matching) +app.get('/api/integration/search-items', (req, res) => { + const { q, limit } = req.query; + if (!q) return res.status(400).json({ error: 'Search query required (?q=diamond)' }); + + const items = inventoryState.itemList || []; + const query = q.toLowerCase(); + const results = items + .filter(i => i.name.toLowerCase().includes(query) || (i.displayName || '').toLowerCase().includes(query)) + .sort((a, b) => b.count - a.count) + .slice(0, parseInt(limit) || 20); + + res.json({ results }); +}); + +// Get storage summary (available space, total items) for turtle decision-making +app.get('/api/integration/storage-status', (req, res) => { + res.json({ + grandTotal: inventoryState.grandTotal || 0, + chestCount: inventoryState.chestCount || 0, + totalSlots: inventoryState.totalSlots || 0, + usedSlots: inventoryState.usedSlots || 0, + freeSlots: (inventoryState.totalSlots || 0) - (inventoryState.usedSlots || 0), + lastUpdate, + bridgeConnected: bridgeClients.size > 0, + }); +}); + +// Get full item list (for turtle dump target selection) +app.get('/api/integration/items', (req, res) => { + const items = inventoryState.itemList || []; + res.json({ items: items.map(i => ({ name: i.name, count: i.count })) }); +}); + +// Get alerts (so turtles know what items are running low) +app.get('/api/integration/low-stock', (req, res) => { + const triggered = (alertsState || []).filter(a => a.triggered !== false); + res.json({ alerts: triggered }); +}); + +// Proxy to turtle server for combined dashboard info +app.get('/api/integration/turtle-status', async (req, res) => { + if (!TURTLE_SERVER_URL) { + return res.json({ configured: false, message: 'TURTLE_SERVER_URL not configured' }); + } + try { + const resp = await fetch(`${TURTLE_SERVER_URL}/api/turtles`); + const data = await resp.json(); + res.json({ configured: true, ...data }); + } catch (err) { + res.status(502).json({ configured: true, error: `Cannot reach turtle server: ${err.message}` }); + } +}); + // ========== Start Server ========== server.listen(PORT, HOST, () => { @@ -973,6 +1045,9 @@ server.listen(PORT, HOST, () => { console.log(`\nBridge HTTP endpoint: http://localhost:${PORT}/api/bridge/state`); console.log(`Bridge WebSocket: ws://localhost:${PORT}/ws/bridge`); console.log(`Web client WebSocket: ws://localhost:${PORT}/ws`); + if (TURTLE_SERVER_URL) { + console.log(`🐢 Turtle server integration: ${TURTLE_SERVER_URL}`); + } }); // Graceful shutdown