Add gmail-agent and new whatsapp-gateway

This commit is contained in:
Antigravity 2026-06-13 15:23:54 -04:00
parent 6d72f461dd
commit 4fbfdf13e7
238 changed files with 12813 additions and 84 deletions

View File

@ -0,0 +1,78 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: gmail-agent
namespace: ai-agents
labels:
app: gmail-agent
spec:
replicas: 1
selector:
matchLabels:
app: gmail-agent
template:
metadata:
labels:
app: gmail-agent
spec:
initContainers:
- name: git-clone
image: alpine/git
command:
- git
- clone
- http://192.168.8.248:3000/deepkoluguri/agentic-os.git
- /app
volumeMounts:
- name: app-volume
mountPath: /app
containers:
- name: gmail-agent
image: node:18-alpine
workingDir: /app/gmail-agent
command:
- sh
- -c
- npm install --omit=dev && node index.js
env:
- name: OLLAMA_URL
value: http://ollama.ai-agents.svc.cluster.local:11434/api/generate
- name: WHATSAPP_GATEWAY_URL
value: http://whatsapp-gateway.ai-agents.svc.cluster.local:5001/api/send-message
- name: WHATSAPP_TO
value: "+14085505485"
- name: OLLAMA_MODEL
value: "qwen2.5:0.5b"
volumeMounts:
- name: app-volume
mountPath: /app
- name: credentials-volume
mountPath: /app/gmail-agent/credentials.json
subPath: credentials.json
readOnly: true
- name: token-volume
mountPath: /app/gmail-agent/token.json
subPath: token.json
readOnly: true
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
volumes:
- name: app-volume
emptyDir: {}
- name: credentials-volume
secret:
secretName: gmail-agent-auth
items:
- key: credentials.json
path: credentials.json
- name: token-volume
secret:
secretName: gmail-agent-auth
items:
- key: token.json
path: token.json

5
gmail-agent/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
node_modules
token.json
credentials.json
.env
.DS_Store

16
gmail-agent/Dockerfile Normal file
View File

@ -0,0 +1,16 @@
FROM node:18-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev
COPY . .
# Expected Env Vars
ENV OLLAMA_URL=http://ollama.ai-agents.svc.cluster.local:11434/api/generate
ENV WHATSAPP_GATEWAY_URL=http://whatsapp-gateway.ai-agents.svc.cluster.local:5001/api/send-message
ENV WHATSAPP_TO=+14085505485
ENV OLLAMA_MODEL=qwen2.5:0.5b
CMD ["node", "index.js"]

46
gmail-agent/auth.js Normal file
View File

@ -0,0 +1,46 @@
const fs = require('fs');
const readline = require('readline');
const { google } = require('googleapis');
// If modifying these scopes, delete token.json.
const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly', 'https://www.googleapis.com/auth/gmail.modify'];
const TOKEN_PATH = 'token.json';
fs.readFile('credentials.json', (err, content) => {
if (err) return console.log('Error loading credentials.json file. Make sure you downloaded it from Google Cloud Console:', err);
authorize(JSON.parse(content), getNewToken);
});
function authorize(credentials, callback) {
const {client_secret, client_id, redirect_uris} = credentials.installed || credentials.web;
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0] || 'urn:ietf:wg:oauth:2.0:oob');
fs.readFile(TOKEN_PATH, (err, token) => {
if (err) return callback(oAuth2Client);
oAuth2Client.setCredentials(JSON.parse(token));
console.log("Token already exists and is valid. You don't need to authenticate again.");
});
}
function getNewToken(oAuth2Client) {
const authUrl = oAuth2Client.generateAuthUrl({
access_type: 'offline',
scope: SCOPES,
});
console.log('Authorize this app by visiting this url:', authUrl);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
});
rl.question('Enter the code from that page here: ', (code) => {
rl.close();
oAuth2Client.getToken(code, (err, token) => {
if (err) return console.error('Error retrieving access token', err);
oAuth2Client.setCredentials(token);
fs.writeFile(TOKEN_PATH, JSON.stringify(token), (err) => {
if (err) return console.error(err);
console.log('Token stored to', TOKEN_PATH);
});
});
});
}

