@@ -11,7 +11,13 @@ import * as Fetch from './Fetch.js'
1111import * as Filter from './Filter.js'
1212import * as Formatter from './Formatter.js'
1313import * as Help from './Help.js'
14- import { builtinCommands , type CommandMeta , type Shell , shells } from './internal/command.js'
14+ import {
15+ builtinCommands ,
16+ type CommandMeta ,
17+ findBuiltin ,
18+ type Shell ,
19+ shells ,
20+ } from './internal/command.js'
1521import * as Command from './internal/command.js'
1622import { isRecord , suggest } from './internal/helpers.js'
1723import { detectRunner } from './internal/pm.js'
@@ -239,6 +245,8 @@ export function create(
239245 return cli
240246 }
241247 commands . set ( nameOrCli , def )
248+ if ( def . aliases )
249+ for ( const a of def . aliases ) commands . set ( a , { _alias : true , target : nameOrCli } )
242250 return cli
243251 }
244252 const mountedRootDef = toRootDefinition . get ( nameOrCli )
@@ -530,8 +538,8 @@ async function serveImpl(
530538 } )
531539 }
532540 } else if ( nonFlags . length === 2 ) {
533- const parent = nonFlags [ nonFlags . length - 1 ]
534- const builtin = builtinCommands . find ( ( b ) => b . name === parent && b . subcommands )
541+ const parent = nonFlags [ nonFlags . length - 1 ] !
542+ const builtin = findBuiltin ( parent )
535543 if ( builtin ?. subcommands )
536544 for ( const sub of builtin . subcommands )
537545 if ( sub . name . startsWith ( current ) )
@@ -547,7 +555,8 @@ async function serveImpl(
547555 let skillsCta : FormattedCtaBlock | undefined
548556 if ( ! llms && ! llmsFull && ! schema && ! help && ! version ) {
549557 const isSkillsAdd =
550- filtered [ 0 ] === 'skills' || ( filtered [ 0 ] === name && filtered [ 1 ] === 'skills' )
558+ findBuiltin ( filtered [ 0 ] ! ) ?. name === 'skills' ||
559+ ( filtered [ 0 ] === name && findBuiltin ( filtered [ 1 ] ! ) ?. name === 'skills' )
551560 const isMcpAdd = filtered [ 0 ] === 'mcp' || ( filtered [ 0 ] === name && filtered [ 1 ] === 'mcp' )
552561 if ( ! isSkillsAdd && ! isMcpAdd ) {
553562 const stored = SyncSkills . readHash ( name )
@@ -574,8 +583,9 @@ async function serveImpl(
574583 const prefix : string [ ] = [ ]
575584 let scopedDescription : string | undefined = options . description
576585 for ( const token of filtered ) {
577- const entry = scopedCommands . get ( token )
578- if ( ! entry ) break
586+ const rawEntry = scopedCommands . get ( token )
587+ if ( ! rawEntry ) break
588+ const entry = resolveAlias ( scopedCommands , rawEntry )
579589 if ( isGroup ( entry ) ) {
580590 scopedCommands = entry . commands
581591 scopedDescription = entry . description
@@ -653,8 +663,12 @@ async function serveImpl(
653663
654664 // skills add: generate skill files and install via `<pm>x skills add` (only when sync is configured)
655665 const skillsIdx =
656- filtered [ 0 ] === 'skills' ? 0 : filtered [ 0 ] === name && filtered [ 1 ] === 'skills' ? 1 : - 1
657- if ( skillsIdx !== - 1 && filtered [ skillsIdx ] === 'skills' ) {
666+ findBuiltin ( filtered [ 0 ] ! ) ?. name === 'skills'
667+ ? 0
668+ : filtered [ 0 ] === name && findBuiltin ( filtered [ 1 ] ! ) ?. name === 'skills'
669+ ? 1
670+ : - 1
671+ if ( skillsIdx !== - 1 ) {
658672 const skillsSub = filtered [ skillsIdx + 1 ]
659673 if ( skillsSub && skillsSub !== 'add' && skillsSub !== 'list' ) {
660674 const suggestion = suggest ( skillsSub , [ 'add' , 'list' ] )
@@ -681,13 +695,13 @@ async function serveImpl(
681695 return
682696 }
683697 if ( ! skillsSub ) {
684- const b = builtinCommands . find ( ( c ) => c . name === 'skills' ) !
698+ const b = findBuiltin ( 'skills' ) !
685699 writeln ( formatBuiltinHelp ( name , b ) )
686700 return
687701 }
688702 if ( skillsSub === 'list' ) {
689703 if ( help ) {
690- const b = builtinCommands . find ( ( c ) => c . name === 'skills' ) !
704+ const b = findBuiltin ( 'skills' ) !
691705 writeln ( formatBuiltinSubcommandHelp ( name , b , 'list' ) )
692706 return
693707 }
@@ -733,7 +747,7 @@ async function serveImpl(
733747 return
734748 }
735749 if ( help ) {
736- const b = builtinCommands . find ( ( c ) => c . name === 'skills' ) !
750+ const b = findBuiltin ( 'skills' ) !
737751 writeln ( formatBuiltinSubcommandHelp ( name , b , 'add' ) )
738752 return
739753 }
@@ -1008,7 +1022,7 @@ async function serveImpl(
10081022 writeln (
10091023 Help . formatCommand ( commandName , {
10101024 alias : cmd . alias as Record < string , string > | undefined ,
1011- aliases : isRootCmd ? options . aliases : undefined ,
1025+ aliases : isRootCmd ? options . aliases : cmd . aliases ,
10121026 configFlag,
10131027 description : cmd . description ,
10141028 version : isRootCmd ? options . version : undefined ,
@@ -1897,7 +1911,7 @@ function resolveCommand(
18971911
18981912 if ( ! first || ! commands . has ( first ) ) return { error : first ?? '(none)' , path : '' , commands, rest }
18991913
1900- let entry = commands . get ( first ) !
1914+ let entry = resolveAlias ( commands , commands . get ( first ) ! )
19011915 const path = [ first ]
19021916 let remaining = rest
19031917 let inheritedOutputPolicy : OutputPolicy | undefined
@@ -1927,15 +1941,16 @@ function resolveCommand(
19271941 commands : entry . commands ,
19281942 }
19291943
1930- const child = entry . commands . get ( next )
1931- if ( ! child ) {
1944+ const rawChild = entry . commands . get ( next )
1945+ if ( ! rawChild ) {
19321946 return {
19331947 error : next ,
19341948 path : path . join ( ' ' ) ,
19351949 commands : entry . commands ,
19361950 rest : remaining . slice ( 1 ) ,
19371951 }
19381952 }
1953+ let child = resolveAlias ( entry . commands , rawChild )
19391954
19401955 path . push ( next )
19411956 remaining = remaining . slice ( 1 )
@@ -2260,6 +2275,7 @@ function collectHelpCommands(
22602275) : { name : string ; description ?: string | undefined } [ ] {
22612276 const result : { name : string ; description ?: string | undefined } [ ] = [ ]
22622277 for ( const [ name , entry ] of commands ) {
2278+ if ( isAlias ( entry ) ) continue
22632279 result . push ( { name, description : entry . description } )
22642280 }
22652281 return result . sort ( ( a , b ) => a . name . localeCompare ( b . name ) )
@@ -2268,6 +2284,7 @@ function collectHelpCommands(
22682284/** @internal Formats group-level help for a built-in command (e.g. `cli skills`). */
22692285function formatBuiltinHelp ( cli : string , builtin : ( typeof builtinCommands ) [ number ] ) : string {
22702286 return Help . formatRoot ( `${ cli } ${ builtin . name } ` , {
2287+ aliases : builtin . aliases ,
22712288 description : builtin . description ,
22722289 commands : builtin . subcommands ?. map ( ( s ) => ( { name : s . name , description : s . description } ) ) ,
22732290 } )
@@ -2314,7 +2331,11 @@ export type CommandsMap = Record<
23142331>
23152332
23162333/** @internal Entry stored in a command map — either a leaf definition, a group, or a fetch gateway. */
2317- type CommandEntry = CommandDefinition < any , any , any > | InternalGroup | InternalFetchGateway
2334+ type CommandEntry =
2335+ | CommandDefinition < any , any , any >
2336+ | InternalGroup
2337+ | InternalFetchGateway
2338+ | InternalAlias
23182339
23192340/** Controls when output data is displayed. `'all'` displays to both humans and agents. `'agent-only'` suppresses data output in human/TTY mode. */
23202341export type OutputPolicy = 'agent-only' | 'all'
@@ -2350,6 +2371,27 @@ function isFetchGateway(entry: CommandEntry): entry is InternalFetchGateway {
23502371 return '_fetch' in entry
23512372}
23522373
2374+ /** @internal An alias entry that points to another command by name. */
2375+ type InternalAlias = {
2376+ _alias : true
2377+ /** The canonical command name this alias resolves to. */
2378+ target : string
2379+ }
2380+
2381+ /** @internal Type guard for alias entries. */
2382+ function isAlias ( entry : CommandEntry ) : entry is InternalAlias {
2383+ return '_alias' in entry
2384+ }
2385+
2386+ /** @internal Follows an alias entry to its canonical target. Returns the entry unchanged if not an alias. */
2387+ function resolveAlias (
2388+ commands : Map < string , CommandEntry > ,
2389+ entry : CommandEntry ,
2390+ ) : Exclude < CommandEntry , InternalAlias > {
2391+ if ( isAlias ( entry ) ) return commands . get ( entry . target ) ! as Exclude < CommandEntry , InternalAlias >
2392+ return entry
2393+ }
2394+
23532395/** @internal Maps CLI instances to their command maps. */
23542396export const toCommands = new WeakMap < Cli , Map < string , CommandEntry > > ( )
23552397
@@ -2672,6 +2714,7 @@ function collectIndexCommands(
26722714) : { name : string ; description ?: string | undefined } [ ] {
26732715 const result : { name : string ; description ?: string | undefined } [ ] = [ ]
26742716 for ( const [ name , entry ] of commands ) {
2717+ if ( isAlias ( entry ) ) continue
26752718 const path = [ ...prefix , name ]
26762719 if ( isGroup ( entry ) ) {
26772720 result . push ( ...collectIndexCommands ( entry . commands , path ) )
@@ -2706,6 +2749,7 @@ function collectCommands(
27062749} [ ] {
27072750 const result : ReturnType < typeof collectCommands > = [ ]
27082751 for ( const [ name , entry ] of commands ) {
2752+ if ( isAlias ( entry ) ) continue
27092753 const path = [ ...prefix , name ]
27102754 if ( isFetchGateway ( entry ) ) {
27112755 const cmd : ( typeof result ) [ number ] = { name : path . join ( ' ' ) }
@@ -2762,6 +2806,7 @@ function collectSkillCommands(
27622806 result . push ( cmd )
27632807 }
27642808 for ( const [ name , entry ] of commands ) {
2809+ if ( isAlias ( entry ) ) continue
27652810 const path = [ ...prefix , name ]
27662811 if ( isFetchGateway ( entry ) ) {
27672812 const cmd : Skill . CommandInfo = { name : path . join ( ' ' ) }
@@ -2931,6 +2976,8 @@ type CommandDefinition<
29312976 vars extends z . ZodObject < any > | undefined = undefined ,
29322977 cliEnv extends z . ZodObject < any > | undefined = undefined ,
29332978> = CommandMeta < options > & {
2979+ /** Alternative names for this command (e.g. `['extensions', 'ext']` for an `extension` command). */
2980+ aliases ?: string [ ] | undefined
29342981 /** Zod schema for positional arguments. */
29352982 args ?: args | undefined
29362983 /** Zod schema for environment variables. Keys are the variable names (e.g. `NPM_TOKEN`). */
0 commit comments