Skip to content

Quickstart

In this tutorial, we will build hello, a small CLI that greets people by name and can also deploy a service. Along the way, we will see how a plain Go struct becomes flags, help output, environment fallbacks, and subcommand routing. Every step ends in a runnable program.

Terminal window
mkdir hello && cd hello
go mod init example.com/hello
go get github.com/conga-sh/conga

Write main.go. Every application is a plain Go struct — field types and struct tags form the CLI schema:

package main
import (
"context"
"fmt"
"strings"
"github.com/conga-sh/conga"
)
type CLI struct {
Name conga.OptionalFlag[string] `default:"World" help:"Name of the person to greet" short:"n"`
Loud conga.OptionalFlag[bool] `help:"Print greeting in uppercase" short:"l"`
}
func (c *CLI) Run(ctx context.Context, session *conga.Session) error {
greeting := fmt.Sprintf("Hello, %s!", c.Name.Get())
if c.Loud.Get() {
greeting = strings.ToUpper(greeting)
}
fmt.Fprintln(session.Stdout(), greeting)
return nil
}
func main() {
app := conga.New[CLI]().
Name("hello").
Description("Minimal single-command hello world CLI").
Version("1.0.0").
EnableWhy()
app.FatalIfError(app.Run())
}

Run it:

Terminal window
$ go run . --name conga --loud
HELLO, CONGA!

Name becomes --name/-n with a default of World; Loud becomes --loud/-l. Run is the command body: it reads resolved values with Get() and writes to the session’s configured output.

Every application gets --help and --version for free; app.EnableWhy() adds the --why flag, which prints where each value came from (CLI, env, config, or default):

Terminal window
$ go run . --help
$ go run . --version
hello has version 1.0.0 built with go1.27.1
$ go run . --why

The version line appends the commit and build timestamp when the binary carries them.

The help screen is derived from the struct, and typos get suggestions: go run . --nmae x answers unknown flag --nmae (did you mean --name?).

Give the app an environment namespace so --name falls back to $HELLO_NAME:

app := conga.New[CLI]().
Name("hello").
EnvPrefix("HELLO").
Version("1.0.0")
Terminal window
$ HELLO_NAME=conga go run .
Hello, conga!

The resolution ladder — CLI > environment > config file > default — is explained in About value precedence.

Add a command field to the root struct and a command struct with its own Run method:

type CLI struct {
Name conga.OptionalFlag[string] `default:"World" help:"Name of the person to greet" short:"n"`
Loud conga.OptionalFlag[bool] `help:"Print greeting in uppercase" short:"l"`
Deploy conga.Command[DeployCommand] `name:"deploy" summary:"Deploy a service"`
}
type DeployCommand struct {
Service conga.RequiredArg[string] `help:"Service name"`
Replicas conga.OptionalFlag[int] `default:"3" help:"Number of replicas"`
}
func (d *DeployCommand) Run(ctx context.Context, session *conga.Session) error {
fmt.Fprintf(session.Stdout(), "deploying %s (replicas: %d)\n", d.Service.Get(), d.Replicas.Get())
return nil
}
Terminal window
$ go run . deploy web-api --replicas 5
deploying web-api (replicas: 5)
package main
import (
"context"
"fmt"
"strings"
"github.com/conga-sh/conga"
)
type CLI struct {
Name conga.OptionalFlag[string] `default:"World" help:"Name of the person to greet" short:"n"`
Loud conga.OptionalFlag[bool] `help:"Print greeting in uppercase" short:"l"`
Deploy conga.Command[DeployCommand] `name:"deploy" summary:"Deploy a service"`
}
func (c *CLI) Run(ctx context.Context, session *conga.Session) error {
greeting := fmt.Sprintf("Hello, %s!", c.Name.Get())
if c.Loud.Get() {
greeting = strings.ToUpper(greeting)
}
fmt.Fprintln(session.Stdout(), greeting)
return nil
}
type DeployCommand struct {
Service conga.RequiredArg[string] `help:"Service name"`
Replicas conga.OptionalFlag[int] `default:"3" help:"Number of replicas"`
}
func (d *DeployCommand) Run(ctx context.Context, session *conga.Session) error {
fmt.Fprintf(session.Stdout(), "deploying %s (replicas: %d)\n", d.Service.Get(), d.Replicas.Get())
return nil
}
func main() {
app := conga.New[CLI]().
Name("hello").
Description("Minimal single-command hello world CLI").
EnvPrefix("HELLO").
Version("1.0.0")
app.FatalIfError(app.Run())
}