167
gmail-agent/index.js Normal file
View File

@ -0,0 +1,167 @@
require('dotenv').config();
const fs = require('fs');
const path = require('path');
const cron = require('node-cron');
const axios = require('axios');
const { google } = require('googleapis');
const TOKEN_PATH = path.join(__dirname, 'token.json');
const CREDENTIALS_PATH = path.join(__dirname, 'credentials.json');
const OLLAMA_URL = process.env.OLLAMA_URL || 'http://ollama.ai-agents.svc.cluster.local:11434/api/generate';
const WHATSAPP_GATEWAY_URL = process.env.WHATSAPP_GATEWAY_URL || 'http://whatsapp-gateway.ai-agents.svc.cluster.local:5001/api/send-message';
const WHATSAPP_TO = process.env.WHATSAPP_TO || '+14085505485';
const OLLAMA_MODEL = process.env.OLLAMA_MODEL || 'qwen2.5:0.5b';
let oAuth2Client;
function loadAuth() {
if (!fs.existsSync(CREDENTIALS_PATH)) {
console.error('Missing credentials.json. Please download it from Google Cloud Console and place it in the project directory.');
process.exit(1);
}
if (!fs.existsSync(TOKEN_PATH)) {
console.error('Missing token.json. Please run "node auth.js" to generate it.');
process.exit(1);
}
const creds = JSON.parse(fs.readFileSync(CREDENTIALS_PATH));
const {client_secret, client_id, redirect_uris} = creds.installed || creds.web;
oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirect_uris[0] || 'urn:ietf:wg:oauth:2.0:oob');
oAuth2Client.setCredentials(JSON.parse(fs.readFileSync(TOKEN_PATH)));
}
async function getUnreadEmails() {
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client });
const res = await gmail.users.messages.list({
userId: 'me',
q: 'is:unread',
maxResults: 10
});
const messages = res.data.messages || [];
const emails = [];
for (const msg of messages) {
const fullMsg = await gmail.users.messages.get({ userId: 'me', id: msg.id, format: 'full' });
const headers = fullMsg.data.payload.headers;
const subject = headers.find(h => h.name === 'Subject')?.value || 'No Subject';
const from = headers.find(h => h.name === 'From')?.value || 'Unknown';
let body = '';
if (fullMsg.data.payload.parts) {
const part = fullMsg.data.payload.parts.find(p => p.mimeType === 'text/plain');
if (part && part.body.data) {
body = Buffer.from(part.body.data, 'base64').toString('utf8');
}
} else if (fullMsg.data.payload.body && fullMsg.data.payload.body.data) {
body = Buffer.from(fullMsg.data.payload.body.data, 'base64').toString('utf8');
}
emails.push({ id: msg.id, subject, from, body: body.substring(0, 1000) });
}
return emails;
}
async function markAsRead(msgId) {
const gmail = google.gmail({ version: 'v1', auth: oAuth2Client });
await gmail.users.messages.modify({
userId: 'me',
id: msgId,
requestBody: { removeLabelIds: ['UNREAD'] }
});
}
async function analyzeWithOllama(email) {
const prompt = `You are an AI assistant that determines if an email is important enough to notify the user immediately on WhatsApp.
Newsletters, marketing, promotions, and generic updates are NOT important.
Important things: Bills due, flight/travel updates, urgent requests from people, security alerts, direct personal messages.
Email Subject: ${email.subject}
Email From: ${email.from}
Email Body Preview:
${email.body}
First, determine if this is important (YES or NO).
If YES, provide a 1-sentence summary of what the user needs to know.
Output format exactly like this:
IMPORTANT: YES/NO
SUMMARY: <your summary if yes, otherwise leave blank>
`;
try {
const res = await axios.post(OLLAMA_URL, {
model: OLLAMA_MODEL,
prompt: prompt,
stream: false
});
const responseText = res.data.response || '';
const isImportant = responseText.includes('IMPORTANT: YES');
const summaryMatch = responseText.match(/SUMMARY:\s*(.*)/);
const summary = summaryMatch ? summaryMatch[1].trim() : '';
return { isImportant, summary };
} catch (err) {
console.error('Ollama Error:', err.message);
return { isImportant: false, summary: '' };
}
}
async function sendWhatsAppNotification(email, summary) {
const message = `📧 *Important Email*\n*From:* ${email.from}\n*Subject:* ${email.subject}\n\n🤖 *Summary:* ${summary}`;
try {
await axios.post(WHATSAPP_GATEWAY_URL, {
to: WHATSAPP_TO,
message: message
}, {
headers: { 'x-app-name': 'Gmail Agent' }
});
console.log(`Notification sent for: ${email.subject}`);
} catch (err) {
console.error('WhatsApp Gateway Error:', err.message);
}
}
async function processEmails() {
console.log(`[${new Date().toISOString()}] Checking for new emails...`);
try {
const emails = await getUnreadEmails();
if (emails.length === 0) {
console.log('No new emails.');
return;
}
console.log(`Found ${emails.length} unread emails.`);
for (const email of emails) {
const analysis = await analyzeWithOllama(email);
if (analysis.isImportant) {
await sendWhatsAppNotification(email, analysis.summary);
} else {
console.log(`Skipped non-important email: ${email.subject}`);
}
// Mark as read so we don't process it again (or add a label, but reading it is safest to prevent loops)
await markAsRead(email.id);
}
} catch (err) {
console.error('Error processing emails:', err.message);
}
}
// Start Application
try {
loadAuth();
console.log('Gmail Agent started successfully.');
console.log(`Cron job scheduled to check every 15 minutes.`);
// Check immediately on startup
processEmails();
// Schedule every 15 minutes
cron.schedule('*/15 * * * *', () => {
processEmails();
});
} catch (e) {
console.error('Failed to start:', e.message);
}

