clk

A very opinionated framework to ease the creation of command line interfaces

View on GitHub

When you work on linked but separate projects — say a backend API and a frontend app — changes in one often need to be verified in the other. If they live in separate directories, you end up constantly cd-ing back and forth, losing context along the way.

clk projects scope their commands to directories: aliases, parameters and scripts only activate when you’re inside the project. That’s great for isolation, but painful when you need to jump between two or three repos in a single workflow.

The problem

Let’s set up two projects to see the problem in action.

mkdir -p billing-api/.clk
cd billing-api
clk alias set build echo "Building the API"
New local alias for build: echo 'Building the API'
clk alias set test echo "Running API tests"
New local alias for test: echo 'Running API tests'
cd ..
mkdir -p billing-app/.clk
cd billing-app
clk alias set build echo "Building the frontend"
New local alias for build: echo 'Building the frontend'
clk alias set test echo "Running frontend tests"
New local alias for test: echo 'Running frontend tests'

Each project works fine on its own. But the moment you’re inside billing-app and want to rebuild the API, the command isn’t there.

clk build 2>/dev/null
Building the frontend

You only see the frontend’s build. To reach the API, you’d need --project.

clk --project ../billing-api build 2>/dev/null
Building the API

That works, but typing clk --project ../billing-api every time you switch context gets old fast — especially when you’re going back and forth several times in a row.

Global aliases

This is where an alias to the root command helps. You can create a global alias that points to clk itself, with the --project option baked in. Do this once for each project.

cd ..
clk alias set api clk --project ./billing-api
New global alias for api: clk --project ./billing-api
clk alias set app clk --project ./billing-app
New global alias for app: clk --project ./billing-app

Now every command from both projects is available from anywhere, under api and app. No more cd-ing around.

clk api build 2>/dev/null
Building the API
clk app build 2>/dev/null
Building the frontend
clk api test 2>/dev/null
Running API tests
clk app test 2>/dev/null
Running frontend tests

You changed an endpoint in the API? Rebuild it, then run the frontend tests — all without leaving your terminal.

Subgroups and introspection commands work too. You can inspect either project’s aliases at any time.

clk api alias show build 2>/dev/null
build echo Building the API
-------------
Legend: local

Ask one of them for help and clk says where it really comes from, so you know which of the two to go and edit.

clk api build --help 2>/dev/null | grep -A1 "This is a sub command"
This is a sub command of 'api' that is an alias towards 'clk'. To edit it, try getting help from both of them or from
the subcommand of the original group (something like `clk build --help`)

Local aliases

The global approach is convenient but pollutes your top-level namespace. If you only need cross-project shortcuts when you’re actually working inside one of the projects, local aliases are a cleaner fit. Each project declares its own shortcut to the sibling, and nothing leaks outside.

First, let’s remove the global aliases.

clk alias unset api
Erasing api alias from global settings
clk alias unset app
Erasing app alias from global settings

Now, inside billing-api, create a local alias that points to the frontend.

cd billing-api
clk alias set app clk --project ../billing-app
New local alias for app: clk --project ../billing-app
cd ..

And inside billing-app, create the reverse shortcut.

cd billing-app
clk alias set api clk --project ../billing-api
New local alias for api: clk --project ../billing-api

From inside billing-app, you can now reach the API the same way as before.

clk api build 2>/dev/null
Building the API
clk api test 2>/dev/null
Running API tests
cd ..

And from inside billing-api, you reach the frontend.

cd billing-api
clk app build 2>/dev/null
Building the frontend
clk app test 2>/dev/null
Running frontend tests

The aliases only exist inside their respective projects, so they won’t clutter your global namespace or show up in unrelated directories.

promoting an alias to the global profile

An alias you end up wanting everywhere need not be typed again elsewhere. From billing-api, clk alias move carries build to the global profile as it stands.

clk alias move build global
Moved alias build, local -> global

It still answers here, now from the global profile rather than the local one.

clk build
Building the API

And billing-app keeps the build of its own, which still wins over the one we just made global.

cd ../billing-app
clk build
Building the frontend

tidying up as the aliases pile up

Made in a hurry, aliases pile up and say nothing about themselves. Here is one more, still in billing-app, standing on the two you already have.

clk alias set ship build , test
New local alias for ship: build , test

Were you setting things up from a script, you would rather it kept quiet. It still does the work, it just stops saying so.

clk --quiet alias set ship-nightly build , test
clk alias show ship-nightly
ship-nightly build, test
-------------
Legend: local

Asked for its help, it can only repeat itself.

clk ship --help | head -3
Usage: clk ship [OPTIONS] [MESSAGE]...

  Alias for: build , test

clk alias set-documentation gives it something better to say.

clk alias set-documentation ship "Build and test the frontend"
clk ship --help | head -3
Usage: clk ship [OPTIONS] [MESSAGE]...

  Build and test the frontend

Before a release you want the API in as well. Copying ship gives you something to start from, that you are then free to let drift.

clk alias copy ship ship-all
Copied alias ship -> ship-all in local
clk alias append ship-all api build , api test

Renaming is not only about the alias itself: the aliases that call it follow. It reaches further than you asked for, so you may want to watch it happen before it does. --dry-run says what it would do and writes nothing.

clk --dry-run alias rename test test-front
Would have moved alias test -> test-front in local
clk alias show
api clk --project ../billing-api
build echo Building the frontend
ship build, test
ship-all build, test, api build, api test
ship-nightly build, test
test echo Running frontend tests
-------------
Legend: local

Nothing moved, so now do it for real.

clk alias rename test test-front
Moved alias test -> test-front in local

Both ship and ship-all were built on test, and now call test-front without your having to say so.

clk alias show
api clk --project ../billing-api
build echo Building the frontend
ship build, test-front
ship-all build, test-front, api build, api test
ship-nightly build, test-front
test-front echo Running frontend tests
-------------
Legend: local

Had one of them lived in a profile clk cannot write to, it would have warned you that the old name is still used there, for you to correct by hand.

Not every name will do, though. Start one with a dash and your shell would hand it to clk as an option, so clk turns it down before you get there.

clk alias set -ship build
Usage: clk alias set [OPTIONS] ALIAS COMMAND [PARAMS]...
error: Aliases must not start with dashes (-)

Begin it with punctuation instead and it tells you what a name may start with.

clk alias set ,ship build
error: Invalid alias name: ,ship. An alias must start with a letter, a digit or an underscore

You see every alias from here, but you only write to one profile at a time. Put one in the global profile and then forget where it lives.

clk alias --global set deploy-prod echo Deploying to production
clk alias set-documentation deploy-prod "Ship to production"
error: The profile local has no 'deploy-prod' alias registered. Try using another profile option (like --local or --global)

Dropping it from here gets the same answer.

clk alias unset deploy-prod
error: The profile local has no alias named 'deploy-prod'. Try using another profile option (like --local or --global)