Adaptive Intent-based CLI State Machines

A presentation at OclifConf in May 2019 in San Francisco, CA, USA by swyx

Slide 1

Slide 1

ADAPTIVE INTENT-BASED CLI STATE MACHINES @swyx May 2019

Slide 2

Slide 2

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx rupa/z

Slide 3

Slide 3

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx “Frecency” https://slack.engineering/a-faster-smarter-quick-switcher-77cbc193cb60

Slide 4

Slide 4

ADAPTIVE INTENT-BASED CLI STATE MACHINES

Slide 5

Slide 5

RIDICULOUSLY OVER-ENGINEERED COMMAND LINE APPS

Slide 6

Slide 6

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx PART I NETLIFY NETLIFY CLI NETLIFY DEV

Slide 7

Slide 7

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx DISCLAIMER THIS IS MOSTLY COLLEAGUES’ WORK

Slide 8

Slide 8

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx BitBalloon

Slide 9

Slide 9

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx StaticGen.com

Slide 10

Slide 10

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx

Slide 11

Slide 11

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx

Slide 12

Slide 12

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx

Slide 13

Slide 13

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx CLI Iterations • • • V0: !Commander.js V1: 🐍Cobra V2: ✨Oclif https://www.netlify.com/blog/2018/09/10/netlify-cli-2.0-now-in-beta-/#our-cli-journey

Slide 14

Slide 14

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx

Slide 15

Slide 15

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx JAMstack.org

Slide 16

Slide 16

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx

Slide 17

Slide 17

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx PROBLEM LOCAL EMULATION

Slide 18

Slide 18

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx The Rise of Dev Servers

Slide 19

Slide 19

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Configuring Proxies in Dev Servers

Slide 20

Slide 20

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Configuring Proxies in Dev Servers

Slide 21

Slide 21

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx

Slide 22

Slide 22

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx PROBLEM SOLVING “DEPLOY AND PRAY”

Slide 23

Slide 23

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Netlify Dev: Wrapping the DevServer

Slide 24

Slide 24

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx New Commands, New Config

Slide 25

Slide 25

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Private Dev with Oclif Plugins

Slide 26

Slide 26

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx

Slide 27

Slide 27

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Netlify Dev Requirements • • • • • • • Read netlify.toml config Check login state Check site link state Check functions folder exists Check _redirects folder exists Respect flag overrides Prompt for what’s missing

Slide 28

Slide 28

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Discovering 12 Factor Apps • • • • • • Help Docs Flags > Args —version stdout vs. stderr Errors, DEBUG=* Be FANCY • • • • • • Prompting Tables (Perceived) Speed Contributions Sub:commands XDG-spec

Slide 29

Slide 29

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx CLI Cheatsheet https://github.com/sw-yx/cli-cheatsheet

Slide 30

Slide 30

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx PART II THE 13th FACTOR

Slide 31

Slide 31

@swyx

Slide 32

Slide 32

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx

Slide 33

Slide 33

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Adaptive Interfaces “Instead of designing web pages and content to respond to various devices… developers are trying to create interfaces that respond to individual users. These interfaces would adapt on the fly to the current user and collect data over time to anticipate each user’s actions and preferences.” https://speckyboy.com/adaptive-user-interfaces/

Slide 34

Slide 34

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx THE 13th FACTOR STATE

Slide 35

Slide 35

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx PROBLEM I STATE IS HARD

Slide 36

Slide 36

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx fs.writeFileSync(‘store.json’, JSON.stringify(data)); fs.writeFileSync(“store.json”, JSON.stringify( Object.assign({}, data, { foo: “bar” })) )

Slide 37

Slide 37

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx path.resolve(process.cwd(), ‘foo’, ‘/store.json’) path.join(process.cwd(), ‘foo’, ‘/store.json’) path.resolve(process.cwd(), ‘store.json’) path.join(process.cwd(), ‘store.json’) path.resolve(__dirname, ‘store.json’) path.join(__dirname, ‘store.json’)

Slide 38

Slide 38

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx

Slide 39

Slide 39

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Don’t roll your own • • • https://github.com/sindresorhus/conf https://github.com/jonschlinkert/data-store https://github.com/davidtheclark/cosmiconfig (useful for reading config in .rc files as well but needs config for XDG compliance)

Slide 40

Slide 40

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx const Conf = require(‘conf’); const config = new Conf(); config.set(‘unicorn’, ‘🦄’); console.log(config.get(‘unicorn’)); //=> ‘🦄’ // Use dot-notation to access nested properties config.set(‘foo.bar’, true); console.log(config.get(‘foo’)); //=> {bar: true} config.delete(‘unicorn’); console.log(config.get(‘unicorn’)); //=> undefined

Slide 41

Slide 41

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx A full model of CLI State CLI Flags • Project State • • Project Filesystem • Machine State Remote user account settings • Remote team account settings • • Remote global defaults

Slide 42

Slide 42

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Solution I: cli-state import { initCLIState, globalState, projectState } from ‘cli-state’;

Slide 43

Slide 43

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Offline State = Cache? CLI Flags • Project State • • Project Filesystem • Machine State Cached user account settings • Cached team account settings • • Cached global defaults

Slide 44

