@t3lnet/sceneforge 1.0.9 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +57 -0
  2. package/cli/cli.js +6 -0
  3. package/cli/commands/context.js +791 -0
  4. package/context/context-builder.ts +318 -0
  5. package/context/index.ts +52 -0
  6. package/context/template-loader.ts +161 -0
  7. package/context/templates/base/actions-reference.md +299 -0
  8. package/context/templates/base/cli-reference.md +236 -0
  9. package/context/templates/base/project-overview.md +58 -0
  10. package/context/templates/base/selectors-guide.md +233 -0
  11. package/context/templates/base/yaml-schema.md +210 -0
  12. package/context/templates/skills/balance-timing.md +136 -0
  13. package/context/templates/skills/debug-selector.md +193 -0
  14. package/context/templates/skills/generate-actions.md +94 -0
  15. package/context/templates/skills/optimize-demo.md +218 -0
  16. package/context/templates/skills/review-demo-yaml.md +164 -0
  17. package/context/templates/skills/write-step-script.md +136 -0
  18. package/context/templates/stages/stage1-actions.md +236 -0
  19. package/context/templates/stages/stage2-scripts.md +197 -0
  20. package/context/templates/stages/stage3-balancing.md +229 -0
  21. package/context/templates/stages/stage4-rebalancing.md +228 -0
  22. package/context/tests/context-builder.test.ts +237 -0
  23. package/context/tests/template-loader.test.ts +181 -0
  24. package/context/tests/tool-formatter.test.ts +198 -0
  25. package/context/tool-formatter.ts +189 -0
  26. package/dist/index.cjs +416 -11
  27. package/dist/index.cjs.map +1 -1
  28. package/dist/index.d.cts +182 -1
  29. package/dist/index.d.ts +182 -1
  30. package/dist/index.js +391 -11
  31. package/dist/index.js.map +1 -1
  32. package/package.json +2 -1
