mlola-ui 1.0.2 → 1.0.4

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 (38) hide show
  1. package/README.md +30 -3
  2. package/package.json +1 -1
  3. package/registry/agents.json +419 -0
  4. package/registry/agents.md +453 -0
  5. package/registry/assets.json +734 -110
  6. package/registry/catalog.json +1187 -0
  7. package/registry/contract.json +1064 -0
  8. package/registry/index.json +65 -65
  9. package/registry/source/packages/components/alert/alert.tsx +1 -1
  10. package/registry/source/packages/components/badge/badge.tsx +2 -2
  11. package/registry/source/packages/components/breadcrumb/breadcrumb.tsx +1 -1
  12. package/registry/source/packages/components/carousel/carousel.tsx +1 -0
  13. package/registry/source/packages/components/checkbox/checkbox.tsx +3 -2
  14. package/registry/source/packages/components/color-picker/color-picker.tsx +15 -13
  15. package/registry/source/packages/components/color-picker/color.ts +1 -1
  16. package/registry/source/packages/components/combobox/combobox.tsx +33 -6
  17. package/registry/source/packages/components/date-picker/date-picker.tsx +5 -3
  18. package/registry/source/packages/components/dropzone/dropzone.tsx +1 -1
  19. package/registry/source/packages/components/empty-state/empty-state.tsx +1 -1
  20. package/registry/source/packages/components/hover-card/hover-card.tsx +1 -1
  21. package/registry/source/packages/components/number-input/number-input.tsx +6 -5
  22. package/registry/source/packages/components/otp-input/otp-input.tsx +6 -4
  23. package/registry/source/packages/components/progress/progress.tsx +1 -1
  24. package/registry/source/packages/components/segmented-control/segmented-control.tsx +5 -3
  25. package/registry/source/packages/components/select/select.tsx +6 -5
  26. package/registry/source/packages/components/tag-input/tag-input.tsx +7 -6
  27. package/registry/source/packages/components/textarea/textarea.tsx +1 -1
  28. package/registry/source/packages/components/time-picker/time-picker.tsx +8 -5
  29. package/registry/source/packages/components/timeline/timeline.tsx +1 -1
  30. package/registry/source/packages/components/toast/toast.tsx +1 -1
  31. package/registry/source/packages/components/toggle/toggle.tsx +1 -0
  32. package/registry/source/packages/components/tour/tour.tsx +3 -3
  33. package/registry/source-manifest.json +24 -24
  34. package/src/agents.js +119 -0
  35. package/src/cli.js +29 -4
  36. package/src/knowledge.js +235 -0
  37. package/src/mcp.js +241 -0
  38. package/src/pro.js +1 -1
