atsfill 0.0.4.dev136__tar.gz → 0.0.5__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 (71) hide show
  1. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/PKG-INFO +59 -15
  2. atsfill-0.0.5/README.md +188 -0
  3. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/pyproject.toml +3 -1
  4. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/pyproject.toml.orig +5 -1
  5. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/__init__.py +16 -0
  6. atsfill-0.0.5/src/atsfill/adapters/adp.py +760 -0
  7. atsfill-0.0.5/src/atsfill/adapters/adpmyjobs.py +817 -0
  8. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/ashby.py +20 -8
  9. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/breezy.py +9 -2
  10. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/isolvedhire.py +6 -1
  11. atsfill-0.0.5/src/atsfill/adapters/jobaffinity.py +455 -0
  12. atsfill-0.0.5/src/atsfill/adapters/jobstreet.py +501 -0
  13. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/join.py +201 -19
  14. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/lever.py +22 -6
  15. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/manatal.py +12 -4
  16. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/oracle.py +7 -1
  17. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/phenom.py +279 -74
  18. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/recruitee.py +9 -2
  19. atsfill-0.0.5/src/atsfill/adapters/recruiterflow.py +467 -0
  20. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/rippling.py +7 -3
  21. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/successfactors.py +4 -0
  22. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/teamtailor.py +20 -7
  23. atsfill-0.0.5/src/atsfill/adapters/welcomekit.py +366 -0
  24. atsfill-0.0.5/src/atsfill/adapters/werecruit.py +230 -0
  25. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/workable.py +9 -2
  26. atsfill-0.0.5/src/atsfill/adapters/workatastartup.py +847 -0
  27. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/cli.py +34 -5
  28. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/core/__init__.py +1 -1
  29. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/core/accounts.py +26 -7
  30. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/core/adapter.py +108 -4
  31. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/core/attempts.py +1 -0
  32. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/core/browser.py +9 -1
  33. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/core/commands.py +413 -130
  34. atsfill-0.0.5/src/atsfill/core/countries.py +260 -0
  35. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/core/matching.py +25 -3
  36. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/core/paths.py +16 -0
  37. atsfill-0.0.5/src/atsfill/core/registry.py +203 -0
  38. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/core/results.py +19 -8
  39. atsfill-0.0.5/src/atsfill/core/snapshot.py +75 -0
  40. atsfill-0.0.5/src/atsfill/core/telemetry.py +236 -0
  41. atsfill-0.0.5/src/atsfill/registry.toml +439 -0
  42. atsfill-0.0.4.dev136/README.md +0 -145
  43. atsfill-0.0.4.dev136/src/atsfill/core/registry.py +0 -260
  44. atsfill-0.0.4.dev136/src/atsfill/registry.toml +0 -442
  45. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/LICENSE +0 -0
  46. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/__init__.py +0 -0
  47. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/bamboohr.py +0 -0
  48. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/dayforce.py +0 -0
  49. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/eightfold.py +0 -0
  50. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/greenhouse.py +0 -0
  51. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/gupy.py +0 -0
  52. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/hireology.py +0 -0
  53. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/hiringthing.py +0 -0
  54. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/hrmdirect.py +0 -0
  55. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/jazzhr.py +0 -0
  56. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/paycor.py +0 -0
  57. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/paylocity.py +0 -0
  58. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/personio.py +0 -0
  59. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/pinpoint.py +0 -0
  60. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/smartrecruiters.py +0 -0
  61. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/successfactors.js +0 -0
  62. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/taleo.py +0 -0
  63. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/ukg.py +0 -0
  64. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/workday.py +0 -0
  65. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/wttj.py +0 -0
  66. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/adapters/zohorecruit.py +0 -0
  67. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/core/conditions.py +0 -0
  68. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/core/html.py +0 -0
  69. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/core/otp.py +0 -0
  70. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/core/passwords.py +0 -0
  71. {atsfill-0.0.4.dev136 → atsfill-0.0.5}/src/atsfill/py.typed +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: atsfill
3
- Version: 0.0.4.dev136
3
+ Version: 0.0.5
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
@@ -20,6 +20,7 @@ Requires-Dist: camoufox==0.5.8
20
20
  Requires-Dist: filelock==4.1.1
21
21
  Requires-Dist: keyring==25.7.0
22
22
  Requires-Dist: packaging==26.3
23
+ Requires-Dist: posthog==7.78.0
23
24
  Requires-Python: >=3.14
24
25
  Project-URL: Homepage, https://github.com/shbernal/atsfill
25
26
  Project-URL: Repository, https://github.com/shbernal/atsfill
@@ -30,7 +31,7 @@ Description-Content-Type: text/markdown
30
31
 
31
32
  # atsfill
32
33
 
33
- Fill job application forms from the command line: read, dry-run, then submit.
34
+ Let your agent apply for you. One CLI for every hiring system.
34
35
 
35
36
  [![PyPI][pypi-badge]][pypi]
36
37
  [![Python][python-badge]][pypi]
@@ -56,17 +57,52 @@ It is built for agents with a shell: every command prints one JSON document and
56
57
 
57
58
  ## Supported hiring systems
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)
61
- <!-- /badges -->
62
-
63
- | System | Account needed | `read` without a browser |
64
- | --- | --- | --- |
65
- | Ashby | no | yes |
66
- | Greenhouse | no | yes |
67
- | Teamtailor | no | no |
68
- | Welcome to the Jungle | yes, `atsfill login wttj` | no |
69
-
60
+ <!-- run-history -->
61
+ [![oracle run history](https://shbernal.github.io/atsfill/run-history/oracle.svg)](https://shbernal.github.io/atsfill/run-history/)
62
+ [![smartrecruiters run history](https://shbernal.github.io/atsfill/run-history/smartrecruiters.svg)](https://shbernal.github.io/atsfill/run-history/)
63
+ [![greenhouse run history](https://shbernal.github.io/atsfill/run-history/greenhouse.svg)](https://shbernal.github.io/atsfill/run-history/)
64
+ [![manatal run history](https://shbernal.github.io/atsfill/run-history/manatal.svg)](https://shbernal.github.io/atsfill/run-history/)
65
+ [![paylocity run history](https://shbernal.github.io/atsfill/run-history/paylocity.svg)](https://shbernal.github.io/atsfill/run-history/)
66
+ [![dayforce run history](https://shbernal.github.io/atsfill/run-history/dayforce.svg)](https://shbernal.github.io/atsfill/run-history/)
67
+ [![workable run history](https://shbernal.github.io/atsfill/run-history/workable.svg)](https://shbernal.github.io/atsfill/run-history/)
68
+ [![ashby run history](https://shbernal.github.io/atsfill/run-history/ashby.svg)](https://shbernal.github.io/atsfill/run-history/)
69
+ [![bamboohr run history](https://shbernal.github.io/atsfill/run-history/bamboohr.svg)](https://shbernal.github.io/atsfill/run-history/)
70
+ [![teamtailor run history](https://shbernal.github.io/atsfill/run-history/teamtailor.svg)](https://shbernal.github.io/atsfill/run-history/)
71
+ [![jazzhr run history](https://shbernal.github.io/atsfill/run-history/jazzhr.svg)](https://shbernal.github.io/atsfill/run-history/)
72
+ [![lever run history](https://shbernal.github.io/atsfill/run-history/lever.svg)](https://shbernal.github.io/atsfill/run-history/)
73
+ [![hireology run history](https://shbernal.github.io/atsfill/run-history/hireology.svg)](https://shbernal.github.io/atsfill/run-history/)
74
+ [![paycor run history](https://shbernal.github.io/atsfill/run-history/paycor.svg)](https://shbernal.github.io/atsfill/run-history/)
75
+ [![zohorecruit run history](https://shbernal.github.io/atsfill/run-history/zohorecruit.svg)](https://shbernal.github.io/atsfill/run-history/)
76
+ [![phenom run history](https://shbernal.github.io/atsfill/run-history/phenom.svg)](https://shbernal.github.io/atsfill/run-history/)
77
+ [![breezy run history](https://shbernal.github.io/atsfill/run-history/breezy.svg)](https://shbernal.github.io/atsfill/run-history/)
78
+ [![recruitee run history](https://shbernal.github.io/atsfill/run-history/recruitee.svg)](https://shbernal.github.io/atsfill/run-history/)
79
+ [![rippling run history](https://shbernal.github.io/atsfill/run-history/rippling.svg)](https://shbernal.github.io/atsfill/run-history/)
80
+ [![personio run history](https://shbernal.github.io/atsfill/run-history/personio.svg)](https://shbernal.github.io/atsfill/run-history/)
81
+ [![eightfold run history](https://shbernal.github.io/atsfill/run-history/eightfold.svg)](https://shbernal.github.io/atsfill/run-history/)
82
+ [![pinpoint run history](https://shbernal.github.io/atsfill/run-history/pinpoint.svg)](https://shbernal.github.io/atsfill/run-history/)
83
+ [![hiringthing run history](https://shbernal.github.io/atsfill/run-history/hiringthing.svg)](https://shbernal.github.io/atsfill/run-history/)
84
+ [![recruiterflow run history](https://shbernal.github.io/atsfill/run-history/recruiterflow.svg)](https://shbernal.github.io/atsfill/run-history/)
85
+ [![werecruit run history](https://shbernal.github.io/atsfill/run-history/werecruit.svg)](https://shbernal.github.io/atsfill/run-history/)
86
+ [![jobaffinity run history](https://shbernal.github.io/atsfill/run-history/jobaffinity.svg)](https://shbernal.github.io/atsfill/run-history/)
87
+ [![welcomekit run history](https://shbernal.github.io/atsfill/run-history/welcomekit.svg)](https://shbernal.github.io/atsfill/run-history/)
88
+ [![join run history](https://shbernal.github.io/atsfill/run-history/join.svg)](https://shbernal.github.io/atsfill/run-history/)
89
+ [![workatastartup run history](https://shbernal.github.io/atsfill/run-history/workatastartup.svg)](https://shbernal.github.io/atsfill/run-history/)
90
+ [![wttj run history](https://shbernal.github.io/atsfill/run-history/wttj.svg)](https://shbernal.github.io/atsfill/run-history/)
91
+ [![workday run history](https://shbernal.github.io/atsfill/run-history/workday.svg)](https://shbernal.github.io/atsfill/run-history/)
92
+ [![successfactors run history](https://shbernal.github.io/atsfill/run-history/successfactors.svg)](https://shbernal.github.io/atsfill/run-history/)
93
+ [![adp run history](https://shbernal.github.io/atsfill/run-history/adp.svg)](https://shbernal.github.io/atsfill/run-history/)
94
+ [![adpmyjobs run history](https://shbernal.github.io/atsfill/run-history/adpmyjobs.svg)](https://shbernal.github.io/atsfill/run-history/)
95
+ [![isolvedhire run history](https://shbernal.github.io/atsfill/run-history/isolvedhire.svg)](https://shbernal.github.io/atsfill/run-history/)
96
+ [![jobstreet run history](https://shbernal.github.io/atsfill/run-history/jobstreet.svg)](https://shbernal.github.io/atsfill/run-history/)
97
+ [![gupy run history](https://shbernal.github.io/atsfill/run-history/gupy.svg)](https://shbernal.github.io/atsfill/run-history/)
98
+ [![ukg run history](https://shbernal.github.io/atsfill/run-history/ukg.svg)](https://shbernal.github.io/atsfill/run-history/)
99
+ <!-- /run-history -->
100
+
101
+ Each line is a hiring system atsfill has an adapter for, and its [run history](https://shbernal.github.io/atsfill/run-history/): the last 30 days of runs, one mark per posting and version, newest first.
102
+
103
+ ![run history legend](https://shbernal.github.io/atsfill/run-history/legend.svg)
104
+
105
+ [The systems](docs/adapters/README.md) lists which ones need an account, to read the form or only to send it.
70
106
  Any other system returns `unsupported`, so the consumer can fall back to driving a browser itself.
71
107
 
72
108
  ## Why atsfill?
@@ -94,6 +130,12 @@ This downloads the Camoufox browser build atsfill is pinned to: 1.3 GB, about 2.
94
130
 
95
131
  Every push to `main` that passes CI is also published as a dev release; `uvx --prerelease allow atsfill` runs the newest.
96
132
 
133
+ To teach your agent the CLI, add its skill, which runs the newest dev release:
134
+
135
+ ```sh
136
+ npx skills add shbernal/atsfill
137
+ ```
138
+
97
139
  ## Quickstart
98
140
 
99
141
  1. List the form's fields:
@@ -132,13 +174,13 @@ Every push to `main` that passes CI is also published as a dev release; `uvx --p
132
174
  ```
133
175
 
134
176
  A dry run is not invisible: some systems save answers or the resume as they are typed.
135
- `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).
136
- A dry run or a `send` through an adapter no send was verified on carries a `warning`; the send goes ahead anyway.
177
+ `atsfill status` lists each system's side effects, whether it is allowed to submit, and its scope: [the systems](docs/adapters/README.md).
137
178
 
138
179
  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)).
139
180
  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.
140
181
  When a system emails an OTP, on a sign-in, a sign-up or a form, atsfill gets it from the user's `[otp-commands]` entry, a command that reads the mailbox (`{email}` and `{since}` are filled in), and types it in; without one the command ends `blocked: no_otp_command`.
141
182
  Where a system's sign-up allows it, `atsfill login <adapter> --create --email <address>` (with `--details <file.json>` where the sign-up asks the user's name, phone or address) creates the account with a password atsfill generates and keeps in the system's secret store, never printed; creating an account is a side effect, never part of `fill` or `send`.
183
+ Where a system applies only from a complete account profile, `atsfill profile <adapter>` reads its profile form and `atsfill profile <adapter> --answers <file.json>` saves it, step by step, skipping the steps that already match; a form command that meets an incomplete profile ends `blocked: profile_incomplete`, naming that command. Saving a profile is a side effect, never part of `fill` or `send` ([ADR 0017](docs/adr/0017-account-profiles.md)).
142
184
 
143
185
  ## How it works
144
186
 
@@ -158,6 +200,8 @@ The core owns every rule about when a send is allowed; an adapter only knows its
158
200
  Every result has a `status` (`ready`, `refused`, `submitted`, `blocked`, `unknown`, `unsupported`) and, when it is not clean, a `reason` code.
159
201
  The exit code is 0 for `ready` and `submitted`, 1 otherwise, 2 for a usage error.
160
202
 
203
+ Each `read`, `fill` and `send` sends one anonymous event (the system, the command, the status, a hash of the posting, never answers or URLs); `atsfill report` sends a failed run's URL and page, scrubbed of your answers, when you ask: [what is sent and how to turn it off](docs/telemetry.md).
204
+
161
205
  ## More
162
206
 
163
207
  - [Architecture decisions](docs/adr/README.md): the contract, the submit gate, the browser, sessions.
@@ -0,0 +1,188 @@
1
+ <div align="center">
2
+
3
+ # atsfill
4
+
5
+ Let your agent apply for you. One CLI for every hiring system.
6
+
7
+ [![PyPI][pypi-badge]][pypi]
8
+ [![Python][python-badge]][pypi]
9
+ [![CI][ci-badge]][ci]
10
+ [![License][license-badge]][license]
11
+
12
+ ---
13
+
14
+ [Install](#install) • [Quickstart](#quickstart) • [How it works](#how-it-works) • [Docs](docs/adr/README.md)
15
+
16
+ ---
17
+
18
+ </div>
19
+
20
+ atsfill reads the application form behind a job posting, fills it with your answers, and checks what the hiring system will receive before anything is sent.
21
+ It is built for agents with a shell: every command prints one JSON document and exits with a code a script can branch on.
22
+
23
+ - `read` lists a form's fields with their ids, types, options and whether they are required.
24
+ - `fill` is a dry run: it fills the form, reads it back field by field, and never submits.
25
+ - `send` fills again and submits only when every answer reads back as given.
26
+ - Submitting is off until you enable it per hiring system, in a config file the agent does not write.
27
+ - A posting already sent is refused unless you pass `--resend`.
28
+
29
+ ## Supported hiring systems
30
+
31
+ <!-- run-history -->
32
+ [![oracle run history](https://shbernal.github.io/atsfill/run-history/oracle.svg)](https://shbernal.github.io/atsfill/run-history/)
33
+ [![smartrecruiters run history](https://shbernal.github.io/atsfill/run-history/smartrecruiters.svg)](https://shbernal.github.io/atsfill/run-history/)
34
+ [![greenhouse run history](https://shbernal.github.io/atsfill/run-history/greenhouse.svg)](https://shbernal.github.io/atsfill/run-history/)
35
+ [![manatal run history](https://shbernal.github.io/atsfill/run-history/manatal.svg)](https://shbernal.github.io/atsfill/run-history/)
36
+ [![paylocity run history](https://shbernal.github.io/atsfill/run-history/paylocity.svg)](https://shbernal.github.io/atsfill/run-history/)
37
+ [![dayforce run history](https://shbernal.github.io/atsfill/run-history/dayforce.svg)](https://shbernal.github.io/atsfill/run-history/)
38
+ [![workable run history](https://shbernal.github.io/atsfill/run-history/workable.svg)](https://shbernal.github.io/atsfill/run-history/)
39
+ [![ashby run history](https://shbernal.github.io/atsfill/run-history/ashby.svg)](https://shbernal.github.io/atsfill/run-history/)
40
+ [![bamboohr run history](https://shbernal.github.io/atsfill/run-history/bamboohr.svg)](https://shbernal.github.io/atsfill/run-history/)
41
+ [![teamtailor run history](https://shbernal.github.io/atsfill/run-history/teamtailor.svg)](https://shbernal.github.io/atsfill/run-history/)
42
+ [![jazzhr run history](https://shbernal.github.io/atsfill/run-history/jazzhr.svg)](https://shbernal.github.io/atsfill/run-history/)
43
+ [![lever run history](https://shbernal.github.io/atsfill/run-history/lever.svg)](https://shbernal.github.io/atsfill/run-history/)
44
+ [![hireology run history](https://shbernal.github.io/atsfill/run-history/hireology.svg)](https://shbernal.github.io/atsfill/run-history/)
45
+ [![paycor run history](https://shbernal.github.io/atsfill/run-history/paycor.svg)](https://shbernal.github.io/atsfill/run-history/)
46
+ [![zohorecruit run history](https://shbernal.github.io/atsfill/run-history/zohorecruit.svg)](https://shbernal.github.io/atsfill/run-history/)
47
+ [![phenom run history](https://shbernal.github.io/atsfill/run-history/phenom.svg)](https://shbernal.github.io/atsfill/run-history/)
48
+ [![breezy run history](https://shbernal.github.io/atsfill/run-history/breezy.svg)](https://shbernal.github.io/atsfill/run-history/)
49
+ [![recruitee run history](https://shbernal.github.io/atsfill/run-history/recruitee.svg)](https://shbernal.github.io/atsfill/run-history/)
50
+ [![rippling run history](https://shbernal.github.io/atsfill/run-history/rippling.svg)](https://shbernal.github.io/atsfill/run-history/)
51
+ [![personio run history](https://shbernal.github.io/atsfill/run-history/personio.svg)](https://shbernal.github.io/atsfill/run-history/)
52
+ [![eightfold run history](https://shbernal.github.io/atsfill/run-history/eightfold.svg)](https://shbernal.github.io/atsfill/run-history/)
53
+ [![pinpoint run history](https://shbernal.github.io/atsfill/run-history/pinpoint.svg)](https://shbernal.github.io/atsfill/run-history/)
54
+ [![hiringthing run history](https://shbernal.github.io/atsfill/run-history/hiringthing.svg)](https://shbernal.github.io/atsfill/run-history/)
55
+ [![recruiterflow run history](https://shbernal.github.io/atsfill/run-history/recruiterflow.svg)](https://shbernal.github.io/atsfill/run-history/)
56
+ [![werecruit run history](https://shbernal.github.io/atsfill/run-history/werecruit.svg)](https://shbernal.github.io/atsfill/run-history/)
57
+ [![jobaffinity run history](https://shbernal.github.io/atsfill/run-history/jobaffinity.svg)](https://shbernal.github.io/atsfill/run-history/)
58
+ [![welcomekit run history](https://shbernal.github.io/atsfill/run-history/welcomekit.svg)](https://shbernal.github.io/atsfill/run-history/)
59
+ [![join run history](https://shbernal.github.io/atsfill/run-history/join.svg)](https://shbernal.github.io/atsfill/run-history/)
60
+ [![workatastartup run history](https://shbernal.github.io/atsfill/run-history/workatastartup.svg)](https://shbernal.github.io/atsfill/run-history/)
61
+ [![wttj run history](https://shbernal.github.io/atsfill/run-history/wttj.svg)](https://shbernal.github.io/atsfill/run-history/)
62
+ [![workday run history](https://shbernal.github.io/atsfill/run-history/workday.svg)](https://shbernal.github.io/atsfill/run-history/)
63
+ [![successfactors run history](https://shbernal.github.io/atsfill/run-history/successfactors.svg)](https://shbernal.github.io/atsfill/run-history/)
64
+ [![adp run history](https://shbernal.github.io/atsfill/run-history/adp.svg)](https://shbernal.github.io/atsfill/run-history/)
65
+ [![adpmyjobs run history](https://shbernal.github.io/atsfill/run-history/adpmyjobs.svg)](https://shbernal.github.io/atsfill/run-history/)
66
+ [![isolvedhire run history](https://shbernal.github.io/atsfill/run-history/isolvedhire.svg)](https://shbernal.github.io/atsfill/run-history/)
67
+ [![jobstreet run history](https://shbernal.github.io/atsfill/run-history/jobstreet.svg)](https://shbernal.github.io/atsfill/run-history/)
68
+ [![gupy run history](https://shbernal.github.io/atsfill/run-history/gupy.svg)](https://shbernal.github.io/atsfill/run-history/)
69
+ [![ukg run history](https://shbernal.github.io/atsfill/run-history/ukg.svg)](https://shbernal.github.io/atsfill/run-history/)
70
+ <!-- /run-history -->
71
+
72
+ Each line is a hiring system atsfill has an adapter for, and its [run history](https://shbernal.github.io/atsfill/run-history/): the last 30 days of runs, one mark per posting and version, newest first.
73
+
74
+ ![run history legend](https://shbernal.github.io/atsfill/run-history/legend.svg)
75
+
76
+ [The systems](docs/adapters/README.md) lists which ones need an account, to read the form or only to send it.
77
+ Any other system returns `unsupported`, so the consumer can fall back to driving a browser itself.
78
+
79
+ ## Why atsfill?
80
+
81
+ An agent applying through a generic browser carries each system's quirks as prose, rereads it every run, and checks the form before submitting only because an instruction says so.
82
+ atsfill moves the quirks into one adapter per system and makes the check code: a `send` whose read-back differs from the answers is refused.
83
+
84
+ | | Generic browser agent | atsfill |
85
+ | --- | --- | --- |
86
+ | System quirks | prose in the prompt | an adapter per system |
87
+ | Check before submit | an instruction | the read-back must match |
88
+ | Permission to submit | whatever the agent decides | a config file, per system |
89
+ | Double send | possible | refused without `--resend` |
90
+ | Output | page snapshots | one JSON result |
91
+
92
+ ## Install
93
+
94
+ atsfill runs on Linux, macOS and Windows with Python 3.14, which `uvx` fetches if needed.
95
+
96
+ ```sh
97
+ uvx atsfill install-browser
98
+ ```
99
+
100
+ This downloads the Camoufox browser build atsfill is pinned to: 1.3 GB, about 2.5 GB once unpacked.
101
+
102
+ Every push to `main` that passes CI is also published as a dev release; `uvx --prerelease allow atsfill` runs the newest.
103
+
104
+ To teach your agent the CLI, add its skill, which runs the newest dev release:
105
+
106
+ ```sh
107
+ npx skills add shbernal/atsfill
108
+ ```
109
+
110
+ ## Quickstart
111
+
112
+ 1. List the form's fields:
113
+
114
+ ```sh
115
+ uvx atsfill read https://job-boards.greenhouse.io/acme/jobs/1234
116
+ ```
117
+
118
+ 2. Write your answers to `answers.json`, keyed by field id. Files take an absolute path:
119
+
120
+ ```json
121
+ {
122
+ "first_name": "Ada",
123
+ "last_name": "Lovelace",
124
+ "email": "ada@example.com",
125
+ "resume": "/home/ada/resume.pdf"
126
+ }
127
+ ```
128
+
129
+ 3. Dry-run the fill. It ends `ready` when every answer reads back as given:
130
+
131
+ ```sh
132
+ uvx atsfill fill https://job-boards.greenhouse.io/acme/jobs/1234 --answers answers.json
133
+ ```
134
+
135
+ 4. Allow the system to submit in `~/.config/atsfill/config.toml`:
136
+
137
+ ```toml
138
+ submit-allowed = ["greenhouse"]
139
+ ```
140
+
141
+ 5. Send. A `submitted` result carries the evidence of the send:
142
+
143
+ ```sh
144
+ uvx atsfill send https://job-boards.greenhouse.io/acme/jobs/1234 --answers answers.json
145
+ ```
146
+
147
+ A dry run is not invisible: some systems save answers or the resume as they are typed.
148
+ `atsfill status` lists each system's side effects, whether it is allowed to submit, and its scope: [the systems](docs/adapters/README.md).
149
+
150
+ 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)).
151
+ 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.
152
+ When a system emails an OTP, on a sign-in, a sign-up or a form, atsfill gets it from the user's `[otp-commands]` entry, a command that reads the mailbox (`{email}` and `{since}` are filled in), and types it in; without one the command ends `blocked: no_otp_command`.
153
+ Where a system's sign-up allows it, `atsfill login <adapter> --create --email <address>` (with `--details <file.json>` where the sign-up asks the user's name, phone or address) creates the account with a password atsfill generates and keeps in the system's secret store, never printed; creating an account is a side effect, never part of `fill` or `send`.
154
+ Where a system applies only from a complete account profile, `atsfill profile <adapter>` reads its profile form and `atsfill profile <adapter> --answers <file.json>` saves it, step by step, skipping the steps that already match; a form command that meets an incomplete profile ends `blocked: profile_incomplete`, naming that command. Saving a profile is a side effect, never part of `fill` or `send` ([ADR 0017](docs/adr/0017-account-profiles.md)).
155
+
156
+ ## How it works
157
+
158
+ ```mermaid
159
+ flowchart LR
160
+ CLI[atsfill CLI] --> Core
161
+ Core -->|detect, read, fill, read back, submit| Adapter[adapter for the system]
162
+ Adapter --> Browser[headless Camoufox]
163
+ Core --> SubmitGate{read-back matches,<br>submit enabled,<br>not already sent}
164
+ SubmitGate -->|yes| Submit[submit]
165
+ SubmitGate -->|no| Refused[refused]
166
+ ```
167
+
168
+ Each command launches its own headless Camoufox, a Firefox build patched to hide automation, with a fresh context and one saved device identity.
169
+ The core owns every rule about when a send is allowed; an adapter only knows its system's page.
170
+
171
+ Every result has a `status` (`ready`, `refused`, `submitted`, `blocked`, `unknown`, `unsupported`) and, when it is not clean, a `reason` code.
172
+ The exit code is 0 for `ready` and `submitted`, 1 otherwise, 2 for a usage error.
173
+
174
+ Each `read`, `fill` and `send` sends one anonymous event (the system, the command, the status, a hash of the posting, never answers or URLs); `atsfill report` sends a failed run's URL and page, scrubbed of your answers, when you ask: [what is sent and how to turn it off](docs/telemetry.md).
175
+
176
+ ## More
177
+
178
+ - [Architecture decisions](docs/adr/README.md): the contract, the submit gate, the browser, sessions.
179
+ - [Hiring systems](docs/adapters/README.md): every system mapped so far, in backlog order, and each one's form as observed.
180
+ - `atsfill --help` for every command and option.
181
+
182
+ [pypi]: https://pypi.org/project/atsfill/
183
+ [pypi-badge]: https://img.shields.io/pypi/v/atsfill?style=for-the-badge&color=7aa2f7&logo=pypi&logoColor=white
184
+ [python-badge]: https://img.shields.io/pypi/pyversions/atsfill?style=for-the-badge&color=9ece6a&logo=python&logoColor=white
185
+ [ci]: https://github.com/shbernal/atsfill/actions/workflows/ci.yml
186
+ [ci-badge]: https://img.shields.io/github/actions/workflow/status/shbernal/atsfill/ci.yml?branch=main&style=for-the-badge&color=bb9af7&logo=githubactions&logoColor=white
187
+ [license]: LICENSE
188
+ [license-badge]: https://img.shields.io/github/license/shbernal/atsfill?style=for-the-badge&color=e0af68
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "atsfill"
3
- version = "0.0.4.dev136"
3
+ version = "0.0.5"
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"
@@ -32,6 +32,7 @@ dependencies = [
32
32
  "filelock==4.1.1",
33
33
  "keyring==25.7.0",
34
34
  "packaging==26.3",
35
+ "posthog==7.78.0",
35
36
  ]
36
37
 
37
38
  [[project.authors]]
@@ -50,6 +51,7 @@ requires = ["uv_build>=0.13.0,<0.14.0"]
50
51
  build-backend = "uv_build"
51
52
 
52
53
  [dependency-groups]
54
+ docs = ["zensical==0.0.73"]
53
55
  commit-check = ["commit-check==2.19.0"]
54
56
  dev = [
55
57
  "pytest>=9.1.1",
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "atsfill"
3
- version = "0.0.4.dev136"
3
+ version = "0.0.5"
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"
@@ -25,6 +25,7 @@ dependencies = [
25
25
  "filelock==4.1.1",
26
26
  "keyring==25.7.0",
27
27
  "packaging==26.3",
28
+ "posthog==7.78.0",
28
29
  ]
29
30
 
30
31
  [project.urls]
@@ -40,6 +41,9 @@ requires = ["uv_build>=0.13.0,<0.14.0"]
40
41
  build-backend = "uv_build"
41
42
 
42
43
  [dependency-groups]
44
+ docs = [
45
+ "zensical==0.0.73",
46
+ ]
43
47
  commit-check = [
44
48
  "commit-check==2.19.0",
45
49
  ]
@@ -1,3 +1,5 @@
1
+ from atsfill.adapters.adp import ADP
2
+ from atsfill.adapters.adpmyjobs import ADPMyJobs
1
3
  from atsfill.adapters.ashby import Ashby
2
4
  from atsfill.adapters.bamboohr import BambooHR
3
5
  from atsfill.adapters.breezy import Breezy
@@ -9,6 +11,8 @@ from atsfill.adapters.hireology import Hireology
9
11
  from atsfill.adapters.hiringthing import HiringThing
10
12
  from atsfill.adapters.isolvedhire import IsolvedHire
11
13
  from atsfill.adapters.jazzhr import JazzHR
14
+ from atsfill.adapters.jobaffinity import JobAffinity
15
+ from atsfill.adapters.jobstreet import Jobstreet
12
16
  from atsfill.adapters.join import Join
13
17
  from atsfill.adapters.lever import Lever
14
18
  from atsfill.adapters.manatal import Manatal
@@ -19,12 +23,16 @@ from atsfill.adapters.personio import Personio
19
23
  from atsfill.adapters.phenom import Phenom
20
24
  from atsfill.adapters.pinpoint import Pinpoint
21
25
  from atsfill.adapters.recruitee import Recruitee
26
+ from atsfill.adapters.recruiterflow import Recruiterflow
22
27
  from atsfill.adapters.rippling import Rippling
23
28
  from atsfill.adapters.smartrecruiters import SmartRecruiters
24
29
  from atsfill.adapters.successfactors import SuccessFactors
25
30
  from atsfill.adapters.teamtailor import Teamtailor
26
31
  from atsfill.adapters.ukg import UKG
32
+ from atsfill.adapters.welcomekit import WelcomeKit
33
+ from atsfill.adapters.werecruit import WeRecruit
27
34
  from atsfill.adapters.workable import Workable
35
+ from atsfill.adapters.workatastartup import WorkAtAStartup
28
36
  from atsfill.adapters.workday import Workday
29
37
  from atsfill.adapters.wttj import WelcomeToTheJungle
30
38
  from atsfill.adapters.zohorecruit import ZohoRecruit
@@ -38,7 +46,11 @@ def adapters() -> list[Adapter]:
38
46
  Teamtailor(),
39
47
  Greenhouse(),
40
48
  WelcomeToTheJungle(),
49
+ WorkAtAStartup(),
41
50
  Join(),
51
+ JobAffinity(),
52
+ WelcomeKit(),
53
+ WeRecruit(),
42
54
  SmartRecruiters(),
43
55
  Oracle(),
44
56
  Manatal(),
@@ -50,6 +62,7 @@ def adapters() -> list[Adapter]:
50
62
  Lever(),
51
63
  Dayforce(),
52
64
  Recruitee(),
65
+ Recruiterflow(),
53
66
  Breezy(),
54
67
  BambooHR(),
55
68
  ZohoRecruit(),
@@ -62,6 +75,9 @@ def adapters() -> list[Adapter]:
62
75
  Gupy(),
63
76
  UKG(),
64
77
  IsolvedHire(),
78
+ Jobstreet(),
79
+ ADPMyJobs(),
80
+ ADP(),
65
81
  SuccessFactors(), # it claims a career site's job path on any host
66
82
  Phenom(), # last: it claims a URL by its path alone
67
83
  ]