From f9d75436116d5d9e19fff61549d6e77ff0500f97 Mon Sep 17 00:00:00 2001 From: Claw Date: Fri, 6 Mar 2026 02:12:17 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20PM2=20as=20canonical=20scheduler=20?= =?UTF-8?q?=E2=80=94=20SKILL.md=20updated,=20setup.mjs=20simplified,=20Ope?= =?UTF-8?q?nClaw=20gateway=20cron=20removed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SKILL.md | 66 +++++++++++++++++++++++++++++++------------------ setup.mjs | 74 ++++++++----------------------------------------------- 2 files changed, 52 insertions(+), 88 deletions(-) diff --git a/SKILL.md b/SKILL.md index a2c34d6..a3a5563 100644 --- a/SKILL.md +++ b/SKILL.md @@ -71,22 +71,59 @@ cp config/search_config.example.json config/search_config.json **`search_config.json`** — keywords, platforms, location filters, salary filters, exclusions -### 4. Verify +### 4. Create .env + +Create a `.env` file in the project root (gitignored — never commit this): ```bash -export KERNEL_API_KEY=your_kernel_api_key -export ANTHROPIC_API_KEY=your_anthropic_api_key # optional, for AI keywords +KERNEL_API_KEY=your_kernel_api_key +ANTHROPIC_API_KEY=your_anthropic_api_key # optional, for AI keywords +``` +### 5. Verify + +```bash node setup.mjs ``` Setup will: - Validate all config files +- Write `.env` (mode 600) if API keys are set - Send a Telegram test message - Test LinkedIn + Wellfound logins -- Auto-register cron jobs (if `OPENCLAW_GATEWAY_URL` + `OPENCLAW_GATEWAY_TOKEN` are set) -### 5. Run manually +### 6. Schedule with PM2 + +PM2 is a Node.js process manager that runs the searcher and applier as proper system daemons — no SIGTERM issues, survives reboots. + +```bash +# Install PM2 +npm install -g pm2 + +# Start both jobs (searcher runs immediately + hourly; applier stopped by default) +pm2 start ecosystem.config.cjs +pm2 stop claw-applier # keep applier off until you're ready + +# Survive reboots +pm2 save +pm2 startup # follow the printed command (requires sudo) +``` + +**PM2 cheatsheet:** +```bash +pm2 list # show all jobs + status +pm2 logs claw-searcher # tail searcher logs +pm2 logs claw-applier # tail applier logs +pm2 start claw-applier # enable applier +pm2 stop claw-applier # pause applier +pm2 restart claw-searcher # restart searcher now +``` + +Schedules (set in `ecosystem.config.cjs`): +- **Searcher**: `0 * * * *` (hourly) +- **Applier**: `0 */6 * * *` (every 6h) — stopped by default, start when ready + +### 7. Run manually ```bash node job_searcher.mjs # search now @@ -95,25 +132,6 @@ node job_applier.mjs # apply now node status.mjs # show queue + run status ``` -### 6. Schedule - -**Via OpenClaw** — set env vars before running `setup.mjs`: -```bash -export OPENCLAW_GATEWAY_URL=http://localhost:3000 -export OPENCLAW_GATEWAY_TOKEN=your_gateway_token -node setup.mjs # registers crons automatically -``` - -Crons registered: -- **Searcher**: `0 * * * *` (hourly), no timeout, enabled by default -- **Applier**: `0 */6 * * *` (every 6h), no timeout, **disabled by default** — enable when ready - -**Via system cron** — add to crontab: -``` -0 * * * * cd /path/to/claw-apply && KERNEL_API_KEY=xxx node job_searcher.mjs >> /tmp/searcher.log 2>&1 -0 */6 * * * cd /path/to/claw-apply && KERNEL_API_KEY=xxx node job_applier.mjs >> /tmp/applier.log 2>&1 -``` - ## How it works **Search** — runs your keyword searches on LinkedIn and Wellfound, paginates through results, inline-classifies each job (Easy Apply vs external ATS), filters exclusions, deduplicates, and queues new jobs. First run searches 90 days back; subsequent runs search 2 days. diff --git a/setup.mjs b/setup.mjs index e573a01..5e9f4f3 100644 --- a/setup.mjs +++ b/setup.mjs @@ -4,7 +4,7 @@ * Verifies config, tests logins, registers cron jobs * Run once after configuring: node setup.mjs */ -import { existsSync, mkdirSync, writeFileSync } from 'fs'; +import { existsSync, mkdirSync, writeFileSync, chmodSync } from 'fs'; import { dirname, resolve } from 'path'; import { fileURLToPath } from 'url'; import { loadConfig } from './lib/queue.mjs'; @@ -94,70 +94,16 @@ async function main() { await wfBrowser?.browser?.close().catch(() => {}); } - // Register cron jobs via OpenClaw gateway - const gatewayUrl = process.env.OPENCLAW_GATEWAY_URL || 'http://localhost:3000'; - const gatewayToken = process.env.OPENCLAW_GATEWAY_TOKEN; - const telegramUserId = settings.notifications?.telegram_user_id; - const searchSchedule = settings.schedules?.search || '0 * * * *'; - const applySchedule = settings.schedules?.apply || '0 */6 * * *'; - - // API keys are loaded from .env at runtime — never embedded in cron payloads - if (gatewayToken && telegramUserId) { - console.log('\nRegistering cron jobs...'); - const headers = { 'Content-Type': 'application/json', Authorization: `Bearer ${gatewayToken}` }; - - const searchJob = { - name: 'claw-apply: job searcher', - schedule: { kind: 'cron', expr: searchSchedule, tz: settings.timezone || 'America/Los_Angeles' }, - payload: { - kind: 'agentTurn', - message: `Launch the claw-apply job searcher as a detached background process: \`cd ${__dir} && nohup node job_searcher.mjs >> /tmp/claw-searcher.log 2>&1 & echo $!\`. Confirm the PID and that it started successfully.`, - timeoutSeconds: 30, - }, - delivery: { mode: 'announce', to: telegramUserId }, - sessionTarget: 'isolated', - enabled: true, - }; - - const applyJob = { - name: 'claw-apply: job applier', - schedule: { kind: 'cron', expr: applySchedule, tz: settings.timezone || 'America/Los_Angeles' }, - payload: { - kind: 'agentTurn', - message: `Launch the claw-apply job applier as a detached background process: \`cd ${__dir} && nohup node job_applier.mjs >> /tmp/claw-applier.log 2>&1 & echo $!\`. Confirm the PID and that it started successfully.`, - timeoutSeconds: 30, - }, - delivery: { mode: 'announce', to: telegramUserId }, - sessionTarget: 'isolated', - enabled: false, // disabled by default — enable when ready - }; - - try { - const sRes = await fetch(`${gatewayUrl}/api/cron/jobs`, { method: 'POST', headers, body: JSON.stringify(searchJob) }); - const sData = await sRes.json(); - console.log(sRes.ok ? ` ✅ Searcher cron registered (ID: ${sData.id})` : ` ❌ Searcher cron failed: ${JSON.stringify(sData)}`); - } catch (e) { - console.log(` ❌ Searcher cron error: ${e.message}`); - } - - try { - const aRes = await fetch(`${gatewayUrl}/api/cron/jobs`, { method: 'POST', headers, body: JSON.stringify(applyJob) }); - const aData = await aRes.json(); - console.log(aRes.ok ? ` ✅ Applier cron registered, disabled (ID: ${aData.id}) — enable when ready` : ` ❌ Applier cron failed: ${JSON.stringify(aData)}`); - } catch (e) { - console.log(` ❌ Applier cron error: ${e.message}`); - } - } else { - console.log('\n⚠️ Skipping cron registration — set OPENCLAW_GATEWAY_URL and OPENCLAW_GATEWAY_TOKEN to auto-register.'); - console.log(' Or register manually with these schedules:'); - console.log(` Search: ${searchSchedule} (timeoutSeconds: 0)`); - console.log(` Apply: ${applySchedule} (timeoutSeconds: 0, start disabled)`); - } - console.log('\n🎉 Setup complete. claw-apply is ready.'); - console.log('\nTo run manually:'); - console.log(' node job_searcher.mjs — search now'); - console.log(' node job_applier.mjs — apply now'); + console.log('\nNext: start the scheduler with PM2:'); + console.log(' npm install -g pm2'); + console.log(' pm2 start ecosystem.config.cjs'); + console.log(' pm2 stop claw-applier # keep off until ready to apply'); + console.log(' pm2 save && pm2 startup # survive reboots'); + console.log('\nRun manually:'); + console.log(' node job_searcher.mjs — search now'); + console.log(' node job_applier.mjs — apply now'); + console.log(' node status.mjs — queue + run status'); } main().catch(e => { console.error('Setup error:', e.message); process.exit(1); });