grg-kit-cli 0.6.11 → 0.6.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.
package/README.md CHANGED
@@ -78,12 +78,15 @@ Examples:
78
78
  - Updates `src/styles.css` with theme import
79
79
 
80
80
  **Available themes:**
81
- - `grg-theme` - Default theme with purple/orange accents
81
+ - `grg-theme` - Default theme with blue accents
82
82
  - `claude` - Claude-inspired warm tones
83
83
  - `clean-slate` - Minimal grayscale palette
84
84
  - `modern-minimal` - Contemporary minimal design
85
85
  - `amber-minimal` - Warm amber accents
86
- - `mocks` - Theme for mockups and prototypes
86
+ - `chroma-clinic` - Professional blue theme for healthcare
87
+ - `bio-lab` - Fresh green theme for life sciences
88
+ - `pharma-teal` - Calming teal for pharmaceutical apps
89
+ - `helix-purple` - DNA-inspired purple for genomics
87
90
 
88
91
  ### `grg add theme`
89
92
 
@@ -153,17 +156,23 @@ Examples:
153
156
  List available blocks and themes.
154
157
 
155
158
  ```bash
156
- grg list [category]
159
+ grg list [category] [options]
160
+
161
+ Options:
162
+ --json Output as JSON (for MCP server integration)
157
163
 
158
164
  Examples:
159
165
  grg list # Show overview
160
166
  grg list blocks # List all blocks
161
167
  grg list themes # List all themes
168
+ grg list --json # Output all resources as JSON
162
169
  ```
163
170
 
171
+ The `--json` flag outputs structured data used by the MCP server to get resource information.
172
+
164
173
  ### `grg llm-setup`
165
174
 
166
- Generate LLM-specific prompts and rules for AI assistants (Windsurf, Cursor, etc.).
175
+ Generate design system rules for AI assistants (Windsurf, Cursor, Claude Code).
167
176
 
168
177
  ```bash
169
178
  grg llm-setup [options]
@@ -172,60 +181,17 @@ Options:
172
181
  -o, --output <path> Output directory for rules (default: ".windsurf/rules")
173
182
 
174
183
  Examples:
175
- grg llm-setup # Windsurf (multiple .md files)
184
+ grg llm-setup # Windsurf/Cursor (design-system.md)
176
185
  grg llm-setup --output .claude # Claude Code (single CLAUDE.md)
177
186
  ```
178
187
 
179
- ## MCP Server Integration
180
-
181
- For AI assistants to automatically discover and use GRG Kit resources:
182
-
183
- ### 1. Install the MCP Server
184
-
185
- ```bash
186
- npm install -g grg-kit-mcp-server
187
- ```
188
-
189
- ### 2. Configure Your AI Assistant
190
-
191
- **Windsurf** (`~/.codeium/windsurf/mcp_config.json`):
192
-
193
- ```json
194
- {
195
- "mcpServers": {
196
- "grg-kit": {
197
- "command": "grg-mcp-server"
198
- }
199
- }
200
- }
201
- ```
202
-
203
- **Claude Code** (`~/.claude/settings.json`):
204
-
205
- ```json
206
- {
207
- "mcpServers": {
208
- "grg-kit": {
209
- "command": "grg-mcp-server"
210
- }
211
- }
212
- }
213
- ```
214
-
215
- ### 3. Generate AI Rules
216
-
217
- ```bash
218
- cd your-angular-project
219
- grg llm-setup
220
- ```
221
-
222
- ### 4. Restart Your IDE
188
+ **What this creates:**
189
+ - `design-system.md` - Component usage patterns, styling rules, import patterns
223
190
 
224
191
  **What this enables:**
225
- - ✅ AI automatically searches GRG Kit before writing custom code
226
- - ✅ AI knows about themes, components, blocks, and examples
227
192
  - ✅ AI follows GRG Kit design system patterns
228
- - ✅ AI can install blocks directly via MCP tools
193
+ - ✅ AI uses Spartan-NG components correctly
194
+ - ✅ AI uses semantic colors instead of raw Tailwind colors
229
195
 
230
196
  ## Quick Reference
231
197
 
package/bin/grg.js CHANGED
@@ -54,6 +54,7 @@ addCommand
54
54
  program
55
55
  .command('list [category]')
56
56
  .description('List available blocks and components')
57
+ .option('--json', 'Output as JSON (for MCP server integration)')
57
58
  .action(list);
58
59
 
59
60
  // LLM Setup command
package/commands/add.js CHANGED
@@ -4,7 +4,7 @@ const ora = require('ora');
4
4
  const fs = require('fs').promises;
5
5
  const path = require('path');
6
6
  const https = require('https');
7
- const { fetchCatalog, REPO } = require('../config/catalog-fetcher');
7
+ const { RESOURCES, REPO } = require('../config/resources');
8
8
 
9
9
  /**
10
10
  * Add command - downloads blocks or specific block files
@@ -16,10 +16,6 @@ const { fetchCatalog, REPO } = require('../config/catalog-fetcher');
16
16
  * grg add block --all # All blocks with all files
17
17
  */
