mlola-ui 1.1.9 → 1.1.10
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 +6 -3
- package/package.json +1 -1
- package/registry/index.json +52 -47
- package/src/knowledge.js +56 -12
- package/src/mcp.js +60 -13
package/README.md
CHANGED
|
@@ -62,8 +62,9 @@ that its UI is Mlola:
|
|
|
62
62
|
`npx mlola-ui mcp` is that server. It answers from the registry bundled with
|
|
63
63
|
this CLI, offline: `get_design_rules`, `search_components`, `get_component`,
|
|
64
64
|
`get_tokens`, `check_markup` (invented classes, wrong `data-*` values, utility
|
|
65
|
-
classes, hand-written colors
|
|
66
|
-
Claude Code
|
|
65
|
+
classes, and hand-written colors or faded text in `style` attributes and
|
|
66
|
+
`<style>` blocks), `add_components` and `init_project`. For Claude Code
|
|
67
|
+
without init:
|
|
67
68
|
|
|
68
69
|
```sh
|
|
69
70
|
claude mcp add mlola --scope project -- npx -y mlola-ui mcp
|
|
@@ -71,7 +72,9 @@ claude mcp add mlola --scope project -- npx -y mlola-ui mcp
|
|
|
71
72
|
|
|
72
73
|
It is started by your agent and talks over stdio, so run by hand it prints one
|
|
73
74
|
line and waits. The same tools, minus the two that write, answer at
|
|
74
|
-
`https://ui.mlola.com/mcp` for agents that cannot run a command
|
|
75
|
+
`https://ui.mlola.com/mcp` for agents that cannot run a command (with
|
|
76
|
+
`get_install_command`, which also gives the CDN link and script for a page with
|
|
77
|
+
no build step), and the server
|
|
75
78
|
is listed in the MCP Registry as `io.github.mlolahq/mlola-ui`. See
|
|
76
79
|
https://ui.mlola.com/docs/agents for Cursor, VS Code, Codex and chat apps.
|
|
77
80
|
|
package/package.json
CHANGED
package/registry/index.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"$schema": "./schema/registry-index.schema.json",
|
|
3
3
|
"schemaVersion": 2,
|
|
4
4
|
"name": "mlola-ui",
|
|
5
|
-
"version": "1.1.
|
|
5
|
+
"version": "1.1.10",
|
|
6
6
|
"homepage": "https://ui.mlola.com",
|
|
7
7
|
"themeContract": {
|
|
8
8
|
"attribute": "data-theme",
|
|
@@ -177,8 +177,8 @@
|
|
|
177
177
|
},
|
|
178
178
|
"registryDependencies": [],
|
|
179
179
|
"engineDependencies": {
|
|
180
|
-
"@mlola-ui/behavior": "^1.1.
|
|
181
|
-
"@mlola-ui/icons": "^1.1.
|
|
180
|
+
"@mlola-ui/behavior": "^1.1.10",
|
|
181
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
182
182
|
},
|
|
183
183
|
"options": [
|
|
184
184
|
{
|
|
@@ -239,7 +239,7 @@
|
|
|
239
239
|
},
|
|
240
240
|
"registryDependencies": [],
|
|
241
241
|
"engineDependencies": {
|
|
242
|
-
"@mlola-ui/icons": "^1.1.
|
|
242
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
243
243
|
},
|
|
244
244
|
"options": [
|
|
245
245
|
{
|
|
@@ -390,7 +390,9 @@
|
|
|
390
390
|
"tags": [
|
|
391
391
|
"status",
|
|
392
392
|
"label",
|
|
393
|
-
"indicator"
|
|
393
|
+
"indicator",
|
|
394
|
+
"chip",
|
|
395
|
+
"pill"
|
|
394
396
|
],
|
|
395
397
|
"usage": {
|
|
396
398
|
"importPath": "{{aliases.components}}/badge",
|
|
@@ -399,7 +401,7 @@
|
|
|
399
401
|
},
|
|
400
402
|
"registryDependencies": [],
|
|
401
403
|
"engineDependencies": {
|
|
402
|
-
"@mlola-ui/icons": "^1.1.
|
|
404
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
403
405
|
},
|
|
404
406
|
"options": [
|
|
405
407
|
{
|
|
@@ -491,7 +493,7 @@
|
|
|
491
493
|
},
|
|
492
494
|
"registryDependencies": [],
|
|
493
495
|
"engineDependencies": {
|
|
494
|
-
"@mlola-ui/icons": "^1.1.
|
|
496
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
495
497
|
},
|
|
496
498
|
"options": [],
|
|
497
499
|
"variants": [],
|
|
@@ -544,7 +546,7 @@
|
|
|
544
546
|
"spinner"
|
|
545
547
|
],
|
|
546
548
|
"engineDependencies": {
|
|
547
|
-
"@mlola-ui/motion": "^1.1.
|
|
549
|
+
"@mlola-ui/motion": "^1.1.10"
|
|
548
550
|
},
|
|
549
551
|
"options": [
|
|
550
552
|
{
|
|
@@ -632,7 +634,10 @@
|
|
|
632
634
|
"tags": [
|
|
633
635
|
"layout",
|
|
634
636
|
"container",
|
|
635
|
-
"surface"
|
|
637
|
+
"surface",
|
|
638
|
+
"stat",
|
|
639
|
+
"kpi",
|
|
640
|
+
"metric"
|
|
636
641
|
],
|
|
637
642
|
"usage": {
|
|
638
643
|
"importPath": "{{aliases.components}}/card",
|
|
@@ -708,8 +713,8 @@
|
|
|
708
713
|
},
|
|
709
714
|
"registryDependencies": [],
|
|
710
715
|
"engineDependencies": {
|
|
711
|
-
"@mlola-ui/behavior": "^1.1.
|
|
712
|
-
"@mlola-ui/icons": "^1.1.
|
|
716
|
+
"@mlola-ui/behavior": "^1.1.10",
|
|
717
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
713
718
|
},
|
|
714
719
|
"options": [
|
|
715
720
|
{
|
|
@@ -777,7 +782,7 @@
|
|
|
777
782
|
},
|
|
778
783
|
"registryDependencies": [],
|
|
779
784
|
"engineDependencies": {
|
|
780
|
-
"@mlola-ui/icons": "^1.1.
|
|
785
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
781
786
|
},
|
|
782
787
|
"options": [],
|
|
783
788
|
"variants": [],
|
|
@@ -832,7 +837,7 @@
|
|
|
832
837
|
"modal"
|
|
833
838
|
],
|
|
834
839
|
"engineDependencies": {
|
|
835
|
-
"@mlola-ui/icons": "^1.1.
|
|
840
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
836
841
|
},
|
|
837
842
|
"options": [],
|
|
838
843
|
"variants": [],
|
|
@@ -895,8 +900,8 @@
|
|
|
895
900
|
},
|
|
896
901
|
"registryDependencies": [],
|
|
897
902
|
"engineDependencies": {
|
|
898
|
-
"@mlola-ui/behavior": "^1.1.
|
|
899
|
-
"@mlola-ui/icons": "^1.1.
|
|
903
|
+
"@mlola-ui/behavior": "^1.1.10",
|
|
904
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
900
905
|
},
|
|
901
906
|
"options": [
|
|
902
907
|
{
|
|
@@ -960,7 +965,7 @@
|
|
|
960
965
|
"input"
|
|
961
966
|
],
|
|
962
967
|
"engineDependencies": {
|
|
963
|
-
"@mlola-ui/icons": "^1.1.
|
|
968
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
964
969
|
},
|
|
965
970
|
"options": [],
|
|
966
971
|
"variants": [],
|
|
@@ -1184,8 +1189,8 @@
|
|
|
1184
1189
|
},
|
|
1185
1190
|
"registryDependencies": [],
|
|
1186
1191
|
"engineDependencies": {
|
|
1187
|
-
"@mlola-ui/behavior": "^1.1.
|
|
1188
|
-
"@mlola-ui/icons": "^1.1.
|
|
1192
|
+
"@mlola-ui/behavior": "^1.1.10",
|
|
1193
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
1189
1194
|
},
|
|
1190
1195
|
"options": [
|
|
1191
1196
|
{
|
|
@@ -1335,7 +1340,7 @@
|
|
|
1335
1340
|
},
|
|
1336
1341
|
"registryDependencies": [],
|
|
1337
1342
|
"engineDependencies": {
|
|
1338
|
-
"@mlola-ui/icons": "^1.1.
|
|
1343
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
1339
1344
|
},
|
|
1340
1345
|
"options": [],
|
|
1341
1346
|
"variants": [],
|
|
@@ -1386,7 +1391,7 @@
|
|
|
1386
1391
|
},
|
|
1387
1392
|
"registryDependencies": [],
|
|
1388
1393
|
"engineDependencies": {
|
|
1389
|
-
"@mlola-ui/behavior": "^1.1.
|
|
1394
|
+
"@mlola-ui/behavior": "^1.1.10"
|
|
1390
1395
|
},
|
|
1391
1396
|
"options": [
|
|
1392
1397
|
{
|
|
@@ -1761,7 +1766,7 @@
|
|
|
1761
1766
|
},
|
|
1762
1767
|
"registryDependencies": [],
|
|
1763
1768
|
"engineDependencies": {
|
|
1764
|
-
"@mlola-ui/behavior": "^1.1.
|
|
1769
|
+
"@mlola-ui/behavior": "^1.1.10"
|
|
1765
1770
|
},
|
|
1766
1771
|
"options": [],
|
|
1767
1772
|
"variants": [],
|
|
@@ -1814,8 +1819,8 @@
|
|
|
1814
1819
|
"input"
|
|
1815
1820
|
],
|
|
1816
1821
|
"engineDependencies": {
|
|
1817
|
-
"@mlola-ui/behavior": "^1.1.
|
|
1818
|
-
"@mlola-ui/icons": "^1.1.
|
|
1822
|
+
"@mlola-ui/behavior": "^1.1.10",
|
|
1823
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
1819
1824
|
},
|
|
1820
1825
|
"options": [
|
|
1821
1826
|
{
|
|
@@ -1892,8 +1897,8 @@
|
|
|
1892
1897
|
},
|
|
1893
1898
|
"registryDependencies": [],
|
|
1894
1899
|
"engineDependencies": {
|
|
1895
|
-
"@mlola-ui/behavior": "^1.1.
|
|
1896
|
-
"@mlola-ui/icons": "^1.1.
|
|
1900
|
+
"@mlola-ui/behavior": "^1.1.10",
|
|
1901
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
1897
1902
|
},
|
|
1898
1903
|
"options": [
|
|
1899
1904
|
{
|
|
@@ -2039,7 +2044,7 @@
|
|
|
2039
2044
|
"input"
|
|
2040
2045
|
],
|
|
2041
2046
|
"engineDependencies": {
|
|
2042
|
-
"@mlola-ui/behavior": "^1.1.
|
|
2047
|
+
"@mlola-ui/behavior": "^1.1.10"
|
|
2043
2048
|
},
|
|
2044
2049
|
"options": [
|
|
2045
2050
|
{
|
|
@@ -2184,7 +2189,7 @@
|
|
|
2184
2189
|
},
|
|
2185
2190
|
"registryDependencies": [],
|
|
2186
2191
|
"engineDependencies": {
|
|
2187
|
-
"@mlola-ui/behavior": "^1.1.
|
|
2192
|
+
"@mlola-ui/behavior": "^1.1.10"
|
|
2188
2193
|
},
|
|
2189
2194
|
"options": [
|
|
2190
2195
|
{
|
|
@@ -2316,7 +2321,7 @@
|
|
|
2316
2321
|
},
|
|
2317
2322
|
"registryDependencies": [],
|
|
2318
2323
|
"engineDependencies": {
|
|
2319
|
-
"@mlola-ui/icons": "^1.1.
|
|
2324
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
2320
2325
|
},
|
|
2321
2326
|
"options": [
|
|
2322
2327
|
{
|
|
@@ -2402,7 +2407,7 @@
|
|
|
2402
2407
|
"spinner"
|
|
2403
2408
|
],
|
|
2404
2409
|
"engineDependencies": {
|
|
2405
|
-
"@mlola-ui/icons": "^1.1.
|
|
2410
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
2406
2411
|
},
|
|
2407
2412
|
"options": [
|
|
2408
2413
|
{
|
|
@@ -2545,7 +2550,7 @@
|
|
|
2545
2550
|
},
|
|
2546
2551
|
"registryDependencies": [],
|
|
2547
2552
|
"engineDependencies": {
|
|
2548
|
-
"@mlola-ui/behavior": "^1.1.
|
|
2553
|
+
"@mlola-ui/behavior": "^1.1.10"
|
|
2549
2554
|
},
|
|
2550
2555
|
"options": [
|
|
2551
2556
|
{
|
|
@@ -2684,7 +2689,7 @@
|
|
|
2684
2689
|
},
|
|
2685
2690
|
"registryDependencies": [],
|
|
2686
2691
|
"engineDependencies": {
|
|
2687
|
-
"@mlola-ui/icons": "^1.1.
|
|
2692
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
2688
2693
|
},
|
|
2689
2694
|
"options": [
|
|
2690
2695
|
{
|
|
@@ -2756,7 +2761,7 @@
|
|
|
2756
2761
|
"popover"
|
|
2757
2762
|
],
|
|
2758
2763
|
"engineDependencies": {
|
|
2759
|
-
"@mlola-ui/icons": "^1.1.
|
|
2764
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
2760
2765
|
},
|
|
2761
2766
|
"options": [
|
|
2762
2767
|
{
|
|
@@ -2826,7 +2831,7 @@
|
|
|
2826
2831
|
"popover"
|
|
2827
2832
|
],
|
|
2828
2833
|
"engineDependencies": {
|
|
2829
|
-
"@mlola-ui/icons": "^1.1.
|
|
2834
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
2830
2835
|
},
|
|
2831
2836
|
"options": [],
|
|
2832
2837
|
"variants": [],
|
|
@@ -3024,7 +3029,7 @@
|
|
|
3024
3029
|
"input"
|
|
3025
3030
|
],
|
|
3026
3031
|
"engineDependencies": {
|
|
3027
|
-
"@mlola-ui/icons": "^1.1.
|
|
3032
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
3028
3033
|
},
|
|
3029
3034
|
"options": [
|
|
3030
3035
|
{
|
|
@@ -3101,7 +3106,7 @@
|
|
|
3101
3106
|
"input"
|
|
3102
3107
|
],
|
|
3103
3108
|
"engineDependencies": {
|
|
3104
|
-
"@mlola-ui/icons": "^1.1.
|
|
3109
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
3105
3110
|
},
|
|
3106
3111
|
"options": [],
|
|
3107
3112
|
"variants": [],
|
|
@@ -3156,7 +3161,7 @@
|
|
|
3156
3161
|
"input"
|
|
3157
3162
|
],
|
|
3158
3163
|
"engineDependencies": {
|
|
3159
|
-
"@mlola-ui/icons": "^1.1.
|
|
3164
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
3160
3165
|
},
|
|
3161
3166
|
"options": [],
|
|
3162
3167
|
"variants": [],
|
|
@@ -3219,7 +3224,7 @@
|
|
|
3219
3224
|
"popover"
|
|
3220
3225
|
],
|
|
3221
3226
|
"engineDependencies": {
|
|
3222
|
-
"@mlola-ui/icons": "^1.1.
|
|
3227
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
3223
3228
|
},
|
|
3224
3229
|
"options": [
|
|
3225
3230
|
{
|
|
@@ -3289,7 +3294,7 @@
|
|
|
3289
3294
|
},
|
|
3290
3295
|
"registryDependencies": [],
|
|
3291
3296
|
"engineDependencies": {
|
|
3292
|
-
"@mlola-ui/icons": "^1.1.
|
|
3297
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
3293
3298
|
},
|
|
3294
3299
|
"options": [
|
|
3295
3300
|
{
|
|
@@ -3408,8 +3413,8 @@
|
|
|
3408
3413
|
},
|
|
3409
3414
|
"registryDependencies": [],
|
|
3410
3415
|
"engineDependencies": {
|
|
3411
|
-
"@mlola-ui/behavior": "^1.1.
|
|
3412
|
-
"@mlola-ui/icons": "^1.1.
|
|
3416
|
+
"@mlola-ui/behavior": "^1.1.10",
|
|
3417
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
3413
3418
|
},
|
|
3414
3419
|
"options": [],
|
|
3415
3420
|
"variants": [],
|
|
@@ -3473,7 +3478,7 @@
|
|
|
3473
3478
|
},
|
|
3474
3479
|
"registryDependencies": [],
|
|
3475
3480
|
"engineDependencies": {
|
|
3476
|
-
"@mlola-ui/behavior": "^1.1.
|
|
3481
|
+
"@mlola-ui/behavior": "^1.1.10"
|
|
3477
3482
|
},
|
|
3478
3483
|
"options": [
|
|
3479
3484
|
{
|
|
@@ -3560,8 +3565,8 @@
|
|
|
3560
3565
|
},
|
|
3561
3566
|
"registryDependencies": [],
|
|
3562
3567
|
"engineDependencies": {
|
|
3563
|
-
"@mlola-ui/behavior": "^1.1.
|
|
3564
|
-
"@mlola-ui/icons": "^1.1.
|
|
3568
|
+
"@mlola-ui/behavior": "^1.1.10",
|
|
3569
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
3565
3570
|
},
|
|
3566
3571
|
"options": [],
|
|
3567
3572
|
"variants": [],
|
|
@@ -3635,7 +3640,7 @@
|
|
|
3635
3640
|
"sheet"
|
|
3636
3641
|
],
|
|
3637
3642
|
"engineDependencies": {
|
|
3638
|
-
"@mlola-ui/icons": "^1.1.
|
|
3643
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
3639
3644
|
},
|
|
3640
3645
|
"options": [],
|
|
3641
3646
|
"variants": [],
|
|
@@ -3758,7 +3763,7 @@
|
|
|
3758
3763
|
"button"
|
|
3759
3764
|
],
|
|
3760
3765
|
"engineDependencies": {
|
|
3761
|
-
"@mlola-ui/icons": "^1.1.
|
|
3766
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
3762
3767
|
},
|
|
3763
3768
|
"options": [
|
|
3764
3769
|
{
|
|
@@ -3861,8 +3866,8 @@
|
|
|
3861
3866
|
"input"
|
|
3862
3867
|
],
|
|
3863
3868
|
"engineDependencies": {
|
|
3864
|
-
"@mlola-ui/behavior": "^1.1.
|
|
3865
|
-
"@mlola-ui/icons": "^1.1.
|
|
3869
|
+
"@mlola-ui/behavior": "^1.1.10",
|
|
3870
|
+
"@mlola-ui/icons": "^1.1.10"
|
|
3866
3871
|
},
|
|
3867
3872
|
"options": [
|
|
3868
3873
|
{
|
package/src/knowledge.js
CHANGED
|
@@ -50,21 +50,41 @@ export function allItems() {
|
|
|
50
50
|
return [...free, ...pro];
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
-
/**
|
|
53
|
+
/**
|
|
54
|
+
* Items matching a query, searched in the name, title, description, category
|
|
55
|
+
* and tags. Items that match every word come first; when none does, the items
|
|
56
|
+
* that match the most words, so "radio card" still finds the radio group. A
|
|
57
|
+
* plural matches its singular, and "stat-card" reads as "stat card".
|
|
58
|
+
*/
|
|
54
59
|
export function searchItems({ query = "", kind, category, tier } = {}) {
|
|
55
|
-
const words = query.toLowerCase().split(
|
|
56
|
-
|
|
60
|
+
const words = query.toLowerCase().split(/[\s-]+/).filter(Boolean);
|
|
61
|
+
const forms = (word) => [word, ...(word.length > 3 && word.endsWith("s") ? [word.slice(0, -1)] : [])];
|
|
62
|
+
const scored = allItems()
|
|
57
63
|
.filter((item) => (!kind || item.kind === kind) && (!category || item.category === category) && (!tier || item.tier === tier))
|
|
58
64
|
.map((item) => {
|
|
59
65
|
const haystack = [item.name, item.title, item.description, item.category, ...item.tags].join(" ").toLowerCase();
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
66
|
+
let matched = 0;
|
|
67
|
+
let score = 0;
|
|
68
|
+
for (const word of words) {
|
|
69
|
+
const hit = Math.max(...forms(word).map((form) => (item.name === form ? 5 : item.name.includes(form) ? 3 : haystack.includes(form) ? 1 : 0)));
|
|
70
|
+
if (hit) matched += 1;
|
|
71
|
+
score += hit;
|
|
72
|
+
}
|
|
73
|
+
return { item, score, matched };
|
|
74
|
+
});
|
|
75
|
+
const best = Math.max(0, ...scored.map(({ matched }) => matched));
|
|
76
|
+
return scored
|
|
77
|
+
.filter(({ matched }) => !words.length || (best > 0 && matched === best))
|
|
64
78
|
.sort((a, b) => b.score - a.score || a.item.name.localeCompare(b.item.name))
|
|
65
79
|
.map(({ item }) => item);
|
|
66
80
|
}
|
|
67
81
|
|
|
82
|
+
/** " Did you mean: …?" for a name that is not an item, or nothing. */
|
|
83
|
+
export function suggest(name) {
|
|
84
|
+
const close = [...new Set([...allItems().filter((entry) => entry.name.includes(name) || name.includes(entry.name)).map((entry) => entry.name), ...searchItems({ query: name }).map((entry) => entry.name)])].slice(0, 5);
|
|
85
|
+
return close.length ? ` Did you mean: ${close.join(", ")}?` : "";
|
|
86
|
+
}
|
|
87
|
+
|
|
68
88
|
/** The classes a component styles (its own prefix) and the attributes each reacts to. */
|
|
69
89
|
function elementsOf(name) {
|
|
70
90
|
const contract = bundled("contract.json");
|
|
@@ -84,7 +104,9 @@ function projectConfig(cwd) {
|
|
|
84
104
|
}
|
|
85
105
|
|
|
86
106
|
/** Everything an agent needs to use one item correctly. */
|
|
87
|
-
export function describeItem(
|
|
107
|
+
export function describeItem(requested, { cwd = process.cwd(), includeSource = false } = {}) {
|
|
108
|
+
// Agents often ask by class ("ml-button") or title ("Date picker"): both mean the item.
|
|
109
|
+
const name = requested.trim().toLowerCase().replace(/^ml-/, "").replace(/\s+/g, "-");
|
|
88
110
|
const registry = loadRegistry();
|
|
89
111
|
const item = registry.items.find((entry) => entry.name === name);
|
|
90
112
|
if (!item) {
|
|
@@ -101,8 +123,7 @@ export function describeItem(name, { cwd = process.cwd(), includeSource = false
|
|
|
101
123
|
note: "Part of Mlola Pro. Its source arrives with a license token; its classes and attributes are listed in mlola-pro.agents.md after the first Pro install.",
|
|
102
124
|
};
|
|
103
125
|
}
|
|
104
|
-
|
|
105
|
-
throw new Error(`No item named "${name}".${close.length ? ` Did you mean: ${close.join(", ")}?` : ""} Search with search_components.`);
|
|
126
|
+
throw new Error(`No item named "${requested}".${suggest(name)} Search with search_components.`);
|
|
106
127
|
}
|
|
107
128
|
const config = projectConfig(cwd);
|
|
108
129
|
const usage = item.usage
|
|
@@ -267,8 +288,8 @@ export function checkMarkup(markup) {
|
|
|
267
288
|
if (name === "data-theme" && value && !themeIds.has(value) && !/^th-[0-9a-z]{12}$/.test(value)) {
|
|
268
289
|
note("error", tag, `data-theme="${value}" is not a theme.`, `Use one of: ${[...themeIds].join(", ")}, or a Studio theme id.`);
|
|
269
290
|
}
|
|
270
|
-
if (name === "data-mode" && !["light", "dark"].includes(value)) {
|
|
271
|
-
note("error", tag, `data-mode is "light" or "
|
|
291
|
+
if (name === "data-mode" && !["light", "dark", "system"].includes(value)) {
|
|
292
|
+
note("error", tag, `data-mode is "light", "dark" or "system"; "${value}" belongs in an attribute of your own.`, "data-theme and data-mode belong to the engine. Use data-kind or data-state for a component's own meaning.");
|
|
272
293
|
}
|
|
273
294
|
if (!SHARED.has(name) || !mlola.length) continue;
|
|
274
295
|
const allowed = new Set();
|
|
@@ -295,5 +316,28 @@ export function checkMarkup(markup) {
|
|
|
295
316
|
}
|
|
296
317
|
}
|
|
297
318
|
}
|
|
319
|
+
|
|
320
|
+
// The page's own stylesheet: the same rules hold in a <style> block.
|
|
321
|
+
for (const [, css] of markup.matchAll(/<style[^>]*>([\s\S]*?)<\/style>/gi)) {
|
|
322
|
+
for (const rule of css.replace(/\/\*[\s\S]*?\*\//g, "").split("}")) {
|
|
323
|
+
const open = rule.lastIndexOf("{");
|
|
324
|
+
if (open < 0) continue;
|
|
325
|
+
const selector = rule.slice(0, open).split("{").pop().trim();
|
|
326
|
+
const body = rule.slice(open + 1).replace(/url\([^)]*\)/g, "");
|
|
327
|
+
const where = `<style> ${selector}`;
|
|
328
|
+
const overridden = [...body.matchAll(/(--ml-[\w-]+)\s*:/g)].map((match) => match[1]).filter((token) => tokenNames.has(token));
|
|
329
|
+
if (COLOR.test(body.replace(/--[\w-]+\s*:[^;]*/g, ""))) {
|
|
330
|
+
note("error", where, "A color is written by hand in the stylesheet.", "Read a token: var(--ml-text), var(--ml-primary-text), var(--ml-surface)… (get_tokens). The theme then keeps its contrast in every mode.");
|
|
331
|
+
}
|
|
332
|
+
if (overridden.length) {
|
|
333
|
+
note("error", where, `${overridden.join(", ")} is a theme token; setting it here overrides the theme.`, "Pick a theme, or change the theme's spec (mlola.theme.json), instead of redefining its tokens.");
|
|
334
|
+
}
|
|
335
|
+
// Faded text: its contrast now depends on the theme behind it.
|
|
336
|
+
const opacity = /(?:^|;)\s*opacity\s*:\s*(0?\.\d+|0)\s*(?:;|$)/.exec(body);
|
|
337
|
+
if (opacity && !/disabled|:empty|::?placeholder|\[hidden\]|inert/.test(selector)) {
|
|
338
|
+
note("warning", where, `opacity: ${opacity[1]} fades whatever text is inside, and how far it falls below the contrast floor depends on the theme and the fill behind it.`, "For quieter text use color: var(--ml-text-muted); on a filled control, its -foreground role. Keep opacity for disabled or decorative parts.");
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
298
342
|
return issues;
|
|
299
343
|
}
|
package/src/mcp.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import readline from "node:readline";
|
|
4
|
-
import { checkMarkup, describeItem, designData, designGuide, searchItems, themes, tokens } from "./knowledge.js";
|
|
4
|
+
import { checkMarkup, describeItem, designData, designGuide, searchItems, suggest, themes, tokens } from "./knowledge.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* `mlola-ui mcp` — a Model Context Protocol server over stdio.
|
|
@@ -24,12 +24,28 @@ const INSTRUCTIONS = `This project's UI is Mlola UI: native CSS classes (ml-*),
|
|
|
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
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).`;
|
|
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). For a page with no build step (one HTML file), get_design_rules gives the stylesheet link and the script to paste.`;
|
|
28
28
|
|
|
29
29
|
const text = (value) => ({ content: [{ type: "text", text: typeof value === "string" ? value : JSON.stringify(value, null, 2) }] });
|
|
30
30
|
|
|
31
|
+
/**
|
|
32
|
+
* How to load Mlola in a page with no build step: the engine's stylesheet and
|
|
33
|
+
* the behavior runtime from a CDN, at this release's version.
|
|
34
|
+
*/
|
|
35
|
+
export function noBuildSetup(version) {
|
|
36
|
+
const cdn = (pkg, file) => `https://cdn.jsdelivr.net/npm/@mlola-ui/${pkg}@${version}/${file}`;
|
|
37
|
+
return [
|
|
38
|
+
"Without a build step (one HTML file), load Mlola from a CDN:",
|
|
39
|
+
`<link rel="stylesheet" href="${cdn("engine", "generated/mlola.css")}">`,
|
|
40
|
+
`<html data-theme="graphite" data-mode="system"> (data-mode system follows the reader's light or dark setting)`,
|
|
41
|
+
"For menus, dialogs, tabs, selects, sliders, switches, tooltips and toasts, mark each root with data-ml=\"<behavior>\" as get_component's html example shows, and load the runtime once:",
|
|
42
|
+
`<script type="module">import { observe } from "${cdn("behavior", "src/index.js")}"; observe();</script>`,
|
|
43
|
+
"The free components work this way. Mlola Pro items need a project, the CLI and a license.",
|
|
44
|
+
].join("\n");
|
|
45
|
+
}
|
|
46
|
+
|
|
31
47
|
/** Tools that only read the registry: the local and the remote server share them. */
|
|
32
|
-
function readTools({ cwd }) {
|
|
48
|
+
function readTools({ cwd, version }) {
|
|
33
49
|
return [
|
|
34
50
|
{
|
|
35
51
|
name: "get_design_rules",
|
|
@@ -39,7 +55,7 @@ function readTools({ cwd }) {
|
|
|
39
55
|
annotations: { readOnlyHint: true },
|
|
40
56
|
handler: () => {
|
|
41
57
|
const data = designData();
|
|
42
|
-
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(" ")}`);
|
|
58
|
+
return text(`${data?.rules ?? ""}\nThemes: ${themes().map((theme) => theme.id).join(", ")} (data-theme on any ancestor, data-mode light|dark|system).\nComposition primitives: ${(data?.primitives ?? []).map((entry) => entry.name).join(" ")}\n\n${noBuildSetup(version)}`);
|
|
43
59
|
},
|
|
44
60
|
},
|
|
45
61
|
{
|
|
@@ -88,7 +104,7 @@ function readTools({ cwd }) {
|
|
|
88
104
|
{
|
|
89
105
|
name: "check_markup",
|
|
90
106
|
title: "Check markup against the Mlola contract",
|
|
91
|
-
description: "Checks HTML or JSX: classes that do not exist, variant classes, data-* values a class does not react to, utility classes,
|
|
107
|
+
description: "Checks HTML or JSX: classes that do not exist, variant classes, data-* values a class does not react to, utility classes, misuse of data-theme or data-mode, and in style attributes and <style> blocks hand-written colors, overridden theme tokens and text faded with opacity. Run it on markup you wrote before finishing.",
|
|
92
108
|
inputSchema: { type: "object", properties: { markup: { type: "string" } }, required: ["markup"], additionalProperties: false },
|
|
93
109
|
annotations: { readOnlyHint: true },
|
|
94
110
|
handler: ({ markup }) => {
|
|
@@ -99,7 +115,7 @@ function readTools({ cwd }) {
|
|
|
99
115
|
];
|
|
100
116
|
}
|
|
101
117
|
|
|
102
|
-
function tools({ cwd, run }) {
|
|
118
|
+
function tools({ cwd, run, version }) {
|
|
103
119
|
const capture = async (argv) => {
|
|
104
120
|
const lines = [];
|
|
105
121
|
const output = { log: (...parts) => lines.push(parts.join(" ")), error: (...parts) => lines.push(parts.join(" ")) };
|
|
@@ -109,7 +125,7 @@ function tools({ cwd, run }) {
|
|
|
109
125
|
return { code, output: lines.join("\n") };
|
|
110
126
|
};
|
|
111
127
|
return [
|
|
112
|
-
...readTools({ cwd }),
|
|
128
|
+
...readTools({ cwd, version }),
|
|
113
129
|
{
|
|
114
130
|
name: "add_components",
|
|
115
131
|
title: "Install Mlola components",
|
|
@@ -179,6 +195,32 @@ function prompt(name, args) {
|
|
|
179
195
|
};
|
|
180
196
|
}
|
|
181
197
|
|
|
198
|
+
/**
|
|
199
|
+
* Arguments that do not fit a tool's input schema, described so an agent can
|
|
200
|
+
* correct the call: a missing required argument, one of the wrong type, one
|
|
201
|
+
* the tool does not take. Null when they fit.
|
|
202
|
+
*/
|
|
203
|
+
function invalidArguments(tool, args) {
|
|
204
|
+
const schema = tool.inputSchema ?? {};
|
|
205
|
+
const properties = schema.properties ?? {};
|
|
206
|
+
const example = (name) => (properties[name]?.type === "array" ? `["…"]` : properties[name]?.type === "boolean" ? "true" : `"…"`);
|
|
207
|
+
const typeOf = (value) => (Array.isArray(value) ? "array" : typeof value);
|
|
208
|
+
if (typeOf(args) !== "object" || args === null) return `${tool.name} takes an object of arguments.`;
|
|
209
|
+
for (const name of schema.required ?? []) {
|
|
210
|
+
if (args[name] === undefined) return `${tool.name} needs "${name}" (${properties[name]?.type ?? "a value"}), for example {"${name}": ${example(name)}}.`;
|
|
211
|
+
}
|
|
212
|
+
for (const [name, value] of Object.entries(args)) {
|
|
213
|
+
const expected = properties[name];
|
|
214
|
+
if (!expected) {
|
|
215
|
+
if (schema.additionalProperties === false) return `${tool.name} does not take "${name}". It takes: ${Object.keys(properties).join(", ") || "no arguments"}.`;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
if (expected.type && typeOf(value) !== expected.type) return `"${name}" should be ${expected.type === "array" ? "an array" : `a ${expected.type}`}, not ${typeOf(value) === "array" ? "an array" : `a ${typeOf(value)}`}.`;
|
|
219
|
+
if (expected.enum && !expected.enum.includes(value)) return `"${name}" is one of: ${expected.enum.join(", ")}.`;
|
|
220
|
+
}
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
|
|
182
224
|
/**
|
|
183
225
|
* The protocol, apart from any transport: takes one JSON-RPC message and
|
|
184
226
|
* resolves to the reply, or to null for a notification. The stdio server
|
|
@@ -208,6 +250,8 @@ export function createMcpHandler({ tools: available, listResources, readResource
|
|
|
208
250
|
case "tools/call": {
|
|
209
251
|
const tool = available.find((entry) => entry.name === params?.name);
|
|
210
252
|
if (!tool) return fail(id, -32602, `Unknown tool ${params?.name}`);
|
|
253
|
+
const problem = invalidArguments(tool, params?.arguments ?? {});
|
|
254
|
+
if (problem) return reply(id, { ...text(problem), isError: true });
|
|
211
255
|
try {
|
|
212
256
|
return reply(id, await tool.handler(params?.arguments ?? {}));
|
|
213
257
|
} catch (error) {
|
|
@@ -246,13 +290,14 @@ export function remoteMcpHandler({ version }) {
|
|
|
246
290
|
const install = {
|
|
247
291
|
name: "get_install_command",
|
|
248
292
|
title: "How to install Mlola components",
|
|
249
|
-
description: "
|
|
250
|
-
inputSchema: { type: "object", properties: { names: { type: "array", items: { type: "string" },
|
|
293
|
+
description: "How to set Mlola up: the commands to run in a project to add components, blocks, pages or templates (with the license step for Mlola Pro items), and the stylesheet link and script for a page with no build step. Leave names out for the setup alone.",
|
|
294
|
+
inputSchema: { type: "object", properties: { names: { type: "array", items: { type: "string" }, description: "Item names from search_components, for example [\"button\", \"date-picker\"]." } }, additionalProperties: false },
|
|
251
295
|
annotations: { readOnlyHint: true },
|
|
252
|
-
handler: ({ names }) => {
|
|
296
|
+
handler: ({ names = [] }) => {
|
|
297
|
+
if (!names.length) return text(["In a project:", "", "npx mlola-ui init # once: config, stylesheet, the engine, and agent instructions", "npx mlola-ui add <names>", "", noBuildSetup(version)].join("\n"));
|
|
253
298
|
const found = names.map((name) => searchItems({ query: name }).find((item) => item.name === name));
|
|
254
299
|
const unknown = names.filter((_, index) => !found[index]);
|
|
255
|
-
if (unknown.length) return { ...text(`No Mlola item is called ${
|
|
300
|
+
if (unknown.length) return { ...text(unknown.map((name) => `No Mlola item is called ${name}.${suggest(name)}`).join("\n") + " Find names with search_components."), isError: true };
|
|
256
301
|
const pro = found.filter((item) => item.tier === "pro").map((item) => item.name);
|
|
257
302
|
return text([
|
|
258
303
|
"Run in the project:",
|
|
@@ -262,11 +307,13 @@ export function remoteMcpHandler({ version }) {
|
|
|
262
307
|
`npx mlola-ui add ${names.join(" ")}`,
|
|
263
308
|
"",
|
|
264
309
|
'Then import styles/mlola/index.css once and set data-theme="graphite" (or atelier, machined, aerogel, nordic) on the root element.',
|
|
310
|
+
"",
|
|
311
|
+
noBuildSetup(version),
|
|
265
312
|
].join("\n"));
|
|
266
313
|
},
|
|
267
314
|
};
|
|
268
315
|
return createMcpHandler({
|
|
269
|
-
tools: [...readTools({ cwd }), install],
|
|
316
|
+
tools: [...readTools({ cwd, version }), install],
|
|
270
317
|
listResources: () => [{ uri: "mlola://guide", name: "Mlola UI design guide", description: "Classes, attributes, tokens and rules, generated from the stylesheet.", mimeType: "text/markdown" }],
|
|
271
318
|
readResource: (uri) => {
|
|
272
319
|
if (uri === "mlola://guide") return designGuide() ?? "";
|
|
@@ -279,7 +326,7 @@ export function remoteMcpHandler({ version }) {
|
|
|
279
326
|
|
|
280
327
|
export async function serveMcp({ cwd, run, version, input = process.stdin, output = process.stdout }) {
|
|
281
328
|
const handle = createMcpHandler({
|
|
282
|
-
tools: tools({ cwd, run }),
|
|
329
|
+
tools: tools({ cwd, run, version }),
|
|
283
330
|
listResources: () => resources(cwd),
|
|
284
331
|
readResource: (uri) => readResource(uri, cwd),
|
|
285
332
|
version,
|