A personal AI assistant and automation agent built for real-world use — searches the web, reads the news, hunts new grad jobs and scores them against a resume, generates cover letters, monitors GitHub trends, fetches research papers, summarizes YouTube videos, and runs a daily interview-prep curriculum over email and Discord.
Live: pulse-rust-eta.vercel.app · Backend: Render · Frontend: Vercel
Built with FastAPI · React + Vite · OpenAI GPT-4.1 mini · APScheduler · GitHub Actions · SQLite · Gmail SMTP · Discord Webhooks
- Autonomous tool-calling agent — decides which tool to use, chains multiple tools together, and streams the final answer in real time
- Real-time token streaming — SSE-based streaming replicates ChatGPT-style response delivery
- Voice input — speak your query via Web Speech API, text fills automatically
- Markdown rendering — responses render with proper headings, lists, bold, tables, and code blocks
- Conversation history — all sessions saved to SQLite and accessible from the sidebar
- Email OTP authentication — 6-digit code sent to owner's email, 5-minute expiry, restricts access to authorized user only
- New grad job matching — Adzuna and Greenhouse postings fanned out across six role queries, deduped, filtered to entry level, then GPT-scored against the resume and delivered 3× daily
- System design lessons — one concept per day with trade-offs, numbers worth quoting, and a scenario-based interview question with the follow-up they'll ask
- DSA revision drills — one topic per day with a recognition pattern, a reusable Python template, a named problem, and the key insight hidden behind a Discord spoiler
- Daily AI briefing — polished AI news summary emailed every morning at 8:00 AM ET
- Motivation messages — angle and quote source rotate daily so no two days repeat
- Nightly research summary — one highlighted Arxiv paper summarized and sent to Discord
- AI/ML concept of the night — one technical concept explained and sent to Discord
- Cover letter generator — paste a job description, get a formatted PDF cover letter emailed as an attachment
- YouTube summarization — drop in a link, Pulse pulls the transcript and explains the video
- Externally triggered schedules — GitHub Actions wakes the backend and fires each job, so a sleeping free-tier container can't silently skip a day
- Mobile responsive — hamburger sidebar, 2-column card grid, optimized for all screen sizes
- Rate limiting — SlowAPI limits on the agent, OTP, and cron endpoints
- Input validation — roles, lengths, and content validated via Pydantic
11 tools callable by the agent:
| Tool | Description |
|---|---|
search_web |
Search the web via Tavily API |
get_news |
Fetch latest news articles via GNews API |
get_wikipedia_summary |
Get factual summaries from Wikipedia |
get_github_trending |
Get trending GitHub repos by language |
get_arxiv_papers |
Fetch latest research papers from Arxiv |
send_email |
Send a summary email to the owner |
send_discord |
Post to the owner's Discord, auto-splitting past the 2000-char limit |
get_jobs |
Search job listings via Adzuna API, limited to recent postings |
get_greenhouse_jobs |
Search open roles across 29 top tech companies on Greenhouse |
get_youtube_transcript |
Fetch a video transcript for summarization |
generate_cover_letter |
Generate a PDF cover letter and email it |
10 scheduled jobs per day (times in ET):
| Time | What Happens |
|---|---|
| 8:00 AM | AI news briefing → email |
| 8:30 AM | Morning motivation → Discord |
| 9:00 AM | Job matching (Adzuna + Greenhouse, GPT-scored) → Discord |
| 12:00 PM | System design concept + interview scenario → Discord |
| 2:30 PM | Job matching → Discord |
| 7:00 PM | DSA revision drill → Discord |
| 8:00 PM | Job matching → Discord |
| 9:00 PM | Evening motivation → Discord |
| 1:00 AM | AI/ML concept of the night → Discord |
| 1:20 AM | Nightly research paper summary → Discord |
APScheduler runs in-process, and Render's free tier spins the service down after ~15 minutes idle — so a job scheduled for 9:00 AM never fires unless something woke the container first, and APScheduler's default misfire_grace_time discards it rather than running it late.
The workflows in .github/workflows/ wake the service and POST /cron/{job} on schedule, so delivery doesn't depend on the container happening to be awake. Set EXTERNAL_JOB_CRON=true in production so the in-process trigger doesn't fire a duplicate.
Interview prep and motivation rotate deterministically by day-of-year rather than random.choice, so a full cycle covers every topic exactly once with no repeats and no gaps:
| List | Size |
|---|---|
| System design concepts | 40 |
| System design scenarios | 26 |
| DSA topics | 34 |
| Motivation themes | 32 |
| Motivation quote sources | 10 |
Each completed cycle also bumps the difficulty on the next pass.
| Layer | Technology |
|---|---|
| Backend | FastAPI, Python 3.11 |
| AI Model | OpenAI GPT-4.1 mini |
| Web Search | Tavily API |
| News | GNews API |
| Knowledge | Wikipedia REST API |
| GitHub | GitHub REST API |
| Research | Arxiv API |
| Job Search | Adzuna API + Greenhouse API |
| Video | youtube-transcript-api (Webshare residential proxy in prod) |
| PDF Generation | ReportLab |
| Gmail SMTP | |
| Discord | Discord Webhooks |
| Authentication | Email OTP (Gmail SMTP) |
| Scheduler | APScheduler + GitHub Actions cron |
| Database | SQLite |
| Frontend | React 19, Vite, react-markdown |
| Rate Limiting | SlowAPI |
| Deployment | Render (backend) + Vercel (frontend) |
pulse/
├── .github/
│ └── workflows/
│ ├── job-alerts.yml # Wakes Render + triggers job matching 3x daily
│ └── interview-prep.yml # Triggers system design + DSA lessons
├── backend/
│ ├── app/
│ │ ├── agent.py # Agentic loop — tool calling + streaming
│ │ ├── config.py # Env var loading
│ │ ├── database.py # SQLite connection and schema
│ │ ├── history.py # Save and load task history
│ │ ├── main.py # FastAPI app, OTP + cron endpoints, routes
│ │ ├── schemas.py # Pydantic request/message models
│ │ ├── scheduler.py # All 10 scheduled pipelines + curricula
│ │ ├── tools.py # All 11 agent tools
│ │ └── resume.txt # Resume + role targeting for job scoring and cover letters
│ ├── Dockerfile
│ ├── .env # Secrets (not committed)
│ └── requirements.txt
└── frontend/
├── src/
│ ├── App.jsx # Full chat UI — streaming, voice, history, OTP lock
│ ├── main.jsx
│ └── index.css # CSS variables, dark mode, lock screen styles
├── public/
├── index.html
└── package.json
resume.txt drives both job scoring and cover letter generation — its Looking For section is what retargets the job alert at different roles.
- Python 3.11+
- Node.js 18+
- API keys for OpenAI, Tavily, GNews, and Adzuna
git clone https://github.com/bimal1023/Pulse.git
cd Pulsecd backend
python3 -m venv venv
source venv/bin/activate # Windows: venv\Scripts\activate
pip install -r requirements.txtCreate a .env file in backend/:
OPENAI_API_KEY=sk-...
TAVILY_API_KEY=tvly-...
GNEWS_API_KEY=...
# Email (Gmail SMTP — use App Password)
EMAIL_SENDER=your-gmail@gmail.com
EMAIL_PASSWORD=your-16-char-app-password
EMAIL_RECEIVER=your-email@gmail.com
# Discord
DISCORD_WEBHOOK_URL=https://discord.com/api/webhooks/...
# Adzuna Job Search
ADZUNA_APP_ID=...
ADZUNA_APP_KEY=...
# External cron (needed in production, optional locally)
CRON_SECRET= # Shared secret for POST /cron/{job}
EXTERNAL_JOB_CRON= # "true" in prod — stops APScheduler double-firing job alerts
# Optional: only needed in prod for get_youtube_transcript.
# YouTube blocks datacenter IPs, so deployed fetches route through a residential proxy.
WEBSHARE_PROXY_USERNAME=
WEBSHARE_PROXY_PASSWORD=Start the server:
uvicorn app.main:app --reload --host 127.0.0.1 --port 8002cd ../frontend
npm installCreate a .env.local file:
VITE_API_BASE=http://127.0.0.1:8002Start the dev server:
npm run devOpen http://localhost:5173.
Pulse uses email OTP authentication to restrict access to the owner only.
- Open the app → lock screen appears
- Click Send Verification Code → 6-digit code sent to owner's email
- Enter the code → access granted for the session
- Session expires when the browser tab is closed
The verified token lives in memory on the server, so it is cleared whenever the container restarts. Automated schedules therefore use /cron/{job} with a shared secret instead.
Runs the agentic loop and streams the response as Server-Sent Events. Requires the X-Auth-Token header.
{ "messages": [{ "role": "user", "content": "What's happening in AI today?" }] }| Event | Payload | Description |
|---|---|---|
step |
{ "tool": "get_news" } |
Tool is being called |
token |
{ "content": "..." } |
Streamed answer token |
done |
{ "steps": [...], "final_answer": "..." } |
Run complete |
Generates a 6-digit OTP and sends it to the owner's email.
Verifies the submitted OTP. Returns { "token": "pulse_authenticated" } on success.
Returns saved task history, newest first, or a single task by ID.
Triggers a scheduled pipeline from outside the container. Authenticated with the X-Cron-Secret header matched against CRON_SECRET.
curl -X POST -H "X-Cron-Secret: $CRON_SECRET" \
https://your-backend.onrender.com/cron/jobsValid jobs: jobs, briefing, research, concept, motivation-morning, motivation-evening, system-design, dsa.
Set every variable from the .env block above, including CRON_SECRET and EXTERNAL_JOB_CRON=true. Render auto-detects the Dockerfile in backend/ and deploys automatically.
VITE_API_BASE=https://your-render-backend.onrender.comVercel auto-detects Vite and builds correctly.
Add two repository secrets under Settings → Secrets and variables → Actions:
| Secret | Value |
|---|---|
BACKEND_URL |
Your Render URL, no trailing slash |
CRON_SECRET |
Same value set in Render |
Both workflows support Run workflow from the Actions tab for manual testing. Note that GitHub's scheduled runs are often delayed several minutes under load, and scheduled workflows are disabled automatically after 60 days of repository inactivity.
| Endpoint | Limit |
|---|---|
/run-agent |
5 requests/minute per IP |
/send-otp |
5 requests/hour per IP |
/cron/{job} |
10 requests/hour per IP |
{ "detail": "Too many requests. Please wait before trying again." }MIT