It sat in clear in the answers table, so whoever copied jobs.db had it.
It is now encrypted with Fernet from the cryptography package, under a key
in data/secret.key, made the first time it is needed and readable by its
owner alone where the system allows it. A password kept plain by an older
AIHawk is sealed on its first read. It is opened whenever the answers are
read, because the form model still has to type it; a key that cannot
open it says the key file was replaced instead of failing quietly. The
constant that names it now lives once, in the store.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
A row in Settings asked the person to open the browser and sign in to a
job site themselves. It should never have been their job. The store now
has an `accounts` table, host to email, and the brief for every form says
either "Your account on nvidia.wd5.myworkdayjobs.com: made on
2026-11-07 with [email protected], and the account_password from the
answers" or "No account of yours is known on this host: if the form needs
one, make it". The model reports with finish_form whether it made one,
and it is written whatever the form then did, because the account exists
on that site either way.
So the row is gone, and with it the hidden `l` key, the worker behind
them and the browser's blank-page sign-in. The mailbox sign-in stays:
that one is a person's own mail, and it is asked for once, when a site
sends a code.
Co-Authored-By: Claude Opus 5 <[email protected]>
A running total nobody asked for, on a screen that is for changing
things. What each application and each scoring cost is still said in the
log as it happens, and a sent application keeps its own cost in its
details, which is where a number answers a question.
Co-Authored-By: Claude Opus 5 <[email protected]>
A row that let the titles be retyped by hand sat above the graph that the
resume builds, with its nodes, forms and weights, and a retyped list
flattened the first node to plain words. The row is gone, and so is the
branch that saved it. The heading still says what the search looks for,
and a different search comes from a different resume.
Co-Authored-By: Claude Opus 5 <[email protected]>
JazzHR, BambooHR, Personio, Breezy, Teamtailor and Greenhouse, each on a
real posting with the test CV, nothing sent: 8 to 48 steps, under a cent
to a cent each. Five of the unknowns the plugins admitted to are now
facts.
The one worth saying once: a file input the page hides refuses the file,
and the upload goes to the button beside it, which the page labels. It
happened on Greenhouse and on Personio in the same run, so it is in the
rules every form gets, and each plugin keeps its own selector:
`[aria-labelledby='upload-label-resume'] button` on Greenhouse,
`[aria-labelledby="doc-label-cv"] button` on Personio, `a.resume` on
Breezy, `#resumator-resume-value` on JazzHR.
Also settled: a closed BambooHR posting sends the browser to the
company's whole list at /careers, which is that plugin's missing
closed-posting fact; Personio's company questions are
`#field-custom_attribute_<number>` and its send control is
`form button[type="submit"]`, with the page in English on some .com
postings; Breezy's send control is a `type="button"` and uploading fills
its summary field; Teamtailor's cookie banner is declined by its
aria-label in the page's language.
docs/sites.md records each run with its steps.
Co-Authored-By: Claude Opus 5 <[email protected]>
The guide still described menus as if menus.md did not exist, told authors
to name a sender domain for the mailbox, and quoted a Lever plugin three
changes out of date. It now says the rule a sentence must pass, with one
example that belongs and one that does not; the two things that are not
road but boundary; and what the code already decides, which is the step
budget, the two mailbox visits, and the rules every form gets. Its menus
section points at menus.md and asks only for what is a site's own. The
Lever example is the file itself. A dry run on a real posting is named as
the first way to test a plugin.
Co-Authored-By: Claude Opus 5 <[email protected]>
Greenhouse, Ashby and Lever, each on a real posting with the test CV,
nothing sent. Lever filled in 22 steps and under a cent, with no captcha;
Ashby in 40 steps and a cent; Greenhouse met a closed posting.
What the runs taught, now in the plugins: a closed Greenhouse posting
sends the browser to the board at /<company>?error=true, which is the
closed-posting fact that plugin lacked; Ashby's fields are bare uuids read
from each label's `for`, its Yes and No are the question's own buttons or
labelled radios, its consent box is required and its send control, which
no id names, was matched alone by `#form > button:last-of-type`; Lever's
location suggestions are `#location-0` and the rest.
On the closed posting the model went to a search engine to look for the
job, which is three steps spent outside the task. The rules every form
gets now say to stay on the form's own site and the mailbox the site's
instructions name, and that a closed posting is reported, not hunted.
Co-Authored-By: Claude Opus 5 <[email protected]>
The step that goes wrong on a real form is almost always a list. Instead
of repeating that in fourteen site plugins, menus.md says it once and is
added to every form's instructions, as the mail plugins are: which of the
four kinds the field is (a native select, a listbox, a typeahead, a radio
group drawn as a menu) and what opens, reads and sets each; that the
choice is read back and never assumed; that a click the tool refuses is
not clicked again, but reached with ArrowDown and Enter, or by its centre
read with browser_evaluate when it sits in a shadow root; and that a list
which has won three times is left, blocked and named, because a form half
filled says more than fifty steps spent on one menu. Paylocity's plugin
keeps only what is its own, the ids and which option declines.
Co-Authored-By: Claude Opus 5 <[email protected]>
From the traces of 2026-11-07: SmartRecruiters' preliminary questions take
their text on `input#question_<uuid>`, not the bare id; Workable's cookie
banner, file input and div radios by the selectors that worked. The guide
has two new sections, "When the model is stuck" and "Menus and dropdowns",
each case with the failure the model sees, the way out, and where it was
seen, so a plugin gives direction in the moments it is needed.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
A dry run with a test CV reached the send control on SmartRecruiters in
42 steps and on Paylocity in 148 of the 150 allowed: the demographic
listboxes and typeahead lists took most of them. The plugin now says what
worked on that form: the listbox options by id, ArrowDown and Enter when
one is out of reach, and the typeahead's first row.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
The owner asked where the money went. Now the app says: every model
call's tokens are priced with the gateway's own list, read once per
start; an application keeps its cost in the store and shows it in its
details, the log says what each application and each scoring cost, and
Settings shows what the session has spent. The shortlist model is not
counted: another endpoint, under a cent a hundred postings. The mail
plugins look for the site's message twice at most and then stop with one
fixed sentence, instead of the spam folder, a new sign-in and guesses.
Six more double-encoded middle dots on the screen are real ones now.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
tests/sites/workday_local.py serves the pages a Workday application
goes through, with the control names read on a real site on 2026-11-07
up to "Create Account", then a "Verify Your Email" page whose six-digit
code goes by mail when AIHAWK_TEST_SMTP is set, and the five steps to
"Submit". It is for trying the Workday plugin and the mail plugins
without an employer's site. AIHAWK_TRACE writes data/trace.log for a
real application as a dry run does.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
A trial is for looking. With AIHAWK_DRY_RUN set, each call to the browser
and what it answered go to a file beside the proofs, with the account
password hidden, and the model's finish_form report closes the entry.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
After the resume, in the voice of the key question: the email you apply
with, prefilled from the CV. Gmail or Outlook is told by the domain, or
asked in a word. The browser then opens on that mailbox, not on a blank
page, and once the person says they are done the inbox is checked: a
mailbox that sends the browser to its sign-in page is said to be not
signed in, and Enter opens it again. Esc leaves it for later, once. When a
site sends a code and the mailbox is not signed in, the mail plugins say
one fixed sentence, the app asks the same question there and then, and
runs those applications again. Settings has "Your mail"; the hidden key
stays for a job site's own sign-in.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
The owner's rule, now general: never "Sign in with", "Register with",
"Continue with" or "Apply with" another service; an account is made by
hand with the candidate's email and the account password, and the
mailbox is only read for a code or a link. AIHAWK_DRY_RUN set in the
environment stops an application once the form is filled and the send
control is found, with a screenshot and nothing sent: for trying a site
or a plugin with a test CV. Documented, tested.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
The answers move from answers.yaml into an `answers` table of data/jobs.db,
the SQLite file that already holds the jobs: name, value, source, when.
An old answers.yaml is brought in once and renamed. At the first start
AIHawk makes `account_password`, twenty characters of every class, and
keeps it there: the one password for sites that need an account, which
the Workday plugin now names. Settings has "Your answers": see, change,
add or remove one. Docs and SECURITY say where the answers and the
password live, and that the password is in clear.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
Read on a real site on 2026-11-07, up to the "Create Account" page: the
/apply page, "Sign in with email", the sign-in and create-account forms by
data-automation-id, the robots-only field never to fill, and the mail from
myworkday.com for a verification code. The steps after sign-in are
Workday's usual form, said as such; docs/sites.md says the plugin has not
yet run on a real form.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
The biggest source, jobhive, publishes around 15:00 UTC and the new-grad
lists commit every hour; the 04:23 build read them up to a day late. Cron
23 */5 * * *, off the top of the hour; timeout 200 minutes so a hung run
does not take the next slot; six dated releases kept, about thirty hours.
Texts say five hours, and the search log gives the build's hour.
Co-Authored-By: Claude Fable 5.1 <[email protected]>
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.
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.
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 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.
This repository is AIHawk again. Dots moved to feder-cr/invisible_dots with its history.
Every day a job merges the public job databases into one (pipeline/), one row per posting, and publishes it as a
release. `aihawk scan` queries it in place, so a user scrapes nothing and downloads only what the search reads. `score`
judges jobs against the CV through OrcaRouter, `apply` tailors the CV and fills the form through invisible-playwright-mcp.
The model never sends an application: it fills the form and names the submit control, and AIHawk clicks it after the
person's yes, or by itself with --auto-submit. While the model works, a click on what would send the form and the Enter
key are refused. What the CV and the answers file do not say comes back as a question, never as a guess.
The units were enabled, so on every boot after the first systemd started them at multi-user.target, before
cloud-init's final stage ran install.sh (cloud-final.service is After=multi-user.target): a Dot's serial log shows
dot-agentd and the engine up about two seconds before install.sh, which is what sets the VM proxy's firewall and
tunnel. A Dot with a proxy could go out directly in that window, and install.sh's own comment said the opposite.
The units are no longer enabled (no [Install]): install.sh starts them on every boot after the proxy, as it already
did on the first, and removes the wants links an earlier runtime made. install.sh is also the first point of the
boot that has the seed's config.json, which the same final stage writes.