18
18
  async function add(blockName, fileIds, options) {
19
- // Fetch catalog dynamically (with caching)
20
- const spinner = ora('Fetching catalog...').start();
21
- const RESOURCES = await fetchCatalog({ silent: true });
22
- spinner.stop();
23
19
 
24
20
  // Handle --all flag (download everything)
25
21
  if (options.all) {
package/commands/list.js CHANGED
@@ -1,16 +1,34 @@
1
1
  const chalk = require('chalk');
2
- const ora = require('ora');
3
- const { fetchCatalog } = require('../config/catalog-fetcher');
2
+ const { RESOURCES } = require('../config/resources');
3
+ const { version } = require('../package.json');
4
4
 
5
5
  /**
6
6
  * List command - displays available blocks, components, and themes
7
- * Usage: grg list [category]
7
+ * Usage: grg list [category] [--json]
8
8
  */
9
- async function list(category) {
10
- // Fetch catalog dynamically
11
- const spinner = ora('Fetching catalog...').start();
12
- const RESOURCES = await fetchCatalog({ silent: true });
13
- spinner.stop();
9
+ async function list(category, options = {}) {
10
+
11
+ // JSON output for MCP server integration
12
+ if (options.json) {
13
+ const output = {
14
+ cli: {
15
+ name: 'grg',
16
+ version,
17
+ commands: {
18
+ init: { usage: 'grg init [--theme <name>]', themeFlag: '--theme' },
19
+ addBlock: { usage: 'grg add block <blockName> [fileIds...]', validBlocks: RESOURCES.blocks.map(b => b.name) },
20
+ addComponent: { usage: 'grg add component <componentName>', validComponents: RESOURCES.components.map(c => c.name) },
21
+ addTheme: { usage: 'grg add theme <themeName>' },
22
+ list: { usage: 'grg list [category] [--json]' }
23
+ }
24
+ },
25
+ themes: RESOURCES.themes,
26
+ components: RESOURCES.components,
27
+ blocks: RESOURCES.blocks
28
+ };
29
+ console.log(JSON.stringify(output, null, 2));
30
+ return;
31
+ }
14
32
 
15
33
  if (!category) {
16
34
  // Show overview
@@ -2,11 +2,10 @@ const fs = require('fs').promises;
2
2
  const path = require('path');
3
3
  const chalk = require('chalk');
4
4
  const ora = require('ora');
5
- const { RESOURCES } = require('../config/resources');
6
5
 
7
6
  /**
8
7
  * LLM Setup command - generates LLM-specific prompts and rules
9
- * Helps AI assistants understand GRG Kit design system and use MCP server
8
+ * Helps AI assistants understand GRG Kit design system
10
9
  */
11
10
  async function llmPrompts(options) {
12
11
  const outputDir = options.output || '.windsurf/rules';
@@ -78,8 +77,7 @@ async function llmPrompts(options) {
78
77
 
79
78
  console.log(chalk.yellow('\nNext steps:'));
80
79
  console.log(chalk.gray(' 1. The CLAUDE.md file will be automatically picked up by Claude Code'));
81
- console.log(chalk.gray(' 2. Make sure the grg-kit MCP server is configured'));
82
- console.log(chalk.gray(' 3. Claude will now check GRG Kit resources before writing custom code'));
80
+ console.log(chalk.gray(' 2. Claude will now follow GRG Kit design system patterns'));
83
81
  console.log();
84
82
  return;
85
83
  }
@@ -109,44 +107,15 @@ async function llmPrompts(options) {
109
107
  process.exit(1);
110
108
  }
111
109
 
112
- // Step 3: Generate grg-kit-mcp.md
113
- spinner.start('Generating grg-kit-mcp.md...');
114
- try {
115
- const mcpContent = generateMCPRules();
116
- const mcpPath = path.join(outputDir, 'grg-kit-mcp.md');
117
- await fs.writeFile(mcpPath, mcpContent);
118
- spinner.succeed(chalk.green('✓ Generated grg-kit-mcp.md'));
119
- } catch (error) {
120
- spinner.fail(chalk.red('Failed to generate grg-kit-mcp.md'));
121
- console.error(chalk.red(error.message));
122
- process.exit(1);
123
- }
124
-
125
- // Step 4: Generate angular-components.md (glob-triggered)
126
- spinner.start('Generating angular-components.md...');
127
- try {
128
- const angularContent = generateAngularComponentRules();
129
- const angularPath = path.join(outputDir, 'angular-components.md');
130
- await fs.writeFile(angularPath, angularContent);
131
- spinner.succeed(chalk.green('✓ Generated angular-components.md'));
132
- } catch (error) {
133
- spinner.fail(chalk.red('Failed to generate angular-components.md'));
134
- console.error(chalk.red(error.message));
135
- process.exit(1);
136
- }
137
-
138
110
  // Success message
139
111
  console.log(chalk.bold.green('\n✨ LLM rules generated successfully!\n'));
140
- console.log(chalk.gray('Files created:'));
112
+ console.log(chalk.gray('File created:'));
141
113
  console.log(chalk.cyan(` ${outputDir}/design-system.md`));
142
- console.log(chalk.cyan(` ${outputDir}/grg-kit-mcp.md`));
143
- console.log(chalk.cyan(` ${outputDir}/angular-components.md`));
144
114
 
145
115
  console.log(chalk.yellow('\nNext steps:'));
146
116
  console.log(chalk.gray(' 1. These rules will be automatically picked up by your AI assistant'));
147
- console.log(chalk.gray(' 2. Make sure the grg-kit MCP server is configured in your IDE'));
148
- console.log(chalk.gray(' 3. AI will now check GRG Kit resources before writing custom code'));
149
- console.log(chalk.gray('\nSupported IDEs: Windsurf, Claude Code, Claude Desktop'));
117
+ console.log(chalk.gray(' 2. AI will now follow GRG Kit design system patterns'));
118
+ console.log(chalk.gray('\nSupported IDEs: Windsurf, Cursor, Claude Code'));
150
119
  console.log();
151
120
  }
152
121
 
@@ -169,8 +138,8 @@ This project uses **GRG Kit**, a comprehensive Angular UI toolkit built on top o
169
138
 
170
139
  ### 1. Always Check GRG Kit First
171
140
  **BEFORE writing any UI component, layout, or page:**
172
- 1. Use the MCP server to search for existing resources: \`search_ui_resources\`
173
- 2. Check if a component, layout, or example already exists
141
+ 1. Check if a Spartan-NG component exists (button, card, dialog, form-field, table, etc.)
142
+ 2. Check if a block exists for page layouts (auth, shell, settings)
174
143
  3. Only write custom code if no suitable resource exists
175
144
 
176
145
  ### 2. Component Organization
@@ -689,411 +658,17 @@ This design system provides a comprehensive foundation for building consistent,
689
658
  `;
690
659
  }
691
660
 
692
- function generateMCPRules() {
693
- // Dynamically build resource lists from resources.js
694
- // Note: Spartan-NG components are pre-installed, so we only list GRG Kit resources
695
- const themes = RESOURCES.themes || [];
696
- const components = RESOURCES.components || [];
697
- const blocks = RESOURCES.blocks || [];
698
-
699
- const themesList = themes.map(t => `- \`theme:${t.name}\` - ${t.description}`).join('\n');
700
- const componentsList = components.map(c => `- \`component:${c.name}\` - ${c.description}`).join('\n');
701
- const blocksList = blocks.map(b => `- \`block:${b.name}\` - ${b.description}`).join('\n');
702
-
703
- const totalResources = themes.length + components.length + blocks.length;
704
-
705
- return `---
706
- trigger: always_on
707
- ---
708
-
709
- # GRG Kit MCP Server Integration
710
-
711
- ## Setup
712
-
713
- Add the grg-kit MCP server to your AI assistant configuration:
714
-
715
- **Windsurf:** \`~/.codeium/windsurf/mcp_config.json\`
716
- **Claude Code:** \`~/.claude/settings.json\`
717
- **Claude Desktop (macOS):** \`~/Library/Application Support/Claude/claude_desktop_config.json\`
718
-
719
- \`\`\`json
720
- {
721
- "mcpServers": {
722
- "grg-kit": {
723
- "command": "grg-mcp-server"
724
- }
725
- }
726
- }
727
- \`\`\`
728
-
729
- After adding, restart your IDE for the MCP server to be available.
730
-
731
- ## Important: Spartan-NG is Pre-installed
732
-
733
- **Spartan-NG components are already installed** in this project. You do NOT need to use MCP to install them.
734
-
735
- - For Spartan-NG usage patterns → Refer to \`design-system.md\`
736
- - For themes, blocks, and GRG Kit components → Use MCP tools below
737
-
738
- ## When to Use MCP
739
-
740
- Use the MCP server for:
741
- 1. **Themes** - Install different color themes
742
- 2. **Blocks** - Pre-built page layouts (auth, shell, settings)
743
- 3. **GRG Kit Components** - Custom components like file-upload
744
-
745
- **Do NOT use MCP for:**
746
- - Spartan-NG components (button, card, dialog, etc.) - already installed
747
- - Basic UI patterns - see \`design-system.md\`
748
-
749
- ## MCP Server Tools
750
-
751
- The \`grg-kit\` MCP server provides these tools:
752
-
753
- ### 1. mcp2_search_ui_resources
754
-
755
- Search for GRG Kit resources (themes, blocks, components).
756
-
757
- \`\`\`typescript
758
- mcp2_search_ui_resources({
759
- query: "auth",
760
- category: "all" // or "themes", "components", "blocks"
761
- })
762
- \`\`\`
763
-
764
- **When to use:**
765
- - User needs a page layout or block
766
- - Looking for a theme
767
- - Need a GRG Kit component (file-upload)
768
-
769
- ### 2. mcp2_suggest_resources
770
-
771
- Get suggestions based on user requirements.
772
-
773
- \`\`\`typescript
774
- mcp2_suggest_resources({
775
- requirement: "I need a login page"
776
- })
777
-
778
- // Returns: block:auth, theme suggestions
779
- \`\`\`
780
-
781
- ### 3. mcp2_get_resource_details
782
-
783
- Get detailed information about a resource.
784
-
785
- \`\`\`typescript
786
- mcp2_get_resource_details({
787
- resource: "block:auth"
788
- })
789
-
790
- // Returns: dependencies, tags, install command
791
- \`\`\`
792
-
793
- ### 4. mcp2_install_resource
794
-
795
- Get the install command for a block. Returns a command to run via run_command tool.
796
-
797
- \`\`\`typescript
798
- mcp2_install_resource({
799
- resource: "auth", // Just the block name, NOT "block:auth"
800
- files: ["login"], // Optional: specific files to install
801
- output: "src/app/pages/auth" // Optional: custom output directory
802
- })
803
- // Returns: { command: "grg add block auth login", instruction: "Run this command..." }
804
- \`\`\`
805
-
806
- **Important:** The resource parameter should be just the block name (e.g., "auth", "shell", "settings"), NOT prefixed with "block:".
807
-
808
- ### 5. mcp2_list_available_resources
809
-
810
- List all available resources.
811
-
812
- \`\`\`typescript
813
- mcp2_list_available_resources({
814
- category: "all" // or "themes", "components", "blocks"
815
- })
816
- \`\`\`
817
-
818
- ## Workflow Examples
819
-
820
- ### Example 1: User Wants a Dashboard
821
-
822
- \`\`\`
823
- User: "Create a dashboard with a sidebar"
824
-
825
- AI Workflow:
826
- 1. mcp2_search_ui_resources({ query: "shell sidebar" })
827
- → Finds: block:shell
828
-
829
- 2. mcp2_install_resource({ resource: "shell", files: ["sidebar"] })
830
- → Returns command: "grg add block shell sidebar"
831
-
832
- 3. Run the command via run_command tool in the Angular project root
833
-
834
- 4. Customize using Spartan-NG components (from design-system.md)
835
- → Add cards, tables, etc.
836
- \`\`\`
837
-
838
- ### Example 2: User Wants a Login Page
839
-
840
- \`\`\`
841
- User: "I need a login page"
842
-
843
- AI Workflow:
844
- 1. mcp2_search_ui_resources({ query: "auth login" })
845
- → Finds: block:auth
846
-
847
- 2. mcp2_install_resource({ resource: "auth", files: ["login"] })
848
- → Returns command: "grg add block auth login"
849
-
850
- 3. Run the command via run_command tool in the Angular project root
851
-
852
- 4. Customize as needed
853
- \`\`\`
854
-
855
- ### Example 3: User Wants a Theme
856
-
857
- \`\`\`
858
- User: "Change the theme to something warmer"
859
-
860
- AI Workflow:
861
- 1. mcp2_list_available_resources({ category: "themes" })
862
- → Show: claude, amber-minimal, etc.
863
-
864
- 2. mcp2_install_resource({ resource: "claude" })
865
- → Returns command: "grg add theme claude"
866
-
867
- 3. Run the command via run_command tool
868
- → Theme is downloaded and styles.css is updated automatically
869
- \`\`\`
870
-
871
- ### Example 4: User Wants a Form Component
872
-
873
- \`\`\`
874
- User: "I need a file upload"
875
-
876
- AI Workflow:
877
- 1. mcp2_search_ui_resources({ query: "file upload" })
878
- → Finds: component:file-upload
879
-
880
- 2. Components are included automatically with grg init
881
- → Just import and use: import { GrgFileUploadImports } from '@grg-kit/ui/file-upload';
882
-
883
- 3. Use with Spartan-NG form components (from design-system.md)
884
- \`\`\`
885
-
886
- ## Available Resources (${totalResources} total)
887
-
888
- ### Themes (${themes.length} available)
889
- ${themesList}
890
-
891
- ### GRG Kit Components (${components.length} available)
892
- ${componentsList}
893
-
894
- ### Blocks/Layouts (${blocks.length} available)
895
- ${blocksList}
896
-
897
- ## Decision Tree
898
-
899
- \`\`\`
900
- User request:
901
-
902
- ├─ Need a button, card, dialog, form field, table, etc.?
903
- │ └─ Use Spartan-NG (see design-system.md) - ALREADY INSTALLED
904
-
905
- ├─ Need a page layout (dashboard, auth, settings)?
906
- │ └─ Use MCP: mcp2_search_ui_resources({ query: "..." })
907
-
908
- ├─ Need a theme?
909
- │ └─ Use MCP: mcp2_list_available_resources({ category: "themes" })
910
-
911
- └─ Need file-upload or other GRG Kit component?
912
- └─ Use MCP: mcp2_search_ui_resources({ query: "..." })
913
- \`\`\`
914
-
915
- ## Remember
916
-
917
- - **Spartan-NG is pre-installed** - Don't search for button, card, dialog, etc.
918
- - **Use design-system.md** for Spartan-NG patterns
919
- - **Use MCP** only for themes, blocks, and GRG Kit components
920
- - **Check blocks first** when building pages - don't start from scratch
921
- `;
922
- }
923
-
924
- function generateAngularComponentRules() {
925
- // Spartan-NG components are pre-installed - list the common ones
926
- const spartanComponents = [
927
- 'accordion', 'alert', 'alert-dialog', 'avatar', 'badge', 'breadcrumb',
928
- 'button', 'calendar', 'card', 'checkbox', 'collapsible', 'combobox',
929
- 'command', 'context-menu', 'data-table', 'date-picker', 'dialog',
930
- 'dropdown-menu', 'form-field', 'hover-card', 'input', 'label', 'menubar',
931
- 'navigation-menu', 'pagination', 'popover', 'progress', 'radio-group',
932
- 'scroll-area', 'select', 'separator', 'sheet', 'sidebar', 'skeleton',
933
- 'slider', 'sonner', 'spinner', 'switch', 'table', 'tabs', 'textarea',
934
- 'toggle', 'tooltip'
935
- ];
936
- const componentNames = spartanComponents.join(', ');
937
-
938
- return `---
939
- trigger: glob
940
- globs: ["**/*.component.ts", "**/*.component.html"]
941
- ---
942
-
943
- # Angular Component Development with GRG Kit
944
-
945
- You are editing an Angular component. Before writing UI code:
946
-
947
- ## Quick Reference
948
-
949
- ### Spartan-NG Components (Pre-installed)
950
- These components are already available - just import and use them:
951
- ${componentNames}
952
-
953
- ### Import Patterns
954
-
955
- **Spartan-NG (hlm prefix):**
956
- \`\`\`typescript
957
- import { HlmButtonImports } from '@spartan-ng/helm/button';
958
- import { HlmCardImports } from '@spartan-ng/helm/card';
959
- import { BrnDialogImports } from '@spartan-ng/brain/dialog';
960
- import { HlmDialogImports } from '@spartan-ng/helm/dialog';
961
- \`\`\`
962
-
963
- **GRG Kit (grg- prefix):**
964
- \`\`\`typescript
965
- import { GrgFileUploadImports } from '@grg-kit/ui/file-upload';
966
- \`\`\`
967
-
968
- ### Common Patterns
969
-
970
- **Button:**
971
- \`\`\`html
972
- <button hlmBtn>Default</button>
973
- <button hlmBtn variant="outline">Outline</button>
974
- <button hlmBtn variant="destructive">Destructive</button>
975
- \`\`\`
976
-
977
- **Card:**
978
- \`\`\`html
979
- <section hlmCard>
980
- <div hlmCardHeader>
981
- <h3 hlmCardTitle>Title</h3>
982
- <p hlmCardDescription>Description</p>
983
- </div>
984
- <div hlmCardContent>Content</div>
985
- </section>
986
- \`\`\`
987
-
988
- **Form Field:**
989
- \`\`\`html
990
- <hlm-form-field>
991
- <input hlmInput [formControl]="control" placeholder="Email" />
992
- <hlm-error>Required</hlm-error>
993
- </hlm-form-field>
994
- \`\`\`
995
-
996
- **Dialog:**
997
- \`\`\`html
998
- <hlm-dialog>
999
- <button brnDialogTrigger hlmBtn>Open</button>
1000
- <hlm-dialog-content *brnDialogContent="let ctx">
1001
- <hlm-dialog-header>
1002
- <h3 hlmDialogTitle>Title</h3>
1003
- </hlm-dialog-header>
1004
- <!-- content -->
1005
- </hlm-dialog-content>
1006
- </hlm-dialog>
1007
- \`\`\`
1008
-
1009
- ### Icons
1010
- \`\`\`typescript
1011
- import { NgIcon, provideIcons } from '@ng-icons/core';
1012
- import { lucideCheck, lucideX } from '@ng-icons/lucide';
1013
-
1014
- @Component({
1015
- providers: [provideIcons({ lucideCheck, lucideX })],
1016
- template: \`<ng-icon hlm name="lucideCheck" />\`
1017
- })
1018
- \`\`\`
1019
-
1020
- ## When to Use MCP
1021
-
1022
- Use MCP only for:
1023
- - **Blocks** (auth, shell, settings) - \`mcp2_search_ui_resources({ query: "auth" })\`
1024
- - **Themes** - \`mcp2_list_available_resources({ category: "themes" })\`
1025
- - **GRG Kit components** (file-upload) - \`mcp2_search_ui_resources({ query: "file-upload" })\`
1026
-
1027
- **Do NOT use MCP for Spartan-NG components** - they are already installed!
1028
-
1029
- ## TailwindCSS Rules (CRITICAL)
1030
-
1031
- ### NEVER use raw Tailwind colors:
1032
- \`\`\`html
1033
- <!-- ❌ FORBIDDEN -->
1034
- <div class="text-green-600 bg-green-100">Success</div>
1035
- <div class="text-red-500">Error</div>
1036
- <div class="bg-yellow-50 border-yellow-200">Warning</div>
1037
- \`\`\`
1038
-
1039
- ### ALWAYS use semantic color tokens:
1040
- \`\`\`html
1041
- <!-- ✅ CORRECT -->
1042
- <div class="bg-primary text-primary-foreground">Primary</div>
1043
- <div class="bg-destructive text-destructive-foreground">Error</div>
1044
- <div class="text-muted-foreground">Secondary text</div>
1045
- <div class="bg-muted">Subtle background</div>
1046
- <div class="border-border">Standard border</div>
1047
- \`\`\`
1048
-
1049
- ### Available semantic colors:
1050
- \`background\`, \`foreground\`, \`card\`, \`card-foreground\`, \`popover\`, \`popover-foreground\`,
1051
- \`primary\`, \`primary-foreground\`, \`secondary\`, \`secondary-foreground\`, \`muted\`, \`muted-foreground\`,
1052
- \`accent\`, \`accent-foreground\`, \`destructive\`, \`destructive-foreground\`, \`border\`, \`input\`, \`ring\`
1053
-
1054
- ### Need a new color (e.g., success, warning)?
1055
- 1. Add CSS variable to \`src/styles.css\` with light AND dark mode values
1056
- 2. Register in \`@theme inline\` block
1057
- 3. Then use: \`bg-success text-success-foreground\`
1058
-
1059
- ### Typography - use standard scale:
1060
- \`\`\`html
1061
- <!-- ✅ CORRECT -->
1062
- <p class="text-sm">Small</p>
1063
- <p class="text-base">Body</p>
1064
- <h2 class="text-xl font-semibold">Heading</h2>
1065
-
1066
- <!-- ❌ WRONG - arbitrary sizes -->
1067
- <p class="text-[13px]">Don't do this</p>
1068
- \`\`\`
1069
-
1070
- ## Remember
1071
- - Spartan-NG components are pre-installed - just import and use
1072
- - Follow existing patterns in the codebase
1073
- - Use TailwindCSS v4 for styling with SEMANTIC colors only
1074
- - NEVER use raw colors like text-green-600, bg-yellow-100, etc.
1075
- `;
1076
- }
1077
-
1078
661
  function generateClaudeMdRules() {
1079
662
  // Generate a combined CLAUDE.md file for Claude Code
1080
- const themes = RESOURCES.themes || [];
1081
- const components = RESOURCES.components || [];
1082
- const blocks = RESOURCES.blocks || [];
1083
-
1084
- const themesList = themes.map(t => `- \`theme:${t.name}\` - ${t.description}`).join('\n');
1085
- const componentsList = components.map(c => `- \`component:${c.name}\` - ${c.description}`).join('\n');
1086
- const blocksList = blocks.map(b => `- \`block:${b.name}\` - ${b.description}`).join('\n');
1087
-
1088
663
  return `# GRG Kit Project Rules
1089
664
 
1090
665
  This project uses **GRG Kit**, an Angular UI toolkit built on **Spartan-NG UI**.
1091
666
 
1092
- ## Critical: Check GRG Kit First
667
+ ## Critical: Check Design System First
1093
668
 
1094
669
  **BEFORE writing any UI component:**
1095
- 1. Use MCP tool \`mcp2_search_ui_resources\` to search for existing resources
1096
- 2. Check if a component, layout, or block already exists
670
+ 1. Check if a Spartan-NG component exists (button, card, dialog, form-field, table, etc.)
671
+ 2. Check if a block exists for page layouts (auth, shell, settings)
1097
672
  3. Only write custom code if no suitable resource exists
1098
673
 
1099
674
  ## Architecture
@@ -1169,34 +744,29 @@ import { lucideCheck } from '@ng-icons/lucide';
1169
744
  })
1170
745
  \`\`\`
1171
746
 
1172
- ## MCP Server Tools
747
+ ## Available Blocks
1173
748
 
1174
- Use the \`grg-kit\` MCP server for themes, blocks, and GRG Kit components:
749
+ Pre-built page layouts available via \`grg add block <name>\`:
750
+ - **auth** - Login, register, forgot-password pages
751
+ - **shell** - Sidebar, topnav, collapsible layouts
752
+ - **settings** - Profile, security, notifications pages
1175
753
 
1176
- - \`mcp2_search_ui_resources({ query: "auth" })\` - Search resources
1177
- - \`mcp2_suggest_resources({ requirement: "login page" })\` - Get suggestions
1178
- - \`mcp2_install_resource({ resource: "auth", files: ["login"] })\` - Get install command (returns command to run)
1179
- - \`mcp2_list_available_resources({ category: "blocks" })\` - List all
1180
-
1181
- **Note:** \`mcp2_install_resource\` returns a command string. Use \`run_command\` to execute it in the Angular project root.
1182
-
1183
- ## Available Resources
754
+ ## Available Themes
1184
755
 
1185
- ### Themes
1186
- ${themesList}
756
+ Switch themes via \`grg add theme <name>\`:
757
+ - grg-theme, claude, amber-minimal, clean-slate, modern-minimal
758
+ - Medical themes: chroma-clinic, bio-lab, pharma-teal, helix-purple
1187
759
 
1188
- ### GRG Kit Components
1189
- ${componentsList}
760
+ ## GRG Kit Components
1190
761
 
1191
- ### Blocks
1192
- ${blocksList}
762
+ - **file-upload** - Drag and drop file upload (\`@grg-kit/ui/file-upload\`)
1193
763
 
1194
764
  ## Decision Tree
1195
765
 
1196
766
  - Need button, card, dialog, form field, table? → Use Spartan-NG (already installed)
1197
- - Need page layout (dashboard, auth, settings)? → Use MCP: \`mcp2_search_ui_resources\`
1198
- - Need theme? → Use MCP: \`mcp2_list_available_resources({ category: "themes" })\`
1199
- - Need file-upload? → Use MCP: \`mcp2_search_ui_resources\`
767
+ - Need page layout (dashboard, auth, settings)? → Run: \`grg add block <name>\`
768
+ - Need theme? → Run: \`grg add theme <name>\`
769
+ - Need file-upload? → Import from \`@grg-kit/ui/file-upload\`
1200
770
 
1201
771
  ## Package Manager
1202
772
 
@@ -29,6 +29,46 @@ const RESOURCES = {
29
29
  "oklch"
30
30
  ]
31
31
  },
32
+ {
33
+ "name": "bio-lab",
34
+ "title": "Bio Lab",
35
+ "description": "Fresh green theme for life sciences",
36
+ "file": "bio-lab.css",
37
+ "path": "templates/ui/themes/bio-lab.css",
38
+ "defaultOutput": "src/themes/bio-lab.css",
39
+ "tags": [
40
+ "medical",
41
+ "green",
42
+ "life-sciences",
43
+ "biotech"
44
+ ],
45
+ "features": [
46
+ "dark-mode",
47
+ "tailwind-v4",
48
+ "spartan-ng",
49
+ "oklch"
50
+ ]
51
+ },
52
+ {
53
+ "name": "chroma-clinic",
54
+ "title": "Chroma Clinic",
55
+ "description": "Professional blue theme with Open Sans",
56
+ "file": "chroma-clinic.css",
57
+ "path": "templates/ui/themes/chroma-clinic.css",
58
+ "defaultOutput": "src/themes/chroma-clinic.css",
59
+ "tags": [
60
+ "medical",
61
+ "professional",
62
+ "blue",
63
+ "healthcare"
64
+ ],
65
+ "features": [
66
+ "dark-mode",
67
+ "tailwind-v4",
68
+ "spartan-ng",
69
+ "oklch"
70
+ ]
71
+ },
32
72
  {
33
73
  "name": "claude",
34
74
  "title": "Claude",
@@ -72,15 +112,15 @@ const RESOURCES = {
72
112
  {
73
113
  "name": "grg-theme",
74
114
  "title": "Grg Theme",
75
- "description": "Default theme with purple/orange accents",
115
+ "description": "Default theme with blue accents",
76
116
  "file": "grg-theme.css",
77
117
  "path": "templates/ui/themes/grg-theme.css",
78
118
  "defaultOutput": "src/themes/grg-theme.css",
79
119
  "tags": [
80
120
  "default",
81
- "purple",
82
- "orange",
83
- "colorful"
121
+ "blue",
122
+ "minimal",
123
+ "professional"
84
124
  ],
85
125
  "features": [
86
126
  "dark-mode",
@@ -90,16 +130,17 @@ const RESOURCES = {
90
130
  ]
91
131
  },
92
132
  {
93
- "name": "mocks",
94
- "title": "Mocks",
95
- "description": "Theme for mockups and prototypes",
96
- "file": "mocks.css",
97
- "path": "templates/ui/themes/mocks.css",
98
- "defaultOutput": "src/themes/mocks.css",
133
+ "name": "helix-purple",
134
+ "title": "Helix Purple",
135
+ "description": "DNA-inspired purple for genomics",
136
+ "file": "helix-purple.css",
137
+ "path": "templates/ui/themes/helix-purple.css",
138
+ "defaultOutput": "src/themes/helix-purple.css",
99
139
  "tags": [
100
- "mockup",
101
- "prototype",
102
- "design"
140
+ "medical",
141
+ "purple",
142
+ "genomics",
143
+ "biotech"
103
144
  ],
104
145
  "features": [
105
146
  "dark-mode",
@@ -127,6 +168,26 @@ const RESOURCES = {
127
168
  "spartan-ng",
128
169
  "oklch"
129
170
  ]
171
+ },
172
+ {
173
+ "name": "pharma-teal",
174
+ "title": "Pharma Teal",
175
+ "description": "Calming teal for pharmaceutical applications",
176
+ "file": "pharma-teal.css",
177
+ "path": "templates/ui/themes/pharma-teal.css",
178
+ "defaultOutput": "src/themes/pharma-teal.css",
179
+ "tags": [
180
+ "medical",
181
+ "teal",
182
+ "pharmaceutical",
183
+ "healthcare"
184
+ ],
185
+ "features": [
186
+ "dark-mode",
187
+ "tailwind-v4",
188
+ "spartan-ng",
189
+ "oklch"
190
+ ]
130
191
  }
131
192
  ],
132
193
  "components": [
@@ -165,6 +226,26 @@ const RESOURCES = {
165
226
  "@spartan-ng/helm/button",
166
227
  "@spartan-ng/helm/card",
167
228
  "@spartan-ng/helm/form-field"
229
+ ],
230
+ "files": [
231
+ {
232
+ "id": "forgot-password",
233
+ "file": "forgot-password.component.ts",
234
+ "title": "Forgot Password",
235
+ "description": "Forgot Password"
236
+ },
237
+ {
238
+ "id": "login",
239
+ "file": "login.component.ts",
240
+ "title": "Login",
241
+ "description": "Login"
242
+ },
243
+ {
244
+ "id": "register",
245
+ "file": "register.component.ts",
246
+ "title": "Register",
247
+ "description": "Register"
248
+ }
168
249
  ]
169
250
  },
170
251
  {
@@ -185,6 +266,32 @@ const RESOURCES = {
185
266
  "@spartan-ng/helm/card",
186
267
  "@spartan-ng/helm/form-field",
187
268
  "@spartan-ng/helm/switch"
269
+ ],
270
+ "files": [
271
+ {
272
+ "id": "danger-zone",
273
+ "file": "danger-zone.component.ts",
274
+ "title": "Danger Zone",
275
+ "description": "Danger Zone"
276
+ },
277
+ {
278
+ "id": "notification",
279
+ "file": "notification-settings.component.ts",
280
+ "title": "Notification Settings",
281
+ "description": "Notification Settings"
282
+ },
283
+ {
284
+ "id": "profile",
285
+ "file": "profile-settings.component.ts",
286
+ "title": "Profile Settings",
287
+ "description": "Profile Settings"
288
+ },
289
+ {
290
+ "id": "security",
291
+ "file": "security-settings.component.ts",
292
+ "title": "Security Settings",
293
+ "description": "Security Settings"
294
+ }
188
295
  ]
189
296
  },
190
297
  {
@@ -207,6 +314,44 @@ const RESOURCES = {
207
314
  "@spartan-ng/helm/button",
208
315
  "@spartan-ng/helm/icon",
209
316
  "@spartan-ng/helm/dropdown-menu"
317
+ ],
318
+ "files": [
319
+ {
320
+ "id": "collapsible-footer",
321
+ "file": "collapsible-shell-footer.component.ts",
322
+ "title": "Collapsible Shell Footer",
323
+ "description": "Collapsible Shell Footer"
324
+ },
325
+ {
326
+ "id": "collapsible",
327
+ "file": "collapsible-shell.component.ts",
328
+ "title": "Collapsible Shell",
329
+ "description": "Collapsible Shell"
330
+ },
331
+ {
332
+ "id": "sidebar-footer",
333
+ "file": "sidebar-shell-footer.component.ts",
334
+ "title": "Sidebar Shell Footer",
335
+ "description": "Sidebar Shell Footer"
336
+ },
337
+ {
338
+ "id": "sidebar",
339
+ "file": "sidebar-shell.component.ts",
340
+ "title": "Sidebar Shell",
341
+ "description": "Sidebar Shell"
342
+ },
343
+ {
344
+ "id": "topnav-footer",
345
+ "file": "topnav-shell-footer.component.ts",
346
+ "title": "Topnav Shell Footer",
347
+ "description": "Topnav Shell Footer"
348
+ },
349
+ {
350
+ "id": "topnav",
351
+ "file": "topnav-shell.component.ts",
352
+ "title": "Topnav Shell",
353
+ "description": "Topnav Shell"
354
+ }
210
355
  ]
211
356
  }
212
357
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grg-kit-cli",
3
- "version": "0.6.11",
3
+ "version": "0.6.13",
4
4
  "description": "CLI tool for pulling GRG Kit resources into your Angular project",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -156,7 +156,7 @@ function scanBlockFiles(blockDir, blockName) {
156
156
  .sort((a, b) => a.file.localeCompare(b.file));
157
157
  }
158
158
 
159
- function generateBlocks(includeFiles = false) {
159
+ function generateBlocks() {
160
160
  const blocksDir = path.join(TEMPLATES_DIR, 'ui/blocks');
161
161
  const dirs = scanDirectory(blocksDir);
162
162
 
@@ -166,22 +166,16 @@ function generateBlocks(includeFiles = false) {
166
166
  const blockDir = path.join(blocksDir, dir.name);
167
167
  const metadata = readMeta(blockDir) || defaultBlockMeta(dir.name);
168
168
 
169
- const block = {
169
+ return {
170
170
  name: dir.name,
171
171
  title: dir.name.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' '),
172
172
  description: metadata.description,
173
173
  path: `templates/ui/blocks/${dir.name}`,
174
174
  defaultOutput: `src/app/blocks/${dir.name}`,
175
175
  tags: metadata.tags || [],
176
- dependencies: metadata.dependencies || []
176
+ dependencies: metadata.dependencies || [],
177
+ files: scanBlockFiles(blockDir, dir.name) // Always include files
177
178
  };
178
-
179
- // Include file-level details for catalog
180
- if (includeFiles) {
181
- block.files = scanBlockFiles(blockDir, dir.name);
182
- }
183
-
184
- return block;
185
179
  });
186
180
  }
187
181
 
@@ -1 +0,0 @@
1
- {"timestamp":1765262345803,"data":{"themes":[{"name":"amber-minimal","title":"Amber Minimal","description":"Warm amber accents","file":"amber-minimal.css","tags":["minimal","warm","amber","orange"],"features":["dark-mode","tailwind-v4","spartan-ng","oklch"],"path":"templates/ui/themes/amber-minimal.css","defaultOutput":"src/themes/amber-minimal.css"},{"name":"claude","title":"Claude","description":"Claude-inspired warm tones","file":"claude.css","tags":["warm","orange","brown","claude"],"features":["dark-mode","tailwind-v4","spartan-ng","oklch"],"path":"templates/ui/themes/claude.css","defaultOutput":"src/themes/claude.css"},{"name":"clean-slate","title":"Clean Slate","description":"Minimal grayscale palette","file":"clean-slate.css","tags":["minimal","grayscale","neutral","clean"],"features":["dark-mode","tailwind-v4","spartan-ng","oklch"],"path":"templates/ui/themes/clean-slate.css","defaultOutput":"src/themes/clean-slate.css"},{"name":"grg-theme","title":"Grg Theme","description":"Default theme with purple/orange accents","file":"grg-theme.css","tags":["default","purple","orange","colorful"],"features":["dark-mode","tailwind-v4","spartan-ng","oklch"],"path":"templates/ui/themes/grg-theme.css","defaultOutput":"src/themes/grg-theme.css"},{"name":"mocks","title":"Mocks","description":"Theme for mockups and prototypes","file":"mocks.css","tags":["mockup","prototype","design"],"features":["dark-mode","tailwind-v4","spartan-ng","oklch"],"path":"templates/ui/themes/mocks.css","defaultOutput":"src/themes/mocks.css"},{"name":"modern-minimal","title":"Modern Minimal","description":"Contemporary minimal design","file":"modern-minimal.css","tags":["minimal","modern","contemporary","clean"],"features":["dark-mode","tailwind-v4","spartan-ng","oklch"],"path":"templates/ui/themes/modern-minimal.css","defaultOutput":"src/themes/modern-minimal.css"}],"components":[{"name":"file-upload","title":"File Upload","description":"Drag and drop file upload component","tags":["file","upload","form","drag-drop"],"dependencies":["@spartan-ng/helm/button"],"path":"templates/ui/components/file-upload","defaultOutput":"src/app/components/file-upload"}],"blocks":[{"name":"auth","title":"Auth","description":"Authentication pages (login, signup, forgot password)","tags":["auth","login","signup","authentication","form"],"dependencies":["@spartan-ng/helm/button","@spartan-ng/helm/card","@spartan-ng/helm/form-field"],"files":[{"id":"forgot-password","file":"forgot-password.component.ts","title":"Forgot Password","description":"Forgot Password"},{"id":"login","file":"login.component.ts","title":"Login","description":"Login"},{"id":"register","file":"register.component.ts","title":"Register","description":"Register"}],"path":"templates/ui/blocks/auth","defaultOutput":"src/app/blocks/auth"},{"name":"settings","title":"Settings","description":"Settings pages: profile, notifications, security, danger zone","tags":["settings","preferences","account","profile","security"],"dependencies":["@spartan-ng/helm/button","@spartan-ng/helm/card","@spartan-ng/helm/form-field","@spartan-ng/helm/switch"],"files":[{"id":"danger-zone","file":"danger-zone.component.ts","title":"Danger Zone","description":"Danger Zone"},{"id":"notification","file":"notification-settings.component.ts","title":"Notification Settings","description":"Notification Settings"},{"id":"profile","file":"profile-settings.component.ts","title":"Profile Settings","description":"Profile Settings"},{"id":"security","file":"security-settings.component.ts","title":"Security Settings","description":"Security Settings"}],"path":"templates/ui/blocks/settings","defaultOutput":"src/app/blocks/settings"},{"name":"shell","title":"Shell","description":"Application shell layouts: sidebar, topnav, collapsible - each with optional footer variant","tags":["shell","layout","sidebar","header","footer","navigation","topnav","collapsible"],"dependencies":["@spartan-ng/helm/button","@spartan-ng/helm/icon","@spartan-ng/helm/dropdown-menu"],"files":[{"id":"collapsible-footer","file":"collapsible-shell-footer.component.ts","title":"Collapsible Shell Footer","description":"Collapsible Shell Footer"},{"id":"collapsible","file":"collapsible-shell.component.ts","title":"Collapsible Shell","description":"Collapsible Shell"},{"id":"sidebar-footer","file":"sidebar-shell-footer.component.ts","title":"Sidebar Shell Footer","description":"Sidebar Shell Footer"},{"id":"sidebar","file":"sidebar-shell.component.ts","title":"Sidebar Shell","description":"Sidebar Shell"},{"id":"topnav-footer","file":"topnav-shell-footer.component.ts","title":"Topnav Shell Footer","description":"Topnav Shell Footer"},{"id":"topnav","file":"topnav-shell.component.ts","title":"Topnav Shell","description":"Topnav Shell"}],"path":"templates/ui/blocks/shell","defaultOutput":"src/app/blocks/shell"}]}}
@@ -1,199 +0,0 @@
1
- /**
2
- * Dynamic catalog fetcher with caching
3
- * Fetches catalog.json from GitHub with fallback to static resources
4
- */
5
-
6
- const fs = require('fs');
7
- const path = require('path');
8
- const https = require('https');
9
-
10
- const REPO = 'Genesis-Research/grg-kit';
11
- const CATALOG_URL = `https://raw.githubusercontent.com/${REPO}/main/templates/catalog.json`;
12
- const CACHE_FILE = path.join(__dirname, '.catalog-cache.json');
13
- const CACHE_TTL_MS = 15 * 60 * 1000; // 15 minutes
14
-
15
- // In-memory cache for current session
16
- let memoryCache = null;
17
- let memoryCacheTime = 0;
18
-
19
- /**
20
- * Fetch JSON from URL
21
- */
22
- function fetchJson(url) {
23
- return new Promise((resolve, reject) => {
24
- https.get(url, (res) => {
25
- if (res.statusCode !== 200) {
26
- reject(new Error(`HTTP ${res.statusCode}`));
27
- return;
28
- }
29
-
30
- let data = '';
31
- res.on('data', chunk => data += chunk);
32
- res.on('end', () => {
33
- try {
34
- resolve(JSON.parse(data));
35
- } catch (e) {
36
- reject(new Error('Invalid JSON'));
37
- }
38
- });
39
- }).on('error', reject);
40
- });
41
- }
42
-
43
- /**
44
- * Read file cache
45
- */
46
- function readFileCache() {
47
- try {
48
- if (fs.existsSync(CACHE_FILE)) {
49
- const cached = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8'));
50
- if (Date.now() - cached.timestamp < CACHE_TTL_MS) {
51
- return cached.data;
52
- }
53
- }
54
- } catch (e) {
55
- // Cache read failed, ignore
56
- }
57
- return null;
58
- }
59
-
60
- /**
61
- * Write file cache
62
- */
63
- function writeFileCache(data) {
64
- try {
65
- fs.writeFileSync(CACHE_FILE, JSON.stringify({
66
- timestamp: Date.now(),
67
- data
68
- }), 'utf-8');
69
- } catch (e) {
70
- // Cache write failed, ignore
71
- }
72
- }
73
-
74
- /**
75
- * Get static fallback resources
76
- */
77
- function getStaticFallback() {
78
- try {
79
- const { RESOURCES } = require('./resources');
80
- return RESOURCES;
81
- } catch (e) {
82
- return { themes: [], components: [], blocks: [] };
83
- }
84
- }
85
-
86
- /**
87
- * Fetch catalog with caching
88
- * Priority: memory cache -> file cache -> network -> static fallback
89
- */
90
- async function fetchCatalog(options = {}) {
91
- const { forceRefresh = false, silent = false } = options;
92
-
93
- // Check memory cache first (fastest)
94
- if (!forceRefresh && memoryCache && (Date.now() - memoryCacheTime < CACHE_TTL_MS)) {
95
- return memoryCache;
96
- }
97
-
98
- // Check file cache
99
- if (!forceRefresh) {
100
- const fileCached = readFileCache();
101
- if (fileCached) {
102
- memoryCache = fileCached;
103
- memoryCacheTime = Date.now();
104
- return fileCached;
105
- }
106
- }
107
-
108
- // Fetch from network
109
- try {
110
- const catalog = await fetchJson(CATALOG_URL);
111
-
112
- // Transform to match expected format
113
- const resources = {
114
- themes: catalog.themes.map(t => ({
115
- ...t,
116
- path: `templates/ui/themes/${t.file}`,
117
- defaultOutput: `src/themes/${t.file}`
118
- })),
119
- components: catalog.components.map(c => ({
120
- ...c,
121
- path: `templates/ui/components/${c.name}`,
122
- defaultOutput: `src/app/components/${c.name}`
123
- })),
124
- blocks: catalog.blocks.map(b => ({
125
- ...b,
126
- path: `templates/ui/blocks/${b.name}`,
127
- defaultOutput: `src/app/blocks/${b.name}`,
128
- files: b.files || []
129
- }))
130
- };
131
-
132
- // Update caches
133
- memoryCache = resources;
134
- memoryCacheTime = Date.now();
135
- writeFileCache(resources);
136
-
137
- return resources;
138
- } catch (error) {
139
- if (!silent) {
140
- console.warn(`Warning: Could not fetch catalog (${error.message}), using cached/static resources`);
141
- }
142
-
143
- // Try file cache even if expired
144
- try {
145
- if (fs.existsSync(CACHE_FILE)) {
146
- const cached = JSON.parse(fs.readFileSync(CACHE_FILE, 'utf-8'));
147
- return cached.data;
148
- }
149
- } catch (e) {
150
- // Ignore
151
- }
152
-
153
- // Final fallback to static resources
154
- return getStaticFallback();
155
- }
156
- }
157
-
158
- /**
159
- * Get resources synchronously (uses cache or static fallback)
160
- */
161
- function getResourcesSync() {
162
- // Check memory cache
163
- if (memoryCache && (Date.now() - memoryCacheTime < CACHE_TTL_MS)) {
164
- return memoryCache;
165
- }
166
-
167
- // Check file cache
168
- const fileCached = readFileCache();
169
- if (fileCached) {
170
- memoryCache = fileCached;
171
- memoryCacheTime = Date.now();
172
- return fileCached;
173
- }
174
-
175
- // Static fallback
176
- return getStaticFallback();
177
- }
178
-
179
- /**
180
- * Clear all caches
181
- */
182
- function clearCache() {
183
- memoryCache = null;
184
- memoryCacheTime = 0;
185
- try {
186
- if (fs.existsSync(CACHE_FILE)) {
187
- fs.unlinkSync(CACHE_FILE);
188
- }
189
- } catch (e) {
190
- // Ignore
191
- }
192
- }
193
-
194
- module.exports = {
195
- fetchCatalog,
196
- getResourcesSync,
197
- clearCache,
198
- REPO
199
- };