@substrate-kit/cli 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (146) hide show
  1. package/.claude-plugin/plugin.json +1 -1
  2. package/LICENSE +21 -0
  3. package/README.md +25 -8
  4. package/dist/cli.d.ts.map +1 -1
  5. package/dist/cli.js +10 -41
  6. package/dist/cli.js.map +1 -1
  7. package/dist/commands/add.d.ts +1 -0
  8. package/dist/commands/add.d.ts.map +1 -1
  9. package/dist/commands/add.js +51 -109
  10. package/dist/commands/add.js.map +1 -1
  11. package/dist/commands/docs.d.ts +4 -2
  12. package/dist/commands/docs.d.ts.map +1 -1
  13. package/dist/commands/docs.js +33 -54
  14. package/dist/commands/docs.js.map +1 -1
  15. package/dist/commands/init.d.ts.map +1 -1
  16. package/dist/commands/init.js +158 -122
  17. package/dist/commands/init.js.map +1 -1
  18. package/dist/index.js +0 -0
  19. package/dist/mcp/server.d.ts.map +1 -1
  20. package/dist/mcp/server.js +8 -7
  21. package/dist/mcp/server.js.map +1 -1
  22. package/dist/mcp/tools/get-component.d.ts +1 -1
  23. package/dist/mcp/tools/get-component.d.ts.map +1 -1
  24. package/dist/mcp/tools/get-component.js +10 -6
  25. package/dist/mcp/tools/get-component.js.map +1 -1
  26. package/dist/mcp/tools/get-docs.d.ts +17 -0
  27. package/dist/mcp/tools/get-docs.d.ts.map +1 -0
  28. package/dist/mcp/tools/get-docs.js +23 -0
  29. package/dist/mcp/tools/get-docs.js.map +1 -0
  30. package/dist/mcp/tools/install.d.ts +2 -1
  31. package/dist/mcp/tools/install.d.ts.map +1 -1
  32. package/dist/mcp/tools/install.js +18 -27
  33. package/dist/mcp/tools/install.js.map +1 -1
  34. package/dist/mcp/tools/search.d.ts +1 -1
  35. package/dist/mcp/tools/search.d.ts.map +1 -1
  36. package/dist/mcp/tools/search.js +2 -4
  37. package/dist/mcp/tools/search.js.map +1 -1
  38. package/dist/{mcp/types.d.ts → registry.d.ts} +6 -1
  39. package/dist/registry.d.ts.map +1 -0
  40. package/dist/registry.js +48 -0
  41. package/dist/registry.js.map +1 -0
  42. package/dist/utils/agents-file.d.ts +4 -0
  43. package/dist/utils/agents-file.d.ts.map +1 -0
  44. package/dist/utils/agents-file.js +36 -0
  45. package/dist/utils/agents-file.js.map +1 -0
  46. package/dist/utils/docs.d.ts +7 -0
  47. package/dist/utils/docs.d.ts.map +1 -0
  48. package/dist/utils/docs.js +28 -0
  49. package/dist/utils/docs.js.map +1 -0
  50. package/dist/utils/shadcn.d.ts +6 -1
  51. package/dist/utils/shadcn.d.ts.map +1 -1
  52. package/dist/utils/shadcn.js +25 -12
  53. package/dist/utils/shadcn.js.map +1 -1
  54. package/dist/utils/version.d.ts +3 -0
  55. package/dist/utils/version.d.ts.map +1 -0
  56. package/dist/utils/version.js +8 -0
  57. package/dist/utils/version.js.map +1 -0
  58. package/docs/components/action.mdx +30 -0
  59. package/docs/components/alignment-controls.mdx +30 -0
  60. package/docs/components/badge.mdx +22 -0
  61. package/docs/components/breadcrumbs.mdx +25 -0
  62. package/docs/components/collapsible-pane.mdx +35 -0
  63. package/docs/components/colour-picker.mdx +66 -0
  64. package/docs/components/command-palette.mdx +36 -0
  65. package/docs/components/context-menu.mdx +31 -0
  66. package/docs/components/controls-timeline.mdx +107 -0
  67. package/docs/components/controls.mdx +168 -0
  68. package/docs/components/design-canvas.mdx +51 -0
  69. package/docs/components/document-tabs.mdx +25 -0
  70. package/docs/components/easing-curve-editor.mdx +85 -0
  71. package/docs/components/empty-state.mdx +26 -0
  72. package/docs/components/gallery.mdx +60 -0
  73. package/docs/components/history-controls.mdx +47 -0
  74. package/docs/components/image-picker.mdx +50 -0
  75. package/docs/components/layer-list.mdx +36 -0
  76. package/docs/components/node.mdx +85 -0
  77. package/docs/components/number-input.mdx +44 -0
  78. package/docs/components/number-slider.mdx +32 -0
  79. package/docs/components/pane.mdx +39 -0
  80. package/docs/components/panel.mdx +28 -0
  81. package/docs/components/popover.mdx +32 -0
  82. package/docs/components/progress-bar.mdx +24 -0
  83. package/docs/components/resizer.mdx +24 -0
  84. package/docs/components/select.mdx +31 -0
  85. package/docs/components/shortcut-overlay.mdx +43 -0
  86. package/docs/components/slider.mdx +20 -0
  87. package/docs/components/spring-editor.mdx +85 -0
  88. package/docs/components/status-bar.mdx +42 -0
  89. package/docs/components/surface.mdx +47 -0
  90. package/docs/components/swatch-palette.mdx +36 -0
  91. package/docs/components/text-area.mdx +26 -0
  92. package/docs/components/timeline.mdx +96 -0
  93. package/docs/components/toast.mdx +42 -0
  94. package/docs/components/toggle-group.mdx +40 -0
  95. package/docs/components/toggle.mdx +27 -0
  96. package/docs/components/toolbar.mdx +24 -0
  97. package/docs/components/tree-list.mdx +46 -0
  98. package/docs/components/xy-pad.mdx +49 -0
  99. package/docs/hooks/use-clipboard.mdx +70 -0
  100. package/docs/hooks/use-drag-reorder.mdx +60 -0
  101. package/docs/hooks/use-selection.mdx +68 -0
  102. package/docs/hooks/use-undoable.mdx +83 -0
  103. package/docs/hooks/use-viewport.mdx +75 -0
  104. package/docs/utilities/bounding-box.mdx +76 -0
  105. package/docs/utilities/keyboard-shortcuts.mdx +64 -0
  106. package/docs/utilities/snap.mdx +73 -0
  107. package/package.json +6 -4
  108. package/registry.json +274 -6
  109. package/skills/3d-scene/SKILL.md +2 -1
  110. package/skills/animation/SKILL.md +2 -1
  111. package/skills/canvas-2d/SKILL.md +67 -5
  112. package/skills/composing-panels/SKILL.md +1 -4
  113. package/skills/data-visualisation/SKILL.md +2 -1
  114. package/skills/image-generation/SKILL.md +2 -1
  115. package/skills/install-components/SKILL.md +22 -16
  116. package/skills/scaffold-playground/SKILL.md +39 -19
  117. package/skills/substrate-canvas/SKILL.md +5 -0
  118. package/skills/substrate-controls/SKILL.md +57 -2
  119. package/skills/substrate-interaction/SKILL.md +19 -10
  120. package/skills/substrate-scaffold/SKILL.md +4 -4
  121. package/skills/theming/SKILL.md +8 -2
  122. package/skills/wire-interactions/SKILL.md +6 -3
  123. package/templates/agents-section.md +41 -0
  124. package/dist/commands/browse.d.ts +0 -2
  125. package/dist/commands/browse.d.ts.map +0 -1
  126. package/dist/commands/browse.js +0 -25
  127. package/dist/commands/browse.js.map +0 -1
  128. package/dist/commands/create.d.ts +0 -9
  129. package/dist/commands/create.d.ts.map +0 -1
  130. package/dist/commands/create.js +0 -27
  131. package/dist/commands/create.js.map +0 -1
  132. package/dist/commands/variables.d.ts +0 -4
  133. package/dist/commands/variables.d.ts.map +0 -1
  134. package/dist/commands/variables.js +0 -111
  135. package/dist/commands/variables.js.map +0 -1
  136. package/dist/mcp/registry.d.ts +0 -3
  137. package/dist/mcp/registry.d.ts.map +0 -1
  138. package/dist/mcp/registry.js +0 -43
  139. package/dist/mcp/registry.js.map +0 -1
  140. package/dist/mcp/tools/theme.d.ts +0 -10
  141. package/dist/mcp/tools/theme.d.ts.map +0 -1
  142. package/dist/mcp/tools/theme.js +0 -37
  143. package/dist/mcp/tools/theme.js.map +0 -1
  144. package/dist/mcp/types.d.ts.map +0 -1
  145. package/dist/mcp/types.js +0 -2
  146. package/dist/mcp/types.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/search.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAA;AAE3C,wBAAsB,YAAY,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;GAsC5C"}
