Overall AI readiness score
A combined result across all audit signals.
Overall AI readiness score
A combined result across all audit signals.
Overall AI readiness assessment
Vibetuner (vibetuner.alltuner.com) received an AI-readiness score of 64/100 in an automated technical audit. llms.txt was accessible, llms-full.txt was accessible, and ai.txt was not found. The robots.txt analysis found 0 explicitly allowed and 0 blocked AI crawlers, with 0 declared sitemaps. Homepage markup completeness was 0%; no Schema.org types were detected and 0 OpenGraph tags were detected. Results reflect the public site response observed on 2026-08-26T02:43:40.286Z.
llms.txt is accessible and contains 4,879 tokens. An expanded llms-full.txt is also available with 30,793 tokens, giving agents more direct context. No separate ai.txt policy was detected; it is optional, but can clarify training, retrieval, and attribution preferences.
robots.txt is available. 11 of 11 tracked AI bots are not blocked. Declared sitemaps: 0. No Schema.org type was detected on the homepage. 0 OpenGraph tags were found and markup completeness is 0%, leaving more entity interpretation to crawlers.
The mobile Lighthouse profile adds Performance 67/100, Accessibility 89/100, Best Practices 96/100, SEO 100/100, and experimental Agentic Browsing 67/100. These signals have a limited weight: they complement rather than replace llms.txt, robots.txt, and structured-data checks.
A mobile Lighthouse measurement. Google’s experimental Agentic Browsing category is explained separately and does not replace the broader llmsmap AI-readiness score.
Performance
Accessibility
Best Practices
Technical SEO
Agentic Browsing
Mobile performance is 67/100, with the largest visible content block appearing in 9.3 s and the browser main thread blocked for 0 ms. Layout shift was 0. The main thread is where the browser runs JavaScript, calculates layout, and paints the page; long work there delays both user input and browser-agent actions.
Accessibility scored 89/100, Best Practices 96/100, and technical SEO 100/100. The experimental Agentic Browsing category scored 67/100. It measures signals Google currently tests for software agents and is shown separately from the llmsmap AI-readiness score.
Split long JavaScript tasks, defer non-critical scripts and styles, and shorten blocking request chains. This helps the primary content appear sooner and makes controls usable earlier.
Remove unused CSS and JavaScript, load heavy widgets on demand, and limit third-party scripts. Less code means less parsing and background work on the device.
Serve correctly sized modern formats, prioritise the primary visual, and lazy-load content below the first viewport.
Improve server response time, remove unnecessary redirects and repeat downloads, and use compression, caching, and selective preconnect hints.
First content
Main content
Layout stability
Blocking time
Visual speed
Machine-readable files, crawler policy, discovery, and homepage markup.
ai.txt file was not found
Sitemap is not declared in robots.txt
Schema.org markup was not found on the homepage
OpenGraph tags were not found on the homepage
Based on robots.txt analysis
# Vibetuner
> Vibetuner is a production-ready FastAPI project scaffolding tool that generates full-stack web applications with authentication, flexible database support (MongoDB or SQL), frontend, Docker deployment, and CLI tools pre-configured in seconds. Built by All Tuner Labs for rapid iteration and modern development.
Important notes:
- Vibetuner consists of four packages: a Python framework (`vibetuner`), a JavaScript build-deps package (`@alltuner/vibetuner`) which bundles tailwind, daisyui, htmx and an npm-shipped mirror of the framework's jinja templates (`@alltuner/vibetuner-jinja`, pulled in as a transitive — consumers don't install it directly), and a Copier scaffolding template
- The framework separates immutable framework code (`vibetuner` package) from your application code (`src/app/`) for clean updates
- In all examples, `app` refers to your project's Python package (the directory under `src/`). The actual name depends on your project slug (e.g., `src/myproject/` for a project named "myproject")
- HTMX is used instead of React/Vue for simplicity - server-rendered HTML with sprinkles of interactivity
- All tools are chosen for speed: uv (Python), bun (JavaScript), Granian (ASGI server), Ruff (linting)
- The project is designed to work excellently with AI coding assistants like Claude, Cursor, and ChatGPT
## Quick Start
- [Quick Start Guide](https://vibetuner.alltuner.com/quick-start/): Get started with Vibetuner in 5 minutes
- [Installation](https://vibetuner.alltuner.com/installation/): Prerequisites and installation options
- [Your First Project](https://vibetuner.alltuner.com/quick-start/#create-your-first-project): Interactive project setup with `uvx vibetuner scaffold new`
## Core Documentation
- [Development Guide](https://vibetuner.alltuner.com/development-guide/): Daily development workflow, adding routes, models, templates, and background jobs
- [Architecture](https://vibetuner.alltuner.com/architecture/): System design, four-package architecture, request flow, and core components
- [Tech Stack](https://vibetuner.alltuner.com/tech-stack/): Detailed information about all technologies used and why they were chosen
- [Authentication](https://vibetuner.alltuner.com/authentication/): OAuth and magic link authentication setup and configuration, plus built-in hardening: `SESSION_KEY` fails closed in production while the shipped placeholder is unchanged (`vibetuner crypto generate-key`), auth endpoints carry a per-IP rate limit (`RATE_LIMIT_AUTH_LIMITS`, default `5/minute`), and post-login redirects (`next`/`current`) are validated against open redirects
- [Background Tasks](https://vibetuner.alltuner.com/background-tasks/): Background task system powered by Streaq and Redis with retries, cron scheduling, and SSE integration
- [Runtime Configuration](https://vibetuner.alltuner.com/runtime-config/): Layered configuration system with MongoDB persistence, debug UI, and feature flags
- [Upgrading](https://vibetuner.alltuner.com/upgrading/): General upgrade workflow — `vibetuner scaffold update` for template changes, `uv add vibetuner@latest` for framework bumps, semver policy, and links to version-specific migration guides
- [Upgrading to v12](https://vibetuner.alltuner.com/upgrading-to-v12/): v11 → v12 breaking changes — fail-closed `SESSION_KEY` guard (`vibetuner crypto generate-key`), Jinja autoescaping in the static-render path, and a `5/minute` default auth rate limit (`RATE_LIMIT_AUTH_LIMITS`)
- [HTMX v2 to v4 Migration](https://vibetuner.alltuner.com/htmx-migration/): Breaking changes and migration guide for upgrading from HTMX v2 to v4
- [Troubleshooting](https://vibetuner.alltuner.com/troubleshooting/): `vibetuner doctor` diagnostic command — what it checks, how to read output, and common issue resolutions
- [Theming](https://vibetuner.alltuner.com/theming/): Build-time brand palette via cascade override of DaisyUI's `[data-theme="…"]` selectors in `config.css` (no consumer-side `daisyui` dep needed), a CSP-nonced no-flash light/dark/system setter (`base/theme_init.html.jinja`) that sets `data-theme` before first paint and exposes `window.cycleTheme()`, plus runtime per-tenant overrides via the embedded `TenantTheme` model + opt-in `register_tenant_theme_provider()` helper + shipped `base/theme.html.jinja` partial that injects DaisyUI role-color overrides at request time
## Features
- **HTMX Request Detection**: `request.state.htmx` available on every request.
Properties: `.boosted`, `.target` (`tag#id`), `.source` (`HX-Source`; the htmx 2
request `HX-Trigger`), `.request_type` (`"full"`/`"partial"`), `.current_url`,
`.history_restore_request`, `.prompt` (via the `hx-prompt` extension). Use
`require_htmx` dependency from `vibetuner` to reject non-HTMX requests with 400
- **CRUD Factory**: `create_crud_routes()` generates list/create/read/update/delete
endpoints for Beanie models with pagination, filtering, sorting, and search
- **SSE Helpers**: `@sse_endpoint()` and `broadcast()` for real-time streaming with
HTMX. Import from `vibetuner`. Redis pub/sub for multi-worker support
- **`@render` Decorator**: `@render("template.html.jinja")` eliminates
`render_template()` boilerplate — route returns a dict, decorator handles rendering.
Returns `Response` objects unchanged (escape hatch). Import from `vibetuner`
- **Streaming HTML**: `render_template_stream()` returns a `StreamingResponse` using
Jinja2's `generate()`, improving TTFB for large pages. Same context merging as
`render_template()`. Import from `vibetuner`
- **Block Rendering**: `render_template_block(template, block_name, request, ctx)`
renders a single `{% block %}` for HTMX partials. `render_template_blocks()` renders
multiple blocks for OOB swaps. No extra dependencies. Import from `vibetuner`
- **Context kwarg**: `render_template*` accepts the user context positionally, as
`ctx=`, or as `context=` (alias for `ctx=`). Passing both, or any unknown kwarg,
raises `TypeError` — typos cannot silently render with an empty context
- **`@cache` Decorator**: `@cache(expire=60)` caches route responses in Redis.
Key derived from path + query params. Use `vary_on=lambda r: str(r.state.user.id)`
for request-dependent keys (per-user, per-tenant). Disabled in debug mode
(override with `force_caching=True`). No-op if Redis unavailable.
`invalidate(path, query_params=..., vary=...)` deletes one exact entry
(pass `vary` to hit a `vary_on` variant); `invalidate_pattern(path)` removes
every variant of a path (or a glob match) via per-path key registries —
no keyspace SCAN. Import from `vibetuner.cache`
- **`@cache_control` Decorator**: `@cache_control(max_age=300, public=True)` sets
`Cache-Control` headers declaratively. Supports `public`, `private`, `no_cache`,
`no_store`, `max_age`, `s_maxage`, `must_revalidate`, `stale_while_revalidate`,
`immutable`. Import from `vibetuner.decorators`
- **HTMX Response Helpers**: `hx_redirect()`, `hx_location()`, `hx_trigger()`,
`hx_push_url()`, `hx_reswap()`, `hx_retarget()`, `hx_refresh()`,
`hx_replace_url()`. Handles JSON serialization internally. Import from
`vibetuner.htmx`
- **HTMX Error-Swap Default**: the skeleton ships
`<meta name="htmx-config" content='{"noSwap": [204, 304, "4xx", "5xx"]}'>`,
so 4xx/5xx response bodies are not swapped into the target (htmx 2 behavior)
— a stray `422` never replaces a fragment with an error page. Opt a specific
element back in with `hx-status:<code>` (wins over the wildcards). Override
the `htmx_config` block to customize; keep `noSwap`
- **Built-in Template Globals**: `request`, `language`, `DEBUG`, `now`
(UTC datetime), `today` (ISO date string), `project` (`settings.project`),
`brand` (`settings.brand`), `csp_nonce`, and `hotreload` are available
in all templates automatically
- **Skeleton Extension Points**: `base/skeleton.html.jinja` exposes blocks
(`extra_head_links`, `extra_scripts`, `before_main`, `after_main`) and
context variables (`color_scheme`, `canonical_url`, `font_preloads`) so
projects can extend instead of wholesale-overriding the skeleton —
upstream changes (CSP nonce, theming, etc.) flow through automatically
- **Brand Configuration**: `BRAND_PRIMARY_COLOR`, `BRAND_BROWSER_THEME_COLOR`,
`BRAND_EMAIL_BUTTON_COLOR` env vars drive favicon meta tags, the PWA
manifest's `theme_color`/`background_color`, and the magic-link email button
— surfaces where DaisyUI/CSS-variable theming can't reach (favicon meta
is read before CSS runs; email clients ignore CSS variables). Accepts
pydantic-Color inputs (named, `rgb()`, hex). For per-tenant in-page
colors use `TenantTheme`; `BrandSettings` is deliberately app-level
- **Encrypted Fields**: `EncryptedFieldsMixin` and `EncryptedStr` type for
transparent Fernet encrypt-on-save / decrypt-on-load on any Beanie model field.
Import from `vibetuner.models.mixins`. Requires `FIELD_ENCRYPTION_KEY` env var
- **Template Context Providers**: `register_globals()` and `@register_context_provider`
inject variables into every template render
- **Per-Tenant Theming**: `TenantTheme` embedded model (`vibetuner.models.TenantTheme`)
with eight optional `#rrggbb` fields for DaisyUI role / role-content colors plus
`.overrides()` for the `{css_var: hex}` map. Wire it up with
`register_tenant_theme_provider(getter)` from `vibetuner` — opt-in, runs on every
render, fail-soft. Vibetuner's `base/skeleton.html.jinja` already includes
`base/theme.html.jinja`, which emits a CSP-noncified `<style>:root { ... }</style>`
block after `bundle.css`. `bundle.css` stays tenant-agnostic and cached
- **i18n Primitives**: `vibetuner.i18n` ships
`register_locale_resolver(getter, *, priority=0)` to inject custom selectors
at the front of `LocaleMiddleware`'s chain (per-tenant locale becomes a
one-liner), `set_request_language(request, code)` to update both the Babel
context and `request.state.language` in one call, and `language_picker(display_locale=None)`
returning `[{code, name}]` with names rendered in the current request's locale
(browsing in Spanish gives "inglés / español / catalán"; never English-only).
`language_picker` is also registered as a Jinja global so templates can call
it directly without overriding any existing template variable. The
Accept-Language selector (`LocaleFromAcceptLanguage` / `negotiate_accept_language`)
negotiates region-aware with language-only subtag fallback, so a region-qualified
top preference (`ca-ES`) resolves to supported `ca` instead of losing to a
lower-ranked exact match (`es`)
- **Framework Translation Catalogs**: vibetuner ships compiled
`.mo` files under `vibetuner/locales/<lang>/LC_MESSAGES/messages.mo`
(currently `en`, `ca`). `frontend/middleware.py` loads them before the
project's own `locales/` so framework templates (`login.html.jinja`,
`user/profile.html.jinja`, etc.) render in the active locale with no
per-app extraction. App catalogs loaded after the framework override
framework strings on collision via gettext domain merging. Add a new
framework locale with `just new-framework-locale <code>` then
`just compile-framework-locales`
- **Service DI**: `get_email_service()`, `get_blob_service()`, `get_runtime_config()`
FastAPI dependency wrappers
- **Email Providers**: Pluggable transactional email via Resend
(`MAIL_RESEND_API_KEY`), Mailjet (`MAIL_MAILJET_API_KEY` +
`MAIL_MAILJET_API_SECRET`), or Cloudflare Email Service
(`MAIL_CLOUDFLARE_API_TOKEN` + `MAIL_CLOUDFLARE_ACCOUNT_ID`). Auto-detected
from available credentials; `MAIL_PROVIDER=resend|mailjet|cloudflare` to
pick explicitly. Auto-detection priority: resend > mailjet > cloudflare.
Resend caps sends at 5 req/s plus a monthly quota; on HTTP 429 the provider
raises `RateLimitError` (no retry/queue) and logs a `WARNING` with the
`ratelimit-*`/`x-resend-monthly-quota` headers, which are logged at `DEBUG`
on every send
- **CSP Nonce Auto-Injection**: `SecurityHeadersMiddleware` generates a unique CSP nonce
per request and auto-injects it into all `<script>` tags in HTML responses. Do NOT
manually add `nonce=` attributes to `<script>` tags in templates, the middleware handles
this automatically. For `<style>` tags or other elements that need the nonce, use the
`{{ csp_nonce }}` template variable (available in all templates via context provider).
CSP is enforced in both production and debug mode by default so violations break the
page locally; set `CSP_ENFORCE_CSP_IN_DEBUG=false` to fall back to
`Content-Security-Policy-Report-Only` in debug. Configure extra sources via `CSP_*`
environment variables (e.g., `CSP_EXTRA_SCRIPT_SRC`). Set `CSP_STYLE_SRC_STRICT=true`
to drop `'unsafe-inline'` from `style-src` and require nonces on `<style>` tags
(opt-in hardening; framework templates already nonce every `<style>`). Requires
`vibetuner` ≥ 10.11.0 (older releases silently ignore the env var). When an OAuth
provider (Google, GitHub) is enabled and has credentials configured, its avatar CDN
host is added to `img-src` automatically — no `CSP_EXTRA_IMG_SRC` entry needed for
OAuth provider profile pictures. The middleware also pairs nosniff with a
`text/plain; charset=utf-8` fallback when a response has no `Content-Type` (e.g. a
bare `Response(status_code=404)`), so bare responses do not get turned into 0-byte
browser downloads (Safari/Firefox) or generic error pages (Chrome)
- **htmx CSP Protection (default-on)**: htmx 4.0.0-beta4 ships an `hx-csp` extension
(renamed from `hx-nonce` in beta3) that gates htmx attribute processing behind the
page CSP nonce. It is loaded by default from the framework-managed block of
`config.js` (`import "@alltuner/vibetuner/htmx/csp";`, alongside `hx-preload` /
`hx-live`), and it is what makes `hx-on:` / `hx-live` work at all under vibetuner's
strict `script-src 'nonce-…' 'strict-dynamic'` (no `'unsafe-eval'`): the import also
flips `htmx.config.safeEval = true` so htmx evaluates those expressions via
nonce-based `<script>` injection instead of `new Function()` (which the nonce +
`strict-dynamic` CSP would reject with an `EvalError`). The extension is fail-closed —
any element carrying an `hx-*` attribute needs a matching `hx-nonce` — but
`SecurityHeadersMiddleware` auto-stamps `hx-nonce` on every htmx element (the same
way it injects the `nonce` into `<script>` tags), so no manual stamping, no
`body_attrs` nonce, and no `safeEval` meta tag are required. Because the gate trusts
whatever the server renders, projects that render untrusted HTML (markdown,
user-supplied content) must still sanitize it. The HTML attribute is still named
`hx-nonce`; only the extension was renamed
- **htmx Live Reactivity (default-on)**: htmx 4's `hx-live` extension is
loaded by default from `config.js` since `@alltuner/vibetuner` 10.15.0. Use
`hx-live="<expr>"` for derived state that recomputes on any DOM mutation,
`hx-on:event="..."` with the `q()` proxy / sigil-`toggle()` / per-element
`debounce()` for richer event handlers. CSP-safe (the default-on `hx-csp`
extension's `safeEval` evaluates them via nonce-based script injection) where raw
inline `onclick=` is not. Vibetuner's preferred path for chip lists, paired
controls, live filters, and inline form-field validation — use this before
reaching for Alpine.js or Stimulus.
See `vibetuner-docs/docs/htmx-migration.md#live-reactivity-with-hx-live` for
idiomatic patterns and rough edges
- **Health Checks**: `/health`, `/health/ping`, `/health/ready`, `/health/id` endpoints
with service connectivity checks
- **Robust Tasks**: `@robust_task()` decorator with exponential backoff retries and
MongoDB dead letter collection
- **Robust Cron**: `@robust_cron("*/15 * * * *")` decorator combines scheduled
execution with the same retry/dead letter support as `@robust_task()`.
Import from `vibetuner.tasks.robust`
- **Streaq UI**: Task queue monitoring dashboard at `/debug/tasks`. `/queue`
and `/workers` carry a 5s request timeout (Redis SCAN can be slow on shared
instances); on expiry the server renders a "Task queue unavailable" page
with an operator checklist (worker / Redis / streaq version) and a retry
link, or returns a `streaq_debug_timeout` JSON payload for API clients
- **Doctor CLI**: `vibetuner doctor` validates project setup, services, and config
- **Config Decorators**: `@config_value()` and `ConfigGroup` for type-safe runtime
configuration. `set_config(key, value)` persists values programmatically.
`VibetunerApp(runtime_config={...})` for declarative registration in `tune.py`
- **Config CLI**: `vibetuner config list|set|delete` manages runtime config
values from the command line. Secret values use hidden input prompts
- **Config Env Fallback**: Runtime config keys auto-resolve from environment
variables derived as `<KEY>.upper().replace(".", "_")` (e.g.
`services.anthropic_api_key` → `SERVICES_ANTHROPIC_API_KEY`). Priority is
runtime override > MongoDB > env var > registered default. `.env` /
`.env.local` are honored. `/debug/config/<key>` renders a "Set this value"
block on secret keys showing the env-var name, CLI command, and `.env`
entry, with a worker/frontend restart reminder
- **Testing Fixtures**: `vibetuner_client`, `mock_auth`, `mock_tasks`,
`override_config`, `vibetuner_db` pytest fixtures. Set `TEST_MONGODB_URL` to
run the suite against a local Mongo while `MONGODB_URL` stays pointed at prod;
`TEST_REDIS_URL` does the same for Redis (rate limiter, cache, pub/sub)
- **Error Messages**: Actionable error messages with env var examples, Docker
commands, and documentation links
- **Docker Builds**: Production Docker image builds (linux/amd64) via
`just release` using `compose.prod.yml` bake configuration. Optional GitHub
Actions workflow for self-hosted runners (enabled via
`use_self_hosted_runner` scaffold prompt)
## Reference
- [CLI Reference](https://vibetuner.alltuner.com/cli-reference/):
Commands for `vibetuner scaffold`, `vibetuner run`, `vibetuner db`,
`vibetuner doctor`, `vibetuner config`, `vibetuner debug`, `vibetuner crypto`,
and `vibetuner version`
- [Scaffolding Reference](https://vibetuner.alltuner.com/scaffolding/):
Template prompts, post-generation tasks, and updating existing projects
- [Deployment](https://vibetuner.alltuner.com/deployment/):
Docker production builds, environment configuration, and deployment options
## Development
- [Contributing Guidelines](https://vibetuner.alltuner.com/contributing/): How to contribute, code style, and PR title format (uses conventional commits)
- [Development Workflow](https://vibetuner.alltuner.com/development/): Setting up the development environment for contributing to Vibetuner itself
- [Changelog](https://vibetuner.alltuner.com/changelog/): Version history and release notes
## Packages
- [Python Package](https://pypi.org/project/vibetuner/): Core framework published to PyPI
- [JavaScript Package](https://www.npmjs.com/package/@alltuner/vibetuner): Frontend build dependencies published to npm
- [Tailwind Source Package](https://www.npmjs.com/package/@alltuner/vibetuner-jinja): npm-shipped mirror of the framework's frontend jinja templates, pulled in transitively by `@alltuner/vibetuner` so tailwind can `@source` them at frontend build time without shelling out to Python
- [Repository](https://github.com/alltuner/vibetuner): Source code and issue tracking