package/dist/index.cjs CHANGED
@@ -34,7 +34,10 @@ var index_exports = {};
34
34
  __export(index_exports, {
35
35
  DEMO_SCHEMA_VERSION: () => DEMO_SCHEMA_VERSION,
36
36
  ScriptGenerator: () => ScriptGenerator,
37
+ TOOL_CONFIGS: () => TOOL_CONFIGS,
37
38
  VoiceSynthesizer: () => VoiceSynthesizer,
39
+ buildContext: () => buildContext,
40
+ composeTemplates: () => composeTemplates,
38
41
  createClickAction: () => createClickAction,
39
42
  createDragAction: () => createDragAction,
40
43
  createEmptyDemo: () => createEmptyDemo,
@@ -53,15 +56,35 @@ __export(index_exports, {
53
56
  demoDefinitionSchema: () => demoDefinitionSchema,
54
57
  demoHover: () => demoHover,
55
58
  demoType: () => demoType,
59
+ deployContext: () => deployContext,
56
60
  discoverDemos: () => discoverDemos,
61
+ formatForTool: () => formatForTool,
62
+ formatStageName: () => formatStageName,
57
63
  formatValidationError: () => formatValidationError,
58
64
  generateTimingManifest: () => generateTimingManifest,
65
+ getOutputPath: () => getOutputPath,
66
+ getSkill: () => getSkill,
67
+ getSplitOutputPaths: () => getSplitOutputPaths,
68
+ getStageFileName: () => getStageFileName,
69
+ getSupportedTools: () => getSupportedTools,
70
+ getToolConfig: () => getToolConfig,
71
+ hasTemplates: () => hasTemplates,
59
72
  highlightElement: () => highlightElement,
60
73
  injectCursorOverlay: () => injectCursorOverlay,
74
+ interpolateVariables: () => interpolateVariables,
75
+ isValidFormat: () => isValidFormat,
76
+ isValidTool: () => isValidTool,
77
+ listDeployedContext: () => listDeployedContext,
78
+ listSkills: () => listSkills,
79
+ listTemplates: () => listTemplates,
61
80
  loadDemoDefinition: () => loadDemoDefinition,
81
+ loadTemplate: () => loadTemplate,
82
+ loadTemplatesByCategory: () => loadTemplatesByCategory,
62
83
  moveCursorTo: () => moveCursorTo,
63
84
  parseDemoDefinition: () => parseDemoDefinition,
64
85
  parseFromYAML: () => parseFromYAML,
86
+ previewContext: () => previewContext,
87
+ removeContext: () => removeContext,
65
88
  removeCursorOverlay: () => removeCursorOverlay,
66
89
  resolvePath: () => resolvePath,
67
90
  resolveTarget: () => resolveTarget,
@@ -69,6 +92,7 @@ __export(index_exports, {
69
92
  runDemoFromFile: () => runDemoFromFile,
70
93
  safeParseDemoDefinition: () => safeParseDemoDefinition,
71
94
  serializeToYAML: () => serializeToYAML,
95
+ templateExists: () => templateExists,
72
96
  triggerClickRipple: () => triggerClickRipple,
73
97
  validateDemoDefinition: () => validateDemoDefinition
74
98
  });
@@ -252,8 +276,8 @@ function formatValidationError(error) {
252
276
  }
253
277
  const issues = error.issues;
254
278
  return issues.map((issue) => {
255
- const path5 = issue.path.length ? issue.path.join(".") : "root";
256
- return `${path5}: ${issue.message}`;
279
+ const path7 = issue.path.length ? issue.path.join(".") : "root";
280
+ return `${path7}: ${issue.message}`;
257
281
  }).join("; ");
258
282
  }
259
283
  function parseDemoDefinition(input) {
@@ -383,7 +407,7 @@ function createEmptyStep(id) {
383
407
  };
384
408
  }
385
409
  var SECRET_PATTERN = /\$\{(ENV|SECRET):([A-Za-z0-9_]+)\}/g;
386
- function resolveSecrets(value, resolver, path5 = "root") {
410
+ function resolveSecrets(value, resolver, path7 = "root") {
387
411
  if (typeof value === "string") {
388
412
  if (!SECRET_PATTERN.test(value)) {
389
413
  return value;
@@ -392,20 +416,20 @@ function resolveSecrets(value, resolver, path5 = "root") {
392
416
  return value.replace(SECRET_PATTERN, (_match, _type, key) => {
393
417
  const resolved = resolver(key);
394
418
  if (resolved === void 0) {
395
- throw new Error(`Missing secret for ${key} at ${path5}`);
419
+ throw new Error(`Missing secret for ${key} at ${path7}`);
396
420
  }
397
421
  return resolved;
398
422
  });
399
423
  }
400
424
  if (Array.isArray(value)) {
401
425
  return value.map(
402
- (entry, index) => resolveSecrets(entry, resolver, `${path5}[${index}]`)
426
+ (entry, index) => resolveSecrets(entry, resolver, `${path7}[${index}]`)
403
427
  );
404
428
  }
405
429
  if (value && typeof value === "object") {
406
430
  const result = {};
407
431
  for (const [key, entry] of Object.entries(value)) {
408
- result[key] = resolveSecrets(entry, resolver, `${path5}.${key}`);
432
+ result[key] = resolveSecrets(entry, resolver, `${path7}.${key}`);
409
433
  }
410
434
  return result;
411
435
  }
@@ -484,10 +508,10 @@ function createTypeAction(selector, text) {
484
508
  text
485
509
  };
486
510
  }
487
- function createNavigateAction(path5) {
511
+ function createNavigateAction(path7) {
488
512
  return {
489
513
  action: "navigate",
490
- path: path5
514
+ path: path7
491
515
  };
492
516
  }
493
517
  function createWaitAction(duration) {
@@ -911,7 +935,7 @@ function runCommand(command, args, options = {}) {
911
935
  cwd,
912
936
  maxOutputBytes = DEFAULT_MAX_OUTPUT_BYTES
913
937
  } = options;
914
- return new Promise((resolve2, reject) => {
938
+ return new Promise((resolve3, reject) => {
915
939
  const child = (0, import_child_process.spawn)(command, args, { stdio, cwd });
916
940
  let stdout = "";
917
941
  let stderr = "";
@@ -930,7 +954,7 @@ function runCommand(command, args, options = {}) {
930
954
  });
931
955
  child.on("close", (code) => {
932
956
  if (code === 0) {
933
- resolve2({ stdout, stderr });
957
+ resolve3({ stdout, stderr });
934
958
  return;
935
959
  }
936
960
  const error = new Error(`${command} exited with code ${code}`);
@@ -961,7 +985,7 @@ async function probeMediaDurationMs(filePath) {
961
985
  return null;
962
986
  }
963
987
  function sleep(ms) {
964
- return new Promise((resolve2) => setTimeout(resolve2, ms));
988
+ return new Promise((resolve3) => setTimeout(resolve3, ms));
965
989
  }
966
990
  function getStatusCode(error) {
967
991
  if (!error || typeof error !== "object") return void 0;
@@ -2457,11 +2481,371 @@ async function discoverDemos(demoDir) {
2457
2481
  const files = await fs4.readdir(demoDir);
2458
2482
  return files.filter((f) => f.endsWith(".yaml") || f.endsWith(".yml")).map((f) => path4.join(demoDir, f));
2459
2483
  }
2484
+
2485
+ // context/template-loader.ts
2486
+ var fs5 = __toESM(require("fs/promises"), 1);
2487
+ var path5 = __toESM(require("path"), 1);
2488
+ var import_url = require("url");
2489
+ var import_meta = {};
2490
+ var __filename = (0, import_url.fileURLToPath)(import_meta.url);
2491
+ var __dirname = path5.dirname(__filename);
2492
+ function getTemplatesDir() {
2493
+ return path5.join(__dirname, "templates");
2494
+ }
2495
+ async function loadTemplate(category, name) {
2496
+ const templatesDir = getTemplatesDir();
2497
+ const filePath = path5.join(templatesDir, category, `${name}.md`);
2498
+ try {
2499
+ const content = await fs5.readFile(filePath, "utf-8");
2500
+ return { name, content, category };
2501
+ } catch (error) {
2502
+ throw new Error(`Failed to load template ${category}/${name}: ${error}`);
2503
+ }
2504
+ }
2505
+ async function loadTemplatesByCategory(category) {
2506
+ const templatesDir = getTemplatesDir();
2507
+ const categoryDir = path5.join(templatesDir, category);
2508
+ try {
2509
+ const files = await fs5.readdir(categoryDir);
2510
+ const templates = [];
2511
+ for (const file of files) {
2512
+ if (file.endsWith(".md")) {
2513
+ const name = file.replace(/\.md$/, "");
2514
+ const template = await loadTemplate(category, name);
2515
+ templates.push(template);
2516
+ }
2517
+ }
2518
+ return templates;
2519
+ } catch (error) {
2520
+ throw new Error(`Failed to load templates from ${category}: ${error}`);
2521
+ }
2522
+ }
2523
+ function interpolateVariables(content, variables) {
2524
+ return content.replace(/\{\{(\w+)\}\}/g, (match, key) => {
2525
+ const value = variables[key];
2526
+ if (value === void 0) {
2527
+ return match;
2528
+ }
2529
+ return String(value);
2530
+ });
2531
+ }
2532
+ function composeTemplates(templates, options) {
2533
+ const { separator = "\n\n---\n\n", includeHeaders = false } = options ?? {};
2534
+ return templates.map((template) => {
2535
+ if (includeHeaders) {
2536
+ return `<!-- Template: ${template.category}/${template.name} -->
2537
+
2538
+ ${template.content}`;
2539
+ }
2540
+ return template.content;
2541
+ }).join(separator);
2542
+ }
2543
+ async function listTemplates() {
2544
+ const templatesDir = getTemplatesDir();
2545
+ const result = {
2546
+ base: [],
2547
+ stages: [],
2548
+ skills: []
2549
+ };
2550
+ for (const category of ["base", "stages", "skills"]) {
2551
+ const categoryDir = path5.join(templatesDir, category);
2552
+ try {
2553
+ const files = await fs5.readdir(categoryDir);
2554
+ result[category] = files.filter((f) => f.endsWith(".md")).map((f) => f.replace(/\.md$/, ""));
2555
+ } catch {
2556
+ result[category] = [];
2557
+ }
2558
+ }
2559
+ return result;
2560
+ }
2561
+ async function templateExists(category, name) {
2562
+ const templatesDir = getTemplatesDir();
2563
+ const filePath = path5.join(templatesDir, category, `${name}.md`);
2564
+ try {
2565
+ await fs5.access(filePath);
2566
+ return true;
2567
+ } catch {
2568
+ return false;
2569
+ }
2570
+ }
2571
+
2572
+ // context/tool-formatter.ts
2573
+ var TOOL_CONFIGS = {
2574
+ cursor: {
2575
+ name: "Cursor",
2576
+ description: "Cursor AI IDE with .cursorrules support",
2577
+ combinedFile: ".cursorrules",
2578
+ splitDir: ".cursor/rules",
2579
+ splitFilePrefix: "",
2580
+ fileExtension: ".md",
2581
+ supportsSkills: true
2582
+ },
2583
+ copilot: {
2584
+ name: "GitHub Copilot",
2585
+ description: "GitHub Copilot with instructions file",
2586
+ combinedFile: ".github/copilot-instructions.md",
2587
+ splitDir: ".github/copilot",
2588
+ splitFilePrefix: "",
2589
+ fileExtension: ".md",
2590
+ supportsSkills: false
2591
+ },
2592
+ claude: {
2593
+ name: "Claude Code",
2594
+ description: "Claude Code CLI with CLAUDE.md support",
2595
+ combinedFile: "CLAUDE.md",
2596
+ splitDir: ".claude/rules",
2597
+ splitFilePrefix: "",
2598
+ fileExtension: ".md",
2599
+ supportsSkills: true
2600
+ },
2601
+ codex: {
2602
+ name: "Codex",
2603
+ description: "OpenAI Codex with AGENTS.md support",
2604
+ combinedFile: "AGENTS.md",
2605
+ splitDir: ".codex",
2606
+ splitFilePrefix: "",
2607
+ fileExtension: ".md",
2608
+ supportsSkills: false
2609
+ }
2610
+ };
2611
+ function getSupportedTools() {
2612
+ return Object.keys(TOOL_CONFIGS);
2613
+ }
2614
+ function getToolConfig(tool) {
2615
+ return TOOL_CONFIGS[tool];
2616
+ }
2617
+ function formatForTool(tool, content, options) {
2618
+ const config = TOOL_CONFIGS[tool];
2619
+ const { stage, includeToolHeader = true } = options ?? {};
2620
+ const lines = [];
2621
+ if (includeToolHeader) {
2622
+ lines.push(`# SceneForge LLM Context`);
2623
+ lines.push(``);
2624
+ lines.push(`> Generated for ${config.name}`);
2625
+ if (stage) {
2626
+ lines.push(`> Stage: ${stage}`);
2627
+ }
2628
+ lines.push(``);
2629
+ lines.push(`---`);
2630
+ lines.push(``);
2631
+ }
2632
+ lines.push(content);
2633
+ return lines.join("\n");
2634
+ }
2635
+ function getOutputPath(tool, format, outputDir, stageName) {
2636
+ const config = TOOL_CONFIGS[tool];
2637
+ if (format === "combined") {
2638
+ return `${outputDir}/${config.combinedFile}`;
2639
+ }
2640
+ const fileName = stageName ? `${config.splitFilePrefix}${stageName}${config.fileExtension}` : `${config.splitFilePrefix}main${config.fileExtension}`;
2641
+ return `${outputDir}/${config.splitDir}/${fileName}`;
2642
+ }
2643
+ function getSplitOutputPaths(tool, outputDir, stageNames) {
2644
+ return stageNames.map((stage) => getOutputPath(tool, "split", outputDir, stage));
2645
+ }
2646
+ function formatStageName(stage) {
2647
+ const stageMap = {
2648
+ actions: "Stage 1: Action Generation",
2649
+ scripts: "Stage 2: Script Writing",
2650
+ balance: "Stage 3: Step Balancing",
2651
+ rebalance: "Stage 4: Rebalancing"
2652
+ };
2653
+ return stageMap[stage] ?? stage;
2654
+ }
2655
+ function getStageFileName(stage) {
2656
+ const stageFileMap = {
2657
+ actions: "stage1-actions",
2658
+ scripts: "stage2-scripts",
2659
+ balance: "stage3-balancing",
2660
+ rebalance: "stage4-rebalancing"
2661
+ };
2662
+ return stageFileMap[stage] ?? stage;
2663
+ }
2664
+ function isValidTool(tool) {
2665
+ return tool in TOOL_CONFIGS;
2666
+ }
2667
+ function isValidFormat(format) {
2668
+ return format === "combined" || format === "split";
2669
+ }
2670
+
2671
+ // context/context-builder.ts
2672
+ var fs6 = __toESM(require("fs/promises"), 1);
2673
+ var path6 = __toESM(require("path"), 1);
2674
+ async function buildContext(tool, stage, variables) {
2675
+ const templates = [];
2676
+ const baseTemplates = await loadTemplatesByCategory("base");
2677
+ templates.push(...baseTemplates);
2678
+ if (stage === "all") {
2679
+ const stageTemplates = await loadTemplatesByCategory("stages");
2680
+ templates.push(...stageTemplates);
2681
+ } else {
2682
+ const stageFileName = getStageFileName(stage);
2683
+ try {
2684
+ const stageTemplate = await loadTemplate("stages", stageFileName);
2685
+ templates.push(stageTemplate);
2686
+ } catch {
2687
+ }
2688
+ }
2689
+ let content = composeTemplates(templates, {
2690
+ separator: "\n\n---\n\n",
2691
+ includeHeaders: false
2692
+ });
2693
+ if (variables) {
2694
+ content = interpolateVariables(content, variables);
2695
+ }
2696
+ const stageName = stage === "all" ? void 0 : formatStageName(stage);
2697
+ return formatForTool(tool, content, { stage: stageName });
2698
+ }
2699
+ async function deployContext(options) {
2700
+ const { target, stage, format, outputDir, variables } = options;
2701
+ const results = [];
2702
+ const tools = target === "all" ? getSupportedTools() : [target];
2703
+ const stages = stage === "all" ? ["actions", "scripts", "balance", "rebalance"] : [stage];
2704
+ for (const tool of tools) {
2705
+ if (format === "combined") {
2706
+ try {
2707
+ const content = await buildContext(tool, "all", variables);
2708
+ const filePath = getOutputPath(tool, format, outputDir);
2709
+ const absolutePath = path6.resolve(filePath);
2710
+ await fs6.mkdir(path6.dirname(absolutePath), { recursive: true });
2711
+ await fs6.writeFile(absolutePath, content, "utf-8");
2712
+ results.push({
2713
+ tool,
2714
+ filePath: absolutePath,
2715
+ created: true
2716
+ });
2717
+ } catch (error) {
2718
+ results.push({
2719
+ tool,
2720
+ filePath: getOutputPath(tool, format, outputDir),
2721
+ created: false,
2722
+ error: error instanceof Error ? error.message : String(error)
2723
+ });
2724
+ }
2725
+ } else {
2726
+ for (const stg of stages) {
2727
+ try {
2728
+ const content = await buildContext(tool, stg, variables);
2729
+ const stageName = getStageFileName(stg);
2730
+ const filePath = getOutputPath(tool, format, outputDir, stageName);
2731
+ const absolutePath = path6.resolve(filePath);
2732
+ await fs6.mkdir(path6.dirname(absolutePath), { recursive: true });
2733
+ await fs6.writeFile(absolutePath, content, "utf-8");
2734
+ results.push({
2735
+ tool,
2736
+ filePath: absolutePath,
2737
+ stage: stg,
2738
+ created: true
2739
+ });
2740
+ } catch (error) {
2741
+ results.push({
2742
+ tool,
2743
+ filePath: getOutputPath(tool, format, outputDir, stg),
2744
+ stage: stg,
2745
+ created: false,
2746
+ error: error instanceof Error ? error.message : String(error)
2747
+ });
2748
+ }
2749
+ }
2750
+ }
2751
+ }
2752
+ return results;
2753
+ }
2754
+ async function previewContext(tool, stage, variables) {
2755
+ const content = await buildContext(tool, stage, variables);
2756
+ return {
2757
+ tool,
2758
+ stage: stage === "all" ? void 0 : stage,
2759
+ content
2760
+ };
2761
+ }
2762
+ async function listDeployedContext(outputDir) {
2763
+ const tools = getSupportedTools();
2764
+ const files = [];
2765
+ for (const tool of tools) {
2766
+ const config = getToolConfig(tool);
2767
+ const combinedPath = path6.join(outputDir, config.combinedFile);
2768
+ try {
2769
+ await fs6.access(combinedPath);
2770
+ files.push({ tool, path: combinedPath, exists: true });
2771
+ } catch {
2772
+ files.push({ tool, path: combinedPath, exists: false });
2773
+ }
2774
+ const splitDir = path6.join(outputDir, config.splitDir);
2775
+ try {
2776
+ const splitFiles = await fs6.readdir(splitDir);
2777
+ for (const file of splitFiles) {
2778
+ if (file.endsWith(config.fileExtension)) {
2779
+ files.push({
2780
+ tool,
2781
+ path: path6.join(splitDir, file),
2782
+ exists: true
2783
+ });
2784
+ }
2785
+ }
2786
+ } catch {
2787
+ }
2788
+ }
2789
+ return { files };
2790
+ }
2791
+ async function removeContext(outputDir, target) {
2792
+ const tools = target === "all" ? getSupportedTools() : [target];
2793
+ const results = [];
2794
+ for (const tool of tools) {
2795
+ const config = getToolConfig(tool);
2796
+ const combinedPath = path6.join(outputDir, config.combinedFile);
2797
+ try {
2798
+ await fs6.unlink(combinedPath);
2799
+ results.push({ path: combinedPath, removed: true });
2800
+ } catch (error) {
2801
+ if (error.code !== "ENOENT") {
2802
+ results.push({
2803
+ path: combinedPath,
2804
+ removed: false,
2805
+ error: error instanceof Error ? error.message : String(error)
2806
+ });
2807
+ }
2808
+ }
2809
+ const splitDir = path6.join(outputDir, config.splitDir);
2810
+ try {
2811
+ await fs6.rm(splitDir, { recursive: true });
2812
+ results.push({ path: splitDir, removed: true });
2813
+ } catch (error) {
2814
+ if (error.code !== "ENOENT") {
2815
+ results.push({
2816
+ path: splitDir,
2817
+ removed: false,
2818
+ error: error instanceof Error ? error.message : String(error)
2819
+ });
2820
+ }
2821
+ }
2822
+ }
2823
+ return results;
2824
+ }
2825
+ async function getSkill(name) {
2826
+ try {
2827
+ const template = await loadTemplate("skills", name);
2828
+ return { name: template.name, content: template.content };
2829
+ } catch {
2830
+ return null;
2831
+ }
2832
+ }
2833
+ async function listSkills() {
2834
+ const templates = await listTemplates();
2835
+ return templates.skills;
2836
+ }
2837
+ async function hasTemplates() {
2838
+ const templates = await listTemplates();
2839
+ return templates.base.length > 0;
2840
+ }
2460
2841
  // Annotate the CommonJS export names for ESM import in node:
2461
2842
  0 && (module.exports = {
2462
2843
  DEMO_SCHEMA_VERSION,
2463
2844
  ScriptGenerator,
2845
+ TOOL_CONFIGS,
2464
2846
  VoiceSynthesizer,
2847
+ buildContext,
2848
+ composeTemplates,
2465
2849
  createClickAction,
2466
2850
  createDragAction,
2467
2851
  createEmptyDemo,
@@ -2480,15 +2864,35 @@ async function discoverDemos(demoDir) {
2480
2864
  demoDefinitionSchema,
2481
2865
  demoHover,
2482
2866
  demoType,
2867
+ deployContext,
2483
2868
  discoverDemos,
2869
+ formatForTool,
2870
+ formatStageName,
2484
2871
  formatValidationError,
2485
2872
  generateTimingManifest,
2873
+ getOutputPath,
2874
+ getSkill,
2875
+ getSplitOutputPaths,
2876
+ getStageFileName,
2877
+ getSupportedTools,
2878
+ getToolConfig,
2879
+ hasTemplates,
2486
2880
  highlightElement,
2487
2881
  injectCursorOverlay,
2882
+ interpolateVariables,
2883
+ isValidFormat,
2884
+ isValidTool,
2885
+ listDeployedContext,
2886
+ listSkills,
2887
+ listTemplates,
2488
2888
  loadDemoDefinition,
2889
+ loadTemplate,
2890
+ loadTemplatesByCategory,
2489
2891
  moveCursorTo,
2490
2892
  parseDemoDefinition,
2491
2893
  parseFromYAML,
2894
+ previewContext,
2895
+ removeContext,
2492
2896
  removeCursorOverlay,
2493
2897
  resolvePath,
2494
2898
  resolveTarget,
@@ -2496,6 +2900,7 @@ async function discoverDemos(demoDir) {
2496
2900
  runDemoFromFile,
2497
2901
  safeParseDemoDefinition,
2498
2902
  serializeToYAML,
2903
+ templateExists,
2499
2904
  triggerClickRipple,
2500
2905
  validateDemoDefinition
2501
2906
  });