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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions