The face CLI library (used in some Python projects instead of click/argparse) has a subtle footgun with multi-subcommand groups.
The single-subcommand pattern works:
seo_cmd = Command(None, name='seo', doc='SEO commands')
seo_cmd.add(keywords_func, name='keywords', doc='...')
seo_cmd.add('xlsx_file', parse_as=str, doc='...') # positional
seo_cmd.add('--verbose', parse_as=True, missing=False)But adding a second subcommand this way makes xlsx_file required for ALL subcommands (positional args are group-level). The fix is the sub-Command pattern:
seo_cmd = Command(None, name='seo', doc='SEO commands')
keywords_sub = Command(keywords_func, name='keywords', doc='...')
keywords_sub.add('xlsx_file', parse_as=str, doc='...')
keywords_sub.add('--verbose', parse_as=True, missing=False)
seo_cmd.add(keywords_sub)
research_sub = Command(research_func, name='research', doc='...')
research_sub.add('--seeds', parse_as=str, missing='seeds.tsv')
seo_cmd.add(research_sub)Each sub-Command owns its own args. The parent group is just a namespace. This matches how other face command groups (e.g. gtm_cmd with train/baseline/test subcommands) are structured in practice.