diff --git a/.agents/skills/jobindex-search/cli/README.md b/.agents/skills/jobindex-search/cli/README.md index a13b69a..386ed78 100644 --- a/.agents/skills/jobindex-search/cli/README.md +++ b/.agents/skills/jobindex-search/cli/README.md @@ -169,12 +169,29 @@ bun run src/cli.ts detail h1647303 --format plain ``` **Field notes:** -- `deadline` — application deadline date string (`YYYY-MM-DD`); `null` if not listed. Postings flagged "ASAP" by the portal carry no fixed deadline and also map to `null`. -- `employmentType` — e.g. `"Fastansættelse"`, `"Midlertidig ansættelse"`; `null` if not listed. -- `hours` — e.g. `"Fuldtid"`, `"Deltid"`; `null` if not listed. -- `applyUrl` — the external application URL (resolved from the Jobindex redirect link `/c?t=...`); `null` if not available. -- `description` — full plain-text job description (HTML stripped). -- All fields may be `null` if not present in the HTML. + +Jobindex serves detail pages in two shapes, and field availability differs: +a **jobindex-native** page (recognisable by its `jd-*` facts blocks) carries +company, location, an ISO deadline, employment type and hours; an **external +ATS passthrough** (the employer's hosted ad, e.g. hr-manager/Talentech, served +through jobindex) has no reliable company anchor, so `company` is `null` there +rather than the ATS brand, and location/deadline come from the ad's own +widgets when present. + +- `id` / `url` — always the jobindex id and its `jobannonce` URL, never the + page's `og:url`/canonical (on passthrough pages those point at the external + ATS, not the posting). +- `deadline` — `YYYY-MM-DD` or `null`; Danish long dates ("13. september + 2026") and `DD-MM-YYYY` widget dates are converted. +- `employmentType` / `hours` — from the native facts blocks; `null` on + passthrough pages. +- `companyUrl` — currently always `null`; no page shape carries a usable + company link. +- `applyUrl` — the Jobindex redirect link (`/c?t=...`) when present; `null` + otherwise. +- `description` — plain text of the ad body (HTML stripped), falling back to + the page's meta description when the body is empty. +- All fields except `id`, `title`, and `url` may be `null`. --- diff --git a/.agents/skills/jobindex-search/cli/src/commands/detail.ts b/.agents/skills/jobindex-search/cli/src/commands/detail.ts index 0e82094..f2dab2d 100644 --- a/.agents/skills/jobindex-search/cli/src/commands/detail.ts +++ b/.agents/skills/jobindex-search/cli/src/commands/detail.ts @@ -1,6 +1,6 @@ import { defineCommand, option } from "@bunli/core" import { z } from "zod" -import { htmlFetch, writeError, extractDivContent } from "../helpers.js" +import { htmlFetch, writeError } from "../helpers.js" const BASE_URL = "https://www.jobindex.dk" @@ -39,6 +39,13 @@ function decodeHtmlEntities(text: string): string { .replace(/"/g, '"') .replace(/'/g, "'") .replace(/'/g, "'") + // Danish letters appear as named entities in employer-hosted ad markup. + .replace(/ø/g, "ø") + .replace(/Ø/g, "Ø") + .replace(/æ/g, "æ") + .replace(/Æ/g, "Æ") + .replace(/å/g, "å") + .replace(/Å/g, "Å") // Numeric character references: decimal (é) and hexadecimal (é). .replace(/(\d+);/g, (_, dec) => numericEntity(parseInt(dec, 10))) .replace(/[xX]([0-9a-fA-F]+);/g, (_, hex) => numericEntity(parseInt(hex, 16))) @@ -72,150 +79,169 @@ function buildUrl(idOrUrl: string): { url: string; id: string } { return { url, id: idOrUrl } } -/** - * Parse the detail HTML page using regex to avoid node-html-parser nesting bugs. - */ -function parseDetailPage(html: string, url: string, id: string): DetailResult { - // Title: extract from
inside a jobindex-native jd-* facts block. */ +function jdBlockValue(html: string, cls: string): string | null { + const m = html.match(new RegExp(`class="${cls}"[^>]*>[\\s\\S]*?
]*>([\\s\\S]*?)
`, "i")) + return m ? decodeHtmlEntities(stripTags(m[1])).replace(/\s+/g, " ").trim() || null : null +} + +/** Drop head/script/style content so text scans never read CSS or JS. */ +function visibleHtml(html: string): string { + return html + .replace(//gi, "") + .replace(/