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.
Files changed (144) hide show
  1. package/README.md +90 -0
  2. package/data/catalog.json +1869 -0
  3. package/data/components/Autocomplete.json +604 -0
  4. package/data/components/Avatar.json +220 -0
  5. package/data/components/Avatars.json +294 -0
  6. package/data/components/Badge.json +261 -0
  7. package/data/components/Block.json +105 -0
  8. package/data/components/Box.json +157 -0
  9. package/data/components/Breadcrumb.json +155 -0
  10. package/data/components/Button.json +599 -0
  11. package/data/components/Buttons.json +533 -0
  12. package/data/components/Card.json +506 -0
  13. package/data/components/Carousel.json +512 -0
  14. package/data/components/Cell.json +154 -0
  15. package/data/components/Checkbox.json +176 -0
  16. package/data/components/Checkboxes.json +240 -0
  17. package/data/components/Code.json +128 -0
  18. package/data/components/Collapse.json +218 -0
  19. package/data/components/Column.json +284 -0
  20. package/data/components/Columns.json +542 -0
  21. package/data/components/ConfigProvider.json +16 -0
  22. package/data/components/Container.json +149 -0
  23. package/data/components/Content.json +228 -0
  24. package/data/components/Control.json +484 -0
  25. package/data/components/DateInput.json +872 -0
  26. package/data/components/DateTimeInput.json +952 -0
  27. package/data/components/Delete.json +153 -0
  28. package/data/components/Dialog.json +285 -0
  29. package/data/components/Divider.json +68 -0
  30. package/data/components/Dropdown.json +353 -0
  31. package/data/components/Emphasis.json +99 -0
  32. package/data/components/Field.json +539 -0
  33. package/data/components/Figure.json +143 -0
  34. package/data/components/File.json +403 -0
  35. package/data/components/Footer.json +109 -0
  36. package/data/components/Grid.json +357 -0
  37. package/data/components/Hero.json +340 -0
  38. package/data/components/Icon.json +232 -0
  39. package/data/components/IconText.json +307 -0
  40. package/data/components/Image.json +166 -0
  41. package/data/components/Input.json +583 -0
  42. package/data/components/Level.json +332 -0
  43. package/data/components/Link.json +154 -0
  44. package/data/components/LinkButton.json +247 -0
  45. package/data/components/ListItem.json +101 -0
  46. package/data/components/Loading.json +323 -0
  47. package/data/components/Media.json +345 -0
  48. package/data/components/Menu.json +317 -0
  49. package/data/components/Message.json +303 -0
  50. package/data/components/Modal.json +444 -0
  51. package/data/components/Navbar.json +996 -0
  52. package/data/components/Notification.json +165 -0
  53. package/data/components/Numberinput.json +364 -0
  54. package/data/components/OrderedList.json +189 -0
  55. package/data/components/Pagination.json +619 -0
  56. package/data/components/Panel.json +582 -0
  57. package/data/components/Paragraph.json +107 -0
  58. package/data/components/Pre.json +114 -0
  59. package/data/components/Progress.json +137 -0
  60. package/data/components/Radio.json +164 -0
  61. package/data/components/Radios.json +240 -0
  62. package/data/components/Rate.json +466 -0
  63. package/data/components/Reveal.json +177 -0
  64. package/data/components/Section.json +123 -0
  65. package/data/components/Select.json +526 -0
  66. package/data/components/Sidebar.json +488 -0
  67. package/data/components/Skeleton.json +162 -0
  68. package/data/components/Slider.json +534 -0
  69. package/data/components/Span.json +100 -0
  70. package/data/components/Steps.json +452 -0
  71. package/data/components/Strong.json +105 -0
  72. package/data/components/SubTitle.json +233 -0
  73. package/data/components/Switch.json +291 -0
  74. package/data/components/Table.json +490 -0
  75. package/data/components/Tabs.json +592 -0
  76. package/data/components/Tag.json +197 -0
  77. package/data/components/Taginput.json +587 -0
  78. package/data/components/Tags.json +181 -0
  79. package/data/components/TextArea.json +483 -0
  80. package/data/components/Theme.json +65 -0
  81. package/data/components/TimeInput.json +906 -0
  82. package/data/components/Title.json +240 -0
  83. package/data/components/Toast.json +308 -0
  84. package/data/components/Tooltip.json +212 -0
  85. package/data/components/UnorderedList.json +149 -0
  86. package/data/components/Valid value constants.json +16 -0
  87. package/data/components/classNames.json +65 -0
  88. package/data/components/useBulmaClasses.json +169 -0
  89. package/data/components/usePrefixedClassNames.json +25 -0
  90. package/data/skills/bestax-custom-component/SKILL.md +196 -0
  91. package/data/skills/bestax-custom-component/examples/stat-card.tsx +105 -0
  92. package/data/skills/bestax-custom-component/references/api.md +90 -0
  93. package/data/skills/bestax-custom-component/references/component-catalog.md +149 -0
  94. package/data/skills/bestax-custom-component/references/library-contributor.md +395 -0
  95. package/data/skills/bestax-custom-component/references/patterns.md +136 -0
  96. package/data/skills/bestax-form/SKILL.md +254 -0
  97. package/data/skills/bestax-form/references/api.md +103 -0
  98. package/data/skills/bestax-form/references/patterns.md +210 -0
  99. package/data/skills/bestax-icons/SKILL.md +80 -0
  100. package/data/skills/bestax-icons/examples/icon-usage.tsx +80 -0
  101. package/data/skills/bestax-icons/references/icon-libraries.md +131 -0
  102. package/data/skills/bestax-layout-scaffold/SKILL.md +196 -0
  103. package/data/skills/bestax-layout-scaffold/examples/app-shell.tsx +86 -0
  104. package/data/skills/bestax-layout-scaffold/examples/card-grid.tsx +106 -0
  105. package/data/skills/bestax-layout-scaffold/examples/centered.tsx +56 -0
  106. package/data/skills/bestax-layout-scaffold/examples/content-page.tsx +90 -0
  107. package/data/skills/bestax-layout-scaffold/examples/landing.tsx +122 -0
  108. package/data/skills/bestax-layout-scaffold/references/archetypes.md +246 -0
  109. package/data/skills/bestax-layout-scaffold/references/layout-components.md +269 -0
  110. package/data/skills/bestax-migrate/SKILL.md +78 -0
  111. package/data/skills/bestax-migrate/references/component-map.md +91 -0
  112. package/data/skills/bestax-migrate/references/css-migration.md +100 -0
  113. package/data/skills/bestax-migrate/references/prop-map.md +48 -0
  114. package/data/skills/bestax-migrate/references/unmappables.md +124 -0
  115. package/data/skills/bestax-optimize/SKILL.md +131 -0
  116. package/data/skills/bestax-optimize/references/modular-build.md +173 -0
  117. package/data/skills/bestax-theming/SKILL.md +117 -0
  118. package/data/skills/bestax-theming/examples/dark-mode.tsx +38 -0
  119. package/data/skills/bestax-theming/examples/theme-config.tsx +60 -0
  120. package/data/skills/bestax-theming/references/css-variables.md +466 -0
  121. package/data/skills/bestax-theming/references/themeable-components.md +119 -0
  122. package/data/skills.json +148 -0
  123. package/dist/data.d.ts +131 -0
  124. package/dist/data.d.ts.map +1 -0
  125. package/dist/data.js +111 -0
  126. package/dist/format.d.ts +37 -0
  127. package/dist/format.d.ts.map +1 -0
  128. package/dist/format.js +178 -0
  129. package/dist/helper-props.d.ts +57 -0
  130. package/dist/helper-props.d.ts.map +1 -0
  131. package/dist/helper-props.js +249 -0
  132. package/dist/index.d.ts +3 -0
  133. package/dist/index.d.ts.map +1 -0
  134. package/dist/index.js +18 -0
  135. package/dist/search.d.ts +32 -0
  136. package/dist/search.d.ts.map +1 -0
  137. package/dist/search.js +194 -0
  138. package/dist/server.d.ts +28 -0
  139. package/dist/server.d.ts.map +1 -0
  140. package/dist/server.js +616 -0
  141. package/dist/version.d.ts +29 -0
  142. package/dist/version.d.ts.map +1 -0
  143. package/dist/version.js +147 -0
  144. 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
+ }
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=index.d.ts.map
@@ -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
+ });
@@ -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
+ }
@@ -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"}