Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,9 @@ SAFETY RULES (most important)
profile. The script selects only the exact "resume_label" saved in profile.json and
never uploads anything.
(Added 2026-10-12, my request.) The company-site agent may also upload my unofficial transcript (local_config.json
"transcript_path", GUI Settings; the homelab gets a copy as local/transcript.pdf), only where a page asks for one.
"transcript_path"; GUI Settings > Your files keeps it as local/transcript.pdf; the homelab gets a copy there), only where
a page asks for one. (2026-10-20) The resume and the transcript given in the GUI are NUauto's own copies in local/,
pushed to the homelab at once (docs/GUI.md, YOUR FILES).
7. (Changed 2026-10-03; GUI added 2026-10-05.) `nuauto apply` submits every Approved row, one at a
time, with no per-job prompt: Approved in the sheet IS my approval. It runs only with me there:
in a real terminal, or from the GUI (`nuauto gui`: I click Start, the browser is visible, Stop =
Expand Down
8 changes: 5 additions & 3 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ MODULES (src/nuauto/)
update_after() is the guard; bank() is the answer-bank command; assist_target(),
nuworks_side().
inspect_form.py read-only lister of an Apply form. main(); FIELDS_JS is reused by apply.py.
sync.py laptop<->homelab rsync. pull(), push(), push_session(), server_busy(), deploy_now(); PACKAGE/CODE
sync.py laptop<->homelab rsync. pull(), push(), push_documents(), push_session(), server_busy(), deploy_now(); PACKAGE/CODE
list the files that reach the homelab (not sent when deploy_from_git is on).
insights.py summarize(pool, details, rows) -> pay (hourly(), buckets, median, middle half), places (place(),
by state), categories, sheet statuses; text() for `nuauto insights`; gui.py's /api/insights.
Expand All @@ -49,7 +49,9 @@ MODULES (src/nuauto/)
Task (one child process + its JSON-lines questions), the pages' data (state, review, apply,
company, answers, settings, setup), open_terminal(), screenshots(). Static files: gui_static/.
onboard.py the setup wizard's steps (steps(), check_client, open_and_prepare, resume_preview, check_prefs,
save_webhook, scheduler_*, launcher_create) and the read-only NUworks reads (resume labels, terms).
save_webhook, scheduler_*, launcher_create), your files (save_document / remove_document /
document_info: the resume and transcript copies in local/) and the read-only NUworks reads (resume
labels, terms).
demo.py demo mode: FakeClient/FakeWorksheet (sheet), serve_nuworks (fake NUworks pages in Playwright), the
fake claude (fake_claude, run through a two-line script), sample data, forced states; setup() / update().
window.py which window (kind(): mac / gtk / app / tab), run_mac() (pywebview, main thread), open_gtk() (the GTK
Expand Down Expand Up @@ -128,7 +130,7 @@ STATE ON DISK (repo root; all gitignored except as noted)
profile.json), answers.json (answers.py), Google files (token.json, google_login.txt;
sheet.py), NUworks session (session_cookies.json, browser_profile/, browser_profile.lock; browser.py),
assist_profile/ (assist.py), web_secret.txt (web.py), discord_webhook.txt (daily.py),
gui.lock (gui.py), onboard.json (onboard.py). Demo mode: all of these under a temp NUAUTO_STATE_DIR.
gui.lock (gui.py), onboard.json, resume.pdf, transcript.pdf (onboard.py). Demo mode: all of these under a temp NUAUTO_STATE_DIR.
Which of these sync to the homelab: docs/DEPLOY.md.
Also in the repo, tracked: prompts/ (Claude prompts), tests/ (offline checks), deploy/systemd/.

