lubaui-mcp 1.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 +207 -0
- package/bin/lubaui-mcp.js +2 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +149 -0
- package/dist/tools/lookup.d.ts +118 -0
- package/dist/tools/lookup.js +201 -0
- package/dist/tools/suggest.d.ts +31 -0
- package/dist/tools/suggest.js +164 -0
- package/dist/tools/validate.d.ts +70 -0
- package/dist/tools/validate.js +82 -0
- package/dist/utils/paths.d.ts +1 -0
- package/dist/utils/paths.js +18 -0
- package/dist/utils/search.d.ts +11 -0
- package/dist/utils/search.js +52 -0
- package/package.json +44 -0
- package/src/data/components.json +345 -0
- package/src/data/primitives.json +134 -0
- package/src/data/tokens.json +172 -0
package/README.md
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
# LubaUI MCP Server
|
|
2
|
+
|
|
3
|
+
An MCP (Model Context Protocol) server that makes the LubaUI design system actively queryable by AI assistants. Rather than reading Swift source files, your AI gets structured answers about tokens, components, and patterns through dedicated tools.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
### Using LubaUI in your own project (recommended)
|
|
10
|
+
|
|
11
|
+
One command — works from any project directory:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
claude mcp add lubaui -- npx lubaui-mcp
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
That's it. Next time you start Claude Code, the LubaUI tools are available. Ask "What spacing should I use for card padding?" and Claude will query the server.
|
|
18
|
+
|
|
19
|
+
### Contributing to LubaUI
|
|
20
|
+
|
|
21
|
+
If you're working inside the LubaUI repo itself, the `.mcp.json` at the project root auto-discovers the server. Just install dependencies:
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
cd mcp-server && npm install
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The server starts on demand when Claude Code opens the project.
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Tools
|
|
32
|
+
|
|
33
|
+
The server provides 7 tools that solve real problems during development.
|
|
34
|
+
|
|
35
|
+
### `lookup_token`
|
|
36
|
+
|
|
37
|
+
Find any design token by name or description.
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
"What spacing should I use for card padding?"
|
|
41
|
+
→ LubaSpacing.lg — 16pt — "Large — card padding, section spacing, standard gap"
|
|
42
|
+
|
|
43
|
+
"What's the accent color hex?"
|
|
44
|
+
→ LubaColors.accent — light: #5F7360, dark: #9AB897
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Parameters:**
|
|
48
|
+
- `query` (required) — Token name, API path, or description
|
|
49
|
+
- `category` (optional) — Filter: `spacing`, `radius`, `color`, `typography`, `motion`, `glass`
|
|
50
|
+
|
|
51
|
+
### `lookup_component`
|
|
52
|
+
|
|
53
|
+
Get the full API for any of the 25 components.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
"How do I use LubaButton?"
|
|
57
|
+
→ Parameters, styles (.primary/.secondary/.ghost/.destructive/.subtle/.glass),
|
|
58
|
+
sizes, code example, related tokens
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
**Parameters:**
|
|
62
|
+
- `query` (required) — Component name (fuzzy: "Button", "LubaTextField", "toast")
|
|
63
|
+
|
|
64
|
+
### `lookup_primitive`
|
|
65
|
+
|
|
66
|
+
Look up any of the 7 interaction primitives.
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
"How do I add swipe-to-delete?"
|
|
70
|
+
→ .lubaSwipeable() modifier, parameters, 6 presets (.delete, .archive, .pin, etc.),
|
|
71
|
+
code example, composability notes
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
**Parameters:**
|
|
75
|
+
- `query` (required) — Primitive name (fuzzy: "pressable", "swipeable", "glass")
|
|
76
|
+
|
|
77
|
+
### `validate_spacing`
|
|
78
|
+
|
|
79
|
+
Check if a spacing value is on the 4pt grid and maps to a named token.
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
"Is 14pt valid?"
|
|
83
|
+
→ No — 14pt is NOT on the 4pt grid.
|
|
84
|
+
Nearest: LubaSpacing.md (12pt) and LubaSpacing.lg (16pt)
|
|
85
|
+
|
|
86
|
+
"Is 16pt valid?"
|
|
87
|
+
→ Yes — 16pt is LubaSpacing.lg
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**Parameters:**
|
|
91
|
+
- `value` (required) — Spacing value in points
|
|
92
|
+
|
|
93
|
+
### `validate_radius`
|
|
94
|
+
|
|
95
|
+
Check if a corner radius is on the LubaRadius scale.
|
|
96
|
+
|
|
97
|
+
```
|
|
98
|
+
"Is 10pt a valid radius?"
|
|
99
|
+
→ No — nearest: LubaRadius.sm (8pt) and LubaRadius.md (12pt)
|
|
100
|
+
Valid radii: none(0), xs(4), sm(8), md(12), lg(16), xl(24), full(9999)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
**Parameters:**
|
|
104
|
+
- `value` (required) — Radius value in points
|
|
105
|
+
|
|
106
|
+
### `get_color_palette`
|
|
107
|
+
|
|
108
|
+
Browse colors by category or search across the palette.
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
"Show me the surface colors"
|
|
112
|
+
→ background (#FAFAFA/#0D0D0D), surface (#FFFFFF/#171717),
|
|
113
|
+
surfaceSecondary (#F5F5F5/#212121), surfaceTertiary (#EFEFEF/#2A2A2A)
|
|
114
|
+
|
|
115
|
+
"What colors are available for errors?"
|
|
116
|
+
→ error (#B54A4A/#E07A7A), errorSubtle (#FAEFEF/#251A1A)
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Parameters:**
|
|
120
|
+
- `query` (required) — Category name (`greyscale`, `surfaces`, `accent`, `text`, `semantic`, `border`, `glass`) or search term
|
|
121
|
+
|
|
122
|
+
### `suggest_tokens`
|
|
123
|
+
|
|
124
|
+
Describe what you're building and get token, component, and primitive recommendations.
|
|
125
|
+
|
|
126
|
+
```
|
|
127
|
+
"I'm building a notification banner"
|
|
128
|
+
→ Spacing: LubaSpacing.md (12pt) internal, LubaSpacing.lg (16pt) horizontal
|
|
129
|
+
Colors: LubaColors.success/warning/error for status
|
|
130
|
+
Radius: LubaRadius.md (12pt)
|
|
131
|
+
Typography: LubaTypography.subheadline for message
|
|
132
|
+
Components: LubaToast, LubaAlert
|
|
133
|
+
Primitives: lubaGlass for frosted effect
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
**Parameters:**
|
|
137
|
+
- `description` (required) — What you're building ("settings page", "profile card", "loading state")
|
|
138
|
+
|
|
139
|
+
Recognizes these patterns: `form`, `notification`, `banner`, `card`, `list`, `loading`, `navigation`, `profile`, `modal`, `settings`.
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Resources
|
|
144
|
+
|
|
145
|
+
Three read-only resources provide full reference data.
|
|
146
|
+
|
|
147
|
+
| URI | Description |
|
|
148
|
+
|-----|-------------|
|
|
149
|
+
| `lubaui://tokens/all` | Complete token catalog — spacing, radius, colors, typography, motion, glass |
|
|
150
|
+
| `lubaui://architecture` | Three-tier system rules, design philosophy, naming conventions |
|
|
151
|
+
| `lubaui://components` | Component and primitive directory with descriptions |
|
|
152
|
+
|
|
153
|
+
---
|
|
154
|
+
|
|
155
|
+
## Architecture
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
mcp-server/
|
|
159
|
+
├── package.json
|
|
160
|
+
├── tsconfig.json
|
|
161
|
+
└── src/
|
|
162
|
+
├── index.ts # Server entry, tool/resource registration
|
|
163
|
+
├── data/
|
|
164
|
+
│ ├── tokens.json # Spacing, radius, colors, typography, motion, glass
|
|
165
|
+
│ ├── components.json # 25 component APIs with parameters and examples
|
|
166
|
+
│ └── primitives.json # 7 primitives with modifiers and patterns
|
|
167
|
+
├── tools/
|
|
168
|
+
│ ├── lookup.ts # lookup_token, lookup_component, lookup_primitive, get_color_palette
|
|
169
|
+
│ ├── validate.ts # validate_spacing, validate_radius
|
|
170
|
+
│ └── suggest.ts # suggest_tokens
|
|
171
|
+
└── utils/
|
|
172
|
+
└── search.ts # Scored fuzzy search (camelCase-aware)
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
**Data strategy:** Hand-authored JSON files transcribed from the Swift sources. The data files include usage guidance, cross-references, and design rationale that don't exist in source code. The token system changes infrequently, so static JSON is reliable and avoids runtime Swift parsing.
|
|
176
|
+
|
|
177
|
+
**Search:** Simple scored matching — exact (100) > starts-with (80) > contains (60) > camelCase part match (50). No external dependencies.
|
|
178
|
+
|
|
179
|
+
**Transport:** stdio via `@modelcontextprotocol/sdk`. Runs with `npx tsx` (no build step).
|
|
180
|
+
|
|
181
|
+
---
|
|
182
|
+
|
|
183
|
+
## Updating Data
|
|
184
|
+
|
|
185
|
+
When tokens or components change in the Swift sources, update the corresponding JSON file in `src/data/`:
|
|
186
|
+
|
|
187
|
+
- **Token changes** (new spacing value, color tweak) → edit `tokens.json`
|
|
188
|
+
- **New component or API change** → edit `components.json`
|
|
189
|
+
- **New primitive or modifier change** → edit `primitives.json`
|
|
190
|
+
|
|
191
|
+
The JSON structure is self-documenting. Match the existing patterns when adding entries.
|
|
192
|
+
|
|
193
|
+
---
|
|
194
|
+
|
|
195
|
+
## Development
|
|
196
|
+
|
|
197
|
+
```bash
|
|
198
|
+
# Install
|
|
199
|
+
cd mcp-server && npm install
|
|
200
|
+
|
|
201
|
+
# Test manually (sends JSON-RPC over stdin)
|
|
202
|
+
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}' | npx tsx src/index.ts
|
|
203
|
+
|
|
204
|
+
# List tools
|
|
205
|
+
echo '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"test","version":"1.0.0"}}}
|
|
206
|
+
{"jsonrpc":"2.0","id":2,"method":"tools/list","params":{}}' | npx tsx src/index.ts
|
|
207
|
+
```
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
2
|
+
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
import { lookupToken, lookupComponent, lookupPrimitive, getColorPalette, tokensData, componentsData, primitivesData, } from "./tools/lookup.js";
|
|
5
|
+
import { validateSpacing, validateRadius } from "./tools/validate.js";
|
|
6
|
+
import { suggestTokens } from "./tools/suggest.js";
|
|
7
|
+
const server = new McpServer({
|
|
8
|
+
name: "lubaui",
|
|
9
|
+
version: "1.0.0",
|
|
10
|
+
});
|
|
11
|
+
// --- Tools ---
|
|
12
|
+
server.tool("lookup_token", "Look up a LubaUI design token by name or description. Returns token name, API, value, tier, and usage guidance.", {
|
|
13
|
+
query: z.string().describe("Token name, API path, or description (e.g. 'spacing large', 'lg', 'card padding')"),
|
|
14
|
+
category: z
|
|
15
|
+
.enum(["spacing", "radius", "color", "typography", "motion", "glass"])
|
|
16
|
+
.optional()
|
|
17
|
+
.describe("Filter by token category"),
|
|
18
|
+
}, async ({ query, category }) => {
|
|
19
|
+
const result = lookupToken(query, category);
|
|
20
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
21
|
+
});
|
|
22
|
+
server.tool("lookup_component", "Look up a LubaUI component by name. Returns init parameters, styles, code example, and related tokens.", {
|
|
23
|
+
query: z.string().describe("Component name (e.g. 'Button', 'LubaTextField', 'toast')"),
|
|
24
|
+
}, async ({ query }) => {
|
|
25
|
+
const result = lookupComponent(query);
|
|
26
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
27
|
+
});
|
|
28
|
+
server.tool("lookup_primitive", "Look up a LubaUI interaction primitive by name. Returns modifier signature, parameters, presets, and code example.", {
|
|
29
|
+
query: z.string().describe("Primitive name (e.g. 'pressable', 'swipeable', 'glass', 'expandable')"),
|
|
30
|
+
}, async ({ query }) => {
|
|
31
|
+
const result = lookupPrimitive(query);
|
|
32
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
33
|
+
});
|
|
34
|
+
server.tool("validate_spacing", "Check if a spacing value is on the LubaUI 4pt grid and maps to a named token. Suggests nearest tokens if not.", {
|
|
35
|
+
value: z.number().describe("Spacing value in points to validate"),
|
|
36
|
+
}, async ({ value }) => {
|
|
37
|
+
const result = validateSpacing(value);
|
|
38
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
39
|
+
});
|
|
40
|
+
server.tool("validate_radius", "Check if a corner radius value is on the LubaUI radius scale. Suggests nearest token if not.", {
|
|
41
|
+
value: z.number().describe("Radius value in points to validate"),
|
|
42
|
+
}, async ({ value }) => {
|
|
43
|
+
const result = validateRadius(value);
|
|
44
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
45
|
+
});
|
|
46
|
+
server.tool("get_color_palette", "Get LubaUI colors by category or search. Returns light/dark hex values and usage context.", {
|
|
47
|
+
query: z.string().describe("Color category (greyscale, surfaces, accent, text, semantic, border, glass) or search term (e.g. 'error', 'background')"),
|
|
48
|
+
}, async ({ query }) => {
|
|
49
|
+
const result = getColorPalette(query);
|
|
50
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
51
|
+
});
|
|
52
|
+
server.tool("suggest_tokens", "Suggest LubaUI tokens, components, and primitives for a UI you're building. Describe what you're building and get recommendations.", {
|
|
53
|
+
description: z.string().describe("Description of what you're building (e.g. 'notification banner', 'settings page', 'profile card')"),
|
|
54
|
+
}, async ({ description }) => {
|
|
55
|
+
const result = suggestTokens(description);
|
|
56
|
+
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
57
|
+
});
|
|
58
|
+
// --- Resources ---
|
|
59
|
+
server.resource("tokens-all", "lubaui://tokens/all", { description: "Complete LubaUI token catalog — spacing, radius, colors, typography, motion, and glass tokens" }, async () => {
|
|
60
|
+
return {
|
|
61
|
+
contents: [
|
|
62
|
+
{
|
|
63
|
+
uri: "lubaui://tokens/all",
|
|
64
|
+
mimeType: "application/json",
|
|
65
|
+
text: JSON.stringify(tokensData, null, 2),
|
|
66
|
+
},
|
|
67
|
+
],
|
|
68
|
+
};
|
|
69
|
+
});
|
|
70
|
+
server.resource("architecture", "lubaui://architecture", { description: "LubaUI design system architecture — three-tier token system, design philosophy, and conventions" }, async () => {
|
|
71
|
+
const architecture = {
|
|
72
|
+
philosophy: "Composability over inheritance. Behaviors are extracted into primitives so any view can gain interactive powers through modifiers, not subclassing. Every magic number has a name, a token, and documented rationale.",
|
|
73
|
+
tokenSystem: {
|
|
74
|
+
tier1: {
|
|
75
|
+
name: "Primitives (LubaPrimitives)",
|
|
76
|
+
rule: "Raw hex values and numbers. The DNA. NEVER reference directly in component code.",
|
|
77
|
+
examples: ["LubaPrimitives.grey900Light → Color(hex: 0x1A1A1A)", "LubaPrimitives.space4 → 4"],
|
|
78
|
+
},
|
|
79
|
+
tier2: {
|
|
80
|
+
name: "Semantic Tokens",
|
|
81
|
+
rule: "Human-readable, context-aware names. USE THESE in components.",
|
|
82
|
+
modules: ["LubaColors", "LubaSpacing", "LubaRadius", "LubaTypography", "LubaMotion", "LubaAnimations"],
|
|
83
|
+
examples: ["LubaColors.textPrimary → adaptive grey900", "LubaSpacing.lg → 16pt", "LubaMotion.pressScale → 0.97"],
|
|
84
|
+
},
|
|
85
|
+
tier3: {
|
|
86
|
+
name: "Component Tokens",
|
|
87
|
+
rule: "Per-component configuration enums. Reference tier 2 tokens internally.",
|
|
88
|
+
modules: ["LubaCardTokens", "LubaFieldTokens", "LubaButtonSize", "LubaToastTokens", "LubaTabsTokens", "etc."],
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
colorSystem: {
|
|
92
|
+
approach: "Greyscale-first with organic sage green accent",
|
|
93
|
+
adaptive: "All colors use LubaColors.adaptive(light:dark:) for automatic light/dark mode",
|
|
94
|
+
surfaceHierarchy: "background → surface → surfaceSecondary → surfaceTertiary",
|
|
95
|
+
accentColor: "Sage green (#5F7360 light / #9AB897 dark) — WCAG AA compliant",
|
|
96
|
+
},
|
|
97
|
+
spacingGrid: "4pt base grid. Named tokens: xs(4), sm(8), md(12), lg(16), xl(24), xxl(32), xxxl(48), huge(64)",
|
|
98
|
+
radiusScale: "none(0), xs(4), sm(8), md(12), lg(16), xl(24), full(9999)",
|
|
99
|
+
typography: "SF Rounded by default. Config-aware via LubaConfig.shared.useRoundedFont",
|
|
100
|
+
motionSystems: {
|
|
101
|
+
LubaMotion: "Raw constants (scale values, animation parameters) — use with .scaleEffect(), .opacity()",
|
|
102
|
+
LubaAnimations: "Applied Animation objects — use with withAnimation(), .animation(), .transition()",
|
|
103
|
+
},
|
|
104
|
+
platforms: "iOS 16+, macOS 13+, watchOS 9+, tvOS 16+, visionOS 1.0+",
|
|
105
|
+
configuration: {
|
|
106
|
+
environment: "@Environment(\\.lubaConfig) for runtime settings, @Environment(\\.lubaTheme) for theming",
|
|
107
|
+
presets: [".minimal", ".accessible", ".debug"],
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
return {
|
|
111
|
+
contents: [
|
|
112
|
+
{
|
|
113
|
+
uri: "lubaui://architecture",
|
|
114
|
+
mimeType: "application/json",
|
|
115
|
+
text: JSON.stringify(architecture, null, 2),
|
|
116
|
+
},
|
|
117
|
+
],
|
|
118
|
+
};
|
|
119
|
+
});
|
|
120
|
+
server.resource("components", "lubaui://components", { description: "LubaUI component directory — all 25 components with descriptions" }, async () => {
|
|
121
|
+
const summary = componentsData.map((c) => ({
|
|
122
|
+
name: c.name,
|
|
123
|
+
description: c.description,
|
|
124
|
+
}));
|
|
125
|
+
const primitivesSummary = primitivesData.map((p) => ({
|
|
126
|
+
name: p.name,
|
|
127
|
+
modifier: p.modifier,
|
|
128
|
+
description: p.description,
|
|
129
|
+
}));
|
|
130
|
+
return {
|
|
131
|
+
contents: [
|
|
132
|
+
{
|
|
133
|
+
uri: "lubaui://components",
|
|
134
|
+
mimeType: "application/json",
|
|
135
|
+
text: JSON.stringify({
|
|
136
|
+
components: summary,
|
|
137
|
+
primitives: primitivesSummary,
|
|
138
|
+
total: { components: summary.length, primitives: primitivesSummary.length },
|
|
139
|
+
}, null, 2),
|
|
140
|
+
},
|
|
141
|
+
],
|
|
142
|
+
};
|
|
143
|
+
});
|
|
144
|
+
// --- Start ---
|
|
145
|
+
async function main() {
|
|
146
|
+
const transport = new StdioServerTransport();
|
|
147
|
+
await server.connect(transport);
|
|
148
|
+
}
|
|
149
|
+
main().catch(console.error);
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
declare const tokensData: any;
|
|
2
|
+
declare const componentsData: ComponentEntry[];
|
|
3
|
+
declare const primitivesData: PrimitiveEntry[];
|
|
4
|
+
interface ComponentEntry {
|
|
5
|
+
name: string;
|
|
6
|
+
description: string;
|
|
7
|
+
parameters: Array<{
|
|
8
|
+
name: string;
|
|
9
|
+
type: string;
|
|
10
|
+
required?: boolean;
|
|
11
|
+
default?: string;
|
|
12
|
+
options?: string[];
|
|
13
|
+
description: string;
|
|
14
|
+
}>;
|
|
15
|
+
example: string;
|
|
16
|
+
tokens: string[];
|
|
17
|
+
notes: string;
|
|
18
|
+
}
|
|
19
|
+
interface PrimitiveEntry {
|
|
20
|
+
name: string;
|
|
21
|
+
modifier: string;
|
|
22
|
+
description: string;
|
|
23
|
+
parameters: Array<{
|
|
24
|
+
name: string;
|
|
25
|
+
type: string;
|
|
26
|
+
required?: boolean;
|
|
27
|
+
default?: string;
|
|
28
|
+
options?: string[];
|
|
29
|
+
description: string;
|
|
30
|
+
}>;
|
|
31
|
+
example: string;
|
|
32
|
+
tokens: string[];
|
|
33
|
+
composability: string;
|
|
34
|
+
[key: string]: unknown;
|
|
35
|
+
}
|
|
36
|
+
export declare function lookupToken(query: string, category?: string): {
|
|
37
|
+
found: boolean;
|
|
38
|
+
message: string;
|
|
39
|
+
count?: undefined;
|
|
40
|
+
tokens?: undefined;
|
|
41
|
+
} | {
|
|
42
|
+
found: boolean;
|
|
43
|
+
count: number;
|
|
44
|
+
tokens: {
|
|
45
|
+
aliasOf?: string | undefined;
|
|
46
|
+
primitive?: string | undefined;
|
|
47
|
+
dark?: string | undefined;
|
|
48
|
+
light?: string | undefined;
|
|
49
|
+
name: string;
|
|
50
|
+
api: string;
|
|
51
|
+
value: string | number | undefined;
|
|
52
|
+
tier: number | undefined;
|
|
53
|
+
category: string;
|
|
54
|
+
subcategory: string | undefined;
|
|
55
|
+
description: string;
|
|
56
|
+
}[];
|
|
57
|
+
message?: undefined;
|
|
58
|
+
};
|
|
59
|
+
export declare function lookupComponent(query: string): {
|
|
60
|
+
found: boolean;
|
|
61
|
+
message: string;
|
|
62
|
+
components?: undefined;
|
|
63
|
+
} | {
|
|
64
|
+
found: boolean;
|
|
65
|
+
components: {
|
|
66
|
+
name: string;
|
|
67
|
+
description: string;
|
|
68
|
+
parameters: {
|
|
69
|
+
name: string;
|
|
70
|
+
type: string;
|
|
71
|
+
required?: boolean;
|
|
72
|
+
default?: string;
|
|
73
|
+
options?: string[];
|
|
74
|
+
description: string;
|
|
75
|
+
}[];
|
|
76
|
+
example: string;
|
|
77
|
+
tokens: string[];
|
|
78
|
+
notes: string;
|
|
79
|
+
}[];
|
|
80
|
+
message?: undefined;
|
|
81
|
+
};
|
|
82
|
+
export declare function lookupPrimitive(query: string): {
|
|
83
|
+
found: boolean;
|
|
84
|
+
message: string;
|
|
85
|
+
primitives?: undefined;
|
|
86
|
+
} | {
|
|
87
|
+
found: boolean;
|
|
88
|
+
primitives: Record<string, unknown>[];
|
|
89
|
+
message?: undefined;
|
|
90
|
+
};
|
|
91
|
+
export declare function getColorPalette(query: string): {
|
|
92
|
+
category: string;
|
|
93
|
+
description: any;
|
|
94
|
+
colors: any;
|
|
95
|
+
found?: undefined;
|
|
96
|
+
message?: undefined;
|
|
97
|
+
} | {
|
|
98
|
+
found: boolean;
|
|
99
|
+
message: string;
|
|
100
|
+
category?: undefined;
|
|
101
|
+
description?: undefined;
|
|
102
|
+
colors?: undefined;
|
|
103
|
+
} | {
|
|
104
|
+
found: boolean;
|
|
105
|
+
colors: {
|
|
106
|
+
aliasOf?: string | undefined;
|
|
107
|
+
name: string;
|
|
108
|
+
api: string;
|
|
109
|
+
light: string | undefined;
|
|
110
|
+
dark: string | undefined;
|
|
111
|
+
description: string;
|
|
112
|
+
subcategory: string | undefined;
|
|
113
|
+
}[];
|
|
114
|
+
category?: undefined;
|
|
115
|
+
description?: undefined;
|
|
116
|
+
message?: undefined;
|
|
117
|
+
};
|
|
118
|
+
export { tokensData, componentsData, primitivesData };
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import { readFileSync } from "fs";
|
|
2
|
+
import { join } from "path";
|
|
3
|
+
import { search } from "../utils/search.js";
|
|
4
|
+
import { dataDir } from "../utils/paths.js";
|
|
5
|
+
// Load data once at module level
|
|
6
|
+
const tokensData = JSON.parse(readFileSync(join(dataDir, "tokens.json"), "utf-8"));
|
|
7
|
+
const componentsData = JSON.parse(readFileSync(join(dataDir, "components.json"), "utf-8"));
|
|
8
|
+
const primitivesData = JSON.parse(readFileSync(join(dataDir, "primitives.json"), "utf-8"));
|
|
9
|
+
function flattenTokens() {
|
|
10
|
+
const flat = [];
|
|
11
|
+
// Spacing
|
|
12
|
+
for (const t of tokensData.spacing.tokens) {
|
|
13
|
+
flat.push({ ...t, category: "spacing", aliases: [t.api] });
|
|
14
|
+
}
|
|
15
|
+
// Radius
|
|
16
|
+
for (const t of tokensData.radius.tokens) {
|
|
17
|
+
flat.push({ ...t, category: "radius", aliases: [t.api] });
|
|
18
|
+
}
|
|
19
|
+
// Colors (all subcategories)
|
|
20
|
+
for (const sub of [
|
|
21
|
+
"greyscale",
|
|
22
|
+
"surfaces",
|
|
23
|
+
"accent",
|
|
24
|
+
"text",
|
|
25
|
+
"semantic",
|
|
26
|
+
"border",
|
|
27
|
+
"glass",
|
|
28
|
+
]) {
|
|
29
|
+
const colors = tokensData.colors[sub];
|
|
30
|
+
if (colors) {
|
|
31
|
+
for (const c of colors) {
|
|
32
|
+
flat.push({
|
|
33
|
+
...c,
|
|
34
|
+
category: "color",
|
|
35
|
+
subcategory: sub,
|
|
36
|
+
aliases: [c.api],
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
// Typography
|
|
42
|
+
for (const t of tokensData.typography.tokens) {
|
|
43
|
+
flat.push({
|
|
44
|
+
...t,
|
|
45
|
+
category: "typography",
|
|
46
|
+
aliases: [t.api],
|
|
47
|
+
value: `${t.size}pt ${t.weight}`,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// Motion constants
|
|
51
|
+
for (const sub of ["pressScales", "animations", "other"]) {
|
|
52
|
+
const items = tokensData.motion.constants[sub];
|
|
53
|
+
if (items) {
|
|
54
|
+
for (const m of items) {
|
|
55
|
+
flat.push({ ...m, category: "motion", subcategory: sub, aliases: [m.api] });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
// Applied animations
|
|
60
|
+
for (const a of tokensData.motion.appliedAnimations) {
|
|
61
|
+
flat.push({ ...a, category: "motion", subcategory: "applied", aliases: [a.api] });
|
|
62
|
+
}
|
|
63
|
+
// Glass
|
|
64
|
+
for (const s of tokensData.glass.styles) {
|
|
65
|
+
flat.push({ ...s, category: "glass", aliases: [s.api] });
|
|
66
|
+
}
|
|
67
|
+
for (const t of tokensData.glass.tokens) {
|
|
68
|
+
flat.push({ ...t, category: "glass", aliases: [t.api], description: t.name });
|
|
69
|
+
}
|
|
70
|
+
return flat;
|
|
71
|
+
}
|
|
72
|
+
const flatTokens = flattenTokens();
|
|
73
|
+
// --- Tool implementations ---
|
|
74
|
+
export function lookupToken(query, category) {
|
|
75
|
+
const results = search(flatTokens, query, { category, maxResults: 8 });
|
|
76
|
+
if (results.length === 0) {
|
|
77
|
+
return { found: false, message: `No tokens matching "${query}"${category ? ` in category "${category}"` : ""}` };
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
found: true,
|
|
81
|
+
count: results.length,
|
|
82
|
+
tokens: results.map((t) => ({
|
|
83
|
+
name: t.name,
|
|
84
|
+
api: t.api,
|
|
85
|
+
value: t.value,
|
|
86
|
+
tier: t.tier,
|
|
87
|
+
category: t.category,
|
|
88
|
+
subcategory: t.subcategory,
|
|
89
|
+
description: t.description,
|
|
90
|
+
...(t.light && { light: t.light }),
|
|
91
|
+
...(t.dark && { dark: t.dark }),
|
|
92
|
+
...(t.primitive && { primitive: t.primitive }),
|
|
93
|
+
...(t.aliasOf && { aliasOf: t.aliasOf }),
|
|
94
|
+
})),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
export function lookupComponent(query) {
|
|
98
|
+
const searchable = componentsData.map((c) => ({
|
|
99
|
+
...c,
|
|
100
|
+
aliases: [c.name.replace("Luba", "")],
|
|
101
|
+
}));
|
|
102
|
+
const results = search(searchable, query, { maxResults: 3 });
|
|
103
|
+
if (results.length === 0) {
|
|
104
|
+
return {
|
|
105
|
+
found: false,
|
|
106
|
+
message: `No component matching "${query}". Available: ${componentsData.map((c) => c.name).join(", ")}`,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
return {
|
|
110
|
+
found: true,
|
|
111
|
+
components: results.map((c) => ({
|
|
112
|
+
name: c.name,
|
|
113
|
+
description: c.description,
|
|
114
|
+
parameters: c.parameters,
|
|
115
|
+
example: c.example,
|
|
116
|
+
tokens: c.tokens,
|
|
117
|
+
notes: c.notes,
|
|
118
|
+
})),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export function lookupPrimitive(query) {
|
|
122
|
+
const searchable = primitivesData.map((p) => ({
|
|
123
|
+
...p,
|
|
124
|
+
aliases: [p.name.replace("luba", ""), p.modifier],
|
|
125
|
+
}));
|
|
126
|
+
const results = search(searchable, query, { maxResults: 3 });
|
|
127
|
+
if (results.length === 0) {
|
|
128
|
+
return {
|
|
129
|
+
found: false,
|
|
130
|
+
message: `No primitive matching "${query}". Available: ${primitivesData.map((p) => p.name).join(", ")}`,
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return {
|
|
134
|
+
found: true,
|
|
135
|
+
primitives: results.map((p) => {
|
|
136
|
+
const result = {
|
|
137
|
+
name: p.name,
|
|
138
|
+
modifier: p.modifier,
|
|
139
|
+
description: p.description,
|
|
140
|
+
parameters: p.parameters,
|
|
141
|
+
example: p.example,
|
|
142
|
+
tokens: p.tokens,
|
|
143
|
+
composability: p.composability,
|
|
144
|
+
};
|
|
145
|
+
if ("presets" in p)
|
|
146
|
+
result.presets = p.presets;
|
|
147
|
+
if ("variants" in p)
|
|
148
|
+
result.variants = p.variants;
|
|
149
|
+
if ("builtInStyles" in p)
|
|
150
|
+
result.builtInStyles = p.builtInStyles;
|
|
151
|
+
if ("hapticStyles" in p)
|
|
152
|
+
result.hapticStyles = p.hapticStyles;
|
|
153
|
+
return result;
|
|
154
|
+
}),
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
export function getColorPalette(query) {
|
|
158
|
+
const categories = [
|
|
159
|
+
"greyscale",
|
|
160
|
+
"surfaces",
|
|
161
|
+
"accent",
|
|
162
|
+
"text",
|
|
163
|
+
"semantic",
|
|
164
|
+
"border",
|
|
165
|
+
"glass",
|
|
166
|
+
];
|
|
167
|
+
// Check if query matches a category directly
|
|
168
|
+
const directMatch = categories.find((c) => c.toLowerCase() === query.toLowerCase() ||
|
|
169
|
+
c.toLowerCase().startsWith(query.toLowerCase()));
|
|
170
|
+
if (directMatch) {
|
|
171
|
+
const colors = tokensData.colors[directMatch];
|
|
172
|
+
return {
|
|
173
|
+
category: directMatch,
|
|
174
|
+
description: tokensData.colors.description,
|
|
175
|
+
colors: colors,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
// Otherwise search across all colors
|
|
179
|
+
const allColors = flatTokens.filter((t) => t.category === "color");
|
|
180
|
+
const results = search(allColors, query, { maxResults: 10 });
|
|
181
|
+
if (results.length === 0) {
|
|
182
|
+
return {
|
|
183
|
+
found: false,
|
|
184
|
+
message: `No colors matching "${query}". Categories: ${categories.join(", ")}`,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
return {
|
|
188
|
+
found: true,
|
|
189
|
+
colors: results.map((c) => ({
|
|
190
|
+
name: c.name,
|
|
191
|
+
api: c.api,
|
|
192
|
+
light: c.light,
|
|
193
|
+
dark: c.dark,
|
|
194
|
+
description: c.description,
|
|
195
|
+
subcategory: c.subcategory,
|
|
196
|
+
...(c.aliasOf && { aliasOf: c.aliasOf }),
|
|
197
|
+
})),
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
// Export data for resources
|
|
201
|
+
export { tokensData, componentsData, primitivesData };
|