@@ -694,54 +694,129 @@ export function parseVolumeMounts(mounts: string[]): ParseVolumeMountsResult | P
694694 return { success : true , mounts : result } ;
695695}
696696
697- const program = new Command ( ) ;
697+ export function formatItem (
698+ term : string ,
699+ description : string ,
700+ termWidth : number ,
701+ indent : number ,
702+ sep : number ,
703+ _helpWidth : number
704+ ) : string {
705+ const indentStr = ' '. repeat ( indent ) ;
706+ const fullWidth = termWidth + sep ;
707+ if ( description ) {
708+ if ( term . length < fullWidth - sep ) {
709+ return `${indentStr } ${term . padEnd ( fullWidth ) } ${description } `;
710+ }
711+ return `${indentStr } ${term } \n${' '. repeat ( indent + fullWidth ) } ${description } `;
712+ }
713+ return `${indentStr } ${term } `;
714+ }
715+
716+ export const program = new Command ( ) ;
717+
718+ // Option group markers used by the custom help formatter to insert section headers.
719+ // Each key is the long flag name of the first option in a group.
720+ const optionGroupHeaders : Record < string , string > = {
721+ 'allow - domains ': 'Domain Filtering :',
722+ 'build - local ': 'Image Management :',
723+ 'env ': 'Container Configuration :',
724+ 'dns - servers ': 'Network & Security :',
725+ 'enable - api - proxy ': 'API Proxy :',
726+ 'log - level ': 'Logging & Debug :',
727+ } ;
698728
699729program
700730 . name ( 'awf ')
701731 . description ( 'Network firewall for agentic workflows with domain whitelisting ')
702732 . version ( version )
733+ . configureHelp ( {
734+ formatHelp ( cmd , helper ) : string {
735+ const termWidth = helper . padWidth ( cmd , helper ) ;
736+ const helpWidth = ( helper as unknown as { helpWidth ?: number } ) . helpWidth ?? 80 ;
737+ const itemIndent = 2 ;
738+ const itemSep = 2 ;
739+
740+ const output : string [ ] = [ ] ;
741+
742+ // Usage line
743+ const usage = helper . commandUsage ( cmd ) ;
744+ output . push ( `Usage: ${ usage } ` ) ;
745+
746+ const desc = helper . commandDescription ( cmd ) ;
747+ if ( desc ) {
748+ output . push ( '' ) ;
749+ output . push ( desc ) ;
750+ }
751+
752+ // Arguments
753+ const args = helper . visibleArguments ( cmd ) ;
754+ if ( args . length > 0 ) {
755+ output . push ( '' ) ;
756+ output . push ( 'Arguments:' ) ;
757+ for ( const arg of args ) {
758+ const term = helper . argumentTerm ( arg ) ;
759+ const argDesc = helper . argumentDescription ( arg ) ;
760+ output . push ( formatItem ( term , argDesc , termWidth , itemIndent , itemSep , helpWidth ) ) ;
761+ }
762+ }
763+
764+ // Options with group headers
765+ const options = helper . visibleOptions ( cmd ) ;
766+ if ( options . length > 0 ) {
767+ output . push ( '' ) ;
768+ output . push ( 'Options:' ) ;
769+ for ( const opt of options ) {
770+ const flags = helper . optionTerm ( opt ) ;
771+ const optDesc = helper . optionDescription ( opt ) ;
772+ const longFlag = opt . long ?. replace ( / ^ - - / , '' ) ;
773+ if ( longFlag && optionGroupHeaders [ longFlag ] ) {
774+ output . push ( '' ) ;
775+ output . push ( ` ${ optionGroupHeaders [ longFlag ] } ` ) ;
776+ }
777+ output . push ( formatItem ( flags , optDesc , termWidth , itemIndent + 2 , itemSep , helpWidth ) ) ;
778+ }
779+ }
780+
781+ return output . join ( '\n' ) + '\n' ;
782+ }
783+ } )
784+
785+ // -- Domain Filtering --
703786 . option (
704787 '-d, --allow-domains <domains>' ,
705788 'Comma-separated list of allowed domains. Supports wildcards and protocol prefixes:\n' +
706- ' github.com - exact domain + subdomains (HTTP & HTTPS)\n' +
707- ' *.github.com - any subdomain of github.com\n' +
708- ' api-*.example.com - api-* subdomains\n' +
709- ' https://secure.com - HTTPS only\n' +
710- ' http://legacy.com - HTTP only\n' +
711- ' localhost - auto-configure for local testing (Playwright, etc.)'
789+ ' github.com - exact domain + subdomains (HTTP & HTTPS)\n' +
790+ ' *.github.com - any subdomain of github.com\n' +
791+ ' api-*.example.com - api-* subdomains\n' +
792+ ' https://secure.com - HTTPS only\n' +
793+ ' http://legacy.com - HTTP only\n' +
794+ ' localhost - auto-configure for local testing (Playwright, etc.)'
712795 )
713796 . option (
714797 '--allow-domains-file <path>' ,
715- 'Path to file containing allowed domains (one per line or comma-separated , supports # comments)'
798+ 'Path to file with allowed domains (one per line, supports # comments)'
716799 )
717800 . option (
718801 '--block-domains <domains>' ,
719- 'Comma-separated list of blocked domains (takes precedence over allowed domains ). Supports wildcards.'
802+ 'Comma-separated blocked domains (overrides allow list ). Supports wildcards.'
720803 )
721804 . option (
722805 '--block-domains-file <path>' ,
723- 'Path to file containing blocked domains (one per line or comma-separated , supports # comments)'
806+ 'Path to file with blocked domains (one per line, supports # comments)'
724807 )
725808 . option (
726- '--log-level <level>' ,
727- 'Log level: debug, info, warn, error' ,
728- 'info'
729- )
730- . option (
731- '-k, --keep-containers' ,
732- 'Keep containers running after command exits' ,
733- false
734- )
735- . option (
736- '--tty' ,
737- 'Allocate a pseudo-TTY for the container (required for interactive tools like Claude Code)' ,
809+ '--ssl-bump' ,
810+ 'Enable SSL Bump for HTTPS content inspection (allows URL path filtering)' ,
738811 false
739812 )
740813 . option (
741- '--work-dir <dir >' ,
742- 'Working directory for temporary files' ,
743- path . join ( os . tmpdir ( ) , `awf- ${ Date . now ( ) } ` )
814+ '--allow-urls <urls >' ,
815+ 'Comma-separated allowed URL patterns for HTTPS (requires --ssl-bump).\n' +
816+ ' Supports wildcards: https://github.com/myorg/*'
744817 )
818+
819+ // -- Image Management --
745820 . option (
746821 '-b, --build-local' ,
747822 'Build containers locally instead of using GHCR images' ,
@@ -750,13 +825,13 @@ program
750825 . option (
751826 '--agent-image <value>' ,
752827 'Agent container image (default: "default")\n' +
753- ' Presets (pre-built, fast):\n' +
754- ' default - Minimal ubuntu:22.04 (~200MB)\n' +
755- ' act - GitHub Actions parity (~2GB)\n' +
756- ' Custom base images (requires --build-local):\n' +
757- ' ubuntu:XX.XX\n' +
758- ' ghcr.io/catthehacker/ubuntu:runner-XX.XX\n' +
759- ' ghcr.io/catthehacker/ubuntu:full-XX.XX'
828+ ' Presets (pre-built, fast):\n' +
829+ ' default - Minimal ubuntu:22.04 (~200MB)\n' +
830+ ' act - GitHub Actions parity (~2GB)\n' +
831+ ' Custom base images (requires --build-local):\n' +
832+ ' ubuntu:XX.XX\n' +
833+ ' ghcr.io/catthehacker/ubuntu:runner-XX.XX\n' +
834+ ' ghcr.io/catthehacker/ubuntu:full-XX.XX'
760835 )
761836 . option (
762837 '--image-registry <registry>' ,
@@ -766,20 +841,22 @@ program
766841 . option (
767842 '--image-tag <tag>' ,
768843 'Container image tag (applies to both squid and agent images)\n' +
769- ' Image name varies by --agent-image preset:\n' +
770- ' default → agent:<tag>\n' +
771- ' act → agent-act:<tag>' ,
844+ ' Image name varies by --agent-image preset:\n' +
845+ ' default → agent:<tag>\n' +
846+ ' act → agent-act:<tag>' ,
772847 'latest'
773848 )
774849 . option (
775850 '--skip-pull' ,
776- 'Use local images without pulling from registry (requires images to be pre-downloaded)' ,
851+ 'Use local images without pulling from registry (requires pre-downloaded images )' ,
777852 false
778853 )
854+
855+ // -- Container Configuration --
779856 . option (
780857 '-e, --env <KEY=VALUE>' ,
781- 'Additional environment variables to pass to container (can be specified multiple times )' ,
782- ( value , previous : string [ ] = [ ] ) => [ ...previous , value ] ,
858+ 'Environment variable for the container (repeatable )' ,
859+ ( value : string , previous : string [ ] = [ ] ) => [ ...previous , value ] ,
783860 [ ]
784861 )
785862 . option (
@@ -789,79 +866,89 @@ program
789866 )
790867 . option (
791868 '-v, --mount <host_path:container_path[:mode]>' ,
792- 'Volume mount (can be specified multiple times ). Format: host_path:container_path[:ro|rw]' ,
793- ( value , previous : string [ ] = [ ] ) => [ ...previous , value ] ,
869+ 'Volume mount (repeatable ). Format: host_path:container_path[:ro|rw]' ,
870+ ( value : string , previous : string [ ] = [ ] ) => [ ...previous , value ] ,
794871 [ ]
795872 )
796873 . option (
797874 '--container-workdir <dir>' ,
798- 'Working directory inside the container (should match GITHUB_WORKSPACE for path consistency) '
875+ 'Working directory inside the container'
799876 )
800877 . option (
801878 '--memory-limit <limit>' ,
802879 'Memory limit for the agent container (e.g., 1g, 2g, 4g, 512m). Default: 2g' ,
803880 '2g'
804881 )
805882 . option (
806- '--dns-servers <servers> ' ,
807- 'Comma-separated list of trusted DNS servers. DNS traffic is ONLY allowed to these servers (default: 8.8.8.8,8.8.4.4 )' ,
808- '8.8.8.8,8.8.4.4'
883+ '--tty ' ,
884+ 'Allocate a pseudo-TTY (required for interactive tools like Claude Code )' ,
885+ false
809886 )
887+
888+ // -- Network & Security --
810889 . option (
811- '--proxy-logs-dir <path>' ,
812- 'Directory to save Squid proxy logs to (writes access.log directly to this directory)'
890+ '--dns-servers <servers>' ,
891+ 'Comma-separated trusted DNS servers' ,
892+ '8.8.8.8,8.8.4.4'
813893 )
814894 . option (
815895 '--enable-host-access' ,
816- 'Enable access to host services via host.docker.internal. ' +
817- 'Security warning: When combined with --allow-domains host.docker.internal, ' +
818- 'containers can access ANY service on the host machine.' ,
896+ 'Enable access to host services via host.docker.internal' ,
819897 false
820898 )
821899 . option (
822900 '--allow-host-ports <ports>' ,
823- 'Comma-separated list of ports or port ranges to allow when using --enable-host-access. ' +
824- 'By default, only ports 80 and 443 are allowed. ' +
825- 'Example: --allow-host-ports 3000 or --allow-host-ports 3000,8080 or --allow-host-ports 3000-3010,8000-8090'
826- )
827- . option (
828- '--ssl-bump' ,
829- 'Enable SSL Bump for HTTPS content inspection (allows URL path filtering for HTTPS)' ,
830- false
831- )
832- . option (
833- '--allow-urls <urls>' ,
834- 'Comma-separated list of allowed URL patterns for HTTPS (requires --ssl-bump).\n' +
835- ' Supports wildcards: https://github.com/myorg/*'
901+ 'Ports/ranges to allow with --enable-host-access (default: 80,443).\n' +
902+ ' Example: 3000,8080 or 3000-3010,8000-8090'
836903 )
904+
905+ // -- API Proxy --
837906 . option (
838907 '--enable-api-proxy' ,
839- 'Enable API proxy sidecar for holding authentication credentials.\n' +
840- ' Deploys a Node.js proxy that injects API keys securely.\n' +
841- ' Supports OpenAI (Codex) and Anthropic (Claude) APIs.' ,
908+ 'Enable API proxy sidecar for secure credential injection.\n' +
909+ ' Supports OpenAI (Codex) and Anthropic (Claude) APIs.' ,
842910 false
843911 )
844912 . option (
845913 '--copilot-api-target <host>' ,
846- 'Target hostname for GitHub Copilot API requests in the api-proxy sidecar.\n' +
847- ' Defaults to api.githubcopilot.com. Useful for GHES deployments.\n' +
848- ' Can also be set via COPILOT_API_TARGET env var.' ,
914+ 'Target hostname for Copilot API requests (default: api.githubcopilot.com)' ,
849915 )
850916 . option (
851917 '--rate-limit-rpm <n>' ,
852- 'Enable rate limiting: max requests per minute per provider (requires --enable-api-proxy)' ,
918+ 'Max requests per minute per provider (requires --enable-api-proxy)' ,
853919 )
854920 . option (
855921 '--rate-limit-rph <n>' ,
856- 'Enable rate limiting: max requests per hour per provider (requires --enable-api-proxy)' ,
922+ 'Max requests per hour per provider (requires --enable-api-proxy)' ,
857923 )
858924 . option (
859925 '--rate-limit-bytes-pm <n>' ,
860- 'Enable rate limiting: max request bytes per minute per provider (requires --enable-api-proxy)' ,
926+ 'Max request bytes per minute per provider (requires --enable-api-proxy)' ,
861927 )
862928 . option (
863929 '--no-rate-limit' ,
864- 'Explicitly disable rate limiting in the API proxy (requires --enable-api-proxy)' ,
930+ 'Disable rate limiting in the API proxy (requires --enable-api-proxy)' ,
931+ )
932+
933+ // -- Logging & Debug --
934+ . option (
935+ '--log-level <level>' ,
936+ 'Log level: debug, info, warn, error' ,
937+ 'info'
938+ )
939+ . option (
940+ '-k, --keep-containers' ,
941+ 'Keep containers running after command exits' ,
942+ false
943+ )
944+ . option (
945+ '--work-dir <dir>' ,
946+ 'Working directory for temporary files' ,
947+ path . join ( os . tmpdir ( ) , `awf-${ Date . now ( ) } ` )
948+ )
949+ . option (
950+ '--proxy-logs-dir <path>' ,
951+ 'Directory to save Squid proxy access.log'
865952 )
866953 . argument ( '[args...]' , 'Command and arguments to execute (use -- to separate from options)' )
867954 . action ( async ( args : string [ ] , options ) => {
0 commit comments