commit 432e6c0025e641e791c1f5f27c540ce62407aee5 Author: Oleg Proskurin Date: Sun May 24 14:13:14 2026 +0700 init CV repo diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3b9b30a --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +node_modules/ +.env +.env.local + +# Generated HTML can be regenerated, but keep PDFs in version control +output/html/ + +# Editor / OS +.DS_Store +*.swp +.idea/ +.vscode/ + +# Puppeteer download cache +.cache/ diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..b6c080a --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,202 @@ +# CV-2026 — Project Instructions for Claude + +This project is Oleg's CV generation pipeline. It supports three workflows: + +1. **Base CV refresh** — turn a new Markdown CV into a styled PDF. +2. **Company tailoring** — quickly adapt the latest base CV to a specific company/role and produce a tuned PDF. +3. **Application tracking** — log applications, outreaches, and follow-up communications. + +The visual format of every generated PDF must match `base/reference/Oleg_Proskurin_Techlead_resume.docx.pdf` — that file is the canonical style reference. + +--- + +## Folder layout + +``` +base/ + cv-YYYY-MM-base.md # current master CV in Markdown + reference/ + Oleg_Proskurin_*.pdf # canonical visual reference (do not edit) +templates/ + cv-style.css # shared stylesheet — used by every CV + cv-template.html # skeleton HTML (use as a starting point) +tailored/ + / + cv.md # tailored Markdown + cv.html # generated HTML + cv.pdf # final PDF + notes.md # job posting, contact, decisions log +tracking/ + applications.md # one row per application + outreach.md # cold outreach + recruiter conversations +scripts/ + generate-pdf.mjs # HTML -> PDF (puppeteer) +output/ + html/ # generated HTML for base CVs (transient) + pdf/ # final PDFs for base CVs (committed) +``` + +--- + +## Workflow 1 — Generate base CV PDF from Markdown + +**Input**: `base/cv-YYYY-MM-base.md` +**Output**: `output/pdf/cv-YYYY-MM-base.pdf` + +Steps: + +1. Read the Markdown source and the reference PDF (`base/reference/...`) to confirm structure. +2. Generate `output/html/cv-YYYY-MM-base.html` using `templates/cv-template.html` as a starting point. + - Link the stylesheet via `` (absolute path — the PDF script serves the project root). + - Map MD content to HTML tags **verbatim** — section titles, punctuation, list structure, dates, and contact separators must match MD character-for-character. The structure below is the tag shape; the **text** comes from MD. See "Content vs. style — separation rule" below. +3. Run the PDF generator: + ```bash + pnpm pdf output/html/cv-YYYY-MM-base.html + ``` +4. Open the resulting PDF (`output/pdf/cv-YYYY-MM-base.pdf`) and verify single-page-ish, no orphan bullets, no overflow. + +--- + +## Workflow 2 — Tailor CV for a specific company + +**Input**: latest `base/cv-*.md` + job description / company info +**Output**: `tailored//cv.pdf` + +Steps: + +1. Create `tailored//` folder. Use a short kebab-case slug (e.g. `vercel`, `stripe-platform`, `anthropic-applied`). +2. Copy the base MD into `tailored//cv.md`. +3. Tailor the content: + - **Summary**: re-frame around the company's stack and the role's keywords. Keep facts intact, shift emphasis. + - **Skills**: reorder so the most relevant categories/tools appear first. Drop categories that are irrelevant to keep density. + - **Bullets**: reorder within each job so the top bullets match what the company values. Edit phrasing to use the company's vocabulary, but never invent achievements. + - Keep length ≤ 2 pages. +4. Save the job posting, recruiter contacts, and tailoring decisions in `tailored//notes.md`. +5. Generate `tailored//cv.html` (same template/CSS). +6. Run the PDF generator with an explicit output directory: + ```bash + pnpm pdf tailored//cv.html tailored/ + ``` + That writes `tailored//cv.pdf`. +7. Add a row to `tracking/applications.md`. + +--- + +## Workflow 3 — Tracking + +- **`tracking/applications.md`**: one row per application. Status column drives the funnel (applied, screen, interview, offer, rejected, withdrawn). Always store absolute dates (e.g. `2026-05-24`). +- **`tracking/outreach.md`**: log cold messages, recruiter pings, and follow-ups. One row per touchpoint; group by company. +- When asked to "log this application" or similar, append to the right file — never rewrite existing rows unless explicitly asked. + +--- + +## Content vs. style — separation rule + +**This is the load-bearing rule for MD→HTML conversion. Read it before generating any HTML.** + +All text in the generated HTML comes **verbatim** from the source Markdown — section titles, contact-row separators, date formats, punctuation, bullet leads, list structure. The reference PDF supplies **only** visual conventions: typography, sizes, margins, colors, spacing, alignment, two-column header rows. The only allowed transformation is mapping MD markup to HTML tags: + +- `# x` → `

x

` +- `## x` → `

x

