# Felipe Millán > Tallinn-based builder-marketer, originally from Santiago, Chile. Over a decade in SaaS growth, product-led growth and community-driven marketing: part of the teams at SendGrid, Twilio and Semrush through two IPOs and a $3B acquisition, then C-suite and co-founder roles at Cynoia, Shroomwell and Hive Identity. Now building AI and open-source tools. Available for select engagements, fully remote. This file is a plain-Markdown map of fmillan.com for LLMs and agents. Facts below match the website copy; if something is not listed here, do not assume it. - Contact: [hello@fmillan.com](mailto:hello@fmillan.com) - LinkedIn: [linkedin.com/in/felipemillan](https://www.linkedin.com/in/felipemillan) - Location: Tallinn, Estonia · remote-first - Focus: go-to-market, product-led growth, community-led growth, AI search visibility (GEO), building with AI-assisted development ## Experience - **Cynoia** — Chief Marketing & Sales Officer, 09/2024 – 03/2026. Led marketing and sales for a team workspace built for African distributed teams: native African languages, partnerships with telecoms and universities, cross-functional teams across Tunisia, Estonia, Senegal and Morocco. 6,000+ users across 13 countries, 10+ enterprise partners, 2 new markets. - **Shroomwell** — Chief Marketing Officer, 09/2023 – 04/2024. Built a net-new marketing division for mycotechnology wellness products. - **Hive Identity** — Co-founder & CMO, 05/2021 – 05/2023. KYC platform for startups. Raised a $500K seed round led by Isaac Saldana, founder of SendGrid. Built PLG from scratch until the war in Ukraine disrupted the team. - **Semrush** — Global Brand Evangelist & Influencer Marketing Manager, 10/2019 – 04/2021. Worked with a 20,000+ member marketing influencer network; part of the team through the ~$2B IPO. - **Twilio** — Senior Community Development Manager (EMEA), 02/2019 – 05/2019. Stayed on after the $3B acquisition of SendGrid to keep the startup program running. - **SendGrid** — Senior Community Development Manager (EMEA), 05/2016 – 02/2019. Nurtured and expanded the Accelerate startup program out of Europe into Africa, the Middle East and Latin America: 550+ startup accounts, 50+ accelerator partners, 14.2M social reach. Part of the team through the ~$1.5B IPO. - **Founder years (2000 – 2016)** — CTO & co-founder of PymeFacil (2010 – 2012), the first entrepreneurship agency in Chile: 93 startups helped launch, about 65% made it off the ground. Founder & CEO of Guubie.com (marketing automation for startups, 2011 – 2015), founder of Biletu.com (peer payments app, 2012 – 2015), SubmitMyStartup.com (2014), Lanuu.co (web builder, 2015 – 2016) and Orange Media Labs (web development, 2000 – 2016). - **Mentor & advisor** — Startup Wise Guys (2016 – 2023), Katapult Accelerator (2017 – 2023), Techstars (2018 – 2020), Founder Institute Chisinau (2017 – 2020), TheFactory Oslo (2018 – 2020), Storytek Creative Hub (2018 – 2020), Fincube Istanbul (2019 – 2020), Global Innovation Catalyst (2021 – 2023). Judge for Start-Up Chile (2018 – 2021); board advisor for the SXSW Pitch competition (2018 – 2019). - **Community** — Startup Grind Tallinn chapter director (2017 – 2019); Fuckup Nights Tallinn co-organizer (2017 – 2019). Investor in Mati Foods (2023 – present). ## Go-to-market, the way I see it I'm a builder-marketer. I run GTM — positioning, community, partnerships, product marketing — and I ship the product too. It's carried a lot of titles: product marketing, growth, developer relations, community and ecosystem, partnerships, market entry, founding GTM. I care more about the work than the label. - **Build an audience, don't rent one.** Community-driven growth is building an audience around your product instead of paying for attention. - **Metrics that connect to revenue.** I know the difference between metrics that matter and vanity. - **Product-led by default.** PLG is how I naturally think. - **Close the loop between field and product.** What users experience goes back to the product team. - **Different markets, same fundamentals.** The basics of building trust and communicating clearly work everywhere. How it evolved: founder GTM in Chile with tight budgets (2010 – 2015) → ecosystems and communities at scale at SendGrid and Semrush (2016 – 2021) → owning the whole funnel as a co-founder and C-suite at Hive Identity and Cynoia (2021 – 2026) → builder-marketer in the AI era (2026 →). People increasingly ask AI assistants for "best tools for X" instead of running a search; if a site isn't structured for AI crawlers, it isn't in the answer. That gap is why I built BoostLLMs. Eight products this past year: web apps, macOS apps, Chrome extensions, AI agent systems, services on Cloudflare's edge. On Perch and AstroEdge, most of the code was written by AI agents directed against written specs — the agents are fast; the discipline is what keeps the output coherent. ## Projects - [BoostLLMs](https://fmillan.com/#p-boostllms): AI readiness scores for any website — and the fixes. Live product · free scan. - [CheckLLMs](https://fmillan.com/#p-checkllms): Audit your domain against 23 AI crawlers, graded 0–100. Free tool · no login. - [AstroEdge](https://fmillan.com/#p-astroedge): Landing pages personalized per visitor at the edge. Pre-launch · MIT. - [Perch](https://fmillan.com/#p-perch): Your Chrome tabs, scored. Close the ones that don't matter. macOS · MIT · 65 tests. - [Coruro](https://fmillan.com/#p-coruro): Your local Git repos as a readable Kanban board. macOS · open source · MIT. - [Jooby](https://fmillan.com/#p-jooby): A native macOS app for your job search pipeline. macOS · open source · MIT. - [accesy](https://fmillan.com/#p-accesy): One-line accessibility preference widget. 12 controls, 6 profiles. Open source · MIT · v0.1.0. - [Artkive](https://fmillan.com/#p-rolodex) (formerly Rolodex): Save every artist. Find them again fast. Free Chrome extension · in Web Store review. - [Consentinel](https://github.com/felipemillan/Galletas4all) (formerly Galletas4All): Self-hosted cookie consent on Cloudflare Workers + D1. npm · MIT · 54 tests. ## Optional - [Full content for agents](https://fmillan.com/llms-full.txt): every project page in Markdown, plus experience and testimonials. - [Resume in Markdown](https://fmillan.com/index.md) --- # Projects in depth ## BoostLLMs **AI readiness scores for your website — and the fixes.** Status: Live product · free scan. Page: https://fmillan.com/#p-boostllms Scan any domain in 15 seconds. See exactly what's blocking AI crawlers from finding you — and get the fixes. BoostLLMs is an AI readiness tool that scans any website domain and returns a 0–100 score across six categories that decide whether AI crawlers can read, understand, and surface your content. The free scan runs in about 15 seconds with no account required. Tags: 19 AI crawlers checked · Score in ~15 seconds · Schema kept current automatically · llms.txt auto-generated - [Run a free scan](https://boostllms.com) - [Original page on fmillan.com](https://fmillan.com/#p-boostllms) ### The gap — Obsessing over SEO while blocking GPTBot. Somewhere around late 2024, I started noticing a pattern: companies spending real budget on SEO rankings while their robots.txt was quietly blocking GPTBot. The AI search channel was live and most sites were dark to it — not by design, just by neglect. People increasingly ask AI assistants for "best tools for X" instead of running a search. If your site isn't structured for AI crawlers, you're not in the answer. No backlink strategy fixes that. BoostLLMs starts with a free scan — paste a domain, get an AI-readiness score from 0–100 across six categories in about 15 seconds, no signup needed. The score isn't a vanity number: it's built from what blocks or helps AI crawlers. ### Features — What you get - **Quick Scan — free** — No signup, no card · Paste a domain, get a score in ~15 seconds · Six categories: crawler access, sitemap, indexability, metadata, structured data, content depth · Per-category breakdown, not a single black-box number · Free forever. One domain. - **Pro Scan — full site** — Every URL, per-page issues · Scans every URL in the project — 100, 400, 1,000+ pages · Per-page evidence, issue codes, and fix instructions · Score trends over time so you can see if it's improving · AI remediation prompt: one paste covers every issue found · $19/domain/month. 100-URL packs at $10 each. - **Schema markup, generated for you** — No forms, nothing to maintain · Reads your existing content and generates accurate markup — nothing to configure · Delivered automatically — no manual updates or deploys needed · Updated markup goes live on its own as your content changes · No forms to fill or settings to map - **llms.txt auto-generated** — The emerging standard, handled for you · Generates the Overview block, About section, and structured links · One click to regenerate when your content changes · Follows the llms.txt spec format · llms.txt isn't universally adopted yet — but early positioning in a new standard tends to matter. - **Set and forget** — Install once, stay current · Install once — a single line added to your site · Schema stays current without touching your site again · Nothing to redo when your content changes - **AI remediation prompt** — Ship every fix in one pass · After a Pro Scan, one ready-to-paste prompt · Covers every issue found across every page · Paste it into the AI tool you already use · Ship every fix in one pass ### How it works — Three steps. - **Scan** — Paste your domain. Get an AI-readiness score in ~15 seconds across six categories. No account needed. - **Audit** — Upgrade to Pro. Every URL on your site gets scanned, scored, and returned with per-page issue codes and fix instructions. - **Fix** — Use the AI remediation prompt to ship every fix in one pass. Add one line to your site once — schema stays current from there. ### Motivation — Why I built it Two things collided at the same time. One: I kept seeing companies spending real budget on SEO while their robots.txt was quietly blocking GPTBot. The AI search channel was live and most sites were dark to it — not by design, just by neglect. That felt like a gap worth filling. Two: I wanted to build a product where the hard part is invisible to the user but the output is meaningfully better because of it. The free scan takes 15 seconds. Getting that right — without asking anything of the user — was the interesting problem to solve. The product is live. The free scan is genuinely free, no card required. Built by Felipe Millán — a builder-marketer with over a decade in growth, SEO, and product, including time on the teams at SendGrid, Twilio, and Semrush. ### Honest limitations — Better up front than in the small print. - **Some crawlers see updates later than others** — Schema updates are picked up when a crawler renders your pages dynamically. Crawlers that only read static content won't see the latest markup right away. Most major AI crawlers (GPTBot, ClaudeBot, PerplexityBot) handle this — but not all, and not on every crawl. - **llms.txt is still an emerging standard** — Not all AI crawlers check for it yet. The spec exists, adoption is growing, but it isn't as established as robots.txt. I ship it anyway because early positioning in a new standard tends to matter. - **URL credits are consumed on scan + schema generation** — A site with 500 URLs that you rescan weekly will burn through credits fast. The pricing page is explicit about this before you hit a billing surprise. - **It's a live, improving product** — Scoring model, schema generation quality, and crawl depth are all actively being refined. There are rough edges I know about and am working through. ### Pricing — Scales with your site. Free plan covers one domain forever. Pro adds full-site scanning and everything that fixes issues. - **Free** — $0 · One domain, forever · Quick Scan — 0–100 score · Six-category breakdown · No signup required - **Pro** — $19/domain/mo · 100 URLs included · +$10/100 URLs · Full-site scan — every URL · Per-page issue codes + fix instructions · Score trends over time · AI remediation prompt · Schema markup — generated and kept current automatically · llms.txt auto-generated ### FAQ — Common questions ### Find out if you're visible to AI crawlers. Free. Paste a domain. Get a score in 15 seconds. No signup, no card. The free scan is genuinely free. ### FAQ **What exactly is an AI readiness score?** It's a 0–100 composite score across six categories — crawler access, sitemap, indexability, metadata quality, structured data, and content depth — based on what blocks or helps AI crawlers read your site. Not a vanity metric: a low structured data score means AI can't extract entities from your pages. A blocked GPTBot means you're invisible to ChatGPT browsing. **What AI crawlers do you check against?** We check 19 known AI crawlers including GPTBot (OpenAI), ClaudeBot (Anthropic), PerplexityBot, Google-Extended, Amazonbot, and others. The full list is on the site. **How does the schema update automatically?** Once installed, schema is generated from your existing content and kept current as your pages change. The major AI crawlers — GPTBot, ClaudeBot, PerplexityBot — see updated markup without you touching your site again. The one caveat we're upfront about: not every crawler picks up updates on every crawl. **What's llms.txt and why does it matter?** llms.txt is a proposed standard — analogous to robots.txt — that gives AI crawlers a structured overview of your site: what it is, who it's for, and links to key content. It's not universally supported yet, but early adoption tends to matter when standards emerge. We generate the Overview block, About section, and structured links the spec defines. **How are URL credits consumed?** Credits are consumed on scan and on schema generation. A Pro plan includes 100 URLs. Additional packs are $10/100 URLs. If you rescan a 500-URL site frequently, do the math before committing — the pricing page is explicit about this. **Is the free scan really free?** Yes. Paste a domain, get a score. No signup, no card, no catch. The free plan covers one domain with the Quick Scan permanently. Pro adds full-site scanning, per-page issue codes, schema generation, and llms.txt. ## CheckLLMs **Which AI crawlers can reach your site?** Status: Free tool · no login. Page: https://fmillan.com/#p-checkllms Audit your domain against 23 AI scrapers — including GPTBot, ClaudeBot, and Google-Extended — and grade your exposure from 0 to 100. Tags: Free Tool · No Login Required · 23 Crawlers Indexed · CF Workers - [Scan your domain](https://checkllms.com) - [Original page on fmillan.com](https://fmillan.com/#p-checkllms) - **23** — Known AI training, RAG, and search crawlers indexed - **0–100** — Weighted protection score (Grade A–F) - **100%** — Runs at the edge — no cookies, no tracking CheckLLMs is a free AI crawler audit tool that checks any domain against 23 known AI training and search bots. It grades your exposure on a 0–100 scale (Grade A–F) and outputs ready-to-paste robots.txt, ai.txt, and edge config rules. No login, no account, results in under a minute. ### What it does — One scan. Clear fixes. Most site owners configured their robots.txt before LLM bots became widespread. CheckLLMs resolves your domain and fetches your robots.txt and ai.txt files live, straight from your origin server. It reads every directive, maps it against 23 scrapers (GPTBot, ClaudeBot, Google-Extended, Bytespider, CCBot, and more), and grades how well your domain is protected. Then it outputs clean, copy-paste config scripts for Nginx, Apache, Cloudflare WAF, and Next.js edge middleware. ### Scoring framework — Weighting critical bots CheckLLMs grades your protection A–F using a weighted scoring model. Critical bots count 2× because they handle most of the LLM data ingestion happening today: A crawler counts as protected if it is blocked either at the path level in robots.txt or at the purpose level via the newer ai.txt standard. - GPTBot (OpenAI) · Weighted 2× - ClaudeBot (Anthropic) · Weighted 2× - Google-Extended (Google) · Weighted 2× - Applebot-Extended (Apple) · Weighted 2× - CCBot (Common Crawl) · Weighted 2× - Bytespider (ByteDance) · Weighted 2× - Meta-ExternalAgent (Meta) · Weighted 2× - PerplexityBot (Perplexity) · Weighted 2× ### Features — Audit, grade, and fix in one scan - **23-crawler directory** — Indexed database · - Training crawlers (GPTBot, ClaudeBot, CCBot) · - Inference and RAG crawlers (ChatGPT-User, Perplexity-User) · - Search indexers (OAI-SearchBot, PerplexityBot) - **Fix generator** — Edge blocks and configs · - Generates standard robots.txt files · - Outputs ai.txt flags (No-Training, No-Inference) · - Provides edge rules for Cloudflare WAF, Nginx, and Next.js · - Outputs single-prompt configs you can hand to an AI agent - **Edge native** — Fast, private execution · - Scans run from the Cloudflare Workers edge network · - DNS and domain ranking data via the Cloudflare Radar API · - Results cached per domain using Workers KV · - No user data saved to disk, no cookies ### Tech stack — How it's built - **Edge Infrastructure** — Cloudflare Workers, Workers KV Caching, Cloudflare Radar API - **Application** — Next.js 15 (App Router), TypeScript Strict Mode, Tailwind CSS - **Security & Verification** — SHA-256 IP Hashing, Default-deny WAF generation, DNS record verification - **Build Process** — Built with Claude Code, Built in the open, Deployed globally ### The builder — About CheckLLMs CheckLLMs is built by Felipe Millán, who spent the past decade working at developer platforms (SendGrid, Twilio) and on SaaS go-to-market systems. He built it to give site owners clear visibility into which AI scrapers reach their content — and how to stop them. The scanner runs on the same crawler database that powers BoostLLMs. One database, two tools: audit your exposure here, then work on your AI visibility over at BoostLLMs. ### FAQ — Common questions ### FAQ **Is this another AI website builder?** No. CheckLLMs is a security and crawling audit tool. It resolves your site's domain configuration, fetches robots.txt and ai.txt live, and checks whether AI scrapers are allowed to take your content for training or search indexing. **What does "at the edge" mean for CheckLLMs?** The audit runs on Cloudflare's edge functions (Cloudflare Workers). Requests go out from the same kind of network infrastructure AI bots use, so the result reflects what those bots would actually see — and it comes back fast. **Do I need to know how to code?** Not to audit your site. Just submit your URL. If gaps are found, CheckLLMs outputs ready-made code snippets for robots.txt and server configs (Nginx, Cloudflare WAF, and more) that you or your developer can paste in seconds. **Is my visitors' data safe?** Yes. CheckLLMs collects no personal data. Scan outputs are cached by domain in Cloudflare Workers KV, and visitor IPs are only hashed using SHA-256 for rate limiting purposes. Hashed IPs are purged automatically. **What does it cost to run?** CheckLLMs is free and requires no account. It runs on Cloudflare Workers, which falls within Cloudflare's free serverless tier. There are no paid plans. Stack: Cloudflare Workers · Workers KV Caching · Cloudflare Radar API · Next.js 15 (App Router) · TypeScript Strict Mode · Tailwind CSS · SHA-256 IP Hashing · Default-deny WAF generation · DNS record verification · Built with Claude Code · Built in the open · Deployed globally ## AstroEdge **Landing pages that know who's visiting — personalized at the edge.** Status: Pre-launch · MIT. Page: https://fmillan.com/#p-astroedge What is AstroEdge? AstroEdge is a landing page engine that runs on Cloudflare's edge network. You describe a page in plain language, and it composes one from a locked set of sections and design tokens. Visitor personalization — name, A/B variant — happens in the response stream before the browser receives the HTML. No flicker, no client script, no server to manage. Describe a page or pick a template. AstroEdge composes it from seven locked sections and a single design token file, then personalizes it per visitor in the edge response — ?firstName=Mike becomes "Hi, Mike" before the HTML reaches the browser. No flicker. No drift. No server to manage. Honest note: AstroEdge is pre-launch. The engine works end to end and there's a live page you can poke at right now. What you won't find here is a wall of customer logos — there aren't any yet. This is a real product, built in the open, still early. Tags: Pre-launch · Built in the open · Cloudflare Pages · Cloudflare D1 · MIT license - [See it working](https://github.com/felipemillan/astroedge) - [Original page on fmillan.com](https://fmillan.com/#p-astroedge) ### The problem — AI is good at writing a landing page once. It's bad at writing the same page twice. Ask a model for a hero section three times and you get three different layouts, three color schemes, and a button that moved. That's fine for a mockup and a problem for a brand. Personalization makes it worse. The usual fix — swap text with client-side JavaScript — means the visitor sees the generic version first, then a flicker as it rewrites. Search engines and link previews see the generic version and cache it. AstroEdge takes a different path on both: the design kit is locked so the output stays consistent, and personalization runs in the edge response before the HTML reaches the browser. ### How it works — Four steps. Describe, compose, configure, ship. - **Onboard your design** — Import a design you already have, or pick a starting palette from the gallery. Either way it lands as a locked set of tokens — colors, type, spacing, radii. - **Describe the page** — "A waitlist page for a coffee subscription, urgent tone, one testimonial." AstroEdge doesn't invent a layout — it composes one from seven locked sections. - **Drop in edge widgets** — Lead capture with spam protection and consent built in. A feedback board. An A/B test that runs at the edge with zero client JavaScript. - **Ship to the edge** — One command builds, lint-gates, and deploys. Personalization happens in the response stream itself — the visitor's first paint is already personalized. ### What's inside — Personalization at the edge. Composition over creation. - **Personalization at the edge — not in the browser** — `?firstName=Mike` becomes "Hi, Mike" before the HTML reaches the browser, rewritten in the response stream by Cloudflare's HTMLRewriter. First paint is correct. No flash of generic content, no client script, nothing for a scraper to cache wrong. Every token is allow-listed, length-capped, and encoded — a name field can't smuggle in a script. - **Compose, don't create** — This is the core idea. Pages are assembled from seven locked sections — Header, Hero, Features, CTA, Testimonials, FAQ, Footer — each at a small set of approved variants, all sitting on one token-backed design system. Generation picks sections and variants. It never writes a novel layout, never invents a color, never reaches for an arbitrary pixel value. A lint gate fails the build if it tries. That's how you get AI speed without AI drift. - **A shelf of edge widgets — integrated, not bolted on** — Lead capture, a social-proof counter, a feedback board, edge A/B testing. Each one is consent-gated by default: no consent, no write, no exception. The A/B test buckets visitors deterministically and swaps content in the same stream as personalization — so there's no flicker and the analytics attribute correctly from the very first view. - **Consent the edge can enforce** — Most consent banners live in the browser and the server never hears about them. AstroEdge mirrors the visitor's choice into a first-party cookie the edge reads on every write. Default-deny: if the edge isn't sure, nothing gets stored. Lead capture, feedback, analytics linkage — all gated on a signal the server can verify. - **Per-link analytics, no third-party pixel** — Views and conversions log to your own database at the edge. Visitor IDs are anonymous and hashed; referrers are stored host-only; there's no personal data in the analytics path. You can see how a variant performs without renting a tracking script from anyone. - **Social assets, generated at the edge** — Open-graph images and carousel slides rendered on-brand from a page's own content, using the same design tokens. Pure JavaScript and WASM — no headless browser, no paid render service. (Honest caveat, stated plainly in the docs: personalized link previews are unreliable because platforms cache the canonical image and strip query params. The solid feature is downloadable, on-brand assets — not personalized unfurls.) - **One engine, many surfaces** — The whole product is a headless engine that runs on its own. A desktop cockpit, a template gallery, and a Chrome design-extractor all drive the same engine — none of them reimplements it. Build from the command line, from the desktop app, or by extracting the bones of a page you like. Same engine underneath. - **Runs on the free tier** — The default architecture fits inside Cloudflare's free limits, and every stage was checked against that budget. Static pages serve effectively unlimited; the edge functions are metered and the build was designed to stay well under the daily ceiling. Cheap to run is a feature, not an accident. ### Deep dive — Compose-don't-create Most "AI website builder" tools let the model write whatever it wants and hope it looks good. It looks good once. Then you ask for a second page and the system has quietly drifted — new spacing, a slightly different blue, a font weight nobody chose. AstroEdge fixes that by making generation a composition problem instead of a creation problem: The result: pages that ship in minutes and still look like they came from the same company. - Tokens are the source of truth — One file defines the design. Change it once, every page updates. There is no second place a color can come from. - Sections are bounded — Seven of them, each with a handful of approved variants. The model picks from a menu; it doesn't paint on a blank canvas. - A lint gate is the hard stop — Arbitrary values, inline styles, raw hex codes, stray CSS — the build fails on all of them. Not a warning. A failed build. ### Designed for — Three kinds of people this was built for If you've ever shipped an AI-built page and then spent an hour fixing the parts that drifted, this is the part that's different. - **Marketers** — who want personalized, on-brand landers without a ticket to engineering for every variant. - **Solo founders** — who need fast pages that don't look AI-generated and don't cost anything to run. - **Agencies** — that want a locked system so every page across every client stays on-brand. ### How I built it — About the build AstroEdge was built solo, in the open, by one marketer who codes — not a team, not a funded startup. The interesting part is how: most of the code was written by Claude Code agents, but every decision — the architecture, the security model, the design lock, what to cut — was a human one. It ran as 44 stages, one commit each, with a named cast of agents at different capability tiers, phase gates that didn't open until the work behind them was verified, and a review step before every high-stakes change. Less "let the AI vibe a website" and more "manage AI like a team that needs a manager." If that approach is interesting to you — as a user, a collaborator, or otherwise — the build log is public and the door's open. ### FAQ — Common questions ### FAQ **Is this another AI website builder?** Sort of, but backwards. Most of them let AI design freely and you clean up the drift. AstroEdge locks the design first and lets AI only compose within it. Speed of AI, consistency of a design system. **What does "at the edge" actually mean?** Your page is served from Cloudflare's network, close to the visitor, and the personalization happens in that same response — not in a round-trip to a server, not in the browser after load. It's fast because there's no second step. **Do I need to know how to code?** To use the engine through the plugin: you describe pages in plain language and pick templates. To self-host and ship: you'll run a couple of commands. It's not no-code, but it's close to describe-and-ship. **Is my visitors' data safe?** Writes are consent-gated by default-deny, SQL is parameterized, personalization input is sanitized and encoded, visitor IDs are anonymous and hashed, and referrers are stored host-only. There's no third-party tracking pixel in the default build. **What does it cost to run?** The default setup is built to fit Cloudflare's free tier. No server, no fixed monthly infra bill for normal volume. **Can I see real code?** Yes. The whole thing was built in the open, one commit per stage, with the build log published. The "read how it was built" link goes straight to the GitHub repo. **Is it production-ready?** The engine is deployed and the security, consent, and personalization paths are all tested end to end. It's pre-launch, which means there are no paying customers yet and the feature set is still growing — honest about that. If you want to be one of the first real users, the GitHub repo is the place to start. ## Perch **Your Chrome tabs, scored.** Status: macOS · MIT · 65 tests. Page: https://fmillan.com/#p-perch Close the ones that don't matter. Perch sits in your menu bar, tracks real tab usage, and surfaces the ones worth closing — with a reason for each. One click closes them. Tags: macOS only · Tauri v2 + Rust · 65 tests · Local-first · MIT license - [View on GitHub](https://github.com/felipemillan/perch) - [Original page on fmillan.com](https://fmillan.com/#p-perch) Perch is a macOS menu-bar app built with Tauri v2 and Rust. It monitors your Chrome tabs through a local WebSocket extension, scores each tab against four staleness rules — Zombie, Duplicate, Stale, and Domain pileup — and shows a ranked list of tabs worth closing. Everything stays on your machine, with no account required. ### What it is — 20% of a tab manager. The part you'd actually use. I keep ~180 Chrome tabs open. I'm not proud of it. Most tab managers either suspend memory or bury everything in a sidebar I never open. I wanted something smaller: a number in the menu bar, and an honest answer to "which of these can I close?" Perch watches your tabs and tracks how much time you spend on each one — but only while Chrome is focused and you're not idle, so background tabs don't pad the numbers. Then it flags the ones that look closeable. Each suggestion shows why it's flagged. Tabs you genuinely use a lot get demoted, not nagged. You close from the panel; the tab disappears in Chrome. At the top, a single Health number sums all of that into one glanceable score — click it to see exactly how it's worked out. - Stale — you haven't touched it in a day. - Zombie — opened hours ago, never once looked at. - Duplicate — the same page open in three tabs. - Domain pileup — eleven GitHub tabs, again. ### What's in it — Six views. One panel. - **Worth-closing list** — Every flagged tab with its reasons, ranked by score. The "Nuke all" button closes the lot in one shot. Each tab shows why it was flagged — Duplicate, Stale, Zombie, or Domain pileup — so nothing is a surprise. - **All tabs — grouped, sortable, searchable** — Every open tab across every window, grouped by domain. Sort by last active, active time, or domain. A "Close all N" button beside each domain closes the whole cluster at once. Search filters by title or URL. - **Windows view** — Tabs organized by Chrome window, collapsible. Windows with a high proportion of flagged tabs get a "Recommended to close" badge. Focus a window or close it entirely — one click. - **Playing — only what makes sound** — A dedicated view that lists only tabs currently playing audio. The moment you hear an unexpected sound, one look tells you exactly which tab it is. - **History + restore** — Recently-closed tabs listed with domain and time. One click restores any of them, Chrome-style. Accidentally nuked something? It's here. - **Stats — where your time goes** — Lifetime tabs closed ("reclaimed") and a bar chart of active time by domain — only time while Chrome was focused and you weren't idle. The honest picture of what you use. ### Why I built it — Two reasons, honestly. - **One — I wanted the tool.** — A glanceable tab count and a short "worth closing" list is the 20% of a tab manager I'd use every day. - **Two — I wanted to find out how far AI agents have come.** — Not a to-do demo, but something with a Rust backend, a browser extension, a local database, and a wire protocol between them. So I treated it like a real build — with a PRD, frozen contracts, and parallel agents — and wrote about what worked and what didn't. ### How it works — Two processes, one loopback. - **The extension is just a sensor** — It streams tab and idle events over a WebSocket bound to localhost. Minimal permissions — tabs, idle, alarms. No host access, no reading page content. - **The app is the brain** — It stores everything locally in SQLite, works out active time, scores the tabs, and draws the panel under the tray icon. It rejects any WebSocket connection that isn't the extension. - **Nothing leaves the machine** — No server, no accounts, no analytics, no network egress. The WebSocket is loopback-only. The SQLite database lives on your disk. ``` Chrome ──tabs/windows/idle──▶ MV3 extension (the sensor) │ loopback WebSocket (127.0.0.1) ▼ Tauri app (the brain + UI) ├─ local SQLite ├─ time attributor ├─ close-suggestion heuristics └─ AppleScript fallback (when extension drops) │ ▼ menu-bar panel ``` ### Health score — One number. Transparent maths. Health answers a single question: what share of your open tabs are worth closing? It's three steps, all visible. ### The formula 100 means nothing is flagged. Lower means a bigger slice of what's open is clutter. No tabs open at all → Health 100. Clamped to 0–100. - Example — 180 tabs open, 36 flagged worth closing → 100 − round(36 / 180 × 100) = 100 − 20 = Health 80 ``` Health = 100 − round( worth_closing / open_tabs × 100 ) ``` ### Step 1 — Score each tab against four rules Every open, non-pinned tab is checked against four rules. Rules stack — a tab can trip several, and the weights add up. Pinned tabs are skipped entirely. - Zombie — Opened over 2h ago and never once focused (+1.5) - Duplicate — The same page (normalized URL) is already open in another tab (+1.2) - Stale — No focus for over 24h (+1.0) - Domain pileup — More than 8 tabs on one domain (counts the overflow) (+1.0) ### Step 2 — Demote the tabs you actually use A raw rule score isn't the final word. Perch keeps lifetime active-time per URL locally, and uses it to demote pages you genuinely live in — so your daily drivers don't get nagged just for sitting open. The more you've actually used a URL over its lifetime, the lower the multiplier — down to a floor of 0.4×. A page you've never touched keeps its full score (1.0×). ``` multiplier = 1 − 0.6 × ( engagement / (1 + engagement) ) final_score = raw_score × multiplier ``` ### Step 3 — Count what crosses the line A tab is worth closing when its final score clears the threshold (default > 1.0). Deliberately tuned so a single soft signal on a page you use a lot won't trip it — but a never-opened zombie (1.5), or a couple of stacked reasons, will. - **It's yours to tune** — The thresholds — stale hours, zombie hours, domain max — all live in Settings. Tighten or loosen them and Health re-scores live. ### The stack — What it's built with - **App** — Tauri v2, Rust, macOS only, tray icon, no dock - **Extension** — Chrome MV3, tabs, idle, alarms, no host access - **Transport** — loopback WebSocket, 127.0.0.1 only, origin-checked - **Storage** — SQLite (rusqlite), local-only, parameterized queries - **UI** — Vanilla TypeScript, Vite, light/dark system-matched - **Quality** — 65 unit tests, security audit, MIT licensed ### How I built Perch with AI agents — I didn't write most of this code. I directed it. Here's the short version: I built Perch by directing AI agents against frozen contracts, and the discipline mattered more than the speed. Letting an agent "just build the app" gives you mush. What worked was treating it like running a small engineering team — a spec first, then parallel agents, then real use until it broke. My job wasn't typing. It was scoping the problem, deciding the open questions, keeping the contracts honest, and using the thing until it broke. The agents are fast; the discipline is what keeps the output coherent. - **01 — Spec before code** — I started with a PRD — goals, non-goals, the data model, the heuristics, acceptance criteria. No code until that was real. - **02 — A Phase 0 gate** — Before anything got built, one pass verified every shaky assumption (Tauri APIs, MV3 service-worker lifecycle, the SQLite approach) against current docs — not from memory — and froze the interface contracts: the WebSocket protocol, the database schema, and the app's command layer. - **03 — Frozen contracts made real parallelism possible** — With the interfaces nailed down, I fanned out several agents at once, each owning a separate set of files. They couldn't collide because they all coded against the same frozen source of truth — and I matched the model to each task's complexity. - **04 — Integration, then real use** — I wired it together, ran it on my Mac with real Chrome, and found bugs the only way you ever really do — by clicking around. The Close button threw an error; the backend and frontend disagreed on one argument's shape. Found it, fixed it, in minutes. - **05 — An audit before publishing** — I ran an AI-assisted security and OSS-readiness audit over the whole repo — secrets, dependencies, license, the works — and fixed what it flagged before making it public. ### Frequently asked questions about building Perch ### Honest limitations — Worth knowing before you run it. - **macOS + Chrome only** — That's the scope, on purpose. - **Not notarized or in the App Store** — It's a v1 and a portfolio project — it runs and it's tested. - **No per-tab memory** — Chrome doesn't expose per-tab memory to installed extensions, so I left it out rather than fake it. - **"Works on my machine" is verified** — Yours may differ. It's open source so you can read every line and run it yourself. ### FAQ **How did you scope what to give each agent?** I matched the model to the task's complexity. Boilerplate (struct definitions, test scaffolding) went to a faster model. Anything touching the heuristics or the scoring logic, where subtlety mattered, got a slower, more capable one. And I wrote the briefs with explicit file-scope boundaries so agents couldn't step on each other's work. **What's the PRD actually look like?** It's in the repo — have a look. It's a plain markdown document covering the problem, the non-goals (just as important), the data model, the scoring rules with rationale, the WebSocket schema, and the acceptance criteria for each feature. The frozen contracts live alongside it: the full message type union, the DB schema, and the Tauri command signatures. That document was the single source of truth every agent brief referred back to. **Did you hit any situations the agents couldn't handle?** A few. When click-to-focus silently did nothing, the agents couldn't diagnose it — because it turned out to be a stale extension in Chrome, not a code problem. That kind of environment-level debugging required me to be at the machine and watch what happened. The other category was anything requiring genuine product judgment: deciding the zombie threshold, tuning the engagement demoting curve so it felt right rather than just correct. Those stayed with me. Stack: Tauri v2 · Rust · macOS only, tray icon, no dock · Chrome MV3 · tabs · idle · alarms · no host access · loopback WebSocket · 127.0.0.1 only · origin-checked · SQLite (rusqlite) · local-only · parameterized queries · Vanilla TypeScript · Vite · light/dark system-matched · 65 unit tests · security audit · MIT licensed ## Coruro **Coruro — your Git repos as a readable board.** Status: macOS · open source · MIT. Page: https://fmillan.com/#p-coruro Scans a local folder, reads every repo, and shows each as a card — on-device AI, no upload, no account. Coruro is an open-source macOS app that scans a local folder of Git repositories and shows each one as a card on a five-column Kanban board. On-device AI summaries, via Apple FoundationModels, describe each repo without uploading your code or needing an API key. Each card shows what the project is, what it's built in, whether it's in sync, and how alive it is — without opening anything. The AI summaries run locally, so your code never leaves the machine. Clone the repo and run your own build — no signed installer, no store, no account. Tags: No account · No cloud — AI runs on your Mac · No API keys · MIT · Open source - [Clone the repo](https://github.com/felipemillan/coruro) - [Original page on fmillan.com](https://fmillan.com/#p-coruro) ### The problem — A folder of repos is a graveyard. Local clones, half-finished side projects, client work, things I starred and forgot. Names like app-final-2 tell me nothing, and opening each one to remember what it was is its own afternoon. Coruro scans a root folder, finds every Git repo, and draws each as a card on a five-column board — Inbox · Backlog · Active · Review · Done. Drag a repo to where it lives. The card carries the rest. Each card reads like a project's vital signs: what it is, what it's built in, whether it's in sync, and how alive it is — at a glance, without opening anything. ### The card — Vital signs, at a glance. Each card adapts to what the repo is — GitHub repos get different stats than local-only ones. - **What it is** — A one-line AI summary and a few topic tags, generated on-device by Apple FoundationModels. No network call, no API key. - **What it's built in** — A language-tinted header so the stack registers before you read a word. Rust gets one color, TypeScript another. - **Whether it's in sync** — Current branch, dirty / ahead / behind, and CI status — all read-only. Coruro never touches your working tree. - **How alive it is** — GitHub repos show stars, issues, and forks. Local-only repos show commits, branches, and last-commit age. The grid decides which. ### What's inside — Six features, one board. - **Editorial repo cards** — Information density, not decoration · White card, soft shadow, language-tint header · GitHub repos: stars, issues, forks · Local-only repos: commits, branches, last-commit age · Card decides which stat grid to show - **On-device AI summaries** — Apple FoundationModels, no network · One-line description and topic tags per repo · Runs automatically after a scan · Content-hash cached — runs once per change · Works without summaries on unsupported Macs — cards still show all Git data · Requires Apple Silicon + macOS 26 + Apple Intelligence. Without it, the app still works fully. - **Local Git status** — Read-only, always current · Current branch, clean or dirty · Ahead / behind upstream · Never touches your working tree · No git commands that modify state - **GitHub enrichment** — Stars, issues, CI — cached locally · Stars, forks, open issues, PRs · CI status and latest release · Topics and license · Token lives in macOS Keychain, never on disk · Optional. Without a token you still get local Git status and AI summaries. - **Kanban workflow** — Inbox · Backlog · Active · Review · Done · Drag repos between columns · Per-repo notes timeline · State stored in one JSON file alongside the repo · Quick-open in editor, terminal, Finder, or GitHub - **Quick actions** — One click to context-switch · Open in your editor · Open in terminal · Reveal in Finder · Open on GitHub ### Architecture — How it works The Rust backend reads local Git state and GitHub data. For the AI, it builds a small context per repo — a README excerpt, the languages, recent commit subjects, the top-level file names — capped to fit the model's window, and hands it to a standalone Swift binary. That binary runs Apple's on-device model and returns structured output. A serial, content-hash-cached queue runs it over each repo after a scan. Nothing about your code is sent to a server. ``` Folder of repos │ scan ▼ Tauri app (Rust) ──► local Git (read-only): branch, ahead/behind, stats │ └► GitHub API: stars, issues, CI, release (cached) │ │ repo context (README excerpt, languages, │ recent commit subjects, top-level files) ▼ Swift sidecar ──► Apple FoundationModels (on-device LLM) │ returns { summary, tags } ▼ Editorial card on the board ``` ### Motivation — Why I built it Two reasons, honestly. One: I wanted the tool. A wall of repos I can actually read beats a folder I have to excavate. The 20% of a repo manager I'd use every day is "tell me what this is without making me open it." Two: I wanted to put Apple's on-device model to real work — not a toy prompt, but a summarizer wired into a Rust backend through a Swift sidecar, cached, bounded to the model's context window, and degrading gracefully on Macs that can't run it. Something with real moving parts. That half turned out to be as interesting as the app. And because it's open source, you don't take my word for any of the privacy claims — you read the code, build it, and run it yourself. ### Build discipline — The vibe-code experience — the actual story. I didn't hand-write most of this. I directed it — and that's the point. Letting an agent "just build the board" gives you mush. What worked was running it like a small engineering team. - **Spec before code** — Each cycle — the card redesign, the AI analysis — started as a written design doc: goals, the data shape, acceptance criteria. No code until that was real. - **Decompose, then fan out** — I broke each cycle into bite-sized tasks with frozen interfaces, then ran several agents in parallel — lighter models on the mechanical files, the strongest available model on the integration and the novel bits. - **Verify independently** — The agents wrote and tested their files but never committed. I ran the full test suite, the Rust build, and the type-check myself, then committed in logical chunks. - **Use it until it breaks** — The AI produced nothing on the first wired-up run. I traced it to four separate problems in the Swift-sidecar spawn path — found each by running the real app and watching, not by reading diffs. - **A security pass before publishing** — An automated review flagged an over-broad argument permission on the sidecar. I tightened it before it went anywhere near a release. - **The discipline is the product** — My job wasn't typing. It was scoping each cycle, freezing the contracts, picking which model does what, and using the thing until it broke. The agents do the volume; the discipline is what makes them produce something that holds together. ### Tech stack — For the curious - **App framework** — Tauri v2, Rust, macOS only - **Board UI** — React 19, TypeScript, Zustand, Tailwind CSS 4 - **AI summarizer** — Apple FoundationModels, Swift sidecar, @Generable structured output - **Source data** — local Git (read-only), GitHub API, macOS Keychain - **Data layer** — local-first, single JSON file, no server, no telemetry - **Quality** — full unit-test suite, security-reviewed, MIT licensed ### Honest limitations — The things to know up front. - **macOS only** — The AI needs Apple Silicon + macOS 26 + Apple Intelligence on. Without it the app works fully — you just don't get the summaries. - **You build it from source** — There's no signed installer and there isn't meant to be. I don't run an Apple Developer account, and an open-source tool you compile yourself doesn't need one. Clone the repo, run the build script, launch it. - **It's a v1 and a portfolio project** — It runs, it's tested, but it's a thing you build and run, not a product in a store. - **GitHub enrichment needs a token** — Optional. It lives in your Keychain, never on disk. ### What's next — Roadmap - Semantic search across repos, on-device (Apple NLContextualEmbedding) - Repo relationships, inferred from AI tags and embeddings - A statistics view that aggregates AI-derived insights - Smoother build-from-source onboarding — one script, clear prerequisites ### Your code never leaves the machine. The AI model runs locally through Apple's FoundationModels framework. The only optional network calls are to the GitHub API, going directly from your machine to GitHub. No intermediary, no telemetry, no server. And because the whole thing is open source, you don't take my word for any of it — you read the code. ### FAQ — Questions people actually ask ### Build it, run it, read the code. Open source. No installer, no account, no API keys. Clone the repo, run the build script, and your repos become a board you can actually read. ### FAQ **Do I need an Apple Developer account?** No — and that's intentional. There's no signed installer and there isn't meant to be. You clone the repo, run the build script, and launch the app yourself. An open-source tool you compile from source doesn't need a developer account. **Does the AI work on my Mac?** The on-device summaries require Apple Silicon, macOS 26, and Apple Intelligence enabled. Without it, Coruro still works fully — you just get cards without the AI summary, and a one-time banner explains why. **Is my code uploaded anywhere?** No. The AI model runs on your Mac through Apple's FoundationModels framework. Your code never leaves the machine. The only optional network calls are to the GitHub API for enrichment data — and those go directly from your machine to GitHub, not through any intermediary. **What does GitHub enrichment require?** A personal access token, optional. Without it you still get local Git status and on-device AI summaries — just no stars, issues, or CI data. When you add a token it lives in your macOS Keychain, never written to disk. **I don't have 50 repos. Is this useful for me?** Coruro is most useful once you have 15–20+ local clones you've stopped tracking. It solves the problem of having so many repos you've lost the map of them. If you still know what's in every folder, a Finder window is fine. **Is this production software?** It's a v1 portfolio project. It runs, it's tested, and it has a security pass behind it — but you build it from source, not install it from a store. Treat it accordingly. Stack: Tauri v2 · Rust · macOS only · React 19 · TypeScript · Zustand · Tailwind CSS 4 · Apple FoundationModels · Swift sidecar · @Generable structured output · local Git (read-only) · GitHub API · macOS Keychain · local-first · single JSON file · no server · no telemetry · full unit-test suite · security-reviewed · MIT licensed ## Jooby **Jooby — native macOS app for your job search pipeline.** Status: macOS · open source · MIT. Page: https://fmillan.com/#p-jooby Built on career-ops. Open source. Contributions welcome. - [View on GitHub](https://github.com/felipemillan/career-ops-desktop) - [Original page on fmillan.com](https://fmillan.com/#p-jooby) Jooby started as a dashboard on top of the career-ops CLI toolset. It's grown into a native macOS app with a Kanban, AI evaluation triggers, embedded terminal, and analytics. Still in active development. If it solves a problem you have too, fork it, run it, or help build it. ### Why I built it — I'm job hunting. Using the time deliberately. I started with career-ops as a user — it handled the hard parts of the pipeline well. Then I noticed the gap: no UI, nothing to look at, everything in Markdown and terminal output. The obvious next step was to build a dashboard. So I did. But the dashboard was also a test. I'm using this stretch — the job search, the free time between things — to push what I can build with AI-assisted development. How far can it go on something with real moving parts? The web dashboard was one answer. Moving it to a native Tauri app with a Rust backend was the next question. A proper PTY for the terminal. Process spawning with a real security model. Atomic writes with build-time enforcement. Each new layer was a new thing to learn. The plan is to keep going. More features, better polish, more surface area for the things career-ops can do. And since it's open source, if you're also using career-ops and want to help build the UI layer — or just have a feature you need — the door is open. ### How it works — Four steps. - **Point Jooby at the companion repo** — Set CAREER_OPS_PATH or pick the folder in-app. The read-only views, analytics, and terminal work immediately. The action buttons need the companion scripts present. - **Browse the pipeline** — Applications view, Kanban, scan history, and reports — all rendered from your existing Markdown and YAML files. - **Trigger actions** — Scan for new roles, run AI evaluations against your CV, generate PDFs — from the action bar or from the terminal. - **Track** — Drag applications through stages. Analytics update automatically. ### What's in it — Five core features. - **Pipeline views** — Read-only views of your applications, pipeline status, scan history, and generated reports — parsed in TypeScript, rendered as tables and Markdown. Applications Kanban with drag-and-drop status changes. - **Action bar** — One-click triggers for companion scripts: scan, merge, dedup, pattern analysis, follow-up cadence, PDF/LaTeX generation. Headless AI evaluation through your local claude CLI. - **Analytics** — KPIs, pipeline funnel, score distribution, weekly activity, status timeline. Built in pure CSS and SVG — no chart library, nothing phoning home. - **Embedded terminal** — Real PTY running your login shell (xterm.js). Interactive claude and /career-ops commands work exactly as they do in Terminal.app. - **Privacy + security** — No inbound network surface. Process spawning uses argv arrays — no shell strings. Inputs validated and path-contained. Telemetry optional, off by default, self-hostable. ### Tech stack — For the curious - **Frontend** — React 19, TypeScript, Vite 7, Tailwind v4, shadcn/ui, TanStack Table, @dnd-kit - **Backend + app** — Tauri v2, Rust, process spawning, input validation, atomic writes - **Terminal** — xterm.js, portable-pty, real PTY - **Data** — Markdown + YAML on disk, no database, no telemetry by default, PostHog optional - **AI evaluation, scanning, PDF** — companion career-ops repo, Node scripts, Claude CLI - **License** — MIT, open source ### Honest limitations — What's not here yet. - Requires setup — Node, Rust, Tauri system dependencies, and the companion career-ops repo. No installer yet — that's on the list. - macOS only in practice — Tauri is cross-platform but only macOS has been tested and exercised. - Action buttons need the companion repo — Read-only views, analytics, and the embedded terminal work standalone. The action bar doesn't. - AI evaluation requires claude CLI on your PATH — That means a Claude Code subscription. No bundled model. - Active development, not finished — Works well for what it does today. There are rough edges and features missing. That's the point — it's growing. ### FAQ — Questions people ask before cloning it ### Come build it with me. Jooby is open source under MIT and actively being developed. If you're using career-ops and want a UI layer, this is it. If there's a feature you need, open an issue or a PR — the CONTRIBUTING guide is straightforward. If you just want to follow along, a on GitHub helps more than it sounds. github.com/felipemillan/career-ops-desktop ### FAQ **Do I need to be a developer to use Jooby?** Jooby needs you to be comfortable running a few terminal commands during setup. After that, you mostly work from the web dashboard and your browser. You do not need to write or read code to use it day to day. **What does Jooby cost?** Jooby is free and open source under the MIT license. You bring your own AI access — Claude Code or a Gemini API key — and, optionally, a free Firecrawl key for automated scanning. There is no subscription and no hosted service to pay for. **Does Jooby apply to jobs for me?** Jooby does not auto-apply. It evaluates fit, drafts tailored CVs and cover letters, and organizes your pipeline. Every submission is manual — you review each draft and send it yourself. The tool never hits submit on your behalf. **Where does my data go?** Your data stays on your machine. Jooby has no hosting and collects nothing. The only data that leaves your computer is what you send to the AI provider you choose, exactly like using their app directly. **Is Jooby its own AI model?** No. Jooby is not an AI model. The reasoning runs on existing AI CLIs — Claude Code or Gemini. The evaluation logic comes from the open-source career-ops project by santifer, which I forked and extended. **Can I use Jooby for any role or industry?** Yes. During setup, you teach Jooby your background, your stories, and the roles you are targeting, and it adapts its scoring and drafting to them. It is not tied to any single industry or job type. Stack: React 19 · TypeScript · Vite 7 · Tailwind v4 · shadcn/ui · TanStack Table · @dnd-kit · Tauri v2 · Rust · process spawning · input validation · atomic writes · xterm.js · portable-pty · real PTY · Markdown + YAML on disk · no database · no telemetry by default · PostHog optional · companion career-ops repo · Node scripts · Claude CLI · MIT · open source ## accesy **One line. Open source. Actually readable.** Status: Open source · MIT · v0.1.0. Page: https://fmillan.com/#p-accesy accesy adds a floating button to your page. Visitors get twelve reading controls and six one-tap profiles — stored in their own browser, nothing sent to a server. Free, MIT-licensed, and readable end to end. accesy is a free, MIT-licensed accessibility preference widget. You drop it into any web project with one line of code. Visitors get 12 reading controls and 6 one-tap profiles — dyslexia, low vision, ADHD, colour-blind, motor-impaired, seizure-safe — saved in their own browser. No backend, no tracking, no subscription. Tags: Open source · MIT · v0.1.0 - [View on GitHub](https://github.com/felipemillan/accesy) - [Overlay Fact Sheet](https://overlayfactsheet.com/) - [Original page on fmillan.com](https://fmillan.com/#p-accesy) - Free forever - MIT licensed — read every line - One-line install - No backend, no tracking ### The problem — Two options, both wrong. When I wanted to add accessibility controls to my own projects, I kept hitting the same two options: build the panel from scratch every time, or drop in a paid overlay that charges a monthly subscription and makes compliance claims that disability advocates actively dispute. Neither sat right. So I built the thing I wanted — a small, honest widget I could read end to end, drop into any project, and trust. accesy adds a floating button to your page. A visitor clicks it (or hits alt+a) and a panel slides out with twelve reading controls and six ready-made profiles. Their choice is saved in their own browser. No account, no tracking, no backend. Entirely client-side. ### Install — One line. Pick your stack. Three delivery options from one engine. Same behaviour, same controls, same zero-config default. ### React ``` npm i @accesy/react // In your app root: import { Accesy } from '@accesy/react'; ``` ### Any other stack ``` ``` ### Features — Twelve controls. Six profiles. One line to install. - **Twelve reading controls** — Visitor picks what helps them read. High contrast, invert colours; Bigger text, text spacing, line height; Legible fonts, dyslexia font; Pause animations, highlight links; Big cursor, hide images, saturation - **Six one-tap profiles** — Right set of controls, one click. Dyslexia; Low vision; ADHD; Colour-blind; Motor-impaired; Seizure-safe - **One line to install** — Zero-config default. in React — zero configuration; One ``` ### Where it applies — Opt-in laws. One mechanic. Consentinel implements the opt-in model: block non-essential scripts until the visitor agrees. That's what GDPR and the ePrivacy Directive require across the EU and UK (with fines reaching up to €20M or 4% of global turnover), what Chile's Ley 21.719 requires from December 1, 2026, and what most Latin American laws — including Brazil's LGPD — are built around. The same code covers all of them, because the mechanic is the same. What it does not do is the US opt-out model — CCPA/CPRA, "Do Not Sell," and the Global Privacy Control signal work the opposite way. That's a different mechanic and it's out of scope. If you need it, the code is MIT. Fork it. Compatible with opt-in laws by design — not legal advice, and not a compliance certification. - **GDPR / ePrivacy** — EU & UK - **Ley 21.719** — Chile (from Dec 2026) - **LGPD** — Brazil ### Installation — By hand or by prompt. Installation is one script tag, or npm install Consentinel if you'd rather bundle it into your build. It's small enough and declarative enough that you don't need a dashboard, an account, or an SDK to learn. You can paste the tag, or hand the README to your AI coding agent — Claude Code, Cursor, bolt.new, Lovable, Replit — and have it wired up in a single prompt. Shipping it as a package instead of a service is the whole point: it drops straight into whatever you're already building. - Claude Code - Cursor - bolt.new - Lovable - Replit ### Architecture — Under the hood - **Browser** — Preact 10 renders the banner — ~3 KB of runtime; MutationObserver rewrites tagged scripts to type="text/plain" until consent is given, then swaps them back; navigator.sendBeacon sends the audit payload — survives tab close, unlike a plain fetch - **The Worker** — Origin allow-list validation on every request; 4 KB body cap measured from actual bytes, not the Content-Length header; Parameterized D1 insert — no SQL injection surface; GET /stats is default-deny until you set STATS_TOKEN - **Audit log** — Every decision writes one row: visitor ID, accepted boolean, category arrays, URL, referrer, user-agent, timestamp; Schema is in the repo; Table lives in your Cloudflare account — not mine; D1 errors return 503; unexpected errors return 500 ### Design decisions — A few calls worth explaining - **navigator.sendBeacon first, not fetch** — The most common failure mode for consent loggers is the user accepting and immediately closing the tab. A plain fetch without keepalive gets cancelled on unload. sendBeacon survives it. I found this bug during a pre-launch audit and fixed it before shipping 1.0.2. - **Content-Type: text/plain on the POST** — This skips the CORS preflight round-trip — text/plain is a CORS-safelisted content type. The server still parses the body as JSON. It saves a round-trip on every consent decision — not a huge optimization, but a free one. - **Default-deny on /stats** — A consent widget handling privacy data shouldn't expose analytics by accident. The stats endpoint returns 503 unless you've explicitly set a bearer token. Most operators won't set one, and that's fine — the safe default is off. - **Preact, not React** — The whole widget is ~9 KB gzipped. Preact's runtime is around 3 KB of that. I didn't want to ship ~45 KB of React to a site that otherwise doesn't use it. - **teardownCookieEnforcer()** — The MutationObserver runs as long as the page exists. In an SPA that mounts the widget on every route change, you'd stack observers indefinitely. The mount() return function now tears the observer down. This was a real memory leak — the kind that only shows up in long-running SPA sessions. ### Tech stack — What it's built with - **Core** — TypeScript (strict), Preact 10, esbuild 0.28 - **Testing** — Vitest 2, jsdom, @cloudflare/vitest-pool-workers - **Infrastructure** — Cloudflare Workers, Cloudflare D1, Cloudflare Pages - **Delivery** — npm, jsDelivr CDN, GitHub Actions CI ### Roadmap — What's next An open-source side project, not a product I'm scaling — so the roadmap is "what I'll probably get to" plus "what I'd happily merge." It's MIT; if you need something sooner, fork it. Nothing here is committed — these are ideas I'd happily merge. Compliance gaps come before polish: the /export endpoint for data-subject requests matters more than a theme editor. Full reasoning in ROADMAP.md. - Google Consent Mode v2 - GTM template - React wrapper - Vue wrapper - Svelte wrapper - Astro wrapper - WordPress recipe - Shopify recipe - Webflow recipe - Ghost recipe - CSS-variable theming - HMAC-signed consent ledger - **v1.0.3** — Accessibility — focus trap, Esc to close, reduced-motion - **v1.0.4** — Worker /health endpoint · custom domain for the demo - **v1.0.5** — /export endpoint for DSAR audit trails (NDJSON, token-gated) ### Deep dives — More on the architecture - **Why Cloudflare Workers + D1, not a traditional backend?** — Cloudflare's free tier covers roughly 100,000 consent events per day — more than enough for most personal or small-business sites. D1 is a SQLite-compatible database that lives in your Cloudflare account, not mine. There's no server to provision, no connection pool to manage, and the cold-start time for a Worker is measured in milliseconds. The self-hosting angle is the real reason. Any hosted consent-log backend means your audit data is on someone else's infrastructure. With D1, the data is yours by default. - **How does script blocking work?** — Before consent, the widget rewrites any data-consent-category-tagged scripts to type="text/plain", which the browser ignores. A MutationObserver watches for new scripts added after the widget mounts — they get the same treatment until consent is recorded. On consent, the type attribute is restored, and the browser re-evaluates the scripts. Reject follows the same flow in reverse — scripts are neutralized and any cookies that were already set are not re-set on subsequent loads. - **Is the SRI hash safe to use as-is?** — Each version of Consentinel ships with a SHA-384 integrity hash pinned to that exact npm bundle. The hash in the script tag above matches version 1.0.2, and jsDelivr serves the file unmodified from npm, so the browser will refuse to run anything that doesn't match. When you upgrade to a newer version, regenerate the hash from the new bundle. The README includes the command to do it. - **What does 'self-hosted' mean in practice?** — You deploy one Cloudflare Worker (a single TypeScript file) and one D1 table to your own Cloudflare account. Setup takes about 10 minutes following the quickstart. After that, the widget's audit POSTs go to your Worker URL — no traffic passes through anything I own. The frontend script still loads from jsDelivr by default (a public CDN), because that's the lowest-friction path. If you want zero third-party requests at all, npm install Consentinel and bundle it yourself. Stack: TypeScript (strict) · Preact 10 · esbuild 0.28 · Vitest 2 · jsdom · @cloudflare/vitest-pool-workers · Cloudflare Workers · Cloudflare D1 · Cloudflare Pages · npm · jsDelivr CDN · GitHub Actions CI --- # Roles in depth ## Cynoia — Chief Marketing & Sales Officer 09/2024 – 03/2026 · Tallinn Led marketing and sales for Cynoia — Africa's all-in-one team workspace replacing Slack, Monday, Zoom and Notion for African businesses. Built the GTM motion from the ground up. What started as a mentorship connection grew into a full rebuild of the go-to-market strategy. - **6,000+** — users across 13 countries - **10+** — telecom and university partnerships - **2** — new country operations — Senegal, Morocco ### The work I'm proudest of — From "global ClickUp competitor" to "made in Africa, by Africans, for Africans." Repositioned Cynoia around where it was built and who it was for, and localized it into Wolof and Swahili. That unlocked partnerships that had been stalled for over a year. ### What we got done - Scaled to 6,000+ users across 13 countries - Opened 2 new country operations (Senegal, Morocco) - Closed 10+ partnerships with top African telecoms and universities - Shipped platform features in native African languages - Led distributed cross-functional teams across Tunisia, Estonia, Senegal and Morocco - Positioned against global incumbents at ~80% lower software cost for African SMBs - Owned the full funnel: brand, content, partnerships, community, PLG onboarding, sales enablement, board reporting - Represented Cynoia at Gitex Africa and continental tech events ## Shroomwell — Chief Marketing Officer 09/2023 – 04/2024 · Tallinn Led marketing for Shroomwell, a wellness company working with medicinal mushrooms and mycotechnology. Built a net-new marketing division and ran it. ### What I owned - **Brand strategy & positioning** — The brand narrative for Shroomwell's products and what makes them different. - **Product marketing** — Launches of mushroom-based products, including Shiitake extract, Reishi and Chaga elixirs and wellness capsules. - **Digital & social** — Digital campaigns and social media to carry the message further. - **PR & communications** — Public image, media coverage and relationships with influencers and experts in the space. - **Content** — Informative content to make Shroomwell a credible voice in mycology and wellness. - **Research & team** — Market and competitor analysis, and building a cohesive marketing team. ## Hive Identity — Co-founder & Chief Marketing Officer 05/2021 – 05/2023 · Tallinn Co-founded Hive Identity, a Customer Identity Platform for startups and small businesses, and ran marketing and growth as CMO. Non-enterprise businesses had been kept out of KYC (Know Your Customer) by cost and overcomplicated setups. To put it simply: underserved and overcharged. Hive was built for them. - **$500K** — seed round, led by Isaac Saldana, founder of SendGrid ### What I did - Owned marketing and growth, scaling a product-led, self-serve strategy - Built PLG from scratch - Raised $500K in a round led by SendGrid's founder ### What it taught me The war in Ukraine disrupted the team, and Hive shut down in 2023 — which taught me more about prioritization than the roles that worked. ## Semrush — Global Brand Evangelist & Influencer Marketing Manager 10/2019 – 04/2021 Joined Semrush as its first Global Brand Evangelist, to build brand awareness and translate the value of the product to communities, customers and professional marketers. Part of the team through the ~$2B IPO. - **20,000+** — influencers in SEO, content, PPC and digital marketing - **30** — people in a private inner circle ### What I did - Worked with a network of 20,000+ influencers in SEO, content marketing, PPC and digital marketing - Ran a private influencer group of 30 - Automated workflows to streamline teamwork and social tasks - Built awareness through evangelism at tech and marketing conferences and events - Secured speaking slots at online events and podcast interviews - Ran workshops and masterclasses with digital marketers - Closed the loop between field and product — carrying what users experienced back to the team ## Twilio — Senior Community Development Manager, EMEA 02/2019 – 05/2019 · EMEA Joined Twilio after the $3B acquisition of SendGrid in 2019. Kept community development running for the Twilio SendGrid program, which gives startups mentoring, networking and free access to SendGrid and Twilio, plus early-release and beta products. ## SendGrid — Senior Community Development Manager, EMEA 05/2016 – 02/2019 · EMEA Joined SendGrid before its ~$1.5B IPO in November 2017 (NYSE: SEND) and stayed through the $3B acquisition by Twilio. Nurtured and expanded SendGrid's Accelerate program out of Europe into Africa, the Middle East and Latin America. The program gives startups mentoring, networking, free access to SendGrid's email platform and early-release products. - **550+** — startup accounts - **50+** — accelerator and ecosystem partners - **20+** — countries with activations ### What I contributed - Evangelized the program to accelerators, managing directors, VC funds, coworking spaces and ecosystem players - Helped bring in 50+ partners and 550+ accounts generating $15K+ MRR and $35K cMRR — including Techstars, 500, Startup Wise Guys, Startup Bootcamp, Kima Ventures, Microsoft Ventures, Lift99 and Mosaik - Delivered presentations in 20+ countries: conferences, CTO and developer meetups, workshops - Ran social engagement with GaggleAmp, reaching 14.2M people - Served as a technical resource: onboarding and office hours to integrate SendGrid into partner stacks - Carried feedback from the field to product and developer-experience teams ## Founder years — Founder years — Chile and the early web 2000 – 2016 Before the scale-ups, I built my own things. Most of what I know about early-stage chaos comes from here: when you've put your own money on the line, you stop caring about vanity metrics and focus on what brings in customers. - **93** — startups helped launch through PymeFacil - **~65%** — made it off the ground ### The ventures - **PymeFacil — CTO & co-founder · 2010 – 2012** — Co-founded the first entrepreneurship agency in Chile, helping 93 startups and companies start their ventures — about 65% of them made it. - **Guubie.com — Founder & CEO · 2011 – 2015** — A marketing automation platform for startups: newsletters, automated emails and landing pages. Part of Startup Incubator Catalyst. - **Biletu.com — Founder · 2012 – 2015** — A mobile app to pay between friends and skip the awkwardness, with gamification and social features. Santiago, Chile. - **SubmitMyStartup.com — Founder & CEO · 2014** — A service for startups to submit their profile to 70+ relevant websites, blogs and directories — an intro to investors, journalists and bloggers. - **Lanuu.co — Founder · 2015 – 2016** — A simple drag-and-drop web builder for non-technical people. - **Orange Media Labs — Founder · 2000 – 2016** — Ran every project, from web development to web consultation. ### Honors - Start-Up Chile — accepted - Startup Wise Guys accelerator - LeWeb Paris 2012 - The Next Web Chile Awards - Guubie — Startup Incubator Catalyst ## Mentor & community — Mentor, advisor, judge, organizer 2016 – 2023 · Europe · Chile · global Alongside the day jobs, I mentored startups across accelerators in Marketing Strategies, Branding, Go-To-Market and Growth — and helped run startup communities in Tallinn. ### Accelerators - **Startup Wise Guys · 2016 – 2023** — Marketing and growth mentor in a three-month program, alongside 150+ international and local mentors. - **Katapult Accelerator, Oslo · 2017 – 2023** — Mentor to startups across cohorts on marketing, branding, GTM and growth. - **Techstars · 2018 – 2020** — Mentor to startups in different cohorts. - **Founder Institute, Chisinau · 2017 – 2020** — Mentor in marketing strategy, branding, GTM and growth. - **TheFactory, Oslo · 2018 – 2020** — Mentor at a fintech, insurtech, regtech and AI accelerator. - **Storytek Creative Hub, Tallinn · 2018 – 2020** — Mentor at an accelerator for audiovisual and creative-tech startups. - **Fincube, Istanbul · 2019 – 2020** — Mentor at the digital garage powered by QNB Finansbank. - **Global Innovation Catalyst · 2021 – 2023** — Fellow mentor in a 12-week experiential learning program on designing and launching new ventures. ### Judging & advisory - **Start-Up Chile · 2018 – 2021** — Judge — evaluated and scored applications to the Chilean government's program for early-stage founders. - **SXSW · 2018 – 2019** — Board advisor for the SXSW Pitch competition, reviewing applications. ### Community - **Startup Grind Tallinn · 2017 – 2019** — Chapter director for the global startup community powered by Google for Entrepreneurs. - **Fuckup Nights Tallinn · 2017 – 2019** — Co-organizer of the nonprofit event series on business failure stories. - **Mati Foods · 2023 – present** — Investor in a mycoprotein food company. --- # Testimonials > "Felipe consistently demonstrated an amazingly positive attitude, an aptitude for finding opportunity in every interaction, the capacity to work with people from a broad range of cultures and backgrounds — all the while doing it with a smile." > — Len Shneyder, Sr. Director of Industry Relations & Outbound PM, SendGrid / Twilio > "Under Felipe's vision, our projects consistently surpassed targets, achieving remarkable growth. His analytical prowess and data-driven decision-making optimized campaigns for maximum ROI." > — Fernando Angulo, Head of Communications, Semrush > "Felipe is the quintessential entrepreneur, down to earth, focused, very smart, fast, and visionary. Would I build my next company with him? YES :)" > — Paolo Privitera, 6X Exit, Serial Entrepreneur & Investor, Evonove > "Felipe has been a mentor for Katapult Accelerator almost since the beginning of our journey, and his involvement and engagement with our portfolio companies have been invaluable." > — Nina Heir, Ex-VC, Angel Investor & Board Member, Katapult Accelerator > "Felipe is an ongoing mentor at SWG accelerator. He helps early-stage startups with building a business mindset and sharing email-marketing knowledge." > — Alexandra Balkova, Investor & Newton Venture Fellow, Startup Wise Guys > "Working with Felipe has always been an extreme pleasure. Whether we are talking about his positive attitude or his problem-solving abilities, his drive to achieve results no matter the circumstances is admirable." > — Sebastien Toupy, Community Builder & Strategist, The Next Web (TNW) > "Felipe is the heart of a startup community. He truly understands the givers gain concept — he's always giving more than he takes." > — Kristine Nagle, Sustainability Strategy Partner > "I had the honor to meet Felipe over a decade ago at Start-Up Chile. Felipe has passion, a global mindset and a real sense to put an idea into a practical and real business." > — Caro Rossi, Head of Strategic Initiatives, ESNA / Start-Up Chile