atsfill 0.0.5.dev139__tar.gz → 0.0.5.dev141__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (58) hide show
  1. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/PKG-INFO +4 -5
  2. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/README.md +3 -4
  3. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/pyproject.toml +1 -1
  4. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/pyproject.toml.orig +1 -1
  5. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/commands.py +1 -26
  6. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/registry.py +33 -100
  7. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/results.py +1 -5
  8. atsfill-0.0.5.dev141/src/atsfill/registry.toml +385 -0
  9. atsfill-0.0.5.dev139/src/atsfill/registry.toml +0 -442
  10. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/LICENSE +0 -0
  11. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/__init__.py +0 -0
  12. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/__init__.py +0 -0
  13. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/ashby.py +0 -0
  14. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/bamboohr.py +0 -0
  15. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/breezy.py +0 -0
  16. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/dayforce.py +0 -0
  17. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/eightfold.py +0 -0
  18. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/greenhouse.py +0 -0
  19. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/gupy.py +0 -0
  20. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/hireology.py +0 -0
  21. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/hiringthing.py +0 -0
  22. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/hrmdirect.py +0 -0
  23. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/isolvedhire.py +0 -0
  24. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/jazzhr.py +0 -0
  25. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/join.py +0 -0
  26. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/lever.py +0 -0
  27. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/manatal.py +0 -0
  28. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/oracle.py +0 -0
  29. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/paycor.py +0 -0
  30. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/paylocity.py +0 -0
  31. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/personio.py +0 -0
  32. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/phenom.py +0 -0
  33. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/pinpoint.py +0 -0
  34. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/recruitee.py +0 -0
  35. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/rippling.py +0 -0
  36. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/smartrecruiters.py +0 -0
  37. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/successfactors.js +0 -0
  38. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/successfactors.py +0 -0
  39. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/taleo.py +0 -0
  40. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/teamtailor.py +0 -0
  41. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/ukg.py +0 -0
  42. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/workable.py +0 -0
  43. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/workday.py +0 -0
  44. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/wttj.py +0 -0
  45. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/adapters/zohorecruit.py +0 -0
  46. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/cli.py +0 -0
  47. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/__init__.py +0 -0
  48. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/accounts.py +0 -0
  49. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/adapter.py +0 -0
  50. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/attempts.py +0 -0
  51. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/browser.py +0 -0
  52. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/conditions.py +0 -0
  53. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/html.py +0 -0
  54. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/matching.py +0 -0
  55. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/otp.py +0 -0
  56. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/passwords.py +0 -0
  57. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/core/paths.py +0 -0
  58. {atsfill-0.0.5.dev139 → atsfill-0.0.5.dev141}/src/atsfill/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atsfill
3
- Version: 0.0.5.dev139
3
+ Version: 0.0.5.dev141
4
4
  Summary: Read, dry-run and submit job application forms on hiring systems: Ashby, Greenhouse, Teamtailor, Welcome to the Jungle.
5
5
  Keywords: job-applications,ats,ashby,greenhouse,teamtailor,ai-agents,cli
6
6
  Author: shbernal
@@ -57,10 +57,10 @@ It is built for agents with a shell: every command prints one JSON document and
57
57
  ## Supported hiring systems
58
58
 
59
59
  <!-- badges -->