` (exact text — do not rename) +- `### x` → article header (exact text — do not reword) +- `**x**` → `x` +- `*x*` / `_x_` → `x` +- `` `x` `` → `x` +- `[text](url)` → `text` +- `- item` → `
  • item
  • ` +- `---` (horizontal rule) → drop (section separators are visual via `

    ` underline) + +Forbidden rewrites (examples — do not do these): + +- `## Summary` → `Professional Summary` (rename) ❌ +- `## Experience` → `Work History` (rename) ❌ +- `·` separators in contacts → `|` (substitution) ❌ +- `Feb 2025 – Present` → `02/2025 — Current` (date reformatting) ❌ +- Skills bullet list collapsed into a dense paragraph (structure change) ❌ +- Job-description paragraph truncated to a short label (content loss) ❌ + +If the visual style of the reference PDF appears to conflict with what's in MD, the **MD wins**. Update MD if you want different content; never silently rewrite it during HTML generation. + +--- + +## Visual format rules (must match the reference PDF) + +The CSS in `templates/cv-style.css` already encodes these — when generating HTML, just use the classes and the format will follow. Reference for human review: + +- **Page**: A4, ~12mm margins, single column. +- **Font**: Times New Roman (serif), body 10.5pt, line-height 1.25. +- **Header**: centered name in 14pt bold caps, role in 11pt bold, single contact row with `|` separators. +- **Section headings**: ALL CAPS, bold, 10.5pt, with a thin bottom rule. +- **Job entry**: + - Header row: company (bold, left) — role (bold, right). + - Meta row: short company descriptor (italic, left) — dates (italic, right). + - Bulleted list with disc markers, hanging indent. +- **Links**: blue `#1155cc`, underlined. +- **Length**: target 2 pages. + +## HTML structure to use + +```html +
    +

    OLEG PROSKURIN

    +

    Role line

    +

    email | LinkedIn | Telegram | Github | ...

    +
    + +
    +

    +

    ...

    +
    + +
    +

    Skills

    + +

    Category

    +
      +
    • Item from MD bullet (text verbatim, including ":" and punctuation)
    • +
    +
    + +
    +

    +
    + +
    + Company + Role +
    + +
    + Location + Dates verbatim from MD (e.g. "Feb 2025 – Present") +
    + +

    Full descriptor paragraph from MD...

    +
      +
    • Bullet text verbatim from MD (keep ...: punctuation as-is)
    • +
    +
    +
    + +
    +

    +

    ...

    +
    + +
    +

    +

    ...

    +
    +``` + +--- + +## Conventions + +- **Language**: every artifact (HTML, MD, PDF, tracking rows, card notes) is in English. Always. +- **Filenames**: kebab-case. CVs: `cv-YYYY-MM-base.md` for base, `cv.md` inside `tailored//` for tailored. +- **Dates**: absolute (`2026-05-24`), never "last Thursday". +- **Don't edit the reference PDF.** It is the immutable visual anchor. +- **Don't auto-regenerate PDFs** unless Oleg asks — show diffs first when content changes. +- **Verify before claiming "done"**: after `pnpm pdf`, confirm the PDF exists and looks correct (open it, check page count). If you can't visually verify, say so. diff --git a/README.md b/README.md new file mode 100644 index 0000000..61f2b39 --- /dev/null +++ b/README.md @@ -0,0 +1,46 @@ +# CV-2026 + +Personal CV generation pipeline for Oleg Proskurin. + +## Pipeline + +``` +base/*.md -> output/html/*.html -> output/pdf/*.pdf + (Claude generates) (puppeteer renders) +``` + +## Folder layout + +| Path | Purpose | +| --- | --- | +| `base/` | Master CV in Markdown (source of truth for content) | +| `base/reference/` | Reference PDF that defines the visual style/format | +| `templates/` | Shared HTML/CSS — used by every generated CV | +| `tailored//` | Company-specific tailored versions (MD + HTML + PDF) | +| `tracking/` | Applications, outreaches, communications log | +| `scripts/` | Build tooling (PDF generator) | +| `output/html/` | Generated HTML (transient, gitignored) | +| `output/pdf/` | Generated PDFs (committed) | + +## Usage + +Install once: + +```bash +pnpm install +``` + +Generate PDF from an HTML file: + +```bash +pnpm pdf output/html/cv-2026-05-base.html +# -> output/pdf/cv-2026-05-base.pdf +``` + +The HTML file should be generated by Claude from the Markdown source — see `CLAUDE.md` for the full flow and styling rules. + +## Workflows + +1. **New base CV**: drop a new `cv-YYYY-MM-base.md` into `base/`, ask Claude to generate the HTML, then run `pnpm pdf`. +2. **Tailor for a company**: ask Claude to create `tailored//cv.md` from the latest base, generate HTML, then PDF. +3. **Track applications**: append to `tracking/applications.md` and `tracking/outreach.md`. diff --git a/base/cv-2026-05-base.md b/base/cv-2026-05-base.md new file mode 100644 index 0000000..f163f84 --- /dev/null +++ b/base/cv-2026-05-base.md @@ -0,0 +1,95 @@ +# OLEG PROSKURIN +**AI Engineer & Tech Lead** + +usulpro@gmail.com · Telegram @usulpro · [LinkedIn](https://www.linkedin.com/in/oleg-proskurin-76784453/) · [GitHub](https://github.com/UsulPro) · [Dev.to](https://dev.to/usulpro) · [Blog](https://focusreactive.com/blog/author/usulpro/) + +--- + +## Summary + +AI Engineer with 9+ years of production engineering across Frontend, Backend, Serverless, Node.js, and Headless CMS / eCommerce platforms, including 5+ years leading remote projects for US, UK, EU, and AU clients. Currently Tech Lead on PrimeUI ([primeui.com](https://primeui.com/)), an AI-native website generation platform launched Feb 2026: 3 independent AI flows (chat, page generation, content writing) orchestrating Claude / GPT / Gemini via Mastra, MCP server as integration bridge, RAG-based component selection, and a code-export pipeline that ships production-ready Next.js sites. + +--- + +## Skills + +**AI Engineering** +- LLM orchestration: multi-model routing, context engineering, prompt caching +- MCP: server & client development, multi-host integration +- RAG & retrieval: embeddings, vector retrieval, hybrid fallback +- Eval & observability: guardrails, schema validation, retry recovery +- AI-augmented development workflows + +**Data & Infrastructure** +- PostgreSQL, Drizzle ORM, Docker +- Vercel, Cloudflare, Serverless & Edge functions + +**Headless CMS & eCommerce** +- Sanity, Storyblok, Contentful, Payload, Shopify, Stripe +- Content modeling, multi-environment publishing, migrations + +**Web & Full-Stack** +- TypeScript, Node.js, React, Next.js (App Router, SSR/SSG/ISR) +- TanStack Start/Router, Tailwind, shadcn-style design systems + +--- + +## Experience + +### PixelPoint Ltd. — Tech Lead & AI Engineer, PrimeUI +**Feb 2025 – Present** · Remote +AI-native website generation platform ([primeui.com](https://primeui.com/), launched Feb 2026) building professional-grade, production-ready code. Drove ~70% of platform architecture across AI layer, sitemap canvas, server runtime, public API, MCP server, CLI, code-export pipeline, and component registry. + +- **Cut client-wireframe turnaround at PixelPoint** from a week of Figma prototyping to a few hours: deployed prototypes with real desktop/mobile responsive components, production-level web performance, and dynamic interactions instead of static mockups. +- **Designed, built, and published MCP server** (`@primeuicom/mcp` on NPM, official TypeScript SDK) as the integration layer letting any external codebase (no PrimeUI export required) connect to PrimeUI from inside AI coding environments. 25 tools exposing project management and AI-driven page creation. Tested and tuned across 6 major AI coding agents, including Claude Code, Cursor, and Codex. +- **Architected end-to-end AI generation layer**: multi-model orchestration via Mastra + Vercel AI SDK Gateway (10 agents, 17 workflows), with per-task model routing: Claude Opus 4.6 (Studio chat), GPT-5.2 (outline + section writer), Gemini 2.5 Flash with cached component library (block planning), Claude Haiku 4.5 (short-form tasks). +- **Built RAG-based component retrieval**: 1,555 markdown chunks across 204 components, indexed with OpenAI `text-embedding-3-small` (1536d, 256/32 overlap), cosine top-K with three-tier fallback (exact key → family bucket → semantic). +- **Built quality guardrails and observability for AI page generation**: deterministic rule-based UX scoring for candidate ranking, JSON + props-schema validation with retry recovery (5 retries; triggers: parse, schema, rate-limit), per-step token / latency / cache-hit logging. +- **Optimized cost-quality balance**: 73% Gemini context-cache hit rate via 24-hour TTL on the component library, ~150K input tokens per page variant across ~9 components and ~5 retrievals, 2–3 min end-to-end latency for the default 3-variant page generation. +- **Designed code-export pipeline and CLI**: generates a project repo in ~45 sec, ~1.5 min end-to-end from UI Export to a running local project. Output is a production-ready Next.js application with embedded AI tooling, customer-owned and pre-configured for 8 selectable AI coding agents. +- **Drove team-wide AI-augmented development**: introduced Claude Code from project start, authored team skills and CLAUDE.md conventions, led the team's transition to Codex-based workflow ahead of public release. Built [codex-bee](https://github.com/usulpro/codex-bee) (CLI wrapper for long-running Codex sessions) and [epic-loop](https://github.com/usulpro/epic-loop) (Codex skill framework with tech-lead and developer agent roles for autonomous large-task-list execution) as a result. + +### FocusReactive — Tech Lead & Senior Software Engineer +**May 2019 – Feb 2025** (5 yrs 10 mos) · London · Remote +International web consultancy for clients in the US, UK, Europe, and Australia. + +- **Architected Tipico US backend platform on Sanity Headless CMS**: 5 publishing environments, 3 datasets, white-label architecture for core + state teams. Operating scale: 122 CMS users, 75M requests/month, 98 document types, 165K+ stored documents. +- **Led migration of 15+ client projects to Headless CMS platforms** (Sanity, Storyblok, Contentful, Payload), restructuring content models and reducing update cycles from weeks to days. Used Claude.ai to analyze inherited codebases, identifying weak points and integration surfaces ahead of migration design. +- **Built a Claude-based content automation pipeline**: JSON translation flow for 190 country-specific Storyblok pages, integrated into the publishing process and saving ~1 month of manual content work. +- **Designed and built CMS-KIT**, an open-source Headless CMS starter and component library used across 15+ client projects, reducing kickoff from days to hours and accelerating development by ~30%. +- **Led technical hiring and team development**: designed an assessment system mirroring real working conditions, conducted 30+ interviews resulting in 8 hires, mentored new engineers; drove team adoption of Claude.ai for research, content, and codebase analysis from 2024 onwards. + +### GitNation — Senior Software Engineer (Part-time, concurrent) +**May 2019 – Feb 2025** · Remote +Built publishing flow via Slack API and a reusable web framework for conference websites; migrated GitNation conferences to Headless CMS. + +### Osome — Frontend Developer +**Jun 2018 – May 2019** · Singapore +Built fuzzy-logic search system and advanced UI animations for the corporate-services platform. + +### Skipp — JavaScript Engineer +**Nov 2017 – Jun 2018** · Moscow +B2B marketplace for Skolkovo Innovation Center (100+ companies integrated); GraphQL-subscription chat for car owners and lessees. + +### BL Group — Head of Engineering Team +**2010 – 2017** · Moscow +Led an engineering team designing automation and control systems for dynamic architectural lighting (50+ installations). + +--- + +## Open Source + +**Storybook** — contributor and steering team member during 2016–2017 community revival; addon maintainer (**storybook-addon-console** — 7M+ NPM downloads; **storybook-addon-material-ui** — 18K weekly). +**AI developer tooling** — [codex-bee](https://github.com/usulpro/codex-bee), [epic-loop](https://github.com/usulpro/epic-loop). + +--- + +## Education + +**Master in Engineering**, Faculty of Electronic Engineering — Moscow Power Engineering University (MPEI), Russia + +--- + +## Languages + +English C1 · Russian native diff --git a/base/reference/Oleg_Proskurin_Techlead_resume.docx.pdf b/base/reference/Oleg_Proskurin_Techlead_resume.docx.pdf new file mode 100644 index 0000000..c51d7f3 Binary files /dev/null and b/base/reference/Oleg_Proskurin_Techlead_resume.docx.pdf differ diff --git a/output/pdf/cv-2026-05-base.pdf b/output/pdf/cv-2026-05-base.pdf new file mode 100644 index 0000000..9845b2c Binary files /dev/null and b/output/pdf/cv-2026-05-base.pdf differ diff --git a/package.json b/package.json new file mode 100644 index 0000000..bdae0b8 --- /dev/null +++ b/package.json @@ -0,0 +1,13 @@ +{ + "name": "cv-2026", + "version": "1.0.0", + "description": "CV generation pipeline: Markdown -> HTML -> PDF, with tailoring for specific companies.", + "private": true, + "type": "module", + "scripts": { + "pdf": "node scripts/generate-pdf.mjs" + }, + "dependencies": { + "puppeteer": "^24.37.3" + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml new file mode 100644 index 0000000..233bc05 --- /dev/null +++ b/pnpm-lock.yaml @@ -0,0 +1,870 @@ +lockfileVersion: '9.0' + +settings: + autoInstallPeers: true + excludeLinksFromLockfile: false + +importers: + + .: + dependencies: + puppeteer: + specifier: ^24.37.3 + version: 24.43.1 + +packages: + + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + + '@puppeteer/browsers@2.13.2': + resolution: {integrity: sha512-5EUZSUIc37H6aIXyWO0Z4y8NlF8NnjgmqeQgOGiswAU7pY0HOo16ho4+alIWmSfdZnjqBRawMsP3I5YqLSn6kw==} + engines: {node: '>=18'} + hasBin: true + + '@tootallnate/quickjs-emscripten@0.23.0': + resolution: {integrity: sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==} + + '@types/node@25.9.1': + resolution: {integrity: sha512-xfrlY7UD5rMJk3ZVJP8BNzS28J36YJg+xp+LPXV1TdWxr8uMH5A860QNxYDGQe/ylDSgjxE52Q9VnO7p75tJxg==} + + '@types/yauzl@2.10.3': + resolution: {integrity: sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==} + + agent-base@7.1.4: + resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==} + engines: {node: '>= 14'} + + ansi-regex@5.0.1: + resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} + engines: {node: '>=8'} + + ansi-styles@4.3.0: + resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} + engines: {node: '>=8'} + + argparse@2.0.1: + resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} + + ast-types@0.13.4: + resolution: {integrity: sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==} + engines: {node: '>=4'} + + b4a@1.8.1: + resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + + bare-events@2.8.3: + resolution: {integrity: sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + bare-fs@4.7.1: + resolution: {integrity: sha512-WDRsyVN52eAx/lBamKD6uyw8H4228h/x0sGGGegOamM2cd7Pag88GfMQalobXI+HaEUxpCkbKQUDOQqt9wawRw==} + engines: {bare: '>=1.16.0'} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-os@3.9.1: + resolution: {integrity: sha512-6M5XjcnsygQNPMCMPXSK379xrJFiZ/AEMNBmFEmQW8d/789VQATvriyi5r0HYTL9TkQ26rn3kgdTG3aisbrXkQ==} + engines: {bare: '>=1.14.0'} + + bare-path@3.0.0: + resolution: {integrity: sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==} + + bare-stream@2.13.1: + resolution: {integrity: sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==} + peerDependencies: + bare-abort-controller: '*' + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + bare-buffer: + optional: true + bare-events: + optional: true + + bare-url@2.4.3: + resolution: {integrity: sha512-Kccpc7ACfXaxfeInfqKcZtW4pT5YBn1mesc4sCsun6sRwtbJ4h+sNOaksUpYEJUKfN65YWC6Bw2OJEFiKxq8nQ==} + + basic-ftp@5.3.1: + resolution: {integrity: sha512-bopVNp6ugyA150DDuZfPFdt1KZ5a94ZDiwX4hMgZDzF+GttD80lEy8kj98kbyhLXnPvhtIo93mdnLIjpCAeeOw==} + engines: {node: '>=10.0.0'} + + buffer-crc32@0.2.13: + resolution: {integrity: sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==} + + callsites@3.1.0: + resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} + engines: {node: '>=6'} + + chromium-bidi@14.0.0: + resolution: {integrity: sha512-9gYlLtS6tStdRWzrtXaTMnqcM4dudNegMXJxkR0I/CXObHalYeYcAMPrL19eroNZHtJ8DQmu1E+ZNOYu/IXMXw==} + peerDependencies: + devtools-protocol: '*' + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + color-convert@2.0.1: + resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} + engines: {node: '>=7.0.0'} + + color-name@1.1.4: + resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + + cosmiconfig@9.0.1: + resolution: {integrity: sha512-hr4ihw+DBqcvrsEDioRO31Z17x71pUYoNe/4h6Z0wB72p7MU7/9gH8Q3s12NFhHPfYBBOV3qyfUxmr/Yn3shnQ==} + engines: {node: '>=14'} + peerDependencies: + typescript: '>=4.9.5' + peerDependenciesMeta: + typescript: + optional: true + + data-uri-to-buffer@6.0.2: + resolution: {integrity: sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==} + engines: {node: '>= 14'} + + debug@4.4.3: + resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==} + engines: {node: '>=6.0'} + peerDependencies: + supports-color: '*' + peerDependenciesMeta: + supports-color: + optional: true + + degenerator@5.0.1: + resolution: {integrity: sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==} + engines: {node: '>= 14'} + + devtools-protocol@0.0.1608973: + resolution: {integrity: sha512-Tpm17fxYzt+J7VrGdc1k8YdRqS3YV7se/M6KeemEqvUbq/n7At1rWVuXMxQgpWkdwSdIEKYbU//Bve+Shm4YNQ==} + + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + + end-of-stream@1.4.5: + resolution: {integrity: sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==} + + env-paths@2.2.1: + resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} + engines: {node: '>=6'} + + error-ex@1.3.4: + resolution: {integrity: sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==} + + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + + escodegen@2.1.0: + resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} + engines: {node: '>=6.0'} + hasBin: true + + esprima@4.0.1: + resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} + engines: {node: '>=4'} + hasBin: true + + estraverse@5.3.0: + resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} + engines: {node: '>=4.0'} + + esutils@2.0.3: + resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} + engines: {node: '>=0.10.0'} + + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} + + extract-zip@2.0.1: + resolution: {integrity: sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==} + engines: {node: '>= 10.17.0'} + hasBin: true + + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + + fd-slicer@1.1.0: + resolution: {integrity: sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==} + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + + get-stream@5.2.0: + resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} + engines: {node: '>=8'} + + get-uri@6.0.5: + resolution: {integrity: sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==} + engines: {node: '>= 14'} + + http-proxy-agent@7.0.2: + resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} + engines: {node: '>= 14'} + + https-proxy-agent@7.0.6: + resolution: {integrity: sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==} + engines: {node: '>= 14'} + + import-fresh@3.3.1: + resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} + engines: {node: '>=6'} + + ip-address@10.2.0: + resolution: {integrity: sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==} + engines: {node: '>= 12'} + + is-arrayish@0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + + js-tokens@4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + + js-yaml@4.1.1: + resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} + hasBin: true + + json-parse-even-better-errors@2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + + lines-and-columns@1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + + lru-cache@7.18.3: + resolution: {integrity: sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==} + engines: {node: '>=12'} + + mitt@3.0.1: + resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==} + + ms@2.1.3: + resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} + + netmask@2.1.1: + resolution: {integrity: sha512-eonl3sLUha+S1GzTPxychyhnUzKyeQkZ7jLjKrBagJgPla13F+uQ71HgpFefyHgqrjEbCPkDArxYsjY8/+gLKA==} + engines: {node: '>= 0.4.0'} + + once@1.4.0: + resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} + + pac-proxy-agent@7.2.0: + resolution: {integrity: sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==} + engines: {node: '>= 14'} + + pac-resolver@7.0.1: + resolution: {integrity: sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==} + engines: {node: '>= 14'} + + parent-module@1.0.1: + resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} + engines: {node: '>=6'} + + parse-json@5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + + pend@1.2.0: + resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} + + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + proxy-agent@6.5.0: + resolution: {integrity: sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==} + engines: {node: '>= 14'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + + pump@3.0.4: + resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==} + + puppeteer-core@24.43.1: + resolution: {integrity: sha512-T5ScUMAsmhdNbgDR41AGESYeS6V9MSgetkSnVhhW+gXvzC42VesKCn5ld87gAZDJ6vLHL9GkRvY9WtQWSnwFbw==} + engines: {node: '>=18'} + + puppeteer@24.43.1: + resolution: {integrity: sha512-/FSOViCrqRdb1HDocpsM9Z1giA71gTQPUt3SpHGVRALKAy/rJr1fLFYZW9F23qPxqVxTHQnbh/5B5opJST3kAw==} + engines: {node: '>=18'} + hasBin: true + + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + + resolve-from@4.0.0: + resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} + engines: {node: '>=4'} + + semver@7.8.1: + resolution: {integrity: sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==} + engines: {node: '>=10'} + hasBin: true + + smart-buffer@4.2.0: + resolution: {integrity: sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==} + engines: {node: '>= 6.0.0', npm: '>= 3.0.0'} + + socks-proxy-agent@8.0.5: + resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==} + engines: {node: '>= 14'} + + socks@2.8.9: + resolution: {integrity: sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==} + engines: {node: '>= 10.0.0', npm: '>= 3.0.0'} + + source-map@0.6.1: + resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==} + engines: {node: '>=0.10.0'} + + streamx@2.25.0: + resolution: {integrity: sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==} + + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + + strip-ansi@6.0.1: + resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} + engines: {node: '>=8'} + + tar-fs@3.1.2: + resolution: {integrity: sha512-QGxxTxxyleAdyM3kpFs14ymbYmNFrfY+pHj7Z8FgtbZ7w2//VAgLMac7sT6nRpIHjppXO2AwwEOg0bPFVRcmXw==} + + tar-stream@3.2.0: + resolution: {integrity: sha512-ojzvCvVaNp6aOTFmG7jaRD0meowIAuPc3cMMhSgKiVWws1GyHbGd/xvnyuRKcKlMpt3qvxx6r0hreCNITP9hIg==} + + teex@1.0.1: + resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + + text-decoder@1.2.7: + resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} + + tslib@2.8.1: + resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} + + typed-query-selector@2.12.2: + resolution: {integrity: sha512-EOPFbyIub4ngnEdqi2yOcNeDLaX/0jcE1JoAXQDDMIthap7FoN795lc/SHfIq2d416VufXpM8z/lD+WRm2gfOQ==} + + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + + webdriver-bidi-protocol@0.4.1: + resolution: {integrity: sha512-ARrjNjtWRRs2w4Tk7nqrf2gBI0QXWuOmMCx2hU+1jUt6d00MjMxURrhxhGbrsoiZKJrhTSTzbIrc554iKI10qw==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + + wrappy@1.0.2: + resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + + ws@8.21.0: + resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + + yauzl@2.10.0: + resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} + + zod@3.25.76: + resolution: {integrity: sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==} + +snapshots: + + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/helper-validator-identifier@7.28.5': {} + + '@puppeteer/browsers@2.13.2': + dependencies: + debug: 4.4.3 + extract-zip: 2.0.1 + progress: 2.0.3 + proxy-agent: 6.5.0 + semver: 7.8.1 + tar-fs: 3.1.2 + yargs: 17.7.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + - supports-color + + '@tootallnate/quickjs-emscripten@0.23.0': {} + + '@types/node@25.9.1': + dependencies: + undici-types: 7.24.6 + optional: true + + '@types/yauzl@2.10.3': + dependencies: + '@types/node': 25.9.1 + optional: true + + agent-base@7.1.4: {} + + ansi-regex@5.0.1: {} + + ansi-styles@4.3.0: + dependencies: + color-convert: 2.0.1 + + argparse@2.0.1: {} + + ast-types@0.13.4: + dependencies: + tslib: 2.8.1 + + b4a@1.8.1: {} + + bare-events@2.8.3: {} + + bare-fs@4.7.1: + dependencies: + bare-events: 2.8.3 + bare-path: 3.0.0 + bare-stream: 2.13.1(bare-events@2.8.3) + bare-url: 2.4.3 + fast-fifo: 1.3.2 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + bare-os@3.9.1: {} + + bare-path@3.0.0: + dependencies: + bare-os: 3.9.1 + + bare-stream@2.13.1(bare-events@2.8.3): + dependencies: + streamx: 2.25.0 + teex: 1.0.1 + optionalDependencies: + bare-events: 2.8.3 + transitivePeerDependencies: + - react-native-b4a + + bare-url@2.4.3: + dependencies: + bare-path: 3.0.0 + + basic-ftp@5.3.1: {} + + buffer-crc32@0.2.13: {} + + callsites@3.1.0: {} + + chromium-bidi@14.0.0(devtools-protocol@0.0.1608973): + dependencies: + devtools-protocol: 0.0.1608973 + mitt: 3.0.1 + zod: 3.25.76 + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + color-convert@2.0.1: + dependencies: + color-name: 1.1.4 + + color-name@1.1.4: {} + + cosmiconfig@9.0.1: + dependencies: + env-paths: 2.2.1 + import-fresh: 3.3.1 + js-yaml: 4.1.1 + parse-json: 5.2.0 + + data-uri-to-buffer@6.0.2: {} + + debug@4.4.3: + dependencies: + ms: 2.1.3 + + degenerator@5.0.1: + dependencies: + ast-types: 0.13.4 + escodegen: 2.1.0 + esprima: 4.0.1 + + devtools-protocol@0.0.1608973: {} + + emoji-regex@8.0.0: {} + + end-of-stream@1.4.5: + dependencies: + once: 1.4.0 + + env-paths@2.2.1: {} + + error-ex@1.3.4: + dependencies: + is-arrayish: 0.2.1 + + escalade@3.2.0: {} + + escodegen@2.1.0: + dependencies: + esprima: 4.0.1 + estraverse: 5.3.0 + esutils: 2.0.3 + optionalDependencies: + source-map: 0.6.1 + + esprima@4.0.1: {} + + estraverse@5.3.0: {} + + esutils@2.0.3: {} + + events-universal@1.0.1: + dependencies: + bare-events: 2.8.3 + transitivePeerDependencies: + - bare-abort-controller + + extract-zip@2.0.1: + dependencies: + debug: 4.4.3 + get-stream: 5.2.0 + yauzl: 2.10.0 + optionalDependencies: + '@types/yauzl': 2.10.3 + transitivePeerDependencies: + - supports-color + + fast-fifo@1.3.2: {} + + fd-slicer@1.1.0: + dependencies: + pend: 1.2.0 + + get-caller-file@2.0.5: {} + + get-stream@5.2.0: + dependencies: + pump: 3.0.4 + + get-uri@6.0.5: + dependencies: + basic-ftp: 5.3.1 + data-uri-to-buffer: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + http-proxy-agent@7.0.2: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + https-proxy-agent@7.0.6: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + + import-fresh@3.3.1: + dependencies: + parent-module: 1.0.1 + resolve-from: 4.0.0 + + ip-address@10.2.0: {} + + is-arrayish@0.2.1: {} + + is-fullwidth-code-point@3.0.0: {} + + js-tokens@4.0.0: {} + + js-yaml@4.1.1: + dependencies: + argparse: 2.0.1 + + json-parse-even-better-errors@2.3.1: {} + + lines-and-columns@1.2.4: {} + + lru-cache@7.18.3: {} + + mitt@3.0.1: {} + + ms@2.1.3: {} + + netmask@2.1.1: {} + + once@1.4.0: + dependencies: + wrappy: 1.0.2 + + pac-proxy-agent@7.2.0: + dependencies: + '@tootallnate/quickjs-emscripten': 0.23.0 + agent-base: 7.1.4 + debug: 4.4.3 + get-uri: 6.0.5 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + pac-resolver: 7.0.1 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + pac-resolver@7.0.1: + dependencies: + degenerator: 5.0.1 + netmask: 2.1.1 + + parent-module@1.0.1: + dependencies: + callsites: 3.1.0 + + parse-json@5.2.0: + dependencies: + '@babel/code-frame': 7.29.0 + error-ex: 1.3.4 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + + pend@1.2.0: {} + + picocolors@1.1.1: {} + + progress@2.0.3: {} + + proxy-agent@6.5.0: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.6 + lru-cache: 7.18.3 + pac-proxy-agent: 7.2.0 + proxy-from-env: 1.1.0 + socks-proxy-agent: 8.0.5 + transitivePeerDependencies: + - supports-color + + proxy-from-env@1.1.0: {} + + pump@3.0.4: + dependencies: + end-of-stream: 1.4.5 + once: 1.4.0 + + puppeteer-core@24.43.1: + dependencies: + '@puppeteer/browsers': 2.13.2 + chromium-bidi: 14.0.0(devtools-protocol@0.0.1608973) + debug: 4.4.3 + devtools-protocol: 0.0.1608973 + typed-query-selector: 2.12.2 + webdriver-bidi-protocol: 0.4.1 + ws: 8.21.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - bufferutil + - react-native-b4a + - supports-color + - utf-8-validate + + puppeteer@24.43.1: + dependencies: + '@puppeteer/browsers': 2.13.2 + chromium-bidi: 14.0.0(devtools-protocol@0.0.1608973) + cosmiconfig: 9.0.1 + devtools-protocol: 0.0.1608973 + puppeteer-core: 24.43.1 + typed-query-selector: 2.12.2 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - bufferutil + - react-native-b4a + - supports-color + - typescript + - utf-8-validate + + require-directory@2.1.1: {} + + resolve-from@4.0.0: {} + + semver@7.8.1: {} + + smart-buffer@4.2.0: {} + + socks-proxy-agent@8.0.5: + dependencies: + agent-base: 7.1.4 + debug: 4.4.3 + socks: 2.8.9 + transitivePeerDependencies: + - supports-color + + socks@2.8.9: + dependencies: + ip-address: 10.2.0 + smart-buffer: 4.2.0 + + source-map@0.6.1: + optional: true + + streamx@2.25.0: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + + strip-ansi@6.0.1: + dependencies: + ansi-regex: 5.0.1 + + tar-fs@3.1.2: + dependencies: + pump: 3.0.4 + tar-stream: 3.2.0 + optionalDependencies: + bare-fs: 4.7.1 + bare-path: 3.0.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + tar-stream@3.2.0: + dependencies: + b4a: 1.8.1 + bare-fs: 4.7.1 + fast-fifo: 1.3.2 + streamx: 2.25.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - react-native-b4a + + teex@1.0.1: + dependencies: + streamx: 2.25.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + text-decoder@1.2.7: + dependencies: + b4a: 1.8.1 + transitivePeerDependencies: + - react-native-b4a + + tslib@2.8.1: {} + + typed-query-selector@2.12.2: {} + + undici-types@7.24.6: + optional: true + + webdriver-bidi-protocol@0.4.1: {} + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + + wrappy@1.0.2: {} + + ws@8.21.0: {} + + y18n@5.0.8: {} + + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + + yauzl@2.10.0: + dependencies: + buffer-crc32: 0.2.13 + fd-slicer: 1.1.0 + + zod@3.25.76: {} diff --git a/scripts/generate-pdf.mjs b/scripts/generate-pdf.mjs new file mode 100644 index 0000000..6ead23e --- /dev/null +++ b/scripts/generate-pdf.mjs @@ -0,0 +1,103 @@ +import puppeteer from 'puppeteer'; +import { resolve, basename, dirname, extname } from 'path'; +import { existsSync, mkdirSync } from 'fs'; +import { fileURLToPath } from 'url'; +import { createServer } from 'http'; +import { readFile } from 'fs/promises'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const PROJECT_ROOT = resolve(__dirname, '..'); +const OUTPUT_DIR = resolve(PROJECT_ROOT, 'output/pdf'); + +const MIME_TYPES = { + '.html': 'text/html', + '.css': 'text/css', + '.js': 'application/javascript', + '.mjs': 'application/javascript', + '.png': 'image/png', + '.jpg': 'image/jpeg', + '.jpeg': 'image/jpeg', + '.webp': 'image/webp', + '.svg': 'image/svg+xml', + '.json': 'application/json', + '.woff': 'font/woff', + '.woff2': 'font/woff2', + '.ttf': 'font/ttf', +}; + +async function generatePdf(htmlPath, outDirOverride) { + const absolutePath = resolve(htmlPath); + if (!existsSync(absolutePath)) { + console.error(`File not found: ${absolutePath}`); + process.exit(1); + } + + const outDir = outDirOverride ? resolve(outDirOverride) : OUTPUT_DIR; + mkdirSync(outDir, { recursive: true }); + + const pdfName = basename(absolutePath, '.html') + '.pdf'; + const pdfPath = resolve(outDir, pdfName); + + // Serve the project root so the HTML can load /templates/cv-style.css and any other assets. + const server = createServer(async (req, res) => { + const filePath = resolve( + PROJECT_ROOT, + decodeURIComponent(req.url).replace(/^\//, ''), + ); + try { + const data = await readFile(filePath); + const ext = extname(filePath); + res.writeHead(200, { + 'Content-Type': MIME_TYPES[ext] || 'application/octet-stream', + }); + res.end(data); + } catch { + res.writeHead(404); + res.end('Not found'); + } + }); + + await new Promise((r) => server.listen(0, '127.0.0.1', r)); + const port = server.address().port; + const relPath = absolutePath.replace(PROJECT_ROOT + '/', ''); + + const browser = await puppeteer.launch({ + headless: true, + args: ['--no-sandbox'], + }); + const page = await browser.newPage(); + + await page.goto(`http://127.0.0.1:${port}/${relPath}`, { + waitUntil: 'networkidle0', + timeout: 30000, + }); + + // Wait for web fonts (if any) to be ready before rasterizing. + await page.evaluate(async () => { + if (document.fonts && document.fonts.ready) { + await document.fonts.ready; + } + }); + + await page.pdf({ + path: pdfPath, + format: 'A4', + printBackground: true, + margin: { top: 0, right: 0, bottom: 0, left: 0 }, + preferCSSPageSize: true, + }); + + await browser.close(); + server.close(); + console.log(`PDF generated: ${pdfPath}`); + return pdfPath; +} + +const args = process.argv.slice(2); +if (args.length === 0) { + console.error('Usage: node generate-pdf.mjs [out-dir]'); + process.exit(1); +} + +const [htmlFile, outDir] = args; +generatePdf(htmlFile, outDir); diff --git a/tailored/_template/notes.md b/tailored/_template/notes.md new file mode 100644 index 0000000..8aea244 --- /dev/null +++ b/tailored/_template/notes.md @@ -0,0 +1,33 @@ +# + +Copy this folder to `tailored//` and fill it in. + +## Posting + +- URL: +- Date posted: +- Date applied: +- Source (LinkedIn / referral / direct): + +## Contacts + +- Recruiter: +- Hiring manager: +- Referral: + +## Why this role + +Bullet a few sentences — what makes this a good match, what hooks to lead with. + +## Tailoring decisions + +What was changed vs. base CV and why: + +- Summary: ... +- Skills order: ... +- Bullets reordered/edited: ... + +## Timeline + +- 2026-MM-DD — applied +- 2026-MM-DD — ... diff --git a/templates/cv-style.css b/templates/cv-style.css new file mode 100644 index 0000000..a1f8493 --- /dev/null +++ b/templates/cv-style.css @@ -0,0 +1,210 @@ +/* + * CV style — mirrors the visual format of base/reference/Oleg_Proskurin_Techlead_resume.docx.pdf. + * + * Key conventions: + * - Serif (Times-like) body font, ~10.5pt, dense single-line spacing. + * - A4 page, ~12mm margins. Single column. + * - Centered name + role at the top, single-line contact row. + * - Section headings: ALL CAPS bold with bottom underline. + * - Job header: company (bold left) | role (bold right), then italic subtitle | dates right. + * - Bullets: disc, hanging indent. + * - Links: blue, underlined. + */ + +@page { + size: A4; + margin: 12mm 14mm; +} + +:root { + --text: #000; + --link: #1155cc; + --rule: #000; + --muted: #333; +} + +* { + box-sizing: border-box; +} + +html, +body { + margin: 0; + padding: 0; +} + +body { + font-family: 'Times New Roman', Times, 'Liberation Serif', serif; + font-size: 10.5pt; + line-height: 1.25; + color: var(--text); + background: #fff; + -webkit-print-color-adjust: exact; + print-color-adjust: exact; +} + +a { + color: var(--link); + text-decoration: underline; +} + +/* ───── Header ───── */ + +.cv-header { + text-align: center; + margin-bottom: 8pt; +} + +.cv-header .name { + font-size: 14pt; + font-weight: 700; + letter-spacing: 0.5pt; + margin: 0; +} + +.cv-header .role { + font-size: 11pt; + font-weight: 700; + margin: 2pt 0 4pt 0; +} + +.cv-header .contacts { + font-size: 10pt; + margin: 0; +} + +.cv-header .contacts a { + white-space: nowrap; +} + +/* ───── Section ───── */ + +.cv-section { + margin-top: 8pt; +} + +.cv-section > h2 { + font-size: 10.5pt; + font-weight: 700; + text-transform: uppercase; + margin: 0 0 4pt 0; + padding-bottom: 1pt; + border-bottom: 0.5pt solid var(--rule); +} + +/* ───── Summary / plain paragraph ───── */ + +.cv-section p { + margin: 0 0 4pt 0; + text-align: justify; +} + +/* ───── Skills block ───── */ + +.cv-skills p { + margin: 0 0 3pt 0; +} + +.cv-skills .skills-category { + font-weight: 700; +} + +/* ───── Job entries ───── */ + +.cv-job { + margin-top: 6pt; +} + +/* Keep the job header block (company/role, meta row, descriptor) together + and glued to the first bullet, so the section title never dangles alone + at the bottom of a page. Individual bullets can still flow across pages. */ +.cv-job-header, +.cv-job-meta, +.cv-job-desc { + page-break-after: avoid; + break-after: avoid; + page-break-inside: avoid; + break-inside: avoid; +} + +.cv-job li { + page-break-inside: avoid; + break-inside: avoid; +} + +.cv-job-header { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 12pt; +} + +.cv-job-header .company { + font-weight: 700; +} + +.cv-job-header .role { + font-weight: 700; + text-align: right; +} + +.cv-job-meta { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 12pt; + font-style: italic; + color: var(--muted); + margin-top: 1pt; +} + +.cv-job-meta .dates { + font-style: italic; + text-align: right; + white-space: nowrap; +} + +.cv-job-desc { + font-style: italic; + margin: 2pt 0 0 0; +} + +.cv-job ul { + margin: 3pt 0 0 0; + padding-left: 16pt; +} + +.cv-job li { + margin: 0 0 2pt 0; + padding-left: 2pt; +} + +.cv-job li::marker { + font-size: 1em; +} + +/* ───── Portfolio / Education / Languages ───── */ + +.cv-inline-list { + margin: 0; +} + +.cv-portfolio a + a::before { + content: ', '; + color: var(--text); + text-decoration: none; +} + +/* ───── Misc ───── */ + +strong { + font-weight: 700; +} + +em { + font-style: italic; +} + +hr { + display: none; +} diff --git a/templates/cv-template.html b/templates/cv-template.html new file mode 100644 index 0000000..f3a1856 --- /dev/null +++ b/templates/cv-template.html @@ -0,0 +1,63 @@ + + + + + Oleg Proskurin — CV + + + +
    +

    OLEG PROSKURIN

    +

    Senior/Lead Full-stack Software Engineer

    +

    + usulpro@gmail.com | + LinkedIn | + Telegram: @usulpro | + Github +

    +
    + +
    +

    Professional Summary

    +

    One short paragraph. Keep it dense — role, years, focus areas, current scale-of-impact.

    +
    + +
    +

    Skills

    +

    Category A: comma-separated list of tools/platforms.

    +

    Category B: comma-separated list of tools/platforms.

    +

    Languages: English C1, Russian native

    +
    + +
    +

    Work History

    + +
    +
    + Company — City/Country + Role Title +
    +
    + Short company descriptor + MM/YYYY — Current +
    +
      +
    • Bullet point — start with a verb, quantify impact when possible.
    • +
    • Another bullet — concrete result, not responsibility.
    • +
    +
    +
    + +
    +

    Portfolio

    +

    + Project 1, Project 2, Project 3 +

    +
    + +
    +

    Education

    +

    Master in Engineering, Faculty of Electronic Engineering, Moscow Power Engineering University (MPEI) — Moscow, Russia

    +
    + + diff --git a/tracking/applications.md b/tracking/applications.md new file mode 100644 index 0000000..90a5ccc --- /dev/null +++ b/tracking/applications.md @@ -0,0 +1,9 @@ +# Applications + +One row per application. Append; never rewrite history. + +Statuses: `applied` → `screen` → `interview` → `offer` / `rejected` / `withdrawn` / `ghosted` + +| Date | Company | Role | Channel | CV used | Status | Last update | Notes | +| --- | --- | --- | --- | --- | --- | --- | --- | +| 2026-MM-DD | Example Co | Senior FE Engineer | LinkedIn / referral / direct | `tailored/example-co/cv.pdf` | applied | 2026-MM-DD | Link to JD, recruiter name | diff --git a/tracking/outreach.md b/tracking/outreach.md new file mode 100644 index 0000000..62d2519 --- /dev/null +++ b/tracking/outreach.md @@ -0,0 +1,7 @@ +# Outreach & communications + +Cold outreach, recruiter pings, follow-ups. One row per touchpoint. + +| Date | Company | Person | Channel | Direction | Topic | Next step | Notes | +| --- | --- | --- | --- | --- | --- | --- | --- | +| 2026-MM-DD | Example Co | Jane Doe (recruiter) | LinkedIn | inbound / outbound | initial chat / follow-up / scheduling | reply by YYYY-MM-DD | Short context |