Repository: Accenture/alexia
Branch: master
Commit: 623d56797cef
Files: 71
Total size: 118.9 KB
Directory structure:
gitextract_tge_d4e1/
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── adop/
│ ├── LICENSE.md
│ ├── README.md
│ ├── jenkins/
│ │ └── jobs/
│ │ ├── dsl/
│ │ │ └── alxeia_pipeline_jobs.groovy
│ │ ├── generate.sh
│ │ └── xml/
│ │ └── .gitkeep
│ ├── metadata.cartridge
│ └── src/
│ └── urls.txt
├── examples/
│ ├── actions.js
│ ├── app-info.js
│ ├── async-response.js
│ ├── built-in-intents.js
│ ├── create-request.js
│ ├── custom-slots.js
│ ├── hello-world.js
│ ├── more-utterances.js
│ ├── multi-language/
│ │ ├── locales/
│ │ │ ├── de/
│ │ │ │ ├── custom-slots.json
│ │ │ │ └── translation.json
│ │ │ └── en/
│ │ │ ├── custom-slots.json
│ │ │ └── translation.json
│ │ └── multi-language-app.js
│ ├── original-request-data.js
│ ├── response-object.js
│ ├── session-attributes.js
│ ├── slots.js
│ ├── spech-assets.js
│ └── start-end.js
├── nodemon.json
├── package.json
├── src/
│ ├── alexia.js
│ ├── built-in-intents-map.js
│ ├── built-in-slots-map.js
│ ├── create-app.js
│ ├── create-custom-slot.js
│ ├── create-intent.js
│ ├── create-request.js
│ ├── create-server.js
│ ├── error-handler.js
│ ├── generate-speech-assets-i18n.js
│ ├── generate-speech-assets.js
│ ├── handle-request.js
│ ├── parse-rich-utterances.js
│ ├── save-speech-assets.js
│ └── validator.js
└── test/
├── .eslintrc
├── actions.spec.js
├── basic.spec.js
├── contacts.spec.js
├── create-server.spec.js
├── generate.spec.js
├── mock/
│ ├── assets.js
│ └── multi-lang-assets.js
├── multi-language.spec.js
├── register-intents.spec.js
├── save-speech.spec.js
└── test-apps/
├── actions-app.js
├── basic-app.js
├── contacts-app.js
├── multi-language/
│ ├── locales/
│ │ ├── de/
│ │ │ └── translation.json
│ │ └── en/
│ │ ├── custom-slots.json
│ │ └── translation.json
│ └── multi-language-app.js
└── separate-intents/
├── bye-intent.js
└── hello-intent.js
================================================
FILE CONTENTS
================================================
================================================
FILE: .editorconfig
================================================
# http://editorconfig.org
root = true
[*]
indent_style = space
indent_size = 2
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false
================================================
FILE: .eslintignore
================================================
coverage/**
node_modules/**
*.log
docs/**
examples/**
================================================
FILE: .eslintrc
================================================
{
"extends": "standard",
"rules": {
"semi": [1, "always"],
"padded-blocks": 0
}
}
================================================
FILE: .gitattributes
================================================
* text=auto
================================================
FILE: .gitignore
================================================
*.idea
*.log
node_modules
speechAssets
coverage
*~
*.swp
*.swo
================================================
FILE: .travis.yml
================================================
language: node_js
node_js:
- 4.2.0
script: "npm run test-lcov"
after_script: "npm install coveralls && cat ./coverage/lcov.info | coveralls"
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2016 Accenture
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================

A Framework for creating Amazon Echo (Alexa) skills using Node.js
[![NPM Version][npm-image]][npm-url]
[![Build Status][travis-image]][travis-url]
[![Coverage Status][coveralls-image]][coveralls-url]
```javascript
const alexia = require('alexia');
const app = alexia.createApp();
app.intent('HelloIntent', 'Hello', () => {
return 'Hello from Alexia app';
});
```
**HTTPS Server**
```javascript
app.createServer().start();
```
*or*
**AWS Lamba**
```javascript
exports.handler = (event, context, callback) => {
app.handle(event, data => {
callback(null, data);
});
};
```
## Installation
`npm install alexia --save`
Optional: requires [Handling Amazon Requests manually](#handling-amazon-requests-manually)
`npm install hapi --save`
## Overview
Alexia helps you to write Amazon Echo skills using Node.js. This framework handles Amazon Echo requests and automatically calls intents in your application. See the [Features and Samples](#features-and-samples)
## Table of Contents
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->
- [Terminology](#terminology)
- [Features and Samples](#features-and-samples)
- [Create App](#create-app)
- [Set default value for shouldEndSession](#set-default-value-for-shouldendsession)
- [Create Intents](#create-intents)
- [Create Welcome Message](#create-welcome-message)
- [Built-in Intents](#built-in-intents)
- [Slots](#slots)
- [Custom Slots](#custom-slots)
- [Session Attributes](#session-attributes)
- [Cards](#cards)
- [Reprompt](#reprompt)
- [SSML](#ssml)
- [Read Original Request Data](#read-original-request-data)
- [Asynch Intent Handling](#asynch-intent-handling)
- [Generate Speech Assets](#generate-speech-assets)
- [Save Speech Assets To Directory](#save-speech-assets-to-directory)
- [Register Intents using pattern matching](#register-intents-using-pattern-matching)
- [Actions](#actions)
- [Localization](#localization)
- [Handling Amazon Requests](#handling-amazon-requests)
- [Handling Amazon Requests Manually](#handling-amazon-requests-manually)
- [Deploy](#deploy)
- [Heroku](#heroku)
- [AWS Lambda](#aws-lambda)
- [Create Alexa skill](#create-alexa-skill)
- [Testing](#testing)
- [Device Testing](#device-testing)
- [Echosim.io (Online Simulator)](#echosimio-online-simulator)
- [Unit Testing](#unit-testing)
- [Debugging](#debugging)
- [Scripts](#scripts)
- [Contributing](#contributing)
- [License](#license)
<!-- END doctoc generated TOC please keep comment here to allow auto update -->
## Terminology
Creating new skills for Amazon Echo using alexia requires you to understand some basic terms. This part should clarify the most of them.
- **Skill** - Alexa app
- **Intent** - Invoked if one of intent `utterances` is recognized
- **Utterance** - Voice input example
- **Slot** - Variable part of utterance
- **Session Attributes** - data persisted through the session
- **Cards** - visual output displayed in [Alexa app](http://alexa.amazon.com/)
## Features and Samples
### Create App
To create new app simply call `alexia.createApp()`
```javascript
const alexia = require('alexia');
const app = alexia.createApp('MyApp');
```
### Set default value for shouldEndSession
If you want to set default value of shouldEndSession response property you can do it by specifying `shouldEndSessionByDefault` property in App options.
```javascript
const app = alexia.createApp('MyApp', {shouldEndSessionByDefault: true});
```
Alternatively you can use `app.setShouldEndSessionByDefault()` method.
```javascript
app.setShouldEndSessionByDefault(true);
```
### Create Intents
You have to give a name for the intent when you are creating it. Also you can set multiple utterances for any of your intents.
```javascript
// Named intent
app.intent('MyIntent', 'Hello Alexa my name is Michael', () => 'Hi Michael');
// Intent with more utterances
app.intent('AnotherIntent', ['Hello', 'Hi', 'Whats up'], () => 'Hello yourself');
```
### Create Welcome Message
If you want more than just a generic "Welcome" from Alexa, you can use the onStart method to help you achieve that.
```javascript
app.onStart(() => {
return 'Welcome to My Hello World App, say hello world to get started, or say help to get more instructions';
});
```
### Built-in Intents
Amazon Alexa Skills Kit provides a collection of built-in intents. These are intents for very common actions. Alexia provides convenient methods for their reusing and extending.
List of built-in intents: `cancel`, `help`, `next`, `no`, `pause`, `previous`, `repeat`, `resume`, `startOver`, `stop`, `yes`.
See official Amazon docs: [Available Built-in Intents](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/implementing-the-built-in-intents#Available Built-in Intents)
```javascript
// Use default built-in utterances
app.builtInIntent('stop', () => 'Stopping now');
// Extend built-in utterances
app.builtInIntent('stop', 'Stop now', () => 'Stopping now');
app.builtInIntent('stop', ['Stop now', 'Please stop'], () => 'Stopping now');
```
### Slots
As mentioned in [Terminology](#terminology) section - slots represent variable part of user input in utterances. To make their creation bit easier our utterances contain slot name with type. These samples are converted into common utterances recognized by Alexa and slots are included in intentSchema.
```javascript
app.intent('SlotIntent', 'My number is {num:Number}', (slots) => {
return `Your number is ${slots.num}`;
});
```
### Custom Slots
Alexia helps you to create custom slots by specifying its `name` and `utterances`
```javascript
app.customSlot('Name', ['Arnold', 'Otto', 'Walda', 'Pete']);
app.intent('CustomSlotIntent', 'My name is {name:Name}', (slots) => {
return `Hi ${slots.name}`;
});
```
### Session Attributes
Intent can be resolved using simple string (a text response) or more complex `responseObject`. Its attribute `attrs` will override current sessionAttributes. If you wish to extend current session attributes you can use for example `Object.assign` method. Make sure you set `end` attribute to `false` to keep the session open (default: `true`). See [Session Attributes example](examples/session-attributes.js). Session attribute `previousIntent` is reserved.
```javascript
app.intent('AttrsIntent', 'session attributes test', (slots, attrs) => {
return {
text: 'Alexa response text here',
attrs: {
attr1: 'Whatever to be remebered in this session'
},
end: false
};
});
```
### Cards
To display card in Alexa app add configuration to responseObject `card` property
```javascript
app.intent('CardsIntent', 'Whats in shopping cart', () => {
return {
text: 'Your shopping cart contains Amazon Echo Device and 2 more items. To see the full list check out your Alexa app',
card: {
title: 'Shopping cart',
content: 'You shopping cart contains: Amazon Echo, Amazon Tap, Echo Dot'
}
};
});
```
### Reprompt
To add reprompt text to your response add `reprompt` string value to responseObject
```javascript
app.intent('RepromptIntent', 'Send email to Mom', () => {
return {
text: 'What is the text of your message',
reprompt: 'Sorry I did not catch it. What is the text of your message'
};
});
```
### SSML
Use SSML to create more complex text responses. Just set the `ssml` parameter of responseObject to `true` and enter `SSML` into `text` property. See official Amazon docs: [Speech Synthesis Markup Language](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/speech-synthesis-markup-language-ssml-reference)
```javascript
app.intent('SSMLIntent', 'what are the digits of number {num:Number}', (slots) => {
return `<say-as interpret-as="digits">${number}</say-as>`
});
```
### Read Original Request Data
You can access the original Amazon request data from third parameter of handler. See example below.
```javascript
app.intent('OriginalRequestData', 'read original request data', (slots, attrs, data) => {
console.log('userId', data.session.user.userId);
return 'Hi';
});
```
### Asynch Intent Handling
For asynchronous intent handling add fourth parameter to your handler callback and call it when your response is ready. The response structure is identical to responseObject.
```javascript
app.intent('AsyncIntent', 'Search for something in database', (slots, attrs, data, done) => {
setTimeout(() => {
done('Work complete');
}, 120);
});
```
### Generate Speech Assets
To minimize manual work needed while deploying your Alexa skills you can use our speechAssets generator. This helps you to create `intentSchema`, `sampleUtterances` and `customSlots` for your apps.
Speech assets consists of:
- **intentSchema** - array of intents with slots
- **utterances** - phrases that are used to invoke intents
- **customSlots** - custom slot types with samples
For more information see [interaction model reference](https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interaction-model-reference)
```javascript
const speechAssets = app.speechAssets(); // object
console.log(speechAssets.toString()); // stringified version - f.e. copy paste from console
```
### Save Speech Assets To Directory
If you want to use your assets (`intentSchema`, `sampleUtterances` and `customSlots`) later and have them stored, this function will do it for you. You can pass the name of your directory or leave it empty which defaults to `/speechAssets`.
Directory structure looks like this:
```
├── speechAssets
├── intentSchema.json
├── utterances.txt
└── customSlots
├── Name.txt
├── Age.txt
...
```
```javascript
app.saveSpeechAssets('speechAssets'); // No argument leads to default value 'speechAssets'
```
### Register Intents using pattern matching
If your intents are located in separate files you need to register them to the app. One way how to do this is to wrap intent into function taking `app` as a parameter.
**src/intents/hello-intent.js**
```javascript
module.exports = app => app.intent('HelloIntent', 'hello', () => {
return 'Hello';
});
```
Next you need to register it by importing it manually and supplying the `app` as a parameter.
You can also use our shorthand function for finding and registering all intents files that match pattern. See [node-glob](https://github.com/isaacs/node-glob) for more pattern matching examples.
**src/app.js**
```javascript
app.registerIntents('src/intents/*-intent.js');
```
### Actions
Feature of Alexia that helps you to control flow of the intents. To understand it easier see the code below.
By defining the action you enable transition from one intent to another. When no actions are specified, every intent transition is allowed.
Action properties `from` and `to` can be defined as `string` (one intent), `array` (multiple intents) or `'*'` (all intents).
Each action could have condition to check whether the transition should be handled or the fail method should be invoked. If no fail method is defined `app.defaultActionFail()` is invoked when condition of handling is not met or the action (transition) is not defined.
```javascript
// Allow transition from any intent to `intent1`.
app.action({
from: '*',
to: 'intent1'
});
// Allow transition from `@start` intent to `intent2`.
app.action({
from: '@start',
to: 'intent2'
});
// Allow transition from `intent1` to `intent2` if condition is met using custom fail handler
app.action({
from: 'intent1',
to: 'intent2',
if: (slots, attrs) => slots.pin === 1234,
fail: (slots, attrs) => 'Sorry, your pin is invalid'
});
// Allow transition from `intent2` to `intent3` and also `intent4`.
app.action({
from: 'intent2',
to: ['intent3', 'intent4']
});
// Set default fail handler
app.defaultActionFail(() => 'Sorry, your request is invalid');
```
### Localization
Alexia uses [i18next](https://github.com/i18next/i18next) for localizing response texts, utterances and custom slots.
For better understanding see localized app example: [examples/multi-language](examples/multi-language).
These are the steps required to localize your existing application:
1. Install dependencies: `npm install --save i18next i18next-node-fs-backend`
2. Initialize `i18next` instance - see [the example app](examples/multi-language/multi-language-app.js)
3. Set `i18next` instance to your app to enable localization: `app.setI18next(i18next)`
4. Create directory with all locales
5. Omit utterances in all intents and access the translate function using `app.t('key')`
Localized intent example:
```javascript
app.intent('LocalizedIntent', slots => {
return app.t('text', slots);
});
```
Example `locales` directory structure:
```
locales/
├── en/ # Directory for all en locales
│ ├── translation.js # Translations of response texts and utterances for each intent
│ └── custom-slots.js # Translations of custom slots
└── de/ # Directory for all de locales ...
├── translation.js
└── custom-slots.js
```
Localization notes:
- You can localize `LaunchRequest` or `SessionEndedRequest` as well. Just add the entry along the intent names in translations
- To localize built in intents, say `AMAZON.YesIntent` use entry names after the `.` suffix. So `AMAZON.YesIntent` becomes just `YesIntent`
- To access the translation use: `app.t('key')` This `key` needs to be nested in the current intent translation entry. You don't have to use the full path to the key - the prefix is automatically added depending on the current request
- Each intent translation should have `utterances` property. We support the `richUtterances` syntax f.e: `My age is {age:Number}`
- The locale to be used is decided depending on the `data.request.locale` Its value could be currently one of: `en-US`, `en-GB`, `en-CA`, `en-IN`, `en-AU`, `de-DE`, `fr-FR`, `ja-JP`
### Handling Amazon Requests
To handle Amazon requests you need to create HTTP server with POST route. You can take advantage or our API to create Hapi server so you don't have to create it manually. This requires to install `hapi` as dependency:
```
npm install hapi --save
```
```javascript
const options = {
path: '/', // defaults to: '/'
port: 8888 // defaults to: process.env.PORT or 8888
};
const server = app.createServer(options);
```
### Handling Amazon Requests Manually
You can create your own HTTP from scratch to handle Amazon requests manually. See below example with [Hapi](http://hapijs.com/) server
```javascript
const Hapi = require('hapi');
const server = new Hapi.Server();
const app = require('./app'); // Your app
server.connection({
port: process.env.PORT || 8888
});
server.route({
path: '/',
method: 'POST',
handler: (request, response) => {
app.handle(request.payload, (data) => {
response(data);
});
}
});
server.start((err) => {
if (err) throw err;
console.log('Server running at:', server.info.uri);
app.saveSpeechAssets();
});
```
## Deploy
### Heroku
1. Create free [Heroku](https://www.heroku.com) acount
2. Install [Heroku CLI](https://devcenter.heroku.com/articles/heroku-cli)
- ensure that you don’t have the legacy Heroku Toolbelt or Heroku Ruby gem installed
3. Be sure to have `start` script defined in `package.json`
4. Be sure to create server handler on POST endpoint. See [Handling Amazon Requests](#handling-amazon-requests)
5. Run `git init` if git was not yet initialized in your project
6. Be sure to `heroku login` and enter your credentials
7. Run `heroku create` in project directory
8. Run `git push heroku master`
9. Copy your server URL to your Alexa Skill configuration. See [Create Alexa Skill](#create-alexa-skill)
### AWS Lambda
1. Create account and login to [AWS Console](https://console.aws.amazon.com/console)
2. Create new Lambda function
3. Set function invocation to `index.handler`
4. Add Alexa Skills Kit trigger
5. Export `handler` in your index.js file
6. Upload zipped project folder into AWS Lambda
7. Copy Lambda function ARN to your Alexa Skill configuration
```javascript
exports.handler = (event, context, callback) => {
app.handle(event, data => {
callback(null, data);
});
};
```
## Create Alexa skill
- Login to your [Amazon developer account](https://developer.amazon.com)
- Select Apps & Services
- Select Alexa
- Select Alexa Skills Kit
- Add a new Skill
- Set skill info required to run app:
**Skill Information**
- Name: Name of your app, can be whatever
- Invocation Name: Short phrase or abbreviation of your app name. Will be used to start your app by saying: `Alexa, start MyApp` if your invocation name is `MyApp`
**Interaction model**
- Use our speech assets generator `app.saveSpeechAssets()` to generate and save speech assets to `speechAssets` directory
- Custom Slot Types: Click `Add Slot Type`
- Type: name of custom slot type
- Values: contents of `speechAssets/customSlots/**` or enter custom slot samples manually
- Do this for each custom slot
- Intent Schema: enter contents of `speechAssets/intentSchema.json`
- Sample Utterances: enter contents of `speechAssets/sampleUtterances.txt`
**Configuration**
- Endpoint: select HTTPS and enter url or your publicly accesible server
**SSL Certificate**
- Select what applies to your SSL certificate
- Could remain unselected when no certificate is required
**Test**
- Enable skill testing on this account
- Enter one of your utterances and click `Ask MyApp`
## Testing
### Device Testing
- Connect to your Amazon Echo device using the same developer account where you created skill
- Enable application for testing
- Say `Alexa, start <myApp>`
### Echosim.io (Online Simulator)
- Open [Echosim.io](https://echosim.io/)
- Login with your Amazon developer account
- Interact with Alexa simulator
### Unit Testing
Each application should be unit-tested. We are exposing simple API helping you to create sample Alexa requests for testing and debugging.
```javascript
alexia.createRequest({
type: 'IntentRequest',
name: 'UnknownIntent',
slots: {},
attrs: {},
appId: 'amzn1.echo-sdk-123456',
sessionId: 'SessionId.357a6s7',
userId: 'amzn1.account.abc123',
requestId: 'EdwRequestId.abc123456',
timestamp: '2016-06-16T14:38:46Z',
locale: 'en-US',
new: false
});
```
All the properties optional and defaults to the values you see in the example above. Sample usage:
```javascript
alexia.createRequest({type: 'IntentRequest', name: 'HelloIntent', slots: ..., attrs: ...});
alexia.createIntentRequest('HelloIntent', slots, attrs, isNew, appId); // Shorter version - does not support all of the properties
```
Before writing unit tests make sure to install all the dependencies. In our example we will be using mocha and chai with expect.
```bash
npm install mocha chai expect --save-dev
```
Example below illustrates simple unit testing for intentRequest. Testing of launchRequest or sessionEndedRequest would look the same
```javascript
const expect = require('chai').expect;
const alexia = require('alexia');
const app = require('./path-to-app.js');
// Create sample requests
const launchRequest = alexia.createLaunchRequest();
const sessionEndedRequest = alexia.createSessionEndedRequest();
const intentRequest = alexia.createIntentRequest('MyIntent');
// Sample MyIntent test suite
describe('(Intent) MyIntent', () => {
it('should handle MyIntent', done => {
// Simulate Alexa request handling
app.handle(intentRequest, response => {
// Test the response
expect(response).to.be.defined;
done();
});
});
});
```
## Debugging
We are using [debug](https://github.com/visionmedia/debug) package to debug our alexia applications. To start application in debug mode export environment variable `DEBUG`
Examples:
- `DEBUG=alexia:info` - print only info logs
- `DEBUG=alexia:debug` - print only debug logs
- `DEBUG=alexia:*` - print all logs
To start your app with info logs run in terminal:
```bash
DEBUG=alexia:info npm start
```
## Scripts
- `npm test` - run unit tests
- `npm test:dev` - run unit tests in development mode using nodemon as watcher
- `npm run lint` - run eslint
- `npm run lint:fix` - run eslint and automatically fix problems
- `npm run toc` - update TOC in README.md
## Contributing
Alexia is an open source project and we encourage contributions. Please make sure to cover your code with unit tests.
After updating README.md please run: `npm run toc`
For more information refer to general guide [Contributing to Open Source](https://guides.github.com/activities/contributing-to-open-source/)
## License
[MIT](LICENSE)
[npm-image]: https://img.shields.io/npm/v/alexia.svg
[npm-url]: https://npmjs.org/package/alexia
[travis-image]: https://img.shields.io/travis/Accenture/alexia/master.svg
[travis-url]: https://travis-ci.org/Accenture/alexia
[coveralls-image]: https://coveralls.io/repos/github/Accenture/alexia/badge.svg?branch=master
[coveralls-url]: https://coveralls.io/github/Accenture/alexia?branch=master
================================================
FILE: adop/LICENSE.md
================================================
Copyright (c) 2016, Accenture All rights reserved.
Apache License, Version 2.0
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files.
"Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work.
2. Grant of Copyright License.
Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form.
3. Grant of Patent License.
Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed.
4. Redistribution.
You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions:
You must give any other recipients of the Work or Derivative Works a copy of this License; and
You must cause any modified files to carry prominent notices stating that You changed the files; and
You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and
If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License.
You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License.
5. Submission of Contributions.
Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions.
6. Trademarks.
This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty.
Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License.
8. Limitation of Liability.
In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability.
While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: HOW TO APPLY THE APACHE LICENSE TO YOUR WORK
To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives.
Copyright [2016] [Accenture All rights reserved]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: adop/README.md
================================================
# What is Cartridge?
A [Cartridge](http://accenture.github.io/adop-docker-compose/docs/operating/cartridges/) is a set of resources that are loaded into the Platform for a particular [ADOP](http://accenture.github.io/adop-docker-compose/) project. They may contain anything from a simple reference implementation for a technology to a set of best practice examples for building, deploying, and managing a technology stack that can be used by a project.
This cartridge consists of jenkins jobs for getting started developing Alexa.
## Source code repositories
Cartrige loads the source code repositories
* [alexia](https://github.com/Accenture/alexia)
## Jenkins Jobs
This cartridge generates the jenkins jobs and pipeline views to perform Continuous Delivery of a Docker container
* Get_Code
* Install
* Lint
* Test
# License
Please view [license information](LICENSE.md) for the software contained on this image.
## Documentation
Documentation will be captured within this README.md and this repository's Wiki.
## Issues
If you have any problems with or questions about this image, please contact us through a [GitHub issue](https://github.com/Accenture/adop-platform-management/issues).
## Contribute
You are invited to contribute new features, fixes, or updates, large or small; we are always thrilled to receive pull requests, and do our best to process them as fast as we can.
Before you start to code, we recommend discussing your plans through a [GitHub issue](https://github.com/Accenture/adop-platform-management/issues), especially for more ambitious contributions. This gives other contributors a chance to point you in the right direction, give you feedback on your design, and help you find out if someone else is working on the same thing.
================================================
FILE: adop/jenkins/jobs/dsl/alxeia_pipeline_jobs.groovy
================================================
// Folders
def workspaceFolderName = "${WORKSPACE_NAME}"
def projectFolderName = "${PROJECT_NAME}"
// Variables
def referenceAppGitRepo = "alexia"
def referenceAppGitUrl = "ssh://jenkins@gerrit:29418/${PROJECT_NAME}/" + referenceAppGitRepo
// Jobs
def getCode = freeStyleJob(projectFolderName + "/Get_Code")
def install = freeStyleJob(projectFolderName + "/Install")
def test = freeStyleJob(projectFolderName + "/Test")
def lint = freeStyleJob(projectFolderName + "/Lint")
// Views
def pipelineView = buildPipelineView(projectFolderName + "/Example_Alexia_Pipeline")
pipelineView.with{
title('Example Alexia Pipeline')
displayedBuilds(5)
selectedJob(projectFolderName + "/Get_Code")
showPipelineParameters()
showPipelineDefinitionHeader()
refreshFrequency(5)
}
getCode.with{
description("This job downloads the code from Git.")
wrappers {
preBuildCleanup()
injectPasswords()
maskPasswords()
sshAgent("adop-jenkins-master")
}
scm{
git{
remote{
url(referenceAppGitUrl)
credentials("adop-jenkins-master")
}
branch("*/master")
}
}
environmentVariables {
env('WORKSPACE_NAME',workspaceFolderName)
env('PROJECT_NAME',projectFolderName)
}
triggers{
gerrit{
events{
refUpdated()
}
configure { gerritxml ->
gerritxml / 'gerritProjects' {
'com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.data.GerritProject' {
compareType("PLAIN")
pattern(projectFolderName + "/" + referenceAppgitRepo)
'branches' {
'com.sonyericsson.hudson.plugins.gerrit.trigger.hudsontrigger.data.Branch' {
compareType("PLAIN")
pattern("master")
}
}
}
}
gerritxml / serverName("ADOP Gerrit")
}
}
}
label("docker")
steps {
shell('''set -xe
|echo Pull the code from Git
|'''.stripMargin())
}
publishers{
downstreamParameterized{
trigger(projectFolderName + "/Install"){
condition("UNSTABLE_OR_BETTER")
parameters{
predefinedProp("B",'${BUILD_NUMBER}')
predefinedProp("PARENT_BUILD",'${JOB_NAME}')
}
}
}
}
}
install.with{
description("This job performs an npm install")
parameters{
stringParam("B",'',"Parent build number")
stringParam("PARENT_BUILD","Get_Code","Parent build name")
}
environmentVariables {
env('WORKSPACE_NAME',workspaceFolderName)
env('PROJECT_NAME',projectFolderName)
}
wrappers {
preBuildCleanup()
injectPasswords()
maskPasswords()
sshAgent("adop-jenkins-master")
}
label("docker")
steps {
shell('''set -x
|echo Run an install
|
|docker run \\
| --rm \\
| -v /var/run/docker.sock:/var/run/docker.sock \\
| -v jenkins_slave_home:/jenkins_slave_home/ \\
| --workdir /jenkins_slave_home/${PROJECT_NAME}/Get_Code \\
| node \\
| npm install --save
|'''.stripMargin())
}
publishers{
downstreamParameterized{
trigger(projectFolderName + "/Test"){
condition("UNSTABLE_OR_BETTER")
parameters{
predefinedProp("B",'${B}')
predefinedProp("PARENT_BUILD", '${JOB_NAME}')
}
}
}
}
}
test.with{
description("When triggered this will run the tests.")
parameters{
stringParam("B",'',"Parent build number")
stringParam("PARENT_BUILD","Get_Code","Parent build name")
}
wrappers {
preBuildCleanup()
injectPasswords()
maskPasswords()
sshAgent("adop-jenkins-master")
}
environmentVariables {
env('WORKSPACE_NAME',workspaceFolderName)
env('PROJECT_NAME',projectFolderName)
}
label("docker")
steps {
shell('''set -x
|echo Run unit tests
|
|docker run \\
| --rm \\
| -v /var/run/docker.sock:/var/run/docker.sock \\
| -v jenkins_slave_home:/jenkins_slave_home/ \\
| --workdir /jenkins_slave_home/${PROJECT_NAME}/Get_Code \\
| node \\
| npm run test
|'''.stripMargin())
}
publishers{
downstreamParameterized{
trigger(projectFolderName + "/Lint"){
condition("UNSTABLE_OR_BETTER")
parameters{
predefinedProp("B",'${BUILD_NUMBER}')
predefinedProp("PARENT_BUILD", '${JOB_NAME}')
}
}
}
}
}
lint.with{
description("This job will perform static code analysis")
parameters{
stringParam("B",'',"Parent build number")
stringParam("PARENT_BUILD","Get_Code","Parent build name")
}
environmentVariables {
env('WORKSPACE_NAME',workspaceFolderName)
env('PROJECT_NAME',projectFolderName)
}
wrappers {
preBuildCleanup()
injectPasswords()
maskPasswords()
sshAgent("adop-jenkins-master")
}
label("docker")
steps {
shell('''set -x
|echo Run static code analysis
|
|docker run \\
| --rm \\
| -v /var/run/docker.sock:/var/run/docker.sock \\
| -v jenkins_slave_home:/jenkins_slave_home/ \\
| --workdir /jenkins_slave_home/${PROJECT_NAME}/Get_Code \\
| node \\
| npm run lint
|'''.stripMargin())
}
}
================================================
FILE: adop/jenkins/jobs/generate.sh
================================================
# Export variables
export TOKEN_WORKSPACE_FOLDER_NAME="${WORKSPACE_NAME}"
export TOKEN_PROJECT_FOLDER_NAME=$(echo "${PROJECT_NAME}" | cut -d'/' -f2)
================================================
FILE: adop/jenkins/jobs/xml/.gitkeep
================================================
================================================
FILE: adop/metadata.cartridge
================================================
CARTRIDGE_SDK_VERSION=1.1
================================================
FILE: adop/src/urls.txt
================================================
https://github.com/Accenture/alexia.git
================================================
FILE: examples/actions.js
================================================
'use strict';
const alexia = require('..');
const app = alexia.createApp('ActionsExample');
const intentA = app.intent('intentA', 'clear my calendar for {date:Date}', (slots) => {
return {
text: 'Are you sure you want to clear your calendar?',
attrs: {
date: slots.date
},
end: false
};
});
// Or use built-in amazon intents. See: `examples/built-in-intents.js`
const intentB = app.intent('intentB', 'yes', (slots, attrs) => {
// Clear calendar for date `attrs.date` here
return 'Your calendar has been cleared';
});
app.intent('intentC', 'no', () => {
return 'Your calendar was not modified';
});
app.action({
from: '*', // Allow transition from any intent to `intent1`. Use '@start' to allow intent only on start
to: 'intentA' // Refer to intent by its name or remember its reference. See below
});
app.action({
from: intentA,
to: intentB,
if: (slots, attrs) => attrs.date, // Note: date should be validated here
fail: (slots, attrs) => 'Sorry, your command is invalid'
});
app.defaultActionFail(() => 'Sorry, your request is invalid');
module.exports = app;
================================================
FILE: examples/app-info.js
================================================
'use strict';
const alexia = require('..');
// Create app with additional info (optional)
const app = alexia.createApp('AppInfoExample', {
version: '1.2.3',
ids: ['appId1', 'appId2']
});
app.intent('SampleIntent', 'sample', () => {
return 'Hello again';
});
module.exports = app;
================================================
FILE: examples/async-response.js
================================================
'use strict';
const alexia = require('..');
const app = alexia.createApp('AsyncResponseExample');
app.intent('AsyncIntent', 'async', (slots, attrs, data, done) => {
setTimeout(() => {
done('Response here');
}, 120);
});
module.exports = app;
================================================
FILE: examples/built-in-intents.js
================================================
'use strict';
const alexia = require('..');
const app = alexia.createApp('BuiltInIntentsExample');
// You can extend built-in intent with one or more utterances
app.builtInIntent('cancel', 'Cancel it', () => 'Cancel it is');
app.builtInIntent('help', ['Help me please', 'Could you help me'], () => 'I shall help you');
app.builtInIntent('next', () => 'Your next item in basket is: Amazon Echo Device');
app.builtInIntent('no', () => 'No');
app.builtInIntent('pause', () => 'Pause');
app.builtInIntent('previous', () => 'Previous');
app.builtInIntent('repeat', () => 'Repeat');
app.builtInIntent('resume', () => 'Resume');
app.builtInIntent('startOver', () => 'Start Over');
app.builtInIntent('stop', () => 'Stop');
app.builtInIntent('yes', () => 'Yes');
module.exports = app;
================================================
FILE: examples/create-request.js
================================================
'use strict';
const alexia = require('alexia');
const launchRequest = alexia.createLaunchRequest();
const sessionEndedRequest = alexia.createSessionEndedRequest();
const intentRequest = alexia.createIntentRequest('MyIntent');
console.log(launchRequest);
console.log(sessionEndedRequest);
console.log(intentRequest);
================================================
FILE: examples/custom-slots.js
================================================
'use strict';
const alexia = require('..');
const app = alexia.createApp('CustomSlotsExample');
app.slot('Name', ['Foo', 'Bar']);
app.intent('CustomSlotIntent', 'My name is {name:Name}', (slots) => {
return `You sure your name is ${slots.name}?`;
});
module.exports = app;
================================================
FILE: examples/hello-world.js
================================================
'use strict';
const alexia = require('..');
const app = alexia.createApp('HelloWorldExample');
app.intent('HelloIntent', 'hello', () => {
return 'Hello World!';
});
module.exports = app;
================================================
FILE: examples/more-utterances.js
================================================
'use strict';
const alexia = require('..');
const app = alexia.createApp('MoreUtterancesExample');
app.intent('MoreUtterancesIntent', ['hello sir', 'good evening sir', 'whats up'], () => {
return 'Hello yourself';
});
module.exports = app;
================================================
FILE: examples/multi-language/locales/de/custom-slots.json
================================================
{
"Name": ["Michael", "Michal", "Slavomir"]
}
================================================
FILE: examples/multi-language/locales/de/translation.json
================================================
{
"LaunchRequest": {
"text": "Wilkommen"
},
"SessionEndedRequest": {
"text": "auf Wiedersehen"
},
"LocalizedIntent": {
"utterances": "Meine name ist {name:Name}",
"text": "Hallo {{name}}"
},
"YesIntent": {
"text": "Hallo"
}
}
================================================
FILE: examples/multi-language/locales/en/custom-slots.json
================================================
{
"Name": ["Michael", "Michal", "Slavomir"]
}
================================================
FILE: examples/multi-language/locales/en/translation.json
================================================
{
"LaunchRequest": {
"text": "Welcome"
},
"SessionEndedRequest": {
"text": "Goodbye"
},
"LocalizedIntent": {
"utterances": "My name is {name:Name}",
"text": "Hi {{name}}"
},
"YesIntent": {
"text": "Hi"
}
}
================================================
FILE: examples/multi-language/multi-language-app.js
================================================
'use strict';
const i18next = require('i18next');
const FilesystemBackend = require('i18next-node-fs-backend');
const alexia = require('../..');
const app = alexia.createApp();
// Initialize i18next internationalization
i18next
.use(FilesystemBackend)
.init({
// debug: true,
lng: 'en',
fallbackLng: 'en',
backend: {
loadPath: 'locales/{{lng}}/{{ns}}.json' // Path is relative to your current working directory - change it accordingly
},
preload: ['en', 'de'],
ns: ['translation', 'custom-slots']
});
// Pass i18 to alexia app to make it available for requests and speech assets generation
app.setI18next(i18next);
app.onStart(() => {
return app.t('text');
});
app.onEnd(() => {
return app.t('text');
});
app.intent('LocalizedIntent', slots => {
return app.t('text', slots);
});
app.builtInIntent('yes', () => {
return app.t('text');
});
module.exports = app;
================================================
FILE: examples/original-request-data.js
================================================
'use strict';
const alexia = require('..');
const app = alexia.createApp('OriginalRequestDataExample');
app.intent('OriginalRequestData', 'read original request data', (slots, attrs, data) => {
console.log('userId', data.session.user.userId);
return 'Hi';
});
module.exports = app;
================================================
FILE: examples/response-object.js
================================================
'use strict';
const alexia = require('..');
const app = alexia.createApp('ResponseObjectExample');
app.intent('SSMLIntent', 'response with ssml', () => {
return {
text: '<say-as interpret-as="cardinal">12345</say-as>',
ssml: true
};
});
app.intent('CardIntent', 'response with card', () => {
return {
text: 'Check out card in alexa app',
card: {
title: 'Card title',
content: 'Card content'
}
};
});
app.intent('RepromptIntent', 'response with reprompt', () => {
return {
text: 'I should reprompt you in a second',
reprompt: 'This is a reprompt'
};
});
app.intent('KeepSessionIntent', 'keep session', () => {
return {
text: 'I should terminate this session',
end: false
};
});
module.exports = app;
================================================
FILE: examples/session-attributes.js
================================================
'use strict';
const alexia = require('..');
const app = alexia.createApp('SessionAttributesExample');
// Store attribute
app.intent('AttrsIntent', 'My name is {name:Name}', (slots) => {
return {
text: `Hi ${slots.name}`,
attrs: {name: slots.name},
end: false
};
});
// Use attribute
app.intent('AttrsIntentTwo', 'What is my name', (_slots, attrs) => {
if(attrs.name) {
return `Your name is ${attrs.name}`;
} else {
return 'You are no one';
}
});
// Add another and keep previous attributes
app.intent('AttrsIntentThree', 'I am {age:Number} years old', (slots, attrs) => {
return {
text: `Hi ${slots.name}`,
attrs: Object.assign({}, attrs, {name: slots.age})
};
});
module.exports = app;
================================================
FILE: examples/slots.js
================================================
'use strict';
const alexia = require('..');
const app = alexia.createApp('SlotsExample');
app.intent('SlotsIntent', 'I am {age:Number} years old', (slots) => {
if(slots.age) {
return `Hello person, you are ${slots.age} years old`;
} else {
return 'I did not catch your age';
}
});
// See: https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interaction-model-reference#Slot%20Types
module.exports = app;
================================================
FILE: examples/spech-assets.js
================================================
'use strict';
const alexia = require('..');
const app = alexia.createApp('SpeechAssetsExample');
app.customSlot('Mood', ['fine', 'meh']);
app.intent('WhatsUpIntent', 'I am doing {mood:Mood}', (slots, attrs) => {
return 'Whatever you say';
});
app.intent('AwesomeIntent', 'You are awesome', (slots, attrs) => {
return 'Yes';
});
const speechAssets = app.speechAssets();
console.log(speechAssets.toString());
module.exports = app;
================================================
FILE: examples/start-end.js
================================================
'use strict';
const alexia = require('..');
const app = alexia.createApp('StartEndExample');
app.onStart(() => {
return 'Welcome to this app';
});
app.onEnd(() => {
return 'So long';
});
app.intent('MySampleIntent', 'whatever', () => {
return 'Hello, I guess';
});
module.exports = app;
================================================
FILE: nodemon.json
================================================
{
"ignore": ["node_modules", "speechAssets", "mySpeechAssetsDirectory"]
}
================================================
FILE: package.json
================================================
{
"name": "alexia",
"version": "2.4.0",
"description": "A Framework for creating Amazon Echo (Alexa) skills using Node.js",
"main": "src/alexia.js",
"repository": {
"type": "git",
"url": "git+https://github.com/Accenture/alexia.git"
},
"author": {
"name": "Matthew D. Lancaster",
"email": "matthew.d.lancaster@accenture.com",
"url": "https://www.accenture.com/us-en"
},
"contributors": [
"Slavomir Kubacka <slavomir.kubacka@accenture.com>",
"Michal Morvay <michal.morvay@accenture.com",
"Michael Gloger <michael.gloger@accenture.com>"
],
"scripts": {
"lint": "eslint .",
"lint:fix": "npm run lint -- --fix",
"test": "istanbul cover node_modules/mocha/bin/_mocha -- -u exports --reporter spec",
"test-lcov": "istanbul cover node_modules/mocha/bin/_mocha --report lcovonly -- -u exports --reporter spec",
"test:dev": "nodemon --exec \"mocha || true\"",
"toc": "doctoc README.md --github"
},
"dependencies": {
"debug": "^2.3.0",
"doctoc": "^1.2.0",
"glob": "^7.1.1",
"lodash": "^4.16.6",
"rimraf": "^2.5.4",
"stack-trace": "0.0.9"
},
"devDependencies": {
"chai": "^3.5.0",
"eslint": "^3.9.1",
"eslint-config-standard": "^6.2.1",
"eslint-plugin-promise": "^3.3.1",
"eslint-plugin-standard": "^2.0.1",
"hapi": "^15.2.0",
"i18next": "^4.1.0",
"i18next-node-fs-backend": "^0.1.3",
"istanbul": "^0.4.5",
"mocha": "^3.1.2",
"nodemon": "^1.11.0",
"request": "^2.78.0",
"sinon": "^1.17.6"
},
"engines": {
"node": ">=4.2.0"
},
"license": "MIT",
"files": [
"LICENSE",
"README.md",
"src/"
],
"keywords": [
"alexa",
"alexia",
"echo",
"amazon",
"voice",
"skill",
"intent"
],
"bugs": {
"url": "https://github.com/Accenture/alexia/issues",
"email": "michael.gloger@accenture.com"
}
}
================================================
FILE: src/alexia.js
================================================
'use strict';
const createApp = require('./create-app');
const createRequest = require('./create-request');
module.exports = {
createApp,
createRequest,
createLaunchRequest: (attrs, appId) => {
return createRequest({type: 'LaunchRequest', new: true, attrs, appId});
},
createSessionEndedRequest: (attrs, appId) => {
return createRequest({type: 'SessionEndedRequest', new: false, attrs, appId});
},
createIntentRequest: (name, slots, attrs, isNew, appId) => {
return createRequest({
type: 'IntentRequest',
name,
slots,
new: isNew,
attrs,
appId
});
}
};
================================================
FILE: src/built-in-intents-map.js
================================================
'use strict';
module.exports = {
cancel: 'AMAZON.CancelIntent',
help: 'AMAZON.HelpIntent',
next: 'AMAZON.NextIntent',
no: 'AMAZON.NoIntent',
pause: 'AMAZON.PauseIntent',
previous: 'AMAZON.PreviousIntent',
repeat: 'AMAZON.RepeatIntent',
resume: 'AMAZON.ResumeIntent',
startOver: 'AMAZON.StartOverIntent',
stop: 'AMAZON.StopIntent',
yes: 'AMAZON.YesIntent'
};
================================================
FILE: src/built-in-slots-map.js
================================================
'use strict';
module.exports = {
'Date': 'AMAZON.DATE',
'Duration': 'AMAZON.DURATION',
'FourDigitNumber': 'AMAZON.FOUR_DIGIT_NUMBER',
'Number': 'AMAZON.NUMBER',
'Time': 'AMAZON.TIME',
'USCity': 'AMAZON.US_CITY',
'USFirstname': 'AMAZON.US_FIRST_NAME',
'USState': 'AMAZON.US_STATE',
'ATCity': 'AMAZON.AT_CITY',
'ATRegion': 'AMAZON.AT_REGION',
'DECity': 'AMAZON.DE_CITY',
'DEFirstname': 'AMAZON.DE_FIRST_NAME',
'DERegion': 'AMAZON.DE_REGION',
'GBCity': 'AMAZON.GB_CITY',
'GBFirstname': 'AMAZON.GB_FIRST_NAME',
'GBRegion': 'AMAZON.GB_REGION',
'EUCity': 'AMAZON.EUROPE_CITY',
'Literal': 'AMAZON.LITERAL'
};
================================================
FILE: src/create-app.js
================================================
'use strict';
const _ = require('lodash');
const nodeDebug = require('debug');
const glob = require('glob');
const path = require('path');
const handleRequest = require('./handle-request');
const createIntent = require('./create-intent');
const createCustomSlot = require('./create-custom-slot');
const generateSpeechAssets = require('./generate-speech-assets');
const generateSpeechAssetsI18n = require('./generate-speech-assets-i18n');
const saveSpeechAssets = require('./save-speech-assets');
const builtInIntentsMap = require('./built-in-intents-map');
const createServer = require('./create-server');
const parseError = require('./error-handler').parseError;
const rimraf = require('rimraf');
const fs = require('fs');
const builtInIntentsList = _.keys(builtInIntentsMap).join(', ');
const debug = nodeDebug('alexia:debug');
/**
* Create new app
* @param {string} name - App name
* @param {Object} [options] - Additional app options
* @param {string} [options.version] - App version
* @param {string[]} [options.ids] - Array of app ids. Only requests with supported app ids will be handled
*/
module.exports = (name, options) => {
let app = {
name: name,
options: options || {},
intents: {},
customSlots: {},
actions: [],
i18next: undefined,
t: key => key
};
let handlers = {
onStart: () => 'Welcome',
onEnd: () => 'Bye',
defaultActionFail: () => 'Sorry, your command is invalid'
};
/**
* Sets handler to be called on application start
* @param {function} handler - Handler to be called when app is started without intent
*/
app.onStart = (handler) => {
handlers.onStart = handler;
};
/**
* Sets handler to be called on application end
* @param {function} handler - Handler to be called when application is unexpectedly terminated
*/
app.onEnd = (handler) => {
handlers.onEnd = handler;
};
/**
* Sets handler to be called on default action fail
* @param {function} handler - Default handler to be called when action can not be invoked
*/
app.defaultActionFail = (handler) => {
handlers.defaultActionFail = handler;
};
/**
* Sets shouldEndSessionByDefault option that says if session should end after
* @param {boolean} value - default value for shouldEndSession attribute
*/
app.setShouldEndSessionByDefault = (value) => {
app.options.shouldEndSessionByDefault = value;
};
/**
* Creates intent
* @param {string} name - Intent name. Should not be equal to built-in intent name. It is possible to use this function to create built-in intents but utterances are required argument and you need to specify full built-in intent name f.e. `AMAZON.StopIntent`. See `{@link app.builtInIntent}`. If not specified (null, undefined or empty string), automatically generated intent name is used but we recommend to name each intent
* @param {(string|string[])} richUtterances - one or more utterances. Utterances contain utterance description with slots types. Example: `My age is {age:Number}`
* @param {function} handler - Function to be called when intent is invoked
*/
app.intent = (name, richUtterances, handler) => {
// Shift ommited arguments (utternaces are optional)
if (!handler) {
handler = richUtterances;
richUtterances = undefined;
}
const intent = createIntent(app.intents, name, richUtterances, handler);
app.intents[intent.name] = intent;
return intent;
};
/**
* Creates built-int intent.
* Essentialy the same as `intent` but with optional `utterances` since we need to specify each built-in intent has its own set of default utterances you are not required to extend
* @param {string} name - Built-in Intent name. Must be one of: `cancel`, `help`, `next`, `no`, `pause`, `previous`, `repeat`, `resume`, `startOver`, `stop`, `yes`
* @param {(string|string[]|function)} [utterances] - one or more utterances without slots. Could be ommited and handler could be 2nd parameter instead
* @param {function} handler - Function to be called when intent is invoked
*/
app.builtInIntent = (name, utterances, handler) => {
// Validate built-in intent name
if (!builtInIntentsMap[name]) {
const e = parseError(new Error(`Built-in Intent name ${name} is invalid. Please use one of: ${builtInIntentsList}`));
throw e;
}
// Shift ommited arguments (utternaces are optional)
if (!handler) {
handler = utterances;
utterances = undefined;
}
app.intent(name, utterances, handler);
};
/**
* Handles request and calls done when finished
* @param {Object} data - Request JSON to be handled.
* @param {Function} done - Callback to be called when request is handled. Callback is called with one argument - response JSON
*/
app.handle = (data, done) => {
// Internationalization is enabled and locale is specified in request
if (app.i18next) {
const locale = data.request.locale || 'en-US';
// Make sure all locale resources are loaded
app.i18next.loadResources(() => {
// Get translation function for current locale
const t = app.i18next.getFixedT(locale, 'translation');
// Prefix key by using intent name or request type for launch / end requests
let prefix;
if (data.request.type === 'IntentRequest') {
prefix = _.last(data.request.intent.name.split('.'));
} else {
// Transform f.e: AMAZON.YesIntent -> YesIntent
prefix = data.request.type;
}
// Wrap translation function and prepend prefix to keys to make them shorter
app.t = (key, options) => {
return t(`${prefix}.${key}`, options);
};
// Handle request
handleRequest(app, data, handlers, done);
});
} else {
// Otherwise just handle request
handleRequest(app, data, handlers, done);
}
};
/**
* Creates custom slot
* @param {string} name - Name of the custom slot
* @param {string[]} samples - Array of custom slot samples
*/
app.customSlot = (name, samples) => {
const customSlot = createCustomSlot(app.customSlots, name, samples);
app.customSlots[name] = customSlot;
};
/**
* Creates action
* @param {string} action - Action object
* @param {string} action.from - Name of the intent to allow transition from
* @param {string} action.to - Name of the intent to allow transition to
* @param {function} action.if - Function returning boolean whether this transition should be handled.
* @param {function} action.fail - Handler to be called if `action.if` returned `false`
*/
app.action = (action) => {
if (Array.isArray(action.from)) {
action.from.forEach(fromItem => {
if (Array.isArray(action.to)) {
action.to.forEach(toItem => {
addAction(app.actions, fromItem, toItem, action.if, action.fail);
});
} else {
addAction(app.actions, fromItem, action.to, action.if, action.fail);
}
});
} else {
if (Array.isArray(action.to)) {
action.to.forEach(toItem => {
addAction(app.actions, action.from, toItem, action.if, action.fail);
});
} else {
addAction(app.actions, action.from, action.to, action.if, action.fail);
}
}
};
/**
* Generate speech assets object: {schema, utterances, customSlots}
* @deprecated Use `app.saveSpeechAssets()` instead
*/
app.speechAssets = () => {
return generateSpeechAssets(app);
};
/**
* Save speech assets to their respective files: intentSchema.json, utterances.txt, customSlots.txt
* @param {string} [directory] - directory folder name, defaults to '/speechAssets'
* @param {function} [done] - callback to be called once assets are saved (useful for internationalized apps)
*/
app.saveSpeechAssets = (directory, done) => {
const dir = directory || 'speechAssets';
rimraf.sync(dir);
// No internationalization
if (!app.i18next) {
const assets = generateSpeechAssets(app);
saveSpeechAssets(assets, dir);
if (done) done();
} else {
// Internationalization is enabled
app.i18next.loadResources(() => {
const localizedAssets = generateSpeechAssetsI18n(app);
fs.mkdirSync(dir);
_.forEach(localizedAssets, (assets, locale) => {
saveSpeechAssets(assets, `${dir}/${locale}`);
});
/* istanbul ignore else */
if (done) done();
});
}
};
/**
* Creates Hapi server with one route that handles all request for this app. Server must be started using `server.start()`
* @param {number} [options] - Server options
* @property {number} [options.path] - Path to run server route on. Defaults to `/`
* @property {number} [options.port] - Port to run server on. If not specified then `process.env.PORT` is used. Defaults to `8888`
* @returns {object} server
*/
app.createServer = (options) => {
return createServer(app, options);
};
/**
* Registers all intents matching specified pattern
* @param {string} pattern - Pattern used for intent matching. Must be relative to project root. Example: 'src/intents/**.js'
*/
app.registerIntents = (pattern) => {
const files = glob.sync(pattern);
if (files.length === 0) {
console.warn(`No intents found using pattern '${pattern}'`);
}
files.forEach(intentFile => {
debug(`Registering intent '${intentFile}'`);
require(path.relative(__dirname, intentFile))(app);
});
};
/**
* Sets i18next instance.
* Use this to enable internationalization and make it available to the app
*/
app.setI18next = i18next => {
app.i18next = i18next;
};
return app;
};
/**
* Adds action to app's actions array
* @param {object} actions - Actions object of alexia app
* @param {string} from - Name of the intent to allow transition from
* @param {string} to - Name of the intent to allow transition to
* @param {function} condition - Function returning boolean whether this transition should be handled
* @param {function} fail - Handler to be called if `condition` returned `false`
*/
const addAction = (actions, from, to, condition, fail) => {
actions.push({
from: typeof (from) === 'string' ? from : from.name,
to: typeof (to) === 'string' ? to : to.name,
if: condition,
fail: fail
});
};
================================================
FILE: src/create-custom-slot.js
================================================
'use strict';
const _ = require('lodash');
const builtInSlotsMap = require('./built-in-slots-map');
const validator = require('./validator');
const builtInSlotsValues = _.values(builtInSlotsMap);
const parseError = require('./error-handler').parseError;
/**
* Creates custom slot. Checks if custom slot name is not conflicting with amazon built in slots
* @param {Object} customSlots - Map of custom slot names to custom slots
* @param {string} name - Name of the custom slot
* @param {string[]} samples - Array of custom slot samples
*/
module.exports = (customSlots, name, samples) => {
if (customSlots[name]) {
const e = parseError(new Error(`Slot with name ${name} is already defined`));
throw e;
}
if (builtInSlotsMap[name] || builtInSlotsValues.indexOf(name) !== -1) {
const e = parseError(new Error(`Slot with name ${name} is already defined in built-in slots`));
throw e;
}
if (!validator.isCustomSlotNameValid(name)) {
const e = parseError(new Error(`Custom slot name ${name} is invalid. Only lowercase, uppercase letters and underscores are allowed`));
throw e;
}
samples.forEach(sample => {
if (validator.isCustomSlotValueValid(sample)) {
const e = parseError(new Error(`Custom slot with name ${name} contains invalid special character(~, ^, *, (, ), [, ], §, !, ?, ;, :, " and |): ${sample}`));
throw e;
}
});
return {
name,
samples
};
};
================================================
FILE: src/create-intent.js
================================================
'use strict';
const _ = require('lodash');
const builtInIntentsMap = require('./built-in-intents-map');
const validator = require('./validator');
const parseError = require('./error-handler').parseError;
const parseRichUtterances = require('./parse-rich-utterances');
/**
* Creates intent
* @param {Object[]} intents - Array of intents. Required for determining taken intent names
* @param {string} name - Name of the intent. If null or undefined, automatically generated intent name is used
* @param {(string|string[])} richUtterances - Utterance or array of rich utterances
* @param {function} handler - Function to be called when intent is invoked
*/
module.exports = (intents, name, richUtterances, handler) => {
// Convert utterances to array
richUtterances = _.isArray(richUtterances) ? richUtterances : [richUtterances];
if (!name) {
const e = parseError(new Error(`All intents must have name.`));
throw e;
} else if (!validator.isNameValid(name)) {
const e = parseError(new Error(`Intent name ${name} is invalid. Only lowercase and uppercase letters are allowed.`));
throw e;
} else if (builtInIntentsMap[name]) {
// If built-in intent name was used map intent name to it
name = builtInIntentsMap[name];
}
// Transformed slots and utterances from richUtterances
let slots = [];
let utterances = [];
parseRichUtterances(richUtterances, slots, utterances);
return {
name: name,
slots: slots,
utterances: utterances,
handler: handler
};
};
================================================
FILE: src/create-request.js
================================================
'use strict';
const _ = require('lodash');
/**
* Creates Alexa request
* @param [options]
* @param [options.type]
* @param [options.name]
* @param [options.slots]
* @param [options.attrs]
* @param [options.appId]
* @param [options.sessionid]
* @param [options.userId]
* @param [options.requestId]
* @param [options.timestamp]
* @param [options.locale]
* @param [options.new]
*/
module.exports = options => {
// Assign default request options
options = Object.assign({
type: 'IntentRequest',
name: 'UnknownIntent',
slots: {},
attrs: {},
appId: 'amzn1.echo-sdk-123456',
sessionId: 'SessionId.357a6s7',
userId: 'amzn1.account.abc123',
requestId: 'EdwRequestId.abc123456',
timestamp: '2016-06-16T14:38:46Z',
locale: 'en-US',
new: false
}, options);
let intent;
if (options.type === 'IntentRequest') {
// Transform slots from minimal schema into slot schema sent by Amazon
const transformedSlots = _.transform(options.slots, (result, key, value) => {
result[key] = {
name: value,
value: key
};
}, {});
intent = {
name: options.name,
slots: options.slots ? transformedSlots : undefined
};
}
return {
session: {
attributes: options.attrs || {},
sessionId: options.sessionId,
application: {
applicationId: options.appId
},
user: {
userId: options.userId
},
new: options.new
},
request: {
type: options.type,
requestId: options.requestId,
timestamp: options.timestamp,
intent: intent,
locale: options.locale
}
};
};
================================================
FILE: src/create-server.js
================================================
'use strict';
const debug = require('debug')('alexia:debug');
const info = require('debug')('alexia:info');
/**
* Creates Hapi server with one route that handles all requests with app specified in param. Server must be started using `server.start()`
* @param {object} app - App created using alexia.createApp(...)
* @param {number} [options] - Server options
* @property {number} [options.path] - Path to run server route on. Defaults to `/`
* @property {number} [options.port] - Port to run server on. If not specified then `process.env.PORT` is used. Defaults to `8888`
* @returns {object} server
*/
module.exports = (app, options) => {
const Hapi = require('hapi');
const server = new Hapi.Server();
options = Object.assign({}, options);
server.connection({
port: options.port || process.env.PORT || 8888
});
server.route({
path: options.path || '/',
method: 'POST',
handler: (request, response) => {
app.handle(request.payload, (data) => {
response(data);
});
}
});
info(`Server created on URI: "${server.info.uri}"`);
debug(`Server created on URI: "${server.info.uri}"`);
return server;
};
================================================
FILE: src/error-handler.js
================================================
'use strict';
const stackTrace = require('stack-trace');
module.exports = {
/**
* @returns {string} formatted stack trace for errors
* Evaluates every stack trace item and compose them together to make sure nothing important is missing
*/
parseError: (e) => {
const error = stackTrace.parse(e);
let res = [];
res.push(`Error: ${e.message}`);
error.forEach((trace) => {
if (trace.fileName && trace.lineNumber && trace.columnNumber) {
const functionName = trace.functionName || '';
const struct = `\t at ${functionName} (${trace.fileName}:${trace.lineNumber}:${trace.columnNumber})`;
res.push(struct);
}
});
return res.join('\n');
}
};
================================================
FILE: src/generate-speech-assets-i18n.js
================================================
'use strict';
const _ = require('lodash');
const parseRichUtterances = require('./parse-rich-utterances');
module.exports = (app) => {
let assets = {};
const languages = app.i18next.options.preload;
_.each(languages, locale => {
const intentSchemaAndUtterances = genIntentSchemaAndUtterances(app, locale);
assets[locale] = {
intentSchema: intentSchemaAndUtterances.intentSchema,
utterances: intentSchemaAndUtterances.utterances,
customSlots: genCustomSlots(app, locale)
};
});
return assets;
};
/**
* Generates intent schema JSON string and utterances
* @return {string} strigified intent schema object generated from intents
*/
const genIntentSchemaAndUtterances = (app, locale) => {
let intentSchema = {
intents: []
};
let allUtterances = [];
// Load complete locale resource
const localeResource = app.i18next.getResource(locale, 'translation');
// For each intent
_.each(app.intents, intent => {
let currentSchema = {
intent: intent.name
};
intentSchema.intents.push(currentSchema);
// Remove AMAZON prefix for built-in intents
const nameWithoutAmazonPrefix = _.last(intent.name.split('.'));
// Get current intent resource
const intentResource = localeResource[nameWithoutAmazonPrefix];
if (!intentResource) {
return;
}
let slots = [];
let utterances = [];
// Transform intent utterances to array if its string
let intentUtterances = _.isArray(intentResource.utterances) ? intentResource.utterances : [intentResource.utterances];
// Parse rich utterances to extract slot types and transform utterances to simple form
parseRichUtterances(intentUtterances, slots, utterances);
// Slots found
if (slots.length > 0) {
currentSchema.slots = slots;
}
// Iterate over each utterance, transform and add it to array
_.each(utterances, utterance => {
if (utterance) {
allUtterances.push(`${intent.name} ${utterance}`);
}
});
});
return {
intentSchema: JSON.stringify(intentSchema, null, 2),
utterances: allUtterances.join('\n')
};
};
/**
* @return {object} where key = slot type and value is string interpretation of
* custom slot type samples
*/
const genCustomSlots = (app, locale) => {
return app.i18next.getResource(locale, 'custom-slots');
};
================================================
FILE: src/generate-speech-assets.js
================================================
'use strict';
const _ = require('lodash');
module.exports = (app) => {
let assets = {
intentSchema: genIntentSchema(app.intents),
utterances: genUtterances(app.intents),
customSlots: genCustomSlots(app.customSlots)
};
assets.toString = createStringifyAssets(assets);
return assets;
};
/**
* @param {object} assets
* @param {object} assets.intentSchema
* @param {object} assets.utterances
* @param {object} assets.customSlots
* @returns {function} returning stringified version of speech assetsuseful for printing in terminal
*/
const createStringifyAssets = (assets) => () => {
let customSlotsString = _.map(assets.customSlots, (samples, name) => {
return `${name}:\n${samples.join('\n')}\n`;
}).join('\n');
return createAsset('intentSchema', assets.intentSchema) +
createAsset('utterances', assets.utterances) +
createAsset('customSlots', customSlotsString);
};
/**
* Creates stringified part of speechAssets
* @param {string} type
* @param {object} data
* @returns {string}
*/
const createAsset = (type, data) => {
return `${type}:\n${data}\n\n`;
};
/**
* Generates intent schema JSON string
* @return {string} strigified intent schema object generated from intents
*/
const genIntentSchema = (intents) => {
let intentSchema = {
intents: []
};
_.forOwn(intents, intent => {
let currentSchema = {
intent: intent.name
};
// Property slots is optional
if (intent.slots && intent.slots.length > 0) {
currentSchema.slots = intent.slots;
}
intentSchema.intents.push(currentSchema);
});
return JSON.stringify(intentSchema, null, 2);
};
/**
* Generates sample utterances tied to intent name
* @return {string} interpretation of all sample utterances
*/
const genUtterances = (intents) => {
let sampleUtterances = [];
_.forOwn(intents, intent => {
intent.utterances.forEach(utterance => {
if (utterance) {
sampleUtterances.push(`${intent.name} ${utterance}`);
}
});
});
return sampleUtterances.join('\n');
};
/**
* @return {object} where key = slot type and value is string interpretation of
* custom slot type samples
*/
const genCustomSlots = (customSlots) => {
let allCustomSlotSamples = {};
_.forOwn(customSlots, (customSlot) => {
allCustomSlotSamples[customSlot.name] = customSlot.samples;
});
return allCustomSlotSamples;
};
================================================
FILE: src/handle-request.js
================================================
'use strict';
const _ = require('lodash');
const debug = require('debug')('alexia:debug');
const info = require('debug')('alexia:info');
const parseError = require('./error-handler').parseError;
/**
* Handles request and calls done when finished
* @param {Object} app - Application object
* @param {Object|string} data - Request JSON or JSON string to be handled
* @param {Function} handlers - Handlers to be called. Contains onStart, onEnd, actionFail
* @param {Function} done - Callback to be called when request is handled. Callback is called with one argument - response JSON
*/
module.exports = (app, data, handlers, done) => {
data = typeof data === 'object' ? data : JSON.parse(data);
const appId = data.session.application.applicationId;
const options = app.options;
// Application ids is specified and does not contain app id in request
if (options && options.ids && options.ids.length > 0 && options.ids.indexOf(appId) === -1) {
const e = parseError(new Error(`Application id: '${appId}' is not valid`));
throw e;
}
if (!data.session.attributes) {
data.session.attributes = {};
}
const requestType = data.request.type;
info(`Handling request: "${requestType}"`);
debug(`Request payload: ${JSON.stringify(data, null, 2)}`);
switch (requestType) {
case 'LaunchRequest':
callHandler(handlers.onStart, null, data.session.attributes, app, data, false, done);
break;
case 'IntentRequest':
const intentName = data.request.intent.name;
const intent = app.intents[data.request.intent.name];
info(`Handling intent: "${intentName}"`);
if (!intent) {
const e = parseError(new Error(`Nonexistent intent: '${intentName}'`));
throw e;
}
checkActionsAndHandle(intent, data.request.intent.slots, data.session.attributes, app, handlers, data, done);
break;
case 'SessionEndedRequest':
callHandler(handlers.onEnd, null, data.session.attributes, app, data, false, done);
break;
default:
const e = parseError(new Error(`Unsupported request: '${requestType}'`));
throw e;
}
};
/**
* @returns {String} options.attrs.previousIntent if set, otherwise returns previousIntent
*/
const getPreviousIntent = (options, previousIntent) => {
if (options.attrs && options.attrs.previousIntent) {
return options.attrs.previousIntent;
} else {
return previousIntent;
}
};
const callHandler = (handler, slots, attrs, app, data, error, done) => {
// Transform slots into simple key:value schema
slots = _.transform(slots, (result, value) => {
result[value.name] = value.value;
}, {});
const optionsReady = options => {
if (data.session.new && data.request.type === 'LaunchRequest') {
attrs.previousIntent = getPreviousIntent(options, '@start');
} else if (!error && !options.error && data.request.type === 'IntentRequest') {
attrs.previousIntent = getPreviousIntent(options, data.request.intent.name);
}
done(createResponse(options, slots, attrs, app));
};
// Handle intent synchronously if has < 4 arguments. 4th is `done`
if (handler.length < 4) {
optionsReady(handler(slots, attrs, data));
} else {
handler(slots, attrs, data, optionsReady);
}
};
/**
* Checks for `actions` presence to help us with Alexa conversation workflow configuration
*
* 1) no actions: just call the intent.handler method without any checks
* 2) with actions: check if action for current intent transition is found
* a) action found: call its `if` function and if condition fails run `fail` function
* b) no action: call default `fail` function
*
* @param intent
* @param slots
* @param attrs
* @param app
* @param handlers
* @param data
* @param done
*/
const checkActionsAndHandle = (intent, slots, attrs, app, handlers, data, done) => {
if (app.actions.length === 0) {
// There are no actions. Just call handler on this intent
callHandler(intent.handler, slots, attrs, app, data, false, done);
} else {
// If there are some actions, try to validate current transition
let action = _.find(app.actions, {from: attrs.previousIntent, to: intent.name});
// Try to find action with wildcards if no action was found
if (!action) {
action = _.find(app.actions, {from: attrs.previousIntent, to: '*'});
}
if (!action) {
action = _.find(app.actions, {from: '*', to: intent.name});
}
if (action) {
// Action was found. Check if this transition is valid
if (action.if ? action.if(slots, attrs) : true) {
// Transition is valid. Remember intentName and handle intent
callHandler(intent.handler, slots, attrs, app, data, false, done);
} else {
// Transition is invalid. Call fail function
if (action.fail) {
callHandler(action.fail, slots, attrs, app, data, true, done);
} else {
callHandler(handlers.defaultActionFail, slots, attrs, app, data, true, done);
}
}
} else {
// No action found
callHandler(handlers.defaultActionFail, slots, attrs, app, data, true, done);
}
}
};
/**
* Creates card object with default card type
* @param {Object} card - Card object from responseData options
* @returns {Object} card - Card object or undefined if card is not specified
*/
const createCardObject = (card) => {
if (card) {
// Set default card type to 'Simple'
if (!card.type) {
card.type = 'Simple';
}
return card;
}
};
/**
* Reads options.end and returns bool indicating whether to end session
* @param {object} [intentOptions] Options object for intent
* @param {object} [appOptions] Options object for whole app
* @returns bool from intentOptions.end or appOptions.shouldEndSessionByDefault, if any of them is set than returns true
*/
const getShouldEndSession = (intentOptions, appOptions) => {
if (!intentOptions || intentOptions.end === undefined) {
if (!appOptions || appOptions.shouldEndSessionByDefault === undefined) {
return true;
} else {
return appOptions.shouldEndSessionByDefault;
}
}
return intentOptions.end;
};
const createResponse = (options, slots, attrs, app) => {
// Convert text options to object
if (typeof (options) === 'string') {
options = {
text: options
};
}
// Create outputSpeech object for text or ssml
const outputSpeech = createOutputSpeechObject(options.text, options.ssml);
let sessionAttributes;
if (options.attrs) {
// Use session attributes from responseObject and remember previousIntent
sessionAttributes = options.attrs;
sessionAttributes.previousIntent = attrs.previousIntent;
} else {
// No session attributes specified in user response
sessionAttributes = attrs;
}
let responseObject = {
version: (app.options && app.options.version) ? app.options.version : '0.0.1',
sessionAttributes: sessionAttributes,
response: {
outputSpeech: outputSpeech,
shouldEndSession: getShouldEndSession(options, app.options)
}
};
if (options.reprompt) {
responseObject.response.reprompt = {
outputSpeech: createOutputSpeechObject(options.reprompt, options.ssml)
};
}
let card = createCardObject(options.card);
if (card) {
responseObject.response.card = card;
}
return responseObject;
};
/**
* Creates output speech object used for text response or reprompt
* @param {string} text - Text or Speech Synthesis Markup (see sendResponse docs)
* @param {bool} ssml - Whether to use ssml
* @returns {Object} outputSpeechObject in one of text or ssml formats
*/
const createOutputSpeechObject = (text, ssml) => {
let outputSpeech = {};
if (text.includes('<speak>') || ssml) {
outputSpeech.type = 'SSML';
outputSpeech.ssml = text;
} else {
outputSpeech.type = 'PlainText';
outputSpeech.text = text;
}
return outputSpeech;
};
================================================
FILE: src/parse-rich-utterances.js
================================================
'use strict';
const builtInSlotsMap = require('./built-in-slots-map');
const validator = require('./validator');
const parseError = require('./error-handler').parseError;
const _ = require('lodash');
module.exports = (richUtterances, slots, utterances) => {
// Iterate over each rich utterance and transform it by removing slots description
_.each(richUtterances, function (utterance) {
var matches = findUtteranceMatches(utterance);
_.each(matches, function (match) {
const slotName = match[1];
const slotType = match[2];
// Prevent duplicate slot definition
if (!_.find(slots, {name: slotName})) {
// Remember slot type
slots.push({
name: slotName,
type: transformSlotType(slotType)
});
}
// Replace utterance slot type (there could be multiple slots in utterance)
utterance = utterance.replace(match[0], '{' + slotName + '}');
});
if (validator.isUtteranceValid(utterance)) {
// Remember utterance
utterances.push(utterance);
} else {
const e = parseError(new Error(`Sample utterance: '${utterance}' is not valid. Each sample utterance must consist only of alphabet characters, spaces, dots, hyphens, brackets and single quotes`));
throw e;
}
});
};
const transformSlotType = (type) => {
const transformedType = builtInSlotsMap[type];
return transformedType || type;
};
const findUtteranceMatches = (utterance) => {
// Example: for 'move forward by {value:Number}' we get:
// [[ '{value:Number}', 'value', 'Number', index: 16, input: 'move forward by {value:Number}' ]]
const myregex = /{(.*?):(.*?)\}/gmi;
let result;
let allMatches = [];
while ((result = myregex.exec(utterance)) != null) {
allMatches.push(result);
}
return allMatches;
};
================================================
FILE: src/save-speech-assets.js
================================================
'use strict';
const fs = require('fs');
const path = require('path');
const _ = require('lodash');
module.exports = (assets, directory) => {
fs.mkdirSync(directory);
// Save intentSchema.json and utterances.txt
saveToFile(assets.intentSchema, directory, 'intentSchema', 'json');
saveToFile(assets.utterances, directory, 'utterances', 'txt');
// Save customSlots
const customSlotsDir = path.join(directory, 'customSlots');
const customSlotNames = _.keys(assets.customSlots);
if (customSlotNames && customSlotNames.length > 0) {
fs.mkdirSync(customSlotsDir);
customSlotNames.forEach((key) => {
const newLineFormat = assets.customSlots[key].join('\n');
saveToFile(newLineFormat, customSlotsDir, key, 'txt');
});
}
};
/**
* Saves data to directory with specified filename and extension
* @param {object|string} data
* @param {string} directory
* @param {string} filename
* @param {string} extension
*/
const saveToFile = (data, directory, filename, extension) => {
const pathname = path.join(directory, `${filename}.${extension}`);
fs.writeFileSync(pathname, data);
};
================================================
FILE: src/validator.js
================================================
'use strict';
module.exports = {
/**
* @returns {boolean} whether given name is correct or not.
* Each name must consist only of lowercase and uppercase letters
*/
isNameValid: (name) => /^[a-zA-Z]+$/.test(name),
/**
* @returns {boolean} whether given utterance is correct or not.
* Each sample utterance must consist only of alphabets, white-spaces and valid
* punctuation marks. Valid punctuation marks are periods for abbreviations,
* possesive apostrophes, hyphens and brackets for slots.
* @see https://developer.amazon.com/appsandservices/solutions/alexa/alexa-skills-kit/docs/defining-the-voice-interface
*/
isUtteranceValid: (utterance) => /^[a-z\s.'\-{}äöüÄÖÜß]*$/gmi.test(utterance),
/**
* @returns {boolean} whether given custom slot name is correct or not.
* Each custom slot name must consist only of lowercase, uppercase letters and underscores
*/
isCustomSlotNameValid: (name) => /^[a-zA-Z_]+$/.test(name),
/**
* @returns {boolean} whether given custom slot value is correct or not.
* Each custom slot value must not include special characters as ~, ^, *, (, ), [, ], §, !, ?, ;, :, " and |
* @see https://developer.amazon.com/public/solutions/alexa/alexa-skills-kit/docs/alexa-skills-kit-interaction-model-reference#h2_custom_syntax
*/
isCustomSlotValueValid: (value) => /[~^*()[\]§!?;:"|]+$/gmi.test(value)
};
================================================
FILE: test/.eslintrc
================================================
{
"globals": {
"describe": true,
"before": true,
"after": true,
"beforeEach": true,
"afterEach": true,
"it": true,
"expect": true,
"global": true
}
}
================================================
FILE: test/actions.spec.js
================================================
'use strict';
const expect = require('chai').expect;
const app = require('./test-apps/actions-app');
const alexia = require('..');
describe('action app handler', () => {
let attrs;
it('should handle IntentA and remember previousIntent', (done) => {
const request = alexia.createIntentRequest('IntentA', null, null, true);
app.handle(request, (response) => {
attrs = response.sessionAttributes;
expect(response).to.be.defined;
expect(response.sessionAttributes.previousIntent).to.equal('IntentA');
done();
});
});
it('should handle IntentB and remember previousIntent', (done) => {
// console.log(attrs)
const request = alexia.createIntentRequest('IntentB', null, attrs);
app.handle(request, (response) => {
expect(response).to.be.defined;
expect(response.sessionAttributes.previousIntent).to.equal('IntentB');
done();
});
});
it('should not handle nonexistent intent and throw error', () => {
const request = alexia.createIntentRequest('IntentZ', null, attrs);
try {
app.handle(request);
} catch (e) {
expect(e).to.include('Nonexistent intent: \'IntentZ\'');
}
});
it('should not handle IntentB again', (done) => {
const request = alexia.createIntentRequest('IntentB', null, attrs);
app.handle(request, (response) => {
expect(response.response.outputSpeech.text).to.equal('Sorry, your command is invalid');
done();
});
});
it('should not handle IntentB again with defaultActionFail', (done) => {
const request = alexia.createIntentRequest('IntentB', null, attrs);
app.defaultActionFail(() => 'Sry bye');
app.handle(request, (response) => {
expect(response.response.outputSpeech.text).to.equal('Sry bye');
done();
});
});
it('should not handle IntentB -> IntentC', (done) => {
const request = alexia.createIntentRequest('IntentC', null, attrs);
app.handle(request, (response) => {
expect(response.response.outputSpeech.text).to.equal('Could not handle request');
done();
});
});
it('should not handle IntentB -> IntentD', (done) => {
const request = alexia.createIntentRequest('IntentD', null, attrs);
app.handle(request, (response) => {
expect(response.response.outputSpeech.text).to.equal('Sry bye');
done();
});
});
it('should handle async IntentE with session attributes', (done) => {
const request = alexia.createIntentRequest('IntentE', null, attrs);
app.handle(request, (response) => {
expect(response.sessionAttributes).to.deep.equal({
previousIntent: 'IntentE',
foo: true
});
done();
});
});
it('should handle Intent and not update previousIntent if responseObject.error is true', (done) => {
const app2 = alexia.createApp();
app2.intent('Intent', () => {
return {
text: 'Some error occured',
error: true
};
});
const anotherAttrs = {
previousIntent: 'SomeIntent'
};
const request = alexia.createIntentRequest('Intent', null, anotherAttrs);
app2.handle(request, (response) => {
expect(response.sessionAttributes.previousIntent).to.equal('SomeIntent');
done();
});
});
it('should handle Intent and override previousIntent', (done) => {
const app2 = alexia.createApp();
app2.intent('Intent', () => {
return {
text: 'Hello, World',
attrs: {
previousIntent: 'MyCustomValue'
}
};
});
const request = alexia.createIntentRequest('Intent');
app2.handle(request, (response) => {
expect(response.sessionAttributes.previousIntent).to.equal('MyCustomValue');
done();
});
});
});
================================================
FILE: test/basic.spec.js
================================================
'use strict';
const _ = require('lodash');
const expect = require('chai').expect;
const alexia = require('..');
const app = require('./test-apps/basic-app');
const intents = _.values(app.intents);
describe('basic app handler', () => {
let attrs;
it('should set shouldEndSessionByDefault', (done) => {
const testApp = alexia.createApp('testApp');
testApp.setShouldEndSessionByDefault(false);
testApp.handle(alexia.createLaunchRequest(), (response) => {
expect(response).to.be.defined;
expect(response.response.shouldEndSession).to.equal(false);
done();
});
});
it('should create default alexa request', () => {
const data = alexia.createRequest();
expect(data.request.type).to.equal('IntentRequest');
expect(data.request.intent.name).to.equal('UnknownIntent');
});
it('should handle LaunchRequest', (done) => {
const request = alexia.createLaunchRequest(null, 'appId1');
app.handle(request, response => {
expect(response).to.be.defined;
done();
});
});
it('should handle SessionEndedRequest', (done) => {
const request = alexia.createSessionEndedRequest(null, 'appId1');
app.handle(request, response => {
expect(response).to.be.defined;
done();
});
});
it('should handle custom LaunchRequest', (done) => {
const app2 = alexia.createApp('TestApp1');
app2.onStart(() => 'foo');
app2.handle(alexia.createLaunchRequest(), (response) => {
expect(response).to.be.defined;
expect(response.response.outputSpeech.text).to.equal('foo');
done();
});
});
it('should handle custom SessionEndedRequest', (done) => {
const app2 = alexia.createApp('TestApp2');
app2.onEnd(() => 'bar');
app2.handle(alexia.createSessionEndedRequest(), (response) => {
expect(response).to.be.defined;
expect(response.response.outputSpeech.text).to.equal('bar');
done();
});
});
it('should handle FirstIntent request', (done) => {
// Same as alexia.createIntentRequest('FirstIntent', null, null, false, 'appId1');
const request = alexia.createRequest({name: 'FirstIntent', appId: 'appId1'});
// Remove empty session attributes to cover scenario when missing
request.session.attributes = undefined;
const expectedResponse = {
version: '1.2.3',
sessionAttributes: {previousIntent: 'FirstIntent'},
response: {
outputSpeech: {type: 'PlainText', text: 'All good'},
shouldEndSession: true
}
};
app.handle(request, (response) => {
expect(response).to.be.defined;
expect(response).to.deep.equal(expectedResponse);
done();
});
});
it('should handle intent with slots', (done) => {
const request = alexia.createIntentRequest(intents[4].name, {name: 'Borimir'}, null, false, 'appId1');
app.handle(request, (response) => {
expect(response.response.outputSpeech.text).equal('okay sir your name is Borimir');
done();
});
});
it('should handle IntentA request', (done) => {
const request = alexia.createIntentRequest('IntentA', null, null, false, 'appId1');
app.handle(request, (response) => {
attrs = response.sessionAttributes;
expect(attrs).to.deep.equal({previousIntent: 'IntentA', yes: true});
expect(response.response.outputSpeech).to.deep.equal({type: 'SSML', ssml: '<speak>Hi</speak>'});
expect(response.response.reprompt).to.deep.equal({outputSpeech: {type: 'SSML', ssml: '<speak>Sup</speak>'}});
done();
});
});
it('should handle IntentB request', (done) => {
const request = alexia.createIntentRequest('IntentB', null, attrs, false, 'appId1');
app.handle(request, (response) => {
attrs = response.sessionAttributes;
expect(attrs).to.deep.equal({yes: true, previousIntent: 'IntentB'});
expect(response.response.outputSpeech).to.deep.equal({type: 'PlainText', text: 'attribute value is true'});
done();
});
});
it('should handle IntentC request with SSML outputSpeech type', (done) => {
const request = alexia.createIntentRequest('IntentC', null, attrs, false, 'appId1');
app.handle(request, (response) => {
attrs = response.sessionAttributes;
expect(response.response.outputSpeech).to.deep.equal({type: 'SSML', ssml: '<speak>Hi</speak>'});
done();
});
});
it('should handle Async intent', (done) => {
const request = alexia.createIntentRequest(intents[11].name, null, null, false, 'appId1');
app.handle(request, (response) => {
expect(response.response.outputSpeech.text).equal('I just did stuff asynchronously. Thank you for this opportunity');
done();
});
});
it('should handle intent with three arguments', (done) => {
const app2 = alexia.createApp('App2');
app2.intent('FullRequestDataIntent', 'hi', (slots, attrs, data) => 'hello');
const request = alexia.createIntentRequest('FullRequestDataIntent');
app2.handle(request, (response) => {
expect(response.response.outputSpeech.text).to.equal('hello');
done();
});
});
it('should create intent with utterances using space, dot, hyphen & apostrophe', () => {
const app2 = alexia.createApp('App2');
app2.intent('MegaIntent', 'Not good -\' .. ', () => 'Nope');
expect(app2.intents.MegaIntent.name).to.equal('MegaIntent');
});
it('should create custom slot with name using underscores', () => {
const app2 = alexia.createApp('App2');
app2.customSlot('NEXT_SLOT', ['next please']);
expect(app2.customSlots.NEXT_SLOT.name).to.equal('NEXT_SLOT');
});
it('should not handle request for wrong app id', () => {
const app2 = alexia.createApp('App2', {ids: 'supported-app-id'});
try {
app2.handle(alexia.createLaunchRequest(null, 'wrong-app-id'));
throw new Error('App was handled with unsupported application id');
} catch (e) {
expect(e).to.include('Application id: \'wrong-app-id\' is not valid');
}
});
it('should not handle nonexistent intent', () => {
const request = alexia.createIntentRequest('UnsupportedIntentName', null, null, false, 'appId1');
try {
app.handle(request);
} catch (e) {
expect(e).to.include('Nonexistent intent: \'UnsupportedIntentName\'');
}
});
it('should not handle unsupported request', () => {
const request = alexia.createIntentRequest('IntentA', null, null, false, 'appId1');
request.request.type = 'UnsupportedRequestType';
try {
app.handle(request);
} catch (e) {
expect(e).to.include('Unsupported request: \'UnsupportedRequestType\'');
}
});
it('should not redefine custom slots', () => {
try {
const app2 = alexia.createApp('App2');
app2.customSlot('MyCustomSlot', []);
app2.customSlot('MyCustomSlot', []); // redefine
throw new Error('App was handled with custom slot redefinition');
} catch (e) {
expect(e).to.include('Slot with name MyCustomSlot is already defined');
}
});
it('should not redefine built-in slots', () => {
const app2 = alexia.createApp('App2');
try {
app2.customSlot('Number', []);
throw new Error('App was handled with custom slot redefinition');
} catch (e) {
expect(e).to.include('Slot with name Number is already defined in built-in slots');
}
try {
app2.customSlot('AMAZON.NUMBER', []);
throw new Error('App was handled with custom slot redefinition');
} catch (e) {
expect(e).to.include('Slot with name AMAZON.NUMBER is already defined in built-in slots');
}
});
it('should not create intent with invalid utterances', () => {
try {
const app2 = alexia.createApp('App2');
app2.intent('MegaIntent', 'Not good* utterance *-#$%^&*', () => 'Nope');
throw new Error('App was handled with invalid intent utterance');
} catch (e) {
expect(e).to.include('Sample utterance: \'Not good* utterance *-#$%^&*\' is not valid. Each sample utterance must consist only of alphabet characters, spaces, dots, hyphens, brackets and single quotes');
}
});
it('should not create intent without name', () => {
try {
const app2 = alexia.createApp('App2');
app2.intent(null, 'Name', () => 'Must have name');
throw new Error('Name of intent was not specified.');
} catch (e) {
expect(e).to.include('All intents must have name.');
}
});
it('should not create intent with invalid name', () => {
try {
const app2 = alexia.createApp('App2');
app2.intent('Mega -.- Intent o/', 'Hi', () => 'Nope bye');
throw new Error('App was handled with invalid intent name');
} catch (e) {
expect(e).to.include('Intent name Mega -.- Intent o/ is invalid. Only lowercase and uppercase letters are allowed.');
}
});
it('should not create built-in intent with invalid name', () => {
try {
const app2 = alexia.createApp('App2');
app2.builtInIntent('InvalidBuiltInIntent', 'stop pls', () => 'hi');
throw new Error('App was handled with invalid Built-in Intent name');
} catch (e) {
expect(e).to.include('Built-in Intent name InvalidBuiltInIntent is invalid. Please use one of: cancel, help, next, no, pause, previous, repeat, resume, startOver, stop, yes');
}
});
it('should not create custom slot with invalid name', () => {
try {
const app2 = alexia.createApp('App2');
app2.customSlot('Mega -.- CustomSlot /-', []);
throw new Error('App was handled with invalid custom slot name');
} catch (e) {
expect(e).to.include('Custom slot name Mega -.- CustomSlot /- is invalid. Only lowercase, uppercase letters and underscores are allowed');
}
});
it('should not create custom slot with invalid sample utterance', () => {
try {
const app2 = alexia.createApp('App2');
app2.customSlot('MyCustomSlot', ['Nope ***']);
throw new Error('App was handled with invalid custom slot sample utterance');
} catch (e) {
expect(e).to.include('Custom slot with name MyCustomSlot contains invalid special character(~, ^, *, (, ), [, ], §, !, ?, ;, :, " and |): Nope ***');
}
});
it('should not create duplicate slot definition for multiple utterances', () => {
const app2 = alexia.createApp('App2');
app2.intent('SomeIntent', ['First utterance {number:Number}', 'Second utterance {number:Number}'], () => {
return 'Hey';
});
expect(app2.intents['SomeIntent'].slots).to.have.length(1);
});
it('should handle AnotherCardIntentSample with card type', (done) => {
const request = alexia.createIntentRequest('AnotherCardIntentSample', null, null, false, 'appId1');
app.handle(request, (response) => {
expect(response.response.card.type).to.equal('Standard');
done();
});
});
it('should handle intent and set shouldEndSession to false', (done) => {
const request = alexia.createIntentRequest('AnotherCardIntentSample', null, null, false, 'appId1');
app.handle(request, (response) => {
expect(response.response.shouldEndSession).to.equal(false);
done();
});
});
it('should handle request with string payload', (done) => {
const request = alexia.createIntentRequest('AnotherCardIntentSample', null, null, false, 'appId1');
const stringifiedRequest = JSON.stringify(request);
app.handle(stringifiedRequest, (response) => {
expect(response.response.outputSpeech.text).to.equal('Hey');
done();
});
});
});
================================================
FILE: test/contacts.spec.js
================================================
'use strict';
const expect = require('chai').expect;
const app = require('./test-apps/contacts-app');
const alexia = require('..');
describe('action app handler', () => {
it('should handle OpenContactList -> NewContact and return correct outputSpeech', (done) => {
const request = alexia.createIntentRequest('NewContact', null, {previousIntent: 'OpenContactList'});
app.handle(request, (response) => {
expect(response.response.outputSpeech.text).to.equal('Please insert value for name or phone number of contact.');
done();
});
});
it('should handle NewContact -> SetName and return correct outputSpeech', (done) => {
const request = alexia.createIntentRequest('SetName', null, {previousIntent: 'NewContact'});
app.handle(request, (response) => {
expect(response.response.outputSpeech.text).to.equal('Name was saved.');
done();
});
});
it('should handle SetName -> CloseContactList and return correct outputSpeech', (done) => {
const request = alexia.createIntentRequest('CloseContactList', null, {previousIntent: 'SetName'});
app.handle(request, (response) => {
expect(response.response.outputSpeech.text).to.equal('See you next time.');
done();
});
});
it('should not handle OpenContactList -> SetName and return correct outputSpeech', (done) => {
const request = alexia.createIntentRequest('SetName', null, {previousIntent: 'OpenContactList'});
app.handle(request, (response) => {
expect(response.response.outputSpeech.text).to.equal('Sorry, your command is invalid');
done();
});
});
});
================================================
FILE: test/create-server.spec.js
================================================
'use strict';
const expect = require('chai').expect;
const alexia = require('..');
const sinon = require('sinon');
const request = require('request');
describe('server', () => {
let app;
const mockRequest = {
someRequest: 1
};
const mockResponse = {
someResponse: 2
};
const createRequestOptions = (uri) => ({
uri: uri,
method: 'POST',
json: mockRequest
});
beforeEach(() => {
app = alexia.createApp('MyApp');
sinon.stub(app, 'handle', (requestObject, done) => {
// ... responseObject in lib generated
done(mockResponse);
});
});
afterEach(() => {
app.handle.restore();
});
it('should create working Hapi server', (done) => {
const server = app.createServer(app);
server.start(serverError => {
expect(serverError).to.be.not.ok;
// Send POST request to server
request(createRequestOptions(server.info.uri), (requestError, response) => {
expect(requestError).to.be.not.ok;
expect(response.body).to.deep.equal(mockResponse);
expect(response.statusCode === 200);
server.stop();
done();
});
});
});
});
================================================
FILE: test/generate.spec.js
================================================
'use strict';
const expect = require('chai').expect;
const app = require('./test-apps/basic-app');
const assetsMock = require('./mock/assets');
describe('generateSpeechAssets', () => {
const assets = app.speechAssets();
it('should generate intentSchema', () => {
expect(JSON.parse(assets.intentSchema)).to.deep.equal(assetsMock.intentSchema);
});
it('should generate utterances', () => {
expect(assets.utterances.split('\n')).to.deep.equal(assetsMock.utterances);
});
it('should generate customSlots', () => {
expect(assets.customSlots).to.deep.equal({
Name: assetsMock.nameCustomSlot
});
});
it('should generate stringified speechAssets', () => {
const stringifiedAssets = assets.toString();
expect(typeof stringifiedAssets).to.equal('string');
expect(stringifiedAssets.split('\n\n').length).to.equal(4);
});
});
================================================
FILE: test/mock/assets.js
================================================
'use strict';
exports.intentSchema = {
intents: [
{intent: 'FirstIntent'},
{intent: 'NamedIntent'},
{intent: 'Multiple'},
{
intent: 'Age',
slots: [{name: 'age', type: 'AMAZON.NUMBER'}]
},
{
intent: 'Name',
slots: [{name: 'name', type: 'Name'}]
},
{intent: 'AMAZON.StopIntent'},
{intent: 'AMAZON.HelpIntent'},
{intent: 'AMAZON.CancelIntent'},
{intent: 'IntentA'},
{intent: 'IntentB'},
{intent: 'IntentC'},
{intent: 'Async'},
{intent: 'AnotherCardIntentSample'},
{intent: 'IntentWithoutUtterances'}
]
};
exports.utterances = [
'FirstIntent utterance',
'NamedIntent utteranceB',
'NamedIntent utteranceC',
'Multiple multiple',
'Multiple utterances',
'Age I am {age} years old',
'Name My name is {name}',
'AMAZON.StopIntent one utterance',
'AMAZON.HelpIntent two utterances',
'AMAZON.HelpIntent yup',
'IntentA another utterance',
'IntentB another utterance',
'IntentC another utterance',
'Async async response',
'AnotherCardIntentSample card intent'
];
exports.nameCustomSlot = ['Borimir', 'Vlasto'];
================================================
FILE: test/mock/multi-lang-assets.js
================================================
'use strict';
exports.intentSchema = {
'intents': [
{
'intent': 'LocalizedIntent'
},
{
'intent': 'IntentWithoutLocalization'
},
{
'intent': 'LocalizedSlotIntent',
'slots': [
{
'name': 'number',
'type': 'AMAZON.NUMBER'
}
]
},
{
'intent': 'AMAZON.YesIntent'
},
{
'intent': 'AMAZON.HelpIntent'
}
]
};
exports.itemCustomSlot = ['Car', 'House', 'Flat'];
exports.utterancesENG = [
'LocalizedIntent say hello',
'LocalizedIntent say hello to me',
'LocalizedSlotIntent my magic number is {number}',
'AMAZON.HelpIntent Testing built in intent with utterance'
];
exports.utterancesDE = [
'LocalizedIntent sag hallo',
'LocalizedSlotIntent Meine magische nummer ist {number}. Testen äöüÄÖÜß',
'AMAZON.HelpIntent Testen eingebaut intent mit utterance'
];
================================================
FILE: test/multi-language.spec.js
================================================
'use strict';
const expect = require('chai').expect;
const alexia = require('..');
const fs = require('fs');
const app = require('./test-apps/multi-language/multi-language-app');
const assetsMock = require('./mock/multi-lang-assets.js');
describe('multi language app', () => {
it('should create app', () => {
expect(app).to.be.ok;
});
it('should handle Localized LaunchRequest for locale: en', done => {
const request = alexia.createRequest({
type: 'LaunchRequest',
locale: 'en-US'
});
app.handle(request, data => {
expect(data.response.outputSpeech.text).to.equal('Welcome');
done();
});
});
it('should handle Localized LaunchRequest for locale: de', done => {
const request = alexia.createRequest({
type: 'LaunchRequest',
locale: 'de-DE'
});
app.handle(request, data => {
expect(data.response.outputSpeech.text).to.equal('Willkommen');
done();
});
});
it('should handle LocalizedIntent for locale: en', done => {
const request = alexia.createRequest({
name: 'LocalizedIntent',
locale: 'en-US'
});
app.handle(request, data => {
expect(data.response.outputSpeech.text).to.equal('Hello World');
done();
});
});
it('should handle LocalizedIntent for locale: de', done => {
const request = alexia.createRequest({
name: 'LocalizedIntent',
locale: 'de-DE'
});
app.handle(request, data => {
expect(data.response.outputSpeech.text).to.equal('Hallo Welt');
done();
});
});
it('should handle LocalizedIntent without locale (backwards compatibility)', done => {
const request = alexia.createRequest({
name: 'LocalizedIntent'
});
delete request.request.locale;
app.handle(request, data => {
expect(data.response.outputSpeech.text).to.equal('Hello World');
done();
});
});
it('should have access to app.t function for all apps', (done) => {
// This app is not localized
const app2 = alexia.createApp();
app2.intent('SomeIntent', () => {
// Should be using fallback `.t` function
return app2.t('something.is.wrong');
});
const request = alexia.createIntentRequest('SomeIntent');
app2.handle(request, data => {
expect(data.response.outputSpeech.text).to.equal('something.is.wrong');
done();
});
});
it('handle LocalizedSlotIntent', (done) => {
const request = alexia.createRequest({
name: 'LocalizedSlotIntent',
slots: {
number: 7
}
});
app.handle(request, data => {
expect(data.response.outputSpeech.text).to.equal('hello 7');
done();
});
});
it('handle localized built in intent', (done) => {
const request = alexia.createRequest({
name: 'AMAZON.YesIntent',
locale: 'de-DE'
});
app.handle(request, data => {
expect(data.response.outputSpeech.text).to.equal('Testen der Punktsyntax');
done();
});
});
it('should save localized speech assets', (done) => {
// Note: the testing app is missing custom-slots definition for `de` locale on purpose
app.saveSpeechAssets('speechAssets', done);
});
it('should save intentSchema to JSON file for ENG locale that contains all intents', (done) => {
app.saveSpeechAssets('speechAssets', () => {
const intentSchemaENG = fs.readFileSync('speechAssets/en/intentSchema.json', 'utf8');
expect(intentSchemaENG).to.exist;
expect(JSON.parse(intentSchemaENG)).to.deep.equal(assetsMock.intentSchema);
done();
});
});
it('should save utterances to *.txt file for ENG locale that contains all utterances', (done) => {
app.saveSpeechAssets('speechAssets', () => {
const utterancesENG = fs.readFileSync('speechAssets/en/utterances.txt', 'utf8');
expect(utterancesENG).to.exist;
expect(utterancesENG.split('\n')).to.deep.equal(assetsMock.utterancesENG);
done();
});
});
it('should save intentSchema to JSON file for DE locale that contains all intents', (done) => {
app.saveSpeechAssets('speechAssets', () => {
const intentSchemaDE = fs.readFileSync('speechAssets/en/intentSchema.json', 'utf8');
expect(intentSchemaDE).to.exist;
expect(JSON.parse(intentSchemaDE)).to.deep.equal(assetsMock.intentSchema);
done();
});
});
it('should save utterances to *.txt file for DE locale that contains all utterances', (done) => {
app.saveSpeechAssets('speechAssets', () => {
const utterancesDE = fs.readFileSync('speechAssets/de/utterances.txt', 'utf8');
expect(utterancesDE).to.exist;
expect(utterancesDE.split('\n')).to.deep.equal(assetsMock.utterancesDE);
done();
});
});
it('should save customSlot to separate file with correct content for ENG locale', () => {
const customSlotFile = fs.readFileSync('speechAssets/en/customSlots/item.txt', 'utf8');
expect(customSlotFile).to.exist;
expect(customSlotFile.split('\n')).to.deep.equal(assetsMock.itemCustomSlot);
});
it('should not save customSlot to separate file for DE locale since it is not defined', () => {
expect(fs.existsSync('speechAssets/de/customSlots/item.txt')).to.equal(false);
});
});
================================================
FILE: test/register-intents.spec.js
================================================
'use strict';
const expect = require('chai').expect;
const alexia = require('../');
describe('registerIntents', () => {
let app;
beforeEach(() => {
app = alexia.createApp();
});
it('should register all intents maching pattern', () => {
app.registerIntents('test/test-apps/separate-intents/*-intent.js');
expect(Object.keys(app.intents)).to.have.length(2);
});
it('should register no intents if pattern is wrong', () => {
app.registerIntents('not-found/*-intent.js');
expect(Object.keys(app.intents)).to.have.length(0);
});
});
================================================
FILE: test/save-speech.spec.js
================================================
'use strict';
const expect = require('chai').expect;
const fs = require('fs');
const rimraf = require('rimraf');
const app = require('./test-apps/basic-app');
const assetsMock = require('./mock/assets');
describe('saveSpeechAssets', () => {
describe('defaultDirectory', () => {
before(() => {
rimraf.sync('speechAssets');
fs.mkdirSync('speechAssets');
fs.mkdirSync('speechAssets/customSlots');
fs.writeFileSync('speechAssets/customSlots/testCustomSlot.txt', 'test');
app.saveSpeechAssets();
});
it('should save intentSchema to JSON file', () => {
const intentSchema = fs.readFileSync('speechAssets/intentSchema.json', 'utf8');
expect(JSON.parse(intentSchema)).to.deep.equal(assetsMock.intentSchema);
});
it('should save utterances to txt file', () => {
const utterances = fs.readFileSync('speechAssets/utterances.txt', 'utf8');
expect(utterances.split('\n')).to.deep.equal(assetsMock.utterances);
});
it('should save customSlots to separate files', () => {
const customSlot = fs.readFileSync('speechAssets/customSlots/Name.txt', 'utf8');
expect(customSlot.split('\n')).to.deep.equal(assetsMock.nameCustomSlot);
});
it('should not contain previously created file testCustomSlot', () => {
expect(fs.existsSync('speechAssets/customSlots/testCustomSlot.txt')).to.equal(false);
});
after(() => {
rimraf.sync('speechAssets');
});
});
describe('customSpeechAssetsDirectory', () => {
const mySpeechAssetsDirectory = 'mySpeechAssetsDirectory';
before(done => {
rimraf.sync(mySpeechAssetsDirectory);
app.saveSpeechAssets(mySpeechAssetsDirectory, done);
});
it('should save speechAssets to customDirectory ', () => {
const intentSchema = fs.readFileSync(mySpeechAssetsDirectory + '/intentSchema.json', 'utf8');
expect(JSON.parse(intentSchema)).to.deep.equal(assetsMock.intentSchema);
});
after(() => {
rimraf.sync(mySpeechAssetsDirectory);
});
});
});
================================================
FILE: test/test-apps/actions-app.js
================================================
'use strict';
const alexia = require('../..');
const app = alexia.createApp('ActionsApp');
const intentA = app.intent('IntentA', 'remove stuff', () => {
return {
text: 'Are you sure you want to remove stuff?',
end: false
};
});
const intentB = app.intent('IntentB', 'yes', () => {
return 'Your stuff has been cleared';
});
app.intent('IntentC', 'no', () => {
return 'Testing actions1';
});
app.intent('IntentD', 'no', () => {
return 'Testing actions2';
});
app.intent('IntentE', 'maybe', (slots, attrs, data, done) => {
done({
text: 'Testing async actions3 with responseObject attrs',
attrs: {
foo: true
}
});
});
app.action({from: '*', to: 'IntentA'});
app.action({
from: intentA,
to: intentB,
if: () => 1 === 1 // eslint-disable-line no-self-compare
});
app.action({
from: intentB,
to: 'IntentC',
if: () => 1 === 2,
fail: () => 'Could not handle request'
});
app.action({
from: intentB,
to: 'IntentD',
if: () => 1 === 2
});
app.action({from: '*', to: 'IntentE'});
module.exports = app;
================================================
FILE: test/test-apps/basic-app.js
================================================
'use strict';
const alexia = require('../..');
const app = alexia.createApp('MyApp', {
version: '1.2.3',
shouldEndSessionByDefault: true,
ids: ['appId1', 'appId2']
});
app.intent('FirstIntent', 'utterance', () => {
return 'All good';
});
app.intent('NamedIntent', ['utteranceB', 'utteranceC'], () => {
return 'All good';
});
app.intent('Multiple', ['multiple', 'utterances'], () => {
return {
text: 'All good sir again'
};
});
app.intent('Age', 'I am {age:Number} years old', (slots) => {
return `okay sir you are ${slots.age} years old`;
});
app.customSlot('Name', ['Borimir', 'Vlasto']);
app.intent('Name', 'My name is {name:Name}', (slots) => {
return `okay sir your name is ${slots.name}`;
});
app.builtInIntent('stop', 'one utterance', () => {
return 'okay';
});
app.builtInIntent('help', ['two utterances', 'yup'], () => {
return 'nok';
});
app.builtInIntent('cancel', () => {
return 'cancel';
});
app.intent('IntentA', 'another utterance', () => {
return {
text: '<speak>Hi</speak>',
reprompt: '<speak>Sup</speak>',
ssml: true,
attrs: {
yes: true
},
card: {
title: 'Hello',
content: 'Once upon a time ...'
},
end: false
};
});
app.intent('IntentB', 'another utterance', (slots, attrs) => {
return `attribute value is ${attrs.yes}`;
});
app.intent('IntentC', 'another utterance', (slots, attrs) => {
return {
text: '<speak>Hi</speak>'
};
});
app.intent('Async', 'async response', (slots, attrs, data, done) => {
setTimeout(() => {
done('I just did stuff asynchronously. Thank you for this opportunity');
}, 120);
});
app.intent('AnotherCardIntentSample', 'card intent', () => {
return {
text: 'Hey',
card: {
type: 'Standard',
title: 'Hello',
content: 'Once upon a time ...'
},
end: false
};
});
app.intent('IntentWithoutUtterances', () => {
return 'All good.';
});
module.exports = app;
================================================
FILE: test/test-apps/contacts-app.js
================================================
// short app for testing action's transitions saved in arrays
'use strict';
const alexia = require('../..');
const app = alexia.createApp('ContactsApp');
app.intent('OpenContactList', 'Open contact list', () => {
return 'You can list your contacts, add new one or change already saved ones.';
});
app.intent('NewContact', 'Create new contact', () => {
return 'Please insert value for name or phone number of contact.';
});
app.intent('ChangeContact', 'Change name for Glogo contact', () => {
return 'Please insert new value for name or phone number.';
});
app.intent('SetName', 'Set name to Misyak', () => {
return 'Name was saved.';
});
app.intent('SetNumber', 'Set number to {number:NUMBER}', () => {
return 'Number was saved.';
});
app.intent('CloseContactList', 'Close contact list', () => {
return 'See you next time.';
});
app.action({
from: 'OpenContactList',
to: ['NewContact', 'ChangeContact']
});
app.action({
from: ['NewContact', 'ChangeContact'],
to: ['SetName', 'SetNumber']
});
app.action({
from: ['NewContact', 'ChangeContact', 'SetName', 'SetNumber'],
to: 'CloseContactList'
});
module.exports = app;
================================================
FILE: test/test-apps/multi-language/locales/de/translation.json
================================================
{
"LaunchRequest": {
"text": "Willkommen"
},
"LocalizedIntent": {
"utterances": "sag hallo",
"text": "Hallo Welt"
},
"LocalizedSlotIntent": {
"utterances": "Meine magische nummer ist {number:Number}. Testen äöüÄÖÜß",
"text": "hallo {{number}}"
},
"YesIntent": {
"text": "Testen der Punktsyntax",
"card": {
"title": "Titel",
"content": "Inhalt"
}
},
"HelpIntent": {
"utterances": "Testen eingebaut intent mit utterance"
}
}
================================================
FILE: test/test-apps/multi-language/locales/en/custom-slots.json
================================================
{
"item": ["Car", "House", "Flat"]
}
================================================
FILE: test/test-apps/multi-language/locales/en/translation.json
================================================
{
"LaunchRequest": {
"text": "Welcome"
},
"LocalizedIntent": {
"utterances": ["say hello", "say hello to me"],
"text": "Hello World"
},
"LocalizedSlotIntent": {
"utterances": "my magic number is {number:Number}",
"text": "hello {{number}}"
},
"YesIntent": {
"text": "Testing the dot syntax",
"card": {
"title": "Title",
"content": "Content"
}
},
"HelpIntent": {
"utterances": "Testing built in intent with utterance"
}
}
================================================
FILE: test/test-apps/multi-language/multi-language-app.js
================================================
'use strict';
const i18next = require('i18next');
const FilesystemBackend = require('i18next-node-fs-backend');
const alexia = require('../../..');
const app = alexia.createApp();
// Initialize i18next internationalization
i18next
.use(FilesystemBackend)
.init({
// debug: true,
lng: 'en',
fallbackLng: 'en',
backend: {
loadPath: 'test/test-apps/multi-language/locales/{{lng}}/{{ns}}.json'
},
preload: ['en', 'de'],
ns: ['translation', 'custom-slots']
});
// Pass i18 to alexia app to make it available for requests and speech assets generation
app.setI18next(i18next);
app.onStart(() => {
return app.t('text');
});
app.intent('LocalizedIntent', () => {
return app.t('text');
});
app.intent('IntentWithoutLocalization', () => {
return 'Hi, this intent does not have any utterances nor locale translation.';
// So utterances won't be generated
});
app.intent('LocalizedSlotIntent', (slots) => {
return app.t('text', slots);
});
app.intent('yes', () => {
return app.t('text');
});
app.builtInIntent('help', () => {
return app.t('text');
});
module.exports = app;
================================================
FILE: test/test-apps/separate-intents/bye-intent.js
================================================
'use strict';
module.exports = app => app.intent('ByeIntent', 'bye', () => {
return 'Bye';
});
================================================
FILE: test/test-apps/separate-intents/hello-intent.js
================================================
'use strict';
module.exports = app => app.intent('HelloIntent', 'hello', () => {
return 'Hello';
});
gitextract_tge_d4e1/
├── .editorconfig
├── .eslintignore
├── .eslintrc
├── .gitattributes
├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── adop/
│ ├── LICENSE.md
│ ├── README.md
│ ├── jenkins/
│ │ └── jobs/
│ │ ├── dsl/
│ │ │ └── alxeia_pipeline_jobs.groovy
│ │ ├── generate.sh
│ │ └── xml/
│ │ └── .gitkeep
│ ├── metadata.cartridge
│ └── src/
│ └── urls.txt
├── examples/
│ ├── actions.js
│ ├── app-info.js
│ ├── async-response.js
│ ├── built-in-intents.js
│ ├── create-request.js
│ ├── custom-slots.js
│ ├── hello-world.js
│ ├── more-utterances.js
│ ├── multi-language/
│ │ ├── locales/
│ │ │ ├── de/
│ │ │ │ ├── custom-slots.json
│ │ │ │ └── translation.json
│ │ │ └── en/
│ │ │ ├── custom-slots.json
│ │ │ └── translation.json
│ │ └── multi-language-app.js
│ ├── original-request-data.js
│ ├── response-object.js
│ ├── session-attributes.js
│ ├── slots.js
│ ├── spech-assets.js
│ └── start-end.js
├── nodemon.json
├── package.json
├── src/
│ ├── alexia.js
│ ├── built-in-intents-map.js
│ ├── built-in-slots-map.js
│ ├── create-app.js
│ ├── create-custom-slot.js
│ ├── create-intent.js
│ ├── create-request.js
│ ├── create-server.js
│ ├── error-handler.js
│ ├── generate-speech-assets-i18n.js
│ ├── generate-speech-assets.js
│ ├── handle-request.js
│ ├── parse-rich-utterances.js
│ ├── save-speech-assets.js
│ └── validator.js
└── test/
├── .eslintrc
├── actions.spec.js
├── basic.spec.js
├── contacts.spec.js
├── create-server.spec.js
├── generate.spec.js
├── mock/
│ ├── assets.js
│ └── multi-lang-assets.js
├── multi-language.spec.js
├── register-intents.spec.js
├── save-speech.spec.js
└── test-apps/
├── actions-app.js
├── basic-app.js
├── contacts-app.js
├── multi-language/
│ ├── locales/
│ │ ├── de/
│ │ │ └── translation.json
│ │ └── en/
│ │ ├── custom-slots.json
│ │ └── translation.json
│ └── multi-language-app.js
└── separate-intents/
├── bye-intent.js
└── hello-intent.js
Condensed preview — 71 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (130K chars).
[
{
"path": ".editorconfig",
"chars": 197,
"preview": "# http://editorconfig.org\nroot = true\n\n[*]\nindent_style = space\nindent_size = 2\ncharset = utf-8\ntrim_trailing_whitespace"
},
{
"path": ".eslintignore",
"chars": 54,
"preview": "coverage/**\nnode_modules/**\n*.log\ndocs/**\nexamples/**\n"
},
{
"path": ".eslintrc",
"chars": 96,
"preview": "{\n \"extends\": \"standard\",\n \"rules\": {\n \"semi\": [1, \"always\"],\n \"padded-blocks\": 0\n }\n}\n"
},
{
"path": ".gitattributes",
"chars": 11,
"preview": "* text=auto"
},
{
"path": ".gitignore",
"chars": 63,
"preview": "*.idea\n*.log\nnode_modules\nspeechAssets\ncoverage\n*~\n*.swp\n*.swo\n"
},
{
"path": ".travis.yml",
"chars": 142,
"preview": "language: node_js\nnode_js:\n - 4.2.0\nscript: \"npm run test-lcov\"\nafter_script: \"npm install coveralls && cat ./coverage/"
},
{
"path": "LICENSE",
"chars": 1076,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2016 Accenture\n\nPermission is hereby granted, free of charge, to any person obtaini"
},
{
"path": "README.md",
"chars": 21286,
"preview": "\n\nA Framework for creating Amazon Echo (Alexa) skills using Node.js\n\n [ is a set "
},
{
"path": "adop/jenkins/jobs/dsl/alxeia_pipeline_jobs.groovy",
"chars": 5451,
"preview": "// Folders\ndef workspaceFolderName = \"${WORKSPACE_NAME}\"\ndef projectFolderName = \"${PROJECT_NAME}\"\n\n// Variables\ndef ref"
},
{
"path": "adop/jenkins/jobs/generate.sh",
"chars": 150,
"preview": "# Export variables\nexport TOKEN_WORKSPACE_FOLDER_NAME=\"${WORKSPACE_NAME}\"\nexport TOKEN_PROJECT_FOLDER_NAME=$(echo \"${PRO"
},
{
"path": "adop/jenkins/jobs/xml/.gitkeep",
"chars": 0,
"preview": ""
},
{
"path": "adop/metadata.cartridge",
"chars": 26,
"preview": "CARTRIDGE_SDK_VERSION=1.1\n"
},
{
"path": "adop/src/urls.txt",
"chars": 40,
"preview": "https://github.com/Accenture/alexia.git\n"
},
{
"path": "examples/actions.js",
"chars": 1113,
"preview": "'use strict';\nconst alexia = require('..');\nconst app = alexia.createApp('ActionsExample');\n\nconst intentA = app.intent("
},
{
"path": "examples/app-info.js",
"chars": 289,
"preview": "'use strict';\nconst alexia = require('..');\n\n// Create app with additional info (optional)\nconst app = alexia.createApp("
},
{
"path": "examples/async-response.js",
"chars": 252,
"preview": "'use strict';\nconst alexia = require('..');\nconst app = alexia.createApp('AsyncResponseExample');\n\napp.intent('AsyncInte"
},
{
"path": "examples/built-in-intents.js",
"chars": 778,
"preview": "'use strict';\nconst alexia = require('..');\nconst app = alexia.createApp('BuiltInIntentsExample');\n\n// You can extend bu"
},
{
"path": "examples/create-request.js",
"chars": 317,
"preview": "'use strict';\nconst alexia = require('alexia');\n\nconst launchRequest = alexia.createLaunchRequest();\nconst sessionEndedR"
},
{
"path": "examples/custom-slots.js",
"chars": 278,
"preview": "'use strict';\nconst alexia = require('..');\nconst app = alexia.createApp('CustomSlotsExample');\n\napp.slot('Name', ['Foo'"
},
{
"path": "examples/hello-world.js",
"chars": 191,
"preview": "'use strict';\nconst alexia = require('..');\nconst app = alexia.createApp('HelloWorldExample');\n\napp.intent('HelloIntent'"
},
{
"path": "examples/more-utterances.js",
"chars": 244,
"preview": "'use strict';\nconst alexia = require('..');\nconst app = alexia.createApp('MoreUtterancesExample');\n\napp.intent('MoreUtte"
},
{
"path": "examples/multi-language/locales/de/custom-slots.json",
"chars": 48,
"preview": "{\n \"Name\": [\"Michael\", \"Michal\", \"Slavomir\"]\n}\n"
},
{
"path": "examples/multi-language/locales/de/translation.json",
"chars": 262,
"preview": "{\n \"LaunchRequest\": {\n \"text\": \"Wilkommen\"\n },\n \"SessionEndedRequest\": {\n \"text\": \"auf Wiedersehen\"\n },\n \"Loc"
},
{
"path": "examples/multi-language/locales/en/custom-slots.json",
"chars": 48,
"preview": "{\n \"Name\": [\"Michael\", \"Michal\", \"Slavomir\"]\n}\n"
},
{
"path": "examples/multi-language/locales/en/translation.json",
"chars": 242,
"preview": "{\n \"LaunchRequest\": {\n \"text\": \"Welcome\"\n },\n \"SessionEndedRequest\": {\n \"text\": \"Goodbye\"\n },\n \"LocalizedInte"
},
{
"path": "examples/multi-language/multi-language-app.js",
"chars": 916,
"preview": "'use strict';\nconst i18next = require('i18next');\nconst FilesystemBackend = require('i18next-node-fs-backend');\nconst al"
},
{
"path": "examples/original-request-data.js",
"chars": 288,
"preview": "'use strict';\nconst alexia = require('..');\nconst app = alexia.createApp('OriginalRequestDataExample');\n\napp.intent('Ori"
},
{
"path": "examples/response-object.js",
"chars": 768,
"preview": "'use strict';\nconst alexia = require('..');\nconst app = alexia.createApp('ResponseObjectExample');\n\napp.intent('SSMLInte"
},
{
"path": "examples/session-attributes.js",
"chars": 733,
"preview": "'use strict';\nconst alexia = require('..');\nconst app = alexia.createApp('SessionAttributesExample');\n\n// Store attribut"
},
{
"path": "examples/slots.js",
"chars": 460,
"preview": "'use strict';\nconst alexia = require('..');\nconst app = alexia.createApp('SlotsExample');\n\napp.intent('SlotsIntent', 'I "
},
{
"path": "examples/spech-assets.js",
"chars": 438,
"preview": "'use strict';\nconst alexia = require('..');\nconst app = alexia.createApp('SpeechAssetsExample');\n\napp.customSlot('Mood',"
},
{
"path": "examples/start-end.js",
"chars": 297,
"preview": "'use strict';\nconst alexia = require('..');\nconst app = alexia.createApp('StartEndExample');\n\napp.onStart(() => {\n retu"
},
{
"path": "nodemon.json",
"chars": 76,
"preview": "{\n \"ignore\": [\"node_modules\", \"speechAssets\", \"mySpeechAssetsDirectory\"]\n}\n"
},
{
"path": "package.json",
"chars": 1900,
"preview": "{\n \"name\": \"alexia\",\n \"version\": \"2.4.0\",\n \"description\": \"A Framework for creating Amazon Echo (Alexa) skills using "
},
{
"path": "src/alexia.js",
"chars": 624,
"preview": "'use strict';\nconst createApp = require('./create-app');\nconst createRequest = require('./create-request');\n\nmodule.expo"
},
{
"path": "src/built-in-intents-map.js",
"chars": 380,
"preview": "'use strict';\nmodule.exports = {\n cancel: 'AMAZON.CancelIntent',\n help: 'AMAZON.HelpIntent',\n next: 'AMAZON.NextInten"
},
{
"path": "src/built-in-slots-map.js",
"chars": 638,
"preview": "'use strict';\nmodule.exports = {\n 'Date': 'AMAZON.DATE',\n 'Duration': 'AMAZON.DURATION',\n 'FourDigitNumber': 'AMAZON."
},
{
"path": "src/create-app.js",
"chars": 10450,
"preview": "'use strict';\nconst _ = require('lodash');\nconst nodeDebug = require('debug');\nconst glob = require('glob');\nconst path "
},
{
"path": "src/create-custom-slot.js",
"chars": 1434,
"preview": "'use strict';\nconst _ = require('lodash');\nconst builtInSlotsMap = require('./built-in-slots-map');\nconst validator = re"
},
{
"path": "src/create-intent.js",
"chars": 1520,
"preview": "'use strict';\nconst _ = require('lodash');\nconst builtInIntentsMap = require('./built-in-intents-map');\nconst validator "
},
{
"path": "src/create-request.js",
"chars": 1651,
"preview": "'use strict';\nconst _ = require('lodash');\n\n/**\n * Creates Alexa request\n * @param [options]\n * @param [options.type]\n *"
},
{
"path": "src/create-server.js",
"chars": 1167,
"preview": "'use strict';\nconst debug = require('debug')('alexia:debug');\nconst info = require('debug')('alexia:info');\n\n/**\n * Crea"
},
{
"path": "src/error-handler.js",
"chars": 714,
"preview": "'use strict';\n\nconst stackTrace = require('stack-trace');\n\nmodule.exports = {\n /**\n * @returns {string} formatted sta"
},
{
"path": "src/generate-speech-assets-i18n.js",
"chars": 2366,
"preview": "'use strict';\nconst _ = require('lodash');\nconst parseRichUtterances = require('./parse-rich-utterances');\n\nmodule.expor"
},
{
"path": "src/generate-speech-assets.js",
"chars": 2407,
"preview": "'use strict';\nconst _ = require('lodash');\n\nmodule.exports = (app) => {\n let assets = {\n intentSchema: genIntentSche"
},
{
"path": "src/handle-request.js",
"chars": 7943,
"preview": "'use strict';\nconst _ = require('lodash');\nconst debug = require('debug')('alexia:debug');\nconst info = require('debug')"
},
{
"path": "src/parse-rich-utterances.js",
"chars": 1821,
"preview": "'use strict';\n\nconst builtInSlotsMap = require('./built-in-slots-map');\nconst validator = require('./validator');\nconst "
},
{
"path": "src/save-speech-assets.js",
"chars": 1123,
"preview": "'use strict';\nconst fs = require('fs');\nconst path = require('path');\nconst _ = require('lodash');\n\nmodule.exports = (as"
},
{
"path": "src/validator.js",
"chars": 1394,
"preview": "'use strict';\nmodule.exports = {\n /**\n * @returns {boolean} whether given name is correct or not.\n * Each name must"
},
{
"path": "test/.eslintrc",
"chars": 186,
"preview": "{\n \"globals\": {\n \"describe\": true,\n \"before\": true,\n \"after\": true,\n \"beforeEach\": true,\n \"afterEach\": t"
},
{
"path": "test/actions.spec.js",
"chars": 3732,
"preview": "'use strict';\nconst expect = require('chai').expect;\nconst app = require('./test-apps/actions-app');\nconst alexia = requ"
},
{
"path": "test/basic.spec.js",
"chars": 11530,
"preview": "'use strict';\nconst _ = require('lodash');\nconst expect = require('chai').expect;\nconst alexia = require('..');\nconst ap"
},
{
"path": "test/contacts.spec.js",
"chars": 1614,
"preview": "'use strict';\nconst expect = require('chai').expect;\nconst app = require('./test-apps/contacts-app');\nconst alexia = req"
},
{
"path": "test/create-server.spec.js",
"chars": 1158,
"preview": "'use strict';\nconst expect = require('chai').expect;\nconst alexia = require('..');\nconst sinon = require('sinon');\nconst"
},
{
"path": "test/generate.spec.js",
"chars": 872,
"preview": "'use strict';\nconst expect = require('chai').expect;\nconst app = require('./test-apps/basic-app');\nconst assetsMock = re"
},
{
"path": "test/mock/assets.js",
"chars": 1121,
"preview": "'use strict';\n\nexports.intentSchema = {\n intents: [\n {intent: 'FirstIntent'},\n {intent: 'NamedIntent'},\n {inte"
},
{
"path": "test/mock/multi-lang-assets.js",
"chars": 881,
"preview": "'use strict';\n\nexports.intentSchema = {\n 'intents': [\n {\n 'intent': 'LocalizedIntent'\n },\n {\n 'inten"
},
{
"path": "test/multi-language.spec.js",
"chars": 5237,
"preview": "'use strict';\nconst expect = require('chai').expect;\nconst alexia = require('..');\nconst fs = require('fs');\nconst app ="
},
{
"path": "test/register-intents.spec.js",
"chars": 567,
"preview": "'use strict';\nconst expect = require('chai').expect;\nconst alexia = require('../');\n\ndescribe('registerIntents', () => {"
},
{
"path": "test/save-speech.spec.js",
"chars": 2046,
"preview": "'use strict';\nconst expect = require('chai').expect;\nconst fs = require('fs');\nconst rimraf = require('rimraf');\nconst a"
},
{
"path": "test/test-apps/actions-app.js",
"chars": 1059,
"preview": "'use strict';\nconst alexia = require('../..');\nconst app = alexia.createApp('ActionsApp');\n\nconst intentA = app.intent('"
},
{
"path": "test/test-apps/basic-app.js",
"chars": 1948,
"preview": "'use strict';\nconst alexia = require('../..');\nconst app = alexia.createApp('MyApp', {\n version: '1.2.3',\n shouldEndSe"
},
{
"path": "test/test-apps/contacts-app.js",
"chars": 1151,
"preview": "// short app for testing action's transitions saved in arrays\n'use strict';\nconst alexia = require('../..');\nconst app ="
},
{
"path": "test/test-apps/multi-language/locales/de/translation.json",
"chars": 491,
"preview": "{\n \"LaunchRequest\": {\n \"text\": \"Willkommen\"\n },\n \"LocalizedIntent\": {\n \"utterances\": \"sag hallo\",\n \"text\": \""
},
{
"path": "test/test-apps/multi-language/locales/en/custom-slots.json",
"chars": 39,
"preview": "{\n \"item\": [\"Car\", \"House\", \"Flat\"]\n}\n"
},
{
"path": "test/test-apps/multi-language/locales/en/translation.json",
"chars": 490,
"preview": "{\n \"LaunchRequest\": {\n \"text\": \"Welcome\"\n },\n \"LocalizedIntent\": {\n \"utterances\": [\"say hello\", \"say hello to m"
},
{
"path": "test/test-apps/multi-language/multi-language-app.js",
"chars": 1125,
"preview": "'use strict';\nconst i18next = require('i18next');\nconst FilesystemBackend = require('i18next-node-fs-backend');\nconst al"
},
{
"path": "test/test-apps/separate-intents/bye-intent.js",
"chars": 97,
"preview": "'use strict';\nmodule.exports = app => app.intent('ByeIntent', 'bye', () => {\n return 'Bye';\n});\n"
},
{
"path": "test/test-apps/separate-intents/hello-intent.js",
"chars": 103,
"preview": "'use strict';\nmodule.exports = app => app.intent('HelloIntent', 'hello', () => {\n return 'Hello';\n});\n"
}
]
About this extraction
This page contains the full source code of the Accenture/alexia GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 71 files (118.9 KB), approximately 31.7k tokens. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.