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

> Explore how croc's cli.go parses command-line arguments using a layered architecture with global flags, subcommands, and intelligent routing for send/receive operations.

- Repository: [Zack/croc](https://github.com/schollz/croc)
- Tags: deep-dive
- Published: 2026-07-26

---

**The [`cli.go`](https://github.com/schollz/croc/blob/main/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`](https://github.com/schollz/croc/blob/main/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`](https://github.com/schollz/croc/blob/main/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.

```go
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

```go
{
    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`](https://github.com/schollz/croc/blob/main/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:**

```bash
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:**

```bash
croc secret123

```

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

**Custom relay initialization:**

```bash
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`](https://github.com/schollz/croc/blob/main/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`](https://github.com/schollz/croc/blob/main/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`](https://github.com/schollz/croc/blob/main/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`](https://github.com/schollz/croc/blob/main/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.