@chemx/starter-kit 26.9.11-481 → 26.9.11-631

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.
@@ -0,0 +1,264 @@
1
+ export const toPascalCase = (str) =>
2
+ str
3
+ .replace(/^([a-z])-/, '')
4
+ .split('-')
5
+ .map((p) => p.charAt(0).toUpperCase() + p.slice(1))
6
+ .join('');
7
+
8
+ export const buildReactComponent = (name, pascalName) => `import React from 'react';
9
+ import type { ${pascalName}Props } from './types';
10
+ import { use${pascalName}Controller } from './${name}.controller';
11
+
12
+ export const ${pascalName}: React.FC<${pascalName}Props> = ({
13
+ title,
14
+ subtitle,
15
+ variant = 'standard',
16
+ onAction
17
+ }) => {
18
+ const { state, canProceed, descriptor, handleAction } = use${pascalName}Controller({
19
+ initialState: { status: 'idle' },
20
+ onAction
21
+ });
22
+
23
+ return (
24
+ <div className="${name}">
25
+ <div className="${name}__header">
26
+ <h3 className="${name}__title">{title}</h3>
27
+ {subtitle && <p className="${name}__subtitle">{subtitle}</p>}
28
+ <span className={descriptor.className}>{descriptor.text}</span>
29
+ </div>
30
+ <div className="${name}__body">
31
+ <button
32
+ type="button"
33
+ className="${name}__action"
34
+ disabled={!canProceed}
35
+ onClick={handleAction}
36
+ >
37
+ {variant}
38
+ </button>
39
+ </div>
40
+ </div>
41
+ );
42
+ };
43
+
44
+ export default ${pascalName};
45
+ `;
46
+
47
+ export const buildVueComponent = (name, pascalName) => `<script setup lang="ts">
48
+ import type { ${pascalName}Props, ${pascalName}Emits } from './types';
49
+ import { use${pascalName}Controller } from './${name}.controller';
50
+
51
+ const props = withDefaults(defineProps<${pascalName}Props>(), {
52
+ variant: 'standard'
53
+ });
54
+
55
+ const emit = defineEmits<${pascalName}Emits>();
56
+
57
+ const { state, canProceed, descriptor, handleAction } = use${pascalName}Controller({
58
+ initialState: { status: 'idle' },
59
+ onAction: () => emit('action', props.title)
60
+ });
61
+ </script>
62
+
63
+ <template>
64
+ <div class="${name}">
65
+ <div class="${name}__header">
66
+ <h3 class="${name}__title">{{ title }}</h3>
67
+ <p v-if="subtitle" class="${name}__subtitle">{{ subtitle }}</p>
68
+ <span :class="descriptor.className">{{ descriptor.text }}</span>
69
+ </div>
70
+ <div class="${name}__body">
71
+ <button
72
+ type="button"
73
+ class="${name}__action"
74
+ :disabled="!canProceed"
75
+ @click="handleAction"
76
+ >
77
+ {{ variant }}
78
+ </button>
79
+ </div>
80
+ </div>
81
+ </template>
82
+
83
+ <style lang="scss" scoped>
84
+ @use './_${name}.scss';
85
+ </style>
86
+ `;
87
+
88
+ export const buildSvelteComponent = (name, pascalName) => `<script lang="ts">
89
+ import type { ${pascalName}Props } from './types';
90
+ import { create${pascalName}Controller } from './${name}.controller';
91
+
92
+ const {
93
+ title,
94
+ subtitle = '',
95
+ variant = 'standard',
96
+ onAction
97
+ }: ${pascalName}Props = $props();
98
+
99
+ const controller = create${pascalName}Controller({
100
+ initialState: { status: 'idle' },
101
+ onAction
102
+ });
103
+ </script>
104
+
105
+ <div class="${name}">
106
+ <div class="${name}__header">
107
+ <h3 class="${name}__title">{title}</h3>
108
+ {#if subtitle}
109
+ <p class="${name}__subtitle">{subtitle}</p>
110
+ {/if}
111
+ <span class={controller.descriptor.className}>{controller.descriptor.text}</span>
112
+ </div>
113
+ <div class="${name}__body">
114
+ <button
115
+ type="button"
116
+ class="${name}__action"
117
+ disabled={!controller.canProceed}
118
+ onclick={controller.handleAction}
119
+ >
120
+ {variant}
121
+ </button>
122
+ </div>
123
+ </div>
124
+
125
+ <style lang="scss">
126
+ @use './_${name}.scss';
127
+ </style>
128
+ `;
129
+
130
+ export const buildController = (name, pascalName) => `import { useState, useMemo } from 'react';
131
+ import type { ${pascalName}State, ${pascalName}Descriptor } from './types';
132
+
133
+ interface ControllerOptions {
134
+ readonly initialState?: ${pascalName}State;
135
+ readonly onAction?: () => void;
136
+ }
137
+
138
+ export const use${pascalName}Controller = (options: ControllerOptions = {}) => {
139
+ const [state, setState] = useState<${pascalName}State>(
140
+ options.initialState || { status: 'idle' }
141
+ );
142
+
143
+ const isIdle = state.status === 'idle';
144
+ const isPending = state.status === 'loading';
145
+ const canProceed = isIdle && !isPending;
146
+
147
+ const descriptor: ${pascalName}Descriptor = useMemo(() => {
148
+ if (state.status === 'loading') {
149
+ return { text: 'Loading...', className: '${name}__badge ${name}__badge--pending' };
150
+ }
151
+ if (state.status === 'active') {
152
+ return { text: 'Active', className: '${name}__badge ${name}__badge--active' };
153
+ }
154
+ return { text: 'Ready', className: '${name}__badge ${name}__badge--ready' };
155
+ }, [state.status]);
156
+
157
+ const handleAction = () => {
158
+ if (!canProceed) return;
159
+ setState({ status: 'active', activeId: 'item-1' });
160
+ options.onAction?.();
161
+ };
162
+
163
+ return { state, canProceed, descriptor, handleAction };
164
+ };
165
+ `;
166
+
167
+ export const buildTypes = (name, pascalName) => `export type ${pascalName}State =
168
+ | { readonly status: 'idle' }
169
+ | { readonly status: 'loading'; readonly progress: number }
170
+ | { readonly status: 'active'; readonly activeId: string }
171
+ | { readonly status: 'fault'; readonly faultMessage: string };
172
+
173
+ export interface ${pascalName}Descriptor {
174
+ readonly text: string;
175
+ readonly className: string;
176
+ }
177
+
178
+ export interface ${pascalName}Props {
179
+ readonly title: string;
180
+ readonly subtitle?: string;
181
+ readonly variant?: 'standard' | 'highlight';
182
+ readonly onAction?: () => void;
183
+ }
184
+
185
+ export interface ${pascalName}Emits {
186
+ (e: 'action', title: string): void;
187
+ }
188
+ `;
189
+
190
+ export const buildScss = (name) => `.${name} {
191
+ display: flex;
192
+ flex-direction: column;
193
+ padding: 16px;
194
+ background: rgba(19, 30, 58, 0.7);
195
+ backdrop-filter: blur(12px);
196
+ border: 1px solid rgba(98, 201, 255, 0.15);
197
+ border-radius: 8px;
198
+
199
+ &__header {
200
+ display: flex;
201
+ justify-content: space-between;
202
+ align-items: center;
203
+ }
204
+
205
+ &__title {
206
+ margin: 0;
207
+ font-size: 16px;
208
+ color: #ffffff;
209
+ }
210
+
211
+ &__subtitle {
212
+ margin: 4px 0 0;
213
+ font-size: 12px;
214
+ color: #94a3b8;
215
+ }
216
+
217
+ &__badge {
218
+ font-size: 11px;
219
+ padding: 2px 8px;
220
+ border-radius: 4px;
221
+ background: #0b1329;
222
+
223
+ &--ready { color: #62c9ff; }
224
+ &--active { color: #4ade80; }
225
+ &--pending { color: #facc15; }
226
+ }
227
+
228
+ &__body {
229
+ margin-top: 14px;
230
+ display: flex;
231
+ justify-content: flex-end;
232
+ }
233
+
234
+ &__action {
235
+ padding: 6px 14px;
236
+ border-radius: 4px;
237
+ background: #1e293b;
238
+ border: 1px solid #334155;
239
+ color: #ffffff;
240
+ cursor: pointer;
241
+
242
+ &:hover:not(:disabled) {
243
+ background: #334155;
244
+ border-color: #62c9ff;
245
+ }
246
+
247
+ &:disabled {
248
+ opacity: 0.5;
249
+ cursor: not-allowed;
250
+ }
251
+ }
252
+ }
253
+ `;
254
+
255
+ export const buildIndex = (name, pascalName, ext) => {
256
+ const compExport = ext === 'vue' || ext === 'svelte'
257
+ ? `export { default as ${pascalName} } from './${name}.${ext}';`
258
+ : `export { ${pascalName} } from './${name}';`;
259
+
260
+ return `${compExport}
261
+ export { use${pascalName}Controller } from './${name}.controller';
262
+ export type { ${pascalName}Props, ${pascalName}State } from './types';
263
+ `;
264
+ };
@@ -0,0 +1,10 @@
1
+ export interface GeneratorOptions {
2
+ readonly name?: string;
3
+ readonly tier?: 'm' | 'a' | 'o' | 't';
4
+ readonly framework?: 'react' | 'vue' | 'svelte';
5
+ readonly dir?: string;
6
+ readonly isLean?: boolean;
7
+ }
8
+
9
+ export declare function runGenerateWizard(rawArgs?: string[]): Promise<void>;
10
+ export declare function runGenerateCapsule(capsuleName: string): Promise<void>;
@@ -0,0 +1,151 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { hasGum, gumChoose, gumInput, promptQuestion, renderBanner } from './terminal.js';
4
+ import { checkOrPromptEvaluation } from './license.js';
5
+ import {
6
+ toPascalCase,
7
+ buildReactComponent,
8
+ buildVueComponent,
9
+ buildSvelteComponent,
10
+ buildController,
11
+ buildTypes,
12
+ buildScss,
13
+ buildIndex
14
+ } from './generator-templates.js';
15
+
16
+ const TIERS = [
17
+ { prefix: 'm-', label: '1. m- Molecule (Self-contained feature block < 100 lines - Recommended)', tier: 'm' },
18
+ { prefix: 'a-', label: '2. a- Atom (Single foundational UI element)', tier: 'a' },
19
+ { prefix: 'o-', label: '3. o- Organism (Complex module combining molecules)', tier: 'o' },
20
+ { prefix: 't-', label: '4. t- Template (Structural layout blueprint)', tier: 't' }
21
+ ];
22
+
23
+ const FRAMEWORKS = [
24
+ { id: 'react', ext: 'tsx', label: '1. React 19 (TSX + Controller Hook)', builder: buildReactComponent },
25
+ { id: 'vue', ext: 'vue', label: '2. Vue 3.4+ (SFC <script setup lang="ts">)', builder: buildVueComponent },
26
+ { id: 'svelte', ext: 'svelte', label: '3. Svelte 5 (Runes + {prop} Shorthand)', builder: buildSvelteComponent }
27
+ ];
28
+
29
+ const detectBaseDir = () => {
30
+ const candidates = ['src/components/molecules', 'src/components', 'components', 'src'];
31
+ for (const c of candidates) {
32
+ if (fs.existsSync(path.resolve(process.cwd(), c))) return c;
33
+ }
34
+ return '.';
35
+ };
36
+
37
+ export const runGenerateWizard = async (rawArgs = []) => {
38
+ renderBanner('Chemical X: Molecular Capsule Wizard');
39
+ await checkOrPromptEvaluation('generate capsule');
40
+
41
+ const useGum = hasGum();
42
+ const isYes = rawArgs.includes('-y') || rawArgs.includes('--yes');
43
+
44
+ const nameArg = rawArgs.find((a) => !a.startsWith('-') && a !== 'generate' && a !== 'capsule' && a !== 'add');
45
+ const frameworkArg = (rawArgs.find((a) => a.startsWith('--framework=')) || '').split('=')[1]
46
+ || (rawArgs.includes('-f') ? rawArgs[rawArgs.indexOf('-f') + 1] : null);
47
+ const tierArg = (rawArgs.find((a) => a.startsWith('--tier=')) || '').split('=')[1];
48
+ const dirArg = (rawArgs.find((a) => a.startsWith('--dir=')) || '').split('=')[1];
49
+ const isLean = rawArgs.includes('--lean');
50
+
51
+ let rawName = nameArg;
52
+ if (!rawName) {
53
+ rawName = useGum
54
+ ? gumInput('Capsule feature name (e.g. user-avatar, spark-kpi):', 'user-avatar')
55
+ : await promptQuestion('Capsule feature name [user-avatar]: ');
56
+ }
57
+ const cleanName = (rawName || 'user-avatar').trim().toLowerCase();
58
+
59
+ let selectedPrefix = 'm-';
60
+ const existingPrefixMatch = cleanName.match(/^([a-z])-+/);
61
+ if (tierArg) {
62
+ selectedPrefix = `${tierArg.replace(/[^a-z]/g, '')}-`;
63
+ } else if (!isYes && !existingPrefixMatch) {
64
+ const tierChoice = useGum
65
+ ? gumChoose(TIERS.map((t) => t.label), 'Select Architectural Tier')
66
+ : await promptQuestion('Select Architectural Tier [1=m, 2=a, 3=o, 4=t] (default: 1): ');
67
+ const matched = TIERS.find((t) => tierChoice && (tierChoice.includes(t.label) || tierChoice.startsWith(t.tier) || tierChoice === t.prefix));
68
+ if (matched) selectedPrefix = matched.prefix;
69
+ } else if (existingPrefixMatch) {
70
+ selectedPrefix = existingPrefixMatch[0];
71
+ }
72
+
73
+ const baseSlug = cleanName.replace(/^([a-z])-/, '');
74
+ const capsuleName = `${selectedPrefix}${baseSlug}`;
75
+ const pascalName = toPascalCase(capsuleName);
76
+
77
+ let selectedFramework = FRAMEWORKS[0];
78
+ if (frameworkArg) {
79
+ const found = FRAMEWORKS.find((f) => f.id === frameworkArg.toLowerCase() || f.ext === frameworkArg.toLowerCase());
80
+ if (found) selectedFramework = found;
81
+ } else if (!isYes) {
82
+ const fwChoice = useGum
83
+ ? gumChoose(FRAMEWORKS.map((f) => f.label), 'Select Framework Flavor')
84
+ : await promptQuestion('Select Framework Flavor [1=React, 2=Vue 3, 3=Svelte 5] (default: 1): ');
85
+ const found = FRAMEWORKS.find((f) => fwChoice && (fwChoice.includes(f.label) || fwChoice.toLowerCase().includes(f.id)));
86
+ if (found) selectedFramework = found;
87
+ }
88
+
89
+ const detectedDir = detectBaseDir();
90
+ let targetParent = dirArg || (isYes ? detectedDir : null);
91
+ if (!targetParent) {
92
+ const dirChoices = [
93
+ `1. Detected components directory (${detectedDir}/${capsuleName})`,
94
+ `2. Current working directory (./${capsuleName})`,
95
+ '3. Custom directory path'
96
+ ];
97
+ const dirPick = useGum
98
+ ? gumChoose(dirChoices, 'Select Destination Directory')
99
+ : await promptQuestion(`Destination Directory [1=${detectedDir}, 2=current, 3=custom] (default: 1): `);
100
+
101
+ if (dirPick && dirPick.startsWith('2.')) {
102
+ targetParent = '.';
103
+ } else if (dirPick && dirPick.startsWith('3.')) {
104
+ targetParent = useGum
105
+ ? gumInput('Enter custom parent directory path:', detectedDir)
106
+ : await promptQuestion(`Enter custom parent directory path [${detectedDir}]: `);
107
+ } else {
108
+ targetParent = detectedDir;
109
+ }
110
+ }
111
+
112
+ const resolvedParent = path.resolve(process.cwd(), targetParent || '.');
113
+ const targetDir = path.resolve(resolvedParent, capsuleName);
114
+
115
+ if (fs.existsSync(targetDir)) {
116
+ process.stderr.write(`\x1b[31m✕ Error: Directory ${capsuleName} already exists at ${targetDir}.\x1b[0m\n`);
117
+ process.exit(1);
118
+ }
119
+
120
+ fs.mkdirSync(targetDir, { recursive: true });
121
+
122
+ const compFile = `${capsuleName}.${selectedFramework.ext}`;
123
+ const compContent = selectedFramework.builder(capsuleName, pascalName);
124
+ const typesContent = buildTypes(capsuleName, pascalName);
125
+ const indexContent = buildIndex(capsuleName, pascalName, selectedFramework.ext);
126
+
127
+ fs.writeFileSync(path.join(targetDir, compFile), compContent, 'utf-8');
128
+ fs.writeFileSync(path.join(targetDir, 'types.d.ts'), typesContent, 'utf-8');
129
+ fs.writeFileSync(path.join(targetDir, 'index.ts'), indexContent, 'utf-8');
130
+
131
+ const filesCreated = [compFile, 'types.d.ts', 'index.ts'];
132
+
133
+ if (!isLean) {
134
+ const controllerFile = `${capsuleName}.controller.ts`;
135
+ const scssFile = `_${capsuleName}.scss`;
136
+ fs.writeFileSync(path.join(targetDir, controllerFile), buildController(capsuleName, pascalName), 'utf-8');
137
+ fs.writeFileSync(path.join(targetDir, scssFile), buildScss(capsuleName), 'utf-8');
138
+ filesCreated.push(controllerFile, scssFile);
139
+ }
140
+
141
+ const relTargetDir = path.relative(process.cwd(), targetDir);
142
+ process.stdout.write(`\n\x1b[1m\x1b[32m✔ Successfully generated crystalline capsule:\x1b[0m \x1b[36m${relTargetDir}/\x1b[0m\n`);
143
+ for (const f of filesCreated) {
144
+ process.stdout.write(` \x1b[32m✔\x1b[0m ${f}\n`);
145
+ }
146
+ process.stdout.write('\n\x1b[2mChemical X Standards verified: < 100 lines per file, 2-stage booleans, zero inline styles.\x1b[0m\n\n');
147
+ };
148
+
149
+ export const runGenerateCapsule = async (capsuleName) => {
150
+ return runGenerateWizard([capsuleName, '-y']);
151
+ };
package/cli/help.js CHANGED
@@ -23,13 +23,16 @@ export const printHelp = () => {
23
23
  ` Computes Molecular Health Index (MHI: 0-100) and letter grades (A+ to F).`,
24
24
  ` Defaults to interactive zero-scroll Gum terminal dashboard.`,
25
25
  '',
26
- ` ${CYAN}generate${RESET} <m-name> ${DIM}(aliases: capsule, add)${RESET}`,
27
- ` Scaffold an isolated crystalline molecule capsule directory:`,
28
- ` ${DIM}- ${RESET}<m-name>/<m-name>.tsx ${DIM}(declarative component < 50 lines of code)${RESET}`,
29
- ` ${DIM}- ${RESET}<m-name>/types.d.ts ${DIM}(props and emits interfaces)${RESET}`,
30
- ` ${DIM}- ${RESET}<m-name>/index.ts ${DIM}(clean public barrel export)${RESET}`,
31
- ` Free evaluation mode supported with non-blocking license reminder.`,
32
- ` Example: ${CYAN}npx chemx generate m-user-avatar${RESET}`,
26
+ ` ${CYAN}generate${RESET} [name] ${DIM}(aliases: capsule, add)${RESET}`,
27
+ ` Launch the interactive Molecular Capsule Generator wizard.`,
28
+ ` Scaffolds a compliant, crystalline capsule directory with:`,
29
+ ` ${DIM}- ${RESET}<name>.<ext> ${DIM}(React .tsx, Vue .vue, or Svelte .svelte < 100 lines)${RESET}`,
30
+ ` ${DIM}- ${RESET}<name>.controller.ts ${DIM}(pure reactive state & 2-stage booleans)${RESET}`,
31
+ ` ${DIM}- ${RESET}_<name>.scss ${DIM}(mixin-only glass styling with zero inline styles)${RESET}`,
32
+ ` ${DIM}- ${RESET}types.d.ts ${DIM}(discriminated union states and props interfaces)${RESET}`,
33
+ ` ${DIM}- ${RESET}index.ts ${DIM}(clean public barrel export)${RESET}`,
34
+ ` Flags: ${CYAN}--tier=m|a|o|t${RESET}, ${CYAN}--framework=react|vue|svelte${RESET}, ${CYAN}--dir=<path>${RESET}, ${CYAN}-y${RESET}`,
35
+ ` Example: ${CYAN}npx chemx generate${RESET} or ${CYAN}npx chemx generate m-user-avatar -y${RESET}`,
33
36
  '',
34
37
  ` ${CYAN}init${RESET} [directory]`,
35
38
  ` ${YELLOW}[PAID]${RESET} Drop full Chemical X blueprints, core hooks (toResult,`,
package/cli/index.js CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  runScaffold,
16
16
  runInit,
17
17
  runGenerateCapsule,
18
+ runGenerateWizard,
18
19
  printHelp
19
20
  } from './scaffold.js';
