bestax-mcp 0.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +90 -0
- package/data/catalog.json +1869 -0
- package/data/components/Autocomplete.json +604 -0
- package/data/components/Avatar.json +220 -0
- package/data/components/Avatars.json +294 -0
- package/data/components/Badge.json +261 -0
- package/data/components/Block.json +105 -0
- package/data/components/Box.json +157 -0
- package/data/components/Breadcrumb.json +155 -0
- package/data/components/Button.json +599 -0
- package/data/components/Buttons.json +533 -0
- package/data/components/Card.json +506 -0
- package/data/components/Carousel.json +512 -0
- package/data/components/Cell.json +154 -0
- package/data/components/Checkbox.json +176 -0
- package/data/components/Checkboxes.json +240 -0
- package/data/components/Code.json +128 -0
- package/data/components/Collapse.json +218 -0
- package/data/components/Column.json +284 -0
- package/data/components/Columns.json +542 -0
- package/data/components/ConfigProvider.json +16 -0
- package/data/components/Container.json +149 -0
- package/data/components/Content.json +228 -0
- package/data/components/Control.json +484 -0
- package/data/components/DateInput.json +872 -0
- package/data/components/DateTimeInput.json +952 -0
- package/data/components/Delete.json +153 -0
- package/data/components/Dialog.json +285 -0
- package/data/components/Divider.json +68 -0
- package/data/components/Dropdown.json +353 -0
- package/data/components/Emphasis.json +99 -0
- package/data/components/Field.json +539 -0
- package/data/components/Figure.json +143 -0
- package/data/components/File.json +403 -0
- package/data/components/Footer.json +109 -0
- package/data/components/Grid.json +357 -0
- package/data/components/Hero.json +340 -0
- package/data/components/Icon.json +232 -0
- package/data/components/IconText.json +307 -0
- package/data/components/Image.json +166 -0
- package/data/components/Input.json +583 -0
- package/data/components/Level.json +332 -0
- package/data/components/Link.json +154 -0
- package/data/components/LinkButton.json +247 -0
- package/data/components/ListItem.json +101 -0
- package/data/components/Loading.json +323 -0
- package/data/components/Media.json +345 -0
- package/data/components/Menu.json +317 -0
- package/data/components/Message.json +303 -0
- package/data/components/Modal.json +444 -0
- package/data/components/Navbar.json +996 -0
- package/data/components/Notification.json +165 -0
- package/data/components/Numberinput.json +364 -0
- package/data/components/OrderedList.json +189 -0
- package/data/components/Pagination.json +619 -0
- package/data/components/Panel.json +582 -0
- package/data/components/Paragraph.json +107 -0
- package/data/components/Pre.json +114 -0
- package/data/components/Progress.json +137 -0
- package/data/components/Radio.json +164 -0
- package/data/components/Radios.json +240 -0
- package/data/components/Rate.json +466 -0
- package/data/components/Reveal.json +177 -0
- package/data/components/Section.json +123 -0
- package/data/components/Select.json +526 -0
- package/data/components/Sidebar.json +488 -0
- package/data/components/Skeleton.json +162 -0
- package/data/components/Slider.json +534 -0
- package/data/components/Span.json +100 -0
- package/data/components/Steps.json +452 -0
- package/data/components/Strong.json +105 -0
- package/data/components/SubTitle.json +233 -0
- package/data/components/Switch.json +291 -0
- package/data/components/Table.json +490 -0
- package/data/components/Tabs.json +592 -0
- package/data/components/Tag.json +197 -0
- package/data/components/Taginput.json +587 -0
- package/data/components/Tags.json +181 -0
- package/data/components/TextArea.json +483 -0
- package/data/components/Theme.json +65 -0
- package/data/components/TimeInput.json +906 -0
- package/data/components/Title.json +240 -0
- package/data/components/Toast.json +308 -0
- package/data/components/Tooltip.json +212 -0
- package/data/components/UnorderedList.json +149 -0
- package/data/components/Valid value constants.json +16 -0
- package/data/components/classNames.json +65 -0
- package/data/components/useBulmaClasses.json +169 -0
- package/data/components/usePrefixedClassNames.json +25 -0
- package/data/skills/bestax-custom-component/SKILL.md +196 -0
- package/data/skills/bestax-custom-component/examples/stat-card.tsx +105 -0
- package/data/skills/bestax-custom-component/references/api.md +90 -0
- package/data/skills/bestax-custom-component/references/component-catalog.md +149 -0
- package/data/skills/bestax-custom-component/references/library-contributor.md +395 -0
- package/data/skills/bestax-custom-component/references/patterns.md +136 -0
- package/data/skills/bestax-form/SKILL.md +254 -0
- package/data/skills/bestax-form/references/api.md +103 -0
- package/data/skills/bestax-form/references/patterns.md +210 -0
- package/data/skills/bestax-icons/SKILL.md +80 -0
- package/data/skills/bestax-icons/examples/icon-usage.tsx +80 -0
- package/data/skills/bestax-icons/references/icon-libraries.md +131 -0
- package/data/skills/bestax-layout-scaffold/SKILL.md +196 -0
- package/data/skills/bestax-layout-scaffold/examples/app-shell.tsx +86 -0
- package/data/skills/bestax-layout-scaffold/examples/card-grid.tsx +106 -0
- package/data/skills/bestax-layout-scaffold/examples/centered.tsx +56 -0
- package/data/skills/bestax-layout-scaffold/examples/content-page.tsx +90 -0
- package/data/skills/bestax-layout-scaffold/examples/landing.tsx +122 -0
- package/data/skills/bestax-layout-scaffold/references/archetypes.md +246 -0
- package/data/skills/bestax-layout-scaffold/references/layout-components.md +269 -0
- package/data/skills/bestax-migrate/SKILL.md +78 -0
- package/data/skills/bestax-migrate/references/component-map.md +91 -0
- package/data/skills/bestax-migrate/references/css-migration.md +100 -0
- package/data/skills/bestax-migrate/references/prop-map.md +48 -0
- package/data/skills/bestax-migrate/references/unmappables.md +124 -0
- package/data/skills/bestax-optimize/SKILL.md +131 -0
- package/data/skills/bestax-optimize/references/modular-build.md +173 -0
- package/data/skills/bestax-theming/SKILL.md +117 -0
- package/data/skills/bestax-theming/examples/dark-mode.tsx +38 -0
- package/data/skills/bestax-theming/examples/theme-config.tsx +60 -0
- package/data/skills/bestax-theming/references/css-variables.md +466 -0
- package/data/skills/bestax-theming/references/themeable-components.md +119 -0
- package/data/skills.json +148 -0
- package/dist/data.d.ts +131 -0
- package/dist/data.d.ts.map +1 -0
- package/dist/data.js +111 -0
- package/dist/format.d.ts +37 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +178 -0
- package/dist/helper-props.d.ts +57 -0
- package/dist/helper-props.d.ts.map +1 -0
- package/dist/helper-props.js +249 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +18 -0
- package/dist/search.d.ts +32 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +194 -0
- package/dist/server.d.ts +28 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +616 -0
- package/dist/version.d.ts +29 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +147 -0
- package/package.json +70 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"helper-props.d.ts","sourceRoot":"","sources":["../src/helper-props.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAEH;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CA6B/C;AAED,MAAM,WAAW,WAAW;IAC1B,qEAAqE;IACrE,QAAQ,EAAE,MAAM,EAAE,CAAC;IACnB,yEAAyE;IACzE,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,2EAA2E;IAC3E,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;GAGG;AACH,eAAO,MAAM,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,CAqGrD,CAAC;AAEF,eAAO,MAAM,WAAW,UAA6B,CAAC;AAEtD,8EAA8E;AAC9E,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAezD;AAKD,uFAAuF;AACvF,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,EAAE,GAAG,MAAM,CAe/D;AAMD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CASvD;AAED,6EAA6E;AAC7E,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAgBnE"}
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shaping the helper-prop reference so asking one question does not cost the whole page.
|
|
3
|
+
*
|
|
4
|
+
* `get_helper_props` was 54,886 characters — 49% of everything a cold-start builder read in
|
|
5
|
+
* a measured session, on the tool this server's own `instructions` tell every client to call
|
|
6
|
+
* before writing styling. Its `group` argument was supposed to prevent that and did not: the
|
|
7
|
+
* filter substring-matched whole section BODIES, and three sections (`Supported Props`,
|
|
8
|
+
* `Viewport-Specific Properties`, `Full TypeScript Definition`) name every helper keyword, so
|
|
9
|
+
* `group: "text"` matched 21 of 41 sections — 87% of the page.
|
|
10
|
+
*
|
|
11
|
+
* Matching headings instead is not the fix either; it is a regression. `spacing` and
|
|
12
|
+
* `typography` — two of the three groups the tool's own description advertises — match no
|
|
13
|
+
* heading at all and would answer nothing.
|
|
14
|
+
*
|
|
15
|
+
* So the groups are curated, and each one carries its own slice of the prop table. The
|
|
16
|
+
* governing rule is that **no call ever loses a prop name**: the default lists all 46 with
|
|
17
|
+
* their accepted values, because a builder has to be able to look up a valid value in one
|
|
18
|
+
* call. What moves behind `group` is the prose and the live examples, which are re-fetchable
|
|
19
|
+
* and which nothing breaks without.
|
|
20
|
+
*/
|
|
21
|
+
/**
|
|
22
|
+
* Markdown tables in the generated index are column-padded for human reading. That padding
|
|
23
|
+
* is 32% of this document — 16,352 characters, of which `Supported Props` alone contributes
|
|
24
|
+
* 12,455. Stripping it is free: no model reads alignment, and the cell contents are
|
|
25
|
+
* untouched.
|
|
26
|
+
*/
|
|
27
|
+
export function reflowTables(md) {
|
|
28
|
+
// Fenced code is left exactly as written. Today's document happens to contain no table
|
|
29
|
+
// inside a fence, so this changes nothing — but "pure whitespace, nothing lost" has to be
|
|
30
|
+
// a property of the function rather than of the content it currently gets handed, and a
|
|
31
|
+
// docs example that renders a markdown table is an ordinary thing to add.
|
|
32
|
+
let inFence = false;
|
|
33
|
+
return md
|
|
34
|
+
.split('\n')
|
|
35
|
+
.map(line => {
|
|
36
|
+
const t = line.trim();
|
|
37
|
+
if (t.startsWith('```') || t.startsWith('~~~')) {
|
|
38
|
+
inFence = !inFence;
|
|
39
|
+
return line;
|
|
40
|
+
}
|
|
41
|
+
if (inFence)
|
|
42
|
+
return line;
|
|
43
|
+
if (!t.startsWith('|') || !t.endsWith('|'))
|
|
44
|
+
return line;
|
|
45
|
+
// Split on unescaped pipes only. A `\|` inside a cell is markdown's escape for a
|
|
46
|
+
// literal pipe — the natural way to write a value union like `` `a` \| `b` `` — and
|
|
47
|
+
// splitting on it would invent a column, which is exactly the information loss this
|
|
48
|
+
// function promises not to cause. The index has no escaped pipes today; the guarantee
|
|
49
|
+
// has to hold for the document, not for today's contents.
|
|
50
|
+
const cells = t.slice(1, -1).split(/(?<!\\)\|/);
|
|
51
|
+
// A separator row (`| --- | --- |`) collapses to the shortest legal form.
|
|
52
|
+
if (cells.every(c => /^\s*:?-+:?\s*$/.test(c))) {
|
|
53
|
+
return `|${cells.map(c => (c.includes(':') ? c.trim() : '---')).join('|')}|`;
|
|
54
|
+
}
|
|
55
|
+
return `|${cells.map(c => ` ${c.trim()} `).join('|')}|`;
|
|
56
|
+
})
|
|
57
|
+
.join('\n');
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Every prop in the table belongs to exactly one group — checked by a test, because a prop
|
|
61
|
+
* that falls through the map is a prop a `group` call can never surface.
|
|
62
|
+
*/
|
|
63
|
+
export const HELPER_GROUPS = {
|
|
64
|
+
spacing: {
|
|
65
|
+
headings: ['### Margin'],
|
|
66
|
+
props: [
|
|
67
|
+
'm',
|
|
68
|
+
'mt',
|
|
69
|
+
'mr',
|
|
70
|
+
'mb',
|
|
71
|
+
'ml',
|
|
72
|
+
'mx',
|
|
73
|
+
'my',
|
|
74
|
+
'p',
|
|
75
|
+
'pt',
|
|
76
|
+
'pr',
|
|
77
|
+
'pb',
|
|
78
|
+
'pl',
|
|
79
|
+
'px',
|
|
80
|
+
'py',
|
|
81
|
+
],
|
|
82
|
+
aliases: ['margin', 'padding', 'gap', 'space'],
|
|
83
|
+
},
|
|
84
|
+
color: {
|
|
85
|
+
headings: [
|
|
86
|
+
'### Colors',
|
|
87
|
+
'### Background Color',
|
|
88
|
+
'### Color Shade',
|
|
89
|
+
'### Background Color Shade',
|
|
90
|
+
],
|
|
91
|
+
props: ['color', 'backgroundColor', 'colorShade', 'backgroundColorShade'],
|
|
92
|
+
aliases: ['colour', 'background', 'bgcolor', 'textcolor', 'shade'],
|
|
93
|
+
},
|
|
94
|
+
typography: {
|
|
95
|
+
headings: [
|
|
96
|
+
'### Text Size',
|
|
97
|
+
'### Text Align',
|
|
98
|
+
'### Text Transform',
|
|
99
|
+
'### Text Weight',
|
|
100
|
+
'### Font Family',
|
|
101
|
+
],
|
|
102
|
+
props: [
|
|
103
|
+
'textSize',
|
|
104
|
+
'textAlign',
|
|
105
|
+
'textTransform',
|
|
106
|
+
'textWeight',
|
|
107
|
+
'fontFamily',
|
|
108
|
+
],
|
|
109
|
+
aliases: ['text', 'font', 'weight', 'align', 'size', 'uppercase', 'italic'],
|
|
110
|
+
},
|
|
111
|
+
flex: {
|
|
112
|
+
headings: ['### With Flexbox Helpers'],
|
|
113
|
+
props: [
|
|
114
|
+
'flexDirection',
|
|
115
|
+
'flexWrap',
|
|
116
|
+
'justifyContent',
|
|
117
|
+
'alignContent',
|
|
118
|
+
'alignItems',
|
|
119
|
+
'alignSelf',
|
|
120
|
+
'flexGrow',
|
|
121
|
+
'flexShrink',
|
|
122
|
+
],
|
|
123
|
+
aliases: ['flexbox', 'justify', 'row', 'column'],
|
|
124
|
+
},
|
|
125
|
+
layout: {
|
|
126
|
+
headings: [
|
|
127
|
+
'### Visibility',
|
|
128
|
+
'### Overflow',
|
|
129
|
+
'### Clearfix',
|
|
130
|
+
'### Relative Position',
|
|
131
|
+
],
|
|
132
|
+
props: [
|
|
133
|
+
'display',
|
|
134
|
+
'visibility',
|
|
135
|
+
'float',
|
|
136
|
+
'overflow',
|
|
137
|
+
'clearfix',
|
|
138
|
+
'relative',
|
|
139
|
+
'fullHeight',
|
|
140
|
+
],
|
|
141
|
+
aliases: ['hidden', 'show', 'hide', 'block', 'inline', 'position'],
|
|
142
|
+
},
|
|
143
|
+
interaction: {
|
|
144
|
+
headings: [
|
|
145
|
+
'### Interaction',
|
|
146
|
+
'### Cursor',
|
|
147
|
+
'### Overlay',
|
|
148
|
+
'### Skeleton Examples',
|
|
149
|
+
],
|
|
150
|
+
props: ['interaction', 'cursor', 'overlay', 'skeleton', 'radius', 'shadow'],
|
|
151
|
+
aliases: ['hover', 'click', 'loading', 'rounded'],
|
|
152
|
+
},
|
|
153
|
+
responsive: {
|
|
154
|
+
headings: [
|
|
155
|
+
'### Responsive Props',
|
|
156
|
+
'### Global Viewport Property (Legacy)',
|
|
157
|
+
'### Viewport-Specific Properties (Recommended)',
|
|
158
|
+
'### Properties That Support Viewport Modifiers',
|
|
159
|
+
'### Breakpoint Reference',
|
|
160
|
+
],
|
|
161
|
+
props: ['responsive', 'viewport'],
|
|
162
|
+
aliases: ['mobile', 'tablet', 'desktop', 'breakpoint', 'widescreen'],
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
export const GROUP_NAMES = Object.keys(HELPER_GROUPS);
|
|
166
|
+
/** A builder's word → a group name, or null when nothing sensible matches. */
|
|
167
|
+
export function resolveGroup(input) {
|
|
168
|
+
const q = input.trim().toLowerCase();
|
|
169
|
+
if (!q)
|
|
170
|
+
return null;
|
|
171
|
+
if (HELPER_GROUPS[q])
|
|
172
|
+
return q;
|
|
173
|
+
for (const [name, g] of Object.entries(HELPER_GROUPS)) {
|
|
174
|
+
if (g.aliases.some(a => a === q))
|
|
175
|
+
return name;
|
|
176
|
+
// A prop name is the most precise thing a builder can ask for: `mt` → spacing.
|
|
177
|
+
if (g.props.some(p => p.toLowerCase() === q))
|
|
178
|
+
return name;
|
|
179
|
+
}
|
|
180
|
+
// Last resort, substring against names and aliases, so "colors" and "flexbox layout" land.
|
|
181
|
+
for (const [name, g] of Object.entries(HELPER_GROUPS)) {
|
|
182
|
+
if (name.includes(q) || q.includes(name))
|
|
183
|
+
return name;
|
|
184
|
+
if (g.aliases.some(a => q.includes(a)))
|
|
185
|
+
return name;
|
|
186
|
+
}
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
const sections = (doc) => doc.split(/\n(?=#{2,3} )/);
|
|
190
|
+
const headingOf = (chunk) => chunk.split('\n')[0].trim();
|
|
191
|
+
/** The `## Supported Props` table, reflowed, header rows plus the rows for `props`. */
|
|
192
|
+
export function propTable(doc, props) {
|
|
193
|
+
const chunk = sections(doc).find(c => headingOf(c) === '## Supported Props');
|
|
194
|
+
if (!chunk)
|
|
195
|
+
return '';
|
|
196
|
+
const lines = reflowTables(chunk).split('\n');
|
|
197
|
+
const rows = lines.filter(l => l.trim().startsWith('|'));
|
|
198
|
+
if (rows.length < 3)
|
|
199
|
+
return '';
|
|
200
|
+
const [header, sep, ...body] = rows;
|
|
201
|
+
const wanted = props
|
|
202
|
+
? body.filter(r => {
|
|
203
|
+
const m = r.match(/^\s*\|\s*`?([A-Za-z][A-Za-z0-9]*)`?\s*\|/);
|
|
204
|
+
return m ? props.includes(m[1]) : false;
|
|
205
|
+
})
|
|
206
|
+
: body;
|
|
207
|
+
if (!wanted.length)
|
|
208
|
+
return '';
|
|
209
|
+
return [header, sep, ...wanted].join('\n');
|
|
210
|
+
}
|
|
211
|
+
const GROUP_INDEX = `Narrow with \`get_helper_props({ group })\` — ` +
|
|
212
|
+
`${GROUP_NAMES.join(' · ')} — for the examples and the prose behind any row above.`;
|
|
213
|
+
/**
|
|
214
|
+
* The no-argument answer: every prop and every accepted value, and nothing else.
|
|
215
|
+
*
|
|
216
|
+
* Deliberately excluded, and why each is safe to drop: `Full TypeScript Definition` (5,143)
|
|
217
|
+
* is a lossless restatement of the table above it; `Composable Mini-Hooks` (4,792) is
|
|
218
|
+
* material for writing a component, which is `bestax-custom-component`'s job; the twenty-odd
|
|
219
|
+
* live example blocks are reachable through `group`.
|
|
220
|
+
*/
|
|
221
|
+
export function renderHelperDefault(doc) {
|
|
222
|
+
const keep = ['## Overview', '## Import'];
|
|
223
|
+
const intro = sections(doc)
|
|
224
|
+
.filter(c => keep.includes(headingOf(c)))
|
|
225
|
+
.map(c => reflowTables(c).trim());
|
|
226
|
+
const table = propTable(doc);
|
|
227
|
+
return ['# Helper props', ...intro, '## Supported props', table, GROUP_INDEX]
|
|
228
|
+
.filter(Boolean)
|
|
229
|
+
.join('\n\n');
|
|
230
|
+
}
|
|
231
|
+
/** One group: its example sections, then just its rows of the prop table. */
|
|
232
|
+
export function renderHelperGroup(doc, name) {
|
|
233
|
+
const group = HELPER_GROUPS[name];
|
|
234
|
+
if (!group)
|
|
235
|
+
return '';
|
|
236
|
+
const bodies = sections(doc)
|
|
237
|
+
.filter(c => group.headings.includes(headingOf(c)))
|
|
238
|
+
.map(c => reflowTables(c).trim());
|
|
239
|
+
const table = propTable(doc, group.props);
|
|
240
|
+
return [
|
|
241
|
+
`# Helper props — ${name}`,
|
|
242
|
+
table && `## Props\n\n${table}`,
|
|
243
|
+
...bodies,
|
|
244
|
+
`Other groups: ${GROUP_NAMES.filter(g => g !== name).join(' · ')}. ` +
|
|
245
|
+
`Call \`get_helper_props\` with no argument for every prop at once.`,
|
|
246
|
+
]
|
|
247
|
+
.filter(Boolean)
|
|
248
|
+
.join('\n\n');
|
|
249
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* stdio entry point.
|
|
4
|
+
*
|
|
5
|
+
* Nothing may be written to stdout except JSON-RPC frames — stdout IS the
|
|
6
|
+
* transport, and a stray console.log corrupts the stream in a way that
|
|
7
|
+
* surfaces to the user as an unexplained disconnect. Diagnostics go to stderr.
|
|
8
|
+
*/
|
|
9
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
10
|
+
import { createServer } from './server.js';
|
|
11
|
+
async function main() {
|
|
12
|
+
const server = await createServer();
|
|
13
|
+
await server.connect(new StdioServerTransport());
|
|
14
|
+
}
|
|
15
|
+
main().catch((err) => {
|
|
16
|
+
process.stderr.write(`bestax-mcp: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
});
|
package/dist/search.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ranking for `search_bestax`, the entry point a model reaches for first.
|
|
3
|
+
*
|
|
4
|
+
* Deliberately a small scorer over the catalog rather than an index: the corpus
|
|
5
|
+
* is 87 components, ~1500 props and ~900 examples, all already in memory, and
|
|
6
|
+
* anything cleverer would need a build step this package does not have.
|
|
7
|
+
*
|
|
8
|
+
* What matters more than the algorithm is that every hit names the tool to call
|
|
9
|
+
* next. A search result a model cannot act on costs a round trip to rediscover
|
|
10
|
+
* what it just found.
|
|
11
|
+
*/
|
|
12
|
+
import type { Catalog, ComponentRecord, Skill } from './data.js';
|
|
13
|
+
export type HitKind = 'component' | 'prop' | 'example' | 'css-var' | 'skill';
|
|
14
|
+
export interface Hit {
|
|
15
|
+
kind: HitKind;
|
|
16
|
+
name: string;
|
|
17
|
+
detail: string;
|
|
18
|
+
/** The tool call that returns the full thing. */
|
|
19
|
+
next: string;
|
|
20
|
+
score: number;
|
|
21
|
+
}
|
|
22
|
+
export declare function searchAll(query: string, catalog: Catalog, components: ComponentRecord[], skills: Skill[], kinds: HitKind[]): Hit[];
|
|
23
|
+
/**
|
|
24
|
+
* Near misses for a name that did not resolve.
|
|
25
|
+
*
|
|
26
|
+
* Real edit distance rather than a prefix heuristic, because the typos that
|
|
27
|
+
* actually happen are a dropped or transposed letter — and a first-letter
|
|
28
|
+
* heuristic answered "Buton" with "Badge, Block, Box", which is worse than
|
|
29
|
+
* saying nothing.
|
|
30
|
+
*/
|
|
31
|
+
export declare function suggest(input: string, names: string[], limit?: number): string[];
|
|
32
|
+
//# sourceMappingURL=search.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,OAAO,KAAK,EAAE,OAAO,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,WAAW,CAAC;AAEjE,MAAM,MAAM,OAAO,GAAG,WAAW,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,CAAC;AAE7E,MAAM,WAAW,GAAG;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,iDAAiD;IACjD,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;CACf;AAyED,wBAAgB,SAAS,CACvB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,EAChB,UAAU,EAAE,eAAe,EAAE,EAC7B,MAAM,EAAE,KAAK,EAAE,EACf,KAAK,EAAE,OAAO,EAAE,GACf,GAAG,EAAE,CAoGP;AAoBD;;;;;;;GAOG;AACH,wBAAgB,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,SAAI,GAAG,MAAM,EAAE,CAY3E"}
|
package/dist/search.js
ADDED
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
const norm = (s) => s.toLowerCase();
|
|
2
|
+
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
3
|
+
/**
|
|
4
|
+
* A prefix of a term that survives common English inflection.
|
|
5
|
+
*
|
|
6
|
+
* Exact substring matching is too brittle here, and the miss is not
|
|
7
|
+
* hypothetical: "theme" does not occur in `bestax-theming`, whose own
|
|
8
|
+
* description says "colors", "dark mode" and "tokens" but never "theme". A user
|
|
9
|
+
* asking how to theme their app got nothing. Trimming two characters catches
|
|
10
|
+
* theme/theming, color/colors, migrate/migration and icon/icons without
|
|
11
|
+
* needing a stemmer.
|
|
12
|
+
*
|
|
13
|
+
* The floor of 4 matters: shorter stems ("the") match everything.
|
|
14
|
+
*/
|
|
15
|
+
const stem = (t) => t.slice(0, Math.max(4, t.length - 2));
|
|
16
|
+
/**
|
|
17
|
+
* Compiled word-initial matchers, memoised per stem.
|
|
18
|
+
*
|
|
19
|
+
* `termHit` runs once per term per haystack, and a search covers ~1,500 props and ~900
|
|
20
|
+
* examples — so compiling here meant thousands of identical `new RegExp` calls per query,
|
|
21
|
+
* which is where the measured 5.8 s at 3,000 terms went. The terms in one query are a tiny
|
|
22
|
+
* set; compiling each once turns that into a lookup.
|
|
23
|
+
*
|
|
24
|
+
* The cap exists because the cache outlives a request: a stdio session is long-lived, and
|
|
25
|
+
* every distinct query would otherwise pin a regex forever. Clearing wholesale rather than
|
|
26
|
+
* evicting LRU keeps this honest — the cache is an optimisation, not state anything depends
|
|
27
|
+
* on.
|
|
28
|
+
*/
|
|
29
|
+
const STEM_RE_CACHE = new Map();
|
|
30
|
+
const STEM_RE_CACHE_MAX = 500;
|
|
31
|
+
function wordInitialRe(stemmed) {
|
|
32
|
+
let re = STEM_RE_CACHE.get(stemmed);
|
|
33
|
+
if (!re) {
|
|
34
|
+
if (STEM_RE_CACHE.size >= STEM_RE_CACHE_MAX)
|
|
35
|
+
STEM_RE_CACHE.clear();
|
|
36
|
+
re = new RegExp(`\\b${escapeRe(stemmed)}`);
|
|
37
|
+
STEM_RE_CACHE.set(stemmed, re);
|
|
38
|
+
}
|
|
39
|
+
return re;
|
|
40
|
+
}
|
|
41
|
+
/** 2 for a substring hit, 1 for a word-initial stem hit, 0 for neither. */
|
|
42
|
+
function termHit(hay, term) {
|
|
43
|
+
if (hay.includes(term))
|
|
44
|
+
return 2;
|
|
45
|
+
const s = stem(term);
|
|
46
|
+
// Word-initial, so "them" finds "theming" but not "anthem".
|
|
47
|
+
return s !== term && wordInitialRe(s).test(hay) ? 1 : 0;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Score a haystack against the query terms.
|
|
51
|
+
*
|
|
52
|
+
* Whole-phrase and exact-name matches dominate; term coverage breaks ties. A
|
|
53
|
+
* miss on any term is not fatal — "button icon" should still find Button —
|
|
54
|
+
* but scoring coverage means the thing matching both wins.
|
|
55
|
+
*/
|
|
56
|
+
function score(haystack, name, terms, query) {
|
|
57
|
+
const hay = norm(haystack);
|
|
58
|
+
const lowerName = norm(name);
|
|
59
|
+
let s = 0;
|
|
60
|
+
if (lowerName === query)
|
|
61
|
+
s += 100;
|
|
62
|
+
else if (lowerName.startsWith(query))
|
|
63
|
+
s += 60;
|
|
64
|
+
else if (lowerName.includes(query))
|
|
65
|
+
s += 40;
|
|
66
|
+
else if (termHit(lowerName, query))
|
|
67
|
+
s += 25;
|
|
68
|
+
if (hay.includes(query))
|
|
69
|
+
s += 20;
|
|
70
|
+
for (const t of terms)
|
|
71
|
+
s += termHit(hay, t) * 5;
|
|
72
|
+
return s;
|
|
73
|
+
}
|
|
74
|
+
export function searchAll(query, catalog, components, skills, kinds) {
|
|
75
|
+
const q = norm(query.trim());
|
|
76
|
+
const terms = q.split(/\s+/).filter(Boolean);
|
|
77
|
+
if (!terms.length)
|
|
78
|
+
return [];
|
|
79
|
+
const hits = [];
|
|
80
|
+
const want = (k) => kinds.includes(k);
|
|
81
|
+
if (want('component')) {
|
|
82
|
+
for (const c of catalog.components) {
|
|
83
|
+
const s = score(`${c.name} ${c.purpose} ${c.category}`, c.name, terms, q);
|
|
84
|
+
if (s > 0) {
|
|
85
|
+
hits.push({
|
|
86
|
+
kind: 'component',
|
|
87
|
+
name: c.name,
|
|
88
|
+
detail: c.purpose,
|
|
89
|
+
// A helper has no prop table, and `get_component` on one is itself only a
|
|
90
|
+
// pointer at `get_helper_props` — so naming it here would make a search hit a
|
|
91
|
+
// three-hop trip to the answer.
|
|
92
|
+
next: c.kind === 'helper'
|
|
93
|
+
? 'get_helper_props()'
|
|
94
|
+
: `get_component({ name: "${c.name}" })`,
|
|
95
|
+
score: s,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
for (const record of components) {
|
|
101
|
+
if (want('prop')) {
|
|
102
|
+
for (const part of record.parts) {
|
|
103
|
+
for (const p of part.props) {
|
|
104
|
+
const s = score(`${p.name} ${p.description}`, p.name, terms, q);
|
|
105
|
+
if (s > 20) {
|
|
106
|
+
hits.push({
|
|
107
|
+
kind: 'prop',
|
|
108
|
+
name: `${part.path}.${p.name}`,
|
|
109
|
+
detail: `${p.type}${p.description ? ` — ${p.description}` : ''}`,
|
|
110
|
+
next: `get_props({ component: "${record.name}", path: "${part.path}" })`,
|
|
111
|
+
score: s,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (want('example')) {
|
|
118
|
+
for (const e of record.examples) {
|
|
119
|
+
const s = score(`${e.title} ${e.code}`, e.title, terms, q);
|
|
120
|
+
if (s > 20) {
|
|
121
|
+
hits.push({
|
|
122
|
+
kind: 'example',
|
|
123
|
+
name: `${record.name}: ${e.title}`,
|
|
124
|
+
detail: e.code.split('\n')[0].slice(0, 100),
|
|
125
|
+
next: `get_examples({ component: "${record.name}", query: "${e.title}" })`,
|
|
126
|
+
score: s,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
if (want('css-var')) {
|
|
133
|
+
for (const [cssVar, owner] of Object.entries(catalog.cssVarIndex)) {
|
|
134
|
+
const s = score(cssVar, cssVar, terms, q);
|
|
135
|
+
if (s > 0) {
|
|
136
|
+
hits.push({
|
|
137
|
+
kind: 'css-var',
|
|
138
|
+
name: cssVar,
|
|
139
|
+
detail: `declared by ${owner}`,
|
|
140
|
+
next: `get_css_variables({ component: "${owner}" })`,
|
|
141
|
+
score: s,
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
if (want('skill')) {
|
|
147
|
+
for (const skill of skills) {
|
|
148
|
+
const s = score(`${skill.name} ${skill.description}`, skill.name, terms, q);
|
|
149
|
+
if (s > 0) {
|
|
150
|
+
hits.push({
|
|
151
|
+
kind: 'skill',
|
|
152
|
+
name: skill.name,
|
|
153
|
+
detail: skill.description.slice(0, 160),
|
|
154
|
+
next: `get_skill({ name: "${skill.name}" })`,
|
|
155
|
+
score: s,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
return hits.sort((a, b) => b.score - a.score || (a.name < b.name ? -1 : a.name > b.name ? 1 : 0));
|
|
161
|
+
}
|
|
162
|
+
/** Edit distance, capped — the names are short and the corpus is 87 entries. */
|
|
163
|
+
function editDistance(a, b) {
|
|
164
|
+
if (a === b)
|
|
165
|
+
return 0;
|
|
166
|
+
let prev = Array.from({ length: b.length + 1 }, (_, i) => i);
|
|
167
|
+
for (let i = 1; i <= a.length; i++) {
|
|
168
|
+
const row = [i];
|
|
169
|
+
for (let j = 1; j <= b.length; j++) {
|
|
170
|
+
row[j] = Math.min(prev[j] + 1, row[j - 1] + 1, prev[j - 1] + (a[i - 1] === b[j - 1] ? 0 : 1));
|
|
171
|
+
}
|
|
172
|
+
prev = row;
|
|
173
|
+
}
|
|
174
|
+
return prev[b.length];
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Near misses for a name that did not resolve.
|
|
178
|
+
*
|
|
179
|
+
* Real edit distance rather than a prefix heuristic, because the typos that
|
|
180
|
+
* actually happen are a dropped or transposed letter — and a first-letter
|
|
181
|
+
* heuristic answered "Buton" with "Badge, Block, Box", which is worse than
|
|
182
|
+
* saying nothing.
|
|
183
|
+
*/
|
|
184
|
+
export function suggest(input, names, limit = 3) {
|
|
185
|
+
const q = norm(input);
|
|
186
|
+
return (names
|
|
187
|
+
.map(n => ({ n, d: editDistance(q, norm(n)) }))
|
|
188
|
+
// Scale with length so short names don't swallow every query, and long
|
|
189
|
+
// ones still tolerate a typo or two.
|
|
190
|
+
.filter(x => x.d <= Math.max(1, Math.floor(x.n.length / 3)))
|
|
191
|
+
.sort((a, b) => a.d - b.d || (a.n < b.n ? -1 : 1))
|
|
192
|
+
.slice(0, limit)
|
|
193
|
+
.map(x => x.n));
|
|
194
|
+
}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The bestax MCP server.
|
|
3
|
+
*
|
|
4
|
+
* Nine tools, four resource templates, and one prompt per Agent Skill — all
|
|
5
|
+
* built from the generated index in `data/`, so nothing here carries a list
|
|
6
|
+
* that can drift from the library.
|
|
7
|
+
*
|
|
8
|
+
* Two things shape the surface:
|
|
9
|
+
*
|
|
10
|
+
* 1. PROGRESSIVE DISCLOSURE. `search_bestax` and `list_components` are cheap
|
|
11
|
+
* and every result names the tool to call next; the expensive things (full
|
|
12
|
+
* prop tables, 24 usage examples, a 23 KB skill reference) are separate
|
|
13
|
+
* calls. A server that returns everything on the first call spends the
|
|
14
|
+
* context the client needs for the actual task.
|
|
15
|
+
*
|
|
16
|
+
* 2. MORE THAN A PROP LOOKUP. bestax is a component library with opinions —
|
|
17
|
+
* helper props instead of inline styles, `--bulma-*` overrides instead of
|
|
18
|
+
* hand-written CSS, Field/Control composition instead of bare inputs. Those
|
|
19
|
+
* live in the skills, so the skills ship as prompts and resources rather
|
|
20
|
+
* than being left to a separate install step.
|
|
21
|
+
*/
|
|
22
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
23
|
+
export interface CreateServerOptions {
|
|
24
|
+
/** Directory to resolve the consumer's installed library from. */
|
|
25
|
+
cwd?: string;
|
|
26
|
+
}
|
|
27
|
+
export declare function createServer(options?: CreateServerOptions): Promise<McpServer>;
|
|
28
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,OAAO,EACL,SAAS,EAEV,MAAM,yCAAyC,CAAC;AA6FjD,MAAM,WAAW,mBAAmB;IAClC,kEAAkE;IAClE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,wBAAsB,YAAY,CAChC,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,SAAS,CAAC,CAurBpB"}
|