60
- ![oracle: dry run verified 2026-10-20](https://img.shields.io/badge/oracle-dry%20run%20verified%202026--10--20-7aa2f7) ![smartrecruiters: dry run verified 2026-10-20](https://img.shields.io/badge/smartrecruiters-dry%20run%20verified%202026--10--20-7aa2f7) ![greenhouse: dry run verified 2026-10-19](https://img.shields.io/badge/greenhouse-dry%20run%20verified%202026--10--19-7aa2f7) ![manatal: dry run verified 2026-10-20](https://img.shields.io/badge/manatal-dry%20run%20verified%202026--10--20-7aa2f7) ![paylocity: dry run verified 2026-10-20](https://img.shields.io/badge/paylocity-dry%20run%20verified%202026--10--20-7aa2f7) ![dayforce: dry run verified 2026-10-20](https://img.shields.io/badge/dayforce-dry%20run%20verified%202026--10--20-7aa2f7) ![workable: dry run verified 2026-10-20](https://img.shields.io/badge/workable-dry%20run%20verified%202026--10--20-7aa2f7) ![ashby: send verified 2026-10-19](https://img.shields.io/badge/ashby-send%20verified%202026--10--19-9ece6a) ![bamboohr: dry run verified 2026-10-21](https://img.shields.io/badge/bamboohr-dry%20run%20verified%202026--10--21-7aa2f7) ![teamtailor: send verified 2026-10-20](https://img.shields.io/badge/teamtailor-send%20verified%202026--10--20-9ece6a) ![jazzhr: dry run verified 2026-10-20](https://img.shields.io/badge/jazzhr-dry%20run%20verified%202026--10--20-7aa2f7) ![lever: dry run verified 2026-10-20](https://img.shields.io/badge/lever-dry%20run%20verified%202026--10--20-7aa2f7) ![hireology: dry run verified 2026-10-20](https://img.shields.io/badge/hireology-dry%20run%20verified%202026--10--20-7aa2f7) ![paycor: dry run verified 2026-10-20](https://img.shields.io/badge/paycor-dry%20run%20verified%202026--10--20-7aa2f7) ![zohorecruit: dry run verified 2026-10-22](https://img.shields.io/badge/zohorecruit-dry%20run%20verified%202026--10--22-7aa2f7) ![phenom: dry run verified 2026-10-21](https://img.shields.io/badge/phenom-dry%20run%20verified%202026--10--21-7aa2f7) ![breezy: dry run verified 2026-10-21](https://img.shields.io/badge/breezy-dry%20run%20verified%202026--10--21-7aa2f7) ![recruitee: dry run verified 2026-10-25](https://img.shields.io/badge/recruitee-dry%20run%20verified%202026--10--25-7aa2f7) ![rippling: dry run verified 2026-10-22](https://img.shields.io/badge/rippling-dry%20run%20verified%202026--10--22-7aa2f7) ![personio: dry run verified 2026-10-22](https://img.shields.io/badge/personio-dry%20run%20verified%202026--10--22-7aa2f7) ![eightfold: dry run verified 2026-10-22](https://img.shields.io/badge/eightfold-dry%20run%20verified%202026--10--22-7aa2f7) ![pinpoint: dry run verified 2026-10-22](https://img.shields.io/badge/pinpoint-dry%20run%20verified%202026--10--22-7aa2f7) ![hiringthing: dry run verified 2026-10-22](https://img.shields.io/badge/hiringthing-dry%20run%20verified%202026--10--22-7aa2f7) ![join: dry run verified 2026-10-26](https://img.shields.io/badge/join-dry%20run%20verified%202026--10--26-7aa2f7) ![wttj: send verified 2026-10-20](https://img.shields.io/badge/wttj-send%20verified%202026--10--20-9ece6a) ![workday: dry run verified 2026-10-24](https://img.shields.io/badge/workday-dry%20run%20verified%202026--10--24-7aa2f7) ![successfactors: dry run verified 2026-10-26](https://img.shields.io/badge/successfactors-dry%20run%20verified%202026--10--26-7aa2f7) ![isolvedhire: dry run verified 2026-10-26](https://img.shields.io/badge/isolvedhire-dry%20run%20verified%202026--10--26-7aa2f7) ![gupy: dry run verified 2026-10-26](https://img.shields.io/badge/gupy-dry%20run%20verified%202026--10--26-7aa2f7) ![ukg: unsupported 2026-10-26](https://img.shields.io/badge/ukg-unsupported%202026--10--26-e0af68)
60
+ ![oracle: supported 2026-10-20](https://img.shields.io/badge/oracle-supported%202026--10--20-9ece6a) ![smartrecruiters: supported 2026-10-20](https://img.shields.io/badge/smartrecruiters-supported%202026--10--20-9ece6a) ![greenhouse: supported 2026-10-19](https://img.shields.io/badge/greenhouse-supported%202026--10--19-9ece6a) ![manatal: supported 2026-10-20](https://img.shields.io/badge/manatal-supported%202026--10--20-9ece6a) ![paylocity: supported 2026-10-20](https://img.shields.io/badge/paylocity-supported%202026--10--20-9ece6a) ![dayforce: supported 2026-10-20](https://img.shields.io/badge/dayforce-supported%202026--10--20-9ece6a) ![workable: supported 2026-10-20](https://img.shields.io/badge/workable-supported%202026--10--20-9ece6a) ![ashby: supported 2026-10-19](https://img.shields.io/badge/ashby-supported%202026--10--19-9ece6a) ![bamboohr: supported 2026-10-21](https://img.shields.io/badge/bamboohr-supported%202026--10--21-9ece6a) ![teamtailor: supported 2026-10-25](https://img.shields.io/badge/teamtailor-supported%202026--10--25-9ece6a) ![jazzhr: supported 2026-10-20](https://img.shields.io/badge/jazzhr-supported%202026--10--20-9ece6a) ![lever: supported 2026-10-20](https://img.shields.io/badge/lever-supported%202026--10--20-9ece6a) ![hireology: supported 2026-10-20](https://img.shields.io/badge/hireology-supported%202026--10--20-9ece6a) ![paycor: supported 2026-10-20](https://img.shields.io/badge/paycor-supported%202026--10--20-9ece6a) ![zohorecruit: supported 2026-10-22](https://img.shields.io/badge/zohorecruit-supported%202026--10--22-9ece6a) ![phenom: supported 2026-10-21](https://img.shields.io/badge/phenom-supported%202026--10--21-9ece6a) ![breezy: supported 2026-10-21](https://img.shields.io/badge/breezy-supported%202026--10--21-9ece6a) ![recruitee: supported 2026-10-25](https://img.shields.io/badge/recruitee-supported%202026--10--25-9ece6a) ![rippling: supported 2026-10-22](https://img.shields.io/badge/rippling-supported%202026--10--22-9ece6a) ![personio: supported 2026-10-22](https://img.shields.io/badge/personio-supported%202026--10--22-9ece6a) ![eightfold: supported 2026-10-22](https://img.shields.io/badge/eightfold-supported%202026--10--22-9ece6a) ![pinpoint: supported 2026-10-22](https://img.shields.io/badge/pinpoint-supported%202026--10--22-9ece6a) ![hiringthing: supported 2026-10-22](https://img.shields.io/badge/hiringthing-supported%202026--10--22-9ece6a) ![join: supported 2026-10-26](https://img.shields.io/badge/join-supported%202026--10--26-9ece6a) ![wttj: supported 2026-10-24](https://img.shields.io/badge/wttj-supported%202026--10--24-9ece6a) ![workday: supported 2026-10-24](https://img.shields.io/badge/workday-supported%202026--10--24-9ece6a) ![successfactors: supported 2026-10-26](https://img.shields.io/badge/successfactors-supported%202026--10--26-9ece6a) ![isolvedhire: supported 2026-10-26](https://img.shields.io/badge/isolvedhire-supported%202026--10--26-9ece6a) ![gupy: supported 2026-10-26](https://img.shields.io/badge/gupy-supported%202026--10--26-9ece6a) ![ukg: in scope 2026-10-26](https://img.shields.io/badge/ukg-in%20scope%202026--10--26-e0af68)
61
61
  <!-- /badges -->
62
62
 
63
- Each badge is a hiring system atsfill has an adapter for, with how far it was verified and when: a dry run, or a real send.
63
+ Each badge is a hiring system atsfill has an adapter for: `supported` once a dry run passed on five employers, with its date.
64
64
  [The systems](docs/adapters/README.md) lists which ones need an account, to read the form or only to send it.
65
65
  Any other system returns `unsupported`, so the consumer can fall back to driving a browser itself.
66
66
 
@@ -127,8 +127,7 @@ Every push to `main` that passes CI is also published as a dev release; `uvx --p
127
127
  ```
128
128
 
129
129
  A dry run is not invisible: some systems save answers or the resume as they are typed.
130
- `atsfill status` lists each system's side effects, whether it is allowed to submit, and how far atsfill has verified it: [the systems](docs/adapters/README.md).
131
- A dry run or a `send` through an adapter no send was verified on carries a `warning`; the send goes ahead anyway.
130
+ `atsfill status` lists each system's side effects, whether it is allowed to submit, and its scope: [the systems](docs/adapters/README.md).
132
131
 
133
132
  One home can send for more than one user: `--user <name>` gives each their own sessions, double-send guard and `[users.<name>]` section in the config file ([ADR 0015](docs/adr/0015-users-and-accounts.md)).
134
133
  Given an account's email (`atsfill login <adapter> --email`) and a password source (a `[password-commands]` entry such as `rbw get example.com {email}`, or `--password -` on stdin), atsfill signs in by itself, and again whenever a form command finds the session signed out.
@@ -29,10 +29,10 @@ It is built for agents with a shell: every command prints one JSON document and
29
29
  ## Supported hiring systems
30
30
 
31
31
  <!-- badges -->
32
- ![oracle: dry run verified 2026-10-20](https://img.shields.io/badge/oracle-dry%20run%20verified%202026--10--20-7aa2f7) ![smartrecruiters: dry run verified 2026-10-20](https://img.shields.io/badge/smartrecruiters-dry%20run%20verified%202026--10--20-7aa2f7) ![greenhouse: dry run verified 2026-10-19](https://img.shields.io/badge/greenhouse-dry%20run%20verified%202026--10--19-7aa2f7) ![manatal: dry run verified 2026-10-20](https://img.shields.io/badge/manatal-dry%20run%20verified%202026--10--20-7aa2f7) ![paylocity: dry run verified 2026-10-20](https://img.shields.io/badge/paylocity-dry%20run%20verified%202026--10--20-7aa2f7) ![dayforce: dry run verified 2026-10-20](https://img.shields.io/badge/dayforce-dry%20run%20verified%202026--10--20-7aa2f7) ![workable: dry run verified 2026-10-20](https://img.shields.io/badge/workable-dry%20run%20verified%202026--10--20-7aa2f7) ![ashby: send verified 2026-10-19](https://img.shields.io/badge/ashby-send%20verified%202026--10--19-9ece6a) ![bamboohr: dry run verified 2026-10-21](https://img.shields.io/badge/bamboohr-dry%20run%20verified%202026--10--21-7aa2f7) ![teamtailor: send verified 2026-10-20](https://img.shields.io/badge/teamtailor-send%20verified%202026--10--20-9ece6a) ![jazzhr: dry run verified 2026-10-20](https://img.shields.io/badge/jazzhr-dry%20run%20verified%202026--10--20-7aa2f7) ![lever: dry run verified 2026-10-20](https://img.shields.io/badge/lever-dry%20run%20verified%202026--10--20-7aa2f7) ![hireology: dry run verified 2026-10-20](https://img.shields.io/badge/hireology-dry%20run%20verified%202026--10--20-7aa2f7) ![paycor: dry run verified 2026-10-20](https://img.shields.io/badge/paycor-dry%20run%20verified%202026--10--20-7aa2f7) ![zohorecruit: dry run verified 2026-10-22](https://img.shields.io/badge/zohorecruit-dry%20run%20verified%202026--10--22-7aa2f7) ![phenom: dry run verified 2026-10-21](https://img.shields.io/badge/phenom-dry%20run%20verified%202026--10--21-7aa2f7) ![breezy: dry run verified 2026-10-21](https://img.shields.io/badge/breezy-dry%20run%20verified%202026--10--21-7aa2f7) ![recruitee: dry run verified 2026-10-25](https://img.shields.io/badge/recruitee-dry%20run%20verified%202026--10--25-7aa2f7) ![rippling: dry run verified 2026-10-22](https://img.shields.io/badge/rippling-dry%20run%20verified%202026--10--22-7aa2f7) ![personio: dry run verified 2026-10-22](https://img.shields.io/badge/personio-dry%20run%20verified%202026--10--22-7aa2f7) ![eightfold: dry run verified 2026-10-22](https://img.shields.io/badge/eightfold-dry%20run%20verified%202026--10--22-7aa2f7) ![pinpoint: dry run verified 2026-10-22](https://img.shields.io/badge/pinpoint-dry%20run%20verified%202026--10--22-7aa2f7) ![hiringthing: dry run verified 2026-10-22](https://img.shields.io/badge/hiringthing-dry%20run%20verified%202026--10--22-7aa2f7) ![join: dry run verified 2026-10-26](https://img.shields.io/badge/join-dry%20run%20verified%202026--10--26-7aa2f7) ![wttj: send verified 2026-10-20](https://img.shields.io/badge/wttj-send%20verified%202026--10--20-9ece6a) ![workday: dry run verified 2026-10-24](https://img.shields.io/badge/workday-dry%20run%20verified%202026--10--24-7aa2f7) ![successfactors: dry run verified 2026-10-26](https://img.shields.io/badge/successfactors-dry%20run%20verified%202026--10--26-7aa2f7) ![isolvedhire: dry run verified 2026-10-26](https://img.shields.io/badge/isolvedhire-dry%20run%20verified%202026--10--26-7aa2f7) ![gupy: dry run verified 2026-10-26](https://img.shields.io/badge/gupy-dry%20run%20verified%202026--10--26-7aa2f7) ![ukg: unsupported 2026-10-26](https://img.shields.io/badge/ukg-unsupported%202026--10--26-e0af68)
32
+ ![oracle: supported 2026-10-20](https://img.shields.io/badge/oracle-supported%202026--10--20-9ece6a) ![smartrecruiters: supported 2026-10-20](https://img.shields.io/badge/smartrecruiters-supported%202026--10--20-9ece6a) ![greenhouse: supported 2026-10-19](https://img.shields.io/badge/greenhouse-supported%202026--10--19-9ece6a) ![manatal: supported 2026-10-20](https://img.shields.io/badge/manatal-supported%202026--10--20-9ece6a) ![paylocity: supported 2026-10-20](https://img.shields.io/badge/paylocity-supported%202026--10--20-9ece6a) ![dayforce: supported 2026-10-20](https://img.shields.io/badge/dayforce-supported%202026--10--20-9ece6a) ![workable: supported 2026-10-20](https://img.shields.io/badge/workable-supported%202026--10--20-9ece6a) ![ashby: supported 2026-10-19](https://img.shields.io/badge/ashby-supported%202026--10--19-9ece6a) ![bamboohr: supported 2026-10-21](https://img.shields.io/badge/bamboohr-supported%202026--10--21-9ece6a) ![teamtailor: supported 2026-10-25](https://img.shields.io/badge/teamtailor-supported%202026--10--25-9ece6a) ![jazzhr: supported 2026-10-20](https://img.shields.io/badge/jazzhr-supported%202026--10--20-9ece6a) ![lever: supported 2026-10-20](https://img.shields.io/badge/lever-supported%202026--10--20-9ece6a) ![hireology: supported 2026-10-20](https://img.shields.io/badge/hireology-supported%202026--10--20-9ece6a) ![paycor: supported 2026-10-20](https://img.shields.io/badge/paycor-supported%202026--10--20-9ece6a) ![zohorecruit: supported 2026-10-22](https://img.shields.io/badge/zohorecruit-supported%202026--10--22-9ece6a) ![phenom: supported 2026-10-21](https://img.shields.io/badge/phenom-supported%202026--10--21-9ece6a) ![breezy: supported 2026-10-21](https://img.shields.io/badge/breezy-supported%202026--10--21-9ece6a) ![recruitee: supported 2026-10-25](https://img.shields.io/badge/recruitee-supported%202026--10--25-9ece6a) ![rippling: supported 2026-10-22](https://img.shields.io/badge/rippling-supported%202026--10--22-9ece6a) ![personio: supported 2026-10-22](https://img.shields.io/badge/personio-supported%202026--10--22-9ece6a) ![eightfold: supported 2026-10-22](https://img.shields.io/badge/eightfold-supported%202026--10--22-9ece6a) ![pinpoint: supported 2026-10-22](https://img.shields.io/badge/pinpoint-supported%202026--10--22-9ece6a) ![hiringthing: supported 2026-10-22](https://img.shields.io/badge/hiringthing-supported%202026--10--22-9ece6a) ![join: supported 2026-10-26](https://img.shields.io/badge/join-supported%202026--10--26-9ece6a) ![wttj: supported 2026-10-24](https://img.shields.io/badge/wttj-supported%202026--10--24-9ece6a) ![workday: supported 2026-10-24](https://img.shields.io/badge/workday-supported%202026--10--24-9ece6a) ![successfactors: supported 2026-10-26](https://img.shields.io/badge/successfactors-supported%202026--10--26-9ece6a) ![isolvedhire: supported 2026-10-26](https://img.shields.io/badge/isolvedhire-supported%202026--10--26-9ece6a) ![gupy: supported 2026-10-26](https://img.shields.io/badge/gupy-supported%202026--10--26-9ece6a) ![ukg: in scope 2026-10-26](https://img.shields.io/badge/ukg-in%20scope%202026--10--26-e0af68)
33
33
  <!-- /badges -->
34
34
 
35
- Each badge is a hiring system atsfill has an adapter for, with how far it was verified and when: a dry run, or a real send.
35
+ Each badge is a hiring system atsfill has an adapter for: `supported` once a dry run passed on five employers, with its date.
36
36
  [The systems](docs/adapters/README.md) lists which ones need an account, to read the form or only to send it.
37
37
  Any other system returns `unsupported`, so the consumer can fall back to driving a browser itself.
38
38
 
@@ -99,8 +99,7 @@ Every push to `main` that passes CI is also published as a dev release; `uvx --p
99
99
  ```
100
100
 
101
101
  A dry run is not invisible: some systems save answers or the resume as they are typed.
102
- `atsfill status` lists each system's side effects, whether it is allowed to submit, and how far atsfill has verified it: [the systems](docs/adapters/README.md).
103
- A dry run or a `send` through an adapter no send was verified on carries a `warning`; the send goes ahead anyway.
102
+ `atsfill status` lists each system's side effects, whether it is allowed to submit, and its scope: [the systems](docs/adapters/README.md).
104
103
 
105
104
  One home can send for more than one user: `--user <name>` gives each their own sessions, double-send guard and `[users.<name>]` section in the config file ([ADR 0015](docs/adr/0015-users-and-accounts.md)).
106
105
  Given an account's email (`atsfill login <adapter> --email`) and a password source (a `[password-commands]` entry such as `rbw get example.com {email}`, or `--password -` on stdin), atsfill signs in by itself, and again whenever a form command finds the session signed out.
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "atsfill"
3
- version = "0.0.5.dev139"
3
+ version = "0.0.5.dev141"
4
4
  description = "Read, dry-run and submit job application forms on hiring systems: Ashby, Greenhouse, Teamtailor, Welcome to the Jungle."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "atsfill"
3
- version = "0.0.5.dev139"
3
+ version = "0.0.5.dev141"
4
4
  description = "Read, dry-run and submit job application forms on hiring systems: Ashby, Greenhouse, Teamtailor, Welcome to the Jungle."
5
5
  readme = "README.md"
6
6
  license = "MIT"
@@ -324,7 +324,6 @@ def _form_command(
324
324
  result = _fill(page, adapter, url, answers, user)
325
325
  if result.status is not Status.READY:
326
326
  return result
327
- result.warning = _unverified(adapter.name)
328
327
  if not send:
329
328
  return result
330
329
  attempts.record(mine, posting, attempts.PENDING, url=url)
@@ -398,18 +397,6 @@ def send(
398
397
  )
399
398
 
400
399
 
401
- def _unverified(adapter: str) -> str | None:
402
- """The warning a ready dry run or a send carries when no send through the adapter was
403
- verified, or the last verified one fails now (ADR 0012)."""
404
- source = registry.for_adapter(adapter)
405
- if source and source.send_broken:
406
- return f"send_broken: {_broken(source)}"
407
- if source and source.send_verified:
408
- return None
409
- verification = source.verification if source else None
410
- return f"not_send_verified: {adapter} is {verification or 'not in the registry'}"
411
-
412
-
413
400
  def _sessions(data: Path, users: list[str], candidates: list[Adapter]) -> list[SessionReport]:
414
401
  """Every session a user stored, each asked of its system."""
415
402
  return [
@@ -439,26 +426,14 @@ def status(*, data_dir: Path | None = None) -> StatusReport:
439
426
  )
440
427
 
441
428
 
442
- def _broken(source: registry.Source) -> str:
443
- broken, last = source.send_broken, source.send_verified
444
- assert broken
445
- since = f"send broken since {broken.date} ({broken.version}), {broken.reason}"
446
- return since + (f"; last verified {last.date} ({last.version})" if last else "")
447
-
448
-
449
429
  def _adapter_status(adapter: Adapter, users: list[str]) -> AdapterStatus:
450
430
  source = registry.for_adapter(adapter.name)
451
- verified = source.verification if source else None
452
- v = getattr(source, verified) if source and verified else None
453
431
  return AdapterStatus(
454
432
  adapter.name,
455
433
  submit_allowed=[u for u in users if paths.submit_enabled(adapter.name, u)],
456
434
  side_effects=adapter.side_effects,
457
435
  side_effects_muted=[u for u in users if paths.muted(adapter.name, u)],
458
- verified=verified,
459
- verified_version=v.version if v else None,
460
- verified_release=registry.release(v.version, version("atsfill")) if v else None,
461
- send_broken=_broken(source) if source and source.send_broken else None,
436
+ scope=source.scope.value if source else None,
462
437
  )
463
438
 
464
439
 
@@ -1,61 +1,31 @@
1
1
  """The committed registry of freehire sources (ADR 0012)."""
2
2
 
3
+ import datetime
3
4
  import tomllib
4
5
  from dataclasses import dataclass, fields
5
6
  from datetime import date
6
7
  from functools import cache
7
8
  from importlib.resources import files
8
9
 
9
- from packaging.version import Version
10
-
11
10
  KINDS = ("ats", "board", "aggregator", "custom")
12
11
  ACCOUNTS = ("none", "to_send", "to_read") # also the backlog order (ADR 0013)
13
12
  HOSTING = ("hosted", "embedded")
14
13
  VENDORS = ("DataDome", "Cloudflare", "Akamai", "PerimeterX", "hCaptcha", "unknown")
15
14
  STEPS = ("posting", "form", "upload")
16
- VERIFICATIONS = ("mapped", "dry_run_verified", "send_verified")
17
- CAUSES = ("system", "code", "unknown")
18
- RELEASES = ("this_release", "older", "newer")
15
+ SCOPES = ("out_of_scope", "in_scope", "supported")
19
16
 
20
17
 
21
18
  @dataclass(frozen=True)
22
- class Verification:
23
- date: date
24
- version: str # the release, compared with the installed one
25
- by: str
26
- commit: str | None = None # the exact code
27
-
19
+ class Scope:
20
+ """Where a source stands (ADR 0016). `supported`: a dev dry run passed at `commit`, never
21
+ demoted. `in_scope`: wanted, its `reason` the known gap a round stopped at, if one did.
22
+ `out_of_scope`: never filled, it needs the user live (ADR 0014). A `reason` names fields
23
+ and steps, never postings."""
28
24
 
29
- @dataclass(frozen=True)
30
- class Broken:
31
- """A send that worked fails now (ADR 0012); `reason` names fields and steps, never postings."""
32
-
33
- date: date
34
- version: str
35
- commit: str
36
- by: str
37
- reason: str
38
- cause: str = "unknown"
39
-
40
-
41
- @dataclass(frozen=True)
42
- class Shortfall:
43
- """A round that ended without an adapter passing the adapter gate (ADR 0013): still wanted.
44
- `reason` names the known gap that stopped it, never postings."""
45
-
46
- date: date
47
- version: str
48
- commit: str
49
- by: str
50
- reason: str
51
-
52
-
53
- @dataclass(frozen=True)
54
- class OutOfScope:
55
- """A source atsfill will never fill: it needs the user live (ADR 0014)."""
56
-
57
- reason: str
58
- date: date
25
+ value: str = "in_scope"
26
+ date: datetime.date | None = None
27
+ commit: str | None = None
28
+ reason: str | None = None
59
29
 
60
30
 
61
31
  @dataclass(frozen=True)
@@ -79,17 +49,7 @@ class Source:
79
49
  share: float | None = None
80
50
  links_to: tuple[str, ...] = () # an aggregator's: the sources its postings link to
81
51
  block: Block | None = None
82
- mapped: Verification | None = None
83
- dry_run_verified: Verification | None = None
84
- send_verified: Verification | None = None # the last known good, kept through a break
85
- send_broken: Broken | None = None
86
- unsupported: Shortfall | None = None
87
- out_of_scope: OutOfScope | None = None
88
-
89
- @property
90
- def verification(self) -> str | None:
91
- """The furthest verification reached; each one implies the ones before it."""
92
- return next((s for s in reversed(VERIFICATIONS) if getattr(self, s)), None)
52
+ scope: Scope = Scope()
93
53
 
94
54
 
95
55
  def _one_of(name: str, key: str, value: object, allowed: tuple[str, ...]) -> None:
@@ -112,13 +72,8 @@ def _source(name: str, entry: dict) -> Source:
112
72
  _one_of(name, "block.vendor", block.get("vendor"), VENDORS)
113
73
  _one_of(name, "block.step", block.get("step"), STEPS)
114
74
  block = Block(**block)
115
- verifications = {s: Verification(**entry[s]) for s in VERIFICATIONS if s in entry}
116
- broken = entry.get("send_broken")
117
- if broken is not None:
118
- _one_of(name, "send_broken.cause", broken.get("cause", "unknown"), CAUSES)
119
- broken = Broken(**broken)
120
- unsupported = entry.get("unsupported")
121
- out_of_scope = entry.get("out_of_scope")
75
+ scope = dict(entry.get("scope", {"is": "in_scope"}))
76
+ _one_of(name, "scope.is", scope.get("is"), SCOPES)
122
77
  return Source(
123
78
  name,
124
79
  entry["kind"],
@@ -128,10 +83,7 @@ def _source(name: str, entry: dict) -> Source:
128
83
  entry.get("share"),
129
84
  tuple(entry.get("links_to", [])),
130
85
  block,
131
- **verifications,
132
- send_broken=broken,
133
- unsupported=Shortfall(**unsupported) if unsupported is not None else None,
134
- out_of_scope=OutOfScope(**out_of_scope) if out_of_scope is not None else None,
86
+ Scope(scope.pop("is"), **scope),
135
87
  )
136
88
 
137
89
 
@@ -148,52 +100,40 @@ def _toml(value: object) -> str:
148
100
  return str(value) if isinstance(value, date) else f'"{value}"'
149
101
 
150
102
 
151
- def with_verification(
152
- text: str, source: str, key: str, value: Verification | Broken | Shortfall
153
- ) -> str:
154
- """The registry file's text with `key` of `source` set to `value`, the rest untouched.
155
- Writing `send_verified` ends a `send_broken`."""
103
+ def with_scope(text: str, source: str, scope: Scope) -> str:
104
+ """The registry file's text with `source`'s scope set, the rest untouched."""
156
105
  lines = text.splitlines()
157
106
  start = lines.index(f"[{source}]")
158
107
  end = next((i for i in range(start + 1, len(lines)) if lines[i].startswith("[")), len(lines))
159
108
  while lines[end - 1] == "":
160
109
  end -= 1
161
- gone = {key, "send_broken"} if key == "send_verified" else {key}
162
- entry = lines[start:end]
163
- at = next((i for i, x in enumerate(entry) if x.split(" =")[0] in gone), len(entry))
164
- entry = [x for x in entry if x.split(" =")[0] not in gone]
110
+ entry = [x for x in lines[start:end] if not x.startswith("scope =")]
165
111
  pairs = ", ".join(
166
- f"{f.name} = {_toml(getattr(value, f.name))}"
167
- for f in fields(value)
168
- if getattr(value, f.name) is not None
112
+ f"{'is' if f.name == 'value' else f.name} = {_toml(getattr(scope, f.name))}"
113
+ for f in fields(scope)
114
+ if getattr(scope, f.name) is not None
169
115
  )
170
- entry.insert(min(at, len(entry)), f"{key} = {{ {pairs} }}")
116
+ entry.append(f"scope = {{ {pairs} }}")
171
117
  return "\n".join(lines[:start] + entry + lines[end:]) + "\n"
172
118
 
173
119
 
174
- def release(verified: str, installed: str) -> str:
175
- """Whether a verified release is the installed one, `older` or `newer`."""
176
- a, b = (Version(Version(v).public) for v in (verified, installed))
177
- return "this_release" if a == b else "older" if a < b else "newer"
178
-
179
-
180
120
  def for_adapter(name: str) -> Source | None:
181
121
  return next((s for s in sources().values() if s.adapter == name), None)
182
122
 
183
123
 
184
124
  def _backlog_key(s: Source) -> tuple:
185
- stopped = s.block is not None or s.unsupported is not None
125
+ stopped = s.block is not None or s.scope.reason is not None
186
126
  return (stopped, ACCOUNTS.index(s.account), -(s.share or 0), s.name)
187
127
 
188
128
 
189
129
  def _row(s: Source) -> str:
190
130
  share = f"{s.share:.1%}" if s.share is not None else ""
191
131
  block = f"{s.block.vendor} at {s.block.step}" if s.block else ""
192
- v = "send_broken" if s.send_broken else "unsupported" if s.unsupported else s.verification or ""
193
- if s.out_of_scope:
194
- v = f"out of scope: {s.out_of_scope.reason}"
132
+ v = s.scope.value.replace("_", " ")
133
+ if s.scope.reason:
134
+ v += f": {s.scope.reason}"
195
135
  if s.adapter:
196
- v = f"[{v}]({s.adapter}.md)" if v else f"[adapter]({s.adapter}.md)"
136
+ v = f"[{v}]({s.adapter}.md)"
197
137
  kind = f"{s.kind} → {', '.join(s.links_to)}" if s.links_to else s.kind
198
138
  cells = [s.name, kind, share, s.account, ", ".join(s.hosting), block, v]
199
139
  return "| " + " | ".join(cells) + " |"
@@ -202,9 +142,9 @@ def _row(s: Source) -> str:
202
142
  def backlog(registry: dict[str, Source] | None = None) -> str:
203
143
  """`docs/adapters/README.md`: the systems in backlog order (ADR 0013)."""
204
144
  registry = sources() if registry is None else registry
205
- columns = ("Source", "Kind", "Share", "Account", "Hosting", "Block", "Verification")
145
+ columns = ("Source", "Kind", "Share", "Account", "Hosting", "Block", "Scope")
206
146
  head = f"| {' | '.join(columns)} |\n" + "|---" * len(columns) + "|"
207
- wanted = lambda s: s.kind in ("ats", "board") and not s.out_of_scope # noqa: E731
147
+ wanted = lambda s: s.kind in ("ats", "board") and s.scope.value != "out_of_scope" # noqa: E731
208
148
  adapters = sorted((s for s in registry.values() if wanted(s)), key=_backlog_key)
209
149
  others = sorted((s for s in registry.values() if not wanted(s)), key=_backlog_key)
210
150
  text = [
@@ -214,7 +154,7 @@ def backlog(registry: dict[str, Source] | None = None) -> str:
214
154
  " `uv run python -m scripts.backlog`. The registry is described in"
215
155
  " [ADR 0012](../adr/0012-systems-registry.md), the order in"
216
156
  " [ADR 0013](../adr/0013-survey-and-adapter-rounds.md): no account first, blocked and"
217
- " unsupported last, then by share of postings.",
157
+ " stopped by a known gap last, then by share of postings.",
218
158
  "",
219
159
  f"The registry covers {sum(s.share or 0 for s in registry.values()):.1%} of postings.",
220
160
  "",
@@ -234,19 +174,12 @@ def backlog(registry: dict[str, Source] | None = None) -> str:
234
174
 
235
175
 
236
176
  BADGES = ("<!-- badges -->", "<!-- /badges -->")
237
- COLORS = {"send_verified": "9ece6a", "dry_run_verified": "7aa2f7", "mapped": "a9b1d6"}
177
+ COLORS = {"supported": "9ece6a", "in_scope": "e0af68", "out_of_scope": "565f89"}
238
178
 
239
179
 
240
180
  def _badge(s: Source) -> str:
241
- if s.send_broken:
242
- message, color = f"send broken {s.send_broken.date}", "f7768e"
243
- elif s.unsupported:
244
- message, color = f"unsupported {s.unsupported.date}", "e0af68"
245
- elif s.verification:
246
- v: Verification = getattr(s, s.verification)
247
- message, color = f"{s.verification.replace('_', ' ')} {v.date}", COLORS[s.verification]
248
- else:
249
- message, color = "unverified", "565f89"
181
+ message = " ".join(str(x) for x in (s.scope.value.replace("_", " "), s.scope.date) if x)
182
+ color = COLORS[s.scope.value]
250
183
  escape = lambda t: t.replace("-", "--").replace("_", "__").replace(" ", "%20") # noqa: E731
251
184
  url = f"https://img.shields.io/badge/{escape(s.adapter or s.name)}-{escape(message)}-{color}"
252
185
  return f"![{s.adapter}: {message}]({url})"
@@ -101,7 +101,6 @@ class Result:
101
101
  checks: list[Check] = field(default_factory=list)
102
102
  side_effects: list[SideEffect] = field(default_factory=list)
103
103
  evidence: str | None = None
104
- warning: str | None = None # `code: detail`, on a ready dry run or a send that went ahead
105
104
 
106
105
 
107
106
  @dataclass
@@ -122,10 +121,7 @@ class AdapterStatus:
122
121
  submit_allowed: list[str] # the users whose config lets it submit
123
122
  side_effects: list[SideEffect]
124
123
  side_effects_muted: list[str] # the users who muted them
125
- verified: str | None = None # mapped | dry_run_verified | send_verified (ADR 0012)
126
- verified_version: str | None = None
127
- verified_release: str | None = None # this_release | older | newer than the installed one
128
- send_broken: str | None = None # since when and why, with the last verified send
124
+ scope: str | None = None # supported | in_scope | out_of_scope, None when not in the registry
129
125
 
130
126
 
131
127
  @dataclass