Initial import of wskdebug software donation from Adobe Inc.

wskdebug - Debugging and development tool for Apache OpenWhisk
- Sources for this grant: https://github.com/adobe/wskdebug/archive/v1.1.2.zip
- sha1 d83281f6abcebbf2e3dd5791e673159474b1c84d
- previously developed at: https://github.com/adobe/wskdebug

Co-authored-by: Moritz Raho <raho@adobe.com>
diff --git a/.circleci/config.yml b/.circleci/config.yml
new file mode 100644
index 0000000..2d1ab27
--- /dev/null
+++ b/.circleci/config.yml
@@ -0,0 +1,62 @@
+version: 2
+workflows:
+  version: 2
+  build:
+    jobs:
+      - build-node-latest
+      - build-node-12
+      - build-node-10
+      - build-node-8
+
+jobs:
+  build-node-latest: &build-template
+    machine: true
+    environment:
+      NODE_VERSION: node
+    steps:
+      # fix for local builds https://github.com/CircleCI-Public/circleci-cli/issues/330
+      - run:
+          name: Local build handling
+          command: |
+            if [[ ${CIRCLE_SHELL_ENV} =~ "localbuild" ]]; then
+              if [ -d /workdir ]; then
+                ln -s /workdir /tmp/_circleci_local_build_repo
+              else
+                echo "Run this local build using: circleci build -v \$(pwd):/workdir"
+                exit 1
+              fi
+            fi
+      - checkout
+      - run:
+          name: Install node
+          command: |
+            if [ -d /opt/circleci/.nvm ]; then
+              echo 'export NVM_DIR="/opt/circleci/.nvm"' >> $BASH_ENV
+            else
+              curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.35.1/install.sh | bash
+              echo 'export NVM_DIR="$HOME/.nvm"' >> $BASH_ENV
+            fi
+            echo ' [ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"' >> $BASH_ENV
+            source $BASH_ENV
+            nvm install $NODE_VERSION
+            nvm alias default $NODE_VERSION
+      - run: npm install
+      - run: npm test -- -v
+      - run: npm run report-coverage
+      - store_test_results:
+          path: test-results
+
+  build-node-12:
+    <<: *build-template
+    environment:
+      NODE_VERSION: 12
+
+  build-node-10:
+    <<: *build-template
+    environment:
+      NODE_VERSION: 10
+
+  build-node-8:
+    <<: *build-template
+    environment:
+      NODE_VERSION: 8
diff --git a/.eslintrc.js b/.eslintrc.js
new file mode 100644
index 0000000..a5a6719
--- /dev/null
+++ b/.eslintrc.js
@@ -0,0 +1,65 @@
+/*
+ Copyright 2019 Adobe. All rights reserved.
+ This file is licensed to you 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 REPRESENTATIONS
+ OF ANY KIND, either express or implied. See the License for the specific language
+ governing permissions and limitations under the License.
+*/
+
+// our principles:
+// - only problems, no syntax/stylistic related rules (done by extending from "eslint-config-problems")
+// - do not force new EcmaScript features
+//     + warn instead of error for improvements like "prefer-template"
+//     + ignore if they depend on the situation or can make readability harder like "prefer-arrow-callback" or "object-shorthand"
+
+module.exports = {
+    "extends": "problems",
+    "env": {
+        "node": true
+    },
+    "parserOptions": {
+        "ecmaVersion": 2018
+    },
+    "plugins": [
+        "mocha"
+    ],
+    "rules": {
+        "prefer-arrow-callback": "off",
+        "prefer-template": "off",
+        "object-shorthand": "off",
+
+        // console.* is wanted in OpenWhisk actions
+        "no-console": ["off", {"allow": true}],
+
+        "template-curly-spacing": ["warn", "never"],
+
+        "no-else-return": "off",
+
+        // mocha rules intended to catch common problems:
+        // - tests marked with .only() is usually only during development
+        // - tests with identical titles are confusing
+        // - tests defined using () => {} notation do not have access to globals
+        // - tests nested in tests is confusing
+        // - empty tests point to incomplete code
+        // - mocha allows for synch tests, async tests using 'done' callback,
+        //   async tests using Promise. Combining callback and a return of some value
+        //   indicates mixing up the test types
+        // - multiple before/after hooks in a single test suite/test is confusing
+        // - passing async functions to describe() is usually wrong, the individual tests
+        //   can be async however
+        "mocha/no-exclusive-tests": "error",
+        "mocha/no-identical-title": "error",
+        "mocha/no-mocha-arrows": "error",
+        "mocha/no-nested-tests": "error",
+        "mocha/no-pending-tests": "error",
+        "mocha/no-return-and-callback": "error",
+        "mocha/no-sibling-hooks": "error",
+        "mocha/no-async-describe": "error",
+        "indent": ["error", 4],
+        "keyword-spacing": [2]
+    }
+};
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
new file mode 100644
index 0000000..9132c36
--- /dev/null
+++ b/.github/CONTRIBUTING.md
@@ -0,0 +1,47 @@
+# Contributing
+
+Thanks for choosing to contribute!
+
+The following are a set of guidelines to follow when contributing to this project.
+
+## Code Of Conduct
+
+This project adheres to the Adobe [code of conduct](../CODE_OF_CONDUCT.md). By participating,
+you are expected to uphold this code. Please report unacceptable behavior to
+[Grp-opensourceoffice@adobe.com](mailto:Grp-opensourceoffice@adobe.com).
+
+## Have A Question?
+
+Start by filing an issue. The existing committers on this project work to reach
+consensus around project direction and issue solutions within issue threads
+(when appropriate).
+
+## Contributor License Agreement
+
+All third-party contributions to this project must be accompanied by a signed contributor
+license agreement. This gives Adobe permission to redistribute your contributions
+as part of the project. [Sign our CLA](http://opensource.adobe.com/cla.html). You
+only need to submit an Adobe CLA one time, so if you have submitted one previously,
+you are good to go!
+
+## Code Reviews
+
+All submissions should come in the form of pull requests and need to be reviewed
+by project committers. Read [GitHub's pull request documentation](https://help.github.com/articles/about-pull-requests/)
+for more information on sending pull requests.
+
+Lastly, please follow the [pull request template](PULL_REQUEST_TEMPLATE.md) when
+submitting a pull request!
+
+## From Contributor To Committer
+
+We love contributions from our community! If you'd like to go a step beyond contributor
+and become a committer with full write access and a say in the project, you must
+be invited to the project. The existing committers employ an internal nomination
+process that must reach lazy consensus (silence is approval) before invitations
+are issued. If you feel you are qualified and want to get more deeply involved,
+feel free to reach out to existing committers to have a conversation about that.
+
+## Security Issues
+
+Security issues shouldn't be reported on this issue tracker. Instead, [file an issue to our security experts](https://helpx.adobe.com/security/alertus.html)
\ No newline at end of file
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..73ce74b
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+# package directories
+node_modules
+jspm_packages
+
+# Serverless directories
+.serverless
+
+# node package
+*.tgz
+
+.webpack
+
+.nyc_output
+coverage
+build
+coverage.lcov
+test-results
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..7d5e6c8
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,24 @@
+examples/
+
+.eslint*
+.vscode/
+.DS_Store
+
+*wskdebug*.tgz
+*.gif
+*.png
+*.jpg
+TODO.md
+LICENSE_HEADER.md
+CODE_OF_CONDUCT.md
+.github/
+build/
+test/
+.circleci/
+.webpack
+
+codecov.yml
+coverage.lcov
+.nyc_output/
+coverage/
+test-results/
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..e97095e
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,6 @@
+{
+    "licenser.license": "Custom",
+    "licenser.customHeader": " Copyright 2019 Adobe. All rights reserved.\n\n This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n you may not use this file except in compliance with the License. You may obtain a copy\n of the License at http://www.apache.org/licenses/LICENSE-2.0\n\n Unless required by applicable law or agreed to in writing, software distributed under\n the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n OF ANY KIND, either express or implied. See the License for the specific language\n governing permissions and limitations under the License.",
+    "licenser.useSingleLineStyle": false,
+    "licenser.author": ""
+}
\ No newline at end of file
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 0000000..730fe24
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,74 @@
+# Adobe Code of Conduct
+
+## Our Pledge
+
+In the interest of fostering an open and welcoming environment, we as
+contributors and maintainers pledge to making participation in our project and
+our community a harassment-free experience for everyone, regardless of age, body
+size, disability, ethnicity, gender identity and expression, level of experience,
+nationality, personal appearance, race, religion, or sexual identity and
+orientation.
+
+## Our Standards
+
+Examples of behavior that contributes to creating a positive environment
+include:
+
+* Using welcoming and inclusive language
+* Being respectful of differing viewpoints and experiences
+* Gracefully accepting constructive criticism
+* Focusing on what is best for the community
+* Showing empathy towards other community members
+
+Examples of unacceptable behavior by participants include:
+
+* The use of sexualized language or imagery and unwelcome sexual attention or
+advances
+* Trolling, insulting/derogatory comments, and personal or political attacks
+* Public or private harassment
+* Publishing others' private information, such as a physical or electronic
+  address, without explicit permission
+* Other conduct which could reasonably be considered inappropriate in a
+  professional setting
+
+## Our Responsibilities
+
+Project maintainers are responsible for clarifying the standards of acceptable
+behavior and are expected to take appropriate and fair corrective action in
+response to any instances of unacceptable behavior.
+
+Project maintainers have the right and responsibility to remove, edit, or
+reject comments, commits, code, wiki edits, issues, and other contributions
+that are not aligned to this Code of Conduct, or to ban temporarily or
+permanently any contributor for other behaviors that they deem inappropriate,
+threatening, offensive, or harmful.
+
+## Scope
+
+This Code of Conduct applies both within project spaces and in public spaces
+when an individual is representing the project or its community. Examples of
+representing a project or community include using an official project e-mail
+address, posting via an official social media account, or acting as an appointed
+representative at an online or offline event. Representation of a project may be
+further defined and clarified by project maintainers.
+
+## Enforcement
+
+Instances of abusive, harassing, or otherwise unacceptable behavior may be
+reported by contacting the project team at Grp-opensourceoffice@adobe.com. All
+complaints will be reviewed and investigated and will result in a response that
+is deemed necessary and appropriate to the circumstances. The project team is
+obligated to maintain confidentiality with regard to the reporter of an incident.
+Further details of specific enforcement policies may be posted separately.
+
+Project maintainers who do not follow or enforce the Code of Conduct in good
+faith may face temporary or permanent repercussions as determined by other
+members of the project's leadership.
+
+## Attribution
+
+This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
+available at [https://contributor-covenant.org/version/1/4][version]
+
+[homepage]: https://contributor-covenant.org
+[version]: https://contributor-covenant.org/version/1/4/
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..e3af604
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+                                 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:
+
+      (a) You must give any other recipients of the Work or
+          Derivative Works a copy of this License; and
+
+      (b) You must cause any modified files to carry prominent notices
+          stating that You changed the files; and
+
+      (c) 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
+
+      (d) 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 2019 Adobe
+
+   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.
diff --git a/LICENSE_HEADER.md b/LICENSE_HEADER.md
new file mode 100644
index 0000000..1afa910
--- /dev/null
+++ b/LICENSE_HEADER.md
@@ -0,0 +1,20 @@
+Apache V2 License Header
+------------------------
+
+Use this header for all source files.
+
+This includes an Adobe copyright notice.
+
+```
+/*
+ Copyright 2019 Adobe. All rights reserved.
+ This file is licensed to you 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 REPRESENTATIONS
+ OF ANY KIND, either express or implied. See the License for the specific language
+ governing permissions and limitations under the License.
+*/
+```
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..5cb7eb3
--- /dev/null
+++ b/README.md
@@ -0,0 +1,590 @@
+<!-- disabled badges
+cli: ![](https://img.shields.io/badge/cli-wskdebug-brightgreen)
+github actions: [![Actions Status](https://github.com/adobe/wskdebug/workflows/CI/badge.svg)](https://github.com/adobe/wskdebug/actions)
+-->
+[![npm version](https://img.shields.io/npm/v/@adobe/wskdebug)](https://www.npmjs.com/package/@adobe/wskdebug) [![License](https://img.shields.io/badge/license-Apache--2.0-blue.svg)](http://www.apache.org/licenses/LICENSE-2.0) [![CircleCI](https://circleci.com/gh/adobe/wskdebug.svg?style=shield)](https://circleci.com/gh/adobe/wskdebug) [![codecov](https://codecov.io/gh/adobe/wskdebug/branch/master/graph/badge.svg)](https://codecov.io/gh/adobe/wskdebug) [![Total alerts](https://img.shields.io/lgtm/alerts/g/adobe/wskdebug.svg?logo=lgtm&logoWidth=18)](https://lgtm.com/projects/g/adobe/wskdebug/alerts/)
+
+
+wskdebug
+========
+
+Debugging and live development for [Apache OpenWhisk](https://openwhisk.apache.org). CLI tool written in [Node.js](https://nodejs.org) and depending on a [local Docker](https://www.docker.com/products/docker-desktop). Integrates easily into IDEs such as [Visual Studio Code](https://code.visualstudio.com).
+
+---
+
+Live development of a web action using `wskdebug`:
+
+![screen cast showing debugging of a web action using wskdebug](resources/wskdebug-demo.gif)
+
+_On the left [Visual Studio Code](https://code.visualstudio.com) in debug mode. On the right, a browser with the page rendered by the web action. The developer notices the feature of handling the `name` is not implemented yet. A breakpoint shows them that `name` is set, but it's not used. They add the code to respond and greet with `name`. Simply by saving the code, the browser auto reloads the page and the breakpoint is hit again. They step through to see that the new code is working fine, and get the expected result in the browser: "Hello, Alex!"._
+
+---
+
+[Youtube video of wskdebug being demoed to the OpenWhisk community](https://youtu.be/Qtsi8VFm0uY?t=727):
+
+[![youtube video demoing wskdebug to the openwhisk community](resources/wskdebug-ow-meeting-screenshot.png)](https://youtu.be/Qtsi8VFm0uY?t=727)
+
+---
+
+### Contents
+
+  * [Installation](#installation)
+  * [About](#about)
+  * [Usage](#usage)
+  * [Troubleshooting](#troubleshooting)
+  * [How it works](#how-it-works)
+  * [Development](#development)
+  * [Contributing](#contributing)
+  * [Licensing](#licensing)
+
+<a name="installation"></a>
+## Installation
+
+`wskdebug` requires [Node.js](https://nodejs.org) (version 10+), `npm` and a local [Docker](https://www.docker.com/products/docker-desktop) environment.
+
+To install or update run:
+
+```
+npm install -g @adobe/wskdebug
+```
+
+<a name="uninstall"></a>
+### Uninstall
+
+```
+npm uninstall -g @adobe/wskdebug
+```
+
+<a name="about"></a>
+## About
+
+_wskdebug_ is a command line tool to **develop and debug** [OpenWhisk actions](https://openwhisk.apache.org/documentation.html#programming-model-actions) in your favorite IDE or debugger with a **fast feedback loop**. It features:
+
+* full debugging of actions of the respective language runtime
+* automatic code reloading
+* LiveReload for web actions
+* auto-invoking of actions on code changes
+* or running any shell command such as a curl request on code changes
+
+Currently, [Node.js actions](https://openwhisk.apache.org/documentation.html#nodejs) are supported out of the box. For others, basic debugging can be [configured on the command line](#unsupported-action-kinds), while automatic code reloading needs an [extension in `wskdebug`](#extending-wskdebug-for-other-kinds).
+
+### Note on timeouts
+
+Web actions or other blocking invocations time out after **1 minute in OpenWhisk**. This limit cannot be configured. This means that if the debugging session (stepping through code) takes longer than 1 minute, any web action will return an error and any blocking invocations will just get the activation id, which most callers of a blocking invocation do not expect. 
+
+However, there is no time limit on stepping through the code itself if you do not care about the result of the action being handled synchronously.
+
+<a name="usage"></a>
+## Usage
+
+The action to debug (e.g. `myaction`) must already be deployed.
+
++ [Node.js: Visual Studio Code](#nodejs-visual-studio-code)
++ [Node.js: Multiple actions](#nodejs-multiple-actions)
++ [Node.js: Plain usage](#nodejs-plain-usage)
++ [Node.js: Chrome DevTools](#nodejs-chrome-devtools)
++ [Node.js: node-inspect command line](#nodejs-node-inspect-command-line)
++ [Unsupported action kinds](#unsupported-action-kinds)
++ [Source mounting](#source-mounting)
++ [Live reloading](#live-reloading)
++ [Hit condition](#hit-condition)
++ [Custom build step](#custom-build-step)
++ [Help output](#help-output)
+
+<a name="nodejs-visual-studio-code"></a>
+### Node.js: Visual Studio Code
+
+Add the configuration below to your [launch.json](https://code.visualstudio.com/docs/editor/debugging#_launch-configurations). Replace `MYACTION` with the name of your action and `ACTION.js` with the source file containing the action. When you run this, it will start wskdebug and should automatically connect the debugger.
+
+```
+    "configurations": [
+        {
+            "type": "node",
+            "request": "launch",
+            "name": "wskdebug MYACTION",
+            "runtimeExecutable": "wskdebug",
+            "args": [ "MYACTION", "ACTION.js", "-l" ],
+            "localRoot": "${workspaceFolder}",
+            "remoteRoot": "/code",
+            "outputCapture": "std"
+        }
+    ]
+```
+
+Stop the debugger in VS Code to end the debugging session and `wskdebug`.
+
+This snippets enables browser LiveReloading using `-l`. For other reloading options, see [live reloading](#live-reloading).
+
+For troubleshooting, you can run the debugger in verbose mode by adding `"-v"` to the `args` array.
+
+<a name="nodejs-multiple-actions"></a>
+### Node.js: Multiple actions
+
+Each `wskdebug` process can debug and live reload exactly a single action. To debug multiple actions, run `wskdebug` for each. If all of them are using the same kind/language, where the default debug port is the same, different ports need to be used.
+
+In VS code you can start multiple debuggers from the same window thanks to compounds. Compounds provide a way to aggregate VS code configurations to run them together.
+Here is a `.vscode/launch.json` example that uses compounds to expose a config starting 2 wskdebug instances:
+
+```json
+{
+  "configurations": [
+    {
+      "type": "node",
+      "request": "launch",
+      "name": "mypackage/action1",
+      "runtimeExecutable": "wskdebug",
+      "args": [
+        "mypackage/action1",
+        "action1.js"
+      ],
+      "localRoot": "${workspaceFolder}",
+      "remoteRoot": "/code",
+      "outputCapture": "std"
+    },
+    {
+      "type": "node",
+      "request": "launch",
+      "name": "mypackage/action2",
+      "runtimeExecutable": "wskdebug",
+      "args": [
+        "mypackage/action2",
+        "action2.js"
+      ],
+      "localRoot": "${workspaceFolder}",
+      "remoteRoot": "/code",
+      "outputCapture": "std"
+    }
+  ],
+  "compounds": [
+    {
+      "name": "All actions",
+      "configurations": [
+        "mypackage/action1",
+        "mypackage/action2"
+      ]
+    }
+  ]
+}
+```
+
+Alternatively, if you don't want to use compounds, you can have a separate VS code window for each action with separate VS code `launch` configurations.
+
+With `launch`, VS Code will automatically pick an unused debug port and pass it as `--inspect=port` param to `wskdebug` as if it were `node`, and `wskdebug` understands this as alias for its `--port` argument.
+
+Otherwise you have to make sure to pass a different `--port` to each `wskdebug`. Similarly, if you use browser live reloading for multiple actions, you must specify different ports for that uing `--lr-port` on each instance.
+
+<a name="nodejs-plain-usage"></a>
+### Node.js: Plain usage
+
+Run `wskdebug` and specify the action
+
+```
+wskdebug myaction
+```
+
+This will output (in case of a nodejs action):
+
+```
+Debug type: nodejs
+Debug port: localhost:9229
+Ready, waiting for activations of myaction
+Use CTRL+C to exit
+```
+
+You can then use a debugger to connect to the debug port, in this case `localhost:9229`. See below.
+
+When done, terminate `wskdebug` (not kill!) using CTRL+C. It will cleanup and remove the forwarding agent and restore the original action.
+
+<a name="nodejs-chrome-devtools"></a>
+### Node.js: Chrome DevTools
+
+Run [Node.js: Plain usage](#nodejs-plain-usage) and then:
+
+1. Open Chrome
+2. Enter `about:inspect`
+3. You should see a remote target `app.js`
+4. Click on "Open dedicated DevTools for Node" (but not on "inspect" under Target)
+5. This should open a new window
+6. Go to Sources > Node
+7. Find the `runner.js`
+8. Set a breakpoint on the line `thisRunner.userScriptMain(args)` inside `this.run()` (around line 97)
+9. Invoke the action
+10. Debugger should hit the breakpoint
+11. Then step into the function, it should now show the action sources in a tab named like `VM201` (the openwhisk nodejs runtime evals() the script, hence it's not directly listed as source file)
+
+See also this [article](https://medium.com/@paul_irish/debugging-node-js-nightlies-with-chrome-devtools-7c4a1b95ae27).
+
+<a name="nodejs-node-inspect-command-line"></a>
+### Node.js: node-inspect command line
+
+Run [Node.js: Plain usage](#nodejs-plain-usage) and then:
+
+Use the command line Node debugger [node-inspect](https://github.com/nodejs/node-inspect):
+
+```
+node-inspect 127.0.0.1:9229
+```
+
+<a name="unsupported-action-kinds"></a>
+### Unsupported action kinds
+
+To enable debugging for kinds/languages not supported out of the box, you can specify these cli arguments manually:
+
+* `--internal-port` the actual language debug port inside the container
+* `--command` override the docker run command for the image to e.g. pass a debug flag to the language enviroment
+* `--port` (optional) the port as it will be exposed from the container to the host, i.e. to what clients will connect to. defaults to `--internal-port` if set
+* `--image` (optional) control the docker image used as runtime for the action
+
+Once you found a working configuration, feel encouraged to open a pull request to [add support for this out of the box](#default-debug-ports-and-commands)!
+
+For automatic code reloading for other languages, `wskdebug` needs to be [extended](#extending-wskdebug-for-other-kinds).
+
+<a name="source-mounting"></a>
+### Source mounting
+
+When a `<source-path>` is provided, `wskdebug` will load the local sources and run them as action (for supported languages/kinds). This enables the hot code reloading feature.
+
+For this to work, you must run `wskdebug` in the root folder of your project, _below_ which all the sources are (e.g. in nodejs anything that is loaded through `require()` statements), and then provide a relative path to the main js file (the one that contains the action `main` function) as `<source-path>` . If you have sources outside the current working directory `wskdebug` is executed in, they would not be visible to the action that `wskdebug` runs.
+
+For example, say you have a folder structure like this:
+
+```
+lib/
+    action/
+        action.js
+    util.js
+    other.js
+```
+
+Then you want to run it in the root like this:
+
+```
+wskdebug myaction lib/action/action.js
+```
+
+Under the hood, `wskdebug` will mount the working directory it is executed in into the local action container (under `/code` inside the container), and then tell it to load the `lib/action/action.js` file as action entry point.
+
+If `--on-build` and `--build-path` are specified, then `--build-path` is used instead of the `<source-path>` for running the action inside the container. It still mounts the current working directory. But `<source-path>` is still relevant for detecting local modifications for [live reloading](#live-reloading).
+
+<a name="live-reloading"></a>
+### Live reloading
+
+There are 3 different live reload mechanism possible that will trigger something when sources are modified. Any of them enables the hot reloading of code on any new activation.
+
+* Browser `LiveReload` using `-l`: works with [LiveReload](http://livereload.com) browser extensions (though we noticed only Chrome worked reliably) that will automatically reload the web page. Great for web actions that render HTML to browsers.
+* Action invocation using `-P` and `-a`: specify `-P` pointing to a json file with the invocation parameters and the debugged action will be automatically invoked with these parameters. This will also automatically invoke if that json file is modified. If you need to trigger a different action (because there is chain of actions before the one you are debugging), define it using `-a`.
+* Arbitrary shell command using `-r`: this can be used to invoke web APIs implemented by web actions using `curl`, or any scenario where something needs to be triggered so that the debugged action gets activated downstream.
+
+By default it watches for changes underneath the current working directory for these file extensions (reflecting common OpenWhisk kinds and json for `-P params.json` auto-execution):
+
+```
+json, js, ts, coffee, py, rb, erb, go, java, scala, php, swift, rs, cs, bal, php, php5
+```
+
+The directory or directories to watch can be changed using the `--watch` argument, which can be a directory path glob. You can also specify multiple via `--watch one --watch two` or `--watch one two`.
+
+The extensions to watch can be changed through the `--watch-exts` argument, e.g. `--watch-exts js ts`.
+
+<a name="hit-condition"></a>
+### Hit condition
+
+If an action is invoked frequently but you only want to catch certain invocations, such as ones you control, you can set a condition to limit when the debugger should be invoked using `-c` or `--condition`. This must be a javascript expression which will be evaluated agains the input parameters.
+
+For example, with a condition like this:
+
+```
+-c "debug === 'true'"
+```
+
+an invocation with these parameters would trigger the debugger:
+
+```
+{
+  "debug": "true",
+  "some": "value"
+}
+```
+
+In another example for a web action, let's assume we want to catch all requests from Chrome. We would check for the header:
+
+```
+-c "__ow_headers['user-agent'].includes('Chrome')"
+```
+
+If the hit condition is true, the action will be forwarded to the local debug container. If not, the original action (copy) in the OpenWhisk system will be invoked.
+
+Please note that if source mounting is enabled, this will not have an effect on the original action copy that is invoked if the hit condition is not met. This means if condition is met, the latest local code changes will have an effect, but if not, the version of the action before wskdebug was started will be executed.
+
+<a name="custom-build-step"></a>
+### Custom build step
+
+For some projects, the raw source code that developers edit in the IDE goes through a build process before being deployed as OpenWhisk action. To support this, `wskdebug` has these arguments:
+
+* `--on-build`: Shell command for custom action build step
+* `--build-path`: Path to built action, result of --on-build command
+
+As a simple example, imagine the build process for an action with source file `action.js` deployed as `myaction` is simply renaming the file and placing it as `index.js` in a `build/` directory:
+
+```
+mkdir build/
+cp action.js build/index.js
+```
+
+Replace the copy/rename here with whatever build step is happening. Make sure source maps are enabled.
+
+Then you would invoke `wskdebug` like this:
+
+```
+wskdebug myaction action.js \
+    --on-build "mkdir build/; cp action.js build/index.js" \
+    --build-path build/index.js
+```
+
+**Note**: When using `--on-build`, you might have to set `--watch` to the directory that holds the source files and which is not the build directory. Otherwise you could get an endless loop as writing the build files will trigger `--on-build` again. The `--build-path` file will be explicitly excluded from watching, but other files next to it that might be generated as part of the build are not. For example, if there is a `src/` and `build/` directory and multiple files would be generated under `build/`, add `--watch src`:
+
+```
+wskdebug myaction src/action.js \
+    --on-build "mkdir build/; cp action.js build/index.js; cp util.js build/util.js" \
+    --build-path build/index.js \
+    --watch src
+```
+
+<a name="help-output"></a>
+### Help output
+
+```
+wskdebug <action> [source-path]
+
+Debug an OpenWhisk <action> by forwarding its activations to a local docker container that
+has debugging enabled and its debug port exposed to the host.
+
+If only <action> is specified, the deployed action code is debugged.
+
+If [source-path] is set, it must point to the local action sources which will be mounted
+into the debug container. Sources will be automatically reloaded on each new activation.
+This feature depends on the kind.
+
+Supported kinds:
+- nodejs: Node.js V8 inspect debugger on port 9229. Supports source mount
+
+
+Arguments:
+  action       Name of action to debug                                            [string]
+  source-path  Path to local action sources, file or folder (optional)            [string]
+
+Action options:
+  -m, --main         Name of action entry point                                   [string]
+  -k, --kind         Action kind override, needed for blackbox images             [string]
+  -i, --image        Docker image to use as action container                      [string]
+  --on-build         Shell command for custom action build step                   [string]
+  --build-path       Path to built action, result of --on-build command           [string]
+
+LiveReload options:
+  -l            Enable browser LiveReload on [source-path]                       [boolean]
+  --lr-port     Port for browser LiveReload (defaults to 35729)                   [number]
+  -P            Invoke action with these parameters on changes to [source-path].
+                Argument can be json string or name of json file.                 [string]
+  -a            Name of custom action to invoke upon changes to [source-path].
+                Defaults to <action> if -P is set.                                [string]
+  -r            Shell command to run upon changes to [source-path]                [string]
+  --watch       Glob pattern(s) to watch for source modifications                  [array]
+  --watch-exts  File extensions to watch for modifications                         [array]
+
+Debugger options:
+  -p, --port       Debug port exposed from container that debugging clients connect to.
+                   Defaults to --internal-port if set or standard debug port of the kind.
+                   Node.js arguments --inspect and co. can be used too.           [number]
+  --internal-port  Actual debug port inside the container. Must match port opened by
+                   --command. Defaults to standard debug port of kind.            [number]
+  --command        Custom container command that enables debugging                [string]
+  --docker-args    Additional docker run arguments for container. Must be quoted and start
+                   with space: 'wskdebug --docker-args " -e key=var" myaction'    [string]
+  --on-start       Shell command to run when debugger is up                       [string]
+
+Agent options:
+  -c, --condition  Hit condition to trigger debugger. Javascript expression evaluated
+                   against input parameters. Example: 'debug == 'true'            [string]
+  --agent-timeout  Debugging agent timeout (seconds). Default: 5 min              [number]
+  --ngrok          Use ngrok.com for agent forwarding.                           [boolean]
+  --ngrok-region   Ngrok region to use. Defaults to 'us'.                         [string]
+
+Options:
+  -v, --verbose  Verbose output. Logs activation parameters and result           [boolean]
+  --version      Show version number                                             [boolean]
+  -h, --help     Show help                                                       [boolean]
+```
+
+<a name="troubleshooting"></a>
+## Troubleshooting
+
+### Cannot install globally
+
+If you get an error during `npm install -g @adobe/wskdebug` like this:
+
+```
+ngrok - downloading binary https://bin.equinox.io/c/4VmDzA7iaHb/ngrok-stable-darwin-amd64.zip
+ngrok - error storing binary to local file [Error: EACCES: permission denied, open '/usr/local/lib/node_modules/@adobe/wskdebug/node_modules/ngrok/bin/aHR0cHM6Ly9iaW4uZXF1aW5veC5pby9jLzRWbUR6QTdpYUhiL25ncm9rLXN0YWJsZS1kYXJ3aW4tYW1kNjQuemlw.zip'] {
+  errno: -13,
+  code: 'EACCES',
+  syscall: 'open',
+  path: '/usr/local/lib/node_modules/@adobe/wskdebug/node_modules/ngrok/bin/aHR0cHM6Ly9iaW4uZXF1aW5veC5pby9jLzRWbUR6QTdpYUhiL25ncm9rLXN0YWJsZS1kYXJ3aW4tYW1kNjQuemlw.zip'
+}
+```
+
+run this command below before trying the install again:
+
+```
+sudo chown -R $(whoami) /usr/{lib/node_modules}
+```
+
+
+The dependency `ngrok` requires full write permission in `/usr/local/lib/node_modules` during its custom install phase. This is a [known ngrok issue](https://github.com/inconshreveable/ngrok/issues/429).
+
+
+### Does not work, namespace shows as undefined
+
+Your `~/.wskprops` must include the correct `NAMESPACE` field. See [issue #3](https://github.com/adobe/wskdebug/issues/3).
+
+### No invocations visible in wskdebug
+
+* Is `wskdebug` working against the correct namespace? You can see that in the "Starting debugger for ..." output at the very start. If you tend to use `WSK_CONFIG_FILE` in your shell, please be aware that IDEs starting `wskdebug` will use `~/.wskprops` unless you set the environment variable for the `wskdebug` invocation in the IDE.
+* Wait a bit and try again. Restart (CTRL+C, then start `wskdebug` again), wait a bit and try again. Catching the invocations is not 100% perfect.
+
+### Port is already allocated
+
+You can only run one `wskdebug` aka one action for the same runtime (debug port) at a time.
+
+If you get an error like this:
+
+```
+docker: Error response from daemon: driver failed programming external connectivity on endpoint wskdebug-webaction-1559204115390 (3919892fab2981bf9feab0b6ba3fc256676de59d1a6ab67519295757313e8ac3): Bind for 0.0.0.0:9229 failed: port is already allocated.
+```
+
+it means that there is another `wskdebug` already running or that its container was left over, blocking the debug port.
+
+Either quit the other `wskdebug` or if its an unexpected left over, terminate the docker container using:
+
+```
+docker rm -f wskdebug-webaction-1559204115390
+```
+
+The containers are named `wskdebug-ACTION-TIMESTAMP`.
+
+### Restore action
+
+If `wskdebug` fails unexpectedly or gets killed, it might leave the forwarding agent behind in place of the action. You should be able to restore the original action using the copied action named `*_wskdebug_original`.
+
+```
+wsk action delete myaction
+wsk action create --copy myaction myaction_wskdebug_original
+wsk action delete myaction_wskdebug_original
+```
+
+Alternatively you could also redeploy your action and then delete the backup:
+
+```
+# deploy command might vary
+wsk action update myaction myaction.js
+
+wsk action delete myaction_wskdebug_original
+```
+
+<a name="how-it-works"></a>
+## How it works
+
+`wskdebug` supports debugging of an action by **forwarding** it from the OpenWhisk system to a **local container on your desktop** and executing it there. By overriding the command to run in the container and other `docker run` configurations, the local container respectively the language runtime inside the container is run in debug mode and the respective debug port is opened and exposed to the local desktop.
+
+Furthermore, the local container can **mount the local source files** and automatically reload them on every invocation. `wskdebug` can also listen for changes to the source files and trigger an automatic reload of a web action or direct invocation of the action or just any shell command, e.g. if you need to make more nuanced curl requests to trigger your API.
+
+The forwarding works by **replacing the original action with a special agent**. There are different [agent variations](agent/), which all achieve the same: catch activations of the action on the OpenWhisk side, pass them on to the local container and finally return the local result back to the original activation.
+
+The fastest option (concurrency) leverages the NodeJS concurrency feature available in some OpenWhisk installations where a single container instance will receive all activations. It uses queues implemented as global variables of the action so that multiple invocations of this action (agent) can see and wait for each other.
+
+The second fastest option - and fastest in case of an OpenWhisk that does not support concurrency - is using [ngrok](https://ngrok.com) localhost forwarding. It must be manually selected using `--ngrok` on the command line. This works even without an ngrok account.
+
+Lastly, there is the "activation DB" agent which simply stores the activation input and result as separate activations (using helper actions named `*_wskdebug_invoked` and `*_wskdebug_completed`) and polls them via `wsk activation list`, both from wskdebug (for new activations) and in the agent itself (waiting for results).
+
+Inside the agents waiting for the result is where the limits have an effect: if the invocation is synchronous (blocking=true) or a web action, OpenWhisk will not wait for more than 1 minute. For asynchronous invocations, it depends on the timeout setting of the action. `wskdebug` sets it to 5 minute by default but it can be controlled via `--agent-timeout` to set it to a feasible maximum.
+
+The debugger works with all normal actions, including web actions. Sequences are not directly supported but can be debugged by starting a debugger for each action in the sequence see [Nodejs Multiple actions](#nodejs-multiple-actions). Compositions itself (not the component actions) are not supported. The solution is only based on custom actions and works with any OpenWhisk system. `wskdebug` was inspired by the now defunct [wskdb](https://github.com/apache/incubator-openwhisk-debugger).
+
+![diagram showing wskdebug](resources/wskdebug-architecture.png)
+
+_This diagram shows how `wskdebug` works including debugging, source mounting and browser LiveReload. The wskdebug components are marked blue. Follow the steps from (1) to (10) to see what happens when the user edits and saves a source file._
+
+<a name="development"></a>
+## Development
+
+<a name="extending-wskdebug-for-other-kinds"></a>
+### Extending wskdebug for other kinds
+
+For automatic code reloading for other languages, `wskdebug` needs to be extended to support these kinds. This happens inside [src/kinds](src/kinds).
+
+- [Mapping of kinds to docker images](#mapping-of-kinds-to-docker-images)
+- [Custom debug kind](#custom-debug-kind)
+- [Default debug ports and commands](#default-debug-ports-and-commands)
+- [Support code reloading](#support-code-reloading)
+- [Available variables](#available-variables)
+
+
+<a name="mapping-of-kinds-to-docker-images"></a>
+#### Mapping of kinds to docker images
+
+To change the mapping of kinds to docker images (based on [runtimes.json](https://github.com/apache/incubator-openwhisk/blob/master/ansible/files/runtimes.json) from OpenWhisk), change [src/kinds/kinds.js](src/kinds/kinds.js).
+
+<a name="custom-debug-kind"></a>
+#### Custom debug kind
+
+For default debug instructions and live code reloading, a custom "debug kind js" needs to be provided at `src/kinds/<debugKind>/<debugKind>.js`.
+
+`<debugKind>` must be without the version, i.e. the part before the `:` in a kind. For example for `nodejs:8` it will be `nodejs`, for `nodejs:default` it will be `nodejs` as well. This is because normally the debug mechanism is the same across language versions. To define a different debug kind, add a `debug` field in [src/kinds/kinds.js](src/kinds/kinds.js) for the particular kind, e.g. for `nodejs:6`set `debug: "nodejsLegacy"` and then it must be under `src/kinds/nodejsLegacy/nodejsLegacy.js`.
+
+This js module needs to export an object with different fields. These can be either a literal value (for simple fixed things such as a port) or a function (allowing for dynamic logic based on cli arguments etc.). These functions get the `invoker` passed as argument, which provides [certain variables](#available-variables) such as cli arguments.
+
+A complete example is the [src/kinds/nodejs/nodejs.js](src/kinds/nodejs/nodejs.js).
+
+See below for the different items to do.
+
+<a name="default-debug-ports-and-commands"></a>
+#### Default debug ports and commands
+
+To just add default debug ports and docker command for a kind, add a custom debug kind and export an object with  `description`, `port` and `command` fields. Optionally `dockerArgs` for extra docker arguments (such as passing in environment variables using `-e` if necessary).
+
+<a name="support-code-reloading"></a>
+#### Support code reloading
+
+To support live code reloading/mounting, add a custom debug kind and export an object with a `mountAction` function. This has to return an action that dynamically loads the code at the start of each activation. A typical approach is to mount the `<source-path>` (folder) passed on the cli as `/code` inside the docker container, from where the mount action can reload it. The exact mechanism will depend on the language - in node.js for example, `eval()` is [used for plain actions](src/kinds/nodejs/mount-plain.js#L30). The docker mounting can be specified in `dockerArgs`.
+
+The `mountAction(invoker)` must return an object that is an openwhisk action `/init` definition, which consists of:
+
+* `binary`: true if zip or binary distribution (depends on kind), false if plain code (for scripting languages)
+* `main`: name of the entry function
+* `code`: string with source code or base64 encoded if binary for the live mount
+
+Example mounting actions from nodejs are [mount-plain.js](src/kinds/nodejs/mount-plain.js) (for plain node.js actions) and [mount-require.js](src/kinds/nodejs/mount-require.js) (for action zips expecting node modules using `require()`).
+
+<a name="available-variables"></a>
+#### Available variables
+
+See also [invoker.js](src/invoker.js). Note that some of these might not be set yet, for example `invoker.debug.port` is not yet available when `port()` is invoked. The raw cli args are usually available as `invoker.<cli-arg>`.
+
+| Variable | Type | Description |
+|----------|------|-------------|
+| `invoker.main` | `string` | name of the `main` entry point (from cli args) |
+| `invoker.sourcePath` | `string` | path to the source file either `<source-path>` or the `--build-path` |
+| `invoker.sourceDir` | `string` | absolute path to root directory to mount in the container |
+| `invoker.sourceFile` | `string` | relative path from `sourceDir` to `sourcePath` |
+| `invoker.action` | `object` | the object representing the debugged action, as specified as `Action` model in the [openwhisk REST API spec](http://petstore.swagger.io/?url=https://raw.githubusercontent.com/openwhisk/openwhisk/master/core/controller/src/main/resources/apiv1swagger.json) |
+| `invoker.debug.port` | `number` | `--port` from cli args or `--internal-port` or the `port` from the debug kind js (in that preference) |
+| `invoker.debug.internalPort` | `number` | `--internal-port` from cli args or if not specified, the `port` from the debug kind js |
+| `invoker.debug.command` | `string` | `--command` from cli args or the `command` from the debug kind js (in that preference) |
+
+<a name="contributing"></a>
+## Contributing
+
+Contributions are welcomed! Read the [Contributing Guide](.github/CONTRIBUTING.md) for more information.
+
+<a name="licensing"></a>
+## Licensing
+
+This project is licensed under the Apache V2 License. See [LICENSE](LICENSE) for more information.
diff --git a/agent/agent-activationdb.js b/agent/agent-activationdb.js
new file mode 100644
index 0000000..7a76a3b
--- /dev/null
+++ b/agent/agent-activationdb.js
@@ -0,0 +1,157 @@
+/*
+ Copyright 2019 Adobe. All rights reserved.
+ This file is licensed to you 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 REPRESENTATIONS
+ OF ANY KIND, either express or implied. See the License for the specific language
+ governing permissions and limitations under the License.
+*/
+
+/* eslint-disable strict */
+
+// agent that forwards invocations to the developer's computer by storing them in the
+// activation db using simple "echo.js" actions (_wskdebug_invoked & _wskdebug_completed),
+// and polling the activation db for those
+
+const openwhisk = require('openwhisk');
+
+const activationListFilterOnlyBasename = false;
+
+function outOfTime(deadline) {
+    // stop 10 seconds before timeout, to have enough buffer
+    return (Date.now() >= ((deadline || process.env.__OW_DEADLINE) - 10*1000));
+}
+
+async function sleep(millis) {
+    return new Promise(resolve => setTimeout(resolve, millis));
+}
+
+function removePrefix(str, prefix) {
+    if (str.startsWith(prefix)) {
+        return str.substring(prefix.length);
+    }
+    return str;
+}
+
+function actionName() {
+    return removePrefix(process.env.__OW_ACTION_NAME, `/${process.env.__OW_NAMESPACE}/`);
+}
+
+async function newActivation(args) {
+    args.$activationId = process.env.__OW_ACTIVATION_ID;
+    await openwhisk().actions.invoke({
+        name: `${actionName()}_wskdebug_invoked`,
+        params: args
+    });
+    return args.$activationId;
+}
+
+async function pollActivations(actionName, onActivation, onLoop) {
+    const wsk = openwhisk();
+
+    const since = Date.now();
+
+    while (true) {
+
+        let name = actionName;
+        if (activationListFilterOnlyBasename) {
+            if (actionName.includes("/")) {
+                name = actionName.substring(actionName.lastIndexOf("/") + 1);
+            }
+        }
+
+        const activations = await wsk.activations.list({
+            name: name,
+            since: since,
+            docs: true // include results
+        });
+
+        for (const a of activations) {
+            const result = onActivation(a);
+            if (result) {
+                return result;
+            }
+        }
+
+        await sleep(1000);
+
+        onLoop();
+    }
+}
+
+async function waitForCompletion(activationId) {
+    return pollActivations(
+        `${actionName()}_wskdebug_completed`,
+        a => {
+            // find the one with the $activationId we are waiting on
+            if (a.response && a.response.result &&
+                a.response.result.$activationId === activationId) {
+                const result = a.response.result;
+                delete result.$activationId;
+                return result;
+            }
+        },
+        () => {
+            if (outOfTime()) {
+                throw new Error(`Debugger did not complete activation within timeout.`);
+            }
+        }
+    );
+}
+
+// Note: this function is duplicated by all agents
+function hit(args, condition) {
+    if (condition) {
+        console.log("arguments:", args);
+        console.log("evaluating hit condition: ", condition);
+        // eslint-disable-next-line no-with
+        with (args) { // lgtm [js/with-statement]
+            try {
+                // eslint-disable-next-line no-eval
+                return eval(condition);
+            } catch (e) {
+                console.log("failed to eval condition:", e);
+                // be safe: do not hit if error in condition
+                return false;
+            }
+        }
+    } else {
+        // no condition => always hit
+        return true;
+    }
+}
+
+async function doMain(args) {
+    // normal activation: make activation available to debugger
+    console.log("activation");
+
+    if (hit(args, args.$condition)) {
+        console.log("passing on to debugger");
+        const id = await newActivation(args);
+        return waitForCompletion( id );
+
+    } else {
+        console.log("condition evaluated to false, executing original action");
+        return openwhisk().actions.invoke({
+            name: `${process.env.__OW_ACTION_NAME}_wskdebug_original`,
+            params: args,
+            blocking: true,
+            result: true
+        });
+    }
+}
+
+// OpenWhisk does not like raw exceptions, the error object should be the string message only
+async function main(args) {
+    try {
+        return await doMain(args);
+    } catch (e) {
+        console.log("Exception:", e);
+        return Promise.reject({ error: e.message, code: e.code});
+    }
+}
+
+module.export = main;
diff --git a/agent/agent-concurrency.js b/agent/agent-concurrency.js
new file mode 100644
index 0000000..2da1b2a
--- /dev/null
+++ b/agent/agent-concurrency.js
@@ -0,0 +1,144 @@
+/*
+ Copyright 2019 Adobe. All rights reserved.
+ This file is licensed to you 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 REPRESENTATIONS
+ OF ANY KIND, either express or implied. See the License for the specific language
+ governing permissions and limitations under the License.
+*/
+
+/* eslint-disable strict */
+
+// agent that forwards invocations to the developer's computer by leveraging a concurrent
+// nodejs action where one container will receive all activations and using queues
+// implemented as global variables of the action so that multiple invocations of this
+// action (agent) can see and wait for each other
+
+const openwhisk = require('openwhisk');
+const os = require("os");
+
+// shared across activations
+const activations = [];
+const completions = {};
+
+function checkTimeout(deadline) {
+    // stop 10 seconds before timeout, to have enough buffer
+    if (Date.now() >= ((deadline || process.env.__OW_DEADLINE) - 10*1000)) {
+        const e = new Error("No activation within timeout. Please retry.");
+        e.code = 42;
+        throw e;
+    }
+}
+
+async function sleep(millis) {
+    return new Promise(resolve => setTimeout(resolve, millis));
+}
+
+function newActivation(args) {
+    args.$activationId = process.env.__OW_ACTIVATION_ID;
+    activations.push(args);
+    return args.$activationId;
+}
+
+async function waitForActivation() {
+    // blocking invocations only wait for 1 minute, regardless of the action timeout
+    const oneMinuteDeadline = Date.now() + 60*1000;
+
+    while (activations.length === 0) {
+        await sleep(100);
+
+        checkTimeout(oneMinuteDeadline);
+    }
+
+    const activation = activations.shift();
+    console.log("activation id:", activation.$activationId);
+    return activation;
+}
+
+function complete(result) {
+    const id = result.$activationId;
+    completions[result.$activationId] = result;
+    delete result.$activationId;
+    return {
+        message: `completed activation ${id}`
+    };
+}
+
+async function waitForCompletion(activationId) {
+    while (!completions[activationId]) {
+        await sleep(100);
+    }
+    const result = completions[activationId];
+    delete completions[activationId];
+    return result;
+}
+
+// Note: this function is duplicated by all agents
+function hit(args, condition) {
+    if (condition) {
+        console.log("arguments:", args);
+        console.log("evaluating hit condition: ", condition);
+        // eslint-disable-next-line no-with
+        with (args) { // lgtm [js/with-statement]
+            try {
+                // eslint-disable-next-line no-eval
+                return eval(condition);
+            } catch (e) {
+                console.log("failed to eval condition:", e);
+                // be safe: do not hit if error in condition
+                return false;
+            }
+        }
+    } else {
+        // no condition => always hit
+        return true;
+    }
+}
+
+async function doMain(args) {
+    console.log("hostname:", os.hostname());
+
+    if (args.$waitForActivation) {
+        // debugger connects and waits for new activations
+        console.log("debugger connected, waiting for activation");
+        return waitForActivation();
+
+    } else if (args.$activationId) {
+        // debugger pushes result of completed activation
+        console.log("completing activation", args.$activationId);
+        return complete(args);
+
+    } else {
+        // normal activation: make activation available to debugger
+        console.log("activation");
+
+        if (hit(args, args.$condition)) {
+            console.log("passing on to debugger");
+            return waitForCompletion( newActivation(args) );
+
+        } else {
+            console.log("condition evaluated to false, executing original action");
+            return openwhisk().actions.invoke({
+                name: `${process.env.__OW_ACTION_NAME}_wskdebug_original`,
+                params: args,
+                blocking: true,
+                result: true
+            });
+        }
+    }
+}
+
+// OpenWhisk does not like raw exceptions, the error object should be the string message only
+async function main(args) {
+    try {
+        return await doMain(args);
+    } catch (e) {
+        console.log("Exception:", e);
+        return Promise.reject({ error: e.message, code: e.code});
+    }
+}
+
+module.export = main;
diff --git a/agent/agent-ngrok.js b/agent/agent-ngrok.js
new file mode 100644
index 0000000..0aac990
--- /dev/null
+++ b/agent/agent-ngrok.js
@@ -0,0 +1,107 @@
+/*
+ Copyright 2019 Adobe. All rights reserved.
+ This file is licensed to you 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 REPRESENTATIONS
+ OF ANY KIND, either express or implied. See the License for the specific language
+ governing permissions and limitations under the License.
+*/
+
+/* eslint-disable strict */
+
+// agent that forwards invocations to the developer's computer using ngrok.com
+
+const openwhisk = require('openwhisk');
+const https = require('https');
+
+// Note: this function is duplicated by all agents
+function hit(args, condition) {
+    if (condition) {
+        console.log("arguments:", args);
+        console.log("evaluating hit condition: ", condition);
+        // eslint-disable-next-line no-with
+        with (args) { // lgtm [js/with-statement]
+            try {
+                // eslint-disable-next-line no-eval
+                return eval(condition);
+            } catch (e) {
+                console.log("failed to eval condition:", e);
+                // be safe: do not hit if error in condition
+                return false;
+            }
+        }
+    } else {
+        // no condition => always hit
+        return true;
+    }
+}
+
+async function doMain(args) {
+    // normal activation: make activation available to debugger
+    console.log("activation");
+
+    if (hit(args, args.$condition) && args.$ngrokUrl) {
+        console.log("passing on to debugger");
+
+        console.log("post to ngrok", args.$ngrokUrl);
+        const options = {
+            hostname: args.$ngrokUrl,
+            port: 443,
+            path: '/',
+            method: 'POST',
+            headers: {
+                authorization: args.$ngrokAuth
+            }
+        };
+        return new Promise((resolve, reject) => {
+            const req = https.request(options, (resp) => {
+                console.log("response: ", resp.statusCode);
+                let body = '';
+
+                // A chunk of data has been recieved.
+                resp.on('data', (chunk) => {
+                    body += chunk;
+                });
+
+                // The whole response has been received. Print out the result.
+                resp.on('end', () => {
+                    resolve(JSON.parse(body));
+                });
+
+            });
+            req.on("error", err => {
+                console.error(err);
+                reject(err);
+            });
+            args.$activationId = process.env.__OW_ACTIVATION_ID;
+            delete args.$ngrokUrl;
+            delete args.$ngrokAuth;
+            req.write(JSON.stringify(args));
+            req.end();
+        });
+
+    } else {
+        console.log("condition evaluated to false (or $ngrokUrl missing), executing original action");
+        return openwhisk().actions.invoke({
+            name: `${process.env.__OW_ACTION_NAME}_wskdebug_original`,
+            params: args,
+            blocking: true,
+            result: true
+        });
+    }
+}
+
+// OpenWhisk does not like raw exceptions, the error object should be the string message only
+async function main(args) {
+    try {
+        return await doMain(args);
+    } catch (e) {
+        console.log("Exception:", e);
+        return Promise.reject({ error: e.message, code: e.code});
+    }
+}
+
+module.export = main;
diff --git a/agent/echo.js b/agent/echo.js
new file mode 100644
index 0000000..6effa26
--- /dev/null
+++ b/agent/echo.js
@@ -0,0 +1,20 @@
+/*
+ Copyright 2019 Adobe. All rights reserved.
+ This file is licensed to you 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 REPRESENTATIONS
+ OF ANY KIND, either express or implied. See the License for the specific language
+ governing permissions and limitations under the License.
+*/
+
+'use strict';
+
+// action that simply stores the arguments in the result
+// used for <func>_wskdebug_invoked and <func>_wskdebug_completed helper actions
+// of the agent that does not rely on concurrency (agent-activationdb.js)
+
+// eslint-disable-next-line no-unused-vars
+const main = args => args;
diff --git a/cli.js b/cli.js
new file mode 100755
index 0000000..330d209
--- /dev/null
+++ b/cli.js
@@ -0,0 +1,19 @@
+#!/usr/bin/env node
+
+/*
+ Copyright 2019 Adobe. All rights reserved.
+ This file is licensed to you 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 REPRESENTATIONS
+ OF ANY KIND, either express or implied. See the License for the specific language
+ governing permissions and limitations under the License.
+*/
+
+'use strict';
+
+const wskdebug = require('./index');
+
+wskdebug(process.argv.slice(2), true);
diff --git a/codecov.yml b/codecov.yml
new file mode 100644
index 0000000..b4e9d3f
--- /dev/null
+++ b/codecov.yml
@@ -0,0 +1,3 @@
+parsers:
+  javascript:
+    enable_partials: yes
diff --git a/examples/java/.gitignore b/examples/java/.gitignore
new file mode 100644
index 0000000..4d56530
--- /dev/null
+++ b/examples/java/.gitignore
@@ -0,0 +1,5 @@
+target
+wskdebug.log
+.classpath
+.project
+.settings
diff --git a/examples/java/Hello.java b/examples/java/Hello.java
new file mode 100644
index 0000000..26a7839
--- /dev/null
+++ b/examples/java/Hello.java
@@ -0,0 +1,12 @@
+import com.google.gson.JsonObject;
+
+public class Hello {
+    public static JsonObject main(JsonObject args) {
+        String name = "stranger";
+        if (args.has("name"))
+            name = args.getAsJsonPrimitive("name").getAsString();
+        JsonObject response = new JsonObject();
+        response.addProperty("greeting", "Hello " + name + "!");
+        return response;
+    }
+}
\ No newline at end of file
diff --git a/examples/java/debugParams.json b/examples/java/debugParams.json
new file mode 100644
index 0000000..077404a
--- /dev/null
+++ b/examples/java/debugParams.json
@@ -0,0 +1,3 @@
+{
+  
+}
\ No newline at end of file
diff --git a/examples/java/pom.xml b/examples/java/pom.xml
new file mode 100644
index 0000000..1c312ba
--- /dev/null
+++ b/examples/java/pom.xml
@@ -0,0 +1,42 @@
+<project xmlns="http://maven.apache.org/POM/4.0.0" 
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
+	http://maven.apache.org/maven-v4_0_0.xsd">
+	<modelVersion>4.0.0</modelVersion>
+
+	<groupId>wskdebug.examples</groupId>
+	<artifactId>hello-java</artifactId>
+	<version>1.0</version>
+	
+	<!-- Output to jar format -->
+	<packaging>jar</packaging>
+
+	<dependencies>
+        <dependency>
+          <groupId>com.google.code.gson</groupId>
+          <artifactId>gson</artifactId>
+          <version>2.8.5</version>
+        </dependency>
+    </dependencies>
+
+	<build>
+        <sourceDirectory>.</sourceDirectory>
+		<plugins>
+			<!-- Make this jar executable -->
+			<plugin>
+				<groupId>org.apache.maven.plugins</groupId>
+				<artifactId>maven-jar-plugin</artifactId>
+                <version>3.1.2</version>
+				<configuration>
+				  <archive>
+					<manifest>
+						<!-- Jar file entry point -->
+						<mainClass>Hello</mainClass>
+					</manifest>
+				  </archive>
+				</configuration>
+			</plugin>
+		</plugins>
+	</build>
+
+</project>
\ No newline at end of file
diff --git a/examples/nodejs/.vscode/launch.json b/examples/nodejs/.vscode/launch.json
new file mode 100644
index 0000000..aeb9f4e
--- /dev/null
+++ b/examples/nodejs/.vscode/launch.json
@@ -0,0 +1,55 @@
+{
+    // Use IntelliSense to learn about possible attributes.
+    // Hover to view descriptions of existing attributes.
+    // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
+    "version": "0.2.0",
+    "configurations": [
+        {
+            "type": "node",
+            "request": "launch",
+            "name": "wskdebug sample webaction",
+            "runtimeExecutable": "wskdebug",
+            "args": [
+                "wskdebug-examples/webaction",
+                "${workspaceFolder}/webaction.js",
+                "-l",
+                "-v"
+            ],
+            "localRoot": "${workspaceFolder}",
+            "remoteRoot": "/code",
+            "outputCapture": "std"
+        },
+        {
+            "type": "node",
+            "request": "launch",
+            "name": "wskdebug ngrok",
+            "runtimeExecutable": "wskdebug",
+            "args": [
+                "wskdebug-examples/webaction",
+                "${workspaceFolder}/webaction.js",
+                "-l",
+                "--ngrok"
+            ],
+            "localRoot": "${workspaceFolder}",
+            "remoteRoot": "/code",
+            "outputCapture": "std"
+        },
+        {
+            "type": "node",
+            "request": "launch",
+            "name": "wskdebug with build",
+            "runtimeExecutable": "wskdebug",
+            "args": [
+                "wskdebug-examples/webaction",
+                "${workspaceFolder}/webaction.js",
+                "-l",
+                "--on-build", "mkdir build/; cp webaction.js build/",
+                "--build-path", "build/webaction.js"
+            ],
+            "localRoot": "${workspaceFolder}",
+            "remoteRoot": "/code",
+            "outputCapture": "std"
+        }
+
+    ]
+}
\ No newline at end of file
diff --git a/examples/nodejs/deploy.sh b/examples/nodejs/deploy.sh
new file mode 100755
index 0000000..3f88c8e
--- /dev/null
+++ b/examples/nodejs/deploy.sh
@@ -0,0 +1,8 @@
+#!/bin/bash
+
+PKG=wskdebug-examples
+
+wsk package update $PKG
+wsk action update $PKG/webaction webaction.js --web true
+url=$(wsk action get $PKG/webaction --url | tail -n1)
+open -a "Google Chrome" $url
diff --git a/examples/nodejs/restore.sh b/examples/nodejs/restore.sh
new file mode 100755
index 0000000..5dcee74
--- /dev/null
+++ b/examples/nodejs/restore.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+PKG=wskdebug-examples
+
+wsk action list | grep wskdebug-examples
+
+wsk action delete $PKG/webaction
+wsk action create --copy $PKG/webaction $PKG/webaction_wskdebug_original
+wsk action delete $PKG/webaction_wskdebug_original
+
+wsk action list | grep wskdebug-examples
diff --git a/examples/nodejs/test.json b/examples/nodejs/test.json
new file mode 100644
index 0000000..3239a95
--- /dev/null
+++ b/examples/nodejs/test.json
@@ -0,0 +1,3 @@
+{
+  "hello": "from test.json"
+}
\ No newline at end of file
diff --git a/examples/nodejs/webaction.js b/examples/nodejs/webaction.js
new file mode 100644
index 0000000..6a006a3
--- /dev/null
+++ b/examples/nodejs/webaction.js
@@ -0,0 +1,41 @@
+/*
+ Copyright 2019 Adobe. All rights reserved.
+ This file is licensed to you 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 REPRESENTATIONS
+ OF ANY KIND, either express or implied. See the License for the specific language
+ governing permissions and limitations under the License.
+*/
+
+// Simple web action example
+// from https://github.com/apache/incubator-openwhisk/blob/master/docs/webactions.md
+
+'use strict';
+
+// eslint-disable-next-line no-unused-vars
+function main({name}) {
+    let msg = 'You did not tell me who you are.';
+    if (name) {
+        msg = `Hello ${name}!`;
+    } else {
+        name = "";
+    }
+    return {
+        body:
+        `<html>
+            <head>
+                <title>Hello!</title>
+            </head>
+            <body>
+                <h3>${msg}</h3>
+                <form>
+                <input name="name" value="${name}"/>
+                <input type="submit" value="Send" />
+                </form>
+            </body>
+        </html>`
+    };
+}
\ No newline at end of file
diff --git a/index.js b/index.js
new file mode 100755
index 0000000..8e78ea2
--- /dev/null
+++ b/index.js
@@ -0,0 +1,306 @@
+/*
+ Copyright 2019 Adobe. All rights reserved.
+ This file is licensed to you 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 REPRESENTATIONS
+ OF ANY KIND, either express or implied. See the License for the specific language
+ governing permissions and limitations under the License.
+*/
+
+'use strict';
+
+const yargs = require("yargs");
+const Debugger = require("./src/debugger");
+const path = require("path");
+const fs = require("fs");
+
+function enableConsoleColors() {
+    // colorful console.error() and co
+    let originalConsole = null;
+    if (!console._logToFile) {
+        originalConsole = {
+            log: console.log,
+            error: console.error,
+            info: console.info,
+            debug: console.debug
+        };
+        // overwrites console.log and co
+        require('manakin').global;
+    }
+    return originalConsole;
+}
+
+function resetConsoleColors(originalConsole) {
+    if (originalConsole) {
+        console.log = originalConsole.log;
+        console.error = originalConsole.error;
+        console.info = originalConsole.info;
+        console.debug = originalConsole.debug;
+    }
+}
+
+function getSupportedKinds() {
+    const kinds = [];
+    const basePath = path.resolve(__dirname, "src/kinds");
+    fs.readdirSync(basePath).forEach(function(entry) {
+        const p = path.resolve(basePath, entry);
+        if (fs.statSync(p).isDirectory()) {
+            const kind = require(path.resolve(p, entry));
+            kinds.push(`${entry}: ${kind.description}`);
+        }
+    });
+    return kinds;
+}
+
+function yargsOptions(yargs) {
+    yargs.positional('action', {
+        describe: 'Name of action to debug',
+        type: 'string'
+    });
+    yargs.positional('source-path', {
+        describe: 'Path to local action sources, file or folder (optional)',
+        type: 'string'
+    });
+
+    // action options
+    yargs.option("m", {
+        alias: "main",
+        type: "string",
+        group: "Action options:",
+        describe: "Name of action entry point"
+    });
+    yargs.option("k", {
+        alias: "kind",
+        type: "string",
+        group: "Action options:",
+        describe: "Action kind override, needed for blackbox images"
+    });
+    yargs.option("i", {
+        alias: "image",
+        type: "string",
+        group: "Action options:",
+        describe: "Docker image to use as action container"
+    });
+    yargs.option("on-build", {
+        type: "string",
+        group: "Action options:",
+        describe: "Shell command for custom action build step"
+    });
+    yargs.option("build-path", {
+        type: "string",
+        group: "Action options:",
+        describe: "Path to built action, result of --on-build command"
+    });
+
+    // source watching
+    yargs.option("l", {
+        type: "boolean",
+        implies: "source-path",
+        group: "LiveReload options:",
+        describe: "Enable browser LiveReload on [source-path]"
+    });
+    yargs.option("lr-port", {
+        type: "number",
+        implies: "l",
+        group: "LiveReload options:",
+        describe: "Port for browser LiveReload (defaults to 35729)"
+    });
+    yargs.option("P", {
+        type: "string",
+        group: "LiveReload options:",
+        describe: "Invoke action with these parameters on changes to [source-path].\nArgument can be json string or name of json file."
+    });
+    yargs.option("a", {
+        type: "string",
+        group: "LiveReload options:",
+        describe: "Name of custom action to invoke upon changes to [source-path].\nDefaults to <action> if -P is set."
+    });
+    yargs.option("r", {
+        type: "string",
+        group: "LiveReload options:",
+        describe: "Shell command to run upon changes to [source-path]"
+    });
+    yargs.option("watch", {
+        type: "string",
+        array: true,
+        group: "LiveReload options:",
+        describe: "Glob pattern(s) to watch for source modifications"
+    });
+    yargs.option("watch-exts", {
+        type: "string",
+        array: true,
+        group: "LiveReload options:",
+        describe: "File extensions to watch for modifications"
+    });
+
+    // Debugger options
+    yargs.option("p", {
+        alias: "port",
+        type: "number",
+        group: "Debugger options:",
+        describe: "Debug port exposed from container that debugging clients connect to. Defaults to --internal-port if set or standard debug port of the kind. Node.js arguments --inspect and co. can be used too."
+    });
+    yargs.option("internal-port", {
+        type: "number",
+        group: "Debugger options:",
+        describe: "Actual debug port inside the container. Must match port opened by --command. Defaults to standard debug port of kind."
+    });
+    yargs.option("command", {
+        type: "string",
+        group: "Debugger options:",
+        describe: "Custom container command that enables debugging"
+    });
+    yargs.option("docker-args", {
+        type: "string",
+        group: "Debugger options:",
+        describe: "Additional docker run arguments for container. Must be quoted and start with space: 'wskdebug --docker-args \" -e key=var\" myaction'"
+    });
+    yargs.option("on-start", {
+        type: "string",
+        group: "Debugger options:",
+        describe: "Shell command to run when debugger is up"
+    });
+
+    // Agent options
+    yargs.option("c", {
+        alias: "condition",
+        type: "string",
+        group: "Agent options:",
+        describe: "Hit condition to trigger debugger. Javascript expression evaluated against input parameters. Example: 'debug == 'true'"
+    });
+    yargs.option("agent-timeout", {
+        type: "number",
+        group: "Agent options:",
+        describe: "Debugging agent timeout (seconds). Default: 5 min"
+    });
+    yargs.option("ngrok", {
+        type: "boolean",
+        group: "Agent options:",
+        describe: "Use ngrok.com for agent forwarding."
+    });
+    yargs.option("ngrok-region", {
+        type: "string",
+        group: "Agent options:",
+        describe: "Ngrok region to use. Defaults to 'us'."
+    });
+
+    // nodejs options
+    yargs.option("inspect", {
+        alias: ["inspect-brk", "inspect-port", "debug", "debug-brk", "debug-port"],
+        hidden: true,
+        type: "number"
+    });
+
+    // general options
+    yargs.option("v", {
+        alias: "verbose",
+        type: "boolean",
+        describe: "Verbose output. Logs activation parameters and result"
+    });
+    yargs.version(require("./package.json").version);
+}
+
+function getYargsParser() {
+    return yargs
+        .help()
+        .alias("h", "help")
+        .updateStrings({
+            'Positionals:': 'Arguments:',
+            'Not enough non-option arguments: got %s, need at least %s': "Error: Missing argument <action> (%s/%s)"
+        })
+        .version(false)
+        .wrap(90)
+        .command(
+            "* <action> [source-path]",
+            `Debug an OpenWhisk <action> by forwarding its activations to a local docker container that
+            has debugging enabled and its debug port exposed to the host.
+
+            If only <action> is specified, the deployed action code is debugged.
+
+            If [source-path] is set, it must point to the local action sources which will be mounted
+            into the debug container. Sources will be automatically reloaded on each new activation.
+            This feature depends on the kind.
+
+            Supported kinds:
+            - ${getSupportedKinds().join("\n")}
+            `,
+            yargsOptions
+        );
+}
+
+function normalizeArgs(argv) {
+    // pass hidden node.js arg aliases to port option
+    argv.port = argv.inspect || argv.p;
+    // more readable internal argument names
+    argv.livereload = argv.l;
+    argv.livereloadPort = argv.lrPort;
+    argv.invokeParams = argv.P;
+    argv.invokeAction = argv.a;
+    argv.onChange = argv.r;
+}
+
+function printErrorAndExit(err, argv) {
+    console.log();
+    if (argv.verbose) {
+        console.error(err);
+    } else {
+        console.error("Error:", err.message);
+    }
+    process.exit(1);
+}
+
+function registerExitHandler(dbg) {
+    // ensure we remove the agent when this app gets terminated
+    ['SIGINT', 'SIGTERM'].forEach(signal => {
+        process.on(signal, async () => {
+            await dbg.kill();
+
+            process.exit();
+        });
+    });
+}
+
+async function wskdebug(args, isCommandLine=false) {
+    const originalConsole = enableConsoleColors();
+
+    try {
+        const parser = getYargsParser();
+
+        // if cli mode, we want to exit the process, otherwise throw an error
+        parser.showHelpOnFail(isCommandLine);
+        parser.exitProcess(isCommandLine);
+
+        const argv = parser.parse(args);
+        normalizeArgs(argv);
+
+        if (argv.help || argv.version) {
+            // do nothing
+            return;
+        }
+
+        try {
+            const dbg = new Debugger(argv);
+            if (isCommandLine) {
+                registerExitHandler(dbg);
+            }
+            await dbg.start();
+            await dbg.run();
+
+        } catch (e) {
+            if (isCommandLine) {
+                printErrorAndExit(e, argv);
+            } else {
+                throw e;
+            }
+        }
+
+    } finally {
+        resetConsoleColors(originalConsole);
+    }
+}
+
+// exporting the resulting promise for unit tests
+module.exports = wskdebug;
\ No newline at end of file
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..ff8cff8
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,3223 @@
+{
+    "name": "@adobe/wskdebug",
+    "version": "1.1.2",
+    "lockfileVersion": 1,
+    "requires": true,
+    "dependencies": {
+        "@babel/code-frame": {
+            "version": "7.5.5",
+            "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.5.5.tgz",
+            "integrity": "sha512-27d4lZoomVyo51VegxI20xZPuSHusqbQag/ztrBC7wegWoQ1nLREPVSKSW8byhTlzTKyNE4ifaTA6lCp7JjpFw==",
+            "dev": true,
+            "requires": {
+                "@babel/highlight": "^7.0.0"
+            }
+        },
+        "@babel/generator": {
+            "version": "7.7.2",
+            "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.7.2.tgz",
+            "integrity": "sha512-WthSArvAjYLz4TcbKOi88me+KmDJdKSlfwwN8CnUYn9jBkzhq0ZEPuBfkAWIvjJ3AdEV1Cf/+eSQTnp3IDJKlQ==",
+            "dev": true,
+            "requires": {
+                "@babel/types": "^7.7.2",
+                "jsesc": "^2.5.1",
+                "lodash": "^4.17.13",
+                "source-map": "^0.5.0"
+            }
+        },
+        "@babel/helper-function-name": {
+            "version": "7.7.0",
+            "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.7.0.tgz",
+            "integrity": "sha512-tDsJgMUAP00Ugv8O2aGEua5I2apkaQO7lBGUq1ocwN3G23JE5Dcq0uh3GvFTChPa4b40AWiAsLvCZOA2rdnQ7Q==",
+            "dev": true,
+            "requires": {
+                "@babel/helper-get-function-arity": "^7.7.0",
+                "@babel/template": "^7.7.0",
+                "@babel/types": "^7.7.0"
+            }
+        },
+        "@babel/helper-get-function-arity": {
+            "version": "7.7.0",
+            "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.7.0.tgz",
+            "integrity": "sha512-tLdojOTz4vWcEnHWHCuPN5P85JLZWbm5Fx5ZsMEMPhF3Uoe3O7awrbM2nQ04bDOUToH/2tH/ezKEOR8zEYzqyw==",
+            "dev": true,
+            "requires": {
+                "@babel/types": "^7.7.0"
+            }
+        },
+        "@babel/helper-split-export-declaration": {
+            "version": "7.7.0",
+            "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.7.0.tgz",
+            "integrity": "sha512-HgYSI8rH08neWlAH3CcdkFg9qX9YsZysZI5GD8LjhQib/mM0jGOZOVkoUiiV2Hu978fRtjtsGsW6w0pKHUWtqA==",
+            "dev": true,
+            "requires": {
+                "@babel/types": "^7.7.0"
+            }
+        },
+        "@babel/highlight": {
+            "version": "7.5.0",
+            "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.5.0.tgz",
+            "integrity": "sha512-7dV4eu9gBxoM0dAnj/BCFDW9LFU0zvTrkq0ugM7pnHEgguOEeOz1so2ZghEdzviYzQEED0r4EAgpsBChKy1TRQ==",
+            "dev": true,
+            "requires": {
+                "chalk": "^2.0.0",
+                "esutils": "^2.0.2",
+                "js-tokens": "^4.0.0"
+            }
+        },
+        "@babel/parser": {
+            "version": "7.7.3",
+            "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.7.3.tgz",
+            "integrity": "sha512-bqv+iCo9i+uLVbI0ILzKkvMorqxouI+GbV13ivcARXn9NNEabi2IEz912IgNpT/60BNXac5dgcfjb94NjsF33A==",
+            "dev": true
+        },
+        "@babel/template": {
+            "version": "7.7.0",
+            "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.7.0.tgz",
+            "integrity": "sha512-OKcwSYOW1mhWbnTBgQY5lvg1Fxg+VyfQGjcBduZFljfc044J5iDlnDSfhQ867O17XHiSCxYHUxHg2b7ryitbUQ==",
+            "dev": true,
+            "requires": {
+                "@babel/code-frame": "^7.0.0",
+                "@babel/parser": "^7.7.0",
+                "@babel/types": "^7.7.0"
+            }
+        },
+        "@babel/traverse": {
+            "version": "7.7.2",
+            "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.7.2.tgz",
+            "integrity": "sha512-TM01cXib2+rgIZrGJOLaHV/iZUAxf4A0dt5auY6KNZ+cm6aschuJGqKJM3ROTt3raPUdIDk9siAufIFEleRwtw==",
+            "dev": true,
+            "requires": {
+                "@babel/code-frame": "^7.5.5",
+                "@babel/generator": "^7.7.2",
+                "@babel/helper-function-name": "^7.7.0",
+                "@babel/helper-split-export-declaration": "^7.7.0",
+                "@babel/parser": "^7.7.2",
+                "@babel/types": "^7.7.2",
+                "debug": "^4.1.0",
+                "globals": "^11.1.0",
+                "lodash": "^4.17.13"
+            },
+            "dependencies": {
+                "debug": {
+                    "version": "4.1.1",
+                    "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+                    "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+                    "dev": true,
+                    "requires": {
+                        "ms": "^2.1.1"
+                    }
+                },
+                "ms": {
+                    "version": "2.1.2",
+                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+                    "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+                    "dev": true
+                }
+            }
+        },
+        "@babel/types": {
+            "version": "7.7.2",
+            "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.7.2.tgz",
+            "integrity": "sha512-YTf6PXoh3+eZgRCBzzP25Bugd2ngmpQVrk7kXX0i5N9BO7TFBtIgZYs7WtxtOGs8e6A4ZI7ECkbBCEHeXocvOA==",
+            "dev": true,
+            "requires": {
+                "esutils": "^2.0.2",
+                "lodash": "^4.17.13",
+                "to-fast-properties": "^2.0.0"
+            }
+        },
+        "@types/caseless": {
+            "version": "0.12.2",
+            "resolved": "https://registry.npmjs.org/@types/caseless/-/caseless-0.12.2.tgz",
+            "integrity": "sha512-6ckxMjBBD8URvjB6J3NcnuAn5Pkl7t3TizAg+xdlzzQGSPSmBcXf8KoIH0ua/i+tio+ZRUHEXp0HEmvaR4kt0w=="
+        },
+        "@types/node": {
+            "version": "8.10.59",
+            "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.59.tgz",
+            "integrity": "sha512-8RkBivJrDCyPpBXhVZcjh7cQxVBSmRk9QM7hOketZzp6Tg79c0N8kkpAIito9bnJ3HCVCHVYz+KHTEbfQNfeVQ=="
+        },
+        "@types/request": {
+            "version": "2.48.3",
+            "resolved": "https://registry.npmjs.org/@types/request/-/request-2.48.3.tgz",
+            "integrity": "sha512-3Wo2jNYwqgXcIz/rrq18AdOZUQB8cQ34CXZo+LUwPJNpvRAL86+Kc2wwI8mqpz9Cr1V+enIox5v+WZhy/p3h8w==",
+            "requires": {
+                "@types/caseless": "*",
+                "@types/node": "*",
+                "@types/tough-cookie": "*",
+                "form-data": "^2.5.0"
+            }
+        },
+        "@types/tough-cookie": {
+            "version": "2.3.5",
+            "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-2.3.5.tgz",
+            "integrity": "sha512-SCcK7mvGi3+ZNz833RRjFIxrn4gI1PPR3NtuIS+6vMkvmsGjosqTJwRt5bAEFLRz+wtJMWv8+uOnZf2hi2QXTg=="
+        },
+        "abbrev": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+            "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q=="
+        },
+        "acorn": {
+            "version": "6.3.0",
+            "resolved": "https://registry.npmjs.org/acorn/-/acorn-6.3.0.tgz",
+            "integrity": "sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA==",
+            "dev": true
+        },
+        "acorn-jsx": {
+            "version": "5.1.0",
+            "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.1.0.tgz",
+            "integrity": "sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw==",
+            "dev": true
+        },
+        "agent-base": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-4.3.0.tgz",
+            "integrity": "sha512-salcGninV0nPrwpGNn4VTXBb1SOuXQBiqbrNXoeizJsHrsL6ERFM2Ne3JUSBWRE6aeNJI2ROP/WEEIDUiDe3cg==",
+            "dev": true,
+            "requires": {
+                "es6-promisify": "^5.0.0"
+            }
+        },
+        "ajv": {
+            "version": "6.10.2",
+            "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.10.2.tgz",
+            "integrity": "sha512-TXtUUEYHuaTEbLZWIKUr5pmBuhDLy+8KYtPYdcV8qC+pOZL+NKqYwvWSRrVXHn+ZmRRAu8vJTAznH7Oag6RVRw==",
+            "requires": {
+                "fast-deep-equal": "^2.0.1",
+                "fast-json-stable-stringify": "^2.0.0",
+                "json-schema-traverse": "^0.4.1",
+                "uri-js": "^4.2.2"
+            }
+        },
+        "ansi-colors": {
+            "version": "3.2.3",
+            "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz",
+            "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==",
+            "dev": true
+        },
+        "ansi-escapes": {
+            "version": "3.2.0",
+            "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-3.2.0.tgz",
+            "integrity": "sha512-cBhpre4ma+U0T1oM5fXg7Dy1Jw7zzwv7lt/GoCpr+hDQJoYnKVPLL4dCvSEFMmQurOQvSrwT7SL/DAlhBI97RQ==",
+            "dev": true
+        },
+        "ansi-regex": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
+            "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg==",
+            "dev": true
+        },
+        "ansi-styles": {
+            "version": "3.2.1",
+            "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+            "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+            "requires": {
+                "color-convert": "^1.9.0"
+            }
+        },
+        "anymatch": {
+            "version": "3.1.1",
+            "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.1.tgz",
+            "integrity": "sha512-mM8522psRCqzV+6LhomX5wgp25YVibjh8Wj23I5RPkPppSVSjyKD2A2mBJmWGa+KN7f2D6LNh9jkBCeyLktzjg==",
+            "requires": {
+                "normalize-path": "^3.0.0",
+                "picomatch": "^2.0.4"
+            }
+        },
+        "append-transform": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-1.0.0.tgz",
+            "integrity": "sha512-P009oYkeHyU742iSZJzZZywj4QRJdnTWffaKuJQLablCZ1uz6/cW4yaRgcDaoQ+uwOxxnt0gRUcwfsNP2ri0gw==",
+            "dev": true,
+            "requires": {
+                "default-require-extensions": "^2.0.0"
+            }
+        },
+        "archy": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz",
+            "integrity": "sha1-+cjBN1fMHde8N5rHeyxipcKGjEA=",
+            "dev": true
+        },
+        "argparse": {
+            "version": "1.0.10",
+            "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+            "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+            "dev": true,
+            "requires": {
+                "sprintf-js": "~1.0.2"
+            }
+        },
+        "argv": {
+            "version": "0.0.2",
+            "resolved": "https://registry.npmjs.org/argv/-/argv-0.0.2.tgz",
+            "integrity": "sha1-7L0W+JSbFXGDcRsb2jNPN4QBhas=",
+            "dev": true
+        },
+        "asn1": {
+            "version": "0.2.4",
+            "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.4.tgz",
+            "integrity": "sha512-jxwzQpLQjSmWXgwaCZE9Nz+glAG01yF1QnWgbhGwHI5A6FRIEY6IVqtHhIepHqI7/kyEyQEagBC5mBEFlIYvdg==",
+            "requires": {
+                "safer-buffer": "~2.1.0"
+            }
+        },
+        "assert-plus": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
+            "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
+        },
+        "assertion-error": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
+            "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
+            "dev": true
+        },
+        "astral-regex": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-1.0.0.tgz",
+            "integrity": "sha512-+Ryf6g3BKoRc7jfp7ad8tM4TtMiaWvbF/1/sQcZPkkS7ag3D5nMBCe2UfOTONtAkaG0tO0ij3C5Lwmf1EiyjHg==",
+            "dev": true
+        },
+        "async-limiter": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz",
+            "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ=="
+        },
+        "asynckit": {
+            "version": "0.4.0",
+            "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+            "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k="
+        },
+        "aws-sign2": {
+            "version": "0.7.0",
+            "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz",
+            "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg="
+        },
+        "aws4": {
+            "version": "1.8.0",
+            "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.8.0.tgz",
+            "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ=="
+        },
+        "balanced-match": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
+            "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
+            "dev": true
+        },
+        "bcrypt-pbkdf": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz",
+            "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=",
+            "requires": {
+                "tweetnacl": "^0.14.3"
+            }
+        },
+        "binary": {
+            "version": "0.3.0",
+            "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
+            "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=",
+            "requires": {
+                "buffers": "~0.1.1",
+                "chainsaw": "~0.1.0"
+            }
+        },
+        "binary-extensions": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.0.0.tgz",
+            "integrity": "sha512-Phlt0plgpIIBOGTT/ehfFnbNlfsDEiqmzE2KRXoX1bLIlir4X/MR+zSyBEkL05ffWgnRSf/DXv+WrUAVr93/ow=="
+        },
+        "brace-expansion": {
+            "version": "1.1.11",
+            "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+            "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+            "dev": true,
+            "requires": {
+                "balanced-match": "^1.0.0",
+                "concat-map": "0.0.1"
+            }
+        },
+        "braces": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+            "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+            "requires": {
+                "fill-range": "^7.0.1"
+            }
+        },
+        "browser-stdout": {
+            "version": "1.3.1",
+            "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+            "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+            "dev": true
+        },
+        "buffers": {
+            "version": "0.1.1",
+            "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
+            "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s="
+        },
+        "caching-transform": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-3.0.2.tgz",
+            "integrity": "sha512-Mtgcv3lh3U0zRii/6qVgQODdPA4G3zhG+jtbCWj39RXuUFTMzH0vcdMtaJS1jPowd+It2Pqr6y3NJMQqOqCE2w==",
+            "dev": true,
+            "requires": {
+                "hasha": "^3.0.0",
+                "make-dir": "^2.0.0",
+                "package-hash": "^3.0.0",
+                "write-file-atomic": "^2.4.2"
+            }
+        },
+        "callsites": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+            "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+            "dev": true
+        },
+        "camelcase": {
+            "version": "5.3.1",
+            "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+            "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
+        },
+        "caseless": {
+            "version": "0.12.0",
+            "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
+            "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
+        },
+        "chai": {
+            "version": "4.2.0",
+            "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
+            "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
+            "dev": true,
+            "requires": {
+                "assertion-error": "^1.1.0",
+                "check-error": "^1.0.2",
+                "deep-eql": "^3.0.1",
+                "get-func-name": "^2.0.0",
+                "pathval": "^1.1.0",
+                "type-detect": "^4.0.5"
+            }
+        },
+        "chainsaw": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
+            "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=",
+            "requires": {
+                "traverse": ">=0.3.0 <0.4"
+            }
+        },
+        "chalk": {
+            "version": "2.4.2",
+            "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+            "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+            "dev": true,
+            "requires": {
+                "ansi-styles": "^3.2.1",
+                "escape-string-regexp": "^1.0.5",
+                "supports-color": "^5.3.0"
+            }
+        },
+        "chardet": {
+            "version": "0.7.0",
+            "resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
+            "integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==",
+            "dev": true
+        },
+        "check-error": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
+            "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
+            "dev": true
+        },
+        "chokidar": {
+            "version": "3.3.1",
+            "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.3.1.tgz",
+            "integrity": "sha512-4QYCEWOcK3OJrxwvyyAOxFuhpvOVCYkr33LPfFNBjAD/w3sEzWsp2BUOkI4l9bHvWioAd0rc6NlHUOEaWkTeqg==",
+            "requires": {
+                "anymatch": "~3.1.1",
+                "braces": "~3.0.2",
+                "fsevents": "~2.1.2",
+                "glob-parent": "~5.1.0",
+                "is-binary-path": "~2.1.0",
+                "is-glob": "~4.0.1",
+                "normalize-path": "~3.0.0",
+                "readdirp": "~3.3.0"
+            }
+        },
+        "cli-cursor": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-2.1.0.tgz",
+            "integrity": "sha1-s12sN2R5+sw+lHR9QdDQ9SOP/LU=",
+            "dev": true,
+            "requires": {
+                "restore-cursor": "^2.0.0"
+            }
+        },
+        "cli-width": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.0.tgz",
+            "integrity": "sha1-/xnt6Kml5XkyQUewwR8PvLq+1jk=",
+            "dev": true
+        },
+        "cliui": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
+            "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
+            "requires": {
+                "string-width": "^3.1.0",
+                "strip-ansi": "^5.2.0",
+                "wrap-ansi": "^5.1.0"
+            },
+            "dependencies": {
+                "ansi-regex": {
+                    "version": "4.1.0",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+                    "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
+                },
+                "strip-ansi": {
+                    "version": "5.2.0",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+                    "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+                    "requires": {
+                        "ansi-regex": "^4.1.0"
+                    }
+                }
+            }
+        },
+        "clone": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz",
+            "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=",
+            "dev": true
+        },
+        "codecov": {
+            "version": "3.6.1",
+            "resolved": "https://registry.npmjs.org/codecov/-/codecov-3.6.1.tgz",
+            "integrity": "sha512-IUJB6WG47nWK7o50etF8jBadxdMw7DmoQg05yIljstXFBGB6clOZsIj6iD4P82T2YaIU3qq+FFu8K9pxgkCJDQ==",
+            "dev": true,
+            "requires": {
+                "argv": "^0.0.2",
+                "ignore-walk": "^3.0.1",
+                "js-yaml": "^3.13.1",
+                "teeny-request": "^3.11.3",
+                "urlgrey": "^0.4.4"
+            }
+        },
+        "color-convert": {
+            "version": "1.9.3",
+            "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+            "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+            "requires": {
+                "color-name": "1.1.3"
+            }
+        },
+        "color-name": {
+            "version": "1.1.3",
+            "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+            "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU="
+        },
+        "combined-stream": {
+            "version": "1.0.8",
+            "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+            "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+            "requires": {
+                "delayed-stream": "~1.0.0"
+            }
+        },
+        "commander": {
+            "version": "2.20.3",
+            "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+            "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+            "dev": true,
+            "optional": true
+        },
+        "commondir": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz",
+            "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=",
+            "dev": true
+        },
+        "concat-map": {
+            "version": "0.0.1",
+            "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+            "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
+            "dev": true
+        },
+        "convert-source-map": {
+            "version": "1.7.0",
+            "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz",
+            "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==",
+            "dev": true,
+            "requires": {
+                "safe-buffer": "~5.1.1"
+            }
+        },
+        "core-util-is": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
+            "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
+        },
+        "cp-file": {
+            "version": "6.2.0",
+            "resolved": "https://registry.npmjs.org/cp-file/-/cp-file-6.2.0.tgz",
+            "integrity": "sha512-fmvV4caBnofhPe8kOcitBwSn2f39QLjnAnGq3gO9dfd75mUytzKNZB1hde6QHunW2Rt+OwuBOMc3i1tNElbszA==",
+            "dev": true,
+            "requires": {
+                "graceful-fs": "^4.1.2",
+                "make-dir": "^2.0.0",
+                "nested-error-stacks": "^2.0.0",
+                "pify": "^4.0.1",
+                "safe-buffer": "^5.0.1"
+            }
+        },
+        "cross-spawn": {
+            "version": "6.0.5",
+            "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
+            "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
+            "dev": true,
+            "requires": {
+                "nice-try": "^1.0.4",
+                "path-key": "^2.0.1",
+                "semver": "^5.5.0",
+                "shebang-command": "^1.2.0",
+                "which": "^1.2.9"
+            }
+        },
+        "dashdash": {
+            "version": "1.14.1",
+            "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz",
+            "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=",
+            "requires": {
+                "assert-plus": "^1.0.0"
+            }
+        },
+        "decamelize": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+            "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
+        },
+        "decompress-zip": {
+            "version": "0.3.2",
+            "resolved": "https://registry.npmjs.org/decompress-zip/-/decompress-zip-0.3.2.tgz",
+            "integrity": "sha512-Ab1QY4LrWMrUuo53lLnmGOby7v8ryqxJ+bKibKSiPisx+25mhut1dScVBXAYx14i/PqSrFZvR2FRRazhLbvL+g==",
+            "requires": {
+                "binary": "^0.3.0",
+                "graceful-fs": "^4.1.3",
+                "mkpath": "^0.1.0",
+                "nopt": "^3.0.1",
+                "q": "^1.1.2",
+                "readable-stream": "^1.1.8",
+                "touch": "0.0.3"
+            },
+            "dependencies": {
+                "isarray": {
+                    "version": "0.0.1",
+                    "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
+                    "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
+                },
+                "readable-stream": {
+                    "version": "1.1.14",
+                    "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.1.14.tgz",
+                    "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=",
+                    "requires": {
+                        "core-util-is": "~1.0.0",
+                        "inherits": "~2.0.1",
+                        "isarray": "0.0.1",
+                        "string_decoder": "~0.10.x"
+                    }
+                },
+                "string_decoder": {
+                    "version": "0.10.31",
+                    "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
+                    "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
+                }
+            }
+        },
+        "deep-eql": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
+            "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
+            "dev": true,
+            "requires": {
+                "type-detect": "^4.0.0"
+            }
+        },
+        "deep-is": {
+            "version": "0.1.3",
+            "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz",
+            "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=",
+            "dev": true
+        },
+        "default-require-extensions": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-2.0.0.tgz",
+            "integrity": "sha1-9fj7sYp9bVCyH2QfZJ67Uiz+JPc=",
+            "dev": true,
+            "requires": {
+                "strip-bom": "^3.0.0"
+            }
+        },
+        "define-properties": {
+            "version": "1.1.3",
+            "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
+            "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
+            "dev": true,
+            "requires": {
+                "object-keys": "^1.0.12"
+            }
+        },
+        "delayed-stream": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+            "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk="
+        },
+        "diff": {
+            "version": "3.5.0",
+            "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
+            "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
+            "dev": true
+        },
+        "doctrine": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+            "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+            "dev": true,
+            "requires": {
+                "esutils": "^2.0.2"
+            }
+        },
+        "ecc-jsbn": {
+            "version": "0.1.2",
+            "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz",
+            "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=",
+            "requires": {
+                "jsbn": "~0.1.0",
+                "safer-buffer": "^2.1.0"
+            }
+        },
+        "emoji-regex": {
+            "version": "7.0.3",
+            "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
+            "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
+        },
+        "encoding": {
+            "version": "0.1.12",
+            "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.12.tgz",
+            "integrity": "sha1-U4tm8+5izRq1HsMjgp0flIDHS+s=",
+            "requires": {
+                "iconv-lite": "~0.4.13"
+            }
+        },
+        "error-ex": {
+            "version": "1.3.2",
+            "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+            "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+            "dev": true,
+            "requires": {
+                "is-arrayish": "^0.2.1"
+            }
+        },
+        "es-abstract": {
+            "version": "1.16.0",
+            "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.16.0.tgz",
+            "integrity": "sha512-xdQnfykZ9JMEiasTAJZJdMWCQ1Vm00NBw79/AWi7ELfZuuPCSOMDZbT9mkOfSctVtfhb+sAAzrm+j//GjjLHLg==",
+            "dev": true,
+            "requires": {
+                "es-to-primitive": "^1.2.0",
+                "function-bind": "^1.1.1",
+                "has": "^1.0.3",
+                "has-symbols": "^1.0.0",
+                "is-callable": "^1.1.4",
+                "is-regex": "^1.0.4",
+                "object-inspect": "^1.6.0",
+                "object-keys": "^1.1.1",
+                "string.prototype.trimleft": "^2.1.0",
+                "string.prototype.trimright": "^2.1.0"
+            }
+        },
+        "es-to-primitive": {
+            "version": "1.2.1",
+            "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+            "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+            "dev": true,
+            "requires": {
+                "is-callable": "^1.1.4",
+                "is-date-object": "^1.0.1",
+                "is-symbol": "^1.0.2"
+            }
+        },
+        "es6-error": {
+            "version": "4.1.1",
+            "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz",
+            "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==",
+            "dev": true
+        },
+        "es6-promise": {
+            "version": "4.2.8",
+            "resolved": "https://registry.npmjs.org/es6-promise/-/es6-promise-4.2.8.tgz",
+            "integrity": "sha512-HJDGx5daxeIvxdBxvG2cb9g4tEvwIk3i8+nhX0yGrYmZUzbkdg8QbDevheDB8gd0//uPj4c1EQua8Q+MViT0/w=="
+        },
+        "es6-promisify": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/es6-promisify/-/es6-promisify-5.0.0.tgz",
+            "integrity": "sha1-UQnWLz5W6pZ8S2NQWu8IKRyKUgM=",
+            "dev": true,
+            "requires": {
+                "es6-promise": "^4.0.3"
+            }
+        },
+        "escape-string-regexp": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+            "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
+            "dev": true
+        },
+        "eslint": {
+            "version": "5.16.0",
+            "resolved": "https://registry.npmjs.org/eslint/-/eslint-5.16.0.tgz",
+            "integrity": "sha512-S3Rz11i7c8AA5JPv7xAH+dOyq/Cu/VXHiHXBPOU1k/JAM5dXqQPt3qcrhpHSorXmrpu2g0gkIBVXAqCpzfoZIg==",
+            "dev": true,
+            "requires": {
+                "@babel/code-frame": "^7.0.0",
+                "ajv": "^6.9.1",
+                "chalk": "^2.1.0",
+                "cross-spawn": "^6.0.5",
+                "debug": "^4.0.1",
+                "doctrine": "^3.0.0",
+                "eslint-scope": "^4.0.3",
+                "eslint-utils": "^1.3.1",
+                "eslint-visitor-keys": "^1.0.0",
+                "espree": "^5.0.1",
+                "esquery": "^1.0.1",
+                "esutils": "^2.0.2",
+                "file-entry-cache": "^5.0.1",
+                "functional-red-black-tree": "^1.0.1",
+                "glob": "^7.1.2",
+                "globals": "^11.7.0",
+                "ignore": "^4.0.6",
+                "import-fresh": "^3.0.0",
+                "imurmurhash": "^0.1.4",
+                "inquirer": "^6.2.2",
+                "js-yaml": "^3.13.0",
+                "json-stable-stringify-without-jsonify": "^1.0.1",
+                "levn": "^0.3.0",
+                "lodash": "^4.17.11",
+                "minimatch": "^3.0.4",
+                "mkdirp": "^0.5.1",
+                "natural-compare": "^1.4.0",
+                "optionator": "^0.8.2",
+                "path-is-inside": "^1.0.2",
+                "progress": "^2.0.0",
+                "regexpp": "^2.0.1",
+                "semver": "^5.5.1",
+                "strip-ansi": "^4.0.0",
+                "strip-json-comments": "^2.0.1",
+                "table": "^5.2.3",
+                "text-table": "^0.2.0"
+            },
+            "dependencies": {
+                "ansi-regex": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+                    "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+                    "dev": true
+                },
+                "debug": {
+                    "version": "4.1.1",
+                    "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+                    "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+                    "dev": true,
+                    "requires": {
+                        "ms": "^2.1.1"
+                    }
+                },
+                "ms": {
+                    "version": "2.1.2",
+                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+                    "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+                    "dev": true
+                },
+                "strip-ansi": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+                    "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+                    "dev": true,
+                    "requires": {
+                        "ansi-regex": "^3.0.0"
+                    }
+                }
+            }
+        },
+        "eslint-config-problems": {
+            "version": "3.0.1",
+            "resolved": "https://registry.npmjs.org/eslint-config-problems/-/eslint-config-problems-3.0.1.tgz",
+            "integrity": "sha512-MHEGRZW7a6aGmN1/0wI7nebCexwgnpz9esibWp2zq3rwOjNNdnTCXhqGvMCo5wSsCH8OZ0FJH0JGjxffbkDQEA==",
+            "dev": true
+        },
+        "eslint-plugin-mocha": {
+            "version": "5.3.0",
+            "resolved": "https://registry.npmjs.org/eslint-plugin-mocha/-/eslint-plugin-mocha-5.3.0.tgz",
+            "integrity": "sha512-3uwlJVLijjEmBeNyH60nzqgA1gacUWLUmcKV8PIGNvj1kwP/CTgAWQHn2ayyJVwziX+KETkr9opNwT1qD/RZ5A==",
+            "dev": true,
+            "requires": {
+                "ramda": "^0.26.1"
+            }
+        },
+        "eslint-scope": {
+            "version": "4.0.3",
+            "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-4.0.3.tgz",
+            "integrity": "sha512-p7VutNr1O/QrxysMo3E45FjYDTeXBy0iTltPFNSqKAIfjDSXC+4dj+qfyuD8bfAXrW/y6lW3O76VaYNPKfpKrg==",
+            "dev": true,
+            "requires": {
+                "esrecurse": "^4.1.0",
+                "estraverse": "^4.1.1"
+            }
+        },
+        "eslint-utils": {
+            "version": "1.4.3",
+            "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-1.4.3.tgz",
+            "integrity": "sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==",
+            "dev": true,
+            "requires": {
+                "eslint-visitor-keys": "^1.1.0"
+            }
+        },
+        "eslint-visitor-keys": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.1.0.tgz",
+            "integrity": "sha512-8y9YjtM1JBJU/A9Kc+SbaOV4y29sSWckBwMHa+FGtVj5gN/sbnKDf6xJUl+8g7FAij9LVaP8C24DUiH/f/2Z9A==",
+            "dev": true
+        },
+        "espree": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/espree/-/espree-5.0.1.tgz",
+            "integrity": "sha512-qWAZcWh4XE/RwzLJejfcofscgMc9CamR6Tn1+XRXNzrvUSSbiAjGOI/fggztjIi7y9VLPqnICMIPiGyr8JaZ0A==",
+            "dev": true,
+            "requires": {
+                "acorn": "^6.0.7",
+                "acorn-jsx": "^5.0.0",
+                "eslint-visitor-keys": "^1.0.0"
+            }
+        },
+        "esprima": {
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+            "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+            "dev": true
+        },
+        "esquery": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.0.1.tgz",
+            "integrity": "sha512-SmiyZ5zIWH9VM+SRUReLS5Q8a7GxtRdxEBVZpm98rJM7Sb+A9DVCndXfkeFUd3byderg+EbDkfnevfCwynWaNA==",
+            "dev": true,
+            "requires": {
+                "estraverse": "^4.0.0"
+            }
+        },
+        "esrecurse": {
+            "version": "4.2.1",
+            "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.2.1.tgz",
+            "integrity": "sha512-64RBB++fIOAXPw3P9cy89qfMlvZEXZkqqJkjqqXIvzP5ezRZjW+lPWjw35UX/3EhUPFYbg5ER4JYgDw4007/DQ==",
+            "dev": true,
+            "requires": {
+                "estraverse": "^4.1.0"
+            }
+        },
+        "estraverse": {
+            "version": "4.3.0",
+            "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+            "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+            "dev": true
+        },
+        "esutils": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+            "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+            "dev": true
+        },
+        "extend": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
+            "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g=="
+        },
+        "external-editor": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/external-editor/-/external-editor-3.1.0.tgz",
+            "integrity": "sha512-hMQ4CX1p1izmuLYyZqLMO/qGNw10wSv9QDCPfzXfyFrOaCSSoRfqE1Kf1s5an66J5JZC62NewG+mK49jOCtQew==",
+            "dev": true,
+            "requires": {
+                "chardet": "^0.7.0",
+                "iconv-lite": "^0.4.24",
+                "tmp": "^0.0.33"
+            },
+            "dependencies": {
+                "tmp": {
+                    "version": "0.0.33",
+                    "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz",
+                    "integrity": "sha512-jRCJlojKnZ3addtTOjdIqoRuPEKBvNXcGYqzO6zWZX8KfKEpnGY5jfggJQ3EjKuu8D4bJRr0y+cYJFmYbImXGw==",
+                    "dev": true,
+                    "requires": {
+                        "os-tmpdir": "~1.0.2"
+                    }
+                }
+            }
+        },
+        "extsprintf": {
+            "version": "1.3.0",
+            "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz",
+            "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU="
+        },
+        "fast-deep-equal": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz",
+            "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk="
+        },
+        "fast-json-stable-stringify": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz",
+            "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I="
+        },
+        "fast-levenshtein": {
+            "version": "2.0.6",
+            "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+            "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=",
+            "dev": true
+        },
+        "fetch-retry": {
+            "version": "2.2.3",
+            "resolved": "https://registry.npmjs.org/fetch-retry/-/fetch-retry-2.2.3.tgz",
+            "integrity": "sha512-aI/dDD6wNIqG5IET5d26tTt1zjehIpoJREQ5kQoWaz4Vo4v3w/+24l0ih/9Q7KKYrTR4ovsYhBMxd3VNsKx3ow==",
+            "requires": {
+                "es6-promise": "^4.2.8",
+                "isomorphic-fetch": "^2.2.1"
+            }
+        },
+        "figures": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/figures/-/figures-2.0.0.tgz",
+            "integrity": "sha1-OrGi0qYsi/tDGgyUy3l6L84nyWI=",
+            "dev": true,
+            "requires": {
+                "escape-string-regexp": "^1.0.5"
+            }
+        },
+        "file-entry-cache": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-5.0.1.tgz",
+            "integrity": "sha512-bCg29ictuBaKUwwArK4ouCaqDgLZcysCFLmM/Yn/FDoqndh/9vNuQfXRDvTuXKLxfD/JtZQGKFT8MGcJBK644g==",
+            "dev": true,
+            "requires": {
+                "flat-cache": "^2.0.1"
+            }
+        },
+        "fill-range": {
+            "version": "7.0.1",
+            "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+            "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+            "requires": {
+                "to-regex-range": "^5.0.1"
+            }
+        },
+        "find-cache-dir": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-2.1.0.tgz",
+            "integrity": "sha512-Tq6PixE0w/VMFfCgbONnkiQIVol/JJL7nRMi20fqzA4NRs9AfeqMGeRdPi3wIhYkxjeBaWh2rxwapn5Tu3IqOQ==",
+            "dev": true,
+            "requires": {
+                "commondir": "^1.0.1",
+                "make-dir": "^2.0.0",
+                "pkg-dir": "^3.0.0"
+            }
+        },
+        "find-up": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+            "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
+            "requires": {
+                "locate-path": "^3.0.0"
+            }
+        },
+        "flat": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz",
+            "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==",
+            "dev": true,
+            "requires": {
+                "is-buffer": "~2.0.3"
+            },
+            "dependencies": {
+                "is-buffer": {
+                    "version": "2.0.4",
+                    "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
+                    "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
+                    "dev": true
+                }
+            }
+        },
+        "flat-cache": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-2.0.1.tgz",
+            "integrity": "sha512-LoQe6yDuUMDzQAEH8sgmh4Md6oZnc/7PjtwjNFSzveXqSHt6ka9fPBuso7IGf9Rz4uqnSnWiFH2B/zj24a5ReA==",
+            "dev": true,
+            "requires": {
+                "flatted": "^2.0.0",
+                "rimraf": "2.6.3",
+                "write": "1.0.3"
+            }
+        },
+        "flatted": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/flatted/-/flatted-2.0.1.tgz",
+            "integrity": "sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==",
+            "dev": true
+        },
+        "foreground-child": {
+            "version": "1.5.6",
+            "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz",
+            "integrity": "sha1-T9ca0t/elnibmApcCilZN8svXOk=",
+            "dev": true,
+            "requires": {
+                "cross-spawn": "^4",
+                "signal-exit": "^3.0.0"
+            },
+            "dependencies": {
+                "cross-spawn": {
+                    "version": "4.0.2",
+                    "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-4.0.2.tgz",
+                    "integrity": "sha1-e5JHYhwjrf3ThWAEqCPL45dCTUE=",
+                    "dev": true,
+                    "requires": {
+                        "lru-cache": "^4.0.1",
+                        "which": "^1.2.9"
+                    }
+                }
+            }
+        },
+        "forever-agent": {
+            "version": "0.6.1",
+            "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz",
+            "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE="
+        },
+        "form-data": {
+            "version": "2.5.1",
+            "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz",
+            "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==",
+            "requires": {
+                "asynckit": "^0.4.0",
+                "combined-stream": "^1.0.6",
+                "mime-types": "^2.1.12"
+            }
+        },
+        "fs-extra": {
+            "version": "8.1.0",
+            "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz",
+            "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==",
+            "requires": {
+                "graceful-fs": "^4.2.0",
+                "jsonfile": "^4.0.0",
+                "universalify": "^0.1.0"
+            }
+        },
+        "fs.realpath": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+            "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
+            "dev": true
+        },
+        "fsevents": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.1.2.tgz",
+            "integrity": "sha512-R4wDiBwZ0KzpgOWetKDug1FZcYhqYnUYKtfZYt4mD5SBz76q0KR4Q9o7GIPamsVPGmW3EYPPJ0dOOjvx32ldZA==",
+            "optional": true
+        },
+        "function-bind": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+            "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+            "dev": true
+        },
+        "functional-red-black-tree": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+            "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=",
+            "dev": true
+        },
+        "get-caller-file": {
+            "version": "2.0.5",
+            "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+            "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
+        },
+        "get-func-name": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
+            "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
+            "dev": true
+        },
+        "get-port": {
+            "version": "5.0.0",
+            "resolved": "https://registry.npmjs.org/get-port/-/get-port-5.0.0.tgz",
+            "integrity": "sha512-imzMU0FjsZqNa6BqOjbbW6w5BivHIuQKopjpPqcnx0AVHJQKCxK1O+Ab3OrVXhrekqfVMjwA9ZYu062R+KcIsQ==",
+            "dev": true,
+            "requires": {
+                "type-fest": "^0.3.0"
+            }
+        },
+        "getpass": {
+            "version": "0.1.7",
+            "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz",
+            "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=",
+            "requires": {
+                "assert-plus": "^1.0.0"
+            }
+        },
+        "glob": {
+            "version": "7.1.6",
+            "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
+            "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
+            "dev": true,
+            "requires": {
+                "fs.realpath": "^1.0.0",
+                "inflight": "^1.0.4",
+                "inherits": "2",
+                "minimatch": "^3.0.4",
+                "once": "^1.3.0",
+                "path-is-absolute": "^1.0.0"
+            }
+        },
+        "glob-parent": {
+            "version": "5.1.0",
+            "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.0.tgz",
+            "integrity": "sha512-qjtRgnIVmOfnKUE3NJAQEdk+lKrxfw8t5ke7SXtfMTHcjsBfOfWXCQfdb30zfDoZQ2IRSIiidmjtbHZPZ++Ihw==",
+            "requires": {
+                "is-glob": "^4.0.1"
+            }
+        },
+        "globals": {
+            "version": "11.12.0",
+            "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+            "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+            "dev": true
+        },
+        "graceful-fs": {
+            "version": "4.2.3",
+            "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.3.tgz",
+            "integrity": "sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ=="
+        },
+        "growl": {
+            "version": "1.10.5",
+            "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
+            "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
+            "dev": true
+        },
+        "handlebars": {
+            "version": "4.5.3",
+            "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz",
+            "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==",
+            "dev": true,
+            "requires": {
+                "neo-async": "^2.6.0",
+                "optimist": "^0.6.1",
+                "source-map": "^0.6.1",
+                "uglify-js": "^3.1.4"
+            },
+            "dependencies": {
+                "source-map": {
+                    "version": "0.6.1",
+                    "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+                    "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+                    "dev": true
+                }
+            }
+        },
+        "har-schema": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz",
+            "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI="
+        },
+        "har-validator": {
+            "version": "5.1.3",
+            "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.3.tgz",
+            "integrity": "sha512-sNvOCzEQNr/qrvJgc3UG/kD4QtlHycrzwS+6mfTrrSq97BvaYcPZZI1ZSqGSPR73Cxn4LKTD4PttRwfU7jWq5g==",
+            "requires": {
+                "ajv": "^6.5.5",
+                "har-schema": "^2.0.0"
+            }
+        },
+        "has": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+            "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+            "dev": true,
+            "requires": {
+                "function-bind": "^1.1.1"
+            }
+        },
+        "has-flag": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+            "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
+            "dev": true
+        },
+        "has-symbols": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
+            "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=",
+            "dev": true
+        },
+        "hasha": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/hasha/-/hasha-3.0.0.tgz",
+            "integrity": "sha1-UqMvq4Vp1BymmmH/GiFPjrfIvTk=",
+            "dev": true,
+            "requires": {
+                "is-stream": "^1.0.1"
+            }
+        },
+        "he": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+            "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+            "dev": true
+        },
+        "hosted-git-info": {
+            "version": "2.8.5",
+            "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.5.tgz",
+            "integrity": "sha512-kssjab8CvdXfcXMXVcvsXum4Hwdq9XGtRD3TteMEvEbq0LXyiNQr6AprqKqfeaDXze7SxWvRxdpwE6ku7ikLkg==",
+            "dev": true
+        },
+        "http-signature": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz",
+            "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=",
+            "requires": {
+                "assert-plus": "^1.0.0",
+                "jsprim": "^1.2.2",
+                "sshpk": "^1.7.0"
+            }
+        },
+        "https-proxy-agent": {
+            "version": "2.2.4",
+            "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-2.2.4.tgz",
+            "integrity": "sha512-OmvfoQ53WLjtA9HeYP9RNrWMJzzAz1JGaSFr1nijg0PVR1JaD/xbJq1mdEIIlxGpXp9eSe/O2LgU9DJmTPd0Eg==",
+            "dev": true,
+            "requires": {
+                "agent-base": "^4.3.0",
+                "debug": "^3.1.0"
+            },
+            "dependencies": {
+                "debug": {
+                    "version": "3.2.6",
+                    "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+                    "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+                    "dev": true,
+                    "requires": {
+                        "ms": "^2.1.1"
+                    }
+                },
+                "ms": {
+                    "version": "2.1.2",
+                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+                    "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+                    "dev": true
+                }
+            }
+        },
+        "iconv-lite": {
+            "version": "0.4.24",
+            "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz",
+            "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==",
+            "requires": {
+                "safer-buffer": ">= 2.1.2 < 3"
+            }
+        },
+        "ignore": {
+            "version": "4.0.6",
+            "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+            "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+            "dev": true
+        },
+        "ignore-walk": {
+            "version": "3.0.3",
+            "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.3.tgz",
+            "integrity": "sha512-m7o6xuOaT1aqheYHKf8W6J5pYH85ZI9w077erOzLje3JsB1gkafkAhHHY19dqjulgIZHFm32Cp5uNZgcQqdJKw==",
+            "dev": true,
+            "requires": {
+                "minimatch": "^3.0.4"
+            }
+        },
+        "import-fresh": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.1.0.tgz",
+            "integrity": "sha512-PpuksHKGt8rXfWEr9m9EHIpgyyaltBy8+eF6GJM0QCAxMgxCfucMF3mjecK2QsJr0amJW7gTqh5/wht0z2UhEQ==",
+            "dev": true,
+            "requires": {
+                "parent-module": "^1.0.0",
+                "resolve-from": "^4.0.0"
+            }
+        },
+        "imurmurhash": {
+            "version": "0.1.4",
+            "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+            "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=",
+            "dev": true
+        },
+        "inflight": {
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+            "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
+            "dev": true,
+            "requires": {
+                "once": "^1.3.0",
+                "wrappy": "1"
+            }
+        },
+        "inherits": {
+            "version": "2.0.4",
+            "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+            "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+        },
+        "inquirer": {
+            "version": "6.5.2",
+            "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-6.5.2.tgz",
+            "integrity": "sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ==",
+            "dev": true,
+            "requires": {
+                "ansi-escapes": "^3.2.0",
+                "chalk": "^2.4.2",
+                "cli-cursor": "^2.1.0",
+                "cli-width": "^2.0.0",
+                "external-editor": "^3.0.3",
+                "figures": "^2.0.0",
+                "lodash": "^4.17.12",
+                "mute-stream": "0.0.7",
+                "run-async": "^2.2.0",
+                "rxjs": "^6.4.0",
+                "string-width": "^2.1.0",
+                "strip-ansi": "^5.1.0",
+                "through": "^2.3.6"
+            },
+            "dependencies": {
+                "ansi-regex": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+                    "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+                    "dev": true
+                },
+                "string-width": {
+                    "version": "2.1.1",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+                    "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+                    "dev": true,
+                    "requires": {
+                        "is-fullwidth-code-point": "^2.0.0",
+                        "strip-ansi": "^4.0.0"
+                    },
+                    "dependencies": {
+                        "strip-ansi": {
+                            "version": "4.0.0",
+                            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+                            "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+                            "dev": true,
+                            "requires": {
+                                "ansi-regex": "^3.0.0"
+                            }
+                        }
+                    }
+                },
+                "strip-ansi": {
+                    "version": "5.2.0",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+                    "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+                    "dev": true,
+                    "requires": {
+                        "ansi-regex": "^4.1.0"
+                    },
+                    "dependencies": {
+                        "ansi-regex": {
+                            "version": "4.1.0",
+                            "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+                            "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
+                            "dev": true
+                        }
+                    }
+                }
+            }
+        },
+        "is-arrayish": {
+            "version": "0.2.1",
+            "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+            "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=",
+            "dev": true
+        },
+        "is-binary-path": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+            "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+            "requires": {
+                "binary-extensions": "^2.0.0"
+            }
+        },
+        "is-callable": {
+            "version": "1.1.4",
+            "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
+            "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
+            "dev": true
+        },
+        "is-date-object": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
+            "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
+            "dev": true
+        },
+        "is-extglob": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+            "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI="
+        },
+        "is-fullwidth-code-point": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
+            "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8="
+        },
+        "is-glob": {
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz",
+            "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==",
+            "requires": {
+                "is-extglob": "^2.1.1"
+            }
+        },
+        "is-number": {
+            "version": "7.0.0",
+            "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+            "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng=="
+        },
+        "is-promise": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-2.1.0.tgz",
+            "integrity": "sha1-eaKp7OfwlugPNtKy87wWwf9L8/o=",
+            "dev": true
+        },
+        "is-regex": {
+            "version": "1.0.4",
+            "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
+            "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
+            "dev": true,
+            "requires": {
+                "has": "^1.0.1"
+            }
+        },
+        "is-stream": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
+            "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ="
+        },
+        "is-symbol": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
+            "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
+            "dev": true,
+            "requires": {
+                "has-symbols": "^1.0.0"
+            }
+        },
+        "is-typedarray": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz",
+            "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo="
+        },
+        "isexe": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+            "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
+            "dev": true
+        },
+        "isomorphic-fetch": {
+            "version": "2.2.1",
+            "resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz",
+            "integrity": "sha1-YRrhrPFPXoH3KVB0coGf6XM1WKk=",
+            "requires": {
+                "node-fetch": "^1.0.1",
+                "whatwg-fetch": ">=0.10.0"
+            }
+        },
+        "isstream": {
+            "version": "0.1.2",
+            "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
+            "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo="
+        },
+        "istanbul-lib-coverage": {
+            "version": "2.0.5",
+            "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.5.tgz",
+            "integrity": "sha512-8aXznuEPCJvGnMSRft4udDRDtb1V3pkQkMMI5LI+6HuQz5oQ4J2UFn1H82raA3qJtyOLkkwVqICBQkjnGtn5mA==",
+            "dev": true
+        },
+        "istanbul-lib-hook": {
+            "version": "2.0.7",
+            "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-2.0.7.tgz",
+            "integrity": "sha512-vrRztU9VRRFDyC+aklfLoeXyNdTfga2EI3udDGn4cZ6fpSXpHLV9X6CHvfoMCPtggg8zvDDmC4b9xfu0z6/llA==",
+            "dev": true,
+            "requires": {
+                "append-transform": "^1.0.0"
+            }
+        },
+        "istanbul-lib-instrument": {
+            "version": "3.3.0",
+            "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-3.3.0.tgz",
+            "integrity": "sha512-5nnIN4vo5xQZHdXno/YDXJ0G+I3dAm4XgzfSVTPLQpj/zAV2dV6Juy0yaf10/zrJOJeHoN3fraFe+XRq2bFVZA==",
+            "dev": true,
+            "requires": {
+                "@babel/generator": "^7.4.0",
+                "@babel/parser": "^7.4.3",
+                "@babel/template": "^7.4.0",
+                "@babel/traverse": "^7.4.3",
+                "@babel/types": "^7.4.0",
+                "istanbul-lib-coverage": "^2.0.5",
+                "semver": "^6.0.0"
+            },
+            "dependencies": {
+                "semver": {
+                    "version": "6.3.0",
+                    "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+                    "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+                    "dev": true
+                }
+            }
+        },
+        "istanbul-lib-report": {
+            "version": "2.0.8",
+            "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-2.0.8.tgz",
+            "integrity": "sha512-fHBeG573EIihhAblwgxrSenp0Dby6tJMFR/HvlerBsrCTD5bkUuoNtn3gVh29ZCS824cGGBPn7Sg7cNk+2xUsQ==",
+            "dev": true,
+            "requires": {
+                "istanbul-lib-coverage": "^2.0.5",
+                "make-dir": "^2.1.0",
+                "supports-color": "^6.1.0"
+            },
+            "dependencies": {
+                "supports-color": {
+                    "version": "6.1.0",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.1.0.tgz",
+                    "integrity": "sha512-qe1jfm1Mg7Nq/NSh6XE24gPXROEVsWHxC1LIx//XNlD9iw7YZQGjZNjYN7xGaEG6iKdA8EtNFW6R0gjnVXp+wQ==",
+                    "dev": true,
+                    "requires": {
+                        "has-flag": "^3.0.0"
+                    }
+                }
+            }
+        },
+        "istanbul-lib-source-maps": {
+            "version": "3.0.6",
+            "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-3.0.6.tgz",
+            "integrity": "sha512-R47KzMtDJH6X4/YW9XTx+jrLnZnscW4VpNN+1PViSYTejLVPWv7oov+Duf8YQSPyVRUvueQqz1TcsC6mooZTXw==",
+            "dev": true,
+            "requires": {
+                "debug": "^4.1.1",
+                "istanbul-lib-coverage": "^2.0.5",
+                "make-dir": "^2.1.0",
+                "rimraf": "^2.6.3",
+                "source-map": "^0.6.1"
+            },
+            "dependencies": {
+                "debug": {
+                    "version": "4.1.1",
+                    "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+                    "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+                    "dev": true,
+                    "requires": {
+                        "ms": "^2.1.1"
+                    }
+                },
+                "ms": {
+                    "version": "2.1.2",
+                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+                    "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+                    "dev": true
+                },
+                "source-map": {
+                    "version": "0.6.1",
+                    "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+                    "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+                    "dev": true
+                }
+            }
+        },
+        "istanbul-reports": {
+            "version": "2.2.6",
+            "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-2.2.6.tgz",
+            "integrity": "sha512-SKi4rnMyLBKe0Jy2uUdx28h8oG7ph2PPuQPvIAh31d+Ci+lSiEu4C+h3oBPuJ9+mPKhOyW0M8gY4U5NM1WLeXA==",
+            "dev": true,
+            "requires": {
+                "handlebars": "^4.1.2"
+            }
+        },
+        "js-tokens": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+            "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+            "dev": true
+        },
+        "js-yaml": {
+            "version": "3.13.1",
+            "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
+            "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
+            "dev": true,
+            "requires": {
+                "argparse": "^1.0.7",
+                "esprima": "^4.0.0"
+            }
+        },
+        "jsbn": {
+            "version": "0.1.1",
+            "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz",
+            "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM="
+        },
+        "jsesc": {
+            "version": "2.5.2",
+            "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+            "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+            "dev": true
+        },
+        "json-parse-better-errors": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz",
+            "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==",
+            "dev": true
+        },
+        "json-schema": {
+            "version": "0.2.3",
+            "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz",
+            "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM="
+        },
+        "json-schema-traverse": {
+            "version": "0.4.1",
+            "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+            "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+        },
+        "json-stable-stringify-without-jsonify": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+            "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=",
+            "dev": true
+        },
+        "json-stringify-safe": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz",
+            "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus="
+        },
+        "jsonfile": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz",
+            "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=",
+            "requires": {
+                "graceful-fs": "^4.1.6"
+            }
+        },
+        "jsprim": {
+            "version": "1.4.1",
+            "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz",
+            "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=",
+            "requires": {
+                "assert-plus": "1.0.0",
+                "extsprintf": "1.3.0",
+                "json-schema": "0.2.3",
+                "verror": "1.10.0"
+            }
+        },
+        "levn": {
+            "version": "0.3.0",
+            "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz",
+            "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=",
+            "dev": true,
+            "requires": {
+                "prelude-ls": "~1.1.2",
+                "type-check": "~0.3.2"
+            }
+        },
+        "livereload": {
+            "version": "0.9.1",
+            "resolved": "https://registry.npmjs.org/livereload/-/livereload-0.9.1.tgz",
+            "integrity": "sha512-9g7sua11kkyZNo2hLRCG3LuZZwqexoyEyecSlV8cAsfAVVCZqLzVir6XDqmH0r+Vzgnd5LrdHDMyjtFnJQLAYw==",
+            "requires": {
+                "chokidar": "^3.3.0",
+                "livereload-js": "^3.1.0",
+                "opts": ">= 1.2.0",
+                "ws": "^6.2.1"
+            }
+        },
+        "livereload-js": {
+            "version": "3.2.1",
+            "resolved": "https://registry.npmjs.org/livereload-js/-/livereload-js-3.2.1.tgz",
+            "integrity": "sha512-AJ+SUkAbAU6ijexIG/33PnBerq8O8g7b/qC4GdJLq2F5y4Ekf0XtCbXKv2C7kFzPmHc+lnCvDp+AaFG74NgHIQ=="
+        },
+        "load-json-file": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz",
+            "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=",
+            "dev": true,
+            "requires": {
+                "graceful-fs": "^4.1.2",
+                "parse-json": "^4.0.0",
+                "pify": "^3.0.0",
+                "strip-bom": "^3.0.0"
+            },
+            "dependencies": {
+                "pify": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+                    "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+                    "dev": true
+                }
+            }
+        },
+        "locate-path": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+            "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
+            "requires": {
+                "p-locate": "^3.0.0",
+                "path-exists": "^3.0.0"
+            }
+        },
+        "lodash": {
+            "version": "4.17.15",
+            "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
+            "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A=="
+        },
+        "lodash.flattendeep": {
+            "version": "4.4.0",
+            "resolved": "https://registry.npmjs.org/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz",
+            "integrity": "sha1-+wMJF/hqMTTlvJvsDWngAT3f7bI=",
+            "dev": true
+        },
+        "log-symbols": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
+            "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
+            "dev": true,
+            "requires": {
+                "chalk": "^2.0.1"
+            }
+        },
+        "lru-cache": {
+            "version": "4.1.5",
+            "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-4.1.5.tgz",
+            "integrity": "sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==",
+            "dev": true,
+            "requires": {
+                "pseudomap": "^1.0.2",
+                "yallist": "^2.1.2"
+            }
+        },
+        "make-dir": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-2.1.0.tgz",
+            "integrity": "sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==",
+            "dev": true,
+            "requires": {
+                "pify": "^4.0.1",
+                "semver": "^5.6.0"
+            }
+        },
+        "manakin": {
+            "version": "0.5.2",
+            "resolved": "https://registry.npmjs.org/manakin/-/manakin-0.5.2.tgz",
+            "integrity": "sha512-pfDSB7QYoVg0Io4KMV9hhPoXpj6p0uBscgtyUSKCOFZe8bqgbpStfgnKIbF/ulnr6U3ICu4OqdyxAqBgOhZwBQ=="
+        },
+        "merge-source-map": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/merge-source-map/-/merge-source-map-1.1.0.tgz",
+            "integrity": "sha512-Qkcp7P2ygktpMPh2mCQZaf3jhN6D3Z/qVZHSdWvQ+2Ef5HgRAPBO57A77+ENm0CPx2+1Ce/MYKi3ymqdfuqibw==",
+            "dev": true,
+            "requires": {
+                "source-map": "^0.6.1"
+            },
+            "dependencies": {
+                "source-map": {
+                    "version": "0.6.1",
+                    "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+                    "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+                    "dev": true
+                }
+            }
+        },
+        "mime-db": {
+            "version": "1.42.0",
+            "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz",
+            "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ=="
+        },
+        "mime-types": {
+            "version": "2.1.25",
+            "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz",
+            "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==",
+            "requires": {
+                "mime-db": "1.42.0"
+            }
+        },
+        "mimic-fn": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz",
+            "integrity": "sha512-jf84uxzwiuiIVKiOLpfYk7N46TSy8ubTonmneY9vrpHNAnp0QBt2BxWV9dO3/j+BoVAb+a5G6YDPW3M5HOdMWQ==",
+            "dev": true
+        },
+        "minimatch": {
+            "version": "3.0.4",
+            "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
+            "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
+            "dev": true,
+            "requires": {
+                "brace-expansion": "^1.1.7"
+            }
+        },
+        "minimist": {
+            "version": "0.0.8",
+            "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
+            "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
+            "dev": true
+        },
+        "mkdirp": {
+            "version": "0.5.1",
+            "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
+            "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
+            "dev": true,
+            "requires": {
+                "minimist": "0.0.8"
+            }
+        },
+        "mkpath": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/mkpath/-/mkpath-0.1.0.tgz",
+            "integrity": "sha1-dVSm+Nhxg0zJe1RisSLEwSTW3pE="
+        },
+        "mocha": {
+            "version": "6.2.2",
+            "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.2.tgz",
+            "integrity": "sha512-FgDS9Re79yU1xz5d+C4rv1G7QagNGHZ+iXF81hO8zY35YZZcLEsJVfFolfsqKFWunATEvNzMK0r/CwWd/szO9A==",
+            "dev": true,
+            "requires": {
+                "ansi-colors": "3.2.3",
+                "browser-stdout": "1.3.1",
+                "debug": "3.2.6",
+                "diff": "3.5.0",
+                "escape-string-regexp": "1.0.5",
+                "find-up": "3.0.0",
+                "glob": "7.1.3",
+                "growl": "1.10.5",
+                "he": "1.2.0",
+                "js-yaml": "3.13.1",
+                "log-symbols": "2.2.0",
+                "minimatch": "3.0.4",
+                "mkdirp": "0.5.1",
+                "ms": "2.1.1",
+                "node-environment-flags": "1.0.5",
+                "object.assign": "4.1.0",
+                "strip-json-comments": "2.0.1",
+                "supports-color": "6.0.0",
+                "which": "1.3.1",
+                "wide-align": "1.1.3",
+                "yargs": "13.3.0",
+                "yargs-parser": "13.1.1",
+                "yargs-unparser": "1.6.0"
+            },
+            "dependencies": {
+                "debug": {
+                    "version": "3.2.6",
+                    "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+                    "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+                    "dev": true,
+                    "requires": {
+                        "ms": "^2.1.1"
+                    }
+                },
+                "glob": {
+                    "version": "7.1.3",
+                    "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
+                    "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
+                    "dev": true,
+                    "requires": {
+                        "fs.realpath": "^1.0.0",
+                        "inflight": "^1.0.4",
+                        "inherits": "2",
+                        "minimatch": "^3.0.4",
+                        "once": "^1.3.0",
+                        "path-is-absolute": "^1.0.0"
+                    }
+                },
+                "ms": {
+                    "version": "2.1.1",
+                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
+                    "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
+                    "dev": true
+                },
+                "supports-color": {
+                    "version": "6.0.0",
+                    "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz",
+                    "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==",
+                    "dev": true,
+                    "requires": {
+                        "has-flag": "^3.0.0"
+                    }
+                }
+            }
+        },
+        "mocha-multi-reporters": {
+            "version": "1.1.7",
+            "resolved": "https://registry.npmjs.org/mocha-multi-reporters/-/mocha-multi-reporters-1.1.7.tgz",
+            "integrity": "sha1-zH8/TTL0eFIJQdhSq7ZNmYhYfYI=",
+            "dev": true,
+            "requires": {
+                "debug": "^3.1.0",
+                "lodash": "^4.16.4"
+            },
+            "dependencies": {
+                "debug": {
+                    "version": "3.2.6",
+                    "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+                    "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+                    "dev": true,
+                    "requires": {
+                        "ms": "^2.1.1"
+                    }
+                },
+                "ms": {
+                    "version": "2.1.2",
+                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+                    "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+                    "dev": true
+                }
+            }
+        },
+        "mute-stream": {
+            "version": "0.0.7",
+            "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.7.tgz",
+            "integrity": "sha1-MHXOk7whuPq0PhvE2n6BFe0ee6s=",
+            "dev": true
+        },
+        "natural-compare": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+            "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=",
+            "dev": true
+        },
+        "needle": {
+            "version": "2.4.0",
+            "resolved": "https://registry.npmjs.org/needle/-/needle-2.4.0.tgz",
+            "integrity": "sha512-4Hnwzr3mi5L97hMYeNl8wRW/Onhy4nUKR/lVemJ8gJedxxUyBLm9kkrDColJvoSfwi0jCNhD+xCdOtiGDQiRZg==",
+            "requires": {
+                "debug": "^3.2.6",
+                "iconv-lite": "^0.4.4",
+                "sax": "^1.2.4"
+            },
+            "dependencies": {
+                "debug": {
+                    "version": "3.2.6",
+                    "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
+                    "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
+                    "requires": {
+                        "ms": "^2.1.1"
+                    }
+                },
+                "ms": {
+                    "version": "2.1.2",
+                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+                    "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+                }
+            }
+        },
+        "neo-async": {
+            "version": "2.6.1",
+            "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.1.tgz",
+            "integrity": "sha512-iyam8fBuCUpWeKPGpaNMetEocMt364qkCsfL9JuhjXX6dRnguRVOfk2GZaDpPjcOKiiXCPINZC1GczQ7iTq3Zw==",
+            "dev": true
+        },
+        "nested-error-stacks": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/nested-error-stacks/-/nested-error-stacks-2.1.0.tgz",
+            "integrity": "sha512-AO81vsIO1k1sM4Zrd6Hu7regmJN1NSiAja10gc4bX3F0wd+9rQmcuHQaHVQCYIEC8iFXnE+mavh23GOt7wBgug==",
+            "dev": true
+        },
+        "ngrok": {
+            "version": "3.2.5",
+            "resolved": "https://registry.npmjs.org/ngrok/-/ngrok-3.2.5.tgz",
+            "integrity": "sha512-FWWQJSg8A1L6prZmT53onZMiFiaY+CfDgS9YStKjbE3qf2WDmRdi6kNBFvQKD2ARSv/te+rqeizAOGSUH5X56w==",
+            "requires": {
+                "@types/node": "^8.10.50",
+                "@types/request": "^2.48.2",
+                "decompress-zip": "^0.3.2",
+                "request": "^2.88.0",
+                "request-promise-native": "^1.0.7",
+                "uuid": "^3.3.2"
+            }
+        },
+        "nice-try": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
+            "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
+            "dev": true
+        },
+        "nock": {
+            "version": "11.7.0",
+            "resolved": "https://registry.npmjs.org/nock/-/nock-11.7.0.tgz",
+            "integrity": "sha512-7c1jhHew74C33OBeRYyQENT+YXQiejpwIrEjinh6dRurBae+Ei4QjeUaPlkptIF0ZacEiVCnw8dWaxqepkiihg==",
+            "dev": true,
+            "requires": {
+                "chai": "^4.1.2",
+                "debug": "^4.1.0",
+                "json-stringify-safe": "^5.0.1",
+                "lodash": "^4.17.13",
+                "mkdirp": "^0.5.0",
+                "propagate": "^2.0.0"
+            },
+            "dependencies": {
+                "debug": {
+                    "version": "4.1.1",
+                    "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz",
+                    "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==",
+                    "dev": true,
+                    "requires": {
+                        "ms": "^2.1.1"
+                    }
+                },
+                "ms": {
+                    "version": "2.1.2",
+                    "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+                    "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+                    "dev": true
+                }
+            }
+        },
+        "node-environment-flags": {
+            "version": "1.0.5",
+            "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz",
+            "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==",
+            "dev": true,
+            "requires": {
+                "object.getownpropertydescriptors": "^2.0.3",
+                "semver": "^5.7.0"
+            }
+        },
+        "node-fetch": {
+            "version": "1.7.3",
+            "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-1.7.3.tgz",
+            "integrity": "sha512-NhZ4CsKx7cYm2vSrBAr2PvFOe6sWDf0UYLRqA6svUYg7+/TSfVAu49jYC4BvQ4Sms9SZgdqGBgroqfDhJdTyKQ==",
+            "requires": {
+                "encoding": "^0.1.11",
+                "is-stream": "^1.0.1"
+            }
+        },
+        "nopt": {
+            "version": "3.0.6",
+            "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz",
+            "integrity": "sha1-xkZdvwirzU2zWTF/eaxopkayj/k=",
+            "requires": {
+                "abbrev": "1"
+            }
+        },
+        "normalize-package-data": {
+            "version": "2.5.0",
+            "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+            "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+            "dev": true,
+            "requires": {
+                "hosted-git-info": "^2.1.4",
+                "resolve": "^1.10.0",
+                "semver": "2 || 3 || 4 || 5",
+                "validate-npm-package-license": "^3.0.1"
+            }
+        },
+        "normalize-path": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+            "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA=="
+        },
+        "nyc": {
+            "version": "14.1.1",
+            "resolved": "https://registry.npmjs.org/nyc/-/nyc-14.1.1.tgz",
+            "integrity": "sha512-OI0vm6ZGUnoGZv/tLdZ2esSVzDwUC88SNs+6JoSOMVxA+gKMB8Tk7jBwgemLx4O40lhhvZCVw1C+OYLOBOPXWw==",
+            "dev": true,
+            "requires": {
+                "archy": "^1.0.0",
+                "caching-transform": "^3.0.2",
+                "convert-source-map": "^1.6.0",
+                "cp-file": "^6.2.0",
+                "find-cache-dir": "^2.1.0",
+                "find-up": "^3.0.0",
+                "foreground-child": "^1.5.6",
+                "glob": "^7.1.3",
+                "istanbul-lib-coverage": "^2.0.5",
+                "istanbul-lib-hook": "^2.0.7",
+                "istanbul-lib-instrument": "^3.3.0",
+                "istanbul-lib-report": "^2.0.8",
+                "istanbul-lib-source-maps": "^3.0.6",
+                "istanbul-reports": "^2.2.4",
+                "js-yaml": "^3.13.1",
+                "make-dir": "^2.1.0",
+                "merge-source-map": "^1.1.0",
+                "resolve-from": "^4.0.0",
+                "rimraf": "^2.6.3",
+                "signal-exit": "^3.0.2",
+                "spawn-wrap": "^1.4.2",
+                "test-exclude": "^5.2.3",
+                "uuid": "^3.3.2",
+                "yargs": "^13.2.2",
+                "yargs-parser": "^13.0.0"
+            }
+        },
+        "oauth-sign": {
+            "version": "0.9.0",
+            "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
+            "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ=="
+        },
+        "object-inspect": {
+            "version": "1.7.0",
+            "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.7.0.tgz",
+            "integrity": "sha512-a7pEHdh1xKIAgTySUGgLMx/xwDZskN1Ud6egYYN3EdRW4ZMPNEDUTF+hwy2LUC+Bl+SyLXANnwz/jyh/qutKUw==",
+            "dev": true
+        },
+        "object-keys": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+            "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+            "dev": true
+        },
+        "object.assign": {
+            "version": "4.1.0",
+            "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
+            "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
+            "dev": true,
+            "requires": {
+                "define-properties": "^1.1.2",
+                "function-bind": "^1.1.1",
+                "has-symbols": "^1.0.0",
+                "object-keys": "^1.0.11"
+            }
+        },
+        "object.getownpropertydescriptors": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
+            "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
+            "dev": true,
+            "requires": {
+                "define-properties": "^1.1.2",
+                "es-abstract": "^1.5.1"
+            }
+        },
+        "once": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+            "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
+            "dev": true,
+            "requires": {
+                "wrappy": "1"
+            }
+        },
+        "onetime": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/onetime/-/onetime-2.0.1.tgz",
+            "integrity": "sha1-BnQoIw/WdEOyeUsiu6UotoZ5YtQ=",
+            "dev": true,
+            "requires": {
+                "mimic-fn": "^1.0.0"
+            }
+        },
+        "openwhisk": {
+            "version": "3.20.0",
+            "resolved": "https://registry.npmjs.org/openwhisk/-/openwhisk-3.20.0.tgz",
+            "integrity": "sha512-WzB69ij39kb+lVpM9DncaPbRZ00tcARgOI0/XfaVqZDexspLwBZ/1TswuLSuxV2ocozMqclD+1lKlmmVn0DU0g==",
+            "requires": {
+                "needle": "^2.4.0"
+            }
+        },
+        "optimist": {
+            "version": "0.6.1",
+            "resolved": "https://registry.npmjs.org/optimist/-/optimist-0.6.1.tgz",
+            "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=",
+            "dev": true,
+            "requires": {
+                "minimist": "~0.0.1",
+                "wordwrap": "~0.0.2"
+            }
+        },
+        "optionator": {
+            "version": "0.8.3",
+            "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz",
+            "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==",
+            "dev": true,
+            "requires": {
+                "deep-is": "~0.1.3",
+                "fast-levenshtein": "~2.0.6",
+                "levn": "~0.3.0",
+                "prelude-ls": "~1.1.2",
+                "type-check": "~0.3.2",
+                "word-wrap": "~1.2.3"
+            }
+        },
+        "opts": {
+            "version": "1.2.7",
+            "resolved": "https://registry.npmjs.org/opts/-/opts-1.2.7.tgz",
+            "integrity": "sha512-hwZhzGGG/GQ7igxAVFOEun2N4fWul31qE9nfBdCnZGQCB5+L7tN9xZ+94B4aUpLOJx/of3zZs5XsuubayQYQjA=="
+        },
+        "os-homedir": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz",
+            "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=",
+            "dev": true
+        },
+        "os-tmpdir": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz",
+            "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=",
+            "dev": true
+        },
+        "p-limit": {
+            "version": "2.2.1",
+            "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.1.tgz",
+            "integrity": "sha512-85Tk+90UCVWvbDavCLKPOLC9vvY8OwEX/RtKF+/1OADJMVlFfEHOiMTPVyxg7mk/dKa+ipdHm0OUkTvCpMTuwg==",
+            "requires": {
+                "p-try": "^2.0.0"
+            }
+        },
+        "p-locate": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+            "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
+            "requires": {
+                "p-limit": "^2.0.0"
+            }
+        },
+        "p-try": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+            "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
+        },
+        "package-hash": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-3.0.0.tgz",
+            "integrity": "sha512-lOtmukMDVvtkL84rJHI7dpTYq+0rli8N2wlnqUcBuDWCfVhRUfOmnR9SsoHFMLpACvEV60dX7rd0rFaYDZI+FA==",
+            "dev": true,
+            "requires": {
+                "graceful-fs": "^4.1.15",
+                "hasha": "^3.0.0",
+                "lodash.flattendeep": "^4.4.0",
+                "release-zalgo": "^1.0.0"
+            }
+        },
+        "parent-module": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+            "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+            "dev": true,
+            "requires": {
+                "callsites": "^3.0.0"
+            }
+        },
+        "parse-json": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz",
+            "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=",
+            "dev": true,
+            "requires": {
+                "error-ex": "^1.3.1",
+                "json-parse-better-errors": "^1.0.1"
+            }
+        },
+        "path-exists": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+            "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
+        },
+        "path-is-absolute": {
+            "version": "1.0.1",
+            "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+            "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
+            "dev": true
+        },
+        "path-is-inside": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz",
+            "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=",
+            "dev": true
+        },
+        "path-key": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
+            "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
+            "dev": true
+        },
+        "path-parse": {
+            "version": "1.0.6",
+            "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz",
+            "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==",
+            "dev": true
+        },
+        "path-type": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/path-type/-/path-type-3.0.0.tgz",
+            "integrity": "sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==",
+            "dev": true,
+            "requires": {
+                "pify": "^3.0.0"
+            },
+            "dependencies": {
+                "pify": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz",
+                    "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=",
+                    "dev": true
+                }
+            }
+        },
+        "pathval": {
+            "version": "1.1.0",
+            "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
+            "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
+            "dev": true
+        },
+        "performance-now": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz",
+            "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns="
+        },
+        "picomatch": {
+            "version": "2.2.1",
+            "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.1.tgz",
+            "integrity": "sha512-ISBaA8xQNmwELC7eOjqFKMESB2VIqt4PPDD0nsS95b/9dZXvVKOlz9keMSnoGGKcOHXfTvDD6WMaRoSc9UuhRA=="
+        },
+        "pify": {
+            "version": "4.0.1",
+            "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz",
+            "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==",
+            "dev": true
+        },
+        "pkg-dir": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz",
+            "integrity": "sha512-/E57AYkoeQ25qkxMj5PBOVgF8Kiu/h7cYS30Z5+R7WaiCCBfLq58ZI/dSeaEKb9WVJV5n/03QwrN3IeWIFllvw==",
+            "dev": true,
+            "requires": {
+                "find-up": "^3.0.0"
+            }
+        },
+        "prelude-ls": {
+            "version": "1.1.2",
+            "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz",
+            "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=",
+            "dev": true
+        },
+        "progress": {
+            "version": "2.0.3",
+            "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+            "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+            "dev": true
+        },
+        "propagate": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/propagate/-/propagate-2.0.1.tgz",
+            "integrity": "sha512-vGrhOavPSTz4QVNuBNdcNXePNdNMaO1xj9yBeH1ScQPjk/rhg9sSlCXPhMkFuaNNW/syTvYqsnbIJxMBfRbbag==",
+            "dev": true
+        },
+        "pseudomap": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz",
+            "integrity": "sha1-8FKijacOYYkX7wqKw0wa5aaChrM=",
+            "dev": true
+        },
+        "psl": {
+            "version": "1.4.0",
+            "resolved": "https://registry.npmjs.org/psl/-/psl-1.4.0.tgz",
+            "integrity": "sha512-HZzqCGPecFLyoRj5HLfuDSKYTJkAfB5thKBIkRHtGjWwY7p1dAyveIbXIq4tO0KYfDF2tHqPUgY9SDnGm00uFw=="
+        },
+        "punycode": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+            "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+        },
+        "q": {
+            "version": "1.5.1",
+            "resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
+            "integrity": "sha1-fjL3W0E4EpHQRhHxvxQQmsAGUdc="
+        },
+        "qs": {
+            "version": "6.5.2",
+            "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz",
+            "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA=="
+        },
+        "ramda": {
+            "version": "0.26.1",
+            "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.26.1.tgz",
+            "integrity": "sha512-hLWjpy7EnsDBb0p+Z3B7rPi3GDeRG5ZtiI33kJhTt+ORCd38AbAIjB/9zRIUoeTbE/AVX5ZkU7m6bznsvrf8eQ==",
+            "dev": true
+        },
+        "read-pkg": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-3.0.0.tgz",
+            "integrity": "sha1-nLxoaXj+5l0WwA4rGcI3/Pbjg4k=",
+            "dev": true,
+            "requires": {
+                "load-json-file": "^4.0.0",
+                "normalize-package-data": "^2.3.2",
+                "path-type": "^3.0.0"
+            }
+        },
+        "read-pkg-up": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-4.0.0.tgz",
+            "integrity": "sha512-6etQSH7nJGsK0RbG/2TeDzZFa8shjQ1um+SwQQ5cwKy0dhSXdOncEhb1CPpvQG4h7FyOV6EB6YlV0yJvZQNAkA==",
+            "dev": true,
+            "requires": {
+                "find-up": "^3.0.0",
+                "read-pkg": "^3.0.0"
+            }
+        },
+        "readdirp": {
+            "version": "3.3.0",
+            "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.3.0.tgz",
+            "integrity": "sha512-zz0pAkSPOXXm1viEwygWIPSPkcBYjW1xU5j/JBh5t9bGCJwa6f9+BJa6VaB2g+b55yVrmXzqkyLf4xaWYM0IkQ==",
+            "requires": {
+                "picomatch": "^2.0.7"
+            }
+        },
+        "regexpp": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-2.0.1.tgz",
+            "integrity": "sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==",
+            "dev": true
+        },
+        "release-zalgo": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz",
+            "integrity": "sha1-CXALflB0Mpc5Mw5TXFqQ+2eFFzA=",
+            "dev": true,
+            "requires": {
+                "es6-error": "^4.0.1"
+            }
+        },
+        "request": {
+            "version": "2.88.0",
+            "resolved": "https://registry.npmjs.org/request/-/request-2.88.0.tgz",
+            "integrity": "sha512-NAqBSrijGLZdM0WZNsInLJpkJokL72XYjUpnB0iwsRgxh7dB6COrHnTBNwN0E+lHDAJzu7kLAkDeY08z2/A0hg==",
+            "requires": {
+                "aws-sign2": "~0.7.0",
+                "aws4": "^1.8.0",
+                "caseless": "~0.12.0",
+                "combined-stream": "~1.0.6",
+                "extend": "~3.0.2",
+                "forever-agent": "~0.6.1",
+                "form-data": "~2.3.2",
+                "har-validator": "~5.1.0",
+                "http-signature": "~1.2.0",
+                "is-typedarray": "~1.0.0",
+                "isstream": "~0.1.2",
+                "json-stringify-safe": "~5.0.1",
+                "mime-types": "~2.1.19",
+                "oauth-sign": "~0.9.0",
+                "performance-now": "^2.1.0",
+                "qs": "~6.5.2",
+                "safe-buffer": "^5.1.2",
+                "tough-cookie": "~2.4.3",
+                "tunnel-agent": "^0.6.0",
+                "uuid": "^3.3.2"
+            },
+            "dependencies": {
+                "form-data": {
+                    "version": "2.3.3",
+                    "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz",
+                    "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==",
+                    "requires": {
+                        "asynckit": "^0.4.0",
+                        "combined-stream": "^1.0.6",
+                        "mime-types": "^2.1.12"
+                    }
+                }
+            }
+        },
+        "request-promise-core": {
+            "version": "1.1.3",
+            "resolved": "https://registry.npmjs.org/request-promise-core/-/request-promise-core-1.1.3.tgz",
+            "integrity": "sha512-QIs2+ArIGQVp5ZYbWD5ZLCY29D5CfWizP8eWnm8FoGD1TX61veauETVQbrV60662V0oFBkrDOuaBI8XgtuyYAQ==",
+            "requires": {
+                "lodash": "^4.17.15"
+            }
+        },
+        "request-promise-native": {
+            "version": "1.0.8",
+            "resolved": "https://registry.npmjs.org/request-promise-native/-/request-promise-native-1.0.8.tgz",
+            "integrity": "sha512-dapwLGqkHtwL5AEbfenuzjTYg35Jd6KPytsC2/TLkVMz8rm+tNt72MGUWT1RP/aYawMpN6HqbNGBQaRcBtjQMQ==",
+            "requires": {
+                "request-promise-core": "1.1.3",
+                "stealthy-require": "^1.1.1",
+                "tough-cookie": "^2.3.3"
+            }
+        },
+        "require-directory": {
+            "version": "2.1.1",
+            "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+            "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
+        },
+        "require-main-filename": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+            "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
+        },
+        "resolve": {
+            "version": "1.12.0",
+            "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.12.0.tgz",
+            "integrity": "sha512-B/dOmuoAik5bKcD6s6nXDCjzUKnaDvdkRyAk6rsmsKLipWj4797iothd7jmmUhWTfinVMU+wc56rYKsit2Qy4w==",
+            "dev": true,
+            "requires": {
+                "path-parse": "^1.0.6"
+            }
+        },
+        "resolve-from": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+            "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+            "dev": true
+        },
+        "restore-cursor": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-2.0.0.tgz",
+            "integrity": "sha1-n37ih/gv0ybU/RYpI9YhKe7g368=",
+            "dev": true,
+            "requires": {
+                "onetime": "^2.0.0",
+                "signal-exit": "^3.0.2"
+            }
+        },
+        "rimraf": {
+            "version": "2.6.3",
+            "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz",
+            "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==",
+            "dev": true,
+            "requires": {
+                "glob": "^7.1.3"
+            }
+        },
+        "run-async": {
+            "version": "2.3.0",
+            "resolved": "https://registry.npmjs.org/run-async/-/run-async-2.3.0.tgz",
+            "integrity": "sha1-A3GrSuC91yDUFm19/aZP96RFpsA=",
+            "dev": true,
+            "requires": {
+                "is-promise": "^2.1.0"
+            }
+        },
+        "rxjs": {
+            "version": "6.5.3",
+            "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.3.tgz",
+            "integrity": "sha512-wuYsAYYFdWTAnAaPoKGNhfpWwKZbJW+HgAJ+mImp+Epl7BG8oNWBCTyRM8gba9k4lk8BgWdoYm21Mo/RYhhbgA==",
+            "dev": true,
+            "requires": {
+                "tslib": "^1.9.0"
+            }
+        },
+        "safe-buffer": {
+            "version": "5.1.2",
+            "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+            "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
+        },
+        "safer-buffer": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+            "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
+        },
+        "sax": {
+            "version": "1.2.4",
+            "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
+            "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
+        },
+        "semver": {
+            "version": "5.7.1",
+            "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
+            "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
+            "dev": true
+        },
+        "set-blocking": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+            "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
+        },
+        "shebang-command": {
+            "version": "1.2.0",
+            "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
+            "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
+            "dev": true,
+            "requires": {
+                "shebang-regex": "^1.0.0"
+            }
+        },
+        "shebang-regex": {
+            "version": "1.0.0",
+            "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
+            "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
+            "dev": true
+        },
+        "signal-exit": {
+            "version": "3.0.2",
+            "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
+            "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
+            "dev": true
+        },
+        "slice-ansi": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-2.1.0.tgz",
+            "integrity": "sha512-Qu+VC3EwYLldKa1fCxuuvULvSJOKEgk9pi8dZeCVK7TqBfUNTH4sFkk4joj8afVSfAYgJoSOetjx9QWOJ5mYoQ==",
+            "dev": true,
+            "requires": {
+                "ansi-styles": "^3.2.0",
+                "astral-regex": "^1.0.0",
+                "is-fullwidth-code-point": "^2.0.0"
+            }
+        },
+        "source-map": {
+            "version": "0.5.7",
+            "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+            "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
+            "dev": true
+        },
+        "spawn-wrap": {
+            "version": "1.4.3",
+            "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-1.4.3.tgz",
+            "integrity": "sha512-IgB8md0QW/+tWqcavuFgKYR/qIRvJkRLPJDFaoXtLLUaVcCDK0+HeFTkmQHj3eprcYhc+gOl0aEA1w7qZlYezw==",
+            "dev": true,
+            "requires": {
+                "foreground-child": "^1.5.6",
+                "mkdirp": "^0.5.0",
+                "os-homedir": "^1.0.1",
+                "rimraf": "^2.6.2",
+                "signal-exit": "^3.0.2",
+                "which": "^1.3.0"
+            }
+        },
+        "spdx-correct": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz",
+            "integrity": "sha512-lr2EZCctC2BNR7j7WzJ2FpDznxky1sjfxvvYEyzxNyb6lZXHODmEoJeFu4JupYlkfha1KZpJyoqiJ7pgA1qq8Q==",
+            "dev": true,
+            "requires": {
+                "spdx-expression-parse": "^3.0.0",
+                "spdx-license-ids": "^3.0.0"
+            }
+        },
+        "spdx-exceptions": {
+            "version": "2.2.0",
+            "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.2.0.tgz",
+            "integrity": "sha512-2XQACfElKi9SlVb1CYadKDXvoajPgBVPn/gOQLrTvHdElaVhr7ZEbqJaRnJLVNeaI4cMEAgVCeBMKF6MWRDCRA==",
+            "dev": true
+        },
+        "spdx-expression-parse": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.0.tgz",
+            "integrity": "sha512-Yg6D3XpRD4kkOmTpdgbUiEJFKghJH03fiC1OPll5h/0sO6neh2jqRDVHOQ4o/LMea0tgCkbMgea5ip/e+MkWyg==",
+            "dev": true,
+            "requires": {
+                "spdx-exceptions": "^2.1.0",
+                "spdx-license-ids": "^3.0.0"
+            }
+        },
+        "spdx-license-ids": {
+            "version": "3.0.5",
+            "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.5.tgz",
+            "integrity": "sha512-J+FWzZoynJEXGphVIS+XEh3kFSjZX/1i9gFBaWQcB+/tmpe2qUsSBABpcxqxnAxFdiUFEgAX1bjYGQvIZmoz9Q==",
+            "dev": true
+        },
+        "sprintf-js": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+            "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
+            "dev": true
+        },
+        "sshpk": {
+            "version": "1.16.1",
+            "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz",
+            "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==",
+            "requires": {
+                "asn1": "~0.2.3",
+                "assert-plus": "^1.0.0",
+                "bcrypt-pbkdf": "^1.0.0",
+                "dashdash": "^1.12.0",
+                "ecc-jsbn": "~0.1.1",
+                "getpass": "^0.1.1",
+                "jsbn": "~0.1.0",
+                "safer-buffer": "^2.0.2",
+                "tweetnacl": "~0.14.0"
+            }
+        },
+        "stealthy-require": {
+            "version": "1.1.1",
+            "resolved": "https://registry.npmjs.org/stealthy-require/-/stealthy-require-1.1.1.tgz",
+            "integrity": "sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks="
+        },
+        "string-width": {
+            "version": "3.1.0",
+            "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
+            "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
+            "requires": {
+                "emoji-regex": "^7.0.1",
+                "is-fullwidth-code-point": "^2.0.0",
+                "strip-ansi": "^5.1.0"
+            },
+            "dependencies": {
+                "ansi-regex": {
+                    "version": "4.1.0",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+                    "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
+                },
+                "strip-ansi": {
+                    "version": "5.2.0",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+                    "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+                    "requires": {
+                        "ansi-regex": "^4.1.0"
+                    }
+                }
+            }
+        },
+        "string.prototype.trimleft": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/string.prototype.trimleft/-/string.prototype.trimleft-2.1.0.tgz",
+            "integrity": "sha512-FJ6b7EgdKxxbDxc79cOlok6Afd++TTs5szo+zJTUyow3ycrRfJVE2pq3vcN53XexvKZu/DJMDfeI/qMiZTrjTw==",
+            "dev": true,
+            "requires": {
+                "define-properties": "^1.1.3",
+                "function-bind": "^1.1.1"
+            }
+        },
+        "string.prototype.trimright": {
+            "version": "2.1.0",
+            "resolved": "https://registry.npmjs.org/string.prototype.trimright/-/string.prototype.trimright-2.1.0.tgz",
+            "integrity": "sha512-fXZTSV55dNBwv16uw+hh5jkghxSnc5oHq+5K/gXgizHwAvMetdAJlHqqoFC1FSDVPYWLkAKl2cxpUT41sV7nSg==",
+            "dev": true,
+            "requires": {
+                "define-properties": "^1.1.3",
+                "function-bind": "^1.1.1"
+            }
+        },
+        "strip-ansi": {
+            "version": "6.0.0",
+            "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
+            "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
+            "dev": true,
+            "requires": {
+                "ansi-regex": "^5.0.0"
+            }
+        },
+        "strip-bom": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz",
+            "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=",
+            "dev": true
+        },
+        "strip-json-comments": {
+            "version": "2.0.1",
+            "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
+            "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
+            "dev": true
+        },
+        "supports-color": {
+            "version": "5.5.0",
+            "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+            "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+            "dev": true,
+            "requires": {
+                "has-flag": "^3.0.0"
+            }
+        },
+        "table": {
+            "version": "5.4.6",
+            "resolved": "https://registry.npmjs.org/table/-/table-5.4.6.tgz",
+            "integrity": "sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==",
+            "dev": true,
+            "requires": {
+                "ajv": "^6.10.2",
+                "lodash": "^4.17.14",
+                "slice-ansi": "^2.1.0",
+                "string-width": "^3.0.0"
+            }
+        },
+        "teeny-request": {
+            "version": "3.11.3",
+            "resolved": "https://registry.npmjs.org/teeny-request/-/teeny-request-3.11.3.tgz",
+            "integrity": "sha512-CKncqSF7sH6p4rzCgkb/z/Pcos5efl0DmolzvlqRQUNcpRIruOhY9+T1FsIlyEbfWd7MsFpodROOwHYh2BaXzw==",
+            "dev": true,
+            "requires": {
+                "https-proxy-agent": "^2.2.1",
+                "node-fetch": "^2.2.0",
+                "uuid": "^3.3.2"
+            },
+            "dependencies": {
+                "node-fetch": {
+                    "version": "2.6.0",
+                    "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz",
+                    "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==",
+                    "dev": true
+                }
+            }
+        },
+        "test-exclude": {
+            "version": "5.2.3",
+            "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-5.2.3.tgz",
+            "integrity": "sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g==",
+            "dev": true,
+            "requires": {
+                "glob": "^7.1.3",
+                "minimatch": "^3.0.4",
+                "read-pkg-up": "^4.0.0",
+                "require-main-filename": "^2.0.0"
+            }
+        },
+        "text-table": {
+            "version": "0.2.0",
+            "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+            "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=",
+            "dev": true
+        },
+        "through": {
+            "version": "2.3.8",
+            "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
+            "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=",
+            "dev": true
+        },
+        "tmp": {
+            "version": "0.1.0",
+            "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.1.0.tgz",
+            "integrity": "sha512-J7Z2K08jbGcdA1kkQpJSqLF6T0tdQqpR2pnSUXsIchbPdTI9v3e85cLW0d6WDhwuAleOV71j2xWs8qMPfK7nKw==",
+            "dev": true,
+            "requires": {
+                "rimraf": "^2.6.3"
+            }
+        },
+        "to-fast-properties": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+            "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=",
+            "dev": true
+        },
+        "to-regex-range": {
+            "version": "5.0.1",
+            "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+            "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+            "requires": {
+                "is-number": "^7.0.0"
+            }
+        },
+        "touch": {
+            "version": "0.0.3",
+            "resolved": "https://registry.npmjs.org/touch/-/touch-0.0.3.tgz",
+            "integrity": "sha1-Ua7z1ElXHU8oel2Hyci0kYGg2x0=",
+            "requires": {
+                "nopt": "~1.0.10"
+            },
+            "dependencies": {
+                "nopt": {
+                    "version": "1.0.10",
+                    "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz",
+                    "integrity": "sha1-bd0hvSoxQXuScn3Vhfim83YI6+4=",
+                    "requires": {
+                        "abbrev": "1"
+                    }
+                }
+            }
+        },
+        "tough-cookie": {
+            "version": "2.4.3",
+            "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.4.3.tgz",
+            "integrity": "sha512-Q5srk/4vDM54WJsJio3XNn6K2sCG+CQ8G5Wz6bZhRZoAe/+TxjWB/GlFAnYEbkYVlON9FMk/fE3h2RLpPXo4lQ==",
+            "requires": {
+                "psl": "^1.1.24",
+                "punycode": "^1.4.1"
+            },
+            "dependencies": {
+                "punycode": {
+                    "version": "1.4.1",
+                    "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz",
+                    "integrity": "sha1-wNWmOycYgArY4esPpSachN1BhF4="
+                }
+            }
+        },
+        "traverse": {
+            "version": "0.3.9",
+            "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
+            "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk="
+        },
+        "tslib": {
+            "version": "1.10.0",
+            "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.10.0.tgz",
+            "integrity": "sha512-qOebF53frne81cf0S9B41ByenJ3/IuH8yJKngAX35CmiZySA0khhkovshKK+jGCaMnVomla7gVlIcc3EvKPbTQ==",
+            "dev": true
+        },
+        "tunnel-agent": {
+            "version": "0.6.0",
+            "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz",
+            "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=",
+            "requires": {
+                "safe-buffer": "^5.0.1"
+            }
+        },
+        "tweetnacl": {
+            "version": "0.14.5",
+            "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
+            "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
+        },
+        "type-check": {
+            "version": "0.3.2",
+            "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz",
+            "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=",
+            "dev": true,
+            "requires": {
+                "prelude-ls": "~1.1.2"
+            }
+        },
+        "type-detect": {
+            "version": "4.0.8",
+            "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
+            "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
+            "dev": true
+        },
+        "type-fest": {
+            "version": "0.3.1",
+            "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.3.1.tgz",
+            "integrity": "sha512-cUGJnCdr4STbePCgqNFbpVNCepa+kAVohJs1sLhxzdH+gnEoOd8VhbYa7pD3zZYGiURWM2xzEII3fQcRizDkYQ==",
+            "dev": true
+        },
+        "uglify-js": {
+            "version": "3.6.9",
+            "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.9.tgz",
+            "integrity": "sha512-pcnnhaoG6RtrvHJ1dFncAe8Od6Nuy30oaJ82ts6//sGSXOP5UjBMEthiProjXmMNHOfd93sqlkztifFMcb+4yw==",
+            "dev": true,
+            "optional": true,
+            "requires": {
+                "commander": "~2.20.3",
+                "source-map": "~0.6.1"
+            },
+            "dependencies": {
+                "source-map": {
+                    "version": "0.6.1",
+                    "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
+                    "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==",
+                    "dev": true,
+                    "optional": true
+                }
+            }
+        },
+        "universalify": {
+            "version": "0.1.2",
+            "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz",
+            "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg=="
+        },
+        "uri-js": {
+            "version": "4.2.2",
+            "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz",
+            "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==",
+            "requires": {
+                "punycode": "^2.1.0"
+            }
+        },
+        "urlgrey": {
+            "version": "0.4.4",
+            "resolved": "https://registry.npmjs.org/urlgrey/-/urlgrey-0.4.4.tgz",
+            "integrity": "sha1-iS/pWWCAXoVRnxzUOJ8stMu3ZS8=",
+            "dev": true
+        },
+        "uuid": {
+            "version": "3.3.3",
+            "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.3.tgz",
+            "integrity": "sha512-pW0No1RGHgzlpHJO1nsVrHKpOEIxkGg1xB+v0ZmdNH5OAeAwzAVrCnI2/6Mtx+Uys6iaylxa+D3g4j63IKKjSQ=="
+        },
+        "validate-npm-package-license": {
+            "version": "3.0.4",
+            "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+            "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+            "dev": true,
+            "requires": {
+                "spdx-correct": "^3.0.0",
+                "spdx-expression-parse": "^3.0.0"
+            }
+        },
+        "verror": {
+            "version": "1.10.0",
+            "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz",
+            "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=",
+            "requires": {
+                "assert-plus": "^1.0.0",
+                "core-util-is": "1.0.2",
+                "extsprintf": "^1.2.0"
+            }
+        },
+        "whatwg-fetch": {
+            "version": "3.0.0",
+            "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz",
+            "integrity": "sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q=="
+        },
+        "which": {
+            "version": "1.3.1",
+            "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
+            "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
+            "dev": true,
+            "requires": {
+                "isexe": "^2.0.0"
+            }
+        },
+        "which-module": {
+            "version": "2.0.0",
+            "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
+            "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
+        },
+        "wide-align": {
+            "version": "1.1.3",
+            "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
+            "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
+            "dev": true,
+            "requires": {
+                "string-width": "^1.0.2 || 2"
+            },
+            "dependencies": {
+                "ansi-regex": {
+                    "version": "3.0.0",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
+                    "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
+                    "dev": true
+                },
+                "string-width": {
+                    "version": "2.1.1",
+                    "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
+                    "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
+                    "dev": true,
+                    "requires": {
+                        "is-fullwidth-code-point": "^2.0.0",
+                        "strip-ansi": "^4.0.0"
+                    }
+                },
+                "strip-ansi": {
+                    "version": "4.0.0",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
+                    "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
+                    "dev": true,
+                    "requires": {
+                        "ansi-regex": "^3.0.0"
+                    }
+                }
+            }
+        },
+        "word-wrap": {
+            "version": "1.2.3",
+            "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+            "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+            "dev": true
+        },
+        "wordwrap": {
+            "version": "0.0.3",
+            "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-0.0.3.tgz",
+            "integrity": "sha1-o9XabNXAvAAI03I0u68b7WMFkQc=",
+            "dev": true
+        },
+        "wrap-ansi": {
+            "version": "5.1.0",
+            "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
+            "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
+            "requires": {
+                "ansi-styles": "^3.2.0",
+                "string-width": "^3.0.0",
+                "strip-ansi": "^5.0.0"
+            },
+            "dependencies": {
+                "ansi-regex": {
+                    "version": "4.1.0",
+                    "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
+                    "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
+                },
+                "strip-ansi": {
+                    "version": "5.2.0",
+                    "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+                    "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+                    "requires": {
+                        "ansi-regex": "^4.1.0"
+                    }
+                }
+            }
+        },
+        "wrappy": {
+            "version": "1.0.2",
+            "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+            "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
+            "dev": true
+        },
+        "write": {
+            "version": "1.0.3",
+            "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz",
+            "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==",
+            "dev": true,
+            "requires": {
+                "mkdirp": "^0.5.1"
+            }
+        },
+        "write-file-atomic": {
+            "version": "2.4.3",
+            "resolved": "https://registry.npmjs.org/write-file-atomic/-/write-file-atomic-2.4.3.tgz",
+            "integrity": "sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==",
+            "dev": true,
+            "requires": {
+                "graceful-fs": "^4.1.11",
+                "imurmurhash": "^0.1.4",
+                "signal-exit": "^3.0.2"
+            }
+        },
+        "ws": {
+            "version": "6.2.1",
+            "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz",
+            "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==",
+            "requires": {
+                "async-limiter": "~1.0.0"
+            }
+        },
+        "y18n": {
+            "version": "4.0.0",
+            "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
+            "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
+        },
+        "yallist": {
+            "version": "2.1.2",
+            "resolved": "https://registry.npmjs.org/yallist/-/yallist-2.1.2.tgz",
+            "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=",
+            "dev": true
+        },
+        "yargs": {
+            "version": "13.3.0",
+            "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.0.tgz",
+            "integrity": "sha512-2eehun/8ALW8TLoIl7MVaRUrg+yCnenu8B4kBlRxj3GJGDKU1Og7sMXPNm1BYyM1DOJmTZ4YeN/Nwxv+8XJsUA==",
+            "requires": {
+                "cliui": "^5.0.0",
+                "find-up": "^3.0.0",
+                "get-caller-file": "^2.0.1",
+                "require-directory": "^2.1.1",
+                "require-main-filename": "^2.0.0",
+                "set-blocking": "^2.0.0",
+                "string-width": "^3.0.0",
+                "which-module": "^2.0.0",
+                "y18n": "^4.0.0",
+                "yargs-parser": "^13.1.1"
+            }
+        },
+        "yargs-parser": {
+            "version": "13.1.1",
+            "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
+            "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
+            "requires": {
+                "camelcase": "^5.0.0",
+                "decamelize": "^1.2.0"
+            }
+        },
+        "yargs-unparser": {
+            "version": "1.6.0",
+            "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz",
+            "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==",
+            "dev": true,
+            "requires": {
+                "flat": "^4.1.0",
+                "lodash": "^4.17.15",
+                "yargs": "^13.3.0"
+            }
+        }
+    }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..3633659
--- /dev/null
+++ b/package.json
@@ -0,0 +1,71 @@
+{
+    "name": "@adobe/wskdebug",
+    "version": "1.1.2",
+    "description": "Debugging and live development tool for Apache OpenWhisk",
+    "license": "Apache-2.0",
+    "author": "Adobe Inc.",
+    "repository": "github:adobe/wskdebug",
+    "homepage": "https://github.com/adobe/wskdebug#readme",
+    "bugs": "https://github.com/adobe/wskdebug/issues",
+    "keywords": [
+        "openwhisk",
+        "debug",
+        "debugger",
+        "serverless"
+    ],
+    "main": "index.js",
+    "bin": {
+        "wskdebug": "./cli.js"
+    },
+    "scripts": {
+        "test": "nyc mocha test/**/*.test.js",
+        "posttest": "eslint .",
+        "report-coverage": "nyc report --reporter=json && codecov -f coverage/coverage-final.json"
+    },
+    "nyc": {
+        "all": true,
+        "es-modules": false,
+        "reporter": "text",
+        "include": [
+            "[^.]*.js",
+            "src/**/*.js",
+            "agent/**/*.js"
+        ]
+    },
+    "mocha": {
+        "reporter": "mocha-multi-reporters",
+        "reporterOptions": {
+            "configFile": "test/multireporterconfig.json"
+        },
+        "file": "test/logfile.setup.js"
+    },
+    "dependencies": {
+        "fetch-retry": "^2.2.0",
+        "fs-extra": "^8.0.1",
+        "livereload": "^0.9.1",
+        "manakin": "^0.5.2",
+        "ngrok": "^3.2.5",
+        "openwhisk": "^3.19.0",
+        "yargs": "^13.2.4"
+    },
+    "devDependencies": {
+        "clone": "^2.1.2",
+        "codecov": "^3.6.1",
+        "eslint": "^5.16.0",
+        "eslint-config-problems": "^3.0.1",
+        "eslint-plugin-mocha": "^5.3.0",
+        "get-port": "^5.0.0",
+        "mocha": "^6.2.2",
+        "mocha-multi-reporters": "^1.1.7",
+        "nock": "^11.7.0",
+        "nyc": "^14.1.1",
+        "strip-ansi": "^6.0.0",
+        "tmp": "^0.1.0"
+    },
+    "publishConfig": {
+        "access": "public"
+    },
+    "engines": {
+        "node": ">=10.0.0"
+    }
+}
diff --git a/resources/wskdebug-architecture.png b/resources/wskdebug-architecture.png
new file mode 100644
index 0000000..b2784cb
--- /dev/null
+++ b/resources/wskdebug-architecture.png
Binary files differ
diff --git a/resources/wskdebug-demo.gif b/resources/wskdebug-demo.gif
new file mode 100644
index 0000000..0666a14
--- /dev/null
+++ b/resources/wskdebug-demo.gif
Binary files differ
diff --git a/resources/wskdebug-ow-meeting-screenshot.png b/resources/wskdebug-ow-meeting-screenshot.png
new file mode 100644
index 0000000..9dc7ac0
--- /dev/null
+++ b/resources/wskdebug-ow-meeting-screenshot.png
Binary files differ
diff --git a/src/debugger.js b/src/debugger.js
new file mode 100644
index 0000000..4c97952
--- /dev/null
+++ b/src/debugger.js
@@ -0,0 +1,871 @@
+/*
+ Copyright 2019 Adobe. All rights reserved.
+ This file is licensed to you 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 REPRESENTATIONS
+ OF ANY KIND, either express or implied. See the License for the specific language
+ governing permissions and limitations under the License.
+*/
+
+'use strict';
+
+const openwhisk = require("openwhisk");
+const wskprops = require('./wskprops');
+const fs = require('fs-extra');
+const OpenWhiskInvoker = require('./invoker');
+const { spawnSync } = require('child_process');
+const livereload = require('livereload');
+const http = require('http');
+const ngrok = require('ngrok');
+const url = require('url');
+const util = require('util');
+const crypto = require("crypto");
+
+async function sleep(millis) {
+    return new Promise(resolve => setTimeout(resolve, millis));
+}
+
+function getAnnotation(action, key) {
+    const a = action.annotations.find(a => a.key === key);
+    if (a) {
+        return a.value;
+    }
+}
+
+class Debugger {
+    constructor(argv) {
+        this.argv = argv;
+        this.action = argv.action;
+
+        this.wskProps = wskprops.get();
+        if (argv.ignoreCerts) {
+            this.wskProps.ignore_certs = true;
+        }
+    }
+
+    async start() {
+        await this.setupWsk();
+
+        // quick fail for missing requirements such as docker not running
+        await OpenWhiskInvoker.checkIfAvailable();
+
+        console.info(`Starting debugger for /${this.wskProps.namespace}/${this.action}`);
+
+        // get the action
+        const { action, agentAlreadyInstalled } = await this.getAction(this.action);
+
+        // local debug container
+        this.invoker = new OpenWhiskInvoker(this.action, action, this.argv, this.wskProps, this.wsk);
+
+        try {
+            // run build initially (would be required by starting container)
+            if (this.argv.onBuild) {
+                console.info("=> Build:", this.argv.onBuild);
+                spawnSync(this.argv.onBuild, {shell: true, stdio: "inherit"});
+            }
+
+            // start container - get it up fast for VSCode to connect within its 10 seconds timeout
+            await this.invoker.startContainer();
+
+            // get code and /init local container
+            if (this.argv.verbose) {
+                console.log(`Fetching action code from OpenWhisk: ${this.action}`);
+            }
+            const actionWithCode = await this.wsk.actions.get(this.action);
+            action.exec = actionWithCode.exec;
+            await this.invoker.init(actionWithCode);
+
+            // setup agent in openwhisk
+
+            // user can switch between agents (ngrok or not), hence we need to restore
+            // (better would be to track the agent + its version and avoid a restore, but that's TBD)
+            if (agentAlreadyInstalled) {
+                await this.restoreAction(this.action);
+            }
+
+            await this.installAgent(this.action, action);
+
+            if (this.argv.onStart) {
+                console.log("On start:", this.argv.onStart);
+                spawnSync(this.argv.onStart, {shell: true, stdio: "inherit"});
+            }
+
+            // start live reload (if requested)
+            await this.startSourceWatching();
+
+            console.log();
+            console.info(`Action     : ${this.action}`);
+            this.invoker.logInfo();
+            if (this.argv.condition) {
+                console.info(`Condition  : ${this.argv.condition}`);
+            }
+            console.log();
+            console.info(`Ready, waiting for activations! Use CTRL+C to exit`);
+
+            this.ready = true;
+
+        } catch (e) {
+            await this.shutdown();
+            throw e;
+        }
+    }
+
+    async run() {
+        return this.runPromise = this._run();
+    }
+
+    async _run() {
+        try {
+            this.running = true;
+
+            // main blocking loop
+            // abort if this.running is set to false
+            // from here on, user can end debugger with ctrl+c
+            while (this.running) {
+                if (this.argv.ngrok) {
+                    // agent: ngrok
+                    // simply block, ngrokServer keeps running in background
+                    await sleep(1000);
+
+                } else {
+                    // agent: concurrent
+                    // agent: non-concurrent
+                    // wait for activation, run it, complete, repeat
+                    const activation = await this.waitForActivations(this.action);
+                    if (!activation) {
+                        this.running = false;
+                        return;
+                    }
+
+                    const id = activation.$activationId;
+                    delete activation.$activationId;
+
+                    const startTime = Date.now();
+
+                    // run this activation on the local docker container
+                    // which will block if the actual debugger hits a breakpoint
+                    const result = await this.invoker.run(activation, id);
+
+                    const duration = Date.now() - startTime;
+
+                    // pass on the local result to the agent in openwhisk
+                    await this.completeActivation(this.action, id, result, duration);
+                }
+            }
+        } finally {
+            await this.shutdown();
+        }
+    }
+
+    async stop() {
+        this.running = false;
+        if (this.runPromise) {
+            // wait for the main loop to gracefully end, which will call shutdown()
+            await this.runPromise;
+        } else {
+            // someone called stop() without run()
+            await this.shutdown();
+        }
+    }
+
+    async kill() {
+        this.running = false;
+
+        await this.shutdown();
+    }
+
+    async shutdown() {
+        // only log this if we started properly
+        if (this.ready) {
+            console.log();
+            console.log();
+            console.log("Shutting down...");
+        }
+
+        // need to shutdown everything even if some fail, hence tryCatch() for each
+
+        if (this.action) {
+            await this.tryCatch(this.restoreAction(this.action));
+        }
+        await this.tryCatch(this.invoker.stop());
+
+        if (this.liveReloadServer) {
+            await this.tryCatch(() => {
+                if (this.liveReloadServer.server) {
+                    this.liveReloadServer.close();
+                } else {
+                    this.liveReloadServer.watcher.close();
+                }
+                this.liveReloadServer = null;
+            });
+        }
+
+        if (this.ngrokServer) {
+            await this.tryCatch(() => {
+                this.ngrokServer.close();
+                this.ngrokServer = null;
+            });
+        }
+        await this.tryCatch(ngrok.kill());
+
+        // only log this if we started properly
+        if (this.ready) {
+            console.log(`Done`);
+        }
+        this.ready = false;
+    }
+
+    // ------------------------------------------------< openwhisk utils >------------------
+
+    async setupWsk() {
+        if (!this.wsk) {
+            this.wsk = openwhisk(this.wskProps);
+            if (this.wskProps.namespace === undefined) {
+                // there is a strict 1-1 bijection between auth and namespace, hence auth is enough.
+                // while the openwhisk() client does not care about the namespace being set,
+                // some code here in wskdebug relies on it to be set correctly.
+                const namespaces = await this.wsk.namespaces.list();
+                if (!namespaces || namespaces.length < 1) {
+                    console.error("Error: Unknown namespace. Please specify as NAMESPACE in .wskprops.");
+                    process.exit(2);
+                }
+                if (namespaces.length > 1) {
+                    console.error("Error: OpenWhisk reports access to more than one namespace. Please specify the namespace to use as NAMESPACE in .wskprops.", namespaces);
+                    process.exit(2);
+                }
+                this.wskProps.namespace = namespaces[0];
+            }
+        }
+    }
+
+    async getWskActionWithoutCode(actionName) {
+        if (this.argv.verbose) {
+            console.log(`Getting action metadata from OpenWhisk: ${actionName}`);
+        }
+        try {
+            return await this.wsk.actions.get({name: actionName, code:false});
+        } catch (e) {
+            if (e.statusCode === 404) {
+                return null;
+            } else {
+                throw e;
+            }
+        }
+    }
+
+    async actionExists(name) {
+        try {
+            await this.wsk.actions.get({name: name, code: false});
+            return true;
+        } catch (e) {
+            return false;
+        }
+    }
+
+    async deleteActionIfExists(name) {
+        if (await this.actionExists(name)) {
+            await this.wsk.actions.delete(name);
+        }
+    }
+
+    // ------------------------------------------------< agent >------------------
+
+    getActionCopyName(name) {
+        return `${name}_wskdebug_original`;
+    }
+
+    isAgent(action) {
+        return getAnnotation(action, "wskdebug") ||
+               (getAnnotation(action, "description") || "").startsWith("wskdebug agent.");
+    }
+
+    async getAction(actionName) {
+        let action = await this.getWskActionWithoutCode(actionName);
+        if (action === null) {
+            throw new Error(`Action not found: ${actionName}`);
+        }
+
+        let agentAlreadyInstalled = false;
+
+        // check if this actoin needs to
+        if (this.isAgent(action)) {
+            // ups, action is our agent, not the original
+            // happens if a previous wskdebug was killed and could not restore before it exited
+            const backupName = this.getActionCopyName(actionName);
+
+            // check the backup action
+            try {
+                const backup = await this.wsk.actions.get(backupName);
+
+                if (this.isAgent(backup)) {
+                    // backup is also an agent (should not happen)
+                    // backup is useless, delete it
+                    // await this.wsk.actions.delete(backupName);
+                    throw new Error(`Dang! Agent is already installed and action backup is broken (${backupName}).\n\nPlease redeploy your action first before running wskdebug again.`);
+
+                } else {
+                    console.warn("Agent was already installed, but backup is still present. All good.");
+
+                    // need to look at the original action
+                    action = backup;
+                    agentAlreadyInstalled = true;
+                    this.agentInstalled = true;
+                }
+
+            } catch (e) {
+                if (e.statusCode === 404) {
+                    // backup missing
+                    throw new Error(`Dang! Agent is already installed and action backup is gone (${backupName}).\n\nPlease redeploy your action first before running wskdebug again.`);
+
+                } else {
+                    // other error
+                    throw e;
+                }
+            }
+        }
+        return {action, agentAlreadyInstalled };
+    }
+
+    async createHelperAction(actionName, file) {
+        const nodejs8 = await this.openwhiskSupports("nodejs8");
+
+        await this.wsk.actions.update({
+            name: actionName,
+            action: {
+                exec: {
+                    kind: nodejs8 ? "nodejs:default" : "blackbox",
+                    image: nodejs8 ? undefined : "openwhisk/action-nodejs-v8",
+                    code: fs.readFileSync(file, {encoding: 'utf8'})
+                },
+                limits: {
+                    timeout: (this.argv.agentTimeout || 300) * 1000
+                },
+                annotations: [
+                    { key: "description", value: `wskdebug agent helper. temporarily installed.` }
+                ]
+            }
+        });
+    }
+
+    async installAgent(actionName, action) {
+        this.agentInstalled = true;
+
+        const agentDir = `${__dirname}/../agent`;
+        let agentName;
+
+        // choose the right agent implementation
+        let code;
+        if (this.argv.ngrok) {
+            // user manually requested ngrok
+            if (this.argv.verbose) {
+                console.log("Setting up ngrok", this.argv.ngrokRegion ? `(region: ${this.argv.ngrokRegion})` : "");
+            }
+
+            // 1. start local server on random port
+            this.ngrokServer = http.createServer(this.ngrokHandler.bind(this));
+            // turn server.listen() into promise so we can await
+            const listen = util.promisify( this.ngrokServer.listen.bind(this.ngrokServer) );
+            await listen(0, '127.0.0.1');
+
+            // 2. start ngrok tunnel connected to that port
+            this.ngrokServerPort = this.ngrokServer.address().port;
+
+            // create a unique authorization token that we check on our local instance later
+            // this adds extra protection on top of the uniquely generated ngrok subdomain (e.g. a01ae275.ngrok.io)
+            this.ngrokAuth = crypto.randomBytes(32).toString("hex");
+            const ngrokUrl = await ngrok.connect({
+                addr: this.ngrokServerPort,
+                region: this.argv.ngrokRegion
+            });
+
+            // 3. pass on public ngrok url to agent
+            action.parameters.push({
+                key: "$ngrokUrl",
+                value: url.parse(ngrokUrl).host
+            });
+            action.parameters.push({
+                key: "$ngrokAuth",
+                value: this.ngrokAuth
+            });
+
+            console.log(`Ngrok forwarding: ${ngrokUrl} => http://localhost:${this.ngrokServerPort} (auth: ${this.ngrokAuth})`);
+
+            // agent using ngrok for forwarding
+            agentName = "ngrok";
+            code = fs.readFileSync(`${agentDir}/agent-ngrok.js`, {encoding: 'utf8'});
+
+        } else {
+            this.concurrency = await this.openwhiskSupports("concurrency");
+            if (this.concurrency) {
+                // normal fast agent using concurrent node.js actions
+                agentName = "concurrency";
+                code = fs.readFileSync(`${agentDir}/agent-concurrency.js`, {encoding: 'utf8'});
+
+            } else {
+                console.log("This OpenWhisk does not support action concurrency. Debugging will be a bit slower. Consider using '--ngrok' which might be a faster option.");
+
+                agentName = "polling activation db";
+
+                // this needs 2 helper actions in addition to the agent in place of the action
+                await this.createHelperAction(`${actionName}_wskdebug_invoked`,   `${agentDir}/echo.js`);
+                await this.createHelperAction(`${actionName}_wskdebug_completed`, `${agentDir}/echo.js`);
+
+                code = fs.readFileSync(`${agentDir}/agent-activationdb.js`, {encoding: 'utf8'});
+                // rewrite the code to pass config (we want to avoid fiddling with default params of the action)
+                if (await this.openwhiskSupports("activationListFilterOnlyBasename")) {
+                    code = code.replace("const activationListFilterOnlyBasename = false;", "const activationListFilterOnlyBasename = true;");
+                }
+            }
+        }
+
+        const backupName = this.getActionCopyName(actionName);
+
+        if (this.argv.verbose) {
+            console.log(`Installing agent in OpenWhisk (${agentName})...`);
+        }
+
+        // create copy
+        await this.wsk.actions.update({
+            name: backupName,
+            action: action
+        });
+
+        if (this.argv.verbose) {
+            console.log(`Original action backed up at ${backupName}.`);
+        }
+
+        // this is to support older openwhisks for which nodejs:default is less than version 8
+        const nodejs8 = await this.openwhiskSupports("nodejs8");
+
+        if (this.argv.condition) {
+            action.parameters.push({
+                key: "$condition",
+                value: this.argv.condition
+            });
+        }
+
+        // overwrite action with agent
+        await this.wsk.actions.update({
+            name: actionName,
+            action: {
+                exec: {
+                    kind: nodejs8 ? "nodejs:default" : "blackbox",
+                    image: nodejs8 ? undefined : "openwhisk/action-nodejs-v8",
+                    code: code
+                },
+                limits: {
+                    timeout: (this.argv.agentTimeout || 300) * 1000,
+                    concurrency: this.concurrency ? 200: 1
+                },
+                annotations: [
+                    ...action.annotations,
+                    { key: "provide-api-key", value: true },
+                    { key: "wskdebug", value: true },
+                    { key: "description", value: `wskdebug agent. temporarily installed over original action. original action backup at ${backupName}.` }
+                ],
+                parameters: action.parameters
+            }
+        });
+
+        if (this.argv.verbose) {
+            console.log(`Agent installed.`);
+        }
+    }
+
+    async restoreAction(actionName) {
+        if (this.agentInstalled) {
+            if (this.argv.verbose) {
+                console.log();
+                console.log(`Restoring action`);
+            }
+
+            const copy = this.getActionCopyName(actionName);
+
+            try {
+                const original = await this.wsk.actions.get(copy);
+
+                // copy the backup (copy) to the regular action
+                await this.wsk.actions.update({
+                    name: actionName,
+                    action: original
+                });
+
+                // remove the backup
+                await this.wsk.actions.delete(copy);
+
+                // remove any helpers if they exist
+                await this.deleteActionIfExists(`${actionName}_wskdebug_invoked`);
+                await this.deleteActionIfExists(`${actionName}_wskdebug_completed`);
+
+            } catch (e) {
+                console.error("Error while restoring original action:", e);
+            }
+        }
+    }
+
+    // ------------------------------------------------< ngrok >------------------
+
+    // local http server retrieving forwards from the ngrok agent, running them
+    // as a blocking local invocation and then returning the activation result back
+    ngrokHandler(req, res) {
+        // check authorization against our unique token
+        const authHeader = req.headers.authorization;
+        if (authHeader !== this.ngrokAuth) {
+            res.statusCode = 401;
+            res.end();
+            return;
+        }
+
+        if (req.method === 'POST') {
+            // agent POSTs arguments as json body
+            let body = '';
+            // collect full request body first
+            req.on('data', chunk => {
+                body += chunk.toString();
+            });
+            req.on('end', async () => {
+                try {
+                    const params = JSON.parse(body);
+                    const id = params.$activationId;
+                    delete params.$activationId;
+
+                    if (this.argv.verbose) {
+                        console.log();
+                        console.info(`Activation: ${id}`);
+                        console.log(params);
+                    } else {
+                        console.info(`Activation: ${id}`);
+                    }
+
+                    const startTime = Date.now();
+
+                    const result = await this.invoker.run(params, id);
+
+                    const duration = Date.now() - startTime;
+                    console.info(`Completed activation ${id} in ${duration/1000.0} sec`);
+                    if (this.argv.verbose) {
+                        console.log(result);
+                    }
+
+                    res.statusCode = 200;
+                    res.setHeader("Content-Type", "application/json");
+                    res.end(JSON.stringify(result));
+
+                } catch (e) {
+                    console.error(e);
+                    res.statusCode = 400;
+                    res.end();
+                }
+            });
+        } else {
+            res.statusCode = 404;
+            res.end();
+        }
+    }
+
+    // ------------------------------------------------< polling >------------------
+
+    async waitForActivations(actionName) {
+        this.activationsSeen = this.activationsSeen || {};
+
+        // secondary loop to get next activation
+        // the $waitForActivation agent activation will block, but only until
+        // it times out, hence we need to retry when it fails
+        while (this.running) {
+            if (this.argv.verbose) {
+                process.stdout.write(".");
+            }
+            try {
+                let activation;
+                if (this.concurrency) {
+                    // invoke - blocking for up to 1 minute
+                    activation = await this.wsk.actions.invoke({
+                        name: actionName,
+                        params: {
+                            $waitForActivation: true
+                        },
+                        blocking: true
+                    });
+
+                } else {
+                    // poll for the newest activation
+                    const since = Date.now();
+
+                    // older openwhisk only allows the name of an action when filtering activations
+                    // newer openwhisk versions want package/name
+                    let name = actionName;
+                    if (await this.openwhiskSupports("activationListFilterOnlyBasename")) {
+                        if (actionName.includes("/")) {
+                            name = actionName.substring(actionName.lastIndexOf("/") + 1);
+                        }
+                    }
+
+                    while (true) {
+                        if (this.argv.verbose) {
+                            process.stdout.write(".");
+                        }
+
+                        const activations = await this.wsk.activations.list({
+                            name: `${name}_wskdebug_invoked`,
+                            since: since,
+                            limit: 1, // get the most recent one only
+                            docs: true // include results
+                        });
+
+                        if (activations && activations.length >= 1) {
+                            const a = activations[0];
+                            if (a.response && a.response.result && !this.activationsSeen[a.activationId]) {
+                                activation = a;
+                                break;
+                            }
+                        }
+
+                        // need to limit load on openwhisk (activation list)
+                        await sleep(1000);
+                    }
+                }
+
+                // check for successful response with a new activation
+                if (activation && activation.response) {
+                    const params = activation.response.result;
+
+                    // mark this as seen so we don't reinvoke it
+                    this.activationsSeen[activation.activationId] = true;
+
+                    if (this.argv.verbose) {
+                        console.log();
+                        console.info(`Activation: ${params.$activationId}`);
+                        console.log(params);
+                    } else {
+                        console.info(`Activation: ${params.$activationId}`);
+                    }
+                    return params;
+
+                } else if (activation && activation.activationId) {
+                    // ignore this and retry.
+                    // usually means the action did not respond within one second,
+                    // which in turn is unlikely for the agent who should exit itself
+                    // after 50 seconds, so can only happen if there was some delay
+                    // outside the action itself
+
+                } else {
+                    // unexpected, just log and retry
+                    console.log("Unexpected empty response while waiting for new activations:", activation);
+                }
+
+            } catch (e) {
+                // look for special error codes from agent
+                const errorCode = this.getActivationError(e).code;
+                // 42 => retry
+                if (errorCode === 42) {
+                    // do nothing
+                } else if (errorCode === 43) {
+                    // 43 => graceful shutdown (for unit tests)
+                    console.log("Graceful shutdown requested by agent (only for unit tests)");
+                    return null;
+                } else {
+                    // otherwise log error and abort
+                    console.error();
+                    console.error("Unexpected error while polling agent for activation:");
+                    console.dir(e, { depth: null });
+                    throw new Error("Unexpected error while polling agent for activation.");
+                }
+            }
+
+            // some small wait to avoid too many requests in case things run amok
+            await sleep(100);
+        }
+    }
+
+    getActivationError(e) {
+        if (e.error && e.error.response && e.error.response.result && e.error.response.result.error) {
+            return e.error.response.result.error;
+        }
+        return {};
+    }
+
+    async completeActivation(actionName, activationId, result, duration) {
+        console.info(`Completed activation ${activationId} in ${duration/1000.0} sec`);
+        if (this.argv.verbose) {
+            console.log(result);
+        }
+
+        try {
+            result.$activationId = activationId;
+            await this.wsk.actions.invoke({
+                name: this.concurrency ? actionName : `${actionName}_wskdebug_completed`,
+                params: result,
+                blocking: true
+            });
+        } catch (e) {
+            // look for special error codes from agent
+            const errorCode = this.getActivationError(e).code;
+            // 42 => retry
+            if (errorCode === 42) {
+                // do nothing
+            } else if (errorCode === 43) {
+                // 43 => graceful shutdown (for unit tests)
+                console.log("Graceful shutdown requested by agent (only for unit tests)");
+                this.running = false;
+            } else {
+                console.error("Unexpected error while completing activation:", e);
+            }
+        }
+    }
+
+    // ----------------------------------------< openwhisk feature detection >-----------------
+
+    async getOpenWhiskVersion() {
+        if (this.openwhiskVersion === undefined) {
+            try {
+                const json = await this.wsk.actions.client.request("GET", "/api/v1");
+                if (json && typeof json.build === "string") {
+                    this.openwhiskVersion = json.build;
+                } else {
+                    this.openwhiskVersion = null;
+                }
+            } catch (e) {
+                console.warn("Could not retrieve OpenWhisk version:", e.message);
+                this.openwhiskVersion = null;
+            }
+        }
+        return this.openwhiskVersion;
+    }
+
+    async openwhiskSupports(feature) {
+        const FEATURES = {
+            // guesstimated
+            activationListFilterOnlyBasename: v => v.startsWith("2018") || v.startsWith("2017"),
+            // hack
+            nodejs8: v => !v.startsWith("2018") && !v.startsWith("2017"),
+            concurrency: async (_, wsk) => {
+                // check swagger api docs instead of version to see if concurrency is supported
+                try {
+                    const swagger = await wsk.actions.client.request("GET", "/api/v1/api-docs");
+
+                    if (swagger && swagger.definitions && swagger.definitions.ActionLimits && swagger.definitions.ActionLimits.properties) {
+                        return swagger.definitions.ActionLimits.properties.concurrency;
+                    }
+                } catch (e) {
+                    console.warn('Could not read /api/v1/api-docs, setting max action concurrency to 1')
+                    return false;
+                }
+            }
+        };
+        const checker = FEATURES[feature];
+        if (checker) {
+            return checker(await this.getOpenWhiskVersion(), this.wsk);
+        } else {
+            throw new Error("Unknown feature " + feature);
+        }
+    }
+
+    // ------------------------------------------------< source watching >-----------------
+
+    async startSourceWatching() {
+        const watch = this.argv.watch || process.cwd();
+        if (watch &&
+            // each of these triggers listening
+            (   this.argv.livereload
+             || this.argv.onBuild
+             || this.argv.onChange
+             || this.argv.invokeParams
+             || this.argv.invokeAction )
+        ) {
+            this.liveReloadServer = livereload.createServer({
+                port: this.argv.livereloadPort,
+                noListen: !this.argv.livereload,
+                exclusions: [this.argv.buildPath, "node_modules/**"],
+                exts: this.argv.watchExts || ["json", "js", "ts", "coffee", "py", "rb", "erb", "go", "java", "scala", "php", "swift", "rs", "cs", "bal", "php", "php5"],
+                extraExts: []
+            });
+            this.liveReloadServer.watch(watch);
+
+            // overwrite function to get notified on changes
+            const refresh = this.liveReloadServer.refresh;
+            const argv = this.argv;
+            const wsk = this.wsk;
+            this.liveReloadServer.refresh = function(filepath) {
+                try {
+                    let result = [];
+
+                    if (argv.verbose) {
+                        console.log("File modified:", filepath);
+                    }
+
+                    // call original function if we are listening
+                    if (argv.livereload) {
+                        result = refresh.call(this, filepath);
+                    }
+
+                    // run build command before invoke triggers below
+                    if (argv.onBuild) {
+                        console.info("=> Build:", argv.onBuild);
+                        spawnSync(argv.onBuild, {shell: true, stdio: "inherit"});
+                    }
+
+                    // run shell command
+                    if (argv.onChange) {
+                        console.info("=> Run:", argv.onChange);
+                        spawnSync(argv.onChange, {shell: true, stdio: "inherit"});
+                    }
+
+                    // action invoke
+                    if (argv.invokeParams || argv.invokeAction) {
+                        let json = {};
+                        if (argv.invokeParams) {
+                            if (argv.invokeParams.trim().startsWith("{")) {
+                                json = JSON.parse(argv.invokeParams);
+                            } else {
+                                json = JSON.parse(fs.readFileSync(argv.invokeParams, {encoding: 'utf8'}));
+                            }
+                        }
+                        const action = argv.invokeAction || argv.action;
+                        wsk.actions.invoke({
+                            name: action,
+                            params: json
+                        }).then(response => {
+                            console.info(`=> Invoked action ${action} with params ${argv.invokeParams}: ${response.activationId}`);
+                        }).catch(err => {
+                            console.error("Error invoking action:", err);
+                        });
+                    }
+
+                    return result;
+                } catch (e) {
+                    console.error(e);
+                }
+            };
+
+            if (this.argv.livereload) {
+                console.info(`LiveReload enabled for ${watch} on port ${this.liveReloadServer.config.port}`);
+            }
+        }
+    }
+
+    // ------------------------------------------------< utils >-----------------
+
+    async tryCatch(task, message="Error during shutdown:") {
+        try {
+            if (typeof task === "function") {
+                task();
+            } else {
+                await task;
+            }
+        } catch (e) {
+            console.log(e);
+            if (this.argv.verbose) {
+                console.error(message);
+                console.error(e);
+            } else {
+                console.error(message, e.message);
+            }
+        }
+    }
+
+}
+
+module.exports = Debugger;
\ No newline at end of file
diff --git a/src/invoker.js b/src/invoker.js
new file mode 100644
index 0000000..6acdaab
--- /dev/null
+++ b/src/invoker.js
@@ -0,0 +1,347 @@
+/*
+ Copyright 2019 Adobe. All rights reserved.
+ This file is licensed to you 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 REPRESENTATIONS
+ OF ANY KIND, either express or implied. See the License for the specific language
+ governing permissions and limitations under the License.
+*/
+
+'use strict';
+
+const { spawn, execSync } = require('child_process');
+const fetch = require('fetch-retry');
+const kinds = require('./kinds/kinds');
+const path = require('path');
+
+const RUNTIME_PORT = 8080;
+const INIT_RETRY_DELAY_MS = 100;
+
+// https://github.com/apache/incubator-openwhisk/blob/master/docs/reference.md#system-limits
+const OPENWHISK_DEFAULTS = {
+    timeout: 60*1000,
+    memory: 256
+};
+
+function execute(cmd, options, verbose) {
+    cmd = cmd.replace(/\s+/g, ' ');
+    if (verbose) {
+        console.log(cmd);
+    }
+    const result = execSync(cmd, options);
+    if (result) {
+        return result.toString().trim();
+    } else {
+        return '';
+    }
+}
+
+// if value is a function, invoke it with args, otherwise return it as object
+// if value is undefined, will return undefined
+function resolveValue(value, ...args) {
+    if (typeof value === "function") {
+        return value(...args);
+    } else {
+        return value;
+    }
+}
+
+class OpenWhiskInvoker {
+    constructor(actionName, action, options, wskProps, wsk) {
+        this.actionName = actionName;
+        this.action = action;
+
+        this.kind = options.kind;
+        this.image = options.image;
+        this.port = options.port;
+        this.internalPort = options.internalPort;
+        this.command = options.command;
+        this.dockerArgs = options.dockerArgs;
+        this.verbose = options.verbose;
+
+        // the build path can be separate, if not, same as the source/watch path
+        this.sourcePath = options.buildPath || options.sourcePath;
+        if (this.sourcePath) {
+            this.sourceDir = process.cwd();
+            // ensure sourcePath is relative to sourceDir
+            this.sourceFile = path.relative(this.sourceDir, this.sourcePath);
+        }
+
+        this.main = options.main;
+
+        this.wskProps = wskProps;
+        this.wsk = wsk;
+
+        this.containerName = this.asContainerName(`wskdebug-${this.action.name}-${Date.now()}`);
+    }
+
+    static async checkIfAvailable() {
+        try {
+            execute("docker info", {stdio: 'ignore'});
+        } catch (e) {
+            throw new Error("Docker not running on local system. A local docker environment is required for the debugger.")
+        }
+    }
+
+    async getImageForKind(kind) {
+        try {
+            const owSystemInfo = await this.wsk.actions.client.request("GET", "/");
+            if (owSystemInfo.runtimes) {
+                // transform result into a nice dictionary kind => image
+                const runtimes = {};
+                for (const set of Object.values(owSystemInfo.runtimes)) {
+                    for (const entry of set) {
+                        let image = entry.image;
+                        // fix for Adobe I/O Runtime reporting incorrect image prefixes
+                        image = image.replace("bladerunner/", "adobeapiplatform/");
+                        runtimes[entry.kind] = image;
+                    }
+                }
+                return runtimes[kind];
+
+            } else if (this.verbose) {
+                console.warn("Could not retrieve runtime images from OpenWhisk, using default image list.");
+            }
+
+        } catch (e) {
+            if (this.verbose) {
+                console.warn("Could not retrieve runtime images from OpenWhisk, using default image list.", e.message);
+            }
+        }
+        return kinds.images[kind];
+    }
+
+    async startContainer() {
+        const action = this.action;
+
+        // this must run after initial build was kicked off in Debugger.startSourceWatching()
+        // so that built files are present
+
+        // kind and image
+
+        // precendence:
+        // 1. arguments (this.image)
+        // 2. action (action.exec.image)
+        // 3. defaults (kinds.images[kind])
+
+        const kind = this.kind || action.exec.kind;
+
+        if (kind === "blackbox") {
+            throw new Error("Action is of kind 'blackbox', must specify kind using `--kind` argument.");
+        }
+
+        // const runtime = kinds[kind] || {};
+        this.image = this.image || action.exec.image || await this.getImageForKind(kind);
+
+        if (!this.image) {
+            throw new Error(`Unknown kind: ${kind}. You might want to specify --image.`);
+        }
+
+        // debugging instructions
+        this.debugKind = kinds.debugKinds[kind] || kind.split(":")[0];
+        try {
+            this.debug = require(`${__dirname}/kinds/${this.debugKind}/${this.debugKind}`);
+        } catch (e) {
+            if (this.verbose) {
+                console.error(`Cannot find debug info for kind ${this.debugKind}:`, e.message);
+            }
+            this.debug = {};
+        }
+
+        this.debug.internalPort = this.internalPort                      || resolveValue(this.debug.port, this);
+        this.debug.port         = this.port         || this.internalPort || resolveValue(this.debug.port, this);
+
+        // ------------------------
+
+        this.debug.command = this.command || resolveValue(this.debug.command, this);
+
+        if (!this.debug.port) {
+            throw new Error(`No debug port known for kind: ${kind}. Please specify --port.`);
+        }
+        if (!this.debug.internalPort) {
+            throw new Error(`No debug port known for kind: ${kind}. Please specify --internal-port.`);
+        }
+        if (!this.debug.command) {
+            throw new Error(`No debug command known for kind: ${kind}. Please specify --command.`);
+        }
+
+        // limits
+        const memory = (action.limits.memory || OPENWHISK_DEFAULTS.memory) * 1024 * 1024;
+
+        // source mounting
+        if (this.sourcePath) {
+            if (!this.debug.mountAction) {
+                console.warn(`Warning: Sorry, mounting sources not yet supported for: ${kind}.`);
+                this.sourcePath = undefined;
+            }
+        }
+
+        const dockerArgsFromKind = resolveValue(this.debug.dockerArgs, this) || "";
+        const dockerArgsFromUser = this.dockerArgs || "";
+
+        let showDockerRunOutput = this.verbose;
+
+        try {
+            execute(`docker inspect --type=image ${this.image} 2> /dev/null`);
+        } catch (e) {
+            // make sure the user can see the image download process as part of docker run
+            showDockerRunOutput = true;
+            console.log(`
++------------------------------------------------------------------------------------------+
+| Docker image must be downloaded: ${this.image}
+|                                                                                          |
+| Note: If you debug in VS Code and it fails with "Cannot connect to runtime process"      |
+| due to a timeout, run this command once:                                                 |
+|                                                                                          |
+|     docker pull ${this.image}
+|                                                                                          |
+| Alternatively set a higher 'timeout' in the launch configuration, such as 60000 (1 min). |
++------------------------------------------------------------------------------------------+
+`);
+        }
+
+        if (this.verbose) {
+            console.log(`Starting local debug container ${this.name()}`);
+        }
+
+        execute(
+            `docker run
+                -d
+                --name ${this.name()}
+                --rm
+                -m ${memory}
+                -p ${RUNTIME_PORT}
+                -p ${this.debug.port}:${this.debug.internalPort}
+                ${dockerArgsFromKind}
+                ${dockerArgsFromUser}
+                ${this.image}
+                ${this.debug.command}
+            `,
+            // live stream view for docker image download output
+            { stdio: showDockerRunOutput ? "inherit" : null },
+            this.verbose
+        );
+
+        this.containerRunning = true;
+
+        spawn("docker", ["logs", "-t", "-f", this.name()], {
+            stdio: [
+                "inherit", // stdin
+                global.mochaLogFile || "inherit", // stdout
+                global.mochaLogFile || "inherit"  // stderr
+            ]
+        });
+    }
+
+    async logInfo() {
+        if (this.sourcePath) {
+            console.info(`Sources    : ${this.sourcePath}`);
+        }
+        console.info(`Image      : ${this.image}`);
+        console.info(`Debug type : ${this.debugKind}`);
+        console.info(`Debug port : localhost:${this.debug.port}`)
+    }
+
+    async init(actionWithCode) {
+        let action;
+        if (this.sourcePath && this.debug.mountAction) {
+            action = resolveValue(this.debug.mountAction, this);
+
+            if (this.verbose) {
+                console.log(`Mounting sources onto local debug container: ${this.sourcePath}`);
+            }
+        } else {
+            if (this.verbose) {
+                console.log(`Pushing action code to local debug container: ${this.action.name}`);
+            }
+            action = {
+                binary: actionWithCode.exec.binary,
+                main:   actionWithCode.exec.main || "main",
+                code:   actionWithCode.exec.code,
+            };
+        }
+
+        const response = await fetch(`${this.url()}/init`, {
+            method: "POST",
+            headers: {
+                'Content-Type': 'application/json'
+            },
+            body: JSON.stringify({
+                value: action
+            }),
+            retries: this.timeout() / INIT_RETRY_DELAY_MS,
+            retryDelay: INIT_RETRY_DELAY_MS
+        });
+
+        if (response.status === 502) {
+            const body = await response.json();
+            throw new Error("Could not initialize action code on local debug container:\n\n" + body.error);
+        }
+    }
+
+    async run(args, activationId) {
+        const response = await fetch(`${this.url()}/run`, {
+            method: "POST",
+            headers: {
+                'Content-Type': 'application/json'
+            },
+            body: JSON.stringify({
+                value: args,
+
+                api_host        : this.wskProps.apihost,
+                api_key         : this.wskProps.api_key,
+                namespace       : this.wskProps.namespace,
+                action_name     : `/${this.wskProps.namespace}/${this.actionName}`,
+                activation_id   : activationId,
+                deadline        : `${Date.now() + this.timeout()}`,
+                allow_concurrent: "true"
+            })
+        });
+
+        return response.json();
+    }
+
+    async stop() {
+        if (this.containerRunning) {
+            if (this.verbose) {
+                console.log("Stopping local debug container");
+            }
+            execute(`docker kill ${this.name()}`);
+        }
+    }
+
+    name() {
+        return this.containerName;
+    }
+
+    url() {
+        if (!this.containerURL) {
+            // ask docker for the exposed IP and port of the RUNTIME_PORT on the container
+            const host = execute(`docker port ${this.name()} ${RUNTIME_PORT}`);
+            this.containerURL = `http://${host}`;
+        }
+        return this.containerURL;
+    }
+
+    timeout() {
+        return this.action.limits.timeout || OPENWHISK_DEFAULTS.timeout;
+    }
+
+    asContainerName(name) {
+        // docker container names are restricted to [a-zA-Z0-9][a-zA-Z0-9_.-]*
+
+        // 1. replace special characters with dash
+        name = name.replace(/[^a-zA-Z0-9_.-]+/g, '-');
+        // 2. leading character is more limited
+        name = name.replace(/^[^a-zA-Z0-9]+/g, '');
+        // 3. (nice to have) remove trailing special chars
+        name = name.replace(/[^a-zA-Z0-9]+$/g, '');
+
+        return name;
+    }
+}
+
+module.exports = OpenWhiskInvoker;
\ No newline at end of file
diff --git a/src/kinds/kinds.js b/src/kinds/kinds.js
new file mode 100644
index 0000000..297a226
--- /dev/null
+++ b/src/kinds/kinds.js
@@ -0,0 +1,47 @@
+/*
+ Copyright 2019 Adobe. All rights reserved.
+ This file is licensed to you 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 REPRESENTATIONS
+ OF ANY KIND, either express or implied. See the License for the specific language
+ governing permissions and limitations under the License.
+*/
+
+'use strict';
+
+module.exports = {
+    //  map to a shared debug kind, otherwise uses the kind name itself
+    debugKinds: {
+        // "nodejs:6": "nodejsLegacy"
+    },
+    // fallback in case the openwhisk api doesn't work or doesn't return runtimes
+    // list taken from: https://github.com/apache/incubator-openwhisk/blob/master/ansible/files/runtimes.json
+    images: {
+        "nodejs": "openwhisk/action-nodejs-v10:latest", // deprecated (no version)
+        "nodejs:default": "openwhisk/action-nodejs-v10:latest",
+        "nodejs:6": "openwhisk/nodejs6action:latest",
+        "nodejs:8": "openwhisk/action-nodejs-v8:latest",
+        "nodejs:10": "openwhisk/action-nodejs-v10:latest",
+        "nodejs:12": "openwhisk/action-nodejs-v12:latest",
+        "python": "openwhisk/python2action:latest", // deprecated (no version)
+        "python:2": "openwhisk/python2action:latest",
+        "python:3": "openwhisk/python3action:latest",
+        "swift": "openwhisk/action-swift-v4.1:latest", // deprecated (no version)
+        "swift:3": "openwhisk/swift3action:latest", // deprecated, but still available
+        "swift:3.1.1": "openwhisk/action-swift-v3.1.1:latest",
+        "swift:4.1": "openwhisk/action-swift-v4.1:latest",
+        "swift:4.2": "openwhisk/action-swift-v4.2:latest",
+        "java": "openwhisk/java8action:latest",
+        "php:7.1": "openwhisk/action-php-v7.1:latest",
+        "php:7.2": "openwhisk/action-php-v7.2:latest",
+        "php:7.3": "openwhisk/action-php-v7.3:latest",
+        "ruby:2.5": "openwhisk/action-ruby-v2.5:latest",
+        "go:1.11": "openwhisk/actionloop-golang-v1.11:latest",
+        "dotnet:2.2": "openwhisk/action-dotnet-v2.2:latest",
+        "ballerina:0.990": "openwhisk/action-ballerina-v0.990.2:latest",
+        "native": "openwhisk/dockerskeleton:latest"
+    }
+}
diff --git a/src/kinds/nodejs/mount-plain.js b/src/kinds/nodejs/mount-plain.js
new file mode 100644
index 0000000..62535d2
--- /dev/null
+++ b/src/kinds/nodejs/mount-plain.js
@@ -0,0 +1,49 @@
+/*
+ Copyright 2019 Adobe. All rights reserved.
+ This file is licensed to you 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 REPRESENTATIONS
+ OF ANY KIND, either express or implied. See the License for the specific language
+ governing permissions and limitations under the License.
+*/
+
+/* eslint-disable strict */
+
+const fs = require('fs');
+
+// Variables will be replaced before the code is loaded
+
+// path to actual action sources
+const path = "$$sourcePath$$";
+// main function
+const mainFn = "$$main$$";
+// name of module file (for helpful errors)
+const sourceFile = "$$sourceFile$$";
+
+function load(path) {
+    const code = fs.readFileSync(path, {encoding: 'utf8'});
+
+    // eslint-disable-next-line no-eval
+    const fn = eval('(function(){' + code + '\n; return ' + mainFn + '})()\n //@ sourceURL=' + path);
+
+    if (typeof fn !== 'function') {
+        throw `'${mainFn}' is not a function in '${sourceFile}'. Specify the right function in wskdebug using --main.`;
+    }
+
+    return fn;
+}
+
+// load and validate on /init for quick feedback
+load(path);
+
+// eslint-disable-next-line no-unused-vars
+function main(args) { // lgtm [js/unused-local-variable]
+    // load code again on every new invocation
+    const actionMain = load(path);
+
+    // invoke
+    return actionMain(args);
+}
\ No newline at end of file
diff --git a/src/kinds/nodejs/mount-require.js b/src/kinds/nodejs/mount-require.js
new file mode 100644
index 0000000..60f7bbe
--- /dev/null
+++ b/src/kinds/nodejs/mount-require.js
@@ -0,0 +1,41 @@
+/*
+ Copyright 2019 Adobe. All rights reserved.
+ This file is licensed to you 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 REPRESENTATIONS
+ OF ANY KIND, either express or implied. See the License for the specific language
+ governing permissions and limitations under the License.
+*/
+
+/* eslint-disable strict */
+
+// Variables will be replaced before the code is loaded
+
+// path to actual action sources
+const path = "$$sourcePath$$";
+// main function
+const mainFn = "$$main$$";
+// name of module file (for helpful errors)
+const sourceFile = "$$sourceFile$$";
+
+// load and validate on /init for quick feedback
+try {
+    require(path);
+} catch (e) {
+    throw `Cannot load module '${sourceFile}': ${e}`;
+}
+if (typeof require(path)[mainFn] !== "function") {
+    throw `'${mainFn}' is not a function in '${sourceFile}'. Specify the right function in wskdebug using --main.`;
+}
+
+// eslint-disable-next-line no-unused-vars
+function main(args) { // lgtm [js/unused-local-variable]
+    // force reload of mounted action on every invocation
+    delete require.cache[require.resolve(path)];
+
+    // require and invoke main function
+    return require(path)[mainFn](args);
+}
\ No newline at end of file
diff --git a/src/kinds/nodejs/nodejs.js b/src/kinds/nodejs/nodejs.js
new file mode 100644
index 0000000..51fd1b8
--- /dev/null
+++ b/src/kinds/nodejs/nodejs.js
@@ -0,0 +1,72 @@
+/*
+ Copyright 2019 Adobe. All rights reserved.
+ This file is licensed to you 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 REPRESENTATIONS
+ OF ANY KIND, either express or implied. See the License for the specific language
+ governing permissions and limitations under the License.
+*/
+
+'use strict';
+
+const fs = require('fs-extra');
+const path = require('path');
+
+// path inside docker container where action code is mounted
+const CODE_MOUNT = "/code";
+
+module.exports = {
+    description: "Node.js V8 inspect debugger on port 9229. Supports source mount",
+
+    // additional debug port to expose
+    port: 9229,
+
+    // modified docker image command/entrypoint to enable debugging
+    command: function(invoker) {
+        return `node --expose-gc --inspect=0.0.0.0:${invoker.debug.internalPort} app.js`
+    },
+
+    // return extra docker arguments such as mounting the source path
+    dockerArgs: function(invoker) {
+        if (invoker.sourceDir) {
+            if (!invoker.sourceFile) {
+                throw new Error("[source-path] or --build-path must point to the action javascript source file, it cannot be a folder.");
+            }
+
+            return `-v "${invoker.sourceDir}:${CODE_MOUNT}"`;
+        }
+    },
+
+    // return action for /init that mounts the sources specified by invoker.sourcePath
+    mountAction: function(invoker) {
+        // bridge that mounts local source path
+
+        // test if code uses commonjs require()
+        const isCommonJS = /(\s|=)require\(\s*['"`]/.test(fs.readFileSync(invoker.sourcePath));
+
+        // is it a require() based action or a plain JS one?
+        const bridgeSource = isCommonJS ? "mount-require.js" : "mount-plain.js";
+
+        let code = fs.readFileSync(`${__dirname}/${bridgeSource}`, {encoding: 'utf8'});
+        let sourceFile = invoker.sourceFile.toString();
+
+        // On Windows, the path set on the cli would typically be in windows format,
+        // but the nodejs container is Unix and requires Unix paths
+        if (path.sep !== path.posix.sep) {
+            sourceFile = sourceFile.split(path.sep).join(path.posix.sep);
+        }
+
+        code = code.replace("$$main$$",        invoker.main || "main");
+        code = code.replace("$$sourcePath$$", `${CODE_MOUNT}/${sourceFile}`);
+        code = code.replace("$$sourceFile$$",  sourceFile);
+
+        return {
+            binary: false,
+            main:   "main",
+            code:   code,
+        };
+    }
+}
diff --git a/src/wskprops.js b/src/wskprops.js
new file mode 100644
index 0000000..251ed3e
--- /dev/null
+++ b/src/wskprops.js
@@ -0,0 +1,71 @@
+/*
+ Copyright 2019 Adobe. All rights reserved.
+ This file is licensed to you 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 REPRESENTATIONS
+ OF ANY KIND, either express or implied. See the License for the specific language
+ governing permissions and limitations under the License.
+*/
+
+// based on from serverless-openwhisk, MIT licensed
+// but changed to drop usage of async Promises and some renaming
+// https://github.com/serverless/serverless-openwhisk/blob/master/provider/credentials.js
+
+'use strict';
+
+const path = require('path');
+const fs = require('fs-extra');
+
+const ENV_PARAMS = ['OW_APIHOST', 'OW_AUTH', 'OW_NAMESPACE', 'OW_APIGW_ACCESS_TOKEN'];
+
+function getWskPropsFile() {
+    const Home = process.env[(process.platform === 'win32') ? 'USERPROFILE' : 'HOME'];
+    return process.env.WSK_CONFIG_FILE || path.format({ dir: Home, base: '.wskprops' });
+}
+
+function readWskPropsFile() {
+    const wskFilePath = getWskPropsFile();
+
+    if (fs.existsSync(wskFilePath)) {
+        return fs.readFileSync(wskFilePath, 'utf8');
+    } else {
+        return null;
+    }
+}
+
+function getWskProps() {
+    const data = readWskPropsFile();
+    if (!data) return {};
+
+    const wskProps = data.trim().split('\n')
+        .map(line => line.split('='))
+        .reduce((params, keyValue) => {
+            params[keyValue[0].toLowerCase()] = keyValue[1]; // eslint-disable-line no-param-reassign
+            return params;
+        }, {});
+
+    return wskProps;
+}
+
+function getWskEnvProps() {
+    const envProps = {};
+    ENV_PARAMS.forEach((envName) => {
+        if (process.env[envName]) envProps[envName.slice(3).toLowerCase()] = process.env[envName];
+    });
+    return envProps;
+}
+
+module.exports = {
+    get() {
+        const props = Object.assign(getWskProps(), getWskEnvProps());
+        if (props.auth) {
+            props.api_key = props.auth;
+            delete props.auth;
+        }
+        return props;
+    },
+    ENV_PARAMS,
+};
\ No newline at end of file
diff --git a/test/cli.test.js b/test/cli.test.js
new file mode 100644
index 0000000..8d0bc78
--- /dev/null
+++ b/test/cli.test.js
@@ -0,0 +1,65 @@
+/**
+ *  Copyright 2019 Adobe. All rights reserved.
+ *
+ *  This file is licensed to you 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 REPRESENTATIONS
+ *  OF ANY KIND, either express or implied. See the License for the specific language
+ *  governing permissions and limitations under the License.
+ */
+
+
+/* eslint-env mocha */
+
+'use strict';
+
+// tests basic cli
+
+const wskdebug = require('../index');
+
+const test = require('./test');
+const assert = require('assert');
+const stripAnsi = require('strip-ansi');
+const {execSync} = require('child_process');
+
+describe('cli', function() {
+
+    it("should print version (via cli.js)", async function() {
+        this.timeout(5000);
+        const stdout = execSync("node cli.js --version").toString();
+        assert.equal(stripAnsi(stdout.trim()), require(`${process.cwd()}/package.json`).version);
+    });
+
+    it("should print help", async function() {
+        test.startCaptureStdout();
+
+        await wskdebug(`-h`);
+
+        const stdio = test.endCaptureStdout();
+
+        assert.equal(stdio.stderr, "");
+        // testing a couple strings that should rarely change
+        assert(stdio.stdout.includes("Debug an OpenWhisk <action> by forwarding its activations to a local docker container"));
+        assert(stdio.stdout.includes("Supported kinds:"));
+        assert(stdio.stdout.includes("Arguments:"));
+        assert(stdio.stdout.includes("Action options:"));
+        assert(stdio.stdout.includes("LiveReload options:"));
+        assert(stdio.stdout.includes("Debugger options:"));
+        assert(stdio.stdout.includes("Agent options:"));
+        assert(stdio.stdout.includes("Options:"));
+    });
+
+    it("should print the version", async function() {
+        test.startCaptureStdout();
+
+        await wskdebug(`--version`);
+
+        const stdio = test.endCaptureStdout();
+        assert.equal(stdio.stderr, "");
+        assert.equal(stripAnsi(stdio.stdout.trim()), require(`${process.cwd()}/package.json`).version);
+    });
+
+});
\ No newline at end of file
diff --git a/test/fake/params.json b/test/fake/params.json
new file mode 100644
index 0000000..07e2265
--- /dev/null
+++ b/test/fake/params.json
@@ -0,0 +1 @@
+{ "key": "invocationOnSourceModification" }
\ No newline at end of file
diff --git a/test/logfile.setup.js b/test/logfile.setup.js
new file mode 100644
index 0000000..907444a
--- /dev/null
+++ b/test/logfile.setup.js
@@ -0,0 +1,121 @@
+/**
+ *  Copyright 2019 Adobe. All rights reserved.
+ *
+ *  This file is licensed to you 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 REPRESENTATIONS
+ *  OF ANY KIND, either express or implied. See the License for the specific language
+ *  governing permissions and limitations under the License.
+ */
+
+'use strict';
+
+/* eslint-env mocha */
+
+// redirect console log output into a log file during unit tests to keep stdout clean
+// - log file: build/mocha.test.log
+// - can be changed by setting the MOCHA_TEST_LOG_FILE environment variable
+
+// this is a global mocha setup file, before() and after() here run before and after all tests
+
+// disable log file when asked using -v or inside vscode test runners
+if (process.argv.includes("-v") || process.argv.some(s => s.includes("vscode-mocha-test-adapter"))) {
+    return;
+}
+
+const TEST_LOG_FILE = process.env.MOCHA_TEST_LOG_FILE || "build/mocha.test.log";
+
+const clone = require('clone');
+const util = require('util');
+const path = require('path');
+const fsExtra = require('fs-extra');
+
+// ---------------------------------------------------------------------
+// file writing using native fs binding, which works around mock-fs
+
+const fsBinding = clone(process.binding('fs'));
+
+function fileOpen(path) {
+    // overwrite = 1537,
+    // append = 521
+    return fsBinding.open(path, 1537, 438, undefined, { path: path });
+}
+
+function fileWrite(fd, data) {
+    const buffer = Buffer.from(data);
+    fsBinding.writeBuffer(fd, buffer, 0, buffer.length, null, undefined, {});
+}
+
+function fileClose(fd) {
+    fsBinding.close(fd, undefined, {});
+}
+
+// ---------------------------------------------------------------------
+
+let logFile;
+const originalConsole = {
+    log: console.log,
+    error: console.error,
+    info: console.info,
+    debug: console.debug
+};
+
+before(function() {
+    console.log(`Log output in '${TEST_LOG_FILE}'. To log on stdout, run 'npm test -- -v'.`);
+    console.log();
+    process.on('exit', function() {
+        console.log(`Log output written to '${TEST_LOG_FILE}'. To log on stdout, run 'npm test -- -v'.`);
+        console.log();
+    });
+
+    fsExtra.mkdirsSync(path.dirname(TEST_LOG_FILE));
+    logFile = fileOpen(TEST_LOG_FILE);
+    // make available globally for e.g. child process output
+    global.mochaLogFile = logFile;
+
+    console.log = function(...args) {
+        if (global.disableMochaLogFile) {
+            process.stdout.write(util.format(...args));
+        } else {
+            fileWrite(logFile, util.format(...args) + "\n");
+        }
+    };
+    console.error = function(...args) {
+        if (global.disableMochaLogFile) {
+            process.stderr.write(util.format(...args));
+        } else {
+            fileWrite(logFile, util.format(...args) + "\n");
+        }
+    }
+    console.info = console.log;
+    console.debug = console.log;
+    console.warn = console.error;
+    console._logToFile = true;
+});
+
+beforeEach(function() {
+    // print full test title - all nested describes and current test
+    let t = this.currentTest;
+    let title = t.title;
+    while (t.parent && t.parent.title) {
+        t = t.parent;
+        title = t.title + " > " + title;
+    }
+    console.log("[TEST]", title);
+    console.log();
+});
+
+afterEach(function() {
+    console.log();
+});
+
+after(function() {
+    fileClose(logFile);
+    console.log = originalConsole.log;
+    console.error = originalConsole.error;
+    console.info = originalConsole.info;
+    console.debug = originalConsole.debug;
+});
diff --git a/test/multireporterconfig.json b/test/multireporterconfig.json
new file mode 100644
index 0000000..5679c3d
--- /dev/null
+++ b/test/multireporterconfig.json
@@ -0,0 +1,8 @@
+{
+    "reporterEnabled": "spec, xunit",
+
+    "xunitReporterOptions": {
+        "id": "xunit",
+        "output": "test-results/mocha/test-results.xml"
+    }
+}
\ No newline at end of file
diff --git a/test/ngrok.test.js b/test/ngrok.test.js
new file mode 100644
index 0000000..03ab8bd
--- /dev/null
+++ b/test/ngrok.test.js
@@ -0,0 +1,73 @@
+/**
+ *  Copyright 2019 Adobe. All rights reserved.
+ *
+ *  This file is licensed to you 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 REPRESENTATIONS
+ *  OF ANY KIND, either express or implied. See the License for the specific language
+ *  governing permissions and limitations under the License.
+ */
+
+/* eslint-env mocha */
+
+'use strict';
+
+const Debugger = require("../src/debugger");
+
+const test = require('./test');
+const assert = require('assert');
+const nock = require('nock');
+
+describe('ngrok',  function() {
+    this.timeout(30000);
+
+    before(function() {
+        test.isDockerInstalled();
+    });
+
+    beforeEach(async function() {
+        await test.beforeEach();
+    });
+
+    afterEach(function() {
+        test.afterEach();
+    });
+
+    it("should connect to ngrok if selected", async function() {
+        test.mockActionAndInvocation(
+            "myaction",
+            // should not use this code if we specify local sources which return CORRECT
+            `const main = () => ({ msg: 'WRONG' });`,
+            {},
+            { msg: "CORRECT" }
+        );
+
+        // validate that it connects to ngrok
+        // leaving it at that for now - more validation would be quite difficult
+        const ngrok = nock('http://127.0.0.1', {
+            filteringScope: scope => /^http:\/\/127\.0\.0\.1:.*/.test(scope),
+        })
+            .post('/api/tunnels')
+            .reply(201, { "public_url":"https://UNIT_TEST.ngrok.io" });
+
+        // wskdebug myaction --ngrok -p ${test.port}
+        const argv = {
+            port: test.port,
+            action: "myaction",
+            ngrok: true
+        };
+
+        const dbgr = new Debugger(argv);
+        await dbgr.start();
+        // no need to run() for this test
+        dbgr.run();
+        await dbgr.stop();
+
+        assert(ngrok.isDone(), "Expected these HTTP requests: " + ngrok.pendingMocks().join());
+    });
+
+    // TODO: test ngrokHandler, POST to local server
+});
\ No newline at end of file
diff --git a/test/nodejs.test.js b/test/nodejs.test.js
new file mode 100644
index 0000000..9898cfa
--- /dev/null
+++ b/test/nodejs.test.js
@@ -0,0 +1,426 @@
+/**
+ *  Copyright 2019 Adobe. All rights reserved.
+ *
+ *  This file is licensed to you 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 REPRESENTATIONS
+ *  OF ANY KIND, either express or implied. See the License for the specific language
+ *  governing permissions and limitations under the License.
+ */
+
+/* eslint-env mocha */
+
+'use strict';
+
+// tests for node.js debugging
+
+// here is how most tests are setup:
+// - requests to openwhisk and the agent are mocked using nock
+// - docker is required and the containers actually run
+
+const wskdebug = require('../index');
+const Debugger = require("../src/debugger");
+
+const test = require('./test');
+const assert = require('assert');
+const fse = require('fs-extra');
+
+describe('nodejs', function() {
+    this.timeout(30000);
+
+    before(function() {
+        test.isDockerInstalled();
+    });
+
+    beforeEach(async function() {
+        await test.beforeEach();
+    });
+
+    afterEach(function() {
+        test.afterEach();
+    });
+
+    it("should run an action without local sources", async function() {
+        test.mockActionAndInvocation(
+            "myaction",
+            `function main(params) {
+                return {
+                    msg: 'CORRECT',
+                    input: params.input
+                }
+            }`,
+            { input: "test-input" },
+            { msg: "CORRECT", input: "test-input" }
+        );
+
+        await wskdebug(`myaction -p ${test.port}`);
+
+        test.assertAllNocksInvoked();
+    });
+
+    it("should mount local sources with plain js and flat source structure", async function() {
+        test.mockActionAndInvocation(
+            "myaction",
+            // should not use this code if we specify local sources which return CORRECT
+            `const main = () => ({ msg: 'WRONG' });`,
+            {},
+            { msg: "CORRECT" }
+        );
+
+        process.chdir("test/nodejs/plain-flat");
+        await wskdebug(`myaction action.js -p ${test.port}`);
+
+        test.assertAllNocksInvoked();
+    });
+
+    it("should mount local sources with plain js and one level deep source structure", async function() {
+        test.mockActionAndInvocation(
+            "myaction",
+            `const main = () => ({ msg: 'WRONG' });`,
+            {},
+            { msg: "CORRECT" }
+        );
+
+        process.chdir("test/nodejs/plain-onelevel");
+        await wskdebug(`myaction lib/action.js -p ${test.port}`);
+
+        test.assertAllNocksInvoked();
+    });
+
+    it("it should always use linux paths in docker code", async function() {
+        const nodejs = require("../src/kinds/nodejs/nodejs")
+        const path = require("path")
+
+        // manually mock path
+        path.sep = '\\'
+        const posix = path.posix
+        path.posix = { sep: '/' }
+
+        process.chdir("test/nodejs/plain-onelevel");
+        const ret = nodejs.mountAction({
+            sourceFile: 'lib\\action.js',
+            sourcePath: 'lib/action.js'
+        })
+
+        // restore mock
+        path.sep = '/'
+        path.posix = posix
+
+        // asserts
+        assert(ret.code.includes('lib/action.js'))
+        assert(!ret.code.includes('lib\\action.js'))
+    });
+
+    it("should mount local sources with a require(../) dependency", async function() {
+        this.timeout(10000);
+        test.mockActionAndInvocation(
+            "myaction",
+            // should not use this code if we specify local sources which return CORRECT
+            `const main = () => ({ msg: 'WRONG' });`,
+            {},
+            { msg: "CORRECT" },
+            true // binary
+        );
+
+        process.chdir("test/nodejs/commonjs-onelevel");
+        await wskdebug(`myaction lib/action.js -p ${test.port}`);
+
+        test.assertAllNocksInvoked();
+    });
+
+    it("should mount local sources with a require(../) dependency reported as non binary", async function() {
+        this.timeout(10000);
+        test.mockActionAndInvocation(
+            "myaction",
+            // should not use this code if we specify local sources which return CORRECT
+            `const main = () => ({ msg: 'WRONG' });`,
+            {},
+            { msg: "CORRECT" }
+        );
+
+        process.chdir("test/nodejs/commonjs-onelevel");
+        await wskdebug(`myaction lib/action.js -p ${test.port}`);
+
+        test.assertAllNocksInvoked();
+    });
+
+    it("should mount local sources with a require(../) dependency using absolute paths", async function() {
+        this.timeout(10000);
+        test.mockActionAndInvocation(
+            "myaction",
+            // should not use this code if we specify local sources which return CORRECT
+            `const main = () => ({ msg: 'WRONG' });`,
+            {},
+            { msg: "CORRECT" },
+            true // binary
+        );
+
+        process.chdir("test/nodejs/commonjs-onelevel");
+        await wskdebug(`myaction ${process.cwd()}/lib/action.js -p ${test.port}`);
+
+        test.assertAllNocksInvoked();
+    });
+
+    it("should mount local sources with a require(../) dependency and run build with --on-build set", async function() {
+        this.timeout(10000);
+        test.mockActionAndInvocation(
+            "myaction",
+            // should not use this code if we specify local sources which return CORRECT
+            `const main = () => ({ msg: 'WRONG' });`,
+            {},
+            { msg: "CORRECT" }
+        );
+
+        process.chdir("test/nodejs/commonjs-onelevel");
+        fse.removeSync("build");
+
+        // simulate a build that moves things into a separate directory with different naming
+        const onBuild = "mkdir -p build/out; cp -R lib build/out/folder; cp dependency.js build/out";
+        await wskdebug(`myaction lib/action.js --on-build '${onBuild}' --build-path build/out/folder/action.js -p ${test.port}`);
+
+        fse.removeSync("build");
+        test.assertAllNocksInvoked();
+    });
+
+
+
+    it("should mount and run local sources with a comment on the last line", async function() {
+        test.mockActionAndInvocation(
+            "myaction",
+            `const main = () => ({ msg: 'WRONG' });`,
+            { },
+            { msg: "CORRECT" }
+        );
+
+        process.chdir("test/nodejs/trailing-comment");
+        await wskdebug(`myaction -p ${test.port} action.js`);
+
+        test.assertAllNocksInvoked();
+    });
+
+    it("should mount local sources with commonjs and flat source structure", async function() {
+        test.mockActionAndInvocation(
+            "myaction",
+            `const main = () => ({ msg: 'WRONG' });`,
+            {},
+            { msg: "CORRECT/RESULT" },
+            true // binary = true for nodejs means zip action with commonjs (require) loading
+        );
+
+        process.chdir("test/nodejs/commonjs-flat");
+        await wskdebug(`myaction action.js -p ${test.port}`);
+
+        test.assertAllNocksInvoked();
+    });
+
+    it("should mount local sources with plain js reported as binary", async function() {
+        test.mockActionAndInvocation(
+            "myaction",
+            // should not use this code if we specify local sources which return CORRECT
+            `const main = () => ({ msg: 'WRONG' });`,
+            {},
+            { msg: "CORRECT" },
+            true // binary
+        );
+
+        process.chdir("test/nodejs/plain-flat");
+        await wskdebug(`myaction action.js -p ${test.port}`);
+
+        test.assertAllNocksInvoked();
+    });
+
+    it("should mount local sources with commonjs reported as non binary", async function() {
+        this.timeout(10000);
+        test.mockActionAndInvocation(
+            "myaction",
+            // should not use this code if we specify local sources which return CORRECT
+            `const main = () => ({ msg: 'WRONG' });`,
+            {},
+            { msg: "CORRECT/RESULT" },
+            false // binary
+        );
+
+        process.chdir("test/nodejs/commonjs-flat");
+        await wskdebug(`myaction action.js -p ${test.port}`);
+
+        test.assertAllNocksInvoked();
+    });
+
+    it("should invoke and handle action when a source file changes and -P is set", async function() {
+        const action = "myaction";
+        const code = `const main = () => ({ msg: 'WRONG' });`;
+
+        test.mockAction(action, code);
+        test.expectAgent(action, code);
+
+        // mock agent & action invocaton logic on the openwhisk side
+        const ACTIVATION_ID = "1234567890";
+        let invokedAction = false;
+        let completedAction = false;
+
+        test.nockActivation("myaction")
+            .reply(async (uri, body) => {
+                let response = [];
+                // wskdebug polling the agent
+                if (body.$waitForActivation === true) {
+                    // when the action got invoked, we tell it wskdebug
+                    // but only once
+                    if (invokedAction && !completedAction) {
+                        response = [ 200, {
+                            response: {
+                                result: {
+                                    $activationId: ACTIVATION_ID
+                                }
+                            }
+                        }];
+                    } else {
+                        // tell wskdebug to retry polling
+                        response = [ 502, test.agentRetryResponse() ];
+                    }
+                } else if (body.key === "invocationOnSourceModification") {
+                    // the action got invoked
+                    invokedAction = true;
+                    response = [ 200, { activationId: ACTIVATION_ID } ];
+
+                } else if (body.$activationId === ACTIVATION_ID) {
+                    // action was completed by wskdebug
+                    completedAction = true;
+                    response = [200, {}];
+                }
+                return response;
+            })
+            .persist();
+
+        // wskdebug myaction action.js -l -P '{...}' -p ${test.port}
+        process.chdir("test/nodejs/plain-flat");
+        const argv = {
+            port: test.port,
+            action: "myaction",
+            sourcePath: `${process.cwd()}/action.js`,
+            invokeParams: '{ "key": "invocationOnSourceModification" }'
+        };
+
+        const dbgr = new Debugger(argv);
+        await dbgr.start();
+        dbgr.run();
+
+        // wait a bit
+        await test.sleep(500);
+
+        // simulate a source file change
+        test.touchFile("action.js");
+
+        // eslint-disable-next-line no-unmodified-loop-condition
+        while (!completedAction && test.hasNotTimedOut(this)) {
+            await test.sleep(100);
+        }
+
+        await dbgr.stop();
+
+        assert.ok(invokedAction, "action was not invoked on source change");
+        assert.ok(completedAction, "action invocation was not handled and completed");
+        test.assertAllNocksInvoked();
+    });
+
+    it("should invoke and handle action when a source file changes and --on-build and --build-path and -P are set", async function() {
+        this.timeout(10000);
+        const action = "myaction";
+        const code = `const main = () => ({ msg: 'WRONG' });`;
+
+        test.mockAction(action, code);
+        test.expectAgent(action, code);
+
+        // mock agent & action invocaton logic on the openwhisk side
+        const ACTIVATION_ID = "1234567890";
+        let invokedAction = false;
+        let completedAction = false;
+
+        test.nockActivation("myaction")
+            .reply(async (uri, body) => {
+                let response = [];
+                // wskdebug polling the agent
+                if (body.$waitForActivation === true) {
+                    // when the action got invoked, we tell it wskdebug
+                    // but only once
+                    if (invokedAction && !completedAction) {
+                        response = [ 200, {
+                            response: {
+                                result: {
+                                    $activationId: ACTIVATION_ID
+                                }
+                            }
+                        }];
+                    } else {
+                        // tell wskdebug to retry polling
+                        response = [ 502, test.agentRetryResponse() ];
+                    }
+                } else if (body.key === "invocationOnSourceModification") {
+                    // the action got invoked
+                    invokedAction = true;
+                    response = [ 200, { activationId: ACTIVATION_ID } ];
+
+                } else if (body.$activationId === ACTIVATION_ID) {
+                    // action was completed by wskdebug
+                    if (body.msg === "CORRECT") {
+                        completedAction = true;
+                        response = [200, {}];
+                    } else {
+                        response = [502, test.agentExitResponse()];
+                    }
+                }
+                return response;
+            })
+            .persist();
+
+        // wskdebug myaction action.js --on-build "..." --build-path build/action.js -P '{...}' -p ${test.port}
+        process.chdir("test/nodejs/build-step");
+
+        fse.removeSync("build");
+
+        const argv = {
+            port: test.port,
+            action: "myaction",
+            // copy a different file with "CORRECT in it"
+            onBuild: `mkdir -p build; cp action-build.txt build/action.js`,
+            buildPath: `build/action.js`,
+            sourcePath: `action.js`,
+            invokeParams: '{ "key": "invocationOnSourceModification" }'
+        };
+
+        const dbgr = new Debugger(argv);
+        await dbgr.start();
+        dbgr.run();
+
+        // wait a bit
+        await test.sleep(500);
+
+        // simulate a source file change
+        test.touchFile("action.js");
+
+        // eslint-disable-next-line no-unmodified-loop-condition
+        while (!completedAction && test.hasNotTimedOut(this)) {
+            await test.sleep(100);
+        }
+
+        await dbgr.stop();
+
+        fse.removeSync("build");
+        assert.ok(invokedAction, "action was not invoked on source change");
+        assert.ok(completedAction, "action invocation was not handled and completed");
+        test.assertAllNocksInvoked();
+    });
+
+    // TODO: test -l livereload connection
+
+    // TODO: test agents - conditions (unit test agent code locally)
+    // TODO: test agent already installed (debugger.getAction())
+
+    // TODO: test breakpoint debugging
+    // TODO: test action options
+    // TODO: test debugger options
+    // TODO: test non-concurrent openwhisk
+
+});
\ No newline at end of file
diff --git a/test/nodejs/action.js b/test/nodejs/action.js
new file mode 100644
index 0000000..54f91d3
--- /dev/null
+++ b/test/nodejs/action.js
@@ -0,0 +1 @@
+// dummy file for watch tests
\ No newline at end of file
diff --git a/test/nodejs/build-step/action-build.txt b/test/nodejs/build-step/action-build.txt
new file mode 100644
index 0000000..db75bf8
--- /dev/null
+++ b/test/nodejs/build-step/action-build.txt
@@ -0,0 +1,6 @@
+'use strict';
+
+// eslint-disable-next-line no-unused-vars
+function main(params) {
+    return { msg: 'CORRECT' };
+}
\ No newline at end of file
diff --git a/test/nodejs/build-step/action.js b/test/nodejs/build-step/action.js
new file mode 100644
index 0000000..b894d90
--- /dev/null
+++ b/test/nodejs/build-step/action.js
@@ -0,0 +1,6 @@
+'use strict';
+
+// eslint-disable-next-line no-unused-vars
+function main(params) {
+    return { msg: 'WRONG' };
+}
\ No newline at end of file
diff --git a/test/nodejs/commonjs-flat/action.js b/test/nodejs/commonjs-flat/action.js
new file mode 100644
index 0000000..303f1e1
--- /dev/null
+++ b/test/nodejs/commonjs-flat/action.js
@@ -0,0 +1,7 @@
+'use strict';
+
+const path = require('path');
+
+exports.main = function() {
+    return { msg: path.join("CORRECT", "RESULT") };
+}
\ No newline at end of file
diff --git a/test/nodejs/commonjs-onelevel/dependency.js b/test/nodejs/commonjs-onelevel/dependency.js
new file mode 100644
index 0000000..6bcda62
--- /dev/null
+++ b/test/nodejs/commonjs-onelevel/dependency.js
@@ -0,0 +1,5 @@
+'use strict';
+
+module.exports = {
+    msg: "CORRECT"
+}
diff --git a/test/nodejs/commonjs-onelevel/lib/action.js b/test/nodejs/commonjs-onelevel/lib/action.js
new file mode 100644
index 0000000..b971f2d
--- /dev/null
+++ b/test/nodejs/commonjs-onelevel/lib/action.js
@@ -0,0 +1,5 @@
+'use strict';
+
+exports.main = function() {
+    return require('../dependency');
+}
\ No newline at end of file
diff --git a/test/nodejs/plain-flat/action.js b/test/nodejs/plain-flat/action.js
new file mode 100644
index 0000000..d53c3d4
--- /dev/null
+++ b/test/nodejs/plain-flat/action.js
@@ -0,0 +1,6 @@
+'use strict';
+
+// eslint-disable-next-line no-unused-vars
+function main(params) {
+    return { msg: 'CORRECT' }
+}
\ No newline at end of file
diff --git a/test/nodejs/plain-flat/params.json b/test/nodejs/plain-flat/params.json
new file mode 100644
index 0000000..a35356d
--- /dev/null
+++ b/test/nodejs/plain-flat/params.json
@@ -0,0 +1,3 @@
+{
+    "key": "invocationOnSourceModification"
+}
\ No newline at end of file
diff --git a/test/nodejs/plain-onelevel/lib/action.js b/test/nodejs/plain-onelevel/lib/action.js
new file mode 100644
index 0000000..e697181
--- /dev/null
+++ b/test/nodejs/plain-onelevel/lib/action.js
@@ -0,0 +1,6 @@
+'use strict';
+
+// eslint-disable-next-line no-unused-vars
+function main() {
+    return { msg: 'CORRECT' };
+}
\ No newline at end of file
diff --git a/test/nodejs/trailing-comment/action.js b/test/nodejs/trailing-comment/action.js
new file mode 100644
index 0000000..212eeb8
--- /dev/null
+++ b/test/nodejs/trailing-comment/action.js
@@ -0,0 +1,7 @@
+'use strict';
+
+// eslint-disable-next-line no-unused-vars
+function main() {
+    return { msg: 'CORRECT' };
+}
+// trailing comment (do not remove!)
\ No newline at end of file
diff --git a/test/nodejs/watch/action.xyz b/test/nodejs/watch/action.xyz
new file mode 100644
index 0000000..3149c54
--- /dev/null
+++ b/test/nodejs/watch/action.xyz
@@ -0,0 +1 @@
+# dummy file for source watching
\ No newline at end of file
diff --git a/test/nodejs/watch/dummy.js b/test/nodejs/watch/dummy.js
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/test/nodejs/watch/dummy.js
diff --git a/test/nodejs/watch/src/action.js b/test/nodejs/watch/src/action.js
new file mode 100644
index 0000000..d53c3d4
--- /dev/null
+++ b/test/nodejs/watch/src/action.js
@@ -0,0 +1,6 @@
+'use strict';
+
+// eslint-disable-next-line no-unused-vars
+function main(params) {
+    return { msg: 'CORRECT' }
+}
\ No newline at end of file
diff --git a/test/openwhisk-swagger.json b/test/openwhisk-swagger.json
new file mode 100644
index 0000000..f74a2a1
--- /dev/null
+++ b/test/openwhisk-swagger.json
@@ -0,0 +1,2682 @@
+{
+    "swagger": "2.0",
+    "info": {
+      "title": "OpenWhisk REST API",
+      "description": "API for OpenWhisk",
+      "version": "0.1.0"
+    },
+    "produces": [
+      "application/json"
+    ],
+    "basePath": "/api/v1",
+    "securityDefinitions": {
+      "basicAuth": {
+        "type": "basic"
+      }
+    },
+    "security": [
+      {
+        "basicAuth": [
+  
+        ]
+      }
+    ],
+    "tags": [
+      {
+        "name": "Actions"
+      },
+      {
+        "name": "Rules"
+      },
+      {
+        "name": "Triggers"
+      },
+      {
+        "name": "Activations"
+      },
+      {
+        "name": "Packages"
+      },
+      {
+        "name": "Namespaces"
+      }
+    ],
+    "paths": {
+      "/namespaces": {
+        "get": {
+          "tags": [
+            "Namespaces"
+          ],
+          "description": "Get all namespaces for authenticated user",
+          "summary": "Get all namespaces for authenticated user",
+          "operationId": "getAllNamespaces",
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Array of namespaces",
+              "schema": {
+                "type": "array",
+                "items": {
+                  "type": "string"
+                }
+              }
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        }
+      },
+      "/namespaces/{namespace}/actions": {
+        "get": {
+          "tags": [
+            "Actions"
+          ],
+          "description": "Get all actions",
+          "summary": "Get all actions",
+          "operationId": "getAllActions",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "limit",
+              "in": "query",
+              "description": "Number of entities to include in the result (0-200). The default limit is 30. A value of 0 sets the limit to the maximum.",
+              "required": false,
+              "type": "integer"
+            },
+            {
+              "name": "skip",
+              "in": "query",
+              "description": "Number of entities to skip in the result.",
+              "required": false,
+              "type": "integer"
+            }
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Actions response",
+              "schema": {
+                "type": "array",
+                "items": {
+                  "$ref": "#/definitions/Action"
+                }
+              }
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        }
+      },
+      "/namespaces/{namespace}/actions/{actionName}": {
+        "parameters": [
+          {
+            "name": "namespace",
+            "in": "path",
+            "description": "The entity namespace",
+            "required": true,
+            "type": "string"
+          },
+          {
+            "name": "actionName",
+            "in": "path",
+            "description": "Name of action to fetch",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "get": {
+          "tags": [
+            "Actions"
+          ],
+          "summary": "Get action information",
+          "description": "Get action information.",
+          "operationId": "getActionByName",
+          "parameters": [
+            {
+              "name": "code",
+              "in": "query",
+              "description": "Include action code in the result",
+              "required": false,
+              "type": "boolean"
+            }
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Returned action",
+              "schema": {
+                "$ref": "#/definitions/Action"
+              }
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "403": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        },
+        "put": {
+          "tags": [
+            "Actions"
+          ],
+          "description": "Create or update an action",
+          "summary": "Create or update an action",
+          "operationId": "updateAction",
+          "parameters": [
+            {
+              "name": "overwrite",
+              "in": "query",
+              "description": "Overwrite item if it exists. Default is false.",
+              "required": false,
+              "type": "string",
+              "enum": [
+                "true",
+                "false"
+              ]
+            },
+            {
+              "name": "action",
+              "in": "body",
+              "description": "The action being updated",
+              "required": true,
+              "schema": {
+                "$ref": "#/definitions/ActionPut"
+              }
+            }
+          ],
+          "consumes": [
+            "application/json"
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Updated Action",
+              "schema": {
+                "$ref": "#/definitions/Action"
+              }
+            },
+            "400": {
+              "$ref": "#/responses/BadRequest"
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "403": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "409": {
+              "$ref": "#/responses/Conflict"
+            },
+            "413": {
+              "$ref": "#/responses/RequestEntityTooLarge"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        },
+        "delete": {
+          "tags": [
+            "Actions"
+          ],
+          "description": "Delete an action",
+          "summary": "Delete an action",
+          "operationId": "deleteAction",
+          "responses": {
+            "200": {
+              "$ref": "#/responses/DeletedItem"
+            },
+            "400": {
+              "$ref": "#/responses/BadRequest"
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "403": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "409": {
+              "$ref": "#/responses/Conflict"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        },
+        "post": {
+          "tags": [
+            "Actions"
+          ],
+          "description": "Invoke an action",
+          "summary": "Invoke an action",
+          "operationId": "invokeAction",
+          "parameters": [
+            {
+              "name": "blocking",
+              "in": "query",
+              "description": "Blocking or non-blocking invocation. Default is non-blocking.",
+              "required": false,
+              "type": "string",
+              "enum": [
+                "true",
+                "false"
+              ]
+            },
+            {
+              "name": "result",
+              "in": "query",
+              "description": "Return only the result of a blocking activation. Default is false.",
+              "required": false,
+              "type": "string",
+              "enum": [
+                "true",
+                "false"
+              ]
+            },
+            {
+              "name": "timeout",
+              "in": "query",
+              "description": "Wait no more than specified duration in milliseconds for a blocking response. Default value and max allowed timeout are 60000.",
+              "required": false,
+              "type": "integer"
+            },
+            {
+              "name": "payload",
+              "in": "body",
+              "description": "The parameters for the action being invoked",
+              "required": false,
+              "schema": {
+                "type": "object"
+              }
+            }
+          ],
+          "consumes": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Successful activation"
+            },
+            "202": {
+              "$ref": "#/responses/AcceptedActivation"
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "403": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "408": {
+              "$ref": "#/responses/Timeout"
+            },
+            "429": {
+              "$ref": "#/responses/TooManyRequests"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            },
+            "502": {
+              "description": "Activation produced an application error"
+            }
+          }
+        }
+      },
+      "/namespaces/{namespace}/actions/{packageName}/{actionName}": {
+        "parameters": [
+          {
+            "name": "namespace",
+            "in": "path",
+            "description": "The entity namespace",
+            "required": true,
+            "type": "string"
+          },
+          {
+            "name": "packageName",
+            "in": "path",
+            "description": "Name of package that contains action",
+            "required": true,
+            "type": "string"
+          },
+          {
+            "name": "actionName",
+            "in": "path",
+            "description": "Name of action to fetch",
+            "required": true,
+            "type": "string"
+          }
+        ],
+        "get": {
+          "tags": [
+            "Actions"
+          ],
+          "summary": "Get action information",
+          "description": "Get action information.",
+          "operationId": "getActionInPackageByName",
+          "parameters": [
+            {
+              "name": "code",
+              "in": "query",
+              "description": "Include action code in the result",
+              "required": false,
+              "type": "boolean"
+            }
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Returned action",
+              "schema": {
+                "$ref": "#/definitions/Action"
+              }
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "403": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        },
+        "put": {
+          "tags": [
+            "Actions"
+          ],
+          "description": "Create or update an action",
+          "summary": "Create or update an action",
+          "operationId": "updateActionInPackage",
+          "parameters": [
+            {
+              "name": "overwrite",
+              "in": "query",
+              "description": "Overwrite item if it exists. Default is false.",
+              "required": false,
+              "type": "string",
+              "enum": [
+                "true",
+                "false"
+              ]
+            },
+            {
+              "name": "action",
+              "in": "body",
+              "description": "The action being updated",
+              "required": true,
+              "schema": {
+                "$ref": "#/definitions/ActionPut"
+              }
+            }
+          ],
+          "consumes": [
+            "application/json"
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Updated Action",
+              "schema": {
+                "$ref": "#/definitions/Action"
+              }
+            },
+            "400": {
+              "$ref": "#/responses/BadRequest"
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "403": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "409": {
+              "$ref": "#/responses/Conflict"
+            },
+            "413": {
+              "$ref": "#/responses/RequestEntityTooLarge"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        },
+        "delete": {
+          "tags": [
+            "Actions"
+          ],
+          "description": "Delete an action",
+          "summary": "Delete an action",
+          "operationId": "deleteActionInPackage",
+          "responses": {
+            "200": {
+              "$ref": "#/responses/DeletedItem"
+            },
+            "400": {
+              "$ref": "#/responses/BadRequest"
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "403": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "409": {
+              "$ref": "#/responses/Conflict"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        },
+        "post": {
+          "tags": [
+            "Actions"
+          ],
+          "description": "Invoke an action",
+          "summary": "Invoke an action",
+          "operationId": "invokeActionInPackage",
+          "parameters": [
+            {
+              "name": "blocking",
+              "in": "query",
+              "description": "Blocking or non-blocking invocation. Default is non-blocking.",
+              "required": false,
+              "type": "string",
+              "enum": [
+                "true",
+                "false"
+              ]
+            },
+            {
+              "name": "result",
+              "in": "query",
+              "description": "Return only the result of a blocking activation. Default is false.",
+              "required": false,
+              "type": "string",
+              "enum": [
+                "true",
+                "false"
+              ]
+            },
+            {
+              "name": "timeout",
+              "in": "query",
+              "description": "Wait no more than specified duration in milliseconds for a blocking response. Default value and max allowed timeout are 60000.",
+              "required": false,
+              "type": "integer"
+            },
+            {
+              "name": "payload",
+              "in": "body",
+              "description": "The parameters for the action being invoked",
+              "required": false,
+              "schema": {
+                "type": "object"
+              }
+            }
+          ],
+          "consumes": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Successful activation"
+            },
+            "202": {
+              "$ref": "#/responses/AcceptedActivation"
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "403": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "408": {
+              "$ref": "#/responses/Timeout"
+            },
+            "429": {
+              "$ref": "#/responses/TooManyRequests"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            },
+            "502": {
+              "description": "Activation produced an application error"
+            }
+          }
+        }
+      },
+      "/web/{namespace}/{packageName}/{actionName}.{extension}": {
+        "parameters": [
+          {
+            "name": "namespace",
+            "type": "string",
+            "in": "path",
+            "required": true
+          },
+          {
+            "name": "packageName",
+            "type": "string",
+            "in": "path",
+            "required": true
+          },
+          {
+            "name": "actionName",
+            "type": "string",
+            "in": "path",
+            "required": true
+          },
+          {
+            "name": "extension",
+            "type": "string",
+            "in": "path",
+            "required": true
+          }
+        ],
+        "get": {
+          "tags": [
+            "Actions"
+          ],
+          "responses": {
+            "default": {
+              "description": "any response",
+              "schema": {
+              }
+            }
+          }
+        },
+        "put": {
+          "tags": [
+            "Actions"
+          ],
+          "responses": {
+            "default": {
+              "description": "any response",
+              "schema": {
+              }
+            }
+          }
+        },
+        "delete": {
+          "tags": [
+            "Actions"
+          ],
+          "responses": {
+            "default": {
+              "description": "any response",
+              "schema": {
+              }
+            }
+          }
+        },
+        "post": {
+          "tags": [
+            "Actions"
+          ],
+          "parameters": [
+            {
+              "name": "payload",
+              "in": "body",
+              "description": "The parameters for the action being invoked",
+              "required": false,
+              "schema": {
+                "type": "object"
+              }
+            }
+          ],
+          "responses": {
+            "default": {
+              "description": "any response",
+              "schema": {
+              }
+            }
+          }
+        }
+      },
+      "/namespaces/{namespace}/rules": {
+        "get": {
+          "tags": [
+            "Rules"
+          ],
+          "description": "Get all rules",
+          "summary": "Get all rules",
+          "operationId": "getAllRules",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "limit",
+              "in": "query",
+              "description": "Number of entities to include in the result (0-200). The default limit is 30. A value of 0 sets the limit to the maximum.",
+              "required": false,
+              "type": "integer"
+            },
+            {
+              "name": "skip",
+              "in": "query",
+              "description": "Number of entities to skip in the result.",
+              "required": false,
+              "type": "integer"
+            }
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Rules response",
+              "schema": {
+                "type": "array",
+                "items": {
+                  "$ref": "#/definitions/Rule"
+                }
+              }
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        }
+      },
+      "/namespaces/{namespace}/rules/{ruleName}": {
+        "get": {
+          "tags": [
+            "Rules"
+          ],
+          "description": "Get rule information",
+          "summary": "Get rule information",
+          "operationId": "getRuleByName",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "ruleName",
+              "in": "path",
+              "description": "Name of rule to fetch",
+              "required": true,
+              "type": "string"
+            }
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Returned rule",
+              "schema": {
+                "$ref": "#/definitions/Rule"
+              }
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        },
+        "put": {
+          "tags": [
+            "Rules"
+          ],
+          "description": "Create or update a rule",
+          "summary": "Create or update a rule",
+          "operationId": "updateRule",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "ruleName",
+              "in": "path",
+              "description": "Name of rule to update",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "overwrite",
+              "in": "query",
+              "description": "Overwrite item if it exists. Default is false.",
+              "required": false,
+              "type": "string",
+              "enum": [
+                "true",
+                "false"
+              ]
+            },
+            {
+              "name": "rule",
+              "in": "body",
+              "description": "The rule being updated",
+              "required": true,
+              "schema": {
+                "$ref": "#/definitions/RulePut"
+              }
+            }
+          ],
+          "consumes": [
+            "application/json"
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Updated rule",
+              "schema": {
+                "$ref": "#/definitions/Rule"
+              }
+            },
+            "400": {
+              "$ref": "#/responses/BadRequest"
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "409": {
+              "$ref": "#/responses/Conflict"
+            },
+            "413": {
+              "$ref": "#/responses/RequestEntityTooLarge"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        },
+        "delete": {
+          "tags": [
+            "Rules"
+          ],
+          "description": "Delete a rule",
+          "summary": "Delete a rule",
+          "operationId": "deleteRule",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "ruleName",
+              "in": "path",
+              "description": "Name of rule to delete",
+              "required": true,
+              "type": "string"
+            }
+          ],
+          "responses": {
+            "200": {
+              "$ref": "#/responses/DeletedItem"
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        },
+        "post": {
+          "tags": [
+            "Rules"
+          ],
+          "description": "Enable or disable a rule",
+          "summary": "Enable or disable a rule",
+          "operationId": "setState",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "ruleName",
+              "in": "path",
+              "description": "Name of rule to update",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "status",
+              "in": "body",
+              "description": "Set status to active or inactive",
+              "required": true,
+              "schema": {
+                "type": "object",
+                "required": [
+                  "status"
+                ],
+                "properties": {
+                  "status": {
+                    "type": "string",
+                    "enum": [
+                      "inactive",
+                      "active"
+                    ]
+                  }
+                }
+              }
+            }
+          ],
+          "produces": [
+            "application/json",
+            "text/plain"
+          ],
+          "responses": {
+            "200": {
+              "$ref": "#/responses/AcceptedRuleStateChange"
+            },
+            "202": {
+              "$ref": "#/responses/AcceptedRuleStateChange"
+            },
+            "400": {
+              "$ref": "#/responses/BadRequest"
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        }
+      },
+      "/namespaces/{namespace}/triggers": {
+        "get": {
+          "tags": [
+            "Triggers"
+          ],
+          "description": "Get all triggers",
+          "summary": "Get all triggers",
+          "operationId": "getAllTriggers",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "limit",
+              "in": "query",
+              "description": "Number of entities to include in the result (0-200). The default limit is 30. A value of 0 sets the limit to the maximum.",
+              "required": false,
+              "type": "integer"
+            },
+            {
+              "name": "skip",
+              "in": "query",
+              "description": "Number of entities to skip in the result.",
+              "required": false,
+              "type": "integer"
+            }
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Triggers response",
+              "schema": {
+                "type": "array",
+                "items": {
+                  "$ref": "#/definitions/Trigger"
+                }
+              }
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        }
+      },
+      "/namespaces/{namespace}/triggers/{triggerName}": {
+        "get": {
+          "tags": [
+            "Triggers"
+          ],
+          "description": "Get trigger information",
+          "summary": "Get trigger information",
+          "operationId": "getTriggerByName",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "triggerName",
+              "in": "path",
+              "description": "Name of trigger to fetch",
+              "required": true,
+              "type": "string"
+            }
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Returned trigger",
+              "schema": {
+                "$ref": "#/definitions/Trigger"
+              }
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        },
+        "put": {
+          "tags": [
+            "Triggers"
+          ],
+          "description": "Create or update a trigger",
+          "summary": "Create or update a trigger",
+          "operationId": "updateTrigger",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "triggerName",
+              "in": "path",
+              "description": "Name of trigger to update",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "overwrite",
+              "in": "query",
+              "description": "Overwrite item if it exists. Default is false.",
+              "required": false,
+              "type": "string",
+              "enum": [
+                "true",
+                "false"
+              ]
+            },
+            {
+              "name": "trigger",
+              "in": "body",
+              "description": "The trigger being updated",
+              "required": true,
+              "schema": {
+                "$ref": "#/definitions/TriggerPut"
+              }
+            }
+          ],
+          "consumes": [
+            "application/json"
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Updated trigger",
+              "schema": {
+                "$ref": "#/definitions/Trigger"
+              }
+            },
+            "400": {
+              "$ref": "#/responses/BadRequest"
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "409": {
+              "$ref": "#/responses/Conflict"
+            },
+            "413": {
+              "$ref": "#/responses/RequestEntityTooLarge"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        },
+        "delete": {
+          "tags": [
+            "Triggers"
+          ],
+          "description": "Delete a trigger",
+          "summary": "Delete a trigger",
+          "operationId": "deleteTrigger",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "triggerName",
+              "in": "path",
+              "description": "Name of trigger to delete",
+              "required": true,
+              "type": "string"
+            }
+          ],
+          "responses": {
+            "200": {
+              "$ref": "#/responses/DeletedItem"
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        },
+        "post": {
+          "tags": [
+            "Triggers"
+          ],
+          "description": "Fire a trigger",
+          "summary": "Fire a trigger",
+          "operationId": "fireTrigger",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "triggerName",
+              "in": "path",
+              "description": "Name of trigger being fired",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "payload",
+              "in": "body",
+              "description": "The trigger payload",
+              "required": false,
+              "schema": {
+                "type": "object"
+              }
+            }
+          ],
+          "responses": {
+            "202": {
+              "description": "Activation id",
+              "schema": {
+                "$ref": "#/definitions/ActivationId"
+              }
+            },
+            "204": {
+              "$ref": "#/responses/NoActiveRules"
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "408": {
+              "$ref": "#/responses/Timeout"
+            },
+            "429": {
+              "$ref": "#/responses/TooManyRequests"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        }
+      },
+      "/namespaces/{namespace}/packages": {
+        "get": {
+          "tags": [
+            "Packages"
+          ],
+          "description": "Get all packages",
+          "summary": "Get all packages",
+          "operationId": "getAllPackages",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "public",
+              "in": "query",
+              "description": "Include publicly shared entitles in the result.",
+              "required": false,
+              "type": "boolean"
+            },
+            {
+              "name": "limit",
+              "in": "query",
+              "description": "Number of entities to include in the result (0-200). The default limit is 30. A value of 0 sets the limit to the maximum.",
+              "required": false,
+              "type": "integer"
+            },
+            {
+              "name": "skip",
+              "in": "query",
+              "description": "Number of entities to skip in the result.",
+              "required": false,
+              "type": "integer"
+            }
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Packages response",
+              "schema": {
+                "type": "array",
+                "items": {
+                  "$ref": "#/definitions/Package"
+                }
+              }
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "403": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        }
+      },
+      "/namespaces/{namespace}/packages/{packageName}": {
+        "get": {
+          "tags": [
+            "Packages"
+          ],
+          "summary": "Get package information",
+          "description": "Get package information.",
+          "operationId": "getPackageByName",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "packageName",
+              "in": "path",
+              "description": "Name of package to fetch",
+              "required": true,
+              "type": "string"
+            }
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Returned package",
+              "schema": {
+                "$ref": "#/definitions/Package"
+              }
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "403": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "409": {
+              "$ref": "#/responses/Conflict"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        },
+        "put": {
+          "tags": [
+            "Packages"
+          ],
+          "description": "Create or update a package",
+          "summary": "Create or update a package",
+          "operationId": "updatePackage",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "packageName",
+              "in": "path",
+              "description": "Name of package",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "overwrite",
+              "in": "query",
+              "description": "Overwrite item if it exists. Default is false.",
+              "required": false,
+              "type": "string",
+              "enum": [
+                "true",
+                "false"
+              ]
+            },
+            {
+              "name": "package",
+              "in": "body",
+              "description": "The package being updated",
+              "required": true,
+              "schema": {
+                "$ref": "#/definitions/PackagePut"
+              }
+            }
+          ],
+          "consumes": [
+            "application/json"
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Updated Package",
+              "schema": {
+                "$ref": "#/definitions/Package"
+              }
+            },
+            "400": {
+              "$ref": "#/responses/BadRequest"
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "403": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "409": {
+              "$ref": "#/responses/Conflict"
+            },
+            "413": {
+              "$ref": "#/responses/RequestEntityTooLarge"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        },
+        "delete": {
+          "tags": [
+            "Packages"
+          ],
+          "description": "Delete a package",
+          "summary": "Delete a package",
+          "operationId": "deletePackage",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "packageName",
+              "in": "path",
+              "description": "Name of package",
+              "required": true,
+              "type": "string"
+            }
+          ],
+          "responses": {
+            "200": {
+              "$ref": "#/responses/DeletedItem"
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "409": {
+              "$ref": "#/responses/Conflict"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        }
+      },
+      "/namespaces/{namespace}/activations": {
+        "get": {
+          "tags": [
+            "Activations"
+          ],
+          "summary": "Get activation summary",
+          "description": "Get activation summary.",
+          "operationId": "getActivations",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "name",
+              "in": "query",
+              "description": "Name of item",
+              "required": false,
+              "type": "string"
+            },
+            {
+              "name": "limit",
+              "in": "query",
+              "description": "Number of entities to include in the result (0-200). The default limit is 30. A value of 0 sets the limit to the maximum.",
+              "required": false,
+              "type": "integer"
+            },
+            {
+              "name": "skip",
+              "in": "query",
+              "description": "Number of entities to skip in the result.",
+              "required": false,
+              "type": "integer"
+            },
+            {
+              "name": "since",
+              "in": "query",
+              "description": "Only include entities later than this timestamp (measured in milliseconds since Thu, 01 Jan 1970)",
+              "required": false,
+              "type": "integer"
+            },
+            {
+              "name": "upto",
+              "in": "query",
+              "description": "Only include entities earlier than this timestamp (measured in milliseconds since Thu, 01 Jan 1970)",
+              "required": false,
+              "type": "integer"
+            },
+            {
+              "name": "docs",
+              "in": "query",
+              "description": "Whether to include full entity description.",
+              "required": false,
+              "type": "boolean"
+            }
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Activations response",
+              "schema": {
+                "type": "array",
+                "items": {
+                  "$ref": "#/definitions/ActivationBrief"
+                }
+              }
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        }
+      },
+      "/namespaces/{namespace}/activations/{activationid}": {
+        "get": {
+          "tags": [
+            "Activations"
+          ],
+          "summary": "Get activation information",
+          "description": "Get activation information.",
+          "operationId": "getActivationById",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "activationid",
+              "in": "path",
+              "description": "Name of activation to fetch",
+              "required": true,
+              "type": "string"
+            }
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Return output",
+              "schema": {
+                "$ref": "#/definitions/Activation"
+              }
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        }
+      },
+      "/namespaces/{namespace}/activations/{activationid}/logs": {
+        "get": {
+          "tags": [
+            "Activations"
+          ],
+          "summary": "Get the logs for an activation",
+          "description": "Get activation logs information.",
+          "operationId": "getActivationLogs",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "activationid",
+              "in": "path",
+              "description": "Name of activation to fetch",
+              "required": true,
+              "type": "string"
+            }
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Return output",
+              "schema": {
+                "$ref": "#/definitions/ActivationLogs"
+              }
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        }
+      },
+      "/namespaces/{namespace}/activations/{activationid}/result": {
+        "get": {
+          "tags": [
+            "Activations"
+          ],
+          "summary": "Get the result of an activation",
+          "description": "Get activation result.",
+          "operationId": "getActivationResult",
+          "parameters": [
+            {
+              "name": "namespace",
+              "in": "path",
+              "description": "The entity namespace",
+              "required": true,
+              "type": "string"
+            },
+            {
+              "name": "activationid",
+              "in": "path",
+              "description": "Name of activation to fetch",
+              "required": true,
+              "type": "string"
+            }
+          ],
+          "produces": [
+            "application/json"
+          ],
+          "responses": {
+            "200": {
+              "description": "Return output",
+              "schema": {
+                "$ref": "#/definitions/ActivationResult"
+              }
+            },
+            "401": {
+              "$ref": "#/responses/UnauthorizedRequest"
+            },
+            "404": {
+              "$ref": "#/responses/ItemNotFound"
+            },
+            "500": {
+              "$ref": "#/responses/ServerError"
+            }
+          }
+        }
+      }
+    },
+    "definitions": {
+      "KeyValue": {
+        "properties": {
+          "key": {
+            "type": "string"
+          },
+          "value": {
+            "description": "Any JSON value"
+          }
+        }
+      },
+      "ItemId": {
+        "required": [
+          "id"
+        ],
+        "properties": {
+          "id": {
+            "type": "string"
+          }
+        }
+      },
+      "PathName": {
+        "required": [
+          "path",
+          "name"
+        ],
+        "properties": {
+          "path": {
+            "type": "string"
+          },
+          "name": {
+            "type": "string"
+          }
+        }
+      },
+      "ErrorMessage": {
+        "required": [
+          "error"
+        ],
+        "properties": {
+          "error": {
+            "type": "string"
+          },
+          "code": {
+            "type": "string"
+          }
+        }
+      },
+      "ActionLimits": {
+        "description": "Limits on a specific action",
+        "properties": {
+          "timeout": {
+            "type": "integer",
+            "format": "int32",
+            "description": "timeout in milliseconds",
+            "default": 60000
+          },
+          "memory": {
+            "type": "integer",
+            "format": "int32",
+            "description": "memory in megabytes",
+            "default": 256
+          },
+          "logs": {
+            "type": "integer",
+            "format": "int32",
+            "description": "log size in megabytes",
+            "default": 10
+          },
+          "concurrency": {
+            "type": "integer",
+            "format": "int32",
+            "description": "number of concurrent activations allowed",
+            "default": 1
+          }
+        }
+      },
+      "EntityBrief": {
+        "required": [
+          "namespace",
+          "name",
+          "version",
+          "publish"
+        ],
+        "properties": {
+          "namespace": {
+            "type": "string",
+            "description": "Namespace of the item",
+            "minLength": 1
+          },
+          "name": {
+            "type": "string",
+            "description": "Name of the item",
+            "minLength": 1
+          },
+          "version": {
+            "type": "string",
+            "description": "Semantic version of the item",
+            "minLength": 1
+          },
+          "publish": {
+            "type": "boolean",
+            "description": "Whether to publish the item or not"
+          }
+        }
+      },
+      "Action": {
+        "required": [
+          "namespace",
+          "name",
+          "version",
+          "publish",
+          "exec",
+          "limits"
+        ],
+        "properties": {
+          "namespace": {
+            "type": "string",
+            "description": "Namespace of the item",
+            "minLength": 1
+          },
+          "name": {
+            "type": "string",
+            "description": "Name of the item",
+            "minLength": 1
+          },
+          "version": {
+            "type": "string",
+            "description": "Semantic version of the item",
+            "minLength": 1
+          },
+          "publish": {
+            "type": "boolean",
+            "description": "Whether to publish the item or not"
+          },
+          "exec": {
+            "$ref": "#/definitions/ActionExec"
+          },
+          "annotations": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "annotations on the item"
+          },
+          "parameters": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "parameter bindings included in the context passed to the action"
+          },
+          "limits": {
+            "$ref": "#/definitions/ActionLimits"
+          },
+          "updated": {
+            "type": "integer",
+            "description": "Time when the action was updated"
+          }
+        }
+      },
+      "ActionPut": {
+        "description": "A restricted Action view used when updating an Action",
+        "properties": {
+          "namespace": {
+            "type": "string",
+            "description": "Namespace of the item",
+            "minLength": 1
+          },
+          "name": {
+            "type": "string",
+            "description": "Name of the item",
+            "minLength": 1
+          },
+          "version": {
+            "type": "string",
+            "description": "Semantic version of the item",
+            "minLength": 1
+          },
+          "publish": {
+            "type": "boolean",
+            "description": "Whether to publish the item or not"
+          },
+          "exec": {
+            "$ref": "#/definitions/ActionExec"
+          },
+          "annotations": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "annotations on the item"
+          },
+          "parameters": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "parameter bindings included in the context passed to the action"
+          },
+          "limits": {
+            "$ref": "#/definitions/ActionLimits"
+          }
+        }
+      },
+      "ActionExec": {
+        "properties": {
+          "kind": {
+            "type": "string",
+            "enum": [
+              "blackbox",
+              "java:8",
+              "java:default",
+              "nodejs:6",
+              "nodejs:8",
+              "nodejs:10",
+              "nodejs:12",
+              "nodejs:default",
+              "php:7.3",
+              "php:default",
+              "python:2",
+              "python:3",
+              "python:default",
+              "ruby:2.5",
+              "ruby:default",
+              "go:1.11",
+              "go:default",
+              "sequence",
+              "swift:4.2",
+              "swift:default",
+              "dotnet:2.2",
+              "dotnet:default",
+              "ballerina:0.990",
+              "ballerina:default"
+            ],
+            "description": "the type of action"
+          },
+          "code": {
+            "type": "string",
+            "description": "The code to execute when kind is not 'blackbox'"
+          },
+          "image": {
+            "type": "string",
+            "description": "container image name when kind is 'blackbox'"
+          },
+          "main": {
+            "type": "string",
+            "description": "main entrypoint of the action code"
+          },
+          "binary": {
+            "type": "boolean",
+            "description": "Whether the action has a binary attachment or not. This attribute is ignored when creating or updating an action."
+          },
+          "components": {
+            "type": "array",
+            "description": "For sequence actions, the individual action components",
+            "items": {
+              "type": "string"
+            }
+          }
+        },
+        "description": "definition of the action, such as javascript code or the name of a container"
+      },
+      "ActionPayload": {
+        "required": [
+          "payload"
+        ],
+        "properties": {
+          "payload": {
+            "type": "string",
+            "description": "The payload to pass to the action."
+          }
+        }
+      },
+      "Rule": {
+        "required": [
+          "namespace",
+          "name",
+          "version",
+          "publish",
+          "trigger",
+          "action"
+        ],
+        "properties": {
+          "namespace": {
+            "type": "string",
+            "description": "Namespace of the item",
+            "minLength": 1
+          },
+          "name": {
+            "type": "string",
+            "description": "Name of the item",
+            "minLength": 1
+          },
+          "version": {
+            "type": "string",
+            "description": "Semantic version of the item",
+            "minLength": 1
+          },
+          "publish": {
+            "type": "boolean",
+            "description": "Whether to publish the item or not"
+          },
+          "annotations": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "annotations on the item"
+          },
+          "status": {
+            "type": "string",
+            "description": "Status of a rule",
+            "enum": [
+              "active",
+              "inactive",
+              "activating",
+              "deactivating"
+            ]
+          },
+          "trigger": {
+            "$ref": "#/definitions/PathName"
+          },
+          "action": {
+            "$ref": "#/definitions/PathName"
+          }
+        }
+      },
+      "RulePut": {
+        "description": "A restricted Rule view used when updating a Rule",
+        "properties": {
+          "name": {
+            "type": "string",
+            "description": "Name of the item",
+            "minLength": 1
+          },
+          "version": {
+            "type": "string",
+            "description": "Semantic version of the item",
+            "minLength": 1
+          },
+          "publish": {
+            "type": "boolean",
+            "description": "Whether to publish the item or not"
+          },
+          "annotations": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "annotations on the item"
+          },
+          "status": {
+            "type": "string",
+            "description": "Status of a rule",
+            "enum": [
+              "active",
+              "inactive",
+              ""
+            ]
+          },
+          "trigger": {
+            "type": "string",
+            "description": "Name of the trigger",
+            "minLength": 1
+          },
+          "action": {
+            "type": "string",
+            "description": "Name of the action",
+            "minLength": 1
+          }
+        }
+      },
+      "Trigger": {
+        "required": [
+          "namespace",
+          "name",
+          "version",
+          "publish"
+        ],
+        "properties": {
+          "namespace": {
+            "type": "string",
+            "description": "Namespace of the item",
+            "minLength": 1
+          },
+          "name": {
+            "type": "string",
+            "description": "Name of the item",
+            "minLength": 1
+          },
+          "version": {
+            "type": "string",
+            "description": "Semantic version of the item",
+            "minLength": 1
+          },
+          "publish": {
+            "type": "boolean",
+            "description": "Whether to publish the item or not"
+          },
+          "annotations": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "annotations on the item"
+          },
+          "parameters": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "parameter bindings for the trigger"
+          },
+          "limits": {
+            "$ref": "#/definitions/TriggerLimits"
+          },
+          "rules": {
+            "type": "object",
+            "description": "rules associated with the trigger"
+          },
+          "updated": {
+            "type": "integer",
+            "description": "Time when the trigger was updated"
+          }
+        }
+      },
+      "TriggerPut": {
+        "description": "A restricted Trigger view used when updating the Trigger",
+        "properties": {
+          "namespace": {
+            "type": "string",
+            "description": "Namespace of the item",
+            "minLength": 1
+          },
+          "name": {
+            "type": "string",
+            "description": "Name of the item",
+            "minLength": 1
+          },
+          "version": {
+            "type": "string",
+            "description": "Semantic version of the item",
+            "minLength": 1
+          },
+          "publish": {
+            "type": "boolean",
+            "description": "Whether to publish the item or not"
+          },
+          "annotations": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "annotations on the item"
+          },
+          "parameters": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "parameter bindings included in the context passed to the trigger"
+          },
+          "limits": {
+            "$ref": "#/definitions/TriggerLimits"
+          }
+        }
+      },
+      "TriggerPayload": {
+        "required": [
+          "payload"
+        ],
+        "properties": {
+          "payload": {
+            "type": "string",
+            "description": "The payload of the trigger event."
+          }
+        }
+      },
+      "TriggerLimits": {
+        "description": "Limits on a specific trigger",
+        "type": "object"
+      },
+      "Package": {
+        "required": [
+          "namespace",
+          "name",
+          "version",
+          "publish"
+        ],
+        "properties": {
+          "namespace": {
+            "type": "string",
+            "description": "Namespace of the item",
+            "minLength": 1
+          },
+          "name": {
+            "type": "string",
+            "description": "Name of the item",
+            "minLength": 1
+          },
+          "version": {
+            "type": "string",
+            "description": "Semantic version of the item",
+            "minLength": 1
+          },
+          "publish": {
+            "type": "boolean",
+            "description": "Whether to publish the item or not"
+          },
+          "annotations": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "annotations on the item"
+          },
+          "parameters": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "parameter for the package"
+          },
+          "binding": {
+            "$ref": "#/definitions/PackageBinding"
+          },
+          "actions": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/PackageAction"
+            },
+            "description": "Actions contained in this package"
+          },
+          "feeds": {
+            "type": "array",
+            "items": {
+              "type": "object"
+            },
+            "description": "Feeds contained in this package"
+          },
+          "updated": {
+            "type": "integer",
+            "description": "Time when the package was updated"
+          }
+        }
+      },
+      "PackagePut": {
+        "description": "A restricted Package view used when updating a Package",
+        "properties": {
+          "namespace": {
+            "type": "string",
+            "description": "Namespace of the item",
+            "minLength": 1
+          },
+          "name": {
+            "type": "string",
+            "description": "Name of the item",
+            "minLength": 1
+          },
+          "version": {
+            "type": "string",
+            "description": "Semantic version of the item",
+            "minLength": 1
+          },
+          "publish": {
+            "type": "boolean",
+            "description": "Whether to publish the item or not"
+          },
+          "annotations": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "annotations on the item"
+          },
+          "parameters": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "parameter for the package"
+          },
+          "binding": {
+            "$ref": "#/definitions/PackageBinding"
+          }
+        }
+      },
+      "PackageBinding": {
+        "properties": {
+          "namespace": {
+            "type": "string",
+            "description": "Namespace of the item"
+          },
+          "name": {
+            "type": "string",
+            "description": "Name of the item"
+          }
+        }
+      },
+      "PackageAction": {
+        "description": "A restricted Action view used when listing actions in a package",
+        "required": [
+          "name",
+          "version"
+        ],
+        "properties": {
+          "name": {
+            "type": "string",
+            "description": "Name of the item",
+            "minLength": 1
+          },
+          "version": {
+            "type": "string",
+            "description": "Semantic version of the item",
+            "minLength": 1
+          },
+          "annotations": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "annotations on the item"
+          },
+          "parameters": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "parameter bindings included in the context passed to the action"
+          }
+        }
+      },
+      "ActivationBrief": {
+        "required": [
+          "namespace",
+          "name",
+          "version",
+          "publish",
+          "activationId",
+          "start"
+        ],
+        "properties": {
+          "namespace": {
+            "type": "string",
+            "description": "Namespace of the associated item"
+          },
+          "name": {
+            "type": "string",
+            "description": "Name of the item"
+          },
+          "version": {
+            "type": "string",
+            "description": "Semantic version of the item"
+          },
+          "publish": {
+            "type": "boolean",
+            "description": "Whether to publish the item or not"
+          },
+          "annotations": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "annotations on the item"
+          },
+          "activationId": {
+            "type": "string",
+            "description": "Id of the activation"
+          },
+          "start": {
+            "type": "integer",
+            "description": "Time when the activation began"
+          },
+          "end": {
+            "type": "integer",
+            "description": "Time when the activation completed"
+          },
+          "duration": {
+            "type": "integer",
+            "description": "How long the invocation took, in millisecnods"
+          },
+          "cause": {
+            "type": "string",
+            "description": "the activation id that caused this activation"
+          },
+          "statusCode": {
+            "type": "integer",
+            "format": "int32",
+            "description": "The status code",
+            "enum": [
+              0,
+              1,
+              2
+            ]
+          }
+        }
+      },
+      "Activation": {
+        "required": [
+          "namespace",
+          "name",
+          "version",
+          "publish",
+          "subject",
+          "activationId",
+          "start",
+          "response",
+          "logs"
+        ],
+        "properties": {
+          "namespace": {
+            "type": "string",
+            "description": "Namespace of the associated item"
+          },
+          "name": {
+            "type": "string",
+            "description": "Name of the item"
+          },
+          "version": {
+            "type": "string",
+            "description": "Semantic version of the item"
+          },
+          "publish": {
+            "type": "boolean",
+            "description": "Whether to publish the item or not"
+          },
+          "annotations": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "annotations on the item"
+          },
+          "subject": {
+            "type": "string",
+            "description": "The subject that activated the item"
+          },
+          "activationId": {
+            "type": "string",
+            "description": "Id of the activation"
+          },
+          "start": {
+            "type": "integer",
+            "description": "Time when the activation began"
+          },
+          "end": {
+            "type": "integer",
+            "description": "Time when the activation completed"
+          },
+          "duration": {
+            "type": "integer",
+            "description": "How long the invocation took, in millisecnods"
+          },
+          "response": {
+            "$ref": "#/definitions/ActivationResult"
+          },
+          "logs": {
+            "type": "array",
+            "description": "Logs generated by the activation",
+            "items": {
+              "type": "string"
+            }
+          },
+          "cause": {
+            "type": "string",
+            "description": "the activation id that caused this activation"
+          },
+          "statusCode": {
+            "type": "integer",
+            "format": "int32",
+            "description": "The status code",
+            "enum": [
+              0,
+              1,
+              2
+            ]
+          }
+        }
+      },
+      "ActivationId": {
+        "required": [
+          "activationId"
+        ],
+        "properties": {
+          "activationId": {
+            "type": "string"
+          }
+        }
+      },
+      "ActivationIds": {
+        "properties": {
+          "ids": {
+            "type": "array",
+            "description": "Array of activation ids",
+            "items": {
+              "$ref": "#/definitions/ActivationId"
+            }
+          }
+        }
+      },
+      "ActivationInfo": {
+        "properties": {
+          "id": {
+            "type": "string",
+            "description": "Activation id",
+            "minLength": 1
+          },
+          "result": {
+            "type": "object",
+            "description": "Activation result",
+            "required": [
+              "status"
+            ],
+            "properties": {
+              "status": {
+                "type": "string"
+              }
+            }
+          },
+          "stdout": {
+            "type": "string",
+            "description": "Standard output from activation"
+          },
+          "stderr": {
+            "type": "string",
+            "description": "Standard error from activation"
+          }
+        }
+      },
+      "ActivationLogs": {
+        "properties": {
+          "logs": {
+            "type": "array",
+            "description": "Interleaved standard output and error of an activation",
+            "items": {
+              "type": "string"
+            }
+          }
+        }
+      },
+      "ActivationStderr": {
+        "properties": {
+          "stderr": {
+            "type": "string",
+            "description": "Standard error of an activation"
+          }
+        }
+      },
+      "ActivationResult": {
+        "properties": {
+          "status": {
+            "type": "string",
+            "description": "Exit status of the activation"
+          },
+          "result": {
+            "description": "The return value from the activation"
+          },
+          "success": {
+            "type": "boolean",
+            "description": "Whether the activation was successful or not"
+          }
+        }
+      },
+      "ProviderTrigger": {
+        "required": [
+          "name"
+        ],
+        "properties": {
+          "name": {
+            "type": "string",
+            "description": "Name of the trigger",
+            "minLength": 1
+          }
+        }
+      },
+      "ProviderAction": {
+        "required": [
+          "name"
+        ],
+        "properties": {
+          "name": {
+            "type": "string",
+            "description": "Name of the action",
+            "minLength": 1
+          }
+        }
+      },
+      "ProviderBinding": {
+        "required": [
+          "name"
+        ],
+        "properties": {
+          "name": {
+            "type": "string",
+            "description": "Name of the binding",
+            "minLength": 1
+          }
+        }
+      },
+      "Provider": {
+        "required": [
+          "name"
+        ],
+        "properties": {
+          "name": {
+            "type": "string",
+            "description": "Name of the provider",
+            "minLength": 1
+          },
+          "publish": {
+            "type": "boolean",
+            "description": "Whether to publish the provider or not"
+          },
+          "parameters": {
+            "type": "array",
+            "items": {
+              "$ref": "#/definitions/KeyValue"
+            },
+            "description": "parameter bindings included in the context passed to the provider"
+          }
+        }
+      }
+    },
+    "responses": {
+      "BadRequest": {
+        "description": "Bad request",
+        "schema": {
+          "$ref": "#/definitions/ErrorMessage"
+        }
+      },
+      "UnauthorizedRequest": {
+        "description": "Unauthorized request",
+        "schema": {
+          "$ref": "#/definitions/ErrorMessage"
+        }
+      },
+      "ServerError": {
+        "description": "Server error",
+        "schema": {
+          "$ref": "#/definitions/ErrorMessage"
+        }
+      },
+      "AddedItem": {
+        "description": "Added Item",
+        "schema": {
+          "$ref": "#/definitions/ItemId"
+        }
+      },
+      "UpdatedItem": {
+        "description": "Updated Item",
+        "schema": {
+          "$ref": "#/definitions/ItemId"
+        }
+      },
+      "DeletedItem": {
+        "description": "Deleted Item"
+      },
+      "ItemNotFound": {
+        "description": "Item not found",
+        "schema": {
+          "$ref": "#/definitions/ErrorMessage"
+        }
+      },
+      "Timeout": {
+        "description": "Request timed out"
+      },
+      "Conflict": {
+        "description": "Conflicting item already exists",
+        "schema": {
+          "$ref": "#/definitions/ErrorMessage"
+        }
+      },
+      "AcceptedActivation": {
+        "description": "Accepted activation request",
+        "schema": {
+          "$ref": "#/definitions/ActivationId"
+        }
+      },
+      "AcceptedRuleStateChange": {
+        "description": "Rule has been enabled or disabled"
+      },
+      "RequestEntityTooLarge": {
+        "description": "Request entity too large",
+        "schema": {
+          "$ref": "#/definitions/ErrorMessage"
+        }
+      },
+      "NoActiveRules": {
+        "description": "Trigger has no active rules"
+      },
+      "TooManyRequests": {
+        "description": "Too many requests in a given time period"
+      }
+    }
+  }
\ No newline at end of file
diff --git a/test/test.js b/test/test.js
new file mode 100644
index 0000000..c2b9ba5
--- /dev/null
+++ b/test/test.js
@@ -0,0 +1,423 @@
+/**
+ *  Copyright 2019 Adobe. All rights reserved.
+ *
+ *  This file is licensed to you 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 REPRESENTATIONS
+ *  OF ANY KIND, either express or implied. See the License for the specific language
+ *  governing permissions and limitations under the License.
+ */
+
+'use strict';
+
+const assert = require('assert');
+const nock = require('nock');
+const fs = require('fs');
+const { execSync } = require('child_process');
+const path = require('path');
+const getPort = require('get-port');
+
+const FAKE_OPENWHISK_SERVER = "https://example.com";
+const FAKE_OPENWHISK_AUTH = "c3VwZXItc2VjcmV0LWtleQ==";
+const FAKE_OPENWHISK_NAMESPACE = "test";
+
+const WSKDEBUG_BACKUP_ACTION_SUFFIX = "_wskdebug_original";
+
+let openwhisk;
+
+function isDockerInstalled() {
+    try {
+        execSync("docker info", {stdio: 'ignore'});
+    } catch (e) {
+        throw new Error("Docker not available or running on local system. These unit tests require it.")
+    }
+}
+
+async function beforeEach() {
+    process.env.WSK_CONFIG_FILE = path.join(process.cwd(), "test/wskprops");
+    // nock.recorder.rec({ enable_reqheaders_recording: true });
+    openwhisk = nock(FAKE_OPENWHISK_SERVER);
+    // openwhisk.log(console.log);
+    mockOpenwhiskSwagger(openwhisk);
+
+    // save current working dir
+    this.cwd = process.cwd();
+
+    // find free port
+    this.port = await getPort(9229);
+    console.log("[test] free port:", this.port);
+}
+
+function afterEach() {
+    delete process.env.WSK_CONFIG_FILE;
+    nock.cleanAll();
+
+    // restore working dir from beforeEach()
+    process.chdir(this.cwd);
+}
+
+function assertAllNocksInvoked() {
+    assert(
+        openwhisk.isDone(),
+        "Expected these HTTP requests: " + openwhisk.pendingMocks().join()
+    );
+}
+
+function agentRetryResponse() {
+    return {
+        response: {
+            success: false,
+            result: {
+                error: {
+                    error: "Please retry.",
+                    code: 42 // retry
+                }
+            }
+        }
+    };
+}
+
+function agentExitResponse() {
+    return {
+        response: {
+            success: false,
+            result: {
+                error: {
+                    error: "Please exit, thanks.",
+                    code: 43 // graceful exit
+                }
+            }
+        }
+    };
+}
+
+function mockAction(name, code, binary=false) {
+    // reading action without code
+    openwhisk
+        .get(`/api/v1/namespaces/${FAKE_OPENWHISK_NAMESPACE}/actions/${name}`)
+        .matchHeader("authorization", `Basic ${FAKE_OPENWHISK_AUTH}`)
+        .query({"code":"false"})
+        .reply(200, nodejsActionDescription(name, binary));
+
+    // with code
+    const action = nodejsActionDescription(name, binary);
+    action.exec.code = code;
+
+    // reading action with code
+    openwhisk
+        .get(`/api/v1/namespaces/${FAKE_OPENWHISK_NAMESPACE}/actions/${name}`)
+        .matchHeader("authorization", `Basic ${FAKE_OPENWHISK_AUTH}`)
+        .reply(200, action);
+}
+
+function expectAgent(name, code, binary=false) {
+    const backupName = name + WSKDEBUG_BACKUP_ACTION_SUFFIX;
+
+    // wskdebug creating the backup action
+    openwhisk
+        .put(`/api/v1/namespaces/${FAKE_OPENWHISK_NAMESPACE}/actions/${backupName}?overwrite=true`)
+        .matchHeader("authorization", `Basic ${FAKE_OPENWHISK_AUTH}`)
+        .reply(200, nodejsActionDescription(backupName, binary));
+
+    // wskdebug creating the backup action
+    openwhisk
+        .put(
+            `/api/v1/namespaces/${FAKE_OPENWHISK_NAMESPACE}/actions/${name}?overwrite=true`,
+            body => body.annotations.some(v => v.key === "wskdebug" && v.value === true)
+        )
+        .matchHeader("authorization", `Basic ${FAKE_OPENWHISK_AUTH}`)
+        .reply(200, nodejsActionDescription(name));
+
+    // reading it later on restore
+    openwhisk
+        .get(`/api/v1/namespaces/${FAKE_OPENWHISK_NAMESPACE}/actions/${backupName}`)
+        .matchHeader("authorization", `Basic ${FAKE_OPENWHISK_AUTH}`)
+        .reply(200, Object.assign(nodejsActionDescription(backupName, binary), { exec: { code } }));
+
+    // restoring action
+    openwhisk
+        .put(
+            `/api/v1/namespaces/${FAKE_OPENWHISK_NAMESPACE}/actions/${name}?overwrite=true`,
+            body => body.exec && body.exec.code === code
+        )
+        .matchHeader("authorization", `Basic ${FAKE_OPENWHISK_AUTH}`)
+        .reply(200, nodejsActionDescription(name, binary));
+
+    // removing backup after restore
+    openwhisk
+        .delete(`/api/v1/namespaces/${FAKE_OPENWHISK_NAMESPACE}/actions/${backupName}`)
+        .matchHeader("authorization", `Basic ${FAKE_OPENWHISK_AUTH}`)
+        .reply(200);
+}
+
+function nockActivation(name, bodyFn) {
+    return openwhisk
+        .post(`/api/v1/namespaces/${FAKE_OPENWHISK_NAMESPACE}/actions/${name}`, bodyFn)
+        .query(true) // support both ?blocking=true and non blocking (no query params)
+        .matchHeader("authorization", `Basic ${FAKE_OPENWHISK_AUTH}`);
+}
+
+function mockAgentPoll(name) {
+    return nockActivation(name, body => body.$waitForActivation === true)
+        .optionally()
+        .reply(502, agentRetryResponse())
+        .persist();
+}
+
+function expectAgentInvocation(name, params, result) {
+    params = params || {};
+    const activationId = Date.now();
+    result.$activationId = activationId;
+
+    // wskdebug agent ping for new activation
+    nockActivation(name, body => body.$waitForActivation === true)
+        .reply(200, {
+            response: {
+                result: Object.assign(params, { $activationId: activationId })
+            }
+        });
+
+    // wskdebug sending result back to agent
+    nockActivation(
+        name,
+        body => {
+            assert.deepStrictEqual(body, result);
+            return true;
+        }
+    ).reply(200, {
+        response: {
+            result: {
+                message: "Completed"
+            }
+        }
+    });
+
+    // graceful shutdown for wskdebug to end test
+    nockActivation(name, body => body.$waitForActivation === true)
+        .reply(502, {
+            response: {
+                success: false,
+                result: {
+                    error: {
+                        error: "Please exit, thanks.",
+                        code: 43 // graceful exit
+                    }
+                }
+            }
+        });
+}
+
+function mockActionAndInvocation(action, code, params, expectedResult, binary=false) {
+    mockAction(action, code, binary);
+    expectAgent(action, code, binary);
+
+    expectAgentInvocation(action, params, expectedResult);
+}
+
+// --------------------------------------------< internal >---------------
+
+function nodejsActionDescription(name, binary=false) {
+    return {
+        "annotations":[
+            { "key": "exec", "value": "nodejs:10" },
+            { "key": "provide-api-key", "value": true }
+        ],
+        "exec":{
+            "kind": "nodejs:10",
+            "binary": binary
+        },
+        "limits":{
+            "concurrency": 200,
+            "logs": 10,
+            "memory": 256,
+            "timeout": 300000
+        },
+        "name": name,
+        "namespace": FAKE_OPENWHISK_NAMESPACE,
+        "parameters": [],
+        "publish": false,
+        "version": "0.0.1"
+    };
+}
+
+function mockOpenwhiskSwagger(openwhisk) {
+    // mock swagger api response
+    openwhisk
+        .get('/')
+        .optionally()
+        .matchHeader("accept", "application/json")
+        .matchHeader("authorization", `Basic ${FAKE_OPENWHISK_AUTH}`)
+        .reply(200, {
+            "api_paths": ["/api/v1"],
+            "description": "OpenWhisk",
+            "limits": {
+                "actions_per_minute":600,
+                "concurrent_actions":100,
+                "max_action_duration":3600000,
+                "max_action_logs":10485760,
+                "max_action_memory":52428800000,
+                "min_action_duration":100,
+                "min_action_logs":0,
+                "min_action_memory":134217728,
+                "sequence_length":50,
+                "triggers_per_minute":600
+            },
+            "runtimes":{
+                "nodejs": [
+                    {
+                        "kind":"nodejs:10",
+                        "attached":true,
+                        "default":true,
+                        "deprecated":false,
+                        "image":"bladerunner/adobe-action-nodejs-v10:3.0.21",
+                        "requireMain":false
+                    },{
+                        "kind":"nodejs",
+                        "attached":true,
+                        "default":false,
+                        "deprecated":true,
+                        "image":"bladerunner/adobe-action-nodejs-v10-fat:3.0.17",
+                        "requireMain":false
+                    },{
+                        "kind":"nodejs:10-fat",
+                        "attached":true,
+                        "default":false,
+                        "deprecated":true,
+                        "image":"bladerunner/adobe-action-nodejs-v10-fat:3.0.17",
+                        "requireMain":false
+                    },{
+                        "kind":"nodejs:6",
+                        "attached":true,
+                        "default":false,
+                        "deprecated":true,
+                        "image":"bladerunner/adobe-action-nodejs-v10-fat:3.0.17",
+                        "requireMain":false
+                    },{
+                        "kind":"nodejs:8",
+                        "attached":true,
+                        "default":false,
+                        "deprecated":true,
+                        "image":"bladerunner/adobe-action-nodejs-v10-fat:3.0.17",
+                        "requireMain":false
+                    }
+                ]
+            },
+            "support":{
+                "github":"https://github.com/apache/openwhisk/issues",
+                "slack":"http://slack.openwhisk.org"
+            }
+        });
+
+    openwhisk
+        .get('/api/v1')
+        .optionally()
+        .matchHeader("accept", "application/json")
+        .matchHeader("authorization", `Basic ${FAKE_OPENWHISK_AUTH}`)
+        .reply(200,{
+            "api_version":"1.0.0",
+            "api_version_path":"v1",
+            "build":"2019-11-08 - a",
+            "buildno":"v58 - runtime-prs-v59-f7774d5",
+            "description":"OpenWhisk API",
+            "swagger_paths": {
+                "api-docs":"/api-docs",
+                "ui":"/docs"
+            }
+        });
+
+    openwhisk
+        .get('/api/v1/api-docs')
+        .optionally()
+        .matchHeader("accept", "application/json")
+        .matchHeader("authorization", `Basic ${FAKE_OPENWHISK_AUTH}`)
+        .reply(200, JSON.parse(fs.readFileSync("./test/openwhisk-swagger.json")));
+}
+
+// --------------------------------------------< utils >---------------
+
+let capture;
+
+function startCaptureStdout() {
+    endCaptureStdout();
+    global.disableMochaLogFile = true;
+
+    capture = {
+        stdout: "",
+        stderr: "",
+        original: {
+            stdoutWrite: process.stdout.write,
+            stderrWrite: process.stderr.write
+        }
+    };
+    process.stdout.write = function(string) {
+        capture.stdout += string;
+    };
+    process.stderr.write = function(string) {
+        capture.stderr += string;
+    };
+}
+
+function endCaptureStdout() {
+    delete global.disableMochaLogFile;
+
+    if (capture && capture.original) {
+        process.stdout.write = capture.original.stdoutWrite;
+        process.stderr.write = capture.original.stderrWrite;
+        delete capture.original;
+    }
+    if (capture) {
+        return {
+            stdout: capture.stdout,
+            stderr: capture.stderr
+        };
+    } else {
+        return {};
+    }
+}
+
+async function sleep(millis) {
+    return new Promise(resolve => setTimeout(resolve, millis));
+}
+
+function touchFile(file) {
+    fs.utimesSync(file, Date.now(), Date.now());
+}
+
+function hasNotTimedOut(testCtx) {
+    if (!testCtx.test) {
+        return false;
+    }
+    if (testCtx.test.__deadline === undefined) {
+        testCtx.test.__deadline = Date.now() + testCtx.timeout() * 0.8;
+        return true;
+    }
+    return Date.now() < testCtx.test.__deadline;
+}
+
+// --------------------------------------------< exports >---------------
+
+module.exports = {
+    isDockerInstalled,
+    beforeEach,
+    afterEach,
+    assertAllNocksInvoked,
+    // mock
+    mockActionAndInvocation,
+    // advanced
+    mockAction,
+    expectAgent,
+    nockActivation,
+    expectAgentInvocation,
+    mockAgentPoll,
+    agentRetryResponse,
+    agentExitResponse,
+    // utils
+    startCaptureStdout,
+    endCaptureStdout,
+    sleep,
+    touchFile,
+    hasNotTimedOut
+}
\ No newline at end of file
diff --git a/test/watch.test.js b/test/watch.test.js
new file mode 100644
index 0000000..ff3f2cd
--- /dev/null
+++ b/test/watch.test.js
@@ -0,0 +1,627 @@
+/**
+ *  Copyright 2019 Adobe. All rights reserved.
+ *
+ *  This file is licensed to you 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 REPRESENTATIONS
+ *  OF ANY KIND, either express or implied. See the License for the specific language
+ *  governing permissions and limitations under the License.
+ */
+
+/* eslint-env mocha */
+
+'use strict';
+
+const Debugger = require("../src/debugger");
+
+const test = require('./test');
+const assert = require('assert');
+const tmp = require('tmp');
+const fs = require('fs');
+
+describe('source watching', function() {
+    this.timeout(30000);
+
+    before(function() {
+        test.isDockerInstalled();
+    });
+
+    beforeEach(async function() {
+        await test.beforeEach();
+    });
+
+    afterEach(function() {
+        test.afterEach();
+    });
+
+    it("should invoke action when a source file changes and -P is set", async function() {
+        const action = "myaction";
+        const code = `const main = () => ({ msg: 'WRONG' });`;
+
+        test.mockAction(action, code);
+        test.expectAgent(action, code);
+
+        let invokedAction = false;
+        test.nockActivation("myaction")
+            .reply((uri, body) => {
+                if (body.key === "invocationOnSourceModification") {
+                    // right action got invoked
+                    invokedAction = true;
+                    return [200, {}];
+                }
+                return [500, {}];
+            });
+
+        // wskdebug myaction action.js -P '{...}' -p ${test.port}
+        process.chdir("test/nodejs/plain-flat");
+        const argv = {
+            port: test.port,
+            action: "myaction",
+            sourcePath: `${process.cwd()}/action.js`,
+            invokeParams: '{ "key": "invocationOnSourceModification" }'
+        };
+
+        const dbgr = new Debugger(argv);
+        await dbgr.start();
+        // no need to run() for this test
+
+        // wait a bit
+        await test.sleep(500);
+
+        // simulate a source file change
+        test.touchFile("action.js");
+
+        // eslint-disable-next-line no-unmodified-loop-condition
+        while (!invokedAction && test.hasNotTimedOut(this)) {
+            await test.sleep(100);
+        }
+
+        await dbgr.stop();
+
+        assert.ok(invokedAction, "action was not invoked on source change");
+        test.assertAllNocksInvoked();
+    });
+
+    it("should invoke action when a source file changes and -P is set when source-path points to directory", async function() {
+        const action = "myaction";
+        const code = `const main = () => ({ msg: 'WRONG' });`;
+
+        test.mockAction(action, code);
+        test.expectAgent(action, code);
+
+        let invokedAction = false;
+        test.nockActivation("myaction")
+            .reply((uri, body) => {
+                if (body.key === "invocationOnSourceModification") {
+                    // right action got invoked
+                    invokedAction = true;
+                    return [200, {}];
+                }
+                return [500, {}];
+            });
+
+        // wskdebug myaction test/fake -P '{...}' -p ${test.port}
+        const argv = {
+            port: test.port,
+            action: "myaction",
+            sourcePath: "test/fake",
+            invokeParams: '{ "key": "invocationOnSourceModification" }',
+            // fake language/kind, manually set all required elements
+            kind: "fake",
+            image: "adobeapiplatform/adobe-action-nodejs-v10:3.0.21",
+            internalPort: 1234,
+            command: "node app.js"
+        };
+
+        const dbgr = new Debugger(argv);
+        await dbgr.start();
+        // no need to run() for this test
+
+        // wait a bit
+        await test.sleep(500);
+
+        // simulate a source file change
+        test.touchFile("test/fake/params.json");
+
+        // eslint-disable-next-line no-unmodified-loop-condition
+        while (!invokedAction && test.hasNotTimedOut(this)) {
+            await test.sleep(100);
+        }
+
+        await dbgr.stop();
+
+        assert.ok(invokedAction, "action was not invoked on source change");
+        test.assertAllNocksInvoked();
+    });
+
+    it("should not invoke action when a source file in parent dir changes and -P is set", async function() {
+        this.timeout(10000);
+        const deadline = Date.now() + this.timeout()/2;
+
+        const action = "myaction";
+        const code = `const main = () => ({ msg: 'WRONG' });`;
+
+        test.mockAction(action, code);
+        test.expectAgent(action, code);
+
+        let invokedAction = false;
+        test.nockActivation("myaction")
+            .optionally()
+            .reply((uri, body) => {
+                if (body.key === "invocationOnSourceModification") {
+                    // right action got invoked
+                    invokedAction = true;
+                    return [200, {}];
+                }
+                return [500, {}];
+            });
+
+        // wskdebug myaction action.js -P '{...}' -p ${test.port}
+        process.chdir("test/nodejs/plain-flat");
+        const argv = {
+            port: test.port,
+            action: "myaction",
+            sourcePath: `${process.cwd()}/action.js`,
+            invokeParams: '{ "key": "invocationOnSourceModification" }'
+        };
+
+        const dbgr = new Debugger(argv);
+        await dbgr.start();
+        // no need to run() for this test
+
+        // wait a bit
+        await test.sleep(500);
+
+        // simulate a source file change in (unwatched) parent directory
+        test.touchFile("../action.js");
+
+        // eslint-disable-next-line no-unmodified-loop-condition
+        while (Date.now() < deadline) {
+            await test.sleep(100);
+            if (invokedAction) {
+                // this would be wrong, but let's abort then
+                break;
+            }
+        }
+
+        await dbgr.stop();
+
+        assert.ok(!invokedAction, "action was invoked on unwatched source change");
+    });
+
+    it("should invoke action when a source file changes and -P is set to a filename", async function() {
+        const action = "myaction";
+        const code = `const main = () => ({ msg: 'WRONG' });`;
+
+        test.mockAction(action, code);
+        test.expectAgent(action, code);
+
+        let invokedAction = false;
+        test.nockActivation("myaction")
+            .reply((uri, body) => {
+                if (body.key === "invocationOnSourceModification") {
+                    // right action got invoked
+                    invokedAction = true;
+                    return [200, {}];
+                }
+                return [500, {}];
+            });
+
+        // wskdebug myaction action.js -P params.json -p ${test.port}
+        process.chdir("test/nodejs/plain-flat");
+        const argv = {
+            port: test.port,
+            action: "myaction",
+            sourcePath: `${process.cwd()}/action.js`,
+            invokeParams: 'params.json'
+        };
+
+        const dbgr = new Debugger(argv);
+        await dbgr.start();
+        // no need to run() for this test
+
+        // wait a bit
+        await test.sleep(500);
+
+        // simulate a source file change
+        test.touchFile("action.js");
+
+        // eslint-disable-next-line no-unmodified-loop-condition
+        while (!invokedAction && test.hasNotTimedOut(this)) {
+            await test.sleep(100);
+        }
+
+        await dbgr.stop();
+
+        assert.ok(invokedAction, "action was not invoked on source change");
+        test.assertAllNocksInvoked();
+    });
+
+    it("should invoke action when a source file changes and -a and -P is set", async function() {
+        const action = "myaction";
+        const code = `const main = () => ({ msg: 'WRONG' });`;
+
+        test.mockAction(action, code);
+        test.expectAgent(action, code);
+
+        // mock agent & action invocaton logic on the openwhisk side
+        let invokedAction = false;
+        let invokedWrongAction = false;
+
+        test.nockActivation("another-action")
+            .reply((uri, body) => {
+                if (body.key === "invocationOnSourceModification") {
+                    // right action got invoked
+                    invokedAction = true;
+                    return [200, {}];
+                }
+                return [500, {}];
+            });
+
+        test.nockActivation("myaction")
+            .optionally()
+            .reply(async () => {
+                invokedWrongAction = true;
+            });
+
+        // wskdebug myaction action.js -P '{...}' -a another-action -p ${test.port}
+        process.chdir("test/nodejs/plain-flat");
+        const argv = {
+            port: test.port,
+            action: "myaction",
+            sourcePath: `${process.cwd()}/action.js`,
+            invokeParams: '{ "key": "invocationOnSourceModification" }',
+            invokeAction: 'another-action'
+        };
+
+        const dbgr = new Debugger(argv);
+        await dbgr.start();
+        // no need to run() for this test
+
+        // wait a bit
+        await test.sleep(500);
+
+        // simulate a source file change
+        test.touchFile("action.js");
+
+        // eslint-disable-next-line no-unmodified-loop-condition
+        while (!invokedAction && test.hasNotTimedOut(this)) {
+            await test.sleep(100);
+        }
+
+        await dbgr.stop();
+
+        assert.ok(!invokedWrongAction, "ignored -a and incorrectly invoked the action itself");
+        assert.ok(invokedAction, "action was not invoked on source change");
+        test.assertAllNocksInvoked();
+    });
+
+    it("should invoke action when a source file changes and -a is set", async function() {
+        const action = "myaction";
+        const code = `const main = () => ({ msg: 'WRONG' });`;
+
+        test.mockAction(action, code);
+        test.expectAgent(action, code);
+
+        // mock agent & action invocaton logic on the openwhisk side
+        let invokedAction = false;
+        let invokedWrongAction = false;
+
+        test.nockActivation("another-action")
+            .reply(() => {
+                // right action got invoked
+                invokedAction = true;
+                return [200, {}];
+            });
+
+        test.nockActivation("myaction")
+            .optionally()
+            .reply(async () => {
+                invokedWrongAction = true;
+            });
+
+        // wskdebug myaction action.js -P '{...}' -a another-action -p ${test.port}
+        process.chdir("test/nodejs/plain-flat");
+        const argv = {
+            port: test.port,
+            action: "myaction",
+            sourcePath: `${process.cwd()}/action.js`,
+            invokeAction: 'another-action'
+        };
+
+        const dbgr = new Debugger(argv);
+        await dbgr.start();
+        // no need to run() for this test
+
+        // wait a bit
+        await test.sleep(500);
+
+        // simulate a source file change
+        test.touchFile("action.js");
+
+        // eslint-disable-next-line no-unmodified-loop-condition
+        while (!invokedAction && test.hasNotTimedOut(this)) {
+            await test.sleep(100);
+        }
+
+        await dbgr.stop();
+
+        assert.ok(!invokedWrongAction, "ignored -a and incorrectly invoked the action itself");
+        assert.ok(invokedAction, "action was not invoked on source change");
+        test.assertAllNocksInvoked();
+    });
+
+    it("should run shell command when a source file changes and -r is set", async function() {
+        const action = "myaction";
+        const code = `const main = () => ({ msg: 'WRONG' });`;
+
+        test.mockAction(action, code);
+        test.expectAgent(action, code);
+
+        const tmpFile = tmp.fileSync().name;
+        tmp.setGracefulCleanup();
+
+        // wskdebug myaction action.js -r 'echo ...' -p ${test.port}
+        process.chdir("test/nodejs/plain-flat");
+        const argv = {
+            port: test.port,
+            action: "myaction",
+            sourcePath: `${process.cwd()}/action.js`,
+            onChange: `echo "CORRECT" > ${tmpFile}`
+        };
+
+        const dbgr = new Debugger(argv);
+        await dbgr.start();
+        // no need to run() for this test
+
+        // wait a bit
+        await test.sleep(500);
+
+        // simulate a source file change
+        test.touchFile("action.js");
+
+        // wait for result of shell file command
+        let ranShellCommand = false;
+        while (!ranShellCommand && test.hasNotTimedOut(this)) {
+            await test.sleep(100);
+            if (fs.readFileSync(tmpFile).toString().trim() === "CORRECT") {
+                ranShellCommand = true;
+            }
+        }
+
+        await dbgr.stop();
+
+        assert.ok(ranShellCommand, "shell command was not run on source change");
+        test.assertAllNocksInvoked();
+    });
+
+    it("should run shell command when a file with a custom extension set in --watch-exts changes", async function() {
+        const action = "myaction";
+        const code = `const main = () => ({ msg: 'WRONG' });`;
+
+        test.mockAction(action, code);
+        test.expectAgent(action, code);
+
+        const tmpFile = tmp.fileSync().name;
+        tmp.setGracefulCleanup();
+
+        // wskdebug myaction action.js -r 'echo ...' -p ${test.port}
+        process.chdir("test/nodejs/watch");
+        const argv = {
+            port: test.port,
+            action: "myaction",
+            sourcePath: `src/action.js`,
+            onChange: `echo "CORRECT" > ${tmpFile}`,
+            watchExts: "xyz"
+        };
+
+        const dbgr = new Debugger(argv);
+        await dbgr.start();
+        // no need to run() for this test
+
+        // wait a bit
+        await test.sleep(500);
+
+        // simulate a source file change
+        test.touchFile("action.xyz");
+
+        // wait for result of shell file command
+        let ranShellCommand = false;
+        while (!ranShellCommand && test.hasNotTimedOut(this)) {
+            await test.sleep(100);
+            if (fs.readFileSync(tmpFile).toString().trim() === "CORRECT") {
+                ranShellCommand = true;
+            }
+        }
+
+        await dbgr.stop();
+
+        assert.ok(ranShellCommand, "shell command was not run on source change");
+        test.assertAllNocksInvoked();
+    });
+
+    it("should not run shell command when a file with an extension not set in --watch-exts changes", async function() {
+        this.timeout(10000);
+        const deadline = Date.now() + this.timeout()/2;
+
+        const action = "myaction";
+        const code = `const main = () => ({ msg: 'WRONG' });`;
+
+        test.mockAction(action, code);
+        test.expectAgent(action, code);
+
+        const tmpFile = tmp.fileSync().name;
+        console.log(tmpFile);
+        tmp.setGracefulCleanup();
+
+        // wskdebug myaction action.js -r 'echo ...' -p ${test.port}
+        process.chdir("test/nodejs/watch");
+        const argv = {
+            verbose: true,
+            port: test.port,
+            action: "myaction",
+            sourcePath: `src/action.js`,
+            onChange: `echo "CORRECT" > ${tmpFile}`,
+            watchExts: "xyz"
+        };
+
+        const dbgr = new Debugger(argv);
+        await dbgr.start();
+        // no need to run() for this test
+
+        // wait a bit
+        await test.sleep(500);
+
+        // simulate a source file change but != xyz extension set above
+        test.touchFile("src/action.js");
+
+        // wait for result of shell file command
+        let ranShellCommand = false;
+        while (Date.now() < deadline) {
+            await test.sleep(100);
+            if (fs.readFileSync(tmpFile).toString().trim() === "CORRECT") {
+                ranShellCommand = true;
+            }
+        }
+
+        await dbgr.stop();
+
+        assert.ok(!ranShellCommand, "shell command was incorrectly run on source change");
+        test.assertAllNocksInvoked();
+    });
+
+    it("should trigger when a source file inside --watch dir is changed", async function() {
+        const action = "myaction";
+        const code = `const main = () => ({ msg: 'WRONG' });`;
+
+        test.mockAction(action, code);
+        test.expectAgent(action, code);
+
+        const tmpFile = tmp.fileSync().name;
+        tmp.setGracefulCleanup();
+
+        // wskdebug myaction action.js -r 'echo ...' -p ${test.port}
+        process.chdir("test/nodejs/watch");
+        const argv = {
+            port: test.port,
+            action: "myaction",
+            sourcePath: `src/action.js`,
+            onChange: `echo "CORRECT" > ${tmpFile}`,
+            watch: "src"
+        };
+
+        const dbgr = new Debugger(argv);
+        await dbgr.start();
+        // no need to run() for this test
+
+        // wait a bit
+        await test.sleep(500);
+
+        // simulate a source file change
+        test.touchFile("src/action.js");
+
+        // eslint-disable-next-line no-unmodified-loop-condition
+        let ranShellCommand = false;
+        while (!ranShellCommand && test.hasNotTimedOut(this)) {
+            await test.sleep(100);
+            if (fs.readFileSync(tmpFile).toString().trim() === "CORRECT") {
+                ranShellCommand = true;
+            }
+        }
+
+        await dbgr.stop();
+
+        assert.ok(ranShellCommand, "shell command was not run on source change");
+        test.assertAllNocksInvoked();
+    });
+
+    it("should not trigger when a source file outside --watch dir is changed", async function() {
+        this.timeout(10000);
+        const deadline = Date.now() + this.timeout()/2;
+
+        const action = "myaction";
+        const code = `const main = () => ({ msg: 'WRONG' });`;
+
+        test.mockAction(action, code);
+        test.expectAgent(action, code);
+
+        const tmpFile = tmp.fileSync().name;
+        tmp.setGracefulCleanup();
+
+        // wskdebug myaction action.js -r 'echo ...' -p ${test.port}
+        process.chdir("test/nodejs/watch");
+        const argv = {
+            port: test.port,
+            action: "myaction",
+            sourcePath: `src/action.js`,
+            onChange: `echo "CORRECT" > ${tmpFile}`,
+            watch: "src"
+        };
+
+        const dbgr = new Debugger(argv);
+        await dbgr.start();
+        // no need to run() for this test
+
+        // wait a bit
+        await test.sleep(500);
+
+        // simulate a source file change
+        test.touchFile("dummy.js");
+
+        // eslint-disable-next-line no-unmodified-loop-condition
+        let ranShellCommand = false;
+        while (Date.now() < deadline) {
+            await test.sleep(100);
+            if (fs.readFileSync(tmpFile).toString().trim() === "CORRECT") {
+                ranShellCommand = true;
+                // this would be wrong, but let's abort then
+                break;
+            }
+        }
+
+        await dbgr.stop();
+
+        assert.ok(!ranShellCommand, "shell command was run on unwatched source change");
+    });
+
+    it("should run shell command on start when --on-start is set", async function() {
+        const action = "myaction";
+        const code = `const main = () => ({ msg: 'WRONG' });`;
+
+        test.mockAction(action, code);
+        test.expectAgent(action, code);
+
+        const tmpFile = tmp.fileSync().name;
+        tmp.setGracefulCleanup();
+
+        // wskdebug myaction -r 'echo ...' -p ${test.port}
+        process.chdir("test/nodejs/plain-flat");
+        const argv = {
+            port: test.port,
+            action: "myaction",
+            onStart: `echo "CORRECT" > ${tmpFile}`
+        };
+
+        const dbgr = new Debugger(argv);
+        await dbgr.start();
+        // no need to run() for this test
+
+        // wait for result of shell file command
+        let ranShellCommand = false;
+        while (!ranShellCommand && test.hasNotTimedOut(this)) {
+            await test.sleep(100);
+            if (fs.readFileSync(tmpFile).toString().trim() === "CORRECT") {
+                ranShellCommand = true;
+            }
+        }
+
+        await dbgr.stop();
+
+        assert.ok(ranShellCommand, "shell command was not run on start");
+        test.assertAllNocksInvoked();
+    });
+
+});
\ No newline at end of file
diff --git a/test/wskprops b/test/wskprops
new file mode 100644
index 0000000..9ec0359
--- /dev/null
+++ b/test/wskprops
@@ -0,0 +1,3 @@
+APIHOST=https://example.com
+NAMESPACE=test
+AUTH=super-secret-key
\ No newline at end of file