The guard in the four bunli-based CLIs inspected only tokens starting
with `--`, so an undefined short flag bypassed it: bunli discarded it,
the search ran unfiltered, and the CLI exited 0. Live against jobnet,
`search -q "sygeplejerske"` returned all 18,179 ads as a successful
search against 667 for the real `--search-string` query - the same shape
as review finding F13 that motivated the guard.
Both dash forms are now checked. Declared shorts (jobindex's -q) and
bunli's built-in -h/-v stay valid. A negative number is rejected too:
bunli discards a `-`-prefixed token rather than consuming it as the
previous flag's value, so `--radius -5` silently fell back to the
default instead of failing its own min(1) schema; a value that must
begin with a dash uses the `--flag=value` form.
Fixes#426.
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
date: job.publicationDate.slice(0, 10) trusted a TypeScript interface
claim nothing validates at runtime: apiFetch casts the JSON body, so one
ad with a null publication date threw TypeError inside the jobAds map
and the whole search exited 1 as API_ERROR. The neighboring
applicationDeadline field was already null-guarded with a 1900-01-01
sentinel. publicationDate is now typed nullable so the compiler enforces
the guard, and the ad degrades per-item to date: null. New test verified
to fail on the unfixed code with the exact production TypeError.
search guards the API's undisclosed-deadline sentinel and a test pins
it; detail dumped the raw response, so the same field for the same job
behaved two ways, and an undisclosed deadline stored via detail read as
126 years expired - /rank's sweep would retire the job on sight. All
three output formats now flow through prepareDetail. Review finding F33
(2026-08-19).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Silently discarded flags produced silently wrong results: jobdanmark
with --query (its real flag is --text) returned all 13,862 jobs as if
they matched, exit 0, empty stderr - indistinguishable from a real
result set. The four bunli CLIs get an argv preflight built from each
command's own options object; linkedin and freehire validate parsed
flags against per-command known sets. help/version still pass, and
add-portal.md's existing bogus-flag-exits-1 contract now holds for the
reference implementations contributors copy. One linkedin pin updated:
"--jobage-minutes -5" now fails as UNKNOWN_FLAG (the stray -5 token)
rather than BAD_ARG - same loud-failure invariant, earlier gate. Review
finding F13 (2026-08-19), decision approved by Mads.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds additive normalization so jobnet search output carries the cross-portal contract fields (company, location, date, deadline with the 1900-01-01 NotDisclosed sentinel mapped to null, and url). Emits the public /find-job/{jobAdId} route and corrects the skill's own stale /job/ documentation, which redirects anonymous visitors into the MitID login flow.
Co-authored-by: oscarbol09 <80536682+oscarbol09@users.noreply.github.com>
* fix(cli): send User-Agent on jobnet and jobdanmark API requests
apiFetch/apiPost hit the portals' APIs without a User-Agent header, while every other Danish-portal CLI sends one on purpose (jobbank exports USER_AGENT and its tests assert it; jobindex sets it on htmlFetch). Requests without one are rejected by the portals' bot filters.
* fix(cli): satisfy strict typecheck in user-agent regression test
* refactor(cli): reframe user-agent tests as honest self-identification
* docs(changelog): entry for #283 user-agent self-identification
Follow-up to #191: it tightened page/limit/per-page, but five filter flags still used bare z.coerce.number() and accepted negative and fractional values that were sent raw to the portals (e.g. --jobage=-5, --radius=2.5).
jobindex --jobage, jobnet --radius, jobdanmark --category/--jobtitle-id and jobbank --company now use .int().min(1), mirroring #191. Adds 8 network-free regression tests (a negative and a fractional case per flag) using the same validation-error pattern as the existing cli-flag-validation suites.
None of the board CLIs set a fetch timeout, and the retry loops react only to HTTP status codes, not to a connection that is accepted then never responds (black-holed TCP, hung TLS, stalled proxy) - so await fetch(...) never settles and the command hangs with no output and no exit. freehire's helper even documented a fast-degrade contract its try/catch didn't deliver on a mid-flight stall. Adds signal: AbortSignal.timeout(15000) to every fetch across all six CLIs, with network-free tests asserting the signal is present on each request wrapper.
By @thejesh23. Verified: 8 timeout tests pass locally with fetch stubbed (no network), and would fail on the pre-fix code.
Closes#196
Bare z.coerce.number() accepted negative and fractional values for count/pagination flags, and slice(0, limit) with a negative limit silently dropped trailing results instead of erroring. Tightens the schemas to .int().min(1) across all four Danish portal CLIs (including jobnet occupations --per-page) with network-free validation tests.
By @ayobamiseun.
The pre-#21 .gitignore's unanchored 'commands/' rule silently excluded
.agents/skills/*/cli/src/commands/ (and the tsconfigs) from the initial
release, so every clone's four Danish portal CLIs failed on import with
'Cannot find module ./commands/search.js'. #21 fixed the rule but the
files were never restored - git history has no trace of them.
Restored from the maintainer's working copies, including the updated
jobindex helpers.ts (Jobindex moved search results from the JSON
endpoint, which now returns 204, into an embedded HTML Stash blob).
Verified: all four CLIs typecheck and return live results with their
documented flags. Surfaced while reviewing #52.
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>