@cascivo/mcp 0.1.8 → 0.3.3
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/LICENSE +1 -1
- package/README.md +53 -22
- package/dist/context/accordion.md +93 -0
- package/dist/context/action-sheet.md +117 -0
- package/dist/context/alert-dialog.md +122 -0
- package/dist/context/alert.md +110 -0
- package/dist/context/app-shell.md +98 -0
- package/dist/context/aspect-ratio.md +92 -0
- package/dist/context/avatar-group.md +111 -0
- package/dist/context/avatar.md +100 -0
- package/dist/context/badge.md +119 -0
- package/dist/context/block/console-app.md +66 -0
- package/dist/context/block/dashboard-charts.md +61 -0
- package/dist/context/block/empty-dashboard.md +60 -0
- package/dist/context/block/login-page.md +60 -0
- package/dist/context/block/notification-center.md +61 -0
- package/dist/context/block/page-with-breadcrumb.md +54 -0
- package/dist/context/block/settings-form-page.md +60 -0
- package/dist/context/block/sidebar-app.md +55 -0
- package/dist/context/block/stats-cards.md +61 -0
- package/dist/context/block/users-table-page.md +61 -0
- package/dist/context/blockquote.md +95 -0
- package/dist/context/bottom-sheet.md +112 -0
- package/dist/context/breadcrumb.md +106 -0
- package/dist/context/button-group.md +100 -0
- package/dist/context/button.md +117 -0
- package/dist/context/calendar.md +118 -0
- package/dist/context/card.md +90 -0
- package/dist/context/carousel.md +101 -0
- package/dist/context/chart/area-chart.md +99 -0
- package/dist/context/chart/bar-chart.md +114 -0
- package/dist/context/chart/boxplot.md +82 -0
- package/dist/context/chart/bubble-chart.md +80 -0
- package/dist/context/chart/bullet.md +75 -0
- package/dist/context/chart/calendar.md +79 -0
- package/dist/context/chart/candlestick.md +104 -0
- package/dist/context/chart/combo-chart.md +78 -0
- package/dist/context/chart/funnel.md +87 -0
- package/dist/context/chart/gauge.md +87 -0
- package/dist/context/chart/heatmap.md +77 -0
- package/dist/context/chart/histogram.md +74 -0
- package/dist/context/chart/kpi.md +70 -0
- package/dist/context/chart/line-chart.md +113 -0
- package/dist/context/chart/meter.md +71 -0
- package/dist/context/chart/pie-chart.md +126 -0
- package/dist/context/chart/polar.md +92 -0
- package/dist/context/chart/radar.md +81 -0
- package/dist/context/chart/radial-bar.md +96 -0
- package/dist/context/chart/sankey.md +89 -0
- package/dist/context/chart/scatter-chart.md +90 -0
- package/dist/context/chart/sparkline.md +69 -0
- package/dist/context/chart/stream.md +88 -0
- package/dist/context/chart/sunburst.md +93 -0
- package/dist/context/chart/treemap.md +84 -0
- package/dist/context/chat-bubble.md +116 -0
- package/dist/context/checkbox-card.md +104 -0
- package/dist/context/checkbox.md +104 -0
- package/dist/context/code-snippet.md +118 -0
- package/dist/context/code.md +103 -0
- package/dist/context/collapsible.md +104 -0
- package/dist/context/color-picker.md +101 -0
- package/dist/context/combobox.md +125 -0
- package/dist/context/command-menu.md +121 -0
- package/dist/context/comparison.md +117 -0
- package/dist/context/contained-list.md +103 -0
- package/dist/context/context-menu.md +103 -0
- package/dist/context/copy-button.md +106 -0
- package/dist/context/data-list.md +99 -0
- package/dist/context/data-table.md +149 -0
- package/dist/context/date-picker.md +116 -0
- package/dist/context/date-range-picker.md +113 -0
- package/dist/context/dock.md +117 -0
- package/dist/context/drawer.md +111 -0
- package/dist/context/dropdown.md +96 -0
- package/dist/context/editable.md +101 -0
- package/dist/context/editor/code-editor.md +146 -0
- package/dist/context/editor/highlight.md +74 -0
- package/dist/context/empty-state.md +101 -0
- package/dist/context/fab.md +128 -0
- package/dist/context/field.md +118 -0
- package/dist/context/file-uploader.md +121 -0
- package/dist/context/filter.md +129 -0
- package/dist/context/flow/flow-background.md +103 -0
- package/dist/context/flow/flow-canvas.md +82 -0
- package/dist/context/flow/flow-controls.md +84 -0
- package/dist/context/flow/flow-edge.md +128 -0
- package/dist/context/flow/flow-handle.md +83 -0
- package/dist/context/flow/flow-minimap.md +89 -0
- package/dist/context/flow/flow-node.md +97 -0
- package/dist/context/flow/flow-panel.md +75 -0
- package/dist/context/flow/flow-story.md +145 -0
- package/dist/context/flow/flow.md +139 -0
- package/dist/context/form.md +104 -0
- package/dist/context/header-panel.md +96 -0
- package/dist/context/header.md +104 -0
- package/dist/context/heading.md +107 -0
- package/dist/context/hover-card.md +108 -0
- package/dist/context/icon-button.md +115 -0
- package/dist/context/image.md +115 -0
- package/dist/context/indicator.md +95 -0
- package/dist/context/inline-loading.md +101 -0
- package/dist/context/input-group.md +122 -0
- package/dist/context/input.md +101 -0
- package/dist/context/item.md +109 -0
- package/dist/context/join.md +97 -0
- package/dist/context/kbd.md +100 -0
- package/dist/context/label.md +117 -0
- package/dist/context/layout/app-shell.md +85 -0
- package/dist/context/layout/auth-layout.md +74 -0
- package/dist/context/layout/auto-grid.md +69 -0
- package/dist/context/layout/center.md +67 -0
- package/dist/context/layout/columns.md +70 -0
- package/dist/context/layout/dashboard-layout.md +68 -0
- package/dist/context/layout/grid.md +70 -0
- package/dist/context/layout/masonry.md +70 -0
- package/dist/context/layout/page-header.md +75 -0
- package/dist/context/layout/section.md +69 -0
- package/dist/context/layout/settings-layout.md +68 -0
- package/dist/context/layout/spacer.md +64 -0
- package/dist/context/layout/split-view.md +71 -0
- package/dist/context/layout/stack.md +93 -0
- package/dist/context/link.md +112 -0
- package/dist/context/list.md +109 -0
- package/dist/context/log-viewer.md +124 -0
- package/dist/context/menu-button.md +137 -0
- package/dist/context/menu.md +108 -0
- package/dist/context/menubar.md +92 -0
- package/dist/context/modal.md +96 -0
- package/dist/context/multi-select.md +104 -0
- package/dist/context/native-select.md +118 -0
- package/dist/context/navigation-menu.md +95 -0
- package/dist/context/notification.md +116 -0
- package/dist/context/number-input.md +117 -0
- package/dist/context/otp-input.md +98 -0
- package/dist/context/overflow-menu.md +107 -0
- package/dist/context/pagination.md +123 -0
- package/dist/context/password-input.md +100 -0
- package/dist/context/popover.md +108 -0
- package/dist/context/progress-bar.md +107 -0
- package/dist/context/progress-circle.md +101 -0
- package/dist/context/progress-indicator.md +105 -0
- package/dist/context/progress.md +109 -0
- package/dist/context/prose.md +110 -0
- package/dist/context/pull-to-refresh.md +101 -0
- package/dist/context/qr-code.md +101 -0
- package/dist/context/radial-progress.md +117 -0
- package/dist/context/radio-card.md +95 -0
- package/dist/context/radio.md +94 -0
- package/dist/context/rating-group.md +97 -0
- package/dist/context/relative-time.md +90 -0
- package/dist/context/resizable.md +102 -0
- package/dist/context/scroll-area.md +101 -0
- package/dist/context/search.md +113 -0
- package/dist/context/section/cta.md +83 -0
- package/dist/context/section/feature-grid.md +98 -0
- package/dist/context/section/hero.md +101 -0
- package/dist/context/section/media-masonry.md +77 -0
- package/dist/context/section/page-footer.md +101 -0
- package/dist/context/section/stats-band.md +81 -0
- package/dist/context/segmented-control.md +101 -0
- package/dist/context/select.md +101 -0
- package/dist/context/separator.md +88 -0
- package/dist/context/sheet.md +100 -0
- package/dist/context/shell-header.md +105 -0
- package/dist/context/side-nav.md +134 -0
- package/dist/context/skeleton.md +101 -0
- package/dist/context/skip-nav.md +103 -0
- package/dist/context/slider.md +98 -0
- package/dist/context/spinner.md +89 -0
- package/dist/context/stack.md +96 -0
- package/dist/context/stat.md +101 -0
- package/dist/context/status.md +105 -0
- package/dist/context/steps.md +124 -0
- package/dist/context/structured-list.md +107 -0
- package/dist/context/swap.md +100 -0
- package/dist/context/swipe-item.md +110 -0
- package/dist/context/switcher.md +97 -0
- package/dist/context/tabs.md +93 -0
- package/dist/context/tag.md +110 -0
- package/dist/context/tags-input.md +94 -0
- package/dist/context/text.md +111 -0
- package/dist/context/textarea.md +98 -0
- package/dist/context/tile.md +116 -0
- package/dist/context/time-picker.md +102 -0
- package/dist/context/timeline.md +93 -0
- package/dist/context/toast.md +91 -0
- package/dist/context/toc.md +110 -0
- package/dist/context/toggle-group.md +118 -0
- package/dist/context/toggle.md +103 -0
- package/dist/context/toggletip.md +119 -0
- package/dist/context/tooltip.md +92 -0
- package/dist/context/tree-view.md +107 -0
- package/dist/context/user.md +100 -0
- package/dist/context/visually-hidden.md +90 -0
- package/dist/context.json +22456 -0
- package/dist/index.d.mts +5 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +374 -64
- package/dist/index.mjs.map +1 -1
- package/dist/marketplace.json +70 -0
- package/dist/registry.json +5622 -786
- package/dist/tokens.catalog.json +2375 -0
- package/dist/tokens.variants.json +3291 -0
- package/package.json +4 -3
- package/readme.body.md +53 -22
package/dist/index.d.mts
CHANGED
|
@@ -22,7 +22,8 @@ interface ThemeColors {
|
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Generate a custom cascivo theme as CSS. Maps the three input colors onto the
|
|
25
|
-
* semantic token layer
|
|
25
|
+
* semantic token layer via OKLCH relative color syntax + contrast-color(), each
|
|
26
|
+
* behind a static OKLCH fallback. Component tokens inherit automatically.
|
|
26
27
|
*/
|
|
27
28
|
declare function generateThemeCss(colors: ThemeColors, name?: string): string;
|
|
28
29
|
//#endregion
|
|
@@ -74,6 +75,8 @@ interface RegistryComponent {
|
|
|
74
75
|
category: string;
|
|
75
76
|
version: string;
|
|
76
77
|
files: string[];
|
|
78
|
+
/** filename → sha256 of upstream content — lets agents detect drift between installed and current source. */
|
|
79
|
+
fileHashes?: Record<string, string>;
|
|
77
80
|
dependencies: string[];
|
|
78
81
|
tags: string[];
|
|
79
82
|
meta: ComponentManifest;
|
|
@@ -138,7 +141,7 @@ interface GenerationPromptOptions {
|
|
|
138
141
|
declare function buildGenerationPrompt(registry: Registry, options?: GenerationPromptOptions): string;
|
|
139
142
|
//#endregion
|
|
140
143
|
//#region src/index.d.ts
|
|
141
|
-
declare const VERSION
|
|
144
|
+
declare const VERSION: string;
|
|
142
145
|
/** Start the MCP server over stdio. */
|
|
143
146
|
declare function main(): Promise<void>;
|
|
144
147
|
//#endregion
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/server.ts","../src/theme.ts","../src/scaffold.ts","../src/registry.ts","../src/grammar.ts","../src/prompt.ts","../src/index.ts"],"mappings":";;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/server.ts","../src/theme.ts","../src/scaffold.ts","../src/registry.ts","../src/grammar.ts","../src/prompt.ts","../src/index.ts"],"mappings":";;;KA4BK,OAAA,IAAW,GAAA,UAAa,IAAA,GAAO,WAAA,KAAgB,OAAA,CAAQ,QAAA;AAAA,UAE3C,aAAA;EACf,YAAA;EACA,OAAA;;EAEA,OAAA,GAAU,OAAO;AAAA;;iBAeH,YAAA,CAAa,OAAA,GAAS,aAAA,GAAqB,SAAS;;;UCjDnD,WAAA;;EAEf,OAAA;ED0BG;ECxBH,OAAA;;EAEA,MAAA;AAAA;;;;;AD4BiB;iBCkJH,gBAAA,CAAiB,MAAA,EAAQ,WAAW,EAAE,IAAA;;;UCpLrC,eAAA;EACf,WAAA;EACA,UAAU;AAAA;;;;;iBAgCI,YAAA,CAAa,OAAwB,EAAf,eAAe;;;UC7BpC,YAAA;EACf,IAAA;EACA,IAAA;EACA,QAAA;EACA,OAAA;EACA,WAAA;AAAA;AAAA,UAGe,iBAAA;EACf,IAAA;EACA,WAAA;EACA,QAAA;EACA,MAAA;EACA,QAAA;EACA,KAAA;EACA,KAAA,EAAO,YAAY;EACnB,MAAA;EACA,aAAA;IAAiB,IAAA;IAAc,IAAA;IAAc,QAAA;EAAA;EAC7C,QAAA;IAAY,KAAA;IAAe,IAAA;IAAc,WAAA;EAAA;EACzC,YAAA;EACA,IAAA;AAAA;AAAA,UAGe,iBAAA;EACf,IAAA;EACA,IAAA;EACA,WAAA;EACA,QAAA;EACA,OAAA;EACA,KAAA;EHekE;EGblE,UAAA,GAAa,MAAA;EACb,YAAA;EACA,IAAA;EACA,IAAA,EAAM,iBAAiB;AAAA;AAAA,UAGR,QAAA;EACf,OAAA;EACA,WAAA;EACA,UAAA,EAAY,iBAAiB;AAAA;AAAA,iBAwBf,YAAA,CAAa,IAAA,YAAgB,QAAQ;;iBAUrC,cAAA,CACd,QAAA,EAAU,QAAA,EACV,QAAA,WACA,IAAA,YACC,iBAAiB;;iBAQJ,YAAA,CAAa,QAAA,EAAU,QAAA,EAAU,IAAA,WAAe,iBAAiB;AA9EjF;AAAA,iBA0MgB,gBAAA,CAAiB,QAAA,EAAU,QAAA,EAAU,KAAA,WAAgB,iBAAiB;;;UC3MrE,WAAA;EACf,IAAA;EACA,QAAA;EJcU;EIZV,IAAA;EJYkC;EIVlC,IAAA;AAAA;AAAA,UAGe,gBAAA;EACf,IAAA;EACA,QAAA;EACA,KAAA,EAAO,WAAW;EAClB,QAAA;EACA,KAAA;AAAA;AAAA,UAGe,WAAA;EACf,UAAA,EAAY,gBAAgB;AAAA;;;;;;iBAQd,SAAA,CAAU,IAAY;AAAA,iBA8BtB,YAAA,CAAa,QAAA,EAAU,QAAA,EAAU,MAAA,cAAoB,WAAW;;AJlC7D;AAenB;;;;iBI0EgB,aAAA,CAAc,OAAoB,EAAX,WAAW;;;UChHjC,uBAAA;;EAEf,UAAU;AAAA;;;;;;iBAsCI,qBAAA,CACd,QAAA,EAAU,QAAA,EACV,OAAA,GAAS,uBAA4B;;;cC5C1B,OAAA;;iBA4BS,IAAA,IAAQ,OAAO"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from "node:module";
|
|
2
3
|
import { argv } from "node:process";
|
|
3
4
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
4
5
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
@@ -7,8 +8,17 @@ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
|
7
8
|
import { z } from "zod";
|
|
8
9
|
import { existsSync, readFileSync } from "node:fs";
|
|
9
10
|
import { dirname, join } from "node:path";
|
|
11
|
+
//#region src/host.ts
|
|
12
|
+
/**
|
|
13
|
+
* Canonical host for hosted cascivo artifacts (registry.json, context.json,
|
|
14
|
+
* tokens.catalog.json, marketplace.json, per-item r/<name>.json, llms.txt).
|
|
15
|
+
* docs.cascivo.com serves the same tree as a mirror — code always fetches the
|
|
16
|
+
* canonical host. Keep in sync with CASCIVO_HOST in packages/cli.
|
|
17
|
+
*/
|
|
18
|
+
const CASCIVO_HOST = "https://cascivo.com";
|
|
19
|
+
//#endregion
|
|
10
20
|
//#region src/registry.ts
|
|
11
|
-
const HERE$
|
|
21
|
+
const HERE$4 = dirname(fileURLToPath(import.meta.url));
|
|
12
22
|
/**
|
|
13
23
|
* Resolve the registry.json location. Checks, in order: an explicit path, the
|
|
14
24
|
* `CASCIVO_REGISTRY_PATH` env var, a copy bundled next to the built server
|
|
@@ -18,9 +28,9 @@ function resolveRegistryPath(explicit) {
|
|
|
18
28
|
const candidates = [
|
|
19
29
|
explicit,
|
|
20
30
|
process.env.CASCIVO_REGISTRY_PATH,
|
|
21
|
-
join(HERE$
|
|
22
|
-
join(HERE$
|
|
23
|
-
join(HERE$
|
|
31
|
+
join(HERE$4, "registry.json"),
|
|
32
|
+
join(HERE$4, "..", "..", "..", "registry.json"),
|
|
33
|
+
join(HERE$4, "..", "..", "registry.json")
|
|
24
34
|
].filter((p) => typeof p === "string" && p.length > 0);
|
|
25
35
|
return candidates.find((p) => existsSync(p)) ?? candidates[candidates.length - 1] ?? "registry.json";
|
|
26
36
|
}
|
|
@@ -42,7 +52,23 @@ function getComponent(registry, name) {
|
|
|
42
52
|
const target = name.toLowerCase();
|
|
43
53
|
return registry.components.find((c) => c.name.toLowerCase() === target || c.meta.name.toLowerCase() === target)?.meta;
|
|
44
54
|
}
|
|
45
|
-
|
|
55
|
+
/**
|
|
56
|
+
* Manifest enriched with distribution facts (version, file hashes) so an agent
|
|
57
|
+
* can compare its installed copy against upstream — `getComponent` alone
|
|
58
|
+
* drops those registry-entry fields.
|
|
59
|
+
*/
|
|
60
|
+
function getComponentWithVersion(registry, name) {
|
|
61
|
+
const target = name.toLowerCase();
|
|
62
|
+
const entry = registry.components.find((c) => c.name.toLowerCase() === target || c.meta.name.toLowerCase() === target);
|
|
63
|
+
if (!entry) return void 0;
|
|
64
|
+
return {
|
|
65
|
+
...entry.meta,
|
|
66
|
+
version: entry.version,
|
|
67
|
+
files: entry.files,
|
|
68
|
+
...entry.fileHashes ? { fileHashes: entry.fileHashes } : {}
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
const DIRECTORY_URL = `${CASCIVO_HOST}/r/registries.json`;
|
|
46
72
|
const FETCH_TIMEOUT_MS = 15e3;
|
|
47
73
|
const MAX_BODY_BYTES = 1048576;
|
|
48
74
|
async function fetchWithGuards(url, fetchFn = fetch) {
|
|
@@ -111,64 +137,175 @@ function searchComponents(registry, query) {
|
|
|
111
137
|
}
|
|
112
138
|
//#endregion
|
|
113
139
|
//#region src/theme.ts
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
140
|
+
const NAMED = {
|
|
141
|
+
black: "#000000",
|
|
142
|
+
white: "#ffffff",
|
|
143
|
+
red: "#ff0000",
|
|
144
|
+
green: "#008000",
|
|
145
|
+
blue: "#0000ff",
|
|
146
|
+
gray: "#808080",
|
|
147
|
+
grey: "#808080",
|
|
148
|
+
silver: "#c0c0c0",
|
|
149
|
+
maroon: "#800000",
|
|
150
|
+
olive: "#808000",
|
|
151
|
+
lime: "#00ff00",
|
|
152
|
+
aqua: "#00ffff",
|
|
153
|
+
teal: "#008080",
|
|
154
|
+
navy: "#000080",
|
|
155
|
+
fuchsia: "#ff00ff",
|
|
156
|
+
purple: "#800080",
|
|
157
|
+
orange: "#ffa500",
|
|
158
|
+
yellow: "#ffff00"
|
|
159
|
+
};
|
|
160
|
+
const clamp = (n, lo, hi) => Math.min(hi, Math.max(lo, n));
|
|
161
|
+
const round = (n, p) => {
|
|
162
|
+
const f = 10 ** p;
|
|
163
|
+
return Math.round(n * f) / f;
|
|
164
|
+
};
|
|
165
|
+
/** sRGB (0–1, gamma-encoded) → OKLCH. Ottosson's reference transform. */
|
|
166
|
+
function srgbToOklch(r, g, b) {
|
|
167
|
+
const lin = (c) => c <= .04045 ? c / 12.92 : ((c + .055) / 1.055) ** 2.4;
|
|
168
|
+
const rl = lin(r);
|
|
169
|
+
const gl = lin(g);
|
|
170
|
+
const bl = lin(b);
|
|
171
|
+
const l_ = Math.cbrt(.4122214708 * rl + .5363325363 * gl + .0514459929 * bl);
|
|
172
|
+
const m_ = Math.cbrt(.2119034982 * rl + .6806995451 * gl + .1073969566 * bl);
|
|
173
|
+
const s_ = Math.cbrt(.0883024619 * rl + .2817188376 * gl + .6299787005 * bl);
|
|
174
|
+
const L = .2104542553 * l_ + .793617785 * m_ - .0040720468 * s_;
|
|
175
|
+
const a = 1.9779984951 * l_ - 2.428592205 * m_ + .4505937099 * s_;
|
|
176
|
+
const bb = .0259040371 * l_ + .7827717662 * m_ - .808675766 * s_;
|
|
177
|
+
const C = Math.hypot(a, bb);
|
|
178
|
+
let H = Math.atan2(bb, a) * 180 / Math.PI;
|
|
179
|
+
if (H < 0) H += 360;
|
|
180
|
+
return {
|
|
181
|
+
l: clamp(L, 0, 1),
|
|
182
|
+
c: Math.max(0, C),
|
|
183
|
+
h: H
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
function hexToOklch(hex) {
|
|
187
|
+
let h = hex.slice(1);
|
|
188
|
+
if (h.length === 3) h = h.split("").map((ch) => ch + ch).join("");
|
|
189
|
+
return srgbToOklch(parseInt(h.slice(0, 2), 16) / 255, parseInt(h.slice(2, 4), 16) / 255, parseInt(h.slice(4, 6), 16) / 255);
|
|
117
190
|
}
|
|
118
|
-
/**
|
|
119
|
-
function
|
|
120
|
-
|
|
191
|
+
/** Parse a hex / rgb() / oklch() / named color into OKLCH. Throws on unknown input. */
|
|
192
|
+
function parseToOklch(input) {
|
|
193
|
+
const raw = input.trim().toLowerCase();
|
|
194
|
+
const named = NAMED[raw];
|
|
195
|
+
if (named) return hexToOklch(named);
|
|
196
|
+
if (raw.startsWith("#")) return hexToOklch(raw);
|
|
197
|
+
const oklchM = /^oklch\(\s*([\d.]+%?)\s+([\d.]+)\s+([\d.]+)(?:\s*\/\s*([\d.]+%?))?\s*\)$/.exec(raw);
|
|
198
|
+
if (oklchM) {
|
|
199
|
+
const parsePct = (s) => s.endsWith("%") ? parseFloat(s) / 100 : parseFloat(s);
|
|
200
|
+
const o = {
|
|
201
|
+
l: clamp(parsePct(oklchM[1]), 0, 1),
|
|
202
|
+
c: parseFloat(oklchM[2]),
|
|
203
|
+
h: parseFloat(oklchM[3])
|
|
204
|
+
};
|
|
205
|
+
if (oklchM[4]) o.a = parsePct(oklchM[4]);
|
|
206
|
+
return o;
|
|
207
|
+
}
|
|
208
|
+
const rgbM = /^rgba?\(\s*([\d.]+)[\s,]+([\d.]+)[\s,]+([\d.]+)/.exec(raw);
|
|
209
|
+
if (rgbM) return srgbToOklch(parseFloat(rgbM[1]) / 255, parseFloat(rgbM[2]) / 255, parseFloat(rgbM[3]) / 255);
|
|
210
|
+
throw new Error(`create_theme: cannot parse color "${input}" — use hex, rgb(), oklch(), or a named color`);
|
|
211
|
+
}
|
|
212
|
+
/** Serialize OKLCH to a canonical `oklch(L C H[ / a])` literal. */
|
|
213
|
+
function fmt(o) {
|
|
214
|
+
const base = `oklch(${round(o.l, 3)} ${round(o.c, 3)} ${round(o.h, 1)}`;
|
|
215
|
+
return o.a !== void 0 ? `${base} / ${round(o.a, 3)})` : `${base})`;
|
|
216
|
+
}
|
|
217
|
+
/** Apply a step to an OKLCH color (lightness/chroma deltas, optional alpha). */
|
|
218
|
+
function step(o, d) {
|
|
219
|
+
const next = {
|
|
220
|
+
l: clamp(o.l + (d.dl ?? 0), 0, 1),
|
|
221
|
+
c: Math.max(0, o.c * (d.dc ?? 1)),
|
|
222
|
+
h: o.h
|
|
223
|
+
};
|
|
224
|
+
if (d.a !== void 0) next.a = d.a;
|
|
225
|
+
return next;
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Emit a fallback-then-derived declaration pair: the static OKLCH literal first
|
|
229
|
+
* (what every browser uses), then the relative-color form (progressive). Both
|
|
230
|
+
* are computed from the same base + delta so they agree.
|
|
231
|
+
*/
|
|
232
|
+
function derive(prop, baseToken, base, d) {
|
|
233
|
+
return ` ${prop}: ${fmt(step(base, d))};\n ${prop}: ${`oklch(from var(${baseToken}) ${d.dl ? `calc(l ${d.dl >= 0 ? "+" : "-"} ${Math.abs(d.dl)})` : "l"} ${d.dc !== void 0 && d.dc !== 1 ? `calc(c * ${d.dc})` : "c"} h${d.a !== void 0 ? ` / ${d.a}` : ""})`};\n`;
|
|
234
|
+
}
|
|
235
|
+
/** A plain static OKLCH literal declaration (no derivation). */
|
|
236
|
+
function lit(prop, o) {
|
|
237
|
+
return ` ${prop}: ${fmt(o)};\n`;
|
|
238
|
+
}
|
|
239
|
+
/**
|
|
240
|
+
* Pick a WCAG-safe on-color (used as the static fallback for contrast-color()):
|
|
241
|
+
* dark text on light surfaces, light text on dark ones.
|
|
242
|
+
*/
|
|
243
|
+
function onColor(base) {
|
|
244
|
+
return base.l > .6 ? {
|
|
245
|
+
l: .145,
|
|
246
|
+
c: .005,
|
|
247
|
+
h: base.h
|
|
248
|
+
} : {
|
|
249
|
+
l: 1,
|
|
250
|
+
c: 0,
|
|
251
|
+
h: 0
|
|
252
|
+
};
|
|
121
253
|
}
|
|
254
|
+
const DESTRUCTIVE = {
|
|
255
|
+
l: .577,
|
|
256
|
+
c: .215,
|
|
257
|
+
h: 27.3
|
|
258
|
+
};
|
|
259
|
+
const SUCCESS = {
|
|
260
|
+
l: .627,
|
|
261
|
+
c: .176,
|
|
262
|
+
h: 149.6
|
|
263
|
+
};
|
|
264
|
+
const WARNING = {
|
|
265
|
+
l: .769,
|
|
266
|
+
c: .166,
|
|
267
|
+
h: 70.7
|
|
268
|
+
};
|
|
122
269
|
/**
|
|
123
270
|
* Generate a custom cascivo theme as CSS. Maps the three input colors onto the
|
|
124
|
-
* semantic token layer
|
|
271
|
+
* semantic token layer via OKLCH relative color syntax + contrast-color(), each
|
|
272
|
+
* behind a static OKLCH fallback. Component tokens inherit automatically.
|
|
125
273
|
*/
|
|
126
274
|
function generateThemeCss(colors, name = "custom") {
|
|
127
|
-
const
|
|
275
|
+
const primary = parseToOklch(colors.primary);
|
|
276
|
+
const neutral = parseToOklch(colors.neutral);
|
|
277
|
+
const accent = parseToOklch(colors.accent);
|
|
278
|
+
const surface = (l, cMul = .15) => ({
|
|
279
|
+
l,
|
|
280
|
+
c: round(neutral.c * cMul, 3),
|
|
281
|
+
h: neutral.h
|
|
282
|
+
});
|
|
283
|
+
const onAccent = onColor(primary);
|
|
284
|
+
const onDestructive = onColor(DESTRUCTIVE);
|
|
128
285
|
return `/* cascivo — Generated theme: ${name} */
|
|
286
|
+
/* Built like the first-party themes: OKLCH + relative color syntax + */
|
|
287
|
+
/* contrast-color(), every progressive value behind a static fallback. */
|
|
129
288
|
|
|
130
289
|
@layer cascivo.theme {
|
|
131
290
|
[data-theme='${name}'] {
|
|
132
291
|
color-scheme: light;
|
|
133
292
|
|
|
134
293
|
/* ── Surface (derived from neutral) ───────────────── */
|
|
135
|
-
|
|
136
|
-
--cascivo-color-bg-subtle: ${lighten(neutral, 92)};
|
|
137
|
-
--cascivo-color-surface: ${lighten(neutral, 98)};
|
|
138
|
-
--cascivo-color-surface-raised: ${lighten(neutral, 94)};
|
|
139
|
-
--cascivo-color-surface-overlay: ${lighten(neutral, 98)};
|
|
140
|
-
--cascivo-color-border: ${lighten(neutral, 80)};
|
|
141
|
-
--cascivo-color-border-strong: ${lighten(neutral, 65)};
|
|
142
|
-
|
|
294
|
+
${lit("--cascivo-color-bg", surface(.99))}${lit("--cascivo-color-bg-subtle", surface(.975))}${lit("--cascivo-color-surface", surface(.985))}${lit("--cascivo-color-surface-raised", surface(.97))}${lit("--cascivo-color-surface-overlay", surface(.99))}${lit("--cascivo-color-border", surface(.9, .25))}${lit("--cascivo-color-border-strong", surface(.8, .3))}
|
|
143
295
|
/* ── Text (derived from neutral) ──────────────────── */
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
--cascivo-color-text-
|
|
147
|
-
--cascivo-color-text-on-accent:
|
|
148
|
-
--cascivo-color-text-on-destructive:
|
|
149
|
-
|
|
150
|
-
/* ── Accent / primary interactive ─────────────────── */
|
|
151
|
-
--cascivo-color-accent: ${primary};
|
|
152
|
-
--cascivo-color-accent-hover: ${darken(primary, 12)};
|
|
153
|
-
--cascivo-color-accent-active: ${darken(primary, 24)};
|
|
154
|
-
--cascivo-color-accent-subtle: ${lighten(primary, 88)};
|
|
155
|
-
--cascivo-color-accent-muted: ${lighten(primary, 76)};
|
|
296
|
+
${lit("--cascivo-color-text", surface(.22, .4))}${lit("--cascivo-color-text-subtle", surface(.45, .35))}${lit("--cascivo-color-text-muted", surface(.6, .3))}
|
|
297
|
+
/* on-color: static fallback then auto-contrast (WCAG) */
|
|
298
|
+
--cascivo-color-text-on-accent: ${fmt(onAccent)};
|
|
299
|
+
--cascivo-color-text-on-accent: contrast-color(var(--cascivo-color-accent));
|
|
300
|
+
--cascivo-color-text-on-destructive: ${fmt(onDestructive)};
|
|
301
|
+
--cascivo-color-text-on-destructive: contrast-color(var(--cascivo-color-destructive));
|
|
156
302
|
|
|
303
|
+
/* ── Accent / primary interactive (derived from one base) ── */
|
|
304
|
+
${lit("--cascivo-color-accent", primary)}${derive("--cascivo-color-accent-hover", "--cascivo-color-accent", primary, { dl: -.077 })}${derive("--cascivo-color-accent-active", "--cascivo-color-accent", primary, { dl: -.154 })}${derive("--cascivo-color-accent-subtle", "--cascivo-color-accent", primary, { a: .1 })}${derive("--cascivo-color-accent-muted", "--cascivo-color-accent", primary, { a: .2 })}
|
|
157
305
|
/* ── Secondary accent (info / focus) ──────────────── */
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
--cascivo-focus-ring: 0 0 0 3px ${lighten(accent, 20)};
|
|
162
|
-
|
|
163
|
-
/* ── Status ───────────────────────────────────────── */
|
|
164
|
-
--cascivo-color-destructive: #dc2626;
|
|
165
|
-
--cascivo-color-destructive-hover: ${darken("#dc2626", 12)};
|
|
166
|
-
--cascivo-color-destructive-subtle: ${lighten("#dc2626", 88)};
|
|
167
|
-
--cascivo-color-success: #16a34a;
|
|
168
|
-
--cascivo-color-success-subtle: ${lighten("#16a34a", 88)};
|
|
169
|
-
--cascivo-color-warning: #f59e0b;
|
|
170
|
-
--cascivo-color-warning-subtle: ${lighten("#f59e0b", 88)};
|
|
171
|
-
}
|
|
306
|
+
${lit("--cascivo-color-info", accent)}${derive("--cascivo-color-info-subtle", "--cascivo-color-info", accent, { a: .1 })}${lit("--cascivo-color-focus-ring", accent)}
|
|
307
|
+
/* ── Status (in-system OKLCH; no hex) ─────────────── */
|
|
308
|
+
${lit("--cascivo-color-destructive", DESTRUCTIVE)}${derive("--cascivo-color-destructive-hover", "--cascivo-color-destructive", DESTRUCTIVE, { dl: -.077 })}${derive("--cascivo-color-destructive-subtle", "--cascivo-color-destructive", DESTRUCTIVE, { a: .1 })}${lit("--cascivo-color-success", SUCCESS)}${derive("--cascivo-color-success-subtle", "--cascivo-color-success", SUCCESS, { a: .1 })}${lit("--cascivo-color-warning", WARNING)}${derive("--cascivo-color-warning-subtle", "--cascivo-color-warning", WARNING, { a: .1 })} }
|
|
172
309
|
}
|
|
173
310
|
`;
|
|
174
311
|
}
|
|
@@ -496,21 +633,21 @@ function buildGenerationPrompt(registry, options = {}) {
|
|
|
496
633
|
}
|
|
497
634
|
//#endregion
|
|
498
635
|
//#region src/tokens.ts
|
|
499
|
-
const HERE$
|
|
500
|
-
const CATALOG_BASE_URL =
|
|
636
|
+
const HERE$3 = dirname(fileURLToPath(import.meta.url));
|
|
637
|
+
const CATALOG_BASE_URL$1 = CASCIVO_HOST;
|
|
501
638
|
async function loadTokenCatalog(fetchFn) {
|
|
502
|
-
const localPaths = [join(HERE$
|
|
639
|
+
const localPaths = [join(HERE$3, "..", "..", "..", "apps", "site", "public", "tokens.catalog.json"), join(HERE$3, "tokens.catalog.json")];
|
|
503
640
|
for (const p of localPaths) if (existsSync(p)) return JSON.parse(readFileSync(p, "utf8"));
|
|
504
|
-
const res = await (fetchFn ?? fetch)(`${CATALOG_BASE_URL}/tokens.catalog.json`);
|
|
641
|
+
const res = await (fetchFn ?? fetch)(`${CATALOG_BASE_URL$1}/tokens.catalog.json`);
|
|
505
642
|
if (!res.ok) throw new Error(`Failed to fetch token catalog: ${res.status}`);
|
|
506
643
|
return res.json();
|
|
507
644
|
}
|
|
508
645
|
//#endregion
|
|
509
646
|
//#region src/variants.ts
|
|
510
|
-
const HERE$
|
|
511
|
-
const BASE_URL =
|
|
647
|
+
const HERE$2 = dirname(fileURLToPath(import.meta.url));
|
|
648
|
+
const BASE_URL = CASCIVO_HOST;
|
|
512
649
|
async function loadVariantMatrix(fetchFn) {
|
|
513
|
-
const localPaths = [join(HERE$
|
|
650
|
+
const localPaths = [join(HERE$2, "..", "..", "..", "apps", "site", "public", "tokens.variants.json"), join(HERE$2, "tokens.variants.json")];
|
|
514
651
|
for (const p of localPaths) if (existsSync(p)) return JSON.parse(readFileSync(p, "utf8"));
|
|
515
652
|
const res = await (fetchFn ?? fetch)(`${BASE_URL}/tokens.variants.json`);
|
|
516
653
|
if (!res.ok) throw new Error(`Failed to fetch variant matrix: ${res.status}`);
|
|
@@ -647,6 +784,27 @@ function checkTokens(css, tokenNames) {
|
|
|
647
784
|
}
|
|
648
785
|
return violations;
|
|
649
786
|
}
|
|
787
|
+
/** Flag `var(--cascivo-…)` references that use an alias where a canonical token exists. */
|
|
788
|
+
function checkAliasTokens(css, aliasMap) {
|
|
789
|
+
if (aliasMap.size === 0) return [];
|
|
790
|
+
const violations = [];
|
|
791
|
+
const re = /var\(\s*(--cascivo-[\w-]+)/g;
|
|
792
|
+
let m;
|
|
793
|
+
const seen = /* @__PURE__ */ new Set();
|
|
794
|
+
while ((m = re.exec(css)) !== null) {
|
|
795
|
+
const token = m[1];
|
|
796
|
+
const canonical = aliasMap.get(token);
|
|
797
|
+
if (!canonical || seen.has(token)) continue;
|
|
798
|
+
seen.add(token);
|
|
799
|
+
violations.push({
|
|
800
|
+
rule: "alias-token-used",
|
|
801
|
+
severity: "warning",
|
|
802
|
+
line: lineOf(css, m.index),
|
|
803
|
+
detail: `"${token}" is an alias — prefer the canonical "${canonical}"`
|
|
804
|
+
});
|
|
805
|
+
}
|
|
806
|
+
return violations;
|
|
807
|
+
}
|
|
650
808
|
/** Run all structural invariants over a candidate component's source. */
|
|
651
809
|
function validateComponentSource(input, options = {}) {
|
|
652
810
|
const violations = [];
|
|
@@ -655,6 +813,7 @@ function validateComponentSource(input, options = {}) {
|
|
|
655
813
|
violations.push(...checkBreakpoints(input.css));
|
|
656
814
|
violations.push(...checkFallbacks(input.css));
|
|
657
815
|
if (options.tokenNames) violations.push(...checkTokens(input.css, options.tokenNames));
|
|
816
|
+
if (options.aliasMap) violations.push(...checkAliasTokens(input.css, options.aliasMap));
|
|
658
817
|
}
|
|
659
818
|
violations.sort((a, b) => a.line - b.line);
|
|
660
819
|
return {
|
|
@@ -665,10 +824,10 @@ function validateComponentSource(input, options = {}) {
|
|
|
665
824
|
}
|
|
666
825
|
//#endregion
|
|
667
826
|
//#region src/context.ts
|
|
668
|
-
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
669
|
-
const CONTEXT_BASE_URL =
|
|
827
|
+
const HERE$1 = dirname(fileURLToPath(import.meta.url));
|
|
828
|
+
const CONTEXT_BASE_URL = CASCIVO_HOST;
|
|
670
829
|
async function loadContext(fetchFn) {
|
|
671
|
-
const localPaths = [join(HERE, "..", "..", "..", "apps", "
|
|
830
|
+
const localPaths = [join(HERE$1, "..", "..", "..", "apps", "site", "public", "context.json"), join(HERE$1, "context.json")];
|
|
672
831
|
for (const p of localPaths) if (existsSync(p)) return JSON.parse(readFileSync(p, "utf8"));
|
|
673
832
|
const res = await (fetchFn ?? fetch)(`${CONTEXT_BASE_URL}/context.json`);
|
|
674
833
|
if (!res.ok) throw new Error(`Failed to fetch context bundle: ${res.status}`);
|
|
@@ -676,7 +835,7 @@ async function loadContext(fetchFn) {
|
|
|
676
835
|
}
|
|
677
836
|
async function loadComponentMarkdown(name, fetchFn) {
|
|
678
837
|
const slug = name.toLowerCase().replace(/\s+/g, "-");
|
|
679
|
-
const localPaths = [join(HERE, "..", "..", "..", "apps", "
|
|
838
|
+
const localPaths = [join(HERE$1, "..", "..", "..", "apps", "site", "public", "context", `${slug}.md`), join(HERE$1, "context", `${slug}.md`)];
|
|
680
839
|
for (const p of localPaths) if (existsSync(p)) return readFileSync(p, "utf8");
|
|
681
840
|
const url = `${CONTEXT_BASE_URL}/context/${slug}.md`;
|
|
682
841
|
const fn = fetchFn ?? fetch;
|
|
@@ -739,6 +898,60 @@ function selectComponent(need, components) {
|
|
|
739
898
|
})).filter((r) => r.score >= 0).sort((a, b) => b.score - a.score || a.name.localeCompare(b.name)).slice(0, 5);
|
|
740
899
|
}
|
|
741
900
|
//#endregion
|
|
901
|
+
//#region src/templates.ts
|
|
902
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
903
|
+
const CATALOG_BASE_URL = CASCIVO_HOST;
|
|
904
|
+
/**
|
|
905
|
+
* Resolve a local marketplace catalog. Checks, in order: an explicit path,
|
|
906
|
+
* the `CASCIVO_MARKETPLACE_PATH` env var, a copy bundled next to the built
|
|
907
|
+
* server, and the in-repo `apps/site/public/marketplace.json` (dev).
|
|
908
|
+
* Returns null when no candidate exists on disk.
|
|
909
|
+
*/
|
|
910
|
+
function resolveCatalogPath(explicit) {
|
|
911
|
+
return [
|
|
912
|
+
explicit,
|
|
913
|
+
process.env.CASCIVO_MARKETPLACE_PATH,
|
|
914
|
+
join(HERE, "marketplace.json"),
|
|
915
|
+
join(HERE, "..", "..", "..", "apps", "site", "public", "marketplace.json"),
|
|
916
|
+
join(HERE, "..", "..", "apps", "site", "public", "marketplace.json")
|
|
917
|
+
].filter((p) => typeof p === "string" && p.length > 0).find((p) => existsSync(p)) ?? null;
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* Load the marketplace catalog: bundled/local copy first, then the hosted one.
|
|
921
|
+
* Throws when neither is available — an empty catalog would silently make
|
|
922
|
+
* every template invisible, which is worse than a visible error.
|
|
923
|
+
*/
|
|
924
|
+
async function loadCatalog(explicit, fetchFn) {
|
|
925
|
+
const path = resolveCatalogPath(explicit);
|
|
926
|
+
if (path) try {
|
|
927
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
928
|
+
} catch {}
|
|
929
|
+
const url = `${CATALOG_BASE_URL}/marketplace.json`;
|
|
930
|
+
const fn = fetchFn ?? fetch;
|
|
931
|
+
let res;
|
|
932
|
+
try {
|
|
933
|
+
res = await fn(url);
|
|
934
|
+
} catch (e) {
|
|
935
|
+
throw new Error(`Marketplace catalog unavailable: no local copy and ${url} unreachable (${e instanceof Error ? e.message : e})`);
|
|
936
|
+
}
|
|
937
|
+
if (!res.ok) throw new Error(`Marketplace catalog unavailable: no local copy and ${url} returned ${res.status}`);
|
|
938
|
+
return res.json();
|
|
939
|
+
}
|
|
940
|
+
/** Filter the catalog's templates. Pure. */
|
|
941
|
+
function listTemplates(catalog, filter = {}) {
|
|
942
|
+
return catalog.templates.filter((t) => {
|
|
943
|
+
if (filter.category && t.category !== filter.category) return false;
|
|
944
|
+
if (filter.framework && t.framework !== filter.framework) return false;
|
|
945
|
+
if (filter.verifiedOnly && !t.verified) return false;
|
|
946
|
+
if (filter.tag && !t.tags.includes(filter.tag)) return false;
|
|
947
|
+
return true;
|
|
948
|
+
});
|
|
949
|
+
}
|
|
950
|
+
/** Find one template by name or installSpec. Pure. */
|
|
951
|
+
function getTemplate(catalog, name) {
|
|
952
|
+
return catalog.templates.find((t) => t.name === name || t.installSpec === name);
|
|
953
|
+
}
|
|
954
|
+
//#endregion
|
|
742
955
|
//#region src/server.ts
|
|
743
956
|
const json = (value) => ({ content: [{
|
|
744
957
|
type: "text",
|
|
@@ -815,7 +1028,7 @@ function createServer(options = {}) {
|
|
|
815
1028
|
const meta = getComponent(remoteRegistry, name);
|
|
816
1029
|
return meta ? json(meta) : error(`Component "${name}" not found in registry "${ns}".`);
|
|
817
1030
|
}
|
|
818
|
-
const meta =
|
|
1031
|
+
const meta = getComponentWithVersion(registry, name);
|
|
819
1032
|
return meta ? json(meta) : error(`Component "${name}" not found.`);
|
|
820
1033
|
});
|
|
821
1034
|
server.registerTool("search_components", {
|
|
@@ -860,6 +1073,96 @@ function createServer(options = {}) {
|
|
|
860
1073
|
if (result.status !== 0) return error(result.stderr || result.error?.message || `Failed to add "${name}".`);
|
|
861
1074
|
return text(result.stdout || `Added ${name}.`);
|
|
862
1075
|
});
|
|
1076
|
+
server.registerTool("list_templates", {
|
|
1077
|
+
title: "List templates",
|
|
1078
|
+
description: "List marketplace templates (whole-page compositions) from the static catalog, optionally filtered by category, tag, framework, or verified status.",
|
|
1079
|
+
inputSchema: {
|
|
1080
|
+
category: z.string().optional().describe("Filter by category, e.g. \"dashboard\""),
|
|
1081
|
+
tag: z.string().optional().describe("Filter by tag"),
|
|
1082
|
+
framework: z.enum(["react-vite", "react-next"]).optional().describe("Filter by target framework"),
|
|
1083
|
+
verifiedOnly: z.boolean().optional().describe("Only verified templates")
|
|
1084
|
+
}
|
|
1085
|
+
}, async ({ category, tag, framework, verifiedOnly }) => {
|
|
1086
|
+
let catalog;
|
|
1087
|
+
try {
|
|
1088
|
+
catalog = await loadCatalog(void 0, fetchFn);
|
|
1089
|
+
} catch (e) {
|
|
1090
|
+
return error(e instanceof Error ? e.message : String(e));
|
|
1091
|
+
}
|
|
1092
|
+
const filter = {};
|
|
1093
|
+
if (category) filter.category = category;
|
|
1094
|
+
if (tag) filter.tag = tag;
|
|
1095
|
+
if (framework) filter.framework = framework;
|
|
1096
|
+
if (verifiedOnly) filter.verifiedOnly = verifiedOnly;
|
|
1097
|
+
return json(listTemplates(catalog, filter));
|
|
1098
|
+
});
|
|
1099
|
+
server.registerTool("get_template", {
|
|
1100
|
+
title: "Get template",
|
|
1101
|
+
description: "Get one marketplace template by name or install spec — its components, install command, demo link, and screenshots.",
|
|
1102
|
+
inputSchema: { name: z.string().describe("Template name or install spec, e.g. \"@cascivo/dashboard\"") }
|
|
1103
|
+
}, async ({ name }) => {
|
|
1104
|
+
let catalog;
|
|
1105
|
+
try {
|
|
1106
|
+
catalog = await loadCatalog(void 0, fetchFn);
|
|
1107
|
+
} catch (e) {
|
|
1108
|
+
return error(e instanceof Error ? e.message : String(e));
|
|
1109
|
+
}
|
|
1110
|
+
const tpl = getTemplate(catalog, name);
|
|
1111
|
+
if (!tpl) return error(`Template "${name}" not found in the marketplace catalog.`);
|
|
1112
|
+
return json(tpl);
|
|
1113
|
+
});
|
|
1114
|
+
server.registerTool("add_template", {
|
|
1115
|
+
title: "Add template",
|
|
1116
|
+
description: "Install a marketplace template (its components + page/fixture files) into the current project by running the cascade CLI.",
|
|
1117
|
+
inputSchema: {
|
|
1118
|
+
name: z.string().describe("Template name or install spec, e.g. \"@cascivo/dashboard\""),
|
|
1119
|
+
cwd: z.string().optional().describe("Project directory (default: current directory)")
|
|
1120
|
+
}
|
|
1121
|
+
}, async ({ name, cwd }) => {
|
|
1122
|
+
const catalog = await loadCatalog(void 0, fetchFn).catch(() => null);
|
|
1123
|
+
const spec = (catalog ? getTemplate(catalog, name)?.installSpec : void 0) ?? name;
|
|
1124
|
+
const result = spawnSync("npx", [
|
|
1125
|
+
"-y",
|
|
1126
|
+
"cascivo",
|
|
1127
|
+
"add",
|
|
1128
|
+
spec
|
|
1129
|
+
], {
|
|
1130
|
+
encoding: "utf8",
|
|
1131
|
+
...cwd ? { cwd } : {}
|
|
1132
|
+
});
|
|
1133
|
+
if (result.status !== 0) return error(result.stderr || result.error?.message || `Failed to add template "${spec}".`);
|
|
1134
|
+
return text(result.stdout || `Added template ${spec}.`);
|
|
1135
|
+
});
|
|
1136
|
+
server.registerTool("create_app", {
|
|
1137
|
+
title: "Create app",
|
|
1138
|
+
description: "Scaffold a complete, ready-to-run cascivo app (Vite + React + TypeScript) wired with the app shell, side navigation, header, and a theme — one page per nav section, with signal-driven section switching. Runs `cascivo create` as a child process, writing the project into a new <name> directory.",
|
|
1139
|
+
inputSchema: {
|
|
1140
|
+
name: z.string().describe("Project name and directory, e.g. \"my-app\""),
|
|
1141
|
+
theme: z.enum([
|
|
1142
|
+
"light",
|
|
1143
|
+
"dark",
|
|
1144
|
+
"warm"
|
|
1145
|
+
]).optional().describe("Theme to wire in (default: light)"),
|
|
1146
|
+
sections: z.array(z.string()).optional().describe("Side-nav section labels; one page is generated per section (default: Dashboard, Reports, Settings)"),
|
|
1147
|
+
cwd: z.string().optional().describe("Directory to create the app in (default: current directory)")
|
|
1148
|
+
}
|
|
1149
|
+
}, ({ name, theme, sections, cwd }) => {
|
|
1150
|
+
const args = [
|
|
1151
|
+
"-y",
|
|
1152
|
+
"cascivo",
|
|
1153
|
+
"create",
|
|
1154
|
+
name,
|
|
1155
|
+
"--yes"
|
|
1156
|
+
];
|
|
1157
|
+
if (theme) args.push("--theme", theme);
|
|
1158
|
+
if (sections && sections.length > 0) args.push("--sections", sections.join(", "));
|
|
1159
|
+
const result = spawnSync("npx", args, {
|
|
1160
|
+
encoding: "utf8",
|
|
1161
|
+
...cwd ? { cwd } : {}
|
|
1162
|
+
});
|
|
1163
|
+
if (result.status !== 0) return error(result.stderr || result.error?.message || `Failed to create "${name}".`);
|
|
1164
|
+
return text(result.stdout || `Created ${name}.`);
|
|
1165
|
+
});
|
|
863
1166
|
server.registerTool("create_theme", {
|
|
864
1167
|
title: "Create theme",
|
|
865
1168
|
description: "Generate a cascade theme CSS file from three colors.",
|
|
@@ -990,18 +1293,20 @@ export function Diagram() {
|
|
|
990
1293
|
});
|
|
991
1294
|
server.registerTool("get_tokens", {
|
|
992
1295
|
title: "Get tokens",
|
|
993
|
-
description: "Get the cascade token catalog (closed set). Agents must select from this catalog rather than hard-coding values.",
|
|
1296
|
+
description: "Get the cascade token catalog (closed set). Agents must select from this catalog rather than hard-coding values. Returns the CANONICAL token set by default — exactly one name per purpose. Pass includeAliases: true to also list backwards-compat aliases, each tagged with its canonical name.",
|
|
994
1297
|
inputSchema: {
|
|
995
1298
|
group: z.string().optional().describe("Filter by token group, e.g. \"color\", \"space\", \"radius\""),
|
|
996
1299
|
layer: z.enum([
|
|
997
1300
|
"primitive",
|
|
998
1301
|
"semantic",
|
|
999
1302
|
"component"
|
|
1000
|
-
]).optional().describe("Filter by layer")
|
|
1303
|
+
]).optional().describe("Filter by layer"),
|
|
1304
|
+
includeAliases: z.boolean().optional().describe("Include backwards-compat alias tokens (e.g. --cascivo-color-bg → --cascivo-color-background). Off by default so agents see one correct token per purpose.")
|
|
1001
1305
|
}
|
|
1002
|
-
}, async ({ group, layer }) => {
|
|
1306
|
+
}, async ({ group, layer, includeAliases }) => {
|
|
1003
1307
|
try {
|
|
1004
1308
|
let tokens = (await loadTokenCatalog(fetchFn)).tokens;
|
|
1309
|
+
if (!includeAliases) tokens = tokens.filter((t) => !t.aliasOf);
|
|
1005
1310
|
if (group) tokens = tokens.filter((t) => t.group === group);
|
|
1006
1311
|
if (layer) tokens = tokens.filter((t) => t.layer === layer);
|
|
1007
1312
|
return json({
|
|
@@ -1053,15 +1358,20 @@ export function Diagram() {
|
|
|
1053
1358
|
}
|
|
1054
1359
|
}, async ({ tsx, css, name }) => {
|
|
1055
1360
|
let tokenNames;
|
|
1361
|
+
let aliasMap;
|
|
1056
1362
|
try {
|
|
1057
1363
|
const catalog = await loadTokenCatalog(fetchFn);
|
|
1058
1364
|
tokenNames = new Set(catalog.tokens.map((t) => t.name));
|
|
1365
|
+
aliasMap = new Map(catalog.tokens.filter((t) => t.aliasOf).map((t) => [t.name, t.aliasOf]));
|
|
1059
1366
|
} catch {}
|
|
1060
1367
|
return json(validateComponentSource({
|
|
1061
1368
|
...tsx ? { tsx } : {},
|
|
1062
1369
|
...css ? { css } : {},
|
|
1063
1370
|
...name ? { name } : {}
|
|
1064
|
-
},
|
|
1371
|
+
}, {
|
|
1372
|
+
...tokenNames ? { tokenNames } : {},
|
|
1373
|
+
...aliasMap && aliasMap.size > 0 ? { aliasMap } : {}
|
|
1374
|
+
}));
|
|
1065
1375
|
});
|
|
1066
1376
|
server.registerTool("get_context", {
|
|
1067
1377
|
title: "Get context",
|
|
@@ -1105,7 +1415,7 @@ export function Diagram() {
|
|
|
1105
1415
|
}
|
|
1106
1416
|
//#endregion
|
|
1107
1417
|
//#region src/index.ts
|
|
1108
|
-
const VERSION = "
|
|
1418
|
+
const VERSION = createRequire(import.meta.url)("../package.json").version;
|
|
1109
1419
|
/** Start the MCP server over stdio. */
|
|
1110
1420
|
async function main() {
|
|
1111
1421
|
await createServer({ version: VERSION }).connect(new StdioServerTransport());
|