1
+ {"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../../src/mcp/tools/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAgB,KAAK,QAAQ,EAAE,MAAM,mBAAmB,CAAA;AAE/D,wBAAsB,YAAY,CAChC,QAAQ,EAAE,QAAQ,EAClB,IAAI,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAE;;;;;GAgC5C"}
@@ -1,3 +1,4 @@
1
+ import { matchesQuery } from "../../registry.js";
1
2
  export async function handleSearch(registry, args) {
2
3
  let items = registry.items;
3
4
  if (args.category) {
@@ -6,10 +7,7 @@ export async function handleSearch(registry, args) {
6
7
  }
7
8
  if (args.query) {
8
9
  const q = args.query.toLowerCase();
9
- items = items.filter((i) => i.name.includes(q) ||
10
- (i.title || "").toLowerCase().includes(q) ||
11
- (i.description || "").toLowerCase().includes(q) ||
12
- (i.categories || []).some((c) => c.toLowerCase().includes(q)));
10
+ items = items.filter((i) => matchesQuery(i, q));
13
11
  }
14
12
  const results = items.map((i) => ({
15
13
  name: i.name,
@@ -1 +1 @@
1
- {"version":3,"file":"search.js","sourceRoot":"","sources":["../../../src/mcp/tools/search.ts"],"names":[],"mappings":"AAEA,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,QAAkB,EAClB,IAA2C;IAE3C,IAAI,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAA;IAE1B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAA;QACvC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CACzB,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,CAC1D,CAAA;IACH,CAAC;IAED,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAA;QAClC,KAAK,GAAG,KAAK,CAAC,MAAM,CAClB,CAAC,CAAC,EAAE,EAAE,CACJ,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;YAClB,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;YACzC,CAAC,CAAC,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;YAC/C,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAChE,CAAA;IACH,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAChC,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,OAAO,EAAE,yBAAyB,CAAC,CAAC,IAAI,EAAE;KAC3C,CAAC,CAAC,CAAA;IAEH,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAe;gBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;aACvC;SACF;KACF,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"search.js","sourceRoot":"","sources":["../../../src/mcp/tools/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAiB,MAAM,mBAAmB,CAAA;AAE/D,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,QAAkB,EAClB,IAA2C;IAE3C,IAAI,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAA;IAE1B,IAAI,IAAI,CAAC,QAAQ,EAAE,CAAC;QAClB,MAAM,GAAG,GAAG,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAE,CAAA;QACvC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CACzB,CAAC,CAAC,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,KAAK,GAAG,CAAC,CAC1D,CAAA;IACH,CAAC;IAED,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;QACf,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAA;QAClC,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,YAAY,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAA;IACjD,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAChC,IAAI,EAAE,CAAC,CAAC,IAAI;QACZ,KAAK,EAAE,CAAC,CAAC,KAAK;QACd,WAAW,EAAE,CAAC,CAAC,WAAW;QAC1B,UAAU,EAAE,CAAC,CAAC,UAAU;QACxB,OAAO,EAAE,yBAAyB,CAAC,CAAC,IAAI,EAAE;KAC3C,CAAC,CAAC,CAAA;IAEH,OAAO;QACL,OAAO,EAAE;YACP;gBACE,IAAI,EAAE,MAAe;gBACrB,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;aACvC;SACF;KACF,CAAA;AACH,CAAC"}
@@ -1,3 +1,5 @@
1
+ export declare const SITE_URL = "https://substrate.georgedrury.co.uk";
2
+ export declare const REGISTRY_BASE: string;
1
3
  export interface RegistryItem {
2
4
  name: string;
3
5
  type: string;
@@ -28,4 +30,7 @@ export interface RegistryItem {
28
30
  export interface Registry {
29
31
  items: RegistryItem[];
30
32
  }
31
- //# sourceMappingURL=types.d.ts.map
33
+ export declare function loadRegistry(): Promise<Registry>;
34
+ export declare function overrideKeys(item: RegistryItem): string[];
35
+ export declare function matchesQuery(item: RegistryItem, query: string): boolean;
36
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,QAAQ,wCAAwC,CAAA;AAG7D,eAAO,MAAM,aAAa,QAAwD,CAAA;AAKlF,MAAM,WAAW,YAAY;IAC5B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,UAAU,CAAC,EAAE,MAAM,EAAE,CAAA;IACrB,YAAY,CAAC,EAAE,MAAM,EAAE,CAAA;IACvB,oBAAoB,CAAC,EAAE,MAAM,EAAE,CAAA;IAC/B,IAAI,CAAC,EAAE;QACN,OAAO,CAAC,EAAE,MAAM,CAAA;QAChB,OAAO,CAAC,EAAE,KAAK,CAAC;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC,CAAA;KAChD,CAAA;IACD,OAAO,CAAC,EAAE;QACT,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC9B,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;QAC9B,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;KAC7B,CAAA;IACD,KAAK,CAAC,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAC7D;AAED,MAAM,WAAW,QAAQ;IACxB,KAAK,EAAE,YAAY,EAAE,CAAA;CACrB;AAKD,wBAAsB,YAAY,IAAI,OAAO,CAAC,QAAQ,CAAC,CAkBtD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,EAAE,CAOzD;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAQvE"}
@@ -0,0 +1,48 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { PACKAGE_ROOT } from "./utils/version.js";
4
+ export const SITE_URL = "https://substrate.georgedrury.co.uk";
5
+ // SUBSTRATE_REGISTRY_URL points installs at a local registry for testing
6
+ export const REGISTRY_BASE = process.env.SUBSTRATE_REGISTRY_URL ?? `${SITE_URL}/r`;
7
+ const REGISTRY_URL = `${SITE_URL}/registry.json`;
8
+ const FETCH_TIMEOUT_MS = 5000;
9
+ // The live registry is the newest; the copy bundled with the package keeps
10
+ // the CLI working offline. A registry.json in the current folder is never
11
+ // read, since it could belong to an unrelated project.
12
+ export async function loadRegistry() {
13
+ try {
14
+ const res = await fetch(REGISTRY_URL, { signal: AbortSignal.timeout(FETCH_TIMEOUT_MS) });
15
+ if (res.ok)
16
+ return (await res.json());
17
+ }
18
+ catch {
19
+ /* fall through to the bundled copy */
20
+ }
21
+ const bundled = join(PACKAGE_ROOT, "registry.json");
22
+ if (existsSync(bundled)) {
23
+ try {
24
+ return JSON.parse(readFileSync(bundled, "utf-8"));
25
+ }
26
+ catch {
27
+ /* fall through */
28
+ }
29
+ }
30
+ throw new Error("Could not load the Substrate registry");
31
+ }
32
+ export function overrideKeys(item) {
33
+ const keys = [];
34
+ for (const value of Object.values(item.cssVars?.theme ?? {})) {
35
+ const match = value.match(/^var\(--([^,)]+)/);
36
+ if (match)
37
+ keys.push(`--${match[1]}`);
38
+ }
39
+ return keys;
40
+ }
41
+ export function matchesQuery(item, query) {
42
+ const q = query.toLowerCase();
43
+ return (item.name.includes(q) ||
44
+ (item.title ?? "").toLowerCase().includes(q) ||
45
+ (item.description ?? "").toLowerCase().includes(q) ||
46
+ (item.categories ?? []).some((c) => c.toLowerCase().includes(q)));
47
+ }
48
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AAClD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAEjD,MAAM,CAAC,MAAM,QAAQ,GAAG,qCAAqC,CAAA;AAE7D,yEAAyE;AACzE,MAAM,CAAC,MAAM,aAAa,GAAG,OAAO,CAAC,GAAG,CAAC,sBAAsB,IAAI,GAAG,QAAQ,IAAI,CAAA;AAElF,MAAM,YAAY,GAAG,GAAG,QAAQ,gBAAgB,CAAA;AAChD,MAAM,gBAAgB,GAAG,IAAI,CAAA;AA2B7B,2EAA2E;AAC3E,0EAA0E;AAC1E,uDAAuD;AACvD,MAAM,CAAC,KAAK,UAAU,YAAY;IACjC,IAAI,CAAC;QACJ,MAAM,GAAG,GAAG,MAAM,KAAK,CAAC,YAAY,EAAE,EAAE,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,gBAAgB,CAAC,EAAE,CAAC,CAAA;QACxF,IAAI,GAAG,CAAC,EAAE;YAAE,OAAO,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAa,CAAA;IAClD,CAAC;IAAC,MAAM,CAAC;QACR,sCAAsC;IACvC,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,CAAC,YAAY,EAAE,eAAe,CAAC,CAAA;IACnD,IAAI,UAAU,CAAC,OAAO,CAAC,EAAE,CAAC;QACzB,IAAI,CAAC;YACJ,OAAO,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,OAAO,EAAE,OAAO,CAAC,CAAa,CAAA;QAC9D,CAAC;QAAC,MAAM,CAAC;YACR,kBAAkB;QACnB,CAAC;IACF,CAAC;IAED,MAAM,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAA;AACzD,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAkB;IAC9C,MAAM,IAAI,GAAa,EAAE,CAAA;IACzB,KAAK,MAAM,KAAK,IAAI,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,KAAK,IAAI,EAAE,CAAC,EAAE,CAAC;QAC9D,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,kBAAkB,CAAC,CAAA;QAC7C,IAAI,KAAK;YAAE,IAAI,CAAC,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;IACtC,CAAC;IACD,OAAO,IAAI,CAAA;AACZ,CAAC;AAED,MAAM,UAAU,YAAY,CAAC,IAAkB,EAAE,KAAa;IAC7D,MAAM,CAAC,GAAG,KAAK,CAAC,WAAW,EAAE,CAAA;IAC7B,OAAO,CACN,IAAI,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QACrB,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC5C,CAAC,IAAI,CAAC,WAAW,IAAI,EAAE,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QAClD,CAAC,IAAI,CAAC,UAAU,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAChE,CAAA;AACF,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare function agentsSection(): string;
2
+ export type MergeResult = "created" | "added" | "updated" | "unchanged";
3
+ export declare function mergeSection(file: string, section: string): MergeResult;
4
+ //# sourceMappingURL=agents-file.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents-file.d.ts","sourceRoot":"","sources":["../../src/utils/agents-file.ts"],"names":[],"mappings":"AAQA,wBAAgB,aAAa,IAAI,MAAM,CAGtC;AAED,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,WAAW,CAAA;AAIvE,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,WAAW,CAuBvE"}
@@ -0,0 +1,36 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { exists, writeFile } from "./fs.js";
4
+ import { PACKAGE_ROOT } from "./version.js";
5
+ const START = "<!-- substrate:start -->";
6
+ const END = "<!-- substrate:end -->";
7
+ export function agentsSection() {
8
+ const body = readFileSync(join(PACKAGE_ROOT, "templates", "agents-section.md"), "utf-8").trimEnd();
9
+ return `${START}\n${body}\n${END}\n`;
10
+ }
11
+ // Puts the Substrate guidance between markers so it can be refreshed later
12
+ // without touching anything the project wrote itself.
13
+ export function mergeSection(file, section) {
14
+ if (!exists(file)) {
15
+ writeFile(file, section);
16
+ return "created";
17
+ }
18
+ const current = readFileSync(file, "utf-8");
19
+ const start = current.indexOf(START);
20
+ const end = current.indexOf(END);
21
+ let next;
22
+ let result;
23
+ if (start !== -1 && end > start) {
24
+ next = current.slice(0, start) + section.trimEnd() + current.slice(end + END.length);
25
+ result = "updated";
26
+ }
27
+ else {
28
+ next = `${current.trimEnd()}\n\n${section}`;
29
+ result = "added";
30
+ }
31
+ if (next === current)
32
+ return "unchanged";
33
+ writeFile(file, next);
34
+ return result;
35
+ }
36
+ //# sourceMappingURL=agents-file.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agents-file.js","sourceRoot":"","sources":["../../src/utils/agents-file.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,SAAS,CAAA;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAE3C,MAAM,KAAK,GAAG,0BAA0B,CAAA;AACxC,MAAM,GAAG,GAAG,wBAAwB,CAAA;AAEpC,MAAM,UAAU,aAAa;IAC5B,MAAM,IAAI,GAAG,YAAY,CAAC,IAAI,CAAC,YAAY,EAAE,WAAW,EAAE,mBAAmB,CAAC,EAAE,OAAO,CAAC,CAAC,OAAO,EAAE,CAAA;IAClG,OAAO,GAAG,KAAK,KAAK,IAAI,KAAK,GAAG,IAAI,CAAA;AACrC,CAAC;AAID,2EAA2E;AAC3E,sDAAsD;AACtD,MAAM,UAAU,YAAY,CAAC,IAAY,EAAE,OAAe;IACzD,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC;QACnB,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;QACxB,OAAO,SAAS,CAAA;IACjB,CAAC;IAED,MAAM,OAAO,GAAG,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC,CAAA;IAC3C,MAAM,KAAK,GAAG,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,CAAA;IACpC,MAAM,GAAG,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAA;IAEhC,IAAI,IAAY,CAAA;IAChB,IAAI,MAAmB,CAAA;IACvB,IAAI,KAAK,KAAK,CAAC,CAAC,IAAI,GAAG,GAAG,KAAK,EAAE,CAAC;QACjC,IAAI,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,GAAG,OAAO,CAAC,OAAO,EAAE,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,GAAG,GAAG,CAAC,MAAM,CAAC,CAAA;QACpF,MAAM,GAAG,SAAS,CAAA;IACnB,CAAC;SAAM,CAAC;QACP,IAAI,GAAG,GAAG,OAAO,CAAC,OAAO,EAAE,OAAO,OAAO,EAAE,CAAA;QAC3C,MAAM,GAAG,OAAO,CAAA;IACjB,CAAC;IAED,IAAI,IAAI,KAAK,OAAO;QAAE,OAAO,WAAW,CAAA;IACxC,SAAS,CAAC,IAAI,EAAE,IAAI,CAAC,CAAA;IACrB,OAAO,MAAM,CAAA;AACd,CAAC"}
@@ -0,0 +1,7 @@
1
+ export interface DocsPage {
2
+ url: string;
3
+ content: string;
4
+ }
5
+ export declare function findDocsPage(name: string): DocsPage | null;
6
+ export declare function docsUrl(name: string): string;
7
+ //# sourceMappingURL=docs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs.d.ts","sourceRoot":"","sources":["../../src/utils/docs.ts"],"names":[],"mappings":"AASA,MAAM,WAAW,QAAQ;IACxB,GAAG,EAAE,MAAM,CAAA;IACX,OAAO,EAAE,MAAM,CAAA;CACf;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,GAAG,QAAQ,GAAG,IAAI,CAa1D;AAGD,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE5C"}
@@ -0,0 +1,28 @@
1
+ import { existsSync, readFileSync, readdirSync } from "node:fs";
2
+ import { join } from "node:path";
3
+ import { SITE_URL } from "../registry.js";
4
+ import { PACKAGE_ROOT } from "./version.js";
5
+ // Docs pages are copied into the package at publish time (see
6
+ // scripts/bundle-cli-assets.mjs), one folder per section of the docs site.
7
+ const DOCS_DIR = join(PACKAGE_ROOT, "docs");
8
+ export function findDocsPage(name) {
9
+ if (!/^[a-z0-9-]+$/.test(name) || !existsSync(DOCS_DIR))
10
+ return null;
11
+ for (const section of readdirSync(DOCS_DIR, { withFileTypes: true })) {
12
+ if (!section.isDirectory())
13
+ continue;
14
+ const file = join(DOCS_DIR, section.name, `${name}.mdx`);
15
+ if (existsSync(file)) {
16
+ return {
17
+ url: `${SITE_URL}/docs/${section.name}/${name}`,
18
+ content: readFileSync(file, "utf-8"),
19
+ };
20
+ }
21
+ }
22
+ return null;
23
+ }
24
+ // Hooks and utilities live under their own sections of the docs site
25
+ export function docsUrl(name) {
26
+ return findDocsPage(name)?.url ?? `${SITE_URL}/docs/components/${name}`;
27
+ }
28
+ //# sourceMappingURL=docs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"docs.js","sourceRoot":"","sources":["../../src/utils/docs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAC/D,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAChC,OAAO,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AAE3C,8DAA8D;AAC9D,2EAA2E;AAC3E,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,CAAA;AAO3C,MAAM,UAAU,YAAY,CAAC,IAAY;IACxC,IAAI,CAAC,cAAc,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC;QAAE,OAAO,IAAI,CAAA;IACpE,KAAK,MAAM,OAAO,IAAI,WAAW,CAAC,QAAQ,EAAE,EAAE,aAAa,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;QACtE,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE;YAAE,SAAQ;QACpC,MAAM,IAAI,GAAG,IAAI,CAAC,QAAQ,EAAE,OAAO,CAAC,IAAI,EAAE,GAAG,IAAI,MAAM,CAAC,CAAA;QACxD,IAAI,UAAU,CAAC,IAAI,CAAC,EAAE,CAAC;YACtB,OAAO;gBACN,GAAG,EAAE,GAAG,QAAQ,SAAS,OAAO,CAAC,IAAI,IAAI,IAAI,EAAE;gBAC/C,OAAO,EAAE,YAAY,CAAC,IAAI,EAAE,OAAO,CAAC;aACpC,CAAA;QACF,CAAC;IACF,CAAC;IACD,OAAO,IAAI,CAAA;AACZ,CAAC;AAED,qEAAqE;AACrE,MAAM,UAAU,OAAO,CAAC,IAAY;IACnC,OAAO,YAAY,CAAC,IAAI,CAAC,EAAE,GAAG,IAAI,GAAG,QAAQ,oBAAoB,IAAI,EAAE,CAAA;AACxE,CAAC"}
@@ -2,6 +2,11 @@ export interface ShadcnAddOptions {
2
2
  cwd?: string;
3
3
  /** Capture output instead of printing it (used by the MCP server) */
4
4
  silent?: boolean;
5
+ /** Pass --yes so shadcn skips its confirmation prompts */
6
+ yes?: boolean;
7
+ /** How many times to try before giving up (default 3) */
8
+ attempts?: number;
5
9
  }
6
- export declare function shadcnAdd(url: string, options?: ShadcnAddOptions): void;
10
+ export type RetryHandler = (attempt: number, error: unknown) => void;
11
+ export declare function shadcnAdd(urls: string[], options?: ShadcnAddOptions, onRetry?: RetryHandler): void;
7
12
  //# sourceMappingURL=shadcn.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"shadcn.d.ts","sourceRoot":"","sources":["../../src/utils/shadcn.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,qEAAqE;IACrE,MAAM,CAAC,EAAE,OAAO,CAAA;CAChB;AAKD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,GAAE,gBAAqB,GAAG,IAAI,CAW3E"}
1
+ {"version":3,"file":"shadcn.d.ts","sourceRoot":"","sources":["../../src/utils/shadcn.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,gBAAgB;IAChC,GAAG,CAAC,EAAE,MAAM,CAAA;IACZ,qEAAqE;IACrE,MAAM,CAAC,EAAE,OAAO,CAAA;IAChB,0DAA0D;IAC1D,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,yDAAyD;IACzD,QAAQ,CAAC,EAAE,MAAM,CAAA;CACjB;AAGD,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,KAAK,IAAI,CAAA;AAOpE,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,OAAO,GAAE,gBAAqB,EAAE,OAAO,CAAC,EAAE,YAAY,GAAG,IAAI,CAoBtG"}
@@ -1,16 +1,29 @@
1
- import { execSync } from "node:child_process";
2
- // shadcn asks before overwriting a file that differs from the registry. With
3
- // no terminal to answer, it exits successfully having written nothing, so
4
- // answer "no" for it: existing files are kept and the rest are installed.
5
- export function shadcnAdd(url, options = {}) {
6
- const { cwd, silent = false } = options;
1
+ import { execFileSync } from "node:child_process";
2
+ // One shadcn call installs every item, so shared dependencies are resolved
3
+ // once. shadcn asks before overwriting a file that differs from the registry;
4
+ // with no terminal to answer it exits successfully having written nothing,
5
+ // so answer "no" for it: existing files are kept and the rest are installed.
6
+ // A dropped request fails the whole call, so it is retried.
7
+ export function shadcnAdd(urls, options = {}, onRetry) {
8
+ const { cwd, silent = false, yes = false, attempts = 3 } = options;
7
9
  const interactive = !silent && process.stdin.isTTY === true;
8
10
  const output = silent ? "pipe" : "inherit";
9
- execSync(`npx shadcn@latest add ${url}`, {
10
- cwd,
11
- encoding: "utf-8",
12
- stdio: interactive ? "inherit" : ["pipe", output, output],
13
- input: interactive ? undefined : "n\n".repeat(200),
14
- });
11
+ const npx = process.platform === "win32" ? "npx.cmd" : "npx";
12
+ for (let attempt = 1;; attempt++) {
13
+ try {
14
+ execFileSync(npx, ["shadcn@latest", "add", ...(yes ? ["--yes"] : []), ...urls], {
15
+ cwd,
16
+ encoding: "utf-8",
17
+ stdio: interactive ? "inherit" : ["pipe", output, output],
18
+ input: interactive ? undefined : "n\n".repeat(200),
19
+ });
20
+ return;
21
+ }
22
+ catch (error) {
23
+ if (attempt >= attempts)
24
+ throw error;
25
+ onRetry?.(attempt, error);
26
+ }
27
+ }
15
28
  }
16
29
  //# sourceMappingURL=shadcn.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"shadcn.js","sourceRoot":"","sources":["../../src/utils/shadcn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAA;AAQ7C,6EAA6E;AAC7E,0EAA0E;AAC1E,0EAA0E;AAC1E,MAAM,UAAU,SAAS,CAAC,GAAW,EAAE,UAA4B,EAAE;IACpE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,KAAK,EAAE,GAAG,OAAO,CAAA;IACvC,MAAM,WAAW,GAAG,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,CAAA;IAC3D,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;IAE1C,QAAQ,CAAC,yBAAyB,GAAG,EAAE,EAAE;QACxC,GAAG;QACH,QAAQ,EAAE,OAAO;QACjB,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;QACzD,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC;KAClD,CAAC,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"shadcn.js","sourceRoot":"","sources":["../../src/utils/shadcn.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAA;AAejD,2EAA2E;AAC3E,8EAA8E;AAC9E,2EAA2E;AAC3E,6EAA6E;AAC7E,4DAA4D;AAC5D,MAAM,UAAU,SAAS,CAAC,IAAc,EAAE,UAA4B,EAAE,EAAE,OAAsB;IAC/F,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,KAAK,EAAE,GAAG,GAAG,KAAK,EAAE,QAAQ,GAAG,CAAC,EAAE,GAAG,OAAO,CAAA;IAClE,MAAM,WAAW,GAAG,CAAC,MAAM,IAAI,OAAO,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,CAAA;IAC3D,MAAM,MAAM,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,SAAS,CAAA;IAC1C,MAAM,GAAG,GAAG,OAAO,CAAC,QAAQ,KAAK,OAAO,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAA;IAE5D,KAAK,IAAI,OAAO,GAAG,CAAC,GAAI,OAAO,EAAE,EAAE,CAAC;QACnC,IAAI,CAAC;YACJ,YAAY,CAAC,GAAG,EAAE,CAAC,eAAe,EAAE,KAAK,EAAE,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,GAAG,IAAI,CAAC,EAAE;gBAC/E,GAAG;gBACH,QAAQ,EAAE,OAAO;gBACjB,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;gBACzD,KAAK,EAAE,WAAW,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC;aAClD,CAAC,CAAA;YACF,OAAM;QACP,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YAChB,IAAI,OAAO,IAAI,QAAQ;gBAAE,MAAM,KAAK,CAAA;YACpC,OAAO,EAAE,CAAC,OAAO,EAAE,KAAK,CAAC,CAAA;QAC1B,CAAC;IACF,CAAC;AACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ export declare const PACKAGE_ROOT: string;
2
+ export declare const VERSION: string;
3
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":"AAOA,eAAO,MAAM,YAAY,QAAc,CAAA;AAEvC,eAAO,MAAM,OAAO,EAAE,MAEb,CAAA"}
@@ -0,0 +1,8 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { dirname, join } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ // Both src/utils and dist/utils sit two levels below the package root
5
+ const packageRoot = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
6
+ export const PACKAGE_ROOT = packageRoot;
7
+ export const VERSION = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf-8")).version;
8
+ //# sourceMappingURL=version.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.js","sourceRoot":"","sources":["../../src/utils/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAA;AACtC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAA;AAExC,sEAAsE;AACtE,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,CAAA;AAE7E,MAAM,CAAC,MAAM,YAAY,GAAG,WAAW,CAAA;AAEvC,MAAM,CAAC,MAAM,OAAO,GACnB,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,WAAW,EAAE,cAAc,CAAC,EAAE,OAAO,CAAC,CACnE,CAAC,OAAO,CAAA"}
@@ -0,0 +1,30 @@
1
+ ---
2
+ title: Action
3
+ description: Form field row layout for property editors.
4
+ ---
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npx shadcn@latest add https://substrate.georgedrury.co.uk/r/action
10
+ ```
11
+
12
+ ## Examples
13
+
14
+ ### Default
15
+
16
+ <ComponentPreview name="action-demo" />
17
+
18
+ ### With colour picker
19
+
20
+ `ActionControls` automatically sizes and strips borders from nested `ColourSwatch`, `Slider`, `NumberInput`, and other slot-aware components.
21
+
22
+ <ComponentPreview name="action-colour" />
23
+
24
+ ## Components
25
+
26
+ | Component | Description |
27
+ | ---------------- | -------------------------------------- |
28
+ | `Action` | Container with flex row layout and gap |
29
+ | `ActionLabel` | Fixed-width label |
30
+ | `ActionControls` | Flex-1 control area |
@@ -0,0 +1,30 @@
1
+ ---
2
+ title: Alignment controls
3
+ description: Align and distribute selected elements – left, centre, right, top, middle, bottom.
4
+ ---
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npx shadcn@latest add https://substrate.georgedrury.co.uk/r/alignment-controls
10
+ ```
11
+
12
+ ## Examples
13
+
14
+ ### Default
15
+
16
+ The first row shows all controls including distribute. The second row hides distribute with `showDistribute={false}`.
17
+
18
+ <ComponentPreview name="alignment-controls-demo" />
19
+
20
+ ## Props
21
+
22
+ | Prop | Type | Default | Description |
23
+ | ---------------- | ------------------------------- | ------- | ----------------------------------- |
24
+ | `onAlign` | `(action: AlignAction) => void` | — | Called with the alignment action |
25
+ | `showDistribute` | `boolean` | `true` | Show distribute horizontal/vertical |
26
+ | `disabled` | `boolean` | — | Disables all buttons |
27
+
28
+ ### AlignAction values
29
+
30
+ `left` · `center-h` · `right` · `top` · `center-v` · `bottom` · `distribute-h` · `distribute-v`
@@ -0,0 +1,22 @@
1
+ ---
2
+ title: Badge
3
+ description: Small label for element types, node categories, and status indicators.
4
+ ---
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npx shadcn@latest add https://substrate.georgedrury.co.uk/r/badge
10
+ ```
11
+
12
+ ## Examples
13
+
14
+ ### Default
15
+
16
+ <ComponentPreview name="badge-demo" />
17
+
18
+ ## Props
19
+
20
+ | Prop | Type | Default | Description |
21
+ | --------- | -------------------------------------------------------- | ----------- | ------------ |
22
+ | `variant` | `"default"` \| `"secondary"` \| `"outline"` \| `"muted"` | `"default"` | Visual style |
@@ -0,0 +1,25 @@
1
+ ---
2
+ title: Breadcrumbs
3
+ description: Navigation path for drilling into nested frames and groups.
4
+ ---
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npx shadcn@latest add https://substrate.georgedrury.co.uk/r/breadcrumbs
10
+ ```
11
+
12
+ ## Examples
13
+
14
+ ### Default
15
+
16
+ Click any ancestor to navigate back up. The active (last) item is bold and non-clickable.
17
+
18
+ <ComponentPreview name="breadcrumbs-demo" />
19
+
20
+ ## Components
21
+
22
+ | Component | Description |
23
+ | ---------------- | ------------------------------------------------ |
24
+ | `Breadcrumbs` | `nav` wrapper with horizontal flex layout |
25
+ | `BreadcrumbItem` | Clickable path segment with auto separator arrow |
@@ -0,0 +1,35 @@
1
+ ---
2
+ title: Collapsible pane
3
+ description: Pane section with expand/collapse toggle. Built on Radix Collapsible with animated height transitions.
4
+ ---
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npx shadcn@latest add https://substrate.georgedrury.co.uk/r/collapsible-pane
10
+ ```
11
+
12
+ ## Examples
13
+
14
+ ### Default
15
+
16
+ The first section starts open, the second starts collapsed. Click the chevron to toggle.
17
+
18
+ <ComponentPreview name="collapsible-pane-demo" />
19
+
20
+ ## Components
21
+
22
+ | Component | Description |
23
+ | ------------------------ | ------------------------------------------- |
24
+ | `CollapsiblePane` | Root wrapper with Radix Collapsible state |
25
+ | `CollapsiblePaneHeader` | Header row with expand/collapse chevron |
26
+ | `CollapsiblePaneLabel` | Section title text |
27
+ | `CollapsiblePaneContent` | Animated body that collapses to zero height |
28
+
29
+ ## Props (CollapsiblePane)
30
+
31
+ | Prop | Type | Default | Description |
32
+ | -------------- | ------------------------- | ------- | --------------------------------- |
33
+ | `defaultOpen` | `boolean` | `true` | Initial open state (uncontrolled) |
34
+ | `open` | `boolean` | — | Controlled open state |
35
+ | `onOpenChange` | `(open: boolean) => void` | — | Called when open state changes |
@@ -0,0 +1,66 @@
1
+ ---
2
+ title: Colour picker
3
+ description: Hex colour picker with swatch trigger, popover panel, and text input — built on react-colorful and Radix Popover.
4
+ ---
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npx shadcn@latest add https://substrate.georgedrury.co.uk/r/colour-picker
10
+ ```
11
+
12
+ ## Examples
13
+
14
+ ### Default
15
+
16
+ <ComponentPreview name="colour-picker-demo" />
17
+
18
+ ### Inline
19
+
20
+ <ComponentPreview name="colour-picker-inline" />
21
+
22
+ ### Custom trigger
23
+
24
+ <ComponentPreview name="colour-picker-custom-trigger" />
25
+
26
+ ## Components
27
+
28
+ | Component | Description |
29
+ | --------------------- | ---------------------------------------------------------------------- |
30
+ | `ColourPicker` | Popover wrapper — renders swatch trigger and picker content in a panel |
31
+ | `ColourPickerContent` | Saturation/hue picker with hex input and preview swatch |
32
+ | `ColourSwatch` | Colour preview button sized `sm` (20px) or `md` (28px) |
33
+ | `ColourInput` | Hex colour text input wrapping `HexColorInput` from react-colorful |
34
+
35
+ ## Props
36
+
37
+ ### ColourPicker
38
+
39
+ | Prop | Type | Default | Description |
40
+ | ---------- | -------------------------- | ------- | -------------------------- |
41
+ | `value` | `string` | — | Current colour, in any CSS format (hex, `oklch()`, `rgb()`, named) |
42
+ | `onChange` | `(colour: string) => void` | — | Called with the new colour as hex |
43
+ | `onStart` | `() => void` | — | Called when a drag or edit begins. Useful for undo snapshots. |
44
+ | `children` | `React.ReactNode` | Swatch | Custom trigger element |
45
+
46
+ ### ColourPickerContent
47
+
48
+ | Prop | Type | Default | Description |
49
+ | ---------- | -------------------------- | ------- | -------------------------- |
50
+ | `value` | `string` | — | Current colour, in any CSS format |
51
+ | `onChange` | `(colour: string) => void` | — | Called with the new colour as hex |
52
+ | `onStart` | `() => void` | — | Called when a drag or edit begins |
53
+
54
+ ### ColourSwatch
55
+
56
+ | Prop | Type | Default | Description |
57
+ | -------- | -------------- | ------- | --------------------- |
58
+ | `colour` | `string` | — | Any CSS colour to display |
59
+ | `size` | `"sm" \| "md"` | `"md"` | Swatch size |
60
+
61
+ ### ColourInput
62
+
63
+ | Prop | Type | Default | Description |
64
+ | ---------- | -------------------------- | ------- | -------------------------- |
65
+ | `value` | `string` | — | Current hex colour value |
66
+ | `onChange` | `(colour: string) => void` | — | Called when colour changes |
@@ -0,0 +1,36 @@
1
+ ---
2
+ title: Command palette
3
+ description: Cmd+K search dialog for tools, actions, and keyboard shortcuts. Built on cmdk and Radix Dialog.
4
+ ---
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npx shadcn@latest add https://substrate.georgedrury.co.uk/r/command-palette
10
+ ```
11
+
12
+ ## Examples
13
+
14
+ ### Default
15
+
16
+ <ComponentPreview name="command-palette-demo" />
17
+
18
+ ## Interaction
19
+
20
+ - **⌘K / Ctrl+K** opens and closes the palette.
21
+ - Type to fuzzy-search across all items.
22
+ - Arrow keys to navigate, Enter to select.
23
+ - Escape to dismiss.
24
+
25
+ ## Components
26
+
27
+ | Component | Description |
28
+ | ------------------ | --------------------------------------- |
29
+ | `CommandPalette` | Dialog wrapper with ⌘K listener |
30
+ | `CommandInput` | Search input with magnifying glass icon |
31
+ | `CommandList` | Scrollable results container |
32
+ | `CommandEmpty` | Shown when no results match |
33
+ | `CommandGroup` | Labelled group of items |
34
+ | `CommandItem` | Selectable result row |
35
+ | `CommandShortcut` | Right-aligned keyboard shortcut hint |
36
+ | `CommandSeparator` | Horizontal divider between groups |
@@ -0,0 +1,31 @@
1
+ ---
2
+ title: Context menu
3
+ description: Right-click menu built on Radix ContextMenu with keyboard shortcut display and submenus.
4
+ ---
5
+
6
+ ## Install
7
+
8
+ ```bash
9
+ npx shadcn@latest add https://substrate.georgedrury.co.uk/r/context-menu
10
+ ```
11
+
12
+ ## Examples
13
+
14
+ ### Default
15
+
16
+ <ComponentPreview name="context-menu-demo" />
17
+
18
+ ## Components
19
+
20
+ | Component | Description |
21
+ | ----------------------- | ------------------------------------- |
22
+ | `ContextMenu` | Root wrapper (Radix ContextMenu.Root) |
23
+ | `ContextMenuTrigger` | Area that responds to right-click |
24
+ | `ContextMenuContent` | Popover menu panel |
25
+ | `ContextMenuItem` | Clickable menu item |
26
+ | `ContextMenuSeparator` | Horizontal divider |
27
+ | `ContextMenuLabel` | Non-interactive section heading |
28
+ | `ContextMenuShortcut` | Right-aligned keyboard shortcut hint |
29
+ | `ContextMenuSub` | Submenu root |
30
+ | `ContextMenuSubTrigger` | Item that opens a submenu |
31
+ | `ContextMenuSubContent` | Submenu panel |