Skip to content

Commit f95444a

Browse files
Mossakaclaude
andcommitted
feat(cli): organize help text with logical option groups
Group CLI flags into Domain Filtering, Image Management, Container Configuration, Network & Security, API Proxy, and Logging & Debug sections with visual section headers in --help output. Fixes #500 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 177e1f2 commit f95444a

File tree

1 file changed

+156
-69
lines changed

1 file changed

+156
-69
lines changed

src/cli.ts

Lines changed: 156 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
680699
const 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+
682712
program
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
'--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-
'--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
'--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>',
@@ -748,18 +823,20 @@ program
748823
)
749824
.option(
750825
'--image-tag <tag>',
751-
'Container image tag',
826+
'Container image tag (applied to --agent-image preset name, e.g. agent:<tag>)',
752827
'latest'
753828
)
754829
.option(
755830
'--skip-pull',
756-
'Use local images without pulling from registry (requires images to be pre-downloaded)',
831+
'Use local images without pulling from registry (requires pre-downloaded images)',
757832
false
758833
)
834+
835+
// -- Container Configuration --
759836
.option(
760837
'-e, --env <KEY=VALUE>',
761-
'Additional environment variables to pass to container (can be specified multiple times)',
762-
(value, previous: string[] = []) => [...previous, value],
838+
'Environment variable for the container (repeatable)',
839+
(value: string, previous: string[] = []) => [...previous, value],
763840
[]
764841
)
765842
.option(
@@ -769,74 +846,84 @@ program
769846
)
770847
.option(
771848
'-v, --mount <host_path:container_path[:mode]>',
772-
'Volume mount (can be specified multiple times). Format: host_path:container_path[:ro|rw]',
773-
(value, previous: string[] = []) => [...previous, value],
849+
'Volume mount (repeatable). Format: host_path:container_path[:ro|rw]',
850+
(value: string, previous: string[] = []) => [...previous, value],
774851
[]
775852
)
776853
.option(
777854
'--container-workdir <dir>',
778-
'Working directory inside the container (should match GITHUB_WORKSPACE for path consistency)'
855+
'Working directory inside the container'
779856
)
780857
.option(
781-
'--dns-servers <servers>',
782-
'Comma-separated list of trusted DNS servers. DNS traffic is ONLY allowed to these servers (default: 8.8.8.8,8.8.4.4)',
783-
'8.8.8.8,8.8.4.4'
858+
'--tty',
859+
'Allocate a pseudo-TTY (required for interactive tools like Claude Code)',
860+
false
784861
)
862+
863+
// -- Network & Security --
785864
.option(
786-
'--proxy-logs-dir <path>',
787-
'Directory to save Squid proxy logs to (writes access.log directly to this directory)'
865+
'--dns-servers <servers>',
866+
'Comma-separated trusted DNS servers',
867+
'8.8.8.8,8.8.4.4'
788868
)
789869
.option(
790870
'--enable-host-access',
791-
'Enable access to host services via host.docker.internal. ' +
792-
'Security warning: When combined with --allow-domains host.docker.internal, ' +
793-
'containers can access ANY service on the host machine.',
871+
'Enable access to host services via host.docker.internal',
794872
false
795873
)
796874
.option(
797875
'--allow-host-ports <ports>',
798-
'Comma-separated list of ports or port ranges to allow when using --enable-host-access. ' +
799-
'By default, only ports 80 and 443 are allowed. ' +
800-
'Example: --allow-host-ports 3000 or --allow-host-ports 3000,8080 or --allow-host-ports 3000-3010,8000-8090'
801-
)
802-
.option(
803-
'--ssl-bump',
804-
'Enable SSL Bump for HTTPS content inspection (allows URL path filtering for HTTPS)',
805-
false
806-
)
807-
.option(
808-
'--allow-urls <urls>',
809-
'Comma-separated list of allowed URL patterns for HTTPS (requires --ssl-bump).\n' +
810-
' Supports wildcards: https://github.com/myorg/*'
876+
'Ports/ranges to allow with --enable-host-access (default: 80,443).\n' +
877+
' Example: 3000,8080 or 3000-3010,8000-8090'
811878
)
879+
880+
// -- API Proxy --
812881
.option(
813882
'--enable-api-proxy',
814-
'Enable API proxy sidecar for holding authentication credentials.\n' +
815-
' Deploys a Node.js proxy that injects API keys securely.\n' +
816-
' Supports OpenAI (Codex) and Anthropic (Claude) APIs.',
883+
'Enable API proxy sidecar for secure credential injection.\n' +
884+
' Supports OpenAI (Codex) and Anthropic (Claude) APIs.',
817885
false
818886
)
819887
.option(
820888
'--copilot-api-target <host>',
821-
'Target hostname for GitHub Copilot API requests in the api-proxy sidecar.\n' +
822-
' Defaults to api.githubcopilot.com. Useful for GHES deployments.\n' +
823-
' Can also be set via COPILOT_API_TARGET env var.',
889+
'Target hostname for Copilot API requests (default: api.githubcopilot.com)',
824890
)
825891
.option(
826892
'--rate-limit-rpm <n>',
827-
'Enable rate limiting: max requests per minute per provider (requires --enable-api-proxy)',
893+
'Max requests per minute per provider (requires --enable-api-proxy)',
828894
)
829895
.option(
830896
'--rate-limit-rph <n>',
831-
'Enable rate limiting: max requests per hour per provider (requires --enable-api-proxy)',
897+
'Max requests per hour per provider (requires --enable-api-proxy)',
832898
)
833899
.option(
834900
'--rate-limit-bytes-pm <n>',
835-
'Enable rate limiting: max request bytes per minute per provider (requires --enable-api-proxy)',
901+
'Max request bytes per minute per provider (requires --enable-api-proxy)',
836902
)
837903
.option(
838904
'--no-rate-limit',
839-
'Explicitly disable rate limiting in the API proxy (requires --enable-api-proxy)',
905+
'Disable rate limiting in the API proxy (requires --enable-api-proxy)',
906+
)
907+
908+
// -- Logging & Debug --
909+
.option(
910+
'--log-level <level>',
911+
'Log level: debug, info, warn, error',
912+
'info'
913+
)
914+
.option(
915+
'--keep-containers',
916+
'Keep containers running after command exits',
917+
false
918+
)
919+
.option(
920+
'--work-dir <dir>',
921+
'Working directory for temporary files',
922+
path.join(os.tmpdir(), `awf-${Date.now()}`)
923+
)
924+
.option(
925+
'--proxy-logs-dir <path>',
926+
'Directory to save Squid proxy access.log'
840927
)
841928
.argument('[args...]', 'Command and arguments to execute (use -- to separate from options)')
842929
.action(async (args: string[], options) => {

0 commit comments

Comments
 (0)