@@ -265,6 +265,11 @@ export async function init(options: InitOptions = {}): Promise<void> {
265265 ' ' + logger . code ( exampleImport ) ,
266266 ] ) ;
267267
268+ // SDUI scaffolding (when --sdui flag is set)
269+ if ( options . sdui ) {
270+ await scaffoldSdui ( framework , options . skipPrompts ) ;
271+ }
272+
268273 // Clean up temporary download directory if it exists
269274 await cleanupTempDownload ( ) ;
270275 } catch ( error ) {
@@ -505,6 +510,209 @@ function showTypeScriptNote(wasUpdated: boolean = false): void {
505510 }
506511}
507512
513+ /**
514+ * Scaffold a minimal Schema-Driven UI app for the given framework.
515+ * Creates src/sdui/fixture.ts and src/sdui/SduiDemo.{tsx|vue|ts}, then
516+ * installs the appropriate renderer package.
517+ */
518+ async function scaffoldSdui ( framework : Framework , skipPrompts : boolean = false ) : Promise < void > {
519+ logger . newline ( ) ;
520+ logger . info ( pc . cyan ( 'Schema-Driven UI' ) + ' — scaffolding starter files...' ) ;
521+
522+ const sduiDir = path . join ( process . cwd ( ) , 'src' , 'sdui' ) ;
523+ await ensureDir ( sduiDir ) ;
524+
525+ // Write shared fixture file
526+ const fixtureContent = `import type { AgNode } from '@agnosticui/schema';
527+
528+ export const fixture: AgNode[] = [
529+ { id: 'f-name', component: 'AgInput', label: 'Full name', type: 'text', placeholder: 'Jane Smith', required: true, rounded: true },
530+ { id: 'f-email', component: 'AgInput', label: 'Email', type: 'email', placeholder: 'jane@example.com', required: true, rounded: true },
531+ { id: 'f-submit', component: 'AgButton', variant: 'primary', type: 'submit', shape: 'rounded', on_click: 'SUBMIT', children: ['f-submit-label'] },
532+ { id: 'f-submit-label', component: 'AgText', text: 'Send message' },
533+ ];
534+ ` ;
535+ await writeFile ( path . join ( sduiDir , 'fixture.ts' ) , fixtureContent ) ;
536+ logger . info ( pc . green ( '✓' ) + ' Created ' + pc . dim ( 'src/sdui/fixture.ts' ) ) ;
537+
538+ // Write framework-specific demo component
539+ if ( framework === 'react' ) {
540+ const demoContent = `import { useState } from 'react';
541+ import { AgDynamicRenderer } from '@agnosticui/render-react';
542+ import type { AgNode } from '@agnosticui/schema';
543+ import { fixture } from './fixture';
544+
545+ function SkinToggle() {
546+ const toggle = () => {
547+ const root = document.documentElement;
548+ root.setAttribute('data-theme', root.getAttribute('data-theme') === 'dark' ? '' : 'dark');
549+ };
550+ return (
551+ <button
552+ onClick={toggle}
553+ style={{ position: 'fixed', bottom: '1rem', right: '1rem', padding: '0.5rem 1rem', cursor: 'pointer' }}
554+ >
555+ Toggle dark
556+ </button>
557+ );
558+ }
559+
560+ export function SduiDemo() {
561+ const [nodes] = useState<AgNode[]>(fixture);
562+ return (
563+ <div style={{ maxWidth: '600px', margin: '2rem auto', padding: '0 1rem' }}>
564+ <h1>Schema-Driven UI</h1>
565+ <AgDynamicRenderer nodes={nodes} actions={{}} />
566+ <SkinToggle />
567+ </div>
568+ );
569+ }
570+ ` ;
571+ await writeFile ( path . join ( sduiDir , 'SduiDemo.tsx' ) , demoContent ) ;
572+ logger . info ( pc . green ( '✓' ) + ' Created ' + pc . dim ( 'src/sdui/SduiDemo.tsx' ) ) ;
573+ } else if ( framework === 'vue' ) {
574+ const demoContent = `<script setup lang="ts">
575+ import { ref } from 'vue';
576+ import { AgDynamicRenderer } from '@agnosticui/render-vue';
577+ import type { AgNode } from '@agnosticui/schema';
578+ import { fixture } from './fixture';
579+
580+ const nodes = ref<AgNode[]>(fixture);
581+
582+ function toggleDark() {
583+ const root = document.documentElement;
584+ root.setAttribute('data-theme', root.getAttribute('data-theme') === 'dark' ? '' : 'dark');
585+ }
586+ </script>
587+
588+ <template>
589+ <div style="max-width: 600px; margin: 2rem auto; padding: 0 1rem">
590+ <h1>Schema-Driven UI</h1>
591+ <AgDynamicRenderer :nodes="nodes" :actions="{}" />
592+ <button
593+ @click="toggleDark"
594+ style="position: fixed; bottom: 1rem; right: 1rem; padding: 0.5rem 1rem; cursor: pointer"
595+ >
596+ Toggle dark
597+ </button>
598+ </div>
599+ </template>
600+ ` ;
601+ await writeFile ( path . join ( sduiDir , 'SduiDemo.vue' ) , demoContent ) ;
602+ logger . info ( pc . green ( '✓' ) + ' Created ' + pc . dim ( 'src/sdui/SduiDemo.vue' ) ) ;
603+ } else {
604+ // Lit (and other web-component-based frameworks)
605+ const demoContent = `import { LitElement, html, css } from 'lit';
606+ import { state } from 'lit/decorators.js';
607+ import '@agnosticui/render-lit';
608+ import type { AgNode } from '@agnosticui/schema';
609+ import { fixture } from './fixture';
610+
611+ export class SduiDemo extends LitElement {
612+ static styles = css\`
613+ :host { display: block; }
614+ .container { max-width: 600px; margin: 2rem auto; padding: 0 1rem; }
615+ .skin-toggle { position: fixed; bottom: 1rem; right: 1rem; padding: 0.5rem 1rem; cursor: pointer; }
616+ \`;
617+
618+ @state() private nodes: AgNode[] = fixture;
619+
620+ private toggleDark() {
621+ const root = document.documentElement;
622+ root.setAttribute('data-theme', root.getAttribute('data-theme') === 'dark' ? '' : 'dark');
623+ }
624+
625+ render() {
626+ return html\`
627+ <div class="container">
628+ <h1>Schema-Driven UI</h1>
629+ <ag-dynamic-renderer .nodes=\${this.nodes} .actions=\${{}}></ag-dynamic-renderer>
630+ <button class="skin-toggle" @click=\${this.toggleDark}>Toggle dark</button>
631+ </div>
632+ \`;
633+ }
634+ }
635+
636+ customElements.define('ag-sdui-demo', SduiDemo);
637+ ` ;
638+ await writeFile ( path . join ( sduiDir , 'SduiDemo.ts' ) , demoContent ) ;
639+ logger . info ( pc . green ( '✓' ) + ' Created ' + pc . dim ( 'src/sdui/SduiDemo.ts' ) ) ;
640+ }
641+
642+ // Install renderer package
643+ const rendererPkg =
644+ framework === 'react' ? '@agnosticui/render-react' :
645+ framework === 'vue' ? '@agnosticui/render-vue' :
646+ '@agnosticui/render-lit' ;
647+ const sduiDeps = [ rendererPkg , '@agnosticui/schema' ] ;
648+ const packageManager = detectPackageManager ( ) ;
649+
650+ if ( checkDependenciesInstalled ( sduiDeps ) ) {
651+ logger . info ( 'SDUI renderer already installed: ' + pc . dim ( sduiDeps . join ( ', ' ) ) ) ;
652+ } else {
653+ let shouldInstall = true ;
654+
655+ if ( ! skipPrompts ) {
656+ logger . newline ( ) ;
657+ logger . info ( 'SDUI requires the following packages:' ) ;
658+ sduiDeps . forEach ( dep => console . log ( ' ' + pc . cyan ( dep ) ) ) ;
659+ logger . newline ( ) ;
660+
661+ const answer = await p . confirm ( {
662+ message : `Install using ${ pc . cyan ( packageManager ) } ?` ,
663+ initialValue : true ,
664+ } ) ;
665+
666+ if ( p . isCancel ( answer ) || ! answer ) {
667+ shouldInstall = false ;
668+ logger . warn ( 'Skipped SDUI dependency installation.' ) ;
669+ logger . info ( `Install manually: ${ pc . cyan ( `${ packageManager } ${ packageManager === 'npm' ? 'install' : 'add' } ${ sduiDeps . join ( ' ' ) } ` ) } ` ) ;
670+ }
671+ }
672+
673+ if ( shouldInstall ) {
674+ const spinner = p . spinner ( ) ;
675+ spinner . start ( 'Installing SDUI renderer...' ) ;
676+ try {
677+ installDependencies ( sduiDeps ) ;
678+ spinner . stop ( pc . green ( '✓' ) + ' SDUI renderer installed!' ) ;
679+ } catch ( error ) {
680+ spinner . stop ( pc . red ( '✖' ) + ' Failed to install SDUI renderer' ) ;
681+ logger . error ( `Installation failed: ${ error instanceof Error ? error . message : 'Unknown error' } ` ) ;
682+ logger . info ( `Install manually: ${ pc . cyan ( `${ packageManager } ${ packageManager === 'npm' ? 'install' : 'add' } ${ sduiDeps . join ( ' ' ) } ` ) } ` ) ;
683+ }
684+ }
685+ }
686+
687+ const demoFile =
688+ framework === 'react' ? 'SduiDemo.tsx' :
689+ framework === 'vue' ? 'SduiDemo.vue' :
690+ 'SduiDemo.ts' ;
691+
692+ const importSnippet =
693+ framework === 'react' ? `import { SduiDemo } from './sdui/SduiDemo'` :
694+ framework === 'vue' ? `import SduiDemo from './sdui/SduiDemo.vue'` :
695+ `import './sdui/SduiDemo'` ;
696+
697+ const useSnippet =
698+ framework === 'react' ? `<SduiDemo />` :
699+ framework === 'vue' ? `<SduiDemo />` :
700+ `<ag-sdui-demo></ag-sdui-demo>` ;
701+
702+ logger . newline ( ) ;
703+ logger . box ( 'SDUI Scaffold Ready:' , [
704+ pc . dim ( 'Files created:' ) ,
705+ ' ' + pc . cyan ( 'src/sdui/fixture.ts' ) + pc . dim ( ' — edit this to change the rendered UI' ) ,
706+ ' ' + pc . cyan ( `src/sdui/${ demoFile } ` ) + pc . dim ( ' — AgDynamicRenderer wired to fixture' ) ,
707+ '' ,
708+ pc . dim ( 'Wire it into your App:' ) ,
709+ ' ' + logger . code ( importSnippet ) ,
710+ ' ' + logger . code ( useSnippet ) ,
711+ '' ,
712+ pc . dim ( 'Learn more: https://www.agnosticui.com/sdui.html' ) ,
713+ ] ) ;
714+ }
715+
508716/**
509717 * Strip comments from JSON content
510718 *
0 commit comments