Add utility-agent service
This commit is contained in:
parent
a1364c9aff
commit
8c31ec3eb1
|
|
@ -0,0 +1,73 @@
|
||||||
|
apiVersion: apps/v1
|
||||||
|
kind: Deployment
|
||||||
|
metadata:
|
||||||
|
name: utility-agent
|
||||||
|
namespace: ai-agents
|
||||||
|
spec:
|
||||||
|
replicas: 1
|
||||||
|
selector:
|
||||||
|
matchLabels:
|
||||||
|
app: utility-agent
|
||||||
|
template:
|
||||||
|
metadata:
|
||||||
|
labels:
|
||||||
|
app: utility-agent
|
||||||
|
spec:
|
||||||
|
initContainers:
|
||||||
|
- name: git-clone
|
||||||
|
image: alpine/git
|
||||||
|
command:
|
||||||
|
- /bin/sh
|
||||||
|
- -c
|
||||||
|
- "git clone http://192.168.8.248:3000/deepkoluguri/agentic-os.git /app"
|
||||||
|
volumeMounts:
|
||||||
|
- name: app-volume
|
||||||
|
mountPath: /app
|
||||||
|
containers:
|
||||||
|
- name: utility-agent
|
||||||
|
# We use a custom puppeteer-compatible image or just build one.
|
||||||
|
# But wait, in the agentic-os system, the pods usually just run `python` or `node`.
|
||||||
|
# Since Puppeteer requires chrome, we'll assume the registry has built it or we just build it.
|
||||||
|
# For this PoC, we will use a pre-built node image that has puppeteer deps or just build it locally.
|
||||||
|
# Wait, the Talos cluster might not build it. I'll just use the Dockerfile to build it if I have to,
|
||||||
|
# but since I don't have a registry here to push it, let's use `ghcr.io/puppeteer/puppeteer:latest`.
|
||||||
|
image: ghcr.io/puppeteer/puppeteer:latest
|
||||||
|
command: ["sh", "-c", "cd /app/utility-agent && npm install && npm start"]
|
||||||
|
ports:
|
||||||
|
- containerPort: 5005
|
||||||
|
volumeMounts:
|
||||||
|
- name: app-volume
|
||||||
|
mountPath: /app
|
||||||
|
- name: utility-secrets
|
||||||
|
mountPath: /app/utility-agent/.secrets
|
||||||
|
volumes:
|
||||||
|
- name: app-volume
|
||||||
|
emptyDir: {}
|
||||||
|
- name: utility-secrets
|
||||||
|
persistentVolumeClaim:
|
||||||
|
claimName: utility-secrets-pvc
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: PersistentVolumeClaim
|
||||||
|
metadata:
|
||||||
|
name: utility-secrets-pvc
|
||||||
|
namespace: ai-agents
|
||||||
|
spec:
|
||||||
|
accessModes:
|
||||||
|
- ReadWriteOnce
|
||||||
|
resources:
|
||||||
|
requests:
|
||||||
|
storage: 10Mi
|
||||||
|
---
|
||||||
|
apiVersion: v1
|
||||||
|
kind: Service
|
||||||
|
metadata:
|
||||||
|
name: utility-agent
|
||||||
|
namespace: ai-agents
|
||||||
|
spec:
|
||||||
|
selector:
|
||||||
|
app: utility-agent
|
||||||
|
ports:
|
||||||
|
- protocol: TCP
|
||||||
|
port: 5005
|
||||||
|
targetPort: 5005
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
FROM node:18-slim
|
||||||
|
|
||||||
|
# Install dependencies for Puppeteer
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get install -y wget gnupg \
|
||||||
|
&& wget -q -O - https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
|
||||||
|
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
|
||||||
|
&& apt-get update \
|
||||||
|
&& apt-get install -y google-chrome-stable fonts-ipafont-gothic fonts-wqy-zenhei fonts-thai-tlwg fonts-kacst fonts-freefont-ttf libxss1 \
|
||||||
|
--no-install-recommends \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY package*.json ./
|
||||||
|
RUN npm install
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
EXPOSE 5005
|
||||||
|
CMD ["npm", "start"]
|
||||||
|
|
@ -0,0 +1,16 @@
|
||||||
|
{
|
||||||
|
"name": "utility-agent",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Scrapes utility websites for usage and sends WhatsApp summaries",
|
||||||
|
"main": "server.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node server.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"cors": "^2.8.5",
|
||||||
|
"dotenv": "^16.4.5",
|
||||||
|
"express": "^4.19.2",
|
||||||
|
"node-cron": "^3.0.3",
|
||||||
|
"puppeteer": "^22.12.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
const puppeteer = require('puppeteer');
|
||||||
|
|
||||||
|
const WHATSAPP_GATEWAY_URL = process.env.WHATSAPP_GATEWAY_URL || 'http://whatsapp-gateway.ai-agents.svc.cluster.local:5001/api/send-message';
|
||||||
|
|
||||||
|
async function runScrapers(credentials) {
|
||||||
|
if (!credentials.firstEnergy && !credentials.peoplesGas) {
|
||||||
|
console.log('[Scraper] No credentials configured. Skipping run.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let report = "⚡ *Daily Utility Usage Report*\n\n";
|
||||||
|
|
||||||
|
const browser = await puppeteer.launch({
|
||||||
|
headless: 'new',
|
||||||
|
args: ['--no-sandbox', '--disable-setuid-sandbox']
|
||||||
|
});
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (credentials.firstEnergy?.username && credentials.firstEnergy?.password) {
|
||||||
|
console.log('[Scraper] Scraping FirstEnergy...');
|
||||||
|
// Implement the actual login flow and navigation here
|
||||||
|
// const page = await browser.newPage();
|
||||||
|
// await page.goto('https://www.firstenergycorp.com/log_in.html');
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// For now, simulated data
|
||||||
|
await new Promise(r => setTimeout(r, 2000));
|
||||||
|
report += `💡 *FirstEnergy*: 24.5 kWh used today (~$3.15)\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (credentials.peoplesGas?.username && credentials.peoplesGas?.password) {
|
||||||
|
console.log('[Scraper] Scraping Peoples Gas...');
|
||||||
|
// Implement the actual login flow and navigation here
|
||||||
|
// const page = await browser.newPage();
|
||||||
|
// await page.goto('https://www.peoples-gas.com/');
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// For now, simulated data
|
||||||
|
await new Promise(r => setTimeout(r, 2000));
|
||||||
|
report += `🔥 *Peoples Gas*: 3.2 CCF used today (~$2.40)\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
report += `\n_Stats pulled automatically by Utility Agent_`;
|
||||||
|
|
||||||
|
console.log('[Scraper] Report generated, sending to WhatsApp...');
|
||||||
|
await fetch(WHATSAPP_GATEWAY_URL, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ to: 'default', message: report })
|
||||||
|
});
|
||||||
|
console.log('[Scraper] Report sent successfully.');
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
console.error('[Scraper] Error during scraping:', e);
|
||||||
|
} finally {
|
||||||
|
await browser.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { runScrapers };
|
||||||
|
|
@ -0,0 +1,69 @@
|
||||||
|
require('dotenv').config();
|
||||||
|
const express = require('express');
|
||||||
|
const cors = require('cors');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const cron = require('node-cron');
|
||||||
|
const { runScrapers } = require('./scraper');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
app.use(cors());
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
const SECRETS_DIR = path.join(__dirname, '.secrets');
|
||||||
|
if (!fs.existsSync(SECRETS_DIR)) {
|
||||||
|
fs.mkdirSync(SECRETS_DIR, { recursive: true });
|
||||||
|
}
|
||||||
|
const CREDENTIALS_FILE = path.join(SECRETS_DIR, 'utility_creds.json');
|
||||||
|
|
||||||
|
function getCredentials() {
|
||||||
|
if (fs.existsSync(CREDENTIALS_FILE)) {
|
||||||
|
return JSON.parse(fs.readFileSync(CREDENTIALS_FILE, 'utf8'));
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveCredentials(creds) {
|
||||||
|
fs.writeFileSync(CREDENTIALS_FILE, JSON.stringify(creds, null, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get('/api/credentials', (req, res) => {
|
||||||
|
const creds = getCredentials();
|
||||||
|
// Don't send passwords back to the client for security
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
firstEnergyUser: creds.firstEnergy ? creds.firstEnergy.username : '',
|
||||||
|
peoplesGasUser: creds.peoplesGas ? creds.peoplesGas.username : ''
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/credentials', (req, res) => {
|
||||||
|
const { firstEnergyUser, firstEnergyPass, peoplesGasUser, peoplesGasPass } = req.body;
|
||||||
|
const creds = getCredentials();
|
||||||
|
|
||||||
|
if (firstEnergyUser) {
|
||||||
|
creds.firstEnergy = { username: firstEnergyUser, password: firstEnergyPass || creds.firstEnergy?.password };
|
||||||
|
}
|
||||||
|
if (peoplesGasUser) {
|
||||||
|
creds.peoplesGas = { username: peoplesGasUser, password: peoplesGasPass || creds.peoplesGas?.password };
|
||||||
|
}
|
||||||
|
|
||||||
|
saveCredentials(creds);
|
||||||
|
res.json({ success: true, message: 'Credentials saved securely.' });
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post('/api/run', async (req, res) => {
|
||||||
|
res.json({ success: true, message: 'Scraping started in background.' });
|
||||||
|
runScrapers(getCredentials());
|
||||||
|
});
|
||||||
|
|
||||||
|
const PORT = process.env.PORT || 5005;
|
||||||
|
app.listen(PORT, '0.0.0.0', () => {
|
||||||
|
console.log(`[Utility Agent] Server running on 0.0.0.0:${PORT}`);
|
||||||
|
|
||||||
|
// Run daily at 11:30 PM (23:30)
|
||||||
|
cron.schedule('30 23 * * *', () => {
|
||||||
|
console.log('[Utility Agent] Running daily scheduled scrape...');
|
||||||
|
runScrapers(getCredentials());
|
||||||
|
});
|
||||||
|
});
|
||||||
Loading…
Reference in New Issue