* test(cli): pin the 429/5xx retry contract in all six portal CLIs The portal-skill contract requires backoff on 429/5xx, and every CLI implements it - a retry loop with exponential delay and jitter - but nothing verified the loops actually retry, stop retrying on plain 4xx, or give up after the documented attempt budget. A regression here is invisible: a CLI that stops retrying still works on every healthy request. Each CLI gains tests/retry-backoff.test.ts, network-free, using the request-timeout.test.ts pattern from #197 (import the fetch wrapper, stub globalThis.fetch): a stubbed fetch counts attempts, and a stubbed setTimeout fires immediately so the exhaustion case does not sleep through the real 500ms -> 5s/8s backoff schedule (tests run in milliseconds, not ~17s). Three assertions per fetch wrapper, adapted to each CLI's documented semantics: - a 429 is retried and the next attempt's result is returned - a plain 4xx is not retried (jobbank's fetchWithUA RETURNS the response for callers to handle - pinned as such; linkedin's htmlFetch returns "" on 404; freehire's apiGet returns null) - persistent 5xx gives up after the initial attempt plus six retries (7 fetch calls) with the status in the error freehire additionally pins its documented graceful-degradation contract: a connection failure fails fast with no retry. jobdanmark exercises both apiFetch and apiPost, which carry separate copies of the loop that could drift apart. Mutation-checked: changing maxRetries in jobindex makes the exhaustion test fail, so the tests distinguish the current behavior from a silently altered one. Verified: bun test green in all six CLIs (jobindex 19, jobnet 20, jobbank 20, jobdanmark 21, linkedin 21, freehire 31 - 0 fail); tsc --noEmit clean in all six; python3 tools/lint_skills.py OK. * test(jobindex): pin apiFetch's retry loop alongside htmlFetch's Review parity gap: jobindex carries two separate copies of the retry loop and only htmlFetch was exercised, so apiFetch's retry budget could drift silently - the same situation jobdanmark's test already handles for its apiFetch/apiPost pair. apiFetch gets the same three assertions, adapted to its documented semantics (JSON return on success, throw on plain 4xx): a 429 is retried and the next attempt's parsed body returned, a 400 is not retried, persistent 5xx gives up after the initial attempt plus six retries (7 calls). Mutation-checked on the new axis: changing apiFetch's maxRetries (the file's first copy of the loop) fails its exhaustion test while htmlFetch's tests stay green, so each wrapper is now pinned independently. Verified: bun test 30 pass / 0 fail (full jobindex suite); tsc --noEmit clean.
linkedin-cli
CLI for searching jobs on LinkedIn's public job listings, for any country/region (and remote), across any sector.
Data source: LinkedIn jobs-guest endpoints (seeMoreJobPostings/search and jobPosting/<id>).
Authentication: None required.
Dependencies: None (plain bun + fetch). bun install is optional and only pulls dev type defs.
Personal use only. This uses LinkedIn's public job pages; automated access is against LinkedIn's Terms of Service. Keep volume low, don't use it commercially or for bulk data collection, and run it on your own responsibility.
Installation
cd .agents/skills/linkedin-search/cli
bun install # optional — only installs TypeScript dev types
The CLI runs without any install because it has zero runtime dependencies.
Commands
| Command | Description |
|---|---|
search |
Search for job listings (--location required) |
detail |
Fetch full detail for a single job listing |
search accepts --format json|table|plain (default json); detail accepts --format json|plain.
All errors are written to stderr as { "error": "...", "code": "..." } with exit code 1.
Quick examples
# Software roles in Hyderabad, last 7 days
bun run src/cli.ts search -q "backend engineer" -l "Hyderabad, Telangana, India" --jobage 7 --format table
# Design roles in London
bun run src/cli.ts search -q "product designer" -l "London, United Kingdom" --format table
# Fully remote
bun run src/cli.ts search -q "technical writer" -l "Remote" --remote remote --format table
# Full detail for one job
bun run src/cli.ts detail 4426311357 --format plain
See ../SKILL.md for the full flag reference and the Terms-of-Service note.
Search flags
| Flag | Alias | Description |
|---|---|---|
--location |
-l |
Required. Place string, e.g. "Mumbai, Maharashtra, India", "Berlin, Germany", "Remote". |
--query |
-q |
Keywords (title / skill / role). Recommended. |
--jobage |
Posted within N days: 1, 7, 14, 30. |
|
--remote |
remote | hybrid | onsite. |
|
--page |
1-indexed page (10 results/page). | |
--limit |
-n |
Cap results emitted. |
--format |
json | table | plain. |