Most component libraries leave a coding agent to guess. It reads a prop name off a blog post from two majors ago and writes markup that looks plausible. The mistake surfaces at runtime, or never: an unknown prop in Vue is an attribute.
bitboss-ui ships four things instead:
- a knowledge base written for agents
- a manifest of every component's real surface
- a check that validates your markup and stylesheets against that manifest
- an MCP server your agent can query directly
One command sets up all of it.
Why this exists
Three failure modes cost the most, and each has a tool aimed at it.
Invented props. An agent writes color="primary" because every other library
has one. v3 removed color from checkboxes, radios, switches, sliders, ratings, avatars, icons and spinners. Nothing throws; the attribute
lands on the DOM node and does nothing. The same goes for names borrowed from
elsewhere: toast.success(), v-model:open on a dialog, variant="danger".
Stale API. Training data is full of v2 and of earlier 3.0 betas.
showClose, allowWriting, noDataText and type on BbIcon all read as
current and are all wrong.
Right component, wrong situation. BbTag and a multiple BbSelect look
interchangeable until you notice one is for values the user invents.
Point your agent at it
Run one command in the app, after installing bitboss-ui:
npx bitboss-ui ai-init
It does four things. It writes a pointer to the knowledge base for every assistant the project might use, so an agent finds the rules without being told:
AGENTS.md, for Cursor, Codex and most other agents. The command adds a marked fragment and leaves the rest of the file alone..cursor/rules/bitboss-ui.mdc, for Cursor..github/copilot-instructions.md, for GitHub Copilot, again as a marked fragment..claude/skills/bitboss-ui/SKILL.md, for Claude Code..windsurf/rules/bitboss-ui.md, for Windsurf.
It sets vueCompilerOptions.checkUnknownComponents: true in your tsconfig, so
a mistyped or removed component is a type error (in Nuxt it tells you where to
set it instead).
It registers the MCP server in .mcp.json, .cursor/mcp.json and
.vscode/mcp.json. And it installs the server's two dependencies,
@modelcontextprotocol/sdk and zod, as dev dependencies, using the package
manager your lockfile names.
Every write is idempotent and merges rather than overwrites: your own lines in
AGENTS.md and the other MCP servers you have configured survive. Some starters
(Laravel's vue starter kit) git-ignore AGENTS.md, CLAUDE.md, .claude,
.cursor and .mcp.json; un-ignore them, or your teammates never get the pointers. Re-run it
after every upgrade, so the pointers name the version you have. --update is an
alias that reads better in a script:
npx bitboss-ui ai-init --update
To write the pointers only, with no MCP config and no install, opt out:
npx bitboss-ui ai-init --no-mcp
What the agent reads
The knowledge base lives at node_modules/bitboss-ui/dist/ai/, exported as
bitboss-ui/ai. It is a hop map, not one large document. The agent opens the router, then
one contract or recipe, and loads a few kilobytes instead of the whole
catalogue.
The route an agent takes:
guides/ai-router.md: start here. Which file to open next, in what order.guides/agent-contract.md: the hard rules, before any code is written.guides/component-picker.md: which component fits a need, and the markup that means one already exists.
One contract per thing:
<Component>.md: one per component, with props, events, slots,v-modelandptparts.composables/<name>.md: one per composable, such asuseToast,useConfirmoruseBbFormField.source/<Component>.md: the component's full implementation (template, types, CSS). Only for rebuilding chrome the library does not ship.
Data for scripts and tools:
components.json: the machine-readable API surface of every component, plus the public types.design-tokens.json: the--bb-*tokens as data, covering colours, radius, type and spacing.changelog.json: every breaking change since v2, with the version that shipped it.
Playbooks and the rest:
guides/*.md: cross-cutting playbooks on coherence, options and items, forms, passthrough.recipes/{vue,nuxt,inertia}/: whole-page shapes, one file per platform.guides/migration/: the v2 upgrade, the class renames between 3.0 betas, andv3-beta-tooling.mdfor scripts that parse these files.index.md: the full catalogue. A last resort, not a first stop.
If the agent can only fetch URLs, the same knowledge base is hosted. Give it one of these:
https://llms-bitboss-ui.bitboss.it/dist/llms-medium.txt: about 260 KB, the rules, setup, component picker, design language and catalogue in one request. Fetch this one if the agent takes only one.https://llms-bitboss-ui.bitboss.it/llms.txt: a 27 KB index of every document.https://llms-bitboss-ui.bitboss.it/dist/llms-full.txt: everything concatenated, about 6 MB, for bulk ingestion only.
Installed, the three files resolve as bitboss-ui/llms.txt,
bitboss-ui/llms-medium.txt and bitboss-ui/llms-full.txt.
components.json is not a side artifact. Every API table on this site is
generated from it. The guides around the tables are written by hand, but the
surface you read here comes from the file an agent reads.
Checking the markup
The check validates Bb* markup in your .vue and .md files against the
installed manifest, not against a copy of the rules. You are checked against the
version you actually have:
# every .vue and .md below this directory
npx bitboss-ui check
# one directory; prefer it to a quoted glob
npx bitboss-ui check src
# only the files git reports changed: the fast loop after an edit
npx bitboss-ui check --changed
# findings as JSON, for a CI annotation step
npx bitboss-ui check --json
# also list what the app still builds by hand (report-only)
npx bitboss-ui check --idioms
A finding names the file, the line and the fix:
[bitboss-ui] check FAILED — 2 finding(s) across 1 file(s):
src/CustomerForm.vue
✗ line 6: prop `color` is not in BbButton's API
✗ line 7: prop `show-close` was removed from BbDialog; use `hideClose`
hint: ai/guides/migration/components/bb-dialog.md
It exits non-zero on findings, which makes it a CI step. It also exits non-zero when an explicit path matches nothing, so a typo in a CI path cannot validate zero files and pass. What it reports, grouped:
| Area | Findings |
|---|---|
| Markup | unknown or removed props, v-model targets, events and slots; a variant from another library (unknown-variant: danger on BbAlert); href or method on a component that ignores them |
| Accessibility | an icon-only BbButton or BbBadge, or a BbProgress, with no accessible name (nameless-control) |
| Items | object items with neither a label nor a text key and no item-text (object-items-without-text); an item-text / item-value that restates the default (redundant-item-accessor, a warning); a dropdown, tab or breadcrumb item still spelled key / text (renamed-item-field); a dropdown row in a selectable group with no value (missing-value) or a selectable group with no id (missing-id); a BbTable column keyed like one of its own slots (reserved-key) |
| Forms | v-model on a BbForm (form-v-model: it takes :model); two fields on one path, a row in a v-for with no name, rules a schema will ignore, a string rule nothing registers |
| Integration | a Bb* tag the file never imports, which renders empty (unimported-component); a trigger in a BbDropdown's default slot, which opens nothing (dropdown-default-slot); a BbCollapsible toggle with no aria-expanded (collapsible-trigger-aria); a v-model on a reactive() const (reactive-v-model); a portal name nothing declares (unknown-portal); a v-bb-dialog / v-bb-off-canvas or context id no dialog declares (unknown-dialog) |
| Your components | a wrapper that declares Bb*Props and never passes some of them on (wrapper-drops-props): a caller's disabled or pt goes nowhere; Preset components shows the fix |
| Stylesheets | a selector on a .bb-* class the library no longer renders (unknown-class, renamed-class), a token or local nothing reads (inert-token, inert-local), a hand-imported styles.css |
| Imports | the retired bitboss-ui/validated entrypoint, an import from inside dist/, a Pt* type the package does not export |
npx bitboss-ui check --fix applies the automatic migrations to your .vue
files first (renamed props, slots and pt keys, inverted booleans, retired
imports), then reports what is left for you. It needs eslint and vue-eslint-parser installed. The
check respects eslint-disable comments, so a finding you silenced on purpose
does not fail CI.
Around an upgrade, judge your code against the new version before you install it, then list what broke since the version you were on:
# before installing: which of your files the new version breaks
npm pack bitboss-ui@beta && tar -xzf bitboss-ui-*.tgz
npx bitboss-ui check --manifest package/dist/ai/components.json
# after installing: every break since the version you had
# (the one npm ls bitboss-ui printed before the upgrade)
npx bitboss-ui changelog --since <old-version>
Do not name your own components Bb…; use your app's prefix. If you already
have one, the check leaves it alone when it can see it is yours (imported from a
local file, or a .vue in your components/ directories) and prints a single
local-bb-component warning. allowComponents in package.json is only for a
component registered globally, where no import shows:
{
"bitboss-ui": { "allowComponents": ["BbRichEditor"] }
}
The MCP server
The MCP server answers the same questions as the files in dist/ai/, as tool
calls: an agent asks for one component or one list of changes instead of
opening files.
| Tool | Answers |
|---|---|
search_docs | "How do I…": the guide or component section that answers it, old names included |
search_components, get_component | The catalogue, then one component: every prop, event, slot and pt part. Also resolves type names and renamed v2 or beta names |
get_guide, list_guides, get_composable | A playbook or a composable's contract |
list_recipes, get_recipe | Whole-page shapes for Vue, Nuxt and Inertia |
list_tokens | The design tokens and their values |
list_changes | The breaking changes between two versions, class renames included |
validate | The markup check, on a snippet |
You never start the server yourself. ai-init registers it, and the harness
launches it on demand. Restart the harness once after the first run so it picks
the server up. Each harness gets an entry in its own format:
| File | Harness |
|---|---|
.mcp.json | Claude Code |
.cursor/mcp.json | Cursor |
.vscode/mcp.json | VS Code and Copilot |
The entry runs the installed binary, node node_modules/bitboss-ui/bin/bitboss-ui.mjs mcp,
not npx. That way it cannot fetch anything from the registry and always answers
from the version in your node_modules. The path is relative to the project, so
commit these files and the server works for your teammates too:
{
"mcpServers": {
"bitboss-ui": {
"command": "node",
"args": ["node_modules/bitboss-ui/bin/bitboss-ui.mjs", "mcp"]
}
}
}
Three things to know:
- Windsurf is not registered: it reads one global config in your home
directory, and a project command should not edit that. Add the entry to
~/.codeium/windsurf/mcp_config.jsonyourself, with the absolute path to this project'snode_modules/bitboss-ui/bin/bitboss-ui.mjs.ai-initstill writes the.windsurf/rulespointer. - The two dependencies are optional peers.
@modelcontextprotocol/sdkandzodweigh about 12 MB, so installingbitboss-uinever pulls them in.ai-initinstalls them; if that fails, it prints the command and writes the config files anyway. - You can skip the server and lose no knowledge. It only reads
node_modules/bitboss-ui/dist/ai/, andnpx bitboss-ui checkruns without the peers. If the project cannot take new dependencies, runai-init --no-mcpand point the agent atdist/ai/guides/ai-router.md.
In a Vite or Nuxt app, the plugin can register the server instead: pass
mcp: true and the dev server writes the same three files on boot. It never
installs anything, so run ai-init once for the peers. See
Installation for the option.
bitbossUi({ mcp: true });
Catching it in the editor
The library ships an ESLint plugin, so the same manifest checks run while typing
rather than at review. The recommended preset is an array: spread it. It parses
<script setup lang="ts"> once @typescript-eslint/parser is installed.
npm i -D @typescript-eslint/parser
import vue from 'eslint-plugin-vue';
import tsParser from '@typescript-eslint/parser';
import bitbossUi from 'bitboss-ui/eslint-plugin';
export default [
...vue.configs['flat/recommended'],
{ files: ['**/*.ts'], languageOptions: { parser: tsParser } },
{
files: ['**/*.vue'],
languageOptions: { parserOptions: { parser: tsParser } },
},
...bitbossUi.configs.recommended,
];
Without the TypeScript parser, every .vue file with a TS script block fails to
parse, and no rule runs on it at all. eslint --fix applies the registered v2
renames and polarity flips; a "did you mean" hint is never applied for you.
Beyond unknown props, slots and events, recommended flags the mistakes that
render without complaint:
- an icon-only button with no accessible name (
require-accessible-name) methodon aBbButtonwith nohref(require-partner-prop)- a string rule in
rulesthat nothing registers (no-unregistered-rule) - a
v-modelon aBbForm(form-model) - a trigger in a
BbDropdown's default slot (dropdown-default-slot) - a
Bb*tag the file never imports (no-unimported-component) - a
BbCollapsibletoggle with noaria-expanded(collapsible-trigger-aria) - a
v-modelon areactive()const (no-reactive-v-model) - a portal name, or a dialog id, nothing declares (
portal-names) - an import from
bitboss-ui/validated(no-retired-entrypoint) - a
var(--bb-…)token the library does not declare (no-unknown-token, a warning) - a v2
:reverse="false"(no-reverse-false) - a dropdown, tab or breadcrumb item still spelled
key/text(no-renamed-item-field, which rewrites it) - an
item-text/item-valuethat restates the default (no-redundant-item-accessor, a warning it fixes)
Keep explicit imports. With checkUnknownComponents (ai-init sets it), a
mistyped or removed component becomes a type error instead of an empty space on
the page.
The shortest reliable setup is ai-init, the recommended ESLint config and
npx bitboss-ui check in CI. Add MCP when the agent can use tools; keep the
file-based knowledge pointers either way, because they remain available to every
agent and in offline work.