View File

@ -0,0 +1,78 @@
apiVersion: apps/v1
kind: Deployment
metadata:
name: gmail-agent
namespace: ai-agents
labels:
app: gmail-agent
spec:
replicas: 1
selector:
matchLabels:
app: gmail-agent
template:
metadata:
labels:
app: gmail-agent
spec:
initContainers:
- name: git-clone
image: alpine/git
command:
- git
- clone
- http://192.168.8.248:3000/deepkoluguri/agentic-os.git
- /app
volumeMounts:
- name: app-volume
mountPath: /app
containers:
- name: gmail-agent
image: node:18-alpine
workingDir: /app/gmail-agent
command:
- sh
- -c
- npm install --omit=dev && node index.js
env:
- name: OLLAMA_URL
value: http://ollama.ai-agents.svc.cluster.local:11434/api/generate
- name: WHATSAPP_GATEWAY_URL
value: http://whatsapp-gateway.ai-agents.svc.cluster.local:5001/api/send-message
- name: WHATSAPP_TO
value: "+14085505485"
- name: OLLAMA_MODEL
value: "qwen2.5:0.5b"
volumeMounts:
- name: app-volume
mountPath: /app
- name: credentials-volume
mountPath: /app/gmail-agent/credentials.json
subPath: credentials.json
readOnly: true
- name: token-volume
mountPath: /app/gmail-agent/token.json
subPath: token.json
readOnly: true
resources:
requests:
cpu: 50m
memory: 64Mi
limits:
cpu: 200m
memory: 128Mi
volumes:
- name: app-volume
emptyDir: {}
- name: credentials-volume
secret:
secretName: gmail-agent-auth
items:
- key: credentials.json
path: credentials.json
- name: token-volume
secret:
secretName: gmail-agent-auth
items:
- key: token.json
path: token.json

