Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,7 @@ QUIZ_EMAIL_RECIPIENT=your_recipient_email_here
# Telegram
TELEGRAM_BOT_TOKEN=your_telegram_bot_token_here # @BotFather on Telegram → /newbot
TELEGRAM_CHAT_ID=your_telegram_chat_id_here # message @userinfobot on Telegram to get it
# LangSmith (optional — tracing for LangChain agent calls)
LANGSMITH_TRACING=true
LANGSMITH_API_KEY=your_langsmith_api_key_here
LANGSMITH_PROJECT=oh-my-workers
3 changes: 3 additions & 0 deletions .github/workflows/cleanup.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ jobs:
COMPANY_DB_URL: ${{ secrets.NEON_MOCK_COMPANY_DB_URL }}
COMPANY_CLEANUP_TABLE: ${{ secrets.COMPANY_CLEANUP_TABLE }}
COMPANY_CLEANUP_THRESHOLD_DAYS: ${{ secrets.COMPANY_CLEANUP_THRESHOLD_DAYS }}
LANGSMITH_TRACING: 'true'
LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }}
LANGSMITH_PROJECT: oh-my-workers
run: pnpm cleanup
3 changes: 3 additions & 0 deletions .github/workflows/daily-kpi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,7 @@ jobs:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TARGET_GITHUB_USERNAME: ${{ secrets.TARGET_GITHUB_USERNAME }}
MANUAL_ACTIVITIES: ${{ github.event.inputs.activities }}
LANGSMITH_TRACING: 'true'
LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }}
LANGSMITH_PROJECT: oh-my-workers
run: pnpm start
3 changes: 3 additions & 0 deletions .github/workflows/morning-news.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,7 @@ jobs:
DATABASE_URL: ${{ secrets.NEON_WORK_COORDINATOR_DB_URL }}
TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
LANGSMITH_TRACING: 'true'
LANGSMITH_API_KEY: ${{ secrets.LANGSMITH_API_KEY }}
LANGSMITH_PROJECT: oh-my-workers
run: pnpm news
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,7 @@ data/
wiki/
src/assets/slides_to_video/
src/scripts/test-registry.ts
dist/
dist/
.claude
commands/
hooks/
121 changes: 44 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,100 +3,79 @@
A personal AI agent suite for software engineers. Runs daily jobs automatically via GitHub Actions:

- **5pm Sydney** — fetches GitHub activity, asks what else you did, generates a KPI diary report
- **8am Sydney** — scrapes GitHub trending repos (TypeScript/JavaScript), deduplicates against recent sends, curates top picks with an LLM, delivers via Telegram
- **8am Sydney** — scrapes GitHub trending repos (TypeScript/JavaScript), deduplicates, curates top picks with an LLM, delivers via Telegram

Built with TypeScript, LangChain, and Claude.

