170 lines
5.8 KiB
JavaScript
170 lines
5.8 KiB
JavaScript
// Hookd Relay — AI processing bridge between Chrome extension and Horus agents
|
|
const express = require('express');
|
|
const cors = require('cors');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const app = express();
|
|
app.use(cors());
|
|
app.use(express.json({ limit: '10mb' }));
|
|
|
|
const ITEMS_FILE = '/root/.openclaw/workspace/chrome-extensions/hookd/hookd-items.json';
|
|
const RELAY_PORT = 4321;
|
|
|
|
// Categories for AI classification
|
|
const CATEGORIES = ['AI', 'News', 'Ideas', 'Memes', 'Other'];
|
|
|
|
// Simple keyword-based classifier (fallback when no LLM available)
|
|
function classifyWithKeywords(text) {
|
|
const lower = text.toLowerCase();
|
|
if (/ai|gpt|llm|model|changelog|gpu|cuda|neural|openai|anthropic|gemini|claude|chatbot/.test(lower)) return 'AI';
|
|
if (/news|breaking|launch|report|announced|released|updated|version|update/.test(lower)) return 'News';
|
|
if (/idea|concept|thought|what if|imagine|inspiration| brainstorm/.test(lower)) return 'Ideas';
|
|
if (/meme|lmao|lol|funny|rofl|comedy|😂|wtf|brokie/.test(lower)) return 'Memes';
|
|
return 'Other';
|
|
}
|
|
|
|
// Score relevance 1-10 based on keywords and engagement signals
|
|
function scoreRelevance(text, item) {
|
|
let score = 5;
|
|
const lower = text.toLowerCase();
|
|
if (/important|breaking|urgent|alert/.test(lower)) score += 2;
|
|
if (/tutorial|guide|how to|explained/.test(lower)) score += 1;
|
|
if (/deal|discount|free|offer|save/.test(lower)) score += 1;
|
|
if (item.type === 'dm') score += 1;
|
|
return Math.min(10, score);
|
|
}
|
|
|
|
// --- Routes ---
|
|
|
|
// Health / status
|
|
app.get('/status', (req, res) => {
|
|
res.json({ ok: true, relay: 'hookd', timestamp: new Date().toISOString() });
|
|
});
|
|
|
|
// Get all items (for Horus agents)
|
|
app.get('/items', (req, res) => {
|
|
try {
|
|
if (!fs.existsSync(ITEMS_FILE)) return res.json({ items: [] });
|
|
const raw = fs.readFileSync(ITEMS_FILE, 'utf8');
|
|
res.json({ items: JSON.parse(raw) });
|
|
} catch (e) {
|
|
res.status(500).json({ error: e.message });
|
|
}
|
|
});
|
|
|
|
// Save items from extension
|
|
app.post('/items', (req, res) => {
|
|
try {
|
|
let existing = [];
|
|
if (fs.existsSync(ITEMS_FILE)) {
|
|
existing = JSON.parse(fs.readFileSync(ITEMS_FILE, 'utf8'));
|
|
}
|
|
const newItems = req.body.items || [];
|
|
newItems.forEach(item => {
|
|
item.id = item.id || Date.now();
|
|
item.saved = true;
|
|
item.time = item.time || new Date().toISOString();
|
|
existing.unshift(item);
|
|
});
|
|
fs.writeFileSync(ITEMS_FILE, JSON.stringify(existing, null, 2));
|
|
res.json({ success: true, count: newItems.length, total: existing.length });
|
|
} catch (e) {
|
|
res.status(500).json({ error: e.message });
|
|
}
|
|
});
|
|
|
|
// Process items with AI (keyword-based + hook into Horus if available)
|
|
app.post('/process', async (req, res) => {
|
|
try {
|
|
const { items } = req.body;
|
|
if (!items || !Array.isArray(items)) return res.status(400).json({ error: 'items array required' });
|
|
|
|
const results = [];
|
|
|
|
// Try to call Horus agent for AI analysis
|
|
let agentAnalysis = null;
|
|
try {
|
|
// Call Horus via local Hermes CLI
|
|
const { execSync } = require('child_process');
|
|
const content = items.map(i => `[${i.id}] @${i.author}: ${i.content || ''}`).join('\n');
|
|
const analysis = execSync(
|
|
`cd /root/.hermes && echo '${content.replace(/'/g, "'\\''")}' | timeout 20 hermes --model claude-sonnet-4 --no-stream "Analyze these X posts and for each one respond with ONLY valid JSON: {id, score(1-10), category(AI/News/Ideas/Memes/Other), summary, tags}. Items: ${content}" 2>/dev/null`,
|
|
{ timeout: 25000 }
|
|
).toString();
|
|
agentAnalysis = JSON.parse(analysis);
|
|
} catch {
|
|
// Agent not available or parse failed — fall back to keywords
|
|
}
|
|
|
|
items.forEach(item => {
|
|
const text = `${item.content || ''} ${item.author || ''}`;
|
|
let result;
|
|
|
|
if (agentAnalysis && agentAnalysis.find(a => a.id === item.id)) {
|
|
result = agentAnalysis.find(a => a.id === item.id);
|
|
} else {
|
|
const category = classifyWithKeywords(text);
|
|
const score = scoreRelevance(text, item);
|
|
result = {
|
|
id: item.id,
|
|
category,
|
|
score,
|
|
summary: text.slice(0, 80) + '...',
|
|
tags: [category, score >= 7 ? 'high-value' : 'normal']
|
|
};
|
|
}
|
|
|
|
results.push(result);
|
|
});
|
|
|
|
// Also save processed items to hookd-items.json
|
|
if (fs.existsSync(ITEMS_FILE)) {
|
|
const stored = JSON.parse(fs.readFileSync(ITEMS_FILE, 'utf8'));
|
|
const updated = stored.map(item => {
|
|
const processed = results.find(r => r.id === item.id);
|
|
if (processed) {
|
|
return { ...item, aiProcessed: true, ...processed };
|
|
}
|
|
return item;
|
|
});
|
|
fs.writeFileSync(ITEMS_FILE, JSON.stringify(updated, null, 2));
|
|
}
|
|
|
|
res.json({ success: true, results });
|
|
} catch (e) {
|
|
res.status(500).json({ error: e.message });
|
|
}
|
|
});
|
|
|
|
// Delete items
|
|
app.delete('/items', (req, res) => {
|
|
try {
|
|
const { ids } = req.body;
|
|
if (!fs.existsSync(ITEMS_FILE)) return res.json({ success: true, deleted: 0 });
|
|
let items = JSON.parse(fs.readFileSync(ITEMS_FILE, 'utf8'));
|
|
const before = items.length;
|
|
items = items.filter(i => !ids.includes(String(i.id)));
|
|
fs.writeFileSync(ITEMS_FILE, JSON.stringify(items, null, 2));
|
|
res.json({ success: true, deleted: before - items.length });
|
|
} catch (e) {
|
|
res.status(500).json({ error: e.message });
|
|
}
|
|
});
|
|
|
|
// Export items as JSON (for download)
|
|
app.get('/export', (req, res) => {
|
|
try {
|
|
if (!fs.existsSync(ITEMS_FILE)) return res.json({ items: [] });
|
|
const items = JSON.parse(fs.readFileSync(ITEMS_FILE, 'utf8'));
|
|
res.setHeader('Content-Disposition', 'attachment; filename=hookd-export.json');
|
|
res.json({ items });
|
|
} catch (e) {
|
|
res.status(500).json({ error: e.message });
|
|
}
|
|
});
|
|
|
|
app.listen(RELAY_PORT, '127.0.0.1', () => {
|
|
console.log(`Hookd Relay running on http://127.0.0.1:${RELAY_PORT}`);
|
|
});
|