mlola-ui 1.0.13 → 1.1.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/package.json +3 -2
- package/registry/agents.md +2 -0
- package/registry/contract.json +4 -0
- package/registry/examples.json +29 -17
- package/registry/index.json +218 -137
- package/registry/source/packages/components/_internal/dialog.ts +30 -35
- package/registry/source/packages/components/_internal/dom.ts +14 -0
- package/registry/source/packages/components/_internal/floating.ts +13 -16
- package/registry/source/packages/components/_internal/media.ts +34 -0
- package/registry/source/packages/components/_internal/react.ts +43 -0
- package/registry/source/packages/components/accordion/accordion.tsx +11 -7
- package/registry/source/packages/components/activity-heatmap/activity-heatmap.tsx +2 -1
- package/registry/source/packages/components/alert/alert.tsx +2 -1
- package/registry/source/packages/components/app-shell/app-shell.tsx +8 -16
- package/registry/source/packages/components/avatar/avatar.tsx +4 -3
- package/registry/source/packages/components/button/button.tsx +4 -1
- package/registry/source/packages/components/card/card.tsx +8 -4
- package/registry/source/packages/components/carousel/carousel.tsx +2 -9
- package/registry/source/packages/components/color-picker/color-picker.tsx +15 -12
- package/registry/source/packages/components/combobox/combobox.tsx +18 -13
- package/registry/source/packages/components/command-menu/command-menu.tsx +5 -5
- package/registry/source/packages/components/context-menu/context-menu.tsx +1 -1
- package/registry/source/packages/components/date-picker/date-picker.tsx +12 -9
- package/registry/source/packages/components/empty-state/empty-state.tsx +5 -3
- package/registry/source/packages/components/hover-card/hover-card.tsx +3 -7
- package/registry/source/packages/components/input/input.tsx +43 -5
- package/registry/source/packages/components/modal/modal.tsx +21 -9
- package/registry/source/packages/components/number-input/number-input.tsx +11 -7
- package/registry/source/packages/components/otp-input/otp-input.tsx +19 -10
- package/registry/source/packages/components/popover/popover.tsx +2 -1
- package/registry/source/packages/components/progress/progress.tsx +14 -11
- package/registry/source/packages/components/select/select.tsx +34 -11
- package/registry/source/packages/components/sheet/sheet.tsx +22 -11
- package/registry/source/packages/components/slider/slider.tsx +8 -8
- package/registry/source/packages/components/tabs/tabs.tsx +8 -7
- package/registry/source/packages/components/tag-input/tag-input.tsx +11 -7
- package/registry/source/packages/components/textarea/textarea.tsx +5 -4
- package/registry/source/packages/components/time-picker/time-picker.tsx +12 -9
- package/registry/source/packages/components/tooltip/tooltip.tsx +4 -3
- package/registry/source/packages/components/tour/tour.tsx +6 -8
- package/registry/source/packages/components/virtual-list/virtual-list.tsx +12 -15
- package/registry/source-manifest.json +36 -34
- package/src/cli.js +8 -1
- package/src/config.js +6 -2
- package/src/doctor.js +11 -1
- package/src/mcp.js +94 -26
- package/src/theme-build.js +60 -0
- package/src/theme-pull.js +1 -1
|
@@ -1,71 +1,73 @@
|
|
|
1
1
|
{
|
|
2
2
|
"packages/components/_internal/anchor.ts": "sha256-/yW3RVjoY6QfTWnUYSeZ9rFkC9W4WGB1qop8Jp/H1r8=",
|
|
3
3
|
"packages/components/_internal/clipboard.ts": "sha256-0y5p88CTXWuOrUYlC5J4XP1VCrOwkx9jYWVHmD2LouQ=",
|
|
4
|
-
"packages/components/_internal/dialog.ts": "sha256-
|
|
5
|
-
"packages/components/_internal/
|
|
4
|
+
"packages/components/_internal/dialog.ts": "sha256-dfOsfaszM/rhBJXvl7vtM4Vdh7SGflY5Capi1YoY6ow=",
|
|
5
|
+
"packages/components/_internal/dom.ts": "sha256-TJp94MipbXHonH/PC7FFHu5pnAqcoxzLocksWLsTAsc=",
|
|
6
|
+
"packages/components/_internal/floating.ts": "sha256-8iKD2nqqwTY0grZ+KDnP81CcChrQK4CDvlDvApl0fF4=",
|
|
6
7
|
"packages/components/_internal/match.ts": "sha256-3tUYob6xmD2nLerh634E3R5iPbzMeNBzdDynsDFjj28=",
|
|
7
|
-
"packages/components/_internal/
|
|
8
|
+
"packages/components/_internal/media.ts": "sha256-ziH0YZtu8y3X7OcDI6lYxpvGJCi9zQXq4dynG8n8U9c=",
|
|
9
|
+
"packages/components/_internal/react.ts": "sha256-INfds1J2f1MLpN6kBXz0GkfBEMIiRlhv0DXX455qAZo=",
|
|
8
10
|
"packages/components/_internal/scroll.ts": "sha256-/YxhPPJa6Iax4q5eEs8dmpQp634TbvB+421MphEyscQ=",
|
|
9
|
-
"packages/components/accordion/accordion.tsx": "sha256-
|
|
10
|
-
"packages/components/activity-heatmap/activity-heatmap.tsx": "sha256-
|
|
11
|
+
"packages/components/accordion/accordion.tsx": "sha256-NodsdXiIL5VE7C2m3C0IC854lNUOIWgQQmmSKr6v59E=",
|
|
12
|
+
"packages/components/activity-heatmap/activity-heatmap.tsx": "sha256-nHXmfiLpZOH+PrqE1/fyIjevjyPD2/8hSPZRn/+cYcY=",
|
|
11
13
|
"packages/components/activity-heatmap/calendar.ts": "sha256-NB+AjXY5Xc+KzR8MMTRBgZxdzOeoHhkW0KQOW7WJPuQ=",
|
|
12
|
-
"packages/components/alert/alert.tsx": "sha256-
|
|
13
|
-
"packages/components/app-shell/app-shell.tsx": "sha256-
|
|
14
|
-
"packages/components/avatar/avatar.tsx": "sha256-
|
|
14
|
+
"packages/components/alert/alert.tsx": "sha256-vQ0pvYOpLmafcglpwub6aHLjDPSP4N8qoTCwjo5aEi8=",
|
|
15
|
+
"packages/components/app-shell/app-shell.tsx": "sha256-slT7UclIZtl2xu7MALrR/i52BHGOR05eCwFTD1mh9AI=",
|
|
16
|
+
"packages/components/avatar/avatar.tsx": "sha256-61GyO7vZiTEe05z8si1gXKrzgm7I9kN+OFdgk0H8u4Y=",
|
|
15
17
|
"packages/components/badge/badge.tsx": "sha256-flEH8S2jZ/64n3Y/ROhC9+9rvF7wXaN0xLcMMwqNOVQ=",
|
|
16
18
|
"packages/components/breadcrumb/breadcrumb.tsx": "sha256-mxQpach2kFayT8H1vMt5252LQLxVV+sohdVXDhPuKoU=",
|
|
17
|
-
"packages/components/button/button.tsx": "sha256-
|
|
19
|
+
"packages/components/button/button.tsx": "sha256-sMEaFncanTcURNkkrEH6MK87wrY/HtJSSzqpY8kxaK8=",
|
|
18
20
|
"packages/components/calendar/calendar.tsx": "sha256-rWiS4YAueZfVoo6j5Fi3uN7A+yApe5U6HMm1woTVjys=",
|
|
19
21
|
"packages/components/calendar/dates.ts": "sha256-mygYkoC70av2ZQ0vkxf885trwW97Wd10I4dptYQe+4A=",
|
|
20
|
-
"packages/components/card/card.tsx": "sha256-
|
|
21
|
-
"packages/components/carousel/carousel.tsx": "sha256-
|
|
22
|
+
"packages/components/card/card.tsx": "sha256-YNqJWotnswv0UiYGtmJV9KxylKopXC0EZD7hTt8eQ0E=",
|
|
23
|
+
"packages/components/carousel/carousel.tsx": "sha256-WxvQoG9/u190ka4x6cpZT8ZS6XyhEaAlJZ/JyXG+ACU=",
|
|
22
24
|
"packages/components/checkbox/checkbox.tsx": "sha256-hz4YbGwOxv/aJpJHrLXR4MKSWJGyjzoJEIGvdhyML9M=",
|
|
23
|
-
"packages/components/color-picker/color-picker.tsx": "sha256
|
|
25
|
+
"packages/components/color-picker/color-picker.tsx": "sha256-/UyEbxqknsHheX444Imc3WphyixZbvj8hGL+r+zyG5w=",
|
|
24
26
|
"packages/components/color-picker/color.ts": "sha256-YqSgYjzn7XfPWAHuyzbwOhbg/ScaSfB7V73e9w+OjJs=",
|
|
25
|
-
"packages/components/combobox/combobox.tsx": "sha256-
|
|
26
|
-
"packages/components/command-menu/command-menu.tsx": "sha256-
|
|
27
|
-
"packages/components/context-menu/context-menu.tsx": "sha256-
|
|
27
|
+
"packages/components/combobox/combobox.tsx": "sha256-fiK8UpFpI5G1tZGFXiUmjMvCWcL8aW7qy9FUaMmAqhw=",
|
|
28
|
+
"packages/components/command-menu/command-menu.tsx": "sha256-wZnlj17zVlcfp5I08ziggHtchfdqjlvNl+MyiP+Rdbo=",
|
|
29
|
+
"packages/components/context-menu/context-menu.tsx": "sha256-OGaKzthZJGaV5T+4sRWYecA64wEEg2KImKnIzMMg1i4=",
|
|
28
30
|
"packages/components/copy-button/copy-button.tsx": "sha256-6fvfifoiDOtW+WxKWon4SGEn3THtERuCX2hqGSZDtVM=",
|
|
29
|
-
"packages/components/date-picker/date-picker.tsx": "sha256-
|
|
31
|
+
"packages/components/date-picker/date-picker.tsx": "sha256-QABmZzPKHc706/Rs+ln3ifDK3T2lXl34rehhuRXW+8o=",
|
|
30
32
|
"packages/components/dropdown-menu/dropdown-menu.tsx": "sha256-xgybCu9HBxIieafu5CjAtY4aCOQoaAj/VUCH6D6vLP8=",
|
|
31
33
|
"packages/components/dropzone/dropzone.tsx": "sha256-tHCCpEWE6839UeLOszTo+R2vAL4Sp9UxuRdLkFLfnq4=",
|
|
32
|
-
"packages/components/empty-state/empty-state.tsx": "sha256
|
|
33
|
-
"packages/components/hover-card/hover-card.tsx": "sha256-
|
|
34
|
-
"packages/components/input/input.tsx": "sha256-
|
|
34
|
+
"packages/components/empty-state/empty-state.tsx": "sha256-/phk3A6TDaiqo61fCcAc/4Bi85e1yBGAtKXFaGhrjyw=",
|
|
35
|
+
"packages/components/hover-card/hover-card.tsx": "sha256-Bu5m03mFjiM7x38U1aSrcb9Th8RLYozyjwW/Cu3knlk=",
|
|
36
|
+
"packages/components/input/input.tsx": "sha256-K0YrpEsEJZTzw64t+Cyg/OGYwlU6LU1/yXTNUxYUIAI=",
|
|
35
37
|
"packages/components/kbd/kbd.tsx": "sha256-e8EO2Gw6JiUyrU0YcEZOaQNMel1pe/EcJrxwwgd1Ggk=",
|
|
36
|
-
"packages/components/modal/modal.tsx": "sha256-
|
|
37
|
-
"packages/components/number-input/number-input.tsx": "sha256-
|
|
38
|
+
"packages/components/modal/modal.tsx": "sha256-gqrMncs7lEgUjsWXXEl5FYIUoYAxj93gcVh1pZTuatQ=",
|
|
39
|
+
"packages/components/number-input/number-input.tsx": "sha256-mo7rVoOQ4p/I0Hhx5w+SwXUtl3sI4VOt3QX9FwdB2q4=",
|
|
38
40
|
"packages/components/number-input/number.ts": "sha256-IrHCQrEhI5ein9ifMFuSViSoxQUBoT3tNmmCu/paGGM=",
|
|
39
|
-
"packages/components/otp-input/otp-input.tsx": "sha256-
|
|
41
|
+
"packages/components/otp-input/otp-input.tsx": "sha256-0KeyDLCC27/GqOocMWXggJ255PCEpCOCcINAcabksSk=",
|
|
40
42
|
"packages/components/pagination/pagination.tsx": "sha256-FBwk8tq4JcsK2D0dlWEYiDSRx3owgBmGzEIlVRaNBoE=",
|
|
41
43
|
"packages/components/password-input/password-input.tsx": "sha256-oY//vTILhUicJyfAWZrSUTl8WWsf4P2S1Bh0x4OxwLs=",
|
|
42
|
-
"packages/components/popover/popover.tsx": "sha256
|
|
44
|
+
"packages/components/popover/popover.tsx": "sha256-NYKKNo0sO3jvR5W4iuIlnTw7B+xyOhMZczAtQG/lmw4=",
|
|
43
45
|
"packages/components/priority-icon/labels.ts": "sha256-zhmJIb7/gS16wU+xCJ7hkReBi9q5RI3SMwdjkJvvvKU=",
|
|
44
46
|
"packages/components/priority-icon/priority-icon.tsx": "sha256-3D6hFRpht3YuWouLhakE2DFdldavmJx0B/povEDwrtQ=",
|
|
45
|
-
"packages/components/progress/progress.tsx": "sha256-
|
|
47
|
+
"packages/components/progress/progress.tsx": "sha256-oDxj1Ccl1pKV4+e6A7aaJL1FThW8qs9r4Gb0hEmExeM=",
|
|
46
48
|
"packages/components/radio-group/radio-group.tsx": "sha256-jhnSEBLkdpe8dv5us/Q1sMsQF4sKGRgDXWGKm2JSFew=",
|
|
47
49
|
"packages/components/rating/rating.tsx": "sha256-zgPOQXzAiRE4qGNPQ8qat3SP1XSI/7567kA5VaT51P0=",
|
|
48
50
|
"packages/components/resizable/resizable.tsx": "sha256-Yxlq88qElhv2Q5DooPdriZkawRS0QWx08nd6ybz5oeY=",
|
|
49
51
|
"packages/components/segmented-control/segmented-control.tsx": "sha256-MCBG5XHFWCwbACctsYKbqe8mvSSR6pf60tyRgdCDKDA=",
|
|
50
|
-
"packages/components/select/select.tsx": "sha256-
|
|
51
|
-
"packages/components/sheet/sheet.tsx": "sha256-
|
|
52
|
+
"packages/components/select/select.tsx": "sha256-WfS//CI0wnqhsQvglElqPK1MOL5vwjSaBnaC1BpWVVc=",
|
|
53
|
+
"packages/components/sheet/sheet.tsx": "sha256-gmc8rZ87NHfN/bErEWHm6HlaAoeNT/Fw6y2PrW88WFI=",
|
|
52
54
|
"packages/components/skeleton/skeleton.tsx": "sha256-2Fk72NbLr3+CrXH7jGbHNsc3t+vskMqSyySe/UwbGCs=",
|
|
53
|
-
"packages/components/slider/slider.tsx": "sha256-
|
|
55
|
+
"packages/components/slider/slider.tsx": "sha256-tqffrl4r0kxrVFqX1ExBHZKzenVFAZcOCQl8khJ/QEU=",
|
|
54
56
|
"packages/components/spinner/spinner.tsx": "sha256-FZQmcGkQ8N0485n9bYsLTpTnolbM729YKjkQrXriz9A=",
|
|
55
57
|
"packages/components/status-icon/labels.ts": "sha256-EE8q+geQBbJUs7I3RmaHZnPwn7Vjk2GjCfxvI4ruXKM=",
|
|
56
58
|
"packages/components/status-icon/status-icon.tsx": "sha256-TtrTeoePyfH1VhKuWC0v12FuwfgND1CHnH5x8fyf4AM=",
|
|
57
59
|
"packages/components/stepper/stepper.tsx": "sha256-uI/dRmkbijlixHiGUceWcpaDzpEixrzQ1UTmoPbGZAY=",
|
|
58
60
|
"packages/components/table/table.tsx": "sha256-xJlWzCH76Lrgg/HrlnVTHkG5dfCkbzjWXEdTVG5Jk4w=",
|
|
59
|
-
"packages/components/tabs/tabs.tsx": "sha256-
|
|
60
|
-
"packages/components/tag-input/tag-input.tsx": "sha256-
|
|
61
|
-
"packages/components/textarea/textarea.tsx": "sha256-
|
|
62
|
-
"packages/components/time-picker/time-picker.tsx": "sha256-
|
|
61
|
+
"packages/components/tabs/tabs.tsx": "sha256-Wp0WhVFPwaX3aIGXNJBbSGNWPMT6X/Sbl+kE+zZSyvk=",
|
|
62
|
+
"packages/components/tag-input/tag-input.tsx": "sha256-LnOwotLuPB06nlkdzckjDVhk03D6c/exJXJs2blZNRQ=",
|
|
63
|
+
"packages/components/textarea/textarea.tsx": "sha256-zculOGfrK4LVcWne3+jS+7qKcfJPM/25vJdm7AXOJfk=",
|
|
64
|
+
"packages/components/time-picker/time-picker.tsx": "sha256-HquGogkQB7YGs/1iTSqftSJ310hgXqKdAc+fU1RtQ5Y=",
|
|
63
65
|
"packages/components/time-picker/times.ts": "sha256-V/4uqFZPn9gVnyx8cB9vwp/RocmTYaPmLxjfy8DPQOI=",
|
|
64
66
|
"packages/components/timeline/timeline.tsx": "sha256-nVg7lBcAUAzqEb0YrHagJXSaHE2aWWE9bUbQMWQY6PU=",
|
|
65
67
|
"packages/components/toast/toast.tsx": "sha256-f0DwN0XuV46fSsQf3i9RJcfQbAs2mSz7A8UJoIIICHg=",
|
|
66
68
|
"packages/components/toggle/toggle.tsx": "sha256-8qPHeSliHNdzQS7OLJ47BrvRR75MYyUGJlK4s21XS/U=",
|
|
67
|
-
"packages/components/tooltip/tooltip.tsx": "sha256-
|
|
68
|
-
"packages/components/tour/tour.tsx": "sha256-
|
|
69
|
-
"packages/components/virtual-list/virtual-list.tsx": "sha256-
|
|
69
|
+
"packages/components/tooltip/tooltip.tsx": "sha256-OEghEwKydt5SU7XNGuQxmmy+S7YyLKu02pPQeoNZQKw=",
|
|
70
|
+
"packages/components/tour/tour.tsx": "sha256-b2XJokzSio2N9MFDbAbXn6w2f7diJb1KWedSCycRMmE=",
|
|
71
|
+
"packages/components/virtual-list/virtual-list.tsx": "sha256-oA8xFCwUgskmM+symA4gZxFqSyBCpeKfyM5zHyJaYaQ=",
|
|
70
72
|
"packages/components/virtual-list/virtual.ts": "sha256-PyDIj3C2Vwb1i4ZssxSlYjgKmZPBIFLZFNq8YCW2Z78="
|
|
71
73
|
}
|
package/src/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ import { loadCatalogNames, loadRegistry, resolveItems } from "./registry.js";
|
|
|
14
14
|
import { clearCredentials, fetchProItems, GUIDE_FILENAME, hostFrom, readCredentials, saveCredentials, verifyToken, writeProGuide, writeProStyles } from "./pro.js";
|
|
15
15
|
import { serveMcp } from "./mcp.js";
|
|
16
16
|
import { pullTheme } from "./theme-pull.js";
|
|
17
|
+
import { buildTheme } from "./theme-build.js";
|
|
17
18
|
|
|
18
19
|
const HELP = `Mlola UI — source-copy components with Theme
|
|
19
20
|
|
|
@@ -28,6 +29,7 @@ Usage:
|
|
|
28
29
|
mlola-ui list [--kind component|block|template|asset] [--json]
|
|
29
30
|
mlola-ui doctor
|
|
30
31
|
mlola-ui theme pull <theme-id | url> [--host <url>] [--overwrite]
|
|
32
|
+
mlola-ui theme build [--file mlola.theme.json]
|
|
31
33
|
|
|
32
34
|
Examples:
|
|
33
35
|
npx mlola-ui init (also tells your coding agents about Mlola)
|
|
@@ -37,6 +39,7 @@ Examples:
|
|
|
37
39
|
npx mlola-ui add asset empty-inbox orb
|
|
38
40
|
npx mlola-ui list --kind block
|
|
39
41
|
npx mlola-ui theme pull https://ui.mlola.com/t/th-4k2x9qf7wz3m
|
|
42
|
+
npx mlola-ui theme build (renders your mlola.theme.json, offline)
|
|
40
43
|
npx mlola-ui mcp (an MCP server for Claude Code, Cursor, Codex…)
|
|
41
44
|
`;
|
|
42
45
|
|
|
@@ -301,7 +304,11 @@ export async function run(argv, options = {}) {
|
|
|
301
304
|
|
|
302
305
|
if (command === "theme") {
|
|
303
306
|
const [subcommand, reference] = args;
|
|
304
|
-
if (subcommand
|
|
307
|
+
if (subcommand === "build") {
|
|
308
|
+
await buildTheme(cwd, { file: optionValue(args, "--file") ?? undefined, output });
|
|
309
|
+
return 0;
|
|
310
|
+
}
|
|
311
|
+
if (subcommand !== "pull") throw new Error(`Unknown theme command "${subcommand ?? ""}". Try: mlola-ui theme build, or mlola-ui theme pull <theme-id | url>`);
|
|
305
312
|
await pullTheme(cwd, reference, {
|
|
306
313
|
host: optionValue(args, "--host") ?? (process.env.MLOLA_STUDIO_URL || undefined),
|
|
307
314
|
overwrite: hasFlag(args, "--overwrite"),
|
package/src/config.js
CHANGED
|
@@ -35,7 +35,9 @@ export const DEFAULT_CONFIG = {
|
|
|
35
35
|
};
|
|
36
36
|
|
|
37
37
|
const REQUIRED_ALIASES = ["components", "blocks", "templates", "lib", "styles"];
|
|
38
|
-
const THEMES = new Set(["graphite", "atelier", "machined", "aerogel", "nordic"]);
|
|
38
|
+
export const THEMES = new Set(["graphite", "atelier", "machined", "aerogel", "nordic"]);
|
|
39
|
+
/** A project theme's id, as mlola.theme.json and data-theme spell it. */
|
|
40
|
+
const THEME_ID = /^[a-z][a-z0-9-]{0,47}$/;
|
|
39
41
|
|
|
40
42
|
export function configPath(cwd) {
|
|
41
43
|
return path.join(cwd, CONFIG_FILENAME);
|
|
@@ -45,7 +47,9 @@ export function validateConfig(config) {
|
|
|
45
47
|
const errors = [];
|
|
46
48
|
if (!config || typeof config !== "object") return ["configuration must be an object"];
|
|
47
49
|
if (config.version !== 1) errors.push(`version must be 1 (received ${config.version})`);
|
|
48
|
-
if (!THEMES.has(config.theme)
|
|
50
|
+
if (typeof config.theme !== "string" || !(THEMES.has(config.theme) || THEME_ID.test(config.theme))) {
|
|
51
|
+
errors.push(`theme must be one of ${[...THEMES].join(", ")}, or your own theme's id from mlola.theme.json (lowercase letters, digits and dashes)`);
|
|
52
|
+
}
|
|
49
53
|
for (const key of REQUIRED_ALIASES) {
|
|
50
54
|
if (typeof config.aliases?.[key] !== "string" || !config.aliases[key]) {
|
|
51
55
|
errors.push(`aliases.${key} must be a non-empty string`);
|
package/src/doctor.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { interpolateTarget, loadConfig, targetRoot } from "./config.js";
|
|
3
|
+
import { THEMES, interpolateTarget, loadConfig, targetRoot } from "./config.js";
|
|
4
4
|
import { createTransformContext, expectedFile } from "./installer.js";
|
|
5
5
|
import { loadRegistry, readJson, sha256, sourcePath } from "./registry.js";
|
|
6
6
|
|
|
@@ -110,6 +110,16 @@ export function runDoctor(cwd) {
|
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
112
|
|
|
113
|
+
// A project theme is rendered into the stylesheet, by `theme build` or `theme pull`.
|
|
114
|
+
if (!THEMES.has(config.theme)) {
|
|
115
|
+
const themeCss = path.join(cwd, targetRoot(config, "styles"), "mlola", "theme.css");
|
|
116
|
+
if (fs.existsSync(themeCss) && fs.readFileSync(themeCss, "utf8").includes(`[data-theme="${config.theme}"]`)) {
|
|
117
|
+
findings.push(finding("pass", `the project theme "${config.theme}" is rendered in ${path.relative(cwd, themeCss)}`));
|
|
118
|
+
} else {
|
|
119
|
+
findings.push(finding("warn", `the theme "${config.theme}" is not rendered yet; run mlola-ui theme build`));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
113
123
|
if (projectContains(cwd, /\bdata-theme\s*=/)) {
|
|
114
124
|
findings.push(finding("pass", "data-theme is present in application source"));
|
|
115
125
|
} else if (projectContains(cwd, /\bdata-skin\s*=/)) {
|
package/src/mcp.js
CHANGED
|
@@ -23,17 +23,13 @@ const PROTOCOL_VERSIONS = ["2025-11-25", "2025-06-18", "2025-03-26", "2024-11-05
|
|
|
23
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
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
25
|
|
|
26
|
+
const REMOTE_INSTRUCTIONS = `Mlola UI is a component system on native CSS classes (ml-*), data-* attributes for state and variant, and --ml-* tokens. There is no Tailwind.
|
|
27
|
+
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. After writing markup, run check_markup and fix what it reports. This server cannot write files: get_install_command gives the commands to run in the project, and Mlola Pro items need a license token (npx mlola-ui login).`;
|
|
28
|
+
|
|
26
29
|
const text = (value) => ({ content: [{ type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }] });
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
const lines = [];
|
|
31
|
-
const output = { log: (...parts) => lines.push(parts.join(" ")), error: (...parts) => lines.push(parts.join(" ")) };
|
|
32
|
-
// stdout is this server's protocol channel: a package manager started by
|
|
33
|
-
// init or add writes to stderr and reads nothing.
|
|
34
|
-
const code = await run(argv, { cwd, output, installStdio: ["ignore", 2, 2] });
|
|
35
|
-
return { code, output: lines.join("\n") };
|
|
36
|
-
};
|
|
31
|
+
/** Tools that only read the registry: the local and the remote server share them. */
|
|
32
|
+
function readTools({ cwd }) {
|
|
37
33
|
return [
|
|
38
34
|
{
|
|
39
35
|
name: "get_design_rules",
|
|
@@ -100,6 +96,20 @@ function tools({ cwd, run }) {
|
|
|
100
96
|
return text(issues.length ? issues : "No issues: every Mlola class exists and every data-* value is one its element reacts to.");
|
|
101
97
|
},
|
|
102
98
|
},
|
|
99
|
+
];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function tools({ cwd, run }) {
|
|
103
|
+
const capture = async (argv) => {
|
|
104
|
+
const lines = [];
|
|
105
|
+
const output = { log: (...parts) => lines.push(parts.join(" ")), error: (...parts) => lines.push(parts.join(" ")) };
|
|
106
|
+
// stdout is this server's protocol channel: a package manager started by
|
|
107
|
+
// init or add writes to stderr and reads nothing.
|
|
108
|
+
const code = await run(argv, { cwd, output, installStdio: ["ignore", 2, 2] });
|
|
109
|
+
return { code, output: lines.join("\n") };
|
|
110
|
+
};
|
|
111
|
+
return [
|
|
112
|
+
...readTools({ cwd }),
|
|
103
113
|
{
|
|
104
114
|
name: "add_components",
|
|
105
115
|
title: "Install Mlola components",
|
|
@@ -169,26 +179,27 @@ function prompt(name, args) {
|
|
|
169
179
|
};
|
|
170
180
|
}
|
|
171
181
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
182
|
+
/**
|
|
183
|
+
* The protocol, apart from any transport: takes one JSON-RPC message and
|
|
184
|
+
* resolves to the reply, or to null for a notification. The stdio server
|
|
185
|
+
* here and the site's HTTP endpoint both answer through it.
|
|
186
|
+
*/
|
|
187
|
+
export function createMcpHandler({ tools: available, listResources, readResource: read, version, instructions }) {
|
|
188
|
+
const reply = (id, result) => ({ jsonrpc: "2.0", id, result });
|
|
189
|
+
const fail = (id, code, message) => ({ jsonrpc: "2.0", id, error: { code, message } });
|
|
190
|
+
return async function handle(message) {
|
|
179
191
|
const { id, method, params } = message ?? {};
|
|
180
192
|
const isRequest = id !== undefined && id !== null;
|
|
181
193
|
try {
|
|
182
194
|
switch (method) {
|
|
183
195
|
case "initialize": {
|
|
184
196
|
const requested = params?.protocolVersion;
|
|
185
|
-
reply(id, {
|
|
197
|
+
return reply(id, {
|
|
186
198
|
protocolVersion: PROTOCOL_VERSIONS.includes(requested) ? requested : PROTOCOL_VERSIONS[0],
|
|
187
199
|
capabilities: { tools: { listChanged: false }, resources: { listChanged: false }, prompts: { listChanged: false } },
|
|
188
200
|
serverInfo: { name: "mlola-ui", title: "Mlola UI", version },
|
|
189
|
-
instructions
|
|
201
|
+
instructions,
|
|
190
202
|
});
|
|
191
|
-
return;
|
|
192
203
|
}
|
|
193
204
|
case "ping":
|
|
194
205
|
return reply(id, {});
|
|
@@ -205,23 +216,76 @@ export async function serveMcp({ cwd, run, version, input = process.stdin, outpu
|
|
|
205
216
|
}
|
|
206
217
|
}
|
|
207
218
|
case "resources/list":
|
|
208
|
-
return reply(id, { resources:
|
|
219
|
+
return reply(id, { resources: listResources() });
|
|
209
220
|
case "resources/templates/list":
|
|
210
221
|
return reply(id, { resourceTemplates: [] });
|
|
211
222
|
case "resources/read":
|
|
212
|
-
return reply(id, { contents: [{ uri: params?.uri, mimeType: "text/markdown", text:
|
|
223
|
+
return reply(id, { contents: [{ uri: params?.uri, mimeType: "text/markdown", text: read(params?.uri) }] });
|
|
213
224
|
case "prompts/list":
|
|
214
225
|
return reply(id, { prompts: PROMPTS });
|
|
215
226
|
case "prompts/get":
|
|
216
227
|
return reply(id, prompt(params?.name, params?.arguments));
|
|
217
228
|
default:
|
|
218
|
-
if (method?.startsWith("notifications/")) return;
|
|
219
|
-
|
|
229
|
+
if (method?.startsWith("notifications/")) return null;
|
|
230
|
+
return isRequest ? fail(id, -32601, `Method not found: ${method}`) : null;
|
|
220
231
|
}
|
|
221
232
|
} catch (error) {
|
|
222
|
-
|
|
233
|
+
return isRequest ? fail(id, error.code ?? -32603, error.message) : null;
|
|
223
234
|
}
|
|
224
|
-
}
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* The server ui.mlola.com/mcp runs: the read-only tools, and install
|
|
240
|
+
* commands instead of installs, since a remote server cannot write to the
|
|
241
|
+
* project. Mlola Pro items are described; their source needs a license and
|
|
242
|
+
* arrives through `npx mlola-ui add`.
|
|
243
|
+
*/
|
|
244
|
+
export function remoteMcpHandler({ version }) {
|
|
245
|
+
const cwd = process.cwd();
|
|
246
|
+
const install = {
|
|
247
|
+
name: "get_install_command",
|
|
248
|
+
title: "How to install Mlola components",
|
|
249
|
+
description: "The commands to run in the project to set Mlola up and add components, blocks, pages or templates, including the license step for Mlola Pro items.",
|
|
250
|
+
inputSchema: { type: "object", properties: { names: { type: "array", items: { type: "string" }, minItems: 1 } }, required: ["names"], additionalProperties: false },
|
|
251
|
+
annotations: { readOnlyHint: true },
|
|
252
|
+
handler: ({ names }) => {
|
|
253
|
+
const found = names.map((name) => searchItems({ query: name }).find((item) => item.name === name));
|
|
254
|
+
const unknown = names.filter((_, index) => !found[index]);
|
|
255
|
+
if (unknown.length) return { ...text(`No Mlola item is called ${unknown.join(", ")}. Find names with search_components.`), isError: true };
|
|
256
|
+
const pro = found.filter((item) => item.tier === "pro").map((item) => item.name);
|
|
257
|
+
return text([
|
|
258
|
+
"Run in the project:",
|
|
259
|
+
"",
|
|
260
|
+
"npx mlola-ui init # once: config, stylesheet, the engine, and agent instructions",
|
|
261
|
+
...(pro.length ? [`npx mlola-ui login <token> # ${pro.join(", ")} ${pro.length === 1 ? "is" : "are"} Mlola Pro: create a token at https://ui.mlola.com/account`] : []),
|
|
262
|
+
`npx mlola-ui add ${names.join(" ")}`,
|
|
263
|
+
"",
|
|
264
|
+
'Then import styles/mlola/index.css once and set data-theme="graphite" (or atelier, machined, aerogel, nordic) on the root element.',
|
|
265
|
+
].join("\n"));
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
return createMcpHandler({
|
|
269
|
+
tools: [...readTools({ cwd }), install],
|
|
270
|
+
listResources: () => [{ uri: "mlola://guide", name: "Mlola UI design guide", description: "Classes, attributes, tokens and rules, generated from the stylesheet.", mimeType: "text/markdown" }],
|
|
271
|
+
readResource: (uri) => {
|
|
272
|
+
if (uri === "mlola://guide") return designGuide() ?? "";
|
|
273
|
+
throw Object.assign(new Error(`Unknown resource ${uri}`), { code: -32002 });
|
|
274
|
+
},
|
|
275
|
+
version,
|
|
276
|
+
instructions: REMOTE_INSTRUCTIONS,
|
|
277
|
+
});
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
export async function serveMcp({ cwd, run, version, input = process.stdin, output = process.stdout }) {
|
|
281
|
+
const handle = createMcpHandler({
|
|
282
|
+
tools: tools({ cwd, run }),
|
|
283
|
+
listResources: () => resources(cwd),
|
|
284
|
+
readResource: (uri) => readResource(uri, cwd),
|
|
285
|
+
version,
|
|
286
|
+
instructions: INSTRUCTIONS,
|
|
287
|
+
});
|
|
288
|
+
const send = (message) => output.write(`${JSON.stringify(message)}\n`);
|
|
225
289
|
|
|
226
290
|
const lines = readline.createInterface({ input, crlfDelay: Infinity });
|
|
227
291
|
const pending = new Set();
|
|
@@ -235,7 +299,11 @@ export async function serveMcp({ cwd, run, version, input = process.stdin, outpu
|
|
|
235
299
|
continue;
|
|
236
300
|
}
|
|
237
301
|
for (const entry of Array.isArray(message) ? message : [message]) {
|
|
238
|
-
const task = handle(entry)
|
|
302
|
+
const task = handle(entry)
|
|
303
|
+
.then((response) => {
|
|
304
|
+
if (response) send(response);
|
|
305
|
+
})
|
|
306
|
+
.finally(() => pending.delete(task));
|
|
239
307
|
pending.add(task);
|
|
240
308
|
}
|
|
241
309
|
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createRequire } from "node:module";
|
|
4
|
+
import { pathToFileURL } from "node:url";
|
|
5
|
+
import { DEFAULT_CONFIG, loadConfig, targetRoot } from "./config.js";
|
|
6
|
+
import { ensureImport } from "./theme-pull.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* `mlola-ui theme build`: render the project's own theme, offline.
|
|
10
|
+
*
|
|
11
|
+
* Reads mlola.theme.json (written by hand or pulled from the Studio) and
|
|
12
|
+
* renders it with the engine installed in the project, the same function
|
|
13
|
+
* that renders every canonical theme, so its palette is solved for contrast
|
|
14
|
+
* the same way. Writes <styles>/mlola/theme.css, imported from index.css,
|
|
15
|
+
* which is where `theme pull` puts a Studio theme.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
export const THEME_FILENAME = "mlola.theme.json";
|
|
19
|
+
|
|
20
|
+
/** The engine's theme renderer, from the project's own node_modules. */
|
|
21
|
+
async function projectEngine(cwd, engine) {
|
|
22
|
+
const require = createRequire(path.join(cwd, "package.json"));
|
|
23
|
+
let resolved;
|
|
24
|
+
try {
|
|
25
|
+
resolved = require.resolve(`${engine}/theme`);
|
|
26
|
+
} catch {
|
|
27
|
+
throw new Error(`Building a theme needs ${engine} 1.1 or later in this project. Install it: npm install ${engine}@latest`);
|
|
28
|
+
}
|
|
29
|
+
return import(pathToFileURL(resolved).href);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export async function buildTheme(cwd, { file = THEME_FILENAME, output = console, loadEngine = projectEngine } = {}) {
|
|
33
|
+
const themeFile = path.resolve(cwd, file);
|
|
34
|
+
if (!fs.existsSync(themeFile)) {
|
|
35
|
+
throw new Error(`No ${file} in ${cwd}. Write one (https://ui.mlola.com/docs/theming#custom) or pull one from the Studio: mlola-ui theme pull <theme-id>`);
|
|
36
|
+
}
|
|
37
|
+
let spec;
|
|
38
|
+
try {
|
|
39
|
+
spec = JSON.parse(fs.readFileSync(themeFile, "utf8"));
|
|
40
|
+
} catch (error) {
|
|
41
|
+
throw new Error(`${file} is not valid JSON: ${error.message}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const config = fs.existsSync(path.join(cwd, "mlola.config.json")) ? loadConfig(cwd) : DEFAULT_CONFIG;
|
|
45
|
+
const engine = await loadEngine(cwd, config.engine?.engine ?? "@mlola-ui/engine");
|
|
46
|
+
const { id } = engine.defineTheme(spec);
|
|
47
|
+
const css = engine.renderThemeCss(spec);
|
|
48
|
+
|
|
49
|
+
const stylesDirectory = path.join(cwd, targetRoot(config, "styles"), "mlola");
|
|
50
|
+
fs.mkdirSync(stylesDirectory, { recursive: true });
|
|
51
|
+
fs.writeFileSync(path.join(stylesDirectory, "theme.css"), css);
|
|
52
|
+
const imported = ensureImport(path.join(stylesDirectory, "index.css"), '@import "./theme.css";');
|
|
53
|
+
|
|
54
|
+
const stylesRelative = path.relative(cwd, stylesDirectory);
|
|
55
|
+
output.log(`✓ Built ${path.join(stylesRelative, "theme.css")} from ${path.relative(cwd, themeFile)}`);
|
|
56
|
+
if (imported) output.log(`✓ Imported it from ${path.join(stylesRelative, "index.css")}`);
|
|
57
|
+
if (config.theme !== id) output.log(`Set "theme": "${id}" in mlola.config.json, and data-theme="${id}" on <html> or any container.`);
|
|
58
|
+
else output.log(`Set data-theme="${id}" on <html> or any container.`);
|
|
59
|
+
return { id };
|
|
60
|
+
}
|
package/src/theme-pull.js
CHANGED
|
@@ -45,7 +45,7 @@ async function fetchText(url, fetcher) {
|
|
|
45
45
|
return response.text();
|
|
46
46
|
}
|
|
47
47
|
|
|
48
|
-
function ensureImport(indexFile, line) {
|
|
48
|
+
export function ensureImport(indexFile, line) {
|
|
49
49
|
const current = fs.existsSync(indexFile) ? fs.readFileSync(indexFile, "utf8") : "";
|
|
50
50
|
if (current.includes(line)) return false;
|
|
51
51
|
fs.mkdirSync(path.dirname(indexFile), { recursive: true });
|