@@ -44,10 +44,16 @@ export function withGenerateDocumentationArgs<T extends ParsedArgs>(
4444 type : 'string' ,
4545 description :
4646 'Specifies the `tsconfig` used when loading typescript based CLIs.' ,
47+ } )
48+ . option ( 'llms' , {
49+ type : 'boolean' ,
50+ description :
51+ 'Generate an llms.txt file describing the CLI for AI agents.' ,
52+ default : true ,
4753 } ) ;
4854}
4955
50- export const generateDocumentationCommand : CLI < any , any , any > = cli ( 'generate-documentation' , {
56+ export const generateDocumentationCommand = cli ( 'generate-documentation' , {
5157 description : 'Generate documentation for the given CLI' ,
5258 examples : [
5359 'cli-forge generate-documentation ./bin/my-cli' ,
@@ -70,6 +76,10 @@ export const generateDocumentationCommand: CLI<any, any, any> = cli('generate-do
7076 ensureDirSync ( outdir ) ;
7177 writeFileSync ( outfile , JSON . stringify ( documentation , null , 2 ) ) ;
7278 }
79+
80+ if ( args . llms ) {
81+ generateLlmsTxt ( documentation , args ) ;
82+ }
7383 } ,
7484} ) ;
7585
@@ -81,6 +91,156 @@ async function generateMarkdownDocumentation(
8191 await generateMarkdownForSingleCommand ( docs , args . output , args . output , md ) ;
8292}
8393
94+ function generateLlmsTxt ( docs : Documentation , args : GenerateDocsArgs ) {
95+ const content = generateLlmsTxtContent ( docs ) ;
96+ const outfile = join ( args . output , 'llms.txt' ) ;
97+ ensureDirSync ( args . output ) ;
98+ writeFileSync ( outfile , content ) ;
99+ }
100+
101+ function generateLlmsTxtContent (
102+ docs : Documentation ,
103+ depth = 0 ,
104+ commandPath : string [ ] = [ ]
105+ ) : string {
106+ const lines : string [ ] = [ ] ;
107+ const indent = ' ' . repeat ( depth ) ;
108+ const currentPath = [ ...commandPath , docs . name ] ;
109+ const fullCommand = currentPath . join ( ' ' ) ;
110+
111+ // Command header
112+ if ( depth === 0 ) {
113+ lines . push ( `# ${ docs . name } ` ) ;
114+ lines . push ( '' ) ;
115+ if ( docs . description ) {
116+ lines . push ( docs . description ) ;
117+ lines . push ( '' ) ;
118+ }
119+ lines . push ( 'This document describes the CLI commands and options for AI agent consumption.' ) ;
120+ lines . push ( '' ) ;
121+ } else {
122+ lines . push ( `${ indent } ## ${ fullCommand } ` ) ;
123+ if ( docs . description ) {
124+ lines . push ( `${ indent } ${ docs . description } ` ) ;
125+ }
126+ lines . push ( '' ) ;
127+ }
128+
129+ // Usage
130+ lines . push ( `${ indent } Usage: ${ docs . usage } ` ) ;
131+ lines . push ( '' ) ;
132+
133+ // Positional arguments
134+ if ( docs . positionals . length > 0 ) {
135+ lines . push ( `${ indent } Positional Arguments:` ) ;
136+ for ( const pos of docs . positionals ) {
137+ const typeStr = formatOptionType ( pos ) ;
138+ const reqStr = pos . required ? ' (required)' : ' (optional)' ;
139+ lines . push ( `${ indent } <${ pos . key } > - ${ typeStr } ${ reqStr } ` ) ;
140+ if ( pos . description ) {
141+ lines . push ( `${ indent } ${ pos . description } ` ) ;
142+ }
143+ if ( pos . default !== undefined ) {
144+ lines . push ( `${ indent } Default: ${ JSON . stringify ( pos . default ) } ` ) ;
145+ }
146+ }
147+ lines . push ( '' ) ;
148+ }
149+
150+ // Options
151+ const optionEntries = Object . entries ( docs . options ) ;
152+ if ( optionEntries . length > 0 ) {
153+ lines . push ( `${ indent } Options:` ) ;
154+ for ( const [ , opt ] of optionEntries ) {
155+ const typeStr = formatOptionType ( opt ) ;
156+ const aliasStr = opt . alias ?. length
157+ ? ` (aliases: ${ opt . alias . map ( ( a ) => ( a . length === 1 ? `-${ a } ` : `--${ a } ` ) ) . join ( ', ' ) } )`
158+ : '' ;
159+ const reqStr =
160+ opt . required && opt . default === undefined ? ' [required]' : '' ;
161+ const deprecatedStr = opt . deprecated ? ' [deprecated]' : '' ;
162+ lines . push (
163+ `${ indent } --${ opt . key } ${ aliasStr } <${ typeStr } >${ reqStr } ${ deprecatedStr } `
164+ ) ;
165+ if ( opt . description ) {
166+ lines . push ( `${ indent } ${ opt . description } ` ) ;
167+ }
168+ if ( opt . default !== undefined ) {
169+ lines . push ( `${ indent } Default: ${ JSON . stringify ( opt . default ) } ` ) ;
170+ }
171+ if ( 'choices' in opt && opt . choices ) {
172+ const choicesList =
173+ typeof opt . choices === 'function' ? opt . choices ( ) : opt . choices ;
174+ lines . push ( `${ indent } Valid values: ${ choicesList . join ( ', ' ) } ` ) ;
175+ }
176+ }
177+ lines . push ( '' ) ;
178+ }
179+
180+ // Grouped options
181+ for ( const group of docs . groupedOptions ) {
182+ if ( group . keys . length > 0 ) {
183+ lines . push ( `${ indent } ${ group . label } :` ) ;
184+ for ( const opt of group . keys ) {
185+ const typeStr = formatOptionType ( opt ) ;
186+ const aliasStr = opt . alias ?. length
187+ ? ` (aliases: ${ opt . alias . map ( ( a ) => ( a . length === 1 ? `-${ a } ` : `--${ a } ` ) ) . join ( ', ' ) } )`
188+ : '' ;
189+ const reqStr =
190+ opt . required && opt . default === undefined ? ' [required]' : '' ;
191+ lines . push ( `${ indent } --${ opt . key } ${ aliasStr } <${ typeStr } >${ reqStr } ` ) ;
192+ if ( opt . description ) {
193+ lines . push ( `${ indent } ${ opt . description } ` ) ;
194+ }
195+ if ( opt . default !== undefined ) {
196+ lines . push ( `${ indent } Default: ${ JSON . stringify ( opt . default ) } ` ) ;
197+ }
198+ }
199+ lines . push ( '' ) ;
200+ }
201+ }
202+
203+ // Examples
204+ if ( docs . examples . length > 0 ) {
205+ lines . push ( `${ indent } Examples:` ) ;
206+ for ( const example of docs . examples ) {
207+ lines . push ( `${ indent } $ ${ example } ` ) ;
208+ }
209+ lines . push ( '' ) ;
210+ }
211+
212+ // Subcommands
213+ if ( docs . subcommands . length > 0 ) {
214+ lines . push ( `${ indent } Subcommands:` ) ;
215+ for ( const sub of docs . subcommands ) {
216+ lines . push (
217+ `${ indent } ${ sub . name } ${ sub . description ? ` - ${ sub . description } ` : '' } `
218+ ) ;
219+ }
220+ lines . push ( '' ) ;
221+
222+ // Recursively document subcommands
223+ for ( const sub of docs . subcommands ) {
224+ lines . push ( generateLlmsTxtContent ( sub , depth + 1 , currentPath ) ) ;
225+ }
226+ }
227+
228+ // Epilogue
229+ if ( docs . epilogue && depth === 0 ) {
230+ lines . push ( `Note: ${ docs . epilogue } ` ) ;
231+ lines . push ( '' ) ;
232+ }
233+
234+ return lines . join ( '\n' ) ;
235+ }
236+
237+ function formatOptionType ( opt : Documentation [ 'options' ] [ string ] ) : string {
238+ if ( 'items' in opt && opt . type === 'array' ) {
239+ return `${ opt . items } []` ;
240+ }
241+ return opt . type ;
242+ }
243+
84244async function generateMarkdownForSingleCommand (
85245 docs : Documentation ,
86246 out : string ,
@@ -367,7 +527,10 @@ async function loadCLIModule(
367527 } ) ;
368528 } else {
369529 const tsx = ( await import ( 'tsx/cjs/api' ) ) as typeof import ( 'tsx/cjs/api' ) ;
370- return tsx . require ( cliPath , join ( process . cwd ( ) , 'fake-file-for-require.ts' ) ) ;
530+ return tsx . require (
531+ cliPath ,
532+ join ( process . cwd ( ) , 'fake-file-for-require.ts' )
533+ ) ;
371534 }
372535 } catch {
373536 try {
0 commit comments