feat: rich status report — searcher/applier last run time, timeAgo, per-run metadata files

This commit is contained in:
2026-03-06 00:33:03 +00:00
parent 5382e8432e
commit 1920df51a4
3 changed files with 69 additions and 9 deletions

View File

@@ -11,6 +11,7 @@ import { fileURLToPath } from 'url';
const __dir = dirname(fileURLToPath(import.meta.url));
import { getJobsByStatus, updateJobStatus, appendLog, loadConfig } from './lib/queue.mjs';
import { writeFileSync } from 'fs';
import { acquireLock } from './lib/lock.mjs';
import { createBrowser } from './lib/browser.mjs';
import { FormFiller } from './lib/form_filler.mjs';
@@ -133,6 +134,12 @@ async function main() {
console.log(`\n${summary.replace(/\*/g, '')}`);
await sendTelegram(settings, summary);
// Write last-run metadata for status.mjs
writeFileSync(resolve(__dir, 'data/applier_last_run.json'), JSON.stringify({
finished_at: Date.now(),
...results,
}, null, 2));
console.log('\n✅ Apply run complete');
return results;
}

View File

@@ -10,6 +10,7 @@ import { fileURLToPath } from 'url';
const __dir = dirname(fileURLToPath(import.meta.url));
import { addJobs, loadQueue, loadConfig } from './lib/queue.mjs';
import { writeFileSync } from 'fs';
import { acquireLock } from './lib/lock.mjs';
import { createBrowser } from './lib/browser.mjs';
import { verifyLogin as liLogin, searchLinkedIn } from './lib/linkedin.mjs';
@@ -135,6 +136,15 @@ async function main() {
console.log(`\n${summary.replace(/\*/g, '')}`);
if (totalAdded > 0) await sendTelegram(settings, summary);
// Write last-run metadata for status.mjs
writeFileSync(resolve(__dir, 'data/searcher_last_run.json'), JSON.stringify({
finished_at: Date.now(),
added: totalAdded,
seen: totalSeen,
skipped_dupes: totalSeen - totalAdded,
platforms: platformsRun,
}, null, 2));
console.log('\n✅ Search complete');
return { added: totalAdded, seen: totalSeen };
}

View File

@@ -22,6 +22,11 @@ function isRunning(name) {
try { process.kill(pid, 0); return true; } catch { return false; }
}
function readLastRun(name) {
const path = resolve(__dir, `data/${name}_last_run.json`);
return readJson(path);
}
function buildStatus() {
const queue = readJson(resolve(__dir, 'data/jobs_queue.json')) || [];
const log = readJson(resolve(__dir, 'data/applications_log.json')) || [];
@@ -51,12 +56,17 @@ function buildStatus() {
.sort((a, b) => (b.applied_at || 0) - (a.applied_at || 0));
const lastApplied = applied[0] || null;
const searcherLastRun = readLastRun('searcher');
const applierLastRun = readLastRun('applier');
return {
searcher: {
running: isRunning('searcher'),
last_run: searcherLastRun,
},
applier: {
running: isRunning('applier'),
last_run: applierLastRun,
},
queue: {
total: queue.length,
@@ -80,30 +90,63 @@ function buildStatus() {
};
}
function timeAgo(ms) {
if (!ms) return 'never';
const diff = Date.now() - ms;
const mins = Math.floor(diff / 60000);
if (mins < 1) return 'just now';
if (mins < 60) return `${mins}m ago`;
const hrs = Math.floor(mins / 60);
if (hrs < 24) return `${hrs}h ago`;
return `${Math.floor(hrs / 24)}d ago`;
}
function formatReport(s) {
const q = s.queue;
const searcherStatus = s.searcher.running ? '🔄 Running now' : '⏸️ Idle';
const applierStatus = s.applier.running ? '🔄 Running now' : '⏸️ Idle';
// Searcher section
const sr = s.searcher;
const searcherLine = sr.running
? `🔄 Running now — ${q.total} jobs found so far`
: `⏸️ Last ran ${timeAgo(sr.last_run?.finished_at)}`;
const lastRunDetail = sr.last_run && !sr.running
? ` Found ${sr.last_run.added} new jobs (${sr.last_run.seen} seen, ${sr.last_run.skipped_dupes || 0} dupes)`
: null;
// Applier section
const ar = s.applier;
const applierLine = ar.running
? `🔄 Running now`
: `⏸️ Last ran ${timeAgo(ar.last_run?.finished_at)}`;
const lastApplierDetail = ar.last_run && !ar.running
? ` Applied ${ar.last_run.submitted} jobs that run`
: null;
const lines = [
`📊 *claw-apply Status*`,
``,
`🔍 *Searcher:* ${searcherStatus}`,
`🚀 *Applier:* ${applierStatus}`,
`🔍 *Searcher:* ${searcherLine}`,
];
if (lastRunDetail) lines.push(lastRunDetail);
lines.push(`🚀 *Applier:* ${applierLine}`);
if (lastApplierDetail) lines.push(lastApplierDetail);
lines.push(
``,
`📋 *Queue — ${q.total} total jobs*`,
` 🆕 New (pending): ${q.new}`,
` 🆕 Ready to apply: ${q.new}`,
` ✅ Applied: ${q.applied}`,
` 💬 Needs your answer: ${q.needs_answer}`,
` ❌ Failed: ${q.failed}`,
` 🚫 Recruiter-only: ${q.skipped_recruiter}`,
` ⏭️ No Easy Apply: ${q.skipped_no_easy_apply}`,
` 🌐 External ATS: ${q.skipped_external}`,
];
);
if (Object.keys(s.ats_breakdown).length > 0) {
const sorted = Object.entries(s.ats_breakdown).sort((a, b) => b[1] - a[1]);
lines.push(``, `🌐 *ATS Breakdown:*`);
lines.push(``, `🌐 *External ATS — ${q.skipped_external} jobs (saved for later):*`);
for (const [platform, count] of sorted) {
lines.push(`${platform}: ${count}`);
}
@@ -111,8 +154,8 @@ function formatReport(s) {
if (s.last_applied) {
const la = s.last_applied;
const when = la.at ? new Date(la.at).toLocaleString() : 'unknown time';
lines.push(``, `📬 *Last applied:* ${la.title} @ ${la.company} (${la.platform})${when}`);
const when = la.at ? timeAgo(la.at) : 'unknown';
lines.push(``, `📬 *Last applied:* ${la.title} @ ${la.company}${when}`);
} else {
lines.push(``, `📬 *Last applied:* None yet`);
}