1345
gmail-agent/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

19
gmail-agent/package.json Normal file
View File

@ -0,0 +1,19 @@
{
"name": "gmail-agent",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "commonjs",
"dependencies": {
"axios": "^1.17.0",
"dotenv": "^17.4.2",
"googleapis": "^173.0.0",
"node-cron": "^4.2.1"
}
}

View File

@ -0,0 +1,6 @@
node_modules
.wwebjs_auth
.wwebjs_cache
npm-debug.log
.env
.DS_Store

BIN
whatsapp-gateway/.gitignore vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,27 @@
// Copyright 2015 The Chromium Authors. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@ -0,0 +1 @@
[{"description":"treehash per file","signed_content":{"payload":"eyJjb250ZW50X2hhc2hlcyI6W3siYmxvY2tfc2l6ZSI6NDA5NiwiZGlnZXN0Ijoic2hhMjU2IiwiZmlsZXMiOlt7InBhdGgiOiJMSUNFTlNFIiwicm9vdF9oYXNoIjoiUGIwc2tBVUxaUzFqWldTQnctV0hIRkltRlhVcExiZDlUcVkwR2ZHSHBWcyJ9LHsicGF0aCI6ImNybC1zZXQiLCJyb290X2hhc2giOiJsdXgwNjlTZE1EQWY5TDJQNG5EMHpQMlhyNkdHR21pc1dyRWxvMWVJeFJJIn0seyJwYXRoIjoibWFuaWZlc3QuanNvbiIsInJvb3RfaGFzaCI6InhFeDI1V3RiVnBtdXJRTHliSWh5MFFRYVYyTGRyNXA2VUpKTUlHMmxoTHMifV0sImZvcm1hdCI6InRyZWVoYXNoIiwiaGFzaF9ibG9ja19zaXplIjo0MDk2fV0sIml0ZW1faWQiOiJoZm5rcGltbGhoZ2llYWRkZ2ZlbWpob2ZtZmJsbW5pYiIsIml0ZW1fdmVyc2lvbiI6IjEwNTgzIiwicHJvdG9jb2xfdmVyc2lvbiI6MX0","signatures":[{"header":{"kid":"publisher"},"protected":"eyJhbGciOiJSUzI1NiJ9","signature":"Bjfe4JQInmUCNDEanr6mwLUPoxXXOP8HGNlASDaFaQK1oWbh7hXU-4SlkSh9H25TfCeBidhVH6mtF9Eiejvru4m32UEURKXEGRYuTK6qWL-yYN7fAD5U2SMgA6NabDlzBCIIzCSgy0N1ZK7NhbcadLlwENhNTX8t_LPsWhTfMZeFEBd3zzvcRpClrutRUaxOIMm1dM5JvuZXAWFZXjvCya-GPrytpMnYKVJ0zS6Zfuxb_20w4a5m2o_NB0iKNxYk8BuD9OR9gz_KizMijOC8wDk1QMsu4egp26eVThUvLmC8Nrfat5utV1RsfnnuCkKP-KAZ0BSHtjDdaZ1Sh3CpDQ"},{"header":{"kid":"webstore"},"protected":"eyJhbGciOiJSUzI1NiJ9","signature":"Mbl5IrYWF6r1HdvwZ-_BI6SeKueb2gYQI-dMmVtbJ_Ftj9klu29CPhlWMENSNc16lzNlEKcwpLwkX0aCoIjRFFlVsGfWLX0N4ZrxCPFAijPZnaPzX8GndS_qwjTot5VfaD1AIbwdiOy6CT5sW-1caK0BblehdnzHuqcKaDOoTkDsfXxomxu85Ss_dDe-D6OdmD1Vk31bTfKaB1S-5B7S_p7lDBV5B-uK6b-UO8chu5NFFG-6oO67HgnMFfqiKa_fNZYpB1KtNcspuWF8JC85CzDHaVrCfSix5zHEaXkVnztq4mo7umuj7VfCD6oZjT8ztNT8SNqRPp_bj_YRxwWQdQ"}]}}]

