Files
jobyssey/.agents/skills/jobsdb-search/cli/src/helpers.ts
T

116 lines
4.0 KiB
TypeScript
Raw Normal View History

2026-08-26 10:35:34 -04:00
import { Database } from "bun:sqlite"
import { existsSync } from "node:fs"
export const DB_PATH = process.env.JOBSDB_PATH ?? "documents/postings/jobs.db"
export const REMOTE_CLASS: Record<string, string> = { remote: "R1", "remote-global": "R1", "remote-regional": "R2", hybrid: "H", onsite: "O" }
export const CLASS_LABEL: Record<string, string> = { R1: "Remote (global)", R2: "Remote (regional)", H: "Hybrid", O: "Onsite" }
export interface Row {
posting_id: string
company_id: string | null
company_name: string | null
title: string | null
seniority: string | null
posted_date: string | null
location_text: string | null
location_class: string | null
location_country: string | null
salary_min: string | null
salary_max: string | null
salary_currency: string | null
url: string | null
source_ats: string | null
scrape_timestamp: string | null
target_sector: string | null
crypto_native: string | null
priority_tier: number | null
}
export interface Job {
id: string
title: string | null
company: string | null
location: string | null
date: string | null
date_approximate: boolean
url: string | null
description: null
remote: string | null
seniority: string | null
salary: string | null
sector: string | null
crypto_native: boolean
priority_tier: number | null
ats: string | null
snapshot: string | null
}
export function openDb(): Database {
if (!existsSync(DB_PATH)) {
writeError(`database not found at ${DB_PATH} (set JOBSDB_PATH)`, "NO_DB")
process.exit(1)
}
return new Database(DB_PATH, { readonly: true })
}
/** "Posted 8 Days Ago" + scrape_timestamp -> ISO date. "30+" is a floor, flagged approximate. */
export function resolveDate(posted: string | null, scraped: string | null): { date: string | null; approximate: boolean } {
if (!posted) return { date: null, approximate: false }
// greenhouse/ashby/lever store ISO timestamps; workday stores relative text.
if (/^\d{4}-\d{2}-\d{2}/.test(posted)) return { date: posted.slice(0, 10), approximate: false }
if (!scraped) return { date: null, approximate: false }
const base = new Date(scraped)
if (isNaN(base.getTime())) return { date: null, approximate: false }
const m = /(\d+)(\+?)\s*Days?/i.exec(posted)
let days: number
let approximate = false
if (/Today/i.test(posted)) days = 0
else if (/Yesterday/i.test(posted)) days = 1
else if (m) { days = Number(m[1]); approximate = m[2] === "+" }
else return { date: null, approximate: false }
base.setUTCDate(base.getUTCDate() - days)
return { date: base.toISOString().slice(0, 10), approximate }
}
export function toJob(r: Row): Job {
const { date, approximate } = resolveDate(r.posted_date, r.scrape_timestamp)
const salary = r.salary_min && r.salary_max ? `${r.salary_min}-${r.salary_max} ${r.salary_currency ?? ""}`.trim() : null
return {
id: r.posting_id,
title: r.title || null,
company: r.company_name || null,
location: r.location_text || null,
date,
date_approximate: approximate,
url: r.url || null,
description: null,
remote: r.location_class ? CLASS_LABEL[r.location_class] ?? null : null,
seniority: r.seniority || null,
salary,
sector: r.target_sector || null,
crypto_native: r.crypto_native === "True",
priority_tier: r.priority_tier ?? null,
ats: r.source_ats || null,
snapshot: r.scrape_timestamp ? r.scrape_timestamp.slice(0, 10) : null,
}
}
export function writeError(error: string, code: string): void {
process.stderr.write(JSON.stringify({ error, code }) + "\n")
}
export function renderTable(jobs: Job[]): string {
if (jobs.length === 0) return "No results."
const rows = jobs.map((j) =>
[
(j.title ?? "").slice(0, 44).padEnd(44),
(j.company ?? "-").slice(0, 22).padEnd(22),
(j.location ?? "-").slice(0, 22).padEnd(22),
(j.remote ?? "-").slice(0, 16).padEnd(16),
j.date ? j.date + (j.date_approximate ? "~" : " ") : "-".padEnd(11),
j.url ?? "",
].join(" "),
)
return rows.join("\n")
}