Exoskeleton is a library for creating modern multi-CLI applications whose commands are external to them.
Exoskeleton is similar to frameworks like Cobra and Oclif in that it simplifies building subcommand-based command line applications (like git clone
, git log
) by providing:
- nested subcommands and automatically generated menus of subcommands
- tab-completion for shells
- suggestions for mistyped commands ("Did you mean ...?")
Exoskeleton differs from other CLI frameworks in that each subcommand maps to a separate, standalone executable. This allows subcommands to be implemented in different languages and released on different schedules!
Exoskeleton's goal is to create a common entrypoint, better discoverability, and a cohesive experience for a decentralized suite of commandline tools.
Install Exoskeleton by running
go get -u github.com/square/exoskeleton@latest
The simplest Exoskeleton application looks like this:
package main
import (
"os"
"github.com/square/exit"
"github.com/square/exoskeleton"
)
func main() {
paths := []string{
// paths
// you want Exoskeleton to search
// for subcommands
}
// 1. Create a new Command Line application that looks for subcommands in the given paths.
cli, _ := exoskeleton.New(paths)
// 2. Identify the subcommand being invoked from the arguments.
cmd, args := cli.Identify(os.Args[1:])
// 3. Execute the subcommand.
err := cmd.Exec(cli, args, os.Environ())
// 4. Exit the program with the exit code the subcommand returned.
os.Exit(exit.FromError(err))
}
① An exoskeleton is constructed with an array of paths to search for subcommands. ② It uses the arguments to identify which subcommand is being invoked. ③ The subcommand is executed ④ and the program exits (0
if err
is nil
, 1
or a semantic exit code otherwise).
To see this in action, take a look at the the Hello World example project.
In the real world, an application might also:
- Customize the exoskeleton by passing options to
exoskeleton.New
- Add business logic between ②
Identify
and ③Exec
or ③Exec
and ④os.Exit
Tip
At Square, we use the OnCommandNotFound callback to install subcommands on-demand, check for updates after constructing the exoskeleton, and wrap Exec
to emit usage metrics.
Subcommands can be either shell scripts or binaries.
- They MUST be executable.
- They SHOULD output help text to be displayed when the user invokes them with
--help
. - They MAY respond to
--summary
by outputting a summary of their purpose to be displayed in a menu of commands. - They MAY respond to
--complete <input>
by outputting a list of shell-completions for<input>
.
Compiled binaries should parse their arguments for the --help
and --summary
flags. They should do this early in execution before any expensive set up, write the text to standard out, and exit successfully.
Shell scripts which start with the shebang (#!
) may respond to --help
and --summary
flags or may choose to document themselves with magic comments (# HELP: <help text follows>
, # SUMMARY: <summary line follows>
). See examples/hello_world/libexec/ls and examples/hello_world/libexec/rm for examples.
Exoskeleton uses shellcomp (the API that Cobra developed) to separate shell-specific logic for implementing completions from the logic for producing the suggestions themselves.
Shellcomp scripts invoke exoskeleton with --complete <WHATEVER THE USER TYPED>
and expect to receive a list of suggestions on standard output.
Note
Imagine git
is implemented with Exoskeleton.
If the user types
$ git che<tab>
then the shellcomp scripts will execute:
$ git complete che
and Exoskeleton will suggest completions from the list of commands it knows and output:
checkout
:4
and the shellcomp scripts will complete the command git checkout
.
If the user types
$ git checkout lail/<tab>
then the shellcomp scripts will execute:
$ git complete checkout lail/
and Exoskeleton will dispatch the completion to the checkout
command, executing this:
$ $(git which checkout) --complete -- checkout lail/
and, at this point, if git checkout
handles --complete
, it may list branches that start with lail/
.
See shellcomp's docs for implementing completions for a subcommand.
Call exoskeleton.GenerateCompletionScript to generate the shellcomp scripts for your project.
Tip
At Square, we call this function in our Makefile
and distribute artifacts for Bash and Zsh with releases.
As each subcommand maps to a standalone executable, submenus map to directories.
- The directory MUST contain a file named
.exoskeleton
(the file name is configurable).
Take a look at the dir
module in the Hello World example project.