Files
ai-job-search/.agents/skills/jobindex-search/cli
Rudy Mizrahi Celekli 3b16f80595 docs(jobindex-search): align search examples with the actual CLI contract (#579)
* docs(jobindex-search): align search examples with the actual CLI contract

The posted-today skill example omits the query that the shipped CLI requires, so copying it exits MISSING_REQUIRED. Supply a keyword and make the required flag explicit. Replace stale JSON-endpoint/card-parser search documentation with the implemented HTML Stash route and emitted field behavior.

Run the example read directly from SKILL.md through a real CLI subprocess with loopback HTTP fixtures. Keep a separate query/page routing control; no live portal requests or production-code changes.

* test(jobindex-search): cover Windows documentation newlines
2026-10-18 07:31:12 +02:00
..

jobindex-cli

CLI for searching jobs on Jobindex.dk.

Base URL: https://www.jobindex.dk/ Authentication: None required. Format: The search HTML embeds a JSON Stash payload. The CLI extracts its structured search results and emits JSON, table, or plain output.


Installation

cd .agents/skills/jobindex-search/cli
bun install

Commands

Command Description
search Search for job listings
detail Fetch full detail for a single job listing

All commands accept --format json|table|plain (default: json). All errors are written to stderr as { "error": "...", "code": "..." } and the process exits with code 1.


search — Search for job listings

Endpoint: GET https://www.jobindex.dk/jobsoegning

bun run src/cli.ts search --query "<text>" [flags]

The portal returns 20 results per page (fixed — no --per-page flag). The CLI reads searchResponse.results from the page's var Stash = {...} script. --query is required; a query-free search exits with MISSING_REQUIRED before fetching.

Flags

Flag Type Default Description
--query / -q string — Required. Keyword search query (e.g. python, grafisk designer)
--page number 1 Page number (1-indexed)
--jobage number 9999 Max age of posting in days: 1, 7, 14, 30, or 9999 (all)
--sort string score Sort order: score (relevance) or date (newest first)
--limit number — Cap total results returned by the CLI (client-side)
--format string json Output format: json, table, plain

Sort options

Value Description
score Relevance / best match (default)
date Newest postings first

jobage options

Value Description
1 Posted today
7 Last 7 days
14 Last 14 days
30 Last 30 days
9999 All time (default)

Example

# Search for Python jobs posted in the last 7 days, sorted by date
bun run src/cli.ts search --query python --jobage 7 --sort date

# Search for "grafisk designer" jobs — show first 5 results
bun run src/cli.ts search --query "grafisk designer" --limit 5

# Page 2 of results for data engineer
bun run src/cli.ts search --query "data engineer" --page 2 --format table

Response shape

{
  "meta": {
    "total": 237,
    "page": 1,
    "perPage": 20
  },
  "results": [
    {
      "id": "h1647303",
      "title": "Data Engineer til opbygning af Gavefabrikkens dataplatform",
      "company": "Gavefabrikken",
      "companyUrl": "https://www.gavefabrikken.dk/",
      "location": "Valby",
      "date": "2026-03-12",
      "deadline": "2026-04-01",
      "url": "https://www.jobindex.dk/jobannonce/h1647303",
      "description": null
    }
  ]
}

Field notes:

  • id — string ID prefixed with h (e.g. h1647303). Use this with the detail command.
  • company — company name; may be null for some aggregated listings.
  • companyUrl — company homepage URL; may be null if not present.
  • location — city or area; may be null if not listed.
  • date — the posting's firstdate value; may be null.
  • deadline — the first ten characters of apply_deadline, falling back to lastdate; null when apply_deadline_asap is set or no date is present.
  • description — currently always null in search results. Use detail for the full ad.
  • url — canonical https://www.jobindex.dk/jobannonce/{id} URL; a result without an ID falls back to its share_url or url.
  • total in meta — numeric searchResponse.hitcount, falling back to the number of returned results if the count is absent.

Note on area filtering: The Jobindex API does not reliably support area/region filtering via query parameters. area and geoareaid params are silently ignored. To filter by location, use --query with a city name (e.g. --query "python aarhus") or apply --limit and filter the JSON output externally.


detail — Fetch full job listing detail

URL: https://www.jobindex.dk/jobannonce/{id}/{slug}

bun run src/cli.ts detail <id> [--format json|plain]

The id is the job ID from search results (e.g. h1647303). The slug is optional — the CLI fetches the canonical URL by first constructing https://www.jobindex.dk/jobannonce/{id} and following any redirect, or by using the full URL from the url field in search results.

You may also pass the full URL directly as the id argument.

Flags

Flag Type Default Description
--format string json Output format: json, plain

Example

# Using ID from search results
bun run src/cli.ts detail h1647303

# Using full URL
bun run src/cli.ts detail "https://www.jobindex.dk/jobannonce/h1647303/data-engineer-til-opbygning-af-gavefabrikkens-dataplatform"

# Plain text output
bun run src/cli.ts detail h1647303 --format plain

Response shape

{
  "id": "h1647303",
  "title": "Data Engineer til opbygning af Gavefabrikkens dataplatform",
  "company": "Gavefabrikken",
  "companyUrl": "https://www.gavefabrikken.dk/",
  "location": "Valby, København",
  "date": "2026-03-12",
  "deadline": "2026-04-01",
  "employmentType": "Fastansættelse",
  "hours": "Fuldtid",
  "applyUrl": "https://www.gavefabrikken.dk/jobs/apply/123",
  "url": "https://www.jobindex.dk/jobannonce/h1647303/data-engineer-til-opbygning-af-gavefabrikkens-dataplatform",
  "description": "Full job description text here..."
}

Field notes:

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.

Error handling

All errors are written to stderr in JSON format and exit with code 1:

{ "error": "Job not found", "code": "NOT_FOUND" }
{ "error": "API request failed: 500 Internal Server Error", "code": "API_ERROR" }
{ "error": "Failed to parse job listing HTML", "code": "PARSE_ERROR" }
{ "error": "--query is required", "code": "MISSING_REQUIRED" }

URL construction

Job detail pages on jobindex.dk:

  • https://www.jobindex.dk/jobannonce/{id}/{slug}

Search emits the canonical URL without a slug. When calling detail with an ID or full posting URL, the CLI rebuilds https://www.jobindex.dk/jobannonce/{id} and follows redirects.


Parsing notes

Search results from Stash

The former /jobsoegning.json endpoint no longer provides the search payload. The HTML search page embeds structured data in a var Stash = {...} script. The CLI locates a nested searchResponse object with a results array, for example under jobsearch.result_app.storeData.searchResponse:

{
  "hitcount": 237,
  "results": [
    {
      "tid": "h1647303",
      "headline": "Data Engineer",
      "company": { "name": "Gavefabrikken", "homeurl": "https://www.gavefabrikken.dk/" },
      "area": "Valby",
      "firstdate": "2026-03-12",
      "apply_deadline": "2026-04-01T00:00:00"
    }
  ]
}

The active search path maps these fields directly; it does not parse job-card HTML or hitcount_html. extractStash and parseSearchPage in src/helpers.ts implement this path. The legacy parseJobCards helper is not used by search.