Skip to content

Instantly share code, notes, and snippets.

@anikethsaha
Forked from swyxio/adaptive_intent.md
Created October 22, 2019 17:38
Show Gist options
  • Save anikethsaha/97f5a26a90f4142cb1148e5a7a280920 to your computer and use it in GitHub Desktop.
Save anikethsaha/97f5a26a90f4142cb1148e5a7a280920 to your computer and use it in GitHub Desktop.
an adaptive, intent based CLI "state machine"

an adaptive, intent based CLI "state machine"

one realization from working on Netlify's CLI is that the CLI framework we used, oclif, didn't provide a great user experience out of the box.

Emphasis on great: it does a lot of nice things, like offering flag and argument parsing, help documentation, and pluggability. That's good for the CLI developer. But what about the CLI user?

  • Idiomatic oclif code often checks for required preconditions, and if it doesn't exist, it prints a warning and then process.exit(1).
  • Decent code prints a helpful warning telling the user what they got wrong. It is informative.
  • Better code offers a prompt, creates a file, or something similar to solve the precondition before proceeding. (possibly recursively). It is intent-based.
  • Great code remembers past inputs to prompts and uses that to offer useful defaults. It is adaptive.
  • Fantastic code steps beginners through so they don't need to read docs, but also offers escape hatches and shortcuts for pro users to super effective. It embraces progressive disclosure.

It is possible to code all of these individually within each oclif command, but at some point the criss-crossing needs of various CLI commands start to cause a lot of overlap. This is where we need a "state machine" to coordinate and deduplicate precondition resolution.

Intent based

Bear with me here. What this term tries to encapsulate is the realization that most CLIs are just "procedure calls". You type a few letters in a console, it runs code. If every precondition is met, the code runs as intended. If preconditions are not met, errors occur and the command fails. If we are lucky, these errors are caught and a helpful message is displayed. Often we are not so lucky.

An "intent based" CLI doesn't put the onus on the user to get every precondition exactly right to proceed. Momentum does not die the moment something is underspecified. Intents have explicitly specified preconditions, and specified precondition resolutions. Commands don't directly execute code, they spawn intents. Intents can spawn nested intents (a weak point of oclif), possibly to resolve preconditions.

By decoupling commands from intents, we allow mistakes. We allow multiple paths to feed into the right code paths. We allow "did you mean...?" intent resolution. We allow non-CLI reuse of the code, e.g. with chatbots/voice UI's, anything with generative and iterative prompting and a need to carry the ball in a task from start to finish (this typically rules out graphical/web UI's).

Adaptive

CLIs can access a lot of context to gain power:

  • project config/filesystem
  • machine cache level
  • user account level
  • global telemetry-based level

The more context we can read, the better defaults we can offer, the less documentation the user needs, the faster the user can achieve their intent. My favorite CLI is rupa/z, and it has just one command that gets more useful the longer i use it, because it records my past actions and preferences and takes maximum advantage of that. We should all aspire to be as useful.

But the code to read and modify this context is laborious to write. You'd think we'd want to make this easy to write so that we can offer this power easily. It isn't.

"State Machine"

The reason I think this.. thing, whatever it turns out to be in the end, doesnt require a new framework to replace oclif, is because it is just an intelligent layer sitting behind oclif. It starts to look like a state machine with active data flow (instead of passive, like Redux or XState, where motive power lies outside the state machine, this is active, where it always wants to keep going, and only pauses to wait for user input).

This "state machine" combines the context and intents we specify above and describes the flow of intent through state, kicking off other intents if needed, modifying state after intents are executed, finally bringing the user to the successful conclusion of their specified intent (or the user can always cancel). This is a stretch, but it may also offer the ability to go back on some reversible state changes if the user changes their mind.

Conclusion

these are just three things i think are necessary to have to make it easier to build CLI with great UX. It should be easier than it is today.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment