10 CLIs I Can Not Live Without!
Today we’ll explore ten CLI tools without which my life would be meaningless. Well… Maybe not meaningless, but certainly much less productive and harder.
So, without further ado, those are tend CLIs I use on a daily basis, and you should use them as well.
Setup
The setup and the demo is based on zsh
. Some modifications might be needed for other shells.
git clone https://github.com/vfarcic/clis-demo
cd clis-demo
Watch https://youtu.be/WiFLtcBvGMU if you are not familiar with Devbox. Alternatively, you can skip Devbox and install all the tools listed in
devbox.json
yourself.
devbox shell
zoxide add $PWD
zoxide add $PWD/.devbox/bin
zoxide add $PWD/.devbox/gen
zoxide add $PWD/.devbox/gen/flake
zoxide add $PWD/.devbox/nix
devbox add kind@0.23.0
yq --inplace ".metadata.annotations.something = \"else\"" \
k8s/app.yaml
eza: ls Replacement
The first tool is eza which is, effectivelly, a replacement for the ls baked in all shells.
With ls
, we can list files and directories in the current directory.
ls
The output is as follows.
Dockerfile-earthly cosign go.mod kind.yaml memory.go root.go timoni
Earthfile devbox.json go.sum kubeconfig.yaml okteto.yaml root_test.go tmp
README.md devbox.lock helm kustomize ping.go schemahero vendor
argo-cd.yaml fibonacci.go k8s main.go renovate.json shell.nix video.go
Here’s the output of eza
.
eza
The output is as follows.
argo-cd.yaml devbox.lock fibonacci.go helm kubeconfig.yaml memory.go README.md root_test.go timoni video.go
cosign Dockerfile-earthly go.mod k8s kustomize okteto.yaml renovate.json schemahero tmp
devbox.json Earthfile go.sum kind.yaml main.go ping.go root.go shell.nix vendor
I could not show colors in this blog post. I suggest you follow along in your terminal to see the output as they should be seen.
Now, that by itself might not be that impressive since the only notable difference is that eza output is colored. Actually, scratch that. Colors alone make it much easier to distinguish between different types of files and directories.
Nevertheless, there’s much much more eza offers.
We can, for example, display extended file metadata as a table and ensure that all files, including hidden ones, are listed.
eza --long --all
The output is as follows.
drwxr-xr-x - vfarcic 15 Jun 23:41 .git
.rw-r--r-- 449 vfarcic 15 Jun 23:41 argo-cd.yaml
drwxr-xr-x - vfarcic 15 Jun 23:41 cosign
.rw-r--r-- 419 vfarcic 15 Jun 23:41 devbox.json
.rw-r--r-- 23k vfarcic 15 Jun 23:41 devbox.lock
.rw-r--r-- 120 vfarcic 15 Jun 23:41 Dockerfile-earthly
.rw-r--r-- 2.3k vfarcic 15 Jun 23:41 Earthfile
.rw-r--r-- 543 vfarcic 15 Jun 23:41 fibonacci.go
.rw-r--r-- 1.8k vfarcic 15 Jun 23:41 go.mod
.rw-r--r-- 25k vfarcic 15 Jun 23:41 go.sum
drwxr-xr-x - vfarcic 15 Jun 23:41 helm
drwxr-xr-x - vfarcic 15 Jun 23:41 k8s
.rw-r--r-- 411 vfarcic 15 Jun 23:41 kind.yaml
.rw-r--r-- 2.8k vfarcic 15 Jun 23:41 kubeconfig.yaml
drwxr-xr-x - vfarcic 15 Jun 23:41 kustomize
.rw-r--r-- 1.1k vfarcic 15 Jun 23:41 main.go
.rw-r--r-- 1.5k vfarcic 15 Jun 23:41 memory.go
.rw-r--r-- 365 vfarcic 15 Jun 23:41 okteto.yaml
.rw-r--r-- 706 vfarcic 15 Jun 23:41 ping.go
.rw-r--r-- 772 vfarcic 15 Jun 23:41 README.md
.rw-r--r-- 175 vfarcic 15 Jun 23:41 renovate.json
.rw-r--r-- 708 vfarcic 15 Jun 23:41 root.go
.rw-r--r-- 1.9k vfarcic 15 Jun 23:41 root_test.go
drwxr-xr-x - vfarcic 15 Jun 23:41 schemahero
.rw-r--r-- 223 vfarcic 15 Jun 23:41 shell.nix
drwxr-xr-x - vfarcic 15 Jun 23:41 timoni
drwxr-xr-x - vfarcic 15 Jun 23:41 tmp
drwxr-xr-x - vfarcic 15 Jun 23:41 vendor
.rw-r--r-- 2.6k vfarcic 15 Jun 23:41 video.go
That’s still very similar to what we’d get if we’d execute ls -la but nicely colored. So, let’s spice it up a bit more by executing eza
as we did before but by hiding permissions (--no-permissions
), file sizes (--no-filesize
), user (--no-user
), and time (--no-time
), and adding information from Git.
eza --long --all --no-permissions --no-filesize --no-user \
--no-time --git
The output is as follows.
-- .devbox
-I .git
-- argo-cd.yaml
-- cosign
-M devbox.json
-M devbox.lock
-- Dockerfile-earthly
-- Earthfile
-- fibonacci.go
-- go.mod
-- go.sum
-- helm
-M k8s
-- kind.yaml
-- kubeconfig.yaml
-- kustomize
-- main.go
-- memory.go
-- okteto.yaml
-- ping.go
-- README.md
-- renovate.json
-- root.go
-- root_test.go
-- schemahero
-- shell.nix
-- timoni
-- tmp
-- vendor
-- video.go
We have a clean colored output with the files and directories and also Git statuses of each of them. We can easily see that devbox.json
and devbox.lock
are modified.
Eza has a massive number of parameters we can use to customize the output.
eza --help
I won’t go through all of them. That’s something you can explore on your own. The only thing I’ll add is that my brain is wired to type ls
instead of eza
and, if yours is as well, you might want to create an alias in .zshrc
or .bashrc
or whichever shell you’re using.
echo "alias ls='eza --long --all --no-permissions --no-filesize \
--no-user --no-time --git'" | tee -a ~/.zshrc
source ~/.zshrc
As a result, every time you execute ls
as you would normally do, you’ll get a nicer and more useful default output.
ls
The output is as follows.
-- .devbox
-I .git
-- argo-cd.yaml
-- cosign
-M devbox.json
-M devbox.lock
-- Dockerfile-earthly
-- Earthfile
-- fibonacci.go
-- go.mod
-- go.sum
-- helm
-M k8s
-- kind.yaml
-- kubeconfig.yaml
-- kustomize
-- main.go
-- memory.go
-- okteto.yaml
-- ping.go
-- README.md
-- renovate.json
-- root.go
-- root_test.go
-- schemahero
-- shell.nix
-- timoni
-- tmp
-- vendor
-- video.go
From there on, we can add additional arguments depending on what we’re trying to do like, for example, show all files and directories in a tree-like structure.
ls --tree
The output is as follows (truncated for brevity).
...
-- │ ├── gopkg.in
-- │ │ └── yaml.v3
-- │ │ ├── apic.go
-- │ │ ├── decode.go
-- │ │ ├── emitterc.go
-- │ │ ├── encode.go
-- │ │ ├── LICENSE
-- │ │ ├── NOTICE
-- │ │ ├── parserc.go
-- │ │ ├── readerc.go
-- │ │ ├── README.md
-- │ │ ├── resolve.go
-- │ │ ├── scannerc.go
-- │ │ ├── sorter.go
-- │ │ ├── writerc.go
-- │ │ ├── yaml.go
-- │ │ ├── yamlh.go
-- │ │ └── yamlprivateh.go
-- │ ├── mellium.im
-- │ │ └── sasl
-- │ │ ├── .gitignore
-- │ │ ├── CHANGELOG.md
-- │ │ ├── DCO
-- │ │ ├── doc.go
-- │ │ ├── LICENSE
-- │ │ ├── mechanism.go
-- │ │ ├── negotiator.go
-- │ │ ├── nonce.go
-- │ │ ├── options.go
-- │ │ ├── plain.go
-- │ │ ├── README.md
-- │ │ ├── scram.go
-- │ │ ├── xor.go
-- │ │ ├── xor_amd64.go
-- │ │ ├── xor_amd64.s
-- │ │ ├── xor_arm64.go
-- │ │ ├── xor_arm64.s
-- │ │ ├── xor_generic.go
-- │ │ ├── xor_go.go
-- │ │ ├── xor_ppc64x.go
-- │ │ └── xor_ppc64x.s
-- │ └── modules.txt
-- └── video.go
That was too much so let me limit the depth to two levels.
ls --tree --level 2
The output is as follows (truncated for brevity).
...
-- ├── timoni
-- │ ├── cue.mod
-- │ ├── go.mod
-- │ ├── go.sum
-- │ ├── templates
-- │ ├── test_tool.cue
-- │ ├── test_values.cue
-- │ ├── timoni.cue
-- │ ├── timoni.ignore
-- │ ├── update-version.sh
-- │ ├── values-db-aws.yaml
-- │ ├── values-db-cnpg-otel.yaml
-- │ ├── values-db-cnpg.yaml
-- │ ├── values-db.yaml
-- │ ├── values-dev.yaml
-- │ ├── values-otel.yaml
-- │ ├── values.cue
-- │ └── values.yaml
-- ├── tmp
-- ├── vendor
-- │ ├── github.com
-- │ ├── golang.org
-- │ ├── google.golang.org
-- │ ├── gopkg.in
-- │ ├── mellium.im
-- │ └── modules.txt
-- └── video.go
That makes more sense. I can easily see up to two levels of files and directories in a tree-like structure.
Let’s move onto the second CLI.
bat: cat With Syntaz Highlighting
The next CLI is also a replacement of a familiar command.
If we want to output contents of a file, we execute cat
.
cat k8s/app.yaml
The output is as follows (truncated for brevity).
...
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
annotations:
description: This is a silly demo
language: Go
owner: Viktor Farcic (viktor@farcic.com)
team: dot
labels:
app.kubernetes.io/name: silly-demo
name: silly-demo
spec:
ingressClassName: traefik
rules:
- host: sillydemo.127.0.0.1.nip.io
http:
paths:
- backend:
service:
name: silly-demo
port:
number: 8080
path: /
pathType: ImplementationSpecific
---
bat provides a similar functionality but with syntax highlightning, Git integration, and quite a few other things.
Here’s the output of the same YAML file.
bat k8s/app.yaml
The output is as follows (truncated for brevity).
───────┬─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
│ File: k8s/app.yaml
───────┼─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
1 │ apiVersion: apps/v1
2 │ kind: Deployment
3 _ │ metadata:
4 │ labels:
5 │ app.kubernetes.io/name: silly-demo
6 │ name: silly-demo
7 + │ annotations:
8 + │ something: else
9 │ spec:
10 │ replicas: 2
11 │ selector:
12 │ matchLabels:
13 │ app.kubernetes.io/name: silly-demo
14 │ template:
15 │ metadata:
16 │ labels:
17 │ app.kubernetes.io/name: silly-demo
18 │ spec:
19 │ shareProcessNamespace: true
20 │ containers:
21 ~ │ - image: ghcr.io/vfarcic/silly-demo:1.4.117
22 ~ │ livenessProbe:
23 ~ │ httpGet:
24 ~ │ path: /
25 ~ │ port: 8080
26 ~ │ name: silly-demo
27 ~ │ ports:
28 ~ │ - containerPort: 8080
───────┴─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
We can see that it is now colored, there are line numbers, and, as it’s the case of lines 7 and 8, we can see which ones changed when compared to what is in Git (_
, +
, and ~
).
From here on, we can customize it by, for example, removing pagination (--paging never
), and applying a theme (--theme DarkNeon
) and a style (--style
).
bat --paging never --theme DarkNeon --style plain k8s/app.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app.kubernetes.io/name: silly-demo
name: silly-demo
annotations:
something: else
spec:
replicas: 2
selector:
matchLabels:
app.kubernetes.io/name: silly-demo
template:
metadata:
labels:
app.kubernetes.io/name: silly-demo
spec:
shareProcessNamespace: true
containers:
- image: ghcr.io/vfarcic/silly-demo:1.4.117
livenessProbe:
httpGet:
path: /
port: 8080
name: silly-demo
ports:
- containerPort: 8080
Just as with eza
, we can create an alias in .zshrc
or .bashrc
or whichever shell you’re using.
echo "alias cat='bat --paging never --theme DarkNeon \
--style plain'" | tee -a ~/.zshrc
source ~/.zshrc
From now on, we can continue exercising our muscle memory by typing cat
but getting a much nicer output.
cat main.go
The output is as follows (truncated for brevity).
package main
import (
"fmt"
"log"
"log/slog"
"net/http"
"os"
"github.com/gin-gonic/gin"
)
func main() {
log.SetOutput(os.Stderr)
if os.Getenv("DEBUG") == "true" {
slog.SetLogLoggerLevel(slog.LevelDebug)
}
if os.Getenv("MEMORY_LEAK_MAX_MEMORY") != "" {
go func() { memoryLeak(0, 0) }()
}
...
}
...
We got a nicely formatted Go code. Brilliant!
Let’s move to the third CLI which, unlike the previous two, is not a replacement of an existing command but something completely different.
fzf: Command-Line Fuzzy Finder
fzf is a general-purpose command-line fuzzy finder. To explain it in simpler terms, it allows us to list and search files.
Here it goes.
fzf
Use ↑ and ↓ to select a file. Type
yaml
to narrow down the search. Selectkind.yaml
and press theenter
key to output it.
The output is the list of all files in the current directory and all subdirectories.
From here on, we can use arrows up and down to navigate through the list. We can also narrow down the output by typing a part of the file name.
So, if we type yaml
, we’ll see only files that contain that string.
Once we find the file we’re interested in, we can press enter
to output it.
We can also choose to select multiple files through the --multi
argument.
fzf --multi
Use tab to select multiple files. Press
enter
to output them.
From here on, we can use tab to select any number of files and output them all by pressing enter
.
Now, outputting one or more file names might not be that interesting. The power of fzf lies in combining it with other commands.
For example, we can use it with bat
to preview the contents of the selected file.
fzf --preview 'bat --style numbers --color always {}'
Use ↑ and ↓ to select a file and see the preview. Press
enter
to output the name of the file.
Now we can navigate through the list of files and instantly preview any of them. Since that preview is done by bat
, it is nicely colored and formatted.
Since it would be inpractical to try to remember such a long command, we should probably create an alias in .zshrc
or .bashrc
or whichever shell you’re using.
echo "alias fzfp='fzf --preview \"bat --style numbers \
--color always {}\"'" | tee -a ~/.zshrc
source ~/.zshrc
Now we can preview files through the alias fzfp
.
fzfp
Press
enter
.
zoxide: Smarter cd Command
The next in line is zoxide which is a better version of cd
command. Actually, better would be an understatement. It is much much better than cd
.
To use it, we’ll add zoxide init
command to .zshrc
or .bashrc
or whichever shell you’re using.
It will, effectively, replace cd
with zoxide
.
echo 'eval "$(zoxide init --cmd cd zsh)"' | tee -a ~/.zshrc
source ~/.zshrc
Let’s take a look at the files and, more importantly, the directories we have.
ls
The output is as follows.
-- .devbox
-I .git
-- argo-cd.yaml
-- cosign
-M devbox.json
-M devbox.lock
-- Dockerfile-earthly
-- Earthfile
-- fibonacci.go
-- go.mod
-- go.sum
-- helm
-M k8s
-- kind.yaml
-- kubeconfig.yaml
-- kustomize
-- main.go
-- memory.go
-- okteto.yaml
-- ping.go
-- README.md
-- renovate.json
-- root.go
-- root_test.go
-- schemahero
-- shell.nix
-- timoni
-- tmp
-- vendor
-- video.go
Now, let’s say that we’d like to go to the flake directory which is inside the gen directory which is inside .devbox
. Typically, we would need to execute something like cd .devbox/gen/flake.
With zoxide, which is now replacing cd, we can do it by simply telling it to cd
to flake
.
cd flake
Based on our navigation history, it figured out that we want to go to .devbox/gen/flake
and executed the equivalent of cd .devbox/gen/flake. We are now three directories deep without having to type a single slash.
Now, let’s say that we’d like to go back to the clis-demo directory which is three lavels below the current directory. Instead of typing cd ../../../, we can simply tell zoxide to go to lis
, press the space
key, and then tab
to autocomplete.
cd lis
Make sure that there is space at the end. Press
tab
to autocomplete.
It figured out that lis is the substring of clis-demo
and took us there.
In cases there are multiple directories in the history that contain the same substring it would give us the list of all those that match the substring and let us choose where to go.
zoxide alone saves a lot of time. I stopped thinking where is what since all I have to do is type a few letters of the directory I want to go and zoxide takes me there no matter where that directory is, as long as I visited it at least once before.
The Fuck: Error Corrections
To demonstrate the next command, we will first create a KinD cluster.
kid create cluster
The output is as follows.
zsh: command not found: kid
Fuck! That’s the word I would utter every time I make a mistake like that. I typed kid
instead of kind.
Here’s the thing. Instead of yelling fuck! before typing the command again, we can just type fuck.
fuck
Press ↑ and ↓ to change suggestions. Press
enter
after seleting thekind
command.
The output is as follows.
✗ fuck
kind create cluster [enter/↑/↓/ctrl+c]
Fuck gives us what would normally come after yelling “Fuck!” It gives us a list of suggestions which command we should have executed instead.
More often than not, the first suggested command is the correct one, but, if it’s not, we can see other suggestions by pressing arrow keys up and down. Once we find the command we should have executed, all the have to do is press the enter key.
jq: sed for Json
Commands we execute often output Json, YAML, TOML, or some other format. If those would be files, we would format them with bat but, unfortunately, bat tends to have difficulties working with output since it uses file extensions to figure out what to display. More over, we often need not only to format but also filter outputs.
Here’s an example.
kubectl get namespace kube-system --output json
The output is as follows.
{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": {
"creationTimestamp": "2024-06-09T01:21:20Z",
"labels": {
"kubernetes.io/metadata.name": "kube-system"
},
"name": "kube-system",
"resourceVersion": "5",
"uid": "6f178028-fda4-41d9-a2c9-0ad5b8fe6803"
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Active"
}
}
While that output is correct, there is no syntax highlighting. On top of that, many commands do not provide a way to filter outputs while those that do, like kubectl, often use some silly syntax that is hard to remember.
That’s where jq comes into play, at least when Json is concerned.
We can, for example, take the previous command, and pipe the output to jq
to format it.
kubectl get namespace kube-system --output json | jq .
The output is as follows.
{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": {
"creationTimestamp": "2024-06-09T01:21:20Z",
"labels": {
"kubernetes.io/metadata.name": "kube-system"
},
"name": "kube-system",
"resourceVersion": "5",
"uid": "6f178028-fda4-41d9-a2c9-0ad5b8fe6803"
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Active"
}
}
That is much easier to read.
We can also filter the output to, let’s say, retrieve the .status.phase
field.
kubectl get namespace kube-system --output json \
| jq ".status.phase"
The output is as follows.
"Active"
yq: Like jq But For YAML
The next in line is yq which is just like jq but for YAML. Even the syntax is almost the same so we can, for example, output namespaces to yaml
and pipe it to yq
to format it.
kubectl get namespace kube-system --output yaml \
| yq .
The output is as follows.
apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: "2024-06-09T01:21:20Z"
labels:
kubernetes.io/metadata.name: kube-system
name: kube-system
resourceVersion: "5"
uid: 6f178028-fda4-41d9-a2c9-0ad5b8fe6803
spec:
finalizers:
- kubernetes
status:
phase: Active
Similarly, we can also use it to filter the output so that, for example, only the .status.phase
field is returned.
kubectl get namespace kube-system --output yaml \
| yq ".status.phase"
The output is as follows.
Active
One notable difference is that yq
is not limited only to YAML. We can, for example, use json
as input and yaml
as output.
kubectl get namespace kube-system --output json \
| yq --input-format json
The output is as follows.
apiVersion: v1
kind: Namespace
metadata:
creationTimestamp: "2024-06-09T01:21:20Z"
labels:
kubernetes.io/metadata.name: kube-system
name: kube-system
resourceVersion: "5"
uid: 6f178028-fda4-41d9-a2c9-0ad5b8fe6803
spec:
finalizers:
- kubernetes
status:
phase: Active
Similarly, we can take YAML as input and output formatted JSON.
kubectl get namespace kube-system --output yaml \
| yq --output-format json .
The output is as follows.
{
"apiVersion": "v1",
"kind": "Namespace",
"metadata": {
"creationTimestamp": "2024-06-09T01:21:20Z",
"labels": {
"kubernetes.io/metadata.name": "kube-system"
},
"name": "kube-system",
"resourceVersion": "5",
"uid": "6f178028-fda4-41d9-a2c9-0ad5b8fe6803"
},
"spec": {
"finalizers": [
"kubernetes"
]
},
"status": {
"phase": "Active"
}
}
Effectively, yq is like jq but for YAML, but it can also replace jq since it can work with JSON as well. That means that we do not necessarily need jq. Nevertheless, I tend to use both. jq for JSON and yq for YAML. I’m aware that there is no need to jq but I’m so used to it that I keep using it.
Teller: Universal Secrets Manager
Then there is Teller. It is a universal secrets manager. I use it with almost every project I work in. If I need credentials for Kubernetes, or Azure, or AWS, or Google, or OpenAI, or GitHub tokens, or anything else, I add .teller.yml file to the project and in that file I point to whichever secrets store I use.
Here’s an example.
cat .teller.yml
The output is as follows.
project: dot
providers:
google_secretmanager:
env:
AZURE_OPENAI_API_VERSION:
path: projects/vfarcic/secrets/azure-openai-api-version/versions/1
AZURE_OPENAI_ENDPOINT:
path: projects/vfarcic/secrets/azure-openai-endpoint/versions/2
AZURE_OPENAI_API_KEY:
path: projects/vfarcic/secrets/azure-openai-key/versions/2
AZURE_OPENAI_MODEL:
path: projects/vfarcic/secrets/azure-openai-model/versions/2
YOUTUBE_API_KEY:
path: projects/vfarcic/secrets/youtube-api-key/versions/1
Over there I specified that the azure-openai-api-version
secret stored in my Google Secret Manager should be used as the AZURE_OPENAI_API_VERSION
environment variable. The same goes for azure-openai-endpoint
, azure-openai-key
, azure-openai-model
, and youtube-api-key
.
Actually, Teller is so convenient that I do not use it only for secrets but for any kind of environment variables, no matter whether they contain confidential values or not.
That file can be safely stored in Git and live side-by-side with the rest of the project. I or anyone else working with me on that project can instantly get all those credentials, as long as they have the access to that secrets store.
From there on, there are many different formats we can use to output those secrets.
For example, we can output them as environment variables which, in this case, I’ll pipe to teller redact
so that you don’t see them. I like you, especially if you subscribed, but I do not yet trust you.
teller env | teller redact
If you’re following along by executing commands, you’ll notice that
teller
will be failing because it’s configured to use my Google Cloud Secret Manager. You’ll have to make changes to.teller.yml
to make it work with whichever Secret Storage you might be using.
The output is as follows.
YOUTUBE_API_KEY=**REDACTED**
AZURE_OPENAI_MODEL=**REDACTED**
AZURE_OPENAI_ENDPOINT=**REDACTED**
AZURE_OPENAI_API_VERSION=**REDACTED**
AZURE_OPENAI_API_KEY=**REDACTED**
We can redirect the output to a configuration file like the one I use for Fabric.
teller env >.fabric
We can use it to scan the source code for secrets.
teller scan
The output is as follows.
[high] .fabric (1,16): found match for google_secretmanager/YOUTUBE_API_KEY (AI*****)
[high] .fabric (2,19): found match for google_secretmanager/AZURE_OPENAI_MODEL (gp*****)
[high] .fabric (3,22): found match for google_secretmanager/AZURE_OPENAI_ENDPOINT (ht*****)
[high] .fabric (4,25): found match for google_secretmanager/AZURE_OPENAI_API_VERSION (20*****)
[high] .fabric (5,21): found match for google_secretmanager/AZURE_OPENAI_API_KEY (fe*****)
It’s clear that .fabric
contains secrets and that I should not push it to git so I should either remove it or add it to .gitignore.
Teller is simple, yet it has quite a few different features that are very handy when working with confidential information either locally or in CI/CD pipelines.
If you’re interested in it, you might want to check the Secrets Made My Life Miserable - Consume Secrets Easily With Teller video for more details.
GitHub CLI (gh): GitHub To Your Terminal
The next in line is GitHub CLI or gh.
Even if you are using GitLab or if you are very unfortunate to be stuck with BitBucket, you still need to interact with GitHub, at least when working with open source projects. GitHub is part of everyone’s life, whether we like it or not.
GitHub CLI is mostly focused on features and capabilities missing in Git.
For example, if we would like to fork a repository, instead of opening GitHub in a web browser and start clicking buttons, we can simply execute gh repo fork
command,…
gh repo fork vfarcic/dotfiles --clone --remote
…and enter the clone of that repo.
cd dotfiles
If we would like to set the fork as the default remote repository, we can do that with gh repo set-default
.
gh repo set-default
If we are nostalgic and would like to see the repository in a web browser, we can do that with gh repo view
.
gh repo view --web
GitHub CLI is full of features. We can use it to create and manage pull requests, issues, and many other things. It’s a must-have for anyone working with GitHub which, effectively, means everyone.
Please watch GitHub CLI (gh) - How to manage repositories more efficiently if you’d like more details about it.
Let’s go back before we move onto the next CLI.
cd ..
Devbox: Isolated Shells
The last CLI I’d like to show you is Devbox. It is a tool we can use to create isolated shells or isolated environments. It is a wrapper around Nix Shell that makes it more user-friendly and easier to use. It is, potentially, the most important tool in my toolbox.
We are in a Devbox environment right now. We can see that by taking a look at teller
CLI we explored a few minutes ago.
which teller
The output is as follows.
/Users/vfarcic/code/clis-demo/.devbox/nix/profile/default/bin/teller
We can see that it is not a tool installed permanently on my machine but a tool that was installed specifically for this demo project. As a matter of fact, I do not have Teller on my machine at all. I have it only in this isolated environment.
I can prove that by going out of the Devbox Shell,…
exit
…and trying to locate teller
again.
which teller
The output is as follows.
teller not found
It’s nowhere to be found. It does not exist on my machine permanently but only in environments I create with Devbox.
Let’s start a new Devbox Shell,…
devbox shell
…and try to locate teller
again.
which teller
The output is as follows.
/Users/vfarcic/code/clis-demo/.devbox/nix/profile/default/bin/teller
Now it’s back.
Devbox allows me to specify all the tools I need for each individual project I work on and create isolated environments for each of them. That way, I can have different versions of the same tool in different projects without any conflicts. Also, anyone working with me on that project will have those tools as well.
All we have to do is specify the tools we need in devbox.json
file.
cat devbox.json
The output is as follows.
{
"$schema": "https://raw.githubusercontent.com/jetify-com/devbox/0.11.1/.schema/devbox.schema.json",
"packages": [
"eza@0.18.17",
"bat@0.24.0",
"fzf@0.53.0",
"zoxide@0.9.4",
"thefuck@3.32",
"jq@1.7.1",
"yq-go@4.44.1",
"teller@1.5.6",
"gh@2.50.0",
"gum@0.14.1",
"kind@0.23.0"
],
"shell": {
"init_hook": [],
"scripts": {}
}
}
If you’d like to explore Devbox in more depth, please watch the Nix for Everyone: Unleash Devbox for Simplified Development video.
Those are the ten must-have CLIs I use on daily basis. Which CLIs are your favorites? Please let me know in the comments. I’d love to know what you’re using and what I might be missing.
Thank you for watching. See you in the next one. Cheers.
Destroy
git stash
kind delete cluster
exit