mlola-ui 1.0.13 → 1.1.1
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.json +1 -1
- package/registry/agents.md +6 -1
- package/registry/contract.json +9 -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 +11 -3
- package/src/config.js +6 -2
- package/src/doctor.js +11 -1
- package/src/mcp.js +94 -26
- package/src/pro.js +25 -5
- 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
|
@@ -11,9 +11,10 @@ import { addAssets, assetHint, loadAssetIndex } from "./assets.js";
|
|
|
11
11
|
import { runDoctor } from "./doctor.js";
|
|
12
12
|
import { collectEngineDependencies, installItems } from "./installer.js";
|
|
13
13
|
import { loadCatalogNames, loadRegistry, resolveItems } from "./registry.js";
|
|
14
|
-
import { clearCredentials, fetchProItems, GUIDE_FILENAME, hostFrom, readCredentials, saveCredentials, verifyToken, writeProGuide, writeProStyles } from "./pro.js";
|
|
14
|
+
import { catalogStylesheets, 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
|
|
|
@@ -148,6 +151,7 @@ export async function run(argv, options = {}) {
|
|
|
148
151
|
output.log(
|
|
149
152
|
`Set data-theme="${config.theme}" on your document root and import ${stylesResult.relative}.`,
|
|
150
153
|
);
|
|
154
|
+
output.log(`Add data-mode="system" to follow the reader's light or dark setting, and load the theme's fonts: https://ui.mlola.com/docs#fonts-and-mode`);
|
|
151
155
|
if (!hasFlag(args, "--no-agents")) reportAgents(writeAgentFiles(cwd), output);
|
|
152
156
|
return ensurePackages(cwd, new Map([[config.engine.engine ?? "@mlola-ui/engine", `^${CLI_VERSION}`]]), install);
|
|
153
157
|
}
|
|
@@ -270,7 +274,7 @@ export async function run(argv, options = {}) {
|
|
|
270
274
|
|
|
271
275
|
const items = resolveItems(registry, names, catalogNames);
|
|
272
276
|
const result = installItems(cwd, registry, items, config, { overwrite });
|
|
273
|
-
const styleResult = writeProStyles(cwd, config, styles, { overwrite });
|
|
277
|
+
const styleResult = writeProStyles(cwd, config, styles, { overwrite, imports: catalogStylesheets(cwd, config, items) });
|
|
274
278
|
result.written.push(...styleResult.written);
|
|
275
279
|
result.unchanged.push(...styleResult.unchanged);
|
|
276
280
|
result.conflicts.push(...styleResult.conflicts);
|
|
@@ -301,7 +305,11 @@ export async function run(argv, options = {}) {
|
|
|
301
305
|
|
|
302
306
|
if (command === "theme") {
|
|
303
307
|
const [subcommand, reference] = args;
|
|
304
|
-
if (subcommand
|
|
308
|
+
if (subcommand === "build") {
|
|
309
|
+
await buildTheme(cwd, { file: optionValue(args, "--file") ?? undefined, output });
|
|
310
|
+
return 0;
|
|
311
|
+
}
|
|
312
|
+
if (subcommand !== "pull") throw new Error(`Unknown theme command "${subcommand ?? ""}". Try: mlola-ui theme build, or mlola-ui theme pull <theme-id | url>`);
|
|
305
313
|
await pullTheme(cwd, reference, {
|
|
306
314
|
host: optionValue(args, "--host") ?? (process.env.MLOLA_STUDIO_URL || undefined),
|
|
307
315
|
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
|
}
|
package/src/pro.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import os from "node:os";
|
|
3
3
|
import path from "node:path";
|
|
4
|
-
import { targetRoot } from "./config.js";
|
|
4
|
+
import { interpolateTarget, targetRoot } from "./config.js";
|
|
5
5
|
import { sha256 } from "./registry.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -120,11 +120,24 @@ export function writeProGuide(cwd, guide, { overwrite = false } = {}) {
|
|
|
120
120
|
return { status: "written" };
|
|
121
121
|
}
|
|
122
122
|
|
|
123
|
+
/**
|
|
124
|
+
* The stylesheets that blocks, pages and templates carry beside their source
|
|
125
|
+
* (navbar.css beside navbar.tsx), as files the index can import.
|
|
126
|
+
*/
|
|
127
|
+
export function catalogStylesheets(cwd, config, items) {
|
|
128
|
+
return items
|
|
129
|
+
.filter((item) => ["registry:block", "registry:page", "registry:template"].includes(item.type))
|
|
130
|
+
.flatMap((item) => item.files.filter((file) => file.target.endsWith(".css")))
|
|
131
|
+
.map((file) => path.join(cwd, interpolateTarget(file.target, config)));
|
|
132
|
+
}
|
|
133
|
+
|
|
123
134
|
/**
|
|
124
135
|
* Pro stylesheets land in <styles>/mlola-pro/<name>.css, gathered by
|
|
125
136
|
* <styles>/mlola-pro.css, which the project imports once after the engine.
|
|
137
|
+
* The stylesheets of installed blocks, pages and templates are gathered there
|
|
138
|
+
* too, where they are, so importing the index styles everything installed.
|
|
126
139
|
*/
|
|
127
|
-
export function writeProStyles(cwd, config, styles, { overwrite = false } = {}) {
|
|
140
|
+
export function writeProStyles(cwd, config, styles, { overwrite = false, imports = [] } = {}) {
|
|
128
141
|
const root = targetRoot(config, "styles");
|
|
129
142
|
const written = [];
|
|
130
143
|
const unchanged = [];
|
|
@@ -151,8 +164,15 @@ export function writeProStyles(cwd, config, styles, { overwrite = false } = {})
|
|
|
151
164
|
const indexFile = path.join(cwd, indexRelative);
|
|
152
165
|
let index = fs.existsSync(indexFile) ? fs.readFileSync(indexFile, "utf8") : "/* Mlola Pro styles. Import this file once, after the engine stylesheet. */\n";
|
|
153
166
|
let changed = false;
|
|
154
|
-
|
|
155
|
-
|
|
167
|
+
const indexDirectory = path.dirname(indexFile);
|
|
168
|
+
const lines = [
|
|
169
|
+
...styles.map((style) => `@import "./mlola-pro/${style.name}.css";`),
|
|
170
|
+
...imports.map((file) => {
|
|
171
|
+
const relative = path.relative(indexDirectory, file).split(path.sep).join("/");
|
|
172
|
+
return `@import "${relative.startsWith(".") ? relative : `./${relative}`}";`;
|
|
173
|
+
}),
|
|
174
|
+
];
|
|
175
|
+
for (const line of lines) {
|
|
156
176
|
if (!index.includes(line)) {
|
|
157
177
|
index += `${line}\n`;
|
|
158
178
|
changed = true;
|
|
@@ -162,5 +182,5 @@ export function writeProStyles(cwd, config, styles, { overwrite = false } = {})
|
|
|
162
182
|
fs.mkdirSync(path.dirname(indexFile), { recursive: true });
|
|
163
183
|
fs.writeFileSync(indexFile, index);
|
|
164
184
|
}
|
|
165
|
-
return { written, unchanged, conflicts, index:
|
|
185
|
+
return { written, unchanged, conflicts, index: lines.length ? indexRelative : null };
|
|
166
186
|
}
|
|
@@ -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 });
|