sonance-brand-mcp 1.3.12 → 1.3.13

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.
@@ -140,6 +140,27 @@ const ELEMENT_CATEGORIES: Record<string, ElementCategory> = {
140
140
  // All element tags to scan for
141
141
  const ALL_ELEMENT_TAGS = Object.keys(ELEMENT_CATEGORIES);
142
142
 
143
+ /**
144
+ * Detect project structure by checking where layout.tsx lives
145
+ */
146
+ function detectProjectStructure(projectRoot: string): { baseDir: string; useSrcDir: boolean } {
147
+ const hasRootLayout = fs.existsSync(path.join(projectRoot, "app/layout.tsx"));
148
+ const hasSrcLayout = fs.existsSync(path.join(projectRoot, "src/app/layout.tsx"));
149
+
150
+ let useSrcDir: boolean;
151
+ if (hasRootLayout) {
152
+ useSrcDir = false;
153
+ } else if (hasSrcLayout) {
154
+ useSrcDir = true;
155
+ } else {
156
+ // Fallback to directory detection
157
+ useSrcDir = fs.existsSync(path.join(projectRoot, "src/app")) ||
158
+ fs.existsSync(path.join(projectRoot, "src/components"));
159
+ }
160
+
161
+ return { baseDir: useSrcDir ? "src" : "", useSrcDir };
162
+ }
163
+
143
164
  /**
144
165
  * Extracts all design-related elements from a file using pattern matching.
145
166
  * This is a simplified AST approach that handles common patterns.
@@ -622,6 +643,7 @@ function analyzeColorArchitecture(sources: ColorSource[]): ColorArchitecture {
622
643
  function scanDirectory(
623
644
  dir: string,
624
645
  projectRoot: string,
646
+ baseDir: string = "src",
625
647
  extensions: string[] = [".tsx", ".jsx", ".js", ".ts", ".css"]
626
648
  ): { elements: ScannedElement[]; images: ScannedElement[]; themeFiles: ThemeFile[]; colorSources: ColorSource[]; filesScanned: number } {
627
649
  const elements: ScannedElement[] = [];
@@ -661,11 +683,13 @@ function scanDirectory(
661
683
  elements.push(...fileElements);
662
684
  }
663
685
 
664
- // Detect component definitions (files in src/components/ui or src/components/layout)
686
+ // Detect component definitions (files in components/ui or components/layout)
665
687
  if (entry.name.endsWith(".tsx") && !entry.name.endsWith(".stories.tsx")) {
688
+ const componentsUiPath = baseDir ? `${baseDir}/components/ui/` : "components/ui/";
689
+ const componentsLayoutPath = baseDir ? `${baseDir}/components/layout/` : "components/layout/";
666
690
  const isComponentDef =
667
- relativePath.startsWith("src/components/ui/") ||
668
- relativePath.startsWith("src/components/layout/");
691
+ relativePath.startsWith(componentsUiPath) ||
692
+ relativePath.startsWith(componentsLayoutPath);
669
693
 
670
694
  if (isComponentDef) {
671
695
  const componentName = entry.name.replace(".tsx", "");
@@ -838,10 +862,11 @@ export async function GET() {
838
862
 
839
863
  try {
840
864
  const projectRoot = process.cwd();
841
- const srcDir = path.join(projectRoot, "src");
865
+ const { baseDir } = detectProjectStructure(projectRoot);
866
+ const scanRoot = baseDir ? path.join(projectRoot, baseDir) : projectRoot;
842
867
 
843
868
  // Scan the codebase
844
- const { elements, images, themeFiles, colorSources, filesScanned } = scanDirectory(srcDir, projectRoot);
869
+ const { elements, images, themeFiles, colorSources, filesScanned } = scanDirectory(scanRoot, projectRoot, baseDir);
845
870
 
846
871
  // Analyze color architecture
847
872
  const colorArchitecture = analyzeColorArchitecture(colorSources);
@@ -920,10 +945,11 @@ export async function POST(request: Request) {
920
945
  if (action === "auto-tag-all") {
921
946
  // Bulk inject IDs into elements missing them
922
947
  const projectRoot = process.cwd();
923
- const srcDir = path.join(projectRoot, "src");
948
+ const { baseDir } = detectProjectStructure(projectRoot);
949
+ const scanRoot = baseDir ? path.join(projectRoot, baseDir) : projectRoot;
924
950
 
925
951
  // Re-scan to get fresh data
926
- const { elements } = scanDirectory(srcDir, projectRoot);
952
+ const { elements } = scanDirectory(scanRoot, projectRoot, baseDir);
927
953
 
928
954
  // Filter to elements that need IDs
929
955
  let targetElements = elements.filter(el => !el.hasId);
package/dist/index.js CHANGED
@@ -27,6 +27,8 @@ function getClaudeConfigPath() {
27
27
  return path.join(os.homedir(), ".config", "Claude", "claude_desktop_config.json");
28
28
  }
29
29
  }
30
+ // ---- DevTools Installation Manifest ----
31
+ const MANIFEST_FILENAME = ".sonance-devtools-manifest.json";
30
32
  /**
31
33
  * Run the installer to add sonance-brand to Claude Desktop config
32
34
  */
@@ -430,25 +432,34 @@ function runDevToolsInstaller() {
430
432
  console.log(" 📂 Installing files...");
431
433
  // Path prefix for logging (shows "src/" or "" based on detected structure)
432
434
  const pathPrefix = baseDir ? `${baseDir}/` : "";
435
+ // Track all created files and directories for the manifest
436
+ const createdFiles = [];
437
+ const createdDirectories = [];
438
+ const modifiedFiles = [];
433
439
  // 1. Install lib files (brand-system.ts, brand-context.tsx, utils.ts)
434
440
  if (!fs.existsSync(libDir)) {
435
441
  fs.mkdirSync(libDir, { recursive: true });
436
442
  }
437
443
  fs.copyFileSync(sourceBrandSystem, path.join(libDir, "brand-system.ts"));
444
+ createdFiles.push(`${pathPrefix}lib/brand-system.ts`);
438
445
  console.log(` ✓ Created ${pathPrefix}lib/brand-system.ts`);
439
446
  fs.copyFileSync(sourceBrandContext, path.join(libDir, "brand-context.tsx"));
447
+ createdFiles.push(`${pathPrefix}lib/brand-context.tsx`);
440
448
  console.log(` ✓ Created ${pathPrefix}lib/brand-context.tsx`);
441
449
  fs.copyFileSync(sourceUtils, path.join(libDir, "utils.ts"));
450
+ createdFiles.push(`${pathPrefix}lib/utils.ts`);
442
451
  console.log(` ✓ Created ${pathPrefix}lib/utils.ts`);
443
452
  // 2. Install DevTools components
444
453
  if (!fs.existsSync(devToolsDir)) {
445
454
  fs.mkdirSync(devToolsDir, { recursive: true });
446
455
  }
456
+ createdDirectories.push(`${pathPrefix}components/dev-tools`);
447
457
  // Copy directory contents
448
458
  const entries = fs.readdirSync(sourceDevTools, { withFileTypes: true });
449
459
  for (const entry of entries) {
450
460
  if (entry.isFile()) {
451
461
  fs.copyFileSync(path.join(sourceDevTools, entry.name), path.join(devToolsDir, entry.name));
462
+ createdFiles.push(`${pathPrefix}components/dev-tools/${entry.name}`);
452
463
  }
453
464
  }
454
465
  console.log(` ✓ Created ${pathPrefix}components/dev-tools/`);
@@ -456,47 +467,61 @@ function runDevToolsInstaller() {
456
467
  if (!fs.existsSync(apiThemeDir)) {
457
468
  fs.mkdirSync(apiThemeDir, { recursive: true });
458
469
  }
470
+ createdDirectories.push(`${pathPrefix}app/api/sonance-theme`);
459
471
  fs.copyFileSync(sourceApiTheme, path.join(apiThemeDir, "route.ts"));
472
+ createdFiles.push(`${pathPrefix}app/api/sonance-theme/route.ts`);
460
473
  console.log(` ✓ Created ${pathPrefix}app/api/sonance-theme/route.ts`);
461
474
  // 4. Install API route for component detection
462
475
  if (!fs.existsSync(apiComponentsDir)) {
463
476
  fs.mkdirSync(apiComponentsDir, { recursive: true });
464
477
  }
478
+ createdDirectories.push(`${pathPrefix}app/api/sonance-components`);
465
479
  fs.copyFileSync(sourceApiComponents, path.join(apiComponentsDir, "route.ts"));
480
+ createdFiles.push(`${pathPrefix}app/api/sonance-components/route.ts`);
466
481
  console.log(` ✓ Created ${pathPrefix}app/api/sonance-components/route.ts`);
467
482
  // 5. Install API route for saving logo configuration
468
483
  if (!fs.existsSync(apiSaveLogoDir)) {
469
484
  fs.mkdirSync(apiSaveLogoDir, { recursive: true });
470
485
  }
486
+ createdDirectories.push(`${pathPrefix}app/api/sonance-save-logo`);
471
487
  fs.copyFileSync(sourceApiSaveLogo, path.join(apiSaveLogoDir, "route.ts"));
488
+ createdFiles.push(`${pathPrefix}app/api/sonance-save-logo/route.ts`);
472
489
  console.log(` ✓ Created ${pathPrefix}app/api/sonance-save-logo/route.ts`);
473
490
  // 7. Install API route for listing logo assets
474
491
  if (!fs.existsSync(apiAssetsDir)) {
475
492
  fs.mkdirSync(apiAssetsDir, { recursive: true });
476
493
  }
494
+ createdDirectories.push(`${pathPrefix}app/api/sonance-assets`);
477
495
  fs.copyFileSync(sourceApiAssets, path.join(apiAssetsDir, "route.ts"));
496
+ createdFiles.push(`${pathPrefix}app/api/sonance-assets/route.ts`);
478
497
  console.log(` ✓ Created ${pathPrefix}app/api/sonance-assets/route.ts`);
479
498
  // 8. Install API route for auto-fixing logo IDs
480
499
  if (!fs.existsSync(apiInjectIdDir)) {
481
500
  fs.mkdirSync(apiInjectIdDir, { recursive: true });
482
501
  }
502
+ createdDirectories.push(`${pathPrefix}app/api/sonance-inject-id`);
483
503
  fs.copyFileSync(sourceApiInjectId, path.join(apiInjectIdDir, "route.ts"));
504
+ createdFiles.push(`${pathPrefix}app/api/sonance-inject-id/route.ts`);
484
505
  console.log(` ✓ Created ${pathPrefix}app/api/sonance-inject-id/route.ts`);
485
506
  // 9. Install API route for project analysis
486
507
  if (!fs.existsSync(apiAnalyzeDir)) {
487
508
  fs.mkdirSync(apiAnalyzeDir, { recursive: true });
488
509
  }
510
+ createdDirectories.push(`${pathPrefix}app/api/sonance-analyze`);
489
511
  fs.copyFileSync(sourceApiAnalyze, path.join(apiAnalyzeDir, "route.ts"));
512
+ createdFiles.push(`${pathPrefix}app/api/sonance-analyze/route.ts`);
490
513
  console.log(` ✓ Created ${pathPrefix}app/api/sonance-analyze/route.ts`);
491
514
  // 11. Install brand-overrides.css for production logo sizing
492
515
  if (!fs.existsSync(stylesDir)) {
493
516
  fs.mkdirSync(stylesDir, { recursive: true });
494
517
  }
495
518
  fs.copyFileSync(sourceBrandOverridesCss, path.join(stylesDir, "brand-overrides.css"));
519
+ createdFiles.push(`${pathPrefix}styles/brand-overrides.css`);
496
520
  console.log(` ✓ Created ${pathPrefix}styles/brand-overrides.css`);
497
521
  // 12. Create theme directory with initial files
498
522
  if (!fs.existsSync(themeDir)) {
499
523
  fs.mkdirSync(themeDir, { recursive: true });
524
+ createdDirectories.push(`${pathPrefix}theme`);
500
525
  // Create initial theme CSS
501
526
  const initialCss = `/**
502
527
  * Sonance Theme - Auto-generated by DevTools
@@ -508,6 +533,7 @@ function runDevToolsInstaller() {
508
533
  }
509
534
  `;
510
535
  fs.writeFileSync(path.join(themeDir, "sonance-theme.css"), initialCss, "utf-8");
536
+ createdFiles.push(`${pathPrefix}theme/sonance-theme.css`);
511
537
  // Create initial config
512
538
  const initialConfig = {
513
539
  baseColor: "#333F48",
@@ -519,6 +545,7 @@ function runDevToolsInstaller() {
519
545
  spacing: "default"
520
546
  };
521
547
  fs.writeFileSync(path.join(themeDir, "sonance-config.json"), JSON.stringify(initialConfig, null, 2), "utf-8");
548
+ createdFiles.push(`${pathPrefix}theme/sonance-config.json`);
522
549
  console.log(` ✓ Created ${pathPrefix}theme/ with initial files`);
523
550
  }
524
551
  // Detect user's file paths
@@ -571,6 +598,7 @@ function runDevToolsInstaller() {
571
598
  if (newImports.length > 0) {
572
599
  cssContent = newImports.join("\n") + "\n" + cssContent;
573
600
  fs.writeFileSync(globalsFullPath, cssContent, "utf-8");
601
+ modifiedFiles.push({ path: detectedGlobalsCss, modification: "css-import" });
574
602
  }
575
603
  cssAutoConfigured = true;
576
604
  }
@@ -626,6 +654,7 @@ function runDevToolsInstaller() {
626
654
  }
627
655
  if (modified) {
628
656
  fs.writeFileSync(layoutFullPath, layoutContent, "utf-8");
657
+ modifiedFiles.push({ path: detectedLayout, modification: "layout-component" });
629
658
  }
630
659
  // Check if layout was fully configured
631
660
  layoutAutoConfigured = layoutContent.includes("SonanceDevTools") && layoutContent.includes("<SonanceDevTools");
@@ -664,6 +693,18 @@ function runDevToolsInstaller() {
664
693
  console.log(" 🚀 DONE! Run your dev server and look for the DevTools button.");
665
694
  }
666
695
  console.log("");
696
+ // Write installation manifest for safe uninstall
697
+ const manifest = {
698
+ version: "1.3.12",
699
+ installedAt: new Date().toISOString(),
700
+ structure: useSrcDir ? "src" : "root",
701
+ files: createdFiles,
702
+ directories: createdDirectories,
703
+ modifiedFiles: modifiedFiles
704
+ };
705
+ fs.writeFileSync(path.join(targetDir, MANIFEST_FILENAME), JSON.stringify(manifest, null, 2));
706
+ console.log(` 📝 Created ${MANIFEST_FILENAME} (for safe uninstall)`);
707
+ console.log("");
667
708
  }
668
709
  /**
669
710
  * Run the uninstaller for DevTools Plugin (removes files and imports)
@@ -743,54 +784,102 @@ function runDevToolsUninstaller() {
743
784
  }
744
785
  }
745
786
  // --- 3. Delete installed directories/files ---
746
- // Detect project structure by checking where layout.tsx actually lives
747
- const hasRootLayout = fs.existsSync(path.join(targetDir, "app/layout.tsx"));
748
- const hasSrcLayout = fs.existsSync(path.join(targetDir, "src/app/layout.tsx"));
749
- let useSrcDir;
750
- if (hasRootLayout) {
751
- // Root layout exists - use root structure (even if src/ also exists)
752
- useSrcDir = false;
753
- }
754
- else if (hasSrcLayout) {
755
- // Only src layout exists
756
- useSrcDir = true;
787
+ // Check for manifest file first (safe uninstall)
788
+ const manifestPath = path.join(targetDir, MANIFEST_FILENAME);
789
+ if (fs.existsSync(manifestPath)) {
790
+ // Use manifest for safe, precise uninstall
791
+ console.log(" 📋 Found installation manifest - using safe uninstall");
792
+ console.log("");
793
+ try {
794
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, "utf-8"));
795
+ // Delete files listed in manifest
796
+ for (const file of manifest.files) {
797
+ const fullPath = path.join(targetDir, file);
798
+ if (fs.existsSync(fullPath)) {
799
+ try {
800
+ fs.unlinkSync(fullPath);
801
+ removedItems.push(`Deleted ${file}`);
802
+ }
803
+ catch (err) {
804
+ errors.push(`Could not delete ${file}`);
805
+ }
806
+ }
807
+ }
808
+ // Delete directories listed in manifest (in reverse order to handle nested)
809
+ const sortedDirs = [...manifest.directories].sort((a, b) => b.length - a.length);
810
+ for (const dir of sortedDirs) {
811
+ const fullPath = path.join(targetDir, dir);
812
+ if (fs.existsSync(fullPath)) {
813
+ try {
814
+ fs.rmSync(fullPath, { recursive: true, force: true });
815
+ removedItems.push(`Deleted ${dir}/`);
816
+ }
817
+ catch (err) {
818
+ errors.push(`Could not delete ${dir}`);
819
+ }
820
+ }
821
+ }
822
+ // Delete the manifest file itself
823
+ fs.unlinkSync(manifestPath);
824
+ removedItems.push(`Deleted ${MANIFEST_FILENAME}`);
825
+ }
826
+ catch (err) {
827
+ errors.push("Could not parse manifest file - falling back to legacy uninstall");
828
+ }
757
829
  }
758
830
  else {
759
- // No layout found - fall back to directory detection
760
- useSrcDir = fs.existsSync(path.join(targetDir, "src/app")) ||
761
- fs.existsSync(path.join(targetDir, "src/components"));
762
- }
763
- const baseDir = useSrcDir ? "src" : "";
764
- const pathPrefix = baseDir ? `${baseDir}/` : "";
765
- const itemsToDelete = [
766
- `${pathPrefix}components/dev-tools`,
767
- `${pathPrefix}styles/brand-overrides.css`,
768
- `${pathPrefix}theme`,
769
- `${pathPrefix}app/api/sonance-theme`,
770
- `${pathPrefix}app/api/sonance-components`,
771
- `${pathPrefix}app/api/sonance-save-logo`,
772
- `${pathPrefix}app/api/sonance-assets`,
773
- `${pathPrefix}app/api/sonance-inject-id`,
774
- `${pathPrefix}app/api/sonance-analyze`,
775
- `${pathPrefix}lib/brand-system.ts`,
776
- `${pathPrefix}lib/brand-context.tsx`,
777
- `${pathPrefix}lib/utils.ts`
778
- ];
779
- for (const item of itemsToDelete) {
780
- const fullPath = path.join(targetDir, item);
781
- if (fs.existsSync(fullPath)) {
782
- try {
783
- const stats = fs.statSync(fullPath);
784
- if (stats.isDirectory()) {
785
- fs.rmSync(fullPath, { recursive: true, force: true });
831
+ // Legacy uninstall (no manifest) - warn user and use hardcoded list
832
+ console.log(" ⚠️ No installation manifest found - using legacy uninstall");
833
+ console.log(" (This may delete files that existed before installation)");
834
+ console.log("");
835
+ // Detect project structure by checking where layout.tsx actually lives
836
+ const hasRootLayout = fs.existsSync(path.join(targetDir, "app/layout.tsx"));
837
+ const hasSrcLayout = fs.existsSync(path.join(targetDir, "src/app/layout.tsx"));
838
+ let useSrcDir;
839
+ if (hasRootLayout) {
840
+ useSrcDir = false;
841
+ }
842
+ else if (hasSrcLayout) {
843
+ useSrcDir = true;
844
+ }
845
+ else {
846
+ useSrcDir = fs.existsSync(path.join(targetDir, "src/app")) ||
847
+ fs.existsSync(path.join(targetDir, "src/components"));
848
+ }
849
+ const baseDir = useSrcDir ? "src" : "";
850
+ const pathPrefix = baseDir ? `${baseDir}/` : "";
851
+ // Legacy list - NOTE: Excludes lib/utils.ts to prevent deleting user's pre-existing utils
852
+ const itemsToDelete = [
853
+ `${pathPrefix}components/dev-tools`,
854
+ `${pathPrefix}styles/brand-overrides.css`,
855
+ `${pathPrefix}theme`,
856
+ `${pathPrefix}app/api/sonance-theme`,
857
+ `${pathPrefix}app/api/sonance-components`,
858
+ `${pathPrefix}app/api/sonance-save-logo`,
859
+ `${pathPrefix}app/api/sonance-assets`,
860
+ `${pathPrefix}app/api/sonance-inject-id`,
861
+ `${pathPrefix}app/api/sonance-analyze`,
862
+ `${pathPrefix}lib/brand-system.ts`,
863
+ `${pathPrefix}lib/brand-context.tsx`
864
+ // NOTE: lib/utils.ts intentionally excluded from legacy uninstall
865
+ // to prevent deleting pre-existing user files
866
+ ];
867
+ for (const item of itemsToDelete) {
868
+ const fullPath = path.join(targetDir, item);
869
+ if (fs.existsSync(fullPath)) {
870
+ try {
871
+ const stats = fs.statSync(fullPath);
872
+ if (stats.isDirectory()) {
873
+ fs.rmSync(fullPath, { recursive: true, force: true });
874
+ }
875
+ else {
876
+ fs.unlinkSync(fullPath);
877
+ }
878
+ removedItems.push(`Deleted ${item}`);
786
879
  }
787
- else {
788
- fs.unlinkSync(fullPath);
880
+ catch (err) {
881
+ errors.push(`Could not delete ${item}`);
789
882
  }
790
- removedItems.push(`Deleted ${item}`);
791
- }
792
- catch (err) {
793
- errors.push(`Could not delete ${item}`);
794
883
  }
795
884
  }
796
885
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sonance-brand-mcp",
3
- "version": "1.3.12",
3
+ "version": "1.3.13",
4
4
  "description": "MCP Server for Sonance Brand Guidelines and Component Library - gives Claude instant access to brand colors, typography, and UI components.",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",