init CV repo

This commit is contained in:
Oleg Proskurin 2026-05-24 14:13:14 +07:00
commit 432e6c0025
14 changed files with 1666 additions and 0 deletions

15
.gitignore vendored Normal file
View File

@ -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/

202
CLAUDE.md Normal file
View File

@ -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/
<company-slug>/
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 `<link rel="stylesheet" href="/templates/cv-style.css" />` (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/<company-slug>/cv.pdf`
Steps:
1. Create `tailored/<company-slug>/` folder. Use a short kebab-case slug (e.g. `vercel`, `stripe-platform`, `anthropic-applied`).
2. Copy the base MD into `tailored/<company-slug>/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/<company-slug>/notes.md`.
5. Generate `tailored/<company-slug>/cv.html` (same template/CSS).
6. Run the PDF generator with an explicit output directory:
```bash
pnpm pdf tailored/<company-slug>/cv.html tailored/<company-slug>
```
That writes `tailored/<company-slug>/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``<h1>x</h1>`
- `## x``<h2>x</h2>` (exact text — do not rename)
- `### x` → article header (exact text — do not reword)
- `**x**``<strong>x</strong>`
- `*x*` / `_x_``<em>x</em>`
- `` `x` `` → `<code>x</code>`
- `[text](url)``<a href="url">text</a>`
- `- item``<li>item</li>`
- `---` (horizontal rule) → drop (section separators are visual via `<h2>` 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
<header class="cv-header">
<h1 class="name">OLEG PROSKURIN</h1>
<p class="role">Role line</p>
<p class="contacts">email | LinkedIn | Telegram | Github | ...</p>
</header>
<section class="cv-section">
<h2><!-- exact ## heading from MD, e.g. "Summary" --></h2>
<p>...</p>
</section>
<section class="cv-section cv-skills">
<h2>Skills</h2>
<!-- For each `**Category**` block in MD followed by a `-` list,
render the category as <strong> then a <ul> of bullets verbatim. -->
<p><strong>Category</strong></p>
<ul>
<li>Item from MD bullet (text verbatim, including ":" and punctuation)</li>
</ul>
</section>
<section class="cv-section">
<h2><!-- e.g. "Experience" --></h2>
<article class="cv-job">
<!-- ### Company — Role → split on first " — " -->
<div class="cv-job-header">
<span class="company">Company</span>
<span class="role">Role</span>
</div>
<!-- "**Dates** · Location" line → location left, dates right -->
<div class="cv-job-meta">
<span>Location</span>
<span class="dates">Dates verbatim from MD (e.g. "Feb 2025 Present")</span>
</div>
<!-- Full description paragraph from MD (do not truncate). Italic. -->
<p class="cv-job-desc">Full descriptor paragraph from MD...</p>
<ul>
<li>Bullet text verbatim from MD (keep <strong>...</strong>: punctuation as-is)</li>
</ul>
</article>
</section>
<section class="cv-section">
<h2><!-- e.g. "Education" --></h2>
<p>...</p>
</section>
<section class="cv-section">
<h2><!-- e.g. "Languages" --></h2>
<p>...</p>
</section>
```
---
## 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/<slug>/` 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.

46
README.md Normal file
View File

@ -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>/` | 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/<company>/cv.md` from the latest base, generate HTML, then PDF.
3. **Track applications**: append to `tracking/applications.md` and `tracking/outreach.md`.

95
base/cv-2026-05-base.md Normal file
View File

@ -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, 23 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 20162017 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

Binary file not shown.

Binary file not shown.

13
package.json Normal file
View File

@ -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"
}
}

870
pnpm-lock.yaml Normal file
View File

@ -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: {}

103
scripts/generate-pdf.mjs Normal file
View File

@ -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 <html-file> [out-dir]');
process.exit(1);
}
const [htmlFile, outDir] = args;
generatePdf(htmlFile, outDir);

View File

@ -0,0 +1,33 @@
# <Company><Role>
Copy this folder to `tailored/<company-slug>/` 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 — ...

210
templates/cv-style.css Normal file
View File

@ -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;
}

View File

@ -0,0 +1,63 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Oleg Proskurin — CV</title>
<link rel="stylesheet" href="/templates/cv-style.css" />
</head>
<body>
<header class="cv-header">
<h1 class="name">OLEG PROSKURIN</h1>
<p class="role">Senior/Lead Full-stack Software Engineer</p>
<p class="contacts">
<a href="mailto:usulpro@gmail.com">usulpro@gmail.com</a> |
<a href="https://www.linkedin.com/in/oleg-proskurin-76784453/">LinkedIn</a> |
Telegram: <a href="https://t.me/usulpro">@usulpro</a> |
<a href="https://github.com/UsulPro">Github</a>
</p>
</header>
<section class="cv-section">
<h2>Professional Summary</h2>
<p>One short paragraph. Keep it dense — role, years, focus areas, current scale-of-impact.</p>
</section>
<section class="cv-section cv-skills">
<h2>Skills</h2>
<p><span class="skills-category">Category A</span>: comma-separated list of tools/platforms.</p>
<p><span class="skills-category">Category B</span>: comma-separated list of tools/platforms.</p>
<p><span class="skills-category">Languages</span>: English C1, Russian native</p>
</section>
<section class="cv-section">
<h2>Work History</h2>
<article class="cv-job">
<div class="cv-job-header">
<span class="company"><a href="https://example.com">Company</a> — City/Country</span>
<span class="role">Role Title</span>
</div>
<div class="cv-job-meta">
<span>Short company descriptor</span>
<span class="dates">MM/YYYY — Current</span>
</div>
<ul>
<li>Bullet point — start with a verb, quantify impact when possible.</li>
<li>Another bullet — concrete result, not responsibility.</li>
</ul>
</article>
</section>
<section class="cv-section">
<h2>Portfolio</h2>
<p class="cv-portfolio">
<a href="#">Project 1</a>, <a href="#">Project 2</a>, <a href="#">Project 3</a>
</p>
</section>
<section class="cv-section">
<h2>Education</h2>
<p><a href="#">Master in Engineering</a>, Faculty of Electronic Engineering, Moscow Power Engineering University (<a href="#">MPEI</a>) — Moscow, Russia</p>
</section>
</body>
</html>

9
tracking/applications.md Normal file
View File

@ -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 |

7
tracking/outreach.md Normal file
View File

@ -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 |