grg-kit-cli 0.6.9 → 0.6.11

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
@@ -21,6 +21,10 @@ grg init
21
21
  # Or with a specific theme
22
22
  grg init --theme claude
23
23
 
24
+ # Add or switch theme
25
+ grg add theme claude
26
+ grg add theme modern-minimal
27
+
24
28
  # Add blocks (all files)
25
29
  grg add block auth
26
30
  grg add block shell
@@ -32,10 +36,14 @@ grg add block shell sidebar
32
36
  # Add all blocks
33
37
  grg add block --all
34
38
 
39
+ # Add components
40
+ grg add component file-upload
41
+
35
42
  # List available resources
36
43
  grg list
37
44
  grg list blocks
38
45
  grg list themes
46
+ grg list components
39
47
  ```
40
48
 
41
49
  ## Commands
@@ -77,6 +85,25 @@ Examples:
77
85
  - `amber-minimal` - Warm amber accents
78
86
  - `mocks` - Theme for mockups and prototypes
79
87
 
88
+ ### `grg add theme`
89
+
90
+ Add or switch theme in an existing project.
91
+
92
+ ```bash
93
+ grg add theme <themeName>
94
+
95
+ Options:
96
+ -o, --output <path> Custom themes directory (default: "src/themes")
97
+
98
+ Examples:
99
+ grg add theme claude # Download and set claude theme
100
+ grg add theme modern-minimal # Download and set modern-minimal
101
+ ```
102
+
103
+ **What it does:**
104
+ - Downloads the theme CSS file to `src/themes/`
105
+ - Updates `src/styles.css` to import the new theme (replaces existing theme import)
106
+
80
107
  ### `grg add block`
81
108
 
82
109
  Add blocks or specific block files to your project.
@@ -97,6 +124,22 @@ Examples:
97
124
  grg add block --all # All blocks
98
125
  ```
99
126
 
127
+ ### `grg add component`
128
+
129
+ Add GRG Kit components to your project.
130
+
131
+ ```bash
132
+ grg add component <componentName>
133
+
134
+ Options:
135
+ --all Add all components
136
+ -o, --output <path> Custom output directory
137
+
138
+ Examples:
139
+ grg add component file-upload # Download file-upload component
140
+ grg add component --all # All components
141
+ ```
142
+
100
143
  **Available blocks and files:**
101
144
 
102
145
  | Block | Files |
@@ -195,6 +238,10 @@ cd my-app
195
238
  grg init # Default theme
196
239
  grg init --theme claude # Custom theme
197
240
 
241
+ # Add/switch theme
242
+ grg add theme claude # Switch to claude theme
243
+ grg add theme modern-minimal # Switch to modern-minimal
244
+
198
245
  # Add blocks (all files)
199
246
  grg add block auth # All auth files
200
247
  grg add block shell # All shell layouts
@@ -205,10 +252,14 @@ grg add block auth login # Just login
205
252
  grg add block shell sidebar # Just sidebar shell
206
253
  grg add block --all # All blocks
207
254
 
255
+ # Add components
256
+ grg add component file-upload # File upload component
257
+
208
258
  # List resources
209
259
  grg list # Overview
210
260
  grg list blocks # Available blocks
211
261
  grg list themes # Available themes
262
+ grg list components # Available components
212
263
 
213
264
  # AI setup
214
265
  grg llm-setup # Generate AI rules
package/bin/grg.js CHANGED
@@ -41,6 +41,15 @@ addCommand
41
41
  await addComponent(componentName, options);
42
42
  });
43
43
 
44
+ addCommand
45
+ .command('theme [themeName]')
46
+ .description('Add or switch theme (e.g., grg add theme claude)')
47
+ .option('-o, --output <path>', 'Custom themes directory', 'src/themes')
48
+ .action(async (themeName, options) => {
49
+ const { addTheme } = require('../commands/add');
50
+ await addTheme(themeName, options);
51
+ });
52
+
44
53
  // List command
45
54
  program
46
55
  .command('list [category]')
package/commands/add.js CHANGED
@@ -292,4 +292,157 @@ function showComponentUsage(RESOURCES) {
292
292
  console.log(chalk.gray('\nRun'), chalk.cyan('grg list components'), chalk.gray('for more details'));
293
293
  }
294
294
 