View File

@ -0,0 +1,5 @@
{
"manifest_version": 2,
"name": "crl-set-13199566045662348766.data",
"version": "10583"
}

View File

@ -0,0 +1 @@
SQLCache3

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1 @@
MANIFEST-000001

View File

@ -0,0 +1,2 @@
2026/06/13-13:50:24.913 9670 Creating DB C:\Users\sunde\proxmox\whatsapp-gateway\.wwebjs_auth\session\Default\Extension Rules since it was missing.
2026/06/13-13:50:25.014 9670 Reusing MANIFEST C:\Users\sunde\proxmox\whatsapp-gateway\.wwebjs_auth\session\Default\Extension Rules/MANIFEST-000001

View File

@ -0,0 +1 @@
MANIFEST-000001

View File

@ -0,0 +1,2 @@
2026/06/13-13:50:25.037 9670 Creating DB C:\Users\sunde\proxmox\whatsapp-gateway\.wwebjs_auth\session\Default\Extension Scripts since it was missing.
2026/06/13-13:50:25.065 9670 Reusing MANIFEST C:\Users\sunde\proxmox\whatsapp-gateway\.wwebjs_auth\session\Default\Extension Scripts/MANIFEST-000001

View File

@ -0,0 +1 @@
MANIFEST-000001

View File

@ -0,0 +1,2 @@
2026/06/13-13:50:41.525 7594 Creating DB C:\Users\sunde\proxmox\whatsapp-gateway\.wwebjs_auth\session\Default\Extension State since it was missing.
2026/06/13-13:50:41.580 7594 Reusing MANIFEST C:\Users\sunde\proxmox\whatsapp-gateway\.wwebjs_auth\session\Default\Extension State/MANIFEST-000001

Binary file not shown.

View File

@ -0,0 +1 @@
MANIFEST-000001

View File

@ -0,0 +1,2 @@
2026/06/13-14:45:06.870 9b84 Creating DB C:\Users\sunde\proxmox\whatsapp-gateway\.wwebjs_auth\session\Default\GCM Store since it was missing.
2026/06/13-14:45:08.886 9b84 Reusing MANIFEST C:\Users\sunde\proxmox\whatsapp-gateway\.wwebjs_auth\session\Default\GCM Store/MANIFEST-000001

Binary file not shown.

View File

@ -0,0 +1,5 @@
2026/06/13-13:50:50.014 9670 Creating DB C:\Users\sunde\proxmox\whatsapp-gateway\.wwebjs_auth\session\Default\IndexedDB\https_web.whatsapp.com_0.indexeddb.leveldb since it was missing.
2026/06/13-13:50:50.131 9670 Reusing MANIFEST C:\Users\sunde\proxmox\whatsapp-gateway\.wwebjs_auth\session\Default\IndexedDB\https_web.whatsapp.com_0.indexeddb.leveldb/MANIFEST-000001
2026/06/13-14:18:37.492 79b0 Level-0 table #5: started
2026/06/13-14:18:38.073 79b0 Level-0 table #5: 1910225 bytes OK
2026/06/13-14:18:38.148 79b0 Delete type=0 #3

View File

@ -0,0 +1 @@
MANIFEST-000001

View File

@ -0,0 +1,2 @@
2026/06/13-13:50:24.154 9280 Creating DB C:\Users\sunde\proxmox\whatsapp-gateway\.wwebjs_auth\session\Default\Local Storage\leveldb since it was missing.
2026/06/13-13:50:24.310 9280 Reusing MANIFEST C:\Users\sunde\proxmox\whatsapp-gateway\.wwebjs_auth\session\Default\Local Storage\leveldb/MANIFEST-000001

Some files were not shown because too many files have changed in this diff Show More