@nqlib/nqui 0.4.1 → 0.4.2

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 (83) hide show
  1. package/INSTALLATION.md +215 -0
  2. package/README.md +2 -1
  3. package/dist/command-palette-BuYcxPCc.cjs +5 -0
  4. package/dist/command-palette-dEJ9aEk4.js +694 -0
  5. package/dist/command.cjs.js +1 -1
  6. package/dist/command.es.js +1 -1
  7. package/dist/components/custom/enhanced-badge.d.ts +1 -1
  8. package/dist/components/custom/enhanced-button.d.ts +6 -1
  9. package/dist/components/custom/enhanced-button.d.ts.map +1 -1
  10. package/dist/components/custom/enhanced-checkbox.d.ts +11 -0
  11. package/dist/components/custom/enhanced-checkbox.d.ts.map +1 -1
  12. package/dist/components/custom/enhanced-radio-group.d.ts +13 -4
  13. package/dist/components/custom/enhanced-radio-group.d.ts.map +1 -1
  14. package/dist/components/custom/enhanced-sonner.d.ts +5 -6
  15. package/dist/components/custom/enhanced-sonner.d.ts.map +1 -1
  16. package/dist/components/custom/enhanced-tabs.d.ts.map +1 -1
  17. package/dist/components/error-boundary.d.ts +20 -0
  18. package/dist/components/error-boundary.d.ts.map +1 -0
  19. package/dist/components/index.d.ts +102 -0
  20. package/dist/components/index.d.ts.map +1 -0
  21. package/dist/components/ui/badge.d.ts +1 -1
  22. package/dist/components/ui/button.d.ts +1 -1
  23. package/dist/components/ui/checkbox.d.ts +4 -1
  24. package/dist/components/ui/checkbox.d.ts.map +1 -1
  25. package/dist/components/ui/input-group.d.ts +1 -1
  26. package/dist/components/ui/input-group.d.ts.map +1 -1
  27. package/dist/components/ui/radio-group.d.ts +3 -1
  28. package/dist/components/ui/radio-group.d.ts.map +1 -1
  29. package/dist/components/ui/sidebar.d.ts.map +1 -1
  30. package/dist/debug-panel-AjzBdMMz.js +9198 -0
  31. package/dist/debug-panel-NaOmD68t.cjs +171 -0
  32. package/dist/debug.cjs.js +1 -0
  33. package/dist/debug.es.js +7 -0
  34. package/dist/drawer-Cqq0Ozb2.cjs +1 -0
  35. package/dist/{drawer-CU4lkcz7.js → drawer-pUXPg3lF.js} +2 -2
  36. package/dist/drawer.cjs.js +1 -1
  37. package/dist/drawer.es.js +1 -1
  38. package/dist/entries/debug.d.ts +14 -0
  39. package/dist/entries/debug.d.ts.map +1 -0
  40. package/dist/hooks/use-mobile.d.ts.map +1 -1
  41. package/dist/hooks/use-scroll-spy.d.ts.map +1 -1
  42. package/dist/index-CI756mSv.cjs +41 -0
  43. package/dist/index-CgfzsUO6.js +1069 -0
  44. package/dist/index.d.ts +2 -98
  45. package/dist/index.d.ts.map +1 -1
  46. package/dist/lib/index.d.ts +9 -0
  47. package/dist/lib/index.d.ts.map +1 -0
  48. package/dist/nqui.cjs.js +42 -212
  49. package/dist/nqui.es.js +8589 -17780
  50. package/dist/sonner-BtzU00r3.js +248 -0
  51. package/dist/sonner-Dfk26eds.cjs +54 -0
  52. package/dist/sonner.cjs.js +1 -1
  53. package/dist/sonner.es.js +1 -1
  54. package/dist/styles.css +3 -0
  55. package/docs/components/README.md +99 -1
  56. package/docs/components/nqui-card.md +7 -0
  57. package/docs/components/nqui-checkbox.md +23 -1
  58. package/docs/components/nqui-radio-group.md +45 -2
  59. package/docs/components/nqui-tabs.md +11 -1
  60. package/docs/nqui-skills/SKILL.md +95 -0
  61. package/docs/nqui-skills/design-system.md +130 -0
  62. package/docs/nqui-skills/rules/composition.md +183 -0
  63. package/docs/nqui-skills/rules/forms.md +190 -0
  64. package/docs/nqui-skills/rules/icons.md +158 -0
  65. package/docs/nqui-skills/rules/styling.md +192 -0
  66. package/package.json +23 -10
  67. package/scripts/cli.js +1 -0
  68. package/scripts/download-skills.js +91 -0
  69. package/scripts/examples/nextjs-layout-sidebar.tsx +100 -0
  70. package/scripts/examples/nextjs-page-sidebar.tsx +81 -0
  71. package/scripts/examples/vite-app.tsx +135 -0
  72. package/scripts/examples/vite-main.tsx +17 -0
  73. package/scripts/examples.js +92 -6
  74. package/scripts/generate-docs.js +169 -0
  75. package/scripts/init-css.js +34 -14
  76. package/scripts/init-cursor.js +8 -0
  77. package/scripts/post-install.js +41 -9
  78. package/scripts/wizard.js +12 -7
  79. package/dist/command-palette-UHk8zZOg.cjs +0 -45
  80. package/dist/command-palette-d-TrdBsM.js +0 -1778
  81. package/dist/drawer-BcIxWRN8.cjs +0 -1
  82. package/dist/sonner-Co6YpYVs.js +0 -546
  83. package/dist/sonner-DbQhVp8m.cjs +0 -330