package/src/mcp.js ADDED
@@ -0,0 +1,241 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import readline from "node:readline";
4
+ import { checkMarkup, describeItem, designData, designGuide, searchItems, themes, tokens } from "./knowledge.js";
5
+
6
+ /**
7
+ * `mlola-ui mcp` — a Model Context Protocol server over stdio.
8
+ *
9
+ * A coding agent (Claude Code, Cursor, Codex, VS Code, …) starts it in the
10
+ * project and asks it about Mlola instead of recalling Tailwind from its
11
+ * training: which component fits, what it is called, which token to read,
12
+ * whether the markup it just wrote is right. It can also install components
13
+ * and set a project up. Answers come from the registry bundled with this
14
+ * CLI, so they match what `add` installs, offline.
15
+ *
16
+ * The protocol is newline-delimited JSON-RPC 2.0, small enough to speak
17
+ * without a dependency. Nothing but protocol messages is written to stdout;
18
+ * diagnostics go to stderr.
19
+ */
20
+
21
+ const PROTOCOL_VERSIONS = ["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05"];
22
+
23
+ const INSTRUCTIONS = `This project's UI is Mlola UI: native CSS classes (ml-*), data-* attributes for state and variant, and --ml-* tokens. There is no Tailwind.
24
+ Before writing UI: call get_design_rules once, then search_components for what you need and get_component for how to use it. Read tokens with get_tokens instead of writing colors, sizes, shadows or durations. After writing markup, run check_markup on it and fix what it reports.`;
25
+
26
+ const text = (value) => ({ content: [{ type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }] });
27
+
28
+ function tools({ cwd, run }) {
29
+ const capture = async (argv) => {
30
+ const lines = [];
31
+ const output = { log: (...parts) => lines.push(parts.join(" ")), error: (...parts) => lines.push(parts.join(" ")) };
32
+ const code = await run(argv, { cwd, output });
33
+ return { code, output: lines.join("\n") };
34
+ };
35
+ return [
36
+ {
37
+ name: "get_design_rules",
38
+ title: "Mlola design rules",
39
+ description: "The rules for building UI with Mlola: compose first, color by role, measure with the scales, shared state words, touch and focus. Read once before writing UI.",
40
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
41
+ annotations: { readOnlyHint: true },
42
+ handler: () => {
43
+ const data = designData();
44
+ return text(`${data?.rules ?? ""}\nThemes: ${themes().map((theme) => theme.id).join(", ")} (data-theme on any ancestor, data-mode light|dark).\nComposition primitives: ${(data?.primitives ?? []).map((entry) => entry.name).join(" ")}`);
45
+ },
46
+ },
47
+ {
48
+ name: "search_components",
49
+ title: "Search Mlola components",
50
+ description: "Find components, blocks, pages and templates by what they do (for example 'date range', 'chat input', 'pricing'). Returns name, tier (free or Mlola Pro) and a one-line description.",
51
+ inputSchema: {
52
+ type: "object",
53
+ properties: {
54
+ query: { type: "string", description: "Words describing what you need. Empty lists everything." },
55
+ kind: { type: "string", enum: ["component", "block", "page", "template"] },
56
+ tier: { type: "string", enum: ["free", "pro"] },
57
+ },
58
+ additionalProperties: false,
59
+ },
60
+ annotations: { readOnlyHint: true },
61
+ handler: ({ query = "", kind, tier }) => {
62
+ const found = searchItems({ query, kind, tier });
63
+ return text(found.length ? found.slice(0, 40).map((item) => `${item.name} (${item.kind}, ${item.tier}) — ${item.description}`).join("\n") : `Nothing matches "${query}". Try other words, or search with an empty query to list everything.`);
64
+ },
65
+ },
66
+ {
67
+ name: "get_component",
68
+ title: "How to use a Mlola component",
69
+ description: "Everything needed to use one item: import, variants, sizes, the classes it styles and the data-* values each reacts to, dependencies and the install command. Set include_source to read a free component's source.",
70
+ inputSchema: {
71
+ type: "object",
72
+ properties: { name: { type: "string" }, include_source: { type: "boolean", default: false } },
73
+ required: ["name"],
74
+ additionalProperties: false,
75
+ },
76
+ annotations: { readOnlyHint: true },
77
+ handler: ({ name, include_source = false }) => text(describeItem(name, { cwd, includeSource: include_source })),
78
+ },
79
+ {
80
+ name: "get_tokens",
81
+ title: "Mlola design tokens",
82
+ description: "The --ml-* tokens grouped by purpose (planes and ink, color roles, spacing, type, density, shape, depth, motion, layers…). Filter with a group name or part of a token name.",
83
+ inputSchema: { type: "object", properties: { group: { type: "string", description: "For example 'spacing', 'color', 'shadow', 'radius'." } }, additionalProperties: false },
84
+ annotations: { readOnlyHint: true },
85
+ handler: ({ group }) => {
86
+ const found = tokens(group);
87
+ return text(found.length ? found.map((entry) => `${entry.group}: ${entry.purpose}\n ${entry.names.join(" ")}`).join("\n\n") : `No token group matches "${group}".`);
88
+ },
89
+ },
90
+ {
91
+ name: "check_markup",
92
+ title: "Check markup against the Mlola contract",
93
+ description: "Checks HTML or JSX: classes that do not exist, variant classes, data-* values a class does not react to, utility classes, hand-written colors, and misuse of data-theme or data-mode. Run it on markup you wrote before finishing.",
94
+ inputSchema: { type: "object", properties: { markup: { type: "string" } }, required: ["markup"], additionalProperties: false },
95
+ annotations: { readOnlyHint: true },
96
+ handler: ({ markup }) => {
97
+ const issues = checkMarkup(markup);
98
+ return text(issues.length ? issues : "No issues: every Mlola class exists and every data-* value is one its element reacts to.");
99
+ },
100
+ },
101
+ {
102
+ name: "add_components",
103
+ title: "Install Mlola components",
104
+ description: "Copies components into the project with the Mlola CLI (npx mlola-ui add), with their dependencies and styles. Pro items need a login first.",
105
+ inputSchema: {
106
+ type: "object",
107
+ properties: { names: { type: "array", items: { type: "string" }, minItems: 1 }, overwrite: { type: "boolean", default: false } },
108
+ required: ["names"],
109
+ additionalProperties: false,
110
+ },
111
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
112
+ handler: async ({ names, overwrite = false }) => {
113
+ const result = await capture(["add", ...names, ...(overwrite ? ["--overwrite"] : [])]);
114
+ return { ...text(result.output || "Done."), isError: result.code !== 0 };
115
+ },
116
+ },
117
+ {
118
+ name: "init_project",
119
+ title: "Set a project up for Mlola",
120
+ description: "Runs npx mlola-ui init: writes mlola.config.json, the stylesheet entry and the agent instructions (AGENTS.md, the design guide, MCP config).",
121
+ inputSchema: { type: "object", properties: {}, additionalProperties: false },
122
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true },
123
+ handler: async () => {
124
+ const result = await capture(["init"]);
125
+ return { ...text(result.output), isError: result.code !== 0 };
126
+ },
127
+ },
128
+ ];
129
+ }
130
+
131
+ function resources(cwd) {
132
+ const list = [{ uri: "mlola://guide", name: "Mlola UI design guide", description: "Classes, attributes, tokens and rules, generated from the stylesheet.", mimeType: "text/markdown" }];
133
+ if (fs.existsSync(path.join(cwd, "mlola-pro.agents.md"))) {
134
+ list.push({ uri: "mlola://guide/pro", name: "Mlola Pro design guide", description: "The classes and attributes of the Pro items installed here.", mimeType: "text/markdown" });
135
+ }
136
+ return list;
137
+ }
138
+
139
+ function readResource(uri, cwd) {
140
+ if (uri === "mlola://guide") return designGuide() ?? "";
141
+ if (uri === "mlola://guide/pro") return fs.readFileSync(path.join(cwd, "mlola-pro.agents.md"), "utf8");
142
+ throw Object.assign(new Error(`Unknown resource ${uri}`), { code: -32002 });
143
+ }
144
+
145
+ const PROMPTS = [
146
+ {
147
+ name: "build_ui",
148
+ title: "Build UI with Mlola",
149
+ description: "Build a screen or component the Mlola way, and check it.",
150
+ arguments: [{ name: "goal", description: "What to build.", required: true }],
151
+ },
152
+ ];
153
+
154
+ function prompt(name, args) {
155
+ if (name !== "build_ui") throw Object.assign(new Error(`Unknown prompt ${name}`), { code: -32602 });
156
+ return {
157
+ description: "Build UI with Mlola",
158
+ messages: [
159
+ {
160
+ role: "user",
161
+ content: {
162
+ type: "text",
163
+ text: `Build this with Mlola UI: ${args?.goal ?? ""}\n\n1. Read the rules (get_design_rules).\n2. Find the components that cover it (search_components, get_component) and install what is missing (add_components).\n3. Compose with them and the composition primitives; write CSS only for what they do not cover, reading --ml-* tokens (get_tokens).\n4. Check the markup (check_markup) and fix every issue before you finish.`,
164
+ },
165
+ },
166
+ ],
167
+ };
168
+ }
169
+
170
+ export async function serveMcp({ cwd, run, version, input = process.stdin, output = process.stdout }) {
171
+ const available = tools({ cwd, run });
172
+ const send = (message) => output.write(`${JSON.stringify(message)}\n`);
173
+ const reply = (id, result) => send({ jsonrpc: "2.0", id, result });
174
+ const fail = (id, code, message) => send({ jsonrpc: "2.0", id, error: { code, message } });
175
+
176
+ async function handle(message) {
177
+ const { id, method, params } = message ?? {};
178
+ const isRequest = id !== undefined && id !== null;
179
+ try {
180
+ switch (method) {
181
+ case "initialize": {
182
+ const requested = params?.protocolVersion;
183
+ reply(id, {
184
+ protocolVersion: PROTOCOL_VERSIONS.includes(requested) ? requested : PROTOCOL_VERSIONS[0],
185
+ capabilities: { tools: { listChanged: false }, resources: { listChanged: false }, prompts: { listChanged: false } },
186
+ serverInfo: { name: "mlola-ui", title: "Mlola UI", version },
187
+ instructions: INSTRUCTIONS,
188
+ });
189
+ return;
190
+ }
191
+ case "ping":
192
+ return reply(id, {});
193
+ case "tools/list":
194
+ return reply(id, { tools: available.map(({ handler, ...tool }) => tool) });
195
+ case "tools/call": {
196
+ const tool = available.find((entry) => entry.name === params?.name);
197
+ if (!tool) return fail(id, -32602, `Unknown tool ${params?.name}`);
198
+ try {
199
+ return reply(id, await tool.handler(params?.arguments ?? {}));
200
+ } catch (error) {
201
+ // A tool that fails answers with the reason, so the agent can correct course.
202
+ return reply(id, { ...text(error.message), isError: true });
203
+ }
204
+ }
205
+ case "resources/list":
206
+ return reply(id, { resources: resources(cwd) });
207
+ case "resources/templates/list":
208
+ return reply(id, { resourceTemplates: [] });
209
+ case "resources/read":
210
+ return reply(id, { contents: [{ uri: params?.uri, mimeType: "text/markdown", text: readResource(params?.uri, cwd) }] });
211
+ case "prompts/list":
212
+ return reply(id, { prompts: PROMPTS });
213
+ case "prompts/get":
214
+ return reply(id, prompt(params?.name, params?.arguments));
215
+ default:
216
+ if (method?.startsWith("notifications/")) return;
217
+ if (isRequest) fail(id, -32601, `Method not found: ${method}`);
218
+ }
219
+ } catch (error) {
220
+ if (isRequest) fail(id, error.code ?? -32603, error.message);
221
+ }
222
+ }
223
+
224
+ const lines = readline.createInterface({ input, crlfDelay: Infinity });
225
+ const pending = new Set();
226
+ for await (const line of lines) {
227
+ if (!line.trim()) continue;
228
+ let message;
229
+ try {
230
+ message = JSON.parse(line);
231
+ } catch {
232
+ send({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } });
233
+ continue;
234
+ }
235
+ for (const entry of Array.isArray(message) ? message : [message]) {
236
+ const task = handle(entry).finally(() => pending.delete(task));
237
+ pending.add(task);
238
+ }
239
+ }
240
+ await Promise.all(pending);
241
+ }
package/src/pro.js CHANGED
@@ -10,7 +10,7 @@ import { sha256 } from "./registry.js";
10
10
  * Pro source is never bundled. `mlola-ui login <token>` stores a token made at
11
11
  * /account (readable by the current user only); `add` sends it to the Pro
12
12
  * endpoint, which returns the items and their Pro dependencies stamped with
13
- * the licence. In CI, `MLOLA_PRO_TOKEN` takes the place of a login.
13
+ * the license. In CI, `MLOLA_PRO_TOKEN` takes the place of a login.
14
14
  */
15
15
 
16
16
  export const DEFAULT_HOST = "https://ui.mlola.com";