Slide 44

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx PROBLEM II CLI’S AS INTOLERANT PROCEDURE CALLS

Slide 45

Slide 45

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx

Slide 46

Slide 46

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Context is hard

Slide 47

Slide 47

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Handling Incomplete State • • • Worst: Silent Exit OK: Throw Error Better: Error Message

Slide 48

Slide 48

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Handling Incomplete State • • • • • Worst: Silent Exit OK: Throw Error Better: Error Message Good: Prompt for fix Great: Adaptive prompting

Slide 49

Slide 49

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx class Example2 extends Command { // … async run() { // … myBusinessLogic(state.name) // throws! } }

Slide 50

Slide 50

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx class Example2 extends Command { // … async run() { // … if (state.name) { myBusinessLogic(state.name) } // silent error } }

Slide 51

Slide 51

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx const chalk = require(“chalk”) class Example2 extends Command { // … async run() { // … if (state.name) { myBusinessLogic(state.name) } else { this.error(you did not provide ${chalk.yellow(name)}. please retry) } } }

Slide 52

Slide 52

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx const chalk = require(“chalk”) class Example2 extends Command { // … async run() { // … if (state.name) { myBusinessLogic(state.name) } else { this.error(Error code) this.error(Error title) this.error(Error description (Optional)) this.error(How to fix the error URL for more information) } } }

Slide 53

Slide 53

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx const chalk = require(“chalk”) const { prompt } = require(“enquirer”) class Example2 extends Command { // … async run() { // … if (state.name) { myBusinessLogic(state.name) } else { const name = prompt(“give me a name”) if (name) { myBusinessLogic(name) } else { this.error(Error code) this.error(Error title) this.error(Error description (Optional)) this.error(How to fix the error URL for more information) } } } }

Slide 54

Slide 54

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx const chalk = require(“chalk”) const { prompt } = require(“enquirer”) class Example2 extends Command { // … async run() { // … if (state.name) { myBusinessLogic(state.name) } else { const name = prompt(“give me a name”) if (name) { console.log(“next time you can pass a —name flag!”) myBusinessLogic(name) } else { this.error(Error code) this.error(Error title) this.error(Error description (Optional)) this.error(How to fix the error URL for more information) } } } }

Slide 55

Slide 55

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx KEY REALIZATION WE WRITE CLI’S LIKE WE WROTE JQUERY

Slide 56

Slide 56

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx mycli login login.js mycli deploy deploy.js

Slide 57

Slide 57

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx mycli login login.js mycli deploy deploy.js

Slide 58

Slide 58

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx

Slide 59

Slide 59

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 mycli login @swyx login.js utils/login.js mycli deploy deploy.js

Slide 60

Slide 60

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx KEY REALIZATION COMMAND != INTENT

Slide 61

Slide 61

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Use a State Machine https://statecharts.github.io/xstate-viz/

Slide 62

Slide 62

netlify Adaptive Intent-Based CLI State Machines notLoggedIn Oclif Conf, May 2019 Login @swyx isLoggedIn Deploy isDeployed

Slide 63

Slide 63

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx noName getName hasName noFolder makeFolder hasFolder notLoggedIn Login isLoggedIn Deploy isDeployed

Slide 64

Slide 64

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Shortest Paths https://xstate.js.org/docs/packages/xstate-graph/#api

Slide 65

Slide 65

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Solution II: cli-state-machine import { Action, State, initStateMachine, processStateMachine } from ‘cli-state-machine’

Slide 66

Slide 66

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Defining State export const loggedInState: State = { stateId: ‘loggedIn’, getValue: async () => loginStatus, assert: async (status: boolean) => status === true, }

Slide 67

Slide 67

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Defining Action export const loginAction: Action = { actionId: ‘loginAction’, beforeState: loggedOutState, afterState: loggedInState, execute: async () => { // console.log(‘logging in’) loginStatus = true }, }

Slide 68

Slide 68

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Defining another Action export const deployAction: Action = { actionId: ‘deploy’, beforeState: loggedInState, execute: async () => { console.log(‘deployed!’) }, }

Slide 69

Slide 69

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Running the State Machine initStateMachine([ loginAction, logoutAction, deployAction ]) // map intents to actions // let StateMachine search for valid adjacent states await processStateMachine(deployAction, {})

Slide 70

Slide 70

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx More To Do • Binding flags to States Helper functions for: • • file/folder existence • Prompting for required field • ??? Visualization output • Principle: Componentized and Declarative Business Logic

Slide 71

Slide 71

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx the Full State Machine // allActions and otherState defined above initCLIState(); const cliState = { projectState, globalState, …otherState } initStateMachine(allActions) await processStateMachine(deployAction, cliState)

Slide 72

Slide 72

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Help us build it netlify.com/careers github.com/sw-yx/cli-state github.com/sw-yx/cli-state-machine

Slide 73

Slide 73

ADAPTIVE INTENT-BASED CLI STATE MACHINES swyx.io/talks npm i -g netlify-cli

Slide 74

Slide 74

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx Principles of HCI

Slide 75

Slide 75

netlify Adaptive Intent-Based CLI State Machines Oclif Conf, May 2019 @swyx 12 General Principles of HCI