@@ -0,0 +1,17 @@
1
+ import { StrictMode } from "react";
2
+ import { createRoot } from "react-dom/client";
3
+ import { ThemeProvider } from "next-themes";
4
+ import { BrowserRouter } from "react-router-dom";
5
+ import "./index.css";
6
+ import App from "./App";
7
+ // Required dependencies: npm install @nqlib/nqui tw-animate-css next-themes hugeicons-core-free-icons hugeicons-react react-router-dom
8
+
9
+ createRoot(document.getElementById("root")!).render(
10
+ <StrictMode>
11
+ <ThemeProvider attribute="class" defaultTheme="system" enableSystem disableTransitionOnChange>
12
+ <BrowserRouter>
13
+ <App />
14
+ </BrowserRouter>
15
+ </ThemeProvider>
16
+ </StrictMode>
17
+ );
@@ -7,7 +7,7 @@ import { askQuestion } from './wizard.js';
7
7
  /**
8
8
  * Copy Next.js example files to user's project
9
9
  */
10
- export async function copyNextJsExamples(framework, { force }) {
10
+ export async function copyNextJsExamples(framework, { force, sidebar }) {
11
11
  if (framework !== 'nextjs') {
12
12
  return;
13
13
  }
@@ -21,9 +21,95 @@ export async function copyNextJsExamples(framework, { force }) {
21
21
  ? join(cwd, 'src', 'app')
22
22
  : join(cwd, 'app');
23
23
 
24
+ // Choose examples based on sidebar flag
25
+ const examples = sidebar
26
+ ? [
27
+ { src: 'nextjs-page-sidebar.tsx', dest: join(appDir, 'page.tsx'), name: 'page.tsx' },
28
+ { src: 'nextjs-layout-sidebar.tsx', dest: join(appDir, 'layout.tsx'), name: 'layout.tsx' },
29
+ ]
30
+ : [
31
+ { src: 'nextjs-page.tsx', dest: join(appDir, 'page.tsx'), name: 'page.tsx' },
32
+ { src: 'nextjs-layout.tsx', dest: join(appDir, 'layout.tsx'), name: 'layout.tsx' },
33
+ ];
34
+
35
+ const copied = [];
36
+ const existing = [];
37
+
38
+ // Check which files exist
39
+ for (const { dest, name } of examples) {
40
+ if (existsSync(dest)) {
41
+ existing.push({ dest, name });
42
+ }
43
+ }
44
+
45
+ // Ask about overwriting if files exist and force is not set
46
+ let shouldOverwrite = force;
47
+ if (existing.length > 0 && !force) {
48
+ const fileList = existing.map(e => e.name).join(' and ');
49
+ const answer = await askQuestion(
50
+ `\n⚠️ ${fileList} already exist(s). Overwrite? (y/n): `
51
+ );
52
+ shouldOverwrite = answer === 'y';
53
+ }
54
+
55
+ for (const { src, dest, name } of examples) {
56
+ const srcPath = join(examplesDir, src);
57
+
58
+ if (!existsSync(srcPath)) {
59
+ console.warn(`⚠️ Example file not found: ${srcPath}`);
60
+ continue;
61
+ }
62
+
63
+ if (existsSync(dest) && !shouldOverwrite) {
64
+ console.log(`⏭️ Skipped: ${dest} (already exists)`);
65
+ continue;
66
+ }
67
+
68
+ const content = readFileSync(srcPath, 'utf8');
69
+ writeFileSync(dest, content, 'utf8');
70
+ copied.push(dest);
71
+ console.log(`✅ ${existsSync(dest) && shouldOverwrite ? 'Overwritten' : 'Created'}: ${dest}`);
72
+ }
73
+
74
+ if (copied.length > 0) {
75
+ const deps = sidebar
76
+ ? 'npm install @nqlib/nqui @hugeicons/react @hugeicons/core-free-icons tw-animate-css next-themes react-router-dom'
77
+ : 'npm install @nqlib/nqui tw-animate-css next-themes';
78
+ console.log(`\n📝 Required dependencies for example files:`);
79
+ console.log(` ${deps}\n`);
80
+ if (sidebar) {
81
+ console.log(` Note: 3-column layout uses Sidebar, TableOfContents; main.tsx includes ThemeProvider + BrowserRouter for Vite.\n`);
82
+ }
83
+ }
84
+
85
+ return copied;
86
+ }
87
+
88
+ /**
89
+ * Copy Vite example files to user's project
90
+ */
91
+ export async function copyViteExamples(framework, { force, sidebar }) {
92
+ if (framework !== 'vite') {
93
+ return;
94
+ }
95
+
96
+ const root = getPackageRoot();
97
+ const examplesDir = join(root, 'scripts', 'examples');
98
+ const cwd = process.cwd();
99
+
100
+ // Determine src directory
101
+ const srcDir = existsSync(join(cwd, 'src')) ? join(cwd, 'src') : join(cwd, 'src');
102
+
103
+ // Ensure src directory exists
104
+ if (!existsSync(srcDir)) {
105
+ console.warn('⚠️ src directory not found');
106
+ return;
107
+ }
108
+
109
+ // Choose examples based on sidebar flag (3-column with TOC when sidebar; same files + main for ThemeProvider)
24
110
  const examples = [
25
- { src: 'nextjs-page.tsx', dest: join(appDir, 'page.tsx'), name: 'page.tsx' },
26
- { src: 'nextjs-layout.tsx', dest: join(appDir, 'layout.tsx'), name: 'layout.tsx' },
111
+ { src: 'vite-app.tsx', dest: join(srcDir, 'App.tsx'), name: 'App.tsx' },
112
+ { src: 'vite-main.tsx', dest: join(srcDir, 'main.tsx'), name: 'main.tsx' },
27
113
  ];
28
114
 
29
115
  const copied = [];
@@ -66,11 +152,11 @@ export async function copyNextJsExamples(framework, { force }) {
66
152
  }
67
153
 
68
154
  if (copied.length > 0) {
155
+ const deps = 'npm install @nqlib/nqui tw-animate-css next-themes @hugeicons/react @hugeicons/core-free-icons react-router-dom';
69
156
  console.log(`\n📝 Required dependencies for example files:`);
70
- console.log(` npm install @nqlib/nqui tw-animate-css next-themes\n`);
71
- console.log(` Make sure your globals.css includes the setup from nqui/nqui-setup.css\n`);
157
+ console.log(` ${deps}`);
158
+ console.log(` Note: For Vite, wrap your app with ThemeProvider and BrowserRouter in main.tsx\n`);
72
159
  }
73
160
 
74
161
  return copied;
75
162
  }
76
-
@@ -0,0 +1,169 @@
1
+ /**
2
+ * Generates downloadable documentation files for nqui.
3
+ *
4
+ * Outputs:
5
+ * - INSTALL.md: Installation instructions
6
+ * - COMPONENTS.md: All component documentation bundled
7
+ *
8
+ * Usage:
9
+ * node scripts/generate-docs.js
10
+ * node scripts/generate-docs.js --install
11
+ * node scripts/generate-docs.js --components
12
+ */
13
+
14
+ import { readFileSync, writeFileSync, readdirSync, existsSync } from 'fs';
15
+ import { join, dirname } from 'path';
16
+ import { fileURLToPath } from 'url';
17
+
18
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
+ const DOCS_DIR = join(__dirname, '../docs');
20
+ const OUTPUT_DIR = join(__dirname, '../dist/docs');
21
+
22
+ // Ensure output directory exists
23
+ import { mkdirSync, rmSync } from 'fs';
24
+
25
+ function ensureDir(dir) {
26
+ if (!existsSync(dir)) {
27
+ mkdirSync(dir, { recursive: true });
28
+ }
29
+ }
30
+
31
+ function readDoc(filePath) {
32
+ try {
33
+ return readFileSync(filePath, 'utf-8');
34
+ } catch (e) {
35
+ return null;
36
+ }
37
+ }
38
+
39
+ function generateInstallDoc() {
40
+ const installPath = join(DOCS_DIR, 'internal-notes/INSTALLATION.md');
41
+ let content = readDoc(installPath);
42
+
43
+ if (!content) {
44
+ console.error('Could not find INSTALLATION.md');
45
+ return null;
46
+ }
47
+
48
+ // Add header
49
+ const header = `# @nqlib/nqui Installation Guide
50
+
51
+ > Generated for LLM consumption. Use this file to understand how to install and set up nqui.
52
+
53
+ `;
54
+
55
+ return header + content;
56
+ }
57
+
58
+ function generateComponentsDoc() {
59
+ // Read the main README for shared conventions
60
+ const readmePath = join(DOCS_DIR, 'components/README.md');
61
+ let readmeContent = readDoc(readmePath);
62
+
63
+ if (!readmeContent) {
64
+ console.error('Could not find README.md');
65
+ return null;
66
+ }
67
+
68
+ // Extract sections from README that are useful for implementation
69
+ // (Prerequisites, Shared Conventions, When to Use tables)
70
+ const sectionsToKeep = [
71
+ '# nqui Component Instructions',
72
+ '## Prerequisites',
73
+ '## Shared Conventions',
74
+ '## When to Use',
75
+ '## AI Implementation Checklist'
76
+ ];
77
+
78
+ // Build the content
79
+ let content = `# @nqlib/nqui Component Reference
80
+
81
+ > Generated for LLM consumption. Use this file when implementing nqui components.
82
+
83
+ **Import:** \`import { X } from "@nqlib/nqui"\`
84
+ **CSS:** \`@import "@nqlib/nqui/styles"\` (via \`npx @nqlib/nqui init-css\`)
85
+
86
+ ---
87
+
88
+ `;
89
+
90
+ // Add the main sections from README
91
+ let currentSection = '';
92
+ const lines = readmeContent.split('\n');
93
+
94
+ for (const line of lines) {
95
+ // Track current section
96
+ if (line.startsWith('##')) {
97
+ currentSection = line;
98
+ }
99
+
100
+ // Keep specific sections
101
+ if (sectionsToKeep.some(s => line.includes(s) || currentSection.includes(s.replace('## ', '').replace('# ', '')))) {
102
+ content += line + '\n';
103
+ }
104
+ }
105
+
106
+ // Add component docs
107
+ content += '\n---\n\n## Component Documentation\n\n';
108
+
109
+ const componentsDir = join(DOCS_DIR, 'components');
110
+ const files = readdirSync(componentsDir)
111
+ .filter(f => f.startsWith('nqui-') && f.endsWith('.md'))
112
+ .sort();
113
+
114
+ for (const file of files) {
115
+ const componentPath = join(componentsDir, file);
116
+ const componentContent = readDoc(componentPath);
117
+
118
+ if (componentContent) {
119
+ // Extract the relevant part (without frontmatter if present)
120
+ let docContent = componentContent;
121
+
122
+ // Remove frontmatter
123
+ if (docContent.startsWith('---')) {
124
+ const endOfFm = docContent.indexOf('---', 3);
125
+ if (endOfFm !== -1) {
126
+ docContent = docContent.substring(endOfFm + 3);
127
+ }
128
+ }
129
+
130
+ // Clean up and add
131
+ docContent = docContent.trim();
132
+ content += docContent + '\n\n---\n\n';
133
+ }
134
+ }
135
+
136
+ return content;
137
+ }
138
+
139
+ function main() {
140
+ const args = process.argv.slice(2);
141
+ const generateInstall = args.includes('--install') || args.length === 0;
142
+ const generateComponents = args.includes('--components') || args.length === 0;
143
+
144
+ ensureDir(OUTPUT_DIR);
145
+
146
+ console.log('Generating nqui documentation...\n');
147
+
148
+ if (generateInstall) {
149
+ const installDoc = generateInstallDoc();
150
+ if (installDoc) {
151
+ const outputPath = join(OUTPUT_DIR, 'INSTALL.md');
152
+ writeFileSync(outputPath, installDoc);
153
+ console.log(`Generated: ${outputPath}`);
154
+ }
155
+ }
156
+
157
+ if (generateComponents) {
158
+ const componentsDoc = generateComponentsDoc();
159
+ if (componentsDoc) {
160
+ const outputPath = join(OUTPUT_DIR, 'COMPONENTS.md');
161
+ writeFileSync(outputPath, componentsDoc);
162
+ console.log(`Generated: ${outputPath}`);
163
+ }
164
+ }
165
+
166
+ console.log('\nDone!');
167
+ }
168
+
169
+ main();
@@ -16,12 +16,12 @@ import { runPipeline } from './pipeline/index.js';
16
16
  import { wizard, askAboutExamples } from './wizard.js';
17
17
  import { detectFramework, findMainCssFile } from './framework.js';
18
18
  import { generateSetupContent } from './setup-helper.js';
19
- import { copyNextJsExamples } from './examples.js';
19
+ import { copyNextJsExamples, copyViteExamples } from './examples.js';
20
20
  import { emit } from './pipeline/emit.js';
21
21
 
22
22
  // Guard: if first arg is a subcommand, user has old package (main bin was init-css). Redirect.
23
23
  const firstArg = process.argv[2];
24
- const subcommands = ['init-cursor', 'install-peers', 'init-debug', 'init-debug-css', 'setup'];
24
+ const subcommands = ['init-cursor', 'init-skills', 'install-peers', 'init-debug', 'init-debug-css', 'setup'];
25
25
  if (subcommands.includes(firstArg)) {
26
26
  console.error(`
27
27
  ❌ Outdated @nqlib/nqui — "npx @nqlib/nqui ${firstArg}" routed to init-css.
@@ -29,17 +29,17 @@ if (subcommands.includes(firstArg)) {
29
29
  Fix: npm install @nqlib/nqui@latest
30
30
  Then: npx @nqlib/nqui ${firstArg}
31
31
 
32
- Or run the binary directly: npm exec nqui-init-cursor
32
+ Or run the binary directly: npm exec nqui-init-skills (for init-skills), etc.
33
33
  `);
34
34
  process.exit(1);
35
35
  }
36
36
 
37
- // Filter out command names from argv
38
- const commandNames = ['init-css', 'nqui', 'nqui-init-css'];
37
+ // Filter out command names from argv (so they are never used as output path)
38
+ const commandNames = ['init-css', 'init-skills', 'nqui', 'nqui-init-css'];
39
39
  const filteredArgs = process.argv.slice(2).filter(arg => !commandNames.includes(arg));
40
40
 
41
41
  const args = minimist(filteredArgs, {
42
- boolean: ['js', 'tokens-only', 'force', 'dry-run', 'wizard', 'setup', 'help', 'version', 'local-copy'],
42
+ boolean: ['js', 'tokens-only', 'force', 'dry-run', 'wizard', 'setup', 'help', 'version', 'local-copy', 'sidebar'],
43
43
  alias: { h: 'help', v: 'version' },
44
44
  });
45
45
 
@@ -55,7 +55,11 @@ if (args.version) {
55
55
  process.exit(0);
56
56
  }
57
57
 
58
- const output = args._[0] || 'nqui/index.css';
58
+ // Prevent subcommand names or bare filenames from becoming output path (would e.g. create init-skills or root nqui-setup.css)
59
+ const rawOutput = args._[0] || 'nqui/index.css';
60
+ const output = !rawOutput || rawOutput === 'init-skills' || rawOutput === 'init-cursor' || !dirname(rawOutput)
61
+ ? 'nqui/index.css'
62
+ : rawOutput;
59
63
 
60
64
  /**
61
65
  * Generate index.css that imports from library package
@@ -101,29 +105,45 @@ function generateIndexCssContent() {
101
105
  });
102
106
  }
103
107
 
104
- // Generate setup helper file (always, unless dry-run)
108
+ // Generate setup helper file (always, unless dry-run). Always under nqui/ to avoid writing to project root.
105
109
  if (!args['dry-run'] && (args.setup || !args['tokens-only'])) {
106
110
  const setupContent = generateSetupContent(framework, output, useLibraryImport);
107
- const setupPath = resolve(process.cwd(), dirname(output), 'nqui-setup.css');
111
+ const setupDir = dirname(output) || 'nqui';
112
+ const setupPath = resolve(process.cwd(), setupDir, 'nqui-setup.css');
108
113
  emit(setupPath, setupContent, { force: args.force, dryRun: false });
109
114
 
110
115
  const mainCssFile = findMainCssFile(framework);
111
116
 
112
117
  console.log(`\n📝 Next steps:`);
113
- console.log(` 1. Copy the contents of ${dirname(output)}/nqui-setup.css`);
118
+ console.log(` 1. Copy the contents of ${setupDir}/nqui-setup.css`);
114
119
  console.log(` 2. Paste them at the VERY TOP of your main CSS file`);
115
120
  console.log(` (e.g. ${mainCssFile})\n`);
116
121
  }
117
122
 
118
- // Ask about copying examples (if not in wizard mode, ask for Next.js projects)
123
+ // Copy examples: when --sidebar and --force, auto-enable so nqui:init is non-interactive
119
124
  let shouldCopyExamples = wiz.copyExamples;
120
- if (!args['dry-run'] && !shouldCopyExamples && framework === 'nextjs' && !args.wizard) {
121
- shouldCopyExamples = await askAboutExamples(framework);
125
+ let useSidebar = wiz.sidebarLayout || args.sidebar;
126
+ if (!args.wizard && (framework === 'nextjs' || framework === 'vite')) {
127
+ if (args.sidebar && args.force) {
128
+ shouldCopyExamples = true;
129
+ useSidebar = true;
130
+ } else if (!args['dry-run'] && !shouldCopyExamples) {
131
+ shouldCopyExamples = await askAboutExamples(framework, useSidebar);
132
+ if (shouldCopyExamples) {
133
+ const sidebarAnswer = await askQuestion('\n🎨 Use sidebar layout (recommended for apps)? (y/n): ');
134
+ useSidebar = sidebarAnswer === 'y' || sidebarAnswer === 'yes';
135
+ }
136
+ }
122
137
  }
123
138
 
124
139
  // Copy example files if requested
125
140
  if (!args['dry-run'] && shouldCopyExamples) {
126
- const copied = await copyNextJsExamples(framework, { force: args.force });
141
+ let copied;
142
+ if (framework === 'nextjs') {
143
+ copied = await copyNextJsExamples(framework, { force: args.force, sidebar: useSidebar });
144
+ } else if (framework === 'vite') {
145
+ copied = await copyViteExamples(framework, { force: args.force, sidebar: useSidebar });
146
+ }
127
147
  if (copied && copied.length === 0) {
128
148
  // Files were skipped (user said no to overwrite)
129
149
  console.log('\n⏭️ Example files skipped. Run with --force to overwrite existing files.\n');
@@ -13,6 +13,7 @@ import { fileURLToPath } from 'url';
13
13
  import { FULL_PEER_LIST } from './peer-deps.js';
14
14
  import { buildInstallSkill, buildComponentsSkill } from './skill-templates.js';
15
15
  import { resolveTargetDir } from './resolve-target-dir.js';
16
+ import { downloadSkills } from './download-skills.js';
16
17
 
17
18
  const __dirname = dirname(fileURLToPath(import.meta.url));
18
19
  const cwd = process.cwd();
@@ -153,12 +154,19 @@ function main() {
153
154
  }
154
155
 
155
156
  writeCursorRule(targetDir);
157
+
158
+ // Also download nqui-skills to user's .cursor folder
159
+ console.log('\n📚 Downloading nqui-skills...');
160
+ downloadSkills({ force: false });
161
+
156
162
  console.log(`
157
163
  ✅ Cursor rules + skills installed
158
164
 
159
165
  ${displayPath}/.cursor/rules/nqui-components.mdc
160
166
  ${displayPath}/.cursor/skills/nqui-install/
161
167
  ${displayPath}/.cursor/skills/nqui-components/
168
+ ${displayPath}/.cursor/nqui-skills/
169
+ ${displayPath}/AGENTS.md
162
170
 
163
171
  Open this folder in Cursor for skills to work. Docs: node_modules/@nqlib/nqui/docs/components/
164
172
  `);
@@ -7,7 +7,7 @@
7
7
  * Auto-injects Cursor rules so consumers don't need to remember init-cursor.
8
8
  */
9
9
 
10
- import { existsSync } from 'fs';
10
+ import { existsSync, readFileSync, writeFileSync } from 'fs';
11
11
  import { resolve } from 'path';
12
12
 
13
13
  // Skip in CI to reduce noise
@@ -36,6 +36,31 @@ function getInstallCmd(pkgs) {
36
36
  }
37
37
  }
38
38
 
39
+ function addNquiInitScript() {
40
+ const cwd = process.cwd();
41
+ const packageJsonPath = resolve(cwd, 'package.json');
42
+
43
+ if (!existsSync(packageJsonPath)) return false;
44
+
45
+ try {
46
+ const pkg = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
47
+
48
+ // Check if script already exists
49
+ if (pkg.scripts?.['nqui:init']) {
50
+ return false; // Already exists
51
+ }
52
+
53
+ // Add the script
54
+ pkg.scripts = pkg.scripts || {};
55
+ pkg.scripts['nqui:init'] = 'npx @nqlib/nqui install-peers && npx @nqlib/nqui init-cursor && npx @nqlib/nqui init-skills && npx @nqlib/nqui init-css --sidebar --force';
56
+
57
+ writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2) + '\n');
58
+ return true;
59
+ } catch (e) {
60
+ return false;
61
+ }
62
+ }
63
+
39
64
  import { FULL_PEER_LIST } from './peer-deps.js';