Expand Down
5 changes: 3 additions & 2 deletions docs/DEPLOY.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,9 @@ SECRETS AND STATE (never print or log any of these; all mode 600)
own ones imported from Bitwarden (`nuauto accounts import`); with
remote_assist merged both ways by sync.share (docs/REMOTE.md), else never
synced; `nuauto accounts export` (or Settings) writes Bitwarden's import file
transcript.pdf (yes) (yes) your unofficial transcript (transcript_path, any place on the laptop), for the assistant to
upload; with remote_assist sync.push copies it there (via a 600 copy in local/)
transcript.pdf (yes) (yes) your unofficial transcript (transcript_path: since 2026-10-20 the GUI's copy here, or
any place on the laptop), for the assistant to upload; with remote_assist
sync.push copies it there (via a 600 copy in local/), at once when given in the GUI
gmail_token.json yes (yes) the read-only Gmail login for the assistant's email codes (`nuauto login gmail`;
renewed by `nuauto login google`); with remote_assist sync.push (if newer)
gui.lock yes (yes) the running GUI (pid, port, a secret that only opens a window); not synced (the
Expand Down
21 changes: 17 additions & 4 deletions docs/GUI.md
Original file line number Diff line number Diff line change
Expand Up @@ -158,7 +158,7 @@ SCREENS
a dialog, the browser's own find
Settings every health check (run them now), logins (Google, NUworks, Claude Code, and Gmail: the read-only login
for the assistant's email codes, with a link to turn the Gmail API on), resume label, applications per
week (the weekly cap), week start;
week (the weekly cap), week start; Your files (the resume and the transcript: YOUR FILES);
the company-site assistant: account email, effort (low / medium / high), assistants at once (1-3, on each
machine; no homelab needed), the folders it may read, your writeups (assist_writeups: the first place it
looks for essays), its job-site logins (how many; Export for Bitwarden: the import file in ~/Downloads,
Expand All @@ -184,7 +184,7 @@ SCREENS
(remote_auto {"nuworks", "other"}), its hours (remote_hours), assistants at once (assist_slots), Pair this
computer, Open it; setup, logs, quit. On the homelab's page one This homelab card: Paired devices (each
one, "this one", Unpair, Make a pairing link for another device) and its assistant's queue settings (no
on / off: the laptop's); the email and effort only (the transcript, folders, export: on your laptop); no
on / off: the laptop's); the email and effort only (folders, export: on your laptop); Your files read-only; no
Password manager card (Bitwarden unlocks on the laptop), no Quit
Setup the wizard (below); opens by itself until setup is done (never on the homelab's page: setup is the
laptop's; there it says so, and the steps it can do are preferences, Discord and the resume label)
Expand All @@ -202,7 +202,8 @@ SETUP WIZARD (onboard.py + gui_static/setup.js; each step checks itself)
accepted; saved local/client_secret.json, mode 600); then the Google login
sheet create one (Sheets API spreadsheets.create; gspread's create needs the Drive API) or paste the
address (ID extracted, access tested). Empty = set up; other data = refused, never overwritten.
resume native file dialog (osascript / zenity / kdialog) or a path; the PDF must have text
resume drop the PDF or choose it (the page's own file chooser), or one it found (PDFs called resume / CV in
Documents, Downloads, Desktop); the PDF must have text. NUauto keeps its own copy (YOUR FILES)
nuworks login (Firefox window; you type nothing into NUauto), then the resume label read from one job's
real Apply popup (read-only: opens it, reads the Resume dropdown, Cancel)
preferences what you study first (fields.py: Engineering or CS, Business, Health sciences or nursing, Natural
Expand Down Expand Up @@ -405,21 +406,33 @@ API (JSON; all need the cookie; POSTs need X-NUauto: 1)
{on, check, waiting, own, only, bw}, homelab_assistant {available, on, app, auto (any tab),
auto_tabs {nuworks, other}, hours, slots, review_url}, where, homelab (the laptop:
onboard.homelab_link) and (the homelab) devices, this
POST /api/files {"kind": "resume" | "transcript", "data": the PDF in base64} or {"kind", "remove": true}
(transcript only) -> settings; up to 10 MB (the only request over 2 MB); the homelab: 409
POST /api/logins/export Bitwarden's import file of the logins not exported yet ({"every": true}: all) -> {"path", "count"}
GET /api/setup wizard steps; POST /api/setup {"action": ack|client_upload|client_downloads|
sheet_create|sheet_link|resume_pick|resume_set|labels_read|label_set|terms_read|
sheet_create|sheet_link|resume_set (a path it found: copied in)|labels_read|label_set|terms_read|
prefs_save|discord_save|discord_test|discord_remove|schedule_on|schedule_off|launcher}
GET /api/logs, /api/file?path= past runs (the assistant's own screenshots are in a run's browser/); a screenshot or
log under logs/ only
POST /api/quit

YOUR FILES (2026-10-20, my request: "a way to upload a resume and a transcript into the gui")
- The resume (Setup's resume step, or Settings > Your files) and the transcript (Settings > Your files; optional, Remove
takes it out) are given to NUauto by dropping or choosing the PDF in the page. NUauto keeps its own copy,
local/resume.pdf / local/transcript.pdf (mode 600; onboard.save_document: a PDF, up to 10 MB, the resume with text
Claude can read), and points resume_path / transcript_path at it. So changing the PDF you had elsewhere changes
nothing: give NUauto the new one. An older resume_path / transcript_path anywhere on the laptop still works.
- With a homelab the laptop pushes the copies there at once (sync.push_documents; a removed transcript is deleted
there too), not at the next push. The homelab's page shows the copies it has (name, size, when) and can't change them.

FILES IT WRITES (all under local/, mode 700, except as noted)
gui.lock pid, port and a secret that only lets a second start ask for a window (removed on exit); on the
homelab nuauto-web's (its runs check it)
settings.lock local_config.json and profile.json are changed under it (Settings, setup, sync's merge)
review_devices.json, review_pair.json (homelab) the paired devices' key hashes, the open pairing links' hashes
browser_profile.lock who uses the NUworks browser (pid, what, since)
onboard.json what the read-only NUworks reads found (resume labels, terms)
resume.pdf, transcript.pdf YOUR FILES (mode 600)
local_config.json gains tos_ack, preferences, tools (saved tool paths) through the wizard (mode 644, as before)
scheduler: ~/.config/systemd/user/nuauto-daily.{timer,service} or ~/Library/LaunchAgents/com.nuauto.daily.plist
launcher: ~/.local/share/applications/nuauto.desktop (+ icon) or ~/Applications/NUauto.app
Expand Down
4 changes: 2 additions & 2 deletions docs/PIPELINE.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,6 @@ year's lists are new repos: changing the term moves the addresses with it); off
Discord: new pool internships after a run; the morning message adds the last day's internship counts.

RESUME
- The PDF at local_config.json resume_path (the only file in its folder). The homelab
gets a copy as resume.pdf. Updated 2026-10-02 16:44 (minor: past job title/description).
- The PDF at local_config.json resume_path: since 2026-10-20 the GUI keeps the one you give it as local/resume.pdf
(docs/GUI.md, YOUR FILES). The homelab gets a copy as resume.pdf. Updated 2026-10-02 16:44 (minor: past job title/description).
Jobs scored before that used the 10:03 version; not re-scored since the change is minor.
1 change: 1 addition & 0 deletions docs/REMOTE.md
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ WHAT LIVES WHERE (with NUauto on the homelab)
kept with it ("other_passwords"); the merge names the site. Sent as JSON over ssh, never printed or logged.
discord_webhook.txt pushed by push() when the laptop's is newer (the homelab sends the messages).
transcript (transcript_path) pushed by push() to local/transcript.pdf (mode 600); the agent there uploads it.
resume, transcript, given in the GUI pushed at once (sync.push_documents; docs/GUI.md, YOUR FILES).
gmail_token.json pushed by push() when the laptop's is newer (`nuauto login google` / `login gmail`).
your writeups (assist_writeups) copied by push() to ~/nuauto-notes/<folder name> on the homelab: only the files the
agent may read anyway (sync.notes_files = assist.secret_path: no keys, tokens, .env, .git, NUauto's local/),
Expand Down
26 changes: 18 additions & 8 deletions src/nuauto/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -64,14 +64,24 @@
# host is deliberately NOT here; log in by hand with `nuauto login`.
ALLOWED_HOSTS = {"northeastern-csm.symplicity.com"}
# Job pool (jobs.py)
# Laptop: the resume at local_config resume_path. Homelab: a copy the laptop pushes to local/resume.pdf.
LAPTOP_RESUME = os.path.expanduser(LOCAL["resume_path"])
RESUME_PATH = LAPTOP_RESUME if os.path.exists(LAPTOP_RESUME) else os.path.join(LOCAL_DIR, "resume.pdf")
# The unofficial transcript (optional), for the company-site assistant to upload where an application asks for one.
# Laptop: local_config "transcript_path". Homelab (remote_assist): a copy the laptop pushes to local/transcript.pdf.
LAPTOP_TRANSCRIPT = os.path.expanduser(LOCAL.get("transcript_path") or "")
TRANSCRIPT_PATH = (LAPTOP_TRANSCRIPT if LAPTOP_TRANSCRIPT and os.path.exists(LAPTOP_TRANSCRIPT)
else os.path.join(LOCAL_DIR, "transcript.pdf"))


def set_files(cfg):
"""The resume and the transcript this process uses (at import, and again after a settings change).
Laptop: the resume at local_config resume_path (since 2026-10-20 the GUI keeps what you give it as local/resume.pdf
and points resume_path there). Homelab: a copy the laptop pushes to local/resume.pdf.
The unofficial transcript (optional), for the company-site assistant to upload where an application asks for one:
laptop local_config "transcript_path" (the GUI's: local/transcript.pdf); homelab (remote_assist): a copy the laptop
pushes to local/transcript.pdf."""
global LAPTOP_RESUME, RESUME_PATH, LAPTOP_TRANSCRIPT, TRANSCRIPT_PATH
LAPTOP_RESUME = os.path.expanduser(cfg.get("resume_path") or "")
RESUME_PATH = LAPTOP_RESUME if os.path.exists(LAPTOP_RESUME) else os.path.join(LOCAL_DIR, "resume.pdf")
LAPTOP_TRANSCRIPT = os.path.expanduser(cfg.get("transcript_path") or "")
TRANSCRIPT_PATH = (LAPTOP_TRANSCRIPT if LAPTOP_TRANSCRIPT and os.path.exists(LAPTOP_TRANSCRIPT)
else os.path.join(LOCAL_DIR, "transcript.pdf"))


set_files(LOCAL)
DATA_DIR = os.path.join(STATE_DIR, "data") # list, triage, details, scores, ratings, pool
WORK_DIR = os.path.join(STATE_DIR, "work") # batch files exchanged with the Claude subagents
ANSWERS_PATH = os.path.join(LOCAL_DIR, "answers.json")
Expand Down
Loading
Loading