@@ -677,54 +677,129 @@ export function parseVolumeMounts(mounts: string[]): ParseVolumeMountsResult | P
677677 return { success : true , mounts : result } ;
678678}
679679
680+ function formatItem (
681+ term : string ,
682+ description : string ,
683+ termWidth : number ,
684+ indent : number ,
685+ sep : number ,
686+ _helpWidth : number
687+ ) : string {
688+ const indentStr = ' '. repeat ( indent ) ;
689+ const fullWidth = termWidth + sep ;
690+ if ( description ) {
691+ if ( term . length < fullWidth - sep ) {
692+ return `${indentStr } ${term . padEnd ( fullWidth ) } ${description } `;
693+ }
694+ return `${indentStr } ${term } \n${' '. repeat ( indent + fullWidth ) } ${description } `;
695+ }
696+ return `${indentStr } ${term } `;
697+ }
698+
680699const program = new Command ( ) ;
681700
701+ // Option group markers used by the custom help formatter to insert section headers.
702+ // Each key is the long flag name of the first option in a group.
703+ const optionGroupHeaders : Record < string , string > = {
704+ 'allow - domains ': 'Domain Filtering :',
705+ 'build - local ': 'Image Management :',
706+ 'env ': 'Container Configuration :',
707+ 'dns - servers ': 'Network & Security :',
708+ 'enable - api - proxy ': 'API Proxy :',
709+ 'log - level ': 'Logging & Debug :',
710+ } ;
711+
682712program
683713 . name ( 'awf ')
684714 . description ( 'Network firewall for agentic workflows with domain whitelisting ')
685715 . version ( version )
716+ . configureHelp ( {
717+ formatHelp ( cmd , helper ) : string {
718+ const termWidth = helper . padWidth ( cmd , helper ) ;
719+ const helpWidth = ( helper as unknown as { helpWidth ?: number } ) . helpWidth ?? 80 ;
720+ const itemIndent = 2 ;
721+ const itemSep = 2 ;
722+
723+ const output : string [ ] = [ ] ;
724+
725+ // Usage line
726+ const usage = helper . commandUsage ( cmd ) ;
727+ output . push ( `Usage: ${ usage } ` ) ;
728+
729+ const desc = helper . commandDescription ( cmd ) ;
730+ if ( desc ) {
731+ output . push ( '' ) ;
732+ output . push ( desc ) ;
733+ }
734+
735+ // Arguments
736+ const args = helper . visibleArguments ( cmd ) ;
737+ if ( args . length > 0 ) {
738+ output . push ( '' ) ;
739+ output . push ( 'Arguments:' ) ;
740+ for ( const arg of args ) {
741+ const term = helper . argumentTerm ( arg ) ;
742+ const argDesc = helper . argumentDescription ( arg ) ;
743+ output . push ( formatItem ( term , argDesc , termWidth , itemIndent , itemSep , helpWidth ) ) ;
744+ }
745+ }
746+
747+ // Options with group headers
748+ const options = helper . visibleOptions ( cmd ) ;
749+ if ( options . length > 0 ) {
750+ output . push ( '' ) ;
751+ output . push ( 'Options:' ) ;
752+ for ( const opt of options ) {
753+ const flags = helper . optionTerm ( opt ) ;
754+ const optDesc = helper . optionDescription ( opt ) ;
755+ const longFlag = opt . long ?. replace ( / ^ - - / , '' ) ;
756+ if ( longFlag && optionGroupHeaders [ longFlag ] ) {
757+ output . push ( '' ) ;
758+ output . push ( ` ${ optionGroupHeaders [ longFlag ] } ` ) ;
759+ }
760+ output . push ( formatItem ( flags , optDesc , termWidth , itemIndent + 2 , itemSep , helpWidth ) ) ;
761+ }
762+ }
763+
764+ return output . join ( '\n' ) + '\n' ;
765+ }
766+ } )
767+
768+ // -- Domain Filtering --
686769 . option (
687770 '-d, --allow-domains <domains>' ,
688771 'Comma-separated list of allowed domains. Supports wildcards and protocol prefixes:\n' +
689- ' github.com - exact domain + subdomains (HTTP & HTTPS)\n' +
690- ' *.github.com - any subdomain of github.com\n' +
691- ' api-*.example.com - api-* subdomains\n' +
692- ' https://secure.com - HTTPS only\n' +
693- ' http://legacy.com - HTTP only\n' +
694- ' localhost - auto-configure for local testing (Playwright, etc.)'
772+ ' github.com - exact domain + subdomains (HTTP & HTTPS)\n' +
773+ ' *.github.com - any subdomain of github.com\n' +
774+ ' api-*.example.com - api-* subdomains\n' +
775+ ' https://secure.com - HTTPS only\n' +
776+ ' http://legacy.com - HTTP only\n' +
777+ ' localhost - auto-configure for local testing (Playwright, etc.)'
695778 )
696779 . option (
697780 '--allow-domains-file <path>' ,
698- 'Path to file containing allowed domains (one per line or comma-separated , supports # comments)'
781+ 'Path to file with allowed domains (one per line, supports # comments)'
699782 )
700783 . option (
701784 '--block-domains <domains>' ,
702- 'Comma-separated list of blocked domains (takes precedence over allowed domains ). Supports wildcards.'
785+ 'Comma-separated blocked domains (overrides allow list ). Supports wildcards.'
703786 )
704787 . option (
705788 '--block-domains-file <path>' ,
706- 'Path to file containing blocked domains (one per line or comma-separated , supports # comments)'
789+ 'Path to file with blocked domains (one per line, supports # comments)'
707790 )
708791 . option (
709- '--log-level <level>' ,
710- 'Log level: debug, info, warn, error' ,
711- 'info'
712- )
713- . option (
714- '-k, --keep-containers' ,
715- 'Keep containers running after command exits' ,
716- false
717- )
718- . option (
719- '--tty' ,
720- 'Allocate a pseudo-TTY for the container (required for interactive tools like Claude Code)' ,
792+ '--ssl-bump' ,
793+ 'Enable SSL Bump for HTTPS content inspection (allows URL path filtering)' ,
721794 false
722795 )
723796 . option (
724- '--work-dir <dir >' ,
725- 'Working directory for temporary files' ,
726- path . join ( os . tmpdir ( ) , `awf- ${ Date . now ( ) } ` )
797+ '--allow-urls <urls >' ,
798+ 'Comma-separated allowed URL patterns for HTTPS (requires --ssl-bump).\n' +
799+ ' Supports wildcards: https://github.com/myorg/*'
727800 )
801+
802+ // -- Image Management --
728803 . option (
729804 '-b, --build-local' ,
730805 'Build containers locally instead of using GHCR images' ,
@@ -733,13 +808,13 @@ program
733808 . option (
734809 '--agent-image <value>' ,
735810 'Agent container image (default: "default")\n' +
736- ' Presets (pre-built, fast):\n' +
737- ' default - Minimal ubuntu:22.04 (~200MB)\n' +
738- ' act - GitHub Actions parity (~2GB)\n' +
739- ' Custom base images (requires --build-local):\n' +
740- ' ubuntu:XX.XX\n' +
741- ' ghcr.io/catthehacker/ubuntu:runner-XX.XX\n' +
742- ' ghcr.io/catthehacker/ubuntu:full-XX.XX'
811+ ' Presets (pre-built, fast):\n' +
812+ ' default - Minimal ubuntu:22.04 (~200MB)\n' +
813+ ' act - GitHub Actions parity (~2GB)\n' +
814+ ' Custom base images (requires --build-local):\n' +
815+ ' ubuntu:XX.XX\n' +
816+ ' ghcr.io/catthehacker/ubuntu:runner-XX.XX\n' +
817+ ' ghcr.io/catthehacker/ubuntu:full-XX.XX'
743818 )
744819 . option (
745820 '--image-registry <registry>' ,
@@ -749,20 +824,22 @@ program
749824 . option (
750825 '--image-tag <tag>' ,
751826 'Container image tag (applies to both squid and agent images)\n' +
752- ' Image name varies by --agent-image preset:\n' +
753- ' default → agent:<tag>\n' +
754- ' act → agent-act:<tag>' ,
827+ ' Image name varies by --agent-image preset:\n' +
828+ ' default → agent:<tag>\n' +
829+ ' act → agent-act:<tag>' ,
755830 'latest'
756831 )
757832 . option (
758833 '--skip-pull' ,
759- 'Use local images without pulling from registry (requires images to be pre-downloaded)' ,
834+ 'Use local images without pulling from registry (requires pre-downloaded images )' ,
760835 false
761836 )
837+
838+ // -- Container Configuration --
762839 . option (
763840 '-e, --env <KEY=VALUE>' ,
764- 'Additional environment variables to pass to container (can be specified multiple times )' ,
765- ( value , previous : string [ ] = [ ] ) => [ ...previous , value ] ,
841+ 'Environment variable for the container (repeatable )' ,
842+ ( value : string , previous : string [ ] = [ ] ) => [ ...previous , value ] ,
766843 [ ]
767844 )
768845 . option (
@@ -772,74 +849,84 @@ program
772849 )
773850 . option (
774851 '-v, --mount <host_path:container_path[:mode]>' ,
775- 'Volume mount (can be specified multiple times ). Format: host_path:container_path[:ro|rw]' ,
776- ( value , previous : string [ ] = [ ] ) => [ ...previous , value ] ,
852+ 'Volume mount (repeatable ). Format: host_path:container_path[:ro|rw]' ,
853+ ( value : string , previous : string [ ] = [ ] ) => [ ...previous , value ] ,
777854 [ ]
778855 )
779856 . option (
780857 '--container-workdir <dir>' ,
781- 'Working directory inside the container (should match GITHUB_WORKSPACE for path consistency) '
858+ 'Working directory inside the container'
782859 )
783860 . option (
784- '--dns-servers <servers> ' ,
785- 'Comma-separated list of trusted DNS servers. DNS traffic is ONLY allowed to these servers (default: 8.8.8.8,8.8.4.4 )' ,
786- '8.8.8.8,8.8.4.4'
861+ '--tty ' ,
862+ 'Allocate a pseudo-TTY (required for interactive tools like Claude Code )' ,
863+ false
787864 )
865+
866+ // -- Network & Security --
788867 . option (
789- '--proxy-logs-dir <path>' ,
790- 'Directory to save Squid proxy logs to (writes access.log directly to this directory)'
868+ '--dns-servers <servers>' ,
869+ 'Comma-separated trusted DNS servers' ,
870+ '8.8.8.8,8.8.4.4'
791871 )
792872 . option (
793873 '--enable-host-access' ,
794- 'Enable access to host services via host.docker.internal. ' +
795- 'Security warning: When combined with --allow-domains host.docker.internal, ' +
796- 'containers can access ANY service on the host machine.' ,
874+ 'Enable access to host services via host.docker.internal' ,
797875 false
798876 )
799877 . option (
800878 '--allow-host-ports <ports>' ,
801- 'Comma-separated list of ports or port ranges to allow when using --enable-host-access. ' +
802- 'By default, only ports 80 and 443 are allowed. ' +
803- 'Example: --allow-host-ports 3000 or --allow-host-ports 3000,8080 or --allow-host-ports 3000-3010,8000-8090'
804- )
805- . option (
806- '--ssl-bump' ,
807- 'Enable SSL Bump for HTTPS content inspection (allows URL path filtering for HTTPS)' ,
808- false
809- )
810- . option (
811- '--allow-urls <urls>' ,
812- 'Comma-separated list of allowed URL patterns for HTTPS (requires --ssl-bump).\n' +
813- ' Supports wildcards: https://github.com/myorg/*'
879+ 'Ports/ranges to allow with --enable-host-access (default: 80,443).\n' +
880+ ' Example: 3000,8080 or 3000-3010,8000-8090'
814881 )
882+
883+ // -- API Proxy --
815884 . option (
816885 '--enable-api-proxy' ,
817- 'Enable API proxy sidecar for holding authentication credentials.\n' +
818- ' Deploys a Node.js proxy that injects API keys securely.\n' +
819- ' Supports OpenAI (Codex) and Anthropic (Claude) APIs.' ,
886+ 'Enable API proxy sidecar for secure credential injection.\n' +
887+ ' Supports OpenAI (Codex) and Anthropic (Claude) APIs.' ,
820888 false
821889 )
822890 . option (
823891 '--copilot-api-target <host>' ,
824- 'Target hostname for GitHub Copilot API requests in the api-proxy sidecar.\n' +
825- ' Defaults to api.githubcopilot.com. Useful for GHES deployments.\n' +
826- ' Can also be set via COPILOT_API_TARGET env var.' ,
892+ 'Target hostname for Copilot API requests (default: api.githubcopilot.com)' ,
827893 )
828894 . option (
829895 '--rate-limit-rpm <n>' ,
830- 'Enable rate limiting: max requests per minute per provider (requires --enable-api-proxy)' ,
896+ 'Max requests per minute per provider (requires --enable-api-proxy)' ,
831897 )
832898 . option (
833899 '--rate-limit-rph <n>' ,
834- 'Enable rate limiting: max requests per hour per provider (requires --enable-api-proxy)' ,
900+ 'Max requests per hour per provider (requires --enable-api-proxy)' ,
835901 )
836902 . option (
837903 '--rate-limit-bytes-pm <n>' ,
838- 'Enable rate limiting: max request bytes per minute per provider (requires --enable-api-proxy)' ,
904+ 'Max request bytes per minute per provider (requires --enable-api-proxy)' ,
839905 )
840906 . option (
841907 '--no-rate-limit' ,
842- 'Explicitly disable rate limiting in the API proxy (requires --enable-api-proxy)' ,
908+ 'Disable rate limiting in the API proxy (requires --enable-api-proxy)' ,
909+ )
910+
911+ // -- Logging & Debug --
912+ . option (
913+ '--log-level <level>' ,
914+ 'Log level: debug, info, warn, error' ,
915+ 'info'
916+ )
917+ . option (
918+ '-k, --keep-containers' ,
919+ 'Keep containers running after command exits' ,
920+ false
921+ )
922+ . option (
923+ '--work-dir <dir>' ,
924+ 'Working directory for temporary files' ,
925+ path . join ( os . tmpdir ( ) , `awf-${ Date . now ( ) } ` )
926+ )
927+ . option (
928+ '--proxy-logs-dir <path>' ,
929+ 'Directory to save Squid proxy access.log'
843930 )
844931 . argument ( '[args...]' , 'Command and arguments to execute (use -- to separate from options)' )
845932 . action ( async ( args : string [ ] , options ) => {
0 commit comments