<video src="https://github.com/user-attachments/assets/1c37e4f1-4a8a-4100-8a07-9cedfa7e745f" width="600" height="360" controls></video>
Built with TypeScript, LangChain/LangGraph, and any OpenAI-compatible LLM (defaults to OpenRouter, free tier). More detail in the [wiki](https://github.com/DamengRandom/oh-my-workers/wiki).

---

## How it works

**KPI pipeline (5pm):**
```
Phase 1 (parallel): cleanupAgent + githubAgent
Phase 2 (sequential): manualKpiAgent ← waits for your input
Phase 3 (sequential): diaryAgent ← writes report from Phase 1+2 data
```
**KPI pipeline (5pm):** `cleanupAgent + githubAgent` (parallel) → `manualKpiAgent` (waits for your input) → `diaryAgent` (writes report)

**GitHub Trending pipeline (8am):**
```
Scrape GitHub trending (TS + JS)
Dedup against DB (last 7 days)
LLM curator → picks top 5-8, writes summaries + tags
Telegram delivery → saved to github_trending table
Scrape (TS + JS) → Dedup (last 7 days) → LLM curator → Telegram → saved to DB
```

The curator generates 3-5 tags per repo (e.g. `#ai`, `#framework`, `#bundler`) for future vector search classification.
The curator is a small LangGraph retry loop: if the LLM's output doesn't parse, it retries once with the parse error as feedback; if it still fails, the job alerts you via Telegram instead of failing silently. See [Curator Retry Graph](https://github.com/DamengRandom/oh-my-workers/wiki/Curator-Retry-Graph).

---

## Setup

### 1. Install

```bash
pnpm install
cp .env.example .env
cp .env.example .env # fill in your keys — see below
pnpm run setup # create database tables
pnpm news # test the GitHub trending pipeline
pnpm start # test the KPI pipeline
pnpm test # run the unit test suite
```

### 2. Configure `.env`
Minimum required in `.env`:

```env
ANTHROPIC_API_KEY=
GITHUB_TOKEN= # github.com/settings/tokens (read:user, repo scopes)
LLM_API_KEY= # any OpenAI-compatible provider; defaults to OpenRouter
GITHUB_TOKEN= # github.com/settings/tokens (read:user, repo scopes)
TARGET_GITHUB_USERNAME=

DATABASE_URL=postgresql://postgres:password@localhost:5432/work_coordinator
COMPANY_DB_URL=postgresql://user:password@company-host:5432/company_db

# Telegram
TELEGRAM_BOT_TOKEN= # @BotFather on Telegram → /newbot
TELEGRAM_CHAT_ID= # message @userinfobot to get your ID, then start your bot first
TELEGRAM_BOT_TOKEN= # @BotFather on Telegram → /newbot
TELEGRAM_CHAT_ID= # message @userinfobot, then start your bot first
```

### 3. Initialize database
`LANGSMITH_TRACING` / `LANGSMITH_API_KEY` / `LANGSMITH_PROJECT` are optional (see below). Using Neon? Drop `&channel_binding=require` from the connection string — `pg` doesn't support it.

```bash
pnpm run setup
```
---

> Using Neon? Pass the URL inline: `DATABASE_URL="postgresql://...?sslmode=require" pnpm run setup`
> Remove `&channel_binding=require` from Neon URLs — the `pg` library doesn't support it.
## Observability (LangSmith)

### 4. Test
Optional tracing for every LangChain/LangGraph call — free tier (5,000 traces/month) comfortably covers this project. Set the three `LANGSMITH_*` vars in `.env` (get a key at [smith.langchain.com](https://smith.langchain.com)) — no code changes needed.

```bash
pnpm start # runs all KPI jobs now
pnpm news # runs GitHub trending pipeline now
```
![LangSmith trace logs](/src/assets/images/langsmith-logs.png)

---

## Automate via GitHub Actions (recommended)

Push to GitHub, then add these secrets under **Settings → Secrets and variables → Actions**:
Push to GitHub, add these secrets under **Settings → Secrets and variables → Actions**:

| Secret | Value |
|---|---|
| `LLM_API_KEY` | from openrouter.ai/keys (free tier works) |
| `NEON_WORK_COORDINATOR_DB_URL` | Neon connection string (remove `&channel_binding=require`) |
| `NEON_WORK_COORDINATOR_DB_URL` | Neon connection string (no `&channel_binding=require`) |
| `NEON_MOCK_COMPANY_DB_URL` | Neon connection string for company DB |
| `COMPANY_CLEANUP_TABLE` | table to clean, e.g. `mockTestUsers` |
| `COMPANY_CLEANUP_THRESHOLD_DAYS` | stale threshold, e.g. `30` |
| `TARGET_GITHUB_USERNAME` | your GitHub username |
| `TELEGRAM_BOT_TOKEN` | from @BotFather |
| `TELEGRAM_CHAT_ID` | from @userinfobot |
| `LANGSMITH_API_KEY` | optional |

![GitHub Actions secrets](/src/assets/images/github-actions-secrets.png)

Workflows run automatically. Trigger manually: **Actions** tab → select workflow → **Run workflow**.

The **Daily KPI Report** workflow has a form — enter comma-separated activities before clicking Run.
Trigger manually: **Actions tab → select workflow → Run workflow**. The Daily KPI Report workflow takes comma-separated activities as input.

---

Expand All @@ -105,15 +84,12 @@ The **Daily KPI Report** workflow has a form — enter comma-separated activitie
```bash
crontab -e
```

Add (replace paths with your own):
```
PATH=/Users/yourname/.nvm/versions/node/v22.14.0/bin:/opt/homebrew/bin:/usr/bin:/bin
TZ=Australia/Sydney
0 17 * * * /opt/homebrew/bin/pnpm --prefix /path/to/project cleanup >> /path/to/project/data/cron.log 2>&1
```

> Mac must be awake at 5pm. GitHub Actions is more reliable for unattended runs.
> Mac must be awake at 5pm — GitHub Actions is more reliable for unattended runs.

---

Expand All @@ -122,15 +98,16 @@ TZ=Australia/Sydney
| Command | What it does |
|---|---|
| `pnpm run setup` | One-time DB table creation |
| `pnpm cleanup` | Stale data deletion only (used by crontab) — alias for `--job=cleanup` |
| `pnpm cleanup` | Stale data deletion only — alias for `--job=cleanup` |
| `pnpm start` | GitHub fetch + manual KPI input + diary report — alias for `--job=daily-kpi` |
| `pnpm news` | Scrape GitHub trending, curate, send via Telegram — alias for `--job=news` |
| `pnpm news` | Scrape, curate, send via Telegram — alias for `--job=news` |
| `pnpm jobs` | List every registered job with its cron schedule |
| `pnpm run dev --job=<name>` | Run any registered job by name (generic dispatch) |
| `pnpm dev` | Long-running daemon (fires scheduled jobs via node-cron) |
| `pnpm run dev --job=<name>` | Run any registered job by name |
| `pnpm dev` | Long-running daemon (node-cron) |
| `pnpm seed-mock` | Seed expired mock users into company DB |
| `pnpm format` | Auto-format with Prettier |
| `pnpm test` | Run the unit test suite (`node:test` via `tsx`) |
| `pnpm tsc` | TypeScript type check |
| `pnpm format` | Auto-format with Prettier |

---

Expand All @@ -140,28 +117,18 @@ TZ=Australia/Sydney
src/
├── agent/
│ ├── index.ts # WorkCoordinator — orchestrates all agents
│ ├── prompt.ts # System prompts for all agents
│ ├── cleanup.agent.ts
│ ├── github.agent.ts
│ ├── manual-kpi.agent.ts
│ ├── diary.agent.ts
│ ├── news-curator.agent.ts # LLM curator for trending repos
│ └── news-telegram.agent.ts # Telegram delivery agent
│ ├── prompt.ts # System prompts for all agents
│ ├── llm.ts # Shared model factory (any OpenAI-compatible provider)
│ ├── utils.ts # Shared helpers: toolOutput, parseJson, notifyError
│ ├── curator.graph.ts # LangGraph: self-correcting retry loop for curation
│ ├── curator-graph.test.ts # Unit tests for the curator retry graph
│ └── *.agent.ts # One focused agent per task
├── tools/ # DynamicStructuredTool implementations
│ ├── trending-scrape.tool.ts # GitHub trending HTML scraper
│ ├── news-curator.tool.ts # Curate + tag trending repos
│ └── news-telegram.tool.ts # Format + send Telegram message
├── jobs/
│ ├── registry.ts # Generic Job registry — add new cron jobs here
│ └── scheduler.ts # node-cron loop over registry
├── jobs/ # registry.ts (add jobs here) + scheduler.ts
├── storage/ # PostgreSQL queries (own-db + company-db)
├── schemas/index.ts # Zod schemas
└── index.ts # Entry point + CLI flags
.github/workflows/
├── cleanup.yml # Daily 5pm cleanup
├── daily-kpi.yml # Manual KPI trigger (workflow_dispatch)
├── seed-mock-users.yml # Daily 4:30pm mock data seeding
└── morning-news.yml # Daily 8am GitHub trending digest
├── schemas/index.ts # Zod schemas + shared types (TrendingRepo, CuratedRepo, ...)
└── index.ts # Entry point + CLI flags
.github/workflows/ # cleanup, daily-kpi, seed-mock-users, morning-news
```

---
Expand All @@ -170,10 +137,10 @@ src/

| Table | Description |
|---|---|
| `kpi` | Daily GitHub activity records (commits, PRs, manual activities) |
| `kpi` | Daily GitHub activity records |
| `diary` | AI-generated daily KPI reports |
| `cleanup_log` | Company DB cleanup history (deleted count, errors) |
| `github_trending` | Trending repos with summaries, tags, and sent status |
| `cleanup_log` | Company DB cleanup history |
| `github_trending` | Trending repos with summaries, tags, sent status |

---

Expand Down
8 changes: 7 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "oh-my-workers",
"version": "1.1.0",
"version": "1.2.0",
"description": "A personal AI agent that automates daily working tasks at workplace, eg: Cronjobs, KPI reporting and etc",
"type": "module",
"main": "index.js",
Expand All @@ -10,6 +10,7 @@
"start": "pnpm run dev --job=daily-kpi",
"cleanup": "pnpm run dev --job=cleanup",
"tsc": "pnpm exec tsc --noEmit 2>&1",
"test": "node --import tsx --test \"src/**/*.test.ts\"",
"news": "pnpm run dev --job=news",
"jobs": "pnpm run dev --list-jobs",
"seed-mock": "node --loader ts-node/esm src/scripts/seed-mock-users.ts",
Expand All @@ -27,6 +28,9 @@
"automation",
"AI agent",
"langchain",
"langsmith",
"Nvidia",
"nemotron",
"typescript",
"neon",
"postgresql",
Expand All @@ -38,6 +42,7 @@
"packageManager": "pnpm@10.7.1",
"dependencies": {
"@langchain/core": "^1.2.3",
"@langchain/langgraph": "^1.4.8",
"@langchain/openai": "^1.5.5",
"@octokit/rest": "^22.0.1",
"dotenv": "^17.3.1",
Expand All @@ -52,6 +57,7 @@
"@types/pg": "^8.20.0",
"prettier": "^3.8.1",
"ts-node": "^10.9.2",
"tsx": "^4.23.1",
"typescript": "^6.0.2"
}
}
Loading
Loading