295
- module.exports = { add, addComponent };
295
+ /**
296
+ * Add theme command - downloads and sets up a theme
297
+ * Format: grg add theme <themeName>
298
+ * Examples:
299
+ * grg add theme claude # Download and set claude theme
300
+ * grg add theme modern-minimal # Download and set modern-minimal theme
301
+ */
302
+ async function addTheme(themeName, options) {
303
+ // Fetch catalog dynamically (with caching)
304
+ const spinner = ora('Fetching catalog...').start();
305
+ const RESOURCES = await fetchCatalog({ silent: true });
306
+ spinner.stop();
307
+
308
+ // Validate theme name
309
+ if (!themeName) {
310
+ showThemeUsage(RESOURCES);
311
+ process.exit(1);
312
+ }
313
+
314
+ const theme = RESOURCES.themes.find(t => t.name === themeName);
315
+ if (!theme) {
316
+ console.error(chalk.red(`\nError: Theme "${themeName}" not found`));
317
+ console.log(chalk.yellow('\nAvailable themes:'));
318
+ RESOURCES.themes.forEach(t => {
319
+ console.log(chalk.cyan(` ${t.name}`), chalk.gray(`- ${t.description}`));
320
+ });
321
+ process.exit(1);
322
+ }
323
+
324
+ await downloadTheme(theme, options.output);
325
+ console.log(chalk.bold.green('✨ Done!'));
326
+ }
327
+
328
+ /**
329
+ * Download a theme file
330
+ */
331
+ async function downloadTheme(theme, customOutput) {
332
+ const downloadSpinner = ora();
333
+ const themesDir = customOutput || 'src/themes';
334
+ const outputPath = path.join(themesDir, theme.file);
335
+
336
+ // Ensure themes directory exists
337
+ await fs.mkdir(themesDir, { recursive: true });
338
+
339
+ downloadSpinner.start(`Downloading ${theme.title} theme...`);
340
+
341
+ try {
342
+ const themeUrl = `https://raw.githubusercontent.com/${REPO}/main/${theme.path}`;
343
+ await downloadFileToPath(themeUrl, outputPath);
344
+
345
+ downloadSpinner.succeed(chalk.green(`āœ“ ${theme.title} theme downloaded`));
346
+ console.log(chalk.gray(` Location: ${outputPath}`));
347
+
348
+ } catch (error) {
349
+ downloadSpinner.fail(chalk.red(`Failed to download ${theme.title}`));
350
+ console.error(chalk.red(error.message));
351
+ process.exit(1);
352
+ }
353
+
354
+ // Update styles.css
355
+ downloadSpinner.start('Updating src/styles.css...');
356
+ try {
357
+ const stylesPath = 'src/styles.css';
358
+ let stylesContent = '';
359
+
360
+ try {
361
+ stylesContent = await fs.readFile(stylesPath, 'utf-8');
362
+ } catch (error) {
363
+ // File doesn't exist, will create it
364
+ stylesContent = '';
365
+ }
366
+
367
+ const themeImport = `@import './themes/${theme.file}';`;
368
+
369
+ // Check if any theme is already imported
370
+ const themeImportRegex = /@import\s+['"]\.\/themes\/[^'"]+['"];?\n?/g;
371
+ const existingThemeImports = stylesContent.match(themeImportRegex);
372
+
373
+ if (existingThemeImports && existingThemeImports.length > 0) {
374
+ // Remove ALL existing theme imports first
375
+ stylesContent = stylesContent.replace(themeImportRegex, '');
376
+ // Clean up any resulting double blank lines
377
+ stylesContent = stylesContent.replace(/\n{3,}/g, '\n\n');
378
+ // Add the new theme import after the spartan preset import
379
+ const spartanImport = '@import "@spartan-ng/brain/hlm-tailwind-preset.css";';
380
+ if (stylesContent.includes(spartanImport)) {
381
+ stylesContent = stylesContent.replace(spartanImport, `${spartanImport}\n\n${themeImport}`);
382
+ } else {
383
+ // Fallback: add at the beginning
384
+ stylesContent = themeImport + '\n' + stylesContent;
385
+ }
386
+ await fs.writeFile(stylesPath, stylesContent);
387
+ downloadSpinner.succeed(chalk.green(`āœ“ Updated theme import in src/styles.css`));
388
+ } else if (!stylesContent.includes(themeImport)) {
389
+ // No theme import exists, add required imports
390
+ const requiredImports = [
391
+ '@import "@angular/cdk/overlay-prebuilt.css";',
392
+ '@import "tailwindcss";',
393
+ '@import "@spartan-ng/brain/hlm-tailwind-preset.css";',
394
+ '',
395
+ themeImport
396
+ ];
397
+
398
+ const newContent = requiredImports.join('\n') + '\n';
399
+ await fs.writeFile(stylesPath, newContent);
400
+ downloadSpinner.succeed(chalk.green('āœ“ Created src/styles.css with theme import'));
401
+ } else {
402
+ downloadSpinner.succeed(chalk.green('āœ“ Theme already imported in src/styles.css'));
403
+ }
404
+ } catch (error) {
405
+ downloadSpinner.warn(chalk.yellow('Could not update src/styles.css automatically'));
406
+ console.log(chalk.gray('\nPlease add the following to your src/styles.css:'));
407
+ console.log(chalk.cyan(` @import './themes/${theme.file}';`));
408
+ }
409
+
410
+ console.log();
411
+ }
412
+
413
+ /**
414
+ * Download file to a specific path
415
+ */
416
+ function downloadFileToPath(url, outputPath) {
417
+ return new Promise((resolve, reject) => {
418
+ https.get(url, (res) => {
419
+ if (res.statusCode === 301 || res.statusCode === 302) {
420
+ // Follow redirect
421
+ https.get(res.headers.location, (res2) => {
422
+ handleResponse(res2, outputPath, resolve, reject);
423
+ }).on('error', reject);
424
+ } else {
425
+ handleResponse(res, outputPath, resolve, reject);
426
+ }
427
+ }).on('error', reject);
428
+ });
429
+ }
430
+
431
+ /**
432
+ * Show theme usage help
433
+ */
434
+ function showThemeUsage(RESOURCES) {
435
+ console.log(chalk.yellow('\nUsage: grg add theme <themeName>\n'));
436
+ console.log(chalk.bold('Examples:'));
437
+ console.log(chalk.cyan(' grg add theme claude'), chalk.gray(' # Download and set claude theme'));
438
+ console.log(chalk.cyan(' grg add theme modern-minimal'), chalk.gray(' # Download and set modern-minimal'));
439
+
440
+ console.log(chalk.bold('\nAvailable themes:'));
441
+ RESOURCES.themes.forEach(t => {
442
+ console.log(chalk.cyan(` ${t.name}`), chalk.gray(`- ${t.description}`));
443
+ });
444
+
445
+ console.log(chalk.gray('\nRun'), chalk.cyan('grg list themes'), chalk.gray('for more details'));
446
+ }
447
+
448
+ module.exports = { add, addComponent, addTheme };
package/commands/init.js CHANGED
@@ -2,6 +2,7 @@ const fs = require('fs').promises;
2
2
  const path = require('path');
