Founder & Sole Developer
An AI resume-optimization platform — tailors resumes to target roles, renders clean PDFs, and ships with a Chrome extension.
HireOS is a bring-your-own-key AI resume optimizer, built solo. A user pastes a job description and their current resume; the platform rewrites and tailors the resume against that specific role using whichever AI provider the user has a key for, then renders an ATS-parseable PDF. A companion Chrome extension triggers the whole flow from a job listing page without leaving it.
The interesting constraint was the business model, and it drove the architecture: users bring their own API key and pay their own inference costs. That removes my per-user marginal cost entirely, but it means the system has to work correctly across five providers with different APIs, different rate limits, and different failure behavior.
Job seekers are told to tailor their resume per application, and they are right to. But doing it properly takes twenty to thirty minutes per role, which does not survive contact with an actual job search where you are applying to dozens. So people either send the same generic resume everywhere or they burn out.
The tools built to solve this had a trust problem rather than a capability problem. They were monthly subscriptions, they did not disclose which model was running, and the output frequently broke ATS parsing — the one thing a resume tool absolutely must not do. Paying a subscription to have your resume silently rejected by a parser is worse than doing nothing.
On the engineering side, three things were genuinely hard. Producing a PDF that both looks designed and survives ATS text extraction is a real constraint, not a styling preference. Supporting five AI providers means five different error shapes, token accounting models, and streaming formats. And a Chrome MV3 extension reading LinkedIn's DOM is scraping a hostile, frequently changing target inside a manifest version that removed persistent background pages.
The bring-your-own-key model is enforced end to end: keys are held client-side and passed per request, never stored server-side in plaintext. That is both a security posture and a marketing claim I can actually defend — there is no key database to breach.
Cloudflare Workers sit in front of every provider as a normalizing proxy. Each provider gets a small adapter that translates its request shape, response shape, streaming format, and error taxonomy into one internal contract, so the application code targets a single interface and a new provider is one adapter rather than a set of conditionals scattered through the codebase. Running this at the edge also cut round-trip latency noticeably compared with proxying through a single region.
For PDF output I generate a real text layer rather than rendering to an image, and I test extraction rather than trusting appearance. The template system keeps layout to a single-column flow with proper heading semantics — the layout ATS parsers actually handle — while still looking deliberately designed.
The Chrome MV3 extension keeps its LinkedIn selectors in one small, replaceable module with a text-heuristic fallback, on the assumption that the DOM will change without notice. When extraction fails it degrades to a paste field instead of silently producing a bad resume.
Choosing bring-your-own-key was the decision everything else followed from. It removes my inference cost, removes the incentive to quietly downgrade to a cheaper model, and makes the model choice the user's. The cost is a harder integration surface and a slightly steeper onboarding step, which I judged worth paying for a tool where trust is the product.
Cloudflare Workers over serverless functions in a single region was about the shape of the workload. These are long-lived streaming requests that mostly wait on an upstream API, which is exactly what Workers are efficient at and what a traditional per-invocation function bills badly for. Edge placement also put the proxy nearer the user than a single-region deployment would.
Next.js 16 with React 19 gave me streaming server components, which matter here because a tailored resume takes real seconds to generate. The user watches sections resolve progressively instead of watching a spinner, which is the difference between a feature that feels fast and one that feels stuck.
Manifest V3 was not optional — MV2 is deprecated — but it forced the extension into a service-worker model with no persistent state. I treated that as a design constraint and kept the extension deliberately dumb: it extracts context and hands off to the web app, rather than trying to hold a session of its own.
I'm available for full-time startup roles and select freelance projects.