@@ -104,22 +104,22 @@ const CORE_DEPS = ['termui', 'react', 'ink'];
104104 * Map: adapter name the user types → npm package(s) to install.
105105 */
106106const ADAPTER_DEPS : Record < string , string [ ] > = {
107- commander : [ 'commander' ] ,
108- chalk : [ 'chalk' ] ,
109- ora : [ 'ora' ] ,
110- meow : [ 'meow' ] ,
111- inquirer : [ 'inquirer' ] ,
112- yargs : [ 'yargs' ] ,
113- vue : [ 'vue' ] ,
114- svelte : [ 'svelte' ] ,
115- conf : [ 'conf' ] ,
116- execa : [ 'execa' ] ,
117- 'node-pty' : [ 'node-pty' ] ,
118- pty : [ 'node-pty' ] ,
119- keychain : [ 'keytar' ] ,
120- git : [ 'simple-git' ] ,
121- github : [ '@octokit/rest' ] ,
122- ai : [ '@anthropic-ai/sdk' ] ,
107+ commander : [ 'commander' ] ,
108+ chalk : [ 'chalk' ] ,
109+ ora : [ 'ora' ] ,
110+ meow : [ 'meow' ] ,
111+ inquirer : [ 'inquirer' ] ,
112+ yargs : [ 'yargs' ] ,
113+ vue : [ 'vue' ] ,
114+ svelte : [ 'svelte' ] ,
115+ conf : [ 'conf' ] ,
116+ execa : [ 'execa' ] ,
117+ 'node-pty' : [ 'node-pty' ] ,
118+ pty : [ 'node-pty' ] ,
119+ keychain : [ 'keytar' ] ,
120+ git : [ 'simple-git' ] ,
121+ github : [ '@octokit/rest' ] ,
122+ ai : [ '@anthropic-ai/sdk' ] ,
123123} ;
124124
125125// ─── Prettier formatting (optional) ──────────────────────────────────────────
@@ -163,7 +163,7 @@ export async function add(args: string[], opts?: { isNested?: boolean }): Promis
163163
164164 const cwd = process . cwd ( ) ;
165165 const config = getConfig ( cwd ) ;
166- const registryUrl = process . env [ 'TERMUI_REGISTRY' ] ?? config . registry ;
166+ const registryUrl = config . registry ;
167167
168168 if ( ! opts ?. isNested ) {
169169 printLogo ( ) ;
@@ -266,7 +266,7 @@ export async function add(args: string[], opts?: { isNested?: boolean }): Promis
266266 const config = getConfig ( cwd ) ;
267267
268268 // Build ordered registry URL list: primary first, then any extras from config.registries
269- const primaryUrl = process . env [ 'TERMUI_REGISTRY' ] ?? config . registry ;
269+ const primaryUrl = config . registry ;
270270 const extraRegistries = ( config . registries ?? [ ] ) . filter ( ( r ) => r !== primaryUrl ) ;
271271 const allRegistryUrls = [ primaryUrl , ...extraRegistries ] ;
272272
@@ -334,6 +334,42 @@ export async function add(args: string[], opts?: { isNested?: boolean }): Promis
334334 step ( `${ c . yellow } [dry-run]${ c . reset } No files will be written` ) ;
335335 }
336336
337+ const allComponentNames = Object . keys ( registry . components ) ;
338+
339+ // Validate all requested components exist before installing anything
340+ if ( ! installAll ) {
341+ for ( const componentName of targets ) {
342+ if ( ADAPTER_DEPS [ componentName ] ) continue ; // adapters are always valid
343+ if ( ! registry . components [ componentName ] ) {
344+ fail ( `No component ${ bold ( `'${ componentName } '` ) } found.` ) ;
345+
346+ const closest = findClosestMatch ( componentName , allComponentNames ) ;
347+ if ( closest && closest . distance <= 3 ) {
348+ console . log ( `${ c . yellow } Did you mean ${ closest . name } ?${ c . reset } ` ) ;
349+ const closestMeta = registry . components [ closest . name ] ;
350+ if ( closestMeta ) {
351+ const sameCategory = allComponentNames . filter (
352+ ( n ) => registry . components [ n ] ?. category === closestMeta . category
353+ ) ;
354+ console . log ( ` Available in ${ closestMeta . category } : ${ sameCategory . join ( ', ' ) } ` ) ;
355+ }
356+ } else {
357+ const byCategory : Record < string , string [ ] > = { } ;
358+ for ( const name of allComponentNames ) {
359+ const cat = registry . components [ name ] ?. category ?? 'other' ;
360+ if ( ! byCategory [ cat ] ) byCategory [ cat ] = [ ] ;
361+ byCategory [ cat ] ! . push ( name ) ;
362+ }
363+ for ( const [ cat , names ] of Object . entries ( byCategory ) ) {
364+ console . log ( ` Available in ${ cat } : ${ names . join ( ', ' ) } ` ) ;
365+ }
366+ }
367+
368+ process . exit ( 1 ) ;
369+ }
370+ }
371+ }
372+
337373 // Ensure core deps are installed for every component
338374 if ( ! isDryRun ) {
339375 const missingCore = getMissingDeps ( CORE_DEPS , cwd ) ;
@@ -342,8 +378,6 @@ export async function add(args: string[], opts?: { isNested?: boolean }): Promis
342378 installDeps ( missingCore , cwd ) ;
343379 }
344380 }
345-
346- const allComponentNames = Object . keys ( registry . components ) ;
347381 const installed = new Set < string > ( ) ;
348382 let addedCount = 0 ;
349383 let existedCount = 0 ;
@@ -356,7 +390,9 @@ export async function add(args: string[], opts?: { isNested?: boolean }): Promis
356390 if ( ! isDryRun ) {
357391 step ( `Adapter ${ hi ( componentName ) } → installing ${ hi ( adapterDeps . join ( ', ' ) ) } ` ) ;
358392 installDeps ( adapterDeps , cwd ) ;
359- step ( `${ hi ( '◇' ) } termui/${ componentName } ready ${ dim ( `import from 'termui/${ componentName } '` ) } ` ) ;
393+ step (
394+ `${ hi ( '◇' ) } termui/${ componentName } ready ${ dim ( `import from 'termui/${ componentName } '` ) } `
395+ ) ;
360396 } else {
361397 step ( `${ c . yellow } [dry-run]${ c . reset } Would install: ${ adapterDeps . join ( ', ' ) } ` ) ;
362398 }
@@ -365,36 +401,7 @@ export async function add(args: string[], opts?: { isNested?: boolean }): Promis
365401 }
366402
367403 const meta = registry . components [ componentName ] ;
368- if ( ! meta ) {
369- fail ( `No component ${ bold ( `'${ componentName } '` ) } found.` ) ;
370-
371- // Fuzzy match: find closest candidate
372- const closest = findClosestMatch ( componentName , allComponentNames ) ;
373- if ( closest && closest . distance <= 3 ) {
374- console . log ( `${ c . yellow } Did you mean ${ closest . name } ?${ c . reset } ` ) ;
375- // Show all components in the same category as the closest match
376- const closestMeta = registry . components [ closest . name ] ;
377- if ( closestMeta ) {
378- const sameCategory = allComponentNames . filter (
379- ( n ) => registry . components [ n ] ?. category === closestMeta . category
380- ) ;
381- console . log ( ` Available in ${ closestMeta . category } : ${ sameCategory . join ( ', ' ) } ` ) ;
382- }
383- } else {
384- // No close match — show all categories
385- const byCategory : Record < string , string [ ] > = { } ;
386- for ( const name of allComponentNames ) {
387- const cat = registry . components [ name ] ?. category ?? 'other' ;
388- if ( ! byCategory [ cat ] ) byCategory [ cat ] = [ ] ;
389- byCategory [ cat ] ! . push ( name ) ;
390- }
391- for ( const [ cat , names ] of Object . entries ( byCategory ) ) {
392- console . log ( ` Available in ${ cat } : ${ names . join ( ', ' ) } ` ) ;
393- }
394- }
395-
396- process . exit ( 1 ) ;
397- }
404+ if ( ! meta ) continue ; // pre-flight check already caught unknown components
398405
399406 const result = await installComponent (
400407 meta ,
0 commit comments