build a small python cli tool called `usajobs.py` for exploring usajobs results. goal: - query the official usajobs api - apply strict local filters - show results in a readable terminal table - let me select rows to export into an org-mode file stack: - python 3.11+ - click for cli args - requests for api - rich for table + row selection prompt - pathlib/json/csv stdlib only otherwise env vars: - USAJOBS_EMAIL - USAJOBS_KEY basic command: `python jobs.py search --location "Washington, DC" --radius 25 --salary-min 150 --grade-min 15 --grade-max 15 --series 2210 --series 0340 --clearance 3 --clearance 4` option behavior: - --radius 25 means 25 miles - --salary-min 150 means $150,000 - --grade-min/--grade-max filter locally against low/high grade - --series may repeat; pass to api as semicolon list - --clearance may repeat; pass to api as semicolon list - --pay-plan may repeat, default gs and gg - --limit defaults 100 - --out defaults jobs.org - --cache-dir defaults .cache/usajobs search behavior: 1. call https://data.usajobs.gov/api/search using official headers: host: data.usajobs.gov user-agent: $USAJOBS_EMAIL authorization-key: $USAJOBS_KEY 2. request fields=full, resultsperpage=500, sortfield=opendate, sortdirection=desc 3. cache raw json response per query/page under .cache/usajobs 4. apply local filters after fetching: - pay plan in allowed pay plans - low_grade >= grade_min - high_grade <= grade_max - salary max >= salary_min, or salary min >= salary_min if max absent - location string contains/near requested location as available 5. output table with columns: idx, title, agency, grade, salary, location, close date, clearance match, url 6. selection/export: - after displaying table, allow user to arrow/highlight and mark for export - pick sensible defaults, eg x or m for mark, u for unmark, a for all, e for export - export selected jobs to new file with short slug name + datetime stamp - org output format: ``` ** [[url][link]] :properties: :agency: :grade: - :close_date: :end: salary: location: travel: clearance: *** posting ``` 7. (stretch) Cache each query, allow arrow/scroll through them like a cli, recall, or save filters. could be too much. implementation notes: - write clean functional code: - build_params() - fetch_page() - fetch_all() - normalize_job() - passes_filters() - render_table() - parse_selection() - export_org() - normalize both official api shape and frontend-ish shape if present: - api jobs may use MatchedObjectDescriptor - details may be under UserArea.Details - raw posting text should combine title, summary, duties, requirements, qualifications, evaluations, other info, key requirements. - shortened job title should be max 80 chars, strip all-caps screaming where reasonable, preserve meaning. - include helpful errors if env vars missing. - include a --offline flag that only reads cached json and does not call api. - include a --debug flag that prints api params and counts before/after filtering. acceptance test: - running the command with `--salary-min 150 --grade-min 15 --grade-max 15 --radius 25` should not show gs/gg-13 jobs after local filtering. - selecting `none` exits without writing.