3
3
  const { exec } = require('child_process');
4
4
  const { promisify } = require('util');
5
+ const https = require('https');
5
6
  const degit = require('degit');
6
7
  const chalk = require('chalk');
7
8
  const ora = require('ora');
@@ -9,6 +10,28 @@ const { RESOURCES, REPO } = require('../config/resources');
9
10
 
10
11
  const execAsync = promisify(exec);
11
12
 
13
+ /**
14
+ * Download a file from a URL
15
+ */
16
+ function downloadFile(url) {
17
+ return new Promise((resolve, reject) => {
18
+ https.get(url, (res) => {
19
+ if (res.statusCode === 301 || res.statusCode === 302) {
20
+ // Follow redirect
21
+ return downloadFile(res.headers.location).then(resolve).catch(reject);
22
+ }
23
+ if (res.statusCode !== 200) {
24
+ reject(new Error(`HTTP ${res.statusCode}`));
25
+ return;
26
+ }
27
+
28
+ let data = '';
29
+ res.on('data', chunk => data += chunk);
30
+ res.on('end', () => resolve(data));
31
+ }).on('error', reject);
32
+ });
33
+ }
34
+
12
35
  /**
13
36
  * Init command - initializes GRG Kit in an existing Angular project
14
37
  * Installs Tailwind CSS v4, runs spartan-ng ui, downloads theme
@@ -45,10 +68,21 @@ async function init(options) {
45
68
  console.log(chalk.bold.cyan('\nšŸš€ Initializing GRG Kit\n'));
46
69
  console.log(chalk.gray(` Theme: ${theme.title}\n`));
47
70
 
48
- // Step 1: Install Tailwind CSS v4
71
+ // Step 1: Fresh install of packages
72
+ spinner.start('Cleaning node_modules and reinstalling packages...');
73
+ try {
74
+ await execAsync('rm -rf node_modules && npm install');
75
+ spinner.succeed(chalk.green('āœ“ Fresh package install complete'));
76
+ } catch (error) {
77
+ spinner.fail(chalk.red('Failed to reinstall packages'));
78
+ console.error(chalk.gray(error.message));
79
+ process.exit(1);
80
+ }
81
+
82
+ // Step 2: Install Tailwind CSS v4
49
83
  spinner.start('Installing Tailwind CSS v4...');
50
84
  try {
51
- await execAsync('npm install tailwindcss @tailwindcss/postcss postcss');
85
+ await execAsync('npm install tailwindcss @tailwindcss/postcss postcss --force');
52
86
  spinner.succeed(chalk.green('āœ“ Tailwind CSS v4 installed'));
53
87
  } catch (error) {
54
88
  spinner.fail(chalk.red('Failed to install Tailwind CSS v4'));
@@ -227,15 +261,12 @@ async function init(options) {
227
261
  spinner.warn(chalk.yellow('Themes directory may already exist'));
228
262
  }
229
263
 
230
- // Step 10: Download theme
264
+ // Step 10: Download theme file
231
265
  spinner.start(`Downloading ${theme.title} theme...`);
232
266
  try {
233
- const emitter = degit(`${REPO}/${theme.path}`, {
234
- cache: false,
235
- force: true,
236
- verbose: false,
237
- });
238
- await emitter.clone(theme.defaultOutput);
267
+ const themeUrl = `https://raw.githubusercontent.com/${REPO}/main/${theme.path}`;
268
+ const themeContent = await downloadFile(themeUrl);
269
+ await fs.writeFile(theme.defaultOutput, themeContent);
239
270
  spinner.succeed(chalk.green(`āœ“ Downloaded ${theme.title} theme`));
240
271
  } catch (error) {
241
272
  spinner.fail(chalk.red('Failed to download theme'));
package/commands/list.js CHANGED
@@ -25,7 +25,7 @@ async function list(category) {
25
25
  console.log(chalk.gray(' Run: grg list components\n'));
26
26
 
27
27
  console.log(chalk.bold('Themes') + chalk.gray(` (${RESOURCES.themes.length} available)`));
28
- console.log(chalk.gray(' Set with: grg init --theme <name>'));
28
+ console.log(chalk.gray(' Add with: grg add theme <name>'));
29
29
  console.log(chalk.gray(' Run: grg list themes\n'));
30
30
 
31
31
  return;
@@ -67,11 +67,11 @@ async function list(category) {
67
67
 
68
68
  case 'themes':
69
69
  console.log(chalk.bold.cyan('\nšŸŽØ Available Themes\n'));
70
- console.log(chalk.gray(' Use with: grg init --theme <name>\n'));
70
+ console.log(chalk.gray(' Use with: grg add theme <name>\n'));
71
71
  RESOURCES.themes.forEach(theme => {
72
72
  console.log(chalk.bold(` ${theme.name}`));
73
73
  console.log(chalk.gray(` ${theme.description}`));
74
- console.log(chalk.yellow(` grg init --theme ${theme.name}`));
74
+ console.log(chalk.yellow(` grg add theme ${theme.name}`));
75
75
  if (theme.tags && theme.tags.length > 0) {
76
76
  console.log(chalk.gray(` Tags: ${theme.tags.join(', ')}`));
77
77
  }
@@ -861,10 +861,11 @@ AI Workflow:
861
861
  1. mcp2_list_available_resources({ category: "themes" })
862
862
  → Show: claude, amber-minimal, etc.
863
863
 
864
- 2. Themes are set via: grg init --theme <name>
865
- → Run: grg init --theme claude
864
+ 2. mcp2_install_resource({ resource: "claude" })
865
+ → Returns command: "grg add theme claude"
866
866
 
867
- 3. Update src/styles.css import if needed
867
+ 3. Run the command via run_command tool
868
+ → Theme is downloaded and styles.css is updated automatically
868
869
  \`\`\`
869
870
 
870
871
  ### Example 4: User Wants a Form Component
@@ -0,0 +1 @@
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"}]}}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "grg-kit-cli",
3
- "version": "0.6.9",
3
+ "version": "0.6.11",
4
4
  "description": "CLI tool for pulling GRG Kit resources into your Angular project",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -314,11 +314,21 @@ module.exports = { RESOURCES, REPO };
314
314
  description: 'Initialize GRG Kit in current Angular project',
315
315
  themeFlag: '--theme'
316
316
  },
317
+ addTheme: {
318
+ usage: 'grg add theme <themeName>',
319
+ description: 'Add or switch theme in existing project',
320
+ validThemes: catalogThemes.map(t => t.name)
321
+ },
317
322
  addBlock: {
318
323
  usage: 'grg add block <blockName> [fileIds...]',
319
324
  description: 'Add blocks to your project',
320
325
  validBlocks: catalogBlocks.map(b => b.name)
321
326
  },
327
+ addComponent: {
328
+ usage: 'grg add component <componentName>',
329
+ description: 'Add GRG components to your project',
330
+ validComponents: catalogComponents.map(c => c.name)
331
+ },
322
332
  list: {
323
333
  usage: 'grg list [category]',
324
334
  description: 'List available resources'