@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.
Files changed (205) hide show
  1. package/LICENSE +1 -1
  2. package/README.md +53 -22
  3. package/dist/context/accordion.md +93 -0
  4. package/dist/context/action-sheet.md +117 -0
  5. package/dist/context/alert-dialog.md +122 -0
  6. package/dist/context/alert.md +110 -0
  7. package/dist/context/app-shell.md +98 -0
  8. package/dist/context/aspect-ratio.md +92 -0
  9. package/dist/context/avatar-group.md +111 -0
  10. package/dist/context/avatar.md +100 -0
  11. package/dist/context/badge.md +119 -0
  12. package/dist/context/block/console-app.md +66 -0
  13. package/dist/context/block/dashboard-charts.md +61 -0
  14. package/dist/context/block/empty-dashboard.md +60 -0
  15. package/dist/context/block/login-page.md +60 -0
  16. package/dist/context/block/notification-center.md +61 -0
  17. package/dist/context/block/page-with-breadcrumb.md +54 -0
  18. package/dist/context/block/settings-form-page.md +60 -0
  19. package/dist/context/block/sidebar-app.md +55 -0
  20. package/dist/context/block/stats-cards.md +61 -0
  21. package/dist/context/block/users-table-page.md +61 -0
  22. package/dist/context/blockquote.md +95 -0
  23. package/dist/context/bottom-sheet.md +112 -0
  24. package/dist/context/breadcrumb.md +106 -0
  25. package/dist/context/button-group.md +100 -0
  26. package/dist/context/button.md +117 -0
  27. package/dist/context/calendar.md +118 -0
  28. package/dist/context/card.md +90 -0
  29. package/dist/context/carousel.md +101 -0
  30. package/dist/context/chart/area-chart.md +99 -0
  31. package/dist/context/chart/bar-chart.md +114 -0
  32. package/dist/context/chart/boxplot.md +82 -0
  33. package/dist/context/chart/bubble-chart.md +80 -0
  34. package/dist/context/chart/bullet.md +75 -0
  35. package/dist/context/chart/calendar.md +79 -0
  36. package/dist/context/chart/candlestick.md +104 -0
  37. package/dist/context/chart/combo-chart.md +78 -0
  38. package/dist/context/chart/funnel.md +87 -0
  39. package/dist/context/chart/gauge.md +87 -0
  40. package/dist/context/chart/heatmap.md +77 -0
  41. package/dist/context/chart/histogram.md +74 -0
  42. package/dist/context/chart/kpi.md +70 -0
  43. package/dist/context/chart/line-chart.md +113 -0
  44. package/dist/context/chart/meter.md +71 -0
  45. package/dist/context/chart/pie-chart.md +126 -0
  46. package/dist/context/chart/polar.md +92 -0
  47. package/dist/context/chart/radar.md +81 -0
  48. package/dist/context/chart/radial-bar.md +96 -0
  49. package/dist/context/chart/sankey.md +89 -0
  50. package/dist/context/chart/scatter-chart.md +90 -0
  51. package/dist/context/chart/sparkline.md +69 -0
  52. package/dist/context/chart/stream.md +88 -0
  53. package/dist/context/chart/sunburst.md +93 -0
  54. package/dist/context/chart/treemap.md +84 -0
  55. package/dist/context/chat-bubble.md +116 -0
  56. package/dist/context/checkbox-card.md +104 -0
  57. package/dist/context/checkbox.md +104 -0
  58. package/dist/context/code-snippet.md +118 -0
  59. package/dist/context/code.md +103 -0
  60. package/dist/context/collapsible.md +104 -0
  61. package/dist/context/color-picker.md +101 -0
  62. package/dist/context/combobox.md +125 -0
  63. package/dist/context/command-menu.md +121 -0
  64. package/dist/context/comparison.md +117 -0
  65. package/dist/context/contained-list.md +103 -0
  66. package/dist/context/context-menu.md +103 -0
  67. package/dist/context/copy-button.md +106 -0
  68. package/dist/context/data-list.md +99 -0
  69. package/dist/context/data-table.md +149 -0
  70. package/dist/context/date-picker.md +116 -0
  71. package/dist/context/date-range-picker.md +113 -0
  72. package/dist/context/dock.md +117 -0
  73. package/dist/context/drawer.md +111 -0
  74. package/dist/context/dropdown.md +96 -0
  75. package/dist/context/editable.md +101 -0
  76. package/dist/context/editor/code-editor.md +146 -0
  77. package/dist/context/editor/highlight.md +74 -0
  78. package/dist/context/empty-state.md +101 -0
  79. package/dist/context/fab.md +128 -0
  80. package/dist/context/field.md +118 -0
  81. package/dist/context/file-uploader.md +121 -0
  82. package/dist/context/filter.md +129 -0
  83. package/dist/context/flow/flow-background.md +103 -0
  84. package/dist/context/flow/flow-canvas.md +82 -0
  85. package/dist/context/flow/flow-controls.md +84 -0
  86. package/dist/context/flow/flow-edge.md +128 -0
  87. package/dist/context/flow/flow-handle.md +83 -0
  88. package/dist/context/flow/flow-minimap.md +89 -0
  89. package/dist/context/flow/flow-node.md +97 -0
  90. package/dist/context/flow/flow-panel.md +75 -0
  91. package/dist/context/flow/flow-story.md +145 -0
  92. package/dist/context/flow/flow.md +139 -0
  93. package/dist/context/form.md +104 -0
  94. package/dist/context/header-panel.md +96 -0
  95. package/dist/context/header.md +104 -0
  96. package/dist/context/heading.md +107 -0
  97. package/dist/context/hover-card.md +108 -0
  98. package/dist/context/icon-button.md +115 -0
  99. package/dist/context/image.md +115 -0
  100. package/dist/context/indicator.md +95 -0
  101. package/dist/context/inline-loading.md +101 -0
  102. package/dist/context/input-group.md +122 -0
  103. package/dist/context/input.md +101 -0
  104. package/dist/context/item.md +109 -0
  105. package/dist/context/join.md +97 -0
  106. package/dist/context/kbd.md +100 -0
  107. package/dist/context/label.md +117 -0
  108. package/dist/context/layout/app-shell.md +85 -0
  109. package/dist/context/layout/auth-layout.md +74 -0
  110. package/dist/context/layout/auto-grid.md +69 -0
  111. package/dist/context/layout/center.md +67 -0
  112. package/dist/context/layout/columns.md +70 -0
  113. package/dist/context/layout/dashboard-layout.md +68 -0
  114. package/dist/context/layout/grid.md +70 -0
  115. package/dist/context/layout/masonry.md +70 -0
  116. package/dist/context/layout/page-header.md +75 -0
  117. package/dist/context/layout/section.md +69 -0
  118. package/dist/context/layout/settings-layout.md +68 -0
  119. package/dist/context/layout/spacer.md +64 -0
  120. package/dist/context/layout/split-view.md +71 -0
  121. package/dist/context/layout/stack.md +93 -0
  122. package/dist/context/link.md +112 -0
  123. package/dist/context/list.md +109 -0
  124. package/dist/context/log-viewer.md +124 -0
  125. package/dist/context/menu-button.md +137 -0
  126. package/dist/context/menu.md +108 -0
  127. package/dist/context/menubar.md +92 -0
  128. package/dist/context/modal.md +96 -0
  129. package/dist/context/multi-select.md +104 -0
  130. package/dist/context/native-select.md +118 -0
  131. package/dist/context/navigation-menu.md +95 -0
  132. package/dist/context/notification.md +116 -0
  133. package/dist/context/number-input.md +117 -0
  134. package/dist/context/otp-input.md +98 -0
  135. package/dist/context/overflow-menu.md +107 -0
  136. package/dist/context/pagination.md +123 -0
  137. package/dist/context/password-input.md +100 -0
  138. package/dist/context/popover.md +108 -0
  139. package/dist/context/progress-bar.md +107 -0
  140. package/dist/context/progress-circle.md +101 -0
  141. package/dist/context/progress-indicator.md +105 -0
  142. package/dist/context/progress.md +109 -0
  143. package/dist/context/prose.md +110 -0
  144. package/dist/context/pull-to-refresh.md +101 -0
  145. package/dist/context/qr-code.md +101 -0
  146. package/dist/context/radial-progress.md +117 -0
  147. package/dist/context/radio-card.md +95 -0
  148. package/dist/context/radio.md +94 -0
  149. package/dist/context/rating-group.md +97 -0
  150. package/dist/context/relative-time.md +90 -0
  151. package/dist/context/resizable.md +102 -0
  152. package/dist/context/scroll-area.md +101 -0
  153. package/dist/context/search.md +113 -0
  154. package/dist/context/section/cta.md +83 -0
  155. package/dist/context/section/feature-grid.md +98 -0
  156. package/dist/context/section/hero.md +101 -0
  157. package/dist/context/section/media-masonry.md +77 -0
  158. package/dist/context/section/page-footer.md +101 -0
  159. package/dist/context/section/stats-band.md +81 -0
  160. package/dist/context/segmented-control.md +101 -0
  161. package/dist/context/select.md +101 -0
  162. package/dist/context/separator.md +88 -0
  163. package/dist/context/sheet.md +100 -0
  164. package/dist/context/shell-header.md +105 -0
  165. package/dist/context/side-nav.md +134 -0
  166. package/dist/context/skeleton.md +101 -0
  167. package/dist/context/skip-nav.md +103 -0
  168. package/dist/context/slider.md +98 -0
  169. package/dist/context/spinner.md +89 -0
  170. package/dist/context/stack.md +96 -0
  171. package/dist/context/stat.md +101 -0
  172. package/dist/context/status.md +105 -0
  173. package/dist/context/steps.md +124 -0
  174. package/dist/context/structured-list.md +107 -0
  175. package/dist/context/swap.md +100 -0
  176. package/dist/context/swipe-item.md +110 -0
  177. package/dist/context/switcher.md +97 -0
  178. package/dist/context/tabs.md +93 -0
  179. package/dist/context/tag.md +110 -0
  180. package/dist/context/tags-input.md +94 -0
  181. package/dist/context/text.md +111 -0
  182. package/dist/context/textarea.md +98 -0
  183. package/dist/context/tile.md +116 -0
  184. package/dist/context/time-picker.md +102 -0
  185. package/dist/context/timeline.md +93 -0
  186. package/dist/context/toast.md +91 -0
  187. package/dist/context/toc.md +110 -0
  188. package/dist/context/toggle-group.md +118 -0
  189. package/dist/context/toggle.md +103 -0
  190. package/dist/context/toggletip.md +119 -0
  191. package/dist/context/tooltip.md +92 -0
  192. package/dist/context/tree-view.md +107 -0
  193. package/dist/context/user.md +100 -0
  194. package/dist/context/visually-hidden.md +90 -0
  195. package/dist/context.json +22456 -0
  196. package/dist/index.d.mts +5 -2
  197. package/dist/index.d.mts.map +1 -1
  198. package/dist/index.mjs +374 -64
  199. package/dist/index.mjs.map +1 -1
  200. package/dist/marketplace.json +70 -0
  201. package/dist/registry.json +5622 -786
  202. package/dist/tokens.catalog.json +2375 -0
  203. package/dist/tokens.variants.json +3291 -0
  204. package/package.json +4 -3
  205. 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; component tokens inherit automatically.
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 = "0.0.0";
144
+ declare const VERSION: string;
142
145
  /** Start the MCP server over stdio. */
