At my last job I wrote the CLI for deploying AI/ML models. I had never written a CLI before. It's not particularly difficult to do, but they are deceivingly difficult to get "just right". Since my time at that company I've been writing and deploying a lot of sites. Part of my goal of these projects is to be as low complexity/resource demanding as necessary as I'll be footing the server bill each month.

So I started demoing different providers and their offerings. GCP was nice but not ideal for my use case. I had already used AWS before. Then I stumbled upon Cloudflare - they offer free static site hosting that is entirely easier to set up compared to GCP. So I migrated my blog again and came up with a few static sites. You can deploy projects via GUI, or from the command line via wrangler, their CLI which is now a part of their "workers-sdk" repository1. It's written in Javascript, easy to install, and is strangely similar to the CLI I wrote. I was surprised to see it written in Javascript, with Python or Go being my first two choices. There are some bigger differences though. In hindsight I can see why Cloudflare's approach is better.

Auth and prompting the user

The first thing that stuck out was: if the user forgot to pass their token, launch an OAuth workflow via browser. All I had to do was log in to Cloudflare in my browser, with my username and password saved in the browser. This lets the user's browser handle the username and password concern, which is desirable.

$ npx wrangler pages deploy html/ --project-name myproject --skip-caching
Attempting to login via OAuth...
Opening a link in your default browser:[redacted]&redirect_uri=http%3A%2F%2Flocalhost%3A8976%2Foauth%2Fcallback&scope=account%3Aread%20user%3Aread%20workers%3Awrite%20workers_kv%3Awrite%20workers_routes%3Awrite%20workers_scripts%3Awrite%20workers_tail%3Aread%20d1%3Awrite%20pages%3Awrite%20zone%3Aread%20ssl_certs%3Awrite%20constellation%3Awrite%20offline_access&state=lcykA2LiHgli6Az1DFDjbquPHBXWRlnr&code_challenge=_9Jkt0gMzkwSfM-vy9xrSC-yy9y7fa0OYCf5zMYS1Pg&code_challenge_method=S256
Successfully logged in.
🌍  Uploading... (2/2)

✨ Success! Uploaded 2 files (0.74 sec)

✨ Deployment complete! Take a peek over at [redacted]

Seeing is knowing

Command line semantic consistency and visualizing the taxonomy2. I remember implementing a command that was not intuitive and having to refactor it. This could have been avoided by drawing out the planned command(s).

Looking for inspiration (and choosing the right one)

Whenever I faced a problem where I had no experience in, or had no intuition for, I would begin searching Github for code that solves a similar problem. I did consult Heroku's CLI, but I passed over it. Cloudflare took inspiration in the form of "colon-delimited command namespacing". I like how it makes painfully clear what the command is operating on. User proofing your interfaces is difficult.

Avoid logic that requires passing in raw ID strings

The third takeaway in the Cloudflare blog post I am guilty of: my delete command required the GUID of the deployment to delete. Being able to issue a command based on a name is better user experience than having to paste a long string into the command line. I myself am so used to doing this that it simply doesn't bother me anymore, and so I never thought of it. It's dangerous to assume that your experience will be the same as others!


Designing a CLI just right is more difficult than you would think: it needs to be intuitive for all users with a variety of computing backgrounds. It needs collaboration to do well. While I didn't write the perfect CLI, I did add some interesting features that most CLIs don't have: