How `croc`'s `cli.go` Handles Command-Line Argument Parsing: A Deep Dive

The cli.go package in schollz/croc uses a fork of urfave/cli (github.com/schollz/cli/v2) to parse arguments through a layered architecture of global flags, subcommands, and a top-level action dispatcher that intelligently routes between send and receive operations.

The croc file transfer tool implements its command-line interface in src/cli/cli.go, where the Run function orchestrates argument parsing using a structured approach. This implementation leverages the github.com/schollz/cli/v2 library to transform raw command-line input into typed configuration objects that drive the secure file transfer logic.

The CLI Framework and Application Setup

At the heart of the argument parsing system is the Run function in src/cli/cli.go, which instantiates a cli.App struct from the underlying library. This object serves as the central registry for all command-line definitions.

app := cli.NewApp()
app.Name = "croc"
app.Version = Version               // see lines 30-42
app.Usage = "easily and securely transfer stuff from one computer to another"
app.UsageText = `croc [GLOBAL OPTIONS] [COMMAND] …`

The cli.NewApp() call initializes the root command object, with fields populated using static metadata including the application name, version string, and usage descriptions.

Global Flags and Subcommand Structure

The parsing strategy separates configuration into two distinct layers: global flags that apply to every invocation, and command-specific flags tied to individual subcommands.

Global flags are registered in app.Flags (lines 130-158) and include options like --classic, --debug, --quiet, and --relay. These are parsed automatically before any subcommand execution.

Subcommands are defined as cli.Command structs stored in app.Commands (lines 65-95). Each command contains:

  • Name — the invocable command name
  • Usage — help text description
  • ArgsUsage — argument display template
  • Flags — slice of cli.Flag objects for command-specific options
  • Action — the function executed when the command is invoked
{
    Name:  "send",
    Usage: "send file(s), or folder (see options with croc send -h)",
    Flags: []cli.Flag{
        &cli.BoolFlag{Name: "zip", Usage: "zip folder before sending"},
        &cli.StringFlag{Name: "code", Aliases: []string{"c"}, Usage: "codephrase used to connect to relay"},
        // … additional flags …
    },
    Action: send,
}

The Top-Level Action: Intelligent Routing Without Explicit Commands

When users invoke croc without an explicit subcommand, the library executes the anonymous function assigned to app.Action (lines 161-247). This dispatcher implements a heuristic-based routing system that determines whether the user intends to send or receive files.

The logic flows through several decision points:

  • Classic mode detection — toggles legacy insecure operation when --classic is present
  • File existence heuristic — inspects c.Args() to check if the first non-flag argument points to existing files; if true, prompts for confirmation to forward to the send action
  • Default receive path — calls receive(c) when no files are detected, treating arguments as connection codes

Access to raw arguments occurs through c.Args().First() (lines 692-693) or environment variables like CROC_SECRET (lines 523-556), allowing the receive function to extract transfer codes without explicit flag syntax.

Helper Functions for Complex Argument Processing

Several specialized functions in src/cli/cli.go handle complex transformation of parsed flags before they reach the core business logic:

  • parseRelayPorts (lines 219-229) — Normalizes the --ports flag for relay and serve commands by splitting comma-separated strings into string slices
  • determinePass (lines 298-306) — Reads the --pass flag with support for file path syntax, enabling password retrieval from files
  • setDebugLevel (lines 252-269) — Configures logger verbosity based on boolean states of --debug and --quiet flags

Each helper receives the populated cli.Context pointer (c *cli.Context), demonstrating tight coupling between the parsing library and application configuration.

Complete Execution Flow Example

The following examples illustrate how raw arguments traverse the parsing layers:

Explicit send with flags:

croc send --code secret123 --zip example-folder

The send action accesses parsed values via c.String("code") and c.Bool("zip") to populate a croc.Options struct (lines 332-395).

Implicit receive:

croc secret123

With no subcommand specified, app.Action executes and routes to receive(c), extracting secret123 from c.Args().First().

Custom relay initialization:

croc relay --host 0.0.0.0 --ports 9009,9010,9011

The relay action receives parsed flags, with parseRelayPorts transforming the port string into a slice for network initialization.

Summary

  • src/cli/cli.go uses github.com/schollz/cli/v2 to define the interface through cli.App initialization in the Run function
  • Global flags (lines 130-158) like --debug and --relay apply to all commands, while subcommand flags (lines 66-91) are specific to actions like send
  • The top-level action (lines 161-247) implements heuristic-based routing that distinguishes between sending and receiving without explicit command keywords
  • Helper functions process complex flag values (ports, passwords, debug levels) before passing them to the core transfer logic in src/croc/croc.go
  • All actions consume parsed arguments through the cli.Context object, accessing values via type-safe methods like c.String() and c.Bool()

Frequently Asked Questions

What CLI library does croc use for argument parsing?

croc uses github.com/schollz/cli/v2, a fork of the popular urfave/cli library. This dependency provides the cli.App struct and cli.Context type used throughout src/cli/cli.go to define commands, register flags, and dispatch actions based on parsed arguments.

How does croc determine whether to send or receive files without an explicit command?

When no subcommand is specified, the anonymous function assigned to app.Action (lines 161-247) executes a heuristic check on c.Args(). If the first argument points to existing files, it prompts for confirmation to send; otherwise, it defaults to the receive action, treating arguments as connection codes.

Where are the global command-line flags defined in the source code?

Global flags that apply to every croc invocation are defined in the app.Flags slice assignment located at lines 130-158 in src/cli/cli.go. These include boolean flags like --classic and --quiet, as well as string flags like --relay and --out.

How does croc handle comma-separated port ranges for relay commands?

The parseRelayPorts helper function (lines 219-229) processes the --ports flag by splitting the comma-separated string value into a slice of strings. This allows the relay and serve commands to accept port ranges like 9009,9010,9011 and convert them into usable network configuration parameters.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →