# What are the current (late 2024) best practices for CLI development?

**URL:** https://discourse.julialang.org/t/what-are-the-current-late-2024-best-practices-for-cli-development/122013
**Category:** General Usage
**Tags:** package, cli
**Created:** [October 30, 2024, 6:01pm UTC](https://discourse.julialang.org/t/what-are-the-current-late-2024-best-practices-for-cli-development/122013 "2024-10-30T18:01:58Z")
**Posts on this page:** 4
**Page:** 1

<div class="post-metadata">

### Author: ![kevbonham](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kevbonham/32/216165_2.png) [@kevbonham](https://discourse.julialang.org/u/kevbonham)
#### Post date: [October 30, 2024, 6:01pm UTC](https://discourse.julialang.org/t/what-are-the-current-late-2024-best-practices-for-cli-development/122013/1 "2024-10-30T18:01:58Z")

</div>

Say someone is developing a package, and wants to include some command line interface. What are current best practices / ways to do this? My motivation here is twofold - I am actively working on such a project, and also would like to contribute to Modern Julia Workflows (cf. [this issue](https://github.com/modernjuliaworkflows/modernjuliaworkflows.github.io/issues/96)).

Ideally, this guidance would be

- Currently supported - that is something can can be used with currently released julia features (eg not relying on juliac / [Pkg support](https://github.com/JuliaLang/Pkg.jl/issues/1962))
- Generic - that is not reliant on specific frameworks (eg Comonicon.jl) or argument parsing libraries (eg ArgParse.jl, Docopt.jl)
- Have a clear upgrade path for when juliac / App support _does_ arrive.

Some things to consider:

- Code organization - CLI as a module included within package? Separate package? `bin/` directory inside package?
- Installation - How to make cli calls available to users / accessible via PATH, etc?
- Precompilation / sysimages - how to reduce the latency for users?
- Upgrade paths - assuming a user already has an installation, how to replace it / make sure the latest version is installed?

As a provocation to get things going, lets say we want to add a CLI for Example.jl. One way to do this (this is how I’m doing it in my current project) - we can create `Example.jl/bin/src/ExampleCLI.jl` that contains

```julia
module ExampleCLI

export main

using Example

function (@main)(ARGS)
    if first(ARGS) == "hello"
        println(hello(last(ARGS)))
    elseif first(ARGS) == "domath"
        num = parse(Int, last(ARGS))
        println(domath(num))
    else
        throw(ArgumentError("Command $(first(ARGS)) not supported"))
    end
    return 0
end

end

```

Then create `bin/Project.toml` with:

```toml
name = "ExampleCLI"
uuid = "9a0c4f26-9756-4254-a226-e60a319fd9cd"

[deps]
Example = "7876af07-990d-54b4-ab0e-23690620f79a"

[compat]
Example = "0.5.5"

```

And run

```sh
❯ julia --project=bin -e 'using Pkg; Pkg.instantiate()'

❯ julia --project=bin -e 'using ExampleCLI' domath 32
37

❯ julia --project=bin -e 'using ExampleCLI' hello world
Hello, world

```

So then, how would we precompile this or make a system image for it? How would we ask users to install it? How would we want to actually run it? Would you organize this differently?

Some related topics:

- [How to PackageCompile ArgParse - #6 by mattother](https://discourse.julialang.org/t/how-to-packagecompile-argparse/31994/6)
- [Tooling for Julia command-line scripts](https://discourse.julialang.org/t/tooling-for-julia-command-line-scripts/73915)
- [Organizing a project with a CLI - #2 by Tero\_Frondelius](https://discourse.julialang.org/t/organizing-a-project-with-a-cli/65005/2)
- [Creating a CLI app in Julia](https://discourse.julialang.org/t/creating-a-cli-app-in-julia/111160)
- [Command line interface for the Julia language - a CLI tool (with a GUI future)](https://discourse.julialang.org/t/command-line-interface-for-the-julia-language-a-cli-tool-with-a-gui-future/94388)
- [https://discourse.julialang.org/t/comonicon-jl-fast-simple-and-light-weight-cli-generator/4374](https://discourse.julialang.org/t/comonicon-jl-fast-simple-and-light-weight-cli-generator/4374)

---

<div class="post-metadata">

### Author: ![Eben60](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/eben60/32/13475_2.png) [@Eben60](https://discourse.julialang.org/u/Eben60)
#### Post date: [October 30, 2024, 7:02pm UTC](https://discourse.julialang.org/t/what-are-the-current-late-2024-best-practices-for-cli-development/122013/2 "2024-10-30T19:02:52Z")

</div>

> [@kevbonham](#):
>
> [Creating a CLI app in Julia](https://discourse.julialang.org/t/creating-a-cli-app-in-julia/111160)

See also the follow-up: [[ANN] YAArguParser and GivEmXL](https://discourse.julialang.org/t/ann-yaarguparser-and-givemxl/117343)

---

<div class="post-metadata">

### Author: ![kevbonham](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/kevbonham/32/216165_2.png) [@kevbonham](https://discourse.julialang.org/u/kevbonham)
#### Post date: [November 6, 2024, 11:41pm UTC](https://discourse.julialang.org/t/what-are-the-current-late-2024-best-practices-for-cli-development/122013/3 "2024-11-06T23:41:11Z")

</div>

OK, given the lack of responses here, I suppose I will just make stuff up 😛

In seriousness, I will take inspiration from the links above, plus two packages with CLIs from @fredrikekre, [GitHub - fredrikekre/jlpkg: A command line interface (CLI) for Pkg, Julia's package manager.](https://github.com/fredrikekre/jlpkg) and [GitHub - fredrikekre/Runic.jl: A code formatter for Julia with rules set in stone.](https://github.com/fredrikekre/Runic.jl/).

Preserving this from the slack hole, Fredrik said:

> The jlpkg Makefile is just for development purposes, there is `jlpkg.install` ([jlpkg/src/jlpkg.jl at 53fa79d1ea1547a9e7b14dded3d5742c43f05f82 · fredrikekre/jlpkg · GitHub](https://github.com/fredrikekre/jlpkg/blob/53fa79d1ea1547a9e7b14dded3d5742c43f05f82/src/jlpkg.jl#L30)) for putting the executable somewhere. It defaults to `.julia/bin` but nowadays, when most people(?) use juliaup, it might be better to use `.juliaup/bin` since that should already be in the users PATH.
> 
> The Makefile in Runic is just for building the executable with juliac and this is very experimental and doesn’t put the artifact in path. For Runic I tried something different and recommend `julia --project=@runic -m Runic` instead, possibly with a shell alias, see [GitHub - fredrikekre/Runic.jl: A code formatter for Julia with rules set in stone.](https://github.com/fredrikekre/Runic.jl?tab=readme-ov-file#cli)

@davidanthoff do you have any thoughts about things going in `~/.julia/juliaup/bin`? @tecosaur I know you had thoughts expressed in one of those long threads or issues. I’m sure I’ll find it again, but if you want to reiterate here I wouldn’t mind 😉

---

<div class="post-metadata">

### Author: ![tecosaur](https://sea2.discourse-cdn.com/julialang/user_avatar/discourse.julialang.org/tecosaur/32/23206_2.png) [@tecosaur](https://discourse.julialang.org/u/tecosaur)
#### Post date: [November 7, 2024, 3:12am UTC](https://discourse.julialang.org/t/what-are-the-current-late-2024-best-practices-for-cli-development/122013/4 "2024-11-07T03:12:28Z")

</div>

> [@kevbonham](#):
>
> @tecosaur I know you had thoughts expressed in one of those long threads or issues. I’m sure I’ll find it again, but if you want to reiterate here I wouldn’t mind 😉

Oh, I’ve just got my usual thoughts about it being nice to take advantage of user bin directories already on `$PATH` before creating new directories. I have an “app” package that does this by symlinking an executable artifact to `BaseDirs.User.bin("juliaclient")`:

> **[GitHub - tecosaur/DaemonConductor.jl: Run many times, compile once.](https://github.com/tecosaur/DaemonConductor.jl)**
>
> Run many times, compile once.

This isn’t a Julia-written executable, but I feel like this is somewhat general. Unfortunately, platforms are varying degrees of annoying to support with a `BaseDirs.User.bin("juliaclient")`-like form.

- Linux: excellent, `.local/bin` is part of multiple standards (XDG Base Directories, Systemd file-hierarchy spec, etc.)
- MacOS: Dodgy, there’s _no_ official option, but there are a few common choices for people who create a directory for this purpose (e.g. `~/bin`)
- Windows: Similar/worse than Mac, a few common choices but no standard (despite the massive number of known folders…)

David would know more, but I believe Windows does have good support for adding something to the path with an installer?
