Skip to content

3. Anatomy of a Skill

Gabi Dobocan edited this page Jan 11, 2019 · 3 revisions

The main components of a skill are:

  • index.js, the main entrypoint
  • the intents folder, defining intent metadata like command structure and examples
  • the actions folder, containing the actual action code for each of de defined intents

The index.js entrypoint

Should export a function that receives an empty skill object, updates its metadata and returns it.

module.exports = skill => skill
  .can('multiply numbers')

You can also define slots or aliases at skill level, which will make them global to all intents within that skill.

The Intents Folder

Each file here represents a single intent. Each intent should export a function that receives an empty intent object, updates its metadata and returns it.

module.exports = intent => intent.ns('multiply')
  .can('multiply numbers')
  .examples([
    'whats 182 times 56',
    'calculate 410 x 54',
  ])
  .command([
    '~[whats] @[number_one] ~[times] @[number_two]',
  ], { train: 100, test: 10 })
  .slot('number_one', { numberPattern: true })
  .slot('number_two', { numberPattern: true })
  .alias('whats', ['whats', 'what is', 'calculate', 'calc', 'do'])
  .alias('times', ['times', 'x', 'multiplied by']);

Significant intent methods:

  • can: Use to provide one or more utterances describing intent functionality - this will help Niles understand when users ask for topical help and provide support. You can also use aliases here, but the first string provided should be "clean" as it will be used to describe the intent in the global help response.

  • examples: Use to provide usage examples that will be displayed by help intents.

  • command: Use to provide one or more command formats, using references to slots and aliases. You can also define how many training and test utterances to generate; the default values are 200 for training and 20 for testing, but you should experiment and adjust those as needed for your specific case. Keep in mind that if you don't provide sufficient format variation but keep the default numbers, you might see a generation error on build, telling you that you've requested more utterances than can be generated.

  • slot: Use this to define information you need extracted from the commands. The second argument to this can be an array explicitly listing string options (you can also use aliases here), or an object specifying which patterns the data falls into. Available patterns to apply:

Pattern Description
wordPattern random 3-10 letter string
filePathPattern random file paths
fileExtensionPattern random file extensions
fileNamePattern random filenames
textContentPattern random words within quotes
userPattern linux users pattern
timePattern random datetime formats
numberPattern random int or float number
  • alias: Use this to define synonyms (you can also reference aliases within aliases). There's also a set of global, predefined aliases you can use anywhere:
Alias Description
~[_letter] random single lowercase letter
~[_digit] random single digit
~[_bla] random 3-5 letter string
~[_word] random 3-10 letter string
~[_dir] "dir", "directory" or "folder"
~[_extension] random file extension
~[_time_interval] "seconds", "minutes", "hours", "days", "months" or "years"
~[_time_interval#singular] "second", "minute", "hour", "day", "month", "year"
~[_day] random weekday, short or long format
~[_month] random month, short or long format
~[_year] random year

The Actions Folder

Each file here represents a single action, associated to a single intent by file name. Each action should export a single function, receiving context and params and executing the actual intent functionality.

module.exports = (params, ctx) => {
  const { logger } = ctx;
  const { chalk } = logger;
  let { number_one: numberOne, number_two: numberTwo } = params;

  if (!numberOne || !numberTwo) {
    // Maybe read them from the user with ctx.logger.inquirer instead
    throw new Error('I\'m sorry, I couldn\'t understand the numbers to multiply.');
  }

  numberOne = parseFloat(numberOne.value.trim());
  numberTwo = parseFloat(numberTwo.value.trim());

  logger.log(`That would be ${chalk.blueBright((numberOne * numberTwo).toFixed(2))}, sir.`);
};

Params are sent as an object, each key within representing an identified slot by its defined name. Each slot itself is an object with three properties: entity (the slot name again), value and confidence.

The context is a collection of tools and utilities assisting with skill development. Here's a breakdown of relevant context objects/methods:

Key Description
homePath Path to the home directory for Niles.
shell Shelljs instance you can use to run shell commands.
shell.execAsync(cmd) Convenient method to run command as promise.
shell.parseTree(output) Use this to convert tree-like ASCII structures to JSON objects.
axios Axios instance you can use to make http requests.
logger Use this instead of console to print output.
logger.inquirer Inquirer.js instance you can use to require user input.
logger.chalk Chalk instance you can use for styled output.
logger.table Table instance you can use to create ASCII tables.
logger.table.borderless(data, padding = 2, config = {}) Convenient method to generate simple, compact tables.
logger.table.template(data, template = 'ramac', config = {}) Convenient method to create templated tables - see table docs.
analytics.track(event, properties = {}) Use this to track relevant events.
db.get(name) Use this to retrieve a saved value (namespaced by skill).
db.save(name, value) Use this to save a value locally (namespaced by skill).
session.intent Use this to access the current intent object directly.