At Inferless I authored the CLI that developers use to deploy models and test them locally against Inferless-managed environments. A CLI is a product — its UX matters as much as any web app.

Principles I kept coming back to

  • Commands should read like sentences. inferless deploy and inferless run over flag soup.
  • Errors should tell you what to do next, not just what went wrong.
  • The happy path should be one command.

A taste of the interface

# log in once
inferless login
 
# scaffold, then deploy
inferless init my-model
inferless deploy --gpu A10
 
# test locally against a managed runtime
inferless run --input ./sample.json

Errors that respect the reader

Compare the difference a good message makes:

errors.py
class DeployError(Exception):
    def __init__(self, reason: str, hint: str | None = None):
        self.reason = reason
        self.hint = hint
        super().__init__(reason)
 
 
def require_gpu(gpu: str | None) -> str:
    if not gpu:
        raise DeployError(
            reason="No GPU specified for this model.",
            hint="Pass one with --gpu, e.g. `inferless deploy --gpu A10`.",  
        )
    return gpu

Rendered, that becomes:

✗ No GPU specified for this model.
  → Pass one with --gpu, e.g. `inferless deploy --gpu A10`.

The payoff

Good DX compounds. When the CLI is predictable and its docs match reality, developers self-serve, file fewer tickets, and trust the platform more. That trust is the whole game for a developer tool.