Recorded on a real SmartRecruiters form: about 40 of 150 steps went on PageUp and a screenshot, in a form that scrolls inside its own container. The model is told not to repeat an action that changed nothing, and the plugin says how that form scrolls. On the next runs SmartRecruiters and Paylocity reached the send button; nothing was sent. Also the last mentions of the per-job CV.
A step table and four reasons, as the projects people use in this field do it, so that a reader knows what the program does before installing it. In the banner the app answers each send beside Firefox, the confirmations can be read, and the counter has its time.
Logo, four short chapters, and windows that open and work: the terminal, AIHawk matching the jobs, Firefox applying form after form. On GitHub's exact background, so the banner has no edge.
The MCP client's task groups wrapped every failure during a form, so a refused key was not recognised and the person read 'unhandled errors in a TaskGroup'. A page still showing 'Submitting...' was judged at once and the browser closed. docs/sites.md records the real runs on Workable, SmartRecruiters and Paylocity.
One film, in the formats of the other projects' banners. It says one thing: nobody wants to apply for a job, you want the job. The table of sites stays in docs/sites.md.
- CV: a tailored CV the font cannot draw is not written; the person's own CV is sent, with one line saying why.
- Judge: only a request too large for the endpoint is split; other failures end at once, and the funnel says how many
postings were not judged and why.
- Database: the date is the manifest's; a database older than 7 days is said to be stale; an empty search says when the
60-day age limit is the reason.
- Plugins: a plugin that is not UTF-8 raises PluginError naming the file, as a bad plugin already does.
The browser gets the environment without the model key: the one the settings name, and the default.
rich is declared, as tui.py imports it.
Dead code removed: Store.to_apply, the threshold setting, and tests of removed names.
A new resume replaces the plan and keeps the person's filters, such as exclude.
An unreadable description database is reported once per search, and the search goes on.
_places has no empty part, so a place without a city no longer reads ", Italy".
The apply protocol is named Browser, not Session.
Confirm, Finish and Complete are held back from any element, as sends do.
Judge comments say why, with no numbers a reader cannot check.
README says what leaves the computer and what it costs, who answers for what is sent, how to update and uninstall, and where the search is changed. It states that the mail plugins' read-only rule is an instruction to the model, not enforced in code.
CONTRIBUTING: CI runs on Ubuntu, Windows and macOS, Python 3.11 and 3.12; ruff check runs the default rules, so the 160 line length is not enforced.
docs/plugins.md: the example is lever.md as it is.
SECURITY.md: the mail plugins' read-only rule is an instruction, not a guarantee.
Phone numbers removed from docs/sites.md and the SmartRecruiters plugin; the plugin gives a placeholder.
LLDB engine credit reworded in NOTICE and profile.py comment; CHANGELOG says what the repository was.
The README site list is replaced by a one-line summary and a link to a
table of status per site, from the real forms trials and the index counts.
Ashby's after-sending section quotes the page a real send showed; the
SmartRecruiters plugin says what stops it today.
A select, click or key press that leaves the site returns before the new page has begun to load, so the address read
right after it could still be the form's and the form went on. The page is now read again, for a short while, until its
document has changed (its time origin) or its address has. A page that stays the same is the answer, not a fault.
Each screenshot the model is sent stayed in every later call. Now only the latest one stays, and an older one is a note
that it was taken. The model acts on the latest picture.
A click the check cannot read (Playwright >> chains, :has-text, a control inside a shadow root) is refused with the way
to click it instead: browser_click_at on its centre. The point check reads the innermost element inside open shadow
roots, as the mouse reaches it, so a send control there is refused. A selector that also matches inside a shadow root is
not read. Space and Enter read the focused element inside shadow roots too.
A call refused three times in a row stops the form, and a form that reaches the step limit stops too. Either way the
model is asked once, with only finish_form, what is filled and what did not stick. It never comes back ready, and
needs_input keeps its questions. The step limit is unchanged.
The system rules say a >> or text-syntax selector is refused and what to click instead, and the SmartRecruiters plugin
says the Easy Apply form is in a shadow root.
A key that OrcaRouter refuses is forgotten when it came from the key file, and asked for again; a key from the
environment is named by its variable and not forgotten. Ctrl+C leaves at every prompt, and leaving while a prompt
is open no longer raises NoMatches from the heading. Log lines wrap to the whole width, and Quit stays on the
footer at 80 columns. Database and model failures say what happened in plain words, with the detail after them.
Files saved with a byte-order mark are read. The README gives the install commands checked in new venvs, the
index size from the manifest, and how to change the key. CI runs the unit job on Python 3.11 and 3.12 on Ubuntu,
Windows and macOS; gate still needs all of it. The classifiers name the three systems.
The form model may click a captcha shown as a box to tick (Turnstile,
reCAPTCHA), usually at its coordinates, wait a few seconds, read the page
again and try once more if the box is still unticked. A puzzle or image
challenge, a login wall or a closed posting is still blocked. No solving
service and no script on a captcha.
Tests: a click on the captcha's frame passes the guard, a captcha frame
does not count as sending or as touching the form, and a captcha click
followed by a send is handled through fill_form.
The default form model moves to deepseek/deepseek-v4.1-flash: about 3 minutes and 1.5 US cents a form on 11 real sites,
85 of 100 on the hard local forms, level with the best within noise, where glm-5.3-flash took twice as long at three
times the cost.
Enter is refused everywhere except a bare Enter in a text input or textarea whose own placeholder or aria-label asks
for it (Paylocity's skills, Teamtailor's tags). Control+Enter and other combinations stay refused.
One question before a batch is sent (Enter sends, Escape cancels); no per-job confirmation and no --auto-submit.
A form's questions are asked after the others are sent, then the form is tried again.
After the click the verifying model gets the plugin's text as its rules and the page as untrusted text.
Each clicked job keeps its time, final address, verdict and evidence, page text and a screenshot under data/proofs.
Key s lists every job tried with its plain status; applied.csv is rewritten after each attempt, cells that start like a formula are made text.
The model may run script again, by the owner's decision, knowing a script could send a form without the person's yes.
NEVER_OFFERED is gone. The system prompt says script comes only after selectors, coordinates and a screenshot have
failed, and never to send the application. browser_evaluate counts as acting, so a script that leaves the form's host
stops the form as unconfirmed. The docstrings and the README say what holds.
Each of the 12 site plugins gets a last section: what follows a submit,
with the evidence level per site (real send, saved page, documentation or
nothing). No form was submitted for this.