143
146
  declare function main(): Promise<void>;
144
147
  //#endregion
@@ -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":";;;KA0BK,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;;;UC/CnD,WAAA;;EAEf,OAAA;EDwBG;ECtBH,OAAA;;EAEA,MAAA;AAAA;;;;;iBAiBc,gBAAA,CAAiB,MAAA,EAAQ,WAAW,EAAE,IAAA;;;UCvBrC,eAAA;EACf,WAAA;EACA,UAAU;AAAA;;;;;iBAgCI,YAAA,CAAa,OAAwB,EAAf,eAAe;;;UC9BpC,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;EACA,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;;iBAoGjE,gBAAA,CAAiB,QAAA,EAAU,QAAA,EAAU,KAAA,WAAgB,iBAAiB;;;UChLrE,WAAA;EACf,IAAA;EACA,QAAA;EJYU;EIVV,IAAA;EJUkC;EIRlC,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;;AJpC7D;AAenB;;;;iBI4EgB,aAAA,CAAc,OAAoB,EAAX,WAAW;;;UChHjC,uBAAA;;EAEf,UAAU;AAAA;;;;;;iBAsCI,qBAAA,CACd,QAAA,EAAU,QAAA,EACV,OAAA,GAAS,uBAA4B;;;cChD1B,OAAA;;iBA0BS,IAAA,IAAQ,OAAO"}
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$3 = dirname(fileURLToPath(import.meta.url));
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$3, "registry.json"),
22
- join(HERE$3, "..", "..", "..", "registry.json"),
23
- join(HERE$3, "..", "..", "registry.json")
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
- const DIRECTORY_URL = "https://cascivo.com/r/registries.json";
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
- /** Darken a color by mixing in black (modern CSS, no preprocessor). */
115
- function darken(color, amount) {
116
- return `color-mix(in oklab, ${color}, black ${amount}%)`;
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
- /** Lighten a color by mixing in white. */
119
- function lighten(color, amount) {
120
- return `color-mix(in oklab, ${color}, white ${amount}%)`;
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; component tokens inherit automatically.
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 { primary, neutral, accent } = colors;
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
- --cascivo-color-bg: ${lighten(neutral, 96)};
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
- --cascivo-color-text: ${darken(neutral, 80)};
145
- --cascivo-color-text-subtle: ${darken(neutral, 50)};
146
- --cascivo-color-text-muted: ${lighten(neutral, 30)};
147
- --cascivo-color-text-on-accent: #ffffff;
148
- --cascivo-color-text-on-destructive: #ffffff;
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
- --cascivo-color-info: ${accent};
159
- --cascivo-color-info-subtle: ${lighten(accent, 88)};
160
- --cascivo-color-focus-ring: ${accent};
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$2 = dirname(fileURLToPath(import.meta.url));
500
- const CATALOG_BASE_URL = "https://cascivo.com";
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$2, "..", "..", "..", "apps", "docs", "public", "tokens.catalog.json"), join(HERE$2, "tokens.catalog.json")];
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$1 = dirname(fileURLToPath(import.meta.url));
511
- const BASE_URL = "https://cascivo.com";
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$1, "..", "..", "..", "apps", "docs", "public", "tokens.variants.json"), join(HERE$1, "tokens.variants.json")];
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 = "https://cascivo.com";
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", "docs", "public", "context.json"), join(HERE, "context.json")];
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", "docs", "public", "context", `${slug}.md`), join(HERE, "context", `${slug}.md`)];
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 = getComponent(registry, name);
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
- }, tokenNames ? { tokenNames } : {}));
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 = "0.0.0";
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());