Skip to content

WorkWingman — Technical Overview

WorkWingman is a local-first Windows desktop application: a job-application Career Concierge that pulls your saved jobs, tailors documents in your voice, fills applications in a browser you can watch, helps you study for each role, and tracks where you applied and your interviews. Everything sensitive stays on your machine.

  • Repository: private GitHub repo andrewjonesdev/WorkWingman.
  • Target: Windows desktop, single user, offline-capable.

The problem it solves

Applying to jobs is repetitive, demoralizing, and error-prone. You re-type the same personal details into form after form, rewrite the same resume for every posting, and lose track of who you applied to and when interviews land. Existing "auto-apply" tools solve this by uploading your entire profile to a hosted service and blasting out applications without your review.

WorkWingman takes the opposite stance. It does the tedious work on your own machine, shows you everything before anything is submitted, and never auto-submits. It is a Career Concierge, not an autopilot.

Stack

Layer Technology Docs
Shell Electron (spawns the API + hosts the UI) electronjs.org/docs · GitHub
Frontend Angular 22 (standalone components, signals) angular.dev · GitHub
Backend ASP.NET Core on .NET 10, loopback-only learn.microsoft.com/dotnet · GitHub
HTTP clients Flurl.Http flurl.dev · GitHub
Resilience Polly (retry + backoff on 429 / 5xx / network) pollydocs.org · GitHub
Browser automation Microsoft.Playwright (.NET) playwright.dev/dotnet · GitHub
HTML parsing HtmlAgilityPack html-agility-pack.net · GitHub
Secrets KeePass vault (vault.kdbx) keepass.info · KeePassXC

Solution structure

The .NET solution (WorkWingman.slnx) is split into four projects with a strict dependency direction (Core has no dependencies; everyone points inward toward it):

Project Responsibility
WorkWingman.Core Domain models + service interfaces. No infrastructure dependencies.
WorkWingman.Infrastructure Flurl + Polly REST clients, Playwright automation, KeePass vault, LocalJsonStore, dependency doctor, service implementations.
WorkWingman.Api ASP.NET Core controllers, bound to loopback http://127.0.0.1:5211 only (never 0.0.0.0).
WorkWingman.Tests 381 xUnit tests (unit + in-memory API smoke tests).

Alongside the solution: frontend/ (Angular), electron/ (Electron main + preload), and design/ (imported prototype).

Architecture

The Electron shell is the process supervisor: it spawns the .NET API, waits for it to come up on loopback, then opens the Angular renderer. All app data flows renderer → loopback API → controllers → Core interfaces → Infrastructure implementations → external systems.

flowchart TD
    subgraph Electron["Electron shell (process supervisor)"]
        Renderer["Angular 22 renderer<br/>(10 screens, signals)"]
        LiveView["Embedded automation browser<br/>(live-run viewer)"]
    end

    Renderer -->|"loopback HTTP<br/>127.0.0.1:5211"| API

    subgraph Backend[".NET 10 API (loopback only)"]
        API["Controllers"]
        Core["Core interfaces<br/>(models + contracts)"]
        API --> Core

        subgraph Infra["Infrastructure implementations"]
            Clients["Flurl + Polly REST clients"]
            Auto["Playwright automation<br/>(scraper + Workday engine)"]
            Vault["KeePass vault<br/>(metadata over HTTP, secrets in-process)"]
            Store["LocalJsonStore<br/>(~/Wingman/data — source of truth)"]
            Doctor["Dependency doctor"]
        end
        Core --> Clients
        Core --> Auto
        Core --> Vault
        Core --> Store
        Core --> Doctor
    end

    Clients -->|"REST"| Ext["Google Sheets / Calendar,<br/>Apple Calendar, LinkedIn"]
    Auto -->|"drives"| LiveView
    Auto -->|"scrapes / fills"| Sites["LinkedIn jobs tracker,<br/>Workday applications"]
    Doctor -->|"winget / npm / Playwright"| Tools["Node, Chromium, KeePassXC,<br/>Claude CLI, .NET, Git, gh"]

Key boundary rule: secrets never cross the HTTP API. The vault exposes only metadata over HTTP; the automation engine resolves passwords in-process, just-in-time.

The 10 screens

# Screen What it does
1 Connections Shows the status of external accounts (Google, Apple, LinkedIn) and connection settings.
2 Onboarding Captures the intake profile — the basic facts used to fill common form fields.
3 Your story A textarea-first deep dive into your background and voice; the raw material for tailored documents. Long spoken answers (via system dictation) beat short typed ones.
4 Job queue Pulls saved jobs from your LinkedIn jobs-tracker, oldest-first, and lets you resync.
5 Documents Generates and versions a tailored resume + cover letter per job, with an approval step. When no ATS adapter exists, offers one-click copy-paste of every field.
6 Study & prep Builds a per-job study plan: docs, YouTube, Udemy, Google Skills Boost, AWS Skill Builder, MS Learn, LeetCode, and Medium links, plus an optional Claude study project.
7 Live run The watchable run: fills a Workday application in an embedded browser, pausing to ask when unsure. Pause / resume / stop / resolve controls.
8 Applied & interviews Tracks submitted applications and interviews, synced to Google + Apple calendars.
9 Settings & vault Vault status/unlock and secret metadata; app settings.
10 Dependencies (doctor) Checks required tooling versions on launch; installs/repairs after per-item approval.

The 5-step run, end to end

  1. Read jobsLinkedInJobsScraper (Playwright) harvests saved jobs oldest-first into the local store.
  2. Tailor documentsDocumentService produces a versioned resume + cover letter in the user's voice, gated by an approval step.
  3. Fill the applicationWorkdayAutomationEngine fills the form in a watchable browser and pauses for judgement calls when confidence is low.
  4. StudyStudyPlanService assembles a per-job study plan from curated learning sources.
  5. TrackApplicationTrackerService records the submission and surfaces interviews on the user's calendars.

Trust guarantees (baked into the code)

These are not settings — they are structural properties of the system:

  1. Never auto-submits. AutomationSettings.AlwaysRequireReviewBeforeSubmit is a computed true. There is no code path to disable it; the final submit always waits for you.
  2. Judgement calls, not guesses. Below the fill-confidence threshold (default 90%) the run pauses and presents ranked options. Your pick persists as a rule and is never asked again.
  3. Secrets never cross the HTTP API. The vault exposes metadata only; the automation engine resolves passwords in-process, just-in-time. A hostile local web page hitting the loopback API cannot read your passwords.
  4. Local-first. The JSON store under ~/Wingman/data is the source of truth. Google Drive/Sheets is backup sync, not hosting.
  5. Encouragement, deliberately. Rotating affirmations and milestone toasts, because job hunting is demoralizing.

Dependency doctor security

On launch the doctor checks that Node 22.12+, Playwright's Chromium, KeePassXC, the Claude CLI, .NET, Git, and gh meet their minimum versions (read-only --version probes). It installs or repairs via winget / npm / the in-process Playwright installer only after per-item user approval. Because the install endpoint shells installers, it is token-gated by a per-launch secret readable only by processes running as you — a hostile web page hitting loopback cannot trigger installs. Detection (GET) stays open; only installs are gated.

Current stubs (compile, DI-registered, wired later)

These exist deliberately so the app compiles and runs end-to-end today, with one obvious replacement point each. They are placeholders, not finished features:

Stub Current behavior Replacement point
Google OAuth UnconfiguredGoogleTokenProvider throws with setup instructions. Desktop loopback OAuth flow; refresh token into the vault.
KeePass kdbx In-memory seed data. Real KeePassLib reads/writes of ~/Wingman/vault.kdbx.
Claude drafting TemplateDrafter produces deterministic placeholder documents. Real Claude-backed tailoring; versioning/approval already works around it.
Gmail interview detection Sync hook present, no real Gmail search. Search Gmail for invites, then create calendar events.
Live-run browser Angular shows a placeholder frame. Electron WebContentsView attached over that region, driven by the automation engine.
Sheets mirroring Not yet called on submission. Append a row to Google Sheets once OAuth is wired.

Roadmap (not yet built)

The following are planned, not implemented. They are named here only so the stubs above have context — do not treat them as working today:

  • Real Google OAuth and Sheets/Calendar mirroring on submission.
  • On-disk encrypted KeePass vault.kdbx (replacing the in-memory seed).
  • Real Claude-backed document tailoring and Claude study projects.
  • Gmail-based interview detection feeding the calendars.
  • The Electron WebContentsView live-run browser.