Interactive disambiguation of user intent
@emilyoram is already working on this.
Since May 25, 2026.
- Dominant language
- TypeScript
- Stars
- 7
- Forks
- 0
- Avg merge
- 15h 36m
- Merged PRs (30d)
- 28
Description
Problem
Two user-reported issues highlight the same root problem — c8ctl cannot always determine what the user intends, and when it guesses wrong the consequences are costly:
-
Deploying to the wrong cluster (https://github.com/camunda/c8ctl/issues/393) — When multiple profiles are configured,
c8 deploysilently uses whichever profile happens to be active. A user who forgot toc8 use profiledeploys to production instead of staging. -
Deploy includes unexpected files (https://github.com/camunda/c8ctl/issues/350) — The expanded resource deployment candidate set in Camunda 8.10 changes which file extensions are deployable. A directory that previously deployed only
.bpmnfiles now picks up.md,.json,.yaml, etc. The determination surface differs between server versions, degrading certainty about what will be deployed.
Both issues are about disambiguating user intent — the CLI needs to know with certainty where the user wants to deploy and what they want to deploy.
Proposal: optional interactive disambiguation
When ambiguity exists and the terminal is interactive (TTY), c8ctl should ask the user rather than guess or bail with a complex message. The interactive prompts clarify intent at the moment it matters, with options to persist the choice so the user is not asked repeatedly.
Power users can disable this mode, and it is suppressed in non-interactive CI.
A working prototype of this UX pattern is on the poc/interactive-menu branch. Video demo.
Disambiguation point 1: deploy target (which cluster?)
When multiple profiles are configured and the user did not explicitly specify a target (--profile or --yes), present an arrow-key selector:
? Which profile do you want to deploy to?
❯ staging https://staging.example.com
production https://production.example.com
local http://localhost:8080/v2
The currently active profile is pre-selected. The user confirms with Enter or cancels with Escape.
Disambiguation point 2: skipped files (which resources?)
When a directory scan skips files because their extensions are outside the default allow-list (.bpmn, .dmn, .form), present the skipped files and a menu of actions:
Found 3 file(s) with extensions not in the allow-list (.md, .json):
• readme.md
• config.json
• subdir/docs.md
? What do you want to do about these files?
❯ Deploy them include in this deployment only
Ignore them skip for now
Deploy them always remember these files in .c8ignore
Ignore them always remember to skip these files in .c8ignore
Show me how to configure this
"Deploy always" and "Ignore always" persist the choice to .c8ignore so the user is not prompted again.
Disambiguation point 3: profile selection for use and remove
When c8 use profile or c8 remove profile is invoked without a name argument, present an interactive picker instead of erroring:
? Which profile do you want to use?
❯ staging https://staging.example.com
production https://production.example.com (Modeler)
local http://localhost:8080/v2
remove profile additionally confirms before deleting:
? Remove profile 'staging'? [y/N]
Non-interactive fallback
All prompts degrade gracefully when stdin or stderr is not a TTY (CI, pipes, scripts):
- Deploy target: auto-approves and logs the target to stderr.
- Skipped files: silently ignores (preserves current behavior).
- Profile pickers: throws with a usage hint (
c8 use profile <name>).
--yes (-y) is a global flag that suppresses all interactive prompts, equivalent to non-interactive mode.
Design principles (from the prototype)
- Zero external dependencies — uses Node's built-in
readlineand ANSI escape codes. Noinquirer,prompts, or similar. - stderr for prompts, stdout for data — interactive menus write to stderr, keeping stdout clean for piped JSON/table output.
- TTY detection —
process.stdin.isTTY && process.stderr.isTTYgates all interactive behavior. select()andconfirm()primitives — reusable across any command that needs disambiguation. Exposed to plugins viaPluginCtx.prompt.- Pre-flight server version check —
deploychecks the Camunda version before resource collection so the skipped-files menu correctly reflects what the server accepts (8.10+ supports extended extensions).
Alternatives evaluated
- Bail with a help hint — print the combination of flags needed to establish certainty and exit non-zero. This forces the user to re-run the command. Rejected because it adds friction without guiding the choice.
- Emit a message telling the user how to persist their preference — log a hint after the operation. This doesn't prevent the mistake — the deploy has already happened. Rejected because the disambiguation needs to happen before the side effect.
Scope
The prototype on poc/interactive-menu implements all three disambiguation points along with the --yes global flag, TTY detection, deploy confirmation guard, and tests. It is a proof-of-concept for the UX pattern, not a final implementation. Work items to productionize:
- Extract
select()/confirm()into their own PR (framework primitives) - Deploy target confirmation (interactive profile picker +
--yesflag) - Skipped-files interactive menu (with
.c8ignorepersistence and server version check) - Interactive profile pickers for
use profile/remove profile - Shell completion for
--yes/-y - Documentation updates (README, EXAMPLES.md, command reference)
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.