You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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:
: 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.
--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.
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:
Aliases keep old forms working — no scripts break.
README/docs regeneration — scripts/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.
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::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.--projectis required on every project-scoped command. No notion of "current project". Users type--project mysiteforlogs,env:set,env:list,deploy,run,scale:set,domains:add,volumes:list— every invocation. Pure friction.set/get/listvsadd/remove/listvsset/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:
Peers:
oclif supports both via
topicSeparator. Switching is one config line plus per-commandaliasesto keep the old form working.Subject placement (positional vs flag)
projects:remove <name>projects:add --name <name>projects:move --project <name>domains:add <domain> --project <p>nodes:remove <name>apikeys:remove <key>discos:remove <name>Convention: primary subject positional, scope (project, disco) as flag. Outliers:
projects:addandprojects:move.Required
--projecteverywherelogs,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--projectand most require it.Verb consistency
Three different verb vocabularies (CRUD-ish, add/remove, login-style).
Other inconsistencies
deployis a top-level verb, butdeploy:list/deploy:cancel/deploy:outputtreat it as a noun.disco deploy(action) anddisco deploy:list(noun's subcommand) live side by side.projects:movemeans "transfer to another disco". The name suggests in-place rename. Becomes a footgun onceprojects:renamelands (separately scoped, see daemon repo issue Project deployment failed #101).registries:set(default registry) andregistries:login(creds) share a topic but are unrelated concepts.setis easily misread.--no-inputexists onprojects:removeandvolumes:exportonly. Other destructive commands (apikeys:remove,domains:remove,discos:remove,nodes:remove) lack it — either no confirm at all or no scriptable bypass.postgres:databases:addis three levels deep where two would do.Proposed approach — three tracks, ship independently
Track 1 — separator (shippable in one PR)
Switch
topicSeparatorfrom":"to" ". Register the colon form as an alias on every command: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
--projectin this order:--projectflag (current behavior)DISCO_PROJECTenv vardisco.jsonin cwd (file already exists for deploys — natural anchor)disco use <project>writes a default into local CLI configAfter this, the common case becomes:
instead of
--project myblogon every line. Add to every command currently taking--projectvia a shared resolver inconfig.ts.Track 3 — naming/verb cleanup (v1.0 polish)
Each as a small focused PR with aliases for the old name:
projects move→projects transfer(alias the old). Avoids future collision withprojects rename.deploy:list/deploy:cancel/deploy:output→ newdeploymentstopic. Keep baredisco deployas the action shortcut.registries set→registries default(alias old).scaletopic: addscale listto round out CRUD.--yes(matchesgh,apt,helm) and add toapikeys remove,domains remove,discos remove,nodes remove. Add confirmation prompts where missing.projects add --name Xtoprojects add X(positional). Alias the flag form.postgres:databases:add→postgres database add(or similar) where nesting earns nothing.Risk
scripts/generate-readme.jswill pick up new help text, but examples in marketing pages and tutorials need a manual sweep.@oclif/plugin-autocompleteis already installed; it regenerates per release.disco.jsonlookup: must handle the case where the project named in cwd'sdisco.jsondoesn't exist on the target disco. Surface a clear error, don't silently fall through to "no project".Effort
--project.I'd ship Tracks 1 + 2 in the same release, then Track 3 as v1.0 polish.
Out of scope
discostaysdisco.