SiteMente - AI-Powered Lead Generation Platform

Features:
- Mission Control dashboard
- HP Submissions tracking
- AI Agents integration
- Lead management CRM
- Marketing email templates
- Chrome extension support

Tech: Next.js, TypeScript, Tailwind CSS, MySQL
This commit is contained in:
2026-03-19 17:38:12 +01:00
parent 9981d2a9d2
commit d5575b58e3
34 changed files with 3061 additions and 22 deletions
Binary file not shown.
+78
View File
@@ -0,0 +1,78 @@
# Hookd - Chrome Extension
Save, organize and AI-sort your X (Twitter) content.
## Features
- 📥 **DM Organizer** - Extract and categorize DMs
- 🐦 **Feed Saver** - Checkmark posts to save in bulk
- 🏷️ **Custom Categories** - AI, News, Ideas, Memes, Other
- 🤖 **AI Processing Ready** - Connect to OpenClaw for AI analysis
- 👥 **Contact Lists** - Quick share to friends/family lists
- 💾 **Export** - Save your data anytime
## Installation
1. Open Chrome and go to `chrome://extensions/`
2. Enable "Developer mode" (toggle in top right)
3. Click "Load unpacked"
4. Select the `hookd` folder
5. Click the extension icon and pin it to toolbar
## OpenClaw Integration
To enable AI processing:
1. Install OpenClaw Browser Relay extension
2. Configure connection in extension settings (⚙️ tab)
3. AI will automatically:
- Read saved content
- Suggest categories
- Score by relevance
- Delete spam
## Categories
- 💡 **AI** - AI tools, news, developments
- 📰 **News** - Breaking news, trends
- 💡 **Ideas** - Inspiration, ideas, concepts
- 😂 **Memes** - Fun content
- 📁 **Other** - Uncategorized
## Usage
### Saving DMs
1. Open X DMs
2. Hover over a message
3. Click 📌 to save
### Saving Feed Posts
1. Open X home/timeline
2. Hover over any tweet
3. Click the ○ checkbox on left
4. Select multiple tweets
5. Click "Save to Hookd" in floating bar
### Viewing Saved
1. Click Hookd extension icon
2. Go to 💾 Saved tab
### AI Processing
1. Go to 🤖 AI tab
2. Click "Process All with AI"
3. AI will analyze and categorize
## Files
- `manifest.json` - Extension config
- `popup.html/js` - Popup UI
- `background.js` - Service worker
- `content.js/css` - X.com page interaction
## TODO
- [ ] Connect to OpenClaw Browser Relay
- [ ] Add X API integration (alternative to scraping)
- [ ] FB/IG support
- [ ] Cloud sync
- [ ] Mobile companion
+159
View File
@@ -0,0 +1,159 @@
// Service Worker - Hookd Extension
const STORAGE_KEY = 'hookd_items';
const SETTINGS_KEY = 'hookd_settings';
const LISTS_KEY = 'hookd_lists';
const defaultSettings = {
categories: ['AI', 'News', 'Ideas', 'Memes', 'Other'],
userHandle: 'HaithamEKhalifa',
relayEnabled: true
};
const defaultLists = [
{ id: 'dev', name: 'Dev Friends', emoji: '👨‍💻', contacts: [] },
{ id: 'memes', name: 'Meme Buddies', emoji: '😂', contacts: [] },
{ id: 'business', name: 'Business', emoji: '💼', contacts: [] },
{ id: 'family', name: 'Family', emoji: '👨‍👩‍👧', contacts: [] }
];
// Initialize
chrome.runtime.onInstalled.addListener(() => {
console.log('Hookd installed');
chrome.storage.local.set({
[STORAGE_KEY]: [],
[SETTINGS_KEY]: defaultSettings,
[LISTS_KEY]: defaultLists
});
});
// Handle messages
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
// Get items
if (message.type === 'GET_ITEMS') {
chrome.storage.local.get([STORAGE_KEY], (r) => sendResponse(r[STORAGE_KEY] || []));
return true;
}
// Get lists
if (message.type === 'GET_LISTS') {
chrome.storage.local.get([LISTS_KEY], (r) => sendResponse(r[LISTS_KEY] || defaultLists));
return true;
}
// Add contact to list
if (message.type === 'ADD_CONTACT') {
chrome.storage.local.get([LISTS_KEY], (r) => {
const lists = r[LISTS_KEY] || defaultLists;
const list = lists.find(l => l.id === message.listId);
if (list && !list.contacts.find(c => c.username === message.contact.username)) {
list.contacts.push(message.contact);
chrome.storage.local.set({ [LISTS_KEY]: lists });
}
sendResponse({ success: true });
});
return true;
}
// Remove contact from list
if (message.type === 'REMOVE_CONTACT') {
chrome.storage.local.get([LISTS_KEY], (r) => {
const lists = r[LISTS_KEY] || defaultLists;
const list = lists.find(l => l.id === message.listId);
if (list) {
list.contacts = list.contacts.filter(c => c.username !== message.username);
chrome.storage.local.set({ [LISTS_KEY]: lists });
}
sendResponse({ success: true });
});
return true;
}
// Create new list
if (message.type === 'CREATE_LIST') {
chrome.storage.local.get([LISTS_KEY], (r) => {
const lists = r[LISTS_KEY] || defaultLists;
lists.push({
id: Date.now().toString(),
name: message.name,
emoji: message.emoji || '📌',
contacts: []
});
chrome.storage.local.set({ [LISTS_KEY]: lists });
sendResponse({ success: true });
});
return true;
}
// Delete list
if (message.type === 'DELETE_LIST') {
chrome.storage.local.get([LISTS_KEY], (r) => {
let lists = r[LISTS_KEY] || defaultLists;
lists = lists.filter(l => l.id !== message.listId);
chrome.storage.local.set({ [LISTS_KEY]: lists });
sendResponse({ success: true });
});
return true;
}
// Share item to list
if (message.type === 'SHARE_TO_LIST') {
chrome.storage.local.get([STORAGE_KEY], (r) => {
const items = r[STORAGE_KEY] || [];
// Mark item as shared
const item = items.find(i => i.id === message.itemId);
if (item) {
item.sharedTo = item.sharedTo || [];
item.sharedTo.push(message.listId);
chrome.storage.local.set({ [STORAGE_KEY]: items });
}
sendResponse({ success: true });
});
return true;
}
// Save item
if (message.type === 'SAVE_ITEM') {
chrome.storage.local.get([STORAGE_KEY], (r) => {
const items = r[STORAGE_KEY] || [];
const newItem = {
id: Date.now(),
...message.data,
type: message.data.source || 'dm',
saved: false,
aiProcessed: false,
sharedTo: [],
time: new Date().toISOString()
};
items.unshift(newItem);
chrome.storage.local.set({ [STORAGE_KEY]: items });
sendResponse({ success: true, item: newItem });
});
return true;
}
// Delete items
if (message.type === 'DELETE_ITEMS') {
chrome.storage.local.get([STORAGE_KEY], (r) => {
const items = r[STORAGE_KEY] || [];
const remaining = items.filter(i => !message.ids.includes(i.id));
chrome.storage.local.set({ [STORAGE_KEY]: remaining });
sendResponse({ success: true, deleted: message.ids.length });
});
return true;
}
// Update item
if (message.type === 'UPDATE_ITEM') {
chrome.storage.local.get([STORAGE_KEY], (r) => {
const items = r[STORAGE_KEY] || [];
const idx = items.findIndex(i => i.id === message.data.id);
if (idx !== -1) {
items[idx] = { ...items[idx], ...message.data };
chrome.storage.local.set({ [STORAGE_KEY]: items });
}
sendResponse({ success: true });
});
return true;
}
});
+28
View File
@@ -0,0 +1,28 @@
/* Content script styles for X.com */
/* Hover effect on tweets */
[data-testid="tweet"]:hover .hookd-checkbox {
display: flex !important;
}
/* Save button hover */
.hookd-save-btn:hover {
opacity: 1 !important;
transform: scale(1.1);
}
/* Notification animation */
@keyframes hookd-slide-in {
from {
transform: translateY(100%);
opacity: 0;
}
to {
transform: translateY(0);
opacity: 1;
}
}
#hookd-floating-bar {
animation: hookd-slide-in 0.3s ease-out;
}
+190
View File
@@ -0,0 +1,190 @@
// Content Script - runs on X.com
// Adds "Save to Hookd" option to tweet/post menu
console.log('Hookd content script loaded');
// Listen for messages from background
chrome.runtime.onMessage.addListener((msg, sender, response) => {
if (msg.type === 'GET_SAVED') {
response([]);
}
});
// Add "Save to Hookd" to X's menu
function addHookdOption() {
// Find all "more" buttons (the ... button on tweets/posts)
document.querySelectorAll('[data-testid="tweet"] [role="button"], [data-testid="tweet"] [aria-label*="more"], [data-testid="tweet"] [aria-label*="More"]').forEach(menuBtn => {
// Check if we already added our option
if (menuBtn.closest('[data-testid="tweet"]')?.querySelector('.hookd-menu-option')) continue;
const tweet = menuBtn.closest('[data-testid="tweet"]');
if (!tweet) return;
// Create save button
const saveBtn = document.createElement('div');
saveBtn.className = 'hookd-menu-option';
saveBtn.style.cssText = `
padding: 12px 16px;
cursor: pointer;
display: flex;
align-items: center;
gap: 12px;
font-size: 15px;
color: #fff;
`;
saveBtn.innerHTML = '<span style="font-size:18px;">📌</span><span>Save to Hookd</span>';
saveBtn.addEventListener('click', (e) => {
e.stopPropagation();
saveTweet(tweet);
});
// Find the dropdown menu and append
const dropdown = menuBtn.closest('[role="menu"]') || menuBtn.closest('div[aria-labelledby]');
if (dropdown && !dropdown.querySelector('.hookd-menu-option')) {
dropdown.appendChild(saveBtn);
}
});
// Also try to find DM more options
document.querySelectorAll('[data-testid="DMMessage"] [role="button"]').forEach(btn => {
if (btn.textContent.includes('more') || btn.getAttribute('aria-label')?.includes('more')) {
const dm = btn.closest('[data-testid="DMMessage"]');
if (dm && !dm.querySelector('.hookd-menu-option')) {
const saveBtn = document.createElement('div');
saveBtn.className = 'hookd-menu-option';
saveBtn.style.cssText = `
padding: 8px 12px;
cursor: pointer;
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
`;
saveBtn.innerHTML = '📌 Save to Hookd';
saveBtn.addEventListener('click', (e) => {
e.stopPropagation();
saveDM(dm);
});
// Insert after the clickable area
btn.parentElement?.appendChild(saveBtn);
}
}
});
}
// Save tweet
function saveTweet(tweetElement) {
try {
// Extract author
const authorEl = tweetElement.querySelector('[data-testid="User-Name"] span a[role="link"]') ||
tweetElement.querySelector('a[tabindex="-1"]');
const author = authorEl?.textContent?.trim()?.replace('@', '') || 'unknown';
// Extract content
const contentEl = tweetElement.querySelector('[data-testid="tweetText"]');
const content = contentEl?.textContent?.trim() || '';
// Extract link
const linkEl = tweetElement.querySelector('a[href*="/status/"]');
const link = linkEl?.href || '';
const item = {
id: Date.now(),
author: author,
content: content.substring(0, 500),
link: link,
source: 'tweet',
time: new Date().toISOString()
};
// Save to storage via background
chrome.runtime.sendMessage({
type: 'SAVE_ITEM',
data: item
}, (response) => {
if (response?.success) {
showNotification('Saved to Hookd!');
} else {
showNotification('Failed to save');
}
});
} catch (err) {
console.error('Hookd save error:', err);
showNotification('Error saving');
}
}
// Save DM
function saveDM(dmElement) {
try {
const contentEl = dmElement.querySelector('[data-testid="dmMessageContent"]');
const content = contentEl?.textContent?.trim() || '';
const item = {
id: Date.now(),
author: 'DM',
content: content.substring(0, 500),
source: 'dm',
time: new Date().toISOString()
};
chrome.runtime.sendMessage({
type: 'SAVE_ITEM',
data: item
}, (response) => {
if (response?.success) {
showNotification('Saved to Hookd!');
} else {
showNotification('Failed to save');
}
});
} catch (err) {
console.error('Hookd save error:', err);
}
}
// Show notification
function showNotification(message) {
const notif = document.createElement('div');
notif.style.cssText = `
position: fixed;
bottom: 20px;
right: 20px;
background: #4F46E5;
color: white;
padding: 12px 20px;
border-radius: 8px;
font-family: Arial, sans-serif;
font-size: 14px;
z-index: 99999;
box-shadow: 0 4px 12px rgba(0,0,0,0.3);
`;
notif.textContent = message;
document.body.appendChild(notif);
setTimeout(() => {
notif.style.opacity = '0';
notif.style.transition = 'opacity 0.3s';
setTimeout(() => notif.remove(), 300);
}, 2000);
}
// Watch for new elements
const observer = new MutationObserver(() => {
addHookdOption();
});
// Start observing
observer.observe(document.body, {
childList: true,
subtree: true
});
// Initial scan
setTimeout(addHookdOption, 1000);
setTimeout(addHookdOption, 3000);
console.log('Hookd content script initialized');
Binary file not shown.

After

Width:  |  Height:  |  Size: 552 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 121 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 272 B

+11
View File
@@ -0,0 +1,11 @@
{
"manifest_version": 3,
"name": "Hookd",
"version": "1.1.0",
"description": "Save, organize and AI-sort your X content",
"permissions": ["storage"],
"host_permissions": ["https://x.com/*", "https://twitter.com/*"],
"action": {
"default_popup": "popup.html"
}
}
+73
View File
@@ -0,0 +1,73 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Hookd</title>
<style>
body { width: 320px; min-height: 450px; font-family: Arial, sans-serif; background: #0f172a; color: #fff; margin: 0; padding: 0; }
.header { background: #4F46E5; padding: 14px; text-align: center; }
.header h1 { margin: 0; font-size: 18px; }
.tabs { display: flex; background: #1e293b; }
.tab { flex: 1; padding: 12px 8px; text-align: center; cursor: pointer; font-size: 11px; border-bottom: 2px solid #334155; }
.tab:hover { background: #334155; }
.tab.active { border-bottom-color: #4F46E5; background: #1e293b; }
.content { padding: 14px; }
.tab-content { display: none; }
.tab-content.active { display: block; }
h3 { margin: 0 0 12px 0; font-size: 13px; color: #94a3b8; }
.box { background: #1e293b; border-radius: 8px; padding: 12px; margin-bottom: 8px; }
.box-list { background: #1e293b; border-radius: 8px; padding: 10px; margin-bottom: 6px; display: flex; align-items: center; justify-content: space-between; }
.btn { background: #4F46E5; color: white; border: none; padding: 10px 16px; border-radius: 6px; cursor: pointer; width: 100%; margin-top: 8px; font-size: 12px; }
.btn-sm { padding: 6px 12px; width: auto; }
.btn:hover { background: #5a82f0; }
.btn-danger { background: #dc2626; }
.btn-danger:hover { background: #ef4444; }
input { width: 100%; padding: 10px; background: #0f172a; border: 1px solid #334155; border-radius: 6px; color: white; margin-bottom: 8px; font-size: 12px; box-sizing: border-box; }
input::placeholder { color: #64748b; }
.delete-btn { background: none; border: none; color: #dc2626; cursor: pointer; font-size: 16px; padding: 4px 8px; }
.empty { text-align: center; padding: 30px; color: #64748b; font-size: 12px; }
.add-form { display: flex; gap: 6px; margin-top: 10px; }
.add-form input { margin: 0; flex: 1; }
</style>
</head>
<body>
<div class="header"><h1>Hookd</h1></div>
<div class="tabs">
<div class="tab active" id="tab-inbox">INBOX</div>
<div class="tab" id="tab-lists">LISTS</div>
<div class="tab" id="tab-settings">SET</div>
</div>
<div class="content">
<div id="inbox" class="tab-content active">
<h3>INBOX <span id="inbox-count">(0)</span></h3>
<div id="inbox-list"></div>
<div class="empty" id="inbox-empty">No items.</div>
<button class="btn btn-sm" id="btn-test-add">+ Test Add Item</button>
</div>
<div id="lists" class="tab-content">
<h3>LISTS</h3>
<div id="lists-container"></div>
<div class="add-form">
<input type="text" id="new-list-name" placeholder="New list name...">
<button class="btn btn-sm" id="btn-create-list">+</button>
</div>
</div>
<div id="settings" class="tab-content">
<h3>SETTINGS</h3>
<div class="box">
<input type="text" id="setting-handle" placeholder="@username">
<button class="btn" id="btn-save-handle">SAVE HANDLE</button>
</div>
<div class="box" style="margin-top:12px;">
<button class="btn btn-danger" id="btn-clear-all">CLEAR ALL DATA</button>
</div>
</div>
</div>
<script src="popup.js"></script>
</body>
</html>
+180
View File
@@ -0,0 +1,180 @@
// State
var items = [];
var lists = [
{ id: '1', name: 'Dev Friends', contacts: [] },
{ id: '2', name: 'Meme Buddies', contacts: [] },
{ id: '3', name: 'Business', contacts: [] }
];
// Load data
function loadData() {
chrome.storage.local.get(['hookd_items', 'hookd_lists'], function(result) {
items = result.hookd_items || [];
lists = result.hookd_lists || [
{ id: '1', name: 'Dev Friends', contacts: [] },
{ id: '2', name: 'Meme Buddies', contacts: [] },
{ id: '3', name: 'Business', contacts: [] }
];
renderAll();
});
}
// Save items
function saveItems() {
chrome.storage.local.set({ hookd_items: items });
}
// Render all
function renderAll() {
renderInbox();
renderLists();
loadHandle();
}
// Tab switching
function switchTab(tabId) {
var contents = document.querySelectorAll('.tab-content');
for (var i = 0; i < contents.length; i++) {
contents[i].classList.remove('active');
}
var tabs = document.querySelectorAll('.tab');
for (var i = 0; i < tabs.length; i++) {
tabs[i].classList.remove('active');
}
document.getElementById(tabId).classList.add('active');
document.getElementById('tab-' + tabId).classList.add('active');
}
// Render inbox
function renderInbox() {
document.getElementById('inbox-count').textContent = '(' + items.length + ')';
var list = document.getElementById('inbox-list');
var empty = document.getElementById('inbox-empty');
if (items.length === 0) {
list.innerHTML = '';
empty.style.display = 'block';
} else {
empty.style.display = 'none';
var html = '';
for (var i = 0; i < items.length; i++) {
html += '<div class="box" style="position:relative;">' +
'<div style="font-size:10px;color:#64748b;margin-bottom:4px;">@' + (items[i].author || 'unknown') + '</div>' +
'<div style="font-size:12px;">' + ((items[i].content || 'No content').substring(0, 60)) + '</div>' +
'<button class="delete-btn" data-index="' + i + '">x</button>' +
'</div>';
}
list.innerHTML = html;
// Add delete handlers
var deleteBtns = list.querySelectorAll('.delete-btn');
for (var i = 0; i < deleteBtns.length; i++) {
deleteBtns[i].addEventListener('click', function() {
var idx = parseInt(this.getAttribute('data-index'));
items.splice(idx, 1);
saveItems();
renderInbox();
});
}
}
}
// Test add item
function testAddItem() {
items.unshift({
id: Date.now(),
author: 'TestUser',
content: 'Test item saved!',
time: new Date().toISOString()
});
saveItems();
renderInbox();
}
// Render lists
function renderLists() {
var container = document.getElementById('lists-container');
var html = '';
for (var i = 0; i < lists.length; i++) {
html += '<div class="box-list">' +
'<span>' + lists[i].name + ' (' + lists[i].contacts.length + ')</span>' +
'<button class="delete-btn" data-list-index="' + i + '">x</button>' +
'</div>';
}
container.innerHTML = html;
// Add delete handlers
var deleteBtns = container.querySelectorAll('.delete-btn');
for (var i = 0; i < deleteBtns.length; i++) {
deleteBtns[i].addEventListener('click', function() {
var idx = parseInt(this.getAttribute('data-list-index'));
lists.splice(idx, 1);
chrome.storage.local.set({ hookd_lists: lists });
renderLists();
});
}
}
// Create list
function createList() {
var input = document.getElementById('new-list-name');
var name = input.value.trim();
if (!name) return;
lists.push({
id: String(Date.now()),
name: name,
contacts: []
});
chrome.storage.local.set({ hookd_lists: lists });
input.value = '';
renderLists();
}
// Load handle
function loadHandle() {
chrome.storage.local.get(['hookd_handle'], function(result) {
if (result.hookd_handle) {
document.getElementById('setting-handle').value = result.hookd_handle;
}
});
}
// Save handle
function saveHandle() {
var handle = document.getElementById('setting-handle').value.trim();
if (!handle) return;
chrome.storage.local.set({ hookd_handle: handle });
alert('Saved: ' + handle);
}
// Clear all
function clearAll() {
if (!confirm('Delete ALL data?')) return;
items = [];
lists = [
{ id: '1', name: 'Dev Friends', contacts: [] },
{ id: '2', name: 'Meme Buddies', contacts: [] },
{ id: '3', name: 'Business', contacts: [] }
];
chrome.storage.local.set({ hookd_items: [], hookd_lists: lists });
renderAll();
alert('All data cleared');
}
// Setup
document.addEventListener('DOMContentLoaded', function() {
// Tab clicks
document.getElementById('tab-inbox').addEventListener('click', function() { switchTab('inbox'); });
document.getElementById('tab-lists').addEventListener('click', function() { switchTab('lists'); });
document.getElementById('tab-settings').addEventListener('click', function() { switchTab('settings'); });
// Buttons
document.getElementById('btn-test-add').addEventListener('click', testAddItem);
document.getElementById('btn-create-list').addEventListener('click', createList);
document.getElementById('btn-save-handle').addEventListener('click', saveHandle);
document.getElementById('btn-clear-all').addEventListener('click', clearAll);
// Load data
loadData();
});
Binary file not shown.
BIN
View File
Binary file not shown.