20
21
  import {
@@ -163,13 +164,7 @@ const main = async () => {
163
164
  case 'generate':
164
165
  case 'capsule':
165
166
  case 'add':
166
- if (!rawArgs[1]) {
167
- process.stderr.write(
168
- 'Usage: npx chemx generate <capsule-name>\nExample: npx chemx generate m-user-avatar\n'
169
- );
170
- process.exit(1);
171
- }
172
- await runGenerateCapsule(rawArgs[1]);
167
+ await runGenerateWizard(rawArgs.slice(1));
173
168
  break;
174
169
  case 'help':
175
170
  case '--help':
@@ -177,8 +172,8 @@ const main = async () => {
177
172
  printHelp();
178
173
  break;
179
174
  default:
180
- if (firstArg && firstArg.startsWith('m-')) {
181
- await runGenerateCapsule(firstArg);
175
+ if (firstArg && (firstArg.startsWith('m-') || firstArg.startsWith('a-') || firstArg.startsWith('o-') || firstArg.startsWith('t-'))) {
176
+ await runGenerateWizard(rawArgs);
182
177
  } else if (firstArg && !firstArg.startsWith('-')) {
183
178
  await runScaffold(firstArg, rawArgs, runAudit);
184
179
  } else {
package/cli/license.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import os from 'node:os';
4
+ import { spawnSync } from 'node:child_process';
4
5
  import {
5
6
  hasGum,
6
7
  gumChoose,
package/cli/scaffold.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
  import { hasGum, gumInput, promptQuestion, renderBanner } from './terminal.js';
4
- import { obtainLicenseKey, fetchStarterKitFiles, checkOrPromptEvaluation } from './license.js';
4
+ import { obtainLicenseKey, fetchStarterKitFiles } from './license.js';
5
5
 
6
6
  export const runScaffold = async (projectName, rawArgs = [], onRunAudit = null) => {
7
7
  renderBanner('Chemical X: Molecular Architecture Scaffolder (npm create chemx)');
@@ -92,57 +92,6 @@ export const runInit = async (targetSubDir = 'src/chemical-x', rawArgs = [], onR
92
92
  );
93
93
  };
94
94
 
95
- export const runGenerateCapsule = async (capsuleName) => {
96
- await checkOrPromptEvaluation('generate capsule');
97
-
98
- const normalizedName = capsuleName.startsWith('m-') ? capsuleName : `m-${capsuleName}`;
99
- const targetDir = path.resolve(process.cwd(), normalizedName);
100
-
101
- if (fs.existsSync(targetDir)) {
102
- process.stderr.write(`\x1b[31m✕ Error: Directory ${normalizedName} already exists.\x1b[0m\n`);
103
- process.exit(1);
104
- }
105
-
106
- fs.mkdirSync(targetDir, { recursive: true });
107
-
108
- const pascalName = normalizedName
109
- .split('-')
110
- .map((p) => p.charAt(0).toUpperCase() + p.slice(1))
111
- .join('');
112
-
113
- const componentCode = `import React from 'react';
114
- import type { ${pascalName}Props } from './types';
115
-
116
- export const ${pascalName}: React.FC<${pascalName}Props> = ({ label }) => {
117
- return (
118
- <div className="${normalizedName}">
119
- <span>{label}</span>
120
- </div>
121
- );
122
- };
123
-
124
- export default ${pascalName};
125
- `;
126
-
127
- const typesCode = `export interface ${pascalName}Props {
128
- readonly label: string;
129
- }
130
- `;
131
-
132
- const indexCode = `export { ${pascalName} } from './${normalizedName}';
133
- export type { ${pascalName}Props } from './types';
134
- `;
135
-
136
- fs.writeFileSync(path.join(targetDir, `${normalizedName}.tsx`), componentCode, 'utf-8');
137
- fs.writeFileSync(path.join(targetDir, 'types.d.ts'), typesCode, 'utf-8');
138
- fs.writeFileSync(path.join(targetDir, 'index.ts'), indexCode, 'utf-8');
139
-
140
- process.stdout.write(
141
- `\x1b[32m✔ Successfully generated crystalline capsule:\x1b[0m ${normalizedName}/\n`
142
- );
143
- process.stdout.write(` - ${normalizedName}/${normalizedName}.tsx (< 50 lines)\n`);
144
- process.stdout.write(` - ${normalizedName}/types.d.ts\n`);
145
- process.stdout.write(` - ${normalizedName}/index.ts\n\n`);
146
- };
147
-
95
+ export { runGenerateCapsule, runGenerateWizard } from './generator.js';
148
96
  export { printHelp } from './help.js';
97
+
package/docs/CHANGELOG.md CHANGED
@@ -18,11 +18,17 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
18
18
 
19
19
  ## [2026-09-11]
20
20
 
21
+ ### Added
22
+ - Interactive Molecular Capsule Generator wizard for `npx chemx generate` (and aliases `capsule`, `add`), supporting Gum and ANSI fallbacks (`cli/generator.js`, `cli/generator.d.ts`).
23
+ - Multi-framework scaffolding support for React 19 (`.tsx`), Vue 3.4+ (`.vue`), and Svelte 5 (`.svelte`) with scoped SCSS, controllers, and discriminated union types (`cli/generator-templates.js`).
24
+ - CLI flags for non-interactive and fast capsule generation (`--tier`, `--framework`, `--dir`, `--lean`, `-y` / `--yes`).
25
+
21
26
  ### Changed
22
27
  - Conditioned `[ Prompt ] 📋 Copy AI Prompt Fix to Clipboard` dashboard action to hide when the codebase earns a pristine Grade A+ with no pending refactoring prompt (`cli/navigator.js`, `cli/navigator-actions.js`).
23
28
  - Added early-return guard clause to `handleCopyPromptAction` preventing empty clipboard copy operations on pristine Grade A+ audits (`cli/navigator-actions.js`).
24
29
 
25
30
  ### Fixed
31
+ - Fixed missing `spawnSync` import from `node:child_process` in evaluation check prompt causing runtime reference error (`cli/license.js`).
26
32
  - Fixed typographical artifact (`and p`) in the interactive audit publication confirmation prompt across navigator and terminal handlers (`cli/navigator.js`, `cli/terminal.js`).
27
33
 
28
34
  ## [2026-09-10]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@chemx/starter-kit",
3
- "version": "26.9.11-481",
3
+ "version": "26.9.11-631",
4
4
  "description": "Chemical X Protocol: Private drop-in architecture starter kit and capsule generator",
5
5
  "type": "module",
6
6
  "bin": {