40
65
  import { writeCursorRule } from './init-cursor.js';
41
66
  import { resolveTargetDir } from './resolve-target-dir.js';
@@ -47,21 +72,28 @@ const installRequired = getInstallCmd(requiredPeers);
47
72
  const installFull = getInstallCmd(['@nqlib/nqui', ...FULL_PEER_LIST]);
48
73
  const installRecommended = getInstallCmd(recommended);
49
74
 
75
+ // Auto-add nqui:init script to package.json
76
+ const scriptAdded = addNquiInitScript();
77
+
50
78
  const msg = `
51
79
  ╔══════════════════════════════════════════════════════════════════╗
52
- ║ nqui – Next steps
80
+ ║ nqui – Next steps
53
81
  ╚══════════════════════════════════════════════════════════════════╝
54
82
 
55
- 1. Set up CSS (required)
56
- npx @nqlib/nqui init-css
83
+ ${scriptAdded ? '✅ Added "nqui:init" script to package.json\n' : ''}Run the full setup:
84
+
85
+ npm run nqui:init
57
86
 
58
- 2. Install peers: ${installRequired}
59
- Full: npx @nqlib/nqui install-peers
87
+ Or step by step:
60
88
 
61
- 3. Optional: tw-animate-css, next-themes
89
+ npx @nqlib/nqui init-css --sidebar # CSS + sidebar layout
90
+ npx @nqlib/nqui install-peers # Install dependencies
91
+ npx @nqlib/nqui init-cursor # Setup Cursor skills
62
92
 
63
- 4. Cursor skills: auto-injected. Open this folder in Cursor.
64
- Refresh: npx @nqlib/nqui init-cursor
93
+ Manual commands:
94
+ - Install peers: ${installRequired}
95
+ - Full peers: npx @nqlib/nqui install-peers
96
+ - Refresh skills: npx @nqlib/nqui init-skills
65
97
 
66
98
  → Run "npx nqui-setup" anytime to see this again.
67
99
  `;
package/scripts/wizard.js CHANGED
@@ -35,23 +35,28 @@ export async function wizard() {
35
35
 
36
36
  const framework = detectFramework();
37
37
  let copyExamples = false;
38
+ let sidebarLayout = false;
38
39
 
39
- if (framework === 'nextjs') {
40
- copyExamples = (await ask('\nCopy Next.js example files (page.tsx, layout.tsx)? (y/n): ')) === 'y';
40
+ if (framework === 'nextjs' || framework === 'vite') {
41
+ copyExamples = (await ask(`\nCopy ${framework} example files? (y/n): `)) === 'y';
42
+ if (copyExamples) {
43
+ sidebarLayout = (await ask('\nUse sidebar layout (recommended for apps)? (y/n): ')) === 'y';
44
+ }
41
45
  }
42
46
 
43
- return { tokensOnly, js, copyExamples };
47
+ return { tokensOnly, js, copyExamples, sidebarLayout };
44
48
  }
45
49
 
46
50
  /**
47
- * Ask about copying examples (used in default mode for Next.js)
51
+ * Ask about copying examples (used in default mode)
48
52
  */
49
- export async function askAboutExamples(framework) {
50
- if (framework !== 'nextjs') {
53
+ export async function askAboutExamples(framework, useSidebar = false) {
54
+ if (framework !== 'nextjs' && framework !== 'vite') {
51
55
  return false;
52
56
  }
53
57
 
54
- const answer = await askQuestion('\n📦 Copy Next.js example files (page.tsx, layout.tsx)? (y/n): ');
58
+ const layoutType = useSidebar ? 'sidebar' : 'basic';
59
+ const answer = await askQuestion(`\n📦 Copy ${framework} example files (${layoutType} layout)? (y/n): `);
55
60
  return answer === 'y' || answer === 'yes';
56
61
  }
57
62