Skip to content

Pitch: modernize CLI command structure (separator, project context, verb consistency) #117

@emanuelefaja

Description

@emanuelefaja

Problem

The CLI works, but the command structure has accumulated inconsistencies that make it feel dated next to peers (gh, kubectl, aws, flyctl). Three issues stand out:

  1. : separator between topic and command (projects:add, env:set, postgres:databases:add) is a Heroku-CLI-era convention. Modern CLIs use spaces. The colon also occasionally trips users who mistake it for shell syntax, and reads worse the deeper you nest.
  2. --project is required on every project-scoped command. No notion of "current project". Users type --project mysite for logs, env:set, env:list, deploy, run, scale:set, domains:add, volumes:list — every invocation. Pure friction.
  3. Inconsistent argument/verb conventions. Subject is sometimes a positional, sometimes a flag. Verbs vary across topics (set/get/list vs add/remove/list vs set/unset/login/logout). Some destructive commands have --no-input, most don't.

None of these are bugs. They're polish issues that compound: every new command inherits the existing patterns and the gap with modern CLIs widens.

Audit

Separator

Disco today:

disco projects:add --name foo --github user/repo
disco postgres:databases:add --instance main
disco env:set API_KEY=xxx --project foo

Peers:

gh pr create
kubectl get pods
aws s3 ls
flyctl deploy

oclif supports both via topicSeparator. Switching is one config line plus per-command aliases to keep the old form working.

Subject placement (positional vs flag)

Command Subject form
projects:remove <name> positional ✓
projects:add --name <name> flag ✗
projects:move --project <name> flag ✗
domains:add <domain> --project <p> mixed
nodes:remove <name> positional ✓
apikeys:remove <key> positional ✓
discos:remove <name> positional ✓

Convention: primary subject positional, scope (project, disco) as flag. Outliers: projects:add and projects:move.

Required --project everywhere

logs, env:set, env:list, env:get, env:remove, deploy, run, scale:set, scale:get, domains:add, domains:list, domains:remove, volumes:list, volumes:export, volumes:import — all 15+ commands take --project and most require it.

Verb consistency

Topic Verbs
env get / set / list / remove
scale get / set (no list, no remove)
registries set / unset / login / logout / list
domains add / list / remove
volumes export / import / list
projects add / list / move / remove
apikeys list / remove

Three different verb vocabularies (CRUD-ish, add/remove, login-style).

Other inconsistencies

  • deploy is a top-level verb, but deploy:list / deploy:cancel / deploy:output treat it as a noun. disco deploy (action) and disco deploy:list (noun's subcommand) live side by side.
  • projects:move means "transfer to another disco". The name suggests in-place rename. Becomes a footgun once projects:rename lands (separately scoped, see daemon repo issue Project deployment failed #101).
  • registries:set (default registry) and registries:login (creds) share a topic but are unrelated concepts. set is easily misread.
  • --no-input exists on projects:remove and volumes:export only. Other destructive commands (apikeys:remove, domains:remove, discos:remove, nodes:remove) lack it — either no confirm at all or no scriptable bypass.
  • postgres:databases:add is three levels deep where two would do.

Proposed approach — three tracks, ship independently

Track 1 — separator (shippable in one PR)

Switch topicSeparator from ":" to " ". Register the colon form as an alias on every command:

export default class ProjectsAdd extends Command {
  static aliases = ['projects:add']
  // ...
}

Optionally print a one-line deprecation notice on the colon form, removable in v1.0. No user breakage.

Track 2 — project context (biggest UX win)

Resolve --project in this order:

  1. Explicit --project flag (current behavior)
  2. DISCO_PROJECT env var
  3. disco.json in cwd (file already exists for deploys — natural anchor)
  4. disco use <project> writes a default into local CLI config

After this, the common case becomes:

$ cd ~/code/myblog
$ disco logs
$ disco env set API_KEY=xxx
$ disco deploy

instead of --project myblog on every line. Add to every command currently taking --project via a shared resolver in config.ts.

Track 3 — naming/verb cleanup (v1.0 polish)

Each as a small focused PR with aliases for the old name:

  • projects moveprojects transfer (alias the old). Avoids future collision with projects rename.
  • deploy:list / deploy:cancel / deploy:output → new deployments topic. Keep bare disco deploy as the action shortcut.
  • registries setregistries default (alias old).
  • scale topic: add scale list to round out CRUD.
  • Standardize destructive flag: pick --yes (matches gh, apt, helm) and add to apikeys remove, domains remove, discos remove, nodes remove. Add confirmation prompts where missing.
  • Move projects add --name X to projects add X (positional). Alias the flag form.
  • Flatten postgres:databases:addpostgres database add (or similar) where nesting earns nothing.

Risk

  • Aliases keep old forms working — no scripts break.
  • README/docs regenerationscripts/generate-readme.js will pick up new help text, but examples in marketing pages and tutorials need a manual sweep.
  • Tab completion@oclif/plugin-autocomplete is already installed; it regenerates per release.
  • Track 2 + disco.json lookup: must handle the case where the project named in cwd's disco.json doesn't exist on the target disco. Surface a clear error, don't silently fall through to "no project".

Effort

  • Track 1 (separator): ~3 hours including alias plumbing on every command.
  • Track 2 (project context): ~1 day including the resolver, tests, and updating every command that takes --project.
  • Track 3 (naming): ~1 day total, but split across multiple small PRs.

I'd ship Tracks 1 + 2 in the same release, then Track 3 as v1.0 polish.

Out of scope

  • Replacing oclif. Not necessary; the framework supports everything above.
  • Renaming the binary. disco stays disco.
  • Daemon API changes. This is purely CLI-side.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions